Compare commits

..

3 Commits

Author SHA1 Message Date
4744ec27b8 Merge pull request #716 from squeed/cp-bugfixes
[release-1.1] Cherry-pick some bugfixes
2022-03-09 09:06:40 -08:00
b1782e50d7 ipam/dhcp: Fix client id in renew/release
The client id was constructed differently in the acquire
function compared to the release and renew functions,
which caused the dhcp-server to consider it a different client.
This is now encapsulated in a common function.

Signed-off-by: Fabian Wiesel <fabian.wiesel@sap.com>
2022-03-09 17:47:10 +01:00
b03deb63a9 call ipam.ExceDel after clean up device in netns
fix #666

Signed-off-by: gojoy <729324352@qq.com>
2022-03-09 17:46:59 +01:00
1524 changed files with 72049 additions and 179589 deletions

View File

@ -1,4 +1,4 @@
FROM alpine:3.20 FROM alpine:3.10
RUN apk add --no-cache curl jq RUN apk add --no-cache curl jq

View File

@ -8,4 +8,4 @@ runs:
using: 'docker' using: 'docker'
image: 'Dockerfile' image: 'Dockerfile'
env: env:
GITHUB_TOKEN: ${{ inputs.token }} GITHUB_TOKEN: ${{ inputs.token }}

View File

@ -27,10 +27,10 @@ curl --request GET \
--header "authorization: Bearer ${GITHUB_TOKEN}" \ --header "authorization: Bearer ${GITHUB_TOKEN}" \
--header "content-type: application/json" | jq '.workflow_runs | max_by(.run_number)' > run.json --header "content-type: application/json" | jq '.workflow_runs | max_by(.run_number)' > run.json
RUN_URL=$(jq -r '.rerun_url' run.json) RERUN_URL=$(jq -r '.rerun_url' run.json)
curl --request POST \ curl --request POST \
--url "${RUN_URL}/rerun-failed-jobs" \ --url "${RERUN_URL}" \
--header "authorization: Bearer ${GITHUB_TOKEN}" \ --header "authorization: Bearer ${GITHUB_TOKEN}" \
--header "content-type: application/json" --header "content-type: application/json"
@ -42,4 +42,4 @@ curl --request POST \
--header "authorization: Bearer ${GITHUB_TOKEN}" \ --header "authorization: Bearer ${GITHUB_TOKEN}" \
--header "accept: application/vnd.github.squirrel-girl-preview+json" \ --header "accept: application/vnd.github.squirrel-girl-preview+json" \
--header "content-type: application/json" \ --header "content-type: application/json" \
--data '{ "content" : "rocket" }' --data '{ "content" : "rocket" }'

View File

@ -1,25 +0,0 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "docker" # See documentation for possible values
directory: "/.github/actions/retest-action" # Location of package manifests
schedule:
interval: "weekly"
- package-ecosystem: "github-actions" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
- package-ecosystem: "gomod" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
groups:
golang:
patterns:
- "*"
exclude-patterns:
- "github.com/containernetworking/*"

View File

@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out code - name: Check out code
uses: actions/checkout@v4 uses: actions/checkout@v2
- name: Re-Test Action - name: Re-Test Action
uses: ./.github/actions/retest-action uses: ./.github/actions/retest-action

View File

@ -1,114 +0,0 @@
---
name: Release binaries
on:
push:
tags:
- 'v*'
jobs:
linux_release:
name: Release linux binaries
runs-on: ubuntu-latest
strategy:
matrix:
goarch: [amd64, arm, arm64, mips64le, ppc64le, riscv64, s390x]
steps:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: 1.22
- name: Checkout code
uses: actions/checkout@v4
- name: Build
env:
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: 0
run: ./build_linux.sh -ldflags '-extldflags -static -X github.com/containernetworking/plugins/pkg/utils/buildversion.BuildVersion=${{ github.ref_name }}'
- name: COPY files
run: cp README.md LICENSE bin/
- name: Change plugin file ownership
working-directory: ./bin
run: sudo chown 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: Install Go
uses: actions/setup-go@v5
with:
go-version: 1.21
- name: Checkout code
uses: actions/checkout@v4
- name: Build
env:
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: 0
run: ./build_windows.sh -ldflags '-extldflags -static -X github.com/containernetworking/plugins/pkg/utils/buildversion.BuildVersion=${{ github.ref_name }}'
- name: COPY files
run: cp README.md LICENSE bin/
- name: Change plugin file ownership
working-directory: ./bin
run: sudo chown 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

View File

