Compare commits

..

56 Commits

Author SHA1 Message Date
62b36d2fbc Merge pull request #433 from ydcool/mips64le-support
add support for mips64le
2020-01-08 16:26:13 +00:00
e5fdd449dd Merge pull request #428 from weibeld/master
bridge: add missing cniVersion in README example
2020-01-08 16:24:48 +00:00
8db5e4d41b add support for mips64le
Signed-off-by: Dominic Yin <yindongchao@inspur.com>
2020-01-06 15:51:47 +08:00
ec8f6c99d0 Merge pull request #421 from aojea/portmapErrors2
Portmap doesn't fail if chain doesn't exist
2019-12-19 00:16:58 +08:00
7dea2a4c1b Add missing cniVersion in README example
Signed-off-by: Daniel Weibel <danielmweibel@gmail.com>
2019-12-18 19:08:37 +08:00
5a02c5bc61 bump go-iptables module to v0.4.5
bump the go-iptables module to v0.4.5 to avoid
concurrency issues with the portmap plugin and
errors related to iptables not able to hold the
lock.

Signed-off-by: Antonio Ojea <antonio.ojea.garcia@gmail.com>
2019-12-16 17:42:20 +01:00
bf8f171041 iptables: add idempotent functions
Add the following idempotent functions to iptables utils:

DeleteRule: idempotently delete an iptables rule
DeleteChain: idempotently delete an iptables chain
ClearChain: idempotently flush an iptables chain

Signed-off-by: Antonio Ojea <antonio.ojea.garcia@gmail.com>
2019-12-12 15:13:15 +01:00
3603738c6a portmap doesn't fail if chain doesn't exist
It turns out that the portmap plugin is not idempotent if its
executed in parallel.
The errors are caused due to a race of different instantiations
deleting the chains.
This patch does that the portmap plugin doesn't fail if the
errors are because the chain doesn't exist on teardown.

Signed-off-by: Antonio Ojea <antonio.ojea.garcia@gmail.com>
2019-12-12 09:03:06 +01:00
d8b1289098 fix portmap port forward flakiness
Use a Describe container for the It code block of the
portmap port forward integration test.

Signed-off-by: Antonio Ojea <antonio.ojea.garcia@gmail.com>
2019-12-12 09:03:06 +01:00
6551165853 Merge pull request #412 from containernetworking/new-maintainers
Add Bruce Ma and Piotr Skarmuk as owners
2019-12-04 10:11:31 -06:00
10a01b09ae Add Bruce Ma and Piotr Skarmuk as owners
Signed-off-by: Bryan Boreham <bryan@weave.works>
2019-11-16 11:45:44 +00:00
497560f35f Merge pull request #408 from tgross/idempotent_chain_creation
ensure iptables chain creation is idempotent
2019-11-13 17:20:45 +01:00
58dd90b996 ensure iptables chain creation is idempotent
Concurrent use of the `portmap` and `firewall` plugins can result in
errors during iptables chain creation:

- The `portmap` plugin has a time-of-check-time-of-use race where it
  checks for existence of the chain but the operation isn't atomic.
- The `firewall` plugin doesn't check for existing chains and just
  returns an error.

This commit makes both operations idempotent by creating the chain and
then discarding the error if it's caused by the chain already
existing. It also factors the chain creation out into `pkg/utils` as a
site for future refactoring work.

Signed-off-by: Tim Gross <tim@0x74696d.com>
2019-11-11 10:00:11 -05:00
d5efdfe1f6 Merge pull request #409 from squeed/fix-integ-tests
integration: fix ip address collision in integration tests
2019-11-11 14:07:52 +01:00
05f121a406 integration: fix ip address collision in integration tests
Signed-off-by: Casey Callendrello <cdc@redhat.com>
2019-11-11 13:36:21 +01:00
825fbd8a95 Merge pull request #405 from mars1024/feat/vlan_mtu_validation
vlan: add MTU validation to loadNetConf
2019-11-06 16:29:57 +00:00
1a30688da0 add some testcases about invalid MTUs
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-10-25 20:15:18 +08:00
bee8d6cf30 vlan: add MTU validation in loadNetConf
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-10-25 19:59:33 +08:00
a16232968d Merge pull request #400 from s1061123/fix/overwrite-ips
static: prioritize the input sources for IPs
2019-10-23 16:54:54 +01:00
1880421389 Merge pull request #401 from giuseppe/run-in-a-userns
testutils: newNS() works in a rootless user namespace
2019-10-23 16:29:59 +01:00
a2ed3d9a69 Merge pull request #403 from s1061123/dev/addgarp
Sending GratuitousArp in case of MAC address update
2019-10-23 16:24:58 +01:00
7bcaae263f Merge pull request #404 from mars1024/feat/mtu_validation
macvlan: add MTU validation to loadNetConf
2019-10-23 16:13:06 +01:00
e1f955d9bf macvlan: add MTU validation to loadNetConf
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-10-23 20:39:14 +08:00
2583a0b4ad Sending GratuitousArp in case of MAC address update
This change sends gratuitous ARP when MAC address is changed to
let other devices to know the MAC address update.

Signed-off-by: Tomofumi Hayashi <tohayash@redhat.com>
2019-10-23 15:17:38 +09:00
85083ea434 testutils: newNS() works in a rootless user namespace
When running in a user namespace created by an unprivileged user the
owner of /var/run will be reported as the unknown user (as defined in
/proc/sys/kernel/overflowuid) so any access to the directory will
fail.

If the XDG_RUNTIME_DIR environment variable is set, check whether the
current user is also the owner of /var/run.  If the owner is different
than the current user, use the $XDG_RUNTIME_DIR/netns directory.

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2019-10-19 12:04:53 +02:00
2290fc8d8a static: prioritize the input sources for IPs
This change introduce priorities for IPs input among CNI_ARGS,
'args' and runtimeConfig. Fix #399.

Signed-off-by: Tomofumi Hayashi <tohayash@redhat.com>
2019-10-17 13:36:49 +09:00
411d060b81 Merge pull request #389 from CallMeFoxie/bw-units
Use uint64 for Bandwidth plugin
2019-10-09 16:25:06 +01:00
5915b49b38 Merge pull request #394 from mars1024/bugfix/validate_vlanid
bridge: check vlan id when loading net conf
2019-10-09 17:23:47 +02:00
c25c62742b Merge pull request #396 from oshothebig/contributing-doc
contributing doc: revise test script name to run
2019-10-09 10:21:03 -05:00
b7ffa24326 vlan/bridge: fix some typo
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-10-08 11:57:30 +08:00
15894b36a0 Merge pull request #397 from oshothebig/install-cnitool
contributing doc: describe cnitool installation
2019-10-07 14:27:23 +02:00
77b51f0bc9 contributing doc: describe cnitool installation
cnitool must be installed before running tests because cnitool is
invoked during the tests

Signed-off-by: Sho SHIMIZU <sho.shimizu@gmail.com>
2019-10-07 17:42:44 +09:00
bd63528b0b contributing doc: revise test script name to run
test.sh doesn't exists now as it was separated into two OS-specific
scripts in 4e1f7802db.

Signed-off-by: Sho SHIMIZU <sho.shimizu@gmail.com>
2019-10-04 18:32:12 +09:00
cf187287af Update tests for uint64
Signed-off-by: Ashley Reese <ashley@victorianfox.com>
2019-10-03 16:55:41 +02:00
0dff883769 Use uint64 for Bandwidth plugin
Signed-off-by: Ashley Reese <ashley@victorianfox.com>
2019-10-03 16:05:27 +02:00
d0eeb27494 Merge pull request #390 from sipsma/firewall-fix
firewall: don't return error in DEL if prevResult is not found.
2019-10-02 10:38:47 -05:00
e70558cbe1 bridge: check vlan id when loading net conf
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-09-30 17:12:31 +08:00
0a1421a08c firewall: remove unused netns check from DEL method
Signed-off-by: Erik Sipsma <sipsma@amazon.com>
2019-09-25 20:38:02 +00:00
0f19aa2f8d Merge pull request #388 from sipsma/fix-ptpdns
ptp: only override DNS conf if DNS settings provided
2019-09-25 17:43:24 +02:00
e91889678b Merge pull request #391 from beautytiger/dev-190925
bugfix: defer after err check, or it may panic
2019-09-25 16:25:21 +01:00
8ec6bd6a42 bugfix: defer after err check, or it may panic
Signed-off-by: Guangming Wang <guangming.wang@daocloud.io>
2019-09-25 22:21:49 +08:00
fc7059c1ae firewall: don't return error in DEL if prevResult is not found.
The CNI spec states that for DEL implementations, "when CNI_NETNS and/or
prevResult are not provided, the plugin should clean up as many resources as
possible (e.g. releasing IPAM allocations) and return a successful response".
This change results in the firewall plugin conforming to the spec by not
returning an error whenever the del method is not provided a prevResult.

Signed-off-by: Erik Sipsma <sipsma@amazon.com>
2019-09-23 21:11:07 +00:00
a96c469e62 ptp: only override DNS conf if DNS settings provided
Previously, if an IPAM plugin provided DNS settings in the result to the PTP
plugin, those settings were always lost because the PTP plugin would always
provide its own DNS settings in the result even if the PTP plugin was not
configured with any DNS settings.

This was especially problematic when trying to use, for example, the host-local
IPAM plugin's support for retrieving DNS settings from a resolv.conf file on
the host. Before this change, those DNS settings were always lost when using the
PTP plugin and couldn't be specified as part of PTP instead because PTP does not
support parsing a resolv.conf file.

This change checks to see if any fields were actually set in the PTP plugin's
DNS settings and only overrides any previous DNS results from an IPAM plugin in
the case that settings actually were provided to PTP. In the case where no
DNS settings are provided to PTP, the DNS results of the IPAM plugin (if any)
are used instead.

Signed-off-by: Erik Sipsma <sipsma@amazon.com>
2019-09-18 21:09:22 +00:00
291ab6cc84 Merge pull request #386 from janisz/patch-1
Bump Go version
2019-09-18 17:12:43 +02:00
90125f40ba Bump Go version
Signed-off-by: Tomasz Janiszewski <janiszt@gmail.com>
2019-09-18 11:20:33 +02:00
23d5525ec3 Merge pull request #383 from mccv1r0/issue381
When prevResults are not supplied to loopback plugin, create results to return
2019-09-11 11:11:55 -05:00
fd42109a06 When prevResults are not returned to loopback plugin, create results to return based on
the lo interface and IP address assigned inside container.

