Compare commits

..

97 Commits

Author SHA1 Message Date
ad10b6fa91 Merge pull request #484 from squeed/disable-ra
ptp, bridge: disable accept_ra on the host-side interface
2020-05-13 09:43:58 +02:00
219eb9e046 ptp, bridge: disable accept_ra on the host-side interface
The interface plugins should have absolute control over their addressing
and routing.

Signed-off-by: Casey Callendrello <cdc@redhat.com>
2020-05-12 15:54:23 +02:00
f7a2fc97e4 Merge pull request #460 from timyinshi/windowcontainer-new
modify the error url of windowscontainer
2020-04-29 16:58:32 +01:00
6957f6ca4e Merge pull request #479 from Nordix/issue-478
plugins/meta/sbr: Adjusted ipv6 address mask to /128
2020-04-29 17:15:54 +02:00
02bfece2e9 plugins/meta/sbr: Adjusted ipv6 address mask to /128
A /64 mask was used which routed an entire cidr based on source,
not only the bound address.

Fixes #478

Signed-off-by: Lars Ekman <lars.g.ekman@est.tech>
2020-04-28 16:38:35 +02:00
5af9ff493e Merge pull request #469 from AlbanBedel/portmap-hairpin-subnet
portmap: Apply the DNAT hairpin to the whole subnet
2020-04-22 17:22:14 +02:00
44d92c19de Merge pull request #475 from vboulineau/vboulineau/hostport_windows
win-bridge: add support for portMappings capability
2020-04-22 17:11:25 +02:00
5e0fbd8374 portmap: Apply the DNAT hairpin to the whole subnet
The DNAT hairpin rule only allow the container itself to access the
ports it is exposing thru the host IP. Other containers in the same
subnet might also want to access this service via the host IP, so
apply this rule to the whole subnet instead of just for the container.

This is particularly useful with setups using a reverse proxy for
https. With such a setup connections between containers (for ex.
oauth2) have to downgrade to http, or need complex dns setup to make
use of the internal IP of the reverse proxy. On the other hand going
thru the host IP is easy as that is probably what the service name
already resolve to.

