Compare commits

..

5 Commits

Author SHA1 Message Date
19bb4a15bb release script: don't run the tests
For two reasons:

1. They're not functional within rkt
2. They rebuild the binaries dynamically
2016-05-06 17:50:01 +02:00
a721ce6bbf build/release: link all release binaries statically 2016-04-28 22:40:59 +02:00
5ab94d6e50 scripts: build static releases and create an ACI
* use SHA1 instead of MD5
2016-04-23 00:53:20 +02:00
2ea9379fa4 travis: don't go get vet 2016-04-22 20:13:37 +02:00
cf43d2f78f scripts: add "release with rkt"
This script uses rkt and a fedora image to build release tarballs.
2016-04-22 19:36:38 +02:00
1653 changed files with 37046 additions and 462198 deletions

View File

@ -1,7 +0,0 @@
FROM alpine:3.10
RUN apk add --no-cache curl jq
COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

View File

@ -1,11 +0,0 @@
name: 'Re-Test'
description: 'Re-Runs the last workflow for a PR'
inputs:
token:
description: 'GitHub API Token'
required: true
runs:
using: 'docker'
image: 'Dockerfile'
env:
GITHUB_TOKEN: ${{ inputs.token }}

View File

@ -1,45 +0,0 @@
#!/bin/sh
set -ex
if ! jq -e '.issue.pull_request' ${GITHUB_EVENT_PATH}; then
echo "Not a PR... Exiting."
exit 0
fi
if [ "$(jq -r '.comment.body' ${GITHUB_EVENT_PATH})" != "/retest" ]; then
echo "Nothing to do... Exiting."
exit 0
fi
PR_URL=$(jq -r '.issue.pull_request.url' ${GITHUB_EVENT_PATH})
curl --request GET \
--url "${PR_URL}" \
--header "authorization: Bearer ${GITHUB_TOKEN}" \
--header "content-type: application/json" > pr.json
ACTOR=$(jq -r '.user.login' pr.json)
BRANCH=$(jq -r '.head.ref' pr.json)
curl --request GET \
--url "https://api.github.com/repos/${GITHUB_REPOSITORY}/actions/runs?event=pull_request&actor=${ACTOR}&branch=${BRANCH}" \
--header "authorization: Bearer ${GITHUB_TOKEN}" \
--header "content-type: application/json" | jq '.workflow_runs | max_by(.run_number)' > run.json
RERUN_URL=$(jq -r '.rerun_url' run.json)
curl --request POST \
--url "${RERUN_URL}" \
--header "authorization: Bearer ${GITHUB_TOKEN}" \
--header "content-type: application/json"
REACTION_URL="$(jq -r '.comment.url' ${GITHUB_EVENT_PATH})/reactions"
curl --request POST \
--url "${REACTION_URL}" \
--header "authorization: Bearer ${GITHUB_TOKEN}" \
--header "accept: application/vnd.github.squirrel-girl-preview+json" \
--header "content-type: application/json" \
--data '{ "content" : "rocket" }'

View File

@ -1,17 +0,0 @@
name: commands
on:
issue_comment:
types: [created]
jobs:
retest:
if: github.repository == 'containernetworking/plugins'
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v2
- name: Re-Test Action
uses: ./.github/actions/retest-action
with:
token: ${{ secrets.REPO_ACCESS_TOKEN }}

View File