Signed-off-by: Michael Cambria <mcambria@redhat.com>
2019-09-11 11:57:03 -04:00
4bb288193c Merge pull request #379 from xcelsion/fix-host-container-address-family-mismatch
Fix dual-stack support in meta/portmap
2019-09-11 17:16:36 +02:00
e8365e126d Fixed issue where hostIP address family was not checked against the containerIP address family. closes #378
Signed-off-by: Niels van Oosterom <xcelsion@users.noreply.github.com>
2019-09-06 15:23:00 +02:00
7e68430081 Merge pull request #377 from mars1024/bumpup/to/0.7.1
bump up libcni to v0.7.1
2019-08-28 10:55:43 -05:00
f81a529ebd Merge pull request #375 from smarkm/master
Fixes #342, cleanup netns after test suite
2019-08-28 17:55:36 +02:00
630a4d8db6 Merge pull request #374 from mars1024/feat/loopback_support_check
loopback support CNI CHECK and result cache
2019-08-28 10:53:43 -05:00
3d56f7504d loopback plugin support to pass previous result transpartently
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-08-23 22:56:22 +08:00
659a09f34e loopback support CNI CHECK
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-08-23 20:36:37 +08:00
b76ace9c64 bump up libcni to v0.7.1
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-08-23 20:24:16 +08:00
0d0dcfc02f Cleanup netns after test suit
Signed-off-by: smarkm <smark@freecoop.net>
2019-08-22 08:10:35 +08:00
47 changed files with 1206 additions and 314 deletions

View File

@ -1,10 +1,11 @@
language: go
sudo: required
dist: trusty
dist: xenial
go:
- 1.11.x
- 1.12.x
- 1.13.x
env:
global:
@ -17,6 +18,7 @@ env:
- TARGET=arm64
- TARGET=ppc64le
- TARGET=s390x
- TARGET=mips64le
matrix:
fast_finish: true

View File

@ -72,10 +72,11 @@ vagrant ssh
# you're now in a shell in a virtual machine
sudo su
go get github.com/onsi/ginkgo/ginkgo
go install github.com/containernetworking/cni/cnitool
cd /go/src/github.com/containernetworking/plugins
# to run the full test suite
./test.sh
./test_linux.sh
# to focus on a particular test suite
cd plugins/main/loopback

View File

@ -1,8 +1,10 @@
# Owners
This is the official list of the CNI network plugins owners:
- Bruce Ma <brucema19901024@gmail.com> (@mars1024)
- Bryan Boreham <bryan@weave.works> (@bboreham)
- Casey Callendrello <casey.callendrello@coreos.com> (@squeed)
- Dan Williams <dcbw@redhat.com> (@dcbw)
- Gabe Rosenhouse <grosenhouse@pivotal.io> (@rosenhouse)
- Matt Dupre <matt@tigera.io> (@matthewdupre)
- Piotr Skarmuk <piotr.skarmuk@gmail.com> (@jellonek)
- Stefan Junker <stefan.junker@coreos.com> (@steveeJ)

4
go.mod
View File

