Compare commits

...

197 Commits
v1.3.0 ... main

Author SHA1 Message Date
dependabot[bot]
6e7fb60738 build(deps): bump golang.org/x/sys in the golang group
Bumps the golang group with 1 update: [golang.org/x/sys](https://github.com/golang/sys).


Updates `golang.org/x/sys` from 0.28.0 to 0.29.0
- [Commits](https://github.com/golang/sys/compare/v0.28.0...v0.29.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-04 14:56:44 +01:00
Or Mergi
7c122fabb4 bridge: Add option to enable port isolation
Enable bridge CNI plugin setting port-isolation [1] the interface.
When port-isolation is enabled, containers connected to the network
cannot communicate with each other over the linux-bridge.
Communication will be enable depending on the gateway appliance according
to its restrictions / policies.

For example: in a scenario the env connected to smart switch, enabling
port-isolation ensure traffic will go outbound, allowing the
smart-switch routing the traffic according to policies.

Add "portIsolation" flag to bridge plugin.
When true, configure the node interface with port-isolation [1].
Default is false.

[1] https://man7.org/linux/man-pages/man8/bridge.8.html (see "isolated" option)

Signed-off-by: Or Mergi <ormergi@redhat.com>
2025-01-29 16:10:47 +01:00
Casey Callendrello
e4ca66b414 build: split CI and go.mod version
Downstream users would like to lower the minimum required go version,
but it would be nice to test and release with the latest go. So, use a
placeholder go version file for CI.

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2025-01-21 13:36:19 +01:00
Tomofumi Hayashi
abfac4a938
Remove scripts/release.sh because of no longer used (#1137)
scripts/release.sh is used for release plugins manually (by
maintainer's hand), previously. Now we introduced automated release
process by github action, hence it is no longer used and no longer
maintained. This change removes this file. Thanks, release.sh for a
long time!

Signed-off-by: Tomofumi Hayashi <tohayash@redhat.com>
2025-01-15 09:51:48 +09:00
dependabot[bot]
eded0afca8 build(deps): bump the golang group across 1 directory with 3 updates
Bumps the golang group with 1 update in the / directory: [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo).


Updates `github.com/onsi/ginkgo/v2` from 2.22.0 to 2.22.2
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.22.0...v2.22.2)

Updates `github.com/onsi/gomega` from 1.36.0 to 1.36.2
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.36.0...v1.36.2)

Updates `golang.org/x/sys` from 0.27.0 to 0.28.0
- [Commits](https://github.com/golang/sys/compare/v0.27.0...v0.28.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: golang
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: golang
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-14 22:02:20 +01:00
dependabot[bot]
41d548592d build(deps): bump alpine in /.github/actions/retest-action
Bumps alpine from 3.20 to 3.21.

---
updated-dependencies:
- dependency-name: alpine
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-14 17:50:13 +01:00
Casey Callendrello
e8c7d9b930 test: enable unpriv user namespaces
These are disabled by default in some distros; we would like to test
rootless, however.

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2025-01-14 17:49:22 +01:00
jingyuanliang
ba8bc7d0c7
Bump Go version to 1.23 in scripts/release.sh (#1123)
Signed-off-by: Jingyuan Liang <jingyuanliang@google.com>
2025-01-07 01:39:11 +09:00
Etienne Champetier
7f756b411e portmap: fix iptables conditions detection
As show in the docs, iptables conditions can also start with '!'

Fixes 01a94e17c77e6ff8e5019e15c42d8d92cf87194f

Signed-off-by: Etienne Champetier <e.champetier@ateme.com>
2024-12-02 17:06:11 +01:00
dependabot[bot]
3ffc42cdfd build(deps): bump the golang group across 1 directory with 7 updates
Bumps the golang group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [github.com/Microsoft/hcsshim](https://github.com/Microsoft/hcsshim) | `0.12.7` | `0.12.9` |
| [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) | `2.20.2` | `2.22.0` |
| [github.com/onsi/gomega](https://github.com/onsi/gomega) | `1.34.2` | `1.36.0` |
| [github.com/opencontainers/selinux](https://github.com/opencontainers/selinux) | `1.11.0` | `1.11.1` |
| [github.com/safchain/ethtool](https://github.com/safchain/ethtool) | `0.4.1` | `0.5.9` |
| [sigs.k8s.io/knftables](https://github.com/kubernetes-sigs/knftables) | `0.0.17` | `0.0.18` |



Updates `github.com/Microsoft/hcsshim` from 0.12.7 to 0.12.9
- [Release notes](https://github.com/Microsoft/hcsshim/releases)
- [Commits](https://github.com/Microsoft/hcsshim/compare/v0.12.7...v0.12.9)

Updates `github.com/onsi/ginkgo/v2` from 2.20.2 to 2.22.0
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.20.2...v2.22.0)

Updates `github.com/onsi/gomega` from 1.34.2 to 1.36.0
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.34.2...v1.36.0)

Updates `github.com/opencontainers/selinux` from 1.11.0 to 1.11.1
- [Release notes](https://github.com/opencontainers/selinux/releases)
- [Commits](https://github.com/opencontainers/selinux/compare/v1.11.0...v1.11.1)

Updates `github.com/safchain/ethtool` from 0.4.1 to 0.5.9
- [Release notes](https://github.com/safchain/ethtool/releases)
- [Commits](https://github.com/safchain/ethtool/compare/v0.4.1...v0.5.9)

Updates `golang.org/x/sys` from 0.26.0 to 0.27.0
- [Commits](https://github.com/golang/sys/compare/v0.26.0...v0.27.0)

Updates `sigs.k8s.io/knftables` from 0.0.17 to 0.0.18
- [Changelog](https://github.com/kubernetes-sigs/knftables/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kubernetes-sigs/knftables/compare/v0.0.17...v0.0.18)

---
updated-dependencies:
- dependency-name: github.com/Microsoft/hcsshim
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: golang
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
- dependency-name: github.com/opencontainers/selinux
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: golang
- dependency-name: github.com/safchain/ethtool
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
- dependency-name: sigs.k8s.io/knftables
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: golang
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 17:04:12 +01:00
Etienne Champetier
6de8a9853c ipmasq: fix nftables backend
Rename
SetupIPMasqForNetwork -> SetupIPMasqForNetworks
TeardownIPMasqForNetwork -> TeardownIPMasqForNetworks
and have them take []*net.IPNet instead of *net.IPNet.

This allow the nftables backend to cleanup stale rules and recreate all
needed rules in a single transaction, where previously the stale rules
cleanup was breaking all but the last IPNet.

Fixes 61d078645a6d2a2391a1555ecda3d0a080a45831

Signed-off-by: Etienne Champetier <e.champetier@ateme.com>
2024-11-21 20:23:25 +01:00
Etienne Champetier
9296c5f80a portmap: fix nftables backend
We can't use dnat from the input hook,
depending on nftables (and kernel ?) version we get
"Error: Could not process rule: Operation not supported"
iptables backend also uses prerouting.

Also 'ip6 protocol tcp' is invalid, so rework / simplify the rules

Fixes 01a94e17c77e6ff8e5019e15c42d8d92cf87194f

Signed-off-by: Etienne Champetier <e.champetier@ateme.com>
2024-11-18 17:04:37 +01:00
Lionel Jouin
fec2d62676 Pass status along ipam update
Signed-off-by: Lionel Jouin <lionel.jouin@est.tech>
2024-10-15 10:22:10 +02:00
Songmin Li
a4fc6f93c7 feat(dhcp): Cancel backoff retry on stop
Signed-off-by: Songmin Li <lisongmin@protonmail.com>
2024-10-14 17:42:30 +02:00
Songmin Li
d61e7e5e1f fix(dhcp): can not renew an ip address
The dhcp server is systemd-networkd, and the dhcp
plugin can request an ip but can not renew it.
The systemd-networkd just ignore the renew request.

```
2024/09/14 21:46:00 no DHCP packet received within 10s
2024/09/14 21:46:00 retrying in 31.529038 seconds
2024/09/14 21:46:42 no DHCP packet received within 10s
2024/09/14 21:46:42 retrying in 63.150490 seconds
2024/09/14 21:47:45 98184616c91f15419f5cacd012697f85afaa2daeb5d3233e28b0ec21589fb45a/iot/eth1: no more tries
2024/09/14 21:47:45 98184616c91f15419f5cacd012697f85afaa2daeb5d3233e28b0ec21589fb45a/iot/eth1: renewal time expired, rebinding
2024/09/14 21:47:45 Link "eth1" down. Attempting to set up
2024/09/14 21:47:45 98184616c91f15419f5cacd012697f85afaa2daeb5d3233e28b0ec21589fb45a/iot/eth1: lease rebound, expiration is 2024-09-14 22:47:45.309270751 +0800 CST m=+11730.048516519
```

Follow the https://datatracker.ietf.org/doc/html/rfc2131#section-4.3.6,
following options must not be sent in renew

- Requested IP Address
- Server Identifier

Since the upstream code has been inactive for 6 years,
we should switch to another dhcpv4 library.
The new selected one is https://github.com/insomniacslk/dhcp.

Signed-off-by: Songmin Li <lisongmin@protonmail.com>
2024-10-14 17:42:30 +02:00
dependabot[bot]
e4950728ce build(deps): bump golang.org/x/sys in the golang group
Bumps the golang group with 1 update: [golang.org/x/sys](https://github.com/golang/sys).


Updates `golang.org/x/sys` from 0.25.0 to 0.26.0
- [Commits](https://github.com/golang/sys/compare/v0.25.0...v0.26.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-14 11:49:45 +02:00
Lionel Jouin
93d197c455 VRF: Wait for the local/host routes to be added
Without waiting for the local/host routes to be added
by the kernel after the IP address is being added to
an interface. The routes requiring the local/host routes
may failed. This caused flaky e2e tests, but could also
happen during the execution of the VRF plugin when the
IPv6 addresses were being re-added to the interface and
when the route were being moved to the VRF table.

Signed-off-by: Lionel Jouin <lionel.jouin@est.tech>
2024-10-14 11:49:25 +02:00
h0nIg
c52e02bccf add problem hint
Signed-off-by: h0nIg <h0nIg@users.noreply.github.com>
2024-10-14 11:47:24 +02:00
h0nIg
24b0bf96af make test working again
Signed-off-by: h0nIg <h0nIg@users.noreply.github.com>
2024-10-14 11:47:24 +02:00
h0nIg
d44bbf28af Revert "Merge pull request #921 from oOraph/dev/exclude_subnets_from_traffic_shapping2"
This reverts commit ef076afac1af0b9a8446f72e3343666567bc04dc, reversing
changes made to 597408952e3e7247fb0deef26a3a935c405aa0cf.

Signed-off-by: h0nIg <h0nIg@users.noreply.github.com>
2024-10-14 11:47:24 +02:00
h0nIg
8ad0361964 resolve merge conflicts
Signed-off-by: h0nIg <h0nIg@users.noreply.github.com>
2024-10-14 11:47:24 +02:00
dependabot[bot]
8324a2e5a4 build(deps): bump the golang group across 1 directory with 2 updates
Bumps the golang group with 1 update in the / directory: [github.com/Microsoft/hcsshim](https://github.com/Microsoft/hcsshim).


Updates `github.com/Microsoft/hcsshim` from 0.12.6 to 0.12.7
- [Release notes](https://github.com/Microsoft/hcsshim/releases)
- [Commits](https://github.com/Microsoft/hcsshim/compare/v0.12.6...v0.12.7)

Updates `golang.org/x/sys` from 0.24.0 to 0.25.0
- [Commits](https://github.com/golang/sys/compare/v0.24.0...v0.25.0)

---
updated-dependencies:
- dependency-name: github.com/Microsoft/hcsshim
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: golang
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-07 04:57:38 +02:00
Etienne Champetier
a4b80cc634 host-device: use temp network namespace for rename
Using a temporary name / doing a fast rename causes
some race conditions with udev and NetworkManager:
https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/issues/1599

Signed-off-by: Etienne Champetier <e.champetier@ateme.com>
2024-10-02 10:30:27 +02:00
Gudmundur Bjarni Olafsson
3a49cff1f6 Fix txqueuelen being accidentally set to zero
TxQLen was unintentionally set to 0 due to a struct literal.

Signed-off-by: Gudmundur Bjarni Olafsson <gudmundur.bjarni@gmail.com>
2024-10-02 10:01:11 +02:00
Lionel Jouin
c11ed48733 Ignore link-local routes in SBR tests
The tests were flaky due to a route with the link-local IP being
automatically added after the test run saves the initial state
(routes before SBR plugin is ran). When the SBR plugin is ran,
the new state is compared with the old state. The new state will
then contain the route with the link-local IP (that has been
added after saving the old state), the old state was not
containing it, so the tests were failing

The solution here is to ignore routes with the link-local IP
for the tests.

fixes: #1096

Signed-off-by: Lionel Jouin <lionel.jouin@est.tech>
2024-10-01 00:36:30 +02:00
dependabot[bot]
fa737f82b2 build(deps): bump the golang group with 3 updates
Bumps the golang group with 3 updates: [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo), [github.com/onsi/gomega](https://github.com/onsi/gomega) and [golang.org/x/sys](https://github.com/golang/sys).


Updates `github.com/onsi/ginkgo/v2` from 2.20.1 to 2.20.2
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.20.1...v2.20.2)

Updates `github.com/onsi/gomega` from 1.34.1 to 1.34.2
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.34.1...v1.34.2)

Updates `golang.org/x/sys` from 0.23.0 to 0.24.0
- [Commits](https://github.com/golang/sys/compare/v0.23.0...v0.24.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: golang
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: golang
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-17 13:14:06 +02:00
Casey Callendrello
e5df283ab3
ci, go.mod: bump to go 1.23 (#1094)
* ci, go.mod: bump to go 1.23

Now that go.mod matches our go version, we can stop setting go version
in CI separately.

Signed-off-by: Casey Callendrello <c1@caseyc.net>

* minor: fix lint errors

Bumping golangci-lint to v1.61 introduced some new reasonable checks;
fix the errors they found.

Signed-off-by: Casey Callendrello <c1@caseyc.net>

* ci: bump golangci-lint to v1.61.0

Also, fix some deprecated config directives.

Signed-off-by: Casey Callendrello <c1@caseyc.net>

---------

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2024-09-17 12:28:55 +02:00
Songmin Li
cc8b1bd80c dhcp: Add priority option to dhcp.
Currently, we can not set the metric of routes in dhcp.
It's ok if there is only a network interface.

But if there are multiple network interfaces, and both have a default route,
We need to set the metric of the route to make the traffic
go through the correct network interface.

For host-local and static, we can set the metric with the route.priority option.
But there is no such option for dhcp.

Signed-off-by: Songmin Li <lisongmin@protonmail.com>
2024-09-17 11:47:37 +02:00
Casey Callendrello
03712a572b .github: add check to verify vendor directory
Make sure we don't slip any changes in there accidentally.

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2024-09-16 22:12:54 +02:00
Dan Winship
01a94e17c7 Add nftables backend to portmap
Signed-off-by: Dan Winship <danwinship@redhat.com>
2024-09-16 21:17:49 +02:00
Dan Winship
3d1968c152 Fix portmap unit tests
Use `conditionsV4` and `conditionsV6` values that actually look like
valid iptables conditions.

Signed-off-by: Dan Winship <danwinship@redhat.com>
2024-09-16 21:17:49 +02:00
Dan Winship
a3ccebc6ec Add a backend abstraction to the portmap plugin
Signed-off-by: Dan Winship <danwinship@redhat.com>
2024-09-16 21:17:49 +02:00
Dan Winship
61d078645a Add nftables implementation of ipmasq
Signed-off-by: Dan Winship <danwinship@redhat.com>
2024-09-16 21:17:49 +02:00
Dan Winship
729dd23c40 Vendor nftables library, add utils.SupportsIPTables and utils.SupportsNFTables
Signed-off-by: Dan Winship <danwinship@redhat.com>
2024-09-16 21:17:49 +02:00
Lionel Jouin
a6d6efa5ca Use of Scope for routes in IPAM
Add Scope for routes for cni spec v1.1

Signed-off-by: Lionel Jouin <lionel.jouin@est.tech>
2024-09-16 17:06:21 +02:00
Lionel Jouin
01b3db8e01
SBR: option to pass the table id (#1088)
* Use of Table ID in IPAM

Signed-off-by: Lionel Jouin <lionel.jouin@est.tech>

* SBR: option to pass the table id

Using the option to set the table number in the SBR meta plugin will
create a policy route for each IP added for the interface returned by
the main plugin.
Unlike the default behavior, the routes will not be moved to the table.
The default behavior of the SBR plugin is kept if the table id is not set.

Signed-off-by: Lionel Jouin <lionel.jouin@est.tech>

---------

Signed-off-by: Lionel Jouin <lionel.jouin@est.tech>
2024-09-09 17:07:23 +02:00
Casey Callendrello
20f31e5e88
Merge pull request #1083 from danwinship/update-deps
update deps (go-iptables, cni)
2024-08-29 12:28:03 +02:00
Dan Winship
06ba001d84 Update containernetworking/cni to v1.2.3 for GC
Signed-off-by: Dan Winship <danwinship@redhat.com>
2024-08-28 12:17:48 -04:00
Dan Winship
deb8ef63f4 Update go-iptables
Signed-off-by: Dan Winship <danwinship@redhat.com>
2024-08-28 08:59:45 -04:00
Casey Callendrello
720b1e9811
Merge pull request #1074 from champtar/macvlan-bcqueuelen
macvlan: add bcqueuelen setting
2024-08-27 17:01:05 +02:00
Etienne Champetier
bdb6814fe2 macvlan: add bcqueuelen setting
This setting was introduced in Linux 5.11
d4bff72c84
42f5642a40

Signed-off-by: Etienne Champetier <e.champetier@ateme.com>
2024-08-27 09:21:29 -04:00
Casey Callendrello
3653221fad
Merge pull request #1076 from lisongmin/main
Fix unnecessary retrying when the link is down in dhcp
2024-08-27 10:31:03 +02:00
Casey Callendrello
0d2780f0e7
Merge branch 'main' into main 2024-08-27 10:20:16 +02:00
Casey Callendrello
5def33291f
Merge pull request #1081 from containernetworking/dependabot/go_modules/golang-9c8d4662b4
build(deps): bump the golang group across 1 directory with 4 updates
2024-08-27 10:18:17 +02:00
dependabot[bot]
07bd325095
build(deps): bump the golang group across 1 directory with 4 updates
Bumps the golang group with 2 updates in the / directory: [github.com/Microsoft/hcsshim](https://github.com/Microsoft/hcsshim) and [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo).


Updates `github.com/Microsoft/hcsshim` from 0.12.4 to 0.12.6
- [Release notes](https://github.com/Microsoft/hcsshim/releases)
- [Commits](https://github.com/Microsoft/hcsshim/compare/v0.12.4...v0.12.6)

Updates `github.com/onsi/ginkgo/v2` from 2.19.0 to 2.20.1
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.19.0...v2.20.1)

Updates `github.com/onsi/gomega` from 1.33.1 to 1.34.1
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.33.1...v1.34.1)

Updates `golang.org/x/sys` from 0.21.0 to 0.23.0
- [Commits](https://github.com/golang/sys/compare/v0.21.0...v0.23.0)

---
updated-dependencies:
- dependency-name: github.com/Microsoft/hcsshim
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: golang
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-27 07:43:01 +00:00
Casey Callendrello
7cff5db82a
Merge pull request #1080 from champtar/update-netlink
build: update github.com/vishvananda/netlink to 1.3.0
2024-08-27 09:41:58 +02:00
Etienne Champetier
d924f05e12 build: update github.com/vishvananda/netlink to 1.3.0
This includes a breaking change:
acdc658b86
route.Dst is now a zero IPNet instead of nil

Signed-off-by: Etienne Champetier <e.champetier@ateme.com>
2024-08-26 14:27:30 -04:00
Songmin Li
6269f399a5
Fix unnecessary retrying when the link is down in dhcp.
From the dhcp daemon log, we can see that dhcp will fail to acquire
the lease when the link is down, and success on retry.

```
2024/08/21 21:30:44 macvlan-dhcp/eth1: acquiring lease
2024/08/21 21:30:44 Link "eth1" down. Attempting to set up
2024/08/21 21:30:44 network is down
2024/08/21 21:30:44 retrying in 2.641696 seconds
2024/08/21 21:30:49 macvlan-dhcp/eth1: lease acquired, expiration is 2024-08-22 09:30:49.755367962 +0800 CST m=+43205.712107889
```

After move the code of set up link to the beginning of the function, the
dhcp success on first time.

```
2024/08/21 22:04:02 macvlan-dhcp/eth1: acquiring lease
2024/08/21 22:04:02 Link "eth1" down. Attempting to set up
2024/08/21 22:04:05 macvlan-dhcp/eth1: lease acquired, expiration is 2024-08-22 10:04:05.297887726 +0800 CST m=+43203.081141304
```

Signed-off-by: Songmin Li <lisongmin@protonmail.com>
2024-08-24 19:54:34 +08:00
Mike Zappa
5188dc8a19
Merge pull request #1065 from squeed/bump-go
.github: bump go
2024-08-22 16:11:01 -06:00
Casey Callendrello
675ca92261 test: bump go version
We were using the go.mod version, which we don't change as frequently.
Switch to use the GO_VERSION defined in the workflow file.

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2024-07-15 16:58:48 +02:00
Casey Callendrello
30078e1cfd .github: fix double-triggering CI
We were accidentally running CI twice.

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2024-07-15 16:53:23 +02:00
Casey Callendrello
acf8ddc8e1
Merge pull request #1058 from s1061123/fix/1053b
Change chown to change current dir as well
2024-06-24 17:20:49 +02:00
Tomofumi Hayashi
352c8b7ab5 Change chown to change current dir as well
Signed-off-by: Tomofumi Hayashi <tohayash@redhat.com>
2024-06-24 16:57:53 +02:00
Casey Callendrello
11ad58cf20
Merge pull request #1060 from containernetworking/dependabot/go_modules/golang-6263b2479d
build(deps): bump the golang group with 2 updates
2024-06-24 12:11:17 +02:00
dependabot[bot]
d5f9ad99d7
build(deps): bump the golang group with 2 updates
Bumps the golang group with 2 updates: [github.com/Microsoft/hcsshim](https://github.com/Microsoft/hcsshim) and [github.com/safchain/ethtool](https://github.com/safchain/ethtool).


Updates `github.com/Microsoft/hcsshim` from 0.12.3 to 0.12.4
- [Release notes](https://github.com/Microsoft/hcsshim/releases)
- [Commits](https://github.com/Microsoft/hcsshim/compare/v0.12.3...v0.12.4)

Updates `github.com/safchain/ethtool` from 0.4.0 to 0.4.1
- [Release notes](https://github.com/safchain/ethtool/releases)
- [Commits](https://github.com/safchain/ethtool/compare/v0.4.0...v0.4.1)

---
updated-dependencies:
- dependency-name: github.com/Microsoft/hcsshim
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: golang
- dependency-name: github.com/safchain/ethtool
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: golang
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-24 02:12:28 +00:00
Casey Callendrello
c29dc79f96
Merge pull request #1056 from containernetworking/dependabot/go_modules/golang-82cdb19fbc
build(deps): bump the golang group across 1 directory with 3 updates
2024-06-17 17:46:35 +02:00
dependabot[bot]
508c94caec
build(deps): bump the golang group across 1 directory with 3 updates
Bumps the golang group with 2 updates in the / directory: [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) and [github.com/safchain/ethtool](https://github.com/safchain/ethtool).


Updates `github.com/onsi/ginkgo/v2` from 2.17.3 to 2.19.0
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.17.3...v2.19.0)

Updates `github.com/safchain/ethtool` from 0.3.0 to 0.4.0
- [Release notes](https://github.com/safchain/ethtool/releases)
- [Commits](https://github.com/safchain/ethtool/compare/v0.3.0...v0.4.0)

Updates `golang.org/x/sys` from 0.20.0 to 0.21.0
- [Commits](https://github.com/golang/sys/compare/v0.20.0...v0.21.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
- dependency-name: github.com/safchain/ethtool
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-17 15:23:00 +00:00
Casey Callendrello
b96f963c7a
Merge pull request #1049 from containernetworking/dependabot/docker/dot-github/actions/retest-action/alpine-3.20
build(deps): bump alpine from 3.19 to 3.20 in /.github/actions/retest-action
2024-06-17 17:06:40 +02:00
Casey Callendrello
518bc80c56
Merge pull request #1054 from s1061123/fix/1053
Fix release tar images' owner to root
2024-06-17 17:04:40 +02:00
Tomofumi Hayashi
434e9b9ef7 Fix release tar images' owner to root
Fix #1053

Signed-off-by: Tomofumi Hayashi <tohayash@redhat.com>
2024-06-17 11:06:40 +09:00
dependabot[bot]
0e87bccf19
build(deps): bump alpine in /.github/actions/retest-action
Bumps alpine from 3.19 to 3.20.

---
updated-dependencies:
- dependency-name: alpine
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-27 02:43:07 +00:00
Casey Callendrello
0259301ae2
Merge pull request #1043 from containernetworking/dependabot/go_modules/golang-efff583b38
build(deps): bump the golang group with 2 updates
2024-05-20 09:14:04 +02:00
dependabot[bot]
14cad164ec
build(deps): bump the golang group with 2 updates
Bumps the golang group with 2 updates: [github.com/onsi/gomega](https://github.com/onsi/gomega) and [golang.org/x/sys](https://github.com/golang/sys).


Updates `github.com/onsi/gomega` from 1.33.0 to 1.33.1
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.33.0...v1.33.1)

Updates `golang.org/x/sys` from 0.19.0 to 0.20.0
- [Commits](https://github.com/golang/sys/compare/v0.19.0...v0.20.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: golang
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-20 02:50:01 +00:00
dependabot[bot]
1868e4fe2d
Merge pull request #1035 from containernetworking/dependabot/github_actions/golangci/golangci-lint-action-6 2024-05-16 15:45:32 +00:00
dependabot[bot]
5c95925a71
build(deps): bump golangci/golangci-lint-action from 4 to 6
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 4 to 6.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v4...v6)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-16 15:35:37 +00:00
dependabot[bot]
505f5a7def
Merge pull request #1042 from containernetworking/dependabot/go_modules/golang-e5a06e9046 2024-05-16 13:27:36 +00:00
dependabot[bot]
dc8b8289fa
build(deps): bump the golang group across 1 directory with 4 updates
Bumps the golang group with 2 updates in the / directory: [github.com/Microsoft/hcsshim](https://github.com/Microsoft/hcsshim) and [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo).


Updates `github.com/Microsoft/hcsshim` from 0.12.2 to 0.12.3
- [Release notes](https://github.com/Microsoft/hcsshim/releases)
- [Commits](https://github.com/Microsoft/hcsshim/compare/v0.12.2...v0.12.3)

Updates `github.com/onsi/ginkgo/v2` from 2.17.1 to 2.17.3
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.17.1...v2.17.3)

Updates `github.com/onsi/gomega` from 1.32.0 to 1.33.0
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.32.0...v1.33.0)

Updates `golang.org/x/sys` from 0.18.0 to 0.19.0
- [Commits](https://github.com/golang/sys/compare/v0.18.0...v0.19.0)

---
updated-dependencies:
- dependency-name: github.com/Microsoft/hcsshim
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: golang
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: golang
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-16 13:16:06 +00:00
Tomofumi Hayashi
196ed2d00e
Merge pull request #1040 from squeed/dependabot-dont-bump-cni
dependabot: don't automatically update cni packages
2024-05-16 22:14:58 +09:00
Tomofumi Hayashi
43139b3375
Merge branch 'main' into dependabot-dont-bump-cni 2024-05-16 21:43:34 +09:00
Tomofumi Hayashi
18d129f18b
Merge pull request #1039 from squeed/bump-go-version
build: bump release go version
2024-05-16 21:42:44 +09:00
Casey Callendrello
9397270f5e dependabot: don't automatically update cni packages
We should manage their lifecycle separately.

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2024-05-16 11:25:50 +02:00
Casey Callendrello
3656c69d0f build: bump release go version
Signed-off-by: Casey Callendrello <c1@caseyc.net>
2024-05-16 11:22:33 +02:00
Casey Callendrello
3af6e6b61d
Merge pull request #1037 from s1061123/fix-buildversion
Fix release script in github action
2024-05-16 11:16:34 +02:00
Tomofumi Hayashi
427af8636f Fix release script in github action
This changes adds BuildVersion linker flag in release to
fix CNI plugins output. Fixes #1019.

Signed-off-by: Tomofumi Hayashi <tohayash@redhat.com>
2024-05-15 02:55:03 +09:00
Tomofumi Hayashi
dc704d19d6
Merge pull request #1034 from testwill/close_file
fix: close resolv.conf
2024-05-13 21:19:20 +09:00
Tomofumi Hayashi
45a24d5d95
Merge branch 'main' into close_file 2024-05-13 17:23:42 +09:00
Tomofumi Hayashi
c0533466c8
Merge pull request #1029 from samuelkarp/log-peer-veth-failure
ip: include peer name in error message
2024-05-13 17:23:30 +09:00
guangwu
ada798a3f7 fix: close resolv.conf
Signed-off-by: guoguangwu <guoguangwug@gmail.com>
2024-05-08 20:38:15 +08:00
Samuel Karp
eb49a034c4
ip: include peer name in error message
If makeVeth fails as a peer exists, the existing peer name should be
included in the error message.

Signed-off-by: Samuel Karp <samuelkarp@google.com>
2024-04-26 15:49:08 -07:00
Tomofumi Hayashi
670139cffa
Merge pull request #1027 from containernetworking/dependabot/go_modules/golang.org/x/net-0.23.0
build(deps): bump golang.org/x/net from 0.20.0 to 0.23.0
2024-04-22 14:56:13 +09:00
dependabot[bot]
717b4337b5
build(deps): bump golang.org/x/net from 0.20.0 to 0.23.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.20.0 to 0.23.0.
- [Commits](https://github.com/golang/net/compare/v0.20.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-19 13:05:48 +00:00
Tomofumi Hayashi
ef076afac1
Merge pull request #921 from oOraph/dev/exclude_subnets_from_traffic_shapping2
Dev/exclude subnets from traffic shaping2
2024-04-09 00:08:02 +09:00
Tomofumi Hayashi
ccc1cfaa58 Simplify unit test
Signed-off-by: Tomofumi Hayashi <tohayash@redhat.com>
2024-04-08 15:42:20 +09:00
Raphael
78ebd8bfb9 minor case change
even if json unmarshalling in golang with the standard libs is case unsensitive regarding the keys

Signed-off-by: Raphael <oOraph@users.noreply.github.com>
2024-04-08 15:39:47 +09:00
Raphael
c666d1400d bandwidth plugin: split unit tests in several files
Signed-off-by: Raphael <oOraph@users.noreply.github.com>
2024-04-08 15:39:47 +09:00
Raphael
ab0b386b4e bandwidth: possibility to specify shaped subnets or to exclude some from shaping
Signed-off-by: Raphael <oOraph@users.noreply.github.com>
2024-04-08 15:39:47 +09:00
Raphael
52da39d3aa bandwidth: possibility to exclude some subnets from traffic shaping
what changed:

we had to refactor the bandwidth plugin and switch from a classless qdisc (tbf)
to a classful qdisc (htb).

subnets are to be provided in config or runtimeconfig just like other parameters

unit and integration tests were also adapted in consequence

unrelated changes:

test fixes: the most important tests were just silently skipped due to ginkgo Measure deprecation
(the ones actually checking the effectiveness of the traffic control)

Signed-off-by: Raphael <oOraph@users.noreply.github.com>
2024-04-08 15:39:46 +09:00
Tomofumi Hayashi
597408952e
Merge pull request #1023 from containernetworking/dependabot/go_modules/golang-09a819dc01
build(deps): bump the golang group with 1 update
2024-04-03 20:45:59 +09:00
dependabot[bot]
6f05dc325a
build(deps): bump the golang group with 1 update
Bumps the golang group with 1 update: [github.com/Microsoft/hcsshim](https://github.com/Microsoft/hcsshim).


Updates `github.com/Microsoft/hcsshim` from 0.12.1 to 0.12.2
- [Release notes](https://github.com/Microsoft/hcsshim/releases)
- [Commits](https://github.com/Microsoft/hcsshim/compare/v0.12.1...v0.12.2)

---
updated-dependencies:
- dependency-name: github.com/Microsoft/hcsshim
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: golang
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-01 02:41:52 +00:00
Casey Callendrello
8ab3c63c2d
Merge pull request #1022 from containernetworking/dependabot/go_modules/golang-e423e5d544
build(deps): bump the golang group with 4 updates
2024-03-25 15:37:06 +01:00
dependabot[bot]
c464674317
build(deps): bump the golang group with 4 updates
Bumps the golang group with 4 updates: [github.com/Microsoft/hcsshim](https://github.com/Microsoft/hcsshim), [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo), [github.com/onsi/gomega](https://github.com/onsi/gomega) and [golang.org/x/sys](https://github.com/golang/sys).


Updates `github.com/Microsoft/hcsshim` from 0.12.0 to 0.12.1
- [Release notes](https://github.com/Microsoft/hcsshim/releases)
- [Commits](https://github.com/Microsoft/hcsshim/compare/v0.12.0...v0.12.1)

Updates `github.com/onsi/ginkgo/v2` from 2.16.0 to 2.17.1
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.16.0...v2.17.1)

Updates `github.com/onsi/gomega` from 1.31.1 to 1.32.0
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.31.1...v1.32.0)

Updates `golang.org/x/sys` from 0.17.0 to 0.18.0
- [Commits](https://github.com/golang/sys/compare/v0.17.0...v0.18.0)

---
updated-dependencies:
- dependency-name: github.com/Microsoft/hcsshim
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: golang
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-25 02:15:46 +00:00
Tomofumi Hayashi
c860b78de4
Merge pull request #1003 from adrianchiris/support-sf
Support DeviceID on Auxiliary Bus
2024-03-12 21:05:16 +09:00
Tomofumi Hayashi
9f1bf2a848
Merge branch 'main' into support-sf 2024-03-12 20:51:56 +09:00
Tomofumi Hayashi
7567d28a73
Merge pull request #1002 from adrianchiris/use-temp-name
Use temporary name for netdevice when moving in/out of NS
2024-03-12 20:51:08 +09:00
adrianc
ba5bdafe5d
Use temporary name for netdevice when moving in/out of NS
Today, it is not possible to use host-device CNI to move a
host device to container namespace if a device already exists
in that namespace.

e.g when a delegate plugin (such as multus) is used to provide
multiple networks to a container, CNI Add call will fail if
the targeted host device name already exists in container network
namespace.

to overcome this, we use a temporary name for the interface before
moving it in/out of container network namespace.

Signed-off-by: adrianc <adrianc@nvidia.com>
2024-03-12 12:25:23 +02:00
adrianc
d34720b531
Support DeviceID on Auxiliary Bus
Device plugins may allocate network device on a bus
different than PCI.

sriov-network-device-plugin supports the allocation
of network devices over Auxiliary bus[1][2][3].

extend host-device CNI to support such devices if provided
through runtime config.

- Check if device provided by DeviceID runtime config
  is present on either PCI bus or Auxiliary bus
- extend getLink method to support getting netdev link obj
  from auxiliary bus
- add unit-test to cover the new flow

[1] https://github.com/k8snetworkplumbingwg/sriov-network-device-plugin/tree/master?tab=readme-ov-file#auxiliary-network-devices-selectors
[2] https://github.com/k8snetworkplumbingwg/sriov-network-device-plugin/tree/master/docs/subfunctions
[3] https://docs.kernel.org/networking/devlink/devlink-port.html

Signed-off-by: adrianc <adrianc@nvidia.com>
2024-03-12 12:09:29 +02:00
Tomofumi Hayashi
8fc26ce7a0
Merge pull request #991 from containernetworking/dependabot/docker/dot-github/actions/retest-action/alpine-3.19
build(deps): bump alpine from 3.18 to 3.19 in /.github/actions/retest-action
2024-03-12 02:54:07 +09:00
Tomofumi Hayashi
c8d165df6d
Merge branch 'main' into dependabot/docker/dot-github/actions/retest-action/alpine-3.19 2024-03-12 02:27:00 +09:00
Casey Callendrello
1b5811957e
Merge pull request #1010 from containernetworking/dependabot/github_actions/actions/checkout-4
build(deps): bump actions/checkout from 3 to 4
2024-03-11 18:12:59 +01:00
dependabot[bot]
8a3014f202
build(deps): bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-11 17:03:59 +00:00
Casey Callendrello
019727a392
Merge pull request #1005 from austinvazquez/update-golangci-lint-action-package
Update golangci-lint-action package to v4
2024-03-11 18:03:14 +01:00
Austin Vazquez
352e0512e8 Update golangci-lint-action package to v4
This change updates golangci-lint-action package to v4 to resolve NodeJS
16 deprecation warnings.

Signed-off-by: Austin Vazquez <macedonv@amazon.com>
2024-03-11 17:52:02 +01:00
Austin Vazquez
9c016b5d12 Rename unused variables to resolve lint warnings
Signed-off-by: Austin Vazquez <macedonv@amazon.com>
2024-03-11 17:52:02 +01:00
Casey Callendrello
0729398940
Merge pull request #1017 from containernetworking/dependabot/go_modules/golang-6a70725da1
build(deps): bump the golang group with 5 updates
2024-03-11 17:50:33 +01:00
dependabot[bot]
394ab0d149
build(deps): bump the golang group with 5 updates
Bumps the golang group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [github.com/Microsoft/hcsshim](https://github.com/Microsoft/hcsshim) | `0.11.4` | `0.12.0` |
| [github.com/alexflint/go-filemutex](https://github.com/alexflint/go-filemutex) | `1.2.0` | `1.3.0` |
| [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) | `2.13.2` | `2.16.0` |
| [github.com/onsi/gomega](https://github.com/onsi/gomega) | `1.30.0` | `1.31.1` |
| [golang.org/x/sys](https://github.com/golang/sys) | `0.15.0` | `0.17.0` |


Updates `github.com/Microsoft/hcsshim` from 0.11.4 to 0.12.0
- [Release notes](https://github.com/Microsoft/hcsshim/releases)
- [Commits](https://github.com/Microsoft/hcsshim/compare/v0.11.4...v0.12.0)

Updates `github.com/alexflint/go-filemutex` from 1.2.0 to 1.3.0
- [Release notes](https://github.com/alexflint/go-filemutex/releases)
- [Commits](https://github.com/alexflint/go-filemutex/compare/v1.2.0...v1.3.0)

Updates `github.com/onsi/ginkgo/v2` from 2.13.2 to 2.16.0
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.13.2...v2.16.0)

Updates `github.com/onsi/gomega` from 1.30.0 to 1.31.1
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.30.0...v1.31.1)

Updates `golang.org/x/sys` from 0.15.0 to 0.17.0
- [Commits](https://github.com/golang/sys/compare/v0.15.0...v0.17.0)

---
updated-dependencies:
- dependency-name: github.com/Microsoft/hcsshim
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
- dependency-name: github.com/alexflint/go-filemutex
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-11 15:54:46 +00:00
Casey Callendrello
0144de0fcf
Merge pull request #1011 from containernetworking/dependabot/github_actions/actions/setup-go-5
build(deps): bump actions/setup-go from 3 to 5
2024-03-11 16:51:01 +01:00
dependabot[bot]
47373d2612
build(deps): bump actions/setup-go from 3 to 5
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3 to 5.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v3...v5)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-11 15:07:08 +00:00
Casey Callendrello
8e8410f726
Merge pull request #1006 from austinvazquez/update-go-in-ci
Update to Go v1.22 in CI and release
2024-03-11 16:06:20 +01:00
Austin Vazquez
2bae9b67d3 Update to Go v1.22 in CI and release
Signed-off-by: Austin Vazquez <macedonv@amazon.com>
2024-03-11 15:52:12 +01:00
Casey Callendrello
976edfe1bc
Merge pull request #1009 from adrianchiris/fix-lint-version
Fix version of golangci-lint
2024-03-11 15:51:56 +01:00
adrianc
cb244060c2
Fix version of golangci-lint
currently the workflow will use latest
version which is a moving target.

newer versions may cause ci failures due
to new checks added.

Signed-off-by: adrianc <adrianc@nvidia.com>
2024-02-13 10:35:41 +02:00
Casey Callendrello
d1aada912d
Merge pull request #1000 from s1061123/add-build-gh-action
Add github action to build binaries for each platform at release
2024-02-12 17:28:13 +01:00
Tomofumi Hayashi
8b2b1d20d6 Add github action to build binaries for each platform at release
This change introduces new github action to build cni plugin
binaries for each platform at tagged new release.

Signed-off-by: Tomofumi Hayashi <tohayash@redhat.com>
2024-02-06 01:01:35 +09:00
Casey Callendrello
14bdce598f
Merge pull request #997 from ormergi/bridge-cont-iface-state
bridge: Enable disabling bridge interface
2024-02-02 21:22:32 +01:00
Or Mergi
7e131a0076 bridge: Enable disabling bridge interface
The new `disableContainerInterface` parameter is added to the bridge plugin to
enable setting the container interface state down.

When the parameter is enabled, the container interface (veth peer that is placed
at the container ns) remain down (i.e: disabled).
The bridge and host peer interfaces state are not affected by the parameter.

Since IPAM logic involve various configurations including waiting for addresses
to be realized and setting the interface state UP, the new parameter cannot work
with IPAM.
In case both IPAM and DisableContainerInterface parameters are set, the bridge
plugin will raise an error.

Signed-off-by: Or Mergi <ormergi@redhat.com>
2024-01-10 15:35:23 +02:00
Casey Callendrello
b6a0e0bc96
Merge pull request #990 from containernetworking/dependabot/github_actions/actions/setup-go-5
build(deps): bump actions/setup-go from 4 to 5
2023-12-11 17:08:17 +01:00
dependabot[bot]
133a764c4d
build(deps): bump alpine in /.github/actions/retest-action
Bumps alpine from 3.18 to 3.19.

---
updated-dependencies:
- dependency-name: alpine
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-11 02:50:07 +00:00
dependabot[bot]
e6099fb83d
build(deps): bump actions/setup-go from 4 to 5
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 4 to 5.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-11 02:16:03 +00:00
Casey Callendrello
1fb5bf669e
Merge pull request #982 from jingyuanliang/patch-1
Bump to golang:1.21-alpine in release.sh
2023-12-04 10:37:33 +01:00
Casey Callendrello
3712c1cfcb
Merge pull request #988 from containernetworking/dependabot/go_modules/golang-2d6cee5bad
build(deps): bump the golang group with 2 updates
2023-12-04 10:36:51 +01:00
dependabot[bot]
825421709e
build(deps): bump the golang group with 2 updates
Bumps the golang group with 2 updates: [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) and [golang.org/x/sys](https://github.com/golang/sys).


Updates `github.com/onsi/ginkgo/v2` from 2.13.1 to 2.13.2
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.13.1...v2.13.2)

Updates `golang.org/x/sys` from 0.14.0 to 0.15.0
- [Commits](https://github.com/golang/sys/compare/v0.14.0...v0.15.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: golang
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-04 02:47:46 +00:00
jingyuanliang
d708217503 Bump to golang:1.21-alpine in release.sh
Also make this overridable in env vars.

Signed-off-by: Jingyuan Liang <jingyuanliang@google.com>
2023-11-17 09:01:30 +00:00
Casey Callendrello
abee8ccc0d
Merge pull request #954 from cyclinder/improve_cmd_del
macvlan cmdDel: replace the loadConf function with json.unmarshal
2023-11-16 19:06:11 +01:00
Casey Callendrello
e1474463ef
Merge pull request #978 from containernetworking/dependabot/go_modules/golang-439c5fc513
build(deps): bump the golang group with 3 updates
2023-11-16 19:05:16 +01:00
Casey Callendrello
11ee4b61d9
Merge pull request #969 from s1061123/fix-netns-override
Add CNI_NETNS_OVERRIDE for upcoming CNI change
2023-11-16 19:04:53 +01:00
Tomofumi Hayashi
a4cbf13a9b Add CNI_NETNS_OVERRIDE for upcoming CNI change
containernetwork/cni#890 introduces CNI_NETNS_OVERRIDE and plugin
testing requires this flag. This change enables CNI_NETNS_OVERRIDE
for further containernetwork/cni vendor update.

Signed-off-by: Tomofumi Hayashi <tohayash@redhat.com>
2023-11-16 23:18:00 +09:00
dependabot[bot]
6cac5d603b
build(deps): bump the golang group with 3 updates
Bumps the golang group with 3 updates: [github.com/Microsoft/hcsshim](https://github.com/Microsoft/hcsshim), [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) and [github.com/onsi/gomega](https://github.com/onsi/gomega).


Updates `github.com/Microsoft/hcsshim` from 0.11.2 to 0.11.4
- [Release notes](https://github.com/Microsoft/hcsshim/releases)
- [Commits](https://github.com/Microsoft/hcsshim/compare/v0.11.2...v0.11.4)

Updates `github.com/onsi/ginkgo/v2` from 2.13.0 to 2.13.1
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.13.0...v2.13.1)

Updates `github.com/onsi/gomega` from 1.29.0 to 1.30.0
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.29.0...v1.30.0)

---
updated-dependencies:
- dependency-name: github.com/Microsoft/hcsshim
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: golang
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: golang
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-16 12:41:14 +00:00
Casey Callendrello
333fc9a0d7
Merge pull request #981 from arthur-zhang/dev-revert
revert some code in pr 962
2023-11-16 13:39:59 +01:00
arthur-zhang
f90ac41ae4 revert some code in pr 962
Signed-off-by: arthur-zhang <zhangya_no1@qq.com>
2023-11-14 10:04:18 +08:00
Casey Callendrello
93a1b3d0e7
Merge pull request #979 from s1061123/fix/ndisc_ipvlan
Add ndisc_notify in ipvlan for ipv6 ndp
2023-11-14 00:27:01 +01:00
Tomofumi Hayashi
00406f9d1e
Merge branch 'main' into fix/ndisc_ipvlan 2023-11-14 08:18:07 +09:00
Casey Callendrello
e82848a9cb
Merge pull request #962 from arthur-zhang/dev-pr-bridge
bridge: remove useless code
2023-11-13 18:01:02 +01:00
arthur-zhang
5280b4d582 bridge: fix spelling
Signed-off-by: arthur-zhang <zhangya_no1@qq.com>
2023-11-13 17:11:21 +01:00
arthur-zhang
495a2cbb0c bridge: remove useless firstV4Addr
Signed-off-by: arthur-zhang <zhangya_no1@qq.com>
2023-11-13 17:11:21 +01:00
arthur-zhang
8c59fc1eea bridge: remove useless check
gws.defaultRouteFound here is always false.

Signed-off-by: arthur-zhang <zhangya_no1@qq.com>
2023-11-13 17:11:21 +01:00
Casey Callendrello
2eee7cef35
Merge pull request #974 from zshi-redhat/macvlan-ipv6-ndisc
macvlan: enable ipv6 ndisc_notify
2023-11-13 17:08:56 +01:00
Tomofumi Hayashi
1079e113fe Add ndisc_notify in ipvlan for ipv6 ndp
Signed-off-by: Tomofumi Hayashi <tohayash@redhat.com>
2023-11-14 01:07:59 +09:00
Zenghui Shi
999ca15763 macvlan: enable ipv6 ndisc_notify
Signed-off-by: Zenghui Shi <zshi@redhat.com>
2023-11-07 19:43:50 +08:00
Casey Callendrello
dad27e9f72
Merge pull request #963 from containernetworking/dependabot/go_modules/google.golang.org/grpc-1.56.3
build(deps): bump google.golang.org/grpc from 1.50.1 to 1.56.3
2023-11-01 10:21:47 +01:00
dependabot[bot]
0b1a96ff30
build(deps): bump google.golang.org/grpc from 1.50.1 to 1.56.3
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.50.1 to 1.56.3.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.50.1...v1.56.3)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-31 19:09:22 +00:00
Casey Callendrello
c8c26897ba
Merge pull request #970 from containernetworking/dependabot/go_modules/golang-1eaf3aa819
build(deps): bump the golang group with 3 updates
2023-10-31 20:08:15 +01:00
dependabot[bot]
28c5faee75
build(deps): bump the golang group with 3 updates
Bumps the golang group with 3 updates: [github.com/Microsoft/hcsshim](https://github.com/Microsoft/hcsshim), [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) and [github.com/onsi/gomega](https://github.com/onsi/gomega).


Updates `github.com/Microsoft/hcsshim` from 0.11.1 to 0.11.2
- [Release notes](https://github.com/Microsoft/hcsshim/releases)
- [Commits](https://github.com/Microsoft/hcsshim/compare/v0.11.1...v0.11.2)

Updates `github.com/onsi/ginkgo/v2` from 2.12.0 to 2.13.0
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.12.0...v2.13.0)

Updates `github.com/onsi/gomega` from 1.28.0 to 1.29.0
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.28.0...v1.29.0)

---
updated-dependencies:
- dependency-name: github.com/Microsoft/hcsshim
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: golang
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: golang
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-31 15:06:19 +00:00
Casey Callendrello
d0d9e36662
Merge pull request #961 from squeed/dependabot-batch
dependabot: batch updates
2023-10-31 16:04:45 +01:00
Casey Callendrello
f7662a2435
Merge pull request #968 from mmorel-35/patch-1
fix workflow warnings
2023-10-31 11:47:42 +01:00
Casey Callendrello
aacae5c053 dependabot: batch updates
Rather than endless rebases, just batch all go updates to once a week.

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2023-10-31 11:46:29 +01:00
Matthieu MOREL
6b7876125d fix workflow warnings
Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>
2023-10-31 09:07:15 +01:00
Casey Callendrello
2f0faf6721
Merge pull request #967 from squeed/fix-lint
fix lint errors
2023-10-31 08:40:30 +01:00
Casey Callendrello
37531cdaf5 fix lint errors
Fix a small ginkgo compare issue, and ignore dot imports.

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2023-10-30 17:55:55 +01:00
Casey Callendrello
a8d4e0a7dd
Merge pull request #949 from containernetworking/dependabot/go_modules/github.com/onsi/gomega-1.28.0
build(deps): bump github.com/onsi/gomega from 1.27.8 to 1.28.0
2023-10-17 12:31:08 +02:00
cyclinder
845ef62b74 macvlan cmdDel: replace the loadConf function with json.unmarshal
When the master interface on the node has been deleted, and loadConf tries
to get the MTU, This causes cmdDel to return a linkNotFound error to the
runtime. The cmdDel only needs to unmarshal the netConf. No need to
get the MTU. So we just replaced the loadConf function with
json.unmarshal in cmdDel.

Signed-off-by: cyclinder <qifeng.guo@daocloud.io>
2023-10-17 10:26:18 +08:00
dependabot[bot]
691186ca7f
build(deps): bump github.com/onsi/gomega from 1.27.8 to 1.28.0
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.27.8 to 1.28.0.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.27.8...v1.28.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-16 15:44:53 +00:00
Casey Callendrello
adaeedd6af
Merge pull request #955 from containernetworking/dependabot/go_modules/golang.org/x/net-0.17.0
build(deps): bump golang.org/x/net from 0.10.0 to 0.17.0
2023-10-16 17:43:46 +02:00
dependabot[bot]
19e5747a8c
build(deps): bump golang.org/x/net from 0.10.0 to 0.17.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.10.0 to 0.17.0.
- [Commits](https://github.com/golang/net/compare/v0.10.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-16 15:26:26 +00:00
Casey Callendrello
4cf3da4ae3
Merge pull request #948 from twz123/posix-sh
build: Use POSIX sh for shell scripts
2023-10-16 17:25:04 +02:00
Casey Callendrello
c20da1521f
Merge pull request #952 from containernetworking/dependabot/go_modules/golang.org/x/sys-0.13.0
build(deps): bump golang.org/x/sys from 0.10.0 to 0.13.0
2023-10-16 17:24:51 +02:00
Casey Callendrello
b66b5dd85f
Merge pull request #945 from containernetworking/dependabot/github_actions/actions/checkout-4
build(deps): bump actions/checkout from 3 to 4
2023-10-16 17:23:45 +02:00
Casey Callendrello
e727ad6697
Merge pull request #946 from containernetworking/dependabot/go_modules/github.com/Microsoft/hcsshim-0.11.1
build(deps): bump github.com/Microsoft/hcsshim from 0.9.9 to 0.11.1
2023-10-16 17:23:25 +02:00
dependabot[bot]
18172539d8
build(deps): bump github.com/Microsoft/hcsshim from 0.9.9 to 0.11.1
Bumps [github.com/Microsoft/hcsshim](https://github.com/Microsoft/hcsshim) from 0.9.9 to 0.11.1.
- [Release notes](https://github.com/Microsoft/hcsshim/releases)
- [Commits](https://github.com/Microsoft/hcsshim/compare/v0.9.9...v0.11.1)

---
updated-dependencies:
- dependency-name: github.com/Microsoft/hcsshim
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-16 15:11:01 +00:00
Casey Callendrello
f20b8408a4
Merge pull request #937 from containernetworking/dependabot/go_modules/github.com/coreos/go-iptables-0.7.0
build(deps): bump github.com/coreos/go-iptables from 0.6.0 to 0.7.0
2023-10-16 17:10:05 +02:00
Dan Williams
6ff8e5eb86
Merge pull request #950 from ricky-rav/OCPBUGS-16788
Create IPAM files with 0600 permissions
2023-10-12 09:13:05 -05:00
dependabot[bot]
61fa963636
build(deps): bump golang.org/x/sys from 0.10.0 to 0.13.0
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.10.0 to 0.13.0.
- [Commits](https://github.com/golang/sys/compare/v0.10.0...v0.13.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-09 02:20:37 +00:00
Riccardo Ravaioli
33ccedc66f Create IPAM files with 0600 permissions
Conform to CIS Benchmarks "1.1.9 Ensure that the Container Network Interface file permissions are set to 600 or more restrictive"
https://www.tenable.com/audits/items/CIS_Kubernetes_v1.20_v1.0.1_Level_1_Master.audit:f1717a5dd65d498074dd41c4a639e47d

Signed-off-by: Riccardo Ravaioli <rravaiol@redhat.com>
2023-10-02 11:59:31 +02:00
Tom Wieczorek
853b82d19f
build: Use POSIX sh for shell scripts
The scripts didn't really use any bash specific features. Convert
them to POSIX shell scripts, so that the plugins can be built without
requiring bash.

Signed-off-by: Tom Wieczorek <twieczorek@mirantis.com>
2023-09-29 16:57:19 +02:00
dependabot[bot]
d216b0c39b
build(deps): bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-11 02:11:46 +00:00
Casey Callendrello
f95505231a
Merge pull request #942 from mmorel-35/patch-1
ci(lint) extend timeout to 5 min
2023-09-04 17:18:17 +02:00
Matthieu MOREL
5f25a93a47 ci(lint) extend timeout to 5 min
This extends the timeout or golangci-lint execution to 5 min as it is only one minute now it fails for several PR.

Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>
2023-09-01 20:45:00 +00:00
dependabot[bot]
7c11d48630
build(deps): bump github.com/coreos/go-iptables from 0.6.0 to 0.7.0
Bumps [github.com/coreos/go-iptables](https://github.com/coreos/go-iptables) from 0.6.0 to 0.7.0.
- [Release notes](https://github.com/coreos/go-iptables/releases)
- [Commits](https://github.com/coreos/go-iptables/compare/v0.6.0...v0.7.0)

---
updated-dependencies:
- dependency-name: github.com/coreos/go-iptables
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-07 02:47:30 +00:00
Casey Callendrello
9d9ec6e3e1
Merge pull request #927 from sockmister/vrf_filter_fix
vrf: fix route filter to use output iface
2023-07-21 13:49:33 +02:00
Casey Callendrello
8fd63065a6
Merge pull request #913 from AlinaSecret/dhcp/fix-race-test
Fix race conditions in DHCP test
2023-07-21 12:55:01 +02:00
Poh Chiat Koh
c1a7948b19 vrf: fix route filter to use output iface
current route filter uses RT_FILTER_IIF in conjunction with LinkIndex.
This combination is ignored by netlink, rendering the filter
ineffective

Signed-off-by: Poh Chiat Koh <poh@inter.link>
2023-07-21 12:50:21 +02:00
Casey Callendrello
1561794ae9
Merge pull request #924 from SirPhuttel/go-iptables_review
Review code using go-iptables module
2023-07-21 12:23:08 +02:00
Casey Callendrello
fb8ca5d31e
Merge pull request #918 from SirPhuttel/rawhide_testing
Two minor testsuite fixes
2023-07-21 12:22:46 +02:00
Casey Callendrello
f2574a7cb1
Merge pull request #926 from containernetworking/dependabot/go_modules/golang.org/x/sys-0.10.0
build(deps): bump golang.org/x/sys from 0.9.0 to 0.10.0
2023-07-21 11:56:30 +02:00
Casey Callendrello
438548a9dd
Merge pull request #902 from SirPhuttel/applyconfigecho
spoofcheck: Make use of go-nft's ApplyConfigEcho()
2023-07-20 12:01:18 +02:00
Phil Sutter
8e69e38d51 test_linux.sh: Do not fail if called twice
The script is set to exit on error, so mkdir failing because
/tmp/cni-rootless already exists aborts the test run. Call 'mkdir -p' to
avoid the spurious error.

Signed-off-by: Phil Sutter <psutter@redhat.com>
2023-07-20 11:34:34 +02:00
Phil Sutter
0a100e5d8f meta: firewall: Fix firewalld test with non-abstract sockets
On a recent Fedora Rawhide, dbus-daemon-1.14.8-1 prints a string
prefixed by 'unix:path' instead of the expected 'unix:abstract', thereby
failing the test. Allowing this alternate prefix fixes the test, so for
communication with the daemon it is not relevant.

Signed-off-by: Phil Sutter <psutter@redhat.com>
2023-07-20 11:34:34 +02:00
Phil Sutter
3eb775c5e6 plugins: meta: portmap: Implement a teardown() fast path
Just attempt to delete the known rules referring to the custom chain,
then flush and delete it. If the latter succeeds, no referencing rules
are left and the job is done.

If the final flush'n'delete fails, fall back to the referencing rule
search which is slow with large rulesets.

Signed-off-by: Phil Sutter <psutter@redhat.com>
2023-07-20 11:34:02 +02:00
Phil Sutter
719f60bb91 utils: iptables: Use go-iptables' ChainExists()
Starting with v0.5.0, go-iptables exports a fast ChainExists() which
does not rely upon listing all chains and searching the results but
probes chain existence by listing its first rule. This should make a
significant difference in rulesets with thousands of chains.

Signed-off-by: Phil Sutter <psutter@redhat.com>
2023-07-20 11:34:02 +02:00
Phil Sutter
2ba7f1608f spoofcheck: Make use of go-nft's ApplyConfigEcho()
Store the relevant applied config part for later to extract the rule to
delete from there instead of having to list the ruleset. This is much
faster especially with large rulesets.

Signed-off-by: Phil Sutter <psutter@redhat.com>
2023-07-20 11:32:09 +02:00
Casey Callendrello
bf79945c70
Merge pull request #929 from squeed/fix-ci-cnitool-version
test: install binaries using `go install`
2023-07-20 11:26:31 +02:00
Casey Callendrello
ba41448fe6 test: install binaries using go install
We were getting tip- versions of all the tools, and this was unstable.

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2023-07-20 11:15:41 +02:00
dependabot[bot]
13fd3de77f
build(deps): bump golang.org/x/sys from 0.9.0 to 0.10.0
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.9.0 to 0.10.0.
- [Commits](https://github.com/golang/sys/compare/v0.9.0...v0.10.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-10 02:29:05 +00:00
Casey Callendrello
283f200489
Merge pull request #897 from containernetworking/dependabot/docker/dot-github/actions/retest-action/alpine-3.18
build(deps): bump alpine from 3.17 to 3.18 in /.github/actions/retest-action
2023-07-03 17:05:49 +02:00
Casey Callendrello
a7e8db00cb
Merge pull request #911 from containernetworking/dependabot/go_modules/golang.org/x/sys-0.9.0
build(deps): bump golang.org/x/sys from 0.7.0 to 0.9.0
2023-07-03 17:05:17 +02:00
Casey Callendrello
ffb2e2d7d1
Merge pull request #912 from containernetworking/dependabot/go_modules/github.com/onsi/ginkgo/v2-2.11.0
build(deps): bump github.com/onsi/ginkgo/v2 from 2.9.2 to 2.11.0
2023-07-03 17:05:02 +02:00
Casey Callendrello
d03b84d8f2
Merge pull request #874 from travelping/vrf-add-routes
Add routes propagation for VRF plugin
2023-06-27 12:20:45 +02:00
Casey Callendrello
1512d727cb
Merge pull request #914 from tariq1890/tx-qlen
[tuning] add ability to set tx queue len
2023-06-26 17:47:01 +02:00
Tariq Ibrahim
470eee1385 [tuning]add ability to set tx queue len
Signed-off-by: Tariq Ibrahim <tibrahim@nvidia.com>
2023-06-25 23:28:48 -07:00
dependabot[bot]
2216cff9e8
build(deps): bump github.com/onsi/ginkgo/v2 from 2.9.2 to 2.11.0
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.9.2 to 2.11.0.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.9.2...v2.11.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-19 02:58:59 +00:00
dependabot[bot]
83029befef
build(deps): bump golang.org/x/sys from 0.7.0 to 0.9.0
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.7.0 to 0.9.0.
- [Commits](https://github.com/golang/sys/compare/v0.7.0...v0.9.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-19 02:58:45 +00:00
Alina Sudakov
48aa2f4eef Fix race conditions in DHCP test
The test named "correctly handles multiple DELs for the same container" in the ipam/dhcp package experiences race conditions when multiple goroutines concurrently access and modify the Args struct (of type CmdArgs).
To address these issues, a copy of the CmdArgs struct is now created in each function to eliminate data races.

Also, the test-linux.sh and test-windows.sh scripts have been updated to include the '-race' flag, enabling race detection during testing. This change helps prevent future race conditions by activating the Go race detector.

Signed-off-by: Alina Sudakov <asudakov@redhat.com>
2023-06-14 17:57:46 +03:00
Artur Korzeniewski
ca12d49b41 Add routes propagation for VRF plugin
Up until now, if previous plugin assigned routes to interface, movement of
this interface to new VRF cause routes to be deleted.

This patch adds funtionality to VRF plugin to save the routes before
interface is assgined to VRF, and then re-apply all saved routes to new VRF.

Signed-off-by: Artur Korzeniewski <artur.korzeniewski@travelping.com>
2023-06-02 14:21:28 +02:00
Casey Callendrello
2b097c5a62
Merge pull request #900 from squeed/disable-stale
github: remove stale issue cleanup
2023-05-25 10:32:04 +02:00
Casey Callendrello
0389a29052 github: remove stale issue cleanup
In retrospect, this was a bad idea. It's closing too many valid issues.

Signed-off-by: Casey Callendrello <c1@caseyc.net>
2023-05-25 10:31:15 +02:00
Dan Williams
6265f4e4ca
Merge pull request #832 from maiqueb/tap-plugin-set-as-bridge-port
tap: allow for a tap device to be created as a bridge port
2023-05-22 10:59:28 -05:00
Miguel Duarte Barroso
edab9efdea tap: allow for a tap device to be created as a bridge port
This extends the tap plugin API enabling the user to instruct the CNI
plugin the created tap device must be set as a port of an *existing*
linux bridge on the pod network namespace.

This is helpful for KubeVirt, allowing network connectivity to be
extended from the pod's interface into the Virtual Machine running
inside the pod.

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>
2023-05-19 16:26:14 +02:00
dependabot[bot]
1b2dc7c2a4
build(deps): bump alpine in /.github/actions/retest-action
Bumps alpine from 3.17 to 3.18.

---
updated-dependencies:
- dependency-name: alpine
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-15 03:01:50 +00:00
1437 changed files with 101781 additions and 57371 deletions

View File

@ -1,4 +1,4 @@
FROM alpine:3.17
FROM alpine:3.21
RUN apk add --no-cache curl jq

View File

@ -17,3 +17,9 @@ updates:
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
groups:
golang:
patterns:
- "*"
exclude-patterns:
- "github.com/containernetworking/*"

1
.github/go-version vendored Normal file
View File

@ -0,0 +1 @@
1.23

View File

@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Re-Test Action
uses: ./.github/actions/retest-action

114
.github/workflows/release.yaml vendored Normal file
View File

@ -0,0 +1,114 @@
---
name: Release binaries
on:
push:
tags:
- 'v*'
jobs:
linux_release:
name: Release linux binaries
runs-on: ubuntu-latest
strategy:
matrix:
goarch: [amd64, arm, arm64, mips64le, ppc64le, riscv64, s390x]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Go
uses: actions/setup-go@v5
with:
go-version-file: .github/go-version
- name: Build
env:
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: 0
run: ./build_linux.sh -ldflags '-extldflags -static -X github.com/containernetworking/plugins/pkg/utils/buildversion.BuildVersion=${{ github.ref_name }}'
- name: COPY files
run: cp README.md LICENSE bin/
- name: Change plugin file ownership
working-directory: ./bin
run: sudo chown -R root:root .
- name: Create dist directory
run: mkdir dist
- name: Create archive file
working-directory: ./bin
run: tar cfzpv ../dist/cni-plugins-linux-${{ matrix.goarch }}-${{ github.ref_name }}.tgz .
- name: Create sha256 checksum
working-directory: ./dist
run: sha256sum cni-plugins-linux-${{ matrix.goarch }}-${{ github.ref_name }}.tgz | tee cni-plugins-linux-${{ matrix.goarch }}-${{ github.ref_name }}.tgz.sha256
- name: Create sha512 checksum
working-directory: ./dist
run: sha512sum cni-plugins-linux-${{ matrix.goarch }}-${{ github.ref_name }}.tgz | tee cni-plugins-linux-${{ matrix.goarch }}-${{ github.ref_name }}.tgz.sha512
- name: Upload binaries to release
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ./dist/*
tag: ${{ github.ref }}
overwrite: true
file_glob: true
windows_releases:
name: Release windows binaries
runs-on: ubuntu-latest
strategy:
matrix:
goarch: [amd64]
steps:
- name: Install dos2unix
run: sudo apt-get install dos2unix
- name: Checkout code
uses: actions/checkout@v4
- name: Install Go
uses: actions/setup-go@v5
with:
go-version-file: .github/go-version
- name: Build
env:
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: 0
run: ./build_windows.sh -ldflags '-extldflags -static -X github.com/containernetworking/plugins/pkg/utils/buildversion.BuildVersion=${{ github.ref_name }}'
- name: COPY files
run: cp README.md LICENSE bin/
- name: Change plugin file ownership
working-directory: ./bin
run: sudo chown -R root:root .
- name: Create dist directory
run: mkdir dist
- name: Create archive file
working-directory: ./bin
run: tar cpfzv ../dist/cni-plugins-windows-${{ matrix.goarch }}-${{ github.ref_name }}.tgz .
- name: Create sha256 checksum
working-directory: ./dist
run: sha256sum cni-plugins-windows-${{ matrix.goarch }}-${{ github.ref_name }}.tgz | tee cni-plugins-windows-${{ matrix.goarch }}-${{ github.ref_name }}.tgz.sha256
- name: Create sha512 checksum
working-directory: ./dist
run: sha512sum cni-plugins-windows-${{ matrix.goarch }}-${{ github.ref_name }}.tgz | tee cni-plugins-windows-${{ matrix.goarch }}-${{ github.ref_name }}.tgz.sha512
- name: Upload binaries to release
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ./dist/*
tag: ${{ github.ref }}
overwrite: true
file_glob: true

View File

@ -1,13 +0,0 @@
name: 'Close stale issues and PRs'
on:
schedule:
- cron: '30 1 * * *'
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v8
with:
stale-pr-message: 'This PR has been untouched for too long without an update. It will be closed in 7 days.'
exempt-issue-labels: 'keep'

View File

@ -1,10 +1,10 @@
---
name: test
on: ["push", "pull_request"]
on:
pull_request: {}
env:
GO_VERSION: "1.20"
LINUX_ARCHES: "amd64 386 arm arm64 s390x mips64le ppc64le riscv64"
jobs:
@ -12,28 +12,42 @@ jobs:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: setup go
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
- uses: actions/checkout@v3
go-version-file: .github/go-version
- uses: ibiqlik/action-yamllint@v3
with:
format: auto
- uses: golangci/golangci-lint-action@v3
- uses: golangci/golangci-lint-action@v6
with:
version: v1.61.0
args: -v
verify-vendor:
name: Verify vendor directory
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Go
uses: actions/setup-go@v5
with:
go-version-file: .github/go-version
- name: Check module vendoring
run: |
go mod tidy
go mod vendor
test -z "$(git status --porcelain)" || (echo "please run 'go mod tidy && go mod vendor', and submit your changes"; exit 1)
build:
name: Build all linux architectures
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: setup go
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
- uses: actions/checkout@v3
go-version-file: .github/go-version
- name: Build on all supported architectures
run: |
set -e
@ -53,24 +67,25 @@ jobs:
sudo apt-get install linux-modules-extra-$(uname -r)
- name: Install nftables
run: sudo apt-get install nftables
- name: Install dnsmasq(dhcp server)
run: |
sudo apt-get install dnsmasq
sudo systemctl disable --now dnsmasq
- uses: actions/checkout@v4
- name: setup go
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
go-version-file: .github/go-version
- name: Set up Go for root
run: |
sudo ln -sf `which go` `sudo which go` || true
sudo go version
- uses: actions/checkout@v3
- name: Install test binaries
env:
GO111MODULE: off
run: |
go get github.com/containernetworking/cni/cnitool
go get github.com/mattn/goveralls
go get github.com/modocache/gover
go install github.com/containernetworking/cni/cnitool@latest
go install github.com/mattn/goveralls@latest
go install github.com/modocache/gover@latest
- name: test
run: PATH=$PATH:$(go env GOPATH)/bin COVERALLS=1 ./test_linux.sh
@ -87,10 +102,10 @@ jobs:
needs: build
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: setup go
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
- uses: actions/checkout@v3
go-version-file: .github/go-version
- name: test
run: bash ./test_windows.sh

View File

@ -6,6 +6,8 @@ issues:
- linters:
- revive
text: " and that stutters;"
- path: '(.+)_test\.go'
text: "dot-imports: should not use dot imports"
linters:
disable:
@ -38,5 +40,5 @@ linters-settings:
- prefix(github.com/containernetworking)
run:
skip-dirs:
- vendor
timeout: 5m
modules-download-mode: vendor

View File

@ -1,8 +1,8 @@
#!/usr/bin/env bash
#!/usr/bin/env sh
set -e
cd "$(dirname "$0")"
if [ "$(uname)" == "Darwin" ]; then
if [ "$(uname)" = "Darwin" ]; then
export GOOS="${GOOS:-linux}"
fi

View File

@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/usr/bin/env sh
set -e
cd "$(dirname "$0")"

62
go.mod
View File

@ -1,44 +1,54 @@
module github.com/containernetworking/plugins
go 1.20
go 1.23
require (
github.com/Microsoft/hcsshim v0.9.9
github.com/alexflint/go-filemutex v1.2.0
github.com/Microsoft/hcsshim v0.12.9
github.com/alexflint/go-filemutex v1.3.0
github.com/buger/jsonparser v1.1.1
github.com/containernetworking/cni v1.1.2
github.com/coreos/go-iptables v0.6.0
github.com/containernetworking/cni v1.2.3
github.com/coreos/go-iptables v0.8.0
github.com/coreos/go-systemd/v22 v22.5.0
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c
github.com/d2g/dhcp4client v1.0.0
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5
github.com/godbus/dbus/v5 v5.1.0
github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475
github.com/mattn/go-shellwords v1.0.12
github.com/networkplumbing/go-nft v0.3.0
github.com/onsi/ginkgo/v2 v2.9.2
github.com/onsi/gomega v1.27.6
github.com/opencontainers/selinux v1.11.0
github.com/safchain/ethtool v0.3.0
github.com/vishvananda/netlink v1.2.1-beta.2
golang.org/x/sys v0.7.0
github.com/networkplumbing/go-nft v0.4.0
github.com/onsi/ginkgo/v2 v2.22.2
github.com/onsi/gomega v1.36.2
github.com/opencontainers/selinux v1.11.1
github.com/safchain/ethtool v0.5.9
github.com/vishvananda/netlink v1.3.0
golang.org/x/sys v0.29.0
sigs.k8s.io/knftables v0.0.18
)
require (
github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/containerd/cgroups v1.1.0 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/containerd/cgroups/v3 v3.0.3 // indirect
github.com/containerd/errdefs v0.3.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/typeurl/v2 v2.2.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/pprof v0.0.0-20230323073829-e72429f035bd // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/mdlayher/packet v1.1.2 // indirect
github.com/mdlayher/socket v0.5.1 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/mod v0.9.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/text v0.8.0 // indirect
golang.org/x/tools v0.7.0 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/tools v0.28.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/grpc v1.67.0 // indirect
google.golang.org/protobuf v1.36.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

1033
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -17,6 +17,7 @@ import (
"bytes"
"fmt"
"io"
"log"
"math/rand"
"net"
"os"
@ -60,6 +61,13 @@ var _ = Describe("Basic PTP using cnitool", func() {
netConfPath, err := filepath.Abs("./testdata")
Expect(err).NotTo(HaveOccurred())
// Flush ipam stores to avoid conflicts
err = os.RemoveAll("/tmp/chained-ptp-bandwidth-test")
Expect(err).NotTo(HaveOccurred())
err = os.RemoveAll("/tmp/basic-ptp-test")
Expect(err).NotTo(HaveOccurred())
env = TestEnv([]string{
"CNI_PATH=" + cniPath,
"NETCONFPATH=" + netConfPath,
@ -82,6 +90,7 @@ var _ = Describe("Basic PTP using cnitool", func() {
env.runInNS(hostNS, cnitoolBin, "add", netName, contNS.LongName())
addrOutput := env.runInNS(contNS, "ip", "addr")
Expect(addrOutput).To(ContainSubstring(expectedIPPrefix))
env.runInNS(hostNS, cnitoolBin, "del", netName, contNS.LongName())
@ -145,9 +154,13 @@ var _ = Describe("Basic PTP using cnitool", func() {
chainedBridgeBandwidthEnv.runInNS(hostNS, cnitoolBin, "del", "network-chain-test", contNS1.LongName())
basicBridgeEnv.runInNS(hostNS, cnitoolBin, "del", "network-chain-test", contNS2.LongName())
contNS1.Del()
contNS2.Del()
hostNS.Del()
})
Measure("limits traffic only on the restricted bandwidth veth device", func(b Benchmarker) {
It("limits traffic only on the restricted bandwidth veth device", func() {
ipRegexp := regexp.MustCompile(`10\.1[12]\.2\.\d{1,3}`)
By(fmt.Sprintf("adding %s to %s\n\n", "chained-bridge-bandwidth", contNS1.ShortName()))
@ -172,17 +185,19 @@ var _ = Describe("Basic PTP using cnitool", func() {
// balanced by run time.
By(fmt.Sprintf("sending tcp traffic to the chained, bridged, traffic shaped container on ip address '%s:%d'\n\n", chainedBridgeIP, chainedBridgeBandwidthPort))
runtimeWithLimit := b.Time("with chained bridge and bandwidth plugins", func() {
makeTCPClientInNS(hostNS.ShortName(), chainedBridgeIP, chainedBridgeBandwidthPort, packetInBytes)
})
start := time.Now()
makeTCPClientInNS(hostNS.ShortName(), chainedBridgeIP, chainedBridgeBandwidthPort, packetInBytes)
runtimeWithLimit := time.Since(start)
log.Printf("Runtime with qos limit %.2f seconds", runtimeWithLimit.Seconds())
By(fmt.Sprintf("sending tcp traffic to the basic bridged container on ip address '%s:%d'\n\n", basicBridgeIP, basicBridgePort))
runtimeWithoutLimit := b.Time("with basic bridged plugin", func() {
makeTCPClientInNS(hostNS.ShortName(), basicBridgeIP, basicBridgePort, packetInBytes)
})
start = time.Now()
makeTCPClientInNS(hostNS.ShortName(), basicBridgeIP, basicBridgePort, packetInBytes)
runtimeWithoutLimit := time.Since(start)
log.Printf("Runtime without qos limit %.2f seconds", runtimeWithoutLimit.Seconds())
Expect(runtimeWithLimit).To(BeNumerically(">", runtimeWithoutLimit+1000*time.Millisecond))
}, 1)
})
})
})

View File

@ -6,6 +6,7 @@
"mtu": 512,
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24"
"subnet": "10.1.2.0/24",
"dataDir": "/tmp/basic-ptp-test"
}
}

View File

@ -8,7 +8,8 @@
"mtu": 512,
"ipam": {
"type": "host-local",
"subnet": "10.9.2.0/24"
"subnet": "10.9.2.0/24",
"dataDir": "/tmp/chained-ptp-bandwidth-test"
}
},
{

View File

@ -43,7 +43,7 @@ func TestAnnotate(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if !reflect.DeepEqual(Annotatef(test.existingErr, test.contextMessage), test.expectedErr) {
if !reflect.DeepEqual(Annotate(test.existingErr, test.contextMessage), test.expectedErr) {
t.Errorf("test case %s fails", test.name)
return
}

View File

@ -0,0 +1,180 @@
// Copyright 2015 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 ip
import (
"errors"
"fmt"
"net"
"strings"
"github.com/coreos/go-iptables/iptables"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/plugins/pkg/utils"
)
// setupIPMasqIPTables is the iptables-based implementation of SetupIPMasqForNetworks
func setupIPMasqIPTables(ipns []*net.IPNet, network, _, containerID string) error {
// Note: for historical reasons, the iptables implementation ignores ifname.
chain := utils.FormatChainName(network, containerID)
comment := utils.FormatComment(network, containerID)
for _, ip := range ipns {
if err := SetupIPMasq(ip, chain, comment); err != nil {
return err
}
}
return nil
}
// SetupIPMasq installs iptables rules to masquerade traffic
// coming from ip of ipn and going outside of ipn.
// Deprecated: This function only supports iptables. Use SetupIPMasqForNetworks, which
// supports both iptables and nftables.
func SetupIPMasq(ipn *net.IPNet, chain string, comment string) error {
isV6 := ipn.IP.To4() == nil
var ipt *iptables.IPTables
var err error
var multicastNet string
if isV6 {
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv6)
multicastNet = "ff00::/8"
} else {
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
multicastNet = "224.0.0.0/4"
}
if err != nil {
return fmt.Errorf("failed to locate iptables: %v", err)
}
// Create chain if doesn't exist
exists := false
chains, err := ipt.ListChains("nat")
if err != nil {
return fmt.Errorf("failed to list chains: %v", err)
}
for _, ch := range chains {
if ch == chain {
exists = true
break
}
}
if !exists {
if err = ipt.NewChain("nat", chain); err != nil {
return err
}
}
// Packets to this network should not be touched
if err := ipt.AppendUnique("nat", chain, "-d", ipn.String(), "-j", "ACCEPT", "-m", "comment", "--comment", comment); err != nil {
return err
}
// Don't masquerade multicast - pods should be able to talk to other pods
// on the local network via multicast.
if err := ipt.AppendUnique("nat", chain, "!", "-d", multicastNet, "-j", "MASQUERADE", "-m", "comment", "--comment", comment); err != nil {
return err
}
// Packets from the specific IP of this network will hit the chain
return ipt.AppendUnique("nat", "POSTROUTING", "-s", ipn.IP.String(), "-j", chain, "-m", "comment", "--comment", comment)
}
// teardownIPMasqIPTables is the iptables-based implementation of TeardownIPMasqForNetworks
func teardownIPMasqIPTables(ipns []*net.IPNet, network, _, containerID string) error {
// Note: for historical reasons, the iptables implementation ignores ifname.
chain := utils.FormatChainName(network, containerID)
comment := utils.FormatComment(network, containerID)
var errs []string
for _, ipn := range ipns {
err := TeardownIPMasq(ipn, chain, comment)
if err != nil {
errs = append(errs, err.Error())
}
}
if errs == nil {
return nil
}
return errors.New(strings.Join(errs, "\n"))
}
// TeardownIPMasq undoes the effects of SetupIPMasq.
// Deprecated: This function only supports iptables. Use TeardownIPMasqForNetworks, which
// supports both iptables and nftables.
func TeardownIPMasq(ipn *net.IPNet, chain string, comment string) error {
isV6 := ipn.IP.To4() == nil
var ipt *iptables.IPTables
var err error
if isV6 {
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv6)
} else {
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
}
if err != nil {
return fmt.Errorf("failed to locate iptables: %v", err)
}
err = ipt.Delete("nat", "POSTROUTING", "-s", ipn.IP.String(), "-j", chain, "-m", "comment", "--comment", comment)
if err != nil && !isNotExist(err) {
return err
}
// for downward compatibility
err = ipt.Delete("nat", "POSTROUTING", "-s", ipn.String(), "-j", chain, "-m", "comment", "--comment", comment)
if err != nil && !isNotExist(err) {
return err
}
err = ipt.ClearChain("nat", chain)
if err != nil && !isNotExist(err) {
return err
}
err = ipt.DeleteChain("nat", chain)
if err != nil && !isNotExist(err) {
return err
}
return nil
}
// gcIPMasqIPTables is the iptables-based implementation of GCIPMasqForNetwork
func gcIPMasqIPTables(_ string, _ []types.GCAttachment) error {
// FIXME: The iptables implementation does not support GC.
//
// (In theory, it _could_ backward-compatibly support it, by adding a no-op rule
// with a comment indicating the network to each chain it creates, so that it
// could later figure out which chains corresponded to which networks; older
// implementations would ignore the extra rule but would still correctly delete
// the chain on teardown (because they ClearChain() before doing DeleteChain()).
return nil
}
// isNotExist returnst true if the error is from iptables indicating
// that the target does not exist.
func isNotExist(err error) bool {
e, ok := err.(*iptables.Error)
if !ok {
return false
}
return e.IsNotExist()
}

View File

@ -15,111 +15,78 @@
package ip
import (
"errors"
"fmt"
"net"
"strings"
"github.com/coreos/go-iptables/iptables"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/plugins/pkg/utils"
)
// SetupIPMasq installs iptables rules to masquerade traffic
// coming from ip of ipn and going outside of ipn
func SetupIPMasq(ipn *net.IPNet, chain string, comment string) error {
isV6 := ipn.IP.To4() == nil
var ipt *iptables.IPTables
var err error
var multicastNet string
if isV6 {
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv6)
multicastNet = "ff00::/8"
} else {
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
multicastNet = "224.0.0.0/4"
}
if err != nil {
return fmt.Errorf("failed to locate iptables: %v", err)
}
// Create chain if doesn't exist
exists := false
chains, err := ipt.ListChains("nat")
if err != nil {
return fmt.Errorf("failed to list chains: %v", err)
}
for _, ch := range chains {
if ch == chain {
exists = true
break
}
}
if !exists {
if err = ipt.NewChain("nat", chain); err != nil {
return err
// SetupIPMasqForNetworks installs rules to masquerade traffic coming from ips of ipns and
// going outside of ipns, using a chain name based on network, ifname, and containerID. The
// backend can be either "iptables" or "nftables"; if it is nil, then a suitable default
// implementation will be used.
func SetupIPMasqForNetworks(backend *string, ipns []*net.IPNet, network, ifname, containerID string) error {
if backend == nil {
// Prefer iptables, unless only nftables is available
defaultBackend := "iptables"
if !utils.SupportsIPTables() && utils.SupportsNFTables() {
defaultBackend = "nftables"
}
backend = &defaultBackend
}
// Packets to this network should not be touched
if err := ipt.AppendUnique("nat", chain, "-d", ipn.String(), "-j", "ACCEPT", "-m", "comment", "--comment", comment); err != nil {
return err
switch *backend {
case "iptables":
return setupIPMasqIPTables(ipns, network, ifname, containerID)
case "nftables":
return setupIPMasqNFTables(ipns, network, ifname, containerID)
default:
return fmt.Errorf("unknown ipmasq backend %q", *backend)
}
// Don't masquerade multicast - pods should be able to talk to other pods
// on the local network via multicast.
if err := ipt.AppendUnique("nat", chain, "!", "-d", multicastNet, "-j", "MASQUERADE", "-m", "comment", "--comment", comment); err != nil {
return err
}
// Packets from the specific IP of this network will hit the chain
return ipt.AppendUnique("nat", "POSTROUTING", "-s", ipn.IP.String(), "-j", chain, "-m", "comment", "--comment", comment)
}
// TeardownIPMasq undoes the effects of SetupIPMasq
func TeardownIPMasq(ipn *net.IPNet, chain string, comment string) error {
isV6 := ipn.IP.To4() == nil
// TeardownIPMasqForNetworks undoes the effects of SetupIPMasqForNetworks
func TeardownIPMasqForNetworks(ipns []*net.IPNet, network, ifname, containerID string) error {
var errs []string
var ipt *iptables.IPTables
var err error
// Do both the iptables and the nftables cleanup, since the pod may have been
// created with a different version of this plugin or a different configuration.
if isV6 {
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv6)
} else {
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
}
if err != nil {
return fmt.Errorf("failed to locate iptables: %v", err)
err := teardownIPMasqIPTables(ipns, network, ifname, containerID)
if err != nil && utils.SupportsIPTables() {
errs = append(errs, err.Error())
}
err = ipt.Delete("nat", "POSTROUTING", "-s", ipn.IP.String(), "-j", chain, "-m", "comment", "--comment", comment)
if err != nil && !isNotExist(err) {
return err
err = teardownIPMasqNFTables(ipns, network, ifname, containerID)
if err != nil && utils.SupportsNFTables() {
errs = append(errs, err.Error())
}
// for downward compatibility
err = ipt.Delete("nat", "POSTROUTING", "-s", ipn.String(), "-j", chain, "-m", "comment", "--comment", comment)
if err != nil && !isNotExist(err) {
return err
if errs == nil {
return nil
}
err = ipt.ClearChain("nat", chain)
if err != nil && !isNotExist(err) {
return err
}
err = ipt.DeleteChain("nat", chain)
if err != nil && !isNotExist(err) {
return err
}
return nil
return errors.New(strings.Join(errs, "\n"))
}
// isNotExist returnst true if the error is from iptables indicating
// that the target does not exist.
func isNotExist(err error) bool {
e, ok := err.(*iptables.Error)
if !ok {
return false
// GCIPMasqForNetwork garbage collects stale IPMasq entries for network
func GCIPMasqForNetwork(network string, attachments []types.GCAttachment) error {
var errs []string
err := gcIPMasqIPTables(network, attachments)
if err != nil && utils.SupportsIPTables() {
errs = append(errs, err.Error())
}
return e.IsNotExist()
err = gcIPMasqNFTables(network, attachments)
if err != nil && utils.SupportsNFTables() {
errs = append(errs, err.Error())
}
if errs == nil {
return nil
}
return errors.New(strings.Join(errs, "\n"))
}

View File

@ -0,0 +1,231 @@
// Copyright 2023 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 ip
import (
"context"
"fmt"
"net"
"strings"
"sigs.k8s.io/knftables"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/plugins/pkg/utils"
)
const (
ipMasqTableName = "cni_plugins_masquerade"
ipMasqChainName = "masq_checks"
)
// The nftables ipmasq implementation is mostly like the iptables implementation, with
// minor updates to fix a bug (adding `ifname`) and to allow future GC support.
//
// We add a rule for each mapping, with a comment containing a hash of its identifiers,
// so that we can later reliably delete the rules we want. (This is important because in
// edge cases, it's possible the plugin might see "ADD container A with IP 192.168.1.3",
// followed by "ADD container B with IP 192.168.1.3" followed by "DEL container A with IP
// 192.168.1.3", and we need to make sure that the DEL causes us to delete the rule for
// container A, and not the rule for container B.)
//
// It would be more nftables-y to have a chain with a single rule doing a lookup against a
// set with an element per mapping, rather than having a chain with a rule per mapping.
// But there's no easy, non-racy way to say "delete the element 192.168.1.3 from the set,
// but only if it was added for container A, not if it was added for container B".
// hashForNetwork returns a unique hash for this network
func hashForNetwork(network string) string {
return utils.MustFormatHashWithPrefix(16, "", network)
}
// hashForInstance returns a unique hash identifying the rules for this
// network/ifname/containerID
func hashForInstance(network, ifname, containerID string) string {
return hashForNetwork(network) + "-" + utils.MustFormatHashWithPrefix(16, "", ifname+":"+containerID)
}
// commentForInstance returns a comment string that begins with a unique hash and
// ends with a (possibly-truncated) human-readable description.
func commentForInstance(network, ifname, containerID string) string {
comment := fmt.Sprintf("%s, net: %s, if: %s, id: %s",
hashForInstance(network, ifname, containerID),
strings.ReplaceAll(network, `"`, ``),
strings.ReplaceAll(ifname, `"`, ``),
strings.ReplaceAll(containerID, `"`, ``),
)
if len(comment) > knftables.CommentLengthMax {
comment = comment[:knftables.CommentLengthMax]
}
return comment
}
// setupIPMasqNFTables is the nftables-based implementation of SetupIPMasqForNetworks
func setupIPMasqNFTables(ipns []*net.IPNet, network, ifname, containerID string) error {
nft, err := knftables.New(knftables.InetFamily, ipMasqTableName)
if err != nil {
return err
}
return setupIPMasqNFTablesWithInterface(nft, ipns, network, ifname, containerID)
}
func setupIPMasqNFTablesWithInterface(nft knftables.Interface, ipns []*net.IPNet, network, ifname, containerID string) error {
staleRules, err := findRules(nft, hashForInstance(network, ifname, containerID))
if err != nil {
return err
}
tx := nft.NewTransaction()
// Ensure that our table and chains exist.
tx.Add(&knftables.Table{
Comment: knftables.PtrTo("Masquerading for plugins from github.com/containernetworking/plugins"),
})
tx.Add(&knftables.Chain{
Name: ipMasqChainName,
Comment: knftables.PtrTo("Masquerade traffic from certain IPs to any (non-multicast) IP outside their subnet"),
})
// Ensure that the postrouting chain exists and has the correct rules. (Has to be
// done after creating ipMasqChainName, so we can jump to it.)
tx.Add(&knftables.Chain{
Name: "postrouting",
Type: knftables.PtrTo(knftables.NATType),
Hook: knftables.PtrTo(knftables.PostroutingHook),
Priority: knftables.PtrTo(knftables.SNATPriority),
})
tx.Flush(&knftables.Chain{
Name: "postrouting",
})
tx.Add(&knftables.Rule{
Chain: "postrouting",
Rule: "ip daddr == 224.0.0.0/4 return",
})
tx.Add(&knftables.Rule{
Chain: "postrouting",
Rule: "ip6 daddr == ff00::/8 return",
})
tx.Add(&knftables.Rule{
Chain: "postrouting",
Rule: knftables.Concat(
"goto", ipMasqChainName,
),
})
// Delete stale rules, add new rules to masquerade chain
for _, rule := range staleRules {
tx.Delete(rule)
}
for _, ipn := range ipns {
ip := "ip"
if ipn.IP.To4() == nil {
ip = "ip6"
}
// e.g. if ipn is "192.168.1.4/24", then dstNet is "192.168.1.0/24"
dstNet := &net.IPNet{IP: ipn.IP.Mask(ipn.Mask), Mask: ipn.Mask}
tx.Add(&knftables.Rule{
Chain: ipMasqChainName,
Rule: knftables.Concat(
ip, "saddr", "==", ipn.IP,
ip, "daddr", "!=", dstNet,
"masquerade",
),
Comment: knftables.PtrTo(commentForInstance(network, ifname, containerID)),
})
}
return nft.Run(context.TODO(), tx)
}
// teardownIPMasqNFTables is the nftables-based implementation of TeardownIPMasqForNetworks
func teardownIPMasqNFTables(ipns []*net.IPNet, network, ifname, containerID string) error {
nft, err := knftables.New(knftables.InetFamily, ipMasqTableName)
if err != nil {
return err
}
return teardownIPMasqNFTablesWithInterface(nft, ipns, network, ifname, containerID)
}
func teardownIPMasqNFTablesWithInterface(nft knftables.Interface, _ []*net.IPNet, network, ifname, containerID string) error {
rules, err := findRules(nft, hashForInstance(network, ifname, containerID))
if err != nil {
return err
} else if len(rules) == 0 {
return nil
}
tx := nft.NewTransaction()
for _, rule := range rules {
tx.Delete(rule)
}
return nft.Run(context.TODO(), tx)
}
// gcIPMasqNFTables is the nftables-based implementation of GCIPMasqForNetwork
func gcIPMasqNFTables(network string, attachments []types.GCAttachment) error {
nft, err := knftables.New(knftables.InetFamily, ipMasqTableName)
if err != nil {
return err
}
return gcIPMasqNFTablesWithInterface(nft, network, attachments)
}
func gcIPMasqNFTablesWithInterface(nft knftables.Interface, network string, attachments []types.GCAttachment) error {
// Find all rules for the network
rules, err := findRules(nft, hashForNetwork(network))
if err != nil {
return err
} else if len(rules) == 0 {
return nil
}
// Compute the comments for all elements of attachments
validAttachments := map[string]bool{}
for _, attachment := range attachments {
validAttachments[commentForInstance(network, attachment.IfName, attachment.ContainerID)] = true
}
// Delete anything in rules that isn't in validAttachments
tx := nft.NewTransaction()
for _, rule := range rules {
if !validAttachments[*rule.Comment] {
tx.Delete(rule)
}
}
return nft.Run(context.TODO(), tx)
}
// findRules finds rules with comments that start with commentPrefix.
func findRules(nft knftables.Interface, commentPrefix string) ([]*knftables.Rule, error) {
rules, err := nft.ListRules(context.TODO(), ipMasqChainName)
if err != nil {
if knftables.IsNotFound(err) {
// If ipMasqChainName doesn't exist yet, that's fine
return nil, nil
}
return nil, err
}
matchingRules := make([]*knftables.Rule, 0, 1)
for _, rule := range rules {
if rule.Comment != nil && strings.HasPrefix(*rule.Comment, commentPrefix) {
matchingRules = append(matchingRules, rule)
}
}
return matchingRules, nil
}

View File

@ -0,0 +1,213 @@
// Copyright 2023 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 ip
import (
"net"
"strings"
"testing"
"github.com/vishvananda/netlink"
"sigs.k8s.io/knftables"
"github.com/containernetworking/cni/pkg/types"
)
func Test_setupIPMasqNFTables(t *testing.T) {
nft := knftables.NewFake(knftables.InetFamily, ipMasqTableName)
containers := []struct {
network string
ifname string
containerID string
addrs []string
}{
{
network: "unit-test",
ifname: "eth0",
containerID: "one",
addrs: []string{"192.168.1.1/24"},
},
{
network: "unit-test",
ifname: "eth0",
containerID: "two",
addrs: []string{"192.168.1.2/24", "2001:db8::2/64"},
},
{
network: "unit-test",
ifname: "eth0",
containerID: "three",
addrs: []string{"192.168.99.5/24"},
},
{
network: "alternate",
ifname: "net1",
containerID: "three",
addrs: []string{
"10.0.0.5/24",
"10.0.0.6/24",
"10.0.1.7/24",
"2001:db8::5/64",
"2001:db8::6/64",
"2001:db8:1::7/64",
},
},
}
for _, c := range containers {
ipns := []*net.IPNet{}
for _, addr := range c.addrs {
nladdr, err := netlink.ParseAddr(addr)
if err != nil {
t.Fatalf("failed to parse test addr: %v", err)
}
ipns = append(ipns, nladdr.IPNet)
}
err := setupIPMasqNFTablesWithInterface(nft, ipns, c.network, c.ifname, c.containerID)
if err != nil {
t.Fatalf("error from setupIPMasqNFTables: %v", err)
}
}
expected := strings.TrimSpace(`
add table inet cni_plugins_masquerade { comment "Masquerading for plugins from github.com/containernetworking/plugins" ; }
add chain inet cni_plugins_masquerade masq_checks { comment "Masquerade traffic from certain IPs to any (non-multicast) IP outside their subnet" ; }
add chain inet cni_plugins_masquerade postrouting { type nat hook postrouting priority 100 ; }
add rule inet cni_plugins_masquerade masq_checks ip saddr == 192.168.1.1 ip daddr != 192.168.1.0/24 masquerade comment "6fd94d501e58f0aa-287fc69eff0574a2, net: unit-test, if: eth0, id: one"
add rule inet cni_plugins_masquerade masq_checks ip saddr == 192.168.1.2 ip daddr != 192.168.1.0/24 masquerade comment "6fd94d501e58f0aa-d750b2c8f0f25d5f, net: unit-test, if: eth0, id: two"
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8::2 ip6 daddr != 2001:db8::/64 masquerade comment "6fd94d501e58f0aa-d750b2c8f0f25d5f, net: unit-test, if: eth0, id: two"
add rule inet cni_plugins_masquerade masq_checks ip saddr == 192.168.99.5 ip daddr != 192.168.99.0/24 masquerade comment "6fd94d501e58f0aa-a4d4adb82b669cfe, net: unit-test, if: eth0, id: three"
add rule inet cni_plugins_masquerade masq_checks ip saddr == 10.0.0.5 ip daddr != 10.0.0.0/24 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
add rule inet cni_plugins_masquerade masq_checks ip saddr == 10.0.0.6 ip daddr != 10.0.0.0/24 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
add rule inet cni_plugins_masquerade masq_checks ip saddr == 10.0.1.7 ip daddr != 10.0.1.0/24 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8::5 ip6 daddr != 2001:db8::/64 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8::6 ip6 daddr != 2001:db8::/64 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8:1::7 ip6 daddr != 2001:db8:1::/64 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
add rule inet cni_plugins_masquerade postrouting ip daddr == 224.0.0.0/4 return
add rule inet cni_plugins_masquerade postrouting ip6 daddr == ff00::/8 return
add rule inet cni_plugins_masquerade postrouting goto masq_checks
`)
dump := strings.TrimSpace(nft.Dump())
if dump != expected {
t.Errorf("expected nftables state:\n%s\n\nactual:\n%s\n\n", expected, dump)
}
// Add a new container reusing "one"'s address, before deleting "one"
c := containers[0]
addr, err := netlink.ParseAddr(c.addrs[0])
if err != nil {
t.Fatalf("failed to parse test addr: %v", err)
}
err = setupIPMasqNFTablesWithInterface(nft, []*net.IPNet{addr.IPNet}, "unit-test", "eth0", "four")
if err != nil {
t.Fatalf("error from setupIPMasqNFTables: %v", err)
}
// Remove "one"
err = teardownIPMasqNFTablesWithInterface(nft, []*net.IPNet{addr.IPNet}, c.network, c.ifname, c.containerID)
if err != nil {
t.Fatalf("error from teardownIPMasqNFTables: %v", err)
}
// Check that "one" was deleted (and "four" wasn't)
expected = strings.TrimSpace(`
add table inet cni_plugins_masquerade { comment "Masquerading for plugins from github.com/containernetworking/plugins" ; }
add chain inet cni_plugins_masquerade masq_checks { comment "Masquerade traffic from certain IPs to any (non-multicast) IP outside their subnet" ; }
add chain inet cni_plugins_masquerade postrouting { type nat hook postrouting priority 100 ; }
add rule inet cni_plugins_masquerade masq_checks ip saddr == 192.168.1.2 ip daddr != 192.168.1.0/24 masquerade comment "6fd94d501e58f0aa-d750b2c8f0f25d5f, net: unit-test, if: eth0, id: two"
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8::2 ip6 daddr != 2001:db8::/64 masquerade comment "6fd94d501e58f0aa-d750b2c8f0f25d5f, net: unit-test, if: eth0, id: two"
add rule inet cni_plugins_masquerade masq_checks ip saddr == 192.168.99.5 ip daddr != 192.168.99.0/24 masquerade comment "6fd94d501e58f0aa-a4d4adb82b669cfe, net: unit-test, if: eth0, id: three"
add rule inet cni_plugins_masquerade masq_checks ip saddr == 10.0.0.5 ip daddr != 10.0.0.0/24 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
add rule inet cni_plugins_masquerade masq_checks ip saddr == 10.0.0.6 ip daddr != 10.0.0.0/24 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
add rule inet cni_plugins_masquerade masq_checks ip saddr == 10.0.1.7 ip daddr != 10.0.1.0/24 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8::5 ip6 daddr != 2001:db8::/64 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8::6 ip6 daddr != 2001:db8::/64 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8:1::7 ip6 daddr != 2001:db8:1::/64 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
add rule inet cni_plugins_masquerade masq_checks ip saddr == 192.168.1.1 ip daddr != 192.168.1.0/24 masquerade comment "6fd94d501e58f0aa-e766de567ef6c543, net: unit-test, if: eth0, id: four"
add rule inet cni_plugins_masquerade postrouting ip daddr == 224.0.0.0/4 return
add rule inet cni_plugins_masquerade postrouting ip6 daddr == ff00::/8 return
add rule inet cni_plugins_masquerade postrouting goto masq_checks
`)
dump = strings.TrimSpace(nft.Dump())
if dump != expected {
t.Errorf("expected nftables state:\n%s\n\nactual:\n%s\n\n", expected, dump)
}
// GC "four" from the "unit-test" network
err = gcIPMasqNFTablesWithInterface(nft, "unit-test", []types.GCAttachment{
{IfName: "eth0", ContainerID: "two"},
{IfName: "eth0", ContainerID: "three"},
// (irrelevant extra element)
{IfName: "eth0", ContainerID: "one"},
})
if err != nil {
t.Fatalf("error from gcIPMasqNFTables: %v", err)
}
// GC the "alternate" network without removing anything
err = gcIPMasqNFTablesWithInterface(nft, "alternate", []types.GCAttachment{
{IfName: "net1", ContainerID: "three"},
})
if err != nil {
t.Fatalf("error from gcIPMasqNFTables: %v", err)
}
// Re-dump
expected = strings.TrimSpace(`
add table inet cni_plugins_masquerade { comment "Masquerading for plugins from github.com/containernetworking/plugins" ; }
add chain inet cni_plugins_masquerade masq_checks { comment "Masquerade traffic from certain IPs to any (non-multicast) IP outside their subnet" ; }
add chain inet cni_plugins_masquerade postrouting { type nat hook postrouting priority 100 ; }
add rule inet cni_plugins_masquerade masq_checks ip saddr == 192.168.1.2 ip daddr != 192.168.1.0/24 masquerade comment "6fd94d501e58f0aa-d750b2c8f0f25d5f, net: unit-test, if: eth0, id: two"
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8::2 ip6 daddr != 2001:db8::/64 masquerade comment "6fd94d501e58f0aa-d750b2c8f0f25d5f, net: unit-test, if: eth0, id: two"
add rule inet cni_plugins_masquerade masq_checks ip saddr == 192.168.99.5 ip daddr != 192.168.99.0/24 masquerade comment "6fd94d501e58f0aa-a4d4adb82b669cfe, net: unit-test, if: eth0, id: three"
add rule inet cni_plugins_masquerade masq_checks ip saddr == 10.0.0.5 ip daddr != 10.0.0.0/24 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
add rule inet cni_plugins_masquerade masq_checks ip saddr == 10.0.0.6 ip daddr != 10.0.0.0/24 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
add rule inet cni_plugins_masquerade masq_checks ip saddr == 10.0.1.7 ip daddr != 10.0.1.0/24 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8::5 ip6 daddr != 2001:db8::/64 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8::6 ip6 daddr != 2001:db8::/64 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8:1::7 ip6 daddr != 2001:db8:1::/64 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
add rule inet cni_plugins_masquerade postrouting ip daddr == 224.0.0.0/4 return
add rule inet cni_plugins_masquerade postrouting ip6 daddr == ff00::/8 return
add rule inet cni_plugins_masquerade postrouting goto masq_checks
`)
dump = strings.TrimSpace(nft.Dump())
if dump != expected {
t.Errorf("expected nftables state:\n%s\n\nactual:\n%s\n\n", expected, dump)
}
// GC everything
err = gcIPMasqNFTablesWithInterface(nft, "unit-test", []types.GCAttachment{})
if err != nil {
t.Fatalf("error from gcIPMasqNFTables: %v", err)
}
err = gcIPMasqNFTablesWithInterface(nft, "alternate", []types.GCAttachment{})
if err != nil {
t.Fatalf("error from gcIPMasqNFTables: %v", err)
}
expected = strings.TrimSpace(`
add table inet cni_plugins_masquerade { comment "Masquerading for plugins from github.com/containernetworking/plugins" ; }
add chain inet cni_plugins_masquerade masq_checks { comment "Masquerade traffic from certain IPs to any (non-multicast) IP outside their subnet" ; }
add chain inet cni_plugins_masquerade postrouting { type nat hook postrouting priority 100 ; }
add rule inet cni_plugins_masquerade postrouting ip daddr == 224.0.0.0/4 return
add rule inet cni_plugins_masquerade postrouting ip6 daddr == ff00::/8 return
add rule inet cni_plugins_masquerade postrouting goto masq_checks
`)
dump = strings.TrimSpace(nft.Dump())
if dump != expected {
t.Errorf("expected nftables state:\n%s\n\nactual:\n%s\n\n", expected, dump)
}
}

View File

@ -32,11 +32,12 @@ var ErrLinkNotFound = errors.New("link not found")
// makeVethPair is called from within the container's network namespace
func makeVethPair(name, peer string, mtu int, mac string, hostNS ns.NetNS) (netlink.Link, error) {
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = name
linkAttrs.MTU = mtu
veth := &netlink.Veth{
LinkAttrs: netlink.LinkAttrs{
Name: name,
MTU: mtu,
},
LinkAttrs: linkAttrs,
PeerName: peer,
PeerNamespace: netlink.NsFd(int(hostNS.Fd())),
}
@ -90,7 +91,7 @@ func makeVeth(name, vethPeerName string, mtu int, mac string, hostNS ns.NetNS) (
if peerExists(peerName) && vethPeerName == "" {
continue
}
return peerName, veth, fmt.Errorf("container veth name provided (%v) already exists", name)
return peerName, veth, fmt.Errorf("container veth name (%q) peer provided (%q) already exists", name, peerName)
default:
return peerName, veth, fmt.Errorf("failed to make veth pair: %v", err)
}

View File

@ -149,9 +149,9 @@ var _ = Describe("Link", func() {
It("returns useful error", func() {
_ = containerNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, _, err := ip.SetupVeth(containerVethName, mtu, "", hostNetNS)
Expect(err.Error()).To(Equal(fmt.Sprintf("container veth name provided (%s) already exists", containerVethName)))
testHostVethName := "test" + hostVethName
_, _, err := ip.SetupVethWithName(containerVethName, testHostVethName, mtu, "", hostNetNS)
Expect(err.Error()).To(Equal(fmt.Sprintf("container veth name (%q) peer provided (%q) already exists", containerVethName, testHostVethName)))
return nil
})
@ -180,9 +180,8 @@ var _ = Describe("Link", func() {
It("returns useful error", func() {
_ = containerNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, _, err := ip.SetupVeth(containerVethName, mtu, "", hostNetNS)
Expect(err.Error()).To(HavePrefix("container veth name provided"))
Expect(err.Error()).To(HaveSuffix("already exists"))
_, _, err := ip.SetupVethWithName(containerVethName, hostVethName, mtu, "", hostNetNS)
Expect(err.Error()).To(Equal(fmt.Sprintf("container veth name (%q) peer provided (%q) already exists", containerVethName, hostVethName)))
return nil
})
})

View File

@ -50,3 +50,16 @@ func AddDefaultRoute(gw net.IP, dev netlink.Link) error {
}
return AddRoute(defNet, gw, dev)
}
// IsIPNetZero check if the IPNet is "0.0.0.0/0" or "::/0"
// This is needed as go-netlink replaces nil Dst with a '0' IPNet since
// https://github.com/vishvananda/netlink/commit/acdc658b8613655ddb69f978e9fb4cf413e2b830
func IsIPNetZero(ipnet *net.IPNet) bool {
if ipnet == nil {
return true
}
if ones, _ := ipnet.Mask.Size(); ones != 0 {
return false
}
return ipnet.IP.Equal(net.IPv4zero) || ipnet.IP.Equal(net.IPv6zero)
}

View File

@ -32,3 +32,7 @@ func ExecCheck(plugin string, netconf []byte) error {
func ExecDel(plugin string, netconf []byte) error {
return invoke.DelegateDel(context.TODO(), plugin, netconf, nil)
}
func ExecStatus(plugin string, netconf []byte) error {
return invoke.DelegateStatus(context.TODO(), plugin, netconf, nil)
}

View File

@ -117,10 +117,27 @@ func ConfigureIface(ifName string, res *current.Result) error {
Dst: &r.Dst,
LinkIndex: link.Attrs().Index,
Gw: gw,
Priority: r.Priority,
}
if r.Table != nil {
route.Table = *r.Table
}
if r.Scope != nil {
route.Scope = netlink.Scope(*r.Scope)
}
if r.Table != nil {
route.Table = *r.Table
}
if r.Scope != nil {
route.Scope = netlink.Scope(*r.Scope)
}
if err = netlink.RouteAddEcmp(&route); err != nil {
return fmt.Errorf("failed to add route '%v via %v dev %v': %v", r.Dst, gw, ifName, err)
return fmt.Errorf("failed to add route '%v via %v dev %v metric %d (Scope: %v, Table: %d)': %v", r.Dst, gw, ifName, r.Priority, route.Scope, route.Table, err)
}
}

View File

@ -41,9 +41,11 @@ func ipNetEqual(a, b *net.IPNet) bool {
var _ = Describe("ConfigureIface", func() {
var originalNS ns.NetNS
var ipv4, ipv6, routev4, routev6 *net.IPNet
var ipv4, ipv6, routev4, routev6, routev4Scope *net.IPNet
var ipgw4, ipgw6, routegwv4, routegwv6 net.IP
var routeScope int
var result *current.Result
var routeTable int
BeforeEach(func() {
// Create a new NetNS so we don't modify the host
@ -54,11 +56,12 @@ var _ = Describe("ConfigureIface", func() {
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = LINK_NAME
// Add master
err = netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: LINK_NAME,
},
LinkAttrs: linkAttrs,
})
Expect(err).NotTo(HaveOccurred())
_, err = netlink.LinkByName(LINK_NAME)
@ -77,6 +80,10 @@ var _ = Describe("ConfigureIface", func() {
routegwv4 = net.ParseIP("1.2.3.5")
Expect(routegwv4).NotTo(BeNil())
_, routev4Scope, err = net.ParseCIDR("1.2.3.4/32")
Expect(err).NotTo(HaveOccurred())
Expect(routev4Scope).NotTo(BeNil())
ipgw4 = net.ParseIP("1.2.3.1")
Expect(ipgw4).NotTo(BeNil())
@ -93,6 +100,9 @@ var _ = Describe("ConfigureIface", func() {
ipgw6 = net.ParseIP("abcd:1234:ffff::1")
Expect(ipgw6).NotTo(BeNil())
routeTable := 5000
routeScope = 200
result = &current.Result{
Interfaces: []*current.Interface{
{
@ -121,6 +131,8 @@ var _ = Describe("ConfigureIface", func() {
Routes: []*types.Route{
{Dst: *routev4, GW: routegwv4},
{Dst: *routev6, GW: routegwv6},
{Dst: *routev4, GW: routegwv4, Table: &routeTable},
{Dst: *routev4Scope, Scope: &routeScope},
},
}
})
@ -162,7 +174,7 @@ var _ = Describe("ConfigureIface", func() {
routes, err := netlink.RouteList(link, 0)
Expect(err).NotTo(HaveOccurred())
var v4found, v6found bool
var v4found, v6found, v4Scopefound bool
for _, route := range routes {
isv4 := route.Dst.IP.To4() != nil
if isv4 && ipNetEqual(route.Dst, routev4) && route.Gw.Equal(routegwv4) {
@ -171,13 +183,17 @@ var _ = Describe("ConfigureIface", func() {
if !isv4 && ipNetEqual(route.Dst, routev6) && route.Gw.Equal(routegwv6) {
v6found = true
}
if isv4 && ipNetEqual(route.Dst, routev4Scope) && int(route.Scope) == routeScope {
v4Scopefound = true
}
if v4found && v6found {
if v4found && v6found && v4Scopefound {
break
}
}
Expect(v4found).To(BeTrue())
Expect(v6found).To(BeTrue())
Expect(v4Scopefound).To(BeTrue())
return nil
})
@ -201,7 +217,7 @@ var _ = Describe("ConfigureIface", func() {
routes, err := netlink.RouteList(link, 0)
Expect(err).NotTo(HaveOccurred())
var v4found, v6found bool
var v4found, v6found, v4Tablefound bool
for _, route := range routes {
isv4 := route.Dst.IP.To4() != nil
if isv4 && ipNetEqual(route.Dst, routev4) && route.Gw.Equal(ipgw4) {
@ -218,6 +234,29 @@ var _ = Describe("ConfigureIface", func() {
Expect(v4found).To(BeTrue())
Expect(v6found).To(BeTrue())
// Need to read all tables, so cannot use RouteList
routeFilter := &netlink.Route{
Table: routeTable,
}
routes, err = netlink.RouteListFiltered(netlink.FAMILY_ALL,
routeFilter,
netlink.RT_FILTER_TABLE)
Expect(err).NotTo(HaveOccurred())
for _, route := range routes {
isv4 := route.Dst.IP.To4() != nil
if isv4 && ipNetEqual(route.Dst, routev4) && route.Gw.Equal(ipgw4) {
v4Tablefound = true
}
if v4Tablefound {
break
}
}
Expect(v4Tablefound).To(BeTrue())
return nil
})
Expect(err).NotTo(HaveOccurred())

View File

@ -30,7 +30,7 @@ const (
)
type NftConfigurer interface {
Apply(*nft.Config) error
Apply(*nft.Config) (*nft.Config, error)
Read(filterCommands ...string) (*nft.Config, error)
}
@ -39,12 +39,16 @@ type SpoofChecker struct {
macAddress string
refID string
configurer NftConfigurer
rulestore *nft.Config
}
type defaultNftConfigurer struct{}
func (dnc defaultNftConfigurer) Apply(cfg *nft.Config) error {
return nft.ApplyConfig(cfg)
func (dnc defaultNftConfigurer) Apply(cfg *nft.Config) (*nft.Config, error) {
const timeout = 55 * time.Second
ctxWithTimeout, cancelFunc := context.WithTimeout(context.Background(), timeout)
defer cancelFunc()
return nft.ApplyConfigEcho(ctxWithTimeout, cfg)
}
func (dnc defaultNftConfigurer) Read(filterCommands ...string) (*nft.Config, error) {
@ -59,7 +63,7 @@ func NewSpoofChecker(iface, macAddress, refID string) *SpoofChecker {
}
func NewSpoofCheckerWithConfigurer(iface, macAddress, refID string, configurer NftConfigurer) *SpoofChecker {
return &SpoofChecker{iface, macAddress, refID, configurer}
return &SpoofChecker{iface, macAddress, refID, configurer, nil}
}
// Setup applies nftables configuration to restrict traffic
@ -88,7 +92,7 @@ func (sc *SpoofChecker) Setup() error {
macChain := sc.macChain(ifaceChain.Name)
baseConfig.AddChain(macChain)
if err := sc.configurer.Apply(baseConfig); err != nil {
if _, err := sc.configurer.Apply(baseConfig); err != nil {
return fmt.Errorf("failed to setup spoof-check: %v", err)
}
@ -102,37 +106,51 @@ func (sc *SpoofChecker) Setup() error {
rulesConfig.AddRule(sc.matchMacRule(macChain.Name))
rulesConfig.AddRule(sc.dropRule(macChain.Name))
if err := sc.configurer.Apply(rulesConfig); err != nil {
rulestore, err := sc.configurer.Apply(rulesConfig)
if err != nil {
return fmt.Errorf("failed to setup spoof-check: %v", err)
}
sc.rulestore = rulestore
return nil
}
func (sc *SpoofChecker) findPreroutingRule(ruleToFind *schema.Rule) ([]*schema.Rule, error) {
ruleset := sc.rulestore
if ruleset == nil {
chain, err := sc.configurer.Read(listChainBridgeNatPrerouting()...)
if err != nil {
return nil, err
}
ruleset = chain
}
return ruleset.LookupRule(ruleToFind), nil
}
// Teardown removes the interface and mac-address specific chains and their rules.
// The table and base-chain are expected to survive while the base-chain rule that matches the
// interface is removed.
func (sc *SpoofChecker) Teardown() error {
ifaceChain := sc.ifaceChain()
currentConfig, ifaceMatchRuleErr := sc.configurer.Read(listChainBridgeNatPrerouting()...)
if ifaceMatchRuleErr == nil {
expectedRuleToFind := sc.matchIfaceJumpToChainRule(preRoutingBaseChainName, ifaceChain.Name)
// It is safer to exclude the statement matching, avoiding cases where a current statement includes
// additional default entries (e.g. counters).
ruleToFindExcludingStatements := *expectedRuleToFind
ruleToFindExcludingStatements.Expr = nil
rules := currentConfig.LookupRule(&ruleToFindExcludingStatements)
if len(rules) > 0 {
c := nft.NewConfig()
for _, rule := range rules {
c.DeleteRule(rule)
}
if err := sc.configurer.Apply(c); err != nil {
ifaceMatchRuleErr = fmt.Errorf("failed to delete iface match rule: %v", err)
}
} else {
fmt.Fprintf(os.Stderr, "spoofcheck/teardown: unable to detect iface match rule for deletion: %+v", expectedRuleToFind)
expectedRuleToFind := sc.matchIfaceJumpToChainRule(preRoutingBaseChainName, ifaceChain.Name)
// It is safer to exclude the statement matching, avoiding cases where a current statement includes
// additional default entries (e.g. counters).
ruleToFindExcludingStatements := *expectedRuleToFind
ruleToFindExcludingStatements.Expr = nil
rules, ifaceMatchRuleErr := sc.findPreroutingRule(&ruleToFindExcludingStatements)
if ifaceMatchRuleErr == nil && len(rules) > 0 {
c := nft.NewConfig()
for _, rule := range rules {
c.DeleteRule(rule)
}
if _, err := sc.configurer.Apply(c); err != nil {
ifaceMatchRuleErr = fmt.Errorf("failed to delete iface match rule: %v", err)
}
// Drop the cache, it should contain deleted rule(s) now
sc.rulestore = nil
} else {
fmt.Fprintf(os.Stderr, "spoofcheck/teardown: unable to detect iface match rule for deletion: %+v", expectedRuleToFind)
}
regularChainsConfig := nft.NewConfig()
@ -140,7 +158,7 @@ func (sc *SpoofChecker) Teardown() error {
regularChainsConfig.DeleteChain(sc.macChain(ifaceChain.Name))
var regularChainsErr error
if err := sc.configurer.Apply(regularChainsConfig); err != nil {
if _, err := sc.configurer.Apply(regularChainsConfig); err != nil {
regularChainsErr = fmt.Errorf("failed to delete regular chains: %v", err)
}

View File

@ -15,6 +15,7 @@
package link_test
import (
"errors"
"fmt"
"github.com/networkplumbing/go-nft/nft"
@ -113,6 +114,25 @@ var _ = Describe("spoofcheck", func() {
)))
})
})
Context("echo", func() {
It("succeeds, no read called", func() {
c := configurerStub{}
sc := link.NewSpoofCheckerWithConfigurer(iface, mac, id, &c)
Expect(sc.Setup()).To(Succeed())
Expect(sc.Teardown()).To(Succeed())
Expect(c.readCalled).To(BeFalse())
})
It("succeeds, fall back to config read", func() {
c := configurerStub{applyReturnNil: true}
sc := link.NewSpoofCheckerWithConfigurer(iface, mac, id, &c)
Expect(sc.Setup()).To(Succeed())
c.readConfig = c.applyConfig[0]
Expect(sc.Teardown()).To(Succeed())
Expect(c.readCalled).To(BeTrue())
})
})
})
func assertExpectedRegularChainsDeletionInTeardownConfig(action configurerStub) {
@ -274,23 +294,30 @@ type configurerStub struct {
failFirstApplyConfig bool
failSecondApplyConfig bool
failReadConfig bool
applyReturnNil bool
readCalled bool
}
func (a *configurerStub) Apply(c *nft.Config) error {
func (a *configurerStub) Apply(c *nft.Config) (*nft.Config, error) {
a.applyCounter++
if a.failFirstApplyConfig && a.applyCounter == 1 {
return fmt.Errorf(errorFirstApplyText)
return nil, errors.New(errorFirstApplyText)
}
if a.failSecondApplyConfig && a.applyCounter == 2 {
return fmt.Errorf(errorSecondApplyText)
return nil, errors.New(errorSecondApplyText)
}
a.applyConfig = append(a.applyConfig, c)
return nil
if a.applyReturnNil {
return nil, nil
}
return c, nil
}
func (a *configurerStub) Read(_ ...string) (*nft.Config, error) {
a.readCalled = true
if a.failReadConfig {
return nil, fmt.Errorf(errorReadText)
return nil, errors.New(errorReadText)
}
return a.readConfig, nil
}

View File

@ -13,10 +13,10 @@ The `ns.Do()` method provides **partial** control over network namespaces for yo
```go
err = targetNs.Do(func(hostNs ns.NetNS) error {
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = "dummy0"
dummy := &netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: "dummy0",
},
LinkAttrs: linkAttrs,
}
return netlink.LinkAdd(dummy)
})

View File

@ -31,6 +31,10 @@ func GetCurrentNS() (NetNS, error) {
// return an unexpected network namespace.
runtime.LockOSThread()
defer runtime.UnlockOSThread()
return getCurrentNSNoLock()
}
func getCurrentNSNoLock() (NetNS, error) {
return GetNS(getCurrentThreadNetNSPath())
}
@ -152,6 +156,54 @@ func GetNS(nspath string) (NetNS, error) {
return &netNS{file: fd}, nil
}
// Returns a new empty NetNS.
// Calling Close() let the kernel garbage collect the network namespace.
func TempNetNS() (NetNS, error) {
var tempNS NetNS
var err error
var wg sync.WaitGroup
wg.Add(1)
// Create the new namespace in a new goroutine so that if we later fail
// to switch the namespace back to the original one, we can safely
// leave the thread locked to die without a risk of the current thread
// left lingering with incorrect namespace.
go func() {
defer wg.Done()
runtime.LockOSThread()
var threadNS NetNS
// save a handle to current network namespace
threadNS, err = getCurrentNSNoLock()
if err != nil {
err = fmt.Errorf("failed to open current namespace: %v", err)
return
}
defer threadNS.Close()
// create the temporary network namespace
err = unix.Unshare(unix.CLONE_NEWNET)
if err != nil {
return
}
// get a handle to the temporary network namespace
tempNS, err = getCurrentNSNoLock()
err2 := threadNS.Set()
if err2 == nil {
// Unlock the current thread only when we successfully switched back
// to the original namespace; otherwise leave the thread locked which
// will force the runtime to scrap the current thread, that is maybe
// not as optimal but at least always safe to do.
runtime.UnlockOSThread()
}
}()
wg.Wait()
return tempNS, err
}
func (ns *netNS) Path() string {
return ns.file.Name()
}
@ -173,7 +225,7 @@ func (ns *netNS) Do(toRun func(NetNS) error) error {
}
containedCall := func(hostNS NetNS) error {
threadNS, err := GetCurrentNS()
threadNS, err := getCurrentNSNoLock()
if err != nil {
return fmt.Errorf("failed to open current netns: %v", err)
}

View File

@ -182,7 +182,7 @@ var _ = Describe("Linux namespace operations", func() {
testNsInode, err := getInodeNS(targetNetNS)
Expect(err).NotTo(HaveOccurred())
Expect(testNsInode).NotTo(Equal(0))
Expect(testNsInode).NotTo(Equal(uint64(0)))
Expect(testNsInode).NotTo(Equal(origNSInode))
})

View File

@ -29,6 +29,7 @@ func envCleanup() {
os.Unsetenv("CNI_NETNS")
os.Unsetenv("CNI_IFNAME")
os.Unsetenv("CNI_CONTAINERID")
os.Unsetenv("CNI_NETNS_OVERRIDE")
}
func CmdAdd(cniNetns, cniContainerID, cniIfname string, conf []byte, f func() error) (types.Result, []byte, error) {
@ -37,6 +38,7 @@ func CmdAdd(cniNetns, cniContainerID, cniIfname string, conf []byte, f func() er
os.Setenv("CNI_NETNS", cniNetns)
os.Setenv("CNI_IFNAME", cniIfname)
os.Setenv("CNI_CONTAINERID", cniContainerID)
os.Setenv("CNI_NETNS_OVERRIDE", "1")
defer envCleanup()
// Redirect stdout to capture plugin result
@ -87,6 +89,7 @@ func CmdCheck(cniNetns, cniContainerID, cniIfname string, f func() error) error
os.Setenv("CNI_NETNS", cniNetns)
os.Setenv("CNI_IFNAME", cniIfname)
os.Setenv("CNI_CONTAINERID", cniContainerID)
os.Setenv("CNI_NETNS_OVERRIDE", "1")
defer envCleanup()
return f()
@ -102,6 +105,7 @@ func CmdDel(cniNetns, cniContainerID, cniIfname string, f func() error) error {
os.Setenv("CNI_NETNS", cniNetns)
os.Setenv("CNI_IFNAME", cniIfname)
os.Setenv("CNI_CONTAINERID", cniContainerID)
os.Setenv("CNI_NETNS_OVERRIDE", "1")
defer envCleanup()
return f()
@ -110,3 +114,12 @@ func CmdDel(cniNetns, cniContainerID, cniIfname string, f func() error) error {
func CmdDelWithArgs(args *skel.CmdArgs, f func() error) error {
return CmdDel(args.Netns, args.ContainerID, args.IfName, f)
}
func CmdStatus(f func() error) error {
os.Setenv("CNI_COMMAND", "STATUS")
os.Setenv("CNI_PATH", os.Getenv("PATH"))
os.Setenv("CNI_NETNS_OVERRIDE", "1")
defer envCleanup()
return f()
}

View File

@ -19,7 +19,7 @@ import (
)
// AllSpecVersions contains all CNI spec version numbers
var AllSpecVersions = [...]string{"0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0"}
var AllSpecVersions = [...]string{"0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0", "1.1.0"}
// SpecVersionHasIPVersion returns true if the given CNI specification version
// includes the "version" field in the IP address elements
@ -39,6 +39,13 @@ func SpecVersionHasCHECK(ver string) bool {
return ok
}
// SpecVersionHasSTATUS returns true if the given CNI specification version
// supports the STATUS command
func SpecVersionHasSTATUS(ver string) bool {
ok, _ := version.GreaterThanOrEqualTo(ver, "1.1.0")
return ok
}
// SpecVersionHasChaining returns true if the given CNI specification version
// supports plugin chaining
func SpecVersionHasChaining(ver string) bool {

View File

@ -51,7 +51,7 @@ func DeleteConntrackEntriesForDstIP(dstIP string, protocol uint8) error {
filter.AddIP(netlink.ConntrackOrigDstIP, ip)
filter.AddProtocol(protocol)
_, err := netlink.ConntrackDeleteFilter(netlink.ConntrackTable, family, filter)
_, err := netlink.ConntrackDeleteFilters(netlink.ConntrackTable, family, filter)
if err != nil {
return fmt.Errorf("error deleting connection tracking state for protocol: %d IP: %s, error: %v", protocol, ip, err)
}
@ -65,7 +65,7 @@ func DeleteConntrackEntriesForDstPort(port uint16, protocol uint8, family netlin
filter.AddProtocol(protocol)
filter.AddPort(netlink.ConntrackOrigDstPort, port)
_, err := netlink.ConntrackDeleteFilter(netlink.ConntrackTable, family, filter)
_, err := netlink.ConntrackDeleteFilters(netlink.ConntrackTable, family, filter)
if err != nil {
return fmt.Errorf("error deleting connection tracking state for protocol: %d Port: %d, error: %v", protocol, port, err)
}

View File

@ -29,9 +29,9 @@ func EnsureChain(ipt *iptables.IPTables, table, chain string) error {
if ipt == nil {
return errors.New("failed to ensure iptable chain: IPTables was nil")
}
exists, err := ChainExists(ipt, table, chain)
exists, err := ipt.ChainExists(table, chain)
if err != nil {
return fmt.Errorf("failed to list iptables chains: %v", err)
return fmt.Errorf("failed to check iptables chain existence: %v", err)
}
if !exists {
err = ipt.NewChain(table, chain)
@ -45,24 +45,6 @@ func EnsureChain(ipt *iptables.IPTables, table, chain string) error {
return nil
}
// ChainExists checks whether an iptables chain exists.
func ChainExists(ipt *iptables.IPTables, table, chain string) (bool, error) {
if ipt == nil {
return false, errors.New("failed to check iptable chain: IPTables was nil")
}
chains, err := ipt.ListChains(table)
if err != nil {
return false, err
}
for _, ch := range chains {
if ch == chain {
return true, nil
}
}
return false, nil
}
// DeleteRule idempotently delete the iptables rule in the specified table/chain.
// It does not return an error if the referring chain doesn't exist
func DeleteRule(ipt *iptables.IPTables, table, chain string, rulespec ...string) error {

46
pkg/utils/netfilter.go Normal file
View File

@ -0,0 +1,46 @@
// Copyright 2023 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 utils
import (
"github.com/coreos/go-iptables/iptables"
"sigs.k8s.io/knftables"
)
// SupportsIPTables tests whether the system supports using netfilter via the iptables API
// (whether via "iptables-legacy" or "iptables-nft"). (Note that this returns true if it
// is *possible* to use iptables; it does not test whether any other components on the
// system are *actually* using iptables.)
func SupportsIPTables() bool {
ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
if err != nil {
return false
}
// We don't care whether the chain actually exists, only whether we can *check*
// whether it exists.
_, err = ipt.ChainExists("filter", "INPUT")
return err == nil
}
// SupportsNFTables tests whether the system supports using netfilter via the nftables API
// (ie, not via "iptables-nft"). (Note that this returns true if it is *possible* to use
// nftables; it does not test whether any other components on the system are *actually*
// using nftables.)
func SupportsNFTables() bool {
// knftables.New() does sanity checks so we don't need any further test like in
// the iptables case.
_, err := knftables.New(knftables.IPv4Family, "supports_nftables_test")
return err == nil
}

View File

@ -0,0 +1,52 @@
// Copyright 2023 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 utils
import (
"os"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("netfilter support", func() {
When("it is available", func() {
It("reports that iptables is supported", func() {
Expect(SupportsIPTables()).To(BeTrue(), "This test should only fail if iptables is not available, but the test suite as a whole requires it to be available.")
})
It("reports that nftables is supported", func() {
Expect(SupportsNFTables()).To(BeTrue(), "This test should only fail if nftables is not available, but the test suite as a whole requires it to be available.")
})
})
// These are Serial because os.Setenv has process-wide effect
When("it is not available", Serial, func() {
var origPath string
BeforeEach(func() {
origPath = os.Getenv("PATH")
os.Setenv("PATH", "/does-not-exist")
})
AfterEach(func() {
os.Setenv("PATH", origPath)
})
It("reports that iptables is not supported", func() {
Expect(SupportsIPTables()).To(BeFalse(), "found iptables outside of PATH??")
})
It("reports that nftables is not supported", func() {
Expect(SupportsNFTables()).To(BeFalse(), "found nftables outside of PATH??")
})
})
})

View File

@ -48,11 +48,11 @@ var _ = Describe("Sysctl tests", func() {
Expect(err).NotTo(HaveOccurred())
testIfaceName = fmt.Sprintf("cnitest.%d", rand.Intn(100000))
testLinkAttrs := netlink.NewLinkAttrs()
testLinkAttrs.Name = testIfaceName
testLinkAttrs.Namespace = netlink.NsFd(int(testNs.Fd()))
testIface := &netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: testIfaceName,
Namespace: netlink.NsFd(int(testNs.Fd())),
},
LinkAttrs: testLinkAttrs,
}
err = netlink.LinkAdd(testIface)

View File

@ -1,135 +0,0 @@
// Copyright 2021 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/d2g/dhcp4"
"github.com/d2g/dhcp4client"
)
const (
MaxDHCPLen = 576
)
// Send the Discovery Packet to the Broadcast Channel
func DhcpSendDiscoverPacket(c *dhcp4client.Client, options dhcp4.Options) (dhcp4.Packet, error) {
discoveryPacket := c.DiscoverPacket()
for opt, data := range options {
discoveryPacket.AddOption(opt, data)
}
discoveryPacket.PadToMinSize()
return discoveryPacket, c.SendPacket(discoveryPacket)
}
// Send Request Based On the offer Received.
func DhcpSendRequest(c *dhcp4client.Client, options dhcp4.Options, offerPacket *dhcp4.Packet) (dhcp4.Packet, error) {
requestPacket := c.RequestPacket(offerPacket)
for opt, data := range options {
requestPacket.AddOption(opt, data)
}
requestPacket.PadToMinSize()
return requestPacket, c.SendPacket(requestPacket)
}
// Send Decline to the received acknowledgement.
func DhcpSendDecline(c *dhcp4client.Client, acknowledgementPacket *dhcp4.Packet, options dhcp4.Options) (dhcp4.Packet, error) {
declinePacket := c.DeclinePacket(acknowledgementPacket)
for opt, data := range options {
declinePacket.AddOption(opt, data)
}
declinePacket.PadToMinSize()
return declinePacket, c.SendPacket(declinePacket)
}
// Lets do a Full DHCP Request.
func DhcpRequest(c *dhcp4client.Client, options dhcp4.Options) (bool, dhcp4.Packet, error) {
discoveryPacket, err := DhcpSendDiscoverPacket(c, options)
if err != nil {
return false, discoveryPacket, err
}
offerPacket, err := c.GetOffer(&discoveryPacket)
if err != nil {
return false, offerPacket, err
}
requestPacket, err := DhcpSendRequest(c, options, &offerPacket)
if err != nil {
return false, requestPacket, err
}
acknowledgement, err := c.GetAcknowledgement(&requestPacket)
if err != nil {
return false, acknowledgement, err
}
acknowledgementOptions := acknowledgement.ParseOptions()
if dhcp4.MessageType(acknowledgementOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.ACK {
return false, acknowledgement, nil
}
return true, acknowledgement, nil
}
// Renew a lease backed on the Acknowledgement Packet.
// Returns Successful, The AcknoledgementPacket, Any Errors
func DhcpRenew(c *dhcp4client.Client, acknowledgement dhcp4.Packet, options dhcp4.Options) (bool, dhcp4.Packet, error) {
renewRequest := c.RenewalRequestPacket(&acknowledgement)
for opt, data := range options {
renewRequest.AddOption(opt, data)
}
renewRequest.PadToMinSize()
err := c.SendPacket(renewRequest)
if err != nil {
return false, renewRequest, err
}
newAcknowledgement, err := c.GetAcknowledgement(&renewRequest)
if err != nil {
return false, newAcknowledgement, err
}
newAcknowledgementOptions := newAcknowledgement.ParseOptions()
if dhcp4.MessageType(newAcknowledgementOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.ACK {
return false, newAcknowledgement, nil
}
return true, newAcknowledgement, nil
}
// Release a lease backed on the Acknowledgement Packet.
// Returns Any Errors
func DhcpRelease(c *dhcp4client.Client, acknowledgement dhcp4.Packet, options dhcp4.Options) error {
release := c.ReleasePacket(&acknowledgement)
for opt, data := range options {
release.AddOption(opt, data)
}
release.PadToMinSize()
return c.SendPacket(release)
}

View File

@ -39,19 +39,21 @@ import (
var errNoMoreTries = errors.New("no more tries")
type DHCP struct {
mux sync.Mutex
leases map[string]*DHCPLease
hostNetnsPrefix string
clientTimeout time.Duration
clientResendMax time.Duration
broadcast bool
mux sync.Mutex
leases map[string]*DHCPLease
hostNetnsPrefix string
clientTimeout time.Duration
clientResendMax time.Duration
clientResendTimeout time.Duration
broadcast bool
}
func newDHCP(clientTimeout, clientResendMax time.Duration) *DHCP {
func newDHCP(clientTimeout, clientResendMax time.Duration, resendTimeout time.Duration) *DHCP {
return &DHCP{
leases: make(map[string]*DHCPLease),
clientTimeout: clientTimeout,
clientResendMax: clientResendMax,
leases: make(map[string]*DHCPLease),
clientTimeout: clientTimeout,
clientResendMax: clientResendMax,
clientResendTimeout: resendTimeout,
}
}
@ -74,7 +76,7 @@ func (d *DHCP) Allocate(args *skel.CmdArgs, result *current.Result) error {
return fmt.Errorf("error parsing netconf: %v", err)
}
optsRequesting, optsProviding, err := prepareOptions(args.Args, conf.IPAM.ProvideOptions, conf.IPAM.RequestOptions)
opts, err := prepareOptions(args.Args, conf.IPAM.ProvideOptions, conf.IPAM.RequestOptions)
if err != nil {
return err
}
@ -89,8 +91,8 @@ func (d *DHCP) Allocate(args *skel.CmdArgs, result *current.Result) error {
} else {
hostNetns := d.hostNetnsPrefix + args.Netns
l, err = AcquireLease(clientID, hostNetns, args.IfName,
optsRequesting, optsProviding,
d.clientTimeout, d.clientResendMax, d.broadcast)
opts,
d.clientTimeout, d.clientResendMax, d.clientResendTimeout, d.broadcast)
if err != nil {
return err
}
@ -109,6 +111,11 @@ func (d *DHCP) Allocate(args *skel.CmdArgs, result *current.Result) error {
Gateway: l.Gateway(),
}}
result.Routes = l.Routes()
if conf.IPAM.Priority != 0 {
for _, r := range result.Routes {
r.Priority = conf.IPAM.Priority
}
}
return nil
}
@ -185,7 +192,8 @@ func getListener(socketPath string) (net.Listener, error) {
func runDaemon(
pidfilePath, hostPrefix, socketPath string,
dhcpClientTimeout time.Duration, resendMax time.Duration, broadcast bool,
dhcpClientTimeout time.Duration, resendMax time.Duration, resendTimeout time.Duration,
broadcast bool,
) error {
// since other goroutines (on separate threads) will change namespaces,
// ensure the RPC server does not get scheduled onto those
@ -220,7 +228,7 @@ func runDaemon(
done <- true
}()
dhcp := newDHCP(dhcpClientTimeout, resendMax)
dhcp := newDHCP(dhcpClientTimeout, resendMax, resendTimeout)
dhcp.hostNetnsPrefix = hostPrefix
dhcp.broadcast = broadcast
rpc.Register(dhcp)

View File

@ -61,13 +61,12 @@ var _ = Describe("DHCP Multiple Lease Operations", func() {
})
// Start the DHCP server
dhcpServerDone, err = dhcpServerStart(originalNS, 2, dhcpServerStopCh)
Expect(err).NotTo(HaveOccurred())
dhcpServerDone = dhcpServerStart(originalNS, 2, dhcpServerStopCh)
// Start the DHCP client daemon
dhcpPluginPath, err := exec.LookPath("dhcp")
Expect(err).NotTo(HaveOccurred())
clientCmd = exec.Command(dhcpPluginPath, "daemon", "-socketpath", socketPath)
clientCmd = exec.Command(dhcpPluginPath, "daemon", "-socketpath", socketPath, "--timeout", "2s", "--resendtimeout", "8s")
err = clientCmd.Start()
Expect(err).NotTo(HaveOccurred())
Expect(clientCmd.Process).NotTo(BeNil())

View File

@ -25,10 +25,6 @@ import (
"sync"
"time"
"github.com/d2g/dhcp4"
"github.com/d2g/dhcp4server"
"github.com/d2g/dhcp4server/leasepool"
"github.com/d2g/dhcp4server/leasepool/memorypool"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/vishvananda/netlink"
@ -48,31 +44,52 @@ func getTmpDir() (string, error) {
return tmpDir, err
}
func dhcpServerStart(netns ns.NetNS, numLeases int, stopCh <-chan bool) (*sync.WaitGroup, error) {
// Add the expected IP to the pool
lp := memorypool.MemoryPool{}
type DhcpServer struct {
cmd *exec.Cmd
lock sync.Mutex
Expect(numLeases).To(BeNumerically(">", 0))
// Currently tests only need at most 2
Expect(numLeases).To(BeNumerically("<=", 2))
startAddr net.IP
endAddr net.IP
leaseTime time.Duration
}
// tests expect first lease to be at address 192.168.1.5
for i := 5; i < numLeases+5; i++ {
err := lp.AddLease(leasepool.Lease{IP: dhcp4.IPAdd(net.IPv4(192, 168, 1, byte(i)), 0)})
if err != nil {
return nil, fmt.Errorf("error adding IP to DHCP pool: %v", err)
}
func (s *DhcpServer) Serve() error {
if err := s.Start(); err != nil {
return err
}
return s.cmd.Wait()
}
dhcpServer, err := dhcp4server.New(
net.IPv4(192, 168, 1, 1),
&lp,
dhcp4server.SetLocalAddr(net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 67}),
dhcp4server.SetRemoteAddr(net.UDPAddr{IP: net.IPv4bcast, Port: 68}),
dhcp4server.LeaseDuration(time.Minute*15),
func (s *DhcpServer) Start() error {
s.lock.Lock()
defer s.lock.Unlock()
s.cmd = exec.Command(
"dnsmasq",
"--no-daemon",
"--dhcp-sequential-ip", // allocate IPs sequentially
"--port=0", // disable DNS
"--conf-file=-", // Do not read /etc/dnsmasq.conf
fmt.Sprintf("--dhcp-range=%s,%s,%d", s.startAddr, s.endAddr, int(s.leaseTime.Seconds())),
)
if err != nil {
return nil, fmt.Errorf("failed to create DHCP server: %v", err)
s.cmd.Stdin = bytes.NewBufferString("")
s.cmd.Stdout = os.Stdout
s.cmd.Stderr = os.Stderr
return s.cmd.Start()
}
func (s *DhcpServer) Stop() error {
s.lock.Lock()
defer s.lock.Unlock()
return s.cmd.Process.Kill()
}
func dhcpServerStart(netns ns.NetNS, numLeases int, stopCh <-chan bool) *sync.WaitGroup {
dhcpServer := &DhcpServer{
startAddr: net.IPv4(192, 168, 1, 5),
endAddr: net.IPv4(192, 168, 1, 5+uint8(numLeases)-1),
leaseTime: 5 * time.Minute,
}
stopWg := sync.WaitGroup{}
@ -84,9 +101,10 @@ func dhcpServerStart(netns ns.NetNS, numLeases int, stopCh <-chan bool) (*sync.W
go func() {
defer GinkgoRecover()
err = netns.Do(func(ns.NetNS) error {
err := netns.Do(func(ns.NetNS) error {
startWg.Done()
if err := dhcpServer.ListenAndServe(); err != nil {
if err := dhcpServer.Serve(); err != nil {
// Log, but don't trap errors; the server will
// always report an error when stopped
GinkgoT().Logf("DHCP server finished with error: %v", err)
@ -103,12 +121,12 @@ func dhcpServerStart(netns ns.NetNS, numLeases int, stopCh <-chan bool) (*sync.W
go func() {
startWg.Done()
<-stopCh
dhcpServer.Shutdown()
dhcpServer.Stop()
stopWg.Done()
}()
startWg.Wait()
return &stopWg, nil
return &stopWg
}
const (
@ -155,11 +173,11 @@ var _ = Describe("DHCP Operations", func() {
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = hostVethName
err = netlink.LinkAdd(&netlink.Veth{
LinkAttrs: netlink.LinkAttrs{
Name: hostVethName,
},
PeerName: contVethName,
LinkAttrs: linkAttrs,
PeerName: contVethName,
})
Expect(err).NotTo(HaveOccurred())
@ -200,8 +218,7 @@ var _ = Describe("DHCP Operations", func() {
})
// Start the DHCP server
dhcpServerDone, err = dhcpServerStart(originalNS, 1, dhcpServerStopCh)
Expect(err).NotTo(HaveOccurred())
dhcpServerDone = dhcpServerStart(originalNS, 1, dhcpServerStopCh)
// Start the DHCP client daemon
dhcpPluginPath, err := exec.LookPath("dhcp")
@ -332,9 +349,17 @@ var _ = Describe("DHCP Operations", func() {
started.Done()
started.Wait()
err = originalNS.Do(func(ns.NetNS) error {
err := originalNS.Do(func(ns.NetNS) error {
return testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
copiedArgs := &skel.CmdArgs{
ContainerID: args.ContainerID,
Netns: args.Netns,
IfName: args.IfName,
StdinData: args.StdinData,
Path: args.Path,
Args: args.Args,
}
return cmdDel(copiedArgs)
})
})
Expect(err).NotTo(HaveOccurred())
@ -386,11 +411,11 @@ func dhcpSetupOriginalNS() (chan bool, string, ns.NetNS, ns.NetNS, error) {
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = hostBridgeName
// Create bridge in the "host" (original) NS
br = &netlink.Bridge{
LinkAttrs: netlink.LinkAttrs{
Name: hostBridgeName,
},
LinkAttrs: linkAttrs,
}
err = netlink.LinkAdd(br)
@ -509,8 +534,7 @@ var _ = Describe("DHCP Lease Unavailable Operations", func() {
})
// Start the DHCP server
dhcpServerDone, err = dhcpServerStart(originalNS, 1, dhcpServerStopCh)
Expect(err).NotTo(HaveOccurred())
dhcpServerDone = dhcpServerStart(originalNS, 1, dhcpServerStopCh)
// Start the DHCP client daemon
dhcpPluginPath, err := exec.LookPath("dhcp")
@ -520,7 +544,7 @@ var _ = Describe("DHCP Lease Unavailable Operations", func() {
// `go test` timeout with default delays. Since our DHCP server
// and client daemon are local processes anyway, we can depend on
// them to respond very quickly.
clientCmd = exec.Command(dhcpPluginPath, "daemon", "-socketpath", socketPath, "-timeout", "2s", "-resendmax", "8s")
clientCmd = exec.Command(dhcpPluginPath, "daemon", "-socketpath", socketPath, "-timeout", "2s", "-resendmax", "8s", "--resendtimeout", "10s")
// copy dhcp client's stdout/stderr to test stdout
var b bytes.Buffer

View File

@ -15,6 +15,7 @@
package main
import (
"context"
"fmt"
"log"
"math/rand"
@ -24,8 +25,8 @@ import (
"sync/atomic"
"time"
"github.com/d2g/dhcp4"
"github.com/d2g/dhcp4client"
dhcp4 "github.com/insomniacslk/dhcp/dhcpv4"
"github.com/insomniacslk/dhcp/dhcpv4/nclient4"
"github.com/vishvananda/netlink"
"github.com/containernetworking/cni/pkg/types"
@ -35,8 +36,10 @@ import (
// RFC 2131 suggests using exponential backoff, starting with 4sec
// and randomized to +/- 1sec
const (
resendDelay0 = 4 * time.Second
resendDelayMax = 62 * time.Second
resendDelay0 = 4 * time.Second
resendDelayMax = 62 * time.Second
defaultLeaseTime = 60 * time.Minute
defaultResendTimeout = 208 * time.Second // fast resend + backoff resend
)
// To speed up the retry for first few failures, we retry without
@ -60,34 +63,35 @@ const (
type DHCPLease struct {
clientID string
ack *dhcp4.Packet
opts dhcp4.Options
latestLease *nclient4.Lease
link netlink.Link
renewalTime time.Time
rebindingTime time.Time
expireTime time.Time
timeout time.Duration
resendMax time.Duration
resendTimeout time.Duration
broadcast bool
stopping uint32
stop chan struct{}
check chan struct{}
wg sync.WaitGroup
cancelFunc context.CancelFunc
ctx context.Context
// list of requesting and providing options and if they are necessary / their value
optsRequesting map[dhcp4.OptionCode]bool
optsProviding map[dhcp4.OptionCode][]byte
opts []dhcp4.Option
}
var requestOptionsDefault = map[dhcp4.OptionCode]bool{
dhcp4.OptionRouter: true,
dhcp4.OptionSubnetMask: true,
var requestOptionsDefault = []dhcp4.OptionCode{
dhcp4.OptionRouter,
dhcp4.OptionSubnetMask,
}
func prepareOptions(cniArgs string, provideOptions []ProvideOption, requestOptions []RequestOption) (
map[dhcp4.OptionCode]bool, map[dhcp4.OptionCode][]byte, error,
[]dhcp4.Option, error,
) {
var optsRequesting map[dhcp4.OptionCode]bool
var optsProviding map[dhcp4.OptionCode][]byte
var opts []dhcp4.Option
var err error
// parse CNI args
cniArgsParsed := map[string]string{}
@ -100,46 +104,51 @@ func prepareOptions(cniArgs string, provideOptions []ProvideOption, requestOptio
// parse providing options map
var optParsed dhcp4.OptionCode
optsProviding = make(map[dhcp4.OptionCode][]byte)
for _, opt := range provideOptions {
optParsed, err = parseOptionName(string(opt.Option))
if err != nil {
return nil, nil, fmt.Errorf("Can not parse option %q: %w", opt.Option, err)
return nil, fmt.Errorf("Can not parse option %q: %w", opt.Option, err)
}
if len(opt.Value) > 0 {
if len(opt.Value) > 255 {
return nil, nil, fmt.Errorf("value too long for option %q: %q", opt.Option, opt.Value)
return nil, fmt.Errorf("value too long for option %q: %q", opt.Option, opt.Value)
}
optsProviding[optParsed] = []byte(opt.Value)
opts = append(opts, dhcp4.Option{Code: optParsed, Value: dhcp4.String(opt.Value)})
}
if value, ok := cniArgsParsed[opt.ValueFromCNIArg]; ok {
if len(value) > 255 {
return nil, nil, fmt.Errorf("value too long for option %q from CNI_ARGS %q: %q", opt.Option, opt.ValueFromCNIArg, opt.Value)
return nil, fmt.Errorf("value too long for option %q from CNI_ARGS %q: %q", opt.Option, opt.ValueFromCNIArg, opt.Value)
}
optsProviding[optParsed] = []byte(value)
opts = append(opts, dhcp4.Option{Code: optParsed, Value: dhcp4.String(value)})
}
}
// parse necessary options map
optsRequesting = make(map[dhcp4.OptionCode]bool)
var optsRequesting dhcp4.OptionCodeList
skipRequireDefault := false
for _, opt := range requestOptions {
if opt.SkipDefault {
skipRequireDefault = true
}
if opt.Option == "" {
continue
}
optParsed, err = parseOptionName(string(opt.Option))
if err != nil {
return nil, nil, fmt.Errorf("Can not parse option %q: %w", opt.Option, err)
return nil, fmt.Errorf("Can not parse option %q: %w", opt.Option, err)
}
optsRequesting[optParsed] = true
optsRequesting.Add(optParsed)
}
for k, v := range requestOptionsDefault {
// only set if not skipping default and this value does not exists
if _, ok := optsRequesting[k]; !ok && !skipRequireDefault {
optsRequesting[k] = v
if !skipRequireDefault {
for _, opt := range requestOptionsDefault {
optsRequesting.Add(opt)
}
}
return optsRequesting, optsProviding, err
if len(optsRequesting) > 0 {
opts = append(opts, dhcp4.Option{Code: dhcp4.OptionParameterRequestList, Value: optsRequesting})
}
return opts, err
}
// AcquireLease gets an DHCP lease and then maintains it in the background
@ -147,19 +156,25 @@ func prepareOptions(cniArgs string, provideOptions []ProvideOption, requestOptio
// calling DHCPLease.Stop()
func AcquireLease(
clientID, netns, ifName string,
optsRequesting map[dhcp4.OptionCode]bool, optsProviding map[dhcp4.OptionCode][]byte,
timeout, resendMax time.Duration, broadcast bool,
opts []dhcp4.Option,
timeout, resendMax time.Duration, resendTimeout time.Duration, broadcast bool,
) (*DHCPLease, error) {
errCh := make(chan error, 1)
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
l := &DHCPLease{
clientID: clientID,
stop: make(chan struct{}),
check: make(chan struct{}),
timeout: timeout,
resendMax: resendMax,
broadcast: broadcast,
optsRequesting: optsRequesting,
optsProviding: optsProviding,
clientID: clientID,
stop: make(chan struct{}),
check: make(chan struct{}),
timeout: timeout,
resendMax: resendMax,
resendTimeout: resendTimeout,
broadcast: broadcast,
opts: opts,
cancelFunc: cancel,
ctx: ctx,
}
log.Printf("%v: acquiring lease", clientID)
@ -201,6 +216,7 @@ func AcquireLease(
func (l *DHCPLease) Stop() {
if atomic.CompareAndSwapUint32(&l.stopping, 0, 1) {
close(l.stop)
l.cancelFunc()
}
l.wg.Wait()
}
@ -209,92 +225,65 @@ func (l *DHCPLease) Check() {
l.check <- struct{}{}
}
func (l *DHCPLease) getOptionsWithClientID() dhcp4.Options {
opts := make(dhcp4.Options)
opts[dhcp4.OptionClientIdentifier] = []byte(l.clientID)
// client identifier's first byte is "type"
newClientID := []byte{0}
newClientID = append(newClientID, opts[dhcp4.OptionClientIdentifier]...)
opts[dhcp4.OptionClientIdentifier] = newClientID
return opts
func withClientID(clientID string) dhcp4.Modifier {
return func(d *dhcp4.DHCPv4) {
optClientID := []byte{0}
optClientID = append(optClientID, []byte(clientID)...)
d.Options.Update(dhcp4.OptClientIdentifier(optClientID))
}
}
func (l *DHCPLease) getAllOptions() dhcp4.Options {
opts := l.getOptionsWithClientID()
for k, v := range l.optsProviding {
opts[k] = v
func withAllOptions(l *DHCPLease) dhcp4.Modifier {
return func(d *dhcp4.DHCPv4) {
for _, opt := range l.opts {
d.Options.Update(opt)
}
}
opts[dhcp4.OptionParameterRequestList] = []byte{}
for k := range l.optsRequesting {
opts[dhcp4.OptionParameterRequestList] = append(opts[dhcp4.OptionParameterRequestList], byte(k))
}
return opts
}
func (l *DHCPLease) acquire() error {
c, err := newDHCPClient(l.link, l.timeout, l.broadcast)
if (l.link.Attrs().Flags & net.FlagUp) != net.FlagUp {
log.Printf("Link %q down. Attempting to set up", l.link.Attrs().Name)
if err := netlink.LinkSetUp(l.link); err != nil {
return err
}
}
c, err := newDHCPClient(l.link, l.timeout)
if err != nil {
return err
}
defer c.Close()
if (l.link.Attrs().Flags & net.FlagUp) != net.FlagUp {
log.Printf("Link %q down. Attempting to set up", l.link.Attrs().Name)
if err = netlink.LinkSetUp(l.link); err != nil {
return err
}
}
opts := l.getAllOptions()
pkt, err := backoffRetry(l.resendMax, func() (*dhcp4.Packet, error) {
ok, ack, err := DhcpRequest(c, opts)
switch {
case err != nil:
return nil, err
case !ok:
return nil, fmt.Errorf("DHCP server NACK'd own offer")
default:
return &ack, nil
}
timeoutCtx, cancel := context.WithTimeoutCause(l.ctx, l.resendTimeout, errNoMoreTries)
defer cancel()
pkt, err := backoffRetry(timeoutCtx, l.resendMax, func() (*nclient4.Lease, error) {
return c.Request(
timeoutCtx,
withClientID(l.clientID),
withAllOptions(l),
)
})
if err != nil {
return err
}
return l.commit(pkt)
l.commit(pkt)
return nil
}
func (l *DHCPLease) commit(ack *dhcp4.Packet) error {
opts := ack.ParseOptions()
func (l *DHCPLease) commit(lease *nclient4.Lease) {
l.latestLease = lease
ack := lease.ACK
leaseTime, err := parseLeaseTime(opts)
if err != nil {
return err
}
rebindingTime, err := parseRebindingTime(opts)
if err != nil || rebindingTime > leaseTime {
// Per RFC 2131 Section 4.4.5, it should default to 85% of lease time
rebindingTime = leaseTime * 85 / 100
}
renewalTime, err := parseRenewalTime(opts)
if err != nil || renewalTime > rebindingTime {
// Per RFC 2131 Section 4.4.5, it should default to 50% of lease time
renewalTime = leaseTime / 2
}
leaseTime := ack.IPAddressLeaseTime(defaultLeaseTime)
rebindingTime := ack.IPAddressRebindingTime(leaseTime * 85 / 100)
renewalTime := ack.IPAddressRenewalTime(leaseTime / 2)
now := time.Now()
l.expireTime = now.Add(leaseTime)
l.renewalTime = now.Add(renewalTime)
l.rebindingTime = now.Add(rebindingTime)
l.ack = ack
l.opts = opts
return nil
}
func (l *DHCPLease) maintain() {
@ -362,44 +351,40 @@ func (l *DHCPLease) downIface() {
}
func (l *DHCPLease) renew() error {
c, err := newDHCPClient(l.link, l.timeout, l.broadcast)
c, err := newDHCPClient(l.link, l.timeout)
if err != nil {
return err
}
defer c.Close()
opts := l.getAllOptions()
pkt, err := backoffRetry(l.resendMax, func() (*dhcp4.Packet, error) {
ok, ack, err := DhcpRenew(c, *l.ack, opts)
switch {
case err != nil:
return nil, err
case !ok:
return nil, fmt.Errorf("DHCP server did not renew lease")
default:
return &ack, nil
}
timeoutCtx, cancel := context.WithTimeoutCause(l.ctx, l.resendTimeout, errNoMoreTries)
defer cancel()
lease, err := backoffRetry(timeoutCtx, l.resendMax, func() (*nclient4.Lease, error) {
return c.Renew(
timeoutCtx,
l.latestLease,
withClientID(l.clientID),
withAllOptions(l),
)
})
if err != nil {
return err
}
l.commit(pkt)
l.commit(lease)
return nil
}
func (l *DHCPLease) release() error {
log.Printf("%v: releasing lease", l.clientID)
c, err := newDHCPClient(l.link, l.timeout, l.broadcast)
c, err := newDHCPClient(l.link, l.timeout)
if err != nil {
return err
}
defer c.Close()
opts := l.getOptionsWithClientID()
if err = DhcpRelease(c, *l.ack, opts); err != nil {
if err = c.Release(l.latestLease, withClientID(l.clientID)); err != nil {
return fmt.Errorf("failed to send DHCPRELEASE")
}
@ -407,33 +392,47 @@ func (l *DHCPLease) release() error {
}
func (l *DHCPLease) IPNet() (*net.IPNet, error) {
mask := parseSubnetMask(l.opts)
ack := l.latestLease.ACK
mask := ack.SubnetMask()
if mask == nil {
return nil, fmt.Errorf("DHCP option Subnet Mask not found in DHCPACK")
}
return &net.IPNet{
IP: l.ack.YIAddr(),
IP: ack.YourIPAddr,
Mask: mask,
}, nil
}
func (l *DHCPLease) Gateway() net.IP {
return parseRouter(l.opts)
ack := l.latestLease.ACK
gws := ack.Router()
if len(gws) > 0 {
return gws[0]
}
return nil
}
func (l *DHCPLease) Routes() []*types.Route {
routes := []*types.Route{}
ack := l.latestLease.ACK
// RFC 3442 states that if Classless Static Routes (option 121)
// exist, we ignore Static Routes (option 33) and the Router/Gateway.
opt121Routes := parseCIDRRoutes(l.opts)
opt121Routes := ack.ClasslessStaticRoute()
if len(opt121Routes) > 0 {
return append(routes, opt121Routes...)
for _, r := range opt121Routes {
routes = append(routes, &types.Route{Dst: *r.Dest, GW: r.Router})
}
return routes
}
// Append Static Routes
routes = append(routes, parseRoutes(l.opts)...)
if ack.Options.Has(dhcp4.OptionStaticRoutingTable) {
routes = append(routes, parseRoutes(ack.Options.Get(dhcp4.OptionStaticRoutingTable))...)
}
// The CNI spec says even if there is a gateway specified, we must
// add a default route in the routes section.
@ -450,7 +449,7 @@ func jitter(span time.Duration) time.Duration {
return time.Duration(float64(span) * (2.0*rand.Float64() - 1.0))
}
func backoffRetry(resendMax time.Duration, f func() (*dhcp4.Packet, error)) (*dhcp4.Packet, error) {
func backoffRetry(ctx context.Context, resendMax time.Duration, f func() (*nclient4.Lease, error)) (*nclient4.Lease, error) {
baseDelay := resendDelay0
var sleepTime time.Duration
fastRetryLimit := resendFastMax
@ -471,33 +470,23 @@ func backoffRetry(resendMax time.Duration, f func() (*dhcp4.Packet, error)) (*dh
log.Printf("retrying in %f seconds", sleepTime.Seconds())
time.Sleep(sleepTime)
// only adjust delay time if we are in normal backoff stage
if baseDelay < resendMax && fastRetryLimit == 0 {
baseDelay *= 2
} else if fastRetryLimit == 0 { // only break if we are at normal delay
break
select {
case <-ctx.Done():
return nil, context.Cause(ctx)
case <-time.After(sleepTime):
// only adjust delay time if we are in normal backoff stage
if baseDelay < resendMax && fastRetryLimit == 0 {
baseDelay *= 2
}
}
}
return nil, errNoMoreTries
}
func newDHCPClient(
link netlink.Link,
timeout time.Duration,
broadcast bool,
) (*dhcp4client.Client, error) {
pktsock, err := dhcp4client.NewPacketSock(link.Attrs().Index)
if err != nil {
return nil, err
}
return dhcp4client.New(
dhcp4client.HardwareAddr(link.Attrs().HardwareAddr),
dhcp4client.Timeout(timeout),
dhcp4client.Broadcast(broadcast),
dhcp4client.Connection(pktsock),
)
clientOpts ...nclient4.ClientOpt,
) (*nclient4.Client, error) {
clientOpts = append(clientOpts, nclient4.WithTimeout(timeout))
return nclient4.New(link.Attrs().Name, clientOpts...)
}

View File

@ -51,6 +51,8 @@ type IPAMConfig struct {
// To override default requesting fields, set `skipDefault` to `false`.
// If an field is not optional, but the server failed to provide it, error will be raised.
RequestOptions []RequestOption `json:"request"`
// The metric of routes
Priority int `json:"priority,omitempty"`
}
// DHCPOption represents a DHCP option. It can be a number, or a string defined in manual dhcp-options(5).
@ -78,25 +80,33 @@ func main() {
var broadcast bool
var timeout time.Duration
var resendMax time.Duration
var resendTimeout time.Duration
daemonFlags := flag.NewFlagSet("daemon", flag.ExitOnError)
daemonFlags.StringVar(&pidfilePath, "pidfile", "", "optional path to write daemon PID to")
daemonFlags.StringVar(&hostPrefix, "hostprefix", "", "optional prefix to host root")
daemonFlags.StringVar(&socketPath, "socketpath", "", "optional dhcp server socketpath")
daemonFlags.BoolVar(&broadcast, "broadcast", false, "broadcast DHCP leases")
daemonFlags.DurationVar(&timeout, "timeout", 10*time.Second, "optional dhcp client timeout duration")
daemonFlags.DurationVar(&resendMax, "resendmax", resendDelayMax, "optional dhcp client resend max duration")
daemonFlags.DurationVar(&timeout, "timeout", 10*time.Second, "optional dhcp client timeout duration for each request")
daemonFlags.DurationVar(&resendMax, "resendmax", resendDelayMax, "optional dhcp client max resend delay between requests")
daemonFlags.DurationVar(&resendTimeout, "resendtimeout", defaultResendTimeout, "optional dhcp client resend timeout, no more retries after this timeout")
daemonFlags.Parse(os.Args[2:])
if socketPath == "" {
socketPath = defaultSocketPath
}
if err := runDaemon(pidfilePath, hostPrefix, socketPath, timeout, resendMax, broadcast); err != nil {
if err := runDaemon(pidfilePath, hostPrefix, socketPath, timeout, resendMax, resendTimeout, broadcast); err != nil {
log.Print(err.Error())
os.Exit(1)
}
} else {
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("dhcp"))
skel.PluginMainFuncs(skel.CNIFuncs{
Add: cmdAdd,
Check: cmdCheck,
Del: cmdDel,
/* FIXME GC */
/* FIXME Status */
}, version.All, bv.BuildString("dhcp"))
}
}

View File

@ -15,13 +15,11 @@
package main
import (
"encoding/binary"
"fmt"
"net"
"strconv"
"time"
"github.com/d2g/dhcp4"
dhcp4 "github.com/insomniacslk/dhcp/dhcpv4"
"github.com/containernetworking/cni/pkg/types"
)
@ -31,8 +29,8 @@ var optionNameToID = map[string]dhcp4.OptionCode{
"subnet-mask": dhcp4.OptionSubnetMask,
"routers": dhcp4.OptionRouter,
"host-name": dhcp4.OptionHostName,
"user-class": dhcp4.OptionUserClass,
"vendor-class-identifier": dhcp4.OptionVendorClassIdentifier,
"user-class": dhcp4.OptionUserClassInformation,
"vendor-class-identifier": dhcp4.OptionClassIdentifier,
}
func parseOptionName(option string) (dhcp4.OptionCode, error) {
@ -41,18 +39,9 @@ func parseOptionName(option string) (dhcp4.OptionCode, error) {
}
i, err := strconv.ParseUint(option, 10, 8)
if err != nil {
return 0, fmt.Errorf("Can not parse option: %w", err)
return dhcp4.OptionPad, fmt.Errorf("Can not parse option: %w", err)
}
return dhcp4.OptionCode(i), nil
}
func parseRouter(opts dhcp4.Options) net.IP {
if opts, ok := opts[dhcp4.OptionRouter]; ok {
if len(opts) == 4 {
return net.IP(opts)
}
}
return nil
return dhcp4.GenericOptionCode(i), nil
}
func classfulSubnet(sn net.IP) net.IPNet {
@ -62,100 +51,22 @@ func classfulSubnet(sn net.IP) net.IPNet {
}
}
func parseRoutes(opts dhcp4.Options) []*types.Route {
func parseRoutes(opt []byte) []*types.Route {
// StaticRoutes format: pairs of:
// Dest = 4 bytes; Classful IP subnet
// Router = 4 bytes; IP address of router
routes := []*types.Route{}
if opt, ok := opts[dhcp4.OptionStaticRoute]; ok {
for len(opt) >= 8 {
sn := opt[0:4]
r := opt[4:8]
rt := &types.Route{
Dst: classfulSubnet(sn),
GW: r,
}
routes = append(routes, rt)
opt = opt[8:]
for len(opt) >= 8 {
sn := opt[0:4]
r := opt[4:8]
rt := &types.Route{
Dst: classfulSubnet(sn),
GW: r,
}
routes = append(routes, rt)
opt = opt[8:]
}
return routes
}
func parseCIDRRoutes(opts dhcp4.Options) []*types.Route {
// See RFC4332 for format (http://tools.ietf.org/html/rfc3442)
routes := []*types.Route{}
if opt, ok := opts[dhcp4.OptionClasslessRouteFormat]; ok {
for len(opt) >= 5 {
width := int(opt[0])
if width > 32 {
// error: can't have more than /32
return nil
}
// network bits are compacted to avoid zeros
octets := 0
if width > 0 {
octets = (width-1)/8 + 1
}
if len(opt) < 1+octets+4 {
// error: too short
return nil
}
sn := make([]byte, 4)
copy(sn, opt[1:octets+1])
gw := net.IP(opt[octets+1 : octets+5])
rt := &types.Route{
Dst: net.IPNet{
IP: net.IP(sn),
Mask: net.CIDRMask(width, 32),
},
GW: gw,
}
routes = append(routes, rt)
opt = opt[octets+5:]
}
}
return routes
}
func parseSubnetMask(opts dhcp4.Options) net.IPMask {
mask, ok := opts[dhcp4.OptionSubnetMask]
if !ok {
return nil
}
return net.IPMask(mask)
}
func parseDuration(opts dhcp4.Options, code dhcp4.OptionCode, optName string) (time.Duration, error) {
val, ok := opts[code]
if !ok {
return 0, fmt.Errorf("option %v not found", optName)
}
if len(val) != 4 {
return 0, fmt.Errorf("option %v is not 4 bytes", optName)
}
secs := binary.BigEndian.Uint32(val)
return time.Duration(secs) * time.Second, nil
}
func parseLeaseTime(opts dhcp4.Options) (time.Duration, error) {
return parseDuration(opts, dhcp4.OptionIPAddressLeaseTime, "LeaseTime")
}
func parseRenewalTime(opts dhcp4.Options) (time.Duration, error) {
return parseDuration(opts, dhcp4.OptionRenewalTimeValue, "RenewalTime")
}
func parseRebindingTime(opts dhcp4.Options) (time.Duration, error) {
return parseDuration(opts, dhcp4.OptionRebindingTimeValue, "RebindingTime")
}

View File

@ -19,7 +19,7 @@ import (
"reflect"
"testing"
"github.com/d2g/dhcp4"
dhcp4 "github.com/insomniacslk/dhcp/dhcpv4"
"github.com/containernetworking/cni/pkg/types"
)
@ -61,17 +61,8 @@ func validateRoutes(t *testing.T, routes []*types.Route) {
}
func TestParseRoutes(t *testing.T) {
opts := make(dhcp4.Options)
opts[dhcp4.OptionStaticRoute] = []byte{10, 0, 0, 0, 10, 1, 2, 3, 192, 168, 1, 0, 192, 168, 2, 3}
routes := parseRoutes(opts)
validateRoutes(t, routes)
}
func TestParseCIDRRoutes(t *testing.T) {
opts := make(dhcp4.Options)
opts[dhcp4.OptionClasslessRouteFormat] = []byte{8, 10, 10, 1, 2, 3, 24, 192, 168, 1, 192, 168, 2, 3}
routes := parseCIDRRoutes(opts)
data := []byte{10, 0, 0, 0, 10, 1, 2, 3, 192, 168, 1, 0, 192, 168, 2, 3}
routes := parseRoutes(data)
validateRoutes(t, routes)
}
@ -87,10 +78,10 @@ func TestParseOptionName(t *testing.T) {
"hostname", "host-name", dhcp4.OptionHostName, false,
},
{
"hostname in number", "12", dhcp4.OptionHostName, false,
"hostname in number", "12", dhcp4.GenericOptionCode(12), false,
},
{
"random string", "doNotparseMe", 0, true,
"random string", "doNotparseMe", dhcp4.OptionPad, true,
},
}
for _, tt := range tests {

View File

@ -60,7 +60,7 @@ func New(network, dataDir string) (*Store, error) {
func (s *Store) Reserve(id string, ifname string, ip net.IP, rangeID string) (bool, error) {
fname := GetEscapedPath(s.dataDir, ip.String())
f, err := os.OpenFile(fname, os.O_RDWR|os.O_EXCL|os.O_CREATE, 0o644)
f, err := os.OpenFile(fname, os.O_RDWR|os.O_EXCL|os.O_CREATE, 0o600)
if os.IsExist(err) {
return false, nil
}
@ -78,7 +78,7 @@ func (s *Store) Reserve(id string, ifname string, ip net.IP, rangeID string) (bo
}
// store the reserved ip in lastIPFile
ipfile := GetEscapedPath(s.dataDir, lastIPFilePrefix+rangeID)
err = os.WriteFile(ipfile, []byte(ip.String()), 0o644)
err = os.WriteFile(ipfile, []byte(ip.String()), 0o600)
if err != nil {
return false, err
}

View File

@ -28,6 +28,7 @@ func parseResolvConf(filename string) (*types.DNS, error) {
if err != nil {
return nil, err
}
defer fp.Close()
dns := types.DNS{}
scanner := bufio.NewScanner(fp)

View File

@ -15,6 +15,7 @@
package main
import (
"errors"
"fmt"
"net"
"strings"
@ -29,7 +30,13 @@ import (
)
func main() {
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("host-local"))
skel.PluginMainFuncs(skel.CNIFuncs{
Add: cmdAdd,
Check: cmdCheck,
Del: cmdDel,
/* FIXME GC */
/* FIXME Status */
}, version.All, bv.BuildString("host-local"))
}
func cmdCheck(args *skel.CmdArgs) error {
@ -124,7 +131,7 @@ func cmdAdd(args *skel.CmdArgs) error {
for _, ip := range requestedIPs {
errstr = errstr + " " + ip.String()
}
return fmt.Errorf(errstr)
return errors.New(errstr)
}
result.Routes = ipamConf.Routes
@ -145,18 +152,18 @@ func cmdDel(args *skel.CmdArgs) error {
defer store.Close()
// Loop through all ranges, releasing all IPs, even if an error occurs
var errors []string
var errs []string
for idx, rangeset := range ipamConf.Ranges {
ipAllocator := allocator.NewIPAllocator(&rangeset, store, idx)
err := ipAllocator.Release(args.ContainerID, args.IfName)
if err != nil {
errors = append(errors, err.Error())
errs = append(errs, err.Error())
}
}
if errors != nil {
return fmt.Errorf(strings.Join(errors, ";"))
if errs != nil {
return errors.New(strings.Join(errs, ";"))
}
return nil
}

View File

@ -68,7 +68,13 @@ type Address struct {
}
func main() {
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("static"))
skel.PluginMainFuncs(skel.CNIFuncs{
Add: cmdAdd,
Check: cmdCheck,
Del: cmdDel,
/* FIXME GC */
/* FIXME Status */
}, version.All, bv.BuildString("static"))
}
func loadNetConf(bytes []byte) (*types.NetConf, string, error) {

View File

@ -35,7 +35,6 @@ import (
"github.com/containernetworking/plugins/pkg/ipam"
"github.com/containernetworking/plugins/pkg/link"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/utils"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
"github.com/containernetworking/plugins/pkg/utils/sysctl"
)
@ -47,19 +46,22 @@ const defaultBrName = "cni0"
type NetConf struct {
types.NetConf
BrName string `json:"bridge"`
IsGW bool `json:"isGateway"`
IsDefaultGW bool `json:"isDefaultGateway"`
ForceAddress bool `json:"forceAddress"`
IPMasq bool `json:"ipMasq"`
MTU int `json:"mtu"`
HairpinMode bool `json:"hairpinMode"`
PromiscMode bool `json:"promiscMode"`
Vlan int `json:"vlan"`
VlanTrunk []*VlanTrunk `json:"vlanTrunk,omitempty"`
PreserveDefaultVlan bool `json:"preserveDefaultVlan"`
MacSpoofChk bool `json:"macspoofchk,omitempty"`
EnableDad bool `json:"enabledad,omitempty"`
BrName string `json:"bridge"`
IsGW bool `json:"isGateway"`
IsDefaultGW bool `json:"isDefaultGateway"`
ForceAddress bool `json:"forceAddress"`
IPMasq bool `json:"ipMasq"`
IPMasqBackend *string `json:"ipMasqBackend,omitempty"`
MTU int `json:"mtu"`
HairpinMode bool `json:"hairpinMode"`
PromiscMode bool `json:"promiscMode"`
Vlan int `json:"vlan"`
VlanTrunk []*VlanTrunk `json:"vlanTrunk,omitempty"`
PreserveDefaultVlan bool `json:"preserveDefaultVlan"`
MacSpoofChk bool `json:"macspoofchk,omitempty"`
EnableDad bool `json:"enabledad,omitempty"`
DisableContainerInterface bool `json:"disableContainerInterface,omitempty"`
PortIsolation bool `json:"portIsolation,omitempty"`
Args struct {
Cni BridgeArgs `json:"cni,omitempty"`
@ -334,16 +336,11 @@ func bridgeByName(name string) (*netlink.Bridge, error) {
}
func ensureBridge(brName string, mtu int, promiscMode, vlanFiltering bool) (*netlink.Bridge, error) {
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = brName
linkAttrs.MTU = mtu
br := &netlink.Bridge{
LinkAttrs: netlink.LinkAttrs{
Name: brName,
MTU: mtu,
// Let kernel use default txqueuelen; leaving it unset
// means 0, and a zero-length TX queue messes up FIFO
// traffic shapers which use TX queue length as the
// default packet limit
TxQLen: -1,
},
LinkAttrs: linkAttrs,
}
if vlanFiltering {
br.VlanFiltering = &vlanFiltering
@ -391,7 +388,7 @@ func ensureVlanInterface(br *netlink.Bridge, vlanID int, preserveDefaultVlan boo
return nil, fmt.Errorf("faild to find host namespace: %v", err)
}
_, brGatewayIface, err := setupVeth(hostNS, br, name, br.MTU, false, vlanID, nil, preserveDefaultVlan, "")
_, brGatewayIface, err := setupVeth(hostNS, br, name, br.MTU, false, vlanID, nil, preserveDefaultVlan, "", false)
if err != nil {
return nil, fmt.Errorf("faild to create vlan gateway %q: %v", name, err)
}
@ -410,7 +407,18 @@ func ensureVlanInterface(br *netlink.Bridge, vlanID int, preserveDefaultVlan boo
return brGatewayVeth, nil
}
func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairpinMode bool, vlanID int, vlans []int, preserveDefaultVlan bool, mac string) (*current.Interface, *current.Interface, error) {
func setupVeth(
netns ns.NetNS,
br *netlink.Bridge,
ifName string,
mtu int,
hairpinMode bool,
vlanID int,
vlans []int,
preserveDefaultVlan bool,
mac string,
portIsolation bool,
) (*current.Interface, *current.Interface, error) {
contIface := &current.Interface{}
hostIface := &current.Interface{}
@ -447,6 +455,11 @@ func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairp
return nil, nil, fmt.Errorf("failed to setup hairpin mode for %v: %v", hostVeth.Attrs().Name, err)
}
// set isolation mode
if err = netlink.LinkSetIsolated(hostVeth, portIsolation); err != nil {
return nil, nil, fmt.Errorf("failed to set isolated on for %v: %v", hostVeth.Attrs().Name, err)
}
if (vlanID != 0 || len(vlans) > 0) && !preserveDefaultVlan {
err = removeDefaultVlan(hostVeth)
if err != nil {
@ -530,6 +543,10 @@ func cmdAdd(args *skel.CmdArgs) error {
isLayer3 := n.IPAM.Type != ""
if isLayer3 && n.DisableContainerInterface {
return fmt.Errorf("cannot use IPAM when DisableContainerInterface flag is set")
}
if n.IsDefaultGW {
n.IsGW = true
}
@ -549,7 +566,7 @@ func cmdAdd(args *skel.CmdArgs) error {
}
defer netns.Close()
hostInterface, containerInterface, err := setupVeth(netns, br, args.IfName, n.MTU, n.HairpinMode, n.Vlan, n.vlans, n.PreserveDefaultVlan, n.mac)
hostInterface, containerInterface, err := setupVeth(netns, br, args.IfName, n.MTU, n.HairpinMode, n.Vlan, n.vlans, n.PreserveDefaultVlan, n.mac, n.PortIsolation)
if err != nil {
return err
}
@ -629,14 +646,10 @@ func cmdAdd(args *skel.CmdArgs) error {
}
if n.IsGW {
var firstV4Addr net.IP
var vlanInterface *current.Interface
// Set the IP address(es) on the bridge and enable forwarding
for _, gws := range []*gwInfo{gwsV4, gwsV6} {
for _, gw := range gws.gws {
if gw.IP.To4() != nil && firstV4Addr == nil {
firstV4Addr = gw.IP
}
if n.Vlan != 0 {
vlanIface, err := ensureVlanInterface(br, n.Vlan, n.PreserveDefaultVlan)
if err != nil {
@ -672,20 +685,21 @@ func cmdAdd(args *skel.CmdArgs) error {
}
if n.IPMasq {
chain := utils.FormatChainName(n.Name, args.ContainerID)
comment := utils.FormatComment(n.Name, args.ContainerID)
ipns := []*net.IPNet{}
for _, ipc := range result.IPs {
if err = ip.SetupIPMasq(&ipc.Address, chain, comment); err != nil {
return err
}
ipns = append(ipns, &ipc.Address)
}
if err = ip.SetupIPMasqForNetworks(n.IPMasqBackend, ipns, n.Name, args.IfName, args.ContainerID); err != nil {
return err
}
}
} else {
} else if !n.DisableContainerInterface {
if err := netns.Do(func(_ ns.NetNS) error {
link, err := netlink.LinkByName(args.IfName)
if err != nil {
return fmt.Errorf("failed to retrieve link: %v", err)
}
// If layer 2 we still need to set the container veth to up
if err = netlink.LinkSetUp(link); err != nil {
return fmt.Errorf("failed to set %q up: %v", args.IfName, err)
@ -696,23 +710,28 @@ func cmdAdd(args *skel.CmdArgs) error {
}
}
var hostVeth netlink.Link
hostVeth, err := netlink.LinkByName(hostInterface.Name)
if err != nil {
return err
}
// check bridge port state
retries := []int{0, 50, 500, 1000, 1000}
for idx, sleep := range retries {
time.Sleep(time.Duration(sleep) * time.Millisecond)
if !n.DisableContainerInterface {
// check bridge port state
retries := []int{0, 50, 500, 1000, 1000}
for idx, sleep := range retries {
time.Sleep(time.Duration(sleep) * time.Millisecond)
hostVeth, err = netlink.LinkByName(hostInterface.Name)
if err != nil {
return err
}
if hostVeth.Attrs().OperState == netlink.OperUp {
break
}
hostVeth, err = netlink.LinkByName(hostInterface.Name)
if err != nil {
return err
}
if hostVeth.Attrs().OperState == netlink.OperUp {
break
}
if idx == len(retries)-1 {
return fmt.Errorf("bridge port in error state: %s", hostVeth.Attrs().OperState)
if idx == len(retries)-1 {
return fmt.Errorf("bridge port in error state: %s", hostVeth.Attrs().OperState)
}
}
}
@ -733,7 +752,7 @@ func cmdAdd(args *skel.CmdArgs) error {
}
// Use incoming DNS settings if provided, otherwise use the
// settings that were already configued by the IPAM plugin
// settings that were already configured by the IPAM plugin
if dnsConfSet(n.DNS) {
result.DNS = n.DNS
}
@ -783,7 +802,6 @@ func cmdDel(args *skel.CmdArgs) error {
}
return err
})
if err != nil {
// if NetNs is passed down by the Cloud Orchestration Engine, or if it called multiple times
// so don't return an error if the device is already removed.
@ -808,12 +826,8 @@ func cmdDel(args *skel.CmdArgs) error {
}
if isLayer3 && n.IPMasq {
chain := utils.FormatChainName(n.Name, args.ContainerID)
comment := utils.FormatComment(n.Name, args.ContainerID)
for _, ipn := range ipnets {
if err := ip.TeardownIPMasq(ipn, chain, comment); err != nil {
return err
}
if err := ip.TeardownIPMasqForNetworks(ipnets, n.Name, args.IfName, args.ContainerID); err != nil {
return err
}
}
@ -821,7 +835,13 @@ func cmdDel(args *skel.CmdArgs) error {
}
func main() {
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("bridge"))
skel.PluginMainFuncs(skel.CNIFuncs{
Add: cmdAdd,
Check: cmdCheck,
Del: cmdDel,
Status: cmdStatus,
/* FIXME GC */
}, version.All, bv.BuildString("bridge"))
}
type cniBridgeIf struct {
@ -1082,3 +1102,18 @@ func cmdCheck(args *skel.CmdArgs) error {
func uniqueID(containerID, cniIface string) string {
return containerID + "-" + cniIface
}
func cmdStatus(args *skel.CmdArgs) error {
conf := NetConf{}
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
return fmt.Errorf("failed to load netconf: %w", err)
}
if conf.IPAM.Type != "" {
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
return err
}
}
return nil
}

View File

@ -15,6 +15,7 @@
package main
import (
"context"
"encoding/json"
"fmt"
"net"
@ -27,6 +28,7 @@ import (
. "github.com/onsi/gomega"
"github.com/vishvananda/netlink"
"github.com/vishvananda/netlink/nl"
"sigs.k8s.io/knftables"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
@ -77,11 +79,15 @@ type testCase struct {
vlanTrunk []*VlanTrunk
removeDefaultVlan bool
ipMasq bool
ipMasqBackend string
macspoofchk bool
AddErr020 string
DelErr020 string
AddErr010 string
DelErr010 string
disableContIface bool
portIsolation bool
AddErr020 string
DelErr020 string
AddErr010 string
DelErr010 string
envArgs string // CNI_ARGS
runtimeConfig struct {
@ -154,6 +160,12 @@ const (
netDefault = `,
"isDefaultGateway": true`
disableContainerInterface = `,
"disableContainerInterface": true`
portIsolation = `,
"portIsolation": true`
ipamStartStr = `,
"ipam": {
"type": "host-local"`
@ -167,6 +179,9 @@ const (
ipMasqConfStr = `,
"ipMasq": %t`
ipMasqBackendConfStr = `,
"ipMasqBackend": "%s"`
// Single subnet configuration (legacy)
subnetConfStr = `,
"subnet": "%s"`
@ -238,6 +253,9 @@ func (tc testCase) netConfJSON(dataDir string) string {
if tc.ipMasq {
conf += tc.ipMasqConfig()
}
if tc.ipMasqBackend != "" {
conf += tc.ipMasqBackendConfig()
}
if tc.args.cni.mac != "" {
conf += fmt.Sprintf(argsFormat, tc.args.cni.mac)
}
@ -248,6 +266,14 @@ func (tc testCase) netConfJSON(dataDir string) string {
conf += fmt.Sprintf(macspoofchkFormat, tc.macspoofchk)
}
if tc.disableContIface {
conf += disableContainerInterface
}
if tc.portIsolation {
conf += portIsolation
}
if !tc.isLayer2 {
conf += netDefault
if tc.subnet != "" || tc.ranges != nil {
@ -286,6 +312,11 @@ func (tc testCase) ipMasqConfig() string {
return conf
}
func (tc testCase) ipMasqBackendConfig() string {
conf := fmt.Sprintf(ipMasqBackendConfStr, tc.ipMasqBackend)
return conf
}
func (tc testCase) rangesConfig() string {
conf := rangesStartStr
for i, tcRange := range tc.ranges {
@ -485,6 +516,11 @@ type (
func newTesterByVersion(version string, testNS, targetNS ns.NetNS) cmdAddDelTester {
switch {
case strings.HasPrefix(version, "1.1."):
return &testerV10x{
testNS: testNS,
targetNS: targetNS,
}
case strings.HasPrefix(version, "1.0."):
return &testerV10x{
testNS: testNS,
@ -621,6 +657,10 @@ func (tester *testerV10x) cmdAddTest(tc testCase, dataDir string) (types.Result,
Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{}))
tester.vethName = result.Interfaces[1].Name
protInfo, err := netlink.LinkGetProtinfo(link)
Expect(err).NotTo(HaveOccurred())
Expect(protInfo.Isolated).To(Equal(tc.portIsolation), "link isolation should be on when portIsolation is set")
// check vlan exist on the veth interface
if tc.vlan != 0 {
interfaceMap, err := netlink.BridgeVlanList()
@ -677,14 +717,16 @@ func (tester *testerV10x) cmdAddTest(tc testCase, dataDir string) (types.Result,
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal(IFNAME))
Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{}))
assertContainerInterfaceLinkState(&tc, link)
expCIDRsV4, expCIDRsV6 := tc.expectedCIDRs()
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
Expect(err).NotTo(HaveOccurred())
Expect(addrs).To(HaveLen(len(expCIDRsV4)))
addrs, err = netlink.AddrList(link, netlink.FAMILY_V6)
Expect(addrs).To(HaveLen(len(expCIDRsV6) + 1)) // add one for the link-local
Expect(err).NotTo(HaveOccurred())
assertIPv6Addresses(&tc, addrs, expCIDRsV6)
// Ignore link local address which may or may not be
// ready when we read addresses.
var foundAddrs int
@ -713,7 +755,7 @@ func (tester *testerV10x) cmdAddTest(tc testCase, dataDir string) (types.Result,
continue
}
for _, route := range routes {
*found = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwIP))
*found = (ip.IsIPNetZero(route.Dst) && route.Src == nil && route.Gw.Equal(gwIP))
if *found {
break
}
@ -728,6 +770,15 @@ func (tester *testerV10x) cmdAddTest(tc testCase, dataDir string) (types.Result,
return result, nil
}
func assertContainerInterfaceLinkState(tc *testCase, link netlink.Link) {
linkState := int(link.Attrs().OperState)
if tc.disableContIface {
Expect(linkState).ToNot(Equal(netlink.OperUp))
} else {
Expect(linkState).To(Equal(netlink.OperUp))
}
}
func (tester *testerV10x) cmdCheckTest(tc testCase, conf *Net, _ string) {
// Generate network config and command arguments
tester.args = tc.createCheckCmdArgs(tester.targetNS, conf)
@ -789,7 +840,7 @@ func (tester *testerV10x) cmdCheckTest(tc testCase, conf *Net, _ string) {
continue
}
for _, route := range routes {
*found = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwIP))
*found = (ip.IsIPNetZero(route.Dst) && route.Src == nil && route.Gw.Equal(gwIP))
if *found {
break
}
@ -1008,8 +1059,9 @@ func (tester *testerV04x) cmdAddTest(tc testCase, dataDir string) (types.Result,
Expect(err).NotTo(HaveOccurred())
Expect(addrs).To(HaveLen(len(expCIDRsV4)))
addrs, err = netlink.AddrList(link, netlink.FAMILY_V6)
Expect(addrs).To(HaveLen(len(expCIDRsV6) + 1)) // add one for the link-local
Expect(err).NotTo(HaveOccurred())
assertIPv6Addresses(&tc, addrs, expCIDRsV6)
// Ignore link local address which may or may not be
// ready when we read addresses.
var foundAddrs int
@ -1038,7 +1090,7 @@ func (tester *testerV04x) cmdAddTest(tc testCase, dataDir string) (types.Result,
continue
}
for _, route := range routes {
*found = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwIP))
*found = (ip.IsIPNetZero(route.Dst) && route.Src == nil && route.Gw.Equal(gwIP))
if *found {
break
}
@ -1053,6 +1105,14 @@ func (tester *testerV04x) cmdAddTest(tc testCase, dataDir string) (types.Result,
return result, nil
}
func assertIPv6Addresses(tc *testCase, addrs []netlink.Addr, expCIDRsV6 []*net.IPNet) {
if tc.disableContIface {
Expect(addrs).To(BeEmpty())
} else {
Expect(addrs).To(HaveLen(len(expCIDRsV6) + 1)) // add one for the link-local
}
}
func (tester *testerV04x) cmdCheckTest(tc testCase, conf *Net, _ string) {
// Generate network config and command arguments
tester.args = tc.createCheckCmdArgs(tester.targetNS, conf)
@ -1114,7 +1174,7 @@ func (tester *testerV04x) cmdCheckTest(tc testCase, conf *Net, _ string) {
continue
}
for _, route := range routes {
*found = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwIP))
*found = (ip.IsIPNetZero(route.Dst) && route.Src == nil && route.Gw.Equal(gwIP))
if *found {
break
}
@ -1362,7 +1422,7 @@ func (tester *testerV03x) cmdAddTest(tc testCase, dataDir string) (types.Result,
continue
}
for _, route := range routes {
*found = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwIP))
*found = (ip.IsIPNetZero(route.Dst) && route.Src == nil && route.Gw.Equal(gwIP))
if *found {
break
}
@ -1440,6 +1500,14 @@ func (tester *testerV01xOr02x) cmdAddTest(tc testCase, dataDir string) (types.Re
err := tester.testNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
// check that STATUS is
if testutils.SpecVersionHasSTATUS(tc.cniVersion) {
err := testutils.CmdStatus(func() error {
return cmdStatus(&skel.CmdArgs{StdinData: []byte(tc.netConfJSON(dataDir))})
})
Expect(err).NotTo(HaveOccurred())
}
r, raw, err := testutils.CmdAddWithArgs(tester.args, func() error {
return cmdAdd(tester.args)
})
@ -1583,7 +1651,7 @@ func (tester *testerV01xOr02x) cmdAddTest(tc testCase, dataDir string) (types.Re
continue
}
for _, route := range routes {
*found = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwIP))
*found = (ip.IsIPNetZero(route.Dst) && route.Src == nil && route.Gw.Equal(gwIP))
if *found {
break
}
@ -1847,10 +1915,10 @@ var _ = Describe("bridge Operations", func() {
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = BRNAME
err := netlink.LinkAdd(&netlink.Bridge{
LinkAttrs: netlink.LinkAttrs{
Name: BRNAME,
},
LinkAttrs: linkAttrs,
})
Expect(err).NotTo(HaveOccurred())
link, err := netlink.LinkByName(BRNAME)
@ -2361,41 +2429,82 @@ var _ = Describe("bridge Operations", func() {
})
if testutils.SpecVersionHasChaining(ver) {
It(fmt.Sprintf("[%s] configures a bridge and ipMasq rules", ver), func() {
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
tc := testCase{
ranges: []rangeInfo{{
subnet: "10.1.2.0/24",
}},
ipMasq: true,
cniVersion: ver,
}
for _, tc := range []testCase{
{
ranges: []rangeInfo{{
subnet: "10.1.2.0/24",
}},
ipMasq: true,
cniVersion: ver,
},
{
ranges: []rangeInfo{{
subnet: "10.1.2.0/24",
}},
ipMasq: true,
ipMasqBackend: "iptables",
cniVersion: ver,
},
{
ranges: []rangeInfo{{
subnet: "10.1.2.0/24",
}},
ipMasq: true,
ipMasqBackend: "nftables",
cniVersion: ver,
},
} {
tc := tc
It(fmt.Sprintf("[%s] configures a bridge and ipMasq rules with ipMasqBackend %q", ver, tc.ipMasqBackend), func() {
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
args := tc.createCmdArgs(originalNS, dataDir)
r, _, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
args := tc.createCmdArgs(originalNS, dataDir)
r, _, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
result, err := types100.GetResult(r)
Expect(err).NotTo(HaveOccurred())
Expect(result.IPs).Should(HaveLen(1))
ip := result.IPs[0].Address.IP.String()
// Update this if the default ipmasq backend changes
switch tc.ipMasqBackend {
case "iptables", "":
ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
Expect(err).NotTo(HaveOccurred())
rules, err := ipt.List("nat", "POSTROUTING")
Expect(err).NotTo(HaveOccurred())
Expect(rules).Should(ContainElement(ContainSubstring(ip)))
case "nftables":
nft, err := knftables.New(knftables.InetFamily, "cni_plugins_masquerade")
Expect(err).NotTo(HaveOccurred())
rules, err := nft.ListRules(context.TODO(), "masq_checks")
Expect(err).NotTo(HaveOccurred())
// FIXME: ListRules() doesn't return the actual rule strings,
// and we can't easily compute the ipmasq plugin's comment.
comments := 0
for _, r := range rules {
if r.Comment != nil {
comments++
break
}
}
Expect(comments).To(Equal(1), "expected to find exactly one Rule with a comment")
}
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
result, err := types100.GetResult(r)
Expect(err).NotTo(HaveOccurred())
Expect(result.IPs).Should(HaveLen(1))
ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
Expect(err).NotTo(HaveOccurred())
rules, err := ipt.List("nat", "POSTROUTING")
Expect(err).NotTo(HaveOccurred())
Expect(rules).Should(ContainElement(ContainSubstring(result.IPs[0].Address.IP.String())))
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
}
for i, tc := range []testCase{
{
@ -2461,6 +2570,66 @@ var _ = Describe("bridge Operations", func() {
return nil
})).To(Succeed())
})
It(fmt.Sprintf("[%s] should fail when both IPAM and DisableContainerInterface are set", ver), func() {
Expect(originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
tc := testCase{
cniVersion: ver,
subnet: "10.1.2.0/24",
disableContIface: true,
}
args := tc.createCmdArgs(targetNS, dataDir)
Expect(cmdAdd(args)).To(HaveOccurred())
return nil
})).To(Succeed())
})
It(fmt.Sprintf("[%s] should set the container veth peer state down", ver), func() {
Expect(originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
tc := testCase{
cniVersion: ver,
disableContIface: true,
isLayer2: true,
AddErr020: "cannot convert: no valid IP addresses",
AddErr010: "cannot convert: no valid IP addresses",
}
cmdAddDelTest(originalNS, targetNS, tc, dataDir)
return nil
})).To(Succeed())
})
It(fmt.Sprintf("[%s] when port-isolation is off, should set the veth peer on node with isolation off", ver), func() {
Expect(originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
tc := testCase{
cniVersion: ver,
portIsolation: false,
isLayer2: true,
AddErr020: "cannot convert: no valid IP addresses",
AddErr010: "cannot convert: no valid IP addresses",
}
cmdAddDelTest(originalNS, targetNS, tc, dataDir)
return nil
})).To(Succeed())
})
It(fmt.Sprintf("[%s] when port-isolation is on, should set the veth peer on node with isolation on", ver), func() {
Expect(originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
tc := testCase{
cniVersion: ver,
portIsolation: true,
isLayer2: true,
AddErr020: "cannot convert: no valid IP addresses",
AddErr010: "cannot convert: no valid IP addresses",
}
cmdAddDelTest(originalNS, targetNS, tc, dataDir)
return nil
})).To(Succeed())
})
}
It("check vlan id when loading net conf", func() {

View File

@ -43,11 +43,12 @@ func parseNetConf(bytes []byte) (*types.NetConf, error) {
func createDummy(ifName string, netns ns.NetNS) (*current.Interface, error) {
dummy := &current.Interface{}
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = ifName
linkAttrs.Namespace = netlink.NsFd(int(netns.Fd()))
dm := &netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: ifName,
Namespace: netlink.NsFd(int(netns.Fd())),
},
LinkAttrs: linkAttrs,
}
if err := netlink.LinkAdd(dm); err != nil {
@ -136,7 +137,6 @@ func cmdAdd(args *skel.CmdArgs) error {
err = netns.Do(func(_ ns.NetNS) error {
return ipam.ConfigureIface(args.IfName, result)
})
if err != nil {
return err
}
@ -165,7 +165,6 @@ func cmdDel(args *skel.CmdArgs) error {
}
return err
})
if err != nil {
// if NetNs is passed down by the Cloud Orchestration Engine, or if it called multiple times
// so don't return an error if the device is already removed.
@ -181,7 +180,13 @@ func cmdDel(args *skel.CmdArgs) error {
}
func main() {
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("dummy"))
skel.PluginMainFuncs(skel.CNIFuncs{
Add: cmdAdd,
Check: cmdCheck,
Del: cmdDel,
Status: cmdStatus,
/* FIXME GC */
}, version.All, bv.BuildString("dummy"))
}
func cmdCheck(args *skel.CmdArgs) error {
@ -290,3 +295,16 @@ func validateCniContainerInterface(intf current.Interface) error {
return nil
}
func cmdStatus(args *skel.CmdArgs) error {
conf := types.NetConf{}
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
return fmt.Errorf("failed to load netconf: %w", err)
}
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
return err
}
return nil
}

View File

@ -106,7 +106,7 @@ type (
func newTesterByVersion(version string) tester {
switch {
case strings.HasPrefix(version, "1.0."):
case strings.HasPrefix(version, "1."):
return &testerV10x{}
case strings.HasPrefix(version, "0.4."):
return &testerV04x{}
@ -180,11 +180,11 @@ var _ = Describe("dummy Operations", func() {
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = MASTER_NAME
// Add master
err = netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: MASTER_NAME,
},
LinkAttrs: linkAttrs,
})
Expect(err).NotTo(HaveOccurred())
m, err := netlink.LinkByName(MASTER_NAME)
@ -261,6 +261,13 @@ var _ = Describe("dummy Operations", func() {
defer GinkgoRecover()
var err error
if testutils.SpecVersionHasSTATUS(ver) {
err = testutils.CmdStatus(func() error {
return cmdStatus(args)
})
Expect(err).NotTo(HaveOccurred())
}
result, _, err = testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
@ -371,7 +378,7 @@ var _ = Describe("dummy Operations", func() {
StdinData: []byte(fmt.Sprintf(confFmt, ver)),
}
_ = originalNS.Do(func(netNS ns.NetNS) error {
_ = originalNS.Do(func(_ ns.NetNS) error {
defer GinkgoRecover()
_, _, err = testutils.CmdAddWithArgs(args, func() error {

View File

@ -37,7 +37,10 @@ import (
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
var sysBusPCI = "/sys/bus/pci/devices"
var (
sysBusPCI = "/sys/bus/pci/devices"
sysBusAuxiliary = "/sys/bus/auxiliary/devices"
)
// Array of different linux drivers bound to network device needed for DPDK
var userspaceDrivers = []string{"vfio-pci", "uio_pci_generic", "igb_uio"}
@ -53,6 +56,9 @@ type NetConf struct {
RuntimeConfig struct {
DeviceID string `json:"deviceID,omitempty"`
} `json:"runtimeConfig,omitempty"`
// for internal use
auxDevice string `json:"-"` // Auxiliary device name as appears on Auxiliary bus (/sys/bus/auxiliary)
}
func init() {
@ -62,6 +68,31 @@ func init() {
runtime.LockOSThread()
}
// handleDeviceID updates netconf fields with DeviceID runtime config
func handleDeviceID(netconf *NetConf) error {
deviceID := netconf.RuntimeConfig.DeviceID
if deviceID == "" {
return nil
}
// Check if deviceID is a PCI device
pciPath := filepath.Join(sysBusPCI, deviceID)
if _, err := os.Stat(pciPath); err == nil {
netconf.PCIAddr = deviceID
return nil
}
// Check if deviceID is an Auxiliary device
auxPath := filepath.Join(sysBusAuxiliary, deviceID)
if _, err := os.Stat(auxPath); err == nil {
netconf.PCIAddr = ""
netconf.auxDevice = deviceID
return nil
}
return fmt.Errorf("runtime config DeviceID %s not found or unsupported", deviceID)
}
func loadConf(bytes []byte) (*NetConf, error) {
n := &NetConf{}
var err error
@ -69,12 +100,12 @@ func loadConf(bytes []byte) (*NetConf, error) {
return nil, fmt.Errorf("failed to load netconf: %v", err)
}
if n.RuntimeConfig.DeviceID != "" {
// Override PCI device with the standardized DeviceID provided in Runtime Config.
n.PCIAddr = n.RuntimeConfig.DeviceID
// Override device with the standardized DeviceID if provided in Runtime Config.
if err := handleDeviceID(n); err != nil {
return nil, err
}
if n.Device == "" && n.HWAddr == "" && n.KernelPath == "" && n.PCIAddr == "" {
if n.Device == "" && n.HWAddr == "" && n.KernelPath == "" && n.PCIAddr == "" && n.auxDevice == "" {
return nil, fmt.Errorf(`specify either "device", "hwaddr", "kernelpath" or "pciBusID"`)
}
@ -102,7 +133,7 @@ func cmdAdd(args *skel.CmdArgs) error {
result := &current.Result{}
var contDev netlink.Link
if !cfg.DPDKMode {
hostDev, err := getLink(cfg.Device, cfg.HWAddr, cfg.KernelPath, cfg.PCIAddr)
hostDev, err := getLink(cfg.Device, cfg.HWAddr, cfg.KernelPath, cfg.PCIAddr, cfg.auxDevice)
if err != nil {
return fmt.Errorf("failed to find host device: %v", err)
}
@ -199,40 +230,121 @@ func cmdDel(args *skel.CmdArgs) error {
return nil
}
func moveLinkIn(hostDev netlink.Link, containerNs ns.NetNS, ifName string) (netlink.Link, error) {
if err := netlink.LinkSetNsFd(hostDev, int(containerNs.Fd())); err != nil {
return nil, err
func moveLinkIn(hostDev netlink.Link, containerNs ns.NetNS, containerIfName string) (netlink.Link, error) {
hostDevName := hostDev.Attrs().Name
// With recent kernels we could do all changes in a single netlink call,
// but on failure the device is left in a partially modified state.
// Doing changes one by one allow us to (try to) rollback to the initial state.
// Create a temporary namespace to rename (and modify) the device in.
// We were previously using a temporary name, but rapid rename leads to
// race condition with udev and NetworkManager.
tempNS, err := ns.TempNetNS()
if err != nil {
return nil, fmt.Errorf("failed to create tempNS: %v", err)
}
defer tempNS.Close()
// Restore original up state in case of error
// This must be done in the hostNS as moving
// device between namespaces sets the link down
if hostDev.Attrs().Flags&net.FlagUp == net.FlagUp {
defer func() {
if err != nil {
// lookup the device again (index might have changed)
if hostDev, err := netlink.LinkByName(hostDevName); err == nil {
_ = netlink.LinkSetUp(hostDev)
}
}
}()
}
// Move the host device into tempNS
if err = netlink.LinkSetNsFd(hostDev, int(tempNS.Fd())); err != nil {
return nil, fmt.Errorf("failed to move %q to tempNS: %v", hostDevName, err)
}
var contDev netlink.Link
if err := containerNs.Do(func(_ ns.NetNS) error {
var err error
contDev, err = netlink.LinkByName(hostDev.Attrs().Name)
// In a container in container scenario, hostNS is not the initial net namespace,
// but host / container naming is easier to follow.
if err = tempNS.Do(func(hostNS ns.NetNS) error {
// lookup the device in tempNS (index might have changed)
tempNSDev, err := netlink.LinkByName(hostDevName)
if err != nil {
return fmt.Errorf("failed to find %q: %v", hostDev.Attrs().Name, err)
return fmt.Errorf("failed to find %q in tempNS: %v", hostDevName, err)
}
// Devices can be renamed only when down
if err = netlink.LinkSetDown(contDev); err != nil {
return fmt.Errorf("failed to set %q down: %v", hostDev.Attrs().Name, err)
// detroying a non empty tempNS would move physical devices back to the initial net namespace,
// not the namespace of the "parent" process, and virtual devices would be destroyed,
// so we need to actively move the device back to hostNS on error
defer func() {
if err != nil && tempNSDev != nil {
_ = netlink.LinkSetNsFd(tempNSDev, int(hostNS.Fd()))
}
}()
// Rename the device to the wanted name
if err = netlink.LinkSetName(tempNSDev, containerIfName); err != nil {
return fmt.Errorf("failed to rename host device %q to %q: %v", hostDevName, containerIfName, err)
}
// Restore the original device name in case of error
defer func() {
if err != nil && tempNSDev != nil {
_ = netlink.LinkSetName(tempNSDev, hostDevName)
}
}()
// Save host device name into the container device's alias property
if err := netlink.LinkSetAlias(contDev, hostDev.Attrs().Name); err != nil {
return fmt.Errorf("failed to set alias to %q: %v", hostDev.Attrs().Name, err)
if err = netlink.LinkSetAlias(tempNSDev, hostDevName); err != nil {
return fmt.Errorf("failed to set alias to %q: %v", hostDevName, err)
}
// Rename container device to respect args.IfName
if err := netlink.LinkSetName(contDev, ifName); err != nil {
return fmt.Errorf("failed to rename device %q to %q: %v", hostDev.Attrs().Name, ifName, err)
// Remove the alias on error
defer func() {
if err != nil && tempNSDev != nil {
_ = netlink.LinkSetAlias(tempNSDev, "")
}
}()
// Move the device to the containerNS
if err = netlink.LinkSetNsFd(tempNSDev, int(containerNs.Fd())); err != nil {
return fmt.Errorf("failed to move %q (host: %q) to container NS: %v", containerIfName, hostDevName, err)
}
// Bring container device up
if err = netlink.LinkSetUp(contDev); err != nil {
return fmt.Errorf("failed to set %q up: %v", ifName, err)
}
// Retrieve link again to get up-to-date name and attributes
contDev, err = netlink.LinkByName(ifName)
if err != nil {
return fmt.Errorf("failed to find %q: %v", ifName, err)
}
return nil
// Lookup the device again on error, the index might have changed
defer func() {
if err != nil {
tempNSDev, _ = netlink.LinkByName(containerIfName)
}
}()
err = containerNs.Do(func(_ ns.NetNS) error {
var err error
contDev, err = netlink.LinkByName(containerIfName)
if err != nil {
return fmt.Errorf("failed to find %q in container NS: %v", containerIfName, err)
}
// Move the interface back to tempNS on error
defer func() {
if err != nil {
_ = netlink.LinkSetNsFd(contDev, int(tempNS.Fd()))
}
}()
// Bring the device up
// This must be done in the containerNS
if err = netlink.LinkSetUp(contDev); err != nil {
return fmt.Errorf("failed to set %q up: %v", containerIfName, err)
}
return nil
})
return err
}); err != nil {
return nil, err
}
@ -240,45 +352,110 @@ func moveLinkIn(hostDev netlink.Link, containerNs ns.NetNS, ifName string) (netl
return contDev, nil
}
func moveLinkOut(containerNs ns.NetNS, ifName string) error {
defaultNs, err := ns.GetCurrentNS()
func moveLinkOut(containerNs ns.NetNS, containerIfName string) error {
// Create a temporary namespace to rename (and modify) the device in.
// We were previously using a temporary name, but multiple rapid renames
// leads to race condition with udev and NetworkManager.
tempNS, err := ns.TempNetNS()
if err != nil {
return err
return fmt.Errorf("failed to create tempNS: %v", err)
}
defer defaultNs.Close()
defer tempNS.Close()
return containerNs.Do(func(_ ns.NetNS) error {
dev, err := netlink.LinkByName(ifName)
if err != nil {
return fmt.Errorf("failed to find %q: %v", ifName, err)
}
var contDev netlink.Link
// Devices can be renamed only when down
if err = netlink.LinkSetDown(dev); err != nil {
return fmt.Errorf("failed to set %q down: %v", ifName, err)
}
defer func() {
// If moving the device to the host namespace fails, set its name back to ifName so that this
// function can be retried. Also bring the device back up, unless it was already down before.
if err != nil {
_ = netlink.LinkSetName(dev, ifName)
if dev.Attrs().Flags&net.FlagUp == net.FlagUp {
_ = netlink.LinkSetUp(dev)
// Restore original up state in case of error
// This must be done in the containerNS as moving
// device between namespaces sets the link down
defer func() {
if err != nil && contDev != nil && contDev.Attrs().Flags&net.FlagUp == net.FlagUp {
containerNs.Do(func(_ ns.NetNS) error {
// lookup the device again (index might have changed)
if contDev, err := netlink.LinkByName(containerIfName); err == nil {
_ = netlink.LinkSetUp(contDev)
}
}
}()
return nil
})
}
}()
// Rename the device to its original name from the host namespace
if err = netlink.LinkSetName(dev, dev.Attrs().Alias); err != nil {
return fmt.Errorf("failed to restore %q to original name %q: %v", ifName, dev.Attrs().Alias, err)
err = containerNs.Do(func(_ ns.NetNS) error {
var err error
// Lookup the device in the containerNS
contDev, err = netlink.LinkByName(containerIfName)
if err != nil {
return fmt.Errorf("failed to find %q in containerNS: %v", containerIfName, err)
}
if err = netlink.LinkSetNsFd(dev, int(defaultNs.Fd())); err != nil {
return fmt.Errorf("failed to move %q to host netns: %v", dev.Attrs().Alias, err)
// Verify we have the original name
if contDev.Attrs().Alias == "" {
return fmt.Errorf("failed to find original ifname for %q (alias is not set)", containerIfName)
}
// Move the device to the tempNS
if err = netlink.LinkSetNsFd(contDev, int(tempNS.Fd())); err != nil {
return fmt.Errorf("failed to move %q to tempNS: %v", containerIfName, err)
}
return nil
})
if err != nil {
return err
}
err = tempNS.Do(func(hostNS ns.NetNS) error {
// Lookup the device in tempNS (index might have changed)
tempNSDev, err := netlink.LinkByName(containerIfName)
if err != nil {
return fmt.Errorf("failed to find %q in tempNS: %v", containerIfName, err)
}
// Move the device back to containerNS on error
defer func() {
if err != nil {
_ = netlink.LinkSetNsFd(tempNSDev, int(containerNs.Fd()))
}
}()
hostDevName := tempNSDev.Attrs().Alias
// Rename container device to hostDevName
if err = netlink.LinkSetName(tempNSDev, hostDevName); err != nil {
return fmt.Errorf("failed to rename device %q to %q: %v", containerIfName, hostDevName, err)
}
// Rename the device back to containerIfName on error
defer func() {
if err != nil {
_ = netlink.LinkSetName(tempNSDev, containerIfName)
}
}()
// Unset device's alias property
if err = netlink.LinkSetAlias(tempNSDev, ""); err != nil {
return fmt.Errorf("failed to unset alias of %q: %v", hostDevName, err)
}
// Set back the device alias to hostDevName on error
defer func() {
if err != nil {
_ = netlink.LinkSetAlias(tempNSDev, hostDevName)
}
}()
// Finally move the device to the hostNS
if err = netlink.LinkSetNsFd(tempNSDev, int(hostNS.Fd())); err != nil {
return fmt.Errorf("failed to move %q to hostNS: %v", hostDevName, err)
}
// As we don't know the previous state, leave the link down
return nil
})
if err != nil {
return err
}
return nil
}
func hasDpdkDriver(pciaddr string) (bool, error) {
@ -314,11 +491,19 @@ func printLink(dev netlink.Link, cniVersion string, containerNs ns.NetNS) error
return types.PrintResult(&result, cniVersion)
}
func getLink(devname, hwaddr, kernelpath, pciaddr string) (netlink.Link, error) {
links, err := netlink.LinkList()
func linkFromPath(path string) (netlink.Link, error) {
entries, err := os.ReadDir(path)
if err != nil {
return nil, fmt.Errorf("failed to list node links: %v", err)
return nil, fmt.Errorf("failed to read directory %s: %q", path, err)
}
if len(entries) > 0 {
// grab the first net device
return netlink.LinkByName(entries[0].Name())
}
return nil, fmt.Errorf("failed to find network device in path %s", path)
}
func getLink(devname, hwaddr, kernelpath, pciaddr string, auxDev string) (netlink.Link, error) {
switch {
case len(devname) > 0:
@ -329,6 +514,11 @@ func getLink(devname, hwaddr, kernelpath, pciaddr string) (netlink.Link, error)
return nil, fmt.Errorf("failed to parse MAC address %q: %v", hwaddr, err)
}
links, err := netlink.LinkList()
if err != nil {
return nil, fmt.Errorf("failed to list node links: %v", err)
}
for _, link := range links {
if bytes.Equal(link.Attrs().HardwareAddr, hwAddr) {
return link, nil
@ -339,20 +529,7 @@ func getLink(devname, hwaddr, kernelpath, pciaddr string) (netlink.Link, error)
return nil, fmt.Errorf("kernel device path %q must be absolute and begin with /sys/devices/", kernelpath)
}
netDir := filepath.Join(kernelpath, "net")
entries, err := os.ReadDir(netDir)
if err != nil {
return nil, fmt.Errorf("failed to find network devices at %q", netDir)
}
// Grab the first device from eg /sys/devices/pci0000:00/0000:00:19.0/net
for _, entry := range entries {
// Make sure it's really an interface
for _, l := range links {
if entry.Name() == l.Attrs().Name {
return l, nil
}
}
}
return linkFromPath(netDir)
case len(pciaddr) > 0:
netDir := filepath.Join(sysBusPCI, pciaddr, "net")
if _, err := os.Lstat(netDir); err != nil {
@ -363,21 +540,23 @@ func getLink(devname, hwaddr, kernelpath, pciaddr string) (netlink.Link, error)
}
netDir = matches[0]
}
entries, err := os.ReadDir(netDir)
if err != nil {
return nil, fmt.Errorf("failed to read net directory %s: %q", netDir, err)
}
if len(entries) > 0 {
return netlink.LinkByName(entries[0].Name())
}
return nil, fmt.Errorf("failed to find device name for pci address %s", pciaddr)
return linkFromPath(netDir)
case len(auxDev) > 0:
netDir := filepath.Join(sysBusAuxiliary, auxDev, "net")
return linkFromPath(netDir)
}
return nil, fmt.Errorf("failed to find physical interface")
}
func main() {
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("host-device"))
skel.PluginMainFuncs(skel.CNIFuncs{
Add: cmdAdd,
Check: cmdCheck,
Del: cmdDel,
Status: cmdStatus,
/* FIXME GC */
}, version.All, bv.BuildString("host-device"))
}
func cmdCheck(args *skel.CmdArgs) error {
@ -484,3 +663,20 @@ func validateCniContainerInterface(intf current.Interface) error {
return nil
}
func cmdStatus(args *skel.CmdArgs) error {
conf := NetConf{}
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
return fmt.Errorf("failed to load netconf: %w", err)
}
if conf.IPAM.Type != "" {
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
return err
}
}
// TODO: Check if host device exists.
return nil
}

View File

@ -231,7 +231,7 @@ type (
func newTesterByVersion(version string) tester {
switch {
case strings.HasPrefix(version, "1.0."):
case strings.HasPrefix(version, "1."):
return &testerV10x{}
case strings.HasPrefix(version, "0.4."):
return &testerV04x{}
@ -341,10 +341,10 @@ var _ = Describe("base functionality", func() {
// prepare ifname in original namespace
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = ifname
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: ifname,
},
LinkAttrs: linkAttrs,
})
Expect(err).NotTo(HaveOccurred())
origLink, err = netlink.LinkByName(ifname)
@ -362,6 +362,15 @@ var _ = Describe("base functionality", func() {
"type": "host-device",
"device": %q
}`, ver, ifname)
// if v1.1 or greater, call CmdStatus
if testutils.SpecVersionHasSTATUS(ver) {
err := testutils.CmdStatus(func() error {
return cmdStatus(&skel.CmdArgs{StdinData: []byte(conf)})
})
Expect(err).NotTo(HaveOccurred())
}
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNS.Path(),
@ -422,10 +431,10 @@ var _ = Describe("base functionality", func() {
// prepare host device in original namespace
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = ifname
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: ifname,
},
LinkAttrs: linkAttrs,
})
Expect(err).NotTo(HaveOccurred())
origLink, err = netlink.LinkByName(ifname)
@ -483,10 +492,10 @@ var _ = Describe("base functionality", func() {
// create another conflict host device with same name "dummy0"
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = ifname
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: ifname,
},
LinkAttrs: linkAttrs,
})
Expect(err).NotTo(HaveOccurred())
conflictLink, err = netlink.LinkByName(ifname)
@ -608,10 +617,10 @@ var _ = Describe("base functionality", func() {
// prepare ifname in original namespace
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = ifname
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: ifname,
},
LinkAttrs: linkAttrs,
})
Expect(err).NotTo(HaveOccurred())
origLink, err = netlink.LinkByName(ifname)
@ -720,10 +729,10 @@ var _ = Describe("base functionality", func() {
// prepare ifname in original namespace
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = ifname
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: ifname,
},
LinkAttrs: linkAttrs,
})
Expect(err).NotTo(HaveOccurred())
origLink, err = netlink.LinkByName(ifname)
@ -898,16 +907,81 @@ var _ = Describe("base functionality", func() {
})
})
It(fmt.Sprintf("Works with a valid %s config on auxiliary device", ver), func() {
var origLink netlink.Link
ifname := "eth0"
fs := &fakeFilesystem{
dirs: []string{
fmt.Sprintf("sys/bus/auxiliary/devices/mlx5_core.sf.4/net/%s", ifname),
},
}
defer fs.use()()
// prepare ifname in original namespace
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = ifname
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: linkAttrs,
})
Expect(err).NotTo(HaveOccurred())
origLink, err = netlink.LinkByName(ifname)
Expect(err).NotTo(HaveOccurred())
err = netlink.LinkSetUp(origLink)
Expect(err).NotTo(HaveOccurred())
return nil
})
// call CmdAdd
cniName := "net1"
conf := fmt.Sprintf(`{
"cniVersion": "%s",
"name": "cni-plugin-host-device-test",
"type": "host-device",
"runtimeConfig": {"deviceID": %q}
}`, ver, "mlx5_core.sf.4")
args := &skel.CmdArgs{
ContainerID: "dummy",
IfName: cniName,
Netns: targetNS.Path(),
StdinData: []byte(conf),
}
var resI types.Result
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
var err error
resI, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) })
return err
})
Expect(err).NotTo(HaveOccurred())
// check that the result was sane
t := newTesterByVersion(ver)
t.expectInterfaces(resI, cniName, origLink.Attrs().HardwareAddr.String(), targetNS.Path())
// call CmdDel
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
})
It(fmt.Sprintf("Works with a valid %s config with IPAM", ver), func() {
var origLink netlink.Link
// prepare ifname in original namespace
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = ifname
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: ifname,
},
LinkAttrs: linkAttrs,
})
Expect(err).NotTo(HaveOccurred())
origLink, err = netlink.LinkByName(ifname)
@ -1028,10 +1102,10 @@ var _ = Describe("base functionality", func() {
// prepare host device in original namespace
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = ifname
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: ifname,
},
LinkAttrs: linkAttrs,
})
Expect(err).NotTo(HaveOccurred())
origLink, err = netlink.LinkByName(ifname)
@ -1089,10 +1163,10 @@ var _ = Describe("base functionality", func() {
// create another conflict host device with same name "dummy0"
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = ifname
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: ifname,
},
LinkAttrs: linkAttrs,
})
Expect(err).NotTo(HaveOccurred())
conflictLink, err = netlink.LinkByName(ifname)
@ -1149,6 +1223,114 @@ var _ = Describe("base functionality", func() {
return nil
})
})
It(fmt.Sprintf("Test CmdAdd/Del when additioinal interface alreay exists in container ns with same name. %s config", ver), func() {
var (
origLink netlink.Link
containerLink netlink.Link
)
hostIfname := "eth0"
containerAdditionalIfname := "eth0"
// prepare host device in original namespace
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = hostIfname
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: linkAttrs,
})
Expect(err).NotTo(HaveOccurred())
origLink, err = netlink.LinkByName(hostIfname)
Expect(err).NotTo(HaveOccurred())
err = netlink.LinkSetUp(origLink)
Expect(err).NotTo(HaveOccurred())
return nil
})
// prepare device in container namespace with same name as host device
_ = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = containerAdditionalIfname
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: linkAttrs,
})
Expect(err).NotTo(HaveOccurred())
containerLink, err = netlink.LinkByName(containerAdditionalIfname)
Expect(err).NotTo(HaveOccurred())
err = netlink.LinkSetUp(containerLink)
Expect(err).NotTo(HaveOccurred())
return nil
})
// call CmdAdd
cniName := "net1"
conf := fmt.Sprintf(`{
"cniVersion": "%s",
"name": "cni-plugin-host-device-test",
"type": "host-device",
"device": %q
}`, ver, hostIfname)
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNS.Path(),
IfName: cniName,
StdinData: []byte(conf),
}
var resI types.Result
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
var err error
resI, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) })
return err
})
Expect(err).NotTo(HaveOccurred())
// check that the result was sane
t := newTesterByVersion(ver)
t.expectInterfaces(resI, cniName, origLink.Attrs().HardwareAddr.String(), targetNS.Path())
// assert that host device is now in the target namespace and is up
_ = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(cniName)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
Expect(link.Attrs().Flags & net.FlagUp).To(Equal(net.FlagUp))
return nil
})
// call CmdDel, expect it to succeed
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).ToNot(HaveOccurred())
return nil
})
// assert container interface "eth0" still exists in target namespace and is up
err = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(containerAdditionalIfname)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(containerLink.Attrs().HardwareAddr))
Expect(link.Attrs().Flags & net.FlagUp).To(Equal(net.FlagUp))
return nil
})
Expect(err).NotTo(HaveOccurred())
// assert that host device is now back in the original namespace
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, err := netlink.LinkByName(hostIfname)
Expect(err).NotTo(HaveOccurred())
return nil
})
})
}
})
@ -1181,6 +1363,7 @@ func (fs *fakeFilesystem) use() func() {
}
sysBusPCI = path.Join(fs.rootDir, "/sys/bus/pci/devices")
sysBusAuxiliary = path.Join(fs.rootDir, "/sys/bus/auxiliary/devices")
return func() {
// remove temporary fake fs

View File

@ -144,14 +144,15 @@ func createIpvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interf
return nil, err
}
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.MTU = conf.MTU
linkAttrs.Name = tmpName
linkAttrs.ParentIndex = m.Attrs().Index
linkAttrs.Namespace = netlink.NsFd(int(netns.Fd()))
mv := &netlink.IPVlan{
LinkAttrs: netlink.LinkAttrs{
MTU: conf.MTU,
Name: tmpName,
ParentIndex: m.Attrs().Index,
Namespace: netlink.NsFd(int(netns.Fd())),
},
Mode: mode,
LinkAttrs: linkAttrs,
Mode: mode,
}
if conf.LinkContNs {
@ -195,7 +196,7 @@ func getDefaultRouteInterfaceName() (string, error) {
}
for _, v := range routeToDstIP {
if v.Dst == nil {
if ip.IsIPNetZero(v.Dst) {
l, err := netlink.LinkByIndex(v.LinkIndex)
if err != nil {
return "", err
@ -293,6 +294,7 @@ func cmdAdd(args *skel.CmdArgs) error {
err = netns.Do(func(_ ns.NetNS) error {
_, _ = sysctl.Sysctl(fmt.Sprintf("net/ipv4/conf/%s/arp_notify", args.IfName), "1")
_, _ = sysctl.Sysctl(fmt.Sprintf("net/ipv6/conf/%s/ndisc_notify", args.IfName), "1")
return ipam.ConfigureIface(args.IfName, result)
})
@ -333,7 +335,6 @@ func cmdDel(args *skel.CmdArgs) error {
}
return nil
})
if err != nil {
// if NetNs is passed down by the Cloud Orchestration Engine, or if it called multiple times
// so don't return an error if the device is already removed.
@ -349,7 +350,13 @@ func cmdDel(args *skel.CmdArgs) error {
}
func main() {
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("ipvlan"))
skel.PluginMainFuncs(skel.CNIFuncs{
Add: cmdAdd,
Check: cmdCheck,
Del: cmdDel,
Status: cmdStatus,
/* FIXME GC */
}, version.All, bv.BuildString("ipvlan"))
}
func cmdCheck(args *skel.CmdArgs) error {
@ -484,3 +491,19 @@ func validateCniContainerInterface(intf current.Interface, modeExpected string)
return nil
}
func cmdStatus(args *skel.CmdArgs) error {
conf := NetConf{}
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
return fmt.Errorf("failed to load netconf: %w", err)
}
if conf.IPAM.Type != "" {
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
return err
}
}
// TODO: Check if master interface exists.
return nil
}

View File

@ -114,6 +114,13 @@ func ipvlanAddCheckDelTest(conf, masterName string, originalNS, targetNS ns.NetN
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
if testutils.SpecVersionHasSTATUS(cniVersion) {
err = testutils.CmdStatus(func() error {
return cmdStatus(args)
})
Expect(err).NotTo(HaveOccurred())
}
result, _, err = testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
@ -214,7 +221,7 @@ type (
func newTesterByVersion(version string) tester {
switch {
case strings.HasPrefix(version, "1.0."):
case strings.HasPrefix(version, "1."):
return &testerV10x{}
case strings.HasPrefix(version, "0.4.") || strings.HasPrefix(version, "0.3."):
return &testerV04x{}
@ -281,11 +288,11 @@ var _ = Describe("ipvlan Operations", func() {
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = MASTER_NAME
// Add master
err = netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: MASTER_NAME,
},
LinkAttrs: linkAttrs,
})
Expect(err).NotTo(HaveOccurred())
_, err = netlink.LinkByName(MASTER_NAME)
@ -297,11 +304,11 @@ var _ = Describe("ipvlan Operations", func() {
err = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = MASTER_NAME_INCONTAINER
// Add master
err = netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: MASTER_NAME_INCONTAINER,
},
LinkAttrs: linkAttrs,
})
Expect(err).NotTo(HaveOccurred())
_, err = netlink.LinkByName(MASTER_NAME_INCONTAINER)

View File

@ -172,7 +172,13 @@ func cmdDel(args *skel.CmdArgs) error {
}
func main() {
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("loopback"))
skel.PluginMainFuncs(skel.CNIFuncs{
Add: cmdAdd,
Check: cmdCheck,
Del: cmdDel,
/* FIXME GC */
/* FIXME Status */
}, version.All, bv.BuildString("loopback"))
}
func cmdCheck(args *skel.CmdArgs) error {

View File

@ -74,7 +74,8 @@ var _ = Describe("Loopback", func() {
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
Expect(err).NotTo(HaveOccurred())
Eventually(session).Should(gbytes.Say(`{.*}`))
// "(?s)" turns on the "s" flag, making "." match newlines too.
Eventually(session).Should(gbytes.Say(`(?s){.*}`))
Eventually(session).Should(gexec.Exit(0))
var lo *net.Interface

View File

@ -41,6 +41,7 @@ type NetConf struct {
MTU int `json:"mtu"`
Mac string `json:"mac,omitempty"`
LinkContNs bool `json:"linkInContainer,omitempty"`
BcQueueLen uint32 `json:"bcqueuelen,omitempty"`
RuntimeConfig struct {
Mac string `json:"mac,omitempty"`
@ -67,7 +68,7 @@ func getDefaultRouteInterfaceName() (string, error) {
}
for _, v := range routeToDstIP {
if v.Dst == nil {
if ip.IsIPNetZero(v.Dst) {
l, err := netlink.LinkByIndex(v.LinkIndex)
if err != nil {
return "", err
@ -225,12 +226,11 @@ func createMacvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Inter
return nil, err
}
linkAttrs := netlink.LinkAttrs{
MTU: conf.MTU,
Name: tmpName,
ParentIndex: m.Attrs().Index,
Namespace: netlink.NsFd(int(netns.Fd())),
}
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.MTU = conf.MTU
linkAttrs.Name = tmpName
linkAttrs.ParentIndex = m.Attrs().Index
linkAttrs.Namespace = netlink.NsFd(int(netns.Fd()))
if conf.Mac != "" {
addr, err := net.ParseMAC(conf.Mac)
@ -245,6 +245,8 @@ func createMacvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Inter
Mode: mode,
}
mv.BCQueueLen = conf.BcQueueLen
if conf.LinkContNs {
err = netns.Do(func(_ ns.NetNS) error {
return netlink.LinkAdd(mv)
@ -351,6 +353,7 @@ func cmdAdd(args *skel.CmdArgs) error {
err = netns.Do(func(_ ns.NetNS) error {
_, _ = sysctl.Sysctl(fmt.Sprintf("net/ipv4/conf/%s/arp_notify", args.IfName), "1")
_, _ = sysctl.Sysctl(fmt.Sprintf("net/ipv6/conf/%s/ndisc_notify", args.IfName), "1")
return ipam.ConfigureIface(args.IfName, result)
})
@ -382,13 +385,13 @@ func cmdAdd(args *skel.CmdArgs) error {
}
func cmdDel(args *skel.CmdArgs) error {
n, _, err := loadConf(args, args.Args)
var n NetConf
err := json.Unmarshal(args.StdinData, &n)
if err != nil {
return err
return fmt.Errorf("failed to load netConf: %v", err)
}
isLayer3 := n.IPAM.Type != ""
if isLayer3 {
err = ipam.ExecDel(n.IPAM.Type, args.StdinData)
if err != nil {
@ -410,7 +413,6 @@ func cmdDel(args *skel.CmdArgs) error {
}
return nil
})
if err != nil {
// if NetNs is passed down by the Cloud Orchestration Engine, or if it called multiple times
// so don't return an error if the device is already removed.
@ -426,7 +428,13 @@ func cmdDel(args *skel.CmdArgs) error {
}
func main() {
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("macvlan"))
skel.PluginMainFuncs(skel.CNIFuncs{
Add: cmdAdd,
Check: cmdCheck,
Del: cmdDel,
Status: cmdStatus,
/* FIXME GC */
}, version.All, bv.BuildString("macvlan"))
}
func cmdCheck(args *skel.CmdArgs) error {
@ -562,3 +570,20 @@ func validateCniContainerInterface(intf current.Interface, modeExpected string)
return nil
}
func cmdStatus(args *skel.CmdArgs) error {
conf := NetConf{}
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
return fmt.Errorf("failed to load netconf: %w", err)
}
if conf.IPAM.Type != "" {
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
return err
}
}
// TODO: Check if master interface exists.
return nil
}

View File

@ -117,7 +117,7 @@ type (
func newTesterByVersion(version string) tester {
switch {
case strings.HasPrefix(version, "1.0."):
case strings.HasPrefix(version, "1."):
return &testerV10x{}
case strings.HasPrefix(version, "0.4."):
return &testerV04x{}
@ -208,11 +208,11 @@ var _ = Describe("macvlan Operations", func() {
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = MASTER_NAME
// Add master
err = netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: MASTER_NAME,
},
LinkAttrs: linkAttrs,
})
Expect(err).NotTo(HaveOccurred())
_, err = netlink.LinkByName(MASTER_NAME)
@ -224,11 +224,11 @@ var _ = Describe("macvlan Operations", func() {
err = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = MASTER_NAME_INCONTAINER
// Add master
err = netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: MASTER_NAME_INCONTAINER,
},
LinkAttrs: linkAttrs,
})
Expect(err).NotTo(HaveOccurred())
_, err = netlink.LinkByName(MASTER_NAME_INCONTAINER)
@ -322,6 +322,13 @@ var _ = Describe("macvlan Operations", func() {
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
if testutils.SpecVersionHasSTATUS(ver) {
err := testutils.CmdStatus(func() error {
return cmdStatus(args)
})
Expect(err).NotTo(HaveOccurred())
}
result, _, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
@ -434,6 +441,13 @@ var _ = Describe("macvlan Operations", func() {
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
if testutils.SpecVersionHasSTATUS(ver) {
err := testutils.CmdStatus(func() error {
return cmdStatus(args)
})
Expect(err).NotTo(HaveOccurred())
}
result, _, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
@ -520,6 +534,13 @@ var _ = Describe("macvlan Operations", func() {
defer GinkgoRecover()
var err error
if testutils.SpecVersionHasSTATUS(ver) {
err := testutils.CmdStatus(func() error {
return cmdStatus(args)
})
Expect(err).NotTo(HaveOccurred())
}
result, _, err = testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
@ -660,6 +681,13 @@ var _ = Describe("macvlan Operations", func() {
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
if testutils.SpecVersionHasSTATUS(ver) {
err := testutils.CmdStatus(func() error {
return cmdStatus(args)
})
Expect(err).NotTo(HaveOccurred())
}
result, _, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})

View File

@ -31,7 +31,6 @@ import (
"github.com/containernetworking/plugins/pkg/ip"
"github.com/containernetworking/plugins/pkg/ipam"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/utils"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
@ -44,8 +43,9 @@ func init() {
type NetConf struct {
types.NetConf
IPMasq bool `json:"ipMasq"`
MTU int `json:"mtu"`
IPMasq bool `json:"ipMasq"`
IPMasqBackend *string `json:"ipMasqBackend,omitempty"`
MTU int `json:"mtu"`
}
func setupContainerVeth(netns ns.NetNS, ifName string, mtu int, pr *current.Result) (*current.Interface, *current.Interface, error) {
@ -229,12 +229,12 @@ func cmdAdd(args *skel.CmdArgs) error {
}
if conf.IPMasq {
chain := utils.FormatChainName(conf.Name, args.ContainerID)
comment := utils.FormatComment(conf.Name, args.ContainerID)
ipns := []*net.IPNet{}
for _, ipc := range result.IPs {
if err = ip.SetupIPMasq(&ipc.Address, chain, comment); err != nil {
return err
}
ipns = append(ipns, &ipc.Address)
}
if err = ip.SetupIPMasqForNetworks(conf.IPMasqBackend, ipns, conf.Name, args.IfName, args.ContainerID); err != nil {
return err
}
}
@ -293,10 +293,8 @@ func cmdDel(args *skel.CmdArgs) error {
}
if len(ipnets) != 0 && conf.IPMasq {
chain := utils.FormatChainName(conf.Name, args.ContainerID)
comment := utils.FormatComment(conf.Name, args.ContainerID)
for _, ipn := range ipnets {
err = ip.TeardownIPMasq(ipn, chain, comment)
if err := ip.TeardownIPMasqForNetworks(ipnets, conf.Name, args.IfName, args.ContainerID); err != nil {
return err
}
}
@ -304,7 +302,13 @@ func cmdDel(args *skel.CmdArgs) error {
}
func main() {
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("ptp"))
skel.PluginMainFuncs(skel.CNIFuncs{
Add: cmdAdd,
Check: cmdCheck,
Del: cmdDel,
Status: cmdStatus,
/* FIXME GC */
}, version.All, bv.BuildString("ptp"))
}
func cmdCheck(args *skel.CmdArgs) error {
@ -407,3 +411,16 @@ func validateCniContainerInterface(intf current.Interface) error {
return nil
}
func cmdStatus(args *skel.CmdArgs) error {
conf := NetConf{}
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
return fmt.Errorf("failed to load netconf: %w", err)
}
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
return err
}
return nil
}

View File

@ -39,6 +39,7 @@ type Net struct {
CNIVersion string `json:"cniVersion"`
Type string `json:"type,omitempty"`
IPMasq bool `json:"ipMasq"`
IPMasqBackend *string `json:"ipMasqBackend,omitempty"`
MTU int `json:"mtu"`
IPAM *allocator.IPAMConfig `json:"ipam"`
DNS types.DNS `json:"dns"`
@ -104,7 +105,7 @@ type (
func newTesterByVersion(version string) tester {
switch {
case strings.HasPrefix(version, "1.0."):
case strings.HasPrefix(version, "1."):
return &testerV10x{}
case strings.HasPrefix(version, "0.4."):
return &testerV04x{}
@ -249,6 +250,14 @@ var _ = Describe("ptp Operations", func() {
defer GinkgoRecover()
var err error
if testutils.SpecVersionHasSTATUS(cniVersion) {
By("Doing a cni STATUS")
err = testutils.CmdStatus(func() error {
return cmdStatus(args)
})
Expect(err).NotTo(HaveOccurred())
}
result, _, err = testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
@ -368,6 +377,62 @@ var _ = Describe("ptp Operations", func() {
doTest(conf, ver, 1, dnsConf, targetNS)
})
It(fmt.Sprintf("[%s] configures and deconfigures a ptp link when specifying ipMasqBackend: iptables", ver), func() {
dnsConf := types.DNS{
Nameservers: []string{"10.1.2.123"},
Domain: "some.domain.test",
Search: []string{"search.test"},
Options: []string{"option1:foo"},
}
dnsConfBytes, err := json.Marshal(dnsConf)
Expect(err).NotTo(HaveOccurred())
conf := fmt.Sprintf(`{
"cniVersion": "%s",
"name": "mynet",
"type": "ptp",
"ipMasq": true,
"ipMasqBackend": "iptables",
"mtu": 5000,
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24",
"dataDir": "%s"
},
"dns": %s
}`, ver, dataDir, string(dnsConfBytes))
doTest(conf, ver, 1, dnsConf, targetNS)
})
It(fmt.Sprintf("[%s] configures and deconfigures a ptp link when specifying ipMasqBackend: nftables", ver), func() {
dnsConf := types.DNS{
Nameservers: []string{"10.1.2.123"},
Domain: "some.domain.test",
Search: []string{"search.test"},
Options: []string{"option1:foo"},
}
dnsConfBytes, err := json.Marshal(dnsConf)
Expect(err).NotTo(HaveOccurred())
conf := fmt.Sprintf(`{
"cniVersion": "%s",
"name": "mynet",
"type": "ptp",
"ipMasq": true,
"ipMasqBackend": "nftables",
"mtu": 5000,
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24",
"dataDir": "%s"
},
"dns": %s
}`, ver, dataDir, string(dnsConfBytes))
doTest(conf, ver, 1, dnsConf, targetNS)
})
It(fmt.Sprintf("[%s] configures and deconfigures a dual-stack ptp link + routes with ADD/DEL", ver), func() {
conf := fmt.Sprintf(`{
"cniVersion": "%s",

View File

@ -47,6 +47,7 @@ type NetConf struct {
Owner *uint32 `json:"owner,omitempty"`
Group *uint32 `json:"group,omitempty"`
SelinuxContext string `json:"selinuxContext,omitempty"`
Bridge string `json:"bridge,omitempty"`
Args *struct{} `json:"args,omitempty"`
RuntimeConfig struct {
Mac string `json:"mac,omitempty"`
@ -136,10 +137,9 @@ func createTapWithIptool(tmpName string, mtu int, multiqueue bool, mac string, o
}
func createLinkWithNetlink(tmpName string, mtu int, nsFd int, multiqueue bool, mac string, owner *uint32, group *uint32) error {
linkAttrs := netlink.LinkAttrs{
Name: tmpName,
Namespace: netlink.NsFd(nsFd),
}
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = tmpName
linkAttrs.Namespace = netlink.NsFd(nsFd)
if mtu != 0 {
linkAttrs.MTU = mtu
}
@ -216,6 +216,18 @@ func createTap(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interface
return fmt.Errorf("failed to refetch tap %q: %v", ifName, err)
}
if conf.Bridge != "" {
bridge, err := netlink.LinkByName(conf.Bridge)
if err != nil {
return fmt.Errorf("failed to get bridge %s: %v", conf.Bridge, err)
}
tapDev := link
if err := netlink.LinkSetMaster(tapDev, bridge); err != nil {
return fmt.Errorf("failed to set tap %s as a port of bridge %s: %v", tap.Name, conf.Bridge, err)
}
}
err = netlink.LinkSetUp(link)
if err != nil {
return fmt.Errorf("failed to set tap interface up: %v", err)
@ -358,7 +370,6 @@ func cmdDel(args *skel.CmdArgs) error {
}
return nil
})
if err != nil {
// if NetNs is passed down by the Cloud Orchestration Engine, or if it called multiple times
// so don't return an error if the device is already removed.
@ -374,7 +385,13 @@ func cmdDel(args *skel.CmdArgs) error {
}
func main() {
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("tap"))
skel.PluginMainFuncs(skel.CNIFuncs{
Add: cmdAdd,
Check: cmdCheck,
Del: cmdDel,
Status: cmdStatus,
/* FIXME GC */
}, version.All, bv.BuildString("tap"))
}
func cmdCheck(args *skel.CmdArgs) error {
@ -443,3 +460,18 @@ func cmdCheck(args *skel.CmdArgs) error {
return nil
})
}
func cmdStatus(args *skel.CmdArgs) error {
conf := NetConf{}
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
return fmt.Errorf("failed to load netconf: %w", err)
}
if conf.IPAM.Type != "" {
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
return err
}
}
return nil
}

View File

@ -108,7 +108,7 @@ type (
func newTesterByVersion(version string) tester {
switch {
case strings.HasPrefix(version, "1.0."):
case strings.HasPrefix(version, "1."):
return &testerV10x{}
case strings.HasPrefix(version, "0.4."):
return &testerV04x{}
@ -223,6 +223,13 @@ var _ = Describe("Add, check, remove tap plugin", func() {
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
if testutils.SpecVersionHasSTATUS(ver) {
err := testutils.CmdStatus(func() error {
return cmdStatus(args)
})
Expect(err).NotTo(HaveOccurred())
}
result, _, err = testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
@ -314,5 +321,127 @@ var _ = Describe("Add, check, remove tap plugin", func() {
Expect(err).NotTo(HaveOccurred())
})
It(fmt.Sprintf("[%s] add, check and remove a tap device as a bridge port", ver), func() {
const bridgeName = "br1"
conf := fmt.Sprintf(`{
"cniVersion": "%s",
"name": "tapTest",
"type": "tap",
"owner": 0,
"group": 0,
"bridge": %q,
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24",
"dataDir": "%s"
}
}`, ver, bridgeName, dataDir)
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNS.Path(),
IfName: IFNAME,
StdinData: []byte(conf),
}
t := newTesterByVersion(ver)
var bridge netlink.Link
var result types.Result
var macAddress string
var err error
Expect(
targetNS.Do(func(ns.NetNS) error {
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = bridgeName
if err := netlink.LinkAdd(&netlink.Bridge{
LinkAttrs: linkAttrs,
}); err != nil {
return err
}
bridge, err = netlink.LinkByName(bridgeName)
if err != nil {
return err
}
return nil
}),
).To(Succeed())
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
if testutils.SpecVersionHasSTATUS(ver) {
err := testutils.CmdStatus(func() error {
return cmdStatus(args)
})
Expect(err).NotTo(HaveOccurred())
}
result, _, err = testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
macAddress = t.verifyResult(result, IFNAME)
return nil
})
Expect(err).NotTo(HaveOccurred())
By("Make sure the tap link exists in the target namespace")
err = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal(IFNAME))
Expect(link.Type()).To(Equal(TYPETAP))
Expect(link.Attrs().MasterIndex).To(Equal(bridge.Attrs().Index))
if macAddress != "" {
hwaddr, err := net.ParseMAC(macAddress)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(hwaddr))
}
addrs, err := netlink.AddrList(link, syscall.AF_INET)
Expect(err).NotTo(HaveOccurred())
Expect(addrs).To(HaveLen(1))
return nil
})
Expect(err).NotTo(HaveOccurred())
By("Running cmdDel")
args.StdinData = []byte(conf)
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
err = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(IFNAME)
Expect(err).To(HaveOccurred())
Expect(link).To(BeNil())
return nil
})
Expect(err).NotTo(HaveOccurred())
By("Running cmdDel more than once without error")
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
}
})

View File

@ -119,14 +119,15 @@ func createVlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interfac
return nil, err
}
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.MTU = conf.MTU
linkAttrs.Name = tmpName
linkAttrs.ParentIndex = m.Attrs().Index
linkAttrs.Namespace = netlink.NsFd(int(netns.Fd()))
v := &netlink.Vlan{
LinkAttrs: netlink.LinkAttrs{
MTU: conf.MTU,
Name: tmpName,
ParentIndex: m.Attrs().Index,
Namespace: netlink.NsFd(int(netns.Fd())),
},
VlanId: conf.VlanID,
LinkAttrs: linkAttrs,
VlanId: conf.VlanID,
}
if conf.LinkContNs {
@ -244,7 +245,6 @@ func cmdDel(args *skel.CmdArgs) error {
}
return err
})
if err != nil {
// if NetNs is passed down by the Cloud Orchestration Engine, or if it called multiple times
// so don't return an error if the device is already removed.
@ -260,7 +260,13 @@ func cmdDel(args *skel.CmdArgs) error {
}
func main() {
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("vlan"))
skel.PluginMainFuncs(skel.CNIFuncs{
Add: cmdAdd,
Check: cmdCheck,
Del: cmdDel,
Status: cmdStatus,
/* FIXME GC */
}, version.All, bv.BuildString("vlan"))
}
func cmdCheck(args *skel.CmdArgs) error {
@ -393,3 +399,18 @@ func validateCniContainerInterface(intf current.Interface, vlanID int, mtu int)
return nil
}
func cmdStatus(args *skel.CmdArgs) error {
conf := NetConf{}
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
return fmt.Errorf("failed to load netconf: %w", err)
}
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
return err
}
// TODO: Check if master interface exists.
return nil
}

View File

@ -113,7 +113,7 @@ type (
func newTesterByVersion(version string) tester {
switch {
case strings.HasPrefix(version, "1.0."):
case strings.HasPrefix(version, "1."):
return &testerV10x{}
case strings.HasPrefix(version, "0.4."):
return &testerV04x{}
@ -187,11 +187,11 @@ var _ = Describe("vlan Operations", func() {
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = MASTER_NAME
// Add master
err = netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: MASTER_NAME,
},
LinkAttrs: linkAttrs,
})
Expect(err).NotTo(HaveOccurred())
m, err := netlink.LinkByName(MASTER_NAME)
@ -205,11 +205,11 @@ var _ = Describe("vlan Operations", func() {
err = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = MASTER_NAME_INCONTAINER
// Add master
err = netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: MASTER_NAME_INCONTAINER,
},
LinkAttrs: linkAttrs,
})
Expect(err).NotTo(HaveOccurred())
m, err := netlink.LinkByName(MASTER_NAME_INCONTAINER)
@ -354,6 +354,13 @@ var _ = Describe("vlan Operations", func() {
defer GinkgoRecover()
var err error
if testutils.SpecVersionHasSTATUS(ver) {
err := testutils.CmdStatus(func() error {
return cmdStatus(args)
})
Expect(err).NotTo(HaveOccurred())
}
result, _, err = testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
@ -499,7 +506,7 @@ var _ = Describe("vlan Operations", func() {
StdinData: []byte(fmt.Sprintf(confFmt, ver, masterInterface, 1600, isInContainer, dataDir)),
}
_ = originalNS.Do(func(netNS ns.NetNS) error {
_ = originalNS.Do(func(_ ns.NetNS) error {
defer GinkgoRecover()
_, _, err = testutils.CmdAddWithArgs(args, func() error {
@ -520,7 +527,7 @@ var _ = Describe("vlan Operations", func() {
StdinData: []byte(fmt.Sprintf(confFmt, ver, masterInterface, -100, isInContainer, dataDir)),
}
_ = originalNS.Do(func(netNS ns.NetNS) error {
_ = originalNS.Do(func(_ ns.NetNS) error {
defer GinkgoRecover()
_, _, err = testutils.CmdAddWithArgs(args, func() error {

View File

@ -215,5 +215,26 @@ func cmdCheck(_ *skel.CmdArgs) error {
}
func main() {
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("win-bridge"))
skel.PluginMainFuncs(skel.CNIFuncs{
Add: cmdAdd,
Check: cmdCheck,
Del: cmdDel,
Status: cmdStatus,
/* FIXME GC */
}, version.All, bv.BuildString("win-bridge"))
}
func cmdStatus(args *skel.CmdArgs) error {
conf := NetConf{}
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
return fmt.Errorf("failed to load netconf: %w", err)
}
if conf.IPAM.Type != "" {
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
return err
}
}
return nil
}

View File

@ -106,13 +106,13 @@ func cmdHcnAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
return nil, errors.Annotatef(err, "error while hcn.GetNetworkByName(%s)", networkName)
}
if hcnNetwork == nil {
return nil, fmt.Errorf("network %v is not found", networkName)
return nil, fmt.Errorf("network %v is not found", networkName)
}
if hnsNetwork == nil {
return nil, fmt.Errorf("network %v not found", networkName)
}
if !strings.EqualFold(string (hcnNetwork.Type), "Overlay") {
if !strings.EqualFold(string(hcnNetwork.Type), "Overlay") {
return nil, fmt.Errorf("network %v is of an unexpected type: %v", networkName, hcnNetwork.Type)
}
@ -131,7 +131,6 @@ func cmdHcnAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
n.ApplyOutboundNatPolicy(hnsNetwork.Subnets[0].AddressPrefix)
}
hcnEndpoint, err := hns.GenerateHcnEndpoint(epInfo, &n.NetConf)
if err != nil {
return nil, errors.Annotate(err, "error while generating HostComputeEndpoint")
}
@ -142,15 +141,14 @@ func cmdHcnAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
}
result, err := hns.ConstructHcnResult(hcnNetwork, hcnEndpoint)
if err != nil {
ipam.ExecDel(n.IPAM.Type, args.StdinData)
return nil, errors.Annotate(err, "error while constructing HostComputeEndpoint addition result")
}
return result, nil
}
func cmdHnsAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
success := false
@ -243,6 +241,7 @@ func cmdHnsAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
success = true
return result, nil
}
func cmdAdd(args *skel.CmdArgs) error {
n, cniVersion, err := loadNetConf(args.StdinData)
if err != nil {
@ -288,5 +287,24 @@ func cmdCheck(_ *skel.CmdArgs) error {
}
func main() {
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("win-overlay"))
skel.PluginMainFuncs(skel.CNIFuncs{
Add: cmdAdd,
Check: cmdCheck,
Del: cmdDel,
Status: cmdStatus,
/* FIXME GC */
}, version.All, bv.BuildString("win-overlay"))
}
func cmdStatus(args *skel.CmdArgs) error {
conf := NetConf{}
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
return fmt.Errorf("failed to load netconf: %w", err)
}
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
return err
}
return nil
}

View File

@ -165,7 +165,7 @@ var _ = Describe("bandwidth test", func() {
StdinData: []byte(conf),
}
Expect(hostNs.Do(func(netNS ns.NetNS) error {
Expect(hostNs.Do(func(_ ns.NetNS) error {
defer GinkgoRecover()
r, out, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
Expect(err).NotTo(HaveOccurred(), string(out))
@ -202,7 +202,7 @@ var _ = Describe("bandwidth test", func() {
return nil
})).To(Succeed())
Expect(hostNs.Do(func(n ns.NetNS) error {
Expect(hostNs.Do(func(_ ns.NetNS) error {
defer GinkgoRecover()
ifbLink, err := netlink.LinkByName(hostIfname)
@ -260,7 +260,7 @@ var _ = Describe("bandwidth test", func() {
StdinData: []byte(conf),
}
Expect(hostNs.Do(func(netNS ns.NetNS) error {
Expect(hostNs.Do(func(_ ns.NetNS) error {
defer GinkgoRecover()
_, out, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, ifbDeviceName, []byte(conf), func() error { return cmdAdd(args) })
@ -271,7 +271,7 @@ var _ = Describe("bandwidth test", func() {
return nil
})).To(Succeed())
Expect(hostNs.Do(func(n ns.NetNS) error {
Expect(hostNs.Do(func(_ ns.NetNS) error {
defer GinkgoRecover()
containerIfLink, err := netlink.LinkByName(hostIfname)
@ -327,7 +327,7 @@ var _ = Describe("bandwidth test", func() {
StdinData: []byte(conf),
}
Expect(hostNs.Do(func(netNS ns.NetNS) error {
Expect(hostNs.Do(func(_ ns.NetNS) error {
defer GinkgoRecover()
_, out, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, ifbDeviceName, []byte(conf), func() error { return cmdAdd(args) })
@ -338,7 +338,7 @@ var _ = Describe("bandwidth test", func() {
return nil
})).To(Succeed())
Expect(hostNs.Do(func(n ns.NetNS) error {
Expect(hostNs.Do(func(_ ns.NetNS) error {
defer GinkgoRecover()
containerIfLink, err := netlink.LinkByName(hostIfname)
@ -396,7 +396,7 @@ var _ = Describe("bandwidth test", func() {
StdinData: []byte(conf),
}
Expect(hostNs.Do(func(netNS ns.NetNS) error {
Expect(hostNs.Do(func(_ ns.NetNS) error {
defer GinkgoRecover()
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
@ -448,7 +448,7 @@ var _ = Describe("bandwidth test", func() {
StdinData: []byte(conf),
}
Expect(hostNs.Do(func(netNS ns.NetNS) error {
Expect(hostNs.Do(func(_ ns.NetNS) error {
defer GinkgoRecover()
r, out, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
Expect(err).NotTo(HaveOccurred(), string(out))
@ -485,7 +485,7 @@ var _ = Describe("bandwidth test", func() {
return nil
})).To(Succeed())
Expect(hostNs.Do(func(n ns.NetNS) error {
Expect(hostNs.Do(func(_ ns.NetNS) error {
defer GinkgoRecover()
ifbLink, err := netlink.LinkByName(hostIfname)
@ -551,7 +551,7 @@ var _ = Describe("bandwidth test", func() {
StdinData: []byte(conf),
}
Expect(hostNs.Do(func(netNS ns.NetNS) error {
Expect(hostNs.Do(func(_ ns.NetNS) error {
defer GinkgoRecover()
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
@ -601,7 +601,7 @@ var _ = Describe("bandwidth test", func() {
StdinData: []byte(conf),
}
Expect(hostNs.Do(func(netNS ns.NetNS) error {
Expect(hostNs.Do(func(_ ns.NetNS) error {
defer GinkgoRecover()
_, out, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
Expect(err).NotTo(HaveOccurred(), string(out))
@ -669,7 +669,7 @@ var _ = Describe("bandwidth test", func() {
StdinData: []byte(conf),
}
Expect(hostNs.Do(func(netNS ns.NetNS) error {
Expect(hostNs.Do(func(_ ns.NetNS) error {
defer GinkgoRecover()
r, out, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
Expect(err).NotTo(HaveOccurred(), string(out))
@ -706,7 +706,7 @@ var _ = Describe("bandwidth test", func() {
return nil
})).To(Succeed())
Expect(hostNs.Do(func(n ns.NetNS) error {
Expect(hostNs.Do(func(_ ns.NetNS) error {
defer GinkgoRecover()
ifbLink, err := netlink.LinkByName(hostIfname)
@ -768,7 +768,7 @@ var _ = Describe("bandwidth test", func() {
StdinData: []byte(conf),
}
Expect(hostNs.Do(func(netNS ns.NetNS) error {
Expect(hostNs.Do(func(_ ns.NetNS) error {
defer GinkgoRecover()
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
@ -801,7 +801,7 @@ var _ = Describe("bandwidth test", func() {
StdinData: []byte(conf),
}
Expect(hostNs.Do(func(netNS ns.NetNS) error {
Expect(hostNs.Do(func(_ ns.NetNS) error {
defer GinkgoRecover()
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
@ -853,7 +853,7 @@ var _ = Describe("bandwidth test", func() {
StdinData: []byte(conf),
}
Expect(hostNs.Do(func(netNS ns.NetNS) error {
Expect(hostNs.Do(func(_ ns.NetNS) error {
defer GinkgoRecover()
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })

View File

@ -106,13 +106,13 @@ func makeTCPClientInNS(netns string, address string, port int, numBytes int) {
}
func createVeth(hostNs ns.NetNS, hostVethIfName string, containerNs ns.NetNS, containerVethIfName string, hostIP []byte, containerIP []byte, hostIfaceMTU int) {
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = hostVethIfName
linkAttrs.Flags = net.FlagUp
linkAttrs.MTU = hostIfaceMTU
vethDeviceRequest := &netlink.Veth{
LinkAttrs: netlink.LinkAttrs{
Name: hostVethIfName,
Flags: net.FlagUp,
MTU: hostIfaceMTU,
},
PeerName: containerVethIfName,
LinkAttrs: linkAttrs,
PeerName: containerVethIfName,
}
err := hostNs.Do(func(_ ns.NetNS) error {
@ -193,12 +193,12 @@ func createVeth(hostNs ns.NetNS, hostVethIfName string, containerNs ns.NetNS, co
}
func createVethInOneNs(netNS ns.NetNS, vethName, peerName string) {
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = vethName
linkAttrs.Flags = net.FlagUp
vethDeviceRequest := &netlink.Veth{
LinkAttrs: netlink.LinkAttrs{
Name: vethName,
Flags: net.FlagUp,
},
PeerName: peerName,
LinkAttrs: linkAttrs,
PeerName: peerName,
}
err := netNS.Do(func(_ ns.NetNS) error {
@ -222,13 +222,13 @@ func createMacvlan(netNS ns.NetNS, master, macvlanName string) {
return fmt.Errorf("failed to lookup master %q: %v", master, err)
}
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.MTU = m.Attrs().MTU
linkAttrs.Name = macvlanName
linkAttrs.ParentIndex = m.Attrs().Index
macvlanDeviceRequest := &netlink.Macvlan{
LinkAttrs: netlink.LinkAttrs{
MTU: m.Attrs().MTU,
Name: macvlanName,
ParentIndex: m.Attrs().Index,
},
Mode: netlink.MACVLAN_MODE_BRIDGE,
LinkAttrs: linkAttrs,
Mode: netlink.MACVLAN_MODE_BRIDGE,
}
if err = netlink.LinkAdd(macvlanDeviceRequest); err != nil {

View File

@ -27,11 +27,14 @@ import (
const latencyInMillis = 25
func CreateIfb(ifbDeviceName string, mtu int) error {
// do not set TxQLen > 0 nor TxQLen == -1 until issues have been fixed with numrxqueues / numtxqueues across interfaces
// which needs to get set on IFB devices via upstream library: see hint https://github.com/containernetworking/plugins/pull/1097
err := netlink.LinkAdd(&netlink.Ifb{
LinkAttrs: netlink.LinkAttrs{
Name: ifbDeviceName,
Flags: net.FlagUp,
MTU: mtu,
Name: ifbDeviceName,
Flags: net.FlagUp,
MTU: mtu,
TxQLen: 0,
},
})
if err != nil {

View File

@ -240,7 +240,13 @@ func cmdDel(args *skel.CmdArgs) error {
}
func main() {
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.VersionsStartingFrom("0.3.0"), bv.BuildString("bandwidth"))
skel.PluginMainFuncs(skel.CNIFuncs{
Add: cmdAdd,
Check: cmdCheck,
Del: cmdDel,
/* FIXME GC */
/* FIXME Status */
}, version.VersionsStartingFrom("0.3.0"), bv.BuildString("bandwidth"))
}
func SafeQdiscList(link netlink.Link) ([]netlink.Qdisc, error) {

View File

@ -179,7 +179,13 @@ func cmdDel(args *skel.CmdArgs) error {
}
func main() {
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.VersionsStartingFrom("0.4.0"), bv.BuildString("firewall"))
skel.PluginMainFuncs(skel.CNIFuncs{
Add: cmdAdd,
Check: cmdCheck,
Del: cmdDel,
/* FIXME GC */
/* FIXME Status */
}, version.VersionsStartingFrom("0.4.0"), bv.BuildString("firewall"))
}
func cmdCheck(args *skel.CmdArgs) error {

View File

@ -86,7 +86,8 @@ func spawnSessionDbus(wg *sync.WaitGroup) (string, *exec.Cmd) {
bytes, err := bufio.NewReader(stdout).ReadString('\n')
Expect(err).NotTo(HaveOccurred())
busAddr := strings.TrimSpace(bytes)
Expect(strings.HasPrefix(busAddr, "unix:abstract")).To(BeTrue())
Expect(strings.HasPrefix(busAddr, "unix:abstract") ||
strings.HasPrefix(busAddr, "unix:path")).To(BeTrue())
var startWg sync.WaitGroup
wg.Add(1)

View File

@ -205,10 +205,10 @@ var _ = Describe("firewall plugin iptables backend", func() {
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = IFNAME
err = netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: IFNAME,
},
LinkAttrs: linkAttrs,
})
Expect(err).NotTo(HaveOccurred())
_, err = netlink.LinkByName(IFNAME)

View File

@ -67,13 +67,27 @@ func (c *chain) setup(ipt *iptables.IPTables) error {
// teardown idempotently deletes a chain. It will not error if the chain doesn't exist.
// It will first delete all references to this chain in the entryChains.
func (c *chain) teardown(ipt *iptables.IPTables) error {
// flush the chain
// This will succeed *and create the chain* if it does not exist.
// If the chain doesn't exist, the next checks will fail.
if err := utils.ClearChain(ipt, c.table, c.name); err != nil {
return err
// nothing to do if the custom chain doesn't exist to begin with
exists, err := ipt.ChainExists(c.table, c.name)
if err == nil && !exists {
return nil
}
// delete references created by setup()
for _, entryChain := range c.entryChains {
for _, rule := range c.entryRules {
r := []string{}
r = append(r, rule...)
r = append(r, "-j", c.name)
ipt.Delete(c.table, entryChain, r...)
}
}
// if chain deletion succeeds now, all references are gone
if err := ipt.ClearAndDeleteChain(c.table, c.name); err == nil {
return nil
}
// find references the hard way
for _, entryChain := range c.entryChains {
entryChainRules, err := ipt.List(c.table, entryChain)
if err != nil || len(entryChainRules) < 1 {
@ -98,12 +112,12 @@ func (c *chain) teardown(ipt *iptables.IPTables) error {
}
}
return utils.DeleteChain(ipt, c.table, c.name)
return ipt.ClearAndDeleteChain(c.table, c.name)
}
// check the chain.
func (c *chain) check(ipt *iptables.IPTables) error {
exists, err := utils.ChainExists(ipt, c.table, c.name)
exists, err := ipt.ChainExists(c.table, c.name)
if err != nil {
return err
}

View File

@ -37,9 +37,22 @@ import (
"github.com/containernetworking/cni/pkg/types"
current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/pkg/version"
"github.com/containernetworking/plugins/pkg/utils"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
type PortMapper interface {
forwardPorts(config *PortMapConf, containerNet net.IPNet) error
checkPorts(config *PortMapConf, containerNet net.IPNet) error
unforwardPorts(config *PortMapConf) error
}
// These are vars rather than consts so we can "&" them
var (
iptablesBackend = "iptables"
nftablesBackend = "nftables"
)
// PortMapEntry corresponds to a single entry in the port_mappings argument,
// see CONVENTIONS.md
type PortMapEntry struct {
@ -51,16 +64,23 @@ type PortMapEntry struct {
type PortMapConf struct {
types.NetConf
SNAT *bool `json:"snat,omitempty"`
ConditionsV4 *[]string `json:"conditionsV4"`
ConditionsV6 *[]string `json:"conditionsV6"`
MasqAll bool `json:"masqAll,omitempty"`
MarkMasqBit *int `json:"markMasqBit"`
ExternalSetMarkChain *string `json:"externalSetMarkChain"`
RuntimeConfig struct {
mapper PortMapper
// Generic config
Backend *string `json:"backend,omitempty"`
SNAT *bool `json:"snat,omitempty"`
ConditionsV4 *[]string `json:"conditionsV4"`
ConditionsV6 *[]string `json:"conditionsV6"`
MasqAll bool `json:"masqAll,omitempty"`
MarkMasqBit *int `json:"markMasqBit"`
RuntimeConfig struct {
PortMaps []PortMapEntry `json:"portMappings,omitempty"`
} `json:"runtimeConfig,omitempty"`
// iptables-backend-specific config
ExternalSetMarkChain *string `json:"externalSetMarkChain"`
// These are fields parsed out of the config or the environment;
// included here for convenience
ContainerID string `json:"-"`
@ -89,7 +109,7 @@ func cmdAdd(args *skel.CmdArgs) error {
netConf.ContainerID = args.ContainerID
if netConf.ContIPv4.IP != nil {
if err := forwardPorts(netConf, netConf.ContIPv4); err != nil {
if err := netConf.mapper.forwardPorts(netConf, netConf.ContIPv4); err != nil {
return err
}
// Delete conntrack entries for UDP to avoid conntrack blackholing traffic
@ -98,10 +118,21 @@ func cmdAdd(args *skel.CmdArgs) error {
if err := deletePortmapStaleConnections(netConf.RuntimeConfig.PortMaps, unix.AF_INET); err != nil {
log.Printf("failed to delete stale UDP conntrack entries for %s: %v", netConf.ContIPv4.IP, err)
}
if *netConf.SNAT {
// Set the route_localnet bit on the host interface, so that
// 127/8 can cross a routing boundary.
hostIfName := getRoutableHostIF(netConf.ContIPv4.IP)
if hostIfName != "" {
if err := enableLocalnetRouting(hostIfName); err != nil {
return fmt.Errorf("unable to enable route_localnet: %v", err)
}
}
}
}
if netConf.ContIPv6.IP != nil {
if err := forwardPorts(netConf, netConf.ContIPv6); err != nil {
if err := netConf.mapper.forwardPorts(netConf, netConf.ContIPv6); err != nil {
return err
}
// Delete conntrack entries for UDP to avoid conntrack blackholing traffic
@ -130,11 +161,17 @@ func cmdDel(args *skel.CmdArgs) error {
// We don't need to parse out whether or not we're using v6 or snat,
// deletion is idempotent
return unforwardPorts(netConf)
return netConf.mapper.unforwardPorts(netConf)
}
func main() {
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("portmap"))
skel.PluginMainFuncs(skel.CNIFuncs{
Add: cmdAdd,
Check: cmdCheck,
Del: cmdDel,
/* FIXME GC */
/* FIXME Status */
}, version.All, bv.BuildString("portmap"))
}
func cmdCheck(args *skel.CmdArgs) error {
@ -155,13 +192,13 @@ func cmdCheck(args *skel.CmdArgs) error {
conf.ContainerID = args.ContainerID
if conf.ContIPv4.IP != nil {
if err := checkPorts(conf, conf.ContIPv4); err != nil {
if err := conf.mapper.checkPorts(conf, conf.ContIPv4); err != nil {
return err
}
}
if conf.ContIPv6.IP != nil {
if err := checkPorts(conf, conf.ContIPv6); err != nil {
if err := conf.mapper.checkPorts(conf, conf.ContIPv6); err != nil {
return err
}
}
@ -191,6 +228,8 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, *current.Result, er
}
}
conf.mapper = &portMapperIPTables{}
if conf.SNAT == nil {
tvar := true
conf.SNAT = &tvar
@ -209,6 +248,21 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, *current.Result, er
return nil, nil, fmt.Errorf("MasqMarkBit must be between 0 and 31")
}
err := ensureBackend(&conf)
if err != nil {
return nil, nil, err
}
switch *conf.Backend {
case iptablesBackend:
conf.mapper = &portMapperIPTables{}
case nftablesBackend:
conf.mapper = &portMapperNFTables{}
default:
return nil, nil, fmt.Errorf("unrecognized backend %q", *conf.Backend)
}
// Reject invalid port numbers
for _, pm := range conf.RuntimeConfig.PortMaps {
if pm.ContainerPort <= 0 {
@ -248,3 +302,59 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, *current.Result, er
return &conf, result, nil
}
// ensureBackend validates and/or sets conf.Backend
func ensureBackend(conf *PortMapConf) error {
backendConfig := make(map[string][]string)
if conf.ExternalSetMarkChain != nil {
backendConfig[iptablesBackend] = append(backendConfig[iptablesBackend], "externalSetMarkChain")
}
if conditionsBackend := detectBackendOfConditions(conf.ConditionsV4); conditionsBackend != "" {
backendConfig[conditionsBackend] = append(backendConfig[conditionsBackend], "conditionsV4")
}
if conditionsBackend := detectBackendOfConditions(conf.ConditionsV6); conditionsBackend != "" {
backendConfig[conditionsBackend] = append(backendConfig[conditionsBackend], "conditionsV6")
}
// If backend wasn't requested explicitly, default to iptables, unless it is not
// available (and nftables is). FIXME: flip this default at some point.
if conf.Backend == nil {
if !utils.SupportsIPTables() && utils.SupportsNFTables() {
conf.Backend = &nftablesBackend
} else {
conf.Backend = &iptablesBackend
}
}
// Make sure we dont have config for the wrong backend
var wrongBackend string
if *conf.Backend == iptablesBackend {
wrongBackend = nftablesBackend
} else {
wrongBackend = iptablesBackend
}
if len(backendConfig[wrongBackend]) > 0 {
return fmt.Errorf("%s backend was requested but configuration contains %s-specific options %v", *conf.Backend, wrongBackend, backendConfig[wrongBackend])
}
// OK
return nil
}
// detectBackendOfConditions returns "iptables" if conditions contains iptables
// conditions, "nftables" if it contains nftables conditions, and "" if it is empty.
func detectBackendOfConditions(conditions *[]string) string {
if conditions == nil || len(*conditions) == 0 || (*conditions)[0] == "" {
return ""
}
// The first character of any iptables condition would either be an hyphen
// (e.g. "-d", "--sport", "-m") or an exclamation mark.
// No nftables condition would start that way. (An nftables condition might
// include a negative number, but not as the first token.)
if (*conditions)[0][0] == '-' || (*conditions)[0][0] == '!' {
return iptablesBackend
}
return nftablesBackend
}

View File

@ -25,7 +25,6 @@ import (
"github.com/vishvananda/netlink"
"github.com/containernetworking/plugins/pkg/utils"
"github.com/containernetworking/plugins/pkg/utils/sysctl"
)
// This creates the chains to be added to iptables. The basic structure is
@ -52,9 +51,11 @@ const (
OldTopLevelSNATChainName = "CNI-HOSTPORT-SNAT"
)
type portMapperIPTables struct{}
// forwardPorts establishes port forwarding to a given container IP.
// containerNet.IP can be either v4 or v6.
func forwardPorts(config *PortMapConf, containerNet net.IPNet) error {
func (*portMapperIPTables) forwardPorts(config *PortMapConf, containerNet net.IPNet) error {
isV6 := (containerNet.IP.To4() == nil)
var ipt *iptables.IPTables
@ -87,17 +88,6 @@ func forwardPorts(config *PortMapConf, containerNet net.IPNet) error {
return fmt.Errorf("unable to create chain %s: %v", setMarkChain.name, err)
}
}
if !isV6 {
// Set the route_localnet bit on the host interface, so that
// 127/8 can cross a routing boundary.
hostIfName := getRoutableHostIF(containerNet.IP)
if hostIfName != "" {
if err := enableLocalnetRouting(hostIfName); err != nil {
return fmt.Errorf("unable to enable route_localnet: %v", err)
}
}
}
}
// Generate the DNAT (actual port forwarding) rules
@ -117,7 +107,7 @@ func forwardPorts(config *PortMapConf, containerNet net.IPNet) error {
return nil
}
func checkPorts(config *PortMapConf, containerNet net.IPNet) error {
func (*portMapperIPTables) checkPorts(config *PortMapConf, containerNet net.IPNet) error {
isV6 := (containerNet.IP.To4() == nil)
dnatChain := genDnatChain(config.Name, config.ContainerID)
fillDnatRules(&dnatChain, config, containerNet)
@ -344,14 +334,6 @@ func genMarkMasqChain(markBit int) chain {
return ch
}
// enableLocalnetRouting tells the kernel not to treat 127/8 as a martian,
// so that connections with a source ip of 127/8 can cross a routing boundary.
func enableLocalnetRouting(ifName string) error {
routeLocalnetPath := "net/ipv4/conf/" + ifName + "/route_localnet"
_, err := sysctl.Sysctl(routeLocalnetPath, "1")
return err
}
// genOldSnatChain is no longer used, but used to be created. We'll try and
// tear it down in case the plugin version changed between ADD and DEL
func genOldSnatChain(netName, containerID string) chain {
@ -372,7 +354,7 @@ func genOldSnatChain(netName, containerID string) chain {
// don't know which protocols were used.
// So, we first check that iptables is "generally OK" by doing a check. If
// not, we ignore the error, unless neither v4 nor v6 are OK.
func unforwardPorts(config *PortMapConf) error {
func (*portMapperIPTables) unforwardPorts(config *PortMapConf) error {
dnatChain := genDnatChain(config.Name, config.ContainerID)
// Might be lying around from old versions

View File

@ -0,0 +1,252 @@
// 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 (iptables)", 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
Describe("Generating iptables 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,
}))
})
})
})
}
})

View File

@ -0,0 +1,340 @@
// Copyright 2023 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 (
"context"
"fmt"
"net"
"strconv"
"sigs.k8s.io/knftables"
)
const (
tableName = "cni_hostport"
hostIPHostPortsChain = "hostip_hostports"
hostPortsChain = "hostports"
masqueradingChain = "masquerading"
)
// The nftables portmap implementation is fairly similar to the iptables implementation:
// we add a rule for each mapping, with a comment containing a hash of the container ID,
// so that we can later reliably delete the rules we want. (This is important because in
// edge cases, it's possible the plugin might see "ADD container A with IP 192.168.1.3",
// followed by "ADD container B with IP 192.168.1.3" followed by "DEL container A with IP
// 192.168.1.3", and we need to make sure that the DEL causes us to delete the rule for
// container A, and not the rule for container B.) This iptables implementation actually
// uses a separate chain per container but there's not really any need for that...
//
// As with pkg/ip/ipmasq_nftables_linux.go, it would be more nftables-y to have a chain
// with a single rule doing a lookup against a map with an element per mapping, rather
// than having a chain with a rule per mapping. But there's no easy, non-racy way to say
// "delete the element 192.168.1.3 from the map, but only if it was added for container A,
// not if it was added for container B".
type portMapperNFTables struct {
ipv4 knftables.Interface
ipv6 knftables.Interface
}
// getPortMapNFT creates an nftables.Interface for port mapping for the IP family of ipn
func (pmNFT *portMapperNFTables) getPortMapNFT(ipv6 bool) (knftables.Interface, error) {
var err error
if ipv6 {
if pmNFT.ipv6 == nil {
pmNFT.ipv6, err = knftables.New(knftables.IPv6Family, tableName)
if err != nil {
return nil, err
}
}
return pmNFT.ipv6, nil
}
if pmNFT.ipv4 == nil {
pmNFT.ipv4, err = knftables.New(knftables.IPv4Family, tableName)
if err != nil {
return nil, err
}
}
return pmNFT.ipv4, err
}
// forwardPorts establishes port forwarding to a given container IP.
// containerNet.IP can be either v4 or v6.
func (pmNFT *portMapperNFTables) forwardPorts(config *PortMapConf, containerNet net.IPNet) error {
isV6 := (containerNet.IP.To4() == nil)
nft, err := pmNFT.getPortMapNFT(isV6)
if err != nil {
return err
}
var ipX string
var conditions []string
if isV6 {
ipX = "ip6"
if config.ConditionsV6 != nil {
conditions = *config.ConditionsV6
}
} else if !isV6 {
ipX = "ip"
if config.ConditionsV4 != nil {
conditions = *config.ConditionsV4
}
}
tx := nft.NewTransaction()
// Ensure basic rule structure
tx.Add(&knftables.Table{
Comment: knftables.PtrTo("CNI portmap plugin"),
})
tx.Add(&knftables.Chain{
Name: "hostports",
})
tx.Add(&knftables.Chain{
Name: "hostip_hostports",
})
tx.Add(&knftables.Chain{
Name: "prerouting",
Type: knftables.PtrTo(knftables.NATType),
Hook: knftables.PtrTo(knftables.PreroutingHook),
Priority: knftables.PtrTo(knftables.DNATPriority),
})
tx.Flush(&knftables.Chain{
Name: "prerouting",
})
tx.Add(&knftables.Rule{
Chain: "prerouting",
Rule: knftables.Concat(
conditions,
"jump", hostIPHostPortsChain,
),
})
tx.Add(&knftables.Rule{
Chain: "prerouting",
Rule: knftables.Concat(
conditions,
"jump", hostPortsChain,
),
})
tx.Add(&knftables.Chain{
Name: "output",
Type: knftables.PtrTo(knftables.NATType),
Hook: knftables.PtrTo(knftables.OutputHook),
Priority: knftables.PtrTo(knftables.DNATPriority),
})
tx.Flush(&knftables.Chain{
Name: "output",
})
tx.Add(&knftables.Rule{
Chain: "output",
Rule: knftables.Concat(
conditions,
"jump", hostIPHostPortsChain,
),
})
tx.Add(&knftables.Rule{
Chain: "output",
Rule: knftables.Concat(
conditions,
"fib daddr type local",
"jump", hostPortsChain,
),
})
if *config.SNAT {
tx.Add(&knftables.Chain{
Name: masqueradingChain,
Type: knftables.PtrTo(knftables.NATType),
Hook: knftables.PtrTo(knftables.PostroutingHook),
Priority: knftables.PtrTo(knftables.SNATPriority),
})
}
// Set up this container
for _, e := range config.RuntimeConfig.PortMaps {
useHostIP := false
if e.HostIP != "" {
hostIP := net.ParseIP(e.HostIP)
isHostV6 := (hostIP.To4() == nil)
// Ignore wrong-IP-family HostIPs
if isV6 != isHostV6 {
continue
}
// Unspecified addresses cannot be used as destination
useHostIP = !hostIP.IsUnspecified()
}
if useHostIP {
tx.Add(&knftables.Rule{
Chain: hostIPHostPortsChain,
Rule: knftables.Concat(
ipX, "daddr", e.HostIP,
e.Protocol, "dport", e.HostPort,
"dnat to", net.JoinHostPort(containerNet.IP.String(), strconv.Itoa(e.ContainerPort)),
),
Comment: &config.ContainerID,
})
} else {
tx.Add(&knftables.Rule{
Chain: hostPortsChain,
Rule: knftables.Concat(
e.Protocol, "dport", e.HostPort,
"dnat to", net.JoinHostPort(containerNet.IP.String(), strconv.Itoa(e.ContainerPort)),
),
Comment: &config.ContainerID,
})
}
}
if *config.SNAT {
// Add mark-to-masquerade rules for hairpin and localhost
// In theory we should validate that the original dst IP and port are as
// expected, but *any* traffic matching one of these patterns would need
// to be masqueraded to be able to work correctly anyway.
tx.Add(&knftables.Rule{
Chain: masqueradingChain,
Rule: knftables.Concat(
ipX, "saddr", containerNet.IP,
ipX, "daddr", containerNet.IP,
"masquerade",
),
Comment: &config.ContainerID,
})
if !isV6 {
tx.Add(&knftables.Rule{
Chain: masqueradingChain,
Rule: knftables.Concat(
ipX, "saddr 127.0.0.1",
ipX, "daddr", containerNet.IP,
"masquerade",
),
Comment: &config.ContainerID,
})
}
}
err = nft.Run(context.TODO(), tx)
if err != nil {
return fmt.Errorf("unable to set up nftables rules for port mappings: %v", err)
}
return nil
}
func (pmNFT *portMapperNFTables) checkPorts(config *PortMapConf, containerNet net.IPNet) error {
isV6 := (containerNet.IP.To4() == nil)
var hostPorts, hostIPHostPorts, masqueradings int
for _, e := range config.RuntimeConfig.PortMaps {
if e.HostIP != "" {
hostIPHostPorts++
} else {
hostPorts++
}
}
if *config.SNAT {
masqueradings = len(config.RuntimeConfig.PortMaps)
if isV6 {
masqueradings *= 2
}
}
nft, err := pmNFT.getPortMapNFT(isV6)
if err != nil {
return err
}
if hostPorts > 0 {
err := checkPortsAgainstRules(nft, hostPortsChain, config.ContainerID, hostPorts)
if err != nil {
return err
}
}
if hostIPHostPorts > 0 {
err := checkPortsAgainstRules(nft, hostIPHostPortsChain, config.ContainerID, hostIPHostPorts)
if err != nil {
return err
}
}
if masqueradings > 0 {
err := checkPortsAgainstRules(nft, masqueradingChain, config.ContainerID, masqueradings)
if err != nil {
return err
}
}
return nil
}
func checkPortsAgainstRules(nft knftables.Interface, chain, comment string, nPorts int) error {
rules, err := nft.ListRules(context.TODO(), chain)
if err != nil {
return err
}
found := 0
for _, r := range rules {
if r.Comment != nil && *r.Comment == comment {
found++
}
}
if found < nPorts {
return fmt.Errorf("missing hostport rules in %q chain", chain)
}
return nil
}
// unforwardPorts deletes any nftables rules created by this plugin.
// It should be idempotent - it will not error if the chain does not exist.
func (pmNFT *portMapperNFTables) unforwardPorts(config *PortMapConf) error {
// Always clear both IPv4 and IPv6, just to be sure
for _, family := range []knftables.Family{knftables.IPv4Family, knftables.IPv6Family} {
nft, err := pmNFT.getPortMapNFT(family == knftables.IPv6Family)
if err != nil {
continue
}
tx := nft.NewTransaction()
for _, chain := range []string{hostPortsChain, hostIPHostPortsChain, masqueradingChain} {
rules, err := nft.ListRules(context.TODO(), chain)
if err != nil {
if knftables.IsNotFound(err) {
continue
}
return fmt.Errorf("could not list rules in table %s: %w", tableName, err)
}
for _, r := range rules {
if r.Comment != nil && *r.Comment == config.ContainerID {
tx.Delete(r)
}
}
}
err = nft.Run(context.TODO(), tx)
if err != nil {
return fmt.Errorf("error deleting nftables rules: %w", err)
}
}
return nil
}

View File

@ -0,0 +1,134 @@
// Copyright 2023 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"
"strings"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"sigs.k8s.io/knftables"
"github.com/containernetworking/cni/pkg/types"
)
var _ = Describe("portmapping configuration (nftables)", func() {
containerID := "icee6giejonei6so"
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("nftables rules", func() {
var pmNFT *portMapperNFTables
var ipv4Fake, ipv6Fake *knftables.Fake
BeforeEach(func() {
ipv4Fake = knftables.NewFake(knftables.IPv4Family, tableName)
ipv6Fake = knftables.NewFake(knftables.IPv6Family, tableName)
pmNFT = &portMapperNFTables{
ipv4: ipv4Fake,
ipv6: ipv6Fake,
}
})
It(fmt.Sprintf("[%s] generates correct rules on ADD", ver), func() {
configBytes := []byte(fmt.Sprintf(`{
"name": "test",
"type": "portmap",
"cniVersion": "%s",
"backend": "nftables",
"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
containerNet, err := types.ParseCIDR("10.0.0.2/24")
Expect(err).NotTo(HaveOccurred())
err = pmNFT.forwardPorts(conf, *containerNet)
Expect(err).NotTo(HaveOccurred())
expectedRules := strings.TrimSpace(`
add table ip cni_hostport { comment "CNI portmap plugin" ; }
add chain ip cni_hostport hostip_hostports
add chain ip cni_hostport hostports
add chain ip cni_hostport masquerading { type nat hook postrouting priority 100 ; }
add chain ip cni_hostport output { type nat hook output priority -100 ; }
add chain ip cni_hostport prerouting { type nat hook prerouting priority -100 ; }
add rule ip cni_hostport hostip_hostports ip daddr 192.168.0.2 tcp dport 8083 dnat to 10.0.0.2:83 comment "icee6giejonei6so"
add rule ip cni_hostport hostports tcp dport 8080 dnat to 10.0.0.2:80 comment "icee6giejonei6so"
add rule ip cni_hostport hostports tcp dport 8081 dnat to 10.0.0.2:80 comment "icee6giejonei6so"
add rule ip cni_hostport hostports udp dport 8080 dnat to 10.0.0.2:81 comment "icee6giejonei6so"
add rule ip cni_hostport hostports udp dport 8082 dnat to 10.0.0.2:82 comment "icee6giejonei6so"
add rule ip cni_hostport hostports tcp dport 8084 dnat to 10.0.0.2:84 comment "icee6giejonei6so"
add rule ip cni_hostport masquerading ip saddr 10.0.0.2 ip daddr 10.0.0.2 masquerade comment "icee6giejonei6so"
add rule ip cni_hostport masquerading ip saddr 127.0.0.1 ip daddr 10.0.0.2 masquerade comment "icee6giejonei6so"
add rule ip cni_hostport output a b jump hostip_hostports
add rule ip cni_hostport output a b fib daddr type local jump hostports
add rule ip cni_hostport prerouting a b jump hostip_hostports
add rule ip cni_hostport prerouting a b jump hostports
`)
actualRules := strings.TrimSpace(ipv4Fake.Dump())
Expect(actualRules).To(Equal(expectedRules))
// Disable snat, generate IPv6 rules
*conf.SNAT = false
containerNet, err = types.ParseCIDR("2001:db8::2/64")
Expect(err).NotTo(HaveOccurred())
err = pmNFT.forwardPorts(conf, *containerNet)
Expect(err).NotTo(HaveOccurred())
expectedRules = strings.TrimSpace(`
add table ip6 cni_hostport { comment "CNI portmap plugin" ; }
add chain ip6 cni_hostport hostip_hostports
add chain ip6 cni_hostport hostports
add chain ip6 cni_hostport output { type nat hook output priority -100 ; }
add chain ip6 cni_hostport prerouting { type nat hook prerouting priority -100 ; }
add rule ip6 cni_hostport hostip_hostports ip6 daddr 2001:db8:a::1 tcp dport 8085 dnat to [2001:db8::2]:85 comment "icee6giejonei6so"
add rule ip6 cni_hostport hostports tcp dport 8080 dnat to [2001:db8::2]:80 comment "icee6giejonei6so"
add rule ip6 cni_hostport hostports tcp dport 8081 dnat to [2001:db8::2]:80 comment "icee6giejonei6so"
add rule ip6 cni_hostport hostports udp dport 8080 dnat to [2001:db8::2]:81 comment "icee6giejonei6so"
add rule ip6 cni_hostport hostports udp dport 8082 dnat to [2001:db8::2]:82 comment "icee6giejonei6so"
add rule ip6 cni_hostport hostports tcp dport 8086 dnat to [2001:db8::2]:86 comment "icee6giejonei6so"
add rule ip6 cni_hostport output c d jump hostip_hostports
add rule ip6 cni_hostport output c d fib daddr type local jump hostports
add rule ip6 cni_hostport prerouting c d jump hostip_hostports
add rule ip6 cni_hostport prerouting c d jump hostports
`)
actualRules = strings.TrimSpace(ipv6Fake.Dump())
Expect(actualRules).To(Equal(expectedRules))
})
})
}
})

View File

@ -24,9 +24,6 @@ import (
)
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.
@ -38,6 +35,7 @@ var _ = Describe("portmapping configuration", func() {
"name": "test",
"type": "portmap",
"cniVersion": "%s",
"backend": "iptables",
"runtimeConfig": {
"portMappings": [
{ "hostPort": 8080, "containerPort": 80, "protocol": "tcp"},
@ -45,8 +43,8 @@ var _ = Describe("portmapping configuration", func() {
]
},
"snat": false,
"conditionsV4": ["a", "b"],
"conditionsV6": ["c", "d"],
"conditionsV4": ["-s", "1.2.3.4"],
"conditionsV6": ["!", "-s", "12::34"],
"prevResult": {
"interfaces": [
{"name": "host"},
@ -77,8 +75,8 @@ var _ = Describe("portmapping configuration", func() {
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"}))
Expect(c.ConditionsV4).To(Equal(&[]string{"-s", "1.2.3.4"}))
Expect(c.ConditionsV6).To(Equal(&[]string{"!", "-s", "12::34"}))
fvar := false
Expect(c.SNAT).To(Equal(&fvar))
Expect(c.Name).To(Equal("test"))
@ -97,15 +95,16 @@ var _ = Describe("portmapping configuration", func() {
"name": "test",
"type": "portmap",
"cniVersion": "%s",
"backend": "iptables",
"snat": false,
"conditionsV4": ["a", "b"],
"conditionsV6": ["c", "d"]
"conditionsV4": ["-s", "1.2.3.4"],
"conditionsV6": ["-s", "12::34"]
}`, 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"}))
Expect(c.ConditionsV4).To(Equal(&[]string{"-s", "1.2.3.4"}))
Expect(c.ConditionsV6).To(Equal(&[]string{"-s", "12::34"}))
fvar := false
Expect(c.SNAT).To(Equal(&fvar))
Expect(c.Name).To(Equal("test"))
@ -116,9 +115,10 @@ var _ = Describe("portmapping configuration", func() {
"name": "test",
"type": "portmap",
"cniVersion": "%s",
"backend": "iptables",
"snat": false,
"conditionsV4": ["a", "b"],
"conditionsV6": ["c", "d"],
"conditionsV4": ["-s", "1.2.3.4"],
"conditionsV6": ["-s", "12::34"],
"runtimeConfig": {
"portMappings": [
{ "hostPort": 0, "containerPort": 80, "protocol": "tcp"}
@ -129,6 +129,82 @@ var _ = Describe("portmapping configuration", func() {
Expect(err).To(MatchError("Invalid host port number: 0"))
})
It(fmt.Sprintf("[%s] defaults to iptables when backend is not specified", ver), func() {
// "defaults to iptables" is only true if iptables is installed
// (or if neither iptables nor nftables is installed), but the
// other unit tests would fail if iptables wasn't installed, so
// we know it must be.
configBytes := []byte(fmt.Sprintf(`{
"name": "test",
"type": "portmap",
"cniVersion": "%s"
}`, ver))
c, _, err := parseConfig(configBytes, "container")
Expect(err).NotTo(HaveOccurred())
Expect(c.CNIVersion).To(Equal(ver))
Expect(c.Backend).To(Equal(&iptablesBackend))
Expect(c.Name).To(Equal("test"))
})
It(fmt.Sprintf("[%s] uses nftables if requested", ver), func() {
configBytes := []byte(fmt.Sprintf(`{
"name": "test",
"type": "portmap",
"cniVersion": "%s",
"backend": "nftables"
}`, ver))
c, _, err := parseConfig(configBytes, "container")
Expect(err).NotTo(HaveOccurred())
Expect(c.CNIVersion).To(Equal(ver))
Expect(c.Backend).To(Equal(&nftablesBackend))
Expect(c.Name).To(Equal("test"))
})
It(fmt.Sprintf("[%s] allows nftables conditions if nftables is requested", ver), func() {
configBytes := []byte(fmt.Sprintf(`{
"name": "test",
"type": "portmap",
"cniVersion": "%s",
"backend": "nftables",
"conditionsV4": ["ip", "saddr", "1.2.3.4"],
"conditionsV6": ["ip6", "saddr", "12::34"]
}`, ver))
c, _, err := parseConfig(configBytes, "container")
Expect(err).NotTo(HaveOccurred())
Expect(c.CNIVersion).To(Equal(ver))
Expect(c.Backend).To(Equal(&nftablesBackend))
Expect(c.ConditionsV4).To(Equal(&[]string{"ip", "saddr", "1.2.3.4"}))
Expect(c.ConditionsV6).To(Equal(&[]string{"ip6", "saddr", "12::34"}))
Expect(c.Name).To(Equal("test"))
})
It(fmt.Sprintf("[%s] rejects nftables options with 'backend: iptables'", ver), func() {
configBytes := []byte(fmt.Sprintf(`{
"name": "test",
"type": "portmap",
"cniVersion": "%s",
"backend": "iptables",
"conditionsV4": ["ip", "saddr", "1.2.3.4"],
"conditionsV6": ["ip6", "saddr", "12::34"]
}`, ver))
_, _, err := parseConfig(configBytes, "container")
Expect(err).To(MatchError("iptables backend was requested but configuration contains nftables-specific options [conditionsV4 conditionsV6]"))
})
It(fmt.Sprintf("[%s] rejects iptables options with 'backend: nftables'", ver), func() {
configBytes := []byte(fmt.Sprintf(`{
"name": "test",
"type": "portmap",
"cniVersion": "%s",
"backend": "nftables",
"externalSetMarkChain": "KUBE-MARK-MASQ",
"conditionsV4": ["-s", "1.2.3.4"],
"conditionsV6": ["-s", "12::34"]
}`, ver))
_, _, err := parseConfig(configBytes, "container")
Expect(err).To(MatchError("nftables backend was requested but configuration contains iptables-specific options [externalSetMarkChain conditionsV4 conditionsV6]"))
})
It(fmt.Sprintf("[%s] does not fail on missing prevResult interface index", ver), func() {
configBytes := []byte(fmt.Sprintf(`{
"name": "test",
@ -139,7 +215,7 @@ var _ = Describe("portmapping configuration", func() {
{ "hostPort": 8080, "containerPort": 80, "protocol": "tcp"}
]
},
"conditionsV4": ["a", "b"],
"conditionsV4": ["-s", "1.2.3.4"],
"prevResult": {
"interfaces": [
{"name": "host"}
@ -157,222 +233,5 @@ var _ = Describe("portmapping configuration", func() {
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,
}))
})
})
})
}
})

View File

@ -21,6 +21,8 @@ import (
"strings"
"github.com/vishvananda/netlink"
"github.com/containernetworking/plugins/pkg/utils/sysctl"
)
// fmtIpPort correctly formats ip:port literals for iptables and ip6tables -
@ -52,6 +54,14 @@ func getRoutableHostIF(containerIP net.IP) string {
return ""
}
// enableLocalnetRouting tells the kernel not to treat 127/8 as a martian,
// so that connections with a source ip of 127/8 can cross a routing boundary.
func enableLocalnetRouting(ifName string) error {
routeLocalnetPath := "net/ipv4/conf/" + ifName + "/route_localnet"
_, err := sysctl.Sysctl(routeLocalnetPath, "1")
return err
}
// groupByProto groups port numbers by protocol
func groupByProto(entries []PortMapEntry) map[string][]int {
if len(entries) == 0 {

View File

@ -47,6 +47,7 @@ type PluginConf struct {
PrevResult *current.Result `json:"-"`
// Add plugin-specific flags here
Table *int `json:"table,omitempty"`
}
// Wrapper that does a lock before and unlock after operations to serialise
@ -66,7 +67,6 @@ func withLockAndNetNS(nspath string, toRun func(_ ns.NetNS) error) error {
}
err = ns.WithNetNSPath(nspath, toRun)
if err != nil {
return err
}
@ -164,6 +164,9 @@ func cmdAdd(args *skel.CmdArgs) error {
// Do the actual work.
err = withLockAndNetNS(args.Netns, func(_ ns.NetNS) error {
if conf.Table != nil {
return doRoutesWithTable(ipCfgs, *conf.Table)
}
return doRoutes(ipCfgs, args.IfName)
})
if err != nil {
@ -331,31 +334,73 @@ func doRoutes(ipCfgs []*current.IPConfig, iface string) error {
return nil
}
func doRoutesWithTable(ipCfgs []*current.IPConfig, table int) error {
for _, ipCfg := range ipCfgs {
log.Printf("Set rule for source %s", ipCfg.String())
rule := netlink.NewRule()
rule.Table = table
// Source must be restricted to a single IP, not a full subnet
var src net.IPNet
src.IP = ipCfg.Address.IP
if src.IP.To4() != nil {
src.Mask = net.CIDRMask(32, 32)
} else {
src.Mask = net.CIDRMask(128, 128)
}
log.Printf("Source to use %s", src.String())
rule.Src = &src
if err := netlink.RuleAdd(rule); err != nil {
return fmt.Errorf("failed to add rule: %v", err)
}
}
return nil
}
// cmdDel is called for DELETE requests
func cmdDel(args *skel.CmdArgs) error {
// We care a bit about config because it sets log level.
_, err := parseConfig(args.StdinData)
conf, err := parseConfig(args.StdinData)
if err != nil {
return err
}
log.Printf("Cleaning up SBR for %s", args.IfName)
err = withLockAndNetNS(args.Netns, func(_ ns.NetNS) error {
return tidyRules(args.IfName)
return tidyRules(args.IfName, conf.Table)
})
return err
}
// Tidy up the rules for the deleted interface
func tidyRules(iface string) error {
func tidyRules(iface string, table *int) error {
// We keep on going on rule deletion error, but return the last failure.
var errReturn error
var err error
var rules []netlink.Rule
rules, err := netlink.RuleList(netlink.FAMILY_ALL)
if err != nil {
log.Printf("Failed to list all rules to tidy: %v", err)
return fmt.Errorf("Failed to list all rules to tidy: %v", err)
if table != nil {
rules, err = netlink.RuleListFiltered(
netlink.FAMILY_ALL,
&netlink.Rule{
Table: *table,
},
netlink.RT_FILTER_TABLE,
)
if err != nil {
log.Printf("Failed to list rules of table %d to tidy: %v", *table, err)
return fmt.Errorf("failed to list rules of table %d to tidy: %v", *table, err)
}
} else {
rules, err = netlink.RuleList(netlink.FAMILY_ALL)
if err != nil {
log.Printf("Failed to list all rules to tidy: %v", err)
return fmt.Errorf("Failed to list all rules to tidy: %v", err)
}
}
link, err := netlink.LinkByName(iface)
@ -402,7 +447,13 @@ RULE_LOOP:
}
func main() {
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("sbr"))
skel.PluginMainFuncs(skel.CNIFuncs{
Add: cmdAdd,
Check: cmdCheck,
Del: cmdDel,
/* FIXME GC */
/* FIXME Status */
}, version.All, bv.BuildString("sbr"))
}
func cmdCheck(_ *skel.CmdArgs) error {

View File

@ -53,7 +53,9 @@ func setup(targetNs ns.NetNS, status netStatus) error {
err := targetNs.Do(func(_ ns.NetNS) error {
for _, dev := range status.Devices {
log.Printf("Adding dev %s\n", dev.Name)
link := &netlink.Dummy{LinkAttrs: netlink.LinkAttrs{Name: dev.Name}}
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = dev.Name
link := &netlink.Dummy{LinkAttrs: linkAttrs}
err := netlink.LinkAdd(link)
if err != nil {
return err
@ -117,11 +119,16 @@ func readback(targetNs ns.NetNS, devNames []string) (netStatus, error) {
return err
}
routesNoLinkLocal := []netlink.Route{}
for _, route := range routes {
if route.Dst.IP.IsLinkLocalMulticast() || route.Dst.IP.IsLinkLocalUnicast() {
continue
}
log.Printf("Got %s route %v", name, route)
routesNoLinkLocal = append(routesNoLinkLocal, route)
}
retVal.Devices[i].Routes = routes
retVal.Devices[i].Routes = routesNoLinkLocal
}
rules, err := netlink.RuleList(netlink.FAMILY_ALL)
@ -291,6 +298,7 @@ var _ = Describe("sbr test", func() {
expNet1.Routes = append(expNet1.Routes,
netlink.Route{
Gw: net.IPv4(192, 168, 1, 1),
Dst: &net.IPNet{IP: net.IPv4zero, Mask: net.IPMask(net.IPv4zero)},
Table: 100,
LinkIndex: expNet1.Routes[0].LinkIndex,
})
@ -491,6 +499,7 @@ var _ = Describe("sbr test", func() {
}
expNet1.Routes = append(expNet1.Routes,
netlink.Route{
Dst: &net.IPNet{IP: net.IPv4zero, Mask: net.IPMask(net.IPv4zero)},
Gw: net.IPv4(192, 168, 1, 1),
Table: 100,
LinkIndex: expNet1.Routes[0].LinkIndex,
@ -498,6 +507,7 @@ var _ = Describe("sbr test", func() {
expNet1.Routes = append(expNet1.Routes,
netlink.Route{
Dst: &net.IPNet{IP: net.IPv4zero, Mask: net.IPMask(net.IPv4zero)},
Gw: net.IPv4(192, 168, 101, 1),
Table: 101,
LinkIndex: expNet1.Routes[0].LinkIndex,
@ -540,4 +550,81 @@ var _ = Describe("sbr test", func() {
_, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) })
Expect(err).To(MatchError("This plugin must be called as chained plugin"))
})
It("Works with Table ID", func() {
ifname := "net1"
tableID := 5000
conf := `{
"cniVersion": "0.3.0",
"name": "cni-plugin-sbr-test",
"type": "sbr",
"table": %d,
"prevResult": {
"cniVersion": "0.3.0",
"interfaces": [
{
"name": "%s",
"sandbox": "%s"
}
],
"ips": [
{
"address": "192.168.1.209/24",
"interface": 0
},
{
"address": "192.168.101.209/24",
"interface": 0
}
],
"routes": []
}
}`
conf = fmt.Sprintf(conf, tableID, ifname, targetNs.Path())
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNs.Path(),
IfName: ifname,
StdinData: []byte(conf),
}
preStatus := createDefaultStatus()
err := setup(targetNs, preStatus)
Expect(err).NotTo(HaveOccurred())
oldStatus, err := readback(targetNs, []string{"net1", "eth0"})
Expect(err).NotTo(HaveOccurred())
_, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) })
Expect(err).NotTo(HaveOccurred())
newStatus, err := readback(targetNs, []string{"net1", "eth0"})
Expect(err).NotTo(HaveOccurred())
// Routes have not been moved.
Expect(newStatus).To(Equal(oldStatus))
// Fetch all rules for the requested table ID.
var rules []netlink.Rule
err = targetNs.Do(func(_ ns.NetNS) error {
var err error
rules, err = netlink.RuleListFiltered(
netlink.FAMILY_ALL, &netlink.Rule{
Table: tableID,
},
netlink.RT_FILTER_TABLE,
)
return err
})
Expect(err).NotTo(HaveOccurred())
Expect(rules).To(HaveLen(2))
// Both IPs have been added as source based routes with requested table ID.
Expect(rules[0].Table).To(Equal(tableID))
Expect(rules[0].Src.String()).To(Equal("192.168.101.209/32"))
Expect(rules[1].Table).To(Equal(tableID))
Expect(rules[1].Src.String()).To(Equal("192.168.1.209/32"))
})
})

View File

@ -53,6 +53,7 @@ type TuningConf struct {
Mac string `json:"mac,omitempty"`
Promisc bool `json:"promisc,omitempty"`
Mtu int `json:"mtu,omitempty"`
TxQLen *int `json:"txQLen,omitempty"`
Allmulti *bool `json:"allmulti,omitempty"`
RuntimeConfig struct {
@ -69,6 +70,7 @@ type IPAMArgs struct {
Promisc *bool `json:"promisc,omitempty"`
Mtu *int `json:"mtu,omitempty"`
Allmulti *bool `json:"allmulti,omitempty"`
TxQLen *int `json:"txQLen,omitempty"`
}
// configToRestore will contain interface attributes that should be restored on cmdDel
@ -77,6 +79,7 @@ type configToRestore struct {
Promisc *bool `json:"promisc,omitempty"`
Mtu int `json:"mtu,omitempty"`
Allmulti *bool `json:"allmulti,omitempty"`
TxQLen *int `json:"txQLen,omitempty"`
}
// MacEnvArgs represents CNI_ARG
@ -136,6 +139,10 @@ func parseConf(data []byte, envArgs string) (*TuningConf, error) {
if conf.Args.A.Allmulti != nil {
conf.Allmulti = conf.Args.A.Allmulti
}
if conf.Args.A.TxQLen != nil {
conf.TxQLen = conf.Args.A.TxQLen
}
}
return &conf, nil
@ -204,6 +211,14 @@ func changeAllmulti(ifName string, val bool) error {
return netlink.LinkSetAllmulticastOff(link)
}
func changeTxQLen(ifName string, txQLen int) error {
link, err := netlink.LinkByName(ifName)
if err != nil {
return fmt.Errorf("failed to get %q: %v", ifName, err)
}
return netlink.LinkSetTxQLen(link, txQLen)
}
func createBackup(ifName, containerID, backupPath string, tuningConf *TuningConf) error {
config := configToRestore{}
link, err := netlink.LinkByName(ifName)
@ -224,6 +239,10 @@ func createBackup(ifName, containerID, backupPath string, tuningConf *TuningConf
config.Allmulti = new(bool)
*config.Allmulti = (link.Attrs().RawFlags&unix.IFF_ALLMULTI != 0)
}
if tuningConf.TxQLen != nil {
qlen := link.Attrs().TxQLen
config.TxQLen = &qlen
}
if _, err := os.Stat(backupPath); os.IsNotExist(err) {
if err = os.MkdirAll(backupPath, 0o600); err != nil {
@ -292,8 +311,15 @@ func restoreBackup(ifName, containerID, backupPath string) error {
}
}
if config.TxQLen != nil {
if err = changeTxQLen(ifName, *config.TxQLen); err != nil {
err = fmt.Errorf("failed to restore transmit queue length: %v", err)
errStr = append(errStr, err.Error())
}
}
if len(errStr) > 0 {
return fmt.Errorf(strings.Join(errStr, "; "))
return errors.New(strings.Join(errStr, "; "))
}
if err = os.Remove(filePath); err != nil {
@ -346,7 +372,7 @@ func cmdAdd(args *skel.CmdArgs) error {
}
}
if tuningConf.Mac != "" || tuningConf.Mtu != 0 || tuningConf.Promisc || tuningConf.Allmulti != nil {
if tuningConf.Mac != "" || tuningConf.Mtu != 0 || tuningConf.Promisc || tuningConf.Allmulti != nil || tuningConf.TxQLen != nil {
if err = createBackup(args.IfName, args.ContainerID, tuningConf.DataDir, tuningConf); err != nil {
return err
}
@ -377,6 +403,12 @@ func cmdAdd(args *skel.CmdArgs) error {
return err
}
}
if tuningConf.TxQLen != nil {
if err = changeTxQLen(args.IfName, *tuningConf.TxQLen); err != nil {
return err
}
}
return nil
})
if err != nil {
@ -401,7 +433,13 @@ func cmdDel(args *skel.CmdArgs) error {
}
func main() {
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("tuning"))
skel.PluginMainFuncs(skel.CNIFuncs{
Add: cmdAdd,
Check: cmdCheck,
Del: cmdDel,
/* FIXME GC */
/* FIXME Status */
}, version.All, bv.BuildString("tuning"))
}
func cmdCheck(args *skel.CmdArgs) error {
@ -483,6 +521,13 @@ func cmdCheck(args *skel.CmdArgs) error {
args.IfName, tuningConf.Allmulti, allmulti)
}
}
if tuningConf.TxQLen != nil {
if *tuningConf.TxQLen != link.Attrs().TxQLen {
return fmt.Errorf("Error: Tuning configured Transmit Queue Length of %s is %d, current value is %d",
args.IfName, tuningConf.TxQLen, link.Attrs().TxQLen)
}
}
return nil
})
if err != nil {

View File

@ -110,10 +110,10 @@ var _ = Describe("tuning plugin", func() {
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = IFNAME
err = netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: IFNAME,
},
LinkAttrs: linkAttrs,
})
Expect(err).NotTo(HaveOccurred())
link, err := netlink.LinkByName(IFNAME)
@ -125,6 +125,8 @@ var _ = Describe("tuning plugin", func() {
*beforeConf.Promisc = (link.Attrs().Promisc != 0)
beforeConf.Allmulti = new(bool)
*beforeConf.Allmulti = (link.Attrs().RawFlags&unix.IFF_ALLMULTI != 0)
beforeConf.TxQLen = new(int)
*beforeConf.TxQLen = link.Attrs().TxQLen
return nil
})
Expect(err).NotTo(HaveOccurred())
@ -487,6 +489,138 @@ var _ = Describe("tuning plugin", func() {
Expect(err).NotTo(HaveOccurred())
})
It(fmt.Sprintf("[%s] configures and deconfigures tx queue len with ADD/DEL", ver), func() {
conf := []byte(fmt.Sprintf(`{
"name": "test",
"type": "iplink",
"cniVersion": "%s",
"txQLen": 20000,
"prevResult": {
"interfaces": [
{"name": "dummy0", "sandbox":"netns"}
],
"ips": [
{
"version": "4",
"address": "10.0.0.2/24",
"gateway": "10.0.0.1",
"interface": 0
}
]
}
}`, ver))
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: originalNS.Path(),
IfName: IFNAME,
StdinData: conf,
}
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, _, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().TxQLen).To(Equal(20000))
if testutils.SpecVersionHasCHECK(ver) {
n := &TuningConf{}
Expect(json.Unmarshal(conf, &n)).NotTo(HaveOccurred())
confString, err := buildOneConfig(ver, n, r)
Expect(err).NotTo(HaveOccurred())
args.StdinData = confString
Expect(testutils.CmdCheckWithArgs(args, func() error {
return cmdCheck(args)
})).NotTo(HaveOccurred())
}
err = testutils.CmdDel(originalNS.Path(),
args.ContainerID, "", func() error { return cmdDel(args) })
Expect(err).NotTo(HaveOccurred())
link, err = netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().TxQLen).To(Equal(*beforeConf.TxQLen))
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It(fmt.Sprintf("[%s] configures and deconfigures tx queue len from args with ADD/DEL", ver), func() {
conf := []byte(fmt.Sprintf(`{
"name": "test",
"type": "iplink",
"cniVersion": "%s",
"args": {
"cni": {
"txQLen": 20000
}
},
"prevResult": {
"interfaces": [
{"name": "dummy0", "sandbox":"netns"}
],
"ips": [
{
"version": "4",
"address": "10.0.0.2/24",
"gateway": "10.0.0.1",
"interface": 0
}
]
}
}`, ver))
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: originalNS.Path(),
IfName: IFNAME,
StdinData: conf,
}
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, _, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
result, err := types100.GetResult(r)
Expect(err).NotTo(HaveOccurred())
Expect(result.Interfaces).To(HaveLen(1))
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
Expect(result.IPs).To(HaveLen(1))
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().TxQLen).To(Equal(20000))
err = testutils.CmdDel(originalNS.Path(),
args.ContainerID, "", func() error { return cmdDel(args) })
Expect(err).NotTo(HaveOccurred())
link, err = netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().TxQLen).To(Equal(*beforeConf.TxQLen))
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It(fmt.Sprintf("[%s] configures and deconfigures mac address (from conf file) with ADD/DEL", ver), func() {
mac := "c2:11:22:33:44:55"
conf := []byte(fmt.Sprintf(`{
@ -780,7 +914,7 @@ var _ = Describe("tuning plugin", func() {
Expect(err).NotTo(HaveOccurred())
})
It(fmt.Sprintf("[%s] configures and deconfigures mac address, promisc mode and MTU (from conf file) with custom dataDir", ver), func() {
It(fmt.Sprintf("[%s] configures and deconfigures mac address, promisc mode, MTU and tx queue len (from conf file) with custom dataDir", ver), func() {
conf := []byte(fmt.Sprintf(`{
"name": "test",
"type": "iplink",
@ -788,6 +922,7 @@ var _ = Describe("tuning plugin", func() {
"mac": "c2:11:22:33:44:77",
"promisc": true,
"mtu": 4000,
"txQLen": 20000,
"dataDir": "/tmp/tuning-test",
"prevResult": {
"interfaces": [
@ -834,6 +969,7 @@ var _ = Describe("tuning plugin", func() {
Expect(link.Attrs().HardwareAddr).To(Equal(hw))
Expect(link.Attrs().Promisc).To(Equal(1))
Expect(link.Attrs().MTU).To(Equal(4000))
Expect(link.Attrs().TxQLen).To(Equal(20000))
Expect("/tmp/tuning-test/dummy_dummy0.json").Should(BeAnExistingFile())
@ -862,6 +998,7 @@ var _ = Describe("tuning plugin", func() {
Expect(link.Attrs().HardwareAddr.String()).To(Equal(beforeConf.Mac))
Expect(link.Attrs().MTU).To(Equal(beforeConf.Mtu))
Expect(link.Attrs().Promisc != 0).To(Equal(*beforeConf.Promisc))
Expect(link.Attrs().TxQLen).To(Equal(*beforeConf.TxQLen))
return nil
})

View File

@ -39,7 +39,13 @@ type VRFNetConf struct {
}
func main() {
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.VersionsStartingFrom("0.3.1"), bv.BuildString("vrf"))
skel.PluginMainFuncs(skel.CNIFuncs{
Add: cmdAdd,
Check: cmdCheck,
Del: cmdDel,
/* FIXME GC */
/* FIXME Status */
}, version.VersionsStartingFrom("0.3.1"), bv.BuildString("vrf"))
}
func cmdAdd(args *skel.CmdArgs) error {
@ -75,7 +81,6 @@ func cmdAdd(args *skel.CmdArgs) error {
}
return nil
})
if err != nil {
return fmt.Errorf("cmdAdd failed: %v", err)
}
@ -121,7 +126,6 @@ func cmdDel(args *skel.CmdArgs) error {
}
return nil
})
if err != nil {
// if NetNs is passed down by the Cloud Orchestration Engine, or if it called multiple times
// so don't return an error if the device is already removed.

View File

@ -17,6 +17,8 @@ package main
import (
"fmt"
"math"
"net"
"time"
"github.com/vishvananda/netlink"
)
@ -48,11 +50,11 @@ func createVRF(name string, tableID uint32) (*netlink.Vrf, error) {
}
}
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = name
vrf := &netlink.Vrf{
LinkAttrs: netlink.LinkAttrs{
Name: name,
},
Table: tableID,
LinkAttrs: linkAttrs,
Table: tableID,
}
err = netlink.LinkAdd(vrf)
@ -104,6 +106,19 @@ func addInterface(vrf *netlink.Vrf, intf string) error {
if err != nil {
return fmt.Errorf("failed getting ipv6 addresses for %s", intf)
}
// Save all routes that are not local and connected, before setting master,
// because otherwise those routes will be deleted after interface is moved.
filter := &netlink.Route{
LinkIndex: i.Attrs().Index,
Scope: netlink.SCOPE_UNIVERSE, // Exclude local and connected routes
}
filterMask := netlink.RT_FILTER_OIF | netlink.RT_FILTER_SCOPE // Filter based on link index and scope
routes, err := netlink.RouteListFiltered(netlink.FAMILY_ALL, filter, filterMask)
if err != nil {
return fmt.Errorf("failed getting all routes for %s", intf)
}
err = netlink.LinkSetMaster(i, vrf)
if err != nil {
return fmt.Errorf("could not set vrf %s as master of %s: %v", vrf.Name, intf, err)
@ -111,7 +126,7 @@ func addInterface(vrf *netlink.Vrf, intf string) error {
afterAddresses, err := netlink.AddrList(i, netlink.FAMILY_V6)
if err != nil {
return fmt.Errorf("failed getting ipv6 new addresses for %s", intf)
return fmt.Errorf("failed getting ipv6 new addresses for %s: %v", intf, err)
}
// Since keeping the ipv6 address depends on net.ipv6.conf.all.keep_addr_on_down ,
@ -128,6 +143,49 @@ CONTINUE:
if err != nil {
return fmt.Errorf("could not restore address %s to %s @ %s: %v", toFind, intf, vrf.Name, err)
}
// Waits for local/host routes to be added by the kernel.
maxRetry := 10
for {
routesVRFTable, err := netlink.RouteListFiltered(
netlink.FAMILY_ALL,
&netlink.Route{
Dst: &net.IPNet{
IP: toFind.IP,
Mask: net.IPMask{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
},
Table: int(vrf.Table),
LinkIndex: i.Attrs().Index,
},
netlink.RT_FILTER_OIF|netlink.RT_FILTER_TABLE|netlink.RT_FILTER_DST,
)
if err != nil {
return fmt.Errorf("failed getting routes for %s table %d for dst %s: %v", intf, vrf.Table, toFind.IPNet.String(), err)
}
if len(routesVRFTable) >= 1 {
break
}
maxRetry--
if maxRetry <= 0 {
return fmt.Errorf("failed getting local/host addresses for %s in table %d with dst %s", intf, vrf.Table, toFind.IPNet.String())
}
time.Sleep(10 * time.Millisecond)
}
}
// Apply all saved routes for the interface that was moved to the VRF
for _, route := range routes {
r := route
// Modify original table to vrf one,
r.Table = int(vrf.Table)
// equivalent of 'ip route replace <address> table <int>'.
err = netlink.RouteReplace(&r)
if err != nil {
return fmt.Errorf("could not add route '%s': %v", r, err)
}
}
return nil

View File

@ -17,6 +17,9 @@ package main
import (
"encoding/json"
"fmt"
"net"
"strings"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
@ -92,22 +95,22 @@ var _ = Describe("vrf plugin", func() {
err = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
la0 := netlink.NewLinkAttrs()
la0.Name = IF0Name
err = netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: IF0Name,
},
LinkAttrs: la0,
})
Expect(err).NotTo(HaveOccurred())
_, err = netlink.LinkByName(IF0Name)
Expect(err).NotTo(HaveOccurred())
la1 := netlink.NewLinkAttrs()
la1.Name = IF1Name
err = netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: IF1Name,
},
LinkAttrs: la1,
})
Expect(err).NotTo(HaveOccurred())
_, err = netlink.LinkByName(IF0Name)
_, err = netlink.LinkByName(IF1Name)
Expect(err).NotTo(HaveOccurred())
return nil
})
@ -177,6 +180,292 @@ var _ = Describe("vrf plugin", func() {
Expect(err).NotTo(HaveOccurred())
})
It("adds the interface and custom routing to new VRF", func() {
conf := configWithRouteFor("test", IF0Name, VRF0Name, "10.0.0.2/24", "10.10.10.0/24")
By("Setting custom routing first", func() {
err := targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
ipv4, err := types.ParseCIDR("10.0.0.2/24")
Expect(err).NotTo(HaveOccurred())
Expect(ipv4).NotTo(BeNil())
_, routev4, err := net.ParseCIDR("10.10.10.0/24")
Expect(err).NotTo(HaveOccurred())
ipv6, err := types.ParseCIDR("abcd:1234:ffff::cdde/64")
Expect(err).NotTo(HaveOccurred())
Expect(ipv6).NotTo(BeNil())
_, routev6, err := net.ParseCIDR("1111:dddd::/80")
Expect(err).NotTo(HaveOccurred())
Expect(routev6).NotTo(BeNil())
link, err := netlink.LinkByName(IF0Name)
Expect(err).NotTo(HaveOccurred())
// Add IP addresses for network reachability
netlink.AddrAdd(link, &netlink.Addr{IPNet: ipv4})
netlink.AddrAdd(link, &netlink.Addr{IPNet: ipv6})
// Wait for the corresponding route to be addeded
Eventually(func() bool {
ipv6RouteDst := &net.IPNet{
IP: ipv6.IP,
Mask: net.IPMask{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
}
routes, _ := netlink.RouteListFiltered(netlink.FAMILY_ALL, &netlink.Route{
Dst: ipv6RouteDst,
Table: 0,
}, netlink.RT_FILTER_DST|netlink.RT_FILTER_TABLE)
return err == nil && len(routes) >= 1
}, time.Second, 500*time.Millisecond).Should(BeTrue())
ipAddrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
Expect(err).NotTo(HaveOccurred())
// Check if address was assigned properly
Expect(ipAddrs[0].IP.String()).To(Equal("10.0.0.2"))
// Set interface UP, otherwise local route to 10.0.0.0/24 is not present
err = netlink.LinkSetUp(link)
Expect(err).NotTo(HaveOccurred())
// Add additional route to 10.10.10.0/24 via 10.0.0.1 gateway
r := netlink.Route{
LinkIndex: link.Attrs().Index,
Src: ipv4.IP,
Dst: routev4,
Gw: net.ParseIP("10.0.0.1"),
}
err = netlink.RouteAdd(&r)
Expect(err).NotTo(HaveOccurred())
r6 := netlink.Route{
LinkIndex: link.Attrs().Index,
Src: ipv6.IP,
Dst: routev6,
Gw: net.ParseIP("abcd:1234:ffff::1"),
}
err = netlink.RouteAdd(&r6)
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNS.Path(),
IfName: IF0Name,
StdinData: conf,
}
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, _, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
result, err := current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
Expect(result.Interfaces).To(HaveLen(1))
Expect(result.Interfaces[0].Name).To(Equal(IF0Name))
Expect(result.Routes).To(HaveLen(1))
Expect(result.Routes[0].Dst.IP.String()).To(Equal("10.10.10.0"))
return nil
})
Expect(err).NotTo(HaveOccurred())
err = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
checkInterfaceOnVRF(VRF0Name, IF0Name)
checkRoutesOnVRF(VRF0Name, IF0Name, "10.0.0.2", "10.10.10.0/24", "1111:dddd::/80")
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("filters the correct routes to import to new VRF", func() {
_ = configWithRouteFor("test0", IF0Name, VRF0Name, "10.0.0.2/24", "10.10.10.0/24")
conf1 := configWithRouteFor("test1", IF1Name, VRF1Name, "10.0.0.3/24", "10.11.10.0/24")
By("Setting custom routing for IF0Name", func() {
err := targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
ipv4, err := types.ParseCIDR("10.0.0.2/24")
Expect(err).NotTo(HaveOccurred())
Expect(ipv4).NotTo(BeNil())
_, routev4, err := net.ParseCIDR("10.10.10.0/24")
Expect(err).NotTo(HaveOccurred())
ipv6, err := types.ParseCIDR("abcd:1234:ffff::cdde/64")
Expect(err).NotTo(HaveOccurred())
Expect(ipv6).NotTo(BeNil())
_, routev6, err := net.ParseCIDR("1111:dddd::/80")
Expect(err).NotTo(HaveOccurred())
Expect(routev6).NotTo(BeNil())
link, err := netlink.LinkByName(IF0Name)
Expect(err).NotTo(HaveOccurred())
// Add IP addresses for network reachability
netlink.AddrAdd(link, &netlink.Addr{IPNet: ipv4})
netlink.AddrAdd(link, &netlink.Addr{IPNet: ipv6})
// Wait for the corresponding route to be addeded
Eventually(func() bool {
ipv6RouteDst := &net.IPNet{
IP: ipv6.IP,
Mask: net.IPMask{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
}
routes, _ := netlink.RouteListFiltered(netlink.FAMILY_ALL, &netlink.Route{
Dst: ipv6RouteDst,
Table: 0,
}, netlink.RT_FILTER_DST|netlink.RT_FILTER_TABLE)
return err == nil && len(routes) >= 1
}, time.Second, 500*time.Millisecond).Should(BeTrue())
ipAddrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
Expect(err).NotTo(HaveOccurred())
// Check if address was assigned properly
Expect(ipAddrs[0].IP.String()).To(Equal("10.0.0.2"))
// Set interface UP, otherwise local route to 10.0.0.0/24 is not present
err = netlink.LinkSetUp(link)
Expect(err).NotTo(HaveOccurred())
// Add additional route to 10.10.10.0/24 via 10.0.0.1 gateway
r := netlink.Route{
LinkIndex: link.Attrs().Index,
Dst: routev4,
Gw: net.ParseIP("10.0.0.1"),
}
err = netlink.RouteAdd(&r)
Expect(err).NotTo(HaveOccurred())
r6 := netlink.Route{
LinkIndex: link.Attrs().Index,
Src: ipv6.IP,
Dst: routev6,
Gw: net.ParseIP("abcd:1234:ffff::1"),
}
err = netlink.RouteAdd(&r6)
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
By("Setting custom routing for IF1Name", func() {
err := targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
ipv4, err := types.ParseCIDR("10.0.0.3/24")
Expect(err).NotTo(HaveOccurred())
Expect(ipv4).NotTo(BeNil())
_, routev4, err := net.ParseCIDR("10.11.10.0/24")
Expect(err).NotTo(HaveOccurred())
ipv6, err := types.ParseCIDR("abcd:1234:ffff::cddf/64")
Expect(err).NotTo(HaveOccurred())
Expect(ipv6).NotTo(BeNil())
_, routev6, err := net.ParseCIDR("1111:ddde::/80")
Expect(err).NotTo(HaveOccurred())
Expect(routev6).NotTo(BeNil())
link, err := netlink.LinkByName(IF1Name)
Expect(err).NotTo(HaveOccurred())
// Add IP addresses for network reachability
netlink.AddrAdd(link, &netlink.Addr{IPNet: ipv4})
netlink.AddrAdd(link, &netlink.Addr{IPNet: ipv6})
// Wait for the corresponding route to be addeded
Eventually(func() bool {
ipv6RouteDst := &net.IPNet{
IP: ipv6.IP,
Mask: net.IPMask{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
}
routes, _ := netlink.RouteListFiltered(netlink.FAMILY_ALL, &netlink.Route{
Dst: ipv6RouteDst,
Table: 0,
}, netlink.RT_FILTER_DST|netlink.RT_FILTER_TABLE)
return err == nil && len(routes) >= 1
}, time.Second, 500*time.Millisecond).Should(BeTrue())
ipAddrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
Expect(err).NotTo(HaveOccurred())
// Check if address was assigned properly
Expect(ipAddrs[0].IP.String()).To(Equal("10.0.0.3"))
// Set interface UP, otherwise local route to 10.0.0.0/24 is not present
err = netlink.LinkSetUp(link)
Expect(err).NotTo(HaveOccurred())
// Add additional route to 10.11.10.0/24 via 10.0.0.1 gateway
r := netlink.Route{
LinkIndex: link.Attrs().Index,
Dst: routev4,
Gw: net.ParseIP("10.0.0.1"),
Priority: 100,
}
err = netlink.RouteAdd(&r)
Expect(err).NotTo(HaveOccurred())
r6 := netlink.Route{
LinkIndex: link.Attrs().Index,
Src: ipv6.IP,
Dst: routev6,
Gw: net.ParseIP("abcd:1234:ffff::1"),
}
err = netlink.RouteAdd(&r6)
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
By("Adding if1 to the VRF", func() {
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNS.Path(),
IfName: IF1Name,
StdinData: conf1,
}
_, _, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
By("Checking routes are moved correctly to VRF", func() {
err := targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
checkInterfaceOnVRF(VRF1Name, IF1Name)
checkRoutesOnVRF(VRF1Name, IF1Name, "10.0.0.3", "10.11.10.0/24", "1111:ddde::/80")
return nil
})
Expect(err).NotTo(HaveOccurred())
})
})
It("fails if the interface already has a master set", func() {
conf := configFor("test", IF0Name, VRF0Name, "10.0.0.2/24")
@ -185,10 +474,10 @@ var _ = Describe("vrf plugin", func() {
defer GinkgoRecover()
l, err := netlink.LinkByName(IF0Name)
Expect(err).NotTo(HaveOccurred())
linkAttrs := netlink.NewLinkAttrs()
linkAttrs.Name = "testrbridge"
br := &netlink.Bridge{
LinkAttrs: netlink.LinkAttrs{
Name: "testrbridge",
},
LinkAttrs: linkAttrs,
}
err = netlink.LinkAdd(br)
Expect(err).NotTo(HaveOccurred())
@ -690,6 +979,35 @@ func configWithTableFor(name, intf, vrf, ip string, tableID int) []byte {
return []byte(conf)
}
func configWithRouteFor(name, intf, vrf, ip, route string) []byte {
conf := fmt.Sprintf(`{
"name": "%s",
"type": "vrf",
"cniVersion": "0.3.1",
"vrfName": "%s",
"prevResult": {
"interfaces": [
{"name": "%s", "sandbox":"netns"}
],
"ips": [
{
"version": "4",
"address": "%s",
"gateway": "10.0.0.1",
"interface": 0
}
],
"routes": [
{
"dst": "%s",
"gw": "10.0.0.1"
}
]
}
}`, name, vrf, intf, ip, route)
return []byte(conf)
}
func checkInterfaceOnVRF(vrfName, intfName string) {
vrf, err := netlink.LinkByName(vrfName)
Expect(err).NotTo(HaveOccurred())
@ -702,3 +1020,46 @@ func checkInterfaceOnVRF(vrfName, intfName string) {
Expect(err).NotTo(HaveOccurred())
Expect(master.Attrs().Name).To(Equal(vrfName))
}
func checkRoutesOnVRF(vrfName, intfName string, addrStr string, routesToCheck ...string) {
l, err := netlink.LinkByName(vrfName)
Expect(err).NotTo(HaveOccurred())
Expect(l).To(BeAssignableToTypeOf(&netlink.Vrf{}))
vrf, ok := l.(*netlink.Vrf)
Expect(ok).To(BeTrue())
link, err := netlink.LinkByName(intfName)
Expect(err).NotTo(HaveOccurred())
err = netlink.LinkSetUp(link)
Expect(err).NotTo(HaveOccurred())
ipAddrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
Expect(err).NotTo(HaveOccurred())
Expect(ipAddrs).To(HaveLen(1))
Expect(ipAddrs[0].IP.String()).To(Equal(addrStr))
routeFilter := &netlink.Route{
Table: int(vrf.Table),
}
routes, err := netlink.RouteListFiltered(netlink.FAMILY_ALL,
routeFilter,
netlink.RT_FILTER_TABLE)
Expect(err).NotTo(HaveOccurred())
routesRead := []string{}
for _, route := range routes {
routesRead = append(routesRead, route.String())
Expect(uint32(route.Table)).To(Equal(vrf.Table))
}
routesStr := strings.Join(routesRead, "\n")
for _, route := range routesToCheck {
Expect(routesStr).To(ContainSubstring(route))
}
for _, route := range routes {
Expect(route.LinkIndex).To(Equal(link.Attrs().Index))
}
}

View File

@ -24,6 +24,7 @@ import (
"github.com/containernetworking/cni/pkg/types"
current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/pkg/version"
"github.com/containernetworking/plugins/pkg/ipam"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
@ -150,10 +151,40 @@ func cmdDel(args *skel.CmdArgs) error {
func main() {
// replace TODO with your plugin name
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("TODO"))
skel.PluginMainFuncs(skel.CNIFuncs{
Add: cmdAdd,
Check: cmdCheck,
Del: cmdDel,
Status: cmdStatus,
/* FIXME GC */
}, version.All, bv.BuildString("TODO"))
}
func cmdCheck(_ *skel.CmdArgs) error {
// TODO: implement
return fmt.Errorf("not implemented")
}
// cmdStatus implements the STATUS command, which indicates whether or not
// this plugin is able to accept ADD requests.
//
// If the plugin has external dependencies, such as a daemon
// or chained ipam plugin, it should determine their status. If all is well,
// and an ADD can be successfully processed, return nil
func cmdStatus(args *skel.CmdArgs) error {
conf, err := parseConfig(args.StdinData)
if err != nil {
return err
}
_ = conf
// If this plugins delegates IPAM, ensure that IPAM is also running
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
return err
}
// TODO: implement STATUS here
// e.g. querying an external deamon, or delegating STATUS to an IPAM plugin
return nil
}

View File

@ -1,48 +0,0 @@
#!/usr/bin/env bash
set -xe
SRC_DIR="${SRC_DIR:-$PWD}"
DOCKER="${DOCKER:-docker}"
TAG=$(git describe --tags --dirty)
RELEASE_DIR=release-${TAG}
BUILDFLAGS="-ldflags '-extldflags -static -X github.com/containernetworking/plugins/pkg/utils/buildversion.BuildVersion=${TAG}'"
OUTPUT_DIR=bin
# Always clean first
rm -Rf ${SRC_DIR}/${RELEASE_DIR}
mkdir -p ${SRC_DIR}/${RELEASE_DIR}
mkdir -p ${OUTPUT_DIR}
$DOCKER run -ti -v ${SRC_DIR}:/go/src/github.com/containernetworking/plugins:z --rm golang:1.20-alpine \
/bin/sh -xe -c "\
apk --no-cache add bash tar;
cd /go/src/github.com/containernetworking/plugins; umask 0022;
for arch in amd64 arm arm64 ppc64le s390x mips64le riscv64; do \
rm -f ${OUTPUT_DIR}/*; \
CGO_ENABLED=0 GOARCH=\$arch ./build_linux.sh ${BUILDFLAGS}; \
for format in tgz; do \
FILENAME=cni-plugins-linux-\$arch-${TAG}.\$format; \
FILEPATH=${RELEASE_DIR}/\$FILENAME; \
tar -C ${OUTPUT_DIR} --owner=0 --group=0 -caf \$FILEPATH .; \
done; \
done;
rm -rf ${OUTPUT_DIR}/*; \
CGO_ENABLED=0 GOARCH=amd64 ./build_windows.sh ${BUILDFLAGS}; \
for format in tgz; do \
FILENAME=cni-plugins-windows-amd64-${TAG}.\$format; \
FILEPATH=${RELEASE_DIR}/\$FILENAME; \
tar -C ${OUTPUT_DIR} --owner=0 --group=0 -caf \$FILEPATH .; \
done;
cd ${RELEASE_DIR};
for f in *.tgz; do sha1sum \$f > \$f.sha1; done;
for f in *.tgz; do sha256sum \$f > \$f.sha256; done;
for f in *.tgz; do sha512sum \$f > \$f.sha512; done;
cd ..
chown -R ${UID} ${OUTPUT_DIR} ${RELEASE_DIR}"

View File

@ -10,12 +10,29 @@ set -e
cd "$(dirname "$0")"
# Build all plugins before testing
source ./build_linux.sh
. ./build_linux.sh
echo "Running tests"
function testrun {
sudo -E bash -c "umask 0; PATH=${GOPATH}/bin:$(pwd)/bin:${PATH} go test $@"
testrun() {
sudo -E sh -c "umask 0; PATH=${GOPATH}/bin:$(pwd)/bin:${PATH} go test -race $*"
}
ensure_sysctl() {
local key
local val
local existing
key="$1"
val="$2"
existing="$(sysctl -ben "$key")"
sysctl -r
if [ "$val" -ne "$existing" ]; then
echo "sudo sysctl -we '$key'='$val'"
sudo sysctl -we "$key"="$val"
fi
}
COVERALLS=${COVERALLS:-""}
@ -31,7 +48,7 @@ PKG=${PKG:-$(go list ./... | xargs echo)}
i=0
for t in ${PKG}; do
if [ -n "${COVERALLS}" ]; then
COVERFLAGS="-covermode set -coverprofile ${i}.coverprofile"
COVERFLAGS="-covermode atomic -coverprofile ${i}.coverprofile"
fi
echo "${t}"
testrun "${COVERFLAGS:-""} ${t}"
@ -39,5 +56,8 @@ for t in ${PKG}; do
done
# Run the pkg/ns tests as non root user
mkdir /tmp/cni-rootless
mkdir -p /tmp/cni-rootless
ensure_sysctl kernel.unprivileged_userns_clone 1
ensure_sysctl kernel.apparmor_restrict_unprivileged_userns 0
(export XDG_RUNTIME_DIR=/tmp/cni-rootless; cd pkg/ns/; unshare -rmn go test)

View File

@ -1,11 +1,11 @@
#!/usr/bin/env bash
#!/usr/bin/env sh
#
# Run CNI plugin tests.
#
set -e
cd "$(dirname "$0")"
source ./build_windows.sh
. ./build_windows.sh
echo "Running tests"
@ -17,4 +17,4 @@ for d in $PLUGINS; do
done
echo "testing packages $PKGS"
go test -v $PKGS -ginkgo.randomizeAllSpecs -ginkgo.failOnPending -ginkgo.progress
go test -race -v $PKGS -ginkgo.randomizeAllSpecs -ginkgo.failOnPending -ginkgo.progress

View File

@ -1,19 +1,11 @@
run:
skip-dirs:
- pkg/etw/sample
linters:
enable:
# style
- containedctx # struct contains a context
- dupl # duplicate code
- errname # erorrs are named correctly
- goconst # strings that should be constants
- godot # comments end in a period
- misspell
- nolintlint # "//nolint" directives are properly explained
- revive # golint replacement
- stylecheck # golint replacement, less configurable than revive
- unconvert # unnecessary conversions
- wastedassign
@ -23,13 +15,14 @@ linters:
- exhaustive # check exhaustiveness of enum switch statements
- gofmt # files are gofmt'ed
- gosec # security
- nestif # deeply nested ifs
- nilerr # returns nil even with non-nil error
- prealloc # slices that can be pre-allocated
- structcheck # unused struct fields
- thelper # test helpers without t.Helper()
- unparam # unused function params
issues:
exclude-dirs:
- pkg/etw/sample
exclude-rules:
# err is very often shadowed in nested scopes
- linters:
@ -42,6 +35,18 @@ issues:
text: "^line-length-limit: "
source: "^//(go:generate|sys) "
#TODO: remove after upgrading to go1.18
# ignore comment spacing for nolint and sys directives
- linters:
- revive
text: "^comment-spacings: no space between comment delimiter and comment text"
source: "//(cspell:|nolint:|sys |todo)"
# not on go 1.18 yet, so no any
- linters:
- revive
text: "^use-any: since GO 1.18 'interface{}' can be replaced by 'any'"
# allow unjustified ignores of error checks in defer statements
- linters:
- nolintlint
@ -56,15 +61,15 @@ issues:
linters-settings:
exhaustive:
default-signifies-exhaustive: true
govet:
enable-all: true
disable:
# struct order is often for Win32 compat
# also, ignore pointer bytes/GC issues for now until performance becomes an issue
- fieldalignment
check-shadowing: true
nolintlint:
allow-leading-space: false
require-explanation: true
require-specific: true
revive:
@ -98,6 +103,8 @@ linters-settings:
disabled: true
- name: flag-parameter # excessive, and a common idiom we use
disabled: true
- name: unhandled-error # warns over common fmt.Print* and io.Close; rely on errcheck instead
disabled: true
# general config
- name: line-length-limit
arguments:
@ -138,7 +145,3 @@ linters-settings:
- VPCI
- WCOW
- WIM
stylecheck:
checks:
- "all"
- "-ST1003" # use revive's var naming

View File

@ -10,14 +10,14 @@ import (
"io"
"os"
"runtime"
"syscall"
"unicode/utf16"
"github.com/Microsoft/go-winio/internal/fs"
"golang.org/x/sys/windows"
)
//sys backupRead(h syscall.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupRead
//sys backupWrite(h syscall.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupWrite
//sys backupRead(h windows.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupRead
//sys backupWrite(h windows.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupWrite
const (
BackupData = uint32(iota + 1)
@ -104,7 +104,7 @@ func (r *BackupStreamReader) Next() (*BackupHeader, error) {
if err := binary.Read(r.r, binary.LittleEndian, name); err != nil {
return nil, err
}
hdr.Name = syscall.UTF16ToString(name)
hdr.Name = windows.UTF16ToString(name)
}
if wsi.StreamID == BackupSparseBlock {
if err := binary.Read(r.r, binary.LittleEndian, &hdr.Offset); err != nil {
@ -205,7 +205,7 @@ func NewBackupFileReader(f *os.File, includeSecurity bool) *BackupFileReader {
// Read reads a backup stream from the file by calling the Win32 API BackupRead().
func (r *BackupFileReader) Read(b []byte) (int, error) {
var bytesRead uint32
err := backupRead(syscall.Handle(r.f.Fd()), b, &bytesRead, false, r.includeSecurity, &r.ctx)
err := backupRead(windows.Handle(r.f.Fd()), b, &bytesRead, false, r.includeSecurity, &r.ctx)
if err != nil {
return 0, &os.PathError{Op: "BackupRead", Path: r.f.Name(), Err: err}
}
@ -220,7 +220,7 @@ func (r *BackupFileReader) Read(b []byte) (int, error) {
// the underlying file.
func (r *BackupFileReader) Close() error {
if r.ctx != 0 {
_ = backupRead(syscall.Handle(r.f.Fd()), nil, nil, true, false, &r.ctx)
_ = backupRead(windows.Handle(r.f.Fd()), nil, nil, true, false, &r.ctx)
runtime.KeepAlive(r.f)
r.ctx = 0
}
@ -244,7 +244,7 @@ func NewBackupFileWriter(f *os.File, includeSecurity bool) *BackupFileWriter {
// Write restores a portion of the file using the provided backup stream.
func (w *BackupFileWriter) Write(b []byte) (int, error) {
var bytesWritten uint32
err := backupWrite(syscall.Handle(w.f.Fd()), b, &bytesWritten, false, w.includeSecurity, &w.ctx)
err := backupWrite(windows.Handle(w.f.Fd()), b, &bytesWritten, false, w.includeSecurity, &w.ctx)
if err != nil {
return 0, &os.PathError{Op: "BackupWrite", Path: w.f.Name(), Err: err}
}
@ -259,7 +259,7 @@ func (w *BackupFileWriter) Write(b []byte) (int, error) {
// close the underlying file.
func (w *BackupFileWriter) Close() error {
if w.ctx != 0 {
_ = backupWrite(syscall.Handle(w.f.Fd()), nil, nil, true, false, &w.ctx)
_ = backupWrite(windows.Handle(w.f.Fd()), nil, nil, true, false, &w.ctx)
runtime.KeepAlive(w.f)
w.ctx = 0
}
@ -271,17 +271,14 @@ func (w *BackupFileWriter) Close() error {
//
// If the file opened was a directory, it cannot be used with Readdir().
func OpenForBackup(path string, access uint32, share uint32, createmode uint32) (*os.File, error) {
winPath, err := syscall.UTF16FromString(path)
if err != nil {
return nil, err
}
h, err := syscall.CreateFile(&winPath[0],
access,
share,
h, err := fs.CreateFile(path,
fs.AccessMask(access),
fs.FileShareMode(share),
nil,
createmode,
syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OPEN_REPARSE_POINT,
0)
fs.FileCreationDisposition(createmode),
fs.FILE_FLAG_BACKUP_SEMANTICS|fs.FILE_FLAG_OPEN_REPARSE_POINT,
0,
)
if err != nil {
err = &os.PathError{Op: "open", Path: path, Err: err}
return nil, err

View File

@ -15,26 +15,11 @@ import (
"golang.org/x/sys/windows"
)
//sys cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) = CancelIoEx
//sys createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) = CreateIoCompletionPort
//sys getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) = GetQueuedCompletionStatus
//sys setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes
//sys wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) = ws2_32.WSAGetOverlappedResult
type atomicBool int32
func (b *atomicBool) isSet() bool { return atomic.LoadInt32((*int32)(b)) != 0 }
func (b *atomicBool) setFalse() { atomic.StoreInt32((*int32)(b), 0) }
func (b *atomicBool) setTrue() { atomic.StoreInt32((*int32)(b), 1) }
//revive:disable-next-line:predeclared Keep "new" to maintain consistency with "atomic" pkg
func (b *atomicBool) swap(new bool) bool {
var newInt int32
if new {
newInt = 1
}
return atomic.SwapInt32((*int32)(b), newInt) == 1
}
//sys cancelIoEx(file windows.Handle, o *windows.Overlapped) (err error) = CancelIoEx
//sys createIoCompletionPort(file windows.Handle, port windows.Handle, key uintptr, threadCount uint32) (newport windows.Handle, err error) = CreateIoCompletionPort
//sys getQueuedCompletionStatus(port windows.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) = GetQueuedCompletionStatus
//sys setFileCompletionNotificationModes(h windows.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes
//sys wsaGetOverlappedResult(h windows.Handle, o *windows.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) = ws2_32.WSAGetOverlappedResult
var (
ErrFileClosed = errors.New("file has already been closed")
@ -50,7 +35,7 @@ func (*timeoutError) Temporary() bool { return true }
type timeoutChan chan struct{}
var ioInitOnce sync.Once
var ioCompletionPort syscall.Handle
var ioCompletionPort windows.Handle
// ioResult contains the result of an asynchronous IO operation.
type ioResult struct {
@ -60,12 +45,12 @@ type ioResult struct {
// ioOperation represents an outstanding asynchronous Win32 IO.
type ioOperation struct {
o syscall.Overlapped
o windows.Overlapped
ch chan ioResult
}
func initIO() {
h, err := createIoCompletionPort(syscall.InvalidHandle, 0, 0, 0xffffffff)
h, err := createIoCompletionPort(windows.InvalidHandle, 0, 0, 0xffffffff)
if err != nil {
panic(err)
}
@ -76,10 +61,10 @@ func initIO() {
// win32File implements Reader, Writer, and Closer on a Win32 handle without blocking in a syscall.
// It takes ownership of this handle and will close it if it is garbage collected.
type win32File struct {
handle syscall.Handle
handle windows.Handle
wg sync.WaitGroup
wgLock sync.RWMutex
closing atomicBool
closing atomic.Bool
socket bool
readDeadline deadlineHandler
writeDeadline deadlineHandler
@ -90,11 +75,11 @@ type deadlineHandler struct {
channel timeoutChan
channelLock sync.RWMutex
timer *time.Timer
timedout atomicBool
timedout atomic.Bool
}
// makeWin32File makes a new win32File from an existing file handle.
func makeWin32File(h syscall.Handle) (*win32File, error) {
func makeWin32File(h windows.Handle) (*win32File, error) {
f := &win32File{handle: h}
ioInitOnce.Do(initIO)
_, err := createIoCompletionPort(h, ioCompletionPort, 0, 0xffffffff)
@ -110,7 +95,12 @@ func makeWin32File(h syscall.Handle) (*win32File, error) {
return f, nil
}
// Deprecated: use NewOpenFile instead.
func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) {
return NewOpenFile(windows.Handle(h))
}
func NewOpenFile(h windows.Handle) (io.ReadWriteCloser, error) {
// If we return the result of makeWin32File directly, it can result in an
// interface-wrapped nil, rather than a nil interface value.
f, err := makeWin32File(h)
@ -124,13 +114,13 @@ func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) {
func (f *win32File) closeHandle() {
f.wgLock.Lock()
// Atomically set that we are closing, releasing the resources only once.
if !f.closing.swap(true) {
if !f.closing.Swap(true) {
f.wgLock.Unlock()
// cancel all IO and wait for it to complete
_ = cancelIoEx(f.handle, nil)
f.wg.Wait()
// at this point, no new IO can start
syscall.Close(f.handle)
windows.Close(f.handle)
f.handle = 0
} else {
f.wgLock.Unlock()
@ -145,14 +135,14 @@ func (f *win32File) Close() error {
// IsClosed checks if the file has been closed.
func (f *win32File) IsClosed() bool {
return f.closing.isSet()
return f.closing.Load()
}
// prepareIO prepares for a new IO operation.
// The caller must call f.wg.Done() when the IO is finished, prior to Close() returning.
func (f *win32File) prepareIO() (*ioOperation, error) {
f.wgLock.RLock()
if f.closing.isSet() {
if f.closing.Load() {
f.wgLock.RUnlock()
return nil, ErrFileClosed
}
@ -164,12 +154,12 @@ func (f *win32File) prepareIO() (*ioOperation, error) {
}
// ioCompletionProcessor processes completed async IOs forever.
func ioCompletionProcessor(h syscall.Handle) {
func ioCompletionProcessor(h windows.Handle) {
for {
var bytes uint32
var key uintptr
var op *ioOperation
err := getQueuedCompletionStatus(h, &bytes, &key, &op, syscall.INFINITE)
err := getQueuedCompletionStatus(h, &bytes, &key, &op, windows.INFINITE)
if op == nil {
panic(err)
}
@ -182,11 +172,11 @@ func ioCompletionProcessor(h syscall.Handle) {
// asyncIO processes the return value from ReadFile or WriteFile, blocking until
// the operation has actually completed.
func (f *win32File) asyncIO(c *ioOperation, d *deadlineHandler, bytes uint32, err error) (int, error) {
if err != syscall.ERROR_IO_PENDING { //nolint:errorlint // err is Errno
if err != windows.ERROR_IO_PENDING { //nolint:errorlint // err is Errno
return int(bytes), err
}
if f.closing.isSet() {
if f.closing.Load() {
_ = cancelIoEx(f.handle, &c.o)
}
@ -201,8 +191,8 @@ func (f *win32File) asyncIO(c *ioOperation, d *deadlineHandler, bytes uint32, er
select {
case r = <-c.ch:
err = r.err
if err == syscall.ERROR_OPERATION_ABORTED { //nolint:errorlint // err is Errno
if f.closing.isSet() {
if err == windows.ERROR_OPERATION_ABORTED { //nolint:errorlint // err is Errno
if f.closing.Load() {
err = ErrFileClosed
}
} else if err != nil && f.socket {
@ -214,7 +204,7 @@ func (f *win32File) asyncIO(c *ioOperation, d *deadlineHandler, bytes uint32, er
_ = cancelIoEx(f.handle, &c.o)
r = <-c.ch
err = r.err
if err == syscall.ERROR_OPERATION_ABORTED { //nolint:errorlint // err is Errno
if err == windows.ERROR_OPERATION_ABORTED { //nolint:errorlint // err is Errno
err = ErrTimeout
}
}
@ -235,23 +225,22 @@ func (f *win32File) Read(b []byte) (int, error) {
}
defer f.wg.Done()
if f.readDeadline.timedout.isSet() {
if f.readDeadline.timedout.Load() {
return 0, ErrTimeout
}
var bytes uint32
err = syscall.ReadFile(f.handle, b, &bytes, &c.o)
err = windows.ReadFile(f.handle, b, &bytes, &c.o)
n, err := f.asyncIO(c, &f.readDeadline, bytes, err)
runtime.KeepAlive(b)
// Handle EOF conditions.
if err == nil && n == 0 && len(b) != 0 {
return 0, io.EOF
} else if err == syscall.ERROR_BROKEN_PIPE { //nolint:errorlint // err is Errno
} else if err == windows.ERROR_BROKEN_PIPE { //nolint:errorlint // err is Errno
return 0, io.EOF
} else {
return n, err
}
return n, err
}
// Write writes to a file handle.
@ -262,12 +251,12 @@ func (f *win32File) Write(b []byte) (int, error) {
}
defer f.wg.Done()
if f.writeDeadline.timedout.isSet() {
if f.writeDeadline.timedout.Load() {
return 0, ErrTimeout
}
var bytes uint32
err = syscall.WriteFile(f.handle, b, &bytes, &c.o)
err = windows.WriteFile(f.handle, b, &bytes, &c.o)
n, err := f.asyncIO(c, &f.writeDeadline, bytes, err)
runtime.KeepAlive(b)
return n, err
@ -282,7 +271,7 @@ func (f *win32File) SetWriteDeadline(deadline time.Time) error {
}
func (f *win32File) Flush() error {
return syscall.FlushFileBuffers(f.handle)
return windows.FlushFileBuffers(f.handle)
}
func (f *win32File) Fd() uintptr {
@ -299,7 +288,7 @@ func (d *deadlineHandler) set(deadline time.Time) error {
}
d.timer = nil
}
d.timedout.setFalse()
d.timedout.Store(false)
select {
case <-d.channel:
@ -314,7 +303,7 @@ func (d *deadlineHandler) set(deadline time.Time) error {
}
timeoutIO := func() {
d.timedout.setTrue()
d.timedout.Store(true)
close(d.channel)
}

View File

@ -18,9 +18,18 @@ type FileBasicInfo struct {
_ uint32 // padding
}
// alignedFileBasicInfo is a FileBasicInfo, but aligned to uint64 by containing
// uint64 rather than windows.Filetime. Filetime contains two uint32s. uint64
// alignment is necessary to pass this as FILE_BASIC_INFO.
type alignedFileBasicInfo struct {
CreationTime, LastAccessTime, LastWriteTime, ChangeTime uint64
FileAttributes uint32
_ uint32 // padding
}
// GetFileBasicInfo retrieves times and attributes for a file.
func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) {
bi := &FileBasicInfo{}
bi := &alignedFileBasicInfo{}
if err := windows.GetFileInformationByHandleEx(
windows.Handle(f.Fd()),
windows.FileBasicInfo,
@ -30,16 +39,21 @@ func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) {
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
}
runtime.KeepAlive(f)
return bi, nil
// Reinterpret the alignedFileBasicInfo as a FileBasicInfo so it matches the
// public API of this module. The data may be unnecessarily aligned.
return (*FileBasicInfo)(unsafe.Pointer(bi)), nil
}
// SetFileBasicInfo sets times and attributes for a file.
func SetFileBasicInfo(f *os.File, bi *FileBasicInfo) error {
// Create an alignedFileBasicInfo based on a FileBasicInfo. The copy is
// suitable to pass to GetFileInformationByHandleEx.
biAligned := *(*alignedFileBasicInfo)(unsafe.Pointer(bi))
if err := windows.SetFileInformationByHandle(
windows.Handle(f.Fd()),
windows.FileBasicInfo,
(*byte)(unsafe.Pointer(bi)),
uint32(unsafe.Sizeof(*bi)),
(*byte)(unsafe.Pointer(&biAligned)),
uint32(unsafe.Sizeof(biAligned)),
); err != nil {
return &os.PathError{Op: "SetFileInformationByHandle", Path: f.Name(), Err: err}
}

Some files were not shown because too many files have changed in this diff Show More