@ -1,77 +0,0 @@
---
name: test
on: ["push", "pull_request"]
env:
GO_VERSION: "1.16"
LINUX_ARCHES: "amd64 386 arm arm64 s390x mips64le ppc64le"
jobs:
build:
name: Build all linux architectures
runs-on: ubuntu-latest
steps:
- name: setup go
uses: actions/setup-go@v2
with:
go-version: ${{ env.GO_VERSION }}
- uses: actions/checkout@v2
- name: Build on all supported architectures
run: |
set -e
for arch in ${LINUX_ARCHES}; do
echo "Building for arch $arch"
GOARCH=$arch ./build_linux.sh
rm bin/*
done
test-linux:
name: Run tests on Linux amd64
runs-on: ubuntu-latest
steps:
- name: Install kernel module
run: |
sudo apt-get update
sudo apt-get install linux-modules-extra-$(uname -r)
- name: setup go
uses: actions/setup-go@v2
with:
go-version: ${{ env.GO_VERSION }}
- name: Set up Go for root
run: |
sudo ln -sf `which go` `sudo which go` || true
sudo go version
- uses: actions/checkout@v2
- name: Install test binaries
env:
GO111MODULE: off
run: |
go get github.com/containernetworking/cni/cnitool
go get github.com/mattn/goveralls
go get github.com/modocache/gover
- name: test
run: PATH=$PATH:$(go env GOPATH)/bin COVERALLS=1 ./test_linux.sh
- name: Send coverage to coveralls
env:
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PATH=$PATH:$(go env GOPATH)/bin
gover
goveralls -coverprofile=gover.coverprofile -service=github
test-win:
name: Build and run tests on Windows
runs-on: windows-latest
steps:
- name: setup go
uses: actions/setup-go@v2
with:
go-version: ${{ env.GO_VERSION }}
- uses: actions/checkout@v2
- name: test
run: bash ./test_windows.sh

29
.gitignore vendored
View File

@ -1,30 +1,3 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
bin/
gopath/
.vagrant
.idea
/release-*
*.sw[ponm]

29
.travis.yml Normal file
View File

@ -0,0 +1,29 @@
language: go
sudo: required
dist: trusty
go:
- 1.5.3
- 1.6
- tip
matrix:
allow_failures:
- go: tip
env:
global:
- TOOLS_CMD=golang.org/x/tools/cmd
- PATH=$GOROOT/bin:$PATH
- GO15VENDOREXPERIMENT=1
install:
- go get ${TOOLS_CMD}/cover
- go get github.com/modocache/gover
- go get github.com/mattn/goveralls
script:
- ./test
notifications:
email: false

View File

@ -1,11 +1,11 @@
# How to Contribute
CNI is [Apache 2.0 licensed](LICENSE) and accepts contributions via GitHub
cni is [Apache 2.0 licensed](LICENSE) and accepts contributions via GitHub
pull requests. This document outlines some of the conventions on development
workflow, commit message formatting, contact points and other resources to make
it easier to get your contribution accepted.
We gratefully welcome improvements to documentation as well as to code.
For more information on the policy for accepting contributions, see [POLICY](POLICY.md)
# Certificate of Origin
@ -16,9 +16,9 @@ contribution. See the [DCO](DCO) file for details.
# Email and Chat
The project uses the the cni-dev email list and IRC chat:
The project uses the the cni-dev email list and #appc on Freenode for chat:
- Email: [cni-dev](https://groups.google.com/forum/#!forum/cni-dev)
- IRC: #[containernetworking](irc://irc.freenode.org:6667/#containernetworking) channel on freenode.org
- IRC: #[appc](irc://irc.freenode.org:6667/#appc) IRC channel on freenode.org
Please avoid emailing maintainers found in the MAINTAINERS file directly. They
are very busy and read the mailing lists.
@ -26,76 +26,21 @@ are very busy and read the mailing lists.
## Getting Started
- Fork the repository on GitHub
- Play with the project, submit bugs, submit pull requests!
- Read the [README](README.md) for build and test instructions
- Play with the project, submit bugs, submit patches!
## Contribution Flow
## Building
This is a rough outline of what a contributor's workflow looks like:
Each plugin is compiled simply with `go build`. Two scripts, `build_linux.sh` and `build_windows.sh`,
are supplied which will build all the plugins for their respective OS.
## Contribution workflow
This is a rough outline of how to prepare a contribution:
- Create a topic branch from where you want to base your work (usually branched from master).
- Create a topic branch from where you want to base your work (usually master).
- Make commits of logical units.
- Make sure your commit messages are in the proper format (see below).
- Push your changes to a topic branch in your fork of the repository.
- If you changed code:
- add automated tests to cover your changes, using the [Ginkgo](http://onsi.github.io/ginkgo/) & [Gomega](http://onsi.github.io/gomega/) style
- if the package did not previously have any test coverage, add it to the list
of `TESTABLE` packages in the `test.sh` script.
- run the full test script and ensure it passes
- Make sure any new code files have a license header (this is now enforced by automated tests)
- Make sure the tests pass, and add any new tests as appropriate.
- Submit a pull request to the original repository.
## How to run the test suite
We generally require test coverage of any new features or bug fixes.
Here's how you can run the test suite on any system (even Mac or Windows) using
[Vagrant](https://www.vagrantup.com/) and a hypervisor of your choice:
First, ensure that you have the [CNI repo](https://github.com/containernetworking/cni) and this repo (plugins) cloned side-by-side:
```bash
cd ~/workspace
git clone https://github.com/containernetworking/cni
git clone https://github.com/containernetworking/plugins
```
Next, boot the virtual machine and SSH in to run the tests:
```bash
cd ~/workspace/plugins
vagrant up
vagrant ssh
# you're now in a shell in a virtual machine
sudo su
go get github.com/onsi/ginkgo/ginkgo
go install github.com/containernetworking/cni/cnitool
cd /go/src/github.com/containernetworking/plugins
# to run the full test suite
./test_linux.sh
# to focus on a particular test suite
cd plugins/main/loopback
go test
```
# Acceptance policy
These things will make a PR more likely to be accepted:
* a well-described requirement
* tests for new code
* tests for old code!
* new code and tests follow the conventions in old code and tests
* a good commit message (see below)
In general, we will merge a PR once two maintainers have endorsed it.
Trivial changes (e.g., corrections to spelling) may get waved through.
For substantial changes, more people may become involved, and you might get asked to resubmit the PR or divide the changes into more than one PR.
Thanks for your contributions!
### Format of the Commit Message
@ -126,17 +71,3 @@ The first line is the subject and should be no longer than 70 characters, the
second line is always blank, and other lines should be wrapped at 80 characters.
This allows the message to be easier to read on GitHub as well as in various
git tools.
## 3rd party plugins
So you've built a CNI plugin. Where should it live?
Short answer: We'd be happy to link to it from our [list of 3rd party plugins](README.md#3rd-party-plugins).
But we'd rather you kept the code in your own repo.
Long answer: An advantage of the CNI model is that independent plugins can be
built, distributed and used without any code changes to this repository. While
some widely used plugins (and a few less-popular legacy ones) live in this repo,
we're reluctant to add more.
If you have a good reason why the CNI maintainers should take custody of your
plugin, please open an issue or PR.

37
Documentation/bridge.md Normal file
View File

@ -0,0 +1,37 @@
# bridge plugin
## Overview
With bridge plugin, all containers (on the same host) are plugged into a bridge (virtual switch) that resides in the host network namespace.
The containers receive one end of the veth pair with the other end connected to the bridge.
An IP address is only assigned to one end of the veth pair -- one residing in the container.
The bridge itself can also be assigned an IP address, turning it into a gateway for the containers.
Alternatively, the bridge can function purely in L2 mode and would need to be bridged to the host network interface (if other than container-to-container communication on the same host is desired).
The network configuration specifies the name of the bridge to be used.
If the bridge is missing, the plugin will create one on first use and, if gateway mode is used, assign it an IP that was returned by IPAM plugin via the gateway field.
## Example configuration
```
{
"name": "mynet",
"type": "bridge",
"bridge": "mynet0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "10.10.0.0/16"
}
}
```
## Network configuration reference
* `name` (string, required): the name of the network.
* `type` (string, required): "bridge".
* `bridge` (string, optional): name of the bridge to use/create. Defaults to "cni0".
* `isGateway` (boolean, optional): assign an IP address to the bridge. Defaults to false.
* `ipMasq` (boolean, optional): set up IP Masquerade on the host for traffic originating from this network and destined outside of it. Defaults to false.
* `mtu` (integer, optional): explicitly set MTU to the specified value. Defaults to the value chosen by the kernel.
* `ipam` (dictionary, required): IPAM configuration to be used for this network.

35
Documentation/dhcp.md Normal file
View File

@ -0,0 +1,35 @@
# dhcp plugin
## Overview
With dhcp plugin the containers can get an IP allocated by a DHCP server already running on your network.
This can be especially useful with plugin types such as [macvlan](https://github.com/appc/cni/blob/master/Documentation/macvlan.md).
Because a DHCP lease must be periodically renewed for the duration of container lifetime, a separate daemon is required to be running.
The same plugin binary can also be run in the daemon mode.
## Operation
To use the dhcp IPAM plugin, first launch the dhcp daemon:
```
# Make sure the unix socket has been removed
$ rm -f /run/cni/dhcp.sock
$ ./dhcp daemon
```
Alternatively, you can use systemd socket activation protocol.
Be sure that the .socket file uses /run/cni/dhcp.sock as the socket path.
With the daemon running, containers using the dhcp plugin can be launched.
## Example configuration
```
{
"ipam": {
"type": "dhcp",
}
}
## Network configuration reference
* `type` (string, required): "dhcp"

87
Documentation/flannel.md Normal file
View File

@ -0,0 +1,87 @@
# flannel plugin
## Overview
This plugin is designed to work in conjunction with [flannel](https://github.com/coreos/flannel), a network fabric for containers.
When flannel daemon is started, it outputs a `/run/flannel/subnet.env` file that looks like this:
```
FLANNEL_NETWORK=10.1.0.0/16
FLANNEL_SUBNET=10.1.17.1/24
FLANNEL_MTU=1472
FLANNEL_IPMASQ=true
```
This information reflects the attributes of flannel network on the host.
The flannel CNI plugin uses this information to configure another CNI plugin, such as bridge plugin.
## Operation
Given the following network configuration file and the contents of `/run/flannel/subnet.env` above,
```
{
"name": "mynet",
"type": "flannel"
}
```
the flannel plugin will generate another network configuration file:
```
{
"name": "mynet",
"type": "bridge",
"mtu": 1472,
"ipMasq": false,
"isGateway": true,
"ipam": {
"type": "host-local",
"subnet": "10.1.17.0/24"
}
}
```
It will then invoke the bridge plugin, passing it the generated configuration.
As can be seen from above, the flannel plugin, by default, will delegate to the bridge plugin.
If additional configuration values need to be passed to the bridge plugin, it can be done so via the `delegate` field:
```
{
"name": "mynet",
"type": "flannel",
"delegate": {
"bridge": "mynet0",
"mtu": 1400
}
}
```
This supplies a configuration parameter to the bridge plugin -- the created bridge will now be named `mynet0`.
Notice that `mtu` has also been specified and this value will not be overwritten by flannel plugin.
Additionally, the `delegate` field can be used to select a different kind of plugin altogether.
To use `ipvlan` instead of `bridge`, the following configuration can be specified:
```
{
"name": "mynet",
"type": "flannel",
"delegate": {
"type": "ipvlan",
"master": "eth0"
}
}
```
## Network configuration reference
* `name` (string, required): the name of the network
* `type` (string, required): "flannel"
* `subnetFile` (string, optional): full path to the subnet file written out by flanneld. Defaults to /run/flannel/subnet.env
* `delegate` (dictionary, optional): specifies configuration options for the delegated plugin.
flannel plugin will always set the following fields in the delegated plugin configuration:
* `name`: value of its "name" field.
* `ipam`: "host-local" type will be used with "subnet" set to `$FLANNEL_SUBNET`.
flannel plugin will set the following fields in the delegated plugin configuration if they are not present:
* `ipMasq`: the inverse of `$FLANNEL_IPMASQ`
* `mtu`: `$FLANNEL_MTU`
Additionally, for the bridge plugin, `isGateway` will be set to `true`, if not present.

View File

@ -0,0 +1,41 @@
# host-local plugin
## Overview
host-local IPAM plugin allocates IPv4 addresses out of a specified address range.
It stores the state locally on the host filesystem, therefore ensuring uniqueness of IP addresses on a single host.
## Example configuration
```
{
"ipam": {
"type": "host-local",
"subnet": "10.10.0.0/16",
"rangeStart": "10.10.1.20",
"rangeEnd": "10.10.3.50",
"gateway": "10.10.0.254",
"routes": [
{ "dst": "0.0.0.0/0" },
{ "dst": "192.168.0.0/16", "gw": "10.10.5.1" }
]
}
}
```
## Network configuration reference
* `type` (string, required): "host-local".
* `subnet` (string, required): CIDR block to allocate out of.
* `rangeStart` (string, optional): IP inside of "subnet" from which to start allocating addresses. Defaults to ".2" IP inside of the "subnet" block.
* `rangeEnd` (string, optional): IP inside of "subnet" with which to end allocating addresses. Defaults to ".254" IP inside of the "subnet" block.
* `gateway` (string, optional): IP inside of "subnet" to designate as the gateway. Defaults to ".1" IP inside of the "subnet" block.
* `routes` (string, optional): list of routes to add to the container namespace. Each route is a dictionary with "dst" and optional "gw" fields. If "gw" is omitted, value of "gateway" will be used.
## Supported arguments
The following [CNI_ARGS](https://github.com/appc/cni/blob/master/SPEC.md#parameters) are supported:
* `ip`: request a specific IP address from the subnet. If it's not available, the plugin will exit with an error
## Files
Allocated IP addresses are stored as files in /var/lib/cni/networks/$NETWORK_NAME.

40
Documentation/ipvlan.md Normal file
View File

@ -0,0 +1,40 @@
# ipvlan plugin
## Overview
ipvlan is a new [addition](https://lwn.net/Articles/620087/) to the Linux kernel.
Like its cousin macvlan, it virtualizes the host interface.
However unlike macvlan which generates a new MAC address for each interface, ipvlan devices all share the same MAC.
The kernel driver inspects the IP address of each packet when making a decision about which virtual interface should process the packet.
Because all ipvlan interfaces share the MAC address with the host interface, DHCP can only be used in conjunction with ClientID (currently not supported by DHCP plugin).
## Example configuration
```
{
"name": "mynet",
"type": "ipvlan",
"master": "eth0",
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24",
}
}
```
## Network configuration reference
* `name` (string, required): the name of the network.
* `type` (string, required): "ipvlan".
* `master` (string, required): name of the host interface to enslave.
* `mode` (string, optional): one of "l2", "l3". Defaults to "l2".
* `mtu` (integer, optional): explicitly set MTU to the specified value. Defaults to the value chosen by the kernel.
* `ipam` (dictionary, required): IPAM configuration to be used for this network.
## Notes
* `ipvlan` does not allow virtual interfaces to communicate with the master interface.
Therefore the container will not be able to reach the host via `ipvlan` interface.
Be sure to also have container join a network that provides connectivity to the host (e.g. `ptp`).
* A single master interface can not be enslaved by both `macvlan` and `ipvlan`.

34
Documentation/macvlan.md Normal file
View File

@ -0,0 +1,34 @@
# macvlan plugin
## Overview
[macvlan](http://backreference.org/2014/03/20/some-notes-on-macvlanmacvtap/) functions like a switch that is already connected to the host interface.
A host interface gets "enslaved" with the virtual interfaces sharing the physical device but having distinct MAC addresses.
Since each macvlan interface has its own MAC address, it makes it easy to use with existing DHCP servers already present on the network.
## Example configuration
```
{
"name": "mynet",
"type": "macvlan",
"master": "eth0",
"ipam": {
"type": "dhcp"
}
}
```
## Network configuration reference
* `name` (string, required): the name of the network
* `type` (string, required): "macvlan"
* `master` (string, required): name of the host interface to enslave
* `mode` (string, optional): one of "bridge", "private", "vepa", "passthrough". Defaults to "bridge".
* `mtu` (integer, optional): explicitly set MTU to the specified value. Defaults to the value chosen by the kernel.
* `ipam` (dictionary, required): IPAM configuration to be used for this network.
## Notes
* If are testing on a laptop, please remember that most wireless cards do not support being enslaved by macvlan.
* A single master interface can not be enslaved by both `macvlan` and `ipvlan`.

30
Documentation/ptp.md Normal file
View File

@ -0,0 +1,30 @@
# ptp plugin
## Overview
The ptp plugin creates a point-to-point link between a container and the host by using a veth device.
One end of the veth pair is placed inside a container and the other end resides on the host.
The host-local IPAM plugin can be used to allocate an IP address to the container.
The traffic of the container interface will be routed through the interface of the host.
## Example network configuration
```
{
"name": "mynet",
"type": "ptp",
"ipam": {
"type": "host-local",
"subnet": "10.1.1.0/24"
},
"dns": {
"nameservers": [ "10.1.1.1", "8.8.8.8" ]
}
}
## Network configuration reference
* `name` (string, required): the name of the network
* `type` (string, required): "ptp"
* `ipMasq` (boolean, optional): set up IP Masquerade on the host for traffic originating from this network and destined outside of it. Defaults to false.
* `mtu` (integer, optional): explicitly set MTU to the specified value. Defaults to value chosen by the kernel.
* `ipam` (dictionary, required): IPAM configuration to be used for this network.
* `dns` (dictionary, optional): DNS information to return as described in the [Result](/SPEC.md#result).

36
Documentation/tuning.md Normal file
View File

@ -0,0 +1,36 @@
# tuning plugin
## Overview
This plugin can change some system controls (sysctls) in the network namespace.
It does not create any network interfaces and therefore does not bring connectivity by itself.
It is only useful when used in addition to other plugins.
## Operation
The following network configuration file
```
{
"name": "mytuning",
"type": "tuning",
"sysctl": {
"net.core.somaxconn": "500"
}
}
```
will set /proc/sys/net/core/somaxconn to 500.
Other sysctls can be modified as long as they belong to the network namespace (`/proc/sys/net/*`).
A successful result would simply be:
```
{
"cniVersion": "0.1.0"
}
```
## Network sysctls documentation
Some network sysctls are documented in the Linux sources:
- [Documentation/sysctl/net.txt](https://www.kernel.org/doc/Documentation/sysctl/net.txt)
- [Documentation/networking/ip-sysctl.txt](https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt)
- [Documentation/networking/](https://www.kernel.org/doc/Documentation/networking/)

184
Godeps/Godeps.json generated Normal file
View File

@ -0,0 +1,184 @@
{
"ImportPath": "github.com/appc/cni",
"GoVersion": "go1.6",
"Packages": [
"./..."
],
"Deps": [
{
"ImportPath": "github.com/coreos/go-iptables/iptables",
"Comment": "v0.1.0",
"Rev": "fbb73372b87f6e89951c2b6b31470c2c9d5cfae3"
},
{
"ImportPath": "github.com/coreos/go-systemd/activation",
"Comment": "v2-53-g2688e91",
"Rev": "2688e91251d9d8e404e86dd8f096e23b2f086958"
},
{
"ImportPath": "github.com/d2g/dhcp4",
"Rev": "f0e4d29ff0231dce36e250b2ed9ff08412584bca"
},
{
"ImportPath": "github.com/d2g/dhcp4client",
"Rev": "bed07e1bc5b85f69c6f0fd73393aa35ec68ed892"
},
{
"ImportPath": "github.com/onsi/ginkgo",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/config",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/codelocation",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/containernode",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/failer",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/leafnodes",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/remote",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/spec",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/specrunner",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/suite",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/testingtproxy",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/writer",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/reporters",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/reporters/stenographer",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/types",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/gomega",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/format",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/gbytes",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/gexec",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/internal/assertion",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/internal/asyncassertion",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/internal/oraclematcher",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/internal/testingtsupport",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/matchers",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/bipartitegraph",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/edge",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/node",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/util",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/types",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/vishvananda/netlink",
"Rev": "ecf47fd5739b3d2c3daf7c89c4b9715a2605c21b"
},
{
"ImportPath": "github.com/vishvananda/netlink/nl",
"Rev": "ecf47fd5739b3d2c3daf7c89c4b9715a2605c21b"
},
{
"ImportPath": "golang.org/x/sys/unix",
"Rev": "e11762ca30adc5b39fdbfd8c4250dabeb8e456d3"
}
]
}

5
Godeps/Readme generated Normal file
View File

@ -0,0 +1,5 @@
This directory tree is generated automatically by godep.
Please do not edit.
See https://github.com/tools/godep for more information.

View File

@ -199,3 +199,4 @@
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.

3
MAINTAINERS Normal file
View File

@ -0,0 +1,3 @@
Michael Bridgen <michael@weave.works> (@squaremo)
Stefan Junker <stefan.junker@coreos.com> (@steveeJ)
Zach Gershman <zachgersh@gmail.com> (@zachgersh)

View File

@ -1,9 +0,0 @@
# Owners
This is the official list of the CNI network plugins owners:
- Bruce Ma <brucema19901024@gmail.com> (@mars1024)
- Casey Callendrello <cdc@redhat.com> (@squeed)
- Dan Williams <dcbw@redhat.com> (@dcbw)
- Gabe Rosenhouse <grosenhouse@pivotal.io> (@rosenhouse)
- Matt Dupre <matt@tigera.io> (@matthewdupre)
- Michael Cambria <mcambria@redhat.com> (@mccv1r0)
- Piotr Skarmuk <piotr.skarmuk@gmail.com> (@jellonek)

169
README.md
View File

@ -1,41 +1,146 @@
[![test](https://github.com/containernetworking/plugins/actions/workflows/test.yaml/badge.svg)](https://github.com/containernetworking/plugins/actions/workflows/test.yaml?query=branch%3Amaster)
[![Build Status](https://travis-ci.org/appc/cni.svg?branch=master)](https://travis-ci.org/appc/cni)
[![Coverage Status](https://coveralls.io/repos/github/appc/cni/badge.svg?branch=master)](https://coveralls.io/github/appc/cni?branch=master)
# Plugins
Some CNI network plugins, maintained by the containernetworking team. For more information, see the [CNI website](https://www.cni.dev).
# CNI - the Container Network Interface
Read [CONTRIBUTING](CONTRIBUTING.md) for build and test instructions.
## What is CNI?
## Plugins supplied:
### Main: interface-creating
* `bridge`: Creates a bridge, adds the host and the container to it.
* `ipvlan`: Adds an [ipvlan](https://www.kernel.org/doc/Documentation/networking/ipvlan.txt) interface in the container.
* `loopback`: Set the state of loopback interface to up.
* `macvlan`: Creates a new MAC address, forwards all traffic to that to the container.
* `ptp`: Creates a veth pair.
* `vlan`: Allocates a vlan device.
* `host-device`: Move an already-existing device into a container.
#### Windows: Windows specific
* `win-bridge`: Creates a bridge, adds the host and the container to it.
* `win-overlay`: Creates an overlay interface to the container.
### IPAM: IP address allocation
* `dhcp`: Runs a daemon on the host to make DHCP requests on behalf of the container
* `host-local`: Maintains a local database of allocated IPs
* `static`: Allocate a static IPv4/IPv6 addresses to container and it's useful in debugging purpose.
CNI, the _Container Network Interface_, is a proposed standard for configuring network interfaces for Linux application containers.
The standard consists of a simple specification for how executable plugins can be used to configure network namespaces; this repository also contains a go library implementing that specification.
### Meta: other plugins
* `tuning`: Tweaks sysctl parameters of an existing interface
* `portmap`: An iptables-based portmapping plugin. Maps ports from the host's address space to the container.
* `bandwidth`: Allows bandwidth-limiting through use of traffic control tbf (ingress/egress).
* `sbr`: A plugin that configures source based routing for an interface (from which it is chained).
* `firewall`: A firewall plugin which uses iptables or firewalld to add rules to allow traffic to/from the container.
The specification itself is contained in [SPEC.md](SPEC.md).
### Sample
The sample plugin provides an example for building your own plugin.
## Why develop CNI?
Application containers on Linux are a rapidly evolving area, and within this space networking is a particularly unsolved problem, as it is highly environment-specific.
We believe that every container runtime will seek to solve the same problem of making the network layer pluggable.
To avoid duplication, we think it is prudent to define a common interface between the network plugins and container execution.
Hence we are proposing this specification, along with an initial set of plugins that can be used by different container runtime systems.
## Who is using CNI?
- [rkt - container engine](https://coreos.com/blog/rkt-cni-networking.html)
- [Kurma - container runtime](http://kurma.io/)
- [Kubernetes - a system to simplify container operations](http://kubernetes.io/docs/admin/network-plugins/)
- [Cloud Foundry - a platform for cloud applications](https://github.com/cloudfoundry-incubator/guardian-cni-adapter)
- [Weave - a multi-host Docker network](https://github.com/weaveworks/weave)
- [Project Calico - a layer 3 virtual network](https://github.com/projectcalico/calico-cni)
## Contributing to CNI
We welcome contributions, including [bug reports](https://github.com/appc/cni/issues), and code and documentation improvements.
If you intend to contribute to code or documentation, please read [CONTRIBUTING.md](CONTRIBUTING.md). Also see the [contact section](#contact) in this README.
## How do I use CNI?
### Requirements
CNI requires Go 1.5+ to build.
Go 1.5 users will need to set GO15VENDOREXPERIMENT=1 to get vendored
dependencies. This flag is set by default in 1.6.
### Included Plugins
This repository includes a number of common plugins in the `plugins/` directory.
Please see the [Documentation/](Documentation/) directory for documentation about particular plugins.
### Running the plugins
The scripts/ directory contains two scripts, `priv-net-run.sh` and `docker-run.sh`, that can be used to exercise the plugins.
**note - priv-net-run.sh depends on `jq`**
Start out by creating a netconf file to describe a network:
```bash
$ mkdir -p /etc/cni/net.d
$ cat >/etc/cni/net.d/10-mynet.conf <<EOF
{
"name": "mynet",
"type": "bridge",
"bridge": "cni0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "10.22.0.0/16",
"routes": [
{ "dst": "0.0.0.0/0" }
]
}
}
EOF
$ cat >/etc/cni/net.d/99-loopback.conf <<EOF
{
"type": "loopback"
}
EOF
```
The directory `/etc/cni/net.d` is the default location in which the scripts will look for net configurations.
Next, build the plugins:
```bash
$ ./build
```
Finally, execute a command (`ifconfig` in this example) in a private network namespace that has joined the `mynet` network:
```bash
$ CNI_PATH=`pwd`/bin
$ cd scripts
$ sudo CNI_PATH=$CNI_PATH ./priv-net-run.sh ifconfig
eth0 Link encap:Ethernet HWaddr f2:c2:6f:54:b8:2b
inet addr:10.22.0.2 Bcast:0.0.0.0 Mask:255.255.0.0
inet6 addr: fe80::f0c2:6fff:fe54:b82b/64 Scope:Link
UP BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:1 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:1 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:90 (90.0 B) TX bytes:0 (0.0 B)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
```
The environment variable `CNI_PATH` tells the scripts and library where to look for plugin executables.
## Running a Docker container with network namespace set up by CNI plugins
Use the instructions in the previous section to define a netconf and build the plugins.
Next, docker-run.sh script wraps `docker run`, to execute the plugins prior to entering the container:
```bash
$ CNI_PATH=`pwd`/bin
$ cd scripts
$ sudo CNI_PATH=$CNI_PATH ./docker-run.sh --rm busybox:latest ifconfig
eth0 Link encap:Ethernet HWaddr fa:60:70:aa:07:d1
inet addr:10.22.0.2 Bcast:0.0.0.0 Mask:255.255.0.0
inet6 addr: fe80::f860:70ff:feaa:7d1/64 Scope:Link
UP BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:1 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:1 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:90 (90.0 B) TX bytes:0 (0.0 B)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
```
## Contact
For any questions about CNI, please reach out via:
For any questions about CNI, please reach out on the mailing list or IRC:
- Email: [cni-dev](https://groups.google.com/forum/#!forum/cni-dev)
- Slack: #cni on the [CNCF slack](https://slack.cncf.io/).
If you have a _security_ issue to report, please do so privately to the email addresses listed in the [OWNERS](OWNERS.md) file.
- IRC: #[appc](irc://irc.freenode.org:6667/#appc) IRC channel on freenode.org

View File

@ -1,35 +0,0 @@
# Release process
## Resulting artifacts
Creating a new release produces the following artifacts:
- Binaries (stored in the `release-<TAG>` directory) :
- `cni-plugins-<PLATFORM>-<VERSION>.tgz` binaries
- `cni-plugins-<VERSION>.tgz` binary (copy of amd64 platform binary)
- `sha1`, `sha256` and `sha512` files for the above files.
## Preparing for a release
1. Releases are performed by maintainers and should usually be discussed and planned at a maintainer meeting.
- Choose the version number. It should be prefixed with `v`, e.g. `v1.2.3`
- Take a quick scan through the PRs and issues to make sure there isn't anything crucial that _must_ be in the next release.
- Create a draft of the release note
- Discuss the level of testing that's needed and create a test plan if sensible
- Check what version of `go` is used in the build container, updating it if there's a new stable release.
- Update the vendor directory and Godeps to pin to the corresponding containernetworking/cni release. Create a PR, makes sure it passes CI and get it merged.
## Creating the release artifacts
1. Make sure you are on the master branch and don't have any local uncommitted changes.
1. Create a signed tag for the release `git tag -s $VERSION` (Ensure that GPG keys are created and added to GitHub)
1. Run the release script from the root of the repository
- `scripts/release.sh`
- The script requires Docker and ensures that a consistent environment is used.
- The artifacts will now be present in the `release-<TAG>` directory.
1. Test these binaries according to the test plan.
## Publishing the release
1. Push the tag to git `git push origin <TAG>`
1. Create a release on Github, using the tag which was just pushed.
1. Attach all the artifacts from the release directory.
1. Add the release note to the release.
1. Announce the release on at least the CNI mailing, IRC and Slack.

257
SPEC.md Normal file
View File

@ -0,0 +1,257 @@
# Container Networking Interface Proposal
## Overview
This document proposes a generic plugin-based networking solution for application containers on Linux, the _Container Networking Interface_, or _CNI_.
It is derived from the [rkt Networking Proposal][rkt-networking-proposal], which aimed to satisfy many of the [design considerations][rkt-networking-design] for networking in [rkt][rkt-github].
For the purposes of this proposal, we define two terms very specifically:
- _container_ can be considered synonymous with a [Linux _network namespace_][namespaces]. What unit this corresponds to depends on a particular container runtime implementation: for example, in implementations of the [App Container Spec][appc-github] like rkt, each _pod_ runs in a unique network namespace. In [Docker][docker], on the other hand, network namespaces generally exist for each separate Docker container.
- _network_ refers to a group of entities that are uniquely addressable that can communicate amongst each other. This could be either an individual container (as specified above), a machine, or some other network device (e.g. a router). Containers can be conceptually _added to_ or _removed from_ one or more networks.
[rkt-networking-proposal]: https://docs.google.com/a/coreos.com/document/d/1PUeV68q9muEmkHmRuW10HQ6cHgd4819_67pIxDRVNlM/edit#heading=h.ievko3xsjwxd
[rkt-networking-design]:
https://docs.google.com/a/coreos.com/document/d/1CTAL4gwqRofjxyp4tTkbgHtAwb2YCcP14UEbHNizd8g
[rkt-github]: https://github.com/coreos/rkt
[namespaces]: http://man7.org/linux/man-pages/man7/namespaces.7.html
[appc-github]: https://github.com/appc/spec
[docker]: https://docker.com
## General considerations
The intention is for the container runtime to first create a new network namespace for the container.
It then determines which networks this container should belong to and for each network, which plugin must be executed.
The network configuration is in JSON format and can easily be stored in a file.
The network configuration includes mandatory fields such as "name" and "type" as well as plugin (type) specific ones.
The container runtime sequentially sets up the networks by executing the corresponding plugin for each network.
Upon completion of the container lifecycle, the runtime executes the plugins in reverse order (relative to the order in which they were added) to disconnect them from the networks.
## CNI Plugin
### Overview
Each CNI plugin is implemented as an executable that is invoked by the container management system (e.g. rkt or Docker).
A CNI plugin is responsible for inserting a network interface into the container network namespace (e.g. one end of a veth pair) and making any necessary changes on the host (e.g. attaching other end of veth into a bridge).
It should then assign the IP to the interface and setup the routes consistent with IP Address Management section by invoking appropriate IPAM plugin.
### Parameters
The operations that the CNI plugin needs to support are:
- Add container to network
- Parameters:
- **Version**. The version of CNI spec that the caller is using (container management system or the invoking plugin).
- **Container ID**. This is optional but recommended, and should be unique across an administrative domain while the container is live (it may be reused in the future). For example, an environment with an IPAM system may require that each container is allocated a unique ID and that each IP allocation can thus be correlated back to a particular container. As another example, in appc implementations this would be the _pod ID_.
- **Network namespace path**. This represents the path to the network namespace to be added, i.e. /proc/[pid]/ns/net or a bind-mount/link to it.
- **Network configuration**. This is a JSON document describing a network to which a container can be joined. The schema is described below.
- **Extra arguments**. This allows granular configuration of CNI plugins on a per-container basis.
- **Name of the interface inside the container**. This is the name that should be assigned to the interface created inside the container (network namespace); consequently it must comply with the standard Linux restrictions on interface names.
- Result:
- **IPs assigned to the interface**. This is either an IPv4 address, an IPv6 address, or both.
- **DNS information**. Dictionary that includes DNS information for nameservers, domain, search domains and options.
- Delete container from network
- Parameters:
- **Version**. The version of CNI spec that the caller is using (container management system or the invoking plugin).
- **Container ID**, as defined above.
- **Network namespace path**, as defined above.
- **Network configuration**, as defined above.
- **Extra arguments**, as defined above.
- **Name of the interface inside the container**, as defined above.
The executable command-line API uses the type of network (see [Network Configuration](#network-configuration) below) as the name of the executable to invoke.
It will then look for this executable in a list of predefined directories. Once found, it will invoke the executable using the following environment variables for argument passing:
- `CNI_VERSION`: [Semantic Version 2.0](http://semver.org) of CNI specification. This effectively versions the CNI_XXX environment variables.
- `CNI_COMMAND`: indicates the desired operation; either `ADD` or `DEL`
- `CNI_CONTAINERID`: Container ID
- `CNI_NETNS`: Path to network namespace file
- `CNI_IFNAME`: Interface name to set up
- `CNI_ARGS`: Extra arguments passed in by the user at invocation time. Alphanumeric key-value pairs separated by semicolons; for example, "FOO=BAR;ABC=123"
- `CNI_PATH`: Colon-separated list of paths to search for CNI plugin executables
Network configuration in JSON format is streamed through stdin.
### Result
Success is indicated by a return code of zero and the following JSON printed to stdout in the case of the ADD command. This should be the same output as was returned by the IPAM plugin (see [IP Allocation](#ip-allocation) for details).
```
{
"cniVersion": "0.1.0",
"ip4": {
"ip": <ipv4-and-subnet-in-CIDR>,
"gateway": <ipv4-of-the-gateway>, (optional)
"routes": <list-of-ipv4-routes> (optional)
},
"ip6": {
"ip": <ipv6-and-subnet-in-CIDR>,
"gateway": <ipv6-of-the-gateway>, (optional)
"routes": <list-of-ipv6-routes> (optional)
},
"dns": {
"nameservers": <list-of-nameservers> (optional)
"domain": <name-of-local-domain> (optional)
"search": <list-of-additional-search-domains> (optional)
"options": <list-of-options> (optional)
}
}
```
`cniVersion` specifies a [Semantic Version 2.0](http://semver.org) of CNI specification used by the plugin.
`dns` field contains a dictionary consisting of common DNS information that this network is aware of.
The result is returned in the same format as specified in the [configuration](#network-configuration).
The specification does not declare how this information must be processed by CNI consumers.
Examples include generating an `/etc/resolv.conf` file to be injected into the container filesystem or running a DNS forwarder on the host.
Errors are indicated by a non-zero return code and the following JSON being printed to stdout:
```
{
"cniVersion": "0.1.0",
"code": <numeric-error-code>,
"msg": <short-error-message>,
"details": <long-error-message> (optional)
}
```
`cniVersion` specifies a [Semantic Version 2.0](http://semver.org) of CNI specification used by the plugin.
Error codes 0-99 are reserved for well-known errors (see [Well-known Error Codes](#well-known-error-codes) section).
Values of 100+ can be freely used for plugin specific errors.
In addition, stderr can be used for unstructured output such as logs.
### Network Configuration
The network configuration is described in JSON form. The configuration can be stored on disk or generated from other sources by the container runtime. The following fields are well-known and have the following meaning:
- `cniVersion` (string): [Semantic Version 2.0](http://semver.org) of CNI specification to which this configuration conforms.
- `name` (string): Network name. This should be unique across all containers on the host (or other administrative domain).
- `type` (string): Refers to the filename of the CNI plugin executable.
- `ipMasq` (boolean): Optional (if supported by the plugin). Set up an IP masquerade on the host for this network. This is necessary if the host will act as a gateway to subnets that are not able to route to the IP assigned to the container.
- `ipam`: Dictionary with IPAM specific values:
- `type` (string): Refers to the filename of the IPAM plugin executable.
- `routes` (list): List of subnets (in CIDR notation) that the CNI plugin should ensure are reachable by routing them through the network. Each entry is a dictionary containing:
- `dst` (string): subnet in CIDR notation
- `gw` (string): IP address of the gateway to use. If not specified, the default gateway for the subnet is assumed (as determined by the IPAM plugin).
- `dns`: Dictionary with DNS specific values:
- `nameservers` (list of strings): list of a priority-ordered list of DNS nameservers that this network is aware of. Each entry in the list is a string containing either an IPv4 or an IPv6 address.
- `domain` (string): the local domain used for short hostname lookups.
- `search` (list of strings): list of priority ordered search domains for short hostname lookups. Will be preferred over `domain` by most resolvers.
- `options` (list of strings): list of options that can be passed to the resolver
### Example configurations
```json
{
"cniVersion": "0.1.0",
"name": "dbnet",
"type": "bridge",
// type (plugin) specific
"bridge": "cni0",
"ipam": {
"type": "host-local",
// ipam specific
"subnet": "10.1.0.0/16",
"gateway": "10.1.0.1"
},
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}
```
```json
{
"cniVersion": "0.1.0",
"name": "pci",
"type": "ovs",
// type (plugin) specific
"bridge": "ovs0",
"vxlanID": 42,
"ipam": {
"type": "dhcp",
"routes": [ { "dst": "10.3.0.0/16" }, { "dst": "10.4.0.0/16" } ]
}
}
```
```json
{
"cniVersion": "0.1",
"name": "wan",
"type": "macvlan",
// ipam specific
"ipam": {
"type": "dhcp",
"routes": [ { "dst": "10.0.0.0/8", "gw": "10.0.0.1" } ]
},
"dns": {
"nameservers": [ "10.0.0.1" ]
}
}
```
### IP Allocation
As part of its operation, a CNI plugin is expected to assign (and maintain) an IP address to the interface and install any necessary routes relevant for that interface. This gives the CNI plugin great flexibility but also places a large burden on it. Many CNI plugins would need to have the same code to support several IP management schemes that users may desire (e.g. dhcp, host-local).
To lessen the burden and make IP management strategy be orthogonal to the type of CNI plugin, we define a second type of plugin -- IP Address Management Plugin (IPAM plugin). It is however the responsibility of the CNI plugin to invoke the IPAM plugin at the proper moment in its execution. The IPAM plugin is expected to determine the interface IP/subnet, Gateway and Routes and return this information to the "main" plugin to apply. The IPAM plugin may obtain the information via a protocol (e.g. dhcp), data stored on a local filesystem, the "ipam" section of the Network Configuration file or a combination of the above.
#### IP Address Management (IPAM) Interface
Like CNI plugins, the IPAM plugins are invoked by running an executable. The executable is searched for in a predefined list of paths, indicated to the CNI plugin via `CNI_PATH`. The IPAM Plugin receives all the same environment variables that were passed in to the CNI plugin. Just like the CNI plugin, IPAM receives the network configuration file via stdin.
Success is indicated by a zero return code and the following JSON being printed to stdout (in the case of the ADD command):
```
{
"cniVersion": "0.1.0",
"ip4": {
"ip": <ipv4-and-subnet-in-CIDR>,
"gateway": <ipv4-of-the-gateway>, (optional)
"routes": <list-of-ipv4-routes> (optional)
},
"ip6": {
"ip": <ipv6-and-subnet-in-CIDR>,
"gateway": <ipv6-of-the-gateway>, (optional)
"routes": <list-of-ipv6-routes> (optional)
},
"dns": {
"nameservers": <list-of-nameservers> (optional)
"domain": <name-of-local-domain> (optional)
"search": <list-of-search-domains> (optional)
"options": <list-of-options> (optional)
}
}
```
`cniVersion` specifies a [Semantic Version 2.0](http://semver.org) of CNI specification used by the plugin.
`gateway` is the default gateway for this subnet, if one exists.
It does not instruct the CNI plugin to add any routes with this gateway: routes to add are specified separately via the `routes` field.
An example use of this value is for the CNI plugin to add this IP address to the linux-bridge to make it a gateway.
Each route entry is a dictionary with the following fields:
- `dst` (string): Destination subnet specified in CIDR notation.
- `gw` (string): IP of the gateway. If omitted, a default gateway is assumed (as determined by the CNI plugin).
The "dns" field contains a dictionary consisting of common DNS information.
- `nameservers` (list of strings): list of a priority-ordered list of DNS nameservers that this network is aware of. Each entry in the list is a string containing either an IPv4 or an IPv6 address.
- `domain` (string): the local domain used for short hostname lookups.
- `search` (list of strings): list of priority ordered search domains for short hostname lookups. Will be preferred over `domain` by most resolvers.
- `options` (list of strings): list of options that can be passed to the resolver
See [CNI Plugin Result](#result) section for more information.
Errors and logs are communicated in the same way as the CNI plugin. See [CNI Plugin Result](#result) section for details.
IPAM plugin examples:
- **host-local**: Select an unused (by other containers on the same host) IP within the specified range.
- **dhcp**: Use DHCP protocol to acquire and maintain a lease. The DHCP requests will be sent via the created container interface; therefore, the associated network must support broadcast.
#### Notes
- Routes are expected to be added with a 0 metric.
- A default route may be specified via "0.0.0.0/0". Since another network might have already configured the default route, the CNI plugin should be prepared to skip over its default route definition.
## Well-known Error Codes
- `1` - Incompatible CNI version

30
build Executable file
View File

@ -0,0 +1,30 @@
#!/usr/bin/env bash
set -xe
ORG_PATH="github.com/appc"
REPO_PATH="${ORG_PATH}/cni"
if [ ! -h gopath/src/${REPO_PATH} ]; then
mkdir -p gopath/src/${ORG_PATH}
ln -s ../../../.. gopath/src/${REPO_PATH} || exit 255
fi
export GO15VENDOREXPERIMENT=1
export GOBIN=${PWD}/bin
export GOPATH=${PWD}/gopath
echo "Building API"
go build "$@" ${REPO_PATH}/libcni
echo "Building reference CLI"
go install "$@" ${REPO_PATH}/cnitool
echo "Building plugins"
PLUGINS="plugins/meta/* plugins/main/* plugins/ipam/*"
for d in $PLUGINS; do
if [ -d $d ]; then
plugin=$(basename $d)
echo " " $plugin
go install "$@" ${REPO_PATH}/$d
fi
done

View File

@ -1,23 +0,0 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")"
if [ "$(uname)" == "Darwin" ]; then
export GOOS="${GOOS:-linux}"
fi
export GOFLAGS="${GOFLAGS} -mod=vendor"
mkdir -p "${PWD}/bin"
echo "Building plugins ${GOOS}"
PLUGINS="plugins/meta/* plugins/main/* plugins/ipam/*"
for d in $PLUGINS; do
if [ -d "$d" ]; then
plugin="$(basename "$d")"
if [ "${plugin}" != "windows" ]; then
echo " $plugin"
${GO:-go} build -o "${PWD}/bin/$plugin" "$@" ./"$d"
fi
fi
done

View File

@ -1,15 +0,0 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")"
export GO="${GO:-go}"
export GOOS=windows
export GOFLAGS="${GOFLAGS} -mod=vendor"
echo "$GOFLAGS"
PLUGINS=$(cat plugins/windows_only.txt | dos2unix )
for d in $PLUGINS; do
plugin="$(basename "$d").exe"
echo "building $plugin"
$GO build -o "${PWD}/bin/$plugin" "$@" ./"${d}"
done

87
cnitool/cni.go Normal file
View File

@ -0,0 +1,87 @@
// Copyright 2015 CoreOS, Inc.
//
// 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"
"os"
"path/filepath"
"strings"
"github.com/appc/cni/libcni"
)
const (
EnvCNIPath = "CNI_PATH"
EnvNetDir = "NETCONFPATH"
DefaultNetDir = "/etc/cni/net.d"
CmdAdd = "add"
CmdDel = "del"
)
func main() {
if len(os.Args) < 3 {
usage()
return
}
netdir := os.Getenv(EnvNetDir)
if netdir == "" {
netdir = DefaultNetDir
}
netconf, err := libcni.LoadConf(netdir, os.Args[2])
if err != nil {
exit(err)
}
netns := os.Args[3]
cninet := &libcni.CNIConfig{
Path: strings.Split(os.Getenv(EnvCNIPath), ":"),
}
rt := &libcni.RuntimeConf{
ContainerID: "cni",
NetNS: netns,
IfName: "eth0",
}
switch os.Args[1] {
case CmdAdd:
_, err := cninet.AddNetwork(netconf, rt)
exit(err)
case CmdDel:
exit(cninet.DelNetwork(netconf, rt))
}
}
func usage() {
exe := filepath.Base(os.Args[0])
fmt.Fprintf(os.Stderr, "%s: Add or remove network interfaces from a network namespace\n", exe)
fmt.Fprintf(os.Stderr, " %s %s <net> <netns>\n", exe, CmdAdd)
fmt.Fprintf(os.Stderr, " %s %s <net> <netns>\n", exe, CmdDel)
os.Exit(1)
}
func exit(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}
os.Exit(0)
}

25
go.mod
View File

@ -1,25 +0,0 @@
module github.com/containernetworking/plugins
go 1.16
require (
github.com/Microsoft/hcsshim v0.8.20
github.com/alexflint/go-filemutex v1.1.0
github.com/buger/jsonparser v1.1.1
github.com/containernetworking/cni v1.0.1
github.com/coreos/go-iptables v0.6.0
github.com/coreos/go-systemd/v22 v22.3.2
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.0.4
github.com/j-keck/arping v1.0.2
github.com/mattn/go-shellwords v1.0.12
github.com/onsi/ginkgo v1.16.4
github.com/onsi/gomega v1.15.0
github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e
)

971
go.sum
View File

@ -1,971 +0,0 @@
bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg=
github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/go-winio v0.4.17 h1:iT12IBVClFevaf8PuVyi3UmZOVh4OqnaLxDTW2O6j3w=
github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ=
github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8=
github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg=
github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00=
github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600=
github.com/Microsoft/hcsshim v0.8.20 h1:ZTwcx3NS8n07kPf/JZ1qwU6vnjhVPMUWlXBF8r9UxrE=
github.com/Microsoft/hcsshim v0.8.20/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4=
github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU=
github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0=
github.com/alexflint/go-filemutex v1.1.0 h1:IAWuUuRYL2hETx5b8vCgwnD+xSdlsTQY6s2JjBsqLdg=
github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
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/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
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/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg=
github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc=
github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs=
github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
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/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE=
github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU=
github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU=
github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU=
github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E=
github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss=
github.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss=
github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI=
github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko=
github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM=
github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo=
github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo=
github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE=
github.com/containerd/cgroups v1.0.1 h1:iJnMvco9XGvKUvNQkv88bE4uJXxRQH18efbKo9w5vHQ=
github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU=
github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE=
github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw=
github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ=
github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ=
github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU=
github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI=
github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s=
github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g=
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo=
github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y=
github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ=
github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM=
github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0=
github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0=
github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4=
github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4=
github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU=
github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk=
github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0=
github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0=
github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g=
github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok=
github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok=
github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0=
github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA=
github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow=
github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms=
github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c=
github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY=
github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY=
github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8=
github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y=
github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y=
github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk=
github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg=
github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s=
github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw=
github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y=
github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY=
github.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY=
github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY=
github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
github.com/containernetworking/cni v1.0.0 h1:9VJe1a5uKtdeJIHC/UvbTweracOh6GafT0nfbEGVcQ0=
github.com/containernetworking/cni v1.0.0/go.mod h1:AKuhXbN5EzmD4yTNtfSsX3tPcmtrBI6QcRV0NiNt15Y=
github.com/containernetworking/cni v1.0.1 h1:9OIL/sZmMYDBe+G8svzILAlulUpaDTUjeAbtH/JNLBo=
github.com/containernetworking/cni v1.0.1/go.mod h1:AKuhXbN5EzmD4yTNtfSsX3tPcmtrBI6QcRV0NiNt15Y=
github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM=
github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8=
github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc=
github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4=
github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk=
github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
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=
github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA=
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e h1:BWhy2j3IXJhjCbC68FptL43tDKIq8FladmaTs3Xs7Z8=
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU=
github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
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-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
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.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=
github.com/j-keck/arping v1.0.2 h1:hlLhuXgQkzIJTZuhMigvG/CuSkaspeaD9hRDk2zuiMI=
github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
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/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
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/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ=
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
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.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU=
github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0=
github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs=
github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE=
github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1 h1:ZFfeKAhIQiiOrQaI3/znw0gOmYpO28Tcu1YaqMa/jtQ=
github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=
github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5 h1:+UB2BJA852UkGH42H+Oee69djmxS3ANzl2b/JtT1YiA=
github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA=
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg=
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
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/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-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
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-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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-20200625203802-6e8e738ad208/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-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/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-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/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-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
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 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/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 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo=
k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ=
k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8=
k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc=
k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU=
k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM=
k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q=
k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y=
k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k=
k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0=
k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk=
k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI=
k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM=
k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM=
k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI=
k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI=
k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc=
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM=
k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

View File

@ -1,269 +0,0 @@
// Copyright 2018 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 integration_test
import (
"fmt"
"math/rand"
"os"
"os/exec"
"path/filepath"
"bytes"
"io"
"net"
"regexp"
"strconv"
"strings"
"time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gbytes"
"github.com/onsi/gomega/gexec"
)
var _ = Describe("Basic PTP using cnitool", func() {
var (
cnitoolBin string
cniPath string
)
BeforeEach(func() {
var err error
cniPath, err = filepath.Abs("../bin")
Expect(err).NotTo(HaveOccurred())
cnitoolBin, err = exec.LookPath("cnitool")
Expect(err).NotTo(HaveOccurred(), "expected to find cnitool in your PATH")
})
Context("basic cases", func() {
var (
env TestEnv
hostNS Namespace
contNS Namespace
)
BeforeEach(func() {
var err error
netConfPath, err := filepath.Abs("./testdata")
Expect(err).NotTo(HaveOccurred())
env = TestEnv([]string{
"CNI_PATH=" + cniPath,
"NETCONFPATH=" + netConfPath,
"PATH=" + os.Getenv("PATH"),
})
hostNS = Namespace(fmt.Sprintf("cni-test-host-%x", rand.Int31()))
hostNS.Add()
contNS = Namespace(fmt.Sprintf("cni-test-cont-%x", rand.Int31()))
contNS.Add()
})
AfterEach(func() {
contNS.Del()
hostNS.Del()
})
basicAssertion := func(netName, expectedIPPrefix string) {
env.runInNS(hostNS, cnitoolBin, "add", netName, contNS.LongName())
addrOutput := env.runInNS(contNS, "ip", "addr")
Expect(addrOutput).To(ContainSubstring(expectedIPPrefix))
env.runInNS(hostNS, cnitoolBin, "del", netName, contNS.LongName())
}
It("supports basic network add and del operations", func() {
basicAssertion("basic-ptp", "10.1.2.")
})
It("supports add and del with ptp + bandwidth", func() {
basicAssertion("chained-ptp-bandwidth", "10.9.2.")
})
})
Context("when the bandwidth plugin is chained with a plugin that returns multiple adapters", func() {
var (
hostNS Namespace
contNS1 Namespace
contNS2 Namespace
basicBridgeEnv TestEnv
chainedBridgeBandwidthEnv TestEnv
chainedBridgeBandwidthSession, basicBridgeSession *gexec.Session
)
BeforeEach(func() {
hostNS = Namespace(fmt.Sprintf("cni-test-host-%x", rand.Int31()))
hostNS.Add()
contNS1 = Namespace(fmt.Sprintf("cni-test-cont1-%x", rand.Int31()))
contNS1.Add()
contNS2 = Namespace(fmt.Sprintf("cni-test-cont2-%x", rand.Int31()))
contNS2.Add()
basicBridgeNetConfPath, err := filepath.Abs("./testdata/basic-bridge")
Expect(err).NotTo(HaveOccurred())
basicBridgeEnv = TestEnv([]string{
"CNI_PATH=" + cniPath,
"NETCONFPATH=" + basicBridgeNetConfPath,
"PATH=" + os.Getenv("PATH"),
})
chainedBridgeBandwidthNetConfPath, err := filepath.Abs("./testdata/chained-bridge-bandwidth")
Expect(err).NotTo(HaveOccurred())
chainedBridgeBandwidthEnv = TestEnv([]string{
"CNI_PATH=" + cniPath,
"NETCONFPATH=" + chainedBridgeBandwidthNetConfPath,
"PATH=" + os.Getenv("PATH"),
})
})
AfterEach(func() {
if chainedBridgeBandwidthSession != nil {
chainedBridgeBandwidthSession.Kill()
}
if basicBridgeSession != nil {
basicBridgeSession.Kill()
}
chainedBridgeBandwidthEnv.runInNS(hostNS, cnitoolBin, "del", "network-chain-test", contNS1.LongName())
basicBridgeEnv.runInNS(hostNS, cnitoolBin, "del", "network-chain-test", contNS2.LongName())
})
Measure("limits traffic only on the restricted bandwith veth device", func(b Benchmarker) {
ipRegexp := regexp.MustCompile("10\\.1[12]\\.2\\.\\d{1,3}")
By(fmt.Sprintf("adding %s to %s\n\n", "chained-bridge-bandwidth", contNS1.ShortName()))
chainedBridgeBandwidthEnv.runInNS(hostNS, cnitoolBin, "add", "network-chain-test", contNS1.LongName())
chainedBridgeIP := ipRegexp.FindString(chainedBridgeBandwidthEnv.runInNS(contNS1, "ip", "addr"))
Expect(chainedBridgeIP).To(ContainSubstring("10.12.2."))
By(fmt.Sprintf("adding %s to %s\n\n", "basic-bridge", contNS2.ShortName()))
basicBridgeEnv.runInNS(hostNS, cnitoolBin, "add", "network-chain-test", contNS2.LongName())
basicBridgeIP := ipRegexp.FindString(basicBridgeEnv.runInNS(contNS2, "ip", "addr"))
Expect(basicBridgeIP).To(ContainSubstring("10.11.2."))
var chainedBridgeBandwidthPort, basicBridgePort int
var err error
By(fmt.Sprintf("starting echo server in %s\n\n", contNS1.ShortName()))
chainedBridgeBandwidthPort, chainedBridgeBandwidthSession, err = startEchoServerInNamespace(contNS1)
Expect(err).ToNot(HaveOccurred())
By(fmt.Sprintf("starting echo server in %s\n\n", contNS2.ShortName()))
basicBridgePort, basicBridgeSession, err = startEchoServerInNamespace(contNS2)
Expect(err).ToNot(HaveOccurred())
packetInBytes := 20000 // The shaper needs to 'warm'. Send enough to cause it to throttle,
// balanced by run time.
By(fmt.Sprintf("sending tcp traffic to the chained, bridged, traffic shaped container on ip address '%s:%d'\n\n", chainedBridgeIP, chainedBridgeBandwidthPort))
runtimeWithLimit := b.Time("with chained bridge and bandwidth plugins", func() {
makeTcpClientInNS(hostNS.ShortName(), chainedBridgeIP, chainedBridgeBandwidthPort, packetInBytes)
})
By(fmt.Sprintf("sending tcp traffic to the basic bridged container on ip address '%s:%d'\n\n", basicBridgeIP, basicBridgePort))
runtimeWithoutLimit := b.Time("with basic bridged plugin", func() {
makeTcpClientInNS(hostNS.ShortName(), basicBridgeIP, basicBridgePort, packetInBytes)
})
Expect(runtimeWithLimit).To(BeNumerically(">", runtimeWithoutLimit+1000*time.Millisecond))
}, 1)
})
})
type TestEnv []string
func (e TestEnv) run(bin string, args ...string) string {
cmd := exec.Command(bin, args...)
cmd.Env = e
session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
Expect(err).NotTo(HaveOccurred())
Eventually(session, "5s").Should(gexec.Exit(0))
return string(session.Out.Contents())
}
func (e TestEnv) runInNS(nsShortName Namespace, bin string, args ...string) string {
a := append([]string{"netns", "exec", string(nsShortName), bin}, args...)
return e.run("ip", a...)
}
type Namespace string
func (n Namespace) LongName() string {
return fmt.Sprintf("/var/run/netns/%s", n)
}
func (n Namespace) ShortName() string {
return string(n)
}
func (n Namespace) Add() {
(TestEnv{}).run("ip", "netns", "add", string(n))
}
func (n Namespace) Del() {
(TestEnv{}).run("ip", "netns", "del", string(n))
}
func makeTcpClientInNS(netns string, address string, port int, numBytes int) {
payload := bytes.Repeat([]byte{'a'}, numBytes)
message := string(payload)
var cmd *exec.Cmd
if netns != "" {
netns = filepath.Base(netns)
cmd = exec.Command("ip", "netns", "exec", netns, echoClientBinaryPath, "--target", fmt.Sprintf("%s:%d", address, port), "--message", message)
} else {
cmd = exec.Command(echoClientBinaryPath, "--target", fmt.Sprintf("%s:%d", address, port), "--message", message)
}
cmd.Stdin = bytes.NewBuffer([]byte(message))
cmd.Stderr = GinkgoWriter
out, err := cmd.Output()
Expect(err).NotTo(HaveOccurred())
Expect(string(out)).To(Equal(message))
}
func startEchoServerInNamespace(netNS Namespace) (int, *gexec.Session, error) {
session, err := startInNetNS(echoServerBinaryPath, netNS)
Expect(err).NotTo(HaveOccurred())
// wait for it to print it's address on stdout
Eventually(session.Out).Should(gbytes.Say("\n"))
_, portString, err := net.SplitHostPort(strings.TrimSpace(string(session.Out.Contents())))
Expect(err).NotTo(HaveOccurred())
port, err := strconv.Atoi(portString)
Expect(err).NotTo(HaveOccurred())
go func() {
// print out echoserver output to ginkgo to capture any errors that might be occurring.
io.Copy(GinkgoWriter, io.MultiReader(session.Out, session.Err))
}()
return port, session, nil
}
func startInNetNS(binPath string, namespace Namespace) (*gexec.Session, error) {
cmd := exec.Command("ip", "netns", "exec", namespace.ShortName(), binPath)
return gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
}

View File

@ -1,46 +0,0 @@
// Copyright 2018 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 integration_test
import (
"strings"
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
)
func TestIntegration(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "integration")
}
var echoServerBinaryPath, echoClientBinaryPath string
var _ = SynchronizedBeforeSuite(func() []byte {
serverBinaryPath, err := gexec.Build("github.com/containernetworking/plugins/pkg/testutils/echo/server")
Expect(err).NotTo(HaveOccurred())
clientBinaryPath, err := gexec.Build("github.com/containernetworking/plugins/pkg/testutils/echo/client")
Expect(err).NotTo(HaveOccurred())
return []byte(strings.Join([]string{serverBinaryPath, clientBinaryPath}, ","))
}, func(data []byte) {
binaries := strings.Split(string(data), ",")
echoServerBinaryPath = binaries[0]
echoClientBinaryPath = binaries[1]
})
var _ = SynchronizedAfterSuite(func() {}, func() {
gexec.CleanupBuildArtifacts()
})

View File

@ -1,12 +0,0 @@
{
"cniVersion": "0.3.1",
"name": "network-chain-test",
"type": "bridge",
"bridge": "test-bridge-0",
"isDefaultGateway": true,
"ipam": {
"type": "host-local",
"subnet": "10.11.2.0/24",
"dataDir": "/tmp/foo"
}
}

View File

@ -1,11 +0,0 @@
{
"cniVersion": "0.3.0",
"name": "basic-ptp",
"type": "ptp",
"ipMasq": true,
"mtu": 512,
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24"
}
}

View File

@ -1,27 +0,0 @@
{
"cniVersion": "0.3.1",
"name": "network-chain-test",
"plugins": [
{
"type": "bridge",
"bridge": "test-bridge-1",
"isDefaultGateway": true,
"ipam": {
"type": "host-local",
"subnet": "10.12.2.0/24",
"dataDir": "/tmp/bar"
}
},
{
"type": "bandwidth",
"runtimeConfig": {
"bandWidth": {
"ingressRate": 8000,
"ingressBurst": 16000,
"egressRate": 8000,
"egressBurst": 16000
}
}
}
]
}

View File

@ -1,26 +0,0 @@
{
"cniVersion": "0.3.1",
"name": "chained-ptp-bandwidth",
"plugins": [
{
"type": "ptp",
"ipMasq": true,
"mtu": 512,
"ipam": {
"type": "host-local",
"subnet": "10.9.2.0/24"
}
},
{
"type": "bandwidth",
"runtimeConfig": {
"bandWidth": {
"ingressRate": 800,
"ingressBurst": 200,
"egressRate": 800,
"egressBurst": 200
}
}
}
]
}

73
libcni/api.go Normal file
View File

@ -0,0 +1,73 @@
// 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 libcni
import (
"strings"
"github.com/appc/cni/pkg/invoke"
"github.com/appc/cni/pkg/types"
)
type RuntimeConf struct {
ContainerID string
NetNS string
IfName string
Args [][2]string
}
type NetworkConfig struct {
Network *types.NetConf
Bytes []byte
}
type CNI interface {
AddNetwork(net *NetworkConfig, rt *RuntimeConf) (*types.Result, error)
DelNetwork(net *NetworkConfig, rt *RuntimeConf) error
}
type CNIConfig struct {
Path []string
}
func (c *CNIConfig) AddNetwork(net *NetworkConfig, rt *RuntimeConf) (*types.Result, error) {
pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path)
if err != nil {
return nil, err
}
return invoke.ExecPluginWithResult(pluginPath, net.Bytes, c.args("ADD", rt))
}
func (c *CNIConfig) DelNetwork(net *NetworkConfig, rt *RuntimeConf) error {
pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path)
if err != nil {
return err
}
return invoke.ExecPluginWithoutResult(pluginPath, net.Bytes, c.args("DEL", rt))
}
// =====
func (c *CNIConfig) args(action string, rt *RuntimeConf) *invoke.Args {
return &invoke.Args{
Command: action,
ContainerID: rt.ContainerID,
NetNS: rt.NetNS,
PluginArgs: rt.Args,
IfName: rt.IfName,
Path: strings.Join(c.Path, ":"),
}
}

85
libcni/conf.go Normal file
View File

@ -0,0 +1,85 @@
// 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 libcni
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
)
func ConfFromBytes(bytes []byte) (*NetworkConfig, error) {
conf := &NetworkConfig{Bytes: bytes}
if err := json.Unmarshal(bytes, &conf.Network); err != nil {
return nil, fmt.Errorf("error parsing configuration: %s", err)
}
return conf, nil
}
func ConfFromFile(filename string) (*NetworkConfig, error) {
bytes, err := ioutil.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("error reading %s: %s", filename, err)
}
return ConfFromBytes(bytes)
}
func ConfFiles(dir string) ([]string, error) {
// In part, adapted from rkt/networking/podenv.go#listFiles
files, err := ioutil.ReadDir(dir)
switch {
case err == nil: // break
case os.IsNotExist(err):
return nil, nil
default:
return nil, err
}
confFiles := []string{}
for _, f := range files {
if f.IsDir() {
continue
}
if filepath.Ext(f.Name()) == ".conf" {
confFiles = append(confFiles, filepath.Join(dir, f.Name()))
}
}
return confFiles, nil
}
func LoadConf(dir, name string) (*NetworkConfig, error) {
files, err := ConfFiles(dir)
switch {
case err != nil:
return nil, err
case len(files) == 0:
return nil, fmt.Errorf("no net configurations found")
}
sort.Strings(files)
for _, confFile := range files {
conf, err := ConfFromFile(confFile)
if err != nil {
return nil, err
}
if conf.Network.Name == name {
return conf, nil
}
}
return nil, fmt.Errorf(`no net configuration with name "%s" in %s`, name, dir)
}

View File

@ -1,37 +0,0 @@
// Copyright 2020 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package errors
import "fmt"
// Annotate is used to add extra context to an existing error. The return will be
// a new error which carries error message from both context message and existing error.
func Annotate(err error, message string) error {
if err == nil {
return nil
}
return fmt.Errorf("%s: %v", message, err)
}
// Annotatef is used to add extra context with args to an existing error. The return will be
// a new error which carries error message from both context message and existing error.
func Annotatef(err error, message string, args ...interface{}) error {
if err == nil {
return nil
}
return fmt.Errorf("%s: %v", fmt.Sprintf(message, args...), err)
}

View File

@ -1,96 +0,0 @@
// Copyright 2020 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package errors
import (
"errors"
"reflect"
"testing"
)
func TestAnnotate(t *testing.T) {
tests := []struct {
name string
existingErr error
contextMessage string
expectedErr error
}{
{
"nil error",
nil,
"context",
nil,
},
{
"normal case",
errors.New("existing error"),
"context",
errors.New("context: existing error"),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if !reflect.DeepEqual(Annotatef(test.existingErr, test.contextMessage), test.expectedErr) {
t.Errorf("test case %s fails", test.name)
return
}
})
}
}
func TestAnnotatef(t *testing.T) {
tests := []struct {
name string
existingErr error
contextMessage string
contextArgs []interface{}
expectedErr error
}{
{
"nil error",
nil,
"context",
nil,
nil,
},
{
"normal case",
errors.New("existing error"),
"context",
nil,
errors.New("context: existing error"),
},
{
"normal case with args",
errors.New("existing error"),
"context %s %d",
[]interface{}{
"arg",
100,
},
errors.New("context arg 100: existing error"),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if !reflect.DeepEqual(Annotatef(test.existingErr, test.contextMessage, test.contextArgs...), test.expectedErr) {
t.Errorf("test case %s fails", test.name)
return
}
})
}
}

View File

@ -1,368 +0,0 @@
// 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 hns
import (
"fmt"
"net"
"strings"
"github.com/Microsoft/hcsshim"
"github.com/Microsoft/hcsshim/hcn"
"github.com/containernetworking/cni/pkg/types"
current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/plugins/pkg/errors"
)
const (
pauseContainerNetNS = "none"
)
type EndpointInfo struct {
EndpointName string
DNS types.DNS
NetworkName string
NetworkId string
Gateway net.IP
IpAddress net.IP
}
// GetSandboxContainerID returns the sandbox ID of this pod.
func GetSandboxContainerID(containerID string, netNs string) string {
if len(netNs) != 0 && netNs != pauseContainerNetNS {
splits := strings.SplitN(netNs, ":", 2)
if len(splits) == 2 {
containerID = splits[1]
}
}
return containerID
}
// GetIpString returns the given IP as a string.
func GetIpString(ip *net.IP) string {
if len(*ip) == 0 {
return ""
} else {
return ip.String()
}
}
// GetDefaultDestinationPrefix returns the default destination prefix according to the given IP type.
func GetDefaultDestinationPrefix(ip *net.IP) string {
destinationPrefix := "0.0.0.0/0"
if ip.To4() == nil {
destinationPrefix = "::/0"
}
return destinationPrefix
}
// ConstructEndpointName constructs endpoint id which is used to identify an endpoint from HNS/HCN.
func ConstructEndpointName(containerID string, netNs string, networkName string) string {
return GetSandboxContainerID(containerID, netNs) + "_" + networkName
}
// GenerateHnsEndpoint generates an HNSEndpoint with given info and config.
func GenerateHnsEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcsshim.HNSEndpoint, error) {
// run the IPAM plugin and get back the config to apply
hnsEndpoint, err := hcsshim.GetHNSEndpointByName(epInfo.EndpointName)
if err != nil && !hcsshim.IsNotExist(err) {
return nil, errors.Annotatef(err, "failed to get HNSEndpoint %s", epInfo.EndpointName)
}
if hnsEndpoint != nil {
if strings.EqualFold(hnsEndpoint.VirtualNetwork, epInfo.NetworkId) {
return nil, fmt.Errorf("HNSEndpoint %s is already existed", epInfo.EndpointName)
}
// remove endpoint if corrupted
if _, err = hnsEndpoint.Delete(); err != nil {
return nil, errors.Annotatef(err, "failed to delete corrupted HNSEndpoint %s", epInfo.EndpointName)
}
}
if n.LoopbackDSR {
n.ApplyLoopbackDSRPolicy(&epInfo.IpAddress)
}
hnsEndpoint = &hcsshim.HNSEndpoint{
Name: epInfo.EndpointName,
VirtualNetwork: epInfo.NetworkId,
DNSServerList: strings.Join(epInfo.DNS.Nameservers, ","),
DNSSuffix: strings.Join(epInfo.DNS.Search, ","),
GatewayAddress: GetIpString(&epInfo.Gateway),
IPAddress: epInfo.IpAddress,
Policies: n.GetHNSEndpointPolicies(),
}
return hnsEndpoint, nil
}
// RemoveHnsEndpoint detaches the given name endpoint from container specified by containerID,
// or removes the given name endpoint completely.
func RemoveHnsEndpoint(epName string, netns string, containerID string) error {
if len(netns) == 0 {
return nil
}
hnsEndpoint, err := hcsshim.GetHNSEndpointByName(epName)
if err != nil {
if hcsshim.IsNotExist(err) {
return nil
}
return errors.Annotatef(err, "failed to find HNSEndpoint %s", epName)
}
// for shared endpoint, detach it from the container
if netns != pauseContainerNetNS {
_ = hnsEndpoint.ContainerDetach(containerID)
return nil
}
// for removing the endpoint completely, hot detach is used at first
_ = hcsshim.HotDetachEndpoint(containerID, hnsEndpoint.Id)
_, _ = hnsEndpoint.Delete()
return nil
}
type HnsEndpointMakerFunc func() (*hcsshim.HNSEndpoint, error)
// AddHnsEndpoint attaches an HNSEndpoint to a container specified by containerID.
func AddHnsEndpoint(epName string, expectedNetworkId string, containerID string, netns string, makeEndpoint HnsEndpointMakerFunc) (*hcsshim.HNSEndpoint, error) {
hnsEndpoint, err := hcsshim.GetHNSEndpointByName(epName)
if err != nil {
if !hcsshim.IsNotExist(err) {
return nil, errors.Annotatef(err, "failed to find HNSEndpoint %s", epName)
}
}
// for shared endpoint, we expect that the endpoint already exists
if netns != pauseContainerNetNS {
if hnsEndpoint == nil {
return nil, errors.Annotatef(err, "failed to find HNSEndpoint %s", epName)
}
}
// verify the existing endpoint is corrupted or not
if hnsEndpoint != nil {
if !strings.EqualFold(hnsEndpoint.VirtualNetwork, expectedNetworkId) {
if _, err := hnsEndpoint.Delete(); err != nil {
return nil, errors.Annotatef(err, "failed to delete corrupted HNSEndpoint %s", epName)
}
hnsEndpoint = nil
}
}
// create endpoint if not found
var isNewEndpoint bool
if hnsEndpoint == nil {
if hnsEndpoint, err = makeEndpoint(); err != nil {
return nil, errors.Annotate(err, "failed to make a new HNSEndpoint")
}
if hnsEndpoint, err = hnsEndpoint.Create(); err != nil {
return nil, errors.Annotate(err, "failed to create the new HNSEndpoint")
}
isNewEndpoint = true
}
// attach to container
if err := hcsshim.HotAttachEndpoint(containerID, hnsEndpoint.Id); err != nil {
if isNewEndpoint {
if err := RemoveHnsEndpoint(epName, netns, containerID); err != nil {
return nil, errors.Annotatef(err, "failed to remove the new HNSEndpoint %s after attaching container %s failure", hnsEndpoint.Id, containerID)
}
} else if hcsshim.ErrComputeSystemDoesNotExist == err {
return hnsEndpoint, nil
}
return nil, errors.Annotatef(err, "failed to attach container %s to HNSEndpoint %s", containerID, hnsEndpoint.Id)
}
return hnsEndpoint, nil
}
// ConstructHnsResult constructs the CNI result for the HNSEndpoint.
func ConstructHnsResult(hnsNetwork *hcsshim.HNSNetwork, hnsEndpoint *hcsshim.HNSEndpoint) (*current.Result, error) {
resultInterface := &current.Interface{
Name: hnsEndpoint.Name,
Mac: hnsEndpoint.MacAddress,
}
_, ipSubnet, err := net.ParseCIDR(hnsNetwork.Subnets[0].AddressPrefix)
if err != nil {
return nil, errors.Annotatef(err, "failed to parse CIDR from %s", hnsNetwork.Subnets[0].AddressPrefix)
}
resultIPConfig := &current.IPConfig{
Address: net.IPNet{
IP: hnsEndpoint.IPAddress,
Mask: ipSubnet.Mask},
Gateway: net.ParseIP(hnsEndpoint.GatewayAddress),
}
result := &current.Result{
CNIVersion: current.ImplementedSpecVersion,
Interfaces: []*current.Interface{resultInterface},
IPs: []*current.IPConfig{resultIPConfig},
DNS: types.DNS{
Search: strings.Split(hnsEndpoint.DNSSuffix, ","),
Nameservers: strings.Split(hnsEndpoint.DNSServerList, ","),
},
}
return result, nil
}
// GenerateHcnEndpoint generates a HostComputeEndpoint with given info and config.
func GenerateHcnEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcn.HostComputeEndpoint, error) {
// run the IPAM plugin and get back the config to apply
hcnEndpoint, err := hcn.GetEndpointByName(epInfo.EndpointName)
if err != nil && !hcn.IsNotFoundError(err) {
return nil, errors.Annotatef(err, "failed to get HostComputeEndpoint %s", epInfo.EndpointName)
}
// verify the existing endpoint is corrupted or not
if hcnEndpoint != nil {
if strings.EqualFold(hcnEndpoint.HostComputeNetwork, epInfo.NetworkId) {
return nil, fmt.Errorf("HostComputeNetwork %s is already existed", epInfo.EndpointName)
}
// remove endpoint if corrupted
if err := hcnEndpoint.Delete(); err != nil {
return nil, errors.Annotatef(err, "failed to delete corrupted HostComputeEndpoint %s", epInfo.EndpointName)
}
}
if n.LoopbackDSR {
n.ApplyLoopbackDSRPolicy(&epInfo.IpAddress)
}
hcnEndpoint = &hcn.HostComputeEndpoint{
SchemaVersion: hcn.SchemaVersion{
Major: 2,
Minor: 0,
},
Name: epInfo.EndpointName,
HostComputeNetwork: epInfo.NetworkId,
Dns: hcn.Dns{
Domain: epInfo.DNS.Domain,
Search: epInfo.DNS.Search,
ServerList: epInfo.DNS.Nameservers,
Options: epInfo.DNS.Options,
},
Routes: []hcn.Route{
{
NextHop: GetIpString(&epInfo.Gateway),
DestinationPrefix: GetDefaultDestinationPrefix(&epInfo.Gateway),
},
},
IpConfigurations: []hcn.IpConfig{
{
IpAddress: GetIpString(&epInfo.IpAddress),
},
},
Policies: n.GetHostComputeEndpointPolicies(),
}
return hcnEndpoint, nil
}
// RemoveHcnEndpoint removes the given name endpoint from namespace.
func RemoveHcnEndpoint(epName string) error {
hcnEndpoint, err := hcn.GetEndpointByName(epName)
if err != nil {
if hcn.IsNotFoundError(err) {
return nil
}
return errors.Annotatef(err, "failed to find HostComputeEndpoint %s", epName)
}
err = hcnEndpoint.Delete()
if err != nil {
return errors.Annotatef(err, "failed to remove HostComputeEndpoint %s", epName)
}
return nil
}
type HcnEndpointMakerFunc func() (*hcn.HostComputeEndpoint, error)
// AddHcnEndpoint attaches a HostComputeEndpoint to the given namespace.
func AddHcnEndpoint(epName string, expectedNetworkId string, namespace string, makeEndpoint HcnEndpointMakerFunc) (*hcn.HostComputeEndpoint, error) {
hcnEndpoint, err := hcn.GetEndpointByName(epName)
if err != nil {
if !hcn.IsNotFoundError(err) {
return nil, errors.Annotatef(err, "failed to find HostComputeEndpoint %s", epName)
}
}
// verify the existing endpoint is corrupted or not
if hcnEndpoint != nil {
if !strings.EqualFold(hcnEndpoint.HostComputeNetwork, expectedNetworkId) {
if err := hcnEndpoint.Delete(); err != nil {
return nil, errors.Annotatef(err, "failed to delete corrupted HostComputeEndpoint %s", epName)
}
hcnEndpoint = nil
}
}
// create endpoint if not found
var isNewEndpoint bool
if hcnEndpoint == nil {
if hcnEndpoint, err = makeEndpoint(); err != nil {
return nil, errors.Annotate(err, "failed to make a new HostComputeEndpoint")
}
if hcnEndpoint, err = hcnEndpoint.Create(); err != nil {
return nil, errors.Annotate(err, "failed to create the new HostComputeEndpoint")
}
isNewEndpoint = true
}
// add to namespace
err = hcn.AddNamespaceEndpoint(namespace, hcnEndpoint.Id)
if err != nil {
if isNewEndpoint {
if err := RemoveHcnEndpoint(epName); err != nil {
return nil, errors.Annotatef(err, "failed to remove the new HostComputeEndpoint %s after adding HostComputeNamespace %s failure", epName, namespace)
}
}
return nil, errors.Annotatef(err, "failed to add HostComputeEndpoint %s to HostComputeNamespace %s", epName, namespace)
}
return hcnEndpoint, nil
}
// ConstructHcnResult constructs the CNI result for the HostComputeEndpoint.
func ConstructHcnResult(hcnNetwork *hcn.HostComputeNetwork, hcnEndpoint *hcn.HostComputeEndpoint) (*current.Result, error) {
resultInterface := &current.Interface{
Name: hcnEndpoint.Name,
Mac: hcnEndpoint.MacAddress,
}
_, ipSubnet, err := net.ParseCIDR(hcnNetwork.Ipams[0].Subnets[0].IpAddressPrefix)
if err != nil {
return nil, err
}
ipAddress := net.ParseIP(hcnEndpoint.IpConfigurations[0].IpAddress)
resultIPConfig := &current.IPConfig{
Address: net.IPNet{
IP: ipAddress,
Mask: ipSubnet.Mask},
Gateway: net.ParseIP(hcnEndpoint.Routes[0].NextHop),
}
result := &current.Result{
CNIVersion: current.ImplementedSpecVersion,
Interfaces: []*current.Interface{resultInterface},
IPs: []*current.IPConfig{resultIPConfig},
DNS: types.DNS{
Search: hcnEndpoint.Dns.Search,
Nameservers: hcnEndpoint.Dns.ServerList,
Options: hcnEndpoint.Dns.Options,
Domain: hcnEndpoint.Dns.Domain,
},
}
return result, nil
}

View File

@ -1,26 +0,0 @@
// 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 hns
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestNetConf(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "NetConf Suite")
}

View File

@ -1,348 +0,0 @@
// 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 hns
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net"
"strconv"
"strings"
"github.com/Microsoft/hcsshim/hcn"
"github.com/buger/jsonparser"
"github.com/containernetworking/cni/pkg/types"
)
// NetConf is the CNI spec
type NetConf struct {
types.NetConf
// ApiVersion specifies the policies type of HNS or HCN, select one of [1, 2].
// HNS is the v1 API, which is the default version and applies to dockershim.
// HCN is the v2 API, which can leverage HostComputeNamespace and use in containerd.
ApiVersion int `json:"apiVersion,omitempty"`
// Policies specifies the policy list for HNSEndpoint or HostComputeEndpoint.
Policies []Policy `json:"policies,omitempty"`
// RuntimeConfig represents the options to be passed in by the runtime.
RuntimeConfig RuntimeConfig `json:"runtimeConfig"`
// LoopbackDSR specifies whether to support loopback direct server return.
LoopbackDSR bool `json:"loopbackDSR,omitempty"`
}
type RuntimeDNS struct {
Nameservers []string `json:"servers,omitempty"`
Search []string `json:"searches,omitempty"`
}
type PortMapEntry struct {
HostPort int `json:"hostPort"`
ContainerPort int `json:"containerPort"`
Protocol string `json:"protocol"`
HostIP string `json:"hostIP,omitempty"`
}
// constants of the supported Windows Socket protocol,
// ref to https://docs.microsoft.com/en-us/dotnet/api/system.net.sockets.protocoltype.
var protocolEnums = map[string]uint32{
"icmpv4": 1,
"igmp": 2,
"tcp": 6,
"udp": 17,
"icmpv6": 58,
}
func (p *PortMapEntry) GetProtocolEnum() (uint32, error) {
var u, err = strconv.ParseUint(p.Protocol, 0, 10)
if err != nil {
var pe, exist = protocolEnums[strings.ToLower(p.Protocol)]
if !exist {
return 0, errors.New("invalid protocol supplied to port mapping policy")
}
return pe, nil
}
return uint32(u), nil
}
type RuntimeConfig struct {
DNS RuntimeDNS `json:"dns"`
PortMaps []PortMapEntry `json:"portMappings,omitempty"`
}
type Policy struct {
Name string `json:"name"`
Value json.RawMessage `json:"value"`
}
// GetHNSEndpointPolicies converts the configuration policies to HNSEndpoint policies.
func (n *NetConf) GetHNSEndpointPolicies() []json.RawMessage {
result := make([]json.RawMessage, 0, len(n.Policies))
for _, p := range n.Policies {
if !strings.EqualFold(p.Name, "EndpointPolicy") {
continue
}
result = append(result, p.Value)
}
return result
}
// GetHostComputeEndpointPolicies converts the configuration policies to HostComputeEndpoint policies.
func (n *NetConf) GetHostComputeEndpointPolicies() []hcn.EndpointPolicy {
result := make([]hcn.EndpointPolicy, 0, len(n.Policies))
for _, p := range n.Policies {
if !strings.EqualFold(p.Name, "EndpointPolicy") {
continue
}
var policy hcn.EndpointPolicy
if err := json.Unmarshal(p.Value, &policy); err != nil {
continue
}
result = append(result, policy)
}
return result
}
// GetDNS returns the DNS values if they are there use that else use netconf supplied DNS.
func (n *NetConf) GetDNS() types.DNS {
dnsResult := n.DNS
if len(n.RuntimeConfig.DNS.Nameservers) > 0 {
dnsResult.Nameservers = n.RuntimeConfig.DNS.Nameservers
}
if len(n.RuntimeConfig.DNS.Search) > 0 {
dnsResult.Search = n.RuntimeConfig.DNS.Search
}
return dnsResult
}
// ApplyLoopbackDSRPolicy configures the given IP to support loopback DSR.
func (n *NetConf) ApplyLoopbackDSRPolicy(ip *net.IP) {
if err := hcn.DSRSupported(); err != nil || ip == nil {
return
}
toPolicyValue := func(addr string) json.RawMessage {
if n.ApiVersion == 2 {
return bprintf(`{"Type": "OutBoundNAT", "Settings": {"Destinations": ["%s"]}}`, addr)
}
return bprintf(`{"Type": "OutBoundNAT", "Destinations": ["%s"]}`, addr)
}
ipBytes := []byte(ip.String())
// find OutBoundNAT policy
for i := range n.Policies {
p := &n.Policies[i]
if !strings.EqualFold(p.Name, "EndpointPolicy") {
continue
}
// filter OutBoundNAT policy
typeValue, _ := jsonparser.GetUnsafeString(p.Value, "Type")
if typeValue != "OutBoundNAT" {
continue
}
// parse destination address list
var (
destinationsValue []byte
dt jsonparser.ValueType
)
if n.ApiVersion == 2 {
destinationsValue, dt, _, _ = jsonparser.Get(p.Value, "Settings", "Destinations")
} else {
destinationsValue, dt, _, _ = jsonparser.Get(p.Value, "Destinations")
}
// skip if Destinations/DestinationList field is not found
if dt == jsonparser.NotExist {
continue
}
// return if found the given address
if dt == jsonparser.Array {
var found bool
_, _ = jsonparser.ArrayEach(destinationsValue, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
if dataType == jsonparser.String && len(value) != 0 {
if bytes.Compare(value, ipBytes) == 0 {
found = true
}
}
})
if found {
return
}
}
}
// or add a new OutBoundNAT if not found
n.Policies = append(n.Policies, Policy{
Name: "EndpointPolicy",
Value: toPolicyValue(ip.String()),
})
}
// ApplyOutboundNatPolicy applies the sNAT policy in HNS/HCN and configures the given CIDR as an exception.
func (n *NetConf) ApplyOutboundNatPolicy(exceptionCIDR string) {
if exceptionCIDR == "" {
return
}
toPolicyValue := func(cidr ...string) json.RawMessage {
if n.ApiVersion == 2 {
return bprintf(`{"Type": "OutBoundNAT", "Settings": {"Exceptions": ["%s"]}}`, strings.Join(cidr, `","`))
}
return bprintf(`{"Type": "OutBoundNAT", "ExceptionList": ["%s"]}`, strings.Join(cidr, `","`))
}
exceptionCIDRBytes := []byte(exceptionCIDR)
// find OutBoundNAT policy
for i := range n.Policies {
p := &n.Policies[i]
if !strings.EqualFold(p.Name, "EndpointPolicy") {
continue
}
// filter OutBoundNAT policy
typeValue, _ := jsonparser.GetUnsafeString(p.Value, "Type")
if typeValue != "OutBoundNAT" {
continue
}
// parse exception CIDR list
var (
exceptionsValue []byte
dt jsonparser.ValueType
)
if n.ApiVersion == 2 {
exceptionsValue, dt, _, _ = jsonparser.Get(p.Value, "Settings", "Exceptions")
} else {
exceptionsValue, dt, _, _ = jsonparser.Get(p.Value, "ExceptionList")
}
// skip if Exceptions/ExceptionList field is not found
if dt == jsonparser.NotExist {
continue
}
// return if found the given CIDR
if dt == jsonparser.Array {
var found bool
_, _ = jsonparser.ArrayEach(exceptionsValue, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
if dataType == jsonparser.String && len(value) != 0 {
if bytes.Compare(value, exceptionCIDRBytes) == 0 {
found = true
}
}
})
if found {
return
}
}
}
// or add a new OutBoundNAT if not found
n.Policies = append(n.Policies, Policy{
Name: "EndpointPolicy",
Value: toPolicyValue(exceptionCIDR),
})
}
// ApplyDefaultPAPolicy applies an endpoint PA policy in HNS/HCN.
func (n *NetConf) ApplyDefaultPAPolicy(address string) {
if address == "" {
return
}
toPolicyValue := func(addr string) json.RawMessage {
if n.ApiVersion == 2 {
return bprintf(`{"Type": "ProviderAddress", "Settings": {"ProviderAddress": "%s"}}`, addr)
}
return bprintf(`{"Type": "PA", "PA": "%s"}`, addr)
}
addressBytes := []byte(address)
// find ProviderAddress policy
for i := range n.Policies {
p := &n.Policies[i]
if !strings.EqualFold(p.Name, "EndpointPolicy") {
continue
}
// filter ProviderAddress policy
typeValue, _ := jsonparser.GetUnsafeString(p.Value, "Type")
if typeValue != "PA" && typeValue != "ProviderAddress" {
continue
}
// parse provider address
var (
paValue []byte
dt jsonparser.ValueType
)
if n.ApiVersion == 2 {
paValue, dt, _, _ = jsonparser.Get(p.Value, "Settings", "ProviderAddress")
} else {
paValue, dt, _, _ = jsonparser.Get(p.Value, "PA")
}
// skip if ProviderAddress/PA field is not found
if dt == jsonparser.NotExist {
continue
}
// return if found the given address
if dt == jsonparser.String && bytes.Compare(paValue, addressBytes) == 0 {
return
}
}
// or add a new ProviderAddress if not found
n.Policies = append(n.Policies, Policy{
Name: "EndpointPolicy",
Value: toPolicyValue(address),
})
}
// ApplyPortMappingPolicy applies the host/container port mapping policies in HNS/HCN.
func (n *NetConf) ApplyPortMappingPolicy(portMappings []PortMapEntry) {
if len(portMappings) == 0 {
return
}
toPolicyValue := func(p *PortMapEntry) json.RawMessage {
if n.ApiVersion == 2 {
var protocolEnum, _ = p.GetProtocolEnum()
return bprintf(`{"Type": "PortMapping", "Settings": {"InternalPort": %d, "ExternalPort": %d, "Protocol": %d, "VIP": "%s"}}`, p.ContainerPort, p.HostPort, protocolEnum, p.HostIP)
}
return bprintf(`{"Type": "NAT", "InternalPort": %d, "ExternalPort": %d, "Protocol": "%s"}`, p.ContainerPort, p.HostPort, p.Protocol)
}
for i := range portMappings {
p := &portMappings[i]
// skip the invalid protocol mapping
if _, err := p.GetProtocolEnum(); err != nil {
continue
}
n.Policies = append(n.Policies, Policy{
Name: "EndpointPolicy",
Value: toPolicyValue(p),
})
}
}
// bprintf is similar to fmt.Sprintf and returns a byte array as result.
func bprintf(format string, a ...interface{}) []byte {
return []byte(fmt.Sprintf(format, a...))
}

View File

@ -1,600 +0,0 @@
// 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 hns
import (
"encoding/json"
"net"
"github.com/Microsoft/hcsshim/hcn"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("NetConf", func() {
Describe("ApplyLoopbackDSRPolicy", func() {
Context("via v1 api", func() {
var n NetConf
BeforeEach(func() {
n = NetConf{}
})
It("filter out duplicated IP", func() {
// mock duplicated IP
ip := net.ParseIP("172.16.0.12")
n.ApplyLoopbackDSRPolicy(&ip)
n.ApplyLoopbackDSRPolicy(&ip)
// only one policy
addlArgs := n.Policies
Expect(addlArgs).Should(HaveLen(1))
// normal type judgement
policy := addlArgs[0]
Expect(policy.Name).Should(Equal("EndpointPolicy"))
value := make(map[string]interface{})
json.Unmarshal(policy.Value, &value)
Expect(value).Should(HaveKey("Type"))
Expect(value["Type"]).Should(Equal("OutBoundNAT"))
Expect(value).Should(HaveKey("Destinations"))
// and only one item
destinationList := value["Destinations"].([]interface{})
Expect(destinationList).Should(HaveLen(1))
Expect(destinationList[0].(string)).Should(Equal("172.16.0.12"))
})
It("append different IP", func() {
// mock different IP
ip1 := net.ParseIP("172.16.0.12")
n.ApplyLoopbackDSRPolicy(&ip1)
ip2 := net.ParseIP("172.16.0.13")
n.ApplyLoopbackDSRPolicy(&ip2)
// will be two policies
addlArgs := n.Policies
Expect(addlArgs).Should(HaveLen(2))
// normal type judgement
policy := addlArgs[1] // pick second item
Expect(policy.Name).Should(Equal("EndpointPolicy"))
value := make(map[string]interface{})
json.Unmarshal(policy.Value, &value)
Expect(value).Should(HaveKey("Type"))
Expect(value["Type"]).Should(Equal("OutBoundNAT"))
Expect(value).Should(HaveKey("Destinations"))
// only one item
destinationList := value["Destinations"].([]interface{})
Expect(destinationList).Should(HaveLen(1))
Expect(destinationList[0].(string)).Should(Equal("172.16.0.13"))
})
})
Context("via v2 api", func() {
var n NetConf
BeforeEach(func() {
n = NetConf{ApiVersion: 2}
})
It("filter out duplicated IP", func() {
// mock duplicated IP
ip := net.ParseIP("172.16.0.12")
n.ApplyLoopbackDSRPolicy(&ip)
n.ApplyLoopbackDSRPolicy(&ip)
// only one policy
addlArgs := n.Policies
Expect(addlArgs).Should(HaveLen(1))
// normal type judgement
policy := addlArgs[0]
Expect(policy.Name).Should(Equal("EndpointPolicy"))
value := make(map[string]interface{})
json.Unmarshal(policy.Value, &value)
Expect(value).Should(HaveKey("Type"))
Expect(value["Type"]).Should(Equal("OutBoundNAT"))
Expect(value).Should(HaveKey("Settings"))
// and only one item
settings := value["Settings"].(map[string]interface{})
destinationList := settings["Destinations"].([]interface{})
Expect(destinationList).Should(HaveLen(1))
Expect(destinationList[0].(string)).Should(Equal("172.16.0.12"))
})
It("append different IP", func() {
// mock different IP
ip1 := net.ParseIP("172.16.0.12")
n.ApplyLoopbackDSRPolicy(&ip1)
ip2 := net.ParseIP("172.16.0.13")
n.ApplyLoopbackDSRPolicy(&ip2)
// will be two policies
addlArgs := n.Policies
Expect(addlArgs).Should(HaveLen(2))
// normal type judgement
policy := addlArgs[1] // pick second item
Expect(policy.Name).Should(Equal("EndpointPolicy"))
value := make(map[string]interface{})
json.Unmarshal(policy.Value, &value)
Expect(value).Should(HaveKey("Type"))
Expect(value["Type"]).Should(Equal("OutBoundNAT"))
Expect(value).Should(HaveKey("Settings"))
// only one item
settings := value["Settings"].(map[string]interface{})
destinationList := settings["Destinations"].([]interface{})
Expect(destinationList).Should(HaveLen(1))
Expect(destinationList[0].(string)).Should(Equal("172.16.0.13"))
})
})
})
Describe("ApplyOutBoundNATPolicy", func() {
Context("via v1 api", func() {
var n NetConf
BeforeEach(func() {
n = NetConf{}
})
It("append different IP", func() {
// mock different IP
n.ApplyOutboundNatPolicy("192.168.0.0/16")
n.ApplyOutboundNatPolicy("10.244.0.0/16")
// will be two policies
addlArgs := n.Policies
Expect(addlArgs).Should(HaveLen(2))
// normal type judgement
policy := addlArgs[1] // pick second item
Expect(policy.Name).Should(Equal("EndpointPolicy"))
value := make(map[string]interface{})
json.Unmarshal(policy.Value, &value)
Expect(value).Should(HaveKey("Type"))
Expect(value["Type"]).Should(Equal("OutBoundNAT"))
Expect(value).Should(HaveKey("ExceptionList"))
// but get two items
exceptionList := value["ExceptionList"].([]interface{})
Expect(exceptionList).Should(HaveLen(1))
Expect(exceptionList[0].(string)).Should(Equal("10.244.0.0/16"))
})
It("append a new one if there is not an exception OutBoundNAT policy", func() {
// mock different OutBoundNAT routes
n.Policies = []Policy{
{
Name: "EndpointPolicy",
Value: bprintf(`{"Type": "OutBoundNAT", "OtherList": []}`),
},
}
n.ApplyOutboundNatPolicy("10.244.0.0/16")
// has two policies
addlArgs := n.Policies
Expect(addlArgs).Should(HaveLen(2))
// normal type judgement
policy := addlArgs[0]
Expect(policy.Name).Should(Equal("EndpointPolicy"))
value := make(map[string]interface{})
json.Unmarshal(policy.Value, &value)
Expect(value).Should(HaveKey("Type"))
Expect(value["Type"]).Should(Equal("OutBoundNAT"))
Expect(value).Should(HaveKey("OtherList"))
policy = addlArgs[1]
value = make(map[string]interface{})
json.Unmarshal(policy.Value, &value)
Expect(value).Should(HaveKey("Type"))
Expect(value["Type"]).Should(Equal("OutBoundNAT"))
Expect(value).Should(HaveKey("ExceptionList"))
// only get one item
exceptionList := value["ExceptionList"].([]interface{})
Expect(exceptionList).Should(HaveLen(1))
Expect(exceptionList[0].(string)).Should(Equal("10.244.0.0/16"))
})
It("nothing to do if CIDR is blank", func() {
// mock different OutBoundNAT routes
n.Policies = []Policy{
{
Name: "EndpointPolicy",
Value: bprintf(`{"Type": "OutBoundNAT", "ExceptionList": []}`),
},
}
n.ApplyOutboundNatPolicy("")
// only one policy
addlArgs := n.Policies
Expect(addlArgs).Should(HaveLen(1))
// normal type judgement
policy := addlArgs[0]
Expect(policy.Name).Should(Equal("EndpointPolicy"))
value := make(map[string]interface{})
json.Unmarshal(policy.Value, &value)
Expect(value).Should(HaveKey("Type"))
Expect(value["Type"]).Should(Equal("OutBoundNAT"))
Expect(value).Should(HaveKey("ExceptionList"))
// empty list
Expect(value["ExceptionList"]).ShouldNot(BeNil())
Expect(value["ExceptionList"]).Should(HaveLen(0))
})
})
Context("via v2 api", func() {
var n NetConf
BeforeEach(func() {
n = NetConf{ApiVersion: 2}
})
It("append different IP", func() {
// mock different IP
n.ApplyOutboundNatPolicy("192.168.0.0/16")
n.ApplyOutboundNatPolicy("10.244.0.0/16")
// will be two policies
addlArgs := n.Policies
Expect(addlArgs).Should(HaveLen(2))
// normal type judgement
policy := addlArgs[1] // pick second item
Expect(policy.Name).Should(Equal("EndpointPolicy"))
value := make(map[string]interface{})
json.Unmarshal(policy.Value, &value)
Expect(value).Should(HaveKey("Type"))
Expect(value["Type"]).Should(Equal("OutBoundNAT"))
Expect(value).Should(HaveKey("Settings"))
// but get two items
settings := value["Settings"].(map[string]interface{})
exceptionList := settings["Exceptions"].([]interface{})
Expect(exceptionList).Should(HaveLen(1))
Expect(exceptionList[0].(string)).Should(Equal("10.244.0.0/16"))
})
It("append a new one if there is not an exception OutBoundNAT policy", func() {
// mock different OutBoundNAT routes
n.Policies = []Policy{
{
Name: "EndpointPolicy",
Value: bprintf(`{"Type": "OutBoundNAT", "Settings": {"Others": []}}`),
},
}
n.ApplyOutboundNatPolicy("10.244.0.0/16")
// has two policies
addlArgs := n.Policies
Expect(addlArgs).Should(HaveLen(2))
// normal type judgement
policy := addlArgs[0]
Expect(policy.Name).Should(Equal("EndpointPolicy"))
value := make(map[string]interface{})
json.Unmarshal(policy.Value, &value)
Expect(value).Should(HaveKey("Type"))
Expect(value["Type"]).Should(Equal("OutBoundNAT"))
Expect(value).Should(HaveKey("Settings"))
Expect(value["Settings"]).Should(HaveKey("Others"))
policy = addlArgs[1]
value = make(map[string]interface{})
json.Unmarshal(policy.Value, &value)
Expect(value).Should(HaveKey("Type"))
Expect(value["Type"]).Should(Equal("OutBoundNAT"))
Expect(value).Should(HaveKey("Settings"))
// only get one item
settings := value["Settings"].(map[string]interface{})
exceptionList := settings["Exceptions"].([]interface{})
Expect(exceptionList).Should(HaveLen(1))
Expect(exceptionList[0].(string)).Should(Equal("10.244.0.0/16"))
})
It("nothing to do if CIDR is blank", func() {
// mock different OutBoundNAT routes
n.Policies = []Policy{
{
Name: "EndpointPolicy",
Value: bprintf(`{"Type": "OutBoundNAT", "Settings": {"Exceptions": []}}`),
},
}
n.ApplyOutboundNatPolicy("")
// only one policy
addlArgs := n.Policies
Expect(addlArgs).Should(HaveLen(1))
// normal type judgement
policy := addlArgs[0]
Expect(policy.Name).Should(Equal("EndpointPolicy"))
value := make(map[string]interface{})
json.Unmarshal(policy.Value, &value)
Expect(value).Should(HaveKey("Type"))
Expect(value["Type"]).Should(Equal("OutBoundNAT"))
Expect(value).Should(HaveKey("Settings"))
// empty list
settings := value["Settings"].(map[string]interface{})
Expect(settings["Exceptions"]).ShouldNot(BeNil())
Expect(settings["Exceptions"]).Should(HaveLen(0))
})
})
})
Describe("ApplyDefaultPAPolicy", func() {
Context("via v1 api", func() {
var n NetConf
BeforeEach(func() {
n = NetConf{}
})
It("append different IP", func() {
// mock different IP
n.ApplyDefaultPAPolicy("192.168.0.1")
n.ApplyDefaultPAPolicy("192.168.0.2")
// will be two policies
addlArgs := n.Policies
Expect(addlArgs).Should(HaveLen(2))
// normal type judgement
policy := addlArgs[1] // judge second item
Expect(policy.Name).Should(Equal("EndpointPolicy"))
value := make(map[string]interface{})
json.Unmarshal(policy.Value, &value)
Expect(value).Should(HaveKey("Type"))
Expect(value["Type"]).Should(Equal("PA"))
// compare with second item
paAddress := value["PA"].(string)
Expect(paAddress).Should(Equal("192.168.0.2"))
})
It("nothing to do if IP is blank", func() {
// mock different policy
n.Policies = []Policy{
{
Name: "EndpointPolicy",
Value: bprintf(`{"Type": "OutBoundNAT", "Exceptions": ["192.168.0.0/16"]}`),
},
}
n.ApplyDefaultPAPolicy("")
// nothing
addlArgs := n.Policies
Expect(addlArgs).Should(HaveLen(1))
})
})
Context("via v2 api", func() {
var n NetConf
BeforeEach(func() {
n = NetConf{ApiVersion: 2}
})
It("append different IP", func() {
// mock different IP
n.ApplyDefaultPAPolicy("192.168.0.1")
n.ApplyDefaultPAPolicy("192.168.0.2")
// will be two policies
addlArgs := n.Policies
Expect(addlArgs).Should(HaveLen(2))
// normal type judgement
policy := addlArgs[1] // judge second item
Expect(policy.Name).Should(Equal("EndpointPolicy"))
value := make(map[string]interface{})
json.Unmarshal(policy.Value, &value)
Expect(value).Should(HaveKey("Type"))
Expect(value["Type"]).Should(Equal("ProviderAddress"))
Expect(value).Should(HaveKey("Settings"))
// compare with second item
settings := value["Settings"].(map[string]interface{})
paAddress := settings["ProviderAddress"].(string)
Expect(paAddress).Should(Equal("192.168.0.2"))
})
It("nothing to do if IP is blank", func() {
// mock different policy
n.Policies = []Policy{
{
Name: "EndpointPolicy",
Value: bprintf(`{"Type": "OutBoundNAT", "Settings": {"Exceptions": ["192.168.0.0/16"]}}`),
},
}
n.ApplyDefaultPAPolicy("")
// nothing
addlArgs := n.Policies
Expect(addlArgs).Should(HaveLen(1))
})
})
})
Describe("ApplyPortMappingPolicy", func() {
Context("via v1 api", func() {
var n NetConf
BeforeEach(func() {
n = NetConf{}
})
It("nothing to do if input is empty", func() {
n.ApplyPortMappingPolicy(nil)
Expect(n.Policies).Should(BeNil())
n.ApplyPortMappingPolicy([]PortMapEntry{})
Expect(n.Policies).Should(BeNil())
})
It("create one NAT policy", func() {
// mock different IP
n.ApplyPortMappingPolicy([]PortMapEntry{
{
ContainerPort: 80,
HostPort: 8080,
Protocol: "TCP",
HostIP: "192.168.1.2",
},
})
// only one item
addlArgs := n.Policies
Expect(addlArgs).Should(HaveLen(1))
// normal type judgement
policy := addlArgs[0]
Expect(policy.Name).Should(Equal("EndpointPolicy"))
value := make(map[string]interface{})
json.Unmarshal(policy.Value, &value)
Expect(value).Should(HaveKey("Type"))
Expect(value["Type"]).Should(Equal("NAT"))
// compare all values
Expect(value).Should(HaveKey("InternalPort"))
Expect(value["InternalPort"]).Should(Equal(float64(80)))
Expect(value).Should(HaveKey("ExternalPort"))
Expect(value["ExternalPort"]).Should(Equal(float64(8080)))
Expect(value).Should(HaveKey("Protocol"))
Expect(value["Protocol"]).Should(Equal("TCP"))
})
})
Context("via v2 api", func() {
var n NetConf
BeforeEach(func() {
n = NetConf{ApiVersion: 2}
})
It("nothing to do if input is empty", func() {
n.ApplyPortMappingPolicy(nil)
Expect(n.Policies).Should(BeNil())
n.ApplyPortMappingPolicy([]PortMapEntry{})
Expect(n.Policies).Should(BeNil())
})
It("creates one NAT policy", func() {
// mock different IP
n.ApplyPortMappingPolicy([]PortMapEntry{
{
ContainerPort: 80,
HostPort: 8080,
Protocol: "TCP",
HostIP: "192.168.1.2",
},
})
// only one item
addlArgs := n.Policies
Expect(addlArgs).Should(HaveLen(1))
// normal type judgement
policy := addlArgs[0]
Expect(policy.Name).Should(Equal("EndpointPolicy"))
value := make(map[string]interface{})
json.Unmarshal(policy.Value, &value)
Expect(value).Should(HaveKey("Type"))
Expect(value["Type"]).Should(Equal("PortMapping"))
Expect(value).Should(HaveKey("Settings"))
// compare all values
settings := value["Settings"].(map[string]interface{})
Expect(settings).Should(HaveKey("InternalPort"))
Expect(settings["InternalPort"]).Should(Equal(float64(80)))
Expect(settings).Should(HaveKey("ExternalPort"))
Expect(settings["ExternalPort"]).Should(Equal(float64(8080)))
Expect(settings).Should(HaveKey("Protocol"))
Expect(settings["Protocol"]).Should(Equal(float64(6)))
Expect(settings).Should(HaveKey("VIP"))
Expect(settings["VIP"]).Should(Equal("192.168.1.2"))
})
})
})
Describe("GetXEndpointPolicies", func() {
Context("via v1 api", func() {
var n NetConf
BeforeEach(func() {
n = NetConf{}
})
It("GetHNSEndpointPolicies", func() {
// mock different policies
n.Policies = []Policy{
{
Name: "EndpointPolicy",
Value: []byte(`{"Type": "OutBoundNAT", "ExceptionList": [ "192.168.1.2" ]}`),
},
{
Name: "someOtherType",
Value: []byte(`{"someOtherKey": "someOtherValue"}`),
},
}
// only one valid item
result := n.GetHNSEndpointPolicies()
Expect(len(result)).To(Equal(1))
// normal type judgement
policy := make(map[string]interface{})
err := json.Unmarshal(result[0], &policy)
Expect(err).ToNot(HaveOccurred())
Expect(policy).Should(HaveKey("Type"))
Expect(policy["Type"]).To(Equal("OutBoundNAT"))
Expect(policy).Should(HaveKey("ExceptionList"))
Expect(policy["ExceptionList"]).To(ContainElement("192.168.1.2"))
})
})
Context("via v2 api", func() {
var n NetConf
BeforeEach(func() {
n = NetConf{ApiVersion: 2}
})
It("GetHostComputeEndpointPolicies", func() {
// mock different policies
n.Policies = []Policy{
{
Name: "EndpointPolicy",
Value: []byte(`{"Type": "OutBoundNAT", "Settings": {"Exceptions": [ "192.168.1.2" ]}}`),
},
{
Name: "someOtherType",
Value: []byte(`{"someOtherKey": "someOtherValue"}`),
},
}
// only one valid item
result := n.GetHostComputeEndpointPolicies()
Expect(len(result)).To(Equal(1))
// normal type judgement
policy := result[0]
Expect(policy.Type).Should(Equal(hcn.OutBoundNAT))
settings := make(map[string]interface{})
err := json.Unmarshal(policy.Settings, &settings)
Expect(err).ToNot(HaveOccurred())
Expect(settings["Exceptions"]).To(ContainElement("192.168.1.2"))
})
})
})
})

View File

@ -15,7 +15,6 @@
package invoke
import (
"fmt"
"os"
"strings"
)
@ -23,8 +22,6 @@ import (
type CNIArgs interface {
// For use with os/exec; i.e., return nil to inherit the
// environment from this process
// For use in delegation; inherit the environment from this
// process and allow overrides
AsEnv() []string
}
@ -32,7 +29,7 @@ type inherited struct{}
var inheritArgsFromEnv inherited
func (*inherited) AsEnv() []string {
func (_ *inherited) AsEnv() []string {
return nil
}
@ -50,9 +47,6 @@ type Args struct {
Path string
}
// Args implements the CNIArgs interface
var _ CNIArgs = &Args{}
func (args *Args) AsEnv() []string {
env := os.Environ()
pluginArgsStr := args.PluginArgsStr
@ -60,17 +54,14 @@ func (args *Args) AsEnv() []string {
pluginArgsStr = stringify(args.PluginArgs)
}
// Duplicated values which come first will be overridden, so we must put the
// custom values in the end to avoid being overridden by the process environments.
env = append(env,
"CNI_COMMAND="+args.Command,
"CNI_CONTAINERID="+args.ContainerID,
"CNI_NETNS="+args.NetNS,
"CNI_ARGS="+pluginArgsStr,
"CNI_IFNAME="+args.IfName,
"CNI_PATH="+args.Path,
)
return dedupEnv(env)
"CNI_PATH="+args.Path)
return env
}
// taken from rkt/networking/net_plugin.go
@ -83,46 +74,3 @@ func stringify(pluginArgs [][2]string) string {
return strings.Join(entries, ";")
}
// DelegateArgs implements the CNIArgs interface
// used for delegation to inherit from environments
// and allow some overrides like CNI_COMMAND
var _ CNIArgs = &DelegateArgs{}
type DelegateArgs struct {
Command string
}
func (d *DelegateArgs) AsEnv() []string {
env := os.Environ()
// The custom values should come in the end to override the existing
// process environment of the same key.
env = append(env,
"CNI_COMMAND="+d.Command,
)
return dedupEnv(env)
}
// dedupEnv returns a copy of env with any duplicates removed, in favor of later values.
// Items not of the normal environment "key=value" form are preserved unchanged.
func dedupEnv(env []string) []string {
out := make([]string, 0, len(env))
envMap := map[string]string{}
for _, kv := range env {
// find the first "=" in environment, if not, just keep it
eq := strings.Index(kv, "=")
if eq < 0 {
out = append(out, kv)
continue
}
envMap[kv[:eq]] = kv[eq+1:]
}
for k, v := range envMap {
out = append(out, fmt.Sprintf("%s=%s", k, v))
}
return out
}

39
pkg/invoke/delegate.go Normal file
View File

@ -0,0 +1,39 @@
package invoke
import (
"fmt"
"os"
"strings"
"github.com/appc/cni/pkg/types"
)
func DelegateAdd(delegatePlugin string, netconf []byte) (*types.Result, error) {
if os.Getenv("CNI_COMMAND") != "ADD" {
return nil, fmt.Errorf("CNI_COMMAND is not ADD")
}
paths := strings.Split(os.Getenv("CNI_PATH"), ":")
pluginPath, err := FindInPath(delegatePlugin, paths)
if err != nil {
return nil, err
}
return ExecPluginWithResult(pluginPath, netconf, ArgsFromEnv())
}
func DelegateDel(delegatePlugin string, netconf []byte) error {
if os.Getenv("CNI_COMMAND") != "DEL" {
return fmt.Errorf("CNI_COMMAND is not DEL")
}
paths := strings.Split(os.Getenv("CNI_PATH"), ":")
pluginPath, err := FindInPath(delegatePlugin, paths)
if err != nil {
return err
}
return ExecPluginWithoutResult(pluginPath, netconf, ArgsFromEnv())
}

75
pkg/invoke/exec.go Normal file
View File

@ -0,0 +1,75 @@
// 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 invoke
import (
"bytes"
"encoding/json"
"fmt"
"os"
"os/exec"
"github.com/appc/cni/pkg/types"
)
func pluginErr(err error, output []byte) error {
if _, ok := err.(*exec.ExitError); ok {
emsg := types.Error{}
if perr := json.Unmarshal(output, &emsg); perr != nil {
return fmt.Errorf("netplugin failed but error parsing its diagnostic message %q: %v", string(output), perr)
}
details := ""
if emsg.Details != "" {
details = fmt.Sprintf("; %v", emsg.Details)
}
return fmt.Errorf("%v%v", emsg.Msg, details)
}
return err
}
func ExecPluginWithResult(pluginPath string, netconf []byte, args CNIArgs) (*types.Result, error) {
stdoutBytes, err := execPlugin(pluginPath, netconf, args)
if err != nil {
return nil, err
}
res := &types.Result{}
err = json.Unmarshal(stdoutBytes, res)
return res, err
}
func ExecPluginWithoutResult(pluginPath string, netconf []byte, args CNIArgs) error {
_, err := execPlugin(pluginPath, netconf, args)
return err
}
func execPlugin(pluginPath string, netconf []byte, args CNIArgs) ([]byte, error) {
stdout := &bytes.Buffer{}
c := exec.Cmd{
Env: args.AsEnv(),
Path: pluginPath,
Args: []string{pluginPath},
Stdin: bytes.NewBuffer(netconf),
Stdout: stdout,
Stderr: os.Stderr,
}
if err := c.Run(); err != nil {
return nil, pluginErr(err, stdout.Bytes())
}
return stdout.Bytes(), nil
}

View File

@ -18,7 +18,6 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
)
// FindInPath returns the full path of the plugin by searching in the provided path
@ -27,22 +26,22 @@ func FindInPath(plugin string, paths []string) (string, error) {
return "", fmt.Errorf("no plugin name provided")
}
if strings.ContainsRune(plugin, os.PathSeparator) {
return "", fmt.Errorf("invalid plugin name: %s", plugin)
}
if len(paths) == 0 {
return "", fmt.Errorf("no paths provided")
}
var fullpath string
for _, path := range paths {
for _, fe := range ExecutableFileExtensions {
fullpath := filepath.Join(path, plugin) + fe
if fi, err := os.Stat(fullpath); err == nil && fi.Mode().IsRegular() {
return fullpath, nil
}
full := filepath.Join(path, plugin)
if fi, err := os.Stat(full); err == nil && fi.Mode().IsRegular() {
fullpath = full
break
}
}
return "", fmt.Errorf("failed to find plugin %q in path %s", plugin, paths)
if fullpath == "" {
return "", fmt.Errorf("failed to find plugin %q in path %s", plugin, paths)
}
return fullpath, nil
}

64
pkg/invoke/find_test.go Normal file
View File

@ -0,0 +1,64 @@
package invoke_test
import (
"fmt"
"io/ioutil"
"path/filepath"
"github.com/appc/cni/pkg/invoke"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("FindInPath", func() {
var (
multiplePaths []string
pluginName string
pluginDir string
anotherTempDir string
)
BeforeEach(func() {
tempDir, err := ioutil.TempDir("", "cni-find")
Expect(err).NotTo(HaveOccurred())
plugin, err := ioutil.TempFile(tempDir, "a-cni-plugin")
anotherTempDir, err = ioutil.TempDir("", "nothing-here")
multiplePaths = []string{anotherTempDir, tempDir}
pluginDir, pluginName = filepath.Split(plugin.Name())
})
Context("when multiple paths are provided", func() {
It("returns only the path to the plugin", func() {
pluginPath, err := invoke.FindInPath(pluginName, multiplePaths)
Expect(err).NotTo(HaveOccurred())
Expect(pluginPath).To(Equal(filepath.Join(pluginDir, pluginName)))
})
})
Context("when an error occurs", func() {
Context("when no paths are provided", func() {
It("returns an error noting no paths were provided", func() {
_, err := invoke.FindInPath(pluginName, []string{})
Expect(err).To(MatchError("no paths provided"))
})
})
Context("when no plugin is provided", func() {
It("returns an error noting the plugin name wasn't found", func() {
_, err := invoke.FindInPath("", multiplePaths)
Expect(err).To(MatchError("no plugin name provided"))
})
})
Context("when the plugin cannot be found", func() {
It("returns an error noting the path", func() {
pathsWithNothing := []string{anotherTempDir}
_, err := invoke.FindInPath(pluginName, pathsWithNothing)
Expect(err).To(MatchError(fmt.Sprintf("failed to find plugin %q in path %s", pluginName, pathsWithNothing)))
})
})
})
})

View File

@ -1,4 +1,4 @@
package main_test
package invoke_test
import (
. "github.com/onsi/ginkgo"
@ -7,7 +7,7 @@ import (
"testing"
)
func TestEchosvr(t *testing.T) {
func TestInvoke(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "pkg/testutils/echosvr")
RunSpecs(t, "Invoke Suite")
}

View File

@ -1,68 +0,0 @@
// 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 ip
import (
"fmt"
"syscall"
"time"
"github.com/vishvananda/netlink"
)
const SETTLE_INTERVAL = 50 * time.Millisecond
// SettleAddresses waits for all addresses on a link to leave tentative state.
// This is particularly useful for ipv6, where all addresses need to do DAD.
// There is no easy way to wait for this as an event, so just loop until the
// addresses are no longer tentative.
// If any addresses are still tentative after timeout seconds, then error.
func SettleAddresses(ifName string, timeout int) error {
link, err := netlink.LinkByName(ifName)
if err != nil {
return fmt.Errorf("failed to retrieve link: %v", err)
}
deadline := time.Now().Add(time.Duration(timeout) * time.Second)
for {
addrs, err := netlink.AddrList(link, netlink.FAMILY_ALL)
if err != nil {
return fmt.Errorf("could not list addresses: %v", err)
}
if len(addrs) == 0 {
return nil
}
ok := true
for _, addr := range addrs {
if addr.Flags&(syscall.IFA_F_TENTATIVE|syscall.IFA_F_DADFAILED) > 0 {
ok = false
break // Break out of the `range addrs`, not the `for`
}
}
if ok {
return nil
}
if time.Now().After(deadline) {
return fmt.Errorf("link %s still has tentative addresses after %d seconds",
ifName,
timeout)
}
time.Sleep(SETTLE_INTERVAL)
}
}

View File

@ -31,16 +31,6 @@ func PrevIP(ip net.IP) net.IP {
return intToIP(i.Sub(i, big.NewInt(1)))
}
// Cmp compares two IPs, returning the usual ordering:
// a < b : -1
// a == b : 0
// a > b : 1
func Cmp(a, b net.IP) int {
aa := ipToInt(a)
bb := ipToInt(b)
return aa.Cmp(bb)
}
func ipToInt(ip net.IP) *big.Int {
if v := ip.To4(); v != nil {
return big.NewInt(0).SetBytes(v)

View File

@ -1,105 +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 ip
import (
"fmt"
"net"
"strings"
)
// IP is a CNI maintained type inherited from net.IPNet which can
// represent a single IP address with or without prefix.
type IP struct {
net.IPNet
}
// newIP will create an IP with net.IP and net.IPMask
func newIP(ip net.IP, mask net.IPMask) *IP {
return &IP{
IPNet: net.IPNet{
IP: ip,
Mask: mask,
},
}
}
// ParseIP will parse string s as an IP, and return it.
// The string s must be formed like <ip>[/<prefix>].
// If s is not a valid textual representation of an IP,
// will return nil.
func ParseIP(s string) *IP {
if strings.ContainsAny(s, "/") {
ip, ipNet, err := net.ParseCIDR(s)
if err != nil {
return nil
}
return newIP(ip, ipNet.Mask)
} else {
ip := net.ParseIP(s)
if ip == nil {
return nil
}
return newIP(ip, nil)
}
}
// ToIP will return a net.IP in standard form from this IP.
// If this IP can not be converted to a valid net.IP, will return nil.
func (i *IP) ToIP() net.IP {
switch {
case i.IP.To4() != nil:
return i.IP.To4()
case i.IP.To16() != nil:
return i.IP.To16()
default:
return nil
}
}
// String returns the string form of this IP.
func (i *IP) String() string {
if len(i.Mask) > 0 {
return i.IPNet.String()
}
return i.IP.String()
}
// MarshalText implements the encoding.TextMarshaler interface.
// The encoding is the same as returned by String,
// But when len(ip) is zero, will return an empty slice.
func (i *IP) MarshalText() ([]byte, error) {
if len(i.IP) == 0 {
return []byte{}, nil
}
return []byte(i.String()), nil
}
// UnmarshalText implements the encoding.TextUnmarshaler interface.
// The textual bytes are expected in a form accepted by Parse,
// But when len(b) is zero, will return an empty IP.
func (i *IP) UnmarshalText(b []byte) error {
if len(b) == 0 {
*i = IP{}
return nil
}
ip := ParseIP(string(b))
if ip == nil {
return fmt.Errorf("invalid IP address %s", string(b))
}
*i = *ip
return nil
}

View File

@ -1,272 +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 ip
import (
"encoding/json"
"fmt"
"net"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("IP Operations", func() {
It("Parse", func() {
testCases := []struct {
ipStr string
expected *IP
}{
{
"192.168.0.10",
newIP(net.IPv4(192, 168, 0, 10), nil),
},
{
"2001:db8::1",
newIP(net.ParseIP("2001:db8::1"), nil),
},
{
"192.168.0.10/24",
newIP(net.IPv4(192, 168, 0, 10), net.IPv4Mask(255, 255, 255, 0)),
},
{
"2001:db8::1/64",
newIP(net.ParseIP("2001:db8::1"), net.CIDRMask(64, 128)),
},
{
"invalid",
nil,
},
}
for _, test := range testCases {
ip := ParseIP(test.ipStr)
Expect(ip).To(Equal(test.expected))
}
})
It("String", func() {
testCases := []struct {
ip *IP
expected string
}{
{
newIP(net.IPv4(192, 168, 0, 1), net.IPv4Mask(255, 255, 255, 0)),
"192.168.0.1/24",
},
{
newIP(net.IPv4(192, 168, 0, 2), nil),
"192.168.0.2",
},
{
newIP(net.ParseIP("2001:db8::1"), nil),
"2001:db8::1",
},
{
newIP(net.ParseIP("2001:db8::1"), net.CIDRMask(64, 128)),
"2001:db8::1/64",
},
{
newIP(nil, nil),
"<nil>",
},
}
for _, test := range testCases {
Expect(test.ip.String()).To(Equal(test.expected))
}
})
It("ToIP", func() {
testCases := []struct {
ip *IP
expectedLen int
expectedIP net.IP
}{
{
newIP(net.IPv4(192, 168, 0, 1), net.IPv4Mask(255, 255, 255, 0)),
net.IPv4len,
net.IP{192, 168, 0, 1},
},
{
newIP(net.IPv4(192, 168, 0, 2), nil),
net.IPv4len,
net.IP{192, 168, 0, 2},
},
{
newIP(net.ParseIP("2001:db8::1"), nil),
net.IPv6len,
net.IP{32, 1, 13, 184, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
},
{
newIP(net.ParseIP("2001:db8::1"), net.CIDRMask(64, 128)),
net.IPv6len,
net.IP{32, 1, 13, 184, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
},
{
newIP(nil, nil),
0,
nil,
},
}
for _, test := range testCases {
Expect(len(test.ip.ToIP())).To(Equal(test.expectedLen))
Expect(test.ip.ToIP()).To(Equal(test.expectedIP))
}
})
It("Encode", func() {
testCases := []struct {
object interface{}
expected string
}{
{
newIP(net.IPv4(192, 168, 0, 1), net.IPv4Mask(255, 255, 255, 0)),
`"192.168.0.1/24"`,
},
{
newIP(net.IPv4(192, 168, 0, 2), nil),
`"192.168.0.2"`,
},
{
newIP(net.ParseIP("2001:db8::1"), nil),
`"2001:db8::1"`,
},
{
newIP(net.ParseIP("2001:db8::1"), net.CIDRMask(64, 128)),
`"2001:db8::1/64"`,
},
{
newIP(nil, nil),
`""`,
},
{
[]*IP{
newIP(net.IPv4(192, 168, 0, 1), net.IPv4Mask(255, 255, 255, 0)),
newIP(net.IPv4(192, 168, 0, 2), nil),
newIP(net.ParseIP("2001:db8::1"), nil),
newIP(net.ParseIP("2001:db8::1"), net.CIDRMask(64, 128)),
newIP(nil, nil),
},
`["192.168.0.1/24","192.168.0.2","2001:db8::1","2001:db8::1/64",""]`,
},
}
for _, test := range testCases {
bytes, err := json.Marshal(test.object)
Expect(err).NotTo(HaveOccurred())
Expect(string(bytes)).To(Equal(test.expected))
}
})
It("Decode", func() {
Context("valid IP", func() {
testCases := []struct {
text string
expected *IP
}{
{
`"192.168.0.1"`,
newIP(net.IPv4(192, 168, 0, 1), nil),
},
{
`"192.168.0.1/24"`,
newIP(net.IPv4(192, 168, 0, 1), net.IPv4Mask(255, 255, 255, 0)),
},
{
`"2001:db8::1"`,
newIP(net.ParseIP("2001:db8::1"), nil),
},
{
`"2001:db8::1/64"`,
newIP(net.ParseIP("2001:db8::1"), net.CIDRMask(64, 128)),
},
}
for _, test := range testCases {
ip := &IP{}
err := json.Unmarshal([]byte(test.text), ip)
Expect(err).NotTo(HaveOccurred())
Expect(ip).To(Equal(test.expected))
}
})
Context("empty text", func() {
ip := &IP{}
err := json.Unmarshal([]byte(`""`), ip)
Expect(err).NotTo(HaveOccurred())
Expect(ip).To(Equal(newIP(nil, nil)))
})
Context("invalid IP", func() {
testCases := []struct {
text string
expectedErr error
}{
{
`"192.168.0.1000"`,
fmt.Errorf("invalid IP address 192.168.0.1000"),
},
{
`"2001:db8::1/256"`,
fmt.Errorf("invalid IP address 2001:db8::1/256"),
},
{
`"test"`,
fmt.Errorf("invalid IP address test"),
},
}
for _, test := range testCases {
err := json.Unmarshal([]byte(test.text), &IP{})
Expect(err).To(HaveOccurred())
Expect(err).To(Equal(test.expectedErr))
}
})
Context("IP slice", func() {
testCases := []struct {
text string
expected []*IP
}{
{
`["192.168.0.1/24","192.168.0.2","2001:db8::1","2001:db8::1/64",""]`,
[]*IP{
newIP(net.IPv4(192, 168, 0, 1), net.IPv4Mask(255, 255, 255, 0)),
newIP(net.IPv4(192, 168, 0, 2), nil),
newIP(net.ParseIP("2001:db8::1"), nil),
newIP(net.ParseIP("2001:db8::1"), net.CIDRMask(64, 128)),
newIP(nil, nil),
},
},
}
for _, test := range testCases {
ips := make([]*IP, 0)
err := json.Unmarshal([]byte(test.text), &ips)
Expect(err).NotTo(HaveOccurred())
Expect(ips).To(Equal(test.expected))
}
})
})
})

View File

@ -1,4 +1,4 @@
// Copyright 2016 CNI authors
// 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.
@ -12,16 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package ip_test
package ip
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
"io/ioutil"
)
func TestIp(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "pkg/ip")
func EnableIP4Forward() error {
return echo1("/proc/sys/net/ipv4/ip_forward")
}
func EnableIP6Forward() error {
return echo1("/proc/sys/net/ipv6/conf/all/forwarding")
}
func echo1(f string) error {
return ioutil.WriteFile(f, []byte("1"), 0644)
}

View File

@ -1,62 +0,0 @@
// 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 (
"bytes"
"io/ioutil"
current "github.com/containernetworking/cni/pkg/types/100"
)
func EnableIP4Forward() error {
return echo1("/proc/sys/net/ipv4/ip_forward")
}
func EnableIP6Forward() error {
return echo1("/proc/sys/net/ipv6/conf/all/forwarding")
}
// EnableForward will enable forwarding for all configured
// address families
func EnableForward(ips []*current.IPConfig) error {
v4 := false
v6 := false
for _, ip := range ips {
isV4 := ip.Address.IP.To4() != nil
if isV4 && !v4 {
if err := EnableIP4Forward(); err != nil {
return err
}
v4 = true
} else if !isV4 && !v6 {
if err := EnableIP6Forward(); err != nil {
return err
}
v6 = true
}
}
return nil
}
func echo1(f string) error {
if content, err := ioutil.ReadFile(f); err == nil {
if bytes.Equal(bytes.TrimSpace(content), []byte("1")) {
return nil
}
}
return ioutil.WriteFile(f, []byte("1"), 0644)
}

View File

@ -1,32 +0,0 @@
package ip
import (
"io/ioutil"
"os"
"time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("IpforwardLinux", func() {
It("echo1 must not write the file if content is 1", func() {
file, err := ioutil.TempFile(os.TempDir(), "containernetworking")
Expect(err).NotTo(HaveOccurred())
defer os.Remove(file.Name())
err = echo1(file.Name())
Expect(err).NotTo(HaveOccurred())
statBefore, err := file.Stat()
Expect(err).NotTo(HaveOccurred())
// take a duration here, otherwise next file modification operation time
// will be same as previous one.
time.Sleep(100 * time.Millisecond)
err = echo1(file.Name())
Expect(err).NotTo(HaveOccurred())
statAfter, err := file.Stat()
Expect(err).NotTo(HaveOccurred())
Expect(statBefore.ModTime()).To(Equal(statAfter.ModTime()))
})
})

66
pkg/ip/ipmasq.go Normal file
View File

@ -0,0 +1,66 @@
// 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 (
"fmt"
"net"
"github.com/coreos/go-iptables/iptables"
)
// SetupIPMasq installs iptables rules to masquerade traffic
// coming from ipn and going outside of it
func SetupIPMasq(ipn *net.IPNet, chain string, comment string) error {
ipt, err := iptables.New()
if err != nil {
return fmt.Errorf("failed to locate iptables: %v", err)
}
if err = ipt.NewChain("nat", chain); err != nil {
if err.(*iptables.Error).ExitStatus() != 1 {
// TODO(eyakubovich): assumes exit status 1 implies chain exists
return err
}
}
if err = ipt.AppendUnique("nat", chain, "-d", ipn.String(), "-j", "ACCEPT", "-m", "comment", "--comment", comment); err != nil {
return err
}
if err = ipt.AppendUnique("nat", chain, "!", "-d", "224.0.0.0/4", "-j", "MASQUERADE", "-m", "comment", "--comment", comment); err != nil {
return err
}
return ipt.AppendUnique("nat", "POSTROUTING", "-s", ipn.String(), "-j", chain, "-m", "comment", "--comment", comment)
}
// TeardownIPMasq undoes the effects of SetupIPMasq
func TeardownIPMasq(ipn *net.IPNet, chain string, comment string) error {
ipt, err := iptables.New()
if err != nil {
return fmt.Errorf("failed to locate iptables: %v", err)
}
if err = ipt.Delete("nat", "POSTROUTING", "-s", ipn.String(), "-j", chain, "-m", "comment", "--comment", comment); err != nil {
return err
}
if err = ipt.ClearChain("nat", chain); err != nil {
return err
}
return ipt.DeleteChain("nat", chain)
}

View File

@ -1,126 +0,0 @@
// 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 (
"fmt"
"net"
"github.com/coreos/go-iptables/iptables"
)
// 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
}
}
// 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)
}
// TeardownIPMasq undoes the effects of SetupIPMasq
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
}
// 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()
}

153
pkg/ip/link.go Normal file
View File

@ -0,0 +1,153 @@
// 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 (
"crypto/rand"
"fmt"
"net"
"os"
"github.com/appc/cni/pkg/ns"
"github.com/vishvananda/netlink"
)
func makeVethPair(name, peer string, mtu int) (netlink.Link, error) {
veth := &netlink.Veth{
LinkAttrs: netlink.LinkAttrs{
Name: name,
Flags: net.FlagUp,
MTU: mtu,
},
PeerName: peer,
}
if err := netlink.LinkAdd(veth); err != nil {
return nil, err
}
return veth, nil
}
func makeVeth(name string, mtu int) (peerName string, veth netlink.Link, err error) {
for i := 0; i < 10; i++ {
peerName, err = RandomVethName()
if err != nil {
return
}
veth, err = makeVethPair(name, peerName, mtu)
switch {
case err == nil:
return
case os.IsExist(err):
continue
default:
err = fmt.Errorf("failed to make veth pair: %v", err)
return
}
}
// should really never be hit
err = fmt.Errorf("failed to find a unique veth name")
return
}
// RandomVethName returns string "veth" with random prefix (hashed from entropy)
func RandomVethName() (string, error) {
entropy := make([]byte, 4)
_, err := rand.Reader.Read(entropy)
if err != nil {
return "", fmt.Errorf("failed to generate random veth name: %v", err)
}
// NetworkManager (recent versions) will ignore veth devices that start with "veth"
return fmt.Sprintf("veth%x", entropy), nil
}
// SetupVeth sets up a virtual ethernet link.
// Should be in container netns, and will switch back to hostNS to set the host
// veth end up.
func SetupVeth(contVethName string, mtu int, hostNS *os.File) (hostVeth, contVeth netlink.Link, err error) {
var hostVethName string
hostVethName, contVeth, err = makeVeth(contVethName, mtu)
if err != nil {
return
}
if err = netlink.LinkSetUp(contVeth); err != nil {
err = fmt.Errorf("failed to set %q up: %v", contVethName, err)
return
}
hostVeth, err = netlink.LinkByName(hostVethName)
if err != nil {
err = fmt.Errorf("failed to lookup %q: %v", hostVethName, err)
return
}
if err = netlink.LinkSetNsFd(hostVeth, int(hostNS.Fd())); err != nil {
err = fmt.Errorf("failed to move veth to host netns: %v", err)
return
}
err = ns.WithNetNS(hostNS, false, func(_ *os.File) error {
hostVeth, err := netlink.LinkByName(hostVethName)
if err != nil {
return fmt.Errorf("failed to lookup %q in %q: %v", hostVethName, hostNS.Name(), err)
}
if err = netlink.LinkSetUp(hostVeth); err != nil {
return fmt.Errorf("failed to set %q up: %v", hostVethName, err)
}
return nil
})
return
}
// DelLinkByName removes an interface link.
func DelLinkByName(ifName string) error {
iface, err := netlink.LinkByName(ifName)
if err != nil {
return fmt.Errorf("failed to lookup %q: %v", ifName, err)
}
if err = netlink.LinkDel(iface); err != nil {
return fmt.Errorf("failed to delete %q: %v", ifName, err)
}
return nil
}
// DelLinkByNameAddr remove an interface returns its IP address
// of the specified family
func DelLinkByNameAddr(ifName string, family int) (*net.IPNet, error) {
iface, err := netlink.LinkByName(ifName)
if err != nil {
return nil, fmt.Errorf("failed to lookup %q: %v", ifName, err)
}
addrs, err := netlink.AddrList(iface, family)
if err != nil || len(addrs) == 0 {
return nil, fmt.Errorf("failed to get IP addresses for %q: %v", ifName, err)
}
if err = netlink.LinkDel(iface); err != nil {
return nil, fmt.Errorf("failed to delete %q: %v", ifName, err)
}
return addrs[0].IPNet, nil
}

View File

@ -1,266 +0,0 @@
// 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 (
"crypto/rand"
"errors"
"fmt"
"net"
"os"
"github.com/safchain/ethtool"
"github.com/vishvananda/netlink"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/utils/sysctl"
)
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) {
veth := &netlink.Veth{
LinkAttrs: netlink.LinkAttrs{
Name: name,
Flags: net.FlagUp,
MTU: mtu,
},
PeerName: peer,
PeerNamespace: netlink.NsFd(int(hostNS.Fd())),
}
if mac != "" {
m, err := net.ParseMAC(mac)
if err != nil {
return nil, err
}
veth.LinkAttrs.HardwareAddr = m
}
if err := netlink.LinkAdd(veth); err != nil {
return nil, err
}
// Re-fetch the container link to get its creation-time parameters, e.g. index and mac
veth2, err := netlink.LinkByName(name)
if err != nil {
netlink.LinkDel(veth) // try and clean up the link if possible.
return nil, err
}
return veth2, nil
}
func peerExists(name string) bool {
if _, err := netlink.LinkByName(name); err != nil {
return false
}
return true
}
func makeVeth(name, vethPeerName string, mtu int, mac string, hostNS ns.NetNS) (peerName string, veth netlink.Link, err error) {
for i := 0; i < 10; i++ {
if vethPeerName != "" {
peerName = vethPeerName
} else {
peerName, err = RandomVethName()
if err != nil {
return
}
}
veth, err = makeVethPair(name, peerName, mtu, mac, hostNS)
switch {
case err == nil:
return
case os.IsExist(err):
if peerExists(peerName) && vethPeerName == "" {
continue
}
err = fmt.Errorf("container veth name provided (%v) already exists", name)
return
default:
err = fmt.Errorf("failed to make veth pair: %v", err)
return
}
}
// should really never be hit
err = fmt.Errorf("failed to find a unique veth name")
return
}
// RandomVethName returns string "veth" with random prefix (hashed from entropy)
func RandomVethName() (string, error) {
entropy := make([]byte, 4)
_, err := rand.Reader.Read(entropy)
if err != nil {
return "", fmt.Errorf("failed to generate random veth name: %v", err)
}
// NetworkManager (recent versions) will ignore veth devices that start with "veth"
return fmt.Sprintf("veth%x", entropy), nil
}
func RenameLink(curName, newName string) error {
link, err := netlink.LinkByName(curName)
if err == nil {
err = netlink.LinkSetName(link, newName)
}
return err
}
func ifaceFromNetlinkLink(l netlink.Link) net.Interface {
a := l.Attrs()
return net.Interface{
Index: a.Index,
MTU: a.MTU,
Name: a.Name,
HardwareAddr: a.HardwareAddr,
Flags: a.Flags,
}
}
// SetupVethWithName sets up a pair of virtual ethernet devices.
// Call SetupVethWithName from inside the container netns. It will create both veth
// devices and move the host-side veth into the provided hostNS namespace.
// hostVethName: If hostVethName is not specified, the host-side veth name will use a random string.
// On success, SetupVethWithName returns (hostVeth, containerVeth, nil)
func SetupVethWithName(contVethName, hostVethName string, mtu int, contVethMac string, hostNS ns.NetNS) (net.Interface, net.Interface, error) {
hostVethName, contVeth, err := makeVeth(contVethName, hostVethName, mtu, contVethMac, hostNS)
if err != nil {
return net.Interface{}, net.Interface{}, err
}
if err = netlink.LinkSetUp(contVeth); err != nil {
return net.Interface{}, net.Interface{}, fmt.Errorf("failed to set %q up: %v", contVethName, err)
}
var hostVeth netlink.Link
err = hostNS.Do(func(_ ns.NetNS) error {
hostVeth, err = netlink.LinkByName(hostVethName)
if err != nil {
return fmt.Errorf("failed to lookup %q in %q: %v", hostVethName, hostNS.Path(), err)
}
if err = netlink.LinkSetUp(hostVeth); err != nil {
return fmt.Errorf("failed to set %q up: %v", hostVethName, err)
}
// we want to own the routes for this interface
_, _ = sysctl.Sysctl(fmt.Sprintf("net/ipv6/conf/%s/accept_ra", hostVethName), "0")
return nil
})
if err != nil {
return net.Interface{}, net.Interface{}, err
}
return ifaceFromNetlinkLink(hostVeth), ifaceFromNetlinkLink(contVeth), nil
}
// SetupVeth sets up a pair of virtual ethernet devices.
// Call SetupVeth from inside the container netns. It will create both veth
// devices and move the host-side veth into the provided hostNS namespace.
// On success, SetupVeth returns (hostVeth, containerVeth, nil)
func SetupVeth(contVethName string, mtu int, contVethMac string, hostNS ns.NetNS) (net.Interface, net.Interface, error) {
return SetupVethWithName(contVethName, "", mtu, contVethMac, hostNS)
}
// DelLinkByName removes an interface link.
func DelLinkByName(ifName string) error {
iface, err := netlink.LinkByName(ifName)
if err != nil {
if _, ok := err.(netlink.LinkNotFoundError); ok {
return ErrLinkNotFound
}
return fmt.Errorf("failed to lookup %q: %v", ifName, err)
}
if err = netlink.LinkDel(iface); err != nil {
return fmt.Errorf("failed to delete %q: %v", ifName, err)
}
return nil
}
// DelLinkByNameAddr remove an interface and returns its addresses
func DelLinkByNameAddr(ifName string) ([]*net.IPNet, error) {
iface, err := netlink.LinkByName(ifName)
if err != nil {
if _, ok := err.(netlink.LinkNotFoundError); ok {
return nil, ErrLinkNotFound
}
return nil, fmt.Errorf("failed to lookup %q: %v", ifName, err)
}
addrs, err := netlink.AddrList(iface, netlink.FAMILY_ALL)
if err != nil {
return nil, fmt.Errorf("failed to get IP addresses for %q: %v", ifName, err)
}
if err = netlink.LinkDel(iface); err != nil {
return nil, fmt.Errorf("failed to delete %q: %v", ifName, err)
}
out := []*net.IPNet{}
for _, addr := range addrs {
if addr.IP.IsGlobalUnicast() {
out = append(out, addr.IPNet)
}
}
return out, nil
}
// GetVethPeerIfindex returns the veth link object, the peer ifindex of the
// veth, or an error. This peer ifindex will only be valid in the peer's
// network namespace.
func GetVethPeerIfindex(ifName string) (netlink.Link, int, error) {
link, err := netlink.LinkByName(ifName)
if err != nil {
return nil, -1, fmt.Errorf("could not look up %q: %v", ifName, err)
}
if _, ok := link.(*netlink.Veth); !ok {
return nil, -1, fmt.Errorf("interface %q was not a veth interface", ifName)
}
// veth supports IFLA_LINK (what vishvananda/netlink calls ParentIndex)
// on 4.1 and higher kernels
peerIndex := link.Attrs().ParentIndex
if peerIndex <= 0 {
// Fall back to ethtool for 4.0 and earlier kernels
e, err := ethtool.NewEthtool()
if err != nil {
return nil, -1, fmt.Errorf("failed to initialize ethtool: %v", err)
}
defer e.Close()
stats, err := e.Stats(link.Attrs().Name)
if err != nil {
return nil, -1, fmt.Errorf("failed to request ethtool stats: %v", err)
}
n, ok := stats["peer_ifindex"]
if !ok {
return nil, -1, fmt.Errorf("failed to find 'peer_ifindex' in ethtool stats")
}
if n > 32767 || n == 0 {
return nil, -1, fmt.Errorf("invalid 'peer_ifindex' %d", n)
}
peerIndex = int(n)
}
return link, peerIndex, nil
}

View File

@ -1,297 +0,0 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ip_test
import (
"bytes"
"crypto/rand"
"fmt"
"net"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/containernetworking/plugins/pkg/ip"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils"
"github.com/vishvananda/netlink"
)
func getHwAddr(linkname string) string {
veth, err := netlink.LinkByName(linkname)
Expect(err).NotTo(HaveOccurred())
return fmt.Sprintf("%s", veth.Attrs().HardwareAddr)
}
var _ = Describe("Link", func() {
const (
ifaceFormatString string = "i%d"
mtu int = 1400
ip4onehwaddr = "0a:58:01:01:01:01"
)
var (
hostNetNS ns.NetNS
containerNetNS ns.NetNS
ifaceCounter int = 0
hostVeth net.Interface
containerVeth net.Interface
hostVethName string
containerVethName string
originalRandReader = rand.Reader
)
BeforeEach(func() {
var err error
hostNetNS, err = testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
containerNetNS, err = testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
fakeBytes := make([]byte, 20)
//to be reset in AfterEach block
rand.Reader = bytes.NewReader(fakeBytes)
_ = containerNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
hostVeth, containerVeth, err = ip.SetupVeth(fmt.Sprintf(ifaceFormatString, ifaceCounter), mtu, "", hostNetNS)
if err != nil {
return err
}
Expect(err).NotTo(HaveOccurred())
hostVethName = hostVeth.Name
containerVethName = containerVeth.Name
return nil
})
})
AfterEach(func() {
Expect(containerNetNS.Close()).To(Succeed())
Expect(hostNetNS.Close()).To(Succeed())
ifaceCounter++
rand.Reader = originalRandReader
})
Describe("GetVethPeerIfindex", func() {
It("returns the link and peer index of the named interface", func() {
By("looking up the container veth index using the host veth name")
_ = hostNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
gotHostLink, gotContainerIndex, err := ip.GetVethPeerIfindex(hostVethName)
Expect(err).NotTo(HaveOccurred())
By("checking we got back the host link")
attrs := gotHostLink.Attrs()
Expect(attrs.Index).To(Equal(hostVeth.Index))
Expect(attrs.Name).To(Equal(hostVeth.Name))
By("checking we got back the container veth index")
Expect(gotContainerIndex).To(Equal(containerVeth.Index))
return nil
})
By("looking up the host veth index using the container veth name")
_ = containerNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
gotContainerLink, gotHostIndex, err := ip.GetVethPeerIfindex(containerVethName)
Expect(err).NotTo(HaveOccurred())
By("checking we got back the container link")
attrs := gotContainerLink.Attrs()
Expect(attrs.Index).To(Equal(containerVeth.Index))
Expect(attrs.Name).To(Equal(containerVeth.Name))
By("checking we got back the host veth index")
Expect(gotHostIndex).To(Equal(hostVeth.Index))
return nil
})
})
})
It("SetupVeth must put the veth endpoints into the separate namespaces", func() {
_ = containerNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
containerVethFromName, err := netlink.LinkByName(containerVethName)
Expect(err).NotTo(HaveOccurred())
Expect(containerVethFromName.Attrs().Index).To(Equal(containerVeth.Index))
return nil
})
_ = hostNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
hostVethFromName, err := netlink.LinkByName(hostVethName)
Expect(err).NotTo(HaveOccurred())
Expect(hostVethFromName.Attrs().Index).To(Equal(hostVeth.Index))
return nil
})
})
Context("when container already has an interface with the same name", func() {
It("returns useful error", func() {
_ = containerNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, _, err := ip.SetupVeth(containerVethName, mtu, "", hostNetNS)
Expect(err.Error()).To(Equal(fmt.Sprintf("container veth name provided (%s) already exists", containerVethName)))
return nil
})
})
})
Context("deleting an non-existent device", func() {
It("returns known error", func() {
_ = containerNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
// This string should match the expected error codes in the cmdDel functions of some of the plugins
_, err := ip.DelLinkByNameAddr("THIS_DONT_EXIST")
Expect(err).To(Equal(ip.ErrLinkNotFound))
return nil
})
})
})
Context("when there is no name available for the host-side", func() {
BeforeEach(func() {
//adding different interface to container ns
containerVethName += "0"
})
It("returns useful error", func() {
_ = containerNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, _, err := ip.SetupVeth(containerVethName, mtu, "", hostNetNS)
Expect(err.Error()).To(HavePrefix("container veth name provided"))
Expect(err.Error()).To(HaveSuffix("already exists"))
return nil
})
})
})
Context("when there is no name conflict for the host or container interfaces", func() {
BeforeEach(func() {
//adding different interface to container and host ns
containerVethName += "0"
rand.Reader = originalRandReader
})
It("successfully creates the second veth pair", func() {
_ = containerNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
hostVeth, _, err := ip.SetupVeth(containerVethName, mtu, "", hostNetNS)
Expect(err).NotTo(HaveOccurred())
hostVethName = hostVeth.Name
return nil
})
//verify veths are in different namespaces
_ = containerNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, err := netlink.LinkByName(containerVethName)
Expect(err).NotTo(HaveOccurred())
return nil
})
_ = hostNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, err := netlink.LinkByName(hostVethName)
Expect(err).NotTo(HaveOccurred())
return nil
})
})
It("successfully creates a veth pair with an explicit mac", func() {
const mac = "02:00:00:00:01:23"
_ = containerNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
hostVeth, _, err := ip.SetupVeth(containerVethName, mtu, mac, hostNetNS)
Expect(err).NotTo(HaveOccurred())
hostVethName = hostVeth.Name
link, err := netlink.LinkByName(containerVethName)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr.String()).To(Equal(mac))
return nil
})
_ = hostNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(hostVethName)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr.String()).NotTo(Equal(mac))
return nil
})
})
})
It("DelLinkByName must delete the veth endpoints", func() {
_ = containerNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
// this will delete the host endpoint too
err := ip.DelLinkByName(containerVethName)
Expect(err).NotTo(HaveOccurred())
_, err = netlink.LinkByName(containerVethName)
Expect(err).To(HaveOccurred())
return nil
})
_ = hostNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, err := netlink.LinkByName(hostVethName)
Expect(err).To(HaveOccurred())
return nil
})
})
It("DelLinkByNameAddr should return no IPs when no IPs are configured", func() {
_ = containerNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
// this will delete the host endpoint too
addr, err := ip.DelLinkByNameAddr(containerVethName)
Expect(err).NotTo(HaveOccurred())
Expect(addr).To(HaveLen(0))
return nil
})
})
})

View File

@ -1,4 +1,4 @@
// Copyright 2015-2017 CNI authors
// 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.
@ -20,6 +20,12 @@ import (
"github.com/vishvananda/netlink"
)
// AddDefaultRoute sets the default route on the given gateway.
func AddDefaultRoute(gw net.IP, dev netlink.Link) error {
_, defNet, _ := net.ParseCIDR("0.0.0.0/0")
return AddRoute(defNet, gw, dev)
}
// AddRoute adds a universally-scoped route to a device.
func AddRoute(ipn *net.IPNet, gw net.IP, dev netlink.Link) error {
return netlink.RouteAdd(&netlink.Route{
@ -39,9 +45,3 @@ func AddHostRoute(ipn *net.IPNet, gw net.IP, dev netlink.Link) error {
Gw: gw,
})
}
// AddDefaultRoute sets the default route on the given gateway.
func AddDefaultRoute(gw net.IP, dev netlink.Link) error {
_, defNet, _ := net.ParseCIDR("0.0.0.0/0")
return AddRoute(defNet, gw, dev)
}

View File

@ -1,115 +0,0 @@
// +build linux
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ip
import (
"fmt"
"net"
"github.com/containernetworking/cni/pkg/types"
current "github.com/containernetworking/cni/pkg/types/100"
"github.com/vishvananda/netlink"
)
func ValidateExpectedInterfaceIPs(ifName string, resultIPs []*current.IPConfig) error {
// Ensure ips
for _, ips := range resultIPs {
ourAddr := netlink.Addr{IPNet: &ips.Address}
match := false
link, err := netlink.LinkByName(ifName)
if err != nil {
return fmt.Errorf("Cannot find container link %v", ifName)
}
addrList, err := netlink.AddrList(link, netlink.FAMILY_ALL)
if err != nil {
return fmt.Errorf("Cannot obtain List of IP Addresses")
}
for _, addr := range addrList {
if addr.Equal(ourAddr) {
match = true
break
}
}
if match == false {
return fmt.Errorf("Failed to match addr %v on interface %v", ourAddr, ifName)
}
// Convert the host/prefixlen to just prefix for route lookup.
_, ourPrefix, err := net.ParseCIDR(ourAddr.String())
findGwy := &netlink.Route{Dst: ourPrefix}
routeFilter := netlink.RT_FILTER_DST
family := netlink.FAMILY_V6
if ips.Address.IP.To4() != nil {
family = netlink.FAMILY_V4
}
gwy, err := netlink.RouteListFiltered(family, findGwy, routeFilter)
if err != nil {
return fmt.Errorf("Error %v trying to find Gateway %v for interface %v", err, ips.Gateway, ifName)
}
if gwy == nil {
return fmt.Errorf("Failed to find Gateway %v for interface %v", ips.Gateway, ifName)
}
}
return nil
}
func ValidateExpectedRoute(resultRoutes []*types.Route) error {
// Ensure that each static route in prevResults is found in the routing table
for _, route := range resultRoutes {
find := &netlink.Route{Dst: &route.Dst, Gw: route.GW}
routeFilter := netlink.RT_FILTER_DST | netlink.RT_FILTER_GW
var family int
switch {
case route.Dst.IP.To4() != nil:
family = netlink.FAMILY_V4
// Default route needs Dst set to nil
if route.Dst.String() == "0.0.0.0/0" {
find = &netlink.Route{Dst: nil, Gw: route.GW}
routeFilter = netlink.RT_FILTER_DST
}
case len(route.Dst.IP) == net.IPv6len:
family = netlink.FAMILY_V6
// Default route needs Dst set to nil
if route.Dst.String() == "::/0" {
find = &netlink.Route{Dst: nil, Gw: route.GW}
routeFilter = netlink.RT_FILTER_DST
}
default:
return fmt.Errorf("Invalid static route found %v", route)
}
wasFound, err := netlink.RouteListFiltered(family, find, routeFilter)
if err != nil {
return fmt.Errorf("Expected Route %v not route table lookup error %v", route, err)
}
if wasFound == nil {
return fmt.Errorf("Expected Route %v not found in routing table", route)
}
}
return nil
}

View File

@ -15,19 +15,54 @@
package ipam
import (
"context"
"github.com/containernetworking/cni/pkg/invoke"
"github.com/containernetworking/cni/pkg/types"
"fmt"
"os"
"github.com/appc/cni/pkg/invoke"
"github.com/appc/cni/pkg/ip"
"github.com/appc/cni/pkg/types"
"github.com/vishvananda/netlink"
)
func ExecAdd(plugin string, netconf []byte) (types.Result, error) {
return invoke.DelegateAdd(context.TODO(), plugin, netconf, nil)
}
func ExecCheck(plugin string, netconf []byte) error {
return invoke.DelegateCheck(context.TODO(), plugin, netconf, nil)
func ExecAdd(plugin string, netconf []byte) (*types.Result, error) {
return invoke.DelegateAdd(plugin, netconf)
}
func ExecDel(plugin string, netconf []byte) error {
return invoke.DelegateDel(context.TODO(), plugin, netconf, nil)
return invoke.DelegateDel(plugin, netconf)
}
// ConfigureIface takes the result of IPAM plugin and
// applies to the ifName interface
func ConfigureIface(ifName string, res *types.Result) error {
link, err := netlink.LinkByName(ifName)
if err != nil {
return fmt.Errorf("failed to lookup %q: %v", ifName, err)
}
if err := netlink.LinkSetUp(link); err != nil {
return fmt.Errorf("failed to set %q UP: %v", ifName, err)
}
// TODO(eyakubovich): IPv6
addr := &netlink.Addr{IPNet: &res.IP4.IP, Label: ""}
if err = netlink.AddrAdd(link, addr); err != nil {
return fmt.Errorf("failed to add IP addr to %q: %v", ifName, err)
}
for _, r := range res.IP4.Routes {
gw := r.GW
if gw == nil {
gw = res.IP4.Gateway
}
if err = ip.AddRoute(&r.Dst, gw, link); err != nil {
// we skip over duplicate routes as we assume the first one wins
if !os.IsExist(err) {
return fmt.Errorf("failed to add route '%v via %v dev %v': %v", r.Dst, gw, ifName, err)
}
}
}
return nil
}

View File

@ -1,128 +0,0 @@
// 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 ipam
import (
"fmt"
"net"
"os"
current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/plugins/pkg/ip"
"github.com/containernetworking/plugins/pkg/utils/sysctl"
"github.com/vishvananda/netlink"
)
const (
// Note: use slash as separator so we can have dots in interface name (VLANs)
DisableIPv6SysctlTemplate = "net/ipv6/conf/%s/disable_ipv6"
)
// ConfigureIface takes the result of IPAM plugin and
// applies to the ifName interface
func ConfigureIface(ifName string, res *current.Result) error {
if len(res.Interfaces) == 0 {
return fmt.Errorf("no interfaces to configure")
}
link, err := netlink.LinkByName(ifName)
if err != nil {
return fmt.Errorf("failed to lookup %q: %v", ifName, err)
}
if err := netlink.LinkSetUp(link); err != nil {
return fmt.Errorf("failed to set %q UP: %v", ifName, err)
}
var v4gw, v6gw net.IP
var has_enabled_ipv6 bool = false
for _, ipc := range res.IPs {
if ipc.Interface == nil {
continue
}
intIdx := *ipc.Interface
if intIdx < 0 || intIdx >= len(res.Interfaces) || res.Interfaces[intIdx].Name != ifName {
// IP address is for a different interface
return fmt.Errorf("failed to add IP addr %v to %q: invalid interface index", ipc, ifName)
}
// Make sure sysctl "disable_ipv6" is 0 if we are about to add
// an IPv6 address to the interface
if !has_enabled_ipv6 && ipc.Address.IP.To4() == nil {
// Enabled IPv6 for loopback "lo" and the interface
// being configured
for _, iface := range [2]string{"lo", ifName} {
ipv6SysctlValueName := fmt.Sprintf(DisableIPv6SysctlTemplate, iface)
// Read current sysctl value
value, err := sysctl.Sysctl(ipv6SysctlValueName)
if err != nil {
fmt.Fprintf(os.Stderr, "ipam_linux: failed to read sysctl %q: %v\n", ipv6SysctlValueName, err)
continue
}
if value == "0" {
continue
}
// Write sysctl to enable IPv6
_, err = sysctl.Sysctl(ipv6SysctlValueName, "0")
if err != nil {
return fmt.Errorf("failed to enable IPv6 for interface %q (%s=%s): %v", iface, ipv6SysctlValueName, value, err)
}
}
has_enabled_ipv6 = true
}
addr := &netlink.Addr{IPNet: &ipc.Address, Label: ""}
if err = netlink.AddrAdd(link, addr); err != nil {
return fmt.Errorf("failed to add IP addr %v to %q: %v", ipc, ifName, err)
}
gwIsV4 := ipc.Gateway.To4() != nil
if gwIsV4 && v4gw == nil {
v4gw = ipc.Gateway
} else if !gwIsV4 && v6gw == nil {
v6gw = ipc.Gateway
}
}
if v6gw != nil {
ip.SettleAddresses(ifName, 10)
}
for _, r := range res.Routes {
routeIsV4 := r.Dst.IP.To4() != nil
gw := r.GW
if gw == nil {
if routeIsV4 && v4gw != nil {
gw = v4gw
} else if !routeIsV4 && v6gw != nil {
gw = v6gw
}
}
route := netlink.Route{
Dst: &r.Dst,
LinkIndex: link.Attrs().Index,
Gw: gw,
}
if err = netlink.RouteAddEcmp(&route); err != nil {
return fmt.Errorf("failed to add route '%v via %v dev %v': %v", r.Dst, gw, ifName, err)
}
}
return nil
}

View File

@ -1,296 +0,0 @@
// 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 ipam
import (
"net"
"syscall"
"github.com/containernetworking/cni/pkg/types"
current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils"
"github.com/vishvananda/netlink"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
const LINK_NAME = "eth0"
func ipNetEqual(a, b *net.IPNet) bool {
aPrefix, aBits := a.Mask.Size()
bPrefix, bBits := b.Mask.Size()
if aPrefix != bPrefix || aBits != bBits {
return false
}
return a.IP.Equal(b.IP)
}
var _ = Describe("ConfigureIface", func() {
var originalNS ns.NetNS
var ipv4, ipv6, routev4, routev6 *net.IPNet
var ipgw4, ipgw6, routegwv4, routegwv6 net.IP
var result *current.Result
BeforeEach(func() {
// Create a new NetNS so we don't modify the host
var err error
originalNS, err = testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
// Add master
err = netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: LINK_NAME,
},
})
Expect(err).NotTo(HaveOccurred())
_, err = netlink.LinkByName(LINK_NAME)
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
ipv4, err = types.ParseCIDR("1.2.3.30/24")
Expect(err).NotTo(HaveOccurred())
Expect(ipv4).NotTo(BeNil())
_, routev4, err = net.ParseCIDR("15.5.6.8/24")
Expect(err).NotTo(HaveOccurred())
Expect(routev4).NotTo(BeNil())
routegwv4 = net.ParseIP("1.2.3.5")
Expect(routegwv4).NotTo(BeNil())
ipgw4 = net.ParseIP("1.2.3.1")
Expect(ipgw4).NotTo(BeNil())
ipv6, err = types.ParseCIDR("abcd:1234:ffff::cdde/64")
Expect(err).NotTo(HaveOccurred())
Expect(ipv6).NotTo(BeNil())
_, routev6, err = net.ParseCIDR("1111:dddd::aaaa/80")
Expect(err).NotTo(HaveOccurred())
Expect(routev6).NotTo(BeNil())
routegwv6 = net.ParseIP("abcd:1234:ffff::10")
Expect(routegwv6).NotTo(BeNil())
ipgw6 = net.ParseIP("abcd:1234:ffff::1")
Expect(ipgw6).NotTo(BeNil())
result = &current.Result{
Interfaces: []*current.Interface{
{
Name: "eth0",
Mac: "00:11:22:33:44:55",
Sandbox: "/proc/3553/ns/net",
},
{
Name: "fake0",
Mac: "00:33:44:55:66:77",
Sandbox: "/proc/1234/ns/net",
},
},
IPs: []*current.IPConfig{
{
Interface: current.Int(0),
Address: *ipv4,
Gateway: ipgw4,
},
{
Interface: current.Int(0),
Address: *ipv6,
Gateway: ipgw6,
},
},
Routes: []*types.Route{
{Dst: *routev4, GW: routegwv4},
{Dst: *routev6, GW: routegwv6},
},
}
})
AfterEach(func() {
Expect(originalNS.Close()).To(Succeed())
})
It("configures a link with addresses and routes", func() {
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := ConfigureIface(LINK_NAME, result)
Expect(err).NotTo(HaveOccurred())
link, err := netlink.LinkByName(LINK_NAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal(LINK_NAME))
v4addrs, err := netlink.AddrList(link, syscall.AF_INET)
Expect(err).NotTo(HaveOccurred())
Expect(len(v4addrs)).To(Equal(1))
Expect(ipNetEqual(v4addrs[0].IPNet, ipv4)).To(Equal(true))
v6addrs, err := netlink.AddrList(link, syscall.AF_INET6)
Expect(err).NotTo(HaveOccurred())
Expect(len(v6addrs)).To(Equal(2))
var found bool
for _, a := range v6addrs {
if ipNetEqual(a.IPNet, ipv6) {
found = true
break
}
}
Expect(found).To(Equal(true))
// Ensure the v4 route, v6 route, and subnet route
routes, err := netlink.RouteList(link, 0)
Expect(err).NotTo(HaveOccurred())
var v4found, v6found bool
for _, route := range routes {
isv4 := route.Dst.IP.To4() != nil
if isv4 && ipNetEqual(route.Dst, routev4) && route.Gw.Equal(routegwv4) {
v4found = true
}
if !isv4 && ipNetEqual(route.Dst, routev6) && route.Gw.Equal(routegwv6) {
v6found = true
}
if v4found && v6found {
break
}
}
Expect(v4found).To(Equal(true))
Expect(v6found).To(Equal(true))
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("configures a link with routes using address gateways", func() {
result.Routes[0].GW = nil
result.Routes[1].GW = nil
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := ConfigureIface(LINK_NAME, result)
Expect(err).NotTo(HaveOccurred())
link, err := netlink.LinkByName(LINK_NAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal(LINK_NAME))
// Ensure the v4 route, v6 route, and subnet route
routes, err := netlink.RouteList(link, 0)
Expect(err).NotTo(HaveOccurred())
var v4found, v6found bool
for _, route := range routes {
isv4 := route.Dst.IP.To4() != nil
if isv4 && ipNetEqual(route.Dst, routev4) && route.Gw.Equal(ipgw4) {
v4found = true
}
if !isv4 && ipNetEqual(route.Dst, routev6) && route.Gw.Equal(ipgw6) {
v6found = true
}
if v4found && v6found {
break
}
}
Expect(v4found).To(Equal(true))
Expect(v6found).To(Equal(true))
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("returns an error when the interface index doesn't match the link name", func() {
result.IPs[0].Interface = current.Int(1)
err := originalNS.Do(func(ns.NetNS) error {
return ConfigureIface(LINK_NAME, result)
})
Expect(err).To(HaveOccurred())
})
It("returns an error when the interface index is too big", func() {
result.IPs[0].Interface = current.Int(2)
err := originalNS.Do(func(ns.NetNS) error {
return ConfigureIface(LINK_NAME, result)
})
Expect(err).To(HaveOccurred())
})
It("returns an error when the interface index is too small", func() {
result.IPs[0].Interface = current.Int(-1)
err := originalNS.Do(func(ns.NetNS) error {
return ConfigureIface(LINK_NAME, result)
})
Expect(err).To(HaveOccurred())
})
It("returns an error when there are no interfaces to configure", func() {
result.Interfaces = []*current.Interface{}
err := originalNS.Do(func(ns.NetNS) error {
return ConfigureIface(LINK_NAME, result)
})
Expect(err).To(HaveOccurred())
})
It("returns an error when configuring the wrong interface", func() {
err := originalNS.Do(func(ns.NetNS) error {
return ConfigureIface("asdfasdf", result)
})
Expect(err).To(HaveOccurred())
})
It("does not panic when interface is not specified", func() {
result = &current.Result{
Interfaces: []*current.Interface{
{
Name: "eth0",
Mac: "00:11:22:33:44:55",
Sandbox: "/proc/3553/ns/net",
},
{
Name: "fake0",
Mac: "00:33:44:55:66:77",
Sandbox: "/proc/1234/ns/net",
},
},
IPs: []*current.IPConfig{
{
Address: *ipv4,
Gateway: ipgw4,
},
{
Address: *ipv6,
Gateway: ipgw6,
},
},
}
err := originalNS.Do(func(ns.NetNS) error {
return ConfigureIface(LINK_NAME, result)
})
Expect(err).NotTo(HaveOccurred())
})
})

View File

@ -1,27 +0,0 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ipam_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestIpam(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "pkg/ipam")
}

View File

@ -1,41 +0,0 @@
### Namespaces, Threads, and Go
On Linux each OS thread can have a different network namespace. Go's thread scheduling model switches goroutines between OS threads based on OS thread load and whether the goroutine would block other goroutines. This can result in a goroutine switching network namespaces without notice and lead to errors in your code.
### Namespace Switching
Switching namespaces with the `ns.Set()` method is not recommended without additional strategies to prevent unexpected namespace changes when your goroutines switch OS threads.
Go provides the `runtime.LockOSThread()` function to ensure a specific goroutine executes on its current OS thread and prevents any other goroutine from running in that thread until the locked one exits. Careful usage of `LockOSThread()` and goroutines can provide good control over which network namespace a given goroutine executes in.
For example, you cannot rely on the `ns.Set()` namespace being the current namespace after the `Set()` call unless you do two things. First, the goroutine calling `Set()` must have previously called `LockOSThread()`. Second, you must ensure `runtime.UnlockOSThread()` is not called somewhere in-between. You also cannot rely on the initial network namespace remaining the current network namespace if any other code in your program switches namespaces, unless you have already called `LockOSThread()` in that goroutine. Note that `LockOSThread()` prevents the Go scheduler from optimally scheduling goroutines for best performance, so `LockOSThread()` should only be used in small, isolated goroutines that release the lock quickly.
### Do() The Recommended Thing
The `ns.Do()` method provides **partial** control over network namespaces for you by implementing these strategies. All code dependent on a particular network namespace (including the root namespace) should be wrapped in the `ns.Do()` method to ensure the correct namespace is selected for the duration of your code. For example:
```go
err = targetNs.Do(func(hostNs ns.NetNS) error {
dummy := &netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: "dummy0",
},
}
return netlink.LinkAdd(dummy)
})
```
Note this requirement to wrap every network call is very onerous - any libraries you call might call out to network services such as DNS, and all such calls need to be protected after you call `ns.Do()`. All goroutines spawned from within the `ns.Do` will not inherit the new namespace. The CNI plugins all exit very soon after calling `ns.Do()` which helps to minimize the problem.
When a new thread is spawned in Linux, it inherits the namespace of its parent. In versions of go **prior to 1.10**, if the runtime spawns a new OS thread, it picks the parent randomly. If the chosen parent thread has been moved to a new namespace (even temporarily), the new OS thread will be permanently "stuck in the wrong namespace", and goroutines will non-deterministically switch namespaces as they are rescheduled.
In short, **there was no safe way to change network namespaces, even temporarily, from within a long-lived, multithreaded Go process**. If you wish to do this, you must use go 1.10 or greater.
### Creating network namespaces
Earlier versions of this library managed namespace creation, but as CNI does not actually utilize this feature (and it was essentially unmaintained), it was removed. If you're writing a container runtime, you should implement namespace management yourself. However, there are some gotchas when doing so, especially around handling `/var/run/netns`. A reasonably correct reference implementation, borrowed from `rkt`, can be found in `pkg/testutils/netns_linux.go` if you're in need of a source of inspiration.
### Further Reading
- https://github.com/golang/go/wiki/LockOSThread
- http://morsmachine.dk/go-scheduler
- https://github.com/containernetworking/cni/issues/262
- https://golang.org/pkg/runtime/
- https://www.weave.works/blog/linux-namespaces-and-go-don-t-mix

93
pkg/ns/ns.go Normal file
View File

@ -0,0 +1,93 @@
// 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 ns
import (
"fmt"
"os"
"runtime"
"syscall"
)
var setNsMap = map[string]uintptr{
"386": 346,
"amd64": 308,
"arm": 374,
}
// SetNS sets the network namespace on a target file.
func SetNS(f *os.File, flags uintptr) error {
if runtime.GOOS != "linux" {
return fmt.Errorf("unsupported OS: %s", runtime.GOOS)
}
trap, ok := setNsMap[runtime.GOARCH]
if !ok {
return fmt.Errorf("unsupported arch: %s", runtime.GOARCH)
}
_, _, err := syscall.RawSyscall(trap, f.Fd(), flags, 0)
if err != 0 {
return err
}
return nil
}
// WithNetNSPath executes the passed closure under the given network
// namespace, restoring the original namespace afterwards.
// Changing namespaces must be done on a goroutine that has been
// locked to an OS thread. If lockThread arg is true, this function
// locks the goroutine prior to change namespace and unlocks before
// returning
func WithNetNSPath(nspath string, lockThread bool, f func(*os.File) error) error {
ns, err := os.Open(nspath)
if err != nil {
return fmt.Errorf("Failed to open %v: %v", nspath, err)
}
defer ns.Close()
return WithNetNS(ns, lockThread, f)
}
// WithNetNS executes the passed closure under the given network
// namespace, restoring the original namespace afterwards.
// Changing namespaces must be done on a goroutine that has been
// locked to an OS thread. If lockThread arg is true, this function
// locks the goroutine prior to change namespace and unlocks before
// returning. If the closure returns an error, WithNetNS attempts to
// restore the original namespace before returning.
func WithNetNS(ns *os.File, lockThread bool, f func(*os.File) error) error {
if lockThread {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
}
// save a handle to current (host) network namespace
thisNS, err := os.Open("/proc/self/ns/net")
if err != nil {
return fmt.Errorf("Failed to open /proc/self/ns/net: %v", err)
}
defer thisNS.Close()
if err = SetNS(ns, syscall.CLONE_NEWNET); err != nil {
return fmt.Errorf("Error switching to ns %v: %v", ns.Name(), err)
}
defer SetNS(thisNS, syscall.CLONE_NEWNET) // switch back
if err = f(thisNS); err != nil {
return err
}
return nil
}

View File

@ -1,234 +0,0 @@
// Copyright 2015-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 ns
import (
"fmt"
"os"
"runtime"
"sync"
"syscall"
"golang.org/x/sys/unix"
)
// Returns an object representing the current OS thread's network namespace
func GetCurrentNS() (NetNS, error) {
// Lock the thread in case other goroutine executes in it and changes its
// network namespace after getCurrentThreadNetNSPath(), otherwise it might
// return an unexpected network namespace.
runtime.LockOSThread()
defer runtime.UnlockOSThread()
return GetNS(getCurrentThreadNetNSPath())
}
func getCurrentThreadNetNSPath() string {
// /proc/self/ns/net returns the namespace of the main thread, not
// of whatever thread this goroutine is running on. Make sure we
// use the thread's net namespace since the thread is switching around
return fmt.Sprintf("/proc/%d/task/%d/ns/net", os.Getpid(), unix.Gettid())
}
func (ns *netNS) Close() error {
if err := ns.errorIfClosed(); err != nil {
return err
}
if err := ns.file.Close(); err != nil {
return fmt.Errorf("Failed to close %q: %v", ns.file.Name(), err)
}
ns.closed = true
return nil
}
func (ns *netNS) Set() error {
if err := ns.errorIfClosed(); err != nil {
return err
}
if err := unix.Setns(int(ns.Fd()), unix.CLONE_NEWNET); err != nil {
return fmt.Errorf("Error switching to ns %v: %v", ns.file.Name(), err)
}
return nil
}
type NetNS interface {
// Executes the passed closure in this object's network namespace,
// attempting to restore the original namespace before returning.
// However, since each OS thread can have a different network namespace,
// and Go's thread scheduling is highly variable, callers cannot
// guarantee any specific namespace is set unless operations that
// require that namespace are wrapped with Do(). Also, no code called
// from Do() should call runtime.UnlockOSThread(), or the risk
// of executing code in an incorrect namespace will be greater. See
// https://github.com/golang/go/wiki/LockOSThread for further details.
Do(toRun func(NetNS) error) error
// Sets the current network namespace to this object's network namespace.
// Note that since Go's thread scheduling is highly variable, callers
// cannot guarantee the requested namespace will be the current namespace
// after this function is called; to ensure this wrap operations that
// require the namespace with Do() instead.
Set() error
// Returns the filesystem path representing this object's network namespace
Path() string
// Returns a file descriptor representing this object's network namespace
Fd() uintptr
// Cleans up this instance of the network namespace; if this instance
// is the last user the namespace will be destroyed
Close() error
}
type netNS struct {
file *os.File
closed bool
}
// netNS implements the NetNS interface
var _ NetNS = &netNS{}
const (
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/magic.h
NSFS_MAGIC = 0x6e736673
PROCFS_MAGIC = 0x9fa0
)
type NSPathNotExistErr struct{ msg string }
func (e NSPathNotExistErr) Error() string { return e.msg }
type NSPathNotNSErr struct{ msg string }
func (e NSPathNotNSErr) Error() string { return e.msg }
func IsNSorErr(nspath string) error {
stat := syscall.Statfs_t{}
if err := syscall.Statfs(nspath, &stat); err != nil {
if os.IsNotExist(err) {
err = NSPathNotExistErr{msg: fmt.Sprintf("failed to Statfs %q: %v", nspath, err)}
} else {
err = fmt.Errorf("failed to Statfs %q: %v", nspath, err)
}
return err
}
switch stat.Type {
case PROCFS_MAGIC, NSFS_MAGIC:
return nil
default:
return NSPathNotNSErr{msg: fmt.Sprintf("unknown FS magic on %q: %x", nspath, stat.Type)}
}
}
// Returns an object representing the namespace referred to by @path
func GetNS(nspath string) (NetNS, error) {
err := IsNSorErr(nspath)
if err != nil {
return nil, err
}
fd, err := os.Open(nspath)
if err != nil {
return nil, err
}
return &netNS{file: fd}, nil
}
func (ns *netNS) Path() string {
return ns.file.Name()
}
func (ns *netNS) Fd() uintptr {
return ns.file.Fd()
}
func (ns *netNS) errorIfClosed() error {
if ns.closed {
return fmt.Errorf("%q has already been closed", ns.file.Name())
}
return nil
}
func (ns *netNS) Do(toRun func(NetNS) error) error {
if err := ns.errorIfClosed(); err != nil {
return err
}
containedCall := func(hostNS NetNS) error {
threadNS, err := GetCurrentNS()
if err != nil {
return fmt.Errorf("failed to open current netns: %v", err)
}
defer threadNS.Close()
// switch to target namespace
if err = ns.Set(); err != nil {
return fmt.Errorf("error switching to ns %v: %v", ns.file.Name(), err)
}
defer func() {
err := threadNS.Set() // switch back
if err == 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()
}
}()
return toRun(hostNS)
}
// save a handle to current network namespace
hostNS, err := GetCurrentNS()
if err != nil {
return fmt.Errorf("Failed to open current namespace: %v", err)
}
defer hostNS.Close()
var wg sync.WaitGroup
wg.Add(1)
// Start the callback in a new green thread 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.
var innerError error
go func() {
defer wg.Done()
runtime.LockOSThread()
innerError = containedCall(hostNS)
}()
wg.Wait()
return innerError
}
// WithNetNSPath executes the passed closure under the given network
// namespace, restoring the original namespace afterwards.
func WithNetNSPath(nspath string, toRun func(NetNS) error) error {
ns, err := GetNS(nspath)
if err != nil {
return err
}
defer ns.Close()
return ns.Do(toRun)
}

View File

@ -1,292 +0,0 @@
// Copyright 2016-2018 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 ns_test
import (
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sync"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"golang.org/x/sys/unix"
)
func getInodeCurNetNS() (uint64, error) {
curNS, err := ns.GetCurrentNS()
if err != nil {
return 0, err
}
defer curNS.Close()
return getInodeNS(curNS)
}
func getInodeNS(netns ns.NetNS) (uint64, error) {
return getInodeFd(int(netns.Fd()))
}
func getInode(path string) (uint64, error) {
file, err := os.Open(path)
if err != nil {
return 0, err
}
defer file.Close()
return getInodeFd(int(file.Fd()))
}
func getInodeFd(fd int) (uint64, error) {
stat := &unix.Stat_t{}
err := unix.Fstat(fd, stat)
return stat.Ino, err
}
var _ = Describe("Linux namespace operations", func() {
Describe("WithNetNS", func() {
var (
originalNetNS ns.NetNS
targetNetNS ns.NetNS
)
BeforeEach(func() {
var err error
originalNetNS, err = testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
targetNetNS, err = testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
})
AfterEach(func() {
targetNetNS.Close()
originalNetNS.Close()
Expect(testutils.UnmountNS(targetNetNS)).To(Succeed())
Expect(testutils.UnmountNS(originalNetNS)).To(Succeed())
})
It("executes the callback within the target network namespace", func() {
expectedInode, err := getInodeNS(targetNetNS)
Expect(err).NotTo(HaveOccurred())
err = targetNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
actualInode, err := getInodeCurNetNS()
Expect(err).NotTo(HaveOccurred())
Expect(actualInode).To(Equal(expectedInode))
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("provides the original namespace as the argument to the callback", func() {
// Ensure we start in originalNetNS
err := originalNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
origNSInode, err := getInodeNS(originalNetNS)
Expect(err).NotTo(HaveOccurred())
err = targetNetNS.Do(func(hostNS ns.NetNS) error {
defer GinkgoRecover()
hostNSInode, err := getInodeNS(hostNS)
Expect(err).NotTo(HaveOccurred())
Expect(hostNSInode).To(Equal(origNSInode))
return nil
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
Context("when called concurrently", func() {
It("provides the original namespace as the argument to the callback", func() {
concurrency := 200
origNS, err := ns.GetCurrentNS()
Expect(err).NotTo(HaveOccurred())
origNSInode, err := getInodeNS(origNS)
Expect(err).NotTo(HaveOccurred())
var wg sync.WaitGroup
wg.Add(concurrency)
for i := 0; i < concurrency; i++ {
go func() {
defer wg.Done()
targetNetNS.Do(func(hostNS ns.NetNS) error {
defer GinkgoRecover()
hostNSInode, err := getInodeNS(hostNS)
Expect(err).NotTo(HaveOccurred())
Expect(hostNSInode).To(Equal(origNSInode))
return nil
})
}()
}
wg.Wait()
})
})
Context("when the callback returns an error", func() {
It("restores the calling thread to the original namespace before returning", func() {
err := originalNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
preTestInode, err := getInodeCurNetNS()
Expect(err).NotTo(HaveOccurred())
_ = targetNetNS.Do(func(ns.NetNS) error {
return errors.New("potato")
})
postTestInode, err := getInodeCurNetNS()
Expect(err).NotTo(HaveOccurred())
Expect(postTestInode).To(Equal(preTestInode))
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("returns the error from the callback", func() {
err := targetNetNS.Do(func(ns.NetNS) error {
return errors.New("potato")
})
Expect(err).To(MatchError("potato"))
})
})
Describe("validating inode mapping to namespaces", func() {
It("checks that different namespaces have different inodes", func() {
origNSInode, err := getInodeNS(originalNetNS)
Expect(err).NotTo(HaveOccurred())
testNsInode, err := getInodeNS(targetNetNS)
Expect(err).NotTo(HaveOccurred())
Expect(testNsInode).NotTo(Equal(0))
Expect(testNsInode).NotTo(Equal(origNSInode))
})
It("should not leak a closed netns onto any threads in the process", func() {
By("creating a new netns")
createdNetNS, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
By("discovering the inode of the created netns")
createdNetNSInode, err := getInodeNS(createdNetNS)
Expect(err).NotTo(HaveOccurred())
createdNetNS.Close()
Expect(testutils.UnmountNS(createdNetNS)).NotTo(HaveOccurred())
By("comparing against the netns inode of every thread in the process")
for _, netnsPath := range allNetNSInCurrentProcess() {
netnsInode, err := getInode(netnsPath)
if !os.IsNotExist(err) {
Expect(err).NotTo(HaveOccurred())
}
Expect(netnsInode).NotTo(Equal(createdNetNSInode))
}
})
It("fails when the path is not a namespace", func() {
tempFile, err := ioutil.TempFile("", "nstest")
Expect(err).NotTo(HaveOccurred())
defer tempFile.Close()
nspath := tempFile.Name()
defer os.Remove(nspath)
_, err = ns.GetNS(nspath)
Expect(err).To(HaveOccurred())
Expect(err).To(BeAssignableToTypeOf(ns.NSPathNotNSErr{}))
Expect(err).NotTo(BeAssignableToTypeOf(ns.NSPathNotExistErr{}))
})
})
Describe("closing a network namespace", func() {
It("should prevent further operations", func() {
createdNetNS, err := testutils.NewNS()
defer testutils.UnmountNS(createdNetNS)
Expect(err).NotTo(HaveOccurred())
err = createdNetNS.Close()
Expect(err).NotTo(HaveOccurred())
err = createdNetNS.Do(func(ns.NetNS) error { return nil })
Expect(err).To(HaveOccurred())
err = createdNetNS.Set()
Expect(err).To(HaveOccurred())
})
It("should only work once", func() {
createdNetNS, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
defer testutils.UnmountNS(createdNetNS)
err = createdNetNS.Close()
Expect(err).NotTo(HaveOccurred())
err = createdNetNS.Close()
Expect(err).To(HaveOccurred())
})
})
})
Describe("IsNSorErr", func() {
It("should detect a namespace", func() {
createdNetNS, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
defer testutils.UnmountNS(createdNetNS)
err = ns.IsNSorErr(createdNetNS.Path())
Expect(err).NotTo(HaveOccurred())
})
It("should refuse other paths", func() {
tempFile, err := ioutil.TempFile("", "nstest")
Expect(err).NotTo(HaveOccurred())
defer tempFile.Close()
nspath := tempFile.Name()
defer os.Remove(nspath)
err = ns.IsNSorErr(nspath)
Expect(err).To(HaveOccurred())
Expect(err).To(BeAssignableToTypeOf(ns.NSPathNotNSErr{}))
Expect(err).NotTo(BeAssignableToTypeOf(ns.NSPathNotExistErr{}))
})
It("should error on non-existing paths", func() {
err := ns.IsNSorErr("/tmp/IDoNotExist")
Expect(err).To(HaveOccurred())
Expect(err).To(BeAssignableToTypeOf(ns.NSPathNotExistErr{}))
Expect(err).NotTo(BeAssignableToTypeOf(ns.NSPathNotNSErr{}))
})
})
})
func allNetNSInCurrentProcess() []string {
pid := unix.Getpid()
paths, err := filepath.Glob(fmt.Sprintf("/proc/%d/task/*/ns/net", pid))
Expect(err).NotTo(HaveOccurred())
return paths
}