Signed-off-by: Alban Bedel <albeu@free.fr>
--
v2: Fixed the tests
v3: Updated iptables rules documentation in README.md
v4: Fixed the network addresses in README.md to match iptables output
2020-04-17 16:27:57 +02:00
a78853f29f Support device id in host device plugin (#471)
* Add support for `deviceID` runtime config attribute

Signed-off-by: Adrian Chiris <adrianc@mellanox.com>
2020-04-15 10:45:14 -05:00
2d2583ee33 win-bridge: add support for portMappings capability
If the pluging receives portMappings in runtimeConfig, the pluing will add a NAT policy for each port mapping on the generated endpoints.
It enables HostPort usage on Windows with win-bridge.

Signed-off-by: Vincent Boulineau <vincent.boulineau@datadoghq.com>
2020-04-15 15:01:32 +02:00
f4332fec59 Merge pull request #468 from hongli-my/fix-port-state
check bridge's port state
2020-04-08 11:58:56 -04:00
ed16760739 Merge pull request #472 from ahenan/master
Reset the route flag before moving the rule
2020-04-08 17:55:59 +02:00
30776ff858 check bridge's port state
fix #463
link host veth pair to bridge, the Initial state
of port is BR_STATE_DISABLED and change to
BR_STATE_FORWARDING async.

Signed-off-by: honglichang <honglichang@tencent.com>
2020-04-08 14:15:50 +08:00
2a48d68937 Reset the route flag before moving the rule
Signed-off-by: ahenan <ahenan00@gmail.com>
2020-04-06 17:12:45 +02:00
117e30ff21 Merge pull request #458 from mars1024/remove/jujuerrors
replace juju/errors because of CNCF license scan
2020-04-01 18:01:32 +02:00
486ef96e6f [DO NOT REVIEW] vendor upate to remove useless dependencies
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2020-03-17 14:30:28 +08:00
8a0e3fe10e build error utility package to replace juju/errors
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2020-03-11 20:33:21 +08:00
ca419073e4 modify the error url of windowscontainer
Signed-off-by: root <timyinshi>
2020-03-05 09:38:33 +08:00
47a9fd80c8 Merge pull request #455 from booxter/master
Unlock OS thread after netns is restored
2020-03-04 11:34:44 -05:00
112288ecb2 Unlock OS thread after netns is restored
The current ns package code is very careful about not leaving the calling
thread with the overridden namespace set, for example when origns.Set() fails.
This is achieved by starting a new green thread, locking its OS thread, and
never unlocking it. Which makes golang runtime to scrap the OS thread backing
the green thread after the go routine exits.

While this works, it's probably not as optimal: stopping and starting a new OS
thread is expensive and may be avoided if we unlock the thread after resetting
network namespace to the original. On the other hand, if resetting fails, it's
better to leave the thread locked and die.

While it won't work in all cases, we can still make an attempt to reuse the OS
thread when resetting the namespace succeeds. This can be achieved by unlocking
the thread conditionally to the namespace reset success.

Signed-off-by: Ihar Hrachyshka <ihrachys@redhat.com>
2020-02-20 17:24:36 -05:00
32fc3ee9d3 Merge pull request #454 from dcbw/update-coreos-owners
owners: updates for maintainer changes
2020-02-19 17:29:54 +01:00
c7e2cf7602 owners: updates for maintainer changes
Add Michael Cambria per https://github.com/containernetworking/cni/pull/751
Remove Stefan Junker per personal request
Update Casey's email to @redhat.com

Signed-off-by: Dan Williams <dcbw@redhat.com>
2020-02-19 10:23:21 -06:00
5c512194eb Merge pull request #453 from Nordix/nfvi_virtio
Make host-device to work with virtio net device
2020-02-18 11:22:03 +01:00
a9b4e04bc4 Make host-device to work with virtio net device
In case pciBusID contains pci address of the virtio device,
then lookup the net directory under virtio<id> directory.

Issue: https://github.com/containernetworking/plugins/issues/320

Signed-off-by: Periyasamy Palanisamy <periyasamy.palanisamy@est.tech>
2020-02-11 18:05:37 +01:00
f5c3d1b1ba Merge pull request #443 from mars1024/bugfix/black_box_test
pkg/utils: sysctl package should use black-box testing
2020-01-29 17:26:04 +01:00
8bf6a7b362 Merge pull request #444 from mars1024/bugfix/ptp_redundant
ptp: remove some redundant lines
2020-01-29 10:23:06 -06:00
66e0aaf9c1 Merge pull request #445 from mars1024/bugfix/link_del
pkg/ip: use type cast instead of untrusty error message
2020-01-29 10:20:32 -06:00
45fd949465 ptp: remove some redundant lines
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2020-01-29 20:17:01 +08:00
2ff84a481e pkg/ip: use type cast instead of untrusty error message
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2020-01-29 20:03:15 +08:00
37207f05b4 pkg/utils: sysctl package should use black-box testing
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2020-01-27 21:09:04 +08:00
832f2699c2 Merge pull request #442 from ofiliz/loopback-fix
loopback: Fix ipv6 address checks
2020-01-27 09:38:52 +01:00
d1360b82ab loopback: Fix ipv6 address checks
Signed-off-by: Onur Filiz <ofiliz@users.noreply.github.com>
2020-01-23 17:33:47 -08:00
1f33fb729a Merge pull request #438 from tnqn/vlan-multi-del
Fix vlan plugin returning error when device is already removed
2020-01-22 17:18:18 +01:00
754e153b03 Merge pull request #437 from jcaamano/master
Improve support of sysctl name separators
2020-01-22 17:14:46 +01:00
0edf8a4208 Merge pull request #434 from carlosedp/vlanfilter
Fix for the case where kernel doesn't have CONFIG_BRIDGE_VLAN_FILTERING
2020-01-15 16:51:53 +00:00
db7e6cfabf Fix vlan plugin returning error when device is already removed
DEL can be called multiple times, a plugin should return no error if
the device is already removed, and other errors should be returned. It
was the opposite for vlan plugin. This PR fixes it.

Signed-off-by: Quan Tian <qtian@vmware.com>
2020-01-14 23:19:22 -08:00
963aaf86e6 Format with gofmt
Signed-off-by: Jaime Caamaño Ruiz <jcaamano@suse.com>
2020-01-13 19:44:40 +01:00
cd9d6b28da Use Replace instead of ReplaceAll
Signed-off-by: Jaime Caamaño Ruiz <jcaamano@suse.com>
2020-01-13 16:50:13 +01:00
0452c1dd10 Fix copyrights
Signed-off-by: Jaime Caamaño Ruiz <jcaamano@suse.com>
2020-01-13 14:56:58 +01:00
d671d29ad5 Improve support of sysctl name seprators
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 firt ocurrence.

Reference: http://man7.org/linux/man-pages/man5/sysctl.d.5.html

Signed-off-by: Jaime Caamaño Ruiz <jcaamano@suse.com>
2020-01-13 14:40:42 +01:00
cc6154603e Fix for the case for kernels without CONFIG_BRIDGE_VLAN_FILTERING
If the Linux kernel is not built with the parameter
CONFIG_BRIDGE_VLAN_FILTERING, passing vlanFiltering in
the Bridge struct returns an error creating the bridge interface.
This happens even when no parameter is set on Vlan in the CNI config.

This change fixes the case where no Vlan parameter is configured on
CNI config file so the flag doesn't need to be included in the struct.

Signed-off-by: Carlos de Paula <me@carlosedp.com>
2020-01-10 09:17:54 -03:00
62b36d2fbc Merge pull request #433 from ydcool/mips64le-support
add support for mips64le
2020-01-08 16:26:13 +00:00
e5fdd449dd Merge pull request #428 from weibeld/master
bridge: add missing cniVersion in README example
2020-01-08 16:24:48 +00:00
8db5e4d41b add support for mips64le
Signed-off-by: Dominic Yin <yindongchao@inspur.com>
2020-01-06 15:51:47 +08:00
ec8f6c99d0 Merge pull request #421 from aojea/portmapErrors2
Portmap doesn't fail if chain doesn't exist
2019-12-19 00:16:58 +08:00
7dea2a4c1b Add missing cniVersion in README example
Signed-off-by: Daniel Weibel <danielmweibel@gmail.com>
2019-12-18 19:08:37 +08:00
5a02c5bc61 bump go-iptables module to v0.4.5
bump the go-iptables module to v0.4.5 to avoid
concurrency issues with the portmap plugin and
errors related to iptables not able to hold the
lock.

Signed-off-by: Antonio Ojea <antonio.ojea.garcia@gmail.com>
2019-12-16 17:42:20 +01:00
bf8f171041 iptables: add idempotent functions
Add the following idempotent functions to iptables utils:

DeleteRule: idempotently delete an iptables rule
DeleteChain: idempotently delete an iptables chain
ClearChain: idempotently flush an iptables chain

Signed-off-by: Antonio Ojea <antonio.ojea.garcia@gmail.com>
2019-12-12 15:13:15 +01:00
3603738c6a portmap doesn't fail if chain doesn't exist
It turns out that the portmap plugin is not idempotent if its
executed in parallel.
The errors are caused due to a race of different instantiations
deleting the chains.
This patch does that the portmap plugin doesn't fail if the
errors are because the chain doesn't exist on teardown.

Signed-off-by: Antonio Ojea <antonio.ojea.garcia@gmail.com>
2019-12-12 09:03:06 +01:00
d8b1289098 fix portmap port forward flakiness
Use a Describe container for the It code block of the
portmap port forward integration test.

Signed-off-by: Antonio Ojea <antonio.ojea.garcia@gmail.com>
2019-12-12 09:03:06 +01:00
6551165853 Merge pull request #412 from containernetworking/new-maintainers
Add Bruce Ma and Piotr Skarmuk as owners
2019-12-04 10:11:31 -06:00
10a01b09ae Add Bruce Ma and Piotr Skarmuk as owners
Signed-off-by: Bryan Boreham <bryan@weave.works>
2019-11-16 11:45:44 +00:00
497560f35f Merge pull request #408 from tgross/idempotent_chain_creation
ensure iptables chain creation is idempotent
2019-11-13 17:20:45 +01:00
58dd90b996 ensure iptables chain creation is idempotent
Concurrent use of the `portmap` and `firewall` plugins can result in
errors during iptables chain creation:

- The `portmap` plugin has a time-of-check-time-of-use race where it
  checks for existence of the chain but the operation isn't atomic.
- The `firewall` plugin doesn't check for existing chains and just
  returns an error.

This commit makes both operations idempotent by creating the chain and
then discarding the error if it's caused by the chain already
existing. It also factors the chain creation out into `pkg/utils` as a
site for future refactoring work.

Signed-off-by: Tim Gross <tim@0x74696d.com>
2019-11-11 10:00:11 -05:00
d5efdfe1f6 Merge pull request #409 from squeed/fix-integ-tests
integration: fix ip address collision in integration tests
2019-11-11 14:07:52 +01:00
05f121a406 integration: fix ip address collision in integration tests
Signed-off-by: Casey Callendrello <cdc@redhat.com>
2019-11-11 13:36:21 +01:00
825fbd8a95 Merge pull request #405 from mars1024/feat/vlan_mtu_validation
vlan: add MTU validation to loadNetConf
2019-11-06 16:29:57 +00:00
1a30688da0 add some testcases about invalid MTUs
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-10-25 20:15:18 +08:00
bee8d6cf30 vlan: add MTU validation in loadNetConf
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-10-25 19:59:33 +08:00
a16232968d Merge pull request #400 from s1061123/fix/overwrite-ips
static: prioritize the input sources for IPs
2019-10-23 16:54:54 +01:00
1880421389 Merge pull request #401 from giuseppe/run-in-a-userns
testutils: newNS() works in a rootless user namespace
2019-10-23 16:29:59 +01:00
a2ed3d9a69 Merge pull request #403 from s1061123/dev/addgarp
Sending GratuitousArp in case of MAC address update
2019-10-23 16:24:58 +01:00
7bcaae263f Merge pull request #404 from mars1024/feat/mtu_validation
macvlan: add MTU validation to loadNetConf
2019-10-23 16:13:06 +01:00
e1f955d9bf macvlan: add MTU validation to loadNetConf
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-10-23 20:39:14 +08:00
2583a0b4ad Sending GratuitousArp in case of MAC address update
This change sends gratuitous ARP when MAC address is changed to
let other devices to know the MAC address update.

Signed-off-by: Tomofumi Hayashi <tohayash@redhat.com>
2019-10-23 15:17:38 +09:00
85083ea434 testutils: newNS() works in a rootless user namespace
When running in a user namespace created by an unprivileged user the
owner of /var/run will be reported as the unknown user (as defined in
/proc/sys/kernel/overflowuid) so any access to the directory will
fail.

If the XDG_RUNTIME_DIR environment variable is set, check whether the
current user is also the owner of /var/run.  If the owner is different
than the current user, use the $XDG_RUNTIME_DIR/netns directory.

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2019-10-19 12:04:53 +02:00
2290fc8d8a static: prioritize the input sources for IPs
This change introduce priorities for IPs input among CNI_ARGS,
'args' and runtimeConfig. Fix #399.

Signed-off-by: Tomofumi Hayashi <tohayash@redhat.com>
2019-10-17 13:36:49 +09:00
411d060b81 Merge pull request #389 from CallMeFoxie/bw-units
Use uint64 for Bandwidth plugin
2019-10-09 16:25:06 +01:00
5915b49b38 Merge pull request #394 from mars1024/bugfix/validate_vlanid
bridge: check vlan id when loading net conf
2019-10-09 17:23:47 +02:00
c25c62742b Merge pull request #396 from oshothebig/contributing-doc
contributing doc: revise test script name to run
2019-10-09 10:21:03 -05:00
b7ffa24326 vlan/bridge: fix some typo
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-10-08 11:57:30 +08:00
15894b36a0 Merge pull request #397 from oshothebig/install-cnitool
contributing doc: describe cnitool installation
2019-10-07 14:27:23 +02:00
77b51f0bc9 contributing doc: describe cnitool installation
cnitool must be installed before running tests because cnitool is
invoked during the tests

Signed-off-by: Sho SHIMIZU <sho.shimizu@gmail.com>
2019-10-07 17:42:44 +09:00
bd63528b0b contributing doc: revise test script name to run
test.sh doesn't exists now as it was separated into two OS-specific
scripts in 4e1f7802db.

Signed-off-by: Sho SHIMIZU <sho.shimizu@gmail.com>
2019-10-04 18:32:12 +09:00
cf187287af Update tests for uint64
Signed-off-by: Ashley Reese <ashley@victorianfox.com>
2019-10-03 16:55:41 +02:00
0dff883769 Use uint64 for Bandwidth plugin
Signed-off-by: Ashley Reese <ashley@victorianfox.com>
2019-10-03 16:05:27 +02:00
d0eeb27494 Merge pull request #390 from sipsma/firewall-fix
firewall: don't return error in DEL if prevResult is not found.
2019-10-02 10:38:47 -05:00
e70558cbe1 bridge: check vlan id when loading net conf
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-09-30 17:12:31 +08:00
0a1421a08c firewall: remove unused netns check from DEL method
Signed-off-by: Erik Sipsma <sipsma@amazon.com>
2019-09-25 20:38:02 +00:00
0f19aa2f8d Merge pull request #388 from sipsma/fix-ptpdns
ptp: only override DNS conf if DNS settings provided
2019-09-25 17:43:24 +02:00
e91889678b Merge pull request #391 from beautytiger/dev-190925
bugfix: defer after err check, or it may panic
2019-09-25 16:25:21 +01:00
8ec6bd6a42 bugfix: defer after err check, or it may panic
Signed-off-by: Guangming Wang <guangming.wang@daocloud.io>
2019-09-25 22:21:49 +08:00
fc7059c1ae firewall: don't return error in DEL if prevResult is not found.
The CNI spec states that for DEL implementations, "when CNI_NETNS and/or
prevResult are not provided, the plugin should clean up as many resources as
possible (e.g. releasing IPAM allocations) and return a successful response".
This change results in the firewall plugin conforming to the spec by not
returning an error whenever the del method is not provided a prevResult.

Signed-off-by: Erik Sipsma <sipsma@amazon.com>
2019-09-23 21:11:07 +00:00
a96c469e62 ptp: only override DNS conf if DNS settings provided
Previously, if an IPAM plugin provided DNS settings in the result to the PTP
plugin, those settings were always lost because the PTP plugin would always
provide its own DNS settings in the result even if the PTP plugin was not
configured with any DNS settings.

This was especially problematic when trying to use, for example, the host-local
IPAM plugin's support for retrieving DNS settings from a resolv.conf file on
the host. Before this change, those DNS settings were always lost when using the
PTP plugin and couldn't be specified as part of PTP instead because PTP does not
support parsing a resolv.conf file.

This change checks to see if any fields were actually set in the PTP plugin's
DNS settings and only overrides any previous DNS results from an IPAM plugin in
the case that settings actually were provided to PTP. In the case where no
DNS settings are provided to PTP, the DNS results of the IPAM plugin (if any)
are used instead.

Signed-off-by: Erik Sipsma <sipsma@amazon.com>
2019-09-18 21:09:22 +00:00
291ab6cc84 Merge pull request #386 from janisz/patch-1
Bump Go version
2019-09-18 17:12:43 +02:00
90125f40ba Bump Go version
Signed-off-by: Tomasz Janiszewski <janiszt@gmail.com>
2019-09-18 11:20:33 +02:00
23d5525ec3 Merge pull request #383 from mccv1r0/issue381
When prevResults are not supplied to loopback plugin, create results to return
2019-09-11 11:11:55 -05:00
fd42109a06 When prevResults are not returned to loopback plugin, create results to return based on
the lo interface and IP address assigned inside container.

Signed-off-by: Michael Cambria <mcambria@redhat.com>
2019-09-11 11:57:03 -04:00
4bb288193c Merge pull request #379 from xcelsion/fix-host-container-address-family-mismatch
Fix dual-stack support in meta/portmap
2019-09-11 17:16:36 +02:00
e8365e126d Fixed issue where hostIP address family was not checked against the containerIP address family. closes #378
Signed-off-by: Niels van Oosterom <xcelsion@users.noreply.github.com>
2019-09-06 15:23:00 +02:00
7e68430081 Merge pull request #377 from mars1024/bumpup/to/0.7.1
bump up libcni to v0.7.1
2019-08-28 10:55:43 -05:00
f81a529ebd Merge pull request #375 from smarkm/master
Fixes #342, cleanup netns after test suite
2019-08-28 17:55:36 +02:00
630a4d8db6 Merge pull request #374 from mars1024/feat/loopback_support_check
loopback support CNI CHECK and result cache
2019-08-28 10:53:43 -05:00
3d56f7504d loopback plugin support to pass previous result transpartently
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-08-23 22:56:22 +08:00
659a09f34e loopback support CNI CHECK
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-08-23 20:36:37 +08:00
b76ace9c64 bump up libcni to v0.7.1
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-08-23 20:24:16 +08:00
0d0dcfc02f Cleanup netns after test suit
Signed-off-by: smarkm <smark@freecoop.net>
2019-08-22 08:10:35 +08:00
77 changed files with 1780 additions and 2318 deletions

View File

@ -1,10 +1,11 @@
language: go
sudo: required
dist: trusty
dist: xenial
go:
- 1.11.x
- 1.12.x
- 1.13.x
env:
global:
@ -17,6 +18,7 @@ env:
- TARGET=arm64
- TARGET=ppc64le
- TARGET=s390x
- TARGET=mips64le
matrix:
fast_finish: true

View File

@ -72,10 +72,11 @@ 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.sh
./test_linux.sh
# to focus on a particular test suite
cd plugins/main/loopback

View File

@ -1,8 +1,10 @@
# Owners
This is the official list of the CNI network plugins owners:
- Bruce Ma <brucema19901024@gmail.com> (@mars1024)
- Bryan Boreham <bryan@weave.works> (@bboreham)
- Casey Callendrello <casey.callendrello@coreos.com> (@squeed)
- Casey Callendrello <cdc@redhat.com> (@squeed)
- Dan Williams <dcbw@redhat.com> (@dcbw)
- Gabe Rosenhouse <grosenhouse@pivotal.io> (@rosenhouse)
- Matt Dupre <matt@tigera.io> (@matthewdupre)
- Stefan Junker <stefan.junker@coreos.com> (@steveeJ)
- Michael Cambria <mcambria@redhat.com> (@mccv1r0)
- Piotr Skarmuk <piotr.skarmuk@gmail.com> (@jellonek)

11
go.mod
View File

@ -7,8 +7,8 @@ require (
github.com/Microsoft/hcsshim v0.8.6
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44
github.com/containernetworking/cni v0.7.0
github.com/coreos/go-iptables v0.4.2
github.com/containernetworking/cni v0.7.1
github.com/coreos/go-iptables v0.4.5
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c
github.com/d2g/dhcp4client v1.0.0
@ -17,10 +17,6 @@ require (
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c
github.com/golang/protobuf v1.3.1 // indirect
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56
github.com/juju/errors v0.0.0-20180806074554-22422dad46e1
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 // indirect
github.com/juju/testing v0.0.0-20190613124551-e81189438503 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/mattn/go-shellwords v1.0.3
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b
github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a
@ -33,8 +29,5 @@ require (
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 // indirect
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f
gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 // indirect
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce // indirect
gopkg.in/yaml.v2 v2.2.2 // indirect
)

26
go.sum
View File

@ -6,10 +6,10 @@ github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae h1:AMzIhMUq
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0=
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44 h1:y853v6rXx+zefEcjET3JuKAqvhj+FKflQijjeaSv2iA=
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/containernetworking/cni v0.7.0 h1:1Qy7EwdC08mx5wUB0DpjCuBrk6e/uXg9yI9TvAvgox8=
github.com/containernetworking/cni v0.7.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
github.com/coreos/go-iptables v0.4.2 h1:KH0EwId05JwWIfb96gWvkiT2cbuOu8ygqUaB+yPAwIg=
github.com/coreos/go-iptables v0.4.2/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
github.com/containernetworking/cni v0.7.1 h1:fE3r16wpSEyaqY4Z4oFrLMmIGfBYIKpPrHK31EJ9FzE=
github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
github.com/coreos/go-iptables v0.4.5 h1:DpHb9vJrZQEFMcVLFKAAGMUVX0XoRC0ptCthinRYm38=
github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7 h1:u9SHYsPQNyt5tgDm3YN7+9dYrpK96E5wFilTFWIDZOM=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c h1:Xo2rK1pzOm0jO6abTPIQwbAmqBIOj132otexc1mmzFc=
@ -28,17 +28,6 @@ github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56 h1:742eGXur0715JMq73aD95/FU0XpVKXqNuTnEfXsLOYQ=
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=
github.com/juju/errors v0.0.0-20180806074554-22422dad46e1 h1:wnhMXidtb70kDZCeLt/EfsVtkXS5c8zLnE9y/6DIRAU=
github.com/juju/errors v0.0.0-20180806074554-22422dad46e1/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 h1:UUHMLvzt/31azWTN/ifGWef4WUqvXk0iRqdhdy/2uzI=
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
github.com/juju/testing v0.0.0-20190613124551-e81189438503 h1:ZUgTbk8oHgP0jpMieifGC9Lv47mHn8Pb3mFX3/Ew4iY=
github.com/juju/testing v0.0.0-20190613124551-e81189438503/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
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/mattn/go-shellwords v1.0.3 h1:K/VxK7SZ+cvuPgFSLKi5QPI9Vr/ipOf4C1gN+ntueUk=
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b h1:Ey6yH0acn50T/v6CB75bGP4EMJqnv9WvnjN7oZaj+xE=
@ -66,12 +55,5 @@ golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 h1:OAj3g0cR6Dx/R07QgQe8wkA9RNjB2u4i700xBkIT4e0=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU=
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -149,12 +149,12 @@ var _ = Describe("Basic PTP using cnitool", func() {
})
Measure("limits traffic only on the restricted bandwith veth device", func(b Benchmarker) {
ipRegexp := regexp.MustCompile("10\\.11\\.2\\.\\d{1,3}")
ipRegexp := regexp.MustCompile("10\\.1[12]\\.2\\.\\d{1,3}")
By(fmt.Sprintf("adding %s to %s\n\n", "chained-bridge-bandwidth", contNS1.ShortName()))
chainedBridgeBandwidthEnv.runInNS(hostNS, cnitoolBin, "add", "network-chain-test", contNS1.LongName())
chainedBridgeIP := ipRegexp.FindString(chainedBridgeBandwidthEnv.runInNS(contNS1, "ip", "addr"))
Expect(chainedBridgeIP).To(ContainSubstring("10.11.2."))
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())

View File

@ -4,12 +4,12 @@
"plugins": [
{
"type": "bridge",
"bridge": "test-bridge-0",
"bridge": "test-bridge-1",
"isDefaultGateway": true,
"ipam": {
"type": "host-local",
"subnet": "10.11.2.0/24",
"dataDir": "/tmp/foo"
"subnet": "10.12.2.0/24",
"dataDir": "/tmp/bar"
}
},
{

37
pkg/errors/errors.go Normal file
View File

@ -0,0 +1,37 @@
// 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)
}

96
pkg/errors/errors_test.go Normal file
View File

@ -0,0 +1,96 @@
// 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

@ -21,9 +21,10 @@ import (
"github.com/Microsoft/hcsshim"
"github.com/Microsoft/hcsshim/hcn"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/juju/errors"
"github.com/containernetworking/plugins/pkg/errors"
)
const (
@ -64,14 +65,14 @@ func GenerateHnsEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcsshim.HNSEndpoint
// 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, "Attempt to get endpoint \"%v\" failed", epInfo.EndpointName)
return nil, errors.Annotatef(err, "failed to get endpoint %q", epInfo.EndpointName)
}
if hnsEndpoint != nil {
if hnsEndpoint.VirtualNetwork != epInfo.NetworkId {
_, err = hnsEndpoint.Delete()
if err != nil {
return nil, errors.Annotatef(err, "Failed to delete endpoint %v", epInfo.EndpointName)
return nil, errors.Annotatef(err, "failed to delete endpoint %s", epInfo.EndpointName)
}
hnsEndpoint = nil
}
@ -98,7 +99,7 @@ func GenerateHcnEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcn.HostComputeEndp
// 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, "Attempt to get endpoint \"%v\" failed", epInfo.EndpointName)
return nil, errors.Annotatef(err, "failed to get endpoint %q", epInfo.EndpointName)
}
if hcnEndpoint != nil {
@ -108,12 +109,10 @@ func GenerateHcnEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcn.HostComputeEndp
if !strings.EqualFold(hcnEndpoint.HostComputeNetwork, epInfo.NetworkId) {
err = hcnEndpoint.Delete()
if err != nil {
return nil, errors.Annotatef(err, "Failed to delete endpoint %v", epInfo.EndpointName)
hcnEndpoint = nil
return nil, errors.Annotatef(err, "failed to delete endpoint %s", epInfo.EndpointName)
}
} else {
return nil, fmt.Errorf("Endpoint \"%v\" already exits", epInfo.EndpointName)
return nil, fmt.Errorf("endpoint %q already exits", epInfo.EndpointName)
}
}
@ -270,7 +269,7 @@ func AddHcnEndpoint(epName string, expectedNetworkId string, namespace string,
if err != nil {
return nil, errors.Annotatef(err, "failed to Remove Endpoint after AddNamespaceEndpoint failure")
}
return nil, errors.Annotatef(err, "Failed to Add endpoint to namespace")
return nil, errors.Annotate(err, "failed to Add endpoint to namespace")
}
return hcnEndpoint, nil

View File

@ -20,10 +20,11 @@ import (
"fmt"
"net"
"strings"
"github.com/Microsoft/hcsshim/hcn"
"github.com/buger/jsonparser"
"github.com/containernetworking/cni/pkg/types"
"strings"
)
// NetConf is the CNI spec
@ -46,8 +47,16 @@ type RuntimeDNS struct {
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"`
}
type RuntimeConfig struct {
DNS RuntimeDNS `json:"dns"`
DNS RuntimeDNS `json:"dns"`
PortMaps []PortMapEntry `json:"portMappings,omitempty"`
}
type policy struct {
@ -207,3 +216,21 @@ func (n *NetConf) ApplyDefaultPAPolicy(paAddress string) {
Value: []byte(`{"Type": "PA", "PA": "` + paAddress + `"}`),
})
}
// ApplyPortMappingPolicy is used to configure HostPort<>ContainerPort mapping in HNS
func (n *NetConf) ApplyPortMappingPolicy(portMappings []PortMapEntry) {
if portMappings == nil {
return
}
if n.Policies == nil {
n.Policies = make([]policy, 0)
}
for _, portMapping := range portMappings {
n.Policies = append(n.Policies, policy{
Name: "EndpointPolicy",
Value: []byte(fmt.Sprintf(`{"Type": "NAT", "InternalPort": %d, "ExternalPort": %d, "Protocol": "%s"}`, portMapping.ContainerPort, portMapping.HostPort, portMapping.Protocol)),
})
}
}

View File

@ -128,6 +128,53 @@ var _ = Describe("HNS NetConf", func() {
})
})
Describe("ApplyPortMappingPolicy", func() {
Context("when portMappings not activated", func() {
It("does nothing", func() {
n := NetConf{}
n.ApplyPortMappingPolicy(nil)
Expect(n.Policies).Should(BeNil())
n.ApplyPortMappingPolicy([]PortMapEntry{})
Expect(n.Policies).Should(HaveLen(0))
})
})
Context("when portMappings is activated", func() {
It("creates NAT policies", func() {
n := NetConf{}
n.ApplyPortMappingPolicy([]PortMapEntry{
{
ContainerPort: 80,
HostPort: 8080,
Protocol: "TCP",
HostIP: "ignored",
},
})
Expect(n.Policies).Should(HaveLen(1))
policy := n.Policies[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"))
Expect(value).Should(HaveKey("InternalPort"))
Expect(value["InternalPort"]).Should(Equal(float64(80)))
Expect(value).Should(HaveKey("ExternalPort"))
Expect(value["ExternalPort"]).Should(Equal(float64(8080)))
Expect(value).Should(HaveKey("Protocol"))
Expect(value["Protocol"]).Should(Equal("TCP"))
})
})
})
Describe("MarshalPolicies", func() {
Context("when not set by user", func() {
It("sets it by adding a policy", func() {

View File

@ -21,10 +21,12 @@ import (
"net"
"os"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/utils/hwaddr"
"github.com/safchain/ethtool"
"github.com/vishvananda/netlink"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/utils/hwaddr"
"github.com/containernetworking/plugins/pkg/utils/sysctl"
)
var (
@ -158,6 +160,9 @@ func SetupVethWithName(contVethName, hostVethName string, mtu int, hostNS ns.Net
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 {
@ -178,7 +183,7 @@ func SetupVeth(contVethName string, mtu int, hostNS ns.NetNS) (net.Interface, ne
func DelLinkByName(ifName string) error {
iface, err := netlink.LinkByName(ifName)
if err != nil {
if err.Error() == "Link not found" {
if _, ok := err.(netlink.LinkNotFoundError); ok {
return ErrLinkNotFound
}
return fmt.Errorf("failed to lookup %q: %v", ifName, err)
@ -195,7 +200,7 @@ func DelLinkByName(ifName string) error {
func DelLinkByNameAddr(ifName string) ([]*net.IPNet, error) {
iface, err := netlink.LinkByName(ifName)
if err != nil {
if err != nil && err.Error() == "Link not found" {
if _, ok := err.(netlink.LinkNotFoundError); ok {
return nil, ErrLinkNotFound
}
return nil, fmt.Errorf("failed to lookup %q: %v", ifName, err)

View File

@ -178,7 +178,16 @@ func (ns *netNS) Do(toRun func(NetNS) error) error {
if err = ns.Set(); err != nil {
return fmt.Errorf("error switching to ns %v: %v", ns.file.Name(), err)
}
defer threadNS.Set() // switch back
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)
}
@ -193,6 +202,10 @@ func (ns *netNS) Do(toRun func(NetNS) error) error {
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()

60
pkg/testutils/dns.go Normal file
View File

@ -0,0 +1,60 @@
// 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

@ -22,17 +22,36 @@ import (
"runtime"
"strings"
"sync"
"syscall"
"github.com/containernetworking/plugins/pkg/ns"
"golang.org/x/sys/unix"
)
const nsRunDir = "/var/run/netns"
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 {
@ -135,7 +154,7 @@ func NewNS() (ns.NetNS, error) {
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, nsRunDir) {
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)
}

121
pkg/utils/iptables.go Normal file
View File

@ -0,0 +1,121 @@
// 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

@ -0,0 +1,97 @@
// 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

@ -35,7 +35,7 @@ func Sysctl(name string, params ...string) (string, error) {
}
func getSysctl(name string) (string, error) {
fullName := filepath.Join("/proc/sys", strings.Replace(name, ".", "/", -1))
fullName := filepath.Join("/proc/sys", toNormalName(name))
fullName = filepath.Clean(fullName)
data, err := ioutil.ReadFile(fullName)
if err != nil {
@ -46,7 +46,7 @@ func getSysctl(name string) (string, error) {
}
func setSysctl(name, value string) (string, error) {
fullName := filepath.Join("/proc/sys", strings.Replace(name, ".", "/", -1))
fullName := filepath.Join("/proc/sys", toNormalName(name))
fullName = filepath.Clean(fullName)
if err := ioutil.WriteFile(fullName, []byte(value), 0644); err != nil {
return "", err
@ -54,3 +54,27 @@ func setSysctl(name, value string) (string, error) {
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

@ -0,0 +1,114 @@
// 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

@ -0,0 +1,27 @@
// 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

@ -65,12 +65,11 @@ options four
func parse(contents string) (*types.DNS, error) {
f, err := ioutil.TempFile("", "host_local_resolv")
defer f.Close()
defer os.Remove(f.Name())
if err != nil {
return nil, err
}
defer f.Close()
defer os.Remove(f.Name())
if _, err := f.WriteString(contents); err != nil {
return nil, err

View File

@ -60,3 +60,9 @@ The plugin also support following [capability argument](https://github.com/conta
The following [args conventions](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md#args-in-network-config) are supported:
* `ips` (array of strings): A list of custom IPs to attempt to allocate, with prefix (e.g. '10.10.0.1/24')
Notice: If some of above are used at same time, only one will work according to the priorities below
1. [capability argument](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md)
1. [args conventions](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md#args-in-network-config)
1. [CNI_ARGS](https://github.com/containernetworking/cni/blob/master/SPEC.md#parameters)

View File

@ -145,11 +145,44 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
return nil, "", err
}
if len(n.RuntimeConfig.IPs) != 0 {
// args IP overwrites IP, so clear IPAM Config
n.IPAM.Addresses = make([]Address, 0, len(n.RuntimeConfig.IPs))
for _, addr := range n.RuntimeConfig.IPs {
n.IPAM.Addresses = append(n.IPAM.Addresses, Address{AddressStr: addr})
// load IP from CNI_ARGS
if envArgs != "" {
e := IPAMEnvArgs{}
err := types.LoadArgs(envArgs, &e)
if err != nil {
return nil, "", err
}
if e.IP != "" {
for _, item := range strings.Split(string(e.IP), ",") {
ipstr := strings.TrimSpace(item)
ip, subnet, err := net.ParseCIDR(ipstr)
if err != nil {
return nil, "", fmt.Errorf("invalid CIDR %s: %s", ipstr, err)
}
addr := Address{
Address: net.IPNet{IP: ip, Mask: subnet.Mask},
AddressStr: ipstr,
}
n.IPAM.Addresses = append(n.IPAM.Addresses, addr)
}
}
if e.GATEWAY != "" {
for _, item := range strings.Split(string(e.GATEWAY), ",") {
gwip := net.ParseIP(strings.TrimSpace(item))
if gwip == nil {
return nil, "", fmt.Errorf("invalid gateway address: %s", item)
}
for i := range n.IPAM.Addresses {
if n.IPAM.Addresses[i].Address.Contains(gwip) {
n.IPAM.Addresses[i].Gateway = gwip
}
}
}
}
}
@ -162,6 +195,15 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
}
}
// import address from runtimeConfig
if len(n.RuntimeConfig.IPs) != 0 {
// runtimeConfig IP overwrites IP, so clear IPAM Config
n.IPAM.Addresses = make([]Address, 0, len(n.RuntimeConfig.IPs))
for _, addr := range n.RuntimeConfig.IPs {
n.IPAM.Addresses = append(n.IPAM.Addresses, Address{AddressStr: addr})
}
}
if n.IPAM == nil {
return nil, "", fmt.Errorf("IPAM config missing 'ipam' key")
}
@ -191,50 +233,6 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
}
}
if envArgs != "" {
e := IPAMEnvArgs{}
err := types.LoadArgs(envArgs, &e)
if err != nil {
return nil, "", err
}
if e.IP != "" {
for _, item := range strings.Split(string(e.IP), ",") {
ipstr := strings.TrimSpace(item)
ip, subnet, err := net.ParseCIDR(ipstr)
if err != nil {
return nil, "", fmt.Errorf("invalid CIDR %s: %s", ipstr, err)
}
addr := Address{Address: net.IPNet{IP: ip, Mask: subnet.Mask}}
if addr.Address.IP.To4() != nil {
addr.Version = "4"
numV4++
} else {
addr.Version = "6"
numV6++
}
n.IPAM.Addresses = append(n.IPAM.Addresses, addr)
}
}
if e.GATEWAY != "" {
for _, item := range strings.Split(string(e.GATEWAY), ",") {
gwip := net.ParseIP(strings.TrimSpace(item))
if gwip == nil {
return nil, "", fmt.Errorf("invalid gateway address: %s", item)
}
for i := range n.IPAM.Addresses {
if n.IPAM.Addresses[i].Address.Contains(gwip) {
n.IPAM.Addresses[i].Gateway = gwip
}
}
}
}
}
// CNI spec 0.2.0 and below supported only one v4 and v6 address
if numV4 > 1 || numV6 > 1 {
for _, v := range types020.SupportedVersions {

View File

@ -404,6 +404,82 @@ var _ = Describe("static Operations", func() {
})
Expect(err).NotTo(HaveOccurred())
})
It("allocates and releases multiple addresses with ADD/DEL, from RuntimeConfig/ARGS/CNI_ARGS", func() {
const ifname string = "eth0"
const nspath string = "/some/where"
conf := `{
"cniVersion": "0.3.1",
"name": "mynet",
"type": "ipvlan",
"master": "foo0",
"capabilities": {"ips": true},
"ipam": {
"type": "static",
"routes": [
{ "dst": "0.0.0.0/0", "gw": "10.10.0.254" },
{ "dst": "3ffe:ffff:0:01ff::1/64",
"gw": "3ffe:ffff:0::1" } ],
"dns": {
"nameservers" : ["8.8.8.8"],
"domain": "example.com",
"search": [ "example.com" ]
}
},
"RuntimeConfig": {
"ips" : ["10.10.0.1/24", "3ffe:ffff:0:01ff::1/64"]
},
"args": {
"cni": {
"ips" : ["10.10.0.2/24", "3ffe:ffff:0:01ff::2/64"]
}
}
}`
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: nspath,
IfName: ifname,
StdinData: []byte(conf),
Args: "IP=10.10.0.3/24,11.11.0.3/24;GATEWAY=10.10.0.254",
}
// Allocate the IP
r, raw, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
result, err := current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
// only addresses in runtimeConfig configured because of its priorities
Expect(*result.IPs[0]).To(Equal(
current.IPConfig{
Version: "4",
Address: mustCIDR("10.10.0.1/24"),
}))
Expect(*result.IPs[1]).To(Equal(
current.IPConfig{
Version: "6",
Address: mustCIDR("3ffe:ffff:0:01ff::1/64"),
},
))
Expect(len(result.IPs)).To(Equal(2))
Expect(result.Routes).To(Equal([]*types.Route{
{Dst: mustCIDR("0.0.0.0/0"), GW: net.ParseIP("10.10.0.254")},
{Dst: mustCIDR("3ffe:ffff:0:01ff::1/64"), GW: net.ParseIP("3ffe:ffff:0::1")},
}))
// Release the IP
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
})
})
func mustCIDR(s string) net.IPNet {

View File

@ -14,17 +14,18 @@ If the bridge is missing, the plugin will create one on first use and, if gatewa
## Example configuration
```
{
"name": "mynet",
"type": "bridge",
"bridge": "mynet0",
"isDefaultGateway": true,
"forceAddress": false,
"ipMasq": true,
"hairpinMode": true,
"ipam": {
"type": "host-local",
"subnet": "10.10.0.0/16"
}
"cniVersion": "0.3.1",
"name": "mynet",
"type": "bridge",
"bridge": "mynet0",
"isDefaultGateway": true,
"forceAddress": false,
"ipMasq": true,
"hairpinMode": true,
"ipam": {
"type": "host-local",
"subnet": "10.10.0.0/16"
}
}
```
@ -32,10 +33,10 @@ If the bridge is missing, the plugin will create one on first use and, if gatewa
```
{
"cniVersion": "0.3.1",
"name": "mynet",
"type": "bridge",
"bridge": "mynet0",
"ipam": {}
"name": "mynet",
"type": "bridge",
"bridge": "mynet0",
"ipam": {}
}
```
@ -56,4 +57,4 @@ If the bridge is missing, the plugin will create one on first use and, if gatewa
*Note:* The VLAN parameter configures the VLAN tag on the host end of the veth and also enables the vlan_filtering feature on the bridge interface.
*Note:* To configure uplink for L2 network you need to allow the vlan on the uplink interface by using the following command ``` bridge vlan add vid VLAN_ID dev DEV```.
*Note:* To configure uplink for L2 network you need to allow the vlan on the uplink interface by using the following command ``` bridge vlan add vid VLAN_ID dev DEV```.

View File

@ -22,6 +22,7 @@ import (
"net"
"runtime"
"syscall"
"time"
"github.com/j-keck/arping"
"github.com/vishvananda/netlink"
@ -35,6 +36,7 @@ import (
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/utils"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
"github.com/containernetworking/plugins/pkg/utils/sysctl"
)
// For testcases to force an error after IPAM has been performed
@ -75,6 +77,9 @@ func loadNetConf(bytes []byte) (*NetConf, string, error) {
if err := json.Unmarshal(bytes, n); err != nil {
return nil, "", fmt.Errorf("failed to load netconf: %v", err)
}
if n.Vlan < 0 || n.Vlan > 4094 {
return nil, "", fmt.Errorf("invalid VLAN ID %d (must be between 0 and 4094)", n.Vlan)
}
return n, n.CNIVersion, nil
}
@ -221,7 +226,9 @@ func ensureBridge(brName string, mtu int, promiscMode, vlanFiltering bool) (*net
// default packet limit
TxQLen: -1,
},
VlanFiltering: &vlanFiltering,
}
if vlanFiltering {
br.VlanFiltering = &vlanFiltering
}
err := netlink.LinkAdd(br)
@ -242,6 +249,9 @@ func ensureBridge(brName string, mtu int, promiscMode, vlanFiltering bool) (*net
return nil, err
}
// we want to own the routes for this interface
_, _ = sysctl.Sysctl(fmt.Sprintf("net/ipv6/conf/%s/accept_ra", brName), "0")
if err := netlink.LinkSetUp(br); err != nil {
return nil, err
}
@ -439,11 +449,6 @@ func cmdAdd(args *skel.CmdArgs) error {
// Configure the container hardware address and IP address(es)
if err := netns.Do(func(_ ns.NetNS) error {
contVeth, err := net.InterfaceByName(args.IfName)
if err != nil {
return err
}
// Disable IPv6 DAD just in case hairpin mode is enabled on the
// bridge. Hairpin mode causes echos of neighbor solicitation
// packets, which causes DAD failures.
@ -460,8 +465,36 @@ func cmdAdd(args *skel.CmdArgs) error {
if err := ipam.ConfigureIface(args.IfName, result); err != nil {
return err
}
return nil
}); err != nil {
return err
}
// check bridge port state
retries := []int{0, 50, 500, 1000, 1000}
for idx, sleep := range retries {
time.Sleep(time.Duration(sleep) * time.Millisecond)
hostVeth, err := netlink.LinkByName(hostInterface.Name)
if err != nil {
return err
}
if hostVeth.Attrs().OperState == netlink.OperUp {
break
}
if idx == len(retries)-1 {
return fmt.Errorf("bridge port in error state: %s", hostVeth.Attrs().OperState)
}
}
// Send a gratuitous arp
if err := netns.Do(func(_ ns.NetNS) error {
contVeth, err := net.InterfaceByName(args.IfName)
if err != nil {
return err
}
// Send a gratuitous arp
for _, ipc := range result.IPs {
if ipc.Version == "4" {
_ = arping.GratuitousArpOverIface(ipc.Address.IP, *contVeth)

View File

@ -27,7 +27,7 @@ import (
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/020"
types020 "github.com/containernetworking/cni/pkg/types/020"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils"
@ -1125,6 +1125,7 @@ var _ = Describe("bridge Operations", func() {
AfterEach(func() {
Expect(os.RemoveAll(dataDir)).To(Succeed())
Expect(originalNS.Close()).To(Succeed())
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
})
It("creates a bridge", func() {
@ -1644,4 +1645,48 @@ var _ = Describe("bridge Operations", func() {
})
Expect(err).NotTo(HaveOccurred())
})
It("check vlan id when loading net conf", func() {
tests := []struct {
tc testCase
err error
}{
{
tc: testCase{
cniVersion: "0.4.0",
},
err: nil,
},
{
tc: testCase{
cniVersion: "0.4.0",
vlan: 0,
},
err: nil,
},
{
tc: testCase{
cniVersion: "0.4.0",
vlan: -100,
},
err: fmt.Errorf("invalid VLAN ID -100 (must be between 0 and 4094)"),
},
{
tc: testCase{
cniVersion: "0.4.0",
vlan: 5000,
},
err: fmt.Errorf("invalid VLAN ID 5000 (must be between 0 and 4094)"),
},
}
for _, test := range tests {
_, _, err := loadNetConf([]byte(test.tc.netConfJSON("")))
if test.err == nil {
Expect(err).To(BeNil())
} else {
Expect(err).To(Equal(test.err))
}
}
})
})

View File

@ -17,6 +17,9 @@ The device can be specified with any one of four properties:
For this plugin, `CNI_IFNAME` will be ignored. Upon DEL, the device will be moved back.
The plugin also supports the following [capability argument](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md):
* `deviceID`: A PCI address of the network device, e.g `0000:00:1f.6`
## Example configuration
A sample configuration with `device` property looks like:
@ -38,3 +41,26 @@ A sample configuration with `pciBusID` property looks like:
"pciBusID": "0000:3d:00.1"
}
```
A sample configuration utilizing `deviceID` runtime configuration looks like:
1. From operator perspective:
```json
{
"cniVersion": "0.3.1",
"type": "host-device",
"capabilities": {
"deviceID": true
}
}
```
2. From plugin perspective:
```json
{
"cniVersion": "0.3.1",
"type": "host-device",
"runtimeConfig": {
"deviceID": "0000:3d:00.1"
}
}
```

View File

@ -46,10 +46,13 @@ const (
//NetConf for host-device config, look the README to learn how to use those parameters
type NetConf struct {
types.NetConf
Device string `json:"device"` // Device-Name, something like eth0 or can0 etc.
HWAddr string `json:"hwaddr"` // MAC Address of target network interface
KernelPath string `json:"kernelpath"` // Kernelpath of the device
PCIAddr string `json:"pciBusID"` // PCI Address of target network device
Device string `json:"device"` // Device-Name, something like eth0 or can0 etc.
HWAddr string `json:"hwaddr"` // MAC Address of target network interface
KernelPath string `json:"kernelpath"` // Kernelpath of the device
PCIAddr string `json:"pciBusID"` // PCI Address of target network device
RuntimeConfig struct {
DeviceID string `json:"deviceID,omitempty"`
} `json:"runtimeConfig,omitempty"`
}
func init() {
@ -64,9 +67,16 @@ func loadConf(bytes []byte) (*NetConf, error) {
if err := json.Unmarshal(bytes, n); err != nil {
return nil, fmt.Errorf("failed to load netconf: %v", err)
}
if n.RuntimeConfig.DeviceID != "" {
// Override PCI device with the standardized DeviceID provided in Runtime Config.
n.PCIAddr = n.RuntimeConfig.DeviceID
}
if n.Device == "" && n.HWAddr == "" && n.KernelPath == "" && n.PCIAddr == "" {
return nil, fmt.Errorf(`specify either "device", "hwaddr", "kernelpath" or "pciBusID"`)
}
return n, nil
}
@ -296,7 +306,12 @@ func getLink(devname, hwaddr, kernelpath, pciaddr string) (netlink.Link, error)
} else if len(pciaddr) > 0 {
netDir := filepath.Join(sysBusPCI, pciaddr, "net")
if _, err := os.Lstat(netDir); err != nil {
return nil, fmt.Errorf("no net directory under pci device %s: %q", pciaddr, err)
virtioNetDir := filepath.Join(sysBusPCI, pciaddr, "virtio*", "net")
matches, err := filepath.Glob(virtioNetDir)
if matches == nil || err != nil {
return nil, fmt.Errorf("no net directory under pci device %s", pciaddr)
}
netDir = matches[0]
}
fInfo, err := ioutil.ReadDir(netDir)
if err != nil {

View File

@ -233,7 +233,8 @@ var _ = Describe("base functionality", func() {
})
AfterEach(func() {
originalNS.Close()
Expect(originalNS.Close()).To(Succeed())
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
})
It("Works with a valid config without IPAM", func() {

View File

@ -297,6 +297,7 @@ var _ = Describe("ipvlan Operations", func() {
AfterEach(func() {
Expect(originalNS.Close()).To(Succeed())
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
})
It("creates an ipvlan link in a non-default namespace", func() {

View File

@ -15,9 +15,15 @@
package main
import (
"encoding/json"
"errors"
"fmt"
"net"
"github.com/vishvananda/netlink"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/cni/pkg/version"
@ -25,9 +31,34 @@ import (
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
func parseNetConf(bytes []byte) (*types.NetConf, error) {
conf := &types.NetConf{}
if err := json.Unmarshal(bytes, conf); err != nil {
return nil, fmt.Errorf("failed to parse network config: %v", err)
}
if conf.RawPrevResult != nil {
if err := version.ParsePrevResult(conf); err != nil {
return nil, fmt.Errorf("failed to parse prevResult: %v", err)
}
if _, err := current.NewResultFromResult(conf.PrevResult); err != nil {
return nil, fmt.Errorf("failed to convert result to current version: %v", err)
}
}
return conf, nil
}
func cmdAdd(args *skel.CmdArgs) error {
conf, err := parseNetConf(args.StdinData)
if err != nil {
return err
}
var v4Addr, v6Addr *net.IPNet
args.IfName = "lo" // ignore config, this only works for loopback
err := ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
link, err := netlink.LinkByName(args.IfName)
if err != nil {
return err // not tested
@ -38,14 +69,69 @@ func cmdAdd(args *skel.CmdArgs) error {
return err // not tested
}
v4Addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
if err != nil {
return err // not tested
}
if len(v4Addrs) != 0 {
v4Addr = v4Addrs[0].IPNet
// sanity check that this is a loopback address
for _, addr := range v4Addrs {
if !addr.IP.IsLoopback() {
return fmt.Errorf("loopback interface found with non-loopback address %q", addr.IP)
}
}
}
v6Addrs, err := netlink.AddrList(link, netlink.FAMILY_V6)
if err != nil {
return err // not tested
}
if len(v6Addrs) != 0 {
v6Addr = v6Addrs[0].IPNet
// sanity check that this is a loopback address
for _, addr := range v6Addrs {
if !addr.IP.IsLoopback() {
return fmt.Errorf("loopback interface found with non-loopback address %q", addr.IP)
}
}
}
return nil
})
if err != nil {
return err // not tested
}
result := current.Result{}
return result.Print()
var result types.Result
if conf.PrevResult != nil {
// If loopback has previous result which passes from previous CNI plugin,
// loopback should pass it transparently
result = conf.PrevResult
} else {
loopbackInterface := &current.Interface{Name: args.IfName, Mac: "00:00:00:00:00:00", Sandbox: args.Netns}
r := &current.Result{CNIVersion: conf.CNIVersion, Interfaces: []*current.Interface{loopbackInterface}}
if v4Addr != nil {
r.IPs = append(r.IPs, &current.IPConfig{
Version: "4",
Interface: current.Int(0),
Address: *v4Addr,
})
}
if v6Addr != nil {
r.IPs = append(r.IPs, &current.IPConfig{
Version: "6",
Interface: current.Int(0),
Address: *v6Addr,
})
}
result = r
}
return types.PrintResult(result, conf.CNIVersion)
}
func cmdDel(args *skel.CmdArgs) error {
@ -78,6 +164,18 @@ func main() {
}
func cmdCheck(args *skel.CmdArgs) error {
// TODO: implement
return nil
args.IfName = "lo" // ignore config, this only works for loopback
return ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
link, err := netlink.LinkByName(args.IfName)
if err != nil {
return err
}
if link.Attrs().Flags&net.FlagUp != net.FlagUp {
return errors.New("loopback interface is down")
}
return nil
})
}

View File

@ -49,17 +49,16 @@ var _ = Describe("Loopback", func() {
fmt.Sprintf("CNI_ARGS=%s", "none"),
fmt.Sprintf("CNI_PATH=%s", "/some/test/path"),
}
command.Stdin = strings.NewReader(`{ "cniVersion": "0.1.0" }`)
command.Stdin = strings.NewReader(`{ "name": "loopback-test", "cniVersion": "0.1.0" }`)
})
AfterEach(func() {
Expect(networkNS.Close()).To(Succeed())
Expect(testutils.UnmountNS(networkNS)).To(Succeed())
})
Context("when given a network namespace", func() {
It("sets the lo device to UP", func() {
Skip("TODO: add network name")
command.Env = append(environ, fmt.Sprintf("CNI_COMMAND=%s", "ADD"))
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
@ -80,8 +79,6 @@ var _ = Describe("Loopback", func() {
})
It("sets the lo device to DOWN", func() {
Skip("TODO: add network name")
command.Env = append(environ, fmt.Sprintf("CNI_COMMAND=%s", "DEL"))
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)

View File

@ -23,9 +23,9 @@ Since each macvlan interface has its own MAC address, it makes it easy to use wi
* `name` (string, required): the name of the network
* `type` (string, required): "macvlan"
* `master` (string, optional): name of the host interface to enslave. Defaults to default route interace.
* `master` (string, optional): name of the host interface to enslave. Defaults to default route interface.
* `mode` (string, optional): one of "bridge", "private", "vepa", "passthru". Defaults to "bridge".
* `mtu` (integer, optional): explicitly set MTU to the specified value. Defaults to the value chosen by the kernel.
* `mtu` (integer, optional): explicitly set MTU to the specified value. Defaults to the value chosen by the kernel. The value must be \[0, master's MTU\].
* `ipam` (dictionary, required): IPAM configuration to be used for this network. For interface only without ip address, create empty dictionary.
## Notes

View File

@ -85,9 +85,27 @@ func loadConf(bytes []byte) (*NetConf, string, error) {
}
n.Master = defaultRouteInterface
}
// check existing and MTU of master interface
masterMTU, err := getMTUByName(n.Master)
if err != nil {
return nil, "", err
}
if n.MTU < 0 || n.MTU > masterMTU {
return nil, "", fmt.Errorf("invalid MTU %d, must be [0, master MTU(%d)]", n.MTU, masterMTU)
}
return n, n.CNIVersion, nil
}
func getMTUByName(ifName string) (int, error) {
link, err := netlink.LinkByName(ifName)
if err != nil {
return 0, err
}
return link.Attrs().MTU, nil
}
func modeFromString(s string) (netlink.MacvlanMode, error) {
switch s {
case "", "bridge":

View File

@ -123,6 +123,7 @@ var _ = Describe("macvlan Operations", func() {
AfterEach(func() {
Expect(originalNS.Close()).To(Succeed())
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
})
It("creates an macvlan link in a non-default namespace", func() {

View File

@ -228,7 +228,7 @@ func cmdAdd(args *skel.CmdArgs) error {
}
defer netns.Close()
hostInterface, containerInterface, err := setupContainerVeth(netns, args.IfName, conf.MTU, result)
hostInterface, _, err := setupContainerVeth(netns, args.IfName, conf.MTU, result)
if err != nil {
return err
}
@ -247,12 +247,23 @@ func cmdAdd(args *skel.CmdArgs) error {
}
}
result.DNS = conf.DNS
result.Interfaces = []*current.Interface{hostInterface, containerInterface}
// Only override the DNS settings in the previous result if any DNS fields
// were provided to the ptp plugin. This allows, for example, IPAM plugins
// to specify the DNS settings instead of the ptp plugin.
if dnsConfSet(conf.DNS) {
result.DNS = conf.DNS
}
return types.PrintResult(result, conf.CNIVersion)
}
func dnsConfSet(dnsConf types.DNS) bool {
return dnsConf.Nameservers != nil ||
dnsConf.Search != nil ||
dnsConf.Options != nil ||
dnsConf.Domain != ""
}
func cmdDel(args *skel.CmdArgs) error {
conf := NetConf{}
if err := json.Unmarshal(args.StdinData, &conf); err != nil {

View File

@ -17,6 +17,7 @@ package main
import (
"encoding/json"
"fmt"
"os"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
@ -98,9 +99,10 @@ var _ = Describe("ptp Operations", func() {
AfterEach(func() {
Expect(originalNS.Close()).To(Succeed())
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
})
doTest := func(conf string, numIPs int) {
doTest := func(conf string, numIPs int, expectedDNSConf types.DNS) {
const IFNAME = "ptp0"
targetNs, err := testutils.NewNS()
@ -175,6 +177,9 @@ var _ = Describe("ptp Operations", func() {
Expect(res.Interfaces[1].Mac).To(Equal(wantMac))
Expect(res.Interfaces[1].Sandbox).To(Equal(targetNs.Path()))
// make sure DNS is correct
Expect(res.DNS).To(Equal(expectedDNSConf))
// Call the plugins with the DEL command, deleting the veth endpoints
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
@ -327,7 +332,16 @@ var _ = Describe("ptp Operations", func() {
}
It("configures and deconfigures a ptp link with ADD/DEL", func() {
conf := `{
dnsConf := types.DNS{
Nameservers: []string{"10.1.2.123"},
Domain: "some.domain.test",
Search: []string{"search.test"},
Options: []string{"option1:foo"},
}
dnsConfBytes, err := json.Marshal(dnsConf)
Expect(err).NotTo(HaveOccurred())
conf := fmt.Sprintf(`{
"cniVersion": "0.3.1",
"name": "mynet",
"type": "ptp",
@ -336,10 +350,11 @@ var _ = Describe("ptp Operations", func() {
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24"
}
}`
},
"dns": %s
}`, string(dnsConfBytes))
doTest(conf, 1)
doTest(conf, 1, dnsConf)
})
It("configures and deconfigures a dual-stack ptp link with ADD/DEL", func() {
@ -358,7 +373,112 @@ var _ = Describe("ptp Operations", func() {
}
}`
doTest(conf, 2)
doTest(conf, 2, types.DNS{})
})
It("does not override IPAM DNS settings if no DNS settings provided", func() {
ipamDNSConf := types.DNS{
Nameservers: []string{"10.1.2.123"},
Domain: "some.domain.test",
Search: []string{"search.test"},
Options: []string{"option1:foo"},
}
resolvConfPath, err := testutils.TmpResolvConf(ipamDNSConf)
Expect(err).NotTo(HaveOccurred())
defer os.RemoveAll(resolvConfPath)
conf := fmt.Sprintf(`{
"cniVersion": "0.3.1",
"name": "mynet",
"type": "ptp",
"ipMasq": true,
"mtu": 5000,
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24",
"resolvConf": "%s"
}
}`, resolvConfPath)
doTest(conf, 1, ipamDNSConf)
})
It("overrides IPAM DNS settings if any DNS settings provided", func() {
ipamDNSConf := types.DNS{
Nameservers: []string{"10.1.2.123"},
Domain: "some.domain.test",
Search: []string{"search.test"},
Options: []string{"option1:foo"},
}
resolvConfPath, err := testutils.TmpResolvConf(ipamDNSConf)
Expect(err).NotTo(HaveOccurred())
defer os.RemoveAll(resolvConfPath)
for _, ptpDNSConf := range []types.DNS{
{
Nameservers: []string{"10.1.2.234"},
},
{
Domain: "someother.domain.test",
},
{
Search: []string{"search.elsewhere.test"},
},
{
Options: []string{"option2:bar"},
},
} {
dnsConfBytes, err := json.Marshal(ptpDNSConf)
Expect(err).NotTo(HaveOccurred())
conf := fmt.Sprintf(`{
"cniVersion": "0.3.1",
"name": "mynet",
"type": "ptp",
"ipMasq": true,
"mtu": 5000,
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24",
"resolvConf": "%s"
},
"dns": %s
}`, resolvConfPath, string(dnsConfBytes))
doTest(conf, 1, ptpDNSConf)
}
})
It("overrides IPAM DNS settings if any empty list DNS settings provided", func() {
ipamDNSConf := types.DNS{
Nameservers: []string{"10.1.2.123"},
Domain: "some.domain.test",
Search: []string{"search.test"},
Options: []string{"option1:foo"},
}
resolvConfPath, err := testutils.TmpResolvConf(ipamDNSConf)
Expect(err).NotTo(HaveOccurred())
defer os.RemoveAll(resolvConfPath)
conf := fmt.Sprintf(`{
"cniVersion": "0.3.1",
"name": "mynet",
"type": "ptp",
"ipMasq": true,
"mtu": 5000,
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24",
"resolvConf": "%s"
},
"dns": {
"nameservers": [],
"search": [],
"options": []
}
}`, resolvConfPath)
doTest(conf, 1, types.DNS{})
})
It("deconfigures an unconfigured ptp link with DEL", func() {

View File

@ -53,14 +53,32 @@ func loadConf(bytes []byte) (*NetConf, string, error) {
return nil, "", fmt.Errorf("failed to load netconf: %v", err)
}
if n.Master == "" {
return nil, "", fmt.Errorf(`"master" field is required. It specifies the host interface name to create the VLAN for.`)
return nil, "", fmt.Errorf("\"master\" field is required. It specifies the host interface name to create the VLAN for.")
}
if n.VlanId < 0 || n.VlanId > 4094 {
return nil, "", fmt.Errorf(`invalid VLAN ID %d (must be between 0 and 4095 inclusive)`, n.VlanId)
return nil, "", fmt.Errorf("invalid VLAN ID %d (must be between 0 and 4095 inclusive)", n.VlanId)
}
// check existing and MTU of master interface
masterMTU, err := getMTUByName(n.Master)
if err != nil {
return nil, "", err
}
if n.MTU < 0 || n.MTU > masterMTU {
return nil, "", fmt.Errorf("invalid MTU %d, must be [0, master MTU(%d)]", n.MTU, masterMTU)
}
return n, n.CNIVersion, nil
}
func getMTUByName(ifName string) (int, error) {
link, err := netlink.LinkByName(ifName)
if err != nil {
return 0, err
}
return link.Attrs().MTU, nil
}
func createVlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interface, error) {
vlan := &current.Interface{}
@ -76,10 +94,6 @@ func createVlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interfac
return nil, err
}
if conf.MTU <= 0 {
conf.MTU = m.Attrs().MTU
}
v := &netlink.Vlan{
LinkAttrs: netlink.LinkAttrs{
MTU: conf.MTU,
@ -193,7 +207,7 @@ func cmdDel(args *skel.CmdArgs) error {
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
err = ip.DelLinkByName(args.IfName)
if err != nil && err != ip.ErrLinkNotFound {
if err != nil && err == ip.ErrLinkNotFound {
return nil
}
return err

View File

@ -121,6 +121,7 @@ var _ = Describe("vlan Operations", func() {
AfterEach(func() {
Expect(originalNS.Close()).To(Succeed())
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
})
It("creates an vlan link in a non-default namespace with given MTU", func() {
@ -291,6 +292,19 @@ var _ = Describe("vlan Operations", func() {
return nil
})
Expect(err).NotTo(HaveOccurred())
// DEL can be called multiple times, make sure no error is returned
// if the device is already removed.
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("configures and deconfigures an CNI V4 vlan link with ADD/CHECK/DEL", func() {
@ -407,4 +421,75 @@ var _ = Describe("vlan Operations", func() {
})
Expect(err).NotTo(HaveOccurred())
})
Describe("fails to create vlan link with invalid MTU", func() {
conf := `{
"cniVersion": "0.3.1",
"name": "mynet",
"type": "vlan",
"master": "%s",
"mtu": %d,
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24"
}
}`
BeforeEach(func() {
var err error
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
// set master link's MTU to 1500
link, err := netlink.LinkByName(MASTER_NAME)
Expect(err).NotTo(HaveOccurred())
err = netlink.LinkSetMTU(link, 1500)
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("fails to create vlan link with greater MTU than master interface", func() {
var err error
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: "/var/run/netns/test",
IfName: "eth0",
StdinData: []byte(fmt.Sprintf(conf, MASTER_NAME, 1600)),
}
_ = originalNS.Do(func(netNS ns.NetNS) error {
defer GinkgoRecover()
_, _, err = testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).To(Equal(fmt.Errorf("invalid MTU 1600, must be [0, master MTU(1500)]")))
return nil
})
})
It("fails to create vlan link with negative MTU", func() {
var err error
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: "/var/run/netns/test",
IfName: "eth0",
StdinData: []byte(fmt.Sprintf(conf, MASTER_NAME, -100)),
}
_ = originalNS.Do(func(netNS ns.NetNS) error {
defer GinkgoRecover()
_, _, err = testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).To(Equal(fmt.Errorf("invalid MTU -100, must be [0, master MTU(1500)]")))
return nil
})
})
})
})

View File

@ -35,7 +35,8 @@ With win-bridge plugin, all containers (on the same host) are plugged into an L2
],
"loopbackDSR": true,
"capabilities": {
"dns": true
"dns": true,
"portMappings": true
}
}
```
@ -54,4 +55,5 @@ With win-bridge plugin, all containers (on the same host) are plugged into an L2
* `HcnPolicyArgs` (list, optional): List of hcn policies to be used (only used when ApiVersion is 2).
* `loopbackDSR` (bool, optional): If true, will add a policy to allow the interface to support loopback direct server return.
* `capabilities` (dictionary, optional): Runtime capabilities to enable.
* `dns` (boolean, optional): If true, will take the dns config supplied by the runtime and override other settings.
* `dns` (boolean, optional): If true, will take the dns config supplied by the runtime and override other settings.
* `portMappings` (boolean, optional): If true, will handle HostPort<>ContainerPort mapping using NAT HNS Policies

View File

@ -22,13 +22,13 @@ import (
"github.com/Microsoft/hcsshim"
"github.com/Microsoft/hcsshim/hcn"
"github.com/juju/errors"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/cni/pkg/version"
"github.com/containernetworking/plugins/pkg/errors"
"github.com/containernetworking/plugins/pkg/hns"
"github.com/containernetworking/plugins/pkg/ipam"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
@ -72,7 +72,7 @@ func ProcessEndpointArgs(args *skel.CmdArgs, n *NetConf) (*hns.EndpointInfo, err
return nil, errors.Annotatef(err, "error while NewResultFromResult")
} else {
if len(result.IPs) == 0 {
return nil, errors.New("IPAM plugin return is missing IP config")
return nil, fmt.Errorf("IPAM plugin return is missing IP config")
}
epInfo.IpAddress = result.IPs[0].Address.IP
epInfo.Gateway = result.IPs[0].Address.IP.Mask(result.IPs[0].Address.Mask)
@ -86,6 +86,9 @@ func ProcessEndpointArgs(args *skel.CmdArgs, n *NetConf) (*hns.EndpointInfo, err
n.ApplyOutboundNatPolicy(n.IPMasqNetwork)
}
// Add HostPort mapping if any present
n.ApplyPortMappingPolicy(n.RuntimeConfig.PortMaps)
epInfo.DNS = n.GetDNS()
return epInfo, nil
@ -107,7 +110,6 @@ func cmdHnsAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
}
epName := hns.ConstructEndpointName(args.ContainerID, args.Netns, n.Name)
hnsEndpoint, err := hns.ProvisionEndpoint(epName, hnsNetwork.Id, args.ContainerID, args.Netns, func() (*hcsshim.HNSEndpoint, error) {
epInfo, err := ProcessEndpointArgs(args, n)
epInfo.NetworkId = hnsNetwork.Id
@ -130,7 +132,6 @@ func cmdHnsAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
}
return result, nil
}
func cmdHcnAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
@ -194,7 +195,7 @@ func cmdAdd(args *skel.CmdArgs) error {
}
if result == nil {
return errors.New("result for ADD not populated correctly")
return fmt.Errorf("result for ADD not populated correctly")
}
return types.PrintResult(result, cniVersion)
}

View File

@ -21,13 +21,13 @@ import (
"strings"
"github.com/Microsoft/hcsshim"
"github.com/juju/errors"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/cni/pkg/version"
"github.com/containernetworking/plugins/pkg/errors"
"github.com/containernetworking/plugins/pkg/hns"
"github.com/containernetworking/plugins/pkg/ipam"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
@ -100,12 +100,12 @@ func cmdAdd(args *skel.CmdArgs) error {
}
if len(result.IPs) == 0 {
return nil, errors.New("IPAM plugin return is missing IP config")
return nil, fmt.Errorf("IPAM plugin return is missing IP config")
}
ipAddr := result.IPs[0].Address.IP.To4()
if ipAddr == nil {
return nil, errors.New("win-overlay doesn't support IPv6 now")
return nil, fmt.Errorf("win-overlay doesn't support IPv6 now")
}
// conjure a MAC based on the IP for Overlay

View File

@ -621,6 +621,21 @@ var _ = Describe("bandwidth test", func() {
})
})
Describe("Validating input", func() {
It("Should allow only 4GB burst rate", func() {
err := validateRateAndBurst(5000, 4*1024*1024*1024*8-16) // 2 bytes less than the max should pass
Expect(err).NotTo(HaveOccurred())
err = validateRateAndBurst(5000, 4*1024*1024*1024*8) // we're 1 bit above MaxUint32
Expect(err).To(HaveOccurred())
err = validateRateAndBurst(0, 1)
Expect(err).To(HaveOccurred())
err = validateRateAndBurst(1, 0)
Expect(err).To(HaveOccurred())
err = validateRateAndBurst(0, 0)
Expect(err).NotTo(HaveOccurred())
})
})
Describe("Getting the host interface which plugin should work on from veth peer of container interface", func() {
It("Should work with multiple host veth interfaces", func() {
conf := `{
@ -874,8 +889,8 @@ var _ = Describe("bandwidth test", func() {
Context("when chaining bandwidth plugin with PTP using 0.3.0 config", func() {
var ptpConf string
var rateInBits int
var burstInBits int
var rateInBits uint64
var burstInBits uint64
var packetInBytes int
var containerWithoutTbfNS ns.NetNS
var containerWithTbfNS ns.NetNS
@ -889,7 +904,7 @@ var _ = Describe("bandwidth test", func() {
BeforeEach(func() {
rateInBytes := 1000
rateInBits = rateInBytes * 8
rateInBits = uint64(rateInBytes * 8)
burstInBits = rateInBits * 2
packetInBytes = rateInBytes * 25
@ -1019,8 +1034,8 @@ var _ = Describe("bandwidth test", func() {
Context("when chaining bandwidth plugin with PTP using 0.4.0 config", func() {
var ptpConf string
var rateInBits int
var burstInBits int
var rateInBits uint64
var burstInBits uint64
var packetInBytes int
var containerWithoutTbfNS ns.NetNS
var containerWithTbfNS ns.NetNS
@ -1034,7 +1049,7 @@ var _ = Describe("bandwidth test", func() {
BeforeEach(func() {
rateInBytes := 1000
rateInBits = rateInBytes * 8
rateInBits = uint64(rateInBytes * 8)
burstInBits = rateInBits * 2
packetInBytes = rateInBytes * 25

View File

@ -50,7 +50,7 @@ func TeardownIfb(deviceName string) error {
return err
}
func CreateIngressQdisc(rateInBits, burstInBits int, hostDeviceName string) error {
func CreateIngressQdisc(rateInBits, burstInBits uint64, hostDeviceName string) error {
hostDevice, err := netlink.LinkByName(hostDeviceName)
if err != nil {
return fmt.Errorf("get host device: %s", err)
@ -58,7 +58,7 @@ func CreateIngressQdisc(rateInBits, burstInBits int, hostDeviceName string) erro
return createTBF(rateInBits, burstInBits, hostDevice.Attrs().Index)
}
func CreateEgressQdisc(rateInBits, burstInBits int, hostDeviceName string, ifbDeviceName string) error {
func CreateEgressQdisc(rateInBits, burstInBits uint64, hostDeviceName string, ifbDeviceName string) error {
ifbDevice, err := netlink.LinkByName(ifbDeviceName)
if err != nil {
return fmt.Errorf("get ifb device: %s", err)
@ -113,7 +113,7 @@ func CreateEgressQdisc(rateInBits, burstInBits int, hostDeviceName string, ifbDe
return nil
}
func createTBF(rateInBits, burstInBits, linkIndex int) error {
func createTBF(rateInBits, burstInBits uint64, linkIndex int) error {
// Equivalent to
// tc qdisc add dev link root tbf
// rate netConf.BandwidthLimits.Rate

View File

@ -17,6 +17,7 @@ package main
import (
"encoding/json"
"fmt"
"math"
"github.com/vishvananda/netlink"
@ -37,11 +38,11 @@ const ifbDevicePrefix = "bwp"
// BandwidthEntry corresponds to a single entry in the bandwidth argument,
// see CONVENTIONS.md
type BandwidthEntry struct {
IngressRate int `json:"ingressRate"` //Bandwidth rate in bps for traffic through container. 0 for no limit. If ingressRate is set, ingressBurst must also be set
IngressBurst int `json:"ingressBurst"` //Bandwidth burst in bits for traffic through container. 0 for no limit. If ingressBurst is set, ingressRate must also be set
IngressRate uint64 `json:"ingressRate"` //Bandwidth rate in bps for traffic through container. 0 for no limit. If ingressRate is set, ingressBurst must also be set
IngressBurst uint64 `json:"ingressBurst"` //Bandwidth burst in bits for traffic through container. 0 for no limit. If ingressBurst is set, ingressRate must also be set
EgressRate int `json:"egressRate"` //Bandwidth rate in bps for traffic through container. 0 for no limit. If egressRate is set, egressBurst must also be set
EgressBurst int `json:"egressBurst"` //Bandwidth burst in bits for traffic through container. 0 for no limit. If egressBurst is set, egressRate must also be set
EgressRate uint64 `json:"egressRate"` //Bandwidth rate in bps for traffic through container. 0 for no limit. If egressRate is set, egressBurst must also be set
EgressBurst uint64 `json:"egressBurst"` //Bandwidth burst in bits for traffic through container. 0 for no limit. If egressBurst is set, egressRate must also be set
}
func (bw *BandwidthEntry) isZero() bool {
@ -101,7 +102,7 @@ func getBandwidth(conf *PluginConf) *BandwidthEntry {
return conf.BandwidthEntry
}
func validateRateAndBurst(rate int, burst int) error {
func validateRateAndBurst(rate, burst uint64) error {
switch {
case burst < 0 || rate < 0:
return fmt.Errorf("rate and burst must be a positive integer")
@ -109,6 +110,8 @@ func validateRateAndBurst(rate int, burst int) error {
return fmt.Errorf("if rate is set, burst must also be set")
case rate == 0 && burst != 0:
return fmt.Errorf("if burst is set, rate must also be set")
case burst/8 >= math.MaxUint32:
return fmt.Errorf("burst cannot be more than 4GB")
}
return nil

View File

@ -27,7 +27,6 @@ import (
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/cni/pkg/version"
"github.com/containernetworking/plugins/pkg/ns"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
@ -68,9 +67,15 @@ func parseConf(data []byte) (*FirewallNetConf, *current.Result, error) {
return nil, nil, fmt.Errorf("failed to load netconf: %v", err)
}
// Default the firewalld zone to trusted
if conf.FirewalldZone == "" {
conf.FirewalldZone = "trusted"
}
// Parse previous result.
if conf.RawPrevResult == nil {
return nil, nil, fmt.Errorf("missing prevResult from earlier plugin")
// return early if there was no previous result, which is allowed for DEL calls
return &conf, &current.Result{}, nil
}
// Parse previous result.
@ -85,11 +90,6 @@ func parseConf(data []byte) (*FirewallNetConf, *current.Result, error) {
return nil, nil, fmt.Errorf("could not convert result to current version: %v", err)
}
// Default the firewalld zone to trusted
if conf.FirewalldZone == "" {
conf.FirewalldZone = "trusted"
}
return &conf, result, nil
}
@ -116,6 +116,10 @@ func cmdAdd(args *skel.CmdArgs) error {
return err
}
if conf.PrevResult == nil {
return fmt.Errorf("missing prevResult from earlier plugin")
}
backend, err := getBackend(conf)
if err != nil {
return err
@ -142,12 +146,6 @@ func cmdDel(args *skel.CmdArgs) error {
return err
}
// Tolerate errors if the container namespace has been torn down already
containerNS, err := ns.GetNS(args.Netns)
if err == nil {
defer containerNS.Close()
}
// Runtime errors are ignored
if err := backend.Del(conf, result); err != nil {
return err
@ -167,8 +165,8 @@ func cmdCheck(args *skel.CmdArgs) error {
}
// Ensure we have previous result.
if result == nil {
return fmt.Errorf("Required prevResult missing")
if conf.PrevResult == nil {
return fmt.Errorf("missing prevResult from earlier plugin")
}
backend, err := getBackend(conf)

View File

@ -270,6 +270,13 @@ var _ = Describe("firewall plugin iptables backend", func() {
Expect(err).NotTo(HaveOccurred())
validateFullRuleset(fullConf)
// ensure creation is idempotent
_, _, err = testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, fullConf, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())

View File

@ -22,6 +22,7 @@ import (
"net"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/plugins/pkg/utils"
"github.com/coreos/go-iptables/iptables"
)
@ -32,20 +33,6 @@ func getPrivChainRules(ip string) [][]string {
return rules
}
func ensureChain(ipt *iptables.IPTables, table, chain string) error {
chains, err := ipt.ListChains(table)
if err != nil {
return fmt.Errorf("failed to list iptables chains: %v", err)
}
for _, ch := range chains {
if ch == chain {
return nil
}
}
return ipt.NewChain(table, chain)
}
func generateFilterRule(privChainName string) []string {
return []string{"-m", "comment", "--comment", "CNI firewall plugin rules", "-j", privChainName}
}
@ -73,10 +60,10 @@ func (ib *iptablesBackend) setupChains(ipt *iptables.IPTables) error {
adminRule := generateFilterRule(ib.adminChainName)
// Ensure our private chains exist
if err := ensureChain(ipt, "filter", ib.privChainName); err != nil {
if err := utils.EnsureChain(ipt, "filter", ib.privChainName); err != nil {
return err
}
if err := ensureChain(ipt, "filter", ib.adminChainName); err != nil {
if err := utils.EnsureChain(ipt, "filter", ib.adminChainName); err != nil {
return err
}
@ -160,10 +147,10 @@ func (ib *iptablesBackend) checkRules(conf *FirewallNetConf, result *current.Res
}
// Ensure our private chains exist
if err := ensureChain(ipt, "filter", ib.privChainName); err != nil {
if err := utils.EnsureChain(ipt, "filter", ib.privChainName); err != nil {
return err
}
if err := ensureChain(ipt, "filter", ib.adminChainName); err != nil {
if err := utils.EnsureChain(ipt, "filter", ib.adminChainName); err != nil {
return err
}

View File

@ -89,7 +89,7 @@ Additionally, for the bridge plugin, `isGateway` will be set to `true`, if not p
## Windows Support (Experimental)
This plugin supports delegating to the windows CNI plugins (overlay.exe, l2bridge.exe) to work in conjunction with [Flannel on Windows](https://github.com/coreos/flannel/issues/833).
Flannel sets up an [HNS Network](https://docs.microsoft.com/en-us/virtualization/windowscontainers/manage-containers/container-networking) in L2Bridge mode for host-gw and in Overlay mode for vxlan.
Flannel sets up an [HNS Network](https://docs.microsoft.com/en-us/virtualization/windowscontainers/container-networking/architecture) in L2Bridge mode for host-gw and in Overlay mode for vxlan.
The following fields must be set in the delegated plugin configuration:
* `name` (string, required): the name of the network (must match the name in Flannel config / name of the HNS network)
@ -132,4 +132,4 @@ For this example, Flannel CNI would generate the following config to delegate to
"type": "host-local"
}
}
```
```

View File

@ -72,7 +72,7 @@ will masquerade traffic as needed.
The DNAT rule rewrites the destination port and address of new connections.
There is a top-level chain, `CNI-HOSTPORT-DNAT` which is always created and
never deleted. Each plugin execution creates an additional chain for ease
of cleanup. So, if a single container exists on IP 172.16.30.2 with ports
of cleanup. So, if a single container exists on IP 172.16.30.2/24 with ports
8080 and 8043 on the host forwarded to ports 80 and 443 in the container, the
rules look like this:
@ -86,10 +86,10 @@ rules look like this:
- `-j MARK --set-xmark 0x2000/0x2000`
`CNI-DN-xxxxxx` chain:
- `-p tcp -s 172.16.30.2 --dport 8080 -j CNI-HOSTPORT-SETMARK` (masquerade hairpin traffic)
- `-p tcp -s 172.16.30.0/24 --dport 8080 -j CNI-HOSTPORT-SETMARK` (masquerade hairpin traffic)
- `-p tcp -s 127.0.0.1 --dport 8080 -j CNI-HOSTPORT-SETMARK` (masquerade localhost traffic)
- `-p tcp --dport 8080 -j DNAT --to-destination 172.16.30.2:80` (rewrite destination)
- `-p tcp -s 172.16.30.2 --dport 8043 -j CNI-HOSTPORT-SETMARK`
- `-p tcp -s 172.16.30.0/24 --dport 8043 -j CNI-HOSTPORT-SETMARK`
- `-p tcp -s 127.0.0.1 --dport 8043 -j CNI-HOSTPORT-SETMARK`
- `-p tcp --dport 8043 -j DNAT --to-destination 172.16.30.2:443`

View File

@ -18,6 +18,7 @@ import (
"fmt"
"strings"
"github.com/containernetworking/plugins/pkg/utils"
"github.com/coreos/go-iptables/iptables"
"github.com/mattn/go-shellwords"
)
@ -35,16 +36,11 @@ type chain struct {
// setup idempotently creates the chain. It will not error if the chain exists.
func (c *chain) setup(ipt *iptables.IPTables) error {
// create the chain
exists, err := chainExists(ipt, c.table, c.name)
err := utils.EnsureChain(ipt, c.table, c.name)
if err != nil {
return err
}
if !exists {
if err := ipt.NewChain(c.table, c.name); err != nil {
return err
}
}
// Add the rules to the chain
for _, rule := range c.rules {
@ -74,7 +70,7 @@ func (c *chain) teardown(ipt *iptables.IPTables) error {
// flush the chain
// This will succeed *and create the chain* if it does not exist.
// If the chain doesn't exist, the next checks will fail.
if err := ipt.ClearChain(c.table, c.name); err != nil {
if err := utils.ClearChain(ipt, c.table, c.name); err != nil {
return err
}
@ -94,17 +90,15 @@ func (c *chain) teardown(ipt *iptables.IPTables) error {
}
chainParts = chainParts[2:] // List results always include an -A CHAINNAME
if err := ipt.Delete(c.table, entryChain, chainParts...); err != nil {
return fmt.Errorf("Failed to delete referring rule %s %s: %v", c.table, entryChainRule, err)
if err := utils.DeleteRule(ipt, c.table, entryChain, chainParts...); err != nil {
return err
}
}
}
}
if err := ipt.DeleteChain(c.table, c.name); err != nil {
return err
}
return nil
return utils.DeleteChain(ipt, c.table, c.name)
}
// insertUnique will add a rule to a chain if it does not already exist.
@ -125,24 +119,10 @@ func insertUnique(ipt *iptables.IPTables, table, chain string, prepend bool, rul
}
}
func chainExists(ipt *iptables.IPTables, tableName, chainName string) (bool, error) {
chains, err := ipt.ListChains(tableName)
if err != nil {
return false, err
}
for _, ch := range chains {
if ch == chainName {
return true, nil
}
}
return false, nil
}
// check the chain.
func (c *chain) check(ipt *iptables.IPTables) error {
exists, err := chainExists(ipt, c.table, c.name)
exists, err := utils.ChainExists(ipt, c.table, c.name)
if err != nil {
return err
}

View File

@ -18,6 +18,7 @@ import (
"fmt"
"math/rand"
"runtime"
"sync"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils"
@ -32,6 +33,7 @@ const TABLE = "filter" // We'll monkey around here
var _ = Describe("chain tests", func() {
var testChain chain
var ipt *iptables.IPTables
var testNs ns.NetNS
var cleanup func()
BeforeEach(func() {
@ -41,7 +43,7 @@ var _ = Describe("chain tests", func() {
currNs, err := ns.GetCurrentNS()
Expect(err).NotTo(HaveOccurred())
testNs, err := testutils.NewNS()
testNs, err = testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
tlChainName := fmt.Sprintf("cni-test-%d", rand.Intn(10000000))
@ -195,4 +197,38 @@ var _ = Describe("chain tests", func() {
}
}
})
It("deletes chains idempotently in parallel", func() {
defer cleanup()
// number of parallel executions
N := 10
var wg sync.WaitGroup
err := testChain.setup(ipt)
Expect(err).NotTo(HaveOccurred())
errCh := make(chan error, N)
for i := 0; i < N; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// teardown chain
errCh <- testNs.Do(func(ns.NetNS) error {
return testChain.teardown(ipt)
})
}()
}
wg.Wait()
close(errCh)
for err := range errCh {
Expect(err).NotTo(HaveOccurred())
}
chains, err := ipt.ListChains(TABLE)
Expect(err).NotTo(HaveOccurred())
for _, chain := range chains {
if chain == testChain.name {
Fail("Chain was not deleted")
}
}
})
})

View File

@ -60,9 +60,9 @@ type PortMapConf struct {
// These are fields parsed out of the config or the environment;
// included here for convenience
ContainerID string `json:"-"`
ContIPv4 net.IP `json:"-"`
ContIPv6 net.IP `json:"-"`
ContainerID string `json:"-"`
ContIPv4 net.IPNet `json:"-"`
ContIPv6 net.IPNet `json:"-"`
}
// The default mark bit to signal that masquerading is required
@ -85,13 +85,13 @@ func cmdAdd(args *skel.CmdArgs) error {
netConf.ContainerID = args.ContainerID
if netConf.ContIPv4 != nil {
if netConf.ContIPv4.IP != nil {
if err := forwardPorts(netConf, netConf.ContIPv4); err != nil {
return err
}
}
if netConf.ContIPv6 != nil {
if netConf.ContIPv6.IP != nil {
if err := forwardPorts(netConf, netConf.ContIPv6); err != nil {
return err
}
@ -138,13 +138,13 @@ func cmdCheck(args *skel.CmdArgs) error {
conf.ContainerID = args.ContainerID
if conf.ContIPv4 != nil {
if conf.ContIPv4.IP != nil {
if err := checkPorts(conf, conf.ContIPv4); err != nil {
return err
}
}
if conf.ContIPv6 != nil {
if conf.ContIPv6.IP != nil {
if err := checkPorts(conf, conf.ContIPv6); err != nil {
return err
}
@ -205,9 +205,9 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, *current.Result, er
if conf.PrevResult != nil {
for _, ip := range result.IPs {
if ip.Version == "6" && conf.ContIPv6 != nil {
if ip.Version == "6" && conf.ContIPv6.IP != nil {
continue
} else if ip.Version == "4" && conf.ContIPv4 != nil {
} else if ip.Version == "4" && conf.ContIPv4.IP != nil {
continue
}
@ -223,9 +223,9 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, *current.Result, er
}
switch ip.Version {
case "6":
conf.ContIPv6 = ip.Address.IP
conf.ContIPv6 = ip.Address
case "4":
conf.ContIPv4 = ip.Address.IP
conf.ContIPv4 = ip.Address
}
}
}

View File

@ -48,9 +48,9 @@ const MarkMasqChainName = "CNI-HOSTPORT-MASQ"
const OldTopLevelSNATChainName = "CNI-HOSTPORT-SNAT"
// forwardPorts establishes port forwarding to a given container IP.
// containerIP can be either v4 or v6.
func forwardPorts(config *PortMapConf, containerIP net.IP) error {
isV6 := (containerIP.To4() == nil)
// containerNet.IP can be either v4 or v6.
func forwardPorts(config *PortMapConf, containerNet net.IPNet) error {
isV6 := (containerNet.IP.To4() == nil)
var ipt *iptables.IPTables
var err error
@ -86,7 +86,7 @@ func forwardPorts(config *PortMapConf, containerIP net.IP) error {
if !isV6 {
// Set the route_localnet bit on the host interface, so that
// 127/8 can cross a routing boundary.
hostIfName := getRoutableHostIF(containerIP)
hostIfName := getRoutableHostIF(containerNet.IP)
if hostIfName != "" {
if err := enableLocalnetRouting(hostIfName); err != nil {
return fmt.Errorf("unable to enable route_localnet: %v", err)
@ -104,7 +104,7 @@ func forwardPorts(config *PortMapConf, containerIP net.IP) error {
dnatChain := genDnatChain(config.Name, config.ContainerID)
// First, idempotently tear down this chain in case there was some
// sort of collision or bad state.
fillDnatRules(&dnatChain, config, containerIP)
fillDnatRules(&dnatChain, config, containerNet)
if err := dnatChain.setup(ipt); err != nil {
return fmt.Errorf("unable to setup DNAT: %v", err)
}
@ -112,10 +112,10 @@ func forwardPorts(config *PortMapConf, containerIP net.IP) error {
return nil
}
func checkPorts(config *PortMapConf, containerIP net.IP) error {
func checkPorts(config *PortMapConf, containerNet net.IPNet) error {
dnatChain := genDnatChain(config.Name, config.ContainerID)
fillDnatRules(&dnatChain, config, containerIP)
fillDnatRules(&dnatChain, config, containerNet)
ip4t := maybeGetIptables(false)
ip6t := maybeGetIptables(true)
@ -124,7 +124,7 @@ func checkPorts(config *PortMapConf, containerIP net.IP) error {
}
if ip4t != nil {
exists, err := chainExists(ip4t, dnatChain.table, dnatChain.name)
exists, err := utils.ChainExists(ip4t, dnatChain.table, dnatChain.name)
if err != nil {
return err
}
@ -137,7 +137,7 @@ func checkPorts(config *PortMapConf, containerIP net.IP) error {
}
if ip6t != nil {
exists, err := chainExists(ip6t, dnatChain.table, dnatChain.name)
exists, err := utils.ChainExists(ip6t, dnatChain.table, dnatChain.name)
if err != nil {
return err
}
@ -180,8 +180,8 @@ func genDnatChain(netName, containerID string) chain {
// dnatRules generates the destination NAT rules, one per port, to direct
// traffic from hostip:hostport to podip:podport
func fillDnatRules(c *chain, config *PortMapConf, containerIP net.IP) {
isV6 := (containerIP.To4() == nil)
func fillDnatRules(c *chain, config *PortMapConf, containerNet net.IPNet) {
isV6 := (containerNet.IP.To4() == nil)
comment := trimComment(fmt.Sprintf(`dnat name: "%s" id: "%s"`, config.Name, config.ContainerID))
entries := config.RuntimeConfig.PortMaps
setMarkChainName := SetMarkChainName
@ -224,6 +224,16 @@ func fillDnatRules(c *chain, config *PortMapConf, containerIP net.IP) {
// the ordering is important here; the mark rules must be first.
c.rules = make([][]string, 0, 3*len(entries))
for _, entry := range entries {
// If a HostIP is given, only process the entry if host and container address families match
if entry.HostIP != "" {
hostIP := net.ParseIP(entry.HostIP)
isHostV6 := (hostIP.To4() == nil)
if isV6 != isHostV6 {
continue
}
}
ruleBase := []string{
"-p", entry.Protocol,
"--dport", strconv.Itoa(entry.HostPort)}
@ -239,7 +249,7 @@ func fillDnatRules(c *chain, config *PortMapConf, containerIP net.IP) {
copy(hpRule, ruleBase)
hpRule = append(hpRule,
"-s", containerIP.String(),
"-s", containerNet.String(),
"-j", setMarkChainName,
)
c.rules = append(c.rules, hpRule)
@ -262,7 +272,7 @@ func fillDnatRules(c *chain, config *PortMapConf, containerIP net.IP) {
copy(dnatRule, ruleBase)
dnatRule = append(dnatRule,
"-j", "DNAT",
"--to-destination", fmtIpPort(containerIP, entry.ContainerPort),
"--to-destination", fmtIpPort(containerNet.IP, entry.ContainerPort),
)
c.rules = append(c.rules, dnatRule)
}

View File

@ -96,119 +96,133 @@ var _ = Describe("portmap integration tests", func() {
}
})
// This needs to be done using Ginkgo's asynchronous testing mode.
It("forwards a TCP port on ipv4", func(done Done) {
var err error
hostPort := rand.Intn(10000) + 1025
runtimeConfig := libcni.RuntimeConf{
ContainerID: fmt.Sprintf("unit-test-%d", hostPort),
NetNS: targetNS.Path(),
IfName: "eth0",
CapabilityArgs: map[string]interface{}{
"portMappings": []map[string]interface{}{
{
"hostPort": hostPort,
"containerPort": containerPort,
"protocol": "tcp",
Describe("Creating an interface in a namespace with the ptp plugin", func() {
// This needs to be done using Ginkgo's asynchronous testing mode.
It("forwards a TCP port on ipv4", func(done Done) {
var err error
hostPort := rand.Intn(10000) + 1025
runtimeConfig := libcni.RuntimeConf{
ContainerID: fmt.Sprintf("unit-test-%d", hostPort),
NetNS: targetNS.Path(),
IfName: "eth0",
CapabilityArgs: map[string]interface{}{
"portMappings": []map[string]interface{}{
{
"hostPort": hostPort,
"containerPort": containerPort,
"protocol": "tcp",
},
},
},
},
}
// Make delete idempotent, so we can clean up on failure
netDeleted := false
deleteNetwork := func() error {
if netDeleted {
return nil
}
netDeleted = true
return cniConf.DelNetworkList(context.TODO(), configList, &runtimeConfig)
}
// we'll also manually check the iptables chains
ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
Expect(err).NotTo(HaveOccurred())
dnatChainName := genDnatChain("cni-portmap-unit-test", runtimeConfig.ContainerID).name
// Create the network
resI, err := cniConf.AddNetworkList(context.TODO(), configList, &runtimeConfig)
Expect(err).NotTo(HaveOccurred())
defer deleteNetwork()
// Undo Docker's forwarding policy
cmd := exec.Command("iptables", "-t", "filter",
"-P", "FORWARD", "ACCEPT")
cmd.Stderr = GinkgoWriter
err = cmd.Run()
Expect(err).NotTo(HaveOccurred())
// Check the chain exists
_, err = ipt.List("nat", dnatChainName)
Expect(err).NotTo(HaveOccurred())
result, err := current.GetResult(resI)
Expect(err).NotTo(HaveOccurred())
var contIP net.IP
for _, ip := range result.IPs {
intfIndex := *ip.Interface
if result.Interfaces[intfIndex].Sandbox == "" {
continue
// Make delete idempotent, so we can clean up on failure
netDeleted := false
deleteNetwork := func() error {
if netDeleted {
return nil
}
netDeleted = true
return cniConf.DelNetworkList(context.TODO(), configList, &runtimeConfig)
}
contIP = ip.Address.IP
}
if contIP == nil {
Fail("could not determine container IP")
}
hostIP := getLocalIP()
fmt.Fprintf(GinkgoWriter, "hostIP: %s:%d, contIP: %s:%d\n",
hostIP, hostPort, contIP, containerPort)
// we'll also manually check the iptables chains
ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
Expect(err).NotTo(HaveOccurred())
dnatChainName := genDnatChain("cni-portmap-unit-test", runtimeConfig.ContainerID).name
// dump iptables-save output for debugging
cmd = exec.Command("iptables-save")
cmd.Stderr = GinkgoWriter
cmd.Stdout = GinkgoWriter
Expect(cmd.Run()).To(Succeed())
// Create the network
resI, err := cniConf.AddNetworkList(context.TODO(), configList, &runtimeConfig)
Expect(err).NotTo(HaveOccurred())
defer deleteNetwork()
// Sanity check: verify that the container is reachable directly
contOK := testEchoServer(contIP.String(), containerPort, "")
// Undo Docker's forwarding policy
cmd := exec.Command("iptables", "-t", "filter",
"-P", "FORWARD", "ACCEPT")
cmd.Stderr = GinkgoWriter
err = cmd.Run()
Expect(err).NotTo(HaveOccurred())
// Verify that a connection to the forwarded port works
dnatOK := testEchoServer(hostIP, hostPort, "")
// Check the chain exists
_, err = ipt.List("nat", dnatChainName)
Expect(err).NotTo(HaveOccurred())
// Verify that a connection to localhost works
snatOK := testEchoServer("127.0.0.1", hostPort, "")
result, err := current.GetResult(resI)
Expect(err).NotTo(HaveOccurred())
var contIP net.IP
// verify that hairpin works
hairpinOK := testEchoServer(hostIP, hostPort, targetNS.Path())
for _, ip := range result.IPs {
intfIndex := *ip.Interface
if result.Interfaces[intfIndex].Sandbox == "" {
continue
}
contIP = ip.Address.IP
}
if contIP == nil {
Fail("could not determine container IP")
}
// Cleanup
session.Terminate()
err = deleteNetwork()
Expect(err).NotTo(HaveOccurred())
hostIP := getLocalIP()
fmt.Fprintf(GinkgoWriter, "hostIP: %s:%d, contIP: %s:%d\n",
hostIP, hostPort, contIP, containerPort)
// Verify iptables rules are gone
_, err = ipt.List("nat", dnatChainName)
Expect(err).To(MatchError(ContainSubstring("iptables: No chain/target/match by that name.")))
// dump iptables-save output for debugging
cmd = exec.Command("iptables-save")
cmd.Stderr = GinkgoWriter
cmd.Stdout = GinkgoWriter
Expect(cmd.Run()).To(Succeed())
// Check that everything succeeded *after* we clean up the network
if !contOK {
Fail("connection direct to " + contIP.String() + " failed")
}
if !dnatOK {
Fail("Connection to " + hostIP + " was not forwarded")
}
if !snatOK {
Fail("connection to 127.0.0.1 was not forwarded")
}
if !hairpinOK {
Fail("Hairpin connection failed")
}
// dump ip routes output for debugging
cmd = exec.Command("ip", "route")
cmd.Stderr = GinkgoWriter
cmd.Stdout = GinkgoWriter
Expect(cmd.Run()).To(Succeed())
close(done)
// dump ip addresses output for debugging
cmd = exec.Command("ip", "addr")
cmd.Stderr = GinkgoWriter
cmd.Stdout = GinkgoWriter
Expect(cmd.Run()).To(Succeed())
}, TIMEOUT*9)
// Sanity check: verify that the container is reachable directly
contOK := testEchoServer(contIP.String(), containerPort, "")
// Verify that a connection to the forwarded port works
dnatOK := testEchoServer(hostIP, hostPort, "")
// Verify that a connection to localhost works
snatOK := testEchoServer("127.0.0.1", hostPort, "")
// verify that hairpin works
hairpinOK := testEchoServer(hostIP, hostPort, targetNS.Path())
// Cleanup
session.Terminate()
err = deleteNetwork()
Expect(err).NotTo(HaveOccurred())
// Verify iptables rules are gone
_, err = ipt.List("nat", dnatChainName)
Expect(err).To(MatchError(ContainSubstring("iptables: No chain/target/match by that name.")))
// Check that everything succeeded *after* we clean up the network
if !contOK {
Fail("connection direct to " + contIP.String() + " failed")
}
if !dnatOK {
Fail("Connection to " + hostIP + " was not forwarded")
}
if !snatOK {
Fail("connection to 127.0.0.1 was not forwarded")
}
if !hairpinOK {
Fail("Hairpin connection failed")
}
close(done)
}, TIMEOUT*9)
})
})
// testEchoServer returns true if we found an echo server on the port

View File

@ -16,7 +16,8 @@ package main
import (
"fmt"
"net"
"github.com/containernetworking/cni/pkg/types"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
@ -77,8 +78,10 @@ var _ = Describe("portmapping configuration", func() {
Expect(c.SNAT).To(Equal(&fvar))
Expect(c.Name).To(Equal("test"))
Expect(c.ContIPv4).To(Equal(net.ParseIP("10.0.0.2")))
Expect(c.ContIPv6).To(Equal(net.ParseIP("2001:db8:1::2")))
n, err := types.ParseCIDR("10.0.0.2/24")
Expect(c.ContIPv4).To(Equal(*n))
n, err = types.ParseCIDR("2001:db8:1::2/64")
Expect(c.ContIPv6).To(Equal(*n))
})
It("Correctly parses a DEL config", func() {
@ -186,7 +189,8 @@ var _ = Describe("portmapping configuration", func() {
entryChains: []string{"CNI-HOSTPORT-DNAT"},
}))
fillDnatRules(&ch, conf, net.ParseIP("10.0.0.2"))
n, err := types.ParseCIDR("10.0.0.2/24")
fillDnatRules(&ch, conf, *n)
Expect(ch.entryRules).To(Equal([][]string{
{"-m", "comment", "--comment",
@ -204,16 +208,16 @@ var _ = Describe("portmapping configuration", func() {
}))
Expect(ch.rules).To(Equal([][]string{
{"-p", "tcp", "--dport", "8080", "-s", "10.0.0.2", "-j", "CNI-HOSTPORT-SETMARK"},
{"-p", "tcp", "--dport", "8080", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
{"-p", "tcp", "--dport", "8080", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
{"-p", "tcp", "--dport", "8081", "-s", "10.0.0.2", "-j", "CNI-HOSTPORT-SETMARK"},
{"-p", "tcp", "--dport", "8081", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
{"-p", "tcp", "--dport", "8081", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
{"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
{"-p", "udp", "--dport", "8080", "-s", "10.0.0.2", "-j", "CNI-HOSTPORT-SETMARK"},
{"-p", "udp", "--dport", "8080", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
{"-p", "udp", "--dport", "8080", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
{"-p", "udp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:81"},
{"-p", "udp", "--dport", "8082", "-s", "10.0.0.2", "-j", "CNI-HOSTPORT-SETMARK"},
{"-p", "udp", "--dport", "8082", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
{"-p", "udp", "--dport", "8082", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
{"-p", "udp", "--dport", "8082", "-j", "DNAT", "--to-destination", "10.0.0.2:82"},
}))
@ -221,16 +225,17 @@ var _ = Describe("portmapping configuration", func() {
ch.rules = nil
ch.entryRules = nil
fillDnatRules(&ch, conf, net.ParseIP("2001:db8::2"))
n, err = types.ParseCIDR("2001:db8::2/64")
fillDnatRules(&ch, conf, *n)
Expect(ch.rules).To(Equal([][]string{
{"-p", "tcp", "--dport", "8080", "-s", "2001:db8::2", "-j", "CNI-HOSTPORT-SETMARK"},
{"-p", "tcp", "--dport", "8080", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "[2001:db8::2]:80"},
{"-p", "tcp", "--dport", "8081", "-s", "2001:db8::2", "-j", "CNI-HOSTPORT-SETMARK"},
{"-p", "tcp", "--dport", "8081", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
{"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "[2001:db8::2]:80"},
{"-p", "udp", "--dport", "8080", "-s", "2001:db8::2", "-j", "CNI-HOSTPORT-SETMARK"},
{"-p", "udp", "--dport", "8080", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
{"-p", "udp", "--dport", "8080", "-j", "DNAT", "--to-destination", "[2001:db8::2]:81"},
{"-p", "udp", "--dport", "8082", "-s", "2001:db8::2", "-j", "CNI-HOSTPORT-SETMARK"},
{"-p", "udp", "--dport", "8082", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
{"-p", "udp", "--dport", "8082", "-j", "DNAT", "--to-destination", "[2001:db8::2]:82"},
}))
@ -240,7 +245,8 @@ var _ = Describe("portmapping configuration", func() {
fvar := false
conf.SNAT = &fvar
fillDnatRules(&ch, conf, net.ParseIP("10.0.0.2"))
n, err = types.ParseCIDR("10.0.0.2/24")
fillDnatRules(&ch, conf, *n)
Expect(ch.rules).To(Equal([][]string{
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
{"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
@ -276,9 +282,10 @@ var _ = Describe("portmapping configuration", func() {
conf.ContainerID = containerID
ch = genDnatChain(conf.Name, containerID)
fillDnatRules(&ch, conf, net.ParseIP("10.0.0.2"))
n, err := types.ParseCIDR("10.0.0.2/24")
fillDnatRules(&ch, conf, *n)
Expect(ch.rules).To(Equal([][]string{
{"-p", "tcp", "--dport", "8080", "-s", "10.0.0.2", "-j", "PLZ-SET-MARK"},
{"-p", "tcp", "--dport", "8080", "-s", "10.0.0.2/24", "-j", "PLZ-SET-MARK"},
{"-p", "tcp", "--dport", "8080", "-s", "127.0.0.1", "-j", "PLZ-SET-MARK"},
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
}))

View File

@ -237,7 +237,7 @@ func doRoutes(ipCfgs []*current.IPConfig, origRoutes []*types.Route, iface strin
if ipCfg.Version == "4" {
src.Mask = net.CIDRMask(32, 32)
} else {
src.Mask = net.CIDRMask(64, 64)
src.Mask = net.CIDRMask(128, 128)
}
log.Printf("Source to use %s", src.String())
@ -258,7 +258,7 @@ func doRoutes(ipCfgs []*current.IPConfig, origRoutes []*types.Route, iface strin
dest.Mask = net.CIDRMask(0, 32)
} else {
dest.IP = net.IPv6zero
dest.Mask = net.CIDRMask(0, 64)
dest.Mask = net.CIDRMask(0, 128)
}
route := netlink.Route{
@ -296,6 +296,10 @@ func doRoutes(ipCfgs []*current.IPConfig, origRoutes []*types.Route, iface strin
route.Table = table
// Reset the route flags since if it is dynamically created,
// adding it to the new table will fail with "invalid argument"
route.Flags = 0
// We use route replace in case the route already exists, which
// is possible for the default gateway we added above.
err = netlink.RouteReplace(&route)

View File

@ -25,6 +25,7 @@ import (
"path/filepath"
"strings"
"github.com/j-keck/arping"
"github.com/vishvananda/netlink"
"github.com/containernetworking/cni/pkg/skel"
@ -179,7 +180,7 @@ func cmdAdd(args *skel.CmdArgs) error {
return err
}
_, err = current.NewResultFromResult(tuningConf.PrevResult)
result, err := current.NewResultFromResult(tuningConf.PrevResult)
if err != nil {
return err
}
@ -208,6 +209,13 @@ func cmdAdd(args *skel.CmdArgs) error {
if err = changeMacAddr(args.IfName, tuningConf.Mac); err != nil {
return err
}
for _, ipc := range result.IPs {
if ipc.Version == "4" {
_ = arping.GratuitousArpOverIfaceByName(ipc.Address.IP, args.IfName)
}
}
updateResultsMacAddr(*tuningConf, args.IfName, tuningConf.Mac)
}

View File

@ -21,7 +21,7 @@ $DOCKER run -ti -v ${SRC_DIR}:/go/src/github.com/containernetworking/plugins --r
apk --no-cache add bash tar;
cd /go/src/github.com/containernetworking/plugins; umask 0022;
for arch in amd64 arm arm64 ppc64le s390x; do \
for arch in amd64 arm arm64 ppc64le s390x mips64le; do \
rm -f ${OUTPUT_DIR}/*; \
CGO_ENABLED=0 GOARCH=\$arch ./build_linux.sh ${BUILDFLAGS}; \
for format in tgz; do \

View File

@ -57,3 +57,7 @@ if [ -n "${vetRes}" ]; then
echo -e "govet checking failed:\n${vetRes}"
exit 255
fi
# Run the pkg/ns tests as non root user
mkdir /tmp/cni-rootless
(export XDG_RUNTIME_DIR=/tmp/cni-rootless; cd pkg/ns/; unshare -rmn go test)

View File

@ -69,6 +69,7 @@ type CNI interface {
AddNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
CheckNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) error
DelNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) error
GetNetworkListCachedResult(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
AddNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) (types.Result, error)
CheckNetwork(ctx context.Context, net *NetworkConfig, rt *RuntimeConf) error

View File

@ -15,6 +15,7 @@
package invoke
import (
"fmt"
"os"
"strings"
)
@ -22,6 +23,8 @@ 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
}
@ -57,17 +60,17 @@ func (args *Args) AsEnv() []string {
pluginArgsStr = stringify(args.PluginArgs)
}
// Ensure that the custom values are first, so any value present in
// the process environment won't override them.
env = append([]string{
"CNI_COMMAND=" + args.Command,
"CNI_CONTAINERID=" + args.ContainerID,
"CNI_NETNS=" + args.NetNS,
"CNI_ARGS=" + pluginArgsStr,
"CNI_IFNAME=" + args.IfName,
"CNI_PATH=" + args.Path,
}, env...)
return env
// Duplicated values which come first will be overrided, so we must put the
// custom values in the end to avoid being overrided 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)
}
// taken from rkt/networking/net_plugin.go
@ -80,3 +83,46 @@ 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
}

View File

@ -16,22 +16,17 @@ package invoke
import (
"context"
"fmt"
"os"
"path/filepath"
"github.com/containernetworking/cni/pkg/types"
)
func delegateCommon(expectedCommand, delegatePlugin string, exec Exec) (string, Exec, error) {
func delegateCommon(delegatePlugin string, exec Exec) (string, Exec, error) {
if exec == nil {
exec = defaultExec
}
if os.Getenv("CNI_COMMAND") != expectedCommand {
return "", nil, fmt.Errorf("CNI_COMMAND is not " + expectedCommand)
}
paths := filepath.SplitList(os.Getenv("CNI_PATH"))
pluginPath, err := exec.FindInPath(delegatePlugin, paths)
if err != nil {
@ -44,32 +39,42 @@ func delegateCommon(expectedCommand, delegatePlugin string, exec Exec) (string,
// DelegateAdd calls the given delegate plugin with the CNI ADD action and
// JSON configuration
func DelegateAdd(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) (types.Result, error) {
pluginPath, realExec, err := delegateCommon("ADD", delegatePlugin, exec)
pluginPath, realExec, err := delegateCommon(delegatePlugin, exec)
if err != nil {
return nil, err
}
return ExecPluginWithResult(ctx, pluginPath, netconf, ArgsFromEnv(), realExec)
// DelegateAdd will override the original "CNI_COMMAND" env from process with ADD
return ExecPluginWithResult(ctx, pluginPath, netconf, delegateArgs("ADD"), realExec)
}
// DelegateCheck calls the given delegate plugin with the CNI CHECK action and
// JSON configuration
func DelegateCheck(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error {
pluginPath, realExec, err := delegateCommon("CHECK", delegatePlugin, exec)
pluginPath, realExec, err := delegateCommon(delegatePlugin, exec)
if err != nil {
return err
}
return ExecPluginWithoutResult(ctx, pluginPath, netconf, ArgsFromEnv(), realExec)
// DelegateCheck will override the original CNI_COMMAND env from process with CHECK
return ExecPluginWithoutResult(ctx, pluginPath, netconf, delegateArgs("CHECK"), realExec)
}
// DelegateDel calls the given delegate plugin with the CNI DEL action and
// JSON configuration
func DelegateDel(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error {
pluginPath, realExec, err := delegateCommon("DEL", delegatePlugin, exec)
pluginPath, realExec, err := delegateCommon(delegatePlugin, exec)
if err != nil {
return err
}
return ExecPluginWithoutResult(ctx, pluginPath, netconf, ArgsFromEnv(), realExec)
// DelegateDel will override the original CNI_COMMAND env from process with DEL
return ExecPluginWithoutResult(ctx, pluginPath, netconf, delegateArgs("DEL"), realExec)
}
// return CNIArgs used by delegation
func delegateArgs(action string) *DelegateArgs {
return &DelegateArgs{
Command: action,
}
}

View File

@ -48,9 +48,13 @@ func (e *Error) Error() string {
// IsNotExist returns true if the error is due to the chain or rule not existing
func (e *Error) IsNotExist() bool {
return e.ExitStatus() == 1 &&
(e.msg == fmt.Sprintf("%s: Bad rule (does a matching rule exist in that chain?).\n", getIptablesCommand(e.proto)) ||
e.msg == fmt.Sprintf("%s: No chain/target/match by that name.\n", getIptablesCommand(e.proto)))
if e.ExitStatus() != 1 {
return false
}
cmdIptables := getIptablesCommand(e.proto)
msgNoRuleExist := fmt.Sprintf("%s: Bad rule (does a matching rule exist in that chain?).\n", cmdIptables)
msgNoChainExist := fmt.Sprintf("%s: No chain/target/match by that name.\n", cmdIptables)
return strings.Contains(e.msg, msgNoRuleExist) || strings.Contains(e.msg, msgNoChainExist)
}
// Protocol to differentiate between IPv4 and IPv6
@ -101,7 +105,13 @@ func NewWithProtocol(proto Protocol) (*IPTables, error) {
return nil, err
}
vstring, err := getIptablesVersionString(path)
if err != nil {
return nil, fmt.Errorf("could not get iptables version: %v", err)
}
v1, v2, v3, mode, err := extractIptablesVersion(vstring)
if err != nil {
return nil, fmt.Errorf("failed to extract iptables version from [%s]: %v", vstring, err)
}
checkPresent, waitPresent, randomFullyPresent := getIptablesCommandSupport(v1, v2, v3)

View File

@ -1,23 +0,0 @@
# 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

191
vendor/github.com/juju/errors/LICENSE generated vendored
View File

@ -1,191 +0,0 @@
All files in this repository are licensed as follows. If you contribute
to this repository, it is assumed that you license your contribution
under the same license unless you state otherwise.
All files Copyright (C) 2015 Canonical Ltd. unless otherwise specified in the file.
This software is licensed under the LGPLv3, included below.
As a special exception to the GNU Lesser General Public License version 3
("LGPL3"), the copyright holders of this Library give you permission to
convey to a third party a Combined Work that links statically or dynamically
to this Library without providing any Minimal Corresponding Source or
Minimal Application Code as set out in 4d or providing the installation
information set out in section 4e, provided that you comply with the other
provisions of LGPL3 and provided that you meet, for the Application the
terms and conditions of the license(s) which apply to the Application.
Except as stated in this special exception, the provisions of LGPL3 will
continue to comply in full to this Library. If you modify this Library, you
may apply this exception to your version of this Library, but you are not
obliged to do so. If you do not wish to do so, delete this exception
statement from your version. This exception does not (and cannot) modify any
license terms which apply to the Application, with which you must still
comply.
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

View File

@ -1,24 +0,0 @@
PROJECT := github.com/juju/errors
.PHONY: check-licence check-go check docs
check: check-licence check-go
go test $(PROJECT)/...
check-licence:
@(fgrep -rl "Licensed under the LGPLv3" --exclude *.s .;\
fgrep -rl "MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT" --exclude *.s .;\
find . -name "*.go") | sed -e 's,\./,,' | sort | uniq -u | \
xargs -I {} echo FAIL: licence missed: {}
check-go:
$(eval GOFMT := $(strip $(shell gofmt -l .| sed -e "s/^/ /g")))
@(if [ x$(GOFMT) != x"" ]; then \
echo go fmt is sad: $(GOFMT); \
exit 1; \
fi )
@(go tool vet -all -composites=false -copylocks=false .)
docs:
godoc2md github.com/juju/errors > README.md
sed -i 's|\[godoc-link-here\]|[![GoDoc](https://godoc.org/github.com/juju/errors?status.svg)](https://godoc.org/github.com/juju/errors)|' README.md

View File

@ -1,707 +0,0 @@
# errors
import "github.com/juju/errors"
[![GoDoc](https://godoc.org/github.com/juju/errors?status.svg)](https://godoc.org/github.com/juju/errors)
The juju/errors provides an easy way to annotate errors without losing the
orginal error context.
The exported `New` and `Errorf` functions are designed to replace the
`errors.New` and `fmt.Errorf` functions respectively. The same underlying
error is there, but the package also records the location at which the error
was created.
A primary use case for this library is to add extra context any time an
error is returned from a function.
if err := SomeFunc(); err != nil {
return err
}
This instead becomes:
if err := SomeFunc(); err != nil {
return errors.Trace(err)
}
which just records the file and line number of the Trace call, or
if err := SomeFunc(); err != nil {
return errors.Annotate(err, "more context")
}
which also adds an annotation to the error.
When you want to check to see if an error is of a particular type, a helper
function is normally exported by the package that returned the error, like the
`os` package does. The underlying cause of the error is available using the
`Cause` function.
os.IsNotExist(errors.Cause(err))
The result of the `Error()` call on an annotated error is the annotations joined
with colons, then the result of the `Error()` method for the underlying error
that was the cause.
err := errors.Errorf("original")
err = errors.Annotatef(err, "context")
err = errors.Annotatef(err, "more context")
err.Error() -> "more context: context: original"
Obviously recording the file, line and functions is not very useful if you
cannot get them back out again.
errors.ErrorStack(err)
will return something like:
first error
github.com/juju/errors/annotation_test.go:193:
github.com/juju/errors/annotation_test.go:194: annotation
github.com/juju/errors/annotation_test.go:195:
github.com/juju/errors/annotation_test.go:196: more context
github.com/juju/errors/annotation_test.go:197:
The first error was generated by an external system, so there was no location
associated. The second, fourth, and last lines were generated with Trace calls,
and the other two through Annotate.
Sometimes when responding to an error you want to return a more specific error
for the situation.
if err := FindField(field); err != nil {
return errors.Wrap(err, errors.NotFoundf(field))
}
This returns an error where the complete error stack is still available, and
`errors.Cause()` will return the `NotFound` error.
## func AlreadyExistsf
``` go
func AlreadyExistsf(format string, args ...interface{}) error
```
AlreadyExistsf returns an error which satisfies IsAlreadyExists().
## func Annotate
``` go
func Annotate(other error, message string) error
```
Annotate is used to add extra context to an existing error. The location of
the Annotate call is recorded with the annotations. The file, line and
function are also recorded.
For example:
if err := SomeFunc(); err != nil {
return errors.Annotate(err, "failed to frombulate")
}
## func Annotatef
``` go
func Annotatef(other error, format string, args ...interface{}) error
```
Annotatef is used to add extra context to an existing error. The location of
the Annotate call is recorded with the annotations. The file, line and
function are also recorded.
For example:
if err := SomeFunc(); err != nil {
return errors.Annotatef(err, "failed to frombulate the %s", arg)
}
## func BadRequestf
``` go
func BadRequestf(format string, args ...interface{}) error
```
BadRequestf returns an error which satisfies IsBadRequest().
## func Cause
``` go
func Cause(err error) error
```
Cause returns the cause of the given error. This will be either the
original error, or the result of a Wrap or Mask call.
Cause is the usual way to diagnose errors that may have been wrapped by
the other errors functions.
## func DeferredAnnotatef
``` go
func DeferredAnnotatef(err *error, format string, args ...interface{})
```
DeferredAnnotatef annotates the given error (when it is not nil) with the given
format string and arguments (like fmt.Sprintf). If *err is nil, DeferredAnnotatef
does nothing. This method is used in a defer statement in order to annotate any
resulting error with the same message.
For example:
defer DeferredAnnotatef(&err, "failed to frombulate the %s", arg)
## func Details
``` go
func Details(err error) string
```
Details returns information about the stack of errors wrapped by err, in
the format:
[{filename:99: error one} {otherfile:55: cause of error one}]
This is a terse alternative to ErrorStack as it returns a single line.
## func ErrorStack
``` go
func ErrorStack(err error) string
```
ErrorStack returns a string representation of the annotated error. If the
error passed as the parameter is not an annotated error, the result is
simply the result of the Error() method on that error.
If the error is an annotated error, a multi-line string is returned where
each line represents one entry in the annotation stack. The full filename
from the call stack is used in the output.
first error
github.com/juju/errors/annotation_test.go:193:
github.com/juju/errors/annotation_test.go:194: annotation
github.com/juju/errors/annotation_test.go:195:
github.com/juju/errors/annotation_test.go:196: more context
github.com/juju/errors/annotation_test.go:197:
## func Errorf
``` go
func Errorf(format string, args ...interface{}) error
```
Errorf creates a new annotated error and records the location that the
error is created. This should be a drop in replacement for fmt.Errorf.
For example:
return errors.Errorf("validation failed: %s", message)
## func Forbiddenf
``` go
func Forbiddenf(format string, args ...interface{}) error
```
Forbiddenf returns an error which satistifes IsForbidden()
## func IsAlreadyExists
``` go
func IsAlreadyExists(err error) bool
```
IsAlreadyExists reports whether the error was created with
AlreadyExistsf() or NewAlreadyExists().
## func IsBadRequest
``` go
func IsBadRequest(err error) bool
```
IsBadRequest reports whether err was created with BadRequestf() or
NewBadRequest().
## func IsForbidden
``` go
func IsForbidden(err error) bool
```
IsForbidden reports whether err was created with Forbiddenf() or
NewForbidden().
## func IsMethodNotAllowed
``` go
func IsMethodNotAllowed(err error) bool
```
IsMethodNotAllowed reports whether err was created with MethodNotAllowedf() or
NewMethodNotAllowed().
## func IsNotAssigned
``` go
func IsNotAssigned(err error) bool
```
IsNotAssigned reports whether err was created with NotAssignedf() or
NewNotAssigned().
## func IsNotFound
``` go
func IsNotFound(err error) bool
```
IsNotFound reports whether err was created with NotFoundf() or
NewNotFound().
## func IsNotImplemented
``` go
func IsNotImplemented(err error) bool
```
IsNotImplemented reports whether err was created with
NotImplementedf() or NewNotImplemented().
## func IsNotProvisioned
``` go
func IsNotProvisioned(err error) bool
```
IsNotProvisioned reports whether err was created with NotProvisionedf() or
NewNotProvisioned().
## func IsNotSupported
``` go
func IsNotSupported(err error) bool
```
IsNotSupported reports whether the error was created with
NotSupportedf() or NewNotSupported().
## func IsNotValid
``` go
func IsNotValid(err error) bool
```
IsNotValid reports whether the error was created with NotValidf() or
NewNotValid().
## func IsUnauthorized
``` go
func IsUnauthorized(err error) bool
```
IsUnauthorized reports whether err was created with Unauthorizedf() or
NewUnauthorized().
## func IsUserNotFound
``` go
func IsUserNotFound(err error) bool
```
IsUserNotFound reports whether err was created with UserNotFoundf() or
NewUserNotFound().
## func Mask
``` go
func Mask(other error) error
```
Mask hides the underlying error type, and records the location of the masking.
## func Maskf
``` go
func Maskf(other error, format string, args ...interface{}) error
```
Mask masks the given error with the given format string and arguments (like
fmt.Sprintf), returning a new error that maintains the error stack, but
hides the underlying error type. The error string still contains the full
annotations. If you want to hide the annotations, call Wrap.
## func MethodNotAllowedf
``` go
func MethodNotAllowedf(format string, args ...interface{}) error
```
MethodNotAllowedf returns an error which satisfies IsMethodNotAllowed().
## func New
``` go
func New(message string) error
```
New is a drop in replacement for the standard library errors module that records
the location that the error is created.
For example:
return errors.New("validation failed")
## func NewAlreadyExists
``` go
func NewAlreadyExists(err error, msg string) error
```
NewAlreadyExists returns an error which wraps err and satisfies
IsAlreadyExists().
## func NewBadRequest
``` go
func NewBadRequest(err error, msg string) error
```
NewBadRequest returns an error which wraps err that satisfies
IsBadRequest().
## func NewForbidden
``` go
func NewForbidden(err error, msg string) error
```
NewForbidden returns an error which wraps err that satisfies
IsForbidden().
## func NewMethodNotAllowed
``` go
func NewMethodNotAllowed(err error, msg string) error
```
NewMethodNotAllowed returns an error which wraps err that satisfies
IsMethodNotAllowed().
## func NewNotAssigned
``` go
func NewNotAssigned(err error, msg string) error
```
NewNotAssigned returns an error which wraps err that satisfies
IsNotAssigned().
## func NewNotFound
``` go
func NewNotFound(err error, msg string) error
```
NewNotFound returns an error which wraps err that satisfies
IsNotFound().
## func NewNotImplemented
``` go
func NewNotImplemented(err error, msg string) error
```
NewNotImplemented returns an error which wraps err and satisfies
IsNotImplemented().
## func NewNotProvisioned
``` go
func NewNotProvisioned(err error, msg string) error
```
NewNotProvisioned returns an error which wraps err that satisfies
IsNotProvisioned().
## func NewNotSupported
``` go
func NewNotSupported(err error, msg string) error
```
NewNotSupported returns an error which wraps err and satisfies
IsNotSupported().
## func NewNotValid
``` go
func NewNotValid(err error, msg string) error
```
NewNotValid returns an error which wraps err and satisfies IsNotValid().
## func NewUnauthorized
``` go
func NewUnauthorized(err error, msg string) error
```
NewUnauthorized returns an error which wraps err and satisfies
IsUnauthorized().
## func NewUserNotFound
``` go
func NewUserNotFound(err error, msg string) error
```
NewUserNotFound returns an error which wraps err and satisfies
IsUserNotFound().
## func NotAssignedf
``` go
func NotAssignedf(format string, args ...interface{}) error
```
NotAssignedf returns an error which satisfies IsNotAssigned().
## func NotFoundf
``` go
func NotFoundf(format string, args ...interface{}) error
```
NotFoundf returns an error which satisfies IsNotFound().
## func NotImplementedf
``` go
func NotImplementedf(format string, args ...interface{}) error
```
NotImplementedf returns an error which satisfies IsNotImplemented().
## func NotProvisionedf
``` go
func NotProvisionedf(format string, args ...interface{}) error
```
NotProvisionedf returns an error which satisfies IsNotProvisioned().
## func NotSupportedf
``` go
func NotSupportedf(format string, args ...interface{}) error
```
NotSupportedf returns an error which satisfies IsNotSupported().
## func NotValidf
``` go
func NotValidf(format string, args ...interface{}) error
```
NotValidf returns an error which satisfies IsNotValid().
## func Trace
``` go
func Trace(other error) error
```
Trace adds the location of the Trace call to the stack. The Cause of the
resulting error is the same as the error parameter. If the other error is
nil, the result will be nil.
For example:
if err := SomeFunc(); err != nil {
return errors.Trace(err)
}
## func Unauthorizedf
``` go
func Unauthorizedf(format string, args ...interface{}) error
```
Unauthorizedf returns an error which satisfies IsUnauthorized().
## func UserNotFoundf
``` go
func UserNotFoundf(format string, args ...interface{}) error
```
UserNotFoundf returns an error which satisfies IsUserNotFound().
## func Wrap
``` go
func Wrap(other, newDescriptive error) error
```
Wrap changes the Cause of the error. The location of the Wrap call is also
stored in the error stack.
For example:
if err := SomeFunc(); err != nil {
newErr := &packageError{"more context", private_value}
return errors.Wrap(err, newErr)
}
## func Wrapf
``` go
func Wrapf(other, newDescriptive error, format string, args ...interface{}) error
```
Wrapf changes the Cause of the error, and adds an annotation. The location
of the Wrap call is also stored in the error stack.
For example:
if err := SomeFunc(); err != nil {
return errors.Wrapf(err, simpleErrorType, "invalid value %q", value)
}
## type Err
``` go
type Err struct {
// contains filtered or unexported fields
}
```
Err holds a description of an error along with information about
where the error was created.
It may be embedded in custom error types to add extra information that
this errors package can understand.
### func NewErr
``` go
func NewErr(format string, args ...interface{}) Err
```
NewErr is used to return an Err for the purpose of embedding in other
structures. The location is not specified, and needs to be set with a call
to SetLocation.
For example:
type FooError struct {
errors.Err
code int
}
func NewFooError(code int) error {
err := &FooError{errors.NewErr("foo"), code}
err.SetLocation(1)
return err
}
### func NewErrWithCause
``` go
func NewErrWithCause(other error, format string, args ...interface{}) Err
```
NewErrWithCause is used to return an Err with case by other error for the purpose of embedding in other
structures. The location is not specified, and needs to be set with a call
to SetLocation.
For example:
type FooError struct {
errors.Err
code int
}
func (e *FooError) Annotate(format string, args ...interface{}) error {
err := &FooError{errors.NewErrWithCause(e.Err, format, args...), e.code}
err.SetLocation(1)
return err
})
### func (\*Err) Cause
``` go
func (e *Err) Cause() error
```
The Cause of an error is the most recent error in the error stack that
meets one of these criteria: the original error that was raised; the new
error that was passed into the Wrap function; the most recently masked
error; or nil if the error itself is considered the Cause. Normally this
method is not invoked directly, but instead through the Cause stand alone
function.
### func (\*Err) Error
``` go
func (e *Err) Error() string
```
Error implements error.Error.
### func (\*Err) Format
``` go
func (e *Err) Format(s fmt.State, verb rune)
```
Format implements fmt.Formatter
When printing errors with %+v it also prints the stack trace.
%#v unsurprisingly will print the real underlying type.
### func (\*Err) Location
``` go
func (e *Err) Location() (filename string, line int)
```
Location is the file and line of where the error was most recently
created or annotated.
### func (\*Err) Message
``` go
func (e *Err) Message() string
```
Message returns the message stored with the most recent location. This is
the empty string if the most recent call was Trace, or the message stored
with Annotate or Mask.
### func (\*Err) SetLocation
``` go
func (e *Err) SetLocation(callDepth int)
```
SetLocation records the source location of the error at callDepth stack
frames above the call.
### func (\*Err) StackTrace
``` go
func (e *Err) StackTrace() []string
```
StackTrace returns one string for each location recorded in the stack of
errors. The first value is the originating error, with a line for each
other annotation or tracing of the error.
### func (\*Err) Underlying
``` go
func (e *Err) Underlying() error
```
Underlying returns the previous error in the error stack, if any. A client
should not ever really call this method. It is used to build the error
stack and should not be introspected by client calls. Or more
specifically, clients should not depend on anything but the `Cause` of an
error.
- - -
Generated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md)

View File

@ -1,5 +0,0 @@
github.com/juju/loggo git 8232ab8918d91c72af1a9fb94d3edbe31d88b790 2017-06-05T01:46:07Z
github.com/juju/testing git 72703b1e95eb8ce4737fd8a3d8496c6b0be280a6 2018-05-17T13:41:05Z
gopkg.in/check.v1 git 4f90aeace3a26ad7021961c297b22c42160c7b25 2016-01-05T16:49:36Z
gopkg.in/mgo.v2 git f2b6f6c918c452ad107eec89615f074e3bd80e33 2016-08-18T01:52:18Z
gopkg.in/yaml.v2 git 1be3d31502d6eabc0dd7ce5b0daab022e14a5538 2017-07-12T05:45:46Z
1 github.com/juju/loggo git 8232ab8918d91c72af1a9fb94d3edbe31d88b790 2017-06-05T01:46:07Z
2 github.com/juju/testing git 72703b1e95eb8ce4737fd8a3d8496c6b0be280a6 2018-05-17T13:41:05Z
3 gopkg.in/check.v1 git 4f90aeace3a26ad7021961c297b22c42160c7b25 2016-01-05T16:49:36Z
4 gopkg.in/mgo.v2 git f2b6f6c918c452ad107eec89615f074e3bd80e33 2016-08-18T01:52:18Z
5 gopkg.in/yaml.v2 git 1be3d31502d6eabc0dd7ce5b0daab022e14a5538 2017-07-12T05:45:46Z

81
vendor/github.com/juju/errors/doc.go generated vendored
View File

@ -1,81 +0,0 @@
// Copyright 2013, 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
/*
[godoc-link-here]
The juju/errors provides an easy way to annotate errors without losing the
orginal error context.
The exported `New` and `Errorf` functions are designed to replace the
`errors.New` and `fmt.Errorf` functions respectively. The same underlying
error is there, but the package also records the location at which the error
was created.
A primary use case for this library is to add extra context any time an
error is returned from a function.
if err := SomeFunc(); err != nil {
return err
}
This instead becomes:
if err := SomeFunc(); err != nil {
return errors.Trace(err)
}
which just records the file and line number of the Trace call, or
if err := SomeFunc(); err != nil {
return errors.Annotate(err, "more context")
}
which also adds an annotation to the error.
When you want to check to see if an error is of a particular type, a helper
function is normally exported by the package that returned the error, like the
`os` package does. The underlying cause of the error is available using the
`Cause` function.
os.IsNotExist(errors.Cause(err))
The result of the `Error()` call on an annotated error is the annotations joined
with colons, then the result of the `Error()` method for the underlying error
that was the cause.
err := errors.Errorf("original")
err = errors.Annotatef(err, "context")
err = errors.Annotatef(err, "more context")
err.Error() -> "more context: context: original"
Obviously recording the file, line and functions is not very useful if you
cannot get them back out again.
errors.ErrorStack(err)
will return something like:
first error
github.com/juju/errors/annotation_test.go:193:
github.com/juju/errors/annotation_test.go:194: annotation
github.com/juju/errors/annotation_test.go:195:
github.com/juju/errors/annotation_test.go:196: more context
github.com/juju/errors/annotation_test.go:197:
The first error was generated by an external system, so there was no location
associated. The second, fourth, and last lines were generated with Trace calls,
and the other two through Annotate.
Sometimes when responding to an error you want to return a more specific error
for the situation.
if err := FindField(field); err != nil {
return errors.Wrap(err, errors.NotFoundf(field))
}
This returns an error where the complete error stack is still available, and
`errors.Cause()` will return the `NotFound` error.
*/
package errors

View File

@ -1,172 +0,0 @@
// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package errors
import (
"fmt"
"reflect"
"runtime"
)
// Err holds a description of an error along with information about
// where the error was created.
//
// It may be embedded in custom error types to add extra information that
// this errors package can understand.
type Err struct {
// message holds an annotation of the error.
message string
// cause holds the cause of the error as returned
// by the Cause method.
cause error
// previous holds the previous error in the error stack, if any.
previous error
// file and line hold the source code location where the error was
// created.
file string
line int
}
// NewErr is used to return an Err for the purpose of embedding in other
// structures. The location is not specified, and needs to be set with a call
// to SetLocation.
//
// For example:
// type FooError struct {
// errors.Err
// code int
// }
//
// func NewFooError(code int) error {
// err := &FooError{errors.NewErr("foo"), code}
// err.SetLocation(1)
// return err
// }
func NewErr(format string, args ...interface{}) Err {
return Err{
message: fmt.Sprintf(format, args...),
}
}
// NewErrWithCause is used to return an Err with case by other error for the purpose of embedding in other
// structures. The location is not specified, and needs to be set with a call
// to SetLocation.
//
// For example:
// type FooError struct {
// errors.Err
// code int
// }
//
// func (e *FooError) Annotate(format string, args ...interface{}) error {
// err := &FooError{errors.NewErrWithCause(e.Err, format, args...), e.code}
// err.SetLocation(1)
// return err
// })
func NewErrWithCause(other error, format string, args ...interface{}) Err {
return Err{
message: fmt.Sprintf(format, args...),
cause: Cause(other),
previous: other,
}
}
// Location is the file and line of where the error was most recently
// created or annotated.
func (e *Err) Location() (filename string, line int) {
return e.file, e.line
}
// Underlying returns the previous error in the error stack, if any. A client
// should not ever really call this method. It is used to build the error
// stack and should not be introspected by client calls. Or more
// specifically, clients should not depend on anything but the `Cause` of an
// error.
func (e *Err) Underlying() error {
return e.previous
}
// The Cause of an error is the most recent error in the error stack that
// meets one of these criteria: the original error that was raised; the new
// error that was passed into the Wrap function; the most recently masked
// error; or nil if the error itself is considered the Cause. Normally this
// method is not invoked directly, but instead through the Cause stand alone
// function.
func (e *Err) Cause() error {
return e.cause
}
// Message returns the message stored with the most recent location. This is
// the empty string if the most recent call was Trace, or the message stored
// with Annotate or Mask.
func (e *Err) Message() string {
return e.message
}
// Error implements error.Error.
func (e *Err) Error() string {
// We want to walk up the stack of errors showing the annotations
// as long as the cause is the same.
err := e.previous
if !sameError(Cause(err), e.cause) && e.cause != nil {
err = e.cause
}
switch {
case err == nil:
return e.message
case e.message == "":
return err.Error()
}
return fmt.Sprintf("%s: %v", e.message, err)
}
// Format implements fmt.Formatter
// When printing errors with %+v it also prints the stack trace.
// %#v unsurprisingly will print the real underlying type.
func (e *Err) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
switch {
case s.Flag('+'):
fmt.Fprintf(s, "%s", ErrorStack(e))
return
case s.Flag('#'):
// avoid infinite recursion by wrapping e into a type
// that doesn't implement Formatter.
fmt.Fprintf(s, "%#v", (*unformatter)(e))
return
}
fallthrough
case 's':
fmt.Fprintf(s, "%s", e.Error())
}
}
// helper for Format
type unformatter Err
func (unformatter) Format() { /* break the fmt.Formatter interface */ }
// SetLocation records the source location of the error at callDepth stack
// frames above the call.
func (e *Err) SetLocation(callDepth int) {
_, file, line, _ := runtime.Caller(callDepth + 1)
e.file = trimGoPath(file)
e.line = line
}
// StackTrace returns one string for each location recorded in the stack of
// errors. The first value is the originating error, with a line for each
// other annotation or tracing of the error.
func (e *Err) StackTrace() []string {
return errorStack(e)
}
// Ideally we'd have a way to check identity, but deep equals will do.
func sameError(e1, e2 error) bool {
return reflect.DeepEqual(e1, e2)
}

View File

@ -1,333 +0,0 @@
// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package errors
import (
"fmt"
)
// wrap is a helper to construct an *wrapper.
func wrap(err error, format, suffix string, args ...interface{}) Err {
newErr := Err{
message: fmt.Sprintf(format+suffix, args...),
previous: err,
}
newErr.SetLocation(2)
return newErr
}
// timeout represents an error on timeout.
type timeout struct {
Err
}
// Timeoutf returns an error which satisfies IsTimeout().
func Timeoutf(format string, args ...interface{}) error {
return &timeout{wrap(nil, format, " timeout", args...)}
}
// NewTimeout returns an error which wraps err that satisfies
// IsTimeout().
func NewTimeout(err error, msg string) error {
return &timeout{wrap(err, msg, "")}
}
// IsTimeout reports whether err was created with Timeoutf() or
// NewTimeout().
func IsTimeout(err error) bool {
err = Cause(err)
_, ok := err.(*timeout)
return ok
}
// notFound represents an error when something has not been found.
type notFound struct {
Err
}
// NotFoundf returns an error which satisfies IsNotFound().
func NotFoundf(format string, args ...interface{}) error {
return &notFound{wrap(nil, format, " not found", args...)}
}
// NewNotFound returns an error which wraps err that satisfies
// IsNotFound().
func NewNotFound(err error, msg string) error {
return &notFound{wrap(err, msg, "")}
}
// IsNotFound reports whether err was created with NotFoundf() or
// NewNotFound().
func IsNotFound(err error) bool {
err = Cause(err)
_, ok := err.(*notFound)
return ok
}
// userNotFound represents an error when an inexistent user is looked up.
type userNotFound struct {
Err
}
// UserNotFoundf returns an error which satisfies IsUserNotFound().
func UserNotFoundf(format string, args ...interface{}) error {
return &userNotFound{wrap(nil, format, " user not found", args...)}
}
// NewUserNotFound returns an error which wraps err and satisfies
// IsUserNotFound().
func NewUserNotFound(err error, msg string) error {
return &userNotFound{wrap(err, msg, "")}
}
// IsUserNotFound reports whether err was created with UserNotFoundf() or
// NewUserNotFound().
func IsUserNotFound(err error) bool {
err = Cause(err)
_, ok := err.(*userNotFound)
return ok
}
// unauthorized represents an error when an operation is unauthorized.
type unauthorized struct {
Err
}
// Unauthorizedf returns an error which satisfies IsUnauthorized().
func Unauthorizedf(format string, args ...interface{}) error {
return &unauthorized{wrap(nil, format, "", args...)}
}
// NewUnauthorized returns an error which wraps err and satisfies
// IsUnauthorized().
func NewUnauthorized(err error, msg string) error {
return &unauthorized{wrap(err, msg, "")}
}
// IsUnauthorized reports whether err was created with Unauthorizedf() or
// NewUnauthorized().
func IsUnauthorized(err error) bool {
err = Cause(err)
_, ok := err.(*unauthorized)
return ok
}
// notImplemented represents an error when something is not
// implemented.
type notImplemented struct {
Err
}
// NotImplementedf returns an error which satisfies IsNotImplemented().
func NotImplementedf(format string, args ...interface{}) error {
return &notImplemented{wrap(nil, format, " not implemented", args...)}
}
// NewNotImplemented returns an error which wraps err and satisfies
// IsNotImplemented().
func NewNotImplemented(err error, msg string) error {
return &notImplemented{wrap(err, msg, "")}
}
// IsNotImplemented reports whether err was created with
// NotImplementedf() or NewNotImplemented().
func IsNotImplemented(err error) bool {
err = Cause(err)
_, ok := err.(*notImplemented)
return ok
}
// alreadyExists represents and error when something already exists.
type alreadyExists struct {
Err
}
// AlreadyExistsf returns an error which satisfies IsAlreadyExists().
func AlreadyExistsf(format string, args ...interface{}) error {
return &alreadyExists{wrap(nil, format, " already exists", args...)}
}
// NewAlreadyExists returns an error which wraps err and satisfies
// IsAlreadyExists().
func NewAlreadyExists(err error, msg string) error {
return &alreadyExists{wrap(err, msg, "")}
}
// IsAlreadyExists reports whether the error was created with
// AlreadyExistsf() or NewAlreadyExists().
func IsAlreadyExists(err error) bool {
err = Cause(err)
_, ok := err.(*alreadyExists)
return ok
}
// notSupported represents an error when something is not supported.
type notSupported struct {
Err
}
// NotSupportedf returns an error which satisfies IsNotSupported().
func NotSupportedf(format string, args ...interface{}) error {
return &notSupported{wrap(nil, format, " not supported", args...)}
}
// NewNotSupported returns an error which wraps err and satisfies
// IsNotSupported().
func NewNotSupported(err error, msg string) error {
return &notSupported{wrap(err, msg, "")}
}
// IsNotSupported reports whether the error was created with
// NotSupportedf() or NewNotSupported().
func IsNotSupported(err error) bool {
err = Cause(err)
_, ok := err.(*notSupported)
return ok
}
// notValid represents an error when something is not valid.
type notValid struct {
Err
}
// NotValidf returns an error which satisfies IsNotValid().
func NotValidf(format string, args ...interface{}) error {
return &notValid{wrap(nil, format, " not valid", args...)}
}
// NewNotValid returns an error which wraps err and satisfies IsNotValid().
func NewNotValid(err error, msg string) error {
return &notValid{wrap(err, msg, "")}
}
// IsNotValid reports whether the error was created with NotValidf() or
// NewNotValid().
func IsNotValid(err error) bool {
err = Cause(err)
_, ok := err.(*notValid)
return ok
}
// notProvisioned represents an error when something is not yet provisioned.
type notProvisioned struct {
Err
}
// NotProvisionedf returns an error which satisfies IsNotProvisioned().
func NotProvisionedf(format string, args ...interface{}) error {
return &notProvisioned{wrap(nil, format, " not provisioned", args...)}
}
// NewNotProvisioned returns an error which wraps err that satisfies
// IsNotProvisioned().
func NewNotProvisioned(err error, msg string) error {
return &notProvisioned{wrap(err, msg, "")}
}
// IsNotProvisioned reports whether err was created with NotProvisionedf() or
// NewNotProvisioned().
func IsNotProvisioned(err error) bool {
err = Cause(err)
_, ok := err.(*notProvisioned)
return ok
}
// notAssigned represents an error when something is not yet assigned to
// something else.
type notAssigned struct {
Err
}
// NotAssignedf returns an error which satisfies IsNotAssigned().
func NotAssignedf(format string, args ...interface{}) error {
return &notAssigned{wrap(nil, format, " not assigned", args...)}
}
// NewNotAssigned returns an error which wraps err that satisfies
// IsNotAssigned().
func NewNotAssigned(err error, msg string) error {
return &notAssigned{wrap(err, msg, "")}
}
// IsNotAssigned reports whether err was created with NotAssignedf() or
// NewNotAssigned().
func IsNotAssigned(err error) bool {
err = Cause(err)
_, ok := err.(*notAssigned)
return ok
}
// badRequest represents an error when a request has bad parameters.
type badRequest struct {
Err
}
// BadRequestf returns an error which satisfies IsBadRequest().
func BadRequestf(format string, args ...interface{}) error {
return &badRequest{wrap(nil, format, "", args...)}
}
// NewBadRequest returns an error which wraps err that satisfies
// IsBadRequest().
func NewBadRequest(err error, msg string) error {
return &badRequest{wrap(err, msg, "")}
}
// IsBadRequest reports whether err was created with BadRequestf() or
// NewBadRequest().
func IsBadRequest(err error) bool {
err = Cause(err)
_, ok := err.(*badRequest)
return ok
}
// methodNotAllowed represents an error when an HTTP request
// is made with an inappropriate method.
type methodNotAllowed struct {
Err
}
// MethodNotAllowedf returns an error which satisfies IsMethodNotAllowed().
func MethodNotAllowedf(format string, args ...interface{}) error {
return &methodNotAllowed{wrap(nil, format, "", args...)}
}
// NewMethodNotAllowed returns an error which wraps err that satisfies
// IsMethodNotAllowed().
func NewMethodNotAllowed(err error, msg string) error {
return &methodNotAllowed{wrap(err, msg, "")}
}
// IsMethodNotAllowed reports whether err was created with MethodNotAllowedf() or
// NewMethodNotAllowed().
func IsMethodNotAllowed(err error) bool {
err = Cause(err)
_, ok := err.(*methodNotAllowed)
return ok
}
// forbidden represents an error when a request cannot be completed because of
// missing privileges
type forbidden struct {
Err
}
// Forbiddenf returns an error which satistifes IsForbidden()
func Forbiddenf(format string, args ...interface{}) error {
return &forbidden{wrap(nil, format, "", args...)}
}
// NewForbidden returns an error which wraps err that satisfies
// IsForbidden().
func NewForbidden(err error, msg string) error {
return &forbidden{wrap(err, msg, "")}
}
// IsForbidden reports whether err was created with Forbiddenf() or
// NewForbidden().
func IsForbidden(err error) bool {
err = Cause(err)
_, ok := err.(*forbidden)
return ok
}

View File

@ -1,330 +0,0 @@
// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package errors
import (
"fmt"
"strings"
)
// New is a drop in replacement for the standard library errors module that records
// the location that the error is created.
//
// For example:
// return errors.New("validation failed")
//
func New(message string) error {
err := &Err{message: message}
err.SetLocation(1)
return err
}
// Errorf creates a new annotated error and records the location that the
// error is created. This should be a drop in replacement for fmt.Errorf.
//
// For example:
// return errors.Errorf("validation failed: %s", message)
//
func Errorf(format string, args ...interface{}) error {
err := &Err{message: fmt.Sprintf(format, args...)}
err.SetLocation(1)
return err
}
// Trace adds the location of the Trace call to the stack. The Cause of the
// resulting error is the same as the error parameter. If the other error is
// nil, the result will be nil.
//
// For example:
// if err := SomeFunc(); err != nil {
// return errors.Trace(err)
// }
//
func Trace(other error) error {
if other == nil {
return nil
}
err := &Err{previous: other, cause: Cause(other)}
err.SetLocation(1)
return err
}
// Annotate is used to add extra context to an existing error. The location of
// the Annotate call is recorded with the annotations. The file, line and
// function are also recorded.
//
// For example:
// if err := SomeFunc(); err != nil {
// return errors.Annotate(err, "failed to frombulate")
// }
//
func Annotate(other error, message string) error {
if other == nil {
return nil
}
err := &Err{
previous: other,
cause: Cause(other),
message: message,
}
err.SetLocation(1)
return err
}
// Annotatef is used to add extra context to an existing error. The location of
// the Annotate call is recorded with the annotations. The file, line and
// function are also recorded.
//
// For example:
// if err := SomeFunc(); err != nil {
// return errors.Annotatef(err, "failed to frombulate the %s", arg)
// }
//
func Annotatef(other error, format string, args ...interface{}) error {
if other == nil {
return nil
}
err := &Err{
previous: other,
cause: Cause(other),
message: fmt.Sprintf(format, args...),
}
err.SetLocation(1)
return err
}
// DeferredAnnotatef annotates the given error (when it is not nil) with the given
// format string and arguments (like fmt.Sprintf). If *err is nil, DeferredAnnotatef
// does nothing. This method is used in a defer statement in order to annotate any
// resulting error with the same message.
//
// For example:
//
// defer DeferredAnnotatef(&err, "failed to frombulate the %s", arg)
//
func DeferredAnnotatef(err *error, format string, args ...interface{}) {
if *err == nil {
return
}
newErr := &Err{
message: fmt.Sprintf(format, args...),
cause: Cause(*err),
previous: *err,
}
newErr.SetLocation(1)
*err = newErr
}
// Wrap changes the Cause of the error. The location of the Wrap call is also
// stored in the error stack.
//
// For example:
// if err := SomeFunc(); err != nil {
// newErr := &packageError{"more context", private_value}
// return errors.Wrap(err, newErr)
// }
//
func Wrap(other, newDescriptive error) error {
err := &Err{
previous: other,
cause: newDescriptive,
}
err.SetLocation(1)
return err
}
// Wrapf changes the Cause of the error, and adds an annotation. The location
// of the Wrap call is also stored in the error stack.
//
// For example:
// if err := SomeFunc(); err != nil {
// return errors.Wrapf(err, simpleErrorType, "invalid value %q", value)
// }
//
func Wrapf(other, newDescriptive error, format string, args ...interface{}) error {
err := &Err{
message: fmt.Sprintf(format, args...),
previous: other,
cause: newDescriptive,
}
err.SetLocation(1)
return err
}
// Mask masks the given error with the given format string and arguments (like
// fmt.Sprintf), returning a new error that maintains the error stack, but
// hides the underlying error type. The error string still contains the full
// annotations. If you want to hide the annotations, call Wrap.
func Maskf(other error, format string, args ...interface{}) error {
if other == nil {
return nil
}
err := &Err{
message: fmt.Sprintf(format, args...),
previous: other,
}
err.SetLocation(1)
return err
}
// Mask hides the underlying error type, and records the location of the masking.
func Mask(other error) error {
if other == nil {
return nil
}
err := &Err{
previous: other,
}
err.SetLocation(1)
return err
}
// Cause returns the cause of the given error. This will be either the
// original error, or the result of a Wrap or Mask call.
//
// Cause is the usual way to diagnose errors that may have been wrapped by
// the other errors functions.
func Cause(err error) error {
var diag error
if err, ok := err.(causer); ok {
diag = err.Cause()
}
if diag != nil {
return diag
}
return err
}
type causer interface {
Cause() error
}
type wrapper interface {
// Message returns the top level error message,
// not including the message from the Previous
// error.
Message() string
// Underlying returns the Previous error, or nil
// if there is none.
Underlying() error
}
type locationer interface {
Location() (string, int)
}
var (
_ wrapper = (*Err)(nil)
_ locationer = (*Err)(nil)
_ causer = (*Err)(nil)
)
// Details returns information about the stack of errors wrapped by err, in
// the format:
//
// [{filename:99: error one} {otherfile:55: cause of error one}]
//
// This is a terse alternative to ErrorStack as it returns a single line.
func Details(err error) string {
if err == nil {
return "[]"
}
var s []byte
s = append(s, '[')
for {
s = append(s, '{')
if err, ok := err.(locationer); ok {
file, line := err.Location()
if file != "" {
s = append(s, fmt.Sprintf("%s:%d", file, line)...)
s = append(s, ": "...)
}
}
if cerr, ok := err.(wrapper); ok {
s = append(s, cerr.Message()...)
err = cerr.Underlying()
} else {
s = append(s, err.Error()...)
err = nil
}
s = append(s, '}')
if err == nil {
break
}
s = append(s, ' ')
}
s = append(s, ']')
return string(s)
}
// ErrorStack returns a string representation of the annotated error. If the
// error passed as the parameter is not an annotated error, the result is
// simply the result of the Error() method on that error.
//
// If the error is an annotated error, a multi-line string is returned where
// each line represents one entry in the annotation stack. The full filename
// from the call stack is used in the output.
//
// first error
// github.com/juju/errors/annotation_test.go:193:
// github.com/juju/errors/annotation_test.go:194: annotation
// github.com/juju/errors/annotation_test.go:195:
// github.com/juju/errors/annotation_test.go:196: more context
// github.com/juju/errors/annotation_test.go:197:
func ErrorStack(err error) string {
return strings.Join(errorStack(err), "\n")
}
func errorStack(err error) []string {
if err == nil {
return nil
}
// We want the first error first
var lines []string
for {
var buff []byte
if err, ok := err.(locationer); ok {
file, line := err.Location()
// Strip off the leading GOPATH/src path elements.
file = trimGoPath(file)
if file != "" {
buff = append(buff, fmt.Sprintf("%s:%d", file, line)...)
buff = append(buff, ": "...)
}
}
if cerr, ok := err.(wrapper); ok {
message := cerr.Message()
buff = append(buff, message...)
// If there is a cause for this error, and it is different to the cause
// of the underlying error, then output the error string in the stack trace.
var cause error
if err1, ok := err.(causer); ok {
cause = err1.Cause()
}
err = cerr.Underlying()
if cause != nil && !sameError(Cause(err), cause) {
if message != "" {
buff = append(buff, ": "...)
}
buff = append(buff, cause.Error()...)
}
} else {
buff = append(buff, err.Error()...)
err = nil
}
lines = append(lines, string(buff))
if err == nil {
break
}
}
// reverse the lines to get the original error, which was at the end of
// the list, back to the start.
var result []string
for i := len(lines); i > 0; i-- {
result = append(result, lines[i-1])
}
return result
}

View File

@ -1,19 +0,0 @@
// Copyright 2013, 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package errors
import (
"fmt"
"go/build"
"os"
"path/filepath"
"strings"
)
var goPath = build.Default.GOPATH
var srcDir = filepath.Join(goPath, "src")
func trimGoPath(filename string) string {
return strings.TrimPrefix(filename, fmt.Sprintf("%s%s", srcDir, string(os.PathSeparator)))
}

6
vendor/modules.txt vendored
View File

@ -24,7 +24,7 @@ github.com/Microsoft/hcsshim/internal/safefile
github.com/alexflint/go-filemutex
# github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44
github.com/buger/jsonparser
# github.com/containernetworking/cni v0.7.0
# github.com/containernetworking/cni v0.7.1
github.com/containernetworking/cni/pkg/types
github.com/containernetworking/cni/pkg/types/current
github.com/containernetworking/cni/pkg/invoke
@ -32,7 +32,7 @@ github.com/containernetworking/cni/pkg/skel
github.com/containernetworking/cni/pkg/version
github.com/containernetworking/cni/pkg/types/020
github.com/containernetworking/cni/libcni
# github.com/coreos/go-iptables v0.4.2
# github.com/coreos/go-iptables v0.4.5
github.com/coreos/go-iptables/iptables
# github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7
github.com/coreos/go-systemd/activation
@ -48,8 +48,6 @@ github.com/d2g/dhcp4server/leasepool/memorypool
github.com/godbus/dbus
# github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56
github.com/j-keck/arping
# github.com/juju/errors v0.0.0-20180806074554-22422dad46e1
github.com/juju/errors
# github.com/mattn/go-shellwords v1.0.3
github.com/mattn/go-shellwords
# github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b