Compare commits
438 Commits
Author | SHA1 | Date | |
---|---|---|---|
0259301ae2 | |||
14cad164ec | |||
1868e4fe2d | |||
5c95925a71 | |||
505f5a7def | |||
dc8b8289fa | |||
196ed2d00e | |||
43139b3375 | |||
18d129f18b | |||
9397270f5e | |||
3656c69d0f | |||
3af6e6b61d | |||
427af8636f | |||
dc704d19d6 | |||
45a24d5d95 | |||
c0533466c8 | |||
ada798a3f7 | |||
eb49a034c4 | |||
670139cffa | |||
717b4337b5 | |||
ef076afac1 | |||
ccc1cfaa58 | |||
78ebd8bfb9 | |||
c666d1400d | |||
ab0b386b4e | |||
52da39d3aa | |||
597408952e | |||
6f05dc325a | |||
8ab3c63c2d | |||
c464674317 | |||
c860b78de4 | |||
9f1bf2a848 | |||
7567d28a73 | |||
ba5bdafe5d | |||
d34720b531 | |||
8fc26ce7a0 | |||
c8d165df6d | |||
1b5811957e | |||
8a3014f202 | |||
019727a392 | |||
352e0512e8 | |||
9c016b5d12 | |||
0729398940 | |||
394ab0d149 | |||
0144de0fcf | |||
47373d2612 | |||
8e8410f726 | |||
2bae9b67d3 | |||
976edfe1bc | |||
cb244060c2 | |||
d1aada912d | |||
8b2b1d20d6 | |||
14bdce598f | |||
7e131a0076 | |||
b6a0e0bc96 | |||
133a764c4d | |||
e6099fb83d | |||
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 |
2
.github/actions/retest-action/Dockerfile
vendored
2
.github/actions/retest-action/Dockerfile
vendored
@ -1,4 +1,4 @@
|
||||
FROM alpine:3.10
|
||||
FROM alpine:3.19
|
||||
|
||||
RUN apk add --no-cache curl jq
|
||||
|
||||
|
2
.github/actions/retest-action/action.yml
vendored
2
.github/actions/retest-action/action.yml
vendored
@ -8,4 +8,4 @@ runs:
|
||||
using: 'docker'
|
||||
image: 'Dockerfile'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ inputs.token }}
|
||||
GITHUB_TOKEN: ${{ inputs.token }}
|
||||
|
6
.github/actions/retest-action/entrypoint.sh
vendored
6
.github/actions/retest-action/entrypoint.sh
vendored
@ -27,10 +27,10 @@ curl --request GET \
|
||||
--header "authorization: Bearer ${GITHUB_TOKEN}" \
|
||||
--header "content-type: application/json" | jq '.workflow_runs | max_by(.run_number)' > run.json
|
||||
|
||||
RERUN_URL=$(jq -r '.rerun_url' run.json)
|
||||
RUN_URL=$(jq -r '.rerun_url' run.json)
|
||||
|
||||
curl --request POST \
|
||||
--url "${RERUN_URL}" \
|
||||
--url "${RUN_URL}/rerun-failed-jobs" \
|
||||
--header "authorization: Bearer ${GITHUB_TOKEN}" \
|
||||
--header "content-type: application/json"
|
||||
|
||||
@ -42,4 +42,4 @@ curl --request POST \
|
||||
--header "authorization: Bearer ${GITHUB_TOKEN}" \
|
||||
--header "accept: application/vnd.github.squirrel-girl-preview+json" \
|
||||
--header "content-type: application/json" \
|
||||
--data '{ "content" : "rocket" }'
|
||||
--data '{ "content" : "rocket" }'
|
||||
|
25
.github/dependabot.yml
vendored
Normal file
25
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
# 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:
|
||||
- "*"
|
||||
exclude-patterns:
|
||||
- "github.com/containernetworking/*"
|
2
.github/workflows/commands.yml
vendored
2
.github/workflows/commands.yml
vendored
@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Re-Test Action
|
||||
uses: ./.github/actions/retest-action
|
||||
|
106
.github/workflows/release.yaml
vendored
Normal file
106
.github/workflows/release.yaml
vendored
Normal file
@ -0,0 +1,106 @@
|
||||
---
|
||||
name: Release binaries
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
linux_release:
|
||||
name: Release linux binaries
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
goarch: [amd64, arm, arm64, mips64le, ppc64le, riscv64, s390x]
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.22
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
CGO_ENABLED: 0
|
||||
run: ./build_linux.sh -ldflags '-extldflags -static -X github.com/containernetworking/plugins/pkg/utils/buildversion.BuildVersion=${{ github.ref_name }}'
|
||||
|
||||
- name: COPY files
|
||||
run: cp README.md LICENSE bin/
|
||||
|
||||
- name: Create dist directory
|
||||
run: mkdir dist
|
||||
|
||||
- name: Create archive file
|
||||
working-directory: ./bin
|
||||
run: tar cfzv ../dist/cni-plugins-linux-${{ matrix.goarch }}-${{ github.ref_name }}.tgz .
|
||||
|
||||
- name: Create sha256 checksum
|
||||
working-directory: ./dist
|
||||
run: sha256sum cni-plugins-linux-${{ matrix.goarch }}-${{ github.ref_name }}.tgz | tee cni-plugins-linux-${{ matrix.goarch }}-${{ github.ref_name }}.tgz.sha256
|
||||
|
||||
- name: Create sha512 checksum
|
||||
working-directory: ./dist
|
||||
run: sha512sum cni-plugins-linux-${{ matrix.goarch }}-${{ github.ref_name }}.tgz | tee cni-plugins-linux-${{ matrix.goarch }}-${{ github.ref_name }}.tgz.sha512
|
||||
|
||||
- name: Upload binaries to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: ./dist/*
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
file_glob: true
|
||||
|
||||
windows_releases:
|
||||
name: Release windows binaries
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
goarch: [amd64]
|
||||
steps:
|
||||
- name: Install dos2unix
|
||||
run: sudo apt-get install dos2unix
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.21
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
CGO_ENABLED: 0
|
||||
run: ./build_windows.sh -ldflags '-extldflags -static -X github.com/containernetworking/plugins/pkg/utils/buildversion.BuildVersion=${{ github.ref_name }}'
|
||||
|
||||
- name: COPY files
|
||||
run: cp README.md LICENSE bin/
|
||||
|
||||
- name: Create dist directory
|
||||
run: mkdir dist
|
||||
|
||||
- name: Create archive file
|
||||
working-directory: ./bin
|
||||
run: tar cfzv ../dist/cni-plugins-windows-${{ matrix.goarch }}-${{ github.ref_name }}.tgz .
|
||||
|
||||
- name: Create sha256 checksum
|
||||
working-directory: ./dist
|
||||
run: sha256sum cni-plugins-windows-${{ matrix.goarch }}-${{ github.ref_name }}.tgz | tee cni-plugins-windows-${{ matrix.goarch }}-${{ github.ref_name }}.tgz.sha256
|
||||
|
||||
- name: Create sha512 checksum
|
||||
working-directory: ./dist
|
||||
run: sha512sum cni-plugins-windows-${{ matrix.goarch }}-${{ github.ref_name }}.tgz | tee cni-plugins-windows-${{ matrix.goarch }}-${{ github.ref_name }}.tgz.sha512
|
||||
|
||||
- name: Upload binaries to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: ./dist/*
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
file_glob: true
|
67
.github/workflows/test.yaml
vendored
67
.github/workflows/test.yaml
vendored
@ -4,20 +4,37 @@ name: test
|
||||
on: ["push", "pull_request"]
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.15"
|
||||
LINUX_ARCHES: "amd64 386 arm arm64 s390x mips64le ppc64le"
|
||||
GO_VERSION: "1.22"
|
||||
LINUX_ARCHES: "amd64 386 arm arm64 s390x mips64le ppc64le riscv64"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build all linux architectures
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: setup go
|
||||
uses: actions/setup-go@v1
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
go-version-file: go.mod
|
||||
- uses: ibiqlik/action-yamllint@v3
|
||||
with:
|
||||
format: auto
|
||||
- uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: v1.55.2
|
||||
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@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
- name: Build on all supported architectures
|
||||
run: |
|
||||
set -e
|
||||
@ -26,24 +43,32 @@ jobs:
|
||||
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@v1
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
- uses: actions/checkout@v2
|
||||
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
|
||||
env:
|
||||
GO111MODULE: off
|
||||
run: |
|
||||
go get github.com/containernetworking/cni/cnitool
|
||||
go get github.com/mattn/goveralls
|
||||
go get github.com/modocache/gover
|
||||
go install github.com/containernetworking/cni/cnitool@latest
|
||||
go install github.com/mattn/goveralls@latest
|
||||
go install github.com/modocache/gover@latest
|
||||
|
||||
- name: test
|
||||
run: PATH=$PATH:$(go env GOPATH)/bin COVERALLS=1 ./test_linux.sh
|
||||
@ -55,15 +80,15 @@ jobs:
|
||||
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@v1
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
- uses: actions/checkout@v2
|
||||
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
|
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)
|
||||
|
10
README.md
10
README.md
@ -1,6 +1,6 @@
|
||||
[](https://travis-ci.org/containernetworking/plugins)
|
||||
[](https://github.com/containernetworking/plugins/actions/workflows/test.yaml?query=branch%3Amaster)
|
||||
|
||||
# plugins
|
||||
# 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).
|
||||
|
@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/usr/bin/env sh
|
||||
set -e
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
if [ "$(uname)" == "Darwin" ]; then
|
||||
if [ "$(uname)" = "Darwin" ]; then
|
||||
export GOOS="${GOOS:-linux}"
|
||||
fi
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/usr/bin/env sh
|
||||
set -e
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
|
61
go.mod
61
go.mod
@ -1,29 +1,48 @@
|
||||
module github.com/containernetworking/plugins
|
||||
|
||||
go 1.14
|
||||
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.8.1
|
||||
github.com/coreos/go-iptables v0.5.0
|
||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7
|
||||
github.com/Microsoft/hcsshim v0.12.3
|
||||
github.com/alexflint/go-filemutex v1.3.0
|
||||
github.com/buger/jsonparser v1.1.1
|
||||
github.com/containernetworking/cni v1.1.2
|
||||
github.com/coreos/go-iptables v0.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/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56
|
||||
github.com/mattn/go-shellwords v1.0.3
|
||||
github.com/onsi/ginkgo v1.12.1
|
||||
github.com/onsi/gomega v1.10.3
|
||||
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8
|
||||
github.com/sirupsen/logrus v1.0.6 // indirect
|
||||
github.com/stretchr/testify v1.3.0 // indirect
|
||||
github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852
|
||||
golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect
|
||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 // indirect
|
||||
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.17.3
|
||||
github.com/onsi/gomega v1.33.1
|
||||
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.20.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/containerd/cgroups/v3 v3.0.2 // indirect
|
||||
github.com/containerd/errdefs v0.1.0 // indirect
|
||||
github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // 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-20240424215950-a892ee059fd6 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
golang.org/x/net v0.24.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/tools v0.20.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect
|
||||
google.golang.org/grpc v1.62.0 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
236
go.sum
236
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.8.1 h1:7zpDnQ3T3s4ucOuJ/ZCLrYBxzkg0AELFfII3Epo9TmI=
|
||||
github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
|
||||
github.com/coreos/go-iptables v0.5.0 h1:mw6SAibtHKZcNzAsOxjoHIG0gy5YFHhypWSSNc6EjbQ=
|
||||
github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
|
||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7 h1:u9SHYsPQNyt5tgDm3YN7+9dYrpK96E5wFilTFWIDZOM=
|
||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
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.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/Microsoft/hcsshim v0.12.3 h1:LS9NXqXhMoqNCplK1ApmVSfB4UnVLRDWRapB6EIlxE0=
|
||||
github.com/Microsoft/hcsshim v0.12.3/go.mod h1:Iyl1WVpZzr+UkzjekHZbV8o5Z9ZkxNGx6CtY2Qg/JVQ=
|
||||
github.com/alexflint/go-filemutex v1.3.0 h1:LgE+nTUWnQCyRKbpoceKZsPQbs84LivvgwUymZXdOcM=
|
||||
github.com/alexflint/go-filemutex v1.3.0/go.mod h1:U0+VA/i30mGBlLCrFPGtTe9y6wGQfNAWPBTekHQ+c8A=
|
||||
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/v3 v3.0.2 h1:f5WFqIVSgo5IZmtTT3qVBo6TzI1ON6sycSBKkymb9L0=
|
||||
github.com/containerd/cgroups/v3 v3.0.2/go.mod h1:JUgITrzdFqp42uI2ryGA+ge0ap/nxzYgkGmIcetmErE=
|
||||
github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM=
|
||||
github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0=
|
||||
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,90 +32,212 @@ 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/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
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/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/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/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/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
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/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.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
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 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
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-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg=
|
||||
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
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/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56 h1:742eGXur0715JMq73aD95/FU0XpVKXqNuTnEfXsLOYQ=
|
||||
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=
|
||||
github.com/mattn/go-shellwords v1.0.3 h1:K/VxK7SZ+cvuPgFSLKi5QPI9Vr/ipOf4C1gN+ntueUk=
|
||||
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
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 h1:mFwc4LvZ0xpSvDZ3E+k8Yte0hLOMxXUlP+yXtJqkYfQ=
|
||||
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.17.3 h1:oJcvKpIb7/8uLpDDtnQuf18xVnwKp8DTD7DQ6gTd/MU=
|
||||
github.com/onsi/ginkgo/v2 v2.17.3/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA=
|
||||
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
|
||||
github.com/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.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
|
||||
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
|
||||
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.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/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 v1.1.1-0.20201029203352-d40f9887b852 h1:cPXZWzzG0NllBLdjWoD1nDfaqu98YMv+OneaKc8sPOA=
|
||||
github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=
|
||||
github.com/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.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/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
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.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-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
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.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
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-20201006153459-a7d1128ccaa0 h1:wBouT66WTYFXdxfVdz9sVWARVd/2vfGcmI45D2gj45M=
|
||||
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
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-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/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-20201117170446-d9b008d0a637 h1:O5hKNaGxIT4A8OTMnuh6UpmBdI3SAPxlZ3g0olDrJVM=
|
||||
golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.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 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/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-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
|
||||
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
|
||||
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/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s=
|
||||
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.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk=
|
||||
google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo=
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
||||
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.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 h1:OAj3g0cR6Dx/R07QgQe8wkA9RNjB2u4i700xBkIT4e0=
|
||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.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 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
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=
|
||||
|
@ -4,7 +4,7 @@
|
||||
// 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
|
||||
// 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,
|
||||
@ -14,21 +14,21 @@
|
||||
package integration_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"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"
|
||||
@ -61,6 +61,13 @@ var _ = Describe("Basic PTP using cnitool", func() {
|
||||
netConfPath, err := filepath.Abs("./testdata")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Flush ipam stores to avoid conflicts
|
||||
err = os.RemoveAll("/tmp/chained-ptp-bandwidth-test")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = os.RemoveAll("/tmp/basic-ptp-test")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
env = TestEnv([]string{
|
||||
"CNI_PATH=" + cniPath,
|
||||
"NETCONFPATH=" + netConfPath,
|
||||
@ -83,6 +90,7 @@ var _ = Describe("Basic PTP using cnitool", func() {
|
||||
env.runInNS(hostNS, cnitoolBin, "add", netName, contNS.LongName())
|
||||
|
||||
addrOutput := env.runInNS(contNS, "ip", "addr")
|
||||
|
||||
Expect(addrOutput).To(ContainSubstring(expectedIPPrefix))
|
||||
|
||||
env.runInNS(hostNS, cnitoolBin, "del", netName, contNS.LongName())
|
||||
@ -146,10 +154,14 @@ var _ = Describe("Basic PTP using cnitool", func() {
|
||||
|
||||
chainedBridgeBandwidthEnv.runInNS(hostNS, cnitoolBin, "del", "network-chain-test", contNS1.LongName())
|
||||
basicBridgeEnv.runInNS(hostNS, cnitoolBin, "del", "network-chain-test", contNS2.LongName())
|
||||
|
||||
contNS1.Del()
|
||||
contNS2.Del()
|
||||
hostNS.Del()
|
||||
})
|
||||
|
||||
Measure("limits traffic only on the restricted bandwith veth device", func(b Benchmarker) {
|
||||
ipRegexp := regexp.MustCompile("10\\.1[12]\\.2\\.\\d{1,3}")
|
||||
It("limits traffic only on the restricted bandwidth veth device", func() {
|
||||
ipRegexp := regexp.MustCompile(`10\.1[12]\.2\.\d{1,3}`)
|
||||
|
||||
By(fmt.Sprintf("adding %s to %s\n\n", "chained-bridge-bandwidth", contNS1.ShortName()))
|
||||
chainedBridgeBandwidthEnv.runInNS(hostNS, cnitoolBin, "add", "network-chain-test", contNS1.LongName())
|
||||
@ -162,31 +174,30 @@ 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.
|
||||
packetInBytes := 3000
|
||||
|
||||
By(fmt.Sprintf("sending tcp traffic to the chained, bridged, traffic shaped container on ip address '%s:%d'\n\n", chainedBridgeIP, chainedBridgeBandwidthPort))
|
||||
runtimeWithLimit := b.Time("with chained bridge and bandwidth plugins", func() {
|
||||
makeTcpClientInNS(hostNS.ShortName(), chainedBridgeIP, chainedBridgeBandwidthPort, packetInBytes)
|
||||
})
|
||||
start := time.Now()
|
||||
makeTCPClientInNS(hostNS.ShortName(), chainedBridgeIP, chainedBridgeBandwidthPort, packetInBytes)
|
||||
runtimeWithLimit := time.Since(start)
|
||||
|
||||
log.Printf("Runtime with qos limit %.2f seconds", runtimeWithLimit.Seconds())
|
||||
|
||||
By(fmt.Sprintf("sending tcp traffic to the basic bridged container on ip address '%s:%d'\n\n", basicBridgeIP, basicBridgePort))
|
||||
runtimeWithoutLimit := b.Time("with basic bridged plugin", func() {
|
||||
makeTcpClientInNS(hostNS.ShortName(), basicBridgeIP, basicBridgePort, packetInBytes)
|
||||
})
|
||||
start = time.Now()
|
||||
makeTCPClientInNS(hostNS.ShortName(), basicBridgeIP, basicBridgePort, packetInBytes)
|
||||
runtimeWithoutLimit := time.Since(start)
|
||||
log.Printf("Runtime without qos limit %.2f seconds", runtimeWithLimit.Seconds())
|
||||
|
||||
Expect(runtimeWithLimit).To(BeNumerically(">", runtimeWithoutLimit+1000*time.Millisecond))
|
||||
}, 1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -224,7 +235,7 @@ func (n Namespace) Del() {
|
||||
(TestEnv{}).run("ip", "netns", "del", string(n))
|
||||
}
|
||||
|
||||
func makeTcpClientInNS(netns string, address string, port int, numBytes int) {
|
||||
func makeTCPClientInNS(netns string, address string, port int, numBytes int) {
|
||||
payload := bytes.Repeat([]byte{'a'}, numBytes)
|
||||
message := string(payload)
|
||||
|
||||
@ -243,7 +254,7 @@ func makeTcpClientInNS(netns string, address string, port int, numBytes int) {
|
||||
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())
|
||||
|
||||
@ -260,7 +271,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) {
|
||||
|
@ -4,7 +4,7 @@
|
||||
// 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
|
||||
// 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,
|
||||
@ -17,7 +17,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/onsi/gomega/gexec"
|
||||
)
|
||||
|
3
integration/testdata/basic-ptp.json
vendored
3
integration/testdata/basic-ptp.json
vendored
@ -6,6 +6,7 @@
|
||||
"mtu": 512,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.2.0/24"
|
||||
"subnet": "10.1.2.0/24",
|
||||
"dataDir": "/tmp/basic-ptp-test"
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,8 @@
|
||||
"mtu": 512,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.9.2.0/24"
|
||||
"subnet": "10.9.2.0/24",
|
||||
"dataDir": "/tmp/chained-ptp-bandwidth-test"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -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,222 +63,136 @@ func GetIpString(ip *net.IP) string {
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
// 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"
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
hnsEndpoint = nil
|
||||
}
|
||||
}
|
||||
|
||||
if n.LoopbackDSR {
|
||||
n.ApplyLoopbackDSR(&epInfo.IpAddress)
|
||||
}
|
||||
if hnsEndpoint == nil {
|
||||
hnsEndpoint = &hcsshim.HNSEndpoint{
|
||||
Name: epInfo.EndpointName,
|
||||
VirtualNetwork: epInfo.NetworkId,
|
||||
DNSServerList: strings.Join(epInfo.DNS.Nameservers, ","),
|
||||
DNSSuffix: strings.Join(epInfo.DNS.Search, ","),
|
||||
GatewayAddress: GetIpString(&epInfo.Gateway),
|
||||
IPAddress: epInfo.IpAddress,
|
||||
Policies: n.MarshalPolicies(),
|
||||
}
|
||||
}
|
||||
return hnsEndpoint, nil
|
||||
return destinationPrefix
|
||||
}
|
||||
|
||||
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
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// 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 HNSEndpoint %s", epInfo.EndpointName)
|
||||
}
|
||||
|
||||
if hnsEndpoint != nil {
|
||||
if strings.EqualFold(hnsEndpoint.VirtualNetwork, epInfo.NetworkId) {
|
||||
return nil, fmt.Errorf("HNSEndpoint %s is already existed", epInfo.EndpointName)
|
||||
}
|
||||
// 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.ApplyLoopbackDSRPolicy(&epInfo.IpAddress)
|
||||
}
|
||||
hnsEndpoint = &hcsshim.HNSEndpoint{
|
||||
Name: epInfo.EndpointName,
|
||||
VirtualNetwork: epInfo.NetworkId,
|
||||
DNSServerList: strings.Join(epInfo.DNS.Nameservers, ","),
|
||||
DNSSuffix: strings.Join(epInfo.DNS.Search, ","),
|
||||
GatewayAddress: GetIpString(&epInfo.Gateway),
|
||||
IPAddress: epInfo.IpAddress,
|
||||
Policies: n.GetHNSEndpointPolicies(),
|
||||
}
|
||||
return hnsEndpoint, nil
|
||||
}
|
||||
|
||||
// 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 hcsshim.IsNotExist(err) {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
if err != nil {
|
||||
if hcsshim.IsNotExist(err) {
|
||||
return 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)
|
||||
if err != nil {
|
||||
// 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 {
|
||||
if hnsEndpoint != nil {
|
||||
if _, err = hnsEndpoint.Delete(); err != nil {
|
||||
return nil, errors.Annotate(err, "failed to delete the stale HNSEndpoint")
|
||||
// verify the existing endpoint is corrupted or not
|
||||
if hnsEndpoint != nil {
|
||||
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{
|
||||
Search: strings.Split(hnsEndpoint.DNSSuffix, ","),
|
||||
Nameservers: strings.Split(hnsEndpoint.DNSServerList, ","),
|
||||
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 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 {
|
||||
err = hcnEndpoint.Delete()
|
||||
if err != nil {
|
||||
return fmt.Errorf("[win-cni] Failed to delete endpoint %v, err:%v", epName, err)
|
||||
if err != nil {
|
||||
if hcn.IsNotFoundError(err) {
|
||||
return 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 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{
|
||||
Search: hcnEndpoint.Dns.Search,
|
||||
Nameservers: hcnEndpoint.Dns.ServerList,
|
||||
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
|
||||
|
@ -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
|
||||
}
|
||||
result = append(result, p.Value)
|
||||
}
|
||||
return destinationPrefix
|
||||
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)
|
||||
// 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
|
||||
}
|
||||
|
||||
result := make([]json.RawMessage, 0, len(n.Policies))
|
||||
for _, p := range n.Policies {
|
||||
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())
|
||||
|
||||
// find OutBoundNAT policy
|
||||
for i := range n.Policies {
|
||||
p := &n.Policies[i]
|
||||
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") {
|
||||
// filter OutBoundNAT policy
|
||||
typeValue, _ := jsonparser.GetUnsafeString(p.Value, "Type")
|
||||
if typeValue != "OutBoundNAT" {
|
||||
continue
|
||||
}
|
||||
|
||||
typeValue, err := jsonparser.GetUnsafeString(p.Value, "Type")
|
||||
if err != nil || len(typeValue) == 0 {
|
||||
// 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
|
||||
}
|
||||
|
||||
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
|
||||
// return if found the given address
|
||||
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) {
|
||||
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, nwToNatBytes) != 0 {
|
||||
buf.WriteByte('"')
|
||||
buf.Write(value)
|
||||
buf.WriteByte('"')
|
||||
buf.WriteByte(',')
|
||||
if bytes.Compare(value, ipBytes) == 0 {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
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 + `"]}`),
|
||||
if found {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
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": "OutBoundNAT", "ExceptionList": ["` + nwToNat + `"]}`),
|
||||
Value: toPolicyValue(ip.String()),
|
||||
})
|
||||
}
|
||||
|
||||
// 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)
|
||||
// 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
|
||||
}
|
||||
|
||||
// if its already present, leave untouched
|
||||
for i, p := range n.Policies {
|
||||
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
|
||||
}
|
||||
|
||||
paValue, dt, _, _ := jsonparser.Get(p.Value, "PA")
|
||||
// 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
|
||||
} 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 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
|
||||
}
|
||||
}
|
||||
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(exceptionCIDR),
|
||||
})
|
||||
}
|
||||
|
||||
// ApplyPortMappingPolicy is used to configure HostPort<>ContainerPort mapping in HNS
|
||||
func (n *NetConf) ApplyPortMappingPolicy(portMappings []PortMapEntry) {
|
||||
if portMappings == nil {
|
||||
// ApplyDefaultPAPolicy applies an endpoint PA policy in HNS/HCN.
|
||||
func (n *NetConf) ApplyDefaultPAPolicy(address string) {
|
||||
if address == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if n.Policies == nil {
|
||||
n.Policies = make([]policy, 0)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
for _, portMapping := range portMappings {
|
||||
n.Policies = append(n.Policies, policy{
|
||||
// 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 len(portMappings) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
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 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"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("MarshalPolicies", func() {
|
||||
Context("when not set by user", func() {
|
||||
It("sets it by adding a policy", func() {
|
||||
|
||||
n := NetConf{
|
||||
Policies: []policy{
|
||||
{
|
||||
Name: "EndpointPolicy",
|
||||
Value: []byte(`{"someKey": "someValue"}`),
|
||||
},
|
||||
{
|
||||
Name: "someOtherType",
|
||||
Value: []byte(`{"someOtherKey": "someOtherValue"}`),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result := n.MarshalPolicies()
|
||||
Expect(len(result)).To(Equal(1))
|
||||
|
||||
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"))
|
||||
Context("via v2 api", func() {
|
||||
var n NetConf
|
||||
BeforeEach(func() {
|
||||
n = NetConf{ApiVersion: 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")
|
||||
It("nothing to do if input is empty", func() {
|
||||
n.ApplyPortMappingPolicy(nil)
|
||||
Expect(n.Policies).Should(BeNil())
|
||||
|
||||
// then attempt to update it
|
||||
n.ApplyOutboundNatPolicy("10.244.0.0/16")
|
||||
n.ApplyPortMappingPolicy([]PortMapEntry{})
|
||||
Expect(n.Policies).Should(BeNil())
|
||||
})
|
||||
|
||||
// it should be unchanged!
|
||||
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"))
|
||||
|
||||
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["Type"]).Should(Equal("PortMapping"))
|
||||
Expect(value).Should(HaveKey("Settings"))
|
||||
|
||||
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"))
|
||||
// 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(`{"Type": "OutBoundNAT", "ExceptionList": [ "192.168.1.2" ]}`),
|
||||
},
|
||||
{
|
||||
Name: "someOtherType",
|
||||
Value: []byte(`{"someOtherKey": "someOtherValue"}`),
|
||||
},
|
||||
}
|
||||
|
||||
// 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("Type"))
|
||||
Expect(policy["Type"]).To(Equal("OutBoundNAT"))
|
||||
Expect(policy).Should(HaveKey("ExceptionList"))
|
||||
Expect(policy["ExceptionList"]).To(ContainElement("192.168.1.2"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("via v2 api", func() {
|
||||
var n NetConf
|
||||
BeforeEach(func() {
|
||||
n = NetConf{ApiVersion: 2}
|
||||
})
|
||||
|
||||
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"}`),
|
||||
},
|
||||
}
|
||||
|
||||
// only one valid item
|
||||
result := n.GetHostComputeEndpointPolicies()
|
||||
Expect(len(result)).To(Equal(1))
|
||||
|
||||
// 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,
|
||||
Name: name,
|
||||
MTU: mtu,
|
||||
},
|
||||
PeerName: peer,
|
||||
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 (%q) peer provided (%q) already exists", name, peerName)
|
||||
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
|
||||
}
|
||||
@ -158,9 +149,9 @@ var _ = Describe("Link", func() {
|
||||
It("returns useful error", func() {
|
||||
_ = containerNetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err := ip.SetupVeth(containerVethName, mtu, hostNetNS)
|
||||
Expect(err.Error()).To(Equal(fmt.Sprintf("container veth name provided (%s) already exists", containerVethName)))
|
||||
testHostVethName := "test" + hostVethName
|
||||
_, _, err := ip.SetupVethWithName(containerVethName, testHostVethName, mtu, "", hostNetNS)
|
||||
Expect(err.Error()).To(Equal(fmt.Sprintf("container veth name (%q) peer provided (%q) already exists", containerVethName, testHostVethName)))
|
||||
|
||||
return nil
|
||||
})
|
||||
@ -183,15 +174,14 @@ 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.SetupVethWithName(containerVethName, hostVethName, mtu, "", hostNetNS)
|
||||
Expect(err.Error()).To(Equal(fmt.Sprintf("container veth name (%q) peer provided (%q) already exists", containerVethName, hostVethName)))
|
||||
return nil
|
||||
})
|
||||
})
|
||||
@ -199,7 +189,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 +197,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 +223,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 +282,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
|
||||
}
|
@ -106,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,16 +17,16 @@ package ns_test
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/testutils"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "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"
|
||||
)
|
||||
|
||||
func getInodeCurNetNS() (uint64, error) {
|
||||
@ -182,7 +182,7 @@ var _ = Describe("Linux namespace operations", func() {
|
||||
testNsInode, err := getInodeNS(targetNetNS)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(testNsInode).NotTo(Equal(0))
|
||||
Expect(testNsInode).NotTo(Equal(uint64(0)))
|
||||
Expect(testNsInode).NotTo(Equal(origNSInode))
|
||||
})
|
||||
|
||||
@ -208,7 +208,7 @@ var _ = Describe("Linux namespace operations", func() {
|
||||
})
|
||||
|
||||
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()
|
||||
|
||||
@ -262,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)
|
||||
}
|
||||
|
@ -2,12 +2,12 @@ package main_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/onsi/gomega/gbytes"
|
||||
"github.com/onsi/gomega/gexec"
|
||||
@ -74,7 +74,7 @@ var _ = Describe("Echosvr", func() {
|
||||
defer conn.Close()
|
||||
|
||||
fmt.Fprintf(conn, "hello\n")
|
||||
Expect(ioutil.ReadAll(conn)).To(Equal([]byte("hello")))
|
||||
Expect(io.ReadAll(conn)).To(Equal([]byte("hello")))
|
||||
})
|
||||
})
|
||||
|
||||
@ -86,7 +86,7 @@ var _ = Describe("Echosvr", func() {
|
||||
It("connects successfully using echo client", func() {
|
||||
Eventually(session.Out).Should(gbytes.Say("\n"))
|
||||
serverAddress := strings.TrimSpace(string(session.Out.Contents()))
|
||||
fmt.Println("Server address", string(serverAddress))
|
||||
fmt.Println("Server address", serverAddress)
|
||||
|
||||
cmd := exec.Command(clientBinaryPath, "-target", serverAddress, "-message", "hello")
|
||||
clientSession, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
|
||||
|
@ -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,9 +1,10 @@
|
||||
// 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.
|
||||
//
|
||||
// 127.0.0.1:xxxxx
|
||||
// A test should wait for this line, parse it
|
||||
// and may then attempt to connect.
|
||||
package main
|
||||
|
||||
import (
|
||||
@ -43,11 +44,13 @@ func main() {
|
||||
// Start UDP server
|
||||
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%s", port))
|
||||
if err != nil {
|
||||
log.Fatalf("Error from net.ResolveUDPAddr(): %s", err)
|
||||
log.Printf("Error from net.ResolveUDPAddr(): %s", err)
|
||||
return
|
||||
}
|
||||
sock, err := net.ListenUDP("udp", addr)
|
||||
if err != nil {
|
||||
log.Fatalf("Error from ListenUDP(): %s", err)
|
||||
log.Printf("Error from ListenUDP(): %s", err)
|
||||
return
|
||||
}
|
||||
defer sock.Close()
|
||||
|
||||
@ -55,10 +58,11 @@ func main() {
|
||||
for {
|
||||
n, addr, err := sock.ReadFrom(buffer)
|
||||
if err != nil {
|
||||
log.Fatalf("Error from ReadFrom(): %s", err)
|
||||
log.Printf("Error from ReadFrom(): %s", err)
|
||||
return
|
||||
}
|
||||
sock.SetWriteDeadline(time.Now().Add(1 * time.Minute))
|
||||
n, err = sock.WriteTo(buffer[0:n], addr)
|
||||
_, err = sock.WriteTo(buffer[0:n], addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -62,8 +62,8 @@ func DeleteConntrackEntriesForDstIP(dstIP string, protocol uint8) error {
|
||||
// by the given destination port, protocol and IP family
|
||||
func DeleteConntrackEntriesForDstPort(port uint16, protocol uint8, family netlink.InetFamily) error {
|
||||
filter := &netlink.ConntrackFilter{}
|
||||
filter.AddPort(netlink.ConntrackOrigDstPort, port)
|
||||
filter.AddProtocol(protocol)
|
||||
filter.AddPort(netlink.ConntrackOrigDstPort, port)
|
||||
|
||||
_, err := netlink.ConntrackDeleteFilter(netlink.ConntrackTable, family, filter)
|
||||
if err != 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,4 +1,4 @@
|
||||
|
||||
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
|
||||
|
||||
You can find it online here: https://cni.dev/plugins/ipam/dhcp/
|
||||
You can find it online here: https://cni.dev/plugins/current/ipam/dhcp/
|
||||
|
@ -23,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()
|
||||
|
||||
@ -35,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)
|
||||
|
||||
@ -48,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)
|
||||
|
||||
@ -61,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 {
|
||||
@ -91,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)
|
||||
|
||||
@ -120,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,26 +15,26 @@
|
||||
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/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/coreos/go-systemd/activation"
|
||||
)
|
||||
"github.com/coreos/go-systemd/v22/activation"
|
||||
|
||||
const listenFdsStart = 3
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
current "github.com/containernetworking/cni/pkg/types/100"
|
||||
)
|
||||
|
||||
var errNoMoreTries = errors.New("no more tries")
|
||||
|
||||
@ -43,35 +43,59 @@ type DHCP struct {
|
||||
leases map[string]*DHCPLease
|
||||
hostNetnsPrefix string
|
||||
clientTimeout time.Duration
|
||||
clientResendMax time.Duration
|
||||
broadcast bool
|
||||
}
|
||||
|
||||
func newDHCP(clientTimeout time.Duration) *DHCP {
|
||||
func newDHCP(clientTimeout, clientResendMax time.Duration) *DHCP {
|
||||
return &DHCP{
|
||||
leases: make(map[string]*DHCPLease),
|
||||
clientTimeout: clientTimeout,
|
||||
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, d.clientTimeout, d.broadcast)
|
||||
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()
|
||||
@ -81,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(),
|
||||
}}
|
||||
@ -92,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)
|
||||
}
|
||||
@ -127,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()
|
||||
@ -144,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)
|
||||
@ -162,7 +185,7 @@ func getListener(socketPath string) (net.Listener, error) {
|
||||
|
||||
func runDaemon(
|
||||
pidfilePath, hostPrefix, socketPath string,
|
||||
dhcpClientTimeout time.Duration, broadcast bool,
|
||||
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
|
||||
@ -173,7 +196,7 @@ func runDaemon(
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -183,11 +206,27 @@ func runDaemon(
|
||||
return fmt.Errorf("Error getting listener: %v", err)
|
||||
}
|
||||
|
||||
dhcp := newDHCP(dhcpClientTimeout)
|
||||
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,118 +230,135 @@ 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() {
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"ipam": {
|
||||
"type": "dhcp",
|
||||
"daemonSocketPath": "%s"
|
||||
}
|
||||
}`, socketPath)
|
||||
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
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: contVethName,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
It(fmt.Sprintf("[%s] configures and deconfigures a link with ADD/DEL", ver), func() {
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"ipam": {
|
||||
"type": "dhcp",
|
||||
"daemonSocketPath": "%s"
|
||||
}
|
||||
}`, ver, socketPath)
|
||||
|
||||
var addResult *current.Result
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: contVethName,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
addResult, err = current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(addResult.IPs)).To(Equal(1))
|
||||
Expect(addResult.IPs[0].Address.String()).To(Equal("192.168.1.5/24"))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
return testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("correctly handles multiple DELs for the same container", func() {
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"ipam": {
|
||||
"type": "dhcp",
|
||||
"daemonSocketPath": "%s"
|
||||
}
|
||||
}`, socketPath)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: contVethName,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
var addResult *current.Result
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
addResult, err = current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(addResult.IPs)).To(Equal(1))
|
||||
Expect(addResult.IPs[0].Address.String()).To(Equal("192.168.1.5/24"))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(3)
|
||||
started := sync.WaitGroup{}
|
||||
started.Add(3)
|
||||
for i := 0; i < 3; i++ {
|
||||
go func() {
|
||||
var addResult *types100.Result
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
// Wait until all goroutines are running
|
||||
started.Done()
|
||||
started.Wait()
|
||||
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
return testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
return testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
addResult, err = types100.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(addResult.IPs).To(HaveLen(1))
|
||||
Expect(addResult.IPs[0].Address.String()).To(Equal("192.168.1.5/24"))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
return testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] correctly handles multiple DELs for the same container", ver), func() {
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"ipam": {
|
||||
"type": "dhcp",
|
||||
"daemonSocketPath": "%s"
|
||||
}
|
||||
}`, ver, socketPath)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: contVethName,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
var addResult *types100.Result
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
addResult, err = types100.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(addResult.IPs).To(HaveLen(1))
|
||||
Expect(addResult.IPs[0].Address.String()).To(Equal("192.168.1.5/24"))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(3)
|
||||
started := sync.WaitGroup{}
|
||||
started.Add(3)
|
||||
for i := 0; i < 3; i++ {
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
|
||||
// Wait until all goroutines are running
|
||||
started.Done()
|
||||
started.Wait()
|
||||
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
return testutils.CmdDelWithArgs(args, func() error {
|
||||
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())
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
return testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
})
|
||||
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,92 +554,101 @@ 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() {
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"type": "bridge",
|
||||
"bridge": "%s",
|
||||
"ipam": {
|
||||
"type": "dhcp",
|
||||
"daemonSocketPath": "%s"
|
||||
}
|
||||
}`, hostBridgeName, socketPath)
|
||||
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
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: contVethName0,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
It(fmt.Sprintf("[%s] configures multiple links with multiple ADD with second lease unavailable", ver), func() {
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "bridge",
|
||||
"bridge": "%s",
|
||||
"ipam": {
|
||||
"type": "dhcp",
|
||||
"daemonSocketPath": "%s"
|
||||
}
|
||||
}`, ver, hostBridgeName, socketPath)
|
||||
|
||||
var addResult *current.Result
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: contVethName0,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
var addResult *types100.Result
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
addResult, err = types100.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(addResult.IPs).To(HaveLen(1))
|
||||
Expect(addResult.IPs[0].Address.String()).To(Equal("192.168.1.5/24"))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
addResult, err = current.GetResult(r)
|
||||
args = &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: contVethName1,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).To(HaveOccurred())
|
||||
println(err.Error())
|
||||
Expect(err.Error()).To(Equal("error calling DHCP.Allocate: no more tries"))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(addResult.IPs)).To(Equal(1))
|
||||
Expect(addResult.IPs[0].Address.String()).To(Equal("192.168.1.5/24"))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
args = &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: contVethName1,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
args = &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: contVethName1,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
return testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
})
|
||||
Expect(err).To(HaveOccurred())
|
||||
println(err.Error())
|
||||
Expect(err.Error()).To(Equal("error calling DHCP.Allocate: no more tries"))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
args = &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: contVethName1,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
args = &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: contVethName0,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
return testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
return testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
args = &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: contVethName0,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
return testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
})
|
||||
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 = 62 * 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
|
||||
@ -57,10 +67,79 @@ type DHCPLease struct {
|
||||
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
|
||||
@ -68,14 +147,19 @@ type DHCPLease struct {
|
||||
// calling DHCPLease.Stop()
|
||||
func AcquireLease(
|
||||
clientID, netns, ifName string,
|
||||
timeout time.Duration, broadcast bool,
|
||||
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{}),
|
||||
timeout: timeout,
|
||||
broadcast: broadcast,
|
||||
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)
|
||||
@ -121,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, l.timeout, l.broadcast)
|
||||
c, err := newDHCPClient(l.link, l.timeout, l.broadcast)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -135,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:
|
||||
@ -195,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
|
||||
@ -207,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 {
|
||||
@ -233,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)
|
||||
@ -249,16 +362,14 @@ func (l *DHCPLease) downIface() {
|
||||
}
|
||||
|
||||
func (l *DHCPLease) renew() error {
|
||||
c, err := newDHCPClient(l.link, l.clientID, l.timeout, l.broadcast)
|
||||
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:
|
||||
@ -280,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, l.timeout, l.broadcast)
|
||||
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")
|
||||
@ -317,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
|
||||
@ -340,10 +450,10 @@ 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
|
||||
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 {
|
||||
@ -352,15 +462,21 @@ func backoffRetry(f func() (*dhcp4.Packet, error)) (*dhcp4.Packet, error) {
|
||||
|
||||
log.Print(err)
|
||||
|
||||
sleepTime = baseDelay + jitter(time.Second)
|
||||
if fastRetryLimit == 0 {
|
||||
sleepTime = baseDelay + jitter(time.Second)
|
||||
} else {
|
||||
sleepTime = resendFastDelay + jitter(time.Second)
|
||||
fastRetryLimit--
|
||||
}
|
||||
|
||||
log.Printf("retrying in %f seconds", sleepTime.Seconds())
|
||||
|
||||
time.Sleep(sleepTime)
|
||||
|
||||
if baseDelay < resendDelayMax {
|
||||
// only adjust delay time if we are in normal backoff stage
|
||||
if baseDelay < resendMax && fastRetryLimit == 0 {
|
||||
baseDelay *= 2
|
||||
} else {
|
||||
} else if fastRetryLimit == 0 { // only break if we are at normal delay
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -369,7 +485,7 @@ func backoffRetry(f func() (*dhcp4.Packet, error)) (*dhcp4.Packet, error) {
|
||||
}
|
||||
|
||||
func newDHCPClient(
|
||||
link netlink.Link, clientID string,
|
||||
link netlink.Link,
|
||||
timeout time.Duration,
|
||||
broadcast bool,
|
||||
) (*dhcp4client.Client, error) {
|
||||
|
@ -26,13 +26,50 @@ import (
|
||||
|
||||
"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
|
||||
@ -40,20 +77,22 @@ func main() {
|
||||
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, timeout, broadcast); 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 {
|
||||
@ -69,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
|
||||
}
|
||||
@ -79,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,4 +1,4 @@
|
||||
|
||||
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
|
||||
|
||||
You can find it online here: https://cni.dev/plugins/ipam/host-local/
|
||||
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.
|
||||
startIP net.IP
|
||||
startRange int
|
||||
// The IP where we started iterating; if we hit this again, we're done.
|
||||
startIP net.IP
|
||||
}
|
||||
|
||||
// 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,10 +149,8 @@ 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 {
|
||||
return nil, "", fmt.Errorf("CNI version %v does not support more than 1 address per family", n.CNIVersion)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,8 +206,9 @@ var _ = Describe("IPAM config", func() {
|
||||
}))
|
||||
})
|
||||
|
||||
It("Should parse CNI_ARGS env", func() {
|
||||
input := `{
|
||||
Context("Should parse CNI_ARGS env", func() {
|
||||
It("without prefix", func() {
|
||||
input := `{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
@ -224,16 +226,43 @@ var _ = Describe("IPAM config", func() {
|
||||
}
|
||||
}`
|
||||
|
||||
envArgs := "IP=10.1.2.10"
|
||||
envArgs := "IP=10.1.2.10"
|
||||
|
||||
conf, _, err := LoadIPAMConfig([]byte(input), envArgs)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(conf.IPArgs).To(Equal([]net.IP{{10, 1, 2, 10}}))
|
||||
conf, _, err := LoadIPAMConfig([]byte(input), envArgs)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(conf.IPArgs).To(Equal([]net.IP{{10, 1, 2, 10}}))
|
||||
})
|
||||
|
||||
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}}))
|
||||
})
|
||||
})
|
||||
|
||||
It("Should parse config args", func() {
|
||||
input := `{
|
||||
Context("Should parse config args", func() {
|
||||
It("without prefix", func() {
|
||||
input := `{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
@ -265,16 +294,62 @@ var _ = Describe("IPAM config", func() {
|
||||
}
|
||||
}`
|
||||
|
||||
envArgs := "IP=10.1.2.10"
|
||||
envArgs := "IP=10.1.2.10"
|
||||
|
||||
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"),
|
||||
}))
|
||||
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("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() {
|
||||
@ -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,10 +67,8 @@ func (s *RangeSet) Canonicalize() error {
|
||||
}
|
||||
if i == 0 {
|
||||
fam = len((*s)[i].RangeStart)
|
||||
} else {
|
||||
if fam != len((*s)[i].RangeStart) {
|
||||
return fmt.Errorf("mixed address families")
|
||||
}
|
||||
} else if fam != len((*s)[i].RangeStart) {
|
||||
return fmt.Errorf("mixed address families")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -28,6 +28,7 @@ func parseResolvConf(filename string) (*types.DNS, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer fp.Close()
|
||||
|
||||
dns := types.DNS{}
|
||||
scanner := bufio.NewScanner(fp)
|
||||
|
@ -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) {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -19,14 +19,13 @@ import (
|
||||
"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() {
|
||||
@ -34,7 +33,6 @@ func main() {
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
|
||||
ipamConf, _, err := allocator.LoadIPAMConfig(args.StdinData, args.Args)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -48,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)
|
||||
}
|
||||
|
||||
@ -62,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)
|
||||
@ -84,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,4 +1,4 @@
|
||||
|
||||
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
|
||||
|
||||
You can find it online here: https://cni.dev/plugins/ipam/static/
|
||||
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,46 +206,46 @@ 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
|
||||
numV4 := 0
|
||||
numV6 := 0
|
||||
|
||||
for i := range n.IPAM.Addresses {
|
||||
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)
|
||||
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(
|
||||
"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
|
||||
}
|
||||
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 {
|
||||
return nil, "", fmt.Errorf("CNI version %v does not support more than 1 address per family", n.CNIVersion)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"
|
||||
)
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -6,6 +6,7 @@ plugins/main/loopback
|
||||
plugins/main/macvlan
|
||||
plugins/main/ptp
|
||||
plugins/main/vlan
|
||||
plugins/main/dummy
|
||||
plugins/meta/portmap
|
||||
plugins/meta/tuning
|
||||
plugins/meta/bandwidth
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user