View File

@ -1,17 +1,3 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ns_test
import (
@ -30,5 +16,5 @@ func TestNs(t *testing.T) {
runtime.LockOSThread()
RegisterFailHandler(Fail)
RunSpecs(t, "pkg/ns")
RunSpecs(t, "pkg/ns Suite")
}

153
pkg/ns/ns_test.go Normal file
View File

@ -0,0 +1,153 @@
package ns_test
import (
"errors"
"fmt"
"math/rand"
"os"
"os/exec"
"path/filepath"
"golang.org/x/sys/unix"
"github.com/appc/cni/pkg/ns"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func getInode(path string) (uint64, error) {
file, err := os.Open(path)
if err != nil {
return 0, err
}
defer file.Close()
return getInodeF(file)
}
func getInodeF(file *os.File) (uint64, error) {
stat := &unix.Stat_t{}
err := unix.Fstat(int(file.Fd()), stat)
return stat.Ino, err
}
const CurrentNetNS = "/proc/self/ns/net"
var _ = Describe("Linux namespace operations", func() {
Describe("WithNetNS", func() {
var (
originalNetNS *os.File
targetNetNSName string
targetNetNSPath string
targetNetNS *os.File
)
BeforeEach(func() {
var err error
originalNetNS, err = os.Open(CurrentNetNS)
Expect(err).NotTo(HaveOccurred())
targetNetNSName = fmt.Sprintf("test-netns-%d", rand.Int())
err = exec.Command("ip", "netns", "add", targetNetNSName).Run()
Expect(err).NotTo(HaveOccurred())
targetNetNSPath = filepath.Join("/var/run/netns/", targetNetNSName)
targetNetNS, err = os.Open(targetNetNSPath)
Expect(err).NotTo(HaveOccurred())
})
AfterEach(func() {
Expect(targetNetNS.Close()).To(Succeed())
err := exec.Command("ip", "netns", "del", targetNetNSName).Run()
Expect(err).NotTo(HaveOccurred())
Expect(originalNetNS.Close()).To(Succeed())
})
It("executes the callback within the target network namespace", func() {
expectedInode, err := getInode(targetNetNSPath)
Expect(err).NotTo(HaveOccurred())
var actualInode uint64
var innerErr error
err = ns.WithNetNS(targetNetNS, false, func(*os.File) error {
actualInode, innerErr = getInode(CurrentNetNS)
return nil
})
Expect(err).NotTo(HaveOccurred())
Expect(innerErr).NotTo(HaveOccurred())
Expect(actualInode).To(Equal(expectedInode))
})
It("provides the original namespace as the argument to the callback", func() {
hostNSInode, err := getInode(CurrentNetNS)
Expect(err).NotTo(HaveOccurred())
var inputNSInode uint64
var innerErr error
err = ns.WithNetNS(targetNetNS, false, func(inputNS *os.File) error {
inputNSInode, err = getInodeF(inputNS)
return nil
})
Expect(err).NotTo(HaveOccurred())
Expect(innerErr).NotTo(HaveOccurred())
Expect(inputNSInode).To(Equal(hostNSInode))
})
It("restores the calling thread to the original network namespace", func() {
preTestInode, err := getInode(CurrentNetNS)
Expect(err).NotTo(HaveOccurred())
err = ns.WithNetNS(targetNetNS, false, func(*os.File) error {
return nil
})
Expect(err).NotTo(HaveOccurred())
postTestInode, err := getInode(CurrentNetNS)
Expect(err).NotTo(HaveOccurred())
Expect(postTestInode).To(Equal(preTestInode))
})
Context("when the callback returns an error", func() {
It("restores the calling thread to the original namespace before returning", func() {
preTestInode, err := getInode(CurrentNetNS)
Expect(err).NotTo(HaveOccurred())
_ = ns.WithNetNS(targetNetNS, false, func(*os.File) error {
return errors.New("potato")
})
postTestInode, err := getInode(CurrentNetNS)
Expect(err).NotTo(HaveOccurred())
Expect(postTestInode).To(Equal(preTestInode))
})
It("returns the error from the callback", func() {
err := ns.WithNetNS(targetNetNS, false, func(*os.File) error {
return errors.New("potato")
})
Expect(err).To(MatchError("potato"))
})
})
Describe("validating inode mapping to namespaces", func() {
It("checks that different namespaces have different inodes", func() {
hostNSInode, err := getInode(CurrentNetNS)
Expect(err).NotTo(HaveOccurred())
testNsInode, err := getInode(targetNetNSPath)
Expect(err).NotTo(HaveOccurred())
Expect(hostNSInode).NotTo(Equal(0))
Expect(testNsInode).NotTo(Equal(0))
Expect(testNsInode).NotTo(Equal(hostNSInode))
})
})
})
})

117
pkg/skel/skel.go Normal file
View File

@ -0,0 +1,117 @@
// Copyright 2014 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 skel provides skeleton code for a CNI plugin.
// In particular, it implements argument parsing and validation.
package skel
import (
"fmt"
"io/ioutil"
"log"
"os"
"github.com/appc/cni/pkg/types"
)
// CmdArgs captures all the arguments passed in to the plugin
// via both env vars and stdin
type CmdArgs struct {
ContainerID string
Netns string
IfName string
Args string
Path string
StdinData []byte
}
// PluginMain is the "main" for a plugin. It accepts
// two callback functions for add and del commands.
func PluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error) {
var cmd, contID, netns, ifName, args, path string
vars := []struct {
name string
val *string
req bool
}{
{"CNI_COMMAND", &cmd, true},
{"CNI_CONTAINERID", &contID, false},
{"CNI_NETNS", &netns, true},
{"CNI_IFNAME", &ifName, true},
{"CNI_ARGS", &args, false},
{"CNI_PATH", &path, true},
}
argsMissing := false
for _, v := range vars {
*v.val = os.Getenv(v.name)
if v.req && *v.val == "" {
log.Printf("%v env variable missing", v.name)
argsMissing = true
}
}
if argsMissing {
dieMsg("required env variables missing")
}
stdinData, err := ioutil.ReadAll(os.Stdin)
if err != nil {
dieMsg("error reading from stdin: %v", err)
}
cmdArgs := &CmdArgs{
ContainerID: contID,
Netns: netns,
IfName: ifName,
Args: args,
Path: path,
StdinData: stdinData,
}
switch cmd {
case "ADD":
err = cmdAdd(cmdArgs)
case "DEL":
err = cmdDel(cmdArgs)
default:
dieMsg("unknown CNI_COMMAND: %v", cmd)
}
if err != nil {
if e, ok := err.(*types.Error); ok {
// don't wrap Error in Error
dieErr(e)
}
dieMsg(err.Error())
}
}
func dieMsg(f string, args ...interface{}) {
e := &types.Error{
Code: 100,
Msg: fmt.Sprintf(f, args...),
}
dieErr(e)
}
func dieErr(e *types.Error) {
if err := e.Print(); err != nil {
log.Print("Error writing error JSON to stdout: ", err)
}
os.Exit(1)
}

View File

@ -0,0 +1,13 @@
package skel
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestSkel(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Skel Suite")
}

61
pkg/skel/skel_test.go Normal file
View File

@ -0,0 +1,61 @@
package skel
import (
"os"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Skel", func() {
var (
fNoop = func(_ *CmdArgs) error { return nil }
// fErr = func(_ *CmdArgs) error { return errors.New("dummy") }
envVars = []struct {
name string
val string
}{
{"CNI_CONTAINERID", "dummy"},
{"CNI_NETNS", "dummy"},
{"CNI_IFNAME", "dummy"},
{"CNI_ARGS", "dummy"},
{"CNI_PATH", "dummy"},
}
)
It("Must be possible to set the env vars", func() {
for _, v := range envVars {
err := os.Setenv(v.name, v.val)
Expect(err).NotTo(HaveOccurred())
}
})
Context("When dummy environment variables are passed", func() {
It("should not fail with ADD and noop callback", func() {
err := os.Setenv("CNI_COMMAND", "ADD")
Expect(err).NotTo(HaveOccurred())
PluginMain(fNoop, nil)
})
// TODO: figure out howto mock printing and os.Exit()
// It("should fail with ADD and error callback", func() {
// err := os.Setenv("CNI_COMMAND", "ADD")
// Expect(err).NotTo(HaveOccurred())
// PluginMain(fErr, nil)
// })
It("should not fail with DEL and noop callback", func() {
err := os.Setenv("CNI_COMMAND", "DEL")
Expect(err).NotTo(HaveOccurred())
PluginMain(nil, fNoop)
})
// TODO: figure out howto mock printing and os.Exit()
// It("should fail with DEL and error callback", func() {
// err := os.Setenv("CNI_COMMAND", "DEL")
// Expect(err).NotTo(HaveOccurred())
// PluginMain(fErr, nil)
// })
})
})

