Compare commits
197 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
6e7fb60738 | ||
![]() |
7c122fabb4 | ||
![]() |
e4ca66b414 | ||
![]() |
abfac4a938 | ||
![]() |
eded0afca8 | ||
![]() |
41d548592d | ||
![]() |
e8c7d9b930 | ||
![]() |
ba8bc7d0c7 | ||
![]() |
7f756b411e | ||
![]() |
3ffc42cdfd | ||
![]() |
6de8a9853c | ||
![]() |
9296c5f80a | ||
![]() |
fec2d62676 | ||
![]() |
a4fc6f93c7 | ||
![]() |
d61e7e5e1f | ||
![]() |
e4950728ce | ||
![]() |
93d197c455 | ||
![]() |
c52e02bccf | ||
![]() |
24b0bf96af | ||
![]() |
d44bbf28af | ||
![]() |
8ad0361964 | ||
![]() |
8324a2e5a4 | ||
![]() |
a4b80cc634 | ||
![]() |
3a49cff1f6 | ||
![]() |
c11ed48733 | ||
![]() |
fa737f82b2 | ||
![]() |
e5df283ab3 | ||
![]() |
cc8b1bd80c | ||
![]() |
03712a572b | ||
![]() |
01a94e17c7 | ||
![]() |
3d1968c152 | ||
![]() |
a3ccebc6ec | ||
![]() |
61d078645a | ||
![]() |
729dd23c40 | ||
![]() |
a6d6efa5ca | ||
![]() |
01b3db8e01 | ||
![]() |
20f31e5e88 | ||
![]() |
06ba001d84 | ||
![]() |
deb8ef63f4 | ||
![]() |
720b1e9811 | ||
![]() |
bdb6814fe2 | ||
![]() |
3653221fad | ||
![]() |
0d2780f0e7 | ||
![]() |
5def33291f | ||
![]() |
07bd325095 | ||
![]() |
7cff5db82a | ||
![]() |
d924f05e12 | ||
![]() |
6269f399a5 | ||
![]() |
5188dc8a19 | ||
![]() |
675ca92261 | ||
![]() |
30078e1cfd | ||
![]() |
acf8ddc8e1 | ||
![]() |
352c8b7ab5 | ||
![]() |
11ad58cf20 | ||
![]() |
d5f9ad99d7 | ||
![]() |
c29dc79f96 | ||
![]() |
508c94caec | ||
![]() |
b96f963c7a | ||
![]() |
518bc80c56 | ||
![]() |
434e9b9ef7 | ||
![]() |
0e87bccf19 | ||
![]() |
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 |
2
.github/actions/retest-action/Dockerfile
vendored
2
.github/actions/retest-action/Dockerfile
vendored
@ -1,4 +1,4 @@
|
||||
FROM alpine:3.17
|
||||
FROM alpine:3.21
|
||||
|
||||
RUN apk add --no-cache curl jq
|
||||
|
||||
|
6
.github/dependabot.yml
vendored
6
.github/dependabot.yml
vendored
@ -17,3 +17,9 @@ updates:
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
groups:
|
||||
golang:
|
||||
patterns:
|
||||
- "*"
|
||||
exclude-patterns:
|
||||
- "github.com/containernetworking/*"
|
||||
|
1
.github/go-version
vendored
Normal file
1
.github/go-version
vendored
Normal file
@ -0,0 +1 @@
|
||||
1.23
|
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@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Re-Test Action
|
||||
uses: ./.github/actions/retest-action
|
||||
|
114
.github/workflows/release.yaml
vendored
Normal file
114
.github/workflows/release.yaml
vendored
Normal file
@ -0,0 +1,114 @@
|
||||
---
|
||||
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: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: .github/go-version
|
||||
|
||||
- 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: Change plugin file ownership
|
||||
working-directory: ./bin
|
||||
run: sudo chown -R root:root .
|
||||
|
||||
- name: Create dist directory
|
||||
run: mkdir dist
|
||||
|
||||
- name: Create archive file
|
||||
working-directory: ./bin
|
||||
run: tar cfzpv ../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: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: .github/go-version
|
||||
|
||||
- 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: Change plugin file ownership
|
||||
working-directory: ./bin
|
||||
run: sudo chown -R root:root .
|
||||
|
||||
- name: Create dist directory
|
||||
run: mkdir dist
|
||||
|
||||
- name: Create archive file
|
||||
working-directory: ./bin
|
||||
run: tar cpfzv ../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
|
13
.github/workflows/stale.yml
vendored
13
.github/workflows/stale.yml
vendored
@ -1,13 +0,0 @@
|
||||
name: 'Close stale issues and PRs'
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 1 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v8
|
||||
with:
|
||||
stale-pr-message: 'This PR has been untouched for too long without an update. It will be closed in 7 days.'
|
||||
exempt-issue-labels: 'keep'
|
59
.github/workflows/test.yaml
vendored
59
.github/workflows/test.yaml
vendored
@ -1,10 +1,10 @@
|
||||
---
|
||||
name: test
|
||||
|
||||
on: ["push", "pull_request"]
|
||||
on:
|
||||
pull_request: {}
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.20"
|
||||
LINUX_ARCHES: "amd64 386 arm arm64 s390x mips64le ppc64le riscv64"
|
||||
|
||||
jobs:
|
||||
@ -12,28 +12,42 @@ jobs:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: setup go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
- uses: actions/checkout@v3
|
||||
go-version-file: .github/go-version
|
||||
- uses: ibiqlik/action-yamllint@v3
|
||||
with:
|
||||
format: auto
|
||||
- uses: golangci/golangci-lint-action@v3
|
||||
- uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: v1.61.0
|
||||
args: -v
|
||||
verify-vendor:
|
||||
name: Verify vendor directory
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: .github/go-version
|
||||
- name: Check module vendoring
|
||||
run: |
|
||||
go mod tidy
|
||||
go mod vendor
|
||||
test -z "$(git status --porcelain)" || (echo "please run 'go mod tidy && go mod vendor', and submit your changes"; exit 1)
|
||||
build:
|
||||
name: Build all linux architectures
|
||||
needs: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: setup go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
go-version-file: .github/go-version
|
||||
- name: Build on all supported architectures
|
||||
run: |
|
||||
set -e
|
||||
@ -53,24 +67,25 @@ jobs:
|
||||
sudo apt-get install linux-modules-extra-$(uname -r)
|
||||
- name: Install nftables
|
||||
run: sudo apt-get install nftables
|
||||
|
||||
- name: Install dnsmasq(dhcp server)
|
||||
run: |
|
||||
sudo apt-get install dnsmasq
|
||||
sudo systemctl disable --now dnsmasq
|
||||
- uses: actions/checkout@v4
|
||||
- name: setup go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
go-version-file: .github/go-version
|
||||
- name: Set up Go for root
|
||||
run: |
|
||||
sudo ln -sf `which go` `sudo which go` || true
|
||||
sudo go version
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- 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
|
||||
@ -87,10 +102,10 @@ jobs:
|
||||
needs: build
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: setup go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
- uses: actions/checkout@v3
|
||||
go-version-file: .github/go-version
|
||||
- name: test
|
||||
run: bash ./test_windows.sh
|
||||
|
@ -6,6 +6,8 @@ issues:
|
||||
- linters:
|
||||
- revive
|
||||
text: " and that stutters;"
|
||||
- path: '(.+)_test\.go'
|
||||
text: "dot-imports: should not use dot imports"
|
||||
|
||||
linters:
|
||||
disable:
|
||||
@ -38,5 +40,5 @@ linters-settings:
|
||||
- prefix(github.com/containernetworking)
|
||||
|
||||
run:
|
||||
skip-dirs:
|
||||
- vendor
|
||||
timeout: 5m
|
||||
modules-download-mode: vendor
|
||||
|
@ -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")"
|
||||
|
||||
|
62
go.mod
62
go.mod
@ -1,44 +1,54 @@
|
||||
module github.com/containernetworking/plugins
|
||||
|
||||
go 1.20
|
||||
go 1.23
|
||||
|
||||
require (
|
||||
github.com/Microsoft/hcsshim v0.9.9
|
||||
github.com/alexflint/go-filemutex v1.2.0
|
||||
github.com/Microsoft/hcsshim v0.12.9
|
||||
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.6.0
|
||||
github.com/containernetworking/cni v1.2.3
|
||||
github.com/coreos/go-iptables v0.8.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/godbus/dbus/v5 v5.1.0
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475
|
||||
github.com/mattn/go-shellwords v1.0.12
|
||||
github.com/networkplumbing/go-nft v0.3.0
|
||||
github.com/onsi/ginkgo/v2 v2.9.2
|
||||
github.com/onsi/gomega v1.27.6
|
||||
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.7.0
|
||||
github.com/networkplumbing/go-nft v0.4.0
|
||||
github.com/onsi/ginkgo/v2 v2.22.2
|
||||
github.com/onsi/gomega v1.36.2
|
||||
github.com/opencontainers/selinux v1.11.1
|
||||
github.com/safchain/ethtool v0.5.9
|
||||
github.com/vishvananda/netlink v1.3.0
|
||||
golang.org/x/sys v0.29.0
|
||||
sigs.k8s.io/knftables v0.0.18
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.6.0 // indirect
|
||||
github.com/containerd/cgroups v1.1.0 // indirect
|
||||
github.com/go-logr/logr v1.2.4 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/containerd/cgroups/v3 v3.0.3 // indirect
|
||||
github.com/containerd/errdefs v0.3.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/containerd/typeurl/v2 v2.2.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/pprof v0.0.0-20230323073829-e72429f035bd // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/mdlayher/packet v1.1.2 // indirect
|
||||
github.com/mdlayher/socket v0.5.1 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
golang.org/x/mod v0.9.0 // indirect
|
||||
golang.org/x/net v0.8.0 // indirect
|
||||
golang.org/x/text v0.8.0 // indirect
|
||||
golang.org/x/tools v0.7.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/tools v0.28.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||
google.golang.org/grpc v1.67.0 // indirect
|
||||
google.golang.org/protobuf v1.36.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
@ -60,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,
|
||||
@ -82,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())
|
||||
@ -145,9 +154,13 @@ 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 bandwidth veth device", func(b Benchmarker) {
|
||||
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()))
|
||||
@ -172,17 +185,19 @@ var _ = Describe("Basic PTP using cnitool", func() {
|
||||
// balanced by run time.
|
||||
|
||||
By(fmt.Sprintf("sending tcp traffic to the chained, bridged, traffic shaped container on ip address '%s:%d'\n\n", chainedBridgeIP, chainedBridgeBandwidthPort))
|
||||
runtimeWithLimit := b.Time("with chained bridge and bandwidth plugins", func() {
|
||||
makeTCPClientInNS(hostNS.ShortName(), chainedBridgeIP, chainedBridgeBandwidthPort, packetInBytes)
|
||||
})
|
||||
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", runtimeWithoutLimit.Seconds())
|
||||
|
||||
Expect(runtimeWithLimit).To(BeNumerically(">", runtimeWithoutLimit+1000*time.Millisecond))
|
||||
}, 1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
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"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -43,7 +43,7 @@ func TestAnnotate(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if !reflect.DeepEqual(Annotatef(test.existingErr, test.contextMessage), test.expectedErr) {
|
||||
if !reflect.DeepEqual(Annotate(test.existingErr, test.contextMessage), test.expectedErr) {
|
||||
t.Errorf("test case %s fails", test.name)
|
||||
return
|
||||
}
|
||||
|
180
pkg/ip/ipmasq_iptables_linux.go
Normal file
180
pkg/ip/ipmasq_iptables_linux.go
Normal file
@ -0,0 +1,180 @@
|
||||
// Copyright 2015 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/plugins/pkg/utils"
|
||||
)
|
||||
|
||||
// setupIPMasqIPTables is the iptables-based implementation of SetupIPMasqForNetworks
|
||||
func setupIPMasqIPTables(ipns []*net.IPNet, network, _, containerID string) error {
|
||||
// Note: for historical reasons, the iptables implementation ignores ifname.
|
||||
chain := utils.FormatChainName(network, containerID)
|
||||
comment := utils.FormatComment(network, containerID)
|
||||
for _, ip := range ipns {
|
||||
if err := SetupIPMasq(ip, chain, comment); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetupIPMasq installs iptables rules to masquerade traffic
|
||||
// coming from ip of ipn and going outside of ipn.
|
||||
// Deprecated: This function only supports iptables. Use SetupIPMasqForNetworks, which
|
||||
// supports both iptables and nftables.
|
||||
func SetupIPMasq(ipn *net.IPNet, chain string, comment string) error {
|
||||
isV6 := ipn.IP.To4() == nil
|
||||
|
||||
var ipt *iptables.IPTables
|
||||
var err error
|
||||
var multicastNet string
|
||||
|
||||
if isV6 {
|
||||
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv6)
|
||||
multicastNet = "ff00::/8"
|
||||
} else {
|
||||
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||
multicastNet = "224.0.0.0/4"
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to locate iptables: %v", err)
|
||||
}
|
||||
|
||||
// Create chain if doesn't exist
|
||||
exists := false
|
||||
chains, err := ipt.ListChains("nat")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list chains: %v", err)
|
||||
}
|
||||
for _, ch := range chains {
|
||||
if ch == chain {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exists {
|
||||
if err = ipt.NewChain("nat", chain); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Packets to this network should not be touched
|
||||
if err := ipt.AppendUnique("nat", chain, "-d", ipn.String(), "-j", "ACCEPT", "-m", "comment", "--comment", comment); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Don't masquerade multicast - pods should be able to talk to other pods
|
||||
// on the local network via multicast.
|
||||
if err := ipt.AppendUnique("nat", chain, "!", "-d", multicastNet, "-j", "MASQUERADE", "-m", "comment", "--comment", comment); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Packets from the specific IP of this network will hit the chain
|
||||
return ipt.AppendUnique("nat", "POSTROUTING", "-s", ipn.IP.String(), "-j", chain, "-m", "comment", "--comment", comment)
|
||||
}
|
||||
|
||||
// teardownIPMasqIPTables is the iptables-based implementation of TeardownIPMasqForNetworks
|
||||
func teardownIPMasqIPTables(ipns []*net.IPNet, network, _, containerID string) error {
|
||||
// Note: for historical reasons, the iptables implementation ignores ifname.
|
||||
chain := utils.FormatChainName(network, containerID)
|
||||
comment := utils.FormatComment(network, containerID)
|
||||
|
||||
var errs []string
|
||||
for _, ipn := range ipns {
|
||||
err := TeardownIPMasq(ipn, chain, comment)
|
||||
if err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if errs == nil {
|
||||
return nil
|
||||
}
|
||||
return errors.New(strings.Join(errs, "\n"))
|
||||
}
|
||||
|
||||
// TeardownIPMasq undoes the effects of SetupIPMasq.
|
||||
// Deprecated: This function only supports iptables. Use TeardownIPMasqForNetworks, which
|
||||
// supports both iptables and nftables.
|
||||
func TeardownIPMasq(ipn *net.IPNet, chain string, comment string) error {
|
||||
isV6 := ipn.IP.To4() == nil
|
||||
|
||||
var ipt *iptables.IPTables
|
||||
var err error
|
||||
|
||||
if isV6 {
|
||||
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv6)
|
||||
} else {
|
||||
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to locate iptables: %v", err)
|
||||
}
|
||||
|
||||
err = ipt.Delete("nat", "POSTROUTING", "-s", ipn.IP.String(), "-j", chain, "-m", "comment", "--comment", comment)
|
||||
if err != nil && !isNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
// for downward compatibility
|
||||
err = ipt.Delete("nat", "POSTROUTING", "-s", ipn.String(), "-j", chain, "-m", "comment", "--comment", comment)
|
||||
if err != nil && !isNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ipt.ClearChain("nat", chain)
|
||||
if err != nil && !isNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ipt.DeleteChain("nat", chain)
|
||||
if err != nil && !isNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// gcIPMasqIPTables is the iptables-based implementation of GCIPMasqForNetwork
|
||||
func gcIPMasqIPTables(_ string, _ []types.GCAttachment) error {
|
||||
// FIXME: The iptables implementation does not support GC.
|
||||
//
|
||||
// (In theory, it _could_ backward-compatibly support it, by adding a no-op rule
|
||||
// with a comment indicating the network to each chain it creates, so that it
|
||||
// could later figure out which chains corresponded to which networks; older
|
||||
// implementations would ignore the extra rule but would still correctly delete
|
||||
// the chain on teardown (because they ClearChain() before doing DeleteChain()).
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isNotExist returnst true if the error is from iptables indicating
|
||||
// that the target does not exist.
|
||||
func isNotExist(err error) bool {
|
||||
e, ok := err.(*iptables.Error)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return e.IsNotExist()
|
||||
}
|
@ -15,111 +15,78 @@
|
||||
package ip
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/plugins/pkg/utils"
|
||||
)
|
||||
|
||||
// SetupIPMasq installs iptables rules to masquerade traffic
|
||||
// coming from ip of ipn and going outside of ipn
|
||||
func SetupIPMasq(ipn *net.IPNet, chain string, comment string) error {
|
||||
isV6 := ipn.IP.To4() == nil
|
||||
|
||||
var ipt *iptables.IPTables
|
||||
var err error
|
||||
var multicastNet string
|
||||
|
||||
if isV6 {
|
||||
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv6)
|
||||
multicastNet = "ff00::/8"
|
||||
} else {
|
||||
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||
multicastNet = "224.0.0.0/4"
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to locate iptables: %v", err)
|
||||
}
|
||||
|
||||
// Create chain if doesn't exist
|
||||
exists := false
|
||||
chains, err := ipt.ListChains("nat")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list chains: %v", err)
|
||||
}
|
||||
for _, ch := range chains {
|
||||
if ch == chain {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exists {
|
||||
if err = ipt.NewChain("nat", chain); err != nil {
|
||||
return err
|
||||
// SetupIPMasqForNetworks installs rules to masquerade traffic coming from ips of ipns and
|
||||
// going outside of ipns, using a chain name based on network, ifname, and containerID. The
|
||||
// backend can be either "iptables" or "nftables"; if it is nil, then a suitable default
|
||||
// implementation will be used.
|
||||
func SetupIPMasqForNetworks(backend *string, ipns []*net.IPNet, network, ifname, containerID string) error {
|
||||
if backend == nil {
|
||||
// Prefer iptables, unless only nftables is available
|
||||
defaultBackend := "iptables"
|
||||
if !utils.SupportsIPTables() && utils.SupportsNFTables() {
|
||||
defaultBackend = "nftables"
|
||||
}
|
||||
backend = &defaultBackend
|
||||
}
|
||||
|
||||
// Packets to this network should not be touched
|
||||
if err := ipt.AppendUnique("nat", chain, "-d", ipn.String(), "-j", "ACCEPT", "-m", "comment", "--comment", comment); err != nil {
|
||||
return err
|
||||
switch *backend {
|
||||
case "iptables":
|
||||
return setupIPMasqIPTables(ipns, network, ifname, containerID)
|
||||
case "nftables":
|
||||
return setupIPMasqNFTables(ipns, network, ifname, containerID)
|
||||
default:
|
||||
return fmt.Errorf("unknown ipmasq backend %q", *backend)
|
||||
}
|
||||
|
||||
// Don't masquerade multicast - pods should be able to talk to other pods
|
||||
// on the local network via multicast.
|
||||
if err := ipt.AppendUnique("nat", chain, "!", "-d", multicastNet, "-j", "MASQUERADE", "-m", "comment", "--comment", comment); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Packets from the specific IP of this network will hit the chain
|
||||
return ipt.AppendUnique("nat", "POSTROUTING", "-s", ipn.IP.String(), "-j", chain, "-m", "comment", "--comment", comment)
|
||||
}
|
||||
|
||||
// TeardownIPMasq undoes the effects of SetupIPMasq
|
||||
func TeardownIPMasq(ipn *net.IPNet, chain string, comment string) error {
|
||||
isV6 := ipn.IP.To4() == nil
|
||||
// TeardownIPMasqForNetworks undoes the effects of SetupIPMasqForNetworks
|
||||
func TeardownIPMasqForNetworks(ipns []*net.IPNet, network, ifname, containerID string) error {
|
||||
var errs []string
|
||||
|
||||
var ipt *iptables.IPTables
|
||||
var err error
|
||||
// Do both the iptables and the nftables cleanup, since the pod may have been
|
||||
// created with a different version of this plugin or a different configuration.
|
||||
|
||||
if isV6 {
|
||||
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv6)
|
||||
} else {
|
||||
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to locate iptables: %v", err)
|
||||
err := teardownIPMasqIPTables(ipns, network, ifname, containerID)
|
||||
if err != nil && utils.SupportsIPTables() {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
|
||||
err = ipt.Delete("nat", "POSTROUTING", "-s", ipn.IP.String(), "-j", chain, "-m", "comment", "--comment", comment)
|
||||
if err != nil && !isNotExist(err) {
|
||||
return err
|
||||
err = teardownIPMasqNFTables(ipns, network, ifname, containerID)
|
||||
if err != nil && utils.SupportsNFTables() {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
|
||||
// for downward compatibility
|
||||
err = ipt.Delete("nat", "POSTROUTING", "-s", ipn.String(), "-j", chain, "-m", "comment", "--comment", comment)
|
||||
if err != nil && !isNotExist(err) {
|
||||
return err
|
||||
if errs == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = ipt.ClearChain("nat", chain)
|
||||
if err != nil && !isNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ipt.DeleteChain("nat", chain)
|
||||
if err != nil && !isNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return errors.New(strings.Join(errs, "\n"))
|
||||
}
|
||||
|
||||
// isNotExist returnst true if the error is from iptables indicating
|
||||
// that the target does not exist.
|
||||
func isNotExist(err error) bool {
|
||||
e, ok := err.(*iptables.Error)
|
||||
if !ok {
|
||||
return false
|
||||
// GCIPMasqForNetwork garbage collects stale IPMasq entries for network
|
||||
func GCIPMasqForNetwork(network string, attachments []types.GCAttachment) error {
|
||||
var errs []string
|
||||
|
||||
err := gcIPMasqIPTables(network, attachments)
|
||||
if err != nil && utils.SupportsIPTables() {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
return e.IsNotExist()
|
||||
|
||||
err = gcIPMasqNFTables(network, attachments)
|
||||
if err != nil && utils.SupportsNFTables() {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
|
||||
if errs == nil {
|
||||
return nil
|
||||
}
|
||||
return errors.New(strings.Join(errs, "\n"))
|
||||
}
|
||||
|
231
pkg/ip/ipmasq_nftables_linux.go
Normal file
231
pkg/ip/ipmasq_nftables_linux.go
Normal file
@ -0,0 +1,231 @@
|
||||
// Copyright 2023 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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/knftables"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/plugins/pkg/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
ipMasqTableName = "cni_plugins_masquerade"
|
||||
ipMasqChainName = "masq_checks"
|
||||
)
|
||||
|
||||
// The nftables ipmasq implementation is mostly like the iptables implementation, with
|
||||
// minor updates to fix a bug (adding `ifname`) and to allow future GC support.
|
||||
//
|
||||
// We add a rule for each mapping, with a comment containing a hash of its identifiers,
|
||||
// so that we can later reliably delete the rules we want. (This is important because in
|
||||
// edge cases, it's possible the plugin might see "ADD container A with IP 192.168.1.3",
|
||||
// followed by "ADD container B with IP 192.168.1.3" followed by "DEL container A with IP
|
||||
// 192.168.1.3", and we need to make sure that the DEL causes us to delete the rule for
|
||||
// container A, and not the rule for container B.)
|
||||
//
|
||||
// It would be more nftables-y to have a chain with a single rule doing a lookup against a
|
||||
// set with an element per mapping, rather than having a chain with a rule per mapping.
|
||||
// But there's no easy, non-racy way to say "delete the element 192.168.1.3 from the set,
|
||||
// but only if it was added for container A, not if it was added for container B".
|
||||
|
||||
// hashForNetwork returns a unique hash for this network
|
||||
func hashForNetwork(network string) string {
|
||||
return utils.MustFormatHashWithPrefix(16, "", network)
|
||||
}
|
||||
|
||||
// hashForInstance returns a unique hash identifying the rules for this
|
||||
// network/ifname/containerID
|
||||
func hashForInstance(network, ifname, containerID string) string {
|
||||
return hashForNetwork(network) + "-" + utils.MustFormatHashWithPrefix(16, "", ifname+":"+containerID)
|
||||
}
|
||||
|
||||
// commentForInstance returns a comment string that begins with a unique hash and
|
||||
// ends with a (possibly-truncated) human-readable description.
|
||||
func commentForInstance(network, ifname, containerID string) string {
|
||||
comment := fmt.Sprintf("%s, net: %s, if: %s, id: %s",
|
||||
hashForInstance(network, ifname, containerID),
|
||||
strings.ReplaceAll(network, `"`, ``),
|
||||
strings.ReplaceAll(ifname, `"`, ``),
|
||||
strings.ReplaceAll(containerID, `"`, ``),
|
||||
)
|
||||
if len(comment) > knftables.CommentLengthMax {
|
||||
comment = comment[:knftables.CommentLengthMax]
|
||||
}
|
||||
return comment
|
||||
}
|
||||
|
||||
// setupIPMasqNFTables is the nftables-based implementation of SetupIPMasqForNetworks
|
||||
func setupIPMasqNFTables(ipns []*net.IPNet, network, ifname, containerID string) error {
|
||||
nft, err := knftables.New(knftables.InetFamily, ipMasqTableName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return setupIPMasqNFTablesWithInterface(nft, ipns, network, ifname, containerID)
|
||||
}
|
||||
|
||||
func setupIPMasqNFTablesWithInterface(nft knftables.Interface, ipns []*net.IPNet, network, ifname, containerID string) error {
|
||||
staleRules, err := findRules(nft, hashForInstance(network, ifname, containerID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tx := nft.NewTransaction()
|
||||
|
||||
// Ensure that our table and chains exist.
|
||||
tx.Add(&knftables.Table{
|
||||
Comment: knftables.PtrTo("Masquerading for plugins from github.com/containernetworking/plugins"),
|
||||
})
|
||||
tx.Add(&knftables.Chain{
|
||||
Name: ipMasqChainName,
|
||||
Comment: knftables.PtrTo("Masquerade traffic from certain IPs to any (non-multicast) IP outside their subnet"),
|
||||
})
|
||||
|
||||
// Ensure that the postrouting chain exists and has the correct rules. (Has to be
|
||||
// done after creating ipMasqChainName, so we can jump to it.)
|
||||
tx.Add(&knftables.Chain{
|
||||
Name: "postrouting",
|
||||
Type: knftables.PtrTo(knftables.NATType),
|
||||
Hook: knftables.PtrTo(knftables.PostroutingHook),
|
||||
Priority: knftables.PtrTo(knftables.SNATPriority),
|
||||
})
|
||||
tx.Flush(&knftables.Chain{
|
||||
Name: "postrouting",
|
||||
})
|
||||
tx.Add(&knftables.Rule{
|
||||
Chain: "postrouting",
|
||||
Rule: "ip daddr == 224.0.0.0/4 return",
|
||||
})
|
||||
tx.Add(&knftables.Rule{
|
||||
Chain: "postrouting",
|
||||
Rule: "ip6 daddr == ff00::/8 return",
|
||||
})
|
||||
tx.Add(&knftables.Rule{
|
||||
Chain: "postrouting",
|
||||
Rule: knftables.Concat(
|
||||
"goto", ipMasqChainName,
|
||||
),
|
||||
})
|
||||
|
||||
// Delete stale rules, add new rules to masquerade chain
|
||||
for _, rule := range staleRules {
|
||||
tx.Delete(rule)
|
||||
}
|
||||
for _, ipn := range ipns {
|
||||
ip := "ip"
|
||||
if ipn.IP.To4() == nil {
|
||||
ip = "ip6"
|
||||
}
|
||||
|
||||
// e.g. if ipn is "192.168.1.4/24", then dstNet is "192.168.1.0/24"
|
||||
dstNet := &net.IPNet{IP: ipn.IP.Mask(ipn.Mask), Mask: ipn.Mask}
|
||||
|
||||
tx.Add(&knftables.Rule{
|
||||
Chain: ipMasqChainName,
|
||||
Rule: knftables.Concat(
|
||||
ip, "saddr", "==", ipn.IP,
|
||||
ip, "daddr", "!=", dstNet,
|
||||
"masquerade",
|
||||
),
|
||||
Comment: knftables.PtrTo(commentForInstance(network, ifname, containerID)),
|
||||
})
|
||||
}
|
||||
|
||||
return nft.Run(context.TODO(), tx)
|
||||
}
|
||||
|
||||
// teardownIPMasqNFTables is the nftables-based implementation of TeardownIPMasqForNetworks
|
||||
func teardownIPMasqNFTables(ipns []*net.IPNet, network, ifname, containerID string) error {
|
||||
nft, err := knftables.New(knftables.InetFamily, ipMasqTableName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return teardownIPMasqNFTablesWithInterface(nft, ipns, network, ifname, containerID)
|
||||
}
|
||||
|
||||
func teardownIPMasqNFTablesWithInterface(nft knftables.Interface, _ []*net.IPNet, network, ifname, containerID string) error {
|
||||
rules, err := findRules(nft, hashForInstance(network, ifname, containerID))
|
||||
if err != nil {
|
||||
return err
|
||||
} else if len(rules) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
tx := nft.NewTransaction()
|
||||
for _, rule := range rules {
|
||||
tx.Delete(rule)
|
||||
}
|
||||
return nft.Run(context.TODO(), tx)
|
||||
}
|
||||
|
||||
// gcIPMasqNFTables is the nftables-based implementation of GCIPMasqForNetwork
|
||||
func gcIPMasqNFTables(network string, attachments []types.GCAttachment) error {
|
||||
nft, err := knftables.New(knftables.InetFamily, ipMasqTableName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return gcIPMasqNFTablesWithInterface(nft, network, attachments)
|
||||
}
|
||||
|
||||
func gcIPMasqNFTablesWithInterface(nft knftables.Interface, network string, attachments []types.GCAttachment) error {
|
||||
// Find all rules for the network
|
||||
rules, err := findRules(nft, hashForNetwork(network))
|
||||
if err != nil {
|
||||
return err
|
||||
} else if len(rules) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compute the comments for all elements of attachments
|
||||
validAttachments := map[string]bool{}
|
||||
for _, attachment := range attachments {
|
||||
validAttachments[commentForInstance(network, attachment.IfName, attachment.ContainerID)] = true
|
||||
}
|
||||
|
||||
// Delete anything in rules that isn't in validAttachments
|
||||
tx := nft.NewTransaction()
|
||||
for _, rule := range rules {
|
||||
if !validAttachments[*rule.Comment] {
|
||||
tx.Delete(rule)
|
||||
}
|
||||
}
|
||||
return nft.Run(context.TODO(), tx)
|
||||
}
|
||||
|
||||
// findRules finds rules with comments that start with commentPrefix.
|
||||
func findRules(nft knftables.Interface, commentPrefix string) ([]*knftables.Rule, error) {
|
||||
rules, err := nft.ListRules(context.TODO(), ipMasqChainName)
|
||||
if err != nil {
|
||||
if knftables.IsNotFound(err) {
|
||||
// If ipMasqChainName doesn't exist yet, that's fine
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
matchingRules := make([]*knftables.Rule, 0, 1)
|
||||
for _, rule := range rules {
|
||||
if rule.Comment != nil && strings.HasPrefix(*rule.Comment, commentPrefix) {
|
||||
matchingRules = append(matchingRules, rule)
|
||||
}
|
||||
}
|
||||
|
||||
return matchingRules, nil
|
||||
}
|
213
pkg/ip/ipmasq_nftables_linux_test.go
Normal file
213
pkg/ip/ipmasq_nftables_linux_test.go
Normal file
@ -0,0 +1,213 @@
|
||||
// Copyright 2023 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"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
"sigs.k8s.io/knftables"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
)
|
||||
|
||||
func Test_setupIPMasqNFTables(t *testing.T) {
|
||||
nft := knftables.NewFake(knftables.InetFamily, ipMasqTableName)
|
||||
|
||||
containers := []struct {
|
||||
network string
|
||||
ifname string
|
||||
containerID string
|
||||
addrs []string
|
||||
}{
|
||||
{
|
||||
network: "unit-test",
|
||||
ifname: "eth0",
|
||||
containerID: "one",
|
||||
addrs: []string{"192.168.1.1/24"},
|
||||
},
|
||||
{
|
||||
network: "unit-test",
|
||||
ifname: "eth0",
|
||||
containerID: "two",
|
||||
addrs: []string{"192.168.1.2/24", "2001:db8::2/64"},
|
||||
},
|
||||
{
|
||||
network: "unit-test",
|
||||
ifname: "eth0",
|
||||
containerID: "three",
|
||||
addrs: []string{"192.168.99.5/24"},
|
||||
},
|
||||
{
|
||||
network: "alternate",
|
||||
ifname: "net1",
|
||||
containerID: "three",
|
||||
addrs: []string{
|
||||
"10.0.0.5/24",
|
||||
"10.0.0.6/24",
|
||||
"10.0.1.7/24",
|
||||
"2001:db8::5/64",
|
||||
"2001:db8::6/64",
|
||||
"2001:db8:1::7/64",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range containers {
|
||||
ipns := []*net.IPNet{}
|
||||
for _, addr := range c.addrs {
|
||||
nladdr, err := netlink.ParseAddr(addr)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse test addr: %v", err)
|
||||
}
|
||||
ipns = append(ipns, nladdr.IPNet)
|
||||
}
|
||||
err := setupIPMasqNFTablesWithInterface(nft, ipns, c.network, c.ifname, c.containerID)
|
||||
if err != nil {
|
||||
t.Fatalf("error from setupIPMasqNFTables: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
expected := strings.TrimSpace(`
|
||||
add table inet cni_plugins_masquerade { comment "Masquerading for plugins from github.com/containernetworking/plugins" ; }
|
||||
add chain inet cni_plugins_masquerade masq_checks { comment "Masquerade traffic from certain IPs to any (non-multicast) IP outside their subnet" ; }
|
||||
add chain inet cni_plugins_masquerade postrouting { type nat hook postrouting priority 100 ; }
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 192.168.1.1 ip daddr != 192.168.1.0/24 masquerade comment "6fd94d501e58f0aa-287fc69eff0574a2, net: unit-test, if: eth0, id: one"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 192.168.1.2 ip daddr != 192.168.1.0/24 masquerade comment "6fd94d501e58f0aa-d750b2c8f0f25d5f, net: unit-test, if: eth0, id: two"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8::2 ip6 daddr != 2001:db8::/64 masquerade comment "6fd94d501e58f0aa-d750b2c8f0f25d5f, net: unit-test, if: eth0, id: two"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 192.168.99.5 ip daddr != 192.168.99.0/24 masquerade comment "6fd94d501e58f0aa-a4d4adb82b669cfe, net: unit-test, if: eth0, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 10.0.0.5 ip daddr != 10.0.0.0/24 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 10.0.0.6 ip daddr != 10.0.0.0/24 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 10.0.1.7 ip daddr != 10.0.1.0/24 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8::5 ip6 daddr != 2001:db8::/64 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8::6 ip6 daddr != 2001:db8::/64 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8:1::7 ip6 daddr != 2001:db8:1::/64 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade postrouting ip daddr == 224.0.0.0/4 return
|
||||
add rule inet cni_plugins_masquerade postrouting ip6 daddr == ff00::/8 return
|
||||
add rule inet cni_plugins_masquerade postrouting goto masq_checks
|
||||
`)
|
||||
dump := strings.TrimSpace(nft.Dump())
|
||||
if dump != expected {
|
||||
t.Errorf("expected nftables state:\n%s\n\nactual:\n%s\n\n", expected, dump)
|
||||
}
|
||||
|
||||
// Add a new container reusing "one"'s address, before deleting "one"
|
||||
c := containers[0]
|
||||
addr, err := netlink.ParseAddr(c.addrs[0])
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse test addr: %v", err)
|
||||
}
|
||||
err = setupIPMasqNFTablesWithInterface(nft, []*net.IPNet{addr.IPNet}, "unit-test", "eth0", "four")
|
||||
if err != nil {
|
||||
t.Fatalf("error from setupIPMasqNFTables: %v", err)
|
||||
}
|
||||
|
||||
// Remove "one"
|
||||
err = teardownIPMasqNFTablesWithInterface(nft, []*net.IPNet{addr.IPNet}, c.network, c.ifname, c.containerID)
|
||||
if err != nil {
|
||||
t.Fatalf("error from teardownIPMasqNFTables: %v", err)
|
||||
}
|
||||
|
||||
// Check that "one" was deleted (and "four" wasn't)
|
||||
expected = strings.TrimSpace(`
|
||||
add table inet cni_plugins_masquerade { comment "Masquerading for plugins from github.com/containernetworking/plugins" ; }
|
||||
add chain inet cni_plugins_masquerade masq_checks { comment "Masquerade traffic from certain IPs to any (non-multicast) IP outside their subnet" ; }
|
||||
add chain inet cni_plugins_masquerade postrouting { type nat hook postrouting priority 100 ; }
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 192.168.1.2 ip daddr != 192.168.1.0/24 masquerade comment "6fd94d501e58f0aa-d750b2c8f0f25d5f, net: unit-test, if: eth0, id: two"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8::2 ip6 daddr != 2001:db8::/64 masquerade comment "6fd94d501e58f0aa-d750b2c8f0f25d5f, net: unit-test, if: eth0, id: two"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 192.168.99.5 ip daddr != 192.168.99.0/24 masquerade comment "6fd94d501e58f0aa-a4d4adb82b669cfe, net: unit-test, if: eth0, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 10.0.0.5 ip daddr != 10.0.0.0/24 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 10.0.0.6 ip daddr != 10.0.0.0/24 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 10.0.1.7 ip daddr != 10.0.1.0/24 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8::5 ip6 daddr != 2001:db8::/64 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8::6 ip6 daddr != 2001:db8::/64 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8:1::7 ip6 daddr != 2001:db8:1::/64 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 192.168.1.1 ip daddr != 192.168.1.0/24 masquerade comment "6fd94d501e58f0aa-e766de567ef6c543, net: unit-test, if: eth0, id: four"
|
||||
add rule inet cni_plugins_masquerade postrouting ip daddr == 224.0.0.0/4 return
|
||||
add rule inet cni_plugins_masquerade postrouting ip6 daddr == ff00::/8 return
|
||||
add rule inet cni_plugins_masquerade postrouting goto masq_checks
|
||||
`)
|
||||
dump = strings.TrimSpace(nft.Dump())
|
||||
if dump != expected {
|
||||
t.Errorf("expected nftables state:\n%s\n\nactual:\n%s\n\n", expected, dump)
|
||||
}
|
||||
|
||||
// GC "four" from the "unit-test" network
|
||||
err = gcIPMasqNFTablesWithInterface(nft, "unit-test", []types.GCAttachment{
|
||||
{IfName: "eth0", ContainerID: "two"},
|
||||
{IfName: "eth0", ContainerID: "three"},
|
||||
// (irrelevant extra element)
|
||||
{IfName: "eth0", ContainerID: "one"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("error from gcIPMasqNFTables: %v", err)
|
||||
}
|
||||
// GC the "alternate" network without removing anything
|
||||
err = gcIPMasqNFTablesWithInterface(nft, "alternate", []types.GCAttachment{
|
||||
{IfName: "net1", ContainerID: "three"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("error from gcIPMasqNFTables: %v", err)
|
||||
}
|
||||
|
||||
// Re-dump
|
||||
expected = strings.TrimSpace(`
|
||||
add table inet cni_plugins_masquerade { comment "Masquerading for plugins from github.com/containernetworking/plugins" ; }
|
||||
add chain inet cni_plugins_masquerade masq_checks { comment "Masquerade traffic from certain IPs to any (non-multicast) IP outside their subnet" ; }
|
||||
add chain inet cni_plugins_masquerade postrouting { type nat hook postrouting priority 100 ; }
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 192.168.1.2 ip daddr != 192.168.1.0/24 masquerade comment "6fd94d501e58f0aa-d750b2c8f0f25d5f, net: unit-test, if: eth0, id: two"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8::2 ip6 daddr != 2001:db8::/64 masquerade comment "6fd94d501e58f0aa-d750b2c8f0f25d5f, net: unit-test, if: eth0, id: two"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 192.168.99.5 ip daddr != 192.168.99.0/24 masquerade comment "6fd94d501e58f0aa-a4d4adb82b669cfe, net: unit-test, if: eth0, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 10.0.0.5 ip daddr != 10.0.0.0/24 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 10.0.0.6 ip daddr != 10.0.0.0/24 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 10.0.1.7 ip daddr != 10.0.1.0/24 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8::5 ip6 daddr != 2001:db8::/64 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8::6 ip6 daddr != 2001:db8::/64 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8:1::7 ip6 daddr != 2001:db8:1::/64 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade postrouting ip daddr == 224.0.0.0/4 return
|
||||
add rule inet cni_plugins_masquerade postrouting ip6 daddr == ff00::/8 return
|
||||
add rule inet cni_plugins_masquerade postrouting goto masq_checks
|
||||
`)
|
||||
dump = strings.TrimSpace(nft.Dump())
|
||||
if dump != expected {
|
||||
t.Errorf("expected nftables state:\n%s\n\nactual:\n%s\n\n", expected, dump)
|
||||
}
|
||||
|
||||
// GC everything
|
||||
err = gcIPMasqNFTablesWithInterface(nft, "unit-test", []types.GCAttachment{})
|
||||
if err != nil {
|
||||
t.Fatalf("error from gcIPMasqNFTables: %v", err)
|
||||
}
|
||||
err = gcIPMasqNFTablesWithInterface(nft, "alternate", []types.GCAttachment{})
|
||||
if err != nil {
|
||||
t.Fatalf("error from gcIPMasqNFTables: %v", err)
|
||||
}
|
||||
|
||||
expected = strings.TrimSpace(`
|
||||
add table inet cni_plugins_masquerade { comment "Masquerading for plugins from github.com/containernetworking/plugins" ; }
|
||||
add chain inet cni_plugins_masquerade masq_checks { comment "Masquerade traffic from certain IPs to any (non-multicast) IP outside their subnet" ; }
|
||||
add chain inet cni_plugins_masquerade postrouting { type nat hook postrouting priority 100 ; }
|
||||
add rule inet cni_plugins_masquerade postrouting ip daddr == 224.0.0.0/4 return
|
||||
add rule inet cni_plugins_masquerade postrouting ip6 daddr == ff00::/8 return
|
||||
add rule inet cni_plugins_masquerade postrouting goto masq_checks
|
||||
`)
|
||||
dump = strings.TrimSpace(nft.Dump())
|
||||
if dump != expected {
|
||||
t.Errorf("expected nftables state:\n%s\n\nactual:\n%s\n\n", expected, dump)
|
||||
}
|
||||
}
|
@ -32,11 +32,12 @@ var ErrLinkNotFound = errors.New("link not found")
|
||||
|
||||
// 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) {
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = name
|
||||
linkAttrs.MTU = mtu
|
||||
|
||||
veth := &netlink.Veth{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: name,
|
||||
MTU: mtu,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
PeerName: peer,
|
||||
PeerNamespace: netlink.NsFd(int(hostNS.Fd())),
|
||||
}
|
||||
@ -90,7 +91,7 @@ func makeVeth(name, vethPeerName string, mtu int, mac string, hostNS ns.NetNS) (
|
||||
if peerExists(peerName) && vethPeerName == "" {
|
||||
continue
|
||||
}
|
||||
return peerName, veth, fmt.Errorf("container veth name provided (%v) already exists", name)
|
||||
return peerName, veth, fmt.Errorf("container veth name (%q) peer provided (%q) already exists", name, peerName)
|
||||
default:
|
||||
return peerName, veth, fmt.Errorf("failed to make veth pair: %v", err)
|
||||
}
|
||||
|
@ -149,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
|
||||
})
|
||||
@ -180,9 +180,8 @@ var _ = Describe("Link", func() {
|
||||
It("returns useful error", func() {
|
||||
_ = containerNetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
_, _, err := ip.SetupVeth(containerVethName, mtu, "", hostNetNS)
|
||||
Expect(err.Error()).To(HavePrefix("container veth name provided"))
|
||||
Expect(err.Error()).To(HaveSuffix("already exists"))
|
||||
_, _, 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
|
||||
})
|
||||
})
|
||||
|
@ -50,3 +50,16 @@ func AddDefaultRoute(gw net.IP, dev netlink.Link) error {
|
||||
}
|
||||
return AddRoute(defNet, gw, dev)
|
||||
}
|
||||
|
||||
// IsIPNetZero check if the IPNet is "0.0.0.0/0" or "::/0"
|
||||
// This is needed as go-netlink replaces nil Dst with a '0' IPNet since
|
||||
// https://github.com/vishvananda/netlink/commit/acdc658b8613655ddb69f978e9fb4cf413e2b830
|
||||
func IsIPNetZero(ipnet *net.IPNet) bool {
|
||||
if ipnet == nil {
|
||||
return true
|
||||
}
|
||||
if ones, _ := ipnet.Mask.Size(); ones != 0 {
|
||||
return false
|
||||
}
|
||||
return ipnet.IP.Equal(net.IPv4zero) || ipnet.IP.Equal(net.IPv6zero)
|
||||
}
|
||||
|
@ -32,3 +32,7 @@ func ExecCheck(plugin string, netconf []byte) error {
|
||||
func ExecDel(plugin string, netconf []byte) error {
|
||||
return invoke.DelegateDel(context.TODO(), plugin, netconf, nil)
|
||||
}
|
||||
|
||||
func ExecStatus(plugin string, netconf []byte) error {
|
||||
return invoke.DelegateStatus(context.TODO(), plugin, netconf, nil)
|
||||
}
|
||||
|
@ -117,10 +117,27 @@ func ConfigureIface(ifName string, res *current.Result) error {
|
||||
Dst: &r.Dst,
|
||||
LinkIndex: link.Attrs().Index,
|
||||
Gw: gw,
|
||||
Priority: r.Priority,
|
||||
}
|
||||
|
||||
if r.Table != nil {
|
||||
route.Table = *r.Table
|
||||
}
|
||||
|
||||
if r.Scope != nil {
|
||||
route.Scope = netlink.Scope(*r.Scope)
|
||||
}
|
||||
|
||||
if r.Table != nil {
|
||||
route.Table = *r.Table
|
||||
}
|
||||
|
||||
if r.Scope != nil {
|
||||
route.Scope = netlink.Scope(*r.Scope)
|
||||
}
|
||||
|
||||
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)
|
||||
return fmt.Errorf("failed to add route '%v via %v dev %v metric %d (Scope: %v, Table: %d)': %v", r.Dst, gw, ifName, r.Priority, route.Scope, route.Table, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,9 +41,11 @@ func ipNetEqual(a, b *net.IPNet) bool {
|
||||
|
||||
var _ = Describe("ConfigureIface", func() {
|
||||
var originalNS ns.NetNS
|
||||
var ipv4, ipv6, routev4, routev6 *net.IPNet
|
||||
var ipv4, ipv6, routev4, routev6, routev4Scope *net.IPNet
|
||||
var ipgw4, ipgw6, routegwv4, routegwv6 net.IP
|
||||
var routeScope int
|
||||
var result *current.Result
|
||||
var routeTable int
|
||||
|
||||
BeforeEach(func() {
|
||||
// Create a new NetNS so we don't modify the host
|
||||
@ -54,11 +56,12 @@ var _ = Describe("ConfigureIface", func() {
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = LINK_NAME
|
||||
|
||||
// Add master
|
||||
err = netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: LINK_NAME,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
_, err = netlink.LinkByName(LINK_NAME)
|
||||
@ -77,6 +80,10 @@ var _ = Describe("ConfigureIface", func() {
|
||||
routegwv4 = net.ParseIP("1.2.3.5")
|
||||
Expect(routegwv4).NotTo(BeNil())
|
||||
|
||||
_, routev4Scope, err = net.ParseCIDR("1.2.3.4/32")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(routev4Scope).NotTo(BeNil())
|
||||
|
||||
ipgw4 = net.ParseIP("1.2.3.1")
|
||||
Expect(ipgw4).NotTo(BeNil())
|
||||
|
||||
@ -93,6 +100,9 @@ var _ = Describe("ConfigureIface", func() {
|
||||
ipgw6 = net.ParseIP("abcd:1234:ffff::1")
|
||||
Expect(ipgw6).NotTo(BeNil())
|
||||
|
||||
routeTable := 5000
|
||||
routeScope = 200
|
||||
|
||||
result = ¤t.Result{
|
||||
Interfaces: []*current.Interface{
|
||||
{
|
||||
@ -121,6 +131,8 @@ var _ = Describe("ConfigureIface", func() {
|
||||
Routes: []*types.Route{
|
||||
{Dst: *routev4, GW: routegwv4},
|
||||
{Dst: *routev6, GW: routegwv6},
|
||||
{Dst: *routev4, GW: routegwv4, Table: &routeTable},
|
||||
{Dst: *routev4Scope, Scope: &routeScope},
|
||||
},
|
||||
}
|
||||
})
|
||||
@ -162,7 +174,7 @@ var _ = Describe("ConfigureIface", func() {
|
||||
routes, err := netlink.RouteList(link, 0)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
var v4found, v6found bool
|
||||
var v4found, v6found, v4Scopefound bool
|
||||
for _, route := range routes {
|
||||
isv4 := route.Dst.IP.To4() != nil
|
||||
if isv4 && ipNetEqual(route.Dst, routev4) && route.Gw.Equal(routegwv4) {
|
||||
@ -171,13 +183,17 @@ var _ = Describe("ConfigureIface", func() {
|
||||
if !isv4 && ipNetEqual(route.Dst, routev6) && route.Gw.Equal(routegwv6) {
|
||||
v6found = true
|
||||
}
|
||||
if isv4 && ipNetEqual(route.Dst, routev4Scope) && int(route.Scope) == routeScope {
|
||||
v4Scopefound = true
|
||||
}
|
||||
|
||||
if v4found && v6found {
|
||||
if v4found && v6found && v4Scopefound {
|
||||
break
|
||||
}
|
||||
}
|
||||
Expect(v4found).To(BeTrue())
|
||||
Expect(v6found).To(BeTrue())
|
||||
Expect(v4Scopefound).To(BeTrue())
|
||||
|
||||
return nil
|
||||
})
|
||||
@ -201,7 +217,7 @@ var _ = Describe("ConfigureIface", func() {
|
||||
routes, err := netlink.RouteList(link, 0)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
var v4found, v6found bool
|
||||
var v4found, v6found, v4Tablefound bool
|
||||
for _, route := range routes {
|
||||
isv4 := route.Dst.IP.To4() != nil
|
||||
if isv4 && ipNetEqual(route.Dst, routev4) && route.Gw.Equal(ipgw4) {
|
||||
@ -218,6 +234,29 @@ var _ = Describe("ConfigureIface", func() {
|
||||
Expect(v4found).To(BeTrue())
|
||||
Expect(v6found).To(BeTrue())
|
||||
|
||||
// Need to read all tables, so cannot use RouteList
|
||||
routeFilter := &netlink.Route{
|
||||
Table: routeTable,
|
||||
}
|
||||
|
||||
routes, err = netlink.RouteListFiltered(netlink.FAMILY_ALL,
|
||||
routeFilter,
|
||||
netlink.RT_FILTER_TABLE)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
for _, route := range routes {
|
||||
isv4 := route.Dst.IP.To4() != nil
|
||||
if isv4 && ipNetEqual(route.Dst, routev4) && route.Gw.Equal(ipgw4) {
|
||||
v4Tablefound = true
|
||||
}
|
||||
|
||||
if v4Tablefound {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
Expect(v4Tablefound).To(BeTrue())
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
@ -30,7 +30,7 @@ const (
|
||||
)
|
||||
|
||||
type NftConfigurer interface {
|
||||
Apply(*nft.Config) error
|
||||
Apply(*nft.Config) (*nft.Config, error)
|
||||
Read(filterCommands ...string) (*nft.Config, error)
|
||||
}
|
||||
|
||||
@ -39,12 +39,16 @@ type SpoofChecker struct {
|
||||
macAddress string
|
||||
refID string
|
||||
configurer NftConfigurer
|
||||
rulestore *nft.Config
|
||||
}
|
||||
|
||||
type defaultNftConfigurer struct{}
|
||||
|
||||
func (dnc defaultNftConfigurer) Apply(cfg *nft.Config) error {
|
||||
return nft.ApplyConfig(cfg)
|
||||
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) {
|
||||
@ -59,7 +63,7 @@ func NewSpoofChecker(iface, macAddress, refID string) *SpoofChecker {
|
||||
}
|
||||
|
||||
func NewSpoofCheckerWithConfigurer(iface, macAddress, refID string, configurer NftConfigurer) *SpoofChecker {
|
||||
return &SpoofChecker{iface, macAddress, refID, configurer}
|
||||
return &SpoofChecker{iface, macAddress, refID, configurer, nil}
|
||||
}
|
||||
|
||||
// Setup applies nftables configuration to restrict traffic
|
||||
@ -88,7 +92,7 @@ func (sc *SpoofChecker) Setup() error {
|
||||
macChain := sc.macChain(ifaceChain.Name)
|
||||
baseConfig.AddChain(macChain)
|
||||
|
||||
if err := sc.configurer.Apply(baseConfig); err != nil {
|
||||
if _, err := sc.configurer.Apply(baseConfig); err != nil {
|
||||
return fmt.Errorf("failed to setup spoof-check: %v", err)
|
||||
}
|
||||
|
||||
@ -102,37 +106,51 @@ func (sc *SpoofChecker) Setup() error {
|
||||
rulesConfig.AddRule(sc.matchMacRule(macChain.Name))
|
||||
rulesConfig.AddRule(sc.dropRule(macChain.Name))
|
||||
|
||||
if err := sc.configurer.Apply(rulesConfig); err != nil {
|
||||
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()
|
||||
currentConfig, ifaceMatchRuleErr := sc.configurer.Read(listChainBridgeNatPrerouting()...)
|
||||
if ifaceMatchRuleErr == nil {
|
||||
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 := currentConfig.LookupRule(&ruleToFindExcludingStatements)
|
||||
if 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)
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "spoofcheck/teardown: unable to detect iface match rule for deletion: %+v", expectedRuleToFind)
|
||||
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()
|
||||
@ -140,7 +158,7 @@ func (sc *SpoofChecker) Teardown() error {
|
||||
regularChainsConfig.DeleteChain(sc.macChain(ifaceChain.Name))
|
||||
|
||||
var regularChainsErr error
|
||||
if err := sc.configurer.Apply(regularChainsConfig); err != nil {
|
||||
if _, err := sc.configurer.Apply(regularChainsConfig); err != nil {
|
||||
regularChainsErr = fmt.Errorf("failed to delete regular chains: %v", err)
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
package link_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/networkplumbing/go-nft/nft"
|
||||
@ -113,6 +114,25 @@ var _ = Describe("spoofcheck", func() {
|
||||
)))
|
||||
})
|
||||
})
|
||||
|
||||
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) {
|
||||
@ -274,23 +294,30 @@ type configurerStub struct {
|
||||
failFirstApplyConfig bool
|
||||
failSecondApplyConfig bool
|
||||
failReadConfig bool
|
||||
|
||||
applyReturnNil bool
|
||||
readCalled bool
|
||||
}
|
||||
|
||||
func (a *configurerStub) Apply(c *nft.Config) error {
|
||||
func (a *configurerStub) Apply(c *nft.Config) (*nft.Config, error) {
|
||||
a.applyCounter++
|
||||
if a.failFirstApplyConfig && a.applyCounter == 1 {
|
||||
return fmt.Errorf(errorFirstApplyText)
|
||||
return nil, errors.New(errorFirstApplyText)
|
||||
}
|
||||
if a.failSecondApplyConfig && a.applyCounter == 2 {
|
||||
return fmt.Errorf(errorSecondApplyText)
|
||||
return nil, errors.New(errorSecondApplyText)
|
||||
}
|
||||
a.applyConfig = append(a.applyConfig, c)
|
||||
return nil
|
||||
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 nil, errors.New(errorReadText)
|
||||
}
|
||||
return a.readConfig, nil
|
||||
}
|
||||
|
@ -13,10 +13,10 @@ The `ns.Do()` method provides **partial** control over network namespaces for yo
|
||||
|
||||
```go
|
||||
err = targetNs.Do(func(hostNs ns.NetNS) error {
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = "dummy0"
|
||||
dummy := &netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: "dummy0",
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
}
|
||||
return netlink.LinkAdd(dummy)
|
||||
})
|
||||
|
@ -31,6 +31,10 @@ func GetCurrentNS() (NetNS, error) {
|
||||
// return an unexpected network namespace.
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
return getCurrentNSNoLock()
|
||||
}
|
||||
|
||||
func getCurrentNSNoLock() (NetNS, error) {
|
||||
return GetNS(getCurrentThreadNetNSPath())
|
||||
}
|
||||
|
||||
@ -152,6 +156,54 @@ func GetNS(nspath string) (NetNS, error) {
|
||||
return &netNS{file: fd}, nil
|
||||
}
|
||||
|
||||
// Returns a new empty NetNS.
|
||||
// Calling Close() let the kernel garbage collect the network namespace.
|
||||
func TempNetNS() (NetNS, error) {
|
||||
var tempNS NetNS
|
||||
var err error
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
// Create the new namespace in a new goroutine so that if we later fail
|
||||
// to switch the namespace back to the original one, we can safely
|
||||
// leave the thread locked to die without a risk of the current thread
|
||||
// left lingering with incorrect namespace.
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
runtime.LockOSThread()
|
||||
|
||||
var threadNS NetNS
|
||||
// save a handle to current network namespace
|
||||
threadNS, err = getCurrentNSNoLock()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to open current namespace: %v", err)
|
||||
return
|
||||
}
|
||||
defer threadNS.Close()
|
||||
|
||||
// create the temporary network namespace
|
||||
err = unix.Unshare(unix.CLONE_NEWNET)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// get a handle to the temporary network namespace
|
||||
tempNS, err = getCurrentNSNoLock()
|
||||
|
||||
err2 := threadNS.Set()
|
||||
if err2 == nil {
|
||||
// Unlock the current thread only when we successfully switched back
|
||||
// to the original namespace; otherwise leave the thread locked which
|
||||
// will force the runtime to scrap the current thread, that is maybe
|
||||
// not as optimal but at least always safe to do.
|
||||
runtime.UnlockOSThread()
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
return tempNS, err
|
||||
}
|
||||
|
||||
func (ns *netNS) Path() string {
|
||||
return ns.file.Name()
|
||||
}
|
||||
@ -173,7 +225,7 @@ func (ns *netNS) Do(toRun func(NetNS) error) error {
|
||||
}
|
||||
|
||||
containedCall := func(hostNS NetNS) error {
|
||||
threadNS, err := GetCurrentNS()
|
||||
threadNS, err := getCurrentNSNoLock()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open current netns: %v", err)
|
||||
}
|
||||
|
@ -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))
|
||||
})
|
||||
|
||||
|
@ -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
|
||||
@ -87,6 +89,7 @@ func CmdCheck(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()
|
||||
@ -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()
|
||||
@ -110,3 +114,12 @@ func CmdDel(cniNetns, cniContainerID, cniIfname string, f func() error) error {
|
||||
func CmdDelWithArgs(args *skel.CmdArgs, f func() error) error {
|
||||
return CmdDel(args.Netns, args.ContainerID, args.IfName, f)
|
||||
}
|
||||
|
||||
func CmdStatus(f func() error) error {
|
||||
os.Setenv("CNI_COMMAND", "STATUS")
|
||||
os.Setenv("CNI_PATH", os.Getenv("PATH"))
|
||||
os.Setenv("CNI_NETNS_OVERRIDE", "1")
|
||||
defer envCleanup()
|
||||
|
||||
return f()
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ import (
|
||||
)
|
||||
|
||||
// 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"}
|
||||
var AllSpecVersions = [...]string{"0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0", "1.1.0"}
|
||||
|
||||
// SpecVersionHasIPVersion returns true if the given CNI specification version
|
||||
// includes the "version" field in the IP address elements
|
||||
@ -39,6 +39,13 @@ func SpecVersionHasCHECK(ver string) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// SpecVersionHasSTATUS returns true if the given CNI specification version
|
||||
// supports the STATUS command
|
||||
func SpecVersionHasSTATUS(ver string) bool {
|
||||
ok, _ := version.GreaterThanOrEqualTo(ver, "1.1.0")
|
||||
return ok
|
||||
}
|
||||
|
||||
// SpecVersionHasChaining returns true if the given CNI specification version
|
||||
// supports plugin chaining
|
||||
func SpecVersionHasChaining(ver string) bool {
|
||||
|
@ -51,7 +51,7 @@ func DeleteConntrackEntriesForDstIP(dstIP string, protocol uint8) error {
|
||||
filter.AddIP(netlink.ConntrackOrigDstIP, ip)
|
||||
filter.AddProtocol(protocol)
|
||||
|
||||
_, err := netlink.ConntrackDeleteFilter(netlink.ConntrackTable, family, filter)
|
||||
_, err := netlink.ConntrackDeleteFilters(netlink.ConntrackTable, family, filter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting connection tracking state for protocol: %d IP: %s, error: %v", protocol, ip, err)
|
||||
}
|
||||
@ -65,7 +65,7 @@ func DeleteConntrackEntriesForDstPort(port uint16, protocol uint8, family netlin
|
||||
filter.AddProtocol(protocol)
|
||||
filter.AddPort(netlink.ConntrackOrigDstPort, port)
|
||||
|
||||
_, err := netlink.ConntrackDeleteFilter(netlink.ConntrackTable, family, filter)
|
||||
_, err := netlink.ConntrackDeleteFilters(netlink.ConntrackTable, family, filter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting connection tracking state for protocol: %d Port: %d, error: %v", protocol, port, err)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
46
pkg/utils/netfilter.go
Normal file
46
pkg/utils/netfilter.go
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright 2023 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
"sigs.k8s.io/knftables"
|
||||
)
|
||||
|
||||
// SupportsIPTables tests whether the system supports using netfilter via the iptables API
|
||||
// (whether via "iptables-legacy" or "iptables-nft"). (Note that this returns true if it
|
||||
// is *possible* to use iptables; it does not test whether any other components on the
|
||||
// system are *actually* using iptables.)
|
||||
func SupportsIPTables() bool {
|
||||
ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
// We don't care whether the chain actually exists, only whether we can *check*
|
||||
// whether it exists.
|
||||
_, err = ipt.ChainExists("filter", "INPUT")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// SupportsNFTables tests whether the system supports using netfilter via the nftables API
|
||||
// (ie, not via "iptables-nft"). (Note that this returns true if it is *possible* to use
|
||||
// nftables; it does not test whether any other components on the system are *actually*
|
||||
// using nftables.)
|
||||
func SupportsNFTables() bool {
|
||||
// knftables.New() does sanity checks so we don't need any further test like in
|
||||
// the iptables case.
|
||||
_, err := knftables.New(knftables.IPv4Family, "supports_nftables_test")
|
||||
return err == nil
|
||||
}
|
52
pkg/utils/netfilter_test.go
Normal file
52
pkg/utils/netfilter_test.go
Normal file
@ -0,0 +1,52 @@
|
||||
// Copyright 2023 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("netfilter support", func() {
|
||||
When("it is available", func() {
|
||||
It("reports that iptables is supported", func() {
|
||||
Expect(SupportsIPTables()).To(BeTrue(), "This test should only fail if iptables is not available, but the test suite as a whole requires it to be available.")
|
||||
})
|
||||
It("reports that nftables is supported", func() {
|
||||
Expect(SupportsNFTables()).To(BeTrue(), "This test should only fail if nftables is not available, but the test suite as a whole requires it to be available.")
|
||||
})
|
||||
})
|
||||
|
||||
// These are Serial because os.Setenv has process-wide effect
|
||||
When("it is not available", Serial, func() {
|
||||
var origPath string
|
||||
BeforeEach(func() {
|
||||
origPath = os.Getenv("PATH")
|
||||
os.Setenv("PATH", "/does-not-exist")
|
||||
})
|
||||
AfterEach(func() {
|
||||
os.Setenv("PATH", origPath)
|
||||
})
|
||||
|
||||
It("reports that iptables is not supported", func() {
|
||||
Expect(SupportsIPTables()).To(BeFalse(), "found iptables outside of PATH??")
|
||||
})
|
||||
It("reports that nftables is not supported", func() {
|
||||
Expect(SupportsNFTables()).To(BeFalse(), "found nftables outside of PATH??")
|
||||
})
|
||||
})
|
||||
})
|
@ -48,11 +48,11 @@ var _ = Describe("Sysctl tests", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
testIfaceName = fmt.Sprintf("cnitest.%d", rand.Intn(100000))
|
||||
testLinkAttrs := netlink.NewLinkAttrs()
|
||||
testLinkAttrs.Name = testIfaceName
|
||||
testLinkAttrs.Namespace = netlink.NsFd(int(testNs.Fd()))
|
||||
testIface := &netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: testIfaceName,
|
||||
Namespace: netlink.NsFd(int(testNs.Fd())),
|
||||
},
|
||||
LinkAttrs: testLinkAttrs,
|
||||
}
|
||||
|
||||
err = netlink.LinkAdd(testIface)
|
||||
|
@ -1,135 +0,0 @@
|
||||
// Copyright 2021 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/d2g/dhcp4"
|
||||
"github.com/d2g/dhcp4client"
|
||||
)
|
||||
|
||||
const (
|
||||
MaxDHCPLen = 576
|
||||
)
|
||||
|
||||
// Send the Discovery Packet to the Broadcast Channel
|
||||
func DhcpSendDiscoverPacket(c *dhcp4client.Client, options dhcp4.Options) (dhcp4.Packet, error) {
|
||||
discoveryPacket := c.DiscoverPacket()
|
||||
|
||||
for opt, data := range options {
|
||||
discoveryPacket.AddOption(opt, data)
|
||||
}
|
||||
|
||||
discoveryPacket.PadToMinSize()
|
||||
return discoveryPacket, c.SendPacket(discoveryPacket)
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
for opt, data := range options {
|
||||
requestPacket.AddOption(opt, data)
|
||||
}
|
||||
|
||||
requestPacket.PadToMinSize()
|
||||
|
||||
return requestPacket, c.SendPacket(requestPacket)
|
||||
}
|
||||
|
||||
// Send Decline to the received acknowledgement.
|
||||
func DhcpSendDecline(c *dhcp4client.Client, acknowledgementPacket *dhcp4.Packet, options dhcp4.Options) (dhcp4.Packet, error) {
|
||||
declinePacket := c.DeclinePacket(acknowledgementPacket)
|
||||
|
||||
for opt, data := range options {
|
||||
declinePacket.AddOption(opt, data)
|
||||
}
|
||||
|
||||
declinePacket.PadToMinSize()
|
||||
|
||||
return declinePacket, c.SendPacket(declinePacket)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return false, discoveryPacket, err
|
||||
}
|
||||
|
||||
offerPacket, err := c.GetOffer(&discoveryPacket)
|
||||
if err != nil {
|
||||
return false, offerPacket, err
|
||||
}
|
||||
|
||||
requestPacket, err := DhcpSendRequest(c, options, &offerPacket)
|
||||
if err != nil {
|
||||
return false, requestPacket, err
|
||||
}
|
||||
|
||||
acknowledgement, err := c.GetAcknowledgement(&requestPacket)
|
||||
if err != nil {
|
||||
return false, acknowledgement, err
|
||||
}
|
||||
|
||||
acknowledgementOptions := acknowledgement.ParseOptions()
|
||||
if dhcp4.MessageType(acknowledgementOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.ACK {
|
||||
return false, acknowledgement, nil
|
||||
}
|
||||
|
||||
return true, acknowledgement, nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
for opt, data := range options {
|
||||
renewRequest.AddOption(opt, data)
|
||||
}
|
||||
|
||||
renewRequest.PadToMinSize()
|
||||
|
||||
err := c.SendPacket(renewRequest)
|
||||
if err != nil {
|
||||
return false, renewRequest, err
|
||||
}
|
||||
|
||||
newAcknowledgement, err := c.GetAcknowledgement(&renewRequest)
|
||||
if err != nil {
|
||||
return false, newAcknowledgement, err
|
||||
}
|
||||
|
||||
newAcknowledgementOptions := newAcknowledgement.ParseOptions()
|
||||
if dhcp4.MessageType(newAcknowledgementOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.ACK {
|
||||
return false, newAcknowledgement, nil
|
||||
}
|
||||
|
||||
return true, newAcknowledgement, nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
for opt, data := range options {
|
||||
release.AddOption(opt, data)
|
||||
}
|
||||
|
||||
release.PadToMinSize()
|
||||
|
||||
return c.SendPacket(release)
|
||||
}
|
@ -39,19 +39,21 @@ import (
|
||||
var errNoMoreTries = errors.New("no more tries")
|
||||
|
||||
type DHCP struct {
|
||||
mux sync.Mutex
|
||||
leases map[string]*DHCPLease
|
||||
hostNetnsPrefix string
|
||||
clientTimeout time.Duration
|
||||
clientResendMax time.Duration
|
||||
broadcast bool
|
||||
mux sync.Mutex
|
||||
leases map[string]*DHCPLease
|
||||
hostNetnsPrefix string
|
||||
clientTimeout time.Duration
|
||||
clientResendMax time.Duration
|
||||
clientResendTimeout time.Duration
|
||||
broadcast bool
|
||||
}
|
||||
|
||||
func newDHCP(clientTimeout, clientResendMax time.Duration) *DHCP {
|
||||
func newDHCP(clientTimeout, clientResendMax time.Duration, resendTimeout time.Duration) *DHCP {
|
||||
return &DHCP{
|
||||
leases: make(map[string]*DHCPLease),
|
||||
clientTimeout: clientTimeout,
|
||||
clientResendMax: clientResendMax,
|
||||
leases: make(map[string]*DHCPLease),
|
||||
clientTimeout: clientTimeout,
|
||||
clientResendMax: clientResendMax,
|
||||
clientResendTimeout: resendTimeout,
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,7 +76,7 @@ func (d *DHCP) Allocate(args *skel.CmdArgs, result *current.Result) error {
|
||||
return fmt.Errorf("error parsing netconf: %v", err)
|
||||
}
|
||||
|
||||
optsRequesting, optsProviding, err := prepareOptions(args.Args, conf.IPAM.ProvideOptions, conf.IPAM.RequestOptions)
|
||||
opts, err := prepareOptions(args.Args, conf.IPAM.ProvideOptions, conf.IPAM.RequestOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -89,8 +91,8 @@ func (d *DHCP) Allocate(args *skel.CmdArgs, result *current.Result) error {
|
||||
} else {
|
||||
hostNetns := d.hostNetnsPrefix + args.Netns
|
||||
l, err = AcquireLease(clientID, hostNetns, args.IfName,
|
||||
optsRequesting, optsProviding,
|
||||
d.clientTimeout, d.clientResendMax, d.broadcast)
|
||||
opts,
|
||||
d.clientTimeout, d.clientResendMax, d.clientResendTimeout, d.broadcast)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -109,6 +111,11 @@ func (d *DHCP) Allocate(args *skel.CmdArgs, result *current.Result) error {
|
||||
Gateway: l.Gateway(),
|
||||
}}
|
||||
result.Routes = l.Routes()
|
||||
if conf.IPAM.Priority != 0 {
|
||||
for _, r := range result.Routes {
|
||||
r.Priority = conf.IPAM.Priority
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -185,7 +192,8 @@ func getListener(socketPath string) (net.Listener, error) {
|
||||
|
||||
func runDaemon(
|
||||
pidfilePath, hostPrefix, socketPath string,
|
||||
dhcpClientTimeout time.Duration, resendMax time.Duration, broadcast bool,
|
||||
dhcpClientTimeout time.Duration, resendMax time.Duration, resendTimeout time.Duration,
|
||||
broadcast bool,
|
||||
) error {
|
||||
// since other goroutines (on separate threads) will change namespaces,
|
||||
// ensure the RPC server does not get scheduled onto those
|
||||
@ -220,7 +228,7 @@ func runDaemon(
|
||||
done <- true
|
||||
}()
|
||||
|
||||
dhcp := newDHCP(dhcpClientTimeout, resendMax)
|
||||
dhcp := newDHCP(dhcpClientTimeout, resendMax, resendTimeout)
|
||||
dhcp.hostNetnsPrefix = hostPrefix
|
||||
dhcp.broadcast = broadcast
|
||||
rpc.Register(dhcp)
|
||||
|
@ -61,13 +61,12 @@ var _ = Describe("DHCP Multiple Lease Operations", func() {
|
||||
})
|
||||
|
||||
// Start the DHCP server
|
||||
dhcpServerDone, err = dhcpServerStart(originalNS, 2, dhcpServerStopCh)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
dhcpServerDone = dhcpServerStart(originalNS, 2, dhcpServerStopCh)
|
||||
|
||||
// Start the DHCP client daemon
|
||||
dhcpPluginPath, err := exec.LookPath("dhcp")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
clientCmd = exec.Command(dhcpPluginPath, "daemon", "-socketpath", socketPath)
|
||||
clientCmd = exec.Command(dhcpPluginPath, "daemon", "-socketpath", socketPath, "--timeout", "2s", "--resendtimeout", "8s")
|
||||
err = clientCmd.Start()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(clientCmd.Process).NotTo(BeNil())
|
||||
|
@ -25,10 +25,6 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/d2g/dhcp4"
|
||||
"github.com/d2g/dhcp4server"
|
||||
"github.com/d2g/dhcp4server/leasepool"
|
||||
"github.com/d2g/dhcp4server/leasepool/memorypool"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/vishvananda/netlink"
|
||||
@ -48,31 +44,52 @@ func getTmpDir() (string, error) {
|
||||
return tmpDir, err
|
||||
}
|
||||
|
||||
func dhcpServerStart(netns ns.NetNS, numLeases int, stopCh <-chan bool) (*sync.WaitGroup, error) {
|
||||
// Add the expected IP to the pool
|
||||
lp := memorypool.MemoryPool{}
|
||||
type DhcpServer struct {
|
||||
cmd *exec.Cmd
|
||||
lock sync.Mutex
|
||||
|
||||
Expect(numLeases).To(BeNumerically(">", 0))
|
||||
// Currently tests only need at most 2
|
||||
Expect(numLeases).To(BeNumerically("<=", 2))
|
||||
startAddr net.IP
|
||||
endAddr net.IP
|
||||
leaseTime time.Duration
|
||||
}
|
||||
|
||||
// tests expect first lease to be at address 192.168.1.5
|
||||
for i := 5; i < numLeases+5; i++ {
|
||||
err := lp.AddLease(leasepool.Lease{IP: dhcp4.IPAdd(net.IPv4(192, 168, 1, byte(i)), 0)})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error adding IP to DHCP pool: %v", err)
|
||||
}
|
||||
func (s *DhcpServer) Serve() error {
|
||||
if err := s.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.cmd.Wait()
|
||||
}
|
||||
|
||||
dhcpServer, err := dhcp4server.New(
|
||||
net.IPv4(192, 168, 1, 1),
|
||||
&lp,
|
||||
dhcp4server.SetLocalAddr(net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 67}),
|
||||
dhcp4server.SetRemoteAddr(net.UDPAddr{IP: net.IPv4bcast, Port: 68}),
|
||||
dhcp4server.LeaseDuration(time.Minute*15),
|
||||
func (s *DhcpServer) Start() error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
s.cmd = exec.Command(
|
||||
"dnsmasq",
|
||||
"--no-daemon",
|
||||
"--dhcp-sequential-ip", // allocate IPs sequentially
|
||||
"--port=0", // disable DNS
|
||||
"--conf-file=-", // Do not read /etc/dnsmasq.conf
|
||||
fmt.Sprintf("--dhcp-range=%s,%s,%d", s.startAddr, s.endAddr, int(s.leaseTime.Seconds())),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create DHCP server: %v", err)
|
||||
s.cmd.Stdin = bytes.NewBufferString("")
|
||||
s.cmd.Stdout = os.Stdout
|
||||
s.cmd.Stderr = os.Stderr
|
||||
|
||||
return s.cmd.Start()
|
||||
}
|
||||
|
||||
func (s *DhcpServer) Stop() error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
return s.cmd.Process.Kill()
|
||||
}
|
||||
|
||||
func dhcpServerStart(netns ns.NetNS, numLeases int, stopCh <-chan bool) *sync.WaitGroup {
|
||||
dhcpServer := &DhcpServer{
|
||||
startAddr: net.IPv4(192, 168, 1, 5),
|
||||
endAddr: net.IPv4(192, 168, 1, 5+uint8(numLeases)-1),
|
||||
leaseTime: 5 * time.Minute,
|
||||
}
|
||||
|
||||
stopWg := sync.WaitGroup{}
|
||||
@ -84,9 +101,10 @@ func dhcpServerStart(netns ns.NetNS, numLeases int, stopCh <-chan bool) (*sync.W
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
|
||||
err = netns.Do(func(ns.NetNS) error {
|
||||
err := netns.Do(func(ns.NetNS) error {
|
||||
startWg.Done()
|
||||
if err := dhcpServer.ListenAndServe(); err != nil {
|
||||
|
||||
if err := dhcpServer.Serve(); err != nil {
|
||||
// Log, but don't trap errors; the server will
|
||||
// always report an error when stopped
|
||||
GinkgoT().Logf("DHCP server finished with error: %v", err)
|
||||
@ -103,12 +121,12 @@ func dhcpServerStart(netns ns.NetNS, numLeases int, stopCh <-chan bool) (*sync.W
|
||||
go func() {
|
||||
startWg.Done()
|
||||
<-stopCh
|
||||
dhcpServer.Shutdown()
|
||||
dhcpServer.Stop()
|
||||
stopWg.Done()
|
||||
}()
|
||||
startWg.Wait()
|
||||
|
||||
return &stopWg, nil
|
||||
return &stopWg
|
||||
}
|
||||
|
||||
const (
|
||||
@ -155,11 +173,11 @@ var _ = Describe("DHCP Operations", func() {
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = hostVethName
|
||||
err = netlink.LinkAdd(&netlink.Veth{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: hostVethName,
|
||||
},
|
||||
PeerName: contVethName,
|
||||
LinkAttrs: linkAttrs,
|
||||
PeerName: contVethName,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
@ -200,8 +218,7 @@ var _ = Describe("DHCP Operations", func() {
|
||||
})
|
||||
|
||||
// Start the DHCP server
|
||||
dhcpServerDone, err = dhcpServerStart(originalNS, 1, dhcpServerStopCh)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
dhcpServerDone = dhcpServerStart(originalNS, 1, dhcpServerStopCh)
|
||||
|
||||
// Start the DHCP client daemon
|
||||
dhcpPluginPath, err := exec.LookPath("dhcp")
|
||||
@ -332,9 +349,17 @@ var _ = Describe("DHCP Operations", func() {
|
||||
started.Done()
|
||||
started.Wait()
|
||||
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
return testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
copiedArgs := &skel.CmdArgs{
|
||||
ContainerID: args.ContainerID,
|
||||
Netns: args.Netns,
|
||||
IfName: args.IfName,
|
||||
StdinData: args.StdinData,
|
||||
Path: args.Path,
|
||||
Args: args.Args,
|
||||
}
|
||||
return cmdDel(copiedArgs)
|
||||
})
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@ -386,11 +411,11 @@ func dhcpSetupOriginalNS() (chan bool, string, ns.NetNS, ns.NetNS, error) {
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = hostBridgeName
|
||||
// Create bridge in the "host" (original) NS
|
||||
br = &netlink.Bridge{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: hostBridgeName,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
}
|
||||
|
||||
err = netlink.LinkAdd(br)
|
||||
@ -509,8 +534,7 @@ var _ = Describe("DHCP Lease Unavailable Operations", func() {
|
||||
})
|
||||
|
||||
// Start the DHCP server
|
||||
dhcpServerDone, err = dhcpServerStart(originalNS, 1, dhcpServerStopCh)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
dhcpServerDone = dhcpServerStart(originalNS, 1, dhcpServerStopCh)
|
||||
|
||||
// Start the DHCP client daemon
|
||||
dhcpPluginPath, err := exec.LookPath("dhcp")
|
||||
@ -520,7 +544,7 @@ var _ = Describe("DHCP Lease Unavailable Operations", func() {
|
||||
// `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")
|
||||
clientCmd = exec.Command(dhcpPluginPath, "daemon", "-socketpath", socketPath, "-timeout", "2s", "-resendmax", "8s", "--resendtimeout", "10s")
|
||||
|
||||
// copy dhcp client's stdout/stderr to test stdout
|
||||
var b bytes.Buffer
|
||||
|
@ -15,6 +15,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
@ -24,8 +25,8 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/d2g/dhcp4"
|
||||
"github.com/d2g/dhcp4client"
|
||||
dhcp4 "github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4/nclient4"
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
@ -35,8 +36,10 @@ import (
|
||||
// RFC 2131 suggests using exponential backoff, starting with 4sec
|
||||
// and randomized to +/- 1sec
|
||||
const (
|
||||
resendDelay0 = 4 * time.Second
|
||||
resendDelayMax = 62 * time.Second
|
||||
resendDelay0 = 4 * time.Second
|
||||
resendDelayMax = 62 * time.Second
|
||||
defaultLeaseTime = 60 * time.Minute
|
||||
defaultResendTimeout = 208 * time.Second // fast resend + backoff resend
|
||||
)
|
||||
|
||||
// To speed up the retry for first few failures, we retry without
|
||||
@ -60,34 +63,35 @@ const (
|
||||
|
||||
type DHCPLease struct {
|
||||
clientID string
|
||||
ack *dhcp4.Packet
|
||||
opts dhcp4.Options
|
||||
latestLease *nclient4.Lease
|
||||
link netlink.Link
|
||||
renewalTime time.Time
|
||||
rebindingTime time.Time
|
||||
expireTime time.Time
|
||||
timeout time.Duration
|
||||
resendMax time.Duration
|
||||
resendTimeout time.Duration
|
||||
broadcast bool
|
||||
stopping uint32
|
||||
stop chan struct{}
|
||||
check chan struct{}
|
||||
wg sync.WaitGroup
|
||||
cancelFunc context.CancelFunc
|
||||
ctx context.Context
|
||||
// list of requesting and providing options and if they are necessary / their value
|
||||
optsRequesting map[dhcp4.OptionCode]bool
|
||||
optsProviding map[dhcp4.OptionCode][]byte
|
||||
opts []dhcp4.Option
|
||||
}
|
||||
|
||||
var requestOptionsDefault = map[dhcp4.OptionCode]bool{
|
||||
dhcp4.OptionRouter: true,
|
||||
dhcp4.OptionSubnetMask: true,
|
||||
var requestOptionsDefault = []dhcp4.OptionCode{
|
||||
dhcp4.OptionRouter,
|
||||
dhcp4.OptionSubnetMask,
|
||||
}
|
||||
|
||||
func prepareOptions(cniArgs string, provideOptions []ProvideOption, requestOptions []RequestOption) (
|
||||
map[dhcp4.OptionCode]bool, map[dhcp4.OptionCode][]byte, error,
|
||||
[]dhcp4.Option, error,
|
||||
) {
|
||||
var optsRequesting map[dhcp4.OptionCode]bool
|
||||
var optsProviding map[dhcp4.OptionCode][]byte
|
||||
var opts []dhcp4.Option
|
||||
|
||||
var err error
|
||||
// parse CNI args
|
||||
cniArgsParsed := map[string]string{}
|
||||
@ -100,46 +104,51 @@ func prepareOptions(cniArgs string, provideOptions []ProvideOption, requestOptio
|
||||
|
||||
// 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)
|
||||
return 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)
|
||||
return nil, fmt.Errorf("value too long for option %q: %q", opt.Option, opt.Value)
|
||||
}
|
||||
optsProviding[optParsed] = []byte(opt.Value)
|
||||
opts = append(opts, dhcp4.Option{Code: optParsed, Value: dhcp4.String(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)
|
||||
return nil, fmt.Errorf("value too long for option %q from CNI_ARGS %q: %q", opt.Option, opt.ValueFromCNIArg, opt.Value)
|
||||
}
|
||||
optsProviding[optParsed] = []byte(value)
|
||||
opts = append(opts, dhcp4.Option{Code: optParsed, Value: dhcp4.String(value)})
|
||||
}
|
||||
}
|
||||
|
||||
// parse necessary options map
|
||||
optsRequesting = make(map[dhcp4.OptionCode]bool)
|
||||
var optsRequesting dhcp4.OptionCodeList
|
||||
skipRequireDefault := false
|
||||
for _, opt := range requestOptions {
|
||||
if opt.SkipDefault {
|
||||
skipRequireDefault = true
|
||||
}
|
||||
if opt.Option == "" {
|
||||
continue
|
||||
}
|
||||
optParsed, err = parseOptionName(string(opt.Option))
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Can not parse option %q: %w", opt.Option, err)
|
||||
return nil, fmt.Errorf("Can not parse option %q: %w", opt.Option, err)
|
||||
}
|
||||
optsRequesting[optParsed] = true
|
||||
optsRequesting.Add(optParsed)
|
||||
}
|
||||
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
|
||||
if !skipRequireDefault {
|
||||
for _, opt := range requestOptionsDefault {
|
||||
optsRequesting.Add(opt)
|
||||
}
|
||||
}
|
||||
return optsRequesting, optsProviding, err
|
||||
if len(optsRequesting) > 0 {
|
||||
opts = append(opts, dhcp4.Option{Code: dhcp4.OptionParameterRequestList, Value: optsRequesting})
|
||||
}
|
||||
|
||||
return opts, err
|
||||
}
|
||||
|
||||
// AcquireLease gets an DHCP lease and then maintains it in the background
|
||||
@ -147,19 +156,25 @@ func prepareOptions(cniArgs string, provideOptions []ProvideOption, requestOptio
|
||||
// calling DHCPLease.Stop()
|
||||
func AcquireLease(
|
||||
clientID, netns, ifName string,
|
||||
optsRequesting map[dhcp4.OptionCode]bool, optsProviding map[dhcp4.OptionCode][]byte,
|
||||
timeout, resendMax time.Duration, broadcast bool,
|
||||
opts []dhcp4.Option,
|
||||
timeout, resendMax time.Duration, resendTimeout time.Duration, broadcast bool,
|
||||
) (*DHCPLease, error) {
|
||||
errCh := make(chan error, 1)
|
||||
|
||||
ctx := context.Background()
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
||||
l := &DHCPLease{
|
||||
clientID: clientID,
|
||||
stop: make(chan struct{}),
|
||||
check: make(chan struct{}),
|
||||
timeout: timeout,
|
||||
resendMax: resendMax,
|
||||
broadcast: broadcast,
|
||||
optsRequesting: optsRequesting,
|
||||
optsProviding: optsProviding,
|
||||
clientID: clientID,
|
||||
stop: make(chan struct{}),
|
||||
check: make(chan struct{}),
|
||||
timeout: timeout,
|
||||
resendMax: resendMax,
|
||||
resendTimeout: resendTimeout,
|
||||
broadcast: broadcast,
|
||||
opts: opts,
|
||||
cancelFunc: cancel,
|
||||
ctx: ctx,
|
||||
}
|
||||
|
||||
log.Printf("%v: acquiring lease", clientID)
|
||||
@ -201,6 +216,7 @@ func AcquireLease(
|
||||
func (l *DHCPLease) Stop() {
|
||||
if atomic.CompareAndSwapUint32(&l.stopping, 0, 1) {
|
||||
close(l.stop)
|
||||
l.cancelFunc()
|
||||
}
|
||||
l.wg.Wait()
|
||||
}
|
||||
@ -209,92 +225,65 @@ 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 withClientID(clientID string) dhcp4.Modifier {
|
||||
return func(d *dhcp4.DHCPv4) {
|
||||
optClientID := []byte{0}
|
||||
optClientID = append(optClientID, []byte(clientID)...)
|
||||
d.Options.Update(dhcp4.OptClientIdentifier(optClientID))
|
||||
}
|
||||
}
|
||||
|
||||
func (l *DHCPLease) getAllOptions() dhcp4.Options {
|
||||
opts := l.getOptionsWithClientID()
|
||||
|
||||
for k, v := range l.optsProviding {
|
||||
opts[k] = v
|
||||
func withAllOptions(l *DHCPLease) dhcp4.Modifier {
|
||||
return func(d *dhcp4.DHCPv4) {
|
||||
for _, opt := range l.opts {
|
||||
d.Options.Update(opt)
|
||||
}
|
||||
}
|
||||
|
||||
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.timeout, l.broadcast)
|
||||
if (l.link.Attrs().Flags & net.FlagUp) != net.FlagUp {
|
||||
log.Printf("Link %q down. Attempting to set up", l.link.Attrs().Name)
|
||||
if err := netlink.LinkSetUp(l.link); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
c, err := newDHCPClient(l.link, l.timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
if (l.link.Attrs().Flags & net.FlagUp) != net.FlagUp {
|
||||
log.Printf("Link %q down. Attempting to set up", l.link.Attrs().Name)
|
||||
if err = netlink.LinkSetUp(l.link); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
opts := l.getAllOptions()
|
||||
|
||||
pkt, err := backoffRetry(l.resendMax, func() (*dhcp4.Packet, error) {
|
||||
ok, ack, err := DhcpRequest(c, opts)
|
||||
switch {
|
||||
case err != nil:
|
||||
return nil, err
|
||||
case !ok:
|
||||
return nil, fmt.Errorf("DHCP server NACK'd own offer")
|
||||
default:
|
||||
return &ack, nil
|
||||
}
|
||||
timeoutCtx, cancel := context.WithTimeoutCause(l.ctx, l.resendTimeout, errNoMoreTries)
|
||||
defer cancel()
|
||||
pkt, err := backoffRetry(timeoutCtx, l.resendMax, func() (*nclient4.Lease, error) {
|
||||
return c.Request(
|
||||
timeoutCtx,
|
||||
withClientID(l.clientID),
|
||||
withAllOptions(l),
|
||||
)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return l.commit(pkt)
|
||||
l.commit(pkt)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *DHCPLease) commit(ack *dhcp4.Packet) error {
|
||||
opts := ack.ParseOptions()
|
||||
func (l *DHCPLease) commit(lease *nclient4.Lease) {
|
||||
l.latestLease = lease
|
||||
ack := lease.ACK
|
||||
|
||||
leaseTime, err := parseLeaseTime(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rebindingTime, err := parseRebindingTime(opts)
|
||||
if err != nil || rebindingTime > leaseTime {
|
||||
// Per RFC 2131 Section 4.4.5, it should default to 85% of lease time
|
||||
rebindingTime = leaseTime * 85 / 100
|
||||
}
|
||||
|
||||
renewalTime, err := parseRenewalTime(opts)
|
||||
if err != nil || renewalTime > rebindingTime {
|
||||
// Per RFC 2131 Section 4.4.5, it should default to 50% of lease time
|
||||
renewalTime = leaseTime / 2
|
||||
}
|
||||
leaseTime := ack.IPAddressLeaseTime(defaultLeaseTime)
|
||||
rebindingTime := ack.IPAddressRebindingTime(leaseTime * 85 / 100)
|
||||
renewalTime := ack.IPAddressRenewalTime(leaseTime / 2)
|
||||
|
||||
now := time.Now()
|
||||
l.expireTime = now.Add(leaseTime)
|
||||
l.renewalTime = now.Add(renewalTime)
|
||||
l.rebindingTime = now.Add(rebindingTime)
|
||||
l.ack = ack
|
||||
l.opts = opts
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *DHCPLease) maintain() {
|
||||
@ -362,44 +351,40 @@ func (l *DHCPLease) downIface() {
|
||||
}
|
||||
|
||||
func (l *DHCPLease) renew() error {
|
||||
c, err := newDHCPClient(l.link, l.timeout, l.broadcast)
|
||||
c, err := newDHCPClient(l.link, l.timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
opts := l.getAllOptions()
|
||||
pkt, err := backoffRetry(l.resendMax, func() (*dhcp4.Packet, error) {
|
||||
ok, ack, err := DhcpRenew(c, *l.ack, opts)
|
||||
switch {
|
||||
case err != nil:
|
||||
return nil, err
|
||||
case !ok:
|
||||
return nil, fmt.Errorf("DHCP server did not renew lease")
|
||||
default:
|
||||
return &ack, nil
|
||||
}
|
||||
timeoutCtx, cancel := context.WithTimeoutCause(l.ctx, l.resendTimeout, errNoMoreTries)
|
||||
defer cancel()
|
||||
lease, err := backoffRetry(timeoutCtx, l.resendMax, func() (*nclient4.Lease, error) {
|
||||
return c.Renew(
|
||||
timeoutCtx,
|
||||
l.latestLease,
|
||||
withClientID(l.clientID),
|
||||
withAllOptions(l),
|
||||
)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.commit(pkt)
|
||||
l.commit(lease)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *DHCPLease) release() error {
|
||||
log.Printf("%v: releasing lease", l.clientID)
|
||||
|
||||
c, err := newDHCPClient(l.link, l.timeout, l.broadcast)
|
||||
c, err := newDHCPClient(l.link, l.timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
opts := l.getOptionsWithClientID()
|
||||
|
||||
if err = DhcpRelease(c, *l.ack, opts); err != nil {
|
||||
if err = c.Release(l.latestLease, withClientID(l.clientID)); err != nil {
|
||||
return fmt.Errorf("failed to send DHCPRELEASE")
|
||||
}
|
||||
|
||||
@ -407,33 +392,47 @@ func (l *DHCPLease) release() error {
|
||||
}
|
||||
|
||||
func (l *DHCPLease) IPNet() (*net.IPNet, error) {
|
||||
mask := parseSubnetMask(l.opts)
|
||||
ack := l.latestLease.ACK
|
||||
|
||||
mask := ack.SubnetMask()
|
||||
if mask == nil {
|
||||
return nil, fmt.Errorf("DHCP option Subnet Mask not found in DHCPACK")
|
||||
}
|
||||
|
||||
return &net.IPNet{
|
||||
IP: l.ack.YIAddr(),
|
||||
IP: ack.YourIPAddr,
|
||||
Mask: mask,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *DHCPLease) Gateway() net.IP {
|
||||
return parseRouter(l.opts)
|
||||
ack := l.latestLease.ACK
|
||||
gws := ack.Router()
|
||||
if len(gws) > 0 {
|
||||
return gws[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *DHCPLease) Routes() []*types.Route {
|
||||
routes := []*types.Route{}
|
||||
|
||||
ack := l.latestLease.ACK
|
||||
|
||||
// RFC 3442 states that if Classless Static Routes (option 121)
|
||||
// exist, we ignore Static Routes (option 33) and the Router/Gateway.
|
||||
opt121Routes := parseCIDRRoutes(l.opts)
|
||||
opt121Routes := ack.ClasslessStaticRoute()
|
||||
if len(opt121Routes) > 0 {
|
||||
return append(routes, opt121Routes...)
|
||||
for _, r := range opt121Routes {
|
||||
routes = append(routes, &types.Route{Dst: *r.Dest, GW: r.Router})
|
||||
}
|
||||
return routes
|
||||
}
|
||||
|
||||
// Append Static Routes
|
||||
routes = append(routes, parseRoutes(l.opts)...)
|
||||
if ack.Options.Has(dhcp4.OptionStaticRoutingTable) {
|
||||
routes = append(routes, parseRoutes(ack.Options.Get(dhcp4.OptionStaticRoutingTable))...)
|
||||
}
|
||||
|
||||
// The CNI spec says even if there is a gateway specified, we must
|
||||
// add a default route in the routes section.
|
||||
@ -450,7 +449,7 @@ func jitter(span time.Duration) time.Duration {
|
||||
return time.Duration(float64(span) * (2.0*rand.Float64() - 1.0))
|
||||
}
|
||||
|
||||
func backoffRetry(resendMax time.Duration, f func() (*dhcp4.Packet, error)) (*dhcp4.Packet, error) {
|
||||
func backoffRetry(ctx context.Context, resendMax time.Duration, f func() (*nclient4.Lease, error)) (*nclient4.Lease, error) {
|
||||
baseDelay := resendDelay0
|
||||
var sleepTime time.Duration
|
||||
fastRetryLimit := resendFastMax
|
||||
@ -471,33 +470,23 @@ func backoffRetry(resendMax time.Duration, f func() (*dhcp4.Packet, error)) (*dh
|
||||
|
||||
log.Printf("retrying in %f seconds", sleepTime.Seconds())
|
||||
|
||||
time.Sleep(sleepTime)
|
||||
|
||||
// only adjust delay time if we are in normal backoff stage
|
||||
if baseDelay < resendMax && fastRetryLimit == 0 {
|
||||
baseDelay *= 2
|
||||
} else if fastRetryLimit == 0 { // only break if we are at normal delay
|
||||
break
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, context.Cause(ctx)
|
||||
case <-time.After(sleepTime):
|
||||
// only adjust delay time if we are in normal backoff stage
|
||||
if baseDelay < resendMax && fastRetryLimit == 0 {
|
||||
baseDelay *= 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errNoMoreTries
|
||||
}
|
||||
|
||||
func newDHCPClient(
|
||||
link netlink.Link,
|
||||
timeout time.Duration,
|
||||
broadcast bool,
|
||||
) (*dhcp4client.Client, error) {
|
||||
pktsock, err := dhcp4client.NewPacketSock(link.Attrs().Index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dhcp4client.New(
|
||||
dhcp4client.HardwareAddr(link.Attrs().HardwareAddr),
|
||||
dhcp4client.Timeout(timeout),
|
||||
dhcp4client.Broadcast(broadcast),
|
||||
dhcp4client.Connection(pktsock),
|
||||
)
|
||||
clientOpts ...nclient4.ClientOpt,
|
||||
) (*nclient4.Client, error) {
|
||||
clientOpts = append(clientOpts, nclient4.WithTimeout(timeout))
|
||||
return nclient4.New(link.Attrs().Name, clientOpts...)
|
||||
}
|
||||
|
@ -51,6 +51,8 @@ type IPAMConfig struct {
|
||||
// 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"`
|
||||
// The metric of routes
|
||||
Priority int `json:"priority,omitempty"`
|
||||
}
|
||||
|
||||
// DHCPOption represents a DHCP option. It can be a number, or a string defined in manual dhcp-options(5).
|
||||
@ -78,25 +80,33 @@ func main() {
|
||||
var broadcast bool
|
||||
var timeout time.Duration
|
||||
var resendMax time.Duration
|
||||
var resendTimeout 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.DurationVar(&timeout, "timeout", 10*time.Second, "optional dhcp client timeout duration for each request")
|
||||
daemonFlags.DurationVar(&resendMax, "resendmax", resendDelayMax, "optional dhcp client max resend delay between requests")
|
||||
daemonFlags.DurationVar(&resendTimeout, "resendtimeout", defaultResendTimeout, "optional dhcp client resend timeout, no more retries after this timeout")
|
||||
daemonFlags.Parse(os.Args[2:])
|
||||
|
||||
if socketPath == "" {
|
||||
socketPath = defaultSocketPath
|
||||
}
|
||||
|
||||
if err := runDaemon(pidfilePath, hostPrefix, socketPath, timeout, resendMax, broadcast); err != nil {
|
||||
if err := runDaemon(pidfilePath, hostPrefix, socketPath, timeout, resendMax, resendTimeout, broadcast); err != nil {
|
||||
log.Print(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("dhcp"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
/* FIXME GC */
|
||||
/* FIXME Status */
|
||||
}, version.All, bv.BuildString("dhcp"))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,13 +15,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/d2g/dhcp4"
|
||||
dhcp4 "github.com/insomniacslk/dhcp/dhcpv4"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
)
|
||||
@ -31,8 +29,8 @@ var optionNameToID = map[string]dhcp4.OptionCode{
|
||||
"subnet-mask": dhcp4.OptionSubnetMask,
|
||||
"routers": dhcp4.OptionRouter,
|
||||
"host-name": dhcp4.OptionHostName,
|
||||
"user-class": dhcp4.OptionUserClass,
|
||||
"vendor-class-identifier": dhcp4.OptionVendorClassIdentifier,
|
||||
"user-class": dhcp4.OptionUserClassInformation,
|
||||
"vendor-class-identifier": dhcp4.OptionClassIdentifier,
|
||||
}
|
||||
|
||||
func parseOptionName(option string) (dhcp4.OptionCode, error) {
|
||||
@ -41,18 +39,9 @@ func parseOptionName(option string) (dhcp4.OptionCode, error) {
|
||||
}
|
||||
i, err := strconv.ParseUint(option, 10, 8)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("Can not parse option: %w", err)
|
||||
return dhcp4.OptionPad, 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 {
|
||||
return net.IP(opts)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return dhcp4.GenericOptionCode(i), nil
|
||||
}
|
||||
|
||||
func classfulSubnet(sn net.IP) net.IPNet {
|
||||
@ -62,100 +51,22 @@ func classfulSubnet(sn net.IP) net.IPNet {
|
||||
}
|
||||
}
|
||||
|
||||
func parseRoutes(opts dhcp4.Options) []*types.Route {
|
||||
func parseRoutes(opt []byte) []*types.Route {
|
||||
// StaticRoutes format: pairs of:
|
||||
// Dest = 4 bytes; Classful IP subnet
|
||||
// Router = 4 bytes; IP address of router
|
||||
|
||||
routes := []*types.Route{}
|
||||
if opt, ok := opts[dhcp4.OptionStaticRoute]; ok {
|
||||
for len(opt) >= 8 {
|
||||
sn := opt[0:4]
|
||||
r := opt[4:8]
|
||||
rt := &types.Route{
|
||||
Dst: classfulSubnet(sn),
|
||||
GW: r,
|
||||
}
|
||||
routes = append(routes, rt)
|
||||
opt = opt[8:]
|
||||
for len(opt) >= 8 {
|
||||
sn := opt[0:4]
|
||||
r := opt[4:8]
|
||||
rt := &types.Route{
|
||||
Dst: classfulSubnet(sn),
|
||||
GW: r,
|
||||
}
|
||||
routes = append(routes, rt)
|
||||
opt = opt[8:]
|
||||
}
|
||||
|
||||
return routes
|
||||
}
|
||||
|
||||
func parseCIDRRoutes(opts dhcp4.Options) []*types.Route {
|
||||
// See RFC4332 for format (http://tools.ietf.org/html/rfc3442)
|
||||
|
||||
routes := []*types.Route{}
|
||||
if opt, ok := opts[dhcp4.OptionClasslessRouteFormat]; ok {
|
||||
for len(opt) >= 5 {
|
||||
width := int(opt[0])
|
||||
if width > 32 {
|
||||
// error: can't have more than /32
|
||||
return nil
|
||||
}
|
||||
// network bits are compacted to avoid zeros
|
||||
octets := 0
|
||||
if width > 0 {
|
||||
octets = (width-1)/8 + 1
|
||||
}
|
||||
|
||||
if len(opt) < 1+octets+4 {
|
||||
// error: too short
|
||||
return nil
|
||||
}
|
||||
|
||||
sn := make([]byte, 4)
|
||||
copy(sn, opt[1:octets+1])
|
||||
|
||||
gw := net.IP(opt[octets+1 : octets+5])
|
||||
|
||||
rt := &types.Route{
|
||||
Dst: net.IPNet{
|
||||
IP: net.IP(sn),
|
||||
Mask: net.CIDRMask(width, 32),
|
||||
},
|
||||
GW: gw,
|
||||
}
|
||||
routes = append(routes, rt)
|
||||
|
||||
opt = opt[octets+5:]
|
||||
}
|
||||
}
|
||||
return routes
|
||||
}
|
||||
|
||||
func parseSubnetMask(opts dhcp4.Options) net.IPMask {
|
||||
mask, ok := opts[dhcp4.OptionSubnetMask]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return net.IPMask(mask)
|
||||
}
|
||||
|
||||
func parseDuration(opts dhcp4.Options, code dhcp4.OptionCode, optName string) (time.Duration, error) {
|
||||
val, ok := opts[code]
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("option %v not found", optName)
|
||||
}
|
||||
if len(val) != 4 {
|
||||
return 0, fmt.Errorf("option %v is not 4 bytes", optName)
|
||||
}
|
||||
|
||||
secs := binary.BigEndian.Uint32(val)
|
||||
return time.Duration(secs) * time.Second, nil
|
||||
}
|
||||
|
||||
func parseLeaseTime(opts dhcp4.Options) (time.Duration, error) {
|
||||
return parseDuration(opts, dhcp4.OptionIPAddressLeaseTime, "LeaseTime")
|
||||
}
|
||||
|
||||
func parseRenewalTime(opts dhcp4.Options) (time.Duration, error) {
|
||||
return parseDuration(opts, dhcp4.OptionRenewalTimeValue, "RenewalTime")
|
||||
}
|
||||
|
||||
func parseRebindingTime(opts dhcp4.Options) (time.Duration, error) {
|
||||
return parseDuration(opts, dhcp4.OptionRebindingTimeValue, "RebindingTime")
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/d2g/dhcp4"
|
||||
dhcp4 "github.com/insomniacslk/dhcp/dhcpv4"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
)
|
||||
@ -61,17 +61,8 @@ func validateRoutes(t *testing.T, routes []*types.Route) {
|
||||
}
|
||||
|
||||
func TestParseRoutes(t *testing.T) {
|
||||
opts := make(dhcp4.Options)
|
||||
opts[dhcp4.OptionStaticRoute] = []byte{10, 0, 0, 0, 10, 1, 2, 3, 192, 168, 1, 0, 192, 168, 2, 3}
|
||||
routes := parseRoutes(opts)
|
||||
|
||||
validateRoutes(t, routes)
|
||||
}
|
||||
|
||||
func TestParseCIDRRoutes(t *testing.T) {
|
||||
opts := make(dhcp4.Options)
|
||||
opts[dhcp4.OptionClasslessRouteFormat] = []byte{8, 10, 10, 1, 2, 3, 24, 192, 168, 1, 192, 168, 2, 3}
|
||||
routes := parseCIDRRoutes(opts)
|
||||
data := []byte{10, 0, 0, 0, 10, 1, 2, 3, 192, 168, 1, 0, 192, 168, 2, 3}
|
||||
routes := parseRoutes(data)
|
||||
|
||||
validateRoutes(t, routes)
|
||||
}
|
||||
@ -87,10 +78,10 @@ func TestParseOptionName(t *testing.T) {
|
||||
"hostname", "host-name", dhcp4.OptionHostName, false,
|
||||
},
|
||||
{
|
||||
"hostname in number", "12", dhcp4.OptionHostName, false,
|
||||
"hostname in number", "12", dhcp4.GenericOptionCode(12), false,
|
||||
},
|
||||
{
|
||||
"random string", "doNotparseMe", 0, true,
|
||||
"random string", "doNotparseMe", dhcp4.OptionPad, true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
@ -60,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, 0o644)
|
||||
f, err := os.OpenFile(fname, os.O_RDWR|os.O_EXCL|os.O_CREATE, 0o600)
|
||||
if os.IsExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
@ -78,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 = os.WriteFile(ipfile, []byte(ip.String()), 0o644)
|
||||
err = os.WriteFile(ipfile, []byte(ip.String()), 0o600)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
@ -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,6 +15,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
@ -29,7 +30,13 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("host-local"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
/* FIXME GC */
|
||||
/* FIXME Status */
|
||||
}, version.All, bv.BuildString("host-local"))
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
@ -124,7 +131,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
for _, ip := range requestedIPs {
|
||||
errstr = errstr + " " + ip.String()
|
||||
}
|
||||
return fmt.Errorf(errstr)
|
||||
return errors.New(errstr)
|
||||
}
|
||||
|
||||
result.Routes = ipamConf.Routes
|
||||
@ -145,18 +152,18 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
defer store.Close()
|
||||
|
||||
// Loop through all ranges, releasing all IPs, even if an error occurs
|
||||
var errors []string
|
||||
var errs []string
|
||||
for idx, rangeset := range ipamConf.Ranges {
|
||||
ipAllocator := allocator.NewIPAllocator(&rangeset, store, idx)
|
||||
|
||||
err := ipAllocator.Release(args.ContainerID, args.IfName)
|
||||
if err != nil {
|
||||
errors = append(errors, err.Error())
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if errors != nil {
|
||||
return fmt.Errorf(strings.Join(errors, ";"))
|
||||
if errs != nil {
|
||||
return errors.New(strings.Join(errs, ";"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -68,7 +68,13 @@ type Address struct {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("static"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
/* FIXME GC */
|
||||
/* FIXME Status */
|
||||
}, version.All, bv.BuildString("static"))
|
||||
}
|
||||
|
||||
func loadNetConf(bytes []byte) (*types.NetConf, string, error) {
|
||||
|
@ -35,7 +35,6 @@ import (
|
||||
"github.com/containernetworking/plugins/pkg/ipam"
|
||||
"github.com/containernetworking/plugins/pkg/link"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/utils"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
"github.com/containernetworking/plugins/pkg/utils/sysctl"
|
||||
)
|
||||
@ -47,19 +46,22 @@ const defaultBrName = "cni0"
|
||||
|
||||
type NetConf struct {
|
||||
types.NetConf
|
||||
BrName string `json:"bridge"`
|
||||
IsGW bool `json:"isGateway"`
|
||||
IsDefaultGW bool `json:"isDefaultGateway"`
|
||||
ForceAddress bool `json:"forceAddress"`
|
||||
IPMasq bool `json:"ipMasq"`
|
||||
MTU int `json:"mtu"`
|
||||
HairpinMode bool `json:"hairpinMode"`
|
||||
PromiscMode bool `json:"promiscMode"`
|
||||
Vlan int `json:"vlan"`
|
||||
VlanTrunk []*VlanTrunk `json:"vlanTrunk,omitempty"`
|
||||
PreserveDefaultVlan bool `json:"preserveDefaultVlan"`
|
||||
MacSpoofChk bool `json:"macspoofchk,omitempty"`
|
||||
EnableDad bool `json:"enabledad,omitempty"`
|
||||
BrName string `json:"bridge"`
|
||||
IsGW bool `json:"isGateway"`
|
||||
IsDefaultGW bool `json:"isDefaultGateway"`
|
||||
ForceAddress bool `json:"forceAddress"`
|
||||
IPMasq bool `json:"ipMasq"`
|
||||
IPMasqBackend *string `json:"ipMasqBackend,omitempty"`
|
||||
MTU int `json:"mtu"`
|
||||
HairpinMode bool `json:"hairpinMode"`
|
||||
PromiscMode bool `json:"promiscMode"`
|
||||
Vlan int `json:"vlan"`
|
||||
VlanTrunk []*VlanTrunk `json:"vlanTrunk,omitempty"`
|
||||
PreserveDefaultVlan bool `json:"preserveDefaultVlan"`
|
||||
MacSpoofChk bool `json:"macspoofchk,omitempty"`
|
||||
EnableDad bool `json:"enabledad,omitempty"`
|
||||
DisableContainerInterface bool `json:"disableContainerInterface,omitempty"`
|
||||
PortIsolation bool `json:"portIsolation,omitempty"`
|
||||
|
||||
Args struct {
|
||||
Cni BridgeArgs `json:"cni,omitempty"`
|
||||
@ -334,16 +336,11 @@ func bridgeByName(name string) (*netlink.Bridge, error) {
|
||||
}
|
||||
|
||||
func ensureBridge(brName string, mtu int, promiscMode, vlanFiltering bool) (*netlink.Bridge, error) {
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = brName
|
||||
linkAttrs.MTU = mtu
|
||||
br := &netlink.Bridge{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: brName,
|
||||
MTU: mtu,
|
||||
// Let kernel use default txqueuelen; leaving it unset
|
||||
// means 0, and a zero-length TX queue messes up FIFO
|
||||
// traffic shapers which use TX queue length as the
|
||||
// default packet limit
|
||||
TxQLen: -1,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
}
|
||||
if vlanFiltering {
|
||||
br.VlanFiltering = &vlanFiltering
|
||||
@ -391,7 +388,7 @@ func ensureVlanInterface(br *netlink.Bridge, vlanID int, preserveDefaultVlan boo
|
||||
return nil, fmt.Errorf("faild to find host namespace: %v", err)
|
||||
}
|
||||
|
||||
_, brGatewayIface, err := setupVeth(hostNS, br, name, br.MTU, false, vlanID, nil, preserveDefaultVlan, "")
|
||||
_, brGatewayIface, err := setupVeth(hostNS, br, name, br.MTU, false, vlanID, nil, preserveDefaultVlan, "", false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("faild to create vlan gateway %q: %v", name, err)
|
||||
}
|
||||
@ -410,7 +407,18 @@ func ensureVlanInterface(br *netlink.Bridge, vlanID int, preserveDefaultVlan boo
|
||||
return brGatewayVeth, nil
|
||||
}
|
||||
|
||||
func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairpinMode bool, vlanID int, vlans []int, preserveDefaultVlan bool, mac string) (*current.Interface, *current.Interface, error) {
|
||||
func setupVeth(
|
||||
netns ns.NetNS,
|
||||
br *netlink.Bridge,
|
||||
ifName string,
|
||||
mtu int,
|
||||
hairpinMode bool,
|
||||
vlanID int,
|
||||
vlans []int,
|
||||
preserveDefaultVlan bool,
|
||||
mac string,
|
||||
portIsolation bool,
|
||||
) (*current.Interface, *current.Interface, error) {
|
||||
contIface := ¤t.Interface{}
|
||||
hostIface := ¤t.Interface{}
|
||||
|
||||
@ -447,6 +455,11 @@ func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairp
|
||||
return nil, nil, fmt.Errorf("failed to setup hairpin mode for %v: %v", hostVeth.Attrs().Name, err)
|
||||
}
|
||||
|
||||
// set isolation mode
|
||||
if err = netlink.LinkSetIsolated(hostVeth, portIsolation); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to set isolated on for %v: %v", hostVeth.Attrs().Name, err)
|
||||
}
|
||||
|
||||
if (vlanID != 0 || len(vlans) > 0) && !preserveDefaultVlan {
|
||||
err = removeDefaultVlan(hostVeth)
|
||||
if err != nil {
|
||||
@ -530,6 +543,10 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
|
||||
isLayer3 := n.IPAM.Type != ""
|
||||
|
||||
if isLayer3 && n.DisableContainerInterface {
|
||||
return fmt.Errorf("cannot use IPAM when DisableContainerInterface flag is set")
|
||||
}
|
||||
|
||||
if n.IsDefaultGW {
|
||||
n.IsGW = true
|
||||
}
|
||||
@ -549,7 +566,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
defer netns.Close()
|
||||
|
||||
hostInterface, containerInterface, err := setupVeth(netns, br, args.IfName, n.MTU, n.HairpinMode, n.Vlan, n.vlans, n.PreserveDefaultVlan, n.mac)
|
||||
hostInterface, containerInterface, err := setupVeth(netns, br, args.IfName, n.MTU, n.HairpinMode, n.Vlan, n.vlans, n.PreserveDefaultVlan, n.mac, n.PortIsolation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -629,14 +646,10 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
if n.IsGW {
|
||||
var firstV4Addr net.IP
|
||||
var vlanInterface *current.Interface
|
||||
// Set the IP address(es) on the bridge and enable forwarding
|
||||
for _, gws := range []*gwInfo{gwsV4, gwsV6} {
|
||||
for _, gw := range gws.gws {
|
||||
if gw.IP.To4() != nil && firstV4Addr == nil {
|
||||
firstV4Addr = gw.IP
|
||||
}
|
||||
if n.Vlan != 0 {
|
||||
vlanIface, err := ensureVlanInterface(br, n.Vlan, n.PreserveDefaultVlan)
|
||||
if err != nil {
|
||||
@ -672,20 +685,21 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
if n.IPMasq {
|
||||
chain := utils.FormatChainName(n.Name, args.ContainerID)
|
||||
comment := utils.FormatComment(n.Name, args.ContainerID)
|
||||
ipns := []*net.IPNet{}
|
||||
for _, ipc := range result.IPs {
|
||||
if err = ip.SetupIPMasq(&ipc.Address, chain, comment); err != nil {
|
||||
return err
|
||||
}
|
||||
ipns = append(ipns, &ipc.Address)
|
||||
}
|
||||
if err = ip.SetupIPMasqForNetworks(n.IPMasqBackend, ipns, n.Name, args.IfName, args.ContainerID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else if !n.DisableContainerInterface {
|
||||
if err := netns.Do(func(_ ns.NetNS) error {
|
||||
link, err := netlink.LinkByName(args.IfName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to retrieve link: %v", err)
|
||||
}
|
||||
|
||||
// If layer 2 we still need to set the container veth to up
|
||||
if err = netlink.LinkSetUp(link); err != nil {
|
||||
return fmt.Errorf("failed to set %q up: %v", args.IfName, err)
|
||||
@ -696,23 +710,28 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
}
|
||||
|
||||
var hostVeth netlink.Link
|
||||
hostVeth, err := netlink.LinkByName(hostInterface.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check bridge port state
|
||||
retries := []int{0, 50, 500, 1000, 1000}
|
||||
for idx, sleep := range retries {
|
||||
time.Sleep(time.Duration(sleep) * time.Millisecond)
|
||||
if !n.DisableContainerInterface {
|
||||
// check bridge port state
|
||||
retries := []int{0, 50, 500, 1000, 1000}
|
||||
for idx, sleep := range retries {
|
||||
time.Sleep(time.Duration(sleep) * time.Millisecond)
|
||||
|
||||
hostVeth, err = netlink.LinkByName(hostInterface.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hostVeth.Attrs().OperState == netlink.OperUp {
|
||||
break
|
||||
}
|
||||
hostVeth, err = netlink.LinkByName(hostInterface.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hostVeth.Attrs().OperState == netlink.OperUp {
|
||||
break
|
||||
}
|
||||
|
||||
if idx == len(retries)-1 {
|
||||
return fmt.Errorf("bridge port in error state: %s", hostVeth.Attrs().OperState)
|
||||
if idx == len(retries)-1 {
|
||||
return fmt.Errorf("bridge port in error state: %s", hostVeth.Attrs().OperState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -733,7 +752,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
// Use incoming DNS settings if provided, otherwise use the
|
||||
// settings that were already configued by the IPAM plugin
|
||||
// settings that were already configured by the IPAM plugin
|
||||
if dnsConfSet(n.DNS) {
|
||||
result.DNS = n.DNS
|
||||
}
|
||||
@ -783,7 +802,6 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
// if NetNs is passed down by the Cloud Orchestration Engine, or if it called multiple times
|
||||
// so don't return an error if the device is already removed.
|
||||
@ -808,12 +826,8 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
if isLayer3 && n.IPMasq {
|
||||
chain := utils.FormatChainName(n.Name, args.ContainerID)
|
||||
comment := utils.FormatComment(n.Name, args.ContainerID)
|
||||
for _, ipn := range ipnets {
|
||||
if err := ip.TeardownIPMasq(ipn, chain, comment); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ip.TeardownIPMasqForNetworks(ipnets, n.Name, args.IfName, args.ContainerID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@ -821,7 +835,13 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("bridge"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
Status: cmdStatus,
|
||||
/* FIXME GC */
|
||||
}, version.All, bv.BuildString("bridge"))
|
||||
}
|
||||
|
||||
type cniBridgeIf struct {
|
||||
@ -1082,3 +1102,18 @@ func cmdCheck(args *skel.CmdArgs) error {
|
||||
func uniqueID(containerID, cniIface string) string {
|
||||
return containerID + "-" + cniIface
|
||||
}
|
||||
|
||||
func cmdStatus(args *skel.CmdArgs) error {
|
||||
conf := NetConf{}
|
||||
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
|
||||
return fmt.Errorf("failed to load netconf: %w", err)
|
||||
}
|
||||
|
||||
if conf.IPAM.Type != "" {
|
||||
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
@ -27,6 +28,7 @@ import (
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/vishvananda/netlink"
|
||||
"github.com/vishvananda/netlink/nl"
|
||||
"sigs.k8s.io/knftables"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
@ -77,11 +79,15 @@ type testCase struct {
|
||||
vlanTrunk []*VlanTrunk
|
||||
removeDefaultVlan bool
|
||||
ipMasq bool
|
||||
ipMasqBackend string
|
||||
macspoofchk bool
|
||||
AddErr020 string
|
||||
DelErr020 string
|
||||
AddErr010 string
|
||||
DelErr010 string
|
||||
disableContIface bool
|
||||
portIsolation bool
|
||||
|
||||
AddErr020 string
|
||||
DelErr020 string
|
||||
AddErr010 string
|
||||
DelErr010 string
|
||||
|
||||
envArgs string // CNI_ARGS
|
||||
runtimeConfig struct {
|
||||
@ -154,6 +160,12 @@ const (
|
||||
netDefault = `,
|
||||
"isDefaultGateway": true`
|
||||
|
||||
disableContainerInterface = `,
|
||||
"disableContainerInterface": true`
|
||||
|
||||
portIsolation = `,
|
||||
"portIsolation": true`
|
||||
|
||||
ipamStartStr = `,
|
||||
"ipam": {
|
||||
"type": "host-local"`
|
||||
@ -167,6 +179,9 @@ const (
|
||||
ipMasqConfStr = `,
|
||||
"ipMasq": %t`
|
||||
|
||||
ipMasqBackendConfStr = `,
|
||||
"ipMasqBackend": "%s"`
|
||||
|
||||
// Single subnet configuration (legacy)
|
||||
subnetConfStr = `,
|
||||
"subnet": "%s"`
|
||||
@ -238,6 +253,9 @@ func (tc testCase) netConfJSON(dataDir string) string {
|
||||
if tc.ipMasq {
|
||||
conf += tc.ipMasqConfig()
|
||||
}
|
||||
if tc.ipMasqBackend != "" {
|
||||
conf += tc.ipMasqBackendConfig()
|
||||
}
|
||||
if tc.args.cni.mac != "" {
|
||||
conf += fmt.Sprintf(argsFormat, tc.args.cni.mac)
|
||||
}
|
||||
@ -248,6 +266,14 @@ func (tc testCase) netConfJSON(dataDir string) string {
|
||||
conf += fmt.Sprintf(macspoofchkFormat, tc.macspoofchk)
|
||||
}
|
||||
|
||||
if tc.disableContIface {
|
||||
conf += disableContainerInterface
|
||||
}
|
||||
|
||||
if tc.portIsolation {
|
||||
conf += portIsolation
|
||||
}
|
||||
|
||||
if !tc.isLayer2 {
|
||||
conf += netDefault
|
||||
if tc.subnet != "" || tc.ranges != nil {
|
||||
@ -286,6 +312,11 @@ func (tc testCase) ipMasqConfig() string {
|
||||
return conf
|
||||
}
|
||||
|
||||
func (tc testCase) ipMasqBackendConfig() string {
|
||||
conf := fmt.Sprintf(ipMasqBackendConfStr, tc.ipMasqBackend)
|
||||
return conf
|
||||
}
|
||||
|
||||
func (tc testCase) rangesConfig() string {
|
||||
conf := rangesStartStr
|
||||
for i, tcRange := range tc.ranges {
|
||||
@ -485,6 +516,11 @@ type (
|
||||
|
||||
func newTesterByVersion(version string, testNS, targetNS ns.NetNS) cmdAddDelTester {
|
||||
switch {
|
||||
case strings.HasPrefix(version, "1.1."):
|
||||
return &testerV10x{
|
||||
testNS: testNS,
|
||||
targetNS: targetNS,
|
||||
}
|
||||
case strings.HasPrefix(version, "1.0."):
|
||||
return &testerV10x{
|
||||
testNS: testNS,
|
||||
@ -621,6 +657,10 @@ func (tester *testerV10x) cmdAddTest(tc testCase, dataDir string) (types.Result,
|
||||
Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{}))
|
||||
tester.vethName = result.Interfaces[1].Name
|
||||
|
||||
protInfo, err := netlink.LinkGetProtinfo(link)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(protInfo.Isolated).To(Equal(tc.portIsolation), "link isolation should be on when portIsolation is set")
|
||||
|
||||
// check vlan exist on the veth interface
|
||||
if tc.vlan != 0 {
|
||||
interfaceMap, err := netlink.BridgeVlanList()
|
||||
@ -677,14 +717,16 @@ func (tester *testerV10x) cmdAddTest(tc testCase, dataDir string) (types.Result,
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().Name).To(Equal(IFNAME))
|
||||
Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{}))
|
||||
assertContainerInterfaceLinkState(&tc, link)
|
||||
|
||||
expCIDRsV4, expCIDRsV6 := tc.expectedCIDRs()
|
||||
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(addrs).To(HaveLen(len(expCIDRsV4)))
|
||||
addrs, err = netlink.AddrList(link, netlink.FAMILY_V6)
|
||||
Expect(addrs).To(HaveLen(len(expCIDRsV6) + 1)) // add one for the link-local
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
assertIPv6Addresses(&tc, addrs, expCIDRsV6)
|
||||
|
||||
// Ignore link local address which may or may not be
|
||||
// ready when we read addresses.
|
||||
var foundAddrs int
|
||||
@ -713,7 +755,7 @@ func (tester *testerV10x) cmdAddTest(tc testCase, dataDir string) (types.Result,
|
||||
continue
|
||||
}
|
||||
for _, route := range routes {
|
||||
*found = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwIP))
|
||||
*found = (ip.IsIPNetZero(route.Dst) && route.Src == nil && route.Gw.Equal(gwIP))
|
||||
if *found {
|
||||
break
|
||||
}
|
||||
@ -728,6 +770,15 @@ func (tester *testerV10x) cmdAddTest(tc testCase, dataDir string) (types.Result,
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func assertContainerInterfaceLinkState(tc *testCase, link netlink.Link) {
|
||||
linkState := int(link.Attrs().OperState)
|
||||
if tc.disableContIface {
|
||||
Expect(linkState).ToNot(Equal(netlink.OperUp))
|
||||
} else {
|
||||
Expect(linkState).To(Equal(netlink.OperUp))
|
||||
}
|
||||
}
|
||||
|
||||
func (tester *testerV10x) cmdCheckTest(tc testCase, conf *Net, _ string) {
|
||||
// Generate network config and command arguments
|
||||
tester.args = tc.createCheckCmdArgs(tester.targetNS, conf)
|
||||
@ -789,7 +840,7 @@ func (tester *testerV10x) cmdCheckTest(tc testCase, conf *Net, _ string) {
|
||||
continue
|
||||
}
|
||||
for _, route := range routes {
|
||||
*found = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwIP))
|
||||
*found = (ip.IsIPNetZero(route.Dst) && route.Src == nil && route.Gw.Equal(gwIP))
|
||||
if *found {
|
||||
break
|
||||
}
|
||||
@ -1008,8 +1059,9 @@ func (tester *testerV04x) cmdAddTest(tc testCase, dataDir string) (types.Result,
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(addrs).To(HaveLen(len(expCIDRsV4)))
|
||||
addrs, err = netlink.AddrList(link, netlink.FAMILY_V6)
|
||||
Expect(addrs).To(HaveLen(len(expCIDRsV6) + 1)) // add one for the link-local
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
assertIPv6Addresses(&tc, addrs, expCIDRsV6)
|
||||
|
||||
// Ignore link local address which may or may not be
|
||||
// ready when we read addresses.
|
||||
var foundAddrs int
|
||||
@ -1038,7 +1090,7 @@ func (tester *testerV04x) cmdAddTest(tc testCase, dataDir string) (types.Result,
|
||||
continue
|
||||
}
|
||||
for _, route := range routes {
|
||||
*found = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwIP))
|
||||
*found = (ip.IsIPNetZero(route.Dst) && route.Src == nil && route.Gw.Equal(gwIP))
|
||||
if *found {
|
||||
break
|
||||
}
|
||||
@ -1053,6 +1105,14 @@ func (tester *testerV04x) cmdAddTest(tc testCase, dataDir string) (types.Result,
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func assertIPv6Addresses(tc *testCase, addrs []netlink.Addr, expCIDRsV6 []*net.IPNet) {
|
||||
if tc.disableContIface {
|
||||
Expect(addrs).To(BeEmpty())
|
||||
} else {
|
||||
Expect(addrs).To(HaveLen(len(expCIDRsV6) + 1)) // add one for the link-local
|
||||
}
|
||||
}
|
||||
|
||||
func (tester *testerV04x) cmdCheckTest(tc testCase, conf *Net, _ string) {
|
||||
// Generate network config and command arguments
|
||||
tester.args = tc.createCheckCmdArgs(tester.targetNS, conf)
|
||||
@ -1114,7 +1174,7 @@ func (tester *testerV04x) cmdCheckTest(tc testCase, conf *Net, _ string) {
|
||||
continue
|
||||
}
|
||||
for _, route := range routes {
|
||||
*found = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwIP))
|
||||
*found = (ip.IsIPNetZero(route.Dst) && route.Src == nil && route.Gw.Equal(gwIP))
|
||||
if *found {
|
||||
break
|
||||
}
|
||||
@ -1362,7 +1422,7 @@ func (tester *testerV03x) cmdAddTest(tc testCase, dataDir string) (types.Result,
|
||||
continue
|
||||
}
|
||||
for _, route := range routes {
|
||||
*found = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwIP))
|
||||
*found = (ip.IsIPNetZero(route.Dst) && route.Src == nil && route.Gw.Equal(gwIP))
|
||||
if *found {
|
||||
break
|
||||
}
|
||||
@ -1440,6 +1500,14 @@ func (tester *testerV01xOr02x) cmdAddTest(tc testCase, dataDir string) (types.Re
|
||||
err := tester.testNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
// check that STATUS is
|
||||
if testutils.SpecVersionHasSTATUS(tc.cniVersion) {
|
||||
err := testutils.CmdStatus(func() error {
|
||||
return cmdStatus(&skel.CmdArgs{StdinData: []byte(tc.netConfJSON(dataDir))})
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
r, raw, err := testutils.CmdAddWithArgs(tester.args, func() error {
|
||||
return cmdAdd(tester.args)
|
||||
})
|
||||
@ -1583,7 +1651,7 @@ func (tester *testerV01xOr02x) cmdAddTest(tc testCase, dataDir string) (types.Re
|
||||
continue
|
||||
}
|
||||
for _, route := range routes {
|
||||
*found = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwIP))
|
||||
*found = (ip.IsIPNetZero(route.Dst) && route.Src == nil && route.Gw.Equal(gwIP))
|
||||
if *found {
|
||||
break
|
||||
}
|
||||
@ -1847,10 +1915,10 @@ var _ = Describe("bridge Operations", func() {
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = BRNAME
|
||||
err := netlink.LinkAdd(&netlink.Bridge{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: BRNAME,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
link, err := netlink.LinkByName(BRNAME)
|
||||
@ -2361,41 +2429,82 @@ var _ = Describe("bridge Operations", func() {
|
||||
})
|
||||
|
||||
if testutils.SpecVersionHasChaining(ver) {
|
||||
It(fmt.Sprintf("[%s] configures a bridge and ipMasq rules", ver), func() {
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
tc := testCase{
|
||||
ranges: []rangeInfo{{
|
||||
subnet: "10.1.2.0/24",
|
||||
}},
|
||||
ipMasq: true,
|
||||
cniVersion: ver,
|
||||
}
|
||||
for _, tc := range []testCase{
|
||||
{
|
||||
ranges: []rangeInfo{{
|
||||
subnet: "10.1.2.0/24",
|
||||
}},
|
||||
ipMasq: true,
|
||||
cniVersion: ver,
|
||||
},
|
||||
{
|
||||
ranges: []rangeInfo{{
|
||||
subnet: "10.1.2.0/24",
|
||||
}},
|
||||
ipMasq: true,
|
||||
ipMasqBackend: "iptables",
|
||||
cniVersion: ver,
|
||||
},
|
||||
{
|
||||
ranges: []rangeInfo{{
|
||||
subnet: "10.1.2.0/24",
|
||||
}},
|
||||
ipMasq: true,
|
||||
ipMasqBackend: "nftables",
|
||||
cniVersion: ver,
|
||||
},
|
||||
} {
|
||||
tc := tc
|
||||
It(fmt.Sprintf("[%s] configures a bridge and ipMasq rules with ipMasqBackend %q", ver, tc.ipMasqBackend), func() {
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
args := tc.createCmdArgs(originalNS, dataDir)
|
||||
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
args := tc.createCmdArgs(originalNS, dataDir)
|
||||
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
result, err := types100.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(result.IPs).Should(HaveLen(1))
|
||||
|
||||
ip := result.IPs[0].Address.IP.String()
|
||||
|
||||
// Update this if the default ipmasq backend changes
|
||||
switch tc.ipMasqBackend {
|
||||
case "iptables", "":
|
||||
ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
rules, err := ipt.List("nat", "POSTROUTING")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(rules).Should(ContainElement(ContainSubstring(ip)))
|
||||
case "nftables":
|
||||
nft, err := knftables.New(knftables.InetFamily, "cni_plugins_masquerade")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
rules, err := nft.ListRules(context.TODO(), "masq_checks")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
// FIXME: ListRules() doesn't return the actual rule strings,
|
||||
// and we can't easily compute the ipmasq plugin's comment.
|
||||
comments := 0
|
||||
for _, r := range rules {
|
||||
if r.Comment != nil {
|
||||
comments++
|
||||
break
|
||||
}
|
||||
}
|
||||
Expect(comments).To(Equal(1), "expected to find exactly one Rule with a comment")
|
||||
}
|
||||
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
result, err := types100.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(result.IPs).Should(HaveLen(1))
|
||||
|
||||
ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
rules, err := ipt.List("nat", "POSTROUTING")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(rules).Should(ContainElement(ContainSubstring(result.IPs[0].Address.IP.String())))
|
||||
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
}
|
||||
|
||||
for i, tc := range []testCase{
|
||||
{
|
||||
@ -2461,6 +2570,66 @@ var _ = Describe("bridge Operations", func() {
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] should fail when both IPAM and DisableContainerInterface are set", ver), func() {
|
||||
Expect(originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
tc := testCase{
|
||||
cniVersion: ver,
|
||||
subnet: "10.1.2.0/24",
|
||||
disableContIface: true,
|
||||
}
|
||||
args := tc.createCmdArgs(targetNS, dataDir)
|
||||
Expect(cmdAdd(args)).To(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] should set the container veth peer state down", ver), func() {
|
||||
Expect(originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
tc := testCase{
|
||||
cniVersion: ver,
|
||||
disableContIface: true,
|
||||
isLayer2: true,
|
||||
AddErr020: "cannot convert: no valid IP addresses",
|
||||
AddErr010: "cannot convert: no valid IP addresses",
|
||||
}
|
||||
cmdAddDelTest(originalNS, targetNS, tc, dataDir)
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] when port-isolation is off, should set the veth peer on node with isolation off", ver), func() {
|
||||
Expect(originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
tc := testCase{
|
||||
cniVersion: ver,
|
||||
portIsolation: false,
|
||||
isLayer2: true,
|
||||
AddErr020: "cannot convert: no valid IP addresses",
|
||||
AddErr010: "cannot convert: no valid IP addresses",
|
||||
}
|
||||
cmdAddDelTest(originalNS, targetNS, tc, dataDir)
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] when port-isolation is on, should set the veth peer on node with isolation on", ver), func() {
|
||||
Expect(originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
tc := testCase{
|
||||
cniVersion: ver,
|
||||
portIsolation: true,
|
||||
isLayer2: true,
|
||||
AddErr020: "cannot convert: no valid IP addresses",
|
||||
AddErr010: "cannot convert: no valid IP addresses",
|
||||
}
|
||||
cmdAddDelTest(originalNS, targetNS, tc, dataDir)
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
})
|
||||
}
|
||||
|
||||
It("check vlan id when loading net conf", func() {
|
||||
|
@ -43,11 +43,12 @@ func parseNetConf(bytes []byte) (*types.NetConf, error) {
|
||||
func createDummy(ifName string, netns ns.NetNS) (*current.Interface, error) {
|
||||
dummy := ¤t.Interface{}
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = ifName
|
||||
linkAttrs.Namespace = netlink.NsFd(int(netns.Fd()))
|
||||
|
||||
dm := &netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: ifName,
|
||||
Namespace: netlink.NsFd(int(netns.Fd())),
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
}
|
||||
|
||||
if err := netlink.LinkAdd(dm); err != nil {
|
||||
@ -136,7 +137,6 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
err = netns.Do(func(_ ns.NetNS) error {
|
||||
return ipam.ConfigureIface(args.IfName, result)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -165,7 +165,6 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
// if NetNs is passed down by the Cloud Orchestration Engine, or if it called multiple times
|
||||
// so don't return an error if the device is already removed.
|
||||
@ -181,7 +180,13 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("dummy"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
Status: cmdStatus,
|
||||
/* FIXME GC */
|
||||
}, version.All, bv.BuildString("dummy"))
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
@ -290,3 +295,16 @@ func validateCniContainerInterface(intf current.Interface) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cmdStatus(args *skel.CmdArgs) error {
|
||||
conf := types.NetConf{}
|
||||
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
|
||||
return fmt.Errorf("failed to load netconf: %w", err)
|
||||
}
|
||||
|
||||
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -106,7 +106,7 @@ type (
|
||||
|
||||
func newTesterByVersion(version string) tester {
|
||||
switch {
|
||||
case strings.HasPrefix(version, "1.0."):
|
||||
case strings.HasPrefix(version, "1."):
|
||||
return &testerV10x{}
|
||||
case strings.HasPrefix(version, "0.4."):
|
||||
return &testerV04x{}
|
||||
@ -180,11 +180,11 @@ var _ = Describe("dummy Operations", func() {
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = MASTER_NAME
|
||||
// Add master
|
||||
err = netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: MASTER_NAME,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
m, err := netlink.LinkByName(MASTER_NAME)
|
||||
@ -261,6 +261,13 @@ var _ = Describe("dummy Operations", func() {
|
||||
defer GinkgoRecover()
|
||||
|
||||
var err error
|
||||
if testutils.SpecVersionHasSTATUS(ver) {
|
||||
err = testutils.CmdStatus(func() error {
|
||||
return cmdStatus(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
result, _, err = testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
@ -371,7 +378,7 @@ var _ = Describe("dummy Operations", func() {
|
||||
StdinData: []byte(fmt.Sprintf(confFmt, ver)),
|
||||
}
|
||||
|
||||
_ = originalNS.Do(func(netNS ns.NetNS) error {
|
||||
_ = originalNS.Do(func(_ ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err = testutils.CmdAddWithArgs(args, func() error {
|
||||
|
@ -37,7 +37,10 @@ import (
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
var sysBusPCI = "/sys/bus/pci/devices"
|
||||
var (
|
||||
sysBusPCI = "/sys/bus/pci/devices"
|
||||
sysBusAuxiliary = "/sys/bus/auxiliary/devices"
|
||||
)
|
||||
|
||||
// Array of different linux drivers bound to network device needed for DPDK
|
||||
var userspaceDrivers = []string{"vfio-pci", "uio_pci_generic", "igb_uio"}
|
||||
@ -53,6 +56,9 @@ type NetConf struct {
|
||||
RuntimeConfig struct {
|
||||
DeviceID string `json:"deviceID,omitempty"`
|
||||
} `json:"runtimeConfig,omitempty"`
|
||||
|
||||
// for internal use
|
||||
auxDevice string `json:"-"` // Auxiliary device name as appears on Auxiliary bus (/sys/bus/auxiliary)
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -62,6 +68,31 @@ func init() {
|
||||
runtime.LockOSThread()
|
||||
}
|
||||
|
||||
// handleDeviceID updates netconf fields with DeviceID runtime config
|
||||
func handleDeviceID(netconf *NetConf) error {
|
||||
deviceID := netconf.RuntimeConfig.DeviceID
|
||||
if deviceID == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if deviceID is a PCI device
|
||||
pciPath := filepath.Join(sysBusPCI, deviceID)
|
||||
if _, err := os.Stat(pciPath); err == nil {
|
||||
netconf.PCIAddr = deviceID
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if deviceID is an Auxiliary device
|
||||
auxPath := filepath.Join(sysBusAuxiliary, deviceID)
|
||||
if _, err := os.Stat(auxPath); err == nil {
|
||||
netconf.PCIAddr = ""
|
||||
netconf.auxDevice = deviceID
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("runtime config DeviceID %s not found or unsupported", deviceID)
|
||||
}
|
||||
|
||||
func loadConf(bytes []byte) (*NetConf, error) {
|
||||
n := &NetConf{}
|
||||
var err error
|
||||
@ -69,12 +100,12 @@ func loadConf(bytes []byte) (*NetConf, error) {
|
||||
return nil, fmt.Errorf("failed to load netconf: %v", err)
|
||||
}
|
||||
|
||||
if n.RuntimeConfig.DeviceID != "" {
|
||||
// Override PCI device with the standardized DeviceID provided in Runtime Config.
|
||||
n.PCIAddr = n.RuntimeConfig.DeviceID
|
||||
// Override device with the standardized DeviceID if provided in Runtime Config.
|
||||
if err := handleDeviceID(n); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if n.Device == "" && n.HWAddr == "" && n.KernelPath == "" && n.PCIAddr == "" {
|
||||
if n.Device == "" && n.HWAddr == "" && n.KernelPath == "" && n.PCIAddr == "" && n.auxDevice == "" {
|
||||
return nil, fmt.Errorf(`specify either "device", "hwaddr", "kernelpath" or "pciBusID"`)
|
||||
}
|
||||
|
||||
@ -102,7 +133,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
result := ¤t.Result{}
|
||||
var contDev netlink.Link
|
||||
if !cfg.DPDKMode {
|
||||
hostDev, err := getLink(cfg.Device, cfg.HWAddr, cfg.KernelPath, cfg.PCIAddr)
|
||||
hostDev, err := getLink(cfg.Device, cfg.HWAddr, cfg.KernelPath, cfg.PCIAddr, cfg.auxDevice)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find host device: %v", err)
|
||||
}
|
||||
@ -199,40 +230,121 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func moveLinkIn(hostDev netlink.Link, containerNs ns.NetNS, ifName string) (netlink.Link, error) {
|
||||
if err := netlink.LinkSetNsFd(hostDev, int(containerNs.Fd())); err != nil {
|
||||
return nil, err
|
||||
func moveLinkIn(hostDev netlink.Link, containerNs ns.NetNS, containerIfName string) (netlink.Link, error) {
|
||||
hostDevName := hostDev.Attrs().Name
|
||||
|
||||
// With recent kernels we could do all changes in a single netlink call,
|
||||
// but on failure the device is left in a partially modified state.
|
||||
// Doing changes one by one allow us to (try to) rollback to the initial state.
|
||||
|
||||
// Create a temporary namespace to rename (and modify) the device in.
|
||||
// We were previously using a temporary name, but rapid rename leads to
|
||||
// race condition with udev and NetworkManager.
|
||||
tempNS, err := ns.TempNetNS()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create tempNS: %v", err)
|
||||
}
|
||||
defer tempNS.Close()
|
||||
|
||||
// Restore original up state in case of error
|
||||
// This must be done in the hostNS as moving
|
||||
// device between namespaces sets the link down
|
||||
if hostDev.Attrs().Flags&net.FlagUp == net.FlagUp {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
// lookup the device again (index might have changed)
|
||||
if hostDev, err := netlink.LinkByName(hostDevName); err == nil {
|
||||
_ = netlink.LinkSetUp(hostDev)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Move the host device into tempNS
|
||||
if err = netlink.LinkSetNsFd(hostDev, int(tempNS.Fd())); err != nil {
|
||||
return nil, fmt.Errorf("failed to move %q to tempNS: %v", hostDevName, err)
|
||||
}
|
||||
|
||||
var contDev netlink.Link
|
||||
if err := containerNs.Do(func(_ ns.NetNS) error {
|
||||
var err error
|
||||
contDev, err = netlink.LinkByName(hostDev.Attrs().Name)
|
||||
|
||||
// In a container in container scenario, hostNS is not the initial net namespace,
|
||||
// but host / container naming is easier to follow.
|
||||
if err = tempNS.Do(func(hostNS ns.NetNS) error {
|
||||
// lookup the device in tempNS (index might have changed)
|
||||
tempNSDev, err := netlink.LinkByName(hostDevName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find %q: %v", hostDev.Attrs().Name, err)
|
||||
return fmt.Errorf("failed to find %q in tempNS: %v", hostDevName, err)
|
||||
}
|
||||
// Devices can be renamed only when down
|
||||
if err = netlink.LinkSetDown(contDev); err != nil {
|
||||
return fmt.Errorf("failed to set %q down: %v", hostDev.Attrs().Name, err)
|
||||
|
||||
// detroying a non empty tempNS would move physical devices back to the initial net namespace,
|
||||
// not the namespace of the "parent" process, and virtual devices would be destroyed,
|
||||
// so we need to actively move the device back to hostNS on error
|
||||
defer func() {
|
||||
if err != nil && tempNSDev != nil {
|
||||
_ = netlink.LinkSetNsFd(tempNSDev, int(hostNS.Fd()))
|
||||
}
|
||||
}()
|
||||
|
||||
// Rename the device to the wanted name
|
||||
if err = netlink.LinkSetName(tempNSDev, containerIfName); err != nil {
|
||||
return fmt.Errorf("failed to rename host device %q to %q: %v", hostDevName, containerIfName, err)
|
||||
}
|
||||
|
||||
// Restore the original device name in case of error
|
||||
defer func() {
|
||||
if err != nil && tempNSDev != nil {
|
||||
_ = netlink.LinkSetName(tempNSDev, hostDevName)
|
||||
}
|
||||
}()
|
||||
|
||||
// Save host device name into the container device's alias property
|
||||
if err := netlink.LinkSetAlias(contDev, hostDev.Attrs().Name); err != nil {
|
||||
return fmt.Errorf("failed to set alias to %q: %v", hostDev.Attrs().Name, err)
|
||||
if err = netlink.LinkSetAlias(tempNSDev, hostDevName); err != nil {
|
||||
return fmt.Errorf("failed to set alias to %q: %v", hostDevName, err)
|
||||
}
|
||||
// Rename container device to respect args.IfName
|
||||
if err := netlink.LinkSetName(contDev, ifName); err != nil {
|
||||
return fmt.Errorf("failed to rename device %q to %q: %v", hostDev.Attrs().Name, ifName, err)
|
||||
|
||||
// Remove the alias on error
|
||||
defer func() {
|
||||
if err != nil && tempNSDev != nil {
|
||||
_ = netlink.LinkSetAlias(tempNSDev, "")
|
||||
}
|
||||
}()
|
||||
|
||||
// Move the device to the containerNS
|
||||
if err = netlink.LinkSetNsFd(tempNSDev, int(containerNs.Fd())); err != nil {
|
||||
return fmt.Errorf("failed to move %q (host: %q) to container NS: %v", containerIfName, hostDevName, err)
|
||||
}
|
||||
// Bring container device up
|
||||
if err = netlink.LinkSetUp(contDev); err != nil {
|
||||
return fmt.Errorf("failed to set %q up: %v", ifName, err)
|
||||
}
|
||||
// Retrieve link again to get up-to-date name and attributes
|
||||
contDev, err = netlink.LinkByName(ifName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find %q: %v", ifName, err)
|
||||
}
|
||||
return nil
|
||||
|
||||
// Lookup the device again on error, the index might have changed
|
||||
defer func() {
|
||||
if err != nil {
|
||||
tempNSDev, _ = netlink.LinkByName(containerIfName)
|
||||
}
|
||||
}()
|
||||
|
||||
err = containerNs.Do(func(_ ns.NetNS) error {
|
||||
var err error
|
||||
contDev, err = netlink.LinkByName(containerIfName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find %q in container NS: %v", containerIfName, err)
|
||||
}
|
||||
|
||||
// Move the interface back to tempNS on error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = netlink.LinkSetNsFd(contDev, int(tempNS.Fd()))
|
||||
}
|
||||
}()
|
||||
|
||||
// Bring the device up
|
||||
// This must be done in the containerNS
|
||||
if err = netlink.LinkSetUp(contDev); err != nil {
|
||||
return fmt.Errorf("failed to set %q up: %v", containerIfName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -240,45 +352,110 @@ func moveLinkIn(hostDev netlink.Link, containerNs ns.NetNS, ifName string) (netl
|
||||
return contDev, nil
|
||||
}
|
||||
|
||||
func moveLinkOut(containerNs ns.NetNS, ifName string) error {
|
||||
defaultNs, err := ns.GetCurrentNS()
|
||||
func moveLinkOut(containerNs ns.NetNS, containerIfName string) error {
|
||||
// Create a temporary namespace to rename (and modify) the device in.
|
||||
// We were previously using a temporary name, but multiple rapid renames
|
||||
// leads to race condition with udev and NetworkManager.
|
||||
tempNS, err := ns.TempNetNS()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to create tempNS: %v", err)
|
||||
}
|
||||
defer defaultNs.Close()
|
||||
defer tempNS.Close()
|
||||
|
||||
return containerNs.Do(func(_ ns.NetNS) error {
|
||||
dev, err := netlink.LinkByName(ifName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find %q: %v", ifName, err)
|
||||
}
|
||||
var contDev netlink.Link
|
||||
|
||||
// Devices can be renamed only when down
|
||||
if err = netlink.LinkSetDown(dev); err != nil {
|
||||
return fmt.Errorf("failed to set %q down: %v", ifName, err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
// If moving the device to the host namespace fails, set its name back to ifName so that this
|
||||
// function can be retried. Also bring the device back up, unless it was already down before.
|
||||
if err != nil {
|
||||
_ = netlink.LinkSetName(dev, ifName)
|
||||
if dev.Attrs().Flags&net.FlagUp == net.FlagUp {
|
||||
_ = netlink.LinkSetUp(dev)
|
||||
// Restore original up state in case of error
|
||||
// This must be done in the containerNS as moving
|
||||
// device between namespaces sets the link down
|
||||
defer func() {
|
||||
if err != nil && contDev != nil && contDev.Attrs().Flags&net.FlagUp == net.FlagUp {
|
||||
containerNs.Do(func(_ ns.NetNS) error {
|
||||
// lookup the device again (index might have changed)
|
||||
if contDev, err := netlink.LinkByName(containerIfName); err == nil {
|
||||
_ = netlink.LinkSetUp(contDev)
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
// Rename the device to its original name from the host namespace
|
||||
if err = netlink.LinkSetName(dev, dev.Attrs().Alias); err != nil {
|
||||
return fmt.Errorf("failed to restore %q to original name %q: %v", ifName, dev.Attrs().Alias, err)
|
||||
err = containerNs.Do(func(_ ns.NetNS) error {
|
||||
var err error
|
||||
// Lookup the device in the containerNS
|
||||
contDev, err = netlink.LinkByName(containerIfName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find %q in containerNS: %v", containerIfName, err)
|
||||
}
|
||||
|
||||
if err = netlink.LinkSetNsFd(dev, int(defaultNs.Fd())); err != nil {
|
||||
return fmt.Errorf("failed to move %q to host netns: %v", dev.Attrs().Alias, err)
|
||||
// Verify we have the original name
|
||||
if contDev.Attrs().Alias == "" {
|
||||
return fmt.Errorf("failed to find original ifname for %q (alias is not set)", containerIfName)
|
||||
}
|
||||
|
||||
// Move the device to the tempNS
|
||||
if err = netlink.LinkSetNsFd(contDev, int(tempNS.Fd())); err != nil {
|
||||
return fmt.Errorf("failed to move %q to tempNS: %v", containerIfName, err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = tempNS.Do(func(hostNS ns.NetNS) error {
|
||||
// Lookup the device in tempNS (index might have changed)
|
||||
tempNSDev, err := netlink.LinkByName(containerIfName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find %q in tempNS: %v", containerIfName, err)
|
||||
}
|
||||
|
||||
// Move the device back to containerNS on error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = netlink.LinkSetNsFd(tempNSDev, int(containerNs.Fd()))
|
||||
}
|
||||
}()
|
||||
|
||||
hostDevName := tempNSDev.Attrs().Alias
|
||||
|
||||
// Rename container device to hostDevName
|
||||
if err = netlink.LinkSetName(tempNSDev, hostDevName); err != nil {
|
||||
return fmt.Errorf("failed to rename device %q to %q: %v", containerIfName, hostDevName, err)
|
||||
}
|
||||
|
||||
// Rename the device back to containerIfName on error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = netlink.LinkSetName(tempNSDev, containerIfName)
|
||||
}
|
||||
}()
|
||||
|
||||
// Unset device's alias property
|
||||
if err = netlink.LinkSetAlias(tempNSDev, ""); err != nil {
|
||||
return fmt.Errorf("failed to unset alias of %q: %v", hostDevName, err)
|
||||
}
|
||||
|
||||
// Set back the device alias to hostDevName on error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = netlink.LinkSetAlias(tempNSDev, hostDevName)
|
||||
}
|
||||
}()
|
||||
|
||||
// Finally move the device to the hostNS
|
||||
if err = netlink.LinkSetNsFd(tempNSDev, int(hostNS.Fd())); err != nil {
|
||||
return fmt.Errorf("failed to move %q to hostNS: %v", hostDevName, err)
|
||||
}
|
||||
|
||||
// As we don't know the previous state, leave the link down
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func hasDpdkDriver(pciaddr string) (bool, error) {
|
||||
@ -314,11 +491,19 @@ func printLink(dev netlink.Link, cniVersion string, containerNs ns.NetNS) error
|
||||
return types.PrintResult(&result, cniVersion)
|
||||
}
|
||||
|
||||
func getLink(devname, hwaddr, kernelpath, pciaddr string) (netlink.Link, error) {
|
||||
links, err := netlink.LinkList()
|
||||
func linkFromPath(path string) (netlink.Link, error) {
|
||||
entries, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list node links: %v", err)
|
||||
return nil, fmt.Errorf("failed to read directory %s: %q", path, err)
|
||||
}
|
||||
if len(entries) > 0 {
|
||||
// grab the first net device
|
||||
return netlink.LinkByName(entries[0].Name())
|
||||
}
|
||||
return nil, fmt.Errorf("failed to find network device in path %s", path)
|
||||
}
|
||||
|
||||
func getLink(devname, hwaddr, kernelpath, pciaddr string, auxDev string) (netlink.Link, error) {
|
||||
switch {
|
||||
|
||||
case len(devname) > 0:
|
||||
@ -329,6 +514,11 @@ func getLink(devname, hwaddr, kernelpath, pciaddr string) (netlink.Link, error)
|
||||
return nil, fmt.Errorf("failed to parse MAC address %q: %v", hwaddr, err)
|
||||
}
|
||||
|
||||
links, err := netlink.LinkList()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list node links: %v", err)
|
||||
}
|
||||
|
||||
for _, link := range links {
|
||||
if bytes.Equal(link.Attrs().HardwareAddr, hwAddr) {
|
||||
return link, nil
|
||||
@ -339,20 +529,7 @@ func getLink(devname, hwaddr, kernelpath, pciaddr string) (netlink.Link, error)
|
||||
return nil, fmt.Errorf("kernel device path %q must be absolute and begin with /sys/devices/", kernelpath)
|
||||
}
|
||||
netDir := filepath.Join(kernelpath, "net")
|
||||
entries, err := os.ReadDir(netDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find network devices at %q", netDir)
|
||||
}
|
||||
|
||||
// Grab the first device from eg /sys/devices/pci0000:00/0000:00:19.0/net
|
||||
for _, entry := range entries {
|
||||
// Make sure it's really an interface
|
||||
for _, l := range links {
|
||||
if entry.Name() == l.Attrs().Name {
|
||||
return l, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return linkFromPath(netDir)
|
||||
case len(pciaddr) > 0:
|
||||
netDir := filepath.Join(sysBusPCI, pciaddr, "net")
|
||||
if _, err := os.Lstat(netDir); err != nil {
|
||||
@ -363,21 +540,23 @@ func getLink(devname, hwaddr, kernelpath, pciaddr string) (netlink.Link, error)
|
||||
}
|
||||
netDir = matches[0]
|
||||
}
|
||||
entries, err := os.ReadDir(netDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read net directory %s: %q", netDir, err)
|
||||
}
|
||||
if len(entries) > 0 {
|
||||
return netlink.LinkByName(entries[0].Name())
|
||||
}
|
||||
return nil, fmt.Errorf("failed to find device name for pci address %s", pciaddr)
|
||||
return linkFromPath(netDir)
|
||||
case len(auxDev) > 0:
|
||||
netDir := filepath.Join(sysBusAuxiliary, auxDev, "net")
|
||||
return linkFromPath(netDir)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to find physical interface")
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("host-device"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
Status: cmdStatus,
|
||||
/* FIXME GC */
|
||||
}, version.All, bv.BuildString("host-device"))
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
@ -484,3 +663,20 @@ func validateCniContainerInterface(intf current.Interface) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cmdStatus(args *skel.CmdArgs) error {
|
||||
conf := NetConf{}
|
||||
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
|
||||
return fmt.Errorf("failed to load netconf: %w", err)
|
||||
}
|
||||
|
||||
if conf.IPAM.Type != "" {
|
||||
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Check if host device exists.
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -231,7 +231,7 @@ type (
|
||||
|
||||
func newTesterByVersion(version string) tester {
|
||||
switch {
|
||||
case strings.HasPrefix(version, "1.0."):
|
||||
case strings.HasPrefix(version, "1."):
|
||||
return &testerV10x{}
|
||||
case strings.HasPrefix(version, "0.4."):
|
||||
return &testerV04x{}
|
||||
@ -341,10 +341,10 @@ var _ = Describe("base functionality", func() {
|
||||
// prepare ifname in original namespace
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = ifname
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: ifname,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
origLink, err = netlink.LinkByName(ifname)
|
||||
@ -362,6 +362,15 @@ var _ = Describe("base functionality", func() {
|
||||
"type": "host-device",
|
||||
"device": %q
|
||||
}`, ver, ifname)
|
||||
|
||||
// if v1.1 or greater, call CmdStatus
|
||||
if testutils.SpecVersionHasSTATUS(ver) {
|
||||
err := testutils.CmdStatus(func() error {
|
||||
return cmdStatus(&skel.CmdArgs{StdinData: []byte(conf)})
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
@ -422,10 +431,10 @@ var _ = Describe("base functionality", func() {
|
||||
// prepare host device in original namespace
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = ifname
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: ifname,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
origLink, err = netlink.LinkByName(ifname)
|
||||
@ -483,10 +492,10 @@ var _ = Describe("base functionality", func() {
|
||||
// create another conflict host device with same name "dummy0"
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = ifname
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: ifname,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
conflictLink, err = netlink.LinkByName(ifname)
|
||||
@ -608,10 +617,10 @@ var _ = Describe("base functionality", func() {
|
||||
// prepare ifname in original namespace
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = ifname
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: ifname,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
origLink, err = netlink.LinkByName(ifname)
|
||||
@ -720,10 +729,10 @@ var _ = Describe("base functionality", func() {
|
||||
// prepare ifname in original namespace
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = ifname
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: ifname,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
origLink, err = netlink.LinkByName(ifname)
|
||||
@ -898,16 +907,81 @@ var _ = Describe("base functionality", func() {
|
||||
})
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("Works with a valid %s config on auxiliary device", ver), func() {
|
||||
var origLink netlink.Link
|
||||
ifname := "eth0"
|
||||
|
||||
fs := &fakeFilesystem{
|
||||
dirs: []string{
|
||||
fmt.Sprintf("sys/bus/auxiliary/devices/mlx5_core.sf.4/net/%s", ifname),
|
||||
},
|
||||
}
|
||||
defer fs.use()()
|
||||
|
||||
// prepare ifname in original namespace
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = ifname
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
origLink, err = netlink.LinkByName(ifname)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = netlink.LinkSetUp(origLink)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
|
||||
// call CmdAdd
|
||||
cniName := "net1"
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "cni-plugin-host-device-test",
|
||||
"type": "host-device",
|
||||
"runtimeConfig": {"deviceID": %q}
|
||||
}`, ver, "mlx5_core.sf.4")
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
IfName: cniName,
|
||||
Netns: targetNS.Path(),
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
var resI types.Result
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
var err error
|
||||
resI, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) })
|
||||
return err
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// check that the result was sane
|
||||
t := newTesterByVersion(ver)
|
||||
t.expectInterfaces(resI, cniName, origLink.Attrs().HardwareAddr.String(), targetNS.Path())
|
||||
|
||||
// call CmdDel
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("Works with a valid %s config with IPAM", ver), func() {
|
||||
var origLink netlink.Link
|
||||
|
||||
// prepare ifname in original namespace
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = ifname
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: ifname,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
origLink, err = netlink.LinkByName(ifname)
|
||||
@ -1028,10 +1102,10 @@ var _ = Describe("base functionality", func() {
|
||||
// prepare host device in original namespace
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = ifname
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: ifname,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
origLink, err = netlink.LinkByName(ifname)
|
||||
@ -1089,10 +1163,10 @@ var _ = Describe("base functionality", func() {
|
||||
// create another conflict host device with same name "dummy0"
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = ifname
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: ifname,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
conflictLink, err = netlink.LinkByName(ifname)
|
||||
@ -1149,6 +1223,114 @@ var _ = Describe("base functionality", func() {
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("Test CmdAdd/Del when additioinal interface alreay exists in container ns with same name. %s config", ver), func() {
|
||||
var (
|
||||
origLink netlink.Link
|
||||
containerLink netlink.Link
|
||||
)
|
||||
|
||||
hostIfname := "eth0"
|
||||
containerAdditionalIfname := "eth0"
|
||||
|
||||
// prepare host device in original namespace
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = hostIfname
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
origLink, err = netlink.LinkByName(hostIfname)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = netlink.LinkSetUp(origLink)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
|
||||
// prepare device in container namespace with same name as host device
|
||||
_ = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = containerAdditionalIfname
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
containerLink, err = netlink.LinkByName(containerAdditionalIfname)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = netlink.LinkSetUp(containerLink)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
|
||||
// call CmdAdd
|
||||
cniName := "net1"
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "cni-plugin-host-device-test",
|
||||
"type": "host-device",
|
||||
"device": %q
|
||||
}`, ver, hostIfname)
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: cniName,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
var resI types.Result
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
var err error
|
||||
resI, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) })
|
||||
return err
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// check that the result was sane
|
||||
t := newTesterByVersion(ver)
|
||||
t.expectInterfaces(resI, cniName, origLink.Attrs().HardwareAddr.String(), targetNS.Path())
|
||||
|
||||
// assert that host device is now in the target namespace and is up
|
||||
_ = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
link, err := netlink.LinkByName(cniName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
|
||||
Expect(link.Attrs().Flags & net.FlagUp).To(Equal(net.FlagUp))
|
||||
return nil
|
||||
})
|
||||
|
||||
// call CmdDel, expect it to succeed
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
|
||||
// assert container interface "eth0" still exists in target namespace and is up
|
||||
err = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
link, err := netlink.LinkByName(containerAdditionalIfname)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr).To(Equal(containerLink.Attrs().HardwareAddr))
|
||||
Expect(link.Attrs().Flags & net.FlagUp).To(Equal(net.FlagUp))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// assert that host device is now back in the original namespace
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
_, err := netlink.LinkByName(hostIfname)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@ -1181,6 +1363,7 @@ func (fs *fakeFilesystem) use() func() {
|
||||
}
|
||||
|
||||
sysBusPCI = path.Join(fs.rootDir, "/sys/bus/pci/devices")
|
||||
sysBusAuxiliary = path.Join(fs.rootDir, "/sys/bus/auxiliary/devices")
|
||||
|
||||
return func() {
|
||||
// remove temporary fake fs
|
||||
|
@ -144,14 +144,15 @@ func createIpvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interf
|
||||
return nil, err
|
||||
}
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.MTU = conf.MTU
|
||||
linkAttrs.Name = tmpName
|
||||
linkAttrs.ParentIndex = m.Attrs().Index
|
||||
linkAttrs.Namespace = netlink.NsFd(int(netns.Fd()))
|
||||
|
||||
mv := &netlink.IPVlan{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
MTU: conf.MTU,
|
||||
Name: tmpName,
|
||||
ParentIndex: m.Attrs().Index,
|
||||
Namespace: netlink.NsFd(int(netns.Fd())),
|
||||
},
|
||||
Mode: mode,
|
||||
LinkAttrs: linkAttrs,
|
||||
Mode: mode,
|
||||
}
|
||||
|
||||
if conf.LinkContNs {
|
||||
@ -195,7 +196,7 @@ func getDefaultRouteInterfaceName() (string, error) {
|
||||
}
|
||||
|
||||
for _, v := range routeToDstIP {
|
||||
if v.Dst == nil {
|
||||
if ip.IsIPNetZero(v.Dst) {
|
||||
l, err := netlink.LinkByIndex(v.LinkIndex)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -293,6 +294,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
|
||||
err = netns.Do(func(_ ns.NetNS) error {
|
||||
_, _ = sysctl.Sysctl(fmt.Sprintf("net/ipv4/conf/%s/arp_notify", args.IfName), "1")
|
||||
_, _ = sysctl.Sysctl(fmt.Sprintf("net/ipv6/conf/%s/ndisc_notify", args.IfName), "1")
|
||||
|
||||
return ipam.ConfigureIface(args.IfName, result)
|
||||
})
|
||||
@ -333,7 +335,6 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
// if NetNs is passed down by the Cloud Orchestration Engine, or if it called multiple times
|
||||
// so don't return an error if the device is already removed.
|
||||
@ -349,7 +350,13 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("ipvlan"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
Status: cmdStatus,
|
||||
/* FIXME GC */
|
||||
}, version.All, bv.BuildString("ipvlan"))
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
@ -484,3 +491,19 @@ func validateCniContainerInterface(intf current.Interface, modeExpected string)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cmdStatus(args *skel.CmdArgs) error {
|
||||
conf := NetConf{}
|
||||
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
|
||||
return fmt.Errorf("failed to load netconf: %w", err)
|
||||
}
|
||||
if conf.IPAM.Type != "" {
|
||||
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Check if master interface exists.
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -114,6 +114,13 @@ func ipvlanAddCheckDelTest(conf, masterName string, originalNS, targetNS ns.NetN
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
if testutils.SpecVersionHasSTATUS(cniVersion) {
|
||||
err = testutils.CmdStatus(func() error {
|
||||
return cmdStatus(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
result, _, err = testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
@ -214,7 +221,7 @@ type (
|
||||
|
||||
func newTesterByVersion(version string) tester {
|
||||
switch {
|
||||
case strings.HasPrefix(version, "1.0."):
|
||||
case strings.HasPrefix(version, "1."):
|
||||
return &testerV10x{}
|
||||
case strings.HasPrefix(version, "0.4.") || strings.HasPrefix(version, "0.3."):
|
||||
return &testerV04x{}
|
||||
@ -281,11 +288,11 @@ var _ = Describe("ipvlan Operations", func() {
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = MASTER_NAME
|
||||
// Add master
|
||||
err = netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: MASTER_NAME,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
_, err = netlink.LinkByName(MASTER_NAME)
|
||||
@ -297,11 +304,11 @@ var _ = Describe("ipvlan Operations", func() {
|
||||
err = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = MASTER_NAME_INCONTAINER
|
||||
// Add master
|
||||
err = netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: MASTER_NAME_INCONTAINER,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
_, err = netlink.LinkByName(MASTER_NAME_INCONTAINER)
|
||||
|
@ -172,7 +172,13 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("loopback"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
/* FIXME GC */
|
||||
/* FIXME Status */
|
||||
}, version.All, bv.BuildString("loopback"))
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
|
@ -74,7 +74,8 @@ var _ = Describe("Loopback", func() {
|
||||
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Eventually(session).Should(gbytes.Say(`{.*}`))
|
||||
// "(?s)" turns on the "s" flag, making "." match newlines too.
|
||||
Eventually(session).Should(gbytes.Say(`(?s){.*}`))
|
||||
Eventually(session).Should(gexec.Exit(0))
|
||||
|
||||
var lo *net.Interface
|
||||
|
@ -41,6 +41,7 @@ type NetConf struct {
|
||||
MTU int `json:"mtu"`
|
||||
Mac string `json:"mac,omitempty"`
|
||||
LinkContNs bool `json:"linkInContainer,omitempty"`
|
||||
BcQueueLen uint32 `json:"bcqueuelen,omitempty"`
|
||||
|
||||
RuntimeConfig struct {
|
||||
Mac string `json:"mac,omitempty"`
|
||||
@ -67,7 +68,7 @@ func getDefaultRouteInterfaceName() (string, error) {
|
||||
}
|
||||
|
||||
for _, v := range routeToDstIP {
|
||||
if v.Dst == nil {
|
||||
if ip.IsIPNetZero(v.Dst) {
|
||||
l, err := netlink.LinkByIndex(v.LinkIndex)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -225,12 +226,11 @@ func createMacvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Inter
|
||||
return nil, err
|
||||
}
|
||||
|
||||
linkAttrs := netlink.LinkAttrs{
|
||||
MTU: conf.MTU,
|
||||
Name: tmpName,
|
||||
ParentIndex: m.Attrs().Index,
|
||||
Namespace: netlink.NsFd(int(netns.Fd())),
|
||||
}
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.MTU = conf.MTU
|
||||
linkAttrs.Name = tmpName
|
||||
linkAttrs.ParentIndex = m.Attrs().Index
|
||||
linkAttrs.Namespace = netlink.NsFd(int(netns.Fd()))
|
||||
|
||||
if conf.Mac != "" {
|
||||
addr, err := net.ParseMAC(conf.Mac)
|
||||
@ -245,6 +245,8 @@ func createMacvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Inter
|
||||
Mode: mode,
|
||||
}
|
||||
|
||||
mv.BCQueueLen = conf.BcQueueLen
|
||||
|
||||
if conf.LinkContNs {
|
||||
err = netns.Do(func(_ ns.NetNS) error {
|
||||
return netlink.LinkAdd(mv)
|
||||
@ -351,6 +353,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
|
||||
err = netns.Do(func(_ ns.NetNS) error {
|
||||
_, _ = sysctl.Sysctl(fmt.Sprintf("net/ipv4/conf/%s/arp_notify", args.IfName), "1")
|
||||
_, _ = sysctl.Sysctl(fmt.Sprintf("net/ipv6/conf/%s/ndisc_notify", args.IfName), "1")
|
||||
|
||||
return ipam.ConfigureIface(args.IfName, result)
|
||||
})
|
||||
@ -382,13 +385,13 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func cmdDel(args *skel.CmdArgs) error {
|
||||
n, _, err := loadConf(args, args.Args)
|
||||
var n NetConf
|
||||
err := json.Unmarshal(args.StdinData, &n)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to load netConf: %v", err)
|
||||
}
|
||||
|
||||
isLayer3 := n.IPAM.Type != ""
|
||||
|
||||
if isLayer3 {
|
||||
err = ipam.ExecDel(n.IPAM.Type, args.StdinData)
|
||||
if err != nil {
|
||||
@ -410,7 +413,6 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
// if NetNs is passed down by the Cloud Orchestration Engine, or if it called multiple times
|
||||
// so don't return an error if the device is already removed.
|
||||
@ -426,7 +428,13 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("macvlan"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
Status: cmdStatus,
|
||||
/* FIXME GC */
|
||||
}, version.All, bv.BuildString("macvlan"))
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
@ -562,3 +570,20 @@ func validateCniContainerInterface(intf current.Interface, modeExpected string)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cmdStatus(args *skel.CmdArgs) error {
|
||||
conf := NetConf{}
|
||||
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
|
||||
return fmt.Errorf("failed to load netconf: %w", err)
|
||||
}
|
||||
|
||||
if conf.IPAM.Type != "" {
|
||||
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Check if master interface exists.
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -117,7 +117,7 @@ type (
|
||||
|
||||
func newTesterByVersion(version string) tester {
|
||||
switch {
|
||||
case strings.HasPrefix(version, "1.0."):
|
||||
case strings.HasPrefix(version, "1."):
|
||||
return &testerV10x{}
|
||||
case strings.HasPrefix(version, "0.4."):
|
||||
return &testerV04x{}
|
||||
@ -208,11 +208,11 @@ var _ = Describe("macvlan Operations", func() {
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = MASTER_NAME
|
||||
// Add master
|
||||
err = netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: MASTER_NAME,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
_, err = netlink.LinkByName(MASTER_NAME)
|
||||
@ -224,11 +224,11 @@ var _ = Describe("macvlan Operations", func() {
|
||||
err = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = MASTER_NAME_INCONTAINER
|
||||
// Add master
|
||||
err = netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: MASTER_NAME_INCONTAINER,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
_, err = netlink.LinkByName(MASTER_NAME_INCONTAINER)
|
||||
@ -322,6 +322,13 @@ var _ = Describe("macvlan Operations", func() {
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
if testutils.SpecVersionHasSTATUS(ver) {
|
||||
err := testutils.CmdStatus(func() error {
|
||||
return cmdStatus(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
result, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
@ -434,6 +441,13 @@ var _ = Describe("macvlan Operations", func() {
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
if testutils.SpecVersionHasSTATUS(ver) {
|
||||
err := testutils.CmdStatus(func() error {
|
||||
return cmdStatus(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
result, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
@ -520,6 +534,13 @@ var _ = Describe("macvlan Operations", func() {
|
||||
defer GinkgoRecover()
|
||||
|
||||
var err error
|
||||
if testutils.SpecVersionHasSTATUS(ver) {
|
||||
err := testutils.CmdStatus(func() error {
|
||||
return cmdStatus(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
result, _, err = testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
@ -660,6 +681,13 @@ var _ = Describe("macvlan Operations", func() {
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
if testutils.SpecVersionHasSTATUS(ver) {
|
||||
err := testutils.CmdStatus(func() error {
|
||||
return cmdStatus(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
result, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
|
@ -31,7 +31,6 @@ import (
|
||||
"github.com/containernetworking/plugins/pkg/ip"
|
||||
"github.com/containernetworking/plugins/pkg/ipam"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/utils"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
@ -44,8 +43,9 @@ func init() {
|
||||
|
||||
type NetConf struct {
|
||||
types.NetConf
|
||||
IPMasq bool `json:"ipMasq"`
|
||||
MTU int `json:"mtu"`
|
||||
IPMasq bool `json:"ipMasq"`
|
||||
IPMasqBackend *string `json:"ipMasqBackend,omitempty"`
|
||||
MTU int `json:"mtu"`
|
||||
}
|
||||
|
||||
func setupContainerVeth(netns ns.NetNS, ifName string, mtu int, pr *current.Result) (*current.Interface, *current.Interface, error) {
|
||||
@ -229,12 +229,12 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
if conf.IPMasq {
|
||||
chain := utils.FormatChainName(conf.Name, args.ContainerID)
|
||||
comment := utils.FormatComment(conf.Name, args.ContainerID)
|
||||
ipns := []*net.IPNet{}
|
||||
for _, ipc := range result.IPs {
|
||||
if err = ip.SetupIPMasq(&ipc.Address, chain, comment); err != nil {
|
||||
return err
|
||||
}
|
||||
ipns = append(ipns, &ipc.Address)
|
||||
}
|
||||
if err = ip.SetupIPMasqForNetworks(conf.IPMasqBackend, ipns, conf.Name, args.IfName, args.ContainerID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@ -293,10 +293,8 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
if len(ipnets) != 0 && conf.IPMasq {
|
||||
chain := utils.FormatChainName(conf.Name, args.ContainerID)
|
||||
comment := utils.FormatComment(conf.Name, args.ContainerID)
|
||||
for _, ipn := range ipnets {
|
||||
err = ip.TeardownIPMasq(ipn, chain, comment)
|
||||
if err := ip.TeardownIPMasqForNetworks(ipnets, conf.Name, args.IfName, args.ContainerID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@ -304,7 +302,13 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("ptp"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
Status: cmdStatus,
|
||||
/* FIXME GC */
|
||||
}, version.All, bv.BuildString("ptp"))
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
@ -407,3 +411,16 @@ func validateCniContainerInterface(intf current.Interface) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cmdStatus(args *skel.CmdArgs) error {
|
||||
conf := NetConf{}
|
||||
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
|
||||
return fmt.Errorf("failed to load netconf: %w", err)
|
||||
}
|
||||
|
||||
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ type Net struct {
|
||||
CNIVersion string `json:"cniVersion"`
|
||||
Type string `json:"type,omitempty"`
|
||||
IPMasq bool `json:"ipMasq"`
|
||||
IPMasqBackend *string `json:"ipMasqBackend,omitempty"`
|
||||
MTU int `json:"mtu"`
|
||||
IPAM *allocator.IPAMConfig `json:"ipam"`
|
||||
DNS types.DNS `json:"dns"`
|
||||
@ -104,7 +105,7 @@ type (
|
||||
|
||||
func newTesterByVersion(version string) tester {
|
||||
switch {
|
||||
case strings.HasPrefix(version, "1.0."):
|
||||
case strings.HasPrefix(version, "1."):
|
||||
return &testerV10x{}
|
||||
case strings.HasPrefix(version, "0.4."):
|
||||
return &testerV04x{}
|
||||
@ -249,6 +250,14 @@ var _ = Describe("ptp Operations", func() {
|
||||
defer GinkgoRecover()
|
||||
|
||||
var err error
|
||||
if testutils.SpecVersionHasSTATUS(cniVersion) {
|
||||
By("Doing a cni STATUS")
|
||||
err = testutils.CmdStatus(func() error {
|
||||
return cmdStatus(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
result, _, err = testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
@ -368,6 +377,62 @@ var _ = Describe("ptp Operations", func() {
|
||||
doTest(conf, ver, 1, dnsConf, targetNS)
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] configures and deconfigures a ptp link when specifying ipMasqBackend: iptables", ver), func() {
|
||||
dnsConf := types.DNS{
|
||||
Nameservers: []string{"10.1.2.123"},
|
||||
Domain: "some.domain.test",
|
||||
Search: []string{"search.test"},
|
||||
Options: []string{"option1:foo"},
|
||||
}
|
||||
dnsConfBytes, err := json.Marshal(dnsConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "ptp",
|
||||
"ipMasq": true,
|
||||
"ipMasqBackend": "iptables",
|
||||
"mtu": 5000,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.2.0/24",
|
||||
"dataDir": "%s"
|
||||
},
|
||||
"dns": %s
|
||||
}`, ver, dataDir, string(dnsConfBytes))
|
||||
|
||||
doTest(conf, ver, 1, dnsConf, targetNS)
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] configures and deconfigures a ptp link when specifying ipMasqBackend: nftables", ver), func() {
|
||||
dnsConf := types.DNS{
|
||||
Nameservers: []string{"10.1.2.123"},
|
||||
Domain: "some.domain.test",
|
||||
Search: []string{"search.test"},
|
||||
Options: []string{"option1:foo"},
|
||||
}
|
||||
dnsConfBytes, err := json.Marshal(dnsConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "ptp",
|
||||
"ipMasq": true,
|
||||
"ipMasqBackend": "nftables",
|
||||
"mtu": 5000,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.2.0/24",
|
||||
"dataDir": "%s"
|
||||
},
|
||||
"dns": %s
|
||||
}`, ver, dataDir, string(dnsConfBytes))
|
||||
|
||||
doTest(conf, ver, 1, dnsConf, targetNS)
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] configures and deconfigures a dual-stack ptp link + routes with ADD/DEL", ver), func() {
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
|
@ -47,6 +47,7 @@ type NetConf struct {
|
||||
Owner *uint32 `json:"owner,omitempty"`
|
||||
Group *uint32 `json:"group,omitempty"`
|
||||
SelinuxContext string `json:"selinuxContext,omitempty"`
|
||||
Bridge string `json:"bridge,omitempty"`
|
||||
Args *struct{} `json:"args,omitempty"`
|
||||
RuntimeConfig struct {
|
||||
Mac string `json:"mac,omitempty"`
|
||||
@ -136,10 +137,9 @@ func createTapWithIptool(tmpName string, mtu int, multiqueue bool, mac string, o
|
||||
}
|
||||
|
||||
func createLinkWithNetlink(tmpName string, mtu int, nsFd int, multiqueue bool, mac string, owner *uint32, group *uint32) error {
|
||||
linkAttrs := netlink.LinkAttrs{
|
||||
Name: tmpName,
|
||||
Namespace: netlink.NsFd(nsFd),
|
||||
}
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = tmpName
|
||||
linkAttrs.Namespace = netlink.NsFd(nsFd)
|
||||
if mtu != 0 {
|
||||
linkAttrs.MTU = mtu
|
||||
}
|
||||
@ -216,6 +216,18 @@ func createTap(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interface
|
||||
return fmt.Errorf("failed to refetch tap %q: %v", ifName, err)
|
||||
}
|
||||
|
||||
if conf.Bridge != "" {
|
||||
bridge, err := netlink.LinkByName(conf.Bridge)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get bridge %s: %v", conf.Bridge, err)
|
||||
}
|
||||
|
||||
tapDev := link
|
||||
if err := netlink.LinkSetMaster(tapDev, bridge); err != nil {
|
||||
return fmt.Errorf("failed to set tap %s as a port of bridge %s: %v", tap.Name, conf.Bridge, err)
|
||||
}
|
||||
}
|
||||
|
||||
err = netlink.LinkSetUp(link)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set tap interface up: %v", err)
|
||||
@ -358,7 +370,6 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
// if NetNs is passed down by the Cloud Orchestration Engine, or if it called multiple times
|
||||
// so don't return an error if the device is already removed.
|
||||
@ -374,7 +385,13 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("tap"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
Status: cmdStatus,
|
||||
/* FIXME GC */
|
||||
}, version.All, bv.BuildString("tap"))
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
@ -443,3 +460,18 @@ func cmdCheck(args *skel.CmdArgs) error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func cmdStatus(args *skel.CmdArgs) error {
|
||||
conf := NetConf{}
|
||||
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
|
||||
return fmt.Errorf("failed to load netconf: %w", err)
|
||||
}
|
||||
|
||||
if conf.IPAM.Type != "" {
|
||||
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ type (
|
||||
|
||||
func newTesterByVersion(version string) tester {
|
||||
switch {
|
||||
case strings.HasPrefix(version, "1.0."):
|
||||
case strings.HasPrefix(version, "1."):
|
||||
return &testerV10x{}
|
||||
case strings.HasPrefix(version, "0.4."):
|
||||
return &testerV04x{}
|
||||
@ -223,6 +223,13 @@ var _ = Describe("Add, check, remove tap plugin", func() {
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
if testutils.SpecVersionHasSTATUS(ver) {
|
||||
err := testutils.CmdStatus(func() error {
|
||||
return cmdStatus(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
result, _, err = testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
@ -314,5 +321,127 @@ var _ = Describe("Add, check, remove tap plugin", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] add, check and remove a tap device as a bridge port", ver), func() {
|
||||
const bridgeName = "br1"
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "tapTest",
|
||||
"type": "tap",
|
||||
"owner": 0,
|
||||
"group": 0,
|
||||
"bridge": %q,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.2.0/24",
|
||||
"dataDir": "%s"
|
||||
}
|
||||
}`, ver, bridgeName, dataDir)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: IFNAME,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
t := newTesterByVersion(ver)
|
||||
|
||||
var bridge netlink.Link
|
||||
var result types.Result
|
||||
var macAddress string
|
||||
var err error
|
||||
|
||||
Expect(
|
||||
targetNS.Do(func(ns.NetNS) error {
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = bridgeName
|
||||
if err := netlink.LinkAdd(&netlink.Bridge{
|
||||
LinkAttrs: linkAttrs,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
bridge, err = netlink.LinkByName(bridgeName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
).To(Succeed())
|
||||
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
if testutils.SpecVersionHasSTATUS(ver) {
|
||||
err := testutils.CmdStatus(func() error {
|
||||
return cmdStatus(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
result, _, err = testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
macAddress = t.verifyResult(result, IFNAME)
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("Make sure the tap link exists in the target namespace")
|
||||
err = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
link, err := netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().Name).To(Equal(IFNAME))
|
||||
Expect(link.Type()).To(Equal(TYPETAP))
|
||||
Expect(link.Attrs().MasterIndex).To(Equal(bridge.Attrs().Index))
|
||||
|
||||
if macAddress != "" {
|
||||
hwaddr, err := net.ParseMAC(macAddress)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr).To(Equal(hwaddr))
|
||||
}
|
||||
addrs, err := netlink.AddrList(link, syscall.AF_INET)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(addrs).To(HaveLen(1))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("Running cmdDel")
|
||||
args.StdinData = []byte(conf)
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
link, err := netlink.LinkByName(IFNAME)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(link).To(BeNil())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("Running cmdDel more than once without error")
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@ -119,14 +119,15 @@ func createVlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interfac
|
||||
return nil, err
|
||||
}
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.MTU = conf.MTU
|
||||
linkAttrs.Name = tmpName
|
||||
linkAttrs.ParentIndex = m.Attrs().Index
|
||||
linkAttrs.Namespace = netlink.NsFd(int(netns.Fd()))
|
||||
|
||||
v := &netlink.Vlan{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
MTU: conf.MTU,
|
||||
Name: tmpName,
|
||||
ParentIndex: m.Attrs().Index,
|
||||
Namespace: netlink.NsFd(int(netns.Fd())),
|
||||
},
|
||||
VlanId: conf.VlanID,
|
||||
LinkAttrs: linkAttrs,
|
||||
VlanId: conf.VlanID,
|
||||
}
|
||||
|
||||
if conf.LinkContNs {
|
||||
@ -244,7 +245,6 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
// if NetNs is passed down by the Cloud Orchestration Engine, or if it called multiple times
|
||||
// so don't return an error if the device is already removed.
|
||||
@ -260,7 +260,13 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("vlan"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
Status: cmdStatus,
|
||||
/* FIXME GC */
|
||||
}, version.All, bv.BuildString("vlan"))
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
@ -393,3 +399,18 @@ func validateCniContainerInterface(intf current.Interface, vlanID int, mtu int)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cmdStatus(args *skel.CmdArgs) error {
|
||||
conf := NetConf{}
|
||||
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
|
||||
return fmt.Errorf("failed to load netconf: %w", err)
|
||||
}
|
||||
|
||||
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: Check if master interface exists.
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ type (
|
||||
|
||||
func newTesterByVersion(version string) tester {
|
||||
switch {
|
||||
case strings.HasPrefix(version, "1.0."):
|
||||
case strings.HasPrefix(version, "1."):
|
||||
return &testerV10x{}
|
||||
case strings.HasPrefix(version, "0.4."):
|
||||
return &testerV04x{}
|
||||
@ -187,11 +187,11 @@ var _ = Describe("vlan Operations", func() {
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = MASTER_NAME
|
||||
// Add master
|
||||
err = netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: MASTER_NAME,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
m, err := netlink.LinkByName(MASTER_NAME)
|
||||
@ -205,11 +205,11 @@ var _ = Describe("vlan Operations", func() {
|
||||
err = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = MASTER_NAME_INCONTAINER
|
||||
// Add master
|
||||
err = netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: MASTER_NAME_INCONTAINER,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
m, err := netlink.LinkByName(MASTER_NAME_INCONTAINER)
|
||||
@ -354,6 +354,13 @@ var _ = Describe("vlan Operations", func() {
|
||||
defer GinkgoRecover()
|
||||
|
||||
var err error
|
||||
if testutils.SpecVersionHasSTATUS(ver) {
|
||||
err := testutils.CmdStatus(func() error {
|
||||
return cmdStatus(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
result, _, err = testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
@ -499,7 +506,7 @@ var _ = Describe("vlan Operations", func() {
|
||||
StdinData: []byte(fmt.Sprintf(confFmt, ver, masterInterface, 1600, isInContainer, dataDir)),
|
||||
}
|
||||
|
||||
_ = originalNS.Do(func(netNS ns.NetNS) error {
|
||||
_ = originalNS.Do(func(_ ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err = testutils.CmdAddWithArgs(args, func() error {
|
||||
@ -520,7 +527,7 @@ var _ = Describe("vlan Operations", func() {
|
||||
StdinData: []byte(fmt.Sprintf(confFmt, ver, masterInterface, -100, isInContainer, dataDir)),
|
||||
}
|
||||
|
||||
_ = originalNS.Do(func(netNS ns.NetNS) error {
|
||||
_ = originalNS.Do(func(_ ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err = testutils.CmdAddWithArgs(args, func() error {
|
||||
|
@ -215,5 +215,26 @@ func cmdCheck(_ *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("win-bridge"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
Status: cmdStatus,
|
||||
/* FIXME GC */
|
||||
}, version.All, bv.BuildString("win-bridge"))
|
||||
}
|
||||
|
||||
func cmdStatus(args *skel.CmdArgs) error {
|
||||
conf := NetConf{}
|
||||
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
|
||||
return fmt.Errorf("failed to load netconf: %w", err)
|
||||
}
|
||||
|
||||
if conf.IPAM.Type != "" {
|
||||
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -106,13 +106,13 @@ func cmdHcnAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
|
||||
return nil, errors.Annotatef(err, "error while hcn.GetNetworkByName(%s)", networkName)
|
||||
}
|
||||
if hcnNetwork == nil {
|
||||
return nil, fmt.Errorf("network %v is not found", networkName)
|
||||
return nil, fmt.Errorf("network %v is not found", networkName)
|
||||
}
|
||||
if hnsNetwork == nil {
|
||||
return nil, fmt.Errorf("network %v not found", networkName)
|
||||
}
|
||||
|
||||
if !strings.EqualFold(string (hcnNetwork.Type), "Overlay") {
|
||||
if !strings.EqualFold(string(hcnNetwork.Type), "Overlay") {
|
||||
return nil, fmt.Errorf("network %v is of an unexpected type: %v", networkName, hcnNetwork.Type)
|
||||
}
|
||||
|
||||
@ -131,7 +131,6 @@ func cmdHcnAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
|
||||
n.ApplyOutboundNatPolicy(hnsNetwork.Subnets[0].AddressPrefix)
|
||||
}
|
||||
hcnEndpoint, err := hns.GenerateHcnEndpoint(epInfo, &n.NetConf)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Annotate(err, "error while generating HostComputeEndpoint")
|
||||
}
|
||||
@ -142,15 +141,14 @@ func cmdHcnAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
|
||||
}
|
||||
|
||||
result, err := hns.ConstructHcnResult(hcnNetwork, hcnEndpoint)
|
||||
|
||||
if err != nil {
|
||||
ipam.ExecDel(n.IPAM.Type, args.StdinData)
|
||||
return nil, errors.Annotate(err, "error while constructing HostComputeEndpoint addition result")
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
||||
}
|
||||
|
||||
func cmdHnsAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
|
||||
success := false
|
||||
|
||||
@ -243,6 +241,7 @@ func cmdHnsAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
|
||||
success = true
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func cmdAdd(args *skel.CmdArgs) error {
|
||||
n, cniVersion, err := loadNetConf(args.StdinData)
|
||||
if err != nil {
|
||||
@ -288,5 +287,24 @@ func cmdCheck(_ *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("win-overlay"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
Status: cmdStatus,
|
||||
/* FIXME GC */
|
||||
}, version.All, bv.BuildString("win-overlay"))
|
||||
}
|
||||
|
||||
func cmdStatus(args *skel.CmdArgs) error {
|
||||
conf := NetConf{}
|
||||
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
|
||||
return fmt.Errorf("failed to load netconf: %w", err)
|
||||
}
|
||||
|
||||
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -165,7 +165,7 @@ var _ = Describe("bandwidth test", func() {
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
||||
Expect(hostNs.Do(func(_ ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
r, out, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
|
||||
Expect(err).NotTo(HaveOccurred(), string(out))
|
||||
@ -202,7 +202,7 @@ var _ = Describe("bandwidth test", func() {
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
|
||||
Expect(hostNs.Do(func(n ns.NetNS) error {
|
||||
Expect(hostNs.Do(func(_ ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
ifbLink, err := netlink.LinkByName(hostIfname)
|
||||
@ -260,7 +260,7 @@ var _ = Describe("bandwidth test", func() {
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
||||
Expect(hostNs.Do(func(_ ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, out, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, ifbDeviceName, []byte(conf), func() error { return cmdAdd(args) })
|
||||
@ -271,7 +271,7 @@ var _ = Describe("bandwidth test", func() {
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
|
||||
Expect(hostNs.Do(func(n ns.NetNS) error {
|
||||
Expect(hostNs.Do(func(_ ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
containerIfLink, err := netlink.LinkByName(hostIfname)
|
||||
@ -327,7 +327,7 @@ var _ = Describe("bandwidth test", func() {
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
||||
Expect(hostNs.Do(func(_ ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, out, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, ifbDeviceName, []byte(conf), func() error { return cmdAdd(args) })
|
||||
@ -338,7 +338,7 @@ var _ = Describe("bandwidth test", func() {
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
|
||||
Expect(hostNs.Do(func(n ns.NetNS) error {
|
||||
Expect(hostNs.Do(func(_ ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
containerIfLink, err := netlink.LinkByName(hostIfname)
|
||||
@ -396,7 +396,7 @@ var _ = Describe("bandwidth test", func() {
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
||||
Expect(hostNs.Do(func(_ ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
|
||||
@ -448,7 +448,7 @@ var _ = Describe("bandwidth test", func() {
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
||||
Expect(hostNs.Do(func(_ ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
r, out, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
|
||||
Expect(err).NotTo(HaveOccurred(), string(out))
|
||||
@ -485,7 +485,7 @@ var _ = Describe("bandwidth test", func() {
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
|
||||
Expect(hostNs.Do(func(n ns.NetNS) error {
|
||||
Expect(hostNs.Do(func(_ ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
ifbLink, err := netlink.LinkByName(hostIfname)
|
||||
@ -551,7 +551,7 @@ var _ = Describe("bandwidth test", func() {
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
||||
Expect(hostNs.Do(func(_ ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
|
||||
@ -601,7 +601,7 @@ var _ = Describe("bandwidth test", func() {
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
||||
Expect(hostNs.Do(func(_ ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
_, out, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
|
||||
Expect(err).NotTo(HaveOccurred(), string(out))
|
||||
@ -669,7 +669,7 @@ var _ = Describe("bandwidth test", func() {
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
||||
Expect(hostNs.Do(func(_ ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
r, out, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
|
||||
Expect(err).NotTo(HaveOccurred(), string(out))
|
||||
@ -706,7 +706,7 @@ var _ = Describe("bandwidth test", func() {
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
|
||||
Expect(hostNs.Do(func(n ns.NetNS) error {
|
||||
Expect(hostNs.Do(func(_ ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
ifbLink, err := netlink.LinkByName(hostIfname)
|
||||
@ -768,7 +768,7 @@ var _ = Describe("bandwidth test", func() {
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
||||
Expect(hostNs.Do(func(_ ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
|
||||
@ -801,7 +801,7 @@ var _ = Describe("bandwidth test", func() {
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
||||
Expect(hostNs.Do(func(_ ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
|
||||
@ -853,7 +853,7 @@ var _ = Describe("bandwidth test", func() {
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
||||
Expect(hostNs.Do(func(_ ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
|
||||
|
@ -106,13 +106,13 @@ func makeTCPClientInNS(netns string, address string, port int, numBytes int) {
|
||||
}
|
||||
|
||||
func createVeth(hostNs ns.NetNS, hostVethIfName string, containerNs ns.NetNS, containerVethIfName string, hostIP []byte, containerIP []byte, hostIfaceMTU int) {
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = hostVethIfName
|
||||
linkAttrs.Flags = net.FlagUp
|
||||
linkAttrs.MTU = hostIfaceMTU
|
||||
vethDeviceRequest := &netlink.Veth{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: hostVethIfName,
|
||||
Flags: net.FlagUp,
|
||||
MTU: hostIfaceMTU,
|
||||
},
|
||||
PeerName: containerVethIfName,
|
||||
LinkAttrs: linkAttrs,
|
||||
PeerName: containerVethIfName,
|
||||
}
|
||||
|
||||
err := hostNs.Do(func(_ ns.NetNS) error {
|
||||
@ -193,12 +193,12 @@ func createVeth(hostNs ns.NetNS, hostVethIfName string, containerNs ns.NetNS, co
|
||||
}
|
||||
|
||||
func createVethInOneNs(netNS ns.NetNS, vethName, peerName string) {
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = vethName
|
||||
linkAttrs.Flags = net.FlagUp
|
||||
vethDeviceRequest := &netlink.Veth{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: vethName,
|
||||
Flags: net.FlagUp,
|
||||
},
|
||||
PeerName: peerName,
|
||||
LinkAttrs: linkAttrs,
|
||||
PeerName: peerName,
|
||||
}
|
||||
|
||||
err := netNS.Do(func(_ ns.NetNS) error {
|
||||
@ -222,13 +222,13 @@ func createMacvlan(netNS ns.NetNS, master, macvlanName string) {
|
||||
return fmt.Errorf("failed to lookup master %q: %v", master, err)
|
||||
}
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.MTU = m.Attrs().MTU
|
||||
linkAttrs.Name = macvlanName
|
||||
linkAttrs.ParentIndex = m.Attrs().Index
|
||||
macvlanDeviceRequest := &netlink.Macvlan{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
MTU: m.Attrs().MTU,
|
||||
Name: macvlanName,
|
||||
ParentIndex: m.Attrs().Index,
|
||||
},
|
||||
Mode: netlink.MACVLAN_MODE_BRIDGE,
|
||||
LinkAttrs: linkAttrs,
|
||||
Mode: netlink.MACVLAN_MODE_BRIDGE,
|
||||
}
|
||||
|
||||
if err = netlink.LinkAdd(macvlanDeviceRequest); err != nil {
|
||||
|
@ -27,11 +27,14 @@ import (
|
||||
const latencyInMillis = 25
|
||||
|
||||
func CreateIfb(ifbDeviceName string, mtu int) error {
|
||||
// do not set TxQLen > 0 nor TxQLen == -1 until issues have been fixed with numrxqueues / numtxqueues across interfaces
|
||||
// which needs to get set on IFB devices via upstream library: see hint https://github.com/containernetworking/plugins/pull/1097
|
||||
err := netlink.LinkAdd(&netlink.Ifb{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: ifbDeviceName,
|
||||
Flags: net.FlagUp,
|
||||
MTU: mtu,
|
||||
Name: ifbDeviceName,
|
||||
Flags: net.FlagUp,
|
||||
MTU: mtu,
|
||||
TxQLen: 0,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -240,7 +240,13 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.VersionsStartingFrom("0.3.0"), bv.BuildString("bandwidth"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
/* FIXME GC */
|
||||
/* FIXME Status */
|
||||
}, version.VersionsStartingFrom("0.3.0"), bv.BuildString("bandwidth"))
|
||||
}
|
||||
|
||||
func SafeQdiscList(link netlink.Link) ([]netlink.Qdisc, error) {
|
||||
|
@ -179,7 +179,13 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.VersionsStartingFrom("0.4.0"), bv.BuildString("firewall"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
/* FIXME GC */
|
||||
/* FIXME Status */
|
||||
}, version.VersionsStartingFrom("0.4.0"), bv.BuildString("firewall"))
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
|
@ -86,7 +86,8 @@ func spawnSessionDbus(wg *sync.WaitGroup) (string, *exec.Cmd) {
|
||||
bytes, err := bufio.NewReader(stdout).ReadString('\n')
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
busAddr := strings.TrimSpace(bytes)
|
||||
Expect(strings.HasPrefix(busAddr, "unix:abstract")).To(BeTrue())
|
||||
Expect(strings.HasPrefix(busAddr, "unix:abstract") ||
|
||||
strings.HasPrefix(busAddr, "unix:path")).To(BeTrue())
|
||||
|
||||
var startWg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
@ -205,10 +205,10 @@ var _ = Describe("firewall plugin iptables backend", func() {
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = IFNAME
|
||||
err = netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: IFNAME,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
_, err = netlink.LinkByName(IFNAME)
|
||||
|
@ -67,13 +67,27 @@ func (c *chain) setup(ipt *iptables.IPTables) error {
|
||||
// teardown idempotently deletes a chain. It will not error if the chain doesn't exist.
|
||||
// It will first delete all references to this chain in the entryChains.
|
||||
func (c *chain) teardown(ipt *iptables.IPTables) error {
|
||||
// flush the chain
|
||||
// This will succeed *and create the chain* if it does not exist.
|
||||
// If the chain doesn't exist, the next checks will fail.
|
||||
if err := utils.ClearChain(ipt, c.table, c.name); err != nil {
|
||||
return err
|
||||
// nothing to do if the custom chain doesn't exist to begin with
|
||||
exists, err := ipt.ChainExists(c.table, c.name)
|
||||
if err == nil && !exists {
|
||||
return nil
|
||||
}
|
||||
// delete references created by setup()
|
||||
for _, entryChain := range c.entryChains {
|
||||
for _, rule := range c.entryRules {
|
||||
r := []string{}
|
||||
r = append(r, rule...)
|
||||
r = append(r, "-j", c.name)
|
||||
|
||||
ipt.Delete(c.table, entryChain, r...)
|
||||
}
|
||||
}
|
||||
// if chain deletion succeeds now, all references are gone
|
||||
if err := ipt.ClearAndDeleteChain(c.table, c.name); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// find references the hard way
|
||||
for _, entryChain := range c.entryChains {
|
||||
entryChainRules, err := ipt.List(c.table, entryChain)
|
||||
if err != nil || len(entryChainRules) < 1 {
|
||||
@ -98,12 +112,12 @@ func (c *chain) teardown(ipt *iptables.IPTables) error {
|
||||
}
|
||||
}
|
||||
|
||||
return utils.DeleteChain(ipt, c.table, c.name)
|
||||
return ipt.ClearAndDeleteChain(c.table, c.name)
|
||||
}
|
||||
|
||||
// check the chain.
|
||||
func (c *chain) check(ipt *iptables.IPTables) error {
|
||||
exists, err := utils.ChainExists(ipt, c.table, c.name)
|
||||
exists, err := ipt.ChainExists(c.table, c.name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -37,9 +37,22 @@ import (
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
current "github.com/containernetworking/cni/pkg/types/100"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
"github.com/containernetworking/plugins/pkg/utils"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
type PortMapper interface {
|
||||
forwardPorts(config *PortMapConf, containerNet net.IPNet) error
|
||||
checkPorts(config *PortMapConf, containerNet net.IPNet) error
|
||||
unforwardPorts(config *PortMapConf) error
|
||||
}
|
||||
|
||||
// These are vars rather than consts so we can "&" them
|
||||
var (
|
||||
iptablesBackend = "iptables"
|
||||
nftablesBackend = "nftables"
|
||||
)
|
||||
|
||||
// PortMapEntry corresponds to a single entry in the port_mappings argument,
|
||||
// see CONVENTIONS.md
|
||||
type PortMapEntry struct {
|
||||
@ -51,16 +64,23 @@ type PortMapEntry struct {
|
||||
|
||||
type PortMapConf struct {
|
||||
types.NetConf
|
||||
SNAT *bool `json:"snat,omitempty"`
|
||||
ConditionsV4 *[]string `json:"conditionsV4"`
|
||||
ConditionsV6 *[]string `json:"conditionsV6"`
|
||||
MasqAll bool `json:"masqAll,omitempty"`
|
||||
MarkMasqBit *int `json:"markMasqBit"`
|
||||
ExternalSetMarkChain *string `json:"externalSetMarkChain"`
|
||||
RuntimeConfig struct {
|
||||
|
||||
mapper PortMapper
|
||||
|
||||
// Generic config
|
||||
Backend *string `json:"backend,omitempty"`
|
||||
SNAT *bool `json:"snat,omitempty"`
|
||||
ConditionsV4 *[]string `json:"conditionsV4"`
|
||||
ConditionsV6 *[]string `json:"conditionsV6"`
|
||||
MasqAll bool `json:"masqAll,omitempty"`
|
||||
MarkMasqBit *int `json:"markMasqBit"`
|
||||
RuntimeConfig struct {
|
||||
PortMaps []PortMapEntry `json:"portMappings,omitempty"`
|
||||
} `json:"runtimeConfig,omitempty"`
|
||||
|
||||
// iptables-backend-specific config
|
||||
ExternalSetMarkChain *string `json:"externalSetMarkChain"`
|
||||
|
||||
// These are fields parsed out of the config or the environment;
|
||||
// included here for convenience
|
||||
ContainerID string `json:"-"`
|
||||
@ -89,7 +109,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
netConf.ContainerID = args.ContainerID
|
||||
|
||||
if netConf.ContIPv4.IP != nil {
|
||||
if err := forwardPorts(netConf, netConf.ContIPv4); err != nil {
|
||||
if err := netConf.mapper.forwardPorts(netConf, netConf.ContIPv4); err != nil {
|
||||
return err
|
||||
}
|
||||
// Delete conntrack entries for UDP to avoid conntrack blackholing traffic
|
||||
@ -98,10 +118,21 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
if err := deletePortmapStaleConnections(netConf.RuntimeConfig.PortMaps, unix.AF_INET); err != nil {
|
||||
log.Printf("failed to delete stale UDP conntrack entries for %s: %v", netConf.ContIPv4.IP, err)
|
||||
}
|
||||
|
||||
if *netConf.SNAT {
|
||||
// Set the route_localnet bit on the host interface, so that
|
||||
// 127/8 can cross a routing boundary.
|
||||
hostIfName := getRoutableHostIF(netConf.ContIPv4.IP)
|
||||
if hostIfName != "" {
|
||||
if err := enableLocalnetRouting(hostIfName); err != nil {
|
||||
return fmt.Errorf("unable to enable route_localnet: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if netConf.ContIPv6.IP != nil {
|
||||
if err := forwardPorts(netConf, netConf.ContIPv6); err != nil {
|
||||
if err := netConf.mapper.forwardPorts(netConf, netConf.ContIPv6); err != nil {
|
||||
return err
|
||||
}
|
||||
// Delete conntrack entries for UDP to avoid conntrack blackholing traffic
|
||||
@ -130,11 +161,17 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
|
||||
// We don't need to parse out whether or not we're using v6 or snat,
|
||||
// deletion is idempotent
|
||||
return unforwardPorts(netConf)
|
||||
return netConf.mapper.unforwardPorts(netConf)
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("portmap"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
/* FIXME GC */
|
||||
/* FIXME Status */
|
||||
}, version.All, bv.BuildString("portmap"))
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
@ -155,13 +192,13 @@ func cmdCheck(args *skel.CmdArgs) error {
|
||||
conf.ContainerID = args.ContainerID
|
||||
|
||||
if conf.ContIPv4.IP != nil {
|
||||
if err := checkPorts(conf, conf.ContIPv4); err != nil {
|
||||
if err := conf.mapper.checkPorts(conf, conf.ContIPv4); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if conf.ContIPv6.IP != nil {
|
||||
if err := checkPorts(conf, conf.ContIPv6); err != nil {
|
||||
if err := conf.mapper.checkPorts(conf, conf.ContIPv6); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -191,6 +228,8 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, *current.Result, er
|
||||
}
|
||||
}
|
||||
|
||||
conf.mapper = &portMapperIPTables{}
|
||||
|
||||
if conf.SNAT == nil {
|
||||
tvar := true
|
||||
conf.SNAT = &tvar
|
||||
@ -209,6 +248,21 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, *current.Result, er
|
||||
return nil, nil, fmt.Errorf("MasqMarkBit must be between 0 and 31")
|
||||
}
|
||||
|
||||
err := ensureBackend(&conf)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
switch *conf.Backend {
|
||||
case iptablesBackend:
|
||||
conf.mapper = &portMapperIPTables{}
|
||||
|
||||
case nftablesBackend:
|
||||
conf.mapper = &portMapperNFTables{}
|
||||
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unrecognized backend %q", *conf.Backend)
|
||||
}
|
||||
|
||||
// Reject invalid port numbers
|
||||
for _, pm := range conf.RuntimeConfig.PortMaps {
|
||||
if pm.ContainerPort <= 0 {
|
||||
@ -248,3 +302,59 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, *current.Result, er
|
||||
|
||||
return &conf, result, nil
|
||||
}
|
||||
|
||||
// ensureBackend validates and/or sets conf.Backend
|
||||
func ensureBackend(conf *PortMapConf) error {
|
||||
backendConfig := make(map[string][]string)
|
||||
|
||||
if conf.ExternalSetMarkChain != nil {
|
||||
backendConfig[iptablesBackend] = append(backendConfig[iptablesBackend], "externalSetMarkChain")
|
||||
}
|
||||
if conditionsBackend := detectBackendOfConditions(conf.ConditionsV4); conditionsBackend != "" {
|
||||
backendConfig[conditionsBackend] = append(backendConfig[conditionsBackend], "conditionsV4")
|
||||
}
|
||||
if conditionsBackend := detectBackendOfConditions(conf.ConditionsV6); conditionsBackend != "" {
|
||||
backendConfig[conditionsBackend] = append(backendConfig[conditionsBackend], "conditionsV6")
|
||||
}
|
||||
|
||||
// If backend wasn't requested explicitly, default to iptables, unless it is not
|
||||
// available (and nftables is). FIXME: flip this default at some point.
|
||||
if conf.Backend == nil {
|
||||
if !utils.SupportsIPTables() && utils.SupportsNFTables() {
|
||||
conf.Backend = &nftablesBackend
|
||||
} else {
|
||||
conf.Backend = &iptablesBackend
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we dont have config for the wrong backend
|
||||
var wrongBackend string
|
||||
if *conf.Backend == iptablesBackend {
|
||||
wrongBackend = nftablesBackend
|
||||
} else {
|
||||
wrongBackend = iptablesBackend
|
||||
}
|
||||
if len(backendConfig[wrongBackend]) > 0 {
|
||||
return fmt.Errorf("%s backend was requested but configuration contains %s-specific options %v", *conf.Backend, wrongBackend, backendConfig[wrongBackend])
|
||||
}
|
||||
|
||||
// OK
|
||||
return nil
|
||||
}
|
||||
|
||||
// detectBackendOfConditions returns "iptables" if conditions contains iptables
|
||||
// conditions, "nftables" if it contains nftables conditions, and "" if it is empty.
|
||||
func detectBackendOfConditions(conditions *[]string) string {
|
||||
if conditions == nil || len(*conditions) == 0 || (*conditions)[0] == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
// The first character of any iptables condition would either be an hyphen
|
||||
// (e.g. "-d", "--sport", "-m") or an exclamation mark.
|
||||
// No nftables condition would start that way. (An nftables condition might
|
||||
// include a negative number, but not as the first token.)
|
||||
if (*conditions)[0][0] == '-' || (*conditions)[0][0] == '!' {
|
||||
return iptablesBackend
|
||||
}
|
||||
return nftablesBackend
|
||||
}
|
||||
|
@ -25,7 +25,6 @@ import (
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/utils"
|
||||
"github.com/containernetworking/plugins/pkg/utils/sysctl"
|
||||
)
|
||||
|
||||
// This creates the chains to be added to iptables. The basic structure is
|
||||
@ -52,9 +51,11 @@ const (
|
||||
OldTopLevelSNATChainName = "CNI-HOSTPORT-SNAT"
|
||||
)
|
||||
|
||||
type portMapperIPTables struct{}
|
||||
|
||||
// forwardPorts establishes port forwarding to a given container IP.
|
||||
// containerNet.IP can be either v4 or v6.
|
||||
func forwardPorts(config *PortMapConf, containerNet net.IPNet) error {
|
||||
func (*portMapperIPTables) forwardPorts(config *PortMapConf, containerNet net.IPNet) error {
|
||||
isV6 := (containerNet.IP.To4() == nil)
|
||||
|
||||
var ipt *iptables.IPTables
|
||||
@ -87,17 +88,6 @@ func forwardPorts(config *PortMapConf, containerNet net.IPNet) error {
|
||||
return fmt.Errorf("unable to create chain %s: %v", setMarkChain.name, err)
|
||||
}
|
||||
}
|
||||
|
||||
if !isV6 {
|
||||
// Set the route_localnet bit on the host interface, so that
|
||||
// 127/8 can cross a routing boundary.
|
||||
hostIfName := getRoutableHostIF(containerNet.IP)
|
||||
if hostIfName != "" {
|
||||
if err := enableLocalnetRouting(hostIfName); err != nil {
|
||||
return fmt.Errorf("unable to enable route_localnet: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate the DNAT (actual port forwarding) rules
|
||||
@ -117,7 +107,7 @@ func forwardPorts(config *PortMapConf, containerNet net.IPNet) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkPorts(config *PortMapConf, containerNet net.IPNet) error {
|
||||
func (*portMapperIPTables) checkPorts(config *PortMapConf, containerNet net.IPNet) error {
|
||||
isV6 := (containerNet.IP.To4() == nil)
|
||||
dnatChain := genDnatChain(config.Name, config.ContainerID)
|
||||
fillDnatRules(&dnatChain, config, containerNet)
|
||||
@ -344,14 +334,6 @@ func genMarkMasqChain(markBit int) chain {
|
||||
return ch
|
||||
}
|
||||
|
||||
// enableLocalnetRouting tells the kernel not to treat 127/8 as a martian,
|
||||
// so that connections with a source ip of 127/8 can cross a routing boundary.
|
||||
func enableLocalnetRouting(ifName string) error {
|
||||
routeLocalnetPath := "net/ipv4/conf/" + ifName + "/route_localnet"
|
||||
_, err := sysctl.Sysctl(routeLocalnetPath, "1")
|
||||
return err
|
||||
}
|
||||
|
||||
// genOldSnatChain is no longer used, but used to be created. We'll try and
|
||||
// tear it down in case the plugin version changed between ADD and DEL
|
||||
func genOldSnatChain(netName, containerID string) chain {
|
||||
@ -372,7 +354,7 @@ func genOldSnatChain(netName, containerID string) chain {
|
||||
// don't know which protocols were used.
|
||||
// So, we first check that iptables is "generally OK" by doing a check. If
|
||||
// not, we ignore the error, unless neither v4 nor v6 are OK.
|
||||
func unforwardPorts(config *PortMapConf) error {
|
||||
func (*portMapperIPTables) unforwardPorts(config *PortMapConf) error {
|
||||
dnatChain := genDnatChain(config.Name, config.ContainerID)
|
||||
|
||||
// Might be lying around from old versions
|
252
plugins/meta/portmap/portmap_iptables_test.go
Normal file
252
plugins/meta/portmap/portmap_iptables_test.go
Normal file
@ -0,0 +1,252 @@
|
||||
// Copyright 2017 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
)
|
||||
|
||||
var _ = Describe("portmapping configuration (iptables)", func() {
|
||||
netName := "testNetName"
|
||||
containerID := "icee6giejonei6sohng6ahngee7laquohquee9shiGo7fohferakah3Feiyoolu2pei7ciPhoh7shaoX6vai3vuf0ahfaeng8yohb9ceu0daez5hashee8ooYai5wa3y"
|
||||
|
||||
for _, ver := range []string{"0.3.0", "0.3.1", "0.4.0", "1.0.0"} {
|
||||
// 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
|
||||
|
||||
Describe("Generating iptables chains", func() {
|
||||
Context("for DNAT", func() {
|
||||
It(fmt.Sprintf("[%s] generates a correct standard container chain", ver), func() {
|
||||
ch := genDnatChain(netName, containerID)
|
||||
|
||||
Expect(ch).To(Equal(chain{
|
||||
table: "nat",
|
||||
name: "CNI-DN-bfd599665540dd91d5d28",
|
||||
entryChains: []string{TopLevelDNATChainName},
|
||||
}))
|
||||
configBytes := []byte(fmt.Sprintf(`{
|
||||
"name": "test",
|
||||
"type": "portmap",
|
||||
"cniVersion": "%s",
|
||||
"runtimeConfig": {
|
||||
"portMappings": [
|
||||
{ "hostPort": 8080, "containerPort": 80, "protocol": "tcp"},
|
||||
{ "hostPort": 8081, "containerPort": 80, "protocol": "tcp"},
|
||||
{ "hostPort": 8080, "containerPort": 81, "protocol": "udp"},
|
||||
{ "hostPort": 8082, "containerPort": 82, "protocol": "udp"},
|
||||
{ "hostPort": 8083, "containerPort": 83, "protocol": "tcp", "hostIP": "192.168.0.2"},
|
||||
{ "hostPort": 8084, "containerPort": 84, "protocol": "tcp", "hostIP": "0.0.0.0"},
|
||||
{ "hostPort": 8085, "containerPort": 85, "protocol": "tcp", "hostIP": "2001:db8:a::1"},
|
||||
{ "hostPort": 8086, "containerPort": 86, "protocol": "tcp", "hostIP": "::"}
|
||||
]
|
||||
},
|
||||
"snat": true,
|
||||
"conditionsV4": ["-a", "b"],
|
||||
"conditionsV6": ["-c", "d"]
|
||||
}`, ver))
|
||||
|
||||
conf, _, err := parseConfig(configBytes, "foo")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
conf.ContainerID = containerID
|
||||
|
||||
ch = genDnatChain(conf.Name, containerID)
|
||||
Expect(ch).To(Equal(chain{
|
||||
table: "nat",
|
||||
name: "CNI-DN-67e92b96e692a494b6b85",
|
||||
entryChains: []string{"CNI-HOSTPORT-DNAT"},
|
||||
}))
|
||||
|
||||
n, err := types.ParseCIDR("10.0.0.2/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
fillDnatRules(&ch, conf, *n)
|
||||
|
||||
Expect(ch.entryRules).To(Equal([][]string{
|
||||
{
|
||||
"-m", "comment", "--comment",
|
||||
fmt.Sprintf("dnat name: \"test\" id: \"%s\"", containerID),
|
||||
"-m", "multiport",
|
||||
"-p", "tcp",
|
||||
"--destination-ports", "8080,8081,8083,8084,8085,8086",
|
||||
"-a", "b",
|
||||
},
|
||||
{
|
||||
"-m", "comment", "--comment",
|
||||
fmt.Sprintf("dnat name: \"test\" id: \"%s\"", containerID),
|
||||
"-m", "multiport",
|
||||
"-p", "udp",
|
||||
"--destination-ports", "8080,8082",
|
||||
"-a", "b",
|
||||
},
|
||||
}))
|
||||
|
||||
Expect(ch.rules).To(Equal([][]string{
|
||||
// tcp rules and not hostIP
|
||||
{"-p", "tcp", "--dport", "8080", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8080", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||
{"-p", "tcp", "--dport", "8081", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8081", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||
// udp rules and not hostIP
|
||||
{"-p", "udp", "--dport", "8080", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8080", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:81"},
|
||||
{"-p", "udp", "--dport", "8082", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8082", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8082", "-j", "DNAT", "--to-destination", "10.0.0.2:82"},
|
||||
// tcp rules and hostIP
|
||||
{"-p", "tcp", "--dport", "8083", "-d", "192.168.0.2", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8083", "-d", "192.168.0.2", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8083", "-d", "192.168.0.2", "-j", "DNAT", "--to-destination", "10.0.0.2:83"},
|
||||
// tcp rules and hostIP = "0.0.0.0"
|
||||
{"-p", "tcp", "--dport", "8084", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8084", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8084", "-j", "DNAT", "--to-destination", "10.0.0.2:84"},
|
||||
}))
|
||||
|
||||
ch.rules = nil
|
||||
ch.entryRules = nil
|
||||
|
||||
n, err = types.ParseCIDR("2001:db8::2/64")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
fillDnatRules(&ch, conf, *n)
|
||||
|
||||
Expect(ch.rules).To(Equal([][]string{
|
||||
// tcp rules and not hostIP
|
||||
{"-p", "tcp", "--dport", "8080", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "[2001:db8::2]:80"},
|
||||
{"-p", "tcp", "--dport", "8081", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "[2001:db8::2]:80"},
|
||||
// udp rules and not hostIP
|
||||
{"-p", "udp", "--dport", "8080", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8080", "-j", "DNAT", "--to-destination", "[2001:db8::2]:81"},
|
||||
{"-p", "udp", "--dport", "8082", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8082", "-j", "DNAT", "--to-destination", "[2001:db8::2]:82"},
|
||||
// tcp rules and hostIP
|
||||
{"-p", "tcp", "--dport", "8085", "-d", "2001:db8:a::1", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8085", "-d", "2001:db8:a::1", "-j", "DNAT", "--to-destination", "[2001:db8::2]:85"},
|
||||
// tcp rules and hostIP = "::"
|
||||
{"-p", "tcp", "--dport", "8086", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8086", "-j", "DNAT", "--to-destination", "[2001:db8::2]:86"},
|
||||
}))
|
||||
|
||||
// Disable snat, generate rules
|
||||
ch.rules = nil
|
||||
ch.entryRules = nil
|
||||
fvar := false
|
||||
conf.SNAT = &fvar
|
||||
|
||||
n, err = types.ParseCIDR("10.0.0.2/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
fillDnatRules(&ch, conf, *n)
|
||||
Expect(ch.rules).To(Equal([][]string{
|
||||
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||
{"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||
{"-p", "udp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:81"},
|
||||
{"-p", "udp", "--dport", "8082", "-j", "DNAT", "--to-destination", "10.0.0.2:82"},
|
||||
{"-p", "tcp", "--dport", "8083", "-d", "192.168.0.2", "-j", "DNAT", "--to-destination", "10.0.0.2:83"},
|
||||
{"-p", "tcp", "--dport", "8084", "-j", "DNAT", "--to-destination", "10.0.0.2:84"},
|
||||
}))
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] generates a correct chain with external mark", ver), func() {
|
||||
ch := genDnatChain(netName, containerID)
|
||||
|
||||
Expect(ch).To(Equal(chain{
|
||||
table: "nat",
|
||||
name: "CNI-DN-bfd599665540dd91d5d28",
|
||||
entryChains: []string{TopLevelDNATChainName},
|
||||
}))
|
||||
configBytes := []byte(fmt.Sprintf(`{
|
||||
"name": "test",
|
||||
"type": "portmap",
|
||||
"cniVersion": "%s",
|
||||
"runtimeConfig": {
|
||||
"portMappings": [
|
||||
{ "hostPort": 8080, "containerPort": 80, "protocol": "tcp"}
|
||||
]
|
||||
},
|
||||
"externalSetMarkChain": "PLZ-SET-MARK",
|
||||
"conditionsV4": ["-a", "b"],
|
||||
"conditionsV6": ["-c", "d"]
|
||||
}`, ver))
|
||||
|
||||
conf, _, err := parseConfig(configBytes, "foo")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
conf.ContainerID = containerID
|
||||
|
||||
ch = genDnatChain(conf.Name, containerID)
|
||||
n, err := types.ParseCIDR("10.0.0.2/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
fillDnatRules(&ch, conf, *n)
|
||||
Expect(ch.rules).To(Equal([][]string{
|
||||
{"-p", "tcp", "--dport", "8080", "-s", "10.0.0.2/24", "-j", "PLZ-SET-MARK"},
|
||||
{"-p", "tcp", "--dport", "8080", "-s", "127.0.0.1", "-j", "PLZ-SET-MARK"},
|
||||
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||
}))
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] generates a correct top-level chain", ver), func() {
|
||||
ch := genToplevelDnatChain()
|
||||
|
||||
Expect(ch).To(Equal(chain{
|
||||
table: "nat",
|
||||
name: "CNI-HOSTPORT-DNAT",
|
||||
entryChains: []string{"PREROUTING", "OUTPUT"},
|
||||
entryRules: [][]string{{"-m", "addrtype", "--dst-type", "LOCAL"}},
|
||||
}))
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] generates the correct mark chains", ver), func() {
|
||||
masqBit := 5
|
||||
ch := genSetMarkChain(masqBit)
|
||||
Expect(ch).To(Equal(chain{
|
||||
table: "nat",
|
||||
name: "CNI-HOSTPORT-SETMARK",
|
||||
rules: [][]string{{
|
||||
"-m", "comment",
|
||||
"--comment", "CNI portfwd masquerade mark",
|
||||
"-j", "MARK",
|
||||
"--set-xmark", "0x20/0x20",
|
||||
}},
|
||||
}))
|
||||
|
||||
ch = genMarkMasqChain(masqBit)
|
||||
Expect(ch).To(Equal(chain{
|
||||
table: "nat",
|
||||
name: "CNI-HOSTPORT-MASQ",
|
||||
entryChains: []string{"POSTROUTING"},
|
||||
entryRules: [][]string{{
|
||||
"-m", "comment",
|
||||
"--comment", "CNI portfwd requiring masquerade",
|
||||
}},
|
||||
rules: [][]string{{
|
||||
"-m", "mark",
|
||||
"--mark", "0x20/0x20",
|
||||
"-j", "MASQUERADE",
|
||||
}},
|
||||
prependEntry: true,
|
||||
}))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
340
plugins/meta/portmap/portmap_nftables.go
Normal file
340
plugins/meta/portmap/portmap_nftables.go
Normal file
@ -0,0 +1,340 @@
|
||||
// Copyright 2023 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"sigs.k8s.io/knftables"
|
||||
)
|
||||
|
||||
const (
|
||||
tableName = "cni_hostport"
|
||||
|
||||
hostIPHostPortsChain = "hostip_hostports"
|
||||
hostPortsChain = "hostports"
|
||||
masqueradingChain = "masquerading"
|
||||
)
|
||||
|
||||
// The nftables portmap implementation is fairly similar to the iptables implementation:
|
||||
// we add a rule for each mapping, with a comment containing a hash of the container ID,
|
||||
// so that we can later reliably delete the rules we want. (This is important because in
|
||||
// edge cases, it's possible the plugin might see "ADD container A with IP 192.168.1.3",
|
||||
// followed by "ADD container B with IP 192.168.1.3" followed by "DEL container A with IP
|
||||
// 192.168.1.3", and we need to make sure that the DEL causes us to delete the rule for
|
||||
// container A, and not the rule for container B.) This iptables implementation actually
|
||||
// uses a separate chain per container but there's not really any need for that...
|
||||
//
|
||||
// As with pkg/ip/ipmasq_nftables_linux.go, it would be more nftables-y to have a chain
|
||||
// with a single rule doing a lookup against a map with an element per mapping, rather
|
||||
// than having a chain with a rule per mapping. But there's no easy, non-racy way to say
|
||||
// "delete the element 192.168.1.3 from the map, but only if it was added for container A,
|
||||
// not if it was added for container B".
|
||||
|
||||
type portMapperNFTables struct {
|
||||
ipv4 knftables.Interface
|
||||
ipv6 knftables.Interface
|
||||
}
|
||||
|
||||
// getPortMapNFT creates an nftables.Interface for port mapping for the IP family of ipn
|
||||
func (pmNFT *portMapperNFTables) getPortMapNFT(ipv6 bool) (knftables.Interface, error) {
|
||||
var err error
|
||||
if ipv6 {
|
||||
if pmNFT.ipv6 == nil {
|
||||
pmNFT.ipv6, err = knftables.New(knftables.IPv6Family, tableName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return pmNFT.ipv6, nil
|
||||
}
|
||||
|
||||
if pmNFT.ipv4 == nil {
|
||||
pmNFT.ipv4, err = knftables.New(knftables.IPv4Family, tableName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return pmNFT.ipv4, err
|
||||
}
|
||||
|
||||
// forwardPorts establishes port forwarding to a given container IP.
|
||||
// containerNet.IP can be either v4 or v6.
|
||||
func (pmNFT *portMapperNFTables) forwardPorts(config *PortMapConf, containerNet net.IPNet) error {
|
||||
isV6 := (containerNet.IP.To4() == nil)
|
||||
nft, err := pmNFT.getPortMapNFT(isV6)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var ipX string
|
||||
var conditions []string
|
||||
if isV6 {
|
||||
ipX = "ip6"
|
||||
if config.ConditionsV6 != nil {
|
||||
conditions = *config.ConditionsV6
|
||||
}
|
||||
} else if !isV6 {
|
||||
ipX = "ip"
|
||||
if config.ConditionsV4 != nil {
|
||||
conditions = *config.ConditionsV4
|
||||
}
|
||||
}
|
||||
|
||||
tx := nft.NewTransaction()
|
||||
|
||||
// Ensure basic rule structure
|
||||
tx.Add(&knftables.Table{
|
||||
Comment: knftables.PtrTo("CNI portmap plugin"),
|
||||
})
|
||||
|
||||
tx.Add(&knftables.Chain{
|
||||
Name: "hostports",
|
||||
})
|
||||
tx.Add(&knftables.Chain{
|
||||
Name: "hostip_hostports",
|
||||
})
|
||||
|
||||
tx.Add(&knftables.Chain{
|
||||
Name: "prerouting",
|
||||
Type: knftables.PtrTo(knftables.NATType),
|
||||
Hook: knftables.PtrTo(knftables.PreroutingHook),
|
||||
Priority: knftables.PtrTo(knftables.DNATPriority),
|
||||
})
|
||||
tx.Flush(&knftables.Chain{
|
||||
Name: "prerouting",
|
||||
})
|
||||
tx.Add(&knftables.Rule{
|
||||
Chain: "prerouting",
|
||||
Rule: knftables.Concat(
|
||||
conditions,
|
||||
"jump", hostIPHostPortsChain,
|
||||
),
|
||||
})
|
||||
tx.Add(&knftables.Rule{
|
||||
Chain: "prerouting",
|
||||
Rule: knftables.Concat(
|
||||
conditions,
|
||||
"jump", hostPortsChain,
|
||||
),
|
||||
})
|
||||
|
||||
tx.Add(&knftables.Chain{
|
||||
Name: "output",
|
||||
Type: knftables.PtrTo(knftables.NATType),
|
||||
Hook: knftables.PtrTo(knftables.OutputHook),
|
||||
Priority: knftables.PtrTo(knftables.DNATPriority),
|
||||
})
|
||||
tx.Flush(&knftables.Chain{
|
||||
Name: "output",
|
||||
})
|
||||
tx.Add(&knftables.Rule{
|
||||
Chain: "output",
|
||||
Rule: knftables.Concat(
|
||||
conditions,
|
||||
"jump", hostIPHostPortsChain,
|
||||
),
|
||||
})
|
||||
tx.Add(&knftables.Rule{
|
||||
Chain: "output",
|
||||
Rule: knftables.Concat(
|
||||
conditions,
|
||||
"fib daddr type local",
|
||||
"jump", hostPortsChain,
|
||||
),
|
||||
})
|
||||
|
||||
if *config.SNAT {
|
||||
tx.Add(&knftables.Chain{
|
||||
Name: masqueradingChain,
|
||||
Type: knftables.PtrTo(knftables.NATType),
|
||||
Hook: knftables.PtrTo(knftables.PostroutingHook),
|
||||
Priority: knftables.PtrTo(knftables.SNATPriority),
|
||||
})
|
||||
}
|
||||
|
||||
// Set up this container
|
||||
for _, e := range config.RuntimeConfig.PortMaps {
|
||||
useHostIP := false
|
||||
if e.HostIP != "" {
|
||||
hostIP := net.ParseIP(e.HostIP)
|
||||
isHostV6 := (hostIP.To4() == nil)
|
||||
// Ignore wrong-IP-family HostIPs
|
||||
if isV6 != isHostV6 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Unspecified addresses cannot be used as destination
|
||||
useHostIP = !hostIP.IsUnspecified()
|
||||
}
|
||||
|
||||
if useHostIP {
|
||||
tx.Add(&knftables.Rule{
|
||||
Chain: hostIPHostPortsChain,
|
||||
Rule: knftables.Concat(
|
||||
ipX, "daddr", e.HostIP,
|
||||
e.Protocol, "dport", e.HostPort,
|
||||
"dnat to", net.JoinHostPort(containerNet.IP.String(), strconv.Itoa(e.ContainerPort)),
|
||||
),
|
||||
Comment: &config.ContainerID,
|
||||
})
|
||||
} else {
|
||||
tx.Add(&knftables.Rule{
|
||||
Chain: hostPortsChain,
|
||||
Rule: knftables.Concat(
|
||||
e.Protocol, "dport", e.HostPort,
|
||||
"dnat to", net.JoinHostPort(containerNet.IP.String(), strconv.Itoa(e.ContainerPort)),
|
||||
),
|
||||
Comment: &config.ContainerID,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if *config.SNAT {
|
||||
// Add mark-to-masquerade rules for hairpin and localhost
|
||||
// In theory we should validate that the original dst IP and port are as
|
||||
// expected, but *any* traffic matching one of these patterns would need
|
||||
// to be masqueraded to be able to work correctly anyway.
|
||||
tx.Add(&knftables.Rule{
|
||||
Chain: masqueradingChain,
|
||||
Rule: knftables.Concat(
|
||||
ipX, "saddr", containerNet.IP,
|
||||
ipX, "daddr", containerNet.IP,
|
||||
"masquerade",
|
||||
),
|
||||
Comment: &config.ContainerID,
|
||||
})
|
||||
if !isV6 {
|
||||
tx.Add(&knftables.Rule{
|
||||
Chain: masqueradingChain,
|
||||
Rule: knftables.Concat(
|
||||
ipX, "saddr 127.0.0.1",
|
||||
ipX, "daddr", containerNet.IP,
|
||||
"masquerade",
|
||||
),
|
||||
Comment: &config.ContainerID,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
err = nft.Run(context.TODO(), tx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to set up nftables rules for port mappings: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pmNFT *portMapperNFTables) checkPorts(config *PortMapConf, containerNet net.IPNet) error {
|
||||
isV6 := (containerNet.IP.To4() == nil)
|
||||
|
||||
var hostPorts, hostIPHostPorts, masqueradings int
|
||||
for _, e := range config.RuntimeConfig.PortMaps {
|
||||
if e.HostIP != "" {
|
||||
hostIPHostPorts++
|
||||
} else {
|
||||
hostPorts++
|
||||
}
|
||||
}
|
||||
if *config.SNAT {
|
||||
masqueradings = len(config.RuntimeConfig.PortMaps)
|
||||
if isV6 {
|
||||
masqueradings *= 2
|
||||
}
|
||||
}
|
||||
|
||||
nft, err := pmNFT.getPortMapNFT(isV6)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hostPorts > 0 {
|
||||
err := checkPortsAgainstRules(nft, hostPortsChain, config.ContainerID, hostPorts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if hostIPHostPorts > 0 {
|
||||
err := checkPortsAgainstRules(nft, hostIPHostPortsChain, config.ContainerID, hostIPHostPorts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if masqueradings > 0 {
|
||||
err := checkPortsAgainstRules(nft, masqueradingChain, config.ContainerID, masqueradings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkPortsAgainstRules(nft knftables.Interface, chain, comment string, nPorts int) error {
|
||||
rules, err := nft.ListRules(context.TODO(), chain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
found := 0
|
||||
for _, r := range rules {
|
||||
if r.Comment != nil && *r.Comment == comment {
|
||||
found++
|
||||
}
|
||||
}
|
||||
if found < nPorts {
|
||||
return fmt.Errorf("missing hostport rules in %q chain", chain)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// unforwardPorts deletes any nftables rules created by this plugin.
|
||||
// It should be idempotent - it will not error if the chain does not exist.
|
||||
func (pmNFT *portMapperNFTables) unforwardPorts(config *PortMapConf) error {
|
||||
// Always clear both IPv4 and IPv6, just to be sure
|
||||
for _, family := range []knftables.Family{knftables.IPv4Family, knftables.IPv6Family} {
|
||||
nft, err := pmNFT.getPortMapNFT(family == knftables.IPv6Family)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
tx := nft.NewTransaction()
|
||||
for _, chain := range []string{hostPortsChain, hostIPHostPortsChain, masqueradingChain} {
|
||||
rules, err := nft.ListRules(context.TODO(), chain)
|
||||
if err != nil {
|
||||
if knftables.IsNotFound(err) {
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("could not list rules in table %s: %w", tableName, err)
|
||||
}
|
||||
|
||||
for _, r := range rules {
|
||||
if r.Comment != nil && *r.Comment == config.ContainerID {
|
||||
tx.Delete(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = nft.Run(context.TODO(), tx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting nftables rules: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
134
plugins/meta/portmap/portmap_nftables_test.go
Normal file
134
plugins/meta/portmap/portmap_nftables_test.go
Normal file
@ -0,0 +1,134 @@
|
||||
// Copyright 2023 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"sigs.k8s.io/knftables"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
)
|
||||
|
||||
var _ = Describe("portmapping configuration (nftables)", func() {
|
||||
containerID := "icee6giejonei6so"
|
||||
|
||||
for _, ver := range []string{"0.3.0", "0.3.1", "0.4.0", "1.0.0"} {
|
||||
// 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
|
||||
|
||||
Describe("nftables rules", func() {
|
||||
var pmNFT *portMapperNFTables
|
||||
var ipv4Fake, ipv6Fake *knftables.Fake
|
||||
BeforeEach(func() {
|
||||
ipv4Fake = knftables.NewFake(knftables.IPv4Family, tableName)
|
||||
ipv6Fake = knftables.NewFake(knftables.IPv6Family, tableName)
|
||||
pmNFT = &portMapperNFTables{
|
||||
ipv4: ipv4Fake,
|
||||
ipv6: ipv6Fake,
|
||||
}
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] generates correct rules on ADD", ver), func() {
|
||||
configBytes := []byte(fmt.Sprintf(`{
|
||||
"name": "test",
|
||||
"type": "portmap",
|
||||
"cniVersion": "%s",
|
||||
"backend": "nftables",
|
||||
"runtimeConfig": {
|
||||
"portMappings": [
|
||||
{ "hostPort": 8080, "containerPort": 80, "protocol": "tcp"},
|
||||
{ "hostPort": 8081, "containerPort": 80, "protocol": "tcp"},
|
||||
{ "hostPort": 8080, "containerPort": 81, "protocol": "udp"},
|
||||
{ "hostPort": 8082, "containerPort": 82, "protocol": "udp"},
|
||||
{ "hostPort": 8083, "containerPort": 83, "protocol": "tcp", "hostIP": "192.168.0.2"},
|
||||
{ "hostPort": 8084, "containerPort": 84, "protocol": "tcp", "hostIP": "0.0.0.0"},
|
||||
{ "hostPort": 8085, "containerPort": 85, "protocol": "tcp", "hostIP": "2001:db8:a::1"},
|
||||
{ "hostPort": 8086, "containerPort": 86, "protocol": "tcp", "hostIP": "::"}
|
||||
]
|
||||
},
|
||||
"snat": true,
|
||||
"conditionsV4": ["a", "b"],
|
||||
"conditionsV6": ["c", "d"]
|
||||
}`, ver))
|
||||
|
||||
conf, _, err := parseConfig(configBytes, "foo")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
conf.ContainerID = containerID
|
||||
|
||||
containerNet, err := types.ParseCIDR("10.0.0.2/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = pmNFT.forwardPorts(conf, *containerNet)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
expectedRules := strings.TrimSpace(`
|
||||
add table ip cni_hostport { comment "CNI portmap plugin" ; }
|
||||
add chain ip cni_hostport hostip_hostports
|
||||
add chain ip cni_hostport hostports
|
||||
add chain ip cni_hostport masquerading { type nat hook postrouting priority 100 ; }
|
||||
add chain ip cni_hostport output { type nat hook output priority -100 ; }
|
||||
add chain ip cni_hostport prerouting { type nat hook prerouting priority -100 ; }
|
||||
add rule ip cni_hostport hostip_hostports ip daddr 192.168.0.2 tcp dport 8083 dnat to 10.0.0.2:83 comment "icee6giejonei6so"
|
||||
add rule ip cni_hostport hostports tcp dport 8080 dnat to 10.0.0.2:80 comment "icee6giejonei6so"
|
||||
add rule ip cni_hostport hostports tcp dport 8081 dnat to 10.0.0.2:80 comment "icee6giejonei6so"
|
||||
add rule ip cni_hostport hostports udp dport 8080 dnat to 10.0.0.2:81 comment "icee6giejonei6so"
|
||||
add rule ip cni_hostport hostports udp dport 8082 dnat to 10.0.0.2:82 comment "icee6giejonei6so"
|
||||
add rule ip cni_hostport hostports tcp dport 8084 dnat to 10.0.0.2:84 comment "icee6giejonei6so"
|
||||
add rule ip cni_hostport masquerading ip saddr 10.0.0.2 ip daddr 10.0.0.2 masquerade comment "icee6giejonei6so"
|
||||
add rule ip cni_hostport masquerading ip saddr 127.0.0.1 ip daddr 10.0.0.2 masquerade comment "icee6giejonei6so"
|
||||
add rule ip cni_hostport output a b jump hostip_hostports
|
||||
add rule ip cni_hostport output a b fib daddr type local jump hostports
|
||||
add rule ip cni_hostport prerouting a b jump hostip_hostports
|
||||
add rule ip cni_hostport prerouting a b jump hostports
|
||||
`)
|
||||
actualRules := strings.TrimSpace(ipv4Fake.Dump())
|
||||
Expect(actualRules).To(Equal(expectedRules))
|
||||
|
||||
// Disable snat, generate IPv6 rules
|
||||
*conf.SNAT = false
|
||||
containerNet, err = types.ParseCIDR("2001:db8::2/64")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = pmNFT.forwardPorts(conf, *containerNet)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
expectedRules = strings.TrimSpace(`
|
||||
add table ip6 cni_hostport { comment "CNI portmap plugin" ; }
|
||||
add chain ip6 cni_hostport hostip_hostports
|
||||
add chain ip6 cni_hostport hostports
|
||||
add chain ip6 cni_hostport output { type nat hook output priority -100 ; }
|
||||
add chain ip6 cni_hostport prerouting { type nat hook prerouting priority -100 ; }
|
||||
add rule ip6 cni_hostport hostip_hostports ip6 daddr 2001:db8:a::1 tcp dport 8085 dnat to [2001:db8::2]:85 comment "icee6giejonei6so"
|
||||
add rule ip6 cni_hostport hostports tcp dport 8080 dnat to [2001:db8::2]:80 comment "icee6giejonei6so"
|
||||
add rule ip6 cni_hostport hostports tcp dport 8081 dnat to [2001:db8::2]:80 comment "icee6giejonei6so"
|
||||
add rule ip6 cni_hostport hostports udp dport 8080 dnat to [2001:db8::2]:81 comment "icee6giejonei6so"
|
||||
add rule ip6 cni_hostport hostports udp dport 8082 dnat to [2001:db8::2]:82 comment "icee6giejonei6so"
|
||||
add rule ip6 cni_hostport hostports tcp dport 8086 dnat to [2001:db8::2]:86 comment "icee6giejonei6so"
|
||||
add rule ip6 cni_hostport output c d jump hostip_hostports
|
||||
add rule ip6 cni_hostport output c d fib daddr type local jump hostports
|
||||
add rule ip6 cni_hostport prerouting c d jump hostip_hostports
|
||||
add rule ip6 cni_hostport prerouting c d jump hostports
|
||||
`)
|
||||
actualRules = strings.TrimSpace(ipv6Fake.Dump())
|
||||
Expect(actualRules).To(Equal(expectedRules))
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
@ -24,9 +24,6 @@ import (
|
||||
)
|
||||
|
||||
var _ = Describe("portmapping configuration", func() {
|
||||
netName := "testNetName"
|
||||
containerID := "icee6giejonei6sohng6ahngee7laquohquee9shiGo7fohferakah3Feiyoolu2pei7ciPhoh7shaoX6vai3vuf0ahfaeng8yohb9ceu0daez5hashee8ooYai5wa3y"
|
||||
|
||||
for _, ver := range []string{"0.3.0", "0.3.1", "0.4.0", "1.0.0"} {
|
||||
// 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.
|
||||
@ -38,6 +35,7 @@ var _ = Describe("portmapping configuration", func() {
|
||||
"name": "test",
|
||||
"type": "portmap",
|
||||
"cniVersion": "%s",
|
||||
"backend": "iptables",
|
||||
"runtimeConfig": {
|
||||
"portMappings": [
|
||||
{ "hostPort": 8080, "containerPort": 80, "protocol": "tcp"},
|
||||
@ -45,8 +43,8 @@ var _ = Describe("portmapping configuration", func() {
|
||||
]
|
||||
},
|
||||
"snat": false,
|
||||
"conditionsV4": ["a", "b"],
|
||||
"conditionsV6": ["c", "d"],
|
||||
"conditionsV4": ["-s", "1.2.3.4"],
|
||||
"conditionsV6": ["!", "-s", "12::34"],
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{"name": "host"},
|
||||
@ -77,8 +75,8 @@ var _ = Describe("portmapping configuration", func() {
|
||||
c, _, err := parseConfig(configBytes, "container")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(c.CNIVersion).To(Equal(ver))
|
||||
Expect(c.ConditionsV4).To(Equal(&[]string{"a", "b"}))
|
||||
Expect(c.ConditionsV6).To(Equal(&[]string{"c", "d"}))
|
||||
Expect(c.ConditionsV4).To(Equal(&[]string{"-s", "1.2.3.4"}))
|
||||
Expect(c.ConditionsV6).To(Equal(&[]string{"!", "-s", "12::34"}))
|
||||
fvar := false
|
||||
Expect(c.SNAT).To(Equal(&fvar))
|
||||
Expect(c.Name).To(Equal("test"))
|
||||
@ -97,15 +95,16 @@ var _ = Describe("portmapping configuration", func() {
|
||||
"name": "test",
|
||||
"type": "portmap",
|
||||
"cniVersion": "%s",
|
||||
"backend": "iptables",
|
||||
"snat": false,
|
||||
"conditionsV4": ["a", "b"],
|
||||
"conditionsV6": ["c", "d"]
|
||||
"conditionsV4": ["-s", "1.2.3.4"],
|
||||
"conditionsV6": ["-s", "12::34"]
|
||||
}`, ver))
|
||||
c, _, err := parseConfig(configBytes, "container")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(c.CNIVersion).To(Equal(ver))
|
||||
Expect(c.ConditionsV4).To(Equal(&[]string{"a", "b"}))
|
||||
Expect(c.ConditionsV6).To(Equal(&[]string{"c", "d"}))
|
||||
Expect(c.ConditionsV4).To(Equal(&[]string{"-s", "1.2.3.4"}))
|
||||
Expect(c.ConditionsV6).To(Equal(&[]string{"-s", "12::34"}))
|
||||
fvar := false
|
||||
Expect(c.SNAT).To(Equal(&fvar))
|
||||
Expect(c.Name).To(Equal("test"))
|
||||
@ -116,9 +115,10 @@ var _ = Describe("portmapping configuration", func() {
|
||||
"name": "test",
|
||||
"type": "portmap",
|
||||
"cniVersion": "%s",
|
||||
"backend": "iptables",
|
||||
"snat": false,
|
||||
"conditionsV4": ["a", "b"],
|
||||
"conditionsV6": ["c", "d"],
|
||||
"conditionsV4": ["-s", "1.2.3.4"],
|
||||
"conditionsV6": ["-s", "12::34"],
|
||||
"runtimeConfig": {
|
||||
"portMappings": [
|
||||
{ "hostPort": 0, "containerPort": 80, "protocol": "tcp"}
|
||||
@ -129,6 +129,82 @@ var _ = Describe("portmapping configuration", func() {
|
||||
Expect(err).To(MatchError("Invalid host port number: 0"))
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] defaults to iptables when backend is not specified", ver), func() {
|
||||
// "defaults to iptables" is only true if iptables is installed
|
||||
// (or if neither iptables nor nftables is installed), but the
|
||||
// other unit tests would fail if iptables wasn't installed, so
|
||||
// we know it must be.
|
||||
configBytes := []byte(fmt.Sprintf(`{
|
||||
"name": "test",
|
||||
"type": "portmap",
|
||||
"cniVersion": "%s"
|
||||
}`, ver))
|
||||
c, _, err := parseConfig(configBytes, "container")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(c.CNIVersion).To(Equal(ver))
|
||||
Expect(c.Backend).To(Equal(&iptablesBackend))
|
||||
Expect(c.Name).To(Equal("test"))
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] uses nftables if requested", ver), func() {
|
||||
configBytes := []byte(fmt.Sprintf(`{
|
||||
"name": "test",
|
||||
"type": "portmap",
|
||||
"cniVersion": "%s",
|
||||
"backend": "nftables"
|
||||
}`, ver))
|
||||
c, _, err := parseConfig(configBytes, "container")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(c.CNIVersion).To(Equal(ver))
|
||||
Expect(c.Backend).To(Equal(&nftablesBackend))
|
||||
Expect(c.Name).To(Equal("test"))
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] allows nftables conditions if nftables is requested", ver), func() {
|
||||
configBytes := []byte(fmt.Sprintf(`{
|
||||
"name": "test",
|
||||
"type": "portmap",
|
||||
"cniVersion": "%s",
|
||||
"backend": "nftables",
|
||||
"conditionsV4": ["ip", "saddr", "1.2.3.4"],
|
||||
"conditionsV6": ["ip6", "saddr", "12::34"]
|
||||
}`, ver))
|
||||
c, _, err := parseConfig(configBytes, "container")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(c.CNIVersion).To(Equal(ver))
|
||||
Expect(c.Backend).To(Equal(&nftablesBackend))
|
||||
Expect(c.ConditionsV4).To(Equal(&[]string{"ip", "saddr", "1.2.3.4"}))
|
||||
Expect(c.ConditionsV6).To(Equal(&[]string{"ip6", "saddr", "12::34"}))
|
||||
Expect(c.Name).To(Equal("test"))
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] rejects nftables options with 'backend: iptables'", ver), func() {
|
||||
configBytes := []byte(fmt.Sprintf(`{
|
||||
"name": "test",
|
||||
"type": "portmap",
|
||||
"cniVersion": "%s",
|
||||
"backend": "iptables",
|
||||
"conditionsV4": ["ip", "saddr", "1.2.3.4"],
|
||||
"conditionsV6": ["ip6", "saddr", "12::34"]
|
||||
}`, ver))
|
||||
_, _, err := parseConfig(configBytes, "container")
|
||||
Expect(err).To(MatchError("iptables backend was requested but configuration contains nftables-specific options [conditionsV4 conditionsV6]"))
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] rejects iptables options with 'backend: nftables'", ver), func() {
|
||||
configBytes := []byte(fmt.Sprintf(`{
|
||||
"name": "test",
|
||||
"type": "portmap",
|
||||
"cniVersion": "%s",
|
||||
"backend": "nftables",
|
||||
"externalSetMarkChain": "KUBE-MARK-MASQ",
|
||||
"conditionsV4": ["-s", "1.2.3.4"],
|
||||
"conditionsV6": ["-s", "12::34"]
|
||||
}`, ver))
|
||||
_, _, err := parseConfig(configBytes, "container")
|
||||
Expect(err).To(MatchError("nftables backend was requested but configuration contains iptables-specific options [externalSetMarkChain conditionsV4 conditionsV6]"))
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] does not fail on missing prevResult interface index", ver), func() {
|
||||
configBytes := []byte(fmt.Sprintf(`{
|
||||
"name": "test",
|
||||
@ -139,7 +215,7 @@ var _ = Describe("portmapping configuration", func() {
|
||||
{ "hostPort": 8080, "containerPort": 80, "protocol": "tcp"}
|
||||
]
|
||||
},
|
||||
"conditionsV4": ["a", "b"],
|
||||
"conditionsV4": ["-s", "1.2.3.4"],
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{"name": "host"}
|
||||
@ -157,222 +233,5 @@ var _ = Describe("portmapping configuration", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Generating chains", func() {
|
||||
Context("for DNAT", func() {
|
||||
It(fmt.Sprintf("[%s] generates a correct standard container chain", ver), func() {
|
||||
ch := genDnatChain(netName, containerID)
|
||||
|
||||
Expect(ch).To(Equal(chain{
|
||||
table: "nat",
|
||||
name: "CNI-DN-bfd599665540dd91d5d28",
|
||||
entryChains: []string{TopLevelDNATChainName},
|
||||
}))
|
||||
configBytes := []byte(fmt.Sprintf(`{
|
||||
"name": "test",
|
||||
"type": "portmap",
|
||||
"cniVersion": "%s",
|
||||
"runtimeConfig": {
|
||||
"portMappings": [
|
||||
{ "hostPort": 8080, "containerPort": 80, "protocol": "tcp"},
|
||||
{ "hostPort": 8081, "containerPort": 80, "protocol": "tcp"},
|
||||
{ "hostPort": 8080, "containerPort": 81, "protocol": "udp"},
|
||||
{ "hostPort": 8082, "containerPort": 82, "protocol": "udp"},
|
||||
{ "hostPort": 8083, "containerPort": 83, "protocol": "tcp", "hostIP": "192.168.0.2"},
|
||||
{ "hostPort": 8084, "containerPort": 84, "protocol": "tcp", "hostIP": "0.0.0.0"},
|
||||
{ "hostPort": 8085, "containerPort": 85, "protocol": "tcp", "hostIP": "2001:db8:a::1"},
|
||||
{ "hostPort": 8086, "containerPort": 86, "protocol": "tcp", "hostIP": "::"}
|
||||
]
|
||||
},
|
||||
"snat": true,
|
||||
"conditionsV4": ["a", "b"],
|
||||
"conditionsV6": ["c", "d"]
|
||||
}`, ver))
|
||||
|
||||
conf, _, err := parseConfig(configBytes, "foo")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
conf.ContainerID = containerID
|
||||
|
||||
ch = genDnatChain(conf.Name, containerID)
|
||||
Expect(ch).To(Equal(chain{
|
||||
table: "nat",
|
||||
name: "CNI-DN-67e92b96e692a494b6b85",
|
||||
entryChains: []string{"CNI-HOSTPORT-DNAT"},
|
||||
}))
|
||||
|
||||
n, err := types.ParseCIDR("10.0.0.2/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
fillDnatRules(&ch, conf, *n)
|
||||
|
||||
Expect(ch.entryRules).To(Equal([][]string{
|
||||
{
|
||||
"-m", "comment", "--comment",
|
||||
fmt.Sprintf("dnat name: \"test\" id: \"%s\"", containerID),
|
||||
"-m", "multiport",
|
||||
"-p", "tcp",
|
||||
"--destination-ports", "8080,8081,8083,8084,8085,8086",
|
||||
"a", "b",
|
||||
},
|
||||
{
|
||||
"-m", "comment", "--comment",
|
||||
fmt.Sprintf("dnat name: \"test\" id: \"%s\"", containerID),
|
||||
"-m", "multiport",
|
||||
"-p", "udp",
|
||||
"--destination-ports", "8080,8082",
|
||||
"a", "b",
|
||||
},
|
||||
}))
|
||||
|
||||
Expect(ch.rules).To(Equal([][]string{
|
||||
// tcp rules and not hostIP
|
||||
{"-p", "tcp", "--dport", "8080", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8080", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||
{"-p", "tcp", "--dport", "8081", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8081", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||
// udp rules and not hostIP
|
||||
{"-p", "udp", "--dport", "8080", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8080", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:81"},
|
||||
{"-p", "udp", "--dport", "8082", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8082", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8082", "-j", "DNAT", "--to-destination", "10.0.0.2:82"},
|
||||
// tcp rules and hostIP
|
||||
{"-p", "tcp", "--dport", "8083", "-d", "192.168.0.2", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8083", "-d", "192.168.0.2", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8083", "-d", "192.168.0.2", "-j", "DNAT", "--to-destination", "10.0.0.2:83"},
|
||||
// tcp rules and hostIP = "0.0.0.0"
|
||||
{"-p", "tcp", "--dport", "8084", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8084", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8084", "-j", "DNAT", "--to-destination", "10.0.0.2:84"},
|
||||
}))
|
||||
|
||||
ch.rules = nil
|
||||
ch.entryRules = nil
|
||||
|
||||
n, err = types.ParseCIDR("2001:db8::2/64")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
fillDnatRules(&ch, conf, *n)
|
||||
|
||||
Expect(ch.rules).To(Equal([][]string{
|
||||
// tcp rules and not hostIP
|
||||
{"-p", "tcp", "--dport", "8080", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "[2001:db8::2]:80"},
|
||||
{"-p", "tcp", "--dport", "8081", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "[2001:db8::2]:80"},
|
||||
// udp rules and not hostIP
|
||||
{"-p", "udp", "--dport", "8080", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8080", "-j", "DNAT", "--to-destination", "[2001:db8::2]:81"},
|
||||
{"-p", "udp", "--dport", "8082", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8082", "-j", "DNAT", "--to-destination", "[2001:db8::2]:82"},
|
||||
// tcp rules and hostIP
|
||||
{"-p", "tcp", "--dport", "8085", "-d", "2001:db8:a::1", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8085", "-d", "2001:db8:a::1", "-j", "DNAT", "--to-destination", "[2001:db8::2]:85"},
|
||||
// tcp rules and hostIP = "::"
|
||||
{"-p", "tcp", "--dport", "8086", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8086", "-j", "DNAT", "--to-destination", "[2001:db8::2]:86"},
|
||||
}))
|
||||
|
||||
// Disable snat, generate rules
|
||||
ch.rules = nil
|
||||
ch.entryRules = nil
|
||||
fvar := false
|
||||
conf.SNAT = &fvar
|
||||
|
||||
n, err = types.ParseCIDR("10.0.0.2/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
fillDnatRules(&ch, conf, *n)
|
||||
Expect(ch.rules).To(Equal([][]string{
|
||||
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||
{"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||
{"-p", "udp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:81"},
|
||||
{"-p", "udp", "--dport", "8082", "-j", "DNAT", "--to-destination", "10.0.0.2:82"},
|
||||
{"-p", "tcp", "--dport", "8083", "-d", "192.168.0.2", "-j", "DNAT", "--to-destination", "10.0.0.2:83"},
|
||||
{"-p", "tcp", "--dport", "8084", "-j", "DNAT", "--to-destination", "10.0.0.2:84"},
|
||||
}))
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] generates a correct chain with external mark", ver), func() {
|
||||
ch := genDnatChain(netName, containerID)
|
||||
|
||||
Expect(ch).To(Equal(chain{
|
||||
table: "nat",
|
||||
name: "CNI-DN-bfd599665540dd91d5d28",
|
||||
entryChains: []string{TopLevelDNATChainName},
|
||||
}))
|
||||
configBytes := []byte(fmt.Sprintf(`{
|
||||
"name": "test",
|
||||
"type": "portmap",
|
||||
"cniVersion": "%s",
|
||||
"runtimeConfig": {
|
||||
"portMappings": [
|
||||
{ "hostPort": 8080, "containerPort": 80, "protocol": "tcp"}
|
||||
]
|
||||
},
|
||||
"externalSetMarkChain": "PLZ-SET-MARK",
|
||||
"conditionsV4": ["a", "b"],
|
||||
"conditionsV6": ["c", "d"]
|
||||
}`, ver))
|
||||
|
||||
conf, _, err := parseConfig(configBytes, "foo")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
conf.ContainerID = containerID
|
||||
|
||||
ch = genDnatChain(conf.Name, containerID)
|
||||
n, err := types.ParseCIDR("10.0.0.2/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
fillDnatRules(&ch, conf, *n)
|
||||
Expect(ch.rules).To(Equal([][]string{
|
||||
{"-p", "tcp", "--dport", "8080", "-s", "10.0.0.2/24", "-j", "PLZ-SET-MARK"},
|
||||
{"-p", "tcp", "--dport", "8080", "-s", "127.0.0.1", "-j", "PLZ-SET-MARK"},
|
||||
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||
}))
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] generates a correct top-level chain", ver), func() {
|
||||
ch := genToplevelDnatChain()
|
||||
|
||||
Expect(ch).To(Equal(chain{
|
||||
table: "nat",
|
||||
name: "CNI-HOSTPORT-DNAT",
|
||||
entryChains: []string{"PREROUTING", "OUTPUT"},
|
||||
entryRules: [][]string{{"-m", "addrtype", "--dst-type", "LOCAL"}},
|
||||
}))
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] generates the correct mark chains", ver), func() {
|
||||
masqBit := 5
|
||||
ch := genSetMarkChain(masqBit)
|
||||
Expect(ch).To(Equal(chain{
|
||||
table: "nat",
|
||||
name: "CNI-HOSTPORT-SETMARK",
|
||||
rules: [][]string{{
|
||||
"-m", "comment",
|
||||
"--comment", "CNI portfwd masquerade mark",
|
||||
"-j", "MARK",
|
||||
"--set-xmark", "0x20/0x20",
|
||||
}},
|
||||
}))
|
||||
|
||||
ch = genMarkMasqChain(masqBit)
|
||||
Expect(ch).To(Equal(chain{
|
||||
table: "nat",
|
||||
name: "CNI-HOSTPORT-MASQ",
|
||||
entryChains: []string{"POSTROUTING"},
|
||||
entryRules: [][]string{{
|
||||
"-m", "comment",
|
||||
"--comment", "CNI portfwd requiring masquerade",
|
||||
}},
|
||||
rules: [][]string{{
|
||||
"-m", "mark",
|
||||
"--mark", "0x20/0x20",
|
||||
"-j", "MASQUERADE",
|
||||
}},
|
||||
prependEntry: true,
|
||||
}))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@ -21,6 +21,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/utils/sysctl"
|
||||
)
|
||||
|
||||
// fmtIpPort correctly formats ip:port literals for iptables and ip6tables -
|
||||
@ -52,6 +54,14 @@ func getRoutableHostIF(containerIP net.IP) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// enableLocalnetRouting tells the kernel not to treat 127/8 as a martian,
|
||||
// so that connections with a source ip of 127/8 can cross a routing boundary.
|
||||
func enableLocalnetRouting(ifName string) error {
|
||||
routeLocalnetPath := "net/ipv4/conf/" + ifName + "/route_localnet"
|
||||
_, err := sysctl.Sysctl(routeLocalnetPath, "1")
|
||||
return err
|
||||
}
|
||||
|
||||
// groupByProto groups port numbers by protocol
|
||||
func groupByProto(entries []PortMapEntry) map[string][]int {
|
||||
if len(entries) == 0 {
|
||||
|
@ -47,6 +47,7 @@ type PluginConf struct {
|
||||
PrevResult *current.Result `json:"-"`
|
||||
|
||||
// Add plugin-specific flags here
|
||||
Table *int `json:"table,omitempty"`
|
||||
}
|
||||
|
||||
// Wrapper that does a lock before and unlock after operations to serialise
|
||||
@ -66,7 +67,6 @@ func withLockAndNetNS(nspath string, toRun func(_ ns.NetNS) error) error {
|
||||
}
|
||||
|
||||
err = ns.WithNetNSPath(nspath, toRun)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -164,6 +164,9 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
|
||||
// Do the actual work.
|
||||
err = withLockAndNetNS(args.Netns, func(_ ns.NetNS) error {
|
||||
if conf.Table != nil {
|
||||
return doRoutesWithTable(ipCfgs, *conf.Table)
|
||||
}
|
||||
return doRoutes(ipCfgs, args.IfName)
|
||||
})
|
||||
if err != nil {
|
||||
@ -331,31 +334,73 @@ func doRoutes(ipCfgs []*current.IPConfig, iface string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func doRoutesWithTable(ipCfgs []*current.IPConfig, table int) error {
|
||||
for _, ipCfg := range ipCfgs {
|
||||
log.Printf("Set rule for source %s", ipCfg.String())
|
||||
rule := netlink.NewRule()
|
||||
rule.Table = table
|
||||
|
||||
// Source must be restricted to a single IP, not a full subnet
|
||||
var src net.IPNet
|
||||
src.IP = ipCfg.Address.IP
|
||||
if src.IP.To4() != nil {
|
||||
src.Mask = net.CIDRMask(32, 32)
|
||||
} else {
|
||||
src.Mask = net.CIDRMask(128, 128)
|
||||
}
|
||||
|
||||
log.Printf("Source to use %s", src.String())
|
||||
rule.Src = &src
|
||||
|
||||
if err := netlink.RuleAdd(rule); err != nil {
|
||||
return fmt.Errorf("failed to add rule: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// cmdDel is called for DELETE requests
|
||||
func cmdDel(args *skel.CmdArgs) error {
|
||||
// We care a bit about config because it sets log level.
|
||||
_, err := parseConfig(args.StdinData)
|
||||
conf, err := parseConfig(args.StdinData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Cleaning up SBR for %s", args.IfName)
|
||||
err = withLockAndNetNS(args.Netns, func(_ ns.NetNS) error {
|
||||
return tidyRules(args.IfName)
|
||||
return tidyRules(args.IfName, conf.Table)
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Tidy up the rules for the deleted interface
|
||||
func tidyRules(iface string) error {
|
||||
func tidyRules(iface string, table *int) error {
|
||||
// We keep on going on rule deletion error, but return the last failure.
|
||||
var errReturn error
|
||||
var err error
|
||||
var rules []netlink.Rule
|
||||
|
||||
rules, err := netlink.RuleList(netlink.FAMILY_ALL)
|
||||
if err != nil {
|
||||
log.Printf("Failed to list all rules to tidy: %v", err)
|
||||
return fmt.Errorf("Failed to list all rules to tidy: %v", err)
|
||||
if table != nil {
|
||||
rules, err = netlink.RuleListFiltered(
|
||||
netlink.FAMILY_ALL,
|
||||
&netlink.Rule{
|
||||
Table: *table,
|
||||
},
|
||||
netlink.RT_FILTER_TABLE,
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("Failed to list rules of table %d to tidy: %v", *table, err)
|
||||
return fmt.Errorf("failed to list rules of table %d to tidy: %v", *table, err)
|
||||
}
|
||||
} else {
|
||||
rules, err = netlink.RuleList(netlink.FAMILY_ALL)
|
||||
if err != nil {
|
||||
log.Printf("Failed to list all rules to tidy: %v", err)
|
||||
return fmt.Errorf("Failed to list all rules to tidy: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
link, err := netlink.LinkByName(iface)
|
||||
@ -402,7 +447,13 @@ RULE_LOOP:
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("sbr"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
/* FIXME GC */
|
||||
/* FIXME Status */
|
||||
}, version.All, bv.BuildString("sbr"))
|
||||
}
|
||||
|
||||
func cmdCheck(_ *skel.CmdArgs) error {
|
||||
|
@ -53,7 +53,9 @@ func setup(targetNs ns.NetNS, status netStatus) error {
|
||||
err := targetNs.Do(func(_ ns.NetNS) error {
|
||||
for _, dev := range status.Devices {
|
||||
log.Printf("Adding dev %s\n", dev.Name)
|
||||
link := &netlink.Dummy{LinkAttrs: netlink.LinkAttrs{Name: dev.Name}}
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = dev.Name
|
||||
link := &netlink.Dummy{LinkAttrs: linkAttrs}
|
||||
err := netlink.LinkAdd(link)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -117,11 +119,16 @@ func readback(targetNs ns.NetNS, devNames []string) (netStatus, error) {
|
||||
return err
|
||||
}
|
||||
|
||||
routesNoLinkLocal := []netlink.Route{}
|
||||
for _, route := range routes {
|
||||
if route.Dst.IP.IsLinkLocalMulticast() || route.Dst.IP.IsLinkLocalUnicast() {
|
||||
continue
|
||||
}
|
||||
log.Printf("Got %s route %v", name, route)
|
||||
routesNoLinkLocal = append(routesNoLinkLocal, route)
|
||||
}
|
||||
|
||||
retVal.Devices[i].Routes = routes
|
||||
retVal.Devices[i].Routes = routesNoLinkLocal
|
||||
}
|
||||
|
||||
rules, err := netlink.RuleList(netlink.FAMILY_ALL)
|
||||
@ -291,6 +298,7 @@ var _ = Describe("sbr test", func() {
|
||||
expNet1.Routes = append(expNet1.Routes,
|
||||
netlink.Route{
|
||||
Gw: net.IPv4(192, 168, 1, 1),
|
||||
Dst: &net.IPNet{IP: net.IPv4zero, Mask: net.IPMask(net.IPv4zero)},
|
||||
Table: 100,
|
||||
LinkIndex: expNet1.Routes[0].LinkIndex,
|
||||
})
|
||||
@ -491,6 +499,7 @@ var _ = Describe("sbr test", func() {
|
||||
}
|
||||
expNet1.Routes = append(expNet1.Routes,
|
||||
netlink.Route{
|
||||
Dst: &net.IPNet{IP: net.IPv4zero, Mask: net.IPMask(net.IPv4zero)},
|
||||
Gw: net.IPv4(192, 168, 1, 1),
|
||||
Table: 100,
|
||||
LinkIndex: expNet1.Routes[0].LinkIndex,
|
||||
@ -498,6 +507,7 @@ var _ = Describe("sbr test", func() {
|
||||
|
||||
expNet1.Routes = append(expNet1.Routes,
|
||||
netlink.Route{
|
||||
Dst: &net.IPNet{IP: net.IPv4zero, Mask: net.IPMask(net.IPv4zero)},
|
||||
Gw: net.IPv4(192, 168, 101, 1),
|
||||
Table: 101,
|
||||
LinkIndex: expNet1.Routes[0].LinkIndex,
|
||||
@ -540,4 +550,81 @@ var _ = Describe("sbr test", func() {
|
||||
_, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) })
|
||||
Expect(err).To(MatchError("This plugin must be called as chained plugin"))
|
||||
})
|
||||
|
||||
It("Works with Table ID", func() {
|
||||
ifname := "net1"
|
||||
tableID := 5000
|
||||
conf := `{
|
||||
"cniVersion": "0.3.0",
|
||||
"name": "cni-plugin-sbr-test",
|
||||
"type": "sbr",
|
||||
"table": %d,
|
||||
"prevResult": {
|
||||
"cniVersion": "0.3.0",
|
||||
"interfaces": [
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": "%s"
|
||||
}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"address": "192.168.1.209/24",
|
||||
"interface": 0
|
||||
},
|
||||
{
|
||||
"address": "192.168.101.209/24",
|
||||
"interface": 0
|
||||
}
|
||||
],
|
||||
"routes": []
|
||||
}
|
||||
}`
|
||||
conf = fmt.Sprintf(conf, tableID, ifname, targetNs.Path())
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNs.Path(),
|
||||
IfName: ifname,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
preStatus := createDefaultStatus()
|
||||
|
||||
err := setup(targetNs, preStatus)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
oldStatus, err := readback(targetNs, []string{"net1", "eth0"})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
_, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) })
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
newStatus, err := readback(targetNs, []string{"net1", "eth0"})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Routes have not been moved.
|
||||
Expect(newStatus).To(Equal(oldStatus))
|
||||
|
||||
// Fetch all rules for the requested table ID.
|
||||
var rules []netlink.Rule
|
||||
err = targetNs.Do(func(_ ns.NetNS) error {
|
||||
var err error
|
||||
rules, err = netlink.RuleListFiltered(
|
||||
netlink.FAMILY_ALL, &netlink.Rule{
|
||||
Table: tableID,
|
||||
},
|
||||
netlink.RT_FILTER_TABLE,
|
||||
)
|
||||
return err
|
||||
})
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(rules).To(HaveLen(2))
|
||||
|
||||
// Both IPs have been added as source based routes with requested table ID.
|
||||
Expect(rules[0].Table).To(Equal(tableID))
|
||||
Expect(rules[0].Src.String()).To(Equal("192.168.101.209/32"))
|
||||
Expect(rules[1].Table).To(Equal(tableID))
|
||||
Expect(rules[1].Src.String()).To(Equal("192.168.1.209/32"))
|
||||
})
|
||||
})
|
||||
|
@ -53,6 +53,7 @@ type TuningConf struct {
|
||||
Mac string `json:"mac,omitempty"`
|
||||
Promisc bool `json:"promisc,omitempty"`
|
||||
Mtu int `json:"mtu,omitempty"`
|
||||
TxQLen *int `json:"txQLen,omitempty"`
|
||||
Allmulti *bool `json:"allmulti,omitempty"`
|
||||
|
||||
RuntimeConfig struct {
|
||||
@ -69,6 +70,7 @@ type IPAMArgs struct {
|
||||
Promisc *bool `json:"promisc,omitempty"`
|
||||
Mtu *int `json:"mtu,omitempty"`
|
||||
Allmulti *bool `json:"allmulti,omitempty"`
|
||||
TxQLen *int `json:"txQLen,omitempty"`
|
||||
}
|
||||
|
||||
// configToRestore will contain interface attributes that should be restored on cmdDel
|
||||
@ -77,6 +79,7 @@ type configToRestore struct {
|
||||
Promisc *bool `json:"promisc,omitempty"`
|
||||
Mtu int `json:"mtu,omitempty"`
|
||||
Allmulti *bool `json:"allmulti,omitempty"`
|
||||
TxQLen *int `json:"txQLen,omitempty"`
|
||||
}
|
||||
|
||||
// MacEnvArgs represents CNI_ARG
|
||||
@ -136,6 +139,10 @@ func parseConf(data []byte, envArgs string) (*TuningConf, error) {
|
||||
if conf.Args.A.Allmulti != nil {
|
||||
conf.Allmulti = conf.Args.A.Allmulti
|
||||
}
|
||||
|
||||
if conf.Args.A.TxQLen != nil {
|
||||
conf.TxQLen = conf.Args.A.TxQLen
|
||||
}
|
||||
}
|
||||
|
||||
return &conf, nil
|
||||
@ -204,6 +211,14 @@ func changeAllmulti(ifName string, val bool) error {
|
||||
return netlink.LinkSetAllmulticastOff(link)
|
||||
}
|
||||
|
||||
func changeTxQLen(ifName string, txQLen int) error {
|
||||
link, err := netlink.LinkByName(ifName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get %q: %v", ifName, err)
|
||||
}
|
||||
return netlink.LinkSetTxQLen(link, txQLen)
|
||||
}
|
||||
|
||||
func createBackup(ifName, containerID, backupPath string, tuningConf *TuningConf) error {
|
||||
config := configToRestore{}
|
||||
link, err := netlink.LinkByName(ifName)
|
||||
@ -224,6 +239,10 @@ func createBackup(ifName, containerID, backupPath string, tuningConf *TuningConf
|
||||
config.Allmulti = new(bool)
|
||||
*config.Allmulti = (link.Attrs().RawFlags&unix.IFF_ALLMULTI != 0)
|
||||
}
|
||||
if tuningConf.TxQLen != nil {
|
||||
qlen := link.Attrs().TxQLen
|
||||
config.TxQLen = &qlen
|
||||
}
|
||||
|
||||
if _, err := os.Stat(backupPath); os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(backupPath, 0o600); err != nil {
|
||||
@ -292,8 +311,15 @@ func restoreBackup(ifName, containerID, backupPath string) error {
|
||||
}
|
||||
}
|
||||
|
||||
if config.TxQLen != nil {
|
||||
if err = changeTxQLen(ifName, *config.TxQLen); err != nil {
|
||||
err = fmt.Errorf("failed to restore transmit queue length: %v", err)
|
||||
errStr = append(errStr, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if len(errStr) > 0 {
|
||||
return fmt.Errorf(strings.Join(errStr, "; "))
|
||||
return errors.New(strings.Join(errStr, "; "))
|
||||
}
|
||||
|
||||
if err = os.Remove(filePath); err != nil {
|
||||
@ -346,7 +372,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
}
|
||||
|
||||
if tuningConf.Mac != "" || tuningConf.Mtu != 0 || tuningConf.Promisc || tuningConf.Allmulti != nil {
|
||||
if tuningConf.Mac != "" || tuningConf.Mtu != 0 || tuningConf.Promisc || tuningConf.Allmulti != nil || tuningConf.TxQLen != nil {
|
||||
if err = createBackup(args.IfName, args.ContainerID, tuningConf.DataDir, tuningConf); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -377,6 +403,12 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if tuningConf.TxQLen != nil {
|
||||
if err = changeTxQLen(args.IfName, *tuningConf.TxQLen); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
@ -401,7 +433,13 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("tuning"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
/* FIXME GC */
|
||||
/* FIXME Status */
|
||||
}, version.All, bv.BuildString("tuning"))
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
@ -483,6 +521,13 @@ func cmdCheck(args *skel.CmdArgs) error {
|
||||
args.IfName, tuningConf.Allmulti, allmulti)
|
||||
}
|
||||
}
|
||||
|
||||
if tuningConf.TxQLen != nil {
|
||||
if *tuningConf.TxQLen != link.Attrs().TxQLen {
|
||||
return fmt.Errorf("Error: Tuning configured Transmit Queue Length of %s is %d, current value is %d",
|
||||
args.IfName, tuningConf.TxQLen, link.Attrs().TxQLen)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -110,10 +110,10 @@ var _ = Describe("tuning plugin", func() {
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = IFNAME
|
||||
err = netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: IFNAME,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
link, err := netlink.LinkByName(IFNAME)
|
||||
@ -125,6 +125,8 @@ var _ = Describe("tuning plugin", func() {
|
||||
*beforeConf.Promisc = (link.Attrs().Promisc != 0)
|
||||
beforeConf.Allmulti = new(bool)
|
||||
*beforeConf.Allmulti = (link.Attrs().RawFlags&unix.IFF_ALLMULTI != 0)
|
||||
beforeConf.TxQLen = new(int)
|
||||
*beforeConf.TxQLen = link.Attrs().TxQLen
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@ -487,6 +489,138 @@ var _ = Describe("tuning plugin", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] configures and deconfigures tx queue len with ADD/DEL", ver), func() {
|
||||
conf := []byte(fmt.Sprintf(`{
|
||||
"name": "test",
|
||||
"type": "iplink",
|
||||
"cniVersion": "%s",
|
||||
"txQLen": 20000,
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{"name": "dummy0", "sandbox":"netns"}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "10.0.0.2/24",
|
||||
"gateway": "10.0.0.1",
|
||||
"interface": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}`, ver))
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: originalNS.Path(),
|
||||
IfName: IFNAME,
|
||||
StdinData: conf,
|
||||
}
|
||||
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
link, err := netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().TxQLen).To(Equal(20000))
|
||||
|
||||
if testutils.SpecVersionHasCHECK(ver) {
|
||||
n := &TuningConf{}
|
||||
Expect(json.Unmarshal(conf, &n)).NotTo(HaveOccurred())
|
||||
|
||||
confString, err := buildOneConfig(ver, n, r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
args.StdinData = confString
|
||||
|
||||
Expect(testutils.CmdCheckWithArgs(args, func() error {
|
||||
return cmdCheck(args)
|
||||
})).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
err = testutils.CmdDel(originalNS.Path(),
|
||||
args.ContainerID, "", func() error { return cmdDel(args) })
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
link, err = netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().TxQLen).To(Equal(*beforeConf.TxQLen))
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] configures and deconfigures tx queue len from args with ADD/DEL", ver), func() {
|
||||
conf := []byte(fmt.Sprintf(`{
|
||||
"name": "test",
|
||||
"type": "iplink",
|
||||
"cniVersion": "%s",
|
||||
"args": {
|
||||
"cni": {
|
||||
"txQLen": 20000
|
||||
}
|
||||
},
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{"name": "dummy0", "sandbox":"netns"}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "10.0.0.2/24",
|
||||
"gateway": "10.0.0.1",
|
||||
"interface": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}`, ver))
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: originalNS.Path(),
|
||||
IfName: IFNAME,
|
||||
StdinData: conf,
|
||||
}
|
||||
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
result, err := types100.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(result.Interfaces).To(HaveLen(1))
|
||||
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
|
||||
Expect(result.IPs).To(HaveLen(1))
|
||||
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
|
||||
|
||||
link, err := netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().TxQLen).To(Equal(20000))
|
||||
|
||||
err = testutils.CmdDel(originalNS.Path(),
|
||||
args.ContainerID, "", func() error { return cmdDel(args) })
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
link, err = netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().TxQLen).To(Equal(*beforeConf.TxQLen))
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] configures and deconfigures mac address (from conf file) with ADD/DEL", ver), func() {
|
||||
mac := "c2:11:22:33:44:55"
|
||||
conf := []byte(fmt.Sprintf(`{
|
||||
@ -780,7 +914,7 @@ var _ = Describe("tuning plugin", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] configures and deconfigures mac address, promisc mode and MTU (from conf file) with custom dataDir", ver), func() {
|
||||
It(fmt.Sprintf("[%s] configures and deconfigures mac address, promisc mode, MTU and tx queue len (from conf file) with custom dataDir", ver), func() {
|
||||
conf := []byte(fmt.Sprintf(`{
|
||||
"name": "test",
|
||||
"type": "iplink",
|
||||
@ -788,6 +922,7 @@ var _ = Describe("tuning plugin", func() {
|
||||
"mac": "c2:11:22:33:44:77",
|
||||
"promisc": true,
|
||||
"mtu": 4000,
|
||||
"txQLen": 20000,
|
||||
"dataDir": "/tmp/tuning-test",
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
@ -834,6 +969,7 @@ var _ = Describe("tuning plugin", func() {
|
||||
Expect(link.Attrs().HardwareAddr).To(Equal(hw))
|
||||
Expect(link.Attrs().Promisc).To(Equal(1))
|
||||
Expect(link.Attrs().MTU).To(Equal(4000))
|
||||
Expect(link.Attrs().TxQLen).To(Equal(20000))
|
||||
|
||||
Expect("/tmp/tuning-test/dummy_dummy0.json").Should(BeAnExistingFile())
|
||||
|
||||
@ -862,6 +998,7 @@ var _ = Describe("tuning plugin", func() {
|
||||
Expect(link.Attrs().HardwareAddr.String()).To(Equal(beforeConf.Mac))
|
||||
Expect(link.Attrs().MTU).To(Equal(beforeConf.Mtu))
|
||||
Expect(link.Attrs().Promisc != 0).To(Equal(*beforeConf.Promisc))
|
||||
Expect(link.Attrs().TxQLen).To(Equal(*beforeConf.TxQLen))
|
||||
|
||||
return nil
|
||||
})
|
||||
|
@ -39,7 +39,13 @@ type VRFNetConf struct {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.VersionsStartingFrom("0.3.1"), bv.BuildString("vrf"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
/* FIXME GC */
|
||||
/* FIXME Status */
|
||||
}, version.VersionsStartingFrom("0.3.1"), bv.BuildString("vrf"))
|
||||
}
|
||||
|
||||
func cmdAdd(args *skel.CmdArgs) error {
|
||||
@ -75,7 +81,6 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("cmdAdd failed: %v", err)
|
||||
}
|
||||
@ -121,7 +126,6 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
// if NetNs is passed down by the Cloud Orchestration Engine, or if it called multiple times
|
||||
// so don't return an error if the device is already removed.
|
||||
|
@ -17,6 +17,8 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
@ -48,11 +50,11 @@ func createVRF(name string, tableID uint32) (*netlink.Vrf, error) {
|
||||
}
|
||||
}
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = name
|
||||
vrf := &netlink.Vrf{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: name,
|
||||
},
|
||||
Table: tableID,
|
||||
LinkAttrs: linkAttrs,
|
||||
Table: tableID,
|
||||
}
|
||||
|
||||
err = netlink.LinkAdd(vrf)
|
||||
@ -104,6 +106,19 @@ func addInterface(vrf *netlink.Vrf, intf string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed getting ipv6 addresses for %s", intf)
|
||||
}
|
||||
|
||||
// Save all routes that are not local and connected, before setting master,
|
||||
// because otherwise those routes will be deleted after interface is moved.
|
||||
filter := &netlink.Route{
|
||||
LinkIndex: i.Attrs().Index,
|
||||
Scope: netlink.SCOPE_UNIVERSE, // Exclude local and connected routes
|
||||
}
|
||||
filterMask := netlink.RT_FILTER_OIF | netlink.RT_FILTER_SCOPE // Filter based on link index and scope
|
||||
routes, err := netlink.RouteListFiltered(netlink.FAMILY_ALL, filter, filterMask)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed getting all routes for %s", intf)
|
||||
}
|
||||
|
||||
err = netlink.LinkSetMaster(i, vrf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not set vrf %s as master of %s: %v", vrf.Name, intf, err)
|
||||
@ -111,7 +126,7 @@ func addInterface(vrf *netlink.Vrf, intf string) error {
|
||||
|
||||
afterAddresses, err := netlink.AddrList(i, netlink.FAMILY_V6)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed getting ipv6 new addresses for %s", intf)
|
||||
return fmt.Errorf("failed getting ipv6 new addresses for %s: %v", intf, err)
|
||||
}
|
||||
|
||||
// Since keeping the ipv6 address depends on net.ipv6.conf.all.keep_addr_on_down ,
|
||||
@ -128,6 +143,49 @@ CONTINUE:
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not restore address %s to %s @ %s: %v", toFind, intf, vrf.Name, err)
|
||||
}
|
||||
|
||||
// Waits for local/host routes to be added by the kernel.
|
||||
maxRetry := 10
|
||||
for {
|
||||
routesVRFTable, err := netlink.RouteListFiltered(
|
||||
netlink.FAMILY_ALL,
|
||||
&netlink.Route{
|
||||
Dst: &net.IPNet{
|
||||
IP: toFind.IP,
|
||||
Mask: net.IPMask{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
||||
},
|
||||
Table: int(vrf.Table),
|
||||
LinkIndex: i.Attrs().Index,
|
||||
},
|
||||
netlink.RT_FILTER_OIF|netlink.RT_FILTER_TABLE|netlink.RT_FILTER_DST,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed getting routes for %s table %d for dst %s: %v", intf, vrf.Table, toFind.IPNet.String(), err)
|
||||
}
|
||||
|
||||
if len(routesVRFTable) >= 1 {
|
||||
break
|
||||
}
|
||||
|
||||
maxRetry--
|
||||
if maxRetry <= 0 {
|
||||
return fmt.Errorf("failed getting local/host addresses for %s in table %d with dst %s", intf, vrf.Table, toFind.IPNet.String())
|
||||
}
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
// Apply all saved routes for the interface that was moved to the VRF
|
||||
for _, route := range routes {
|
||||
r := route
|
||||
// Modify original table to vrf one,
|
||||
r.Table = int(vrf.Table)
|
||||
// equivalent of 'ip route replace <address> table <int>'.
|
||||
err = netlink.RouteReplace(&r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not add route '%s': %v", r, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -17,6 +17,9 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
@ -92,22 +95,22 @@ var _ = Describe("vrf plugin", func() {
|
||||
err = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
la0 := netlink.NewLinkAttrs()
|
||||
la0.Name = IF0Name
|
||||
err = netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: IF0Name,
|
||||
},
|
||||
LinkAttrs: la0,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
_, err = netlink.LinkByName(IF0Name)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
la1 := netlink.NewLinkAttrs()
|
||||
la1.Name = IF1Name
|
||||
err = netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: IF1Name,
|
||||
},
|
||||
LinkAttrs: la1,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
_, err = netlink.LinkByName(IF0Name)
|
||||
_, err = netlink.LinkByName(IF1Name)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
@ -177,6 +180,292 @@ var _ = Describe("vrf plugin", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("adds the interface and custom routing to new VRF", func() {
|
||||
conf := configWithRouteFor("test", IF0Name, VRF0Name, "10.0.0.2/24", "10.10.10.0/24")
|
||||
|
||||
By("Setting custom routing first", func() {
|
||||
err := targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
ipv4, err := types.ParseCIDR("10.0.0.2/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(ipv4).NotTo(BeNil())
|
||||
|
||||
_, routev4, err := net.ParseCIDR("10.10.10.0/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
ipv6, err := types.ParseCIDR("abcd:1234:ffff::cdde/64")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(ipv6).NotTo(BeNil())
|
||||
|
||||
_, routev6, err := net.ParseCIDR("1111:dddd::/80")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(routev6).NotTo(BeNil())
|
||||
|
||||
link, err := netlink.LinkByName(IF0Name)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Add IP addresses for network reachability
|
||||
netlink.AddrAdd(link, &netlink.Addr{IPNet: ipv4})
|
||||
netlink.AddrAdd(link, &netlink.Addr{IPNet: ipv6})
|
||||
// Wait for the corresponding route to be addeded
|
||||
Eventually(func() bool {
|
||||
ipv6RouteDst := &net.IPNet{
|
||||
IP: ipv6.IP,
|
||||
Mask: net.IPMask{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
||||
}
|
||||
routes, _ := netlink.RouteListFiltered(netlink.FAMILY_ALL, &netlink.Route{
|
||||
Dst: ipv6RouteDst,
|
||||
Table: 0,
|
||||
}, netlink.RT_FILTER_DST|netlink.RT_FILTER_TABLE)
|
||||
return err == nil && len(routes) >= 1
|
||||
}, time.Second, 500*time.Millisecond).Should(BeTrue())
|
||||
|
||||
ipAddrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
// Check if address was assigned properly
|
||||
Expect(ipAddrs[0].IP.String()).To(Equal("10.0.0.2"))
|
||||
|
||||
// Set interface UP, otherwise local route to 10.0.0.0/24 is not present
|
||||
err = netlink.LinkSetUp(link)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Add additional route to 10.10.10.0/24 via 10.0.0.1 gateway
|
||||
r := netlink.Route{
|
||||
LinkIndex: link.Attrs().Index,
|
||||
Src: ipv4.IP,
|
||||
Dst: routev4,
|
||||
Gw: net.ParseIP("10.0.0.1"),
|
||||
}
|
||||
err = netlink.RouteAdd(&r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
r6 := netlink.Route{
|
||||
LinkIndex: link.Attrs().Index,
|
||||
Src: ipv6.IP,
|
||||
Dst: routev6,
|
||||
Gw: net.ParseIP("abcd:1234:ffff::1"),
|
||||
}
|
||||
err = netlink.RouteAdd(&r6)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: IF0Name,
|
||||
StdinData: conf,
|
||||
}
|
||||
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
result, err := current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(result.Interfaces).To(HaveLen(1))
|
||||
Expect(result.Interfaces[0].Name).To(Equal(IF0Name))
|
||||
Expect(result.Routes).To(HaveLen(1))
|
||||
Expect(result.Routes[0].Dst.IP.String()).To(Equal("10.10.10.0"))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
checkInterfaceOnVRF(VRF0Name, IF0Name)
|
||||
checkRoutesOnVRF(VRF0Name, IF0Name, "10.0.0.2", "10.10.10.0/24", "1111:dddd::/80")
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("filters the correct routes to import to new VRF", func() {
|
||||
_ = configWithRouteFor("test0", IF0Name, VRF0Name, "10.0.0.2/24", "10.10.10.0/24")
|
||||
conf1 := configWithRouteFor("test1", IF1Name, VRF1Name, "10.0.0.3/24", "10.11.10.0/24")
|
||||
|
||||
By("Setting custom routing for IF0Name", func() {
|
||||
err := targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
ipv4, err := types.ParseCIDR("10.0.0.2/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(ipv4).NotTo(BeNil())
|
||||
|
||||
_, routev4, err := net.ParseCIDR("10.10.10.0/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
ipv6, err := types.ParseCIDR("abcd:1234:ffff::cdde/64")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(ipv6).NotTo(BeNil())
|
||||
|
||||
_, routev6, err := net.ParseCIDR("1111:dddd::/80")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(routev6).NotTo(BeNil())
|
||||
|
||||
link, err := netlink.LinkByName(IF0Name)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Add IP addresses for network reachability
|
||||
netlink.AddrAdd(link, &netlink.Addr{IPNet: ipv4})
|
||||
netlink.AddrAdd(link, &netlink.Addr{IPNet: ipv6})
|
||||
// Wait for the corresponding route to be addeded
|
||||
Eventually(func() bool {
|
||||
ipv6RouteDst := &net.IPNet{
|
||||
IP: ipv6.IP,
|
||||
Mask: net.IPMask{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
||||
}
|
||||
routes, _ := netlink.RouteListFiltered(netlink.FAMILY_ALL, &netlink.Route{
|
||||
Dst: ipv6RouteDst,
|
||||
Table: 0,
|
||||
}, netlink.RT_FILTER_DST|netlink.RT_FILTER_TABLE)
|
||||
return err == nil && len(routes) >= 1
|
||||
}, time.Second, 500*time.Millisecond).Should(BeTrue())
|
||||
|
||||
ipAddrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
// Check if address was assigned properly
|
||||
Expect(ipAddrs[0].IP.String()).To(Equal("10.0.0.2"))
|
||||
|
||||
// Set interface UP, otherwise local route to 10.0.0.0/24 is not present
|
||||
err = netlink.LinkSetUp(link)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Add additional route to 10.10.10.0/24 via 10.0.0.1 gateway
|
||||
r := netlink.Route{
|
||||
LinkIndex: link.Attrs().Index,
|
||||
Dst: routev4,
|
||||
Gw: net.ParseIP("10.0.0.1"),
|
||||
}
|
||||
err = netlink.RouteAdd(&r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
r6 := netlink.Route{
|
||||
LinkIndex: link.Attrs().Index,
|
||||
Src: ipv6.IP,
|
||||
Dst: routev6,
|
||||
Gw: net.ParseIP("abcd:1234:ffff::1"),
|
||||
}
|
||||
err = netlink.RouteAdd(&r6)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
By("Setting custom routing for IF1Name", func() {
|
||||
err := targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
ipv4, err := types.ParseCIDR("10.0.0.3/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(ipv4).NotTo(BeNil())
|
||||
|
||||
_, routev4, err := net.ParseCIDR("10.11.10.0/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
ipv6, err := types.ParseCIDR("abcd:1234:ffff::cddf/64")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(ipv6).NotTo(BeNil())
|
||||
|
||||
_, routev6, err := net.ParseCIDR("1111:ddde::/80")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(routev6).NotTo(BeNil())
|
||||
|
||||
link, err := netlink.LinkByName(IF1Name)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Add IP addresses for network reachability
|
||||
netlink.AddrAdd(link, &netlink.Addr{IPNet: ipv4})
|
||||
netlink.AddrAdd(link, &netlink.Addr{IPNet: ipv6})
|
||||
// Wait for the corresponding route to be addeded
|
||||
Eventually(func() bool {
|
||||
ipv6RouteDst := &net.IPNet{
|
||||
IP: ipv6.IP,
|
||||
Mask: net.IPMask{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
||||
}
|
||||
routes, _ := netlink.RouteListFiltered(netlink.FAMILY_ALL, &netlink.Route{
|
||||
Dst: ipv6RouteDst,
|
||||
Table: 0,
|
||||
}, netlink.RT_FILTER_DST|netlink.RT_FILTER_TABLE)
|
||||
return err == nil && len(routes) >= 1
|
||||
}, time.Second, 500*time.Millisecond).Should(BeTrue())
|
||||
|
||||
ipAddrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
// Check if address was assigned properly
|
||||
Expect(ipAddrs[0].IP.String()).To(Equal("10.0.0.3"))
|
||||
|
||||
// Set interface UP, otherwise local route to 10.0.0.0/24 is not present
|
||||
err = netlink.LinkSetUp(link)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Add additional route to 10.11.10.0/24 via 10.0.0.1 gateway
|
||||
r := netlink.Route{
|
||||
LinkIndex: link.Attrs().Index,
|
||||
Dst: routev4,
|
||||
Gw: net.ParseIP("10.0.0.1"),
|
||||
Priority: 100,
|
||||
}
|
||||
err = netlink.RouteAdd(&r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
r6 := netlink.Route{
|
||||
LinkIndex: link.Attrs().Index,
|
||||
Src: ipv6.IP,
|
||||
Dst: routev6,
|
||||
Gw: net.ParseIP("abcd:1234:ffff::1"),
|
||||
}
|
||||
err = netlink.RouteAdd(&r6)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
By("Adding if1 to the VRF", func() {
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: IF1Name,
|
||||
StdinData: conf1,
|
||||
}
|
||||
_, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
By("Checking routes are moved correctly to VRF", func() {
|
||||
err := targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
checkInterfaceOnVRF(VRF1Name, IF1Name)
|
||||
checkRoutesOnVRF(VRF1Name, IF1Name, "10.0.0.3", "10.11.10.0/24", "1111:ddde::/80")
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
It("fails if the interface already has a master set", func() {
|
||||
conf := configFor("test", IF0Name, VRF0Name, "10.0.0.2/24")
|
||||
|
||||
@ -185,10 +474,10 @@ var _ = Describe("vrf plugin", func() {
|
||||
defer GinkgoRecover()
|
||||
l, err := netlink.LinkByName(IF0Name)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = "testrbridge"
|
||||
br := &netlink.Bridge{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: "testrbridge",
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
}
|
||||
err = netlink.LinkAdd(br)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@ -690,6 +979,35 @@ func configWithTableFor(name, intf, vrf, ip string, tableID int) []byte {
|
||||
return []byte(conf)
|
||||
}
|
||||
|
||||
func configWithRouteFor(name, intf, vrf, ip, route string) []byte {
|
||||
conf := fmt.Sprintf(`{
|
||||
"name": "%s",
|
||||
"type": "vrf",
|
||||
"cniVersion": "0.3.1",
|
||||
"vrfName": "%s",
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{"name": "%s", "sandbox":"netns"}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "%s",
|
||||
"gateway": "10.0.0.1",
|
||||
"interface": 0
|
||||
}
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"dst": "%s",
|
||||
"gw": "10.0.0.1"
|
||||
}
|
||||
]
|
||||
}
|
||||
}`, name, vrf, intf, ip, route)
|
||||
return []byte(conf)
|
||||
}
|
||||
|
||||
func checkInterfaceOnVRF(vrfName, intfName string) {
|
||||
vrf, err := netlink.LinkByName(vrfName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@ -702,3 +1020,46 @@ func checkInterfaceOnVRF(vrfName, intfName string) {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(master.Attrs().Name).To(Equal(vrfName))
|
||||
}
|
||||
|
||||
func checkRoutesOnVRF(vrfName, intfName string, addrStr string, routesToCheck ...string) {
|
||||
l, err := netlink.LinkByName(vrfName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(l).To(BeAssignableToTypeOf(&netlink.Vrf{}))
|
||||
|
||||
vrf, ok := l.(*netlink.Vrf)
|
||||
Expect(ok).To(BeTrue())
|
||||
|
||||
link, err := netlink.LinkByName(intfName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = netlink.LinkSetUp(link)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
ipAddrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(ipAddrs).To(HaveLen(1))
|
||||
Expect(ipAddrs[0].IP.String()).To(Equal(addrStr))
|
||||
|
||||
routeFilter := &netlink.Route{
|
||||
Table: int(vrf.Table),
|
||||
}
|
||||
|
||||
routes, err := netlink.RouteListFiltered(netlink.FAMILY_ALL,
|
||||
routeFilter,
|
||||
netlink.RT_FILTER_TABLE)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
routesRead := []string{}
|
||||
for _, route := range routes {
|
||||
routesRead = append(routesRead, route.String())
|
||||
Expect(uint32(route.Table)).To(Equal(vrf.Table))
|
||||
}
|
||||
routesStr := strings.Join(routesRead, "\n")
|
||||
for _, route := range routesToCheck {
|
||||
Expect(routesStr).To(ContainSubstring(route))
|
||||
}
|
||||
|
||||
for _, route := range routes {
|
||||
Expect(route.LinkIndex).To(Equal(link.Attrs().Index))
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
current "github.com/containernetworking/cni/pkg/types/100"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
"github.com/containernetworking/plugins/pkg/ipam"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
@ -150,10 +151,40 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
|
||||
func main() {
|
||||
// replace TODO with your plugin name
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("TODO"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
Status: cmdStatus,
|
||||
/* FIXME GC */
|
||||
}, version.All, bv.BuildString("TODO"))
|
||||
}
|
||||
|
||||
func cmdCheck(_ *skel.CmdArgs) error {
|
||||
// TODO: implement
|
||||
return fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
// cmdStatus implements the STATUS command, which indicates whether or not
|
||||
// this plugin is able to accept ADD requests.
|
||||
//
|
||||
// If the plugin has external dependencies, such as a daemon
|
||||
// or chained ipam plugin, it should determine their status. If all is well,
|
||||
// and an ADD can be successfully processed, return nil
|
||||
func cmdStatus(args *skel.CmdArgs) error {
|
||||
conf, err := parseConfig(args.StdinData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = conf
|
||||
|
||||
// If this plugins delegates IPAM, ensure that IPAM is also running
|
||||
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: implement STATUS here
|
||||
// e.g. querying an external deamon, or delegating STATUS to an IPAM plugin
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -1,48 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -xe
|
||||
|
||||
SRC_DIR="${SRC_DIR:-$PWD}"
|
||||
DOCKER="${DOCKER:-docker}"
|
||||
|
||||
TAG=$(git describe --tags --dirty)
|
||||
RELEASE_DIR=release-${TAG}
|
||||
|
||||
BUILDFLAGS="-ldflags '-extldflags -static -X github.com/containernetworking/plugins/pkg/utils/buildversion.BuildVersion=${TAG}'"
|
||||
|
||||
OUTPUT_DIR=bin
|
||||
|
||||
# Always clean first
|
||||
rm -Rf ${SRC_DIR}/${RELEASE_DIR}
|
||||
mkdir -p ${SRC_DIR}/${RELEASE_DIR}
|
||||
mkdir -p ${OUTPUT_DIR}
|
||||
|
||||
$DOCKER run -ti -v ${SRC_DIR}:/go/src/github.com/containernetworking/plugins:z --rm golang:1.20-alpine \
|
||||
/bin/sh -xe -c "\
|
||||
apk --no-cache add bash tar;
|
||||
cd /go/src/github.com/containernetworking/plugins; umask 0022;
|
||||
|
||||
for arch in amd64 arm arm64 ppc64le s390x mips64le riscv64; do \
|
||||
rm -f ${OUTPUT_DIR}/*; \
|
||||
CGO_ENABLED=0 GOARCH=\$arch ./build_linux.sh ${BUILDFLAGS}; \
|
||||
for format in tgz; do \
|
||||
FILENAME=cni-plugins-linux-\$arch-${TAG}.\$format; \
|
||||
FILEPATH=${RELEASE_DIR}/\$FILENAME; \
|
||||
tar -C ${OUTPUT_DIR} --owner=0 --group=0 -caf \$FILEPATH .; \
|
||||
done; \
|
||||
done;
|
||||
|
||||
rm -rf ${OUTPUT_DIR}/*; \
|
||||
CGO_ENABLED=0 GOARCH=amd64 ./build_windows.sh ${BUILDFLAGS}; \
|
||||
for format in tgz; do \
|
||||
FILENAME=cni-plugins-windows-amd64-${TAG}.\$format; \
|
||||
FILEPATH=${RELEASE_DIR}/\$FILENAME; \
|
||||
tar -C ${OUTPUT_DIR} --owner=0 --group=0 -caf \$FILEPATH .; \
|
||||
done;
|
||||
|
||||
|
||||
cd ${RELEASE_DIR};
|
||||
for f in *.tgz; do sha1sum \$f > \$f.sha1; done;
|
||||
for f in *.tgz; do sha256sum \$f > \$f.sha256; done;
|
||||
for f in *.tgz; do sha512sum \$f > \$f.sha512; done;
|
||||
cd ..
|
||||
chown -R ${UID} ${OUTPUT_DIR} ${RELEASE_DIR}"
|
@ -10,12 +10,29 @@ set -e
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# Build all plugins before testing
|
||||
source ./build_linux.sh
|
||||
. ./build_linux.sh
|
||||
|
||||
echo "Running tests"
|
||||
|
||||
function testrun {
|
||||
sudo -E bash -c "umask 0; PATH=${GOPATH}/bin:$(pwd)/bin:${PATH} go test $@"
|
||||
testrun() {
|
||||
sudo -E sh -c "umask 0; PATH=${GOPATH}/bin:$(pwd)/bin:${PATH} go test -race $*"
|
||||
}
|
||||
|
||||
ensure_sysctl() {
|
||||
local key
|
||||
local val
|
||||
local existing
|
||||
|
||||
key="$1"
|
||||
val="$2"
|
||||
existing="$(sysctl -ben "$key")"
|
||||
|
||||
sysctl -r
|
||||
|
||||
if [ "$val" -ne "$existing" ]; then
|
||||
echo "sudo sysctl -we '$key'='$val'"
|
||||
sudo sysctl -we "$key"="$val"
|
||||
fi
|
||||
}
|
||||
|
||||
COVERALLS=${COVERALLS:-""}
|
||||
@ -31,7 +48,7 @@ PKG=${PKG:-$(go list ./... | xargs echo)}
|
||||
i=0
|
||||
for t in ${PKG}; do
|
||||
if [ -n "${COVERALLS}" ]; then
|
||||
COVERFLAGS="-covermode set -coverprofile ${i}.coverprofile"
|
||||
COVERFLAGS="-covermode atomic -coverprofile ${i}.coverprofile"
|
||||
fi
|
||||
echo "${t}"
|
||||
testrun "${COVERFLAGS:-""} ${t}"
|
||||
@ -39,5 +56,8 @@ for t in ${PKG}; do
|
||||
done
|
||||
|
||||
# Run the pkg/ns tests as non root user
|
||||
mkdir /tmp/cni-rootless
|
||||
mkdir -p /tmp/cni-rootless
|
||||
ensure_sysctl kernel.unprivileged_userns_clone 1
|
||||
ensure_sysctl kernel.apparmor_restrict_unprivileged_userns 0
|
||||
|
||||
(export XDG_RUNTIME_DIR=/tmp/cni-rootless; cd pkg/ns/; unshare -rmn go test)
|
||||
|
@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/usr/bin/env sh
|
||||
#
|
||||
# Run CNI plugin tests.
|
||||
#
|
||||
set -e
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
source ./build_windows.sh
|
||||
. ./build_windows.sh
|
||||
|
||||
echo "Running tests"
|
||||
|
||||
@ -17,4 +17,4 @@ for d in $PLUGINS; do
|
||||
done
|
||||
|
||||
echo "testing packages $PKGS"
|
||||
go test -v $PKGS -ginkgo.randomizeAllSpecs -ginkgo.failOnPending -ginkgo.progress
|
||||
go test -race -v $PKGS -ginkgo.randomizeAllSpecs -ginkgo.failOnPending -ginkgo.progress
|
||||
|
37
vendor/github.com/Microsoft/go-winio/.golangci.yml
generated
vendored
37
vendor/github.com/Microsoft/go-winio/.golangci.yml
generated
vendored
@ -1,19 +1,11 @@
|
||||
run:
|
||||
skip-dirs:
|
||||
- pkg/etw/sample
|
||||
|
||||
linters:
|
||||
enable:
|
||||
# style
|
||||
- containedctx # struct contains a context
|
||||
- dupl # duplicate code
|
||||
- errname # erorrs are named correctly
|
||||
- goconst # strings that should be constants
|
||||
- godot # comments end in a period
|
||||
- misspell
|
||||
- nolintlint # "//nolint" directives are properly explained
|
||||
- revive # golint replacement
|
||||
- stylecheck # golint replacement, less configurable than revive
|
||||
- unconvert # unnecessary conversions
|
||||
- wastedassign
|
||||
|
||||
@ -23,13 +15,14 @@ linters:
|
||||
- exhaustive # check exhaustiveness of enum switch statements
|
||||
- gofmt # files are gofmt'ed
|
||||
- gosec # security
|
||||
- nestif # deeply nested ifs
|
||||
- nilerr # returns nil even with non-nil error
|
||||
- prealloc # slices that can be pre-allocated
|
||||
- structcheck # unused struct fields
|
||||
- thelper # test helpers without t.Helper()
|
||||
- unparam # unused function params
|
||||
|
||||
issues:
|
||||
exclude-dirs:
|
||||
- pkg/etw/sample
|
||||
|
||||
exclude-rules:
|
||||
# err is very often shadowed in nested scopes
|
||||
- linters:
|
||||
@ -42,6 +35,18 @@ issues:
|
||||
text: "^line-length-limit: "
|
||||
source: "^//(go:generate|sys) "
|
||||
|
||||
#TODO: remove after upgrading to go1.18
|
||||
# ignore comment spacing for nolint and sys directives
|
||||
- linters:
|
||||
- revive
|
||||
text: "^comment-spacings: no space between comment delimiter and comment text"
|
||||
source: "//(cspell:|nolint:|sys |todo)"
|
||||
|
||||
# not on go 1.18 yet, so no any
|
||||
- linters:
|
||||
- revive
|
||||
text: "^use-any: since GO 1.18 'interface{}' can be replaced by 'any'"
|
||||
|
||||
# allow unjustified ignores of error checks in defer statements
|
||||
- linters:
|
||||
- nolintlint
|
||||
@ -56,15 +61,15 @@ issues:
|
||||
|
||||
|
||||
linters-settings:
|
||||
exhaustive:
|
||||
default-signifies-exhaustive: true
|
||||
govet:
|
||||
enable-all: true
|
||||
disable:
|
||||
# struct order is often for Win32 compat
|
||||
# also, ignore pointer bytes/GC issues for now until performance becomes an issue
|
||||
- fieldalignment
|
||||
check-shadowing: true
|
||||
nolintlint:
|
||||
allow-leading-space: false
|
||||
require-explanation: true
|
||||
require-specific: true
|
||||
revive:
|
||||
@ -98,6 +103,8 @@ linters-settings:
|
||||
disabled: true
|
||||
- name: flag-parameter # excessive, and a common idiom we use
|
||||
disabled: true
|
||||
- name: unhandled-error # warns over common fmt.Print* and io.Close; rely on errcheck instead
|
||||
disabled: true
|
||||
# general config
|
||||
- name: line-length-limit
|
||||
arguments:
|
||||
@ -138,7 +145,3 @@ linters-settings:
|
||||
- VPCI
|
||||
- WCOW
|
||||
- WIM
|
||||
stylecheck:
|
||||
checks:
|
||||
- "all"
|
||||
- "-ST1003" # use revive's var naming
|
||||
|
33
vendor/github.com/Microsoft/go-winio/backup.go
generated
vendored
33
vendor/github.com/Microsoft/go-winio/backup.go
generated
vendored
@ -10,14 +10,14 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unicode/utf16"
|
||||
|
||||
"github.com/Microsoft/go-winio/internal/fs"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
//sys backupRead(h syscall.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupRead
|
||||
//sys backupWrite(h syscall.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupWrite
|
||||
//sys backupRead(h windows.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupRead
|
||||
//sys backupWrite(h windows.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupWrite
|
||||
|
||||
const (
|
||||
BackupData = uint32(iota + 1)
|
||||
@ -104,7 +104,7 @@ func (r *BackupStreamReader) Next() (*BackupHeader, error) {
|
||||
if err := binary.Read(r.r, binary.LittleEndian, name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hdr.Name = syscall.UTF16ToString(name)
|
||||
hdr.Name = windows.UTF16ToString(name)
|
||||
}
|
||||
if wsi.StreamID == BackupSparseBlock {
|
||||
if err := binary.Read(r.r, binary.LittleEndian, &hdr.Offset); err != nil {
|
||||
@ -205,7 +205,7 @@ func NewBackupFileReader(f *os.File, includeSecurity bool) *BackupFileReader {
|
||||
// Read reads a backup stream from the file by calling the Win32 API BackupRead().
|
||||
func (r *BackupFileReader) Read(b []byte) (int, error) {
|
||||
var bytesRead uint32
|
||||
err := backupRead(syscall.Handle(r.f.Fd()), b, &bytesRead, false, r.includeSecurity, &r.ctx)
|
||||
err := backupRead(windows.Handle(r.f.Fd()), b, &bytesRead, false, r.includeSecurity, &r.ctx)
|
||||
if err != nil {
|
||||
return 0, &os.PathError{Op: "BackupRead", Path: r.f.Name(), Err: err}
|
||||
}
|
||||
@ -220,7 +220,7 @@ func (r *BackupFileReader) Read(b []byte) (int, error) {
|
||||
// the underlying file.
|
||||
func (r *BackupFileReader) Close() error {
|
||||
if r.ctx != 0 {
|
||||
_ = backupRead(syscall.Handle(r.f.Fd()), nil, nil, true, false, &r.ctx)
|
||||
_ = backupRead(windows.Handle(r.f.Fd()), nil, nil, true, false, &r.ctx)
|
||||
runtime.KeepAlive(r.f)
|
||||
r.ctx = 0
|
||||
}
|
||||
@ -244,7 +244,7 @@ func NewBackupFileWriter(f *os.File, includeSecurity bool) *BackupFileWriter {
|
||||
// Write restores a portion of the file using the provided backup stream.
|
||||
func (w *BackupFileWriter) Write(b []byte) (int, error) {
|
||||
var bytesWritten uint32
|
||||
err := backupWrite(syscall.Handle(w.f.Fd()), b, &bytesWritten, false, w.includeSecurity, &w.ctx)
|
||||
err := backupWrite(windows.Handle(w.f.Fd()), b, &bytesWritten, false, w.includeSecurity, &w.ctx)
|
||||
if err != nil {
|
||||
return 0, &os.PathError{Op: "BackupWrite", Path: w.f.Name(), Err: err}
|
||||
}
|
||||
@ -259,7 +259,7 @@ func (w *BackupFileWriter) Write(b []byte) (int, error) {
|
||||
// close the underlying file.
|
||||
func (w *BackupFileWriter) Close() error {
|
||||
if w.ctx != 0 {
|
||||
_ = backupWrite(syscall.Handle(w.f.Fd()), nil, nil, true, false, &w.ctx)
|
||||
_ = backupWrite(windows.Handle(w.f.Fd()), nil, nil, true, false, &w.ctx)
|
||||
runtime.KeepAlive(w.f)
|
||||
w.ctx = 0
|
||||
}
|
||||
@ -271,17 +271,14 @@ func (w *BackupFileWriter) Close() error {
|
||||
//
|
||||
// If the file opened was a directory, it cannot be used with Readdir().
|
||||
func OpenForBackup(path string, access uint32, share uint32, createmode uint32) (*os.File, error) {
|
||||
winPath, err := syscall.UTF16FromString(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h, err := syscall.CreateFile(&winPath[0],
|
||||
access,
|
||||
share,
|
||||
h, err := fs.CreateFile(path,
|
||||
fs.AccessMask(access),
|
||||
fs.FileShareMode(share),
|
||||
nil,
|
||||
createmode,
|
||||
syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OPEN_REPARSE_POINT,
|
||||
0)
|
||||
fs.FileCreationDisposition(createmode),
|
||||
fs.FILE_FLAG_BACKUP_SEMANTICS|fs.FILE_FLAG_OPEN_REPARSE_POINT,
|
||||
0,
|
||||
)
|
||||
if err != nil {
|
||||
err = &os.PathError{Op: "open", Path: path, Err: err}
|
||||
return nil, err
|
||||
|
85
vendor/github.com/Microsoft/go-winio/file.go
generated
vendored
85
vendor/github.com/Microsoft/go-winio/file.go
generated
vendored
@ -15,26 +15,11 @@ import (
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
//sys cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) = CancelIoEx
|
||||
//sys createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) = CreateIoCompletionPort
|
||||
//sys getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) = GetQueuedCompletionStatus
|
||||
//sys setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes
|
||||
//sys wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) = ws2_32.WSAGetOverlappedResult
|
||||
|
||||
type atomicBool int32
|
||||
|
||||
func (b *atomicBool) isSet() bool { return atomic.LoadInt32((*int32)(b)) != 0 }
|
||||
func (b *atomicBool) setFalse() { atomic.StoreInt32((*int32)(b), 0) }
|
||||
func (b *atomicBool) setTrue() { atomic.StoreInt32((*int32)(b), 1) }
|
||||
|
||||
//revive:disable-next-line:predeclared Keep "new" to maintain consistency with "atomic" pkg
|
||||
func (b *atomicBool) swap(new bool) bool {
|
||||
var newInt int32
|
||||
if new {
|
||||
newInt = 1
|
||||
}
|
||||
return atomic.SwapInt32((*int32)(b), newInt) == 1
|
||||
}
|
||||
//sys cancelIoEx(file windows.Handle, o *windows.Overlapped) (err error) = CancelIoEx
|
||||
//sys createIoCompletionPort(file windows.Handle, port windows.Handle, key uintptr, threadCount uint32) (newport windows.Handle, err error) = CreateIoCompletionPort
|
||||
//sys getQueuedCompletionStatus(port windows.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) = GetQueuedCompletionStatus
|
||||
//sys setFileCompletionNotificationModes(h windows.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes
|
||||
//sys wsaGetOverlappedResult(h windows.Handle, o *windows.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) = ws2_32.WSAGetOverlappedResult
|
||||
|
||||
var (
|
||||
ErrFileClosed = errors.New("file has already been closed")
|
||||
@ -50,7 +35,7 @@ func (*timeoutError) Temporary() bool { return true }
|
||||
type timeoutChan chan struct{}
|
||||
|
||||
var ioInitOnce sync.Once
|
||||
var ioCompletionPort syscall.Handle
|
||||
var ioCompletionPort windows.Handle
|
||||
|
||||
// ioResult contains the result of an asynchronous IO operation.
|
||||
type ioResult struct {
|
||||
@ -60,12 +45,12 @@ type ioResult struct {
|
||||
|
||||
// ioOperation represents an outstanding asynchronous Win32 IO.
|
||||
type ioOperation struct {
|
||||
o syscall.Overlapped
|
||||
o windows.Overlapped
|
||||
ch chan ioResult
|
||||
}
|
||||
|
||||
func initIO() {
|
||||
h, err := createIoCompletionPort(syscall.InvalidHandle, 0, 0, 0xffffffff)
|
||||
h, err := createIoCompletionPort(windows.InvalidHandle, 0, 0, 0xffffffff)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -76,10 +61,10 @@ func initIO() {
|
||||
// win32File implements Reader, Writer, and Closer on a Win32 handle without blocking in a syscall.
|
||||
// It takes ownership of this handle and will close it if it is garbage collected.
|
||||
type win32File struct {
|
||||
handle syscall.Handle
|
||||
handle windows.Handle
|
||||
wg sync.WaitGroup
|
||||
wgLock sync.RWMutex
|
||||
closing atomicBool
|
||||
closing atomic.Bool
|
||||
socket bool
|
||||
readDeadline deadlineHandler
|
||||
writeDeadline deadlineHandler
|
||||
@ -90,11 +75,11 @@ type deadlineHandler struct {
|
||||
channel timeoutChan
|
||||
channelLock sync.RWMutex
|
||||
timer *time.Timer
|
||||
timedout atomicBool
|
||||
timedout atomic.Bool
|
||||
}
|
||||
|
||||
// makeWin32File makes a new win32File from an existing file handle.
|
||||
func makeWin32File(h syscall.Handle) (*win32File, error) {
|
||||
func makeWin32File(h windows.Handle) (*win32File, error) {
|
||||
f := &win32File{handle: h}
|
||||
ioInitOnce.Do(initIO)
|
||||
_, err := createIoCompletionPort(h, ioCompletionPort, 0, 0xffffffff)
|
||||
@ -110,7 +95,12 @@ func makeWin32File(h syscall.Handle) (*win32File, error) {
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// Deprecated: use NewOpenFile instead.
|
||||
func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) {
|
||||
return NewOpenFile(windows.Handle(h))
|
||||
}
|
||||
|
||||
func NewOpenFile(h windows.Handle) (io.ReadWriteCloser, error) {
|
||||
// If we return the result of makeWin32File directly, it can result in an
|
||||
// interface-wrapped nil, rather than a nil interface value.
|
||||
f, err := makeWin32File(h)
|
||||
@ -124,13 +114,13 @@ func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) {
|
||||
func (f *win32File) closeHandle() {
|
||||
f.wgLock.Lock()
|
||||
// Atomically set that we are closing, releasing the resources only once.
|
||||
if !f.closing.swap(true) {
|
||||
if !f.closing.Swap(true) {
|
||||
f.wgLock.Unlock()
|
||||
// cancel all IO and wait for it to complete
|
||||
_ = cancelIoEx(f.handle, nil)
|
||||
f.wg.Wait()
|
||||
// at this point, no new IO can start
|
||||
syscall.Close(f.handle)
|
||||
windows.Close(f.handle)
|
||||
f.handle = 0
|
||||
} else {
|
||||
f.wgLock.Unlock()
|
||||
@ -145,14 +135,14 @@ func (f *win32File) Close() error {
|
||||
|
||||
// IsClosed checks if the file has been closed.
|
||||
func (f *win32File) IsClosed() bool {
|
||||
return f.closing.isSet()
|
||||
return f.closing.Load()
|
||||
}
|
||||
|
||||
// prepareIO prepares for a new IO operation.
|
||||
// The caller must call f.wg.Done() when the IO is finished, prior to Close() returning.
|
||||
func (f *win32File) prepareIO() (*ioOperation, error) {
|
||||
f.wgLock.RLock()
|
||||
if f.closing.isSet() {
|
||||
if f.closing.Load() {
|
||||
f.wgLock.RUnlock()
|
||||
return nil, ErrFileClosed
|
||||
}
|
||||
@ -164,12 +154,12 @@ func (f *win32File) prepareIO() (*ioOperation, error) {
|
||||
}
|
||||
|
||||
// ioCompletionProcessor processes completed async IOs forever.
|
||||
func ioCompletionProcessor(h syscall.Handle) {
|
||||
func ioCompletionProcessor(h windows.Handle) {
|
||||
for {
|
||||
var bytes uint32
|
||||
var key uintptr
|
||||
var op *ioOperation
|
||||
err := getQueuedCompletionStatus(h, &bytes, &key, &op, syscall.INFINITE)
|
||||
err := getQueuedCompletionStatus(h, &bytes, &key, &op, windows.INFINITE)
|
||||
if op == nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -182,11 +172,11 @@ func ioCompletionProcessor(h syscall.Handle) {
|
||||
// asyncIO processes the return value from ReadFile or WriteFile, blocking until
|
||||
// the operation has actually completed.
|
||||
func (f *win32File) asyncIO(c *ioOperation, d *deadlineHandler, bytes uint32, err error) (int, error) {
|
||||
if err != syscall.ERROR_IO_PENDING { //nolint:errorlint // err is Errno
|
||||
if err != windows.ERROR_IO_PENDING { //nolint:errorlint // err is Errno
|
||||
return int(bytes), err
|
||||
}
|
||||
|
||||
if f.closing.isSet() {
|
||||
if f.closing.Load() {
|
||||
_ = cancelIoEx(f.handle, &c.o)
|
||||
}
|
||||
|
||||
@ -201,8 +191,8 @@ func (f *win32File) asyncIO(c *ioOperation, d *deadlineHandler, bytes uint32, er
|
||||
select {
|
||||
case r = <-c.ch:
|
||||
err = r.err
|
||||
if err == syscall.ERROR_OPERATION_ABORTED { //nolint:errorlint // err is Errno
|
||||
if f.closing.isSet() {
|
||||
if err == windows.ERROR_OPERATION_ABORTED { //nolint:errorlint // err is Errno
|
||||
if f.closing.Load() {
|
||||
err = ErrFileClosed
|
||||
}
|
||||
} else if err != nil && f.socket {
|
||||
@ -214,7 +204,7 @@ func (f *win32File) asyncIO(c *ioOperation, d *deadlineHandler, bytes uint32, er
|
||||
_ = cancelIoEx(f.handle, &c.o)
|
||||
r = <-c.ch
|
||||
err = r.err
|
||||
if err == syscall.ERROR_OPERATION_ABORTED { //nolint:errorlint // err is Errno
|
||||
if err == windows.ERROR_OPERATION_ABORTED { //nolint:errorlint // err is Errno
|
||||
err = ErrTimeout
|
||||
}
|
||||
}
|
||||
@ -235,23 +225,22 @@ func (f *win32File) Read(b []byte) (int, error) {
|
||||
}
|
||||
defer f.wg.Done()
|
||||
|
||||
if f.readDeadline.timedout.isSet() {
|
||||
if f.readDeadline.timedout.Load() {
|
||||
return 0, ErrTimeout
|
||||
}
|
||||
|
||||
var bytes uint32
|
||||
err = syscall.ReadFile(f.handle, b, &bytes, &c.o)
|
||||
err = windows.ReadFile(f.handle, b, &bytes, &c.o)
|
||||
n, err := f.asyncIO(c, &f.readDeadline, bytes, err)
|
||||
runtime.KeepAlive(b)
|
||||
|
||||
// Handle EOF conditions.
|
||||
if err == nil && n == 0 && len(b) != 0 {
|
||||
return 0, io.EOF
|
||||
} else if err == syscall.ERROR_BROKEN_PIPE { //nolint:errorlint // err is Errno
|
||||
} else if err == windows.ERROR_BROKEN_PIPE { //nolint:errorlint // err is Errno
|
||||
return 0, io.EOF
|
||||
} else {
|
||||
return n, err
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Write writes to a file handle.
|
||||
@ -262,12 +251,12 @@ func (f *win32File) Write(b []byte) (int, error) {
|
||||
}
|
||||
defer f.wg.Done()
|
||||
|
||||
if f.writeDeadline.timedout.isSet() {
|
||||
if f.writeDeadline.timedout.Load() {
|
||||
return 0, ErrTimeout
|
||||
}
|
||||
|
||||
var bytes uint32
|
||||
err = syscall.WriteFile(f.handle, b, &bytes, &c.o)
|
||||
err = windows.WriteFile(f.handle, b, &bytes, &c.o)
|
||||
n, err := f.asyncIO(c, &f.writeDeadline, bytes, err)
|
||||
runtime.KeepAlive(b)
|
||||
return n, err
|
||||
@ -282,7 +271,7 @@ func (f *win32File) SetWriteDeadline(deadline time.Time) error {
|
||||
}
|
||||
|
||||
func (f *win32File) Flush() error {
|
||||
return syscall.FlushFileBuffers(f.handle)
|
||||
return windows.FlushFileBuffers(f.handle)
|
||||
}
|
||||
|
||||
func (f *win32File) Fd() uintptr {
|
||||
@ -299,7 +288,7 @@ func (d *deadlineHandler) set(deadline time.Time) error {
|
||||
}
|
||||
d.timer = nil
|
||||
}
|
||||
d.timedout.setFalse()
|
||||
d.timedout.Store(false)
|
||||
|
||||
select {
|
||||
case <-d.channel:
|
||||
@ -314,7 +303,7 @@ func (d *deadlineHandler) set(deadline time.Time) error {
|
||||
}
|
||||
|
||||
timeoutIO := func() {
|
||||
d.timedout.setTrue()
|
||||
d.timedout.Store(true)
|
||||
close(d.channel)
|
||||
}
|
||||
|
||||
|
22
vendor/github.com/Microsoft/go-winio/fileinfo.go
generated
vendored
22
vendor/github.com/Microsoft/go-winio/fileinfo.go
generated
vendored
@ -18,9 +18,18 @@ type FileBasicInfo struct {
|
||||
_ uint32 // padding
|
||||
}
|
||||
|
||||
// alignedFileBasicInfo is a FileBasicInfo, but aligned to uint64 by containing
|
||||
// uint64 rather than windows.Filetime. Filetime contains two uint32s. uint64
|
||||
// alignment is necessary to pass this as FILE_BASIC_INFO.
|
||||
type alignedFileBasicInfo struct {
|
||||
CreationTime, LastAccessTime, LastWriteTime, ChangeTime uint64
|
||||
FileAttributes uint32
|
||||
_ uint32 // padding
|
||||
}
|
||||
|
||||
// GetFileBasicInfo retrieves times and attributes for a file.
|
||||
func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) {
|
||||
bi := &FileBasicInfo{}
|
||||
bi := &alignedFileBasicInfo{}
|
||||
if err := windows.GetFileInformationByHandleEx(
|
||||
windows.Handle(f.Fd()),
|
||||
windows.FileBasicInfo,
|
||||
@ -30,16 +39,21 @@ func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) {
|
||||
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
|
||||
}
|
||||
runtime.KeepAlive(f)
|
||||
return bi, nil
|
||||
// Reinterpret the alignedFileBasicInfo as a FileBasicInfo so it matches the
|
||||
// public API of this module. The data may be unnecessarily aligned.
|
||||
return (*FileBasicInfo)(unsafe.Pointer(bi)), nil
|
||||
}
|
||||
|
||||
// SetFileBasicInfo sets times and attributes for a file.
|
||||
func SetFileBasicInfo(f *os.File, bi *FileBasicInfo) error {
|
||||
// Create an alignedFileBasicInfo based on a FileBasicInfo. The copy is
|
||||
// suitable to pass to GetFileInformationByHandleEx.
|
||||
biAligned := *(*alignedFileBasicInfo)(unsafe.Pointer(bi))
|
||||
if err := windows.SetFileInformationByHandle(
|
||||
windows.Handle(f.Fd()),
|
||||
windows.FileBasicInfo,
|
||||
(*byte)(unsafe.Pointer(bi)),
|
||||
uint32(unsafe.Sizeof(*bi)),
|
||||
(*byte)(unsafe.Pointer(&biAligned)),
|
||||
uint32(unsafe.Sizeof(biAligned)),
|
||||
); err != nil {
|
||||
return &os.PathError{Op: "SetFileInformationByHandle", Path: f.Name(), Err: err}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user