Compare commits
128 Commits
Author | SHA1 | Date | |
---|---|---|---|
fa48f7515b | |||
74a6b28a2c | |||
8feef71fd3 | |||
d1769ddd68 | |||
343d233b4f | |||
8c66d687c7 | |||
49ee6f3768 | |||
d41acb83c4 | |||
7dc7a002cf | |||
075c5a0d67 | |||
a8d1f5cd1b | |||
354def76b4 | |||
25fc741e37 | |||
3161bb52e0 | |||
48a97a7ad1 | |||
ebce6d0b9b | |||
3819ef7e86 | |||
691238ca57 | |||
23a1d90e0b | |||
e13bab99e5 | |||
3689d53adf | |||
680c6dd0b1 | |||
be19d786a0 | |||
001abd4f55 | |||
8d52c42f90 | |||
3ae85c1093 | |||
25704f9372 | |||
7e59bac059 | |||
eb31403ac7 | |||
336ba52542 | |||
b47d178ae0 | |||
509d645ee9 | |||
108c2aebd4 | |||
cccf5395e8 | |||
c41c78b600 | |||
8aad9739d8 | |||
b7af044cdc | |||
95248d8490 | |||
50a3aa6536 | |||
98c621abe6 | |||
b34402abd3 | |||
ccd872bd7a | |||
5fc309a699 | |||
440dcc331b | |||
362f5d626a | |||
8d0d8a9547 | |||
a9abbaf19b | |||
c4b8bccd2a | |||
1a7f1bd8b1 | |||
8a6e96b3f0 | |||
6df03d7937 | |||
e1517e2498 | |||
322790226b | |||
59baaa1546 | |||
9ce99d3f07 | |||
e78e6aa5b9 | |||
08ff3b6413 | |||
1ea19f9213 | |||
9b8de6a613 | |||
dacb671328 | |||
799d3cbf4c | |||
bd589992fb | |||
839d918e35 | |||
c50490eb76 | |||
01a8de9997 | |||
8a88f90f94 | |||
2b6cd5467f | |||
3aadb402e4 | |||
021462563b | |||
d713ec692c | |||
813f541d30 | |||
6eb8e31d21 | |||
051452cdcf | |||
877602d627 | |||
c90b165c6e | |||
28773dc925 | |||
2bd04cb92f | |||
d4775ecff5 | |||
e1f8f9bee5 | |||
68a80bcf9b | |||
1fb9793607 | |||
5cb3a5e897 | |||
b76fdd7c03 | |||
67175607ad | |||
b9560fd5c1 | |||
79192cb1f1 | |||
ad10b6fa91 | |||
219eb9e046 | |||
f7a2fc97e4 | |||
6957f6ca4e | |||
02bfece2e9 | |||
5af9ff493e | |||
44d92c19de | |||
5e0fbd8374 | |||
a78853f29f | |||
2d2583ee33 | |||
f4332fec59 | |||
ed16760739 | |||
30776ff858 | |||
2a48d68937 | |||
117e30ff21 | |||
486ef96e6f | |||
8a0e3fe10e | |||
ca419073e4 | |||
47a9fd80c8 | |||
112288ecb2 | |||
32fc3ee9d3 | |||
c7e2cf7602 | |||
53854dd948 | |||
5c512194eb | |||
a9b4e04bc4 | |||
f5c3d1b1ba | |||
8bf6a7b362 | |||
66e0aaf9c1 | |||
45fd949465 | |||
2ff84a481e | |||
37207f05b4 | |||
832f2699c2 | |||
d1360b82ab | |||
1f33fb729a | |||
754e153b03 | |||
0edf8a4208 | |||
db7e6cfabf | |||
963aaf86e6 | |||
cd9d6b28da | |||
0452c1dd10 | |||
d671d29ad5 | |||
cc6154603e |
7
.github/actions/retest-action/Dockerfile
vendored
Normal file
7
.github/actions/retest-action/Dockerfile
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
FROM alpine:3.10
|
||||
|
||||
RUN apk add --no-cache curl jq
|
||||
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
11
.github/actions/retest-action/action.yml
vendored
Normal file
11
.github/actions/retest-action/action.yml
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
name: 'Re-Test'
|
||||
description: 'Re-Runs the last workflow for a PR'
|
||||
inputs:
|
||||
token:
|
||||
description: 'GitHub API Token'
|
||||
required: true
|
||||
runs:
|
||||
using: 'docker'
|
||||
image: 'Dockerfile'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ inputs.token }}
|
45
.github/actions/retest-action/entrypoint.sh
vendored
Executable file
45
.github/actions/retest-action/entrypoint.sh
vendored
Executable file
@ -0,0 +1,45 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -ex
|
||||
|
||||
if ! jq -e '.issue.pull_request' ${GITHUB_EVENT_PATH}; then
|
||||
echo "Not a PR... Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$(jq -r '.comment.body' ${GITHUB_EVENT_PATH})" != "/retest" ]; then
|
||||
echo "Nothing to do... Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
PR_URL=$(jq -r '.issue.pull_request.url' ${GITHUB_EVENT_PATH})
|
||||
|
||||
curl --request GET \
|
||||
--url "${PR_URL}" \
|
||||
--header "authorization: Bearer ${GITHUB_TOKEN}" \
|
||||
--header "content-type: application/json" > pr.json
|
||||
|
||||
ACTOR=$(jq -r '.user.login' pr.json)
|
||||
BRANCH=$(jq -r '.head.ref' pr.json)
|
||||
|
||||
curl --request GET \
|
||||
--url "https://api.github.com/repos/${GITHUB_REPOSITORY}/actions/runs?event=pull_request&actor=${ACTOR}&branch=${BRANCH}" \
|
||||
--header "authorization: Bearer ${GITHUB_TOKEN}" \
|
||||
--header "content-type: application/json" | jq '.workflow_runs | max_by(.run_number)' > run.json
|
||||
|
||||
RERUN_URL=$(jq -r '.rerun_url' run.json)
|
||||
|
||||
curl --request POST \
|
||||
--url "${RERUN_URL}" \
|
||||
--header "authorization: Bearer ${GITHUB_TOKEN}" \
|
||||
--header "content-type: application/json"
|
||||
|
||||
|
||||
REACTION_URL="$(jq -r '.comment.url' ${GITHUB_EVENT_PATH})/reactions"
|
||||
|
||||
curl --request POST \
|
||||
--url "${REACTION_URL}" \
|
||||
--header "authorization: Bearer ${GITHUB_TOKEN}" \
|
||||
--header "accept: application/vnd.github.squirrel-girl-preview+json" \
|
||||
--header "content-type: application/json" \
|
||||
--data '{ "content" : "rocket" }'
|
17
.github/workflows/commands.yml
vendored
Normal file
17
.github/workflows/commands.yml
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
name: commands
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
retest:
|
||||
if: github.repository == 'containernetworking/plugins'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Re-Test Action
|
||||
uses: ./.github/actions/retest-action
|
||||
with:
|
||||
token: ${{ secrets.REPO_ACCESS_TOKEN }}
|
69
.github/workflows/test.yaml
vendored
Normal file
69
.github/workflows/test.yaml
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
---
|
||||
name: test
|
||||
|
||||
on: ["push", "pull_request"]
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.15"
|
||||
LINUX_ARCHES: "amd64 386 arm arm64 s390x mips64le ppc64le"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build all linux architectures
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: setup go
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Build on all supported architectures
|
||||
run: |
|
||||
set -e
|
||||
for arch in ${LINUX_ARCHES}; do
|
||||
echo "Building for arch $arch"
|
||||
GOARCH=$arch ./build_linux.sh
|
||||
rm bin/*
|
||||
done
|
||||
|
||||
test-linux:
|
||||
name: Run tests on Linux amd64
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: setup go
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- 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
|
||||
|
||||
- name: test
|
||||
run: PATH=$PATH:$(go env GOPATH)/bin COVERALLS=1 ./test_linux.sh
|
||||
|
||||
- name: Send coverage to coveralls
|
||||
env:
|
||||
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
PATH=$PATH:$(go env GOPATH)/bin
|
||||
gover
|
||||
goveralls -coverprofile=gover.coverprofile -service=github
|
||||
|
||||
test-win:
|
||||
name: Build and run tests on Windows
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: setup go
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
- uses: actions/checkout@v2
|
||||
- name: test
|
||||
run: bash ./test_windows.sh
|
52
.travis.yml
52
.travis.yml
@ -1,52 +0,0 @@
|
||||
language: go
|
||||
sudo: required
|
||||
dist: xenial
|
||||
|
||||
go:
|
||||
- 1.11.x
|
||||
- 1.12.x
|
||||
- 1.13.x
|
||||
|
||||
env:
|
||||
global:
|
||||
- PATH=$GOROOT/bin:$GOPATH/bin:$PATH
|
||||
- CGO_ENABLED=0
|
||||
matrix:
|
||||
- TARGET=386
|
||||
- TARGET=amd64
|
||||
- TARGET=arm
|
||||
- TARGET=arm64
|
||||
- TARGET=ppc64le
|
||||
- TARGET=s390x
|
||||
- TARGET=mips64le
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- os: windows
|
||||
env: TARGET=amd64
|
||||
go: 1.11.x
|
||||
- os: windows
|
||||
env: TARGET=amd64
|
||||
go: 1.12.x
|
||||
|
||||
install:
|
||||
- go get github.com/onsi/ginkgo/ginkgo
|
||||
- go get github.com/containernetworking/cni/cnitool
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
- go get github.com/modocache/gover
|
||||
- go get github.com/mattn/goveralls
|
||||
|
||||
script:
|
||||
- |
|
||||
if [ "${TARGET}" == "amd64" ]; then
|
||||
GOARCH="${TARGET}" ./test_${TRAVIS_OS_NAME}.sh
|
||||
else
|
||||
GOARCH="${TARGET}" ./build_linux.sh
|
||||
fi
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
|
||||
git:
|
||||
depth: 9999999
|
@ -2,9 +2,9 @@
|
||||
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)
|
||||
- Casey Callendrello <cdc@redhat.com> (@squeed)
|
||||
- Dan Williams <dcbw@redhat.com> (@dcbw)
|
||||
- Gabe Rosenhouse <grosenhouse@pivotal.io> (@rosenhouse)
|
||||
- Matt Dupre <matt@tigera.io> (@matthewdupre)
|
||||
- Michael Cambria <mcambria@redhat.com> (@mccv1r0)
|
||||
- Piotr Skarmuk <piotr.skarmuk@gmail.com> (@jellonek)
|
||||
- Stefan Junker <stefan.junker@coreos.com> (@steveeJ)
|
10
README.md
10
README.md
@ -1,7 +1,7 @@
|
||||
[](https://travis-ci.org/containernetworking/plugins)
|
||||
|
||||
# plugins
|
||||
Some CNI network plugins, maintained by the containernetworking team. For more information, see the individual READMEs.
|
||||
Some CNI network plugins, maintained by the containernetworking team. For more information, see the [CNI website](https://www.cni.dev).
|
||||
|
||||
Read [CONTRIBUTING](CONTRIBUTING.md) for build and test instructions.
|
||||
|
||||
@ -32,3 +32,11 @@ Read [CONTRIBUTING](CONTRIBUTING.md) for build and test instructions.
|
||||
|
||||
### Sample
|
||||
The sample plugin provides an example for building your own plugin.
|
||||
|
||||
## Contact
|
||||
|
||||
For any questions about CNI, please reach out via:
|
||||
- Email: [cni-dev](https://groups.google.com/forum/#!forum/cni-dev)
|
||||
- Slack: #cni on the [CNCF slack](https://slack.cncf.io/).
|
||||
|
||||
If you have a _security_ issue to report, please do so privately to the email addresses listed in the [OWNERS](OWNERS.md) file.
|
||||
|
18
Vagrantfile
vendored
18
Vagrantfile
vendored
@ -1,18 +0,0 @@
|
||||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
Vagrant.configure(2) do |config|
|
||||
config.vm.box = "bento/ubuntu-16.04"
|
||||
|
||||
config.vm.synced_folder "..", "/go/src/github.com/containernetworking"
|
||||
|
||||
config.vm.provision "shell", inline: <<-SHELL
|
||||
set -e -x -u
|
||||
apt-get update -y || (sleep 40 && apt-get update -y)
|
||||
apt-get install -y git gcc-multilib gcc-mingw-w64
|
||||
wget -qO- https://storage.googleapis.com/golang/go1.12.7.linux-amd64.tar.gz | tar -C /usr/local -xz
|
||||
echo 'export GOPATH=/go' >> /root/.bashrc
|
||||
echo 'export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin' >> /root/.bashrc
|
||||
cd /go/src/github.com/containernetworking/plugins
|
||||
SHELL
|
||||
end
|
@ -1,21 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
cd $(dirname "$0")
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
if [ "$(uname)" == "Darwin" ]; then
|
||||
export GOOS="${GOOS:-linux}"
|
||||
fi
|
||||
|
||||
ORG_PATH="github.com/containernetworking"
|
||||
export REPO_PATH="${ORG_PATH}/plugins"
|
||||
|
||||
if [ ! -h gopath/src/${REPO_PATH} ]; then
|
||||
mkdir -p gopath/src/${ORG_PATH}
|
||||
ln -s ../../../.. gopath/src/${REPO_PATH} || exit 255
|
||||
fi
|
||||
|
||||
export GOPATH=${PWD}/gopath
|
||||
export GO="${GO:-go}"
|
||||
export GOFLAGS="${GOFLAGS} -mod=vendor"
|
||||
|
||||
mkdir -p "${PWD}/bin"
|
||||
@ -25,9 +15,9 @@ PLUGINS="plugins/meta/* plugins/main/* plugins/ipam/*"
|
||||
for d in $PLUGINS; do
|
||||
if [ -d "$d" ]; then
|
||||
plugin="$(basename "$d")"
|
||||
if [ $plugin != "windows" ]; then
|
||||
if [ "${plugin}" != "windows" ]; then
|
||||
echo " $plugin"
|
||||
$GO build -o "${PWD}/bin/$plugin" "$@" "$REPO_PATH"/$d
|
||||
${GO:-go} build -o "${PWD}/bin/$plugin" "$@" ./"$d"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
@ -1,25 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
cd $(dirname "$0")
|
||||
|
||||
ORG_PATH="github.com/containernetworking"
|
||||
export REPO_PATH="${ORG_PATH}/plugins"
|
||||
|
||||
export GOPATH=$(mktemp -d)
|
||||
mkdir -p ${GOPATH}/src/${ORG_PATH}
|
||||
trap "{ rm -rf $GOPATH; }" EXIT
|
||||
ln -s ${PWD} ${GOPATH}/src/${REPO_PATH} || exit 255
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
export GO="${GO:-go}"
|
||||
export GOOS=windows
|
||||
export GOFLAGS="${GOFLAGS} -mod=vendor"
|
||||
echo $GOFLAGS
|
||||
echo "$GOFLAGS"
|
||||
|
||||
PLUGINS=$(cat plugins/windows_only.txt)
|
||||
PLUGINS=$(cat plugins/windows_only.txt | dos2unix )
|
||||
for d in $PLUGINS; do
|
||||
if [ -d "$d" ]; then
|
||||
plugin="$(basename "$d").exe"
|
||||
echo " $plugin"
|
||||
$GO build -o "${PWD}/bin/$plugin" "$@" "$REPO_PATH"/$d
|
||||
fi
|
||||
plugin="$(basename "$d").exe"
|
||||
echo "building $plugin"
|
||||
$GO build -o "${PWD}/bin/$plugin" "$@" ./"${d}"
|
||||
done
|
||||
|
25
go.mod
25
go.mod
@ -1,40 +1,29 @@
|
||||
module github.com/containernetworking/plugins
|
||||
|
||||
go 1.12
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.4.11 // indirect
|
||||
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.1
|
||||
github.com/coreos/go-iptables v0.4.5
|
||||
github.com/containernetworking/cni v0.8.1
|
||||
github.com/coreos/go-iptables v0.5.0
|
||||
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
|
||||
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5
|
||||
github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4 // indirect
|
||||
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c
|
||||
github.com/golang/protobuf v1.3.1 // indirect
|
||||
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56
|
||||
github.com/juju/errors v0.0.0-20180806074554-22422dad46e1
|
||||
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 // indirect
|
||||
github.com/juju/testing v0.0.0-20190613124551-e81189438503 // indirect
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/mattn/go-shellwords v1.0.3
|
||||
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b
|
||||
github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a
|
||||
github.com/onsi/ginkgo v1.12.1
|
||||
github.com/onsi/gomega v1.10.3
|
||||
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8
|
||||
github.com/sirupsen/logrus v1.0.6 // indirect
|
||||
github.com/stretchr/testify v1.3.0 // indirect
|
||||
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf
|
||||
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc // indirect
|
||||
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941 // indirect
|
||||
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 // indirect
|
||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f
|
||||
github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852
|
||||
golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 // indirect
|
||||
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce // indirect
|
||||
gopkg.in/yaml.v2 v2.2.2 // indirect
|
||||
)
|
||||
|
110
go.sum
110
go.sum
@ -6,14 +6,10 @@ 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.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/containernetworking/cni v0.8.1 h1:7zpDnQ3T3s4ucOuJ/ZCLrYBxzkg0AELFfII3Epo9TmI=
|
||||
github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
|
||||
github.com/coreos/go-iptables v0.5.0 h1:mw6SAibtHKZcNzAsOxjoHIG0gy5YFHhypWSSNc6EjbQ=
|
||||
github.com/coreos/go-iptables v0.5.0/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=
|
||||
@ -26,29 +22,35 @@ github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4 h1:itqmmf1PFpC4n5
|
||||
github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c h1:RBUpb2b14UnmRHNd2uHz20ZHLDK+SW5Us/vWF5IHRaY=
|
||||
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56 h1:742eGXur0715JMq73aD95/FU0XpVKXqNuTnEfXsLOYQ=
|
||||
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=
|
||||
github.com/juju/errors v0.0.0-20180806074554-22422dad46e1 h1:wnhMXidtb70kDZCeLt/EfsVtkXS5c8zLnE9y/6DIRAU=
|
||||
github.com/juju/errors v0.0.0-20180806074554-22422dad46e1/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
|
||||
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 h1:UUHMLvzt/31azWTN/ifGWef4WUqvXk0iRqdhdy/2uzI=
|
||||
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
|
||||
github.com/juju/testing v0.0.0-20190613124551-e81189438503 h1:ZUgTbk8oHgP0jpMieifGC9Lv47mHn8Pb3mFX3/Ew4iY=
|
||||
github.com/juju/testing v0.0.0-20190613124551-e81189438503/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/mattn/go-shellwords v1.0.3 h1:K/VxK7SZ+cvuPgFSLKi5QPI9Vr/ipOf4C1gN+ntueUk=
|
||||
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b h1:Ey6yH0acn50T/v6CB75bGP4EMJqnv9WvnjN7oZaj+xE=
|
||||
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a h1:KfNOeFvoAssuZLT7IntKZElKwi/5LRuxY71k+t6rfaM=
|
||||
github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1 h1:mFwc4LvZ0xpSvDZ3E+k8Yte0hLOMxXUlP+yXtJqkYfQ=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA=
|
||||
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8 h1:2c1EFnZHIPCW8qKWgHMH/fX2PkSabFc5mrVzfUNdg5U=
|
||||
@ -58,24 +60,50 @@ github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjM
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf h1:3J37+NPjNyGW/dbfXtj3yWuF9OEepIdGOXRaJGbORV8=
|
||||
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
|
||||
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc h1:R83G5ikgLMxrBvLh22JhdfI8K6YXEPHx5P03Uu3DRs4=
|
||||
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
|
||||
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941 h1:qBTHLajHecfu+xzRI9PqVDcqx7SdHj9d4B+EzSn3tAc=
|
||||
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 h1:Y/KGZSOdz/2r0WJ9Mkmz6NJBusp0kiNx1Cn82lzJQ6w=
|
||||
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4=
|
||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852 h1:cPXZWzzG0NllBLdjWoD1nDfaqu98YMv+OneaKc8sPOA=
|
||||
github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=
|
||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0 h1:wBouT66WTYFXdxfVdz9sVWARVd/2vfGcmI45D2gj45M=
|
||||
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637 h1:O5hKNaGxIT4A8OTMnuh6UpmBdI3SAPxlZ3g0olDrJVM=
|
||||
golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo=
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 h1:OAj3g0cR6Dx/R07QgQe8wkA9RNjB2u4i700xBkIT4e0=
|
||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
|
||||
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU=
|
||||
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
@ -225,23 +225,22 @@ func (n Namespace) Del() {
|
||||
}
|
||||
|
||||
func makeTcpClientInNS(netns string, address string, port int, numBytes int) {
|
||||
message := bytes.Repeat([]byte{'a'}, numBytes)
|
||||
payload := bytes.Repeat([]byte{'a'}, numBytes)
|
||||
message := string(payload)
|
||||
|
||||
bin, err := exec.LookPath("nc")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
var cmd *exec.Cmd
|
||||
if netns != "" {
|
||||
netns = filepath.Base(netns)
|
||||
cmd = exec.Command("ip", "netns", "exec", netns, bin, "-v", address, strconv.Itoa(port))
|
||||
cmd = exec.Command("ip", "netns", "exec", netns, echoClientBinaryPath, "--target", fmt.Sprintf("%s:%d", address, port), "--message", message)
|
||||
} else {
|
||||
cmd = exec.Command("nc", address, strconv.Itoa(port))
|
||||
cmd = exec.Command(echoClientBinaryPath, "--target", fmt.Sprintf("%s:%d", address, port), "--message", message)
|
||||
}
|
||||
cmd.Stdin = bytes.NewBuffer([]byte(message))
|
||||
cmd.Stderr = GinkgoWriter
|
||||
out, err := cmd.Output()
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(string(out)).To(Equal(string(message)))
|
||||
Expect(string(out)).To(Equal(message))
|
||||
}
|
||||
|
||||
func startEchoServerInNamespace(netNS Namespace) (int, *gexec.Session, error) {
|
||||
|
@ -14,11 +14,10 @@
|
||||
package integration_test
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
"github.com/onsi/ginkgo/config"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/onsi/gomega/gexec"
|
||||
)
|
||||
@ -28,15 +27,18 @@ func TestIntegration(t *testing.T) {
|
||||
RunSpecs(t, "integration")
|
||||
}
|
||||
|
||||
var echoServerBinaryPath string
|
||||
var echoServerBinaryPath, echoClientBinaryPath string
|
||||
|
||||
var _ = SynchronizedBeforeSuite(func() []byte {
|
||||
binaryPath, err := gexec.Build("github.com/containernetworking/plugins/pkg/testutils/echosvr")
|
||||
serverBinaryPath, err := gexec.Build("github.com/containernetworking/plugins/pkg/testutils/echo/server")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return []byte(binaryPath)
|
||||
clientBinaryPath, err := gexec.Build("github.com/containernetworking/plugins/pkg/testutils/echo/client")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return []byte(strings.Join([]string{serverBinaryPath, clientBinaryPath}, ","))
|
||||
}, func(data []byte) {
|
||||
echoServerBinaryPath = string(data)
|
||||
rand.Seed(config.GinkgoConfig.RandomSeed + int64(GinkgoParallelNode()))
|
||||
binaries := strings.Split(string(data), ",")
|
||||
echoServerBinaryPath = binaries[0]
|
||||
echoClientBinaryPath = binaries[1]
|
||||
})
|
||||
|
||||
var _ = SynchronizedAfterSuite(func() {}, func() {
|
||||
|
37
pkg/errors/errors.go
Normal file
37
pkg/errors/errors.go
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright 2020 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 errors
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Annotate is used to add extra context to an existing error. The return will be
|
||||
// a new error which carries error message from both context message and existing error.
|
||||
func Annotate(err error, message string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("%s: %v", message, err)
|
||||
}
|
||||
|
||||
// Annotatef is used to add extra context with args to an existing error. The return will be
|
||||
// a new error which carries error message from both context message and existing error.
|
||||
func Annotatef(err error, message string, args ...interface{}) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("%s: %v", fmt.Sprintf(message, args...), err)
|
||||
}
|
96
pkg/errors/errors_test.go
Normal file
96
pkg/errors/errors_test.go
Normal file
@ -0,0 +1,96 @@
|
||||
// Copyright 2020 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 errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAnnotate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
existingErr error
|
||||
contextMessage string
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
"nil error",
|
||||
nil,
|
||||
"context",
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"normal case",
|
||||
errors.New("existing error"),
|
||||
"context",
|
||||
errors.New("context: existing error"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if !reflect.DeepEqual(Annotatef(test.existingErr, test.contextMessage), test.expectedErr) {
|
||||
t.Errorf("test case %s fails", test.name)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnnotatef(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
existingErr error
|
||||
contextMessage string
|
||||
contextArgs []interface{}
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
"nil error",
|
||||
nil,
|
||||
"context",
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"normal case",
|
||||
errors.New("existing error"),
|
||||
"context",
|
||||
nil,
|
||||
errors.New("context: existing error"),
|
||||
},
|
||||
{
|
||||
"normal case with args",
|
||||
errors.New("existing error"),
|
||||
"context %s %d",
|
||||
[]interface{}{
|
||||
"arg",
|
||||
100,
|
||||
},
|
||||
errors.New("context arg 100: existing error"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if !reflect.DeepEqual(Annotatef(test.existingErr, test.contextMessage, test.contextArgs...), test.expectedErr) {
|
||||
t.Errorf("test case %s fails", test.name)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -21,9 +21,10 @@ import (
|
||||
|
||||
"github.com/Microsoft/hcsshim"
|
||||
"github.com/Microsoft/hcsshim/hcn"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/juju/errors"
|
||||
"github.com/containernetworking/plugins/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -64,14 +65,14 @@ func GenerateHnsEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcsshim.HNSEndpoint
|
||||
// run the IPAM plugin and get back the config to apply
|
||||
hnsEndpoint, err := hcsshim.GetHNSEndpointByName(epInfo.EndpointName)
|
||||
if err != nil && !hcsshim.IsNotExist(err) {
|
||||
return nil, errors.Annotatef(err, "Attempt to get endpoint \"%v\" failed", epInfo.EndpointName)
|
||||
return nil, errors.Annotatef(err, "failed to get endpoint %q", epInfo.EndpointName)
|
||||
}
|
||||
|
||||
if hnsEndpoint != nil {
|
||||
if hnsEndpoint.VirtualNetwork != epInfo.NetworkId {
|
||||
_, err = hnsEndpoint.Delete()
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "Failed to delete endpoint %v", epInfo.EndpointName)
|
||||
return nil, errors.Annotatef(err, "failed to delete endpoint %s", epInfo.EndpointName)
|
||||
}
|
||||
hnsEndpoint = nil
|
||||
}
|
||||
@ -98,7 +99,7 @@ func GenerateHcnEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcn.HostComputeEndp
|
||||
// run the IPAM plugin and get back the config to apply
|
||||
hcnEndpoint, err := hcn.GetEndpointByName(epInfo.EndpointName)
|
||||
if err != nil && !hcn.IsNotFoundError(err) {
|
||||
return nil, errors.Annotatef(err, "Attempt to get endpoint \"%v\" failed", epInfo.EndpointName)
|
||||
return nil, errors.Annotatef(err, "failed to get endpoint %q", epInfo.EndpointName)
|
||||
}
|
||||
|
||||
if hcnEndpoint != nil {
|
||||
@ -108,12 +109,10 @@ func GenerateHcnEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcn.HostComputeEndp
|
||||
if !strings.EqualFold(hcnEndpoint.HostComputeNetwork, epInfo.NetworkId) {
|
||||
err = hcnEndpoint.Delete()
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "Failed to delete endpoint %v", epInfo.EndpointName)
|
||||
hcnEndpoint = nil
|
||||
|
||||
return nil, errors.Annotatef(err, "failed to delete endpoint %s", epInfo.EndpointName)
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("Endpoint \"%v\" already exits", epInfo.EndpointName)
|
||||
return nil, fmt.Errorf("endpoint %q already exits", epInfo.EndpointName)
|
||||
}
|
||||
}
|
||||
|
||||
@ -270,7 +269,7 @@ func AddHcnEndpoint(epName string, expectedNetworkId string, namespace string,
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "failed to Remove Endpoint after AddNamespaceEndpoint failure")
|
||||
}
|
||||
return nil, errors.Annotatef(err, "Failed to Add endpoint to namespace")
|
||||
return nil, errors.Annotate(err, "failed to Add endpoint to namespace")
|
||||
}
|
||||
return hcnEndpoint, nil
|
||||
|
||||
|
@ -1,13 +0,0 @@
|
||||
package hns_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestHns(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Hns Suite")
|
||||
}
|
@ -20,10 +20,11 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"strings"
|
||||
|
||||
"github.com/Microsoft/hcsshim/hcn"
|
||||
"github.com/buger/jsonparser"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NetConf is the CNI spec
|
||||
@ -46,8 +47,16 @@ type RuntimeDNS struct {
|
||||
Search []string `json:"searches,omitempty"`
|
||||
}
|
||||
|
||||
type PortMapEntry struct {
|
||||
HostPort int `json:"hostPort"`
|
||||
ContainerPort int `json:"containerPort"`
|
||||
Protocol string `json:"protocol"`
|
||||
HostIP string `json:"hostIP,omitempty"`
|
||||
}
|
||||
|
||||
type RuntimeConfig struct {
|
||||
DNS RuntimeDNS `json:"dns"`
|
||||
DNS RuntimeDNS `json:"dns"`
|
||||
PortMaps []PortMapEntry `json:"portMappings,omitempty"`
|
||||
}
|
||||
|
||||
type policy struct {
|
||||
@ -207,3 +216,21 @@ func (n *NetConf) ApplyDefaultPAPolicy(paAddress string) {
|
||||
Value: []byte(`{"Type": "PA", "PA": "` + paAddress + `"}`),
|
||||
})
|
||||
}
|
||||
|
||||
// ApplyPortMappingPolicy is used to configure HostPort<>ContainerPort mapping in HNS
|
||||
func (n *NetConf) ApplyPortMappingPolicy(portMappings []PortMapEntry) {
|
||||
if portMappings == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if n.Policies == nil {
|
||||
n.Policies = make([]policy, 0)
|
||||
}
|
||||
|
||||
for _, portMapping := range portMappings {
|
||||
n.Policies = append(n.Policies, policy{
|
||||
Name: "EndpointPolicy",
|
||||
Value: []byte(fmt.Sprintf(`{"Type": "NAT", "InternalPort": %d, "ExternalPort": %d, "Protocol": "%s"}`, portMapping.ContainerPort, portMapping.HostPort, portMapping.Protocol)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -128,6 +128,53 @@ var _ = Describe("HNS NetConf", func() {
|
||||
})
|
||||
})
|
||||
|
||||
Describe("ApplyPortMappingPolicy", func() {
|
||||
Context("when portMappings not activated", func() {
|
||||
It("does nothing", func() {
|
||||
n := NetConf{}
|
||||
n.ApplyPortMappingPolicy(nil)
|
||||
Expect(n.Policies).Should(BeNil())
|
||||
|
||||
n.ApplyPortMappingPolicy([]PortMapEntry{})
|
||||
Expect(n.Policies).Should(HaveLen(0))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when portMappings is activated", func() {
|
||||
It("creates NAT policies", func() {
|
||||
n := NetConf{}
|
||||
n.ApplyPortMappingPolicy([]PortMapEntry{
|
||||
{
|
||||
ContainerPort: 80,
|
||||
HostPort: 8080,
|
||||
Protocol: "TCP",
|
||||
HostIP: "ignored",
|
||||
},
|
||||
})
|
||||
|
||||
Expect(n.Policies).Should(HaveLen(1))
|
||||
|
||||
policy := n.Policies[0]
|
||||
Expect(policy.Name).Should(Equal("EndpointPolicy"))
|
||||
|
||||
value := make(map[string]interface{})
|
||||
json.Unmarshal(policy.Value, &value)
|
||||
|
||||
Expect(value).Should(HaveKey("Type"))
|
||||
Expect(value["Type"]).Should(Equal("NAT"))
|
||||
|
||||
Expect(value).Should(HaveKey("InternalPort"))
|
||||
Expect(value["InternalPort"]).Should(Equal(float64(80)))
|
||||
|
||||
Expect(value).Should(HaveKey("ExternalPort"))
|
||||
Expect(value["ExternalPort"]).Should(Equal(float64(8080)))
|
||||
|
||||
Expect(value).Should(HaveKey("Protocol"))
|
||||
Expect(value["Protocol"]).Should(Equal("TCP"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("MarshalPolicies", func() {
|
||||
Context("when not set by user", func() {
|
||||
It("sets it by adding a policy", func() {
|
||||
|
@ -21,10 +21,12 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/utils/hwaddr"
|
||||
"github.com/safchain/ethtool"
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/utils/hwaddr"
|
||||
"github.com/containernetworking/plugins/pkg/utils/sysctl"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -158,6 +160,9 @@ func SetupVethWithName(contVethName, hostVethName string, mtu int, hostNS ns.Net
|
||||
if err = netlink.LinkSetUp(hostVeth); err != nil {
|
||||
return fmt.Errorf("failed to set %q up: %v", hostVethName, err)
|
||||
}
|
||||
|
||||
// we want to own the routes for this interface
|
||||
_, _ = sysctl.Sysctl(fmt.Sprintf("net/ipv6/conf/%s/accept_ra", hostVethName), "0")
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
@ -178,7 +183,7 @@ func SetupVeth(contVethName string, mtu int, hostNS ns.NetNS) (net.Interface, ne
|
||||
func DelLinkByName(ifName string) error {
|
||||
iface, err := netlink.LinkByName(ifName)
|
||||
if err != nil {
|
||||
if err.Error() == "Link not found" {
|
||||
if _, ok := err.(netlink.LinkNotFoundError); ok {
|
||||
return ErrLinkNotFound
|
||||
}
|
||||
return fmt.Errorf("failed to lookup %q: %v", ifName, err)
|
||||
@ -195,7 +200,7 @@ func DelLinkByName(ifName string) error {
|
||||
func DelLinkByNameAddr(ifName string) ([]*net.IPNet, error) {
|
||||
iface, err := netlink.LinkByName(ifName)
|
||||
if err != nil {
|
||||
if err != nil && err.Error() == "Link not found" {
|
||||
if _, ok := err.(netlink.LinkNotFoundError); ok {
|
||||
return nil, ErrLinkNotFound
|
||||
}
|
||||
return nil, fmt.Errorf("failed to lookup %q: %v", ifName, err)
|
||||
|
@ -26,6 +26,11 @@ import (
|
||||
|
||||
// Returns an object representing the current OS thread's network namespace
|
||||
func GetCurrentNS() (NetNS, error) {
|
||||
// Lock the thread in case other goroutine executes in it and changes its
|
||||
// network namespace after getCurrentThreadNetNSPath(), otherwise it might
|
||||
// return an unexpected network namespace.
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
return GetNS(getCurrentThreadNetNSPath())
|
||||
}
|
||||
|
||||
@ -178,7 +183,16 @@ func (ns *netNS) Do(toRun func(NetNS) error) error {
|
||||
if err = ns.Set(); err != nil {
|
||||
return fmt.Errorf("error switching to ns %v: %v", ns.file.Name(), err)
|
||||
}
|
||||
defer threadNS.Set() // switch back
|
||||
defer func() {
|
||||
err := threadNS.Set() // switch back
|
||||
if err == 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()
|
||||
}
|
||||
}()
|
||||
|
||||
return toRun(hostNS)
|
||||
}
|
||||
@ -193,6 +207,10 @@ func (ns *netNS) Do(toRun func(NetNS) error) error {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
// Start the callback in a new green thread 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.
|
||||
var innerError error
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/testutils"
|
||||
@ -118,6 +119,33 @@ var _ = Describe("Linux namespace operations", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
Context("when called concurrently", func() {
|
||||
It("provides the original namespace as the argument to the callback", func() {
|
||||
concurrency := 200
|
||||
origNS, err := ns.GetCurrentNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
origNSInode, err := getInodeNS(origNS)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(concurrency)
|
||||
for i := 0; i < concurrency; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
targetNetNS.Do(func(hostNS ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
hostNSInode, err := getInodeNS(hostNS)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(hostNSInode).To(Equal(origNSInode))
|
||||
return nil
|
||||
})
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
})
|
||||
})
|
||||
|
||||
Context("when the callback returns an error", func() {
|
||||
It("restores the calling thread to the original namespace before returning", func() {
|
||||
err := originalNetNS.Do(func(ns.NetNS) error {
|
||||
@ -172,7 +200,9 @@ var _ = Describe("Linux namespace operations", func() {
|
||||
By("comparing against the netns inode of every thread in the process")
|
||||
for _, netnsPath := range allNetNSInCurrentProcess() {
|
||||
netnsInode, err := getInode(netnsPath)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
if !os.IsNotExist(err) {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
Expect(netnsInode).NotTo(Equal(createdNetNSInode))
|
||||
}
|
||||
})
|
||||
|
90
pkg/testutils/echo/client/client.go
Normal file
90
pkg/testutils/echo/client/client.go
Normal file
@ -0,0 +1,90 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
)
|
||||
|
||||
func main() {
|
||||
target := flag.String("target", "", "the server address")
|
||||
payload := flag.String("message", "", "the message to send to the server")
|
||||
protocol := flag.String("protocol", "tcp", "the protocol to use with the server [udp,tcp], default tcp")
|
||||
flag.Parse()
|
||||
|
||||
if *target == "" || *payload == "" {
|
||||
flag.Usage()
|
||||
panic("invalid arguments")
|
||||
}
|
||||
|
||||
switch *protocol {
|
||||
case "tcp":
|
||||
connectTCP(*target, *payload)
|
||||
case "udp":
|
||||
connectUDP(*target, *payload)
|
||||
default:
|
||||
panic("invalid protocol")
|
||||
}
|
||||
}
|
||||
|
||||
func connectTCP(target, payload string) {
|
||||
conn, err := net.Dial("tcp", target)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Failed to open connection to [%s] %v", target, err))
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
_, err = conn.Write([]byte(payload))
|
||||
if err != nil {
|
||||
panic("Failed to send payload")
|
||||
}
|
||||
_, err = conn.Write([]byte("\n"))
|
||||
if err != nil {
|
||||
panic("Failed to send payload")
|
||||
}
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
n, err := conn.Read(buf)
|
||||
fmt.Print(string(buf[:n]))
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
panic("Failed to read from socket")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UDP uses a constant source port to trigger conntrack problems
|
||||
func connectUDP(target, payload string) {
|
||||
LocalAddr, err := net.ResolveUDPAddr("udp", ":54321")
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Failed to resolve UDP local address on port 54321 %v", err))
|
||||
}
|
||||
RemoteAddr, err := net.ResolveUDPAddr("udp", target)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Failed to resolve UDP remote address [%s] %v", target, err))
|
||||
}
|
||||
conn, err := net.DialUDP("udp", LocalAddr, RemoteAddr)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Failed to open connection to [%s] %v", target, err))
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
_, err = conn.Write([]byte(payload))
|
||||
if err != nil {
|
||||
panic("Failed to send payload")
|
||||
}
|
||||
_, err = conn.Write([]byte("\n"))
|
||||
if err != nil {
|
||||
panic("Failed to send payload")
|
||||
}
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
panic("Failed to read from socket")
|
||||
}
|
||||
fmt.Print(string(buf[:n]))
|
||||
}
|
98
pkg/testutils/echo/echo_test.go
Normal file
98
pkg/testutils/echo/echo_test.go
Normal file
@ -0,0 +1,98 @@
|
||||
package main_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/onsi/gomega/gbytes"
|
||||
"github.com/onsi/gomega/gexec"
|
||||
)
|
||||
|
||||
var serverBinaryPath, clientBinaryPath string
|
||||
|
||||
var _ = SynchronizedBeforeSuite(func() []byte {
|
||||
serverBinaryPath, err := gexec.Build("github.com/containernetworking/plugins/pkg/testutils/echo/server")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
clientBinaryPath, err := gexec.Build("github.com/containernetworking/plugins/pkg/testutils/echo/client")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return []byte(strings.Join([]string{serverBinaryPath, clientBinaryPath}, ","))
|
||||
}, func(data []byte) {
|
||||
binaries := strings.Split(string(data), ",")
|
||||
serverBinaryPath = binaries[0]
|
||||
clientBinaryPath = binaries[1]
|
||||
})
|
||||
|
||||
var _ = SynchronizedAfterSuite(func() {}, func() {
|
||||
gexec.CleanupBuildArtifacts()
|
||||
})
|
||||
|
||||
var _ = Describe("Echosvr", func() {
|
||||
var session *gexec.Session
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
cmd := exec.Command(serverBinaryPath)
|
||||
session, err = gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
session.Kill().Wait()
|
||||
})
|
||||
|
||||
Context("Server test", func() {
|
||||
It("starts and doesn't terminate immediately", func() {
|
||||
Consistently(session).ShouldNot(gexec.Exit())
|
||||
})
|
||||
|
||||
tryConnect := func() (net.Conn, error) {
|
||||
programOutput := session.Out.Contents()
|
||||
addr := strings.TrimSpace(string(programOutput))
|
||||
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conn, err
|
||||
}
|
||||
|
||||
It("prints its listening address to stdout", func() {
|
||||
Eventually(session.Out).Should(gbytes.Say("\n"))
|
||||
conn, err := tryConnect()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
conn.Close()
|
||||
})
|
||||
|
||||
It("will echo data back to us", func() {
|
||||
Eventually(session.Out).Should(gbytes.Say("\n"))
|
||||
conn, err := tryConnect()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer conn.Close()
|
||||
|
||||
fmt.Fprintf(conn, "hello\n")
|
||||
Expect(ioutil.ReadAll(conn)).To(Equal([]byte("hello")))
|
||||
})
|
||||
})
|
||||
|
||||
Context("Client Server Test", func() {
|
||||
It("starts and doesn't terminate immediately", func() {
|
||||
Consistently(session).ShouldNot(gexec.Exit())
|
||||
})
|
||||
|
||||
It("connects successfully using echo client", func() {
|
||||
Eventually(session.Out).Should(gbytes.Say("\n"))
|
||||
serverAddress := strings.TrimSpace(string(session.Out.Contents()))
|
||||
fmt.Println("Server address", string(serverAddress))
|
||||
|
||||
cmd := exec.Command(clientBinaryPath, "-target", serverAddress, "-message", "hello")
|
||||
clientSession, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Eventually(clientSession.Out).Should(gbytes.Say("hello"))
|
||||
Eventually(clientSession).Should(gexec.Exit())
|
||||
})
|
||||
})
|
||||
})
|
@ -10,6 +10,7 @@ import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
@ -17,21 +18,50 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Start TCP server
|
||||
listener, err := net.Listen("tcp", ":")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer listener.Close()
|
||||
// use the same port for UDP
|
||||
_, port, err := net.SplitHostPort(listener.Addr().String())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("127.0.0.1:%s\n", port)
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
go func() {
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
go handleConnection(conn)
|
||||
}
|
||||
}()
|
||||
|
||||
// Start UDP server
|
||||
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%s", port))
|
||||
if err != nil {
|
||||
log.Fatalf("Error from net.ResolveUDPAddr(): %s", err)
|
||||
}
|
||||
sock, err := net.ListenUDP("udp", addr)
|
||||
if err != nil {
|
||||
log.Fatalf("Error from ListenUDP(): %s", err)
|
||||
}
|
||||
defer sock.Close()
|
||||
|
||||
buffer := make([]byte, 1024)
|
||||
for {
|
||||
n, addr, err := sock.ReadFrom(buffer)
|
||||
if err != nil {
|
||||
log.Fatalf("Error from ReadFrom(): %s", err)
|
||||
}
|
||||
sock.SetWriteDeadline(time.Now().Add(1 * time.Minute))
|
||||
n, err = sock.WriteTo(buffer[0:n], addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
go handleConnection(conn)
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,5 +83,4 @@ func handleConnection(conn net.Conn) {
|
||||
fmt.Fprint(os.Stderr, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
package main_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/onsi/gomega/gbytes"
|
||||
"github.com/onsi/gomega/gexec"
|
||||
)
|
||||
|
||||
var binaryPath string
|
||||
|
||||
var _ = SynchronizedBeforeSuite(func() []byte {
|
||||
binaryPath, err := gexec.Build("github.com/containernetworking/plugins/pkg/testutils/echosvr")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return []byte(binaryPath)
|
||||
}, func(data []byte) {
|
||||
binaryPath = string(data)
|
||||
})
|
||||
|
||||
var _ = SynchronizedAfterSuite(func() {}, func() {
|
||||
gexec.CleanupBuildArtifacts()
|
||||
})
|
||||
|
||||
var _ = Describe("Echosvr", func() {
|
||||
var session *gexec.Session
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
cmd := exec.Command(binaryPath)
|
||||
session, err = gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
session.Kill().Wait()
|
||||
})
|
||||
|
||||
It("starts and doesn't terminate immediately", func() {
|
||||
Consistently(session).ShouldNot(gexec.Exit())
|
||||
})
|
||||
|
||||
tryConnect := func() (net.Conn, error) {
|
||||
programOutput := session.Out.Contents()
|
||||
addr := strings.TrimSpace(string(programOutput))
|
||||
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conn, err
|
||||
}
|
||||
|
||||
It("prints its listening address to stdout", func() {
|
||||
Eventually(session.Out).Should(gbytes.Say("\n"))
|
||||
conn, err := tryConnect()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
conn.Close()
|
||||
})
|
||||
|
||||
It("will echo data back to us", func() {
|
||||
Eventually(session.Out).Should(gbytes.Say("\n"))
|
||||
conn, err := tryConnect()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer conn.Close()
|
||||
|
||||
fmt.Fprintf(conn, "hello\n")
|
||||
Expect(ioutil.ReadAll(conn)).To(Equal([]byte("hello")))
|
||||
})
|
||||
})
|
73
pkg/utils/conntrack.go
Normal file
73
pkg/utils/conntrack.go
Normal file
@ -0,0 +1,73 @@
|
||||
// Copyright 2020 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"
|
||||
"net"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Assigned Internet Protocol Numbers
|
||||
// https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml
|
||||
const (
|
||||
PROTOCOL_TCP = 6
|
||||
PROTOCOL_UDP = 17
|
||||
PROTOCOL_SCTP = 132
|
||||
)
|
||||
|
||||
// getNetlinkFamily returns the Netlink IP family constant
|
||||
func getNetlinkFamily(isIPv6 bool) netlink.InetFamily {
|
||||
if isIPv6 {
|
||||
return unix.AF_INET6
|
||||
}
|
||||
return unix.AF_INET
|
||||
}
|
||||
|
||||
// DeleteConntrackEntriesForDstIP delete the conntrack entries for the connections
|
||||
// specified by the given destination IP and protocol
|
||||
func DeleteConntrackEntriesForDstIP(dstIP string, protocol uint8) error {
|
||||
ip := net.ParseIP(dstIP)
|
||||
if ip == nil {
|
||||
return fmt.Errorf("error deleting connection tracking state, bad IP %s", ip)
|
||||
}
|
||||
family := getNetlinkFamily(ip.To4() == nil)
|
||||
|
||||
filter := &netlink.ConntrackFilter{}
|
||||
filter.AddIP(netlink.ConntrackOrigDstIP, ip)
|
||||
filter.AddProtocol(protocol)
|
||||
|
||||
_, err := netlink.ConntrackDeleteFilter(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)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteConntrackEntriesForDstPort delete the conntrack entries for the connections specified
|
||||
// by the given destination port, protocol and IP family
|
||||
func DeleteConntrackEntriesForDstPort(port uint16, protocol uint8, family netlink.InetFamily) error {
|
||||
filter := &netlink.ConntrackFilter{}
|
||||
filter.AddPort(netlink.ConntrackOrigDstPort, port)
|
||||
filter.AddProtocol(protocol)
|
||||
|
||||
_, err := netlink.ConntrackDeleteFilter(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)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -35,7 +35,7 @@ func Sysctl(name string, params ...string) (string, error) {
|
||||
}
|
||||
|
||||
func getSysctl(name string) (string, error) {
|
||||
fullName := filepath.Join("/proc/sys", strings.Replace(name, ".", "/", -1))
|
||||
fullName := filepath.Join("/proc/sys", toNormalName(name))
|
||||
fullName = filepath.Clean(fullName)
|
||||
data, err := ioutil.ReadFile(fullName)
|
||||
if err != nil {
|
||||
@ -46,7 +46,7 @@ func getSysctl(name string) (string, error) {
|
||||
}
|
||||
|
||||
func setSysctl(name, value string) (string, error) {
|
||||
fullName := filepath.Join("/proc/sys", strings.Replace(name, ".", "/", -1))
|
||||
fullName := filepath.Join("/proc/sys", toNormalName(name))
|
||||
fullName = filepath.Clean(fullName)
|
||||
if err := ioutil.WriteFile(fullName, []byte(value), 0644); err != nil {
|
||||
return "", err
|
||||
@ -54,3 +54,27 @@ func setSysctl(name, value string) (string, error) {
|
||||
|
||||
return getSysctl(name)
|
||||
}
|
||||
|
||||
// Normalize names by using slash as separator
|
||||
// Sysctl names can use dots or slashes as separator:
|
||||
// - if dots are used, dots and slashes are interchanged.
|
||||
// - if slashes are used, slashes and dots are left intact.
|
||||
// Separator in use is determined by first occurrence.
|
||||
func toNormalName(name string) string {
|
||||
interchange := false
|
||||
for _, c := range name {
|
||||
if c == '.' {
|
||||
interchange = true
|
||||
break
|
||||
}
|
||||
if c == '/' {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if interchange {
|
||||
r := strings.NewReplacer(".", "/", "/", ".")
|
||||
return r.Replace(name)
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
114
pkg/utils/sysctl/sysctl_linux_test.go
Normal file
114
pkg/utils/sysctl/sysctl_linux_test.go
Normal file
@ -0,0 +1,114 @@
|
||||
// Copyright 2017-2020 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 sysctl_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/testutils"
|
||||
"github.com/containernetworking/plugins/pkg/utils/sysctl"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
const (
|
||||
sysctlDotKeyTemplate = "net.ipv4.conf.%s.proxy_arp"
|
||||
sysctlSlashKeyTemplate = "net/ipv4/conf/%s/proxy_arp"
|
||||
)
|
||||
|
||||
var _ = Describe("Sysctl tests", func() {
|
||||
var testIfaceName string
|
||||
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())
|
||||
|
||||
testIfaceName = fmt.Sprintf("cnitest.%d", rand.Intn(100000))
|
||||
testIface := &netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: testIfaceName,
|
||||
Namespace: netlink.NsFd(int(testNs.Fd())),
|
||||
},
|
||||
}
|
||||
|
||||
err = netlink.LinkAdd(testIface)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
runtime.LockOSThread()
|
||||
err = testNs.Set()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
cleanup = func() {
|
||||
netlink.LinkDel(testIface)
|
||||
currNs.Set()
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
cleanup()
|
||||
})
|
||||
|
||||
Describe("Sysctl", func() {
|
||||
It("reads keys with dot separators", func() {
|
||||
sysctlIfaceName := strings.Replace(testIfaceName, ".", "/", -1)
|
||||
sysctlKey := fmt.Sprintf(sysctlDotKeyTemplate, sysctlIfaceName)
|
||||
|
||||
_, err := sysctl.Sysctl(sysctlKey)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Sysctl", func() {
|
||||
It("reads keys with slash separators", func() {
|
||||
sysctlKey := fmt.Sprintf(sysctlSlashKeyTemplate, testIfaceName)
|
||||
|
||||
_, err := sysctl.Sysctl(sysctlKey)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Sysctl", func() {
|
||||
It("writes keys with dot separators", func() {
|
||||
sysctlIfaceName := strings.Replace(testIfaceName, ".", "/", -1)
|
||||
sysctlKey := fmt.Sprintf(sysctlDotKeyTemplate, sysctlIfaceName)
|
||||
|
||||
_, err := sysctl.Sysctl(sysctlKey, "1")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Sysctl", func() {
|
||||
It("writes keys with slash separators", func() {
|
||||
sysctlKey := fmt.Sprintf(sysctlSlashKeyTemplate, testIfaceName)
|
||||
|
||||
_, err := sysctl.Sysctl(sysctlKey, "1")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
})
|
27
pkg/utils/sysctl/sysctl_suite_test.go
Normal file
27
pkg/utils/sysctl/sysctl_suite_test.go
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright 2017-2020 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 sysctl_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestSysctl(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Sysctl Suite")
|
||||
}
|
@ -1,39 +1,4 @@
|
||||
# dhcp plugin
|
||||
|
||||
## Overview
|
||||
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
|
||||
|
||||
With dhcp plugin the containers can get an IP allocated by a DHCP server already running on your network.
|
||||
This can be especially useful with plugin types such as [macvlan](../../main/macvlan/README.md).
|
||||
Because a DHCP lease must be periodically renewed for the duration of container lifetime, a separate daemon is required to be running.
|
||||
The same plugin binary can also be run in the daemon mode.
|
||||
|
||||
## Operation
|
||||
To use the dhcp IPAM plugin, first launch the dhcp daemon:
|
||||
|
||||
```
|
||||
# Make sure the unix socket has been removed
|
||||
$ rm -f /run/cni/dhcp.sock
|
||||
$ ./dhcp daemon
|
||||
```
|
||||
|
||||
If given `-pidfile <path>` arguments after 'daemon', the dhcp plugin will write
|
||||
its PID to the given file.
|
||||
If given `-hostprefix <prefix>` arguments after 'daemon', the dhcp plugin will use this prefix for DHCP socket as `<prefix>/run/cni/dhcp.sock`. You can use this prefix for references to the host filesystem, e.g. to access netns and the unix socket.
|
||||
|
||||
Alternatively, you can use systemd socket activation protocol.
|
||||
Be sure that the .socket file uses /run/cni/dhcp.sock as the socket path.
|
||||
|
||||
With the daemon running, containers using the dhcp plugin can be launched.
|
||||
|
||||
## Example configuration
|
||||
|
||||
```
|
||||
{
|
||||
"ipam": {
|
||||
"type": "dhcp",
|
||||
}
|
||||
}
|
||||
|
||||
## Network configuration reference
|
||||
|
||||
* `type` (string, required): "dhcp"
|
||||
You can find it online here: https://cni.dev/plugins/ipam/dhcp/
|
||||
|
@ -1,3 +1,17 @@
|
||||
// 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 (
|
||||
|
@ -26,6 +26,7 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
@ -34,7 +35,6 @@ import (
|
||||
)
|
||||
|
||||
const listenFdsStart = 3
|
||||
const resendCount = 3
|
||||
|
||||
var errNoMoreTries = errors.New("no more tries")
|
||||
|
||||
@ -42,11 +42,14 @@ type DHCP struct {
|
||||
mux sync.Mutex
|
||||
leases map[string]*DHCPLease
|
||||
hostNetnsPrefix string
|
||||
clientTimeout time.Duration
|
||||
broadcast bool
|
||||
}
|
||||
|
||||
func newDHCP() *DHCP {
|
||||
func newDHCP(clientTimeout time.Duration) *DHCP {
|
||||
return &DHCP{
|
||||
leases: make(map[string]*DHCPLease),
|
||||
leases: make(map[string]*DHCPLease),
|
||||
clientTimeout: clientTimeout,
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,7 +67,7 @@ func (d *DHCP) Allocate(args *skel.CmdArgs, result *current.Result) error {
|
||||
|
||||
clientID := generateClientID(args.ContainerID, conf.Name, args.IfName)
|
||||
hostNetns := d.hostNetnsPrefix + args.Netns
|
||||
l, err := AcquireLease(clientID, hostNetns, args.IfName)
|
||||
l, err := AcquireLease(clientID, hostNetns, args.IfName, d.clientTimeout, d.broadcast)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -157,7 +160,10 @@ func getListener(socketPath string) (net.Listener, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func runDaemon(pidfilePath string, hostPrefix string, socketPath string) error {
|
||||
func runDaemon(
|
||||
pidfilePath, hostPrefix, socketPath string,
|
||||
dhcpClientTimeout time.Duration, broadcast bool,
|
||||
) error {
|
||||
// since other goroutines (on separate threads) will change namespaces,
|
||||
// ensure the RPC server does not get scheduled onto those
|
||||
runtime.LockOSThread()
|
||||
@ -177,8 +183,9 @@ func runDaemon(pidfilePath string, hostPrefix string, socketPath string) error {
|
||||
return fmt.Errorf("Error getting listener: %v", err)
|
||||
}
|
||||
|
||||
dhcp := newDHCP()
|
||||
dhcp := newDHCP(dhcpClientTimeout)
|
||||
dhcp.hostNetnsPrefix = hostPrefix
|
||||
dhcp.broadcast = broadcast
|
||||
rpc.Register(dhcp)
|
||||
rpc.HandleHTTP()
|
||||
http.Serve(l, nil)
|
||||
|
@ -34,7 +34,7 @@ import (
|
||||
// RFC 2131 suggests using exponential backoff, starting with 4sec
|
||||
// and randomized to +/- 1sec
|
||||
const resendDelay0 = 4 * time.Second
|
||||
const resendDelayMax = 32 * time.Second
|
||||
const resendDelayMax = 62 * time.Second
|
||||
|
||||
const (
|
||||
leaseStateBound = iota
|
||||
@ -56,6 +56,8 @@ type DHCPLease struct {
|
||||
renewalTime time.Time
|
||||
rebindingTime time.Time
|
||||
expireTime time.Time
|
||||
timeout time.Duration
|
||||
broadcast bool
|
||||
stopping uint32
|
||||
stop chan struct{}
|
||||
wg sync.WaitGroup
|
||||
@ -64,11 +66,16 @@ type DHCPLease struct {
|
||||
// AcquireLease gets an DHCP lease and then maintains it in the background
|
||||
// by periodically renewing it. The acquired lease can be released by
|
||||
// calling DHCPLease.Stop()
|
||||
func AcquireLease(clientID, netns, ifName string) (*DHCPLease, error) {
|
||||
func AcquireLease(
|
||||
clientID, netns, ifName string,
|
||||
timeout time.Duration, broadcast bool,
|
||||
) (*DHCPLease, error) {
|
||||
errCh := make(chan error, 1)
|
||||
l := &DHCPLease{
|
||||
clientID: clientID,
|
||||
stop: make(chan struct{}),
|
||||
clientID: clientID,
|
||||
stop: make(chan struct{}),
|
||||
timeout: timeout,
|
||||
broadcast: broadcast,
|
||||
}
|
||||
|
||||
log.Printf("%v: acquiring lease", clientID)
|
||||
@ -115,7 +122,7 @@ func (l *DHCPLease) Stop() {
|
||||
}
|
||||
|
||||
func (l *DHCPLease) acquire() error {
|
||||
c, err := newDHCPClient(l.link, l.clientID)
|
||||
c, err := newDHCPClient(l.link, l.clientID, l.timeout, l.broadcast)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -242,7 +249,7 @@ func (l *DHCPLease) downIface() {
|
||||
}
|
||||
|
||||
func (l *DHCPLease) renew() error {
|
||||
c, err := newDHCPClient(l.link, l.clientID)
|
||||
c, err := newDHCPClient(l.link, l.clientID, l.timeout, l.broadcast)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -273,7 +280,7 @@ func (l *DHCPLease) renew() error {
|
||||
func (l *DHCPLease) release() error {
|
||||
log.Printf("%v: releasing lease", l.clientID)
|
||||
|
||||
c, err := newDHCPClient(l.link, l.clientID)
|
||||
c, err := newDHCPClient(l.link, l.clientID, l.timeout, l.broadcast)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -335,8 +342,9 @@ func jitter(span time.Duration) time.Duration {
|
||||
|
||||
func backoffRetry(f func() (*dhcp4.Packet, error)) (*dhcp4.Packet, error) {
|
||||
var baseDelay time.Duration = resendDelay0
|
||||
var sleepTime time.Duration
|
||||
|
||||
for i := 0; i < resendCount; i++ {
|
||||
for {
|
||||
pkt, err := f()
|
||||
if err == nil {
|
||||
return pkt, nil
|
||||
@ -344,17 +352,27 @@ func backoffRetry(f func() (*dhcp4.Packet, error)) (*dhcp4.Packet, error) {
|
||||
|
||||
log.Print(err)
|
||||
|
||||
time.Sleep(baseDelay + jitter(time.Second))
|
||||
sleepTime = baseDelay + jitter(time.Second)
|
||||
|
||||
log.Printf("retrying in %f seconds", sleepTime.Seconds())
|
||||
|
||||
time.Sleep(sleepTime)
|
||||
|
||||
if baseDelay < resendDelayMax {
|
||||
baseDelay *= 2
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errNoMoreTries
|
||||
}
|
||||
|
||||
func newDHCPClient(link netlink.Link, clientID string) (*dhcp4client.Client, error) {
|
||||
func newDHCPClient(
|
||||
link netlink.Link, clientID string,
|
||||
timeout time.Duration,
|
||||
broadcast bool,
|
||||
) (*dhcp4client.Client, error) {
|
||||
pktsock, err := dhcp4client.NewPacketSock(link.Attrs().Index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -362,8 +380,8 @@ func newDHCPClient(link netlink.Link, clientID string) (*dhcp4client.Client, err
|
||||
|
||||
return dhcp4client.New(
|
||||
dhcp4client.HardwareAddr(link.Attrs().HardwareAddr),
|
||||
dhcp4client.Timeout(5*time.Second),
|
||||
dhcp4client.Broadcast(false),
|
||||
dhcp4client.Timeout(timeout),
|
||||
dhcp4client.Broadcast(broadcast),
|
||||
dhcp4client.Connection(pktsock),
|
||||
)
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"net/rpc"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
@ -37,17 +38,21 @@ func main() {
|
||||
var pidfilePath string
|
||||
var hostPrefix string
|
||||
var socketPath string
|
||||
var broadcast bool
|
||||
var timeout 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.Parse(os.Args[2:])
|
||||
|
||||
if socketPath == "" {
|
||||
socketPath = defaultSocketPath
|
||||
}
|
||||
|
||||
if err := runDaemon(pidfilePath, hostPrefix, socketPath); err != nil {
|
||||
if err := runDaemon(pidfilePath, hostPrefix, socketPath, timeout, broadcast); err != nil {
|
||||
log.Printf(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
@ -1,142 +1,4 @@
|
||||
# host-local IP address management plugin
|
||||
|
||||
host-local IPAM allocates IPv4 and IPv6 addresses out of a specified address range. Optionally,
|
||||
it can include a DNS configuration from a `resolv.conf` file on the host.
|
||||
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
|
||||
|
||||
## Overview
|
||||
|
||||
host-local IPAM plugin allocates ip addresses out of a set of address ranges.
|
||||
It stores the state locally on the host filesystem, therefore ensuring uniqueness of IP addresses on a single host.
|
||||
|
||||
The allocator can allocate multiple ranges, and supports sets of multiple (disjoint)
|
||||
subnets. The allocation strategy is loosely round-robin within each range set.
|
||||
|
||||
## Example configurations
|
||||
|
||||
Note that the key `ranges` is a list of range sets. That is to say, the length
|
||||
of the top-level array is the number of addresses returned. The second-level
|
||||
array is a set of subnets to use as a pool of possible addresses.
|
||||
|
||||
This example configuration returns 2 IP addresses.
|
||||
|
||||
```json
|
||||
{
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"ranges": [
|
||||
[
|
||||
{
|
||||
"subnet": "10.10.0.0/16",
|
||||
"rangeStart": "10.10.1.20",
|
||||
"rangeEnd": "10.10.3.50",
|
||||
"gateway": "10.10.0.254"
|
||||
},
|
||||
{
|
||||
"subnet": "172.16.5.0/24"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"subnet": "3ffe:ffff:0:01ff::/64",
|
||||
"rangeStart": "3ffe:ffff:0:01ff::0010",
|
||||
"rangeEnd": "3ffe:ffff:0:01ff::0020"
|
||||
}
|
||||
]
|
||||
],
|
||||
"routes": [
|
||||
{ "dst": "0.0.0.0/0" },
|
||||
{ "dst": "192.168.0.0/16", "gw": "10.10.5.1" },
|
||||
{ "dst": "3ffe:ffff:0:01ff::1/64" }
|
||||
],
|
||||
"dataDir": "/run/my-orchestrator/container-ipam-state"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Previous versions of the `host-local` allocator did not support the `ranges`
|
||||
property, and instead expected a single range on the top level. This is
|
||||
deprecated but still supported.
|
||||
```json
|
||||
{
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "3ffe:ffff:0:01ff::/64",
|
||||
"rangeStart": "3ffe:ffff:0:01ff::0010",
|
||||
"rangeEnd": "3ffe:ffff:0:01ff::0020",
|
||||
"routes": [
|
||||
{ "dst": "3ffe:ffff:0:01ff::1/64" }
|
||||
],
|
||||
"resolvConf": "/etc/resolv.conf"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We can test it out on the command-line:
|
||||
|
||||
```bash
|
||||
$ echo '{ "cniVersion": "0.3.1", "name": "examplenet", "ipam": { "type": "host-local", "ranges": [ [{"subnet": "203.0.113.0/24"}], [{"subnet": "2001:db8:1::/64"}]], "dataDir": "/tmp/cni-example" } }' | CNI_COMMAND=ADD CNI_CONTAINERID=example CNI_NETNS=/dev/null CNI_IFNAME=dummy0 CNI_PATH=. ./host-local
|
||||
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "203.0.113.2/24",
|
||||
"gateway": "203.0.113.1"
|
||||
},
|
||||
{
|
||||
"version": "6",
|
||||
"address": "2001:db8:1::2/64",
|
||||
"gateway": "2001:db8:1::1"
|
||||
}
|
||||
],
|
||||
"dns": {}
|
||||
}
|
||||
```
|
||||
|
||||
## Network configuration reference
|
||||
|
||||
* `type` (string, required): "host-local".
|
||||
* `routes` (string, optional): list of routes to add to the container namespace. Each route is a dictionary with "dst" and optional "gw" fields. If "gw" is omitted, value of "gateway" will be used.
|
||||
* `resolvConf` (string, optional): Path to a `resolv.conf` on the host to parse and return as the DNS configuration
|
||||
* `dataDir` (string, optional): Path to a directory to use for maintaining state, e.g. which IPs have been allocated to which containers
|
||||
* `ranges`, (array, required, nonempty) an array of arrays of range objects:
|
||||
* `subnet` (string, required): CIDR block to allocate out of.
|
||||
* `rangeStart` (string, optional): IP inside of "subnet" from which to start allocating addresses. Defaults to ".2" IP inside of the "subnet" block.
|
||||
* `rangeEnd` (string, optional): IP inside of "subnet" with which to end allocating addresses. Defaults to ".254" IP inside of the "subnet" block for ipv4, ".255" for IPv6
|
||||
* `gateway` (string, optional): IP inside of "subnet" to designate as the gateway. Defaults to ".1" IP inside of the "subnet" block.
|
||||
|
||||
Older versions of the `host-local` plugin did not support the `ranges` array. Instead,
|
||||
all the properties in the `range` object were top-level. This is still supported but deprecated.
|
||||
|
||||
## Supported arguments
|
||||
The following [CNI_ARGS](https://github.com/containernetworking/cni/blob/master/SPEC.md#parameters) are supported:
|
||||
|
||||
* `ip`: request a specific IP address from a subnet.
|
||||
|
||||
The following [args conventions](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md) are supported:
|
||||
|
||||
* `ips` (array of strings): A list of custom IPs to attempt to allocate
|
||||
|
||||
The following [Capability Args](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md) are supported:
|
||||
|
||||
* `ipRanges`: The exact same as the `ranges` array - a list of address pools
|
||||
|
||||
### Custom IP allocation
|
||||
For every requested custom IP, the `host-local` allocator will request that IP
|
||||
if it falls within one of the `range` objects. Thus it is possible to specify
|
||||
multiple custom IPs and multiple ranges.
|
||||
|
||||
If any requested IPs cannot be reserved, either because they are already in use
|
||||
or are not part of a specified range, the plugin will return an error.
|
||||
|
||||
|
||||
## Files
|
||||
|
||||
Allocated IP addresses are stored as files in `/var/lib/cni/networks/$NETWORK_NAME`.
|
||||
The path can be customized with the `dataDir` option listed above. Environments
|
||||
where IPs are released automatically on reboot (e.g. running containers are not
|
||||
restored) may wish to specify `/var/run/cni` or another tmpfs mounted directory
|
||||
instead.
|
||||
You can find it online here: https://cni.dev/plugins/ipam/host-local/
|
||||
|
@ -15,7 +15,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
@ -34,14 +33,6 @@ func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("host-local"))
|
||||
}
|
||||
|
||||
func loadNetConf(bytes []byte) (*types.NetConf, string, error) {
|
||||
n := &types.NetConf{}
|
||||
if err := json.Unmarshal(bytes, n); err != nil {
|
||||
return nil, "", fmt.Errorf("failed to load netconf: %v", err)
|
||||
}
|
||||
return n, n.CNIVersion, nil
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
|
||||
ipamConf, _, err := allocator.LoadIPAMConfig(args.StdinData, args.Args)
|
||||
|
@ -1,68 +1,4 @@
|
||||
# static IP address management plugin
|
||||
|
||||
## Overview
|
||||
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
|
||||
|
||||
static IPAM is very simple IPAM plugin that assigns IPv4 and IPv6 addresses statically to container. This will be useful in debugging purpose and in case of assign same IP address in different vlan/vxlan to containers.
|
||||
|
||||
|
||||
## Example configuration
|
||||
|
||||
```
|
||||
{
|
||||
"ipam": {
|
||||
"type": "static",
|
||||
"addresses": [
|
||||
{
|
||||
"address": "10.10.0.1/24",
|
||||
"gateway": "10.10.0.254"
|
||||
},
|
||||
{
|
||||
"address": "3ffe:ffff:0:01ff::1/64",
|
||||
"gateway": "3ffe:ffff:0::1"
|
||||
}
|
||||
],
|
||||
"routes": [
|
||||
{ "dst": "0.0.0.0/0" },
|
||||
{ "dst": "192.168.0.0/16", "gw": "10.10.5.1" },
|
||||
{ "dst": "3ffe:ffff:0:01ff::1/64" }
|
||||
],
|
||||
"dns": {
|
||||
"nameservers" : ["8.8.8.8"],
|
||||
"domain": "example.com",
|
||||
"search": [ "example.com" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Network configuration reference
|
||||
|
||||
* `type` (string, required): "static"
|
||||
* `addresses` (array, optional): an array of ip address objects:
|
||||
* `address` (string, required): CIDR notation IP address.
|
||||
* `gateway` (string, optional): IP inside of "subnet" to designate as the gateway.
|
||||
* `routes` (string, optional): list of routes add to the container namespace. Each route is a dictionary with "dst" and optional "gw" fields. If "gw" is omitted, value of "gateway" will be used.
|
||||
* `dns` (string, optional): the dictionary with "nameservers", "domain" and "search".
|
||||
|
||||
## Supported arguments
|
||||
|
||||
The following [CNI_ARGS](https://github.com/containernetworking/cni/blob/master/SPEC.md#parameters) are supported:
|
||||
|
||||
* `IP`: request a specific CIDR notation IP addresses, comma separated
|
||||
* `GATEWAY`: request a specific gateway address
|
||||
|
||||
(example: CNI_ARGS="IP=10.10.0.1/24;GATEWAY=10.10.0.254")
|
||||
|
||||
The plugin also support following [capability argument](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md).
|
||||
|
||||
* `ips`: Pass IP addresses for CNI interface
|
||||
|
||||
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)
|
||||
You can find it online here: https://cni.dev/plugins/ipam/static/
|
||||
|
@ -10,3 +10,4 @@ plugins/meta/portmap
|
||||
plugins/meta/tuning
|
||||
plugins/meta/bandwidth
|
||||
plugins/meta/firewall
|
||||
plugins/meta/vrf
|
||||
|
@ -1,60 +1,5 @@
|
||||
# bridge plugin
|
||||
|
||||
## Overview
|
||||
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
|
||||
|
||||
With bridge plugin, all containers (on the same host) are plugged into a bridge (virtual switch) that resides in the host network namespace.
|
||||
The containers receive one end of the veth pair with the other end connected to the bridge.
|
||||
An IP address is only assigned to one end of the veth pair -- one residing in the container.
|
||||
The bridge itself can also be assigned an IP address, turning it into a gateway for the containers.
|
||||
Alternatively, the bridge can function purely in L2 mode and would need to be bridged to the host network interface (if other than container-to-container communication on the same host is desired).
|
||||
You can find it online here: https://cni.dev/plugins/main/bridge/
|
||||
|
||||
The network configuration specifies the name of the bridge to be used.
|
||||
If the bridge is missing, the plugin will create one on first use and, if gateway mode is used, assign it an IP that was returned by IPAM plugin via the gateway field.
|
||||
|
||||
## Example configuration
|
||||
```
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Example L2-only configuration
|
||||
```
|
||||
{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"type": "bridge",
|
||||
"bridge": "mynet0",
|
||||
"ipam": {}
|
||||
}
|
||||
```
|
||||
|
||||
## Network configuration reference
|
||||
|
||||
* `name` (string, required): the name of the network.
|
||||
* `type` (string, required): "bridge".
|
||||
* `bridge` (string, optional): name of the bridge to use/create. Defaults to "cni0".
|
||||
* `isGateway` (boolean, optional): assign an IP address to the bridge. Defaults to false.
|
||||
* `isDefaultGateway` (boolean, optional): Sets isGateway to true and makes the assigned IP the default route. Defaults to false.
|
||||
* `forceAddress` (boolean, optional): Indicates if a new IP address should be set if the previous value has been changed. Defaults to false.
|
||||
* `ipMasq` (boolean, optional): set up IP Masquerade on the host for traffic originating from this network and destined outside of it. Defaults to false.
|
||||
* `mtu` (integer, optional): explicitly set MTU to the specified value. Defaults to the value chosen by the kernel.
|
||||
* `hairpinMode` (boolean, optional): set hairpin mode for interfaces on the bridge. Defaults to false.
|
||||
* `ipam` (dictionary, required): IPAM configuration to be used for this network. For L2-only network, create empty dictionary.
|
||||
* `promiscMode` (boolean, optional): set promiscuous mode on the bridge. Defaults to false.
|
||||
* `vlan` (int, optional): assign VLAN tag. Defaults to none.
|
||||
|
||||
*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```.
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"net"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/j-keck/arping"
|
||||
"github.com/vishvananda/netlink"
|
||||
@ -35,6 +36,7 @@ import (
|
||||
"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"
|
||||
)
|
||||
|
||||
// For testcases to force an error after IPAM has been performed
|
||||
@ -224,7 +226,9 @@ func ensureBridge(brName string, mtu int, promiscMode, vlanFiltering bool) (*net
|
||||
// default packet limit
|
||||
TxQLen: -1,
|
||||
},
|
||||
VlanFiltering: &vlanFiltering,
|
||||
}
|
||||
if vlanFiltering {
|
||||
br.VlanFiltering = &vlanFiltering
|
||||
}
|
||||
|
||||
err := netlink.LinkAdd(br)
|
||||
@ -245,6 +249,9 @@ func ensureBridge(brName string, mtu int, promiscMode, vlanFiltering bool) (*net
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// we want to own the routes for this interface
|
||||
_, _ = sysctl.Sysctl(fmt.Sprintf("net/ipv6/conf/%s/accept_ra", brName), "0")
|
||||
|
||||
if err := netlink.LinkSetUp(br); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -442,11 +449,6 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
|
||||
// Configure the container hardware address and IP address(es)
|
||||
if err := netns.Do(func(_ ns.NetNS) error {
|
||||
contVeth, err := net.InterfaceByName(args.IfName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Disable IPv6 DAD just in case hairpin mode is enabled on the
|
||||
// bridge. Hairpin mode causes echos of neighbor solicitation
|
||||
// packets, which causes DAD failures.
|
||||
@ -463,8 +465,36 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
if err := ipam.ConfigureIface(args.IfName, result); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); 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)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// Send a gratuitous arp
|
||||
if err := netns.Do(func(_ ns.NetNS) error {
|
||||
contVeth, err := net.InterfaceByName(args.IfName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Send a gratuitous arp
|
||||
for _, ipc := range result.IPs {
|
||||
if ipc.Version == "4" {
|
||||
_ = arping.GratuitousArpOverIface(ipc.Address.IP, *contVeth)
|
||||
|
@ -776,7 +776,6 @@ func (tester *testerV03x) cmdAddTest(tc testCase, dataDir string) (*current.Resu
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(addrs)).To(Equal(len(expCIDRsV4)))
|
||||
addrs, err = netlink.AddrList(link, netlink.FAMILY_V6)
|
||||
Expect(len(addrs)).To(Equal(len(expCIDRsV6) + 1)) //add one for the link-local
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
// Ignore link local address which may or may not be
|
||||
// ready when we read addresses.
|
||||
|
@ -1,40 +1,5 @@
|
||||
# host-device
|
||||
|
||||
Move an already-existing device into a container.
|
||||
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
|
||||
|
||||
## Overview
|
||||
You can find it online here: https://cni.dev/plugins/main/host-device/
|
||||
|
||||
This simple plugin will move the requested device from the host's network namespace
|
||||
to the container's. IPAM configuration can be used for this plugin.
|
||||
|
||||
## Network configuration reference
|
||||
|
||||
The device can be specified with any one of four properties:
|
||||
* `device`: The device name, e.g. `eth0`, `can0`
|
||||
* `hwaddr`: A MAC address
|
||||
* `kernelpath`: The kernel device kobj, e.g. `/sys/devices/pci0000:00/0000:00:1f.6`
|
||||
* `pciBusID`: A PCI address of network device, e.g `0000:00:1f.6`
|
||||
|
||||
For this plugin, `CNI_IFNAME` will be ignored. Upon DEL, the device will be moved back.
|
||||
|
||||
## Example configuration
|
||||
|
||||
A sample configuration with `device` property looks like:
|
||||
|
||||
```json
|
||||
{
|
||||
"cniVersion": "0.3.1",
|
||||
"type": "host-device",
|
||||
"device": "enp0s1"
|
||||
}
|
||||
```
|
||||
|
||||
A sample configuration with `pciBusID` property looks like:
|
||||
|
||||
```json
|
||||
{
|
||||
"cniVersion": "0.3.1",
|
||||
"type": "host-device",
|
||||
"pciBusID": "0000:3d:00.1"
|
||||
}
|
||||
```
|
||||
|
@ -43,13 +43,19 @@ const (
|
||||
sysBusPCI = "/sys/bus/pci/devices"
|
||||
)
|
||||
|
||||
// Array of different linux drivers bound to network device needed for DPDK
|
||||
var userspaceDrivers = []string{"vfio-pci", "uio_pci_generic", "igb_uio"}
|
||||
|
||||
//NetConf for host-device config, look the README to learn how to use those parameters
|
||||
type NetConf struct {
|
||||
types.NetConf
|
||||
Device string `json:"device"` // Device-Name, something like eth0 or can0 etc.
|
||||
HWAddr string `json:"hwaddr"` // MAC Address of target network interface
|
||||
KernelPath string `json:"kernelpath"` // Kernelpath of the device
|
||||
PCIAddr string `json:"pciBusID"` // PCI Address of target network device
|
||||
Device string `json:"device"` // Device-Name, something like eth0 or can0 etc.
|
||||
HWAddr string `json:"hwaddr"` // MAC Address of target network interface
|
||||
KernelPath string `json:"kernelpath"` // Kernelpath of the device
|
||||
PCIAddr string `json:"pciBusID"` // PCI Address of target network device
|
||||
RuntimeConfig struct {
|
||||
DeviceID string `json:"deviceID,omitempty"`
|
||||
} `json:"runtimeConfig,omitempty"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -64,9 +70,16 @@ func loadConf(bytes []byte) (*NetConf, error) {
|
||||
if err := json.Unmarshal(bytes, n); err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
if n.Device == "" && n.HWAddr == "" && n.KernelPath == "" && n.PCIAddr == "" {
|
||||
return nil, fmt.Errorf(`specify either "device", "hwaddr", "kernelpath" or "pciBusID"`)
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
@ -81,6 +94,16 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
defer containerNs.Close()
|
||||
|
||||
if len(cfg.PCIAddr) > 0 {
|
||||
isDpdkMode, err := hasDpdkDriver(cfg.PCIAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error with host device: %v", err)
|
||||
}
|
||||
if isDpdkMode {
|
||||
return types.PrintResult(¤t.Result{}, cfg.CNIVersion)
|
||||
}
|
||||
}
|
||||
|
||||
hostDev, err := getLink(cfg.Device, cfg.HWAddr, cfg.KernelPath, cfg.PCIAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find host device: %v", err)
|
||||
@ -158,6 +181,16 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
defer containerNs.Close()
|
||||
|
||||
if len(cfg.PCIAddr) > 0 {
|
||||
isDpdkMode, err := hasDpdkDriver(cfg.PCIAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error with host device: %v", err)
|
||||
}
|
||||
if isDpdkMode {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if err := moveLinkOut(containerNs, args.IfName); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -183,6 +216,10 @@ func moveLinkIn(hostDev netlink.Link, containerNs ns.NetNS, ifName string) (netl
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find %q: %v", hostDev.Attrs().Name, 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)
|
||||
}
|
||||
// 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)
|
||||
@ -241,6 +278,25 @@ func moveLinkOut(containerNs ns.NetNS, ifName string) error {
|
||||
})
|
||||
}
|
||||
|
||||
func hasDpdkDriver(pciaddr string) (bool, error) {
|
||||
driverLink := filepath.Join(sysBusPCI, pciaddr, "driver")
|
||||
driverPath, err := filepath.EvalSymlinks(driverLink)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
driverStat, err := os.Stat(driverPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
driverName := driverStat.Name()
|
||||
for _, drv := range userspaceDrivers {
|
||||
if driverName == drv {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func printLink(dev netlink.Link, cniVersion string, containerNs ns.NetNS) error {
|
||||
result := current.Result{
|
||||
CNIVersion: current.ImplementedSpecVersion,
|
||||
@ -296,7 +352,12 @@ func getLink(devname, hwaddr, kernelpath, pciaddr string) (netlink.Link, error)
|
||||
} else if len(pciaddr) > 0 {
|
||||
netDir := filepath.Join(sysBusPCI, pciaddr, "net")
|
||||
if _, err := os.Lstat(netDir); err != nil {
|
||||
return nil, fmt.Errorf("no net directory under pci device %s: %q", pciaddr, err)
|
||||
virtioNetDir := filepath.Join(sysBusPCI, pciaddr, "virtio*", "net")
|
||||
matches, err := filepath.Glob(virtioNetDir)
|
||||
if matches == nil || err != nil {
|
||||
return nil, fmt.Errorf("no net directory under pci device %s", pciaddr)
|
||||
}
|
||||
netDir = matches[0]
|
||||
}
|
||||
fInfo, err := ioutil.ReadDir(netDir)
|
||||
if err != nil {
|
||||
|
@ -1,45 +1,5 @@
|
||||
# ipvlan plugin
|
||||
|
||||
## Overview
|
||||
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
|
||||
|
||||
ipvlan is a new [addition](https://lwn.net/Articles/620087/) to the Linux kernel.
|
||||
Like its cousin macvlan, it virtualizes the host interface.
|
||||
However unlike macvlan which generates a new MAC address for each interface, ipvlan devices all share the same MAC.
|
||||
The kernel driver inspects the IP address of each packet when making a decision about which virtual interface should process the packet.
|
||||
You can find it online here: https://cni.dev/plugins/main/ipvlan/
|
||||
|
||||
Because all ipvlan interfaces share the MAC address with the host interface, DHCP can only be used in conjunction with ClientID (currently not supported by DHCP plugin).
|
||||
|
||||
## Example configuration
|
||||
|
||||
```
|
||||
{
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "eth0",
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.2.0/24"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Network configuration reference
|
||||
|
||||
* `name` (string, required): the name of the network.
|
||||
* `type` (string, required): "ipvlan".
|
||||
* `master` (string, required unless chained): name of the host interface to enslave.
|
||||
* `mode` (string, optional): one of "l2", "l3", "l3s". Defaults to "l2".
|
||||
* `mtu` (integer, optional): explicitly set MTU to the specified value. Defaults to the value chosen by the kernel.
|
||||
* `ipam` (dictionary, required unless chained): IPAM configuration to be used for this network.
|
||||
|
||||
## Notes
|
||||
|
||||
* `ipvlan` does not allow virtual interfaces to communicate with the master interface.
|
||||
Therefore the container will not be able to reach the host via `ipvlan` interface.
|
||||
Be sure to also have container join a network that provides connectivity to the host (e.g. `ptp`).
|
||||
* A single master interface can not be enslaved by both `macvlan` and `ipvlan`.
|
||||
* For IP allocation schemes that cannot be interface agnostic, the ipvlan plugin
|
||||
can be chained with an earlier plugin that handles this logic. If `master` is
|
||||
omitted, then the previous Result must contain a single interface name for the
|
||||
ipvlan plugin to enslave. If `ipam` is omitted, then the previous Result is used
|
||||
to configure the ipvlan interface.
|
||||
|
@ -72,12 +72,18 @@ func loadConf(bytes []byte, cmdCheck bool) (*NetConf, string, error) {
|
||||
}
|
||||
if n.Master == "" {
|
||||
if result == nil {
|
||||
return nil, "", fmt.Errorf(`"master" field is required. It specifies the host interface name to virtualize`)
|
||||
}
|
||||
if len(result.Interfaces) == 1 && result.Interfaces[0].Name != "" {
|
||||
n.Master = result.Interfaces[0].Name
|
||||
defaultRouteInterface, err := getDefaultRouteInterfaceName()
|
||||
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
n.Master = defaultRouteInterface
|
||||
} else {
|
||||
return nil, "", fmt.Errorf("chained master failure. PrevResult lacks a single named interface")
|
||||
if len(result.Interfaces) == 1 && result.Interfaces[0].Name != "" {
|
||||
n.Master = result.Interfaces[0].Name
|
||||
} else {
|
||||
return nil, "", fmt.Errorf("chained master failure. PrevResult lacks a single named interface")
|
||||
}
|
||||
}
|
||||
}
|
||||
return n, n.CNIVersion, nil
|
||||
@ -167,6 +173,25 @@ func createIpvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interf
|
||||
return ipvlan, nil
|
||||
}
|
||||
|
||||
func getDefaultRouteInterfaceName() (string, error) {
|
||||
routeToDstIP, err := netlink.RouteList(nil, netlink.FAMILY_ALL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, v := range routeToDstIP {
|
||||
if v.Dst == nil {
|
||||
l, err := netlink.LinkByIndex(v.LinkIndex)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return l.Attrs().Name, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("no default route interface found")
|
||||
}
|
||||
|
||||
func cmdAdd(args *skel.CmdArgs) error {
|
||||
n, cniVersion, err := loadConf(args.StdinData, false)
|
||||
if err != nil {
|
||||
|
@ -234,7 +234,7 @@ func ipvlanAddCheckDelTest(conf string, netName string, IFNAME string, originalN
|
||||
|
||||
args.StdinData = confString
|
||||
|
||||
// CNI Check on macvlan in the target namespace
|
||||
// CNI Check on ipvlan in the target namespace
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
@ -465,4 +465,50 @@ var _ = Describe("ipvlan Operations", func() {
|
||||
|
||||
ipvlanAddCheckDelTest(conf, "ipvlanTest2", IFNAME, originalNS)
|
||||
})
|
||||
|
||||
It("configures and deconfigures a ipvlan link with ADD/DEL, without master config", func() {
|
||||
const IFNAME = "ipvl0"
|
||||
conf := `{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.2.0/24"
|
||||
}
|
||||
}`
|
||||
|
||||
targetNs, err := testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer targetNs.Close()
|
||||
|
||||
// Make MASTER_NAME as default route interface
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
link, err := netlink.LinkByName(MASTER_NAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = netlink.LinkSetUp(link)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
var address = &net.IPNet{IP: net.IPv4(192, 0, 0, 1), Mask: net.CIDRMask(24, 32)}
|
||||
var addr = &netlink.Addr{IPNet: address}
|
||||
err = netlink.AddrAdd(link, addr)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// add default gateway into MASTER
|
||||
dst := &net.IPNet{
|
||||
IP: net.IPv4(0, 0, 0, 0),
|
||||
Mask: net.CIDRMask(0, 0),
|
||||
}
|
||||
ip := net.IPv4(192, 0, 0, 254)
|
||||
route := netlink.Route{LinkIndex: link.Attrs().Index, Dst: dst, Gw: ip}
|
||||
err = netlink.RouteAdd(&route)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
ipvlanAddDelTest(conf, IFNAME, originalNS)
|
||||
})
|
||||
})
|
||||
|
@ -68,6 +68,7 @@ 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
|
||||
@ -89,7 +90,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
if len(v6Addrs) != 0 {
|
||||
v6Addr = v6Addrs[0].IPNet
|
||||
// sanity check that this is a loopback address
|
||||
for _, addr := range v4Addrs {
|
||||
for _, addr := range v6Addrs {
|
||||
if !addr.IP.IsLoopback() {
|
||||
return fmt.Errorf("loopback interface found with non-loopback address %q", addr.IP)
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ var _ = Describe("Loopback", func() {
|
||||
environ = []string{
|
||||
fmt.Sprintf("CNI_CONTAINERID=%s", "dummy"),
|
||||
fmt.Sprintf("CNI_NETNS=%s", networkNS.Path()),
|
||||
fmt.Sprintf("CNI_IFNAME=%s", "this is ignored"),
|
||||
fmt.Sprintf("CNI_IFNAME=%s", "lo"),
|
||||
fmt.Sprintf("CNI_ARGS=%s", "none"),
|
||||
fmt.Sprintf("CNI_PATH=%s", "/some/test/path"),
|
||||
}
|
||||
|
@ -1,34 +1,5 @@
|
||||
# macvlan plugin
|
||||
|
||||
## Overview
|
||||
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
|
||||
|
||||
[macvlan](http://backreference.org/2014/03/20/some-notes-on-macvlanmacvtap/) functions like a switch that is already connected to the host interface.
|
||||
A host interface gets "enslaved" with the virtual interfaces sharing the physical device but having distinct MAC addresses.
|
||||
Since each macvlan interface has its own MAC address, it makes it easy to use with existing DHCP servers already present on the network.
|
||||
You can find it online here: https://cni.dev/plugins/main/macvlan/
|
||||
|
||||
## Example configuration
|
||||
|
||||
```
|
||||
{
|
||||
"name": "mynet",
|
||||
"type": "macvlan",
|
||||
"master": "eth0",
|
||||
"ipam": {
|
||||
"type": "dhcp"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Network configuration reference
|
||||
|
||||
* `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 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. 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
|
||||
|
||||
* If are testing on a laptop, please remember that most wireless cards do not support being enslaved by macvlan.
|
||||
* A single master interface can not be enslaved by both `macvlan` and `ipvlan`.
|
||||
|
@ -45,6 +45,17 @@ type NetConf struct {
|
||||
Master string `json:"master"`
|
||||
Mode string `json:"mode"`
|
||||
MTU int `json:"mtu"`
|
||||
Mac string `json:"mac,omitempty"`
|
||||
|
||||
RuntimeConfig struct {
|
||||
Mac string `json:"mac,omitempty"`
|
||||
} `json:"runtimeConfig,omitempty"`
|
||||
}
|
||||
|
||||
// MacEnvArgs represents CNI_ARG
|
||||
type MacEnvArgs struct {
|
||||
types.CommonArgs
|
||||
MAC types.UnmarshallableString `json:"mac,omitempty"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -73,7 +84,7 @@ func getDefaultRouteInterfaceName() (string, error) {
|
||||
return "", fmt.Errorf("no default route interface found")
|
||||
}
|
||||
|
||||
func loadConf(bytes []byte) (*NetConf, string, error) {
|
||||
func loadConf(bytes []byte, envArgs string) (*NetConf, string, error) {
|
||||
n := &NetConf{}
|
||||
if err := json.Unmarshal(bytes, n); err != nil {
|
||||
return nil, "", fmt.Errorf("failed to load netconf: %v", err)
|
||||
@ -95,6 +106,22 @@ func loadConf(bytes []byte) (*NetConf, string, error) {
|
||||
return nil, "", fmt.Errorf("invalid MTU %d, must be [0, master MTU(%d)]", n.MTU, masterMTU)
|
||||
}
|
||||
|
||||
if envArgs != "" {
|
||||
e := MacEnvArgs{}
|
||||
err := types.LoadArgs(envArgs, &e)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if e.MAC != "" {
|
||||
n.Mac = string(e.MAC)
|
||||
}
|
||||
}
|
||||
|
||||
if n.RuntimeConfig.Mac != "" {
|
||||
n.Mac = n.RuntimeConfig.Mac
|
||||
}
|
||||
|
||||
return n, n.CNIVersion, nil
|
||||
}
|
||||
|
||||
@ -156,14 +183,24 @@ 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())),
|
||||
}
|
||||
|
||||
if conf.Mac != "" {
|
||||
addr, err := net.ParseMAC(conf.Mac)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid args %v for MAC addr: %v", conf.Mac, err)
|
||||
}
|
||||
linkAttrs.HardwareAddr = addr
|
||||
}
|
||||
|
||||
mv := &netlink.Macvlan{
|
||||
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 err := netlink.LinkAdd(mv); err != nil {
|
||||
@ -204,7 +241,7 @@ func createMacvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Inter
|
||||
}
|
||||
|
||||
func cmdAdd(args *skel.CmdArgs) error {
|
||||
n, cniVersion, err := loadConf(args.StdinData)
|
||||
n, cniVersion, err := loadConf(args.StdinData, args.Args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -311,7 +348,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func cmdDel(args *skel.CmdArgs) error {
|
||||
n, _, err := loadConf(args.StdinData)
|
||||
n, _, err := loadConf(args.StdinData, args.Args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -349,7 +386,7 @@ func main() {
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
|
||||
n, _, err := loadConf(args.StdinData)
|
||||
n, _, err := loadConf(args.StdinData, args.Args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -594,4 +594,176 @@ var _ = Describe("macvlan Operations", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("configures and deconfigures l2 macvlan link with mac address (from CNI_ARGS) with CNI v4 ADD/DEL", func() {
|
||||
const IFNAME = "macvl0"
|
||||
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"type": "macvlan",
|
||||
"master": "%s",
|
||||
"ipam": {}
|
||||
}`, MASTER_NAME)
|
||||
|
||||
targetNs, err := testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer targetNs.Close()
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNs.Path(),
|
||||
IfName: IFNAME,
|
||||
StdinData: []byte(conf),
|
||||
Args: "IgnoreUnknown=true;MAC=c2:11:22:33:44:55",
|
||||
}
|
||||
|
||||
var result *current.Result
|
||||
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(len(result.Interfaces)).To(Equal(1))
|
||||
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
|
||||
Expect(len(result.IPs)).To(Equal(0))
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Make sure macvlan 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))
|
||||
|
||||
hwaddr, err := net.ParseMAC("c2:11:22:33:44:55")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr).To(Equal(hwaddr))
|
||||
|
||||
addrs, err := netlink.AddrList(link, syscall.AF_INET)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(addrs)).To(Equal(0))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
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())
|
||||
|
||||
// Make sure macvlan link has been deleted
|
||||
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())
|
||||
})
|
||||
|
||||
It("configures and deconfigures l2 macvlan link with mac address (from RuntimeConfig) with ADD/DEL", func() {
|
||||
const IFNAME = "macvl0"
|
||||
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"capabilities": {"mac": true},
|
||||
"RuntimeConfig": {
|
||||
"mac": "c2:11:22:33:44:55"
|
||||
},
|
||||
"name": "mynet",
|
||||
"type": "macvlan",
|
||||
"master": "%s",
|
||||
"ipam": {}
|
||||
}`, MASTER_NAME)
|
||||
|
||||
targetNs, err := testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer targetNs.Close()
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNs.Path(),
|
||||
IfName: IFNAME,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
var result *current.Result
|
||||
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(len(result.Interfaces)).To(Equal(1))
|
||||
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
|
||||
Expect(len(result.IPs)).To(Equal(0))
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Make sure macvlan 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))
|
||||
|
||||
hwaddr, err := net.ParseMAC("c2:11:22:33:44:55")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr).To(Equal(hwaddr))
|
||||
|
||||
addrs, err := netlink.AddrList(link, syscall.AF_INET)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(addrs)).To(Equal(0))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
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())
|
||||
|
||||
// Make sure macvlan link has been deleted
|
||||
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())
|
||||
})
|
||||
})
|
||||
|
@ -1,32 +1,5 @@
|
||||
# ptp plugin
|
||||
|
||||
## Overview
|
||||
The ptp plugin creates a point-to-point link between a container and the host by using a veth device.
|
||||
One end of the veth pair is placed inside a container and the other end resides on the host.
|
||||
The host-local IPAM plugin can be used to allocate an IP address to the container.
|
||||
The traffic of the container interface will be routed through the interface of the host.
|
||||
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
|
||||
|
||||
## Example network configuration
|
||||
You can find it online here: https://cni.dev/plugins/main/ptp/
|
||||
|
||||
```
|
||||
{
|
||||
"name": "mynet",
|
||||
"type": "ptp",
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.1.0/24"
|
||||
},
|
||||
"dns": {
|
||||
"nameservers": [ "10.1.1.1", "8.8.8.8" ]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Network configuration reference
|
||||
|
||||
* `name` (string, required): the name of the network
|
||||
* `type` (string, required): "ptp"
|
||||
* `ipMasq` (boolean, optional): set up IP Masquerade on the host for traffic originating from ip of this network and destined outside of this network. Defaults to false.
|
||||
* `mtu` (integer, optional): explicitly set MTU to the specified value. Defaults to value chosen by the kernel.
|
||||
* `ipam` (dictionary, required): IPAM configuration to be used for this network.
|
||||
* `dns` (dictionary, optional): DNS information to return as described in the [Result](https://github.com/containernetworking/cni/blob/master/SPEC.md#result).
|
||||
|
@ -228,7 +228,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
defer netns.Close()
|
||||
|
||||
hostInterface, containerInterface, err := setupContainerVeth(netns, args.IfName, conf.MTU, result)
|
||||
hostInterface, _, err := setupContainerVeth(netns, args.IfName, conf.MTU, result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -254,8 +254,6 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
result.DNS = conf.DNS
|
||||
}
|
||||
|
||||
result.Interfaces = []*current.Interface{hostInterface, containerInterface}
|
||||
|
||||
return types.PrintResult(result, conf.CNIVersion)
|
||||
}
|
||||
|
||||
|
@ -152,7 +152,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
// run the IPAM plugin and get back the config to apply
|
||||
r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to execute IPAM delegate: %v", err)
|
||||
}
|
||||
|
||||
// Invoke ipam del if err to avoid ip leak
|
||||
@ -207,7 +207,7 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
|
||||
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||
err = ip.DelLinkByName(args.IfName)
|
||||
if err != nil && err != ip.ErrLinkNotFound {
|
||||
if err != nil && err == ip.ErrLinkNotFound {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
@ -238,7 +238,7 @@ func cmdCheck(args *skel.CmdArgs) error {
|
||||
return err
|
||||
}
|
||||
if conf.NetConf.RawPrevResult == nil {
|
||||
return fmt.Errorf("ptp: Required prevResult missing")
|
||||
return fmt.Errorf("vlan: Required prevResult missing")
|
||||
}
|
||||
if err := version.ParsePrevResult(&conf.NetConf); err != nil {
|
||||
return err
|
||||
@ -308,10 +308,10 @@ func validateCniContainerInterface(intf current.Interface, masterIndex int, vlan
|
||||
}
|
||||
link, err = netlink.LinkByName(intf.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ptp: Container Interface name in prevResult: %s not found", intf.Name)
|
||||
return fmt.Errorf("vlan: Container Interface name in prevResult: %s not found", intf.Name)
|
||||
}
|
||||
if intf.Sandbox == "" {
|
||||
return fmt.Errorf("ptp: Error: Container interface %s should not be in host namespace", link.Attrs().Name)
|
||||
return fmt.Errorf("vlan: Error: Container interface %s should not be in host namespace", link.Attrs().Name)
|
||||
}
|
||||
|
||||
vlan, isVlan := link.(*netlink.Vlan)
|
||||
|
@ -292,6 +292,19 @@ var _ = Describe("vlan Operations", func() {
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// DEL can be called multiple times, make sure no error is returned
|
||||
// if the device is already removed.
|
||||
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())
|
||||
})
|
||||
|
||||
It("configures and deconfigures an CNI V4 vlan link with ADD/CHECK/DEL", func() {
|
||||
|
@ -1,57 +1,5 @@
|
||||
# win-bridge plugin
|
||||
|
||||
## Overview
|
||||
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
|
||||
|
||||
With win-bridge plugin, all containers (on the same host) are plugged into an L2Bridge network that has one endpoint in the host namespace.
|
||||
You can find it online here: https://cni.dev/plugins/main/win-bridge/
|
||||
|
||||
## Example configuration
|
||||
```
|
||||
{
|
||||
"name": "mynet",
|
||||
"type": "win-bridge",
|
||||
"ipMasqNetwork": "10.244.0.0/16",
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.10.0.0/16"
|
||||
},
|
||||
"policies":[
|
||||
{
|
||||
"name":"EndpointPolicy",
|
||||
"value":{
|
||||
"Type":"ROUTE",
|
||||
"DestinationPrefix":"10.137.198.27/32",
|
||||
"NeedEncap":true
|
||||
}
|
||||
}
|
||||
],
|
||||
"HcnPolicyArgs": [
|
||||
{
|
||||
"Type": "SDNRoute"
|
||||
"Settings": {
|
||||
"DestinationPrefix": "11.0.0.0/8",
|
||||
"NeedEncap": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"loopbackDSR": true,
|
||||
"capabilities": {
|
||||
"dns": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Network configuration reference
|
||||
|
||||
* `ApiVersion` (integer, optional): ApiVersion to use, will default to hns. If set to "2" will try to use hcn APIs.
|
||||
* `name` (string, required): the name of the network.
|
||||
* `type` (string, required): "win-bridge".
|
||||
* `ipMasqNetwork` (string, optional): setup NAT if not empty.
|
||||
* `dns` (dictionary, optional): dns config to be used.
|
||||
* `Nameservers` (list, optional): list of strings to be used for dns nameservers.
|
||||
* `Search` (list, optional): list of stings to be used for dns search.
|
||||
* `ipam` (dictionary, optional): IPAM configuration to be used for this network.
|
||||
* `Policies` (list, optional): List of hns policies to be used (only used when ApiVersion is < 2).
|
||||
* `HcnPolicyArgs` (list, optional): List of hcn policies to be used (only used when ApiVersion is 2).
|
||||
* `loopbackDSR` (bool, optional): If true, will add a policy to allow the interface to support loopback direct server return.
|
||||
* `capabilities` (dictionary, optional): Runtime capabilities to enable.
|
||||
* `dns` (boolean, optional): If true, will take the dns config supplied by the runtime and override other settings.
|
@ -22,13 +22,13 @@ import (
|
||||
|
||||
"github.com/Microsoft/hcsshim"
|
||||
"github.com/Microsoft/hcsshim/hcn"
|
||||
"github.com/juju/errors"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/errors"
|
||||
"github.com/containernetworking/plugins/pkg/hns"
|
||||
"github.com/containernetworking/plugins/pkg/ipam"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
@ -72,7 +72,7 @@ func ProcessEndpointArgs(args *skel.CmdArgs, n *NetConf) (*hns.EndpointInfo, err
|
||||
return nil, errors.Annotatef(err, "error while NewResultFromResult")
|
||||
} else {
|
||||
if len(result.IPs) == 0 {
|
||||
return nil, errors.New("IPAM plugin return is missing IP config")
|
||||
return nil, fmt.Errorf("IPAM plugin return is missing IP config")
|
||||
}
|
||||
epInfo.IpAddress = result.IPs[0].Address.IP
|
||||
epInfo.Gateway = result.IPs[0].Address.IP.Mask(result.IPs[0].Address.Mask)
|
||||
@ -86,6 +86,9 @@ func ProcessEndpointArgs(args *skel.CmdArgs, n *NetConf) (*hns.EndpointInfo, err
|
||||
n.ApplyOutboundNatPolicy(n.IPMasqNetwork)
|
||||
}
|
||||
|
||||
// Add HostPort mapping if any present
|
||||
n.ApplyPortMappingPolicy(n.RuntimeConfig.PortMaps)
|
||||
|
||||
epInfo.DNS = n.GetDNS()
|
||||
|
||||
return epInfo, nil
|
||||
@ -107,7 +110,6 @@ func cmdHnsAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
|
||||
}
|
||||
|
||||
epName := hns.ConstructEndpointName(args.ContainerID, args.Netns, n.Name)
|
||||
|
||||
hnsEndpoint, err := hns.ProvisionEndpoint(epName, hnsNetwork.Id, args.ContainerID, args.Netns, func() (*hcsshim.HNSEndpoint, error) {
|
||||
epInfo, err := ProcessEndpointArgs(args, n)
|
||||
epInfo.NetworkId = hnsNetwork.Id
|
||||
@ -130,7 +132,6 @@ func cmdHnsAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
||||
}
|
||||
|
||||
func cmdHcnAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
|
||||
@ -194,7 +195,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
if result == nil {
|
||||
return errors.New("result for ADD not populated correctly")
|
||||
return fmt.Errorf("result for ADD not populated correctly")
|
||||
}
|
||||
return types.PrintResult(result, cniVersion)
|
||||
}
|
||||
|
@ -1,38 +1,5 @@
|
||||
# win-overlay plugin
|
||||
|
||||
## Overview
|
||||
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
|
||||
|
||||
With win-overlay plugin, all containers (on the same host) are plugged into an Overlay network based on VXLAN encapsulation.
|
||||
You can find it online here: https://cni.dev/plugins/main/win-overlay/
|
||||
|
||||
## Example configuration
|
||||
```
|
||||
{
|
||||
"name": "mynet",
|
||||
"type": "win-overlay",
|
||||
"ipMasq": true,
|
||||
"endpointMacPrefix": "0E-2A",
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.10.0.0/16"
|
||||
},
|
||||
"loopbackDSR": true,
|
||||
"capabilites": {
|
||||
"dns": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Network configuration reference
|
||||
|
||||
* `name` (string, required): the name of the network.
|
||||
* `type` (string, required): "win-overlay".
|
||||
* `ipMasq` (bool, optional): the inverse of `$FLANNEL_IPMASQ`, setup NAT for the hnsNetwork subnet.
|
||||
* `dns` (dictionary, optional): dns config to be used.
|
||||
* `Nameservers` (list, optional): list of strings to be used for dns nameservers.
|
||||
* `Search` (list, optional): list of stings to be used for dns search.
|
||||
* `endpointMacPrefix` (string, optional): set to the MAC prefix configured for Flannel.
|
||||
* `Policies` (list, optional): List of hns policies to be used.
|
||||
* `ipam` (dictionary, required): IPAM configuration to be used for this network.
|
||||
* `loopbackDSR` (bool, optional): If true, will add a policy to allow the interface to support loopback direct server return.
|
||||
* `capabilities` (dictionary, optional): runtime capabilities to be parsed and injected by runtime.
|
||||
* `dns` (boolean, optional): If true, will take the dns config supplied by the runtime and override other settings.
|
@ -21,13 +21,13 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/Microsoft/hcsshim"
|
||||
"github.com/juju/errors"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/errors"
|
||||
"github.com/containernetworking/plugins/pkg/hns"
|
||||
"github.com/containernetworking/plugins/pkg/ipam"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
@ -100,12 +100,12 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
if len(result.IPs) == 0 {
|
||||
return nil, errors.New("IPAM plugin return is missing IP config")
|
||||
return nil, fmt.Errorf("IPAM plugin return is missing IP config")
|
||||
}
|
||||
|
||||
ipAddr := result.IPs[0].Address.IP.To4()
|
||||
if ipAddr == nil {
|
||||
return nil, errors.New("win-overlay doesn't support IPv6 now")
|
||||
return nil, fmt.Errorf("win-overlay doesn't support IPv6 now")
|
||||
}
|
||||
|
||||
// conjure a MAC based on the IP for Overlay
|
||||
|
@ -1,64 +1,5 @@
|
||||
# bandwidth plugin
|
||||
|
||||
## Overview
|
||||
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
|
||||
|
||||
This plugin provides a way to use and configure Linux's Traffic control (tc) subystem. tc encompasses the sets of mechanisms and operations by which packets are queued for transmission/reception on a network interface.
|
||||
You can find it online here: https://cni.dev/plugins/meta/bandwidth/
|
||||
|
||||
This plugin configures a token bucket filter (tbf) queuing discipline (qdisc) on both ingress and egress traffic. Resulting in traffic being shaped when reading / writing.
|
||||
|
||||
Due to limitations on tc shaping rules for ingress, this plugin creates an Intermediate Functional Block device (ifb) to redirect packets from the host interface. tc tbf is then applied to the ifb device. The packets that were redirected to the ifb devices, are written OUT (and shaped) to the host interface.
|
||||
|
||||
This plugin is only useful when used in addition to other plugins.
|
||||
|
||||
## Chaining
|
||||
|
||||
The bandwidth plugin applies traffic shaping to interfaces (as described above) created by previously applied plugins.
|
||||
|
||||
The following is an example [json configuration list](https://github.com/containernetworking/cni/blob/master/SPEC.md#network-configuration-list-runtime-examples) for creating a `ptp` between the host -> container via veth interfaces, whereby traffic is shaped by the `bandwidth` plugin:
|
||||
|
||||
```json
|
||||
{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "ptp",
|
||||
"ipMasq": true,
|
||||
"mtu": 512,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.0.0.0/24"
|
||||
},
|
||||
"dns": {
|
||||
"nameservers": [ "10.1.0.1" ]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "slowdown",
|
||||
"type": "bandwidth",
|
||||
"ingressRate": 123,
|
||||
"ingressBurst": 456,
|
||||
"egressRate": 123,
|
||||
"egressBurst": 456
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The result is an `ifb` device in the host namespace redirecting to the `host-interface`, with `tc tbf` applied on the `ifb` device and the `container-interface`
|
||||
|
||||
## Network configuration reference
|
||||
* ingressRate: is the rate in bps at which traffic can enter an interface. (See http://man7.org/linux/man-pages/man8/tbf.8.html)
|
||||
* ingressBurst: is the maximum amount in bits that tokens can be made available for instantaneously. (See http://man7.org/linux/man-pages/man8/tbf.8.html)
|
||||
* egressRate: is the rate in bps at which traffic can leave an interface. (See http://man7.org/linux/man-pages/man8/tbf.8.html)
|
||||
* egressBurst: is the maximum amount in bits that tokens can be made available for instantaneously. (See http://man7.org/linux/man-pages/man8/tbf.8.html)
|
||||
|
||||
Both ingressRate and ingressBurst must be set in order to limit ingress bandwidth. If neither one is set, then ingress bandwidth is not limited.
|
||||
Both egressRate and egressBurst must be set in order to limit egress bandwidth. If neither one is set, then egress bandwidth is not limited.
|
||||
|
||||
|
||||
## tc tbf documentation
|
||||
|
||||
- [tldp traffic control](http://tldp.org/HOWTO/Traffic-Control-HOWTO/components.html)
|
||||
- [man tbf](http://man7.org/linux/man-pages/man8/tbf.8.html)
|
||||
- [tc ingress and ifb mirroring](https://serverfault.com/questions/350023/tc-ingress-policing-and-ifb-mirroring)
|
||||
|
@ -936,6 +936,7 @@ var _ = Describe("bandwidth test", func() {
|
||||
|
||||
containerWithTbfRes, _, err = testutils.CmdAdd(containerWithTbfNS.Path(), "dummy", containerWithTbfIFName, []byte(ptpConf), func() error {
|
||||
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(r.Print()).To(Succeed())
|
||||
|
||||
return err
|
||||
@ -944,6 +945,7 @@ var _ = Describe("bandwidth test", func() {
|
||||
|
||||
containerWithoutTbfRes, _, err = testutils.CmdAdd(containerWithoutTbfNS.Path(), "dummy2", containerWithoutTbfIFName, []byte(ptpConf), func() error {
|
||||
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(r.Print()).To(Succeed())
|
||||
|
||||
return err
|
||||
@ -1081,6 +1083,7 @@ var _ = Describe("bandwidth test", func() {
|
||||
|
||||
containerWithTbfRes, _, err = testutils.CmdAdd(containerWithTbfNS.Path(), "dummy", containerWithTbfIFName, []byte(ptpConf), func() error {
|
||||
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(r.Print()).To(Succeed())
|
||||
|
||||
return err
|
||||
@ -1089,6 +1092,7 @@ var _ = Describe("bandwidth test", func() {
|
||||
|
||||
containerWithoutTbfRes, _, err = testutils.CmdAdd(containerWithoutTbfNS.Path(), "dummy2", containerWithoutTbfIFName, []byte(ptpConf), func() error {
|
||||
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(r.Print()).To(Succeed())
|
||||
|
||||
return err
|
||||
|
@ -39,14 +39,18 @@ func TestTBF(t *testing.T) {
|
||||
RunSpecs(t, "plugins/meta/bandwidth")
|
||||
}
|
||||
|
||||
var echoServerBinaryPath string
|
||||
var echoServerBinaryPath, echoClientBinaryPath string
|
||||
|
||||
var _ = SynchronizedBeforeSuite(func() []byte {
|
||||
binaryPath, err := gexec.Build("github.com/containernetworking/plugins/pkg/testutils/echosvr")
|
||||
serverBinaryPath, err := gexec.Build("github.com/containernetworking/plugins/pkg/testutils/echo/server")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return []byte(binaryPath)
|
||||
clientBinaryPath, err := gexec.Build("github.com/containernetworking/plugins/pkg/testutils/echo/client")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return []byte(strings.Join([]string{serverBinaryPath, clientBinaryPath}, ","))
|
||||
}, func(data []byte) {
|
||||
echoServerBinaryPath = string(data)
|
||||
binaries := strings.Split(string(data), ",")
|
||||
echoServerBinaryPath = binaries[0]
|
||||
echoClientBinaryPath = binaries[1]
|
||||
})
|
||||
|
||||
var _ = SynchronizedAfterSuite(func() {}, func() {
|
||||
@ -84,23 +88,22 @@ func startEchoServerInNamespace(netNS ns.NetNS) (int, *gexec.Session, error) {
|
||||
}
|
||||
|
||||
func makeTcpClientInNS(netns string, address string, port int, numBytes int) {
|
||||
message := bytes.Repeat([]byte{'a'}, numBytes)
|
||||
payload := bytes.Repeat([]byte{'a'}, numBytes)
|
||||
message := string(payload)
|
||||
|
||||
bin, err := exec.LookPath("nc")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
var cmd *exec.Cmd
|
||||
if netns != "" {
|
||||
netns = filepath.Base(netns)
|
||||
cmd = exec.Command("ip", "netns", "exec", netns, bin, "-v", address, strconv.Itoa(port))
|
||||
cmd = exec.Command("ip", "netns", "exec", netns, echoClientBinaryPath, "--target", fmt.Sprintf("%s:%d", address, port), "--message", message)
|
||||
} else {
|
||||
cmd = exec.Command("nc", address, strconv.Itoa(port))
|
||||
cmd = exec.Command(echoClientBinaryPath, "--target", fmt.Sprintf("%s:%d", address, port), "--message", message)
|
||||
}
|
||||
cmd.Stdin = bytes.NewBuffer([]byte(message))
|
||||
cmd.Stderr = GinkgoWriter
|
||||
out, err := cmd.Output()
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(string(out)).To(Equal(string(message)))
|
||||
Expect(string(out)).To(Equal(message))
|
||||
}
|
||||
|
||||
func createVeth(hostNamespace string, hostVethIfName string, containerNamespace string, containerVethIfName string, hostIP []byte, containerIP []byte, hostIfaceMTU int) {
|
||||
|
@ -1,135 +1,5 @@
|
||||
# firewall plugin
|
||||
|
||||
## Overview
|
||||
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
|
||||
|
||||
This plugin creates firewall rules to allow traffic to/from container IP address via the host network .
|
||||
It does not create any network interfaces and therefore does not set up connectivity by itself.
|
||||
It is intended to be used as a chained plugins.
|
||||
|
||||
## Operation
|
||||
The following network configuration file
|
||||
|
||||
```json
|
||||
{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "bridge-firewalld",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "bridge",
|
||||
"bridge": "cni0",
|
||||
"isGateway": true,
|
||||
"ipMasq": true,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.88.0.0/16",
|
||||
"routes": [
|
||||
{ "dst": "0.0.0.0/0" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "firewall",
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
will allow any IP addresses configured by earlier plugins to send/receive traffic via the host.
|
||||
|
||||
A successful result would simply be an empty result, unless a previous plugin passed a previous result, in which case this plugin will return that previous result.
|
||||
|
||||
## Backends
|
||||
|
||||
This plugin supports multiple firewall backends that implement the desired functionality.
|
||||
Available backends include `iptables` and `firewalld` and may be selected with the `backend` key.
|
||||
If no `backend` key is given, the plugin will use firewalld if the service exists on the D-Bus system bus.
|
||||
If no firewalld service is found, it will fall back to iptables.
|
||||
|
||||
## firewalld backend rule structure
|
||||
When the `firewalld` backend is used, this example will place the IPAM allocated address for the container (e.g. 10.88.0.2) into firewalld's `trusted` zone, allowing it to send/receive traffic.
|
||||
|
||||
|
||||
A sample standalone config list (with the file extension .conflist) using firewalld backend might
|
||||
look like:
|
||||
|
||||
```json
|
||||
{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "bridge-firewalld",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "bridge",
|
||||
"bridge": "cni0",
|
||||
"isGateway": true,
|
||||
"ipMasq": true,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.88.0.0/16",
|
||||
"routes": [
|
||||
{ "dst": "0.0.0.0/0" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "firewall",
|
||||
"backend": "firewalld"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
`FORWARD_IN_ZONES_SOURCE` chain:
|
||||
- `-d 10.88.0.2 -j FWDI_trusted`
|
||||
|
||||
`CNI_FORWARD_OUT_ZONES_SOURCE` chain:
|
||||
- `-s 10.88.0.2 -j FWDO_trusted`
|
||||
|
||||
|
||||
## iptables backend rule structure
|
||||
|
||||
A sample standalone config list (with the file extension .conflist) using iptables backend might
|
||||
look like:
|
||||
|
||||
```json
|
||||
{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "bridge-firewalld",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "bridge",
|
||||
"bridge": "cni0",
|
||||
"isGateway": true,
|
||||
"ipMasq": true,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.88.0.0/16",
|
||||
"routes": [
|
||||
{ "dst": "0.0.0.0/0" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "firewall",
|
||||
"backend": "iptables"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
When the `iptables` backend is used, the above example will create two new iptables chains in the `filter` table and add rules that allow the given interface to send/receive traffic.
|
||||
|
||||
### FORWARD
|
||||
A new chain, CNI-FORWARD is added to the FORWARD chain. CNI-FORWARD is the chain where rules will be added
|
||||
when containers are created and from where rules will be removed when containers terminate.
|
||||
|
||||
`FORWARD` chain:
|
||||
- `-j CNI-FORWARD`
|
||||
|
||||
CNI-FORWARD will have a pair of rules added, one for each direction, using the IPAM assigned IP address
|
||||
of the container as shown:
|
||||
|
||||
`CNI_FORWARD` chain:
|
||||
- `-s 10.88.0.2 -m conntrack --ctstate RELATED,ESTABLISHED -j CNI-FORWARD`
|
||||
- `-d 10.88.0.2 -j CNI-FORWARD`
|
||||
You can find it online here: https://cni.dev/plugins/meta/firewall/
|
||||
|
||||
|
@ -57,7 +57,7 @@ func ensureFirstChainRule(ipt *iptables.IPTables, chain string, rule []string) e
|
||||
|
||||
func (ib *iptablesBackend) setupChains(ipt *iptables.IPTables) error {
|
||||
privRule := generateFilterRule(ib.privChainName)
|
||||
adminRule := generateFilterRule(ib.adminChainName)
|
||||
adminRule := generateAdminRule(ib.adminChainName)
|
||||
|
||||
// Ensure our private chains exist
|
||||
if err := utils.EnsureChain(ipt, "filter", ib.privChainName); err != nil {
|
||||
@ -165,7 +165,7 @@ func (ib *iptablesBackend) checkRules(conf *FirewallNetConf, result *current.Res
|
||||
}
|
||||
|
||||
// Ensure our admin override chain rule exists in our private chain
|
||||
adminRule := generateFilterRule(ib.adminChainName)
|
||||
adminRule := generateAdminRule(ib.adminChainName)
|
||||
adminExists, err := ipt.Exists("filter", ib.privChainName, adminRule...)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1,135 +1,5 @@
|
||||
# flannel plugin
|
||||
|
||||
## Overview
|
||||
This plugin is designed to work in conjunction with [flannel](https://github.com/coreos/flannel), a network fabric for containers.
|
||||
When flannel daemon is started, it outputs a `/run/flannel/subnet.env` file that looks like this:
|
||||
```
|
||||
FLANNEL_NETWORK=10.1.0.0/16
|
||||
FLANNEL_SUBNET=10.1.17.1/24
|
||||
FLANNEL_MTU=1472
|
||||
FLANNEL_IPMASQ=true
|
||||
```
|
||||
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
|
||||
|
||||
This information reflects the attributes of flannel network on the host.
|
||||
The flannel CNI plugin uses this information to configure another CNI plugin, such as bridge plugin.
|
||||
You can find it online here: https://cni.dev/plugins/meta/flannel/
|
||||
|
||||
## Operation
|
||||
Given the following network configuration file and the contents of `/run/flannel/subnet.env` above,
|
||||
```
|
||||
{
|
||||
"name": "mynet",
|
||||
"type": "flannel"
|
||||
}
|
||||
```
|
||||
the flannel plugin will generate another network configuration file:
|
||||
```
|
||||
{
|
||||
"name": "mynet",
|
||||
"type": "bridge",
|
||||
"mtu": 1472,
|
||||
"ipMasq": false,
|
||||
"isGateway": true,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.17.0/24"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
It will then invoke the bridge plugin, passing it the generated configuration.
|
||||
|
||||
As can be seen from above, the flannel plugin, by default, will delegate to the bridge plugin.
|
||||
If additional configuration values need to be passed to the bridge plugin, it can be done so via the `delegate` field:
|
||||
```
|
||||
{
|
||||
"name": "mynet",
|
||||
"type": "flannel",
|
||||
"delegate": {
|
||||
"bridge": "mynet0",
|
||||
"mtu": 1400
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This supplies a configuration parameter to the bridge plugin -- the created bridge will now be named `mynet0`.
|
||||
Notice that `mtu` has also been specified and this value will not be overwritten by flannel plugin.
|
||||
|
||||
Additionally, the `delegate` field can be used to select a different kind of plugin altogether.
|
||||
To use `ipvlan` instead of `bridge`, the following configuration can be specified:
|
||||
|
||||
```
|
||||
{
|
||||
"name": "mynet",
|
||||
"type": "flannel",
|
||||
"delegate": {
|
||||
"type": "ipvlan",
|
||||
"master": "eth0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Network configuration reference
|
||||
|
||||
* `name` (string, required): the name of the network
|
||||
* `type` (string, required): "flannel"
|
||||
* `subnetFile` (string, optional): full path to the subnet file written out by flanneld. Defaults to /run/flannel/subnet.env
|
||||
* `dataDir` (string, optional): path to directory where plugin will store generated network configuration files. Defaults to `/var/lib/cni/flannel`
|
||||
* `delegate` (dictionary, optional): specifies configuration options for the delegated plugin.
|
||||
|
||||
flannel plugin will always set the following fields in the delegated plugin configuration:
|
||||
|
||||
* `name`: value of its "name" field.
|
||||
* `ipam`: "host-local" type will be used with "subnet" set to `$FLANNEL_SUBNET`.
|
||||
|
||||
flannel plugin will set the following fields in the delegated plugin configuration if they are not present:
|
||||
* `ipMasq`: the inverse of `$FLANNEL_IPMASQ`
|
||||
* `mtu`: `$FLANNEL_MTU`
|
||||
|
||||
Additionally, for the bridge plugin, `isGateway` will be set to `true`, if not present.
|
||||
|
||||
## Windows Support (Experimental)
|
||||
This plugin supports delegating to the windows CNI plugins (overlay.exe, l2bridge.exe) to work in conjunction with [Flannel on Windows](https://github.com/coreos/flannel/issues/833).
|
||||
Flannel sets up an [HNS Network](https://docs.microsoft.com/en-us/virtualization/windowscontainers/manage-containers/container-networking) in L2Bridge mode for host-gw and in Overlay mode for vxlan.
|
||||
|
||||
The following fields must be set in the delegated plugin configuration:
|
||||
* `name` (string, required): the name of the network (must match the name in Flannel config / name of the HNS network)
|
||||
* `type` (string, optional): set to `win-l2bridge` by default. Can be set to `win-overlay` or other custom windows CNI
|
||||
* `ipMasq`: the inverse of `$FLANNEL_IPMASQ`
|
||||
* `endpointMacPrefix` (string, optional): required for `win-overlay` mode, set to the MAC prefix configured for Flannel
|
||||
* `clusterNetworkPrefix` (string, optional): required for `win-l2bridge` mode, setup NAT if `ipMasq` is set to true
|
||||
|
||||
For `win-l2bridge`, the Flannel CNI plugin will set:
|
||||
* `ipam`: "host-local" type will be used with "subnet" set to `$FLANNEL_SUBNET` and gateway as the .2 address in `$FLANNEL_NETWORK`
|
||||
|
||||
For `win-overlay`, the Flannel CNI plugin will set:
|
||||
* `ipam`: "host-local" type will be used with "subnet" set to `$FLANNEL_SUBNET` and gateway as the .1 address in `$FLANNEL_NETWORK`
|
||||
|
||||
If IPMASQ is true, the Flannel CNI plugin will setup an OutBoundNAT policy and add FLANNEL_SUBNET to any existing exclusions.
|
||||
|
||||
All other delegate config e.g. other HNS endpoint policies in AdditionalArgs will be passed to WINCNI as-is.
|
||||
|
||||
Example VXLAN Flannel CNI config
|
||||
```
|
||||
{
|
||||
"name": "mynet",
|
||||
"type": "flannel",
|
||||
"delegate": {
|
||||
"type": "win-overlay",
|
||||
"endpointMacPrefix": "0E-2A"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For this example, Flannel CNI would generate the following config to delegate to the windows CNI when FLANNEL_NETWORK=10.244.0.0/16, FLANNEL_SUBNET=10.244.1.0/24 and IPMASQ=true
|
||||
```
|
||||
{
|
||||
"name": "mynet",
|
||||
"type": "win-overlay",
|
||||
"endpointMacPrefix": "0E-2A",
|
||||
"ipMasq": true,
|
||||
"ipam": {
|
||||
"subnet": "10.244.1.0/24",
|
||||
"type": "host-local"
|
||||
}
|
||||
}
|
||||
```
|
@ -46,6 +46,8 @@ const (
|
||||
type NetConf struct {
|
||||
types.NetConf
|
||||
|
||||
// IPAM field "replaces" that of types.NetConf which is incomplete
|
||||
IPAM map[string]interface{} `json:"ipam,omitempty"`
|
||||
SubnetFile string `json:"subnetFile"`
|
||||
DataDir string `json:"dataDir"`
|
||||
Delegate map[string]interface{} `json:"delegate"`
|
||||
@ -55,6 +57,8 @@ type NetConf struct {
|
||||
type subnetEnv struct {
|
||||
nw *net.IPNet
|
||||
sn *net.IPNet
|
||||
ip6Nw *net.IPNet
|
||||
ip6Sn *net.IPNet
|
||||
mtu *uint
|
||||
ipmasq *bool
|
||||
}
|
||||
@ -62,11 +66,11 @@ type subnetEnv struct {
|
||||
func (se *subnetEnv) missing() string {
|
||||
m := []string{}
|
||||
|
||||
if se.nw == nil {
|
||||
m = append(m, "FLANNEL_NETWORK")
|
||||
if se.nw == nil && se.ip6Nw == nil {
|
||||
m = append(m, []string{"FLANNEL_NETWORK", "FLANNEL_IPV6_NETWORK"}...)
|
||||
}
|
||||
if se.sn == nil {
|
||||
m = append(m, "FLANNEL_SUBNET")
|
||||
if se.sn == nil && se.ip6Sn == nil {
|
||||
m = append(m, []string{"FLANNEL_SUBNET", "FLANNEL_IPV6_SUBNET"}...)
|
||||
}
|
||||
if se.mtu == nil {
|
||||
m = append(m, "FLANNEL_MTU")
|
||||
@ -89,6 +93,18 @@ func loadFlannelNetConf(bytes []byte) (*NetConf, error) {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func getIPAMRoutes(n *NetConf) ([]types.Route, error) {
|
||||
rtes := []types.Route{}
|
||||
|
||||
if n.IPAM != nil && hasKey(n.IPAM, "routes") {
|
||||
buf, _ := json.Marshal(n.IPAM["routes"])
|
||||
if err := json.Unmarshal(buf, &rtes); err != nil {
|
||||
return rtes, fmt.Errorf("failed to parse ipam.routes: %w", err)
|
||||
}
|
||||
}
|
||||
return rtes, nil
|
||||
}
|
||||
|
||||
func loadFlannelSubnetEnv(fn string) (*subnetEnv, error) {
|
||||
f, err := os.Open(fn)
|
||||
if err != nil {
|
||||
@ -114,6 +130,18 @@ func loadFlannelSubnetEnv(fn string) (*subnetEnv, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case "FLANNEL_IPV6_NETWORK":
|
||||
_, se.ip6Nw, err = net.ParseCIDR(parts[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case "FLANNEL_IPV6_SUBNET":
|
||||
_, se.ip6Sn, err = net.ParseCIDR(parts[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case "FLANNEL_MTU":
|
||||
mtu, err := strconv.ParseUint(parts[1], 10, 32)
|
||||
if err != nil {
|
||||
@ -146,12 +174,19 @@ func saveScratchNetConf(containerID, dataDir string, netconf []byte) error {
|
||||
return ioutil.WriteFile(path, netconf, 0600)
|
||||
}
|
||||
|
||||
func consumeScratchNetConf(containerID, dataDir string) ([]byte, error) {
|
||||
func consumeScratchNetConf(containerID, dataDir string) (func(error), []byte, error) {
|
||||
path := filepath.Join(dataDir, containerID)
|
||||
// Ignore errors when removing - Per spec safe to continue during DEL
|
||||
defer os.Remove(path)
|
||||
|
||||
return ioutil.ReadFile(path)
|
||||
// cleanup will do clean job when no error happens in consuming/using process
|
||||
cleanup := func(err error) {
|
||||
if err == nil {
|
||||
// Ignore errors when removing - Per spec safe to continue during DEL
|
||||
_ = os.Remove(path)
|
||||
}
|
||||
}
|
||||
netConfBytes, err := ioutil.ReadFile(path)
|
||||
|
||||
return cleanup, netConfBytes, err
|
||||
}
|
||||
|
||||
func delegateAdd(cid, dataDir string, netconf map[string]interface{}) error {
|
||||
|
@ -22,12 +22,56 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/invoke"
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Return IPAM section for Delegate using input IPAM if present and replacing
|
||||
// or complementing as needed.
|
||||
func getDelegateIPAM(n *NetConf, fenv *subnetEnv) (map[string]interface{}, error) {
|
||||
ipam := n.IPAM
|
||||
if ipam == nil {
|
||||
ipam = map[string]interface{}{}
|
||||
}
|
||||
|
||||
if !hasKey(ipam, "type") {
|
||||
ipam["type"] = "host-local"
|
||||
}
|
||||
|
||||
var rangesSlice [][]map[string]interface{}
|
||||
|
||||
if fenv.sn != nil && fenv.sn.String() != "" {
|
||||
rangesSlice = append(rangesSlice, []map[string]interface{}{
|
||||
{"subnet": fenv.sn.String()},
|
||||
},
|
||||
)
|
||||
}
|
||||
if fenv.ip6Sn != nil && fenv.ip6Sn.String() != "" {
|
||||
rangesSlice = append(rangesSlice, []map[string]interface{}{
|
||||
{"subnet": fenv.ip6Sn.String()},
|
||||
},
|
||||
)
|
||||
}
|
||||
ipam["ranges"] = rangesSlice
|
||||
|
||||
rtes, err := getIPAMRoutes(n)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read IPAM routes: %w", err)
|
||||
}
|
||||
if fenv.nw != nil {
|
||||
rtes = append(rtes, types.Route{Dst: *fenv.nw})
|
||||
}
|
||||
if fenv.ip6Nw != nil {
|
||||
rtes = append(rtes, types.Route{Dst: *fenv.ip6Nw})
|
||||
}
|
||||
ipam["routes"] = rtes
|
||||
|
||||
return ipam, nil
|
||||
}
|
||||
|
||||
func doCmdAdd(args *skel.CmdArgs, n *NetConf, fenv *subnetEnv) error {
|
||||
n.Delegate["name"] = n.Name
|
||||
|
||||
@ -55,21 +99,17 @@ func doCmdAdd(args *skel.CmdArgs, n *NetConf, fenv *subnetEnv) error {
|
||||
n.Delegate["cniVersion"] = n.CNIVersion
|
||||
}
|
||||
|
||||
n.Delegate["ipam"] = map[string]interface{}{
|
||||
"type": "host-local",
|
||||
"subnet": fenv.sn.String(),
|
||||
"routes": []types.Route{
|
||||
{
|
||||
Dst: *fenv.nw,
|
||||
},
|
||||
},
|
||||
ipam, err := getDelegateIPAM(n, fenv)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to assemble Delegate IPAM: %w", err)
|
||||
}
|
||||
n.Delegate["ipam"] = ipam
|
||||
|
||||
return delegateAdd(args.ContainerID, n.DataDir, n.Delegate)
|
||||
}
|
||||
|
||||
func doCmdDel(args *skel.CmdArgs, n *NetConf) error {
|
||||
netconfBytes, err := consumeScratchNetConf(args.ContainerID, n.DataDir)
|
||||
func doCmdDel(args *skel.CmdArgs, n *NetConf) (err error) {
|
||||
cleanup, netConfBytes, err := consumeScratchNetConf(args.ContainerID, n.DataDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// Per spec should ignore error if resources are missing / already removed
|
||||
@ -78,10 +118,15 @@ func doCmdDel(args *skel.CmdArgs, n *NetConf) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// cleanup will work when no error happens
|
||||
defer func() {
|
||||
cleanup(err)
|
||||
}()
|
||||
|
||||
nc := &types.NetConf{}
|
||||
if err = json.Unmarshal(netconfBytes, nc); err != nil {
|
||||
if err = json.Unmarshal(netConfBytes, nc); err != nil {
|
||||
return fmt.Errorf("failed to parse netconf: %v", err)
|
||||
}
|
||||
|
||||
return invoke.DelegateDel(context.TODO(), nc.Type, netconfBytes, nil)
|
||||
return invoke.DelegateDel(context.TODO(), nc.Type, netConfBytes, nil)
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@ -29,10 +30,14 @@ import (
|
||||
|
||||
var _ = Describe("Flannel", func() {
|
||||
var (
|
||||
originalNS ns.NetNS
|
||||
input string
|
||||
subnetFile string
|
||||
dataDir string
|
||||
originalNS ns.NetNS
|
||||
onlyIpv4Input string
|
||||
onlyIpv6Input string
|
||||
dualStackInput string
|
||||
onlyIpv4SubnetFile string
|
||||
onlyIpv6SubnetFile string
|
||||
dualStackSubnetFile string
|
||||
dataDir string
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
@ -47,17 +52,47 @@ var _ = Describe("Flannel", func() {
|
||||
|
||||
const inputTemplate = `
|
||||
{
|
||||
"name": "cni-flannel",
|
||||
"type": "flannel",
|
||||
"subnetFile": "%s",
|
||||
"dataDir": "%s"
|
||||
"name": "cni-flannel",
|
||||
"type": "flannel",
|
||||
"subnetFile": "%s",
|
||||
"dataDir": "%s"%s
|
||||
}`
|
||||
|
||||
const flannelSubnetEnv = `
|
||||
const inputIPAMTemplate = `
|
||||
"unknown-param": "unknown-value",
|
||||
"routes": [%s]%s`
|
||||
|
||||
const inputIPAMType = "my-ipam"
|
||||
|
||||
const inputIPAMNoTypeTemplate = `
|
||||
{
|
||||
"unknown-param": "unknown-value",
|
||||
"routes": [%s]%s
|
||||
}`
|
||||
|
||||
const inputIPAMRoutes = `
|
||||
{ "dst": "10.96.0.0/12" },
|
||||
{ "dst": "192.168.244.0/24", "gw": "10.1.17.20" }`
|
||||
|
||||
const onlyIpv4FlannelSubnetEnv = `
|
||||
FLANNEL_NETWORK=10.1.0.0/16
|
||||
FLANNEL_SUBNET=10.1.17.1/24
|
||||
FLANNEL_MTU=1472
|
||||
FLANNEL_IPMASQ=true
|
||||
`
|
||||
const onlyIpv6FlannelSubnetEnv = `
|
||||
FLANNEL_IPV6_NETWORK=fc00::/48
|
||||
FLANNEL_IPV6_SUBNET=fc00::1/64
|
||||
FLANNEL_MTU=1472
|
||||
FLANNEL_IPMASQ=true
|
||||
`
|
||||
const dualStackFlannelSubnetEnv = `
|
||||
FLANNEL_NETWORK=10.1.0.0/16
|
||||
FLANNEL_SUBNET=10.1.17.1/24
|
||||
FLANNEL_IPV6_NETWORK=fc00::/48
|
||||
FLANNEL_IPV6_SUBNET=fc00::1/64
|
||||
FLANNEL_MTU=1472
|
||||
FLANNEL_IPMASQ=true
|
||||
`
|
||||
|
||||
var writeSubnetEnv = func(contents string) string {
|
||||
@ -68,119 +103,373 @@ FLANNEL_IPMASQ=true
|
||||
return file.Name()
|
||||
}
|
||||
|
||||
var makeInputIPAM = func(ipamType, routes, extra string) string {
|
||||
c := "{\n"
|
||||
if len(ipamType) > 0 {
|
||||
c += fmt.Sprintf(" \"type\": \"%s\",", ipamType)
|
||||
}
|
||||
c += fmt.Sprintf(inputIPAMTemplate, routes, extra)
|
||||
c += "\n}"
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
var makeInput = func(inputIPAM string, subnetFile string) string {
|
||||
ipamPart := ""
|
||||
if len(inputIPAM) > 0 {
|
||||
ipamPart = ",\n \"ipam\":\n" + inputIPAM
|
||||
}
|
||||
|
||||
return fmt.Sprintf(inputTemplate, subnetFile, dataDir, ipamPart)
|
||||
}
|
||||
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
// flannel subnet.env
|
||||
subnetFile = writeSubnetEnv(flannelSubnetEnv)
|
||||
onlyIpv4SubnetFile = writeSubnetEnv(onlyIpv4FlannelSubnetEnv)
|
||||
onlyIpv6SubnetFile = writeSubnetEnv(onlyIpv6FlannelSubnetEnv)
|
||||
dualStackSubnetFile = writeSubnetEnv(dualStackFlannelSubnetEnv)
|
||||
|
||||
// flannel state dir
|
||||
dataDir, err = ioutil.TempDir("", "dataDir")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
input = fmt.Sprintf(inputTemplate, subnetFile, dataDir)
|
||||
onlyIpv4Input = makeInput("", onlyIpv4SubnetFile)
|
||||
onlyIpv6Input = makeInput("", onlyIpv6SubnetFile)
|
||||
dualStackInput = makeInput("", dualStackSubnetFile)
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
os.Remove(subnetFile)
|
||||
os.Remove(onlyIpv4SubnetFile)
|
||||
os.Remove(onlyIpv6SubnetFile)
|
||||
os.Remove(dualStackSubnetFile)
|
||||
os.Remove(dataDir)
|
||||
})
|
||||
|
||||
Describe("CNI lifecycle", func() {
|
||||
It("uses dataDir for storing network configuration", func() {
|
||||
const IFNAME = "eth0"
|
||||
Context("when using only ipv4 stack", func() {
|
||||
It("uses dataDir for storing network configuration with ipv4 stack", func() {
|
||||
const IFNAME = "eth0"
|
||||
|
||||
targetNs, err := testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer targetNs.Close()
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "some-container-id",
|
||||
Netns: targetNs.Path(),
|
||||
IfName: IFNAME,
|
||||
StdinData: []byte(input),
|
||||
}
|
||||
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
By("calling ADD")
|
||||
resI, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
targetNs, err := testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer targetNs.Close()
|
||||
|
||||
By("check that plugin writes to net config to dataDir")
|
||||
path := fmt.Sprintf("%s/%s", dataDir, "some-container-id")
|
||||
Expect(path).Should(BeAnExistingFile())
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "some-container-id-ipv4",
|
||||
Netns: targetNs.Path(),
|
||||
IfName: IFNAME,
|
||||
StdinData: []byte(onlyIpv4Input),
|
||||
}
|
||||
|
||||
netConfBytes, err := ioutil.ReadFile(path)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
expected := `{
|
||||
"ipMasq" : false,
|
||||
"ipam" : {
|
||||
"routes" : [
|
||||
{
|
||||
"dst" : "10.1.0.0/16"
|
||||
}
|
||||
],
|
||||
"subnet" : "10.1.17.0/24",
|
||||
"type" : "host-local"
|
||||
},
|
||||
"isGateway": true,
|
||||
"mtu" : 1472,
|
||||
"name" : "cni-flannel",
|
||||
"type" : "bridge"
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
By("calling ADD with ipv4 stack")
|
||||
resI, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("check that plugin writes the net config to dataDir with ipv4 stack")
|
||||
path := fmt.Sprintf("%s/%s", dataDir, "some-container-id-ipv4")
|
||||
Expect(path).Should(BeAnExistingFile())
|
||||
|
||||
netConfBytes, err := ioutil.ReadFile(path)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
expected := `{
|
||||
"ipMasq": false,
|
||||
"ipam": {
|
||||
"routes": [
|
||||
{
|
||||
"dst": "10.1.0.0/16"
|
||||
}
|
||||
],
|
||||
"ranges": [
|
||||
[{
|
||||
"subnet": "10.1.17.0/24"
|
||||
}]
|
||||
],
|
||||
"type": "host-local"
|
||||
},
|
||||
"isGateway": true,
|
||||
"mtu": 1472,
|
||||
"name": "cni-flannel",
|
||||
"type": "bridge"
|
||||
}
|
||||
`
|
||||
Expect(netConfBytes).Should(MatchJSON(expected))
|
||||
Expect(netConfBytes).Should(MatchJSON(expected))
|
||||
|
||||
result, err := current.NewResultFromResult(resI)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(result.IPs).To(HaveLen(1))
|
||||
result, err := current.NewResultFromResult(resI)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(result.IPs).To(HaveLen(1))
|
||||
|
||||
By("calling DEL")
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
By("calling DEL with ipv4 stack")
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("check that plugin removes net config from state dir with ipv4 stack")
|
||||
Expect(path).ShouldNot(BeAnExistingFile())
|
||||
|
||||
By("calling DEL again with ipv4 stack")
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
By("check that plugin does not fail due to missing net config with ipv4 stack")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Context("when using only ipv6 stack", func() {
|
||||
It("uses dataDir for storing network configuration with ipv6 stack", func() {
|
||||
const IFNAME = "eth0"
|
||||
|
||||
targetNs, err := testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer targetNs.Close()
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "some-container-id-ipv6",
|
||||
Netns: targetNs.Path(),
|
||||
IfName: IFNAME,
|
||||
StdinData: []byte(onlyIpv6Input),
|
||||
}
|
||||
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
By("calling ADD with ipv6 stack")
|
||||
resI, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("check that plugin writes the net config to dataDir with ipv6 stack")
|
||||
path := fmt.Sprintf("%s/%s", dataDir, "some-container-id-ipv6")
|
||||
Expect(path).Should(BeAnExistingFile())
|
||||
|
||||
netConfBytes, err := ioutil.ReadFile(path)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
expected := `{
|
||||
"ipMasq": false,
|
||||
"ipam": {
|
||||
"routes": [
|
||||
{
|
||||
"dst": "fc00::/48"
|
||||
}
|
||||
],
|
||||
"ranges": [
|
||||
[{
|
||||
"subnet": "fc00::/64"
|
||||
}]
|
||||
],
|
||||
"type": "host-local"
|
||||
},
|
||||
"isGateway": true,
|
||||
"mtu": 1472,
|
||||
"name": "cni-flannel",
|
||||
"type": "bridge"
|
||||
}
|
||||
`
|
||||
Expect(netConfBytes).Should(MatchJSON(expected))
|
||||
|
||||
result, err := current.NewResultFromResult(resI)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(result.IPs).To(HaveLen(1))
|
||||
|
||||
By("calling DEL with ipv6 stack")
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("check that plugin removes net config from state dir with ipv6 stack")
|
||||
Expect(path).ShouldNot(BeAnExistingFile())
|
||||
|
||||
By("calling DEL again with ipv6 stack")
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
By("check that plugin does not fail due to missing net config with ipv6 stack")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Context("when using dual stack", func() {
|
||||
It("uses dataDir for storing network configuration with dual stack", func() {
|
||||
const IFNAME = "eth0"
|
||||
|
||||
targetNs, err := testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer targetNs.Close()
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "some-container-id-dual-stack",
|
||||
Netns: targetNs.Path(),
|
||||
IfName: IFNAME,
|
||||
StdinData: []byte(dualStackInput),
|
||||
}
|
||||
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
By("calling ADD with dual stack")
|
||||
resI, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("check that plugin writes the net config to dataDir with dual stack")
|
||||
path := fmt.Sprintf("%s/%s", dataDir, "some-container-id-dual-stack")
|
||||
Expect(path).Should(BeAnExistingFile())
|
||||
|
||||
netConfBytes, err := ioutil.ReadFile(path)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
expected := `{
|
||||
"ipMasq": false,
|
||||
"ipam": {
|
||||
"routes": [
|
||||
{
|
||||
"dst": "10.1.0.0/16"
|
||||
},
|
||||
{
|
||||
"dst": "fc00::/48"
|
||||
}
|
||||
],
|
||||
"ranges": [
|
||||
[{
|
||||
"subnet": "10.1.17.0/24"
|
||||
}],
|
||||
[{
|
||||
"subnet": "fc00::/64"
|
||||
}]
|
||||
],
|
||||
"type": "host-local"
|
||||
},
|
||||
"isGateway": true,
|
||||
"mtu": 1472,
|
||||
"name": "cni-flannel",
|
||||
"type": "bridge"
|
||||
}
|
||||
`
|
||||
Expect(netConfBytes).Should(MatchJSON(expected))
|
||||
|
||||
result, err := current.NewResultFromResult(resI)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(result.IPs).To(HaveLen(2))
|
||||
|
||||
By("calling DEL with dual stack")
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("check that plugin removes net config from state dir with dual stack")
|
||||
Expect(path).ShouldNot(BeAnExistingFile())
|
||||
|
||||
By("calling DEL again with dual stack")
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
By("check that plugin does not fail due to missing net config with dual stack")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("check that plugin removes net config from state dir")
|
||||
Expect(path).ShouldNot(BeAnExistingFile())
|
||||
|
||||
By("calling DEL again")
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
By("check that plugin does not fail due to missing net config")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("loadFlannelNetConf", func() {
|
||||
Context("when subnetFile and dataDir are specified", func() {
|
||||
It("loads flannel network config", func() {
|
||||
conf, err := loadFlannelNetConf([]byte(input))
|
||||
Context("when subnetFile and dataDir are specified with ipv4 stack", func() {
|
||||
It("loads flannel network config with ipv4 stack", func() {
|
||||
conf, err := loadFlannelNetConf([]byte(onlyIpv4Input))
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(conf.Name).To(Equal("cni-flannel"))
|
||||
Expect(conf.Type).To(Equal("flannel"))
|
||||
Expect(conf.SubnetFile).To(Equal(subnetFile))
|
||||
Expect(conf.SubnetFile).To(Equal(onlyIpv4SubnetFile))
|
||||
Expect(conf.DataDir).To(Equal(dataDir))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when defaulting subnetFile and dataDir", func() {
|
||||
Context("when subnetFile and dataDir are specified with ipv6 stack", func() {
|
||||
It("loads flannel network config with ipv6 stack", func() {
|
||||
conf, err := loadFlannelNetConf([]byte(onlyIpv6Input))
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(conf.Name).To(Equal("cni-flannel"))
|
||||
Expect(conf.Type).To(Equal("flannel"))
|
||||
Expect(conf.SubnetFile).To(Equal(onlyIpv6SubnetFile))
|
||||
Expect(conf.DataDir).To(Equal(dataDir))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when subnetFile and dataDir are specified with dual stack", func() {
|
||||
It("loads flannel network config with dual stack", func() {
|
||||
conf, err := loadFlannelNetConf([]byte(dualStackInput))
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(conf.Name).To(Equal("cni-flannel"))
|
||||
Expect(conf.Type).To(Equal("flannel"))
|
||||
Expect(conf.SubnetFile).To(Equal(dualStackSubnetFile))
|
||||
Expect(conf.DataDir).To(Equal(dataDir))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when defaulting subnetFile and dataDir with ipv4 stack", func() {
|
||||
BeforeEach(func() {
|
||||
input = `{
|
||||
onlyIpv4Input = `{
|
||||
"name": "cni-flannel",
|
||||
"type": "flannel"
|
||||
}`
|
||||
})
|
||||
|
||||
It("loads flannel network config with defaults", func() {
|
||||
conf, err := loadFlannelNetConf([]byte(input))
|
||||
It("loads flannel network config with defaults with ipv4 stack", func() {
|
||||
conf, err := loadFlannelNetConf([]byte(onlyIpv4Input))
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(conf.Name).To(Equal("cni-flannel"))
|
||||
Expect(conf.Type).To(Equal("flannel"))
|
||||
Expect(conf.SubnetFile).To(Equal(defaultSubnetFile))
|
||||
Expect(conf.DataDir).To(Equal(defaultDataDir))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when defaulting subnetFile and dataDir with ipv6 stack", func() {
|
||||
BeforeEach(func() {
|
||||
onlyIpv6Input = `{
|
||||
"name": "cni-flannel",
|
||||
"type": "flannel"
|
||||
}`
|
||||
})
|
||||
|
||||
It("loads flannel network config with defaults with ipv6 stack", func() {
|
||||
conf, err := loadFlannelNetConf([]byte(onlyIpv6Input))
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(conf.Name).To(Equal("cni-flannel"))
|
||||
Expect(conf.Type).To(Equal("flannel"))
|
||||
Expect(conf.SubnetFile).To(Equal(defaultSubnetFile))
|
||||
Expect(conf.DataDir).To(Equal(defaultDataDir))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when defaulting subnetFile and dataDir with dual stack", func() {
|
||||
BeforeEach(func() {
|
||||
dualStackInput = `{
|
||||
"name": "cni-flannel",
|
||||
"type": "flannel"
|
||||
}`
|
||||
})
|
||||
|
||||
It("loads flannel network config with defaults with dual stack", func() {
|
||||
conf, err := loadFlannelNetConf([]byte(dualStackInput))
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(conf.Name).To(Equal("cni-flannel"))
|
||||
Expect(conf.Type).To(Equal("flannel"))
|
||||
@ -190,9 +479,9 @@ FLANNEL_IPMASQ=true
|
||||
})
|
||||
|
||||
Describe("loadFlannelSubnetEnv", func() {
|
||||
Context("when flannel subnet env is valid", func() {
|
||||
It("loads flannel subnet config", func() {
|
||||
conf, err := loadFlannelSubnetEnv(subnetFile)
|
||||
Context("when flannel subnet env is valid with ipv4 stack", func() {
|
||||
It("loads flannel subnet config with ipv4 stack", func() {
|
||||
conf, err := loadFlannelSubnetEnv(onlyIpv4SubnetFile)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(conf.nw.String()).To(Equal("10.1.0.0/16"))
|
||||
Expect(conf.sn.String()).To(Equal("10.1.17.0/24"))
|
||||
@ -202,15 +491,192 @@ FLANNEL_IPMASQ=true
|
||||
})
|
||||
})
|
||||
|
||||
Context("when flannel subnet env is invalid", func() {
|
||||
Context("when flannel subnet env is valid with ipv6 stack", func() {
|
||||
It("loads flannel subnet config with ipv6 stack", func() {
|
||||
conf, err := loadFlannelSubnetEnv(onlyIpv6SubnetFile)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(conf.ip6Nw.String()).To(Equal("fc00::/48"))
|
||||
Expect(conf.ip6Sn.String()).To(Equal("fc00::/64"))
|
||||
var mtu uint = 1472
|
||||
Expect(*conf.mtu).To(Equal(mtu))
|
||||
Expect(*conf.ipmasq).To(BeTrue())
|
||||
})
|
||||
})
|
||||
|
||||
Context("when flannel subnet env is valid with dual stack", func() {
|
||||
It("loads flannel subnet config with dual stack", func() {
|
||||
conf, err := loadFlannelSubnetEnv(dualStackSubnetFile)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(conf.nw.String()).To(Equal("10.1.0.0/16"))
|
||||
Expect(conf.sn.String()).To(Equal("10.1.17.0/24"))
|
||||
Expect(conf.ip6Nw.String()).To(Equal("fc00::/48"))
|
||||
Expect(conf.ip6Sn.String()).To(Equal("fc00::/64"))
|
||||
var mtu uint = 1472
|
||||
Expect(*conf.mtu).To(Equal(mtu))
|
||||
Expect(*conf.ipmasq).To(BeTrue())
|
||||
})
|
||||
})
|
||||
|
||||
Context("when flannel subnet env is invalid with ipv4 stack", func() {
|
||||
BeforeEach(func() {
|
||||
subnetFile = writeSubnetEnv("foo=bar")
|
||||
onlyIpv4SubnetFile = writeSubnetEnv("foo=bar")
|
||||
})
|
||||
It("returns an error", func() {
|
||||
_, err := loadFlannelSubnetEnv(subnetFile)
|
||||
Expect(err).To(MatchError(ContainSubstring("missing FLANNEL_NETWORK, FLANNEL_SUBNET, FLANNEL_MTU, FLANNEL_IPMASQ")))
|
||||
_, err := loadFlannelSubnetEnv(onlyIpv4SubnetFile)
|
||||
Expect(err).To(MatchError(ContainSubstring("missing FLANNEL_NETWORK, FLANNEL_IPV6_NETWORK, FLANNEL_SUBNET, FLANNEL_IPV6_SUBNET, FLANNEL_MTU, FLANNEL_IPMASQ")))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when flannel subnet env is invalid with ipv6 stack", func() {
|
||||
BeforeEach(func() {
|
||||
onlyIpv6SubnetFile = writeSubnetEnv("foo=bar")
|
||||
})
|
||||
It("returns an error", func() {
|
||||
_, err := loadFlannelSubnetEnv(onlyIpv6SubnetFile)
|
||||
Expect(err).To(MatchError(ContainSubstring("missing FLANNEL_NETWORK, FLANNEL_IPV6_NETWORK, FLANNEL_SUBNET, FLANNEL_IPV6_SUBNET, FLANNEL_MTU, FLANNEL_IPMASQ")))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when flannel subnet env is invalid with dual stack", func() {
|
||||
BeforeEach(func() {
|
||||
dualStackSubnetFile = writeSubnetEnv("foo=bar")
|
||||
})
|
||||
It("returns an error", func() {
|
||||
_, err := loadFlannelSubnetEnv(dualStackSubnetFile)
|
||||
Expect(err).To(MatchError(ContainSubstring("missing FLANNEL_NETWORK, FLANNEL_IPV6_NETWORK, FLANNEL_SUBNET, FLANNEL_IPV6_SUBNET, FLANNEL_MTU, FLANNEL_IPMASQ")))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("getDelegateIPAM", func() {
|
||||
Context("when input IPAM is provided with ipv4 stack", func() {
|
||||
BeforeEach(func() {
|
||||
inputIPAM := makeInputIPAM(inputIPAMType, inputIPAMRoutes, "")
|
||||
onlyIpv4Input = makeInput(inputIPAM, onlyIpv4SubnetFile)
|
||||
})
|
||||
It("configures Delegate IPAM accordingly with ipv4 stack", func() {
|
||||
conf, err := loadFlannelNetConf([]byte(onlyIpv4Input))
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
fenv, err := loadFlannelSubnetEnv(onlyIpv4SubnetFile)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
ipam, err := getDelegateIPAM(conf, fenv)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
podsRoute := "{ \"dst\": \"10.1.0.0/16\" }\n"
|
||||
subnet := "\"ranges\": [[{\"subnet\": \"10.1.17.0/24\"}]]"
|
||||
expected := makeInputIPAM(inputIPAMType, inputIPAMRoutes+",\n"+podsRoute, ",\n"+subnet)
|
||||
buf, _ := json.Marshal(ipam)
|
||||
Expect(buf).Should(MatchJSON(expected))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when input IPAM is provided with ipv6 stack", func() {
|
||||
BeforeEach(func() {
|
||||
inputIPAM := makeInputIPAM(inputIPAMType, inputIPAMRoutes, "")
|
||||
onlyIpv6Input = makeInput(inputIPAM, onlyIpv6SubnetFile)
|
||||
})
|
||||
It("configures Delegate IPAM accordingly with ipv6 stack", func() {
|
||||
conf, err := loadFlannelNetConf([]byte(onlyIpv6Input))
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
fenv, err := loadFlannelSubnetEnv(onlyIpv6SubnetFile)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
ipam, err := getDelegateIPAM(conf, fenv)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
podsRoute := "{ \"dst\": \"fc00::/48\" }\n"
|
||||
subnet := "\"ranges\": [[{ \"subnet\": \"fc00::/64\" }]]"
|
||||
expected := makeInputIPAM(inputIPAMType, inputIPAMRoutes+",\n"+podsRoute, ",\n"+subnet)
|
||||
buf, _ := json.Marshal(ipam)
|
||||
Expect(buf).Should(MatchJSON(expected))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when input IPAM is provided with dual stack", func() {
|
||||
BeforeEach(func() {
|
||||
inputIPAM := makeInputIPAM(inputIPAMType, inputIPAMRoutes, "")
|
||||
dualStackInput = makeInput(inputIPAM, dualStackSubnetFile)
|
||||
})
|
||||
It("configures Delegate IPAM accordingly with dual stack", func() {
|
||||
conf, err := loadFlannelNetConf([]byte(dualStackInput))
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
fenv, err := loadFlannelSubnetEnv(dualStackSubnetFile)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
ipam, err := getDelegateIPAM(conf, fenv)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
podsRoute := "{ \"dst\": \"10.1.0.0/16\" }" + ",\n" + "{ \"dst\": \"fc00::/48\" }\n"
|
||||
subnet := "\"ranges\": [[{ \"subnet\": \"10.1.17.0/24\" }],\n[{ \"subnet\": \"fc00::/64\" }]]"
|
||||
expected := makeInputIPAM(inputIPAMType, inputIPAMRoutes+",\n"+podsRoute, ",\n"+subnet)
|
||||
buf, _ := json.Marshal(ipam)
|
||||
Expect(buf).Should(MatchJSON(expected))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when input IPAM is provided without 'type' with ipv4 stack", func() {
|
||||
BeforeEach(func() {
|
||||
inputIPAM := makeInputIPAM("", inputIPAMRoutes, "")
|
||||
onlyIpv4Input = makeInput(inputIPAM, onlyIpv4SubnetFile)
|
||||
})
|
||||
It("configures Delegate IPAM with 'host-local' ipam with ipv4 stack", func() {
|
||||
conf, err := loadFlannelNetConf([]byte(onlyIpv4Input))
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
fenv, err := loadFlannelSubnetEnv(onlyIpv4SubnetFile)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
ipam, err := getDelegateIPAM(conf, fenv)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
podsRoute := "{ \"dst\": \"10.1.0.0/16\" }\n"
|
||||
subnet := "\"ranges\": [[{\"subnet\": \"10.1.17.0/24\"}]]"
|
||||
expected := makeInputIPAM("host-local", inputIPAMRoutes+",\n"+podsRoute, ",\n"+subnet)
|
||||
buf, _ := json.Marshal(ipam)
|
||||
Expect(buf).Should(MatchJSON(expected))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when input IPAM is provided without 'type' with ipv6 stack", func() {
|
||||
BeforeEach(func() {
|
||||
inputIPAM := makeInputIPAM("", inputIPAMRoutes, "")
|
||||
onlyIpv6Input = makeInput(inputIPAM, onlyIpv6SubnetFile)
|
||||
})
|
||||
It("configures Delegate IPAM with 'host-local' ipam with ipv6 stack", func() {
|
||||
conf, err := loadFlannelNetConf([]byte(onlyIpv6Input))
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
fenv, err := loadFlannelSubnetEnv(onlyIpv6SubnetFile)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
ipam, err := getDelegateIPAM(conf, fenv)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
podsRoute := "{ \"dst\": \"fc00::/48\" }\n"
|
||||
subnet := "\"ranges\": [[{ \"subnet\": \"fc00::/64\" }]]"
|
||||
expected := makeInputIPAM("host-local", inputIPAMRoutes+",\n"+podsRoute, ",\n"+subnet)
|
||||
buf, _ := json.Marshal(ipam)
|
||||
Expect(buf).Should(MatchJSON(expected))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when input IPAM is provided without 'type' with dual stack", func() {
|
||||
BeforeEach(func() {
|
||||
inputIPAM := makeInputIPAM("", inputIPAMRoutes, "")
|
||||
dualStackInput = makeInput(inputIPAM, dualStackSubnetFile)
|
||||
})
|
||||
It("configures Delegate IPAM with 'host-local' ipam with dual stack", func() {
|
||||
conf, err := loadFlannelNetConf([]byte(dualStackInput))
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
fenv, err := loadFlannelSubnetEnv(dualStackSubnetFile)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
ipam, err := getDelegateIPAM(conf, fenv)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
podsRoute := "{ \"dst\": \"10.1.0.0/16\" }" + ",\n" + "{ \"dst\": \"fc00::/48\" }\n"
|
||||
subnet := "\"ranges\": [[{ \"subnet\": \"10.1.17.0/24\" }],\n[{ \"subnet\": \"fc00::/64\" }]]"
|
||||
expected := makeInputIPAM("host-local", inputIPAMRoutes+",\n"+podsRoute, ",\n"+subnet)
|
||||
buf, _ := json.Marshal(ipam)
|
||||
Expect(buf).Should(MatchJSON(expected))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -55,8 +55,8 @@ func doCmdAdd(args *skel.CmdArgs, n *NetConf, fenv *subnetEnv) error {
|
||||
return delegateAdd(hns.GetSandboxContainerID(args.ContainerID, args.Netns), n.DataDir, n.Delegate)
|
||||
}
|
||||
|
||||
func doCmdDel(args *skel.CmdArgs, n *NetConf) error {
|
||||
netconfBytes, err := consumeScratchNetConf(hns.GetSandboxContainerID(args.ContainerID, args.Netns), n.DataDir)
|
||||
func doCmdDel(args *skel.CmdArgs, n *NetConf) (err error) {
|
||||
cleanup, netConfBytes, err := consumeScratchNetConf(hns.GetSandboxContainerID(args.ContainerID, args.Netns), n.DataDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// Per spec should ignore error if resources are missing / already removed
|
||||
@ -65,10 +65,15 @@ func doCmdDel(args *skel.CmdArgs, n *NetConf) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// cleanup will work when no error happens
|
||||
defer func() {
|
||||
cleanup(err)
|
||||
}()
|
||||
|
||||
nc := &types.NetConf{}
|
||||
if err = json.Unmarshal(netconfBytes, nc); err != nil {
|
||||
if err = json.Unmarshal(netConfBytes, nc); err != nil {
|
||||
return fmt.Errorf("failed to parse netconf: %v", err)
|
||||
}
|
||||
|
||||
return invoke.DelegateDel(context.TODO(), nc.Type, netconfBytes, nil)
|
||||
return invoke.DelegateDel(context.TODO(), nc.Type, netConfBytes, nil)
|
||||
}
|
||||
|
@ -1,133 +1,5 @@
|
||||
## Port-mapping plugin
|
||||
|
||||
This plugin will forward traffic from one or more ports on the host to the
|
||||
container. It expects to be run as a chained plugin.
|
||||
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
|
||||
|
||||
## Usage
|
||||
You should use this plugin as part of a network configuration list. It accepts
|
||||
the following configuration options:
|
||||
You can find it online here: https://cni.dev/plugins/meta/portmap/
|
||||
|
||||
* `snat` - boolean, default true. If true or omitted, set up the SNAT chains
|
||||
* `markMasqBit` - int, (0-31), default 13. The mark bit to use for masquerading (see section SNAT). Cannot be set when `externalSetMarkChain` is used.
|
||||
* `externalSetMarkChain` - string, default nil. If you already have a Masquerade mark chain (e.g. Kubernetes), specify it here. This will use that instead of creating a separate chain. When this is set, `markMasqBit` must be unspecified.
|
||||
* `conditionsV4`, `conditionsV6` - array of strings. A list of arbitrary `iptables`
|
||||
matches to add to the per-container rule. This may be useful if you wish to
|
||||
exclude specific IPs from port-mapping
|
||||
|
||||
The plugin expects to receive the actual list of port mappings via the
|
||||
`portMappings` [capability argument](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md)
|
||||
|
||||
A sample standalone config list for Kubernetes (with the file extension .conflist) might
|
||||
look like:
|
||||
|
||||
```json
|
||||
{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "ptp",
|
||||
"ipMasq": true,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "172.16.30.0/24",
|
||||
"routes": [
|
||||
{
|
||||
"dst": "0.0.0.0/0"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "portmap",
|
||||
"capabilities": {"portMappings": true},
|
||||
"externalSetMarkChain": "KUBE-MARK-MASQ"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
A configuration file with all options set:
|
||||
```json
|
||||
{
|
||||
"type": "portmap",
|
||||
"capabilities": {"portMappings": true},
|
||||
"snat": true,
|
||||
"markMasqBit": 13,
|
||||
"externalSetMarkChain": "CNI-HOSTPORT-SETMARK",
|
||||
"conditionsV4": ["!", "-d", "192.0.2.0/24"],
|
||||
"conditionsV6": ["!", "-d", "fc00::/7"]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Rule structure
|
||||
The plugin sets up two sequences of chains and rules - one "primary" DNAT
|
||||
sequence to rewrite the destination, and one additional SNAT sequence that
|
||||
will masquerade traffic as needed.
|
||||
|
||||
|
||||
### DNAT
|
||||
The DNAT rule rewrites the destination port and address of new connections.
|
||||
There is a top-level chain, `CNI-HOSTPORT-DNAT` which is always created and
|
||||
never deleted. Each plugin execution creates an additional chain for ease
|
||||
of cleanup. So, if a single container exists on IP 172.16.30.2 with ports
|
||||
8080 and 8043 on the host forwarded to ports 80 and 443 in the container, the
|
||||
rules look like this:
|
||||
|
||||
`PREROUTING`, `OUTPUT` chains:
|
||||
- `--dst-type LOCAL -j CNI-HOSTPORT-DNAT`
|
||||
|
||||
`CNI-HOSTPORT-DNAT` chain:
|
||||
- `${ConditionsV4/6} -p tcp --destination-ports 8080,8043 -j CNI-DN-xxxxxx` (where xxxxxx is a function of the ContainerID and network name)
|
||||
|
||||
`CNI-HOSTPORT-SETMARK` chain:
|
||||
- `-j MARK --set-xmark 0x2000/0x2000`
|
||||
|
||||
`CNI-DN-xxxxxx` chain:
|
||||
- `-p tcp -s 172.16.30.2 --dport 8080 -j CNI-HOSTPORT-SETMARK` (masquerade hairpin traffic)
|
||||
- `-p tcp -s 127.0.0.1 --dport 8080 -j CNI-HOSTPORT-SETMARK` (masquerade localhost traffic)
|
||||
- `-p tcp --dport 8080 -j DNAT --to-destination 172.16.30.2:80` (rewrite destination)
|
||||
- `-p tcp -s 172.16.30.2 --dport 8043 -j CNI-HOSTPORT-SETMARK`
|
||||
- `-p tcp -s 127.0.0.1 --dport 8043 -j CNI-HOSTPORT-SETMARK`
|
||||
- `-p tcp --dport 8043 -j DNAT --to-destination 172.16.30.2:443`
|
||||
|
||||
New connections to the host will have to traverse every rule, so large numbers
|
||||
of port forwards may have a performance impact. This won't affect established
|
||||
connections, just the first packet.
|
||||
|
||||
### SNAT (Masquerade)
|
||||
Some packets also need to have the source address rewritten:
|
||||
* connections from localhost
|
||||
* Hairpin traffic back to the container.
|
||||
|
||||
In the DNAT chain, a bit is set on the mark for packets that need snat. This
|
||||
chain performs that masquerading. By default, bit 13 is set, but this is
|
||||
configurable. If you are using other tools that also use the iptables mark,
|
||||
you should make sure this doesn't conflict.
|
||||
|
||||
Some container runtimes, most notably Kubernetes, already have a set of rules
|
||||
for masquerading when a specific mark bit is set. If so enabled, the plugin
|
||||
will use that chain instead.
|
||||
|
||||
`POSTROUTING`:
|
||||
- `-j CNI-HOSTPORT-MASQ`
|
||||
|
||||
`CNI-HOSTPORT-MASQ`:
|
||||
- `--mark 0x2000 -j MASQUERADE`
|
||||
|
||||
Because MASQUERADE happens in POSTROUTING, it means that packets with source ip
|
||||
127.0.0.1 need to first pass a routing boundary before being masqueraded. By
|
||||
default, that is not allowed in Linux. So, the plugin needs to enable the sysctl
|
||||
`net.ipv4.conf.IFNAME.route_localnet`, where IFNAME is the name of the host-side
|
||||
interface that routes traffic to the container.
|
||||
|
||||
There is no equivalent to `route_localnet` for ipv6, so connections to ::1
|
||||
will not be portmapped for ipv6. If you need port forwarding from localhost,
|
||||
your container must have an ipv4 address.
|
||||
|
||||
|
||||
## Known issues
|
||||
- ipsets could improve efficiency
|
||||
- forwarding from localhost does not work with ipv6.
|
||||
|
@ -28,12 +28,14 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
@ -60,9 +62,9 @@ type PortMapConf struct {
|
||||
|
||||
// These are fields parsed out of the config or the environment;
|
||||
// included here for convenience
|
||||
ContainerID string `json:"-"`
|
||||
ContIPv4 net.IP `json:"-"`
|
||||
ContIPv6 net.IP `json:"-"`
|
||||
ContainerID string `json:"-"`
|
||||
ContIPv4 net.IPNet `json:"-"`
|
||||
ContIPv6 net.IPNet `json:"-"`
|
||||
}
|
||||
|
||||
// The default mark bit to signal that masquerading is required
|
||||
@ -85,16 +87,28 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
|
||||
netConf.ContainerID = args.ContainerID
|
||||
|
||||
if netConf.ContIPv4 != nil {
|
||||
if netConf.ContIPv4.IP != nil {
|
||||
if err := forwardPorts(netConf, netConf.ContIPv4); err != nil {
|
||||
return err
|
||||
}
|
||||
// Delete conntrack entries for UDP to avoid conntrack blackholing traffic
|
||||
// due to stale connections. We do that after the iptables rules are set, so
|
||||
// the new traffic uses them. Failures are informative only.
|
||||
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.ContIPv6 != nil {
|
||||
if netConf.ContIPv6.IP != nil {
|
||||
if err := forwardPorts(netConf, netConf.ContIPv6); err != nil {
|
||||
return err
|
||||
}
|
||||
// Delete conntrack entries for UDP to avoid conntrack blackholing traffic
|
||||
// due to stale connections. We do that after the iptables rules are set, so
|
||||
// the new traffic uses them. Failures are informative only.
|
||||
if err := deletePortmapStaleConnections(netConf.RuntimeConfig.PortMaps, unix.AF_INET6); err != nil {
|
||||
log.Printf("failed to delete stale UDP conntrack entries for %s: %v", netConf.ContIPv6.IP, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Pass through the previous result
|
||||
@ -107,6 +121,10 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
return fmt.Errorf("failed to parse config: %v", err)
|
||||
}
|
||||
|
||||
if len(netConf.RuntimeConfig.PortMaps) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
netConf.ContainerID = args.ContainerID
|
||||
|
||||
// We don't need to parse out whether or not we're using v6 or snat,
|
||||
@ -138,13 +156,13 @@ func cmdCheck(args *skel.CmdArgs) error {
|
||||
|
||||
conf.ContainerID = args.ContainerID
|
||||
|
||||
if conf.ContIPv4 != nil {
|
||||
if conf.ContIPv4.IP != nil {
|
||||
if err := checkPorts(conf, conf.ContIPv4); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if conf.ContIPv6 != nil {
|
||||
if conf.ContIPv6.IP != nil {
|
||||
if err := checkPorts(conf, conf.ContIPv6); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -205,9 +223,9 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, *current.Result, er
|
||||
|
||||
if conf.PrevResult != nil {
|
||||
for _, ip := range result.IPs {
|
||||
if ip.Version == "6" && conf.ContIPv6 != nil {
|
||||
if ip.Version == "6" && conf.ContIPv6.IP != nil {
|
||||
continue
|
||||
} else if ip.Version == "4" && conf.ContIPv4 != nil {
|
||||
} else if ip.Version == "4" && conf.ContIPv4.IP != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -223,9 +241,9 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, *current.Result, er
|
||||
}
|
||||
switch ip.Version {
|
||||
case "6":
|
||||
conf.ContIPv6 = ip.Address.IP
|
||||
conf.ContIPv6 = ip.Address
|
||||
case "4":
|
||||
conf.ContIPv4 = ip.Address.IP
|
||||
conf.ContIPv4 = ip.Address
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,10 +19,12 @@ import (
|
||||
"net"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/utils"
|
||||
"github.com/containernetworking/plugins/pkg/utils/sysctl"
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
// This creates the chains to be added to iptables. The basic structure is
|
||||
@ -42,15 +44,17 @@ import (
|
||||
// The names of the top-level summary chains.
|
||||
// These should never be changed, or else upgrading will require manual
|
||||
// intervention.
|
||||
const TopLevelDNATChainName = "CNI-HOSTPORT-DNAT"
|
||||
const SetMarkChainName = "CNI-HOSTPORT-SETMARK"
|
||||
const MarkMasqChainName = "CNI-HOSTPORT-MASQ"
|
||||
const OldTopLevelSNATChainName = "CNI-HOSTPORT-SNAT"
|
||||
const (
|
||||
TopLevelDNATChainName = "CNI-HOSTPORT-DNAT"
|
||||
SetMarkChainName = "CNI-HOSTPORT-SETMARK"
|
||||
MarkMasqChainName = "CNI-HOSTPORT-MASQ"
|
||||
OldTopLevelSNATChainName = "CNI-HOSTPORT-SNAT"
|
||||
)
|
||||
|
||||
// forwardPorts establishes port forwarding to a given container IP.
|
||||
// containerIP can be either v4 or v6.
|
||||
func forwardPorts(config *PortMapConf, containerIP net.IP) error {
|
||||
isV6 := (containerIP.To4() == nil)
|
||||
// containerNet.IP can be either v4 or v6.
|
||||
func forwardPorts(config *PortMapConf, containerNet net.IPNet) error {
|
||||
isV6 := (containerNet.IP.To4() == nil)
|
||||
|
||||
var ipt *iptables.IPTables
|
||||
var err error
|
||||
@ -86,7 +90,7 @@ func forwardPorts(config *PortMapConf, containerIP net.IP) error {
|
||||
if !isV6 {
|
||||
// Set the route_localnet bit on the host interface, so that
|
||||
// 127/8 can cross a routing boundary.
|
||||
hostIfName := getRoutableHostIF(containerIP)
|
||||
hostIfName := getRoutableHostIF(containerNet.IP)
|
||||
if hostIfName != "" {
|
||||
if err := enableLocalnetRouting(hostIfName); err != nil {
|
||||
return fmt.Errorf("unable to enable route_localnet: %v", err)
|
||||
@ -104,7 +108,7 @@ func forwardPorts(config *PortMapConf, containerIP net.IP) error {
|
||||
dnatChain := genDnatChain(config.Name, config.ContainerID)
|
||||
// First, idempotently tear down this chain in case there was some
|
||||
// sort of collision or bad state.
|
||||
fillDnatRules(&dnatChain, config, containerIP)
|
||||
fillDnatRules(&dnatChain, config, containerNet)
|
||||
if err := dnatChain.setup(ipt); err != nil {
|
||||
return fmt.Errorf("unable to setup DNAT: %v", err)
|
||||
}
|
||||
@ -112,10 +116,9 @@ func forwardPorts(config *PortMapConf, containerIP net.IP) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkPorts(config *PortMapConf, containerIP net.IP) error {
|
||||
|
||||
func checkPorts(config *PortMapConf, containerNet net.IPNet) error {
|
||||
dnatChain := genDnatChain(config.Name, config.ContainerID)
|
||||
fillDnatRules(&dnatChain, config, containerIP)
|
||||
fillDnatRules(&dnatChain, config, containerNet)
|
||||
|
||||
ip4t := maybeGetIptables(false)
|
||||
ip6t := maybeGetIptables(true)
|
||||
@ -180,8 +183,8 @@ func genDnatChain(netName, containerID string) chain {
|
||||
|
||||
// dnatRules generates the destination NAT rules, one per port, to direct
|
||||
// traffic from hostip:hostport to podip:podport
|
||||
func fillDnatRules(c *chain, config *PortMapConf, containerIP net.IP) {
|
||||
isV6 := (containerIP.To4() == nil)
|
||||
func fillDnatRules(c *chain, config *PortMapConf, containerNet net.IPNet) {
|
||||
isV6 := (containerNet.IP.To4() == nil)
|
||||
comment := trimComment(fmt.Sprintf(`dnat name: "%s" id: "%s"`, config.Name, config.ContainerID))
|
||||
entries := config.RuntimeConfig.PortMaps
|
||||
setMarkChainName := SetMarkChainName
|
||||
@ -189,7 +192,7 @@ func fillDnatRules(c *chain, config *PortMapConf, containerIP net.IP) {
|
||||
setMarkChainName = *config.ExternalSetMarkChain
|
||||
}
|
||||
|
||||
//Generate the dnat entry rules. We'll use multiport, but it ony accepts
|
||||
// Generate the dnat entry rules. We'll use multiport, but it ony accepts
|
||||
// up to 15 rules, so partition the list if needed.
|
||||
// Do it in a stable order for testing
|
||||
protoPorts := groupByProto(entries)
|
||||
@ -225,6 +228,8 @@ func fillDnatRules(c *chain, config *PortMapConf, containerIP net.IP) {
|
||||
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
|
||||
// and append it to the iptables rules
|
||||
addRuleBaseDst := false
|
||||
if entry.HostIP != "" {
|
||||
hostIP := net.ParseIP(entry.HostIP)
|
||||
isHostV6 := (hostIP.To4() == nil)
|
||||
@ -232,12 +237,18 @@ func fillDnatRules(c *chain, config *PortMapConf, containerIP net.IP) {
|
||||
if isV6 != isHostV6 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Unspecified addresses can not be used as destination
|
||||
if !hostIP.IsUnspecified() {
|
||||
addRuleBaseDst = true
|
||||
}
|
||||
}
|
||||
|
||||
ruleBase := []string{
|
||||
"-p", entry.Protocol,
|
||||
"--dport", strconv.Itoa(entry.HostPort)}
|
||||
if entry.HostIP != "" {
|
||||
"--dport", strconv.Itoa(entry.HostPort),
|
||||
}
|
||||
if addRuleBaseDst {
|
||||
ruleBase = append(ruleBase,
|
||||
"-d", entry.HostIP)
|
||||
}
|
||||
@ -249,7 +260,7 @@ func fillDnatRules(c *chain, config *PortMapConf, containerIP net.IP) {
|
||||
copy(hpRule, ruleBase)
|
||||
|
||||
hpRule = append(hpRule,
|
||||
"-s", containerIP.String(),
|
||||
"-s", containerNet.String(),
|
||||
"-j", setMarkChainName,
|
||||
)
|
||||
c.rules = append(c.rules, hpRule)
|
||||
@ -272,7 +283,7 @@ func fillDnatRules(c *chain, config *PortMapConf, containerIP net.IP) {
|
||||
copy(dnatRule, ruleBase)
|
||||
dnatRule = append(dnatRule,
|
||||
"-j", "DNAT",
|
||||
"--to-destination", fmtIpPort(containerIP, entry.ContainerPort),
|
||||
"--to-destination", fmtIpPort(containerNet.IP, entry.ContainerPort),
|
||||
)
|
||||
c.rules = append(c.rules, dnatRule)
|
||||
}
|
||||
@ -399,3 +410,19 @@ func maybeGetIptables(isV6 bool) *iptables.IPTables {
|
||||
|
||||
return ipt
|
||||
}
|
||||
|
||||
// deletePortmapStaleConnections delete the UDP conntrack entries on the specified IP family
|
||||
// from the ports mapped to the container
|
||||
func deletePortmapStaleConnections(portMappings []PortMapEntry, family netlink.InetFamily) error {
|
||||
for _, pm := range portMappings {
|
||||
// skip if is not UDP
|
||||
if strings.ToLower(pm.Protocol) != "udp" {
|
||||
continue
|
||||
}
|
||||
err := utils.DeleteConntrackEntriesForDstPort(uint16(pm.HostPort), utils.PROTOCOL_UDP, family)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/containernetworking/cni/libcni"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
@ -185,16 +184,16 @@ var _ = Describe("portmap integration tests", func() {
|
||||
Expect(cmd.Run()).To(Succeed())
|
||||
|
||||
// Sanity check: verify that the container is reachable directly
|
||||
contOK := testEchoServer(contIP.String(), containerPort, "")
|
||||
contOK := testEchoServer(contIP.String(), "tcp", containerPort, "")
|
||||
|
||||
// Verify that a connection to the forwarded port works
|
||||
dnatOK := testEchoServer(hostIP, hostPort, "")
|
||||
dnatOK := testEchoServer(hostIP, "tcp", hostPort, "")
|
||||
|
||||
// Verify that a connection to localhost works
|
||||
snatOK := testEchoServer("127.0.0.1", hostPort, "")
|
||||
snatOK := testEchoServer("127.0.0.1", "tcp", hostPort, "")
|
||||
|
||||
// verify that hairpin works
|
||||
hairpinOK := testEchoServer(hostIP, hostPort, targetNS.Path())
|
||||
hairpinOK := testEchoServer(hostIP, "tcp", hostPort, targetNS.Path())
|
||||
|
||||
// Cleanup
|
||||
session.Terminate()
|
||||
@ -220,23 +219,216 @@ var _ = Describe("portmap integration tests", func() {
|
||||
}
|
||||
|
||||
close(done)
|
||||
}, TIMEOUT*9)
|
||||
|
||||
It("forwards a UDP port on ipv4 and keep working after creating a second container with the same HostPort", 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": "udp",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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())
|
||||
|
||||
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
|
||||
}
|
||||
contIP = ip.Address.IP
|
||||
}
|
||||
if contIP == nil {
|
||||
Fail("could not determine container IP")
|
||||
}
|
||||
|
||||
hostIP := getLocalIP()
|
||||
fmt.Fprintf(GinkgoWriter, "First container hostIP: %s:%d, contIP: %s:%d\n",
|
||||
hostIP, hostPort, contIP, containerPort)
|
||||
|
||||
// dump iptables-save output for debugging
|
||||
cmd = exec.Command("iptables-save")
|
||||
cmd.Stderr = GinkgoWriter
|
||||
cmd.Stdout = GinkgoWriter
|
||||
Expect(cmd.Run()).To(Succeed())
|
||||
|
||||
// dump ip routes output for debugging
|
||||
cmd = exec.Command("ip", "route")
|
||||
cmd.Stderr = GinkgoWriter
|
||||
cmd.Stdout = GinkgoWriter
|
||||
Expect(cmd.Run()).To(Succeed())
|
||||
|
||||
// dump ip addresses output for debugging
|
||||
cmd = exec.Command("ip", "addr")
|
||||
cmd.Stderr = GinkgoWriter
|
||||
cmd.Stdout = GinkgoWriter
|
||||
Expect(cmd.Run()).To(Succeed())
|
||||
|
||||
// Sanity check: verify that the container is reachable directly
|
||||
fmt.Fprintln(GinkgoWriter, "Connect to container:", contIP.String(), containerPort)
|
||||
contOK := testEchoServer(contIP.String(), "udp", containerPort, "")
|
||||
|
||||
// Verify that a connection to the forwarded port works
|
||||
fmt.Fprintln(GinkgoWriter, "Connect to host:", hostIP, hostPort)
|
||||
dnatOK := testEchoServer(hostIP, "udp", hostPort, "")
|
||||
|
||||
// Cleanup
|
||||
session.Terminate()
|
||||
err = deleteNetwork()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// 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")
|
||||
}
|
||||
// Create a second container
|
||||
targetNS2, err := testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
fmt.Fprintln(GinkgoWriter, "namespace:", targetNS2.Path())
|
||||
|
||||
// Start an echo server and get the port
|
||||
containerPort, session2, err := StartEchoServerInNamespace(targetNS2)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
runtimeConfig2 := libcni.RuntimeConf{
|
||||
ContainerID: fmt.Sprintf("unit-test2-%d", hostPort),
|
||||
NetNS: targetNS2.Path(),
|
||||
IfName: "eth0",
|
||||
CapabilityArgs: map[string]interface{}{
|
||||
"portMappings": []map[string]interface{}{
|
||||
{
|
||||
"hostPort": hostPort,
|
||||
"containerPort": containerPort,
|
||||
"protocol": "udp",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Make delete idempotent, so we can clean up on failure
|
||||
net2Deleted := false
|
||||
deleteNetwork2 := func() error {
|
||||
if net2Deleted {
|
||||
return nil
|
||||
}
|
||||
net2Deleted = true
|
||||
return cniConf.DelNetworkList(context.TODO(), configList, &runtimeConfig2)
|
||||
}
|
||||
|
||||
// Create the network
|
||||
resI2, err := cniConf.AddNetworkList(context.TODO(), configList, &runtimeConfig2)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer deleteNetwork2()
|
||||
|
||||
result2, err := current.GetResult(resI2)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
var contIP2 net.IP
|
||||
|
||||
for _, ip := range result2.IPs {
|
||||
intfIndex := *ip.Interface
|
||||
if result2.Interfaces[intfIndex].Sandbox == "" {
|
||||
continue
|
||||
}
|
||||
contIP2 = ip.Address.IP
|
||||
}
|
||||
if contIP2 == nil {
|
||||
Fail("could not determine container IP")
|
||||
}
|
||||
|
||||
fmt.Fprintf(GinkgoWriter, "Second container: hostIP: %s:%d, contIP: %s:%d\n",
|
||||
hostIP, hostPort, contIP2, containerPort)
|
||||
|
||||
// dump iptables-save output for debugging
|
||||
cmd = exec.Command("iptables-save")
|
||||
cmd.Stderr = GinkgoWriter
|
||||
cmd.Stdout = GinkgoWriter
|
||||
Expect(cmd.Run()).To(Succeed())
|
||||
|
||||
// dump ip routes output for debugging
|
||||
cmd = exec.Command("ip", "route")
|
||||
cmd.Stderr = GinkgoWriter
|
||||
cmd.Stdout = GinkgoWriter
|
||||
Expect(cmd.Run()).To(Succeed())
|
||||
|
||||
// dump ip addresses output for debugging
|
||||
cmd = exec.Command("ip", "addr")
|
||||
cmd.Stderr = GinkgoWriter
|
||||
cmd.Stdout = GinkgoWriter
|
||||
Expect(cmd.Run()).To(Succeed())
|
||||
|
||||
// Sanity check: verify that the container is reachable directly
|
||||
fmt.Fprintln(GinkgoWriter, "Connect to container:", contIP2.String(), containerPort)
|
||||
cont2OK := testEchoServer(contIP2.String(), "udp", containerPort, "")
|
||||
|
||||
// Verify that a connection to the forwarded port works
|
||||
fmt.Fprintln(GinkgoWriter, "Connect to host:", hostIP, hostPort)
|
||||
dnat2OK := testEchoServer(hostIP, "udp", hostPort, "")
|
||||
|
||||
// Cleanup
|
||||
session2.Terminate()
|
||||
err = deleteNetwork2()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Check that everything succeeded *after* we clean up the network
|
||||
if !cont2OK {
|
||||
Fail("connection direct to " + contIP2.String() + " failed")
|
||||
}
|
||||
if !dnat2OK {
|
||||
Fail("Connection to " + hostIP + " was not forwarded")
|
||||
}
|
||||
|
||||
close(done)
|
||||
}, TIMEOUT*9)
|
||||
})
|
||||
})
|
||||
|
||||
// testEchoServer returns true if we found an echo server on the port
|
||||
func testEchoServer(address string, port int, netns string) bool {
|
||||
message := "Aliquid melius quam pessimum optimum non est."
|
||||
func testEchoServer(address, protocol string, port int, netns string) bool {
|
||||
message := "'Aliquid melius quam pessimum optimum non est.'"
|
||||
|
||||
bin, err := exec.LookPath("nc")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
var cmd *exec.Cmd
|
||||
if netns != "" {
|
||||
netns = filepath.Base(netns)
|
||||
cmd = exec.Command("ip", "netns", "exec", netns, bin, "-v", address, strconv.Itoa(port))
|
||||
cmd = exec.Command("ip", "netns", "exec", netns, echoClientBinaryPath, "--target", fmt.Sprintf("%s:%d", address, port), "--message", message, "--protocol", protocol)
|
||||
} else {
|
||||
cmd = exec.Command("nc", address, strconv.Itoa(port))
|
||||
cmd = exec.Command(echoClientBinaryPath, "--target", fmt.Sprintf("%s:%d", address, port), "--message", message, "--protocol", protocol)
|
||||
}
|
||||
cmd.Stdin = bytes.NewBufferString(message)
|
||||
cmd.Stderr = GinkgoWriter
|
||||
|
@ -40,14 +40,18 @@ func TestPortmap(t *testing.T) {
|
||||
RunSpecs(t, "plugins/meta/portmap")
|
||||
}
|
||||
|
||||
var echoServerBinaryPath string
|
||||
var echoServerBinaryPath, echoClientBinaryPath string
|
||||
|
||||
var _ = SynchronizedBeforeSuite(func() []byte {
|
||||
binaryPath, err := gexec.Build("github.com/containernetworking/plugins/pkg/testutils/echosvr")
|
||||
serverBinaryPath, err := gexec.Build("github.com/containernetworking/plugins/pkg/testutils/echo/server")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return []byte(binaryPath)
|
||||
clientBinaryPath, err := gexec.Build("github.com/containernetworking/plugins/pkg/testutils/echo/client")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return []byte(strings.Join([]string{serverBinaryPath, clientBinaryPath}, ","))
|
||||
}, func(data []byte) {
|
||||
echoServerBinaryPath = string(data)
|
||||
binaries := strings.Split(string(data), ",")
|
||||
echoServerBinaryPath = binaries[0]
|
||||
echoClientBinaryPath = binaries[1]
|
||||
})
|
||||
|
||||
var _ = SynchronizedAfterSuite(func() {}, func() {
|
||||
|
@ -16,7 +16,8 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
@ -77,8 +78,10 @@ var _ = Describe("portmapping configuration", func() {
|
||||
Expect(c.SNAT).To(Equal(&fvar))
|
||||
Expect(c.Name).To(Equal("test"))
|
||||
|
||||
Expect(c.ContIPv4).To(Equal(net.ParseIP("10.0.0.2")))
|
||||
Expect(c.ContIPv6).To(Equal(net.ParseIP("2001:db8:1::2")))
|
||||
n, err := types.ParseCIDR("10.0.0.2/24")
|
||||
Expect(c.ContIPv4).To(Equal(*n))
|
||||
n, err = types.ParseCIDR("2001:db8:1::2/64")
|
||||
Expect(c.ContIPv6).To(Equal(*n))
|
||||
})
|
||||
|
||||
It("Correctly parses a DEL config", func() {
|
||||
@ -167,7 +170,11 @@ var _ = Describe("portmapping configuration", func() {
|
||||
{ "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": 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,
|
||||
@ -186,14 +193,15 @@ var _ = Describe("portmapping configuration", func() {
|
||||
entryChains: []string{"CNI-HOSTPORT-DNAT"},
|
||||
}))
|
||||
|
||||
fillDnatRules(&ch, conf, net.ParseIP("10.0.0.2"))
|
||||
n, err := types.ParseCIDR("10.0.0.2/24")
|
||||
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",
|
||||
"--destination-ports", "8080,8081,8083,8084,8085,8086",
|
||||
"a", "b"},
|
||||
{"-m", "comment", "--comment",
|
||||
fmt.Sprintf("dnat name: \"test\" id: \"%s\"", containerID),
|
||||
@ -204,34 +212,53 @@ var _ = Describe("portmapping configuration", func() {
|
||||
}))
|
||||
|
||||
Expect(ch.rules).To(Equal([][]string{
|
||||
{"-p", "tcp", "--dport", "8080", "-s", "10.0.0.2", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
// 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", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-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"},
|
||||
{"-p", "udp", "--dport", "8080", "-s", "10.0.0.2", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
// 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", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-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
|
||||
|
||||
fillDnatRules(&ch, conf, net.ParseIP("2001:db8::2"))
|
||||
n, err = types.ParseCIDR("2001:db8::2/64")
|
||||
fillDnatRules(&ch, conf, *n)
|
||||
|
||||
Expect(ch.rules).To(Equal([][]string{
|
||||
{"-p", "tcp", "--dport", "8080", "-s", "2001:db8::2", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
// 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", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-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"},
|
||||
{"-p", "udp", "--dport", "8080", "-s", "2001:db8::2", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
// 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", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-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
|
||||
@ -240,12 +267,15 @@ var _ = Describe("portmapping configuration", func() {
|
||||
fvar := false
|
||||
conf.SNAT = &fvar
|
||||
|
||||
fillDnatRules(&ch, conf, net.ParseIP("10.0.0.2"))
|
||||
n, err = types.ParseCIDR("10.0.0.2/24")
|
||||
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"},
|
||||
}))
|
||||
})
|
||||
|
||||
@ -276,9 +306,10 @@ var _ = Describe("portmapping configuration", func() {
|
||||
conf.ContainerID = containerID
|
||||
|
||||
ch = genDnatChain(conf.Name, containerID)
|
||||
fillDnatRules(&ch, conf, net.ParseIP("10.0.0.2"))
|
||||
n, err := types.ParseCIDR("10.0.0.2/24")
|
||||
fillDnatRules(&ch, conf, *n)
|
||||
Expect(ch.rules).To(Equal([][]string{
|
||||
{"-p", "tcp", "--dport", "8080", "-s", "10.0.0.2", "-j", "PLZ-SET-MARK"},
|
||||
{"-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"},
|
||||
}))
|
||||
|
@ -1,127 +1,5 @@
|
||||
# Source based routing plugin
|
||||
|
||||
## Introduction
|
||||
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
|
||||
|
||||
This plugin performs Source Based Routing (SBR). The most common and standard way to
|
||||
perform routing is to base it purely on the destination. However, in some
|
||||
applications which are using network separation for traffic management and
|
||||
security, there is no way to tell *a priori* which interface should be used,
|
||||
but the application is capable of making the decision.
|
||||
You can find it online here: https://cni.dev/plugins/meta/sbr/
|
||||
|
||||
As an example, a Telco application might have two networks, a management
|
||||
network and a SIP (telephony) network for traffic, with rules which state that:
|
||||
|
||||
- SIP traffic (only) must be routed over the SIP network;
|
||||
|
||||
- all other traffic (but no SIP traffic) must be routed over the management
|
||||
network.
|
||||
|
||||
There is no way of configuring this based on destination IP, since there is no
|
||||
way of telling whether a destination IP on the internet is (say) an address
|
||||
used for downloading updated software packages or a remote SIP endpoint.
|
||||
|
||||
Hence Source Based Routing is used.
|
||||
|
||||
- The application explicitly listens on the correct interface for incoming
|
||||
traffic.
|
||||
|
||||
- When the application wishes to send to an address via the SIP network, it
|
||||
explicitly binds to the IP of the device on that network.
|
||||
|
||||
- Routes for the SIP interface are configured in a separate routing table, and
|
||||
a rule is configured to use that table based on the source IP address.
|
||||
|
||||
Note that in most cases there is a management device (the first one) and that
|
||||
the SBR plugin is called once for each device after the first, leaving the
|
||||
default routing table applied to the management device. However, this not
|
||||
mandatory, and source based routing may be configured on any or all of the
|
||||
devices as appropriate.
|
||||
|
||||
## Usage
|
||||
|
||||
This plugin runs as a chained plugin, and requires the following information
|
||||
passed in from the previous plugin (which has just set up the network device):
|
||||
|
||||
- What is the network interface in question?
|
||||
|
||||
- What is the IP address (or addresses) of that network interface?
|
||||
|
||||
- What is the default gateway for that interface (if any)?
|
||||
|
||||
It then reads all routes to the network interface in use.
|
||||
|
||||
Here is an example of what the plugin would do. (The `ip` based commands are
|
||||
implemented in go, but easier to describe via the command line.) Suppose that
|
||||
it reads that:
|
||||
|
||||
- The interface is `net1`.
|
||||
|
||||
- The IP address on that interface is `192.168.1.209`.
|
||||
|
||||
- The default gateway on that interface is `192.168.1.1`.
|
||||
|
||||
- There is one route configured on that network interface, which is
|
||||
`192.168.1.0/24`.
|
||||
|
||||
Then the actions it takes are the following.
|
||||
|
||||
- It creates a new routing table, and sets a rule to use it for the IP address in question.
|
||||
|
||||
ip rule add from 192.168.1.209/32 table 100
|
||||
|
||||
- It adds a route to the default gateway for the relevant table.
|
||||
|
||||
ip route add default via 192.168.1.1 dev net1 table 100
|
||||
|
||||
- It moves every existing route on the device to the new table.
|
||||
|
||||
ip route del 192.168.1.0/24 dev net1 src 192.168.1.209
|
||||
ip route add 192.168.1.0/24 dev net1 src 192.168.1.209 table 100
|
||||
|
||||
On deletion it:
|
||||
|
||||
- deletes the rule (`ip rule del from 192.168.1.209/32 table 100`), which it
|
||||
finds by deleting rules relating to IPs on the device which is about to be
|
||||
deleted.
|
||||
|
||||
- does nothing with routes (since the kernel automatically removes routes when
|
||||
the device with which they are associated is deleted).
|
||||
|
||||
## Future enhancements and known limitations
|
||||
|
||||
The following are possible future enhancements.
|
||||
|
||||
- The table number is currently selected by starting at 100, then incrementing
|
||||
the value until an unused table number is found. It might be nice to have an
|
||||
option to pass the table number as an input.
|
||||
|
||||
- There is no log severity, and there is no logging to file (pending changes to
|
||||
CNI logging generally).
|
||||
|
||||
- This plugin sets up Source Based Routing, as described above. In future,
|
||||
there may be a need for a VRF plugin (that uses
|
||||
[VRF routing](https://www.kernel.org/doc/Documentation/networking/vrf.txt)
|
||||
instead of source based routing). If and when this happens, it is likely that
|
||||
the logic would be virtually identical for the plugin, and so the same plugin
|
||||
might offer either SBR or VRF routing depending on configuration.
|
||||
|
||||
## Configuration
|
||||
|
||||
This plugin must be used as a chained plugin. There are no specific configuration parameters.
|
||||
|
||||
A sample configuration for this plugin acting as a chained plugin after flannel
|
||||
is the following.
|
||||
|
||||
~~~json
|
||||
{
|
||||
"name": "flannel-sbr",
|
||||
"cniVersion": "0.3.0",
|
||||
"plugins":
|
||||
[
|
||||
{ "type": "flannel" },
|
||||
{
|
||||
"type": "sbr",
|
||||
}
|
||||
]
|
||||
}
|
||||
~~~
|
||||
|
@ -237,7 +237,7 @@ func doRoutes(ipCfgs []*current.IPConfig, origRoutes []*types.Route, iface strin
|
||||
if ipCfg.Version == "4" {
|
||||
src.Mask = net.CIDRMask(32, 32)
|
||||
} else {
|
||||
src.Mask = net.CIDRMask(64, 64)
|
||||
src.Mask = net.CIDRMask(128, 128)
|
||||
}
|
||||
|
||||
log.Printf("Source to use %s", src.String())
|
||||
@ -258,7 +258,7 @@ func doRoutes(ipCfgs []*current.IPConfig, origRoutes []*types.Route, iface strin
|
||||
dest.Mask = net.CIDRMask(0, 32)
|
||||
} else {
|
||||
dest.IP = net.IPv6zero
|
||||
dest.Mask = net.CIDRMask(0, 64)
|
||||
dest.Mask = net.CIDRMask(0, 128)
|
||||
}
|
||||
|
||||
route := netlink.Route{
|
||||
@ -296,6 +296,10 @@ func doRoutes(ipCfgs []*current.IPConfig, origRoutes []*types.Route, iface strin
|
||||
|
||||
route.Table = table
|
||||
|
||||
// Reset the route flags since if it is dynamically created,
|
||||
// adding it to the new table will fail with "invalid argument"
|
||||
route.Flags = 0
|
||||
|
||||
// We use route replace in case the route already exists, which
|
||||
// is possible for the default gateway we added above.
|
||||
err = netlink.RouteReplace(&route)
|
||||
|
@ -1,73 +1,5 @@
|
||||
# tuning plugin
|
||||
|
||||
## Overview
|
||||
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
|
||||
|
||||
This plugin can change some system controls (sysctls) and several interface attributes (promiscuous mode, MTU and MAC address) in the network namespace.
|
||||
It does not create any network interfaces and therefore does not bring connectivity by itself.
|
||||
It is only useful when used in addition to other plugins.
|
||||
You can find it online here: https://cni.dev/plugins/meta/tuning/
|
||||
|
||||
## System Controls Operation
|
||||
The following network configuration file
|
||||
```
|
||||
{
|
||||
"name": "mytuning",
|
||||
"type": "tuning",
|
||||
"sysctl": {
|
||||
"net.core.somaxconn": "500"
|
||||
}
|
||||
}
|
||||
```
|
||||
will set /proc/sys/net/core/somaxconn to 500.
|
||||
Other sysctls can be modified as long as they belong to the network namespace (`/proc/sys/net/*`).
|
||||
|
||||
A successful result would simply be:
|
||||
```
|
||||
{ }
|
||||
```
|
||||
|
||||
## Network sysctls documentation
|
||||
|
||||
Some network sysctls are documented in the Linux sources:
|
||||
|
||||
- [Documentation/sysctl/net.txt](https://www.kernel.org/doc/Documentation/sysctl/net.txt)
|
||||
- [Documentation/networking/ip-sysctl.txt](https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt)
|
||||
- [Documentation/networking/](https://www.kernel.org/doc/Documentation/networking/)
|
||||
|
||||
## Interface Attribute Operation
|
||||
The parameters, "mac", "mtu" and "promisc", changes the interface attributes as followings:
|
||||
|
||||
```
|
||||
{
|
||||
"name": "mytuning",
|
||||
"type": "tuning",
|
||||
"promisc": true,
|
||||
"mac": "c2:b0:57:49:47:f1",
|
||||
"mtu": 1454
|
||||
}
|
||||
```
|
||||
|
||||
## Interface attribute configuration reference
|
||||
|
||||
* `mac` (string, optional): MAC address (i.e. hardware address) of interface
|
||||
* `mtu` (integer, optional): MTU of interface
|
||||
* `promisc` (bool, optional): Change the promiscuous mode of interface
|
||||
|
||||
## Supported arguments
|
||||
The following [CNI_ARGS](https://github.com/containernetworking/cni/blob/master/SPEC.md#parameters) are supported:
|
||||
|
||||
* `MAC`: request a specific MAC address for the interface
|
||||
|
||||
(example: CNI_ARGS="IgnoreUnknown=true;MAC=c2:11:22:33:44:55")
|
||||
|
||||
Note: You may add `IgnoreUnknown=true` to allow loose CNI argument verification (see CNI's issue[#560](https://github.com/containernetworking/cni/issues/560)).
|
||||
|
||||
The plugin also support following [capability argument](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md).
|
||||
|
||||
* `mac`: Pass MAC addresses for CNI interface
|
||||
|
||||
The following [args conventions](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md#args-in-network-config) are supported:
|
||||
|
||||
* `mac` (string, optional): MAC address (i.e. hardware address) of interface
|
||||
* `mtu` (integer, optional): MTU of interface
|
||||
* `promisc` (bool, optional): Change the promiscuous mode of interface
|
||||
* `sysctl` (object, optional): Change system controls
|
||||
|
@ -22,6 +22,8 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@ -37,9 +39,12 @@ import (
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
const defaultDataDir = "/run/cni/tuning"
|
||||
|
||||
// TuningConf represents the network tuning configuration.
|
||||
type TuningConf struct {
|
||||
types.NetConf
|
||||
DataDir string `json:"dataDir,omitempty"`
|
||||
SysCtl map[string]string `json:"sysctl"`
|
||||
Mac string `json:"mac,omitempty"`
|
||||
Promisc bool `json:"promisc,omitempty"`
|
||||
@ -60,6 +65,13 @@ type IPAMArgs struct {
|
||||
Mtu *int `json:"mtu,omitempty"`
|
||||
}
|
||||
|
||||
// configToRestore will contain interface attributes that should be restored on cmdDel
|
||||
type configToRestore struct {
|
||||
Mac string `json:"mac,omitempty"`
|
||||
Promisc *bool `json:"promisc,omitempty"`
|
||||
Mtu int `json:"mtu,omitempty"`
|
||||
}
|
||||
|
||||
// MacEnvArgs represents CNI_ARG
|
||||
type MacEnvArgs struct {
|
||||
types.CommonArgs
|
||||
@ -72,6 +84,10 @@ func parseConf(data []byte, envArgs string) (*TuningConf, error) {
|
||||
return nil, fmt.Errorf("failed to load netconf: %v", err)
|
||||
}
|
||||
|
||||
if conf.DataDir == "" {
|
||||
conf.DataDir = defaultDataDir
|
||||
}
|
||||
|
||||
// Parse custom Mac from both env args
|
||||
if envArgs != "" {
|
||||
e := MacEnvArgs{}
|
||||
@ -165,6 +181,95 @@ func changeMtu(ifName string, mtu int) error {
|
||||
return netlink.LinkSetMTU(link, mtu)
|
||||
}
|
||||
|
||||
func createBackup(ifName, containerID, backupPath string, tuningConf *TuningConf) error {
|
||||
config := configToRestore{}
|
||||
link, err := netlink.LinkByName(ifName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get %q: %v", ifName, err)
|
||||
}
|
||||
if tuningConf.Mac != "" {
|
||||
config.Mac = link.Attrs().HardwareAddr.String()
|
||||
}
|
||||
if tuningConf.Promisc {
|
||||
config.Promisc = new(bool)
|
||||
*config.Promisc = (link.Attrs().Promisc != 0)
|
||||
}
|
||||
if tuningConf.Mtu != 0 {
|
||||
config.Mtu = link.Attrs().MTU
|
||||
}
|
||||
|
||||
if _, err := os.Stat(backupPath); os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(backupPath, 0600); err != nil {
|
||||
return fmt.Errorf("failed to create backup directory: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(config, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshall data for %q: %v", ifName, err)
|
||||
}
|
||||
if err = ioutil.WriteFile(path.Join(backupPath, containerID+"_"+ifName+".json"), data, 0600); err != nil {
|
||||
return fmt.Errorf("failed to save file %s.json: %v", ifName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func restoreBackup(ifName, containerID, backupPath string) error {
|
||||
filePath := path.Join(backupPath, containerID+"_"+ifName+".json")
|
||||
|
||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||
// No backup file - nothing to revert
|
||||
return nil
|
||||
}
|
||||
|
||||
file, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open file %q: %v", filePath, err)
|
||||
}
|
||||
|
||||
config := configToRestore{}
|
||||
if err = json.Unmarshal([]byte(file), &config); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var errStr []string
|
||||
|
||||
_, err = netlink.LinkByName(ifName)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if config.Mtu != 0 {
|
||||
if err = changeMtu(ifName, config.Mtu); err != nil {
|
||||
err = fmt.Errorf("failed to restore MTU: %v", err)
|
||||
errStr = append(errStr, err.Error())
|
||||
}
|
||||
}
|
||||
if config.Mac != "" {
|
||||
if err = changeMacAddr(ifName, config.Mac); err != nil {
|
||||
err = fmt.Errorf("failed to restore MAC address: %v", err)
|
||||
errStr = append(errStr, err.Error())
|
||||
}
|
||||
}
|
||||
if config.Promisc != nil {
|
||||
if err = changePromisc(ifName, *config.Promisc); err != nil {
|
||||
err = fmt.Errorf("failed to restore promiscuous mode: %v", err)
|
||||
errStr = append(errStr, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if len(errStr) > 0 {
|
||||
return fmt.Errorf(strings.Join(errStr, "; "))
|
||||
}
|
||||
|
||||
if err = os.Remove(filePath); err != nil {
|
||||
return fmt.Errorf("failed to remove file %v: %v", filePath, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cmdAdd(args *skel.CmdArgs) error {
|
||||
tuningConf, err := parseConf(args.StdinData, args.Args)
|
||||
if err != nil {
|
||||
@ -205,6 +310,12 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
}
|
||||
|
||||
if tuningConf.Mac != "" || tuningConf.Mtu != 0 || tuningConf.Promisc {
|
||||
if err = createBackup(args.IfName, args.ContainerID, tuningConf.DataDir, tuningConf); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if tuningConf.Mac != "" {
|
||||
if err = changeMacAddr(args.IfName, tuningConf.Mac); err != nil {
|
||||
return err
|
||||
@ -239,10 +350,17 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
return types.PrintResult(tuningConf.PrevResult, tuningConf.CNIVersion)
|
||||
}
|
||||
|
||||
// cmdDel will restore NIC attributes to the original ones when called
|
||||
func cmdDel(args *skel.CmdArgs) error {
|
||||
// TODO: the settings are not reverted to the previous values. Reverting the
|
||||
// settings is not useful when the whole container goes away but it could be
|
||||
// useful in scenarios where plugins are added and removed at runtime.
|
||||
tuningConf, err := parseConf(args.StdinData, args.Args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||
// MAC address, MTU and promiscuous mode settings will be restored
|
||||
return restoreBackup(args.IfName, args.ContainerID, tuningConf.DataDir)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -17,13 +17,13 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/testutils"
|
||||
"net"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
@ -77,6 +77,7 @@ func buildOneConfig(name, cniVersion string, orig *TuningConf, prevResult types.
|
||||
var _ = Describe("tuning plugin", func() {
|
||||
var originalNS ns.NetNS
|
||||
const IFNAME string = "dummy0"
|
||||
var beforeConf configToRestore
|
||||
|
||||
BeforeEach(func() {
|
||||
// Create a new NetNS so we don't modify the host
|
||||
@ -93,8 +94,13 @@ var _ = Describe("tuning plugin", func() {
|
||||
},
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
_, err = netlink.LinkByName(IFNAME)
|
||||
link, err := netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
beforeConf.Mac = link.Attrs().HardwareAddr.String()
|
||||
beforeConf.Mtu = link.Attrs().MTU
|
||||
beforeConf.Promisc = new(bool)
|
||||
*beforeConf.Promisc = (link.Attrs().Promisc != 0)
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@ -138,6 +144,8 @@ var _ = Describe("tuning plugin", func() {
|
||||
StdinData: conf,
|
||||
}
|
||||
|
||||
beforeConf = configToRestore{}
|
||||
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
@ -153,6 +161,13 @@ var _ = Describe("tuning plugin", func() {
|
||||
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
|
||||
Expect(len(result.IPs)).To(Equal(1))
|
||||
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
|
||||
|
||||
Expect("/tmp/tuning-test/dummy_dummy0.json").ShouldNot(BeAnExistingFile())
|
||||
|
||||
err = testutils.CmdDel(originalNS.Path(),
|
||||
args.ContainerID, "", func() error { return cmdDel(args) })
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@ -210,6 +225,10 @@ var _ = Describe("tuning plugin", func() {
|
||||
args.ContainerID, "", func() error { return cmdDel(args) })
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
link, err = netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().Promisc != 0).To(Equal(*beforeConf.Promisc))
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@ -271,6 +290,10 @@ var _ = Describe("tuning plugin", func() {
|
||||
args.ContainerID, "", func() error { return cmdDel(args) })
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
link, err = netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().Promisc != 0).To(Equal(*beforeConf.Promisc))
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@ -328,6 +351,10 @@ var _ = Describe("tuning plugin", func() {
|
||||
args.ContainerID, "", func() error { return cmdDel(args) })
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
link, err = netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().MTU).To(Equal(beforeConf.Mtu))
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@ -389,6 +416,10 @@ var _ = Describe("tuning plugin", func() {
|
||||
args.ContainerID, "", func() error { return cmdDel(args) })
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
link, err = netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().MTU).To(Equal(beforeConf.Mtu))
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@ -448,6 +479,10 @@ var _ = Describe("tuning plugin", func() {
|
||||
args.ContainerID, "", func() error { return cmdDel(args) })
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
link, err = netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr.String()).To(Equal(beforeConf.Mac))
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@ -511,6 +546,10 @@ var _ = Describe("tuning plugin", func() {
|
||||
args.ContainerID, "", func() error { return cmdDel(args) })
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
link, err = netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr.String()).To(Equal(beforeConf.Mac))
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@ -571,6 +610,10 @@ var _ = Describe("tuning plugin", func() {
|
||||
args.ContainerID, "", func() error { return cmdDel(args) })
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
link, err = netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr.String()).To(Equal(beforeConf.Mac))
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@ -643,6 +686,10 @@ var _ = Describe("tuning plugin", func() {
|
||||
args.ContainerID, "", func() error { return cmdDel(args) })
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
link, err = netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().Promisc != 0).To(Equal(*beforeConf.Promisc))
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@ -715,6 +762,10 @@ var _ = Describe("tuning plugin", func() {
|
||||
args.ContainerID, "", func() error { return cmdDel(args) })
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
link, err = netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().MTU).To(Equal(beforeConf.Mtu))
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@ -789,6 +840,10 @@ var _ = Describe("tuning plugin", func() {
|
||||
args.ContainerID, "", func() error { return cmdDel(args) })
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
link, err = netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr.String()).To(Equal(beforeConf.Mac))
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@ -863,6 +918,10 @@ var _ = Describe("tuning plugin", func() {
|
||||
args.ContainerID, "", func() error { return cmdDel(args) })
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
link, err = netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr.String()).To(Equal(beforeConf.Mac))
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@ -925,6 +984,97 @@ var _ = Describe("tuning plugin", func() {
|
||||
args.ContainerID, "", func() error { return cmdDel(args) })
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
link, err = netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr.String()).To(Equal(beforeConf.Mac))
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("configures and deconfigures mac address, promisc mode and MTU (from conf file) with custom dataDir", func() {
|
||||
conf := []byte(`{
|
||||
"name": "test",
|
||||
"type": "iplink",
|
||||
"cniVersion": "0.4.0",
|
||||
"mac": "c2:11:22:33:44:77",
|
||||
"promisc": true,
|
||||
"mtu": 4000,
|
||||
"dataDir": "/tmp/tuning-test",
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{"name": "dummy0", "sandbox":"netns"}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "10.0.0.2/24",
|
||||
"gateway": "10.0.0.1",
|
||||
"interface": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}`)
|
||||
|
||||
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 := current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(len(result.Interfaces)).To(Equal(1))
|
||||
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
|
||||
Expect(len(result.IPs)).To(Equal(1))
|
||||
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
|
||||
|
||||
link, err := netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
hw, err := net.ParseMAC("c2:11:22:33:44:77")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr).To(Equal(hw))
|
||||
Expect(link.Attrs().Promisc).To(Equal(1))
|
||||
Expect(link.Attrs().MTU).To(Equal(4000))
|
||||
|
||||
Expect("/tmp/tuning-test/dummy_dummy0.json").Should(BeAnExistingFile())
|
||||
|
||||
n := &TuningConf{}
|
||||
err = json.Unmarshal([]byte(conf), &n)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
cniVersion := "0.4.0"
|
||||
_, confString, err := buildOneConfig("testConfig", cniVersion, n, r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
args.StdinData = confString
|
||||
|
||||
err = testutils.CmdCheckWithArgs(args, func() error {
|
||||
return cmdCheck(args)
|
||||
})
|
||||
Expect(err).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().HardwareAddr.String()).To(Equal(beforeConf.Mac))
|
||||
Expect(link.Attrs().MTU).To(Equal(beforeConf.Mtu))
|
||||
Expect(link.Attrs().Promisc != 0).To(Equal(*beforeConf.Promisc))
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
5
plugins/meta/vrf/README.md
Normal file
5
plugins/meta/vrf/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
|
||||
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
|
||||
|
||||
You can find it online here: https://cni.dev/plugins/meta/vrf/
|
||||
|
194
plugins/meta/vrf/main.go
Normal file
194
plugins/meta/vrf/main.go
Normal file
@ -0,0 +1,194 @@
|
||||
// Copyright 2020 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"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"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
// VRFNetConf represents the vrf configuration.
|
||||
type VRFNetConf struct {
|
||||
types.NetConf
|
||||
|
||||
// VRFName is the name of the vrf to add the interface to.
|
||||
VRFName string `json:"vrfname"`
|
||||
// Table is the optional name of the routing table set for the vrf
|
||||
Table uint32 `json:"table"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.PluginSupports("0.3.1", "0.4.0"), bv.BuildString("vrf"))
|
||||
}
|
||||
|
||||
func cmdAdd(args *skel.CmdArgs) error {
|
||||
conf, result, err := parseConf(args.StdinData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if conf.PrevResult == nil {
|
||||
return fmt.Errorf("missing prevResult from earlier plugin")
|
||||
}
|
||||
|
||||
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||
vrf, err := findVRF(conf.VRFName)
|
||||
|
||||
// If the user set a tableid and the vrf is already in the namespace
|
||||
// we check if the tableid is the same one already assigned to the vrf.
|
||||
if err == nil && conf.Table != 0 && vrf.Table != conf.Table {
|
||||
return fmt.Errorf("VRF %s already exist with different routing table %d", conf.VRFName, vrf.Table)
|
||||
}
|
||||
|
||||
if _, ok := err.(netlink.LinkNotFoundError); ok {
|
||||
vrf, err = createVRF(conf.VRFName, conf.Table)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = addInterface(vrf, args.IfName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("cmdAdd failed: %v", err)
|
||||
}
|
||||
|
||||
if result == nil {
|
||||
result = ¤t.Result{}
|
||||
}
|
||||
|
||||
return types.PrintResult(result, conf.CNIVersion)
|
||||
}
|
||||
|
||||
func cmdDel(args *skel.CmdArgs) error {
|
||||
conf, _, err := parseConf(args.StdinData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||
vrf, err := findVRF(conf.VRFName)
|
||||
if _, ok := err.(netlink.LinkNotFoundError); ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = resetMaster(args.IfName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
interfaces, err := assignedInterfaces(vrf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Meaning, we are deleting the last interface assigned to the VRF
|
||||
if len(interfaces) == 0 {
|
||||
err = netlink.LinkDel(vrf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("cmdDel failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
conf, _, err := parseConf(args.StdinData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure we have previous result.
|
||||
if conf.PrevResult == nil {
|
||||
return fmt.Errorf("missing prevResult from earlier plugin")
|
||||
}
|
||||
|
||||
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||
vrf, err := findVRF(conf.VRFName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vrfInterfaces, err := assignedInterfaces(vrf)
|
||||
|
||||
found := false
|
||||
for _, intf := range vrfInterfaces {
|
||||
if intf.Attrs().Name == args.IfName {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("Failed to find %s associated to vrf %s", args.IfName, conf.VRFName)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseConf(data []byte) (*VRFNetConf, *current.Result, error) {
|
||||
conf := VRFNetConf{}
|
||||
if err := json.Unmarshal(data, &conf); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to load netconf: %v", err)
|
||||
}
|
||||
|
||||
if conf.VRFName == "" {
|
||||
return nil, nil, fmt.Errorf("configuration is expected to have a valid vrf name")
|
||||
}
|
||||
|
||||
if conf.RawPrevResult == nil {
|
||||
// return early if there was no previous result, which is allowed for DEL calls
|
||||
return &conf, ¤t.Result{}, nil
|
||||
}
|
||||
|
||||
// Parse previous result.
|
||||
var result *current.Result
|
||||
var err error
|
||||
if err = version.ParsePrevResult(&conf.NetConf); err != nil {
|
||||
return nil, nil, fmt.Errorf("could not parse prevResult: %v", err)
|
||||
}
|
||||
|
||||
result, err = current.NewResultFromResult(conf.PrevResult)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("could not convert result to current version: %v", err)
|
||||
}
|
||||
|
||||
return &conf, result, nil
|
||||
}
|
162
plugins/meta/vrf/vrf.go
Normal file
162
plugins/meta/vrf/vrf.go
Normal file
@ -0,0 +1,162 @@
|
||||
// Copyright 2020 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"
|
||||
"math"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
// findVRF finds a VRF link with the provided name.
|
||||
func findVRF(name string) (*netlink.Vrf, error) {
|
||||
link, err := netlink.LinkByName(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vrf, ok := link.(*netlink.Vrf)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Netlink %s is not a VRF", name)
|
||||
}
|
||||
return vrf, nil
|
||||
}
|
||||
|
||||
// createVRF creates a new VRF and sets it up.
|
||||
func createVRF(name string, tableID uint32) (*netlink.Vrf, error) {
|
||||
links, err := netlink.LinkList()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("createVRF: Failed to find links %v", err)
|
||||
}
|
||||
|
||||
if tableID == 0 {
|
||||
tableID, err = findFreeRoutingTableID(links)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
vrf := &netlink.Vrf{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: name,
|
||||
},
|
||||
Table: tableID,
|
||||
}
|
||||
|
||||
err = netlink.LinkAdd(vrf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not add VRF %s: %v", name, err)
|
||||
}
|
||||
err = netlink.LinkSetUp(vrf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not set link up for VRF %s: %v", name, err)
|
||||
}
|
||||
|
||||
return vrf, nil
|
||||
}
|
||||
|
||||
// assignedInterfaces returns the list of interfaces associated to the given vrf.
|
||||
func assignedInterfaces(vrf *netlink.Vrf) ([]netlink.Link, error) {
|
||||
links, err := netlink.LinkList()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getAssignedInterfaces: Failed to find links %v", err)
|
||||
}
|
||||
res := make([]netlink.Link, 0)
|
||||
for _, l := range links {
|
||||
if l.Attrs().MasterIndex == vrf.Index {
|
||||
res = append(res, l)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// addInterface adds the given interface to the VRF
|
||||
func addInterface(vrf *netlink.Vrf, intf string) error {
|
||||
i, err := netlink.LinkByName(intf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get link by name %s", intf)
|
||||
}
|
||||
|
||||
if i.Attrs().MasterIndex != 0 {
|
||||
master, err := netlink.LinkByIndex(i.Attrs().MasterIndex)
|
||||
if err != nil {
|
||||
return fmt.Errorf("interface %s has already a master set, could not retrieve the name: %v", intf, err)
|
||||
}
|
||||
return fmt.Errorf("interface %s has already a master set: %s", intf, master.Attrs().Name)
|
||||
}
|
||||
|
||||
// IPV6 addresses are not maintained unless
|
||||
// sysctl -w net.ipv6.conf.all.keep_addr_on_down=1 is called
|
||||
// so we save it, and restore it back.
|
||||
beforeAddresses, err := netlink.AddrList(i, netlink.FAMILY_V6)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed getting ipv6 addresses 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)
|
||||
}
|
||||
|
||||
afterAddresses, err := netlink.AddrList(i, netlink.FAMILY_V6)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed getting ipv6 new addresses for %s", intf)
|
||||
}
|
||||
|
||||
// Since keeping the ipv6 address depends on net.ipv6.conf.all.keep_addr_on_down ,
|
||||
// we check if the new interface does not have them and in case we restore them.
|
||||
CONTINUE:
|
||||
for _, toFind := range beforeAddresses {
|
||||
for _, current := range afterAddresses {
|
||||
if toFind.Equal(current) {
|
||||
continue CONTINUE
|
||||
}
|
||||
}
|
||||
// Not found, re-adding it
|
||||
err = netlink.AddrAdd(i, &toFind)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not restore address %s to %s @ %s: %v", toFind, intf, vrf.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func findFreeRoutingTableID(links []netlink.Link) (uint32, error) {
|
||||
takenTables := make(map[uint32]struct{}, len(links))
|
||||
for _, l := range links {
|
||||
if vrf, ok := l.(*netlink.Vrf); ok {
|
||||
takenTables[vrf.Table] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
for res := uint32(1); res < math.MaxUint32; res++ {
|
||||
if _, ok := takenTables[res]; !ok {
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("findFreeRoutingTableID: Failed to find an available routing id")
|
||||
}
|
||||
|
||||
func resetMaster(interfaceName string) error {
|
||||
intf, err := netlink.LinkByName(interfaceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("resetMaster: could not get link by name %s", interfaceName)
|
||||
}
|
||||
err = netlink.LinkSetNoMaster(intf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("resetMaster: could reset master to %s", interfaceName)
|
||||
}
|
||||
return nil
|
||||
}
|
27
plugins/meta/vrf/vrf_suite_test.go
Normal file
27
plugins/meta/vrf/vrf_suite_test.go
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright 2020 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestVRF(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "plugins/meta/vrf")
|
||||
}
|
713
plugins/meta/vrf/vrf_test.go
Normal file
713
plugins/meta/vrf/vrf_test.go
Normal file
@ -0,0 +1,713 @@
|
||||
// Copyright 2020 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/testutils"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/extensions/table"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func buildOneConfig(name, cniVersion string, orig *VRFNetConf, prevResult types.Result) (*VRFNetConf, []byte, error) {
|
||||
var err error
|
||||
|
||||
inject := map[string]interface{}{
|
||||
"name": name,
|
||||
"cniVersion": cniVersion,
|
||||
}
|
||||
// Add previous plugin result
|
||||
if prevResult != nil {
|
||||
inject["prevResult"] = prevResult
|
||||
}
|
||||
|
||||
// Ensure every config uses the same name and version
|
||||
config := make(map[string]interface{})
|
||||
|
||||
confBytes, err := json.Marshal(orig)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(confBytes, &config)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unmarshal existing network bytes: %s", err)
|
||||
}
|
||||
|
||||
for key, value := range inject {
|
||||
config[key] = value
|
||||
}
|
||||
|
||||
newBytes, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
conf := &VRFNetConf{}
|
||||
if err := json.Unmarshal(newBytes, &conf); err != nil {
|
||||
return nil, nil, fmt.Errorf("error parsing configuration: %s", err)
|
||||
}
|
||||
|
||||
return conf, newBytes, nil
|
||||
|
||||
}
|
||||
|
||||
var _ = Describe("vrf plugin", func() {
|
||||
var originalNS ns.NetNS
|
||||
var targetNS ns.NetNS
|
||||
const (
|
||||
IF0Name = "dummy0"
|
||||
IF1Name = "dummy1"
|
||||
VRF0Name = "vrf0"
|
||||
VRF1Name = "vrf1"
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
originalNS, err = testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
targetNS, err = testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
err = netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: IF0Name,
|
||||
},
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
_, err = netlink.LinkByName(IF0Name)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: IF1Name,
|
||||
},
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
_, err = netlink.LinkByName(IF0Name)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(originalNS.Close()).To(Succeed())
|
||||
Expect(targetNS.Close()).To(Succeed())
|
||||
})
|
||||
|
||||
It("passes prevResult through unchanged", func() {
|
||||
conf := configFor("test", IF0Name, VRF0Name, "10.0.0.2/24")
|
||||
|
||||
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(len(result.Interfaces)).To(Equal(1))
|
||||
Expect(result.Interfaces[0].Name).To(Equal(IF0Name))
|
||||
Expect(len(result.IPs)).To(Equal(1))
|
||||
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("configures a VRF and adds the interface to it", func() {
|
||||
conf := configFor("test", IF0Name, VRF0Name, "10.0.0.2/24")
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: IF0Name,
|
||||
StdinData: conf,
|
||||
}
|
||||
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
_, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
|
||||
err = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
checkInterfaceOnVRF(VRF0Name, IF0Name)
|
||||
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")
|
||||
|
||||
By("Setting the interface's master", func() {
|
||||
err := targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
l, err := netlink.LinkByName(IF0Name)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
br := &netlink.Bridge{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: "testrbridge",
|
||||
},
|
||||
}
|
||||
err = netlink.LinkAdd(br)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = netlink.LinkSetMaster(l, br)
|
||||
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()
|
||||
_, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("has already a master set"))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
DescribeTable("handles two interfaces",
|
||||
func(vrf0, vrf1, ip0, ip1 string) {
|
||||
conf0 := configFor("test", IF0Name, vrf0, ip0)
|
||||
conf1 := configFor("test1", IF1Name, vrf1, ip1)
|
||||
|
||||
addr0, err := netlink.ParseAddr(ip0)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
addr1, err := netlink.ParseAddr(ip1)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("Setting the first interface's ip", func() {
|
||||
err := targetNS.Do(func(ns.NetNS) error {
|
||||
l, err := netlink.LinkByName(IF0Name)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = netlink.AddrAdd(l, addr0)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
By("Adding the first interface to first vrf", func() {
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: IF0Name,
|
||||
StdinData: conf0,
|
||||
}
|
||||
_, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
By("Setting the second interface's ip", func() {
|
||||
err := targetNS.Do(func(ns.NetNS) error {
|
||||
l, err := netlink.LinkByName(IF1Name)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = netlink.AddrAdd(l, addr1)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
By("Adding the second interface to second 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 that the first interface is added to first vrf", func() {
|
||||
err := targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
checkInterfaceOnVRF(vrf0, IF0Name)
|
||||
|
||||
link, err := netlink.LinkByName(IF0Name)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
addresses, err := netlink.AddrList(link, netlink.FAMILY_ALL)
|
||||
Expect(len(addresses)).To(Equal(1))
|
||||
Expect(addresses[0].IP.Equal(addr0.IP)).To(BeTrue())
|
||||
Expect(addresses[0].Mask).To(Equal(addr0.Mask))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
By("Checking that the second interface is added to second vrf", func() {
|
||||
err := targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
checkInterfaceOnVRF(vrf0, IF0Name)
|
||||
|
||||
link, err := netlink.LinkByName(IF1Name)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
addresses, err := netlink.AddrList(link, netlink.FAMILY_ALL)
|
||||
Expect(len(addresses)).To(Equal(1))
|
||||
Expect(addresses[0].IP.Equal(addr1.IP)).To(BeTrue())
|
||||
Expect(addresses[0].Mask).To(Equal(addr1.Mask))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
By("Checking that when the vrfs are different, the routing table is different", func() {
|
||||
if vrf0 == vrf1 {
|
||||
return
|
||||
}
|
||||
err := targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
l0, err := netlink.LinkByName(vrf0)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(l0).To(BeAssignableToTypeOf(&netlink.Vrf{}))
|
||||
l1, err := netlink.LinkByName(vrf1)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(l1).To(BeAssignableToTypeOf(&netlink.Vrf{}))
|
||||
|
||||
vrf0Link := l0.(*netlink.Vrf)
|
||||
vrf1Link := l1.(*netlink.Vrf)
|
||||
Expect(vrf0Link.Table).NotTo(Equal(vrf1Link.Table))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
},
|
||||
Entry("added to the same vrf", VRF0Name, VRF0Name, "10.0.0.2/24", "10.0.0.3/24"),
|
||||
Entry("added to different vrfs", VRF0Name, VRF1Name, "10.0.0.2/24", "10.0.0.3/24"),
|
||||
Entry("added to different vrfs with same ip", VRF0Name, VRF1Name, "10.0.0.2/24", "10.0.0.2/24"),
|
||||
Entry("added to the same vrf IPV6", VRF0Name, VRF0Name, "2A00:0C98:2060:A000:0001:0000:1d1e:ca75/64", "2A00:0C98:2060:A000:0001:0000:1d1e:ca76/64"),
|
||||
Entry("added to different vrfs IPV6", VRF0Name, VRF1Name, "2A00:0C98:2060:A000:0001:0000:1d1e:ca75/64", "2A00:0C98:2060:A000:0001:0000:1d1e:ca76/64"),
|
||||
Entry("added to different vrfs with same ip IPV6", VRF0Name, VRF1Name, "2A00:0C98:2060:A000:0001:0000:1d1e:ca75/64", "2A00:0C98:2060:A000:0001:0000:1d1e:ca75/64"),
|
||||
)
|
||||
|
||||
DescribeTable("handles tableid conflicts",
|
||||
func(vrf0, vrf1 string, tableid0, tableid1 int, expectedError string) {
|
||||
conf0 := configWithTableFor("test", IF0Name, vrf0, "10.0.0.2/24", tableid0)
|
||||
conf1 := configWithTableFor("test1", IF1Name, vrf1, "10.0.0.2/24", tableid1)
|
||||
|
||||
By("Adding the first interface to first vrf", func() {
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: IF0Name,
|
||||
StdinData: conf0,
|
||||
}
|
||||
_, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
By("Checking that the first vrf has the right routing table", func() {
|
||||
err := targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
l, err := netlink.LinkByName(vrf0)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
vrf := l.(*netlink.Vrf)
|
||||
Expect(vrf.Table).To(Equal(uint32(tableid0)))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
By("Adding the second interface to second 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)
|
||||
})
|
||||
return err
|
||||
})
|
||||
if expectedError != "" {
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring(expectedError))
|
||||
return
|
||||
}
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
},
|
||||
Entry("same vrf with same tableid", VRF0Name, VRF0Name, 1001, 1001, ""),
|
||||
Entry("different vrf with same tableid", VRF0Name, VRF1Name, 1001, 1001, ""),
|
||||
Entry("same vrf with different tableids", VRF0Name, VRF0Name, 1001, 1002, "already exist with different routing table"),
|
||||
)
|
||||
|
||||
It("removes the VRF only when the last interface is removed", func() {
|
||||
conf0 := configFor("test", IF0Name, VRF0Name, "10.0.0.2/24")
|
||||
conf1 := configFor("test1", IF1Name, VRF0Name, "10.0.0.2/24")
|
||||
|
||||
By("Adding the two interfaces to the VRF", func() {
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: IF0Name,
|
||||
StdinData: conf0,
|
||||
}
|
||||
_, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
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 that the two interfaces are added to the VRF", func() {
|
||||
targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
checkInterfaceOnVRF(VRF0Name, IF0Name)
|
||||
checkInterfaceOnVRF(VRF0Name, IF1Name)
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
By("Removing the first interface from VRF, removing the interface", func() {
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: IF0Name,
|
||||
StdinData: conf0,
|
||||
}
|
||||
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 {
|
||||
link, err := netlink.LinkByName(IF0Name)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = netlink.LinkDel(link)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
By("Checking that the second interface is still on the VRF and that VRF still exists", func() {
|
||||
targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
checkInterfaceOnVRF(VRF0Name, IF1Name)
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
By("Removing the second interface from VRF, deleting the second interface", func() {
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: IF1Name,
|
||||
StdinData: conf1,
|
||||
}
|
||||
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 {
|
||||
link, err := netlink.LinkByName(IF1Name)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = netlink.LinkDel(link)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
By("Checking that the VRF is removed", func() {
|
||||
targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
_, err := netlink.LinkByName(VRF0Name)
|
||||
Expect(err).NotTo(BeNil())
|
||||
return nil
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
It("configures and deconfigures VRF with CNI 0.4.0 ADD/DEL", func() {
|
||||
conf := []byte(fmt.Sprintf(`{
|
||||
"name": "test",
|
||||
"type": "vrf",
|
||||
"cniVersion": "0.4.0",
|
||||
"vrfName": "%s",
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{"name": "%s", "sandbox":"netns"}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "10.0.0.2/24",
|
||||
"gateway": "10.0.0.1",
|
||||
"interface": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}`, VRF0Name, IF0Name))
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: IF0Name,
|
||||
StdinData: conf,
|
||||
}
|
||||
var prevRes types.Result
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
prevRes, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
result, err := current.GetResult(prevRes)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(len(result.Interfaces)).To(Equal(1))
|
||||
Expect(result.Interfaces[0].Name).To(Equal(IF0Name))
|
||||
Expect(len(result.IPs)).To(Equal(1))
|
||||
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
checkInterfaceOnVRF(VRF0Name, IF0Name)
|
||||
return nil
|
||||
})
|
||||
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
cniVersion := "0.4.0"
|
||||
n := &VRFNetConf{}
|
||||
err = json.Unmarshal([]byte(conf), &n)
|
||||
_, confString, err := buildOneConfig("testConfig", cniVersion, n, prevRes)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
args.StdinData = confString
|
||||
|
||||
err = testutils.CmdCheckWithArgs(args, func() error {
|
||||
return cmdCheck(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = testutils.CmdDel(originalNS.Path(),
|
||||
args.ContainerID, "", func() error { return cmdDel(args) })
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
var _ = Describe("unit tests", func() {
|
||||
DescribeTable("When looking for a table id",
|
||||
func(links []netlink.Link, expected uint32, expectFail bool) {
|
||||
newID, err := findFreeRoutingTableID(links)
|
||||
if expectFail {
|
||||
Expect(err).To(HaveOccurred())
|
||||
return
|
||||
}
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(newID).To(Equal(expected))
|
||||
},
|
||||
Entry("Finds first free one", []netlink.Link{
|
||||
&netlink.Vrf{Table: 1},
|
||||
&netlink.Vrf{Table: 2},
|
||||
&netlink.Vrf{Table: 3},
|
||||
&netlink.Vrf{Table: 5},
|
||||
}, uint32(4), false),
|
||||
Entry("Ignores non VRFs free one", []netlink.Link{
|
||||
&netlink.Vrf{Table: 1},
|
||||
&netlink.Vrf{Table: 2},
|
||||
&netlink.Dummy{},
|
||||
&netlink.Vrf{Table: 5},
|
||||
}, uint32(3), false),
|
||||
Entry("Takes the first when no vrfs are there", []netlink.Link{},
|
||||
uint32(1), false),
|
||||
Entry("Works with 999 vrfs already assigned", func() []netlink.Link {
|
||||
res := []netlink.Link{}
|
||||
for i := uint32(1); i < 1000; i++ {
|
||||
res = append(res, &netlink.Vrf{Table: i})
|
||||
}
|
||||
return res
|
||||
}(), uint32(1000), false),
|
||||
)
|
||||
})
|
||||
|
||||
func configFor(name, intf, vrf, ip 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
|
||||
}
|
||||
]
|
||||
}
|
||||
}`, name, vrf, intf, ip)
|
||||
return []byte(conf)
|
||||
}
|
||||
|
||||
func configWithTableFor(name, intf, vrf, ip string, tableID int) []byte {
|
||||
conf := fmt.Sprintf(`{
|
||||
"name": "%s",
|
||||
"type": "vrf",
|
||||
"cniVersion": "0.3.1",
|
||||
"vrfName": "%s",
|
||||
"table": %d,
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{"name": "%s", "sandbox":"netns"}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "%s",
|
||||
"gateway": "10.0.0.1",
|
||||
"interface": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}`, name, vrf, tableID, intf, ip)
|
||||
return []byte(conf)
|
||||
}
|
||||
|
||||
func checkInterfaceOnVRF(vrfName, intfName string) {
|
||||
vrf, err := netlink.LinkByName(vrfName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(vrf).To(BeAssignableToTypeOf(&netlink.Vrf{}))
|
||||
|
||||
link, err := netlink.LinkByName(intfName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
masterIndx := link.Attrs().MasterIndex
|
||||
master, err := netlink.LinkByIndex(masterIndx)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(master.Attrs().Name).To(Equal(vrfName))
|
||||
}
|
||||
|
||||
func checkLinkHasNoMaster(intfName string) {
|
||||
link, err := netlink.LinkByName(intfName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
masterIndx := link.Attrs().MasterIndex
|
||||
Expect(masterIndx).To(Equal(0))
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
plugins/ipam/host-local
|
||||
plugins/main/windows/win-bridge
|
||||
plugins/main/windows/win-overlay
|
||||
plugins/meta/flannel
|
||||
plugins/meta/flannel
|
||||
|
@ -16,7 +16,7 @@ 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 --rm golang:1.12-alpine \
|
||||
$DOCKER run -ti -v ${SRC_DIR}:/go/src/github.com/containernetworking/plugins --rm golang:1.15-alpine \
|
||||
/bin/sh -xe -c "\
|
||||
apk --no-cache add bash tar;
|
||||
cd /go/src/github.com/containernetworking/plugins; umask 0022;
|
||||
|
@ -7,7 +7,7 @@
|
||||
set -e
|
||||
|
||||
# switch into the repo root directory
|
||||
cd "$(dirname $0)"
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# Build all plugins before testing
|
||||
source ./build_linux.sh
|
||||
@ -15,7 +15,7 @@ source ./build_linux.sh
|
||||
echo "Running tests"
|
||||
|
||||
function testrun {
|
||||
sudo -E bash -c "umask 0; cd ${GOPATH}/src; PATH=${GOROOT}/bin:$(pwd)/bin:${PATH} go test $@"
|
||||
sudo -E bash -c "umask 0; PATH=${GOPATH}/bin:$(pwd)/bin:${PATH} go test $@"
|
||||
}
|
||||
|
||||
COVERALLS=${COVERALLS:-""}
|
||||
@ -26,36 +26,30 @@ else
|
||||
echo "without coverage profile generation..."
|
||||
fi
|
||||
|
||||
PKG=${PKG:-$(cd ${GOPATH}/src/${REPO_PATH}; go list ./... | xargs echo)}
|
||||
PKG=${PKG:-$(go list ./... | xargs echo)}
|
||||
|
||||
# coverage profile only works per-package
|
||||
i=0
|
||||
for t in ${PKG}; do
|
||||
if [ -n "${COVERALLS}" ]; then
|
||||
COVERFLAGS="-covermode set -coverprofile ${i}.coverprofile"
|
||||
fi
|
||||
echo "${t}"
|
||||
testrun "${COVERFLAGS:-""} ${t}"
|
||||
i=$((i+1))
|
||||
done
|
||||
|
||||
# Submit coverage information
|
||||
if [ -n "${COVERALLS}" ]; then
|
||||
gover
|
||||
goveralls -service=travis-ci -coverprofile=gover.coverprofile
|
||||
fi
|
||||
|
||||
echo "Checking gofmt..."
|
||||
fmtRes=$(go fmt $PKG)
|
||||
if [ -n "${fmtRes}" ]; then
|
||||
echo -e "go fmt checking failed:\n${fmtRes}"
|
||||
exit 255
|
||||
echo -e "go fmt checking failed:\n${fmtRes}"
|
||||
exit 255
|
||||
fi
|
||||
|
||||
echo "Checking govet..."
|
||||
vetRes=$(go vet $PKG)
|
||||
if [ -n "${vetRes}" ]; then
|
||||
echo -e "govet checking failed:\n${vetRes}"
|
||||
exit 255
|
||||
echo -e "govet checking failed:\n${vetRes}"
|
||||
exit 255
|
||||
fi
|
||||
|
||||
# Run the pkg/ns tests as non root user
|
||||
|
@ -3,12 +3,18 @@
|
||||
# Run CNI plugin tests.
|
||||
#
|
||||
set -e
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
source ./build_windows.sh
|
||||
|
||||
echo "Running tests"
|
||||
|
||||
PLUGINS=$(cat plugins/windows_only.txt | tr '\n' ' ')
|
||||
GINKGO_FLAGS="-p -r --randomizeAllSpecs --randomizeSuites --failOnPending --progress pkg/hns $PLUGINS"
|
||||
PKGS="./pkg/hns/..."
|
||||
|
||||
bash -c "cd ${GOPATH}/src/${REPO_PATH}; PATH='${GOROOT}/bin:$(pwd)/bin:${PATH}' ginkgo ${GINKGO_FLAGS}"
|
||||
PLUGINS=$(cat plugins/windows_only.txt | dos2unix )
|
||||
for d in $PLUGINS; do
|
||||
PKGS="$PKGS ./$d/..."
|
||||
done
|
||||
|
||||
echo "testing packages $PKGS"
|
||||
go test -v $PKGS -ginkgo.randomizeAllSpecs -ginkgo.failOnPending -ginkgo.progress
|
||||
|
943
vendor/github.com/Microsoft/hcsshim/mksyscall_windows.go
generated
vendored
943
vendor/github.com/Microsoft/hcsshim/mksyscall_windows.go
generated
vendored
@ -1,943 +0,0 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
mksyscall_windows generates windows system call bodies
|
||||
|
||||
It parses all files specified on command line containing function
|
||||
prototypes (like syscall_windows.go) and prints system call bodies
|
||||
to standard output.
|
||||
|
||||
The prototypes are marked by lines beginning with "//sys" and read
|
||||
like func declarations if //sys is replaced by func, but:
|
||||
|
||||
* The parameter lists must give a name for each argument. This
|
||||
includes return parameters.
|
||||
|
||||
* The parameter lists must give a type for each argument:
|
||||
the (x, y, z int) shorthand is not allowed.
|
||||
|
||||
* If the return parameter is an error number, it must be named err.
|
||||
|
||||
* If go func name needs to be different from it's winapi dll name,
|
||||
the winapi name could be specified at the end, after "=" sign, like
|
||||
//sys LoadLibrary(libname string) (handle uint32, err error) = LoadLibraryA
|
||||
|
||||
* Each function that returns err needs to supply a condition, that
|
||||
return value of winapi will be tested against to detect failure.
|
||||
This would set err to windows "last-error", otherwise it will be nil.
|
||||
The value can be provided at end of //sys declaration, like
|
||||
//sys LoadLibrary(libname string) (handle uint32, err error) [failretval==-1] = LoadLibraryA
|
||||
and is [failretval==0] by default.
|
||||
|
||||
Usage:
|
||||
mksyscall_windows [flags] [path ...]
|
||||
|
||||
The flags are:
|
||||
-output
|
||||
Specify output file name (outputs to console if blank).
|
||||
-trace
|
||||
Generate print statement after every syscall.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
var (
|
||||
filename = flag.String("output", "", "output file name (standard output if omitted)")
|
||||
printTraceFlag = flag.Bool("trace", false, "generate print statement after every syscall")
|
||||
systemDLL = flag.Bool("systemdll", true, "whether all DLLs should be loaded from the Windows system directory")
|
||||
winio = flag.Bool("winio", false, "import go-winio")
|
||||
)
|
||||
|
||||
func trim(s string) string {
|
||||
return strings.Trim(s, " \t")
|
||||
}
|
||||
|
||||
var packageName string
|
||||
|
||||
func packagename() string {
|
||||
return packageName
|
||||
}
|
||||
|
||||
func syscalldot() string {
|
||||
if packageName == "syscall" {
|
||||
return ""
|
||||
}
|
||||
return "syscall."
|
||||
}
|
||||
|
||||
// Param is function parameter
|
||||
type Param struct {
|
||||
Name string
|
||||
Type string
|
||||
fn *Fn
|
||||
tmpVarIdx int
|
||||
}
|
||||
|
||||
// tmpVar returns temp variable name that will be used to represent p during syscall.
|
||||
func (p *Param) tmpVar() string {
|
||||
if p.tmpVarIdx < 0 {
|
||||
p.tmpVarIdx = p.fn.curTmpVarIdx
|
||||
p.fn.curTmpVarIdx++
|
||||
}
|
||||
return fmt.Sprintf("_p%d", p.tmpVarIdx)
|
||||
}
|
||||
|
||||
// BoolTmpVarCode returns source code for bool temp variable.
|
||||
func (p *Param) BoolTmpVarCode() string {
|
||||
const code = `var %s uint32
|
||||
if %s {
|
||||
%s = 1
|
||||
} else {
|
||||
%s = 0
|
||||
}`
|
||||
tmp := p.tmpVar()
|
||||
return fmt.Sprintf(code, tmp, p.Name, tmp, tmp)
|
||||
}
|
||||
|
||||
// SliceTmpVarCode returns source code for slice temp variable.
|
||||
func (p *Param) SliceTmpVarCode() string {
|
||||
const code = `var %s *%s
|
||||
if len(%s) > 0 {
|
||||
%s = &%s[0]
|
||||
}`
|
||||
tmp := p.tmpVar()
|
||||
return fmt.Sprintf(code, tmp, p.Type[2:], p.Name, tmp, p.Name)
|
||||
}
|
||||
|
||||
// StringTmpVarCode returns source code for string temp variable.
|
||||
func (p *Param) StringTmpVarCode() string {
|
||||
errvar := p.fn.Rets.ErrorVarName()
|
||||
if errvar == "" {
|
||||
errvar = "_"
|
||||
}
|
||||
tmp := p.tmpVar()
|
||||
const code = `var %s %s
|
||||
%s, %s = %s(%s)`
|
||||
s := fmt.Sprintf(code, tmp, p.fn.StrconvType(), tmp, errvar, p.fn.StrconvFunc(), p.Name)
|
||||
if errvar == "-" {
|
||||
return s
|
||||
}
|
||||
const morecode = `
|
||||
if %s != nil {
|
||||
return
|
||||
}`
|
||||
return s + fmt.Sprintf(morecode, errvar)
|
||||
}
|
||||
|
||||
// TmpVarCode returns source code for temp variable.
|
||||
func (p *Param) TmpVarCode() string {
|
||||
switch {
|
||||
case p.Type == "bool":
|
||||
return p.BoolTmpVarCode()
|
||||
case strings.HasPrefix(p.Type, "[]"):
|
||||
return p.SliceTmpVarCode()
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// TmpVarHelperCode returns source code for helper's temp variable.
|
||||
func (p *Param) TmpVarHelperCode() string {
|
||||
if p.Type != "string" {
|
||||
return ""
|
||||
}
|
||||
return p.StringTmpVarCode()
|
||||
}
|
||||
|
||||
// SyscallArgList returns source code fragments representing p parameter
|
||||
// in syscall. Slices are translated into 2 syscall parameters: pointer to
|
||||
// the first element and length.
|
||||
func (p *Param) SyscallArgList() []string {
|
||||
t := p.HelperType()
|
||||
var s string
|
||||
switch {
|
||||
case t[0] == '*':
|
||||
s = fmt.Sprintf("unsafe.Pointer(%s)", p.Name)
|
||||
case t == "bool":
|
||||
s = p.tmpVar()
|
||||
case strings.HasPrefix(t, "[]"):
|
||||
return []string{
|
||||
fmt.Sprintf("uintptr(unsafe.Pointer(%s))", p.tmpVar()),
|
||||
fmt.Sprintf("uintptr(len(%s))", p.Name),
|
||||
}
|
||||
default:
|
||||
s = p.Name
|
||||
}
|
||||
return []string{fmt.Sprintf("uintptr(%s)", s)}
|
||||
}
|
||||
|
||||
// IsError determines if p parameter is used to return error.
|
||||
func (p *Param) IsError() bool {
|
||||
return p.Name == "err" && p.Type == "error"
|
||||
}
|
||||
|
||||
// HelperType returns type of parameter p used in helper function.
|
||||
func (p *Param) HelperType() string {
|
||||
if p.Type == "string" {
|
||||
return p.fn.StrconvType()
|
||||
}
|
||||
return p.Type
|
||||
}
|
||||
|
||||
// join concatenates parameters ps into a string with sep separator.
|
||||
// Each parameter is converted into string by applying fn to it
|
||||
// before conversion.
|
||||
func join(ps []*Param, fn func(*Param) string, sep string) string {
|
||||
if len(ps) == 0 {
|
||||
return ""
|
||||
}
|
||||
a := make([]string, 0)
|
||||
for _, p := range ps {
|
||||
a = append(a, fn(p))
|
||||
}
|
||||
return strings.Join(a, sep)
|
||||
}
|
||||
|
||||
// Rets describes function return parameters.
|
||||
type Rets struct {
|
||||
Name string
|
||||
Type string
|
||||
ReturnsError bool
|
||||
FailCond string
|
||||
}
|
||||
|
||||
// ErrorVarName returns error variable name for r.
|
||||
func (r *Rets) ErrorVarName() string {
|
||||
if r.ReturnsError {
|
||||
return "err"
|
||||
}
|
||||
if r.Type == "error" {
|
||||
return r.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ToParams converts r into slice of *Param.
|
||||
func (r *Rets) ToParams() []*Param {
|
||||
ps := make([]*Param, 0)
|
||||
if len(r.Name) > 0 {
|
||||
ps = append(ps, &Param{Name: r.Name, Type: r.Type})
|
||||
}
|
||||
if r.ReturnsError {
|
||||
ps = append(ps, &Param{Name: "err", Type: "error"})
|
||||
}
|
||||
return ps
|
||||
}
|
||||
|
||||
// List returns source code of syscall return parameters.
|
||||
func (r *Rets) List() string {
|
||||
s := join(r.ToParams(), func(p *Param) string { return p.Name + " " + p.Type }, ", ")
|
||||
if len(s) > 0 {
|
||||
s = "(" + s + ")"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// PrintList returns source code of trace printing part correspondent
|
||||
// to syscall return values.
|
||||
func (r *Rets) PrintList() string {
|
||||
return join(r.ToParams(), func(p *Param) string { return fmt.Sprintf(`"%s=", %s, `, p.Name, p.Name) }, `", ", `)
|
||||
}
|
||||
|
||||
// SetReturnValuesCode returns source code that accepts syscall return values.
|
||||
func (r *Rets) SetReturnValuesCode() string {
|
||||
if r.Name == "" && !r.ReturnsError {
|
||||
return ""
|
||||
}
|
||||
retvar := "r0"
|
||||
if r.Name == "" {
|
||||
retvar = "r1"
|
||||
}
|
||||
errvar := "_"
|
||||
if r.ReturnsError {
|
||||
errvar = "e1"
|
||||
}
|
||||
return fmt.Sprintf("%s, _, %s := ", retvar, errvar)
|
||||
}
|
||||
|
||||
func (r *Rets) useLongHandleErrorCode(retvar string) string {
|
||||
const code = `if %s {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = %sEINVAL
|
||||
}
|
||||
}`
|
||||
cond := retvar + " == 0"
|
||||
if r.FailCond != "" {
|
||||
cond = strings.Replace(r.FailCond, "failretval", retvar, 1)
|
||||
}
|
||||
return fmt.Sprintf(code, cond, syscalldot())
|
||||
}
|
||||
|
||||
// SetErrorCode returns source code that sets return parameters.
|
||||
func (r *Rets) SetErrorCode() string {
|
||||
const code = `if r0 != 0 {
|
||||
%s = %sErrno(r0)
|
||||
}`
|
||||
const hrCode = `if int32(r0) < 0 {
|
||||
if r0&0x1fff0000 == 0x00070000 {
|
||||
r0 &= 0xffff
|
||||
}
|
||||
%s = %sErrno(r0)
|
||||
}`
|
||||
if r.Name == "" && !r.ReturnsError {
|
||||
return ""
|
||||
}
|
||||
if r.Name == "" {
|
||||
return r.useLongHandleErrorCode("r1")
|
||||
}
|
||||
if r.Type == "error" {
|
||||
if r.Name == "hr" {
|
||||
return fmt.Sprintf(hrCode, r.Name, syscalldot())
|
||||
} else {
|
||||
return fmt.Sprintf(code, r.Name, syscalldot())
|
||||
}
|
||||
}
|
||||
s := ""
|
||||
switch {
|
||||
case r.Type[0] == '*':
|
||||
s = fmt.Sprintf("%s = (%s)(unsafe.Pointer(r0))", r.Name, r.Type)
|
||||
case r.Type == "bool":
|
||||
s = fmt.Sprintf("%s = r0 != 0", r.Name)
|
||||
default:
|
||||
s = fmt.Sprintf("%s = %s(r0)", r.Name, r.Type)
|
||||
}
|
||||
if !r.ReturnsError {
|
||||
return s
|
||||
}
|
||||
return s + "\n\t" + r.useLongHandleErrorCode(r.Name)
|
||||
}
|
||||
|
||||
// Fn describes syscall function.
|
||||
type Fn struct {
|
||||
Name string
|
||||
Params []*Param
|
||||
Rets *Rets
|
||||
PrintTrace bool
|
||||
confirmproc bool
|
||||
dllname string
|
||||
dllfuncname string
|
||||
src string
|
||||
// TODO: get rid of this field and just use parameter index instead
|
||||
curTmpVarIdx int // insure tmp variables have uniq names
|
||||
}
|
||||
|
||||
// extractParams parses s to extract function parameters.
|
||||
func extractParams(s string, f *Fn) ([]*Param, error) {
|
||||
s = trim(s)
|
||||
if s == "" {
|
||||
return nil, nil
|
||||
}
|
||||
a := strings.Split(s, ",")
|
||||
ps := make([]*Param, len(a))
|
||||
for i := range ps {
|
||||
s2 := trim(a[i])
|
||||
b := strings.Split(s2, " ")
|
||||
if len(b) != 2 {
|
||||
b = strings.Split(s2, "\t")
|
||||
if len(b) != 2 {
|
||||
return nil, errors.New("Could not extract function parameter from \"" + s2 + "\"")
|
||||
}
|
||||
}
|
||||
ps[i] = &Param{
|
||||
Name: trim(b[0]),
|
||||
Type: trim(b[1]),
|
||||
fn: f,
|
||||
tmpVarIdx: -1,
|
||||
}
|
||||
}
|
||||
return ps, nil
|
||||
}
|
||||
|
||||
// extractSection extracts text out of string s starting after start
|
||||
// and ending just before end. found return value will indicate success,
|
||||
// and prefix, body and suffix will contain correspondent parts of string s.
|
||||
func extractSection(s string, start, end rune) (prefix, body, suffix string, found bool) {
|
||||
s = trim(s)
|
||||
if strings.HasPrefix(s, string(start)) {
|
||||
// no prefix
|
||||
body = s[1:]
|
||||
} else {
|
||||
a := strings.SplitN(s, string(start), 2)
|
||||
if len(a) != 2 {
|
||||
return "", "", s, false
|
||||
}
|
||||
prefix = a[0]
|
||||
body = a[1]
|
||||
}
|
||||
a := strings.SplitN(body, string(end), 2)
|
||||
if len(a) != 2 {
|
||||
return "", "", "", false
|
||||
}
|
||||
return prefix, a[0], a[1], true
|
||||
}
|
||||
|
||||
// newFn parses string s and return created function Fn.
|
||||
func newFn(s string) (*Fn, error) {
|
||||
s = trim(s)
|
||||
f := &Fn{
|
||||
Rets: &Rets{},
|
||||
src: s,
|
||||
PrintTrace: *printTraceFlag,
|
||||
}
|
||||
// function name and args
|
||||
prefix, body, s, found := extractSection(s, '(', ')')
|
||||
if !found || prefix == "" {
|
||||
return nil, errors.New("Could not extract function name and parameters from \"" + f.src + "\"")
|
||||
}
|
||||
f.Name = prefix
|
||||
var err error
|
||||
f.Params, err = extractParams(body, f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// return values
|
||||
_, body, s, found = extractSection(s, '(', ')')
|
||||
if found {
|
||||
r, err := extractParams(body, f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch len(r) {
|
||||
case 0:
|
||||
case 1:
|
||||
if r[0].IsError() {
|
||||
f.Rets.ReturnsError = true
|
||||
} else {
|
||||
f.Rets.Name = r[0].Name
|
||||
f.Rets.Type = r[0].Type
|
||||
}
|
||||
case 2:
|
||||
if !r[1].IsError() {
|
||||
return nil, errors.New("Only last windows error is allowed as second return value in \"" + f.src + "\"")
|
||||
}
|
||||
f.Rets.ReturnsError = true
|
||||
f.Rets.Name = r[0].Name
|
||||
f.Rets.Type = r[0].Type
|
||||
default:
|
||||
return nil, errors.New("Too many return values in \"" + f.src + "\"")
|
||||
}
|
||||
}
|
||||
// fail condition
|
||||
_, body, s, found = extractSection(s, '[', ']')
|
||||
if found {
|
||||
f.Rets.FailCond = body
|
||||
}
|
||||
// dll and dll function names
|
||||
s = trim(s)
|
||||
if s == "" {
|
||||
return f, nil
|
||||
}
|
||||
if !strings.HasPrefix(s, "=") {
|
||||
return nil, errors.New("Could not extract dll name from \"" + f.src + "\"")
|
||||
}
|
||||
s = trim(s[1:])
|
||||
a := strings.Split(s, ".")
|
||||
switch len(a) {
|
||||
case 1:
|
||||
f.dllfuncname = a[0]
|
||||
case 2:
|
||||
f.dllname = a[0]
|
||||
f.dllfuncname = a[1]
|
||||
default:
|
||||
return nil, errors.New("Could not extract dll name from \"" + f.src + "\"")
|
||||
}
|
||||
if f.dllfuncname[len(f.dllfuncname)-1] == '?' {
|
||||
f.confirmproc = true
|
||||
f.dllfuncname = f.dllfuncname[0 : len(f.dllfuncname)-1]
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// DLLName returns DLL name for function f.
|
||||
func (f *Fn) DLLName() string {
|
||||
if f.dllname == "" {
|
||||
return "kernel32"
|
||||
}
|
||||
return f.dllname
|
||||
}
|
||||
|
||||
// DLLName returns DLL function name for function f.
|
||||
func (f *Fn) DLLFuncName() string {
|
||||
if f.dllfuncname == "" {
|
||||
return f.Name
|
||||
}
|
||||
return f.dllfuncname
|
||||
}
|
||||
|
||||
func (f *Fn) ConfirmProc() bool {
|
||||
return f.confirmproc
|
||||
}
|
||||
|
||||
// ParamList returns source code for function f parameters.
|
||||
func (f *Fn) ParamList() string {
|
||||
return join(f.Params, func(p *Param) string { return p.Name + " " + p.Type }, ", ")
|
||||
}
|
||||
|
||||
// HelperParamList returns source code for helper function f parameters.
|
||||
func (f *Fn) HelperParamList() string {
|
||||
return join(f.Params, func(p *Param) string { return p.Name + " " + p.HelperType() }, ", ")
|
||||
}
|
||||
|
||||
// ParamPrintList returns source code of trace printing part correspondent
|
||||
// to syscall input parameters.
|
||||
func (f *Fn) ParamPrintList() string {
|
||||
return join(f.Params, func(p *Param) string { return fmt.Sprintf(`"%s=", %s, `, p.Name, p.Name) }, `", ", `)
|
||||
}
|
||||
|
||||
// ParamCount return number of syscall parameters for function f.
|
||||
func (f *Fn) ParamCount() int {
|
||||
n := 0
|
||||
for _, p := range f.Params {
|
||||
n += len(p.SyscallArgList())
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// SyscallParamCount determines which version of Syscall/Syscall6/Syscall9/...
|
||||
// to use. It returns parameter count for correspondent SyscallX function.
|
||||
func (f *Fn) SyscallParamCount() int {
|
||||
n := f.ParamCount()
|
||||
switch {
|
||||
case n <= 3:
|
||||
return 3
|
||||
case n <= 6:
|
||||
return 6
|
||||
case n <= 9:
|
||||
return 9
|
||||
case n <= 12:
|
||||
return 12
|
||||
case n <= 15:
|
||||
return 15
|
||||
default:
|
||||
panic("too many arguments to system call")
|
||||
}
|
||||
}
|
||||
|
||||
// Syscall determines which SyscallX function to use for function f.
|
||||
func (f *Fn) Syscall() string {
|
||||
c := f.SyscallParamCount()
|
||||
if c == 3 {
|
||||
return syscalldot() + "Syscall"
|
||||
}
|
||||
return syscalldot() + "Syscall" + strconv.Itoa(c)
|
||||
}
|
||||
|
||||
// SyscallParamList returns source code for SyscallX parameters for function f.
|
||||
func (f *Fn) SyscallParamList() string {
|
||||
a := make([]string, 0)
|
||||
for _, p := range f.Params {
|
||||
a = append(a, p.SyscallArgList()...)
|
||||
}
|
||||
for len(a) < f.SyscallParamCount() {
|
||||
a = append(a, "0")
|
||||
}
|
||||
return strings.Join(a, ", ")
|
||||
}
|
||||
|
||||
// HelperCallParamList returns source code of call into function f helper.
|
||||
func (f *Fn) HelperCallParamList() string {
|
||||
a := make([]string, 0, len(f.Params))
|
||||
for _, p := range f.Params {
|
||||
s := p.Name
|
||||
if p.Type == "string" {
|
||||
s = p.tmpVar()
|
||||
}
|
||||
a = append(a, s)
|
||||
}
|
||||
return strings.Join(a, ", ")
|
||||
}
|
||||
|
||||
// IsUTF16 is true, if f is W (utf16) function. It is false
|
||||
// for all A (ascii) functions.
|
||||
func (_ *Fn) IsUTF16() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// StrconvFunc returns name of Go string to OS string function for f.
|
||||
func (f *Fn) StrconvFunc() string {
|
||||
if f.IsUTF16() {
|
||||
return syscalldot() + "UTF16PtrFromString"
|
||||
}
|
||||
return syscalldot() + "BytePtrFromString"
|
||||
}
|
||||
|
||||
// StrconvType returns Go type name used for OS string for f.
|
||||
func (f *Fn) StrconvType() string {
|
||||
if f.IsUTF16() {
|
||||
return "*uint16"
|
||||
}
|
||||
return "*byte"
|
||||
}
|
||||
|
||||
// HasStringParam is true, if f has at least one string parameter.
|
||||
// Otherwise it is false.
|
||||
func (f *Fn) HasStringParam() bool {
|
||||
for _, p := range f.Params {
|
||||
if p.Type == "string" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var uniqDllFuncName = make(map[string]bool)
|
||||
|
||||
// IsNotDuplicate is true if f is not a duplicated function
|
||||
func (f *Fn) IsNotDuplicate() bool {
|
||||
funcName := f.DLLFuncName()
|
||||
if uniqDllFuncName[funcName] == false {
|
||||
uniqDllFuncName[funcName] = true
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HelperName returns name of function f helper.
|
||||
func (f *Fn) HelperName() string {
|
||||
if !f.HasStringParam() {
|
||||
return f.Name
|
||||
}
|
||||
return "_" + f.Name
|
||||
}
|
||||
|
||||
// Source files and functions.
|
||||
type Source struct {
|
||||
Funcs []*Fn
|
||||
Files []string
|
||||
StdLibImports []string
|
||||
ExternalImports []string
|
||||
}
|
||||
|
||||
func (src *Source) Import(pkg string) {
|
||||
src.StdLibImports = append(src.StdLibImports, pkg)
|
||||
sort.Strings(src.StdLibImports)
|
||||
}
|
||||
|
||||
func (src *Source) ExternalImport(pkg string) {
|
||||
src.ExternalImports = append(src.ExternalImports, pkg)
|
||||
sort.Strings(src.ExternalImports)
|
||||
}
|
||||
|
||||
// ParseFiles parses files listed in fs and extracts all syscall
|
||||
// functions listed in sys comments. It returns source files
|
||||
// and functions collection *Source if successful.
|
||||
func ParseFiles(fs []string) (*Source, error) {
|
||||
src := &Source{
|
||||
Funcs: make([]*Fn, 0),
|
||||
Files: make([]string, 0),
|
||||
StdLibImports: []string{
|
||||
"unsafe",
|
||||
},
|
||||
ExternalImports: make([]string, 0),
|
||||
}
|
||||
for _, file := range fs {
|
||||
if err := src.ParseFile(file); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return src, nil
|
||||
}
|
||||
|
||||
// DLLs return dll names for a source set src.
|
||||
func (src *Source) DLLs() []string {
|
||||
uniq := make(map[string]bool)
|
||||
r := make([]string, 0)
|
||||
for _, f := range src.Funcs {
|
||||
name := f.DLLName()
|
||||
if _, found := uniq[name]; !found {
|
||||
uniq[name] = true
|
||||
r = append(r, name)
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// ParseFile adds additional file path to a source set src.
|
||||
func (src *Source) ParseFile(path string) error {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
s := bufio.NewScanner(file)
|
||||
for s.Scan() {
|
||||
t := trim(s.Text())
|
||||
if len(t) < 7 {
|
||||
continue
|
||||
}
|
||||
if !strings.HasPrefix(t, "//sys") {
|
||||
continue
|
||||
}
|
||||
t = t[5:]
|
||||
if !(t[0] == ' ' || t[0] == '\t') {
|
||||
continue
|
||||
}
|
||||
f, err := newFn(t[1:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
src.Funcs = append(src.Funcs, f)
|
||||
}
|
||||
if err := s.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
src.Files = append(src.Files, path)
|
||||
|
||||
// get package name
|
||||
fset := token.NewFileSet()
|
||||
_, err = file.Seek(0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pkg, err := parser.ParseFile(fset, "", file, parser.PackageClauseOnly)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
packageName = pkg.Name.Name
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsStdRepo returns true if src is part of standard library.
|
||||
func (src *Source) IsStdRepo() (bool, error) {
|
||||
if len(src.Files) == 0 {
|
||||
return false, errors.New("no input files provided")
|
||||
}
|
||||
abspath, err := filepath.Abs(src.Files[0])
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
goroot := runtime.GOROOT()
|
||||
if runtime.GOOS == "windows" {
|
||||
abspath = strings.ToLower(abspath)
|
||||
goroot = strings.ToLower(goroot)
|
||||
}
|
||||
sep := string(os.PathSeparator)
|
||||
if !strings.HasSuffix(goroot, sep) {
|
||||
goroot += sep
|
||||
}
|
||||
return strings.HasPrefix(abspath, goroot), nil
|
||||
}
|
||||
|
||||
// Generate output source file from a source set src.
|
||||
func (src *Source) Generate(w io.Writer) error {
|
||||
const (
|
||||
pkgStd = iota // any package in std library
|
||||
pkgXSysWindows // x/sys/windows package
|
||||
pkgOther
|
||||
)
|
||||
isStdRepo, err := src.IsStdRepo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var pkgtype int
|
||||
switch {
|
||||
case isStdRepo:
|
||||
pkgtype = pkgStd
|
||||
case packageName == "windows":
|
||||
// TODO: this needs better logic than just using package name
|
||||
pkgtype = pkgXSysWindows
|
||||
default:
|
||||
pkgtype = pkgOther
|
||||
}
|
||||
if *systemDLL {
|
||||
switch pkgtype {
|
||||
case pkgStd:
|
||||
src.Import("internal/syscall/windows/sysdll")
|
||||
case pkgXSysWindows:
|
||||
default:
|
||||
src.ExternalImport("golang.org/x/sys/windows")
|
||||
}
|
||||
}
|
||||
if *winio {
|
||||
src.ExternalImport("github.com/Microsoft/go-winio")
|
||||
}
|
||||
if packageName != "syscall" {
|
||||
src.Import("syscall")
|
||||
}
|
||||
funcMap := template.FuncMap{
|
||||
"packagename": packagename,
|
||||
"syscalldot": syscalldot,
|
||||
"newlazydll": func(dll string) string {
|
||||
arg := "\"" + dll + ".dll\""
|
||||
if !*systemDLL {
|
||||
return syscalldot() + "NewLazyDLL(" + arg + ")"
|
||||
}
|
||||
if strings.HasPrefix(dll, "api_") || strings.HasPrefix(dll, "ext_") {
|
||||
arg = strings.Replace(arg, "_", "-", -1)
|
||||
}
|
||||
switch pkgtype {
|
||||
case pkgStd:
|
||||
return syscalldot() + "NewLazyDLL(sysdll.Add(" + arg + "))"
|
||||
case pkgXSysWindows:
|
||||
return "NewLazySystemDLL(" + arg + ")"
|
||||
default:
|
||||
return "windows.NewLazySystemDLL(" + arg + ")"
|
||||
}
|
||||
},
|
||||
}
|
||||
t := template.Must(template.New("main").Funcs(funcMap).Parse(srcTemplate))
|
||||
err = t.Execute(w, src)
|
||||
if err != nil {
|
||||
return errors.New("Failed to execute template: " + err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintf(os.Stderr, "usage: mksyscall_windows [flags] [path ...]\n")
|
||||
flag.PrintDefaults()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
if len(flag.Args()) <= 0 {
|
||||
fmt.Fprintf(os.Stderr, "no files to parse provided\n")
|
||||
usage()
|
||||
}
|
||||
|
||||
src, err := ParseFiles(flag.Args())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := src.Generate(&buf); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
data, err := format.Source(buf.Bytes())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if *filename == "" {
|
||||
_, err = os.Stdout.Write(data)
|
||||
} else {
|
||||
err = ioutil.WriteFile(*filename, data, 0644)
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: use println instead to print in the following template
|
||||
const srcTemplate = `
|
||||
|
||||
{{define "main"}}// Code generated mksyscall_windows.exe DO NOT EDIT
|
||||
|
||||
package {{packagename}}
|
||||
|
||||
import (
|
||||
{{range .StdLibImports}}"{{.}}"
|
||||
{{end}}
|
||||
|
||||
{{range .ExternalImports}}"{{.}}"
|
||||
{{end}}
|
||||
)
|
||||
|
||||
var _ unsafe.Pointer
|
||||
|
||||
// Do the interface allocations only once for common
|
||||
// Errno values.
|
||||
const (
|
||||
errnoERROR_IO_PENDING = 997
|
||||
)
|
||||
|
||||
var (
|
||||
errERROR_IO_PENDING error = {{syscalldot}}Errno(errnoERROR_IO_PENDING)
|
||||
)
|
||||
|
||||
// errnoErr returns common boxed Errno values, to prevent
|
||||
// allocations at runtime.
|
||||
func errnoErr(e {{syscalldot}}Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return nil
|
||||
case errnoERROR_IO_PENDING:
|
||||
return errERROR_IO_PENDING
|
||||
}
|
||||
// TODO: add more here, after collecting data on the common
|
||||
// error values see on Windows. (perhaps when running
|
||||
// all.bat?)
|
||||
return e
|
||||
}
|
||||
|
||||
var (
|
||||
{{template "dlls" .}}
|
||||
{{template "funcnames" .}})
|
||||
{{range .Funcs}}{{if .HasStringParam}}{{template "helperbody" .}}{{end}}{{template "funcbody" .}}{{end}}
|
||||
{{end}}
|
||||
|
||||
{{/* help functions */}}
|
||||
|
||||
{{define "dlls"}}{{range .DLLs}} mod{{.}} = {{newlazydll .}}
|
||||
{{end}}{{end}}
|
||||
|
||||
{{define "funcnames"}}{{range .Funcs}}{{if .IsNotDuplicate}} proc{{.DLLFuncName}} = mod{{.DLLName}}.NewProc("{{.DLLFuncName}}"){{end}}
|
||||
{{end}}{{end}}
|
||||
|
||||
{{define "helperbody"}}
|
||||
func {{.Name}}({{.ParamList}}) {{template "results" .}}{
|
||||
{{template "helpertmpvars" .}} return {{.HelperName}}({{.HelperCallParamList}})
|
||||
}
|
||||
{{end}}
|
||||
|
||||
{{define "funcbody"}}
|
||||
func {{.HelperName}}({{.HelperParamList}}) {{template "results" .}}{
|
||||
{{template "tmpvars" .}} {{template "syscallcheck" .}}{{template "syscall" .}}
|
||||
{{template "seterror" .}}{{template "printtrace" .}} return
|
||||
}
|
||||
{{end}}
|
||||
|
||||
{{define "helpertmpvars"}}{{range .Params}}{{if .TmpVarHelperCode}} {{.TmpVarHelperCode}}
|
||||
{{end}}{{end}}{{end}}
|
||||
|
||||
{{define "tmpvars"}}{{range .Params}}{{if .TmpVarCode}} {{.TmpVarCode}}
|
||||
{{end}}{{end}}{{end}}
|
||||
|
||||
{{define "results"}}{{if .Rets.List}}{{.Rets.List}} {{end}}{{end}}
|
||||
|
||||
{{define "syscall"}}{{.Rets.SetReturnValuesCode}}{{.Syscall}}(proc{{.DLLFuncName}}.Addr(), {{.ParamCount}}, {{.SyscallParamList}}){{end}}
|
||||
|
||||
{{define "syscallcheck"}}{{if .ConfirmProc}}if {{.Rets.ErrorVarName}} = proc{{.DLLFuncName}}.Find(); {{.Rets.ErrorVarName}} != nil {
|
||||
return
|
||||
}
|
||||
{{end}}{{end}}
|
||||
|
||||
|
||||
{{define "seterror"}}{{if .Rets.SetErrorCode}} {{.Rets.SetErrorCode}}
|
||||
{{end}}{{end}}
|
||||
|
||||
{{define "printtrace"}}{{if .PrintTrace}} print("SYSCALL: {{.Name}}(", {{.ParamPrintList}}") (", {{.Rets.PrintList}}")\n")
|
||||
{{end}}{{end}}
|
||||
|
||||
`
|
238
vendor/github.com/containernetworking/cni/libcni/api.go
generated
vendored
238
vendor/github.com/containernetworking/cni/libcni/api.go
generated
vendored
@ -25,6 +25,7 @@ import (
|
||||
|
||||
"github.com/containernetworking/cni/pkg/invoke"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/utils"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
)
|
||||
|
||||
@ -32,6 +33,10 @@ var (
|
||||
CacheDir = "/var/lib/cni"
|
||||
)
|
||||
|
||||
const (
|
||||
CNICacheV1 = "cniCacheV1"
|
||||
)
|
||||
|
||||
// A RuntimeConf holds the arguments to one invocation of a CNI plugin
|
||||
// excepting the network configuration, with the nested exception that
|
||||
// the `runtimeConfig` from the network configuration is included
|
||||
@ -48,7 +53,7 @@ type RuntimeConf struct {
|
||||
// to the plugin
|
||||
CapabilityArgs map[string]interface{}
|
||||
|
||||
// A cache directory in which to library data. Defaults to CacheDir
|
||||
// DEPRECATED. Will be removed in a future release.
|
||||
CacheDir string
|
||||
}
|
||||
|
||||
@ -70,19 +75,22 @@ type CNI interface {
|
||||
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)
|
||||
GetNetworkListCachedConfig(net *NetworkConfigList, rt *RuntimeConf) ([]byte, *RuntimeConf, error)
|
||||
|
||||
AddNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) (types.Result, error)
|
||||
CheckNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error
|
||||
DelNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error
|
||||
GetNetworkCachedResult(net *NetworkConfig, rt *RuntimeConf) (types.Result, error)
|
||||
GetNetworkCachedConfig(net *NetworkConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error)
|
||||
|
||||
ValidateNetworkList(ctx context.Context, net *NetworkConfigList) ([]string, error)
|
||||
ValidateNetwork(ctx context.Context, net *NetworkConfig) ([]string, error)
|
||||
}
|
||||
|
||||
type CNIConfig struct {
|
||||
Path []string
|
||||
exec invoke.Exec
|
||||
Path []string
|
||||
exec invoke.Exec
|
||||
cacheDir string
|
||||
}
|
||||
|
||||
// CNIConfig implements the CNI interface
|
||||
@ -92,9 +100,18 @@ var _ CNI = &CNIConfig{}
|
||||
// in the given paths and use the given exec interface to run those plugins,
|
||||
// or if the exec interface is not given, will use a default exec handler.
|
||||
func NewCNIConfig(path []string, exec invoke.Exec) *CNIConfig {
|
||||
return NewCNIConfigWithCacheDir(path, "", exec)
|
||||
}
|
||||
|
||||
// NewCNIConfigWithCacheDir returns a new CNIConfig object that will search for plugins
|
||||
// in the given paths use the given exec interface to run those plugins,
|
||||
// or if the exec interface is not given, will use a default exec handler.
|
||||
// The given cache directory will be used for temporary data storage when needed.
|
||||
func NewCNIConfigWithCacheDir(path []string, cacheDir string, exec invoke.Exec) *CNIConfig {
|
||||
return &CNIConfig{
|
||||
Path: path,
|
||||
exec: exec,
|
||||
Path: path,
|
||||
cacheDir: cacheDir,
|
||||
exec: exec,
|
||||
}
|
||||
}
|
||||
|
||||
@ -165,33 +182,122 @@ func (c *CNIConfig) ensureExec() invoke.Exec {
|
||||
return c.exec
|
||||
}
|
||||
|
||||
func getResultCacheFilePath(netName string, rt *RuntimeConf) string {
|
||||
cacheDir := rt.CacheDir
|
||||
if cacheDir == "" {
|
||||
cacheDir = CacheDir
|
||||
}
|
||||
return filepath.Join(cacheDir, "results", fmt.Sprintf("%s-%s-%s", netName, rt.ContainerID, rt.IfName))
|
||||
type cachedInfo struct {
|
||||
Kind string `json:"kind"`
|
||||
ContainerID string `json:"containerId"`
|
||||
Config []byte `json:"config"`
|
||||
IfName string `json:"ifName"`
|
||||
NetworkName string `json:"networkName"`
|
||||
CniArgs [][2]string `json:"cniArgs,omitempty"`
|
||||
CapabilityArgs map[string]interface{} `json:"capabilityArgs,omitempty"`
|
||||
RawResult map[string]interface{} `json:"result,omitempty"`
|
||||
Result types.Result `json:"-"`
|
||||
}
|
||||
|
||||
func setCachedResult(result types.Result, netName string, rt *RuntimeConf) error {
|
||||
// getCacheDir returns the cache directory in this order:
|
||||
// 1) global cacheDir from CNIConfig object
|
||||
// 2) deprecated cacheDir from RuntimeConf object
|
||||
// 3) fall back to default cache directory
|
||||
func (c *CNIConfig) getCacheDir(rt *RuntimeConf) string {
|
||||
if c.cacheDir != "" {
|
||||
return c.cacheDir
|
||||
}
|
||||
if rt.CacheDir != "" {
|
||||
return rt.CacheDir
|
||||
}
|
||||
return CacheDir
|
||||
}
|
||||
|
||||
func (c *CNIConfig) getCacheFilePath(netName string, rt *RuntimeConf) (string, error) {
|
||||
if netName == "" || rt.ContainerID == "" || rt.IfName == "" {
|
||||
return "", fmt.Errorf("cache file path requires network name (%q), container ID (%q), and interface name (%q)", netName, rt.ContainerID, rt.IfName)
|
||||
}
|
||||
return filepath.Join(c.getCacheDir(rt), "results", fmt.Sprintf("%s-%s-%s", netName, rt.ContainerID, rt.IfName)), nil
|
||||
}
|
||||
|
||||
func (c *CNIConfig) cacheAdd(result types.Result, config []byte, netName string, rt *RuntimeConf) error {
|
||||
cached := cachedInfo{
|
||||
Kind: CNICacheV1,
|
||||
ContainerID: rt.ContainerID,
|
||||
Config: config,
|
||||
IfName: rt.IfName,
|
||||
NetworkName: netName,
|
||||
CniArgs: rt.Args,
|
||||
CapabilityArgs: rt.CapabilityArgs,
|
||||
}
|
||||
|
||||
// We need to get type.Result into cachedInfo as JSON map
|
||||
// Marshal to []byte, then Unmarshal into cached.RawResult
|
||||
data, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fname := getResultCacheFilePath(netName, rt)
|
||||
|
||||
err = json.Unmarshal(data, &cached.RawResult)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newBytes, err := json.Marshal(&cached)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fname, err := c.getCacheFilePath(netName, rt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(fname), 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(fname, data, 0600)
|
||||
|
||||
return ioutil.WriteFile(fname, newBytes, 0600)
|
||||
}
|
||||
|
||||
func delCachedResult(netName string, rt *RuntimeConf) error {
|
||||
fname := getResultCacheFilePath(netName, rt)
|
||||
func (c *CNIConfig) cacheDel(netName string, rt *RuntimeConf) error {
|
||||
fname, err := c.getCacheFilePath(netName, rt)
|
||||
if err != nil {
|
||||
// Ignore error
|
||||
return nil
|
||||
}
|
||||
return os.Remove(fname)
|
||||
}
|
||||
|
||||
func getCachedResult(netName, cniVersion string, rt *RuntimeConf) (types.Result, error) {
|
||||
fname := getResultCacheFilePath(netName, rt)
|
||||
func (c *CNIConfig) getCachedConfig(netName string, rt *RuntimeConf) ([]byte, *RuntimeConf, error) {
|
||||
var bytes []byte
|
||||
|
||||
fname, err := c.getCacheFilePath(netName, rt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
bytes, err = ioutil.ReadFile(fname)
|
||||
if err != nil {
|
||||
// Ignore read errors; the cached result may not exist on-disk
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
unmarshaled := cachedInfo{}
|
||||
if err := json.Unmarshal(bytes, &unmarshaled); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to unmarshal cached network %q config: %v", netName, err)
|
||||
}
|
||||
if unmarshaled.Kind != CNICacheV1 {
|
||||
return nil, nil, fmt.Errorf("read cached network %q config has wrong kind: %v", netName, unmarshaled.Kind)
|
||||
}
|
||||
|
||||
newRt := *rt
|
||||
if unmarshaled.CniArgs != nil {
|
||||
newRt.Args = unmarshaled.CniArgs
|
||||
}
|
||||
newRt.CapabilityArgs = unmarshaled.CapabilityArgs
|
||||
|
||||
return unmarshaled.Config, &newRt, nil
|
||||
}
|
||||
|
||||
func (c *CNIConfig) getLegacyCachedResult(netName, cniVersion string, rt *RuntimeConf) (types.Result, error) {
|
||||
fname, err := c.getCacheFilePath(netName, rt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := ioutil.ReadFile(fname)
|
||||
if err != nil {
|
||||
// Ignore read errors; the cached result may not exist on-disk
|
||||
@ -222,16 +328,73 @@ func getCachedResult(netName, cniVersion string, rt *RuntimeConf) (types.Result,
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (c *CNIConfig) getCachedResult(netName, cniVersion string, rt *RuntimeConf) (types.Result, error) {
|
||||
fname, err := c.getCacheFilePath(netName, rt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fdata, err := ioutil.ReadFile(fname)
|
||||
if err != nil {
|
||||
// Ignore read errors; the cached result may not exist on-disk
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
cachedInfo := cachedInfo{}
|
||||
if err := json.Unmarshal(fdata, &cachedInfo); err != nil || cachedInfo.Kind != CNICacheV1 {
|
||||
return c.getLegacyCachedResult(netName, cniVersion, rt)
|
||||
}
|
||||
|
||||
newBytes, err := json.Marshal(&cachedInfo.RawResult)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal cached network %q config: %v", netName, err)
|
||||
}
|
||||
|
||||
// Read the version of the cached result
|
||||
decoder := version.ConfigDecoder{}
|
||||
resultCniVersion, err := decoder.Decode(newBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Ensure we can understand the result
|
||||
result, err := version.NewResult(resultCniVersion, newBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert to the config version to ensure plugins get prevResult
|
||||
// in the same version as the config. The cached result version
|
||||
// should match the config version unless the config was changed
|
||||
// while the container was running.
|
||||
result, err = result.GetAsVersion(cniVersion)
|
||||
if err != nil && resultCniVersion != cniVersion {
|
||||
return nil, fmt.Errorf("failed to convert cached result version %q to config version %q: %v", resultCniVersion, cniVersion, err)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
// GetNetworkListCachedResult returns the cached Result of the previous
|
||||
// previous AddNetworkList() operation for a network list, or an error.
|
||||
// AddNetworkList() operation for a network list, or an error.
|
||||
func (c *CNIConfig) GetNetworkListCachedResult(list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) {
|
||||
return getCachedResult(list.Name, list.CNIVersion, rt)
|
||||
return c.getCachedResult(list.Name, list.CNIVersion, rt)
|
||||
}
|
||||
|
||||
// GetNetworkCachedResult returns the cached Result of the previous
|
||||
// previous AddNetwork() operation for a network, or an error.
|
||||
// AddNetwork() operation for a network, or an error.
|
||||
func (c *CNIConfig) GetNetworkCachedResult(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) {
|
||||
return getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
|
||||
return c.getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
|
||||
}
|
||||
|
||||
// GetNetworkListCachedConfig copies the input RuntimeConf to output
|
||||
// RuntimeConf with fields updated with info from the cached Config.
|
||||
func (c *CNIConfig) GetNetworkListCachedConfig(list *NetworkConfigList, rt *RuntimeConf) ([]byte, *RuntimeConf, error) {
|
||||
return c.getCachedConfig(list.Name, rt)
|
||||
}
|
||||
|
||||
// GetNetworkCachedConfig copies the input RuntimeConf to output
|
||||
// RuntimeConf with fields updated with info from the cached Config.
|
||||
func (c *CNIConfig) GetNetworkCachedConfig(net *NetworkConfig, rt *RuntimeConf) ([]byte, *RuntimeConf, error) {
|
||||
return c.getCachedConfig(net.Network.Name, rt)
|
||||
}
|
||||
|
||||
func (c *CNIConfig) addNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (types.Result, error) {
|
||||
@ -240,6 +403,15 @@ func (c *CNIConfig) addNetwork(ctx context.Context, name, cniVersion string, net
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := utils.ValidateContainerID(rt.ContainerID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := utils.ValidateNetworkName(name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := utils.ValidateInterfaceName(rt.IfName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt)
|
||||
if err != nil {
|
||||
@ -260,7 +432,7 @@ func (c *CNIConfig) AddNetworkList(ctx context.Context, list *NetworkConfigList,
|
||||
}
|
||||
}
|
||||
|
||||
if err = setCachedResult(result, list.Name, rt); err != nil {
|
||||
if err = c.cacheAdd(result, list.Bytes, list.Name, rt); err != nil {
|
||||
return nil, fmt.Errorf("failed to set network %q cached result: %v", list.Name, err)
|
||||
}
|
||||
|
||||
@ -295,7 +467,7 @@ func (c *CNIConfig) CheckNetworkList(ctx context.Context, list *NetworkConfigLis
|
||||
return nil
|
||||
}
|
||||
|
||||
cachedResult, err := getCachedResult(list.Name, list.CNIVersion, rt)
|
||||
cachedResult, err := c.getCachedResult(list.Name, list.CNIVersion, rt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get network %q cached result: %v", list.Name, err)
|
||||
}
|
||||
@ -332,7 +504,7 @@ func (c *CNIConfig) DelNetworkList(ctx context.Context, list *NetworkConfigList,
|
||||
if gtet, err := version.GreaterThanOrEqualTo(list.CNIVersion, "0.4.0"); err != nil {
|
||||
return err
|
||||
} else if gtet {
|
||||
cachedResult, err = getCachedResult(list.Name, list.CNIVersion, rt)
|
||||
cachedResult, err = c.getCachedResult(list.Name, list.CNIVersion, rt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get network %q cached result: %v", list.Name, err)
|
||||
}
|
||||
@ -344,7 +516,7 @@ func (c *CNIConfig) DelNetworkList(ctx context.Context, list *NetworkConfigList,
|
||||
return err
|
||||
}
|
||||
}
|
||||
_ = delCachedResult(list.Name, rt)
|
||||
_ = c.cacheDel(list.Name, rt)
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -356,7 +528,7 @@ func (c *CNIConfig) AddNetwork(ctx context.Context, net *NetworkConfig, rt *Runt
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = setCachedResult(result, net.Network.Name, rt); err != nil {
|
||||
if err = c.cacheAdd(result, net.Bytes, net.Network.Name, rt); err != nil {
|
||||
return nil, fmt.Errorf("failed to set network %q cached result: %v", net.Network.Name, err)
|
||||
}
|
||||
|
||||
@ -372,7 +544,7 @@ func (c *CNIConfig) CheckNetwork(ctx context.Context, net *NetworkConfig, rt *Ru
|
||||
return fmt.Errorf("configuration version %q does not support the CHECK command", net.Network.CNIVersion)
|
||||
}
|
||||
|
||||
cachedResult, err := getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
|
||||
cachedResult, err := c.getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get network %q cached result: %v", net.Network.Name, err)
|
||||
}
|
||||
@ -387,7 +559,7 @@ func (c *CNIConfig) DelNetwork(ctx context.Context, net *NetworkConfig, rt *Runt
|
||||
if gtet, err := version.GreaterThanOrEqualTo(net.Network.CNIVersion, "0.4.0"); err != nil {
|
||||
return err
|
||||
} else if gtet {
|
||||
cachedResult, err = getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
|
||||
cachedResult, err = c.getCachedResult(net.Network.Name, net.Network.CNIVersion, rt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get network %q cached result: %v", net.Network.Name, err)
|
||||
}
|
||||
@ -396,7 +568,7 @@ func (c *CNIConfig) DelNetwork(ctx context.Context, net *NetworkConfig, rt *Runt
|
||||
if err := c.delNetwork(ctx, net.Network.Name, net.Network.CNIVersion, net, cachedResult, rt); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = delCachedResult(net.Network.Name, rt)
|
||||
_ = c.cacheDel(net.Network.Name, rt)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -455,10 +627,14 @@ func (c *CNIConfig) ValidateNetwork(ctx context.Context, net *NetworkConfig) ([]
|
||||
|
||||
// validatePlugin checks that an individual plugin's configuration is sane
|
||||
func (c *CNIConfig) validatePlugin(ctx context.Context, pluginName, expectedVersion string) error {
|
||||
pluginPath, err := invoke.FindInPath(pluginName, c.Path)
|
||||
c.ensureExec()
|
||||
pluginPath, err := c.exec.FindInPath(pluginName, c.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if expectedVersion == "" {
|
||||
expectedVersion = "0.1.0"
|
||||
}
|
||||
|
||||
vi, err := invoke.GetVersionInfo(ctx, pluginPath, c.exec)
|
||||
if err != nil {
|
||||
|
4
vendor/github.com/containernetworking/cni/libcni/conf.go
generated
vendored
4
vendor/github.com/containernetworking/cni/libcni/conf.go
generated
vendored
@ -114,11 +114,11 @@ func ConfListFromBytes(bytes []byte) (*NetworkConfigList, error) {
|
||||
for i, conf := range plugins {
|
||||
newBytes, err := json.Marshal(conf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to marshal plugin config %d: %v", i, err)
|
||||
return nil, fmt.Errorf("failed to marshal plugin config %d: %v", i, err)
|
||||
}
|
||||
netConf, err := ConfFromBytes(newBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to parse plugin config %d: %v", i, err)
|
||||
return nil, fmt.Errorf("failed to parse plugin config %d: %v", i, err)
|
||||
}
|
||||
list.Plugins = append(list.Plugins, netConf)
|
||||
}
|
||||
|
6
vendor/github.com/containernetworking/cni/pkg/invoke/args.go
generated
vendored
6
vendor/github.com/containernetworking/cni/pkg/invoke/args.go
generated
vendored
@ -32,7 +32,7 @@ type inherited struct{}
|
||||
|
||||
var inheritArgsFromEnv inherited
|
||||
|
||||
func (_ *inherited) AsEnv() []string {
|
||||
func (*inherited) AsEnv() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -60,8 +60,8 @@ func (args *Args) AsEnv() []string {
|
||||
pluginArgsStr = stringify(args.PluginArgs)
|
||||
}
|
||||
|
||||
// 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.
|
||||
// Duplicated values which come first will be overridden, so we must put the
|
||||
// custom values in the end to avoid being overridden by the process environments.
|
||||
env = append(env,
|
||||
"CNI_COMMAND="+args.Command,
|
||||
"CNI_CONTAINERID="+args.ContainerID,
|
||||
|
5
vendor/github.com/containernetworking/cni/pkg/invoke/find.go
generated
vendored
5
vendor/github.com/containernetworking/cni/pkg/invoke/find.go
generated
vendored
@ -18,6 +18,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// FindInPath returns the full path of the plugin by searching in the provided path
|
||||
@ -26,6 +27,10 @@ func FindInPath(plugin string, paths []string) (string, error) {
|
||||
return "", fmt.Errorf("no plugin name provided")
|
||||
}
|
||||
|
||||
if strings.ContainsRune(plugin, os.PathSeparator) {
|
||||
return "", fmt.Errorf("invalid plugin name: %s", plugin)
|
||||
}
|
||||
|
||||
if len(paths) == 0 {
|
||||
return "", fmt.Errorf("no paths provided")
|
||||
}
|
||||
|
52
vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec.go
generated
vendored
52
vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec.go
generated
vendored
@ -21,6 +21,8 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
)
|
||||
@ -31,30 +33,54 @@ type RawExec struct {
|
||||
|
||||
func (e *RawExec) ExecPlugin(ctx context.Context, pluginPath string, stdinData []byte, environ []string) ([]byte, error) {
|
||||
stdout := &bytes.Buffer{}
|
||||
stderr := &bytes.Buffer{}
|
||||
c := exec.CommandContext(ctx, pluginPath)
|
||||
c.Env = environ
|
||||
c.Stdin = bytes.NewBuffer(stdinData)
|
||||
c.Stdout = stdout
|
||||
c.Stderr = e.Stderr
|
||||
if err := c.Run(); err != nil {
|
||||
return nil, pluginErr(err, stdout.Bytes())
|
||||
c.Stderr = stderr
|
||||
|
||||
// Retry the command on "text file busy" errors
|
||||
for i := 0; i <= 5; i++ {
|
||||
err := c.Run()
|
||||
|
||||
// Command succeeded
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
// If the plugin is currently about to be written, then we wait a
|
||||
// second and try it again
|
||||
if strings.Contains(err.Error(), "text file busy") {
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
// All other errors except than the busy text file
|
||||
return nil, e.pluginErr(err, stdout.Bytes(), stderr.Bytes())
|
||||
}
|
||||
|
||||
// Copy stderr to caller's buffer in case plugin printed to both
|
||||
// stdout and stderr for some reason. Ignore failures as stderr is
|
||||
// only informational.
|
||||
if e.Stderr != nil && stderr.Len() > 0 {
|
||||
_, _ = stderr.WriteTo(e.Stderr)
|
||||
}
|
||||
return stdout.Bytes(), nil
|
||||
}
|
||||
|
||||
func pluginErr(err error, output []byte) error {
|
||||
if _, ok := err.(*exec.ExitError); ok {
|
||||
emsg := types.Error{}
|
||||
if len(output) == 0 {
|
||||
emsg.Msg = "netplugin failed with no error message"
|
||||
} else if perr := json.Unmarshal(output, &emsg); perr != nil {
|
||||
emsg.Msg = fmt.Sprintf("netplugin failed but error parsing its diagnostic message %q: %v", string(output), perr)
|
||||
func (e *RawExec) pluginErr(err error, stdout, stderr []byte) error {
|
||||
emsg := types.Error{}
|
||||
if len(stdout) == 0 {
|
||||
if len(stderr) == 0 {
|
||||
emsg.Msg = fmt.Sprintf("netplugin failed with no error message: %v", err)
|
||||
} else {
|
||||
emsg.Msg = fmt.Sprintf("netplugin failed: %q", string(stderr))
|
||||
}
|
||||
return &emsg
|
||||
} else if perr := json.Unmarshal(stdout, &emsg); perr != nil {
|
||||
emsg.Msg = fmt.Sprintf("netplugin failed but error parsing its diagnostic message %q: %v", string(stdout), perr)
|
||||
}
|
||||
|
||||
return err
|
||||
return &emsg
|
||||
}
|
||||
|
||||
func (e *RawExec) FindInPath(plugin string, paths []string) (string, error) {
|
||||
|
99
vendor/github.com/containernetworking/cni/pkg/skel/skel.go
generated
vendored
99
vendor/github.com/containernetworking/cni/pkg/skel/skel.go
generated
vendored
@ -27,6 +27,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/utils"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
)
|
||||
|
||||
@ -53,16 +54,7 @@ type dispatcher struct {
|
||||
|
||||
type reqForCmdEntry map[string]bool
|
||||
|
||||
// internal only error to indicate lack of required environment variables
|
||||
type missingEnvError struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (e missingEnvError) Error() string {
|
||||
return e.msg
|
||||
}
|
||||
|
||||
func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, error) {
|
||||
func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
|
||||
var cmd, contID, netns, ifName, args, path string
|
||||
|
||||
vars := []struct {
|
||||
@ -138,7 +130,7 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, error) {
|
||||
|
||||
if len(argsMissing) > 0 {
|
||||
joined := strings.Join(argsMissing, ",")
|
||||
return "", nil, missingEnvError{fmt.Sprintf("required env variables [%s] missing", joined)}
|
||||
return "", nil, types.NewError(types.ErrInvalidEnvironmentVariables, fmt.Sprintf("required env variables [%s] missing", joined), "")
|
||||
}
|
||||
|
||||
if cmd == "VERSION" {
|
||||
@ -147,7 +139,7 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, error) {
|
||||
|
||||
stdinData, err := ioutil.ReadAll(t.Stdin)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("error reading from stdin: %v", err)
|
||||
return "", nil, types.NewError(types.ErrIOFailure, fmt.Sprintf("error reading from stdin: %v", err), "")
|
||||
}
|
||||
|
||||
cmdArgs := &CmdArgs{
|
||||
@ -161,39 +153,39 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, error) {
|
||||
return cmd, cmdArgs, nil
|
||||
}
|
||||
|
||||
func createTypedError(f string, args ...interface{}) *types.Error {
|
||||
return &types.Error{
|
||||
Code: 100,
|
||||
Msg: fmt.Sprintf(f, args...),
|
||||
}
|
||||
}
|
||||
|
||||
func (t *dispatcher) checkVersionAndCall(cmdArgs *CmdArgs, pluginVersionInfo version.PluginInfo, toCall func(*CmdArgs) error) error {
|
||||
func (t *dispatcher) checkVersionAndCall(cmdArgs *CmdArgs, pluginVersionInfo version.PluginInfo, toCall func(*CmdArgs) error) *types.Error {
|
||||
configVersion, err := t.ConfVersionDecoder.Decode(cmdArgs.StdinData)
|
||||
if err != nil {
|
||||
return err
|
||||
return types.NewError(types.ErrDecodingFailure, err.Error(), "")
|
||||
}
|
||||
verErr := t.VersionReconciler.Check(configVersion, pluginVersionInfo)
|
||||
if verErr != nil {
|
||||
return &types.Error{
|
||||
Code: types.ErrIncompatibleCNIVersion,
|
||||
Msg: "incompatible CNI versions",
|
||||
Details: verErr.Details(),
|
||||
}
|
||||
return types.NewError(types.ErrIncompatibleCNIVersion, "incompatible CNI versions", verErr.Details())
|
||||
}
|
||||
|
||||
return toCall(cmdArgs)
|
||||
if err = toCall(cmdArgs); err != nil {
|
||||
if e, ok := err.(*types.Error); ok {
|
||||
// don't wrap Error in Error
|
||||
return e
|
||||
}
|
||||
return types.NewError(types.ErrInternal, err.Error(), "")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateConfig(jsonBytes []byte) error {
|
||||
func validateConfig(jsonBytes []byte) *types.Error {
|
||||
var conf struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
if err := json.Unmarshal(jsonBytes, &conf); err != nil {
|
||||
return fmt.Errorf("error reading network config: %s", err)
|
||||
return types.NewError(types.ErrDecodingFailure, fmt.Sprintf("error unmarshall network config: %v", err), "")
|
||||
}
|
||||
if conf.Name == "" {
|
||||
return fmt.Errorf("missing network name")
|
||||
return types.NewError(types.ErrInvalidNetworkConfig, "missing network name", "")
|
||||
}
|
||||
if err := utils.ValidateNetworkName(conf.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -202,17 +194,22 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error,
|
||||
cmd, cmdArgs, err := t.getCmdArgsFromEnv()
|
||||
if err != nil {
|
||||
// Print the about string to stderr when no command is set
|
||||
if _, ok := err.(missingEnvError); ok && t.Getenv("CNI_COMMAND") == "" && about != "" {
|
||||
fmt.Fprintln(t.Stderr, about)
|
||||
if err.Code == types.ErrInvalidEnvironmentVariables && t.Getenv("CNI_COMMAND") == "" && about != "" {
|
||||
_, _ = fmt.Fprintln(t.Stderr, about)
|
||||
return nil
|
||||
}
|
||||
return createTypedError(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
if cmd != "VERSION" {
|
||||
err = validateConfig(cmdArgs.StdinData)
|
||||
if err != nil {
|
||||
return createTypedError(err.Error())
|
||||
if err = validateConfig(cmdArgs.StdinData); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = utils.ValidateContainerID(cmdArgs.ContainerID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = utils.ValidateInterfaceName(cmdArgs.IfName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@ -222,45 +219,37 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error,
|
||||
case "CHECK":
|
||||
configVersion, err := t.ConfVersionDecoder.Decode(cmdArgs.StdinData)
|
||||
if err != nil {
|
||||
return createTypedError(err.Error())
|
||||
return types.NewError(types.ErrDecodingFailure, err.Error(), "")
|
||||
}
|
||||
if gtet, err := version.GreaterThanOrEqualTo(configVersion, "0.4.0"); err != nil {
|
||||
return createTypedError(err.Error())
|
||||
return types.NewError(types.ErrDecodingFailure, err.Error(), "")
|
||||
} else if !gtet {
|
||||
return &types.Error{
|
||||
Code: types.ErrIncompatibleCNIVersion,
|
||||
Msg: "config version does not allow CHECK",
|
||||
}
|
||||
return types.NewError(types.ErrIncompatibleCNIVersion, "config version does not allow CHECK", "")
|
||||
}
|
||||
for _, pluginVersion := range versionInfo.SupportedVersions() {
|
||||
gtet, err := version.GreaterThanOrEqualTo(pluginVersion, configVersion)
|
||||
if err != nil {
|
||||
return createTypedError(err.Error())
|
||||
return types.NewError(types.ErrDecodingFailure, err.Error(), "")
|
||||
} else if gtet {
|
||||
if err := t.checkVersionAndCall(cmdArgs, versionInfo, cmdCheck); err != nil {
|
||||
return createTypedError(err.Error())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return &types.Error{
|
||||
Code: types.ErrIncompatibleCNIVersion,
|
||||
Msg: "plugin version does not allow CHECK",
|
||||
}
|
||||
return types.NewError(types.ErrIncompatibleCNIVersion, "plugin version does not allow CHECK", "")
|
||||
case "DEL":
|
||||
err = t.checkVersionAndCall(cmdArgs, versionInfo, cmdDel)
|
||||
case "VERSION":
|
||||
err = versionInfo.Encode(t.Stdout)
|
||||
if err := versionInfo.Encode(t.Stdout); err != nil {
|
||||
return types.NewError(types.ErrIOFailure, err.Error(), "")
|
||||
}
|
||||
default:
|
||||
return createTypedError("unknown CNI_COMMAND: %v", cmd)
|
||||
return types.NewError(types.ErrInvalidEnvironmentVariables, fmt.Sprintf("unknown CNI_COMMAND: %v", cmd), "")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if e, ok := err.(*types.Error); ok {
|
||||
// don't wrap Error in Error
|
||||
return e
|
||||
}
|
||||
return createTypedError(err.Error())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user