View File

@ -1,33 +0,0 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package testutils
import "errors"
// BadReader is an io.Reader which always errors
type BadReader struct {
Error error
}
func (r *BadReader) Read(buffer []byte) (int, error) {
if r.Error != nil {
return 0, r.Error
}
return 0, errors.New("banana")
}
func (r *BadReader) Close() error {
return nil
}

View File

@ -1,112 +0,0 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package testutils
import (
"io/ioutil"
"os"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/version"
)
func envCleanup() {
os.Unsetenv("CNI_COMMAND")
os.Unsetenv("CNI_PATH")
os.Unsetenv("CNI_NETNS")
os.Unsetenv("CNI_IFNAME")
os.Unsetenv("CNI_CONTAINERID")
}
func CmdAdd(cniNetns, cniContainerID, cniIfname string, conf []byte, f func() error) (types.Result, []byte, error) {
os.Setenv("CNI_COMMAND", "ADD")
os.Setenv("CNI_PATH", os.Getenv("PATH"))
os.Setenv("CNI_NETNS", cniNetns)
os.Setenv("CNI_IFNAME", cniIfname)
os.Setenv("CNI_CONTAINERID", cniContainerID)
defer envCleanup()
// Redirect stdout to capture plugin result
oldStdout := os.Stdout
r, w, err := os.Pipe()
if err != nil {
return nil, nil, err
}
os.Stdout = w
err = f()
w.Close()
var out []byte
if err == nil {
out, err = ioutil.ReadAll(r)
}
os.Stdout = oldStdout
// Return errors after restoring stdout so Ginkgo will correctly
// emit verbose error information on stdout
if err != nil {
return nil, nil, err
}
// Plugin must return result in same version as specified in netconf
versionDecoder := &version.ConfigDecoder{}
confVersion, err := versionDecoder.Decode(conf)
if err != nil {
return nil, nil, err
}
result, err := version.NewResult(confVersion, out)
if err != nil {
return nil, nil, err
}
return result, out, nil
}
func CmdAddWithArgs(args *skel.CmdArgs, f func() error) (types.Result, []byte, error) {
return CmdAdd(args.Netns, args.ContainerID, args.IfName, args.StdinData, f)
}
func CmdCheck(cniNetns, cniContainerID, cniIfname string, conf []byte, f func() error) error {
os.Setenv("CNI_COMMAND", "CHECK")
os.Setenv("CNI_PATH", os.Getenv("PATH"))
os.Setenv("CNI_NETNS", cniNetns)
os.Setenv("CNI_IFNAME", cniIfname)
os.Setenv("CNI_CONTAINERID", cniContainerID)
defer envCleanup()
return f()
}
func CmdCheckWithArgs(args *skel.CmdArgs, f func() error) error {
return CmdCheck(args.Netns, args.ContainerID, args.IfName, args.StdinData, f)
}
func CmdDel(cniNetns, cniContainerID, cniIfname string, f func() error) error {
os.Setenv("CNI_COMMAND", "DEL")
os.Setenv("CNI_PATH", os.Getenv("PATH"))
os.Setenv("CNI_NETNS", cniNetns)
os.Setenv("CNI_IFNAME", cniIfname)
os.Setenv("CNI_CONTAINERID", cniContainerID)
defer envCleanup()
return f()
}
func CmdDelWithArgs(args *skel.CmdArgs, f func() error) error {
return CmdDel(args.Netns, args.ContainerID, args.IfName, f)
}

