Compare commits
468 Commits
Author | SHA1 | Date | |
---|---|---|---|
1fb5bf669e | |||
3712c1cfcb | |||
825421709e | |||
d708217503 | |||
abee8ccc0d | |||
e1474463ef | |||
11ee4b61d9 | |||
a4cbf13a9b | |||
6cac5d603b | |||
333fc9a0d7 | |||
f90ac41ae4 | |||
93a1b3d0e7 | |||
00406f9d1e | |||
e82848a9cb | |||
5280b4d582 | |||
495a2cbb0c | |||
8c59fc1eea | |||
2eee7cef35 | |||
1079e113fe | |||
999ca15763 | |||
dad27e9f72 | |||
0b1a96ff30 | |||
c8c26897ba | |||
28c5faee75 | |||
d0d9e36662 | |||
f7662a2435 | |||
aacae5c053 | |||
6b7876125d | |||
2f0faf6721 | |||
37531cdaf5 | |||
a8d4e0a7dd | |||
845ef62b74 | |||
691186ca7f | |||
adaeedd6af | |||
19e5747a8c | |||
4cf3da4ae3 | |||
c20da1521f | |||
b66b5dd85f | |||
e727ad6697 | |||
18172539d8 | |||
f20b8408a4 | |||
6ff8e5eb86 | |||
61fa963636 | |||
33ccedc66f | |||
853b82d19f | |||
d216b0c39b | |||
f95505231a | |||
5f25a93a47 | |||
7c11d48630 | |||
9d9ec6e3e1 | |||
8fd63065a6 | |||
c1a7948b19 | |||
1561794ae9 | |||
fb8ca5d31e | |||
f2574a7cb1 | |||
438548a9dd | |||
8e69e38d51 | |||
0a100e5d8f | |||
3eb775c5e6 | |||
719f60bb91 | |||
2ba7f1608f | |||
bf79945c70 | |||
ba41448fe6 | |||
13fd3de77f | |||
283f200489 | |||
a7e8db00cb | |||
ffb2e2d7d1 | |||
d03b84d8f2 | |||
1512d727cb | |||
470eee1385 | |||
2216cff9e8 | |||
83029befef | |||
48aa2f4eef | |||
ca12d49b41 | |||
2b097c5a62 | |||
0389a29052 | |||
6265f4e4ca | |||
edab9efdea | |||
1b2dc7c2a4 | |||
38f18d26ec | |||
e51301765c | |||
7e918412d5 | |||
99b475ab1a | |||
10b5639361 | |||
65fe256058 | |||
00b82fb666 | |||
c795a3c6b1 | |||
c10af01dfb | |||
9cf1a09835 | |||
d8fc886bf0 | |||
c347755f87 | |||
5b7a263e8f | |||
135292e050 | |||
7dcd738d34 | |||
83fe87c5b0 | |||
090af7db9a | |||
9f1f9a588b | |||
71aa710196 | |||
10ddd9e454 | |||
4a6147a155 | |||
435ef2235d | |||
43db9cc063 | |||
821982da1c | |||
cac8230e7c | |||
bc5f3defe7 | |||
47a4319462 | |||
68a661999a | |||
63235a2531 | |||
7bbd4d19e9 | |||
deec68747e | |||
6f6345ca05 | |||
6c0d73ecc0 | |||
8813bfea7b | |||
16d05ec100 | |||
086f7eb7a1 | |||
d71d0f2da1 | |||
00e0d3b758 | |||
2fb0efe8a3 | |||
3bc00017e3 | |||
c0fe3b7bde | |||
09f36a295d | |||
d3ee71f240 | |||
a02bf4b463 | |||
79f524689c | |||
5a7619c019 | |||
709e775b13 | |||
3a04eb00bb | |||
16ba4222bc | |||
177e0bf2d9 | |||
d12b81dec5 | |||
86e39cfe3c | |||
6223674f25 | |||
36e1e162fa | |||
286064b9ec | |||
9ee4d3225d | |||
2d1005ec02 | |||
23c2134110 | |||
fb92605570 | |||
01d0031487 | |||
98e01b7c80 | |||
9a2f763345 | |||
98359ff8b4 | |||
29e6486154 | |||
bbf47c1083 | |||
5d02d91c96 | |||
755714d716 | |||
e2e14ee46f | |||
fb5d195fc5 | |||
ac7cf82531 | |||
c798f80912 | |||
9fa80036d3 | |||
c4d24e80d6 | |||
2c4c27eb17 | |||
0924b71fc8 | |||
0af8153e9b | |||
83fe27748c | |||
bf9c25887a | |||
0fc229df5e | |||
ec924a4be2 | |||
d27fabcd83 | |||
20a92ff382 | |||
5c29eb7fb5 | |||
f48a5ea512 | |||
a3b678ee03 | |||
87ccb8918b | |||
fa2ed0fd6e | |||
b769956cf4 | |||
df141fc722 | |||
35047644a8 | |||
352f181ff1 | |||
91b1a0e385 | |||
648dd2e14c | |||
615420fa9f | |||
90ed30a55a | |||
020b8db6ab | |||
c5e81e3c05 | |||
3be17f4af7 | |||
f64652faf8 | |||
edfd2274a5 | |||
d7efab18c0 | |||
12471312e1 | |||
c1e2be2765 | |||
fd9408bc97 | |||
ec76e3c35c | |||
c30b47a712 | |||
9dc2ed2c0a | |||
6b30e290d2 | |||
1a6f478913 | |||
63a6dbcfd6 | |||
7e9ada51e7 | |||
198ab129a1 | |||
0463fd19af | |||
ac8673105a | |||
fcf14d39fd | |||
54f69e0a59 | |||
87c3643d3c | |||
f89a005740 | |||
7fcf8e4860 | |||
8c3664b2b1 | |||
e2a71387ab | |||
c78e1e4656 | |||
9f4090dabf | |||
9c59728d39 | |||
6a94696205 | |||
356db54531 | |||
f7dfa0f600 | |||
f1aa6c2622 | |||
bf4068e1cd | |||
7c452c77cd | |||
6264f7bff9 | |||
6bcc877722 | |||
9114aa6d37 | |||
f891722833 | |||
a70e87c3aa | |||
0a0853a756 | |||
0c39335765 | |||
8b8825bcd8 | |||
93604ec20a | |||
e3d563b0f0 | |||
16e4a82b32 | |||
e952f16c75 | |||
5ad4fcf85a | |||
292f188e4e | |||
57c1cb5058 | |||
b8a10bbe11 | |||
600c58a54f | |||
3512b10ff0 | |||
7a98979487 | |||
7aa07efe29 | |||
96c3af81e2 | |||
26745d3752 | |||
9f67faf00f | |||
08d0f33416 | |||
dca23ad451 | |||
76307bf0f6 | |||
ba47b49609 | |||
2be2960897 | |||
8ede7eed87 | |||
42268a4a93 | |||
22dd6c553d | |||
f531419b53 | |||
5a4ecc5402 | |||
90e8e1faf9 | |||
3b2afc93dc | |||
9649ec14f5 | |||
27e830b73e | |||
0c12d8a1c8 | |||
152e7a48e4 | |||
4319bc47f6 | |||
186edecd6c | |||
de5cffee1d | |||
135eb1dd85 | |||
7a55617a0e | |||
5d073d690c | |||
67110e02ba | |||
1324428a9a | |||
fd4c3350ae | |||
cc32993e9e | |||
b76849596f | |||
c9d0423023 | |||
57e95c5dfe | |||
092009035b | |||
547a516c30 | |||
3033fd2e75 | |||
d5a6569102 | |||
f1f128e3c9 | |||
27fdec5cb9 | |||
a1051f3bf1 | |||
c627ea807c | |||
4b216e9d9b | |||
2bebd89aa2 | |||
be383cf30d | |||
6d1f71e55a | |||
24259e7d21 | |||
c16cff9805 | |||
7cb3453c36 | |||
2a9114d1af | |||
2c46a72680 | |||
ceb34eb2e6 | |||
90c018566c | |||
a6b5412c55 | |||
081ed44a1d | |||
189d0c06aa | |||
ba48f8a659 | |||
62952ffdac | |||
8ab23366fb | |||
2052c30acd | |||
8632ace977 | |||
156e59ccd1 | |||
0818512c7a | |||
9b1666d489 | |||
7995c2d934 | |||
0e64b0f8c2 | |||
0e38a1d0c0 | |||
24b3fbc635 | |||
649e0181fe | |||
a49f908168 | |||
f14ff6687a | |||
eddf2f2386 | |||
a3cde17fc0 | |||
a786b12b68 | |||
0db5882a12 | |||
5a408187d4 | |||
2876cd5476 | |||
2f9917ebed | |||
272f15420d | |||
77233dd79d | |||
5238c13aa9 | |||
4b180a9d9c | |||
9215e60986 | |||
93a55036b1 | |||
aa8c8c1489 | |||
ec75bb8587 | |||
78702e9d8a | |||
c4d4aa7d92 | |||
6618a0aba8 | |||
fc2a8805b4 | |||
d1b9c90914 | |||
af26bab500 | |||
f72aa98629 | |||
40c225e990 | |||
8de0287741 | |||
d917ab6b0c | |||
faf6d2629d | |||
76ef07ebc6 | |||
d6bf1eac6c | |||
a5b79632bd | |||
55fa8a91d0 | |||
bdaaa20ef2 | |||
33a29292da | |||
820fee9c33 | |||
f34c600ea4 | |||
b41052c547 | |||
0a03382905 | |||
8f32968f73 | |||
028fc2f219 | |||
7da1c84919 | |||
2eac102887 | |||
f4d2925220 | |||
c3d01539d5 | |||
75b64e0f60 | |||
bc856372bb | |||
d2d89ddfad | |||
59a6259f8c | |||
e27c48b391 | |||
3cc11350b8 | |||
dd1c37bcf4 | |||
d1b2df2e59 | |||
5b02c2ab70 | |||
f275746676 | |||
2989aba9dc | |||
b811967444 | |||
d385120175 | |||
1c1799ef39 | |||
0ea07b8269 | |||
178d7c0934 | |||
e09a17fe64 | |||
47927f5084 | |||
8936113a16 | |||
9b09f167bb | |||
e31cd2ce1a | |||
8e540bf3dc | |||
d2e5b5decb | |||
8f7fe6d8e8 | |||
f33eedb6eb | |||
da52be35bc | |||
02cdaafe93 | |||
f534133ec7 | |||
932653fd3f | |||
dd3f6064f6 | |||
4ddc8ba460 | |||
f56545ca74 | |||
bbf7189c32 | |||
5eae558a8e | |||
5096b53918 | |||
34cee8c758 | |||
c3c286c1c9 | |||
c8f341dff9 | |||
8c25db87b1 | |||
7d8c767622 | |||
9e2430bb80 | |||
fa48f7515b | |||
74a6b28a2c | |||
8feef71fd3 | |||
d1769ddd68 | |||
343d233b4f | |||
8c66d687c7 | |||
49ee6f3768 | |||
d41acb83c4 | |||
7dc7a002cf | |||
075c5a0d67 | |||
a8d1f5cd1b | |||
354def76b4 | |||
25fc741e37 | |||
3161bb52e0 | |||
48a97a7ad1 | |||
ebce6d0b9b | |||
3819ef7e86 | |||
691238ca57 | |||
23a1d90e0b | |||
e13bab99e5 | |||
3689d53adf | |||
680c6dd0b1 | |||
be19d786a0 | |||
001abd4f55 | |||
8d52c42f90 | |||
3ae85c1093 | |||
25704f9372 | |||
7e59bac059 | |||
eb31403ac7 | |||
336ba52542 | |||
b47d178ae0 | |||
509d645ee9 | |||
108c2aebd4 | |||
cccf5395e8 | |||
c41c78b600 | |||
8aad9739d8 | |||
b7af044cdc | |||
95248d8490 | |||
50a3aa6536 | |||
98c621abe6 | |||
b34402abd3 | |||
ccd872bd7a | |||
5fc309a699 | |||
440dcc331b | |||
362f5d626a | |||
8d0d8a9547 | |||
a9abbaf19b | |||
c4b8bccd2a | |||
1a7f1bd8b1 | |||
8a6e96b3f0 | |||
6df03d7937 | |||
e1517e2498 | |||
322790226b | |||
59baaa1546 | |||
9ce99d3f07 | |||
e78e6aa5b9 | |||
08ff3b6413 | |||
1ea19f9213 | |||
9b8de6a613 | |||
dacb671328 | |||
799d3cbf4c | |||
bd589992fb | |||
839d918e35 | |||
c50490eb76 | |||
01a8de9997 | |||
8a88f90f94 | |||
2b6cd5467f | |||
3aadb402e4 | |||
021462563b | |||
d713ec692c | |||
813f541d30 | |||
6eb8e31d21 | |||
051452cdcf | |||
877602d627 | |||
c90b165c6e | |||
28773dc925 | |||
2bd04cb92f | |||
d4775ecff5 | |||
e1f8f9bee5 | |||
68a80bcf9b | |||
1fb9793607 | |||
5cb3a5e897 | |||
b76fdd7c03 | |||
67175607ad | |||
b9560fd5c1 | |||
79192cb1f1 | |||
53854dd948 |
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.18
|
||||
|
||||
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
|
||||
|
||||
RUN_URL=$(jq -r '.rerun_url' run.json)
|
||||
|
||||
curl --request POST \
|
||||
--url "${RUN_URL}/rerun-failed-jobs" \
|
||||
--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" }'
|
23
.github/dependabot.yml
vendored
Normal file
23
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "docker" # See documentation for possible values
|
||||
directory: "/.github/actions/retest-action" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "github-actions" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "gomod" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
groups:
|
||||
golang:
|
||||
patterns:
|
||||
- "*"
|
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@v4
|
||||
|
||||
- name: Re-Test Action
|
||||
uses: ./.github/actions/retest-action
|
||||
with:
|
||||
token: ${{ secrets.REPO_ACCESS_TOKEN }}
|
93
.github/workflows/test.yaml
vendored
Normal file
93
.github/workflows/test.yaml
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
---
|
||||
name: test
|
||||
|
||||
on: ["push", "pull_request"]
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.21"
|
||||
LINUX_ARCHES: "amd64 386 arm arm64 s390x mips64le ppc64le riscv64"
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: setup go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
- uses: ibiqlik/action-yamllint@v3
|
||||
with:
|
||||
format: auto
|
||||
- uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
args: -v
|
||||
skip-cache: true
|
||||
build:
|
||||
name: Build all linux architectures
|
||||
needs: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: setup go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
- 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
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install kernel module
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install linux-modules-extra-$(uname -r)
|
||||
- name: Install nftables
|
||||
run: sudo apt-get install nftables
|
||||
- uses: actions/checkout@v4
|
||||
- name: setup go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
- name: Set up Go for root
|
||||
run: |
|
||||
sudo ln -sf `which go` `sudo which go` || true
|
||||
sudo go version
|
||||
|
||||
- name: Install test binaries
|
||||
run: |
|
||||
go install github.com/containernetworking/cni/cnitool@latest
|
||||
go install github.com/mattn/goveralls@latest
|
||||
go install github.com/modocache/gover@latest
|
||||
|
||||
- name: test
|
||||
run: PATH=$PATH:$(go env GOPATH)/bin COVERALLS=1 ./test_linux.sh
|
||||
|
||||
- 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
|
||||
needs: build
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: setup go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
- name: test
|
||||
run: bash ./test_windows.sh
|
45
.golangci.yml
Normal file
45
.golangci.yml
Normal file
@ -0,0 +1,45 @@
|
||||
issues:
|
||||
exclude-rules:
|
||||
- linters:
|
||||
- revive
|
||||
text: "don't use ALL_CAPS in Go names; use CamelCase"
|
||||
- linters:
|
||||
- revive
|
||||
text: " and that stutters;"
|
||||
- path: '(.+)_test\.go'
|
||||
text: "dot-imports: should not use dot imports"
|
||||
|
||||
linters:
|
||||
disable:
|
||||
- errcheck
|
||||
enable:
|
||||
- contextcheck
|
||||
- durationcheck
|
||||
- gci
|
||||
- ginkgolinter
|
||||
- gocritic
|
||||
- gofumpt
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- misspell
|
||||
- nonamedreturns
|
||||
- predeclared
|
||||
- revive
|
||||
- staticcheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
- wastedassign
|
||||
|
||||
linters-settings:
|
||||
gci:
|
||||
sections:
|
||||
- standard
|
||||
- default
|
||||
- prefix(github.com/containernetworking)
|
||||
|
||||
run:
|
||||
skip-dirs:
|
||||
- vendor
|
||||
timeout: 5m
|
52
.travis.yml
52
.travis.yml
@ -1,52 +0,0 @@
|
||||
language: go
|
||||
sudo: required
|
||||
dist: xenial
|
||||
|
||||
go:
|
||||
- 1.11.x
|
||||
- 1.12.x
|
||||
- 1.13.x
|
||||
|
||||
env:
|
||||
global:
|
||||
- PATH=$GOROOT/bin:$GOPATH/bin:$PATH
|
||||
- CGO_ENABLED=0
|
||||
matrix:
|
||||
- TARGET=386
|
||||
- TARGET=amd64
|
||||
- TARGET=arm
|
||||
- TARGET=arm64
|
||||
- TARGET=ppc64le
|
||||
- TARGET=s390x
|
||||
- TARGET=mips64le
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- os: windows
|
||||
env: TARGET=amd64
|
||||
go: 1.11.x
|
||||
- os: windows
|
||||
env: TARGET=amd64
|
||||
go: 1.12.x
|
||||
|
||||
install:
|
||||
- go get github.com/onsi/ginkgo/ginkgo
|
||||
- go get github.com/containernetworking/cni/cnitool
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
- go get github.com/modocache/gover
|
||||
- go get github.com/mattn/goveralls
|
||||
|
||||
script:
|
||||
- |
|
||||
if [ "${TARGET}" == "amd64" ]; then
|
||||
GOARCH="${TARGET}" ./test_${TRAVIS_OS_NAME}.sh
|
||||
else
|
||||
GOARCH="${TARGET}" ./build_linux.sh
|
||||
fi
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
|
||||
git:
|
||||
depth: 9999999
|
12
.yamllint.yml
Normal file
12
.yamllint.yml
Normal file
@ -0,0 +1,12 @@
|
||||
extends: default
|
||||
|
||||
ignore: |
|
||||
vendor
|
||||
|
||||
rules:
|
||||
document-start: disable
|
||||
line-length: disable
|
||||
truthy:
|
||||
ignore: |
|
||||
.github/workflows/*.yml
|
||||
.github/workflows/*.yaml
|
@ -1,10 +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 <cdc@redhat.com> (@squeed)
|
||||
- Dan Williams <dcbw@redhat.com> (@dcbw)
|
||||
- Gabe Rosenhouse <grosenhouse@pivotal.io> (@rosenhouse)
|
||||
- Matt Dupre <matt@tigera.io> (@matthewdupre)
|
||||
- Michael Cambria <mcambria@redhat.com> (@mccv1r0)
|
||||
- Piotr Skarmuk <piotr.skarmuk@gmail.com> (@jellonek)
|
||||
- Michael Zappa <michael.zappa@gmail.com> (@MikeZappa87)
|
||||
|
20
README.md
20
README.md
@ -1,7 +1,7 @@
|
||||
[](https://travis-ci.org/containernetworking/plugins)
|
||||
[](https://github.com/containernetworking/plugins/actions/workflows/test.yaml?query=branch%3Amaster)
|
||||
|
||||
# plugins
|
||||
Some CNI network plugins, maintained by the containernetworking team. For more information, see the individual READMEs.
|
||||
# Plugins
|
||||
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.
|
||||
|
||||
@ -14,16 +14,16 @@ Read [CONTRIBUTING](CONTRIBUTING.md) for build and test instructions.
|
||||
* `ptp`: Creates a veth pair.
|
||||
* `vlan`: Allocates a vlan device.
|
||||
* `host-device`: Move an already-existing device into a container.
|
||||
#### Windows: windows specific
|
||||
* `dummy`: Creates a new Dummy device in the container.
|
||||
#### Windows: Windows specific
|
||||
* `win-bridge`: Creates a bridge, adds the host and the container to it.
|
||||
* `win-overlay`: Creates an overlay interface to the container.
|
||||
### IPAM: IP address allocation
|
||||
* `dhcp`: Runs a daemon on the host to make DHCP requests on behalf of the container
|
||||
* `host-local`: Maintains a local database of allocated IPs
|
||||
* `static`: Allocate a static IPv4/IPv6 addresses to container and it's useful in debugging purpose.
|
||||
* `static`: Allocate a single static IPv4/IPv6 address to container. It's useful in debugging purpose.
|
||||
|
||||
### Meta: other plugins
|
||||
* `flannel`: Generates an interface corresponding to a flannel config file
|
||||
* `tuning`: Tweaks sysctl parameters of an existing interface
|
||||
* `portmap`: An iptables-based portmapping plugin. Maps ports from the host's address space to the container.
|
||||
* `bandwidth`: Allows bandwidth-limiting through use of traffic control tbf (ingress/egress).
|
||||
@ -32,3 +32,11 @@ Read [CONTRIBUTING](CONTRIBUTING.md) for build and test instructions.
|
||||
|
||||
### Sample
|
||||
The sample plugin provides an example for building your own plugin.
|
||||
|
||||
## Contact
|
||||
|
||||
For any questions about CNI, please reach out via:
|
||||
- Email: [cni-dev](https://groups.google.com/forum/#!forum/cni-dev)
|
||||
- Slack: #cni on the [CNCF slack](https://slack.cncf.io/).
|
||||
|
||||
If you have a _security_ issue to report, please do so privately to the email addresses listed in the [OWNERS](OWNERS.md) file.
|
||||
|
18
Vagrantfile
vendored
18
Vagrantfile
vendored
@ -1,18 +0,0 @@
|
||||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
Vagrant.configure(2) do |config|
|
||||
config.vm.box = "bento/ubuntu-16.04"
|
||||
|
||||
config.vm.synced_folder "..", "/go/src/github.com/containernetworking"
|
||||
|
||||
config.vm.provision "shell", inline: <<-SHELL
|
||||
set -e -x -u
|
||||
apt-get update -y || (sleep 40 && apt-get update -y)
|
||||
apt-get install -y git gcc-multilib gcc-mingw-w64
|
||||
wget -qO- https://storage.googleapis.com/golang/go1.12.7.linux-amd64.tar.gz | tar -C /usr/local -xz
|
||||
echo 'export GOPATH=/go' >> /root/.bashrc
|
||||
echo 'export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin' >> /root/.bashrc
|
||||
cd /go/src/github.com/containernetworking/plugins
|
||||
SHELL
|
||||
end
|
@ -1,21 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/usr/bin/env sh
|
||||
set -e
|
||||
cd $(dirname "$0")
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
if [ "$(uname)" == "Darwin" ]; then
|
||||
if [ "$(uname)" = "Darwin" ]; then
|
||||
export GOOS="${GOOS:-linux}"
|
||||
fi
|
||||
|
||||
ORG_PATH="github.com/containernetworking"
|
||||
export REPO_PATH="${ORG_PATH}/plugins"
|
||||
|
||||
if [ ! -h gopath/src/${REPO_PATH} ]; then
|
||||
mkdir -p gopath/src/${ORG_PATH}
|
||||
ln -s ../../../.. gopath/src/${REPO_PATH} || exit 255
|
||||
fi
|
||||
|
||||
export GOPATH=${PWD}/gopath
|
||||
export GO="${GO:-go}"
|
||||
export GOFLAGS="${GOFLAGS} -mod=vendor"
|
||||
|
||||
mkdir -p "${PWD}/bin"
|
||||
@ -25,9 +15,9 @@ PLUGINS="plugins/meta/* plugins/main/* plugins/ipam/*"
|
||||
for d in $PLUGINS; do
|
||||
if [ -d "$d" ]; then
|
||||
plugin="$(basename "$d")"
|
||||
if [ $plugin != "windows" ]; then
|
||||
if [ "${plugin}" != "windows" ]; then
|
||||
echo " $plugin"
|
||||
$GO build -o "${PWD}/bin/$plugin" "$@" "$REPO_PATH"/$d
|
||||
${GO:-go} build -o "${PWD}/bin/$plugin" "$@" ./"$d"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
@ -1,25 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/usr/bin/env sh
|
||||
set -e
|
||||
cd $(dirname "$0")
|
||||
|
||||
ORG_PATH="github.com/containernetworking"
|
||||
export REPO_PATH="${ORG_PATH}/plugins"
|
||||
|
||||
export GOPATH=$(mktemp -d)
|
||||
mkdir -p ${GOPATH}/src/${ORG_PATH}
|
||||
trap "{ rm -rf $GOPATH; }" EXIT
|
||||
ln -s ${PWD} ${GOPATH}/src/${REPO_PATH} || exit 255
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
export GO="${GO:-go}"
|
||||
export GOOS=windows
|
||||
export GOFLAGS="${GOFLAGS} -mod=vendor"
|
||||
echo $GOFLAGS
|
||||
echo "$GOFLAGS"
|
||||
|
||||
PLUGINS=$(cat plugins/windows_only.txt)
|
||||
PLUGINS=$(cat plugins/windows_only.txt | dos2unix )
|
||||
for d in $PLUGINS; do
|
||||
if [ -d "$d" ]; then
|
||||
plugin="$(basename "$d").exe"
|
||||
echo " $plugin"
|
||||
$GO build -o "${PWD}/bin/$plugin" "$@" "$REPO_PATH"/$d
|
||||
fi
|
||||
echo "building $plugin"
|
||||
$GO build -o "${PWD}/bin/$plugin" "$@" ./"${d}"
|
||||
done
|
||||
|
67
go.mod
67
go.mod
@ -1,33 +1,50 @@
|
||||
module github.com/containernetworking/plugins
|
||||
|
||||
go 1.12
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.4.11 // indirect
|
||||
github.com/Microsoft/hcsshim v0.8.6
|
||||
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae
|
||||
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44
|
||||
github.com/containernetworking/cni v0.7.1
|
||||
github.com/coreos/go-iptables v0.4.5
|
||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7
|
||||
github.com/Microsoft/hcsshim v0.11.4
|
||||
github.com/alexflint/go-filemutex v1.2.0
|
||||
github.com/buger/jsonparser v1.1.1
|
||||
github.com/containernetworking/cni v1.1.2
|
||||
github.com/coreos/go-iptables v0.7.0
|
||||
github.com/coreos/go-systemd/v22 v22.5.0
|
||||
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c
|
||||
github.com/d2g/dhcp4client v1.0.0
|
||||
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5
|
||||
github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4 // indirect
|
||||
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c
|
||||
github.com/golang/protobuf v1.3.1 // indirect
|
||||
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56
|
||||
github.com/mattn/go-shellwords v1.0.3
|
||||
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b
|
||||
github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a
|
||||
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8
|
||||
github.com/sirupsen/logrus v1.0.6 // indirect
|
||||
github.com/stretchr/testify v1.3.0 // indirect
|
||||
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf
|
||||
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc // indirect
|
||||
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941 // indirect
|
||||
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 // indirect
|
||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect
|
||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0
|
||||
github.com/mattn/go-shellwords v1.0.12
|
||||
github.com/networkplumbing/go-nft v0.4.0
|
||||
github.com/onsi/ginkgo/v2 v2.13.2
|
||||
github.com/onsi/gomega v1.30.0
|
||||
github.com/opencontainers/selinux v1.11.0
|
||||
github.com/safchain/ethtool v0.3.0
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2
|
||||
golang.org/x/sys v0.15.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/containerd/cgroups v1.1.0 // indirect
|
||||
github.com/containerd/containerd v1.6.23 // indirect
|
||||
github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4 // indirect
|
||||
github.com/go-logr/logr v1.3.0 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20230323073829-e72429f035bd // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
golang.org/x/mod v0.13.0 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/tools v0.14.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
||||
google.golang.org/grpc v1.56.3 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
292
go.sum
292
go.sum
@ -1,17 +1,29 @@
|
||||
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.7.1 h1:fE3r16wpSEyaqY4Z4oFrLMmIGfBYIKpPrHK31EJ9FzE=
|
||||
github.com/containernetworking/cni v0.7.1/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=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8=
|
||||
github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w=
|
||||
github.com/alexflint/go-filemutex v1.2.0 h1:1v0TJPDtlhgpW4nJ+GvxCLSlUDC3+gW0CQQvlmfDR/s=
|
||||
github.com/alexflint/go-filemutex v1.2.0/go.mod h1:mYyQSWvw9Tx2/H2n9qXPb52tTYfE0pZAWcBq5mK025c=
|
||||
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
|
||||
github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
|
||||
github.com/containerd/containerd v1.6.23 h1:KYJd6UJhKHzwMhiD70iTtSmU+k4565ac22GOTI3AuTA=
|
||||
github.com/containerd/containerd v1.6.23/go.mod h1:UrQOiyzrLi3n4aezYJbQH6Il+YzTvnHFbEuO3yfDrM4=
|
||||
github.com/containernetworking/cni v1.1.2 h1:wtRGZVv7olUHMOqouPpn3cXJWpJgM6+EUl31EQbXALQ=
|
||||
github.com/containernetworking/cni v1.1.2/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw=
|
||||
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
|
||||
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
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=
|
||||
@ -20,40 +32,226 @@ github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5 h1:+CpLbZIeUn94m02
|
||||
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/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c h1:RBUpb2b14UnmRHNd2uHz20ZHLDK+SW5Us/vWF5IHRaY=
|
||||
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/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/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b h1:Ey6yH0acn50T/v6CB75bGP4EMJqnv9WvnjN7oZaj+xE=
|
||||
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a h1:KfNOeFvoAssuZLT7IntKZElKwi/5LRuxY71k+t6rfaM=
|
||||
github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
|
||||
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/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.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
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/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20230323073829-e72429f035bd h1:r8yyd+DJDmsUhGrRBxH5Pj7KeFK5l+Y3FsgT8keqKtk=
|
||||
github.com/google/pprof v0.0.0-20230323073829-e72429f035bd/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/networkplumbing/go-nft v0.4.0 h1:kExVMwXW48DOAukkBwyI16h4uhE5lN9iMvQd52lpTyU=
|
||||
github.com/networkplumbing/go-nft v0.4.0/go.mod h1:HnnM+tYvlGAsMU7yoYwXEVLLiDW9gdMmb5HoGcwpuQs=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||
github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
|
||||
github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
|
||||
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||
github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU=
|
||||
github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
|
||||
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf h1:3J37+NPjNyGW/dbfXtj3yWuF9OEepIdGOXRaJGbORV8=
|
||||
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
|
||||
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc h1:R83G5ikgLMxrBvLh22JhdfI8K6YXEPHx5P03Uu3DRs4=
|
||||
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
|
||||
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941 h1:qBTHLajHecfu+xzRI9PqVDcqx7SdHj9d4B+EzSn3tAc=
|
||||
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 h1:Y/KGZSOdz/2r0WJ9Mkmz6NJBusp0kiNx1Cn82lzJQ6w=
|
||||
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4=
|
||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo=
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 h1:OAj3g0cR6Dx/R07QgQe8wkA9RNjB2u4i700xBkIT4e0=
|
||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
|
||||
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
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-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
|
||||
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
|
||||
google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
||||
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.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
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/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
@ -14,21 +14,20 @@
|
||||
package integration_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"bytes"
|
||||
"io"
|
||||
"net"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/onsi/gomega/gbytes"
|
||||
"github.com/onsi/gomega/gexec"
|
||||
@ -148,8 +147,8 @@ var _ = Describe("Basic PTP using cnitool", func() {
|
||||
basicBridgeEnv.runInNS(hostNS, cnitoolBin, "del", "network-chain-test", contNS2.LongName())
|
||||
})
|
||||
|
||||
Measure("limits traffic only on the restricted bandwith veth device", func(b Benchmarker) {
|
||||
ipRegexp := regexp.MustCompile("10\\.1[12]\\.2\\.\\d{1,3}")
|
||||
Measure("limits traffic only on the restricted bandwidth veth device", func(b Benchmarker) {
|
||||
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())
|
||||
@ -162,27 +161,24 @@ var _ = Describe("Basic PTP using cnitool", func() {
|
||||
Expect(basicBridgeIP).To(ContainSubstring("10.11.2."))
|
||||
|
||||
var chainedBridgeBandwidthPort, basicBridgePort int
|
||||
var err error
|
||||
|
||||
By(fmt.Sprintf("starting echo server in %s\n\n", contNS1.ShortName()))
|
||||
chainedBridgeBandwidthPort, chainedBridgeBandwidthSession, err = startEchoServerInNamespace(contNS1)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
chainedBridgeBandwidthPort, chainedBridgeBandwidthSession = startEchoServerInNamespace(contNS1)
|
||||
|
||||
By(fmt.Sprintf("starting echo server in %s\n\n", contNS2.ShortName()))
|
||||
basicBridgePort, basicBridgeSession, err = startEchoServerInNamespace(contNS2)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
basicBridgePort, basicBridgeSession = startEchoServerInNamespace(contNS2)
|
||||
|
||||
packetInBytes := 20000 // The shaper needs to 'warm'. Send enough to cause it to throttle,
|
||||
// balanced by run time.
|
||||
|
||||
By(fmt.Sprintf("sending tcp traffic to the chained, bridged, traffic shaped container on ip address '%s:%d'\n\n", chainedBridgeIP, chainedBridgeBandwidthPort))
|
||||
runtimeWithLimit := b.Time("with chained bridge and bandwidth plugins", func() {
|
||||
makeTcpClientInNS(hostNS.ShortName(), chainedBridgeIP, chainedBridgeBandwidthPort, packetInBytes)
|
||||
makeTCPClientInNS(hostNS.ShortName(), chainedBridgeIP, chainedBridgeBandwidthPort, packetInBytes)
|
||||
})
|
||||
|
||||
By(fmt.Sprintf("sending tcp traffic to the basic bridged container on ip address '%s:%d'\n\n", basicBridgeIP, basicBridgePort))
|
||||
runtimeWithoutLimit := b.Time("with basic bridged plugin", func() {
|
||||
makeTcpClientInNS(hostNS.ShortName(), basicBridgeIP, basicBridgePort, packetInBytes)
|
||||
makeTCPClientInNS(hostNS.ShortName(), basicBridgeIP, basicBridgePort, packetInBytes)
|
||||
})
|
||||
|
||||
Expect(runtimeWithLimit).To(BeNumerically(">", runtimeWithoutLimit+1000*time.Millisecond))
|
||||
@ -224,27 +220,26 @@ func (n Namespace) Del() {
|
||||
(TestEnv{}).run("ip", "netns", "del", string(n))
|
||||
}
|
||||
|
||||
func makeTcpClientInNS(netns string, address string, port int, numBytes int) {
|
||||
message := bytes.Repeat([]byte{'a'}, numBytes)
|
||||
func makeTCPClientInNS(netns string, address string, port int, numBytes int) {
|
||||
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) {
|
||||
func startEchoServerInNamespace(netNS Namespace) (int, *gexec.Session) {
|
||||
session, err := startInNetNS(echoServerBinaryPath, netNS)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
@ -261,7 +256,7 @@ func startEchoServerInNamespace(netNS Namespace) (int, *gexec.Session, error) {
|
||||
io.Copy(GinkgoWriter, io.MultiReader(session.Out, session.Err))
|
||||
}()
|
||||
|
||||
return port, session, nil
|
||||
return port, session
|
||||
}
|
||||
|
||||
func startInNetNS(binPath string, namespace Namespace) (*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/ginkgo/v2"
|
||||
. "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() {
|
||||
|
@ -23,7 +23,8 @@ import (
|
||||
"github.com/Microsoft/hcsshim/hcn"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
current "github.com/containernetworking/cni/pkg/types/100"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/errors"
|
||||
)
|
||||
|
||||
@ -38,9 +39,10 @@ type EndpointInfo struct {
|
||||
NetworkId string
|
||||
Gateway net.IP
|
||||
IpAddress net.IP
|
||||
MacAddress string
|
||||
}
|
||||
|
||||
// GetSandboxContainerID returns the sandbox ID of this pod
|
||||
// GetSandboxContainerID returns the sandbox ID of this pod.
|
||||
func GetSandboxContainerID(containerID string, netNs string) string {
|
||||
if len(netNs) != 0 && netNs != pauseContainerNetNS {
|
||||
splits := strings.SplitN(netNs, ":", 2)
|
||||
@ -52,7 +54,7 @@ func GetSandboxContainerID(containerID string, netNs string) string {
|
||||
return containerID
|
||||
}
|
||||
|
||||
// short function so we know when to return "" for a string
|
||||
// GetIpString returns the given IP as a string.
|
||||
func GetIpString(ip *net.IP) string {
|
||||
if len(*ip) == 0 {
|
||||
return ""
|
||||
@ -61,27 +63,41 @@ func GetIpString(ip *net.IP) string {
|
||||
}
|
||||
}
|
||||
|
||||
// GetDefaultDestinationPrefix returns the default destination prefix according to the given IP type.
|
||||
func GetDefaultDestinationPrefix(ip *net.IP) string {
|
||||
destinationPrefix := "0.0.0.0/0"
|
||||
if ip.To4() == nil {
|
||||
destinationPrefix = "::/0"
|
||||
}
|
||||
return destinationPrefix
|
||||
}
|
||||
|
||||
// ConstructEndpointName constructs endpoint id which is used to identify an endpoint from HNS/HCN.
|
||||
func ConstructEndpointName(containerID string, netNs string, networkName string) string {
|
||||
return GetSandboxContainerID(containerID, netNs) + "_" + networkName
|
||||
}
|
||||
|
||||
// GenerateHnsEndpoint generates an HNSEndpoint with given info and config.
|
||||
func GenerateHnsEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcsshim.HNSEndpoint, error) {
|
||||
// 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, "failed to get endpoint %q", epInfo.EndpointName)
|
||||
return nil, errors.Annotatef(err, "failed to get HNSEndpoint %s", 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 %s", epInfo.EndpointName)
|
||||
if strings.EqualFold(hnsEndpoint.VirtualNetwork, epInfo.NetworkId) {
|
||||
return nil, fmt.Errorf("HNSEndpoint %s is already existed", epInfo.EndpointName)
|
||||
}
|
||||
hnsEndpoint = nil
|
||||
// remove endpoint if corrupted
|
||||
if _, err = hnsEndpoint.Delete(); err != nil {
|
||||
return nil, errors.Annotatef(err, "failed to delete corrupted HNSEndpoint %s", epInfo.EndpointName)
|
||||
}
|
||||
}
|
||||
|
||||
if n.LoopbackDSR {
|
||||
n.ApplyLoopbackDSR(&epInfo.IpAddress)
|
||||
n.ApplyLoopbackDSRPolicy(&epInfo.IpAddress)
|
||||
}
|
||||
if hnsEndpoint == nil {
|
||||
hnsEndpoint = &hcsshim.HNSEndpoint{
|
||||
Name: epInfo.EndpointName,
|
||||
VirtualNetwork: epInfo.NetworkId,
|
||||
@ -89,194 +105,94 @@ func GenerateHnsEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcsshim.HNSEndpoint
|
||||
DNSSuffix: strings.Join(epInfo.DNS.Search, ","),
|
||||
GatewayAddress: GetIpString(&epInfo.Gateway),
|
||||
IPAddress: epInfo.IpAddress,
|
||||
Policies: n.MarshalPolicies(),
|
||||
}
|
||||
Policies: n.GetHNSEndpointPolicies(),
|
||||
}
|
||||
return hnsEndpoint, nil
|
||||
}
|
||||
|
||||
func GenerateHcnEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcn.HostComputeEndpoint, error) {
|
||||
// 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, "failed to get endpoint %q", epInfo.EndpointName)
|
||||
}
|
||||
|
||||
if hcnEndpoint != nil {
|
||||
// If the endpont already exists, then we should return error unless
|
||||
// the endpoint is based on a different network then delete
|
||||
// should that fail return error
|
||||
if !strings.EqualFold(hcnEndpoint.HostComputeNetwork, epInfo.NetworkId) {
|
||||
err = hcnEndpoint.Delete()
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "failed to delete endpoint %s", epInfo.EndpointName)
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("endpoint %q already exits", epInfo.EndpointName)
|
||||
}
|
||||
}
|
||||
|
||||
if hcnEndpoint == nil {
|
||||
routes := []hcn.Route{
|
||||
{
|
||||
NextHop: GetIpString(&epInfo.Gateway),
|
||||
DestinationPrefix: GetDefaultDestinationPrefix(&epInfo.Gateway),
|
||||
},
|
||||
}
|
||||
|
||||
hcnDns := hcn.Dns{
|
||||
Search: epInfo.DNS.Search,
|
||||
ServerList: epInfo.DNS.Nameservers,
|
||||
}
|
||||
|
||||
hcnIpConfig := hcn.IpConfig{
|
||||
IpAddress: GetIpString(&epInfo.IpAddress),
|
||||
}
|
||||
ipConfigs := []hcn.IpConfig{hcnIpConfig}
|
||||
|
||||
if n.LoopbackDSR {
|
||||
n.ApplyLoopbackDSR(&epInfo.IpAddress)
|
||||
}
|
||||
hcnEndpoint = &hcn.HostComputeEndpoint{
|
||||
SchemaVersion: hcn.Version{Major: 2},
|
||||
Name: epInfo.EndpointName,
|
||||
HostComputeNetwork: epInfo.NetworkId,
|
||||
Dns: hcnDns,
|
||||
Routes: routes,
|
||||
IpConfigurations: ipConfigs,
|
||||
Policies: func() []hcn.EndpointPolicy {
|
||||
if n.HcnPolicyArgs == nil {
|
||||
n.HcnPolicyArgs = []hcn.EndpointPolicy{}
|
||||
}
|
||||
return n.HcnPolicyArgs
|
||||
}(),
|
||||
}
|
||||
}
|
||||
return hcnEndpoint, nil
|
||||
}
|
||||
|
||||
// ConstructEndpointName constructs enpointId which is used to identify an endpoint from HNS
|
||||
// There is a special consideration for netNs name here, which is required for Windows Server 1709
|
||||
// containerID is the Id of the container on which the endpoint is worked on
|
||||
func ConstructEndpointName(containerID string, netNs string, networkName string) string {
|
||||
return GetSandboxContainerID(containerID, netNs) + "_" + networkName
|
||||
}
|
||||
|
||||
// DeprovisionEndpoint removes an endpoint from the container by sending a Detach request to HNS
|
||||
// For shared endpoint, ContainerDetach is used
|
||||
// for removing the endpoint completely, HotDetachEndpoint is used
|
||||
func DeprovisionEndpoint(epName string, netns string, containerID string) error {
|
||||
// RemoveHnsEndpoint detaches the given name endpoint from container specified by containerID,
|
||||
// or removes the given name endpoint completely.
|
||||
func RemoveHnsEndpoint(epName string, netns string, containerID string) error {
|
||||
if len(netns) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
hnsEndpoint, err := hcsshim.GetHNSEndpointByName(epName)
|
||||
|
||||
if err != nil {
|
||||
if hcsshim.IsNotExist(err) {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
}
|
||||
return errors.Annotatef(err, "failed to find HNSEndpoint %s", epName)
|
||||
}
|
||||
|
||||
// for shared endpoint, detach it from the container
|
||||
if netns != pauseContainerNetNS {
|
||||
// Shared endpoint removal. Do not remove the endpoint.
|
||||
hnsEndpoint.ContainerDetach(containerID)
|
||||
_ = hnsEndpoint.ContainerDetach(containerID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do not consider this as failure, else this would leak endpoints
|
||||
hcsshim.HotDetachEndpoint(containerID, hnsEndpoint.Id)
|
||||
|
||||
// Do not return error
|
||||
hnsEndpoint.Delete()
|
||||
|
||||
// for removing the endpoint completely, hot detach is used at first
|
||||
_ = hcsshim.HotDetachEndpoint(containerID, hnsEndpoint.Id)
|
||||
_, _ = hnsEndpoint.Delete()
|
||||
return nil
|
||||
}
|
||||
|
||||
type EndpointMakerFunc func() (*hcsshim.HNSEndpoint, error)
|
||||
type HnsEndpointMakerFunc func() (*hcsshim.HNSEndpoint, error)
|
||||
|
||||
// ProvisionEndpoint provisions an endpoint to a container specified by containerID.
|
||||
// If an endpoint already exists, the endpoint is reused.
|
||||
// This call is idempotent
|
||||
func ProvisionEndpoint(epName string, expectedNetworkId string, containerID string, netns string, makeEndpoint EndpointMakerFunc) (*hcsshim.HNSEndpoint, error) {
|
||||
// On the second add call we expect that the endpoint already exists. If it
|
||||
// does not then we should return an error.
|
||||
if netns != pauseContainerNetNS {
|
||||
_, err := hcsshim.GetHNSEndpointByName(epName)
|
||||
// AddHnsEndpoint attaches an HNSEndpoint to a container specified by containerID.
|
||||
func AddHnsEndpoint(epName string, expectedNetworkId string, containerID string, netns string, makeEndpoint HnsEndpointMakerFunc) (*hcsshim.HNSEndpoint, error) {
|
||||
hnsEndpoint, err := hcsshim.GetHNSEndpointByName(epName)
|
||||
if err != nil {
|
||||
if !hcsshim.IsNotExist(err) {
|
||||
return nil, errors.Annotatef(err, "failed to find HNSEndpoint %s", epName)
|
||||
}
|
||||
}
|
||||
|
||||
// check if endpoint already exists
|
||||
createEndpoint := true
|
||||
hnsEndpoint, err := hcsshim.GetHNSEndpointByName(epName)
|
||||
if hnsEndpoint != nil && strings.EqualFold(hnsEndpoint.VirtualNetwork, expectedNetworkId) {
|
||||
createEndpoint = false
|
||||
// for shared endpoint, we expect that the endpoint already exists
|
||||
if netns != pauseContainerNetNS {
|
||||
if hnsEndpoint == nil {
|
||||
return nil, errors.Annotatef(err, "failed to find HNSEndpoint %s", epName)
|
||||
}
|
||||
}
|
||||
|
||||
if createEndpoint {
|
||||
// verify the existing endpoint is corrupted or not
|
||||
if hnsEndpoint != nil {
|
||||
if _, err = hnsEndpoint.Delete(); err != nil {
|
||||
return nil, errors.Annotate(err, "failed to delete the stale HNSEndpoint")
|
||||
if !strings.EqualFold(hnsEndpoint.VirtualNetwork, expectedNetworkId) {
|
||||
if _, err := hnsEndpoint.Delete(); err != nil {
|
||||
return nil, errors.Annotatef(err, "failed to delete corrupted HNSEndpoint %s", epName)
|
||||
}
|
||||
hnsEndpoint = nil
|
||||
}
|
||||
}
|
||||
|
||||
// create endpoint if not found
|
||||
var isNewEndpoint bool
|
||||
if hnsEndpoint == nil {
|
||||
if hnsEndpoint, err = makeEndpoint(); err != nil {
|
||||
return nil, errors.Annotate(err, "failed to make a new HNSEndpoint")
|
||||
}
|
||||
|
||||
if hnsEndpoint, err = hnsEndpoint.Create(); err != nil {
|
||||
return nil, errors.Annotate(err, "failed to create the new HNSEndpoint")
|
||||
}
|
||||
|
||||
isNewEndpoint = true
|
||||
}
|
||||
|
||||
// hot attach
|
||||
// attach to container
|
||||
if err := hcsshim.HotAttachEndpoint(containerID, hnsEndpoint.Id); err != nil {
|
||||
if createEndpoint {
|
||||
err := DeprovisionEndpoint(epName, netns, containerID)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "failed to Deprovsion after HotAttach failure")
|
||||
if isNewEndpoint {
|
||||
if err := RemoveHnsEndpoint(epName, netns, containerID); err != nil {
|
||||
return nil, errors.Annotatef(err, "failed to remove the new HNSEndpoint %s after attaching container %s failure", hnsEndpoint.Id, containerID)
|
||||
}
|
||||
}
|
||||
if hcsshim.ErrComputeSystemDoesNotExist == err {
|
||||
} else if hcsshim.ErrComputeSystemDoesNotExist == err {
|
||||
return hnsEndpoint, nil
|
||||
}
|
||||
return nil, err
|
||||
return nil, errors.Annotatef(err, "failed to attach container %s to HNSEndpoint %s", containerID, hnsEndpoint.Id)
|
||||
}
|
||||
|
||||
return hnsEndpoint, nil
|
||||
}
|
||||
|
||||
type HcnEndpointMakerFunc func() (*hcn.HostComputeEndpoint, error)
|
||||
|
||||
func AddHcnEndpoint(epName string, expectedNetworkId string, namespace string,
|
||||
makeEndpoint HcnEndpointMakerFunc) (*hcn.HostComputeEndpoint, error) {
|
||||
|
||||
hcnEndpoint, err := makeEndpoint()
|
||||
if err != nil {
|
||||
return nil, errors.Annotate(err, "failed to make a new HNSEndpoint")
|
||||
}
|
||||
|
||||
if hcnEndpoint, err = hcnEndpoint.Create(); err != nil {
|
||||
return nil, errors.Annotate(err, "failed to create the new HNSEndpoint")
|
||||
}
|
||||
|
||||
err = hcn.AddNamespaceEndpoint(namespace, hcnEndpoint.Id)
|
||||
if err != nil {
|
||||
err := RemoveHcnEndpoint(epName)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "failed to Remove Endpoint after AddNamespaceEndpoint failure")
|
||||
}
|
||||
return nil, errors.Annotate(err, "failed to Add endpoint to namespace")
|
||||
}
|
||||
return hcnEndpoint, nil
|
||||
|
||||
}
|
||||
|
||||
// ConstructResult constructs the CNI result for the endpoint
|
||||
func ConstructResult(hnsNetwork *hcsshim.HNSNetwork, hnsEndpoint *hcsshim.HNSEndpoint) (*current.Result, error) {
|
||||
// ConstructHnsResult constructs the CNI result for the HNSEndpoint.
|
||||
func ConstructHnsResult(hnsNetwork *hcsshim.HNSNetwork, hnsEndpoint *hcsshim.HNSEndpoint) (*current.Result, error) {
|
||||
resultInterface := ¤t.Interface{
|
||||
Name: hnsEndpoint.Name,
|
||||
Mac: hnsEndpoint.MacAddress,
|
||||
@ -286,51 +202,151 @@ func ConstructResult(hnsNetwork *hcsshim.HNSNetwork, hnsEndpoint *hcsshim.HNSEnd
|
||||
return nil, errors.Annotatef(err, "failed to parse CIDR from %s", hnsNetwork.Subnets[0].AddressPrefix)
|
||||
}
|
||||
|
||||
var ipVersion string
|
||||
if ipv4 := hnsEndpoint.IPAddress.To4(); ipv4 != nil {
|
||||
ipVersion = "4"
|
||||
} else if ipv6 := hnsEndpoint.IPAddress.To16(); ipv6 != nil {
|
||||
ipVersion = "6"
|
||||
} else {
|
||||
return nil, fmt.Errorf("IPAddress of HNSEndpoint %s isn't a valid ipv4 or ipv6 Address", hnsEndpoint.Name)
|
||||
}
|
||||
|
||||
resultIPConfig := ¤t.IPConfig{
|
||||
Version: ipVersion,
|
||||
Address: net.IPNet{
|
||||
IP: hnsEndpoint.IPAddress,
|
||||
Mask: ipSubnet.Mask},
|
||||
Gateway: net.ParseIP(hnsEndpoint.GatewayAddress),
|
||||
}
|
||||
result := ¤t.Result{}
|
||||
result.Interfaces = []*current.Interface{resultInterface}
|
||||
result.IPs = []*current.IPConfig{resultIPConfig}
|
||||
result.DNS = types.DNS{
|
||||
result := ¤t.Result{
|
||||
CNIVersion: current.ImplementedSpecVersion,
|
||||
Interfaces: []*current.Interface{resultInterface},
|
||||
IPs: []*current.IPConfig{resultIPConfig},
|
||||
DNS: types.DNS{
|
||||
Search: strings.Split(hnsEndpoint.DNSSuffix, ","),
|
||||
Nameservers: strings.Split(hnsEndpoint.DNSServerList, ","),
|
||||
},
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// This version follows the v2 workflow of removing the endpoint from the namespace and deleting it
|
||||
// GenerateHcnEndpoint generates a HostComputeEndpoint with given info and config.
|
||||
func GenerateHcnEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcn.HostComputeEndpoint, error) {
|
||||
// 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, "failed to get HostComputeEndpoint %s", epInfo.EndpointName)
|
||||
}
|
||||
|
||||
// verify the existing endpoint is corrupted or not
|
||||
if hcnEndpoint != nil {
|
||||
if strings.EqualFold(hcnEndpoint.HostComputeNetwork, epInfo.NetworkId) {
|
||||
return nil, fmt.Errorf("HostComputeNetwork %s is already existed", epInfo.EndpointName)
|
||||
}
|
||||
// remove endpoint if corrupted
|
||||
if err := hcnEndpoint.Delete(); err != nil {
|
||||
return nil, errors.Annotatef(err, "failed to delete corrupted HostComputeEndpoint %s", epInfo.EndpointName)
|
||||
}
|
||||
}
|
||||
|
||||
if n.LoopbackDSR {
|
||||
n.ApplyLoopbackDSRPolicy(&epInfo.IpAddress)
|
||||
}
|
||||
hcnEndpoint = &hcn.HostComputeEndpoint{
|
||||
SchemaVersion: hcn.SchemaVersion{
|
||||
Major: 2,
|
||||
Minor: 0,
|
||||
},
|
||||
Name: epInfo.EndpointName,
|
||||
MacAddress: epInfo.MacAddress,
|
||||
HostComputeNetwork: epInfo.NetworkId,
|
||||
Dns: hcn.Dns{
|
||||
Domain: epInfo.DNS.Domain,
|
||||
Search: epInfo.DNS.Search,
|
||||
ServerList: epInfo.DNS.Nameservers,
|
||||
Options: epInfo.DNS.Options,
|
||||
},
|
||||
Routes: []hcn.Route{
|
||||
{
|
||||
NextHop: GetIpString(&epInfo.Gateway),
|
||||
DestinationPrefix: GetDefaultDestinationPrefix(&epInfo.Gateway),
|
||||
},
|
||||
},
|
||||
IpConfigurations: []hcn.IpConfig{
|
||||
{
|
||||
IpAddress: GetIpString(&epInfo.IpAddress),
|
||||
},
|
||||
},
|
||||
Policies: n.GetHostComputeEndpointPolicies(),
|
||||
}
|
||||
return hcnEndpoint, nil
|
||||
}
|
||||
|
||||
// RemoveHcnEndpoint removes the given name endpoint from namespace.
|
||||
func RemoveHcnEndpoint(epName string) error {
|
||||
hcnEndpoint, err := hcn.GetEndpointByName(epName)
|
||||
if err != nil {
|
||||
if hcn.IsNotFoundError(err) {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
_ = fmt.Errorf("[win-cni] Failed to find endpoint %v, err:%v", epName, err)
|
||||
return err
|
||||
}
|
||||
if hcnEndpoint != nil {
|
||||
return errors.Annotatef(err, "failed to find HostComputeEndpoint %s", epName)
|
||||
}
|
||||
epNamespace, err := hcn.GetNamespaceByID(hcnEndpoint.HostComputeNamespace)
|
||||
if err != nil && !hcn.IsNotFoundError(err) {
|
||||
return errors.Annotatef(err, "failed to get HostComputeNamespace %s", epName)
|
||||
}
|
||||
if epNamespace != nil {
|
||||
err = hcn.RemoveNamespaceEndpoint(hcnEndpoint.HostComputeNamespace, hcnEndpoint.Id)
|
||||
if err != nil && !hcn.IsNotFoundError(err) {
|
||||
return errors.Annotatef(err,"error removing endpoint: %s from namespace", epName)
|
||||
}
|
||||
}
|
||||
|
||||
err = hcnEndpoint.Delete()
|
||||
if err != nil {
|
||||
return fmt.Errorf("[win-cni] Failed to delete endpoint %v, err:%v", epName, err)
|
||||
}
|
||||
return errors.Annotatef(err, "failed to remove HostComputeEndpoint %s", epName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type HcnEndpointMakerFunc func() (*hcn.HostComputeEndpoint, error)
|
||||
|
||||
// AddHcnEndpoint attaches a HostComputeEndpoint to the given namespace.
|
||||
func AddHcnEndpoint(epName string, expectedNetworkId string, namespace string, makeEndpoint HcnEndpointMakerFunc) (*hcn.HostComputeEndpoint, error) {
|
||||
hcnEndpoint, err := hcn.GetEndpointByName(epName)
|
||||
if err != nil {
|
||||
if !hcn.IsNotFoundError(err) {
|
||||
return nil, errors.Annotatef(err, "failed to find HostComputeEndpoint %s", epName)
|
||||
}
|
||||
}
|
||||
|
||||
// verify the existing endpoint is corrupted or not
|
||||
if hcnEndpoint != nil {
|
||||
if !strings.EqualFold(hcnEndpoint.HostComputeNetwork, expectedNetworkId) {
|
||||
if err := hcnEndpoint.Delete(); err != nil {
|
||||
return nil, errors.Annotatef(err, "failed to delete corrupted HostComputeEndpoint %s", epName)
|
||||
}
|
||||
hcnEndpoint = nil
|
||||
}
|
||||
}
|
||||
|
||||
// create endpoint if not found
|
||||
var isNewEndpoint bool
|
||||
if hcnEndpoint == nil {
|
||||
if hcnEndpoint, err = makeEndpoint(); err != nil {
|
||||
return nil, errors.Annotate(err, "failed to make a new HostComputeEndpoint")
|
||||
}
|
||||
if hcnEndpoint, err = hcnEndpoint.Create(); err != nil {
|
||||
return nil, errors.Annotate(err, "failed to create the new HostComputeEndpoint")
|
||||
}
|
||||
isNewEndpoint = true
|
||||
}
|
||||
|
||||
// add to namespace
|
||||
err = hcn.AddNamespaceEndpoint(namespace, hcnEndpoint.Id)
|
||||
if err != nil {
|
||||
if isNewEndpoint {
|
||||
if err := RemoveHcnEndpoint(epName); err != nil {
|
||||
return nil, errors.Annotatef(err, "failed to remove the new HostComputeEndpoint %s after adding HostComputeNamespace %s failure", epName, namespace)
|
||||
}
|
||||
}
|
||||
return nil, errors.Annotatef(err, "failed to add HostComputeEndpoint %s to HostComputeNamespace %s", epName, namespace)
|
||||
}
|
||||
return hcnEndpoint, nil
|
||||
}
|
||||
|
||||
// ConstructHcnResult constructs the CNI result for the HostComputeEndpoint.
|
||||
func ConstructHcnResult(hcnNetwork *hcn.HostComputeNetwork, hcnEndpoint *hcn.HostComputeEndpoint) (*current.Result, error) {
|
||||
resultInterface := ¤t.Interface{
|
||||
Name: hcnEndpoint.Name,
|
||||
@ -341,29 +357,23 @@ func ConstructHcnResult(hcnNetwork *hcn.HostComputeNetwork, hcnEndpoint *hcn.Hos
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ipVersion string
|
||||
ipAddress := net.ParseIP(hcnEndpoint.IpConfigurations[0].IpAddress)
|
||||
if ipv4 := ipAddress.To4(); ipv4 != nil {
|
||||
ipVersion = "4"
|
||||
} else if ipv6 := ipAddress.To16(); ipv6 != nil {
|
||||
ipVersion = "6"
|
||||
} else {
|
||||
return nil, fmt.Errorf("[win-cni] The IPAddress of hnsEndpoint isn't a valid ipv4 or ipv6 Address.")
|
||||
}
|
||||
|
||||
resultIPConfig := ¤t.IPConfig{
|
||||
Version: ipVersion,
|
||||
Address: net.IPNet{
|
||||
IP: ipAddress,
|
||||
Mask: ipSubnet.Mask},
|
||||
Gateway: net.ParseIP(hcnEndpoint.Routes[0].NextHop),
|
||||
}
|
||||
result := ¤t.Result{}
|
||||
result.Interfaces = []*current.Interface{resultInterface}
|
||||
result.IPs = []*current.IPConfig{resultIPConfig}
|
||||
result.DNS = types.DNS{
|
||||
result := ¤t.Result{
|
||||
CNIVersion: current.ImplementedSpecVersion,
|
||||
Interfaces: []*current.Interface{resultInterface},
|
||||
IPs: []*current.IPConfig{resultIPConfig},
|
||||
DNS: types.DNS{
|
||||
Search: hcnEndpoint.Dns.Search,
|
||||
Nameservers: hcnEndpoint.Dns.ServerList,
|
||||
Options: hcnEndpoint.Dns.Options,
|
||||
Domain: hcnEndpoint.Dns.Domain,
|
||||
},
|
||||
}
|
||||
|
||||
return result, 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")
|
||||
}
|
@ -14,13 +14,13 @@
|
||||
package hns
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHns(t *testing.T) {
|
||||
func TestNetConf(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "HNS NetConf Suite")
|
||||
RunSpecs(t, "NetConf Suite")
|
||||
}
|
||||
|
@ -17,9 +17,10 @@ package hns
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Microsoft/hcsshim/hcn"
|
||||
@ -30,16 +31,16 @@ import (
|
||||
// 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"`
|
||||
// V1 Api Policies
|
||||
Policies []policy `json:"policies,omitempty"`
|
||||
// Options to be passed in by the runtime
|
||||
// ApiVersion specifies the policies type of HNS or HCN, select one of [1, 2].
|
||||
// HNS is the v1 API, which is the default version and applies to dockershim.
|
||||
// HCN is the v2 API, which can leverage HostComputeNamespace and use in containerd.
|
||||
ApiVersion int `json:"apiVersion,omitempty"`
|
||||
// Policies specifies the policy list for HNSEndpoint or HostComputeEndpoint.
|
||||
Policies []Policy `json:"policies,omitempty"`
|
||||
// RuntimeConfig represents the 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"`
|
||||
// LoopbackDSR specifies whether to support loopback direct server return.
|
||||
LoopbackDSR bool `json:"loopbackDSR,omitempty"`
|
||||
}
|
||||
|
||||
type RuntimeDNS struct {
|
||||
@ -54,42 +55,67 @@ type PortMapEntry struct {
|
||||
HostIP string `json:"hostIP,omitempty"`
|
||||
}
|
||||
|
||||
// constants of the supported Windows Socket protocol,
|
||||
// ref to https://docs.microsoft.com/en-us/dotnet/api/system.net.sockets.protocoltype.
|
||||
var protocolEnums = map[string]uint32{
|
||||
"icmpv4": 1,
|
||||
"igmp": 2,
|
||||
"tcp": 6,
|
||||
"udp": 17,
|
||||
"icmpv6": 58,
|
||||
}
|
||||
|
||||
func (p *PortMapEntry) GetProtocolEnum() (uint32, error) {
|
||||
var u, err = strconv.ParseUint(p.Protocol, 0, 10)
|
||||
if err != nil {
|
||||
var pe, exist = protocolEnums[strings.ToLower(p.Protocol)]
|
||||
if !exist {
|
||||
return 0, errors.New("invalid protocol supplied to port mapping policy")
|
||||
}
|
||||
return pe, nil
|
||||
}
|
||||
return uint32(u), nil
|
||||
}
|
||||
|
||||
type RuntimeConfig struct {
|
||||
DNS RuntimeDNS `json:"dns"`
|
||||
PortMaps []PortMapEntry `json:"portMappings,omitempty"`
|
||||
}
|
||||
|
||||
type policy struct {
|
||||
type Policy struct {
|
||||
Name string `json:"name"`
|
||||
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"
|
||||
// GetHNSEndpointPolicies converts the configuration policies to HNSEndpoint policies.
|
||||
func (n *NetConf) GetHNSEndpointPolicies() []json.RawMessage {
|
||||
result := make([]json.RawMessage, 0, len(n.Policies))
|
||||
for _, p := range n.Policies {
|
||||
if !strings.EqualFold(p.Name, "EndpointPolicy") {
|
||||
continue
|
||||
}
|
||||
return destinationPrefix
|
||||
result = append(result, p.Value)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
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)),
|
||||
// GetHostComputeEndpointPolicies converts the configuration policies to HostComputeEndpoint policies.
|
||||
func (n *NetConf) GetHostComputeEndpointPolicies() []hcn.EndpointPolicy {
|
||||
result := make([]hcn.EndpointPolicy, 0, len(n.Policies))
|
||||
for _, p := range n.Policies {
|
||||
if !strings.EqualFold(p.Name, "EndpointPolicy") {
|
||||
continue
|
||||
}
|
||||
n.HcnPolicyArgs = append(n.HcnPolicyArgs, hcnLoopbackRoute)
|
||||
} else {
|
||||
hnsLoopbackRoute := policy{
|
||||
Name: "EndpointPolicy",
|
||||
Value: []byte(fmt.Sprintf(`{"Type": "OutBoundNAT", %s}`, value)),
|
||||
var policy hcn.EndpointPolicy
|
||||
if err := json.Unmarshal(p.Value, &policy); err != nil {
|
||||
continue
|
||||
}
|
||||
n.Policies = append(n.Policies, hnsLoopbackRoute)
|
||||
result = append(result, policy)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// If runtime dns values are there use that else use cni conf supplied dns
|
||||
// GetDNS returns the DNS values if they are there use that else use netconf supplied DNS.
|
||||
func (n *NetConf) GetDNS() types.DNS {
|
||||
dnsResult := n.DNS
|
||||
if len(n.RuntimeConfig.DNS.Nameservers) > 0 {
|
||||
@ -101,136 +127,222 @@ func (n *NetConf) GetDNS() types.DNS {
|
||||
return dnsResult
|
||||
}
|
||||
|
||||
// MarshalPolicies converts the Endpoint policies in Policies
|
||||
// to HNS specific policies as Json raw bytes
|
||||
func (n *NetConf) MarshalPolicies() []json.RawMessage {
|
||||
if n.Policies == nil {
|
||||
n.Policies = make([]policy, 0)
|
||||
}
|
||||
|
||||
result := make([]json.RawMessage, 0, len(n.Policies))
|
||||
for _, p := range n.Policies {
|
||||
if !strings.EqualFold(p.Name, "EndpointPolicy") {
|
||||
continue
|
||||
}
|
||||
|
||||
result = append(result, p.Value)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// ApplyOutboundNatPolicy applies NAT Policy in VFP using HNS
|
||||
// Simultaneously an exception is added for the network that has to be Nat'd
|
||||
func (n *NetConf) ApplyOutboundNatPolicy(nwToNat string) {
|
||||
if n.Policies == nil {
|
||||
n.Policies = make([]policy, 0)
|
||||
}
|
||||
|
||||
nwToNatBytes := []byte(nwToNat)
|
||||
|
||||
for i, p := range n.Policies {
|
||||
if !strings.EqualFold(p.Name, "EndpointPolicy") {
|
||||
continue
|
||||
}
|
||||
|
||||
typeValue, err := jsonparser.GetUnsafeString(p.Value, "Type")
|
||||
if err != nil || len(typeValue) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if !strings.EqualFold(typeValue, "OutBoundNAT") {
|
||||
continue
|
||||
}
|
||||
|
||||
exceptionListValue, dt, _, _ := jsonparser.Get(p.Value, "ExceptionList")
|
||||
// OutBoundNAT must with ExceptionList, so don't need to judge jsonparser.NotExist
|
||||
if dt == jsonparser.Array {
|
||||
buf := bytes.Buffer{}
|
||||
buf.WriteString(`{"Type": "OutBoundNAT", "ExceptionList": [`)
|
||||
|
||||
jsonparser.ArrayEach(exceptionListValue, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
|
||||
if dataType == jsonparser.String && len(value) != 0 {
|
||||
if bytes.Compare(value, nwToNatBytes) != 0 {
|
||||
buf.WriteByte('"')
|
||||
buf.Write(value)
|
||||
buf.WriteByte('"')
|
||||
buf.WriteByte(',')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
buf.WriteString(`"` + nwToNat + `"]}`)
|
||||
|
||||
n.Policies[i] = policy{
|
||||
Name: "EndpointPolicy",
|
||||
Value: buf.Bytes(),
|
||||
}
|
||||
} else {
|
||||
n.Policies[i] = policy{
|
||||
Name: "EndpointPolicy",
|
||||
Value: []byte(`{"Type": "OutBoundNAT", "ExceptionList": ["` + nwToNat + `"]}`),
|
||||
}
|
||||
}
|
||||
|
||||
// ApplyLoopbackDSRPolicy configures the given IP to support loopback DSR.
|
||||
func (n *NetConf) ApplyLoopbackDSRPolicy(ip *net.IP) {
|
||||
if err := hcn.DSRSupported(); err != nil || ip == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// didn't find the policyArg, add it
|
||||
n.Policies = append(n.Policies, policy{
|
||||
Name: "EndpointPolicy",
|
||||
Value: []byte(`{"Type": "OutBoundNAT", "ExceptionList": ["` + nwToNat + `"]}`),
|
||||
})
|
||||
}
|
||||
|
||||
// ApplyDefaultPAPolicy is used to configure a endpoint PA policy in HNS
|
||||
func (n *NetConf) ApplyDefaultPAPolicy(paAddress string) {
|
||||
if n.Policies == nil {
|
||||
n.Policies = make([]policy, 0)
|
||||
toPolicyValue := func(addr string) json.RawMessage {
|
||||
if n.ApiVersion == 2 {
|
||||
return bprintf(`{"Type": "OutBoundNAT", "Settings": {"Destinations": ["%s"]}}`, addr)
|
||||
}
|
||||
return bprintf(`{"Type": "OutBoundNAT", "Destinations": ["%s"]}`, addr)
|
||||
}
|
||||
ipBytes := []byte(ip.String())
|
||||
|
||||
// if its already present, leave untouched
|
||||
for i, p := range n.Policies {
|
||||
// find OutBoundNAT policy
|
||||
for i := range n.Policies {
|
||||
p := &n.Policies[i]
|
||||
if !strings.EqualFold(p.Name, "EndpointPolicy") {
|
||||
continue
|
||||
}
|
||||
|
||||
paValue, dt, _, _ := jsonparser.Get(p.Value, "PA")
|
||||
// filter OutBoundNAT policy
|
||||
typeValue, _ := jsonparser.GetUnsafeString(p.Value, "Type")
|
||||
if typeValue != "OutBoundNAT" {
|
||||
continue
|
||||
}
|
||||
|
||||
// parse destination address list
|
||||
var (
|
||||
destinationsValue []byte
|
||||
dt jsonparser.ValueType
|
||||
)
|
||||
if n.ApiVersion == 2 {
|
||||
destinationsValue, dt, _, _ = jsonparser.Get(p.Value, "Settings", "Destinations")
|
||||
} else {
|
||||
destinationsValue, dt, _, _ = jsonparser.Get(p.Value, "Destinations")
|
||||
}
|
||||
|
||||
// skip if Destinations/DestinationList field is not found
|
||||
if dt == jsonparser.NotExist {
|
||||
continue
|
||||
} else if dt == jsonparser.String && len(paValue) != 0 {
|
||||
// found it, don't override
|
||||
return
|
||||
}
|
||||
|
||||
n.Policies[i] = policy{
|
||||
Name: "EndpointPolicy",
|
||||
Value: []byte(`{"Type": "PA", "PA": "` + paAddress + `"}`),
|
||||
// return if found the given address
|
||||
if dt == jsonparser.Array {
|
||||
var found bool
|
||||
_, _ = jsonparser.ArrayEach(destinationsValue, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
|
||||
if dataType == jsonparser.String && len(value) != 0 {
|
||||
if bytes.Compare(value, ipBytes) == 0 {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
})
|
||||
if found {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// didn't find the policyArg, add it
|
||||
n.Policies = append(n.Policies, policy{
|
||||
// or add a new OutBoundNAT if not found
|
||||
n.Policies = append(n.Policies, Policy{
|
||||
Name: "EndpointPolicy",
|
||||
Value: []byte(`{"Type": "PA", "PA": "` + paAddress + `"}`),
|
||||
Value: toPolicyValue(ip.String()),
|
||||
})
|
||||
}
|
||||
|
||||
// ApplyPortMappingPolicy is used to configure HostPort<>ContainerPort mapping in HNS
|
||||
// ApplyOutboundNatPolicy applies the sNAT policy in HNS/HCN and configures the given CIDR as an exception.
|
||||
func (n *NetConf) ApplyOutboundNatPolicy(exceptionCIDR string) {
|
||||
if exceptionCIDR == "" {
|
||||
return
|
||||
}
|
||||
|
||||
toPolicyValue := func(cidr ...string) json.RawMessage {
|
||||
if n.ApiVersion == 2 {
|
||||
return bprintf(`{"Type": "OutBoundNAT", "Settings": {"Exceptions": ["%s"]}}`, strings.Join(cidr, `","`))
|
||||
}
|
||||
return bprintf(`{"Type": "OutBoundNAT", "ExceptionList": ["%s"]}`, strings.Join(cidr, `","`))
|
||||
}
|
||||
exceptionCIDRBytes := []byte(exceptionCIDR)
|
||||
|
||||
// find OutBoundNAT policy
|
||||
for i := range n.Policies {
|
||||
p := &n.Policies[i]
|
||||
if !strings.EqualFold(p.Name, "EndpointPolicy") {
|
||||
continue
|
||||
}
|
||||
|
||||
// filter OutBoundNAT policy
|
||||
typeValue, _ := jsonparser.GetUnsafeString(p.Value, "Type")
|
||||
if typeValue != "OutBoundNAT" {
|
||||
continue
|
||||
}
|
||||
|
||||
// parse exception CIDR list
|
||||
var (
|
||||
exceptionsValue []byte
|
||||
dt jsonparser.ValueType
|
||||
)
|
||||
if n.ApiVersion == 2 {
|
||||
exceptionsValue, dt, _, _ = jsonparser.Get(p.Value, "Settings", "Exceptions")
|
||||
} else {
|
||||
exceptionsValue, dt, _, _ = jsonparser.Get(p.Value, "ExceptionList")
|
||||
}
|
||||
|
||||
// skip if Exceptions/ExceptionList field is not found
|
||||
if dt == jsonparser.NotExist {
|
||||
continue
|
||||
}
|
||||
|
||||
// return if found the given CIDR
|
||||
if dt == jsonparser.Array {
|
||||
var found bool
|
||||
_, _ = jsonparser.ArrayEach(exceptionsValue, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
|
||||
if dataType == jsonparser.String && len(value) != 0 {
|
||||
if bytes.Compare(value, exceptionCIDRBytes) == 0 {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
})
|
||||
if found {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// or add a new OutBoundNAT if not found
|
||||
n.Policies = append(n.Policies, Policy{
|
||||
Name: "EndpointPolicy",
|
||||
Value: toPolicyValue(exceptionCIDR),
|
||||
})
|
||||
}
|
||||
|
||||
// ApplyDefaultPAPolicy applies an endpoint PA policy in HNS/HCN.
|
||||
func (n *NetConf) ApplyDefaultPAPolicy(address string) {
|
||||
if address == "" {
|
||||
return
|
||||
}
|
||||
|
||||
toPolicyValue := func(addr string) json.RawMessage {
|
||||
if n.ApiVersion == 2 {
|
||||
return bprintf(`{"Type": "ProviderAddress", "Settings": {"ProviderAddress": "%s"}}`, addr)
|
||||
}
|
||||
return bprintf(`{"Type": "PA", "PA": "%s"}`, addr)
|
||||
}
|
||||
addressBytes := []byte(address)
|
||||
|
||||
// find ProviderAddress policy
|
||||
for i := range n.Policies {
|
||||
p := &n.Policies[i]
|
||||
if !strings.EqualFold(p.Name, "EndpointPolicy") {
|
||||
continue
|
||||
}
|
||||
|
||||
// filter ProviderAddress policy
|
||||
typeValue, _ := jsonparser.GetUnsafeString(p.Value, "Type")
|
||||
if typeValue != "PA" && typeValue != "ProviderAddress" {
|
||||
continue
|
||||
}
|
||||
|
||||
// parse provider address
|
||||
var (
|
||||
paValue []byte
|
||||
dt jsonparser.ValueType
|
||||
)
|
||||
if n.ApiVersion == 2 {
|
||||
paValue, dt, _, _ = jsonparser.Get(p.Value, "Settings", "ProviderAddress")
|
||||
} else {
|
||||
paValue, dt, _, _ = jsonparser.Get(p.Value, "PA")
|
||||
}
|
||||
|
||||
// skip if ProviderAddress/PA field is not found
|
||||
if dt == jsonparser.NotExist {
|
||||
continue
|
||||
}
|
||||
|
||||
// return if found the given address
|
||||
if dt == jsonparser.String && bytes.Compare(paValue, addressBytes) == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// or add a new ProviderAddress if not found
|
||||
n.Policies = append(n.Policies, Policy{
|
||||
Name: "EndpointPolicy",
|
||||
Value: toPolicyValue(address),
|
||||
})
|
||||
}
|
||||
|
||||
// ApplyPortMappingPolicy applies the host/container port mapping policies in HNS/HCN.
|
||||
func (n *NetConf) ApplyPortMappingPolicy(portMappings []PortMapEntry) {
|
||||
if portMappings == nil {
|
||||
if len(portMappings) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if n.Policies == nil {
|
||||
n.Policies = make([]policy, 0)
|
||||
toPolicyValue := func(p *PortMapEntry) json.RawMessage {
|
||||
if n.ApiVersion == 2 {
|
||||
var protocolEnum, _ = p.GetProtocolEnum()
|
||||
return bprintf(`{"Type": "PortMapping", "Settings": {"InternalPort": %d, "ExternalPort": %d, "Protocol": %d, "VIP": "%s"}}`, p.ContainerPort, p.HostPort, protocolEnum, p.HostIP)
|
||||
}
|
||||
return bprintf(`{"Type": "NAT", "InternalPort": %d, "ExternalPort": %d, "Protocol": "%s"}`, p.ContainerPort, p.HostPort, p.Protocol)
|
||||
}
|
||||
|
||||
for _, portMapping := range portMappings {
|
||||
n.Policies = append(n.Policies, policy{
|
||||
for i := range portMappings {
|
||||
p := &portMappings[i]
|
||||
// skip the invalid protocol mapping
|
||||
if _, err := p.GetProtocolEnum(); err != nil {
|
||||
continue
|
||||
}
|
||||
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)),
|
||||
Value: toPolicyValue(p),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// bprintf is similar to fmt.Sprintf and returns a byte array as result.
|
||||
func bprintf(format string, a ...interface{}) []byte {
|
||||
return []byte(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
@ -15,221 +15,585 @@ package hns
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
"github.com/Microsoft/hcsshim/hcn"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("HNS NetConf", func() {
|
||||
Describe("ApplyOutBoundNATPolicy", func() {
|
||||
Context("when not set by user", func() {
|
||||
It("sets it by adding a policy", func() {
|
||||
var _ = Describe("NetConf", func() {
|
||||
Describe("ApplyLoopbackDSRPolicy", func() {
|
||||
Context("via v1 api", func() {
|
||||
var n NetConf
|
||||
BeforeEach(func() {
|
||||
n = NetConf{}
|
||||
})
|
||||
|
||||
// apply it
|
||||
n := NetConf{}
|
||||
n.ApplyOutboundNatPolicy("192.168.0.0/16")
|
||||
It("filter out duplicated IP", func() {
|
||||
// mock duplicated IP
|
||||
ip := net.ParseIP("172.16.0.12")
|
||||
n.ApplyLoopbackDSRPolicy(&ip)
|
||||
n.ApplyLoopbackDSRPolicy(&ip)
|
||||
|
||||
// only one policy
|
||||
addlArgs := n.Policies
|
||||
Expect(addlArgs).Should(HaveLen(1))
|
||||
|
||||
// normal type judgement
|
||||
policy := addlArgs[0]
|
||||
Expect(policy.Name).Should(Equal("EndpointPolicy"))
|
||||
|
||||
value := make(map[string]interface{})
|
||||
json.Unmarshal(policy.Value, &value)
|
||||
|
||||
Expect(value).Should(HaveKey("Type"))
|
||||
Expect(value).Should(HaveKey("ExceptionList"))
|
||||
Expect(value["Type"]).Should(Equal("OutBoundNAT"))
|
||||
Expect(value).Should(HaveKey("Destinations"))
|
||||
|
||||
exceptionList := value["ExceptionList"].([]interface{})
|
||||
Expect(exceptionList).Should(HaveLen(1))
|
||||
Expect(exceptionList[0].(string)).Should(Equal("192.168.0.0/16"))
|
||||
// and only one item
|
||||
destinationList := value["Destinations"].([]interface{})
|
||||
Expect(destinationList).Should(HaveLen(1))
|
||||
Expect(destinationList[0].(string)).Should(Equal("172.16.0.12"))
|
||||
})
|
||||
|
||||
It("append different IP", func() {
|
||||
// mock different IP
|
||||
ip1 := net.ParseIP("172.16.0.12")
|
||||
n.ApplyLoopbackDSRPolicy(&ip1)
|
||||
ip2 := net.ParseIP("172.16.0.13")
|
||||
n.ApplyLoopbackDSRPolicy(&ip2)
|
||||
|
||||
// will be two policies
|
||||
addlArgs := n.Policies
|
||||
Expect(addlArgs).Should(HaveLen(2))
|
||||
|
||||
// normal type judgement
|
||||
policy := addlArgs[1] // pick second item
|
||||
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("OutBoundNAT"))
|
||||
Expect(value).Should(HaveKey("Destinations"))
|
||||
|
||||
// only one item
|
||||
destinationList := value["Destinations"].([]interface{})
|
||||
Expect(destinationList).Should(HaveLen(1))
|
||||
Expect(destinationList[0].(string)).Should(Equal("172.16.0.13"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when set by user", func() {
|
||||
It("appends exceptions to the existing policy", func() {
|
||||
// first set it
|
||||
n := NetConf{}
|
||||
n.ApplyOutboundNatPolicy("192.168.0.0/16")
|
||||
Context("via v2 api", func() {
|
||||
var n NetConf
|
||||
BeforeEach(func() {
|
||||
n = NetConf{ApiVersion: 2}
|
||||
})
|
||||
|
||||
// then attempt to update it
|
||||
n.ApplyOutboundNatPolicy("10.244.0.0/16")
|
||||
It("filter out duplicated IP", func() {
|
||||
// mock duplicated IP
|
||||
ip := net.ParseIP("172.16.0.12")
|
||||
n.ApplyLoopbackDSRPolicy(&ip)
|
||||
n.ApplyLoopbackDSRPolicy(&ip)
|
||||
|
||||
// it should be unchanged!
|
||||
// only one policy
|
||||
addlArgs := n.Policies
|
||||
Expect(addlArgs).Should(HaveLen(1))
|
||||
|
||||
// normal type judgement
|
||||
policy := addlArgs[0]
|
||||
Expect(policy.Name).Should(Equal("EndpointPolicy"))
|
||||
|
||||
var value map[string]interface{}
|
||||
value := make(map[string]interface{})
|
||||
json.Unmarshal(policy.Value, &value)
|
||||
|
||||
Expect(value).Should(HaveKey("Type"))
|
||||
Expect(value).Should(HaveKey("ExceptionList"))
|
||||
Expect(value["Type"]).Should(Equal("OutBoundNAT"))
|
||||
Expect(value).Should(HaveKey("Settings"))
|
||||
|
||||
// and only one item
|
||||
settings := value["Settings"].(map[string]interface{})
|
||||
destinationList := settings["Destinations"].([]interface{})
|
||||
Expect(destinationList).Should(HaveLen(1))
|
||||
Expect(destinationList[0].(string)).Should(Equal("172.16.0.12"))
|
||||
})
|
||||
|
||||
It("append different IP", func() {
|
||||
// mock different IP
|
||||
ip1 := net.ParseIP("172.16.0.12")
|
||||
n.ApplyLoopbackDSRPolicy(&ip1)
|
||||
ip2 := net.ParseIP("172.16.0.13")
|
||||
n.ApplyLoopbackDSRPolicy(&ip2)
|
||||
|
||||
// will be two policies
|
||||
addlArgs := n.Policies
|
||||
Expect(addlArgs).Should(HaveLen(2))
|
||||
|
||||
// normal type judgement
|
||||
policy := addlArgs[1] // pick second item
|
||||
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("OutBoundNAT"))
|
||||
Expect(value).Should(HaveKey("Settings"))
|
||||
|
||||
// only one item
|
||||
settings := value["Settings"].(map[string]interface{})
|
||||
destinationList := settings["Destinations"].([]interface{})
|
||||
Expect(destinationList).Should(HaveLen(1))
|
||||
Expect(destinationList[0].(string)).Should(Equal("172.16.0.13"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("ApplyOutBoundNATPolicy", func() {
|
||||
Context("via v1 api", func() {
|
||||
var n NetConf
|
||||
BeforeEach(func() {
|
||||
n = NetConf{}
|
||||
})
|
||||
|
||||
It("append different IP", func() {
|
||||
// mock different IP
|
||||
n.ApplyOutboundNatPolicy("192.168.0.0/16")
|
||||
n.ApplyOutboundNatPolicy("10.244.0.0/16")
|
||||
|
||||
// will be two policies
|
||||
addlArgs := n.Policies
|
||||
Expect(addlArgs).Should(HaveLen(2))
|
||||
|
||||
// normal type judgement
|
||||
policy := addlArgs[1] // pick second item
|
||||
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("OutBoundNAT"))
|
||||
Expect(value).Should(HaveKey("ExceptionList"))
|
||||
|
||||
// but get two items
|
||||
exceptionList := value["ExceptionList"].([]interface{})
|
||||
Expect(exceptionList).Should(HaveLen(2))
|
||||
Expect(exceptionList[0].(string)).Should(Equal("192.168.0.0/16"))
|
||||
Expect(exceptionList[1].(string)).Should(Equal("10.244.0.0/16"))
|
||||
Expect(exceptionList).Should(HaveLen(1))
|
||||
Expect(exceptionList[0].(string)).Should(Equal("10.244.0.0/16"))
|
||||
})
|
||||
|
||||
It("append a new one if there is not an exception OutBoundNAT policy", func() {
|
||||
// mock different OutBoundNAT routes
|
||||
n.Policies = []Policy{
|
||||
{
|
||||
Name: "EndpointPolicy",
|
||||
Value: bprintf(`{"Type": "OutBoundNAT", "OtherList": []}`),
|
||||
},
|
||||
}
|
||||
n.ApplyOutboundNatPolicy("10.244.0.0/16")
|
||||
|
||||
// has two policies
|
||||
addlArgs := n.Policies
|
||||
Expect(addlArgs).Should(HaveLen(2))
|
||||
|
||||
// normal type judgement
|
||||
policy := addlArgs[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("OutBoundNAT"))
|
||||
Expect(value).Should(HaveKey("OtherList"))
|
||||
policy = addlArgs[1]
|
||||
value = make(map[string]interface{})
|
||||
json.Unmarshal(policy.Value, &value)
|
||||
Expect(value).Should(HaveKey("Type"))
|
||||
Expect(value["Type"]).Should(Equal("OutBoundNAT"))
|
||||
Expect(value).Should(HaveKey("ExceptionList"))
|
||||
|
||||
// only get one item
|
||||
exceptionList := value["ExceptionList"].([]interface{})
|
||||
Expect(exceptionList).Should(HaveLen(1))
|
||||
Expect(exceptionList[0].(string)).Should(Equal("10.244.0.0/16"))
|
||||
})
|
||||
|
||||
It("nothing to do if CIDR is blank", func() {
|
||||
// mock different OutBoundNAT routes
|
||||
n.Policies = []Policy{
|
||||
{
|
||||
Name: "EndpointPolicy",
|
||||
Value: bprintf(`{"Type": "OutBoundNAT", "ExceptionList": []}`),
|
||||
},
|
||||
}
|
||||
n.ApplyOutboundNatPolicy("")
|
||||
|
||||
// only one policy
|
||||
addlArgs := n.Policies
|
||||
Expect(addlArgs).Should(HaveLen(1))
|
||||
|
||||
// normal type judgement
|
||||
policy := addlArgs[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("OutBoundNAT"))
|
||||
Expect(value).Should(HaveKey("ExceptionList"))
|
||||
|
||||
// empty list
|
||||
Expect(value["ExceptionList"]).ShouldNot(BeNil())
|
||||
Expect(value["ExceptionList"]).Should(HaveLen(0))
|
||||
})
|
||||
})
|
||||
|
||||
Context("via v2 api", func() {
|
||||
var n NetConf
|
||||
BeforeEach(func() {
|
||||
n = NetConf{ApiVersion: 2}
|
||||
})
|
||||
|
||||
It("append different IP", func() {
|
||||
// mock different IP
|
||||
n.ApplyOutboundNatPolicy("192.168.0.0/16")
|
||||
n.ApplyOutboundNatPolicy("10.244.0.0/16")
|
||||
|
||||
// will be two policies
|
||||
addlArgs := n.Policies
|
||||
Expect(addlArgs).Should(HaveLen(2))
|
||||
|
||||
// normal type judgement
|
||||
policy := addlArgs[1] // pick second item
|
||||
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("OutBoundNAT"))
|
||||
Expect(value).Should(HaveKey("Settings"))
|
||||
|
||||
// but get two items
|
||||
settings := value["Settings"].(map[string]interface{})
|
||||
exceptionList := settings["Exceptions"].([]interface{})
|
||||
Expect(exceptionList).Should(HaveLen(1))
|
||||
Expect(exceptionList[0].(string)).Should(Equal("10.244.0.0/16"))
|
||||
})
|
||||
|
||||
It("append a new one if there is not an exception OutBoundNAT policy", func() {
|
||||
// mock different OutBoundNAT routes
|
||||
n.Policies = []Policy{
|
||||
{
|
||||
Name: "EndpointPolicy",
|
||||
Value: bprintf(`{"Type": "OutBoundNAT", "Settings": {"Others": []}}`),
|
||||
},
|
||||
}
|
||||
n.ApplyOutboundNatPolicy("10.244.0.0/16")
|
||||
|
||||
// has two policies
|
||||
addlArgs := n.Policies
|
||||
Expect(addlArgs).Should(HaveLen(2))
|
||||
|
||||
// normal type judgement
|
||||
policy := addlArgs[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("OutBoundNAT"))
|
||||
Expect(value).Should(HaveKey("Settings"))
|
||||
Expect(value["Settings"]).Should(HaveKey("Others"))
|
||||
policy = addlArgs[1]
|
||||
value = make(map[string]interface{})
|
||||
json.Unmarshal(policy.Value, &value)
|
||||
Expect(value).Should(HaveKey("Type"))
|
||||
Expect(value["Type"]).Should(Equal("OutBoundNAT"))
|
||||
Expect(value).Should(HaveKey("Settings"))
|
||||
|
||||
// only get one item
|
||||
settings := value["Settings"].(map[string]interface{})
|
||||
exceptionList := settings["Exceptions"].([]interface{})
|
||||
Expect(exceptionList).Should(HaveLen(1))
|
||||
Expect(exceptionList[0].(string)).Should(Equal("10.244.0.0/16"))
|
||||
})
|
||||
|
||||
It("nothing to do if CIDR is blank", func() {
|
||||
// mock different OutBoundNAT routes
|
||||
n.Policies = []Policy{
|
||||
{
|
||||
Name: "EndpointPolicy",
|
||||
Value: bprintf(`{"Type": "OutBoundNAT", "Settings": {"Exceptions": []}}`),
|
||||
},
|
||||
}
|
||||
n.ApplyOutboundNatPolicy("")
|
||||
|
||||
// only one policy
|
||||
addlArgs := n.Policies
|
||||
Expect(addlArgs).Should(HaveLen(1))
|
||||
|
||||
// normal type judgement
|
||||
policy := addlArgs[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("OutBoundNAT"))
|
||||
Expect(value).Should(HaveKey("Settings"))
|
||||
|
||||
// empty list
|
||||
settings := value["Settings"].(map[string]interface{})
|
||||
Expect(settings["Exceptions"]).ShouldNot(BeNil())
|
||||
Expect(settings["Exceptions"]).Should(HaveLen(0))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("ApplyDefaultPAPolicy", func() {
|
||||
Context("when not set by user", func() {
|
||||
It("sets it by adding a policy", func() {
|
||||
|
||||
n := NetConf{}
|
||||
n.ApplyDefaultPAPolicy("192.168.0.1")
|
||||
|
||||
addlArgs := n.Policies
|
||||
Expect(addlArgs).Should(HaveLen(1))
|
||||
|
||||
policy := addlArgs[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("PA"))
|
||||
|
||||
paAddress := value["PA"].(string)
|
||||
Expect(paAddress).Should(Equal("192.168.0.1"))
|
||||
})
|
||||
Context("via v1 api", func() {
|
||||
var n NetConf
|
||||
BeforeEach(func() {
|
||||
n = NetConf{}
|
||||
})
|
||||
|
||||
Context("when set by user", func() {
|
||||
It("does not override", func() {
|
||||
n := NetConf{}
|
||||
It("append different IP", func() {
|
||||
// mock different IP
|
||||
n.ApplyDefaultPAPolicy("192.168.0.1")
|
||||
n.ApplyDefaultPAPolicy("192.168.0.2")
|
||||
|
||||
// will be two policies
|
||||
addlArgs := n.Policies
|
||||
Expect(addlArgs).Should(HaveLen(1))
|
||||
Expect(addlArgs).Should(HaveLen(2))
|
||||
|
||||
policy := addlArgs[0]
|
||||
// normal type judgement
|
||||
policy := addlArgs[1] // judge second item
|
||||
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("PA"))
|
||||
|
||||
// compare with second item
|
||||
paAddress := value["PA"].(string)
|
||||
Expect(paAddress).Should(Equal("192.168.0.1"))
|
||||
Expect(paAddress).ShouldNot(Equal("192.168.0.2"))
|
||||
Expect(paAddress).Should(Equal("192.168.0.2"))
|
||||
})
|
||||
|
||||
It("nothing to do if IP is blank", func() {
|
||||
// mock different policy
|
||||
n.Policies = []Policy{
|
||||
{
|
||||
Name: "EndpointPolicy",
|
||||
Value: bprintf(`{"Type": "OutBoundNAT", "Exceptions": ["192.168.0.0/16"]}`),
|
||||
},
|
||||
}
|
||||
n.ApplyDefaultPAPolicy("")
|
||||
|
||||
// nothing
|
||||
addlArgs := n.Policies
|
||||
Expect(addlArgs).Should(HaveLen(1))
|
||||
})
|
||||
})
|
||||
|
||||
Context("via v2 api", func() {
|
||||
var n NetConf
|
||||
BeforeEach(func() {
|
||||
n = NetConf{ApiVersion: 2}
|
||||
})
|
||||
|
||||
It("append different IP", func() {
|
||||
// mock different IP
|
||||
n.ApplyDefaultPAPolicy("192.168.0.1")
|
||||
n.ApplyDefaultPAPolicy("192.168.0.2")
|
||||
|
||||
// will be two policies
|
||||
addlArgs := n.Policies
|
||||
Expect(addlArgs).Should(HaveLen(2))
|
||||
|
||||
// normal type judgement
|
||||
policy := addlArgs[1] // judge second item
|
||||
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("ProviderAddress"))
|
||||
Expect(value).Should(HaveKey("Settings"))
|
||||
|
||||
// compare with second item
|
||||
settings := value["Settings"].(map[string]interface{})
|
||||
paAddress := settings["ProviderAddress"].(string)
|
||||
Expect(paAddress).Should(Equal("192.168.0.2"))
|
||||
})
|
||||
|
||||
It("nothing to do if IP is blank", func() {
|
||||
// mock different policy
|
||||
n.Policies = []Policy{
|
||||
{
|
||||
Name: "EndpointPolicy",
|
||||
Value: bprintf(`{"Type": "OutBoundNAT", "Settings": {"Exceptions": ["192.168.0.0/16"]}}`),
|
||||
},
|
||||
}
|
||||
n.ApplyDefaultPAPolicy("")
|
||||
|
||||
// nothing
|
||||
addlArgs := n.Policies
|
||||
Expect(addlArgs).Should(HaveLen(1))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("ApplyPortMappingPolicy", func() {
|
||||
Context("when portMappings not activated", func() {
|
||||
It("does nothing", func() {
|
||||
n := NetConf{}
|
||||
Context("via v1 api", func() {
|
||||
var n NetConf
|
||||
BeforeEach(func() {
|
||||
n = NetConf{}
|
||||
})
|
||||
|
||||
It("nothing to do if input is empty", func() {
|
||||
n.ApplyPortMappingPolicy(nil)
|
||||
Expect(n.Policies).Should(BeNil())
|
||||
|
||||
n.ApplyPortMappingPolicy([]PortMapEntry{})
|
||||
Expect(n.Policies).Should(HaveLen(0))
|
||||
})
|
||||
Expect(n.Policies).Should(BeNil())
|
||||
})
|
||||
|
||||
Context("when portMappings is activated", func() {
|
||||
It("creates NAT policies", func() {
|
||||
n := NetConf{}
|
||||
It("create one NAT policy", func() {
|
||||
// mock different IP
|
||||
n.ApplyPortMappingPolicy([]PortMapEntry{
|
||||
{
|
||||
ContainerPort: 80,
|
||||
HostPort: 8080,
|
||||
Protocol: "TCP",
|
||||
HostIP: "ignored",
|
||||
HostIP: "192.168.1.2",
|
||||
},
|
||||
})
|
||||
|
||||
Expect(n.Policies).Should(HaveLen(1))
|
||||
// only one item
|
||||
addlArgs := n.Policies
|
||||
Expect(addlArgs).Should(HaveLen(1))
|
||||
|
||||
policy := n.Policies[0]
|
||||
// normal type judgement
|
||||
policy := addlArgs[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"))
|
||||
|
||||
// compare all values
|
||||
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"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("via v2 api", func() {
|
||||
var n NetConf
|
||||
BeforeEach(func() {
|
||||
n = NetConf{ApiVersion: 2}
|
||||
})
|
||||
|
||||
Describe("MarshalPolicies", func() {
|
||||
Context("when not set by user", func() {
|
||||
It("sets it by adding a policy", func() {
|
||||
It("nothing to do if input is empty", func() {
|
||||
n.ApplyPortMappingPolicy(nil)
|
||||
Expect(n.Policies).Should(BeNil())
|
||||
|
||||
n := NetConf{
|
||||
Policies: []policy{
|
||||
n.ApplyPortMappingPolicy([]PortMapEntry{})
|
||||
Expect(n.Policies).Should(BeNil())
|
||||
})
|
||||
|
||||
It("creates one NAT policy", func() {
|
||||
// mock different IP
|
||||
n.ApplyPortMappingPolicy([]PortMapEntry{
|
||||
{
|
||||
ContainerPort: 80,
|
||||
HostPort: 8080,
|
||||
Protocol: "TCP",
|
||||
HostIP: "192.168.1.2",
|
||||
},
|
||||
})
|
||||
|
||||
// only one item
|
||||
addlArgs := n.Policies
|
||||
Expect(addlArgs).Should(HaveLen(1))
|
||||
|
||||
// normal type judgement
|
||||
policy := addlArgs[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("PortMapping"))
|
||||
Expect(value).Should(HaveKey("Settings"))
|
||||
|
||||
// compare all values
|
||||
settings := value["Settings"].(map[string]interface{})
|
||||
Expect(settings).Should(HaveKey("InternalPort"))
|
||||
Expect(settings["InternalPort"]).Should(Equal(float64(80)))
|
||||
Expect(settings).Should(HaveKey("ExternalPort"))
|
||||
Expect(settings["ExternalPort"]).Should(Equal(float64(8080)))
|
||||
Expect(settings).Should(HaveKey("Protocol"))
|
||||
Expect(settings["Protocol"]).Should(Equal(float64(6)))
|
||||
Expect(settings).Should(HaveKey("VIP"))
|
||||
Expect(settings["VIP"]).Should(Equal("192.168.1.2"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("GetXEndpointPolicies", func() {
|
||||
Context("via v1 api", func() {
|
||||
var n NetConf
|
||||
BeforeEach(func() {
|
||||
n = NetConf{}
|
||||
})
|
||||
|
||||
It("GetHNSEndpointPolicies", func() {
|
||||
// mock different policies
|
||||
n.Policies = []Policy{
|
||||
{
|
||||
Name: "EndpointPolicy",
|
||||
Value: []byte(`{"someKey": "someValue"}`),
|
||||
Value: []byte(`{"Type": "OutBoundNAT", "ExceptionList": [ "192.168.1.2" ]}`),
|
||||
},
|
||||
{
|
||||
Name: "someOtherType",
|
||||
Value: []byte(`{"someOtherKey": "someOtherValue"}`),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result := n.MarshalPolicies()
|
||||
// only one valid item
|
||||
result := n.GetHNSEndpointPolicies()
|
||||
Expect(len(result)).To(Equal(1))
|
||||
|
||||
// normal type judgement
|
||||
policy := make(map[string]interface{})
|
||||
err := json.Unmarshal(result[0], &policy)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(policy).Should(HaveKey("someKey"))
|
||||
Expect(policy["someKey"]).To(Equal("someValue"))
|
||||
Expect(policy).Should(HaveKey("Type"))
|
||||
Expect(policy["Type"]).To(Equal("OutBoundNAT"))
|
||||
Expect(policy).Should(HaveKey("ExceptionList"))
|
||||
Expect(policy["ExceptionList"]).To(ContainElement("192.168.1.2"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when set by user", func() {
|
||||
It("appends exceptions to the existing policy", func() {
|
||||
// first set it
|
||||
n := NetConf{}
|
||||
n.ApplyOutboundNatPolicy("192.168.0.0/16")
|
||||
Context("via v2 api", func() {
|
||||
var n NetConf
|
||||
BeforeEach(func() {
|
||||
n = NetConf{ApiVersion: 2}
|
||||
})
|
||||
|
||||
// then attempt to update it
|
||||
n.ApplyOutboundNatPolicy("10.244.0.0/16")
|
||||
It("GetHostComputeEndpointPolicies", func() {
|
||||
// mock different policies
|
||||
n.Policies = []Policy{
|
||||
{
|
||||
Name: "EndpointPolicy",
|
||||
Value: []byte(`{"Type": "OutBoundNAT", "Settings": {"Exceptions": [ "192.168.1.2" ]}}`),
|
||||
},
|
||||
{
|
||||
Name: "someOtherType",
|
||||
Value: []byte(`{"someOtherKey": "someOtherValue"}`),
|
||||
},
|
||||
}
|
||||
|
||||
// it should be unchanged!
|
||||
addlArgs := n.Policies
|
||||
Expect(addlArgs).Should(HaveLen(1))
|
||||
// only one valid item
|
||||
result := n.GetHostComputeEndpointPolicies()
|
||||
Expect(len(result)).To(Equal(1))
|
||||
|
||||
policy := addlArgs[0]
|
||||
Expect(policy.Name).Should(Equal("EndpointPolicy"))
|
||||
|
||||
var value map[string]interface{}
|
||||
json.Unmarshal(policy.Value, &value)
|
||||
|
||||
Expect(value).Should(HaveKey("Type"))
|
||||
Expect(value).Should(HaveKey("ExceptionList"))
|
||||
Expect(value["Type"]).Should(Equal("OutBoundNAT"))
|
||||
|
||||
exceptionList := value["ExceptionList"].([]interface{})
|
||||
Expect(exceptionList).Should(HaveLen(2))
|
||||
Expect(exceptionList[0].(string)).Should(Equal("192.168.0.0/16"))
|
||||
Expect(exceptionList[1].(string)).Should(Equal("10.244.0.0/16"))
|
||||
// normal type judgement
|
||||
policy := result[0]
|
||||
Expect(policy.Type).Should(Equal(hcn.OutBoundNAT))
|
||||
settings := make(map[string]interface{})
|
||||
err := json.Unmarshal(policy.Settings, &settings)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(settings["Exceptions"]).To(ContainElement("192.168.1.2"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -19,43 +19,87 @@ import (
|
||||
"net"
|
||||
)
|
||||
|
||||
// NextIP returns IP incremented by 1
|
||||
// NextIP returns IP incremented by 1, if IP is invalid, return nil
|
||||
func NextIP(ip net.IP) net.IP {
|
||||
i := ipToInt(ip)
|
||||
return intToIP(i.Add(i, big.NewInt(1)))
|
||||
normalizedIP := normalizeIP(ip)
|
||||
if normalizedIP == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
i := ipToInt(normalizedIP)
|
||||
return intToIP(i.Add(i, big.NewInt(1)), len(normalizedIP) == net.IPv6len)
|
||||
}
|
||||
|
||||
// PrevIP returns IP decremented by 1
|
||||
// PrevIP returns IP decremented by 1, if IP is invalid, return nil
|
||||
func PrevIP(ip net.IP) net.IP {
|
||||
i := ipToInt(ip)
|
||||
return intToIP(i.Sub(i, big.NewInt(1)))
|
||||
normalizedIP := normalizeIP(ip)
|
||||
if normalizedIP == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
i := ipToInt(normalizedIP)
|
||||
return intToIP(i.Sub(i, big.NewInt(1)), len(normalizedIP) == net.IPv6len)
|
||||
}
|
||||
|
||||
// Cmp compares two IPs, returning the usual ordering:
|
||||
// a < b : -1
|
||||
// a == b : 0
|
||||
// a > b : 1
|
||||
// incomparable : -2
|
||||
func Cmp(a, b net.IP) int {
|
||||
aa := ipToInt(a)
|
||||
bb := ipToInt(b)
|
||||
return aa.Cmp(bb)
|
||||
normalizedA := normalizeIP(a)
|
||||
normalizedB := normalizeIP(b)
|
||||
|
||||
if len(normalizedA) == len(normalizedB) && len(normalizedA) != 0 {
|
||||
return ipToInt(normalizedA).Cmp(ipToInt(normalizedB))
|
||||
}
|
||||
|
||||
return -2
|
||||
}
|
||||
|
||||
func ipToInt(ip net.IP) *big.Int {
|
||||
if v := ip.To4(); v != nil {
|
||||
return big.NewInt(0).SetBytes(v)
|
||||
return big.NewInt(0).SetBytes(ip)
|
||||
}
|
||||
|
||||
func intToIP(i *big.Int, isIPv6 bool) net.IP {
|
||||
intBytes := i.Bytes()
|
||||
|
||||
if len(intBytes) == net.IPv4len || len(intBytes) == net.IPv6len {
|
||||
return intBytes
|
||||
}
|
||||
return big.NewInt(0).SetBytes(ip.To16())
|
||||
|
||||
if isIPv6 {
|
||||
return append(make([]byte, net.IPv6len-len(intBytes)), intBytes...)
|
||||
}
|
||||
|
||||
return append(make([]byte, net.IPv4len-len(intBytes)), intBytes...)
|
||||
}
|
||||
|
||||
func intToIP(i *big.Int) net.IP {
|
||||
return net.IP(i.Bytes())
|
||||
// normalizeIP will normalize IP by family,
|
||||
// IPv4 : 4-byte form
|
||||
// IPv6 : 16-byte form
|
||||
// others : nil
|
||||
func normalizeIP(ip net.IP) net.IP {
|
||||
if ipTo4 := ip.To4(); ipTo4 != nil {
|
||||
return ipTo4
|
||||
}
|
||||
return ip.To16()
|
||||
}
|
||||
|
||||
// Network masks off the host portion of the IP
|
||||
// Network masks off the host portion of the IP, if IPNet is invalid,
|
||||
// return nil
|
||||
func Network(ipn *net.IPNet) *net.IPNet {
|
||||
if ipn == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
maskedIP := ipn.IP.Mask(ipn.Mask)
|
||||
if maskedIP == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &net.IPNet{
|
||||
IP: ipn.IP.Mask(ipn.Mask),
|
||||
IP: maskedIP,
|
||||
Mask: ipn.Mask,
|
||||
}
|
||||
}
|
||||
|
247
pkg/ip/cidr_test.go
Normal file
247
pkg/ip/cidr_test.go
Normal file
@ -0,0 +1,247 @@
|
||||
// Copyright 2022 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ip
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("CIDR functions", func() {
|
||||
It("NextIP", func() {
|
||||
testCases := []struct {
|
||||
ip net.IP
|
||||
nextIP net.IP
|
||||
}{
|
||||
{
|
||||
[]byte{192, 0, 2},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
net.ParseIP("192.168.0.1"),
|
||||
net.IPv4(192, 168, 0, 2).To4(),
|
||||
},
|
||||
{
|
||||
net.ParseIP("192.168.0.255"),
|
||||
net.IPv4(192, 168, 1, 0).To4(),
|
||||
},
|
||||
{
|
||||
net.ParseIP("0.1.0.5"),
|
||||
net.IPv4(0, 1, 0, 6).To4(),
|
||||
},
|
||||
{
|
||||
net.ParseIP("AB12::123"),
|
||||
net.ParseIP("AB12::124"),
|
||||
},
|
||||
{
|
||||
net.ParseIP("AB12::FFFF"),
|
||||
net.ParseIP("AB12::1:0"),
|
||||
},
|
||||
{
|
||||
net.ParseIP("0::123"),
|
||||
net.ParseIP("0::124"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
ip := NextIP(test.ip)
|
||||
|
||||
Expect(ip).To(Equal(test.nextIP))
|
||||
}
|
||||
})
|
||||
|
||||
It("PrevIP", func() {
|
||||
testCases := []struct {
|
||||
ip net.IP
|
||||
prevIP net.IP
|
||||
}{
|
||||
{
|
||||
[]byte{192, 0, 2},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
net.ParseIP("192.168.0.2"),
|
||||
net.IPv4(192, 168, 0, 1).To4(),
|
||||
},
|
||||
{
|
||||
net.ParseIP("192.168.1.0"),
|
||||
net.IPv4(192, 168, 0, 255).To4(),
|
||||
},
|
||||
{
|
||||
net.ParseIP("0.1.0.5"),
|
||||
net.IPv4(0, 1, 0, 4).To4(),
|
||||
},
|
||||
{
|
||||
net.ParseIP("AB12::123"),
|
||||
net.ParseIP("AB12::122"),
|
||||
},
|
||||
{
|
||||
net.ParseIP("AB12::1:0"),
|
||||
net.ParseIP("AB12::FFFF"),
|
||||
},
|
||||
{
|
||||
net.ParseIP("0::124"),
|
||||
net.ParseIP("0::123"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
ip := PrevIP(test.ip)
|
||||
|
||||
Expect(ip).To(Equal(test.prevIP))
|
||||
}
|
||||
})
|
||||
|
||||
It("Cmp", func() {
|
||||
testCases := []struct {
|
||||
a net.IP
|
||||
b net.IP
|
||||
result int
|
||||
}{
|
||||
{
|
||||
net.ParseIP("192.168.0.2"),
|
||||
nil,
|
||||
-2,
|
||||
},
|
||||
{
|
||||
net.ParseIP("192.168.0.2"),
|
||||
[]byte{192, 168, 5},
|
||||
-2,
|
||||
},
|
||||
{
|
||||
net.ParseIP("192.168.0.2"),
|
||||
net.ParseIP("AB12::123"),
|
||||
-2,
|
||||
},
|
||||
{
|
||||
net.ParseIP("192.168.0.2"),
|
||||
net.ParseIP("192.168.0.5"),
|
||||
-1,
|
||||
},
|
||||
{
|
||||
net.ParseIP("192.168.0.2"),
|
||||
net.ParseIP("192.168.0.5").To4(),
|
||||
-1,
|
||||
},
|
||||
{
|
||||
net.ParseIP("192.168.0.10"),
|
||||
net.ParseIP("192.168.0.5"),
|
||||
1,
|
||||
},
|
||||
{
|
||||
net.ParseIP("192.168.0.10"),
|
||||
net.ParseIP("192.168.0.10"),
|
||||
0,
|
||||
},
|
||||
{
|
||||
net.ParseIP("192.168.0.10"),
|
||||
net.ParseIP("192.168.0.10").To4(),
|
||||
0,
|
||||
},
|
||||
{
|
||||
net.ParseIP("AB12::122"),
|
||||
net.ParseIP("AB12::123"),
|
||||
-1,
|
||||
},
|
||||
{
|
||||
net.ParseIP("AB12::210"),
|
||||
net.ParseIP("AB12::123"),
|
||||
1,
|
||||
},
|
||||
{
|
||||
net.ParseIP("AB12::210"),
|
||||
net.ParseIP("AB12::210"),
|
||||
0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
result := Cmp(test.a, test.b)
|
||||
|
||||
Expect(result).To(Equal(test.result))
|
||||
}
|
||||
})
|
||||
|
||||
It("Network", func() {
|
||||
testCases := []struct {
|
||||
ipNet *net.IPNet
|
||||
result *net.IPNet
|
||||
}{
|
||||
{
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
&net.IPNet{
|
||||
IP: nil,
|
||||
Mask: net.IPv4Mask(255, 255, 255, 0),
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
&net.IPNet{
|
||||
IP: net.IPv4(192, 168, 0, 1),
|
||||
Mask: nil,
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
&net.IPNet{
|
||||
IP: net.ParseIP("AB12::123"),
|
||||
Mask: net.IPv4Mask(255, 255, 255, 0),
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
&net.IPNet{
|
||||
IP: net.IPv4(192, 168, 0, 100).To4(),
|
||||
Mask: net.CIDRMask(120, 128),
|
||||
},
|
||||
&net.IPNet{
|
||||
IP: net.IPv4(192, 168, 0, 0).To4(),
|
||||
Mask: net.CIDRMask(120, 128),
|
||||
},
|
||||
},
|
||||
{
|
||||
&net.IPNet{
|
||||
IP: net.IPv4(192, 168, 0, 100),
|
||||
Mask: net.CIDRMask(24, 32),
|
||||
},
|
||||
&net.IPNet{
|
||||
IP: net.IPv4(192, 168, 0, 0).To4(),
|
||||
Mask: net.CIDRMask(24, 32),
|
||||
},
|
||||
},
|
||||
{
|
||||
&net.IPNet{
|
||||
IP: net.ParseIP("AB12::123"),
|
||||
Mask: net.CIDRMask(120, 128),
|
||||
},
|
||||
&net.IPNet{
|
||||
IP: net.ParseIP("AB12::100"),
|
||||
Mask: net.CIDRMask(120, 128),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
result := Network(test.ipNet)
|
||||
|
||||
Expect(result).To(Equal(test.result))
|
||||
}
|
||||
})
|
||||
})
|
104
pkg/ip/ip.go
Normal file
104
pkg/ip/ip.go
Normal file
@ -0,0 +1,104 @@
|
||||
// Copyright 2021 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ip
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IP is a CNI maintained type inherited from net.IPNet which can
|
||||
// represent a single IP address with or without prefix.
|
||||
type IP struct {
|
||||
net.IPNet
|
||||
}
|
||||
|
||||
// newIP will create an IP with net.IP and net.IPMask
|
||||
func newIP(ip net.IP, mask net.IPMask) *IP {
|
||||
return &IP{
|
||||
IPNet: net.IPNet{
|
||||
IP: ip,
|
||||
Mask: mask,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ParseIP will parse string s as an IP, and return it.
|
||||
// The string s must be formed like <ip>[/<prefix>].
|
||||
// If s is not a valid textual representation of an IP,
|
||||
// will return nil.
|
||||
func ParseIP(s string) *IP {
|
||||
if strings.ContainsAny(s, "/") {
|
||||
ip, ipNet, err := net.ParseCIDR(s)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return newIP(ip, ipNet.Mask)
|
||||
}
|
||||
ip := net.ParseIP(s)
|
||||
if ip == nil {
|
||||
return nil
|
||||
}
|
||||
return newIP(ip, nil)
|
||||
}
|
||||
|
||||
// ToIP will return a net.IP in standard form from this IP.
|
||||
// If this IP can not be converted to a valid net.IP, will return nil.
|
||||
func (i *IP) ToIP() net.IP {
|
||||
switch {
|
||||
case i.IP.To4() != nil:
|
||||
return i.IP.To4()
|
||||
case i.IP.To16() != nil:
|
||||
return i.IP.To16()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the string form of this IP.
|
||||
func (i *IP) String() string {
|
||||
if len(i.Mask) > 0 {
|
||||
return i.IPNet.String()
|
||||
}
|
||||
return i.IP.String()
|
||||
}
|
||||
|
||||
// MarshalText implements the encoding.TextMarshaler interface.
|
||||
// The encoding is the same as returned by String,
|
||||
// But when len(ip) is zero, will return an empty slice.
|
||||
func (i *IP) MarshalText() ([]byte, error) {
|
||||
if len(i.IP) == 0 {
|
||||
return []byte{}, nil
|
||||
}
|
||||
return []byte(i.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements the encoding.TextUnmarshaler interface.
|
||||
// The textual bytes are expected in a form accepted by Parse,
|
||||
// But when len(b) is zero, will return an empty IP.
|
||||
func (i *IP) UnmarshalText(b []byte) error {
|
||||
if len(b) == 0 {
|
||||
*i = IP{}
|
||||
return nil
|
||||
}
|
||||
|
||||
ip := ParseIP(string(b))
|
||||
if ip == nil {
|
||||
return fmt.Errorf("invalid IP address %s", string(b))
|
||||
}
|
||||
*i = *ip
|
||||
return nil
|
||||
}
|
@ -15,10 +15,10 @@
|
||||
package ip_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestIp(t *testing.T) {
|
||||
|
271
pkg/ip/ip_test.go
Normal file
271
pkg/ip/ip_test.go
Normal file
@ -0,0 +1,271 @@
|
||||
// Copyright 2021 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ip
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("IP Operations", func() {
|
||||
It("Parse", func() {
|
||||
testCases := []struct {
|
||||
ipStr string
|
||||
expected *IP
|
||||
}{
|
||||
{
|
||||
"192.168.0.10",
|
||||
newIP(net.IPv4(192, 168, 0, 10), nil),
|
||||
},
|
||||
{
|
||||
"2001:db8::1",
|
||||
newIP(net.ParseIP("2001:db8::1"), nil),
|
||||
},
|
||||
{
|
||||
"192.168.0.10/24",
|
||||
newIP(net.IPv4(192, 168, 0, 10), net.IPv4Mask(255, 255, 255, 0)),
|
||||
},
|
||||
{
|
||||
"2001:db8::1/64",
|
||||
newIP(net.ParseIP("2001:db8::1"), net.CIDRMask(64, 128)),
|
||||
},
|
||||
{
|
||||
"invalid",
|
||||
nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
ip := ParseIP(test.ipStr)
|
||||
|
||||
Expect(ip).To(Equal(test.expected))
|
||||
}
|
||||
})
|
||||
|
||||
It("String", func() {
|
||||
testCases := []struct {
|
||||
ip *IP
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
newIP(net.IPv4(192, 168, 0, 1), net.IPv4Mask(255, 255, 255, 0)),
|
||||
"192.168.0.1/24",
|
||||
},
|
||||
{
|
||||
newIP(net.IPv4(192, 168, 0, 2), nil),
|
||||
"192.168.0.2",
|
||||
},
|
||||
{
|
||||
newIP(net.ParseIP("2001:db8::1"), nil),
|
||||
"2001:db8::1",
|
||||
},
|
||||
{
|
||||
newIP(net.ParseIP("2001:db8::1"), net.CIDRMask(64, 128)),
|
||||
"2001:db8::1/64",
|
||||
},
|
||||
{
|
||||
newIP(nil, nil),
|
||||
"<nil>",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
Expect(test.ip.String()).To(Equal(test.expected))
|
||||
}
|
||||
})
|
||||
|
||||
It("ToIP", func() {
|
||||
testCases := []struct {
|
||||
ip *IP
|
||||
expectedLen int
|
||||
expectedIP net.IP
|
||||
}{
|
||||
{
|
||||
newIP(net.IPv4(192, 168, 0, 1), net.IPv4Mask(255, 255, 255, 0)),
|
||||
net.IPv4len,
|
||||
net.IP{192, 168, 0, 1},
|
||||
},
|
||||
{
|
||||
newIP(net.IPv4(192, 168, 0, 2), nil),
|
||||
net.IPv4len,
|
||||
net.IP{192, 168, 0, 2},
|
||||
},
|
||||
{
|
||||
newIP(net.ParseIP("2001:db8::1"), nil),
|
||||
net.IPv6len,
|
||||
net.IP{32, 1, 13, 184, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
|
||||
},
|
||||
{
|
||||
newIP(net.ParseIP("2001:db8::1"), net.CIDRMask(64, 128)),
|
||||
net.IPv6len,
|
||||
net.IP{32, 1, 13, 184, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
|
||||
},
|
||||
{
|
||||
newIP(nil, nil),
|
||||
0,
|
||||
nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
Expect(test.ip.ToIP()).To(HaveLen(test.expectedLen))
|
||||
Expect(test.ip.ToIP()).To(Equal(test.expectedIP))
|
||||
}
|
||||
})
|
||||
|
||||
It("Encode", func() {
|
||||
testCases := []struct {
|
||||
object interface{}
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
newIP(net.IPv4(192, 168, 0, 1), net.IPv4Mask(255, 255, 255, 0)),
|
||||
`"192.168.0.1/24"`,
|
||||
},
|
||||
{
|
||||
newIP(net.IPv4(192, 168, 0, 2), nil),
|
||||
`"192.168.0.2"`,
|
||||
},
|
||||
{
|
||||
newIP(net.ParseIP("2001:db8::1"), nil),
|
||||
`"2001:db8::1"`,
|
||||
},
|
||||
{
|
||||
newIP(net.ParseIP("2001:db8::1"), net.CIDRMask(64, 128)),
|
||||
`"2001:db8::1/64"`,
|
||||
},
|
||||
{
|
||||
newIP(nil, nil),
|
||||
`""`,
|
||||
},
|
||||
{
|
||||
[]*IP{
|
||||
newIP(net.IPv4(192, 168, 0, 1), net.IPv4Mask(255, 255, 255, 0)),
|
||||
newIP(net.IPv4(192, 168, 0, 2), nil),
|
||||
newIP(net.ParseIP("2001:db8::1"), nil),
|
||||
newIP(net.ParseIP("2001:db8::1"), net.CIDRMask(64, 128)),
|
||||
newIP(nil, nil),
|
||||
},
|
||||
`["192.168.0.1/24","192.168.0.2","2001:db8::1","2001:db8::1/64",""]`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
bytes, err := json.Marshal(test.object)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(string(bytes)).To(Equal(test.expected))
|
||||
}
|
||||
})
|
||||
|
||||
Context("Decode", func() {
|
||||
It("valid IP", func() {
|
||||
testCases := []struct {
|
||||
text string
|
||||
expected *IP
|
||||
}{
|
||||
{
|
||||
`"192.168.0.1"`,
|
||||
newIP(net.IPv4(192, 168, 0, 1), nil),
|
||||
},
|
||||
{
|
||||
`"192.168.0.1/24"`,
|
||||
newIP(net.IPv4(192, 168, 0, 1), net.IPv4Mask(255, 255, 255, 0)),
|
||||
},
|
||||
{
|
||||
`"2001:db8::1"`,
|
||||
newIP(net.ParseIP("2001:db8::1"), nil),
|
||||
},
|
||||
{
|
||||
`"2001:db8::1/64"`,
|
||||
newIP(net.ParseIP("2001:db8::1"), net.CIDRMask(64, 128)),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
ip := &IP{}
|
||||
err := json.Unmarshal([]byte(test.text), ip)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(ip).To(Equal(test.expected))
|
||||
}
|
||||
})
|
||||
|
||||
It("empty text", func() {
|
||||
ip := &IP{}
|
||||
err := json.Unmarshal([]byte(`""`), ip)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(ip).To(Equal(newIP(nil, nil)))
|
||||
})
|
||||
|
||||
It("invalid IP", func() {
|
||||
testCases := []struct {
|
||||
text string
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
`"192.168.0.1000"`,
|
||||
fmt.Errorf("invalid IP address 192.168.0.1000"),
|
||||
},
|
||||
{
|
||||
`"2001:db8::1/256"`,
|
||||
fmt.Errorf("invalid IP address 2001:db8::1/256"),
|
||||
},
|
||||
{
|
||||
`"test"`,
|
||||
fmt.Errorf("invalid IP address test"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
err := json.Unmarshal([]byte(test.text), &IP{})
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(Equal(test.expectedErr))
|
||||
}
|
||||
})
|
||||
|
||||
It("IP slice", func() {
|
||||
testCases := []struct {
|
||||
text string
|
||||
expected []*IP
|
||||
}{
|
||||
{
|
||||
`["192.168.0.1/24","192.168.0.2","2001:db8::1","2001:db8::1/64",""]`,
|
||||
[]*IP{
|
||||
newIP(net.IPv4(192, 168, 0, 1), net.IPv4Mask(255, 255, 255, 0)),
|
||||
newIP(net.IPv4(192, 168, 0, 2), nil),
|
||||
newIP(net.ParseIP("2001:db8::1"), nil),
|
||||
newIP(net.ParseIP("2001:db8::1"), net.CIDRMask(64, 128)),
|
||||
newIP(nil, nil),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
ips := make([]*IP, 0)
|
||||
err := json.Unmarshal([]byte(test.text), &ips)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(ips).To(Equal(test.expected))
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
@ -16,9 +16,9 @@ package ip
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
current "github.com/containernetworking/cni/pkg/types/100"
|
||||
)
|
||||
|
||||
func EnableIP4Forward() error {
|
||||
@ -36,12 +36,13 @@ func EnableForward(ips []*current.IPConfig) error {
|
||||
v6 := false
|
||||
|
||||
for _, ip := range ips {
|
||||
if ip.Version == "4" && !v4 {
|
||||
isV4 := ip.Address.IP.To4() != nil
|
||||
if isV4 && !v4 {
|
||||
if err := EnableIP4Forward(); err != nil {
|
||||
return err
|
||||
}
|
||||
v4 = true
|
||||
} else if ip.Version == "6" && !v6 {
|
||||
} else if !isV4 && !v6 {
|
||||
if err := EnableIP6Forward(); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -52,10 +53,10 @@ func EnableForward(ips []*current.IPConfig) error {
|
||||
}
|
||||
|
||||
func echo1(f string) error {
|
||||
if content, err := ioutil.ReadFile(f); err == nil {
|
||||
if content, err := os.ReadFile(f); err == nil {
|
||||
if bytes.Equal(bytes.TrimSpace(content), []byte("1")) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return ioutil.WriteFile(f, []byte("1"), 0644)
|
||||
return os.WriteFile(f, []byte("1"), 0o644)
|
||||
}
|
||||
|
@ -1,17 +1,16 @@
|
||||
package ip
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("IpforwardLinux", func() {
|
||||
It("echo1 must not write the file if content is 1", func() {
|
||||
file, err := ioutil.TempFile(os.TempDir(), "containernetworking")
|
||||
file, err := os.CreateTemp("", "containernetworking")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer os.Remove(file.Name())
|
||||
err = echo1(file.Name())
|
||||
|
@ -104,7 +104,6 @@ func TeardownIPMasq(ipn *net.IPNet, chain string, comment string) error {
|
||||
err = ipt.ClearChain("nat", chain)
|
||||
if err != nil && !isNotExist(err) {
|
||||
return err
|
||||
|
||||
}
|
||||
|
||||
err = ipt.DeleteChain("nat", chain)
|
||||
|
@ -25,27 +25,32 @@ import (
|
||||
"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 (
|
||||
ErrLinkNotFound = errors.New("link not found")
|
||||
)
|
||||
var ErrLinkNotFound = errors.New("link not found")
|
||||
|
||||
func makeVethPair(name, peer string, mtu int) (netlink.Link, error) {
|
||||
// makeVethPair is called from within the container's network namespace
|
||||
func makeVethPair(name, peer string, mtu int, mac string, hostNS ns.NetNS) (netlink.Link, error) {
|
||||
veth := &netlink.Veth{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: name,
|
||||
Flags: net.FlagUp,
|
||||
MTU: mtu,
|
||||
},
|
||||
PeerName: peer,
|
||||
PeerNamespace: netlink.NsFd(int(hostNS.Fd())),
|
||||
}
|
||||
if mac != "" {
|
||||
m, err := net.ParseMAC(mac)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
veth.LinkAttrs.HardwareAddr = m
|
||||
}
|
||||
if err := netlink.LinkAdd(veth); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Re-fetch the link to get its creation-time parameters, e.g. index and mac
|
||||
// Re-fetch the container link to get its creation-time parameters, e.g. index and mac
|
||||
veth2, err := netlink.LinkByName(name)
|
||||
if err != nil {
|
||||
netlink.LinkDel(veth) // try and clean up the link if possible.
|
||||
@ -62,44 +67,43 @@ func peerExists(name string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func makeVeth(name, vethPeerName string, mtu int) (peerName string, veth netlink.Link, err error) {
|
||||
func makeVeth(name, vethPeerName string, mtu int, mac string, hostNS ns.NetNS) (string, netlink.Link, error) {
|
||||
var peerName string
|
||||
var veth netlink.Link
|
||||
var err error
|
||||
for i := 0; i < 10; i++ {
|
||||
if vethPeerName != "" {
|
||||
peerName = vethPeerName
|
||||
} else {
|
||||
peerName, err = RandomVethName()
|
||||
if err != nil {
|
||||
return
|
||||
return peerName, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
veth, err = makeVethPair(name, peerName, mtu)
|
||||
veth, err = makeVethPair(name, peerName, mtu, mac, hostNS)
|
||||
switch {
|
||||
case err == nil:
|
||||
return
|
||||
return peerName, veth, nil
|
||||
|
||||
case os.IsExist(err):
|
||||
if peerExists(peerName) && vethPeerName == "" {
|
||||
continue
|
||||
}
|
||||
err = fmt.Errorf("container veth name provided (%v) already exists", name)
|
||||
return
|
||||
|
||||
return peerName, veth, fmt.Errorf("container veth name provided (%v) already exists", name)
|
||||
default:
|
||||
err = fmt.Errorf("failed to make veth pair: %v", err)
|
||||
return
|
||||
return peerName, veth, fmt.Errorf("failed to make veth pair: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// should really never be hit
|
||||
err = fmt.Errorf("failed to find a unique veth name")
|
||||
return
|
||||
return peerName, nil, fmt.Errorf("failed to find a unique veth name")
|
||||
}
|
||||
|
||||
// RandomVethName returns string "veth" with random prefix (hashed from entropy)
|
||||
func RandomVethName() (string, error) {
|
||||
entropy := make([]byte, 4)
|
||||
_, err := rand.Reader.Read(entropy)
|
||||
_, err := rand.Read(entropy)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to generate random veth name: %v", err)
|
||||
}
|
||||
@ -132,25 +136,13 @@ func ifaceFromNetlinkLink(l netlink.Link) net.Interface {
|
||||
// devices and move the host-side veth into the provided hostNS namespace.
|
||||
// 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)
|
||||
func SetupVethWithName(contVethName, hostVethName string, mtu int, contVethMac string, hostNS ns.NetNS) (net.Interface, net.Interface, error) {
|
||||
hostVethName, contVeth, err := makeVeth(contVethName, hostVethName, mtu, contVethMac, hostNS)
|
||||
if err != nil {
|
||||
return net.Interface{}, net.Interface{}, err
|
||||
}
|
||||
|
||||
if err = netlink.LinkSetUp(contVeth); err != nil {
|
||||
return net.Interface{}, net.Interface{}, fmt.Errorf("failed to set %q up: %v", contVethName, err)
|
||||
}
|
||||
|
||||
hostVeth, err := netlink.LinkByName(hostVethName)
|
||||
if err != nil {
|
||||
return net.Interface{}, net.Interface{}, fmt.Errorf("failed to lookup %q: %v", hostVethName, err)
|
||||
}
|
||||
|
||||
if err = netlink.LinkSetNsFd(hostVeth, int(hostNS.Fd())); err != nil {
|
||||
return net.Interface{}, net.Interface{}, fmt.Errorf("failed to move veth to host netns: %v", err)
|
||||
}
|
||||
|
||||
var hostVeth netlink.Link
|
||||
err = hostNS.Do(func(_ ns.NetNS) error {
|
||||
hostVeth, err = netlink.LinkByName(hostVethName)
|
||||
if err != nil {
|
||||
@ -175,8 +167,8 @@ func SetupVethWithName(contVethName, hostVethName string, mtu int, hostNS ns.Net
|
||||
// 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)
|
||||
func SetupVeth(contVethName string, mtu int, contVethMac string, hostNS ns.NetNS) (net.Interface, net.Interface, error) {
|
||||
return SetupVethWithName(contVethName, "", mtu, contVethMac, hostNS)
|
||||
}
|
||||
|
||||
// DelLinkByName removes an interface link.
|
||||
@ -225,33 +217,6 @@ func DelLinkByNameAddr(ifName string) ([]*net.IPNet, error) {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func SetHWAddrByIP(ifName string, ip4 net.IP, ip6 net.IP) error {
|
||||
iface, err := netlink.LinkByName(ifName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to lookup %q: %v", ifName, err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case ip4 == nil && ip6 == nil:
|
||||
return fmt.Errorf("neither ip4 or ip6 specified")
|
||||
|
||||
case ip4 != nil:
|
||||
{
|
||||
hwAddr, err := hwaddr.GenerateHardwareAddr4(ip4, hwaddr.PrivateMACPrefix)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate hardware addr: %v", err)
|
||||
}
|
||||
if err = netlink.LinkSetHardwareAddr(iface, hwAddr); err != nil {
|
||||
return fmt.Errorf("failed to add hardware addr to %q: %v", ifName, err)
|
||||
}
|
||||
}
|
||||
case ip6 != nil:
|
||||
// TODO: IPv6
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetVethPeerIfindex returns the veth link object, the peer ifindex of the
|
||||
// veth, or an error. This peer ifindex will only be valid in the peer's
|
||||
// network namespace.
|
||||
|
@ -20,22 +20,15 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ip"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/testutils"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
func getHwAddr(linkname string) string {
|
||||
veth, err := netlink.LinkByName(linkname)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return fmt.Sprintf("%s", veth.Attrs().HardwareAddr)
|
||||
}
|
||||
|
||||
var _ = Describe("Link", func() {
|
||||
const (
|
||||
ifaceFormatString string = "i%d"
|
||||
@ -51,8 +44,6 @@ var _ = Describe("Link", func() {
|
||||
hostVethName string
|
||||
containerVethName string
|
||||
|
||||
ip4one = net.ParseIP("1.1.1.1")
|
||||
ip4two = net.ParseIP("1.1.1.2")
|
||||
originalRandReader = rand.Reader
|
||||
)
|
||||
|
||||
@ -66,13 +57,13 @@ var _ = Describe("Link", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
fakeBytes := make([]byte, 20)
|
||||
//to be reset in AfterEach block
|
||||
// to be reset in AfterEach block
|
||||
rand.Reader = bytes.NewReader(fakeBytes)
|
||||
|
||||
_ = containerNetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
hostVeth, containerVeth, err = ip.SetupVeth(fmt.Sprintf(ifaceFormatString, ifaceCounter), mtu, hostNetNS)
|
||||
hostVeth, containerVeth, err = ip.SetupVeth(fmt.Sprintf(ifaceFormatString, ifaceCounter), mtu, "", hostNetNS)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -159,7 +150,7 @@ var _ = Describe("Link", func() {
|
||||
_ = containerNetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err := ip.SetupVeth(containerVethName, mtu, hostNetNS)
|
||||
_, _, err := ip.SetupVeth(containerVethName, mtu, "", hostNetNS)
|
||||
Expect(err.Error()).To(Equal(fmt.Sprintf("container veth name provided (%s) already exists", containerVethName)))
|
||||
|
||||
return nil
|
||||
@ -183,15 +174,15 @@ var _ = Describe("Link", func() {
|
||||
|
||||
Context("when there is no name available for the host-side", func() {
|
||||
BeforeEach(func() {
|
||||
//adding different interface to container ns
|
||||
// adding different interface to container ns
|
||||
containerVethName += "0"
|
||||
})
|
||||
It("returns useful error", func() {
|
||||
_ = containerNetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
_, _, err := ip.SetupVeth(containerVethName, mtu, hostNetNS)
|
||||
Expect(err.Error()).To(HavePrefix("failed to move veth to host netns: "))
|
||||
|
||||
_, _, err := ip.SetupVeth(containerVethName, mtu, "", hostNetNS)
|
||||
Expect(err.Error()).To(HavePrefix("container veth name provided"))
|
||||
Expect(err.Error()).To(HaveSuffix("already exists"))
|
||||
return nil
|
||||
})
|
||||
})
|
||||
@ -199,7 +190,7 @@ var _ = Describe("Link", func() {
|
||||
|
||||
Context("when there is no name conflict for the host or container interfaces", func() {
|
||||
BeforeEach(func() {
|
||||
//adding different interface to container and host ns
|
||||
// adding different interface to container and host ns
|
||||
containerVethName += "0"
|
||||
rand.Reader = originalRandReader
|
||||
})
|
||||
@ -207,13 +198,13 @@ var _ = Describe("Link", func() {
|
||||
_ = containerNetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
hostVeth, _, err := ip.SetupVeth(containerVethName, mtu, hostNetNS)
|
||||
hostVeth, _, err := ip.SetupVeth(containerVethName, mtu, "", hostNetNS)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
hostVethName = hostVeth.Name
|
||||
return nil
|
||||
})
|
||||
|
||||
//verify veths are in different namespaces
|
||||
// verify veths are in different namespaces
|
||||
_ = containerNetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
@ -233,6 +224,32 @@ var _ = Describe("Link", func() {
|
||||
})
|
||||
})
|
||||
|
||||
It("successfully creates a veth pair with an explicit mac", func() {
|
||||
const mac = "02:00:00:00:01:23"
|
||||
_ = containerNetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
hostVeth, _, err := ip.SetupVeth(containerVethName, mtu, mac, hostNetNS)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
hostVethName = hostVeth.Name
|
||||
|
||||
link, err := netlink.LinkByName(containerVethName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr.String()).To(Equal(mac))
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
_ = hostNetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
link, err := netlink.LinkByName(hostVethName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr.String()).NotTo(Equal(mac))
|
||||
|
||||
return nil
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
It("DelLinkByName must delete the veth endpoints", func() {
|
||||
@ -266,44 +283,7 @@ var _ = Describe("Link", func() {
|
||||
// this will delete the host endpoint too
|
||||
addr, err := ip.DelLinkByNameAddr(containerVethName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(addr).To(HaveLen(0))
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
It("SetHWAddrByIP must change the interface hwaddr and be predictable", func() {
|
||||
|
||||
_ = containerNetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
var err error
|
||||
hwaddrBefore := getHwAddr(containerVethName)
|
||||
|
||||
err = ip.SetHWAddrByIP(containerVethName, ip4one, nil)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
hwaddrAfter1 := getHwAddr(containerVethName)
|
||||
|
||||
Expect(hwaddrBefore).NotTo(Equal(hwaddrAfter1))
|
||||
Expect(hwaddrAfter1).To(Equal(ip4onehwaddr))
|
||||
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
It("SetHWAddrByIP must be injective", func() {
|
||||
|
||||
_ = containerNetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
err := ip.SetHWAddrByIP(containerVethName, ip4one, nil)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
hwaddrAfter1 := getHwAddr(containerVethName)
|
||||
|
||||
err = ip.SetHWAddrByIP(containerVethName, ip4two, nil)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
hwaddrAfter2 := getHwAddr(containerVethName)
|
||||
|
||||
Expect(hwaddrAfter1).NotTo(Equal(hwaddrAfter2))
|
||||
Expect(addr).To(BeEmpty())
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
@ -42,6 +42,11 @@ func AddHostRoute(ipn *net.IPNet, gw net.IP, dev netlink.Link) error {
|
||||
|
||||
// AddDefaultRoute sets the default route on the given gateway.
|
||||
func AddDefaultRoute(gw net.IP, dev netlink.Link) error {
|
||||
_, defNet, _ := net.ParseCIDR("0.0.0.0/0")
|
||||
var defNet *net.IPNet
|
||||
if gw.To4() != nil {
|
||||
_, defNet, _ = net.ParseCIDR("0.0.0.0/0")
|
||||
} else {
|
||||
_, defNet, _ = net.ParseCIDR("::/0")
|
||||
}
|
||||
return AddRoute(defNet, gw, dev)
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
// Copyright 2016 CNI authors
|
||||
@ -20,13 +21,13 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
current "github.com/containernetworking/cni/pkg/types/100"
|
||||
)
|
||||
|
||||
func ValidateExpectedInterfaceIPs(ifName string, resultIPs []*current.IPConfig) error {
|
||||
|
||||
// Ensure ips
|
||||
for _, ips := range resultIPs {
|
||||
ourAddr := netlink.Addr{IPNet: &ips.Address}
|
||||
@ -48,24 +49,22 @@ func ValidateExpectedInterfaceIPs(ifName string, resultIPs []*current.IPConfig)
|
||||
break
|
||||
}
|
||||
}
|
||||
if match == false {
|
||||
if !match {
|
||||
return fmt.Errorf("Failed to match addr %v on interface %v", ourAddr, ifName)
|
||||
}
|
||||
|
||||
// Convert the host/prefixlen to just prefix for route lookup.
|
||||
_, ourPrefix, err := net.ParseCIDR(ourAddr.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
findGwy := &netlink.Route{Dst: ourPrefix}
|
||||
routeFilter := netlink.RT_FILTER_DST
|
||||
var family int
|
||||
|
||||
switch {
|
||||
case ips.Version == "4":
|
||||
family := netlink.FAMILY_V6
|
||||
if ips.Address.IP.To4() != nil {
|
||||
family = netlink.FAMILY_V4
|
||||
case ips.Version == "6":
|
||||
family = netlink.FAMILY_V6
|
||||
default:
|
||||
return fmt.Errorf("Invalid IP Version %v for interface %v", ips.Version, ifName)
|
||||
}
|
||||
|
||||
gwy, err := netlink.RouteListFiltered(family, findGwy, routeFilter)
|
||||
@ -81,11 +80,13 @@ func ValidateExpectedInterfaceIPs(ifName string, resultIPs []*current.IPConfig)
|
||||
}
|
||||
|
||||
func ValidateExpectedRoute(resultRoutes []*types.Route) error {
|
||||
|
||||
// Ensure that each static route in prevResults is found in the routing table
|
||||
for _, route := range resultRoutes {
|
||||
find := &netlink.Route{Dst: &route.Dst, Gw: route.GW}
|
||||
routeFilter := netlink.RT_FILTER_DST | netlink.RT_FILTER_GW
|
||||
routeFilter := netlink.RT_FILTER_DST
|
||||
if route.GW != nil {
|
||||
routeFilter |= netlink.RT_FILTER_GW
|
||||
}
|
||||
var family int
|
||||
|
||||
switch {
|
||||
|
@ -16,6 +16,7 @@ package ipam
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/invoke"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
)
|
||||
|
@ -19,15 +19,16 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
current "github.com/containernetworking/cni/pkg/types/100"
|
||||
"github.com/containernetworking/plugins/pkg/ip"
|
||||
"github.com/containernetworking/plugins/pkg/utils/sysctl"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
const (
|
||||
DisableIPv6SysctlTemplate = "net.ipv6.conf.%s.disable_ipv6"
|
||||
// Note: use slash as separator so we can have dots in interface name (VLANs)
|
||||
DisableIPv6SysctlTemplate = "net/ipv6/conf/%s/disable_ipv6"
|
||||
)
|
||||
|
||||
// ConfigureIface takes the result of IPAM plugin and
|
||||
@ -42,12 +43,8 @@ func ConfigureIface(ifName string, res *current.Result) error {
|
||||
return fmt.Errorf("failed to lookup %q: %v", ifName, err)
|
||||
}
|
||||
|
||||
if err := netlink.LinkSetUp(link); err != nil {
|
||||
return fmt.Errorf("failed to set %q UP: %v", ifName, err)
|
||||
}
|
||||
|
||||
var v4gw, v6gw net.IP
|
||||
var has_enabled_ipv6 bool = false
|
||||
hasEnabledIpv6 := false
|
||||
for _, ipc := range res.IPs {
|
||||
if ipc.Interface == nil {
|
||||
continue
|
||||
@ -60,7 +57,7 @@ func ConfigureIface(ifName string, res *current.Result) error {
|
||||
|
||||
// Make sure sysctl "disable_ipv6" is 0 if we are about to add
|
||||
// an IPv6 address to the interface
|
||||
if !has_enabled_ipv6 && ipc.Version == "6" {
|
||||
if !hasEnabledIpv6 && ipc.Address.IP.To4() == nil {
|
||||
// Enabled IPv6 for loopback "lo" and the interface
|
||||
// being configured
|
||||
for _, iface := range [2]string{"lo", ifName} {
|
||||
@ -68,8 +65,11 @@ func ConfigureIface(ifName string, res *current.Result) error {
|
||||
|
||||
// Read current sysctl value
|
||||
value, err := sysctl.Sysctl(ipv6SysctlValueName)
|
||||
if err != nil || value == "0" {
|
||||
// FIXME: log warning if unable to read sysctl value
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ipam_linux: failed to read sysctl %q: %v\n", ipv6SysctlValueName, err)
|
||||
continue
|
||||
}
|
||||
if value == "0" {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -79,7 +79,7 @@ func ConfigureIface(ifName string, res *current.Result) error {
|
||||
return fmt.Errorf("failed to enable IPv6 for interface %q (%s=%s): %v", iface, ipv6SysctlValueName, value, err)
|
||||
}
|
||||
}
|
||||
has_enabled_ipv6 = true
|
||||
hasEnabledIpv6 = true
|
||||
}
|
||||
|
||||
addr := &netlink.Addr{IPNet: &ipc.Address, Label: ""}
|
||||
@ -95,6 +95,10 @@ func ConfigureIface(ifName string, res *current.Result) error {
|
||||
}
|
||||
}
|
||||
|
||||
if err := netlink.LinkSetUp(link); err != nil {
|
||||
return fmt.Errorf("failed to set %q UP: %v", ifName, err)
|
||||
}
|
||||
|
||||
if v6gw != nil {
|
||||
ip.SettleAddresses(ifName, 10)
|
||||
}
|
||||
@ -109,11 +113,14 @@ func ConfigureIface(ifName string, res *current.Result) error {
|
||||
gw = v6gw
|
||||
}
|
||||
}
|
||||
if err = ip.AddRoute(&r.Dst, gw, link); err != nil {
|
||||
// we skip over duplicate routes as we assume the first one wins
|
||||
if !os.IsExist(err) {
|
||||
return fmt.Errorf("failed to add route '%v via %v dev %v': %v", r.Dst, gw, ifName, err)
|
||||
route := netlink.Route{
|
||||
Dst: &r.Dst,
|
||||
LinkIndex: link.Attrs().Index,
|
||||
Gw: gw,
|
||||
}
|
||||
|
||||
if err = netlink.RouteAddEcmp(&route); err != nil {
|
||||
return fmt.Errorf("failed to add route '%v via %v dev %v': %v", r.Dst, gw, ifName, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,15 +18,14 @@ import (
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/testutils"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
current "github.com/containernetworking/cni/pkg/types/100"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/testutils"
|
||||
)
|
||||
|
||||
const LINK_NAME = "eth0"
|
||||
@ -109,13 +108,11 @@ var _ = Describe("ConfigureIface", func() {
|
||||
},
|
||||
IPs: []*current.IPConfig{
|
||||
{
|
||||
Version: "4",
|
||||
Interface: current.Int(0),
|
||||
Address: *ipv4,
|
||||
Gateway: ipgw4,
|
||||
},
|
||||
{
|
||||
Version: "6",
|
||||
Interface: current.Int(0),
|
||||
Address: *ipv6,
|
||||
Gateway: ipgw6,
|
||||
@ -145,12 +142,12 @@ var _ = Describe("ConfigureIface", func() {
|
||||
|
||||
v4addrs, err := netlink.AddrList(link, syscall.AF_INET)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(v4addrs)).To(Equal(1))
|
||||
Expect(ipNetEqual(v4addrs[0].IPNet, ipv4)).To(Equal(true))
|
||||
Expect(v4addrs).To(HaveLen(1))
|
||||
Expect(ipNetEqual(v4addrs[0].IPNet, ipv4)).To(BeTrue())
|
||||
|
||||
v6addrs, err := netlink.AddrList(link, syscall.AF_INET6)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(v6addrs)).To(Equal(2))
|
||||
Expect(v6addrs).To(HaveLen(2))
|
||||
|
||||
var found bool
|
||||
for _, a := range v6addrs {
|
||||
@ -159,7 +156,7 @@ var _ = Describe("ConfigureIface", func() {
|
||||
break
|
||||
}
|
||||
}
|
||||
Expect(found).To(Equal(true))
|
||||
Expect(found).To(BeTrue())
|
||||
|
||||
// Ensure the v4 route, v6 route, and subnet route
|
||||
routes, err := netlink.RouteList(link, 0)
|
||||
@ -179,8 +176,8 @@ var _ = Describe("ConfigureIface", func() {
|
||||
break
|
||||
}
|
||||
}
|
||||
Expect(v4found).To(Equal(true))
|
||||
Expect(v6found).To(Equal(true))
|
||||
Expect(v4found).To(BeTrue())
|
||||
Expect(v6found).To(BeTrue())
|
||||
|
||||
return nil
|
||||
})
|
||||
@ -218,8 +215,8 @@ var _ = Describe("ConfigureIface", func() {
|
||||
break
|
||||
}
|
||||
}
|
||||
Expect(v4found).To(Equal(true))
|
||||
Expect(v6found).To(Equal(true))
|
||||
Expect(v4found).To(BeTrue())
|
||||
Expect(v6found).To(BeTrue())
|
||||
|
||||
return nil
|
||||
})
|
||||
@ -281,12 +278,10 @@ var _ = Describe("ConfigureIface", func() {
|
||||
},
|
||||
IPs: []*current.IPConfig{
|
||||
{
|
||||
Version: "4",
|
||||
Address: *ipv4,
|
||||
Gateway: ipgw4,
|
||||
},
|
||||
{
|
||||
Version: "6",
|
||||
Address: *ipv6,
|
||||
Gateway: ipgw6,
|
||||
},
|
||||
|
@ -15,10 +15,10 @@
|
||||
package ipam_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestIpam(t *testing.T) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2016 CNI authors
|
||||
// Copyright 2021 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@ -12,16 +12,16 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package hwaddr_test
|
||||
package link_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestHwaddr(t *testing.T) {
|
||||
func TestIp(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "pkg/utils/hwaddr")
|
||||
RunSpecs(t, "pkg/link")
|
||||
}
|
270
pkg/link/spoofcheck.go
Normal file
270
pkg/link/spoofcheck.go
Normal file
@ -0,0 +1,270 @@
|
||||
// Copyright 2021 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package link
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/networkplumbing/go-nft/nft"
|
||||
"github.com/networkplumbing/go-nft/nft/schema"
|
||||
)
|
||||
|
||||
const (
|
||||
natTableName = "nat"
|
||||
preRoutingBaseChainName = "PREROUTING"
|
||||
)
|
||||
|
||||
type NftConfigurer interface {
|
||||
Apply(*nft.Config) (*nft.Config, error)
|
||||
Read(filterCommands ...string) (*nft.Config, error)
|
||||
}
|
||||
|
||||
type SpoofChecker struct {
|
||||
iface string
|
||||
macAddress string
|
||||
refID string
|
||||
configurer NftConfigurer
|
||||
rulestore *nft.Config
|
||||
}
|
||||
|
||||
type defaultNftConfigurer struct{}
|
||||
|
||||
func (dnc defaultNftConfigurer) Apply(cfg *nft.Config) (*nft.Config, error) {
|
||||
const timeout = 55 * time.Second
|
||||
ctxWithTimeout, cancelFunc := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancelFunc()
|
||||
return nft.ApplyConfigEcho(ctxWithTimeout, cfg)
|
||||
}
|
||||
|
||||
func (dnc defaultNftConfigurer) Read(filterCommands ...string) (*nft.Config, error) {
|
||||
const timeout = 55 * time.Second
|
||||
ctxWithTimeout, cancelFunc := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancelFunc()
|
||||
return nft.ReadConfigContext(ctxWithTimeout, filterCommands...)
|
||||
}
|
||||
|
||||
func NewSpoofChecker(iface, macAddress, refID string) *SpoofChecker {
|
||||
return NewSpoofCheckerWithConfigurer(iface, macAddress, refID, defaultNftConfigurer{})
|
||||
}
|
||||
|
||||
func NewSpoofCheckerWithConfigurer(iface, macAddress, refID string, configurer NftConfigurer) *SpoofChecker {
|
||||
return &SpoofChecker{iface, macAddress, refID, configurer, nil}
|
||||
}
|
||||
|
||||
// Setup applies nftables configuration to restrict traffic
|
||||
// from the provided interface. Only traffic with the mentioned mac address
|
||||
// is allowed to pass, all others are blocked.
|
||||
// The configuration follows the format libvirt and ebtables implemented, allowing
|
||||
// extensions to the rules in the future.
|
||||
// refID is used to label the rules with a unique comment, identifying the rule-set.
|
||||
//
|
||||
// In order to take advantage of the nftables configuration change atomicity, the
|
||||
// following steps are taken to apply the configuration:
|
||||
// - Declare the table and chains (they will be created in case not present).
|
||||
// - Apply the rules, while first flushing the iface/mac specific regular chain rules.
|
||||
// Two transactions are used because the flush succeeds only if the table/chain it targets
|
||||
// exists. This avoids the need to query the existing state and acting upon it (a raceful pattern).
|
||||
// Although two transactions are taken place, only the 2nd one where the rules
|
||||
// are added has a real impact on the system.
|
||||
func (sc *SpoofChecker) Setup() error {
|
||||
baseConfig := nft.NewConfig()
|
||||
|
||||
baseConfig.AddTable(&schema.Table{Family: schema.FamilyBridge, Name: natTableName})
|
||||
|
||||
baseConfig.AddChain(sc.baseChain())
|
||||
ifaceChain := sc.ifaceChain()
|
||||
baseConfig.AddChain(ifaceChain)
|
||||
macChain := sc.macChain(ifaceChain.Name)
|
||||
baseConfig.AddChain(macChain)
|
||||
|
||||
if _, err := sc.configurer.Apply(baseConfig); err != nil {
|
||||
return fmt.Errorf("failed to setup spoof-check: %v", err)
|
||||
}
|
||||
|
||||
rulesConfig := nft.NewConfig()
|
||||
|
||||
rulesConfig.FlushChain(ifaceChain)
|
||||
rulesConfig.FlushChain(macChain)
|
||||
|
||||
rulesConfig.AddRule(sc.matchIfaceJumpToChainRule(preRoutingBaseChainName, ifaceChain.Name))
|
||||
rulesConfig.AddRule(sc.jumpToChainRule(ifaceChain.Name, macChain.Name))
|
||||
rulesConfig.AddRule(sc.matchMacRule(macChain.Name))
|
||||
rulesConfig.AddRule(sc.dropRule(macChain.Name))
|
||||
|
||||
rulestore, err := sc.configurer.Apply(rulesConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to setup spoof-check: %v", err)
|
||||
}
|
||||
sc.rulestore = rulestore
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sc *SpoofChecker) findPreroutingRule(ruleToFind *schema.Rule) ([]*schema.Rule, error) {
|
||||
ruleset := sc.rulestore
|
||||
if ruleset == nil {
|
||||
chain, err := sc.configurer.Read(listChainBridgeNatPrerouting()...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ruleset = chain
|
||||
}
|
||||
return ruleset.LookupRule(ruleToFind), nil
|
||||
}
|
||||
|
||||
// Teardown removes the interface and mac-address specific chains and their rules.
|
||||
// The table and base-chain are expected to survive while the base-chain rule that matches the
|
||||
// interface is removed.
|
||||
func (sc *SpoofChecker) Teardown() error {
|
||||
ifaceChain := sc.ifaceChain()
|
||||
expectedRuleToFind := sc.matchIfaceJumpToChainRule(preRoutingBaseChainName, ifaceChain.Name)
|
||||
// It is safer to exclude the statement matching, avoiding cases where a current statement includes
|
||||
// additional default entries (e.g. counters).
|
||||
ruleToFindExcludingStatements := *expectedRuleToFind
|
||||
ruleToFindExcludingStatements.Expr = nil
|
||||
|
||||
rules, ifaceMatchRuleErr := sc.findPreroutingRule(&ruleToFindExcludingStatements)
|
||||
if ifaceMatchRuleErr == nil && len(rules) > 0 {
|
||||
c := nft.NewConfig()
|
||||
for _, rule := range rules {
|
||||
c.DeleteRule(rule)
|
||||
}
|
||||
if _, err := sc.configurer.Apply(c); err != nil {
|
||||
ifaceMatchRuleErr = fmt.Errorf("failed to delete iface match rule: %v", err)
|
||||
}
|
||||
// Drop the cache, it should contain deleted rule(s) now
|
||||
sc.rulestore = nil
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "spoofcheck/teardown: unable to detect iface match rule for deletion: %+v", expectedRuleToFind)
|
||||
}
|
||||
|
||||
regularChainsConfig := nft.NewConfig()
|
||||
regularChainsConfig.DeleteChain(ifaceChain)
|
||||
regularChainsConfig.DeleteChain(sc.macChain(ifaceChain.Name))
|
||||
|
||||
var regularChainsErr error
|
||||
if _, err := sc.configurer.Apply(regularChainsConfig); err != nil {
|
||||
regularChainsErr = fmt.Errorf("failed to delete regular chains: %v", err)
|
||||
}
|
||||
|
||||
if ifaceMatchRuleErr != nil || regularChainsErr != nil {
|
||||
return fmt.Errorf("failed to teardown spoof-check: %v, %v", ifaceMatchRuleErr, regularChainsErr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sc *SpoofChecker) matchIfaceJumpToChainRule(chain, toChain string) *schema.Rule {
|
||||
return &schema.Rule{
|
||||
Family: schema.FamilyBridge,
|
||||
Table: natTableName,
|
||||
Chain: chain,
|
||||
Expr: []schema.Statement{
|
||||
{Match: &schema.Match{
|
||||
Op: schema.OperEQ,
|
||||
Left: schema.Expression{RowData: []byte(`{"meta":{"key":"iifname"}}`)},
|
||||
Right: schema.Expression{String: &sc.iface},
|
||||
}},
|
||||
{Verdict: schema.Verdict{Jump: &schema.ToTarget{Target: toChain}}},
|
||||
},
|
||||
Comment: ruleComment(sc.refID),
|
||||
}
|
||||
}
|
||||
|
||||
func (sc *SpoofChecker) jumpToChainRule(chain, toChain string) *schema.Rule {
|
||||
return &schema.Rule{
|
||||
Family: schema.FamilyBridge,
|
||||
Table: natTableName,
|
||||
Chain: chain,
|
||||
Expr: []schema.Statement{
|
||||
{Verdict: schema.Verdict{Jump: &schema.ToTarget{Target: toChain}}},
|
||||
},
|
||||
Comment: ruleComment(sc.refID),
|
||||
}
|
||||
}
|
||||
|
||||
func (sc *SpoofChecker) matchMacRule(chain string) *schema.Rule {
|
||||
return &schema.Rule{
|
||||
Family: schema.FamilyBridge,
|
||||
Table: natTableName,
|
||||
Chain: chain,
|
||||
Expr: []schema.Statement{
|
||||
{Match: &schema.Match{
|
||||
Op: schema.OperEQ,
|
||||
Left: schema.Expression{Payload: &schema.Payload{
|
||||
Protocol: schema.PayloadProtocolEther,
|
||||
Field: schema.PayloadFieldEtherSAddr,
|
||||
}},
|
||||
Right: schema.Expression{String: &sc.macAddress},
|
||||
}},
|
||||
{Verdict: schema.Verdict{SimpleVerdict: schema.SimpleVerdict{Return: true}}},
|
||||
},
|
||||
Comment: ruleComment(sc.refID),
|
||||
}
|
||||
}
|
||||
|
||||
func (sc *SpoofChecker) dropRule(chain string) *schema.Rule {
|
||||
return &schema.Rule{
|
||||
Family: schema.FamilyBridge,
|
||||
Table: natTableName,
|
||||
Chain: chain,
|
||||
Expr: []schema.Statement{
|
||||
{Verdict: schema.Verdict{SimpleVerdict: schema.SimpleVerdict{Drop: true}}},
|
||||
},
|
||||
Comment: ruleComment(sc.refID),
|
||||
}
|
||||
}
|
||||
|
||||
func (sc *SpoofChecker) baseChain() *schema.Chain {
|
||||
chainPriority := -300
|
||||
return &schema.Chain{
|
||||
Family: schema.FamilyBridge,
|
||||
Table: natTableName,
|
||||
Name: preRoutingBaseChainName,
|
||||
Type: schema.TypeFilter,
|
||||
Hook: schema.HookPreRouting,
|
||||
Prio: &chainPriority,
|
||||
Policy: schema.PolicyAccept,
|
||||
}
|
||||
}
|
||||
|
||||
func (sc *SpoofChecker) ifaceChain() *schema.Chain {
|
||||
ifaceChainName := "cni-br-iface-" + sc.refID
|
||||
return &schema.Chain{
|
||||
Family: schema.FamilyBridge,
|
||||
Table: natTableName,
|
||||
Name: ifaceChainName,
|
||||
}
|
||||
}
|
||||
|
||||
func (sc *SpoofChecker) macChain(ifaceChainName string) *schema.Chain {
|
||||
macChainName := ifaceChainName + "-mac"
|
||||
return &schema.Chain{
|
||||
Family: schema.FamilyBridge,
|
||||
Table: natTableName,
|
||||
Name: macChainName,
|
||||
}
|
||||
}
|
||||
|
||||
func ruleComment(id string) string {
|
||||
const refIDPrefix = "macspoofchk-"
|
||||
return refIDPrefix + id
|
||||
}
|
||||
|
||||
func listChainBridgeNatPrerouting() []string {
|
||||
return []string{"chain", "bridge", natTableName, preRoutingBaseChainName}
|
||||
}
|
322
pkg/link/spoofcheck_test.go
Normal file
322
pkg/link/spoofcheck_test.go
Normal file
@ -0,0 +1,322 @@
|
||||
// Copyright 2021 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package link_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/networkplumbing/go-nft/nft"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/link"
|
||||
)
|
||||
|
||||
var _ = Describe("spoofcheck", func() {
|
||||
iface := "net0"
|
||||
mac := "02:00:00:00:12:34"
|
||||
id := "container99-net1"
|
||||
|
||||
Context("setup", func() {
|
||||
It("succeeds", func() {
|
||||
c := configurerStub{}
|
||||
sc := link.NewSpoofCheckerWithConfigurer(iface, mac, id, &c)
|
||||
Expect(sc.Setup()).To(Succeed())
|
||||
|
||||
assertExpectedTableAndChainsInSetupConfig(c)
|
||||
assertExpectedRulesInSetupConfig(c)
|
||||
})
|
||||
|
||||
It("fails to setup config when 1st apply is unsuccessful (declare table and chains)", func() {
|
||||
c := &configurerStub{failFirstApplyConfig: true}
|
||||
sc := link.NewSpoofCheckerWithConfigurer(iface, mac, id, c)
|
||||
Expect(sc.Setup()).To(MatchError("failed to setup spoof-check: " + errorFirstApplyText))
|
||||
})
|
||||
|
||||
It("fails to setup config when 2nd apply is unsuccessful (flush and add the rules)", func() {
|
||||
c := &configurerStub{failSecondApplyConfig: true}
|
||||
sc := link.NewSpoofCheckerWithConfigurer(iface, mac, id, c)
|
||||
Expect(sc.Setup()).To(MatchError("failed to setup spoof-check: " + errorSecondApplyText))
|
||||
})
|
||||
})
|
||||
|
||||
Context("teardown", func() {
|
||||
It("succeeds", func() {
|
||||
existingConfig := nft.NewConfig()
|
||||
existingConfig.FromJSON([]byte(rowConfigWithRulesOnly()))
|
||||
c := configurerStub{readConfig: existingConfig}
|
||||
|
||||
sc := link.NewSpoofCheckerWithConfigurer("", "", id, &c)
|
||||
Expect(sc.Teardown()).To(Succeed())
|
||||
|
||||
assertExpectedBaseChainRuleDeletionInTeardownConfig(c)
|
||||
assertExpectedRegularChainsDeletionInTeardownConfig(c)
|
||||
})
|
||||
|
||||
It("fails, 1st apply is unsuccessful (delete iface match rule)", func() {
|
||||
config := nft.NewConfig()
|
||||
config.FromJSON([]byte(rowConfigWithRulesOnly()))
|
||||
c := &configurerStub{applyConfig: []*nft.Config{config}, readConfig: config, failFirstApplyConfig: true}
|
||||
sc := link.NewSpoofCheckerWithConfigurer("", "", id, c)
|
||||
Expect(sc.Teardown()).To(MatchError(fmt.Sprintf(
|
||||
"failed to teardown spoof-check: failed to delete iface match rule: %s, <nil>", errorFirstApplyText,
|
||||
)))
|
||||
})
|
||||
|
||||
It("fails, read current config is unsuccessful", func() {
|
||||
config := nft.NewConfig()
|
||||
config.FromJSON([]byte(rowConfigWithRulesOnly()))
|
||||
c := &configurerStub{applyConfig: []*nft.Config{config}, readConfig: config, failReadConfig: true}
|
||||
sc := link.NewSpoofCheckerWithConfigurer("", "", id, c)
|
||||
Expect(sc.Teardown()).To(MatchError(fmt.Sprintf(
|
||||
"failed to teardown spoof-check: %s, <nil>", errorReadText,
|
||||
)))
|
||||
})
|
||||
|
||||
It("fails, 2nd apply is unsuccessful (delete the regular chains)", func() {
|
||||
config := nft.NewConfig()
|
||||
config.FromJSON([]byte(rowConfigWithRulesOnly()))
|
||||
c := &configurerStub{applyConfig: []*nft.Config{config}, readConfig: config, failSecondApplyConfig: true}
|
||||
sc := link.NewSpoofCheckerWithConfigurer("", "", id, c)
|
||||
Expect(sc.Teardown()).To(MatchError(fmt.Sprintf(
|
||||
"failed to teardown spoof-check: <nil>, failed to delete regular chains: %s", errorSecondApplyText,
|
||||
)))
|
||||
})
|
||||
|
||||
It("fails, both applies are unsuccessful", func() {
|
||||
config := nft.NewConfig()
|
||||
config.FromJSON([]byte(rowConfigWithRulesOnly()))
|
||||
c := &configurerStub{
|
||||
applyConfig: []*nft.Config{config},
|
||||
readConfig: config,
|
||||
failFirstApplyConfig: true,
|
||||
failSecondApplyConfig: true,
|
||||
}
|
||||
sc := link.NewSpoofCheckerWithConfigurer("", "", id, c)
|
||||
Expect(sc.Teardown()).To(MatchError(fmt.Sprintf(
|
||||
"failed to teardown spoof-check: "+
|
||||
"failed to delete iface match rule: %s, "+
|
||||
"failed to delete regular chains: %s",
|
||||
errorFirstApplyText, errorSecondApplyText,
|
||||
)))
|
||||
})
|
||||
})
|
||||
|
||||
Context("echo", func() {
|
||||
It("succeeds, no read called", func() {
|
||||
c := configurerStub{}
|
||||
sc := link.NewSpoofCheckerWithConfigurer(iface, mac, id, &c)
|
||||
Expect(sc.Setup()).To(Succeed())
|
||||
Expect(sc.Teardown()).To(Succeed())
|
||||
Expect(c.readCalled).To(BeFalse())
|
||||
})
|
||||
|
||||
It("succeeds, fall back to config read", func() {
|
||||
c := configurerStub{applyReturnNil: true}
|
||||
sc := link.NewSpoofCheckerWithConfigurer(iface, mac, id, &c)
|
||||
Expect(sc.Setup()).To(Succeed())
|
||||
c.readConfig = c.applyConfig[0]
|
||||
Expect(sc.Teardown()).To(Succeed())
|
||||
Expect(c.readCalled).To(BeTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
func assertExpectedRegularChainsDeletionInTeardownConfig(action configurerStub) {
|
||||
deleteRegularChainRulesJSONConfig, err := action.applyConfig[1].ToJSON()
|
||||
ExpectWithOffset(1, err).NotTo(HaveOccurred())
|
||||
|
||||
expectedDeleteRegularChainRulesJSONConfig := `
|
||||
{"nftables": [
|
||||
{"delete": {"chain": {
|
||||
"family": "bridge",
|
||||
"table": "nat",
|
||||
"name": "cni-br-iface-container99-net1"
|
||||
}}},
|
||||
{"delete": {"chain": {
|
||||
"family": "bridge",
|
||||
"table": "nat",
|
||||
"name": "cni-br-iface-container99-net1-mac"
|
||||
}}}
|
||||
]}`
|
||||
|
||||
ExpectWithOffset(1, string(deleteRegularChainRulesJSONConfig)).To(MatchJSON(expectedDeleteRegularChainRulesJSONConfig))
|
||||
}
|
||||
|
||||
func assertExpectedBaseChainRuleDeletionInTeardownConfig(action configurerStub) {
|
||||
deleteBaseChainRuleJSONConfig, err := action.applyConfig[0].ToJSON()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
expectedDeleteIfaceMatchRuleJSONConfig := `
|
||||
{"nftables": [
|
||||
{"delete": {"rule": {
|
||||
"family": "bridge",
|
||||
"table": "nat",
|
||||
"chain": "PREROUTING",
|
||||
"expr": [
|
||||
{"match": {
|
||||
"op": "==",
|
||||
"left": {"meta": {"key": "iifname"}},
|
||||
"right": "net0"
|
||||
}},
|
||||
{"jump": {"target": "cni-br-iface-container99-net1"}}
|
||||
],
|
||||
"comment": "macspoofchk-container99-net1"
|
||||
}}}
|
||||
]}`
|
||||
Expect(string(deleteBaseChainRuleJSONConfig)).To(MatchJSON(expectedDeleteIfaceMatchRuleJSONConfig))
|
||||
}
|
||||
|
||||
func rowConfigWithRulesOnly() string {
|
||||
return `
|
||||
{"nftables":[
|
||||
{"rule":{"family":"bridge","table":"nat","chain":"PREROUTING",
|
||||
"expr":[
|
||||
{"match":{"op":"==","left":{"meta":{"key":"iifname"}},"right":"net0"}},
|
||||
{"jump":{"target":"cni-br-iface-container99-net1"}}
|
||||
],
|
||||
"comment":"macspoofchk-container99-net1"}},
|
||||
{"rule":{"family":"bridge","table":"nat","chain":"cni-br-iface-container99-net1",
|
||||
"expr":[
|
||||
{"jump":{"target":"cni-br-iface-container99-net1-mac"}}
|
||||
],
|
||||
"comment":"macspoofchk-container99-net1"}},
|
||||
{"rule":{"family":"bridge","table":"nat","chain":"cni-br-iface-container99-net1-mac",
|
||||
"expr":[
|
||||
{"match":{
|
||||
"op":"==",
|
||||
"left":{"payload":{"protocol":"ether","field":"saddr"}},
|
||||
"right":"02:00:00:00:12:34"
|
||||
}},
|
||||
{"return":null}
|
||||
],
|
||||
"comment":"macspoofchk-container99-net1"}},
|
||||
{"rule":{"family":"bridge","table":"nat","chain":"cni-br-iface-container99-net1-mac",
|
||||
"expr":[{"drop":null}],
|
||||
"index":0,
|
||||
"comment":"macspoofchk-container99-net1"}}
|
||||
]}`
|
||||
}
|
||||
|
||||
func assertExpectedTableAndChainsInSetupConfig(c configurerStub) {
|
||||
config := c.applyConfig[0]
|
||||
jsonConfig, err := config.ToJSON()
|
||||
ExpectWithOffset(1, err).NotTo(HaveOccurred())
|
||||
|
||||
expectedConfig := `
|
||||
{"nftables": [
|
||||
{"table": {"family": "bridge", "name": "nat"}},
|
||||
{"chain": {
|
||||
"family": "bridge",
|
||||
"table": "nat",
|
||||
"name": "PREROUTING",
|
||||
"type": "filter",
|
||||
"hook": "prerouting",
|
||||
"prio": -300,
|
||||
"policy": "accept"
|
||||
}},
|
||||
{"chain": {
|
||||
"family": "bridge",
|
||||
"table": "nat",
|
||||
"name": "cni-br-iface-container99-net1"
|
||||
}},
|
||||
{"chain": {
|
||||
"family": "bridge",
|
||||
"table": "nat",
|
||||
"name": "cni-br-iface-container99-net1-mac"
|
||||
}}
|
||||
]}`
|
||||
ExpectWithOffset(1, string(jsonConfig)).To(MatchJSON(expectedConfig))
|
||||
}
|
||||
|
||||
func assertExpectedRulesInSetupConfig(c configurerStub) {
|
||||
config := c.applyConfig[1]
|
||||
jsonConfig, err := config.ToJSON()
|
||||
ExpectWithOffset(1, err).NotTo(HaveOccurred())
|
||||
|
||||
expectedConfig := `
|
||||
{"nftables":[
|
||||
{"flush":{"chain":{"family":"bridge","table":"nat","name":"cni-br-iface-container99-net1"}}},
|
||||
{"flush":{"chain":{"family":"bridge","table":"nat","name":"cni-br-iface-container99-net1-mac"}}},
|
||||
{"rule":{"family":"bridge","table":"nat","chain":"PREROUTING",
|
||||
"expr":[
|
||||
{"match":{"op":"==","left":{"meta":{"key":"iifname"}},"right":"net0"}},
|
||||
{"jump":{"target":"cni-br-iface-container99-net1"}}
|
||||
],
|
||||
"comment":"macspoofchk-container99-net1"}},
|
||||
{"rule":{"family":"bridge","table":"nat","chain":"cni-br-iface-container99-net1",
|
||||
"expr":[
|
||||
{"jump":{"target":"cni-br-iface-container99-net1-mac"}}
|
||||
],
|
||||
"comment":"macspoofchk-container99-net1"}},
|
||||
{"rule":{"family":"bridge","table":"nat","chain":"cni-br-iface-container99-net1-mac",
|
||||
"expr":[
|
||||
{"match":{
|
||||
"op":"==",
|
||||
"left":{"payload":{"protocol":"ether","field":"saddr"}},
|
||||
"right":"02:00:00:00:12:34"
|
||||
}},
|
||||
{"return":null}
|
||||
],
|
||||
"comment":"macspoofchk-container99-net1"}},
|
||||
{"rule":{"family":"bridge","table":"nat","chain":"cni-br-iface-container99-net1-mac",
|
||||
"expr":[{"drop":null}],
|
||||
"comment":"macspoofchk-container99-net1"}}
|
||||
]}`
|
||||
ExpectWithOffset(1, string(jsonConfig)).To(MatchJSON(expectedConfig))
|
||||
}
|
||||
|
||||
const (
|
||||
errorFirstApplyText = "1st apply failed"
|
||||
errorSecondApplyText = "2nd apply failed"
|
||||
errorReadText = "read failed"
|
||||
)
|
||||
|
||||
type configurerStub struct {
|
||||
applyConfig []*nft.Config
|
||||
readConfig *nft.Config
|
||||
|
||||
applyCounter int
|
||||
|
||||
failFirstApplyConfig bool
|
||||
failSecondApplyConfig bool
|
||||
failReadConfig bool
|
||||
|
||||
applyReturnNil bool
|
||||
readCalled bool
|
||||
}
|
||||
|
||||
func (a *configurerStub) Apply(c *nft.Config) (*nft.Config, error) {
|
||||
a.applyCounter++
|
||||
if a.failFirstApplyConfig && a.applyCounter == 1 {
|
||||
return nil, fmt.Errorf(errorFirstApplyText)
|
||||
}
|
||||
if a.failSecondApplyConfig && a.applyCounter == 2 {
|
||||
return nil, fmt.Errorf(errorSecondApplyText)
|
||||
}
|
||||
a.applyConfig = append(a.applyConfig, c)
|
||||
if a.applyReturnNil {
|
||||
return nil, nil
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (a *configurerStub) Read(_ ...string) (*nft.Config, error) {
|
||||
a.readCalled = true
|
||||
if a.failReadConfig {
|
||||
return nil, fmt.Errorf(errorReadText)
|
||||
}
|
||||
return a.readConfig, 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())
|
||||
}
|
||||
|
||||
@ -101,8 +106,8 @@ var _ NetNS = &netNS{}
|
||||
|
||||
const (
|
||||
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/magic.h
|
||||
NSFS_MAGIC = 0x6e736673
|
||||
PROCFS_MAGIC = 0x9fa0
|
||||
NSFS_MAGIC = unix.NSFS_MAGIC
|
||||
PROCFS_MAGIC = unix.PROC_SUPER_MAGIC
|
||||
)
|
||||
|
||||
type NSPathNotExistErr struct{ msg string }
|
||||
|
@ -17,15 +17,16 @@ package ns_test
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/testutils"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func getInodeCurNetNS() (uint64, error) {
|
||||
@ -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 {
|
||||
@ -154,7 +182,7 @@ var _ = Describe("Linux namespace operations", func() {
|
||||
testNsInode, err := getInodeNS(targetNetNS)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(testNsInode).NotTo(Equal(0))
|
||||
Expect(testNsInode).NotTo(Equal(uint64(0)))
|
||||
Expect(testNsInode).NotTo(Equal(origNSInode))
|
||||
})
|
||||
|
||||
@ -172,13 +200,15 @@ 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)
|
||||
if !os.IsNotExist(err) {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
Expect(netnsInode).NotTo(Equal(createdNetNSInode))
|
||||
}
|
||||
})
|
||||
|
||||
It("fails when the path is not a namespace", func() {
|
||||
tempFile, err := ioutil.TempFile("", "nstest")
|
||||
tempFile, err := os.CreateTemp("", "nstest")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer tempFile.Close()
|
||||
|
||||
@ -232,7 +262,7 @@ var _ = Describe("Linux namespace operations", func() {
|
||||
})
|
||||
|
||||
It("should refuse other paths", func() {
|
||||
tempFile, err := ioutil.TempFile("", "nstest")
|
||||
tempFile, err := os.CreateTemp("", "nstest")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer tempFile.Close()
|
||||
|
||||
|
@ -15,18 +15,14 @@
|
||||
package ns_test
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"runtime"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
"github.com/onsi/ginkgo/config"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestNs(t *testing.T) {
|
||||
rand.Seed(config.GinkgoConfig.RandomSeed)
|
||||
runtime.LockOSThread()
|
||||
|
||||
RegisterFailHandler(Fail)
|
||||
|
@ -21,7 +21,7 @@ type BadReader struct {
|
||||
Error error
|
||||
}
|
||||
|
||||
func (r *BadReader) Read(buffer []byte) (int, error) {
|
||||
func (r *BadReader) Read(_ []byte) (int, error) {
|
||||
if r.Error != nil {
|
||||
return 0, r.Error
|
||||
}
|
||||
|
@ -15,7 +15,7 @@
|
||||
package testutils
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
@ -29,6 +29,7 @@ func envCleanup() {
|
||||
os.Unsetenv("CNI_NETNS")
|
||||
os.Unsetenv("CNI_IFNAME")
|
||||
os.Unsetenv("CNI_CONTAINERID")
|
||||
os.Unsetenv("CNI_NETNS_OVERRIDE")
|
||||
}
|
||||
|
||||
func CmdAdd(cniNetns, cniContainerID, cniIfname string, conf []byte, f func() error) (types.Result, []byte, error) {
|
||||
@ -37,6 +38,7 @@ func CmdAdd(cniNetns, cniContainerID, cniIfname string, conf []byte, f func() er
|
||||
os.Setenv("CNI_NETNS", cniNetns)
|
||||
os.Setenv("CNI_IFNAME", cniIfname)
|
||||
os.Setenv("CNI_CONTAINERID", cniContainerID)
|
||||
os.Setenv("CNI_NETNS_OVERRIDE", "1")
|
||||
defer envCleanup()
|
||||
|
||||
// Redirect stdout to capture plugin result
|
||||
@ -52,7 +54,7 @@ func CmdAdd(cniNetns, cniContainerID, cniIfname string, conf []byte, f func() er
|
||||
|
||||
var out []byte
|
||||
if err == nil {
|
||||
out, err = ioutil.ReadAll(r)
|
||||
out, err = io.ReadAll(r)
|
||||
}
|
||||
os.Stdout = oldStdout
|
||||
|
||||
@ -81,19 +83,20 @@ func CmdAddWithArgs(args *skel.CmdArgs, f func() error) (types.Result, []byte, e
|
||||
return CmdAdd(args.Netns, args.ContainerID, args.IfName, args.StdinData, f)
|
||||
}
|
||||
|
||||
func CmdCheck(cniNetns, cniContainerID, cniIfname string, conf []byte, f func() error) error {
|
||||
func CmdCheck(cniNetns, cniContainerID, cniIfname string, f func() error) error {
|
||||
os.Setenv("CNI_COMMAND", "CHECK")
|
||||
os.Setenv("CNI_PATH", os.Getenv("PATH"))
|
||||
os.Setenv("CNI_NETNS", cniNetns)
|
||||
os.Setenv("CNI_IFNAME", cniIfname)
|
||||
os.Setenv("CNI_CONTAINERID", cniContainerID)
|
||||
os.Setenv("CNI_NETNS_OVERRIDE", "1")
|
||||
defer envCleanup()
|
||||
|
||||
return f()
|
||||
}
|
||||
|
||||
func CmdCheckWithArgs(args *skel.CmdArgs, f func() error) error {
|
||||
return CmdCheck(args.Netns, args.ContainerID, args.IfName, args.StdinData, f)
|
||||
return CmdCheck(args.Netns, args.ContainerID, args.IfName, f)
|
||||
}
|
||||
|
||||
func CmdDel(cniNetns, cniContainerID, cniIfname string, f func() error) error {
|
||||
@ -102,6 +105,7 @@ func CmdDel(cniNetns, cniContainerID, cniIfname string, f func() error) error {
|
||||
os.Setenv("CNI_NETNS", cniNetns)
|
||||
os.Setenv("CNI_IFNAME", cniIfname)
|
||||
os.Setenv("CNI_CONTAINERID", cniContainerID)
|
||||
os.Setenv("CNI_NETNS_OVERRIDE", "1")
|
||||
defer envCleanup()
|
||||
|
||||
return f()
|
||||
|
@ -16,7 +16,6 @@ package testutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
@ -28,7 +27,7 @@ import (
|
||||
// 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")
|
||||
f, err := os.CreateTemp("", "cni_test_resolv.conf")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get temp file for CNI test resolv.conf: %v", 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"
|
||||
"net"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "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(io.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", 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())
|
||||
})
|
||||
})
|
||||
})
|
@ -1,10 +1,10 @@
|
||||
package main_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestEchosvr(t *testing.T) {
|
@ -1,6 +1,7 @@
|
||||
// Echosvr is a simple TCP echo server
|
||||
//
|
||||
// It prints its listen address on stdout
|
||||
//
|
||||
// 127.0.0.1:xxxxx
|
||||
// A test should wait for this line, parse it
|
||||
// and may then attempt to connect.
|
||||
@ -10,6 +11,7 @@ import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
@ -17,15 +19,19 @@ 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)
|
||||
go func() {
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
@ -33,6 +39,34 @@ func main() {
|
||||
}
|
||||
go handleConnection(conn)
|
||||
}
|
||||
}()
|
||||
|
||||
// Start UDP server
|
||||
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%s", port))
|
||||
if err != nil {
|
||||
log.Printf("Error from net.ResolveUDPAddr(): %s", err)
|
||||
return
|
||||
}
|
||||
sock, err := net.ListenUDP("udp", addr)
|
||||
if err != nil {
|
||||
log.Printf("Error from ListenUDP(): %s", err)
|
||||
return
|
||||
}
|
||||
defer sock.Close()
|
||||
|
||||
buffer := make([]byte, 1024)
|
||||
for {
|
||||
n, addr, err := sock.ReadFrom(buffer)
|
||||
if err != nil {
|
||||
log.Printf("Error from ReadFrom(): %s", err)
|
||||
return
|
||||
}
|
||||
sock.SetWriteDeadline(time.Now().Add(1 * time.Minute))
|
||||
_, err = sock.WriteTo(buffer[0:n], addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleConnection(conn net.Conn) {
|
||||
@ -53,5 +87,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")))
|
||||
})
|
||||
})
|
@ -24,8 +24,9 @@ import (
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
)
|
||||
|
||||
func getNsRunDir() string {
|
||||
@ -49,11 +50,10 @@ func getNsRunDir() string {
|
||||
// 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)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate random netns name: %v", err)
|
||||
}
|
||||
@ -61,7 +61,7 @@ func NewNS() (ns.NetNS, error) {
|
||||
// Create the directory for mounting network namespaces
|
||||
// This needs to be a shared mountpoint in case it is mounted in to
|
||||
// other namespaces (containers)
|
||||
err = os.MkdirAll(nsRunDir, 0755)
|
||||
err = os.MkdirAll(nsRunDir, 0o755)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -17,13 +17,24 @@ package testutils
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Ping shells out to the `ping` command. Returns nil if successful.
|
||||
func Ping(saddr, daddr string, isV6 bool, timeoutSec int) error {
|
||||
func Ping(saddr, daddr string, timeoutSec int) error {
|
||||
ip := net.ParseIP(saddr)
|
||||
if ip == nil {
|
||||
return fmt.Errorf("failed to parse IP %q", saddr)
|
||||
}
|
||||
|
||||
bin := "ping6"
|
||||
if ip.To4() != nil {
|
||||
bin = "ping"
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"-c", "1",
|
||||
"-W", strconv.Itoa(timeoutSec),
|
||||
@ -31,11 +42,6 @@ func Ping(saddr, daddr string, isV6 bool, timeoutSec int) error {
|
||||
daddr,
|
||||
}
|
||||
|
||||
bin := "ping"
|
||||
if isV6 {
|
||||
bin = "ping6"
|
||||
}
|
||||
|
||||
cmd := exec.Command(bin, args...)
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
|
54
pkg/testutils/testing.go
Normal file
54
pkg/testutils/testing.go
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright 2016 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 (
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
)
|
||||
|
||||
// AllSpecVersions contains all CNI spec version numbers
|
||||
var AllSpecVersions = [...]string{"0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0"}
|
||||
|
||||
// SpecVersionHasIPVersion returns true if the given CNI specification version
|
||||
// includes the "version" field in the IP address elements
|
||||
func SpecVersionHasIPVersion(ver string) bool {
|
||||
for _, i := range []string{"0.3.0", "0.3.1", "0.4.0"} {
|
||||
if ver == i {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SpecVersionHasCHECK returns true if the given CNI specification version
|
||||
// supports the CHECK command
|
||||
func SpecVersionHasCHECK(ver string) bool {
|
||||
ok, _ := version.GreaterThanOrEqualTo(ver, "0.4.0")
|
||||
return ok
|
||||
}
|
||||
|
||||
// SpecVersionHasChaining returns true if the given CNI specification version
|
||||
// supports plugin chaining
|
||||
func SpecVersionHasChaining(ver string) bool {
|
||||
ok, _ := version.GreaterThanOrEqualTo(ver, "0.3.0")
|
||||
return ok
|
||||
}
|
||||
|
||||
// SpecVersionHasMultipleIPs returns true if the given CNI specification version
|
||||
// supports more than one IP address of each family
|
||||
func SpecVersionHasMultipleIPs(ver string) bool {
|
||||
ok, _ := version.GreaterThanOrEqualTo(ver, "0.3.0")
|
||||
return ok
|
||||
}
|
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.AddProtocol(protocol)
|
||||
filter.AddPort(netlink.ConntrackOrigDstPort, port)
|
||||
|
||||
_, 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
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
// Copyright 2016 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 hwaddr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
const (
|
||||
ipRelevantByteLen = 4
|
||||
PrivateMACPrefixString = "0a:58"
|
||||
)
|
||||
|
||||
var (
|
||||
// private mac prefix safe to use
|
||||
PrivateMACPrefix = []byte{0x0a, 0x58}
|
||||
)
|
||||
|
||||
type SupportIp4OnlyErr struct{ msg string }
|
||||
|
||||
func (e SupportIp4OnlyErr) Error() string { return e.msg }
|
||||
|
||||
type MacParseErr struct{ msg string }
|
||||
|
||||
func (e MacParseErr) Error() string { return e.msg }
|
||||
|
||||
type InvalidPrefixLengthErr struct{ msg string }
|
||||
|
||||
func (e InvalidPrefixLengthErr) Error() string { return e.msg }
|
||||
|
||||
// GenerateHardwareAddr4 generates 48 bit virtual mac addresses based on the IP4 input.
|
||||
func GenerateHardwareAddr4(ip net.IP, prefix []byte) (net.HardwareAddr, error) {
|
||||
switch {
|
||||
|
||||
case ip.To4() == nil:
|
||||
return nil, SupportIp4OnlyErr{msg: "GenerateHardwareAddr4 only supports valid IPv4 address as input"}
|
||||
|
||||
case len(prefix) != len(PrivateMACPrefix):
|
||||
return nil, InvalidPrefixLengthErr{msg: fmt.Sprintf(
|
||||
"Prefix has length %d instead of %d", len(prefix), len(PrivateMACPrefix)),
|
||||
}
|
||||
}
|
||||
|
||||
ipByteLen := len(ip)
|
||||
return (net.HardwareAddr)(
|
||||
append(
|
||||
prefix,
|
||||
ip[ipByteLen-ipRelevantByteLen:ipByteLen]...),
|
||||
), nil
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
// Copyright 2016 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 hwaddr_test
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/utils/hwaddr"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Hwaddr", func() {
|
||||
Context("Generate Hardware Address", func() {
|
||||
It("generate hardware address based on ipv4 address", func() {
|
||||
testCases := []struct {
|
||||
ip net.IP
|
||||
expectedMAC net.HardwareAddr
|
||||
}{
|
||||
{
|
||||
ip: net.ParseIP("10.0.0.2"),
|
||||
expectedMAC: (net.HardwareAddr)(append(hwaddr.PrivateMACPrefix, 0x0a, 0x00, 0x00, 0x02)),
|
||||
},
|
||||
{
|
||||
ip: net.ParseIP("10.250.0.244"),
|
||||
expectedMAC: (net.HardwareAddr)(append(hwaddr.PrivateMACPrefix, 0x0a, 0xfa, 0x00, 0xf4)),
|
||||
},
|
||||
{
|
||||
ip: net.ParseIP("172.17.0.2"),
|
||||
expectedMAC: (net.HardwareAddr)(append(hwaddr.PrivateMACPrefix, 0xac, 0x11, 0x00, 0x02)),
|
||||
},
|
||||
{
|
||||
ip: net.IPv4(byte(172), byte(17), byte(0), byte(2)),
|
||||
expectedMAC: (net.HardwareAddr)(append(hwaddr.PrivateMACPrefix, 0xac, 0x11, 0x00, 0x02)),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
mac, err := hwaddr.GenerateHardwareAddr4(tc.ip, hwaddr.PrivateMACPrefix)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(mac).To(Equal(tc.expectedMAC))
|
||||
}
|
||||
})
|
||||
|
||||
It("return error if input is not ipv4 address", func() {
|
||||
testCases := []net.IP{
|
||||
net.ParseIP(""),
|
||||
net.ParseIP("2001:db8:0:1:1:1:1:1"),
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
_, err := hwaddr.GenerateHardwareAddr4(tc, hwaddr.PrivateMACPrefix)
|
||||
Expect(err).To(BeAssignableToTypeOf(hwaddr.SupportIp4OnlyErr{}))
|
||||
}
|
||||
})
|
||||
|
||||
It("return error if prefix is invalid", func() {
|
||||
_, err := hwaddr.GenerateHardwareAddr4(net.ParseIP("10.0.0.2"), []byte{0x58})
|
||||
Expect(err).To(BeAssignableToTypeOf(hwaddr.InvalidPrefixLengthErr{}))
|
||||
})
|
||||
})
|
||||
})
|
@ -29,9 +29,9 @@ func EnsureChain(ipt *iptables.IPTables, table, chain string) error {
|
||||
if ipt == nil {
|
||||
return errors.New("failed to ensure iptable chain: IPTables was nil")
|
||||
}
|
||||
exists, err := ChainExists(ipt, table, chain)
|
||||
exists, err := ipt.ChainExists(table, chain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list iptables chains: %v", err)
|
||||
return fmt.Errorf("failed to check iptables chain existence: %v", err)
|
||||
}
|
||||
if !exists {
|
||||
err = ipt.NewChain(table, chain)
|
||||
@ -45,24 +45,6 @@ func EnsureChain(ipt *iptables.IPTables, table, chain string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChainExists checks whether an iptables chain exists.
|
||||
func ChainExists(ipt *iptables.IPTables, table, chain string) (bool, error) {
|
||||
if ipt == nil {
|
||||
return false, errors.New("failed to check iptable chain: IPTables was nil")
|
||||
}
|
||||
chains, err := ipt.ListChains(table)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, ch := range chains {
|
||||
if ch == chain {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// DeleteRule idempotently delete the iptables rule in the specified table/chain.
|
||||
// It does not return an error if the referring chain doesn't exist
|
||||
func DeleteRule(ipt *iptables.IPTables, table, chain string, rulespec ...string) error {
|
||||
@ -119,3 +101,20 @@ func ClearChain(ipt *iptables.IPTables, table, chain string) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// InsertUnique will add a rule to a chain if it does not already exist.
|
||||
// By default the rule is appended, unless prepend is true.
|
||||
func InsertUnique(ipt *iptables.IPTables, table, chain string, prepend bool, rule []string) error {
|
||||
exists, err := ipt.Exists(table, chain, rule...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
if prepend {
|
||||
return ipt.Insert(table, chain, 1, rule...)
|
||||
}
|
||||
return ipt.Append(table, chain, rule...)
|
||||
}
|
||||
|
@ -19,11 +19,12 @@ import (
|
||||
"math/rand"
|
||||
"runtime"
|
||||
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"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
|
||||
@ -34,7 +35,6 @@ var _ = Describe("chain tests", func() {
|
||||
var cleanup func()
|
||||
|
||||
BeforeEach(func() {
|
||||
|
||||
// Save a reference to the original namespace,
|
||||
// Add a new NS
|
||||
currNs, err := ns.GetCurrentNS()
|
||||
@ -60,7 +60,6 @@ var _ = Describe("chain tests", func() {
|
||||
ipt.DeleteChain(TABLE, testChain)
|
||||
currNs.Set()
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
@ -93,5 +92,4 @@ var _ = Describe("chain tests", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
@ -16,7 +16,7 @@ package sysctl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
@ -36,8 +36,7 @@ func Sysctl(name string, params ...string) (string, error) {
|
||||
|
||||
func getSysctl(name string) (string, error) {
|
||||
fullName := filepath.Join("/proc/sys", toNormalName(name))
|
||||
fullName = filepath.Clean(fullName)
|
||||
data, err := ioutil.ReadFile(fullName)
|
||||
data, err := os.ReadFile(fullName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -47,8 +46,7 @@ func getSysctl(name string) (string, error) {
|
||||
|
||||
func setSysctl(name, value string) (string, error) {
|
||||
fullName := filepath.Join("/proc/sys", toNormalName(name))
|
||||
fullName = filepath.Clean(fullName)
|
||||
if err := ioutil.WriteFile(fullName, []byte(value), 0644); err != nil {
|
||||
if err := os.WriteFile(fullName, []byte(value), 0o644); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
|
@ -20,12 +20,13 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"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 (
|
||||
@ -37,8 +38,7 @@ var _ = Describe("Sysctl tests", func() {
|
||||
var testIfaceName string
|
||||
var cleanup func()
|
||||
|
||||
BeforeEach(func() {
|
||||
|
||||
beforeEach := func() {
|
||||
// Save a reference to the original namespace,
|
||||
// Add a new NS
|
||||
currNs, err := ns.GetCurrentNS()
|
||||
@ -66,8 +66,7 @@ var _ = Describe("Sysctl tests", func() {
|
||||
netlink.LinkDel(testIface)
|
||||
currNs.Set()
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
AfterEach(func() {
|
||||
cleanup()
|
||||
@ -75,7 +74,8 @@ var _ = Describe("Sysctl tests", func() {
|
||||
|
||||
Describe("Sysctl", func() {
|
||||
It("reads keys with dot separators", func() {
|
||||
sysctlIfaceName := strings.Replace(testIfaceName, ".", "/", -1)
|
||||
beforeEach()
|
||||
sysctlIfaceName := strings.ReplaceAll(testIfaceName, ".", "/")
|
||||
sysctlKey := fmt.Sprintf(sysctlDotKeyTemplate, sysctlIfaceName)
|
||||
|
||||
_, err := sysctl.Sysctl(sysctlKey)
|
||||
@ -85,6 +85,7 @@ var _ = Describe("Sysctl tests", func() {
|
||||
|
||||
Describe("Sysctl", func() {
|
||||
It("reads keys with slash separators", func() {
|
||||
beforeEach()
|
||||
sysctlKey := fmt.Sprintf(sysctlSlashKeyTemplate, testIfaceName)
|
||||
|
||||
_, err := sysctl.Sysctl(sysctlKey)
|
||||
@ -94,7 +95,8 @@ var _ = Describe("Sysctl tests", func() {
|
||||
|
||||
Describe("Sysctl", func() {
|
||||
It("writes keys with dot separators", func() {
|
||||
sysctlIfaceName := strings.Replace(testIfaceName, ".", "/", -1)
|
||||
beforeEach()
|
||||
sysctlIfaceName := strings.ReplaceAll(testIfaceName, ".", "/")
|
||||
sysctlKey := fmt.Sprintf(sysctlDotKeyTemplate, sysctlIfaceName)
|
||||
|
||||
_, err := sysctl.Sysctl(sysctlKey, "1")
|
||||
@ -104,11 +106,11 @@ var _ = Describe("Sysctl tests", func() {
|
||||
|
||||
Describe("Sysctl", func() {
|
||||
It("writes keys with slash separators", func() {
|
||||
beforeEach()
|
||||
sysctlKey := fmt.Sprintf(sysctlSlashKeyTemplate, testIfaceName)
|
||||
|
||||
_, err := sysctl.Sysctl(sysctlKey, "1")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
@ -17,7 +17,7 @@ package sysctl_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
|
@ -15,10 +15,10 @@
|
||||
package utils_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestUtils(t *testing.T) {
|
||||
|
@ -18,7 +18,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
@ -26,29 +26,29 @@ var _ = Describe("Utils", func() {
|
||||
Describe("FormatChainName", func() {
|
||||
It("must format a short name", func() {
|
||||
chain := FormatChainName("test", "1234")
|
||||
Expect(len(chain)).To(Equal(maxChainLength))
|
||||
Expect(chain).To(HaveLen(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(HaveLen(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(HaveLen(maxChainLength))
|
||||
Expect(chain2).To(HaveLen(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(HaveLen(maxChainLength))
|
||||
Expect(chain2).To(HaveLen(maxChainLength))
|
||||
Expect(chain1).To(Equal("CNI-374f33fe84ab0ed84dcdebe3"))
|
||||
Expect(chain1).NotTo(Equal(chain2))
|
||||
})
|
||||
@ -57,35 +57,35 @@ var _ = Describe("Utils", func() {
|
||||
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(HaveLen(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(HaveLen(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(HaveLen(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(HaveLen(maxChainLength))
|
||||
Expect(chain2).To(HaveLen(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(HaveLen(maxChainLength))
|
||||
Expect(chain2).To(HaveLen(maxChainLength))
|
||||
Expect(chain1).To(Equal("CNI-PREFIX-374f33fe84ab0ed84"))
|
||||
Expect(chain1).NotTo(Equal(chain2))
|
||||
})
|
||||
@ -161,5 +161,4 @@ var _ = Describe("Utils", func() {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
@ -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/current/ipam/dhcp/
|
||||
|
@ -1,3 +1,17 @@
|
||||
// Copyright 2021 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
@ -9,7 +23,7 @@ const (
|
||||
MaxDHCPLen = 576
|
||||
)
|
||||
|
||||
//Send the Discovery Packet to the Broadcast Channel
|
||||
// Send the Discovery Packet to the Broadcast Channel
|
||||
func DhcpSendDiscoverPacket(c *dhcp4client.Client, options dhcp4.Options) (dhcp4.Packet, error) {
|
||||
discoveryPacket := c.DiscoverPacket()
|
||||
|
||||
@ -21,7 +35,7 @@ func DhcpSendDiscoverPacket(c *dhcp4client.Client, options dhcp4.Options) (dhcp4
|
||||
return discoveryPacket, c.SendPacket(discoveryPacket)
|
||||
}
|
||||
|
||||
//Send Request Based On the offer Received.
|
||||
// Send Request Based On the offer Received.
|
||||
func DhcpSendRequest(c *dhcp4client.Client, options dhcp4.Options, offerPacket *dhcp4.Packet) (dhcp4.Packet, error) {
|
||||
requestPacket := c.RequestPacket(offerPacket)
|
||||
|
||||
@ -34,7 +48,7 @@ func DhcpSendRequest(c *dhcp4client.Client, options dhcp4.Options, offerPacket *
|
||||
return requestPacket, c.SendPacket(requestPacket)
|
||||
}
|
||||
|
||||
//Send Decline to the received acknowledgement.
|
||||
// Send Decline to the received acknowledgement.
|
||||
func DhcpSendDecline(c *dhcp4client.Client, acknowledgementPacket *dhcp4.Packet, options dhcp4.Options) (dhcp4.Packet, error) {
|
||||
declinePacket := c.DeclinePacket(acknowledgementPacket)
|
||||
|
||||
@ -47,7 +61,7 @@ func DhcpSendDecline(c *dhcp4client.Client, acknowledgementPacket *dhcp4.Packet,
|
||||
return declinePacket, c.SendPacket(declinePacket)
|
||||
}
|
||||
|
||||
//Lets do a Full DHCP Request.
|
||||
// Lets do a Full DHCP Request.
|
||||
func DhcpRequest(c *dhcp4client.Client, options dhcp4.Options) (bool, dhcp4.Packet, error) {
|
||||
discoveryPacket, err := DhcpSendDiscoverPacket(c, options)
|
||||
if err != nil {
|
||||
@ -77,8 +91,8 @@ func DhcpRequest(c *dhcp4client.Client, options dhcp4.Options) (bool, dhcp4.Pack
|
||||
return true, acknowledgement, nil
|
||||
}
|
||||
|
||||
//Renew a lease backed on the Acknowledgement Packet.
|
||||
//Returns Successful, The AcknoledgementPacket, Any Errors
|
||||
// Renew a lease backed on the Acknowledgement Packet.
|
||||
// Returns Successful, The AcknoledgementPacket, Any Errors
|
||||
func DhcpRenew(c *dhcp4client.Client, acknowledgement dhcp4.Packet, options dhcp4.Options) (bool, dhcp4.Packet, error) {
|
||||
renewRequest := c.RenewalRequestPacket(&acknowledgement)
|
||||
|
||||
@ -106,8 +120,8 @@ func DhcpRenew(c *dhcp4client.Client, acknowledgement dhcp4.Packet, options dhcp
|
||||
return true, newAcknowledgement, nil
|
||||
}
|
||||
|
||||
//Release a lease backed on the Acknowledgement Packet.
|
||||
//Returns Any Errors
|
||||
// Release a lease backed on the Acknowledgement Packet.
|
||||
// Returns Any Errors
|
||||
func DhcpRelease(c *dhcp4client.Client, acknowledgement dhcp4.Packet, options dhcp4.Options) error {
|
||||
release := c.ReleasePacket(&acknowledgement)
|
||||
|
||||
|
@ -15,60 +15,87 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/rpc"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-systemd/v22/activation"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/coreos/go-systemd/activation"
|
||||
current "github.com/containernetworking/cni/pkg/types/100"
|
||||
)
|
||||
|
||||
const listenFdsStart = 3
|
||||
const resendCount = 3
|
||||
|
||||
var errNoMoreTries = errors.New("no more tries")
|
||||
|
||||
type DHCP struct {
|
||||
mux sync.Mutex
|
||||
leases map[string]*DHCPLease
|
||||
hostNetnsPrefix string
|
||||
clientTimeout time.Duration
|
||||
clientResendMax time.Duration
|
||||
broadcast bool
|
||||
}
|
||||
|
||||
func newDHCP() *DHCP {
|
||||
func newDHCP(clientTimeout, clientResendMax time.Duration) *DHCP {
|
||||
return &DHCP{
|
||||
leases: make(map[string]*DHCPLease),
|
||||
clientTimeout: clientTimeout,
|
||||
clientResendMax: clientResendMax,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: current client ID is too long. At least the container ID should not be used directly.
|
||||
// A separate issue is necessary to ensure no breaking change is affecting other users.
|
||||
func generateClientID(containerID string, netName string, ifName string) string {
|
||||
return containerID + "/" + netName + "/" + ifName
|
||||
clientID := containerID + "/" + netName + "/" + ifName
|
||||
// defined in RFC 2132, length size can not be larger than 1 octet. So we truncate 254 to make everyone happy.
|
||||
if len(clientID) > 254 {
|
||||
clientID = clientID[0:254]
|
||||
}
|
||||
return clientID
|
||||
}
|
||||
|
||||
// Allocate acquires an IP from a DHCP server for a specified container.
|
||||
// The acquired lease will be maintained until Release() is called.
|
||||
func (d *DHCP) Allocate(args *skel.CmdArgs, result *current.Result) error {
|
||||
conf := types.NetConf{}
|
||||
conf := NetConf{}
|
||||
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
|
||||
return fmt.Errorf("error parsing netconf: %v", err)
|
||||
}
|
||||
|
||||
clientID := generateClientID(args.ContainerID, conf.Name, args.IfName)
|
||||
hostNetns := d.hostNetnsPrefix + args.Netns
|
||||
l, err := AcquireLease(clientID, hostNetns, args.IfName)
|
||||
optsRequesting, optsProviding, err := prepareOptions(args.Args, conf.IPAM.ProvideOptions, conf.IPAM.RequestOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clientID := generateClientID(args.ContainerID, conf.Name, args.IfName)
|
||||
|
||||
// If we already have an active lease for this clientID, do not create
|
||||
// another one
|
||||
l := d.getLease(clientID)
|
||||
if l != nil {
|
||||
l.Check()
|
||||
} else {
|
||||
hostNetns := d.hostNetnsPrefix + args.Netns
|
||||
l, err = AcquireLease(clientID, hostNetns, args.IfName,
|
||||
optsRequesting, optsProviding,
|
||||
d.clientTimeout, d.clientResendMax, d.broadcast)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
ipn, err := l.IPNet()
|
||||
if err != nil {
|
||||
l.Stop()
|
||||
@ -78,7 +105,6 @@ func (d *DHCP) Allocate(args *skel.CmdArgs, result *current.Result) error {
|
||||
d.setLease(clientID, l)
|
||||
|
||||
result.IPs = []*current.IPConfig{{
|
||||
Version: "4",
|
||||
Address: *ipn,
|
||||
Gateway: l.Gateway(),
|
||||
}}
|
||||
@ -89,8 +115,8 @@ func (d *DHCP) Allocate(args *skel.CmdArgs, result *current.Result) error {
|
||||
|
||||
// Release stops maintenance of the lease acquired in Allocate()
|
||||
// and sends a release msg to the DHCP server.
|
||||
func (d *DHCP) Release(args *skel.CmdArgs, reply *struct{}) error {
|
||||
conf := types.NetConf{}
|
||||
func (d *DHCP) Release(args *skel.CmdArgs, _ *struct{}) error {
|
||||
conf := NetConf{}
|
||||
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
|
||||
return fmt.Errorf("error parsing netconf: %v", err)
|
||||
}
|
||||
@ -124,7 +150,7 @@ func (d *DHCP) setLease(clientID string, l *DHCPLease) {
|
||||
d.leases[clientID] = l
|
||||
}
|
||||
|
||||
//func (d *DHCP) clearLease(contID, netName, ifName string) {
|
||||
// func (d *DHCP) clearLease(contID, netName, ifName string) {
|
||||
func (d *DHCP) clearLease(clientID string) {
|
||||
d.mux.Lock()
|
||||
defer d.mux.Unlock()
|
||||
@ -141,7 +167,7 @@ func getListener(socketPath string) (net.Listener, error) {
|
||||
|
||||
switch {
|
||||
case len(l) == 0:
|
||||
if err := os.MkdirAll(filepath.Dir(socketPath), 0700); err != nil {
|
||||
if err := os.MkdirAll(filepath.Dir(socketPath), 0o700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return net.Listen("unix", socketPath)
|
||||
@ -157,7 +183,10 @@ func getListener(socketPath string) (net.Listener, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func runDaemon(pidfilePath string, hostPrefix string, socketPath string) error {
|
||||
func runDaemon(
|
||||
pidfilePath, hostPrefix, socketPath string,
|
||||
dhcpClientTimeout time.Duration, resendMax time.Duration, broadcast bool,
|
||||
) error {
|
||||
// since other goroutines (on separate threads) will change namespaces,
|
||||
// ensure the RPC server does not get scheduled onto those
|
||||
runtime.LockOSThread()
|
||||
@ -167,7 +196,7 @@ func runDaemon(pidfilePath string, hostPrefix string, socketPath string) error {
|
||||
if !filepath.IsAbs(pidfilePath) {
|
||||
return fmt.Errorf("Error writing pidfile %q: path not absolute", pidfilePath)
|
||||
}
|
||||
if err := ioutil.WriteFile(pidfilePath, []byte(fmt.Sprintf("%d", os.Getpid())), 0644); err != nil {
|
||||
if err := os.WriteFile(pidfilePath, []byte(fmt.Sprintf("%d", os.Getpid())), 0o644); err != nil {
|
||||
return fmt.Errorf("Error writing pidfile %q: %v", pidfilePath, err)
|
||||
}
|
||||
}
|
||||
@ -177,10 +206,27 @@ func runDaemon(pidfilePath string, hostPrefix string, socketPath string) error {
|
||||
return fmt.Errorf("Error getting listener: %v", err)
|
||||
}
|
||||
|
||||
dhcp := newDHCP()
|
||||
srv := http.Server{}
|
||||
exit := make(chan os.Signal, 1)
|
||||
done := make(chan bool, 1)
|
||||
signal.Notify(exit, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
go func() {
|
||||
<-exit
|
||||
srv.Shutdown(context.TODO())
|
||||
os.Remove(hostPrefix + socketPath)
|
||||
os.Remove(pidfilePath)
|
||||
|
||||
done <- true
|
||||
}()
|
||||
|
||||
dhcp := newDHCP(dhcpClientTimeout, resendMax)
|
||||
dhcp.hostNetnsPrefix = hostPrefix
|
||||
dhcp.broadcast = broadcast
|
||||
rpc.Register(dhcp)
|
||||
rpc.HandleHTTP()
|
||||
http.Serve(l, nil)
|
||||
srv.Serve(l)
|
||||
|
||||
<-done
|
||||
return nil
|
||||
}
|
||||
|
@ -16,21 +16,19 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/testutils"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
current "github.com/containernetworking/cni/pkg/types/100"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/testutils"
|
||||
)
|
||||
|
||||
var _ = Describe("DHCP Multiple Lease Operations", func() {
|
||||
@ -40,11 +38,10 @@ var _ = Describe("DHCP Multiple Lease Operations", func() {
|
||||
var clientCmd *exec.Cmd
|
||||
var socketPath string
|
||||
var tmpDir string
|
||||
var serverIP net.IPNet
|
||||
var err error
|
||||
|
||||
BeforeEach(func() {
|
||||
dhcpServerStopCh, serverIP, socketPath, originalNS, targetNS, err = dhcpSetupOriginalNS()
|
||||
dhcpServerStopCh, socketPath, originalNS, targetNS, err = dhcpSetupOriginalNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Move the container side to the container's NS
|
||||
@ -64,7 +61,7 @@ var _ = Describe("DHCP Multiple Lease Operations", func() {
|
||||
})
|
||||
|
||||
// Start the DHCP server
|
||||
dhcpServerDone, err = dhcpServerStart(originalNS, net.IPv4(192, 168, 1, 5), serverIP.IP, 2, dhcpServerStopCh)
|
||||
dhcpServerDone, err = dhcpServerStart(originalNS, 2, dhcpServerStopCh)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Start the DHCP client daemon
|
||||
@ -123,7 +120,7 @@ var _ = Describe("DHCP Multiple Lease Operations", func() {
|
||||
|
||||
addResult, err = current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(addResult.IPs)).To(Equal(1))
|
||||
Expect(addResult.IPs).To(HaveLen(1))
|
||||
Expect(addResult.IPs[0].Address.String()).To(Equal("192.168.1.5/24"))
|
||||
return nil
|
||||
})
|
||||
@ -146,7 +143,7 @@ var _ = Describe("DHCP Multiple Lease Operations", func() {
|
||||
|
||||
addResult, err = current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(addResult.IPs)).To(Equal(1))
|
||||
Expect(addResult.IPs).To(HaveLen(1))
|
||||
Expect(addResult.IPs[0].Address.String()).To(Equal("192.168.1.6/24"))
|
||||
return nil
|
||||
})
|
||||
|
@ -15,10 +15,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestDHCP(t *testing.T) {
|
||||
|
@ -15,8 +15,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
@ -24,24 +25,22 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/testutils"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/d2g/dhcp4"
|
||||
"github.com/d2g/dhcp4server"
|
||||
"github.com/d2g/dhcp4server/leasepool"
|
||||
"github.com/d2g/dhcp4server/leasepool/memorypool"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
types100 "github.com/containernetworking/cni/pkg/types/100"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/testutils"
|
||||
)
|
||||
|
||||
func getTmpDir() (string, error) {
|
||||
tmpDir, err := ioutil.TempDir(cniDirPrefix, "dhcp")
|
||||
tmpDir, err := os.MkdirTemp(cniDirPrefix, "dhcp")
|
||||
if err == nil {
|
||||
tmpDir = filepath.ToSlash(tmpDir)
|
||||
}
|
||||
@ -49,7 +48,7 @@ func getTmpDir() (string, error) {
|
||||
return tmpDir, err
|
||||
}
|
||||
|
||||
func dhcpServerStart(netns ns.NetNS, leaseIP, serverIP net.IP, numLeases int, stopCh <-chan bool) (*sync.WaitGroup, error) {
|
||||
func dhcpServerStart(netns ns.NetNS, numLeases int, stopCh <-chan bool) (*sync.WaitGroup, error) {
|
||||
// Add the expected IP to the pool
|
||||
lp := memorypool.MemoryPool{}
|
||||
|
||||
@ -119,7 +118,7 @@ const (
|
||||
)
|
||||
|
||||
var _ = BeforeSuite(func() {
|
||||
err := os.MkdirAll(cniDirPrefix, 0700)
|
||||
err := os.MkdirAll(cniDirPrefix, 0o700)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
@ -201,13 +200,18 @@ var _ = Describe("DHCP Operations", func() {
|
||||
})
|
||||
|
||||
// Start the DHCP server
|
||||
dhcpServerDone, err = dhcpServerStart(originalNS, net.IPv4(192, 168, 1, 5), serverIP.IP, 1, dhcpServerStopCh)
|
||||
dhcpServerDone, err = dhcpServerStart(originalNS, 1, dhcpServerStopCh)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Start the DHCP client daemon
|
||||
dhcpPluginPath, err := exec.LookPath("dhcp")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
clientCmd = exec.Command(dhcpPluginPath, "daemon", "-socketpath", socketPath)
|
||||
|
||||
// copy dhcp client's stdout/stderr to test stdout
|
||||
clientCmd.Stdout = os.Stdout
|
||||
clientCmd.Stderr = os.Stderr
|
||||
|
||||
err = clientCmd.Start()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(clientCmd.Process).NotTo(BeNil())
|
||||
@ -226,20 +230,28 @@ var _ = Describe("DHCP Operations", func() {
|
||||
clientCmd.Wait()
|
||||
|
||||
Expect(originalNS.Close()).To(Succeed())
|
||||
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
|
||||
Expect(targetNS.Close()).To(Succeed())
|
||||
defer os.RemoveAll(tmpDir)
|
||||
Expect(testutils.UnmountNS(targetNS)).To(Succeed())
|
||||
|
||||
Expect(os.RemoveAll(tmpDir)).To(Succeed())
|
||||
})
|
||||
|
||||
It("configures and deconfigures a link with ADD/DEL", func() {
|
||||
for _, ver := range testutils.AllSpecVersions {
|
||||
// Redefine ver inside for scope so real value is picked up by each dynamically defined It()
|
||||
// See Gingkgo's "Patterns for dynamically generating tests" documentation.
|
||||
ver := ver
|
||||
|
||||
It(fmt.Sprintf("[%s] configures and deconfigures a link with ADD/DEL", ver), func() {
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"ipam": {
|
||||
"type": "dhcp",
|
||||
"daemonSocketPath": "%s"
|
||||
}
|
||||
}`, socketPath)
|
||||
}`, ver, socketPath)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
@ -248,7 +260,7 @@ var _ = Describe("DHCP Operations", func() {
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
var addResult *current.Result
|
||||
var addResult *types100.Result
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
@ -257,9 +269,9 @@ var _ = Describe("DHCP Operations", func() {
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
addResult, err = current.GetResult(r)
|
||||
addResult, err = types100.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(addResult.IPs)).To(Equal(1))
|
||||
Expect(addResult.IPs).To(HaveLen(1))
|
||||
Expect(addResult.IPs[0].Address.String()).To(Equal("192.168.1.5/24"))
|
||||
return nil
|
||||
})
|
||||
@ -273,16 +285,16 @@ var _ = Describe("DHCP Operations", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("correctly handles multiple DELs for the same container", func() {
|
||||
It(fmt.Sprintf("[%s] correctly handles multiple DELs for the same container", ver), func() {
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"ipam": {
|
||||
"type": "dhcp",
|
||||
"daemonSocketPath": "%s"
|
||||
}
|
||||
}`, socketPath)
|
||||
}`, ver, socketPath)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
@ -291,7 +303,7 @@ var _ = Describe("DHCP Operations", func() {
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
var addResult *current.Result
|
||||
var addResult *types100.Result
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
@ -300,9 +312,9 @@ var _ = Describe("DHCP Operations", func() {
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
addResult, err = current.GetResult(r)
|
||||
addResult, err = types100.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(addResult.IPs)).To(Equal(1))
|
||||
Expect(addResult.IPs).To(HaveLen(1))
|
||||
Expect(addResult.IPs[0].Address.String()).To(Equal("192.168.1.5/24"))
|
||||
return nil
|
||||
})
|
||||
@ -320,9 +332,17 @@ var _ = Describe("DHCP Operations", func() {
|
||||
started.Done()
|
||||
started.Wait()
|
||||
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
return testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
copiedArgs := &skel.CmdArgs{
|
||||
ContainerID: args.ContainerID,
|
||||
Netns: args.Netns,
|
||||
IfName: args.IfName,
|
||||
StdinData: args.StdinData,
|
||||
Path: args.Path,
|
||||
Args: args.Args,
|
||||
}
|
||||
return cmdDel(copiedArgs)
|
||||
})
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@ -338,6 +358,7 @@ var _ = Describe("DHCP Operations", func() {
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const (
|
||||
@ -348,7 +369,7 @@ const (
|
||||
contVethName1 string = "eth1"
|
||||
)
|
||||
|
||||
func dhcpSetupOriginalNS() (chan bool, net.IPNet, string, ns.NetNS, ns.NetNS, error) {
|
||||
func dhcpSetupOriginalNS() (chan bool, string, ns.NetNS, ns.NetNS, error) {
|
||||
var originalNS, targetNS ns.NetNS
|
||||
var dhcpServerStopCh chan bool
|
||||
var socketPath string
|
||||
@ -369,11 +390,6 @@ func dhcpSetupOriginalNS() (chan bool, net.IPNet, string, ns.NetNS, ns.NetNS, er
|
||||
targetNS, err = testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
serverIP := net.IPNet{
|
||||
IP: net.IPv4(192, 168, 1, 1),
|
||||
Mask: net.IPv4Mask(255, 255, 255, 0),
|
||||
}
|
||||
|
||||
// Use (original) NS
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
@ -468,7 +484,7 @@ func dhcpSetupOriginalNS() (chan bool, net.IPNet, string, ns.NetNS, ns.NetNS, er
|
||||
return nil
|
||||
})
|
||||
|
||||
return dhcpServerStopCh, serverIP, socketPath, originalNS, targetNS, err
|
||||
return dhcpServerStopCh, socketPath, originalNS, targetNS, err
|
||||
}
|
||||
|
||||
var _ = Describe("DHCP Lease Unavailable Operations", func() {
|
||||
@ -478,11 +494,10 @@ var _ = Describe("DHCP Lease Unavailable Operations", func() {
|
||||
var clientCmd *exec.Cmd
|
||||
var socketPath string
|
||||
var tmpDir string
|
||||
var serverIP net.IPNet
|
||||
var err error
|
||||
|
||||
BeforeEach(func() {
|
||||
dhcpServerStopCh, serverIP, socketPath, originalNS, targetNS, err = dhcpSetupOriginalNS()
|
||||
dhcpServerStopCh, socketPath, originalNS, targetNS, err = dhcpSetupOriginalNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Move the container side to the container's NS
|
||||
@ -502,13 +517,25 @@ var _ = Describe("DHCP Lease Unavailable Operations", func() {
|
||||
})
|
||||
|
||||
// Start the DHCP server
|
||||
dhcpServerDone, err = dhcpServerStart(originalNS, net.IPv4(192, 168, 1, 5), serverIP.IP, 1, dhcpServerStopCh)
|
||||
dhcpServerDone, err = dhcpServerStart(originalNS, 1, dhcpServerStopCh)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Start the DHCP client daemon
|
||||
dhcpPluginPath, err := exec.LookPath("dhcp")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
clientCmd = exec.Command(dhcpPluginPath, "daemon", "-socketpath", socketPath)
|
||||
// Use very short timeouts for lease-unavailable operations because
|
||||
// the same test is run many times, and the delays will exceed the
|
||||
// `go test` timeout with default delays. Since our DHCP server
|
||||
// and client daemon are local processes anyway, we can depend on
|
||||
// them to respond very quickly.
|
||||
clientCmd = exec.Command(dhcpPluginPath, "daemon", "-socketpath", socketPath, "-timeout", "2s", "-resendmax", "8s")
|
||||
|
||||
// copy dhcp client's stdout/stderr to test stdout
|
||||
var b bytes.Buffer
|
||||
mw := io.MultiWriter(os.Stdout, &b)
|
||||
clientCmd.Stdout = mw
|
||||
clientCmd.Stderr = mw
|
||||
|
||||
err = clientCmd.Start()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(clientCmd.Process).NotTo(BeNil())
|
||||
@ -527,13 +554,21 @@ var _ = Describe("DHCP Lease Unavailable Operations", func() {
|
||||
clientCmd.Wait()
|
||||
|
||||
Expect(originalNS.Close()).To(Succeed())
|
||||
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
|
||||
Expect(targetNS.Close()).To(Succeed())
|
||||
defer os.RemoveAll(tmpDir)
|
||||
Expect(testutils.UnmountNS(targetNS)).To(Succeed())
|
||||
|
||||
Expect(os.RemoveAll(tmpDir)).To(Succeed())
|
||||
})
|
||||
|
||||
It("Configures multiple links with multiple ADD with second lease unavailable", func() {
|
||||
for _, ver := range testutils.AllSpecVersions {
|
||||
// Redefine ver inside for scope so real value is picked up by each dynamically defined It()
|
||||
// See Gingkgo's "Patterns for dynamically generating tests" documentation.
|
||||
ver := ver
|
||||
|
||||
It(fmt.Sprintf("[%s] configures multiple links with multiple ADD with second lease unavailable", ver), func() {
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "bridge",
|
||||
"bridge": "%s",
|
||||
@ -541,7 +576,7 @@ var _ = Describe("DHCP Lease Unavailable Operations", func() {
|
||||
"type": "dhcp",
|
||||
"daemonSocketPath": "%s"
|
||||
}
|
||||
}`, hostBridgeName, socketPath)
|
||||
}`, ver, hostBridgeName, socketPath)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
@ -550,7 +585,7 @@ var _ = Describe("DHCP Lease Unavailable Operations", func() {
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
var addResult *current.Result
|
||||
var addResult *types100.Result
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
@ -559,9 +594,9 @@ var _ = Describe("DHCP Lease Unavailable Operations", func() {
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
addResult, err = current.GetResult(r)
|
||||
addResult, err = types100.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(addResult.IPs)).To(Equal(1))
|
||||
Expect(addResult.IPs).To(HaveLen(1))
|
||||
Expect(addResult.IPs[0].Address.String()).To(Equal("192.168.1.5/24"))
|
||||
return nil
|
||||
})
|
||||
@ -615,4 +650,5 @@ var _ = Describe("DHCP Lease Unavailable Operations", func() {
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@ -33,8 +34,17 @@ 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 (
|
||||
resendDelay0 = 4 * time.Second
|
||||
resendDelayMax = 62 * time.Second
|
||||
)
|
||||
|
||||
// To speed up the retry for first few failures, we retry without
|
||||
// backoff for a few times
|
||||
const (
|
||||
resendFastDelay = 2 * time.Second
|
||||
resendFastMax = 4
|
||||
)
|
||||
|
||||
const (
|
||||
leaseStateBound = iota
|
||||
@ -56,19 +66,100 @@ type DHCPLease struct {
|
||||
renewalTime time.Time
|
||||
rebindingTime time.Time
|
||||
expireTime time.Time
|
||||
timeout time.Duration
|
||||
resendMax time.Duration
|
||||
broadcast bool
|
||||
stopping uint32
|
||||
stop chan struct{}
|
||||
check chan struct{}
|
||||
wg sync.WaitGroup
|
||||
// list of requesting and providing options and if they are necessary / their value
|
||||
optsRequesting map[dhcp4.OptionCode]bool
|
||||
optsProviding map[dhcp4.OptionCode][]byte
|
||||
}
|
||||
|
||||
var requestOptionsDefault = map[dhcp4.OptionCode]bool{
|
||||
dhcp4.OptionRouter: true,
|
||||
dhcp4.OptionSubnetMask: true,
|
||||
}
|
||||
|
||||
func prepareOptions(cniArgs string, provideOptions []ProvideOption, requestOptions []RequestOption) (
|
||||
map[dhcp4.OptionCode]bool, map[dhcp4.OptionCode][]byte, error,
|
||||
) {
|
||||
var optsRequesting map[dhcp4.OptionCode]bool
|
||||
var optsProviding map[dhcp4.OptionCode][]byte
|
||||
var err error
|
||||
// parse CNI args
|
||||
cniArgsParsed := map[string]string{}
|
||||
for _, argPair := range strings.Split(cniArgs, ";") {
|
||||
args := strings.SplitN(argPair, "=", 2)
|
||||
if len(args) > 1 {
|
||||
cniArgsParsed[args[0]] = args[1]
|
||||
}
|
||||
}
|
||||
|
||||
// parse providing options map
|
||||
var optParsed dhcp4.OptionCode
|
||||
optsProviding = make(map[dhcp4.OptionCode][]byte)
|
||||
for _, opt := range provideOptions {
|
||||
optParsed, err = parseOptionName(string(opt.Option))
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Can not parse option %q: %w", opt.Option, err)
|
||||
}
|
||||
if len(opt.Value) > 0 {
|
||||
if len(opt.Value) > 255 {
|
||||
return nil, nil, fmt.Errorf("value too long for option %q: %q", opt.Option, opt.Value)
|
||||
}
|
||||
optsProviding[optParsed] = []byte(opt.Value)
|
||||
}
|
||||
if value, ok := cniArgsParsed[opt.ValueFromCNIArg]; ok {
|
||||
if len(value) > 255 {
|
||||
return nil, nil, fmt.Errorf("value too long for option %q from CNI_ARGS %q: %q", opt.Option, opt.ValueFromCNIArg, opt.Value)
|
||||
}
|
||||
optsProviding[optParsed] = []byte(value)
|
||||
}
|
||||
}
|
||||
|
||||
// parse necessary options map
|
||||
optsRequesting = make(map[dhcp4.OptionCode]bool)
|
||||
skipRequireDefault := false
|
||||
for _, opt := range requestOptions {
|
||||
if opt.SkipDefault {
|
||||
skipRequireDefault = true
|
||||
}
|
||||
optParsed, err = parseOptionName(string(opt.Option))
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Can not parse option %q: %w", opt.Option, err)
|
||||
}
|
||||
optsRequesting[optParsed] = true
|
||||
}
|
||||
for k, v := range requestOptionsDefault {
|
||||
// only set if not skipping default and this value does not exists
|
||||
if _, ok := optsRequesting[k]; !ok && !skipRequireDefault {
|
||||
optsRequesting[k] = v
|
||||
}
|
||||
}
|
||||
return optsRequesting, optsProviding, err
|
||||
}
|
||||
|
||||
// AcquireLease gets an DHCP lease and then maintains it in the background
|
||||
// by periodically renewing it. The acquired lease can be released by
|
||||
// calling DHCPLease.Stop()
|
||||
func AcquireLease(clientID, netns, ifName string) (*DHCPLease, error) {
|
||||
func AcquireLease(
|
||||
clientID, netns, ifName string,
|
||||
optsRequesting map[dhcp4.OptionCode]bool, optsProviding map[dhcp4.OptionCode][]byte,
|
||||
timeout, resendMax time.Duration, broadcast bool,
|
||||
) (*DHCPLease, error) {
|
||||
errCh := make(chan error, 1)
|
||||
l := &DHCPLease{
|
||||
clientID: clientID,
|
||||
stop: make(chan struct{}),
|
||||
check: make(chan struct{}),
|
||||
timeout: timeout,
|
||||
resendMax: resendMax,
|
||||
broadcast: broadcast,
|
||||
optsRequesting: optsRequesting,
|
||||
optsProviding: optsProviding,
|
||||
}
|
||||
|
||||
log.Printf("%v: acquiring lease", clientID)
|
||||
@ -114,8 +205,36 @@ func (l *DHCPLease) Stop() {
|
||||
l.wg.Wait()
|
||||
}
|
||||
|
||||
func (l *DHCPLease) Check() {
|
||||
l.check <- struct{}{}
|
||||
}
|
||||
|
||||
func (l *DHCPLease) getOptionsWithClientID() dhcp4.Options {
|
||||
opts := make(dhcp4.Options)
|
||||
opts[dhcp4.OptionClientIdentifier] = []byte(l.clientID)
|
||||
// client identifier's first byte is "type"
|
||||
newClientID := []byte{0}
|
||||
newClientID = append(newClientID, opts[dhcp4.OptionClientIdentifier]...)
|
||||
opts[dhcp4.OptionClientIdentifier] = newClientID
|
||||
return opts
|
||||
}
|
||||
|
||||
func (l *DHCPLease) getAllOptions() dhcp4.Options {
|
||||
opts := l.getOptionsWithClientID()
|
||||
|
||||
for k, v := range l.optsProviding {
|
||||
opts[k] = v
|
||||
}
|
||||
|
||||
opts[dhcp4.OptionParameterRequestList] = []byte{}
|
||||
for k := range l.optsRequesting {
|
||||
opts[dhcp4.OptionParameterRequestList] = append(opts[dhcp4.OptionParameterRequestList], byte(k))
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func (l *DHCPLease) acquire() error {
|
||||
c, err := newDHCPClient(l.link, l.clientID)
|
||||
c, err := newDHCPClient(l.link, l.timeout, l.broadcast)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -128,11 +247,9 @@ func (l *DHCPLease) acquire() error {
|
||||
}
|
||||
}
|
||||
|
||||
opts := make(dhcp4.Options)
|
||||
opts[dhcp4.OptionClientIdentifier] = []byte(l.clientID)
|
||||
opts[dhcp4.OptionParameterRequestList] = []byte{byte(dhcp4.OptionRouter), byte(dhcp4.OptionSubnetMask)}
|
||||
opts := l.getAllOptions()
|
||||
|
||||
pkt, err := backoffRetry(func() (*dhcp4.Packet, error) {
|
||||
pkt, err := backoffRetry(l.resendMax, func() (*dhcp4.Packet, error) {
|
||||
ok, ack, err := DhcpRequest(c, opts)
|
||||
switch {
|
||||
case err != nil:
|
||||
@ -188,7 +305,7 @@ func (l *DHCPLease) maintain() {
|
||||
|
||||
switch state {
|
||||
case leaseStateBound:
|
||||
sleepDur = l.renewalTime.Sub(time.Now())
|
||||
sleepDur = time.Until(l.renewalTime)
|
||||
if sleepDur <= 0 {
|
||||
log.Printf("%v: renewing lease", l.clientID)
|
||||
state = leaseStateRenewing
|
||||
@ -200,7 +317,7 @@ func (l *DHCPLease) maintain() {
|
||||
log.Printf("%v: %v", l.clientID, err)
|
||||
|
||||
if time.Now().After(l.rebindingTime) {
|
||||
log.Printf("%v: renawal time expired, rebinding", l.clientID)
|
||||
log.Printf("%v: renewal time expired, rebinding", l.clientID)
|
||||
state = leaseStateRebinding
|
||||
}
|
||||
} else {
|
||||
@ -226,6 +343,9 @@ func (l *DHCPLease) maintain() {
|
||||
select {
|
||||
case <-time.After(sleepDur):
|
||||
|
||||
case <-l.check:
|
||||
log.Printf("%v: Checking lease", l.clientID)
|
||||
|
||||
case <-l.stop:
|
||||
if err := l.release(); err != nil {
|
||||
log.Printf("%v: failed to release DHCP lease: %v", l.clientID, err)
|
||||
@ -242,16 +362,14 @@ func (l *DHCPLease) downIface() {
|
||||
}
|
||||
|
||||
func (l *DHCPLease) renew() error {
|
||||
c, err := newDHCPClient(l.link, l.clientID)
|
||||
c, err := newDHCPClient(l.link, l.timeout, l.broadcast)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
opts := make(dhcp4.Options)
|
||||
opts[dhcp4.OptionClientIdentifier] = []byte(l.clientID)
|
||||
|
||||
pkt, err := backoffRetry(func() (*dhcp4.Packet, error) {
|
||||
opts := l.getAllOptions()
|
||||
pkt, err := backoffRetry(l.resendMax, func() (*dhcp4.Packet, error) {
|
||||
ok, ack, err := DhcpRenew(c, *l.ack, opts)
|
||||
switch {
|
||||
case err != nil:
|
||||
@ -273,14 +391,13 @@ func (l *DHCPLease) renew() error {
|
||||
func (l *DHCPLease) release() error {
|
||||
log.Printf("%v: releasing lease", l.clientID)
|
||||
|
||||
c, err := newDHCPClient(l.link, l.clientID)
|
||||
c, err := newDHCPClient(l.link, l.timeout, l.broadcast)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
opts := make(dhcp4.Options)
|
||||
opts[dhcp4.OptionClientIdentifier] = []byte(l.clientID)
|
||||
opts := l.getOptionsWithClientID()
|
||||
|
||||
if err = DhcpRelease(c, *l.ack, opts); err != nil {
|
||||
return fmt.Errorf("failed to send DHCPRELEASE")
|
||||
@ -310,9 +427,9 @@ func (l *DHCPLease) Routes() []*types.Route {
|
||||
|
||||
// RFC 3442 states that if Classless Static Routes (option 121)
|
||||
// exist, we ignore Static Routes (option 33) and the Router/Gateway.
|
||||
opt121_routes := parseCIDRRoutes(l.opts)
|
||||
if len(opt121_routes) > 0 {
|
||||
return append(routes, opt121_routes...)
|
||||
opt121Routes := parseCIDRRoutes(l.opts)
|
||||
if len(opt121Routes) > 0 {
|
||||
return append(routes, opt121Routes...)
|
||||
}
|
||||
|
||||
// Append Static Routes
|
||||
@ -333,10 +450,11 @@ func jitter(span time.Duration) time.Duration {
|
||||
return time.Duration(float64(span) * (2.0*rand.Float64() - 1.0))
|
||||
}
|
||||
|
||||
func backoffRetry(f func() (*dhcp4.Packet, error)) (*dhcp4.Packet, error) {
|
||||
var baseDelay time.Duration = resendDelay0
|
||||
|
||||
for i := 0; i < resendCount; i++ {
|
||||
func backoffRetry(resendMax time.Duration, f func() (*dhcp4.Packet, error)) (*dhcp4.Packet, error) {
|
||||
baseDelay := resendDelay0
|
||||
var sleepTime time.Duration
|
||||
fastRetryLimit := resendFastMax
|
||||
for {
|
||||
pkt, err := f()
|
||||
if err == nil {
|
||||
return pkt, nil
|
||||
@ -344,17 +462,33 @@ func backoffRetry(f func() (*dhcp4.Packet, error)) (*dhcp4.Packet, error) {
|
||||
|
||||
log.Print(err)
|
||||
|
||||
time.Sleep(baseDelay + jitter(time.Second))
|
||||
if fastRetryLimit == 0 {
|
||||
sleepTime = baseDelay + jitter(time.Second)
|
||||
} else {
|
||||
sleepTime = resendFastDelay + jitter(time.Second)
|
||||
fastRetryLimit--
|
||||
}
|
||||
|
||||
if baseDelay < resendDelayMax {
|
||||
log.Printf("retrying in %f seconds", sleepTime.Seconds())
|
||||
|
||||
time.Sleep(sleepTime)
|
||||
|
||||
// only adjust delay time if we are in normal backoff stage
|
||||
if baseDelay < resendMax && fastRetryLimit == 0 {
|
||||
baseDelay *= 2
|
||||
} else if fastRetryLimit == 0 { // only break if we are at normal delay
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errNoMoreTries
|
||||
}
|
||||
|
||||
func newDHCPClient(link netlink.Link, clientID string) (*dhcp4client.Client, error) {
|
||||
func newDHCPClient(
|
||||
link netlink.Link,
|
||||
timeout time.Duration,
|
||||
broadcast bool,
|
||||
) (*dhcp4client.Client, error) {
|
||||
pktsock, err := dhcp4client.NewPacketSock(link.Attrs().Index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -362,8 +496,8 @@ func newDHCPClient(link netlink.Link, clientID string) (*dhcp4client.Client, err
|
||||
|
||||
return dhcp4client.New(
|
||||
dhcp4client.HardwareAddr(link.Attrs().HardwareAddr),
|
||||
dhcp4client.Timeout(5*time.Second),
|
||||
dhcp4client.Broadcast(false),
|
||||
dhcp4client.Timeout(timeout),
|
||||
dhcp4client.Broadcast(broadcast),
|
||||
dhcp4client.Connection(pktsock),
|
||||
)
|
||||
}
|
||||
|
@ -22,33 +22,77 @@ import (
|
||||
"net/rpc"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
current "github.com/containernetworking/cni/pkg/types/100"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
const defaultSocketPath = "/run/cni/dhcp.sock"
|
||||
|
||||
// The top-level network config - IPAM plugins are passed the full configuration
|
||||
// of the calling plugin, not just the IPAM section.
|
||||
type NetConf struct {
|
||||
types.NetConf
|
||||
IPAM *IPAMConfig `json:"ipam"`
|
||||
}
|
||||
|
||||
type IPAMConfig struct {
|
||||
types.IPAM
|
||||
DaemonSocketPath string `json:"daemonSocketPath"`
|
||||
// When requesting IP from DHCP server, carry these options for management purpose.
|
||||
// Some fields have default values, and can be override by setting a new option with the same name at here.
|
||||
ProvideOptions []ProvideOption `json:"provide"`
|
||||
// When requesting IP from DHCP server, claiming these options are necessary. Options are necessary unless `optional`
|
||||
// is set to `false`.
|
||||
// To override default requesting fields, set `skipDefault` to `false`.
|
||||
// If an field is not optional, but the server failed to provide it, error will be raised.
|
||||
RequestOptions []RequestOption `json:"request"`
|
||||
}
|
||||
|
||||
// DHCPOption represents a DHCP option. It can be a number, or a string defined in manual dhcp-options(5).
|
||||
// Note that not all DHCP options are supported at all time. Error will be raised if unsupported options are used.
|
||||
type DHCPOption string
|
||||
|
||||
type ProvideOption struct {
|
||||
Option DHCPOption `json:"option"`
|
||||
|
||||
Value string `json:"value"`
|
||||
ValueFromCNIArg string `json:"fromArg"`
|
||||
}
|
||||
|
||||
type RequestOption struct {
|
||||
SkipDefault bool `json:"skipDefault"`
|
||||
|
||||
Option DHCPOption `json:"option"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) > 1 && os.Args[1] == "daemon" {
|
||||
var pidfilePath string
|
||||
var hostPrefix string
|
||||
var socketPath string
|
||||
var broadcast bool
|
||||
var timeout time.Duration
|
||||
var resendMax time.Duration
|
||||
daemonFlags := flag.NewFlagSet("daemon", flag.ExitOnError)
|
||||
daemonFlags.StringVar(&pidfilePath, "pidfile", "", "optional path to write daemon PID to")
|
||||
daemonFlags.StringVar(&hostPrefix, "hostprefix", "", "optional prefix to host root")
|
||||
daemonFlags.StringVar(&socketPath, "socketpath", "", "optional dhcp server socketpath")
|
||||
daemonFlags.BoolVar(&broadcast, "broadcast", false, "broadcast DHCP leases")
|
||||
daemonFlags.DurationVar(&timeout, "timeout", 10*time.Second, "optional dhcp client timeout duration")
|
||||
daemonFlags.DurationVar(&resendMax, "resendmax", resendDelayMax, "optional dhcp client resend max duration")
|
||||
daemonFlags.Parse(os.Args[2:])
|
||||
|
||||
if socketPath == "" {
|
||||
socketPath = defaultSocketPath
|
||||
}
|
||||
|
||||
if err := runDaemon(pidfilePath, hostPrefix, socketPath); err != nil {
|
||||
log.Printf(err.Error())
|
||||
if err := runDaemon(pidfilePath, hostPrefix, socketPath, timeout, resendMax, broadcast); err != nil {
|
||||
log.Print(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
@ -64,7 +108,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
return err
|
||||
}
|
||||
|
||||
result := ¤t.Result{}
|
||||
result := ¤t.Result{CNIVersion: current.ImplementedSpecVersion}
|
||||
if err := rpcCall("DHCP.Allocate", args, result); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -74,41 +118,24 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
|
||||
func cmdDel(args *skel.CmdArgs) error {
|
||||
result := struct{}{}
|
||||
if err := rpcCall("DHCP.Release", args, &result); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return rpcCall("DHCP.Release", args, &result)
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
// TODO: implement
|
||||
//return fmt.Errorf("not implemented")
|
||||
// Plugin must return result in same version as specified in netconf
|
||||
versionDecoder := &version.ConfigDecoder{}
|
||||
//confVersion, err := versionDecoder.Decode(args.StdinData)
|
||||
// confVersion, err := versionDecoder.Decode(args.StdinData)
|
||||
_, err := versionDecoder.Decode(args.StdinData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result := ¤t.Result{}
|
||||
if err := rpcCall("DHCP.Allocate", args, result); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type SocketPathConf struct {
|
||||
DaemonSocketPath string `json:"daemonSocketPath,omitempty"`
|
||||
}
|
||||
|
||||
type TempNetConf struct {
|
||||
IPAM SocketPathConf `json:"ipam,omitempty"`
|
||||
result := ¤t.Result{CNIVersion: current.ImplementedSpecVersion}
|
||||
return rpcCall("DHCP.Allocate", args, result)
|
||||
}
|
||||
|
||||
func getSocketPath(stdinData []byte) (string, error) {
|
||||
conf := TempNetConf{}
|
||||
conf := NetConf{}
|
||||
if err := json.Unmarshal(stdinData, &conf); err != nil {
|
||||
return "", fmt.Errorf("error parsing socket path conf: %v", err)
|
||||
}
|
||||
|
@ -18,12 +18,34 @@ import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/d2g/dhcp4"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
)
|
||||
|
||||
var optionNameToID = map[string]dhcp4.OptionCode{
|
||||
"dhcp-client-identifier": dhcp4.OptionClientIdentifier,
|
||||
"subnet-mask": dhcp4.OptionSubnetMask,
|
||||
"routers": dhcp4.OptionRouter,
|
||||
"host-name": dhcp4.OptionHostName,
|
||||
"user-class": dhcp4.OptionUserClass,
|
||||
"vendor-class-identifier": dhcp4.OptionVendorClassIdentifier,
|
||||
}
|
||||
|
||||
func parseOptionName(option string) (dhcp4.OptionCode, error) {
|
||||
if val, ok := optionNameToID[option]; ok {
|
||||
return val, nil
|
||||
}
|
||||
i, err := strconv.ParseUint(option, 10, 8)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("Can not parse option: %w", err)
|
||||
}
|
||||
return dhcp4.OptionCode(i), nil
|
||||
}
|
||||
|
||||
func parseRouter(opts dhcp4.Options) net.IP {
|
||||
if opts, ok := opts[dhcp4.OptionRouter]; ok {
|
||||
if len(opts) == 4 {
|
||||
|
@ -16,10 +16,12 @@ package main
|
||||
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/d2g/dhcp4"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
)
|
||||
|
||||
func validateRoutes(t *testing.T, routes []*types.Route) {
|
||||
@ -73,3 +75,34 @@ func TestParseCIDRRoutes(t *testing.T) {
|
||||
|
||||
validateRoutes(t, routes)
|
||||
}
|
||||
|
||||
func TestParseOptionName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
option string
|
||||
want dhcp4.OptionCode
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"hostname", "host-name", dhcp4.OptionHostName, false,
|
||||
},
|
||||
{
|
||||
"hostname in number", "12", dhcp4.OptionHostName, false,
|
||||
},
|
||||
{
|
||||
"random string", "doNotparseMe", 0, true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := parseOptionName(tt.option)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("parseOptionName() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("parseOptionName() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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/current/ipam/host-local/
|
||||
|
@ -21,7 +21,7 @@ import (
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
current "github.com/containernetworking/cni/pkg/types/100"
|
||||
"github.com/containernetworking/plugins/pkg/ip"
|
||||
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend"
|
||||
)
|
||||
@ -108,13 +108,8 @@ func (a *IPAllocator) Get(id string, ifname string, requestedIP net.IP) (*curren
|
||||
if reservedIP == nil {
|
||||
return nil, fmt.Errorf("no IP addresses available in range set: %s", a.rangeset.String())
|
||||
}
|
||||
version := "4"
|
||||
if reservedIP.IP.To4() == nil {
|
||||
version = "6"
|
||||
}
|
||||
|
||||
return ¤t.IPConfig{
|
||||
Version: version,
|
||||
Address: *reservedIP,
|
||||
Gateway: gw,
|
||||
}, nil
|
||||
@ -137,9 +132,8 @@ type RangeIter struct {
|
||||
// Our current position
|
||||
cur net.IP
|
||||
|
||||
// The IP and range index where we started iterating; if we hit this again, we're done.
|
||||
// The IP where we started iterating; if we hit this again, we're done.
|
||||
startIP net.IP
|
||||
startRange int
|
||||
}
|
||||
|
||||
// GetIter encapsulates the strategy for this allocator.
|
||||
@ -169,7 +163,6 @@ func (a *IPAllocator) GetIter() (*RangeIter, error) {
|
||||
for i, r := range *a.rangeset {
|
||||
if r.Contains(lastReservedIP) {
|
||||
iter.rangeIdx = i
|
||||
iter.startRange = i
|
||||
|
||||
// We advance the cursor on every Next(), so the first call
|
||||
// to next() will return lastReservedIP + 1
|
||||
@ -179,7 +172,6 @@ func (a *IPAllocator) GetIter() (*RangeIter, error) {
|
||||
}
|
||||
} else {
|
||||
iter.rangeIdx = 0
|
||||
iter.startRange = 0
|
||||
iter.startIP = (*a.rangeset)[0].RangeStart
|
||||
}
|
||||
return &iter, nil
|
||||
@ -204,7 +196,7 @@ func (i *RangeIter) Next() (*net.IPNet, net.IP) {
|
||||
// If we've reached the end of this range, we need to advance the range
|
||||
// RangeEnd is inclusive as well
|
||||
if i.cur.Equal(r.RangeEnd) {
|
||||
i.rangeIdx += 1
|
||||
i.rangeIdx++
|
||||
i.rangeIdx %= len(*i.rangeset)
|
||||
r = (*i.rangeset)[i.rangeIdx]
|
||||
|
||||
@ -215,7 +207,7 @@ func (i *RangeIter) Next() (*net.IPNet, net.IP) {
|
||||
|
||||
if i.startIP == nil {
|
||||
i.startIP = i.cur
|
||||
} else if i.rangeIdx == i.startRange && i.cur.Equal(i.startIP) {
|
||||
} else if i.cur.Equal(i.startIP) {
|
||||
// IF we've looped back to where we started, give up
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -15,10 +15,10 @@
|
||||
package allocator_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestAllocator(t *testing.T) {
|
||||
|
@ -18,12 +18,12 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
fakestore "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
current "github.com/containernetworking/cni/pkg/types/100"
|
||||
fakestore "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/testing"
|
||||
)
|
||||
|
||||
type AllocatorTestCase struct {
|
||||
@ -49,6 +49,23 @@ func mkalloc() IPAllocator {
|
||||
return alloc
|
||||
}
|
||||
|
||||
func newAllocatorWithMultiRanges() IPAllocator {
|
||||
p := RangeSet{
|
||||
Range{RangeStart: net.IP{192, 168, 1, 0}, RangeEnd: net.IP{192, 168, 1, 3}, Subnet: mustSubnet("192.168.1.1/30")},
|
||||
Range{RangeStart: net.IP{192, 168, 2, 0}, RangeEnd: net.IP{192, 168, 2, 3}, Subnet: mustSubnet("192.168.2.1/30")},
|
||||
}
|
||||
_ = p.Canonicalize()
|
||||
store := fakestore.NewFakeStore(map[string]string{}, map[string]net.IP{})
|
||||
|
||||
alloc := IPAllocator{
|
||||
rangeset: &p,
|
||||
store: store,
|
||||
rangeID: "rangeid",
|
||||
}
|
||||
|
||||
return alloc
|
||||
}
|
||||
|
||||
func (t AllocatorTestCase) run(idx int) (*current.IPConfig, error) {
|
||||
fmt.Fprintln(GinkgoWriter, "Index:", idx)
|
||||
p := RangeSet{}
|
||||
@ -60,7 +77,7 @@ func (t AllocatorTestCase) run(idx int) (*current.IPConfig, error) {
|
||||
p = append(p, Range{Subnet: types.IPNet(*subnet)})
|
||||
}
|
||||
|
||||
Expect(p.Canonicalize()).To(BeNil())
|
||||
Expect(p.Canonicalize()).To(Succeed())
|
||||
|
||||
store := fakestore.NewFakeStore(t.ipmap, map[string]net.IP{"rangeid": net.ParseIP(t.lastIP)})
|
||||
|
||||
@ -245,7 +262,6 @@ var _ = Describe("host-local ip allocator", func() {
|
||||
res, err = alloc.Get("ID", "eth0", nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(res.Address.String()).To(Equal("192.168.1.3/29"))
|
||||
|
||||
})
|
||||
|
||||
Context("when requesting a specific IP", func() {
|
||||
@ -284,7 +300,6 @@ var _ = Describe("host-local ip allocator", func() {
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
Context("when out of ips", func() {
|
||||
It("returns a meaningful error", func() {
|
||||
@ -315,11 +330,44 @@ var _ = Describe("host-local ip allocator", func() {
|
||||
}
|
||||
for idx, tc := range testCases {
|
||||
_, err := tc.run(idx)
|
||||
Expect(err).NotTo(BeNil())
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(HavePrefix("no IP addresses available in range set"))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Context("when lastReservedIP is at the end of one of multi ranges", func() {
|
||||
It("should use the first IP of next range as startIP after Next", func() {
|
||||
a := newAllocatorWithMultiRanges()
|
||||
|
||||
// reserve the last IP of the first range
|
||||
reserved, err := a.store.Reserve("ID", "eth0", net.IP{192, 168, 1, 3}, a.rangeID)
|
||||
Expect(reserved).To(BeTrue())
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// get range iterator and do the first Next
|
||||
r, err := a.GetIter()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
ip := r.nextip()
|
||||
Expect(ip).NotTo(BeNil())
|
||||
|
||||
Expect(r.startIP).To(Equal(net.IP{192, 168, 2, 0}))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when no lastReservedIP", func() {
|
||||
It("should use the first IP of the first range as startIP after Next", func() {
|
||||
a := newAllocatorWithMultiRanges()
|
||||
|
||||
// get range iterator and do the first Next
|
||||
r, err := a.GetIter()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
ip := r.nextip()
|
||||
Expect(ip).NotTo(BeNil())
|
||||
|
||||
Expect(r.startIP).To(Equal(net.IP{192, 168, 1, 0}))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// nextip is a convenience function used for testing
|
||||
|
@ -20,7 +20,8 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/020"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
"github.com/containernetworking/plugins/pkg/ip"
|
||||
)
|
||||
|
||||
// The top-level network config - IPAM plugins are passed the full configuration
|
||||
@ -29,8 +30,10 @@ type Net struct {
|
||||
Name string `json:"name"`
|
||||
CNIVersion string `json:"cniVersion"`
|
||||
IPAM *IPAMConfig `json:"ipam"`
|
||||
RuntimeConfig struct { // The capability arg
|
||||
RuntimeConfig struct {
|
||||
// The capability arg
|
||||
IPRanges []RangeSet `json:"ipRanges,omitempty"`
|
||||
IPs []*ip.IP `json:"ips,omitempty"`
|
||||
} `json:"runtimeConfig,omitempty"`
|
||||
Args *struct {
|
||||
A *IPAMArgs `json:"cni"`
|
||||
@ -39,7 +42,7 @@ type Net struct {
|
||||
|
||||
// IPAMConfig represents the IP related network configuration.
|
||||
// This nests Range because we initially only supported a single
|
||||
// range directly, and wish to preserve backwards compatability
|
||||
// range directly, and wish to preserve backwards compatibility
|
||||
type IPAMConfig struct {
|
||||
*Range
|
||||
Name string
|
||||
@ -48,16 +51,16 @@ type IPAMConfig struct {
|
||||
DataDir string `json:"dataDir"`
|
||||
ResolvConf string `json:"resolvConf"`
|
||||
Ranges []RangeSet `json:"ranges"`
|
||||
IPArgs []net.IP `json:"-"` // Requested IPs from CNI_ARGS and args
|
||||
IPArgs []net.IP `json:"-"` // Requested IPs from CNI_ARGS, args and capabilities
|
||||
}
|
||||
|
||||
type IPAMEnvArgs struct {
|
||||
types.CommonArgs
|
||||
IP net.IP `json:"ip,omitempty"`
|
||||
IP ip.IP `json:"ip,omitempty"`
|
||||
}
|
||||
|
||||
type IPAMArgs struct {
|
||||
IPs []net.IP `json:"ips"`
|
||||
IPs []*ip.IP `json:"ips"`
|
||||
}
|
||||
|
||||
type RangeSet []Range
|
||||
@ -80,7 +83,7 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
|
||||
return nil, "", fmt.Errorf("IPAM config missing 'ipam' key")
|
||||
}
|
||||
|
||||
// Parse custom IP from both env args *and* the top-level args config
|
||||
// parse custom IP from env args
|
||||
if envArgs != "" {
|
||||
e := IPAMEnvArgs{}
|
||||
err := types.LoadArgs(envArgs, &e)
|
||||
@ -88,13 +91,23 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if e.IP != nil {
|
||||
n.IPAM.IPArgs = []net.IP{e.IP}
|
||||
if e.IP.ToIP() != nil {
|
||||
n.IPAM.IPArgs = []net.IP{e.IP.ToIP()}
|
||||
}
|
||||
}
|
||||
|
||||
// parse custom IPs from CNI args in network config
|
||||
if n.Args != nil && n.Args.A != nil && len(n.Args.A.IPs) != 0 {
|
||||
n.IPAM.IPArgs = append(n.IPAM.IPArgs, n.Args.A.IPs...)
|
||||
for _, i := range n.Args.A.IPs {
|
||||
n.IPAM.IPArgs = append(n.IPAM.IPArgs, i.ToIP())
|
||||
}
|
||||
}
|
||||
|
||||
// parse custom IPs from runtime configuration
|
||||
if len(n.RuntimeConfig.IPs) > 0 {
|
||||
for _, i := range n.RuntimeConfig.IPs {
|
||||
n.IPAM.IPArgs = append(n.IPAM.IPArgs, i.ToIP())
|
||||
}
|
||||
}
|
||||
|
||||
for idx := range n.IPAM.IPArgs {
|
||||
@ -136,12 +149,10 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
|
||||
|
||||
// CNI spec 0.2.0 and below supported only one v4 and v6 address
|
||||
if numV4 > 1 || numV6 > 1 {
|
||||
for _, v := range types020.SupportedVersions {
|
||||
if n.CNIVersion == v {
|
||||
if ok, _ := version.GreaterThanOrEqualTo(n.CNIVersion, "0.3.0"); !ok {
|
||||
return nil, "", fmt.Errorf("CNI version %v does not support more than 1 address per family", n.CNIVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for overlaps
|
||||
l := len(n.IPAM.Ranges)
|
||||
|
@ -17,9 +17,10 @@ package allocator
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
)
|
||||
|
||||
var _ = Describe("IPAM config", func() {
|
||||
@ -205,7 +206,8 @@ var _ = Describe("IPAM config", func() {
|
||||
}))
|
||||
})
|
||||
|
||||
It("Should parse CNI_ARGS env", func() {
|
||||
Context("Should parse CNI_ARGS env", func() {
|
||||
It("without prefix", func() {
|
||||
input := `{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
@ -229,10 +231,37 @@ var _ = Describe("IPAM config", func() {
|
||||
conf, _, err := LoadIPAMConfig([]byte(input), envArgs)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(conf.IPArgs).To(Equal([]net.IP{{10, 1, 2, 10}}))
|
||||
|
||||
})
|
||||
|
||||
It("Should parse config args", func() {
|
||||
It("with prefix", func() {
|
||||
input := `{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"ranges": [[
|
||||
{
|
||||
"subnet": "10.1.2.0/24",
|
||||
"rangeStart": "10.1.2.9",
|
||||
"rangeEnd": "10.1.2.20",
|
||||
"gateway": "10.1.2.30"
|
||||
}
|
||||
]]
|
||||
}
|
||||
}`
|
||||
|
||||
envArgs := "IP=10.1.2.11/24"
|
||||
|
||||
conf, _, err := LoadIPAMConfig([]byte(input), envArgs)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(conf.IPArgs).To(Equal([]net.IP{{10, 1, 2, 11}}))
|
||||
})
|
||||
})
|
||||
|
||||
Context("Should parse config args", func() {
|
||||
It("without prefix", func() {
|
||||
input := `{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
@ -277,6 +306,52 @@ var _ = Describe("IPAM config", func() {
|
||||
}))
|
||||
})
|
||||
|
||||
It("with prefix", func() {
|
||||
input := `{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
"args": {
|
||||
"cni": {
|
||||
"ips": [ "10.1.2.11/24", "11.11.11.11/24", "2001:db8:1::11/64"]
|
||||
}
|
||||
},
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"ranges": [
|
||||
[{
|
||||
"subnet": "10.1.2.0/24",
|
||||
"rangeStart": "10.1.2.9",
|
||||
"rangeEnd": "10.1.2.20",
|
||||
"gateway": "10.1.2.30"
|
||||
}],
|
||||
[{
|
||||
"subnet": "11.1.2.0/24",
|
||||
"rangeStart": "11.1.2.9",
|
||||
"rangeEnd": "11.1.2.20",
|
||||
"gateway": "11.1.2.30"
|
||||
}],
|
||||
[{
|
||||
"subnet": "2001:db8:1::/64"
|
||||
}]
|
||||
]
|
||||
}
|
||||
}`
|
||||
|
||||
envArgs := "IP=10.1.2.10/24"
|
||||
|
||||
conf, _, err := LoadIPAMConfig([]byte(input), envArgs)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(conf.IPArgs).To(Equal([]net.IP{
|
||||
{10, 1, 2, 10},
|
||||
{10, 1, 2, 11},
|
||||
{11, 11, 11, 11},
|
||||
net.ParseIP("2001:db8:1::11"),
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
It("Should detect overlap between rangesets", func() {
|
||||
input := `{
|
||||
"cniVersion": "0.3.1",
|
||||
@ -341,7 +416,6 @@ var _ = Describe("IPAM config", func() {
|
||||
}`
|
||||
_, _, err := LoadIPAMConfig([]byte(input), "")
|
||||
Expect(err).To(MatchError("invalid range set 0: mixed address families"))
|
||||
|
||||
})
|
||||
|
||||
It("Should should error on too many ranges", func() {
|
||||
@ -379,4 +453,29 @@ var _ = Describe("IPAM config", func() {
|
||||
_, _, err := LoadIPAMConfig([]byte(input), "")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("Should parse custom IPs from runtime configuration", func() {
|
||||
input := `{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
"runtimeConfig": {
|
||||
"ips": ["192.168.0.1", "192.168.0.5/24", "2001:db8::1/64"]
|
||||
},
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.2.0/24"
|
||||
}
|
||||
}`
|
||||
conf, version, err := LoadIPAMConfig([]byte(input), "")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(version).Should(Equal("0.3.1"))
|
||||
|
||||
Expect(conf.IPArgs).To(Equal([]net.IP{
|
||||
net.IPv4(192, 168, 0, 1).To4(),
|
||||
net.IPv4(192, 168, 0, 5).To4(),
|
||||
net.ParseIP("2001:db8::1"),
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
@ -125,7 +125,7 @@ func (r *Range) Contains(addr net.IP) bool {
|
||||
|
||||
// Overlaps returns true if there is any overlap between ranges
|
||||
func (r *Range) Overlaps(r1 *Range) bool {
|
||||
// different familes
|
||||
// different families
|
||||
if len(r.RangeStart) != len(r1.RangeStart) {
|
||||
return false
|
||||
}
|
||||
|
@ -67,12 +67,10 @@ func (s *RangeSet) Canonicalize() error {
|
||||
}
|
||||
if i == 0 {
|
||||
fam = len((*s)[i].RangeStart)
|
||||
} else {
|
||||
if fam != len((*s)[i].RangeStart) {
|
||||
} else if fam != len((*s)[i].RangeStart) {
|
||||
return fmt.Errorf("mixed address families")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure none of the ranges in the set overlap
|
||||
l := len(*s)
|
||||
|
@ -17,7 +17,7 @@ package allocator
|
||||
import (
|
||||
"net"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
@ -40,7 +40,6 @@ var _ = Describe("range sets", func() {
|
||||
r, err = p.RangeFor(net.IP{192, 168, 99, 99})
|
||||
Expect(r).To(BeNil())
|
||||
Expect(err).To(MatchError("192.168.99.99 not in range set 192.168.0.1-192.168.0.254,172.16.1.1-172.16.1.254"))
|
||||
|
||||
})
|
||||
|
||||
It("should discover overlaps within a set", func() {
|
||||
|
@ -17,11 +17,10 @@ package allocator
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/extensions/table"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
)
|
||||
|
||||
var _ = Describe("IP ranges", func() {
|
||||
|
@ -15,7 +15,6 @@
|
||||
package disk
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@ -25,8 +24,10 @@ import (
|
||||
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend"
|
||||
)
|
||||
|
||||
const lastIPFilePrefix = "last_reserved_ip."
|
||||
const LineBreak = "\r\n"
|
||||
const (
|
||||
lastIPFilePrefix = "last_reserved_ip."
|
||||
LineBreak = "\r\n"
|
||||
)
|
||||
|
||||
var defaultDataDir = "/var/lib/cni/networks"
|
||||
|
||||
@ -45,7 +46,7 @@ func New(network, dataDir string) (*Store, error) {
|
||||
dataDir = defaultDataDir
|
||||
}
|
||||
dir := filepath.Join(dataDir, network)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -59,7 +60,7 @@ func New(network, dataDir string) (*Store, error) {
|
||||
func (s *Store) Reserve(id string, ifname string, ip net.IP, rangeID string) (bool, error) {
|
||||
fname := GetEscapedPath(s.dataDir, ip.String())
|
||||
|
||||
f, err := os.OpenFile(fname, os.O_RDWR|os.O_EXCL|os.O_CREATE, 0644)
|
||||
f, err := os.OpenFile(fname, os.O_RDWR|os.O_EXCL|os.O_CREATE, 0o600)
|
||||
if os.IsExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
@ -77,7 +78,7 @@ func (s *Store) Reserve(id string, ifname string, ip net.IP, rangeID string) (bo
|
||||
}
|
||||
// store the reserved ip in lastIPFile
|
||||
ipfile := GetEscapedPath(s.dataDir, lastIPFilePrefix+rangeID)
|
||||
err = ioutil.WriteFile(ipfile, []byte(ip.String()), 0644)
|
||||
err = os.WriteFile(ipfile, []byte(ip.String()), 0o600)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -87,25 +88,21 @@ func (s *Store) Reserve(id string, ifname string, ip net.IP, rangeID string) (bo
|
||||
// LastReservedIP returns the last reserved IP if exists
|
||||
func (s *Store) LastReservedIP(rangeID string) (net.IP, error) {
|
||||
ipfile := GetEscapedPath(s.dataDir, lastIPFilePrefix+rangeID)
|
||||
data, err := ioutil.ReadFile(ipfile)
|
||||
data, err := os.ReadFile(ipfile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return net.ParseIP(string(data)), nil
|
||||
}
|
||||
|
||||
func (s *Store) Release(ip net.IP) error {
|
||||
return os.Remove(GetEscapedPath(s.dataDir, ip.String()))
|
||||
}
|
||||
|
||||
func (s *Store) FindByKey(id string, ifname string, match string) (bool, error) {
|
||||
func (s *Store) FindByKey(match string) (bool, error) {
|
||||
found := false
|
||||
|
||||
err := 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)
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
@ -115,33 +112,31 @@ func (s *Store) FindByKey(id string, ifname string, match string) (bool, error)
|
||||
return nil
|
||||
})
|
||||
return found, err
|
||||
|
||||
}
|
||||
|
||||
func (s *Store) FindByID(id string, ifname string) bool {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
found := false
|
||||
match := strings.TrimSpace(id) + LineBreak + ifname
|
||||
found, err := s.FindByKey(id, ifname, match)
|
||||
found, err := s.FindByKey(match)
|
||||
|
||||
// Match anything created by this id
|
||||
if !found && err == nil {
|
||||
match := strings.TrimSpace(id)
|
||||
found, err = s.FindByKey(id, ifname, match)
|
||||
found, _ = s.FindByKey(match)
|
||||
}
|
||||
|
||||
return found
|
||||
}
|
||||
|
||||
func (s *Store) ReleaseByKey(id string, ifname string, match string) (bool, error) {
|
||||
func (s *Store) ReleaseByKey(match string) (bool, error) {
|
||||
found := false
|
||||
err := 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)
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
@ -154,20 +149,18 @@ func (s *Store) ReleaseByKey(id string, ifname string, match string) (bool, erro
|
||||
return nil
|
||||
})
|
||||
return found, err
|
||||
|
||||
}
|
||||
|
||||
// N.B. This function eats errors to be tolerant and
|
||||
// release as much as possible
|
||||
func (s *Store) ReleaseByID(id string, ifname string) error {
|
||||
found := false
|
||||
match := strings.TrimSpace(id) + LineBreak + ifname
|
||||
found, err := s.ReleaseByKey(id, ifname, match)
|
||||
found, err := s.ReleaseByKey(match)
|
||||
|
||||
// For backwards compatibility, look for files written by a previous version
|
||||
if !found && err == nil {
|
||||
match := strings.TrimSpace(id)
|
||||
found, err = s.ReleaseByKey(id, ifname, match)
|
||||
_, err = s.ReleaseByKey(match)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@ -185,7 +178,7 @@ func (s *Store) GetByID(id string, ifname string) []net.IP {
|
||||
if err != nil || info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
data, err := ioutil.ReadFile(path)
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
@ -203,7 +196,7 @@ func (s *Store) GetByID(id string, ifname string) []net.IP {
|
||||
|
||||
func GetEscapedPath(dataDir string, fname string) string {
|
||||
if runtime.GOOS == "windows" {
|
||||
fname = strings.Replace(fname, ":", "_", -1)
|
||||
fname = strings.ReplaceAll(fname, ":", "_")
|
||||
}
|
||||
return filepath.Join(dataDir, fname)
|
||||
}
|
||||
|
@ -15,10 +15,10 @@
|
||||
package disk
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestLock(t *testing.T) {
|
||||
|
@ -15,9 +15,10 @@
|
||||
package disk
|
||||
|
||||
import (
|
||||
"github.com/alexflint/go-filemutex"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/alexflint/go-filemutex"
|
||||
)
|
||||
|
||||
// FileLock wraps os.File to be used as a lock using flock
|
||||
|
@ -15,23 +15,22 @@
|
||||
package disk
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Lock Operations", func() {
|
||||
It("locks a file path", func() {
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
dir, err := os.MkdirTemp("", "")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// create a dummy file to lock
|
||||
path := filepath.Join(dir, "x")
|
||||
f, err := os.OpenFile(path, os.O_RDONLY|os.O_CREATE, 0666)
|
||||
f, err := os.OpenFile(path, os.O_RDONLY|os.O_CREATE, 0o666)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = f.Close()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
@ -47,7 +46,7 @@ var _ = Describe("Lock Operations", func() {
|
||||
})
|
||||
|
||||
It("locks a folder path", func() {
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
dir, err := os.MkdirTemp("", "")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
|
@ -22,7 +22,6 @@ type Store interface {
|
||||
Close() error
|
||||
Reserve(id string, ifname string, ip net.IP, rangeID string) (bool, error)
|
||||
LastReservedIP(rangeID string) (net.IP, error)
|
||||
Release(ip net.IP) error
|
||||
ReleaseByID(id string, ifname string) error
|
||||
GetByID(id string, ifname string) []net.IP
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ func (s *FakeStore) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *FakeStore) Reserve(id string, ifname string, ip net.IP, rangeID string) (bool, error) {
|
||||
func (s *FakeStore) Reserve(id string, _ string, ip net.IP, rangeID string) (bool, error) {
|
||||
key := ip.String()
|
||||
if _, ok := s.ipMap[key]; !ok {
|
||||
s.ipMap[key] = id
|
||||
@ -63,12 +63,7 @@ func (s *FakeStore) LastReservedIP(rangeID string) (net.IP, error) {
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
func (s *FakeStore) Release(ip net.IP) error {
|
||||
delete(s.ipMap, ip.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *FakeStore) ReleaseByID(id string, ifname string) error {
|
||||
func (s *FakeStore) ReleaseByID(id string, _ string) error {
|
||||
toDelete := []string{}
|
||||
for k, v := range s.ipMap {
|
||||
if v == id {
|
||||
@ -81,7 +76,7 @@ func (s *FakeStore) ReleaseByID(id string, ifname string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *FakeStore) GetByID(id string, ifname string) []net.IP {
|
||||
func (s *FakeStore) GetByID(id string, _ string) []net.IP {
|
||||
var ips []net.IP
|
||||
for k, v := range s.ipMap {
|
||||
if v == id {
|
||||
|
@ -15,12 +15,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
)
|
||||
|
||||
var _ = Describe("parsing resolv.conf", func() {
|
||||
@ -64,7 +64,7 @@ options four
|
||||
})
|
||||
|
||||
func parse(contents string) (*types.DNS, error) {
|
||||
f, err := ioutil.TempFile("", "host_local_resolv")
|
||||
f, err := os.CreateTemp("", "host_local_resolv")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -15,10 +15,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestHostLocal(t *testing.T) {
|
||||
|
@ -16,39 +16,52 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/020"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
types100 "github.com/containernetworking/cni/pkg/types/100"
|
||||
"github.com/containernetworking/plugins/pkg/testutils"
|
||||
|
||||
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/disk"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
const LineBreak = "\r\n"
|
||||
|
||||
var _ = Describe("host-local Operations", func() {
|
||||
It("allocates and releases addresses with ADD/DEL", func() {
|
||||
const ifname string = "eth0"
|
||||
const nspath string = "/some/where"
|
||||
var tmpDir string
|
||||
const (
|
||||
ifname string = "eth0"
|
||||
nspath string = "/some/where"
|
||||
)
|
||||
|
||||
tmpDir, err := getTmpDir()
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
tmpDir, err = os.MkdirTemp("", "host-local_test")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer os.RemoveAll(tmpDir)
|
||||
tmpDir = filepath.ToSlash(tmpDir)
|
||||
})
|
||||
|
||||
err = ioutil.WriteFile(filepath.Join(tmpDir, "resolv.conf"), []byte("nameserver 192.0.2.3"), 0644)
|
||||
AfterEach(func() {
|
||||
os.RemoveAll(tmpDir)
|
||||
})
|
||||
|
||||
for _, ver := range testutils.AllSpecVersions {
|
||||
// Redefine ver inside for scope so real value is picked up by each dynamically defined It()
|
||||
// See Gingkgo's "Patterns for dynamically generating tests" documentation.
|
||||
ver := ver
|
||||
|
||||
It(fmt.Sprintf("[%s] allocates and releases addresses with ADD/DEL", ver), func() {
|
||||
err := os.WriteFile(filepath.Join(tmpDir, "resolv.conf"), []byte("nameserver 192.0.2.3"), 0o644)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
@ -67,7 +80,7 @@ var _ = Describe("host-local Operations", func() {
|
||||
{"dst": "2001:db8:2::0/64", "gw": "2001:db8:3::1"}
|
||||
]
|
||||
}
|
||||
}`, tmpDir, tmpDir)
|
||||
}`, ver, tmpDir, tmpDir)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
@ -81,52 +94,61 @@ var _ = Describe("host-local Operations", func() {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
if testutils.SpecVersionHasIPVersion(ver) {
|
||||
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
|
||||
}
|
||||
|
||||
result, err := current.GetResult(r)
|
||||
result, err := types100.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Gomega is cranky about slices with different caps
|
||||
Expect(*result.IPs[0]).To(Equal(
|
||||
current.IPConfig{
|
||||
Version: "4",
|
||||
types100.IPConfig{
|
||||
Address: mustCIDR("10.1.2.2/24"),
|
||||
Gateway: net.ParseIP("10.1.2.1"),
|
||||
}))
|
||||
|
||||
Expect(*result.IPs[1]).To(Equal(
|
||||
current.IPConfig{
|
||||
Version: "6",
|
||||
types100.IPConfig{
|
||||
Address: mustCIDR("2001:db8:1::2/64"),
|
||||
Gateway: net.ParseIP("2001:db8:1::1"),
|
||||
},
|
||||
))
|
||||
Expect(len(result.IPs)).To(Equal(2))
|
||||
Expect(result.IPs).To(HaveLen(2))
|
||||
|
||||
Expect(result.Routes).To(Equal([]*types.Route{
|
||||
for _, expectedRoute := range []*types.Route{
|
||||
{Dst: mustCIDR("0.0.0.0/0"), GW: nil},
|
||||
{Dst: mustCIDR("::/0"), GW: nil},
|
||||
{Dst: mustCIDR("192.168.0.0/16"), GW: net.ParseIP("1.1.1.1")},
|
||||
{Dst: mustCIDR("2001:db8:2::0/64"), GW: net.ParseIP("2001:db8:3::1")},
|
||||
}))
|
||||
} {
|
||||
found := false
|
||||
for _, foundRoute := range result.Routes {
|
||||
if foundRoute.String() == expectedRoute.String() {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
Expect(found).To(BeTrue())
|
||||
}
|
||||
|
||||
ipFilePath1 := filepath.Join(tmpDir, "mynet", "10.1.2.2")
|
||||
contents, err := ioutil.ReadFile(ipFilePath1)
|
||||
contents, err := os.ReadFile(ipFilePath1)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(string(contents)).To(Equal(args.ContainerID + LineBreak + ifname))
|
||||
|
||||
ipFilePath2 := filepath.Join(tmpDir, disk.GetEscapedPath("mynet", "2001:db8:1::2"))
|
||||
contents, err = ioutil.ReadFile(ipFilePath2)
|
||||
contents, err = os.ReadFile(ipFilePath2)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(string(contents)).To(Equal(args.ContainerID + LineBreak + ifname))
|
||||
|
||||
lastFilePath1 := filepath.Join(tmpDir, "mynet", "last_reserved_ip.0")
|
||||
contents, err = ioutil.ReadFile(lastFilePath1)
|
||||
contents, err = os.ReadFile(lastFilePath1)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(string(contents)).To(Equal("10.1.2.2"))
|
||||
|
||||
lastFilePath2 := filepath.Join(tmpDir, "mynet", "last_reserved_ip.1")
|
||||
contents, err = ioutil.ReadFile(lastFilePath2)
|
||||
contents, err = os.ReadFile(lastFilePath2)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(string(contents)).To(Equal("2001:db8:1::2"))
|
||||
// Release the IP
|
||||
@ -141,20 +163,14 @@ var _ = Describe("host-local Operations", func() {
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("allocates and releases addresses on specific interface with ADD/DEL", func() {
|
||||
const ifname0 string = "eth0"
|
||||
It(fmt.Sprintf("[%s] allocates and releases addresses on specific interface with ADD/DEL", ver), func() {
|
||||
const ifname1 string = "eth1"
|
||||
const nspath string = "/some/where"
|
||||
|
||||
tmpDir, err := getTmpDir()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
err = ioutil.WriteFile(filepath.Join(tmpDir, "resolv.conf"), []byte("nameserver 192.0.2.3"), 0644)
|
||||
err := os.WriteFile(filepath.Join(tmpDir, "resolv.conf"), []byte("nameserver 192.0.2.3"), 0o644)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
conf0 := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet0",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
@ -166,10 +182,10 @@ var _ = Describe("host-local Operations", func() {
|
||||
[{ "subnet": "10.1.2.0/24" }]
|
||||
]
|
||||
}
|
||||
}`, tmpDir, tmpDir)
|
||||
}`, ver, tmpDir, tmpDir)
|
||||
|
||||
conf1 := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet1",
|
||||
"type": "ipvlan",
|
||||
"master": "foo1",
|
||||
@ -181,12 +197,12 @@ var _ = Describe("host-local Operations", func() {
|
||||
[{ "subnet": "10.2.2.0/24" }]
|
||||
]
|
||||
}
|
||||
}`, tmpDir, tmpDir)
|
||||
}`, ver, tmpDir, tmpDir)
|
||||
|
||||
args0 := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: nspath,
|
||||
IfName: ifname0,
|
||||
IfName: ifname,
|
||||
StdinData: []byte(conf0),
|
||||
}
|
||||
|
||||
@ -195,9 +211,11 @@ var _ = Describe("host-local Operations", func() {
|
||||
return cmdAdd(args0)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
if testutils.SpecVersionHasIPVersion(ver) {
|
||||
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
|
||||
}
|
||||
|
||||
_, err = current.GetResult(r0)
|
||||
_, err = types100.GetResult(r0)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
args1 := &skel.CmdArgs{
|
||||
@ -212,22 +230,24 @@ var _ = Describe("host-local Operations", func() {
|
||||
return cmdAdd(args1)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
if testutils.SpecVersionHasIPVersion(ver) {
|
||||
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
|
||||
}
|
||||
|
||||
_, err = current.GetResult(r1)
|
||||
_, err = types100.GetResult(r1)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
ipFilePath0 := filepath.Join(tmpDir, "mynet0", "10.1.2.2")
|
||||
contents, err := ioutil.ReadFile(ipFilePath0)
|
||||
contents, err := os.ReadFile(ipFilePath0)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(string(contents)).To(Equal(args0.ContainerID + LineBreak + ifname0))
|
||||
Expect(string(contents)).To(Equal(args0.ContainerID + LineBreak + ifname))
|
||||
|
||||
ipFilePath1 := filepath.Join(tmpDir, "mynet1", "10.2.2.2")
|
||||
contents, err = ioutil.ReadFile(ipFilePath1)
|
||||
contents, err = os.ReadFile(ipFilePath1)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(string(contents)).To(Equal(args1.ContainerID + LineBreak + ifname1))
|
||||
|
||||
// Release the IP on ifname0
|
||||
// Release the IP on ifname
|
||||
err = testutils.CmdDelWithArgs(args0, func() error {
|
||||
return cmdDel(args0)
|
||||
})
|
||||
@ -236,7 +256,7 @@ var _ = Describe("host-local Operations", func() {
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
||||
// reread ipFilePath1, ensure that ifname1 didn't get deleted
|
||||
contents, err = ioutil.ReadFile(ipFilePath1)
|
||||
contents, err = os.ReadFile(ipFilePath1)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(string(contents)).To(Equal(args1.ContainerID + LineBreak + ifname1))
|
||||
|
||||
@ -250,16 +270,9 @@ 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)
|
||||
|
||||
It(fmt.Sprintf("[%s] repeat allocating addresses on specific interface for same container ID with ADD", ver), func() {
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet0",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
@ -270,7 +283,7 @@ var _ = Describe("host-local Operations", func() {
|
||||
[{ "subnet": "10.1.2.0/24" }]
|
||||
]
|
||||
}
|
||||
}`, tmpDir)
|
||||
}`, ver, tmpDir)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
@ -291,11 +304,13 @@ var _ = Describe("host-local Operations", func() {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
if testutils.SpecVersionHasIPVersion(ver) {
|
||||
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
|
||||
}
|
||||
|
||||
result0, err := current.GetResult(r0)
|
||||
result0, err := types100.GetResult(r0)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(result0.IPs)).Should(Equal(1))
|
||||
Expect(result0.IPs).Should(HaveLen(1))
|
||||
Expect(result0.IPs[0].Address.String()).Should(Equal("10.1.2.2/24"))
|
||||
|
||||
// Allocate the IP with the same container ID
|
||||
@ -309,11 +324,13 @@ var _ = Describe("host-local Operations", func() {
|
||||
return cmdAdd(args1)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
if testutils.SpecVersionHasIPVersion(ver) {
|
||||
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
|
||||
}
|
||||
|
||||
result1, err := current.GetResult(r1)
|
||||
result1, err := types100.GetResult(r1)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(result1.IPs)).Should(Equal(1))
|
||||
Expect(result1.IPs).Should(HaveLen(1))
|
||||
Expect(result1.IPs[0].Address.String()).Should(Equal("10.1.2.3/24"))
|
||||
|
||||
// Allocate the IP with the same container ID again
|
||||
@ -338,19 +355,12 @@ var _ = Describe("host-local Operations", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("Verify DEL works on backwards compatible allocate", func() {
|
||||
const nspath string = "/some/where"
|
||||
const ifname string = "eth0"
|
||||
|
||||
tmpDir, err := getTmpDir()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
err = ioutil.WriteFile(filepath.Join(tmpDir, "resolv.conf"), []byte("nameserver 192.0.2.3"), 0644)
|
||||
It(fmt.Sprintf("[%s] verify DEL works on backwards compatible allocate", ver), func() {
|
||||
err := os.WriteFile(filepath.Join(tmpDir, "resolv.conf"), []byte("nameserver 192.0.2.3"), 0o644)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "foo",
|
||||
@ -362,7 +372,7 @@ var _ = Describe("host-local Operations", func() {
|
||||
[{ "subnet": "10.1.2.0/24" }]
|
||||
]
|
||||
}
|
||||
}`, tmpDir, tmpDir)
|
||||
}`, ver, tmpDir, tmpDir)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
@ -376,16 +386,18 @@ var _ = Describe("host-local Operations", func() {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
if testutils.SpecVersionHasIPVersion(ver) {
|
||||
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
|
||||
}
|
||||
|
||||
_, err = current.GetResult(r)
|
||||
_, err = types100.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
ipFilePath := filepath.Join(tmpDir, "mynet", "10.1.2.2")
|
||||
contents, err := ioutil.ReadFile(ipFilePath)
|
||||
contents, err := os.ReadFile(ipFilePath)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(string(contents)).To(Equal(args.ContainerID + LineBreak + ifname))
|
||||
err = ioutil.WriteFile(ipFilePath, []byte(strings.TrimSpace(args.ContainerID)), 0644)
|
||||
err = os.WriteFile(ipFilePath, []byte(strings.TrimSpace(args.ContainerID)), 0o644)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
@ -396,16 +408,9 @@ var _ = Describe("host-local Operations", func() {
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("doesn't error when passed an unknown ID on DEL", func() {
|
||||
const ifname string = "eth0"
|
||||
const nspath string = "/some/where"
|
||||
|
||||
tmpDir, err := getTmpDir()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
It(fmt.Sprintf("[%s] doesn't error when passed an unknown ID on DEL", ver), func() {
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.0",
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
@ -414,7 +419,7 @@ var _ = Describe("host-local Operations", func() {
|
||||
"subnet": "10.1.2.0/24",
|
||||
"dataDir": "%s"
|
||||
}
|
||||
}`, tmpDir)
|
||||
}`, ver, tmpDir)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
@ -424,91 +429,15 @@ var _ = Describe("host-local Operations", func() {
|
||||
}
|
||||
|
||||
// Release the IP
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
err := testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("allocates and releases an address with ADD/DEL and 0.1.0 config", func() {
|
||||
const ifname string = "eth0"
|
||||
const nspath string = "/some/where"
|
||||
|
||||
tmpDir, err := getTmpDir()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
err = ioutil.WriteFile(filepath.Join(tmpDir, "resolv.conf"), []byte("nameserver 192.0.2.3"), 0644)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
It(fmt.Sprintf("[%s] ignores whitespace in disk files", ver), func() {
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.1.0",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.2.0/24",
|
||||
"dataDir": "%s",
|
||||
"resolvConf": "%s/resolv.conf"
|
||||
}
|
||||
}`, tmpDir, tmpDir)
|
||||
|
||||
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), "\"ip4\":")).Should(BeNumerically(">", 0))
|
||||
|
||||
result, err := types020.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
expectedAddress, err := types.ParseCIDR("10.1.2.2/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
expectedAddress.IP = expectedAddress.IP.To16()
|
||||
Expect(result.IP4.IP).To(Equal(*expectedAddress))
|
||||
Expect(result.IP4.Gateway).To(Equal(net.ParseIP("10.1.2.1")))
|
||||
|
||||
ipFilePath := filepath.Join(tmpDir, "mynet", "10.1.2.2")
|
||||
contents, err := ioutil.ReadFile(ipFilePath)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(string(contents)).To(Equal(args.ContainerID + LineBreak + ifname))
|
||||
|
||||
lastFilePath := filepath.Join(tmpDir, "mynet", "last_reserved_ip.0")
|
||||
contents, err = ioutil.ReadFile(lastFilePath)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(string(contents)).To(Equal("10.1.2.2"))
|
||||
|
||||
Expect(result.DNS).To(Equal(types.DNS{Nameservers: []string{"192.0.2.3"}}))
|
||||
|
||||
// Release the IP
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
_, err = os.Stat(ipFilePath)
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("ignores whitespace in disk files", 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",
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
@ -517,7 +446,7 @@ var _ = Describe("host-local Operations", func() {
|
||||
"subnet": "10.1.2.0/24",
|
||||
"dataDir": "%s"
|
||||
}
|
||||
}`, tmpDir)
|
||||
}`, ver, tmpDir)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: " dummy\n ",
|
||||
@ -532,11 +461,11 @@ var _ = Describe("host-local Operations", func() {
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
result, err := current.GetResult(r)
|
||||
result, err := types100.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
ipFilePath := filepath.Join(tmpDir, "mynet", result.IPs[0].Address.IP.String())
|
||||
contents, err := ioutil.ReadFile(ipFilePath)
|
||||
contents, err := os.ReadFile(ipFilePath)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(string(contents)).To(Equal("dummy" + LineBreak + ifname))
|
||||
|
||||
@ -550,16 +479,9 @@ var _ = Describe("host-local Operations", func() {
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("does not output an error message upon initial subnet creation", func() {
|
||||
const ifname string = "eth0"
|
||||
const nspath string = "/some/where"
|
||||
|
||||
tmpDir, err := getTmpDir()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
It(fmt.Sprintf("[%s] does not output an error message upon initial subnet creation", ver), func() {
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.2.0",
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
@ -568,7 +490,7 @@ var _ = Describe("host-local Operations", func() {
|
||||
"subnet": "10.1.2.0/24",
|
||||
"dataDir": "%s"
|
||||
}
|
||||
}`, tmpDir)
|
||||
}`, ver, tmpDir)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "testing",
|
||||
@ -582,19 +504,12 @@ var _ = Describe("host-local Operations", func() {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(strings.Index(string(out), "Error retriving last reserved ip")).To(Equal(-1))
|
||||
Expect(strings.Index(string(out), "Error retrieving last reserved ip")).To(Equal(-1))
|
||||
})
|
||||
|
||||
It("allocates a custom IP when requested by config args", func() {
|
||||
const ifname string = "eth0"
|
||||
const nspath string = "/some/where"
|
||||
|
||||
tmpDir, err := getTmpDir()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
It(fmt.Sprintf("[%s] allocates a custom IP when requested by config args", ver), func() {
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
@ -610,7 +525,7 @@ var _ = Describe("host-local Operations", func() {
|
||||
"ips": ["10.1.2.88"]
|
||||
}
|
||||
}
|
||||
}`, tmpDir)
|
||||
}`, ver, tmpDir)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
@ -624,25 +539,18 @@ var _ = Describe("host-local Operations", func() {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
result, err := current.GetResult(r)
|
||||
result, err := types100.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(result.IPs).To(HaveLen(1))
|
||||
Expect(result.IPs[0].Address.IP).To(Equal(net.ParseIP("10.1.2.88")))
|
||||
})
|
||||
|
||||
It("allocates custom IPs from multiple ranges", func() {
|
||||
const ifname string = "eth0"
|
||||
const nspath string = "/some/where"
|
||||
|
||||
tmpDir, err := getTmpDir()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
err = ioutil.WriteFile(filepath.Join(tmpDir, "resolv.conf"), []byte("nameserver 192.0.2.3"), 0644)
|
||||
It(fmt.Sprintf("[%s] allocates custom IPs from multiple ranges", ver), func() {
|
||||
err := os.WriteFile(filepath.Join(tmpDir, "resolv.conf"), []byte("nameserver 192.0.2.3"), 0o644)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
@ -659,7 +567,7 @@ var _ = Describe("host-local Operations", func() {
|
||||
"ips": ["10.1.2.88", "10.1.3.77"]
|
||||
}
|
||||
}
|
||||
}`, tmpDir)
|
||||
}`, ver, tmpDir)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
@ -672,27 +580,25 @@ var _ = Describe("host-local Operations", func() {
|
||||
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
if !testutils.SpecVersionHasMultipleIPs(ver) {
|
||||
errStr := fmt.Sprintf("CNI version %s does not support more than 1 address per family", ver)
|
||||
Expect(err).To(MatchError(errStr))
|
||||
} else {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
result, err := current.GetResult(r)
|
||||
result, err := types100.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(result.IPs).To(HaveLen(2))
|
||||
Expect(result.IPs[0].Address.IP).To(Equal(net.ParseIP("10.1.2.88")))
|
||||
Expect(result.IPs[1].Address.IP).To(Equal(net.ParseIP("10.1.3.77")))
|
||||
}
|
||||
})
|
||||
|
||||
It("allocates custom IPs from multiple protocols", func() {
|
||||
const ifname string = "eth0"
|
||||
const nspath string = "/some/where"
|
||||
|
||||
tmpDir, err := getTmpDir()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
err = ioutil.WriteFile(filepath.Join(tmpDir, "resolv.conf"), []byte("nameserver 192.0.2.3"), 0644)
|
||||
It(fmt.Sprintf("[%s] allocates custom IPs from multiple protocols", ver), func() {
|
||||
err := os.WriteFile(filepath.Join(tmpDir, "resolv.conf"), []byte("nameserver 192.0.2.3"), 0o644)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
@ -709,7 +615,7 @@ var _ = Describe("host-local Operations", func() {
|
||||
"ips": ["10.1.2.88", "2001:db8:1::999"]
|
||||
}
|
||||
}
|
||||
}`, tmpDir)
|
||||
}`, ver, tmpDir)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
@ -723,23 +629,16 @@ var _ = Describe("host-local Operations", func() {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
result, err := current.GetResult(r)
|
||||
result, err := types100.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(result.IPs).To(HaveLen(2))
|
||||
Expect(result.IPs[0].Address.IP).To(Equal(net.ParseIP("10.1.2.88")))
|
||||
Expect(result.IPs[1].Address.IP).To(Equal(net.ParseIP("2001:db8:1::999")))
|
||||
})
|
||||
|
||||
It("fails if a requested custom IP is not used", func() {
|
||||
const ifname string = "eth0"
|
||||
const nspath string = "/some/where"
|
||||
|
||||
tmpDir, err := getTmpDir()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
It(fmt.Sprintf("[%s] fails if a requested custom IP is not used", ver), func() {
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
@ -756,7 +655,7 @@ var _ = Describe("host-local Operations", func() {
|
||||
"ips": ["10.1.2.88", "10.1.2.77"]
|
||||
}
|
||||
}
|
||||
}`, tmpDir)
|
||||
}`, ver, tmpDir)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
@ -766,23 +665,20 @@ var _ = Describe("host-local Operations", func() {
|
||||
}
|
||||
|
||||
// Allocate the IP
|
||||
_, _, err = testutils.CmdAddWithArgs(args, func() error {
|
||||
_, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
if !testutils.SpecVersionHasMultipleIPs(ver) {
|
||||
errStr := fmt.Sprintf("CNI version %s does not support more than 1 address per family", ver)
|
||||
Expect(err).To(MatchError(errStr))
|
||||
} else {
|
||||
Expect(err).To(HaveOccurred())
|
||||
// Need to match prefix, because ordering is not guaranteed
|
||||
Expect(err.Error()).To(HavePrefix("failed to allocate all requested IPs: 10.1.2."))
|
||||
})
|
||||
})
|
||||
|
||||
func getTmpDir() (string, error) {
|
||||
tmpDir, err := ioutil.TempDir("", "host_local_artifacts")
|
||||
if err == nil {
|
||||
tmpDir = filepath.ToSlash(tmpDir)
|
||||
}
|
||||
|
||||
return tmpDir, err
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
func mustCIDR(s string) net.IPNet {
|
||||
ip, n, err := net.ParseCIDR(s)
|
||||
|
@ -15,35 +15,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
current "github.com/containernetworking/cni/pkg/types/100"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
|
||||
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/disk"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("host-local"))
|
||||
}
|
||||
|
||||
func loadNetConf(bytes []byte) (*types.NetConf, string, error) {
|
||||
n := &types.NetConf{}
|
||||
if err := json.Unmarshal(bytes, n); err != nil {
|
||||
return nil, "", fmt.Errorf("failed to load netconf: %v", err)
|
||||
}
|
||||
return n, n.CNIVersion, nil
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
|
||||
ipamConf, _, err := allocator.LoadIPAMConfig(args.StdinData, args.Args)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -57,8 +46,8 @@ func cmdCheck(args *skel.CmdArgs) error {
|
||||
}
|
||||
defer store.Close()
|
||||
|
||||
containerIpFound := store.FindByID(args.ContainerID, args.IfName)
|
||||
if containerIpFound == false {
|
||||
containerIPFound := store.FindByID(args.ContainerID, args.IfName)
|
||||
if !containerIPFound {
|
||||
return fmt.Errorf("host-local: Failed to find address added by container %v", args.ContainerID)
|
||||
}
|
||||
|
||||
@ -71,7 +60,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
return err
|
||||
}
|
||||
|
||||
result := ¤t.Result{}
|
||||
result := ¤t.Result{CNIVersion: current.ImplementedSpecVersion}
|
||||
|
||||
if ipamConf.ResolvConf != "" {
|
||||
dns, err := parseResolvConf(ipamConf.ResolvConf)
|
||||
@ -93,7 +82,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
|
||||
// Store all requested IPs in a map, so we can easily remove ones we use
|
||||
// and error if some remain
|
||||
requestedIPs := map[string]net.IP{} //net.IP cannot be a key
|
||||
requestedIPs := map[string]net.IP{} // net.IP cannot be a key
|
||||
|
||||
for _, ip := range ipamConf.IPArgs {
|
||||
requestedIPs[ip.String()] = ip
|
||||
|
@ -1,68 +1,4 @@
|
||||
# static IP address management plugin
|
||||
|
||||
## Overview
|
||||
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
|
||||
|
||||
static IPAM is very simple IPAM plugin that assigns IPv4 and IPv6 addresses statically to container. This will be useful in debugging purpose and in case of assign same IP address in different vlan/vxlan to containers.
|
||||
|
||||
|
||||
## Example configuration
|
||||
|
||||
```
|
||||
{
|
||||
"ipam": {
|
||||
"type": "static",
|
||||
"addresses": [
|
||||
{
|
||||
"address": "10.10.0.1/24",
|
||||
"gateway": "10.10.0.254"
|
||||
},
|
||||
{
|
||||
"address": "3ffe:ffff:0:01ff::1/64",
|
||||
"gateway": "3ffe:ffff:0::1"
|
||||
}
|
||||
],
|
||||
"routes": [
|
||||
{ "dst": "0.0.0.0/0" },
|
||||
{ "dst": "192.168.0.0/16", "gw": "10.10.5.1" },
|
||||
{ "dst": "3ffe:ffff:0:01ff::1/64" }
|
||||
],
|
||||
"dns": {
|
||||
"nameservers" : ["8.8.8.8"],
|
||||
"domain": "example.com",
|
||||
"search": [ "example.com" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Network configuration reference
|
||||
|
||||
* `type` (string, required): "static"
|
||||
* `addresses` (array, optional): an array of ip address objects:
|
||||
* `address` (string, required): CIDR notation IP address.
|
||||
* `gateway` (string, optional): IP inside of "subnet" to designate as the gateway.
|
||||
* `routes` (string, optional): list of routes add to the container namespace. Each route is a dictionary with "dst" and optional "gw" fields. If "gw" is omitted, value of "gateway" will be used.
|
||||
* `dns` (string, optional): the dictionary with "nameservers", "domain" and "search".
|
||||
|
||||
## Supported arguments
|
||||
|
||||
The following [CNI_ARGS](https://github.com/containernetworking/cni/blob/master/SPEC.md#parameters) are supported:
|
||||
|
||||
* `IP`: request a specific CIDR notation IP addresses, comma separated
|
||||
* `GATEWAY`: request a specific gateway address
|
||||
|
||||
(example: CNI_ARGS="IP=10.10.0.1/24;GATEWAY=10.10.0.254")
|
||||
|
||||
The plugin also support following [capability argument](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md).
|
||||
|
||||
* `ips`: Pass IP addresses for CNI interface
|
||||
|
||||
The following [args conventions](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md#args-in-network-config) are supported:
|
||||
|
||||
* `ips` (array of strings): A list of custom IPs to attempt to allocate, with prefix (e.g. '10.10.0.1/24')
|
||||
|
||||
Notice: If some of above are used at same time, only one will work according to the priorities below
|
||||
|
||||
1. [capability argument](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md)
|
||||
1. [args conventions](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md#args-in-network-config)
|
||||
1. [CNI_ARGS](https://github.com/containernetworking/cni/blob/master/SPEC.md#parameters)
|
||||
You can find it online here: https://cni.dev/plugins/current/ipam/static/
|
||||
|
@ -22,8 +22,7 @@ import (
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
types020 "github.com/containernetworking/cni/pkg/types/020"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
current "github.com/containernetworking/cni/pkg/types/100"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
@ -144,6 +143,9 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
|
||||
if err := json.Unmarshal(bytes, &n); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if n.IPAM == nil {
|
||||
return nil, "", fmt.Errorf("IPAM config missing 'ipam' key")
|
||||
}
|
||||
|
||||
// load IP from CNI_ARGS
|
||||
if envArgs != "" {
|
||||
@ -159,7 +161,7 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
|
||||
|
||||
ip, subnet, err := net.ParseCIDR(ipstr)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("invalid CIDR %s: %s", ipstr, err)
|
||||
return nil, "", fmt.Errorf("the 'ip' field is expected to be in CIDR notation, got: '%s'", ipstr)
|
||||
}
|
||||
|
||||
addr := Address{
|
||||
@ -190,8 +192,13 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
|
||||
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})
|
||||
for _, addrStr := range n.Args.A.IPs {
|
||||
ip, addr, err := net.ParseCIDR(addrStr)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("an entry in the 'ips' field is NOT in CIDR notation, got: '%s'", addrStr)
|
||||
}
|
||||
addr.IP = ip
|
||||
n.IPAM.Addresses = append(n.IPAM.Addresses, Address{AddressStr: addrStr, Address: *addr})
|
||||
}
|
||||
}
|
||||
|
||||
@ -199,13 +206,14 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
|
||||
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})
|
||||
for _, addrStr := range n.RuntimeConfig.IPs {
|
||||
ip, addr, err := net.ParseCIDR(addrStr)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("an entry in the 'ips' field is NOT in CIDR notation, got: '%s'", addrStr)
|
||||
}
|
||||
addr.IP = ip
|
||||
n.IPAM.Addresses = append(n.IPAM.Addresses, Address{AddressStr: addrStr, Address: *addr})
|
||||
}
|
||||
|
||||
if n.IPAM == nil {
|
||||
return nil, "", fmt.Errorf("IPAM config missing 'ipam' key")
|
||||
}
|
||||
|
||||
// Validate all ranges
|
||||
@ -213,34 +221,33 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
|
||||
numV6 := 0
|
||||
|
||||
for i := range n.IPAM.Addresses {
|
||||
if n.IPAM.Addresses[i].Address.IP == nil {
|
||||
ip, addr, err := net.ParseCIDR(n.IPAM.Addresses[i].AddressStr)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("invalid CIDR %s: %s", n.IPAM.Addresses[i].AddressStr, err)
|
||||
return nil, "", fmt.Errorf(
|
||||
"the 'address' field is expected to be in CIDR notation, got: '%s'", n.IPAM.Addresses[i].AddressStr)
|
||||
}
|
||||
n.IPAM.Addresses[i].Address = *addr
|
||||
n.IPAM.Addresses[i].Address.IP = ip
|
||||
}
|
||||
|
||||
if err := canonicalizeIP(&n.IPAM.Addresses[i].Address.IP); err != nil {
|
||||
return nil, "", fmt.Errorf("invalid address %d: %s", i, err)
|
||||
}
|
||||
|
||||
if n.IPAM.Addresses[i].Address.IP.To4() != nil {
|
||||
n.IPAM.Addresses[i].Version = "4"
|
||||
numV4++
|
||||
} else {
|
||||
n.IPAM.Addresses[i].Version = "6"
|
||||
numV6++
|
||||
}
|
||||
}
|
||||
|
||||
// CNI spec 0.2.0 and below supported only one v4 and v6 address
|
||||
if numV4 > 1 || numV6 > 1 {
|
||||
for _, v := range types020.SupportedVersions {
|
||||
if n.CNIVersion == v {
|
||||
if ok, _ := version.GreaterThanOrEqualTo(n.CNIVersion, "0.3.0"); !ok {
|
||||
return nil, "", fmt.Errorf("CNI version %v does not support more than 1 address per family", n.CNIVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy net name into IPAM so not to drag Net struct around
|
||||
n.IPAM.Name = n.Name
|
||||
@ -254,20 +261,22 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
return err
|
||||
}
|
||||
|
||||
result := ¤t.Result{}
|
||||
result.DNS = ipamConf.DNS
|
||||
result.Routes = ipamConf.Routes
|
||||
result := ¤t.Result{
|
||||
CNIVersion: current.ImplementedSpecVersion,
|
||||
DNS: ipamConf.DNS,
|
||||
Routes: ipamConf.Routes,
|
||||
}
|
||||
for _, v := range ipamConf.Addresses {
|
||||
result.IPs = append(result.IPs, ¤t.IPConfig{
|
||||
Version: v.Version,
|
||||
Address: v.Address,
|
||||
Gateway: v.Gateway})
|
||||
Gateway: v.Gateway,
|
||||
})
|
||||
}
|
||||
|
||||
return types.PrintResult(result, confVersion)
|
||||
}
|
||||
|
||||
func cmdDel(args *skel.CmdArgs) error {
|
||||
func cmdDel(_ *skel.CmdArgs) error {
|
||||
// Nothing required because of no resource allocation in static plugin.
|
||||
return nil
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ package main_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
|
@ -15,25 +15,31 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
types100 "github.com/containernetworking/cni/pkg/types/100"
|
||||
"github.com/containernetworking/plugins/pkg/testutils"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("static Operations", func() {
|
||||
It("allocates and releases addresses with ADD/DEL", func() {
|
||||
for _, ver := range testutils.AllSpecVersions {
|
||||
// Redefine ver inside for scope so real value is picked up by each dynamically defined It()
|
||||
// See Gingkgo's "Patterns for dynamically generating tests" documentation.
|
||||
ver := ver
|
||||
|
||||
It(fmt.Sprintf("[%s] allocates and releases addresses with ADD/DEL", ver), func() {
|
||||
const ifname string = "eth0"
|
||||
const nspath string = "/some/where"
|
||||
|
||||
conf := `{
|
||||
"cniVersion": "0.3.1",
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
@ -57,7 +63,7 @@ var _ = Describe("static Operations", func() {
|
||||
"search": [ "example.com" ]
|
||||
}
|
||||
}
|
||||
}`
|
||||
}`, ver)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
@ -71,27 +77,27 @@ var _ = Describe("static Operations", func() {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
if testutils.SpecVersionHasIPVersion(ver) {
|
||||
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
|
||||
}
|
||||
|
||||
result, err := current.GetResult(r)
|
||||
result, err := types100.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Gomega is cranky about slices with different caps
|
||||
Expect(*result.IPs[0]).To(Equal(
|
||||
current.IPConfig{
|
||||
Version: "4",
|
||||
types100.IPConfig{
|
||||
Address: mustCIDR("10.10.0.1/24"),
|
||||
Gateway: net.ParseIP("10.10.0.254"),
|
||||
}))
|
||||
|
||||
Expect(*result.IPs[1]).To(Equal(
|
||||
current.IPConfig{
|
||||
Version: "6",
|
||||
types100.IPConfig{
|
||||
Address: mustCIDR("3ffe:ffff:0:01ff::1/64"),
|
||||
Gateway: net.ParseIP("3ffe:ffff:0::1"),
|
||||
},
|
||||
))
|
||||
Expect(len(result.IPs)).To(Equal(2))
|
||||
Expect(result.IPs).To(HaveLen(2))
|
||||
|
||||
Expect(result.Routes).To(Equal([]*types.Route{
|
||||
{Dst: mustCIDR("0.0.0.0/0")},
|
||||
@ -106,12 +112,12 @@ var _ = Describe("static Operations", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("doesn't error when passed an unknown ID on DEL", func() {
|
||||
It(fmt.Sprintf("[%s] doesn't error when passed an unknown ID on DEL", ver), func() {
|
||||
const ifname string = "eth0"
|
||||
const nspath string = "/some/where"
|
||||
|
||||
conf := `{
|
||||
"cniVersion": "0.3.0",
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
@ -133,7 +139,9 @@ var _ = Describe("static Operations", func() {
|
||||
"nameservers" : ["8.8.8.8"],
|
||||
"domain": "example.com",
|
||||
"search": [ "example.com" ]
|
||||
}}}`
|
||||
}
|
||||
}
|
||||
}`, ver)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
@ -149,12 +157,12 @@ var _ = Describe("static Operations", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("allocates and releases addresses with ADD/DEL, with ENV variables", func() {
|
||||
It(fmt.Sprintf("[%s] allocates and releases addresses with ADD/DEL, with ENV variables", ver), func() {
|
||||
const ifname string = "eth0"
|
||||
const nspath string = "/some/where"
|
||||
|
||||
conf := `{
|
||||
"cniVersion": "0.3.1",
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
@ -169,7 +177,7 @@ var _ = Describe("static Operations", func() {
|
||||
"search": [ "example.com" ]
|
||||
}
|
||||
}
|
||||
}`
|
||||
}`, ver)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
@ -184,20 +192,21 @@ var _ = Describe("static Operations", func() {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
if testutils.SpecVersionHasIPVersion(ver) {
|
||||
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
|
||||
}
|
||||
|
||||
result, err := current.GetResult(r)
|
||||
result, err := types100.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Gomega is cranky about slices with different caps
|
||||
Expect(*result.IPs[0]).To(Equal(
|
||||
current.IPConfig{
|
||||
Version: "4",
|
||||
types100.IPConfig{
|
||||
Address: mustCIDR("10.10.0.1/24"),
|
||||
Gateway: net.ParseIP("10.10.0.254"),
|
||||
}))
|
||||
|
||||
Expect(len(result.IPs)).To(Equal(1))
|
||||
Expect(result.IPs).To(HaveLen(1))
|
||||
|
||||
Expect(result.Routes).To(Equal([]*types.Route{
|
||||
{Dst: mustCIDR("0.0.0.0/0")},
|
||||
@ -211,19 +220,19 @@ var _ = Describe("static Operations", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("allocates and releases multiple addresses with ADD/DEL, with ENV variables", func() {
|
||||
It(fmt.Sprintf("[%s] allocates and releases multiple addresses with ADD/DEL, with ENV variables", ver), func() {
|
||||
const ifname string = "eth0"
|
||||
const nspath string = "/some/where"
|
||||
|
||||
conf := `{
|
||||
"cniVersion": "0.3.1",
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
"ipam": {
|
||||
"type": "static"
|
||||
}
|
||||
}`
|
||||
}`, ver)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
@ -237,27 +246,33 @@ var _ = Describe("static Operations", func() {
|
||||
r, raw, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
|
||||
if !testutils.SpecVersionHasMultipleIPs(ver) {
|
||||
errStr := fmt.Sprintf("CNI version %s does not support more than 1 address per family", ver)
|
||||
Expect(err).To(MatchError(errStr))
|
||||
return
|
||||
}
|
||||
|
||||
result, err := current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
if testutils.SpecVersionHasIPVersion(ver) {
|
||||
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
|
||||
}
|
||||
|
||||
result, err := types100.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Gomega is cranky about slices with different caps
|
||||
Expect(*result.IPs[0]).To(Equal(
|
||||
current.IPConfig{
|
||||
Version: "4",
|
||||
types100.IPConfig{
|
||||
Address: mustCIDR("10.10.0.1/24"),
|
||||
Gateway: net.ParseIP("10.10.0.254"),
|
||||
}))
|
||||
Expect(*result.IPs[1]).To(Equal(
|
||||
current.IPConfig{
|
||||
Version: "4",
|
||||
types100.IPConfig{
|
||||
Address: mustCIDR("11.11.0.1/24"),
|
||||
Gateway: nil,
|
||||
}))
|
||||
|
||||
Expect(len(result.IPs)).To(Equal(2))
|
||||
Expect(result.IPs).To(HaveLen(2))
|
||||
|
||||
// Release the IP
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
@ -266,12 +281,12 @@ var _ = Describe("static Operations", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("allocates and releases multiple addresses with ADD/DEL, from RuntimeConfig", func() {
|
||||
It(fmt.Sprintf("[%s] allocates and releases multiple addresses with ADD/DEL, from RuntimeConfig", ver), func() {
|
||||
const ifname string = "eth0"
|
||||
const nspath string = "/some/where"
|
||||
|
||||
conf := `{
|
||||
"cniVersion": "0.3.1",
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
@ -291,7 +306,7 @@ var _ = Describe("static Operations", func() {
|
||||
"RuntimeConfig": {
|
||||
"ips" : ["10.10.0.1/24", "3ffe:ffff:0:01ff::1/64"]
|
||||
}
|
||||
}`
|
||||
}`, ver)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
@ -305,24 +320,24 @@ var _ = Describe("static Operations", func() {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
if testutils.SpecVersionHasIPVersion(ver) {
|
||||
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
|
||||
}
|
||||
|
||||
result, err := current.GetResult(r)
|
||||
result, err := types100.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Gomega is cranky about slices with different caps
|
||||
Expect(*result.IPs[0]).To(Equal(
|
||||
current.IPConfig{
|
||||
Version: "4",
|
||||
types100.IPConfig{
|
||||
Address: mustCIDR("10.10.0.1/24"),
|
||||
}))
|
||||
Expect(*result.IPs[1]).To(Equal(
|
||||
current.IPConfig{
|
||||
Version: "6",
|
||||
types100.IPConfig{
|
||||
Address: mustCIDR("3ffe:ffff:0:01ff::1/64"),
|
||||
},
|
||||
))
|
||||
Expect(len(result.IPs)).To(Equal(2))
|
||||
Expect(result.IPs).To(HaveLen(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")},
|
||||
@ -335,12 +350,12 @@ var _ = Describe("static Operations", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("allocates and releases multiple addresses with ADD/DEL, from args", func() {
|
||||
It(fmt.Sprintf("[%s] allocates and releases multiple addresses with ADD/DEL, from args", ver), func() {
|
||||
const ifname string = "eth0"
|
||||
const nspath string = "/some/where"
|
||||
|
||||
conf := `{
|
||||
"cniVersion": "0.3.1",
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
@ -361,7 +376,7 @@ var _ = Describe("static Operations", func() {
|
||||
"ips" : ["10.10.0.1/24", "3ffe:ffff:0:01ff::1/64"]
|
||||
}
|
||||
}
|
||||
}`
|
||||
}`, ver)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
@ -375,24 +390,24 @@ var _ = Describe("static Operations", func() {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
if testutils.SpecVersionHasIPVersion(ver) {
|
||||
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
|
||||
}
|
||||
|
||||
result, err := current.GetResult(r)
|
||||
result, err := types100.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Gomega is cranky about slices with different caps
|
||||
Expect(*result.IPs[0]).To(Equal(
|
||||
current.IPConfig{
|
||||
Version: "4",
|
||||
types100.IPConfig{
|
||||
Address: mustCIDR("10.10.0.1/24"),
|
||||
}))
|
||||
Expect(*result.IPs[1]).To(Equal(
|
||||
current.IPConfig{
|
||||
Version: "6",
|
||||
types100.IPConfig{
|
||||
Address: mustCIDR("3ffe:ffff:0:01ff::1/64"),
|
||||
},
|
||||
))
|
||||
Expect(len(result.IPs)).To(Equal(2))
|
||||
Expect(result.IPs).To(HaveLen(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")},
|
||||
@ -405,12 +420,12 @@ var _ = Describe("static Operations", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("allocates and releases multiple addresses with ADD/DEL, from RuntimeConfig/ARGS/CNI_ARGS", func() {
|
||||
It(fmt.Sprintf("[%s] allocates and releases multiple addresses with ADD/DEL, from RuntimeConfig/ARGS/CNI_ARGS", ver), func() {
|
||||
const ifname string = "eth0"
|
||||
const nspath string = "/some/where"
|
||||
|
||||
conf := `{
|
||||
"cniVersion": "0.3.1",
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
@ -435,7 +450,7 @@ var _ = Describe("static Operations", func() {
|
||||
"ips" : ["10.10.0.2/24", "3ffe:ffff:0:01ff::2/64"]
|
||||
}
|
||||
}
|
||||
}`
|
||||
}`, ver)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
@ -450,24 +465,24 @@ var _ = Describe("static Operations", func() {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
if testutils.SpecVersionHasIPVersion(ver) {
|
||||
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
|
||||
}
|
||||
|
||||
result, err := current.GetResult(r)
|
||||
result, err := types100.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",
|
||||
types100.IPConfig{
|
||||
Address: mustCIDR("10.10.0.1/24"),
|
||||
}))
|
||||
Expect(*result.IPs[1]).To(Equal(
|
||||
current.IPConfig{
|
||||
Version: "6",
|
||||
types100.IPConfig{
|
||||
Address: mustCIDR("3ffe:ffff:0:01ff::1/64"),
|
||||
},
|
||||
))
|
||||
Expect(len(result.IPs)).To(Equal(2))
|
||||
Expect(result.IPs).To(HaveLen(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")},
|
||||
@ -480,6 +495,189 @@ var _ = Describe("static Operations", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] is returning an error on missing ipam key when args are set", ver), func() {
|
||||
const ifname string = "eth0"
|
||||
const nspath string = "/some/where"
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0"
|
||||
}`, ver)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: nspath,
|
||||
IfName: ifname,
|
||||
StdinData: []byte(conf),
|
||||
Args: "IP=10.10.0.1/24;GATEWAY=10.10.0.254",
|
||||
}
|
||||
|
||||
// Allocate the IP
|
||||
_, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).Should(MatchError("IPAM config missing 'ipam' key"))
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] is returning an error on missing ipam key when runtimeConfig is set", ver), func() {
|
||||
const ifname string = "eth0"
|
||||
const nspath string = "/some/where"
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
"runtimeConfig": {
|
||||
"ips": ["10.10.0.1/24"]
|
||||
}
|
||||
}`, ver)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: nspath,
|
||||
IfName: ifname,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
// Allocate the IP
|
||||
_, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).Should(MatchError("IPAM config missing 'ipam' key"))
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] errors when passed an invalid CIDR via ipam config", ver), func() {
|
||||
const ifname string = "eth0"
|
||||
const nspath string = "/some/where"
|
||||
const ipStr string = "10.10.0.1"
|
||||
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "bridge",
|
||||
"ipam": {
|
||||
"type": "static",
|
||||
"addresses": [ {
|
||||
"address": "%s"
|
||||
}]
|
||||
}
|
||||
}`, ver, ipStr)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: nspath,
|
||||
IfName: ifname,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
// Allocate the IP
|
||||
_, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).Should(MatchError(
|
||||
fmt.Sprintf("the 'address' field is expected to be in CIDR notation, got: '%s'", ipStr)))
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] errors when passed an invalid CIDR via Args", ver), func() {
|
||||
const ifname string = "eth0"
|
||||
const nspath string = "/some/where"
|
||||
const ipStr string = "10.10.0.1"
|
||||
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "bridge",
|
||||
"ipam": {
|
||||
"type": "static",
|
||||
"routes": [{ "dst": "0.0.0.0/0" }]
|
||||
}
|
||||
}`, ver)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: nspath,
|
||||
IfName: ifname,
|
||||
StdinData: []byte(conf),
|
||||
Args: fmt.Sprintf("IP=%s", ipStr),
|
||||
}
|
||||
|
||||
// Allocate the IP
|
||||
_, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).Should(MatchError(
|
||||
fmt.Sprintf("the 'ip' field is expected to be in CIDR notation, got: '%s'", ipStr)))
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] errors when passed an invalid CIDR via CNI_ARGS", ver), func() {
|
||||
const ifname string = "eth0"
|
||||
const nspath string = "/some/where"
|
||||
const ipStr string = "10.10.0.1"
|
||||
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "bridge",
|
||||
"ipam": {
|
||||
"type": "static",
|
||||
"routes": [{ "dst": "0.0.0.0/0" }]
|
||||
},
|
||||
"args": {
|
||||
"cni": {
|
||||
"ips" : ["%s"]
|
||||
}
|
||||
}
|
||||
}`, ver, ipStr)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: nspath,
|
||||
IfName: ifname,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
// Allocate the IP
|
||||
_, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).Should(MatchError(
|
||||
fmt.Sprintf("an entry in the 'ips' field is NOT in CIDR notation, got: '%s'", ipStr)))
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] errors when passed an invalid CIDR via RuntimeConfig", ver), func() {
|
||||
const ifname string = "eth0"
|
||||
const nspath string = "/some/where"
|
||||
const ipStr string = "10.10.0.1"
|
||||
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "bridge",
|
||||
"ipam": {
|
||||
"type": "static",
|
||||
"routes": [{ "dst": "0.0.0.0/0" }]
|
||||
},
|
||||
"RuntimeConfig": {
|
||||
"ips" : ["%s"]
|
||||
}
|
||||
}`, ver, ipStr)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: nspath,
|
||||
IfName: ifname,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
// Allocate the IP
|
||||
_, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).Should(MatchError(
|
||||
fmt.Sprintf("an entry in the 'ips' field is NOT in CIDR notation, got: '%s'", ipStr)))
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
func mustCIDR(s string) net.IPNet {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user