Compare commits
219 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
62b36d2fbc | ||
|
|
e5fdd449dd | ||
|
|
8db5e4d41b | ||
|
|
ec8f6c99d0 | ||
|
|
7dea2a4c1b | ||
|
|
5a02c5bc61 | ||
|
|
bf8f171041 | ||
|
|
3603738c6a | ||
|
|
d8b1289098 | ||
|
|
6551165853 | ||
|
|
10a01b09ae | ||
|
|
497560f35f | ||
|
|
58dd90b996 | ||
|
|
d5efdfe1f6 | ||
|
|
05f121a406 | ||
|
|
825fbd8a95 | ||
|
|
1a30688da0 | ||
|
|
bee8d6cf30 | ||
|
|
a16232968d | ||
|
|
1880421389 | ||
|
|
a2ed3d9a69 | ||
|
|
7bcaae263f | ||
|
|
e1f955d9bf | ||
|
|
2583a0b4ad | ||
|
|
85083ea434 | ||
|
|
2290fc8d8a | ||
|
|
411d060b81 | ||
|
|
5915b49b38 | ||
|
|
c25c62742b | ||
|
|
b7ffa24326 | ||
|
|
15894b36a0 | ||
|
|
77b51f0bc9 | ||
|
|
bd63528b0b | ||
|
|
cf187287af | ||
|
|
0dff883769 | ||
|
|
d0eeb27494 | ||
|
|
e70558cbe1 | ||
|
|
0a1421a08c | ||
|
|
0f19aa2f8d | ||
|
|
e91889678b | ||
|
|
8ec6bd6a42 | ||
|
|
fc7059c1ae | ||
|
|
a96c469e62 | ||
|
|
291ab6cc84 | ||
|
|
90125f40ba | ||
|
|
23d5525ec3 | ||
|
|
fd42109a06 | ||
|
|
4bb288193c | ||
|
|
e8365e126d | ||
|
|
7e68430081 | ||
|
|
f81a529ebd | ||
|
|
630a4d8db6 | ||
|
|
3d56f7504d | ||
|
|
659a09f34e | ||
|
|
b76ace9c64 | ||
|
|
0d0dcfc02f | ||
|
|
485be65581 | ||
|
|
ca82120019 | ||
|
|
c9e1c0c1ed | ||
|
|
2d6d4b260a | ||
|
|
ad7c1d189b | ||
|
|
a069a5f1a3 | ||
|
|
ccd683e1a3 | ||
|
|
a11cb626b0 | ||
|
|
f36dbc2031 | ||
|
|
e9d511c5bc | ||
|
|
91a68d56f9 | ||
|
|
8902d2614a | ||
|
|
df9af9ab41 | ||
|
|
5e2e365291 | ||
|
|
4b68f56820 | ||
|
|
ded2f17577 | ||
|
|
57650a1e5b | ||
|
|
7ba2bcfeab | ||
|
|
3fb8dcfd4c | ||
|
|
966bbcb8a5 | ||
|
|
7d76537d4a | ||
|
|
f3b1ffc960 | ||
|
|
ce9560712e | ||
|
|
e2984e7840 | ||
|
|
eb1ff18c4c | ||
|
|
e8771b36a2 | ||
|
|
7f8ea631e5 | ||
|
|
0eddc554c0 | ||
|
|
e8a25e33cd | ||
|
|
96bd10f679 | ||
|
|
303299b7b2 | ||
|
|
d42007865a | ||
|
|
ce60e8eb3d | ||
|
|
addbcd34b4 | ||
|
|
e8c953999e | ||
|
|
13fbc4afdf | ||
|
|
545a77f4bb | ||
|
|
c204dbd47c | ||
|
|
660685a8af | ||
|
|
2b6808807f | ||
|
|
869d5ec873 | ||
|
|
93919752fb | ||
|
|
fe60fcddb8 | ||
|
|
e308211d34 | ||
|
|
29a431f1fc | ||
|
|
7d75ab66a4 | ||
|
|
d5c8b4b206 | ||
|
|
5a4085f1fa | ||
|
|
37d2ee1d5d | ||
|
|
7f9b1844b8 | ||
|
|
2753b9af8f | ||
|
|
d35c96dda6 | ||
|
|
344d343431 | ||
|
|
79b1c402c4 |
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
|
||||
49
.travis.yml
49
.travis.yml
@@ -1,49 +0,0 @@
|
||||
language: go
|
||||
sudo: required
|
||||
dist: trusty
|
||||
|
||||
go:
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
|
||||
env:
|
||||
global:
|
||||
- PATH=$GOROOT/bin:$GOPATH/bin:$PATH
|
||||
- CGO_ENABLED=0
|
||||
matrix:
|
||||
- TARGET=amd64
|
||||
- TARGET=arm
|
||||
- TARGET=arm64
|
||||
- TARGET=ppc64le
|
||||
- TARGET=s390x
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- os: windows
|
||||
env: TARGET=amd64
|
||||
go: 1.10.x
|
||||
- os: windows
|
||||
env: TARGET=amd64
|
||||
go: 1.11.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
|
||||
@@ -72,10 +72,11 @@ vagrant ssh
|
||||
# you're now in a shell in a virtual machine
|
||||
sudo su
|
||||
go get github.com/onsi/ginkgo/ginkgo
|
||||
go install github.com/containernetworking/cni/cnitool
|
||||
cd /go/src/github.com/containernetworking/plugins
|
||||
|
||||
# to run the full test suite
|
||||
./test.sh
|
||||
./test_linux.sh
|
||||
|
||||
# to focus on a particular test suite
|
||||
cd plugins/main/loopback
|
||||
|
||||
367
Godeps/Godeps.json
generated
367
Godeps/Godeps.json
generated
@@ -1,367 +0,0 @@
|
||||
{
|
||||
"ImportPath": "github.com/containernetworking/plugins",
|
||||
"GoVersion": "go1.7",
|
||||
"GodepVersion": "v80",
|
||||
"Packages": [
|
||||
"./..."
|
||||
],
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "github.com/Microsoft/go-winio",
|
||||
"Comment": "v0.4.11",
|
||||
"Rev": "97e4973ce50b2ff5f09635a57e2b88a037aae829"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Microsoft/hcsshim",
|
||||
"Comment": "v0.7.6",
|
||||
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Microsoft/hcsshim/internal/guid",
|
||||
"Comment": "v0.7.4",
|
||||
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Microsoft/hcsshim/internal/hcs",
|
||||
"Comment": "v0.7.4",
|
||||
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Microsoft/hcsshim/internal/hcserror",
|
||||
"Comment": "v0.7.4",
|
||||
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Microsoft/hcsshim/internal/hns",
|
||||
"Comment": "v0.7.4",
|
||||
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Microsoft/hcsshim/internal/interop",
|
||||
"Comment": "v0.7.4",
|
||||
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Microsoft/hcsshim/internal/longpath",
|
||||
"Comment": "v0.7.4",
|
||||
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Microsoft/hcsshim/internal/mergemaps",
|
||||
"Comment": "v0.7.4",
|
||||
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Microsoft/hcsshim/internal/safefile",
|
||||
"Comment": "v0.7.4",
|
||||
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Microsoft/hcsshim/internal/schema1",
|
||||
"Comment": "v0.7.4",
|
||||
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Microsoft/hcsshim/internal/timeout",
|
||||
"Comment": "v0.7.4",
|
||||
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Microsoft/hcsshim/internal/wclayer",
|
||||
"Comment": "v0.7.4",
|
||||
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/alexflint/go-filemutex",
|
||||
"Rev": "72bdc8eae2aef913234599b837f5dda445ca9bd9"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/buger/jsonparser",
|
||||
"Rev": "f4dd9f5a6b441265aefc1d44872a6f8c10f42b37"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/containernetworking/cni/libcni",
|
||||
"Comment": "v0.7.0",
|
||||
"Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/containernetworking/cni/pkg/invoke",
|
||||
"Comment": "v0.7.0",
|
||||
"Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/containernetworking/cni/pkg/skel",
|
||||
"Comment": "v0.7.0",
|
||||
"Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/containernetworking/cni/pkg/types",
|
||||
"Comment": "v0.7.0",
|
||||
"Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/containernetworking/cni/pkg/types/020",
|
||||
"Comment": "v0.7.0",
|
||||
"Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/containernetworking/cni/pkg/types/current",
|
||||
"Comment": "v0.7.0",
|
||||
"Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/containernetworking/cni/pkg/version",
|
||||
"Comment": "v0.7.0",
|
||||
"Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/go-iptables/iptables",
|
||||
"Comment": "v0.4.1",
|
||||
"Rev": "78b5fff24e6df8886ef8eca9411f683a884349a5"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/go-systemd/activation",
|
||||
"Comment": "v17",
|
||||
"Rev": "39ca1b05acc7ad1220e09f133283b8859a8b71ab"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/d2g/dhcp4",
|
||||
"Rev": "f0e4d29ff0231dce36e250b2ed9ff08412584bca"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/d2g/dhcp4client",
|
||||
"Rev": "bed07e1bc5b85f69c6f0fd73393aa35ec68ed892"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/d2g/dhcp4server",
|
||||
"Rev": "477b11cea4dcc56af002849238d4f9c1e093c744"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/d2g/dhcp4server/leasepool",
|
||||
"Rev": "477b11cea4dcc56af002849238d4f9c1e093c744"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/d2g/dhcp4server/leasepool/memorypool",
|
||||
"Rev": "477b11cea4dcc56af002849238d4f9c1e093c744"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/godbus/dbus",
|
||||
"Comment": "v4.1.0-6-g885f9cc",
|
||||
"Rev": "885f9cc04c9c1a6a61a2008e211d36c5737be3f5"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/j-keck/arping",
|
||||
"Rev": "2cf9dc699c5640a7e2c81403a44127bf28033600"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/juju/errors",
|
||||
"Rev": "22422dad46e14561a0854ad42497a75af9b61909"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mattn/go-shellwords",
|
||||
"Comment": "v1.0.3",
|
||||
"Rev": "02e3cf038dcea8290e44424da473dd12be796a8a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/config",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/extensions/table",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/internal/codelocation",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/internal/containernode",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/internal/failer",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/internal/leafnodes",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/internal/remote",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/internal/spec",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/internal/specrunner",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/internal/suite",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/internal/testingtproxy",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/internal/writer",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/reporters",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/reporters/stenographer",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/types",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/format",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/gbytes",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/gexec",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/internal/assertion",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/internal/asyncassertion",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/internal/oraclematcher",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/internal/testingtsupport",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/matchers",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/bipartitegraph",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/edge",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/node",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/util",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/types",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/safchain/ethtool",
|
||||
"Rev": "7ff1ba29eca231991280817541cb3903f6be15d1"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/sirupsen/logrus",
|
||||
"Comment": "v1.0.6",
|
||||
"Rev": "3e01752db0189b9157070a0e1668a620f9a85da2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/vishvananda/netlink",
|
||||
"Comment": "v1.0.0-40-g023a6da",
|
||||
"Rev": "023a6dafdcdfa7068ac83b260ab7f03cd4131aca"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/vishvananda/netlink/nl",
|
||||
"Comment": "v1.0.0-40-g023a6da",
|
||||
"Rev": "023a6dafdcdfa7068ac83b260ab7f03cd4131aca"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/vishvananda/netns",
|
||||
"Rev": "13995c7128ccc8e51e9a6bd2b551020a27180abd"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/ssh/terminal",
|
||||
"Rev": "7c1a557ab941a71c619514f229f0b27ccb0c27cf"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/bpf",
|
||||
"Rev": "49bb7cea24b1df9410e1712aa6433dae904ff66a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/internal/iana",
|
||||
"Rev": "49bb7cea24b1df9410e1712aa6433dae904ff66a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/ipv4",
|
||||
"Rev": "49bb7cea24b1df9410e1712aa6433dae904ff66a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/sys/unix",
|
||||
"Rev": "66b7b1311ac80bbafcd2daeef9a5e6e2cd1e2399"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/sys/windows",
|
||||
"Rev": "66b7b1311ac80bbafcd2daeef9a5e6e2cd1e2399"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/internal/socket",
|
||||
"Rev": "49bb7cea24b1df9410e1712aa6433dae904ff66a"
|
||||
}
|
||||
]
|
||||
}
|
||||
5
Godeps/Readme
generated
5
Godeps/Readme
generated
@@ -1,5 +0,0 @@
|
||||
This directory tree is generated automatically by godep.
|
||||
|
||||
Please do not edit.
|
||||
|
||||
See https://github.com/tools/godep for more information.
|
||||
@@ -1,8 +1,10 @@
|
||||
# Owners
|
||||
This is the official list of the CNI network plugins owners:
|
||||
- Bruce Ma <brucema19901024@gmail.com> (@mars1024)
|
||||
- Bryan Boreham <bryan@weave.works> (@bboreham)
|
||||
- Casey Callendrello <casey.callendrello@coreos.com> (@squeed)
|
||||
- Casey Callendrello <cdc@redhat.com> (@squeed)
|
||||
- Dan Williams <dcbw@redhat.com> (@dcbw)
|
||||
- Gabe Rosenhouse <grosenhouse@pivotal.io> (@rosenhouse)
|
||||
- Matt Dupre <matt@tigera.io> (@matthewdupre)
|
||||
- Stefan Junker <stefan.junker@coreos.com> (@steveeJ)
|
||||
- Michael Cambria <mcambria@redhat.com> (@mccv1r0)
|
||||
- Piotr Skarmuk <piotr.skarmuk@gmail.com> (@jellonek)
|
||||
|
||||
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.11.1.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,12 @@
|
||||
#!/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"
|
||||
|
||||
@@ -24,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,23 +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"
|
||||
|
||||
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
|
||||
|
||||
29
go.mod
Normal file
29
go.mod
Normal file
@@ -0,0 +1,29 @@
|
||||
module github.com/containernetworking/plugins
|
||||
|
||||
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.8.0
|
||||
github.com/coreos/go-iptables v0.4.5
|
||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7
|
||||
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c
|
||||
github.com/d2g/dhcp4client v1.0.0
|
||||
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/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56
|
||||
github.com/mattn/go-shellwords v1.0.3
|
||||
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 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/gemnasium/logrus-airbrake-hook.v2 v2.1.2 // indirect
|
||||
)
|
||||
109
go.sum
Normal file
109
go.sum
Normal file
@@ -0,0 +1,109 @@
|
||||
github.com/Microsoft/go-winio v0.4.11 h1:zoIOcVf0xPN1tnMVbTtEdI+P8OofVk3NObnwOQ6nK2Q=
|
||||
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||
github.com/Microsoft/hcsshim v0.8.6 h1:ZfF0+zZeYdzMIVMZHKtDKJvLHj76XCuVae/jNkjj0IA=
|
||||
github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
|
||||
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae h1:AMzIhMUqU3jMrZiTuW0zkYeKlKDAFD+DG20IoO421/Y=
|
||||
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.8.0 h1:BT9lpgGoH4jw3lFC7Odz2prU5ruiYKcgAjMCbgybcKI=
|
||||
github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
|
||||
github.com/coreos/go-iptables v0.4.5 h1:DpHb9vJrZQEFMcVLFKAAGMUVX0XoRC0ptCthinRYm38=
|
||||
github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
|
||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7 h1:u9SHYsPQNyt5tgDm3YN7+9dYrpK96E5wFilTFWIDZOM=
|
||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c h1:Xo2rK1pzOm0jO6abTPIQwbAmqBIOj132otexc1mmzFc=
|
||||
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
|
||||
github.com/d2g/dhcp4client v1.0.0 h1:suYBsYZIkSlUMEz4TAYCczKf62IA2UWC+O8+KtdOhCo=
|
||||
github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
|
||||
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5 h1:+CpLbZIeUn94m02LdEKPcgErLJ347NUwxPKs5u8ieiY=
|
||||
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=
|
||||
github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4 h1:itqmmf1PFpC4n5JW+j4BU7X4MTfVurhYRTjODoPb2Y8=
|
||||
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.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/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/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=
|
||||
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
|
||||
github.com/sirupsen/logrus v1.0.6 h1:hcP1GmhGigz/O7h1WVUM5KklBp1JoNS9FggWKdj/j3s=
|
||||
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
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 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/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/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=
|
||||
@@ -149,12 +149,12 @@ var _ = Describe("Basic PTP using cnitool", func() {
|
||||
})
|
||||
|
||||
Measure("limits traffic only on the restricted bandwith veth device", func(b Benchmarker) {
|
||||
ipRegexp := regexp.MustCompile("10\\.11\\.2\\.\\d{1,3}")
|
||||
ipRegexp := regexp.MustCompile("10\\.1[12]\\.2\\.\\d{1,3}")
|
||||
|
||||
By(fmt.Sprintf("adding %s to %s\n\n", "chained-bridge-bandwidth", contNS1.ShortName()))
|
||||
chainedBridgeBandwidthEnv.runInNS(hostNS, cnitoolBin, "add", "network-chain-test", contNS1.LongName())
|
||||
chainedBridgeIP := ipRegexp.FindString(chainedBridgeBandwidthEnv.runInNS(contNS1, "ip", "addr"))
|
||||
Expect(chainedBridgeIP).To(ContainSubstring("10.11.2."))
|
||||
Expect(chainedBridgeIP).To(ContainSubstring("10.12.2."))
|
||||
|
||||
By(fmt.Sprintf("adding %s to %s\n\n", "basic-bridge", contNS2.ShortName()))
|
||||
basicBridgeEnv.runInNS(hostNS, cnitoolBin, "add", "network-chain-test", contNS2.LongName())
|
||||
@@ -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() {
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
"plugins": [
|
||||
{
|
||||
"type": "bridge",
|
||||
"bridge": "test-bridge-0",
|
||||
"bridge": "test-bridge-1",
|
||||
"isDefaultGateway": true,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.11.2.0/24",
|
||||
"dataDir": "/tmp/foo"
|
||||
"subnet": "10.12.2.0/24",
|
||||
"dataDir": "/tmp/bar"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
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,19 +65,22 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
if n.LoopbackDSR {
|
||||
n.ApplyLoopbackDSR(&epInfo.IpAddress)
|
||||
}
|
||||
if hnsEndpoint == nil {
|
||||
hnsEndpoint = &hcsshim.HNSEndpoint{
|
||||
Name: epInfo.EndpointName,
|
||||
@@ -95,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 {
|
||||
@@ -105,26 +109,18 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
if hcnEndpoint == nil {
|
||||
routes := []hcn.Route{
|
||||
{
|
||||
NextHop: GetIpString(&epInfo.Gateway),
|
||||
DestinationPrefix: func() string {
|
||||
destinationPrefix := "0.0.0.0/0"
|
||||
if ipv6 := epInfo.Gateway.To4(); ipv6 == nil {
|
||||
destinationPrefix = "::/0"
|
||||
}
|
||||
return destinationPrefix
|
||||
}(),
|
||||
NextHop: GetIpString(&epInfo.Gateway),
|
||||
DestinationPrefix: GetDefaultDestinationPrefix(&epInfo.Gateway),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -138,6 +134,9 @@ func GenerateHcnEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcn.HostComputeEndp
|
||||
}
|
||||
ipConfigs := []hcn.IpConfig{hcnIpConfig}
|
||||
|
||||
if n.LoopbackDSR {
|
||||
n.ApplyLoopbackDSR(&epInfo.IpAddress)
|
||||
}
|
||||
hcnEndpoint = &hcn.HostComputeEndpoint{
|
||||
SchemaVersion: hcn.Version{Major: 2},
|
||||
Name: 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")
|
||||
}
|
||||
@@ -17,18 +17,29 @@ package hns
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"strings"
|
||||
|
||||
"github.com/Microsoft/hcsshim/hcn"
|
||||
"github.com/buger/jsonparser"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NetConf is the CNI spec
|
||||
type NetConf struct {
|
||||
types.NetConf
|
||||
// ApiVersion is either 1 or 2, which specifies which hns APIs to call
|
||||
ApiVersion int `json:"ApiVersion"`
|
||||
// V2 Api Policies
|
||||
HcnPolicyArgs []hcn.EndpointPolicy `json:"HcnPolicyArgs,omitempty"`
|
||||
Policies []policy `json:"policies,omitempty"`
|
||||
RuntimeConfig RuntimeConfig `json:"runtimeConfig"`
|
||||
// V1 Api Policies
|
||||
Policies []policy `json:"policies,omitempty"`
|
||||
// Options to be passed in by the runtime
|
||||
RuntimeConfig RuntimeConfig `json:"runtimeConfig"`
|
||||
// If true, adds a policy to endpoints to support loopback direct server return
|
||||
LoopbackDSR bool `json:"loopbackDSR"`
|
||||
}
|
||||
|
||||
type RuntimeDNS struct {
|
||||
@@ -36,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 {
|
||||
@@ -45,6 +64,31 @@ type policy struct {
|
||||
Value json.RawMessage `json:"value"`
|
||||
}
|
||||
|
||||
func GetDefaultDestinationPrefix(ip *net.IP) string {
|
||||
destinationPrefix := "0.0.0.0/0"
|
||||
if ipv6 := ip.To4(); ipv6 == nil {
|
||||
destinationPrefix = "::/0"
|
||||
}
|
||||
return destinationPrefix
|
||||
}
|
||||
|
||||
func (n *NetConf) ApplyLoopbackDSR(ip *net.IP) {
|
||||
value := fmt.Sprintf(`"Destinations" : ["%s"]`, ip.String())
|
||||
if n.ApiVersion == 2 {
|
||||
hcnLoopbackRoute := hcn.EndpointPolicy{
|
||||
Type: "OutBoundNAT",
|
||||
Settings: []byte(fmt.Sprintf("{%s}", value)),
|
||||
}
|
||||
n.HcnPolicyArgs = append(n.HcnPolicyArgs, hcnLoopbackRoute)
|
||||
} else {
|
||||
hnsLoopbackRoute := policy{
|
||||
Name: "EndpointPolicy",
|
||||
Value: []byte(fmt.Sprintf(`{"Type": "OutBoundNAT", %s}`, value)),
|
||||
}
|
||||
n.Policies = append(n.Policies, hnsLoopbackRoute)
|
||||
}
|
||||
}
|
||||
|
||||
// If runtime dns values are there use that else use cni conf supplied dns
|
||||
func (n *NetConf) GetDNS() types.DNS {
|
||||
dnsResult := n.DNS
|
||||
@@ -172,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 (
|
||||
@@ -60,11 +62,15 @@ func peerExists(name string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func makeVeth(name string, mtu int) (peerName string, veth netlink.Link, err error) {
|
||||
func makeVeth(name, vethPeerName string, mtu int) (peerName string, veth netlink.Link, err error) {
|
||||
for i := 0; i < 10; i++ {
|
||||
peerName, err = RandomVethName()
|
||||
if err != nil {
|
||||
return
|
||||
if vethPeerName != "" {
|
||||
peerName = vethPeerName
|
||||
} else {
|
||||
peerName, err = RandomVethName()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
veth, err = makeVethPair(name, peerName, mtu)
|
||||
@@ -73,7 +79,7 @@ func makeVeth(name string, mtu int) (peerName string, veth netlink.Link, err err
|
||||
return
|
||||
|
||||
case os.IsExist(err):
|
||||
if peerExists(peerName) {
|
||||
if peerExists(peerName) && vethPeerName == "" {
|
||||
continue
|
||||
}
|
||||
err = fmt.Errorf("container veth name provided (%v) already exists", name)
|
||||
@@ -121,12 +127,13 @@ func ifaceFromNetlinkLink(l netlink.Link) net.Interface {
|
||||
}
|
||||
}
|
||||
|
||||
// SetupVeth sets up a pair of virtual ethernet devices.
|
||||
// Call SetupVeth from inside the container netns. It will create both veth
|
||||
// SetupVethWithName sets up a pair of virtual ethernet devices.
|
||||
// Call SetupVethWithName from inside the container netns. It will create both veth
|
||||
// devices and move the host-side veth into the provided hostNS namespace.
|
||||
// On success, SetupVeth returns (hostVeth, containerVeth, nil)
|
||||
func SetupVeth(contVethName string, mtu int, hostNS ns.NetNS) (net.Interface, net.Interface, error) {
|
||||
hostVethName, contVeth, err := makeVeth(contVethName, mtu)
|
||||
// hostVethName: If hostVethName is not specified, the host-side veth name will use a random string.
|
||||
// On success, SetupVethWithName returns (hostVeth, containerVeth, nil)
|
||||
func SetupVethWithName(contVethName, hostVethName string, mtu int, hostNS ns.NetNS) (net.Interface, net.Interface, error) {
|
||||
hostVethName, contVeth, err := makeVeth(contVethName, hostVethName, mtu)
|
||||
if err != nil {
|
||||
return net.Interface{}, net.Interface{}, err
|
||||
}
|
||||
@@ -153,6 +160,9 @@ func SetupVeth(contVethName string, mtu int, hostNS ns.NetNS) (net.Interface, ne
|
||||
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 {
|
||||
@@ -161,11 +171,19 @@ func SetupVeth(contVethName string, mtu int, hostNS ns.NetNS) (net.Interface, ne
|
||||
return ifaceFromNetlinkLink(hostVeth), ifaceFromNetlinkLink(contVeth), nil
|
||||
}
|
||||
|
||||
// SetupVeth sets up a pair of virtual ethernet devices.
|
||||
// Call SetupVeth from inside the container netns. It will create both veth
|
||||
// devices and move the host-side veth into the provided hostNS namespace.
|
||||
// On success, SetupVeth returns (hostVeth, containerVeth, nil)
|
||||
func SetupVeth(contVethName string, mtu int, hostNS ns.NetNS) (net.Interface, net.Interface, error) {
|
||||
return SetupVethWithName(contVethName, "", mtu, hostNS)
|
||||
}
|
||||
|
||||
// DelLinkByName removes an interface link.
|
||||
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)
|
||||
@@ -182,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)
|
||||
|
||||
@@ -189,9 +189,8 @@ var _ = Describe("Link", func() {
|
||||
It("returns useful error", func() {
|
||||
_ = containerNetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err := ip.SetupVeth(containerVethName, mtu, hostNetNS)
|
||||
Expect(err.Error()).To(Equal("failed to move veth to host netns: file exists"))
|
||||
Expect(err.Error()).To(HavePrefix("failed to move veth to host netns: "))
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -16,10 +16,8 @@ package ipam
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/containernetworking/cni/pkg/invoke"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"os"
|
||||
)
|
||||
|
||||
func ExecAdd(plugin string, netconf []byte) (types.Result, error) {
|
||||
@@ -31,13 +29,5 @@ func ExecCheck(plugin string, netconf []byte) error {
|
||||
}
|
||||
|
||||
func ExecDel(plugin string, netconf []byte) error {
|
||||
cmd := os.Getenv("CNI_COMMAND")
|
||||
if cmd == "" {
|
||||
return fmt.Errorf("environment variable CNI_COMMAND must be specified.")
|
||||
}
|
||||
// Set CNI_COMMAND to DEL explicity. We might be deleting due to an ADD gone wrong.
|
||||
// restore CNI_COMMAND to original value upon return.
|
||||
os.Setenv("CNI_COMMAND", "DEL")
|
||||
defer os.Setenv("CNI_COMMAND", cmd)
|
||||
return invoke.DelegateDel(context.TODO(), plugin, netconf, nil)
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
})
|
||||
|
||||
60
pkg/testutils/dns.go
Normal file
60
pkg/testutils/dns.go
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright 2019 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package testutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
)
|
||||
|
||||
// TmpResolvConf will create a temporary file and write the provided DNS settings to
|
||||
// it in the resolv.conf format. It returns the path of the created temporary file or
|
||||
// an error if any occurs while creating/writing the file. It is the caller's
|
||||
// responsibility to remove the file.
|
||||
func TmpResolvConf(dnsConf types.DNS) (string, error) {
|
||||
f, err := ioutil.TempFile("", "cni_test_resolv.conf")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get temp file for CNI test resolv.conf: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
path := f.Name()
|
||||
defer func() {
|
||||
if err != nil {
|
||||
os.RemoveAll(path)
|
||||
}
|
||||
}()
|
||||
|
||||
// see "man 5 resolv.conf" for the format of resolv.conf
|
||||
var resolvConfLines []string
|
||||
for _, nameserver := range dnsConf.Nameservers {
|
||||
resolvConfLines = append(resolvConfLines, fmt.Sprintf("nameserver %s", nameserver))
|
||||
}
|
||||
resolvConfLines = append(resolvConfLines, fmt.Sprintf("domain %s", dnsConf.Domain))
|
||||
resolvConfLines = append(resolvConfLines, fmt.Sprintf("search %s", strings.Join(dnsConf.Search, " ")))
|
||||
resolvConfLines = append(resolvConfLines, fmt.Sprintf("options %s", strings.Join(dnsConf.Options, " ")))
|
||||
|
||||
resolvConf := strings.Join(resolvConfLines, "\n")
|
||||
_, err = f.Write([]byte(resolvConf))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to write temp resolv.conf for CNI test: %v", err)
|
||||
}
|
||||
|
||||
return path, err
|
||||
}
|
||||
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")))
|
||||
})
|
||||
})
|
||||
@@ -22,17 +22,36 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const nsRunDir = "/var/run/netns"
|
||||
func getNsRunDir() string {
|
||||
xdgRuntimeDir := os.Getenv("XDG_RUNTIME_DIR")
|
||||
|
||||
/// If XDG_RUNTIME_DIR is set, check if the current user owns /var/run. If
|
||||
// the owner is different, we are most likely running in a user namespace.
|
||||
// In that case use $XDG_RUNTIME_DIR/netns as runtime dir.
|
||||
if xdgRuntimeDir != "" {
|
||||
if s, err := os.Stat("/var/run"); err == nil {
|
||||
st, ok := s.Sys().(*syscall.Stat_t)
|
||||
if ok && int(st.Uid) != os.Geteuid() {
|
||||
return path.Join(xdgRuntimeDir, "netns")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "/var/run/netns"
|
||||
}
|
||||
|
||||
// Creates a new persistent (bind-mounted) network namespace and returns an object
|
||||
// representing that namespace, without switching to it.
|
||||
func NewNS() (ns.NetNS, error) {
|
||||
|
||||
nsRunDir := getNsRunDir()
|
||||
|
||||
b := make([]byte, 16)
|
||||
_, err := rand.Reader.Read(b)
|
||||
if err != nil {
|
||||
@@ -135,7 +154,7 @@ func NewNS() (ns.NetNS, error) {
|
||||
func UnmountNS(ns ns.NetNS) error {
|
||||
nsPath := ns.Path()
|
||||
// Only unmount if it's been bind-mounted (don't touch namespaces in /proc...)
|
||||
if strings.HasPrefix(nsPath, nsRunDir) {
|
||||
if strings.HasPrefix(nsPath, getNsRunDir()) {
|
||||
if err := unix.Unmount(nsPath, 0); err != nil {
|
||||
return fmt.Errorf("failed to unmount NS: at %s: %v", nsPath, err)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
121
pkg/utils/iptables.go
Normal file
121
pkg/utils/iptables.go
Normal file
@@ -0,0 +1,121 @@
|
||||
// Copyright 2017 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
)
|
||||
|
||||
const statusChainExists = 1
|
||||
|
||||
// EnsureChain idempotently creates the iptables chain. It does not
|
||||
// return an error if the chain already exists.
|
||||
func EnsureChain(ipt *iptables.IPTables, table, chain string) error {
|
||||
if ipt == nil {
|
||||
return errors.New("failed to ensure iptable chain: IPTables was nil")
|
||||
}
|
||||
exists, err := ChainExists(ipt, table, chain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list iptables chains: %v", err)
|
||||
}
|
||||
if !exists {
|
||||
err = ipt.NewChain(table, chain)
|
||||
if err != nil {
|
||||
eerr, eok := err.(*iptables.Error)
|
||||
if eok && eerr.ExitStatus() != statusChainExists {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChainExists checks whether an iptables chain exists.
|
||||
func ChainExists(ipt *iptables.IPTables, table, chain string) (bool, error) {
|
||||
if ipt == nil {
|
||||
return false, errors.New("failed to check iptable chain: IPTables was nil")
|
||||
}
|
||||
chains, err := ipt.ListChains(table)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, ch := range chains {
|
||||
if ch == chain {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// DeleteRule idempotently delete the iptables rule in the specified table/chain.
|
||||
// It does not return an error if the referring chain doesn't exist
|
||||
func DeleteRule(ipt *iptables.IPTables, table, chain string, rulespec ...string) error {
|
||||
if ipt == nil {
|
||||
return errors.New("failed to ensure iptable chain: IPTables was nil")
|
||||
}
|
||||
if err := ipt.Delete(table, chain, rulespec...); err != nil {
|
||||
eerr, eok := err.(*iptables.Error)
|
||||
switch {
|
||||
case eok && eerr.IsNotExist():
|
||||
// swallow here, the chain was already deleted
|
||||
return nil
|
||||
case eok && eerr.ExitStatus() == 2:
|
||||
// swallow here, invalid command line parameter because the referring rule is missing
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("Failed to delete referring rule %s %s: %v", table, chain, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteChain idempotently deletes the specified table/chain.
|
||||
// It does not return an errors if the chain does not exist
|
||||
func DeleteChain(ipt *iptables.IPTables, table, chain string) error {
|
||||
if ipt == nil {
|
||||
return errors.New("failed to ensure iptable chain: IPTables was nil")
|
||||
}
|
||||
|
||||
err := ipt.DeleteChain(table, chain)
|
||||
eerr, eok := err.(*iptables.Error)
|
||||
switch {
|
||||
case eok && eerr.IsNotExist():
|
||||
// swallow here, the chain was already deleted
|
||||
return nil
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// ClearChain idempotently clear the iptables rules in the specified table/chain.
|
||||
// If the chain does not exist, a new one will be created
|
||||
func ClearChain(ipt *iptables.IPTables, table, chain string) error {
|
||||
if ipt == nil {
|
||||
return errors.New("failed to ensure iptable chain: IPTables was nil")
|
||||
}
|
||||
err := ipt.ClearChain(table, chain)
|
||||
eerr, eok := err.(*iptables.Error)
|
||||
switch {
|
||||
case eok && eerr.IsNotExist():
|
||||
// swallow here, the chain was already deleted
|
||||
return EnsureChain(ipt, table, chain)
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
97
pkg/utils/iptables_test.go
Normal file
97
pkg/utils/iptables_test.go
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright 2017-2018 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/testutils"
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
const TABLE = "filter" // We'll monkey around here
|
||||
|
||||
var _ = Describe("chain tests", func() {
|
||||
var testChain string
|
||||
var ipt *iptables.IPTables
|
||||
var cleanup func()
|
||||
|
||||
BeforeEach(func() {
|
||||
|
||||
// Save a reference to the original namespace,
|
||||
// Add a new NS
|
||||
currNs, err := ns.GetCurrentNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
testNs, err := testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
testChain = fmt.Sprintf("cni-test-%d", rand.Intn(10000000))
|
||||
|
||||
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
runtime.LockOSThread()
|
||||
err = testNs.Set()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
cleanup = func() {
|
||||
if ipt == nil {
|
||||
return
|
||||
}
|
||||
ipt.ClearChain(TABLE, testChain)
|
||||
ipt.DeleteChain(TABLE, testChain)
|
||||
currNs.Set()
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
cleanup()
|
||||
})
|
||||
|
||||
Describe("EnsureChain", func() {
|
||||
It("creates chains idempotently", func() {
|
||||
err := EnsureChain(ipt, TABLE, testChain)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Create it again!
|
||||
err = EnsureChain(ipt, TABLE, testChain)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("DeleteChain", func() {
|
||||
It("delete chains idempotently", func() {
|
||||
// Create chain
|
||||
err := EnsureChain(ipt, TABLE, testChain)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Delete chain
|
||||
err = DeleteChain(ipt, TABLE, testChain)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Delete it again!
|
||||
err = DeleteChain(ipt, TABLE, testChain)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
@@ -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")
|
||||
}
|
||||
@@ -22,16 +22,22 @@ import (
|
||||
const (
|
||||
maxChainLength = 28
|
||||
chainPrefix = "CNI-"
|
||||
prefixLength = len(chainPrefix)
|
||||
)
|
||||
|
||||
// Generates a chain name to be used with iptables.
|
||||
// Ensures that the generated chain name is exactly
|
||||
// maxChainLength chars in length
|
||||
// FormatChainName generates a chain name to be used
|
||||
// with iptables. Ensures that the generated chain
|
||||
// name is exactly maxChainLength chars in length.
|
||||
func FormatChainName(name string, id string) string {
|
||||
chainBytes := sha512.Sum512([]byte(name + id))
|
||||
chain := fmt.Sprintf("%s%x", chainPrefix, chainBytes)
|
||||
return chain[:maxChainLength]
|
||||
return MustFormatChainNameWithPrefix(name, id, "")
|
||||
}
|
||||
|
||||
// MustFormatChainNameWithPrefix generates a chain name similar
|
||||
// to FormatChainName, but adds a custom prefix between
|
||||
// chainPrefix and unique identifier. Ensures that the
|
||||
// generated chain name is exactly maxChainLength chars in length.
|
||||
// Panics if the given prefix is too long.
|
||||
func MustFormatChainNameWithPrefix(name string, id string, prefix string) string {
|
||||
return MustFormatHashWithPrefix(maxChainLength, chainPrefix+prefix, name+id)
|
||||
}
|
||||
|
||||
// FormatComment returns a comment used for easier
|
||||
@@ -39,3 +45,16 @@ func FormatChainName(name string, id string) string {
|
||||
func FormatComment(name string, id string) string {
|
||||
return fmt.Sprintf("name: %q id: %q", name, id)
|
||||
}
|
||||
|
||||
const MaxHashLen = sha512.Size * 2
|
||||
|
||||
// MustFormatHashWithPrefix returns a string of given length that begins with the
|
||||
// given prefix. It is filled with entropy based on the given string toHash.
|
||||
func MustFormatHashWithPrefix(length int, prefix string, toHash string) string {
|
||||
if len(prefix) >= length || length > MaxHashLen {
|
||||
panic("invalid length")
|
||||
}
|
||||
|
||||
output := sha512.Sum512([]byte(toHash))
|
||||
return fmt.Sprintf("%s%x", prefix, output)[:length]
|
||||
}
|
||||
|
||||
@@ -15,37 +15,151 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Utils", func() {
|
||||
It("must format a short name", func() {
|
||||
chain := FormatChainName("test", "1234")
|
||||
Expect(len(chain)).To(Equal(maxChainLength))
|
||||
Expect(chain).To(Equal("CNI-2bbe0c48b91a7d1b8a6753a8"))
|
||||
Describe("FormatChainName", func() {
|
||||
It("must format a short name", func() {
|
||||
chain := FormatChainName("test", "1234")
|
||||
Expect(len(chain)).To(Equal(maxChainLength))
|
||||
Expect(chain).To(Equal("CNI-2bbe0c48b91a7d1b8a6753a8"))
|
||||
})
|
||||
|
||||
It("must truncate a long name", func() {
|
||||
chain := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
|
||||
Expect(len(chain)).To(Equal(maxChainLength))
|
||||
Expect(chain).To(Equal("CNI-374f33fe84ab0ed84dcdebe3"))
|
||||
})
|
||||
|
||||
It("must be predictable", func() {
|
||||
chain1 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
|
||||
chain2 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
|
||||
Expect(len(chain1)).To(Equal(maxChainLength))
|
||||
Expect(len(chain2)).To(Equal(maxChainLength))
|
||||
Expect(chain1).To(Equal(chain2))
|
||||
})
|
||||
|
||||
It("must change when a character changes", func() {
|
||||
chain1 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
|
||||
chain2 := FormatChainName("testalongnamethatdoesnotmakesense", "1235")
|
||||
Expect(len(chain1)).To(Equal(maxChainLength))
|
||||
Expect(len(chain2)).To(Equal(maxChainLength))
|
||||
Expect(chain1).To(Equal("CNI-374f33fe84ab0ed84dcdebe3"))
|
||||
Expect(chain1).NotTo(Equal(chain2))
|
||||
})
|
||||
})
|
||||
|
||||
It("must truncate a long name", func() {
|
||||
chain := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
|
||||
Expect(len(chain)).To(Equal(maxChainLength))
|
||||
Expect(chain).To(Equal("CNI-374f33fe84ab0ed84dcdebe3"))
|
||||
Describe("MustFormatChainNameWithPrefix", func() {
|
||||
It("generates a chain name with a prefix", func() {
|
||||
chain := MustFormatChainNameWithPrefix("test", "1234", "PREFIX-")
|
||||
Expect(len(chain)).To(Equal(maxChainLength))
|
||||
Expect(chain).To(Equal("CNI-PREFIX-2bbe0c48b91a7d1b8"))
|
||||
})
|
||||
|
||||
It("must format a short name", func() {
|
||||
chain := MustFormatChainNameWithPrefix("test", "1234", "PREFIX-")
|
||||
Expect(len(chain)).To(Equal(maxChainLength))
|
||||
Expect(chain).To(Equal("CNI-PREFIX-2bbe0c48b91a7d1b8"))
|
||||
})
|
||||
|
||||
It("must truncate a long name", func() {
|
||||
chain := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1234", "PREFIX-")
|
||||
Expect(len(chain)).To(Equal(maxChainLength))
|
||||
Expect(chain).To(Equal("CNI-PREFIX-374f33fe84ab0ed84"))
|
||||
})
|
||||
|
||||
It("must be predictable", func() {
|
||||
chain1 := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1234", "PREFIX-")
|
||||
chain2 := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1234", "PREFIX-")
|
||||
Expect(len(chain1)).To(Equal(maxChainLength))
|
||||
Expect(len(chain2)).To(Equal(maxChainLength))
|
||||
Expect(chain1).To(Equal(chain2))
|
||||
})
|
||||
|
||||
It("must change when a character changes", func() {
|
||||
chain1 := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1234", "PREFIX-")
|
||||
chain2 := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1235", "PREFIX-")
|
||||
Expect(len(chain1)).To(Equal(maxChainLength))
|
||||
Expect(len(chain2)).To(Equal(maxChainLength))
|
||||
Expect(chain1).To(Equal("CNI-PREFIX-374f33fe84ab0ed84"))
|
||||
Expect(chain1).NotTo(Equal(chain2))
|
||||
})
|
||||
|
||||
It("panics when prefix is too large", func() {
|
||||
longPrefix := strings.Repeat("PREFIX-", 4)
|
||||
Expect(func() {
|
||||
MustFormatChainNameWithPrefix("test", "1234", longPrefix)
|
||||
}).To(Panic())
|
||||
})
|
||||
})
|
||||
|
||||
It("must be predictable", func() {
|
||||
chain1 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
|
||||
chain2 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
|
||||
Expect(len(chain1)).To(Equal(maxChainLength))
|
||||
Expect(len(chain2)).To(Equal(maxChainLength))
|
||||
Expect(chain1).To(Equal(chain2))
|
||||
Describe("MustFormatHashWithPrefix", func() {
|
||||
It("always returns a string with the given prefix", func() {
|
||||
Expect(MustFormatHashWithPrefix(10, "AAA", "some string")).To(HavePrefix("AAA"))
|
||||
Expect(MustFormatHashWithPrefix(10, "foo", "some string")).To(HavePrefix("foo"))
|
||||
Expect(MustFormatHashWithPrefix(10, "bar", "some string")).To(HavePrefix("bar"))
|
||||
})
|
||||
|
||||
It("always returns a string of the given length", func() {
|
||||
Expect(MustFormatHashWithPrefix(10, "AAA", "some string")).To(HaveLen(10))
|
||||
Expect(MustFormatHashWithPrefix(15, "AAA", "some string")).To(HaveLen(15))
|
||||
Expect(MustFormatHashWithPrefix(5, "AAA", "some string")).To(HaveLen(5))
|
||||
})
|
||||
|
||||
It("is deterministic", func() {
|
||||
val1 := MustFormatHashWithPrefix(10, "AAA", "some string")
|
||||
val2 := MustFormatHashWithPrefix(10, "AAA", "some string")
|
||||
val3 := MustFormatHashWithPrefix(10, "AAA", "some string")
|
||||
Expect(val1).To(Equal(val2))
|
||||
Expect(val1).To(Equal(val3))
|
||||
})
|
||||
|
||||
It("is (nearly) perfect (injective function)", func() {
|
||||
hashes := map[string]int{}
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
name := fmt.Sprintf("string %d", i)
|
||||
hashes[MustFormatHashWithPrefix(8, "", name)]++
|
||||
}
|
||||
|
||||
for key, count := range hashes {
|
||||
Expect(count).To(Equal(1), "for key "+key+" got non-unique correspondence")
|
||||
}
|
||||
})
|
||||
|
||||
assertPanicWith := func(f func(), expectedErrorMessage string) {
|
||||
defer func() {
|
||||
Expect(recover()).To(Equal(expectedErrorMessage))
|
||||
}()
|
||||
f()
|
||||
Fail("function should have panicked but did not")
|
||||
}
|
||||
|
||||
It("panics when prefix is longer than the length", func() {
|
||||
assertPanicWith(
|
||||
func() { MustFormatHashWithPrefix(3, "AAA", "some string") },
|
||||
"invalid length",
|
||||
)
|
||||
})
|
||||
|
||||
It("panics when length is not positive", func() {
|
||||
assertPanicWith(
|
||||
func() { MustFormatHashWithPrefix(0, "", "some string") },
|
||||
"invalid length",
|
||||
)
|
||||
})
|
||||
|
||||
It("panics when length is larger than MaxLen", func() {
|
||||
assertPanicWith(
|
||||
func() { MustFormatHashWithPrefix(MaxHashLen+1, "", "some string") },
|
||||
"invalid length",
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
It("must change when a character changes", func() {
|
||||
chain1 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
|
||||
chain2 := FormatChainName("testalongnamethatdoesnotmakesense", "1235")
|
||||
Expect(len(chain1)).To(Equal(maxChainLength))
|
||||
Expect(len(chain2)).To(Equal(maxChainLength))
|
||||
Expect(chain1).To(Equal("CNI-374f33fe84ab0ed84dcdebe3"))
|
||||
Expect(chain1).NotTo(Equal(chain2))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -34,7 +34,6 @@ import (
|
||||
)
|
||||
|
||||
const listenFdsStart = 3
|
||||
const resendCount = 3
|
||||
|
||||
var errNoMoreTries = errors.New("no more tries")
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -335,8 +335,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,10 +345,16 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -40,7 +40,7 @@ func NewIPAllocator(s *RangeSet, store backend.Store, id int) *IPAllocator {
|
||||
}
|
||||
}
|
||||
|
||||
// Get alocates an IP
|
||||
// Get allocates an IP
|
||||
func (a *IPAllocator) Get(id string, ifname string, requestedIP net.IP) (*current.IPConfig, error) {
|
||||
a.store.Lock()
|
||||
defer a.store.Unlock()
|
||||
@@ -73,6 +73,17 @@ func (a *IPAllocator) Get(id string, ifname string, requestedIP net.IP) (*curren
|
||||
gw = r.Gateway
|
||||
|
||||
} else {
|
||||
// try to get allocated IPs for this given id, if exists, just return error
|
||||
// because duplicate allocation is not allowed in SPEC
|
||||
// https://github.com/containernetworking/cni/blob/master/SPEC.md
|
||||
allocatedIPs := a.store.GetByID(id, ifname)
|
||||
for _, allocatedIP := range allocatedIPs {
|
||||
// check whether the existing IP belong to this range set
|
||||
if _, err := a.rangeset.RangeFor(allocatedIP); err == nil {
|
||||
return nil, fmt.Errorf("%s has been allocated to %s, duplicate allocation is not allowed", allocatedIP.String(), id)
|
||||
}
|
||||
}
|
||||
|
||||
iter, err := a.GetIter()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -221,14 +221,14 @@ var _ = Describe("host-local ip allocator", func() {
|
||||
It("should not allocate the broadcast address", func() {
|
||||
alloc := mkalloc()
|
||||
for i := 2; i < 7; i++ {
|
||||
res, err := alloc.Get("ID", "eth0", nil)
|
||||
res, err := alloc.Get(fmt.Sprintf("ID%d", i), "eth0", nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
s := fmt.Sprintf("192.168.1.%d/29", i)
|
||||
Expect(s).To(Equal(res.Address.String()))
|
||||
fmt.Fprintln(GinkgoWriter, "got ip", res.Address.String())
|
||||
}
|
||||
|
||||
x, err := alloc.Get("ID", "eth0", nil)
|
||||
x, err := alloc.Get("ID8", "eth0", nil)
|
||||
fmt.Fprintln(GinkgoWriter, "got ip", x)
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
@@ -19,10 +19,10 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
const lastIPFilePrefix = "last_reserved_ip."
|
||||
@@ -172,6 +172,35 @@ func (s *Store) ReleaseByID(id string, ifname string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// GetByID returns the IPs which have been allocated to the specific ID
|
||||
func (s *Store) GetByID(id string, ifname string) []net.IP {
|
||||
var ips []net.IP
|
||||
|
||||
match := strings.TrimSpace(id) + LineBreak + ifname
|
||||
// matchOld for backwards compatibility
|
||||
matchOld := strings.TrimSpace(id)
|
||||
|
||||
// walk through all ips in this network to get the ones which belong to a specific ID
|
||||
_ = filepath.Walk(s.dataDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil || info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if strings.TrimSpace(string(data)) == match || strings.TrimSpace(string(data)) == matchOld {
|
||||
_, ipString := filepath.Split(path)
|
||||
if ip := net.ParseIP(ipString); ip != nil {
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return ips
|
||||
}
|
||||
|
||||
func GetEscapedPath(dataDir string, fname string) string {
|
||||
if runtime.GOOS == "windows" {
|
||||
fname = strings.Replace(fname, ":", "_", -1)
|
||||
|
||||
@@ -24,4 +24,5 @@ type Store interface {
|
||||
LastReservedIP(rangeID string) (net.IP, error)
|
||||
Release(ip net.IP) error
|
||||
ReleaseByID(id string, ifname string) error
|
||||
GetByID(id string, ifname string) []net.IP
|
||||
}
|
||||
|
||||
@@ -81,6 +81,16 @@ func (s *FakeStore) ReleaseByID(id string, ifname string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *FakeStore) GetByID(id string, ifname string) []net.IP {
|
||||
var ips []net.IP
|
||||
for k, v := range s.ipMap {
|
||||
if v == id {
|
||||
ips = append(ips, net.ParseIP(k))
|
||||
}
|
||||
}
|
||||
return ips
|
||||
}
|
||||
|
||||
func (s *FakeStore) SetIPMap(m map[string]string) {
|
||||
s.ipMap = m
|
||||
}
|
||||
|
||||
@@ -65,12 +65,11 @@ options four
|
||||
|
||||
func parse(contents string) (*types.DNS, error) {
|
||||
f, err := ioutil.TempFile("", "host_local_resolv")
|
||||
defer f.Close()
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
if _, err := f.WriteString(contents); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -250,6 +250,94 @@ var _ = Describe("host-local Operations", func() {
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("repeat allocating addresses on specific interface for same container ID with ADD", func() {
|
||||
const ifname string = "eth0"
|
||||
const nspath string = "/some/where"
|
||||
|
||||
tmpDir, err := getTmpDir()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet0",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"dataDir": "%s",
|
||||
"ranges": [
|
||||
[{ "subnet": "10.1.2.0/24" }]
|
||||
]
|
||||
}
|
||||
}`, tmpDir)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: nspath,
|
||||
IfName: ifname,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
args1 := &skel.CmdArgs{
|
||||
ContainerID: "dummy1",
|
||||
Netns: nspath,
|
||||
IfName: ifname,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
// Allocate the IP
|
||||
r0, raw, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
|
||||
|
||||
result0, err := current.GetResult(r0)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(result0.IPs)).Should(Equal(1))
|
||||
Expect(result0.IPs[0].Address.String()).Should(Equal("10.1.2.2/24"))
|
||||
|
||||
// Allocate the IP with the same container ID
|
||||
_, _, err = testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
||||
// Allocate the IP with the another container ID
|
||||
r1, raw, err := testutils.CmdAddWithArgs(args1, func() error {
|
||||
return cmdAdd(args1)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
|
||||
|
||||
result1, err := current.GetResult(r1)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(result1.IPs)).Should(Equal(1))
|
||||
Expect(result1.IPs[0].Address.String()).Should(Equal("10.1.2.3/24"))
|
||||
|
||||
// Allocate the IP with the same container ID again
|
||||
_, _, err = testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
||||
ipFilePath := filepath.Join(tmpDir, "mynet0", "10.1.2.2")
|
||||
|
||||
// Release the IPs
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
_, err = os.Stat(ipFilePath)
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
||||
err = testutils.CmdDelWithArgs(args1, func() error {
|
||||
return cmdDel(args1)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("Verify DEL works on backwards compatible allocate", func() {
|
||||
const nspath string = "/some/where"
|
||||
const ifname string = "eth0"
|
||||
|
||||
@@ -1,54 +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")
|
||||
You can find it online here: https://cni.dev/plugins/ipam/static/
|
||||
|
||||
@@ -34,6 +34,13 @@ type Net struct {
|
||||
Name string `json:"name"`
|
||||
CNIVersion string `json:"cniVersion"`
|
||||
IPAM *IPAMConfig `json:"ipam"`
|
||||
|
||||
RuntimeConfig struct {
|
||||
IPs []string `json:"ips,omitempty"`
|
||||
} `json:"runtimeConfig,omitempty"`
|
||||
Args *struct {
|
||||
A *IPAMArgs `json:"cni"`
|
||||
} `json:"args"`
|
||||
}
|
||||
|
||||
type IPAMConfig struct {
|
||||
@@ -50,6 +57,10 @@ type IPAMEnvArgs struct {
|
||||
GATEWAY types.UnmarshallableString `json:"gateway,omitempty"`
|
||||
}
|
||||
|
||||
type IPAMArgs struct {
|
||||
IPs []string `json:"ips"`
|
||||
}
|
||||
|
||||
type Address struct {
|
||||
AddressStr string `json:"address"`
|
||||
Gateway net.IP `json:"gateway,omitempty"`
|
||||
@@ -134,6 +145,65 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// load IP from CNI_ARGS
|
||||
if envArgs != "" {
|
||||
e := IPAMEnvArgs{}
|
||||
err := types.LoadArgs(envArgs, &e)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if e.IP != "" {
|
||||
for _, item := range strings.Split(string(e.IP), ",") {
|
||||
ipstr := strings.TrimSpace(item)
|
||||
|
||||
ip, subnet, err := net.ParseCIDR(ipstr)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("invalid CIDR %s: %s", ipstr, err)
|
||||
}
|
||||
|
||||
addr := Address{
|
||||
Address: net.IPNet{IP: ip, Mask: subnet.Mask},
|
||||
AddressStr: ipstr,
|
||||
}
|
||||
n.IPAM.Addresses = append(n.IPAM.Addresses, addr)
|
||||
}
|
||||
}
|
||||
|
||||
if e.GATEWAY != "" {
|
||||
for _, item := range strings.Split(string(e.GATEWAY), ",") {
|
||||
gwip := net.ParseIP(strings.TrimSpace(item))
|
||||
if gwip == nil {
|
||||
return nil, "", fmt.Errorf("invalid gateway address: %s", item)
|
||||
}
|
||||
|
||||
for i := range n.IPAM.Addresses {
|
||||
if n.IPAM.Addresses[i].Address.Contains(gwip) {
|
||||
n.IPAM.Addresses[i].Gateway = gwip
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// import address from args
|
||||
if n.Args != nil && n.Args.A != nil && len(n.Args.A.IPs) != 0 {
|
||||
// args IP overwrites IP, so clear IPAM Config
|
||||
n.IPAM.Addresses = make([]Address, 0, len(n.Args.A.IPs))
|
||||
for _, addr := range n.Args.A.IPs {
|
||||
n.IPAM.Addresses = append(n.IPAM.Addresses, Address{AddressStr: addr})
|
||||
}
|
||||
}
|
||||
|
||||
// import address from runtimeConfig
|
||||
if len(n.RuntimeConfig.IPs) != 0 {
|
||||
// runtimeConfig IP overwrites IP, so clear IPAM Config
|
||||
n.IPAM.Addresses = make([]Address, 0, len(n.RuntimeConfig.IPs))
|
||||
for _, addr := range n.RuntimeConfig.IPs {
|
||||
n.IPAM.Addresses = append(n.IPAM.Addresses, Address{AddressStr: addr})
|
||||
}
|
||||
}
|
||||
|
||||
if n.IPAM == nil {
|
||||
return nil, "", fmt.Errorf("IPAM config missing 'ipam' key")
|
||||
}
|
||||
@@ -163,50 +233,6 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if envArgs != "" {
|
||||
e := IPAMEnvArgs{}
|
||||
err := types.LoadArgs(envArgs, &e)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if e.IP != "" {
|
||||
for _, item := range strings.Split(string(e.IP), ",") {
|
||||
ipstr := strings.TrimSpace(item)
|
||||
|
||||
ip, subnet, err := net.ParseCIDR(ipstr)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("invalid CIDR %s: %s", ipstr, err)
|
||||
}
|
||||
|
||||
addr := Address{Address: net.IPNet{IP: ip, Mask: subnet.Mask}}
|
||||
if addr.Address.IP.To4() != nil {
|
||||
addr.Version = "4"
|
||||
numV4++
|
||||
} else {
|
||||
addr.Version = "6"
|
||||
numV6++
|
||||
}
|
||||
n.IPAM.Addresses = append(n.IPAM.Addresses, addr)
|
||||
}
|
||||
}
|
||||
|
||||
if e.GATEWAY != "" {
|
||||
for _, item := range strings.Split(string(e.GATEWAY), ",") {
|
||||
gwip := net.ParseIP(strings.TrimSpace(item))
|
||||
if gwip == nil {
|
||||
return nil, "", fmt.Errorf("invalid gateway address: %s", item)
|
||||
}
|
||||
|
||||
for i := range n.IPAM.Addresses {
|
||||
if n.IPAM.Addresses[i].Address.Contains(gwip) {
|
||||
n.IPAM.Addresses[i].Gateway = gwip
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CNI spec 0.2.0 and below supported only one v4 and v6 address
|
||||
if numV4 > 1 || numV6 > 1 {
|
||||
for _, v := range types020.SupportedVersions {
|
||||
|
||||
@@ -265,6 +265,221 @@ var _ = Describe("static Operations", func() {
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("allocates and releases multiple addresses with ADD/DEL, from RuntimeConfig", func() {
|
||||
const ifname string = "eth0"
|
||||
const nspath string = "/some/where"
|
||||
|
||||
conf := `{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
"capabilities": {"ips": true},
|
||||
"ipam": {
|
||||
"type": "static",
|
||||
"routes": [
|
||||
{ "dst": "0.0.0.0/0", "gw": "10.10.0.254" },
|
||||
{ "dst": "3ffe:ffff:0:01ff::1/64",
|
||||
"gw": "3ffe:ffff:0::1" } ],
|
||||
"dns": {
|
||||
"nameservers" : ["8.8.8.8"],
|
||||
"domain": "example.com",
|
||||
"search": [ "example.com" ]
|
||||
}
|
||||
},
|
||||
"RuntimeConfig": {
|
||||
"ips" : ["10.10.0.1/24", "3ffe:ffff:0:01ff::1/64"]
|
||||
}
|
||||
}`
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: nspath,
|
||||
IfName: ifname,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
// Allocate the IP
|
||||
r, raw, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
|
||||
|
||||
result, err := current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Gomega is cranky about slices with different caps
|
||||
Expect(*result.IPs[0]).To(Equal(
|
||||
current.IPConfig{
|
||||
Version: "4",
|
||||
Address: mustCIDR("10.10.0.1/24"),
|
||||
}))
|
||||
Expect(*result.IPs[1]).To(Equal(
|
||||
current.IPConfig{
|
||||
Version: "6",
|
||||
Address: mustCIDR("3ffe:ffff:0:01ff::1/64"),
|
||||
},
|
||||
))
|
||||
Expect(len(result.IPs)).To(Equal(2))
|
||||
Expect(result.Routes).To(Equal([]*types.Route{
|
||||
{Dst: mustCIDR("0.0.0.0/0"), GW: net.ParseIP("10.10.0.254")},
|
||||
{Dst: mustCIDR("3ffe:ffff:0:01ff::1/64"), GW: net.ParseIP("3ffe:ffff:0::1")},
|
||||
}))
|
||||
|
||||
// Release the IP
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("allocates and releases multiple addresses with ADD/DEL, from args", func() {
|
||||
const ifname string = "eth0"
|
||||
const nspath string = "/some/where"
|
||||
|
||||
conf := `{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
"ipam": {
|
||||
"type": "static",
|
||||
"routes": [
|
||||
{ "dst": "0.0.0.0/0", "gw": "10.10.0.254" },
|
||||
{ "dst": "3ffe:ffff:0:01ff::1/64",
|
||||
"gw": "3ffe:ffff:0::1" } ],
|
||||
"dns": {
|
||||
"nameservers" : ["8.8.8.8"],
|
||||
"domain": "example.com",
|
||||
"search": [ "example.com" ]
|
||||
}
|
||||
},
|
||||
"args": {
|
||||
"cni": {
|
||||
"ips" : ["10.10.0.1/24", "3ffe:ffff:0:01ff::1/64"]
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: nspath,
|
||||
IfName: ifname,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
// Allocate the IP
|
||||
r, raw, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
|
||||
|
||||
result, err := current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Gomega is cranky about slices with different caps
|
||||
Expect(*result.IPs[0]).To(Equal(
|
||||
current.IPConfig{
|
||||
Version: "4",
|
||||
Address: mustCIDR("10.10.0.1/24"),
|
||||
}))
|
||||
Expect(*result.IPs[1]).To(Equal(
|
||||
current.IPConfig{
|
||||
Version: "6",
|
||||
Address: mustCIDR("3ffe:ffff:0:01ff::1/64"),
|
||||
},
|
||||
))
|
||||
Expect(len(result.IPs)).To(Equal(2))
|
||||
Expect(result.Routes).To(Equal([]*types.Route{
|
||||
{Dst: mustCIDR("0.0.0.0/0"), GW: net.ParseIP("10.10.0.254")},
|
||||
{Dst: mustCIDR("3ffe:ffff:0:01ff::1/64"), GW: net.ParseIP("3ffe:ffff:0::1")},
|
||||
}))
|
||||
|
||||
// Release the IP
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("allocates and releases multiple addresses with ADD/DEL, from RuntimeConfig/ARGS/CNI_ARGS", func() {
|
||||
const ifname string = "eth0"
|
||||
const nspath string = "/some/where"
|
||||
|
||||
conf := `{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
"capabilities": {"ips": true},
|
||||
"ipam": {
|
||||
"type": "static",
|
||||
"routes": [
|
||||
{ "dst": "0.0.0.0/0", "gw": "10.10.0.254" },
|
||||
{ "dst": "3ffe:ffff:0:01ff::1/64",
|
||||
"gw": "3ffe:ffff:0::1" } ],
|
||||
"dns": {
|
||||
"nameservers" : ["8.8.8.8"],
|
||||
"domain": "example.com",
|
||||
"search": [ "example.com" ]
|
||||
}
|
||||
},
|
||||
"RuntimeConfig": {
|
||||
"ips" : ["10.10.0.1/24", "3ffe:ffff:0:01ff::1/64"]
|
||||
},
|
||||
"args": {
|
||||
"cni": {
|
||||
"ips" : ["10.10.0.2/24", "3ffe:ffff:0:01ff::2/64"]
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: nspath,
|
||||
IfName: ifname,
|
||||
StdinData: []byte(conf),
|
||||
Args: "IP=10.10.0.3/24,11.11.0.3/24;GATEWAY=10.10.0.254",
|
||||
}
|
||||
|
||||
// Allocate the IP
|
||||
r, raw, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
|
||||
|
||||
result, err := current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// only addresses in runtimeConfig configured because of its priorities
|
||||
Expect(*result.IPs[0]).To(Equal(
|
||||
current.IPConfig{
|
||||
Version: "4",
|
||||
Address: mustCIDR("10.10.0.1/24"),
|
||||
}))
|
||||
Expect(*result.IPs[1]).To(Equal(
|
||||
current.IPConfig{
|
||||
Version: "6",
|
||||
Address: mustCIDR("3ffe:ffff:0:01ff::1/64"),
|
||||
},
|
||||
))
|
||||
Expect(len(result.IPs)).To(Equal(2))
|
||||
Expect(result.Routes).To(Equal([]*types.Route{
|
||||
{Dst: mustCIDR("0.0.0.0/0"), GW: net.ParseIP("10.10.0.254")},
|
||||
{Dst: mustCIDR("3ffe:ffff:0:01ff::1/64"), GW: net.ParseIP("3ffe:ffff:0::1")},
|
||||
}))
|
||||
|
||||
// Release the IP
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
func mustCIDR(s string) net.IPNet {
|
||||
|
||||
@@ -10,3 +10,4 @@ plugins/meta/portmap
|
||||
plugins/meta/tuning
|
||||
plugins/meta/bandwidth
|
||||
plugins/meta/firewall
|
||||
plugins/meta/vrf
|
||||
|
||||
@@ -1,59 +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
|
||||
```
|
||||
{
|
||||
"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
|
||||
@@ -75,6 +77,9 @@ func loadNetConf(bytes []byte) (*NetConf, string, error) {
|
||||
if err := json.Unmarshal(bytes, n); err != nil {
|
||||
return nil, "", fmt.Errorf("failed to load netconf: %v", err)
|
||||
}
|
||||
if n.Vlan < 0 || n.Vlan > 4094 {
|
||||
return nil, "", fmt.Errorf("invalid VLAN ID %d (must be between 0 and 4094)", n.Vlan)
|
||||
}
|
||||
return n, n.CNIVersion, nil
|
||||
}
|
||||
|
||||
@@ -175,7 +180,7 @@ func ensureAddr(br netlink.Link, family int, ipn *net.IPNet, forceAddress bool)
|
||||
}
|
||||
|
||||
addr := &netlink.Addr{IPNet: ipn, Label: ""}
|
||||
if err := netlink.AddrAdd(br, addr); err != nil {
|
||||
if err := netlink.AddrAdd(br, addr); err != nil && err != syscall.EEXIST {
|
||||
return fmt.Errorf("could not add IP address to %q: %v", br.Attrs().Name, err)
|
||||
}
|
||||
|
||||
@@ -221,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)
|
||||
@@ -242,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
|
||||
}
|
||||
@@ -439,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.
|
||||
@@ -460,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)
|
||||
@@ -517,7 +550,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
chain := utils.FormatChainName(n.Name, args.ContainerID)
|
||||
comment := utils.FormatComment(n.Name, args.ContainerID)
|
||||
for _, ipc := range result.IPs {
|
||||
if err = ip.SetupIPMasq(ip.Network(&ipc.Address), chain, comment); err != nil {
|
||||
if err = ip.SetupIPMasq(&ipc.Address, chain, comment); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,15 +17,17 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/vishvananda/netlink/nl"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
"github.com/vishvananda/netlink/nl"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/020"
|
||||
types020 "github.com/containernetworking/cni/pkg/types/020"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/testutils"
|
||||
@@ -70,6 +72,7 @@ type testCase struct {
|
||||
isLayer2 bool
|
||||
expGWCIDRs []string // Expected gateway addresses in CIDR form
|
||||
vlan int
|
||||
ipMasq bool
|
||||
}
|
||||
|
||||
// Range definition for each entry in the ranges list
|
||||
@@ -105,8 +108,7 @@ const (
|
||||
"vlan": %d`
|
||||
|
||||
netDefault = `,
|
||||
"isDefaultGateway": true,
|
||||
"ipMasq": false`
|
||||
"isDefaultGateway": true`
|
||||
|
||||
ipamStartStr = `,
|
||||
"ipam": {
|
||||
@@ -115,6 +117,9 @@ const (
|
||||
ipamDataDirStr = `,
|
||||
"dataDir": "%s"`
|
||||
|
||||
ipMasqConfStr = `,
|
||||
"ipMasq": %t`
|
||||
|
||||
// Single subnet configuration (legacy)
|
||||
subnetConfStr = `,
|
||||
"subnet": "%s"`
|
||||
@@ -147,6 +152,9 @@ func (tc testCase) netConfJSON(dataDir string) string {
|
||||
if tc.vlan != 0 {
|
||||
conf += fmt.Sprintf(vlan, tc.vlan)
|
||||
}
|
||||
if tc.ipMasq {
|
||||
conf += tc.ipMasqConfig()
|
||||
}
|
||||
|
||||
if !tc.isLayer2 {
|
||||
conf += netDefault
|
||||
@@ -178,6 +186,11 @@ func (tc testCase) subnetConfig() string {
|
||||
return conf
|
||||
}
|
||||
|
||||
func (tc testCase) ipMasqConfig() string {
|
||||
conf := fmt.Sprintf(ipMasqConfStr, tc.ipMasq)
|
||||
return conf
|
||||
}
|
||||
|
||||
func (tc testCase) rangesConfig() string {
|
||||
conf := rangesStartStr
|
||||
for i, tcRange := range tc.ranges {
|
||||
@@ -763,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.
|
||||
@@ -1112,6 +1124,7 @@ var _ = Describe("bridge Operations", func() {
|
||||
AfterEach(func() {
|
||||
Expect(os.RemoveAll(dataDir)).To(Succeed())
|
||||
Expect(originalNS.Close()).To(Succeed())
|
||||
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
|
||||
})
|
||||
|
||||
It("creates a bridge", func() {
|
||||
@@ -1595,4 +1608,84 @@ var _ = Describe("bridge Operations", func() {
|
||||
cmdAddDelCheckTest(originalNS, tc, dataDir)
|
||||
}
|
||||
})
|
||||
|
||||
It("configures a bridge and ipMasq rules for 0.4.0 config", func() {
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
tc := testCase{
|
||||
ranges: []rangeInfo{{
|
||||
subnet: "10.1.2.0/24",
|
||||
}},
|
||||
ipMasq: true,
|
||||
cniVersion: "0.4.0",
|
||||
}
|
||||
|
||||
args := tc.createCmdArgs(originalNS, dataDir)
|
||||
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
result, err := current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(result.IPs).Should(HaveLen(1))
|
||||
|
||||
ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
rules, err := ipt.List("nat", "POSTROUTING")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(rules).Should(ContainElement(ContainSubstring(result.IPs[0].Address.IP.String())))
|
||||
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("check vlan id when loading net conf", func() {
|
||||
tests := []struct {
|
||||
tc testCase
|
||||
err error
|
||||
}{
|
||||
{
|
||||
tc: testCase{
|
||||
cniVersion: "0.4.0",
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
tc: testCase{
|
||||
cniVersion: "0.4.0",
|
||||
vlan: 0,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
tc: testCase{
|
||||
cniVersion: "0.4.0",
|
||||
vlan: -100,
|
||||
},
|
||||
err: fmt.Errorf("invalid VLAN ID -100 (must be between 0 and 4094)"),
|
||||
},
|
||||
{
|
||||
tc: testCase{
|
||||
cniVersion: "0.4.0",
|
||||
vlan: 5000,
|
||||
},
|
||||
err: fmt.Errorf("invalid VLAN ID 5000 (must be between 0 and 4094)"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
_, _, err := loadNetConf([]byte(test.tc.netConfJSON("")))
|
||||
if test.err == nil {
|
||||
Expect(err).To(BeNil())
|
||||
} else {
|
||||
Expect(err).To(Equal(test.err))
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,22 +1,5 @@
|
||||
# host-device
|
||||
Move an already-existing device into a container.
|
||||
|
||||
This simple plugin will move the requested device from the host's network namespace
|
||||
to the container's. Nothing else will be done - no IPAM, no addresses.
|
||||
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
|
||||
|
||||
The device can be specified with any one of three 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`
|
||||
You can find it online here: https://cni.dev/plugins/main/host-device/
|
||||
|
||||
For this plugin, `CNI_IFNAME` will be ignored. Upon DEL, the device will be moved back.
|
||||
|
||||
A sample configuration might look like:
|
||||
|
||||
```json
|
||||
{
|
||||
"cniVersion": "0.3.1",
|
||||
"type": "host-device",
|
||||
"device": "enp0s1"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
@@ -38,12 +39,20 @@ import (
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
const (
|
||||
sysBusPCI = "/sys/bus/pci/devices"
|
||||
)
|
||||
|
||||
//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
|
||||
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() {
|
||||
@@ -58,9 +67,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.Device == "" && n.HWAddr == "" && n.KernelPath == "" {
|
||||
return nil, fmt.Errorf(`specify either "device", "hwaddr" or "kernelpath"`)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -75,7 +91,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
defer containerNs.Close()
|
||||
|
||||
hostDev, err := getLink(cfg.Device, cfg.HWAddr, cfg.KernelPath)
|
||||
hostDev, err := getLink(cfg.Device, cfg.HWAddr, cfg.KernelPath, cfg.PCIAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find host device: %v", err)
|
||||
}
|
||||
@@ -177,6 +193,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)
|
||||
@@ -212,14 +232,23 @@ func moveLinkOut(containerNs ns.NetNS, ifName string) error {
|
||||
}
|
||||
|
||||
// Devices can be renamed only when down
|
||||
if err := netlink.LinkSetDown(dev); err != nil {
|
||||
if err = netlink.LinkSetDown(dev); err != nil {
|
||||
return fmt.Errorf("failed to set %q down: %v", ifName, err)
|
||||
}
|
||||
|
||||
// Rename device to it's original name
|
||||
if err := netlink.LinkSetName(dev, dev.Attrs().Alias); err != nil {
|
||||
if err = netlink.LinkSetName(dev, dev.Attrs().Alias); err != nil {
|
||||
return fmt.Errorf("failed to restore %q to original name %q: %v", ifName, dev.Attrs().Alias, err)
|
||||
}
|
||||
if err := netlink.LinkSetNsFd(dev, int(defaultNs.Fd())); err != nil {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
// if moving device to host namespace fails, we should revert device name
|
||||
// to ifName to make sure that device can be found in retries
|
||||
_ = netlink.LinkSetName(dev, ifName)
|
||||
}
|
||||
}()
|
||||
|
||||
if err = netlink.LinkSetNsFd(dev, int(defaultNs.Fd())); err != nil {
|
||||
return fmt.Errorf("failed to move %q to host netns: %v", dev.Attrs().Alias, err)
|
||||
}
|
||||
return nil
|
||||
@@ -240,7 +269,7 @@ func printLink(dev netlink.Link, cniVersion string, containerNs ns.NetNS) error
|
||||
return types.PrintResult(&result, cniVersion)
|
||||
}
|
||||
|
||||
func getLink(devname, hwaddr, kernelpath string) (netlink.Link, error) {
|
||||
func getLink(devname, hwaddr, kernelpath, pciaddr string) (netlink.Link, error) {
|
||||
links, err := netlink.LinkList()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list node links: %v", err)
|
||||
@@ -278,6 +307,24 @@ func getLink(devname, hwaddr, kernelpath string) (netlink.Link, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if len(pciaddr) > 0 {
|
||||
netDir := filepath.Join(sysBusPCI, pciaddr, "net")
|
||||
if _, err := os.Lstat(netDir); err != nil {
|
||||
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 {
|
||||
return nil, fmt.Errorf("failed to read net directory %s: %q", netDir, err)
|
||||
}
|
||||
if len(fInfo) > 0 {
|
||||
return netlink.LinkByName(fInfo[0].Name())
|
||||
}
|
||||
return nil, fmt.Errorf("failed to find device name for pci address %s", pciaddr)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to find physical interface")
|
||||
|
||||
@@ -40,6 +40,7 @@ type Net struct {
|
||||
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
|
||||
IPAM *IPAMConfig `json:"ipam,omitempty"`
|
||||
DNS types.DNS `json:"dns"`
|
||||
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
|
||||
@@ -232,14 +233,15 @@ var _ = Describe("base functionality", func() {
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
originalNS.Close()
|
||||
Expect(originalNS.Close()).To(Succeed())
|
||||
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
|
||||
})
|
||||
|
||||
It("Works with a valid config without IPAM", func() {
|
||||
var origLink netlink.Link
|
||||
|
||||
// prepare ifname in original namespace
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
@@ -253,7 +255,6 @@ var _ = Describe("base functionality", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// call CmdAdd
|
||||
targetNS, err := testutils.NewNS()
|
||||
@@ -293,26 +294,24 @@ var _ = Describe("base functionality", func() {
|
||||
}))
|
||||
|
||||
// assert that dummy0 is now in the target namespace
|
||||
err = targetNS.Do(func(ns.NetNS) error {
|
||||
_ = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
link, err := netlink.LinkByName(cniName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// assert that dummy0 is now NOT in the original namespace anymore
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
_, err := netlink.LinkByName(ifname)
|
||||
Expect(err).To(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Check that deleting the device moves it back and restores the name
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
@@ -323,15 +322,16 @@ var _ = Describe("base functionality", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
})
|
||||
|
||||
It("Works with a valid config with IPAM", func() {
|
||||
var origLink netlink.Link
|
||||
It("Test idempotence of CmdDel", func() {
|
||||
var (
|
||||
origLink netlink.Link
|
||||
conflictLink netlink.Link
|
||||
)
|
||||
|
||||
// prepare ifname in original namespace
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
// prepare host device in original namespace
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
@@ -345,8 +345,142 @@ var _ = Describe("base functionality", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
|
||||
// call CmdAdd
|
||||
targetNS, err := testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
cniName := "eth0"
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.0",
|
||||
"name": "cni-plugin-host-device-test",
|
||||
"type": "host-device",
|
||||
"device": %q
|
||||
}`, ifname)
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: cniName,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
var resI types.Result
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
var err error
|
||||
resI, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) })
|
||||
return err
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// check that the result is sane
|
||||
res, err := current.NewResultFromResult(resI)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res.Interfaces).To(Equal([]*current.Interface{
|
||||
{
|
||||
Name: cniName,
|
||||
Mac: origLink.Attrs().HardwareAddr.String(),
|
||||
Sandbox: targetNS.Path(),
|
||||
},
|
||||
}))
|
||||
|
||||
// assert that dummy0 is now in the target namespace
|
||||
_ = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
link, err := netlink.LinkByName(cniName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
|
||||
return nil
|
||||
})
|
||||
|
||||
// assert that dummy0 is now NOT in the original namespace anymore
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
_, err := netlink.LinkByName(ifname)
|
||||
Expect(err).To(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
|
||||
// create another conflict host device with same name "dummy0"
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: ifname,
|
||||
},
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
conflictLink, err = netlink.LinkByName(ifname)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = netlink.LinkSetUp(conflictLink)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
|
||||
// try to call CmdDel and fails
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).To(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
|
||||
// assert container interface "eth0" still exists in target namespace
|
||||
_ = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
link, err := netlink.LinkByName(cniName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
|
||||
return nil
|
||||
})
|
||||
|
||||
// remove the conflict host device
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err = netlink.LinkDel(conflictLink)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
|
||||
// try to call CmdDel and succeed
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
|
||||
// assert that dummy0 is now back in the original namespace
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
_, err := netlink.LinkByName(ifname)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
It("Works with a valid config with IPAM", func() {
|
||||
var origLink netlink.Link
|
||||
|
||||
// prepare ifname in original namespace
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: ifname,
|
||||
},
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
origLink, err = netlink.LinkByName(ifname)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = netlink.LinkSetUp(origLink)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
|
||||
// call CmdAdd
|
||||
targetNS, err := testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@@ -393,7 +527,7 @@ var _ = Describe("base functionality", func() {
|
||||
}))
|
||||
|
||||
// assert that dummy0 is now in the target namespace
|
||||
err = targetNS.Do(func(ns.NetNS) error {
|
||||
_ = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
link, err := netlink.LinkByName(cniName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@@ -408,19 +542,17 @@ var _ = Describe("base functionality", func() {
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// assert that dummy0 is now NOT in the original namespace anymore
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
_, err := netlink.LinkByName(ifname)
|
||||
Expect(err).To(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Check that deleting the device moves it back and restores the name
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
@@ -431,8 +563,6 @@ var _ = Describe("base functionality", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
})
|
||||
|
||||
It("fails an invalid config", func() {
|
||||
@@ -449,7 +579,7 @@ var _ = Describe("base functionality", func() {
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
_, _, err := testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) })
|
||||
Expect(err).To(MatchError(`specify either "device", "hwaddr" or "kernelpath"`))
|
||||
Expect(err).To(MatchError(`specify either "device", "hwaddr", "kernelpath" or "pciBusID"`))
|
||||
|
||||
})
|
||||
|
||||
@@ -457,7 +587,7 @@ var _ = Describe("base functionality", func() {
|
||||
var origLink netlink.Link
|
||||
|
||||
// prepare ifname in original namespace
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
@@ -471,7 +601,6 @@ var _ = Describe("base functionality", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// call CmdAdd
|
||||
targetNS, err := testutils.NewNS()
|
||||
@@ -511,23 +640,21 @@ var _ = Describe("base functionality", func() {
|
||||
}))
|
||||
|
||||
// assert that dummy0 is now in the target namespace
|
||||
err = targetNS.Do(func(ns.NetNS) error {
|
||||
_ = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
link, err := netlink.LinkByName(cniName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// assert that dummy0 is now NOT in the original namespace anymore
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
_, err := netlink.LinkByName(ifname)
|
||||
Expect(err).To(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// call CmdCheck
|
||||
n := &Net{}
|
||||
@@ -554,7 +681,7 @@ var _ = Describe("base functionality", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Check that deleting the device moves it back and restores the name
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
@@ -565,15 +692,13 @@ var _ = Describe("base functionality", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
})
|
||||
|
||||
It("Works with a valid 0.4.0 config with IPAM", func() {
|
||||
var origLink netlink.Link
|
||||
|
||||
// prepare ifname in original namespace
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
@@ -587,7 +712,6 @@ var _ = Describe("base functionality", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// call CmdAdd
|
||||
targetNS, err := testutils.NewNS()
|
||||
@@ -635,7 +759,7 @@ var _ = Describe("base functionality", func() {
|
||||
}))
|
||||
|
||||
// assert that dummy0 is now in the target namespace
|
||||
err = targetNS.Do(func(ns.NetNS) error {
|
||||
_ = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
link, err := netlink.LinkByName(cniName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@@ -650,16 +774,14 @@ var _ = Describe("base functionality", func() {
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// assert that dummy0 is now NOT in the original namespace anymore
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
_, err := netlink.LinkByName(ifname)
|
||||
Expect(err).To(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// call CmdCheck
|
||||
n := &Net{}
|
||||
@@ -689,7 +811,7 @@ var _ = Describe("base functionality", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Check that deleting the device moves it back and restores the name
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
@@ -700,8 +822,144 @@ var _ = Describe("base functionality", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
})
|
||||
|
||||
It("Test idempotence of CmdDel with 0.4.0 config", func() {
|
||||
var (
|
||||
origLink netlink.Link
|
||||
conflictLink netlink.Link
|
||||
)
|
||||
|
||||
// prepare host device in original namespace
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: ifname,
|
||||
},
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
origLink, err = netlink.LinkByName(ifname)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = netlink.LinkSetUp(origLink)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
|
||||
// call CmdAdd
|
||||
targetNS, err := testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
cniName := "eth0"
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "cni-plugin-host-device-test",
|
||||
"type": "host-device",
|
||||
"device": %q
|
||||
}`, ifname)
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: cniName,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
var resI types.Result
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
var err error
|
||||
resI, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) })
|
||||
return err
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// check that the result is sane
|
||||
res, err := current.NewResultFromResult(resI)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res.Interfaces).To(Equal([]*current.Interface{
|
||||
{
|
||||
Name: cniName,
|
||||
Mac: origLink.Attrs().HardwareAddr.String(),
|
||||
Sandbox: targetNS.Path(),
|
||||
},
|
||||
}))
|
||||
|
||||
// assert that dummy0 is now in the target namespace
|
||||
_ = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
link, err := netlink.LinkByName(cniName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
|
||||
return nil
|
||||
})
|
||||
|
||||
// assert that dummy0 is now NOT in the original namespace anymore
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
_, err := netlink.LinkByName(ifname)
|
||||
Expect(err).To(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
|
||||
// create another conflict host device with same name "dummy0"
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: ifname,
|
||||
},
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
conflictLink, err = netlink.LinkByName(ifname)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = netlink.LinkSetUp(conflictLink)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
|
||||
// try to call CmdDel and fails
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).To(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
|
||||
// assert container interface "eth0" still exists in target namespace
|
||||
err = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
link, err := netlink.LinkByName(cniName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// remove the conflict host device
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err = netlink.LinkDel(conflictLink)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
|
||||
// try to call CmdDel and succeed
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
|
||||
// assert that dummy0 is now back in the original namespace
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
_, err := netlink.LinkByName(ifname)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return 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()
|
||||
|
||||
@@ -297,6 +297,7 @@ var _ = Describe("ipvlan Operations", func() {
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(originalNS.Close()).To(Succeed())
|
||||
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
|
||||
})
|
||||
|
||||
It("creates an ipvlan link in a non-default namespace", func() {
|
||||
@@ -464,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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -15,9 +15,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
@@ -25,9 +31,34 @@ import (
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
func parseNetConf(bytes []byte) (*types.NetConf, error) {
|
||||
conf := &types.NetConf{}
|
||||
if err := json.Unmarshal(bytes, conf); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse network config: %v", err)
|
||||
}
|
||||
|
||||
if conf.RawPrevResult != nil {
|
||||
if err := version.ParsePrevResult(conf); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse prevResult: %v", err)
|
||||
}
|
||||
if _, err := current.NewResultFromResult(conf.PrevResult); err != nil {
|
||||
return nil, fmt.Errorf("failed to convert result to current version: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
func cmdAdd(args *skel.CmdArgs) error {
|
||||
conf, err := parseNetConf(args.StdinData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var v4Addr, v6Addr *net.IPNet
|
||||
|
||||
args.IfName = "lo" // ignore config, this only works for loopback
|
||||
err := ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||
link, err := netlink.LinkByName(args.IfName)
|
||||
if err != nil {
|
||||
return err // not tested
|
||||
@@ -38,14 +69,69 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
return err // not tested
|
||||
}
|
||||
|
||||
v4Addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
|
||||
if err != nil {
|
||||
return err // not tested
|
||||
}
|
||||
if len(v4Addrs) != 0 {
|
||||
v4Addr = v4Addrs[0].IPNet
|
||||
// sanity check that this is a loopback address
|
||||
for _, addr := range v4Addrs {
|
||||
if !addr.IP.IsLoopback() {
|
||||
return fmt.Errorf("loopback interface found with non-loopback address %q", addr.IP)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
v6Addrs, err := netlink.AddrList(link, netlink.FAMILY_V6)
|
||||
if err != nil {
|
||||
return err // not tested
|
||||
}
|
||||
if len(v6Addrs) != 0 {
|
||||
v6Addr = v6Addrs[0].IPNet
|
||||
// sanity check that this is a loopback address
|
||||
for _, addr := range v6Addrs {
|
||||
if !addr.IP.IsLoopback() {
|
||||
return fmt.Errorf("loopback interface found with non-loopback address %q", addr.IP)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err // not tested
|
||||
}
|
||||
|
||||
result := current.Result{}
|
||||
return result.Print()
|
||||
var result types.Result
|
||||
if conf.PrevResult != nil {
|
||||
// If loopback has previous result which passes from previous CNI plugin,
|
||||
// loopback should pass it transparently
|
||||
result = conf.PrevResult
|
||||
} else {
|
||||
loopbackInterface := ¤t.Interface{Name: args.IfName, Mac: "00:00:00:00:00:00", Sandbox: args.Netns}
|
||||
r := ¤t.Result{CNIVersion: conf.CNIVersion, Interfaces: []*current.Interface{loopbackInterface}}
|
||||
|
||||
if v4Addr != nil {
|
||||
r.IPs = append(r.IPs, ¤t.IPConfig{
|
||||
Version: "4",
|
||||
Interface: current.Int(0),
|
||||
Address: *v4Addr,
|
||||
})
|
||||
}
|
||||
|
||||
if v6Addr != nil {
|
||||
r.IPs = append(r.IPs, ¤t.IPConfig{
|
||||
Version: "6",
|
||||
Interface: current.Int(0),
|
||||
Address: *v6Addr,
|
||||
})
|
||||
}
|
||||
|
||||
result = r
|
||||
}
|
||||
|
||||
return types.PrintResult(result, conf.CNIVersion)
|
||||
}
|
||||
|
||||
func cmdDel(args *skel.CmdArgs) error {
|
||||
@@ -78,6 +164,18 @@ func main() {
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
// TODO: implement
|
||||
return nil
|
||||
args.IfName = "lo" // ignore config, this only works for loopback
|
||||
|
||||
return ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||
link, err := netlink.LinkByName(args.IfName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if link.Attrs().Flags&net.FlagUp != net.FlagUp {
|
||||
return errors.New("loopback interface is down")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -45,21 +45,20 @@ 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"),
|
||||
}
|
||||
command.Stdin = strings.NewReader(`{ "cniVersion": "0.1.0" }`)
|
||||
command.Stdin = strings.NewReader(`{ "name": "loopback-test", "cniVersion": "0.1.0" }`)
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(networkNS.Close()).To(Succeed())
|
||||
Expect(testutils.UnmountNS(networkNS)).To(Succeed())
|
||||
})
|
||||
|
||||
Context("when given a network namespace", func() {
|
||||
It("sets the lo device to UP", func() {
|
||||
|
||||
Skip("TODO: add network name")
|
||||
command.Env = append(environ, fmt.Sprintf("CNI_COMMAND=%s", "ADD"))
|
||||
|
||||
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
|
||||
@@ -80,8 +79,6 @@ var _ = Describe("Loopback", func() {
|
||||
})
|
||||
|
||||
It("sets the lo device to DOWN", func() {
|
||||
|
||||
Skip("TODO: add network name")
|
||||
command.Env = append(environ, fmt.Sprintf("CNI_COMMAND=%s", "DEL"))
|
||||
|
||||
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
|
||||
|
||||
@@ -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 interace.
|
||||
* `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.
|
||||
* `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)
|
||||
@@ -85,9 +96,43 @@ func loadConf(bytes []byte) (*NetConf, string, error) {
|
||||
}
|
||||
n.Master = defaultRouteInterface
|
||||
}
|
||||
|
||||
// check existing and MTU of master interface
|
||||
masterMTU, err := getMTUByName(n.Master)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if n.MTU < 0 || n.MTU > masterMTU {
|
||||
return nil, "", fmt.Errorf("invalid MTU %d, must be [0, master MTU(%d)]", n.MTU, masterMTU)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func getMTUByName(ifName string) (int, error) {
|
||||
link, err := netlink.LinkByName(ifName)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return link.Attrs().MTU, nil
|
||||
}
|
||||
|
||||
func modeFromString(s string) (netlink.MacvlanMode, error) {
|
||||
switch s {
|
||||
case "", "bridge":
|
||||
@@ -138,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 {
|
||||
@@ -186,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
|
||||
}
|
||||
@@ -293,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
|
||||
}
|
||||
@@ -331,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
|
||||
}
|
||||
|
||||
@@ -123,6 +123,7 @@ var _ = Describe("macvlan Operations", func() {
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(originalNS.Close()).To(Succeed())
|
||||
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
|
||||
})
|
||||
|
||||
It("creates an macvlan link in a non-default namespace", func() {
|
||||
@@ -593,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
|
||||
}
|
||||
@@ -247,12 +247,23 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
}
|
||||
|
||||
result.DNS = conf.DNS
|
||||
result.Interfaces = []*current.Interface{hostInterface, containerInterface}
|
||||
// Only override the DNS settings in the previous result if any DNS fields
|
||||
// were provided to the ptp plugin. This allows, for example, IPAM plugins
|
||||
// to specify the DNS settings instead of the ptp plugin.
|
||||
if dnsConfSet(conf.DNS) {
|
||||
result.DNS = conf.DNS
|
||||
}
|
||||
|
||||
return types.PrintResult(result, conf.CNIVersion)
|
||||
}
|
||||
|
||||
func dnsConfSet(dnsConf types.DNS) bool {
|
||||
return dnsConf.Nameservers != nil ||
|
||||
dnsConf.Search != nil ||
|
||||
dnsConf.Options != nil ||
|
||||
dnsConf.Domain != ""
|
||||
}
|
||||
|
||||
func cmdDel(args *skel.CmdArgs) error {
|
||||
conf := NetConf{}
|
||||
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
|
||||
|
||||
@@ -17,6 +17,7 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
@@ -98,9 +99,10 @@ var _ = Describe("ptp Operations", func() {
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(originalNS.Close()).To(Succeed())
|
||||
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
|
||||
})
|
||||
|
||||
doTest := func(conf string, numIPs int) {
|
||||
doTest := func(conf string, numIPs int, expectedDNSConf types.DNS) {
|
||||
const IFNAME = "ptp0"
|
||||
|
||||
targetNs, err := testutils.NewNS()
|
||||
@@ -175,6 +177,9 @@ var _ = Describe("ptp Operations", func() {
|
||||
Expect(res.Interfaces[1].Mac).To(Equal(wantMac))
|
||||
Expect(res.Interfaces[1].Sandbox).To(Equal(targetNs.Path()))
|
||||
|
||||
// make sure DNS is correct
|
||||
Expect(res.DNS).To(Equal(expectedDNSConf))
|
||||
|
||||
// Call the plugins with the DEL command, deleting the veth endpoints
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
@@ -327,7 +332,16 @@ var _ = Describe("ptp Operations", func() {
|
||||
}
|
||||
|
||||
It("configures and deconfigures a ptp link with ADD/DEL", func() {
|
||||
conf := `{
|
||||
dnsConf := types.DNS{
|
||||
Nameservers: []string{"10.1.2.123"},
|
||||
Domain: "some.domain.test",
|
||||
Search: []string{"search.test"},
|
||||
Options: []string{"option1:foo"},
|
||||
}
|
||||
dnsConfBytes, err := json.Marshal(dnsConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"type": "ptp",
|
||||
@@ -336,10 +350,11 @@ var _ = Describe("ptp Operations", func() {
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.2.0/24"
|
||||
}
|
||||
}`
|
||||
},
|
||||
"dns": %s
|
||||
}`, string(dnsConfBytes))
|
||||
|
||||
doTest(conf, 1)
|
||||
doTest(conf, 1, dnsConf)
|
||||
})
|
||||
|
||||
It("configures and deconfigures a dual-stack ptp link with ADD/DEL", func() {
|
||||
@@ -358,7 +373,112 @@ var _ = Describe("ptp Operations", func() {
|
||||
}
|
||||
}`
|
||||
|
||||
doTest(conf, 2)
|
||||
doTest(conf, 2, types.DNS{})
|
||||
})
|
||||
|
||||
It("does not override IPAM DNS settings if no DNS settings provided", func() {
|
||||
ipamDNSConf := types.DNS{
|
||||
Nameservers: []string{"10.1.2.123"},
|
||||
Domain: "some.domain.test",
|
||||
Search: []string{"search.test"},
|
||||
Options: []string{"option1:foo"},
|
||||
}
|
||||
resolvConfPath, err := testutils.TmpResolvConf(ipamDNSConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer os.RemoveAll(resolvConfPath)
|
||||
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"type": "ptp",
|
||||
"ipMasq": true,
|
||||
"mtu": 5000,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.2.0/24",
|
||||
"resolvConf": "%s"
|
||||
}
|
||||
}`, resolvConfPath)
|
||||
|
||||
doTest(conf, 1, ipamDNSConf)
|
||||
})
|
||||
|
||||
It("overrides IPAM DNS settings if any DNS settings provided", func() {
|
||||
ipamDNSConf := types.DNS{
|
||||
Nameservers: []string{"10.1.2.123"},
|
||||
Domain: "some.domain.test",
|
||||
Search: []string{"search.test"},
|
||||
Options: []string{"option1:foo"},
|
||||
}
|
||||
resolvConfPath, err := testutils.TmpResolvConf(ipamDNSConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer os.RemoveAll(resolvConfPath)
|
||||
|
||||
for _, ptpDNSConf := range []types.DNS{
|
||||
{
|
||||
Nameservers: []string{"10.1.2.234"},
|
||||
},
|
||||
{
|
||||
Domain: "someother.domain.test",
|
||||
},
|
||||
{
|
||||
Search: []string{"search.elsewhere.test"},
|
||||
},
|
||||
{
|
||||
Options: []string{"option2:bar"},
|
||||
},
|
||||
} {
|
||||
dnsConfBytes, err := json.Marshal(ptpDNSConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"type": "ptp",
|
||||
"ipMasq": true,
|
||||
"mtu": 5000,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.2.0/24",
|
||||
"resolvConf": "%s"
|
||||
},
|
||||
"dns": %s
|
||||
}`, resolvConfPath, string(dnsConfBytes))
|
||||
|
||||
doTest(conf, 1, ptpDNSConf)
|
||||
}
|
||||
})
|
||||
|
||||
It("overrides IPAM DNS settings if any empty list DNS settings provided", func() {
|
||||
ipamDNSConf := types.DNS{
|
||||
Nameservers: []string{"10.1.2.123"},
|
||||
Domain: "some.domain.test",
|
||||
Search: []string{"search.test"},
|
||||
Options: []string{"option1:foo"},
|
||||
}
|
||||
resolvConfPath, err := testutils.TmpResolvConf(ipamDNSConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer os.RemoveAll(resolvConfPath)
|
||||
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"type": "ptp",
|
||||
"ipMasq": true,
|
||||
"mtu": 5000,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.2.0/24",
|
||||
"resolvConf": "%s"
|
||||
},
|
||||
"dns": {
|
||||
"nameservers": [],
|
||||
"search": [],
|
||||
"options": []
|
||||
}
|
||||
}`, resolvConfPath)
|
||||
|
||||
doTest(conf, 1, types.DNS{})
|
||||
})
|
||||
|
||||
It("deconfigures an unconfigured ptp link with DEL", func() {
|
||||
|
||||
@@ -53,14 +53,32 @@ func loadConf(bytes []byte) (*NetConf, string, error) {
|
||||
return nil, "", fmt.Errorf("failed to load netconf: %v", err)
|
||||
}
|
||||
if n.Master == "" {
|
||||
return nil, "", fmt.Errorf(`"master" field is required. It specifies the host interface name to create the VLAN for.`)
|
||||
return nil, "", fmt.Errorf("\"master\" field is required. It specifies the host interface name to create the VLAN for.")
|
||||
}
|
||||
if n.VlanId < 0 || n.VlanId > 4094 {
|
||||
return nil, "", fmt.Errorf(`invalid VLAN ID %d (must be between 0 and 4095 inclusive)`, n.VlanId)
|
||||
return nil, "", fmt.Errorf("invalid VLAN ID %d (must be between 0 and 4095 inclusive)", n.VlanId)
|
||||
}
|
||||
|
||||
// check existing and MTU of master interface
|
||||
masterMTU, err := getMTUByName(n.Master)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if n.MTU < 0 || n.MTU > masterMTU {
|
||||
return nil, "", fmt.Errorf("invalid MTU %d, must be [0, master MTU(%d)]", n.MTU, masterMTU)
|
||||
}
|
||||
|
||||
return n, n.CNIVersion, nil
|
||||
}
|
||||
|
||||
func getMTUByName(ifName string) (int, error) {
|
||||
link, err := netlink.LinkByName(ifName)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return link.Attrs().MTU, nil
|
||||
}
|
||||
|
||||
func createVlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interface, error) {
|
||||
vlan := ¤t.Interface{}
|
||||
|
||||
@@ -76,10 +94,6 @@ func createVlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interfac
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if conf.MTU <= 0 {
|
||||
conf.MTU = m.Attrs().MTU
|
||||
}
|
||||
|
||||
v := &netlink.Vlan{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
MTU: conf.MTU,
|
||||
@@ -193,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
|
||||
|
||||
@@ -121,6 +121,7 @@ var _ = Describe("vlan Operations", func() {
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(originalNS.Close()).To(Succeed())
|
||||
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
|
||||
})
|
||||
|
||||
It("creates an vlan link in a non-default namespace with given MTU", func() {
|
||||
@@ -291,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() {
|
||||
@@ -407,4 +421,75 @@ var _ = Describe("vlan Operations", func() {
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
Describe("fails to create vlan link with invalid MTU", func() {
|
||||
conf := `{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"type": "vlan",
|
||||
"master": "%s",
|
||||
"mtu": %d,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.2.0/24"
|
||||
}
|
||||
}`
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
// set master link's MTU to 1500
|
||||
link, err := netlink.LinkByName(MASTER_NAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = netlink.LinkSetMTU(link, 1500)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("fails to create vlan link with greater MTU than master interface", func() {
|
||||
var err error
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: "/var/run/netns/test",
|
||||
IfName: "eth0",
|
||||
StdinData: []byte(fmt.Sprintf(conf, MASTER_NAME, 1600)),
|
||||
}
|
||||
|
||||
_ = originalNS.Do(func(netNS ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err = testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).To(Equal(fmt.Errorf("invalid MTU 1600, must be [0, master MTU(1500)]")))
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
It("fails to create vlan link with negative MTU", func() {
|
||||
var err error
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: "/var/run/netns/test",
|
||||
IfName: "eth0",
|
||||
StdinData: []byte(fmt.Sprintf(conf, MASTER_NAME, -100)),
|
||||
}
|
||||
|
||||
_ = originalNS.Do(func(netNS ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err = testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).To(Equal(fmt.Errorf("invalid MTU -100, must be [0, master MTU(1500)]")))
|
||||
return nil
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,55 +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
|
||||
}
|
||||
}
|
||||
].
|
||||
"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).
|
||||
* `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.
|
||||
@@ -39,6 +39,7 @@
|
||||
"NeedEncap":true
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"loopbackDSR": true
|
||||
}
|
||||
}
|
||||
@@ -19,17 +19,16 @@ import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"os"
|
||||
|
||||
"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"
|
||||
@@ -39,7 +38,6 @@ type NetConf struct {
|
||||
hns.NetConf
|
||||
|
||||
IPMasqNetwork string `json:"ipMasqNetwork,omitempty"`
|
||||
ApiVersion int `json:"ApiVersion"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -74,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)
|
||||
@@ -88,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
|
||||
@@ -104,12 +105,11 @@ func cmdHnsAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
|
||||
return nil, fmt.Errorf("network %v not found", networkName)
|
||||
}
|
||||
|
||||
if !strings.EqualFold(hnsNetwork.Type, "L2Bridge") {
|
||||
if !strings.EqualFold(hnsNetwork.Type, "L2Bridge") && !strings.EqualFold(hnsNetwork.Type, "L2Tunnel") {
|
||||
return nil, fmt.Errorf("network %v is of an unexpected type: %v", networkName, hnsNetwork.Type)
|
||||
}
|
||||
|
||||
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
|
||||
@@ -132,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) {
|
||||
@@ -146,7 +145,7 @@ func cmdHcnAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
|
||||
return nil, fmt.Errorf("network %v not found", networkName)
|
||||
}
|
||||
|
||||
if hcnNetwork.Type != hcn.L2Bridge {
|
||||
if hcnNetwork.Type != hcn.L2Bridge && hcnNetwork.Type != hcn.L2Tunnel {
|
||||
return nil, fmt.Errorf("network %v is of unexpected type: %v", networkName, hcnNetwork.Type)
|
||||
}
|
||||
|
||||
@@ -191,14 +190,12 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
os.Setenv("CNI_COMMAND", "DEL")
|
||||
ipam.ExecDel(n.IPAM.Type, args.StdinData)
|
||||
os.Setenv("CNI_COMMAND", "ADD")
|
||||
return errors.Annotate(err, "error while executing ADD command")
|
||||
}
|
||||
|
||||
if (result == nil) {
|
||||
return errors.New("result for ADD not populated correctly")
|
||||
if result == nil {
|
||||
return fmt.Errorf("result for ADD not populated correctly")
|
||||
}
|
||||
return types.PrintResult(result, cniVersion)
|
||||
}
|
||||
|
||||
@@ -1,37 +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"
|
||||
}
|
||||
"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.
|
||||
* `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.
|
||||
@@ -19,16 +19,15 @@ import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"os"
|
||||
|
||||
"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"
|
||||
@@ -101,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
|
||||
@@ -119,7 +118,9 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
result.DNS = n.GetDNS()
|
||||
|
||||
if n.LoopbackDSR {
|
||||
n.ApplyLoopbackDSR(&ipAddr)
|
||||
}
|
||||
hnsEndpoint := &hcsshim.HNSEndpoint{
|
||||
Name: epName,
|
||||
VirtualNetwork: hnsNetwork.Id,
|
||||
@@ -135,9 +136,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
})
|
||||
defer func() {
|
||||
if !success {
|
||||
os.Setenv("CNI_COMMAND", "DEL")
|
||||
ipam.ExecDel(n.IPAM.Type, args.StdinData)
|
||||
os.Setenv("CNI_COMMAND", "ADD")
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -105,7 +105,7 @@ var _ = Describe("bandwidth test", func() {
|
||||
hostIP = net.IP{169, 254, 0, 1}
|
||||
containerIP = net.IP{10, 254, 0, 1}
|
||||
hostIfaceMTU = 1024
|
||||
ifbDeviceName = "5b6c"
|
||||
ifbDeviceName = "bwpa8eda89404b7"
|
||||
|
||||
createVeth(hostNs.Path(), hostIfname, containerNs.Path(), containerIfname, hostIP, containerIP, hostIfaceMTU)
|
||||
})
|
||||
@@ -621,10 +621,276 @@ var _ = Describe("bandwidth test", func() {
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Validating input", func() {
|
||||
It("Should allow only 4GB burst rate", func() {
|
||||
err := validateRateAndBurst(5000, 4*1024*1024*1024*8-16) // 2 bytes less than the max should pass
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = validateRateAndBurst(5000, 4*1024*1024*1024*8) // we're 1 bit above MaxUint32
|
||||
Expect(err).To(HaveOccurred())
|
||||
err = validateRateAndBurst(0, 1)
|
||||
Expect(err).To(HaveOccurred())
|
||||
err = validateRateAndBurst(1, 0)
|
||||
Expect(err).To(HaveOccurred())
|
||||
err = validateRateAndBurst(0, 0)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Getting the host interface which plugin should work on from veth peer of container interface", func() {
|
||||
It("Should work with multiple host veth interfaces", func() {
|
||||
conf := `{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "cni-plugin-bandwidth-test",
|
||||
"type": "bandwidth",
|
||||
"ingressRate": 8,
|
||||
"ingressBurst": 8,
|
||||
"egressRate": 16,
|
||||
"egressBurst": 8,
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": ""
|
||||
},
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": ""
|
||||
},
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": ""
|
||||
},
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": "%s"
|
||||
}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "%s/24",
|
||||
"gateway": "10.0.0.1",
|
||||
"interface": 1
|
||||
}
|
||||
],
|
||||
"routes": []
|
||||
}
|
||||
}`
|
||||
|
||||
// create veth peer in host ns
|
||||
vethName, peerName := "host-veth-peer1", "host-veth-peer2"
|
||||
createVethInOneNs(hostNs.Path(), vethName, peerName)
|
||||
|
||||
conf = fmt.Sprintf(conf, vethName, peerName, hostIfname, containerIfname, containerNs.Path(), containerIP.String())
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: containerNs.Path(),
|
||||
IfName: containerIfname,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
r, out, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
|
||||
Expect(err).NotTo(HaveOccurred(), string(out))
|
||||
result, err := current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(result.Interfaces).To(HaveLen(5))
|
||||
Expect(result.Interfaces[4].Name).To(Equal(ifbDeviceName))
|
||||
Expect(result.Interfaces[4].Sandbox).To(Equal(""))
|
||||
|
||||
ifbLink, err := netlink.LinkByName(ifbDeviceName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(ifbLink.Attrs().MTU).To(Equal(hostIfaceMTU))
|
||||
|
||||
qdiscs, err := netlink.QdiscList(ifbLink)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(qdiscs).To(HaveLen(1))
|
||||
Expect(qdiscs[0].Attrs().LinkIndex).To(Equal(ifbLink.Attrs().Index))
|
||||
|
||||
Expect(qdiscs[0]).To(BeAssignableToTypeOf(&netlink.Tbf{}))
|
||||
Expect(qdiscs[0].(*netlink.Tbf).Rate).To(Equal(uint64(2)))
|
||||
Expect(qdiscs[0].(*netlink.Tbf).Limit).To(Equal(uint32(1)))
|
||||
|
||||
hostVethLink, err := netlink.LinkByName(hostIfname)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
qdiscFilters, err := netlink.FilterList(hostVethLink, netlink.MakeHandle(0xffff, 0))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(qdiscFilters).To(HaveLen(1))
|
||||
Expect(qdiscFilters[0].(*netlink.U32).Actions[0].(*netlink.MirredAction).Ifindex).To(Equal(ifbLink.Attrs().Index))
|
||||
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
|
||||
Expect(hostNs.Do(func(n ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
ifbLink, err := netlink.LinkByName(hostIfname)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
qdiscs, err := netlink.QdiscList(ifbLink)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(qdiscs).To(HaveLen(2))
|
||||
Expect(qdiscs[0].Attrs().LinkIndex).To(Equal(ifbLink.Attrs().Index))
|
||||
|
||||
Expect(qdiscs[0]).To(BeAssignableToTypeOf(&netlink.Tbf{}))
|
||||
Expect(qdiscs[0].(*netlink.Tbf).Rate).To(Equal(uint64(1)))
|
||||
Expect(qdiscs[0].(*netlink.Tbf).Limit).To(Equal(uint32(1)))
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
|
||||
})
|
||||
|
||||
It("Should fail when container interface has no veth peer", func() {
|
||||
conf := `{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "cni-plugin-bandwidth-test",
|
||||
"type": "bandwidth",
|
||||
"ingressRate": 8,
|
||||
"ingressBurst": 8,
|
||||
"egressRate": 16,
|
||||
"egressBurst": 8,
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": ""
|
||||
},
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": "%s"
|
||||
}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "%s/24",
|
||||
"gateway": "10.0.0.1",
|
||||
"interface": 1
|
||||
}
|
||||
],
|
||||
"routes": []
|
||||
}
|
||||
}`
|
||||
|
||||
// create a macvlan device to be container interface
|
||||
macvlanContainerIfname := "container-macv"
|
||||
createMacvlan(containerNs.Path(), containerIfname, macvlanContainerIfname)
|
||||
|
||||
conf = fmt.Sprintf(conf, hostIfname, macvlanContainerIfname, containerNs.Path(), containerIP.String())
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: containerNs.Path(),
|
||||
IfName: macvlanContainerIfname,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
})
|
||||
|
||||
It("Should fail when preResult has no interfaces", func() {
|
||||
conf := `{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "cni-plugin-bandwidth-test",
|
||||
"type": "bandwidth",
|
||||
"ingressRate": 8,
|
||||
"ingressBurst": 8,
|
||||
"egressRate": 16,
|
||||
"egressBurst": 8,
|
||||
"prevResult": {
|
||||
"interfaces": [],
|
||||
"ips": [],
|
||||
"routes": []
|
||||
}
|
||||
}`
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: containerNs.Path(),
|
||||
IfName: "eth0",
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
})
|
||||
|
||||
It("Should fail when veth peer of container interface does not match any of host interfaces in preResult", func() {
|
||||
conf := `{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "cni-plugin-bandwidth-test",
|
||||
"type": "bandwidth",
|
||||
"ingressRate": 8,
|
||||
"ingressBurst": 8,
|
||||
"egressRate": 16,
|
||||
"egressBurst": 8,
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": ""
|
||||
},
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": "%s"
|
||||
}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "%s/24",
|
||||
"gateway": "10.0.0.1",
|
||||
"interface": 1
|
||||
}
|
||||
],
|
||||
"routes": []
|
||||
}
|
||||
}`
|
||||
|
||||
// fake a non-exist host interface name
|
||||
fakeHostIfname := fmt.Sprintf("%s-fake", hostIfname)
|
||||
|
||||
conf = fmt.Sprintf(conf, fakeHostIfname, containerIfname, containerNs.Path(), containerIP.String())
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: containerNs.Path(),
|
||||
IfName: containerIfname,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
})
|
||||
})
|
||||
|
||||
Context("when chaining bandwidth plugin with PTP using 0.3.0 config", func() {
|
||||
var ptpConf string
|
||||
var rateInBits int
|
||||
var burstInBits int
|
||||
var rateInBits uint64
|
||||
var burstInBits uint64
|
||||
var packetInBytes int
|
||||
var containerWithoutTbfNS ns.NetNS
|
||||
var containerWithTbfNS ns.NetNS
|
||||
@@ -638,7 +904,7 @@ var _ = Describe("bandwidth test", func() {
|
||||
|
||||
BeforeEach(func() {
|
||||
rateInBytes := 1000
|
||||
rateInBits = rateInBytes * 8
|
||||
rateInBits = uint64(rateInBytes * 8)
|
||||
burstInBits = rateInBits * 2
|
||||
packetInBytes = rateInBytes * 25
|
||||
|
||||
@@ -768,8 +1034,8 @@ var _ = Describe("bandwidth test", func() {
|
||||
|
||||
Context("when chaining bandwidth plugin with PTP using 0.4.0 config", func() {
|
||||
var ptpConf string
|
||||
var rateInBits int
|
||||
var burstInBits int
|
||||
var rateInBits uint64
|
||||
var burstInBits uint64
|
||||
var packetInBytes int
|
||||
var containerWithoutTbfNS ns.NetNS
|
||||
var containerWithTbfNS ns.NetNS
|
||||
@@ -783,7 +1049,7 @@ var _ = Describe("bandwidth test", func() {
|
||||
|
||||
BeforeEach(func() {
|
||||
rateInBytes := 1000
|
||||
rateInBits = rateInBytes * 8
|
||||
rateInBits = uint64(rateInBytes * 8)
|
||||
burstInBits = rateInBits * 2
|
||||
packetInBytes = rateInBytes * 25
|
||||
|
||||
|
||||
@@ -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) {
|
||||
@@ -199,3 +202,61 @@ func createVeth(hostNamespace string, hostVethIfName string, containerNamespace
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
func createVethInOneNs(namespace, vethName, peerName string) {
|
||||
vethDeviceRequest := &netlink.Veth{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: vethName,
|
||||
Flags: net.FlagUp,
|
||||
},
|
||||
PeerName: peerName,
|
||||
}
|
||||
|
||||
netNS, err := ns.GetNS(namespace)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = netNS.Do(func(_ ns.NetNS) error {
|
||||
if err := netlink.LinkAdd(vethDeviceRequest); err != nil {
|
||||
return fmt.Errorf("failed to create veth pair: %v", err)
|
||||
}
|
||||
|
||||
_, err := netlink.LinkByName(peerName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find newly-created veth device %q: %v", peerName, err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
func createMacvlan(namespace, master, macvlanName string) {
|
||||
netNS, err := ns.GetNS(namespace)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = netNS.Do(func(_ ns.NetNS) error {
|
||||
m, err := netlink.LinkByName(master)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to lookup master %q: %v", master, err)
|
||||
}
|
||||
|
||||
macvlanDeviceRequest := &netlink.Macvlan{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
MTU: m.Attrs().MTU,
|
||||
Name: macvlanName,
|
||||
ParentIndex: m.Attrs().Index,
|
||||
},
|
||||
Mode: netlink.MACVLAN_MODE_BRIDGE,
|
||||
}
|
||||
|
||||
if err = netlink.LinkAdd(macvlanDeviceRequest); err != nil {
|
||||
return fmt.Errorf("failed to create macvlan device: %s", err)
|
||||
}
|
||||
|
||||
_, err = netlink.LinkByName(macvlanName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find newly-created macvlan device %q: %v", macvlanName, err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ func TeardownIfb(deviceName string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func CreateIngressQdisc(rateInBits, burstInBits int, hostDeviceName string) error {
|
||||
func CreateIngressQdisc(rateInBits, burstInBits uint64, hostDeviceName string) error {
|
||||
hostDevice, err := netlink.LinkByName(hostDeviceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get host device: %s", err)
|
||||
@@ -58,7 +58,7 @@ func CreateIngressQdisc(rateInBits, burstInBits int, hostDeviceName string) erro
|
||||
return createTBF(rateInBits, burstInBits, hostDevice.Attrs().Index)
|
||||
}
|
||||
|
||||
func CreateEgressQdisc(rateInBits, burstInBits int, hostDeviceName string, ifbDeviceName string) error {
|
||||
func CreateEgressQdisc(rateInBits, burstInBits uint64, hostDeviceName string, ifbDeviceName string) error {
|
||||
ifbDevice, err := netlink.LinkByName(ifbDeviceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get ifb device: %s", err)
|
||||
@@ -113,7 +113,7 @@ func CreateEgressQdisc(rateInBits, burstInBits int, hostDeviceName string, ifbDe
|
||||
return nil
|
||||
}
|
||||
|
||||
func createTBF(rateInBits, burstInBits, linkIndex int) error {
|
||||
func createTBF(rateInBits, burstInBits uint64, linkIndex int) error {
|
||||
// Equivalent to
|
||||
// tc qdisc add dev link root tbf
|
||||
// rate netConf.BandwidthLimits.Rate
|
||||
|
||||
@@ -15,10 +15,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
@@ -28,17 +27,22 @@ import (
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ip"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/utils"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
const maxIfbDeviceLength = 15
|
||||
const ifbDevicePrefix = "bwp"
|
||||
|
||||
// BandwidthEntry corresponds to a single entry in the bandwidth argument,
|
||||
// see CONVENTIONS.md
|
||||
type BandwidthEntry struct {
|
||||
IngressRate int `json:"ingressRate"` //Bandwidth rate in bps for traffic through container. 0 for no limit. If ingressRate is set, ingressBurst must also be set
|
||||
IngressBurst int `json:"ingressBurst"` //Bandwidth burst in bits for traffic through container. 0 for no limit. If ingressBurst is set, ingressRate must also be set
|
||||
IngressRate uint64 `json:"ingressRate"` //Bandwidth rate in bps for traffic through container. 0 for no limit. If ingressRate is set, ingressBurst must also be set
|
||||
IngressBurst uint64 `json:"ingressBurst"` //Bandwidth burst in bits for traffic through container. 0 for no limit. If ingressBurst is set, ingressRate must also be set
|
||||
|
||||
EgressRate int `json:"egressRate"` //Bandwidth rate in bps for traffic through container. 0 for no limit. If egressRate is set, egressBurst must also be set
|
||||
EgressBurst int `json:"egressBurst"` //Bandwidth burst in bits for traffic through container. 0 for no limit. If egressBurst is set, egressRate must also be set
|
||||
EgressRate uint64 `json:"egressRate"` //Bandwidth rate in bps for traffic through container. 0 for no limit. If egressRate is set, egressBurst must also be set
|
||||
EgressBurst uint64 `json:"egressBurst"` //Bandwidth burst in bits for traffic through container. 0 for no limit. If egressBurst is set, egressRate must also be set
|
||||
}
|
||||
|
||||
func (bw *BandwidthEntry) isZero() bool {
|
||||
@@ -98,7 +102,7 @@ func getBandwidth(conf *PluginConf) *BandwidthEntry {
|
||||
return conf.BandwidthEntry
|
||||
}
|
||||
|
||||
func validateRateAndBurst(rate int, burst int) error {
|
||||
func validateRateAndBurst(rate, burst uint64) error {
|
||||
switch {
|
||||
case burst < 0 || rate < 0:
|
||||
return fmt.Errorf("rate and burst must be a positive integer")
|
||||
@@ -106,19 +110,15 @@ func validateRateAndBurst(rate int, burst int) error {
|
||||
return fmt.Errorf("if rate is set, burst must also be set")
|
||||
case rate == 0 && burst != 0:
|
||||
return fmt.Errorf("if burst is set, rate must also be set")
|
||||
case burst/8 >= math.MaxUint32:
|
||||
return fmt.Errorf("burst cannot be more than 4GB")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getIfbDeviceName(networkName string, containerId string) (string, error) {
|
||||
hash := sha1.New()
|
||||
_, err := hash.Write([]byte(networkName + containerId))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%x", hash.Sum(nil))[:4], nil
|
||||
func getIfbDeviceName(networkName string, containerId string) string {
|
||||
return utils.MustFormatHashWithPrefix(maxIfbDeviceLength, ifbDevicePrefix, networkName+containerId)
|
||||
}
|
||||
|
||||
func getMTU(deviceName string) (int, error) {
|
||||
@@ -130,21 +130,35 @@ func getMTU(deviceName string) (int, error) {
|
||||
return link.Attrs().MTU, nil
|
||||
}
|
||||
|
||||
func getHostInterface(interfaces []*current.Interface) (*current.Interface, error) {
|
||||
// get the veth peer of container interface in host namespace
|
||||
func getHostInterface(interfaces []*current.Interface, containerIfName string, netns ns.NetNS) (*current.Interface, error) {
|
||||
if len(interfaces) == 0 {
|
||||
return nil, errors.New("no interfaces provided")
|
||||
return nil, fmt.Errorf("no interfaces provided")
|
||||
}
|
||||
|
||||
// get veth peer index of container interface
|
||||
var peerIndex int
|
||||
var err error
|
||||
_ = netns.Do(func(_ ns.NetNS) error {
|
||||
_, peerIndex, err = ip.GetVethPeerIfindex(containerIfName)
|
||||
return nil
|
||||
})
|
||||
if peerIndex <= 0 {
|
||||
return nil, fmt.Errorf("container interface %s has no veth peer: %v", containerIfName, err)
|
||||
}
|
||||
|
||||
// find host interface by index
|
||||
link, err := netlink.LinkByIndex(peerIndex)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("veth peer with index %d is not in host ns", peerIndex)
|
||||
}
|
||||
for _, iface := range interfaces {
|
||||
if iface.Sandbox == "" { // host interface
|
||||
_, _, err = ip.GetVethPeerIfindex(iface.Name)
|
||||
if err == nil {
|
||||
return iface, err
|
||||
}
|
||||
if iface.Sandbox == "" && iface.Name == link.Attrs().Name {
|
||||
return iface, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New(fmt.Sprintf("no host interface found. last error: %s", err))
|
||||
|
||||
return nil, fmt.Errorf("no veth peer of container interface found in host ns")
|
||||
}
|
||||
|
||||
func cmdAdd(args *skel.CmdArgs) error {
|
||||
@@ -166,7 +180,14 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not convert result to current version: %v", err)
|
||||
}
|
||||
hostInterface, err := getHostInterface(result.Interfaces)
|
||||
|
||||
netns, err := ns.GetNS(args.Netns)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open netns %q: %v", netns, err)
|
||||
}
|
||||
defer netns.Close()
|
||||
|
||||
hostInterface, err := getHostInterface(result.Interfaces, args.IfName, netns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -184,10 +205,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
return err
|
||||
}
|
||||
|
||||
ifbDeviceName, err := getIfbDeviceName(conf.Name, args.ContainerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ifbDeviceName := getIfbDeviceName(conf.Name, args.ContainerID)
|
||||
|
||||
err = CreateIfb(ifbDeviceName, mtu)
|
||||
if err != nil {
|
||||
@@ -218,10 +236,7 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
return err
|
||||
}
|
||||
|
||||
ifbDeviceName, err := getIfbDeviceName(conf.Name, args.ContainerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ifbDeviceName := getIfbDeviceName(conf.Name, args.ContainerID)
|
||||
|
||||
if err := TeardownIfb(ifbDeviceName); err != nil {
|
||||
return err
|
||||
@@ -266,7 +281,13 @@ func cmdCheck(args *skel.CmdArgs) error {
|
||||
return fmt.Errorf("could not convert result to current version: %v", err)
|
||||
}
|
||||
|
||||
hostInterface, err := getHostInterface(result.Interfaces)
|
||||
netns, err := ns.GetNS(args.Netns)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open netns %q: %v", netns, err)
|
||||
}
|
||||
defer netns.Close()
|
||||
|
||||
hostInterface, err := getHostInterface(result.Interfaces, args.IfName, netns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -316,10 +337,7 @@ func cmdCheck(args *skel.CmdArgs) error {
|
||||
latency := latencyInUsec(latencyInMillis)
|
||||
limitInBytes := limit(uint64(rateInBytes), latency, uint32(burstInBytes))
|
||||
|
||||
ifbDeviceName, err := getIfbDeviceName(bwConf.Name, args.ContainerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ifbDeviceName := getIfbDeviceName(bwConf.Name, args.ContainerID)
|
||||
|
||||
ifbDevice, err := netlink.LinkByName(ifbDeviceName)
|
||||
if err != nil {
|
||||
|
||||
@@ -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/
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ import (
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
@@ -68,9 +67,15 @@ func parseConf(data []byte) (*FirewallNetConf, *current.Result, error) {
|
||||
return nil, nil, fmt.Errorf("failed to load netconf: %v", err)
|
||||
}
|
||||
|
||||
// Default the firewalld zone to trusted
|
||||
if conf.FirewalldZone == "" {
|
||||
conf.FirewalldZone = "trusted"
|
||||
}
|
||||
|
||||
// Parse previous result.
|
||||
if conf.RawPrevResult == nil {
|
||||
return nil, nil, fmt.Errorf("missing prevResult from earlier plugin")
|
||||
// return early if there was no previous result, which is allowed for DEL calls
|
||||
return &conf, ¤t.Result{}, nil
|
||||
}
|
||||
|
||||
// Parse previous result.
|
||||
@@ -85,11 +90,6 @@ func parseConf(data []byte) (*FirewallNetConf, *current.Result, error) {
|
||||
return nil, nil, fmt.Errorf("could not convert result to current version: %v", err)
|
||||
}
|
||||
|
||||
// Default the firewalld zone to trusted
|
||||
if conf.FirewalldZone == "" {
|
||||
conf.FirewalldZone = "trusted"
|
||||
}
|
||||
|
||||
return &conf, result, nil
|
||||
}
|
||||
|
||||
@@ -116,6 +116,10 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if conf.PrevResult == nil {
|
||||
return fmt.Errorf("missing prevResult from earlier plugin")
|
||||
}
|
||||
|
||||
backend, err := getBackend(conf)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -142,12 +146,6 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Tolerate errors if the container namespace has been torn down already
|
||||
containerNS, err := ns.GetNS(args.Netns)
|
||||
if err == nil {
|
||||
defer containerNS.Close()
|
||||
}
|
||||
|
||||
// Runtime errors are ignored
|
||||
if err := backend.Del(conf, result); err != nil {
|
||||
return err
|
||||
@@ -167,8 +165,8 @@ func cmdCheck(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
// Ensure we have previous result.
|
||||
if result == nil {
|
||||
return fmt.Errorf("Required prevResult missing")
|
||||
if conf.PrevResult == nil {
|
||||
return fmt.Errorf("missing prevResult from earlier plugin")
|
||||
}
|
||||
|
||||
backend, err := getBackend(conf)
|
||||
|
||||
@@ -270,6 +270,13 @@ var _ = Describe("firewall plugin iptables backend", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
validateFullRuleset(fullConf)
|
||||
|
||||
// ensure creation is idempotent
|
||||
_, _, err = testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, fullConf, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/plugins/pkg/utils"
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
)
|
||||
|
||||
@@ -32,20 +33,6 @@ func getPrivChainRules(ip string) [][]string {
|
||||
return rules
|
||||
}
|
||||
|
||||
func ensureChain(ipt *iptables.IPTables, table, chain string) error {
|
||||
chains, err := ipt.ListChains(table)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list iptables chains: %v", err)
|
||||
}
|
||||
for _, ch := range chains {
|
||||
if ch == chain {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return ipt.NewChain(table, chain)
|
||||
}
|
||||
|
||||
func generateFilterRule(privChainName string) []string {
|
||||
return []string{"-m", "comment", "--comment", "CNI firewall plugin rules", "-j", privChainName}
|
||||
}
|
||||
@@ -70,13 +57,13 @@ 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 := ensureChain(ipt, "filter", ib.privChainName); err != nil {
|
||||
if err := utils.EnsureChain(ipt, "filter", ib.privChainName); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ensureChain(ipt, "filter", ib.adminChainName); err != nil {
|
||||
if err := utils.EnsureChain(ipt, "filter", ib.adminChainName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -160,10 +147,10 @@ func (ib *iptablesBackend) checkRules(conf *FirewallNetConf, result *current.Res
|
||||
}
|
||||
|
||||
// Ensure our private chains exist
|
||||
if err := ensureChain(ipt, "filter", ib.privChainName); err != nil {
|
||||
if err := utils.EnsureChain(ipt, "filter", ib.privChainName); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ensureChain(ipt, "filter", ib.adminChainName); err != nil {
|
||||
if err := utils.EnsureChain(ipt, "filter", ib.adminChainName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -178,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"`
|
||||
@@ -89,6 +91,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 {
|
||||
@@ -146,12 +160,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,36 @@ 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"
|
||||
}
|
||||
ipam["subnet"] = fenv.sn.String()
|
||||
|
||||
rtes, err := getIPAMRoutes(n)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read IPAM routes: %w", err)
|
||||
}
|
||||
rtes = append(rtes, types.Route{Dst: *fenv.nw})
|
||||
ipam["routes"] = rtes
|
||||
|
||||
return ipam, nil
|
||||
}
|
||||
|
||||
func doCmdAdd(args *skel.CmdArgs, n *NetConf, fenv *subnetEnv) error {
|
||||
n.Delegate["name"] = n.Name
|
||||
|
||||
@@ -55,21 +79,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 +98,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"
|
||||
@@ -50,9 +51,25 @@ var _ = Describe("Flannel", func() {
|
||||
"name": "cni-flannel",
|
||||
"type": "flannel",
|
||||
"subnetFile": "%s",
|
||||
"dataDir": "%s"
|
||||
"dataDir": "%s"%s
|
||||
}`
|
||||
|
||||
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 flannelSubnetEnv = `
|
||||
FLANNEL_NETWORK=10.1.0.0/16
|
||||
FLANNEL_SUBNET=10.1.17.1/24
|
||||
@@ -68,6 +85,26 @@ 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) 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
|
||||
@@ -76,7 +113,7 @@ FLANNEL_IPMASQ=true
|
||||
// flannel state dir
|
||||
dataDir, err = ioutil.TempDir("", "dataDir")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
input = fmt.Sprintf(inputTemplate, subnetFile, dataDir)
|
||||
input = makeInput("")
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
@@ -108,7 +145,7 @@ FLANNEL_IPMASQ=true
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("check that plugin writes to net config to dataDir")
|
||||
By("check that plugin writes the net config to dataDir")
|
||||
path := fmt.Sprintf("%s/%s", dataDir, "some-container-id")
|
||||
Expect(path).Should(BeAnExistingFile())
|
||||
|
||||
@@ -213,4 +250,49 @@ FLANNEL_IPMASQ=true
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("getDelegateIPAM", func() {
|
||||
Context("when input IPAM is provided", func() {
|
||||
BeforeEach(func() {
|
||||
inputIPAM := makeInputIPAM(inputIPAMType, inputIPAMRoutes, "")
|
||||
input = makeInput(inputIPAM)
|
||||
})
|
||||
It("configures Delegate IPAM accordingly", func() {
|
||||
conf, err := loadFlannelNetConf([]byte(input))
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
fenv, err := loadFlannelSubnetEnv(subnetFile)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
ipam, err := getDelegateIPAM(conf, fenv)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
podsRoute := "{ \"dst\": \"10.1.0.0/16\" }\n"
|
||||
subnet := "\"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 without 'type'", func() {
|
||||
BeforeEach(func() {
|
||||
inputIPAM := makeInputIPAM("", inputIPAMRoutes, "")
|
||||
input = makeInput(inputIPAM)
|
||||
})
|
||||
It("configures Delegate IPAM with 'host-local' ipam", func() {
|
||||
conf, err := loadFlannelNetConf([]byte(input))
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
fenv, err := loadFlannelSubnetEnv(subnetFile)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
ipam, err := getDelegateIPAM(conf, fenv)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
podsRoute := "{ \"dst\": \"10.1.0.0/16\" }\n"
|
||||
subnet := "\"subnet\": \"10.1.17.0/24\""
|
||||
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.
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/utils"
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
"github.com/mattn/go-shellwords"
|
||||
)
|
||||
@@ -35,16 +36,11 @@ type chain struct {
|
||||
|
||||
// setup idempotently creates the chain. It will not error if the chain exists.
|
||||
func (c *chain) setup(ipt *iptables.IPTables) error {
|
||||
// create the chain
|
||||
exists, err := chainExists(ipt, c.table, c.name)
|
||||
|
||||
err := utils.EnsureChain(ipt, c.table, c.name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
if err := ipt.NewChain(c.table, c.name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Add the rules to the chain
|
||||
for _, rule := range c.rules {
|
||||
@@ -74,7 +70,7 @@ func (c *chain) teardown(ipt *iptables.IPTables) error {
|
||||
// flush the chain
|
||||
// This will succeed *and create the chain* if it does not exist.
|
||||
// If the chain doesn't exist, the next checks will fail.
|
||||
if err := ipt.ClearChain(c.table, c.name); err != nil {
|
||||
if err := utils.ClearChain(ipt, c.table, c.name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -94,17 +90,15 @@ func (c *chain) teardown(ipt *iptables.IPTables) error {
|
||||
}
|
||||
chainParts = chainParts[2:] // List results always include an -A CHAINNAME
|
||||
|
||||
if err := ipt.Delete(c.table, entryChain, chainParts...); err != nil {
|
||||
return fmt.Errorf("Failed to delete referring rule %s %s: %v", c.table, entryChainRule, err)
|
||||
if err := utils.DeleteRule(ipt, c.table, entryChain, chainParts...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := ipt.DeleteChain(c.table, c.name); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return utils.DeleteChain(ipt, c.table, c.name)
|
||||
}
|
||||
|
||||
// insertUnique will add a rule to a chain if it does not already exist.
|
||||
@@ -125,24 +119,10 @@ func insertUnique(ipt *iptables.IPTables, table, chain string, prepend bool, rul
|
||||
}
|
||||
}
|
||||
|
||||
func chainExists(ipt *iptables.IPTables, tableName, chainName string) (bool, error) {
|
||||
chains, err := ipt.ListChains(tableName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, ch := range chains {
|
||||
if ch == chainName {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// check the chain.
|
||||
func (c *chain) check(ipt *iptables.IPTables) error {
|
||||
|
||||
exists, err := chainExists(ipt, c.table, c.name)
|
||||
exists, err := utils.ChainExists(ipt, c.table, c.name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user