View File

@ -1,60 +0,0 @@
// Copyright 2019 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package testutils
import (
"fmt"
"io/ioutil"
"os"
"strings"
"github.com/containernetworking/cni/pkg/types"
)
// TmpResolvConf will create a temporary file and write the provided DNS settings to
// it in the resolv.conf format. It returns the path of the created temporary file or
// an error if any occurs while creating/writing the file. It is the caller's
// responsibility to remove the file.
func TmpResolvConf(dnsConf types.DNS) (string, error) {
f, err := ioutil.TempFile("", "cni_test_resolv.conf")
if err != nil {
return "", fmt.Errorf("failed to get temp file for CNI test resolv.conf: %v", err)
}
defer f.Close()
path := f.Name()
defer func() {
if err != nil {
os.RemoveAll(path)
}
}()
// see "man 5 resolv.conf" for the format of resolv.conf
var resolvConfLines []string
for _, nameserver := range dnsConf.Nameservers {
resolvConfLines = append(resolvConfLines, fmt.Sprintf("nameserver %s", nameserver))
}
resolvConfLines = append(resolvConfLines, fmt.Sprintf("domain %s", dnsConf.Domain))
resolvConfLines = append(resolvConfLines, fmt.Sprintf("search %s", strings.Join(dnsConf.Search, " ")))
resolvConfLines = append(resolvConfLines, fmt.Sprintf("options %s", strings.Join(dnsConf.Options, " ")))
resolvConf := strings.Join(resolvConfLines, "\n")
_, err = f.Write([]byte(resolvConf))
if err != nil {
return "", fmt.Errorf("failed to write temp resolv.conf for CNI test: %v", err)
}
return path, err
}