@ -4,37 +4,20 @@ name: test
on: ["push", "pull_request"] on: ["push", "pull_request"]
env: env:
GO_VERSION: "1.22" GO_VERSION: "1.17"
LINUX_ARCHES: "amd64 386 arm arm64 s390x mips64le ppc64le riscv64" LINUX_ARCHES: "amd64 386 arm arm64 s390x mips64le ppc64le"
jobs: jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: setup go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
- uses: ibiqlik/action-yamllint@v3
with:
format: auto
- uses: golangci/golangci-lint-action@v6
with:
version: v1.55.2
args: -v
skip-cache: true
build: build:
name: Build all linux architectures name: Build all linux architectures
needs: lint
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4
- name: setup go - name: setup go
uses: actions/setup-go@v5 uses: actions/setup-go@v2
with: with:
go-version-file: go.mod go-version: ${{ env.GO_VERSION }}
- uses: actions/checkout@v2
- name: Build on all supported architectures - name: Build on all supported architectures
run: | run: |
set -e set -e
@ -43,9 +26,9 @@ jobs:
GOARCH=$arch ./build_linux.sh GOARCH=$arch ./build_linux.sh
rm bin/* rm bin/*
done done
test-linux: test-linux:
name: Run tests on Linux amd64 name: Run tests on Linux amd64
needs: build
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Install kernel module - name: Install kernel module
@ -54,21 +37,24 @@ jobs:
sudo apt-get install linux-modules-extra-$(uname -r) sudo apt-get install linux-modules-extra-$(uname -r)
- name: Install nftables - name: Install nftables
run: sudo apt-get install nftables run: sudo apt-get install nftables
- uses: actions/checkout@v4
- name: setup go - name: setup go
uses: actions/setup-go@v5 uses: actions/setup-go@v2
with: with:
go-version-file: go.mod go-version: ${{ env.GO_VERSION }}
- name: Set up Go for root - name: Set up Go for root
run: | run: |
sudo ln -sf `which go` `sudo which go` || true sudo ln -sf `which go` `sudo which go` || true
sudo go version sudo go version
- uses: actions/checkout@v2
- name: Install test binaries - name: Install test binaries
env:
GO111MODULE: off
run: | run: |
go install github.com/containernetworking/cni/cnitool@latest go get github.com/containernetworking/cni/cnitool
go install github.com/mattn/goveralls@latest go get github.com/mattn/goveralls
go install github.com/modocache/gover@latest go get github.com/modocache/gover
- name: test - name: test
run: PATH=$PATH:$(go env GOPATH)/bin COVERALLS=1 ./test_linux.sh run: PATH=$PATH:$(go env GOPATH)/bin COVERALLS=1 ./test_linux.sh
@ -80,15 +66,15 @@ jobs:
PATH=$PATH:$(go env GOPATH)/bin PATH=$PATH:$(go env GOPATH)/bin
gover gover
goveralls -coverprofile=gover.coverprofile -service=github goveralls -coverprofile=gover.coverprofile -service=github
test-win: test-win:
name: Build and run tests on Windows name: Build and run tests on Windows
needs: build
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- uses: actions/checkout@v4
- name: setup go - name: setup go
uses: actions/setup-go@v5 uses: actions/setup-go@v2
with: with:
go-version-file: go.mod go-version: ${{ env.GO_VERSION }}
- uses: actions/checkout@v2
- name: test - name: test
run: bash ./test_windows.sh run: bash ./test_windows.sh

View File

@ -1,45 +0,0 @@
issues:
exclude-rules:
- linters:
- revive
text: "don't use ALL_CAPS in Go names; use CamelCase"
- linters:
- revive
text: " and that stutters;"
- path: '(.+)_test\.go'
text: "dot-imports: should not use dot imports"
linters:
disable:
- errcheck
enable:
- contextcheck
- durationcheck
- gci
- ginkgolinter
- gocritic
- gofumpt
- gosimple
- govet
- ineffassign
- misspell
- nonamedreturns
- predeclared
- revive
- staticcheck
- unconvert
- unparam
- unused
- wastedassign
linters-settings:
gci:
sections:
- standard
- default
- prefix(github.com/containernetworking)
run:
skip-dirs:
- vendor
timeout: 5m

View File

@ -1,12 +0,0 @@
extends: default
ignore: |
vendor
rules:
document-start: disable
line-length: disable
truthy:
ignore: |
.github/workflows/*.yml
.github/workflows/*.yaml

View File

@ -7,4 +7,3 @@ This is the official list of the CNI network plugins owners:
- Matt Dupre <matt@tigera.io> (@matthewdupre) - Matt Dupre <matt@tigera.io> (@matthewdupre)
- Michael Cambria <mcambria@redhat.com> (@mccv1r0) - Michael Cambria <mcambria@redhat.com> (@mccv1r0)
- Piotr Skarmuk <piotr.skarmuk@gmail.com> (@jellonek) - Piotr Skarmuk <piotr.skarmuk@gmail.com> (@jellonek)
- Michael Zappa <michael.zappa@gmail.com> (@MikeZappa87)

View File

@ -14,14 +14,13 @@ Read [CONTRIBUTING](CONTRIBUTING.md) for build and test instructions.
* `ptp`: Creates a veth pair. * `ptp`: Creates a veth pair.
* `vlan`: Allocates a vlan device. * `vlan`: Allocates a vlan device.
* `host-device`: Move an already-existing device into a container. * `host-device`: Move an already-existing device into a container.
* `dummy`: Creates a new Dummy device in the container.
#### Windows: Windows specific #### Windows: Windows specific
* `win-bridge`: Creates a bridge, adds the host and the container to it. * `win-bridge`: Creates a bridge, adds the host and the container to it.
* `win-overlay`: Creates an overlay interface to the container. * `win-overlay`: Creates an overlay interface to the container.
### IPAM: IP address allocation ### IPAM: IP address allocation
* `dhcp`: Runs a daemon on the host to make DHCP requests on behalf of the container * `dhcp`: Runs a daemon on the host to make DHCP requests on behalf of the container
* `host-local`: Maintains a local database of allocated IPs * `host-local`: Maintains a local database of allocated IPs
* `static`: Allocate a single static IPv4/IPv6 address to container. It's useful in debugging purpose. * `static`: Allocate a static IPv4/IPv6 addresses to container and it's useful in debugging purpose.
### Meta: other plugins ### Meta: other plugins
* `tuning`: Tweaks sysctl parameters of an existing interface * `tuning`: Tweaks sysctl parameters of an existing interface

View File

@ -1,8 +1,8 @@
#!/usr/bin/env sh #!/usr/bin/env bash
set -e set -e
cd "$(dirname "$0")" cd "$(dirname "$0")"
if [ "$(uname)" = "Darwin" ]; then if [ "$(uname)" == "Darwin" ]; then
export GOOS="${GOOS:-linux}" export GOOS="${GOOS:-linux}"
fi fi

View File

@ -1,4 +1,4 @@
#!/usr/bin/env sh #!/usr/bin/env bash
set -e set -e
cd "$(dirname "$0")" cd "$(dirname "$0")"

60
go.mod
View File

@ -1,48 +1,40 @@
module github.com/containernetworking/plugins module github.com/containernetworking/plugins
go 1.20 go 1.17
require ( require (
github.com/Microsoft/hcsshim v0.12.3 github.com/Microsoft/hcsshim v0.8.20
github.com/alexflint/go-filemutex v1.3.0 github.com/alexflint/go-filemutex v1.1.0
github.com/buger/jsonparser v1.1.1 github.com/buger/jsonparser v1.1.1
github.com/containernetworking/cni v1.1.2 github.com/containernetworking/cni v1.0.1
github.com/coreos/go-iptables v0.7.0 github.com/coreos/go-iptables v0.6.0
github.com/coreos/go-systemd/v22 v22.5.0 github.com/coreos/go-systemd/v22 v22.3.2
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c
github.com/d2g/dhcp4client v1.0.0 github.com/d2g/dhcp4client v1.0.0
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5 github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5
github.com/godbus/dbus/v5 v5.1.0 github.com/godbus/dbus/v5 v5.0.4
github.com/mattn/go-shellwords v1.0.12 github.com/mattn/go-shellwords v1.0.12
github.com/networkplumbing/go-nft v0.4.0 github.com/networkplumbing/go-nft v0.2.0
github.com/onsi/ginkgo/v2 v2.19.0 github.com/onsi/ginkgo v1.16.4
github.com/onsi/gomega v1.33.1 github.com/onsi/gomega v1.15.0
github.com/opencontainers/selinux v1.11.0 github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1
github.com/safchain/ethtool v0.4.0 github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5
github.com/vishvananda/netlink v1.2.1-beta.2 golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e
golang.org/x/sys v0.21.0
) )
require ( require (
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.4.17 // indirect
github.com/containerd/cgroups/v3 v3.0.2 // indirect github.com/containerd/cgroups v1.0.1 // indirect
github.com/containerd/errdefs v0.1.0 // indirect github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/go-logr/logr v1.4.1 // indirect github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/nxadm/tail v1.4.8 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect github.com/sirupsen/logrus v1.8.1 // indirect
github.com/vishvananda/netns v0.0.4 // indirect github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect
go.opencensus.io v0.24.0 // indirect go.opencensus.io v0.22.3 // indirect
golang.org/x/net v0.25.0 // indirect golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 // indirect
golang.org/x/text v0.15.0 // indirect golang.org/x/text v0.3.6 // indirect
golang.org/x/tools v0.21.0 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
google.golang.org/grpc v1.62.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )

891
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // http://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
@ -14,21 +14,21 @@
package integration_test package integration_test
import ( import (
"bytes"
"fmt" "fmt"
"io"
"log"
"math/rand" "math/rand"
"net"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"bytes"
"io"
"net"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"time" "time"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gbytes"
"github.com/onsi/gomega/gexec" "github.com/onsi/gomega/gexec"
@ -61,13 +61,6 @@ var _ = Describe("Basic PTP using cnitool", func() {
netConfPath, err := filepath.Abs("./testdata") netConfPath, err := filepath.Abs("./testdata")
Expect(err).NotTo(HaveOccurred()) 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{ env = TestEnv([]string{
"CNI_PATH=" + cniPath, "CNI_PATH=" + cniPath,
"NETCONFPATH=" + netConfPath, "NETCONFPATH=" + netConfPath,
@ -90,7 +83,6 @@ var _ = Describe("Basic PTP using cnitool", func() {
env.runInNS(hostNS, cnitoolBin, "add", netName, contNS.LongName()) env.runInNS(hostNS, cnitoolBin, "add", netName, contNS.LongName())
addrOutput := env.runInNS(contNS, "ip", "addr") addrOutput := env.runInNS(contNS, "ip", "addr")
Expect(addrOutput).To(ContainSubstring(expectedIPPrefix)) Expect(addrOutput).To(ContainSubstring(expectedIPPrefix))
env.runInNS(hostNS, cnitoolBin, "del", netName, contNS.LongName()) env.runInNS(hostNS, cnitoolBin, "del", netName, contNS.LongName())
@ -154,14 +146,10 @@ var _ = Describe("Basic PTP using cnitool", func() {
chainedBridgeBandwidthEnv.runInNS(hostNS, cnitoolBin, "del", "network-chain-test", contNS1.LongName()) chainedBridgeBandwidthEnv.runInNS(hostNS, cnitoolBin, "del", "network-chain-test", contNS1.LongName())
basicBridgeEnv.runInNS(hostNS, cnitoolBin, "del", "network-chain-test", contNS2.LongName()) basicBridgeEnv.runInNS(hostNS, cnitoolBin, "del", "network-chain-test", contNS2.LongName())
contNS1.Del()
contNS2.Del()
hostNS.Del()
}) })
It("limits traffic only on the restricted bandwidth veth device", func() { Measure("limits traffic only on the restricted bandwith veth device", func(b Benchmarker) {
ipRegexp := regexp.MustCompile(`10\.1[12]\.2\.\d{1,3}`) ipRegexp := regexp.MustCompile("10\\.1[12]\\.2\\.\\d{1,3}")
By(fmt.Sprintf("adding %s to %s\n\n", "chained-bridge-bandwidth", contNS1.ShortName())) By(fmt.Sprintf("adding %s to %s\n\n", "chained-bridge-bandwidth", contNS1.ShortName()))
chainedBridgeBandwidthEnv.runInNS(hostNS, cnitoolBin, "add", "network-chain-test", contNS1.LongName()) chainedBridgeBandwidthEnv.runInNS(hostNS, cnitoolBin, "add", "network-chain-test", contNS1.LongName())
@ -174,30 +162,31 @@ var _ = Describe("Basic PTP using cnitool", func() {
Expect(basicBridgeIP).To(ContainSubstring("10.11.2.")) Expect(basicBridgeIP).To(ContainSubstring("10.11.2."))
var chainedBridgeBandwidthPort, basicBridgePort int var chainedBridgeBandwidthPort, basicBridgePort int
var err error
By(fmt.Sprintf("starting echo server in %s\n\n", contNS1.ShortName())) By(fmt.Sprintf("starting echo server in %s\n\n", contNS1.ShortName()))
chainedBridgeBandwidthPort, chainedBridgeBandwidthSession = startEchoServerInNamespace(contNS1) chainedBridgeBandwidthPort, chainedBridgeBandwidthSession, err = startEchoServerInNamespace(contNS1)
Expect(err).ToNot(HaveOccurred())
By(fmt.Sprintf("starting echo server in %s\n\n", contNS2.ShortName())) By(fmt.Sprintf("starting echo server in %s\n\n", contNS2.ShortName()))
basicBridgePort, basicBridgeSession = startEchoServerInNamespace(contNS2) basicBridgePort, basicBridgeSession, err = startEchoServerInNamespace(contNS2)
Expect(err).ToNot(HaveOccurred())
packetInBytes := 3000 packetInBytes := 20000 // The shaper needs to 'warm'. Send enough to cause it to throttle,
// balanced by run time.
By(fmt.Sprintf("sending tcp traffic to the chained, bridged, traffic shaped container on ip address '%s:%d'\n\n", chainedBridgeIP, chainedBridgeBandwidthPort)) By(fmt.Sprintf("sending tcp traffic to the chained, bridged, traffic shaped container on ip address '%s:%d'\n\n", chainedBridgeIP, chainedBridgeBandwidthPort))
start := time.Now() runtimeWithLimit := b.Time("with chained bridge and bandwidth plugins", func() {
makeTCPClientInNS(hostNS.ShortName(), chainedBridgeIP, chainedBridgeBandwidthPort, packetInBytes) 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)) By(fmt.Sprintf("sending tcp traffic to the basic bridged container on ip address '%s:%d'\n\n", basicBridgeIP, basicBridgePort))
start = time.Now() runtimeWithoutLimit := b.Time("with basic bridged plugin", func() {
makeTCPClientInNS(hostNS.ShortName(), basicBridgeIP, basicBridgePort, packetInBytes) makeTcpClientInNS(hostNS.ShortName(), basicBridgeIP, basicBridgePort, packetInBytes)
runtimeWithoutLimit := time.Since(start) })
log.Printf("Runtime without qos limit %.2f seconds", runtimeWithLimit.Seconds())
Expect(runtimeWithLimit).To(BeNumerically(">", runtimeWithoutLimit+1000*time.Millisecond)) Expect(runtimeWithLimit).To(BeNumerically(">", runtimeWithoutLimit+1000*time.Millisecond))
}) }, 1)
}) })
}) })
@ -235,7 +224,7 @@ func (n Namespace) Del() {
(TestEnv{}).run("ip", "netns", "del", string(n)) (TestEnv{}).run("ip", "netns", "del", string(n))
} }
func makeTCPClientInNS(netns string, address string, port int, numBytes int) { func makeTcpClientInNS(netns string, address string, port int, numBytes int) {
payload := bytes.Repeat([]byte{'a'}, numBytes) payload := bytes.Repeat([]byte{'a'}, numBytes)
message := string(payload) message := string(payload)
@ -254,7 +243,7 @@ func makeTCPClientInNS(netns string, address string, port int, numBytes int) {
Expect(string(out)).To(Equal(message)) Expect(string(out)).To(Equal(message))
} }
func startEchoServerInNamespace(netNS Namespace) (int, *gexec.Session) { func startEchoServerInNamespace(netNS Namespace) (int, *gexec.Session, error) {
session, err := startInNetNS(echoServerBinaryPath, netNS) session, err := startInNetNS(echoServerBinaryPath, netNS)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
@ -271,7 +260,7 @@ func startEchoServerInNamespace(netNS Namespace) (int, *gexec.Session) {
io.Copy(GinkgoWriter, io.MultiReader(session.Out, session.Err)) io.Copy(GinkgoWriter, io.MultiReader(session.Out, session.Err))
}() }()
return port, session return port, session, nil
} }
func startInNetNS(binPath string, namespace Namespace) (*gexec.Session, error) { func startInNetNS(binPath string, namespace Namespace) (*gexec.Session, error) {

View File

@ -4,7 +4,7 @@
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // http://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
@ -17,7 +17,7 @@ import (
"strings" "strings"
"testing" "testing"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec" "github.com/onsi/gomega/gexec"
) )

View File

@ -6,7 +6,6 @@
"mtu": 512, "mtu": 512,
"ipam": { "ipam": {
"type": "host-local", "type": "host-local",
"subnet": "10.1.2.0/24", "subnet": "10.1.2.0/24"
"dataDir": "/tmp/basic-ptp-test"
} }
} }

View File

@ -8,8 +8,7 @@
"mtu": 512, "mtu": 512,
"ipam": { "ipam": {
"type": "host-local", "type": "host-local",
"subnet": "10.9.2.0/24", "subnet": "10.9.2.0/24"
"dataDir": "/tmp/chained-ptp-bandwidth-test"
} }
}, },
{ {

View File

@ -39,7 +39,6 @@ type EndpointInfo struct {
NetworkId string NetworkId string
Gateway net.IP Gateway net.IP
IpAddress net.IP IpAddress net.IP
MacAddress string
} }
// GetSandboxContainerID returns the sandbox ID of this pod. // GetSandboxContainerID returns the sandbox ID of this pod.
@ -249,7 +248,6 @@ func GenerateHcnEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcn.HostComputeEndp
Minor: 0, Minor: 0,
}, },
Name: epInfo.EndpointName, Name: epInfo.EndpointName,
MacAddress: epInfo.MacAddress,
HostComputeNetwork: epInfo.NetworkId, HostComputeNetwork: epInfo.NetworkId,
Dns: hcn.Dns{ Dns: hcn.Dns{
Domain: epInfo.DNS.Domain, Domain: epInfo.DNS.Domain,
@ -282,16 +280,6 @@ func RemoveHcnEndpoint(epName string) error {
} }
return errors.Annotatef(err, "failed to find HostComputeEndpoint %s", epName) return errors.Annotatef(err, "failed to find HostComputeEndpoint %s", epName)
} }
epNamespace, err := hcn.GetNamespaceByID(hcnEndpoint.HostComputeNamespace)
if err != nil && !hcn.IsNotFoundError(err) {
return errors.Annotatef(err, "failed to get HostComputeNamespace %s", epName)
}
if epNamespace != nil {
err = hcn.RemoveNamespaceEndpoint(hcnEndpoint.HostComputeNamespace, hcnEndpoint.Id)
if err != nil && !hcn.IsNotFoundError(err) {
return errors.Annotatef(err,"error removing endpoint: %s from namespace", epName)
}
}
err = hcnEndpoint.Delete() err = hcnEndpoint.Delete()
if err != nil { if err != nil {

View File

@ -14,7 +14,7 @@
package hns package hns
import ( import (
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"testing" "testing"

View File

@ -18,7 +18,7 @@ import (
"net" "net"
"github.com/Microsoft/hcsshim/hcn" "github.com/Microsoft/hcsshim/hcn"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )

View File

@ -19,87 +19,43 @@ import (
"net" "net"
) )
// NextIP returns IP incremented by 1, if IP is invalid, return nil // NextIP returns IP incremented by 1
func NextIP(ip net.IP) net.IP { func NextIP(ip net.IP) net.IP {
normalizedIP := normalizeIP(ip) i := ipToInt(ip)
if normalizedIP == nil { return intToIP(i.Add(i, big.NewInt(1)))
return nil
}
i := ipToInt(normalizedIP)
return intToIP(i.Add(i, big.NewInt(1)), len(normalizedIP) == net.IPv6len)
} }
// PrevIP returns IP decremented by 1, if IP is invalid, return nil // PrevIP returns IP decremented by 1
func PrevIP(ip net.IP) net.IP { func PrevIP(ip net.IP) net.IP {
normalizedIP := normalizeIP(ip) i := ipToInt(ip)
if normalizedIP == nil { return intToIP(i.Sub(i, big.NewInt(1)))
return nil
}
i := ipToInt(normalizedIP)
return intToIP(i.Sub(i, big.NewInt(1)), len(normalizedIP) == net.IPv6len)
} }
// Cmp compares two IPs, returning the usual ordering: // Cmp compares two IPs, returning the usual ordering:
// a < b : -1 // a < b : -1
// a == b : 0 // a == b : 0
// a > b : 1 // a > b : 1
// incomparable : -2
func Cmp(a, b net.IP) int { func Cmp(a, b net.IP) int {
normalizedA := normalizeIP(a) aa := ipToInt(a)
normalizedB := normalizeIP(b) bb := ipToInt(b)
return aa.Cmp(bb)
if len(normalizedA) == len(normalizedB) && len(normalizedA) != 0 {
return ipToInt(normalizedA).Cmp(ipToInt(normalizedB))
}
return -2
} }
func ipToInt(ip net.IP) *big.Int { func ipToInt(ip net.IP) *big.Int {
return big.NewInt(0).SetBytes(ip) if v := ip.To4(); v != nil {
return big.NewInt(0).SetBytes(v)
}
return big.NewInt(0).SetBytes(ip.To16())
} }
func intToIP(i *big.Int, isIPv6 bool) net.IP { func intToIP(i *big.Int) net.IP {
intBytes := i.Bytes() return net.IP(i.Bytes())
if len(intBytes) == net.IPv4len || len(intBytes) == net.IPv6len {
return intBytes
}
if isIPv6 {
return append(make([]byte, net.IPv6len-len(intBytes)), intBytes...)
}
return append(make([]byte, net.IPv4len-len(intBytes)), intBytes...)
} }
// normalizeIP will normalize IP by family, // Network masks off the host portion of the IP
// IPv4 : 4-byte form
// IPv6 : 16-byte form
// others : nil
func normalizeIP(ip net.IP) net.IP {
if ipTo4 := ip.To4(); ipTo4 != nil {
return ipTo4
}
return ip.To16()
}
// Network masks off the host portion of the IP, if IPNet is invalid,
// return nil
func Network(ipn *net.IPNet) *net.IPNet { func Network(ipn *net.IPNet) *net.IPNet {
if ipn == nil {
return nil
}
maskedIP := ipn.IP.Mask(ipn.Mask)
if maskedIP == nil {
return nil
}
return &net.IPNet{ return &net.IPNet{
IP: maskedIP, IP: ipn.IP.Mask(ipn.Mask),
Mask: ipn.Mask, Mask: ipn.Mask,
} }
} }

View File

@ -1,247 +0,0 @@
// Copyright 2022 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ip
import (
"net"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("CIDR functions", func() {
It("NextIP", func() {
testCases := []struct {
ip net.IP
nextIP net.IP
}{
{
[]byte{192, 0, 2},
nil,
},
{
net.ParseIP("192.168.0.1"),
net.IPv4(192, 168, 0, 2).To4(),
},
{
net.ParseIP("192.168.0.255"),
net.IPv4(192, 168, 1, 0).To4(),
},
{
net.ParseIP("0.1.0.5"),
net.IPv4(0, 1, 0, 6).To4(),
},
{
net.ParseIP("AB12::123"),
net.ParseIP("AB12::124"),
},
{
net.ParseIP("AB12::FFFF"),
net.ParseIP("AB12::1:0"),
},
{
net.ParseIP("0::123"),
net.ParseIP("0::124"),
},
}
for _, test := range testCases {
ip := NextIP(test.ip)
Expect(ip).To(Equal(test.nextIP))
}
})
It("PrevIP", func() {
testCases := []struct {
ip net.IP
prevIP net.IP
}{
{
[]byte{192, 0, 2},
nil,
},
{
net.ParseIP("192.168.0.2"),
net.IPv4(192, 168, 0, 1).To4(),
},
{
net.ParseIP("192.168.1.0"),
net.IPv4(192, 168, 0, 255).To4(),
},
{
net.ParseIP("0.1.0.5"),
net.IPv4(0, 1, 0, 4).To4(),
},
{
net.ParseIP("AB12::123"),
net.ParseIP("AB12::122"),
},
{
net.ParseIP("AB12::1:0"),
net.ParseIP("AB12::FFFF"),
},
{
net.ParseIP("0::124"),
net.ParseIP("0::123"),
},
}
for _, test := range testCases {
ip := PrevIP(test.ip)
Expect(ip).To(Equal(test.prevIP))
}
})
It("Cmp", func() {
testCases := []struct {
a net.IP
b net.IP
result int
}{
{
net.ParseIP("192.168.0.2"),
nil,
-2,
},
{
net.ParseIP("192.168.0.2"),
[]byte{192, 168, 5},
-2,
},
{
net.ParseIP("192.168.0.2"),
net.ParseIP("AB12::123"),
-2,
},
{
net.ParseIP("192.168.0.2"),
net.ParseIP("192.168.0.5"),
-1,
},
{
net.ParseIP("192.168.0.2"),
net.ParseIP("192.168.0.5").To4(),
-1,
},
{
net.ParseIP("192.168.0.10"),
net.ParseIP("192.168.0.5"),
1,
},
{
net.ParseIP("192.168.0.10"),
net.ParseIP("192.168.0.10"),
0,
},
{
net.ParseIP("192.168.0.10"),
net.ParseIP("192.168.0.10").To4(),
0,
},
{
net.ParseIP("AB12::122"),
net.ParseIP("AB12::123"),
-1,
},
{
net.ParseIP("AB12::210"),
net.ParseIP("AB12::123"),
1,
},
{
net.ParseIP("AB12::210"),
net.ParseIP("AB12::210"),
0,
},
}
for _, test := range testCases {
result := Cmp(test.a, test.b)
Expect(result).To(Equal(test.result))
}
})
It("Network", func() {
testCases := []struct {
ipNet *net.IPNet
result *net.IPNet
}{
{
nil,
nil,
},
{
&net.IPNet{
IP: nil,
Mask: net.IPv4Mask(255, 255, 255, 0),
},
nil,
},
{
&net.IPNet{
IP: net.IPv4(192, 168, 0, 1),
Mask: nil,
},
nil,
},
{
&net.IPNet{
IP: net.ParseIP("AB12::123"),
Mask: net.IPv4Mask(255, 255, 255, 0),
},
nil,
},
{
&net.IPNet{
IP: net.IPv4(192, 168, 0, 100).To4(),
Mask: net.CIDRMask(120, 128),
},
&net.IPNet{
IP: net.IPv4(192, 168, 0, 0).To4(),
Mask: net.CIDRMask(120, 128),
},
},
{
&net.IPNet{
IP: net.IPv4(192, 168, 0, 100),
Mask: net.CIDRMask(24, 32),
},
&net.IPNet{
IP: net.IPv4(192, 168, 0, 0).To4(),
Mask: net.CIDRMask(24, 32),
},
},
{
&net.IPNet{
IP: net.ParseIP("AB12::123"),
Mask: net.CIDRMask(120, 128),
},
&net.IPNet{
IP: net.ParseIP("AB12::100"),
Mask: net.CIDRMask(120, 128),
},
},
}
for _, test := range testCases {
result := Network(test.ipNet)
Expect(result).To(Equal(test.result))
}
})
})

View File

@ -47,12 +47,13 @@ func ParseIP(s string) *IP {
return nil return nil
} }
return newIP(ip, ipNet.Mask) return newIP(ip, ipNet.Mask)
} else {
ip := net.ParseIP(s)
if ip == nil {
return nil
}
return newIP(ip, nil)
} }
ip := net.ParseIP(s)
if ip == nil {
return nil
}
return newIP(ip, nil)
} }
// ToIP will return a net.IP in standard form from this IP. // ToIP will return a net.IP in standard form from this IP.

View File

@ -15,10 +15,10 @@
package ip_test package ip_test
import ( import (
"testing" . "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"testing"
) )
func TestIp(t *testing.T) { func TestIp(t *testing.T) {

View File

@ -19,7 +19,7 @@ import (
"fmt" "fmt"
"net" "net"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )
@ -124,7 +124,7 @@ var _ = Describe("IP Operations", func() {
} }
for _, test := range testCases { for _, test := range testCases {
Expect(test.ip.ToIP()).To(HaveLen(test.expectedLen)) Expect(len(test.ip.ToIP())).To(Equal(test.expectedLen))
Expect(test.ip.ToIP()).To(Equal(test.expectedIP)) Expect(test.ip.ToIP()).To(Equal(test.expectedIP))
} }
}) })
@ -174,8 +174,8 @@ var _ = Describe("IP Operations", func() {
} }
}) })
Context("Decode", func() { It("Decode", func() {
It("valid IP", func() { Context("valid IP", func() {
testCases := []struct { testCases := []struct {
text string text string
expected *IP expected *IP
@ -205,9 +205,10 @@ var _ = Describe("IP Operations", func() {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(ip).To(Equal(test.expected)) Expect(ip).To(Equal(test.expected))
} }
}) })
It("empty text", func() { Context("empty text", func() {
ip := &IP{} ip := &IP{}
err := json.Unmarshal([]byte(`""`), ip) err := json.Unmarshal([]byte(`""`), ip)
@ -215,7 +216,7 @@ var _ = Describe("IP Operations", func() {
Expect(ip).To(Equal(newIP(nil, nil))) Expect(ip).To(Equal(newIP(nil, nil)))
}) })
It("invalid IP", func() { Context("invalid IP", func() {
testCases := []struct { testCases := []struct {
text string text string
expectedErr error expectedErr error
@ -242,7 +243,7 @@ var _ = Describe("IP Operations", func() {
} }
}) })
It("IP slice", func() { Context("IP slice", func() {
testCases := []struct { testCases := []struct {
text string text string
expected []*IP expected []*IP

View File

@ -16,7 +16,7 @@ package ip
import ( import (
"bytes" "bytes"
"os" "io/ioutil"
current "github.com/containernetworking/cni/pkg/types/100" current "github.com/containernetworking/cni/pkg/types/100"
) )
@ -53,10 +53,10 @@ func EnableForward(ips []*current.IPConfig) error {
} }
func echo1(f string) error { func echo1(f string) error {
if content, err := os.ReadFile(f); err == nil { if content, err := ioutil.ReadFile(f); err == nil {
if bytes.Equal(bytes.TrimSpace(content), []byte("1")) { if bytes.Equal(bytes.TrimSpace(content), []byte("1")) {
return nil return nil
} }
} }
return os.WriteFile(f, []byte("1"), 0o644) return ioutil.WriteFile(f, []byte("1"), 0644)
} }

View File

@ -1,16 +1,17 @@
package ip package ip
import ( import (
"io/ioutil"
"os" "os"
"time" "time"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )
var _ = Describe("IpforwardLinux", func() { var _ = Describe("IpforwardLinux", func() {
It("echo1 must not write the file if content is 1", func() { It("echo1 must not write the file if content is 1", func() {
file, err := os.CreateTemp("", "containernetworking") file, err := ioutil.TempFile(os.TempDir(), "containernetworking")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
defer os.Remove(file.Name()) defer os.Remove(file.Name())
err = echo1(file.Name()) err = echo1(file.Name())

View File

@ -104,6 +104,7 @@ func TeardownIPMasq(ipn *net.IPNet, chain string, comment string) error {
err = ipt.ClearChain("nat", chain) err = ipt.ClearChain("nat", chain)
if err != nil && !isNotExist(err) { if err != nil && !isNotExist(err) {
return err return err
} }
err = ipt.DeleteChain("nat", chain) err = ipt.DeleteChain("nat", chain)

View File

@ -28,7 +28,9 @@ import (
"github.com/containernetworking/plugins/pkg/utils/sysctl" "github.com/containernetworking/plugins/pkg/utils/sysctl"
) )
var ErrLinkNotFound = errors.New("link not found") var (
ErrLinkNotFound = errors.New("link not found")
)
// makeVethPair is called from within the container's network namespace // 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) { func makeVethPair(name, peer string, mtu int, mac string, hostNS ns.NetNS) (netlink.Link, error) {
@ -67,37 +69,38 @@ func peerExists(name string) bool {
return true return true
} }
func makeVeth(name, vethPeerName string, mtu int, mac string, hostNS ns.NetNS) (string, netlink.Link, error) { func makeVeth(name, vethPeerName string, mtu int, mac string, hostNS ns.NetNS) (peerName string, veth netlink.Link, err error) {
var peerName string
var veth netlink.Link
var err error
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
if vethPeerName != "" { if vethPeerName != "" {
peerName = vethPeerName peerName = vethPeerName
} else { } else {
peerName, err = RandomVethName() peerName, err = RandomVethName()
if err != nil { if err != nil {
return peerName, nil, err return
} }
} }
veth, err = makeVethPair(name, peerName, mtu, mac, hostNS) veth, err = makeVethPair(name, peerName, mtu, mac, hostNS)
switch { switch {
case err == nil: case err == nil:
return peerName, veth, nil return
case os.IsExist(err): case os.IsExist(err):
if peerExists(peerName) && vethPeerName == "" { if peerExists(peerName) && vethPeerName == "" {
continue continue
} }
return peerName, veth, fmt.Errorf("container veth name (%q) peer provided (%q) already exists", name, peerName) err = fmt.Errorf("container veth name provided (%v) already exists", name)
return
default: default:
return peerName, veth, fmt.Errorf("failed to make veth pair: %v", err) err = fmt.Errorf("failed to make veth pair: %v", err)
return
} }
} }
// should really never be hit // should really never be hit
return peerName, nil, fmt.Errorf("failed to find a unique veth name") err = fmt.Errorf("failed to find a unique veth name")
return
} }
// RandomVethName returns string "veth" with random prefix (hashed from entropy) // RandomVethName returns string "veth" with random prefix (hashed from entropy)

View File

@ -20,15 +20,22 @@ import (
"fmt" "fmt"
"net" "net"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/vishvananda/netlink"
"github.com/containernetworking/plugins/pkg/ip" "github.com/containernetworking/plugins/pkg/ip"
"github.com/containernetworking/plugins/pkg/ns" "github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils" "github.com/containernetworking/plugins/pkg/testutils"
"github.com/vishvananda/netlink"
) )
func getHwAddr(linkname string) string {
veth, err := netlink.LinkByName(linkname)
Expect(err).NotTo(HaveOccurred())
return fmt.Sprintf("%s", veth.Attrs().HardwareAddr)
}
var _ = Describe("Link", func() { var _ = Describe("Link", func() {
const ( const (
ifaceFormatString string = "i%d" ifaceFormatString string = "i%d"
@ -57,7 +64,7 @@ var _ = Describe("Link", func() {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
fakeBytes := make([]byte, 20) fakeBytes := make([]byte, 20)
// to be reset in AfterEach block //to be reset in AfterEach block
rand.Reader = bytes.NewReader(fakeBytes) rand.Reader = bytes.NewReader(fakeBytes)
_ = containerNetNS.Do(func(ns.NetNS) error { _ = containerNetNS.Do(func(ns.NetNS) error {
@ -149,9 +156,9 @@ var _ = Describe("Link", func() {
It("returns useful error", func() { It("returns useful error", func() {
_ = containerNetNS.Do(func(ns.NetNS) error { _ = containerNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover() defer GinkgoRecover()
testHostVethName := "test" + hostVethName
_, _, err := ip.SetupVethWithName(containerVethName, testHostVethName, mtu, "", hostNetNS) _, _, err := ip.SetupVeth(containerVethName, mtu, "", hostNetNS)
Expect(err.Error()).To(Equal(fmt.Sprintf("container veth name (%q) peer provided (%q) already exists", containerVethName, testHostVethName))) Expect(err.Error()).To(Equal(fmt.Sprintf("container veth name provided (%s) already exists", containerVethName)))
return nil return nil
}) })
@ -174,14 +181,15 @@ var _ = Describe("Link", func() {
Context("when there is no name available for the host-side", func() { Context("when there is no name available for the host-side", func() {
BeforeEach(func() { BeforeEach(func() {
// adding different interface to container ns //adding different interface to container ns
containerVethName += "0" containerVethName += "0"
}) })
It("returns useful error", func() { It("returns useful error", func() {
_ = containerNetNS.Do(func(ns.NetNS) error { _ = containerNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover() defer GinkgoRecover()
_, _, err := ip.SetupVethWithName(containerVethName, hostVethName, mtu, "", hostNetNS) _, _, err := ip.SetupVeth(containerVethName, mtu, "", hostNetNS)
Expect(err.Error()).To(Equal(fmt.Sprintf("container veth name (%q) peer provided (%q) already exists", containerVethName, hostVethName))) Expect(err.Error()).To(HavePrefix("container veth name provided"))
Expect(err.Error()).To(HaveSuffix("already exists"))
return nil return nil
}) })
}) })
@ -189,7 +197,7 @@ var _ = Describe("Link", func() {
Context("when there is no name conflict for the host or container interfaces", func() { Context("when there is no name conflict for the host or container interfaces", func() {
BeforeEach(func() { BeforeEach(func() {
// adding different interface to container and host ns //adding different interface to container and host ns
containerVethName += "0" containerVethName += "0"
rand.Reader = originalRandReader rand.Reader = originalRandReader
}) })
@ -203,7 +211,7 @@ var _ = Describe("Link", func() {
return nil return nil
}) })
// verify veths are in different namespaces //verify veths are in different namespaces
_ = containerNetNS.Do(func(ns.NetNS) error { _ = containerNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover() defer GinkgoRecover()
@ -282,7 +290,7 @@ var _ = Describe("Link", func() {
// this will delete the host endpoint too // this will delete the host endpoint too
addr, err := ip.DelLinkByNameAddr(containerVethName) addr, err := ip.DelLinkByNameAddr(containerVethName)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(addr).To(BeEmpty()) Expect(addr).To(HaveLen(0))
return nil return nil
}) })
}) })

View File

@ -42,11 +42,6 @@ func AddHostRoute(ipn *net.IPNet, gw net.IP, dev netlink.Link) error {
// AddDefaultRoute sets the default route on the given gateway. // AddDefaultRoute sets the default route on the given gateway.
func AddDefaultRoute(gw net.IP, dev netlink.Link) error { func AddDefaultRoute(gw net.IP, dev netlink.Link) error {
var defNet *net.IPNet _, defNet, _ := net.ParseCIDR("0.0.0.0/0")
if gw.To4() != nil {
_, defNet, _ = net.ParseCIDR("0.0.0.0/0")
} else {
_, defNet, _ = net.ParseCIDR("::/0")
}
return AddRoute(defNet, gw, dev) return AddRoute(defNet, gw, dev)
} }

View File

@ -21,13 +21,13 @@ import (
"fmt" "fmt"
"net" "net"
"github.com/vishvananda/netlink"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
current "github.com/containernetworking/cni/pkg/types/100" current "github.com/containernetworking/cni/pkg/types/100"
"github.com/vishvananda/netlink"
) )
func ValidateExpectedInterfaceIPs(ifName string, resultIPs []*current.IPConfig) error { func ValidateExpectedInterfaceIPs(ifName string, resultIPs []*current.IPConfig) error {
// Ensure ips // Ensure ips
for _, ips := range resultIPs { for _, ips := range resultIPs {
ourAddr := netlink.Addr{IPNet: &ips.Address} ourAddr := netlink.Addr{IPNet: &ips.Address}
@ -49,15 +49,12 @@ func ValidateExpectedInterfaceIPs(ifName string, resultIPs []*current.IPConfig)
break break
} }
} }
if !match { if match == false {
return fmt.Errorf("Failed to match addr %v on interface %v", ourAddr, ifName) return fmt.Errorf("Failed to match addr %v on interface %v", ourAddr, ifName)
} }
// Convert the host/prefixlen to just prefix for route lookup. // Convert the host/prefixlen to just prefix for route lookup.
_, ourPrefix, err := net.ParseCIDR(ourAddr.String()) _, ourPrefix, err := net.ParseCIDR(ourAddr.String())
if err != nil {
return err
}
findGwy := &netlink.Route{Dst: ourPrefix} findGwy := &netlink.Route{Dst: ourPrefix}
routeFilter := netlink.RT_FILTER_DST routeFilter := netlink.RT_FILTER_DST
@ -80,13 +77,11 @@ func ValidateExpectedInterfaceIPs(ifName string, resultIPs []*current.IPConfig)
} }
func ValidateExpectedRoute(resultRoutes []*types.Route) error { func ValidateExpectedRoute(resultRoutes []*types.Route) error {
// Ensure that each static route in prevResults is found in the routing table // Ensure that each static route in prevResults is found in the routing table
for _, route := range resultRoutes { for _, route := range resultRoutes {
find := &netlink.Route{Dst: &route.Dst, Gw: route.GW} find := &netlink.Route{Dst: &route.Dst, Gw: route.GW}
routeFilter := netlink.RT_FILTER_DST routeFilter := netlink.RT_FILTER_DST | netlink.RT_FILTER_GW
if route.GW != nil {
routeFilter |= netlink.RT_FILTER_GW
}
var family int var family int
switch { switch {

View File

@ -16,7 +16,6 @@ package ipam
import ( import (
"context" "context"
"github.com/containernetworking/cni/pkg/invoke" "github.com/containernetworking/cni/pkg/invoke"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
) )

View File

@ -19,11 +19,11 @@ import (
"net" "net"
"os" "os"
"github.com/vishvananda/netlink"
current "github.com/containernetworking/cni/pkg/types/100" current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/plugins/pkg/ip" "github.com/containernetworking/plugins/pkg/ip"
"github.com/containernetworking/plugins/pkg/utils/sysctl" "github.com/containernetworking/plugins/pkg/utils/sysctl"
"github.com/vishvananda/netlink"
) )
const ( const (
@ -44,7 +44,7 @@ func ConfigureIface(ifName string, res *current.Result) error {
} }
var v4gw, v6gw net.IP var v4gw, v6gw net.IP
hasEnabledIpv6 := false var has_enabled_ipv6 bool = false
for _, ipc := range res.IPs { for _, ipc := range res.IPs {
if ipc.Interface == nil { if ipc.Interface == nil {
continue continue
@ -57,7 +57,7 @@ func ConfigureIface(ifName string, res *current.Result) error {
// Make sure sysctl "disable_ipv6" is 0 if we are about to add // Make sure sysctl "disable_ipv6" is 0 if we are about to add
// an IPv6 address to the interface // an IPv6 address to the interface
if !hasEnabledIpv6 && ipc.Address.IP.To4() == nil { if !has_enabled_ipv6 && ipc.Address.IP.To4() == nil {
// Enabled IPv6 for loopback "lo" and the interface // Enabled IPv6 for loopback "lo" and the interface
// being configured // being configured
for _, iface := range [2]string{"lo", ifName} { for _, iface := range [2]string{"lo", ifName} {
@ -79,7 +79,7 @@ func ConfigureIface(ifName string, res *current.Result) error {
return fmt.Errorf("failed to enable IPv6 for interface %q (%s=%s): %v", iface, ipv6SysctlValueName, value, err) return fmt.Errorf("failed to enable IPv6 for interface %q (%s=%s): %v", iface, ipv6SysctlValueName, value, err)
} }
} }
hasEnabledIpv6 = true has_enabled_ipv6 = true
} }
addr := &netlink.Addr{IPNet: &ipc.Address, Label: ""} addr := &netlink.Addr{IPNet: &ipc.Address, Label: ""}

View File

@ -18,14 +18,15 @@ import (
"net" "net"
"syscall" "syscall"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/vishvananda/netlink"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
current "github.com/containernetworking/cni/pkg/types/100" current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/plugins/pkg/ns" "github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils" "github.com/containernetworking/plugins/pkg/testutils"
"github.com/vishvananda/netlink"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
) )
const LINK_NAME = "eth0" const LINK_NAME = "eth0"
@ -142,12 +143,12 @@ var _ = Describe("ConfigureIface", func() {
v4addrs, err := netlink.AddrList(link, syscall.AF_INET) v4addrs, err := netlink.AddrList(link, syscall.AF_INET)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(v4addrs).To(HaveLen(1)) Expect(len(v4addrs)).To(Equal(1))
Expect(ipNetEqual(v4addrs[0].IPNet, ipv4)).To(BeTrue()) Expect(ipNetEqual(v4addrs[0].IPNet, ipv4)).To(Equal(true))
v6addrs, err := netlink.AddrList(link, syscall.AF_INET6) v6addrs, err := netlink.AddrList(link, syscall.AF_INET6)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(v6addrs).To(HaveLen(2)) Expect(len(v6addrs)).To(Equal(2))
var found bool var found bool
for _, a := range v6addrs { for _, a := range v6addrs {
@ -156,7 +157,7 @@ var _ = Describe("ConfigureIface", func() {
break break
} }
} }
Expect(found).To(BeTrue()) Expect(found).To(Equal(true))
// Ensure the v4 route, v6 route, and subnet route // Ensure the v4 route, v6 route, and subnet route
routes, err := netlink.RouteList(link, 0) routes, err := netlink.RouteList(link, 0)
@ -176,8 +177,8 @@ var _ = Describe("ConfigureIface", func() {
break break
} }
} }
Expect(v4found).To(BeTrue()) Expect(v4found).To(Equal(true))
Expect(v6found).To(BeTrue()) Expect(v6found).To(Equal(true))
return nil return nil
}) })
@ -215,8 +216,8 @@ var _ = Describe("ConfigureIface", func() {
break break
} }
} }
Expect(v4found).To(BeTrue()) Expect(v4found).To(Equal(true))
Expect(v6found).To(BeTrue()) Expect(v6found).To(Equal(true))
return nil return nil
}) })

View File

@ -15,10 +15,10 @@
package ipam_test package ipam_test
import ( import (
"testing" . "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"testing"
) )
func TestIpam(t *testing.T) { func TestIpam(t *testing.T) {

View File

@ -17,7 +17,7 @@ package link_test
import ( import (
"testing" "testing"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )

View File

@ -15,10 +15,8 @@
package link package link
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"time"
"github.com/networkplumbing/go-nft/nft" "github.com/networkplumbing/go-nft/nft"
"github.com/networkplumbing/go-nft/nft/schema" "github.com/networkplumbing/go-nft/nft/schema"
@ -30,8 +28,8 @@ const (
) )
type NftConfigurer interface { type NftConfigurer interface {
Apply(*nft.Config) (*nft.Config, error) Apply(*nft.Config) error
Read(filterCommands ...string) (*nft.Config, error) Read() (*nft.Config, error)
} }
type SpoofChecker struct { type SpoofChecker struct {
@ -39,23 +37,16 @@ type SpoofChecker struct {
macAddress string macAddress string
refID string refID string
configurer NftConfigurer configurer NftConfigurer
rulestore *nft.Config
} }
type defaultNftConfigurer struct{} type defaultNftConfigurer struct{}
func (dnc defaultNftConfigurer) Apply(cfg *nft.Config) (*nft.Config, error) { func (_ defaultNftConfigurer) Apply(cfg *nft.Config) error {
const timeout = 55 * time.Second return nft.ApplyConfig(cfg)
ctxWithTimeout, cancelFunc := context.WithTimeout(context.Background(), timeout)
defer cancelFunc()
return nft.ApplyConfigEcho(ctxWithTimeout, cfg)
} }
func (dnc defaultNftConfigurer) Read(filterCommands ...string) (*nft.Config, error) { func (_ defaultNftConfigurer) Read() (*nft.Config, error) {
const timeout = 55 * time.Second return nft.ReadConfig()
ctxWithTimeout, cancelFunc := context.WithTimeout(context.Background(), timeout)
defer cancelFunc()
return nft.ReadConfigContext(ctxWithTimeout, filterCommands...)
} }
func NewSpoofChecker(iface, macAddress, refID string) *SpoofChecker { func NewSpoofChecker(iface, macAddress, refID string) *SpoofChecker {
@ -63,7 +54,7 @@ func NewSpoofChecker(iface, macAddress, refID string) *SpoofChecker {
} }
func NewSpoofCheckerWithConfigurer(iface, macAddress, refID string, configurer NftConfigurer) *SpoofChecker { func NewSpoofCheckerWithConfigurer(iface, macAddress, refID string, configurer NftConfigurer) *SpoofChecker {
return &SpoofChecker{iface, macAddress, refID, configurer, nil} return &SpoofChecker{iface, macAddress, refID, configurer}
} }
// Setup applies nftables configuration to restrict traffic // Setup applies nftables configuration to restrict traffic
@ -92,7 +83,7 @@ func (sc *SpoofChecker) Setup() error {
macChain := sc.macChain(ifaceChain.Name) macChain := sc.macChain(ifaceChain.Name)
baseConfig.AddChain(macChain) 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) return fmt.Errorf("failed to setup spoof-check: %v", err)
} }
@ -106,51 +97,37 @@ func (sc *SpoofChecker) Setup() error {
rulesConfig.AddRule(sc.matchMacRule(macChain.Name)) rulesConfig.AddRule(sc.matchMacRule(macChain.Name))
rulesConfig.AddRule(sc.dropRule(macChain.Name)) rulesConfig.AddRule(sc.dropRule(macChain.Name))
rulestore, err := sc.configurer.Apply(rulesConfig) if err := sc.configurer.Apply(rulesConfig); err != nil {
if err != nil {
return fmt.Errorf("failed to setup spoof-check: %v", err) return fmt.Errorf("failed to setup spoof-check: %v", err)
} }
sc.rulestore = rulestore
return nil 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. // 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 // The table and base-chain are expected to survive while the base-chain rule that matches the
// interface is removed. // interface is removed.
func (sc *SpoofChecker) Teardown() error { func (sc *SpoofChecker) Teardown() error {
ifaceChain := sc.ifaceChain() ifaceChain := sc.ifaceChain()
expectedRuleToFind := sc.matchIfaceJumpToChainRule(preRoutingBaseChainName, ifaceChain.Name) currentConfig, ifaceMatchRuleErr := sc.configurer.Read()
// It is safer to exclude the statement matching, avoiding cases where a current statement includes if ifaceMatchRuleErr == nil {
// additional default entries (e.g. counters). expectedRuleToFind := sc.matchIfaceJumpToChainRule(preRoutingBaseChainName, ifaceChain.Name)
ruleToFindExcludingStatements := *expectedRuleToFind // It is safer to exclude the statement matching, avoiding cases where a current statement includes
ruleToFindExcludingStatements.Expr = nil // additional default entries (e.g. counters).
ruleToFindExcludingStatements := *expectedRuleToFind
rules, ifaceMatchRuleErr := sc.findPreroutingRule(&ruleToFindExcludingStatements) ruleToFindExcludingStatements.Expr = nil
if ifaceMatchRuleErr == nil && len(rules) > 0 { rules := currentConfig.LookupRule(&ruleToFindExcludingStatements)
c := nft.NewConfig() if len(rules) > 0 {
for _, rule := range rules { c := nft.NewConfig()
c.DeleteRule(rule) 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)
} }
if _, err := sc.configurer.Apply(c); err != nil {
ifaceMatchRuleErr = fmt.Errorf("failed to delete iface match rule: %v", err)
}
// Drop the cache, it should contain deleted rule(s) now
sc.rulestore = nil
} else {
fmt.Fprintf(os.Stderr, "spoofcheck/teardown: unable to detect iface match rule for deletion: %+v", expectedRuleToFind)
} }
regularChainsConfig := nft.NewConfig() regularChainsConfig := nft.NewConfig()
@ -158,7 +135,7 @@ func (sc *SpoofChecker) Teardown() error {
regularChainsConfig.DeleteChain(sc.macChain(ifaceChain.Name)) regularChainsConfig.DeleteChain(sc.macChain(ifaceChain.Name))
var regularChainsErr error 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) regularChainsErr = fmt.Errorf("failed to delete regular chains: %v", err)
} }
@ -218,10 +195,12 @@ func (sc *SpoofChecker) matchMacRule(chain string) *schema.Rule {
} }
func (sc *SpoofChecker) dropRule(chain string) *schema.Rule { func (sc *SpoofChecker) dropRule(chain string) *schema.Rule {
macRulesIndex := nft.NewRuleIndex()
return &schema.Rule{ return &schema.Rule{
Family: schema.FamilyBridge, Family: schema.FamilyBridge,
Table: natTableName, Table: natTableName,
Chain: chain, Chain: chain,
Index: macRulesIndex.Next(),
Expr: []schema.Statement{ Expr: []schema.Statement{
{Verdict: schema.Verdict{SimpleVerdict: schema.SimpleVerdict{Drop: true}}}, {Verdict: schema.Verdict{SimpleVerdict: schema.SimpleVerdict{Drop: true}}},
}, },
@ -229,7 +208,7 @@ func (sc *SpoofChecker) dropRule(chain string) *schema.Rule {
} }
} }
func (sc *SpoofChecker) baseChain() *schema.Chain { func (_ *SpoofChecker) baseChain() *schema.Chain {
chainPriority := -300 chainPriority := -300
return &schema.Chain{ return &schema.Chain{
Family: schema.FamilyBridge, Family: schema.FamilyBridge,
@ -251,7 +230,7 @@ func (sc *SpoofChecker) ifaceChain() *schema.Chain {
} }
} }
func (sc *SpoofChecker) macChain(ifaceChainName string) *schema.Chain { func (_ *SpoofChecker) macChain(ifaceChainName string) *schema.Chain {
macChainName := ifaceChainName + "-mac" macChainName := ifaceChainName + "-mac"
return &schema.Chain{ return &schema.Chain{
Family: schema.FamilyBridge, Family: schema.FamilyBridge,
@ -264,7 +243,3 @@ func ruleComment(id string) string {
const refIDPrefix = "macspoofchk-" const refIDPrefix = "macspoofchk-"
return refIDPrefix + id return refIDPrefix + id
} }
func listChainBridgeNatPrerouting() []string {
return []string{"chain", "bridge", natTableName, preRoutingBaseChainName}
}

View File

@ -16,9 +16,9 @@ package link_test
import ( import (
"fmt" "fmt"
"github.com/networkplumbing/go-nft/nft" "github.com/networkplumbing/go-nft/nft"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/containernetworking/plugins/pkg/link" "github.com/containernetworking/plugins/pkg/link"
@ -113,32 +113,13 @@ 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) { func assertExpectedRegularChainsDeletionInTeardownConfig(action configurerStub) {
deleteRegularChainRulesJSONConfig, err := action.applyConfig[1].ToJSON() deleteRegularChainRulesJsonConfig, err := action.applyConfig[1].ToJSON()
ExpectWithOffset(1, err).NotTo(HaveOccurred()) ExpectWithOffset(1, err).NotTo(HaveOccurred())
expectedDeleteRegularChainRulesJSONConfig := ` expectedDeleteRegularChainRulesJsonConfig := `
{"nftables": [ {"nftables": [
{"delete": {"chain": { {"delete": {"chain": {
"family": "bridge", "family": "bridge",
@ -152,14 +133,14 @@ func assertExpectedRegularChainsDeletionInTeardownConfig(action configurerStub)
}}} }}}
]}` ]}`
ExpectWithOffset(1, string(deleteRegularChainRulesJSONConfig)).To(MatchJSON(expectedDeleteRegularChainRulesJSONConfig)) ExpectWithOffset(1, string(deleteRegularChainRulesJsonConfig)).To(MatchJSON(expectedDeleteRegularChainRulesJsonConfig))
} }
func assertExpectedBaseChainRuleDeletionInTeardownConfig(action configurerStub) { func assertExpectedBaseChainRuleDeletionInTeardownConfig(action configurerStub) {
deleteBaseChainRuleJSONConfig, err := action.applyConfig[0].ToJSON() deleteBaseChainRuleJsonConfig, err := action.applyConfig[0].ToJSON()
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
expectedDeleteIfaceMatchRuleJSONConfig := ` expectedDeleteIfaceMatchRuleJsonConfig := `
{"nftables": [ {"nftables": [
{"delete": {"rule": { {"delete": {"rule": {
"family": "bridge", "family": "bridge",
@ -176,7 +157,7 @@ func assertExpectedBaseChainRuleDeletionInTeardownConfig(action configurerStub)
"comment": "macspoofchk-container99-net1" "comment": "macspoofchk-container99-net1"
}}} }}}
]}` ]}`
Expect(string(deleteBaseChainRuleJSONConfig)).To(MatchJSON(expectedDeleteIfaceMatchRuleJSONConfig)) Expect(string(deleteBaseChainRuleJsonConfig)).To(MatchJSON(expectedDeleteIfaceMatchRuleJsonConfig))
} }
func rowConfigWithRulesOnly() string { func rowConfigWithRulesOnly() string {
@ -273,6 +254,7 @@ func assertExpectedRulesInSetupConfig(c configurerStub) {
"comment":"macspoofchk-container99-net1"}}, "comment":"macspoofchk-container99-net1"}},
{"rule":{"family":"bridge","table":"nat","chain":"cni-br-iface-container99-net1-mac", {"rule":{"family":"bridge","table":"nat","chain":"cni-br-iface-container99-net1-mac",
"expr":[{"drop":null}], "expr":[{"drop":null}],
"index":0,
"comment":"macspoofchk-container99-net1"}} "comment":"macspoofchk-container99-net1"}}
]}` ]}`
ExpectWithOffset(1, string(jsonConfig)).To(MatchJSON(expectedConfig)) ExpectWithOffset(1, string(jsonConfig)).To(MatchJSON(expectedConfig))
@ -293,28 +275,21 @@ type configurerStub struct {
failFirstApplyConfig bool failFirstApplyConfig bool
failSecondApplyConfig bool failSecondApplyConfig bool
failReadConfig bool failReadConfig bool
applyReturnNil bool
readCalled bool
} }
func (a *configurerStub) Apply(c *nft.Config) (*nft.Config, error) { func (a *configurerStub) Apply(c *nft.Config) error {
a.applyCounter++ a.applyCounter++
if a.failFirstApplyConfig && a.applyCounter == 1 { if a.failFirstApplyConfig && a.applyCounter == 1 {
return nil, fmt.Errorf(errorFirstApplyText) return fmt.Errorf(errorFirstApplyText)
} }
if a.failSecondApplyConfig && a.applyCounter == 2 { if a.failSecondApplyConfig && a.applyCounter == 2 {
return nil, fmt.Errorf(errorSecondApplyText) return fmt.Errorf(errorSecondApplyText)
} }
a.applyConfig = append(a.applyConfig, c) a.applyConfig = append(a.applyConfig, c)
if a.applyReturnNil { return nil
return nil, nil
}
return c, nil
} }
func (a *configurerStub) Read(_ ...string) (*nft.Config, error) { func (a *configurerStub) Read() (*nft.Config, error) {
a.readCalled = true
if a.failReadConfig { if a.failReadConfig {
return nil, fmt.Errorf(errorReadText) return nil, fmt.Errorf(errorReadText)
} }

View File

@ -17,16 +17,16 @@ package ns_test
import ( import (
"errors" "errors"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"sync" "sync"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"golang.org/x/sys/unix"
"github.com/containernetworking/plugins/pkg/ns" "github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils" "github.com/containernetworking/plugins/pkg/testutils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"golang.org/x/sys/unix"
) )
func getInodeCurNetNS() (uint64, error) { func getInodeCurNetNS() (uint64, error) {
@ -182,7 +182,7 @@ var _ = Describe("Linux namespace operations", func() {
testNsInode, err := getInodeNS(targetNetNS) testNsInode, err := getInodeNS(targetNetNS)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(testNsInode).NotTo(Equal(uint64(0))) Expect(testNsInode).NotTo(Equal(0))
Expect(testNsInode).NotTo(Equal(origNSInode)) Expect(testNsInode).NotTo(Equal(origNSInode))
}) })
@ -208,7 +208,7 @@ var _ = Describe("Linux namespace operations", func() {
}) })
It("fails when the path is not a namespace", func() { It("fails when the path is not a namespace", func() {
tempFile, err := os.CreateTemp("", "nstest") tempFile, err := ioutil.TempFile("", "nstest")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
defer tempFile.Close() defer tempFile.Close()
@ -262,7 +262,7 @@ var _ = Describe("Linux namespace operations", func() {
}) })
It("should refuse other paths", func() { It("should refuse other paths", func() {
tempFile, err := os.CreateTemp("", "nstest") tempFile, err := ioutil.TempFile("", "nstest")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
defer tempFile.Close() defer tempFile.Close()

View File

@ -15,14 +15,18 @@
package ns_test package ns_test
import ( import (
"math/rand"
"runtime" "runtime"
"testing"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo"
"github.com/onsi/ginkgo/config"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"testing"
) )
func TestNs(t *testing.T) { func TestNs(t *testing.T) {
rand.Seed(config.GinkgoConfig.RandomSeed)
runtime.LockOSThread() runtime.LockOSThread()
RegisterFailHandler(Fail) RegisterFailHandler(Fail)

View File

@ -21,7 +21,7 @@ type BadReader struct {
Error error Error error
} }
func (r *BadReader) Read(_ []byte) (int, error) { func (r *BadReader) Read(buffer []byte) (int, error) {
if r.Error != nil { if r.Error != nil {
return 0, r.Error return 0, r.Error
} }

View File

@ -15,7 +15,7 @@
package testutils package testutils
import ( import (
"io" "io/ioutil"
"os" "os"
"github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/skel"
@ -29,7 +29,6 @@ func envCleanup() {
os.Unsetenv("CNI_NETNS") os.Unsetenv("CNI_NETNS")
os.Unsetenv("CNI_IFNAME") os.Unsetenv("CNI_IFNAME")
os.Unsetenv("CNI_CONTAINERID") os.Unsetenv("CNI_CONTAINERID")
os.Unsetenv("CNI_NETNS_OVERRIDE")
} }
func CmdAdd(cniNetns, cniContainerID, cniIfname string, conf []byte, f func() error) (types.Result, []byte, error) { func CmdAdd(cniNetns, cniContainerID, cniIfname string, conf []byte, f func() error) (types.Result, []byte, error) {
@ -38,7 +37,6 @@ func CmdAdd(cniNetns, cniContainerID, cniIfname string, conf []byte, f func() er
os.Setenv("CNI_NETNS", cniNetns) os.Setenv("CNI_NETNS", cniNetns)
os.Setenv("CNI_IFNAME", cniIfname) os.Setenv("CNI_IFNAME", cniIfname)
os.Setenv("CNI_CONTAINERID", cniContainerID) os.Setenv("CNI_CONTAINERID", cniContainerID)
os.Setenv("CNI_NETNS_OVERRIDE", "1")
defer envCleanup() defer envCleanup()
// Redirect stdout to capture plugin result // Redirect stdout to capture plugin result
@ -54,7 +52,7 @@ func CmdAdd(cniNetns, cniContainerID, cniIfname string, conf []byte, f func() er
var out []byte var out []byte
if err == nil { if err == nil {
out, err = io.ReadAll(r) out, err = ioutil.ReadAll(r)
} }
os.Stdout = oldStdout os.Stdout = oldStdout
@ -83,20 +81,19 @@ func CmdAddWithArgs(args *skel.CmdArgs, f func() error) (types.Result, []byte, e
return CmdAdd(args.Netns, args.ContainerID, args.IfName, args.StdinData, f) return CmdAdd(args.Netns, args.ContainerID, args.IfName, args.StdinData, f)
} }
func CmdCheck(cniNetns, cniContainerID, cniIfname string, f func() error) error { func CmdCheck(cniNetns, cniContainerID, cniIfname string, conf []byte, f func() error) error {
os.Setenv("CNI_COMMAND", "CHECK") os.Setenv("CNI_COMMAND", "CHECK")
os.Setenv("CNI_PATH", os.Getenv("PATH")) os.Setenv("CNI_PATH", os.Getenv("PATH"))
os.Setenv("CNI_NETNS", cniNetns) os.Setenv("CNI_NETNS", cniNetns)
os.Setenv("CNI_IFNAME", cniIfname) os.Setenv("CNI_IFNAME", cniIfname)
os.Setenv("CNI_CONTAINERID", cniContainerID) os.Setenv("CNI_CONTAINERID", cniContainerID)
os.Setenv("CNI_NETNS_OVERRIDE", "1")
defer envCleanup() defer envCleanup()
return f() return f()
} }
func CmdCheckWithArgs(args *skel.CmdArgs, f func() error) error { func CmdCheckWithArgs(args *skel.CmdArgs, f func() error) error {
return CmdCheck(args.Netns, args.ContainerID, args.IfName, f) return CmdCheck(args.Netns, args.ContainerID, args.IfName, args.StdinData, f)
} }
func CmdDel(cniNetns, cniContainerID, cniIfname string, f func() error) error { func CmdDel(cniNetns, cniContainerID, cniIfname string, f func() error) error {
@ -105,7 +102,6 @@ func CmdDel(cniNetns, cniContainerID, cniIfname string, f func() error) error {
os.Setenv("CNI_NETNS", cniNetns) os.Setenv("CNI_NETNS", cniNetns)
os.Setenv("CNI_IFNAME", cniIfname) os.Setenv("CNI_IFNAME", cniIfname)
os.Setenv("CNI_CONTAINERID", cniContainerID) os.Setenv("CNI_CONTAINERID", cniContainerID)
os.Setenv("CNI_NETNS_OVERRIDE", "1")
defer envCleanup() defer envCleanup()
return f() return f()

View File

@ -16,6 +16,7 @@ package testutils
import ( import (
"fmt" "fmt"
"io/ioutil"
"os" "os"
"strings" "strings"
@ -27,7 +28,7 @@ import (
// an error if any occurs while creating/writing the file. It is the caller's // an error if any occurs while creating/writing the file. It is the caller's
// responsibility to remove the file. // responsibility to remove the file.
func TmpResolvConf(dnsConf types.DNS) (string, error) { func TmpResolvConf(dnsConf types.DNS) (string, error) {
f, err := os.CreateTemp("", "cni_test_resolv.conf") f, err := ioutil.TempFile("", "cni_test_resolv.conf")
if err != nil { if err != nil {
return "", fmt.Errorf("failed to get temp file for CNI test resolv.conf: %v", err) return "", fmt.Errorf("failed to get temp file for CNI test resolv.conf: %v", err)
} }

View File

@ -2,12 +2,12 @@ package main_test
import ( import (
"fmt" "fmt"
"io" "io/ioutil"
"net" "net"
"os/exec" "os/exec"
"strings" "strings"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gbytes"
"github.com/onsi/gomega/gexec" "github.com/onsi/gomega/gexec"
@ -74,7 +74,7 @@ var _ = Describe("Echosvr", func() {
defer conn.Close() defer conn.Close()
fmt.Fprintf(conn, "hello\n") fmt.Fprintf(conn, "hello\n")
Expect(io.ReadAll(conn)).To(Equal([]byte("hello"))) Expect(ioutil.ReadAll(conn)).To(Equal([]byte("hello")))
}) })
}) })
@ -86,7 +86,7 @@ var _ = Describe("Echosvr", func() {
It("connects successfully using echo client", func() { It("connects successfully using echo client", func() {
Eventually(session.Out).Should(gbytes.Say("\n")) Eventually(session.Out).Should(gbytes.Say("\n"))
serverAddress := strings.TrimSpace(string(session.Out.Contents())) serverAddress := strings.TrimSpace(string(session.Out.Contents()))
fmt.Println("Server address", serverAddress) fmt.Println("Server address", string(serverAddress))
cmd := exec.Command(clientBinaryPath, "-target", serverAddress, "-message", "hello") cmd := exec.Command(clientBinaryPath, "-target", serverAddress, "-message", "hello")
clientSession, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter) clientSession, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter)

View File

@ -1,10 +1,10 @@
package main_test package main_test
import ( import (
"testing" . "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"testing"
) )
func TestEchosvr(t *testing.T) { func TestEchosvr(t *testing.T) {

View File

@ -1,10 +1,9 @@
// Echosvr is a simple TCP echo server // Echosvr is a simple TCP echo server
// //
// It prints its listen address on stdout // It prints its listen address on stdout
// // 127.0.0.1:xxxxx
// 127.0.0.1:xxxxx // A test should wait for this line, parse it
// A test should wait for this line, parse it // and may then attempt to connect.
// and may then attempt to connect.
package main package main
import ( import (
@ -44,13 +43,11 @@ func main() {
// Start UDP server // Start UDP server
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%s", port)) addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%s", port))
if err != nil { if err != nil {
log.Printf("Error from net.ResolveUDPAddr(): %s", err) log.Fatalf("Error from net.ResolveUDPAddr(): %s", err)
return
} }
sock, err := net.ListenUDP("udp", addr) sock, err := net.ListenUDP("udp", addr)
if err != nil { if err != nil {
log.Printf("Error from ListenUDP(): %s", err) log.Fatalf("Error from ListenUDP(): %s", err)
return
} }
defer sock.Close() defer sock.Close()
@ -58,11 +55,10 @@ func main() {
for { for {
n, addr, err := sock.ReadFrom(buffer) n, addr, err := sock.ReadFrom(buffer)
if err != nil { if err != nil {
log.Printf("Error from ReadFrom(): %s", err) log.Fatalf("Error from ReadFrom(): %s", err)
return
} }
sock.SetWriteDeadline(time.Now().Add(1 * time.Minute)) sock.SetWriteDeadline(time.Now().Add(1 * time.Minute))
_, err = sock.WriteTo(buffer[0:n], addr) n, err = sock.WriteTo(buffer[0:n], addr)
if err != nil { if err != nil {
return return
} }

View File

@ -24,9 +24,8 @@ import (
"sync" "sync"
"syscall" "syscall"
"golang.org/x/sys/unix"
"github.com/containernetworking/plugins/pkg/ns" "github.com/containernetworking/plugins/pkg/ns"
"golang.org/x/sys/unix"
) )
func getNsRunDir() string { func getNsRunDir() string {
@ -50,6 +49,7 @@ func getNsRunDir() string {
// Creates a new persistent (bind-mounted) network namespace and returns an object // Creates a new persistent (bind-mounted) network namespace and returns an object
// representing that namespace, without switching to it. // representing that namespace, without switching to it.
func NewNS() (ns.NetNS, error) { func NewNS() (ns.NetNS, error) {
nsRunDir := getNsRunDir() nsRunDir := getNsRunDir()
b := make([]byte, 16) b := make([]byte, 16)
@ -61,7 +61,7 @@ func NewNS() (ns.NetNS, error) {
// Create the directory for mounting network namespaces // Create the directory for mounting network namespaces
// This needs to be a shared mountpoint in case it is mounted in to // This needs to be a shared mountpoint in case it is mounted in to
// other namespaces (containers) // other namespaces (containers)
err = os.MkdirAll(nsRunDir, 0o755) err = os.MkdirAll(nsRunDir, 0755)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -29,9 +29,9 @@ func EnsureChain(ipt *iptables.IPTables, table, chain string) error {
if ipt == nil { if ipt == nil {
return errors.New("failed to ensure iptable chain: IPTables was nil") return errors.New("failed to ensure iptable chain: IPTables was nil")
} }
exists, err := ipt.ChainExists(table, chain) exists, err := ChainExists(ipt, table, chain)
if err != nil { if err != nil {
return fmt.Errorf("failed to check iptables chain existence: %v", err) return fmt.Errorf("failed to list iptables chains: %v", err)
} }
if !exists { if !exists {
err = ipt.NewChain(table, chain) err = ipt.NewChain(table, chain)
@ -45,6 +45,24 @@ func EnsureChain(ipt *iptables.IPTables, table, chain string) error {
return nil 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. // DeleteRule idempotently delete the iptables rule in the specified table/chain.
// It does not return an error if the referring chain doesn't exist // It does not return an error if the referring chain doesn't exist
func DeleteRule(ipt *iptables.IPTables, table, chain string, rulespec ...string) error { func DeleteRule(ipt *iptables.IPTables, table, chain string, rulespec ...string) error {
@ -115,6 +133,7 @@ func InsertUnique(ipt *iptables.IPTables, table, chain string, prepend bool, rul
if prepend { if prepend {
return ipt.Insert(table, chain, 1, rule...) return ipt.Insert(table, chain, 1, rule...)
} else {
return ipt.Append(table, chain, rule...)
} }
return ipt.Append(table, chain, rule...)
} }

View File

@ -19,12 +19,11 @@ import (
"math/rand" "math/rand"
"runtime" "runtime"
"github.com/coreos/go-iptables/iptables"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/plugins/pkg/ns" "github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils" "github.com/containernetworking/plugins/pkg/testutils"
"github.com/coreos/go-iptables/iptables"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
) )
const TABLE = "filter" // We'll monkey around here const TABLE = "filter" // We'll monkey around here
@ -35,6 +34,7 @@ var _ = Describe("chain tests", func() {
var cleanup func() var cleanup func()
BeforeEach(func() { BeforeEach(func() {
// Save a reference to the original namespace, // Save a reference to the original namespace,
// Add a new NS // Add a new NS
currNs, err := ns.GetCurrentNS() currNs, err := ns.GetCurrentNS()
@ -60,6 +60,7 @@ var _ = Describe("chain tests", func() {
ipt.DeleteChain(TABLE, testChain) ipt.DeleteChain(TABLE, testChain)
currNs.Set() currNs.Set()
} }
}) })
AfterEach(func() { AfterEach(func() {
@ -92,4 +93,5 @@ var _ = Describe("chain tests", func() {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
}) })
}) })
}) })

View File

@ -16,7 +16,7 @@ package sysctl
import ( import (
"fmt" "fmt"
"os" "io/ioutil"
"path/filepath" "path/filepath"
"strings" "strings"
) )
@ -36,7 +36,7 @@ func Sysctl(name string, params ...string) (string, error) {
func getSysctl(name string) (string, error) { func getSysctl(name string) (string, error) {
fullName := filepath.Join("/proc/sys", toNormalName(name)) fullName := filepath.Join("/proc/sys", toNormalName(name))
data, err := os.ReadFile(fullName) data, err := ioutil.ReadFile(fullName)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -46,7 +46,7 @@ func getSysctl(name string) (string, error) {
func setSysctl(name, value string) (string, error) { func setSysctl(name, value string) (string, error) {
fullName := filepath.Join("/proc/sys", toNormalName(name)) fullName := filepath.Join("/proc/sys", toNormalName(name))
if err := os.WriteFile(fullName, []byte(value), 0o644); err != nil { if err := ioutil.WriteFile(fullName, []byte(value), 0644); err != nil {
return "", err return "", err
} }

View File

@ -20,13 +20,12 @@ import (
"runtime" "runtime"
"strings" "strings"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/vishvananda/netlink"
"github.com/containernetworking/plugins/pkg/ns" "github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils" "github.com/containernetworking/plugins/pkg/testutils"
"github.com/containernetworking/plugins/pkg/utils/sysctl" "github.com/containernetworking/plugins/pkg/utils/sysctl"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vishvananda/netlink"
) )
const ( const (
@ -38,7 +37,8 @@ var _ = Describe("Sysctl tests", func() {
var testIfaceName string var testIfaceName string
var cleanup func() var cleanup func()
beforeEach := func() { BeforeEach(func() {
// Save a reference to the original namespace, // Save a reference to the original namespace,
// Add a new NS // Add a new NS
currNs, err := ns.GetCurrentNS() currNs, err := ns.GetCurrentNS()
@ -66,7 +66,8 @@ var _ = Describe("Sysctl tests", func() {
netlink.LinkDel(testIface) netlink.LinkDel(testIface)
currNs.Set() currNs.Set()
} }
}
})
AfterEach(func() { AfterEach(func() {
cleanup() cleanup()
@ -74,8 +75,7 @@ var _ = Describe("Sysctl tests", func() {
Describe("Sysctl", func() { Describe("Sysctl", func() {
It("reads keys with dot separators", func() { It("reads keys with dot separators", func() {
beforeEach() sysctlIfaceName := strings.Replace(testIfaceName, ".", "/", -1)
sysctlIfaceName := strings.ReplaceAll(testIfaceName, ".", "/")
sysctlKey := fmt.Sprintf(sysctlDotKeyTemplate, sysctlIfaceName) sysctlKey := fmt.Sprintf(sysctlDotKeyTemplate, sysctlIfaceName)
_, err := sysctl.Sysctl(sysctlKey) _, err := sysctl.Sysctl(sysctlKey)
@ -85,7 +85,6 @@ var _ = Describe("Sysctl tests", func() {
Describe("Sysctl", func() { Describe("Sysctl", func() {
It("reads keys with slash separators", func() { It("reads keys with slash separators", func() {
beforeEach()
sysctlKey := fmt.Sprintf(sysctlSlashKeyTemplate, testIfaceName) sysctlKey := fmt.Sprintf(sysctlSlashKeyTemplate, testIfaceName)
_, err := sysctl.Sysctl(sysctlKey) _, err := sysctl.Sysctl(sysctlKey)
@ -95,8 +94,7 @@ var _ = Describe("Sysctl tests", func() {
Describe("Sysctl", func() { Describe("Sysctl", func() {
It("writes keys with dot separators", func() { It("writes keys with dot separators", func() {
beforeEach() sysctlIfaceName := strings.Replace(testIfaceName, ".", "/", -1)
sysctlIfaceName := strings.ReplaceAll(testIfaceName, ".", "/")
sysctlKey := fmt.Sprintf(sysctlDotKeyTemplate, sysctlIfaceName) sysctlKey := fmt.Sprintf(sysctlDotKeyTemplate, sysctlIfaceName)
_, err := sysctl.Sysctl(sysctlKey, "1") _, err := sysctl.Sysctl(sysctlKey, "1")
@ -106,11 +104,11 @@ var _ = Describe("Sysctl tests", func() {
Describe("Sysctl", func() { Describe("Sysctl", func() {
It("writes keys with slash separators", func() { It("writes keys with slash separators", func() {
beforeEach()
sysctlKey := fmt.Sprintf(sysctlSlashKeyTemplate, testIfaceName) sysctlKey := fmt.Sprintf(sysctlSlashKeyTemplate, testIfaceName)
_, err := sysctl.Sysctl(sysctlKey, "1") _, err := sysctl.Sysctl(sysctlKey, "1")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
}) })
}) })
}) })

View File

@ -17,7 +17,7 @@ package sysctl_test
import ( import (
"testing" "testing"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )

View File

@ -15,10 +15,10 @@
package utils_test package utils_test
import ( import (
"testing" . "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"testing"
) )
func TestUtils(t *testing.T) { func TestUtils(t *testing.T) {

View File

@ -18,7 +18,7 @@ import (
"fmt" "fmt"
"strings" "strings"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )
@ -26,29 +26,29 @@ var _ = Describe("Utils", func() {
Describe("FormatChainName", func() { Describe("FormatChainName", func() {
It("must format a short name", func() { It("must format a short name", func() {
chain := FormatChainName("test", "1234") chain := FormatChainName("test", "1234")
Expect(chain).To(HaveLen(maxChainLength)) Expect(len(chain)).To(Equal(maxChainLength))
Expect(chain).To(Equal("CNI-2bbe0c48b91a7d1b8a6753a8")) Expect(chain).To(Equal("CNI-2bbe0c48b91a7d1b8a6753a8"))
}) })
It("must truncate a long name", func() { It("must truncate a long name", func() {
chain := FormatChainName("testalongnamethatdoesnotmakesense", "1234") chain := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
Expect(chain).To(HaveLen(maxChainLength)) Expect(len(chain)).To(Equal(maxChainLength))
Expect(chain).To(Equal("CNI-374f33fe84ab0ed84dcdebe3")) Expect(chain).To(Equal("CNI-374f33fe84ab0ed84dcdebe3"))
}) })
It("must be predictable", func() { It("must be predictable", func() {
chain1 := FormatChainName("testalongnamethatdoesnotmakesense", "1234") chain1 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
chain2 := FormatChainName("testalongnamethatdoesnotmakesense", "1234") chain2 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
Expect(chain1).To(HaveLen(maxChainLength)) Expect(len(chain1)).To(Equal(maxChainLength))
Expect(chain2).To(HaveLen(maxChainLength)) Expect(len(chain2)).To(Equal(maxChainLength))
Expect(chain1).To(Equal(chain2)) Expect(chain1).To(Equal(chain2))
}) })
It("must change when a character changes", func() { It("must change when a character changes", func() {
chain1 := FormatChainName("testalongnamethatdoesnotmakesense", "1234") chain1 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
chain2 := FormatChainName("testalongnamethatdoesnotmakesense", "1235") chain2 := FormatChainName("testalongnamethatdoesnotmakesense", "1235")
Expect(chain1).To(HaveLen(maxChainLength)) Expect(len(chain1)).To(Equal(maxChainLength))
Expect(chain2).To(HaveLen(maxChainLength)) Expect(len(chain2)).To(Equal(maxChainLength))
Expect(chain1).To(Equal("CNI-374f33fe84ab0ed84dcdebe3")) Expect(chain1).To(Equal("CNI-374f33fe84ab0ed84dcdebe3"))
Expect(chain1).NotTo(Equal(chain2)) Expect(chain1).NotTo(Equal(chain2))
}) })
@ -57,35 +57,35 @@ var _ = Describe("Utils", func() {
Describe("MustFormatChainNameWithPrefix", func() { Describe("MustFormatChainNameWithPrefix", func() {
It("generates a chain name with a prefix", func() { It("generates a chain name with a prefix", func() {
chain := MustFormatChainNameWithPrefix("test", "1234", "PREFIX-") chain := MustFormatChainNameWithPrefix("test", "1234", "PREFIX-")
Expect(chain).To(HaveLen(maxChainLength)) Expect(len(chain)).To(Equal(maxChainLength))
Expect(chain).To(Equal("CNI-PREFIX-2bbe0c48b91a7d1b8")) Expect(chain).To(Equal("CNI-PREFIX-2bbe0c48b91a7d1b8"))
}) })
It("must format a short name", func() { It("must format a short name", func() {
chain := MustFormatChainNameWithPrefix("test", "1234", "PREFIX-") chain := MustFormatChainNameWithPrefix("test", "1234", "PREFIX-")
Expect(chain).To(HaveLen(maxChainLength)) Expect(len(chain)).To(Equal(maxChainLength))
Expect(chain).To(Equal("CNI-PREFIX-2bbe0c48b91a7d1b8")) Expect(chain).To(Equal("CNI-PREFIX-2bbe0c48b91a7d1b8"))
}) })
It("must truncate a long name", func() { It("must truncate a long name", func() {
chain := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1234", "PREFIX-") chain := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1234", "PREFIX-")
Expect(chain).To(HaveLen(maxChainLength)) Expect(len(chain)).To(Equal(maxChainLength))
Expect(chain).To(Equal("CNI-PREFIX-374f33fe84ab0ed84")) Expect(chain).To(Equal("CNI-PREFIX-374f33fe84ab0ed84"))
}) })
It("must be predictable", func() { It("must be predictable", func() {
chain1 := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1234", "PREFIX-") chain1 := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1234", "PREFIX-")
chain2 := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1234", "PREFIX-") chain2 := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1234", "PREFIX-")
Expect(chain1).To(HaveLen(maxChainLength)) Expect(len(chain1)).To(Equal(maxChainLength))
Expect(chain2).To(HaveLen(maxChainLength)) Expect(len(chain2)).To(Equal(maxChainLength))
Expect(chain1).To(Equal(chain2)) Expect(chain1).To(Equal(chain2))
}) })
It("must change when a character changes", func() { It("must change when a character changes", func() {
chain1 := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1234", "PREFIX-") chain1 := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1234", "PREFIX-")
chain2 := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1235", "PREFIX-") chain2 := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1235", "PREFIX-")
Expect(chain1).To(HaveLen(maxChainLength)) Expect(len(chain1)).To(Equal(maxChainLength))
Expect(chain2).To(HaveLen(maxChainLength)) Expect(len(chain2)).To(Equal(maxChainLength))
Expect(chain1).To(Equal("CNI-PREFIX-374f33fe84ab0ed84")) Expect(chain1).To(Equal("CNI-PREFIX-374f33fe84ab0ed84"))
Expect(chain1).NotTo(Equal(chain2)) Expect(chain1).NotTo(Equal(chain2))
}) })
@ -161,4 +161,5 @@ var _ = Describe("Utils", func() {
) )
}) })
}) })
}) })

View File

@ -23,7 +23,7 @@ const (
MaxDHCPLen = 576 MaxDHCPLen = 576
) )
// Send the Discovery Packet to the Broadcast Channel //Send the Discovery Packet to the Broadcast Channel
func DhcpSendDiscoverPacket(c *dhcp4client.Client, options dhcp4.Options) (dhcp4.Packet, error) { func DhcpSendDiscoverPacket(c *dhcp4client.Client, options dhcp4.Options) (dhcp4.Packet, error) {
discoveryPacket := c.DiscoverPacket() discoveryPacket := c.DiscoverPacket()
@ -35,7 +35,7 @@ func DhcpSendDiscoverPacket(c *dhcp4client.Client, options dhcp4.Options) (dhcp4
return discoveryPacket, c.SendPacket(discoveryPacket) return discoveryPacket, c.SendPacket(discoveryPacket)
} }
// Send Request Based On the offer Received. //Send Request Based On the offer Received.
func DhcpSendRequest(c *dhcp4client.Client, options dhcp4.Options, offerPacket *dhcp4.Packet) (dhcp4.Packet, error) { func DhcpSendRequest(c *dhcp4client.Client, options dhcp4.Options, offerPacket *dhcp4.Packet) (dhcp4.Packet, error) {
requestPacket := c.RequestPacket(offerPacket) requestPacket := c.RequestPacket(offerPacket)
@ -48,7 +48,7 @@ func DhcpSendRequest(c *dhcp4client.Client, options dhcp4.Options, offerPacket *
return requestPacket, c.SendPacket(requestPacket) return requestPacket, c.SendPacket(requestPacket)
} }
// Send Decline to the received acknowledgement. //Send Decline to the received acknowledgement.
func DhcpSendDecline(c *dhcp4client.Client, acknowledgementPacket *dhcp4.Packet, options dhcp4.Options) (dhcp4.Packet, error) { func DhcpSendDecline(c *dhcp4client.Client, acknowledgementPacket *dhcp4.Packet, options dhcp4.Options) (dhcp4.Packet, error) {
declinePacket := c.DeclinePacket(acknowledgementPacket) declinePacket := c.DeclinePacket(acknowledgementPacket)
@ -61,7 +61,7 @@ func DhcpSendDecline(c *dhcp4client.Client, acknowledgementPacket *dhcp4.Packet,
return declinePacket, c.SendPacket(declinePacket) return declinePacket, c.SendPacket(declinePacket)
} }
// Lets do a Full DHCP Request. //Lets do a Full DHCP Request.
func DhcpRequest(c *dhcp4client.Client, options dhcp4.Options) (bool, dhcp4.Packet, error) { func DhcpRequest(c *dhcp4client.Client, options dhcp4.Options) (bool, dhcp4.Packet, error) {
discoveryPacket, err := DhcpSendDiscoverPacket(c, options) discoveryPacket, err := DhcpSendDiscoverPacket(c, options)
if err != nil { if err != nil {
@ -91,8 +91,8 @@ func DhcpRequest(c *dhcp4client.Client, options dhcp4.Options) (bool, dhcp4.Pack
return true, acknowledgement, nil return true, acknowledgement, nil
} }
// Renew a lease backed on the Acknowledgement Packet. //Renew a lease backed on the Acknowledgement Packet.
// Returns Successful, The AcknoledgementPacket, Any Errors //Returns Successful, The AcknoledgementPacket, Any Errors
func DhcpRenew(c *dhcp4client.Client, acknowledgement dhcp4.Packet, options dhcp4.Options) (bool, dhcp4.Packet, error) { func DhcpRenew(c *dhcp4client.Client, acknowledgement dhcp4.Packet, options dhcp4.Options) (bool, dhcp4.Packet, error) {
renewRequest := c.RenewalRequestPacket(&acknowledgement) renewRequest := c.RenewalRequestPacket(&acknowledgement)
@ -120,8 +120,8 @@ func DhcpRenew(c *dhcp4client.Client, acknowledgement dhcp4.Packet, options dhcp
return true, newAcknowledgement, nil return true, newAcknowledgement, nil
} }
// Release a lease backed on the Acknowledgement Packet. //Release a lease backed on the Acknowledgement Packet.
// Returns Any Errors //Returns Any Errors
func DhcpRelease(c *dhcp4client.Client, acknowledgement dhcp4.Packet, options dhcp4.Options) error { func DhcpRelease(c *dhcp4client.Client, acknowledgement dhcp4.Packet, options dhcp4.Options) error {
release := c.ReleasePacket(&acknowledgement) release := c.ReleasePacket(&acknowledgement)

View File

@ -15,25 +15,22 @@
package main package main
import ( import (
"context"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io/ioutil"
"net" "net"
"net/http" "net/http"
"net/rpc" "net/rpc"
"os" "os"
"os/signal"
"path/filepath" "path/filepath"
"runtime" "runtime"
"sync" "sync"
"syscall"
"time" "time"
"github.com/coreos/go-systemd/v22/activation"
"github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/skel"
current "github.com/containernetworking/cni/pkg/types/100" current "github.com/containernetworking/cni/pkg/types/100"
"github.com/coreos/go-systemd/v22/activation"
) )
var errNoMoreTries = errors.New("no more tries") var errNoMoreTries = errors.New("no more tries")
@ -56,7 +53,7 @@ func newDHCP(clientTimeout, clientResendMax time.Duration) *DHCP {
} }
// TODO: current client ID is too long. At least the container ID should not be used directly. // TODO: current client ID is too long. At least the container ID should not be used directly.
// A separate issue is necessary to ensure no breaking change is affecting other users. // A seperate issue is necessary to ensure no breaking change is affecting other users.
func generateClientID(containerID string, netName string, ifName string) string { func generateClientID(containerID string, netName string, ifName string) string {
clientID := containerID + "/" + netName + "/" + ifName clientID := containerID + "/" + netName + "/" + ifName
// defined in RFC 2132, length size can not be larger than 1 octet. So we truncate 254 to make everyone happy. // defined in RFC 2132, length size can not be larger than 1 octet. So we truncate 254 to make everyone happy.
@ -80,20 +77,12 @@ func (d *DHCP) Allocate(args *skel.CmdArgs, result *current.Result) error {
} }
clientID := generateClientID(args.ContainerID, conf.Name, args.IfName) clientID := generateClientID(args.ContainerID, conf.Name, args.IfName)
hostNetns := d.hostNetnsPrefix + args.Netns
// If we already have an active lease for this clientID, do not create l, err := AcquireLease(clientID, hostNetns, args.IfName,
// another one optsRequesting, optsProviding,
l := d.getLease(clientID) d.clientTimeout, d.clientResendMax, d.broadcast)
if l != nil { if err != nil {
l.Check() return err
} else {
hostNetns := d.hostNetnsPrefix + args.Netns
l, err = AcquireLease(clientID, hostNetns, args.IfName,
optsRequesting, optsProviding,
d.clientTimeout, d.clientResendMax, d.broadcast)
if err != nil {
return err
}
} }
ipn, err := l.IPNet() ipn, err := l.IPNet()
@ -115,7 +104,7 @@ func (d *DHCP) Allocate(args *skel.CmdArgs, result *current.Result) error {
// Release stops maintenance of the lease acquired in Allocate() // Release stops maintenance of the lease acquired in Allocate()
// and sends a release msg to the DHCP server. // and sends a release msg to the DHCP server.
func (d *DHCP) Release(args *skel.CmdArgs, _ *struct{}) error { func (d *DHCP) Release(args *skel.CmdArgs, reply *struct{}) error {
conf := NetConf{} conf := NetConf{}
if err := json.Unmarshal(args.StdinData, &conf); err != nil { if err := json.Unmarshal(args.StdinData, &conf); err != nil {
return fmt.Errorf("error parsing netconf: %v", err) return fmt.Errorf("error parsing netconf: %v", err)
@ -150,7 +139,7 @@ func (d *DHCP) setLease(clientID string, l *DHCPLease) {
d.leases[clientID] = l d.leases[clientID] = l
} }
// func (d *DHCP) clearLease(contID, netName, ifName string) { //func (d *DHCP) clearLease(contID, netName, ifName string) {
func (d *DHCP) clearLease(clientID string) { func (d *DHCP) clearLease(clientID string) {
d.mux.Lock() d.mux.Lock()
defer d.mux.Unlock() defer d.mux.Unlock()
@ -167,7 +156,7 @@ func getListener(socketPath string) (net.Listener, error) {
switch { switch {
case len(l) == 0: case len(l) == 0:
if err := os.MkdirAll(filepath.Dir(socketPath), 0o700); err != nil { if err := os.MkdirAll(filepath.Dir(socketPath), 0700); err != nil {
return nil, err return nil, err
} }
return net.Listen("unix", socketPath) return net.Listen("unix", socketPath)
@ -196,7 +185,7 @@ func runDaemon(
if !filepath.IsAbs(pidfilePath) { if !filepath.IsAbs(pidfilePath) {
return fmt.Errorf("Error writing pidfile %q: path not absolute", pidfilePath) return fmt.Errorf("Error writing pidfile %q: path not absolute", pidfilePath)
} }
if err := os.WriteFile(pidfilePath, []byte(fmt.Sprintf("%d", os.Getpid())), 0o644); err != nil { if err := ioutil.WriteFile(pidfilePath, []byte(fmt.Sprintf("%d", os.Getpid())), 0644); err != nil {
return fmt.Errorf("Error writing pidfile %q: %v", pidfilePath, err) return fmt.Errorf("Error writing pidfile %q: %v", pidfilePath, err)
} }
} }
@ -206,27 +195,11 @@ func runDaemon(
return fmt.Errorf("Error getting listener: %v", err) return fmt.Errorf("Error getting listener: %v", err)
} }
srv := http.Server{}
exit := make(chan os.Signal, 1)
done := make(chan bool, 1)
signal.Notify(exit, os.Interrupt, syscall.SIGTERM)
go func() {
<-exit
srv.Shutdown(context.TODO())
os.Remove(hostPrefix + socketPath)
os.Remove(pidfilePath)
done <- true
}()
dhcp := newDHCP(dhcpClientTimeout, resendMax) dhcp := newDHCP(dhcpClientTimeout, resendMax)
dhcp.hostNetnsPrefix = hostPrefix dhcp.hostNetnsPrefix = hostPrefix
dhcp.broadcast = broadcast dhcp.broadcast = broadcast
rpc.Register(dhcp) rpc.Register(dhcp)
rpc.HandleHTTP() rpc.HandleHTTP()
srv.Serve(l) http.Serve(l, nil)
<-done
return nil return nil
} }

View File

@ -16,19 +16,21 @@ package main
import ( import (
"fmt" "fmt"
"net"
"os" "os"
"os/exec" "os/exec"
"sync" "sync"
"time" "time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/vishvananda/netlink"
"github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/skel"
current "github.com/containernetworking/cni/pkg/types/100" current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/plugins/pkg/ns" "github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils" "github.com/containernetworking/plugins/pkg/testutils"
"github.com/vishvananda/netlink"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
) )
var _ = Describe("DHCP Multiple Lease Operations", func() { var _ = Describe("DHCP Multiple Lease Operations", func() {
@ -38,10 +40,11 @@ var _ = Describe("DHCP Multiple Lease Operations", func() {
var clientCmd *exec.Cmd var clientCmd *exec.Cmd
var socketPath string var socketPath string
var tmpDir string var tmpDir string
var serverIP net.IPNet
var err error var err error
BeforeEach(func() { BeforeEach(func() {
dhcpServerStopCh, socketPath, originalNS, targetNS, err = dhcpSetupOriginalNS() dhcpServerStopCh, serverIP, socketPath, originalNS, targetNS, err = dhcpSetupOriginalNS()
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
// Move the container side to the container's NS // Move the container side to the container's NS
@ -61,7 +64,7 @@ var _ = Describe("DHCP Multiple Lease Operations", func() {
}) })
// Start the DHCP server // Start the DHCP server
dhcpServerDone, err = dhcpServerStart(originalNS, 2, dhcpServerStopCh) dhcpServerDone, err = dhcpServerStart(originalNS, net.IPv4(192, 168, 1, 5), serverIP.IP, 2, dhcpServerStopCh)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
// Start the DHCP client daemon // Start the DHCP client daemon
@ -120,7 +123,7 @@ var _ = Describe("DHCP Multiple Lease Operations", func() {
addResult, err = current.GetResult(r) addResult, err = current.GetResult(r)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(addResult.IPs).To(HaveLen(1)) Expect(len(addResult.IPs)).To(Equal(1))
Expect(addResult.IPs[0].Address.String()).To(Equal("192.168.1.5/24")) Expect(addResult.IPs[0].Address.String()).To(Equal("192.168.1.5/24"))
return nil return nil
}) })
@ -143,7 +146,7 @@ var _ = Describe("DHCP Multiple Lease Operations", func() {
addResult, err = current.GetResult(r) addResult, err = current.GetResult(r)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(addResult.IPs).To(HaveLen(1)) Expect(len(addResult.IPs)).To(Equal(1))
Expect(addResult.IPs[0].Address.String()).To(Equal("192.168.1.6/24")) Expect(addResult.IPs[0].Address.String()).To(Equal("192.168.1.6/24"))
return nil return nil
}) })

View File

@ -15,10 +15,10 @@
package main package main
import ( import (
"testing" . "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"testing"
) )
func TestDHCP(t *testing.T) { func TestDHCP(t *testing.T) {

View File

@ -18,6 +18,7 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net" "net"
"os" "os"
"os/exec" "os/exec"
@ -25,22 +26,24 @@ import (
"sync" "sync"
"time" "time"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils"
"github.com/vishvananda/netlink"
"github.com/d2g/dhcp4" "github.com/d2g/dhcp4"
"github.com/d2g/dhcp4server" "github.com/d2g/dhcp4server"
"github.com/d2g/dhcp4server/leasepool" "github.com/d2g/dhcp4server/leasepool"
"github.com/d2g/dhcp4server/leasepool/memorypool" "github.com/d2g/dhcp4server/leasepool/memorypool"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/vishvananda/netlink"
"github.com/containernetworking/cni/pkg/skel" . "github.com/onsi/ginkgo"
types100 "github.com/containernetworking/cni/pkg/types/100" . "github.com/onsi/gomega"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils"
) )
func getTmpDir() (string, error) { func getTmpDir() (string, error) {
tmpDir, err := os.MkdirTemp(cniDirPrefix, "dhcp") tmpDir, err := ioutil.TempDir(cniDirPrefix, "dhcp")
if err == nil { if err == nil {
tmpDir = filepath.ToSlash(tmpDir) tmpDir = filepath.ToSlash(tmpDir)
} }
@ -48,7 +51,7 @@ func getTmpDir() (string, error) {
return tmpDir, err return tmpDir, err
} }
func dhcpServerStart(netns ns.NetNS, numLeases int, stopCh <-chan bool) (*sync.WaitGroup, error) { func dhcpServerStart(netns ns.NetNS, leaseIP, serverIP net.IP, numLeases int, stopCh <-chan bool) (*sync.WaitGroup, error) {
// Add the expected IP to the pool // Add the expected IP to the pool
lp := memorypool.MemoryPool{} lp := memorypool.MemoryPool{}
@ -118,7 +121,7 @@ const (
) )
var _ = BeforeSuite(func() { var _ = BeforeSuite(func() {
err := os.MkdirAll(cniDirPrefix, 0o700) err := os.MkdirAll(cniDirPrefix, 0700)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
}) })
@ -200,7 +203,7 @@ var _ = Describe("DHCP Operations", func() {
}) })
// Start the DHCP server // Start the DHCP server
dhcpServerDone, err = dhcpServerStart(originalNS, 1, dhcpServerStopCh) dhcpServerDone, err = dhcpServerStart(originalNS, net.IPv4(192, 168, 1, 5), serverIP.IP, 1, dhcpServerStopCh)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
// Start the DHCP client daemon // Start the DHCP client daemon
@ -271,7 +274,7 @@ var _ = Describe("DHCP Operations", func() {
addResult, err = types100.GetResult(r) addResult, err = types100.GetResult(r)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(addResult.IPs).To(HaveLen(1)) Expect(len(addResult.IPs)).To(Equal(1))
Expect(addResult.IPs[0].Address.String()).To(Equal("192.168.1.5/24")) Expect(addResult.IPs[0].Address.String()).To(Equal("192.168.1.5/24"))
return nil return nil
}) })
@ -314,7 +317,7 @@ var _ = Describe("DHCP Operations", func() {
addResult, err = types100.GetResult(r) addResult, err = types100.GetResult(r)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(addResult.IPs).To(HaveLen(1)) Expect(len(addResult.IPs)).To(Equal(1))
Expect(addResult.IPs[0].Address.String()).To(Equal("192.168.1.5/24")) Expect(addResult.IPs[0].Address.String()).To(Equal("192.168.1.5/24"))
return nil return nil
}) })
@ -332,17 +335,9 @@ var _ = Describe("DHCP Operations", func() {
started.Done() started.Done()
started.Wait() started.Wait()
err := originalNS.Do(func(ns.NetNS) error { err = originalNS.Do(func(ns.NetNS) error {
return testutils.CmdDelWithArgs(args, func() error { return testutils.CmdDelWithArgs(args, func() error {
copiedArgs := &skel.CmdArgs{ return cmdDel(args)
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()) Expect(err).NotTo(HaveOccurred())
@ -369,7 +364,7 @@ const (
contVethName1 string = "eth1" contVethName1 string = "eth1"
) )
func dhcpSetupOriginalNS() (chan bool, string, ns.NetNS, ns.NetNS, error) { func dhcpSetupOriginalNS() (chan bool, net.IPNet, string, ns.NetNS, ns.NetNS, error) {
var originalNS, targetNS ns.NetNS var originalNS, targetNS ns.NetNS
var dhcpServerStopCh chan bool var dhcpServerStopCh chan bool
var socketPath string var socketPath string
@ -390,6 +385,11 @@ func dhcpSetupOriginalNS() (chan bool, string, ns.NetNS, ns.NetNS, error) {
targetNS, err = testutils.NewNS() targetNS, err = testutils.NewNS()
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
serverIP := net.IPNet{
IP: net.IPv4(192, 168, 1, 1),
Mask: net.IPv4Mask(255, 255, 255, 0),
}
// Use (original) NS // Use (original) NS
err = originalNS.Do(func(ns.NetNS) error { err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover() defer GinkgoRecover()
@ -484,7 +484,7 @@ func dhcpSetupOriginalNS() (chan bool, string, ns.NetNS, ns.NetNS, error) {
return nil return nil
}) })
return dhcpServerStopCh, socketPath, originalNS, targetNS, err return dhcpServerStopCh, serverIP, socketPath, originalNS, targetNS, err
} }
var _ = Describe("DHCP Lease Unavailable Operations", func() { var _ = Describe("DHCP Lease Unavailable Operations", func() {
@ -494,10 +494,11 @@ var _ = Describe("DHCP Lease Unavailable Operations", func() {
var clientCmd *exec.Cmd var clientCmd *exec.Cmd
var socketPath string var socketPath string
var tmpDir string var tmpDir string
var serverIP net.IPNet
var err error var err error
BeforeEach(func() { BeforeEach(func() {
dhcpServerStopCh, socketPath, originalNS, targetNS, err = dhcpSetupOriginalNS() dhcpServerStopCh, serverIP, socketPath, originalNS, targetNS, err = dhcpSetupOriginalNS()
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
// Move the container side to the container's NS // Move the container side to the container's NS
@ -517,7 +518,7 @@ var _ = Describe("DHCP Lease Unavailable Operations", func() {
}) })
// Start the DHCP server // Start the DHCP server
dhcpServerDone, err = dhcpServerStart(originalNS, 1, dhcpServerStopCh) dhcpServerDone, err = dhcpServerStart(originalNS, net.IPv4(192, 168, 1, 5), serverIP.IP, 1, dhcpServerStopCh)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
// Start the DHCP client daemon // Start the DHCP client daemon
@ -596,7 +597,7 @@ var _ = Describe("DHCP Lease Unavailable Operations", func() {
addResult, err = types100.GetResult(r) addResult, err = types100.GetResult(r)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(addResult.IPs).To(HaveLen(1)) Expect(len(addResult.IPs)).To(Equal(1))
Expect(addResult.IPs[0].Address.String()).To(Equal("192.168.1.5/24")) Expect(addResult.IPs[0].Address.String()).To(Equal("192.168.1.5/24"))
return nil return nil
}) })

View File

@ -34,17 +34,13 @@ import (
// RFC 2131 suggests using exponential backoff, starting with 4sec // RFC 2131 suggests using exponential backoff, starting with 4sec
// and randomized to +/- 1sec // and randomized to +/- 1sec
const ( const resendDelay0 = 4 * time.Second
resendDelay0 = 4 * time.Second const resendDelayMax = 62 * time.Second
resendDelayMax = 62 * time.Second
)
// To speed up the retry for first few failures, we retry without // To speed up the retry for first few failures, we retry without
// backoff for a few times // backoff for a few times
const ( const resendFastDelay = 2 * time.Second
resendFastDelay = 2 * time.Second const resendFastMax = 4
resendFastMax = 4
)
const ( const (
leaseStateBound = iota leaseStateBound = iota
@ -71,7 +67,6 @@ type DHCPLease struct {
broadcast bool broadcast bool
stopping uint32 stopping uint32
stop chan struct{} stop chan struct{}
check chan struct{}
wg sync.WaitGroup wg sync.WaitGroup
// list of requesting and providing options and if they are necessary / their value // list of requesting and providing options and if they are necessary / their value
optsRequesting map[dhcp4.OptionCode]bool optsRequesting map[dhcp4.OptionCode]bool
@ -83,12 +78,9 @@ var requestOptionsDefault = map[dhcp4.OptionCode]bool{
dhcp4.OptionSubnetMask: true, dhcp4.OptionSubnetMask: true,
} }
func prepareOptions(cniArgs string, provideOptions []ProvideOption, requestOptions []RequestOption) ( func prepareOptions(cniArgs string, ProvideOptions []ProvideOption, RequestOptions []RequestOption) (
map[dhcp4.OptionCode]bool, map[dhcp4.OptionCode][]byte, error, optsRequesting map[dhcp4.OptionCode]bool, optsProviding map[dhcp4.OptionCode][]byte, err error) {
) {
var optsRequesting map[dhcp4.OptionCode]bool
var optsProviding map[dhcp4.OptionCode][]byte
var err error
// parse CNI args // parse CNI args
cniArgsParsed := map[string]string{} cniArgsParsed := map[string]string{}
for _, argPair := range strings.Split(cniArgs, ";") { for _, argPair := range strings.Split(cniArgs, ";") {
@ -101,20 +93,23 @@ func prepareOptions(cniArgs string, provideOptions []ProvideOption, requestOptio
// parse providing options map // parse providing options map
var optParsed dhcp4.OptionCode var optParsed dhcp4.OptionCode
optsProviding = make(map[dhcp4.OptionCode][]byte) optsProviding = make(map[dhcp4.OptionCode][]byte)
for _, opt := range provideOptions { for _, opt := range ProvideOptions {
optParsed, err = parseOptionName(string(opt.Option)) optParsed, err = parseOptionName(string(opt.Option))
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("Can not parse option %q: %w", opt.Option, err) err = fmt.Errorf("Can not parse option %q: %w", opt.Option, err)
return
} }
if len(opt.Value) > 0 { if len(opt.Value) > 0 {
if len(opt.Value) > 255 { if len(opt.Value) > 255 {
return nil, nil, fmt.Errorf("value too long for option %q: %q", opt.Option, opt.Value) err = fmt.Errorf("value too long for option %q: %q", opt.Option, opt.Value)
return
} }
optsProviding[optParsed] = []byte(opt.Value) optsProviding[optParsed] = []byte(opt.Value)
} }
if value, ok := cniArgsParsed[opt.ValueFromCNIArg]; ok { if value, ok := cniArgsParsed[opt.ValueFromCNIArg]; ok {
if len(value) > 255 { 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) err = fmt.Errorf("value too long for option %q from CNI_ARGS %q: %q", opt.Option, opt.ValueFromCNIArg, opt.Value)
return
} }
optsProviding[optParsed] = []byte(value) optsProviding[optParsed] = []byte(value)
} }
@ -123,13 +118,14 @@ func prepareOptions(cniArgs string, provideOptions []ProvideOption, requestOptio
// parse necessary options map // parse necessary options map
optsRequesting = make(map[dhcp4.OptionCode]bool) optsRequesting = make(map[dhcp4.OptionCode]bool)
skipRequireDefault := false skipRequireDefault := false
for _, opt := range requestOptions { for _, opt := range RequestOptions {
if opt.SkipDefault { if opt.SkipDefault {
skipRequireDefault = true skipRequireDefault = true
} }
optParsed, err = parseOptionName(string(opt.Option)) optParsed, err = parseOptionName(string(opt.Option))
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("Can not parse option %q: %w", opt.Option, err) err = fmt.Errorf("Can not parse option %q: %w", opt.Option, err)
return
} }
optsRequesting[optParsed] = true optsRequesting[optParsed] = true
} }
@ -139,7 +135,7 @@ func prepareOptions(cniArgs string, provideOptions []ProvideOption, requestOptio
optsRequesting[k] = v optsRequesting[k] = v
} }
} }
return optsRequesting, optsProviding, err return
} }
// AcquireLease gets an DHCP lease and then maintains it in the background // AcquireLease gets an DHCP lease and then maintains it in the background
@ -154,7 +150,6 @@ func AcquireLease(
l := &DHCPLease{ l := &DHCPLease{
clientID: clientID, clientID: clientID,
stop: make(chan struct{}), stop: make(chan struct{}),
check: make(chan struct{}),
timeout: timeout, timeout: timeout,
resendMax: resendMax, resendMax: resendMax,
broadcast: broadcast, broadcast: broadcast,
@ -205,11 +200,7 @@ func (l *DHCPLease) Stop() {
l.wg.Wait() l.wg.Wait()
} }
func (l *DHCPLease) Check() { func (l *DHCPLease) getOptionsWithClientId() dhcp4.Options {
l.check <- struct{}{}
}
func (l *DHCPLease) getOptionsWithClientID() dhcp4.Options {
opts := make(dhcp4.Options) opts := make(dhcp4.Options)
opts[dhcp4.OptionClientIdentifier] = []byte(l.clientID) opts[dhcp4.OptionClientIdentifier] = []byte(l.clientID)
// client identifier's first byte is "type" // client identifier's first byte is "type"
@ -220,7 +211,7 @@ func (l *DHCPLease) getOptionsWithClientID() dhcp4.Options {
} }
func (l *DHCPLease) getAllOptions() dhcp4.Options { func (l *DHCPLease) getAllOptions() dhcp4.Options {
opts := l.getOptionsWithClientID() opts := l.getOptionsWithClientId()
for k, v := range l.optsProviding { for k, v := range l.optsProviding {
opts[k] = v opts[k] = v
@ -234,7 +225,7 @@ func (l *DHCPLease) getAllOptions() dhcp4.Options {
} }
func (l *DHCPLease) acquire() error { func (l *DHCPLease) acquire() error {
c, err := newDHCPClient(l.link, l.timeout, l.broadcast) c, err := newDHCPClient(l.link, l.clientID, l.timeout, l.broadcast)
if err != nil { if err != nil {
return err return err
} }
@ -305,7 +296,7 @@ func (l *DHCPLease) maintain() {
switch state { switch state {
case leaseStateBound: case leaseStateBound:
sleepDur = time.Until(l.renewalTime) sleepDur = l.renewalTime.Sub(time.Now())
if sleepDur <= 0 { if sleepDur <= 0 {
log.Printf("%v: renewing lease", l.clientID) log.Printf("%v: renewing lease", l.clientID)
state = leaseStateRenewing state = leaseStateRenewing
@ -317,7 +308,7 @@ func (l *DHCPLease) maintain() {
log.Printf("%v: %v", l.clientID, err) log.Printf("%v: %v", l.clientID, err)
if time.Now().After(l.rebindingTime) { if time.Now().After(l.rebindingTime) {
log.Printf("%v: renewal time expired, rebinding", l.clientID) log.Printf("%v: renawal time expired, rebinding", l.clientID)
state = leaseStateRebinding state = leaseStateRebinding
} }
} else { } else {
@ -343,9 +334,6 @@ func (l *DHCPLease) maintain() {
select { select {
case <-time.After(sleepDur): case <-time.After(sleepDur):
case <-l.check:
log.Printf("%v: Checking lease", l.clientID)
case <-l.stop: case <-l.stop:
if err := l.release(); err != nil { if err := l.release(); err != nil {
log.Printf("%v: failed to release DHCP lease: %v", l.clientID, err) log.Printf("%v: failed to release DHCP lease: %v", l.clientID, err)
@ -362,13 +350,13 @@ func (l *DHCPLease) downIface() {
} }
func (l *DHCPLease) renew() error { func (l *DHCPLease) renew() error {
c, err := newDHCPClient(l.link, l.timeout, l.broadcast) c, err := newDHCPClient(l.link, l.clientID, l.timeout, l.broadcast)
if err != nil { if err != nil {
return err return err
} }
defer c.Close() defer c.Close()
opts := l.getAllOptions() opts := l.getOptionsWithClientId()
pkt, err := backoffRetry(l.resendMax, func() (*dhcp4.Packet, error) { pkt, err := backoffRetry(l.resendMax, func() (*dhcp4.Packet, error) {
ok, ack, err := DhcpRenew(c, *l.ack, opts) ok, ack, err := DhcpRenew(c, *l.ack, opts)
switch { switch {
@ -391,13 +379,13 @@ func (l *DHCPLease) renew() error {
func (l *DHCPLease) release() error { func (l *DHCPLease) release() error {
log.Printf("%v: releasing lease", l.clientID) log.Printf("%v: releasing lease", l.clientID)
c, err := newDHCPClient(l.link, l.timeout, l.broadcast) c, err := newDHCPClient(l.link, l.clientID, l.timeout, l.broadcast)
if err != nil { if err != nil {
return err return err
} }
defer c.Close() defer c.Close()
opts := l.getOptionsWithClientID() opts := l.getOptionsWithClientId()
if err = DhcpRelease(c, *l.ack, opts); err != nil { if err = DhcpRelease(c, *l.ack, opts); err != nil {
return fmt.Errorf("failed to send DHCPRELEASE") return fmt.Errorf("failed to send DHCPRELEASE")
@ -427,9 +415,9 @@ func (l *DHCPLease) Routes() []*types.Route {
// RFC 3442 states that if Classless Static Routes (option 121) // RFC 3442 states that if Classless Static Routes (option 121)
// exist, we ignore Static Routes (option 33) and the Router/Gateway. // exist, we ignore Static Routes (option 33) and the Router/Gateway.
opt121Routes := parseCIDRRoutes(l.opts) opt121_routes := parseCIDRRoutes(l.opts)
if len(opt121Routes) > 0 { if len(opt121_routes) > 0 {
return append(routes, opt121Routes...) return append(routes, opt121_routes...)
} }
// Append Static Routes // Append Static Routes
@ -451,9 +439,9 @@ func jitter(span time.Duration) time.Duration {
} }
func backoffRetry(resendMax time.Duration, f func() (*dhcp4.Packet, error)) (*dhcp4.Packet, error) { func backoffRetry(resendMax time.Duration, f func() (*dhcp4.Packet, error)) (*dhcp4.Packet, error) {
baseDelay := resendDelay0 var baseDelay time.Duration = resendDelay0
var sleepTime time.Duration var sleepTime time.Duration
fastRetryLimit := resendFastMax var fastRetryLimit = resendFastMax
for { for {
pkt, err := f() pkt, err := f()
if err == nil { if err == nil {
@ -485,7 +473,7 @@ func backoffRetry(resendMax time.Duration, f func() (*dhcp4.Packet, error)) (*dh
} }
func newDHCPClient( func newDHCPClient(
link netlink.Link, link netlink.Link, clientID string,
timeout time.Duration, timeout time.Duration,
broadcast bool, broadcast bool,
) (*dhcp4client.Client, error) { ) (*dhcp4client.Client, error) {

View File

@ -118,20 +118,27 @@ func cmdAdd(args *skel.CmdArgs) error {
func cmdDel(args *skel.CmdArgs) error { func cmdDel(args *skel.CmdArgs) error {
result := struct{}{} result := struct{}{}
return rpcCall("DHCP.Release", args, &result) if err := rpcCall("DHCP.Release", args, &result); err != nil {
return err
}
return nil
} }
func cmdCheck(args *skel.CmdArgs) error { func cmdCheck(args *skel.CmdArgs) error {
// Plugin must return result in same version as specified in netconf // Plugin must return result in same version as specified in netconf
versionDecoder := &version.ConfigDecoder{} versionDecoder := &version.ConfigDecoder{}
// confVersion, err := versionDecoder.Decode(args.StdinData) //confVersion, err := versionDecoder.Decode(args.StdinData)
_, err := versionDecoder.Decode(args.StdinData) _, err := versionDecoder.Decode(args.StdinData)
if err != nil { if err != nil {
return err return err
} }
result := &current.Result{CNIVersion: current.ImplementedSpecVersion} result := &current.Result{CNIVersion: current.ImplementedSpecVersion}
return rpcCall("DHCP.Allocate", args, result) if err := rpcCall("DHCP.Allocate", args, result); err != nil {
return err
}
return nil
} }
func getSocketPath(stdinData []byte) (string, error) { func getSocketPath(stdinData []byte) (string, error) {

View File

@ -21,9 +21,8 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/d2g/dhcp4"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
"github.com/d2g/dhcp4"
) )
var optionNameToID = map[string]dhcp4.OptionCode{ var optionNameToID = map[string]dhcp4.OptionCode{

View File

@ -19,9 +19,8 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/d2g/dhcp4"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
"github.com/d2g/dhcp4"
) )
func validateRoutes(t *testing.T, routes []*types.Route) { func validateRoutes(t *testing.T, routes []*types.Route) {

View File

@ -22,6 +22,7 @@ import (
"strconv" "strconv"
current "github.com/containernetworking/cni/pkg/types/100" current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/plugins/pkg/ip" "github.com/containernetworking/plugins/pkg/ip"
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend" "github.com/containernetworking/plugins/plugins/ipam/host-local/backend"
) )
@ -196,7 +197,7 @@ func (i *RangeIter) Next() (*net.IPNet, net.IP) {
// If we've reached the end of this range, we need to advance the range // If we've reached the end of this range, we need to advance the range
// RangeEnd is inclusive as well // RangeEnd is inclusive as well
if i.cur.Equal(r.RangeEnd) { if i.cur.Equal(r.RangeEnd) {
i.rangeIdx++ i.rangeIdx += 1
i.rangeIdx %= len(*i.rangeset) i.rangeIdx %= len(*i.rangeset)
r = (*i.rangeset)[i.rangeIdx] r = (*i.rangeset)[i.rangeIdx]

View File

@ -15,10 +15,10 @@
package allocator_test package allocator_test
import ( import (
"testing" . "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"testing"
) )
func TestAllocator(t *testing.T) { func TestAllocator(t *testing.T) {

View File

@ -18,12 +18,12 @@ import (
"fmt" "fmt"
"net" "net"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
current "github.com/containernetworking/cni/pkg/types/100" current "github.com/containernetworking/cni/pkg/types/100"
fakestore "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/testing" fakestore "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
) )
type AllocatorTestCase struct { type AllocatorTestCase struct {
@ -77,7 +77,7 @@ func (t AllocatorTestCase) run(idx int) (*current.IPConfig, error) {
p = append(p, Range{Subnet: types.IPNet(*subnet)}) p = append(p, Range{Subnet: types.IPNet(*subnet)})
} }
Expect(p.Canonicalize()).To(Succeed()) Expect(p.Canonicalize()).To(BeNil())
store := fakestore.NewFakeStore(t.ipmap, map[string]net.IP{"rangeid": net.ParseIP(t.lastIP)}) store := fakestore.NewFakeStore(t.ipmap, map[string]net.IP{"rangeid": net.ParseIP(t.lastIP)})
@ -262,6 +262,7 @@ var _ = Describe("host-local ip allocator", func() {
res, err = alloc.Get("ID", "eth0", nil) res, err = alloc.Get("ID", "eth0", nil)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(res.Address.String()).To(Equal("192.168.1.3/29")) Expect(res.Address.String()).To(Equal("192.168.1.3/29"))
}) })
Context("when requesting a specific IP", func() { Context("when requesting a specific IP", func() {
@ -300,6 +301,7 @@ var _ = Describe("host-local ip allocator", func() {
Expect(err).To(HaveOccurred()) Expect(err).To(HaveOccurred())
}) })
}) })
}) })
Context("when out of ips", func() { Context("when out of ips", func() {
It("returns a meaningful error", func() { It("returns a meaningful error", func() {
@ -330,7 +332,7 @@ var _ = Describe("host-local ip allocator", func() {
} }
for idx, tc := range testCases { for idx, tc := range testCases {
_, err := tc.run(idx) _, err := tc.run(idx)
Expect(err).To(HaveOccurred()) Expect(err).NotTo(BeNil())
Expect(err.Error()).To(HavePrefix("no IP addresses available in range set")) Expect(err.Error()).To(HavePrefix("no IP addresses available in range set"))
} }
}) })

View File

@ -21,6 +21,7 @@ import (
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/version" "github.com/containernetworking/cni/pkg/version"
"github.com/containernetworking/plugins/pkg/ip" "github.com/containernetworking/plugins/pkg/ip"
) )
@ -42,7 +43,7 @@ type Net struct {
// IPAMConfig represents the IP related network configuration. // IPAMConfig represents the IP related network configuration.
// This nests Range because we initially only supported a single // This nests Range because we initially only supported a single
// range directly, and wish to preserve backwards compatibility // range directly, and wish to preserve backwards compatability
type IPAMConfig struct { type IPAMConfig struct {
*Range *Range
Name string Name string

View File

@ -17,10 +17,9 @@ package allocator
import ( import (
"net" "net"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
) )
var _ = Describe("IPAM config", func() { var _ = Describe("IPAM config", func() {
@ -416,6 +415,7 @@ var _ = Describe("IPAM config", func() {
}` }`
_, _, err := LoadIPAMConfig([]byte(input), "") _, _, err := LoadIPAMConfig([]byte(input), "")
Expect(err).To(MatchError("invalid range set 0: mixed address families")) Expect(err).To(MatchError("invalid range set 0: mixed address families"))
}) })
It("Should should error on too many ranges", func() { It("Should should error on too many ranges", func() {

View File

@ -125,7 +125,7 @@ func (r *Range) Contains(addr net.IP) bool {
// Overlaps returns true if there is any overlap between ranges // Overlaps returns true if there is any overlap between ranges
func (r *Range) Overlaps(r1 *Range) bool { func (r *Range) Overlaps(r1 *Range) bool {
// different families // different familes
if len(r.RangeStart) != len(r1.RangeStart) { if len(r.RangeStart) != len(r1.RangeStart) {
return false return false
} }

View File

@ -67,8 +67,10 @@ func (s *RangeSet) Canonicalize() error {
} }
if i == 0 { if i == 0 {
fam = len((*s)[i].RangeStart) fam = len((*s)[i].RangeStart)
} else if fam != len((*s)[i].RangeStart) { } else {
return fmt.Errorf("mixed address families") if fam != len((*s)[i].RangeStart) {
return fmt.Errorf("mixed address families")
}
} }
} }

View File

@ -17,7 +17,7 @@ package allocator
import ( import (
"net" "net"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )
@ -40,6 +40,7 @@ var _ = Describe("range sets", func() {
r, err = p.RangeFor(net.IP{192, 168, 99, 99}) r, err = p.RangeFor(net.IP{192, 168, 99, 99})
Expect(r).To(BeNil()) Expect(r).To(BeNil())
Expect(err).To(MatchError("192.168.99.99 not in range set 192.168.0.1-192.168.0.254,172.16.1.1-172.16.1.254")) Expect(err).To(MatchError("192.168.99.99 not in range set 192.168.0.1-192.168.0.254,172.16.1.1-172.16.1.254"))
}) })
It("should discover overlaps within a set", func() { It("should discover overlaps within a set", func() {

View File

@ -17,10 +17,11 @@ package allocator
import ( import (
"net" "net"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega"
) )
var _ = Describe("IP ranges", func() { var _ = Describe("IP ranges", func() {

View File

@ -15,6 +15,7 @@
package disk package disk
import ( import (
"io/ioutil"
"net" "net"
"os" "os"
"path/filepath" "path/filepath"
@ -24,10 +25,8 @@ import (
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend" "github.com/containernetworking/plugins/plugins/ipam/host-local/backend"
) )
const ( const lastIPFilePrefix = "last_reserved_ip."
lastIPFilePrefix = "last_reserved_ip." const LineBreak = "\r\n"
LineBreak = "\r\n"
)
var defaultDataDir = "/var/lib/cni/networks" var defaultDataDir = "/var/lib/cni/networks"
@ -46,7 +45,7 @@ func New(network, dataDir string) (*Store, error) {
dataDir = defaultDataDir dataDir = defaultDataDir
} }
dir := filepath.Join(dataDir, network) dir := filepath.Join(dataDir, network)
if err := os.MkdirAll(dir, 0o755); err != nil { if err := os.MkdirAll(dir, 0755); err != nil {
return nil, err return nil, err
} }
@ -60,7 +59,7 @@ func New(network, dataDir string) (*Store, error) {
func (s *Store) Reserve(id string, ifname string, ip net.IP, rangeID string) (bool, error) { func (s *Store) Reserve(id string, ifname string, ip net.IP, rangeID string) (bool, error) {
fname := GetEscapedPath(s.dataDir, ip.String()) fname := GetEscapedPath(s.dataDir, ip.String())
f, err := os.OpenFile(fname, os.O_RDWR|os.O_EXCL|os.O_CREATE, 0o600) f, err := os.OpenFile(fname, os.O_RDWR|os.O_EXCL|os.O_CREATE, 0644)
if os.IsExist(err) { if os.IsExist(err) {
return false, nil return false, nil
} }
@ -78,7 +77,7 @@ func (s *Store) Reserve(id string, ifname string, ip net.IP, rangeID string) (bo
} }
// store the reserved ip in lastIPFile // store the reserved ip in lastIPFile
ipfile := GetEscapedPath(s.dataDir, lastIPFilePrefix+rangeID) ipfile := GetEscapedPath(s.dataDir, lastIPFilePrefix+rangeID)
err = os.WriteFile(ipfile, []byte(ip.String()), 0o600) err = ioutil.WriteFile(ipfile, []byte(ip.String()), 0644)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -88,21 +87,25 @@ func (s *Store) Reserve(id string, ifname string, ip net.IP, rangeID string) (bo
// LastReservedIP returns the last reserved IP if exists // LastReservedIP returns the last reserved IP if exists
func (s *Store) LastReservedIP(rangeID string) (net.IP, error) { func (s *Store) LastReservedIP(rangeID string) (net.IP, error) {
ipfile := GetEscapedPath(s.dataDir, lastIPFilePrefix+rangeID) ipfile := GetEscapedPath(s.dataDir, lastIPFilePrefix+rangeID)
data, err := os.ReadFile(ipfile) data, err := ioutil.ReadFile(ipfile)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return net.ParseIP(string(data)), nil return net.ParseIP(string(data)), nil
} }
func (s *Store) FindByKey(match string) (bool, error) { func (s *Store) Release(ip net.IP) error {
return os.Remove(GetEscapedPath(s.dataDir, ip.String()))
}
func (s *Store) FindByKey(id string, ifname string, match string) (bool, error) {
found := false found := false
err := filepath.Walk(s.dataDir, func(path string, info os.FileInfo, err error) error { err := filepath.Walk(s.dataDir, func(path string, info os.FileInfo, err error) error {
if err != nil || info.IsDir() { if err != nil || info.IsDir() {
return nil return nil
} }
data, err := os.ReadFile(path) data, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
return nil return nil
} }
@ -112,31 +115,33 @@ func (s *Store) FindByKey(match string) (bool, error) {
return nil return nil
}) })
return found, err return found, err
} }
func (s *Store) FindByID(id string, ifname string) bool { func (s *Store) FindByID(id string, ifname string) bool {
s.Lock() s.Lock()
defer s.Unlock() defer s.Unlock()
found := false
match := strings.TrimSpace(id) + LineBreak + ifname match := strings.TrimSpace(id) + LineBreak + ifname
found, err := s.FindByKey(match) found, err := s.FindByKey(id, ifname, match)
// Match anything created by this id // Match anything created by this id
if !found && err == nil { if !found && err == nil {
match := strings.TrimSpace(id) match := strings.TrimSpace(id)
found, _ = s.FindByKey(match) found, err = s.FindByKey(id, ifname, match)
} }
return found return found
} }
func (s *Store) ReleaseByKey(match string) (bool, error) { func (s *Store) ReleaseByKey(id string, ifname string, match string) (bool, error) {
found := false found := false
err := filepath.Walk(s.dataDir, func(path string, info os.FileInfo, err error) error { err := filepath.Walk(s.dataDir, func(path string, info os.FileInfo, err error) error {
if err != nil || info.IsDir() { if err != nil || info.IsDir() {
return nil return nil
} }
data, err := os.ReadFile(path) data, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
return nil return nil
} }
@ -149,18 +154,20 @@ func (s *Store) ReleaseByKey(match string) (bool, error) {
return nil return nil
}) })
return found, err return found, err
} }
// N.B. This function eats errors to be tolerant and // N.B. This function eats errors to be tolerant and
// release as much as possible // release as much as possible
func (s *Store) ReleaseByID(id string, ifname string) error { func (s *Store) ReleaseByID(id string, ifname string) error {
found := false
match := strings.TrimSpace(id) + LineBreak + ifname match := strings.TrimSpace(id) + LineBreak + ifname
found, err := s.ReleaseByKey(match) found, err := s.ReleaseByKey(id, ifname, match)
// For backwards compatibility, look for files written by a previous version // For backwards compatibility, look for files written by a previous version
if !found && err == nil { if !found && err == nil {
match := strings.TrimSpace(id) match := strings.TrimSpace(id)
_, err = s.ReleaseByKey(match) found, err = s.ReleaseByKey(id, ifname, match)
} }
return err return err
} }
@ -178,7 +185,7 @@ func (s *Store) GetByID(id string, ifname string) []net.IP {
if err != nil || info.IsDir() { if err != nil || info.IsDir() {
return nil return nil
} }
data, err := os.ReadFile(path) data, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
return nil return nil
} }
@ -196,7 +203,7 @@ func (s *Store) GetByID(id string, ifname string) []net.IP {
func GetEscapedPath(dataDir string, fname string) string { func GetEscapedPath(dataDir string, fname string) string {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
fname = strings.ReplaceAll(fname, ":", "_") fname = strings.Replace(fname, ":", "_", -1)
} }
return filepath.Join(dataDir, fname) return filepath.Join(dataDir, fname)
} }

View File

@ -15,10 +15,10 @@
package disk package disk
import ( import (
"testing" . "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"testing"
) )
func TestLock(t *testing.T) { func TestLock(t *testing.T) {

View File

@ -15,10 +15,9 @@
package disk package disk
import ( import (
"github.com/alexflint/go-filemutex"
"os" "os"
"path" "path"
"github.com/alexflint/go-filemutex"
) )
// FileLock wraps os.File to be used as a lock using flock // FileLock wraps os.File to be used as a lock using flock

View File

@ -15,22 +15,23 @@
package disk package disk
import ( import (
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )
var _ = Describe("Lock Operations", func() { var _ = Describe("Lock Operations", func() {
It("locks a file path", func() { It("locks a file path", func() {
dir, err := os.MkdirTemp("", "") dir, err := ioutil.TempDir("", "")
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
defer os.RemoveAll(dir) defer os.RemoveAll(dir)
// create a dummy file to lock // create a dummy file to lock
path := filepath.Join(dir, "x") path := filepath.Join(dir, "x")
f, err := os.OpenFile(path, os.O_RDONLY|os.O_CREATE, 0o666) f, err := os.OpenFile(path, os.O_RDONLY|os.O_CREATE, 0666)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
err = f.Close() err = f.Close()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
@ -46,7 +47,7 @@ var _ = Describe("Lock Operations", func() {
}) })
It("locks a folder path", func() { It("locks a folder path", func() {
dir, err := os.MkdirTemp("", "") dir, err := ioutil.TempDir("", "")
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
defer os.RemoveAll(dir) defer os.RemoveAll(dir)

View File

@ -22,6 +22,7 @@ type Store interface {
Close() error Close() error
Reserve(id string, ifname string, ip net.IP, rangeID string) (bool, error) Reserve(id string, ifname string, ip net.IP, rangeID string) (bool, error)
LastReservedIP(rangeID string) (net.IP, error) LastReservedIP(rangeID string) (net.IP, error)
Release(ip net.IP) error
ReleaseByID(id string, ifname string) error ReleaseByID(id string, ifname string) error
GetByID(id string, ifname string) []net.IP GetByID(id string, ifname string) []net.IP
} }

View File

@ -45,7 +45,7 @@ func (s *FakeStore) Close() error {
return nil return nil
} }
func (s *FakeStore) Reserve(id string, _ string, ip net.IP, rangeID string) (bool, error) { func (s *FakeStore) Reserve(id string, ifname string, ip net.IP, rangeID string) (bool, error) {
key := ip.String() key := ip.String()
if _, ok := s.ipMap[key]; !ok { if _, ok := s.ipMap[key]; !ok {
s.ipMap[key] = id s.ipMap[key] = id
@ -63,7 +63,12 @@ func (s *FakeStore) LastReservedIP(rangeID string) (net.IP, error) {
return ip, nil return ip, nil
} }
func (s *FakeStore) ReleaseByID(id string, _ string) error { func (s *FakeStore) Release(ip net.IP) error {
delete(s.ipMap, ip.String())
return nil
}
func (s *FakeStore) ReleaseByID(id string, ifname string) error {
toDelete := []string{} toDelete := []string{}
for k, v := range s.ipMap { for k, v := range s.ipMap {
if v == id { if v == id {
@ -76,7 +81,7 @@ func (s *FakeStore) ReleaseByID(id string, _ string) error {
return nil return nil
} }
func (s *FakeStore) GetByID(id string, _ string) []net.IP { func (s *FakeStore) GetByID(id string, ifname string) []net.IP {
var ips []net.IP var ips []net.IP
for k, v := range s.ipMap { for k, v := range s.ipMap {
if v == id { if v == id {

View File

@ -28,7 +28,6 @@ func parseResolvConf(filename string) (*types.DNS, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer fp.Close()
dns := types.DNS{} dns := types.DNS{}
scanner := bufio.NewScanner(fp) scanner := bufio.NewScanner(fp)

View File

@ -15,12 +15,12 @@
package main package main
import ( import (
"io/ioutil"
"os" "os"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
) )
var _ = Describe("parsing resolv.conf", func() { var _ = Describe("parsing resolv.conf", func() {
@ -64,7 +64,7 @@ options four
}) })
func parse(contents string) (*types.DNS, error) { func parse(contents string) (*types.DNS, error) {
f, err := os.CreateTemp("", "host_local_resolv") f, err := ioutil.TempFile("", "host_local_resolv")
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -15,10 +15,10 @@
package main package main
import ( import (
"testing" . "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"testing"
) )
func TestHostLocal(t *testing.T) { func TestHostLocal(t *testing.T) {

View File

@ -16,19 +16,20 @@ package main
import ( import (
"fmt" "fmt"
"io/ioutil"
"net" "net"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
types100 "github.com/containernetworking/cni/pkg/types/100" "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/plugins/pkg/testutils" "github.com/containernetworking/plugins/pkg/testutils"
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/disk" "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/disk"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
) )
const LineBreak = "\r\n" const LineBreak = "\r\n"
@ -42,7 +43,7 @@ var _ = Describe("host-local Operations", func() {
BeforeEach(func() { BeforeEach(func() {
var err error var err error
tmpDir, err = os.MkdirTemp("", "host-local_test") tmpDir, err = ioutil.TempDir("", "host-local_test")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
tmpDir = filepath.ToSlash(tmpDir) tmpDir = filepath.ToSlash(tmpDir)
}) })
@ -57,7 +58,7 @@ var _ = Describe("host-local Operations", func() {
ver := ver ver := ver
It(fmt.Sprintf("[%s] allocates and releases addresses with ADD/DEL", ver), func() { It(fmt.Sprintf("[%s] allocates and releases addresses with ADD/DEL", ver), func() {
err := os.WriteFile(filepath.Join(tmpDir, "resolv.conf"), []byte("nameserver 192.0.2.3"), 0o644) err := ioutil.WriteFile(filepath.Join(tmpDir, "resolv.conf"), []byte("nameserver 192.0.2.3"), 0644)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
conf := fmt.Sprintf(`{ conf := fmt.Sprintf(`{
@ -114,7 +115,7 @@ var _ = Describe("host-local Operations", func() {
Gateway: net.ParseIP("2001:db8:1::1"), Gateway: net.ParseIP("2001:db8:1::1"),
}, },
)) ))
Expect(result.IPs).To(HaveLen(2)) Expect(len(result.IPs)).To(Equal(2))
for _, expectedRoute := range []*types.Route{ for _, expectedRoute := range []*types.Route{
{Dst: mustCIDR("0.0.0.0/0"), GW: nil}, {Dst: mustCIDR("0.0.0.0/0"), GW: nil},
@ -133,22 +134,22 @@ var _ = Describe("host-local Operations", func() {
} }
ipFilePath1 := filepath.Join(tmpDir, "mynet", "10.1.2.2") ipFilePath1 := filepath.Join(tmpDir, "mynet", "10.1.2.2")
contents, err := os.ReadFile(ipFilePath1) contents, err := ioutil.ReadFile(ipFilePath1)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(string(contents)).To(Equal(args.ContainerID + LineBreak + ifname)) Expect(string(contents)).To(Equal(args.ContainerID + LineBreak + ifname))
ipFilePath2 := filepath.Join(tmpDir, disk.GetEscapedPath("mynet", "2001:db8:1::2")) ipFilePath2 := filepath.Join(tmpDir, disk.GetEscapedPath("mynet", "2001:db8:1::2"))
contents, err = os.ReadFile(ipFilePath2) contents, err = ioutil.ReadFile(ipFilePath2)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(string(contents)).To(Equal(args.ContainerID + LineBreak + ifname)) Expect(string(contents)).To(Equal(args.ContainerID + LineBreak + ifname))
lastFilePath1 := filepath.Join(tmpDir, "mynet", "last_reserved_ip.0") lastFilePath1 := filepath.Join(tmpDir, "mynet", "last_reserved_ip.0")
contents, err = os.ReadFile(lastFilePath1) contents, err = ioutil.ReadFile(lastFilePath1)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(string(contents)).To(Equal("10.1.2.2")) Expect(string(contents)).To(Equal("10.1.2.2"))
lastFilePath2 := filepath.Join(tmpDir, "mynet", "last_reserved_ip.1") lastFilePath2 := filepath.Join(tmpDir, "mynet", "last_reserved_ip.1")
contents, err = os.ReadFile(lastFilePath2) contents, err = ioutil.ReadFile(lastFilePath2)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(string(contents)).To(Equal("2001:db8:1::2")) Expect(string(contents)).To(Equal("2001:db8:1::2"))
// Release the IP // Release the IP
@ -166,7 +167,7 @@ var _ = Describe("host-local Operations", func() {
It(fmt.Sprintf("[%s] allocates and releases addresses on specific interface with ADD/DEL", ver), func() { It(fmt.Sprintf("[%s] allocates and releases addresses on specific interface with ADD/DEL", ver), func() {
const ifname1 string = "eth1" const ifname1 string = "eth1"
err := os.WriteFile(filepath.Join(tmpDir, "resolv.conf"), []byte("nameserver 192.0.2.3"), 0o644) err := ioutil.WriteFile(filepath.Join(tmpDir, "resolv.conf"), []byte("nameserver 192.0.2.3"), 0644)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
conf0 := fmt.Sprintf(`{ conf0 := fmt.Sprintf(`{
@ -238,12 +239,12 @@ var _ = Describe("host-local Operations", func() {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
ipFilePath0 := filepath.Join(tmpDir, "mynet0", "10.1.2.2") ipFilePath0 := filepath.Join(tmpDir, "mynet0", "10.1.2.2")
contents, err := os.ReadFile(ipFilePath0) contents, err := ioutil.ReadFile(ipFilePath0)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(string(contents)).To(Equal(args0.ContainerID + LineBreak + ifname)) Expect(string(contents)).To(Equal(args0.ContainerID + LineBreak + ifname))
ipFilePath1 := filepath.Join(tmpDir, "mynet1", "10.2.2.2") ipFilePath1 := filepath.Join(tmpDir, "mynet1", "10.2.2.2")
contents, err = os.ReadFile(ipFilePath1) contents, err = ioutil.ReadFile(ipFilePath1)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(string(contents)).To(Equal(args1.ContainerID + LineBreak + ifname1)) Expect(string(contents)).To(Equal(args1.ContainerID + LineBreak + ifname1))
@ -256,7 +257,7 @@ var _ = Describe("host-local Operations", func() {
Expect(err).To(HaveOccurred()) Expect(err).To(HaveOccurred())
// reread ipFilePath1, ensure that ifname1 didn't get deleted // reread ipFilePath1, ensure that ifname1 didn't get deleted
contents, err = os.ReadFile(ipFilePath1) contents, err = ioutil.ReadFile(ipFilePath1)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(string(contents)).To(Equal(args1.ContainerID + LineBreak + ifname1)) Expect(string(contents)).To(Equal(args1.ContainerID + LineBreak + ifname1))
@ -310,7 +311,7 @@ var _ = Describe("host-local Operations", func() {
result0, err := types100.GetResult(r0) result0, err := types100.GetResult(r0)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(result0.IPs).Should(HaveLen(1)) Expect(len(result0.IPs)).Should(Equal(1))
Expect(result0.IPs[0].Address.String()).Should(Equal("10.1.2.2/24")) Expect(result0.IPs[0].Address.String()).Should(Equal("10.1.2.2/24"))
// Allocate the IP with the same container ID // Allocate the IP with the same container ID
@ -330,7 +331,7 @@ var _ = Describe("host-local Operations", func() {
result1, err := types100.GetResult(r1) result1, err := types100.GetResult(r1)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(result1.IPs).Should(HaveLen(1)) Expect(len(result1.IPs)).Should(Equal(1))
Expect(result1.IPs[0].Address.String()).Should(Equal("10.1.2.3/24")) Expect(result1.IPs[0].Address.String()).Should(Equal("10.1.2.3/24"))
// Allocate the IP with the same container ID again // Allocate the IP with the same container ID again
@ -356,7 +357,7 @@ var _ = Describe("host-local Operations", func() {
}) })
It(fmt.Sprintf("[%s] verify DEL works on backwards compatible allocate", ver), func() { It(fmt.Sprintf("[%s] verify DEL works on backwards compatible allocate", ver), func() {
err := os.WriteFile(filepath.Join(tmpDir, "resolv.conf"), []byte("nameserver 192.0.2.3"), 0o644) err := ioutil.WriteFile(filepath.Join(tmpDir, "resolv.conf"), []byte("nameserver 192.0.2.3"), 0644)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
conf := fmt.Sprintf(`{ conf := fmt.Sprintf(`{
@ -394,10 +395,10 @@ var _ = Describe("host-local Operations", func() {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
ipFilePath := filepath.Join(tmpDir, "mynet", "10.1.2.2") ipFilePath := filepath.Join(tmpDir, "mynet", "10.1.2.2")
contents, err := os.ReadFile(ipFilePath) contents, err := ioutil.ReadFile(ipFilePath)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(string(contents)).To(Equal(args.ContainerID + LineBreak + ifname)) Expect(string(contents)).To(Equal(args.ContainerID + LineBreak + ifname))
err = os.WriteFile(ipFilePath, []byte(strings.TrimSpace(args.ContainerID)), 0o644) err = ioutil.WriteFile(ipFilePath, []byte(strings.TrimSpace(args.ContainerID)), 0644)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
err = testutils.CmdDelWithArgs(args, func() error { err = testutils.CmdDelWithArgs(args, func() error {
@ -465,7 +466,7 @@ var _ = Describe("host-local Operations", func() {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
ipFilePath := filepath.Join(tmpDir, "mynet", result.IPs[0].Address.IP.String()) ipFilePath := filepath.Join(tmpDir, "mynet", result.IPs[0].Address.IP.String())
contents, err := os.ReadFile(ipFilePath) contents, err := ioutil.ReadFile(ipFilePath)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(string(contents)).To(Equal("dummy" + LineBreak + ifname)) Expect(string(contents)).To(Equal("dummy" + LineBreak + ifname))
@ -504,7 +505,7 @@ var _ = Describe("host-local Operations", func() {
return cmdAdd(args) return cmdAdd(args)
}) })
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(strings.Index(string(out), "Error retrieving last reserved ip")).To(Equal(-1)) Expect(strings.Index(string(out), "Error retriving last reserved ip")).To(Equal(-1))
}) })
It(fmt.Sprintf("[%s] allocates a custom IP when requested by config args", ver), func() { It(fmt.Sprintf("[%s] allocates a custom IP when requested by config args", ver), func() {
@ -546,7 +547,7 @@ var _ = Describe("host-local Operations", func() {
}) })
It(fmt.Sprintf("[%s] allocates custom IPs from multiple ranges", ver), func() { It(fmt.Sprintf("[%s] allocates custom IPs from multiple ranges", ver), func() {
err := os.WriteFile(filepath.Join(tmpDir, "resolv.conf"), []byte("nameserver 192.0.2.3"), 0o644) err := ioutil.WriteFile(filepath.Join(tmpDir, "resolv.conf"), []byte("nameserver 192.0.2.3"), 0644)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
conf := fmt.Sprintf(`{ conf := fmt.Sprintf(`{
@ -594,7 +595,7 @@ var _ = Describe("host-local Operations", func() {
}) })
It(fmt.Sprintf("[%s] allocates custom IPs from multiple protocols", ver), func() { It(fmt.Sprintf("[%s] allocates custom IPs from multiple protocols", ver), func() {
err := os.WriteFile(filepath.Join(tmpDir, "resolv.conf"), []byte("nameserver 192.0.2.3"), 0o644) err := ioutil.WriteFile(filepath.Join(tmpDir, "resolv.conf"), []byte("nameserver 192.0.2.3"), 0644)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
conf := fmt.Sprintf(`{ conf := fmt.Sprintf(`{

View File

@ -19,13 +19,14 @@ import (
"net" "net"
"strings" "strings"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/disk"
"github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
current "github.com/containernetworking/cni/pkg/types/100" current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/pkg/version" "github.com/containernetworking/cni/pkg/version"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/disk"
) )
func main() { func main() {
@ -33,6 +34,7 @@ func main() {
} }
func cmdCheck(args *skel.CmdArgs) error { func cmdCheck(args *skel.CmdArgs) error {
ipamConf, _, err := allocator.LoadIPAMConfig(args.StdinData, args.Args) ipamConf, _, err := allocator.LoadIPAMConfig(args.StdinData, args.Args)
if err != nil { if err != nil {
return err return err
@ -46,8 +48,8 @@ func cmdCheck(args *skel.CmdArgs) error {
} }
defer store.Close() defer store.Close()
containerIPFound := store.FindByID(args.ContainerID, args.IfName) containerIpFound := store.FindByID(args.ContainerID, args.IfName)
if !containerIPFound { if containerIpFound == false {
return fmt.Errorf("host-local: Failed to find address added by container %v", args.ContainerID) return fmt.Errorf("host-local: Failed to find address added by container %v", args.ContainerID)
} }
@ -82,7 +84,7 @@ func cmdAdd(args *skel.CmdArgs) error {
// Store all requested IPs in a map, so we can easily remove ones we use // Store all requested IPs in a map, so we can easily remove ones we use
// and error if some remain // and error if some remain
requestedIPs := map[string]net.IP{} // net.IP cannot be a key requestedIPs := map[string]net.IP{} //net.IP cannot be a key
for _, ip := range ipamConf.IPArgs { for _, ip := range ipamConf.IPArgs {
requestedIPs[ip.String()] = ip requestedIPs[ip.String()] = ip

View File

@ -276,7 +276,7 @@ func cmdAdd(args *skel.CmdArgs) error {
return types.PrintResult(result, confVersion) return types.PrintResult(result, confVersion)
} }
func cmdDel(_ *skel.CmdArgs) error { func cmdDel(args *skel.CmdArgs) error {
// Nothing required because of no resource allocation in static plugin. // Nothing required because of no resource allocation in static plugin.
return nil return nil
} }

View File

@ -17,7 +17,7 @@ package main_test
import ( import (
"testing" "testing"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )

View File

@ -19,13 +19,13 @@ import (
"net" "net"
"strings" "strings"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
types100 "github.com/containernetworking/cni/pkg/types/100" types100 "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/plugins/pkg/testutils" "github.com/containernetworking/plugins/pkg/testutils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
) )
var _ = Describe("static Operations", func() { var _ = Describe("static Operations", func() {
@ -97,7 +97,7 @@ var _ = Describe("static Operations", func() {
Gateway: net.ParseIP("3ffe:ffff:0::1"), Gateway: net.ParseIP("3ffe:ffff:0::1"),
}, },
)) ))
Expect(result.IPs).To(HaveLen(2)) Expect(len(result.IPs)).To(Equal(2))
Expect(result.Routes).To(Equal([]*types.Route{ Expect(result.Routes).To(Equal([]*types.Route{
{Dst: mustCIDR("0.0.0.0/0")}, {Dst: mustCIDR("0.0.0.0/0")},
@ -206,7 +206,7 @@ var _ = Describe("static Operations", func() {
Gateway: net.ParseIP("10.10.0.254"), Gateway: net.ParseIP("10.10.0.254"),
})) }))
Expect(result.IPs).To(HaveLen(1)) Expect(len(result.IPs)).To(Equal(1))
Expect(result.Routes).To(Equal([]*types.Route{ Expect(result.Routes).To(Equal([]*types.Route{
{Dst: mustCIDR("0.0.0.0/0")}, {Dst: mustCIDR("0.0.0.0/0")},
@ -272,7 +272,7 @@ var _ = Describe("static Operations", func() {
Gateway: nil, Gateway: nil,
})) }))
Expect(result.IPs).To(HaveLen(2)) Expect(len(result.IPs)).To(Equal(2))
// Release the IP // Release the IP
err = testutils.CmdDelWithArgs(args, func() error { err = testutils.CmdDelWithArgs(args, func() error {
@ -337,7 +337,7 @@ var _ = Describe("static Operations", func() {
Address: mustCIDR("3ffe:ffff:0:01ff::1/64"), Address: mustCIDR("3ffe:ffff:0:01ff::1/64"),
}, },
)) ))
Expect(result.IPs).To(HaveLen(2)) Expect(len(result.IPs)).To(Equal(2))
Expect(result.Routes).To(Equal([]*types.Route{ Expect(result.Routes).To(Equal([]*types.Route{
{Dst: mustCIDR("0.0.0.0/0"), GW: net.ParseIP("10.10.0.254")}, {Dst: mustCIDR("0.0.0.0/0"), GW: net.ParseIP("10.10.0.254")},
{Dst: mustCIDR("3ffe:ffff:0:01ff::1/64"), GW: net.ParseIP("3ffe:ffff:0::1")}, {Dst: mustCIDR("3ffe:ffff:0:01ff::1/64"), GW: net.ParseIP("3ffe:ffff:0::1")},
@ -407,7 +407,7 @@ var _ = Describe("static Operations", func() {
Address: mustCIDR("3ffe:ffff:0:01ff::1/64"), Address: mustCIDR("3ffe:ffff:0:01ff::1/64"),
}, },
)) ))
Expect(result.IPs).To(HaveLen(2)) Expect(len(result.IPs)).To(Equal(2))
Expect(result.Routes).To(Equal([]*types.Route{ Expect(result.Routes).To(Equal([]*types.Route{
{Dst: mustCIDR("0.0.0.0/0"), GW: net.ParseIP("10.10.0.254")}, {Dst: mustCIDR("0.0.0.0/0"), GW: net.ParseIP("10.10.0.254")},
{Dst: mustCIDR("3ffe:ffff:0:01ff::1/64"), GW: net.ParseIP("3ffe:ffff:0::1")}, {Dst: mustCIDR("3ffe:ffff:0:01ff::1/64"), GW: net.ParseIP("3ffe:ffff:0::1")},
@ -482,7 +482,7 @@ var _ = Describe("static Operations", func() {
Address: mustCIDR("3ffe:ffff:0:01ff::1/64"), Address: mustCIDR("3ffe:ffff:0:01ff::1/64"),
}, },
)) ))
Expect(result.IPs).To(HaveLen(2)) Expect(len(result.IPs)).To(Equal(2))
Expect(result.Routes).To(Equal([]*types.Route{ Expect(result.Routes).To(Equal([]*types.Route{
{Dst: mustCIDR("0.0.0.0/0"), GW: net.ParseIP("10.10.0.254")}, {Dst: mustCIDR("0.0.0.0/0"), GW: net.ParseIP("10.10.0.254")},
{Dst: mustCIDR("3ffe:ffff:0:01ff::1/64"), GW: net.ParseIP("3ffe:ffff:0::1")}, {Dst: mustCIDR("3ffe:ffff:0:01ff::1/64"), GW: net.ParseIP("3ffe:ffff:0::1")},

View File

@ -6,7 +6,6 @@ plugins/main/loopback
plugins/main/macvlan plugins/main/macvlan
plugins/main/ptp plugins/main/ptp
plugins/main/vlan plugins/main/vlan
plugins/main/dummy
plugins/meta/portmap plugins/meta/portmap
plugins/meta/tuning plugins/meta/tuning
plugins/meta/bandwidth plugins/meta/bandwidth

View File

@ -21,7 +21,6 @@ import (
"net" "net"
"os" "os"
"runtime" "runtime"
"sort"
"syscall" "syscall"
"time" "time"
@ -47,20 +46,17 @@ const defaultBrName = "cni0"
type NetConf struct { type NetConf struct {
types.NetConf types.NetConf
BrName string `json:"bridge"` BrName string `json:"bridge"`
IsGW bool `json:"isGateway"` IsGW bool `json:"isGateway"`
IsDefaultGW bool `json:"isDefaultGateway"` IsDefaultGW bool `json:"isDefaultGateway"`
ForceAddress bool `json:"forceAddress"` ForceAddress bool `json:"forceAddress"`
IPMasq bool `json:"ipMasq"` IPMasq bool `json:"ipMasq"`
MTU int `json:"mtu"` MTU int `json:"mtu"`
HairpinMode bool `json:"hairpinMode"` HairpinMode bool `json:"hairpinMode"`
PromiscMode bool `json:"promiscMode"` PromiscMode bool `json:"promiscMode"`
Vlan int `json:"vlan"` Vlan int `json:"vlan"`
VlanTrunk []*VlanTrunk `json:"vlanTrunk,omitempty"` MacSpoofChk bool `json:"macspoofchk,omitempty"`
PreserveDefaultVlan bool `json:"preserveDefaultVlan"` EnableDad bool `json:"enabledad,omitempty"`
MacSpoofChk bool `json:"macspoofchk,omitempty"`
EnableDad bool `json:"enabledad,omitempty"`
DisableContainerInterface bool `json:"disableContainerInterface,omitempty"`
Args struct { Args struct {
Cni BridgeArgs `json:"cni,omitempty"` Cni BridgeArgs `json:"cni,omitempty"`
@ -69,14 +65,7 @@ type NetConf struct {
Mac string `json:"mac,omitempty"` Mac string `json:"mac,omitempty"`
} `json:"runtimeConfig,omitempty"` } `json:"runtimeConfig,omitempty"`
mac string mac string
vlans []int
}
type VlanTrunk struct {
MinID *int `json:"minID,omitempty"`
MaxID *int `json:"maxID,omitempty"`
ID *int `json:"id,omitempty"`
} }
type BridgeArgs struct { type BridgeArgs struct {
@ -105,8 +94,6 @@ func init() {
func loadNetConf(bytes []byte, envArgs string) (*NetConf, string, error) { func loadNetConf(bytes []byte, envArgs string) (*NetConf, string, error) {
n := &NetConf{ n := &NetConf{
BrName: defaultBrName, BrName: defaultBrName,
// Set default value equal to true to maintain existing behavior.
PreserveDefaultVlan: true,
} }
if err := json.Unmarshal(bytes, n); err != nil { if err := json.Unmarshal(bytes, n); err != nil {
return nil, "", fmt.Errorf("failed to load netconf: %v", err) return nil, "", fmt.Errorf("failed to load netconf: %v", err)
@ -114,17 +101,6 @@ func loadNetConf(bytes []byte, envArgs string) (*NetConf, string, error) {
if n.Vlan < 0 || n.Vlan > 4094 { if n.Vlan < 0 || n.Vlan > 4094 {
return nil, "", fmt.Errorf("invalid VLAN ID %d (must be between 0 and 4094)", n.Vlan) return nil, "", fmt.Errorf("invalid VLAN ID %d (must be between 0 and 4094)", n.Vlan)
} }
var err error
n.vlans, err = collectVlanTrunk(n.VlanTrunk)
if err != nil {
// fail to parsing
return nil, "", err
}
// Currently bridge CNI only support access port(untagged only) or trunk port(tagged only)
if n.Vlan > 0 && n.vlans != nil {
return nil, "", errors.New("cannot set vlan and vlanTrunk at the same time")
}
if envArgs != "" { if envArgs != "" {
e := MacEnvArgs{} e := MacEnvArgs{}
@ -148,66 +124,12 @@ func loadNetConf(bytes []byte, envArgs string) (*NetConf, string, error) {
return n, n.CNIVersion, nil return n, n.CNIVersion, nil
} }
// This method is copied from https://github.com/k8snetworkplumbingwg/ovs-cni/blob/v0.27.2/pkg/plugin/plugin.go
func collectVlanTrunk(vlanTrunk []*VlanTrunk) ([]int, error) {
if vlanTrunk == nil {
return nil, nil
}
vlanMap := make(map[int]struct{})
for _, item := range vlanTrunk {
var minID int
var maxID int
var ID int
switch {
case item.MinID != nil && item.MaxID != nil:
minID = *item.MinID
if minID <= 0 || minID > 4094 {
return nil, errors.New("incorrect trunk minID parameter")
}
maxID = *item.MaxID
if maxID <= 0 || maxID > 4094 {
return nil, errors.New("incorrect trunk maxID parameter")
}
if maxID < minID {
return nil, errors.New("minID is greater than maxID in trunk parameter")
}
for v := minID; v <= maxID; v++ {
vlanMap[v] = struct{}{}
}
case item.MinID == nil && item.MaxID != nil:
return nil, errors.New("minID and maxID should be configured simultaneously, minID is missing")
case item.MinID != nil && item.MaxID == nil:
return nil, errors.New("minID and maxID should be configured simultaneously, maxID is missing")
}
// single vid
if item.ID != nil {
ID = *item.ID
if ID <= 0 || ID > 4094 {
return nil, errors.New("incorrect trunk id parameter")
}
vlanMap[ID] = struct{}{}
}
}
if len(vlanMap) == 0 {
return nil, nil
}
vlans := make([]int, 0, len(vlanMap))
for k := range vlanMap {
vlans = append(vlans, k)
}
sort.Slice(vlans, func(i int, j int) bool { return vlans[i] < vlans[j] })
return vlans, nil
}
// calcGateways processes the results from the IPAM plugin and does the // calcGateways processes the results from the IPAM plugin and does the
// following for each IP family: // following for each IP family:
// - Calculates and compiles a list of gateway addresses // - Calculates and compiles a list of gateway addresses
// - Adds a default route if needed // - Adds a default route if needed
func calcGateways(result *current.Result, n *NetConf) (*gwInfo, *gwInfo, error) { func calcGateways(result *current.Result, n *NetConf) (*gwInfo, *gwInfo, error) {
gwsV4 := &gwInfo{} gwsV4 := &gwInfo{}
gwsV6 := &gwInfo{} gwsV6 := &gwInfo{}
@ -378,8 +300,8 @@ func ensureBridge(brName string, mtu int, promiscMode, vlanFiltering bool) (*net
return br, nil return br, nil
} }
func ensureVlanInterface(br *netlink.Bridge, vlanID int, preserveDefaultVlan bool) (netlink.Link, error) { func ensureVlanInterface(br *netlink.Bridge, vlanId int) (netlink.Link, error) {
name := fmt.Sprintf("%s.%d", br.Name, vlanID) name := fmt.Sprintf("%s.%d", br.Name, vlanId)
brGatewayVeth, err := netlink.LinkByName(name) brGatewayVeth, err := netlink.LinkByName(name)
if err != nil { if err != nil {
@ -392,7 +314,7 @@ func ensureVlanInterface(br *netlink.Bridge, vlanID int, preserveDefaultVlan boo
return nil, fmt.Errorf("faild to find host namespace: %v", err) 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, "")
if err != nil { if err != nil {
return nil, fmt.Errorf("faild to create vlan gateway %q: %v", name, err) return nil, fmt.Errorf("faild to create vlan gateway %q: %v", name, err)
} }
@ -411,7 +333,7 @@ func ensureVlanInterface(br *netlink.Bridge, vlanID int, preserveDefaultVlan boo
return brGatewayVeth, nil 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, mac string) (*current.Interface, *current.Interface, error) {
contIface := &current.Interface{} contIface := &current.Interface{}
hostIface := &current.Interface{} hostIface := &current.Interface{}
@ -448,14 +370,6 @@ 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) return nil, nil, fmt.Errorf("failed to setup hairpin mode for %v: %v", hostVeth.Attrs().Name, err)
} }
if (vlanID != 0 || len(vlans) > 0) && !preserveDefaultVlan {
err = removeDefaultVlan(hostVeth)
if err != nil {
return nil, nil, fmt.Errorf("failed to remove default vlan on interface %q: %v", hostIface.Name, err)
}
}
// Currently bridge CNI only support access port(untagged only) or trunk port(tagged only)
if vlanID != 0 { if vlanID != 0 {
err = netlink.BridgeVlanAdd(hostVeth, uint16(vlanID), true, true, false, true) err = netlink.BridgeVlanAdd(hostVeth, uint16(vlanID), true, true, false, true)
if err != nil { if err != nil {
@ -463,35 +377,9 @@ func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairp
} }
} }
for _, v := range vlans {
err = netlink.BridgeVlanAdd(hostVeth, uint16(v), false, false, false, true)
if err != nil {
return nil, nil, fmt.Errorf("failed to setup vlan tag on interface %q: %w", hostIface.Name, err)
}
}
return hostIface, contIface, nil return hostIface, contIface, nil
} }
func removeDefaultVlan(hostVeth netlink.Link) error {
vlanInfo, err := netlink.BridgeVlanList()
if err != nil {
return err
}
brVlanInfo, ok := vlanInfo[int32(hostVeth.Attrs().Index)]
if ok {
for _, info := range brVlanInfo {
err = netlink.BridgeVlanDel(hostVeth, info.Vid, false, false, false, true)
if err != nil {
return err
}
}
}
return nil
}
func calcGatewayIP(ipn *net.IPNet) net.IP { func calcGatewayIP(ipn *net.IPNet) net.IP {
nid := ipn.IP.Mask(ipn.Mask) nid := ipn.IP.Mask(ipn.Mask)
return ip.NextIP(nid) return ip.NextIP(nid)
@ -499,7 +387,7 @@ func calcGatewayIP(ipn *net.IPNet) net.IP {
func setupBridge(n *NetConf) (*netlink.Bridge, *current.Interface, error) { func setupBridge(n *NetConf) (*netlink.Bridge, *current.Interface, error) {
vlanFiltering := false vlanFiltering := false
if n.Vlan != 0 || n.VlanTrunk != nil { if n.Vlan != 0 {
vlanFiltering = true vlanFiltering = true
} }
// create bridge if necessary // create bridge if necessary
@ -522,7 +410,7 @@ func enableIPForward(family int) error {
} }
func cmdAdd(args *skel.CmdArgs) error { func cmdAdd(args *skel.CmdArgs) error {
success := false var success bool = false
n, cniVersion, err := loadNetConf(args.StdinData, args.Args) n, cniVersion, err := loadNetConf(args.StdinData, args.Args)
if err != nil { if err != nil {
@ -531,16 +419,12 @@ func cmdAdd(args *skel.CmdArgs) error {
isLayer3 := n.IPAM.Type != "" isLayer3 := n.IPAM.Type != ""
if isLayer3 && n.DisableContainerInterface {
return fmt.Errorf("cannot use IPAM when DisableContainerInterface flag is set")
}
if n.IsDefaultGW { if n.IsDefaultGW {
n.IsGW = true n.IsGW = true
} }
if n.HairpinMode && n.PromiscMode { if n.HairpinMode && n.PromiscMode {
return fmt.Errorf("cannot set hairpin mode and promiscuous mode at the same time") return fmt.Errorf("cannot set hairpin mode and promiscuous mode at the same time.")
} }
br, brInterface, err := setupBridge(n) br, brInterface, err := setupBridge(n)
@ -554,7 +438,7 @@ func cmdAdd(args *skel.CmdArgs) error {
} }
defer netns.Close() 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.mac)
if err != nil { if err != nil {
return err return err
} }
@ -605,7 +489,6 @@ func cmdAdd(args *skel.CmdArgs) error {
result.IPs = ipamResult.IPs result.IPs = ipamResult.IPs
result.Routes = ipamResult.Routes result.Routes = ipamResult.Routes
result.DNS = ipamResult.DNS
if len(result.IPs) == 0 { if len(result.IPs) == 0 {
return errors.New("IPAM plugin returned missing IP config") return errors.New("IPAM plugin returned missing IP config")
@ -628,27 +511,50 @@ func cmdAdd(args *skel.CmdArgs) error {
_, _ = sysctl.Sysctl(fmt.Sprintf("net/ipv4/conf/%s/arp_notify", args.IfName), "1") _, _ = sysctl.Sysctl(fmt.Sprintf("net/ipv4/conf/%s/arp_notify", args.IfName), "1")
// Add the IP to the interface // Add the IP to the interface
return ipam.ConfigureIface(args.IfName, result) if err := ipam.ConfigureIface(args.IfName, result); err != nil {
return err
}
return nil
}); err != nil { }); err != nil {
return err return err
} }
// check bridge port state
retries := []int{0, 50, 500, 1000, 1000}
for idx, sleep := range retries {
time.Sleep(time.Duration(sleep) * time.Millisecond)
hostVeth, err := netlink.LinkByName(hostInterface.Name)
if err != nil {
return err
}
if hostVeth.Attrs().OperState == netlink.OperUp {
break
}
if idx == len(retries)-1 {
return fmt.Errorf("bridge port in error state: %s", hostVeth.Attrs().OperState)
}
}
if n.IsGW { if n.IsGW {
var firstV4Addr net.IP
var vlanInterface *current.Interface var vlanInterface *current.Interface
// Set the IP address(es) on the bridge and enable forwarding // Set the IP address(es) on the bridge and enable forwarding
for _, gws := range []*gwInfo{gwsV4, gwsV6} { for _, gws := range []*gwInfo{gwsV4, gwsV6} {
for _, gw := range gws.gws { for _, gw := range gws.gws {
if gw.IP.To4() != nil && firstV4Addr == nil {
firstV4Addr = gw.IP
}
if n.Vlan != 0 { if n.Vlan != 0 {
vlanIface, err := ensureVlanInterface(br, n.Vlan, n.PreserveDefaultVlan) vlanIface, err := ensureVlanInterface(br, n.Vlan)
if err != nil { if err != nil {
return fmt.Errorf("failed to create vlan interface: %v", err) return fmt.Errorf("failed to create vlan interface: %v", err)
} }
if vlanInterface == nil { if vlanInterface == nil {
vlanInterface = &current.Interface{ vlanInterface = &current.Interface{Name: vlanIface.Attrs().Name,
Name: vlanIface.Attrs().Name, Mac: vlanIface.Attrs().HardwareAddr.String()}
Mac: vlanIface.Attrs().HardwareAddr.String(),
}
result.Interfaces = append(result.Interfaces, vlanInterface) result.Interfaces = append(result.Interfaces, vlanInterface)
} }
@ -681,13 +587,12 @@ func cmdAdd(args *skel.CmdArgs) error {
} }
} }
} }
} else if !n.DisableContainerInterface { } else {
if err := netns.Do(func(_ ns.NetNS) error { if err := netns.Do(func(_ ns.NetNS) error {
link, err := netlink.LinkByName(args.IfName) link, err := netlink.LinkByName(args.IfName)
if err != nil { if err != nil {
return fmt.Errorf("failed to retrieve link: %v", err) return fmt.Errorf("failed to retrieve link: %v", err)
} }
// If layer 2 we still need to set the container veth to up // If layer 2 we still need to set the container veth to up
if err = netlink.LinkSetUp(link); err != nil { if err = netlink.LinkSetUp(link); err != nil {
return fmt.Errorf("failed to set %q up: %v", args.IfName, err) return fmt.Errorf("failed to set %q up: %v", args.IfName, err)
@ -698,34 +603,6 @@ func cmdAdd(args *skel.CmdArgs) error {
} }
} }
hostVeth, err := netlink.LinkByName(hostInterface.Name)
if err != nil {
return err
}
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
}
if idx == len(retries)-1 {
return fmt.Errorf("bridge port in error state: %s", hostVeth.Attrs().OperState)
}
}
}
// In certain circumstances, the host-side of the veth may change addrs
hostInterface.Mac = hostVeth.Attrs().HardwareAddr.String()
// Refetch the bridge since its MAC address may change when the first // Refetch the bridge since its MAC address may change when the first
// veth is added or after its IP address is set // veth is added or after its IP address is set
br, err = bridgeByName(n.BrName) br, err = bridgeByName(n.BrName)
@ -734,29 +611,18 @@ func cmdAdd(args *skel.CmdArgs) error {
} }
brInterface.Mac = br.Attrs().HardwareAddr.String() brInterface.Mac = br.Attrs().HardwareAddr.String()
result.DNS = n.DNS
// Return an error requested by testcases, if any // Return an error requested by testcases, if any
if debugPostIPAMError != nil { if debugPostIPAMError != nil {
return debugPostIPAMError return debugPostIPAMError
} }
// Use incoming DNS settings if provided, otherwise use the
// settings that were already configured by the IPAM plugin
if dnsConfSet(n.DNS) {
result.DNS = n.DNS
}
success = true success = true
return types.PrintResult(result, cniVersion) return types.PrintResult(result, cniVersion)
} }
func dnsConfSet(dnsConf types.DNS) bool {
return dnsConf.Nameservers != nil ||
dnsConf.Search != nil ||
dnsConf.Options != nil ||
dnsConf.Domain != ""
}
func cmdDel(args *skel.CmdArgs) error { func cmdDel(args *skel.CmdArgs) error {
n, _, err := loadNetConf(args.StdinData, args.Args) n, _, err := loadNetConf(args.StdinData, args.Args)
if err != nil { if err != nil {
@ -790,6 +656,7 @@ func cmdDel(args *skel.CmdArgs) error {
} }
return err return err
}) })
if err != nil { if err != nil {
// if NetNs is passed down by the Cloud Orchestration Engine, or if it called multiple times // 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. // so don't return an error if the device is already removed.
@ -839,6 +706,7 @@ type cniBridgeIf struct {
} }
func validateInterface(intf current.Interface, expectInSb bool) (cniBridgeIf, netlink.Link, error) { func validateInterface(intf current.Interface, expectInSb bool) (cniBridgeIf, netlink.Link, error) {
ifFound := cniBridgeIf{found: false} ifFound := cniBridgeIf{found: false}
if intf.Name == "" { if intf.Name == "" {
return ifFound, nil, fmt.Errorf("Interface name missing ") return ifFound, nil, fmt.Errorf("Interface name missing ")
@ -863,6 +731,7 @@ func validateInterface(intf current.Interface, expectInSb bool) (cniBridgeIf, ne
} }
func validateCniBrInterface(intf current.Interface, n *NetConf) (cniBridgeIf, error) { func validateCniBrInterface(intf current.Interface, n *NetConf) (cniBridgeIf, error) {
brFound, link, err := validateInterface(intf, false) brFound, link, err := validateInterface(intf, false)
if err != nil { if err != nil {
return brFound, err return brFound, err
@ -894,6 +763,7 @@ func validateCniBrInterface(intf current.Interface, n *NetConf) (cniBridgeIf, er
} }
func validateCniVethInterface(intf *current.Interface, brIf cniBridgeIf, contIf cniBridgeIf) (cniBridgeIf, error) { func validateCniVethInterface(intf *current.Interface, brIf cniBridgeIf, contIf cniBridgeIf) (cniBridgeIf, error) {
vethFound, link, err := validateInterface(*intf, false) vethFound, link, err := validateInterface(*intf, false)
if err != nil { if err != nil {
return vethFound, err return vethFound, err
@ -937,6 +807,7 @@ func validateCniVethInterface(intf *current.Interface, brIf cniBridgeIf, contIf
} }
func validateCniContainerInterface(intf current.Interface) (cniBridgeIf, error) { func validateCniContainerInterface(intf current.Interface) (cniBridgeIf, error) {
vethFound, link, err := validateInterface(intf, true) vethFound, link, err := validateInterface(intf, true)
if err != nil { if err != nil {
return vethFound, err return vethFound, err
@ -965,6 +836,7 @@ func validateCniContainerInterface(intf current.Interface) (cniBridgeIf, error)
} }
func cmdCheck(args *skel.CmdArgs) error { func cmdCheck(args *skel.CmdArgs) error {
n, _, err := loadNetConf(args.StdinData, args.Args) n, _, err := loadNetConf(args.StdinData, args.Args)
if err != nil { if err != nil {
return err return err
@ -1071,7 +943,7 @@ func cmdCheck(args *skel.CmdArgs) error {
} }
// Check prevResults for ips, routes and dns against values found in the container // Check prevResults for ips, routes and dns against values found in the container
return netns.Do(func(_ ns.NetNS) error { if err := netns.Do(func(_ ns.NetNS) error {
err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs) err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs)
if err != nil { if err != nil {
return err return err
@ -1082,7 +954,11 @@ func cmdCheck(args *skel.CmdArgs) error {
return err return err
} }
return nil return nil
}) }); err != nil {
return err
}
return nil
} }
func uniqueID(containerID, cniIface string) string { func uniqueID(containerID, cniIface string) string {

View File

@ -15,26 +15,13 @@
package main package main
import ( import (
"testing" . "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
)
var ( "testing"
resolvConf string
err error
) )
func TestBridge(t *testing.T) { func TestBridge(t *testing.T) {
RegisterFailHandler(Fail) RegisterFailHandler(Fail)
resolvConf, err = newResolvConf()
Expect(err).NotTo(HaveOccurred())
RunSpecs(t, "plugins/main/bridge") RunSpecs(t, "plugins/main/bridge")
} }
var _ = AfterSuite(func() {
deleteResolvConf(resolvConf)
})

File diff suppressed because it is too large Load Diff

View File

@ -1,39 +0,0 @@
---
title: dummy plugin
description: "plugins/main/dummy/README.md"
date: 2022-05-12
toc: true
draft: true
weight: 200
---
## Overview
dummy is a useful feature for routing packets through the Linux kernel without transmitting.
Like loopback, it is a purely virtual interface that allows packets to be routed to a designated IP address. Unlike loopback, the IP address can be arbitrary and is not restricted to the `127.0.0.0/8` range.
## Example configuration
```json
{
"name": "mynet",
"type": "dummy",
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24"
}
}
```
## Network configuration reference
* `name` (string, required): the name of the network.
* `type` (string, required): "dummy".
* `ipam` (dictionary, required): IPAM configuration to be used for this network.
## Notes
* `dummy` does not transmit packets.
Therefore the container will not be able to reach any external network.
This solution is designed to be used in conjunction with other CNI plugins (e.g., `bridge`) to provide an internal non-loopback address for applications to use.

View File

@ -1,290 +0,0 @@
// Copyright 2022 Arista Networks
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"encoding/json"
"errors"
"fmt"
"net"
"github.com/vishvananda/netlink"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/pkg/version"
"github.com/containernetworking/plugins/pkg/ip"
"github.com/containernetworking/plugins/pkg/ipam"
"github.com/containernetworking/plugins/pkg/ns"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
func parseNetConf(bytes []byte) (*types.NetConf, error) {
conf := &types.NetConf{}
if err := json.Unmarshal(bytes, conf); err != nil {
return nil, fmt.Errorf("failed to parse network config: %v", err)
}
return conf, nil
}
func createDummy(ifName string, netns ns.NetNS) (*current.Interface, error) {
dummy := &current.Interface{}
dm := &netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: ifName,
Namespace: netlink.NsFd(int(netns.Fd())),
},
}
if err := netlink.LinkAdd(dm); err != nil {
return nil, fmt.Errorf("failed to create dummy: %v", err)
}
dummy.Name = ifName
err := netns.Do(func(_ ns.NetNS) error {
// Re-fetch interface to get all properties/attributes
contDummy, err := netlink.LinkByName(ifName)
if err != nil {
return fmt.Errorf("failed to fetch dummy%q: %v", ifName, err)
}
dummy.Mac = contDummy.Attrs().HardwareAddr.String()
dummy.Sandbox = netns.Path()
return nil
})
if err != nil {
return nil, err
}
return dummy, nil
}
func cmdAdd(args *skel.CmdArgs) error {
conf, err := parseNetConf(args.StdinData)
if err != nil {
return err
}
if conf.IPAM.Type == "" {
return errors.New("dummy interface requires an IPAM configuration")
}
netns, err := ns.GetNS(args.Netns)
if err != nil {
return fmt.Errorf("failed to open netns %q: %v", netns, err)
}
defer netns.Close()
dummyInterface, err := createDummy(args.IfName, netns)
if err != nil {
return err
}
// Delete link if err to avoid link leak in this ns
defer func() {
if err != nil {
netns.Do(func(_ ns.NetNS) error {
return ip.DelLinkByName(args.IfName)
})
}
}()
r, err := ipam.ExecAdd(conf.IPAM.Type, args.StdinData)
if err != nil {
return err
}
// defer ipam deletion to avoid ip leak
defer func() {
if err != nil {
ipam.ExecDel(conf.IPAM.Type, args.StdinData)
}
}()
// convert IPAMResult to current Result type
result, err := current.NewResultFromResult(r)
if err != nil {
return err
}
if len(result.IPs) == 0 {
return errors.New("IPAM plugin returned missing IP config")
}
for _, ipc := range result.IPs {
// all addresses apply to the container dummy interface
ipc.Interface = current.Int(0)
}
result.Interfaces = []*current.Interface{dummyInterface}
err = netns.Do(func(_ ns.NetNS) error {
return ipam.ConfigureIface(args.IfName, result)
})
if err != nil {
return err
}
return types.PrintResult(result, conf.CNIVersion)
}
func cmdDel(args *skel.CmdArgs) error {
conf, err := parseNetConf(args.StdinData)
if err != nil {
return err
}
if err = ipam.ExecDel(conf.IPAM.Type, args.StdinData); err != nil {
return err
}
if args.Netns == "" {
return nil
}
err = ns.WithNetNSPath(args.Netns, func(ns.NetNS) error {
err = ip.DelLinkByName(args.IfName)
if err != nil && err == ip.ErrLinkNotFound {
return nil
}
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.
// https://github.com/kubernetes/kubernetes/issues/43014#issuecomment-287164444
_, ok := err.(ns.NSPathNotExistErr)
if ok {
return nil
}
return err
}
return nil
}
func main() {
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("dummy"))
}
func cmdCheck(args *skel.CmdArgs) error {
conf, err := parseNetConf(args.StdinData)
if err != nil {
return err
}
if conf.IPAM.Type == "" {
return errors.New("dummy interface requires an IPAM configuration")
}
netns, err := ns.GetNS(args.Netns)
if err != nil {
return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
}
defer netns.Close()
// run the IPAM plugin and get back the config to apply
err = ipam.ExecCheck(conf.IPAM.Type, args.StdinData)
if err != nil {
return err
}
if conf.RawPrevResult == nil {
return fmt.Errorf("dummy: Required prevResult missing")
}
if err := version.ParsePrevResult(conf); err != nil {
return err
}
// Convert whatever the IPAM result was into the current Result type
result, err := current.NewResultFromResult(conf.PrevResult)
if err != nil {
return err
}
var contMap current.Interface
// Find interfaces for name whe know, that of dummy device inside container
for _, intf := range result.Interfaces {
if args.IfName == intf.Name {
if args.Netns == intf.Sandbox {
contMap = *intf
continue
}
}
}
// The namespace must be the same as what was configured
if args.Netns != contMap.Sandbox {
return fmt.Errorf("Sandbox in prevResult %s doesn't match configured netns: %s",
contMap.Sandbox, args.Netns)
}
//
// Check prevResults for ips, routes and dns against values found in the container
if err := netns.Do(func(_ ns.NetNS) error {
// Check interface against values found in the container
err := validateCniContainerInterface(contMap)
if err != nil {
return err
}
err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs)
if err != nil {
return err
}
return nil
}); err != nil {
return err
}
return nil
}
func validateCniContainerInterface(intf current.Interface) error {
var link netlink.Link
var err error
if intf.Name == "" {
return fmt.Errorf("Container interface name missing in prevResult: %v", intf.Name)
}
link, err = netlink.LinkByName(intf.Name)
if err != nil {
return fmt.Errorf("Container Interface name in prevResult: %s not found", intf.Name)
}
if intf.Sandbox == "" {
return fmt.Errorf("Error: Container interface %s should not be in host namespace", link.Attrs().Name)
}
_, isDummy := link.(*netlink.Dummy)
if !isDummy {
return fmt.Errorf("Error: Container interface %s not of type dummy", link.Attrs().Name)
}
if intf.Mac != "" {
if intf.Mac != link.Attrs().HardwareAddr.String() {
return fmt.Errorf("Interface %s Mac %s doesn't match container Mac: %s", intf.Name, intf.Mac, link.Attrs().HardwareAddr)
}
}
if link.Attrs().Flags&net.FlagUp != net.FlagUp {
return fmt.Errorf("Interface %s is down", intf.Name)
}
return nil
}

View File

@ -1,40 +0,0 @@
// Copyright 2022 Arista Networks
//
// 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_test
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
)
var pathToLoPlugin string
func TestLoopback(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "plugins/main/dummy")
}
var _ = BeforeSuite(func() {
var err error
pathToLoPlugin, err = gexec.Build("github.com/containernetworking/plugins/plugins/main/dummy")
Expect(err).NotTo(HaveOccurred())
})
var _ = AfterSuite(func() {
gexec.CleanupBuildArtifacts()
})

View File

@ -1,385 +0,0 @@
// Copyright 2022 Arista Networks
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"encoding/json"
"errors"
"fmt"
"net"
"os"
"strings"
"syscall"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/vishvananda/netlink"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
types020 "github.com/containernetworking/cni/pkg/types/020"
types040 "github.com/containernetworking/cni/pkg/types/040"
types100 "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils"
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
)
const MASTER_NAME = "eth0"
type Net struct {
Name string `json:"name"`
CNIVersion string `json:"cniVersion"`
Type string `json:"type,omitempty"`
IPAM *allocator.IPAMConfig `json:"ipam"`
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
PrevResult types100.Result `json:"-"`
}
func buildOneConfig(netName string, cniVersion string, orig *Net, prevResult types.Result) (*Net, error) {
var err error
inject := map[string]interface{}{
"name": netName,
"cniVersion": cniVersion,
}
// Add previous plugin result
if prevResult != nil {
inject["prevResult"] = prevResult
}
// Ensure every config uses the same name and version
config := make(map[string]interface{})
confBytes, err := json.Marshal(orig)
if err != nil {
return nil, err
}
err = json.Unmarshal(confBytes, &config)
if err != nil {
return nil, fmt.Errorf("unmarshal existing network bytes: %s", err)
}
for key, value := range inject {
config[key] = value
}
newBytes, err := json.Marshal(config)
if err != nil {
return nil, err
}
conf := &Net{}
if err := json.Unmarshal(newBytes, &conf); err != nil {
return nil, fmt.Errorf("error parsing configuration: %s", err)
}
return conf, nil
}
type tester interface {
// verifyResult minimally verifies the Result and returns the interface's MAC address
verifyResult(result types.Result, name string) string
}
type testerBase struct{}
type (
testerV10x testerBase
testerV04x testerBase
testerV03x testerBase
testerV01xOr02x testerBase
)
func newTesterByVersion(version string) tester {
switch {
case strings.HasPrefix(version, "1.0."):
return &testerV10x{}
case strings.HasPrefix(version, "0.4."):
return &testerV04x{}
case strings.HasPrefix(version, "0.3."):
return &testerV03x{}
default:
return &testerV01xOr02x{}
}
}
// verifyResult minimally verifies the Result and returns the interface's MAC address
func (t *testerV10x) verifyResult(result types.Result, name string) string {
r, err := types100.GetResult(result)
Expect(err).NotTo(HaveOccurred())
Expect(r.Interfaces).To(HaveLen(1))
Expect(r.Interfaces[0].Name).To(Equal(name))
Expect(r.IPs).To(HaveLen(1))
return r.Interfaces[0].Mac
}
func verify0403(result types.Result, name string) string {
r, err := types040.GetResult(result)
Expect(err).NotTo(HaveOccurred())
Expect(r.Interfaces).To(HaveLen(1))
Expect(r.Interfaces[0].Name).To(Equal(name))
Expect(r.IPs).To(HaveLen(1))
return r.Interfaces[0].Mac
}
// verifyResult minimally verifies the Result and returns the interface's MAC address
func (t *testerV04x) verifyResult(result types.Result, name string) string {
return verify0403(result, name)
}
// verifyResult minimally verifies the Result and returns the interface's MAC address
func (t *testerV03x) verifyResult(result types.Result, name string) string {
return verify0403(result, name)
}
// verifyResult minimally verifies the Result and returns the interface's MAC address
func (t *testerV01xOr02x) verifyResult(result types.Result, _ string) string {
r, err := types020.GetResult(result)
Expect(err).NotTo(HaveOccurred())
Expect(r.IP4.IP.IP).NotTo(BeNil())
Expect(r.IP6).To(BeNil())
// 0.2 and earlier don't return MAC address
return ""
}
var _ = Describe("dummy Operations", func() {
var originalNS, targetNS ns.NetNS
var dataDir string
BeforeEach(func() {
// Create a new NetNS so we don't modify the host
var err error
originalNS, err = testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
targetNS, err = testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
dataDir, err = os.MkdirTemp("", "dummy_test")
Expect(err).NotTo(HaveOccurred())
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
// Add master
err = netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: MASTER_NAME,
},
})
Expect(err).NotTo(HaveOccurred())
m, err := netlink.LinkByName(MASTER_NAME)
Expect(err).NotTo(HaveOccurred())
err = netlink.LinkSetUp(m)
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
AfterEach(func() {
Expect(os.RemoveAll(dataDir)).To(Succeed())
Expect(originalNS.Close()).To(Succeed())
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
Expect(targetNS.Close()).To(Succeed())
Expect(testutils.UnmountNS(targetNS)).To(Succeed())
})
for _, ver := range testutils.AllSpecVersions {
// Redefine ver inside for scope so real value is picked up by each dynamically defined It()
// See Gingkgo's "Patterns for dynamically generating tests" documentation.
ver := ver
It(fmt.Sprintf("[%s] creates an dummy link in a non-default namespace", ver), func() {
// Create dummy in other namespace
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, err := createDummy("foobar0", targetNS)
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// Make sure dummy link exists in the target namespace
err = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName("foobar0")
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal("foobar0"))
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It(fmt.Sprintf("[%s] configures and deconfigures a dummy link with ADD/CHECK/DEL", ver), func() {
const IFNAME = "dummy0"
conf := fmt.Sprintf(`{
"cniVersion": "%s",
"name": "dummyTestv4",
"type": "dummy",
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24",
"dataDir": "%s"
}
}`, ver, dataDir)
args := &skel.CmdArgs{
ContainerID: "contDummy",
Netns: targetNS.Path(),
IfName: IFNAME,
StdinData: []byte(conf),
}
t := newTesterByVersion(ver)
var result types.Result
var macAddress string
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
var err error
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())
// Make sure dummy 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))
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())
// call CmdCheck
n := &Net{}
err = json.Unmarshal([]byte(conf), &n)
Expect(err).NotTo(HaveOccurred())
n.IPAM, _, err = allocator.LoadIPAMConfig([]byte(conf), "")
Expect(err).NotTo(HaveOccurred())
newConf, err := buildOneConfig("dummyTestv4", ver, n, result)
Expect(err).NotTo(HaveOccurred())
confString, err := json.Marshal(newConf)
Expect(err).NotTo(HaveOccurred())
args.StdinData = confString
// CNI Check dummy in the target namespace
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
return testutils.CmdCheckWithArgs(args, func() error { return cmdCheck(args) })
})
if testutils.SpecVersionHasCHECK(ver) {
Expect(err).NotTo(HaveOccurred())
} else {
Expect(err).To(MatchError("config version does not allow CHECK"))
}
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())
// Make sure dummy link has been deleted
err = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(IFNAME)
Expect(err).To(HaveOccurred())
Expect(link).To(BeNil())
return nil
})
Expect(err).NotTo(HaveOccurred())
// DEL can be called multiple times, make sure no error is returned
// if the device is already removed.
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It(fmt.Sprintf("[%s] fails to create dummy link with no ipam", ver), func() {
var err error
const confFmt = `{
"cniVersion": "%s",
"name": "mynet",
"type": "dummy"
}`
args := &skel.CmdArgs{
ContainerID: "dummyCont",
Netns: "/var/run/netns/test",
IfName: "dummy0",
StdinData: []byte(fmt.Sprintf(confFmt, ver)),
}
_ = originalNS.Do(func(_ ns.NetNS) error {
defer GinkgoRecover()
_, _, err = testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).To(Equal(errors.New("dummy interface requires an IPAM configuration")))
return nil
})
})
}
})

View File

@ -19,6 +19,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io/ioutil"
"net" "net"
"os" "os"
"path/filepath" "path/filepath"
@ -31,6 +32,7 @@ import (
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
current "github.com/containernetworking/cni/pkg/types/100" current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/pkg/version" "github.com/containernetworking/cni/pkg/version"
"github.com/containernetworking/plugins/pkg/ip" "github.com/containernetworking/plugins/pkg/ip"
"github.com/containernetworking/plugins/pkg/ipam" "github.com/containernetworking/plugins/pkg/ipam"
"github.com/containernetworking/plugins/pkg/ns" "github.com/containernetworking/plugins/pkg/ns"
@ -38,14 +40,13 @@ import (
) )
var ( var (
sysBusPCI = "/sys/bus/pci/devices" sysBusPCI = "/sys/bus/pci/devices"
sysBusAuxiliary = "/sys/bus/auxiliary/devices"
) )
// Array of different linux drivers bound to network device needed for DPDK // Array of different linux drivers bound to network device needed for DPDK
var userspaceDrivers = []string{"vfio-pci", "uio_pci_generic", "igb_uio"} var userspaceDrivers = []string{"vfio-pci", "uio_pci_generic", "igb_uio"}
// NetConf for host-device config, look the README to learn how to use those parameters //NetConf for host-device config, look the README to learn how to use those parameters
type NetConf struct { type NetConf struct {
types.NetConf types.NetConf
Device string `json:"device"` // Device-Name, something like eth0 or can0 etc. Device string `json:"device"` // Device-Name, something like eth0 or can0 etc.
@ -56,9 +57,6 @@ type NetConf struct {
RuntimeConfig struct { RuntimeConfig struct {
DeviceID string `json:"deviceID,omitempty"` DeviceID string `json:"deviceID,omitempty"`
} `json:"runtimeConfig,omitempty"` } `json:"runtimeConfig,omitempty"`
// for internal use
auxDevice string `json:"-"` // Auxiliary device name as appears on Auxiliary bus (/sys/bus/auxiliary)
} }
func init() { func init() {
@ -68,31 +66,6 @@ func init() {
runtime.LockOSThread() 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) { func loadConf(bytes []byte) (*NetConf, error) {
n := &NetConf{} n := &NetConf{}
var err error var err error
@ -100,12 +73,12 @@ func loadConf(bytes []byte) (*NetConf, error) {
return nil, fmt.Errorf("failed to load netconf: %v", err) return nil, fmt.Errorf("failed to load netconf: %v", err)
} }
// Override device with the standardized DeviceID if provided in Runtime Config. if n.RuntimeConfig.DeviceID != "" {
if err := handleDeviceID(n); err != nil { // Override PCI device with the standardized DeviceID provided in Runtime Config.
return nil, err n.PCIAddr = n.RuntimeConfig.DeviceID
} }
if n.Device == "" && n.HWAddr == "" && n.KernelPath == "" && n.PCIAddr == "" && n.auxDevice == "" { if n.Device == "" && n.HWAddr == "" && n.KernelPath == "" && n.PCIAddr == "" {
return nil, fmt.Errorf(`specify either "device", "hwaddr", "kernelpath" or "pciBusID"`) return nil, fmt.Errorf(`specify either "device", "hwaddr", "kernelpath" or "pciBusID"`)
} }
@ -133,7 +106,7 @@ func cmdAdd(args *skel.CmdArgs) error {
result := &current.Result{} result := &current.Result{}
var contDev netlink.Link var contDev netlink.Link
if !cfg.DPDKMode { if !cfg.DPDKMode {
hostDev, err := getLink(cfg.Device, cfg.HWAddr, cfg.KernelPath, cfg.PCIAddr, cfg.auxDevice) hostDev, err := getLink(cfg.Device, cfg.HWAddr, cfg.KernelPath, cfg.PCIAddr)
if err != nil { if err != nil {
return fmt.Errorf("failed to find host device: %v", err) return fmt.Errorf("failed to find host device: %v", err)
} }
@ -189,7 +162,10 @@ func cmdAdd(args *skel.CmdArgs) error {
if !cfg.DPDKMode { if !cfg.DPDKMode {
err = containerNs.Do(func(_ ns.NetNS) error { err = containerNs.Do(func(_ ns.NetNS) error {
return ipam.ConfigureIface(args.IfName, newResult) if err := ipam.ConfigureIface(args.IfName, newResult); err != nil {
return err
}
return nil
}) })
if err != nil { if err != nil {
return err return err
@ -230,112 +206,34 @@ func cmdDel(args *skel.CmdArgs) error {
return nil return nil
} }
// setTempName sets a temporary name for netdevice, returns updated Link object or error
// if occurred.
func setTempName(dev netlink.Link) (netlink.Link, error) {
tempName := fmt.Sprintf("%s%d", "temp_", dev.Attrs().Index)
// rename to tempName
if err := netlink.LinkSetName(dev, tempName); err != nil {
return nil, fmt.Errorf("failed to rename device %q to %q: %v", dev.Attrs().Name, tempName, err)
}
// Get updated Link obj
tempDev, err := netlink.LinkByName(tempName)
if err != nil {
return nil, fmt.Errorf("failed to find %q after rename to %q: %v", dev.Attrs().Name, tempName, err)
}
return tempDev, nil
}
func moveLinkIn(hostDev netlink.Link, containerNs ns.NetNS, ifName string) (netlink.Link, error) { func moveLinkIn(hostDev netlink.Link, containerNs ns.NetNS, ifName string) (netlink.Link, error) {
origLinkFlags := hostDev.Attrs().Flags if err := netlink.LinkSetNsFd(hostDev, int(containerNs.Fd())); err != nil {
hostDevName := hostDev.Attrs().Name return nil, err
defaultNs, err := ns.GetCurrentNS()
if err != nil {
return nil, fmt.Errorf("failed to get host namespace: %v", err)
}
// Devices can be renamed only when down
if err = netlink.LinkSetDown(hostDev); err != nil {
return nil, fmt.Errorf("failed to set %q down: %v", hostDev.Attrs().Name, err)
}
// restore original link state in case of error
defer func() {
if err != nil {
if origLinkFlags&net.FlagUp == net.FlagUp && hostDev != nil {
_ = netlink.LinkSetUp(hostDev)
}
}
}()
hostDev, err = setTempName(hostDev)
if err != nil {
return nil, fmt.Errorf("failed to rename device %q to temporary name: %v", hostDevName, err)
}
// restore original netdev name in case of error
defer func() {
if err != nil && hostDev != nil {
_ = netlink.LinkSetName(hostDev, hostDevName)
}
}()
if err = netlink.LinkSetNsFd(hostDev, int(containerNs.Fd())); err != nil {
return nil, fmt.Errorf("failed to move %q to container ns: %v", hostDev.Attrs().Name, err)
} }
var contDev netlink.Link var contDev netlink.Link
tempDevName := hostDev.Attrs().Name if err := containerNs.Do(func(_ ns.NetNS) error {
if err = containerNs.Do(func(_ ns.NetNS) error {
var err error var err error
contDev, err = netlink.LinkByName(tempDevName) contDev, err = netlink.LinkByName(hostDev.Attrs().Name)
if err != nil { if err != nil {
return fmt.Errorf("failed to find %q: %v", tempDevName, err) return fmt.Errorf("failed to find %q: %v", hostDev.Attrs().Name, err)
}
// Devices can be renamed only when down
if err = netlink.LinkSetDown(contDev); err != nil {
return fmt.Errorf("failed to set %q down: %v", hostDev.Attrs().Name, err)
} }
// move netdev back to host namespace in case of error
defer func() {
if err != nil {
_ = netlink.LinkSetNsFd(contDev, int(defaultNs.Fd()))
// we need to get updated link object as link was moved back to host namepsace
_ = defaultNs.Do(func(_ ns.NetNS) error {
hostDev, _ = netlink.LinkByName(tempDevName)
return nil
})
}
}()
// Save host device name into the container device's alias property // Save host device name into the container device's alias property
if err = netlink.LinkSetAlias(contDev, hostDevName); err != nil { if err := netlink.LinkSetAlias(contDev, hostDev.Attrs().Name); err != nil {
return fmt.Errorf("failed to set alias to %q: %v", tempDevName, err) return fmt.Errorf("failed to set alias to %q: %v", hostDev.Attrs().Name, err)
} }
// Rename container device to respect args.IfName // Rename container device to respect args.IfName
if err = netlink.LinkSetName(contDev, ifName); err != nil { if err := netlink.LinkSetName(contDev, ifName); err != nil {
return fmt.Errorf("failed to rename device %q to %q: %v", tempDevName, ifName, err) return fmt.Errorf("failed to rename device %q to %q: %v", hostDev.Attrs().Name, ifName, err)
} }
// restore tempDevName in case of error
defer func() {
if err != nil {
_ = netlink.LinkSetName(contDev, tempDevName)
}
}()
// Bring container device up // Bring container device up
if err = netlink.LinkSetUp(contDev); err != nil { if err = netlink.LinkSetUp(contDev); err != nil {
return fmt.Errorf("failed to set %q up: %v", ifName, err) return fmt.Errorf("failed to set %q up: %v", ifName, err)
} }
// bring device down in case of error
defer func() {
if err != nil {
_ = netlink.LinkSetDown(contDev)
}
}()
// Retrieve link again to get up-to-date name and attributes // Retrieve link again to get up-to-date name and attributes
contDev, err = netlink.LinkByName(ifName) contDev, err = netlink.LinkByName(ifName)
if err != nil { if err != nil {
@ -356,14 +254,11 @@ func moveLinkOut(containerNs ns.NetNS, ifName string) error {
} }
defer defaultNs.Close() defer defaultNs.Close()
var tempName string return containerNs.Do(func(_ ns.NetNS) error {
var origDev netlink.Link
err = containerNs.Do(func(_ ns.NetNS) error {
dev, err := netlink.LinkByName(ifName) dev, err := netlink.LinkByName(ifName)
if err != nil { if err != nil {
return fmt.Errorf("failed to find %q: %v", ifName, err) return fmt.Errorf("failed to find %q: %v", ifName, err)
} }
origDev = dev
// Devices can be renamed only when down // Devices can be renamed only when down
if err = netlink.LinkSetDown(dev); err != nil { if err = netlink.LinkSetDown(dev); err != nil {
@ -381,49 +276,16 @@ func moveLinkOut(containerNs ns.NetNS, ifName string) error {
} }
}() }()
newLink, err := setTempName(dev) // Rename the device to its original name from the host namespace
if err != nil { if err = netlink.LinkSetName(dev, dev.Attrs().Alias); err != nil {
return fmt.Errorf("failed to rename device %q to temporary name: %v", ifName, err) return fmt.Errorf("failed to restore %q to original name %q: %v", ifName, dev.Attrs().Alias, err)
} }
dev = newLink
tempName = dev.Attrs().Name
if err = netlink.LinkSetNsFd(dev, int(defaultNs.Fd())); err != nil { if err = netlink.LinkSetNsFd(dev, int(defaultNs.Fd())); err != nil {
return fmt.Errorf("failed to move %q to host netns: %v", tempName, err) return fmt.Errorf("failed to move %q to host netns: %v", dev.Attrs().Alias, err)
} }
return nil return nil
}) })
if err != nil {
return err
}
// Rename the device to its original name from the host namespace
tempDev, err := netlink.LinkByName(tempName)
if err != nil {
return fmt.Errorf("failed to find %q in host namespace: %v", tempName, err)
}
if err = netlink.LinkSetName(tempDev, tempDev.Attrs().Alias); err != nil {
// move device back to container ns so it may be retired
defer func() {
_ = netlink.LinkSetNsFd(tempDev, int(containerNs.Fd()))
_ = containerNs.Do(func(_ ns.NetNS) error {
lnk, err := netlink.LinkByName(tempName)
if err != nil {
return err
}
_ = netlink.LinkSetName(lnk, ifName)
if origDev.Attrs().Flags&net.FlagUp == net.FlagUp {
_ = netlink.LinkSetUp(lnk)
}
return nil
})
}()
return fmt.Errorf("failed to restore %q to original name %q: %v", tempName, tempDev.Attrs().Alias, err)
}
return nil
} }
func hasDpdkDriver(pciaddr string) (bool, error) { func hasDpdkDriver(pciaddr string) (bool, error) {
@ -459,46 +321,45 @@ func printLink(dev netlink.Link, cniVersion string, containerNs ns.NetNS) error
return types.PrintResult(&result, cniVersion) return types.PrintResult(&result, cniVersion)
} }
func linkFromPath(path string) (netlink.Link, error) { func getLink(devname, hwaddr, kernelpath, pciaddr string) (netlink.Link, error) {
entries, err := os.ReadDir(path) links, err := netlink.LinkList()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read directory %s: %q", path, err) return nil, fmt.Errorf("failed to list node links: %v", 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) { if len(devname) > 0 {
switch {
case len(devname) > 0:
return netlink.LinkByName(devname) return netlink.LinkByName(devname)
case len(hwaddr) > 0: } else if len(hwaddr) > 0 {
hwAddr, err := net.ParseMAC(hwaddr) hwAddr, err := net.ParseMAC(hwaddr)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse MAC address %q: %v", hwaddr, err) 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 { for _, link := range links {
if bytes.Equal(link.Attrs().HardwareAddr, hwAddr) { if bytes.Equal(link.Attrs().HardwareAddr, hwAddr) {
return link, nil return link, nil
} }
} }
case len(kernelpath) > 0: } else if len(kernelpath) > 0 {
if !filepath.IsAbs(kernelpath) || !strings.HasPrefix(kernelpath, "/sys/devices/") { if !filepath.IsAbs(kernelpath) || !strings.HasPrefix(kernelpath, "/sys/devices/") {
return nil, fmt.Errorf("kernel device path %q must be absolute and begin with /sys/devices/", kernelpath) return nil, fmt.Errorf("kernel device path %q must be absolute and begin with /sys/devices/", kernelpath)
} }
netDir := filepath.Join(kernelpath, "net") netDir := filepath.Join(kernelpath, "net")
return linkFromPath(netDir) files, err := ioutil.ReadDir(netDir)
case len(pciaddr) > 0: 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 _, file := range files {
// Make sure it's really an interface
for _, l := range links {
if file.Name() == l.Attrs().Name {
return l, nil
}
}
}
} else if len(pciaddr) > 0 {
netDir := filepath.Join(sysBusPCI, pciaddr, "net") netDir := filepath.Join(sysBusPCI, pciaddr, "net")
if _, err := os.Lstat(netDir); err != nil { if _, err := os.Lstat(netDir); err != nil {
virtioNetDir := filepath.Join(sysBusPCI, pciaddr, "virtio*", "net") virtioNetDir := filepath.Join(sysBusPCI, pciaddr, "virtio*", "net")
@ -508,10 +369,14 @@ func getLink(devname, hwaddr, kernelpath, pciaddr string, auxDev string) (netlin
} }
netDir = matches[0] netDir = matches[0]
} }
return linkFromPath(netDir) fInfo, err := ioutil.ReadDir(netDir)
case len(auxDev) > 0: if err != nil {
netDir := filepath.Join(sysBusAuxiliary, auxDev, "net") return nil, fmt.Errorf("failed to read net directory %s: %q", netDir, err)
return linkFromPath(netDir) }
if len(fInfo) > 0 {
return netlink.LinkByName(fInfo[0].Name())
}
return nil, fmt.Errorf("failed to find device name for pci address %s", pciaddr)
} }
return nil, fmt.Errorf("failed to find physical interface") return nil, fmt.Errorf("failed to find physical interface")
@ -522,6 +387,7 @@ func main() {
} }
func cmdCheck(args *skel.CmdArgs) error { func cmdCheck(args *skel.CmdArgs) error {
cfg, err := loadConf(args.StdinData) cfg, err := loadConf(args.StdinData)
if err != nil { if err != nil {
return err return err
@ -578,6 +444,7 @@ func cmdCheck(args *skel.CmdArgs) error {
// //
// Check prevResults for ips, routes and dns against values found in the container // Check prevResults for ips, routes and dns against values found in the container
if err := netns.Do(func(_ ns.NetNS) error { if err := netns.Do(func(_ ns.NetNS) error {
// Check interface against values found in the container // Check interface against values found in the container
err := validateCniContainerInterface(contMap) err := validateCniContainerInterface(contMap)
if err != nil { if err != nil {
@ -603,6 +470,7 @@ func cmdCheck(args *skel.CmdArgs) error {
} }
func validateCniContainerInterface(intf current.Interface) error { func validateCniContainerInterface(intf current.Interface) error {
var link netlink.Link var link netlink.Link
var err error var err error

View File

@ -15,10 +15,10 @@
package main package main
import ( import (
"testing" . "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"testing"
) )
func TestVlan(t *testing.T) { func TestVlan(t *testing.T) {

View File

@ -17,16 +17,13 @@ package main
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil"
"math/rand" "math/rand"
"net" "net"
"os" "os"
"path" "path"
"strings" "strings"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/vishvananda/netlink"
"github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
types040 "github.com/containernetworking/cni/pkg/types/040" types040 "github.com/containernetworking/cni/pkg/types/040"
@ -34,6 +31,10 @@ import (
"github.com/containernetworking/cni/pkg/version" "github.com/containernetworking/cni/pkg/version"
"github.com/containernetworking/plugins/pkg/ns" "github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils" "github.com/containernetworking/plugins/pkg/testutils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vishvananda/netlink"
) )
type Net struct { type Net struct {
@ -86,14 +87,14 @@ func canonicalizeIP(ip *net.IP) error {
// LoadIPAMConfig creates IPAMConfig using json encoded configuration provided // LoadIPAMConfig creates IPAMConfig using json encoded configuration provided
// as `bytes`. At the moment values provided in envArgs are ignored so there // as `bytes`. At the moment values provided in envArgs are ignored so there
// is no possibility to overload the json configuration using envArgs // is no possibility to overload the json configuration using envArgs
func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, error) { func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
n := Net{} n := Net{}
if err := json.Unmarshal(bytes, &n); err != nil { if err := json.Unmarshal(bytes, &n); err != nil {
return nil, err return nil, "", err
} }
if n.IPAM == nil { if n.IPAM == nil {
return nil, fmt.Errorf("IPAM config missing 'ipam' key") return nil, "", fmt.Errorf("IPAM config missing 'ipam' key")
} }
// Validate all ranges // Validate all ranges
@ -103,13 +104,13 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, error) {
for i := range n.IPAM.Addresses { for i := range n.IPAM.Addresses {
ip, addr, err := net.ParseCIDR(n.IPAM.Addresses[i].AddressStr) ip, addr, err := net.ParseCIDR(n.IPAM.Addresses[i].AddressStr)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid CIDR %s: %s", n.IPAM.Addresses[i].AddressStr, err) return nil, "", fmt.Errorf("invalid CIDR %s: %s", n.IPAM.Addresses[i].AddressStr, err)
} }
n.IPAM.Addresses[i].Address = *addr n.IPAM.Addresses[i].Address = *addr
n.IPAM.Addresses[i].Address.IP = ip n.IPAM.Addresses[i].Address.IP = ip
if err := canonicalizeIP(&n.IPAM.Addresses[i].Address.IP); err != nil { if err := canonicalizeIP(&n.IPAM.Addresses[i].Address.IP); err != nil {
return nil, fmt.Errorf("invalid address %d: %s", i, err) return nil, "", fmt.Errorf("invalid address %d: %s", i, err)
} }
if n.IPAM.Addresses[i].Address.IP.To4() != nil { if n.IPAM.Addresses[i].Address.IP.To4() != nil {
@ -123,7 +124,7 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, error) {
e := IPAMEnvArgs{} e := IPAMEnvArgs{}
err := types.LoadArgs(envArgs, &e) err := types.LoadArgs(envArgs, &e)
if err != nil { if err != nil {
return nil, err return nil, "", err
} }
if e.IP != "" { if e.IP != "" {
@ -132,7 +133,7 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, error) {
ip, subnet, err := net.ParseCIDR(ipstr) ip, subnet, err := net.ParseCIDR(ipstr)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid CIDR %s: %s", ipstr, err) return nil, "", fmt.Errorf("invalid CIDR %s: %s", ipstr, err)
} }
addr := Address{Address: net.IPNet{IP: ip, Mask: subnet.Mask}} addr := Address{Address: net.IPNet{IP: ip, Mask: subnet.Mask}}
@ -149,7 +150,7 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, error) {
for _, item := range strings.Split(string(e.GATEWAY), ",") { for _, item := range strings.Split(string(e.GATEWAY), ",") {
gwip := net.ParseIP(strings.TrimSpace(item)) gwip := net.ParseIP(strings.TrimSpace(item))
if gwip == nil { if gwip == nil {
return nil, fmt.Errorf("invalid gateway address: %s", item) return nil, "", fmt.Errorf("invalid gateway address: %s", item)
} }
for i := range n.IPAM.Addresses { for i := range n.IPAM.Addresses {
@ -164,14 +165,14 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, error) {
// CNI spec 0.2.0 and below supported only one v4 and v6 address // CNI spec 0.2.0 and below supported only one v4 and v6 address
if numV4 > 1 || numV6 > 1 { if numV4 > 1 || numV6 > 1 {
if ok, _ := version.GreaterThanOrEqualTo(n.CNIVersion, "0.3.0"); !ok { if ok, _ := version.GreaterThanOrEqualTo(n.CNIVersion, "0.3.0"); !ok {
return nil, fmt.Errorf("CNI version %v does not support more than 1 address per family", n.CNIVersion) return nil, "", fmt.Errorf("CNI version %v does not support more than 1 address per family", n.CNIVersion)
} }
} }
// Copy net name into IPAM so not to drag Net struct around // Copy net name into IPAM so not to drag Net struct around
n.IPAM.Name = n.Name n.IPAM.Name = n.Name
return n.IPAM, nil return n.IPAM, n.CNIVersion, nil
} }
func buildOneConfig(name, cniVersion string, orig *Net, prevResult types.Result) (*Net, error) { func buildOneConfig(name, cniVersion string, orig *Net, prevResult types.Result) (*Net, error) {
@ -214,6 +215,7 @@ func buildOneConfig(name, cniVersion string, orig *Net, prevResult types.Result)
} }
return conf, nil return conf, nil
} }
type tester interface { type tester interface {
@ -223,11 +225,9 @@ type tester interface {
type testerBase struct{} type testerBase struct{}
type ( type testerV10x testerBase
testerV10x testerBase type testerV04x testerBase
testerV04x testerBase type testerV03x testerBase
testerV03x testerBase
)
func newTesterByVersion(version string) tester { func newTesterByVersion(version string) tester {
switch { switch {
@ -260,8 +260,8 @@ func (t *testerV10x) expectDpdkInterfaceIP(result types.Result, ipAddress string
// check that the result was sane // check that the result was sane
res, err := types100.NewResultFromResult(result) res, err := types100.NewResultFromResult(result)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(res.Interfaces).To(BeEmpty()) Expect(len(res.Interfaces)).To(Equal(0))
Expect(res.IPs).To(HaveLen(1)) Expect(len(res.IPs)).To(Equal(1))
Expect(res.IPs[0].Address.String()).To(Equal(ipAddress)) Expect(res.IPs[0].Address.String()).To(Equal(ipAddress))
} }
@ -282,8 +282,8 @@ func (t *testerV04x) expectDpdkInterfaceIP(result types.Result, ipAddress string
// check that the result was sane // check that the result was sane
res, err := types040.NewResultFromResult(result) res, err := types040.NewResultFromResult(result)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(res.Interfaces).To(BeEmpty()) Expect(len(res.Interfaces)).To(Equal(0))
Expect(res.IPs).To(HaveLen(1)) Expect(len(res.IPs)).To(Equal(1))
Expect(res.IPs[0].Address.String()).To(Equal(ipAddress)) Expect(res.IPs[0].Address.String()).To(Equal(ipAddress))
} }
@ -304,8 +304,8 @@ func (t *testerV03x) expectDpdkInterfaceIP(result types.Result, ipAddress string
// check that the result was sane // check that the result was sane
res, err := types040.NewResultFromResult(result) res, err := types040.NewResultFromResult(result)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(res.Interfaces).To(BeEmpty()) Expect(len(res.Interfaces)).To(Equal(0))
Expect(res.IPs).To(HaveLen(1)) Expect(len(res.IPs)).To(Equal(1))
Expect(res.IPs[0].Address.String()).To(Equal(ipAddress)) Expect(res.IPs[0].Address.String()).To(Equal(ipAddress))
} }
@ -665,11 +665,11 @@ var _ = Describe("base functionality", func() {
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr)) Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
Expect(link.Attrs().Flags & net.FlagUp).To(Equal(net.FlagUp)) Expect(link.Attrs().Flags & net.FlagUp).To(Equal(net.FlagUp))
// get the IP address of the interface in the target namespace //get the IP address of the interface in the target namespace
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4) addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
addr := addrs[0].IPNet.String() addr := addrs[0].IPNet.String()
// assert that IP address is what we set //assert that IP address is what we set
Expect(addr).To(Equal(targetIP)) Expect(addr).To(Equal(targetIP))
return nil return nil
@ -712,6 +712,7 @@ var _ = Describe("base functionality", func() {
} }
_, _, err := testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) }) _, _, err := testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) })
Expect(err).To(MatchError(`specify either "device", "hwaddr", "kernelpath" or "pciBusID"`)) Expect(err).To(MatchError(`specify either "device", "hwaddr", "kernelpath" or "pciBusID"`))
}) })
It(fmt.Sprintf("[%s] works with a valid config without IPAM", ver), func() { It(fmt.Sprintf("[%s] works with a valid config without IPAM", ver), func() {
@ -868,7 +869,7 @@ var _ = Describe("base functionality", func() {
err = json.Unmarshal([]byte(conf), &n) err = json.Unmarshal([]byte(conf), &n)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
n.IPAM, err = LoadIPAMConfig([]byte(conf), "") n.IPAM, _, err = LoadIPAMConfig([]byte(conf), "")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
if testutils.SpecVersionHasCHECK(ver) { if testutils.SpecVersionHasCHECK(ver) {
@ -898,71 +899,6 @@ 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()
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: ifname,
},
})
Expect(err).NotTo(HaveOccurred())
origLink, err = netlink.LinkByName(ifname)
Expect(err).NotTo(HaveOccurred())
err = netlink.LinkSetUp(origLink)
Expect(err).NotTo(HaveOccurred())
return nil
})
// call CmdAdd
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() { It(fmt.Sprintf("Works with a valid %s config with IPAM", ver), func() {
var origLink netlink.Link var origLink netlink.Link
@ -1026,11 +962,11 @@ var _ = Describe("base functionality", func() {
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr)) Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
Expect(link.Attrs().Flags & net.FlagUp).To(Equal(net.FlagUp)) Expect(link.Attrs().Flags & net.FlagUp).To(Equal(net.FlagUp))
// get the IP address of the interface in the target namespace //get the IP address of the interface in the target namespace
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4) addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
addr := addrs[0].IPNet.String() addr := addrs[0].IPNet.String()
// assert that IP address is what we set //assert that IP address is what we set
Expect(addr).To(Equal(targetIP)) Expect(addr).To(Equal(targetIP))
return nil return nil
@ -1049,7 +985,7 @@ var _ = Describe("base functionality", func() {
err = json.Unmarshal([]byte(conf), &n) err = json.Unmarshal([]byte(conf), &n)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
n.IPAM, err = LoadIPAMConfig([]byte(conf), "") n.IPAM, _, err = LoadIPAMConfig([]byte(conf), "")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
if testutils.SpecVersionHasCHECK(ver) { if testutils.SpecVersionHasCHECK(ver) {
@ -1214,114 +1150,6 @@ var _ = Describe("base functionality", func() {
return nil 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()
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: hostIfname,
},
})
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()
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: containerAdditionalIfname,
},
})
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
})
})
} }
}) })
@ -1333,14 +1161,14 @@ type fakeFilesystem struct {
func (fs *fakeFilesystem) use() func() { func (fs *fakeFilesystem) use() func() {
// create the new fake fs root dir in /tmp/sriov... // create the new fake fs root dir in /tmp/sriov...
tmpDir, err := os.MkdirTemp("", "sriov") tmpDir, err := ioutil.TempDir("", "sriov")
if err != nil { if err != nil {
panic(fmt.Errorf("error creating fake root dir: %s", err.Error())) panic(fmt.Errorf("error creating fake root dir: %s", err.Error()))
} }
fs.rootDir = tmpDir fs.rootDir = tmpDir
for _, dir := range fs.dirs { for _, dir := range fs.dirs {
err := os.MkdirAll(path.Join(fs.rootDir, dir), 0o755) err := os.MkdirAll(path.Join(fs.rootDir, dir), 0755)
if err != nil { if err != nil {
panic(fmt.Errorf("error creating fake directory: %s", err.Error())) panic(fmt.Errorf("error creating fake directory: %s", err.Error()))
} }
@ -1354,7 +1182,6 @@ func (fs *fakeFilesystem) use() func() {
} }
sysBusPCI = path.Join(fs.rootDir, "/sys/bus/pci/devices") sysBusPCI = path.Join(fs.rootDir, "/sys/bus/pci/devices")
sysBusAuxiliary = path.Join(fs.rootDir, "/sys/bus/auxiliary/devices")
return func() { return func() {
// remove temporary fake fs // remove temporary fake fs

Some files were not shown because too many files have changed in this diff Show More