Compare commits
55 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
6e7fb60738 | ||
![]() |
7c122fabb4 | ||
![]() |
e4ca66b414 | ||
![]() |
abfac4a938 | ||
![]() |
eded0afca8 | ||
![]() |
41d548592d | ||
![]() |
e8c7d9b930 | ||
![]() |
ba8bc7d0c7 | ||
![]() |
7f756b411e | ||
![]() |
3ffc42cdfd | ||
![]() |
6de8a9853c | ||
![]() |
9296c5f80a | ||
![]() |
fec2d62676 | ||
![]() |
a4fc6f93c7 | ||
![]() |
d61e7e5e1f | ||
![]() |
e4950728ce | ||
![]() |
93d197c455 | ||
![]() |
c52e02bccf | ||
![]() |
24b0bf96af | ||
![]() |
d44bbf28af | ||
![]() |
8ad0361964 | ||
![]() |
8324a2e5a4 | ||
![]() |
a4b80cc634 | ||
![]() |
3a49cff1f6 | ||
![]() |
c11ed48733 | ||
![]() |
fa737f82b2 | ||
![]() |
e5df283ab3 | ||
![]() |
cc8b1bd80c | ||
![]() |
03712a572b | ||
![]() |
01a94e17c7 | ||
![]() |
3d1968c152 | ||
![]() |
a3ccebc6ec | ||
![]() |
61d078645a | ||
![]() |
729dd23c40 | ||
![]() |
a6d6efa5ca | ||
![]() |
01b3db8e01 | ||
![]() |
20f31e5e88 | ||
![]() |
06ba001d84 | ||
![]() |
deb8ef63f4 | ||
![]() |
720b1e9811 | ||
![]() |
bdb6814fe2 | ||
![]() |
3653221fad | ||
![]() |
0d2780f0e7 | ||
![]() |
5def33291f | ||
![]() |
07bd325095 | ||
![]() |
7cff5db82a | ||
![]() |
d924f05e12 | ||
![]() |
6269f399a5 | ||
![]() |
5188dc8a19 | ||
![]() |
675ca92261 | ||
![]() |
30078e1cfd | ||
![]() |
acf8ddc8e1 | ||
![]() |
352c8b7ab5 | ||
![]() |
11ad58cf20 | ||
![]() |
d5f9ad99d7 |
2
.github/actions/retest-action/Dockerfile
vendored
2
.github/actions/retest-action/Dockerfile
vendored
@ -1,4 +1,4 @@
|
||||
FROM alpine:3.20
|
||||
FROM alpine:3.21
|
||||
|
||||
RUN apk add --no-cache curl jq
|
||||
|
||||
|
1
.github/go-version
vendored
Normal file
1
.github/go-version
vendored
Normal file
@ -0,0 +1 @@
|
||||
1.23
|
20
.github/workflows/release.yaml
vendored
20
.github/workflows/release.yaml
vendored
@ -13,13 +13,13 @@ jobs:
|
||||
matrix:
|
||||
goarch: [amd64, arm, arm64, mips64le, ppc64le, riscv64, s390x]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.22
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
go-version-file: .github/go-version
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
@ -32,7 +32,7 @@ jobs:
|
||||
|
||||
- name: Change plugin file ownership
|
||||
working-directory: ./bin
|
||||
run: sudo chown root:root ./*
|
||||
run: sudo chown -R root:root .
|
||||
|
||||
- name: Create dist directory
|
||||
run: mkdir dist
|
||||
@ -68,13 +68,13 @@ jobs:
|
||||
- name: Install dos2unix
|
||||
run: sudo apt-get install dos2unix
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.21
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
go-version-file: .github/go-version
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
@ -87,7 +87,7 @@ jobs:
|
||||
|
||||
- name: Change plugin file ownership
|
||||
working-directory: ./bin
|
||||
run: sudo chown root:root ./*
|
||||
run: sudo chown -R root:root .
|
||||
|
||||
- name: Create dist directory
|
||||
run: mkdir dist
|
||||
|
33
.github/workflows/test.yaml
vendored
33
.github/workflows/test.yaml
vendored
@ -1,10 +1,10 @@
|
||||
---
|
||||
name: test
|
||||
|
||||
on: ["push", "pull_request"]
|
||||
on:
|
||||
pull_request: {}
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.22"
|
||||
LINUX_ARCHES: "amd64 386 arm arm64 s390x mips64le ppc64le riscv64"
|
||||
|
||||
jobs:
|
||||
@ -16,15 +16,28 @@ jobs:
|
||||
- name: setup go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
go-version-file: .github/go-version
|
||||
- uses: ibiqlik/action-yamllint@v3
|
||||
with:
|
||||
format: auto
|
||||
- uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: v1.55.2
|
||||
version: v1.61.0
|
||||
args: -v
|
||||
skip-cache: true
|
||||
verify-vendor:
|
||||
name: Verify vendor directory
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: .github/go-version
|
||||
- name: Check module vendoring
|
||||
run: |
|
||||
go mod tidy
|
||||
go mod vendor
|
||||
test -z "$(git status --porcelain)" || (echo "please run 'go mod tidy && go mod vendor', and submit your changes"; exit 1)
|
||||
build:
|
||||
name: Build all linux architectures
|
||||
needs: lint
|
||||
@ -34,7 +47,7 @@ jobs:
|
||||
- name: setup go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
go-version-file: .github/go-version
|
||||
- name: Build on all supported architectures
|
||||
run: |
|
||||
set -e
|
||||
@ -54,11 +67,15 @@ jobs:
|
||||
sudo apt-get install linux-modules-extra-$(uname -r)
|
||||
- name: Install nftables
|
||||
run: sudo apt-get install nftables
|
||||
- name: Install dnsmasq(dhcp server)
|
||||
run: |
|
||||
sudo apt-get install dnsmasq
|
||||
sudo systemctl disable --now dnsmasq
|
||||
- uses: actions/checkout@v4
|
||||
- name: setup go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
go-version-file: .github/go-version
|
||||
- name: Set up Go for root
|
||||
run: |
|
||||
sudo ln -sf `which go` `sudo which go` || true
|
||||
@ -89,6 +106,6 @@ jobs:
|
||||
- name: setup go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
go-version-file: .github/go-version
|
||||
- name: test
|
||||
run: bash ./test_windows.sh
|
||||
|
@ -40,6 +40,5 @@ linters-settings:
|
||||
- prefix(github.com/containernetworking)
|
||||
|
||||
run:
|
||||
skip-dirs:
|
||||
- vendor
|
||||
timeout: 5m
|
||||
modules-download-mode: vendor
|
||||
|
56
go.mod
56
go.mod
@ -1,48 +1,54 @@
|
||||
module github.com/containernetworking/plugins
|
||||
|
||||
go 1.20
|
||||
go 1.23
|
||||
|
||||
require (
|
||||
github.com/Microsoft/hcsshim v0.12.3
|
||||
github.com/Microsoft/hcsshim v0.12.9
|
||||
github.com/alexflint/go-filemutex v1.3.0
|
||||
github.com/buger/jsonparser v1.1.1
|
||||
github.com/containernetworking/cni v1.1.2
|
||||
github.com/coreos/go-iptables v0.7.0
|
||||
github.com/containernetworking/cni v1.2.3
|
||||
github.com/coreos/go-iptables v0.8.0
|
||||
github.com/coreos/go-systemd/v22 v22.5.0
|
||||
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c
|
||||
github.com/d2g/dhcp4client v1.0.0
|
||||
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5
|
||||
github.com/godbus/dbus/v5 v5.1.0
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475
|
||||
github.com/mattn/go-shellwords v1.0.12
|
||||
github.com/networkplumbing/go-nft v0.4.0
|
||||
github.com/onsi/ginkgo/v2 v2.19.0
|
||||
github.com/onsi/gomega v1.33.1
|
||||
github.com/opencontainers/selinux v1.11.0
|
||||
github.com/safchain/ethtool v0.4.0
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2
|
||||
golang.org/x/sys v0.21.0
|
||||
github.com/onsi/ginkgo/v2 v2.22.2
|
||||
github.com/onsi/gomega v1.36.2
|
||||
github.com/opencontainers/selinux v1.11.1
|
||||
github.com/safchain/ethtool v0.5.9
|
||||
github.com/vishvananda/netlink v1.3.0
|
||||
golang.org/x/sys v0.29.0
|
||||
sigs.k8s.io/knftables v0.0.18
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/containerd/cgroups/v3 v3.0.2 // indirect
|
||||
github.com/containerd/errdefs v0.1.0 // indirect
|
||||
github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/containerd/cgroups/v3 v3.0.3 // indirect
|
||||
github.com/containerd/errdefs v0.3.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/containerd/typeurl/v2 v2.2.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/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/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/mdlayher/packet v1.1.2 // indirect
|
||||
github.com/mdlayher/socket v0.5.1 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
golang.org/x/tools v0.21.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect
|
||||
google.golang.org/grpc v1.62.0 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/tools v0.28.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||
google.golang.org/grpc v1.67.0 // indirect
|
||||
google.golang.org/protobuf v1.36.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
170
go.sum
170
go.sum
@ -2,36 +2,29 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/Microsoft/hcsshim v0.12.3 h1:LS9NXqXhMoqNCplK1ApmVSfB4UnVLRDWRapB6EIlxE0=
|
||||
github.com/Microsoft/hcsshim v0.12.3/go.mod h1:Iyl1WVpZzr+UkzjekHZbV8o5Z9ZkxNGx6CtY2Qg/JVQ=
|
||||
github.com/Microsoft/hcsshim v0.12.9 h1:2zJy5KA+l0loz1HzEGqyNnjd3fyZA31ZBCGKacp6lLg=
|
||||
github.com/Microsoft/hcsshim v0.12.9/go.mod h1:fJ0gkFAna6ukt0bLdKB8djt4XIJhF/vEPuoIWYVvZ8Y=
|
||||
github.com/alexflint/go-filemutex v1.3.0 h1:LgE+nTUWnQCyRKbpoceKZsPQbs84LivvgwUymZXdOcM=
|
||||
github.com/alexflint/go-filemutex v1.3.0/go.mod h1:U0+VA/i30mGBlLCrFPGtTe9y6wGQfNAWPBTekHQ+c8A=
|
||||
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/containerd/cgroups/v3 v3.0.2 h1:f5WFqIVSgo5IZmtTT3qVBo6TzI1ON6sycSBKkymb9L0=
|
||||
github.com/containerd/cgroups/v3 v3.0.2/go.mod h1:JUgITrzdFqp42uI2ryGA+ge0ap/nxzYgkGmIcetmErE=
|
||||
github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM=
|
||||
github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0=
|
||||
github.com/containernetworking/cni v1.1.2 h1:wtRGZVv7olUHMOqouPpn3cXJWpJgM6+EUl31EQbXALQ=
|
||||
github.com/containernetworking/cni v1.1.2/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw=
|
||||
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
|
||||
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||
github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0=
|
||||
github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0=
|
||||
github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4=
|
||||
github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||
github.com/containerd/typeurl/v2 v2.2.0 h1:6NBDbQzr7I5LHgp34xAXYF5DOTQDn05X58lsPEmzLso=
|
||||
github.com/containerd/typeurl/v2 v2.2.0/go.mod h1:8XOOxnyatxSWuG8OfsZXVnAF4iZfedjS/8UHSPJnX4g=
|
||||
github.com/containernetworking/cni v1.2.3 h1:hhOcjNVUQTnzdRJ6alC5XF+wd9mfGIUaj8FuJbEslXM=
|
||||
github.com/containernetworking/cni v1.2.3/go.mod h1:DuLgF+aPd3DzcTQTtp/Nvl1Kim23oFKdm2okJzBQA5M=
|
||||
github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc=
|
||||
github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c h1:Xo2rK1pzOm0jO6abTPIQwbAmqBIOj132otexc1mmzFc=
|
||||
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
|
||||
github.com/d2g/dhcp4client v1.0.0 h1:suYBsYZIkSlUMEz4TAYCczKf62IA2UWC+O8+KtdOhCo=
|
||||
github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
|
||||
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5 h1:+CpLbZIeUn94m02LdEKPcgErLJ347NUwxPKs5u8ieiY=
|
||||
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=
|
||||
github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4 h1:itqmmf1PFpC4n5JW+j4BU7X4MTfVurhYRTjODoPb2Y8=
|
||||
github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@ -39,16 +32,15 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
@ -62,70 +54,70 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg=
|
||||
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
|
||||
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475 h1:hxST5pwMBEOWmxpkX20w9oZG+hXdhKmAIPQ3NGGAxas=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic=
|
||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY=
|
||||
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
|
||||
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/mdlayher/packet v1.1.2 h1:3Up1NG6LZrsgDVn6X4L9Ge/iyRyxFEFD9o6Pr3Q1nQY=
|
||||
github.com/mdlayher/packet v1.1.2/go.mod h1:GEu1+n9sG5VtiRE4SydOmX5GTwyyYlteZiFU+x0kew4=
|
||||
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
|
||||
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
|
||||
github.com/networkplumbing/go-nft v0.4.0 h1:kExVMwXW48DOAukkBwyI16h4uhE5lN9iMvQd52lpTyU=
|
||||
github.com/networkplumbing/go-nft v0.4.0/go.mod h1:HnnM+tYvlGAsMU7yoYwXEVLLiDW9gdMmb5HoGcwpuQs=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
|
||||
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
|
||||
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
|
||||
github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU=
|
||||
github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec=
|
||||
github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU=
|
||||
github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk=
|
||||
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
|
||||
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
|
||||
github.com/opencontainers/selinux v1.11.1 h1:nHFvthhM0qY8/m+vfhJylliSshm8G1jJ2jDMcgULaH8=
|
||||
github.com/opencontainers/selinux v1.11.1/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec=
|
||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/safchain/ethtool v0.4.0 h1:vq1i2HCjshJNywOXFZ1BpwIjyeFR/kvNdHiRzqSElDI=
|
||||
github.com/safchain/ethtool v0.4.0/go.mod h1:XLLnZmy4OCRTkksP/UiMjij96YmIsBfmBQcs7H6tA48=
|
||||
github.com/safchain/ethtool v0.5.9 h1://6RvaOKFf3nQ0rl5+8zBbE4/72455VC9Jq61pfq67E=
|
||||
github.com/safchain/ethtool v0.5.9/go.mod h1:w8oSsZeowyRaM7xJJBAbubzzrOkwO8TBgPSEqPP/5mg=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
|
||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
|
||||
github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk=
|
||||
github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs=
|
||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
@ -137,65 +129,60 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
|
||||
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
|
||||
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@ -205,15 +192,15 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk=
|
||||
google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
|
||||
google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw=
|
||||
google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@ -223,20 +210,15 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
|
||||
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
sigs.k8s.io/knftables v0.0.18 h1:6Duvmu0s/HwGifKrtl6G3AyAPYlWiZqTgS8bkVMiyaE=
|
||||
sigs.k8s.io/knftables v0.0.18/go.mod h1:f/5ZLKYEUPUhVjUCg6l80ACdL7CIIyeL0DxfgojGRTk=
|
||||
|
@ -181,20 +181,20 @@ var _ = Describe("Basic PTP using cnitool", func() {
|
||||
By(fmt.Sprintf("starting echo server in %s\n\n", contNS2.ShortName()))
|
||||
basicBridgePort, basicBridgeSession = startEchoServerInNamespace(contNS2)
|
||||
|
||||
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))
|
||||
start := time.Now()
|
||||
makeTCPClientInNS(hostNS.ShortName(), chainedBridgeIP, chainedBridgeBandwidthPort, packetInBytes)
|
||||
runtimeWithLimit := time.Since(start)
|
||||
|
||||
log.Printf("Runtime with qos limit %.2f seconds", runtimeWithLimit.Seconds())
|
||||
|
||||
By(fmt.Sprintf("sending tcp traffic to the basic bridged container on ip address '%s:%d'\n\n", basicBridgeIP, basicBridgePort))
|
||||
start = time.Now()
|
||||
makeTCPClientInNS(hostNS.ShortName(), basicBridgeIP, basicBridgePort, packetInBytes)
|
||||
runtimeWithoutLimit := time.Since(start)
|
||||
log.Printf("Runtime without qos limit %.2f seconds", runtimeWithLimit.Seconds())
|
||||
log.Printf("Runtime without qos limit %.2f seconds", runtimeWithoutLimit.Seconds())
|
||||
|
||||
Expect(runtimeWithLimit).To(BeNumerically(">", runtimeWithoutLimit+1000*time.Millisecond))
|
||||
})
|
||||
|
@ -43,7 +43,7 @@ func TestAnnotate(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if !reflect.DeepEqual(Annotatef(test.existingErr, test.contextMessage), test.expectedErr) {
|
||||
if !reflect.DeepEqual(Annotate(test.existingErr, test.contextMessage), test.expectedErr) {
|
||||
t.Errorf("test case %s fails", test.name)
|
||||
return
|
||||
}
|
||||
|
180
pkg/ip/ipmasq_iptables_linux.go
Normal file
180
pkg/ip/ipmasq_iptables_linux.go
Normal file
@ -0,0 +1,180 @@
|
||||
// Copyright 2015 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ip
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/plugins/pkg/utils"
|
||||
)
|
||||
|
||||
// setupIPMasqIPTables is the iptables-based implementation of SetupIPMasqForNetworks
|
||||
func setupIPMasqIPTables(ipns []*net.IPNet, network, _, containerID string) error {
|
||||
// Note: for historical reasons, the iptables implementation ignores ifname.
|
||||
chain := utils.FormatChainName(network, containerID)
|
||||
comment := utils.FormatComment(network, containerID)
|
||||
for _, ip := range ipns {
|
||||
if err := SetupIPMasq(ip, chain, comment); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetupIPMasq installs iptables rules to masquerade traffic
|
||||
// coming from ip of ipn and going outside of ipn.
|
||||
// Deprecated: This function only supports iptables. Use SetupIPMasqForNetworks, which
|
||||
// supports both iptables and nftables.
|
||||
func SetupIPMasq(ipn *net.IPNet, chain string, comment string) error {
|
||||
isV6 := ipn.IP.To4() == nil
|
||||
|
||||
var ipt *iptables.IPTables
|
||||
var err error
|
||||
var multicastNet string
|
||||
|
||||
if isV6 {
|
||||
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv6)
|
||||
multicastNet = "ff00::/8"
|
||||
} else {
|
||||
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||
multicastNet = "224.0.0.0/4"
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to locate iptables: %v", err)
|
||||
}
|
||||
|
||||
// Create chain if doesn't exist
|
||||
exists := false
|
||||
chains, err := ipt.ListChains("nat")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list chains: %v", err)
|
||||
}
|
||||
for _, ch := range chains {
|
||||
if ch == chain {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exists {
|
||||
if err = ipt.NewChain("nat", chain); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Packets to this network should not be touched
|
||||
if err := ipt.AppendUnique("nat", chain, "-d", ipn.String(), "-j", "ACCEPT", "-m", "comment", "--comment", comment); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Don't masquerade multicast - pods should be able to talk to other pods
|
||||
// on the local network via multicast.
|
||||
if err := ipt.AppendUnique("nat", chain, "!", "-d", multicastNet, "-j", "MASQUERADE", "-m", "comment", "--comment", comment); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Packets from the specific IP of this network will hit the chain
|
||||
return ipt.AppendUnique("nat", "POSTROUTING", "-s", ipn.IP.String(), "-j", chain, "-m", "comment", "--comment", comment)
|
||||
}
|
||||
|
||||
// teardownIPMasqIPTables is the iptables-based implementation of TeardownIPMasqForNetworks
|
||||
func teardownIPMasqIPTables(ipns []*net.IPNet, network, _, containerID string) error {
|
||||
// Note: for historical reasons, the iptables implementation ignores ifname.
|
||||
chain := utils.FormatChainName(network, containerID)
|
||||
comment := utils.FormatComment(network, containerID)
|
||||
|
||||
var errs []string
|
||||
for _, ipn := range ipns {
|
||||
err := TeardownIPMasq(ipn, chain, comment)
|
||||
if err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if errs == nil {
|
||||
return nil
|
||||
}
|
||||
return errors.New(strings.Join(errs, "\n"))
|
||||
}
|
||||
|
||||
// TeardownIPMasq undoes the effects of SetupIPMasq.
|
||||
// Deprecated: This function only supports iptables. Use TeardownIPMasqForNetworks, which
|
||||
// supports both iptables and nftables.
|
||||
func TeardownIPMasq(ipn *net.IPNet, chain string, comment string) error {
|
||||
isV6 := ipn.IP.To4() == nil
|
||||
|
||||
var ipt *iptables.IPTables
|
||||
var err error
|
||||
|
||||
if isV6 {
|
||||
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv6)
|
||||
} else {
|
||||
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to locate iptables: %v", err)
|
||||
}
|
||||
|
||||
err = ipt.Delete("nat", "POSTROUTING", "-s", ipn.IP.String(), "-j", chain, "-m", "comment", "--comment", comment)
|
||||
if err != nil && !isNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
// for downward compatibility
|
||||
err = ipt.Delete("nat", "POSTROUTING", "-s", ipn.String(), "-j", chain, "-m", "comment", "--comment", comment)
|
||||
if err != nil && !isNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ipt.ClearChain("nat", chain)
|
||||
if err != nil && !isNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ipt.DeleteChain("nat", chain)
|
||||
if err != nil && !isNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// gcIPMasqIPTables is the iptables-based implementation of GCIPMasqForNetwork
|
||||
func gcIPMasqIPTables(_ string, _ []types.GCAttachment) error {
|
||||
// FIXME: The iptables implementation does not support GC.
|
||||
//
|
||||
// (In theory, it _could_ backward-compatibly support it, by adding a no-op rule
|
||||
// with a comment indicating the network to each chain it creates, so that it
|
||||
// could later figure out which chains corresponded to which networks; older
|
||||
// implementations would ignore the extra rule but would still correctly delete
|
||||
// the chain on teardown (because they ClearChain() before doing DeleteChain()).
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isNotExist returnst true if the error is from iptables indicating
|
||||
// that the target does not exist.
|
||||
func isNotExist(err error) bool {
|
||||
e, ok := err.(*iptables.Error)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return e.IsNotExist()
|
||||
}
|
@ -15,111 +15,78 @@
|
||||
package ip
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/plugins/pkg/utils"
|
||||
)
|
||||
|
||||
// SetupIPMasq installs iptables rules to masquerade traffic
|
||||
// coming from ip of ipn and going outside of ipn
|
||||
func SetupIPMasq(ipn *net.IPNet, chain string, comment string) error {
|
||||
isV6 := ipn.IP.To4() == nil
|
||||
|
||||
var ipt *iptables.IPTables
|
||||
var err error
|
||||
var multicastNet string
|
||||
|
||||
if isV6 {
|
||||
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv6)
|
||||
multicastNet = "ff00::/8"
|
||||
} else {
|
||||
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||
multicastNet = "224.0.0.0/4"
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to locate iptables: %v", err)
|
||||
}
|
||||
|
||||
// Create chain if doesn't exist
|
||||
exists := false
|
||||
chains, err := ipt.ListChains("nat")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list chains: %v", err)
|
||||
}
|
||||
for _, ch := range chains {
|
||||
if ch == chain {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exists {
|
||||
if err = ipt.NewChain("nat", chain); err != nil {
|
||||
return err
|
||||
// SetupIPMasqForNetworks installs rules to masquerade traffic coming from ips of ipns and
|
||||
// going outside of ipns, using a chain name based on network, ifname, and containerID. The
|
||||
// backend can be either "iptables" or "nftables"; if it is nil, then a suitable default
|
||||
// implementation will be used.
|
||||
func SetupIPMasqForNetworks(backend *string, ipns []*net.IPNet, network, ifname, containerID string) error {
|
||||
if backend == nil {
|
||||
// Prefer iptables, unless only nftables is available
|
||||
defaultBackend := "iptables"
|
||||
if !utils.SupportsIPTables() && utils.SupportsNFTables() {
|
||||
defaultBackend = "nftables"
|
||||
}
|
||||
backend = &defaultBackend
|
||||
}
|
||||
|
||||
// Packets to this network should not be touched
|
||||
if err := ipt.AppendUnique("nat", chain, "-d", ipn.String(), "-j", "ACCEPT", "-m", "comment", "--comment", comment); err != nil {
|
||||
return err
|
||||
switch *backend {
|
||||
case "iptables":
|
||||
return setupIPMasqIPTables(ipns, network, ifname, containerID)
|
||||
case "nftables":
|
||||
return setupIPMasqNFTables(ipns, network, ifname, containerID)
|
||||
default:
|
||||
return fmt.Errorf("unknown ipmasq backend %q", *backend)
|
||||
}
|
||||
|
||||
// Don't masquerade multicast - pods should be able to talk to other pods
|
||||
// on the local network via multicast.
|
||||
if err := ipt.AppendUnique("nat", chain, "!", "-d", multicastNet, "-j", "MASQUERADE", "-m", "comment", "--comment", comment); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Packets from the specific IP of this network will hit the chain
|
||||
return ipt.AppendUnique("nat", "POSTROUTING", "-s", ipn.IP.String(), "-j", chain, "-m", "comment", "--comment", comment)
|
||||
}
|
||||
|
||||
// TeardownIPMasq undoes the effects of SetupIPMasq
|
||||
func TeardownIPMasq(ipn *net.IPNet, chain string, comment string) error {
|
||||
isV6 := ipn.IP.To4() == nil
|
||||
// TeardownIPMasqForNetworks undoes the effects of SetupIPMasqForNetworks
|
||||
func TeardownIPMasqForNetworks(ipns []*net.IPNet, network, ifname, containerID string) error {
|
||||
var errs []string
|
||||
|
||||
var ipt *iptables.IPTables
|
||||
var err error
|
||||
// Do both the iptables and the nftables cleanup, since the pod may have been
|
||||
// created with a different version of this plugin or a different configuration.
|
||||
|
||||
if isV6 {
|
||||
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv6)
|
||||
} else {
|
||||
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to locate iptables: %v", err)
|
||||
err := teardownIPMasqIPTables(ipns, network, ifname, containerID)
|
||||
if err != nil && utils.SupportsIPTables() {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
|
||||
err = ipt.Delete("nat", "POSTROUTING", "-s", ipn.IP.String(), "-j", chain, "-m", "comment", "--comment", comment)
|
||||
if err != nil && !isNotExist(err) {
|
||||
return err
|
||||
err = teardownIPMasqNFTables(ipns, network, ifname, containerID)
|
||||
if err != nil && utils.SupportsNFTables() {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
|
||||
// for downward compatibility
|
||||
err = ipt.Delete("nat", "POSTROUTING", "-s", ipn.String(), "-j", chain, "-m", "comment", "--comment", comment)
|
||||
if err != nil && !isNotExist(err) {
|
||||
return err
|
||||
if errs == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = ipt.ClearChain("nat", chain)
|
||||
if err != nil && !isNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ipt.DeleteChain("nat", chain)
|
||||
if err != nil && !isNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return errors.New(strings.Join(errs, "\n"))
|
||||
}
|
||||
|
||||
// isNotExist returnst true if the error is from iptables indicating
|
||||
// that the target does not exist.
|
||||
func isNotExist(err error) bool {
|
||||
e, ok := err.(*iptables.Error)
|
||||
if !ok {
|
||||
return false
|
||||
// GCIPMasqForNetwork garbage collects stale IPMasq entries for network
|
||||
func GCIPMasqForNetwork(network string, attachments []types.GCAttachment) error {
|
||||
var errs []string
|
||||
|
||||
err := gcIPMasqIPTables(network, attachments)
|
||||
if err != nil && utils.SupportsIPTables() {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
return e.IsNotExist()
|
||||
|
||||
err = gcIPMasqNFTables(network, attachments)
|
||||
if err != nil && utils.SupportsNFTables() {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
|
||||
if errs == nil {
|
||||
return nil
|
||||
}
|
||||
return errors.New(strings.Join(errs, "\n"))
|
||||
}
|
||||
|
231
pkg/ip/ipmasq_nftables_linux.go
Normal file
231
pkg/ip/ipmasq_nftables_linux.go
Normal file
@ -0,0 +1,231 @@
|
||||
// Copyright 2023 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ip
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/knftables"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/plugins/pkg/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
ipMasqTableName = "cni_plugins_masquerade"
|
||||
ipMasqChainName = "masq_checks"
|
||||
)
|
||||
|
||||
// The nftables ipmasq implementation is mostly like the iptables implementation, with
|
||||
// minor updates to fix a bug (adding `ifname`) and to allow future GC support.
|
||||
//
|
||||
// We add a rule for each mapping, with a comment containing a hash of its identifiers,
|
||||
// so that we can later reliably delete the rules we want. (This is important because in
|
||||
// edge cases, it's possible the plugin might see "ADD container A with IP 192.168.1.3",
|
||||
// followed by "ADD container B with IP 192.168.1.3" followed by "DEL container A with IP
|
||||
// 192.168.1.3", and we need to make sure that the DEL causes us to delete the rule for
|
||||
// container A, and not the rule for container B.)
|
||||
//
|
||||
// It would be more nftables-y to have a chain with a single rule doing a lookup against a
|
||||
// set with an element per mapping, rather than having a chain with a rule per mapping.
|
||||
// But there's no easy, non-racy way to say "delete the element 192.168.1.3 from the set,
|
||||
// but only if it was added for container A, not if it was added for container B".
|
||||
|
||||
// hashForNetwork returns a unique hash for this network
|
||||
func hashForNetwork(network string) string {
|
||||
return utils.MustFormatHashWithPrefix(16, "", network)
|
||||
}
|
||||
|
||||
// hashForInstance returns a unique hash identifying the rules for this
|
||||
// network/ifname/containerID
|
||||
func hashForInstance(network, ifname, containerID string) string {
|
||||
return hashForNetwork(network) + "-" + utils.MustFormatHashWithPrefix(16, "", ifname+":"+containerID)
|
||||
}
|
||||
|
||||
// commentForInstance returns a comment string that begins with a unique hash and
|
||||
// ends with a (possibly-truncated) human-readable description.
|
||||
func commentForInstance(network, ifname, containerID string) string {
|
||||
comment := fmt.Sprintf("%s, net: %s, if: %s, id: %s",
|
||||
hashForInstance(network, ifname, containerID),
|
||||
strings.ReplaceAll(network, `"`, ``),
|
||||
strings.ReplaceAll(ifname, `"`, ``),
|
||||
strings.ReplaceAll(containerID, `"`, ``),
|
||||
)
|
||||
if len(comment) > knftables.CommentLengthMax {
|
||||
comment = comment[:knftables.CommentLengthMax]
|
||||
}
|
||||
return comment
|
||||
}
|
||||
|
||||
// setupIPMasqNFTables is the nftables-based implementation of SetupIPMasqForNetworks
|
||||
func setupIPMasqNFTables(ipns []*net.IPNet, network, ifname, containerID string) error {
|
||||
nft, err := knftables.New(knftables.InetFamily, ipMasqTableName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return setupIPMasqNFTablesWithInterface(nft, ipns, network, ifname, containerID)
|
||||
}
|
||||
|
||||
func setupIPMasqNFTablesWithInterface(nft knftables.Interface, ipns []*net.IPNet, network, ifname, containerID string) error {
|
||||
staleRules, err := findRules(nft, hashForInstance(network, ifname, containerID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tx := nft.NewTransaction()
|
||||
|
||||
// Ensure that our table and chains exist.
|
||||
tx.Add(&knftables.Table{
|
||||
Comment: knftables.PtrTo("Masquerading for plugins from github.com/containernetworking/plugins"),
|
||||
})
|
||||
tx.Add(&knftables.Chain{
|
||||
Name: ipMasqChainName,
|
||||
Comment: knftables.PtrTo("Masquerade traffic from certain IPs to any (non-multicast) IP outside their subnet"),
|
||||
})
|
||||
|
||||
// Ensure that the postrouting chain exists and has the correct rules. (Has to be
|
||||
// done after creating ipMasqChainName, so we can jump to it.)
|
||||
tx.Add(&knftables.Chain{
|
||||
Name: "postrouting",
|
||||
Type: knftables.PtrTo(knftables.NATType),
|
||||
Hook: knftables.PtrTo(knftables.PostroutingHook),
|
||||
Priority: knftables.PtrTo(knftables.SNATPriority),
|
||||
})
|
||||
tx.Flush(&knftables.Chain{
|
||||
Name: "postrouting",
|
||||
})
|
||||
tx.Add(&knftables.Rule{
|
||||
Chain: "postrouting",
|
||||
Rule: "ip daddr == 224.0.0.0/4 return",
|
||||
})
|
||||
tx.Add(&knftables.Rule{
|
||||
Chain: "postrouting",
|
||||
Rule: "ip6 daddr == ff00::/8 return",
|
||||
})
|
||||
tx.Add(&knftables.Rule{
|
||||
Chain: "postrouting",
|
||||
Rule: knftables.Concat(
|
||||
"goto", ipMasqChainName,
|
||||
),
|
||||
})
|
||||
|
||||
// Delete stale rules, add new rules to masquerade chain
|
||||
for _, rule := range staleRules {
|
||||
tx.Delete(rule)
|
||||
}
|
||||
for _, ipn := range ipns {
|
||||
ip := "ip"
|
||||
if ipn.IP.To4() == nil {
|
||||
ip = "ip6"
|
||||
}
|
||||
|
||||
// e.g. if ipn is "192.168.1.4/24", then dstNet is "192.168.1.0/24"
|
||||
dstNet := &net.IPNet{IP: ipn.IP.Mask(ipn.Mask), Mask: ipn.Mask}
|
||||
|
||||
tx.Add(&knftables.Rule{
|
||||
Chain: ipMasqChainName,
|
||||
Rule: knftables.Concat(
|
||||
ip, "saddr", "==", ipn.IP,
|
||||
ip, "daddr", "!=", dstNet,
|
||||
"masquerade",
|
||||
),
|
||||
Comment: knftables.PtrTo(commentForInstance(network, ifname, containerID)),
|
||||
})
|
||||
}
|
||||
|
||||
return nft.Run(context.TODO(), tx)
|
||||
}
|
||||
|
||||
// teardownIPMasqNFTables is the nftables-based implementation of TeardownIPMasqForNetworks
|
||||
func teardownIPMasqNFTables(ipns []*net.IPNet, network, ifname, containerID string) error {
|
||||
nft, err := knftables.New(knftables.InetFamily, ipMasqTableName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return teardownIPMasqNFTablesWithInterface(nft, ipns, network, ifname, containerID)
|
||||
}
|
||||
|
||||
func teardownIPMasqNFTablesWithInterface(nft knftables.Interface, _ []*net.IPNet, network, ifname, containerID string) error {
|
||||
rules, err := findRules(nft, hashForInstance(network, ifname, containerID))
|
||||
if err != nil {
|
||||
return err
|
||||
} else if len(rules) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
tx := nft.NewTransaction()
|
||||
for _, rule := range rules {
|
||||
tx.Delete(rule)
|
||||
}
|
||||
return nft.Run(context.TODO(), tx)
|
||||
}
|
||||
|
||||
// gcIPMasqNFTables is the nftables-based implementation of GCIPMasqForNetwork
|
||||
func gcIPMasqNFTables(network string, attachments []types.GCAttachment) error {
|
||||
nft, err := knftables.New(knftables.InetFamily, ipMasqTableName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return gcIPMasqNFTablesWithInterface(nft, network, attachments)
|
||||
}
|
||||
|
||||
func gcIPMasqNFTablesWithInterface(nft knftables.Interface, network string, attachments []types.GCAttachment) error {
|
||||
// Find all rules for the network
|
||||
rules, err := findRules(nft, hashForNetwork(network))
|
||||
if err != nil {
|
||||
return err
|
||||
} else if len(rules) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compute the comments for all elements of attachments
|
||||
validAttachments := map[string]bool{}
|
||||
for _, attachment := range attachments {
|
||||
validAttachments[commentForInstance(network, attachment.IfName, attachment.ContainerID)] = true
|
||||
}
|
||||
|
||||
// Delete anything in rules that isn't in validAttachments
|
||||
tx := nft.NewTransaction()
|
||||
for _, rule := range rules {
|
||||
if !validAttachments[*rule.Comment] {
|
||||
tx.Delete(rule)
|
||||
}
|
||||
}
|
||||
return nft.Run(context.TODO(), tx)
|
||||
}
|
||||
|
||||
// findRules finds rules with comments that start with commentPrefix.
|
||||
func findRules(nft knftables.Interface, commentPrefix string) ([]*knftables.Rule, error) {
|
||||
rules, err := nft.ListRules(context.TODO(), ipMasqChainName)
|
||||
if err != nil {
|
||||
if knftables.IsNotFound(err) {
|
||||
// If ipMasqChainName doesn't exist yet, that's fine
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
matchingRules := make([]*knftables.Rule, 0, 1)
|
||||
for _, rule := range rules {
|
||||
if rule.Comment != nil && strings.HasPrefix(*rule.Comment, commentPrefix) {
|
||||
matchingRules = append(matchingRules, rule)
|
||||
}
|
||||
}
|
||||
|
||||
return matchingRules, nil
|
||||
}
|
213
pkg/ip/ipmasq_nftables_linux_test.go
Normal file
213
pkg/ip/ipmasq_nftables_linux_test.go
Normal file
@ -0,0 +1,213 @@
|
||||
// Copyright 2023 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ip
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
"sigs.k8s.io/knftables"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
)
|
||||
|
||||
func Test_setupIPMasqNFTables(t *testing.T) {
|
||||
nft := knftables.NewFake(knftables.InetFamily, ipMasqTableName)
|
||||
|
||||
containers := []struct {
|
||||
network string
|
||||
ifname string
|
||||
containerID string
|
||||
addrs []string
|
||||
}{
|
||||
{
|
||||
network: "unit-test",
|
||||
ifname: "eth0",
|
||||
containerID: "one",
|
||||
addrs: []string{"192.168.1.1/24"},
|
||||
},
|
||||
{
|
||||
network: "unit-test",
|
||||
ifname: "eth0",
|
||||
containerID: "two",
|
||||
addrs: []string{"192.168.1.2/24", "2001:db8::2/64"},
|
||||
},
|
||||
{
|
||||
network: "unit-test",
|
||||
ifname: "eth0",
|
||||
containerID: "three",
|
||||
addrs: []string{"192.168.99.5/24"},
|
||||
},
|
||||
{
|
||||
network: "alternate",
|
||||
ifname: "net1",
|
||||
containerID: "three",
|
||||
addrs: []string{
|
||||
"10.0.0.5/24",
|
||||
"10.0.0.6/24",
|
||||
"10.0.1.7/24",
|
||||
"2001:db8::5/64",
|
||||
"2001:db8::6/64",
|
||||
"2001:db8:1::7/64",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range containers {
|
||||
ipns := []*net.IPNet{}
|
||||
for _, addr := range c.addrs {
|
||||
nladdr, err := netlink.ParseAddr(addr)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse test addr: %v", err)
|
||||
}
|
||||
ipns = append(ipns, nladdr.IPNet)
|
||||
}
|
||||
err := setupIPMasqNFTablesWithInterface(nft, ipns, c.network, c.ifname, c.containerID)
|
||||
if err != nil {
|
||||
t.Fatalf("error from setupIPMasqNFTables: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
expected := strings.TrimSpace(`
|
||||
add table inet cni_plugins_masquerade { comment "Masquerading for plugins from github.com/containernetworking/plugins" ; }
|
||||
add chain inet cni_plugins_masquerade masq_checks { comment "Masquerade traffic from certain IPs to any (non-multicast) IP outside their subnet" ; }
|
||||
add chain inet cni_plugins_masquerade postrouting { type nat hook postrouting priority 100 ; }
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 192.168.1.1 ip daddr != 192.168.1.0/24 masquerade comment "6fd94d501e58f0aa-287fc69eff0574a2, net: unit-test, if: eth0, id: one"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 192.168.1.2 ip daddr != 192.168.1.0/24 masquerade comment "6fd94d501e58f0aa-d750b2c8f0f25d5f, net: unit-test, if: eth0, id: two"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8::2 ip6 daddr != 2001:db8::/64 masquerade comment "6fd94d501e58f0aa-d750b2c8f0f25d5f, net: unit-test, if: eth0, id: two"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 192.168.99.5 ip daddr != 192.168.99.0/24 masquerade comment "6fd94d501e58f0aa-a4d4adb82b669cfe, net: unit-test, if: eth0, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 10.0.0.5 ip daddr != 10.0.0.0/24 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 10.0.0.6 ip daddr != 10.0.0.0/24 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 10.0.1.7 ip daddr != 10.0.1.0/24 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8::5 ip6 daddr != 2001:db8::/64 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8::6 ip6 daddr != 2001:db8::/64 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8:1::7 ip6 daddr != 2001:db8:1::/64 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade postrouting ip daddr == 224.0.0.0/4 return
|
||||
add rule inet cni_plugins_masquerade postrouting ip6 daddr == ff00::/8 return
|
||||
add rule inet cni_plugins_masquerade postrouting goto masq_checks
|
||||
`)
|
||||
dump := strings.TrimSpace(nft.Dump())
|
||||
if dump != expected {
|
||||
t.Errorf("expected nftables state:\n%s\n\nactual:\n%s\n\n", expected, dump)
|
||||
}
|
||||
|
||||
// Add a new container reusing "one"'s address, before deleting "one"
|
||||
c := containers[0]
|
||||
addr, err := netlink.ParseAddr(c.addrs[0])
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse test addr: %v", err)
|
||||
}
|
||||
err = setupIPMasqNFTablesWithInterface(nft, []*net.IPNet{addr.IPNet}, "unit-test", "eth0", "four")
|
||||
if err != nil {
|
||||
t.Fatalf("error from setupIPMasqNFTables: %v", err)
|
||||
}
|
||||
|
||||
// Remove "one"
|
||||
err = teardownIPMasqNFTablesWithInterface(nft, []*net.IPNet{addr.IPNet}, c.network, c.ifname, c.containerID)
|
||||
if err != nil {
|
||||
t.Fatalf("error from teardownIPMasqNFTables: %v", err)
|
||||
}
|
||||
|
||||
// Check that "one" was deleted (and "four" wasn't)
|
||||
expected = strings.TrimSpace(`
|
||||
add table inet cni_plugins_masquerade { comment "Masquerading for plugins from github.com/containernetworking/plugins" ; }
|
||||
add chain inet cni_plugins_masquerade masq_checks { comment "Masquerade traffic from certain IPs to any (non-multicast) IP outside their subnet" ; }
|
||||
add chain inet cni_plugins_masquerade postrouting { type nat hook postrouting priority 100 ; }
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 192.168.1.2 ip daddr != 192.168.1.0/24 masquerade comment "6fd94d501e58f0aa-d750b2c8f0f25d5f, net: unit-test, if: eth0, id: two"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8::2 ip6 daddr != 2001:db8::/64 masquerade comment "6fd94d501e58f0aa-d750b2c8f0f25d5f, net: unit-test, if: eth0, id: two"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 192.168.99.5 ip daddr != 192.168.99.0/24 masquerade comment "6fd94d501e58f0aa-a4d4adb82b669cfe, net: unit-test, if: eth0, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 10.0.0.5 ip daddr != 10.0.0.0/24 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 10.0.0.6 ip daddr != 10.0.0.0/24 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 10.0.1.7 ip daddr != 10.0.1.0/24 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8::5 ip6 daddr != 2001:db8::/64 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8::6 ip6 daddr != 2001:db8::/64 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8:1::7 ip6 daddr != 2001:db8:1::/64 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 192.168.1.1 ip daddr != 192.168.1.0/24 masquerade comment "6fd94d501e58f0aa-e766de567ef6c543, net: unit-test, if: eth0, id: four"
|
||||
add rule inet cni_plugins_masquerade postrouting ip daddr == 224.0.0.0/4 return
|
||||
add rule inet cni_plugins_masquerade postrouting ip6 daddr == ff00::/8 return
|
||||
add rule inet cni_plugins_masquerade postrouting goto masq_checks
|
||||
`)
|
||||
dump = strings.TrimSpace(nft.Dump())
|
||||
if dump != expected {
|
||||
t.Errorf("expected nftables state:\n%s\n\nactual:\n%s\n\n", expected, dump)
|
||||
}
|
||||
|
||||
// GC "four" from the "unit-test" network
|
||||
err = gcIPMasqNFTablesWithInterface(nft, "unit-test", []types.GCAttachment{
|
||||
{IfName: "eth0", ContainerID: "two"},
|
||||
{IfName: "eth0", ContainerID: "three"},
|
||||
// (irrelevant extra element)
|
||||
{IfName: "eth0", ContainerID: "one"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("error from gcIPMasqNFTables: %v", err)
|
||||
}
|
||||
// GC the "alternate" network without removing anything
|
||||
err = gcIPMasqNFTablesWithInterface(nft, "alternate", []types.GCAttachment{
|
||||
{IfName: "net1", ContainerID: "three"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("error from gcIPMasqNFTables: %v", err)
|
||||
}
|
||||
|
||||
// Re-dump
|
||||
expected = strings.TrimSpace(`
|
||||
add table inet cni_plugins_masquerade { comment "Masquerading for plugins from github.com/containernetworking/plugins" ; }
|
||||
add chain inet cni_plugins_masquerade masq_checks { comment "Masquerade traffic from certain IPs to any (non-multicast) IP outside their subnet" ; }
|
||||
add chain inet cni_plugins_masquerade postrouting { type nat hook postrouting priority 100 ; }
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 192.168.1.2 ip daddr != 192.168.1.0/24 masquerade comment "6fd94d501e58f0aa-d750b2c8f0f25d5f, net: unit-test, if: eth0, id: two"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8::2 ip6 daddr != 2001:db8::/64 masquerade comment "6fd94d501e58f0aa-d750b2c8f0f25d5f, net: unit-test, if: eth0, id: two"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 192.168.99.5 ip daddr != 192.168.99.0/24 masquerade comment "6fd94d501e58f0aa-a4d4adb82b669cfe, net: unit-test, if: eth0, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 10.0.0.5 ip daddr != 10.0.0.0/24 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 10.0.0.6 ip daddr != 10.0.0.0/24 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip saddr == 10.0.1.7 ip daddr != 10.0.1.0/24 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8::5 ip6 daddr != 2001:db8::/64 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8::6 ip6 daddr != 2001:db8::/64 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade masq_checks ip6 saddr == 2001:db8:1::7 ip6 daddr != 2001:db8:1::/64 masquerade comment "82783ef24bdc7036-acb19d111858e348, net: alternate, if: net1, id: three"
|
||||
add rule inet cni_plugins_masquerade postrouting ip daddr == 224.0.0.0/4 return
|
||||
add rule inet cni_plugins_masquerade postrouting ip6 daddr == ff00::/8 return
|
||||
add rule inet cni_plugins_masquerade postrouting goto masq_checks
|
||||
`)
|
||||
dump = strings.TrimSpace(nft.Dump())
|
||||
if dump != expected {
|
||||
t.Errorf("expected nftables state:\n%s\n\nactual:\n%s\n\n", expected, dump)
|
||||
}
|
||||
|
||||
// GC everything
|
||||
err = gcIPMasqNFTablesWithInterface(nft, "unit-test", []types.GCAttachment{})
|
||||
if err != nil {
|
||||
t.Fatalf("error from gcIPMasqNFTables: %v", err)
|
||||
}
|
||||
err = gcIPMasqNFTablesWithInterface(nft, "alternate", []types.GCAttachment{})
|
||||
if err != nil {
|
||||
t.Fatalf("error from gcIPMasqNFTables: %v", err)
|
||||
}
|
||||
|
||||
expected = strings.TrimSpace(`
|
||||
add table inet cni_plugins_masquerade { comment "Masquerading for plugins from github.com/containernetworking/plugins" ; }
|
||||
add chain inet cni_plugins_masquerade masq_checks { comment "Masquerade traffic from certain IPs to any (non-multicast) IP outside their subnet" ; }
|
||||
add chain inet cni_plugins_masquerade postrouting { type nat hook postrouting priority 100 ; }
|
||||
add rule inet cni_plugins_masquerade postrouting ip daddr == 224.0.0.0/4 return
|
||||
add rule inet cni_plugins_masquerade postrouting ip6 daddr == ff00::/8 return
|
||||
add rule inet cni_plugins_masquerade postrouting goto masq_checks
|
||||
`)
|
||||
dump = strings.TrimSpace(nft.Dump())
|
||||
if dump != expected {
|
||||
t.Errorf("expected nftables state:\n%s\n\nactual:\n%s\n\n", expected, dump)
|
||||
}
|
||||
}
|
@ -32,11 +32,12 @@ var ErrLinkNotFound = errors.New("link not found")
|
||||
|
||||
// makeVethPair is called from within the container's network namespace
|
||||
func makeVethPair(name, peer string, mtu int, mac string, hostNS ns.NetNS) (netlink.Link, error) {
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = name
|
||||
linkAttrs.MTU = mtu
|
||||
|
||||
veth := &netlink.Veth{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: name,
|
||||
MTU: mtu,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
PeerName: peer,
|
||||
PeerNamespace: netlink.NsFd(int(hostNS.Fd())),
|
||||
}
|
||||
|
@ -50,3 +50,16 @@ func AddDefaultRoute(gw net.IP, dev netlink.Link) error {
|
||||
}
|
||||
return AddRoute(defNet, gw, dev)
|
||||
}
|
||||
|
||||
// IsIPNetZero check if the IPNet is "0.0.0.0/0" or "::/0"
|
||||
// This is needed as go-netlink replaces nil Dst with a '0' IPNet since
|
||||
// https://github.com/vishvananda/netlink/commit/acdc658b8613655ddb69f978e9fb4cf413e2b830
|
||||
func IsIPNetZero(ipnet *net.IPNet) bool {
|
||||
if ipnet == nil {
|
||||
return true
|
||||
}
|
||||
if ones, _ := ipnet.Mask.Size(); ones != 0 {
|
||||
return false
|
||||
}
|
||||
return ipnet.IP.Equal(net.IPv4zero) || ipnet.IP.Equal(net.IPv6zero)
|
||||
}
|
||||
|
@ -32,3 +32,7 @@ func ExecCheck(plugin string, netconf []byte) error {
|
||||
func ExecDel(plugin string, netconf []byte) error {
|
||||
return invoke.DelegateDel(context.TODO(), plugin, netconf, nil)
|
||||
}
|
||||
|
||||
func ExecStatus(plugin string, netconf []byte) error {
|
||||
return invoke.DelegateStatus(context.TODO(), plugin, netconf, nil)
|
||||
}
|
||||
|
@ -117,10 +117,27 @@ func ConfigureIface(ifName string, res *current.Result) error {
|
||||
Dst: &r.Dst,
|
||||
LinkIndex: link.Attrs().Index,
|
||||
Gw: gw,
|
||||
Priority: r.Priority,
|
||||
}
|
||||
|
||||
if r.Table != nil {
|
||||
route.Table = *r.Table
|
||||
}
|
||||
|
||||
if r.Scope != nil {
|
||||
route.Scope = netlink.Scope(*r.Scope)
|
||||
}
|
||||
|
||||
if r.Table != nil {
|
||||
route.Table = *r.Table
|
||||
}
|
||||
|
||||
if r.Scope != nil {
|
||||
route.Scope = netlink.Scope(*r.Scope)
|
||||
}
|
||||
|
||||
if err = netlink.RouteAddEcmp(&route); err != nil {
|
||||
return fmt.Errorf("failed to add route '%v via %v dev %v': %v", r.Dst, gw, ifName, err)
|
||||
return fmt.Errorf("failed to add route '%v via %v dev %v metric %d (Scope: %v, Table: %d)': %v", r.Dst, gw, ifName, r.Priority, route.Scope, route.Table, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,9 +41,11 @@ func ipNetEqual(a, b *net.IPNet) bool {
|
||||
|
||||
var _ = Describe("ConfigureIface", func() {
|
||||
var originalNS ns.NetNS
|
||||
var ipv4, ipv6, routev4, routev6 *net.IPNet
|
||||
var ipv4, ipv6, routev4, routev6, routev4Scope *net.IPNet
|
||||
var ipgw4, ipgw6, routegwv4, routegwv6 net.IP
|
||||
var routeScope int
|
||||
var result *current.Result
|
||||
var routeTable int
|
||||
|
||||
BeforeEach(func() {
|
||||
// Create a new NetNS so we don't modify the host
|
||||
@ -54,11 +56,12 @@ var _ = Describe("ConfigureIface", func() {
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = LINK_NAME
|
||||
|
||||
// Add master
|
||||
err = netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: LINK_NAME,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
_, err = netlink.LinkByName(LINK_NAME)
|
||||
@ -77,6 +80,10 @@ var _ = Describe("ConfigureIface", func() {
|
||||
routegwv4 = net.ParseIP("1.2.3.5")
|
||||
Expect(routegwv4).NotTo(BeNil())
|
||||
|
||||
_, routev4Scope, err = net.ParseCIDR("1.2.3.4/32")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(routev4Scope).NotTo(BeNil())
|
||||
|
||||
ipgw4 = net.ParseIP("1.2.3.1")
|
||||
Expect(ipgw4).NotTo(BeNil())
|
||||
|
||||
@ -93,6 +100,9 @@ var _ = Describe("ConfigureIface", func() {
|
||||
ipgw6 = net.ParseIP("abcd:1234:ffff::1")
|
||||
Expect(ipgw6).NotTo(BeNil())
|
||||
|
||||
routeTable := 5000
|
||||
routeScope = 200
|
||||
|
||||
result = ¤t.Result{
|
||||
Interfaces: []*current.Interface{
|
||||
{
|
||||
@ -121,6 +131,8 @@ var _ = Describe("ConfigureIface", func() {
|
||||
Routes: []*types.Route{
|
||||
{Dst: *routev4, GW: routegwv4},
|
||||
{Dst: *routev6, GW: routegwv6},
|
||||
{Dst: *routev4, GW: routegwv4, Table: &routeTable},
|
||||
{Dst: *routev4Scope, Scope: &routeScope},
|
||||
},
|
||||
}
|
||||
})
|
||||
@ -162,7 +174,7 @@ var _ = Describe("ConfigureIface", func() {
|
||||
routes, err := netlink.RouteList(link, 0)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
var v4found, v6found bool
|
||||
var v4found, v6found, v4Scopefound bool
|
||||
for _, route := range routes {
|
||||
isv4 := route.Dst.IP.To4() != nil
|
||||
if isv4 && ipNetEqual(route.Dst, routev4) && route.Gw.Equal(routegwv4) {
|
||||
@ -171,13 +183,17 @@ var _ = Describe("ConfigureIface", func() {
|
||||
if !isv4 && ipNetEqual(route.Dst, routev6) && route.Gw.Equal(routegwv6) {
|
||||
v6found = true
|
||||
}
|
||||
if isv4 && ipNetEqual(route.Dst, routev4Scope) && int(route.Scope) == routeScope {
|
||||
v4Scopefound = true
|
||||
}
|
||||
|
||||
if v4found && v6found {
|
||||
if v4found && v6found && v4Scopefound {
|
||||
break
|
||||
}
|
||||
}
|
||||
Expect(v4found).To(BeTrue())
|
||||
Expect(v6found).To(BeTrue())
|
||||
Expect(v4Scopefound).To(BeTrue())
|
||||
|
||||
return nil
|
||||
})
|
||||
@ -201,7 +217,7 @@ var _ = Describe("ConfigureIface", func() {
|
||||
routes, err := netlink.RouteList(link, 0)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
var v4found, v6found bool
|
||||
var v4found, v6found, v4Tablefound bool
|
||||
for _, route := range routes {
|
||||
isv4 := route.Dst.IP.To4() != nil
|
||||
if isv4 && ipNetEqual(route.Dst, routev4) && route.Gw.Equal(ipgw4) {
|
||||
@ -218,6 +234,29 @@ var _ = Describe("ConfigureIface", func() {
|
||||
Expect(v4found).To(BeTrue())
|
||||
Expect(v6found).To(BeTrue())
|
||||
|
||||
// Need to read all tables, so cannot use RouteList
|
||||
routeFilter := &netlink.Route{
|
||||
Table: routeTable,
|
||||
}
|
||||
|
||||
routes, err = netlink.RouteListFiltered(netlink.FAMILY_ALL,
|
||||
routeFilter,
|
||||
netlink.RT_FILTER_TABLE)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
for _, route := range routes {
|
||||
isv4 := route.Dst.IP.To4() != nil
|
||||
if isv4 && ipNetEqual(route.Dst, routev4) && route.Gw.Equal(ipgw4) {
|
||||
v4Tablefound = true
|
||||
}
|
||||
|
||||
if v4Tablefound {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
Expect(v4Tablefound).To(BeTrue())
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
@ -15,6 +15,7 @@
|
||||
package link_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/networkplumbing/go-nft/nft"
|
||||
@ -301,10 +302,10 @@ type configurerStub struct {
|
||||
func (a *configurerStub) Apply(c *nft.Config) (*nft.Config, error) {
|
||||
a.applyCounter++
|
||||
if a.failFirstApplyConfig && a.applyCounter == 1 {
|
||||
return nil, fmt.Errorf(errorFirstApplyText)
|
||||
return nil, errors.New(errorFirstApplyText)
|
||||
}
|
||||
if a.failSecondApplyConfig && a.applyCounter == 2 {
|
||||
return nil, fmt.Errorf(errorSecondApplyText)
|
||||
return nil, errors.New(errorSecondApplyText)
|
||||
}
|
||||
a.applyConfig = append(a.applyConfig, c)
|
||||
if a.applyReturnNil {
|
||||
@ -316,7 +317,7 @@ func (a *configurerStub) Apply(c *nft.Config) (*nft.Config, error) {
|
||||
func (a *configurerStub) Read(_ ...string) (*nft.Config, error) {
|
||||
a.readCalled = true
|
||||
if a.failReadConfig {
|
||||
return nil, fmt.Errorf(errorReadText)
|
||||
return nil, errors.New(errorReadText)
|
||||
}
|
||||
return a.readConfig, nil
|
||||
}
|
||||
|
@ -13,10 +13,10 @@ The `ns.Do()` method provides **partial** control over network namespaces for yo
|
||||
|
||||
```go
|
||||
err = targetNs.Do(func(hostNs ns.NetNS) error {
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = "dummy0"
|
||||
dummy := &netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: "dummy0",
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
}
|
||||
return netlink.LinkAdd(dummy)
|
||||
})
|
||||
|
@ -31,6 +31,10 @@ func GetCurrentNS() (NetNS, error) {
|
||||
// return an unexpected network namespace.
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
return getCurrentNSNoLock()
|
||||
}
|
||||
|
||||
func getCurrentNSNoLock() (NetNS, error) {
|
||||
return GetNS(getCurrentThreadNetNSPath())
|
||||
}
|
||||
|
||||
@ -152,6 +156,54 @@ func GetNS(nspath string) (NetNS, error) {
|
||||
return &netNS{file: fd}, nil
|
||||
}
|
||||
|
||||
// Returns a new empty NetNS.
|
||||
// Calling Close() let the kernel garbage collect the network namespace.
|
||||
func TempNetNS() (NetNS, error) {
|
||||
var tempNS NetNS
|
||||
var err error
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
// Create the new namespace in a new goroutine so that if we later fail
|
||||
// to switch the namespace back to the original one, we can safely
|
||||
// leave the thread locked to die without a risk of the current thread
|
||||
// left lingering with incorrect namespace.
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
runtime.LockOSThread()
|
||||
|
||||
var threadNS NetNS
|
||||
// save a handle to current network namespace
|
||||
threadNS, err = getCurrentNSNoLock()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to open current namespace: %v", err)
|
||||
return
|
||||
}
|
||||
defer threadNS.Close()
|
||||
|
||||
// create the temporary network namespace
|
||||
err = unix.Unshare(unix.CLONE_NEWNET)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// get a handle to the temporary network namespace
|
||||
tempNS, err = getCurrentNSNoLock()
|
||||
|
||||
err2 := threadNS.Set()
|
||||
if err2 == nil {
|
||||
// Unlock the current thread only when we successfully switched back
|
||||
// to the original namespace; otherwise leave the thread locked which
|
||||
// will force the runtime to scrap the current thread, that is maybe
|
||||
// not as optimal but at least always safe to do.
|
||||
runtime.UnlockOSThread()
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
return tempNS, err
|
||||
}
|
||||
|
||||
func (ns *netNS) Path() string {
|
||||
return ns.file.Name()
|
||||
}
|
||||
@ -173,7 +225,7 @@ func (ns *netNS) Do(toRun func(NetNS) error) error {
|
||||
}
|
||||
|
||||
containedCall := func(hostNS NetNS) error {
|
||||
threadNS, err := GetCurrentNS()
|
||||
threadNS, err := getCurrentNSNoLock()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open current netns: %v", err)
|
||||
}
|
||||
|
@ -114,3 +114,12 @@ func CmdDel(cniNetns, cniContainerID, cniIfname string, f func() error) error {
|
||||
func CmdDelWithArgs(args *skel.CmdArgs, f func() error) error {
|
||||
return CmdDel(args.Netns, args.ContainerID, args.IfName, f)
|
||||
}
|
||||
|
||||
func CmdStatus(f func() error) error {
|
||||
os.Setenv("CNI_COMMAND", "STATUS")
|
||||
os.Setenv("CNI_PATH", os.Getenv("PATH"))
|
||||
os.Setenv("CNI_NETNS_OVERRIDE", "1")
|
||||
defer envCleanup()
|
||||
|
||||
return f()
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ import (
|
||||
)
|
||||
|
||||
// AllSpecVersions contains all CNI spec version numbers
|
||||
var AllSpecVersions = [...]string{"0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0"}
|
||||
var AllSpecVersions = [...]string{"0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0", "1.1.0"}
|
||||
|
||||
// SpecVersionHasIPVersion returns true if the given CNI specification version
|
||||
// includes the "version" field in the IP address elements
|
||||
@ -39,6 +39,13 @@ func SpecVersionHasCHECK(ver string) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// SpecVersionHasSTATUS returns true if the given CNI specification version
|
||||
// supports the STATUS command
|
||||
func SpecVersionHasSTATUS(ver string) bool {
|
||||
ok, _ := version.GreaterThanOrEqualTo(ver, "1.1.0")
|
||||
return ok
|
||||
}
|
||||
|
||||
// SpecVersionHasChaining returns true if the given CNI specification version
|
||||
// supports plugin chaining
|
||||
func SpecVersionHasChaining(ver string) bool {
|
||||
|
@ -51,7 +51,7 @@ func DeleteConntrackEntriesForDstIP(dstIP string, protocol uint8) error {
|
||||
filter.AddIP(netlink.ConntrackOrigDstIP, ip)
|
||||
filter.AddProtocol(protocol)
|
||||
|
||||
_, err := netlink.ConntrackDeleteFilter(netlink.ConntrackTable, family, filter)
|
||||
_, err := netlink.ConntrackDeleteFilters(netlink.ConntrackTable, family, filter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting connection tracking state for protocol: %d IP: %s, error: %v", protocol, ip, err)
|
||||
}
|
||||
@ -65,7 +65,7 @@ func DeleteConntrackEntriesForDstPort(port uint16, protocol uint8, family netlin
|
||||
filter.AddProtocol(protocol)
|
||||
filter.AddPort(netlink.ConntrackOrigDstPort, port)
|
||||
|
||||
_, err := netlink.ConntrackDeleteFilter(netlink.ConntrackTable, family, filter)
|
||||
_, err := netlink.ConntrackDeleteFilters(netlink.ConntrackTable, family, filter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting connection tracking state for protocol: %d Port: %d, error: %v", protocol, port, err)
|
||||
}
|
||||
|
46
pkg/utils/netfilter.go
Normal file
46
pkg/utils/netfilter.go
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright 2023 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
"sigs.k8s.io/knftables"
|
||||
)
|
||||
|
||||
// SupportsIPTables tests whether the system supports using netfilter via the iptables API
|
||||
// (whether via "iptables-legacy" or "iptables-nft"). (Note that this returns true if it
|
||||
// is *possible* to use iptables; it does not test whether any other components on the
|
||||
// system are *actually* using iptables.)
|
||||
func SupportsIPTables() bool {
|
||||
ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
// We don't care whether the chain actually exists, only whether we can *check*
|
||||
// whether it exists.
|
||||
_, err = ipt.ChainExists("filter", "INPUT")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// SupportsNFTables tests whether the system supports using netfilter via the nftables API
|
||||
// (ie, not via "iptables-nft"). (Note that this returns true if it is *possible* to use
|
||||
// nftables; it does not test whether any other components on the system are *actually*
|
||||
// using nftables.)
|
||||
func SupportsNFTables() bool {
|
||||
// knftables.New() does sanity checks so we don't need any further test like in
|
||||
// the iptables case.
|
||||
_, err := knftables.New(knftables.IPv4Family, "supports_nftables_test")
|
||||
return err == nil
|
||||
}
|
52
pkg/utils/netfilter_test.go
Normal file
52
pkg/utils/netfilter_test.go
Normal file
@ -0,0 +1,52 @@
|
||||
// Copyright 2023 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("netfilter support", func() {
|
||||
When("it is available", func() {
|
||||
It("reports that iptables is supported", func() {
|
||||
Expect(SupportsIPTables()).To(BeTrue(), "This test should only fail if iptables is not available, but the test suite as a whole requires it to be available.")
|
||||
})
|
||||
It("reports that nftables is supported", func() {
|
||||
Expect(SupportsNFTables()).To(BeTrue(), "This test should only fail if nftables is not available, but the test suite as a whole requires it to be available.")
|
||||
})
|
||||
})
|
||||
|
||||
// These are Serial because os.Setenv has process-wide effect
|
||||
When("it is not available", Serial, func() {
|
||||
var origPath string
|
||||
BeforeEach(func() {
|
||||
origPath = os.Getenv("PATH")
|
||||
os.Setenv("PATH", "/does-not-exist")
|
||||
})
|
||||
AfterEach(func() {
|
||||
os.Setenv("PATH", origPath)
|
||||
})
|
||||
|
||||
It("reports that iptables is not supported", func() {
|
||||
Expect(SupportsIPTables()).To(BeFalse(), "found iptables outside of PATH??")
|
||||
})
|
||||
It("reports that nftables is not supported", func() {
|
||||
Expect(SupportsNFTables()).To(BeFalse(), "found nftables outside of PATH??")
|
||||
})
|
||||
})
|
||||
})
|
@ -48,11 +48,11 @@ var _ = Describe("Sysctl tests", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
testIfaceName = fmt.Sprintf("cnitest.%d", rand.Intn(100000))
|
||||
testLinkAttrs := netlink.NewLinkAttrs()
|
||||
testLinkAttrs.Name = testIfaceName
|
||||
testLinkAttrs.Namespace = netlink.NsFd(int(testNs.Fd()))
|
||||
testIface := &netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: testIfaceName,
|
||||
Namespace: netlink.NsFd(int(testNs.Fd())),
|
||||
},
|
||||
LinkAttrs: testLinkAttrs,
|
||||
}
|
||||
|
||||
err = netlink.LinkAdd(testIface)
|
||||
|
@ -1,135 +0,0 @@
|
||||
// Copyright 2021 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/d2g/dhcp4"
|
||||
"github.com/d2g/dhcp4client"
|
||||
)
|
||||
|
||||
const (
|
||||
MaxDHCPLen = 576
|
||||
)
|
||||
|
||||
// Send the Discovery Packet to the Broadcast Channel
|
||||
func DhcpSendDiscoverPacket(c *dhcp4client.Client, options dhcp4.Options) (dhcp4.Packet, error) {
|
||||
discoveryPacket := c.DiscoverPacket()
|
||||
|
||||
for opt, data := range options {
|
||||
discoveryPacket.AddOption(opt, data)
|
||||
}
|
||||
|
||||
discoveryPacket.PadToMinSize()
|
||||
return discoveryPacket, c.SendPacket(discoveryPacket)
|
||||
}
|
||||
|
||||
// Send Request Based On the offer Received.
|
||||
func DhcpSendRequest(c *dhcp4client.Client, options dhcp4.Options, offerPacket *dhcp4.Packet) (dhcp4.Packet, error) {
|
||||
requestPacket := c.RequestPacket(offerPacket)
|
||||
|
||||
for opt, data := range options {
|
||||
requestPacket.AddOption(opt, data)
|
||||
}
|
||||
|
||||
requestPacket.PadToMinSize()
|
||||
|
||||
return requestPacket, c.SendPacket(requestPacket)
|
||||
}
|
||||
|
||||
// Send Decline to the received acknowledgement.
|
||||
func DhcpSendDecline(c *dhcp4client.Client, acknowledgementPacket *dhcp4.Packet, options dhcp4.Options) (dhcp4.Packet, error) {
|
||||
declinePacket := c.DeclinePacket(acknowledgementPacket)
|
||||
|
||||
for opt, data := range options {
|
||||
declinePacket.AddOption(opt, data)
|
||||
}
|
||||
|
||||
declinePacket.PadToMinSize()
|
||||
|
||||
return declinePacket, c.SendPacket(declinePacket)
|
||||
}
|
||||
|
||||
// Lets do a Full DHCP Request.
|
||||
func DhcpRequest(c *dhcp4client.Client, options dhcp4.Options) (bool, dhcp4.Packet, error) {
|
||||
discoveryPacket, err := DhcpSendDiscoverPacket(c, options)
|
||||
if err != nil {
|
||||
return false, discoveryPacket, err
|
||||
}
|
||||
|
||||
offerPacket, err := c.GetOffer(&discoveryPacket)
|
||||
if err != nil {
|
||||
return false, offerPacket, err
|
||||
}
|
||||
|
||||
requestPacket, err := DhcpSendRequest(c, options, &offerPacket)
|
||||
if err != nil {
|
||||
return false, requestPacket, err
|
||||
}
|
||||
|
||||
acknowledgement, err := c.GetAcknowledgement(&requestPacket)
|
||||
if err != nil {
|
||||
return false, acknowledgement, err
|
||||
}
|
||||
|
||||
acknowledgementOptions := acknowledgement.ParseOptions()
|
||||
if dhcp4.MessageType(acknowledgementOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.ACK {
|
||||
return false, acknowledgement, nil
|
||||
}
|
||||
|
||||
return true, acknowledgement, nil
|
||||
}
|
||||
|
||||
// Renew a lease backed on the Acknowledgement Packet.
|
||||
// Returns Successful, The AcknoledgementPacket, Any Errors
|
||||
func DhcpRenew(c *dhcp4client.Client, acknowledgement dhcp4.Packet, options dhcp4.Options) (bool, dhcp4.Packet, error) {
|
||||
renewRequest := c.RenewalRequestPacket(&acknowledgement)
|
||||
|
||||
for opt, data := range options {
|
||||
renewRequest.AddOption(opt, data)
|
||||
}
|
||||
|
||||
renewRequest.PadToMinSize()
|
||||
|
||||
err := c.SendPacket(renewRequest)
|
||||
if err != nil {
|
||||
return false, renewRequest, err
|
||||
}
|
||||
|
||||
newAcknowledgement, err := c.GetAcknowledgement(&renewRequest)
|
||||
if err != nil {
|
||||
return false, newAcknowledgement, err
|
||||
}
|
||||
|
||||
newAcknowledgementOptions := newAcknowledgement.ParseOptions()
|
||||
if dhcp4.MessageType(newAcknowledgementOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.ACK {
|
||||
return false, newAcknowledgement, nil
|
||||
}
|
||||
|
||||
return true, newAcknowledgement, nil
|
||||
}
|
||||
|
||||
// Release a lease backed on the Acknowledgement Packet.
|
||||
// Returns Any Errors
|
||||
func DhcpRelease(c *dhcp4client.Client, acknowledgement dhcp4.Packet, options dhcp4.Options) error {
|
||||
release := c.ReleasePacket(&acknowledgement)
|
||||
|
||||
for opt, data := range options {
|
||||
release.AddOption(opt, data)
|
||||
}
|
||||
|
||||
release.PadToMinSize()
|
||||
|
||||
return c.SendPacket(release)
|
||||
}
|
@ -39,19 +39,21 @@ import (
|
||||
var errNoMoreTries = errors.New("no more tries")
|
||||
|
||||
type DHCP struct {
|
||||
mux sync.Mutex
|
||||
leases map[string]*DHCPLease
|
||||
hostNetnsPrefix string
|
||||
clientTimeout time.Duration
|
||||
clientResendMax time.Duration
|
||||
broadcast bool
|
||||
mux sync.Mutex
|
||||
leases map[string]*DHCPLease
|
||||
hostNetnsPrefix string
|
||||
clientTimeout time.Duration
|
||||
clientResendMax time.Duration
|
||||
clientResendTimeout time.Duration
|
||||
broadcast bool
|
||||
}
|
||||
|
||||
func newDHCP(clientTimeout, clientResendMax time.Duration) *DHCP {
|
||||
func newDHCP(clientTimeout, clientResendMax time.Duration, resendTimeout time.Duration) *DHCP {
|
||||
return &DHCP{
|
||||
leases: make(map[string]*DHCPLease),
|
||||
clientTimeout: clientTimeout,
|
||||
clientResendMax: clientResendMax,
|
||||
leases: make(map[string]*DHCPLease),
|
||||
clientTimeout: clientTimeout,
|
||||
clientResendMax: clientResendMax,
|
||||
clientResendTimeout: resendTimeout,
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,7 +76,7 @@ func (d *DHCP) Allocate(args *skel.CmdArgs, result *current.Result) error {
|
||||
return fmt.Errorf("error parsing netconf: %v", err)
|
||||
}
|
||||
|
||||
optsRequesting, optsProviding, err := prepareOptions(args.Args, conf.IPAM.ProvideOptions, conf.IPAM.RequestOptions)
|
||||
opts, err := prepareOptions(args.Args, conf.IPAM.ProvideOptions, conf.IPAM.RequestOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -89,8 +91,8 @@ func (d *DHCP) Allocate(args *skel.CmdArgs, result *current.Result) error {
|
||||
} else {
|
||||
hostNetns := d.hostNetnsPrefix + args.Netns
|
||||
l, err = AcquireLease(clientID, hostNetns, args.IfName,
|
||||
optsRequesting, optsProviding,
|
||||
d.clientTimeout, d.clientResendMax, d.broadcast)
|
||||
opts,
|
||||
d.clientTimeout, d.clientResendMax, d.clientResendTimeout, d.broadcast)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -109,6 +111,11 @@ func (d *DHCP) Allocate(args *skel.CmdArgs, result *current.Result) error {
|
||||
Gateway: l.Gateway(),
|
||||
}}
|
||||
result.Routes = l.Routes()
|
||||
if conf.IPAM.Priority != 0 {
|
||||
for _, r := range result.Routes {
|
||||
r.Priority = conf.IPAM.Priority
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -185,7 +192,8 @@ func getListener(socketPath string) (net.Listener, error) {
|
||||
|
||||
func runDaemon(
|
||||
pidfilePath, hostPrefix, socketPath string,
|
||||
dhcpClientTimeout time.Duration, resendMax time.Duration, broadcast bool,
|
||||
dhcpClientTimeout time.Duration, resendMax time.Duration, resendTimeout time.Duration,
|
||||
broadcast bool,
|
||||
) error {
|
||||
// since other goroutines (on separate threads) will change namespaces,
|
||||
// ensure the RPC server does not get scheduled onto those
|
||||
@ -220,7 +228,7 @@ func runDaemon(
|
||||
done <- true
|
||||
}()
|
||||
|
||||
dhcp := newDHCP(dhcpClientTimeout, resendMax)
|
||||
dhcp := newDHCP(dhcpClientTimeout, resendMax, resendTimeout)
|
||||
dhcp.hostNetnsPrefix = hostPrefix
|
||||
dhcp.broadcast = broadcast
|
||||
rpc.Register(dhcp)
|
||||
|
@ -61,13 +61,12 @@ var _ = Describe("DHCP Multiple Lease Operations", func() {
|
||||
})
|
||||
|
||||
// Start the DHCP server
|
||||
dhcpServerDone, err = dhcpServerStart(originalNS, 2, dhcpServerStopCh)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
dhcpServerDone = dhcpServerStart(originalNS, 2, dhcpServerStopCh)
|
||||
|
||||
// Start the DHCP client daemon
|
||||
dhcpPluginPath, err := exec.LookPath("dhcp")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
clientCmd = exec.Command(dhcpPluginPath, "daemon", "-socketpath", socketPath)
|
||||
clientCmd = exec.Command(dhcpPluginPath, "daemon", "-socketpath", socketPath, "--timeout", "2s", "--resendtimeout", "8s")
|
||||
err = clientCmd.Start()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(clientCmd.Process).NotTo(BeNil())
|
||||
|
@ -25,10 +25,6 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/d2g/dhcp4"
|
||||
"github.com/d2g/dhcp4server"
|
||||
"github.com/d2g/dhcp4server/leasepool"
|
||||
"github.com/d2g/dhcp4server/leasepool/memorypool"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/vishvananda/netlink"
|
||||
@ -48,31 +44,52 @@ func getTmpDir() (string, error) {
|
||||
return tmpDir, err
|
||||
}
|
||||
|
||||
func dhcpServerStart(netns ns.NetNS, numLeases int, stopCh <-chan bool) (*sync.WaitGroup, error) {
|
||||
// Add the expected IP to the pool
|
||||
lp := memorypool.MemoryPool{}
|
||||
type DhcpServer struct {
|
||||
cmd *exec.Cmd
|
||||
lock sync.Mutex
|
||||
|
||||
Expect(numLeases).To(BeNumerically(">", 0))
|
||||
// Currently tests only need at most 2
|
||||
Expect(numLeases).To(BeNumerically("<=", 2))
|
||||
startAddr net.IP
|
||||
endAddr net.IP
|
||||
leaseTime time.Duration
|
||||
}
|
||||
|
||||
// tests expect first lease to be at address 192.168.1.5
|
||||
for i := 5; i < numLeases+5; i++ {
|
||||
err := lp.AddLease(leasepool.Lease{IP: dhcp4.IPAdd(net.IPv4(192, 168, 1, byte(i)), 0)})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error adding IP to DHCP pool: %v", err)
|
||||
}
|
||||
func (s *DhcpServer) Serve() error {
|
||||
if err := s.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.cmd.Wait()
|
||||
}
|
||||
|
||||
dhcpServer, err := dhcp4server.New(
|
||||
net.IPv4(192, 168, 1, 1),
|
||||
&lp,
|
||||
dhcp4server.SetLocalAddr(net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 67}),
|
||||
dhcp4server.SetRemoteAddr(net.UDPAddr{IP: net.IPv4bcast, Port: 68}),
|
||||
dhcp4server.LeaseDuration(time.Minute*15),
|
||||
func (s *DhcpServer) Start() error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
s.cmd = exec.Command(
|
||||
"dnsmasq",
|
||||
"--no-daemon",
|
||||
"--dhcp-sequential-ip", // allocate IPs sequentially
|
||||
"--port=0", // disable DNS
|
||||
"--conf-file=-", // Do not read /etc/dnsmasq.conf
|
||||
fmt.Sprintf("--dhcp-range=%s,%s,%d", s.startAddr, s.endAddr, int(s.leaseTime.Seconds())),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create DHCP server: %v", err)
|
||||
s.cmd.Stdin = bytes.NewBufferString("")
|
||||
s.cmd.Stdout = os.Stdout
|
||||
s.cmd.Stderr = os.Stderr
|
||||
|
||||
return s.cmd.Start()
|
||||
}
|
||||
|
||||
func (s *DhcpServer) Stop() error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
return s.cmd.Process.Kill()
|
||||
}
|
||||
|
||||
func dhcpServerStart(netns ns.NetNS, numLeases int, stopCh <-chan bool) *sync.WaitGroup {
|
||||
dhcpServer := &DhcpServer{
|
||||
startAddr: net.IPv4(192, 168, 1, 5),
|
||||
endAddr: net.IPv4(192, 168, 1, 5+uint8(numLeases)-1),
|
||||
leaseTime: 5 * time.Minute,
|
||||
}
|
||||
|
||||
stopWg := sync.WaitGroup{}
|
||||
@ -84,9 +101,10 @@ func dhcpServerStart(netns ns.NetNS, numLeases int, stopCh <-chan bool) (*sync.W
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
|
||||
err = netns.Do(func(ns.NetNS) error {
|
||||
err := netns.Do(func(ns.NetNS) error {
|
||||
startWg.Done()
|
||||
if err := dhcpServer.ListenAndServe(); err != nil {
|
||||
|
||||
if err := dhcpServer.Serve(); err != nil {
|
||||
// Log, but don't trap errors; the server will
|
||||
// always report an error when stopped
|
||||
GinkgoT().Logf("DHCP server finished with error: %v", err)
|
||||
@ -103,12 +121,12 @@ func dhcpServerStart(netns ns.NetNS, numLeases int, stopCh <-chan bool) (*sync.W
|
||||
go func() {
|
||||
startWg.Done()
|
||||
<-stopCh
|
||||
dhcpServer.Shutdown()
|
||||
dhcpServer.Stop()
|
||||
stopWg.Done()
|
||||
}()
|
||||
startWg.Wait()
|
||||
|
||||
return &stopWg, nil
|
||||
return &stopWg
|
||||
}
|
||||
|
||||
const (
|
||||
@ -155,11 +173,11 @@ var _ = Describe("DHCP Operations", func() {
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = hostVethName
|
||||
err = netlink.LinkAdd(&netlink.Veth{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: hostVethName,
|
||||
},
|
||||
PeerName: contVethName,
|
||||
LinkAttrs: linkAttrs,
|
||||
PeerName: contVethName,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
@ -200,8 +218,7 @@ var _ = Describe("DHCP Operations", func() {
|
||||
})
|
||||
|
||||
// Start the DHCP server
|
||||
dhcpServerDone, err = dhcpServerStart(originalNS, 1, dhcpServerStopCh)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
dhcpServerDone = dhcpServerStart(originalNS, 1, dhcpServerStopCh)
|
||||
|
||||
// Start the DHCP client daemon
|
||||
dhcpPluginPath, err := exec.LookPath("dhcp")
|
||||
@ -394,11 +411,11 @@ func dhcpSetupOriginalNS() (chan bool, string, ns.NetNS, ns.NetNS, error) {
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = hostBridgeName
|
||||
// Create bridge in the "host" (original) NS
|
||||
br = &netlink.Bridge{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: hostBridgeName,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
}
|
||||
|
||||
err = netlink.LinkAdd(br)
|
||||
@ -517,8 +534,7 @@ var _ = Describe("DHCP Lease Unavailable Operations", func() {
|
||||
})
|
||||
|
||||
// Start the DHCP server
|
||||
dhcpServerDone, err = dhcpServerStart(originalNS, 1, dhcpServerStopCh)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
dhcpServerDone = dhcpServerStart(originalNS, 1, dhcpServerStopCh)
|
||||
|
||||
// Start the DHCP client daemon
|
||||
dhcpPluginPath, err := exec.LookPath("dhcp")
|
||||
@ -528,7 +544,7 @@ var _ = Describe("DHCP Lease Unavailable Operations", func() {
|
||||
// `go test` timeout with default delays. Since our DHCP server
|
||||
// and client daemon are local processes anyway, we can depend on
|
||||
// them to respond very quickly.
|
||||
clientCmd = exec.Command(dhcpPluginPath, "daemon", "-socketpath", socketPath, "-timeout", "2s", "-resendmax", "8s")
|
||||
clientCmd = exec.Command(dhcpPluginPath, "daemon", "-socketpath", socketPath, "-timeout", "2s", "-resendmax", "8s", "--resendtimeout", "10s")
|
||||
|
||||
// copy dhcp client's stdout/stderr to test stdout
|
||||
var b bytes.Buffer
|
||||
|
@ -15,6 +15,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
@ -24,8 +25,8 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/d2g/dhcp4"
|
||||
"github.com/d2g/dhcp4client"
|
||||
dhcp4 "github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4/nclient4"
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
@ -35,8 +36,10 @@ import (
|
||||
// RFC 2131 suggests using exponential backoff, starting with 4sec
|
||||
// and randomized to +/- 1sec
|
||||
const (
|
||||
resendDelay0 = 4 * time.Second
|
||||
resendDelayMax = 62 * time.Second
|
||||
resendDelay0 = 4 * time.Second
|
||||
resendDelayMax = 62 * time.Second
|
||||
defaultLeaseTime = 60 * time.Minute
|
||||
defaultResendTimeout = 208 * time.Second // fast resend + backoff resend
|
||||
)
|
||||
|
||||
// To speed up the retry for first few failures, we retry without
|
||||
@ -60,34 +63,35 @@ const (
|
||||
|
||||
type DHCPLease struct {
|
||||
clientID string
|
||||
ack *dhcp4.Packet
|
||||
opts dhcp4.Options
|
||||
latestLease *nclient4.Lease
|
||||
link netlink.Link
|
||||
renewalTime time.Time
|
||||
rebindingTime time.Time
|
||||
expireTime time.Time
|
||||
timeout time.Duration
|
||||
resendMax time.Duration
|
||||
resendTimeout time.Duration
|
||||
broadcast bool
|
||||
stopping uint32
|
||||
stop chan struct{}
|
||||
check chan struct{}
|
||||
wg sync.WaitGroup
|
||||
cancelFunc context.CancelFunc
|
||||
ctx context.Context
|
||||
// list of requesting and providing options and if they are necessary / their value
|
||||
optsRequesting map[dhcp4.OptionCode]bool
|
||||
optsProviding map[dhcp4.OptionCode][]byte
|
||||
opts []dhcp4.Option
|
||||
}
|
||||
|
||||
var requestOptionsDefault = map[dhcp4.OptionCode]bool{
|
||||
dhcp4.OptionRouter: true,
|
||||
dhcp4.OptionSubnetMask: true,
|
||||
var requestOptionsDefault = []dhcp4.OptionCode{
|
||||
dhcp4.OptionRouter,
|
||||
dhcp4.OptionSubnetMask,
|
||||
}
|
||||
|
||||
func prepareOptions(cniArgs string, provideOptions []ProvideOption, requestOptions []RequestOption) (
|
||||
map[dhcp4.OptionCode]bool, map[dhcp4.OptionCode][]byte, error,
|
||||
[]dhcp4.Option, error,
|
||||
) {
|
||||
var optsRequesting map[dhcp4.OptionCode]bool
|
||||
var optsProviding map[dhcp4.OptionCode][]byte
|
||||
var opts []dhcp4.Option
|
||||
|
||||
var err error
|
||||
// parse CNI args
|
||||
cniArgsParsed := map[string]string{}
|
||||
@ -100,46 +104,51 @@ func prepareOptions(cniArgs string, provideOptions []ProvideOption, requestOptio
|
||||
|
||||
// parse providing options map
|
||||
var optParsed dhcp4.OptionCode
|
||||
optsProviding = make(map[dhcp4.OptionCode][]byte)
|
||||
for _, opt := range provideOptions {
|
||||
optParsed, err = parseOptionName(string(opt.Option))
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Can not parse option %q: %w", opt.Option, err)
|
||||
return nil, fmt.Errorf("Can not parse option %q: %w", opt.Option, err)
|
||||
}
|
||||
if len(opt.Value) > 0 {
|
||||
if len(opt.Value) > 255 {
|
||||
return nil, nil, fmt.Errorf("value too long for option %q: %q", opt.Option, opt.Value)
|
||||
return nil, fmt.Errorf("value too long for option %q: %q", opt.Option, opt.Value)
|
||||
}
|
||||
optsProviding[optParsed] = []byte(opt.Value)
|
||||
opts = append(opts, dhcp4.Option{Code: optParsed, Value: dhcp4.String(opt.Value)})
|
||||
}
|
||||
if value, ok := cniArgsParsed[opt.ValueFromCNIArg]; ok {
|
||||
if len(value) > 255 {
|
||||
return nil, nil, fmt.Errorf("value too long for option %q from CNI_ARGS %q: %q", opt.Option, opt.ValueFromCNIArg, opt.Value)
|
||||
return nil, fmt.Errorf("value too long for option %q from CNI_ARGS %q: %q", opt.Option, opt.ValueFromCNIArg, opt.Value)
|
||||
}
|
||||
optsProviding[optParsed] = []byte(value)
|
||||
opts = append(opts, dhcp4.Option{Code: optParsed, Value: dhcp4.String(value)})
|
||||
}
|
||||
}
|
||||
|
||||
// parse necessary options map
|
||||
optsRequesting = make(map[dhcp4.OptionCode]bool)
|
||||
var optsRequesting dhcp4.OptionCodeList
|
||||
skipRequireDefault := false
|
||||
for _, opt := range requestOptions {
|
||||
if opt.SkipDefault {
|
||||
skipRequireDefault = true
|
||||
}
|
||||
if opt.Option == "" {
|
||||
continue
|
||||
}
|
||||
optParsed, err = parseOptionName(string(opt.Option))
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Can not parse option %q: %w", opt.Option, err)
|
||||
return nil, fmt.Errorf("Can not parse option %q: %w", opt.Option, err)
|
||||
}
|
||||
optsRequesting[optParsed] = true
|
||||
optsRequesting.Add(optParsed)
|
||||
}
|
||||
for k, v := range requestOptionsDefault {
|
||||
// only set if not skipping default and this value does not exists
|
||||
if _, ok := optsRequesting[k]; !ok && !skipRequireDefault {
|
||||
optsRequesting[k] = v
|
||||
if !skipRequireDefault {
|
||||
for _, opt := range requestOptionsDefault {
|
||||
optsRequesting.Add(opt)
|
||||
}
|
||||
}
|
||||
return optsRequesting, optsProviding, err
|
||||
if len(optsRequesting) > 0 {
|
||||
opts = append(opts, dhcp4.Option{Code: dhcp4.OptionParameterRequestList, Value: optsRequesting})
|
||||
}
|
||||
|
||||
return opts, err
|
||||
}
|
||||
|
||||
// AcquireLease gets an DHCP lease and then maintains it in the background
|
||||
@ -147,19 +156,25 @@ func prepareOptions(cniArgs string, provideOptions []ProvideOption, requestOptio
|
||||
// calling DHCPLease.Stop()
|
||||
func AcquireLease(
|
||||
clientID, netns, ifName string,
|
||||
optsRequesting map[dhcp4.OptionCode]bool, optsProviding map[dhcp4.OptionCode][]byte,
|
||||
timeout, resendMax time.Duration, broadcast bool,
|
||||
opts []dhcp4.Option,
|
||||
timeout, resendMax time.Duration, resendTimeout time.Duration, broadcast bool,
|
||||
) (*DHCPLease, error) {
|
||||
errCh := make(chan error, 1)
|
||||
|
||||
ctx := context.Background()
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
||||
l := &DHCPLease{
|
||||
clientID: clientID,
|
||||
stop: make(chan struct{}),
|
||||
check: make(chan struct{}),
|
||||
timeout: timeout,
|
||||
resendMax: resendMax,
|
||||
broadcast: broadcast,
|
||||
optsRequesting: optsRequesting,
|
||||
optsProviding: optsProviding,
|
||||
clientID: clientID,
|
||||
stop: make(chan struct{}),
|
||||
check: make(chan struct{}),
|
||||
timeout: timeout,
|
||||
resendMax: resendMax,
|
||||
resendTimeout: resendTimeout,
|
||||
broadcast: broadcast,
|
||||
opts: opts,
|
||||
cancelFunc: cancel,
|
||||
ctx: ctx,
|
||||
}
|
||||
|
||||
log.Printf("%v: acquiring lease", clientID)
|
||||
@ -201,6 +216,7 @@ func AcquireLease(
|
||||
func (l *DHCPLease) Stop() {
|
||||
if atomic.CompareAndSwapUint32(&l.stopping, 0, 1) {
|
||||
close(l.stop)
|
||||
l.cancelFunc()
|
||||
}
|
||||
l.wg.Wait()
|
||||
}
|
||||
@ -209,92 +225,65 @@ func (l *DHCPLease) Check() {
|
||||
l.check <- struct{}{}
|
||||
}
|
||||
|
||||
func (l *DHCPLease) getOptionsWithClientID() dhcp4.Options {
|
||||
opts := make(dhcp4.Options)
|
||||
opts[dhcp4.OptionClientIdentifier] = []byte(l.clientID)
|
||||
// client identifier's first byte is "type"
|
||||
newClientID := []byte{0}
|
||||
newClientID = append(newClientID, opts[dhcp4.OptionClientIdentifier]...)
|
||||
opts[dhcp4.OptionClientIdentifier] = newClientID
|
||||
return opts
|
||||
func withClientID(clientID string) dhcp4.Modifier {
|
||||
return func(d *dhcp4.DHCPv4) {
|
||||
optClientID := []byte{0}
|
||||
optClientID = append(optClientID, []byte(clientID)...)
|
||||
d.Options.Update(dhcp4.OptClientIdentifier(optClientID))
|
||||
}
|
||||
}
|
||||
|
||||
func (l *DHCPLease) getAllOptions() dhcp4.Options {
|
||||
opts := l.getOptionsWithClientID()
|
||||
|
||||
for k, v := range l.optsProviding {
|
||||
opts[k] = v
|
||||
func withAllOptions(l *DHCPLease) dhcp4.Modifier {
|
||||
return func(d *dhcp4.DHCPv4) {
|
||||
for _, opt := range l.opts {
|
||||
d.Options.Update(opt)
|
||||
}
|
||||
}
|
||||
|
||||
opts[dhcp4.OptionParameterRequestList] = []byte{}
|
||||
for k := range l.optsRequesting {
|
||||
opts[dhcp4.OptionParameterRequestList] = append(opts[dhcp4.OptionParameterRequestList], byte(k))
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func (l *DHCPLease) acquire() error {
|
||||
c, err := newDHCPClient(l.link, l.timeout, l.broadcast)
|
||||
if (l.link.Attrs().Flags & net.FlagUp) != net.FlagUp {
|
||||
log.Printf("Link %q down. Attempting to set up", l.link.Attrs().Name)
|
||||
if err := netlink.LinkSetUp(l.link); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
c, err := newDHCPClient(l.link, l.timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
if (l.link.Attrs().Flags & net.FlagUp) != net.FlagUp {
|
||||
log.Printf("Link %q down. Attempting to set up", l.link.Attrs().Name)
|
||||
if err = netlink.LinkSetUp(l.link); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
opts := l.getAllOptions()
|
||||
|
||||
pkt, err := backoffRetry(l.resendMax, func() (*dhcp4.Packet, error) {
|
||||
ok, ack, err := DhcpRequest(c, opts)
|
||||
switch {
|
||||
case err != nil:
|
||||
return nil, err
|
||||
case !ok:
|
||||
return nil, fmt.Errorf("DHCP server NACK'd own offer")
|
||||
default:
|
||||
return &ack, nil
|
||||
}
|
||||
timeoutCtx, cancel := context.WithTimeoutCause(l.ctx, l.resendTimeout, errNoMoreTries)
|
||||
defer cancel()
|
||||
pkt, err := backoffRetry(timeoutCtx, l.resendMax, func() (*nclient4.Lease, error) {
|
||||
return c.Request(
|
||||
timeoutCtx,
|
||||
withClientID(l.clientID),
|
||||
withAllOptions(l),
|
||||
)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return l.commit(pkt)
|
||||
l.commit(pkt)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *DHCPLease) commit(ack *dhcp4.Packet) error {
|
||||
opts := ack.ParseOptions()
|
||||
func (l *DHCPLease) commit(lease *nclient4.Lease) {
|
||||
l.latestLease = lease
|
||||
ack := lease.ACK
|
||||
|
||||
leaseTime, err := parseLeaseTime(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rebindingTime, err := parseRebindingTime(opts)
|
||||
if err != nil || rebindingTime > leaseTime {
|
||||
// Per RFC 2131 Section 4.4.5, it should default to 85% of lease time
|
||||
rebindingTime = leaseTime * 85 / 100
|
||||
}
|
||||
|
||||
renewalTime, err := parseRenewalTime(opts)
|
||||
if err != nil || renewalTime > rebindingTime {
|
||||
// Per RFC 2131 Section 4.4.5, it should default to 50% of lease time
|
||||
renewalTime = leaseTime / 2
|
||||
}
|
||||
leaseTime := ack.IPAddressLeaseTime(defaultLeaseTime)
|
||||
rebindingTime := ack.IPAddressRebindingTime(leaseTime * 85 / 100)
|
||||
renewalTime := ack.IPAddressRenewalTime(leaseTime / 2)
|
||||
|
||||
now := time.Now()
|
||||
l.expireTime = now.Add(leaseTime)
|
||||
l.renewalTime = now.Add(renewalTime)
|
||||
l.rebindingTime = now.Add(rebindingTime)
|
||||
l.ack = ack
|
||||
l.opts = opts
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *DHCPLease) maintain() {
|
||||
@ -362,44 +351,40 @@ func (l *DHCPLease) downIface() {
|
||||
}
|
||||
|
||||
func (l *DHCPLease) renew() error {
|
||||
c, err := newDHCPClient(l.link, l.timeout, l.broadcast)
|
||||
c, err := newDHCPClient(l.link, l.timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
opts := l.getAllOptions()
|
||||
pkt, err := backoffRetry(l.resendMax, func() (*dhcp4.Packet, error) {
|
||||
ok, ack, err := DhcpRenew(c, *l.ack, opts)
|
||||
switch {
|
||||
case err != nil:
|
||||
return nil, err
|
||||
case !ok:
|
||||
return nil, fmt.Errorf("DHCP server did not renew lease")
|
||||
default:
|
||||
return &ack, nil
|
||||
}
|
||||
timeoutCtx, cancel := context.WithTimeoutCause(l.ctx, l.resendTimeout, errNoMoreTries)
|
||||
defer cancel()
|
||||
lease, err := backoffRetry(timeoutCtx, l.resendMax, func() (*nclient4.Lease, error) {
|
||||
return c.Renew(
|
||||
timeoutCtx,
|
||||
l.latestLease,
|
||||
withClientID(l.clientID),
|
||||
withAllOptions(l),
|
||||
)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.commit(pkt)
|
||||
l.commit(lease)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *DHCPLease) release() error {
|
||||
log.Printf("%v: releasing lease", l.clientID)
|
||||
|
||||
c, err := newDHCPClient(l.link, l.timeout, l.broadcast)
|
||||
c, err := newDHCPClient(l.link, l.timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
opts := l.getOptionsWithClientID()
|
||||
|
||||
if err = DhcpRelease(c, *l.ack, opts); err != nil {
|
||||
if err = c.Release(l.latestLease, withClientID(l.clientID)); err != nil {
|
||||
return fmt.Errorf("failed to send DHCPRELEASE")
|
||||
}
|
||||
|
||||
@ -407,33 +392,47 @@ func (l *DHCPLease) release() error {
|
||||
}
|
||||
|
||||
func (l *DHCPLease) IPNet() (*net.IPNet, error) {
|
||||
mask := parseSubnetMask(l.opts)
|
||||
ack := l.latestLease.ACK
|
||||
|
||||
mask := ack.SubnetMask()
|
||||
if mask == nil {
|
||||
return nil, fmt.Errorf("DHCP option Subnet Mask not found in DHCPACK")
|
||||
}
|
||||
|
||||
return &net.IPNet{
|
||||
IP: l.ack.YIAddr(),
|
||||
IP: ack.YourIPAddr,
|
||||
Mask: mask,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *DHCPLease) Gateway() net.IP {
|
||||
return parseRouter(l.opts)
|
||||
ack := l.latestLease.ACK
|
||||
gws := ack.Router()
|
||||
if len(gws) > 0 {
|
||||
return gws[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *DHCPLease) Routes() []*types.Route {
|
||||
routes := []*types.Route{}
|
||||
|
||||
ack := l.latestLease.ACK
|
||||
|
||||
// RFC 3442 states that if Classless Static Routes (option 121)
|
||||
// exist, we ignore Static Routes (option 33) and the Router/Gateway.
|
||||
opt121Routes := parseCIDRRoutes(l.opts)
|
||||
opt121Routes := ack.ClasslessStaticRoute()
|
||||
if len(opt121Routes) > 0 {
|
||||
return append(routes, opt121Routes...)
|
||||
for _, r := range opt121Routes {
|
||||
routes = append(routes, &types.Route{Dst: *r.Dest, GW: r.Router})
|
||||
}
|
||||
return routes
|
||||
}
|
||||
|
||||
// Append Static Routes
|
||||
routes = append(routes, parseRoutes(l.opts)...)
|
||||
if ack.Options.Has(dhcp4.OptionStaticRoutingTable) {
|
||||
routes = append(routes, parseRoutes(ack.Options.Get(dhcp4.OptionStaticRoutingTable))...)
|
||||
}
|
||||
|
||||
// The CNI spec says even if there is a gateway specified, we must
|
||||
// add a default route in the routes section.
|
||||
@ -450,7 +449,7 @@ func jitter(span time.Duration) time.Duration {
|
||||
return time.Duration(float64(span) * (2.0*rand.Float64() - 1.0))
|
||||
}
|
||||
|
||||
func backoffRetry(resendMax time.Duration, f func() (*dhcp4.Packet, error)) (*dhcp4.Packet, error) {
|
||||
func backoffRetry(ctx context.Context, resendMax time.Duration, f func() (*nclient4.Lease, error)) (*nclient4.Lease, error) {
|
||||
baseDelay := resendDelay0
|
||||
var sleepTime time.Duration
|
||||
fastRetryLimit := resendFastMax
|
||||
@ -471,33 +470,23 @@ func backoffRetry(resendMax time.Duration, f func() (*dhcp4.Packet, error)) (*dh
|
||||
|
||||
log.Printf("retrying in %f seconds", sleepTime.Seconds())
|
||||
|
||||
time.Sleep(sleepTime)
|
||||
|
||||
// only adjust delay time if we are in normal backoff stage
|
||||
if baseDelay < resendMax && fastRetryLimit == 0 {
|
||||
baseDelay *= 2
|
||||
} else if fastRetryLimit == 0 { // only break if we are at normal delay
|
||||
break
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, context.Cause(ctx)
|
||||
case <-time.After(sleepTime):
|
||||
// only adjust delay time if we are in normal backoff stage
|
||||
if baseDelay < resendMax && fastRetryLimit == 0 {
|
||||
baseDelay *= 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errNoMoreTries
|
||||
}
|
||||
|
||||
func newDHCPClient(
|
||||
link netlink.Link,
|
||||
timeout time.Duration,
|
||||
broadcast bool,
|
||||
) (*dhcp4client.Client, error) {
|
||||
pktsock, err := dhcp4client.NewPacketSock(link.Attrs().Index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dhcp4client.New(
|
||||
dhcp4client.HardwareAddr(link.Attrs().HardwareAddr),
|
||||
dhcp4client.Timeout(timeout),
|
||||
dhcp4client.Broadcast(broadcast),
|
||||
dhcp4client.Connection(pktsock),
|
||||
)
|
||||
clientOpts ...nclient4.ClientOpt,
|
||||
) (*nclient4.Client, error) {
|
||||
clientOpts = append(clientOpts, nclient4.WithTimeout(timeout))
|
||||
return nclient4.New(link.Attrs().Name, clientOpts...)
|
||||
}
|
||||
|
@ -51,6 +51,8 @@ type IPAMConfig struct {
|
||||
// To override default requesting fields, set `skipDefault` to `false`.
|
||||
// If an field is not optional, but the server failed to provide it, error will be raised.
|
||||
RequestOptions []RequestOption `json:"request"`
|
||||
// The metric of routes
|
||||
Priority int `json:"priority,omitempty"`
|
||||
}
|
||||
|
||||
// DHCPOption represents a DHCP option. It can be a number, or a string defined in manual dhcp-options(5).
|
||||
@ -78,25 +80,33 @@ func main() {
|
||||
var broadcast bool
|
||||
var timeout time.Duration
|
||||
var resendMax time.Duration
|
||||
var resendTimeout time.Duration
|
||||
daemonFlags := flag.NewFlagSet("daemon", flag.ExitOnError)
|
||||
daemonFlags.StringVar(&pidfilePath, "pidfile", "", "optional path to write daemon PID to")
|
||||
daemonFlags.StringVar(&hostPrefix, "hostprefix", "", "optional prefix to host root")
|
||||
daemonFlags.StringVar(&socketPath, "socketpath", "", "optional dhcp server socketpath")
|
||||
daemonFlags.BoolVar(&broadcast, "broadcast", false, "broadcast DHCP leases")
|
||||
daemonFlags.DurationVar(&timeout, "timeout", 10*time.Second, "optional dhcp client timeout duration")
|
||||
daemonFlags.DurationVar(&resendMax, "resendmax", resendDelayMax, "optional dhcp client resend max duration")
|
||||
daemonFlags.DurationVar(&timeout, "timeout", 10*time.Second, "optional dhcp client timeout duration for each request")
|
||||
daemonFlags.DurationVar(&resendMax, "resendmax", resendDelayMax, "optional dhcp client max resend delay between requests")
|
||||
daemonFlags.DurationVar(&resendTimeout, "resendtimeout", defaultResendTimeout, "optional dhcp client resend timeout, no more retries after this timeout")
|
||||
daemonFlags.Parse(os.Args[2:])
|
||||
|
||||
if socketPath == "" {
|
||||
socketPath = defaultSocketPath
|
||||
}
|
||||
|
||||
if err := runDaemon(pidfilePath, hostPrefix, socketPath, timeout, resendMax, broadcast); err != nil {
|
||||
if err := runDaemon(pidfilePath, hostPrefix, socketPath, timeout, resendMax, resendTimeout, broadcast); err != nil {
|
||||
log.Print(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("dhcp"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
/* FIXME GC */
|
||||
/* FIXME Status */
|
||||
}, version.All, bv.BuildString("dhcp"))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,13 +15,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/d2g/dhcp4"
|
||||
dhcp4 "github.com/insomniacslk/dhcp/dhcpv4"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
)
|
||||
@ -31,8 +29,8 @@ var optionNameToID = map[string]dhcp4.OptionCode{
|
||||
"subnet-mask": dhcp4.OptionSubnetMask,
|
||||
"routers": dhcp4.OptionRouter,
|
||||
"host-name": dhcp4.OptionHostName,
|
||||
"user-class": dhcp4.OptionUserClass,
|
||||
"vendor-class-identifier": dhcp4.OptionVendorClassIdentifier,
|
||||
"user-class": dhcp4.OptionUserClassInformation,
|
||||
"vendor-class-identifier": dhcp4.OptionClassIdentifier,
|
||||
}
|
||||
|
||||
func parseOptionName(option string) (dhcp4.OptionCode, error) {
|
||||
@ -41,18 +39,9 @@ func parseOptionName(option string) (dhcp4.OptionCode, error) {
|
||||
}
|
||||
i, err := strconv.ParseUint(option, 10, 8)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("Can not parse option: %w", err)
|
||||
return dhcp4.OptionPad, fmt.Errorf("Can not parse option: %w", err)
|
||||
}
|
||||
return dhcp4.OptionCode(i), nil
|
||||
}
|
||||
|
||||
func parseRouter(opts dhcp4.Options) net.IP {
|
||||
if opts, ok := opts[dhcp4.OptionRouter]; ok {
|
||||
if len(opts) == 4 {
|
||||
return net.IP(opts)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return dhcp4.GenericOptionCode(i), nil
|
||||
}
|
||||
|
||||
func classfulSubnet(sn net.IP) net.IPNet {
|
||||
@ -62,100 +51,22 @@ func classfulSubnet(sn net.IP) net.IPNet {
|
||||
}
|
||||
}
|
||||
|
||||
func parseRoutes(opts dhcp4.Options) []*types.Route {
|
||||
func parseRoutes(opt []byte) []*types.Route {
|
||||
// StaticRoutes format: pairs of:
|
||||
// Dest = 4 bytes; Classful IP subnet
|
||||
// Router = 4 bytes; IP address of router
|
||||
|
||||
routes := []*types.Route{}
|
||||
if opt, ok := opts[dhcp4.OptionStaticRoute]; ok {
|
||||
for len(opt) >= 8 {
|
||||
sn := opt[0:4]
|
||||
r := opt[4:8]
|
||||
rt := &types.Route{
|
||||
Dst: classfulSubnet(sn),
|
||||
GW: r,
|
||||
}
|
||||
routes = append(routes, rt)
|
||||
opt = opt[8:]
|
||||
for len(opt) >= 8 {
|
||||
sn := opt[0:4]
|
||||
r := opt[4:8]
|
||||
rt := &types.Route{
|
||||
Dst: classfulSubnet(sn),
|
||||
GW: r,
|
||||
}
|
||||
routes = append(routes, rt)
|
||||
opt = opt[8:]
|
||||
}
|
||||
|
||||
return routes
|
||||
}
|
||||
|
||||
func parseCIDRRoutes(opts dhcp4.Options) []*types.Route {
|
||||
// See RFC4332 for format (http://tools.ietf.org/html/rfc3442)
|
||||
|
||||
routes := []*types.Route{}
|
||||
if opt, ok := opts[dhcp4.OptionClasslessRouteFormat]; ok {
|
||||
for len(opt) >= 5 {
|
||||
width := int(opt[0])
|
||||
if width > 32 {
|
||||
// error: can't have more than /32
|
||||
return nil
|
||||
}
|
||||
// network bits are compacted to avoid zeros
|
||||
octets := 0
|
||||
if width > 0 {
|
||||
octets = (width-1)/8 + 1
|
||||
}
|
||||
|
||||
if len(opt) < 1+octets+4 {
|
||||
// error: too short
|
||||
return nil
|
||||
}
|
||||
|
||||
sn := make([]byte, 4)
|
||||
copy(sn, opt[1:octets+1])
|
||||
|
||||
gw := net.IP(opt[octets+1 : octets+5])
|
||||
|
||||
rt := &types.Route{
|
||||
Dst: net.IPNet{
|
||||
IP: net.IP(sn),
|
||||
Mask: net.CIDRMask(width, 32),
|
||||
},
|
||||
GW: gw,
|
||||
}
|
||||
routes = append(routes, rt)
|
||||
|
||||
opt = opt[octets+5:]
|
||||
}
|
||||
}
|
||||
return routes
|
||||
}
|
||||
|
||||
func parseSubnetMask(opts dhcp4.Options) net.IPMask {
|
||||
mask, ok := opts[dhcp4.OptionSubnetMask]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return net.IPMask(mask)
|
||||
}
|
||||
|
||||
func parseDuration(opts dhcp4.Options, code dhcp4.OptionCode, optName string) (time.Duration, error) {
|
||||
val, ok := opts[code]
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("option %v not found", optName)
|
||||
}
|
||||
if len(val) != 4 {
|
||||
return 0, fmt.Errorf("option %v is not 4 bytes", optName)
|
||||
}
|
||||
|
||||
secs := binary.BigEndian.Uint32(val)
|
||||
return time.Duration(secs) * time.Second, nil
|
||||
}
|
||||
|
||||
func parseLeaseTime(opts dhcp4.Options) (time.Duration, error) {
|
||||
return parseDuration(opts, dhcp4.OptionIPAddressLeaseTime, "LeaseTime")
|
||||
}
|
||||
|
||||
func parseRenewalTime(opts dhcp4.Options) (time.Duration, error) {
|
||||
return parseDuration(opts, dhcp4.OptionRenewalTimeValue, "RenewalTime")
|
||||
}
|
||||
|
||||
func parseRebindingTime(opts dhcp4.Options) (time.Duration, error) {
|
||||
return parseDuration(opts, dhcp4.OptionRebindingTimeValue, "RebindingTime")
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/d2g/dhcp4"
|
||||
dhcp4 "github.com/insomniacslk/dhcp/dhcpv4"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
)
|
||||
@ -61,17 +61,8 @@ func validateRoutes(t *testing.T, routes []*types.Route) {
|
||||
}
|
||||
|
||||
func TestParseRoutes(t *testing.T) {
|
||||
opts := make(dhcp4.Options)
|
||||
opts[dhcp4.OptionStaticRoute] = []byte{10, 0, 0, 0, 10, 1, 2, 3, 192, 168, 1, 0, 192, 168, 2, 3}
|
||||
routes := parseRoutes(opts)
|
||||
|
||||
validateRoutes(t, routes)
|
||||
}
|
||||
|
||||
func TestParseCIDRRoutes(t *testing.T) {
|
||||
opts := make(dhcp4.Options)
|
||||
opts[dhcp4.OptionClasslessRouteFormat] = []byte{8, 10, 10, 1, 2, 3, 24, 192, 168, 1, 192, 168, 2, 3}
|
||||
routes := parseCIDRRoutes(opts)
|
||||
data := []byte{10, 0, 0, 0, 10, 1, 2, 3, 192, 168, 1, 0, 192, 168, 2, 3}
|
||||
routes := parseRoutes(data)
|
||||
|
||||
validateRoutes(t, routes)
|
||||
}
|
||||
@ -87,10 +78,10 @@ func TestParseOptionName(t *testing.T) {
|
||||
"hostname", "host-name", dhcp4.OptionHostName, false,
|
||||
},
|
||||
{
|
||||
"hostname in number", "12", dhcp4.OptionHostName, false,
|
||||
"hostname in number", "12", dhcp4.GenericOptionCode(12), false,
|
||||
},
|
||||
{
|
||||
"random string", "doNotparseMe", 0, true,
|
||||
"random string", "doNotparseMe", dhcp4.OptionPad, true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
@ -15,6 +15,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
@ -29,7 +30,13 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("host-local"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
/* FIXME GC */
|
||||
/* FIXME Status */
|
||||
}, version.All, bv.BuildString("host-local"))
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
@ -124,7 +131,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
for _, ip := range requestedIPs {
|
||||
errstr = errstr + " " + ip.String()
|
||||
}
|
||||
return fmt.Errorf(errstr)
|
||||
return errors.New(errstr)
|
||||
}
|
||||
|
||||
result.Routes = ipamConf.Routes
|
||||
@ -145,18 +152,18 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
defer store.Close()
|
||||
|
||||
// Loop through all ranges, releasing all IPs, even if an error occurs
|
||||
var errors []string
|
||||
var errs []string
|
||||
for idx, rangeset := range ipamConf.Ranges {
|
||||
ipAllocator := allocator.NewIPAllocator(&rangeset, store, idx)
|
||||
|
||||
err := ipAllocator.Release(args.ContainerID, args.IfName)
|
||||
if err != nil {
|
||||
errors = append(errors, err.Error())
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if errors != nil {
|
||||
return fmt.Errorf(strings.Join(errors, ";"))
|
||||
if errs != nil {
|
||||
return errors.New(strings.Join(errs, ";"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -68,7 +68,13 @@ type Address struct {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("static"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
/* FIXME GC */
|
||||
/* FIXME Status */
|
||||
}, version.All, bv.BuildString("static"))
|
||||
}
|
||||
|
||||
func loadNetConf(bytes []byte) (*types.NetConf, string, error) {
|
||||
|
@ -35,7 +35,6 @@ import (
|
||||
"github.com/containernetworking/plugins/pkg/ipam"
|
||||
"github.com/containernetworking/plugins/pkg/link"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/utils"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
"github.com/containernetworking/plugins/pkg/utils/sysctl"
|
||||
)
|
||||
@ -52,6 +51,7 @@ type NetConf struct {
|
||||
IsDefaultGW bool `json:"isDefaultGateway"`
|
||||
ForceAddress bool `json:"forceAddress"`
|
||||
IPMasq bool `json:"ipMasq"`
|
||||
IPMasqBackend *string `json:"ipMasqBackend,omitempty"`
|
||||
MTU int `json:"mtu"`
|
||||
HairpinMode bool `json:"hairpinMode"`
|
||||
PromiscMode bool `json:"promiscMode"`
|
||||
@ -61,6 +61,7 @@ type NetConf struct {
|
||||
MacSpoofChk bool `json:"macspoofchk,omitempty"`
|
||||
EnableDad bool `json:"enabledad,omitempty"`
|
||||
DisableContainerInterface bool `json:"disableContainerInterface,omitempty"`
|
||||
PortIsolation bool `json:"portIsolation,omitempty"`
|
||||
|
||||
Args struct {
|
||||
Cni BridgeArgs `json:"cni,omitempty"`
|
||||
@ -335,16 +336,11 @@ func bridgeByName(name string) (*netlink.Bridge, error) {
|
||||
}
|
||||
|
||||
func ensureBridge(brName string, mtu int, promiscMode, vlanFiltering bool) (*netlink.Bridge, error) {
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = brName
|
||||
linkAttrs.MTU = mtu
|
||||
br := &netlink.Bridge{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: brName,
|
||||
MTU: mtu,
|
||||
// Let kernel use default txqueuelen; leaving it unset
|
||||
// means 0, and a zero-length TX queue messes up FIFO
|
||||
// traffic shapers which use TX queue length as the
|
||||
// default packet limit
|
||||
TxQLen: -1,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
}
|
||||
if vlanFiltering {
|
||||
br.VlanFiltering = &vlanFiltering
|
||||
@ -392,7 +388,7 @@ func ensureVlanInterface(br *netlink.Bridge, vlanID int, preserveDefaultVlan boo
|
||||
return nil, fmt.Errorf("faild to find host namespace: %v", err)
|
||||
}
|
||||
|
||||
_, brGatewayIface, err := setupVeth(hostNS, br, name, br.MTU, false, vlanID, nil, preserveDefaultVlan, "")
|
||||
_, brGatewayIface, err := setupVeth(hostNS, br, name, br.MTU, false, vlanID, nil, preserveDefaultVlan, "", false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("faild to create vlan gateway %q: %v", name, err)
|
||||
}
|
||||
@ -411,7 +407,18 @@ func ensureVlanInterface(br *netlink.Bridge, vlanID int, preserveDefaultVlan boo
|
||||
return brGatewayVeth, nil
|
||||
}
|
||||
|
||||
func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairpinMode bool, vlanID int, vlans []int, preserveDefaultVlan bool, mac string) (*current.Interface, *current.Interface, error) {
|
||||
func setupVeth(
|
||||
netns ns.NetNS,
|
||||
br *netlink.Bridge,
|
||||
ifName string,
|
||||
mtu int,
|
||||
hairpinMode bool,
|
||||
vlanID int,
|
||||
vlans []int,
|
||||
preserveDefaultVlan bool,
|
||||
mac string,
|
||||
portIsolation bool,
|
||||
) (*current.Interface, *current.Interface, error) {
|
||||
contIface := ¤t.Interface{}
|
||||
hostIface := ¤t.Interface{}
|
||||
|
||||
@ -448,6 +455,11 @@ func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairp
|
||||
return nil, nil, fmt.Errorf("failed to setup hairpin mode for %v: %v", hostVeth.Attrs().Name, err)
|
||||
}
|
||||
|
||||
// set isolation mode
|
||||
if err = netlink.LinkSetIsolated(hostVeth, portIsolation); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to set isolated on for %v: %v", hostVeth.Attrs().Name, err)
|
||||
}
|
||||
|
||||
if (vlanID != 0 || len(vlans) > 0) && !preserveDefaultVlan {
|
||||
err = removeDefaultVlan(hostVeth)
|
||||
if err != nil {
|
||||
@ -554,7 +566,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
defer netns.Close()
|
||||
|
||||
hostInterface, containerInterface, err := setupVeth(netns, br, args.IfName, n.MTU, n.HairpinMode, n.Vlan, n.vlans, n.PreserveDefaultVlan, n.mac)
|
||||
hostInterface, containerInterface, err := setupVeth(netns, br, args.IfName, n.MTU, n.HairpinMode, n.Vlan, n.vlans, n.PreserveDefaultVlan, n.mac, n.PortIsolation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -673,12 +685,12 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
if n.IPMasq {
|
||||
chain := utils.FormatChainName(n.Name, args.ContainerID)
|
||||
comment := utils.FormatComment(n.Name, args.ContainerID)
|
||||
ipns := []*net.IPNet{}
|
||||
for _, ipc := range result.IPs {
|
||||
if err = ip.SetupIPMasq(&ipc.Address, chain, comment); err != nil {
|
||||
return err
|
||||
}
|
||||
ipns = append(ipns, &ipc.Address)
|
||||
}
|
||||
if err = ip.SetupIPMasqForNetworks(n.IPMasqBackend, ipns, n.Name, args.IfName, args.ContainerID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if !n.DisableContainerInterface {
|
||||
@ -814,12 +826,8 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
if isLayer3 && n.IPMasq {
|
||||
chain := utils.FormatChainName(n.Name, args.ContainerID)
|
||||
comment := utils.FormatComment(n.Name, args.ContainerID)
|
||||
for _, ipn := range ipnets {
|
||||
if err := ip.TeardownIPMasq(ipn, chain, comment); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ip.TeardownIPMasqForNetworks(ipnets, n.Name, args.IfName, args.ContainerID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@ -827,7 +835,13 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("bridge"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
Status: cmdStatus,
|
||||
/* FIXME GC */
|
||||
}, version.All, bv.BuildString("bridge"))
|
||||
}
|
||||
|
||||
type cniBridgeIf struct {
|
||||
@ -1088,3 +1102,18 @@ func cmdCheck(args *skel.CmdArgs) error {
|
||||
func uniqueID(containerID, cniIface string) string {
|
||||
return containerID + "-" + cniIface
|
||||
}
|
||||
|
||||
func cmdStatus(args *skel.CmdArgs) error {
|
||||
conf := NetConf{}
|
||||
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
|
||||
return fmt.Errorf("failed to load netconf: %w", err)
|
||||
}
|
||||
|
||||
if conf.IPAM.Type != "" {
|
||||
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
@ -27,6 +28,7 @@ import (
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/vishvananda/netlink"
|
||||
"github.com/vishvananda/netlink/nl"
|
||||
"sigs.k8s.io/knftables"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
@ -77,8 +79,10 @@ type testCase struct {
|
||||
vlanTrunk []*VlanTrunk
|
||||
removeDefaultVlan bool
|
||||
ipMasq bool
|
||||
ipMasqBackend string
|
||||
macspoofchk bool
|
||||
disableContIface bool
|
||||
portIsolation bool
|
||||
|
||||
AddErr020 string
|
||||
DelErr020 string
|
||||
@ -159,6 +163,9 @@ const (
|
||||
disableContainerInterface = `,
|
||||
"disableContainerInterface": true`
|
||||
|
||||
portIsolation = `,
|
||||
"portIsolation": true`
|
||||
|
||||
ipamStartStr = `,
|
||||
"ipam": {
|
||||
"type": "host-local"`
|
||||
@ -172,6 +179,9 @@ const (
|
||||
ipMasqConfStr = `,
|
||||
"ipMasq": %t`
|
||||
|
||||
ipMasqBackendConfStr = `,
|
||||
"ipMasqBackend": "%s"`
|
||||
|
||||
// Single subnet configuration (legacy)
|
||||
subnetConfStr = `,
|
||||
"subnet": "%s"`
|
||||
@ -243,6 +253,9 @@ func (tc testCase) netConfJSON(dataDir string) string {
|
||||
if tc.ipMasq {
|
||||
conf += tc.ipMasqConfig()
|
||||
}
|
||||
if tc.ipMasqBackend != "" {
|
||||
conf += tc.ipMasqBackendConfig()
|
||||
}
|
||||
if tc.args.cni.mac != "" {
|
||||
conf += fmt.Sprintf(argsFormat, tc.args.cni.mac)
|
||||
}
|
||||
@ -257,6 +270,10 @@ func (tc testCase) netConfJSON(dataDir string) string {
|
||||
conf += disableContainerInterface
|
||||
}
|
||||
|
||||
if tc.portIsolation {
|
||||
conf += portIsolation
|
||||
}
|
||||
|
||||
if !tc.isLayer2 {
|
||||
conf += netDefault
|
||||
if tc.subnet != "" || tc.ranges != nil {
|
||||
@ -295,6 +312,11 @@ func (tc testCase) ipMasqConfig() string {
|
||||
return conf
|
||||
}
|
||||
|
||||
func (tc testCase) ipMasqBackendConfig() string {
|
||||
conf := fmt.Sprintf(ipMasqBackendConfStr, tc.ipMasqBackend)
|
||||
return conf
|
||||
}
|
||||
|
||||
func (tc testCase) rangesConfig() string {
|
||||
conf := rangesStartStr
|
||||
for i, tcRange := range tc.ranges {
|
||||
@ -494,6 +516,11 @@ type (
|
||||
|
||||
func newTesterByVersion(version string, testNS, targetNS ns.NetNS) cmdAddDelTester {
|
||||
switch {
|
||||
case strings.HasPrefix(version, "1.1."):
|
||||
return &testerV10x{
|
||||
testNS: testNS,
|
||||
targetNS: targetNS,
|
||||
}
|
||||
case strings.HasPrefix(version, "1.0."):
|
||||
return &testerV10x{
|
||||
testNS: testNS,
|
||||
@ -630,6 +657,10 @@ func (tester *testerV10x) cmdAddTest(tc testCase, dataDir string) (types.Result,
|
||||
Expect(link).To(BeAssignableToTypeOf(&netlink.Veth{}))
|
||||
tester.vethName = result.Interfaces[1].Name
|
||||
|
||||
protInfo, err := netlink.LinkGetProtinfo(link)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(protInfo.Isolated).To(Equal(tc.portIsolation), "link isolation should be on when portIsolation is set")
|
||||
|
||||
// check vlan exist on the veth interface
|
||||
if tc.vlan != 0 {
|
||||
interfaceMap, err := netlink.BridgeVlanList()
|
||||
@ -724,7 +755,7 @@ func (tester *testerV10x) cmdAddTest(tc testCase, dataDir string) (types.Result,
|
||||
continue
|
||||
}
|
||||
for _, route := range routes {
|
||||
*found = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwIP))
|
||||
*found = (ip.IsIPNetZero(route.Dst) && route.Src == nil && route.Gw.Equal(gwIP))
|
||||
if *found {
|
||||
break
|
||||
}
|
||||
@ -809,7 +840,7 @@ func (tester *testerV10x) cmdCheckTest(tc testCase, conf *Net, _ string) {
|
||||
continue
|
||||
}
|
||||
for _, route := range routes {
|
||||
*found = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwIP))
|
||||
*found = (ip.IsIPNetZero(route.Dst) && route.Src == nil && route.Gw.Equal(gwIP))
|
||||
if *found {
|
||||
break
|
||||
}
|
||||
@ -1059,7 +1090,7 @@ func (tester *testerV04x) cmdAddTest(tc testCase, dataDir string) (types.Result,
|
||||
continue
|
||||
}
|
||||
for _, route := range routes {
|
||||
*found = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwIP))
|
||||
*found = (ip.IsIPNetZero(route.Dst) && route.Src == nil && route.Gw.Equal(gwIP))
|
||||
if *found {
|
||||
break
|
||||
}
|
||||
@ -1143,7 +1174,7 @@ func (tester *testerV04x) cmdCheckTest(tc testCase, conf *Net, _ string) {
|
||||
continue
|
||||
}
|
||||
for _, route := range routes {
|
||||
*found = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwIP))
|
||||
*found = (ip.IsIPNetZero(route.Dst) && route.Src == nil && route.Gw.Equal(gwIP))
|
||||
if *found {
|
||||
break
|
||||
}
|
||||
@ -1391,7 +1422,7 @@ func (tester *testerV03x) cmdAddTest(tc testCase, dataDir string) (types.Result,
|
||||
continue
|
||||
}
|
||||
for _, route := range routes {
|
||||
*found = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwIP))
|
||||
*found = (ip.IsIPNetZero(route.Dst) && route.Src == nil && route.Gw.Equal(gwIP))
|
||||
if *found {
|
||||
break
|
||||
}
|
||||
@ -1469,6 +1500,14 @@ func (tester *testerV01xOr02x) cmdAddTest(tc testCase, dataDir string) (types.Re
|
||||
err := tester.testNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
// check that STATUS is
|
||||
if testutils.SpecVersionHasSTATUS(tc.cniVersion) {
|
||||
err := testutils.CmdStatus(func() error {
|
||||
return cmdStatus(&skel.CmdArgs{StdinData: []byte(tc.netConfJSON(dataDir))})
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
r, raw, err := testutils.CmdAddWithArgs(tester.args, func() error {
|
||||
return cmdAdd(tester.args)
|
||||
})
|
||||
@ -1612,7 +1651,7 @@ func (tester *testerV01xOr02x) cmdAddTest(tc testCase, dataDir string) (types.Re
|
||||
continue
|
||||
}
|
||||
for _, route := range routes {
|
||||
*found = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwIP))
|
||||
*found = (ip.IsIPNetZero(route.Dst) && route.Src == nil && route.Gw.Equal(gwIP))
|
||||
if *found {
|
||||
break
|
||||
}
|
||||
@ -1876,10 +1915,10 @@ var _ = Describe("bridge Operations", func() {
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = BRNAME
|
||||
err := netlink.LinkAdd(&netlink.Bridge{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: BRNAME,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
link, err := netlink.LinkByName(BRNAME)
|
||||
@ -2390,41 +2429,82 @@ var _ = Describe("bridge Operations", func() {
|
||||
})
|
||||
|
||||
if testutils.SpecVersionHasChaining(ver) {
|
||||
It(fmt.Sprintf("[%s] configures a bridge and ipMasq rules", ver), func() {
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
tc := testCase{
|
||||
ranges: []rangeInfo{{
|
||||
subnet: "10.1.2.0/24",
|
||||
}},
|
||||
ipMasq: true,
|
||||
cniVersion: ver,
|
||||
}
|
||||
for _, tc := range []testCase{
|
||||
{
|
||||
ranges: []rangeInfo{{
|
||||
subnet: "10.1.2.0/24",
|
||||
}},
|
||||
ipMasq: true,
|
||||
cniVersion: ver,
|
||||
},
|
||||
{
|
||||
ranges: []rangeInfo{{
|
||||
subnet: "10.1.2.0/24",
|
||||
}},
|
||||
ipMasq: true,
|
||||
ipMasqBackend: "iptables",
|
||||
cniVersion: ver,
|
||||
},
|
||||
{
|
||||
ranges: []rangeInfo{{
|
||||
subnet: "10.1.2.0/24",
|
||||
}},
|
||||
ipMasq: true,
|
||||
ipMasqBackend: "nftables",
|
||||
cniVersion: ver,
|
||||
},
|
||||
} {
|
||||
tc := tc
|
||||
It(fmt.Sprintf("[%s] configures a bridge and ipMasq rules with ipMasqBackend %q", ver, tc.ipMasqBackend), func() {
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
args := tc.createCmdArgs(originalNS, dataDir)
|
||||
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
args := tc.createCmdArgs(originalNS, dataDir)
|
||||
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
result, err := types100.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(result.IPs).Should(HaveLen(1))
|
||||
|
||||
ip := result.IPs[0].Address.IP.String()
|
||||
|
||||
// Update this if the default ipmasq backend changes
|
||||
switch tc.ipMasqBackend {
|
||||
case "iptables", "":
|
||||
ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
rules, err := ipt.List("nat", "POSTROUTING")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(rules).Should(ContainElement(ContainSubstring(ip)))
|
||||
case "nftables":
|
||||
nft, err := knftables.New(knftables.InetFamily, "cni_plugins_masquerade")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
rules, err := nft.ListRules(context.TODO(), "masq_checks")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
// FIXME: ListRules() doesn't return the actual rule strings,
|
||||
// and we can't easily compute the ipmasq plugin's comment.
|
||||
comments := 0
|
||||
for _, r := range rules {
|
||||
if r.Comment != nil {
|
||||
comments++
|
||||
break
|
||||
}
|
||||
}
|
||||
Expect(comments).To(Equal(1), "expected to find exactly one Rule with a comment")
|
||||
}
|
||||
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
result, err := types100.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(result.IPs).Should(HaveLen(1))
|
||||
|
||||
ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
rules, err := ipt.List("nat", "POSTROUTING")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(rules).Should(ContainElement(ContainSubstring(result.IPs[0].Address.IP.String())))
|
||||
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
}
|
||||
|
||||
for i, tc := range []testCase{
|
||||
{
|
||||
@ -2520,6 +2600,36 @@ var _ = Describe("bridge Operations", func() {
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] when port-isolation is off, should set the veth peer on node with isolation off", ver), func() {
|
||||
Expect(originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
tc := testCase{
|
||||
cniVersion: ver,
|
||||
portIsolation: false,
|
||||
isLayer2: true,
|
||||
AddErr020: "cannot convert: no valid IP addresses",
|
||||
AddErr010: "cannot convert: no valid IP addresses",
|
||||
}
|
||||
cmdAddDelTest(originalNS, targetNS, tc, dataDir)
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] when port-isolation is on, should set the veth peer on node with isolation on", ver), func() {
|
||||
Expect(originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
tc := testCase{
|
||||
cniVersion: ver,
|
||||
portIsolation: true,
|
||||
isLayer2: true,
|
||||
AddErr020: "cannot convert: no valid IP addresses",
|
||||
AddErr010: "cannot convert: no valid IP addresses",
|
||||
}
|
||||
cmdAddDelTest(originalNS, targetNS, tc, dataDir)
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
})
|
||||
}
|
||||
|
||||
It("check vlan id when loading net conf", func() {
|
||||
|
@ -43,11 +43,12 @@ func parseNetConf(bytes []byte) (*types.NetConf, error) {
|
||||
func createDummy(ifName string, netns ns.NetNS) (*current.Interface, error) {
|
||||
dummy := ¤t.Interface{}
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = ifName
|
||||
linkAttrs.Namespace = netlink.NsFd(int(netns.Fd()))
|
||||
|
||||
dm := &netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: ifName,
|
||||
Namespace: netlink.NsFd(int(netns.Fd())),
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
}
|
||||
|
||||
if err := netlink.LinkAdd(dm); err != nil {
|
||||
@ -179,7 +180,13 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("dummy"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
Status: cmdStatus,
|
||||
/* FIXME GC */
|
||||
}, version.All, bv.BuildString("dummy"))
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
@ -288,3 +295,16 @@ func validateCniContainerInterface(intf current.Interface) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cmdStatus(args *skel.CmdArgs) error {
|
||||
conf := types.NetConf{}
|
||||
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
|
||||
return fmt.Errorf("failed to load netconf: %w", err)
|
||||
}
|
||||
|
||||
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -106,7 +106,7 @@ type (
|
||||
|
||||
func newTesterByVersion(version string) tester {
|
||||
switch {
|
||||
case strings.HasPrefix(version, "1.0."):
|
||||
case strings.HasPrefix(version, "1."):
|
||||
return &testerV10x{}
|
||||
case strings.HasPrefix(version, "0.4."):
|
||||
return &testerV04x{}
|
||||
@ -180,11 +180,11 @@ var _ = Describe("dummy Operations", func() {
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = MASTER_NAME
|
||||
// Add master
|
||||
err = netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: MASTER_NAME,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
m, err := netlink.LinkByName(MASTER_NAME)
|
||||
@ -261,6 +261,13 @@ var _ = Describe("dummy Operations", func() {
|
||||
defer GinkgoRecover()
|
||||
|
||||
var err error
|
||||
if testutils.SpecVersionHasSTATUS(ver) {
|
||||
err = testutils.CmdStatus(func() error {
|
||||
return cmdStatus(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
result, _, err = testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
|
@ -230,118 +230,121 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
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) {
|
||||
origLinkFlags := hostDev.Attrs().Flags
|
||||
func moveLinkIn(hostDev netlink.Link, containerNs ns.NetNS, containerIfName string) (netlink.Link, error) {
|
||||
hostDevName := hostDev.Attrs().Name
|
||||
defaultNs, err := ns.GetCurrentNS()
|
||||
|
||||
// With recent kernels we could do all changes in a single netlink call,
|
||||
// but on failure the device is left in a partially modified state.
|
||||
// Doing changes one by one allow us to (try to) rollback to the initial state.
|
||||
|
||||
// Create a temporary namespace to rename (and modify) the device in.
|
||||
// We were previously using a temporary name, but rapid rename leads to
|
||||
// race condition with udev and NetworkManager.
|
||||
tempNS, err := ns.TempNetNS()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get host namespace: %v", err)
|
||||
return nil, fmt.Errorf("failed to create tempNS: %v", err)
|
||||
}
|
||||
defer tempNS.Close()
|
||||
|
||||
// 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)
|
||||
// Restore original up state in case of error
|
||||
// This must be done in the hostNS as moving
|
||||
// device between namespaces sets the link down
|
||||
if hostDev.Attrs().Flags&net.FlagUp == net.FlagUp {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
// lookup the device again (index might have changed)
|
||||
if hostDev, err := netlink.LinkByName(hostDevName); err == nil {
|
||||
_ = netlink.LinkSetUp(hostDev)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
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)
|
||||
// Move the host device into tempNS
|
||||
if err = netlink.LinkSetNsFd(hostDev, int(tempNS.Fd())); err != nil {
|
||||
return nil, fmt.Errorf("failed to move %q to tempNS: %v", hostDevName, err)
|
||||
}
|
||||
|
||||
var contDev netlink.Link
|
||||
tempDevName := hostDev.Attrs().Name
|
||||
if err = containerNs.Do(func(_ ns.NetNS) error {
|
||||
var err error
|
||||
contDev, err = netlink.LinkByName(tempDevName)
|
||||
|
||||
// In a container in container scenario, hostNS is not the initial net namespace,
|
||||
// but host / container naming is easier to follow.
|
||||
if err = tempNS.Do(func(hostNS ns.NetNS) error {
|
||||
// lookup the device in tempNS (index might have changed)
|
||||
tempNSDev, err := netlink.LinkByName(hostDevName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find %q: %v", tempDevName, err)
|
||||
return fmt.Errorf("failed to find %q in tempNS: %v", hostDevName, err)
|
||||
}
|
||||
|
||||
// move netdev back to host namespace in case of error
|
||||
// detroying a non empty tempNS would move physical devices back to the initial net namespace,
|
||||
// not the namespace of the "parent" process, and virtual devices would be destroyed,
|
||||
// so we need to actively move the device back to hostNS on error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = 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
|
||||
})
|
||||
if err != nil && tempNSDev != nil {
|
||||
_ = netlink.LinkSetNsFd(tempNSDev, int(hostNS.Fd()))
|
||||
}
|
||||
}()
|
||||
|
||||
// Rename the device to the wanted name
|
||||
if err = netlink.LinkSetName(tempNSDev, containerIfName); err != nil {
|
||||
return fmt.Errorf("failed to rename host device %q to %q: %v", hostDevName, containerIfName, err)
|
||||
}
|
||||
|
||||
// Restore the original device name in case of error
|
||||
defer func() {
|
||||
if err != nil && tempNSDev != nil {
|
||||
_ = netlink.LinkSetName(tempNSDev, hostDevName)
|
||||
}
|
||||
}()
|
||||
|
||||
// Save host device name into the container device's alias property
|
||||
if err = netlink.LinkSetAlias(contDev, hostDevName); err != nil {
|
||||
return fmt.Errorf("failed to set alias to %q: %v", tempDevName, err)
|
||||
}
|
||||
// Rename container device to respect args.IfName
|
||||
if err = netlink.LinkSetName(contDev, ifName); err != nil {
|
||||
return fmt.Errorf("failed to rename device %q to %q: %v", tempDevName, ifName, err)
|
||||
if err = netlink.LinkSetAlias(tempNSDev, hostDevName); err != nil {
|
||||
return fmt.Errorf("failed to set alias to %q: %v", hostDevName, err)
|
||||
}
|
||||
|
||||
// restore tempDevName in case of error
|
||||
// Remove the alias on error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = netlink.LinkSetName(contDev, tempDevName)
|
||||
if err != nil && tempNSDev != nil {
|
||||
_ = netlink.LinkSetAlias(tempNSDev, "")
|
||||
}
|
||||
}()
|
||||
|
||||
// Bring container device up
|
||||
if err = netlink.LinkSetUp(contDev); err != nil {
|
||||
return fmt.Errorf("failed to set %q up: %v", ifName, err)
|
||||
// Move the device to the containerNS
|
||||
if err = netlink.LinkSetNsFd(tempNSDev, int(containerNs.Fd())); err != nil {
|
||||
return fmt.Errorf("failed to move %q (host: %q) to container NS: %v", containerIfName, hostDevName, err)
|
||||
}
|
||||
|
||||
// bring device down in case of error
|
||||
// Lookup the device again on error, the index might have changed
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = netlink.LinkSetDown(contDev)
|
||||
tempNSDev, _ = netlink.LinkByName(containerIfName)
|
||||
}
|
||||
}()
|
||||
|
||||
// Retrieve link again to get up-to-date name and attributes
|
||||
contDev, err = netlink.LinkByName(ifName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find %q: %v", ifName, err)
|
||||
}
|
||||
return nil
|
||||
err = containerNs.Do(func(_ ns.NetNS) error {
|
||||
var err error
|
||||
contDev, err = netlink.LinkByName(containerIfName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find %q in container NS: %v", containerIfName, err)
|
||||
}
|
||||
|
||||
// Move the interface back to tempNS on error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = netlink.LinkSetNsFd(contDev, int(tempNS.Fd()))
|
||||
}
|
||||
}()
|
||||
|
||||
// Bring the device up
|
||||
// This must be done in the containerNS
|
||||
if err = netlink.LinkSetUp(contDev); err != nil {
|
||||
return fmt.Errorf("failed to set %q up: %v", containerIfName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -349,78 +352,107 @@ func moveLinkIn(hostDev netlink.Link, containerNs ns.NetNS, ifName string) (netl
|
||||
return contDev, nil
|
||||
}
|
||||
|
||||
func moveLinkOut(containerNs ns.NetNS, ifName string) error {
|
||||
defaultNs, err := ns.GetCurrentNS()
|
||||
func moveLinkOut(containerNs ns.NetNS, containerIfName string) error {
|
||||
// Create a temporary namespace to rename (and modify) the device in.
|
||||
// We were previously using a temporary name, but multiple rapid renames
|
||||
// leads to race condition with udev and NetworkManager.
|
||||
tempNS, err := ns.TempNetNS()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to create tempNS: %v", err)
|
||||
}
|
||||
defer defaultNs.Close()
|
||||
defer tempNS.Close()
|
||||
|
||||
var tempName string
|
||||
var origDev netlink.Link
|
||||
err = containerNs.Do(func(_ ns.NetNS) error {
|
||||
dev, err := netlink.LinkByName(ifName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find %q: %v", ifName, err)
|
||||
}
|
||||
origDev = dev
|
||||
var contDev netlink.Link
|
||||
|
||||
// Devices can be renamed only when down
|
||||
if err = netlink.LinkSetDown(dev); err != nil {
|
||||
return fmt.Errorf("failed to set %q down: %v", ifName, err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
// If moving the device to the host namespace fails, set its name back to ifName so that this
|
||||
// function can be retried. Also bring the device back up, unless it was already down before.
|
||||
if err != nil {
|
||||
_ = netlink.LinkSetName(dev, ifName)
|
||||
if dev.Attrs().Flags&net.FlagUp == net.FlagUp {
|
||||
_ = netlink.LinkSetUp(dev)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
newLink, err := setTempName(dev)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to rename device %q to temporary name: %v", ifName, err)
|
||||
}
|
||||
dev = newLink
|
||||
tempName = dev.Attrs().Name
|
||||
|
||||
if err = netlink.LinkSetNsFd(dev, int(defaultNs.Fd())); err != nil {
|
||||
return fmt.Errorf("failed to move %q to host netns: %v", tempName, err)
|
||||
}
|
||||
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)
|
||||
// Restore original up state in case of error
|
||||
// This must be done in the containerNS as moving
|
||||
// device between namespaces sets the link down
|
||||
defer func() {
|
||||
if err != nil && contDev != nil && contDev.Attrs().Flags&net.FlagUp == net.FlagUp {
|
||||
containerNs.Do(func(_ ns.NetNS) error {
|
||||
// lookup the device again (index might have changed)
|
||||
if contDev, err := netlink.LinkByName(containerIfName); err == nil {
|
||||
_ = netlink.LinkSetUp(contDev)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
err = containerNs.Do(func(_ ns.NetNS) error {
|
||||
var err error
|
||||
// Lookup the device in the containerNS
|
||||
contDev, err = netlink.LinkByName(containerIfName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find %q in containerNS: %v", containerIfName, err)
|
||||
}
|
||||
|
||||
// Verify we have the original name
|
||||
if contDev.Attrs().Alias == "" {
|
||||
return fmt.Errorf("failed to find original ifname for %q (alias is not set)", containerIfName)
|
||||
}
|
||||
|
||||
// Move the device to the tempNS
|
||||
if err = netlink.LinkSetNsFd(contDev, int(tempNS.Fd())); err != nil {
|
||||
return fmt.Errorf("failed to move %q to tempNS: %v", containerIfName, err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = tempNS.Do(func(hostNS ns.NetNS) error {
|
||||
// Lookup the device in tempNS (index might have changed)
|
||||
tempNSDev, err := netlink.LinkByName(containerIfName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find %q in tempNS: %v", containerIfName, err)
|
||||
}
|
||||
|
||||
// Move the device back to containerNS on error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = netlink.LinkSetNsFd(tempNSDev, int(containerNs.Fd()))
|
||||
}
|
||||
}()
|
||||
return fmt.Errorf("failed to restore %q to original name %q: %v", tempName, tempDev.Attrs().Alias, err)
|
||||
|
||||
hostDevName := tempNSDev.Attrs().Alias
|
||||
|
||||
// Rename container device to hostDevName
|
||||
if err = netlink.LinkSetName(tempNSDev, hostDevName); err != nil {
|
||||
return fmt.Errorf("failed to rename device %q to %q: %v", containerIfName, hostDevName, err)
|
||||
}
|
||||
|
||||
// Rename the device back to containerIfName on error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = netlink.LinkSetName(tempNSDev, containerIfName)
|
||||
}
|
||||
}()
|
||||
|
||||
// Unset device's alias property
|
||||
if err = netlink.LinkSetAlias(tempNSDev, ""); err != nil {
|
||||
return fmt.Errorf("failed to unset alias of %q: %v", hostDevName, err)
|
||||
}
|
||||
|
||||
// Set back the device alias to hostDevName on error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = netlink.LinkSetAlias(tempNSDev, hostDevName)
|
||||
}
|
||||
}()
|
||||
|
||||
// Finally move the device to the hostNS
|
||||
if err = netlink.LinkSetNsFd(tempNSDev, int(hostNS.Fd())); err != nil {
|
||||
return fmt.Errorf("failed to move %q to hostNS: %v", hostDevName, err)
|
||||
}
|
||||
|
||||
// As we don't know the previous state, leave the link down
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -518,7 +550,13 @@ func getLink(devname, hwaddr, kernelpath, pciaddr string, auxDev string) (netlin
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("host-device"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
Status: cmdStatus,
|
||||
/* FIXME GC */
|
||||
}, version.All, bv.BuildString("host-device"))
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
@ -625,3 +663,20 @@ func validateCniContainerInterface(intf current.Interface) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cmdStatus(args *skel.CmdArgs) error {
|
||||
conf := NetConf{}
|
||||
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
|
||||
return fmt.Errorf("failed to load netconf: %w", err)
|
||||
}
|
||||
|
||||
if conf.IPAM.Type != "" {
|
||||
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Check if host device exists.
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -231,7 +231,7 @@ type (
|
||||
|
||||
func newTesterByVersion(version string) tester {
|
||||
switch {
|
||||
case strings.HasPrefix(version, "1.0."):
|
||||
case strings.HasPrefix(version, "1."):
|
||||
return &testerV10x{}
|
||||
case strings.HasPrefix(version, "0.4."):
|
||||
return &testerV04x{}
|
||||
@ -341,10 +341,10 @@ var _ = Describe("base functionality", func() {
|
||||
// prepare ifname in original namespace
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = ifname
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: ifname,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
origLink, err = netlink.LinkByName(ifname)
|
||||
@ -362,6 +362,15 @@ var _ = Describe("base functionality", func() {
|
||||
"type": "host-device",
|
||||
"device": %q
|
||||
}`, ver, ifname)
|
||||
|
||||
// if v1.1 or greater, call CmdStatus
|
||||
if testutils.SpecVersionHasSTATUS(ver) {
|
||||
err := testutils.CmdStatus(func() error {
|
||||
return cmdStatus(&skel.CmdArgs{StdinData: []byte(conf)})
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
@ -422,10 +431,10 @@ var _ = Describe("base functionality", func() {
|
||||
// prepare host device in original namespace
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = ifname
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: ifname,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
origLink, err = netlink.LinkByName(ifname)
|
||||
@ -483,10 +492,10 @@ var _ = Describe("base functionality", func() {
|
||||
// create another conflict host device with same name "dummy0"
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = ifname
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: ifname,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
conflictLink, err = netlink.LinkByName(ifname)
|
||||
@ -608,10 +617,10 @@ var _ = Describe("base functionality", func() {
|
||||
// prepare ifname in original namespace
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = ifname
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: ifname,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
origLink, err = netlink.LinkByName(ifname)
|
||||
@ -720,10 +729,10 @@ var _ = Describe("base functionality", func() {
|
||||
// prepare ifname in original namespace
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = ifname
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: ifname,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
origLink, err = netlink.LinkByName(ifname)
|
||||
@ -912,10 +921,10 @@ var _ = Describe("base functionality", func() {
|
||||
// prepare ifname in original namespace
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = ifname
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: ifname,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
origLink, err = netlink.LinkByName(ifname)
|
||||
@ -969,10 +978,10 @@ var _ = Describe("base functionality", func() {
|
||||
// prepare ifname in original namespace
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = ifname
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: ifname,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
origLink, err = netlink.LinkByName(ifname)
|
||||
@ -1093,10 +1102,10 @@ var _ = Describe("base functionality", func() {
|
||||
// prepare host device in original namespace
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = ifname
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: ifname,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
origLink, err = netlink.LinkByName(ifname)
|
||||
@ -1154,10 +1163,10 @@ var _ = Describe("base functionality", func() {
|
||||
// create another conflict host device with same name "dummy0"
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = ifname
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: ifname,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
conflictLink, err = netlink.LinkByName(ifname)
|
||||
@ -1227,10 +1236,10 @@ var _ = Describe("base functionality", func() {
|
||||
// prepare host device in original namespace
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = hostIfname
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: hostIfname,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
origLink, err = netlink.LinkByName(hostIfname)
|
||||
@ -1243,10 +1252,10 @@ var _ = Describe("base functionality", func() {
|
||||
// prepare device in container namespace with same name as host device
|
||||
_ = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = containerAdditionalIfname
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: containerAdditionalIfname,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
containerLink, err = netlink.LinkByName(containerAdditionalIfname)
|
||||
|
@ -144,14 +144,15 @@ func createIpvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interf
|
||||
return nil, err
|
||||
}
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.MTU = conf.MTU
|
||||
linkAttrs.Name = tmpName
|
||||
linkAttrs.ParentIndex = m.Attrs().Index
|
||||
linkAttrs.Namespace = netlink.NsFd(int(netns.Fd()))
|
||||
|
||||
mv := &netlink.IPVlan{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
MTU: conf.MTU,
|
||||
Name: tmpName,
|
||||
ParentIndex: m.Attrs().Index,
|
||||
Namespace: netlink.NsFd(int(netns.Fd())),
|
||||
},
|
||||
Mode: mode,
|
||||
LinkAttrs: linkAttrs,
|
||||
Mode: mode,
|
||||
}
|
||||
|
||||
if conf.LinkContNs {
|
||||
@ -195,7 +196,7 @@ func getDefaultRouteInterfaceName() (string, error) {
|
||||
}
|
||||
|
||||
for _, v := range routeToDstIP {
|
||||
if v.Dst == nil {
|
||||
if ip.IsIPNetZero(v.Dst) {
|
||||
l, err := netlink.LinkByIndex(v.LinkIndex)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -349,7 +350,13 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("ipvlan"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
Status: cmdStatus,
|
||||
/* FIXME GC */
|
||||
}, version.All, bv.BuildString("ipvlan"))
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
@ -484,3 +491,19 @@ func validateCniContainerInterface(intf current.Interface, modeExpected string)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cmdStatus(args *skel.CmdArgs) error {
|
||||
conf := NetConf{}
|
||||
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
|
||||
return fmt.Errorf("failed to load netconf: %w", err)
|
||||
}
|
||||
if conf.IPAM.Type != "" {
|
||||
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Check if master interface exists.
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -114,6 +114,13 @@ func ipvlanAddCheckDelTest(conf, masterName string, originalNS, targetNS ns.NetN
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
if testutils.SpecVersionHasSTATUS(cniVersion) {
|
||||
err = testutils.CmdStatus(func() error {
|
||||
return cmdStatus(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
result, _, err = testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
@ -214,7 +221,7 @@ type (
|
||||
|
||||
func newTesterByVersion(version string) tester {
|
||||
switch {
|
||||
case strings.HasPrefix(version, "1.0."):
|
||||
case strings.HasPrefix(version, "1."):
|
||||
return &testerV10x{}
|
||||
case strings.HasPrefix(version, "0.4.") || strings.HasPrefix(version, "0.3."):
|
||||
return &testerV04x{}
|
||||
@ -281,11 +288,11 @@ var _ = Describe("ipvlan Operations", func() {
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = MASTER_NAME
|
||||
// Add master
|
||||
err = netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: MASTER_NAME,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
_, err = netlink.LinkByName(MASTER_NAME)
|
||||
@ -297,11 +304,11 @@ var _ = Describe("ipvlan Operations", func() {
|
||||
err = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = MASTER_NAME_INCONTAINER
|
||||
// Add master
|
||||
err = netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: MASTER_NAME_INCONTAINER,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
_, err = netlink.LinkByName(MASTER_NAME_INCONTAINER)
|
||||
|
@ -172,7 +172,13 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("loopback"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
/* FIXME GC */
|
||||
/* FIXME Status */
|
||||
}, version.All, bv.BuildString("loopback"))
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
|
@ -74,7 +74,8 @@ var _ = Describe("Loopback", func() {
|
||||
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Eventually(session).Should(gbytes.Say(`{.*}`))
|
||||
// "(?s)" turns on the "s" flag, making "." match newlines too.
|
||||
Eventually(session).Should(gbytes.Say(`(?s){.*}`))
|
||||
Eventually(session).Should(gexec.Exit(0))
|
||||
|
||||
var lo *net.Interface
|
||||
|
@ -41,6 +41,7 @@ type NetConf struct {
|
||||
MTU int `json:"mtu"`
|
||||
Mac string `json:"mac,omitempty"`
|
||||
LinkContNs bool `json:"linkInContainer,omitempty"`
|
||||
BcQueueLen uint32 `json:"bcqueuelen,omitempty"`
|
||||
|
||||
RuntimeConfig struct {
|
||||
Mac string `json:"mac,omitempty"`
|
||||
@ -67,7 +68,7 @@ func getDefaultRouteInterfaceName() (string, error) {
|
||||
}
|
||||
|
||||
for _, v := range routeToDstIP {
|
||||
if v.Dst == nil {
|
||||
if ip.IsIPNetZero(v.Dst) {
|
||||
l, err := netlink.LinkByIndex(v.LinkIndex)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -225,12 +226,11 @@ func createMacvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Inter
|
||||
return nil, err
|
||||
}
|
||||
|
||||
linkAttrs := netlink.LinkAttrs{
|
||||
MTU: conf.MTU,
|
||||
Name: tmpName,
|
||||
ParentIndex: m.Attrs().Index,
|
||||
Namespace: netlink.NsFd(int(netns.Fd())),
|
||||
}
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.MTU = conf.MTU
|
||||
linkAttrs.Name = tmpName
|
||||
linkAttrs.ParentIndex = m.Attrs().Index
|
||||
linkAttrs.Namespace = netlink.NsFd(int(netns.Fd()))
|
||||
|
||||
if conf.Mac != "" {
|
||||
addr, err := net.ParseMAC(conf.Mac)
|
||||
@ -245,6 +245,8 @@ func createMacvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Inter
|
||||
Mode: mode,
|
||||
}
|
||||
|
||||
mv.BCQueueLen = conf.BcQueueLen
|
||||
|
||||
if conf.LinkContNs {
|
||||
err = netns.Do(func(_ ns.NetNS) error {
|
||||
return netlink.LinkAdd(mv)
|
||||
@ -426,7 +428,13 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("macvlan"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
Status: cmdStatus,
|
||||
/* FIXME GC */
|
||||
}, version.All, bv.BuildString("macvlan"))
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
@ -562,3 +570,20 @@ func validateCniContainerInterface(intf current.Interface, modeExpected string)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cmdStatus(args *skel.CmdArgs) error {
|
||||
conf := NetConf{}
|
||||
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
|
||||
return fmt.Errorf("failed to load netconf: %w", err)
|
||||
}
|
||||
|
||||
if conf.IPAM.Type != "" {
|
||||
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Check if master interface exists.
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -117,7 +117,7 @@ type (
|
||||
|
||||
func newTesterByVersion(version string) tester {
|
||||
switch {
|
||||
case strings.HasPrefix(version, "1.0."):
|
||||
case strings.HasPrefix(version, "1."):
|
||||
return &testerV10x{}
|
||||
case strings.HasPrefix(version, "0.4."):
|
||||
return &testerV04x{}
|
||||
@ -208,11 +208,11 @@ var _ = Describe("macvlan Operations", func() {
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = MASTER_NAME
|
||||
// Add master
|
||||
err = netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: MASTER_NAME,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
_, err = netlink.LinkByName(MASTER_NAME)
|
||||
@ -224,11 +224,11 @@ var _ = Describe("macvlan Operations", func() {
|
||||
err = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = MASTER_NAME_INCONTAINER
|
||||
// Add master
|
||||
err = netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: MASTER_NAME_INCONTAINER,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
_, err = netlink.LinkByName(MASTER_NAME_INCONTAINER)
|
||||
@ -322,6 +322,13 @@ var _ = Describe("macvlan Operations", func() {
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
if testutils.SpecVersionHasSTATUS(ver) {
|
||||
err := testutils.CmdStatus(func() error {
|
||||
return cmdStatus(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
result, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
@ -434,6 +441,13 @@ var _ = Describe("macvlan Operations", func() {
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
if testutils.SpecVersionHasSTATUS(ver) {
|
||||
err := testutils.CmdStatus(func() error {
|
||||
return cmdStatus(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
result, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
@ -520,6 +534,13 @@ var _ = Describe("macvlan Operations", func() {
|
||||
defer GinkgoRecover()
|
||||
|
||||
var err error
|
||||
if testutils.SpecVersionHasSTATUS(ver) {
|
||||
err := testutils.CmdStatus(func() error {
|
||||
return cmdStatus(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
result, _, err = testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
@ -660,6 +681,13 @@ var _ = Describe("macvlan Operations", func() {
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
if testutils.SpecVersionHasSTATUS(ver) {
|
||||
err := testutils.CmdStatus(func() error {
|
||||
return cmdStatus(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
result, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
|
@ -31,7 +31,6 @@ import (
|
||||
"github.com/containernetworking/plugins/pkg/ip"
|
||||
"github.com/containernetworking/plugins/pkg/ipam"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/utils"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
@ -44,8 +43,9 @@ func init() {
|
||||
|
||||
type NetConf struct {
|
||||
types.NetConf
|
||||
IPMasq bool `json:"ipMasq"`
|
||||
MTU int `json:"mtu"`
|
||||
IPMasq bool `json:"ipMasq"`
|
||||
IPMasqBackend *string `json:"ipMasqBackend,omitempty"`
|
||||
MTU int `json:"mtu"`
|
||||
}
|
||||
|
||||
func setupContainerVeth(netns ns.NetNS, ifName string, mtu int, pr *current.Result) (*current.Interface, *current.Interface, error) {
|
||||
@ -229,12 +229,12 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
if conf.IPMasq {
|
||||
chain := utils.FormatChainName(conf.Name, args.ContainerID)
|
||||
comment := utils.FormatComment(conf.Name, args.ContainerID)
|
||||
ipns := []*net.IPNet{}
|
||||
for _, ipc := range result.IPs {
|
||||
if err = ip.SetupIPMasq(&ipc.Address, chain, comment); err != nil {
|
||||
return err
|
||||
}
|
||||
ipns = append(ipns, &ipc.Address)
|
||||
}
|
||||
if err = ip.SetupIPMasqForNetworks(conf.IPMasqBackend, ipns, conf.Name, args.IfName, args.ContainerID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@ -293,10 +293,8 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
if len(ipnets) != 0 && conf.IPMasq {
|
||||
chain := utils.FormatChainName(conf.Name, args.ContainerID)
|
||||
comment := utils.FormatComment(conf.Name, args.ContainerID)
|
||||
for _, ipn := range ipnets {
|
||||
err = ip.TeardownIPMasq(ipn, chain, comment)
|
||||
if err := ip.TeardownIPMasqForNetworks(ipnets, conf.Name, args.IfName, args.ContainerID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@ -304,7 +302,13 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("ptp"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
Status: cmdStatus,
|
||||
/* FIXME GC */
|
||||
}, version.All, bv.BuildString("ptp"))
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
@ -407,3 +411,16 @@ func validateCniContainerInterface(intf current.Interface) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cmdStatus(args *skel.CmdArgs) error {
|
||||
conf := NetConf{}
|
||||
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
|
||||
return fmt.Errorf("failed to load netconf: %w", err)
|
||||
}
|
||||
|
||||
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ type Net struct {
|
||||
CNIVersion string `json:"cniVersion"`
|
||||
Type string `json:"type,omitempty"`
|
||||
IPMasq bool `json:"ipMasq"`
|
||||
IPMasqBackend *string `json:"ipMasqBackend,omitempty"`
|
||||
MTU int `json:"mtu"`
|
||||
IPAM *allocator.IPAMConfig `json:"ipam"`
|
||||
DNS types.DNS `json:"dns"`
|
||||
@ -104,7 +105,7 @@ type (
|
||||
|
||||
func newTesterByVersion(version string) tester {
|
||||
switch {
|
||||
case strings.HasPrefix(version, "1.0."):
|
||||
case strings.HasPrefix(version, "1."):
|
||||
return &testerV10x{}
|
||||
case strings.HasPrefix(version, "0.4."):
|
||||
return &testerV04x{}
|
||||
@ -249,6 +250,14 @@ var _ = Describe("ptp Operations", func() {
|
||||
defer GinkgoRecover()
|
||||
|
||||
var err error
|
||||
if testutils.SpecVersionHasSTATUS(cniVersion) {
|
||||
By("Doing a cni STATUS")
|
||||
err = testutils.CmdStatus(func() error {
|
||||
return cmdStatus(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
result, _, err = testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
@ -368,6 +377,62 @@ var _ = Describe("ptp Operations", func() {
|
||||
doTest(conf, ver, 1, dnsConf, targetNS)
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] configures and deconfigures a ptp link when specifying ipMasqBackend: iptables", ver), func() {
|
||||
dnsConf := types.DNS{
|
||||
Nameservers: []string{"10.1.2.123"},
|
||||
Domain: "some.domain.test",
|
||||
Search: []string{"search.test"},
|
||||
Options: []string{"option1:foo"},
|
||||
}
|
||||
dnsConfBytes, err := json.Marshal(dnsConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "ptp",
|
||||
"ipMasq": true,
|
||||
"ipMasqBackend": "iptables",
|
||||
"mtu": 5000,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.2.0/24",
|
||||
"dataDir": "%s"
|
||||
},
|
||||
"dns": %s
|
||||
}`, ver, dataDir, string(dnsConfBytes))
|
||||
|
||||
doTest(conf, ver, 1, dnsConf, targetNS)
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] configures and deconfigures a ptp link when specifying ipMasqBackend: nftables", ver), func() {
|
||||
dnsConf := types.DNS{
|
||||
Nameservers: []string{"10.1.2.123"},
|
||||
Domain: "some.domain.test",
|
||||
Search: []string{"search.test"},
|
||||
Options: []string{"option1:foo"},
|
||||
}
|
||||
dnsConfBytes, err := json.Marshal(dnsConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "mynet",
|
||||
"type": "ptp",
|
||||
"ipMasq": true,
|
||||
"ipMasqBackend": "nftables",
|
||||
"mtu": 5000,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.2.0/24",
|
||||
"dataDir": "%s"
|
||||
},
|
||||
"dns": %s
|
||||
}`, ver, dataDir, string(dnsConfBytes))
|
||||
|
||||
doTest(conf, ver, 1, dnsConf, targetNS)
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] configures and deconfigures a dual-stack ptp link + routes with ADD/DEL", ver), func() {
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
|
@ -137,10 +137,9 @@ func createTapWithIptool(tmpName string, mtu int, multiqueue bool, mac string, o
|
||||
}
|
||||
|
||||
func createLinkWithNetlink(tmpName string, mtu int, nsFd int, multiqueue bool, mac string, owner *uint32, group *uint32) error {
|
||||
linkAttrs := netlink.LinkAttrs{
|
||||
Name: tmpName,
|
||||
Namespace: netlink.NsFd(nsFd),
|
||||
}
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = tmpName
|
||||
linkAttrs.Namespace = netlink.NsFd(nsFd)
|
||||
if mtu != 0 {
|
||||
linkAttrs.MTU = mtu
|
||||
}
|
||||
@ -386,7 +385,13 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("tap"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
Status: cmdStatus,
|
||||
/* FIXME GC */
|
||||
}, version.All, bv.BuildString("tap"))
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
@ -455,3 +460,18 @@ func cmdCheck(args *skel.CmdArgs) error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func cmdStatus(args *skel.CmdArgs) error {
|
||||
conf := NetConf{}
|
||||
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
|
||||
return fmt.Errorf("failed to load netconf: %w", err)
|
||||
}
|
||||
|
||||
if conf.IPAM.Type != "" {
|
||||
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ type (
|
||||
|
||||
func newTesterByVersion(version string) tester {
|
||||
switch {
|
||||
case strings.HasPrefix(version, "1.0."):
|
||||
case strings.HasPrefix(version, "1."):
|
||||
return &testerV10x{}
|
||||
case strings.HasPrefix(version, "0.4."):
|
||||
return &testerV04x{}
|
||||
@ -223,6 +223,13 @@ var _ = Describe("Add, check, remove tap plugin", func() {
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
if testutils.SpecVersionHasSTATUS(ver) {
|
||||
err := testutils.CmdStatus(func() error {
|
||||
return cmdStatus(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
result, _, err = testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
@ -346,10 +353,10 @@ var _ = Describe("Add, check, remove tap plugin", func() {
|
||||
|
||||
Expect(
|
||||
targetNS.Do(func(ns.NetNS) error {
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = bridgeName
|
||||
if err := netlink.LinkAdd(&netlink.Bridge{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: bridgeName,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -364,6 +371,13 @@ var _ = Describe("Add, check, remove tap plugin", func() {
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
if testutils.SpecVersionHasSTATUS(ver) {
|
||||
err := testutils.CmdStatus(func() error {
|
||||
return cmdStatus(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
result, _, err = testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
|
@ -119,14 +119,15 @@ func createVlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interfac
|
||||
return nil, err
|
||||
}
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.MTU = conf.MTU
|
||||
linkAttrs.Name = tmpName
|
||||
linkAttrs.ParentIndex = m.Attrs().Index
|
||||
linkAttrs.Namespace = netlink.NsFd(int(netns.Fd()))
|
||||
|
||||
v := &netlink.Vlan{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
MTU: conf.MTU,
|
||||
Name: tmpName,
|
||||
ParentIndex: m.Attrs().Index,
|
||||
Namespace: netlink.NsFd(int(netns.Fd())),
|
||||
},
|
||||
VlanId: conf.VlanID,
|
||||
LinkAttrs: linkAttrs,
|
||||
VlanId: conf.VlanID,
|
||||
}
|
||||
|
||||
if conf.LinkContNs {
|
||||
@ -259,7 +260,13 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("vlan"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
Status: cmdStatus,
|
||||
/* FIXME GC */
|
||||
}, version.All, bv.BuildString("vlan"))
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
@ -392,3 +399,18 @@ func validateCniContainerInterface(intf current.Interface, vlanID int, mtu int)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cmdStatus(args *skel.CmdArgs) error {
|
||||
conf := NetConf{}
|
||||
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
|
||||
return fmt.Errorf("failed to load netconf: %w", err)
|
||||
}
|
||||
|
||||
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: Check if master interface exists.
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ type (
|
||||
|
||||
func newTesterByVersion(version string) tester {
|
||||
switch {
|
||||
case strings.HasPrefix(version, "1.0."):
|
||||
case strings.HasPrefix(version, "1."):
|
||||
return &testerV10x{}
|
||||
case strings.HasPrefix(version, "0.4."):
|
||||
return &testerV04x{}
|
||||
@ -187,11 +187,11 @@ var _ = Describe("vlan Operations", func() {
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = MASTER_NAME
|
||||
// Add master
|
||||
err = netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: MASTER_NAME,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
m, err := netlink.LinkByName(MASTER_NAME)
|
||||
@ -205,11 +205,11 @@ var _ = Describe("vlan Operations", func() {
|
||||
err = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = MASTER_NAME_INCONTAINER
|
||||
// Add master
|
||||
err = netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: MASTER_NAME_INCONTAINER,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
m, err := netlink.LinkByName(MASTER_NAME_INCONTAINER)
|
||||
@ -354,6 +354,13 @@ var _ = Describe("vlan Operations", func() {
|
||||
defer GinkgoRecover()
|
||||
|
||||
var err error
|
||||
if testutils.SpecVersionHasSTATUS(ver) {
|
||||
err := testutils.CmdStatus(func() error {
|
||||
return cmdStatus(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
result, _, err = testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
|
@ -215,5 +215,26 @@ func cmdCheck(_ *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("win-bridge"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
Status: cmdStatus,
|
||||
/* FIXME GC */
|
||||
}, version.All, bv.BuildString("win-bridge"))
|
||||
}
|
||||
|
||||
func cmdStatus(args *skel.CmdArgs) error {
|
||||
conf := NetConf{}
|
||||
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
|
||||
return fmt.Errorf("failed to load netconf: %w", err)
|
||||
}
|
||||
|
||||
if conf.IPAM.Type != "" {
|
||||
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -106,13 +106,13 @@ func cmdHcnAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
|
||||
return nil, errors.Annotatef(err, "error while hcn.GetNetworkByName(%s)", networkName)
|
||||
}
|
||||
if hcnNetwork == nil {
|
||||
return nil, fmt.Errorf("network %v is not found", networkName)
|
||||
return nil, fmt.Errorf("network %v is not found", networkName)
|
||||
}
|
||||
if hnsNetwork == nil {
|
||||
return nil, fmt.Errorf("network %v not found", networkName)
|
||||
}
|
||||
|
||||
if !strings.EqualFold(string (hcnNetwork.Type), "Overlay") {
|
||||
if !strings.EqualFold(string(hcnNetwork.Type), "Overlay") {
|
||||
return nil, fmt.Errorf("network %v is of an unexpected type: %v", networkName, hcnNetwork.Type)
|
||||
}
|
||||
|
||||
@ -131,7 +131,6 @@ func cmdHcnAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
|
||||
n.ApplyOutboundNatPolicy(hnsNetwork.Subnets[0].AddressPrefix)
|
||||
}
|
||||
hcnEndpoint, err := hns.GenerateHcnEndpoint(epInfo, &n.NetConf)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Annotate(err, "error while generating HostComputeEndpoint")
|
||||
}
|
||||
@ -142,15 +141,14 @@ func cmdHcnAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
|
||||
}
|
||||
|
||||
result, err := hns.ConstructHcnResult(hcnNetwork, hcnEndpoint)
|
||||
|
||||
if err != nil {
|
||||
ipam.ExecDel(n.IPAM.Type, args.StdinData)
|
||||
return nil, errors.Annotate(err, "error while constructing HostComputeEndpoint addition result")
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
||||
}
|
||||
|
||||
func cmdHnsAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
|
||||
success := false
|
||||
|
||||
@ -243,6 +241,7 @@ func cmdHnsAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
|
||||
success = true
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func cmdAdd(args *skel.CmdArgs) error {
|
||||
n, cniVersion, err := loadNetConf(args.StdinData)
|
||||
if err != nil {
|
||||
@ -288,5 +287,24 @@ func cmdCheck(_ *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("win-overlay"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
Status: cmdStatus,
|
||||
/* FIXME GC */
|
||||
}, version.All, bv.BuildString("win-overlay"))
|
||||
}
|
||||
|
||||
func cmdStatus(args *skel.CmdArgs) error {
|
||||
conf := NetConf{}
|
||||
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
|
||||
return fmt.Errorf("failed to load netconf: %w", err)
|
||||
}
|
||||
|
||||
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -1,563 +0,0 @@
|
||||
// Copyright 2023 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
types100 "github.com/containernetworking/cni/pkg/types/100"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/testutils"
|
||||
)
|
||||
|
||||
var _ = Describe("bandwidth config test", func() {
|
||||
var (
|
||||
hostNs ns.NetNS
|
||||
containerNs ns.NetNS
|
||||
ifbDeviceName string
|
||||
hostIfname string
|
||||
containerIfname string
|
||||
hostIP net.IP
|
||||
containerIP net.IP
|
||||
hostIfaceMTU int
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
|
||||
hostIfname = "host-veth"
|
||||
containerIfname = "container-veth"
|
||||
|
||||
hostNs, err = testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
containerNs, err = testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
hostIP = net.IP{169, 254, 0, 1}
|
||||
containerIP = net.IP{10, 254, 0, 1}
|
||||
hostIfaceMTU = 1024
|
||||
ifbDeviceName = "bwpa8eda89404b7"
|
||||
|
||||
createVeth(hostNs, hostIfname, containerNs, containerIfname, hostIP, containerIP, hostIfaceMTU)
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(containerNs.Close()).To(Succeed())
|
||||
Expect(testutils.UnmountNS(containerNs)).To(Succeed())
|
||||
Expect(hostNs.Close()).To(Succeed())
|
||||
Expect(testutils.UnmountNS(hostNs)).To(Succeed())
|
||||
})
|
||||
|
||||
// Bandwidth requires host-side interface info, and thus only
|
||||
// supports 0.3.0 and later CNI versions
|
||||
for _, ver := range []string{"0.3.0", "0.3.1", "0.4.0", "1.0.0"} {
|
||||
// Redefine ver inside for scope so real value is picked up by each dynamically defined It()
|
||||
// See Gingkgo's "Patterns for dynamically generating tests" documentation.
|
||||
ver := ver
|
||||
|
||||
Describe("cmdADD", func() {
|
||||
It(fmt.Sprintf("[%s] fails with invalid UnshapedSubnets", ver), func() {
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "cni-plugin-bandwidth-test",
|
||||
"type": "bandwidth",
|
||||
"ingressRate": 123,
|
||||
"ingressBurst": 123,
|
||||
"egressRate": 123,
|
||||
"egressBurst": 123,
|
||||
"unshapedSubnets": ["10.0.0.0/8", "hello"],
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": ""
|
||||
},
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": "%s"
|
||||
}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "%s/24",
|
||||
"gateway": "10.0.0.1",
|
||||
"interface": 1
|
||||
}
|
||||
],
|
||||
"routes": []
|
||||
}
|
||||
}`, ver, hostIfname, containerIfname, containerNs.Path(), containerIP.String())
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: containerNs.Path(),
|
||||
IfName: "eth0",
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
|
||||
Expect(err).To(MatchError("bad subnet \"hello\" provided, details invalid CIDR address: hello"))
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] fails with invalid ShapedSubnets", ver), func() {
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "cni-plugin-bandwidth-test",
|
||||
"type": "bandwidth",
|
||||
"ingressRate": 123,
|
||||
"ingressBurst": 123,
|
||||
"egressRate": 123,
|
||||
"egressBurst": 123,
|
||||
"ShapedSubnets": ["10.0.0.0/8", "hello"],
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": ""
|
||||
},
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": "%s"
|
||||
}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "%s/24",
|
||||
"gateway": "10.0.0.1",
|
||||
"interface": 1
|
||||
}
|
||||
],
|
||||
"routes": []
|
||||
}
|
||||
}`, ver, hostIfname, containerIfname, containerNs.Path(), containerIP.String())
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: containerNs.Path(),
|
||||
IfName: "eth0",
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
|
||||
Expect(err).To(MatchError("bad subnet \"hello\" provided, details invalid CIDR address: hello"))
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] fails with both ShapedSubnets and UnshapedSubnets specified", ver), func() {
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "cni-plugin-bandwidth-test",
|
||||
"type": "bandwidth",
|
||||
"ingressRate": 123,
|
||||
"ingressBurst": 123,
|
||||
"egressRate": 123,
|
||||
"egressBurst": 123,
|
||||
"shapedSubnets": ["10.0.0.0/8"],
|
||||
"unshapedSubnets": ["192.168.0.0/16"],
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": ""
|
||||
},
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": "%s"
|
||||
}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "%s/24",
|
||||
"gateway": "10.0.0.1",
|
||||
"interface": 1
|
||||
}
|
||||
],
|
||||
"routes": []
|
||||
}
|
||||
}`, ver, hostIfname, containerIfname, containerNs.Path(), containerIP.String())
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: containerNs.Path(),
|
||||
IfName: "eth0",
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
|
||||
Expect(err).To(MatchError("unshapedSubnets and shapedSubnets cannot be both specified, one of them should be discarded"))
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] fails an invalid ingress config", ver), func() {
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "cni-plugin-bandwidth-test",
|
||||
"type": "bandwidth",
|
||||
"ingressRate": 0,
|
||||
"ingressBurst": 123,
|
||||
"egressRate": 123,
|
||||
"egressBurst": 123,
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": ""
|
||||
},
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": "%s"
|
||||
}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "%s/24",
|
||||
"gateway": "10.0.0.1",
|
||||
"interface": 1
|
||||
}
|
||||
],
|
||||
"routes": []
|
||||
}
|
||||
}`, ver, hostIfname, containerIfname, containerNs.Path(), containerIP.String())
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: containerNs.Path(),
|
||||
IfName: "eth0",
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
|
||||
Expect(err).To(MatchError("if burst is set, rate must also be set"))
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] fails an invalid egress config", ver), func() {
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "cni-plugin-bandwidth-test",
|
||||
"type": "bandwidth",
|
||||
"ingressRate": 123,
|
||||
"ingressBurst": 123,
|
||||
"egressRate": 0,
|
||||
"egressBurst": 123,
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": ""
|
||||
},
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": "%s"
|
||||
}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "%s/24",
|
||||
"gateway": "10.0.0.1",
|
||||
"interface": 1
|
||||
}
|
||||
],
|
||||
"routes": []
|
||||
}
|
||||
}`, ver, hostIfname, containerIfname, containerNs.Path(), containerIP.String())
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: containerNs.Path(),
|
||||
IfName: "eth0",
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
|
||||
Expect(err).To(MatchError("if burst is set, rate must also be set"))
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
})
|
||||
|
||||
// Runtime config parameters are expected to be preempted by the global config ones whenever specified
|
||||
It(fmt.Sprintf("[%s] should apply static config when both static config and runtime config exist", ver), func() {
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "cni-plugin-bandwidth-test",
|
||||
"type": "bandwidth",
|
||||
"ingressRate": 0,
|
||||
"ingressBurst": 0,
|
||||
"egressRate": 123,
|
||||
"egressBurst": 123,
|
||||
"unshapedSubnets": ["192.168.0.0/24"],
|
||||
"runtimeConfig": {
|
||||
"bandWidth": {
|
||||
"ingressRate": 8,
|
||||
"ingressBurst": 8,
|
||||
"egressRate": 16,
|
||||
"egressBurst": 9,
|
||||
"unshapedSubnets": ["10.0.0.0/8", "fd00:db8:abcd:1234:e000::/68"]
|
||||
}
|
||||
},
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": ""
|
||||
},
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": "%s"
|
||||
}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "%s/24",
|
||||
"gateway": "10.0.0.1",
|
||||
"interface": 1
|
||||
}
|
||||
],
|
||||
"routes": []
|
||||
}
|
||||
}`, ver, hostIfname, containerIfname, containerNs.Path(), containerIP.String())
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: containerNs.Path(),
|
||||
IfName: containerIfname,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
r, out, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
|
||||
Expect(err).NotTo(HaveOccurred(), string(out))
|
||||
result, err := types100.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(result.Interfaces).To(HaveLen(3))
|
||||
Expect(result.Interfaces[2].Name).To(Equal(ifbDeviceName))
|
||||
Expect(result.Interfaces[2].Sandbox).To(Equal(""))
|
||||
|
||||
ifbLink, err := netlink.LinkByName(ifbDeviceName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(ifbLink.Attrs().MTU).To(Equal(hostIfaceMTU))
|
||||
|
||||
qdiscs, err := netlink.QdiscList(ifbLink)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(qdiscs).To(HaveLen(1))
|
||||
Expect(qdiscs[0].Attrs().LinkIndex).To(Equal(ifbLink.Attrs().Index))
|
||||
Expect(qdiscs[0]).To(BeAssignableToTypeOf(&netlink.Htb{}))
|
||||
Expect(qdiscs[0].(*netlink.Htb).Defcls).To(Equal(uint32(ShapedClassMinorID)))
|
||||
|
||||
classes, err := netlink.ClassList(ifbLink, qdiscs[0].Attrs().Handle)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(classes).To(HaveLen(2))
|
||||
|
||||
// Uncapped class
|
||||
Expect(classes[0]).To(BeAssignableToTypeOf(&netlink.HtbClass{}))
|
||||
Expect(classes[0].(*netlink.HtbClass).Handle).To(Equal(netlink.MakeHandle(1, 1)))
|
||||
Expect(classes[0].(*netlink.HtbClass).Rate).To(Equal(UncappedRate))
|
||||
Expect(classes[0].(*netlink.HtbClass).Buffer).To(Equal(uint32(0)))
|
||||
Expect(classes[0].(*netlink.HtbClass).Ceil).To(Equal(UncappedRate))
|
||||
Expect(classes[0].(*netlink.HtbClass).Cbuffer).To(Equal(uint32(0)))
|
||||
|
||||
// Class with traffic shapping settings
|
||||
Expect(classes[1]).To(BeAssignableToTypeOf(&netlink.HtbClass{}))
|
||||
Expect(classes[1].(*netlink.HtbClass).Handle).To(Equal(netlink.MakeHandle(1, uint16(qdiscs[0].(*netlink.Htb).Defcls))))
|
||||
Expect(classes[1].(*netlink.HtbClass).Rate).To(Equal(uint64(15)))
|
||||
// Expect(classes[1].(*netlink.HtbClass).Buffer).To(Equal(uint32(7812500)))
|
||||
Expect(classes[1].(*netlink.HtbClass).Ceil).To(Equal(uint64(30)))
|
||||
// Expect(classes[1].(*netlink.HtbClass).Cbuffer).To(Equal(uint32(0)))
|
||||
|
||||
filters, err := netlink.FilterList(ifbLink, qdiscs[0].Attrs().Handle)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(filters).To(HaveLen(1))
|
||||
|
||||
// traffic to 192.168.0.0/24 redirected to uncapped class
|
||||
Expect(filters[0]).To(BeAssignableToTypeOf(&netlink.U32{}))
|
||||
Expect(filters[0].(*netlink.U32).Actions).To(BeEmpty())
|
||||
Expect(filters[0].Attrs().Protocol).To(Equal(uint16(syscall.ETH_P_IP)))
|
||||
Expect(filters[0].Attrs().LinkIndex).To(Equal(ifbLink.Attrs().Index))
|
||||
Expect(filters[0].Attrs().Priority).To(Equal(uint16(16)))
|
||||
Expect(filters[0].Attrs().Parent).To(Equal(qdiscs[0].Attrs().Handle))
|
||||
Expect(filters[0].(*netlink.U32).ClassId).To(Equal(netlink.MakeHandle(1, 1)))
|
||||
|
||||
filterSel := filters[0].(*netlink.U32).Sel
|
||||
Expect(filterSel).To(BeAssignableToTypeOf(&netlink.TcU32Sel{}))
|
||||
Expect(filterSel.Flags).To(Equal(uint8(netlink.TC_U32_TERMINAL)))
|
||||
Expect(filterSel.Keys).To(HaveLen(1))
|
||||
Expect(filterSel.Nkeys).To(Equal(uint8(1)))
|
||||
|
||||
// The filter should match to 192.168.0.0/24 dst address in other words it should be:
|
||||
// match c0a80000/ffffff00 at 16
|
||||
selKey := filterSel.Keys[0]
|
||||
Expect(selKey.Val).To(Equal(uint32(192*math.Pow(256, 3) + 168*math.Pow(256, 2))))
|
||||
Expect(selKey.Off).To(Equal(int32(16)))
|
||||
Expect(selKey.Mask).To(Equal(uint32(255*math.Pow(256, 3) + 255*math.Pow(256, 2) + 255*256)))
|
||||
|
||||
hostVethLink, err := netlink.LinkByName(hostIfname)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
qdiscFilters, err := netlink.FilterList(hostVethLink, netlink.MakeHandle(0xffff, 0))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(qdiscFilters).To(HaveLen(1))
|
||||
Expect(qdiscFilters[0].(*netlink.U32).Actions[0].(*netlink.MirredAction).Ifindex).To(Equal(ifbLink.Attrs().Index))
|
||||
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
|
||||
// Container ingress (host egress)
|
||||
Expect(hostNs.Do(func(n ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
vethLink, err := netlink.LinkByName(hostIfname)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
qdiscs, err := netlink.QdiscList(vethLink)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// No ingress QoS just mirroring
|
||||
Expect(qdiscs).To(HaveLen(2))
|
||||
Expect(qdiscs[0].Attrs().LinkIndex).To(Equal(vethLink.Attrs().Index))
|
||||
Expect(qdiscs[0]).NotTo(BeAssignableToTypeOf(&netlink.Htb{}))
|
||||
Expect(qdiscs[1]).NotTo(BeAssignableToTypeOf(&netlink.Htb{}))
|
||||
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] should apply static config when both static config and runtime config exist (bad config example)", ver), func() {
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "cni-plugin-bandwidth-test",
|
||||
"type": "bandwidth",
|
||||
"ingressRate": 0,
|
||||
"ingressBurst": 123,
|
||||
"egressRate": 123,
|
||||
"egressBurst": 123,
|
||||
"runtimeConfig": {
|
||||
"bandWidth": {
|
||||
"ingressRate": 8,
|
||||
"ingressBurst": 8,
|
||||
"egressRate": 16,
|
||||
"egressBurst": 9
|
||||
}
|
||||
},
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": ""
|
||||
},
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": "%s"
|
||||
}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "%s/24",
|
||||
"gateway": "10.0.0.1",
|
||||
"interface": 1
|
||||
}
|
||||
],
|
||||
"routes": []
|
||||
}
|
||||
}`, ver, hostIfname, containerIfname, containerNs.Path(), containerIP.String())
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: containerNs.Path(),
|
||||
IfName: "eth0",
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
|
||||
Expect(err).To(MatchError("if burst is set, rate must also be set"))
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
Describe("Validating input", func() {
|
||||
It("Should allow only 4GB burst rate", func() {
|
||||
err := validateRateAndBurst(5000, 4*1024*1024*1024*8-16) // 2 bytes less than the max should pass
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = validateRateAndBurst(5000, 4*1024*1024*1024*8) // we're 1 bit above MaxUint32
|
||||
Expect(err).To(HaveOccurred())
|
||||
err = validateRateAndBurst(0, 1)
|
||||
Expect(err).To(HaveOccurred())
|
||||
err = validateRateAndBurst(1, 0)
|
||||
Expect(err).To(HaveOccurred())
|
||||
err = validateRateAndBurst(0, 0)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("Should fail if both ShapedSubnets and UnshapedSubnets are specified", func() {
|
||||
err := validateSubnets([]string{"10.0.0.0/8"}, []string{"192.168.0.0/24"})
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("Should fail if specified UnshapedSubnets are not valid CIDRs", func() {
|
||||
err := validateSubnets([]string{"10.0.0.0/8", "hello"}, []string{})
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("Should fail if specified ShapedSubnets are not valid CIDRs", func() {
|
||||
err := validateSubnets([]string{}, []string{"10.0.0.0/8", "hello"})
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
})
|
||||
})
|
File diff suppressed because it is too large
Load Diff
@ -1,824 +0,0 @@
|
||||
// Copyright 2023 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/onsi/gomega/gexec"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/invoke"
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
types100 "github.com/containernetworking/cni/pkg/types/100"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/testutils"
|
||||
)
|
||||
|
||||
var _ = Describe("bandwidth measure test", func() {
|
||||
var (
|
||||
hostNs ns.NetNS
|
||||
containerNs ns.NetNS
|
||||
hostIfname string
|
||||
containerIfname string
|
||||
hostIP net.IP
|
||||
containerIP net.IP
|
||||
hostIfaceMTU int
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
|
||||
hostIfname = "host-veth"
|
||||
containerIfname = "container-veth"
|
||||
|
||||
hostNs, err = testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
containerNs, err = testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
hostIP = net.IP{169, 254, 0, 1}
|
||||
containerIP = net.IP{10, 254, 0, 1}
|
||||
hostIfaceMTU = 1024
|
||||
|
||||
createVeth(hostNs, hostIfname, containerNs, containerIfname, hostIP, containerIP, hostIfaceMTU)
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(containerNs.Close()).To(Succeed())
|
||||
Expect(testutils.UnmountNS(containerNs)).To(Succeed())
|
||||
Expect(hostNs.Close()).To(Succeed())
|
||||
Expect(testutils.UnmountNS(hostNs)).To(Succeed())
|
||||
})
|
||||
|
||||
// Bandwidth requires host-side interface info, and thus only
|
||||
// supports 0.3.0 and later CNI versions
|
||||
for _, ver := range []string{"0.3.0", "0.3.1", "0.4.0", "1.0.0"} {
|
||||
Describe(fmt.Sprintf("[%s] QoS effective", ver), func() {
|
||||
Context(fmt.Sprintf("[%s] when chaining bandwidth plugin with PTP", ver), func() {
|
||||
var ptpConf string
|
||||
var rateInBits uint64
|
||||
var burstInBits uint64
|
||||
var packetInBytes int
|
||||
var containerWithoutQoSNS ns.NetNS
|
||||
var containerWithQoSNS ns.NetNS
|
||||
var portServerWithQoS int
|
||||
var portServerWithoutQoS int
|
||||
|
||||
var containerWithQoSRes types.Result
|
||||
var containerWithoutQoSRes types.Result
|
||||
var echoServerWithQoS *gexec.Session
|
||||
var echoServerWithoutQoS *gexec.Session
|
||||
var dataDir string
|
||||
|
||||
BeforeEach(func() {
|
||||
rateInBytes := 1000
|
||||
rateInBits = uint64(rateInBytes * 8)
|
||||
burstInBits = rateInBits * 2
|
||||
|
||||
// NOTE: Traffic shapping is not that precise at low rates, would be better to use higher rates + simple time+netcat for data transfer, rather than the provided
|
||||
// client/server bin (limited to small amount of data)
|
||||
packetInBytes = rateInBytes * 3
|
||||
|
||||
var err error
|
||||
dataDir, err = os.MkdirTemp("", "bandwidth_linux_test")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
ptpConf = fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "myBWnet",
|
||||
"type": "ptp",
|
||||
"ipMasq": true,
|
||||
"mtu": 512,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.2.0/24",
|
||||
"dataDir": "%s"
|
||||
}
|
||||
}`, ver, dataDir)
|
||||
|
||||
const (
|
||||
containerWithQoSIFName = "ptp0"
|
||||
containerWithoutQoSIFName = "ptp1"
|
||||
)
|
||||
|
||||
containerWithQoSNS, err = testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
containerWithoutQoSNS, err = testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("create two containers, and use the bandwidth plugin on one of them")
|
||||
Expect(hostNs.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
containerWithQoSRes, _, err = testutils.CmdAdd(containerWithQoSNS.Path(), "dummy", containerWithQoSIFName, []byte(ptpConf), func() error {
|
||||
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(r.Print()).To(Succeed())
|
||||
|
||||
return err
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
containerWithoutQoSRes, _, err = testutils.CmdAdd(containerWithoutQoSNS.Path(), "dummy2", containerWithoutQoSIFName, []byte(ptpConf), func() error {
|
||||
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(r.Print()).To(Succeed())
|
||||
|
||||
return err
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
containerWithQoSResult, err := types100.GetResult(containerWithQoSRes)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
bandwidthPluginConf := &PluginConf{}
|
||||
err = json.Unmarshal([]byte(ptpConf), &bandwidthPluginConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
bandwidthPluginConf.RuntimeConfig.Bandwidth = &BandwidthEntry{
|
||||
IngressBurst: burstInBits,
|
||||
IngressRate: rateInBits,
|
||||
EgressBurst: burstInBits,
|
||||
EgressRate: rateInBits,
|
||||
}
|
||||
bandwidthPluginConf.Type = "bandwidth"
|
||||
newConfBytes, err := buildOneConfig(ver, bandwidthPluginConf, containerWithQoSResult)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy3",
|
||||
Netns: containerWithQoSNS.Path(),
|
||||
IfName: containerWithQoSIFName,
|
||||
StdinData: newConfBytes,
|
||||
}
|
||||
|
||||
result, out, err := testutils.CmdAdd(containerWithQoSNS.Path(), args.ContainerID, "", newConfBytes, func() error { return cmdAdd(args) })
|
||||
Expect(err).NotTo(HaveOccurred(), string(out))
|
||||
|
||||
if testutils.SpecVersionHasCHECK(ver) {
|
||||
// Do CNI Check
|
||||
checkConf := &PluginConf{}
|
||||
err = json.Unmarshal([]byte(ptpConf), &checkConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
checkConf.RuntimeConfig.Bandwidth = &BandwidthEntry{
|
||||
IngressBurst: burstInBits,
|
||||
IngressRate: rateInBits,
|
||||
EgressBurst: burstInBits,
|
||||
EgressRate: rateInBits,
|
||||
}
|
||||
checkConf.Type = "bandwidth"
|
||||
|
||||
newCheckBytes, err := buildOneConfig(ver, checkConf, result)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
args = &skel.CmdArgs{
|
||||
ContainerID: "dummy3",
|
||||
Netns: containerWithQoSNS.Path(),
|
||||
IfName: containerWithQoSIFName,
|
||||
StdinData: newCheckBytes,
|
||||
}
|
||||
|
||||
err = testutils.CmdCheck(containerWithQoSNS.Path(), args.ContainerID, "", func() error { return cmdCheck(args) })
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
|
||||
By("starting a tcp server on both containers")
|
||||
portServerWithQoS, echoServerWithQoS = startEchoServerInNamespace(containerWithQoSNS)
|
||||
portServerWithoutQoS, echoServerWithoutQoS = startEchoServerInNamespace(containerWithoutQoSNS)
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(os.RemoveAll(dataDir)).To(Succeed())
|
||||
|
||||
Expect(containerWithQoSNS.Close()).To(Succeed())
|
||||
Expect(testutils.UnmountNS(containerWithQoSNS)).To(Succeed())
|
||||
Expect(containerWithoutQoSNS.Close()).To(Succeed())
|
||||
Expect(testutils.UnmountNS(containerWithoutQoSNS)).To(Succeed())
|
||||
|
||||
if echoServerWithoutQoS != nil {
|
||||
echoServerWithoutQoS.Kill()
|
||||
}
|
||||
if echoServerWithQoS != nil {
|
||||
echoServerWithQoS.Kill()
|
||||
}
|
||||
})
|
||||
|
||||
It("limits ingress traffic on veth device", func() {
|
||||
var runtimeWithLimit time.Duration
|
||||
var runtimeWithoutLimit time.Duration
|
||||
|
||||
By("gather timing statistics about both containers")
|
||||
|
||||
By("sending tcp traffic to the container that has traffic shaped", func() {
|
||||
start := time.Now()
|
||||
result, err := types100.GetResult(containerWithQoSRes)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
makeTCPClientInNS(hostNs.Path(), result.IPs[0].Address.IP.String(), portServerWithQoS, packetInBytes)
|
||||
end := time.Now()
|
||||
runtimeWithLimit = end.Sub(start)
|
||||
log.Printf("Elapsed with qos %.2f", runtimeWithLimit.Seconds())
|
||||
})
|
||||
|
||||
By("sending tcp traffic to the container that does not have traffic shaped", func() {
|
||||
start := time.Now()
|
||||
result, err := types100.GetResult(containerWithoutQoSRes)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
makeTCPClientInNS(hostNs.Path(), result.IPs[0].Address.IP.String(), portServerWithoutQoS, packetInBytes)
|
||||
end := time.Now()
|
||||
runtimeWithoutLimit = end.Sub(start)
|
||||
log.Printf("Elapsed without qos %.2f", runtimeWithoutLimit.Seconds())
|
||||
})
|
||||
|
||||
Expect(runtimeWithLimit).To(BeNumerically(">", runtimeWithoutLimit+1000*time.Millisecond))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Context(fmt.Sprintf("[%s] when chaining bandwidth plugin with PTP and excluding specific subnets from traffic", ver), func() {
|
||||
var ptpConf string
|
||||
var rateInBits uint64
|
||||
var burstInBits uint64
|
||||
var packetInBytes int
|
||||
var containerWithoutQoSNS ns.NetNS
|
||||
var containerWithQoSNS ns.NetNS
|
||||
var portServerWithQoS int
|
||||
var portServerWithoutQoS int
|
||||
|
||||
var containerWithQoSRes types.Result
|
||||
var containerWithoutQoSRes types.Result
|
||||
var echoServerWithQoS *gexec.Session
|
||||
var echoServerWithoutQoS *gexec.Session
|
||||
var dataDir string
|
||||
|
||||
BeforeEach(func() {
|
||||
rateInBytes := 1000
|
||||
rateInBits = uint64(rateInBytes * 8)
|
||||
burstInBits = rateInBits * 2
|
||||
unshapedSubnets := []string{"10.1.2.0/24"}
|
||||
// NOTE: Traffic shapping is not that precise at low rates, would be better to use higher rates + simple time+netcat for data transfer, rather than the provided
|
||||
// client/server bin (limited to small amount of data)
|
||||
packetInBytes = rateInBytes * 3
|
||||
|
||||
var err error
|
||||
dataDir, err = os.MkdirTemp("", "bandwidth_linux_test")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
ptpConf = fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "myBWnet",
|
||||
"type": "ptp",
|
||||
"ipMasq": true,
|
||||
"mtu": 512,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.2.0/24",
|
||||
"dataDir": "%s"
|
||||
}
|
||||
}`, ver, dataDir)
|
||||
|
||||
const (
|
||||
containerWithQoSIFName = "ptp0"
|
||||
containerWithoutQoSIFName = "ptp1"
|
||||
)
|
||||
|
||||
containerWithQoSNS, err = testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
containerWithoutQoSNS, err = testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("create two containers, and use the bandwidth plugin on one of them")
|
||||
Expect(hostNs.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
containerWithQoSRes, _, err = testutils.CmdAdd(containerWithQoSNS.Path(), "dummy", containerWithQoSIFName, []byte(ptpConf), func() error {
|
||||
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(r.Print()).To(Succeed())
|
||||
|
||||
return err
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
containerWithoutQoSRes, _, err = testutils.CmdAdd(containerWithoutQoSNS.Path(), "dummy2", containerWithoutQoSIFName, []byte(ptpConf), func() error {
|
||||
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(r.Print()).To(Succeed())
|
||||
|
||||
return err
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
containerWithQoSResult, err := types100.GetResult(containerWithQoSRes)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
bandwidthPluginConf := &PluginConf{}
|
||||
err = json.Unmarshal([]byte(ptpConf), &bandwidthPluginConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
bandwidthPluginConf.RuntimeConfig.Bandwidth = &BandwidthEntry{
|
||||
IngressBurst: burstInBits,
|
||||
IngressRate: rateInBits,
|
||||
EgressBurst: burstInBits,
|
||||
EgressRate: rateInBits,
|
||||
UnshapedSubnets: unshapedSubnets,
|
||||
}
|
||||
bandwidthPluginConf.Type = "bandwidth"
|
||||
newConfBytes, err := buildOneConfig(ver, bandwidthPluginConf, containerWithQoSResult)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy3",
|
||||
Netns: containerWithQoSNS.Path(),
|
||||
IfName: containerWithQoSIFName,
|
||||
StdinData: newConfBytes,
|
||||
}
|
||||
|
||||
result, out, err := testutils.CmdAdd(containerWithQoSNS.Path(), args.ContainerID, "", newConfBytes, func() error { return cmdAdd(args) })
|
||||
Expect(err).NotTo(HaveOccurred(), string(out))
|
||||
|
||||
if testutils.SpecVersionHasCHECK(ver) {
|
||||
// Do CNI Check
|
||||
checkConf := &PluginConf{}
|
||||
err = json.Unmarshal([]byte(ptpConf), &checkConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
checkConf.RuntimeConfig.Bandwidth = &BandwidthEntry{
|
||||
IngressBurst: burstInBits,
|
||||
IngressRate: rateInBits,
|
||||
EgressBurst: burstInBits,
|
||||
EgressRate: rateInBits,
|
||||
UnshapedSubnets: unshapedSubnets,
|
||||
}
|
||||
checkConf.Type = "bandwidth"
|
||||
|
||||
newCheckBytes, err := buildOneConfig(ver, checkConf, result)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
args = &skel.CmdArgs{
|
||||
ContainerID: "dummy3",
|
||||
Netns: containerWithQoSNS.Path(),
|
||||
IfName: containerWithQoSIFName,
|
||||
StdinData: newCheckBytes,
|
||||
}
|
||||
|
||||
err = testutils.CmdCheck(containerWithQoSNS.Path(), args.ContainerID, "", func() error { return cmdCheck(args) })
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
|
||||
By("starting a tcp server on both containers")
|
||||
portServerWithQoS, echoServerWithQoS = startEchoServerInNamespace(containerWithQoSNS)
|
||||
portServerWithoutQoS, echoServerWithoutQoS = startEchoServerInNamespace(containerWithoutQoSNS)
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(os.RemoveAll(dataDir)).To(Succeed())
|
||||
|
||||
Expect(containerWithQoSNS.Close()).To(Succeed())
|
||||
Expect(testutils.UnmountNS(containerWithQoSNS)).To(Succeed())
|
||||
Expect(containerWithoutQoSNS.Close()).To(Succeed())
|
||||
Expect(testutils.UnmountNS(containerWithoutQoSNS)).To(Succeed())
|
||||
|
||||
if echoServerWithoutQoS != nil {
|
||||
echoServerWithoutQoS.Kill()
|
||||
}
|
||||
if echoServerWithQoS != nil {
|
||||
echoServerWithQoS.Kill()
|
||||
}
|
||||
})
|
||||
|
||||
It("does not limits ingress traffic on veth device coming from 10.1.2.0/24", func() {
|
||||
var runtimeWithLimit time.Duration
|
||||
var runtimeWithoutLimit time.Duration
|
||||
|
||||
By("gather timing statistics about both containers")
|
||||
|
||||
By("sending tcp traffic to the container that has traffic shaped", func() {
|
||||
start := time.Now()
|
||||
result, err := types100.GetResult(containerWithQoSRes)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
makeTCPClientInNS(hostNs.Path(), result.IPs[0].Address.IP.String(), portServerWithQoS, packetInBytes)
|
||||
end := time.Now()
|
||||
runtimeWithLimit = end.Sub(start)
|
||||
log.Printf("Elapsed with qos %.2f", runtimeWithLimit.Seconds())
|
||||
})
|
||||
|
||||
By("sending tcp traffic to the container that does not have traffic shaped", func() {
|
||||
start := time.Now()
|
||||
result, err := types100.GetResult(containerWithoutQoSRes)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
makeTCPClientInNS(hostNs.Path(), result.IPs[0].Address.IP.String(), portServerWithoutQoS, packetInBytes)
|
||||
end := time.Now()
|
||||
runtimeWithoutLimit = end.Sub(start)
|
||||
log.Printf("Elapsed without qos %.2f", runtimeWithoutLimit.Seconds())
|
||||
})
|
||||
|
||||
Expect(runtimeWithLimit - runtimeWithoutLimit).To(BeNumerically("<", 100*time.Millisecond))
|
||||
})
|
||||
})
|
||||
|
||||
Context(fmt.Sprintf("[%s] when chaining bandwidth plugin with PTP and only including specific subnets in traffic shapping (not including the main ns one)", ver), func() {
|
||||
var ptpConf string
|
||||
var rateInBits uint64
|
||||
var burstInBits uint64
|
||||
var packetInBytes int
|
||||
var containerWithoutQoSNS ns.NetNS
|
||||
var containerWithQoSNS ns.NetNS
|
||||
var portServerWithQoS int
|
||||
var portServerWithoutQoS int
|
||||
|
||||
var containerWithQoSRes types.Result
|
||||
var containerWithoutQoSRes types.Result
|
||||
var echoServerWithQoS *gexec.Session
|
||||
var echoServerWithoutQoS *gexec.Session
|
||||
var dataDir string
|
||||
|
||||
BeforeEach(func() {
|
||||
rateInBytes := 1000
|
||||
rateInBits = uint64(rateInBytes * 8)
|
||||
burstInBits = rateInBits * 2
|
||||
shapedSubnets := []string{"10.2.2.0/24"}
|
||||
// NOTE: Traffic shapping is not that precise at low rates, would be better to use higher rates + simple time+netcat for data transfer, rather than the provided
|
||||
// client/server bin (limited to small amount of data)
|
||||
packetInBytes = rateInBytes * 3
|
||||
|
||||
var err error
|
||||
dataDir, err = os.MkdirTemp("", "bandwidth_linux_test")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
ptpConf = fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "myBWnet",
|
||||
"type": "ptp",
|
||||
"ipMasq": true,
|
||||
"mtu": 512,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.2.0/24",
|
||||
"dataDir": "%s"
|
||||
}
|
||||
}`, ver, dataDir)
|
||||
|
||||
const (
|
||||
containerWithQoSIFName = "ptp0"
|
||||
containerWithoutQoSIFName = "ptp1"
|
||||
)
|
||||
|
||||
containerWithQoSNS, err = testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
containerWithoutQoSNS, err = testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("create two containers, and use the bandwidth plugin on one of them")
|
||||
|
||||
Expect(hostNs.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
containerWithQoSRes, _, err = testutils.CmdAdd(containerWithQoSNS.Path(), "dummy", containerWithQoSIFName, []byte(ptpConf), func() error {
|
||||
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(r.Print()).To(Succeed())
|
||||
|
||||
return err
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
containerWithoutQoSRes, _, err = testutils.CmdAdd(containerWithoutQoSNS.Path(), "dummy2", containerWithoutQoSIFName, []byte(ptpConf), func() error {
|
||||
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(r.Print()).To(Succeed())
|
||||
|
||||
return err
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
containerWithQoSResult, err := types100.GetResult(containerWithQoSRes)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
bandwidthPluginConf := &PluginConf{}
|
||||
err = json.Unmarshal([]byte(ptpConf), &bandwidthPluginConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
bandwidthPluginConf.RuntimeConfig.Bandwidth = &BandwidthEntry{
|
||||
IngressBurst: burstInBits,
|
||||
IngressRate: rateInBits,
|
||||
EgressBurst: burstInBits,
|
||||
EgressRate: rateInBits,
|
||||
ShapedSubnets: shapedSubnets,
|
||||
}
|
||||
bandwidthPluginConf.Type = "bandwidth"
|
||||
newConfBytes, err := buildOneConfig(ver, bandwidthPluginConf, containerWithQoSResult)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy3",
|
||||
Netns: containerWithQoSNS.Path(),
|
||||
IfName: containerWithQoSIFName,
|
||||
StdinData: newConfBytes,
|
||||
}
|
||||
|
||||
result, out, err := testutils.CmdAdd(containerWithQoSNS.Path(), args.ContainerID, "", newConfBytes, func() error { return cmdAdd(args) })
|
||||
Expect(err).NotTo(HaveOccurred(), string(out))
|
||||
|
||||
if testutils.SpecVersionHasCHECK(ver) {
|
||||
// Do CNI Check
|
||||
checkConf := &PluginConf{}
|
||||
err = json.Unmarshal([]byte(ptpConf), &checkConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
checkConf.RuntimeConfig.Bandwidth = &BandwidthEntry{
|
||||
IngressBurst: burstInBits,
|
||||
IngressRate: rateInBits,
|
||||
EgressBurst: burstInBits,
|
||||
EgressRate: rateInBits,
|
||||
ShapedSubnets: shapedSubnets,
|
||||
}
|
||||
checkConf.Type = "bandwidth"
|
||||
|
||||
newCheckBytes, err := buildOneConfig(ver, checkConf, result)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
args = &skel.CmdArgs{
|
||||
ContainerID: "dummy3",
|
||||
Netns: containerWithQoSNS.Path(),
|
||||
IfName: containerWithQoSIFName,
|
||||
StdinData: newCheckBytes,
|
||||
}
|
||||
|
||||
err = testutils.CmdCheck(containerWithQoSNS.Path(), args.ContainerID, "", func() error { return cmdCheck(args) })
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
|
||||
By("starting a tcp server on both containers")
|
||||
portServerWithQoS, echoServerWithQoS = startEchoServerInNamespace(containerWithQoSNS)
|
||||
portServerWithoutQoS, echoServerWithoutQoS = startEchoServerInNamespace(containerWithoutQoSNS)
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(os.RemoveAll(dataDir)).To(Succeed())
|
||||
|
||||
Expect(containerWithQoSNS.Close()).To(Succeed())
|
||||
Expect(testutils.UnmountNS(containerWithQoSNS)).To(Succeed())
|
||||
Expect(containerWithoutQoSNS.Close()).To(Succeed())
|
||||
Expect(testutils.UnmountNS(containerWithoutQoSNS)).To(Succeed())
|
||||
|
||||
if echoServerWithoutQoS != nil {
|
||||
echoServerWithoutQoS.Kill()
|
||||
}
|
||||
if echoServerWithQoS != nil {
|
||||
echoServerWithQoS.Kill()
|
||||
}
|
||||
})
|
||||
|
||||
It("does not limit ingress traffic on veth device coming from non included subnets", func() {
|
||||
var runtimeWithLimit time.Duration
|
||||
var runtimeWithoutLimit time.Duration
|
||||
|
||||
By("gather timing statistics about both containers")
|
||||
|
||||
By("sending tcp traffic to the container that has traffic shaped", func() {
|
||||
start := time.Now()
|
||||
result, err := types100.GetResult(containerWithQoSRes)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
makeTCPClientInNS(hostNs.Path(), result.IPs[0].Address.IP.String(), portServerWithQoS, packetInBytes)
|
||||
end := time.Now()
|
||||
runtimeWithLimit = end.Sub(start)
|
||||
log.Printf("Elapsed with qos %.2f", runtimeWithLimit.Seconds())
|
||||
})
|
||||
|
||||
By("sending tcp traffic to the container that does not have traffic shaped", func() {
|
||||
start := time.Now()
|
||||
result, err := types100.GetResult(containerWithoutQoSRes)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
makeTCPClientInNS(hostNs.Path(), result.IPs[0].Address.IP.String(), portServerWithoutQoS, packetInBytes)
|
||||
end := time.Now()
|
||||
runtimeWithoutLimit = end.Sub(start)
|
||||
log.Printf("Elapsed without qos %.2f", runtimeWithoutLimit.Seconds())
|
||||
})
|
||||
|
||||
Expect(runtimeWithLimit - runtimeWithoutLimit).To(BeNumerically("<", 100*time.Millisecond))
|
||||
})
|
||||
})
|
||||
|
||||
Context(fmt.Sprintf("[%s] when chaining bandwidth plugin with PTP and only including specific subnets in traffic shapping (including the main ns one)", ver), func() {
|
||||
var ptpConf string
|
||||
var rateInBits uint64
|
||||
var burstInBits uint64
|
||||
var packetInBytes int
|
||||
var containerWithoutQoSNS ns.NetNS
|
||||
var containerWithQoSNS ns.NetNS
|
||||
var portServerWithQoS int
|
||||
var portServerWithoutQoS int
|
||||
|
||||
var containerWithQoSRes types.Result
|
||||
var containerWithoutQoSRes types.Result
|
||||
var echoServerWithQoS *gexec.Session
|
||||
var echoServerWithoutQoS *gexec.Session
|
||||
var dataDir string
|
||||
|
||||
BeforeEach(func() {
|
||||
rateInBytes := 1000
|
||||
rateInBits = uint64(rateInBytes * 8)
|
||||
burstInBits = rateInBits * 2
|
||||
shapedSubnets := []string{"10.1.2.1/32"}
|
||||
// NOTE: Traffic shapping is not that precise at low rates, would be better to use higher rates + simple time+netcat for data transfer, rather than the provided
|
||||
// client/server bin (limited to small amount of data)
|
||||
packetInBytes = rateInBytes * 3
|
||||
|
||||
var err error
|
||||
dataDir, err = os.MkdirTemp("", "bandwidth_linux_test")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
ptpConf = fmt.Sprintf(`{
|
||||
"cniVersion": "%s",
|
||||
"name": "myBWnet",
|
||||
"type": "ptp",
|
||||
"ipMasq": true,
|
||||
"mtu": 512,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.2.0/24",
|
||||
"dataDir": "%s"
|
||||
}
|
||||
}`, ver, dataDir)
|
||||
|
||||
const (
|
||||
containerWithQoSIFName = "ptp0"
|
||||
containerWithoutQoSIFName = "ptp1"
|
||||
)
|
||||
|
||||
containerWithQoSNS, err = testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
containerWithoutQoSNS, err = testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("create two containers, and use the bandwidth plugin on one of them")
|
||||
|
||||
Expect(hostNs.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
containerWithQoSRes, _, err = testutils.CmdAdd(containerWithQoSNS.Path(), "dummy", containerWithQoSIFName, []byte(ptpConf), func() error {
|
||||
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(r.Print()).To(Succeed())
|
||||
|
||||
return err
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
containerWithoutQoSRes, _, err = testutils.CmdAdd(containerWithoutQoSNS.Path(), "dummy2", containerWithoutQoSIFName, []byte(ptpConf), func() error {
|
||||
r, err := invoke.DelegateAdd(context.TODO(), "ptp", []byte(ptpConf), nil)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(r.Print()).To(Succeed())
|
||||
|
||||
return err
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
containerWithQoSResult, err := types100.GetResult(containerWithQoSRes)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
bandwidthPluginConf := &PluginConf{}
|
||||
err = json.Unmarshal([]byte(ptpConf), &bandwidthPluginConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
bandwidthPluginConf.RuntimeConfig.Bandwidth = &BandwidthEntry{
|
||||
IngressBurst: burstInBits,
|
||||
IngressRate: rateInBits,
|
||||
EgressBurst: burstInBits,
|
||||
EgressRate: rateInBits,
|
||||
ShapedSubnets: shapedSubnets,
|
||||
}
|
||||
bandwidthPluginConf.Type = "bandwidth"
|
||||
newConfBytes, err := buildOneConfig(ver, bandwidthPluginConf, containerWithQoSResult)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy3",
|
||||
Netns: containerWithQoSNS.Path(),
|
||||
IfName: containerWithQoSIFName,
|
||||
StdinData: newConfBytes,
|
||||
}
|
||||
|
||||
result, out, err := testutils.CmdAdd(containerWithQoSNS.Path(), args.ContainerID, "", newConfBytes, func() error { return cmdAdd(args) })
|
||||
Expect(err).NotTo(HaveOccurred(), string(out))
|
||||
|
||||
if testutils.SpecVersionHasCHECK(ver) {
|
||||
// Do CNI Check
|
||||
checkConf := &PluginConf{}
|
||||
err = json.Unmarshal([]byte(ptpConf), &checkConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
checkConf.RuntimeConfig.Bandwidth = &BandwidthEntry{
|
||||
IngressBurst: burstInBits,
|
||||
IngressRate: rateInBits,
|
||||
EgressBurst: burstInBits,
|
||||
EgressRate: rateInBits,
|
||||
ShapedSubnets: shapedSubnets,
|
||||
}
|
||||
checkConf.Type = "bandwidth"
|
||||
|
||||
newCheckBytes, err := buildOneConfig(ver, checkConf, result)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
args = &skel.CmdArgs{
|
||||
ContainerID: "dummy3",
|
||||
Netns: containerWithQoSNS.Path(),
|
||||
IfName: containerWithQoSIFName,
|
||||
StdinData: newCheckBytes,
|
||||
}
|
||||
|
||||
err = testutils.CmdCheck(containerWithQoSNS.Path(), args.ContainerID, "", func() error { return cmdCheck(args) })
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
|
||||
By("starting a tcp server on both containers")
|
||||
portServerWithQoS, echoServerWithQoS = startEchoServerInNamespace(containerWithQoSNS)
|
||||
portServerWithoutQoS, echoServerWithoutQoS = startEchoServerInNamespace(containerWithoutQoSNS)
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(os.RemoveAll(dataDir)).To(Succeed())
|
||||
|
||||
Expect(containerWithQoSNS.Close()).To(Succeed())
|
||||
Expect(testutils.UnmountNS(containerWithQoSNS)).To(Succeed())
|
||||
Expect(containerWithoutQoSNS.Close()).To(Succeed())
|
||||
Expect(testutils.UnmountNS(containerWithoutQoSNS)).To(Succeed())
|
||||
|
||||
if echoServerWithoutQoS != nil {
|
||||
echoServerWithoutQoS.Kill()
|
||||
}
|
||||
if echoServerWithQoS != nil {
|
||||
echoServerWithQoS.Kill()
|
||||
}
|
||||
})
|
||||
|
||||
It("limits ingress traffic on veth device coming from included subnets", func() {
|
||||
var runtimeWithLimit time.Duration
|
||||
var runtimeWithoutLimit time.Duration
|
||||
|
||||
By("gather timing statistics about both containers")
|
||||
|
||||
By("sending tcp traffic to the container that has traffic shaped", func() {
|
||||
start := time.Now()
|
||||
result, err := types100.GetResult(containerWithQoSRes)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
makeTCPClientInNS(hostNs.Path(), result.IPs[0].Address.IP.String(), portServerWithQoS, packetInBytes)
|
||||
end := time.Now()
|
||||
runtimeWithLimit = end.Sub(start)
|
||||
log.Printf("Elapsed with qos %.2f", runtimeWithLimit.Seconds())
|
||||
})
|
||||
|
||||
By("sending tcp traffic to the container that does not have traffic shaped", func() {
|
||||
start := time.Now()
|
||||
result, err := types100.GetResult(containerWithoutQoSRes)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
makeTCPClientInNS(hostNs.Path(), result.IPs[0].Address.IP.String(), portServerWithoutQoS, packetInBytes)
|
||||
end := time.Now()
|
||||
runtimeWithoutLimit = end.Sub(start)
|
||||
log.Printf("Elapsed without qos %.2f", runtimeWithoutLimit.Seconds())
|
||||
})
|
||||
|
||||
Expect(runtimeWithLimit).To(BeNumerically(">", runtimeWithoutLimit+1000*time.Millisecond))
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
@ -15,7 +15,6 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
@ -31,11 +30,10 @@ import (
|
||||
"github.com/onsi/gomega/gexec"
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
)
|
||||
|
||||
func TestHTB(t *testing.T) {
|
||||
func TestTBF(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "plugins/meta/bandwidth")
|
||||
}
|
||||
@ -108,13 +106,13 @@ func makeTCPClientInNS(netns string, address string, port int, numBytes int) {
|
||||
}
|
||||
|
||||
func createVeth(hostNs ns.NetNS, hostVethIfName string, containerNs ns.NetNS, containerVethIfName string, hostIP []byte, containerIP []byte, hostIfaceMTU int) {
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = hostVethIfName
|
||||
linkAttrs.Flags = net.FlagUp
|
||||
linkAttrs.MTU = hostIfaceMTU
|
||||
vethDeviceRequest := &netlink.Veth{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: hostVethIfName,
|
||||
Flags: net.FlagUp,
|
||||
MTU: hostIfaceMTU,
|
||||
},
|
||||
PeerName: containerVethIfName,
|
||||
LinkAttrs: linkAttrs,
|
||||
PeerName: containerVethIfName,
|
||||
}
|
||||
|
||||
err := hostNs.Do(func(_ ns.NetNS) error {
|
||||
@ -195,12 +193,12 @@ func createVeth(hostNs ns.NetNS, hostVethIfName string, containerNs ns.NetNS, co
|
||||
}
|
||||
|
||||
func createVethInOneNs(netNS ns.NetNS, vethName, peerName string) {
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = vethName
|
||||
linkAttrs.Flags = net.FlagUp
|
||||
vethDeviceRequest := &netlink.Veth{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: vethName,
|
||||
Flags: net.FlagUp,
|
||||
},
|
||||
PeerName: peerName,
|
||||
LinkAttrs: linkAttrs,
|
||||
PeerName: peerName,
|
||||
}
|
||||
|
||||
err := netNS.Do(func(_ ns.NetNS) error {
|
||||
@ -224,13 +222,13 @@ func createMacvlan(netNS ns.NetNS, master, macvlanName string) {
|
||||
return fmt.Errorf("failed to lookup master %q: %v", master, err)
|
||||
}
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.MTU = m.Attrs().MTU
|
||||
linkAttrs.Name = macvlanName
|
||||
linkAttrs.ParentIndex = m.Attrs().Index
|
||||
macvlanDeviceRequest := &netlink.Macvlan{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
MTU: m.Attrs().MTU,
|
||||
Name: macvlanName,
|
||||
ParentIndex: m.Attrs().Index,
|
||||
},
|
||||
Mode: netlink.MACVLAN_MODE_BRIDGE,
|
||||
LinkAttrs: linkAttrs,
|
||||
Mode: netlink.MACVLAN_MODE_BRIDGE,
|
||||
}
|
||||
|
||||
if err = netlink.LinkAdd(macvlanDeviceRequest); err != nil {
|
||||
@ -245,47 +243,3 @@ func createMacvlan(netNS ns.NetNS, master, macvlanName string) {
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
func buildOneConfig(cniVersion string, orig *PluginConf, prevResult types.Result) ([]byte, error) {
|
||||
var err error
|
||||
|
||||
inject := map[string]interface{}{
|
||||
"name": "myBWnet",
|
||||
"cniVersion": cniVersion,
|
||||
}
|
||||
// Add previous plugin result
|
||||
if prevResult != nil {
|
||||
r, err := prevResult.GetAsVersion(cniVersion)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
inject["prevResult"] = r
|
||||
}
|
||||
|
||||
// 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 := &PluginConf{}
|
||||
if err := json.Unmarshal(newBytes, &conf); err != nil {
|
||||
return nil, fmt.Errorf("error parsing configuration: %s", err)
|
||||
}
|
||||
|
||||
return newBytes, nil
|
||||
}
|
||||
|
@ -15,8 +15,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"syscall"
|
||||
@ -26,24 +24,17 @@ import (
|
||||
"github.com/containernetworking/plugins/pkg/ip"
|
||||
)
|
||||
|
||||
const (
|
||||
latencyInMillis = 25
|
||||
UncappedRate uint64 = 100_000_000_000
|
||||
ShapedClassMinorID uint16 = 48
|
||||
UnShapedClassMinorID uint16 = 1
|
||||
)
|
||||
|
||||
func CreateIfb(ifbDeviceName string, mtu int, qlen int) error {
|
||||
if qlen < 1000 {
|
||||
qlen = 1000
|
||||
}
|
||||
const latencyInMillis = 25
|
||||
|
||||
func CreateIfb(ifbDeviceName string, mtu int) error {
|
||||
// do not set TxQLen > 0 nor TxQLen == -1 until issues have been fixed with numrxqueues / numtxqueues across interfaces
|
||||
// which needs to get set on IFB devices via upstream library: see hint https://github.com/containernetworking/plugins/pull/1097
|
||||
err := netlink.LinkAdd(&netlink.Ifb{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: ifbDeviceName,
|
||||
Flags: net.FlagUp,
|
||||
MTU: mtu,
|
||||
TxQLen: qlen,
|
||||
TxQLen: 0,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@ -61,24 +52,15 @@ func TeardownIfb(deviceName string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func CreateIngressQdisc(rateInBits, burstInBits uint64, excludeSubnets []string, includeSubnets []string, hostDeviceName string) error {
|
||||
func CreateIngressQdisc(rateInBits, burstInBits uint64, hostDeviceName string) error {
|
||||
hostDevice, err := netlink.LinkByName(hostDeviceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get host device: %s", err)
|
||||
}
|
||||
|
||||
subnets := includeSubnets
|
||||
exclude := false
|
||||
|
||||
if len(excludeSubnets) > 0 {
|
||||
subnets = excludeSubnets
|
||||
exclude = true
|
||||
}
|
||||
|
||||
return createHTB(rateInBits, burstInBits, hostDevice.Attrs().Index, subnets, exclude)
|
||||
return createTBF(rateInBits, burstInBits, hostDevice.Attrs().Index)
|
||||
}
|
||||
|
||||
func CreateEgressQdisc(rateInBits, burstInBits uint64, excludeSubnets []string, includeSubnets []string, hostDeviceName string, ifbDeviceName string) error {
|
||||
func CreateEgressQdisc(rateInBits, burstInBits uint64, hostDeviceName string, ifbDeviceName string) error {
|
||||
ifbDevice, err := netlink.LinkByName(ifbDeviceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get ifb device: %s", err)
|
||||
@ -125,216 +107,44 @@ func CreateEgressQdisc(rateInBits, burstInBits uint64, excludeSubnets []string,
|
||||
return fmt.Errorf("add filter: %s", err)
|
||||
}
|
||||
|
||||
subnets := excludeSubnets
|
||||
exclude := true
|
||||
|
||||
if len(includeSubnets) > 0 {
|
||||
subnets = includeSubnets
|
||||
exclude = false
|
||||
}
|
||||
|
||||
// throttle traffic on ifb device
|
||||
err = createHTB(rateInBits, burstInBits, ifbDevice.Attrs().Index, subnets, exclude)
|
||||
err = createTBF(rateInBits, burstInBits, ifbDevice.Attrs().Index)
|
||||
if err != nil {
|
||||
// egress from the container/netns pov = ingress from the main netns/host pov
|
||||
return fmt.Errorf("create htb container egress qos rules: %s", err)
|
||||
return fmt.Errorf("create ifb qdisc: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createHTB(rateInBits, burstInBits uint64, linkIndex int, subnets []string, excludeSubnets bool) error {
|
||||
// Netlink struct fields are not clear, let's use shell
|
||||
|
||||
defaultClassID := UnShapedClassMinorID
|
||||
// If no subnets are specified, then shaping should apply to everything
|
||||
if len(subnets) == 0 || excludeSubnets {
|
||||
defaultClassID = ShapedClassMinorID
|
||||
func createTBF(rateInBits, burstInBits uint64, linkIndex int) error {
|
||||
// Equivalent to
|
||||
// tc qdisc add dev link root tbf
|
||||
// rate netConf.BandwidthLimits.Rate
|
||||
// burst netConf.BandwidthLimits.Burst
|
||||
if rateInBits <= 0 {
|
||||
return fmt.Errorf("invalid rate: %d", rateInBits)
|
||||
}
|
||||
if burstInBits <= 0 {
|
||||
return fmt.Errorf("invalid burst: %d", burstInBits)
|
||||
}
|
||||
rateInBytes := rateInBits / 8
|
||||
burstInBytes := burstInBits / 8
|
||||
bufferInBytes := buffer(rateInBytes, uint32(burstInBytes))
|
||||
latency := latencyInUsec(latencyInMillis)
|
||||
limitInBytes := limit(rateInBytes, latency, uint32(burstInBytes))
|
||||
|
||||
// Step 1 qdisc
|
||||
// cmd := exec.Command("/usr/sbin/tc", "qdisc", "add", "dev", interfaceName, "root", "handle", "1:", "htb", "default", "30")
|
||||
qdisc := &netlink.Htb{
|
||||
qdisc := &netlink.Tbf{
|
||||
QdiscAttrs: netlink.QdiscAttrs{
|
||||
LinkIndex: linkIndex,
|
||||
Handle: netlink.MakeHandle(1, 0),
|
||||
Parent: netlink.HANDLE_ROOT,
|
||||
},
|
||||
Defcls: uint32(defaultClassID),
|
||||
// No idea what these are so let's keep the default values from source code...
|
||||
Version: 3,
|
||||
Rate2Quantum: 10,
|
||||
Limit: limitInBytes,
|
||||
Rate: rateInBytes,
|
||||
Buffer: bufferInBytes,
|
||||
}
|
||||
err := netlink.QdiscAdd(qdisc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while creating qdisc: %s", err)
|
||||
}
|
||||
|
||||
// Step 2 classes
|
||||
|
||||
rateInBytes := rateInBits / 8
|
||||
burstInBytes := burstInBits / 8
|
||||
bufferInBytes := buffer(rateInBytes, uint32(burstInBytes))
|
||||
|
||||
// The capped class for shaped traffic (included subnets or all but excluded subnets)
|
||||
// cmd = exec.Command("/usr/sbin/tc", "class", "add", "dev", interfaceName, "parent", "1:", "classid", "1:30", "htb", "rate",
|
||||
// fmt.Sprintf("%d", rateInBits), "burst", fmt.Sprintf("%d", burstInBits))
|
||||
shapedClass := &netlink.HtbClass{
|
||||
ClassAttrs: netlink.ClassAttrs{
|
||||
LinkIndex: linkIndex,
|
||||
Handle: netlink.MakeHandle(1, ShapedClassMinorID),
|
||||
Parent: netlink.MakeHandle(1, 0),
|
||||
},
|
||||
Rate: rateInBytes,
|
||||
Buffer: bufferInBytes,
|
||||
// Let's set up the "burst" rate to twice the specified rate
|
||||
Ceil: 2 * rateInBytes,
|
||||
Cbuffer: bufferInBytes,
|
||||
}
|
||||
|
||||
err = netlink.ClassAdd(shapedClass)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while creating htb default class: %s", err)
|
||||
}
|
||||
|
||||
// The uncapped class for non shaped traffic (either all but included subnets or excluded subnets only)
|
||||
// cmd = exec.Command("/usr/sbin/tc", "class", "add", "dev", interfaceName, "parent", "1:", "classid", "1:1", "htb",
|
||||
// "rate", "100000000000")
|
||||
bigRate := UncappedRate
|
||||
unshapedClass := &netlink.HtbClass{
|
||||
ClassAttrs: netlink.ClassAttrs{
|
||||
LinkIndex: linkIndex,
|
||||
Handle: netlink.MakeHandle(1, UnShapedClassMinorID),
|
||||
Parent: qdisc.Handle,
|
||||
},
|
||||
Rate: bigRate,
|
||||
Ceil: bigRate,
|
||||
// No need for any burst, the minimum buffer size in q_htb.c should be enough to handle the rate which
|
||||
// is already more than enough
|
||||
}
|
||||
err = netlink.ClassAdd(unshapedClass)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while creating htb uncapped class: %s", err)
|
||||
}
|
||||
|
||||
// Now add filters to redirect subnets to the class 1 if excluded instead of the default one (30)
|
||||
for _, subnet := range subnets {
|
||||
|
||||
// cmd = exec.Command("/usr/sbin/tc", "filter", "add", "dev", interfaceName, "parent", "1:", "protocol", protocol,
|
||||
// "prio", "16", "u32", "match", "ip", "dst", subnet, "flowid", "1:1")
|
||||
_, nw, err := net.ParseCIDR(subnet)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bad subnet %s: %s", subnet, err)
|
||||
}
|
||||
var maskBytes []byte = nw.Mask
|
||||
var subnetBytes []byte = nw.IP
|
||||
|
||||
if len(maskBytes) != len(subnetBytes) {
|
||||
return fmt.Errorf("error using net lib for subnet %s len(maskBytes) != len(subnetBytes) "+
|
||||
"(%d != %d) should not happen", subnet, len(maskBytes), len(subnetBytes))
|
||||
}
|
||||
|
||||
isIpv4 := nw.IP.To4() != nil
|
||||
protocol := syscall.ETH_P_IPV6
|
||||
var prio uint16 = 15
|
||||
var offset int32 = 24
|
||||
keepBytes := 16
|
||||
if isIpv4 {
|
||||
protocol = syscall.ETH_P_IP
|
||||
offset = 16
|
||||
keepBytes = 4
|
||||
// prio/pref needs to be changed if we change the protocol, looks like we cannot mix protocols with the same pref
|
||||
prio = 16
|
||||
}
|
||||
|
||||
if len(maskBytes) < keepBytes {
|
||||
return fmt.Errorf("error with net lib, unexpected count of bytes for ipv4 mask (%d < %d)",
|
||||
len(maskBytes), keepBytes)
|
||||
}
|
||||
if len(subnetBytes) < keepBytes {
|
||||
return fmt.Errorf("error with net lib, unexpected count of bytes for ipv4 subnet (%d < %d)",
|
||||
len(subnetBytes), keepBytes)
|
||||
}
|
||||
maskBytes = maskBytes[len(maskBytes)-keepBytes:]
|
||||
subnetBytes = subnetBytes[len(subnetBytes)-keepBytes:]
|
||||
|
||||
// For ipv4 we should have at most 1 key, for ipv6 at most 4
|
||||
keys := make([]netlink.TcU32Key, 0, 4)
|
||||
|
||||
for i := 0; i < len(maskBytes); i += 4 {
|
||||
var mask, subnetI uint32
|
||||
buf := bytes.NewReader(maskBytes[i : i+4])
|
||||
err = binary.Read(buf, binary.BigEndian, &mask)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error, htb filter, unable to build mask match filter, iter %d for subnet %s",
|
||||
i, subnet)
|
||||
}
|
||||
|
||||
if mask != 0 {
|
||||
// If mask == 0, any value on this section will be a match and we do not need a filter for this
|
||||
buf = bytes.NewReader(subnetBytes[i : i+4])
|
||||
err = binary.Read(buf, binary.BigEndian, &subnetI)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error, htb filter, unable to build subnet match filter, iter %d for subnet %s",
|
||||
i, subnet)
|
||||
}
|
||||
keys = append(keys, netlink.TcU32Key{
|
||||
Mask: mask,
|
||||
Val: subnetI,
|
||||
Off: offset,
|
||||
OffMask: 0,
|
||||
})
|
||||
}
|
||||
|
||||
offset += 4
|
||||
}
|
||||
|
||||
if len(keys) != cap(keys) {
|
||||
shrinkedKeys := make([]netlink.TcU32Key, len(keys))
|
||||
copied := copy(shrinkedKeys, keys)
|
||||
if copied != len(keys) {
|
||||
return fmt.Errorf("copy tc u32 keys error, for subnet %s copied %d != keys %d", subnet, copied, len(keys))
|
||||
}
|
||||
keys = shrinkedKeys
|
||||
}
|
||||
|
||||
if isIpv4 && len(keys) > 1 {
|
||||
return fmt.Errorf("error, htb ipv4 filter, unexpected rule length (%d > 1), for subnet %s",
|
||||
len(keys), subnet)
|
||||
} else if len(keys) > 4 {
|
||||
return fmt.Errorf("error, htb ipv6 filter, unexpected rule length (%d > 4), for subnet %s",
|
||||
len(keys), subnet)
|
||||
}
|
||||
|
||||
// If len(keys) == 0, it means that we want to wildcard all traffic on the non default/uncapped class
|
||||
var selector *netlink.TcU32Sel
|
||||
if len(keys) > 0 {
|
||||
selector = &netlink.TcU32Sel{
|
||||
Nkeys: uint8(len(keys)),
|
||||
Flags: netlink.TC_U32_TERMINAL,
|
||||
Keys: keys,
|
||||
}
|
||||
}
|
||||
|
||||
classID := shapedClass.Handle
|
||||
if excludeSubnets {
|
||||
classID = unshapedClass.Handle
|
||||
}
|
||||
|
||||
tcFilter := netlink.U32{
|
||||
FilterAttrs: netlink.FilterAttrs{
|
||||
LinkIndex: linkIndex,
|
||||
Parent: qdisc.Handle,
|
||||
Priority: prio,
|
||||
Protocol: uint16(protocol),
|
||||
},
|
||||
ClassId: classID,
|
||||
Sel: selector,
|
||||
}
|
||||
|
||||
err = netlink.FilterAdd(&tcFilter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error, unable to create htb filter, details %s", err)
|
||||
}
|
||||
return fmt.Errorf("create qdisc: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -346,3 +156,11 @@ func time2Tick(time uint32) uint32 {
|
||||
func buffer(rate uint64, burst uint32) uint32 {
|
||||
return time2Tick(uint32(float64(burst) * float64(netlink.TIME_UNITS_PER_SEC) / float64(rate)))
|
||||
}
|
||||
|
||||
func limit(rate uint64, latency float64, buffer uint32) uint32 {
|
||||
return uint32(float64(rate)*latency/float64(netlink.TIME_UNITS_PER_SEC)) + buffer
|
||||
}
|
||||
|
||||
func latencyInUsec(latencyInMillis float64) float64 {
|
||||
return float64(netlink.TIME_UNITS_PER_SEC) * (latencyInMillis / 1000.0)
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
@ -40,12 +39,11 @@ const (
|
||||
// BandwidthEntry corresponds to a single entry in the bandwidth argument,
|
||||
// see CONVENTIONS.md
|
||||
type BandwidthEntry struct {
|
||||
UnshapedSubnets []string `json:"unshapedSubnets"` // Ipv4/ipv6 subnets to be excluded from traffic shaping. UnshapedSubnets and ShapedSubnets parameters are mutually exlusive
|
||||
ShapedSubnets []string `json:"shapedSubnets"` // Ipv4/ipv6 subnets to be included in traffic shaping. UnshapedSubnets and ShapedSubnets parameters are mutually exlusive
|
||||
IngressRate uint64 `json:"ingressRate"` // Bandwidth rate in bps for traffic through container. 0 for no limit. If ingressRate is set, ingressBurst must also be set
|
||||
IngressBurst uint64 `json:"ingressBurst"` // Bandwidth burst in bits for traffic through container. 0 for no limit. If ingressBurst is set, ingressRate must also be set
|
||||
EgressRate uint64 `json:"egressRate"` // Bandwidth rate in bps for traffic through container. 0 for no limit. If egressRate is set, egressBurst must also be set
|
||||
EgressBurst uint64 `json:"egressBurst"` // Bandwidth burst in bits for traffic through container. 0 for no limit. If egressBurst is set, egressRate must also be set
|
||||
IngressRate uint64 `json:"ingressRate"` // Bandwidth rate in bps for traffic through container. 0 for no limit. If ingressRate is set, ingressBurst must also be set
|
||||
IngressBurst uint64 `json:"ingressBurst"` // Bandwidth burst in bits for traffic through container. 0 for no limit. If ingressBurst is set, ingressRate must also be set
|
||||
|
||||
EgressRate uint64 `json:"egressRate"` // Bandwidth rate in bps for traffic through container. 0 for no limit. If egressRate is set, egressBurst must also be set
|
||||
EgressBurst uint64 `json:"egressBurst"` // Bandwidth burst in bits for traffic through container. 0 for no limit. If egressBurst is set, egressRate must also be set
|
||||
}
|
||||
|
||||
func (bw *BandwidthEntry) isZero() bool {
|
||||
@ -98,21 +96,10 @@ func parseConfig(stdin []byte) (*PluginConf, error) {
|
||||
}
|
||||
|
||||
func getBandwidth(conf *PluginConf) *BandwidthEntry {
|
||||
bw := conf.BandwidthEntry
|
||||
if bw == nil && conf.RuntimeConfig.Bandwidth != nil {
|
||||
bw = conf.RuntimeConfig.Bandwidth
|
||||
if conf.BandwidthEntry == nil && conf.RuntimeConfig.Bandwidth != nil {
|
||||
return conf.RuntimeConfig.Bandwidth
|
||||
}
|
||||
|
||||
if bw != nil {
|
||||
if bw.UnshapedSubnets == nil {
|
||||
bw.UnshapedSubnets = make([]string, 0)
|
||||
}
|
||||
if bw.ShapedSubnets == nil {
|
||||
bw.ShapedSubnets = make([]string, 0)
|
||||
}
|
||||
}
|
||||
|
||||
return bw
|
||||
return conf.BandwidthEntry
|
||||
}
|
||||
|
||||
func validateRateAndBurst(rate, burst uint64) error {
|
||||
@ -132,13 +119,13 @@ func getIfbDeviceName(networkName string, containerID string) string {
|
||||
return utils.MustFormatHashWithPrefix(maxIfbDeviceLength, ifbDevicePrefix, networkName+containerID)
|
||||
}
|
||||
|
||||
func getMTUAndQLen(deviceName string) (int, int, error) {
|
||||
func getMTU(deviceName string) (int, error) {
|
||||
link, err := netlink.LinkByName(deviceName)
|
||||
if err != nil {
|
||||
return -1, -1, err
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return link.Attrs().MTU, link.Attrs().TxQLen, nil
|
||||
return link.Attrs().MTU, nil
|
||||
}
|
||||
|
||||
// get the veth peer of container interface in host namespace
|
||||
@ -172,28 +159,6 @@ func getHostInterface(interfaces []*current.Interface, containerIfName string, n
|
||||
return nil, fmt.Errorf("no veth peer of container interface found in host ns")
|
||||
}
|
||||
|
||||
func validateSubnets(unshapedSubnets []string, shapedSubnets []string) error {
|
||||
if len(unshapedSubnets) > 0 && len(shapedSubnets) > 0 {
|
||||
return fmt.Errorf("unshapedSubnets and shapedSubnets cannot be both specified, one of them should be discarded")
|
||||
}
|
||||
|
||||
for _, subnet := range unshapedSubnets {
|
||||
_, _, err := net.ParseCIDR(subnet)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bad subnet %q provided, details %s", subnet, err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, subnet := range shapedSubnets {
|
||||
_, _, err := net.ParseCIDR(subnet)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bad subnet %q provided, details %s", subnet, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cmdAdd(args *skel.CmdArgs) error {
|
||||
conf, err := parseConfig(args.StdinData)
|
||||
if err != nil {
|
||||
@ -205,10 +170,6 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
return types.PrintResult(conf.PrevResult, conf.CNIVersion)
|
||||
}
|
||||
|
||||
if err = validateSubnets(bandwidth.UnshapedSubnets, bandwidth.ShapedSubnets); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if conf.PrevResult == nil {
|
||||
return fmt.Errorf("must be called as chained plugin")
|
||||
}
|
||||
@ -230,22 +191,21 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
if bandwidth.IngressRate > 0 && bandwidth.IngressBurst > 0 {
|
||||
err = CreateIngressQdisc(bandwidth.IngressRate, bandwidth.IngressBurst,
|
||||
bandwidth.UnshapedSubnets, bandwidth.ShapedSubnets, hostInterface.Name)
|
||||
err = CreateIngressQdisc(bandwidth.IngressRate, bandwidth.IngressBurst, hostInterface.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if bandwidth.EgressRate > 0 && bandwidth.EgressBurst > 0 {
|
||||
mtu, qlen, err := getMTUAndQLen(hostInterface.Name)
|
||||
mtu, err := getMTU(hostInterface.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ifbDeviceName := getIfbDeviceName(conf.Name, args.ContainerID)
|
||||
|
||||
err = CreateIfb(ifbDeviceName, mtu, qlen)
|
||||
err = CreateIfb(ifbDeviceName, mtu)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -259,9 +219,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
Name: ifbDeviceName,
|
||||
Mac: ifbDevice.Attrs().HardwareAddr.String(),
|
||||
})
|
||||
err = CreateEgressQdisc(bandwidth.EgressRate, bandwidth.EgressBurst,
|
||||
bandwidth.UnshapedSubnets, bandwidth.ShapedSubnets, hostInterface.Name,
|
||||
ifbDeviceName)
|
||||
err = CreateEgressQdisc(bandwidth.EgressRate, bandwidth.EgressBurst, hostInterface.Name, ifbDeviceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -282,7 +240,13 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.VersionsStartingFrom("0.3.0"), bv.BuildString("bandwidth"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
/* FIXME GC */
|
||||
/* FIXME Status */
|
||||
}, version.VersionsStartingFrom("0.3.0"), bv.BuildString("bandwidth"))
|
||||
}
|
||||
|
||||
func SafeQdiscList(link netlink.Link) ([]netlink.Qdisc, error) {
|
||||
@ -334,99 +298,75 @@ func cmdCheck(args *skel.CmdArgs) error {
|
||||
|
||||
bandwidth := getBandwidth(bwConf)
|
||||
|
||||
if err = validateSubnets(bandwidth.UnshapedSubnets, bandwidth.ShapedSubnets); err != nil {
|
||||
return fmt.Errorf("failed to check subnets, details %s", err)
|
||||
}
|
||||
|
||||
if bandwidth.IngressRate > 0 && bandwidth.IngressBurst > 0 {
|
||||
rateInBytes := bandwidth.IngressRate / 8
|
||||
burstInBytes := bandwidth.IngressBurst / 8
|
||||
bufferInBytes := buffer(rateInBytes, uint32(burstInBytes))
|
||||
err = checkHTB(link, rateInBytes, bufferInBytes, bandwidth.ShapedSubnets)
|
||||
latency := latencyInUsec(latencyInMillis)
|
||||
limitInBytes := limit(rateInBytes, latency, uint32(burstInBytes))
|
||||
|
||||
qdiscs, err := SafeQdiscList(link)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(qdiscs) == 0 {
|
||||
return fmt.Errorf("Failed to find qdisc")
|
||||
}
|
||||
|
||||
for _, qdisc := range qdiscs {
|
||||
tbf, isTbf := qdisc.(*netlink.Tbf)
|
||||
if !isTbf {
|
||||
break
|
||||
}
|
||||
if tbf.Rate != rateInBytes {
|
||||
return fmt.Errorf("Rate doesn't match")
|
||||
}
|
||||
if tbf.Limit != limitInBytes {
|
||||
return fmt.Errorf("Limit doesn't match")
|
||||
}
|
||||
if tbf.Buffer != bufferInBytes {
|
||||
return fmt.Errorf("Buffer doesn't match")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if bandwidth.EgressRate > 0 && bandwidth.EgressBurst > 0 {
|
||||
rateInBytes := bandwidth.EgressRate / 8
|
||||
burstInBytes := bandwidth.EgressBurst / 8
|
||||
bufferInBytes := buffer(rateInBytes, uint32(burstInBytes))
|
||||
latency := latencyInUsec(latencyInMillis)
|
||||
limitInBytes := limit(rateInBytes, latency, uint32(burstInBytes))
|
||||
|
||||
ifbDeviceName := getIfbDeviceName(bwConf.Name, args.ContainerID)
|
||||
|
||||
ifbDevice, err := netlink.LinkByName(ifbDeviceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get ifb device: %s", err)
|
||||
}
|
||||
err = checkHTB(ifbDevice, rateInBytes, bufferInBytes, bandwidth.ShapedSubnets)
|
||||
|
||||
qdiscs, err := SafeQdiscList(ifbDevice)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkHTB(link netlink.Link, rateInBytes uint64, bufferInBytes uint32, shapedSubnets []string) error {
|
||||
qdiscs, err := SafeQdiscList(link)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(qdiscs) == 0 {
|
||||
return fmt.Errorf("Failed to find qdisc")
|
||||
}
|
||||
foundHTB := false
|
||||
for _, qdisc := range qdiscs {
|
||||
htb, isHtb := qdisc.(*netlink.Htb)
|
||||
if !isHtb {
|
||||
continue
|
||||
if len(qdiscs) == 0 {
|
||||
return fmt.Errorf("Failed to find qdisc")
|
||||
}
|
||||
|
||||
if foundHTB {
|
||||
return fmt.Errorf("Several htb qdisc found for device %s", link.Attrs().Name)
|
||||
}
|
||||
|
||||
foundHTB = true
|
||||
defaultClassMinorID := ShapedClassMinorID
|
||||
if len(shapedSubnets) > 0 {
|
||||
defaultClassMinorID = UnShapedClassMinorID
|
||||
}
|
||||
|
||||
if htb.Defcls != uint32(defaultClassMinorID) {
|
||||
return fmt.Errorf("Default class does not match")
|
||||
}
|
||||
|
||||
classes, err := netlink.ClassList(link, htb.Handle)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to list classes bound to htb qdisc for device %s. Details %s",
|
||||
link.Attrs().Name, err)
|
||||
}
|
||||
if len(classes) != 2 {
|
||||
return fmt.Errorf("Number of htb classes does not match for device %s (%d != 2)",
|
||||
link.Attrs().Name, len(classes))
|
||||
}
|
||||
|
||||
for _, c := range classes {
|
||||
htbClass, isHtb := c.(*netlink.HtbClass)
|
||||
if !isHtb {
|
||||
return fmt.Errorf("Unexpected class for parent htb qdisc bound to device %s", link.Attrs().Name)
|
||||
for _, qdisc := range qdiscs {
|
||||
tbf, isTbf := qdisc.(*netlink.Tbf)
|
||||
if !isTbf {
|
||||
break
|
||||
}
|
||||
if htbClass.Handle == htb.Defcls {
|
||||
if htbClass.Rate != rateInBytes {
|
||||
return fmt.Errorf("Rate does not match for the default class for device %s (%d != %d)",
|
||||
link.Attrs().Name, htbClass.Rate, rateInBytes)
|
||||
}
|
||||
|
||||
if htbClass.Buffer != bufferInBytes {
|
||||
return fmt.Errorf("Burst buffer size does not match for the default class for device %s (%d != %d)",
|
||||
link.Attrs().Name, htbClass.Buffer, bufferInBytes)
|
||||
}
|
||||
} else if htbClass.Handle == netlink.MakeHandle(1, 1) {
|
||||
if htbClass.Rate != UncappedRate {
|
||||
return fmt.Errorf("Rate does not match for the uncapped class for device %s (%d != %d)",
|
||||
link.Attrs().Name, htbClass.Rate, UncappedRate)
|
||||
}
|
||||
if tbf.Rate != rateInBytes {
|
||||
return fmt.Errorf("Rate doesn't match")
|
||||
}
|
||||
if tbf.Limit != limitInBytes {
|
||||
return fmt.Errorf("Limit doesn't match")
|
||||
}
|
||||
if tbf.Buffer != bufferInBytes {
|
||||
return fmt.Errorf("Buffer doesn't match")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: check subnet filters
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -179,7 +179,13 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.VersionsStartingFrom("0.4.0"), bv.BuildString("firewall"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
/* FIXME GC */
|
||||
/* FIXME Status */
|
||||
}, version.VersionsStartingFrom("0.4.0"), bv.BuildString("firewall"))
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
|
@ -205,10 +205,10 @@ var _ = Describe("firewall plugin iptables backend", func() {
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = IFNAME
|
||||
err = netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: IFNAME,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
_, err = netlink.LinkByName(IFNAME)
|
||||
|
@ -37,9 +37,22 @@ import (
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
current "github.com/containernetworking/cni/pkg/types/100"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
"github.com/containernetworking/plugins/pkg/utils"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
type PortMapper interface {
|
||||
forwardPorts(config *PortMapConf, containerNet net.IPNet) error
|
||||
checkPorts(config *PortMapConf, containerNet net.IPNet) error
|
||||
unforwardPorts(config *PortMapConf) error
|
||||
}
|
||||
|
||||
// These are vars rather than consts so we can "&" them
|
||||
var (
|
||||
iptablesBackend = "iptables"
|
||||
nftablesBackend = "nftables"
|
||||
)
|
||||
|
||||
// PortMapEntry corresponds to a single entry in the port_mappings argument,
|
||||
// see CONVENTIONS.md
|
||||
type PortMapEntry struct {
|
||||
@ -51,16 +64,23 @@ type PortMapEntry struct {
|
||||
|
||||
type PortMapConf struct {
|
||||
types.NetConf
|
||||
SNAT *bool `json:"snat,omitempty"`
|
||||
ConditionsV4 *[]string `json:"conditionsV4"`
|
||||
ConditionsV6 *[]string `json:"conditionsV6"`
|
||||
MasqAll bool `json:"masqAll,omitempty"`
|
||||
MarkMasqBit *int `json:"markMasqBit"`
|
||||
ExternalSetMarkChain *string `json:"externalSetMarkChain"`
|
||||
RuntimeConfig struct {
|
||||
|
||||
mapper PortMapper
|
||||
|
||||
// Generic config
|
||||
Backend *string `json:"backend,omitempty"`
|
||||
SNAT *bool `json:"snat,omitempty"`
|
||||
ConditionsV4 *[]string `json:"conditionsV4"`
|
||||
ConditionsV6 *[]string `json:"conditionsV6"`
|
||||
MasqAll bool `json:"masqAll,omitempty"`
|
||||
MarkMasqBit *int `json:"markMasqBit"`
|
||||
RuntimeConfig struct {
|
||||
PortMaps []PortMapEntry `json:"portMappings,omitempty"`
|
||||
} `json:"runtimeConfig,omitempty"`
|
||||
|
||||
// iptables-backend-specific config
|
||||
ExternalSetMarkChain *string `json:"externalSetMarkChain"`
|
||||
|
||||
// These are fields parsed out of the config or the environment;
|
||||
// included here for convenience
|
||||
ContainerID string `json:"-"`
|
||||
@ -89,7 +109,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
netConf.ContainerID = args.ContainerID
|
||||
|
||||
if netConf.ContIPv4.IP != nil {
|
||||
if err := forwardPorts(netConf, netConf.ContIPv4); err != nil {
|
||||
if err := netConf.mapper.forwardPorts(netConf, netConf.ContIPv4); err != nil {
|
||||
return err
|
||||
}
|
||||
// Delete conntrack entries for UDP to avoid conntrack blackholing traffic
|
||||
@ -98,10 +118,21 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
if err := deletePortmapStaleConnections(netConf.RuntimeConfig.PortMaps, unix.AF_INET); err != nil {
|
||||
log.Printf("failed to delete stale UDP conntrack entries for %s: %v", netConf.ContIPv4.IP, err)
|
||||
}
|
||||
|
||||
if *netConf.SNAT {
|
||||
// Set the route_localnet bit on the host interface, so that
|
||||
// 127/8 can cross a routing boundary.
|
||||
hostIfName := getRoutableHostIF(netConf.ContIPv4.IP)
|
||||
if hostIfName != "" {
|
||||
if err := enableLocalnetRouting(hostIfName); err != nil {
|
||||
return fmt.Errorf("unable to enable route_localnet: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if netConf.ContIPv6.IP != nil {
|
||||
if err := forwardPorts(netConf, netConf.ContIPv6); err != nil {
|
||||
if err := netConf.mapper.forwardPorts(netConf, netConf.ContIPv6); err != nil {
|
||||
return err
|
||||
}
|
||||
// Delete conntrack entries for UDP to avoid conntrack blackholing traffic
|
||||
@ -130,11 +161,17 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
|
||||
// We don't need to parse out whether or not we're using v6 or snat,
|
||||
// deletion is idempotent
|
||||
return unforwardPorts(netConf)
|
||||
return netConf.mapper.unforwardPorts(netConf)
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("portmap"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
/* FIXME GC */
|
||||
/* FIXME Status */
|
||||
}, version.All, bv.BuildString("portmap"))
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
@ -155,13 +192,13 @@ func cmdCheck(args *skel.CmdArgs) error {
|
||||
conf.ContainerID = args.ContainerID
|
||||
|
||||
if conf.ContIPv4.IP != nil {
|
||||
if err := checkPorts(conf, conf.ContIPv4); err != nil {
|
||||
if err := conf.mapper.checkPorts(conf, conf.ContIPv4); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if conf.ContIPv6.IP != nil {
|
||||
if err := checkPorts(conf, conf.ContIPv6); err != nil {
|
||||
if err := conf.mapper.checkPorts(conf, conf.ContIPv6); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -191,6 +228,8 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, *current.Result, er
|
||||
}
|
||||
}
|
||||
|
||||
conf.mapper = &portMapperIPTables{}
|
||||
|
||||
if conf.SNAT == nil {
|
||||
tvar := true
|
||||
conf.SNAT = &tvar
|
||||
@ -209,6 +248,21 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, *current.Result, er
|
||||
return nil, nil, fmt.Errorf("MasqMarkBit must be between 0 and 31")
|
||||
}
|
||||
|
||||
err := ensureBackend(&conf)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
switch *conf.Backend {
|
||||
case iptablesBackend:
|
||||
conf.mapper = &portMapperIPTables{}
|
||||
|
||||
case nftablesBackend:
|
||||
conf.mapper = &portMapperNFTables{}
|
||||
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unrecognized backend %q", *conf.Backend)
|
||||
}
|
||||
|
||||
// Reject invalid port numbers
|
||||
for _, pm := range conf.RuntimeConfig.PortMaps {
|
||||
if pm.ContainerPort <= 0 {
|
||||
@ -248,3 +302,59 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, *current.Result, er
|
||||
|
||||
return &conf, result, nil
|
||||
}
|
||||
|
||||
// ensureBackend validates and/or sets conf.Backend
|
||||
func ensureBackend(conf *PortMapConf) error {
|
||||
backendConfig := make(map[string][]string)
|
||||
|
||||
if conf.ExternalSetMarkChain != nil {
|
||||
backendConfig[iptablesBackend] = append(backendConfig[iptablesBackend], "externalSetMarkChain")
|
||||
}
|
||||
if conditionsBackend := detectBackendOfConditions(conf.ConditionsV4); conditionsBackend != "" {
|
||||
backendConfig[conditionsBackend] = append(backendConfig[conditionsBackend], "conditionsV4")
|
||||
}
|
||||
if conditionsBackend := detectBackendOfConditions(conf.ConditionsV6); conditionsBackend != "" {
|
||||
backendConfig[conditionsBackend] = append(backendConfig[conditionsBackend], "conditionsV6")
|
||||
}
|
||||
|
||||
// If backend wasn't requested explicitly, default to iptables, unless it is not
|
||||
// available (and nftables is). FIXME: flip this default at some point.
|
||||
if conf.Backend == nil {
|
||||
if !utils.SupportsIPTables() && utils.SupportsNFTables() {
|
||||
conf.Backend = &nftablesBackend
|
||||
} else {
|
||||
conf.Backend = &iptablesBackend
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we dont have config for the wrong backend
|
||||
var wrongBackend string
|
||||
if *conf.Backend == iptablesBackend {
|
||||
wrongBackend = nftablesBackend
|
||||
} else {
|
||||
wrongBackend = iptablesBackend
|
||||
}
|
||||
if len(backendConfig[wrongBackend]) > 0 {
|
||||
return fmt.Errorf("%s backend was requested but configuration contains %s-specific options %v", *conf.Backend, wrongBackend, backendConfig[wrongBackend])
|
||||
}
|
||||
|
||||
// OK
|
||||
return nil
|
||||
}
|
||||
|
||||
// detectBackendOfConditions returns "iptables" if conditions contains iptables
|
||||
// conditions, "nftables" if it contains nftables conditions, and "" if it is empty.
|
||||
func detectBackendOfConditions(conditions *[]string) string {
|
||||
if conditions == nil || len(*conditions) == 0 || (*conditions)[0] == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
// The first character of any iptables condition would either be an hyphen
|
||||
// (e.g. "-d", "--sport", "-m") or an exclamation mark.
|
||||
// No nftables condition would start that way. (An nftables condition might
|
||||
// include a negative number, but not as the first token.)
|
||||
if (*conditions)[0][0] == '-' || (*conditions)[0][0] == '!' {
|
||||
return iptablesBackend
|
||||
}
|
||||
return nftablesBackend
|
||||
}
|
||||
|
@ -25,7 +25,6 @@ import (
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/utils"
|
||||
"github.com/containernetworking/plugins/pkg/utils/sysctl"
|
||||
)
|
||||
|
||||
// This creates the chains to be added to iptables. The basic structure is
|
||||
@ -52,9 +51,11 @@ const (
|
||||
OldTopLevelSNATChainName = "CNI-HOSTPORT-SNAT"
|
||||
)
|
||||
|
||||
type portMapperIPTables struct{}
|
||||
|
||||
// forwardPorts establishes port forwarding to a given container IP.
|
||||
// containerNet.IP can be either v4 or v6.
|
||||
func forwardPorts(config *PortMapConf, containerNet net.IPNet) error {
|
||||
func (*portMapperIPTables) forwardPorts(config *PortMapConf, containerNet net.IPNet) error {
|
||||
isV6 := (containerNet.IP.To4() == nil)
|
||||
|
||||
var ipt *iptables.IPTables
|
||||
@ -87,17 +88,6 @@ func forwardPorts(config *PortMapConf, containerNet net.IPNet) error {
|
||||
return fmt.Errorf("unable to create chain %s: %v", setMarkChain.name, err)
|
||||
}
|
||||
}
|
||||
|
||||
if !isV6 {
|
||||
// Set the route_localnet bit on the host interface, so that
|
||||
// 127/8 can cross a routing boundary.
|
||||
hostIfName := getRoutableHostIF(containerNet.IP)
|
||||
if hostIfName != "" {
|
||||
if err := enableLocalnetRouting(hostIfName); err != nil {
|
||||
return fmt.Errorf("unable to enable route_localnet: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate the DNAT (actual port forwarding) rules
|
||||
@ -117,7 +107,7 @@ func forwardPorts(config *PortMapConf, containerNet net.IPNet) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkPorts(config *PortMapConf, containerNet net.IPNet) error {
|
||||
func (*portMapperIPTables) checkPorts(config *PortMapConf, containerNet net.IPNet) error {
|
||||
isV6 := (containerNet.IP.To4() == nil)
|
||||
dnatChain := genDnatChain(config.Name, config.ContainerID)
|
||||
fillDnatRules(&dnatChain, config, containerNet)
|
||||
@ -344,14 +334,6 @@ func genMarkMasqChain(markBit int) chain {
|
||||
return ch
|
||||
}
|
||||
|
||||
// enableLocalnetRouting tells the kernel not to treat 127/8 as a martian,
|
||||
// so that connections with a source ip of 127/8 can cross a routing boundary.
|
||||
func enableLocalnetRouting(ifName string) error {
|
||||
routeLocalnetPath := "net/ipv4/conf/" + ifName + "/route_localnet"
|
||||
_, err := sysctl.Sysctl(routeLocalnetPath, "1")
|
||||
return err
|
||||
}
|
||||
|
||||
// genOldSnatChain is no longer used, but used to be created. We'll try and
|
||||
// tear it down in case the plugin version changed between ADD and DEL
|
||||
func genOldSnatChain(netName, containerID string) chain {
|
||||
@ -372,7 +354,7 @@ func genOldSnatChain(netName, containerID string) chain {
|
||||
// don't know which protocols were used.
|
||||
// So, we first check that iptables is "generally OK" by doing a check. If
|
||||
// not, we ignore the error, unless neither v4 nor v6 are OK.
|
||||
func unforwardPorts(config *PortMapConf) error {
|
||||
func (*portMapperIPTables) unforwardPorts(config *PortMapConf) error {
|
||||
dnatChain := genDnatChain(config.Name, config.ContainerID)
|
||||
|
||||
// Might be lying around from old versions
|
252
plugins/meta/portmap/portmap_iptables_test.go
Normal file
252
plugins/meta/portmap/portmap_iptables_test.go
Normal file
@ -0,0 +1,252 @@
|
||||
// Copyright 2017 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
)
|
||||
|
||||
var _ = Describe("portmapping configuration (iptables)", func() {
|
||||
netName := "testNetName"
|
||||
containerID := "icee6giejonei6sohng6ahngee7laquohquee9shiGo7fohferakah3Feiyoolu2pei7ciPhoh7shaoX6vai3vuf0ahfaeng8yohb9ceu0daez5hashee8ooYai5wa3y"
|
||||
|
||||
for _, ver := range []string{"0.3.0", "0.3.1", "0.4.0", "1.0.0"} {
|
||||
// Redefine ver inside for scope so real value is picked up by each dynamically defined It()
|
||||
// See Gingkgo's "Patterns for dynamically generating tests" documentation.
|
||||
ver := ver
|
||||
|
||||
Describe("Generating iptables chains", func() {
|
||||
Context("for DNAT", func() {
|
||||
It(fmt.Sprintf("[%s] generates a correct standard container chain", ver), func() {
|
||||
ch := genDnatChain(netName, containerID)
|
||||
|
||||
Expect(ch).To(Equal(chain{
|
||||
table: "nat",
|
||||
name: "CNI-DN-bfd599665540dd91d5d28",
|
||||
entryChains: []string{TopLevelDNATChainName},
|
||||
}))
|
||||
configBytes := []byte(fmt.Sprintf(`{
|
||||
"name": "test",
|
||||
"type": "portmap",
|
||||
"cniVersion": "%s",
|
||||
"runtimeConfig": {
|
||||
"portMappings": [
|
||||
{ "hostPort": 8080, "containerPort": 80, "protocol": "tcp"},
|
||||
{ "hostPort": 8081, "containerPort": 80, "protocol": "tcp"},
|
||||
{ "hostPort": 8080, "containerPort": 81, "protocol": "udp"},
|
||||
{ "hostPort": 8082, "containerPort": 82, "protocol": "udp"},
|
||||
{ "hostPort": 8083, "containerPort": 83, "protocol": "tcp", "hostIP": "192.168.0.2"},
|
||||
{ "hostPort": 8084, "containerPort": 84, "protocol": "tcp", "hostIP": "0.0.0.0"},
|
||||
{ "hostPort": 8085, "containerPort": 85, "protocol": "tcp", "hostIP": "2001:db8:a::1"},
|
||||
{ "hostPort": 8086, "containerPort": 86, "protocol": "tcp", "hostIP": "::"}
|
||||
]
|
||||
},
|
||||
"snat": true,
|
||||
"conditionsV4": ["-a", "b"],
|
||||
"conditionsV6": ["-c", "d"]
|
||||
}`, ver))
|
||||
|
||||
conf, _, err := parseConfig(configBytes, "foo")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
conf.ContainerID = containerID
|
||||
|
||||
ch = genDnatChain(conf.Name, containerID)
|
||||
Expect(ch).To(Equal(chain{
|
||||
table: "nat",
|
||||
name: "CNI-DN-67e92b96e692a494b6b85",
|
||||
entryChains: []string{"CNI-HOSTPORT-DNAT"},
|
||||
}))
|
||||
|
||||
n, err := types.ParseCIDR("10.0.0.2/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
fillDnatRules(&ch, conf, *n)
|
||||
|
||||
Expect(ch.entryRules).To(Equal([][]string{
|
||||
{
|
||||
"-m", "comment", "--comment",
|
||||
fmt.Sprintf("dnat name: \"test\" id: \"%s\"", containerID),
|
||||
"-m", "multiport",
|
||||
"-p", "tcp",
|
||||
"--destination-ports", "8080,8081,8083,8084,8085,8086",
|
||||
"-a", "b",
|
||||
},
|
||||
{
|
||||
"-m", "comment", "--comment",
|
||||
fmt.Sprintf("dnat name: \"test\" id: \"%s\"", containerID),
|
||||
"-m", "multiport",
|
||||
"-p", "udp",
|
||||
"--destination-ports", "8080,8082",
|
||||
"-a", "b",
|
||||
},
|
||||
}))
|
||||
|
||||
Expect(ch.rules).To(Equal([][]string{
|
||||
// tcp rules and not hostIP
|
||||
{"-p", "tcp", "--dport", "8080", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8080", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||
{"-p", "tcp", "--dport", "8081", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8081", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||
// udp rules and not hostIP
|
||||
{"-p", "udp", "--dport", "8080", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8080", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:81"},
|
||||
{"-p", "udp", "--dport", "8082", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8082", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8082", "-j", "DNAT", "--to-destination", "10.0.0.2:82"},
|
||||
// tcp rules and hostIP
|
||||
{"-p", "tcp", "--dport", "8083", "-d", "192.168.0.2", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8083", "-d", "192.168.0.2", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8083", "-d", "192.168.0.2", "-j", "DNAT", "--to-destination", "10.0.0.2:83"},
|
||||
// tcp rules and hostIP = "0.0.0.0"
|
||||
{"-p", "tcp", "--dport", "8084", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8084", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8084", "-j", "DNAT", "--to-destination", "10.0.0.2:84"},
|
||||
}))
|
||||
|
||||
ch.rules = nil
|
||||
ch.entryRules = nil
|
||||
|
||||
n, err = types.ParseCIDR("2001:db8::2/64")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
fillDnatRules(&ch, conf, *n)
|
||||
|
||||
Expect(ch.rules).To(Equal([][]string{
|
||||
// tcp rules and not hostIP
|
||||
{"-p", "tcp", "--dport", "8080", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "[2001:db8::2]:80"},
|
||||
{"-p", "tcp", "--dport", "8081", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "[2001:db8::2]:80"},
|
||||
// udp rules and not hostIP
|
||||
{"-p", "udp", "--dport", "8080", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8080", "-j", "DNAT", "--to-destination", "[2001:db8::2]:81"},
|
||||
{"-p", "udp", "--dport", "8082", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8082", "-j", "DNAT", "--to-destination", "[2001:db8::2]:82"},
|
||||
// tcp rules and hostIP
|
||||
{"-p", "tcp", "--dport", "8085", "-d", "2001:db8:a::1", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8085", "-d", "2001:db8:a::1", "-j", "DNAT", "--to-destination", "[2001:db8::2]:85"},
|
||||
// tcp rules and hostIP = "::"
|
||||
{"-p", "tcp", "--dport", "8086", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8086", "-j", "DNAT", "--to-destination", "[2001:db8::2]:86"},
|
||||
}))
|
||||
|
||||
// Disable snat, generate rules
|
||||
ch.rules = nil
|
||||
ch.entryRules = nil
|
||||
fvar := false
|
||||
conf.SNAT = &fvar
|
||||
|
||||
n, err = types.ParseCIDR("10.0.0.2/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
fillDnatRules(&ch, conf, *n)
|
||||
Expect(ch.rules).To(Equal([][]string{
|
||||
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||
{"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||
{"-p", "udp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:81"},
|
||||
{"-p", "udp", "--dport", "8082", "-j", "DNAT", "--to-destination", "10.0.0.2:82"},
|
||||
{"-p", "tcp", "--dport", "8083", "-d", "192.168.0.2", "-j", "DNAT", "--to-destination", "10.0.0.2:83"},
|
||||
{"-p", "tcp", "--dport", "8084", "-j", "DNAT", "--to-destination", "10.0.0.2:84"},
|
||||
}))
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] generates a correct chain with external mark", ver), func() {
|
||||
ch := genDnatChain(netName, containerID)
|
||||
|
||||
Expect(ch).To(Equal(chain{
|
||||
table: "nat",
|
||||
name: "CNI-DN-bfd599665540dd91d5d28",
|
||||
entryChains: []string{TopLevelDNATChainName},
|
||||
}))
|
||||
configBytes := []byte(fmt.Sprintf(`{
|
||||
"name": "test",
|
||||
"type": "portmap",
|
||||
"cniVersion": "%s",
|
||||
"runtimeConfig": {
|
||||
"portMappings": [
|
||||
{ "hostPort": 8080, "containerPort": 80, "protocol": "tcp"}
|
||||
]
|
||||
},
|
||||
"externalSetMarkChain": "PLZ-SET-MARK",
|
||||
"conditionsV4": ["-a", "b"],
|
||||
"conditionsV6": ["-c", "d"]
|
||||
}`, ver))
|
||||
|
||||
conf, _, err := parseConfig(configBytes, "foo")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
conf.ContainerID = containerID
|
||||
|
||||
ch = genDnatChain(conf.Name, containerID)
|
||||
n, err := types.ParseCIDR("10.0.0.2/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
fillDnatRules(&ch, conf, *n)
|
||||
Expect(ch.rules).To(Equal([][]string{
|
||||
{"-p", "tcp", "--dport", "8080", "-s", "10.0.0.2/24", "-j", "PLZ-SET-MARK"},
|
||||
{"-p", "tcp", "--dport", "8080", "-s", "127.0.0.1", "-j", "PLZ-SET-MARK"},
|
||||
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||
}))
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] generates a correct top-level chain", ver), func() {
|
||||
ch := genToplevelDnatChain()
|
||||
|
||||
Expect(ch).To(Equal(chain{
|
||||
table: "nat",
|
||||
name: "CNI-HOSTPORT-DNAT",
|
||||
entryChains: []string{"PREROUTING", "OUTPUT"},
|
||||
entryRules: [][]string{{"-m", "addrtype", "--dst-type", "LOCAL"}},
|
||||
}))
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] generates the correct mark chains", ver), func() {
|
||||
masqBit := 5
|
||||
ch := genSetMarkChain(masqBit)
|
||||
Expect(ch).To(Equal(chain{
|
||||
table: "nat",
|
||||
name: "CNI-HOSTPORT-SETMARK",
|
||||
rules: [][]string{{
|
||||
"-m", "comment",
|
||||
"--comment", "CNI portfwd masquerade mark",
|
||||
"-j", "MARK",
|
||||
"--set-xmark", "0x20/0x20",
|
||||
}},
|
||||
}))
|
||||
|
||||
ch = genMarkMasqChain(masqBit)
|
||||
Expect(ch).To(Equal(chain{
|
||||
table: "nat",
|
||||
name: "CNI-HOSTPORT-MASQ",
|
||||
entryChains: []string{"POSTROUTING"},
|
||||
entryRules: [][]string{{
|
||||
"-m", "comment",
|
||||
"--comment", "CNI portfwd requiring masquerade",
|
||||
}},
|
||||
rules: [][]string{{
|
||||
"-m", "mark",
|
||||
"--mark", "0x20/0x20",
|
||||
"-j", "MASQUERADE",
|
||||
}},
|
||||
prependEntry: true,
|
||||
}))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
340
plugins/meta/portmap/portmap_nftables.go
Normal file
340
plugins/meta/portmap/portmap_nftables.go
Normal file
@ -0,0 +1,340 @@
|
||||
// Copyright 2023 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"sigs.k8s.io/knftables"
|
||||
)
|
||||
|
||||
const (
|
||||
tableName = "cni_hostport"
|
||||
|
||||
hostIPHostPortsChain = "hostip_hostports"
|
||||
hostPortsChain = "hostports"
|
||||
masqueradingChain = "masquerading"
|
||||
)
|
||||
|
||||
// The nftables portmap implementation is fairly similar to the iptables implementation:
|
||||
// we add a rule for each mapping, with a comment containing a hash of the container ID,
|
||||
// so that we can later reliably delete the rules we want. (This is important because in
|
||||
// edge cases, it's possible the plugin might see "ADD container A with IP 192.168.1.3",
|
||||
// followed by "ADD container B with IP 192.168.1.3" followed by "DEL container A with IP
|
||||
// 192.168.1.3", and we need to make sure that the DEL causes us to delete the rule for
|
||||
// container A, and not the rule for container B.) This iptables implementation actually
|
||||
// uses a separate chain per container but there's not really any need for that...
|
||||
//
|
||||
// As with pkg/ip/ipmasq_nftables_linux.go, it would be more nftables-y to have a chain
|
||||
// with a single rule doing a lookup against a map with an element per mapping, rather
|
||||
// than having a chain with a rule per mapping. But there's no easy, non-racy way to say
|
||||
// "delete the element 192.168.1.3 from the map, but only if it was added for container A,
|
||||
// not if it was added for container B".
|
||||
|
||||
type portMapperNFTables struct {
|
||||
ipv4 knftables.Interface
|
||||
ipv6 knftables.Interface
|
||||
}
|
||||
|
||||
// getPortMapNFT creates an nftables.Interface for port mapping for the IP family of ipn
|
||||
func (pmNFT *portMapperNFTables) getPortMapNFT(ipv6 bool) (knftables.Interface, error) {
|
||||
var err error
|
||||
if ipv6 {
|
||||
if pmNFT.ipv6 == nil {
|
||||
pmNFT.ipv6, err = knftables.New(knftables.IPv6Family, tableName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return pmNFT.ipv6, nil
|
||||
}
|
||||
|
||||
if pmNFT.ipv4 == nil {
|
||||
pmNFT.ipv4, err = knftables.New(knftables.IPv4Family, tableName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return pmNFT.ipv4, err
|
||||
}
|
||||
|
||||
// forwardPorts establishes port forwarding to a given container IP.
|
||||
// containerNet.IP can be either v4 or v6.
|
||||
func (pmNFT *portMapperNFTables) forwardPorts(config *PortMapConf, containerNet net.IPNet) error {
|
||||
isV6 := (containerNet.IP.To4() == nil)
|
||||
nft, err := pmNFT.getPortMapNFT(isV6)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var ipX string
|
||||
var conditions []string
|
||||
if isV6 {
|
||||
ipX = "ip6"
|
||||
if config.ConditionsV6 != nil {
|
||||
conditions = *config.ConditionsV6
|
||||
}
|
||||
} else if !isV6 {
|
||||
ipX = "ip"
|
||||
if config.ConditionsV4 != nil {
|
||||
conditions = *config.ConditionsV4
|
||||
}
|
||||
}
|
||||
|
||||
tx := nft.NewTransaction()
|
||||
|
||||
// Ensure basic rule structure
|
||||
tx.Add(&knftables.Table{
|
||||
Comment: knftables.PtrTo("CNI portmap plugin"),
|
||||
})
|
||||
|
||||
tx.Add(&knftables.Chain{
|
||||
Name: "hostports",
|
||||
})
|
||||
tx.Add(&knftables.Chain{
|
||||
Name: "hostip_hostports",
|
||||
})
|
||||
|
||||
tx.Add(&knftables.Chain{
|
||||
Name: "prerouting",
|
||||
Type: knftables.PtrTo(knftables.NATType),
|
||||
Hook: knftables.PtrTo(knftables.PreroutingHook),
|
||||
Priority: knftables.PtrTo(knftables.DNATPriority),
|
||||
})
|
||||
tx.Flush(&knftables.Chain{
|
||||
Name: "prerouting",
|
||||
})
|
||||
tx.Add(&knftables.Rule{
|
||||
Chain: "prerouting",
|
||||
Rule: knftables.Concat(
|
||||
conditions,
|
||||
"jump", hostIPHostPortsChain,
|
||||
),
|
||||
})
|
||||
tx.Add(&knftables.Rule{
|
||||
Chain: "prerouting",
|
||||
Rule: knftables.Concat(
|
||||
conditions,
|
||||
"jump", hostPortsChain,
|
||||
),
|
||||
})
|
||||
|
||||
tx.Add(&knftables.Chain{
|
||||
Name: "output",
|
||||
Type: knftables.PtrTo(knftables.NATType),
|
||||
Hook: knftables.PtrTo(knftables.OutputHook),
|
||||
Priority: knftables.PtrTo(knftables.DNATPriority),
|
||||
})
|
||||
tx.Flush(&knftables.Chain{
|
||||
Name: "output",
|
||||
})
|
||||
tx.Add(&knftables.Rule{
|
||||
Chain: "output",
|
||||
Rule: knftables.Concat(
|
||||
conditions,
|
||||
"jump", hostIPHostPortsChain,
|
||||
),
|
||||
})
|
||||
tx.Add(&knftables.Rule{
|
||||
Chain: "output",
|
||||
Rule: knftables.Concat(
|
||||
conditions,
|
||||
"fib daddr type local",
|
||||
"jump", hostPortsChain,
|
||||
),
|
||||
})
|
||||
|
||||
if *config.SNAT {
|
||||
tx.Add(&knftables.Chain{
|
||||
Name: masqueradingChain,
|
||||
Type: knftables.PtrTo(knftables.NATType),
|
||||
Hook: knftables.PtrTo(knftables.PostroutingHook),
|
||||
Priority: knftables.PtrTo(knftables.SNATPriority),
|
||||
})
|
||||
}
|
||||
|
||||
// Set up this container
|
||||
for _, e := range config.RuntimeConfig.PortMaps {
|
||||
useHostIP := false
|
||||
if e.HostIP != "" {
|
||||
hostIP := net.ParseIP(e.HostIP)
|
||||
isHostV6 := (hostIP.To4() == nil)
|
||||
// Ignore wrong-IP-family HostIPs
|
||||
if isV6 != isHostV6 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Unspecified addresses cannot be used as destination
|
||||
useHostIP = !hostIP.IsUnspecified()
|
||||
}
|
||||
|
||||
if useHostIP {
|
||||
tx.Add(&knftables.Rule{
|
||||
Chain: hostIPHostPortsChain,
|
||||
Rule: knftables.Concat(
|
||||
ipX, "daddr", e.HostIP,
|
||||
e.Protocol, "dport", e.HostPort,
|
||||
"dnat to", net.JoinHostPort(containerNet.IP.String(), strconv.Itoa(e.ContainerPort)),
|
||||
),
|
||||
Comment: &config.ContainerID,
|
||||
})
|
||||
} else {
|
||||
tx.Add(&knftables.Rule{
|
||||
Chain: hostPortsChain,
|
||||
Rule: knftables.Concat(
|
||||
e.Protocol, "dport", e.HostPort,
|
||||
"dnat to", net.JoinHostPort(containerNet.IP.String(), strconv.Itoa(e.ContainerPort)),
|
||||
),
|
||||
Comment: &config.ContainerID,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if *config.SNAT {
|
||||
// Add mark-to-masquerade rules for hairpin and localhost
|
||||
// In theory we should validate that the original dst IP and port are as
|
||||
// expected, but *any* traffic matching one of these patterns would need
|
||||
// to be masqueraded to be able to work correctly anyway.
|
||||
tx.Add(&knftables.Rule{
|
||||
Chain: masqueradingChain,
|
||||
Rule: knftables.Concat(
|
||||
ipX, "saddr", containerNet.IP,
|
||||
ipX, "daddr", containerNet.IP,
|
||||
"masquerade",
|
||||
),
|
||||
Comment: &config.ContainerID,
|
||||
})
|
||||
if !isV6 {
|
||||
tx.Add(&knftables.Rule{
|
||||
Chain: masqueradingChain,
|
||||
Rule: knftables.Concat(
|
||||
ipX, "saddr 127.0.0.1",
|
||||
ipX, "daddr", containerNet.IP,
|
||||
"masquerade",
|
||||
),
|
||||
Comment: &config.ContainerID,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
err = nft.Run(context.TODO(), tx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to set up nftables rules for port mappings: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pmNFT *portMapperNFTables) checkPorts(config *PortMapConf, containerNet net.IPNet) error {
|
||||
isV6 := (containerNet.IP.To4() == nil)
|
||||
|
||||
var hostPorts, hostIPHostPorts, masqueradings int
|
||||
for _, e := range config.RuntimeConfig.PortMaps {
|
||||
if e.HostIP != "" {
|
||||
hostIPHostPorts++
|
||||
} else {
|
||||
hostPorts++
|
||||
}
|
||||
}
|
||||
if *config.SNAT {
|
||||
masqueradings = len(config.RuntimeConfig.PortMaps)
|
||||
if isV6 {
|
||||
masqueradings *= 2
|
||||
}
|
||||
}
|
||||
|
||||
nft, err := pmNFT.getPortMapNFT(isV6)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hostPorts > 0 {
|
||||
err := checkPortsAgainstRules(nft, hostPortsChain, config.ContainerID, hostPorts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if hostIPHostPorts > 0 {
|
||||
err := checkPortsAgainstRules(nft, hostIPHostPortsChain, config.ContainerID, hostIPHostPorts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if masqueradings > 0 {
|
||||
err := checkPortsAgainstRules(nft, masqueradingChain, config.ContainerID, masqueradings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkPortsAgainstRules(nft knftables.Interface, chain, comment string, nPorts int) error {
|
||||
rules, err := nft.ListRules(context.TODO(), chain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
found := 0
|
||||
for _, r := range rules {
|
||||
if r.Comment != nil && *r.Comment == comment {
|
||||
found++
|
||||
}
|
||||
}
|
||||
if found < nPorts {
|
||||
return fmt.Errorf("missing hostport rules in %q chain", chain)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// unforwardPorts deletes any nftables rules created by this plugin.
|
||||
// It should be idempotent - it will not error if the chain does not exist.
|
||||
func (pmNFT *portMapperNFTables) unforwardPorts(config *PortMapConf) error {
|
||||
// Always clear both IPv4 and IPv6, just to be sure
|
||||
for _, family := range []knftables.Family{knftables.IPv4Family, knftables.IPv6Family} {
|
||||
nft, err := pmNFT.getPortMapNFT(family == knftables.IPv6Family)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
tx := nft.NewTransaction()
|
||||
for _, chain := range []string{hostPortsChain, hostIPHostPortsChain, masqueradingChain} {
|
||||
rules, err := nft.ListRules(context.TODO(), chain)
|
||||
if err != nil {
|
||||
if knftables.IsNotFound(err) {
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("could not list rules in table %s: %w", tableName, err)
|
||||
}
|
||||
|
||||
for _, r := range rules {
|
||||
if r.Comment != nil && *r.Comment == config.ContainerID {
|
||||
tx.Delete(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = nft.Run(context.TODO(), tx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting nftables rules: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
134
plugins/meta/portmap/portmap_nftables_test.go
Normal file
134
plugins/meta/portmap/portmap_nftables_test.go
Normal file
@ -0,0 +1,134 @@
|
||||
// Copyright 2023 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"sigs.k8s.io/knftables"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
)
|
||||
|
||||
var _ = Describe("portmapping configuration (nftables)", func() {
|
||||
containerID := "icee6giejonei6so"
|
||||
|
||||
for _, ver := range []string{"0.3.0", "0.3.1", "0.4.0", "1.0.0"} {
|
||||
// Redefine ver inside for scope so real value is picked up by each dynamically defined It()
|
||||
// See Gingkgo's "Patterns for dynamically generating tests" documentation.
|
||||
ver := ver
|
||||
|
||||
Describe("nftables rules", func() {
|
||||
var pmNFT *portMapperNFTables
|
||||
var ipv4Fake, ipv6Fake *knftables.Fake
|
||||
BeforeEach(func() {
|
||||
ipv4Fake = knftables.NewFake(knftables.IPv4Family, tableName)
|
||||
ipv6Fake = knftables.NewFake(knftables.IPv6Family, tableName)
|
||||
pmNFT = &portMapperNFTables{
|
||||
ipv4: ipv4Fake,
|
||||
ipv6: ipv6Fake,
|
||||
}
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] generates correct rules on ADD", ver), func() {
|
||||
configBytes := []byte(fmt.Sprintf(`{
|
||||
"name": "test",
|
||||
"type": "portmap",
|
||||
"cniVersion": "%s",
|
||||
"backend": "nftables",
|
||||
"runtimeConfig": {
|
||||
"portMappings": [
|
||||
{ "hostPort": 8080, "containerPort": 80, "protocol": "tcp"},
|
||||
{ "hostPort": 8081, "containerPort": 80, "protocol": "tcp"},
|
||||
{ "hostPort": 8080, "containerPort": 81, "protocol": "udp"},
|
||||
{ "hostPort": 8082, "containerPort": 82, "protocol": "udp"},
|
||||
{ "hostPort": 8083, "containerPort": 83, "protocol": "tcp", "hostIP": "192.168.0.2"},
|
||||
{ "hostPort": 8084, "containerPort": 84, "protocol": "tcp", "hostIP": "0.0.0.0"},
|
||||
{ "hostPort": 8085, "containerPort": 85, "protocol": "tcp", "hostIP": "2001:db8:a::1"},
|
||||
{ "hostPort": 8086, "containerPort": 86, "protocol": "tcp", "hostIP": "::"}
|
||||
]
|
||||
},
|
||||
"snat": true,
|
||||
"conditionsV4": ["a", "b"],
|
||||
"conditionsV6": ["c", "d"]
|
||||
}`, ver))
|
||||
|
||||
conf, _, err := parseConfig(configBytes, "foo")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
conf.ContainerID = containerID
|
||||
|
||||
containerNet, err := types.ParseCIDR("10.0.0.2/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = pmNFT.forwardPorts(conf, *containerNet)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
expectedRules := strings.TrimSpace(`
|
||||
add table ip cni_hostport { comment "CNI portmap plugin" ; }
|
||||
add chain ip cni_hostport hostip_hostports
|
||||
add chain ip cni_hostport hostports
|
||||
add chain ip cni_hostport masquerading { type nat hook postrouting priority 100 ; }
|
||||
add chain ip cni_hostport output { type nat hook output priority -100 ; }
|
||||
add chain ip cni_hostport prerouting { type nat hook prerouting priority -100 ; }
|
||||
add rule ip cni_hostport hostip_hostports ip daddr 192.168.0.2 tcp dport 8083 dnat to 10.0.0.2:83 comment "icee6giejonei6so"
|
||||
add rule ip cni_hostport hostports tcp dport 8080 dnat to 10.0.0.2:80 comment "icee6giejonei6so"
|
||||
add rule ip cni_hostport hostports tcp dport 8081 dnat to 10.0.0.2:80 comment "icee6giejonei6so"
|
||||
add rule ip cni_hostport hostports udp dport 8080 dnat to 10.0.0.2:81 comment "icee6giejonei6so"
|
||||
add rule ip cni_hostport hostports udp dport 8082 dnat to 10.0.0.2:82 comment "icee6giejonei6so"
|
||||
add rule ip cni_hostport hostports tcp dport 8084 dnat to 10.0.0.2:84 comment "icee6giejonei6so"
|
||||
add rule ip cni_hostport masquerading ip saddr 10.0.0.2 ip daddr 10.0.0.2 masquerade comment "icee6giejonei6so"
|
||||
add rule ip cni_hostport masquerading ip saddr 127.0.0.1 ip daddr 10.0.0.2 masquerade comment "icee6giejonei6so"
|
||||
add rule ip cni_hostport output a b jump hostip_hostports
|
||||
add rule ip cni_hostport output a b fib daddr type local jump hostports
|
||||
add rule ip cni_hostport prerouting a b jump hostip_hostports
|
||||
add rule ip cni_hostport prerouting a b jump hostports
|
||||
`)
|
||||
actualRules := strings.TrimSpace(ipv4Fake.Dump())
|
||||
Expect(actualRules).To(Equal(expectedRules))
|
||||
|
||||
// Disable snat, generate IPv6 rules
|
||||
*conf.SNAT = false
|
||||
containerNet, err = types.ParseCIDR("2001:db8::2/64")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = pmNFT.forwardPorts(conf, *containerNet)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
expectedRules = strings.TrimSpace(`
|
||||
add table ip6 cni_hostport { comment "CNI portmap plugin" ; }
|
||||
add chain ip6 cni_hostport hostip_hostports
|
||||
add chain ip6 cni_hostport hostports
|
||||
add chain ip6 cni_hostport output { type nat hook output priority -100 ; }
|
||||
add chain ip6 cni_hostport prerouting { type nat hook prerouting priority -100 ; }
|
||||
add rule ip6 cni_hostport hostip_hostports ip6 daddr 2001:db8:a::1 tcp dport 8085 dnat to [2001:db8::2]:85 comment "icee6giejonei6so"
|
||||
add rule ip6 cni_hostport hostports tcp dport 8080 dnat to [2001:db8::2]:80 comment "icee6giejonei6so"
|
||||
add rule ip6 cni_hostport hostports tcp dport 8081 dnat to [2001:db8::2]:80 comment "icee6giejonei6so"
|
||||
add rule ip6 cni_hostport hostports udp dport 8080 dnat to [2001:db8::2]:81 comment "icee6giejonei6so"
|
||||
add rule ip6 cni_hostport hostports udp dport 8082 dnat to [2001:db8::2]:82 comment "icee6giejonei6so"
|
||||
add rule ip6 cni_hostport hostports tcp dport 8086 dnat to [2001:db8::2]:86 comment "icee6giejonei6so"
|
||||
add rule ip6 cni_hostport output c d jump hostip_hostports
|
||||
add rule ip6 cni_hostport output c d fib daddr type local jump hostports
|
||||
add rule ip6 cni_hostport prerouting c d jump hostip_hostports
|
||||
add rule ip6 cni_hostport prerouting c d jump hostports
|
||||
`)
|
||||
actualRules = strings.TrimSpace(ipv6Fake.Dump())
|
||||
Expect(actualRules).To(Equal(expectedRules))
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
@ -24,9 +24,6 @@ import (
|
||||
)
|
||||
|
||||
var _ = Describe("portmapping configuration", func() {
|
||||
netName := "testNetName"
|
||||
containerID := "icee6giejonei6sohng6ahngee7laquohquee9shiGo7fohferakah3Feiyoolu2pei7ciPhoh7shaoX6vai3vuf0ahfaeng8yohb9ceu0daez5hashee8ooYai5wa3y"
|
||||
|
||||
for _, ver := range []string{"0.3.0", "0.3.1", "0.4.0", "1.0.0"} {
|
||||
// Redefine ver inside for scope so real value is picked up by each dynamically defined It()
|
||||
// See Gingkgo's "Patterns for dynamically generating tests" documentation.
|
||||
@ -38,6 +35,7 @@ var _ = Describe("portmapping configuration", func() {
|
||||
"name": "test",
|
||||
"type": "portmap",
|
||||
"cniVersion": "%s",
|
||||
"backend": "iptables",
|
||||
"runtimeConfig": {
|
||||
"portMappings": [
|
||||
{ "hostPort": 8080, "containerPort": 80, "protocol": "tcp"},
|
||||
@ -45,8 +43,8 @@ var _ = Describe("portmapping configuration", func() {
|
||||
]
|
||||
},
|
||||
"snat": false,
|
||||
"conditionsV4": ["a", "b"],
|
||||
"conditionsV6": ["c", "d"],
|
||||
"conditionsV4": ["-s", "1.2.3.4"],
|
||||
"conditionsV6": ["!", "-s", "12::34"],
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{"name": "host"},
|
||||
@ -77,8 +75,8 @@ var _ = Describe("portmapping configuration", func() {
|
||||
c, _, err := parseConfig(configBytes, "container")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(c.CNIVersion).To(Equal(ver))
|
||||
Expect(c.ConditionsV4).To(Equal(&[]string{"a", "b"}))
|
||||
Expect(c.ConditionsV6).To(Equal(&[]string{"c", "d"}))
|
||||
Expect(c.ConditionsV4).To(Equal(&[]string{"-s", "1.2.3.4"}))
|
||||
Expect(c.ConditionsV6).To(Equal(&[]string{"!", "-s", "12::34"}))
|
||||
fvar := false
|
||||
Expect(c.SNAT).To(Equal(&fvar))
|
||||
Expect(c.Name).To(Equal("test"))
|
||||
@ -97,15 +95,16 @@ var _ = Describe("portmapping configuration", func() {
|
||||
"name": "test",
|
||||
"type": "portmap",
|
||||
"cniVersion": "%s",
|
||||
"backend": "iptables",
|
||||
"snat": false,
|
||||
"conditionsV4": ["a", "b"],
|
||||
"conditionsV6": ["c", "d"]
|
||||
"conditionsV4": ["-s", "1.2.3.4"],
|
||||
"conditionsV6": ["-s", "12::34"]
|
||||
}`, ver))
|
||||
c, _, err := parseConfig(configBytes, "container")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(c.CNIVersion).To(Equal(ver))
|
||||
Expect(c.ConditionsV4).To(Equal(&[]string{"a", "b"}))
|
||||
Expect(c.ConditionsV6).To(Equal(&[]string{"c", "d"}))
|
||||
Expect(c.ConditionsV4).To(Equal(&[]string{"-s", "1.2.3.4"}))
|
||||
Expect(c.ConditionsV6).To(Equal(&[]string{"-s", "12::34"}))
|
||||
fvar := false
|
||||
Expect(c.SNAT).To(Equal(&fvar))
|
||||
Expect(c.Name).To(Equal("test"))
|
||||
@ -116,9 +115,10 @@ var _ = Describe("portmapping configuration", func() {
|
||||
"name": "test",
|
||||
"type": "portmap",
|
||||
"cniVersion": "%s",
|
||||
"backend": "iptables",
|
||||
"snat": false,
|
||||
"conditionsV4": ["a", "b"],
|
||||
"conditionsV6": ["c", "d"],
|
||||
"conditionsV4": ["-s", "1.2.3.4"],
|
||||
"conditionsV6": ["-s", "12::34"],
|
||||
"runtimeConfig": {
|
||||
"portMappings": [
|
||||
{ "hostPort": 0, "containerPort": 80, "protocol": "tcp"}
|
||||
@ -129,6 +129,82 @@ var _ = Describe("portmapping configuration", func() {
|
||||
Expect(err).To(MatchError("Invalid host port number: 0"))
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] defaults to iptables when backend is not specified", ver), func() {
|
||||
// "defaults to iptables" is only true if iptables is installed
|
||||
// (or if neither iptables nor nftables is installed), but the
|
||||
// other unit tests would fail if iptables wasn't installed, so
|
||||
// we know it must be.
|
||||
configBytes := []byte(fmt.Sprintf(`{
|
||||
"name": "test",
|
||||
"type": "portmap",
|
||||
"cniVersion": "%s"
|
||||
}`, ver))
|
||||
c, _, err := parseConfig(configBytes, "container")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(c.CNIVersion).To(Equal(ver))
|
||||
Expect(c.Backend).To(Equal(&iptablesBackend))
|
||||
Expect(c.Name).To(Equal("test"))
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] uses nftables if requested", ver), func() {
|
||||
configBytes := []byte(fmt.Sprintf(`{
|
||||
"name": "test",
|
||||
"type": "portmap",
|
||||
"cniVersion": "%s",
|
||||
"backend": "nftables"
|
||||
}`, ver))
|
||||
c, _, err := parseConfig(configBytes, "container")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(c.CNIVersion).To(Equal(ver))
|
||||
Expect(c.Backend).To(Equal(&nftablesBackend))
|
||||
Expect(c.Name).To(Equal("test"))
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] allows nftables conditions if nftables is requested", ver), func() {
|
||||
configBytes := []byte(fmt.Sprintf(`{
|
||||
"name": "test",
|
||||
"type": "portmap",
|
||||
"cniVersion": "%s",
|
||||
"backend": "nftables",
|
||||
"conditionsV4": ["ip", "saddr", "1.2.3.4"],
|
||||
"conditionsV6": ["ip6", "saddr", "12::34"]
|
||||
}`, ver))
|
||||
c, _, err := parseConfig(configBytes, "container")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(c.CNIVersion).To(Equal(ver))
|
||||
Expect(c.Backend).To(Equal(&nftablesBackend))
|
||||
Expect(c.ConditionsV4).To(Equal(&[]string{"ip", "saddr", "1.2.3.4"}))
|
||||
Expect(c.ConditionsV6).To(Equal(&[]string{"ip6", "saddr", "12::34"}))
|
||||
Expect(c.Name).To(Equal("test"))
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] rejects nftables options with 'backend: iptables'", ver), func() {
|
||||
configBytes := []byte(fmt.Sprintf(`{
|
||||
"name": "test",
|
||||
"type": "portmap",
|
||||
"cniVersion": "%s",
|
||||
"backend": "iptables",
|
||||
"conditionsV4": ["ip", "saddr", "1.2.3.4"],
|
||||
"conditionsV6": ["ip6", "saddr", "12::34"]
|
||||
}`, ver))
|
||||
_, _, err := parseConfig(configBytes, "container")
|
||||
Expect(err).To(MatchError("iptables backend was requested but configuration contains nftables-specific options [conditionsV4 conditionsV6]"))
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] rejects iptables options with 'backend: nftables'", ver), func() {
|
||||
configBytes := []byte(fmt.Sprintf(`{
|
||||
"name": "test",
|
||||
"type": "portmap",
|
||||
"cniVersion": "%s",
|
||||
"backend": "nftables",
|
||||
"externalSetMarkChain": "KUBE-MARK-MASQ",
|
||||
"conditionsV4": ["-s", "1.2.3.4"],
|
||||
"conditionsV6": ["-s", "12::34"]
|
||||
}`, ver))
|
||||
_, _, err := parseConfig(configBytes, "container")
|
||||
Expect(err).To(MatchError("nftables backend was requested but configuration contains iptables-specific options [externalSetMarkChain conditionsV4 conditionsV6]"))
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] does not fail on missing prevResult interface index", ver), func() {
|
||||
configBytes := []byte(fmt.Sprintf(`{
|
||||
"name": "test",
|
||||
@ -139,7 +215,7 @@ var _ = Describe("portmapping configuration", func() {
|
||||
{ "hostPort": 8080, "containerPort": 80, "protocol": "tcp"}
|
||||
]
|
||||
},
|
||||
"conditionsV4": ["a", "b"],
|
||||
"conditionsV4": ["-s", "1.2.3.4"],
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{"name": "host"}
|
||||
@ -157,222 +233,5 @@ var _ = Describe("portmapping configuration", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Generating chains", func() {
|
||||
Context("for DNAT", func() {
|
||||
It(fmt.Sprintf("[%s] generates a correct standard container chain", ver), func() {
|
||||
ch := genDnatChain(netName, containerID)
|
||||
|
||||
Expect(ch).To(Equal(chain{
|
||||
table: "nat",
|
||||
name: "CNI-DN-bfd599665540dd91d5d28",
|
||||
entryChains: []string{TopLevelDNATChainName},
|
||||
}))
|
||||
configBytes := []byte(fmt.Sprintf(`{
|
||||
"name": "test",
|
||||
"type": "portmap",
|
||||
"cniVersion": "%s",
|
||||
"runtimeConfig": {
|
||||
"portMappings": [
|
||||
{ "hostPort": 8080, "containerPort": 80, "protocol": "tcp"},
|
||||
{ "hostPort": 8081, "containerPort": 80, "protocol": "tcp"},
|
||||
{ "hostPort": 8080, "containerPort": 81, "protocol": "udp"},
|
||||
{ "hostPort": 8082, "containerPort": 82, "protocol": "udp"},
|
||||
{ "hostPort": 8083, "containerPort": 83, "protocol": "tcp", "hostIP": "192.168.0.2"},
|
||||
{ "hostPort": 8084, "containerPort": 84, "protocol": "tcp", "hostIP": "0.0.0.0"},
|
||||
{ "hostPort": 8085, "containerPort": 85, "protocol": "tcp", "hostIP": "2001:db8:a::1"},
|
||||
{ "hostPort": 8086, "containerPort": 86, "protocol": "tcp", "hostIP": "::"}
|
||||
]
|
||||
},
|
||||
"snat": true,
|
||||
"conditionsV4": ["a", "b"],
|
||||
"conditionsV6": ["c", "d"]
|
||||
}`, ver))
|
||||
|
||||
conf, _, err := parseConfig(configBytes, "foo")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
conf.ContainerID = containerID
|
||||
|
||||
ch = genDnatChain(conf.Name, containerID)
|
||||
Expect(ch).To(Equal(chain{
|
||||
table: "nat",
|
||||
name: "CNI-DN-67e92b96e692a494b6b85",
|
||||
entryChains: []string{"CNI-HOSTPORT-DNAT"},
|
||||
}))
|
||||
|
||||
n, err := types.ParseCIDR("10.0.0.2/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
fillDnatRules(&ch, conf, *n)
|
||||
|
||||
Expect(ch.entryRules).To(Equal([][]string{
|
||||
{
|
||||
"-m", "comment", "--comment",
|
||||
fmt.Sprintf("dnat name: \"test\" id: \"%s\"", containerID),
|
||||
"-m", "multiport",
|
||||
"-p", "tcp",
|
||||
"--destination-ports", "8080,8081,8083,8084,8085,8086",
|
||||
"a", "b",
|
||||
},
|
||||
{
|
||||
"-m", "comment", "--comment",
|
||||
fmt.Sprintf("dnat name: \"test\" id: \"%s\"", containerID),
|
||||
"-m", "multiport",
|
||||
"-p", "udp",
|
||||
"--destination-ports", "8080,8082",
|
||||
"a", "b",
|
||||
},
|
||||
}))
|
||||
|
||||
Expect(ch.rules).To(Equal([][]string{
|
||||
// tcp rules and not hostIP
|
||||
{"-p", "tcp", "--dport", "8080", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8080", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||
{"-p", "tcp", "--dport", "8081", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8081", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||
// udp rules and not hostIP
|
||||
{"-p", "udp", "--dport", "8080", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8080", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:81"},
|
||||
{"-p", "udp", "--dport", "8082", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8082", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8082", "-j", "DNAT", "--to-destination", "10.0.0.2:82"},
|
||||
// tcp rules and hostIP
|
||||
{"-p", "tcp", "--dport", "8083", "-d", "192.168.0.2", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8083", "-d", "192.168.0.2", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8083", "-d", "192.168.0.2", "-j", "DNAT", "--to-destination", "10.0.0.2:83"},
|
||||
// tcp rules and hostIP = "0.0.0.0"
|
||||
{"-p", "tcp", "--dport", "8084", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8084", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8084", "-j", "DNAT", "--to-destination", "10.0.0.2:84"},
|
||||
}))
|
||||
|
||||
ch.rules = nil
|
||||
ch.entryRules = nil
|
||||
|
||||
n, err = types.ParseCIDR("2001:db8::2/64")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
fillDnatRules(&ch, conf, *n)
|
||||
|
||||
Expect(ch.rules).To(Equal([][]string{
|
||||
// tcp rules and not hostIP
|
||||
{"-p", "tcp", "--dport", "8080", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "[2001:db8::2]:80"},
|
||||
{"-p", "tcp", "--dport", "8081", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "[2001:db8::2]:80"},
|
||||
// udp rules and not hostIP
|
||||
{"-p", "udp", "--dport", "8080", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8080", "-j", "DNAT", "--to-destination", "[2001:db8::2]:81"},
|
||||
{"-p", "udp", "--dport", "8082", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8082", "-j", "DNAT", "--to-destination", "[2001:db8::2]:82"},
|
||||
// tcp rules and hostIP
|
||||
{"-p", "tcp", "--dport", "8085", "-d", "2001:db8:a::1", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8085", "-d", "2001:db8:a::1", "-j", "DNAT", "--to-destination", "[2001:db8::2]:85"},
|
||||
// tcp rules and hostIP = "::"
|
||||
{"-p", "tcp", "--dport", "8086", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8086", "-j", "DNAT", "--to-destination", "[2001:db8::2]:86"},
|
||||
}))
|
||||
|
||||
// Disable snat, generate rules
|
||||
ch.rules = nil
|
||||
ch.entryRules = nil
|
||||
fvar := false
|
||||
conf.SNAT = &fvar
|
||||
|
||||
n, err = types.ParseCIDR("10.0.0.2/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
fillDnatRules(&ch, conf, *n)
|
||||
Expect(ch.rules).To(Equal([][]string{
|
||||
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||
{"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||
{"-p", "udp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:81"},
|
||||
{"-p", "udp", "--dport", "8082", "-j", "DNAT", "--to-destination", "10.0.0.2:82"},
|
||||
{"-p", "tcp", "--dport", "8083", "-d", "192.168.0.2", "-j", "DNAT", "--to-destination", "10.0.0.2:83"},
|
||||
{"-p", "tcp", "--dport", "8084", "-j", "DNAT", "--to-destination", "10.0.0.2:84"},
|
||||
}))
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] generates a correct chain with external mark", ver), func() {
|
||||
ch := genDnatChain(netName, containerID)
|
||||
|
||||
Expect(ch).To(Equal(chain{
|
||||
table: "nat",
|
||||
name: "CNI-DN-bfd599665540dd91d5d28",
|
||||
entryChains: []string{TopLevelDNATChainName},
|
||||
}))
|
||||
configBytes := []byte(fmt.Sprintf(`{
|
||||
"name": "test",
|
||||
"type": "portmap",
|
||||
"cniVersion": "%s",
|
||||
"runtimeConfig": {
|
||||
"portMappings": [
|
||||
{ "hostPort": 8080, "containerPort": 80, "protocol": "tcp"}
|
||||
]
|
||||
},
|
||||
"externalSetMarkChain": "PLZ-SET-MARK",
|
||||
"conditionsV4": ["a", "b"],
|
||||
"conditionsV6": ["c", "d"]
|
||||
}`, ver))
|
||||
|
||||
conf, _, err := parseConfig(configBytes, "foo")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
conf.ContainerID = containerID
|
||||
|
||||
ch = genDnatChain(conf.Name, containerID)
|
||||
n, err := types.ParseCIDR("10.0.0.2/24")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
fillDnatRules(&ch, conf, *n)
|
||||
Expect(ch.rules).To(Equal([][]string{
|
||||
{"-p", "tcp", "--dport", "8080", "-s", "10.0.0.2/24", "-j", "PLZ-SET-MARK"},
|
||||
{"-p", "tcp", "--dport", "8080", "-s", "127.0.0.1", "-j", "PLZ-SET-MARK"},
|
||||
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||
}))
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] generates a correct top-level chain", ver), func() {
|
||||
ch := genToplevelDnatChain()
|
||||
|
||||
Expect(ch).To(Equal(chain{
|
||||
table: "nat",
|
||||
name: "CNI-HOSTPORT-DNAT",
|
||||
entryChains: []string{"PREROUTING", "OUTPUT"},
|
||||
entryRules: [][]string{{"-m", "addrtype", "--dst-type", "LOCAL"}},
|
||||
}))
|
||||
})
|
||||
|
||||
It(fmt.Sprintf("[%s] generates the correct mark chains", ver), func() {
|
||||
masqBit := 5
|
||||
ch := genSetMarkChain(masqBit)
|
||||
Expect(ch).To(Equal(chain{
|
||||
table: "nat",
|
||||
name: "CNI-HOSTPORT-SETMARK",
|
||||
rules: [][]string{{
|
||||
"-m", "comment",
|
||||
"--comment", "CNI portfwd masquerade mark",
|
||||
"-j", "MARK",
|
||||
"--set-xmark", "0x20/0x20",
|
||||
}},
|
||||
}))
|
||||
|
||||
ch = genMarkMasqChain(masqBit)
|
||||
Expect(ch).To(Equal(chain{
|
||||
table: "nat",
|
||||
name: "CNI-HOSTPORT-MASQ",
|
||||
entryChains: []string{"POSTROUTING"},
|
||||
entryRules: [][]string{{
|
||||
"-m", "comment",
|
||||
"--comment", "CNI portfwd requiring masquerade",
|
||||
}},
|
||||
rules: [][]string{{
|
||||
"-m", "mark",
|
||||
"--mark", "0x20/0x20",
|
||||
"-j", "MASQUERADE",
|
||||
}},
|
||||
prependEntry: true,
|
||||
}))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@ -21,6 +21,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/utils/sysctl"
|
||||
)
|
||||
|
||||
// fmtIpPort correctly formats ip:port literals for iptables and ip6tables -
|
||||
@ -52,6 +54,14 @@ func getRoutableHostIF(containerIP net.IP) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// enableLocalnetRouting tells the kernel not to treat 127/8 as a martian,
|
||||
// so that connections with a source ip of 127/8 can cross a routing boundary.
|
||||
func enableLocalnetRouting(ifName string) error {
|
||||
routeLocalnetPath := "net/ipv4/conf/" + ifName + "/route_localnet"
|
||||
_, err := sysctl.Sysctl(routeLocalnetPath, "1")
|
||||
return err
|
||||
}
|
||||
|
||||
// groupByProto groups port numbers by protocol
|
||||
func groupByProto(entries []PortMapEntry) map[string][]int {
|
||||
if len(entries) == 0 {
|
||||
|
@ -47,6 +47,7 @@ type PluginConf struct {
|
||||
PrevResult *current.Result `json:"-"`
|
||||
|
||||
// Add plugin-specific flags here
|
||||
Table *int `json:"table,omitempty"`
|
||||
}
|
||||
|
||||
// Wrapper that does a lock before and unlock after operations to serialise
|
||||
@ -163,6 +164,9 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
|
||||
// Do the actual work.
|
||||
err = withLockAndNetNS(args.Netns, func(_ ns.NetNS) error {
|
||||
if conf.Table != nil {
|
||||
return doRoutesWithTable(ipCfgs, *conf.Table)
|
||||
}
|
||||
return doRoutes(ipCfgs, args.IfName)
|
||||
})
|
||||
if err != nil {
|
||||
@ -330,31 +334,73 @@ func doRoutes(ipCfgs []*current.IPConfig, iface string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func doRoutesWithTable(ipCfgs []*current.IPConfig, table int) error {
|
||||
for _, ipCfg := range ipCfgs {
|
||||
log.Printf("Set rule for source %s", ipCfg.String())
|
||||
rule := netlink.NewRule()
|
||||
rule.Table = table
|
||||
|
||||
// Source must be restricted to a single IP, not a full subnet
|
||||
var src net.IPNet
|
||||
src.IP = ipCfg.Address.IP
|
||||
if src.IP.To4() != nil {
|
||||
src.Mask = net.CIDRMask(32, 32)
|
||||
} else {
|
||||
src.Mask = net.CIDRMask(128, 128)
|
||||
}
|
||||
|
||||
log.Printf("Source to use %s", src.String())
|
||||
rule.Src = &src
|
||||
|
||||
if err := netlink.RuleAdd(rule); err != nil {
|
||||
return fmt.Errorf("failed to add rule: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// cmdDel is called for DELETE requests
|
||||
func cmdDel(args *skel.CmdArgs) error {
|
||||
// We care a bit about config because it sets log level.
|
||||
_, err := parseConfig(args.StdinData)
|
||||
conf, err := parseConfig(args.StdinData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Cleaning up SBR for %s", args.IfName)
|
||||
err = withLockAndNetNS(args.Netns, func(_ ns.NetNS) error {
|
||||
return tidyRules(args.IfName)
|
||||
return tidyRules(args.IfName, conf.Table)
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Tidy up the rules for the deleted interface
|
||||
func tidyRules(iface string) error {
|
||||
func tidyRules(iface string, table *int) error {
|
||||
// We keep on going on rule deletion error, but return the last failure.
|
||||
var errReturn error
|
||||
var err error
|
||||
var rules []netlink.Rule
|
||||
|
||||
rules, err := netlink.RuleList(netlink.FAMILY_ALL)
|
||||
if err != nil {
|
||||
log.Printf("Failed to list all rules to tidy: %v", err)
|
||||
return fmt.Errorf("Failed to list all rules to tidy: %v", err)
|
||||
if table != nil {
|
||||
rules, err = netlink.RuleListFiltered(
|
||||
netlink.FAMILY_ALL,
|
||||
&netlink.Rule{
|
||||
Table: *table,
|
||||
},
|
||||
netlink.RT_FILTER_TABLE,
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("Failed to list rules of table %d to tidy: %v", *table, err)
|
||||
return fmt.Errorf("failed to list rules of table %d to tidy: %v", *table, err)
|
||||
}
|
||||
} else {
|
||||
rules, err = netlink.RuleList(netlink.FAMILY_ALL)
|
||||
if err != nil {
|
||||
log.Printf("Failed to list all rules to tidy: %v", err)
|
||||
return fmt.Errorf("Failed to list all rules to tidy: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
link, err := netlink.LinkByName(iface)
|
||||
@ -401,7 +447,13 @@ RULE_LOOP:
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("sbr"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
/* FIXME GC */
|
||||
/* FIXME Status */
|
||||
}, version.All, bv.BuildString("sbr"))
|
||||
}
|
||||
|
||||
func cmdCheck(_ *skel.CmdArgs) error {
|
||||
|
@ -53,7 +53,9 @@ func setup(targetNs ns.NetNS, status netStatus) error {
|
||||
err := targetNs.Do(func(_ ns.NetNS) error {
|
||||
for _, dev := range status.Devices {
|
||||
log.Printf("Adding dev %s\n", dev.Name)
|
||||
link := &netlink.Dummy{LinkAttrs: netlink.LinkAttrs{Name: dev.Name}}
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = dev.Name
|
||||
link := &netlink.Dummy{LinkAttrs: linkAttrs}
|
||||
err := netlink.LinkAdd(link)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -117,11 +119,16 @@ func readback(targetNs ns.NetNS, devNames []string) (netStatus, error) {
|
||||
return err
|
||||
}
|
||||
|
||||
routesNoLinkLocal := []netlink.Route{}
|
||||
for _, route := range routes {
|
||||
if route.Dst.IP.IsLinkLocalMulticast() || route.Dst.IP.IsLinkLocalUnicast() {
|
||||
continue
|
||||
}
|
||||
log.Printf("Got %s route %v", name, route)
|
||||
routesNoLinkLocal = append(routesNoLinkLocal, route)
|
||||
}
|
||||
|
||||
retVal.Devices[i].Routes = routes
|
||||
retVal.Devices[i].Routes = routesNoLinkLocal
|
||||
}
|
||||
|
||||
rules, err := netlink.RuleList(netlink.FAMILY_ALL)
|
||||
@ -291,6 +298,7 @@ var _ = Describe("sbr test", func() {
|
||||
expNet1.Routes = append(expNet1.Routes,
|
||||
netlink.Route{
|
||||
Gw: net.IPv4(192, 168, 1, 1),
|
||||
Dst: &net.IPNet{IP: net.IPv4zero, Mask: net.IPMask(net.IPv4zero)},
|
||||
Table: 100,
|
||||
LinkIndex: expNet1.Routes[0].LinkIndex,
|
||||
})
|
||||
@ -491,6 +499,7 @@ var _ = Describe("sbr test", func() {
|
||||
}
|
||||
expNet1.Routes = append(expNet1.Routes,
|
||||
netlink.Route{
|
||||
Dst: &net.IPNet{IP: net.IPv4zero, Mask: net.IPMask(net.IPv4zero)},
|
||||
Gw: net.IPv4(192, 168, 1, 1),
|
||||
Table: 100,
|
||||
LinkIndex: expNet1.Routes[0].LinkIndex,
|
||||
@ -498,6 +507,7 @@ var _ = Describe("sbr test", func() {
|
||||
|
||||
expNet1.Routes = append(expNet1.Routes,
|
||||
netlink.Route{
|
||||
Dst: &net.IPNet{IP: net.IPv4zero, Mask: net.IPMask(net.IPv4zero)},
|
||||
Gw: net.IPv4(192, 168, 101, 1),
|
||||
Table: 101,
|
||||
LinkIndex: expNet1.Routes[0].LinkIndex,
|
||||
@ -540,4 +550,81 @@ var _ = Describe("sbr test", func() {
|
||||
_, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) })
|
||||
Expect(err).To(MatchError("This plugin must be called as chained plugin"))
|
||||
})
|
||||
|
||||
It("Works with Table ID", func() {
|
||||
ifname := "net1"
|
||||
tableID := 5000
|
||||
conf := `{
|
||||
"cniVersion": "0.3.0",
|
||||
"name": "cni-plugin-sbr-test",
|
||||
"type": "sbr",
|
||||
"table": %d,
|
||||
"prevResult": {
|
||||
"cniVersion": "0.3.0",
|
||||
"interfaces": [
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": "%s"
|
||||
}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"address": "192.168.1.209/24",
|
||||
"interface": 0
|
||||
},
|
||||
{
|
||||
"address": "192.168.101.209/24",
|
||||
"interface": 0
|
||||
}
|
||||
],
|
||||
"routes": []
|
||||
}
|
||||
}`
|
||||
conf = fmt.Sprintf(conf, tableID, ifname, targetNs.Path())
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNs.Path(),
|
||||
IfName: ifname,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
preStatus := createDefaultStatus()
|
||||
|
||||
err := setup(targetNs, preStatus)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
oldStatus, err := readback(targetNs, []string{"net1", "eth0"})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
_, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) })
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
newStatus, err := readback(targetNs, []string{"net1", "eth0"})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Routes have not been moved.
|
||||
Expect(newStatus).To(Equal(oldStatus))
|
||||
|
||||
// Fetch all rules for the requested table ID.
|
||||
var rules []netlink.Rule
|
||||
err = targetNs.Do(func(_ ns.NetNS) error {
|
||||
var err error
|
||||
rules, err = netlink.RuleListFiltered(
|
||||
netlink.FAMILY_ALL, &netlink.Rule{
|
||||
Table: tableID,
|
||||
},
|
||||
netlink.RT_FILTER_TABLE,
|
||||
)
|
||||
return err
|
||||
})
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(rules).To(HaveLen(2))
|
||||
|
||||
// Both IPs have been added as source based routes with requested table ID.
|
||||
Expect(rules[0].Table).To(Equal(tableID))
|
||||
Expect(rules[0].Src.String()).To(Equal("192.168.101.209/32"))
|
||||
Expect(rules[1].Table).To(Equal(tableID))
|
||||
Expect(rules[1].Src.String()).To(Equal("192.168.1.209/32"))
|
||||
})
|
||||
})
|
||||
|
@ -319,7 +319,7 @@ func restoreBackup(ifName, containerID, backupPath string) error {
|
||||
}
|
||||
|
||||
if len(errStr) > 0 {
|
||||
return fmt.Errorf(strings.Join(errStr, "; "))
|
||||
return errors.New(strings.Join(errStr, "; "))
|
||||
}
|
||||
|
||||
if err = os.Remove(filePath); err != nil {
|
||||
@ -433,7 +433,13 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("tuning"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
/* FIXME GC */
|
||||
/* FIXME Status */
|
||||
}, version.All, bv.BuildString("tuning"))
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
|
@ -110,10 +110,10 @@ var _ = Describe("tuning plugin", func() {
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = IFNAME
|
||||
err = netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: IFNAME,
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
link, err := netlink.LinkByName(IFNAME)
|
||||
|
@ -39,7 +39,13 @@ type VRFNetConf struct {
|
||||
}
|
||||
|
||||
func main() {
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.VersionsStartingFrom("0.3.1"), bv.BuildString("vrf"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
/* FIXME GC */
|
||||
/* FIXME Status */
|
||||
}, version.VersionsStartingFrom("0.3.1"), bv.BuildString("vrf"))
|
||||
}
|
||||
|
||||
func cmdAdd(args *skel.CmdArgs) error {
|
||||
|
@ -17,6 +17,8 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
@ -48,11 +50,11 @@ func createVRF(name string, tableID uint32) (*netlink.Vrf, error) {
|
||||
}
|
||||
}
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = name
|
||||
vrf := &netlink.Vrf{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: name,
|
||||
},
|
||||
Table: tableID,
|
||||
LinkAttrs: linkAttrs,
|
||||
Table: tableID,
|
||||
}
|
||||
|
||||
err = netlink.LinkAdd(vrf)
|
||||
@ -124,7 +126,7 @@ func addInterface(vrf *netlink.Vrf, intf string) error {
|
||||
|
||||
afterAddresses, err := netlink.AddrList(i, netlink.FAMILY_V6)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed getting ipv6 new addresses for %s", intf)
|
||||
return fmt.Errorf("failed getting ipv6 new addresses for %s: %v", intf, err)
|
||||
}
|
||||
|
||||
// Since keeping the ipv6 address depends on net.ipv6.conf.all.keep_addr_on_down ,
|
||||
@ -141,6 +143,37 @@ CONTINUE:
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not restore address %s to %s @ %s: %v", toFind, intf, vrf.Name, err)
|
||||
}
|
||||
|
||||
// Waits for local/host routes to be added by the kernel.
|
||||
maxRetry := 10
|
||||
for {
|
||||
routesVRFTable, err := netlink.RouteListFiltered(
|
||||
netlink.FAMILY_ALL,
|
||||
&netlink.Route{
|
||||
Dst: &net.IPNet{
|
||||
IP: toFind.IP,
|
||||
Mask: net.IPMask{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
||||
},
|
||||
Table: int(vrf.Table),
|
||||
LinkIndex: i.Attrs().Index,
|
||||
},
|
||||
netlink.RT_FILTER_OIF|netlink.RT_FILTER_TABLE|netlink.RT_FILTER_DST,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed getting routes for %s table %d for dst %s: %v", intf, vrf.Table, toFind.IPNet.String(), err)
|
||||
}
|
||||
|
||||
if len(routesVRFTable) >= 1 {
|
||||
break
|
||||
}
|
||||
|
||||
maxRetry--
|
||||
if maxRetry <= 0 {
|
||||
return fmt.Errorf("failed getting local/host addresses for %s in table %d with dst %s", intf, vrf.Table, toFind.IPNet.String())
|
||||
}
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
// Apply all saved routes for the interface that was moved to the VRF
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
@ -94,19 +95,19 @@ var _ = Describe("vrf plugin", func() {
|
||||
err = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
la0 := netlink.NewLinkAttrs()
|
||||
la0.Name = IF0Name
|
||||
err = netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: IF0Name,
|
||||
},
|
||||
LinkAttrs: la0,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
_, err = netlink.LinkByName(IF0Name)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
la1 := netlink.NewLinkAttrs()
|
||||
la1.Name = IF1Name
|
||||
err = netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: IF1Name,
|
||||
},
|
||||
LinkAttrs: la1,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
_, err = netlink.LinkByName(IF1Name)
|
||||
@ -207,6 +208,18 @@ var _ = Describe("vrf plugin", func() {
|
||||
// Add IP addresses for network reachability
|
||||
netlink.AddrAdd(link, &netlink.Addr{IPNet: ipv4})
|
||||
netlink.AddrAdd(link, &netlink.Addr{IPNet: ipv6})
|
||||
// Wait for the corresponding route to be addeded
|
||||
Eventually(func() bool {
|
||||
ipv6RouteDst := &net.IPNet{
|
||||
IP: ipv6.IP,
|
||||
Mask: net.IPMask{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
||||
}
|
||||
routes, _ := netlink.RouteListFiltered(netlink.FAMILY_ALL, &netlink.Route{
|
||||
Dst: ipv6RouteDst,
|
||||
Table: 0,
|
||||
}, netlink.RT_FILTER_DST|netlink.RT_FILTER_TABLE)
|
||||
return err == nil && len(routes) >= 1
|
||||
}, time.Second, 500*time.Millisecond).Should(BeTrue())
|
||||
|
||||
ipAddrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@ -304,6 +317,18 @@ var _ = Describe("vrf plugin", func() {
|
||||
// Add IP addresses for network reachability
|
||||
netlink.AddrAdd(link, &netlink.Addr{IPNet: ipv4})
|
||||
netlink.AddrAdd(link, &netlink.Addr{IPNet: ipv6})
|
||||
// Wait for the corresponding route to be addeded
|
||||
Eventually(func() bool {
|
||||
ipv6RouteDst := &net.IPNet{
|
||||
IP: ipv6.IP,
|
||||
Mask: net.IPMask{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
||||
}
|
||||
routes, _ := netlink.RouteListFiltered(netlink.FAMILY_ALL, &netlink.Route{
|
||||
Dst: ipv6RouteDst,
|
||||
Table: 0,
|
||||
}, netlink.RT_FILTER_DST|netlink.RT_FILTER_TABLE)
|
||||
return err == nil && len(routes) >= 1
|
||||
}, time.Second, 500*time.Millisecond).Should(BeTrue())
|
||||
|
||||
ipAddrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@ -362,6 +387,18 @@ var _ = Describe("vrf plugin", func() {
|
||||
// Add IP addresses for network reachability
|
||||
netlink.AddrAdd(link, &netlink.Addr{IPNet: ipv4})
|
||||
netlink.AddrAdd(link, &netlink.Addr{IPNet: ipv6})
|
||||
// Wait for the corresponding route to be addeded
|
||||
Eventually(func() bool {
|
||||
ipv6RouteDst := &net.IPNet{
|
||||
IP: ipv6.IP,
|
||||
Mask: net.IPMask{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
||||
}
|
||||
routes, _ := netlink.RouteListFiltered(netlink.FAMILY_ALL, &netlink.Route{
|
||||
Dst: ipv6RouteDst,
|
||||
Table: 0,
|
||||
}, netlink.RT_FILTER_DST|netlink.RT_FILTER_TABLE)
|
||||
return err == nil && len(routes) >= 1
|
||||
}, time.Second, 500*time.Millisecond).Should(BeTrue())
|
||||
|
||||
ipAddrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@ -437,10 +474,10 @@ var _ = Describe("vrf plugin", func() {
|
||||
defer GinkgoRecover()
|
||||
l, err := netlink.LinkByName(IF0Name)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = "testrbridge"
|
||||
br := &netlink.Bridge{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: "testrbridge",
|
||||
},
|
||||
LinkAttrs: linkAttrs,
|
||||
}
|
||||
err = netlink.LinkAdd(br)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
current "github.com/containernetworking/cni/pkg/types/100"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
"github.com/containernetworking/plugins/pkg/ipam"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
@ -150,10 +151,40 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
|
||||
func main() {
|
||||
// replace TODO with your plugin name
|
||||
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("TODO"))
|
||||
skel.PluginMainFuncs(skel.CNIFuncs{
|
||||
Add: cmdAdd,
|
||||
Check: cmdCheck,
|
||||
Del: cmdDel,
|
||||
Status: cmdStatus,
|
||||
/* FIXME GC */
|
||||
}, version.All, bv.BuildString("TODO"))
|
||||
}
|
||||
|
||||
func cmdCheck(_ *skel.CmdArgs) error {
|
||||
// TODO: implement
|
||||
return fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
// cmdStatus implements the STATUS command, which indicates whether or not
|
||||
// this plugin is able to accept ADD requests.
|
||||
//
|
||||
// If the plugin has external dependencies, such as a daemon
|
||||
// or chained ipam plugin, it should determine their status. If all is well,
|
||||
// and an ADD can be successfully processed, return nil
|
||||
func cmdStatus(args *skel.CmdArgs) error {
|
||||
conf, err := parseConfig(args.StdinData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = conf
|
||||
|
||||
// If this plugins delegates IPAM, ensure that IPAM is also running
|
||||
if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: implement STATUS here
|
||||
// e.g. querying an external deamon, or delegating STATUS to an IPAM plugin
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -1,49 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
set -xe
|
||||
|
||||
SRC_DIR="${SRC_DIR:-$PWD}"
|
||||
DOCKER="${DOCKER:-docker}"
|
||||
GOLANG="${GOLANG:-golang:1.22-alpine}"
|
||||
|
||||
TAG=$(git describe --tags --dirty)
|
||||
RELEASE_DIR=release-${TAG}
|
||||
|
||||
BUILDFLAGS="-ldflags '-extldflags -static -X github.com/containernetworking/plugins/pkg/utils/buildversion.BuildVersion=${TAG}'"
|
||||
|
||||
OUTPUT_DIR=bin
|
||||
|
||||
# Always clean first
|
||||
rm -Rf ${SRC_DIR}/${RELEASE_DIR}
|
||||
mkdir -p ${SRC_DIR}/${RELEASE_DIR}
|
||||
mkdir -p ${OUTPUT_DIR}
|
||||
|
||||
$DOCKER run -ti -v ${SRC_DIR}:/go/src/github.com/containernetworking/plugins:z --rm "${GOLANG}" \
|
||||
/bin/sh -xe -c "\
|
||||
apk --no-cache add bash tar;
|
||||
cd /go/src/github.com/containernetworking/plugins; umask 0022;
|
||||
|
||||
for arch in amd64 arm arm64 ppc64le s390x mips64le riscv64; do \
|
||||
rm -f ${OUTPUT_DIR}/*; \
|
||||
CGO_ENABLED=0 GOARCH=\$arch ./build_linux.sh ${BUILDFLAGS}; \
|
||||
for format in tgz; do \
|
||||
FILENAME=cni-plugins-linux-\$arch-${TAG}.\$format; \
|
||||
FILEPATH=${RELEASE_DIR}/\$FILENAME; \
|
||||
tar -C ${OUTPUT_DIR} --owner=0 --group=0 -caf \$FILEPATH .; \
|
||||
done; \
|
||||
done;
|
||||
|
||||
rm -rf ${OUTPUT_DIR}/*; \
|
||||
CGO_ENABLED=0 GOARCH=amd64 ./build_windows.sh ${BUILDFLAGS}; \
|
||||
for format in tgz; do \
|
||||
FILENAME=cni-plugins-windows-amd64-${TAG}.\$format; \
|
||||
FILEPATH=${RELEASE_DIR}/\$FILENAME; \
|
||||
tar -C ${OUTPUT_DIR} --owner=0 --group=0 -caf \$FILEPATH .; \
|
||||
done;
|
||||
|
||||
|
||||
cd ${RELEASE_DIR};
|
||||
for f in *.tgz; do sha1sum \$f > \$f.sha1; done;
|
||||
for f in *.tgz; do sha256sum \$f > \$f.sha256; done;
|
||||
for f in *.tgz; do sha512sum \$f > \$f.sha512; done;
|
||||
cd ..
|
||||
chown -R ${UID} ${OUTPUT_DIR} ${RELEASE_DIR}"
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Run CNI plugin tests.
|
||||
#
|
||||
@ -18,6 +18,23 @@ testrun() {
|
||||
sudo -E sh -c "umask 0; PATH=${GOPATH}/bin:$(pwd)/bin:${PATH} go test -race $*"
|
||||
}
|
||||
|
||||
ensure_sysctl() {
|
||||
local key
|
||||
local val
|
||||
local existing
|
||||
|
||||
key="$1"
|
||||
val="$2"
|
||||
existing="$(sysctl -ben "$key")"
|
||||
|
||||
sysctl -r
|
||||
|
||||
if [ "$val" -ne "$existing" ]; then
|
||||
echo "sudo sysctl -we '$key'='$val'"
|
||||
sudo sysctl -we "$key"="$val"
|
||||
fi
|
||||
}
|
||||
|
||||
COVERALLS=${COVERALLS:-""}
|
||||
|
||||
if [ -n "${COVERALLS}" ]; then
|
||||
@ -40,4 +57,7 @@ done
|
||||
|
||||
# Run the pkg/ns tests as non root user
|
||||
mkdir -p /tmp/cni-rootless
|
||||
ensure_sysctl kernel.unprivileged_userns_clone 1
|
||||
ensure_sysctl kernel.apparmor_restrict_unprivileged_userns 0
|
||||
|
||||
(export XDG_RUNTIME_DIR=/tmp/cni-rootless; cd pkg/ns/; unshare -rmn go test)
|
||||
|
24
vendor/github.com/Microsoft/hcsshim/hcn/hcn.go
generated
vendored
24
vendor/github.com/Microsoft/hcsshim/hcn/hcn.go
generated
vendored
@ -264,6 +264,18 @@ func SetPolicySupported() error {
|
||||
return platformDoesNotSupportError("SetPolicy")
|
||||
}
|
||||
|
||||
// ModifyLoadbalancerSupported returns an error if the HCN version does not support ModifyLoadbalancer.
|
||||
func ModifyLoadbalancerSupported() error {
|
||||
supported, err := GetCachedSupportedFeatures()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if supported.ModifyLoadbalancer {
|
||||
return nil
|
||||
}
|
||||
return platformDoesNotSupportError("ModifyLoadbalancer")
|
||||
}
|
||||
|
||||
// VxlanPortSupported returns an error if the HCN version does not support configuring the VXLAN TCP port.
|
||||
func VxlanPortSupported() error {
|
||||
supported, err := GetCachedSupportedFeatures()
|
||||
@ -324,6 +336,18 @@ func DisableHostPortSupported() error {
|
||||
return platformDoesNotSupportError("DisableHostPort")
|
||||
}
|
||||
|
||||
// AccelnetSupported returns an error if the HCN version does not support Accelnet Feature.
|
||||
func AccelnetSupported() error {
|
||||
supported, err := GetCachedSupportedFeatures()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if supported.Accelnet {
|
||||
return nil
|
||||
}
|
||||
return platformDoesNotSupportError("Accelnet")
|
||||
}
|
||||
|
||||
// RequestType are the different operations performed to settings.
|
||||
// Used to update the settings of Endpoint/Namespace objects.
|
||||
type RequestType string
|
||||
|
5
vendor/github.com/Microsoft/hcsshim/hcn/hcnerrors.go
generated
vendored
5
vendor/github.com/Microsoft/hcsshim/hcn/hcnerrors.go
generated
vendored
@ -52,6 +52,7 @@ type ErrorCode uint32
|
||||
const (
|
||||
ERROR_NOT_FOUND = ErrorCode(windows.ERROR_NOT_FOUND)
|
||||
HCN_E_PORT_ALREADY_EXISTS ErrorCode = ErrorCode(windows.HCN_E_PORT_ALREADY_EXISTS)
|
||||
HCN_E_NOTIMPL ErrorCode = ErrorCode(windows.E_NOTIMPL)
|
||||
)
|
||||
|
||||
type HcnError struct {
|
||||
@ -79,6 +80,10 @@ func IsPortAlreadyExistsError(err error) bool {
|
||||
return CheckErrorWithCode(err, HCN_E_PORT_ALREADY_EXISTS)
|
||||
}
|
||||
|
||||
func IsNotImplemented(err error) bool {
|
||||
return CheckErrorWithCode(err, HCN_E_NOTIMPL)
|
||||
}
|
||||
|
||||
func new(hr error, title string, rest string) error {
|
||||
err := &HcnError{}
|
||||
hcsError := hcserror.New(hr, title, rest)
|
||||
|
4
vendor/github.com/Microsoft/hcsshim/hcn/hcnglobals.go
generated
vendored
4
vendor/github.com/Microsoft/hcsshim/hcn/hcnglobals.go
generated
vendored
@ -87,6 +87,10 @@ var (
|
||||
|
||||
//HNS 15.1 allows support for DisableHostPort flag.
|
||||
DisableHostPortVersion = VersionRanges{VersionRange{MinVersion: Version{Major: 15, Minor: 1}, MaxVersion: Version{Major: math.MaxInt32, Minor: math.MaxInt32}}}
|
||||
// HNS 15.4 allows for Modify Loadbalancer support
|
||||
ModifyLoadbalancerVersion = VersionRanges{VersionRange{MinVersion: Version{Major: 15, Minor: 4}, MaxVersion: Version{Major: math.MaxInt32, Minor: math.MaxInt32}}}
|
||||
// HNS 15.4 allows for Accelnet support
|
||||
AccelnetVersion = VersionRanges{VersionRange{MinVersion: Version{Major: 15, Minor: 4}, MaxVersion: Version{Major: math.MaxInt32, Minor: math.MaxInt32}}}
|
||||
)
|
||||
|
||||
// GetGlobals returns the global properties of the HCN Service.
|
||||
|
60
vendor/github.com/Microsoft/hcsshim/hcn/hcnloadbalancer.go
generated
vendored
60
vendor/github.com/Microsoft/hcsshim/hcn/hcnloadbalancer.go
generated
vendored
@ -163,6 +163,49 @@ func createLoadBalancer(settings string) (*HostComputeLoadBalancer, error) {
|
||||
return &outputLoadBalancer, nil
|
||||
}
|
||||
|
||||
func updateLoadBalancer(loadbalancerId string, settings string) (*HostComputeLoadBalancer, error) {
|
||||
loadBalancerGuid, err := guid.FromString(loadbalancerId)
|
||||
if err != nil {
|
||||
return nil, errInvalidLoadBalancerID
|
||||
}
|
||||
// Update loadBalancer.
|
||||
var (
|
||||
loadBalancerHandle hcnLoadBalancer
|
||||
resultBuffer *uint16
|
||||
propertiesBuffer *uint16
|
||||
)
|
||||
hr := hcnOpenLoadBalancer(&loadBalancerGuid, &loadBalancerHandle, &resultBuffer)
|
||||
if err := checkForErrors("hcnOpenLoadBalancer", hr, resultBuffer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hr = hcnModifyLoadBalancer(loadBalancerHandle, settings, &resultBuffer)
|
||||
if err := checkForErrors("hcnModifyLoadBalancer", hr, resultBuffer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Query loadBalancer.
|
||||
hcnQuery := defaultQuery()
|
||||
query, err := json.Marshal(hcnQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hr = hcnQueryLoadBalancerProperties(loadBalancerHandle, string(query), &propertiesBuffer, &resultBuffer)
|
||||
if err := checkForErrors("hcnQueryLoadBalancerProperties", hr, resultBuffer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
properties := interop.ConvertAndFreeCoTaskMemString(propertiesBuffer)
|
||||
// Close loadBalancer.
|
||||
hr = hcnCloseLoadBalancer(loadBalancerHandle)
|
||||
if err := checkForErrors("hcnCloseLoadBalancer", hr, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Convert output to HostComputeLoadBalancer
|
||||
var outputLoadBalancer HostComputeLoadBalancer
|
||||
if err := json.Unmarshal([]byte(properties), &outputLoadBalancer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &outputLoadBalancer, nil
|
||||
}
|
||||
|
||||
func deleteLoadBalancer(loadBalancerID string) error {
|
||||
loadBalancerGUID, err := guid.FromString(loadBalancerID)
|
||||
if err != nil {
|
||||
@ -237,6 +280,23 @@ func (loadBalancer *HostComputeLoadBalancer) Create() (*HostComputeLoadBalancer,
|
||||
return loadBalancer, nil
|
||||
}
|
||||
|
||||
// Update Loadbalancer.
|
||||
func (loadBalancer *HostComputeLoadBalancer) Update(hnsLoadbalancerID string) (*HostComputeLoadBalancer, error) {
|
||||
logrus.Debugf("hcn::HostComputeLoadBalancer::Create id=%s", hnsLoadbalancerID)
|
||||
|
||||
jsonString, err := json.Marshal(loadBalancer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logrus.Debugf("hcn::HostComputeLoadBalancer::Update JSON: %s", jsonString)
|
||||
loadBalancer, hcnErr := updateLoadBalancer(hnsLoadbalancerID, string(jsonString))
|
||||
if hcnErr != nil {
|
||||
return nil, hcnErr
|
||||
}
|
||||
return loadBalancer, nil
|
||||
}
|
||||
|
||||
// Delete LoadBalancer.
|
||||
func (loadBalancer *HostComputeLoadBalancer) Delete() error {
|
||||
logrus.Debugf("hcn::HostComputeLoadBalancer::Delete id=%s", loadBalancer.Id)
|
||||
|
1
vendor/github.com/Microsoft/hcsshim/hcn/hcnnetwork.go
generated
vendored
1
vendor/github.com/Microsoft/hcsshim/hcn/hcnnetwork.go
generated
vendored
@ -73,6 +73,7 @@ const (
|
||||
None NetworkFlags = 0
|
||||
EnableNonPersistent NetworkFlags = 8
|
||||
DisableHostPort NetworkFlags = 1024
|
||||
EnableIov NetworkFlags = 8192
|
||||
)
|
||||
|
||||
// HostComputeNetwork represents a network
|
||||
|
9
vendor/github.com/Microsoft/hcsshim/hcn/hcnpolicy.go
generated
vendored
9
vendor/github.com/Microsoft/hcsshim/hcn/hcnpolicy.go
generated
vendored
@ -144,10 +144,11 @@ type QosPolicySetting struct {
|
||||
|
||||
// OutboundNatPolicySetting sets outbound Network Address Translation on an Endpoint.
|
||||
type OutboundNatPolicySetting struct {
|
||||
VirtualIP string `json:",omitempty"`
|
||||
Exceptions []string `json:",omitempty"`
|
||||
Destinations []string `json:",omitempty"`
|
||||
Flags NatFlags `json:",omitempty"`
|
||||
VirtualIP string `json:",omitempty"`
|
||||
Exceptions []string `json:",omitempty"`
|
||||
Destinations []string `json:",omitempty"`
|
||||
Flags NatFlags `json:",omitempty"`
|
||||
MaxPortPoolUsage uint16 `json:",omitempty"`
|
||||
}
|
||||
|
||||
// SDNRoutePolicySetting sets SDN Route on an Endpoint.
|
||||
|
4
vendor/github.com/Microsoft/hcsshim/hcn/hcnsupport.go
generated
vendored
4
vendor/github.com/Microsoft/hcsshim/hcn/hcnsupport.go
generated
vendored
@ -38,6 +38,8 @@ type SupportedFeatures struct {
|
||||
NetworkACL bool `json:"NetworkACL"`
|
||||
NestedIpSet bool `json:"NestedIpSet"`
|
||||
DisableHostPort bool `json:"DisableHostPort"`
|
||||
ModifyLoadbalancer bool `json:"ModifyLoadbalancer"`
|
||||
Accelnet bool `json:"Accelnet"`
|
||||
}
|
||||
|
||||
// AclFeatures are the supported ACL possibilities.
|
||||
@ -116,6 +118,8 @@ func getSupportedFeatures() (SupportedFeatures, error) {
|
||||
features.NetworkACL = isFeatureSupported(globals.Version, NetworkACLPolicyVersion)
|
||||
features.NestedIpSet = isFeatureSupported(globals.Version, NestedIpSetVersion)
|
||||
features.DisableHostPort = isFeatureSupported(globals.Version, DisableHostPortVersion)
|
||||
features.ModifyLoadbalancer = isFeatureSupported(globals.Version, ModifyLoadbalancerVersion)
|
||||
features.Accelnet = isFeatureSupported(globals.Version, AccelnetVersion)
|
||||
|
||||
log.L.WithFields(logrus.Fields{
|
||||
"version": globals.Version,
|
||||
|
46
vendor/github.com/Microsoft/hcsshim/hnsaccelnet.go
generated
vendored
Normal file
46
vendor/github.com/Microsoft/hcsshim/hnsaccelnet.go
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
//go:build windows
|
||||
|
||||
package hcsshim
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/Microsoft/hcsshim/internal/hns"
|
||||
)
|
||||
|
||||
// HNSNnvManagementMacAddress represents management mac address
|
||||
// which needs to be excluded from VF reassignment
|
||||
type HNSNnvManagementMacAddress = hns.HNSNnvManagementMacAddress
|
||||
|
||||
// HNSNnvManagementMacList represents a list of management
|
||||
// mac addresses for exclusion from VF reassignment
|
||||
type HNSNnvManagementMacList = hns.HNSNnvManagementMacList
|
||||
|
||||
var (
|
||||
ErrorEmptyMacAddressList = errors.New("management mac_address list is empty")
|
||||
)
|
||||
|
||||
// SetNnvManagementMacAddresses sets a list of
|
||||
// management mac addresses in hns for exclusion from VF reassignment.
|
||||
func SetNnvManagementMacAddresses(managementMacAddresses []string) (*HNSNnvManagementMacList, error) {
|
||||
if len(managementMacAddresses) == 0 {
|
||||
return nil, ErrorEmptyMacAddressList
|
||||
}
|
||||
nnvManagementMacList := &HNSNnvManagementMacList{}
|
||||
for _, mac := range managementMacAddresses {
|
||||
nnvManagementMacList.MacAddressList = append(nnvManagementMacList.MacAddressList, HNSNnvManagementMacAddress{MacAddress: mac})
|
||||
}
|
||||
return nnvManagementMacList.Set()
|
||||
}
|
||||
|
||||
// GetNnvManagementMacAddresses retrieves a list of
|
||||
// management mac addresses in hns for exclusion from VF reassignment.
|
||||
func GetNnvManagementMacAddresses() (*HNSNnvManagementMacList, error) {
|
||||
return hns.GetNnvManagementMacAddressList()
|
||||
}
|
||||
|
||||
// DeleteNnvManagementMacAddresses delete list of
|
||||
// management mac addresses in hns which are excluded from VF reassignment.
|
||||
func DeleteNnvManagementMacAddresses() (*HNSNnvManagementMacList, error) {
|
||||
return hns.DeleteNnvManagementMacAddressList()
|
||||
}
|
60
vendor/github.com/Microsoft/hcsshim/internal/hns/hnsaccelnet.go
generated
vendored
Normal file
60
vendor/github.com/Microsoft/hcsshim/internal/hns/hnsaccelnet.go
generated
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
//go:build windows
|
||||
|
||||
package hns
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// HNSNnvManagementMacAddress represents management mac address
|
||||
// which needs to be excluded from VF reassignment
|
||||
type HNSNnvManagementMacAddress struct {
|
||||
MacAddress string `json:",omitempty"`
|
||||
}
|
||||
|
||||
// HNSNnvManagementMacList represents a list of management
|
||||
// mac addresses for exclusion from VF reassignment
|
||||
type HNSNnvManagementMacList struct {
|
||||
MacAddressList []HNSNnvManagementMacAddress `json:",omitempty"`
|
||||
}
|
||||
|
||||
// HNSNnvManagementMacRequest makes a HNS call to modify/query NnvManagementMacList
|
||||
func HNSNnvManagementMacRequest(method, path, request string) (*HNSNnvManagementMacList, error) {
|
||||
nnvManagementMacList := &HNSNnvManagementMacList{}
|
||||
err := hnsCall(method, "/accelnet/"+path, request, &nnvManagementMacList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nnvManagementMacList, nil
|
||||
}
|
||||
|
||||
// Set ManagementMacAddressList by sending "POST" NnvManagementMacRequest to HNS.
|
||||
func (nnvManagementMacList *HNSNnvManagementMacList) Set() (*HNSNnvManagementMacList, error) {
|
||||
operation := "Set"
|
||||
title := "hcsshim::nnvManagementMacList::" + operation
|
||||
logrus.Debugf(title+" id=%s", nnvManagementMacList.MacAddressList)
|
||||
|
||||
jsonString, err := json.Marshal(nnvManagementMacList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return HNSNnvManagementMacRequest("POST", "", string(jsonString))
|
||||
}
|
||||
|
||||
// Get ManagementMacAddressList by sending "GET" NnvManagementMacRequest to HNS.
|
||||
func GetNnvManagementMacAddressList() (*HNSNnvManagementMacList, error) {
|
||||
operation := "Get"
|
||||
title := "hcsshim::nnvManagementMacList::" + operation
|
||||
logrus.Debugf(title)
|
||||
return HNSNnvManagementMacRequest("GET", "", "")
|
||||
}
|
||||
|
||||
// Delete ManagementMacAddressList by sending "DELETE" NnvManagementMacRequest to HNS.
|
||||
func DeleteNnvManagementMacAddressList() (*HNSNnvManagementMacList, error) {
|
||||
operation := "Delete"
|
||||
title := "hcsshim::nnvManagementMacList::" + operation
|
||||
logrus.Debugf(title)
|
||||
return HNSNnvManagementMacRequest("DELETE", "", "")
|
||||
}
|
23
vendor/github.com/Microsoft/hcsshim/internal/hns/hnsendpoint.go
generated
vendored
23
vendor/github.com/Microsoft/hcsshim/internal/hns/hnsendpoint.go
generated
vendored
@ -10,6 +10,28 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// EndpointState represents the states of an HNS Endpoint lifecycle.
|
||||
type EndpointState uint16
|
||||
|
||||
// EndpointState const
|
||||
// The lifecycle of an Endpoint goes through created, attached, AttachedSharing - endpoint is being shared with other containers,
|
||||
// detached, after being attached, degraded and finally destroyed.
|
||||
// Note: This attribute is used by calico to define stale containers and is dependent on HNS v1 api, if we move to HNS v2 api we will need
|
||||
// to update the current calico code and cordinate the change with calico. Reach out to Microsoft to facilate the change via HNS.
|
||||
const (
|
||||
Uninitialized EndpointState = iota
|
||||
Created EndpointState = 1
|
||||
Attached EndpointState = 2
|
||||
AttachedSharing EndpointState = 3
|
||||
Detached EndpointState = 4
|
||||
Degraded EndpointState = 5
|
||||
Destroyed EndpointState = 6
|
||||
)
|
||||
|
||||
func (es EndpointState) String() string {
|
||||
return [...]string{"Uninitialized", "Created", "Attached", "AttachedSharing", "Detached", "Degraded", "Destroyed"}[es]
|
||||
}
|
||||
|
||||
// HNSEndpoint represents a network endpoint in HNS
|
||||
type HNSEndpoint struct {
|
||||
Id string `json:"ID,omitempty"`
|
||||
@ -34,6 +56,7 @@ type HNSEndpoint struct {
|
||||
Namespace *Namespace `json:",omitempty"`
|
||||
EncapOverhead uint16 `json:",omitempty"`
|
||||
SharedContainers []string `json:",omitempty"`
|
||||
State EndpointState `json:",omitempty"`
|
||||
}
|
||||
|
||||
// SystemType represents the type of the system on which actions are done
|
||||
|
7
vendor/github.com/Microsoft/hcsshim/internal/hns/hnspolicy.go
generated
vendored
7
vendor/github.com/Microsoft/hcsshim/internal/hns/hnspolicy.go
generated
vendored
@ -57,9 +57,10 @@ type PaPolicy struct {
|
||||
|
||||
type OutboundNatPolicy struct {
|
||||
Policy
|
||||
VIP string `json:"VIP,omitempty"`
|
||||
Exceptions []string `json:"ExceptionList,omitempty"`
|
||||
Destinations []string `json:",omitempty"`
|
||||
VIP string `json:"VIP,omitempty"`
|
||||
Exceptions []string `json:"ExceptionList,omitempty"`
|
||||
Destinations []string `json:",omitempty"`
|
||||
MaxPortPoolUsage uint16 `json:",omitempty"`
|
||||
}
|
||||
|
||||
type ProxyPolicy struct {
|
||||
|
11
vendor/github.com/Microsoft/hcsshim/internal/jobobject/jobobject.go
generated
vendored
11
vendor/github.com/Microsoft/hcsshim/internal/jobobject/jobobject.go
generated
vendored
@ -188,7 +188,7 @@ func Open(ctx context.Context, options *Options) (_ *JobObject, err error) {
|
||||
return nil, winapi.RtlNtStatusToDosError(status)
|
||||
}
|
||||
} else {
|
||||
jobHandle, err = winapi.OpenJobObject(winapi.JOB_OBJECT_ALL_ACCESS, 0, unicodeJobName.Buffer)
|
||||
jobHandle, err = winapi.OpenJobObject(winapi.JOB_OBJECT_ALL_ACCESS, false, unicodeJobName.Buffer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -523,12 +523,9 @@ func (job *JobObject) ApplyFileBinding(root, target string, readOnly bool) error
|
||||
func isJobSilo(h windows.Handle) bool {
|
||||
// None of the information from the structure that this info class expects will be used, this is just used as
|
||||
// the call will fail if the job hasn't been upgraded to a silo so we can use this to tell when we open a job
|
||||
// if it's a silo or not. Because none of the info matters simply define a dummy struct with the size that the call
|
||||
// expects which is 16 bytes.
|
||||
type isSiloObj struct {
|
||||
_ [16]byte
|
||||
}
|
||||
var siloInfo isSiloObj
|
||||
// if it's a silo or not. We still need to define the struct layout as expected by Win32, else the struct
|
||||
// alignment might be different and the call will fail.
|
||||
var siloInfo winapi.SILOOBJECT_BASIC_INFORMATION
|
||||
err := winapi.QueryInformationJobObject(
|
||||
h,
|
||||
winapi.JobObjectSiloBasicInformation,
|
||||
|
2
vendor/github.com/Microsoft/hcsshim/internal/oc/errors.go
generated
vendored
2
vendor/github.com/Microsoft/hcsshim/internal/oc/errors.go
generated
vendored
@ -6,7 +6,7 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/containerd/errdefs"
|
||||
errdefs "github.com/containerd/errdefs/pkg/errgrpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
19
vendor/github.com/Microsoft/hcsshim/internal/winapi/jobobject.go
generated
vendored
19
vendor/github.com/Microsoft/hcsshim/internal/winapi/jobobject.go
generated
vendored
@ -28,7 +28,7 @@ const (
|
||||
// https://docs.microsoft.com/en-us/windows/win32/procthread/job-object-security-and-access-rights
|
||||
const (
|
||||
JOB_OBJECT_QUERY = 0x0004
|
||||
JOB_OBJECT_ALL_ACCESS = 0x1F001F
|
||||
JOB_OBJECT_ALL_ACCESS = 0x1F003F
|
||||
)
|
||||
|
||||
// IO limit flags
|
||||
@ -160,6 +160,21 @@ type JOBOBJECT_ASSOCIATE_COMPLETION_PORT struct {
|
||||
CompletionPort windows.Handle
|
||||
}
|
||||
|
||||
// typedef struct _SILOOBJECT_BASIC_INFORMATION {
|
||||
// DWORD SiloId;
|
||||
// DWORD SiloParentId;
|
||||
// DWORD NumberOfProcesses;
|
||||
// BOOLEAN IsInServerSilo;
|
||||
// BYTE Reserved[3];
|
||||
// } SILOOBJECT_BASIC_INFORMATION, *PSILOOBJECT_BASIC_INFORMATION;
|
||||
type SILOOBJECT_BASIC_INFORMATION struct {
|
||||
SiloID uint32
|
||||
SiloParentID uint32
|
||||
NumberOfProcesses uint32
|
||||
IsInServerSilo bool
|
||||
Reserved [3]uint8
|
||||
}
|
||||
|
||||
// BOOL IsProcessInJob(
|
||||
// HANDLE ProcessHandle,
|
||||
// HANDLE JobHandle,
|
||||
@ -184,7 +199,7 @@ type JOBOBJECT_ASSOCIATE_COMPLETION_PORT struct {
|
||||
// LPCWSTR lpName
|
||||
// );
|
||||
//
|
||||
//sys OpenJobObject(desiredAccess uint32, inheritHandle int32, lpName *uint16) (handle windows.Handle, err error) = kernel32.OpenJobObjectW
|
||||
//sys OpenJobObject(desiredAccess uint32, inheritHandle bool, lpName *uint16) (handle windows.Handle, err error) = kernel32.OpenJobObjectW
|
||||
|
||||
// DWORD SetIoRateControlInformationJobObject(
|
||||
// HANDLE hJob,
|
||||
|
8
vendor/github.com/Microsoft/hcsshim/internal/winapi/zsyscall_windows.go
generated
vendored
8
vendor/github.com/Microsoft/hcsshim/internal/winapi/zsyscall_windows.go
generated
vendored
@ -470,8 +470,12 @@ func LocalFree(ptr uintptr) {
|
||||
return
|
||||
}
|
||||
|
||||
func OpenJobObject(desiredAccess uint32, inheritHandle int32, lpName *uint16) (handle windows.Handle, err error) {
|
||||
r0, _, e1 := syscall.SyscallN(procOpenJobObjectW.Addr(), uintptr(desiredAccess), uintptr(inheritHandle), uintptr(unsafe.Pointer(lpName)))
|
||||
func OpenJobObject(desiredAccess uint32, inheritHandle bool, lpName *uint16) (handle windows.Handle, err error) {
|
||||
var _p0 uint32
|
||||
if inheritHandle {
|
||||
_p0 = 1
|
||||
}
|
||||
r0, _, e1 := syscall.SyscallN(procOpenJobObjectW.Addr(), uintptr(desiredAccess), uintptr(_p0), uintptr(unsafe.Pointer(lpName)))
|
||||
handle = windows.Handle(r0)
|
||||
if handle == 0 {
|
||||
err = errnoErr(e1)
|
||||
|
439
vendor/github.com/containerd/errdefs/errors.go
generated
vendored
439
vendor/github.com/containerd/errdefs/errors.go
generated
vendored
@ -21,9 +21,6 @@
|
||||
//
|
||||
// To detect an error class, use the IsXXX functions to tell whether an error
|
||||
// is of a certain type.
|
||||
//
|
||||
// The functions ToGRPC and FromGRPC can be used to map server-side and
|
||||
// client-side errors to the correct types.
|
||||
package errdefs
|
||||
|
||||
import (
|
||||
@ -36,57 +33,411 @@ import (
|
||||
// Packages should return errors of these types when they want to instruct a
|
||||
// client to take a particular action.
|
||||
//
|
||||
// For the most part, we just try to provide local grpc errors. Most conditions
|
||||
// map very well to those defined by grpc.
|
||||
// These errors map closely to grpc errors.
|
||||
var (
|
||||
ErrUnknown = errors.New("unknown") // used internally to represent a missed mapping.
|
||||
ErrInvalidArgument = errors.New("invalid argument")
|
||||
ErrNotFound = errors.New("not found")
|
||||
ErrAlreadyExists = errors.New("already exists")
|
||||
ErrFailedPrecondition = errors.New("failed precondition")
|
||||
ErrUnavailable = errors.New("unavailable")
|
||||
ErrNotImplemented = errors.New("not implemented") // represents not supported and unimplemented
|
||||
ErrUnknown = errUnknown{}
|
||||
ErrInvalidArgument = errInvalidArgument{}
|
||||
ErrNotFound = errNotFound{}
|
||||
ErrAlreadyExists = errAlreadyExists{}
|
||||
ErrPermissionDenied = errPermissionDenied{}
|
||||
ErrResourceExhausted = errResourceExhausted{}
|
||||
ErrFailedPrecondition = errFailedPrecondition{}
|
||||
ErrConflict = errConflict{}
|
||||
ErrNotModified = errNotModified{}
|
||||
ErrAborted = errAborted{}
|
||||
ErrOutOfRange = errOutOfRange{}
|
||||
ErrNotImplemented = errNotImplemented{}
|
||||
ErrInternal = errInternal{}
|
||||
ErrUnavailable = errUnavailable{}
|
||||
ErrDataLoss = errDataLoss{}
|
||||
ErrUnauthenticated = errUnauthorized{}
|
||||
)
|
||||
|
||||
// IsInvalidArgument returns true if the error is due to an invalid argument
|
||||
func IsInvalidArgument(err error) bool {
|
||||
return errors.Is(err, ErrInvalidArgument)
|
||||
}
|
||||
|
||||
// IsNotFound returns true if the error is due to a missing object
|
||||
func IsNotFound(err error) bool {
|
||||
return errors.Is(err, ErrNotFound)
|
||||
}
|
||||
|
||||
// IsAlreadyExists returns true if the error is due to an already existing
|
||||
// metadata item
|
||||
func IsAlreadyExists(err error) bool {
|
||||
return errors.Is(err, ErrAlreadyExists)
|
||||
}
|
||||
|
||||
// IsFailedPrecondition returns true if an operation could not proceed to the
|
||||
// lack of a particular condition
|
||||
func IsFailedPrecondition(err error) bool {
|
||||
return errors.Is(err, ErrFailedPrecondition)
|
||||
}
|
||||
|
||||
// IsUnavailable returns true if the error is due to a resource being unavailable
|
||||
func IsUnavailable(err error) bool {
|
||||
return errors.Is(err, ErrUnavailable)
|
||||
}
|
||||
|
||||
// IsNotImplemented returns true if the error is due to not being implemented
|
||||
func IsNotImplemented(err error) bool {
|
||||
return errors.Is(err, ErrNotImplemented)
|
||||
// cancelled maps to Moby's "ErrCancelled"
|
||||
type cancelled interface {
|
||||
Cancelled()
|
||||
}
|
||||
|
||||
// IsCanceled returns true if the error is due to `context.Canceled`.
|
||||
func IsCanceled(err error) bool {
|
||||
return errors.Is(err, context.Canceled)
|
||||
return errors.Is(err, context.Canceled) || isInterface[cancelled](err)
|
||||
}
|
||||
|
||||
type errUnknown struct{}
|
||||
|
||||
func (errUnknown) Error() string { return "unknown" }
|
||||
|
||||
func (errUnknown) Unknown() {}
|
||||
|
||||
func (e errUnknown) WithMessage(msg string) error {
|
||||
return customMessage{e, msg}
|
||||
}
|
||||
|
||||
// unknown maps to Moby's "ErrUnknown"
|
||||
type unknown interface {
|
||||
Unknown()
|
||||
}
|
||||
|
||||
// IsUnknown returns true if the error is due to an unknown error,
|
||||
// unhandled condition or unexpected response.
|
||||
func IsUnknown(err error) bool {
|
||||
return errors.Is(err, errUnknown{}) || isInterface[unknown](err)
|
||||
}
|
||||
|
||||
type errInvalidArgument struct{}
|
||||
|
||||
func (errInvalidArgument) Error() string { return "invalid argument" }
|
||||
|
||||
func (errInvalidArgument) InvalidParameter() {}
|
||||
|
||||
func (e errInvalidArgument) WithMessage(msg string) error {
|
||||
return customMessage{e, msg}
|
||||
}
|
||||
|
||||
// invalidParameter maps to Moby's "ErrInvalidParameter"
|
||||
type invalidParameter interface {
|
||||
InvalidParameter()
|
||||
}
|
||||
|
||||
// IsInvalidArgument returns true if the error is due to an invalid argument
|
||||
func IsInvalidArgument(err error) bool {
|
||||
return errors.Is(err, ErrInvalidArgument) || isInterface[invalidParameter](err)
|
||||
}
|
||||
|
||||
// deadlineExceed maps to Moby's "ErrDeadline"
|
||||
type deadlineExceeded interface {
|
||||
DeadlineExceeded()
|
||||
}
|
||||
|
||||
// IsDeadlineExceeded returns true if the error is due to
|
||||
// `context.DeadlineExceeded`.
|
||||
func IsDeadlineExceeded(err error) bool {
|
||||
return errors.Is(err, context.DeadlineExceeded)
|
||||
return errors.Is(err, context.DeadlineExceeded) || isInterface[deadlineExceeded](err)
|
||||
}
|
||||
|
||||
type errNotFound struct{}
|
||||
|
||||
func (errNotFound) Error() string { return "not found" }
|
||||
|
||||
func (errNotFound) NotFound() {}
|
||||
|
||||
func (e errNotFound) WithMessage(msg string) error {
|
||||
return customMessage{e, msg}
|
||||
}
|
||||
|
||||
// notFound maps to Moby's "ErrNotFound"
|
||||
type notFound interface {
|
||||
NotFound()
|
||||
}
|
||||
|
||||
// IsNotFound returns true if the error is due to a missing object
|
||||
func IsNotFound(err error) bool {
|
||||
return errors.Is(err, ErrNotFound) || isInterface[notFound](err)
|
||||
}
|
||||
|
||||
type errAlreadyExists struct{}
|
||||
|
||||
func (errAlreadyExists) Error() string { return "already exists" }
|
||||
|
||||
func (errAlreadyExists) AlreadyExists() {}
|
||||
|
||||
func (e errAlreadyExists) WithMessage(msg string) error {
|
||||
return customMessage{e, msg}
|
||||
}
|
||||
|
||||
type alreadyExists interface {
|
||||
AlreadyExists()
|
||||
}
|
||||
|
||||
// IsAlreadyExists returns true if the error is due to an already existing
|
||||
// metadata item
|
||||
func IsAlreadyExists(err error) bool {
|
||||
return errors.Is(err, ErrAlreadyExists) || isInterface[alreadyExists](err)
|
||||
}
|
||||
|
||||
type errPermissionDenied struct{}
|
||||
|
||||
func (errPermissionDenied) Error() string { return "permission denied" }
|
||||
|
||||
func (errPermissionDenied) Forbidden() {}
|
||||
|
||||
func (e errPermissionDenied) WithMessage(msg string) error {
|
||||
return customMessage{e, msg}
|
||||
}
|
||||
|
||||
// forbidden maps to Moby's "ErrForbidden"
|
||||
type forbidden interface {
|
||||
Forbidden()
|
||||
}
|
||||
|
||||
// IsPermissionDenied returns true if the error is due to permission denied
|
||||
// or forbidden (403) response
|
||||
func IsPermissionDenied(err error) bool {
|
||||
return errors.Is(err, ErrPermissionDenied) || isInterface[forbidden](err)
|
||||
}
|
||||
|
||||
type errResourceExhausted struct{}
|
||||
|
||||
func (errResourceExhausted) Error() string { return "resource exhausted" }
|
||||
|
||||
func (errResourceExhausted) ResourceExhausted() {}
|
||||
|
||||
func (e errResourceExhausted) WithMessage(msg string) error {
|
||||
return customMessage{e, msg}
|
||||
}
|
||||
|
||||
type resourceExhausted interface {
|
||||
ResourceExhausted()
|
||||
}
|
||||
|
||||
// IsResourceExhausted returns true if the error is due to
|
||||
// a lack of resources or too many attempts.
|
||||
func IsResourceExhausted(err error) bool {
|
||||
return errors.Is(err, errResourceExhausted{}) || isInterface[resourceExhausted](err)
|
||||
}
|
||||
|
||||
type errFailedPrecondition struct{}
|
||||
|
||||
func (e errFailedPrecondition) Error() string { return "failed precondition" }
|
||||
|
||||
func (errFailedPrecondition) FailedPrecondition() {}
|
||||
|
||||
func (e errFailedPrecondition) WithMessage(msg string) error {
|
||||
return customMessage{e, msg}
|
||||
}
|
||||
|
||||
type failedPrecondition interface {
|
||||
FailedPrecondition()
|
||||
}
|
||||
|
||||
// IsFailedPrecondition returns true if an operation could not proceed due to
|
||||
// the lack of a particular condition
|
||||
func IsFailedPrecondition(err error) bool {
|
||||
return errors.Is(err, errFailedPrecondition{}) || isInterface[failedPrecondition](err)
|
||||
}
|
||||
|
||||
type errConflict struct{}
|
||||
|
||||
func (errConflict) Error() string { return "conflict" }
|
||||
|
||||
func (errConflict) Conflict() {}
|
||||
|
||||
func (e errConflict) WithMessage(msg string) error {
|
||||
return customMessage{e, msg}
|
||||
}
|
||||
|
||||
// conflict maps to Moby's "ErrConflict"
|
||||
type conflict interface {
|
||||
Conflict()
|
||||
}
|
||||
|
||||
// IsConflict returns true if an operation could not proceed due to
|
||||
// a conflict.
|
||||
func IsConflict(err error) bool {
|
||||
return errors.Is(err, errConflict{}) || isInterface[conflict](err)
|
||||
}
|
||||
|
||||
type errNotModified struct{}
|
||||
|
||||
func (errNotModified) Error() string { return "not modified" }
|
||||
|
||||
func (errNotModified) NotModified() {}
|
||||
|
||||
func (e errNotModified) WithMessage(msg string) error {
|
||||
return customMessage{e, msg}
|
||||
}
|
||||
|
||||
// notModified maps to Moby's "ErrNotModified"
|
||||
type notModified interface {
|
||||
NotModified()
|
||||
}
|
||||
|
||||
// IsNotModified returns true if an operation could not proceed due
|
||||
// to an object not modified from a previous state.
|
||||
func IsNotModified(err error) bool {
|
||||
return errors.Is(err, errNotModified{}) || isInterface[notModified](err)
|
||||
}
|
||||
|
||||
type errAborted struct{}
|
||||
|
||||
func (errAborted) Error() string { return "aborted" }
|
||||
|
||||
func (errAborted) Aborted() {}
|
||||
|
||||
func (e errAborted) WithMessage(msg string) error {
|
||||
return customMessage{e, msg}
|
||||
}
|
||||
|
||||
type aborted interface {
|
||||
Aborted()
|
||||
}
|
||||
|
||||
// IsAborted returns true if an operation was aborted.
|
||||
func IsAborted(err error) bool {
|
||||
return errors.Is(err, errAborted{}) || isInterface[aborted](err)
|
||||
}
|
||||
|
||||
type errOutOfRange struct{}
|
||||
|
||||
func (errOutOfRange) Error() string { return "out of range" }
|
||||
|
||||
func (errOutOfRange) OutOfRange() {}
|
||||
|
||||
func (e errOutOfRange) WithMessage(msg string) error {
|
||||
return customMessage{e, msg}
|
||||
}
|
||||
|
||||
type outOfRange interface {
|
||||
OutOfRange()
|
||||
}
|
||||
|
||||
// IsOutOfRange returns true if an operation could not proceed due
|
||||
// to data being out of the expected range.
|
||||
func IsOutOfRange(err error) bool {
|
||||
return errors.Is(err, errOutOfRange{}) || isInterface[outOfRange](err)
|
||||
}
|
||||
|
||||
type errNotImplemented struct{}
|
||||
|
||||
func (errNotImplemented) Error() string { return "not implemented" }
|
||||
|
||||
func (errNotImplemented) NotImplemented() {}
|
||||
|
||||
func (e errNotImplemented) WithMessage(msg string) error {
|
||||
return customMessage{e, msg}
|
||||
}
|
||||
|
||||
// notImplemented maps to Moby's "ErrNotImplemented"
|
||||
type notImplemented interface {
|
||||
NotImplemented()
|
||||
}
|
||||
|
||||
// IsNotImplemented returns true if the error is due to not being implemented
|
||||
func IsNotImplemented(err error) bool {
|
||||
return errors.Is(err, errNotImplemented{}) || isInterface[notImplemented](err)
|
||||
}
|
||||
|
||||
type errInternal struct{}
|
||||
|
||||
func (errInternal) Error() string { return "internal" }
|
||||
|
||||
func (errInternal) System() {}
|
||||
|
||||
func (e errInternal) WithMessage(msg string) error {
|
||||
return customMessage{e, msg}
|
||||
}
|
||||
|
||||
// system maps to Moby's "ErrSystem"
|
||||
type system interface {
|
||||
System()
|
||||
}
|
||||
|
||||
// IsInternal returns true if the error returns to an internal or system error
|
||||
func IsInternal(err error) bool {
|
||||
return errors.Is(err, errInternal{}) || isInterface[system](err)
|
||||
}
|
||||
|
||||
type errUnavailable struct{}
|
||||
|
||||
func (errUnavailable) Error() string { return "unavailable" }
|
||||
|
||||
func (errUnavailable) Unavailable() {}
|
||||
|
||||
func (e errUnavailable) WithMessage(msg string) error {
|
||||
return customMessage{e, msg}
|
||||
}
|
||||
|
||||
// unavailable maps to Moby's "ErrUnavailable"
|
||||
type unavailable interface {
|
||||
Unavailable()
|
||||
}
|
||||
|
||||
// IsUnavailable returns true if the error is due to a resource being unavailable
|
||||
func IsUnavailable(err error) bool {
|
||||
return errors.Is(err, errUnavailable{}) || isInterface[unavailable](err)
|
||||
}
|
||||
|
||||
type errDataLoss struct{}
|
||||
|
||||
func (errDataLoss) Error() string { return "data loss" }
|
||||
|
||||
func (errDataLoss) DataLoss() {}
|
||||
|
||||
func (e errDataLoss) WithMessage(msg string) error {
|
||||
return customMessage{e, msg}
|
||||
}
|
||||
|
||||
// dataLoss maps to Moby's "ErrDataLoss"
|
||||
type dataLoss interface {
|
||||
DataLoss()
|
||||
}
|
||||
|
||||
// IsDataLoss returns true if data during an operation was lost or corrupted
|
||||
func IsDataLoss(err error) bool {
|
||||
return errors.Is(err, errDataLoss{}) || isInterface[dataLoss](err)
|
||||
}
|
||||
|
||||
type errUnauthorized struct{}
|
||||
|
||||
func (errUnauthorized) Error() string { return "unauthorized" }
|
||||
|
||||
func (errUnauthorized) Unauthorized() {}
|
||||
|
||||
func (e errUnauthorized) WithMessage(msg string) error {
|
||||
return customMessage{e, msg}
|
||||
}
|
||||
|
||||
// unauthorized maps to Moby's "ErrUnauthorized"
|
||||
type unauthorized interface {
|
||||
Unauthorized()
|
||||
}
|
||||
|
||||
// IsUnauthorized returns true if the error indicates that the user was
|
||||
// unauthenticated or unauthorized.
|
||||
func IsUnauthorized(err error) bool {
|
||||
return errors.Is(err, errUnauthorized{}) || isInterface[unauthorized](err)
|
||||
}
|
||||
|
||||
func isInterface[T any](err error) bool {
|
||||
for {
|
||||
switch x := err.(type) {
|
||||
case T:
|
||||
return true
|
||||
case customMessage:
|
||||
err = x.err
|
||||
case interface{ Unwrap() error }:
|
||||
err = x.Unwrap()
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
case interface{ Unwrap() []error }:
|
||||
for _, err := range x.Unwrap() {
|
||||
if isInterface[T](err) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// customMessage is used to provide a defined error with a custom message.
|
||||
// The message is not wrapped but can be compared by the `Is(error) bool` interface.
|
||||
type customMessage struct {
|
||||
err error
|
||||
msg string
|
||||
}
|
||||
|
||||
func (c customMessage) Is(err error) bool {
|
||||
return c.err == err
|
||||
}
|
||||
|
||||
func (c customMessage) As(target any) bool {
|
||||
return errors.As(c.err, target)
|
||||
}
|
||||
|
||||
func (c customMessage) Error() string {
|
||||
return c.msg
|
||||
}
|
||||
|
147
vendor/github.com/containerd/errdefs/grpc.go
generated
vendored
147
vendor/github.com/containerd/errdefs/grpc.go
generated
vendored
@ -1,147 +0,0 @@
|
||||
/*
|
||||
Copyright The containerd 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 errdefs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// ToGRPC will attempt to map the backend containerd error into a grpc error,
|
||||
// using the original error message as a description.
|
||||
//
|
||||
// Further information may be extracted from certain errors depending on their
|
||||
// type.
|
||||
//
|
||||
// If the error is unmapped, the original error will be returned to be handled
|
||||
// by the regular grpc error handling stack.
|
||||
func ToGRPC(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if isGRPCError(err) {
|
||||
// error has already been mapped to grpc
|
||||
return err
|
||||
}
|
||||
|
||||
switch {
|
||||
case IsInvalidArgument(err):
|
||||
return status.Errorf(codes.InvalidArgument, err.Error())
|
||||
case IsNotFound(err):
|
||||
return status.Errorf(codes.NotFound, err.Error())
|
||||
case IsAlreadyExists(err):
|
||||
return status.Errorf(codes.AlreadyExists, err.Error())
|
||||
case IsFailedPrecondition(err):
|
||||
return status.Errorf(codes.FailedPrecondition, err.Error())
|
||||
case IsUnavailable(err):
|
||||
return status.Errorf(codes.Unavailable, err.Error())
|
||||
case IsNotImplemented(err):
|
||||
return status.Errorf(codes.Unimplemented, err.Error())
|
||||
case IsCanceled(err):
|
||||
return status.Errorf(codes.Canceled, err.Error())
|
||||
case IsDeadlineExceeded(err):
|
||||
return status.Errorf(codes.DeadlineExceeded, err.Error())
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// ToGRPCf maps the error to grpc error codes, assembling the formatting string
|
||||
// and combining it with the target error string.
|
||||
//
|
||||
// This is equivalent to errdefs.ToGRPC(fmt.Errorf("%s: %w", fmt.Sprintf(format, args...), err))
|
||||
func ToGRPCf(err error, format string, args ...interface{}) error {
|
||||
return ToGRPC(fmt.Errorf("%s: %w", fmt.Sprintf(format, args...), err))
|
||||
}
|
||||
|
||||
// FromGRPC returns the underlying error from a grpc service based on the grpc error code
|
||||
func FromGRPC(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var cls error // divide these into error classes, becomes the cause
|
||||
|
||||
switch code(err) {
|
||||
case codes.InvalidArgument:
|
||||
cls = ErrInvalidArgument
|
||||
case codes.AlreadyExists:
|
||||
cls = ErrAlreadyExists
|
||||
case codes.NotFound:
|
||||
cls = ErrNotFound
|
||||
case codes.Unavailable:
|
||||
cls = ErrUnavailable
|
||||
case codes.FailedPrecondition:
|
||||
cls = ErrFailedPrecondition
|
||||
case codes.Unimplemented:
|
||||
cls = ErrNotImplemented
|
||||
case codes.Canceled:
|
||||
cls = context.Canceled
|
||||
case codes.DeadlineExceeded:
|
||||
cls = context.DeadlineExceeded
|
||||
default:
|
||||
cls = ErrUnknown
|
||||
}
|
||||
|
||||
msg := rebaseMessage(cls, err)
|
||||
if msg != "" {
|
||||
err = fmt.Errorf("%s: %w", msg, cls)
|
||||
} else {
|
||||
err = cls
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// rebaseMessage removes the repeats for an error at the end of an error
|
||||
// string. This will happen when taking an error over grpc then remapping it.
|
||||
//
|
||||
// Effectively, we just remove the string of cls from the end of err if it
|
||||
// appears there.
|
||||
func rebaseMessage(cls error, err error) string {
|
||||
desc := errDesc(err)
|
||||
clss := cls.Error()
|
||||
if desc == clss {
|
||||
return ""
|
||||
}
|
||||
|
||||
return strings.TrimSuffix(desc, ": "+clss)
|
||||
}
|
||||
|
||||
func isGRPCError(err error) bool {
|
||||
_, ok := status.FromError(err)
|
||||
return ok
|
||||
}
|
||||
|
||||
func code(err error) codes.Code {
|
||||
if s, ok := status.FromError(err); ok {
|
||||
return s.Code()
|
||||
}
|
||||
return codes.Unknown
|
||||
}
|
||||
|
||||
func errDesc(err error) string {
|
||||
if s, ok := status.FromError(err); ok {
|
||||
return s.Message()
|
||||
}
|
||||
return err.Error()
|
||||
}
|
191
vendor/github.com/containerd/errdefs/pkg/LICENSE
generated
vendored
Normal file
191
vendor/github.com/containerd/errdefs/pkg/LICENSE
generated
vendored
Normal file
@ -0,0 +1,191 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
https://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright The containerd 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
|
||||
|
||||
https://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.
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user