View File

@ -1,90 +0,0 @@
package main
import (
"flag"
"fmt"
"io"
"net"
)
func main() {
target := flag.String("target", "", "the server address")
payload := flag.String("message", "", "the message to send to the server")
protocol := flag.String("protocol", "tcp", "the protocol to use with the server [udp,tcp], default tcp")
flag.Parse()
if *target == "" || *payload == "" {
flag.Usage()
panic("invalid arguments")
}
switch *protocol {
case "tcp":
connectTCP(*target, *payload)
case "udp":
connectUDP(*target, *payload)
default:
panic("invalid protocol")
}
}
func connectTCP(target, payload string) {
conn, err := net.Dial("tcp", target)
if err != nil {
panic(fmt.Sprintf("Failed to open connection to [%s] %v", target, err))
}
defer conn.Close()
_, err = conn.Write([]byte(payload))
if err != nil {
panic("Failed to send payload")
}
_, err = conn.Write([]byte("\n"))
if err != nil {
panic("Failed to send payload")
}
buf := make([]byte, 1024)
for {
n, err := conn.Read(buf)
fmt.Print(string(buf[:n]))
if err == io.EOF {
break
}
if err != nil {
panic("Failed to read from socket")
}
}
}
// UDP uses a constant source port to trigger conntrack problems
func connectUDP(target, payload string) {
LocalAddr, err := net.ResolveUDPAddr("udp", ":54321")
if err != nil {
panic(fmt.Sprintf("Failed to resolve UDP local address on port 54321 %v", err))
}
RemoteAddr, err := net.ResolveUDPAddr("udp", target)
if err != nil {
panic(fmt.Sprintf("Failed to resolve UDP remote address [%s] %v", target, err))
}
conn, err := net.DialUDP("udp", LocalAddr, RemoteAddr)
if err != nil {
panic(fmt.Sprintf("Failed to open connection to [%s] %v", target, err))
}
defer conn.Close()
_, err = conn.Write([]byte(payload))
if err != nil {
panic("Failed to send payload")
}
_, err = conn.Write([]byte("\n"))
if err != nil {
panic("Failed to send payload")
}
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil {
panic("Failed to read from socket")
}
fmt.Print(string(buf[:n]))
}