@ -7,8 +7,8 @@ require (
github.com/Microsoft/hcsshim v0.8.6
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44
github.com/containernetworking/cni v0.7.0
github.com/coreos/go-iptables v0.4.2
github.com/containernetworking/cni v0.7.1
github.com/coreos/go-iptables v0.4.5
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c
github.com/d2g/dhcp4client v1.0.0

8
go.sum
View File

@ -6,10 +6,14 @@ github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae h1:AMzIhMUq
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0=
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44 h1:y853v6rXx+zefEcjET3JuKAqvhj+FKflQijjeaSv2iA=
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/containernetworking/cni v0.7.0 h1:1Qy7EwdC08mx5wUB0DpjCuBrk6e/uXg9yI9TvAvgox8=
github.com/containernetworking/cni v0.7.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
github.com/containernetworking/cni v0.7.1 h1:fE3r16wpSEyaqY4Z4oFrLMmIGfBYIKpPrHK31EJ9FzE=
github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
github.com/coreos/go-iptables v0.4.2 h1:KH0EwId05JwWIfb96gWvkiT2cbuOu8ygqUaB+yPAwIg=
github.com/coreos/go-iptables v0.4.2/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
github.com/coreos/go-iptables v0.4.4 h1:5oOUvU7Fk53Hn/rkdJ0zcYGCffotqXpyi4ADCkO1TJ8=
github.com/coreos/go-iptables v0.4.4/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
github.com/coreos/go-iptables v0.4.5 h1:DpHb9vJrZQEFMcVLFKAAGMUVX0XoRC0ptCthinRYm38=
github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7 h1:u9SHYsPQNyt5tgDm3YN7+9dYrpK96E5wFilTFWIDZOM=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c h1:Xo2rK1pzOm0jO6abTPIQwbAmqBIOj132otexc1mmzFc=

View File

@ -149,12 +149,12 @@ var _ = Describe("Basic PTP using cnitool", func() {
})
Measure("limits traffic only on the restricted bandwith veth device", func(b Benchmarker) {
ipRegexp := regexp.MustCompile("10\\.11\\.2\\.\\d{1,3}")
ipRegexp := regexp.MustCompile("10\\.1[12]\\.2\\.\\d{1,3}")
By(fmt.Sprintf("adding %s to %s\n\n", "chained-bridge-bandwidth", contNS1.ShortName()))
chainedBridgeBandwidthEnv.runInNS(hostNS, cnitoolBin, "add", "network-chain-test", contNS1.LongName())
chainedBridgeIP := ipRegexp.FindString(chainedBridgeBandwidthEnv.runInNS(contNS1, "ip", "addr"))
Expect(chainedBridgeIP).To(ContainSubstring("10.11.2."))
Expect(chainedBridgeIP).To(ContainSubstring("10.12.2."))
By(fmt.Sprintf("adding %s to %s\n\n", "basic-bridge", contNS2.ShortName()))
basicBridgeEnv.runInNS(hostNS, cnitoolBin, "add", "network-chain-test", contNS2.LongName())

View File

@ -4,12 +4,12 @@
"plugins": [
{
"type": "bridge",
"bridge": "test-bridge-0",
"bridge": "test-bridge-1",
"isDefaultGateway": true,
"ipam": {
"type": "host-local",
"subnet": "10.11.2.0/24",
"dataDir": "/tmp/foo"
"subnet": "10.12.2.0/24",
"dataDir": "/tmp/bar"
}
},
{

60
pkg/testutils/dns.go Normal file
View File

@ -0,0 +1,60 @@
// Copyright 2019 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 testutils
import (
"fmt"
"io/ioutil"
"os"
"strings"
"github.com/containernetworking/cni/pkg/types"
)
// TmpResolvConf will create a temporary file and write the provided DNS settings to
// it in the resolv.conf format. It returns the path of the created temporary file or
// an error if any occurs while creating/writing the file. It is the caller's
// responsibility to remove the file.
func TmpResolvConf(dnsConf types.DNS) (string, error) {
f, err := ioutil.TempFile("", "cni_test_resolv.conf")
if err != nil {
return "", fmt.Errorf("failed to get temp file for CNI test resolv.conf: %v", err)
}
defer f.Close()
path := f.Name()
defer func() {
if err != nil {
os.RemoveAll(path)
}
}()
// see "man 5 resolv.conf" for the format of resolv.conf
var resolvConfLines []string
for _, nameserver := range dnsConf.Nameservers {
resolvConfLines = append(resolvConfLines, fmt.Sprintf("nameserver %s", nameserver))
}
resolvConfLines = append(resolvConfLines, fmt.Sprintf("domain %s", dnsConf.Domain))
resolvConfLines = append(resolvConfLines, fmt.Sprintf("search %s", strings.Join(dnsConf.Search, " ")))
resolvConfLines = append(resolvConfLines, fmt.Sprintf("options %s", strings.Join(dnsConf.Options, " ")))
resolvConf := strings.Join(resolvConfLines, "\n")
_, err = f.Write([]byte(resolvConf))
if err != nil {
return "", fmt.Errorf("failed to write temp resolv.conf for CNI test: %v", err)
}
return path, err
}

View File

@ -22,17 +22,36 @@ import (
"runtime"
"strings"
"sync"
"syscall"
"github.com/containernetworking/plugins/pkg/ns"
"golang.org/x/sys/unix"
)
const nsRunDir = "/var/run/netns"
func getNsRunDir() string {
xdgRuntimeDir := os.Getenv("XDG_RUNTIME_DIR")
/// If XDG_RUNTIME_DIR is set, check if the current user owns /var/run. If
// the owner is different, we are most likely running in a user namespace.
// In that case use $XDG_RUNTIME_DIR/netns as runtime dir.
if xdgRuntimeDir != "" {
if s, err := os.Stat("/var/run"); err == nil {
st, ok := s.Sys().(*syscall.Stat_t)
if ok && int(st.Uid) != os.Geteuid() {
return path.Join(xdgRuntimeDir, "netns")
}
}
}
return "/var/run/netns"
}
// Creates a new persistent (bind-mounted) network namespace and returns an object
// representing that namespace, without switching to it.
func NewNS() (ns.NetNS, error) {
nsRunDir := getNsRunDir()
b := make([]byte, 16)
_, err := rand.Reader.Read(b)
if err != nil {
@ -135,7 +154,7 @@ func NewNS() (ns.NetNS, error) {
func UnmountNS(ns ns.NetNS) error {
nsPath := ns.Path()
// Only unmount if it's been bind-mounted (don't touch namespaces in /proc...)
if strings.HasPrefix(nsPath, nsRunDir) {
if strings.HasPrefix(nsPath, getNsRunDir()) {
if err := unix.Unmount(nsPath, 0); err != nil {
return fmt.Errorf("failed to unmount NS: at %s: %v", nsPath, err)
}

121
pkg/utils/iptables.go Normal file
View File

@ -0,0 +1,121 @@
// 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 utils
import (
"errors"
"fmt"
"github.com/coreos/go-iptables/iptables"
)
const statusChainExists = 1
// EnsureChain idempotently creates the iptables chain. It does not
// return an error if the chain already exists.
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)
if err != nil {
return fmt.Errorf("failed to list iptables chains: %v", err)
}
if !exists {
err = ipt.NewChain(table, chain)
if err != nil {
eerr, eok := err.(*iptables.Error)
if eok && eerr.ExitStatus() != statusChainExists {
return err
}
}
}
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 {
if ipt == nil {
return errors.New("failed to ensure iptable chain: IPTables was nil")
}
if err := ipt.Delete(table, chain, rulespec...); err != nil {
eerr, eok := err.(*iptables.Error)
switch {
case eok && eerr.IsNotExist():
// swallow here, the chain was already deleted
return nil
case eok && eerr.ExitStatus() == 2:
// swallow here, invalid command line parameter because the referring rule is missing
return nil
default:
return fmt.Errorf("Failed to delete referring rule %s %s: %v", table, chain, err)
}
}
return nil
}
// DeleteChain idempotently deletes the specified table/chain.
// It does not return an errors if the chain does not exist
func DeleteChain(ipt *iptables.IPTables, table, chain string) error {
if ipt == nil {
return errors.New("failed to ensure iptable chain: IPTables was nil")
}
err := ipt.DeleteChain(table, chain)
eerr, eok := err.(*iptables.Error)
switch {
case eok && eerr.IsNotExist():
// swallow here, the chain was already deleted
return nil
default:
return err
}
}
// ClearChain idempotently clear the iptables rules in the specified table/chain.
// If the chain does not exist, a new one will be created
func ClearChain(ipt *iptables.IPTables, table, chain string) error {
if ipt == nil {
return errors.New("failed to ensure iptable chain: IPTables was nil")
}
err := ipt.ClearChain(table, chain)
eerr, eok := err.(*iptables.Error)
switch {
case eok && eerr.IsNotExist():
// swallow here, the chain was already deleted
return EnsureChain(ipt, table, chain)
default:
return err
}
}

View File

@ -0,0 +1,97 @@
// Copyright 2017-2018 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 (
"fmt"
"math/rand"
"runtime"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils"
"github.com/coreos/go-iptables/iptables"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
const TABLE = "filter" // We'll monkey around here
var _ = Describe("chain tests", func() {
var testChain string
var ipt *iptables.IPTables
var cleanup func()
BeforeEach(func() {
// Save a reference to the original namespace,
// Add a new NS
currNs, err := ns.GetCurrentNS()
Expect(err).NotTo(HaveOccurred())
testNs, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
testChain = fmt.Sprintf("cni-test-%d", rand.Intn(10000000))
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
Expect(err).NotTo(HaveOccurred())
runtime.LockOSThread()
err = testNs.Set()
Expect(err).NotTo(HaveOccurred())
cleanup = func() {
if ipt == nil {
return
}
ipt.ClearChain(TABLE, testChain)
ipt.DeleteChain(TABLE, testChain)
currNs.Set()
}
})
AfterEach(func() {
cleanup()
})
Describe("EnsureChain", func() {
It("creates chains idempotently", func() {
err := EnsureChain(ipt, TABLE, testChain)
Expect(err).NotTo(HaveOccurred())
// Create it again!
err = EnsureChain(ipt, TABLE, testChain)
Expect(err).NotTo(HaveOccurred())
})
})
Describe("DeleteChain", func() {
It("delete chains idempotently", func() {
// Create chain
err := EnsureChain(ipt, TABLE, testChain)
Expect(err).NotTo(HaveOccurred())
// Delete chain
err = DeleteChain(ipt, TABLE, testChain)
Expect(err).NotTo(HaveOccurred())
// Delete it again!
err = DeleteChain(ipt, TABLE, testChain)
Expect(err).NotTo(HaveOccurred())
})
})
})

View File

@ -65,12 +65,11 @@ options four
func parse(contents string) (*types.DNS, error) {
f, err := ioutil.TempFile("", "host_local_resolv")
defer f.Close()
defer os.Remove(f.Name())
if err != nil {
return nil, err
}
defer f.Close()
defer os.Remove(f.Name())
if _, err := f.WriteString(contents); err != nil {
return nil, err

View File

@ -60,3 +60,9 @@ The plugin also support following [capability argument](https://github.com/conta
The following [args conventions](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md#args-in-network-config) are supported:
* `ips` (array of strings): A list of custom IPs to attempt to allocate, with prefix (e.g. '10.10.0.1/24')
Notice: If some of above are used at same time, only one will work according to the priorities below
1. [capability argument](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md)
1. [args conventions](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md#args-in-network-config)
1. [CNI_ARGS](https://github.com/containernetworking/cni/blob/master/SPEC.md#parameters)

View File

@ -145,11 +145,44 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
return nil, "", err
}
if len(n.RuntimeConfig.IPs) != 0 {
// args IP overwrites IP, so clear IPAM Config
n.IPAM.Addresses = make([]Address, 0, len(n.RuntimeConfig.IPs))
for _, addr := range n.RuntimeConfig.IPs {
n.IPAM.Addresses = append(n.IPAM.Addresses, Address{AddressStr: addr})
// load IP from CNI_ARGS
if envArgs != "" {
e := IPAMEnvArgs{}
err := types.LoadArgs(envArgs, &e)
if err != nil {
return nil, "", err
}
if e.IP != "" {
for _, item := range strings.Split(string(e.IP), ",") {
ipstr := strings.TrimSpace(item)
ip, subnet, err := net.ParseCIDR(ipstr)
if err != nil {
return nil, "", fmt.Errorf("invalid CIDR %s: %s", ipstr, err)
}
addr := Address{
Address: net.IPNet{IP: ip, Mask: subnet.Mask},
AddressStr: ipstr,
}
n.IPAM.Addresses = append(n.IPAM.Addresses, addr)
}
}
if e.GATEWAY != "" {
for _, item := range strings.Split(string(e.GATEWAY), ",") {
gwip := net.ParseIP(strings.TrimSpace(item))
if gwip == nil {
return nil, "", fmt.Errorf("invalid gateway address: %s", item)
}
for i := range n.IPAM.Addresses {
if n.IPAM.Addresses[i].Address.Contains(gwip) {
n.IPAM.Addresses[i].Gateway = gwip
}
}
}
}
}
@ -162,6 +195,15 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
}
}
// import address from runtimeConfig
if len(n.RuntimeConfig.IPs) != 0 {
// runtimeConfig IP overwrites IP, so clear IPAM Config
n.IPAM.Addresses = make([]Address, 0, len(n.RuntimeConfig.IPs))
for _, addr := range n.RuntimeConfig.IPs {
n.IPAM.Addresses = append(n.IPAM.Addresses, Address{AddressStr: addr})
}
}
if n.IPAM == nil {
return nil, "", fmt.Errorf("IPAM config missing 'ipam' key")
}
@ -191,50 +233,6 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
}
}
if envArgs != "" {
e := IPAMEnvArgs{}
err := types.LoadArgs(envArgs, &e)
if err != nil {
return nil, "", err
}
if e.IP != "" {
for _, item := range strings.Split(string(e.IP), ",") {
ipstr := strings.TrimSpace(item)
ip, subnet, err := net.ParseCIDR(ipstr)
if err != nil {
return nil, "", fmt.Errorf("invalid CIDR %s: %s", ipstr, err)
}
addr := Address{Address: net.IPNet{IP: ip, Mask: subnet.Mask}}
if addr.Address.IP.To4() != nil {
addr.Version = "4"
numV4++
} else {
addr.Version = "6"
numV6++
}
n.IPAM.Addresses = append(n.IPAM.Addresses, addr)
}
}
if e.GATEWAY != "" {
for _, item := range strings.Split(string(e.GATEWAY), ",") {
gwip := net.ParseIP(strings.TrimSpace(item))
if gwip == nil {
return nil, "", fmt.Errorf("invalid gateway address: %s", item)
}
for i := range n.IPAM.Addresses {
if n.IPAM.Addresses[i].Address.Contains(gwip) {
n.IPAM.Addresses[i].Gateway = gwip
}
}
}
}
}
// CNI spec 0.2.0 and below supported only one v4 and v6 address
if numV4 > 1 || numV6 > 1 {
for _, v := range types020.SupportedVersions {

View File

@ -404,6 +404,82 @@ var _ = Describe("static Operations", func() {
})
Expect(err).NotTo(HaveOccurred())
})
It("allocates and releases multiple addresses with ADD/DEL, from RuntimeConfig/ARGS/CNI_ARGS", func() {
const ifname string = "eth0"
const nspath string = "/some/where"
conf := `{
"cniVersion": "0.3.1",
"name": "mynet",
"type": "ipvlan",
"master": "foo0",
"capabilities": {"ips": true},
"ipam": {
"type": "static",
"routes": [
{ "dst": "0.0.0.0/0", "gw": "10.10.0.254" },
{ "dst": "3ffe:ffff:0:01ff::1/64",
"gw": "3ffe:ffff:0::1" } ],
"dns": {
"nameservers" : ["8.8.8.8"],
"domain": "example.com",
"search": [ "example.com" ]
}
},
"RuntimeConfig": {
"ips" : ["10.10.0.1/24", "3ffe:ffff:0:01ff::1/64"]
},
"args": {
"cni": {
"ips" : ["10.10.0.2/24", "3ffe:ffff:0:01ff::2/64"]
}
}
}`
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: nspath,
IfName: ifname,
StdinData: []byte(conf),
Args: "IP=10.10.0.3/24,11.11.0.3/24;GATEWAY=10.10.0.254",
}
// Allocate the IP
r, raw, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
result, err := current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
// only addresses in runtimeConfig configured because of its priorities
Expect(*result.IPs[0]).To(Equal(
current.IPConfig{
Version: "4",
Address: mustCIDR("10.10.0.1/24"),
}))
Expect(*result.IPs[1]).To(Equal(
current.IPConfig{
Version: "6",
Address: mustCIDR("3ffe:ffff:0:01ff::1/64"),
},
))
Expect(len(result.IPs)).To(Equal(2))
Expect(result.Routes).To(Equal([]*types.Route{
{Dst: mustCIDR("0.0.0.0/0"), GW: net.ParseIP("10.10.0.254")},
{Dst: mustCIDR("3ffe:ffff:0:01ff::1/64"), GW: net.ParseIP("3ffe:ffff:0::1")},
}))
// Release the IP
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
})
})
func mustCIDR(s string) net.IPNet {

View File

@ -14,17 +14,18 @@ If the bridge is missing, the plugin will create one on first use and, if gatewa
## Example configuration
```
{
"name": "mynet",
"type": "bridge",
"bridge": "mynet0",
"isDefaultGateway": true,
"forceAddress": false,
"ipMasq": true,
"hairpinMode": true,
"ipam": {
"type": "host-local",
"subnet": "10.10.0.0/16"
}
"cniVersion": "0.3.1",
"name": "mynet",
"type": "bridge",
"bridge": "mynet0",
"isDefaultGateway": true,
"forceAddress": false,
"ipMasq": true,
"hairpinMode": true,
"ipam": {
"type": "host-local",
"subnet": "10.10.0.0/16"
}
}
```
@ -32,10 +33,10 @@ If the bridge is missing, the plugin will create one on first use and, if gatewa
```
{
"cniVersion": "0.3.1",
"name": "mynet",
"type": "bridge",
"bridge": "mynet0",
"ipam": {}
"name": "mynet",
"type": "bridge",
"bridge": "mynet0",
"ipam": {}
}
```
@ -56,4 +57,4 @@ If the bridge is missing, the plugin will create one on first use and, if gatewa
*Note:* The VLAN parameter configures the VLAN tag on the host end of the veth and also enables the vlan_filtering feature on the bridge interface.
*Note:* To configure uplink for L2 network you need to allow the vlan on the uplink interface by using the following command ``` bridge vlan add vid VLAN_ID dev DEV```.
*Note:* To configure uplink for L2 network you need to allow the vlan on the uplink interface by using the following command ``` bridge vlan add vid VLAN_ID dev DEV```.

View File

@ -75,6 +75,9 @@ func loadNetConf(bytes []byte) (*NetConf, string, error) {
if err := json.Unmarshal(bytes, n); err != nil {
return nil, "", fmt.Errorf("failed to load netconf: %v", err)
}
if n.Vlan < 0 || n.Vlan > 4094 {
return nil, "", fmt.Errorf("invalid VLAN ID %d (must be between 0 and 4094)", n.Vlan)
}
return n, n.CNIVersion, nil
}

View File

@ -27,7 +27,7 @@ import (
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/020"
types020 "github.com/containernetworking/cni/pkg/types/020"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils"
@ -1125,6 +1125,7 @@ var _ = Describe("bridge Operations", func() {
AfterEach(func() {
Expect(os.RemoveAll(dataDir)).To(Succeed())
Expect(originalNS.Close()).To(Succeed())
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
})
It("creates a bridge", func() {
@ -1644,4 +1645,48 @@ var _ = Describe("bridge Operations", func() {
})
Expect(err).NotTo(HaveOccurred())
})
It("check vlan id when loading net conf", func() {
tests := []struct {
tc testCase
err error
}{
{
tc: testCase{
cniVersion: "0.4.0",
},
err: nil,
},
{
tc: testCase{
cniVersion: "0.4.0",
vlan: 0,
},
err: nil,
},
{
tc: testCase{
cniVersion: "0.4.0",
vlan: -100,
},
err: fmt.Errorf("invalid VLAN ID -100 (must be between 0 and 4094)"),
},
{
tc: testCase{
cniVersion: "0.4.0",
vlan: 5000,
},
err: fmt.Errorf("invalid VLAN ID 5000 (must be between 0 and 4094)"),
},
}
for _, test := range tests {
_, _, err := loadNetConf([]byte(test.tc.netConfJSON("")))
if test.err == nil {
Expect(err).To(BeNil())
} else {
Expect(err).To(Equal(test.err))
}
}
})
})

View File

@ -233,7 +233,8 @@ var _ = Describe("base functionality", func() {
})
AfterEach(func() {
originalNS.Close()
Expect(originalNS.Close()).To(Succeed())
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
})
It("Works with a valid config without IPAM", func() {

View File

@ -297,6 +297,7 @@ var _ = Describe("ipvlan Operations", func() {
AfterEach(func() {
Expect(originalNS.Close()).To(Succeed())
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
})
It("creates an ipvlan link in a non-default namespace", func() {

View File

@ -15,9 +15,15 @@
package main
import (
"encoding/json"
"errors"
"fmt"
"net"
"github.com/vishvananda/netlink"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/cni/pkg/version"
@ -25,9 +31,34 @@ import (
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
func parseNetConf(bytes []byte) (*types.NetConf, error) {
conf := &types.NetConf{}
if err := json.Unmarshal(bytes, conf); err != nil {
return nil, fmt.Errorf("failed to parse network config: %v", err)
}
if conf.RawPrevResult != nil {
if err := version.ParsePrevResult(conf); err != nil {
return nil, fmt.Errorf("failed to parse prevResult: %v", err)
}
if _, err := current.NewResultFromResult(conf.PrevResult); err != nil {
return nil, fmt.Errorf("failed to convert result to current version: %v", err)
}
}
return conf, nil
}
func cmdAdd(args *skel.CmdArgs) error {
conf, err := parseNetConf(args.StdinData)
if err != nil {
return err
}
var v4Addr, v6Addr *net.IPNet
args.IfName = "lo" // ignore config, this only works for loopback
err := ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
link, err := netlink.LinkByName(args.IfName)
if err != nil {
return err // not tested
@ -37,6 +68,33 @@ func cmdAdd(args *skel.CmdArgs) error {
if err != nil {
return err // not tested
}
v4Addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
if err != nil {
return err // not tested
}
if len(v4Addrs) != 0 {
v4Addr = v4Addrs[0].IPNet
// sanity check that this is a loopback address
for _, addr := range v4Addrs {
if !addr.IP.IsLoopback() {
return fmt.Errorf("loopback interface found with non-loopback address %q", addr.IP)
}
}
}
v6Addrs, err := netlink.AddrList(link, netlink.FAMILY_V6)
if err != nil {
return err // not tested
}
if len(v6Addrs) != 0 {
v6Addr = v6Addrs[0].IPNet
// sanity check that this is a loopback address
for _, addr := range v4Addrs {
if !addr.IP.IsLoopback() {
return fmt.Errorf("loopback interface found with non-loopback address %q", addr.IP)
}
}
}
return nil
})
@ -44,8 +102,35 @@ func cmdAdd(args *skel.CmdArgs) error {
return err // not tested
}
result := current.Result{}
return result.Print()
var result types.Result
if conf.PrevResult != nil {
// If loopback has previous result which passes from previous CNI plugin,
// loopback should pass it transparently
result = conf.PrevResult
} else {
loopbackInterface := &current.Interface{Name: args.IfName, Mac: "00:00:00:00:00:00", Sandbox: args.Netns}
r := &current.Result{CNIVersion: conf.CNIVersion, Interfaces: []*current.Interface{loopbackInterface}}
if v4Addr != nil {
r.IPs = append(r.IPs, &current.IPConfig{
Version: "4",
Interface: current.Int(0),
Address: *v4Addr,
})
}
if v6Addr != nil {
r.IPs = append(r.IPs, &current.IPConfig{
Version: "6",
Interface: current.Int(0),
Address: *v6Addr,
})
}
result = r
}
return types.PrintResult(result, conf.CNIVersion)
}
func cmdDel(args *skel.CmdArgs) error {
@ -78,6 +163,18 @@ func main() {
}
func cmdCheck(args *skel.CmdArgs) error {
// TODO: implement
return nil
args.IfName = "lo" // ignore config, this only works for loopback
return ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
link, err := netlink.LinkByName(args.IfName)
if err != nil {
return err
}
if link.Attrs().Flags&net.FlagUp != net.FlagUp {
return errors.New("loopback interface is down")
}
return nil
})
}

View File

@ -49,17 +49,16 @@ var _ = Describe("Loopback", func() {
fmt.Sprintf("CNI_ARGS=%s", "none"),
fmt.Sprintf("CNI_PATH=%s", "/some/test/path"),
}
command.Stdin = strings.NewReader(`{ "cniVersion": "0.1.0" }`)
command.Stdin = strings.NewReader(`{ "name": "loopback-test", "cniVersion": "0.1.0" }`)
})
AfterEach(func() {
Expect(networkNS.Close()).To(Succeed())
Expect(testutils.UnmountNS(networkNS)).To(Succeed())
})
Context("when given a network namespace", func() {
It("sets the lo device to UP", func() {
Skip("TODO: add network name")
command.Env = append(environ, fmt.Sprintf("CNI_COMMAND=%s", "ADD"))
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
@ -80,8 +79,6 @@ var _ = Describe("Loopback", func() {
})
It("sets the lo device to DOWN", func() {
Skip("TODO: add network name")
command.Env = append(environ, fmt.Sprintf("CNI_COMMAND=%s", "DEL"))
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)

View File

@ -23,9 +23,9 @@ Since each macvlan interface has its own MAC address, it makes it easy to use wi
* `name` (string, required): the name of the network
* `type` (string, required): "macvlan"
* `master` (string, optional): name of the host interface to enslave. Defaults to default route interace.
* `master` (string, optional): name of the host interface to enslave. Defaults to default route interface.
* `mode` (string, optional): one of "bridge", "private", "vepa", "passthru". Defaults to "bridge".
* `mtu` (integer, optional): explicitly set MTU to the specified value. Defaults to the value chosen by the kernel.
* `mtu` (integer, optional): explicitly set MTU to the specified value. Defaults to the value chosen by the kernel. The value must be \[0, master's MTU\].
* `ipam` (dictionary, required): IPAM configuration to be used for this network. For interface only without ip address, create empty dictionary.
## Notes

View File

@ -85,9 +85,27 @@ func loadConf(bytes []byte) (*NetConf, string, error) {
}
n.Master = defaultRouteInterface
}
// check existing and MTU of master interface
masterMTU, err := getMTUByName(n.Master)
if err != nil {
return nil, "", err
}
if n.MTU < 0 || n.MTU > masterMTU {
return nil, "", fmt.Errorf("invalid MTU %d, must be [0, master MTU(%d)]", n.MTU, masterMTU)
}
return n, n.CNIVersion, nil
}
func getMTUByName(ifName string) (int, error) {
link, err := netlink.LinkByName(ifName)
if err != nil {
return 0, err
}
return link.Attrs().MTU, nil
}
func modeFromString(s string) (netlink.MacvlanMode, error) {
switch s {
case "", "bridge":

View File

@ -123,6 +123,7 @@ var _ = Describe("macvlan Operations", func() {
AfterEach(func() {
Expect(originalNS.Close()).To(Succeed())
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
})
It("creates an macvlan link in a non-default namespace", func() {

View File

@ -247,12 +247,25 @@ func cmdAdd(args *skel.CmdArgs) error {
}
}
result.DNS = conf.DNS
// Only override the DNS settings in the previous result if any DNS fields
// were provided to the ptp plugin. This allows, for example, IPAM plugins
// to specify the DNS settings instead of the ptp plugin.
if dnsConfSet(conf.DNS) {
result.DNS = conf.DNS
}
result.Interfaces = []*current.Interface{hostInterface, containerInterface}
return types.PrintResult(result, conf.CNIVersion)
}
func dnsConfSet(dnsConf types.DNS) bool {
return dnsConf.Nameservers != nil ||
dnsConf.Search != nil ||
dnsConf.Options != nil ||
dnsConf.Domain != ""
}
func cmdDel(args *skel.CmdArgs) error {
conf := NetConf{}
if err := json.Unmarshal(args.StdinData, &conf); err != nil {

View File

@ -17,6 +17,7 @@ package main
import (
"encoding/json"
"fmt"
"os"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
@ -98,9 +99,10 @@ var _ = Describe("ptp Operations", func() {
AfterEach(func() {
Expect(originalNS.Close()).To(Succeed())
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
})
doTest := func(conf string, numIPs int) {
doTest := func(conf string, numIPs int, expectedDNSConf types.DNS) {
const IFNAME = "ptp0"
targetNs, err := testutils.NewNS()
@ -175,6 +177,9 @@ var _ = Describe("ptp Operations", func() {
Expect(res.Interfaces[1].Mac).To(Equal(wantMac))
Expect(res.Interfaces[1].Sandbox).To(Equal(targetNs.Path()))
// make sure DNS is correct
Expect(res.DNS).To(Equal(expectedDNSConf))
// Call the plugins with the DEL command, deleting the veth endpoints
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
@ -327,7 +332,16 @@ var _ = Describe("ptp Operations", func() {
}
It("configures and deconfigures a ptp link with ADD/DEL", func() {
conf := `{
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": "0.3.1",
"name": "mynet",
"type": "ptp",
@ -336,10 +350,11 @@ var _ = Describe("ptp Operations", func() {
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24"
}
}`
},
"dns": %s
}`, string(dnsConfBytes))
doTest(conf, 1)
doTest(conf, 1, dnsConf)
})
It("configures and deconfigures a dual-stack ptp link with ADD/DEL", func() {
@ -358,7 +373,112 @@ var _ = Describe("ptp Operations", func() {
}
}`
doTest(conf, 2)
doTest(conf, 2, types.DNS{})
})
It("does not override IPAM DNS settings if no DNS settings provided", func() {
ipamDNSConf := types.DNS{
Nameservers: []string{"10.1.2.123"},
Domain: "some.domain.test",
Search: []string{"search.test"},
Options: []string{"option1:foo"},
}
resolvConfPath, err := testutils.TmpResolvConf(ipamDNSConf)
Expect(err).NotTo(HaveOccurred())
defer os.RemoveAll(resolvConfPath)
conf := fmt.Sprintf(`{
"cniVersion": "0.3.1",
"name": "mynet",
"type": "ptp",
"ipMasq": true,
"mtu": 5000,
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24",
"resolvConf": "%s"
}
}`, resolvConfPath)
doTest(conf, 1, ipamDNSConf)
})
It("overrides IPAM DNS settings if any DNS settings provided", func() {
ipamDNSConf := types.DNS{
Nameservers: []string{"10.1.2.123"},
Domain: "some.domain.test",
Search: []string{"search.test"},
Options: []string{"option1:foo"},
}
resolvConfPath, err := testutils.TmpResolvConf(ipamDNSConf)
Expect(err).NotTo(HaveOccurred())
defer os.RemoveAll(resolvConfPath)
for _, ptpDNSConf := range []types.DNS{
{
Nameservers: []string{"10.1.2.234"},
},
{
Domain: "someother.domain.test",
},
{
Search: []string{"search.elsewhere.test"},
},
{
Options: []string{"option2:bar"},
},
} {
dnsConfBytes, err := json.Marshal(ptpDNSConf)
Expect(err).NotTo(HaveOccurred())
conf := fmt.Sprintf(`{
"cniVersion": "0.3.1",
"name": "mynet",
"type": "ptp",
"ipMasq": true,
"mtu": 5000,
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24",
"resolvConf": "%s"
},
"dns": %s
}`, resolvConfPath, string(dnsConfBytes))
doTest(conf, 1, ptpDNSConf)
}
})
It("overrides IPAM DNS settings if any empty list DNS settings provided", func() {
ipamDNSConf := types.DNS{
Nameservers: []string{"10.1.2.123"},
Domain: "some.domain.test",
Search: []string{"search.test"},
Options: []string{"option1:foo"},
}
resolvConfPath, err := testutils.TmpResolvConf(ipamDNSConf)
Expect(err).NotTo(HaveOccurred())
defer os.RemoveAll(resolvConfPath)
conf := fmt.Sprintf(`{
"cniVersion": "0.3.1",
"name": "mynet",
"type": "ptp",
"ipMasq": true,
"mtu": 5000,
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24",
"resolvConf": "%s"
},
"dns": {
"nameservers": [],
"search": [],
"options": []
}
}`, resolvConfPath)
doTest(conf, 1, types.DNS{})
})
It("deconfigures an unconfigured ptp link with DEL", func() {

View File

@ -53,14 +53,32 @@ func loadConf(bytes []byte) (*NetConf, string, error) {
return nil, "", fmt.Errorf("failed to load netconf: %v", err)
}
if n.Master == "" {
return nil, "", fmt.Errorf(`"master" field is required. It specifies the host interface name to create the VLAN for.`)
return nil, "", fmt.Errorf("\"master\" field is required. It specifies the host interface name to create the VLAN for.")
}
if n.VlanId < 0 || n.VlanId > 4094 {
return nil, "", fmt.Errorf(`invalid VLAN ID %d (must be between 0 and 4095 inclusive)`, n.VlanId)
return nil, "", fmt.Errorf("invalid VLAN ID %d (must be between 0 and 4095 inclusive)", n.VlanId)
}
// check existing and MTU of master interface
masterMTU, err := getMTUByName(n.Master)
if err != nil {
return nil, "", err
}
if n.MTU < 0 || n.MTU > masterMTU {
return nil, "", fmt.Errorf("invalid MTU %d, must be [0, master MTU(%d)]", n.MTU, masterMTU)
}
return n, n.CNIVersion, nil
}
func getMTUByName(ifName string) (int, error) {
link, err := netlink.LinkByName(ifName)
if err != nil {
return 0, err
}
return link.Attrs().MTU, nil
}
func createVlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interface, error) {
vlan := &current.Interface{}
@ -76,10 +94,6 @@ func createVlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interfac
return nil, err
}
if conf.MTU <= 0 {
conf.MTU = m.Attrs().MTU
}
v := &netlink.Vlan{
LinkAttrs: netlink.LinkAttrs{
MTU: conf.MTU,

View File

@ -121,6 +121,7 @@ var _ = Describe("vlan Operations", func() {
AfterEach(func() {
Expect(originalNS.Close()).To(Succeed())
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
})
It("creates an vlan link in a non-default namespace with given MTU", func() {
@ -407,4 +408,75 @@ var _ = Describe("vlan Operations", func() {
})
Expect(err).NotTo(HaveOccurred())
})
Describe("fails to create vlan link with invalid MTU", func() {
conf := `{
"cniVersion": "0.3.1",
"name": "mynet",
"type": "vlan",
"master": "%s",
"mtu": %d,
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24"
}
}`
BeforeEach(func() {
var err error
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
// set master link's MTU to 1500
link, err := netlink.LinkByName(MASTER_NAME)
Expect(err).NotTo(HaveOccurred())
err = netlink.LinkSetMTU(link, 1500)
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("fails to create vlan link with greater MTU than master interface", func() {
var err error
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: "/var/run/netns/test",
IfName: "eth0",
StdinData: []byte(fmt.Sprintf(conf, MASTER_NAME, 1600)),
}
_ = originalNS.Do(func(netNS ns.NetNS) error {
defer GinkgoRecover()
_, _, err = testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).To(Equal(fmt.Errorf("invalid MTU 1600, must be [0, master MTU(1500)]")))
return nil
})
})
It("fails to create vlan link with negative MTU", func() {
var err error
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: "/var/run/netns/test",
IfName: "eth0",
StdinData: []byte(fmt.Sprintf(conf, MASTER_NAME, -100)),
}
_ = originalNS.Do(func(netNS ns.NetNS) error {
defer GinkgoRecover()
_, _, err = testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).To(Equal(fmt.Errorf("invalid MTU -100, must be [0, master MTU(1500)]")))
return nil
})
})
})
})

View File

@ -621,6 +621,21 @@ var _ = Describe("bandwidth test", func() {
})
})
Describe("Validating input", func() {
It("Should allow only 4GB burst rate", func() {
err := validateRateAndBurst(5000, 4*1024*1024*1024*8-16) // 2 bytes less than the max should pass
Expect(err).NotTo(HaveOccurred())
err = validateRateAndBurst(5000, 4*1024*1024*1024*8) // we're 1 bit above MaxUint32
Expect(err).To(HaveOccurred())
err = validateRateAndBurst(0, 1)
Expect(err).To(HaveOccurred())
err = validateRateAndBurst(1, 0)
Expect(err).To(HaveOccurred())
err = validateRateAndBurst(0, 0)
Expect(err).NotTo(HaveOccurred())
})
})
Describe("Getting the host interface which plugin should work on from veth peer of container interface", func() {
It("Should work with multiple host veth interfaces", func() {
conf := `{
@ -874,8 +889,8 @@ var _ = Describe("bandwidth test", func() {
Context("when chaining bandwidth plugin with PTP using 0.3.0 config", func() {
var ptpConf string
var rateInBits int
var burstInBits int
var rateInBits uint64
var burstInBits uint64
var packetInBytes int
var containerWithoutTbfNS ns.NetNS
var containerWithTbfNS ns.NetNS
@ -889,7 +904,7 @@ var _ = Describe("bandwidth test", func() {
BeforeEach(func() {
rateInBytes := 1000
rateInBits = rateInBytes * 8
rateInBits = uint64(rateInBytes * 8)
burstInBits = rateInBits * 2
packetInBytes = rateInBytes * 25
@ -1019,8 +1034,8 @@ var _ = Describe("bandwidth test", func() {
Context("when chaining bandwidth plugin with PTP using 0.4.0 config", func() {
var ptpConf string
var rateInBits int
var burstInBits int
var rateInBits uint64
var burstInBits uint64
var packetInBytes int
var containerWithoutTbfNS ns.NetNS
var containerWithTbfNS ns.NetNS
@ -1034,7 +1049,7 @@ var _ = Describe("bandwidth test", func() {
BeforeEach(func() {
rateInBytes := 1000
rateInBits = rateInBytes * 8
rateInBits = uint64(rateInBytes * 8)
burstInBits = rateInBits * 2
packetInBytes = rateInBytes * 25

View File

@ -50,7 +50,7 @@ func TeardownIfb(deviceName string) error {
return err
}
func CreateIngressQdisc(rateInBits, burstInBits int, hostDeviceName string) error {
func CreateIngressQdisc(rateInBits, burstInBits uint64, hostDeviceName string) error {
hostDevice, err := netlink.LinkByName(hostDeviceName)
if err != nil {
return fmt.Errorf("get host device: %s", err)
@ -58,7 +58,7 @@ func CreateIngressQdisc(rateInBits, burstInBits int, hostDeviceName string) erro
return createTBF(rateInBits, burstInBits, hostDevice.Attrs().Index)
}
func CreateEgressQdisc(rateInBits, burstInBits int, hostDeviceName string, ifbDeviceName string) error {
func CreateEgressQdisc(rateInBits, burstInBits uint64, hostDeviceName string, ifbDeviceName string) error {
ifbDevice, err := netlink.LinkByName(ifbDeviceName)
if err != nil {
return fmt.Errorf("get ifb device: %s", err)
@ -113,7 +113,7 @@ func CreateEgressQdisc(rateInBits, burstInBits int, hostDeviceName string, ifbDe
return nil
}
func createTBF(rateInBits, burstInBits, linkIndex int) error {
func createTBF(rateInBits, burstInBits uint64, linkIndex int) error {
// Equivalent to
// tc qdisc add dev link root tbf
// rate netConf.BandwidthLimits.Rate

View File

@ -17,6 +17,7 @@ package main
import (
"encoding/json"
"fmt"
"math"
"github.com/vishvananda/netlink"
@ -37,11 +38,11 @@ const ifbDevicePrefix = "bwp"
// BandwidthEntry corresponds to a single entry in the bandwidth argument,
// see CONVENTIONS.md
type BandwidthEntry struct {
IngressRate int `json:"ingressRate"` //Bandwidth rate in bps for traffic through container. 0 for no limit. If ingressRate is set, ingressBurst must also be set
IngressBurst int `json:"ingressBurst"` //Bandwidth burst in bits for traffic through container. 0 for no limit. If ingressBurst is set, ingressRate must also be set
IngressRate uint64 `json:"ingressRate"` //Bandwidth rate in bps for traffic through container. 0 for no limit. If ingressRate is set, ingressBurst must also be set
IngressBurst uint64 `json:"ingressBurst"` //Bandwidth burst in bits for traffic through container. 0 for no limit. If ingressBurst is set, ingressRate must also be set
EgressRate int `json:"egressRate"` //Bandwidth rate in bps for traffic through container. 0 for no limit. If egressRate is set, egressBurst must also be set
EgressBurst int `json:"egressBurst"` //Bandwidth burst in bits for traffic through container. 0 for no limit. If egressBurst is set, egressRate must also be set
EgressRate uint64 `json:"egressRate"` //Bandwidth rate in bps for traffic through container. 0 for no limit. If egressRate is set, egressBurst must also be set
EgressBurst uint64 `json:"egressBurst"` //Bandwidth burst in bits for traffic through container. 0 for no limit. If egressBurst is set, egressRate must also be set
}
func (bw *BandwidthEntry) isZero() bool {
@ -101,7 +102,7 @@ func getBandwidth(conf *PluginConf) *BandwidthEntry {
return conf.BandwidthEntry
}
func validateRateAndBurst(rate int, burst int) error {
func validateRateAndBurst(rate, burst uint64) error {
switch {
case burst < 0 || rate < 0:
return fmt.Errorf("rate and burst must be a positive integer")
@ -109,6 +110,8 @@ func validateRateAndBurst(rate int, burst int) error {
return fmt.Errorf("if rate is set, burst must also be set")
case rate == 0 && burst != 0:
return fmt.Errorf("if burst is set, rate must also be set")
case burst/8 >= math.MaxUint32:
return fmt.Errorf("burst cannot be more than 4GB")
}
return nil

View File

@ -27,7 +27,6 @@ import (
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/cni/pkg/version"
"github.com/containernetworking/plugins/pkg/ns"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
@ -68,9 +67,15 @@ func parseConf(data []byte) (*FirewallNetConf, *current.Result, error) {
return nil, nil, fmt.Errorf("failed to load netconf: %v", err)
}
// Default the firewalld zone to trusted
if conf.FirewalldZone == "" {
conf.FirewalldZone = "trusted"
}
// Parse previous result.
if conf.RawPrevResult == nil {
return nil, nil, fmt.Errorf("missing prevResult from earlier plugin")
// return early if there was no previous result, which is allowed for DEL calls
return &conf, &current.Result{}, nil
}
// Parse previous result.
@ -85,11 +90,6 @@ func parseConf(data []byte) (*FirewallNetConf, *current.Result, error) {
return nil, nil, fmt.Errorf("could not convert result to current version: %v", err)
}
// Default the firewalld zone to trusted
if conf.FirewalldZone == "" {
conf.FirewalldZone = "trusted"
}
return &conf, result, nil
}
@ -116,6 +116,10 @@ func cmdAdd(args *skel.CmdArgs) error {
return err
}
if conf.PrevResult == nil {
return fmt.Errorf("missing prevResult from earlier plugin")
}
backend, err := getBackend(conf)
if err != nil {
return err
@ -142,12 +146,6 @@ func cmdDel(args *skel.CmdArgs) error {
return err
}
// Tolerate errors if the container namespace has been torn down already
containerNS, err := ns.GetNS(args.Netns)
if err == nil {
defer containerNS.Close()
}
// Runtime errors are ignored
if err := backend.Del(conf, result); err != nil {
return err
@ -167,8 +165,8 @@ func cmdCheck(args *skel.CmdArgs) error {
}
// Ensure we have previous result.
if result == nil {
return fmt.Errorf("Required prevResult missing")
if conf.PrevResult == nil {
return fmt.Errorf("missing prevResult from earlier plugin")
}
backend, err := getBackend(conf)

View File

@ -270,6 +270,13 @@ var _ = Describe("firewall plugin iptables backend", func() {
Expect(err).NotTo(HaveOccurred())
validateFullRuleset(fullConf)
// ensure creation is idempotent
_, _, err = testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, fullConf, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())

View File

@ -22,6 +22,7 @@ import (
"net"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/plugins/pkg/utils"
"github.com/coreos/go-iptables/iptables"
)
@ -32,20 +33,6 @@ func getPrivChainRules(ip string) [][]string {
return rules
}
func ensureChain(ipt *iptables.IPTables, table, chain string) error {
chains, err := ipt.ListChains(table)
if err != nil {
return fmt.Errorf("failed to list iptables chains: %v", err)
}
for _, ch := range chains {
if ch == chain {
return nil
}
}
return ipt.NewChain(table, chain)
}
func generateFilterRule(privChainName string) []string {
return []string{"-m", "comment", "--comment", "CNI firewall plugin rules", "-j", privChainName}
}
@ -73,10 +60,10 @@ func (ib *iptablesBackend) setupChains(ipt *iptables.IPTables) error {
adminRule := generateFilterRule(ib.adminChainName)
// Ensure our private chains exist
if err := ensureChain(ipt, "filter", ib.privChainName); err != nil {
if err := utils.EnsureChain(ipt, "filter", ib.privChainName); err != nil {
return err
}
if err := ensureChain(ipt, "filter", ib.adminChainName); err != nil {
if err := utils.EnsureChain(ipt, "filter", ib.adminChainName); err != nil {
return err
}
@ -160,10 +147,10 @@ func (ib *iptablesBackend) checkRules(conf *FirewallNetConf, result *current.Res
}
// Ensure our private chains exist
if err := ensureChain(ipt, "filter", ib.privChainName); err != nil {
if err := utils.EnsureChain(ipt, "filter", ib.privChainName); err != nil {
return err
}
if err := ensureChain(ipt, "filter", ib.adminChainName); err != nil {
if err := utils.EnsureChain(ipt, "filter", ib.adminChainName); err != nil {
return err
}

View File

@ -18,6 +18,7 @@ import (
"fmt"
"strings"
"github.com/containernetworking/plugins/pkg/utils"
"github.com/coreos/go-iptables/iptables"
"github.com/mattn/go-shellwords"
)
@ -35,16 +36,11 @@ type chain struct {
// setup idempotently creates the chain. It will not error if the chain exists.
func (c *chain) setup(ipt *iptables.IPTables) error {
// create the chain
exists, err := chainExists(ipt, c.table, c.name)
err := utils.EnsureChain(ipt, c.table, c.name)
if err != nil {
return err
}
if !exists {
if err := ipt.NewChain(c.table, c.name); err != nil {
return err
}
}
// Add the rules to the chain
for _, rule := range c.rules {
@ -74,7 +70,7 @@ 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 := ipt.ClearChain(c.table, c.name); err != nil {
if err := utils.ClearChain(ipt, c.table, c.name); err != nil {
return err
}
@ -94,17 +90,15 @@ func (c *chain) teardown(ipt *iptables.IPTables) error {
}
chainParts = chainParts[2:] // List results always include an -A CHAINNAME
if err := ipt.Delete(c.table, entryChain, chainParts...); err != nil {
return fmt.Errorf("Failed to delete referring rule %s %s: %v", c.table, entryChainRule, err)
if err := utils.DeleteRule(ipt, c.table, entryChain, chainParts...); err != nil {
return err
}
}
}
}
if err := ipt.DeleteChain(c.table, c.name); err != nil {
return err
}
return nil
return utils.DeleteChain(ipt, c.table, c.name)
}
// insertUnique will add a rule to a chain if it does not already exist.
@ -125,24 +119,10 @@ func insertUnique(ipt *iptables.IPTables, table, chain string, prepend bool, rul
}
}
func chainExists(ipt *iptables.IPTables, tableName, chainName string) (bool, error) {
chains, err := ipt.ListChains(tableName)
if err != nil {
return false, err
}
for _, ch := range chains {
if ch == chainName {
return true, nil
}
}
return false, nil
}
// check the chain.
func (c *chain) check(ipt *iptables.IPTables) error {
exists, err := chainExists(ipt, c.table, c.name)
exists, err := utils.ChainExists(ipt, c.table, c.name)
if err != nil {
return err
}

View File

@ -18,6 +18,7 @@ import (
"fmt"
"math/rand"
"runtime"
"sync"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils"
@ -32,6 +33,7 @@ const TABLE = "filter" // We'll monkey around here
var _ = Describe("chain tests", func() {
var testChain chain
var ipt *iptables.IPTables
var testNs ns.NetNS
var cleanup func()
BeforeEach(func() {
@ -41,7 +43,7 @@ var _ = Describe("chain tests", func() {
currNs, err := ns.GetCurrentNS()
Expect(err).NotTo(HaveOccurred())
testNs, err := testutils.NewNS()
testNs, err = testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
tlChainName := fmt.Sprintf("cni-test-%d", rand.Intn(10000000))
@ -195,4 +197,38 @@ var _ = Describe("chain tests", func() {
}
}
})
It("deletes chains idempotently in parallel", func() {
defer cleanup()
// number of parallel executions
N := 10
var wg sync.WaitGroup
err := testChain.setup(ipt)
Expect(err).NotTo(HaveOccurred())
errCh := make(chan error, N)
for i := 0; i < N; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// teardown chain
errCh <- testNs.Do(func(ns.NetNS) error {
return testChain.teardown(ipt)
})
}()
}
wg.Wait()
close(errCh)
for err := range errCh {
Expect(err).NotTo(HaveOccurred())
}
chains, err := ipt.ListChains(TABLE)
Expect(err).NotTo(HaveOccurred())
for _, chain := range chains {
if chain == testChain.name {
Fail("Chain was not deleted")
}
}
})
})

View File

@ -124,7 +124,7 @@ func checkPorts(config *PortMapConf, containerIP net.IP) error {
}
if ip4t != nil {
exists, err := chainExists(ip4t, dnatChain.table, dnatChain.name)
exists, err := utils.ChainExists(ip4t, dnatChain.table, dnatChain.name)
if err != nil {
return err
}
@ -137,7 +137,7 @@ func checkPorts(config *PortMapConf, containerIP net.IP) error {
}
if ip6t != nil {
exists, err := chainExists(ip6t, dnatChain.table, dnatChain.name)
exists, err := utils.ChainExists(ip6t, dnatChain.table, dnatChain.name)
if err != nil {
return err
}
@ -224,6 +224,16 @@ func fillDnatRules(c *chain, config *PortMapConf, containerIP net.IP) {
// the ordering is important here; the mark rules must be first.
c.rules = make([][]string, 0, 3*len(entries))
for _, entry := range entries {
// If a HostIP is given, only process the entry if host and container address families match
if entry.HostIP != "" {
hostIP := net.ParseIP(entry.HostIP)
isHostV6 := (hostIP.To4() == nil)
if isV6 != isHostV6 {
continue
}
}
ruleBase := []string{
"-p", entry.Protocol,
"--dport", strconv.Itoa(entry.HostPort)}

View File

@ -96,119 +96,133 @@ var _ = Describe("portmap integration tests", func() {
}
})
// This needs to be done using Ginkgo's asynchronous testing mode.
It("forwards a TCP port on ipv4", func(done Done) {
var err error
hostPort := rand.Intn(10000) + 1025
runtimeConfig := libcni.RuntimeConf{
ContainerID: fmt.Sprintf("unit-test-%d", hostPort),
NetNS: targetNS.Path(),
IfName: "eth0",
CapabilityArgs: map[string]interface{}{
"portMappings": []map[string]interface{}{
{
"hostPort": hostPort,
"containerPort": containerPort,
"protocol": "tcp",
Describe("Creating an interface in a namespace with the ptp plugin", func() {
// This needs to be done using Ginkgo's asynchronous testing mode.
It("forwards a TCP port on ipv4", func(done Done) {
var err error
hostPort := rand.Intn(10000) + 1025
runtimeConfig := libcni.RuntimeConf{
ContainerID: fmt.Sprintf("unit-test-%d", hostPort),
NetNS: targetNS.Path(),
IfName: "eth0",
CapabilityArgs: map[string]interface{}{
"portMappings": []map[string]interface{}{
{
"hostPort": hostPort,
"containerPort": containerPort,
"protocol": "tcp",
},
},
},
},
}
// Make delete idempotent, so we can clean up on failure
netDeleted := false
deleteNetwork := func() error {
if netDeleted {
return nil
}
netDeleted = true
return cniConf.DelNetworkList(context.TODO(), configList, &runtimeConfig)
}
// we'll also manually check the iptables chains
ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
Expect(err).NotTo(HaveOccurred())
dnatChainName := genDnatChain("cni-portmap-unit-test", runtimeConfig.ContainerID).name
// Create the network
resI, err := cniConf.AddNetworkList(context.TODO(), configList, &runtimeConfig)
Expect(err).NotTo(HaveOccurred())
defer deleteNetwork()
// Undo Docker's forwarding policy
cmd := exec.Command("iptables", "-t", "filter",
"-P", "FORWARD", "ACCEPT")
cmd.Stderr = GinkgoWriter
err = cmd.Run()
Expect(err).NotTo(HaveOccurred())
// Check the chain exists
_, err = ipt.List("nat", dnatChainName)
Expect(err).NotTo(HaveOccurred())
result, err := current.GetResult(resI)
Expect(err).NotTo(HaveOccurred())
var contIP net.IP
for _, ip := range result.IPs {
intfIndex := *ip.Interface
if result.Interfaces[intfIndex].Sandbox == "" {
continue
// Make delete idempotent, so we can clean up on failure
netDeleted := false
deleteNetwork := func() error {
if netDeleted {
return nil
}
netDeleted = true
return cniConf.DelNetworkList(context.TODO(), configList, &runtimeConfig)
}
contIP = ip.Address.IP
}
if contIP == nil {
Fail("could not determine container IP")
}
hostIP := getLocalIP()
fmt.Fprintf(GinkgoWriter, "hostIP: %s:%d, contIP: %s:%d\n",
hostIP, hostPort, contIP, containerPort)
// we'll also manually check the iptables chains
ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
Expect(err).NotTo(HaveOccurred())
dnatChainName := genDnatChain("cni-portmap-unit-test", runtimeConfig.ContainerID).name
// dump iptables-save output for debugging
cmd = exec.Command("iptables-save")
cmd.Stderr = GinkgoWriter
cmd.Stdout = GinkgoWriter
Expect(cmd.Run()).To(Succeed())
// Create the network
resI, err := cniConf.AddNetworkList(context.TODO(), configList, &runtimeConfig)
Expect(err).NotTo(HaveOccurred())
defer deleteNetwork()
// Sanity check: verify that the container is reachable directly
contOK := testEchoServer(contIP.String(), containerPort, "")
// Undo Docker's forwarding policy
cmd := exec.Command("iptables", "-t", "filter",
"-P", "FORWARD", "ACCEPT")
cmd.Stderr = GinkgoWriter
err = cmd.Run()
Expect(err).NotTo(HaveOccurred())
// Verify that a connection to the forwarded port works
dnatOK := testEchoServer(hostIP, hostPort, "")
// Check the chain exists
_, err = ipt.List("nat", dnatChainName)
Expect(err).NotTo(HaveOccurred())
// Verify that a connection to localhost works
snatOK := testEchoServer("127.0.0.1", hostPort, "")
result, err := current.GetResult(resI)
Expect(err).NotTo(HaveOccurred())
var contIP net.IP
// verify that hairpin works
hairpinOK := testEchoServer(hostIP, hostPort, targetNS.Path())
for _, ip := range result.IPs {
intfIndex := *ip.Interface
if result.Interfaces[intfIndex].Sandbox == "" {
continue
}
contIP = ip.Address.IP
}
if contIP == nil {
Fail("could not determine container IP")
}
// Cleanup
session.Terminate()
err = deleteNetwork()
Expect(err).NotTo(HaveOccurred())
hostIP := getLocalIP()
fmt.Fprintf(GinkgoWriter, "hostIP: %s:%d, contIP: %s:%d\n",
hostIP, hostPort, contIP, containerPort)
// Verify iptables rules are gone
_, err = ipt.List("nat", dnatChainName)
Expect(err).To(MatchError(ContainSubstring("iptables: No chain/target/match by that name.")))
// dump iptables-save output for debugging
cmd = exec.Command("iptables-save")
cmd.Stderr = GinkgoWriter
cmd.Stdout = GinkgoWriter
Expect(cmd.Run()).To(Succeed())
// Check that everything succeeded *after* we clean up the network
if !contOK {
Fail("connection direct to " + contIP.String() + " failed")
}
if !dnatOK {
Fail("Connection to " + hostIP + " was not forwarded")
}
if !snatOK {
Fail("connection to 127.0.0.1 was not forwarded")
}
if !hairpinOK {
Fail("Hairpin connection failed")
}
// dump ip routes output for debugging
cmd = exec.Command("ip", "route")
cmd.Stderr = GinkgoWriter
cmd.Stdout = GinkgoWriter
Expect(cmd.Run()).To(Succeed())
close(done)
// dump ip addresses output for debugging
cmd = exec.Command("ip", "addr")
cmd.Stderr = GinkgoWriter
cmd.Stdout = GinkgoWriter
Expect(cmd.Run()).To(Succeed())
}, TIMEOUT*9)
// Sanity check: verify that the container is reachable directly
contOK := testEchoServer(contIP.String(), containerPort, "")
// Verify that a connection to the forwarded port works
dnatOK := testEchoServer(hostIP, hostPort, "")
// Verify that a connection to localhost works
snatOK := testEchoServer("127.0.0.1", hostPort, "")
// verify that hairpin works
hairpinOK := testEchoServer(hostIP, hostPort, targetNS.Path())
// Cleanup
session.Terminate()
err = deleteNetwork()
Expect(err).NotTo(HaveOccurred())
// Verify iptables rules are gone
_, err = ipt.List("nat", dnatChainName)
Expect(err).To(MatchError(ContainSubstring("iptables: No chain/target/match by that name.")))
// Check that everything succeeded *after* we clean up the network
if !contOK {
Fail("connection direct to " + contIP.String() + " failed")
}
if !dnatOK {
Fail("Connection to " + hostIP + " was not forwarded")
}
if !snatOK {
Fail("connection to 127.0.0.1 was not forwarded")
}
if !hairpinOK {
Fail("Hairpin connection failed")
}
close(done)
}, TIMEOUT*9)
})
})
// testEchoServer returns true if we found an echo server on the port

View File

@ -25,6 +25,7 @@ import (
"path/filepath"
"strings"
"github.com/j-keck/arping"
"github.com/vishvananda/netlink"
"github.com/containernetworking/cni/pkg/skel"
@ -179,7 +180,7 @@ func cmdAdd(args *skel.CmdArgs) error {
return err
}
_, err = current.NewResultFromResult(tuningConf.PrevResult)
result, err := current.NewResultFromResult(tuningConf.PrevResult)
if err != nil {
return err
}
@ -208,6 +209,13 @@ func cmdAdd(args *skel.CmdArgs) error {
if err = changeMacAddr(args.IfName, tuningConf.Mac); err != nil {
return err
}
for _, ipc := range result.IPs {
if ipc.Version == "4" {
_ = arping.GratuitousArpOverIfaceByName(ipc.Address.IP, args.IfName)
}
}
updateResultsMacAddr(*tuningConf, args.IfName, tuningConf.Mac)
}

View File

@ -21,7 +21,7 @@ $DOCKER run -ti -v ${SRC_DIR}:/go/src/github.com/containernetworking/plugins --r
apk --no-cache add bash tar;
cd /go/src/github.com/containernetworking/plugins; umask 0022;
for arch in amd64 arm arm64 ppc64le s390x; do \
for arch in amd64 arm arm64 ppc64le s390x mips64le; do \
rm -f ${OUTPUT_DIR}/*; \
CGO_ENABLED=0 GOARCH=\$arch ./build_linux.sh ${BUILDFLAGS}; \
for format in tgz; do \

View File

@ -57,3 +57,7 @@ if [ -n "${vetRes}" ]; then
echo -e "govet checking failed:\n${vetRes}"
exit 255
fi
# Run the pkg/ns tests as non root user
mkdir /tmp/cni-rootless
(export XDG_RUNTIME_DIR=/tmp/cni-rootless; cd pkg/ns/; unshare -rmn go test)

View File

@ -69,6 +69,7 @@ type CNI interface {
AddNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
CheckNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) error
DelNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) error
GetNetworkListCachedResult(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
AddNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) (types.Result, error)
CheckNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error

View File

@ -15,6 +15,7 @@
package invoke
import (
"fmt"
"os"
"strings"
)
@ -22,6 +23,8 @@ import (
type CNIArgs interface {
// For use with os/exec; i.e., return nil to inherit the
// environment from this process
// For use in delegation; inherit the environment from this
// process and allow overrides
AsEnv() []string
}
@ -57,17 +60,17 @@ func (args *Args) AsEnv() []string {
pluginArgsStr = stringify(args.PluginArgs)
}
// Ensure that the custom values are first, so any value present in
// the process environment won't override them.
env = append([]string{
"CNI_COMMAND=" + args.Command,
"CNI_CONTAINERID=" + args.ContainerID,
"CNI_NETNS=" + args.NetNS,
"CNI_ARGS=" + pluginArgsStr,
"CNI_IFNAME=" + args.IfName,
"CNI_PATH=" + args.Path,
}, env...)
return env
// Duplicated values which come first will be overrided, so we must put the
// custom values in the end to avoid being overrided by the process environments.
env = append(env,
"CNI_COMMAND="+args.Command,
"CNI_CONTAINERID="+args.ContainerID,
"CNI_NETNS="+args.NetNS,
"CNI_ARGS="+pluginArgsStr,
"CNI_IFNAME="+args.IfName,
"CNI_PATH="+args.Path,
)
return dedupEnv(env)
}
// taken from rkt/networking/net_plugin.go
@ -80,3 +83,46 @@ func stringify(pluginArgs [][2]string) string {
return strings.Join(entries, ";")
}
// DelegateArgs implements the CNIArgs interface
// used for delegation to inherit from environments
// and allow some overrides like CNI_COMMAND
var _ CNIArgs = &DelegateArgs{}
type DelegateArgs struct {
Command string
}
func (d *DelegateArgs) AsEnv() []string {
env := os.Environ()
// The custom values should come in the end to override the existing
// process environment of the same key.
env = append(env,
"CNI_COMMAND="+d.Command,
)
return dedupEnv(env)
}
// dedupEnv returns a copy of env with any duplicates removed, in favor of later values.
// Items not of the normal environment "key=value" form are preserved unchanged.
func dedupEnv(env []string) []string {
out := make([]string, 0, len(env))
envMap := map[string]string{}
for _, kv := range env {
// find the first "=" in environment, if not, just keep it
eq := strings.Index(kv, "=")
if eq < 0 {
out = append(out, kv)
continue
}
envMap[kv[:eq]] = kv[eq+1:]
}
for k, v := range envMap {
out = append(out, fmt.Sprintf("%s=%s", k, v))
}
return out
}

View File

@ -16,22 +16,17 @@ package invoke
import (
"context"
"fmt"
"os"
"path/filepath"
"github.com/containernetworking/cni/pkg/types"
)
func delegateCommon(expectedCommand, delegatePlugin string, exec Exec) (string, Exec, error) {
func delegateCommon(delegatePlugin string, exec Exec) (string, Exec, error) {
if exec == nil {
exec = defaultExec
}
if os.Getenv("CNI_COMMAND") != expectedCommand {
return "", nil, fmt.Errorf("CNI_COMMAND is not " + expectedCommand)
}
paths := filepath.SplitList(os.Getenv("CNI_PATH"))
pluginPath, err := exec.FindInPath(delegatePlugin, paths)
if err != nil {
@ -44,32 +39,42 @@ func delegateCommon(expectedCommand, delegatePlugin string, exec Exec) (string,
// DelegateAdd calls the given delegate plugin with the CNI ADD action and
// JSON configuration
func DelegateAdd(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) (types.Result, error) {
pluginPath, realExec, err := delegateCommon("ADD", delegatePlugin, exec)
pluginPath, realExec, err := delegateCommon(delegatePlugin, exec)
if err != nil {
return nil, err
}
return ExecPluginWithResult(ctx, pluginPath, netconf, ArgsFromEnv(), realExec)
// DelegateAdd will override the original "CNI_COMMAND" env from process with ADD
return ExecPluginWithResult(ctx, pluginPath, netconf, delegateArgs("ADD"), realExec)
}
// DelegateCheck calls the given delegate plugin with the CNI CHECK action and
// JSON configuration
func DelegateCheck(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error {
pluginPath, realExec, err := delegateCommon("CHECK", delegatePlugin, exec)
pluginPath, realExec, err := delegateCommon(delegatePlugin, exec)
if err != nil {
return err
}
return ExecPluginWithoutResult(ctx, pluginPath, netconf, ArgsFromEnv(), realExec)
// DelegateCheck will override the original CNI_COMMAND env from process with CHECK
return ExecPluginWithoutResult(ctx, pluginPath, netconf, delegateArgs("CHECK"), realExec)
}
// DelegateDel calls the given delegate plugin with the CNI DEL action and
// JSON configuration
func DelegateDel(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error {
pluginPath, realExec, err := delegateCommon("DEL", delegatePlugin, exec)
pluginPath, realExec, err := delegateCommon(delegatePlugin, exec)
if err != nil {
return err
}
return ExecPluginWithoutResult(ctx, pluginPath, netconf, ArgsFromEnv(), realExec)
// DelegateDel will override the original CNI_COMMAND env from process with DEL
return ExecPluginWithoutResult(ctx, pluginPath, netconf, delegateArgs("DEL"), realExec)
}
// return CNIArgs used by delegation
func delegateArgs(action string) *DelegateArgs {
return &DelegateArgs{
Command: action,
}
}

View File

@ -48,9 +48,13 @@ func (e *Error) Error() string {
// IsNotExist returns true if the error is due to the chain or rule not existing
func (e *Error) IsNotExist() bool {
return e.ExitStatus() == 1 &&
(e.msg == fmt.Sprintf("%s: Bad rule (does a matching rule exist in that chain?).\n", getIptablesCommand(e.proto)) ||
e.msg == fmt.Sprintf("%s: No chain/target/match by that name.\n", getIptablesCommand(e.proto)))
if e.ExitStatus() != 1 {
return false
}
cmdIptables := getIptablesCommand(e.proto)
msgNoRuleExist := fmt.Sprintf("%s: Bad rule (does a matching rule exist in that chain?).\n", cmdIptables)
msgNoChainExist := fmt.Sprintf("%s: No chain/target/match by that name.\n", cmdIptables)
return strings.Contains(e.msg, msgNoRuleExist) || strings.Contains(e.msg, msgNoChainExist)
}
// Protocol to differentiate between IPv4 and IPv6
@ -101,7 +105,13 @@ func NewWithProtocol(proto Protocol) (*IPTables, error) {
return nil, err
}
vstring, err := getIptablesVersionString(path)
if err != nil {
return nil, fmt.Errorf("could not get iptables version: %v", err)
}
v1, v2, v3, mode, err := extractIptablesVersion(vstring)
if err != nil {
return nil, fmt.Errorf("failed to extract iptables version from [%s]: %v", vstring, err)
}
checkPresent, waitPresent, randomFullyPresent := getIptablesCommandSupport(v1, v2, v3)

4
vendor/modules.txt vendored
View File

@ -24,7 +24,7 @@ github.com/Microsoft/hcsshim/internal/safefile
github.com/alexflint/go-filemutex
# github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44
github.com/buger/jsonparser
# github.com/containernetworking/cni v0.7.0
# github.com/containernetworking/cni v0.7.1
github.com/containernetworking/cni/pkg/types
github.com/containernetworking/cni/pkg/types/current
github.com/containernetworking/cni/pkg/invoke
@ -32,7 +32,7 @@ github.com/containernetworking/cni/pkg/skel
github.com/containernetworking/cni/pkg/version
github.com/containernetworking/cni/pkg/types/020
github.com/containernetworking/cni/libcni
# github.com/coreos/go-iptables v0.4.2
# github.com/coreos/go-iptables v0.4.5
github.com/coreos/go-iptables/iptables
# github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7
github.com/coreos/go-systemd/activation