View File

@ -1,98 +0,0 @@
package main_test
import (
"fmt"
"io/ioutil"
"net"
"os/exec"
"strings"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gbytes"
"github.com/onsi/gomega/gexec"
)
var serverBinaryPath, clientBinaryPath string
var _ = SynchronizedBeforeSuite(func() []byte {
serverBinaryPath, err := gexec.Build("github.com/containernetworking/plugins/pkg/testutils/echo/server")
Expect(err).NotTo(HaveOccurred())
clientBinaryPath, err := gexec.Build("github.com/containernetworking/plugins/pkg/testutils/echo/client")
Expect(err).NotTo(HaveOccurred())
return []byte(strings.Join([]string{serverBinaryPath, clientBinaryPath}, ","))
}, func(data []byte) {
binaries := strings.Split(string(data), ",")
serverBinaryPath = binaries[0]
clientBinaryPath = binaries[1]
})
var _ = SynchronizedAfterSuite(func() {}, func() {
gexec.CleanupBuildArtifacts()
})
var _ = Describe("Echosvr", func() {
var session *gexec.Session
BeforeEach(func() {
var err error
cmd := exec.Command(serverBinaryPath)
session, err = gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
Expect(err).NotTo(HaveOccurred())
})
AfterEach(func() {
session.Kill().Wait()
})
Context("Server test", func() {
It("starts and doesn't terminate immediately", func() {
Consistently(session).ShouldNot(gexec.Exit())
})
tryConnect := func() (net.Conn, error) {
programOutput := session.Out.Contents()
addr := strings.TrimSpace(string(programOutput))
conn, err := net.Dial("tcp", addr)
if err != nil {
return nil, err
}
return conn, err
}
It("prints its listening address to stdout", func() {
Eventually(session.Out).Should(gbytes.Say("\n"))
conn, err := tryConnect()
Expect(err).NotTo(HaveOccurred())
conn.Close()
})
It("will echo data back to us", func() {
Eventually(session.Out).Should(gbytes.Say("\n"))
conn, err := tryConnect()
Expect(err).NotTo(HaveOccurred())
defer conn.Close()
fmt.Fprintf(conn, "hello\n")
Expect(ioutil.ReadAll(conn)).To(Equal([]byte("hello")))
})
})
Context("Client Server Test", func() {
It("starts and doesn't terminate immediately", func() {
Consistently(session).ShouldNot(gexec.Exit())
})
It("connects successfully using echo client", func() {
Eventually(session.Out).Should(gbytes.Say("\n"))
serverAddress := strings.TrimSpace(string(session.Out.Contents()))
fmt.Println("Server address", string(serverAddress))
cmd := exec.Command(clientBinaryPath, "-target", serverAddress, "-message", "hello")
clientSession, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
Expect(err).NotTo(HaveOccurred())
Eventually(clientSession.Out).Should(gbytes.Say("hello"))
Eventually(clientSession).Should(gexec.Exit())
})
})
})

View File

@ -1,86 +0,0 @@
// Echosvr is a simple TCP echo server
//
// It prints its listen address on stdout
// 127.0.0.1:xxxxx
// A test should wait for this line, parse it
// and may then attempt to connect.
package main
import (
"bufio"
"fmt"
"io"
"log"
"net"
"os"
"strings"
"time"
)
func main() {
// Start TCP server
listener, err := net.Listen("tcp", ":")
if err != nil {
panic(err)
}
defer listener.Close()
// use the same port for UDP
_, port, err := net.SplitHostPort(listener.Addr().String())
if err != nil {
panic(err)
}
fmt.Printf("127.0.0.1:%s\n", port)
go func() {
for {
conn, err := listener.Accept()
if err != nil {
panic(err)
}
go handleConnection(conn)
}
}()
// Start UDP server
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%s", port))
if err != nil {
log.Fatalf("Error from net.ResolveUDPAddr(): %s", err)
}
sock, err := net.ListenUDP("udp", addr)
if err != nil {
log.Fatalf("Error from ListenUDP(): %s", err)
}
defer sock.Close()
buffer := make([]byte, 1024)
for {
n, addr, err := sock.ReadFrom(buffer)
if err != nil {
log.Fatalf("Error from ReadFrom(): %s", err)
}
sock.SetWriteDeadline(time.Now().Add(1 * time.Minute))
n, err = sock.WriteTo(buffer[0:n], addr)
if err != nil {
return
}
}
}
func handleConnection(conn net.Conn) {
conn.SetReadDeadline(time.Now().Add(1 * time.Minute))
content, err := bufio.NewReader(conn).ReadString('\n')
if err != nil && err != io.EOF {
fmt.Fprint(os.Stderr, err.Error())
return
}
conn.SetWriteDeadline(time.Now().Add(1 * time.Minute))
if _, err = conn.Write([]byte(strings.TrimSuffix(content, "\n"))); err != nil {
fmt.Fprint(os.Stderr, err.Error())
return
}
if err = conn.Close(); err != nil {
fmt.Fprint(os.Stderr, err.Error())
return
}
}

View File

@ -1,176 +0,0 @@
// Copyright 2018 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package testutils
import (
"crypto/rand"
"fmt"
"os"
"path"
"runtime"
"strings"
"sync"
"syscall"
"github.com/containernetworking/plugins/pkg/ns"
"golang.org/x/sys/unix"
)
func getNsRunDir() string {
xdgRuntimeDir := os.Getenv("XDG_RUNTIME_DIR")
/// If XDG_RUNTIME_DIR is set, check if the current user owns /var/run. If
// the owner is different, we are most likely running in a user namespace.
// In that case use $XDG_RUNTIME_DIR/netns as runtime dir.
if xdgRuntimeDir != "" {
if s, err := os.Stat("/var/run"); err == nil {
st, ok := s.Sys().(*syscall.Stat_t)
if ok && int(st.Uid) != os.Geteuid() {
return path.Join(xdgRuntimeDir, "netns")
}
}
}
return "/var/run/netns"
}
// Creates a new persistent (bind-mounted) network namespace and returns an object
// representing that namespace, without switching to it.
func NewNS() (ns.NetNS, error) {
nsRunDir := getNsRunDir()
b := make([]byte, 16)
_, err := rand.Reader.Read(b)
if err != nil {
return nil, fmt.Errorf("failed to generate random netns name: %v", err)
}
// Create the directory for mounting network namespaces
// This needs to be a shared mountpoint in case it is mounted in to
// other namespaces (containers)
err = os.MkdirAll(nsRunDir, 0755)
if err != nil {
return nil, err
}
// Remount the namespace directory shared. This will fail if it is not
// already a mountpoint, so bind-mount it on to itself to "upgrade" it
// to a mountpoint.
err = unix.Mount("", nsRunDir, "none", unix.MS_SHARED|unix.MS_REC, "")
if err != nil {
if err != unix.EINVAL {
return nil, fmt.Errorf("mount --make-rshared %s failed: %q", nsRunDir, err)
}
// Recursively remount /var/run/netns on itself. The recursive flag is
// so that any existing netns bindmounts are carried over.
err = unix.Mount(nsRunDir, nsRunDir, "none", unix.MS_BIND|unix.MS_REC, "")
if err != nil {
return nil, fmt.Errorf("mount --rbind %s %s failed: %q", nsRunDir, nsRunDir, err)
}
// Now we can make it shared
err = unix.Mount("", nsRunDir, "none", unix.MS_SHARED|unix.MS_REC, "")
if err != nil {
return nil, fmt.Errorf("mount --make-rshared %s failed: %q", nsRunDir, err)
}
}
nsName := fmt.Sprintf("cnitest-%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
// create an empty file at the mount point
nsPath := path.Join(nsRunDir, nsName)
mountPointFd, err := os.Create(nsPath)
if err != nil {
return nil, err
}
mountPointFd.Close()
// Ensure the mount point is cleaned up on errors; if the namespace
// was successfully mounted this will have no effect because the file
// is in-use
defer os.RemoveAll(nsPath)
var wg sync.WaitGroup
wg.Add(1)
// do namespace work in a dedicated goroutine, so that we can safely
// Lock/Unlock OSThread without upsetting the lock/unlock state of
// the caller of this function
go (func() {
defer wg.Done()
runtime.LockOSThread()
// Don't unlock. By not unlocking, golang will kill the OS thread when the
// goroutine is done (for go1.10+)
var origNS ns.NetNS
origNS, err = ns.GetNS(getCurrentThreadNetNSPath())
if err != nil {
return
}
defer origNS.Close()
// create a new netns on the current thread
err = unix.Unshare(unix.CLONE_NEWNET)
if err != nil {
return
}
// Put this thread back to the orig ns, since it might get reused (pre go1.10)
defer origNS.Set()
// bind mount the netns from the current thread (from /proc) onto the
// mount point. This causes the namespace to persist, even when there
// are no threads in the ns.
err = unix.Mount(getCurrentThreadNetNSPath(), nsPath, "none", unix.MS_BIND, "")
if err != nil {
err = fmt.Errorf("failed to bind mount ns at %s: %v", nsPath, err)
}
})()
wg.Wait()
if err != nil {
return nil, fmt.Errorf("failed to create namespace: %v", err)
}
return ns.GetNS(nsPath)
}
// UnmountNS unmounts the NS held by the netns object
func UnmountNS(ns ns.NetNS) error {
nsPath := ns.Path()
// Only unmount if it's been bind-mounted (don't touch namespaces in /proc...)
if strings.HasPrefix(nsPath, getNsRunDir()) {
if err := unix.Unmount(nsPath, 0); err != nil {
return fmt.Errorf("failed to unmount NS: at %s: %v", nsPath, err)
}
if err := os.Remove(nsPath); err != nil {
return fmt.Errorf("failed to remove ns path %s: %v", nsPath, err)
}
}
return nil
}
// getCurrentThreadNetNSPath copied from pkg/ns
func getCurrentThreadNetNSPath() string {
// /proc/self/ns/net returns the namespace of the main thread, not
// of whatever thread this goroutine is running on. Make sure we
// use the thread's net namespace since the thread is switching around
return fmt.Sprintf("/proc/%d/task/%d/ns/net", os.Getpid(), unix.Gettid())
}

View File

@ -1,61 +0,0 @@
// 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 testutils
import (
"bytes"
"fmt"
"net"
"os/exec"
"strconv"
"syscall"
)
// Ping shells out to the `ping` command. Returns nil if successful.
func Ping(saddr, daddr string, timeoutSec int) error {
ip := net.ParseIP(saddr)
if ip == nil {
return fmt.Errorf("failed to parse IP %q", saddr)
}
bin := "ping6"
if ip.To4() != nil {
bin = "ping"
}
args := []string{
"-c", "1",
"-W", strconv.Itoa(timeoutSec),
"-I", saddr,
daddr,
}
cmd := exec.Command(bin, args...)
var stderr bytes.Buffer
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
switch e := err.(type) {
case *exec.ExitError:
return fmt.Errorf("%v exit status %d: %s",
args, e.Sys().(syscall.WaitStatus).ExitStatus(),
stderr.String())
default:
return err
}
}
return nil
}

View File

@ -1,54 +0,0 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package testutils
import (
"github.com/containernetworking/cni/pkg/version"
)
// AllSpecVersions contains all CNI spec version numbers
var AllSpecVersions = [...]string{"0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0"}
// SpecVersionHasIPVersion returns true if the given CNI specification version
// includes the "version" field in the IP address elements
func SpecVersionHasIPVersion(ver string) bool {
for _, i := range []string{"0.3.0", "0.3.1", "0.4.0"} {
if ver == i {
return true
}
}
return false
}
// SpecVersionHasCHECK returns true if the given CNI specification version
// supports the CHECK command
func SpecVersionHasCHECK(ver string) bool {
ok, _ := version.GreaterThanOrEqualTo(ver, "0.4.0")
return ok
}
// SpecVersionHasChaining returns true if the given CNI specification version
// supports plugin chaining
func SpecVersionHasChaining(ver string) bool {
ok, _ := version.GreaterThanOrEqualTo(ver, "0.3.0")
return ok
}
// SpecVersionHasMultipleIPs returns true if the given CNI specification version
// supports more than one IP address of each family
func SpecVersionHasMultipleIPs(ver string) bool {
ok, _ := version.GreaterThanOrEqualTo(ver, "0.3.0")
return ok
}

View File

@ -36,21 +36,11 @@ func (b *UnmarshallableBool) UnmarshalText(data []byte) error {
case "0", "false":
*b = false
default:
return fmt.Errorf("boolean unmarshal error: invalid input %s", s)
return fmt.Errorf("Boolean unmarshal error: invalid input %s", s)
}
return nil
}
// UnmarshallableString typedef for builtin string
type UnmarshallableString string
// UnmarshalText implements the encoding.TextUnmarshaler interface.
// Returns the string
func (s *UnmarshallableString) UnmarshalText(data []byte) error {
*s = UnmarshallableString(data)
return nil
}
// CommonArgs contains the IgnoreUnknown argument
// and must be embedded by all Arg structs
type CommonArgs struct {
@ -63,12 +53,6 @@ func GetKeyField(keyString string, v reflect.Value) reflect.Value {
return v.Elem().FieldByName(keyString)
}
// UnmarshalableArgsError is used to indicate error unmarshalling args
// from the args-string in the form "K=V;K2=V2;..."
type UnmarshalableArgsError struct {
error
}
// LoadArgs parses args from a string in the form "K=V;K2=V2;..."
func LoadArgs(args string, container interface{}) error {
if args == "" {
@ -92,25 +76,10 @@ func LoadArgs(args string, container interface{}) error {
continue
}
var keyFieldInterface interface{}
switch {
case keyField.Kind() == reflect.Ptr:
keyField.Set(reflect.New(keyField.Type().Elem()))
keyFieldInterface = keyField.Interface()
case keyField.CanAddr() && keyField.Addr().CanInterface():
keyFieldInterface = keyField.Addr().Interface()
default:
return UnmarshalableArgsError{fmt.Errorf("field '%s' has no valid interface", keyString)}
}
u, ok := keyFieldInterface.(encoding.TextUnmarshaler)
if !ok {
return UnmarshalableArgsError{fmt.Errorf(
"ARGS: cannot unmarshal into field '%s' - type '%s' does not implement encoding.TextUnmarshaler",
keyString, reflect.TypeOf(keyFieldInterface))}
}
u := keyField.Addr().Interface().(encoding.TextUnmarshaler)
err := u.UnmarshalText([]byte(valueString))
if err != nil {
return fmt.Errorf("ARGS: error parsing value of pair %q: %w", pair, err)
return fmt.Errorf("ARGS: error parsing value of pair %q: %v)", pair, err)
}
}

92
pkg/types/args_test.go Normal file
View File

@ -0,0 +1,92 @@
package types_test
import (
"reflect"
. "github.com/appc/cni/pkg/types"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega"
)
var _ = Describe("UnmarshallableBool UnmarshalText", func() {
DescribeTable("string to bool detection should succeed in all cases",
func(inputs []string, expected bool) {
for _, s := range inputs {
var ub UnmarshallableBool
err := ub.UnmarshalText([]byte(s))
Expect(err).ToNot(HaveOccurred())
Expect(ub).To(Equal(UnmarshallableBool(expected)))
}
},
Entry("parse to true", []string{"True", "true", "1"}, true),
Entry("parse to false", []string{"False", "false", "0"}, false),
)
Context("When passed an invalid value", func() {
It("should result in an error", func() {
var ub UnmarshallableBool
err := ub.UnmarshalText([]byte("invalid"))
Expect(err).To(HaveOccurred())
})
})
})
var _ = Describe("GetKeyField", func() {
type testcontainer struct {
Valid string `json:"valid,omitempty"`
}
var (
container = testcontainer{Valid: "valid"}
containerInterface = func(i interface{}) interface{} { return i }(&container)
containerValue = reflect.ValueOf(containerInterface)
)
Context("When a valid field is provided", func() {
It("should return the correct field", func() {
field := GetKeyField("Valid", containerValue)
Expect(field.String()).To(Equal("valid"))
})
})
})
var _ = Describe("LoadArgs", func() {
Context("When no arguments are passed", func() {
It("LoadArgs should succeed", func() {
err := LoadArgs("", struct{}{})
Expect(err).NotTo(HaveOccurred())
})
})
Context("When unknown arguments are passed and ignored", func() {
It("LoadArgs should succeed", func() {
ca := CommonArgs{}
err := LoadArgs("IgnoreUnknown=True;Unk=nown", &ca)
Expect(err).NotTo(HaveOccurred())
})
})
Context("When unknown arguments are passed and not ignored", func() {
It("LoadArgs should fail", func() {
ca := CommonArgs{}
err := LoadArgs("Unk=nown", &ca)
Expect(err).To(HaveOccurred())
})
})
Context("When unknown arguments are passed and explicitly not ignored", func() {
It("LoadArgs should fail", func() {
ca := CommonArgs{}
err := LoadArgs("IgnoreUnknown=0, Unk=nown", &ca)
Expect(err).To(HaveOccurred())
})
})
Context("When known arguments are passed", func() {
It("LoadArgs should succeed", func() {
ca := CommonArgs{}
err := LoadArgs("IgnoreUnknown=1", &ca)
Expect(err).NotTo(HaveOccurred())
})
})
})

191
pkg/types/types.go Normal file
View File

@ -0,0 +1,191 @@
// 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 types
import (
"encoding/json"
"fmt"
"net"
"os"
)
// like net.IPNet but adds JSON marshalling and unmarshalling
type IPNet net.IPNet
// ParseCIDR takes a string like "10.2.3.1/24" and
// return IPNet with "10.2.3.1" and /24 mask
func ParseCIDR(s string) (*net.IPNet, error) {
ip, ipn, err := net.ParseCIDR(s)
if err != nil {
return nil, err
}
ipn.IP = ip
return ipn, nil
}
func (n IPNet) MarshalJSON() ([]byte, error) {
return json.Marshal((*net.IPNet)(&n).String())
}
func (n *IPNet) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
tmp, err := ParseCIDR(s)
if err != nil {
return err
}
*n = IPNet(*tmp)
return nil
}
// NetConf describes a network.
type NetConf struct {
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
IPAM struct {
Type string `json:"type,omitempty"`
} `json:"ipam,omitempty"`
DNS DNS `json:"dns"`
}
// Result is what gets returned from the plugin (via stdout) to the caller
type Result struct {
IP4 *IPConfig `json:"ip4,omitempty"`
IP6 *IPConfig `json:"ip6,omitempty"`
DNS DNS `json:"dns,omitempty"`
}
func (r *Result) Print() error {
return prettyPrint(r)
}
// String returns a formatted string in the form of "[IP4: $1,][ IP6: $2,] DNS: $3" where
// $1 represents the receiver's IPv4, $2 represents the receiver's IPv6 and $3 the
// receiver's DNS. If $1 or $2 are nil, they won't be present in the returned string.
func (r *Result) String() string {
var str string
if r.IP4 != nil {
str = fmt.Sprintf("IP4:%+v, ", *r.IP4)
}
if r.IP6 != nil {
str += fmt.Sprintf("IP6:%+v, ", *r.IP6)
}
return fmt.Sprintf("%sDNS:%+v", str, r.DNS)
}
// IPConfig contains values necessary to configure an interface
type IPConfig struct {
IP net.IPNet
Gateway net.IP
Routes []Route
}
// DNS contains values interesting for DNS resolvers
type DNS struct {
Nameservers []string `json:"nameservers,omitempty"`
Domain string `json:"domain,omitempty"`
Search []string `json:"search,omitempty"`
Options []string `json:"options,omitempty"`
}
type Route struct {
Dst net.IPNet
GW net.IP
}
type Error struct {
Code uint `json:"code"`
Msg string `json:"msg"`
Details string `json:"details,omitempty"`
}
func (e *Error) Error() string {
return e.Msg
}
func (e *Error) Print() error {
return prettyPrint(e)
}
// net.IPNet is not JSON (un)marshallable so this duality is needed
// for our custom IPNet type
// JSON (un)marshallable types
type ipConfig struct {
IP IPNet `json:"ip"`
Gateway net.IP `json:"gateway,omitempty"`
Routes []Route `json:"routes,omitempty"`
}
type route struct {
Dst IPNet `json:"dst"`
GW net.IP `json:"gw,omitempty"`
}
func (c *IPConfig) MarshalJSON() ([]byte, error) {
ipc := ipConfig{
IP: IPNet(c.IP),
Gateway: c.Gateway,
Routes: c.Routes,
}
return json.Marshal(ipc)
}
func (c *IPConfig) UnmarshalJSON(data []byte) error {
ipc := ipConfig{}
if err := json.Unmarshal(data, &ipc); err != nil {
return err
}
c.IP = net.IPNet(ipc.IP)
c.Gateway = ipc.Gateway
c.Routes = ipc.Routes
return nil
}
func (r *Route) UnmarshalJSON(data []byte) error {
rt := route{}
if err := json.Unmarshal(data, &rt); err != nil {
return err
}
r.Dst = net.IPNet(rt.Dst)
r.GW = rt.GW
return nil
}
func (r *Route) MarshalJSON() ([]byte, error) {
rt := route{
Dst: IPNet(r.Dst),
GW: r.GW,
}
return json.Marshal(rt)
}
func prettyPrint(obj interface{}) error {
data, err := json.MarshalIndent(obj, "", " ")
if err != nil {
return err
}
_, err = os.Stdout.Write(data)
return err
}

View File

@ -0,0 +1,13 @@
package types_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestTypes(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Types Suite")
}

View File

@ -1,26 +0,0 @@
// Copyright 2019 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.
// Buildversion is a destination for the linker trickery so we can auto
// set the build-version
package buildversion
import "fmt"
// This is overridden in the linker script
var BuildVersion = "version unknown"
func BuildString(pluginName string) string {
return fmt.Sprintf("CNI %s plugin %s", pluginName, BuildVersion)
}

View File

@ -1,73 +0,0 @@
// Copyright 2020 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package utils
import (
"fmt"
"net"
"github.com/vishvananda/netlink"
"golang.org/x/sys/unix"
)
// Assigned Internet Protocol Numbers
// https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml
const (
PROTOCOL_TCP = 6
PROTOCOL_UDP = 17
PROTOCOL_SCTP = 132
)
// getNetlinkFamily returns the Netlink IP family constant
func getNetlinkFamily(isIPv6 bool) netlink.InetFamily {
if isIPv6 {
return unix.AF_INET6
}
return unix.AF_INET
}
// DeleteConntrackEntriesForDstIP delete the conntrack entries for the connections
// specified by the given destination IP and protocol
func DeleteConntrackEntriesForDstIP(dstIP string, protocol uint8) error {
ip := net.ParseIP(dstIP)
if ip == nil {
return fmt.Errorf("error deleting connection tracking state, bad IP %s", ip)
}
family := getNetlinkFamily(ip.To4() == nil)
filter := &netlink.ConntrackFilter{}
filter.AddIP(netlink.ConntrackOrigDstIP, ip)
filter.AddProtocol(protocol)
_, err := netlink.ConntrackDeleteFilter(netlink.ConntrackTable, family, filter)
if err != nil {
return fmt.Errorf("error deleting connection tracking state for protocol: %d IP: %s, error: %v", protocol, ip, err)
}
return nil
}
// DeleteConntrackEntriesForDstPort delete the conntrack entries for the connections specified
// by the given destination port, protocol and IP family
func DeleteConntrackEntriesForDstPort(port uint16, protocol uint8, family netlink.InetFamily) error {
filter := &netlink.ConntrackFilter{}
filter.AddPort(netlink.ConntrackOrigDstPort, port)
filter.AddProtocol(protocol)
_, err := netlink.ConntrackDeleteFilter(netlink.ConntrackTable, family, filter)
if err != nil {
return fmt.Errorf("error deleting connection tracking state for protocol: %d Port: %d, error: %v", protocol, port, err)
}
return nil
}

View File

@ -1,121 +0,0 @@
// 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 utils
import (
"errors"
"fmt"
"github.com/coreos/go-iptables/iptables"
)
const statusChainExists = 1
// EnsureChain idempotently creates the iptables chain. It does not
// return an error if the chain already exists.
func EnsureChain(ipt *iptables.IPTables, table, chain string) error {
if ipt == nil {
return errors.New("failed to ensure iptable chain: IPTables was nil")
}
exists, err := ChainExists(ipt, table, chain)
if err != nil {
return fmt.Errorf("failed to list iptables chains: %v", err)
}
if !exists {
err = ipt.NewChain(table, chain)
if err != nil {
eerr, eok := err.(*iptables.Error)
if eok && eerr.ExitStatus() != statusChainExists {
return err
}
}
}
return nil
}
// ChainExists checks whether an iptables chain exists.
func ChainExists(ipt *iptables.IPTables, table, chain string) (bool, error) {
if ipt == nil {
return false, errors.New("failed to check iptable chain: IPTables was nil")
}
chains, err := ipt.ListChains(table)
if err != nil {
return false, err
}
for _, ch := range chains {
if ch == chain {
return true, nil
}
}
return false, nil
}
// DeleteRule idempotently delete the iptables rule in the specified table/chain.
// It does not return an error if the referring chain doesn't exist
func DeleteRule(ipt *iptables.IPTables, table, chain string, rulespec ...string) error {
if ipt == nil {
return errors.New("failed to ensure iptable chain: IPTables was nil")
}
if err := ipt.Delete(table, chain, rulespec...); err != nil {
eerr, eok := err.(*iptables.Error)
switch {
case eok && eerr.IsNotExist():
// swallow here, the chain was already deleted
return nil
case eok && eerr.ExitStatus() == 2:
// swallow here, invalid command line parameter because the referring rule is missing
return nil
default:
return fmt.Errorf("Failed to delete referring rule %s %s: %v", table, chain, err)
}
}
return nil
}
// DeleteChain idempotently deletes the specified table/chain.
// It does not return an errors if the chain does not exist
func DeleteChain(ipt *iptables.IPTables, table, chain string) error {
if ipt == nil {
return errors.New("failed to ensure iptable chain: IPTables was nil")
}
err := ipt.DeleteChain(table, chain)
eerr, eok := err.(*iptables.Error)
switch {
case eok && eerr.IsNotExist():
// swallow here, the chain was already deleted
return nil
default:
return err
}
}
// ClearChain idempotently clear the iptables rules in the specified table/chain.
// If the chain does not exist, a new one will be created
func ClearChain(ipt *iptables.IPTables, table, chain string) error {
if ipt == nil {
return errors.New("failed to ensure iptable chain: IPTables was nil")
}
err := ipt.ClearChain(table, chain)
eerr, eok := err.(*iptables.Error)
switch {
case eok && eerr.IsNotExist():
// swallow here, the chain was already deleted
return EnsureChain(ipt, table, chain)
default:
return err
}
}

View File

@ -1,97 +0,0 @@
// Copyright 2017-2018 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package utils
import (
"fmt"
"math/rand"
"runtime"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils"
"github.com/coreos/go-iptables/iptables"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
const TABLE = "filter" // We'll monkey around here
var _ = Describe("chain tests", func() {
var testChain string
var ipt *iptables.IPTables
var cleanup func()
BeforeEach(func() {
// Save a reference to the original namespace,
// Add a new NS
currNs, err := ns.GetCurrentNS()
Expect(err).NotTo(HaveOccurred())
testNs, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
testChain = fmt.Sprintf("cni-test-%d", rand.Intn(10000000))
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
Expect(err).NotTo(HaveOccurred())
runtime.LockOSThread()
err = testNs.Set()
Expect(err).NotTo(HaveOccurred())
cleanup = func() {
if ipt == nil {
return
}
ipt.ClearChain(TABLE, testChain)
ipt.DeleteChain(TABLE, testChain)
currNs.Set()
}
})
AfterEach(func() {
cleanup()
})
Describe("EnsureChain", func() {
It("creates chains idempotently", func() {
err := EnsureChain(ipt, TABLE, testChain)
Expect(err).NotTo(HaveOccurred())
// Create it again!
err = EnsureChain(ipt, TABLE, testChain)
Expect(err).NotTo(HaveOccurred())
})
})
Describe("DeleteChain", func() {
It("delete chains idempotently", func() {
// Create chain
err := EnsureChain(ipt, TABLE, testChain)
Expect(err).NotTo(HaveOccurred())
// Delete chain
err = DeleteChain(ipt, TABLE, testChain)
Expect(err).NotTo(HaveOccurred())
// Delete it again!
err = DeleteChain(ipt, TABLE, testChain)
Expect(err).NotTo(HaveOccurred())
})
})
})

View File

@ -1,80 +0,0 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sysctl
import (
"fmt"
"io/ioutil"
"path/filepath"
"strings"
)
// Sysctl provides a method to set/get values from /proc/sys - in linux systems
// new interface to set/get values of variables formerly handled by sysctl syscall
// If optional `params` have only one string value - this function will
// set this value into corresponding sysctl variable
func Sysctl(name string, params ...string) (string, error) {
if len(params) > 1 {
return "", fmt.Errorf("unexcepted additional parameters")
} else if len(params) == 1 {
return setSysctl(name, params[0])
}
return getSysctl(name)
}
func getSysctl(name string) (string, error) {
fullName := filepath.Join("/proc/sys", toNormalName(name))
fullName = filepath.Clean(fullName)
data, err := ioutil.ReadFile(fullName)
if err != nil {
return "", err
}
return string(data[:len(data)-1]), nil
}
func setSysctl(name, value string) (string, error) {
fullName := filepath.Join("/proc/sys", toNormalName(name))
fullName = filepath.Clean(fullName)
if err := ioutil.WriteFile(fullName, []byte(value), 0644); err != nil {
return "", err
}
return getSysctl(name)
}
// Normalize names by using slash as separator
// Sysctl names can use dots or slashes as separator:
// - if dots are used, dots and slashes are interchanged.
// - if slashes are used, slashes and dots are left intact.
// Separator in use is determined by first occurrence.
func toNormalName(name string) string {
interchange := false
for _, c := range name {
if c == '.' {
interchange = true
break
}
if c == '/' {
break
}
}
if interchange {
r := strings.NewReplacer(".", "/", "/", ".")
return r.Replace(name)
}
return name
}

View File

@ -1,114 +0,0 @@
// Copyright 2017-2020 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sysctl_test
import (
"fmt"
"math/rand"
"runtime"
"strings"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils"
"github.com/containernetworking/plugins/pkg/utils/sysctl"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vishvananda/netlink"
)
const (
sysctlDotKeyTemplate = "net.ipv4.conf.%s.proxy_arp"
sysctlSlashKeyTemplate = "net/ipv4/conf/%s/proxy_arp"
)
var _ = Describe("Sysctl tests", func() {
var testIfaceName string
var cleanup func()
BeforeEach(func() {
// Save a reference to the original namespace,
// Add a new NS
currNs, err := ns.GetCurrentNS()
Expect(err).NotTo(HaveOccurred())
testNs, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
testIfaceName = fmt.Sprintf("cnitest.%d", rand.Intn(100000))
testIface := &netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: testIfaceName,
Namespace: netlink.NsFd(int(testNs.Fd())),
},
}
err = netlink.LinkAdd(testIface)
Expect(err).NotTo(HaveOccurred())
runtime.LockOSThread()
err = testNs.Set()
Expect(err).NotTo(HaveOccurred())
cleanup = func() {
netlink.LinkDel(testIface)
currNs.Set()
}
})
AfterEach(func() {
cleanup()
})
Describe("Sysctl", func() {
It("reads keys with dot separators", func() {
sysctlIfaceName := strings.Replace(testIfaceName, ".", "/", -1)
sysctlKey := fmt.Sprintf(sysctlDotKeyTemplate, sysctlIfaceName)
_, err := sysctl.Sysctl(sysctlKey)
Expect(err).NotTo(HaveOccurred())
})
})
Describe("Sysctl", func() {
It("reads keys with slash separators", func() {
sysctlKey := fmt.Sprintf(sysctlSlashKeyTemplate, testIfaceName)
_, err := sysctl.Sysctl(sysctlKey)
Expect(err).NotTo(HaveOccurred())
})
})
Describe("Sysctl", func() {
It("writes keys with dot separators", func() {
sysctlIfaceName := strings.Replace(testIfaceName, ".", "/", -1)
sysctlKey := fmt.Sprintf(sysctlDotKeyTemplate, sysctlIfaceName)
_, err := sysctl.Sysctl(sysctlKey, "1")
Expect(err).NotTo(HaveOccurred())
})
})
Describe("Sysctl", func() {
It("writes keys with slash separators", func() {
sysctlKey := fmt.Sprintf(sysctlSlashKeyTemplate, testIfaceName)
_, err := sysctl.Sysctl(sysctlKey, "1")
Expect(err).NotTo(HaveOccurred())
})
})
})

View File

@ -1,27 +0,0 @@
// Copyright 2017-2020 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sysctl_test
import (
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestSysctl(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Sysctl Suite")
}

View File

@ -1,17 +1,3 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package utils
import (
@ -22,22 +8,16 @@ import (
const (
maxChainLength = 28
chainPrefix = "CNI-"
prefixLength = len(chainPrefix)
)
// FormatChainName generates a chain name to be used
// with iptables. Ensures that the generated chain
// name is exactly maxChainLength chars in length.
// Generates a chain name to be used with iptables.
// Ensures that the generated chain name is exactly
// maxChainLength chars in length
func FormatChainName(name string, id string) string {
return MustFormatChainNameWithPrefix(name, id, "")
}
// MustFormatChainNameWithPrefix generates a chain name similar
// to FormatChainName, but adds a custom prefix between
// chainPrefix and unique identifier. Ensures that the
// generated chain name is exactly maxChainLength chars in length.
// Panics if the given prefix is too long.
func MustFormatChainNameWithPrefix(name string, id string, prefix string) string {
return MustFormatHashWithPrefix(maxChainLength, chainPrefix+prefix, name+id)
chainBytes := sha512.Sum512([]byte(name + id))
chain := fmt.Sprintf("%s%x", chainPrefix, chainBytes)
return chain[:maxChainLength]
}
// FormatComment returns a comment used for easier
@ -45,16 +25,3 @@ func MustFormatChainNameWithPrefix(name string, id string, prefix string) string
func FormatComment(name string, id string) string {
return fmt.Sprintf("name: %q id: %q", name, id)
}
const MaxHashLen = sha512.Size * 2
// MustFormatHashWithPrefix returns a string of given length that begins with the
// given prefix. It is filled with entropy based on the given string toHash.
func MustFormatHashWithPrefix(length int, prefix string, toHash string) string {
if len(prefix) >= length || length > MaxHashLen {
panic("invalid length")
}
output := sha512.Sum512([]byte(toHash))
return fmt.Sprintf("%s%x", prefix, output)[:length]
}

View File

@ -1,17 +1,3 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package utils_test
import (
@ -23,5 +9,5 @@ import (
func TestUtils(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "pkg/utils")
RunSpecs(t, "Utils Suite")
}

View File

@ -1,165 +1,37 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package utils
import (
"fmt"
"strings"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Utils", func() {
Describe("FormatChainName", func() {
It("must format a short name", func() {
chain := FormatChainName("test", "1234")
Expect(len(chain)).To(Equal(maxChainLength))
Expect(chain).To(Equal("CNI-2bbe0c48b91a7d1b8a6753a8"))
})
It("must truncate a long name", func() {
chain := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
Expect(len(chain)).To(Equal(maxChainLength))
Expect(chain).To(Equal("CNI-374f33fe84ab0ed84dcdebe3"))
})
It("must be predictable", func() {
chain1 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
chain2 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
Expect(len(chain1)).To(Equal(maxChainLength))
Expect(len(chain2)).To(Equal(maxChainLength))
Expect(chain1).To(Equal(chain2))
})
It("must change when a character changes", func() {
chain1 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
chain2 := FormatChainName("testalongnamethatdoesnotmakesense", "1235")
Expect(len(chain1)).To(Equal(maxChainLength))
Expect(len(chain2)).To(Equal(maxChainLength))
Expect(chain1).To(Equal("CNI-374f33fe84ab0ed84dcdebe3"))
Expect(chain1).NotTo(Equal(chain2))
})
It("must format a short name", func() {
chain := FormatChainName("test", "1234")
Expect(len(chain)).To(Equal(maxChainLength))
Expect(chain).To(Equal("CNI-2bbe0c48b91a7d1b8a6753a8"))
})
Describe("MustFormatChainNameWithPrefix", func() {
It("generates a chain name with a prefix", func() {
chain := MustFormatChainNameWithPrefix("test", "1234", "PREFIX-")
Expect(len(chain)).To(Equal(maxChainLength))
Expect(chain).To(Equal("CNI-PREFIX-2bbe0c48b91a7d1b8"))
})
It("must format a short name", func() {
chain := MustFormatChainNameWithPrefix("test", "1234", "PREFIX-")
Expect(len(chain)).To(Equal(maxChainLength))
Expect(chain).To(Equal("CNI-PREFIX-2bbe0c48b91a7d1b8"))
})
It("must truncate a long name", func() {
chain := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1234", "PREFIX-")
Expect(len(chain)).To(Equal(maxChainLength))
Expect(chain).To(Equal("CNI-PREFIX-374f33fe84ab0ed84"))
})
It("must be predictable", func() {
chain1 := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1234", "PREFIX-")
chain2 := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1234", "PREFIX-")
Expect(len(chain1)).To(Equal(maxChainLength))
Expect(len(chain2)).To(Equal(maxChainLength))
Expect(chain1).To(Equal(chain2))
})
It("must change when a character changes", func() {
chain1 := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1234", "PREFIX-")
chain2 := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1235", "PREFIX-")
Expect(len(chain1)).To(Equal(maxChainLength))
Expect(len(chain2)).To(Equal(maxChainLength))
Expect(chain1).To(Equal("CNI-PREFIX-374f33fe84ab0ed84"))
Expect(chain1).NotTo(Equal(chain2))
})
It("panics when prefix is too large", func() {
longPrefix := strings.Repeat("PREFIX-", 4)
Expect(func() {
MustFormatChainNameWithPrefix("test", "1234", longPrefix)
}).To(Panic())
})
It("must truncate a long name", func() {
chain := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
Expect(len(chain)).To(Equal(maxChainLength))
Expect(chain).To(Equal("CNI-374f33fe84ab0ed84dcdebe3"))
})
Describe("MustFormatHashWithPrefix", func() {
It("always returns a string with the given prefix", func() {
Expect(MustFormatHashWithPrefix(10, "AAA", "some string")).To(HavePrefix("AAA"))
Expect(MustFormatHashWithPrefix(10, "foo", "some string")).To(HavePrefix("foo"))
Expect(MustFormatHashWithPrefix(10, "bar", "some string")).To(HavePrefix("bar"))
})
It("always returns a string of the given length", func() {
Expect(MustFormatHashWithPrefix(10, "AAA", "some string")).To(HaveLen(10))
Expect(MustFormatHashWithPrefix(15, "AAA", "some string")).To(HaveLen(15))
Expect(MustFormatHashWithPrefix(5, "AAA", "some string")).To(HaveLen(5))
})
It("is deterministic", func() {
val1 := MustFormatHashWithPrefix(10, "AAA", "some string")
val2 := MustFormatHashWithPrefix(10, "AAA", "some string")
val3 := MustFormatHashWithPrefix(10, "AAA", "some string")
Expect(val1).To(Equal(val2))
Expect(val1).To(Equal(val3))
})
It("is (nearly) perfect (injective function)", func() {
hashes := map[string]int{}
for i := 0; i < 1000; i++ {
name := fmt.Sprintf("string %d", i)
hashes[MustFormatHashWithPrefix(8, "", name)]++
}
for key, count := range hashes {
Expect(count).To(Equal(1), "for key "+key+" got non-unique correspondence")
}
})
assertPanicWith := func(f func(), expectedErrorMessage string) {
defer func() {
Expect(recover()).To(Equal(expectedErrorMessage))
}()
f()
Fail("function should have panicked but did not")
}
It("panics when prefix is longer than the length", func() {
assertPanicWith(
func() { MustFormatHashWithPrefix(3, "AAA", "some string") },
"invalid length",
)
})
It("panics when length is not positive", func() {
assertPanicWith(
func() { MustFormatHashWithPrefix(0, "", "some string") },
"invalid length",
)
})
It("panics when length is larger than MaxLen", func() {
assertPanicWith(
func() { MustFormatHashWithPrefix(MaxHashLen+1, "", "some string") },
"invalid length",
)
})
It("must be predictable", func() {
chain1 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
chain2 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
Expect(len(chain1)).To(Equal(maxChainLength))
Expect(len(chain2)).To(Equal(maxChainLength))
Expect(chain1).To(Equal(chain2))
})
It("must change when a character changes", func() {
chain1 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
chain2 := FormatChainName("testalongnamethatdoesnotmakesense", "1235")
Expect(len(chain1)).To(Equal(maxChainLength))
Expect(len(chain2)).To(Equal(maxChainLength))
Expect(chain1).To(Equal("CNI-374f33fe84ab0ed84dcdebe3"))
Expect(chain1).NotTo(Equal(chain2))
})
})

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