Compare commits

..

219 Commits

Author SHA1 Message Date
Patryk Strusiewicz-Surmacki
e13bab99e5 tuning: revert values on delete (#540)
Values changed by Tuning plugin should be changed only for pod, therefore should be reverted when NIC is being moved from pod back to host.

Fixes: #493

Signed-off-by: Patryk Strusiewicz-Surmacki <patrykx.strusiewicz-surmacki@intel.com>
2020-12-09 18:16:52 +01:00
Casey Callendrello
3689d53adf Merge pull request #557 from squeed/go-1.15
Go 1.15
2020-12-09 18:15:52 +01:00
Casey Callendrello
680c6dd0b1 go mod tidy
Signed-off-by: Casey Callendrello <cdc@redhat.com>
2020-12-09 17:59:56 +01:00
Casey Callendrello
be19d786a0 bump to go 1.15
Signed-off-by: Casey Callendrello <cdc@redhat.com>
2020-12-09 17:59:33 +01:00
Dan Williams
001abd4f55 Merge pull request #555 from squeed/gh-actions
Add github build & test actions, disable travis.
2020-12-09 10:55:58 -06:00
Casey Callendrello
8d52c42f90 Add ability to trigger retests via comments
Signed-off-by: Casey Callendrello <cdc@redhat.com>
2020-12-09 17:46:29 +01:00
Casey Callendrello
3ae85c1093 pkg/ns: fix test case to tolerate pids going away.
Signed-off-by: Casey Callendrello <cdc@redhat.com>
2020-12-09 17:46:29 +01:00
Casey Callendrello
25704f9372 Add github build & test actions
Signed-off-by: Casey Callendrello <cdc@redhat.com>
2020-12-09 17:46:25 +01:00
Dan Williams
7e59bac059 Merge pull request #556 from dcbw/fix-bridge-v6ll-test
bridge: fix testcase to check addresses we care about
2020-12-09 10:25:46 -06:00
Dan Williams
eb31403ac7 bridge: fix testcase to check addresses we care about
Instead of checking the total number of addresses, which might vary
depending on the IPv6 Privacy Address settings of the distro being
used, just check that we have the number of non-link-local addresses
we expect.

Signed-off-by: Dan Williams <dcbw@redhat.com>
2020-12-09 09:36:37 -06:00
Casey Callendrello
336ba52542 Remove travis.
Signed-off-by: Casey Callendrello <cdc@redhat.com>
2020-12-08 16:34:24 +01:00
Casey Callendrello
b47d178ae0 vendor: bump ginkgo, gover
Signed-off-by: Casey Callendrello <cdc@redhat.com>
2020-12-08 16:33:10 +01:00
Casey Callendrello
509d645ee9 Merge pull request #553 from aojea/conntrack
Delete stale UDP conntrack entries when adding new Portmaps to containers
2020-11-25 17:09:43 +01:00
Antonio Ojea
108c2aebd4 portmap plugin should flush previous udp connections
conntrack does not have any way to track UDP connections, so
it relies on timers to delete a connection.
The problem is that UDP is connectionless, so a client will keep
sending traffic despite the server has gone, thus renewing the
conntrack entries.
Pods that use portmaps to expose UDP services need to flush the existing
conntrack entries on the port exposed when they are created,
otherwise conntrack will keep sending the traffic to the previous IP
until the connection age (the client stops sending traffic)

Signed-off-by: Antonio Ojea <aojea@redhat.com>
2020-11-23 16:29:52 +01:00
Nate W
cccf5395e8 Updating plugin README.md files (#549)
Removing content and pointing at the new website as a part of the CNI Documentation migration.

Signed-off-by: Nate W <4453979+nate-double-u@users.noreply.github.com>
2020-11-18 17:38:45 +01:00
Antonio Ojea
c41c78b600 update netlink dependencies
Signed-off-by: Antonio Ojea <aojea@redhat.com>
2020-11-17 23:32:35 +01:00
Casey Callendrello
8aad9739d8 Merge pull request #536 from fedepaol/customtable
VRF CNI: Add an optional table parameter.
2020-11-11 17:57:54 +01:00
Casey Callendrello
b7af044cdc Merge pull request #546 from fedepaol/vrffixsupporter
VRF: extend supported version to 0.3.1 too
2020-11-11 17:55:36 +01:00
Dan Williams
95248d8490 Merge pull request #551 from xieyanker/patch-1
dhcp: fix example configuration
2020-11-11 10:52:14 -06:00
xieyanker
50a3aa6536 Xdhcp: fix example configuration
The current cni config has an extra comma and cannot be parsed normally, the kubelet will report an error as follows:
"Error loading CNI config file: error parsing configuration: invalid character '}' looking for beginning of object key string"

Signed-off-by: xieyanker <xjsisnice@gmail.com>
2020-11-10 07:48:36 +00:00
Federico Paolinelli
98c621abe6 VRF: extend supported version to 0.3.1 too.
The e2e tests already covers both versions, and since the plugin is
meant to be used in chains, this will augment the scope of the plugins
it can be used with.

Signed-off-by: Federico Paolinelli <fpaoline@redhat.com>
2020-11-03 13:09:03 +01:00
Federico Paolinelli
b34402abd3 VRF CNI: Add an optional table parameter.
When specified from the user, the VRF will get assigned to the given
tableid instead of having the CNI to choose for a free one.

Signed-off-by: Federico Paolinelli <fpaoline@redhat.com>
2020-11-03 10:43:27 +01:00
Bryan Boreham
ccd872bd7a Merge pull request #535 from fedepaol/vrfmoretests
VRF CNI: Additional tests
2020-10-28 15:34:55 +00:00
Federico Paolinelli
5fc309a699 Add more tests for the vrf cni plugin.
The new tests expand coverage, checking deletion, ip address handling,
0.4.0 compatibility, behaviour in case of multiple vrfs.

Signed-off-by: Federico Paolinelli <fpaoline@redhat.com>
2020-10-21 17:59:01 +02:00
Casey Callendrello
440dcc331b Merge pull request #525 from fedepaol/initialvrf
Introduce a new VRF CNI meta plugin (also bump netlink package to v1.1.0)
2020-10-21 17:49:09 +02:00
Federico Paolinelli
362f5d626a Update github.com/vishvananda/netlink to v1.1.0
Latest version allows to set a VRF device as master and not only
a bridge one.

Signed-off-by: Federico Paolinelli <fpaoline@redhat.com>
2020-10-14 17:40:50 +02:00
Federico Paolinelli
8d0d8a9547 Introduce a new VRF CNI meta plugin.
This plugin allows to create a VRF with the given name (or use the existing
one if any) in the target namespace, and to allocate the interface
to it.
VRFs make it possible to use multiple routing tables on the same namespace and
allows isolation among interfaces within the same namespace. On top of that, this
allow different interfaces to have overlapping CIDRs (or even addresses).

This is only useful in addition to other plugins.

The configuration is pretty simple and looks like:

{
    "type": "vrf",
    "vrfname": "blue"
}

Signed-off-by: Federico Paolinelli <fpaoline@redhat.com>
2020-10-14 17:40:50 +02:00
Casey Callendrello
a9abbaf19b Merge pull request #526 from fedepaol/bumpubuntu
Bump up ubuntu CI ubuntu version to bionic
2020-10-14 17:35:58 +02:00
Dan Williams
c4b8bccd2a Merge pull request #539 from squeed/arm-tests
Travis: run tests on arm64
2020-10-14 10:32:09 -05:00
Casey Callendrello
1a7f1bd8b1 Travis: run tests on arm64
Signed-off-by: Casey Callendrello <cdc@redhat.com>
2020-10-12 17:14:44 +02:00
Federico Paolinelli
8a6e96b3f0 Replace nc with the local echo client.
This makes the behaviour more consistent across platforms.

Signed-off-by: Federico Paolinelli <fpaoline@redhat.com>
2020-10-07 20:13:24 +02:00
Casey Callendrello
6df03d7937 Merge pull request #532 from dverbeir/flannel_ipam_routes
flannel: allow input ipam parameters as basis for delegate
2020-10-07 17:55:41 +02:00
Bryan Boreham
e1517e2498 Merge pull request #534 from s1061123/dev/add-default-ipvlan
ipvlan: make master config as optional
2020-10-07 16:29:56 +01:00
Federico Paolinelli
322790226b Add an echo client to be used instead of nc.
nc behaviour depends on the implementation version of what's on the current host.
Here we use our own client with stable behaviour.

Signed-off-by: Federico Paolinelli <fpaoline@redhat.com>
2020-10-02 15:56:27 +02:00
Federico Paolinelli
59baaa1546 Bump up the ubuntu version used in CI to bionic.
VRF support was introduced in ubuntu bionic, while it's missing in Xenial.

This also introduces a change in the behaviour of nc command.
On one hand, it requires a new line to send the buffer on the other side,
on the other it hangs waiting for new input.

To address this, a timeout was introduced to avoid the tests to hang,
plus the buffer sent is terminated with a new line character.

Signed-off-by: Federico Paolinelli <fpaoline@redhat.com>
2020-10-02 14:50:50 +02:00
David Verbeiren
9ce99d3f07 flannel: allow input ipam parameters as basis for delegate
This change allows providing an 'ipam' section as part of the
input network configuration for flannel. It is then used as
basis to construct the ipam parameters provided to the delegate.

All parameters from the input ipam are preserved except:
* 'subnet' which is set to the flannel host subnet
* 'routes' which is complemented by a route to the flannel
  network.

One use case of this feature is to allow adding back the routes
to the cluster services and/or to the hosts (HostPort) when
using isDefaultGateway=false. In that case, the bridge plugin
does not install a default route and, as a result, only pod-to-pod
connectivity would be available.

Example:
    {
      "name": "cbr0",
      "cniVersion": "0.3.1",
      "type": "flannel",
      "ipam": {
        "routes": [
          {
            "dst": "192.168.242.0/24"
          },
          {
            "dst": "10.96.0.0/12"
          }
        ],
        "unknown-param": "value"
      },
      "delegate": {
        "hairpinMode": true,
        "isDefaultGateway": false
      }
      ...
    }

This results in the following 'ipam' being provided to the delegate:
    {
      "routes" : [
        {
          "dst": "192.168.242.0/24"
        },
        {
          "dst": "10.96.0.0/12"
        },
        {
          "dst" : "10.1.0.0/16"
        }
      ],
      "subnet" : "10.1.17.0/24",
      "type" : "host-local"
      "unknown-param": "value"
    }

where "10.1.0.0/16" is the flannel network and "10.1.17.0/24" is
the host flannel subnet.

Note that this also allows setting a different ipam 'type' than
"host-local".

Signed-off-by: David Verbeiren <david.verbeiren@tessares.net>
2020-09-28 17:15:52 +02:00
Bryan Boreham
e78e6aa5b9 Merge pull request #529 from containernetworking/fix-windows-ginko
Remove extraneous test file in Windows plugin
2020-09-16 16:27:32 +01:00
Tomofumi Hayashi
08ff3b6413 ipvlan: make master config as optional
This change makes ipvlan master parameter optional.
Default to default route interface as macvlan does.

Signed-off-by: Tomofumi Hayashi <tohayash@redhat.com>
2020-09-16 22:05:42 +09:00
Bryan Boreham
1ea19f9213 Remove extraneous test file in Windows plugin
We already have a function to run all tests in the package, in netconf_suite_windows_test.go

Signed-off-by: Bryan Boreham <bjboreham@gmail.com>
2020-09-09 16:12:54 +00:00
Dan Williams
9b8de6a613 Merge pull request #449 from mars1024/bugfix/flannel_clean
flannel: remove net conf file after DEL succeed
2020-08-26 10:47:58 -05:00
Casey Callendrello
dacb671328 Merge pull request #523 from tnqn/ns-race
Fix race condition in GetCurrentNS
2020-08-26 17:41:51 +02:00
Quan Tian
799d3cbf4c Fix race condition in GetCurrentNS
In GetCurrentNS, If there is a context-switch between
getCurrentThreadNetNSPath and GetNS, another goroutine may execute in
the original thread and change its network namespace, then the original
goroutine would get the updated network namespace, which could lead to
unexpected behavior, especially when GetCurrentNS is used to get the
host network namespace in netNS.Do.

The added test has a chance to reproduce it with "-count=50".

The patch fixes it by locking the thread in GetCurrentNS.

Signed-off-by: Quan Tian <qtian@vmware.com>
2020-08-21 13:05:21 +08:00
Dan Williams
bd589992fb Merge pull request #521 from dcbw/go-113
Bump Go version to 1.13 and 1.14
2020-08-05 14:38:42 -05:00
Dan Williams
839d918e35 lo: CNI_IFNAME is no longer ignored
{
    "code": 4,
    "msg": "interface name contains / or : or whitespace characters"
}

Signed-off-by: Dan Williams <dcbw@redhat.com>
2020-08-05 14:23:06 -05:00
Dan Williams
c50490eb76 cni: bump to 0.8.0
Signed-off-by: Dan Williams <dcbw@redhat.com>
2020-08-05 14:12:09 -05:00
Dan Williams
01a8de9997 Bump Go version to 1.13 and 1.14
Signed-off-by: Dan Williams <dcbw@redhat.com>
2020-08-05 14:12:09 -05:00
Casey Callendrello
8a88f90f94 Merge pull request #520 from containernetworking/contact-info
Add contact info
2020-08-05 17:49:03 +02:00
Bryan Boreham
2b6cd5467f Add contact info
Signed-off-by: Bryan Boreham <bjboreham@gmail.com>
2020-08-05 15:39:36 +00:00
Dan Williams
3aadb402e4 Merge pull request #505 from xtreme-sameer-vohra/patch-1
Update firewall README.md
2020-08-05 10:26:27 -05:00
Sameer Vohra
021462563b Update firewall README.md CNI-ADMIN
Signed-off-by: Sameer Vohra <vohra.sam@gmail.com>
2020-08-04 15:35:39 -05:00
Dan Williams
d713ec692c Merge pull request #513 from mars1024/firewall_docs
firewall: fix some typos in docs
2020-07-22 10:07:06 -05:00
Bruce Ma
813f541d30 firewall: fix some typos in docs
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2020-07-22 19:38:24 +08:00
Bryan Boreham
6eb8e31d21 Merge pull request #509 from aojea/portmapDel
portmap should not perform deletions if not portMapping config received
2020-07-15 16:24:35 +01:00
Sameer Vohra
051452cdcf Update firewall README.md
Document `CNI-ADMIN` chain usage as well as `iptablesAdminChainName`

Signed-off-by: Sameer Vohra <vohra.sam@gmail.com>
2020-07-13 18:56:19 -05:00
Antonio Ojea
877602d627 portmap DEL noop if no portMappings present
if the runtime is not passing portMappings in the runtimeConfig,
then DEL is a noop.

This solves performance issues, when the portmap plugin is
executed multiple times, holding the iptables lock, despite
it does not have anything to delete.

Signed-off-by: Antonio Ojea <aojea@redhat.com>
2020-07-13 12:43:12 +02:00
Dan Williams
c90b165c6e Merge pull request #506 from dcbw/fw-admin-chain-comment
firewall: fix generate of admin chain comment
2020-07-08 09:50:41 -05:00
Bryan Boreham
28773dc925 Merge pull request #501 from aneeshkp/dhcp-timeout
Fix handling of delay in acquiring lease with stp turned on
2020-07-01 16:46:06 +01:00
Dan Williams
2bd04cb92f firewall: fix generate of admin chain comment
Signed-off-by: Dan Williams <dcbw@redhat.com>
2020-07-01 10:38:12 -05:00
Aneesh Puttur
d4775ecff5 Fix handling of delay in acquiring lease with stp turned on
Signed-off-by: Aneesh Puttur <aneeshputtur@gmail.com>
2020-06-29 13:15:00 -04:00
Casey Callendrello
e1f8f9bee5 Merge pull request #503 from ashish-billore/master
Update README.md
2020-06-24 17:41:35 +02:00
Ashish Billore
68a80bcf9b Update README.md
Updated "Notes" for minor fixes.
2020-06-19 15:39:28 +09:00
Casey Callendrello
1fb9793607 Merge pull request #487 from aojea/pmapHostIp
portmap: don't use unspecified address as iptables rule destination
2020-06-03 17:14:37 +02:00
Antonio Ojea
5cb3a5e897 portmap: don't use unspecified address as destination
It may happen that you want to map a port only in one IP family.
It can be achieved using the unspecified IP address of the
corresponding IP family as HostIP i.e.:

podman run --rm --name some-nginx -d -p 0.0.0.0:8080:80 nginx

The problem is that current implementation considers the
unspecified address valid and appends it to the iptables rule:

-A CNI-DN-60380cb3197c5457ed6ba -s 10.88.0.0/16
-d 0.0.0.0/32 -p tcp -m tcp --dport 8080 -j CNI-HOSTPORT-SETMARK

This rule is not forwarding the traffic to the mapped port.

We should use the unspecified address only to discriminate the IP
family of the port mapping, but not use it to filter the dst.

Signed-off-by: Antonio Ojea <antonio.ojea.garcia@gmail.com>
2020-05-27 22:39:00 +02:00
Casey Callendrello
b76fdd7c03 Merge pull request #486 from Metaswitch/bring-down-interfaces
host-device: Bring interfaces down before moving.
2020-05-27 17:37:31 +02:00
Casey Callendrello
67175607ad Merge pull request #480 from clinta/macvlan-mac
macvlan: set mac address from args
2020-05-27 17:33:30 +02:00
Clint Armstrong
b9560fd5c1 macvlan: set mac address from CNI_ARGS
This change sets the mac address if specified during the creation of the
macvlan interface. This is superior to setting it via the tuning plugin
because this ensures the mac address is set before an IP is set,
allowing a container to get a reserved IP address from DHCP.

Related #450

Signed-off-by: Clint Armstrong <clint@clintarmstrong.net>
2020-05-20 13:58:38 -04:00
cns
79192cb1f1 host-device: Bring interfaces down before moving.
When trying to move a master and slave interface into a container it is not
possible without first bringing the interfaces down. This change ensures
that the interface is set to down prior to trying to move the interface
into the container. This matches the behaviour on moving an interface out
of the container.

Signed-off-by: cns <christopher.swindle@metaswitch.com>
2020-05-17 20:46:43 +01:00
Piotr Skamruk
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
Casey Callendrello
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
Bryan Boreham
f7a2fc97e4 Merge pull request #460 from timyinshi/windowcontainer-new
modify the error url of windowscontainer
2020-04-29 16:58:32 +01:00
Piotr Skamruk
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
Lars Ekman
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
Piotr Skamruk
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
Casey Callendrello
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
Alban Bedel
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
Adrian Chiris
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
Vincent Boulineau
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
Michael C. Cambria
f4332fec59 Merge pull request #468 from hongli-my/fix-port-state
check bridge's port state
2020-04-08 11:58:56 -04:00
Casey Callendrello
ed16760739 Merge pull request #472 from ahenan/master
Reset the route flag before moving the rule
2020-04-08 17:55:59 +02:00
honglichang
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
ahenan
2a48d68937 Reset the route flag before moving the rule
Signed-off-by: ahenan <ahenan00@gmail.com>
2020-04-06 17:12:45 +02:00
Casey Callendrello
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
Bruce Ma
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
Bruce Ma
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
Piotr Skamruk
ca419073e4 modify the error url of windowscontainer
Signed-off-by: root <timyinshi>
2020-03-05 09:38:33 +08:00
Michael C. Cambria
47a9fd80c8 Merge pull request #455 from booxter/master
Unlock OS thread after netns is restored
2020-03-04 11:34:44 -05:00
Ihar Hrachyshka
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
Piotr Skamruk
32fc3ee9d3 Merge pull request #454 from dcbw/update-coreos-owners
owners: updates for maintainer changes
2020-02-19 17:29:54 +01:00
Dan Williams
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
Bruce Ma
53854dd948 flannel: remove net conf file after DEL succeed
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2020-02-19 21:00:37 +08:00
Piotr Skamruk
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
Periyasamy Palanisamy
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
Piotr Skamruk
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
Dan Williams
8bf6a7b362 Merge pull request #444 from mars1024/bugfix/ptp_redundant
ptp: remove some redundant lines
2020-01-29 10:23:06 -06:00
Dan Williams
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
Bruce Ma
45fd949465 ptp: remove some redundant lines
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2020-01-29 20:17:01 +08:00
Bruce Ma
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
Bruce Ma
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
Piotr Skamruk
832f2699c2 Merge pull request #442 from ofiliz/loopback-fix
loopback: Fix ipv6 address checks
2020-01-27 09:38:52 +01:00
Onur Filiz
d1360b82ab loopback: Fix ipv6 address checks
Signed-off-by: Onur Filiz <ofiliz@users.noreply.github.com>
2020-01-23 17:33:47 -08:00
Piotr Skamruk
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
Piotr Skamruk
754e153b03 Merge pull request #437 from jcaamano/master
Improve support of sysctl name separators
2020-01-22 17:14:46 +01:00
Bryan Boreham
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
Quan Tian
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
Jaime Caamaño Ruiz
963aaf86e6 Format with gofmt
Signed-off-by: Jaime Caamaño Ruiz <jcaamano@suse.com>
2020-01-13 19:44:40 +01:00
Jaime Caamaño Ruiz
cd9d6b28da Use Replace instead of ReplaceAll
Signed-off-by: Jaime Caamaño Ruiz <jcaamano@suse.com>
2020-01-13 16:50:13 +01:00
Jaime Caamaño Ruiz
0452c1dd10 Fix copyrights
Signed-off-by: Jaime Caamaño Ruiz <jcaamano@suse.com>
2020-01-13 14:56:58 +01:00
Jaime Caamaño Ruiz
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
Carlos de Paula
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
Bryan Boreham
62b36d2fbc Merge pull request #433 from ydcool/mips64le-support
add support for mips64le
2020-01-08 16:26:13 +00:00
Bryan Boreham
e5fdd449dd Merge pull request #428 from weibeld/master
bridge: add missing cniVersion in README example
2020-01-08 16:24:48 +00:00
Dominic Yin
8db5e4d41b add support for mips64le
Signed-off-by: Dominic Yin <yindongchao@inspur.com>
2020-01-06 15:51:47 +08:00
Bruce Ma
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
Daniel Weibel
7dea2a4c1b Add missing cniVersion in README example
Signed-off-by: Daniel Weibel <danielmweibel@gmail.com>
2019-12-18 19:08:37 +08:00
Antonio Ojea
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
Antonio Ojea
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
Antonio Ojea
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
Antonio Ojea
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
Dan Williams
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
Bryan Boreham
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
Casey Callendrello
497560f35f Merge pull request #408 from tgross/idempotent_chain_creation
ensure iptables chain creation is idempotent
2019-11-13 17:20:45 +01:00
Tim Gross
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
Casey Callendrello
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
Casey Callendrello
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
Bryan Boreham
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
Bruce Ma
1a30688da0 add some testcases about invalid MTUs
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-10-25 20:15:18 +08:00
Bruce Ma
bee8d6cf30 vlan: add MTU validation in loadNetConf
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-10-25 19:59:33 +08:00
Casey Callendrello
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
Matt Dupre
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
Casey Callendrello
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
Casey Callendrello
7bcaae263f Merge pull request #404 from mars1024/feat/mtu_validation
macvlan: add MTU validation to loadNetConf
2019-10-23 16:13:06 +01:00
Bruce Ma
e1f955d9bf macvlan: add MTU validation to loadNetConf
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-10-23 20:39:14 +08:00
Tomofumi Hayashi
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
Giuseppe Scrivano
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
Tomofumi Hayashi
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
Bryan Boreham
411d060b81 Merge pull request #389 from CallMeFoxie/bw-units
Use uint64 for Bandwidth plugin
2019-10-09 16:25:06 +01:00
Casey Callendrello
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
Dan Williams
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
Bruce Ma
b7ffa24326 vlan/bridge: fix some typo
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-10-08 11:57:30 +08:00
Casey Callendrello
15894b36a0 Merge pull request #397 from oshothebig/install-cnitool
contributing doc: describe cnitool installation
2019-10-07 14:27:23 +02:00
Sho SHIMIZU
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
Sho SHIMIZU
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
Ashley Reese
cf187287af Update tests for uint64
Signed-off-by: Ashley Reese <ashley@victorianfox.com>
2019-10-03 16:55:41 +02:00
Ashley Reese
0dff883769 Use uint64 for Bandwidth plugin
Signed-off-by: Ashley Reese <ashley@victorianfox.com>
2019-10-03 16:05:27 +02:00
Dan Williams
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
Bruce Ma
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
Erik Sipsma
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
Casey Callendrello
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
Bryan Boreham
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
Guangming Wang
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
Erik Sipsma
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
Erik Sipsma
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
Piotr Skamruk
291ab6cc84 Merge pull request #386 from janisz/patch-1
Bump Go version
2019-09-18 17:12:43 +02:00
janisz
90125f40ba Bump Go version
Signed-off-by: Tomasz Janiszewski <janiszt@gmail.com>
2019-09-18 11:20:33 +02:00
Dan Williams
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
Michael Cambria
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
Casey Callendrello
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
Niels van Oosterom
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
Dan Williams
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
Casey Callendrello
f81a529ebd Merge pull request #375 from smarkm/master
Fixes #342, cleanup netns after test suite
2019-08-28 17:55:36 +02:00
Dan Williams
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
Bruce Ma
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
Bruce Ma
659a09f34e loopback support CNI CHECK
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-08-23 20:36:37 +08:00
Bruce Ma
b76ace9c64 bump up libcni to v0.7.1
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-08-23 20:24:16 +08:00
smarkm
0d0dcfc02f Cleanup netns after test suit
Signed-off-by: smarkm <smark@freecoop.net>
2019-08-22 08:10:35 +08:00
Gabe Rosenhouse
485be65581 Merge pull request #372 from squeed/fix-build
win-bridge, win-overlay: remove extra import
2019-08-14 11:26:26 -07:00
Casey Callendrello
ca82120019 win-bridge, win-overlay: remove extra import 2019-08-14 18:15:23 +02:00
Casey Callendrello
c9e1c0c1ed Merge pull request #281 from s1061123/dev/support-args
Support "args" in static and tuning
2019-08-14 17:43:04 +02:00
Casey Callendrello
2d6d4b260a Merge pull request #366 from Random-Liu/fix-bridge-race
Fix: failed to set bridge addr: could not add IP address to \"cni0\": file exists
2019-08-12 13:08:32 +02:00
Lantao Liu
ad7c1d189b Fix a race condition in the bridge plugin.
Signed-off-by: Lantao Liu <lantaol@google.com>
2019-08-09 17:08:04 -07:00
Tomofumi Hayashi
a069a5f1a3 Support "args" in static and tuning
Support "args" field in JSON config to additional configuration
in static and tuning plugins.
2019-08-09 11:52:06 +09:00
Gabe Rosenhouse
ccd683e1a3 Merge pull request #357 from mars1024/bugfix/host-device
host-device: revert name setting to make retries idempotent
2019-08-07 08:54:26 -07:00
Casey Callendrello
a11cb626b0 Merge pull request #331 from nagiesek/LoopbackDsr
Loopback dsr & L2Tunnel
2019-08-07 17:48:00 +02:00
Dan Williams
f36dbc2031 Merge pull request #358 from mccv1r0/ipt-vendor
Vendor update go-iptables
2019-08-07 10:40:26 -05:00
Casey Callendrello
e9d511c5bc Merge pull request #364 from s1061123/fix/removeifdown
Remove link Down/Up in MAC address change to prevent route flush
2019-08-07 17:39:02 +02:00
Michael Cambria
91a68d56f9 Vendor update go-iptables to obtain commit f1d0510cabcb710d5c5dd284096f81444b9d8d10
Update go.mod & go.sub
2019-08-07 10:56:30 -04:00
Tomofumi Hayashi
8902d2614a Remove link Down/Up in MAC address change to prevent route flush 2019-08-07 13:54:10 +09:00
Nathan Gieseker
df9af9ab41 [Windows] Adds optional loopbackDSR argument to cni config.
Adds a bool to the cni config that will add a policy that allows for loopbackDSR on an interface. Updates relevant documentation. Allows L2Tunnel networks to be used for L2Bridge plugin.
2019-07-31 15:45:54 -07:00
Bruce Ma
5e2e365291 host-device: remove useless Expects in testcases
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-07-29 21:09:03 +08:00
Bruce Ma
4b68f56820 host-device: add testcases for imdempotence of CmdDel
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-07-29 20:47:28 +08:00
Casey Callendrello
ded2f17577 Merge pull request #328 from mars1024/feature/hostlocal_idempotent
host-local support idempotent allocation
2019-07-24 17:32:15 +02:00
Bruce Ma
57650a1e5b host-device: revert name setting to make retries idempotent
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-07-19 21:26:07 +08:00
Casey Callendrello
7ba2bcfeab Merge pull request #353 from cf-container-networking/fix-hash-func
Increase IfbDeviceName size and refactor hashing functions, fix
2019-07-18 17:12:53 -04:00
Gabe Rosenhouse
3fb8dcfd4c pkg/meta/bandwidth: increase IfbDeviceName size
* Increase entroy from 2 bytes to 7 bytes to prevent collisions
* Extract common library function for hash with prefix
* Refactor portmap plugin to use library function

fixes #347

Co-authored-by: Cameron Moreau <cmoreau@pivotal.io>
Co-authored-by: Mikael Manukyan <mmanukyan@pivotal.io>
2019-07-18 11:45:38 -07:00
Dan Williams
966bbcb8a5 Merge pull request #348 from cf-container-networking/update-vagrantfile
update Go version in Vagrantfile
2019-07-10 10:04:52 -05:00
Dan Williams
7d76537d4a Merge pull request #349 from cf-container-networking/fix-ip-test
pkg/ip unit test: be agnostic of Linux version
2019-07-10 10:04:32 -05:00
Mikael Manukyan
f3b1ffc960 pkg/ip unit test: be agnostic of Linux version
on Linux 4.4 the syscall error message is "invalid argument" not "file
exists"

Co-authored-by: Gabe Rosenhouse <grosenhouse@pivotal.io>
2019-07-09 16:58:57 -07:00
Mikael Manukyan
ce9560712e update Go version in Vagrantfile
update go to the lastest version v1.12.7

Co-authored-by: Gabe Rosenhouse <grosenhouse@pivotal.io>
2019-07-09 16:51:00 -07:00
Bruce Ma
e2984e7840 host-local: return error if duplicate allocation is requested for a given ID
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-07-06 10:05:18 +08:00
Bruce Ma
eb1ff18c4c host-local: add some testcases for allocation idempotency
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-07-06 09:39:56 +08:00
Bruce Ma
e8771b36a2 host-local: make allocation idempotent to multiple requests with same id
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-07-06 09:39:56 +08:00
Bruce Ma
7f8ea631e5 host-local: make Store interface support to get ip list by id
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-07-06 09:39:56 +08:00
Casey Callendrello
0eddc554c0 Merge pull request #343 from s1061123/dev/runtime-ips-mac
Support ips capability in static and mac capability in tuning
2019-07-03 17:48:55 +02:00
Casey Callendrello
e8a25e33cd Merge pull request #344 from cadmuxe/veth_name
Make host-side veth name configurable.
2019-07-03 17:46:02 +02:00
Koonwah Chen
96bd10f679 Add pkg/ip/link_linux.go:SetupVethWithName to support the host-side veth
name configuration.
2019-06-26 10:24:40 -07:00
Dan Williams
303299b7b2 Merge pull request #337 from nagiesek/goModUpdate
Go mod update
2019-06-26 11:03:40 -05:00
Nathan Gieseker
d42007865a update iptables 2019-06-26 02:14:56 -07:00
Nathan Gieseker
ce60e8eb3d dhcp module update 2019-06-26 02:07:24 -07:00
Nathan Gieseker
addbcd34b4 update ethtool 2019-06-26 02:07:24 -07:00
Nathan Gieseker
e8c953999e vendor update 2019-06-26 02:07:24 -07:00
Nathan Gieseker
13fbc4afdf Move over to go mod from dep 2019-06-26 02:07:23 -07:00
Koonwah Chen
545a77f4bb skip makeVeth retry if a vethPeerName is set. 2019-06-25 14:17:42 -07:00
Koonwah Chen
c204dbd47c update pkg/ip/link_linux.go:SetupVeth to support host-side veth name config. 2019-06-21 11:39:30 -07:00
Tomofumi Hayashi
660685a8af Support ips capability in static and mac capability in tuning
This change introduces new capability flag to change MAC address
and to specify IP addresses by tuning and static.
2019-06-20 17:11:47 +09:00
Casey Callendrello
2b6808807f Merge pull request #341 from mars1024/bump_cni_to_0.7.1
bump containernetworking/cni to v0.7.1
2019-06-19 17:51:14 +02:00
Bruce Ma
869d5ec873 pkg/ipam : use delegateArgs instead of env set/unset in ipam.ExecDel
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-06-18 21:02:01 +08:00
Bruce Ma
93919752fb bump containernetworking/cni up to v0.7.1
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-06-18 20:49:51 +08:00
Dan Williams
fe60fcddb8 Merge pull request #326 from squeed/bump-release-go
Release: bump go to v1.12
2019-05-29 11:30:15 -05:00
Dan Williams
e308211d34 Merge pull request #300 from zshi-redhat/host-device-pci-parameter
host-device: add pciBusID property
2019-05-29 11:06:00 -05:00
Casey Callendrello
29a431f1fc Release: bump go to v1.12 2019-05-29 17:58:47 +02:00
Casey Callendrello
7d75ab66a4 Merge pull request #321 from mars1024/modify/bandwidth
bandwidth: get bandwidth interface in host ns through container interface
2019-05-29 17:57:36 +02:00
Bryan Boreham
d5c8b4b206 Merge pull request #325 from nickethier/b-bridge-ipmasq
bridge: fix ipMasq setup to use correct source address
2019-05-29 16:44:18 +01:00
Dan Williams
5a4085f1fa Merge pull request #324 from moshe010/fix_386
fix compilation error on 386
2019-05-29 10:43:18 -05:00
Nick Ethier
37d2ee1d5d bridge: don't use canonical CIDR address when setting up IP masquerade 2019-05-25 20:55:47 -04:00
Nick Ethier
7f9b1844b8 bridge: add test for ipMasq rules 2019-05-24 23:14:31 -04:00
Moshe Levi
2753b9af8f fix compilation error on 386
Update github.com/safchain/ethtool to fix the compilation
error on 386. Also added 386 to the tarvis yaml.

Fixes #322

Signed-off-by: Moshe Levi <moshele@mellanox.com>
2019-05-24 23:43:37 +03:00
Bruce Ma
d35c96dda6 bandwidth: add testcases for func getHostInterface
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-05-21 00:28:04 +08:00
Bruce Ma
344d343431 bandwidth: get bandwidth interface in host ns through container interface
Signed-off-by: Bruce Ma <brucema19901024@gmail.com>
2019-05-18 14:06:45 +08:00
Zenghui Shi
79b1c402c4 host-device: add deviceID property
Fixes #253
2019-04-25 12:17:40 +08:00
1597 changed files with 246000 additions and 347449 deletions

View File

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

View File

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

45
.github/actions/retest-action/entrypoint.sh vendored Executable file
View File

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

17
.github/workflows/commands.yml vendored Normal file
View File

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

69
.github/workflows/test.yaml vendored Normal file
View File

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

View File

@@ -1,49 +0,0 @@
language: go
sudo: required
dist: trusty
go:
- 1.10.x
- 1.11.x
env:
global:
- PATH=$GOROOT/bin:$GOPATH/bin:$PATH
- CGO_ENABLED=0
matrix:
- TARGET=amd64
- TARGET=arm
- TARGET=arm64
- TARGET=ppc64le
- TARGET=s390x
matrix:
fast_finish: true
include:
- os: windows
env: TARGET=amd64
go: 1.10.x
- os: windows
env: TARGET=amd64
go: 1.11.x
install:
- go get github.com/onsi/ginkgo/ginkgo
- go get github.com/containernetworking/cni/cnitool
- go get golang.org/x/tools/cmd/cover
- go get github.com/modocache/gover
- go get github.com/mattn/goveralls
script:
- |
if [ "${TARGET}" == "amd64" ]; then
GOARCH="${TARGET}" ./test_${TRAVIS_OS_NAME}.sh
else
GOARCH="${TARGET}" ./build_linux.sh
fi
notifications:
email: false
git:
depth: 9999999

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

367
Godeps/Godeps.json generated
View File

@@ -1,367 +0,0 @@
{
"ImportPath": "github.com/containernetworking/plugins",
"GoVersion": "go1.7",
"GodepVersion": "v80",
"Packages": [
"./..."
],
"Deps": [
{
"ImportPath": "github.com/Microsoft/go-winio",
"Comment": "v0.4.11",
"Rev": "97e4973ce50b2ff5f09635a57e2b88a037aae829"
},
{
"ImportPath": "github.com/Microsoft/hcsshim",
"Comment": "v0.7.6",
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
},
{
"ImportPath": "github.com/Microsoft/hcsshim/internal/guid",
"Comment": "v0.7.4",
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
},
{
"ImportPath": "github.com/Microsoft/hcsshim/internal/hcs",
"Comment": "v0.7.4",
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
},
{
"ImportPath": "github.com/Microsoft/hcsshim/internal/hcserror",
"Comment": "v0.7.4",
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
},
{
"ImportPath": "github.com/Microsoft/hcsshim/internal/hns",
"Comment": "v0.7.4",
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
},
{
"ImportPath": "github.com/Microsoft/hcsshim/internal/interop",
"Comment": "v0.7.4",
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
},
{
"ImportPath": "github.com/Microsoft/hcsshim/internal/longpath",
"Comment": "v0.7.4",
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
},
{
"ImportPath": "github.com/Microsoft/hcsshim/internal/mergemaps",
"Comment": "v0.7.4",
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
},
{
"ImportPath": "github.com/Microsoft/hcsshim/internal/safefile",
"Comment": "v0.7.4",
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
},
{
"ImportPath": "github.com/Microsoft/hcsshim/internal/schema1",
"Comment": "v0.7.4",
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
},
{
"ImportPath": "github.com/Microsoft/hcsshim/internal/timeout",
"Comment": "v0.7.4",
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
},
{
"ImportPath": "github.com/Microsoft/hcsshim/internal/wclayer",
"Comment": "v0.7.4",
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
},
{
"ImportPath": "github.com/alexflint/go-filemutex",
"Rev": "72bdc8eae2aef913234599b837f5dda445ca9bd9"
},
{
"ImportPath": "github.com/buger/jsonparser",
"Rev": "f4dd9f5a6b441265aefc1d44872a6f8c10f42b37"
},
{
"ImportPath": "github.com/containernetworking/cni/libcni",
"Comment": "v0.7.0",
"Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23"
},
{
"ImportPath": "github.com/containernetworking/cni/pkg/invoke",
"Comment": "v0.7.0",
"Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23"
},
{
"ImportPath": "github.com/containernetworking/cni/pkg/skel",
"Comment": "v0.7.0",
"Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23"
},
{
"ImportPath": "github.com/containernetworking/cni/pkg/types",
"Comment": "v0.7.0",
"Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23"
},
{
"ImportPath": "github.com/containernetworking/cni/pkg/types/020",
"Comment": "v0.7.0",
"Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23"
},
{
"ImportPath": "github.com/containernetworking/cni/pkg/types/current",
"Comment": "v0.7.0",
"Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23"
},
{
"ImportPath": "github.com/containernetworking/cni/pkg/version",
"Comment": "v0.7.0",
"Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23"
},
{
"ImportPath": "github.com/coreos/go-iptables/iptables",
"Comment": "v0.4.1",
"Rev": "78b5fff24e6df8886ef8eca9411f683a884349a5"
},
{
"ImportPath": "github.com/coreos/go-systemd/activation",
"Comment": "v17",
"Rev": "39ca1b05acc7ad1220e09f133283b8859a8b71ab"
},
{
"ImportPath": "github.com/d2g/dhcp4",
"Rev": "f0e4d29ff0231dce36e250b2ed9ff08412584bca"
},
{
"ImportPath": "github.com/d2g/dhcp4client",
"Rev": "bed07e1bc5b85f69c6f0fd73393aa35ec68ed892"
},
{
"ImportPath": "github.com/d2g/dhcp4server",
"Rev": "477b11cea4dcc56af002849238d4f9c1e093c744"
},
{
"ImportPath": "github.com/d2g/dhcp4server/leasepool",
"Rev": "477b11cea4dcc56af002849238d4f9c1e093c744"
},
{
"ImportPath": "github.com/d2g/dhcp4server/leasepool/memorypool",
"Rev": "477b11cea4dcc56af002849238d4f9c1e093c744"
},
{
"ImportPath": "github.com/godbus/dbus",
"Comment": "v4.1.0-6-g885f9cc",
"Rev": "885f9cc04c9c1a6a61a2008e211d36c5737be3f5"
},
{
"ImportPath": "github.com/j-keck/arping",
"Rev": "2cf9dc699c5640a7e2c81403a44127bf28033600"
},
{
"ImportPath": "github.com/juju/errors",
"Rev": "22422dad46e14561a0854ad42497a75af9b61909"
},
{
"ImportPath": "github.com/mattn/go-shellwords",
"Comment": "v1.0.3",
"Rev": "02e3cf038dcea8290e44424da473dd12be796a8a"
},
{
"ImportPath": "github.com/onsi/ginkgo",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/config",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/extensions/table",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/codelocation",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/containernode",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/failer",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/leafnodes",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/remote",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/spec",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/specrunner",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/suite",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/testingtproxy",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/internal/writer",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/reporters",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/reporters/stenographer",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/ginkgo/types",
"Comment": "v1.2.0-29-g7f8ab55",
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
},
{
"ImportPath": "github.com/onsi/gomega",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/format",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/gbytes",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/gexec",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/internal/assertion",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/internal/asyncassertion",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/internal/oraclematcher",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/internal/testingtsupport",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/matchers",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/bipartitegraph",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/edge",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/node",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/util",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/onsi/gomega/types",
"Comment": "v1.0-71-g2152b45",
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
},
{
"ImportPath": "github.com/safchain/ethtool",
"Rev": "7ff1ba29eca231991280817541cb3903f6be15d1"
},
{
"ImportPath": "github.com/sirupsen/logrus",
"Comment": "v1.0.6",
"Rev": "3e01752db0189b9157070a0e1668a620f9a85da2"
},
{
"ImportPath": "github.com/vishvananda/netlink",
"Comment": "v1.0.0-40-g023a6da",
"Rev": "023a6dafdcdfa7068ac83b260ab7f03cd4131aca"
},
{
"ImportPath": "github.com/vishvananda/netlink/nl",
"Comment": "v1.0.0-40-g023a6da",
"Rev": "023a6dafdcdfa7068ac83b260ab7f03cd4131aca"
},
{
"ImportPath": "github.com/vishvananda/netns",
"Rev": "13995c7128ccc8e51e9a6bd2b551020a27180abd"
},
{
"ImportPath": "golang.org/x/crypto/ssh/terminal",
"Rev": "7c1a557ab941a71c619514f229f0b27ccb0c27cf"
},
{
"ImportPath": "golang.org/x/net/bpf",
"Rev": "49bb7cea24b1df9410e1712aa6433dae904ff66a"
},
{
"ImportPath": "golang.org/x/net/internal/iana",
"Rev": "49bb7cea24b1df9410e1712aa6433dae904ff66a"
},
{
"ImportPath": "golang.org/x/net/ipv4",
"Rev": "49bb7cea24b1df9410e1712aa6433dae904ff66a"
},
{
"ImportPath": "golang.org/x/sys/unix",
"Rev": "66b7b1311ac80bbafcd2daeef9a5e6e2cd1e2399"
},
{
"ImportPath": "golang.org/x/sys/windows",
"Rev": "66b7b1311ac80bbafcd2daeef9a5e6e2cd1e2399"
},
{
"ImportPath": "golang.org/x/net/internal/socket",
"Rev": "49bb7cea24b1df9410e1712aa6433dae904ff66a"
}
]
}

5
Godeps/Readme generated
View File

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

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)

View File

@@ -1,7 +1,7 @@
[![Build Status](https://travis-ci.org/containernetworking/plugins.svg?branch=master)](https://travis-ci.org/containernetworking/plugins)
# plugins
Some CNI network plugins, maintained by the containernetworking team. For more information, see the individual READMEs.
Some CNI network plugins, maintained by the containernetworking team. For more information, see the [CNI website](https://www.cni.dev).
Read [CONTRIBUTING](CONTRIBUTING.md) for build and test instructions.
@@ -32,3 +32,11 @@ Read [CONTRIBUTING](CONTRIBUTING.md) for build and test instructions.
### Sample
The sample plugin provides an example for building your own plugin.
## Contact
For any questions about CNI, please reach out via:
- Email: [cni-dev](https://groups.google.com/forum/#!forum/cni-dev)
- Slack: #cni on the [CNCF slack](https://slack.cncf.io/).
If you have a _security_ issue to report, please do so privately to the email addresses listed in the [OWNERS](OWNERS.md) file.

18
Vagrantfile vendored
View File

@@ -1,18 +0,0 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure(2) do |config|
config.vm.box = "bento/ubuntu-16.04"
config.vm.synced_folder "..", "/go/src/github.com/containernetworking"
config.vm.provision "shell", inline: <<-SHELL
set -e -x -u
apt-get update -y || (sleep 40 && apt-get update -y)
apt-get install -y git gcc-multilib gcc-mingw-w64
wget -qO- https://storage.googleapis.com/golang/go1.11.1.linux-amd64.tar.gz | tar -C /usr/local -xz
echo 'export GOPATH=/go' >> /root/.bashrc
echo 'export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin' >> /root/.bashrc
cd /go/src/github.com/containernetworking/plugins
SHELL
end

View File

@@ -1,21 +1,12 @@
#!/usr/bin/env bash
set -e
cd $(dirname "$0")
cd "$(dirname "$0")"
if [ "$(uname)" == "Darwin" ]; then
export GOOS="${GOOS:-linux}"
fi
ORG_PATH="github.com/containernetworking"
export REPO_PATH="${ORG_PATH}/plugins"
if [ ! -h gopath/src/${REPO_PATH} ]; then
mkdir -p gopath/src/${ORG_PATH}
ln -s ../../../.. gopath/src/${REPO_PATH} || exit 255
fi
export GOPATH=${PWD}/gopath
export GO="${GO:-go}"
export GOFLAGS="${GOFLAGS} -mod=vendor"
mkdir -p "${PWD}/bin"
@@ -24,9 +15,9 @@ PLUGINS="plugins/meta/* plugins/main/* plugins/ipam/*"
for d in $PLUGINS; do
if [ -d "$d" ]; then
plugin="$(basename "$d")"
if [ $plugin != "windows" ]; then
if [ "${plugin}" != "windows" ]; then
echo " $plugin"
$GO build -o "${PWD}/bin/$plugin" "$@" "$REPO_PATH"/$d
${GO:-go} build -o "${PWD}/bin/$plugin" "$@" ./"$d"
fi
fi
done

View File

@@ -1,23 +1,15 @@
#!/usr/bin/env bash
set -e
cd $(dirname "$0")
ORG_PATH="github.com/containernetworking"
export REPO_PATH="${ORG_PATH}/plugins"
export GOPATH=$(mktemp -d)
mkdir -p ${GOPATH}/src/${ORG_PATH}
trap "{ rm -rf $GOPATH; }" EXIT
ln -s ${PWD} ${GOPATH}/src/${REPO_PATH} || exit 255
cd "$(dirname "$0")"
export GO="${GO:-go}"
export GOOS=windows
export GOFLAGS="${GOFLAGS} -mod=vendor"
echo "$GOFLAGS"
PLUGINS=$(cat plugins/windows_only.txt)
PLUGINS=$(cat plugins/windows_only.txt | dos2unix )
for d in $PLUGINS; do
if [ -d "$d" ]; then
plugin="$(basename "$d").exe"
echo " $plugin"
$GO build -o "${PWD}/bin/$plugin" "$@" "$REPO_PATH"/$d
fi
plugin="$(basename "$d").exe"
echo "building $plugin"
$GO build -o "${PWD}/bin/$plugin" "$@" ./"${d}"
done

29
go.mod Normal file
View File

@@ -0,0 +1,29 @@
module github.com/containernetworking/plugins
go 1.14
require (
github.com/Microsoft/go-winio v0.4.11 // indirect
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.8.0
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
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5
github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4 // indirect
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56
github.com/mattn/go-shellwords v1.0.3
github.com/onsi/ginkgo v1.12.1
github.com/onsi/gomega v1.10.3
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8
github.com/sirupsen/logrus v1.0.6 // indirect
github.com/stretchr/testify v1.3.0 // indirect
github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852
golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637
gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 // indirect
)

109
go.sum Normal file
View File

@@ -0,0 +1,109 @@
github.com/Microsoft/go-winio v0.4.11 h1:zoIOcVf0xPN1tnMVbTtEdI+P8OofVk3NObnwOQ6nK2Q=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Microsoft/hcsshim v0.8.6 h1:ZfF0+zZeYdzMIVMZHKtDKJvLHj76XCuVae/jNkjj0IA=
github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae h1:AMzIhMUqU3jMrZiTuW0zkYeKlKDAFD+DG20IoO421/Y=
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.8.0 h1:BT9lpgGoH4jw3lFC7Odz2prU5ruiYKcgAjMCbgybcKI=
github.com/containernetworking/cni v0.8.0/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=
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
github.com/d2g/dhcp4client v1.0.0 h1:suYBsYZIkSlUMEz4TAYCczKf62IA2UWC+O8+KtdOhCo=
github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5 h1:+CpLbZIeUn94m02LdEKPcgErLJ347NUwxPKs5u8ieiY=
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=
github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4 h1:itqmmf1PFpC4n5JW+j4BU7X4MTfVurhYRTjODoPb2Y8=
github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c h1:RBUpb2b14UnmRHNd2uHz20ZHLDK+SW5Us/vWF5IHRaY=
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
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/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/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1 h1:mFwc4LvZ0xpSvDZ3E+k8Yte0hLOMxXUlP+yXtJqkYfQ=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA=
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8 h1:2c1EFnZHIPCW8qKWgHMH/fX2PkSabFc5mrVzfUNdg5U=
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
github.com/sirupsen/logrus v1.0.6 h1:hcP1GmhGigz/O7h1WVUM5KklBp1JoNS9FggWKdj/j3s=
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852 h1:cPXZWzzG0NllBLdjWoD1nDfaqu98YMv+OneaKc8sPOA=
github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0 h1:wBouT66WTYFXdxfVdz9sVWARVd/2vfGcmI45D2gj45M=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637 h1:O5hKNaGxIT4A8OTMnuh6UpmBdI3SAPxlZ3g0olDrJVM=
golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/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/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/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())
@@ -225,23 +225,22 @@ func (n Namespace) Del() {
}
func makeTcpClientInNS(netns string, address string, port int, numBytes int) {
message := bytes.Repeat([]byte{'a'}, numBytes)
payload := bytes.Repeat([]byte{'a'}, numBytes)
message := string(payload)
bin, err := exec.LookPath("nc")
Expect(err).NotTo(HaveOccurred())
var cmd *exec.Cmd
if netns != "" {
netns = filepath.Base(netns)
cmd = exec.Command("ip", "netns", "exec", netns, bin, "-v", address, strconv.Itoa(port))
cmd = exec.Command("ip", "netns", "exec", netns, echoClientBinaryPath, "--target", fmt.Sprintf("%s:%d", address, port), "--message", message)
} else {
cmd = exec.Command("nc", address, strconv.Itoa(port))
cmd = exec.Command(echoClientBinaryPath, "--target", fmt.Sprintf("%s:%d", address, port), "--message", message)
}
cmd.Stdin = bytes.NewBuffer([]byte(message))
cmd.Stderr = GinkgoWriter
out, err := cmd.Output()
Expect(err).NotTo(HaveOccurred())
Expect(string(out)).To(Equal(string(message)))
Expect(string(out)).To(Equal(message))
}
func startEchoServerInNamespace(netNS Namespace) (int, *gexec.Session, error) {

View File

@@ -14,11 +14,10 @@
package integration_test
import (
"math/rand"
"strings"
"testing"
. "github.com/onsi/ginkgo"
"github.com/onsi/ginkgo/config"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
)
@@ -28,15 +27,18 @@ func TestIntegration(t *testing.T) {
RunSpecs(t, "integration")
}
var echoServerBinaryPath string
var echoServerBinaryPath, echoClientBinaryPath string
var _ = SynchronizedBeforeSuite(func() []byte {
binaryPath, err := gexec.Build("github.com/containernetworking/plugins/pkg/testutils/echosvr")
serverBinaryPath, err := gexec.Build("github.com/containernetworking/plugins/pkg/testutils/echo/server")
Expect(err).NotTo(HaveOccurred())
return []byte(binaryPath)
clientBinaryPath, err := gexec.Build("github.com/containernetworking/plugins/pkg/testutils/echo/client")
Expect(err).NotTo(HaveOccurred())
return []byte(strings.Join([]string{serverBinaryPath, clientBinaryPath}, ","))
}, func(data []byte) {
echoServerBinaryPath = string(data)
rand.Seed(config.GinkgoConfig.RandomSeed + int64(GinkgoParallelNode()))
binaries := strings.Split(string(data), ",")
echoServerBinaryPath = binaries[0]
echoClientBinaryPath = binaries[1]
})
var _ = SynchronizedAfterSuite(func() {}, func() {

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,19 +65,22 @@ 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
}
}
if n.LoopbackDSR {
n.ApplyLoopbackDSR(&epInfo.IpAddress)
}
if hnsEndpoint == nil {
hnsEndpoint = &hcsshim.HNSEndpoint{
Name: epInfo.EndpointName,
@@ -95,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 {
@@ -105,26 +109,18 @@ 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)
}
}
if hcnEndpoint == nil {
routes := []hcn.Route{
{
NextHop: GetIpString(&epInfo.Gateway),
DestinationPrefix: func() string {
destinationPrefix := "0.0.0.0/0"
if ipv6 := epInfo.Gateway.To4(); ipv6 == nil {
destinationPrefix = "::/0"
}
return destinationPrefix
}(),
NextHop: GetIpString(&epInfo.Gateway),
DestinationPrefix: GetDefaultDestinationPrefix(&epInfo.Gateway),
},
}
@@ -138,6 +134,9 @@ func GenerateHcnEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcn.HostComputeEndp
}
ipConfigs := []hcn.IpConfig{hcnIpConfig}
if n.LoopbackDSR {
n.ApplyLoopbackDSR(&epInfo.IpAddress)
}
hcnEndpoint = &hcn.HostComputeEndpoint{
SchemaVersion: hcn.Version{Major: 2},
Name: 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

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

View File

@@ -17,18 +17,29 @@ package hns
import (
"bytes"
"encoding/json"
"fmt"
"net"
"strings"
"github.com/Microsoft/hcsshim/hcn"
"github.com/buger/jsonparser"
"github.com/containernetworking/cni/pkg/types"
"strings"
)
// NetConf is the CNI spec
type NetConf struct {
types.NetConf
// ApiVersion is either 1 or 2, which specifies which hns APIs to call
ApiVersion int `json:"ApiVersion"`
// V2 Api Policies
HcnPolicyArgs []hcn.EndpointPolicy `json:"HcnPolicyArgs,omitempty"`
Policies []policy `json:"policies,omitempty"`
RuntimeConfig RuntimeConfig `json:"runtimeConfig"`
// V1 Api Policies
Policies []policy `json:"policies,omitempty"`
// Options to be passed in by the runtime
RuntimeConfig RuntimeConfig `json:"runtimeConfig"`
// If true, adds a policy to endpoints to support loopback direct server return
LoopbackDSR bool `json:"loopbackDSR"`
}
type RuntimeDNS struct {
@@ -36,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 {
@@ -45,6 +64,31 @@ type policy struct {
Value json.RawMessage `json:"value"`
}
func GetDefaultDestinationPrefix(ip *net.IP) string {
destinationPrefix := "0.0.0.0/0"
if ipv6 := ip.To4(); ipv6 == nil {
destinationPrefix = "::/0"
}
return destinationPrefix
}
func (n *NetConf) ApplyLoopbackDSR(ip *net.IP) {
value := fmt.Sprintf(`"Destinations" : ["%s"]`, ip.String())
if n.ApiVersion == 2 {
hcnLoopbackRoute := hcn.EndpointPolicy{
Type: "OutBoundNAT",
Settings: []byte(fmt.Sprintf("{%s}", value)),
}
n.HcnPolicyArgs = append(n.HcnPolicyArgs, hcnLoopbackRoute)
} else {
hnsLoopbackRoute := policy{
Name: "EndpointPolicy",
Value: []byte(fmt.Sprintf(`{"Type": "OutBoundNAT", %s}`, value)),
}
n.Policies = append(n.Policies, hnsLoopbackRoute)
}
}
// If runtime dns values are there use that else use cni conf supplied dns
func (n *NetConf) GetDNS() types.DNS {
dnsResult := n.DNS
@@ -172,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 (
@@ -60,11 +62,15 @@ func peerExists(name string) bool {
return true
}
func makeVeth(name string, mtu int) (peerName string, veth netlink.Link, err error) {
func makeVeth(name, vethPeerName string, mtu int) (peerName string, veth netlink.Link, err error) {
for i := 0; i < 10; i++ {
peerName, err = RandomVethName()
if err != nil {
return
if vethPeerName != "" {
peerName = vethPeerName
} else {
peerName, err = RandomVethName()
if err != nil {
return
}
}
veth, err = makeVethPair(name, peerName, mtu)
@@ -73,7 +79,7 @@ func makeVeth(name string, mtu int) (peerName string, veth netlink.Link, err err
return
case os.IsExist(err):
if peerExists(peerName) {
if peerExists(peerName) && vethPeerName == "" {
continue
}
err = fmt.Errorf("container veth name provided (%v) already exists", name)
@@ -121,12 +127,13 @@ func ifaceFromNetlinkLink(l netlink.Link) net.Interface {
}
}
// SetupVeth sets up a pair of virtual ethernet devices.
// Call SetupVeth from inside the container netns. It will create both veth
// SetupVethWithName sets up a pair of virtual ethernet devices.
// Call SetupVethWithName from inside the container netns. It will create both veth
// devices and move the host-side veth into the provided hostNS namespace.
// On success, SetupVeth returns (hostVeth, containerVeth, nil)
func SetupVeth(contVethName string, mtu int, hostNS ns.NetNS) (net.Interface, net.Interface, error) {
hostVethName, contVeth, err := makeVeth(contVethName, mtu)
// hostVethName: If hostVethName is not specified, the host-side veth name will use a random string.
// On success, SetupVethWithName returns (hostVeth, containerVeth, nil)
func SetupVethWithName(contVethName, hostVethName string, mtu int, hostNS ns.NetNS) (net.Interface, net.Interface, error) {
hostVethName, contVeth, err := makeVeth(contVethName, hostVethName, mtu)
if err != nil {
return net.Interface{}, net.Interface{}, err
}
@@ -153,6 +160,9 @@ func SetupVeth(contVethName string, mtu int, hostNS ns.NetNS) (net.Interface, ne
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 {
@@ -161,11 +171,19 @@ func SetupVeth(contVethName string, mtu int, hostNS ns.NetNS) (net.Interface, ne
return ifaceFromNetlinkLink(hostVeth), ifaceFromNetlinkLink(contVeth), nil
}
// SetupVeth sets up a pair of virtual ethernet devices.
// Call SetupVeth from inside the container netns. It will create both veth
// devices and move the host-side veth into the provided hostNS namespace.
// On success, SetupVeth returns (hostVeth, containerVeth, nil)
func SetupVeth(contVethName string, mtu int, hostNS ns.NetNS) (net.Interface, net.Interface, error) {
return SetupVethWithName(contVethName, "", mtu, hostNS)
}
// DelLinkByName removes an interface link.
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)
@@ -182,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

@@ -189,9 +189,8 @@ var _ = Describe("Link", func() {
It("returns useful error", func() {
_ = containerNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, _, err := ip.SetupVeth(containerVethName, mtu, hostNetNS)
Expect(err.Error()).To(Equal("failed to move veth to host netns: file exists"))
Expect(err.Error()).To(HavePrefix("failed to move veth to host netns: "))
return nil
})

View File

@@ -16,10 +16,8 @@ package ipam
import (
"context"
"fmt"
"github.com/containernetworking/cni/pkg/invoke"
"github.com/containernetworking/cni/pkg/types"
"os"
)
func ExecAdd(plugin string, netconf []byte) (types.Result, error) {
@@ -31,13 +29,5 @@ func ExecCheck(plugin string, netconf []byte) error {
}
func ExecDel(plugin string, netconf []byte) error {
cmd := os.Getenv("CNI_COMMAND")
if cmd == "" {
return fmt.Errorf("environment variable CNI_COMMAND must be specified.")
}
// Set CNI_COMMAND to DEL explicity. We might be deleting due to an ADD gone wrong.
// restore CNI_COMMAND to original value upon return.
os.Setenv("CNI_COMMAND", "DEL")
defer os.Setenv("CNI_COMMAND", cmd)
return invoke.DelegateDel(context.TODO(), plugin, netconf, nil)
}

View File

@@ -26,6 +26,11 @@ import (
// Returns an object representing the current OS thread's network namespace
func GetCurrentNS() (NetNS, error) {
// Lock the thread in case other goroutine executes in it and changes its
// network namespace after getCurrentThreadNetNSPath(), otherwise it might
// return an unexpected network namespace.
runtime.LockOSThread()
defer runtime.UnlockOSThread()
return GetNS(getCurrentThreadNetNSPath())
}
@@ -178,7 +183,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 +207,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()

View File

@@ -20,6 +20,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"sync"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/testutils"
@@ -118,6 +119,33 @@ var _ = Describe("Linux namespace operations", func() {
Expect(err).NotTo(HaveOccurred())
})
Context("when called concurrently", func() {
It("provides the original namespace as the argument to the callback", func() {
concurrency := 200
origNS, err := ns.GetCurrentNS()
Expect(err).NotTo(HaveOccurred())
origNSInode, err := getInodeNS(origNS)
Expect(err).NotTo(HaveOccurred())
var wg sync.WaitGroup
wg.Add(concurrency)
for i := 0; i < concurrency; i++ {
go func() {
defer wg.Done()
targetNetNS.Do(func(hostNS ns.NetNS) error {
defer GinkgoRecover()
hostNSInode, err := getInodeNS(hostNS)
Expect(err).NotTo(HaveOccurred())
Expect(hostNSInode).To(Equal(origNSInode))
return nil
})
}()
}
wg.Wait()
})
})
Context("when the callback returns an error", func() {
It("restores the calling thread to the original namespace before returning", func() {
err := originalNetNS.Do(func(ns.NetNS) error {
@@ -172,7 +200,9 @@ var _ = Describe("Linux namespace operations", func() {
By("comparing against the netns inode of every thread in the process")
for _, netnsPath := range allNetNSInCurrentProcess() {
netnsInode, err := getInode(netnsPath)
Expect(err).NotTo(HaveOccurred())
if !os.IsNotExist(err) {
Expect(err).NotTo(HaveOccurred())
}
Expect(netnsInode).NotTo(Equal(createdNetNSInode))
}
})

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

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

View File

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

View File

@@ -10,6 +10,7 @@ import (
"bufio"
"fmt"
"io"
"log"
"net"
"os"
"strings"
@@ -17,21 +18,50 @@ import (
)
func main() {
// Start TCP server
listener, err := net.Listen("tcp", ":")
if err != nil {
panic(err)
}
defer listener.Close()
// use the same port for UDP
_, port, err := net.SplitHostPort(listener.Addr().String())
if err != nil {
panic(err)
}
fmt.Printf("127.0.0.1:%s\n", port)
for {
conn, err := listener.Accept()
if err != nil {
panic(err)
go func() {
for {
conn, err := listener.Accept()
if err != nil {
panic(err)
}
go handleConnection(conn)
}
}()
// Start UDP server
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%s", port))
if err != nil {
log.Fatalf("Error from net.ResolveUDPAddr(): %s", err)
}
sock, err := net.ListenUDP("udp", addr)
if err != nil {
log.Fatalf("Error from ListenUDP(): %s", err)
}
defer sock.Close()
buffer := make([]byte, 1024)
for {
n, addr, err := sock.ReadFrom(buffer)
if err != nil {
log.Fatalf("Error from ReadFrom(): %s", err)
}
sock.SetWriteDeadline(time.Now().Add(1 * time.Minute))
n, err = sock.WriteTo(buffer[0:n], addr)
if err != nil {
return
}
go handleConnection(conn)
}
}
@@ -53,5 +83,4 @@ func handleConnection(conn net.Conn) {
fmt.Fprint(os.Stderr, err.Error())
return
}
}

View File

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

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)
}

73
pkg/utils/conntrack.go Normal file
View File

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

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

@@ -22,16 +22,22 @@ import (
const (
maxChainLength = 28
chainPrefix = "CNI-"
prefixLength = len(chainPrefix)
)
// Generates a chain name to be used with iptables.
// Ensures that the generated chain name is exactly
// maxChainLength chars in length
// FormatChainName generates a chain name to be used
// with iptables. Ensures that the generated chain
// name is exactly maxChainLength chars in length.
func FormatChainName(name string, id string) string {
chainBytes := sha512.Sum512([]byte(name + id))
chain := fmt.Sprintf("%s%x", chainPrefix, chainBytes)
return chain[:maxChainLength]
return MustFormatChainNameWithPrefix(name, id, "")
}
// MustFormatChainNameWithPrefix generates a chain name similar
// to FormatChainName, but adds a custom prefix between
// chainPrefix and unique identifier. Ensures that the
// generated chain name is exactly maxChainLength chars in length.
// Panics if the given prefix is too long.
func MustFormatChainNameWithPrefix(name string, id string, prefix string) string {
return MustFormatHashWithPrefix(maxChainLength, chainPrefix+prefix, name+id)
}
// FormatComment returns a comment used for easier
@@ -39,3 +45,16 @@ func FormatChainName(name string, id string) string {
func FormatComment(name string, id string) string {
return fmt.Sprintf("name: %q id: %q", name, id)
}
const MaxHashLen = sha512.Size * 2
// MustFormatHashWithPrefix returns a string of given length that begins with the
// given prefix. It is filled with entropy based on the given string toHash.
func MustFormatHashWithPrefix(length int, prefix string, toHash string) string {
if len(prefix) >= length || length > MaxHashLen {
panic("invalid length")
}
output := sha512.Sum512([]byte(toHash))
return fmt.Sprintf("%s%x", prefix, output)[:length]
}

View File

@@ -15,37 +15,151 @@
package utils
import (
"fmt"
"strings"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Utils", func() {
It("must format a short name", func() {
chain := FormatChainName("test", "1234")
Expect(len(chain)).To(Equal(maxChainLength))
Expect(chain).To(Equal("CNI-2bbe0c48b91a7d1b8a6753a8"))
Describe("FormatChainName", func() {
It("must format a short name", func() {
chain := FormatChainName("test", "1234")
Expect(len(chain)).To(Equal(maxChainLength))
Expect(chain).To(Equal("CNI-2bbe0c48b91a7d1b8a6753a8"))
})
It("must truncate a long name", func() {
chain := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
Expect(len(chain)).To(Equal(maxChainLength))
Expect(chain).To(Equal("CNI-374f33fe84ab0ed84dcdebe3"))
})
It("must be predictable", func() {
chain1 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
chain2 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
Expect(len(chain1)).To(Equal(maxChainLength))
Expect(len(chain2)).To(Equal(maxChainLength))
Expect(chain1).To(Equal(chain2))
})
It("must change when a character changes", func() {
chain1 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
chain2 := FormatChainName("testalongnamethatdoesnotmakesense", "1235")
Expect(len(chain1)).To(Equal(maxChainLength))
Expect(len(chain2)).To(Equal(maxChainLength))
Expect(chain1).To(Equal("CNI-374f33fe84ab0ed84dcdebe3"))
Expect(chain1).NotTo(Equal(chain2))
})
})
It("must truncate a long name", func() {
chain := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
Expect(len(chain)).To(Equal(maxChainLength))
Expect(chain).To(Equal("CNI-374f33fe84ab0ed84dcdebe3"))
Describe("MustFormatChainNameWithPrefix", func() {
It("generates a chain name with a prefix", func() {
chain := MustFormatChainNameWithPrefix("test", "1234", "PREFIX-")
Expect(len(chain)).To(Equal(maxChainLength))
Expect(chain).To(Equal("CNI-PREFIX-2bbe0c48b91a7d1b8"))
})
It("must format a short name", func() {
chain := MustFormatChainNameWithPrefix("test", "1234", "PREFIX-")
Expect(len(chain)).To(Equal(maxChainLength))
Expect(chain).To(Equal("CNI-PREFIX-2bbe0c48b91a7d1b8"))
})
It("must truncate a long name", func() {
chain := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1234", "PREFIX-")
Expect(len(chain)).To(Equal(maxChainLength))
Expect(chain).To(Equal("CNI-PREFIX-374f33fe84ab0ed84"))
})
It("must be predictable", func() {
chain1 := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1234", "PREFIX-")
chain2 := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1234", "PREFIX-")
Expect(len(chain1)).To(Equal(maxChainLength))
Expect(len(chain2)).To(Equal(maxChainLength))
Expect(chain1).To(Equal(chain2))
})
It("must change when a character changes", func() {
chain1 := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1234", "PREFIX-")
chain2 := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1235", "PREFIX-")
Expect(len(chain1)).To(Equal(maxChainLength))
Expect(len(chain2)).To(Equal(maxChainLength))
Expect(chain1).To(Equal("CNI-PREFIX-374f33fe84ab0ed84"))
Expect(chain1).NotTo(Equal(chain2))
})
It("panics when prefix is too large", func() {
longPrefix := strings.Repeat("PREFIX-", 4)
Expect(func() {
MustFormatChainNameWithPrefix("test", "1234", longPrefix)
}).To(Panic())
})
})
It("must be predictable", func() {
chain1 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
chain2 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
Expect(len(chain1)).To(Equal(maxChainLength))
Expect(len(chain2)).To(Equal(maxChainLength))
Expect(chain1).To(Equal(chain2))
Describe("MustFormatHashWithPrefix", func() {
It("always returns a string with the given prefix", func() {
Expect(MustFormatHashWithPrefix(10, "AAA", "some string")).To(HavePrefix("AAA"))
Expect(MustFormatHashWithPrefix(10, "foo", "some string")).To(HavePrefix("foo"))
Expect(MustFormatHashWithPrefix(10, "bar", "some string")).To(HavePrefix("bar"))
})
It("always returns a string of the given length", func() {
Expect(MustFormatHashWithPrefix(10, "AAA", "some string")).To(HaveLen(10))
Expect(MustFormatHashWithPrefix(15, "AAA", "some string")).To(HaveLen(15))
Expect(MustFormatHashWithPrefix(5, "AAA", "some string")).To(HaveLen(5))
})
It("is deterministic", func() {
val1 := MustFormatHashWithPrefix(10, "AAA", "some string")
val2 := MustFormatHashWithPrefix(10, "AAA", "some string")
val3 := MustFormatHashWithPrefix(10, "AAA", "some string")
Expect(val1).To(Equal(val2))
Expect(val1).To(Equal(val3))
})
It("is (nearly) perfect (injective function)", func() {
hashes := map[string]int{}
for i := 0; i < 1000; i++ {
name := fmt.Sprintf("string %d", i)
hashes[MustFormatHashWithPrefix(8, "", name)]++
}
for key, count := range hashes {
Expect(count).To(Equal(1), "for key "+key+" got non-unique correspondence")
}
})
assertPanicWith := func(f func(), expectedErrorMessage string) {
defer func() {
Expect(recover()).To(Equal(expectedErrorMessage))
}()
f()
Fail("function should have panicked but did not")
}
It("panics when prefix is longer than the length", func() {
assertPanicWith(
func() { MustFormatHashWithPrefix(3, "AAA", "some string") },
"invalid length",
)
})
It("panics when length is not positive", func() {
assertPanicWith(
func() { MustFormatHashWithPrefix(0, "", "some string") },
"invalid length",
)
})
It("panics when length is larger than MaxLen", func() {
assertPanicWith(
func() { MustFormatHashWithPrefix(MaxHashLen+1, "", "some string") },
"invalid length",
)
})
})
It("must change when a character changes", func() {
chain1 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
chain2 := FormatChainName("testalongnamethatdoesnotmakesense", "1235")
Expect(len(chain1)).To(Equal(maxChainLength))
Expect(len(chain2)).To(Equal(maxChainLength))
Expect(chain1).To(Equal("CNI-374f33fe84ab0ed84dcdebe3"))
Expect(chain1).NotTo(Equal(chain2))
})
})

View File

@@ -1,39 +1,4 @@
# dhcp plugin
## Overview
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
With dhcp plugin the containers can get an IP allocated by a DHCP server already running on your network.
This can be especially useful with plugin types such as [macvlan](../../main/macvlan/README.md).
Because a DHCP lease must be periodically renewed for the duration of container lifetime, a separate daemon is required to be running.
The same plugin binary can also be run in the daemon mode.
## Operation
To use the dhcp IPAM plugin, first launch the dhcp daemon:
```
# Make sure the unix socket has been removed
$ rm -f /run/cni/dhcp.sock
$ ./dhcp daemon
```
If given `-pidfile <path>` arguments after 'daemon', the dhcp plugin will write
its PID to the given file.
If given `-hostprefix <prefix>` arguments after 'daemon', the dhcp plugin will use this prefix for DHCP socket as `<prefix>/run/cni/dhcp.sock`. You can use this prefix for references to the host filesystem, e.g. to access netns and the unix socket.
Alternatively, you can use systemd socket activation protocol.
Be sure that the .socket file uses /run/cni/dhcp.sock as the socket path.
With the daemon running, containers using the dhcp plugin can be launched.
## Example configuration
```
{
"ipam": {
"type": "dhcp",
}
}
## Network configuration reference
* `type` (string, required): "dhcp"
You can find it online here: https://cni.dev/plugins/ipam/dhcp/

View File

@@ -34,7 +34,6 @@ import (
)
const listenFdsStart = 3
const resendCount = 3
var errNoMoreTries = errors.New("no more tries")

View File

@@ -34,7 +34,7 @@ import (
// RFC 2131 suggests using exponential backoff, starting with 4sec
// and randomized to +/- 1sec
const resendDelay0 = 4 * time.Second
const resendDelayMax = 32 * time.Second
const resendDelayMax = 62 * time.Second
const (
leaseStateBound = iota
@@ -335,8 +335,9 @@ func jitter(span time.Duration) time.Duration {
func backoffRetry(f func() (*dhcp4.Packet, error)) (*dhcp4.Packet, error) {
var baseDelay time.Duration = resendDelay0
var sleepTime time.Duration
for i := 0; i < resendCount; i++ {
for {
pkt, err := f()
if err == nil {
return pkt, nil
@@ -344,10 +345,16 @@ func backoffRetry(f func() (*dhcp4.Packet, error)) (*dhcp4.Packet, error) {
log.Print(err)
time.Sleep(baseDelay + jitter(time.Second))
sleepTime = baseDelay + jitter(time.Second)
log.Printf("retrying in %f seconds", sleepTime.Seconds())
time.Sleep(sleepTime)
if baseDelay < resendDelayMax {
baseDelay *= 2
} else {
break
}
}

View File

@@ -1,142 +1,4 @@
# host-local IP address management plugin
host-local IPAM allocates IPv4 and IPv6 addresses out of a specified address range. Optionally,
it can include a DNS configuration from a `resolv.conf` file on the host.
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
## Overview
host-local IPAM plugin allocates ip addresses out of a set of address ranges.
It stores the state locally on the host filesystem, therefore ensuring uniqueness of IP addresses on a single host.
The allocator can allocate multiple ranges, and supports sets of multiple (disjoint)
subnets. The allocation strategy is loosely round-robin within each range set.
## Example configurations
Note that the key `ranges` is a list of range sets. That is to say, the length
of the top-level array is the number of addresses returned. The second-level
array is a set of subnets to use as a pool of possible addresses.
This example configuration returns 2 IP addresses.
```json
{
"ipam": {
"type": "host-local",
"ranges": [
[
{
"subnet": "10.10.0.0/16",
"rangeStart": "10.10.1.20",
"rangeEnd": "10.10.3.50",
"gateway": "10.10.0.254"
},
{
"subnet": "172.16.5.0/24"
}
],
[
{
"subnet": "3ffe:ffff:0:01ff::/64",
"rangeStart": "3ffe:ffff:0:01ff::0010",
"rangeEnd": "3ffe:ffff:0:01ff::0020"
}
]
],
"routes": [
{ "dst": "0.0.0.0/0" },
{ "dst": "192.168.0.0/16", "gw": "10.10.5.1" },
{ "dst": "3ffe:ffff:0:01ff::1/64" }
],
"dataDir": "/run/my-orchestrator/container-ipam-state"
}
}
```
Previous versions of the `host-local` allocator did not support the `ranges`
property, and instead expected a single range on the top level. This is
deprecated but still supported.
```json
{
"ipam": {
"type": "host-local",
"subnet": "3ffe:ffff:0:01ff::/64",
"rangeStart": "3ffe:ffff:0:01ff::0010",
"rangeEnd": "3ffe:ffff:0:01ff::0020",
"routes": [
{ "dst": "3ffe:ffff:0:01ff::1/64" }
],
"resolvConf": "/etc/resolv.conf"
}
}
```
We can test it out on the command-line:
```bash
$ echo '{ "cniVersion": "0.3.1", "name": "examplenet", "ipam": { "type": "host-local", "ranges": [ [{"subnet": "203.0.113.0/24"}], [{"subnet": "2001:db8:1::/64"}]], "dataDir": "/tmp/cni-example" } }' | CNI_COMMAND=ADD CNI_CONTAINERID=example CNI_NETNS=/dev/null CNI_IFNAME=dummy0 CNI_PATH=. ./host-local
```
```json
{
"ips": [
{
"version": "4",
"address": "203.0.113.2/24",
"gateway": "203.0.113.1"
},
{
"version": "6",
"address": "2001:db8:1::2/64",
"gateway": "2001:db8:1::1"
}
],
"dns": {}
}
```
## Network configuration reference
* `type` (string, required): "host-local".
* `routes` (string, optional): list of routes to add to the container namespace. Each route is a dictionary with "dst" and optional "gw" fields. If "gw" is omitted, value of "gateway" will be used.
* `resolvConf` (string, optional): Path to a `resolv.conf` on the host to parse and return as the DNS configuration
* `dataDir` (string, optional): Path to a directory to use for maintaining state, e.g. which IPs have been allocated to which containers
* `ranges`, (array, required, nonempty) an array of arrays of range objects:
* `subnet` (string, required): CIDR block to allocate out of.
* `rangeStart` (string, optional): IP inside of "subnet" from which to start allocating addresses. Defaults to ".2" IP inside of the "subnet" block.
* `rangeEnd` (string, optional): IP inside of "subnet" with which to end allocating addresses. Defaults to ".254" IP inside of the "subnet" block for ipv4, ".255" for IPv6
* `gateway` (string, optional): IP inside of "subnet" to designate as the gateway. Defaults to ".1" IP inside of the "subnet" block.
Older versions of the `host-local` plugin did not support the `ranges` array. Instead,
all the properties in the `range` object were top-level. This is still supported but deprecated.
## Supported arguments
The following [CNI_ARGS](https://github.com/containernetworking/cni/blob/master/SPEC.md#parameters) are supported:
* `ip`: request a specific IP address from a subnet.
The following [args conventions](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md) are supported:
* `ips` (array of strings): A list of custom IPs to attempt to allocate
The following [Capability Args](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md) are supported:
* `ipRanges`: The exact same as the `ranges` array - a list of address pools
### Custom IP allocation
For every requested custom IP, the `host-local` allocator will request that IP
if it falls within one of the `range` objects. Thus it is possible to specify
multiple custom IPs and multiple ranges.
If any requested IPs cannot be reserved, either because they are already in use
or are not part of a specified range, the plugin will return an error.
## Files
Allocated IP addresses are stored as files in `/var/lib/cni/networks/$NETWORK_NAME`.
The path can be customized with the `dataDir` option listed above. Environments
where IPs are released automatically on reboot (e.g. running containers are not
restored) may wish to specify `/var/run/cni` or another tmpfs mounted directory
instead.
You can find it online here: https://cni.dev/plugins/ipam/host-local/

View File

@@ -40,7 +40,7 @@ func NewIPAllocator(s *RangeSet, store backend.Store, id int) *IPAllocator {
}
}
// Get alocates an IP
// Get allocates an IP
func (a *IPAllocator) Get(id string, ifname string, requestedIP net.IP) (*current.IPConfig, error) {
a.store.Lock()
defer a.store.Unlock()
@@ -73,6 +73,17 @@ func (a *IPAllocator) Get(id string, ifname string, requestedIP net.IP) (*curren
gw = r.Gateway
} else {
// try to get allocated IPs for this given id, if exists, just return error
// because duplicate allocation is not allowed in SPEC
// https://github.com/containernetworking/cni/blob/master/SPEC.md
allocatedIPs := a.store.GetByID(id, ifname)
for _, allocatedIP := range allocatedIPs {
// check whether the existing IP belong to this range set
if _, err := a.rangeset.RangeFor(allocatedIP); err == nil {
return nil, fmt.Errorf("%s has been allocated to %s, duplicate allocation is not allowed", allocatedIP.String(), id)
}
}
iter, err := a.GetIter()
if err != nil {
return nil, err

View File

@@ -221,14 +221,14 @@ var _ = Describe("host-local ip allocator", func() {
It("should not allocate the broadcast address", func() {
alloc := mkalloc()
for i := 2; i < 7; i++ {
res, err := alloc.Get("ID", "eth0", nil)
res, err := alloc.Get(fmt.Sprintf("ID%d", i), "eth0", nil)
Expect(err).ToNot(HaveOccurred())
s := fmt.Sprintf("192.168.1.%d/29", i)
Expect(s).To(Equal(res.Address.String()))
fmt.Fprintln(GinkgoWriter, "got ip", res.Address.String())
}
x, err := alloc.Get("ID", "eth0", nil)
x, err := alloc.Get("ID8", "eth0", nil)
fmt.Fprintln(GinkgoWriter, "got ip", x)
Expect(err).To(HaveOccurred())
})

View File

@@ -19,10 +19,10 @@ import (
"net"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend"
"runtime"
)
const lastIPFilePrefix = "last_reserved_ip."
@@ -172,6 +172,35 @@ func (s *Store) ReleaseByID(id string, ifname string) error {
return err
}
// GetByID returns the IPs which have been allocated to the specific ID
func (s *Store) GetByID(id string, ifname string) []net.IP {
var ips []net.IP
match := strings.TrimSpace(id) + LineBreak + ifname
// matchOld for backwards compatibility
matchOld := strings.TrimSpace(id)
// walk through all ips in this network to get the ones which belong to a specific ID
_ = filepath.Walk(s.dataDir, func(path string, info os.FileInfo, err error) error {
if err != nil || info.IsDir() {
return nil
}
data, err := ioutil.ReadFile(path)
if err != nil {
return nil
}
if strings.TrimSpace(string(data)) == match || strings.TrimSpace(string(data)) == matchOld {
_, ipString := filepath.Split(path)
if ip := net.ParseIP(ipString); ip != nil {
ips = append(ips, ip)
}
}
return nil
})
return ips
}
func GetEscapedPath(dataDir string, fname string) string {
if runtime.GOOS == "windows" {
fname = strings.Replace(fname, ":", "_", -1)

View File

@@ -24,4 +24,5 @@ type Store interface {
LastReservedIP(rangeID string) (net.IP, error)
Release(ip net.IP) error
ReleaseByID(id string, ifname string) error
GetByID(id string, ifname string) []net.IP
}

View File

@@ -81,6 +81,16 @@ func (s *FakeStore) ReleaseByID(id string, ifname string) error {
return nil
}
func (s *FakeStore) GetByID(id string, ifname string) []net.IP {
var ips []net.IP
for k, v := range s.ipMap {
if v == id {
ips = append(ips, net.ParseIP(k))
}
}
return ips
}
func (s *FakeStore) SetIPMap(m map[string]string) {
s.ipMap = m
}

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

@@ -250,6 +250,94 @@ var _ = Describe("host-local Operations", func() {
Expect(err).To(HaveOccurred())
})
It("repeat allocating addresses on specific interface for same container ID with ADD", func() {
const ifname string = "eth0"
const nspath string = "/some/where"
tmpDir, err := getTmpDir()
Expect(err).NotTo(HaveOccurred())
defer os.RemoveAll(tmpDir)
conf := fmt.Sprintf(`{
"cniVersion": "0.3.1",
"name": "mynet0",
"type": "ipvlan",
"master": "foo0",
"ipam": {
"type": "host-local",
"dataDir": "%s",
"ranges": [
[{ "subnet": "10.1.2.0/24" }]
]
}
}`, tmpDir)
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: nspath,
IfName: ifname,
StdinData: []byte(conf),
}
args1 := &skel.CmdArgs{
ContainerID: "dummy1",
Netns: nspath,
IfName: ifname,
StdinData: []byte(conf),
}
// Allocate the IP
r0, raw, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
result0, err := current.GetResult(r0)
Expect(err).NotTo(HaveOccurred())
Expect(len(result0.IPs)).Should(Equal(1))
Expect(result0.IPs[0].Address.String()).Should(Equal("10.1.2.2/24"))
// Allocate the IP with the same container ID
_, _, err = testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).To(HaveOccurred())
// Allocate the IP with the another container ID
r1, raw, err := testutils.CmdAddWithArgs(args1, func() error {
return cmdAdd(args1)
})
Expect(err).NotTo(HaveOccurred())
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
result1, err := current.GetResult(r1)
Expect(err).NotTo(HaveOccurred())
Expect(len(result1.IPs)).Should(Equal(1))
Expect(result1.IPs[0].Address.String()).Should(Equal("10.1.2.3/24"))
// Allocate the IP with the same container ID again
_, _, err = testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).To(HaveOccurred())
ipFilePath := filepath.Join(tmpDir, "mynet0", "10.1.2.2")
// Release the IPs
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
_, err = os.Stat(ipFilePath)
Expect(err).To(HaveOccurred())
err = testutils.CmdDelWithArgs(args1, func() error {
return cmdDel(args1)
})
Expect(err).NotTo(HaveOccurred())
})
It("Verify DEL works on backwards compatible allocate", func() {
const nspath string = "/some/where"
const ifname string = "eth0"

View File

@@ -1,54 +1,4 @@
# static IP address management plugin
## Overview
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
static IPAM is very simple IPAM plugin that assigns IPv4 and IPv6 addresses statically to container. This will be useful in debugging purpose and in case of assign same IP address in different vlan/vxlan to containers.
## Example configuration
```
{
"ipam": {
"type": "static",
"addresses": [
{
"address": "10.10.0.1/24",
"gateway": "10.10.0.254"
},
{
"address": "3ffe:ffff:0:01ff::1/64",
"gateway": "3ffe:ffff:0::1"
}
],
"routes": [
{ "dst": "0.0.0.0/0" },
{ "dst": "192.168.0.0/16", "gw": "10.10.5.1" },
{ "dst": "3ffe:ffff:0:01ff::1/64" }
],
"dns": {
"nameservers" : ["8.8.8.8"],
"domain": "example.com",
"search": [ "example.com" ]
}
}
}
```
## Network configuration reference
* `type` (string, required): "static"
* `addresses` (array, optional): an array of ip address objects:
* `address` (string, required): CIDR notation IP address.
* `gateway` (string, optional): IP inside of "subnet" to designate as the gateway.
* `routes` (string, optional): list of routes add to the container namespace. Each route is a dictionary with "dst" and optional "gw" fields. If "gw" is omitted, value of "gateway" will be used.
* `dns` (string, optional): the dictionary with "nameservers", "domain" and "search".
## Supported arguments
The following [CNI_ARGS](https://github.com/containernetworking/cni/blob/master/SPEC.md#parameters) are supported:
* `IP`: request a specific CIDR notation IP addresses, comma separated
* `GATEWAY`: request a specific gateway address
(example: CNI_ARGS="IP=10.10.0.1/24;GATEWAY=10.10.0.254")
You can find it online here: https://cni.dev/plugins/ipam/static/

View File

@@ -34,6 +34,13 @@ type Net struct {
Name string `json:"name"`
CNIVersion string `json:"cniVersion"`
IPAM *IPAMConfig `json:"ipam"`
RuntimeConfig struct {
IPs []string `json:"ips,omitempty"`
} `json:"runtimeConfig,omitempty"`
Args *struct {
A *IPAMArgs `json:"cni"`
} `json:"args"`
}
type IPAMConfig struct {
@@ -50,6 +57,10 @@ type IPAMEnvArgs struct {
GATEWAY types.UnmarshallableString `json:"gateway,omitempty"`
}
type IPAMArgs struct {
IPs []string `json:"ips"`
}
type Address struct {
AddressStr string `json:"address"`
Gateway net.IP `json:"gateway,omitempty"`
@@ -134,6 +145,65 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
return nil, "", err
}
// 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
}
}
}
}
}
// import address from args
if n.Args != nil && n.Args.A != nil && len(n.Args.A.IPs) != 0 {
// args IP overwrites IP, so clear IPAM Config
n.IPAM.Addresses = make([]Address, 0, len(n.Args.A.IPs))
for _, addr := range n.Args.A.IPs {
n.IPAM.Addresses = append(n.IPAM.Addresses, Address{AddressStr: addr})
}
}
// 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")
}
@@ -163,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

@@ -265,6 +265,221 @@ var _ = Describe("static Operations", func() {
})
Expect(err).NotTo(HaveOccurred())
})
It("allocates and releases multiple addresses with ADD/DEL, from RuntimeConfig", 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 := &skel.CmdArgs{
ContainerID: "dummy",
Netns: nspath,
IfName: ifname,
StdinData: []byte(conf),
}
// 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())
// Gomega is cranky about slices with different caps
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())
})
It("allocates and releases multiple addresses with ADD/DEL, from args", func() {
const ifname string = "eth0"
const nspath string = "/some/where"
conf := `{
"cniVersion": "0.3.1",
"name": "mynet",
"type": "ipvlan",
"master": "foo0",
"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" ]
}
},
"args": {
"cni": {
"ips" : ["10.10.0.1/24", "3ffe:ffff:0:01ff::1/64"]
}
}
}`
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: nspath,
IfName: ifname,
StdinData: []byte(conf),
}
// 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())
// Gomega is cranky about slices with different caps
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())
})
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

@@ -10,3 +10,4 @@ plugins/meta/portmap
plugins/meta/tuning
plugins/meta/bandwidth
plugins/meta/firewall
plugins/meta/vrf

View File

@@ -1,59 +1,5 @@
# bridge plugin
## Overview
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
With bridge plugin, all containers (on the same host) are plugged into a bridge (virtual switch) that resides in the host network namespace.
The containers receive one end of the veth pair with the other end connected to the bridge.
An IP address is only assigned to one end of the veth pair -- one residing in the container.
The bridge itself can also be assigned an IP address, turning it into a gateway for the containers.
Alternatively, the bridge can function purely in L2 mode and would need to be bridged to the host network interface (if other than container-to-container communication on the same host is desired).
You can find it online here: https://cni.dev/plugins/main/bridge/
The network configuration specifies the name of the bridge to be used.
If the bridge is missing, the plugin will create one on first use and, if gateway mode is used, assign it an IP that was returned by IPAM plugin via the gateway field.
## Example configuration
```
{
"name": "mynet",
"type": "bridge",
"bridge": "mynet0",
"isDefaultGateway": true,
"forceAddress": false,
"ipMasq": true,
"hairpinMode": true,
"ipam": {
"type": "host-local",
"subnet": "10.10.0.0/16"
}
}
```
## Example L2-only configuration
```
{
"cniVersion": "0.3.1",
"name": "mynet",
"type": "bridge",
"bridge": "mynet0",
"ipam": {}
}
```
## Network configuration reference
* `name` (string, required): the name of the network.
* `type` (string, required): "bridge".
* `bridge` (string, optional): name of the bridge to use/create. Defaults to "cni0".
* `isGateway` (boolean, optional): assign an IP address to the bridge. Defaults to false.
* `isDefaultGateway` (boolean, optional): Sets isGateway to true and makes the assigned IP the default route. Defaults to false.
* `forceAddress` (boolean, optional): Indicates if a new IP address should be set if the previous value has been changed. Defaults to false.
* `ipMasq` (boolean, optional): set up IP Masquerade on the host for traffic originating from this network and destined outside of it. Defaults to false.
* `mtu` (integer, optional): explicitly set MTU to the specified value. Defaults to the value chosen by the kernel.
* `hairpinMode` (boolean, optional): set hairpin mode for interfaces on the bridge. Defaults to false.
* `ipam` (dictionary, required): IPAM configuration to be used for this network. For L2-only network, create empty dictionary.
* `promiscMode` (boolean, optional): set promiscuous mode on the bridge. Defaults to false.
* `vlan` (int, optional): assign VLAN tag. Defaults to none.
*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```.

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
}
@@ -175,7 +180,7 @@ func ensureAddr(br netlink.Link, family int, ipn *net.IPNet, forceAddress bool)
}
addr := &netlink.Addr{IPNet: ipn, Label: ""}
if err := netlink.AddrAdd(br, addr); err != nil {
if err := netlink.AddrAdd(br, addr); err != nil && err != syscall.EEXIST {
return fmt.Errorf("could not add IP address to %q: %v", br.Attrs().Name, err)
}
@@ -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)
@@ -517,7 +550,7 @@ func cmdAdd(args *skel.CmdArgs) error {
chain := utils.FormatChainName(n.Name, args.ContainerID)
comment := utils.FormatComment(n.Name, args.ContainerID)
for _, ipc := range result.IPs {
if err = ip.SetupIPMasq(ip.Network(&ipc.Address), chain, comment); err != nil {
if err = ip.SetupIPMasq(&ipc.Address, chain, comment); err != nil {
return err
}
}

View File

@@ -17,15 +17,17 @@ package main
import (
"encoding/json"
"fmt"
"github.com/vishvananda/netlink/nl"
"io/ioutil"
"net"
"os"
"strings"
"github.com/coreos/go-iptables/iptables"
"github.com/vishvananda/netlink/nl"
"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"
@@ -70,6 +72,7 @@ type testCase struct {
isLayer2 bool
expGWCIDRs []string // Expected gateway addresses in CIDR form
vlan int
ipMasq bool
}
// Range definition for each entry in the ranges list
@@ -105,8 +108,7 @@ const (
"vlan": %d`
netDefault = `,
"isDefaultGateway": true,
"ipMasq": false`
"isDefaultGateway": true`
ipamStartStr = `,
"ipam": {
@@ -115,6 +117,9 @@ const (
ipamDataDirStr = `,
"dataDir": "%s"`
ipMasqConfStr = `,
"ipMasq": %t`
// Single subnet configuration (legacy)
subnetConfStr = `,
"subnet": "%s"`
@@ -147,6 +152,9 @@ func (tc testCase) netConfJSON(dataDir string) string {
if tc.vlan != 0 {
conf += fmt.Sprintf(vlan, tc.vlan)
}
if tc.ipMasq {
conf += tc.ipMasqConfig()
}
if !tc.isLayer2 {
conf += netDefault
@@ -178,6 +186,11 @@ func (tc testCase) subnetConfig() string {
return conf
}
func (tc testCase) ipMasqConfig() string {
conf := fmt.Sprintf(ipMasqConfStr, tc.ipMasq)
return conf
}
func (tc testCase) rangesConfig() string {
conf := rangesStartStr
for i, tcRange := range tc.ranges {
@@ -763,7 +776,6 @@ func (tester *testerV03x) cmdAddTest(tc testCase, dataDir string) (*current.Resu
Expect(err).NotTo(HaveOccurred())
Expect(len(addrs)).To(Equal(len(expCIDRsV4)))
addrs, err = netlink.AddrList(link, netlink.FAMILY_V6)
Expect(len(addrs)).To(Equal(len(expCIDRsV6) + 1)) //add one for the link-local
Expect(err).NotTo(HaveOccurred())
// Ignore link local address which may or may not be
// ready when we read addresses.
@@ -1112,6 +1124,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() {
@@ -1595,4 +1608,84 @@ var _ = Describe("bridge Operations", func() {
cmdAddDelCheckTest(originalNS, tc, dataDir)
}
})
It("configures a bridge and ipMasq rules for 0.4.0 config", func() {
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
tc := testCase{
ranges: []rangeInfo{{
subnet: "10.1.2.0/24",
}},
ipMasq: true,
cniVersion: "0.4.0",
}
args := tc.createCmdArgs(originalNS, dataDir)
r, _, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
result, err := current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
Expect(result.IPs).Should(HaveLen(1))
ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
Expect(err).NotTo(HaveOccurred())
rules, err := ipt.List("nat", "POSTROUTING")
Expect(err).NotTo(HaveOccurred())
Expect(rules).Should(ContainElement(ContainSubstring(result.IPs[0].Address.IP.String())))
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
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

@@ -1,22 +1,5 @@
# host-device
Move an already-existing device into a container.
This simple plugin will move the requested device from the host's network namespace
to the container's. Nothing else will be done - no IPAM, no addresses.
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
The device can be specified with any one of three properties:
* `device`: The device name, e.g. `eth0`, `can0`
* `hwaddr`: A MAC address
* `kernelpath`: The kernel device kobj, e.g. `/sys/devices/pci0000:00/0000:00:1f.6`
You can find it online here: https://cni.dev/plugins/main/host-device/
For this plugin, `CNI_IFNAME` will be ignored. Upon DEL, the device will be moved back.
A sample configuration might look like:
```json
{
"cniVersion": "0.3.1",
"type": "host-device",
"device": "enp0s1"
}
```

View File

@@ -21,6 +21,7 @@ import (
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
"runtime"
"strings"
@@ -38,12 +39,20 @@ import (
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
const (
sysBusPCI = "/sys/bus/pci/devices"
)
//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
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() {
@@ -58,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.Device == "" && n.HWAddr == "" && n.KernelPath == "" {
return nil, fmt.Errorf(`specify either "device", "hwaddr" or "kernelpath"`)
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
}
@@ -75,7 +91,7 @@ func cmdAdd(args *skel.CmdArgs) error {
}
defer containerNs.Close()
hostDev, err := getLink(cfg.Device, cfg.HWAddr, cfg.KernelPath)
hostDev, err := getLink(cfg.Device, cfg.HWAddr, cfg.KernelPath, cfg.PCIAddr)
if err != nil {
return fmt.Errorf("failed to find host device: %v", err)
}
@@ -177,6 +193,10 @@ func moveLinkIn(hostDev netlink.Link, containerNs ns.NetNS, ifName string) (netl
if err != nil {
return fmt.Errorf("failed to find %q: %v", hostDev.Attrs().Name, err)
}
// Devices can be renamed only when down
if err = netlink.LinkSetDown(contDev); err != nil {
return fmt.Errorf("failed to set %q down: %v", hostDev.Attrs().Name, err)
}
// Save host device name into the container device's alias property
if err := netlink.LinkSetAlias(contDev, hostDev.Attrs().Name); err != nil {
return fmt.Errorf("failed to set alias to %q: %v", hostDev.Attrs().Name, err)
@@ -212,14 +232,23 @@ func moveLinkOut(containerNs ns.NetNS, ifName string) error {
}
// Devices can be renamed only when down
if err := netlink.LinkSetDown(dev); err != nil {
if err = netlink.LinkSetDown(dev); err != nil {
return fmt.Errorf("failed to set %q down: %v", ifName, err)
}
// Rename device to it's original name
if err := netlink.LinkSetName(dev, dev.Attrs().Alias); err != nil {
if err = netlink.LinkSetName(dev, dev.Attrs().Alias); err != nil {
return fmt.Errorf("failed to restore %q to original name %q: %v", ifName, dev.Attrs().Alias, err)
}
if err := netlink.LinkSetNsFd(dev, int(defaultNs.Fd())); err != nil {
defer func() {
if err != nil {
// if moving device to host namespace fails, we should revert device name
// to ifName to make sure that device can be found in retries
_ = netlink.LinkSetName(dev, ifName)
}
}()
if err = netlink.LinkSetNsFd(dev, int(defaultNs.Fd())); err != nil {
return fmt.Errorf("failed to move %q to host netns: %v", dev.Attrs().Alias, err)
}
return nil
@@ -240,7 +269,7 @@ func printLink(dev netlink.Link, cniVersion string, containerNs ns.NetNS) error
return types.PrintResult(&result, cniVersion)
}
func getLink(devname, hwaddr, kernelpath string) (netlink.Link, error) {
func getLink(devname, hwaddr, kernelpath, pciaddr string) (netlink.Link, error) {
links, err := netlink.LinkList()
if err != nil {
return nil, fmt.Errorf("failed to list node links: %v", err)
@@ -278,6 +307,24 @@ func getLink(devname, hwaddr, kernelpath string) (netlink.Link, error) {
}
}
}
} else if len(pciaddr) > 0 {
netDir := filepath.Join(sysBusPCI, pciaddr, "net")
if _, err := os.Lstat(netDir); err != nil {
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 {
return nil, fmt.Errorf("failed to read net directory %s: %q", netDir, err)
}
if len(fInfo) > 0 {
return netlink.LinkByName(fInfo[0].Name())
}
return nil, fmt.Errorf("failed to find device name for pci address %s", pciaddr)
}
return nil, fmt.Errorf("failed to find physical interface")

View File

@@ -40,6 +40,7 @@ type Net struct {
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
IPAM *IPAMConfig `json:"ipam,omitempty"`
DNS types.DNS `json:"dns"`
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
@@ -232,14 +233,15 @@ 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() {
var origLink netlink.Link
// prepare ifname in original namespace
err := originalNS.Do(func(ns.NetNS) error {
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
@@ -253,7 +255,6 @@ var _ = Describe("base functionality", func() {
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// call CmdAdd
targetNS, err := testutils.NewNS()
@@ -293,26 +294,24 @@ var _ = Describe("base functionality", func() {
}))
// assert that dummy0 is now in the target namespace
err = targetNS.Do(func(ns.NetNS) error {
_ = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(cniName)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
return nil
})
Expect(err).NotTo(HaveOccurred())
// assert that dummy0 is now NOT in the original namespace anymore
err = originalNS.Do(func(ns.NetNS) error {
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, err := netlink.LinkByName(ifname)
Expect(err).To(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// Check that deleting the device moves it back and restores the name
err = originalNS.Do(func(ns.NetNS) error {
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
@@ -323,15 +322,16 @@ var _ = Describe("base functionality", func() {
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("Works with a valid config with IPAM", func() {
var origLink netlink.Link
It("Test idempotence of CmdDel", func() {
var (
origLink netlink.Link
conflictLink netlink.Link
)
// prepare ifname in original namespace
err := originalNS.Do(func(ns.NetNS) error {
// prepare host device in original namespace
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
@@ -345,8 +345,142 @@ var _ = Describe("base functionality", func() {
Expect(err).NotTo(HaveOccurred())
return nil
})
// call CmdAdd
targetNS, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
cniName := "eth0"
conf := fmt.Sprintf(`{
"cniVersion": "0.3.0",
"name": "cni-plugin-host-device-test",
"type": "host-device",
"device": %q
}`, ifname)
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNS.Path(),
IfName: cniName,
StdinData: []byte(conf),
}
var resI types.Result
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
var err error
resI, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) })
return err
})
Expect(err).NotTo(HaveOccurred())
// check that the result is sane
res, err := current.NewResultFromResult(resI)
Expect(err).NotTo(HaveOccurred())
Expect(res.Interfaces).To(Equal([]*current.Interface{
{
Name: cniName,
Mac: origLink.Attrs().HardwareAddr.String(),
Sandbox: targetNS.Path(),
},
}))
// assert that dummy0 is now in the target namespace
_ = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(cniName)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
return nil
})
// assert that dummy0 is now NOT in the original namespace anymore
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, err := netlink.LinkByName(ifname)
Expect(err).To(HaveOccurred())
return nil
})
// create another conflict host device with same name "dummy0"
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: ifname,
},
})
Expect(err).NotTo(HaveOccurred())
conflictLink, err = netlink.LinkByName(ifname)
Expect(err).NotTo(HaveOccurred())
err = netlink.LinkSetUp(conflictLink)
Expect(err).NotTo(HaveOccurred())
return nil
})
// try to call CmdDel and fails
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).To(HaveOccurred())
return nil
})
// assert container interface "eth0" still exists in target namespace
_ = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(cniName)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
return nil
})
// remove the conflict host device
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = netlink.LinkDel(conflictLink)
Expect(err).NotTo(HaveOccurred())
return nil
})
// try to call CmdDel and succeed
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
// assert that dummy0 is now back in the original namespace
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, err := netlink.LinkByName(ifname)
Expect(err).NotTo(HaveOccurred())
return nil
})
})
It("Works with a valid config with IPAM", func() {
var origLink netlink.Link
// prepare ifname in original namespace
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: ifname,
},
})
Expect(err).NotTo(HaveOccurred())
origLink, err = netlink.LinkByName(ifname)
Expect(err).NotTo(HaveOccurred())
err = netlink.LinkSetUp(origLink)
Expect(err).NotTo(HaveOccurred())
return nil
})
// call CmdAdd
targetNS, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
@@ -393,7 +527,7 @@ var _ = Describe("base functionality", func() {
}))
// assert that dummy0 is now in the target namespace
err = targetNS.Do(func(ns.NetNS) error {
_ = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(cniName)
Expect(err).NotTo(HaveOccurred())
@@ -408,19 +542,17 @@ var _ = Describe("base functionality", func() {
return nil
})
Expect(err).NotTo(HaveOccurred())
// assert that dummy0 is now NOT in the original namespace anymore
err = originalNS.Do(func(ns.NetNS) error {
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, err := netlink.LinkByName(ifname)
Expect(err).To(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// Check that deleting the device moves it back and restores the name
err = originalNS.Do(func(ns.NetNS) error {
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
@@ -431,8 +563,6 @@ var _ = Describe("base functionality", func() {
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("fails an invalid config", func() {
@@ -449,7 +579,7 @@ var _ = Describe("base functionality", func() {
StdinData: []byte(conf),
}
_, _, err := testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) })
Expect(err).To(MatchError(`specify either "device", "hwaddr" or "kernelpath"`))
Expect(err).To(MatchError(`specify either "device", "hwaddr", "kernelpath" or "pciBusID"`))
})
@@ -457,7 +587,7 @@ var _ = Describe("base functionality", func() {
var origLink netlink.Link
// prepare ifname in original namespace
err := originalNS.Do(func(ns.NetNS) error {
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
@@ -471,7 +601,6 @@ var _ = Describe("base functionality", func() {
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// call CmdAdd
targetNS, err := testutils.NewNS()
@@ -511,23 +640,21 @@ var _ = Describe("base functionality", func() {
}))
// assert that dummy0 is now in the target namespace
err = targetNS.Do(func(ns.NetNS) error {
_ = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(cniName)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
return nil
})
Expect(err).NotTo(HaveOccurred())
// assert that dummy0 is now NOT in the original namespace anymore
err = originalNS.Do(func(ns.NetNS) error {
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, err := netlink.LinkByName(ifname)
Expect(err).To(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// call CmdCheck
n := &Net{}
@@ -554,7 +681,7 @@ var _ = Describe("base functionality", func() {
Expect(err).NotTo(HaveOccurred())
// Check that deleting the device moves it back and restores the name
err = originalNS.Do(func(ns.NetNS) error {
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
@@ -565,15 +692,13 @@ var _ = Describe("base functionality", func() {
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("Works with a valid 0.4.0 config with IPAM", func() {
var origLink netlink.Link
// prepare ifname in original namespace
err := originalNS.Do(func(ns.NetNS) error {
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
@@ -587,7 +712,6 @@ var _ = Describe("base functionality", func() {
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// call CmdAdd
targetNS, err := testutils.NewNS()
@@ -635,7 +759,7 @@ var _ = Describe("base functionality", func() {
}))
// assert that dummy0 is now in the target namespace
err = targetNS.Do(func(ns.NetNS) error {
_ = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(cniName)
Expect(err).NotTo(HaveOccurred())
@@ -650,16 +774,14 @@ var _ = Describe("base functionality", func() {
return nil
})
Expect(err).NotTo(HaveOccurred())
// assert that dummy0 is now NOT in the original namespace anymore
err = originalNS.Do(func(ns.NetNS) error {
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, err := netlink.LinkByName(ifname)
Expect(err).To(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// call CmdCheck
n := &Net{}
@@ -689,7 +811,7 @@ var _ = Describe("base functionality", func() {
Expect(err).NotTo(HaveOccurred())
// Check that deleting the device moves it back and restores the name
err = originalNS.Do(func(ns.NetNS) error {
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
@@ -700,8 +822,144 @@ var _ = Describe("base functionality", func() {
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("Test idempotence of CmdDel with 0.4.0 config", func() {
var (
origLink netlink.Link
conflictLink netlink.Link
)
// prepare host device in original namespace
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: ifname,
},
})
Expect(err).NotTo(HaveOccurred())
origLink, err = netlink.LinkByName(ifname)
Expect(err).NotTo(HaveOccurred())
err = netlink.LinkSetUp(origLink)
Expect(err).NotTo(HaveOccurred())
return nil
})
// call CmdAdd
targetNS, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
cniName := "eth0"
conf := fmt.Sprintf(`{
"cniVersion": "0.4.0",
"name": "cni-plugin-host-device-test",
"type": "host-device",
"device": %q
}`, ifname)
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNS.Path(),
IfName: cniName,
StdinData: []byte(conf),
}
var resI types.Result
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
var err error
resI, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) })
return err
})
Expect(err).NotTo(HaveOccurred())
// check that the result is sane
res, err := current.NewResultFromResult(resI)
Expect(err).NotTo(HaveOccurred())
Expect(res.Interfaces).To(Equal([]*current.Interface{
{
Name: cniName,
Mac: origLink.Attrs().HardwareAddr.String(),
Sandbox: targetNS.Path(),
},
}))
// assert that dummy0 is now in the target namespace
_ = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(cniName)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
return nil
})
// assert that dummy0 is now NOT in the original namespace anymore
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, err := netlink.LinkByName(ifname)
Expect(err).To(HaveOccurred())
return nil
})
// create another conflict host device with same name "dummy0"
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: ifname,
},
})
Expect(err).NotTo(HaveOccurred())
conflictLink, err = netlink.LinkByName(ifname)
Expect(err).NotTo(HaveOccurred())
err = netlink.LinkSetUp(conflictLink)
Expect(err).NotTo(HaveOccurred())
return nil
})
// try to call CmdDel and fails
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).To(HaveOccurred())
return nil
})
// assert container interface "eth0" still exists in target namespace
err = targetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(cniName)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
return nil
})
Expect(err).NotTo(HaveOccurred())
// remove the conflict host device
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = netlink.LinkDel(conflictLink)
Expect(err).NotTo(HaveOccurred())
return nil
})
// try to call CmdDel and succeed
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
// assert that dummy0 is now back in the original namespace
_ = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, err := netlink.LinkByName(ifname)
Expect(err).NotTo(HaveOccurred())
return nil
})
})
})

View File

@@ -1,45 +1,5 @@
# ipvlan plugin
## Overview
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
ipvlan is a new [addition](https://lwn.net/Articles/620087/) to the Linux kernel.
Like its cousin macvlan, it virtualizes the host interface.
However unlike macvlan which generates a new MAC address for each interface, ipvlan devices all share the same MAC.
The kernel driver inspects the IP address of each packet when making a decision about which virtual interface should process the packet.
You can find it online here: https://cni.dev/plugins/main/ipvlan/
Because all ipvlan interfaces share the MAC address with the host interface, DHCP can only be used in conjunction with ClientID (currently not supported by DHCP plugin).
## Example configuration
```
{
"name": "mynet",
"type": "ipvlan",
"master": "eth0",
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24"
}
}
```
## Network configuration reference
* `name` (string, required): the name of the network.
* `type` (string, required): "ipvlan".
* `master` (string, required unless chained): name of the host interface to enslave.
* `mode` (string, optional): one of "l2", "l3", "l3s". Defaults to "l2".
* `mtu` (integer, optional): explicitly set MTU to the specified value. Defaults to the value chosen by the kernel.
* `ipam` (dictionary, required unless chained): IPAM configuration to be used for this network.
## Notes
* `ipvlan` does not allow virtual interfaces to communicate with the master interface.
Therefore the container will not be able to reach the host via `ipvlan` interface.
Be sure to also have container join a network that provides connectivity to the host (e.g. `ptp`).
* A single master interface can not be enslaved by both `macvlan` and `ipvlan`.
* For IP allocation schemes that cannot be interface agnostic, the ipvlan plugin
can be chained with an earlier plugin that handles this logic. If `master` is
omitted, then the previous Result must contain a single interface name for the
ipvlan plugin to enslave. If `ipam` is omitted, then the previous Result is used
to configure the ipvlan interface.

View File

@@ -72,12 +72,18 @@ func loadConf(bytes []byte, cmdCheck bool) (*NetConf, string, error) {
}
if n.Master == "" {
if result == nil {
return nil, "", fmt.Errorf(`"master" field is required. It specifies the host interface name to virtualize`)
}
if len(result.Interfaces) == 1 && result.Interfaces[0].Name != "" {
n.Master = result.Interfaces[0].Name
defaultRouteInterface, err := getDefaultRouteInterfaceName()
if err != nil {
return nil, "", err
}
n.Master = defaultRouteInterface
} else {
return nil, "", fmt.Errorf("chained master failure. PrevResult lacks a single named interface")
if len(result.Interfaces) == 1 && result.Interfaces[0].Name != "" {
n.Master = result.Interfaces[0].Name
} else {
return nil, "", fmt.Errorf("chained master failure. PrevResult lacks a single named interface")
}
}
}
return n, n.CNIVersion, nil
@@ -167,6 +173,25 @@ func createIpvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interf
return ipvlan, nil
}
func getDefaultRouteInterfaceName() (string, error) {
routeToDstIP, err := netlink.RouteList(nil, netlink.FAMILY_ALL)
if err != nil {
return "", err
}
for _, v := range routeToDstIP {
if v.Dst == nil {
l, err := netlink.LinkByIndex(v.LinkIndex)
if err != nil {
return "", err
}
return l.Attrs().Name, nil
}
}
return "", fmt.Errorf("no default route interface found")
}
func cmdAdd(args *skel.CmdArgs) error {
n, cniVersion, err := loadConf(args.StdinData, false)
if err != nil {

View File

@@ -234,7 +234,7 @@ func ipvlanAddCheckDelTest(conf string, netName string, IFNAME string, originalN
args.StdinData = confString
// CNI Check on macvlan in the target namespace
// CNI Check on ipvlan in the target namespace
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
@@ -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() {
@@ -464,4 +465,50 @@ var _ = Describe("ipvlan Operations", func() {
ipvlanAddCheckDelTest(conf, "ipvlanTest2", IFNAME, originalNS)
})
It("configures and deconfigures a ipvlan link with ADD/DEL, without master config", func() {
const IFNAME = "ipvl0"
conf := `{
"cniVersion": "0.3.1",
"name": "mynet",
"type": "ipvlan",
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24"
}
}`
targetNs, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
defer targetNs.Close()
// Make MASTER_NAME as default route interface
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(MASTER_NAME)
Expect(err).NotTo(HaveOccurred())
err = netlink.LinkSetUp(link)
Expect(err).NotTo(HaveOccurred())
var address = &net.IPNet{IP: net.IPv4(192, 0, 0, 1), Mask: net.CIDRMask(24, 32)}
var addr = &netlink.Addr{IPNet: address}
err = netlink.AddrAdd(link, addr)
Expect(err).NotTo(HaveOccurred())
// add default gateway into MASTER
dst := &net.IPNet{
IP: net.IPv4(0, 0, 0, 0),
Mask: net.CIDRMask(0, 0),
}
ip := net.IPv4(192, 0, 0, 254)
route := netlink.Route{LinkIndex: link.Attrs().Index, Dst: dst, Gw: ip}
err = netlink.RouteAdd(&route)
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
ipvlanAddDelTest(conf, IFNAME, originalNS)
})
})

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

@@ -45,21 +45,20 @@ var _ = Describe("Loopback", func() {
environ = []string{
fmt.Sprintf("CNI_CONTAINERID=%s", "dummy"),
fmt.Sprintf("CNI_NETNS=%s", networkNS.Path()),
fmt.Sprintf("CNI_IFNAME=%s", "this is ignored"),
fmt.Sprintf("CNI_IFNAME=%s", "lo"),
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

@@ -1,34 +1,5 @@
# macvlan plugin
## Overview
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
[macvlan](http://backreference.org/2014/03/20/some-notes-on-macvlanmacvtap/) functions like a switch that is already connected to the host interface.
A host interface gets "enslaved" with the virtual interfaces sharing the physical device but having distinct MAC addresses.
Since each macvlan interface has its own MAC address, it makes it easy to use with existing DHCP servers already present on the network.
You can find it online here: https://cni.dev/plugins/main/macvlan/
## Example configuration
```
{
"name": "mynet",
"type": "macvlan",
"master": "eth0",
"ipam": {
"type": "dhcp"
}
}
```
## Network configuration reference
* `name` (string, required): the name of the network
* `type` (string, required): "macvlan"
* `master` (string, optional): name of the host interface to enslave. Defaults to default route interace.
* `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.
* `ipam` (dictionary, required): IPAM configuration to be used for this network. For interface only without ip address, create empty dictionary.
## Notes
* If are testing on a laptop, please remember that most wireless cards do not support being enslaved by macvlan.
* A single master interface can not be enslaved by both `macvlan` and `ipvlan`.

View File

@@ -45,6 +45,17 @@ type NetConf struct {
Master string `json:"master"`
Mode string `json:"mode"`
MTU int `json:"mtu"`
Mac string `json:"mac,omitempty"`
RuntimeConfig struct {
Mac string `json:"mac,omitempty"`
} `json:"runtimeConfig,omitempty"`
}
// MacEnvArgs represents CNI_ARG
type MacEnvArgs struct {
types.CommonArgs
MAC types.UnmarshallableString `json:"mac,omitempty"`
}
func init() {
@@ -73,7 +84,7 @@ func getDefaultRouteInterfaceName() (string, error) {
return "", fmt.Errorf("no default route interface found")
}
func loadConf(bytes []byte) (*NetConf, string, error) {
func loadConf(bytes []byte, envArgs string) (*NetConf, string, error) {
n := &NetConf{}
if err := json.Unmarshal(bytes, n); err != nil {
return nil, "", fmt.Errorf("failed to load netconf: %v", err)
@@ -85,9 +96,43 @@ 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)
}
if envArgs != "" {
e := MacEnvArgs{}
err := types.LoadArgs(envArgs, &e)
if err != nil {
return nil, "", err
}
if e.MAC != "" {
n.Mac = string(e.MAC)
}
}
if n.RuntimeConfig.Mac != "" {
n.Mac = n.RuntimeConfig.Mac
}
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":
@@ -138,14 +183,24 @@ func createMacvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Inter
return nil, err
}
linkAttrs := netlink.LinkAttrs{
MTU: conf.MTU,
Name: tmpName,
ParentIndex: m.Attrs().Index,
Namespace: netlink.NsFd(int(netns.Fd())),
}
if conf.Mac != "" {
addr, err := net.ParseMAC(conf.Mac)
if err != nil {
return nil, fmt.Errorf("invalid args %v for MAC addr: %v", conf.Mac, err)
}
linkAttrs.HardwareAddr = addr
}
mv := &netlink.Macvlan{
LinkAttrs: netlink.LinkAttrs{
MTU: conf.MTU,
Name: tmpName,
ParentIndex: m.Attrs().Index,
Namespace: netlink.NsFd(int(netns.Fd())),
},
Mode: mode,
LinkAttrs: linkAttrs,
Mode: mode,
}
if err := netlink.LinkAdd(mv); err != nil {
@@ -186,7 +241,7 @@ func createMacvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Inter
}
func cmdAdd(args *skel.CmdArgs) error {
n, cniVersion, err := loadConf(args.StdinData)
n, cniVersion, err := loadConf(args.StdinData, args.Args)
if err != nil {
return err
}
@@ -293,7 +348,7 @@ func cmdAdd(args *skel.CmdArgs) error {
}
func cmdDel(args *skel.CmdArgs) error {
n, _, err := loadConf(args.StdinData)
n, _, err := loadConf(args.StdinData, args.Args)
if err != nil {
return err
}
@@ -331,7 +386,7 @@ func main() {
func cmdCheck(args *skel.CmdArgs) error {
n, _, err := loadConf(args.StdinData)
n, _, err := loadConf(args.StdinData, args.Args)
if err != nil {
return err
}

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() {
@@ -593,4 +594,176 @@ var _ = Describe("macvlan Operations", func() {
Expect(err).NotTo(HaveOccurred())
})
It("configures and deconfigures l2 macvlan link with mac address (from CNI_ARGS) with CNI v4 ADD/DEL", func() {
const IFNAME = "macvl0"
conf := fmt.Sprintf(`{
"cniVersion": "0.3.1",
"name": "mynet",
"type": "macvlan",
"master": "%s",
"ipam": {}
}`, MASTER_NAME)
targetNs, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
defer targetNs.Close()
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNs.Path(),
IfName: IFNAME,
StdinData: []byte(conf),
Args: "IgnoreUnknown=true;MAC=c2:11:22:33:44:55",
}
var result *current.Result
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, _, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
result, err = current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
Expect(len(result.Interfaces)).To(Equal(1))
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
Expect(len(result.IPs)).To(Equal(0))
return nil
})
Expect(err).NotTo(HaveOccurred())
// Make sure macvlan link exists in the target namespace
err = targetNs.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal(IFNAME))
hwaddr, err := net.ParseMAC("c2:11:22:33:44:55")
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(hwaddr))
addrs, err := netlink.AddrList(link, syscall.AF_INET)
Expect(err).NotTo(HaveOccurred())
Expect(len(addrs)).To(Equal(0))
return nil
})
Expect(err).NotTo(HaveOccurred())
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// Make sure macvlan link has been deleted
err = targetNs.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(IFNAME)
Expect(err).To(HaveOccurred())
Expect(link).To(BeNil())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("configures and deconfigures l2 macvlan link with mac address (from RuntimeConfig) with ADD/DEL", func() {
const IFNAME = "macvl0"
conf := fmt.Sprintf(`{
"cniVersion": "0.3.1",
"capabilities": {"mac": true},
"RuntimeConfig": {
"mac": "c2:11:22:33:44:55"
},
"name": "mynet",
"type": "macvlan",
"master": "%s",
"ipam": {}
}`, MASTER_NAME)
targetNs, err := testutils.NewNS()
Expect(err).NotTo(HaveOccurred())
defer targetNs.Close()
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNs.Path(),
IfName: IFNAME,
StdinData: []byte(conf),
}
var result *current.Result
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
r, _, err := testutils.CmdAddWithArgs(args, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
result, err = current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
Expect(len(result.Interfaces)).To(Equal(1))
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
Expect(len(result.IPs)).To(Equal(0))
return nil
})
Expect(err).NotTo(HaveOccurred())
// Make sure macvlan link exists in the target namespace
err = targetNs.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal(IFNAME))
hwaddr, err := net.ParseMAC("c2:11:22:33:44:55")
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().HardwareAddr).To(Equal(hwaddr))
addrs, err := netlink.AddrList(link, syscall.AF_INET)
Expect(err).NotTo(HaveOccurred())
Expect(len(addrs)).To(Equal(0))
return nil
})
Expect(err).NotTo(HaveOccurred())
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := testutils.CmdDelWithArgs(args, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// Make sure macvlan link has been deleted
err = targetNs.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName(IFNAME)
Expect(err).To(HaveOccurred())
Expect(link).To(BeNil())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
})

View File

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

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

@@ -1,55 +1,5 @@
# win-bridge plugin
## Overview
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
With win-bridge plugin, all containers (on the same host) are plugged into an L2Bridge network that has one endpoint in the host namespace.
You can find it online here: https://cni.dev/plugins/main/win-bridge/
## Example configuration
```
{
"name": "mynet",
"type": "win-bridge",
"ipMasqNetwork": "10.244.0.0/16",
"ipam": {
"type": "host-local",
"subnet": "10.10.0.0/16"
},
"policies":[
{
"name":"EndpointPolicy",
"value":{
"Type":"ROUTE",
"DestinationPrefix":"10.137.198.27/32",
"NeedEncap":true
}
}
],
"HcnPolicyArgs": [
{
"Type": "SDNRoute"
"Settings": {
"DestinationPrefix": "11.0.0.0/8",
"NeedEncap": true
}
}
].
"capabilities": {
"dns": true
}
}
```
## Network configuration reference
* `ApiVersion` (integer, optional): ApiVersion to use, will default to hns. If set to "2" will try to use hcn APIs.
* `name` (string, required): the name of the network.
* `type` (string, required): "win-bridge".
* `ipMasqNetwork` (string, optional): setup NAT if not empty.
* `dns` (dictionary, optional): dns config to be used.
* `Nameservers` (list, optional): list of strings to be used for dns nameservers.
* `Search` (list, optional): list of stings to be used for dns search.
* `ipam` (dictionary, optional): IPAM configuration to be used for this network.
* `Policies` (list, optional): List of hns policies to be used (only used when ApiVersion is < 2).
* `HcnPolicyArgs` (list, optional): List of hcn policies to be used (only used when ApiVersion is 2).
* `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.

View File

@@ -39,6 +39,7 @@
"NeedEncap":true
}
}
]
],
"loopbackDSR": true
}
}

View File

@@ -19,17 +19,16 @@ import (
"fmt"
"runtime"
"strings"
"os"
"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"
@@ -39,7 +38,6 @@ type NetConf struct {
hns.NetConf
IPMasqNetwork string `json:"ipMasqNetwork,omitempty"`
ApiVersion int `json:"ApiVersion"`
}
func init() {
@@ -74,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)
@@ -88,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
@@ -104,12 +105,11 @@ func cmdHnsAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
return nil, fmt.Errorf("network %v not found", networkName)
}
if !strings.EqualFold(hnsNetwork.Type, "L2Bridge") {
if !strings.EqualFold(hnsNetwork.Type, "L2Bridge") && !strings.EqualFold(hnsNetwork.Type, "L2Tunnel") {
return nil, fmt.Errorf("network %v is of an unexpected type: %v", networkName, hnsNetwork.Type)
}
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
@@ -132,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) {
@@ -146,7 +145,7 @@ func cmdHcnAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
return nil, fmt.Errorf("network %v not found", networkName)
}
if hcnNetwork.Type != hcn.L2Bridge {
if hcnNetwork.Type != hcn.L2Bridge && hcnNetwork.Type != hcn.L2Tunnel {
return nil, fmt.Errorf("network %v is of unexpected type: %v", networkName, hcnNetwork.Type)
}
@@ -191,14 +190,12 @@ func cmdAdd(args *skel.CmdArgs) error {
}
if err != nil {
os.Setenv("CNI_COMMAND", "DEL")
ipam.ExecDel(n.IPAM.Type, args.StdinData)
os.Setenv("CNI_COMMAND", "ADD")
return errors.Annotate(err, "error while executing ADD command")
}
if (result == nil) {
return errors.New("result for ADD not populated correctly")
if result == nil {
return fmt.Errorf("result for ADD not populated correctly")
}
return types.PrintResult(result, cniVersion)
}

View File

@@ -1,37 +1,5 @@
# win-overlay plugin
## Overview
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
With win-overlay plugin, all containers (on the same host) are plugged into an Overlay network based on VXLAN encapsulation.
You can find it online here: https://cni.dev/plugins/main/win-overlay/
## Example configuration
```
{
"name": "mynet",
"type": "win-overlay",
"ipMasq": true,
"endpointMacPrefix": "0E-2A",
"ipam": {
"type": "host-local",
"subnet": "10.10.0.0/16"
}
"capabilites": {
"dns": true
}
}
```
## Network configuration reference
* `name` (string, required): the name of the network.
* `type` (string, required): "win-overlay".
* `ipMasq` (bool, optional): the inverse of `$FLANNEL_IPMASQ`, setup NAT for the hnsNetwork subnet.
* `dns` (dictionary, optional): dns config to be used.
* `Nameservers` (list, optional): list of strings to be used for dns nameservers.
* `Search` (list, optional): list of stings to be used for dns search.
* `endpointMacPrefix` (string, optional): set to the MAC prefix configured for Flannel.
* `Policies` (list, optional): List of hns policies to be used.
* `ipam` (dictionary, required): IPAM configuration to be used for this network.
* `capabilities` (dictionary, optional): runtime capabilities to be parsed and injected by runtime.
* `dns` (boolean, optional): if true will take the dns config supplied by the runtime and override other settings.

View File

@@ -19,16 +19,15 @@ import (
"fmt"
"runtime"
"strings"
"os"
"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"
@@ -101,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
@@ -119,7 +118,9 @@ func cmdAdd(args *skel.CmdArgs) error {
}
result.DNS = n.GetDNS()
if n.LoopbackDSR {
n.ApplyLoopbackDSR(&ipAddr)
}
hnsEndpoint := &hcsshim.HNSEndpoint{
Name: epName,
VirtualNetwork: hnsNetwork.Id,
@@ -135,9 +136,7 @@ func cmdAdd(args *skel.CmdArgs) error {
})
defer func() {
if !success {
os.Setenv("CNI_COMMAND", "DEL")
ipam.ExecDel(n.IPAM.Type, args.StdinData)
os.Setenv("CNI_COMMAND", "ADD")
}
}()
if err != nil {

View File

@@ -1,64 +1,5 @@
# bandwidth plugin
## Overview
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
This plugin provides a way to use and configure Linux's Traffic control (tc) subystem. tc encompasses the sets of mechanisms and operations by which packets are queued for transmission/reception on a network interface.
You can find it online here: https://cni.dev/plugins/meta/bandwidth/
This plugin configures a token bucket filter (tbf) queuing discipline (qdisc) on both ingress and egress traffic. Resulting in traffic being shaped when reading / writing.
Due to limitations on tc shaping rules for ingress, this plugin creates an Intermediate Functional Block device (ifb) to redirect packets from the host interface. tc tbf is then applied to the ifb device. The packets that were redirected to the ifb devices, are written OUT (and shaped) to the host interface.
This plugin is only useful when used in addition to other plugins.
## Chaining
The bandwidth plugin applies traffic shaping to interfaces (as described above) created by previously applied plugins.
The following is an example [json configuration list](https://github.com/containernetworking/cni/blob/master/SPEC.md#network-configuration-list-runtime-examples) for creating a `ptp` between the host -> container via veth interfaces, whereby traffic is shaped by the `bandwidth` plugin:
```json
{
"cniVersion": "0.3.1",
"name": "mynet",
"plugins": [
{
"type": "ptp",
"ipMasq": true,
"mtu": 512,
"ipam": {
"type": "host-local",
"subnet": "10.0.0.0/24"
},
"dns": {
"nameservers": [ "10.1.0.1" ]
}
},
{
"name": "slowdown",
"type": "bandwidth",
"ingressRate": 123,
"ingressBurst": 456,
"egressRate": 123,
"egressBurst": 456
}
]
}
```
The result is an `ifb` device in the host namespace redirecting to the `host-interface`, with `tc tbf` applied on the `ifb` device and the `container-interface`
## Network configuration reference
* ingressRate: is the rate in bps at which traffic can enter an interface. (See http://man7.org/linux/man-pages/man8/tbf.8.html)
* ingressBurst: is the maximum amount in bits that tokens can be made available for instantaneously. (See http://man7.org/linux/man-pages/man8/tbf.8.html)
* egressRate: is the rate in bps at which traffic can leave an interface. (See http://man7.org/linux/man-pages/man8/tbf.8.html)
* egressBurst: is the maximum amount in bits that tokens can be made available for instantaneously. (See http://man7.org/linux/man-pages/man8/tbf.8.html)
Both ingressRate and ingressBurst must be set in order to limit ingress bandwidth. If neither one is set, then ingress bandwidth is not limited.
Both egressRate and egressBurst must be set in order to limit egress bandwidth. If neither one is set, then egress bandwidth is not limited.
## tc tbf documentation
- [tldp traffic control](http://tldp.org/HOWTO/Traffic-Control-HOWTO/components.html)
- [man tbf](http://man7.org/linux/man-pages/man8/tbf.8.html)
- [tc ingress and ifb mirroring](https://serverfault.com/questions/350023/tc-ingress-policing-and-ifb-mirroring)

View File

@@ -105,7 +105,7 @@ var _ = Describe("bandwidth test", func() {
hostIP = net.IP{169, 254, 0, 1}
containerIP = net.IP{10, 254, 0, 1}
hostIfaceMTU = 1024
ifbDeviceName = "5b6c"
ifbDeviceName = "bwpa8eda89404b7"
createVeth(hostNs.Path(), hostIfname, containerNs.Path(), containerIfname, hostIP, containerIP, hostIfaceMTU)
})
@@ -621,10 +621,276 @@ 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 := `{
"cniVersion": "0.4.0",
"name": "cni-plugin-bandwidth-test",
"type": "bandwidth",
"ingressRate": 8,
"ingressBurst": 8,
"egressRate": 16,
"egressBurst": 8,
"prevResult": {
"interfaces": [
{
"name": "%s",
"sandbox": ""
},
{
"name": "%s",
"sandbox": ""
},
{
"name": "%s",
"sandbox": ""
},
{
"name": "%s",
"sandbox": "%s"
}
],
"ips": [
{
"version": "4",
"address": "%s/24",
"gateway": "10.0.0.1",
"interface": 1
}
],
"routes": []
}
}`
// create veth peer in host ns
vethName, peerName := "host-veth-peer1", "host-veth-peer2"
createVethInOneNs(hostNs.Path(), vethName, peerName)
conf = fmt.Sprintf(conf, vethName, peerName, hostIfname, containerIfname, containerNs.Path(), containerIP.String())
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: containerNs.Path(),
IfName: containerIfname,
StdinData: []byte(conf),
}
Expect(hostNs.Do(func(netNS ns.NetNS) error {
defer GinkgoRecover()
r, out, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
Expect(err).NotTo(HaveOccurred(), string(out))
result, err := current.GetResult(r)
Expect(err).NotTo(HaveOccurred())
Expect(result.Interfaces).To(HaveLen(5))
Expect(result.Interfaces[4].Name).To(Equal(ifbDeviceName))
Expect(result.Interfaces[4].Sandbox).To(Equal(""))
ifbLink, err := netlink.LinkByName(ifbDeviceName)
Expect(err).NotTo(HaveOccurred())
Expect(ifbLink.Attrs().MTU).To(Equal(hostIfaceMTU))
qdiscs, err := netlink.QdiscList(ifbLink)
Expect(err).NotTo(HaveOccurred())
Expect(qdiscs).To(HaveLen(1))
Expect(qdiscs[0].Attrs().LinkIndex).To(Equal(ifbLink.Attrs().Index))
Expect(qdiscs[0]).To(BeAssignableToTypeOf(&netlink.Tbf{}))
Expect(qdiscs[0].(*netlink.Tbf).Rate).To(Equal(uint64(2)))
Expect(qdiscs[0].(*netlink.Tbf).Limit).To(Equal(uint32(1)))
hostVethLink, err := netlink.LinkByName(hostIfname)
Expect(err).NotTo(HaveOccurred())
qdiscFilters, err := netlink.FilterList(hostVethLink, netlink.MakeHandle(0xffff, 0))
Expect(err).NotTo(HaveOccurred())
Expect(qdiscFilters).To(HaveLen(1))
Expect(qdiscFilters[0].(*netlink.U32).Actions[0].(*netlink.MirredAction).Ifindex).To(Equal(ifbLink.Attrs().Index))
return nil
})).To(Succeed())
Expect(hostNs.Do(func(n ns.NetNS) error {
defer GinkgoRecover()
ifbLink, err := netlink.LinkByName(hostIfname)
Expect(err).NotTo(HaveOccurred())
qdiscs, err := netlink.QdiscList(ifbLink)
Expect(err).NotTo(HaveOccurred())
Expect(qdiscs).To(HaveLen(2))
Expect(qdiscs[0].Attrs().LinkIndex).To(Equal(ifbLink.Attrs().Index))
Expect(qdiscs[0]).To(BeAssignableToTypeOf(&netlink.Tbf{}))
Expect(qdiscs[0].(*netlink.Tbf).Rate).To(Equal(uint64(1)))
Expect(qdiscs[0].(*netlink.Tbf).Limit).To(Equal(uint32(1)))
return nil
})).To(Succeed())
})
It("Should fail when container interface has no veth peer", func() {
conf := `{
"cniVersion": "0.4.0",
"name": "cni-plugin-bandwidth-test",
"type": "bandwidth",
"ingressRate": 8,
"ingressBurst": 8,
"egressRate": 16,
"egressBurst": 8,
"prevResult": {
"interfaces": [
{
"name": "%s",
"sandbox": ""
},
{
"name": "%s",
"sandbox": "%s"
}
],
"ips": [
{
"version": "4",
"address": "%s/24",
"gateway": "10.0.0.1",
"interface": 1
}
],
"routes": []
}
}`
// create a macvlan device to be container interface
macvlanContainerIfname := "container-macv"
createMacvlan(containerNs.Path(), containerIfname, macvlanContainerIfname)
conf = fmt.Sprintf(conf, hostIfname, macvlanContainerIfname, containerNs.Path(), containerIP.String())
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: containerNs.Path(),
IfName: macvlanContainerIfname,
StdinData: []byte(conf),
}
Expect(hostNs.Do(func(netNS ns.NetNS) error {
defer GinkgoRecover()
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
Expect(err).To(HaveOccurred())
return nil
})).To(Succeed())
})
It("Should fail when preResult has no interfaces", func() {
conf := `{
"cniVersion": "0.4.0",
"name": "cni-plugin-bandwidth-test",
"type": "bandwidth",
"ingressRate": 8,
"ingressBurst": 8,
"egressRate": 16,
"egressBurst": 8,
"prevResult": {
"interfaces": [],
"ips": [],
"routes": []
}
}`
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: containerNs.Path(),
IfName: "eth0",
StdinData: []byte(conf),
}
Expect(hostNs.Do(func(netNS ns.NetNS) error {
defer GinkgoRecover()
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
Expect(err).To(HaveOccurred())
return nil
})).To(Succeed())
})
It("Should fail when veth peer of container interface does not match any of host interfaces in preResult", func() {
conf := `{
"cniVersion": "0.4.0",
"name": "cni-plugin-bandwidth-test",
"type": "bandwidth",
"ingressRate": 8,
"ingressBurst": 8,
"egressRate": 16,
"egressBurst": 8,
"prevResult": {
"interfaces": [
{
"name": "%s",
"sandbox": ""
},
{
"name": "%s",
"sandbox": "%s"
}
],
"ips": [
{
"version": "4",
"address": "%s/24",
"gateway": "10.0.0.1",
"interface": 1
}
],
"routes": []
}
}`
// fake a non-exist host interface name
fakeHostIfname := fmt.Sprintf("%s-fake", hostIfname)
conf = fmt.Sprintf(conf, fakeHostIfname, containerIfname, containerNs.Path(), containerIP.String())
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: containerNs.Path(),
IfName: containerIfname,
StdinData: []byte(conf),
}
Expect(hostNs.Do(func(netNS ns.NetNS) error {
defer GinkgoRecover()
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
Expect(err).To(HaveOccurred())
return nil
})).To(Succeed())
})
})
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
@@ -638,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
@@ -768,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
@@ -783,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

@@ -39,14 +39,18 @@ func TestTBF(t *testing.T) {
RunSpecs(t, "plugins/meta/bandwidth")
}
var echoServerBinaryPath string
var echoServerBinaryPath, echoClientBinaryPath string
var _ = SynchronizedBeforeSuite(func() []byte {
binaryPath, err := gexec.Build("github.com/containernetworking/plugins/pkg/testutils/echosvr")
serverBinaryPath, err := gexec.Build("github.com/containernetworking/plugins/pkg/testutils/echo/server")
Expect(err).NotTo(HaveOccurred())
return []byte(binaryPath)
clientBinaryPath, err := gexec.Build("github.com/containernetworking/plugins/pkg/testutils/echo/client")
Expect(err).NotTo(HaveOccurred())
return []byte(strings.Join([]string{serverBinaryPath, clientBinaryPath}, ","))
}, func(data []byte) {
echoServerBinaryPath = string(data)
binaries := strings.Split(string(data), ",")
echoServerBinaryPath = binaries[0]
echoClientBinaryPath = binaries[1]
})
var _ = SynchronizedAfterSuite(func() {}, func() {
@@ -84,23 +88,22 @@ func startEchoServerInNamespace(netNS ns.NetNS) (int, *gexec.Session, error) {
}
func makeTcpClientInNS(netns string, address string, port int, numBytes int) {
message := bytes.Repeat([]byte{'a'}, numBytes)
payload := bytes.Repeat([]byte{'a'}, numBytes)
message := string(payload)
bin, err := exec.LookPath("nc")
Expect(err).NotTo(HaveOccurred())
var cmd *exec.Cmd
if netns != "" {
netns = filepath.Base(netns)
cmd = exec.Command("ip", "netns", "exec", netns, bin, "-v", address, strconv.Itoa(port))
cmd = exec.Command("ip", "netns", "exec", netns, echoClientBinaryPath, "--target", fmt.Sprintf("%s:%d", address, port), "--message", message)
} else {
cmd = exec.Command("nc", address, strconv.Itoa(port))
cmd = exec.Command(echoClientBinaryPath, "--target", fmt.Sprintf("%s:%d", address, port), "--message", message)
}
cmd.Stdin = bytes.NewBuffer([]byte(message))
cmd.Stderr = GinkgoWriter
out, err := cmd.Output()
Expect(err).NotTo(HaveOccurred())
Expect(string(out)).To(Equal(string(message)))
Expect(string(out)).To(Equal(message))
}
func createVeth(hostNamespace string, hostVethIfName string, containerNamespace string, containerVethIfName string, hostIP []byte, containerIP []byte, hostIfaceMTU int) {
@@ -199,3 +202,61 @@ func createVeth(hostNamespace string, hostVethIfName string, containerNamespace
Expect(err).NotTo(HaveOccurred())
}
func createVethInOneNs(namespace, vethName, peerName string) {
vethDeviceRequest := &netlink.Veth{
LinkAttrs: netlink.LinkAttrs{
Name: vethName,
Flags: net.FlagUp,
},
PeerName: peerName,
}
netNS, err := ns.GetNS(namespace)
Expect(err).NotTo(HaveOccurred())
err = netNS.Do(func(_ ns.NetNS) error {
if err := netlink.LinkAdd(vethDeviceRequest); err != nil {
return fmt.Errorf("failed to create veth pair: %v", err)
}
_, err := netlink.LinkByName(peerName)
if err != nil {
return fmt.Errorf("failed to find newly-created veth device %q: %v", peerName, err)
}
return nil
})
Expect(err).NotTo(HaveOccurred())
}
func createMacvlan(namespace, master, macvlanName string) {
netNS, err := ns.GetNS(namespace)
Expect(err).NotTo(HaveOccurred())
err = netNS.Do(func(_ ns.NetNS) error {
m, err := netlink.LinkByName(master)
if err != nil {
return fmt.Errorf("failed to lookup master %q: %v", master, err)
}
macvlanDeviceRequest := &netlink.Macvlan{
LinkAttrs: netlink.LinkAttrs{
MTU: m.Attrs().MTU,
Name: macvlanName,
ParentIndex: m.Attrs().Index,
},
Mode: netlink.MACVLAN_MODE_BRIDGE,
}
if err = netlink.LinkAdd(macvlanDeviceRequest); err != nil {
return fmt.Errorf("failed to create macvlan device: %s", err)
}
_, err = netlink.LinkByName(macvlanName)
if err != nil {
return fmt.Errorf("failed to find newly-created macvlan device %q: %v", macvlanName, err)
}
return nil
})
Expect(err).NotTo(HaveOccurred())
}

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

@@ -15,10 +15,9 @@
package main
import (
"crypto/sha1"
"encoding/json"
"errors"
"fmt"
"math"
"github.com/vishvananda/netlink"
@@ -28,17 +27,22 @@ import (
"github.com/containernetworking/cni/pkg/version"
"github.com/containernetworking/plugins/pkg/ip"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containernetworking/plugins/pkg/utils"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
const maxIfbDeviceLength = 15
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 {
@@ -98,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")
@@ -106,19 +110,15 @@ 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
}
func getIfbDeviceName(networkName string, containerId string) (string, error) {
hash := sha1.New()
_, err := hash.Write([]byte(networkName + containerId))
if err != nil {
return "", err
}
return fmt.Sprintf("%x", hash.Sum(nil))[:4], nil
func getIfbDeviceName(networkName string, containerId string) string {
return utils.MustFormatHashWithPrefix(maxIfbDeviceLength, ifbDevicePrefix, networkName+containerId)
}
func getMTU(deviceName string) (int, error) {
@@ -130,21 +130,35 @@ func getMTU(deviceName string) (int, error) {
return link.Attrs().MTU, nil
}
func getHostInterface(interfaces []*current.Interface) (*current.Interface, error) {
// get the veth peer of container interface in host namespace
func getHostInterface(interfaces []*current.Interface, containerIfName string, netns ns.NetNS) (*current.Interface, error) {
if len(interfaces) == 0 {
return nil, errors.New("no interfaces provided")
return nil, fmt.Errorf("no interfaces provided")
}
// get veth peer index of container interface
var peerIndex int
var err error
_ = netns.Do(func(_ ns.NetNS) error {
_, peerIndex, err = ip.GetVethPeerIfindex(containerIfName)
return nil
})
if peerIndex <= 0 {
return nil, fmt.Errorf("container interface %s has no veth peer: %v", containerIfName, err)
}
// find host interface by index
link, err := netlink.LinkByIndex(peerIndex)
if err != nil {
return nil, fmt.Errorf("veth peer with index %d is not in host ns", peerIndex)
}
for _, iface := range interfaces {
if iface.Sandbox == "" { // host interface
_, _, err = ip.GetVethPeerIfindex(iface.Name)
if err == nil {
return iface, err
}
if iface.Sandbox == "" && iface.Name == link.Attrs().Name {
return iface, nil
}
}
return nil, errors.New(fmt.Sprintf("no host interface found. last error: %s", err))
return nil, fmt.Errorf("no veth peer of container interface found in host ns")
}
func cmdAdd(args *skel.CmdArgs) error {
@@ -166,7 +180,14 @@ func cmdAdd(args *skel.CmdArgs) error {
if err != nil {
return fmt.Errorf("could not convert result to current version: %v", err)
}
hostInterface, err := getHostInterface(result.Interfaces)
netns, err := ns.GetNS(args.Netns)
if err != nil {
return fmt.Errorf("failed to open netns %q: %v", netns, err)
}
defer netns.Close()
hostInterface, err := getHostInterface(result.Interfaces, args.IfName, netns)
if err != nil {
return err
}
@@ -184,10 +205,7 @@ func cmdAdd(args *skel.CmdArgs) error {
return err
}
ifbDeviceName, err := getIfbDeviceName(conf.Name, args.ContainerID)
if err != nil {
return err
}
ifbDeviceName := getIfbDeviceName(conf.Name, args.ContainerID)
err = CreateIfb(ifbDeviceName, mtu)
if err != nil {
@@ -218,10 +236,7 @@ func cmdDel(args *skel.CmdArgs) error {
return err
}
ifbDeviceName, err := getIfbDeviceName(conf.Name, args.ContainerID)
if err != nil {
return err
}
ifbDeviceName := getIfbDeviceName(conf.Name, args.ContainerID)
if err := TeardownIfb(ifbDeviceName); err != nil {
return err
@@ -266,7 +281,13 @@ func cmdCheck(args *skel.CmdArgs) error {
return fmt.Errorf("could not convert result to current version: %v", err)
}
hostInterface, err := getHostInterface(result.Interfaces)
netns, err := ns.GetNS(args.Netns)
if err != nil {
return fmt.Errorf("failed to open netns %q: %v", netns, err)
}
defer netns.Close()
hostInterface, err := getHostInterface(result.Interfaces, args.IfName, netns)
if err != nil {
return err
}
@@ -316,10 +337,7 @@ func cmdCheck(args *skel.CmdArgs) error {
latency := latencyInUsec(latencyInMillis)
limitInBytes := limit(uint64(rateInBytes), latency, uint32(burstInBytes))
ifbDeviceName, err := getIfbDeviceName(bwConf.Name, args.ContainerID)
if err != nil {
return err
}
ifbDeviceName := getIfbDeviceName(bwConf.Name, args.ContainerID)
ifbDevice, err := netlink.LinkByName(ifbDeviceName)
if err != nil {

View File

@@ -1,135 +1,5 @@
# firewall plugin
## Overview
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
This plugin creates firewall rules to allow traffic to/from container IP address via the host network .
It does not create any network interfaces and therefore does not set up connectivity by itself.
It is intended to be used as a chained plugins.
## Operation
The following network configuration file
```json
{
"cniVersion": "0.3.1",
"name": "bridge-firewalld",
"plugins": [
{
"type": "bridge",
"bridge": "cni0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "10.88.0.0/16",
"routes": [
{ "dst": "0.0.0.0/0" }
]
}
},
{
"type": "firewall",
}
]
}
```
will allow any IP addresses configured by earlier plugins to send/receive traffic via the host.
A successful result would simply be an empty result, unless a previous plugin passed a previous result, in which case this plugin will return that previous result.
## Backends
This plugin supports multiple firewall backends that implement the desired functionality.
Available backends include `iptables` and `firewalld` and may be selected with the `backend` key.
If no `backend` key is given, the plugin will use firewalld if the service exists on the D-Bus system bus.
If no firewalld service is found, it will fall back to iptables.
## firewalld backend rule structure
When the `firewalld` backend is used, this example will place the IPAM allocated address for the container (e.g. 10.88.0.2) into firewalld's `trusted` zone, allowing it to send/receive traffic.
A sample standalone config list (with the file extension .conflist) using firewalld backend might
look like:
```json
{
"cniVersion": "0.3.1",
"name": "bridge-firewalld",
"plugins": [
{
"type": "bridge",
"bridge": "cni0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "10.88.0.0/16",
"routes": [
{ "dst": "0.0.0.0/0" }
]
}
},
{
"type": "firewall",
"backend": "firewalld"
}
]
}
```
`FORWARD_IN_ZONES_SOURCE` chain:
- `-d 10.88.0.2 -j FWDI_trusted`
`CNI_FORWARD_OUT_ZONES_SOURCE` chain:
- `-s 10.88.0.2 -j FWDO_trusted`
## iptables backend rule structure
A sample standalone config list (with the file extension .conflist) using iptables backend might
look like:
```json
{
"cniVersion": "0.3.1",
"name": "bridge-firewalld",
"plugins": [
{
"type": "bridge",
"bridge": "cni0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "10.88.0.0/16",
"routes": [
{ "dst": "0.0.0.0/0" }
]
}
},
{
"type": "firewall",
"backend": "iptables"
}
]
}
```
When the `iptables` backend is used, the above example will create two new iptables chains in the `filter` table and add rules that allow the given interface to send/receive traffic.
### FORWARD
A new chain, CNI-FORWARD is added to the FORWARD chain. CNI-FORWARD is the chain where rules will be added
when containers are created and from where rules will be removed when containers terminate.
`FORWARD` chain:
- `-j CNI-FORWARD`
CNI-FORWARD will have a pair of rules added, one for each direction, using the IPAM assigned IP address
of the container as shown:
`CNI_FORWARD` chain:
- `-s 10.88.0.2 -m conntrack --ctstate RELATED,ESTABLISHED -j CNI-FORWARD`
- `-d 10.88.0.2 -j CNI-FORWARD`
You can find it online here: https://cni.dev/plugins/meta/firewall/

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}
}
@@ -70,13 +57,13 @@ func ensureFirstChainRule(ipt *iptables.IPTables, chain string, rule []string) e
func (ib *iptablesBackend) setupChains(ipt *iptables.IPTables) error {
privRule := generateFilterRule(ib.privChainName)
adminRule := generateFilterRule(ib.adminChainName)
adminRule := generateAdminRule(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
}
@@ -178,7 +165,7 @@ func (ib *iptablesBackend) checkRules(conf *FirewallNetConf, result *current.Res
}
// Ensure our admin override chain rule exists in our private chain
adminRule := generateFilterRule(ib.adminChainName)
adminRule := generateAdminRule(ib.adminChainName)
adminExists, err := ipt.Exists("filter", ib.privChainName, adminRule...)
if err != nil {
return err

View File

@@ -1,135 +1,5 @@
# flannel plugin
## Overview
This plugin is designed to work in conjunction with [flannel](https://github.com/coreos/flannel), a network fabric for containers.
When flannel daemon is started, it outputs a `/run/flannel/subnet.env` file that looks like this:
```
FLANNEL_NETWORK=10.1.0.0/16
FLANNEL_SUBNET=10.1.17.1/24
FLANNEL_MTU=1472
FLANNEL_IPMASQ=true
```
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
This information reflects the attributes of flannel network on the host.
The flannel CNI plugin uses this information to configure another CNI plugin, such as bridge plugin.
You can find it online here: https://cni.dev/plugins/meta/flannel/
## Operation
Given the following network configuration file and the contents of `/run/flannel/subnet.env` above,
```
{
"name": "mynet",
"type": "flannel"
}
```
the flannel plugin will generate another network configuration file:
```
{
"name": "mynet",
"type": "bridge",
"mtu": 1472,
"ipMasq": false,
"isGateway": true,
"ipam": {
"type": "host-local",
"subnet": "10.1.17.0/24"
}
}
```
It will then invoke the bridge plugin, passing it the generated configuration.
As can be seen from above, the flannel plugin, by default, will delegate to the bridge plugin.
If additional configuration values need to be passed to the bridge plugin, it can be done so via the `delegate` field:
```
{
"name": "mynet",
"type": "flannel",
"delegate": {
"bridge": "mynet0",
"mtu": 1400
}
}
```
This supplies a configuration parameter to the bridge plugin -- the created bridge will now be named `mynet0`.
Notice that `mtu` has also been specified and this value will not be overwritten by flannel plugin.
Additionally, the `delegate` field can be used to select a different kind of plugin altogether.
To use `ipvlan` instead of `bridge`, the following configuration can be specified:
```
{
"name": "mynet",
"type": "flannel",
"delegate": {
"type": "ipvlan",
"master": "eth0"
}
}
```
## Network configuration reference
* `name` (string, required): the name of the network
* `type` (string, required): "flannel"
* `subnetFile` (string, optional): full path to the subnet file written out by flanneld. Defaults to /run/flannel/subnet.env
* `dataDir` (string, optional): path to directory where plugin will store generated network configuration files. Defaults to `/var/lib/cni/flannel`
* `delegate` (dictionary, optional): specifies configuration options for the delegated plugin.
flannel plugin will always set the following fields in the delegated plugin configuration:
* `name`: value of its "name" field.
* `ipam`: "host-local" type will be used with "subnet" set to `$FLANNEL_SUBNET`.
flannel plugin will set the following fields in the delegated plugin configuration if they are not present:
* `ipMasq`: the inverse of `$FLANNEL_IPMASQ`
* `mtu`: `$FLANNEL_MTU`
Additionally, for the bridge plugin, `isGateway` will be set to `true`, if not present.
## 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.
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)
* `type` (string, optional): set to `win-l2bridge` by default. Can be set to `win-overlay` or other custom windows CNI
* `ipMasq`: the inverse of `$FLANNEL_IPMASQ`
* `endpointMacPrefix` (string, optional): required for `win-overlay` mode, set to the MAC prefix configured for Flannel
* `clusterNetworkPrefix` (string, optional): required for `win-l2bridge` mode, setup NAT if `ipMasq` is set to true
For `win-l2bridge`, the Flannel CNI plugin will set:
* `ipam`: "host-local" type will be used with "subnet" set to `$FLANNEL_SUBNET` and gateway as the .2 address in `$FLANNEL_NETWORK`
For `win-overlay`, the Flannel CNI plugin will set:
* `ipam`: "host-local" type will be used with "subnet" set to `$FLANNEL_SUBNET` and gateway as the .1 address in `$FLANNEL_NETWORK`
If IPMASQ is true, the Flannel CNI plugin will setup an OutBoundNAT policy and add FLANNEL_SUBNET to any existing exclusions.
All other delegate config e.g. other HNS endpoint policies in AdditionalArgs will be passed to WINCNI as-is.
Example VXLAN Flannel CNI config
```
{
"name": "mynet",
"type": "flannel",
"delegate": {
"type": "win-overlay",
"endpointMacPrefix": "0E-2A"
}
}
```
For this example, Flannel CNI would generate the following config to delegate to the windows CNI when FLANNEL_NETWORK=10.244.0.0/16, FLANNEL_SUBNET=10.244.1.0/24 and IPMASQ=true
```
{
"name": "mynet",
"type": "win-overlay",
"endpointMacPrefix": "0E-2A",
"ipMasq": true,
"ipam": {
"subnet": "10.244.1.0/24",
"type": "host-local"
}
}
```

View File

@@ -46,6 +46,8 @@ const (
type NetConf struct {
types.NetConf
// IPAM field "replaces" that of types.NetConf which is incomplete
IPAM map[string]interface{} `json:"ipam,omitempty"`
SubnetFile string `json:"subnetFile"`
DataDir string `json:"dataDir"`
Delegate map[string]interface{} `json:"delegate"`
@@ -89,6 +91,18 @@ func loadFlannelNetConf(bytes []byte) (*NetConf, error) {
return n, nil
}
func getIPAMRoutes(n *NetConf) ([]types.Route, error) {
rtes := []types.Route{}
if n.IPAM != nil && hasKey(n.IPAM, "routes") {
buf, _ := json.Marshal(n.IPAM["routes"])
if err := json.Unmarshal(buf, &rtes); err != nil {
return rtes, fmt.Errorf("failed to parse ipam.routes: %w", err)
}
}
return rtes, nil
}
func loadFlannelSubnetEnv(fn string) (*subnetEnv, error) {
f, err := os.Open(fn)
if err != nil {
@@ -146,12 +160,19 @@ func saveScratchNetConf(containerID, dataDir string, netconf []byte) error {
return ioutil.WriteFile(path, netconf, 0600)
}
func consumeScratchNetConf(containerID, dataDir string) ([]byte, error) {
func consumeScratchNetConf(containerID, dataDir string) (func(error), []byte, error) {
path := filepath.Join(dataDir, containerID)
// Ignore errors when removing - Per spec safe to continue during DEL
defer os.Remove(path)
return ioutil.ReadFile(path)
// cleanup will do clean job when no error happens in consuming/using process
cleanup := func(err error) {
if err == nil {
// Ignore errors when removing - Per spec safe to continue during DEL
_ = os.Remove(path)
}
}
netConfBytes, err := ioutil.ReadFile(path)
return cleanup, netConfBytes, err
}
func delegateAdd(cid, dataDir string, netconf map[string]interface{}) error {

View File

@@ -22,12 +22,36 @@ import (
"context"
"encoding/json"
"fmt"
"os"
"github.com/containernetworking/cni/pkg/invoke"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"os"
)
// Return IPAM section for Delegate using input IPAM if present and replacing
// or complementing as needed.
func getDelegateIPAM(n *NetConf, fenv *subnetEnv) (map[string]interface{}, error) {
ipam := n.IPAM
if ipam == nil {
ipam = map[string]interface{}{}
}
if !hasKey(ipam, "type") {
ipam["type"] = "host-local"
}
ipam["subnet"] = fenv.sn.String()
rtes, err := getIPAMRoutes(n)
if err != nil {
return nil, fmt.Errorf("failed to read IPAM routes: %w", err)
}
rtes = append(rtes, types.Route{Dst: *fenv.nw})
ipam["routes"] = rtes
return ipam, nil
}
func doCmdAdd(args *skel.CmdArgs, n *NetConf, fenv *subnetEnv) error {
n.Delegate["name"] = n.Name
@@ -55,21 +79,17 @@ func doCmdAdd(args *skel.CmdArgs, n *NetConf, fenv *subnetEnv) error {
n.Delegate["cniVersion"] = n.CNIVersion
}
n.Delegate["ipam"] = map[string]interface{}{
"type": "host-local",
"subnet": fenv.sn.String(),
"routes": []types.Route{
{
Dst: *fenv.nw,
},
},
ipam, err := getDelegateIPAM(n, fenv)
if err != nil {
return fmt.Errorf("failed to assemble Delegate IPAM: %w", err)
}
n.Delegate["ipam"] = ipam
return delegateAdd(args.ContainerID, n.DataDir, n.Delegate)
}
func doCmdDel(args *skel.CmdArgs, n *NetConf) error {
netconfBytes, err := consumeScratchNetConf(args.ContainerID, n.DataDir)
func doCmdDel(args *skel.CmdArgs, n *NetConf) (err error) {
cleanup, netConfBytes, err := consumeScratchNetConf(args.ContainerID, n.DataDir)
if err != nil {
if os.IsNotExist(err) {
// Per spec should ignore error if resources are missing / already removed
@@ -78,10 +98,15 @@ func doCmdDel(args *skel.CmdArgs, n *NetConf) error {
return err
}
// cleanup will work when no error happens
defer func() {
cleanup(err)
}()
nc := &types.NetConf{}
if err = json.Unmarshal(netconfBytes, nc); err != nil {
if err = json.Unmarshal(netConfBytes, nc); err != nil {
return fmt.Errorf("failed to parse netconf: %v", err)
}
return invoke.DelegateDel(context.TODO(), nc.Type, netconfBytes, nil)
return invoke.DelegateDel(context.TODO(), nc.Type, netConfBytes, nil)
}

View File

@@ -14,6 +14,7 @@
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
@@ -50,9 +51,25 @@ var _ = Describe("Flannel", func() {
"name": "cni-flannel",
"type": "flannel",
"subnetFile": "%s",
"dataDir": "%s"
"dataDir": "%s"%s
}`
const inputIPAMTemplate = `
"unknown-param": "unknown-value",
"routes": [%s]%s`
const inputIPAMType = "my-ipam"
const inputIPAMNoTypeTemplate = `
{
"unknown-param": "unknown-value",
"routes": [%s]%s
}`
const inputIPAMRoutes = `
{ "dst": "10.96.0.0/12" },
{ "dst": "192.168.244.0/24", "gw": "10.1.17.20" }`
const flannelSubnetEnv = `
FLANNEL_NETWORK=10.1.0.0/16
FLANNEL_SUBNET=10.1.17.1/24
@@ -68,6 +85,26 @@ FLANNEL_IPMASQ=true
return file.Name()
}
var makeInputIPAM = func(ipamType, routes, extra string) string {
c := "{\n"
if len(ipamType) > 0 {
c += fmt.Sprintf(" \"type\": \"%s\",", ipamType)
}
c += fmt.Sprintf(inputIPAMTemplate, routes, extra)
c += "\n}"
return c
}
var makeInput = func(inputIPAM string) string {
ipamPart := ""
if len(inputIPAM) > 0 {
ipamPart = ",\n \"ipam\":\n" + inputIPAM
}
return fmt.Sprintf(inputTemplate, subnetFile, dataDir, ipamPart)
}
BeforeEach(func() {
var err error
// flannel subnet.env
@@ -76,7 +113,7 @@ FLANNEL_IPMASQ=true
// flannel state dir
dataDir, err = ioutil.TempDir("", "dataDir")
Expect(err).NotTo(HaveOccurred())
input = fmt.Sprintf(inputTemplate, subnetFile, dataDir)
input = makeInput("")
})
AfterEach(func() {
@@ -108,7 +145,7 @@ FLANNEL_IPMASQ=true
})
Expect(err).NotTo(HaveOccurred())
By("check that plugin writes to net config to dataDir")
By("check that plugin writes the net config to dataDir")
path := fmt.Sprintf("%s/%s", dataDir, "some-container-id")
Expect(path).Should(BeAnExistingFile())
@@ -213,4 +250,49 @@ FLANNEL_IPMASQ=true
})
})
})
Describe("getDelegateIPAM", func() {
Context("when input IPAM is provided", func() {
BeforeEach(func() {
inputIPAM := makeInputIPAM(inputIPAMType, inputIPAMRoutes, "")
input = makeInput(inputIPAM)
})
It("configures Delegate IPAM accordingly", func() {
conf, err := loadFlannelNetConf([]byte(input))
Expect(err).ShouldNot(HaveOccurred())
fenv, err := loadFlannelSubnetEnv(subnetFile)
Expect(err).ShouldNot(HaveOccurred())
ipam, err := getDelegateIPAM(conf, fenv)
Expect(err).ShouldNot(HaveOccurred())
podsRoute := "{ \"dst\": \"10.1.0.0/16\" }\n"
subnet := "\"subnet\": \"10.1.17.0/24\""
expected := makeInputIPAM(inputIPAMType, inputIPAMRoutes+",\n"+podsRoute, ",\n"+subnet)
buf, _ := json.Marshal(ipam)
Expect(buf).Should(MatchJSON(expected))
})
})
Context("when input IPAM is provided without 'type'", func() {
BeforeEach(func() {
inputIPAM := makeInputIPAM("", inputIPAMRoutes, "")
input = makeInput(inputIPAM)
})
It("configures Delegate IPAM with 'host-local' ipam", func() {
conf, err := loadFlannelNetConf([]byte(input))
Expect(err).ShouldNot(HaveOccurred())
fenv, err := loadFlannelSubnetEnv(subnetFile)
Expect(err).ShouldNot(HaveOccurred())
ipam, err := getDelegateIPAM(conf, fenv)
Expect(err).ShouldNot(HaveOccurred())
podsRoute := "{ \"dst\": \"10.1.0.0/16\" }\n"
subnet := "\"subnet\": \"10.1.17.0/24\""
expected := makeInputIPAM("host-local", inputIPAMRoutes+",\n"+podsRoute, ",\n"+subnet)
buf, _ := json.Marshal(ipam)
Expect(buf).Should(MatchJSON(expected))
})
})
})
})

View File

@@ -55,8 +55,8 @@ func doCmdAdd(args *skel.CmdArgs, n *NetConf, fenv *subnetEnv) error {
return delegateAdd(hns.GetSandboxContainerID(args.ContainerID, args.Netns), n.DataDir, n.Delegate)
}
func doCmdDel(args *skel.CmdArgs, n *NetConf) error {
netconfBytes, err := consumeScratchNetConf(hns.GetSandboxContainerID(args.ContainerID, args.Netns), n.DataDir)
func doCmdDel(args *skel.CmdArgs, n *NetConf) (err error) {
cleanup, netConfBytes, err := consumeScratchNetConf(hns.GetSandboxContainerID(args.ContainerID, args.Netns), n.DataDir)
if err != nil {
if os.IsNotExist(err) {
// Per spec should ignore error if resources are missing / already removed
@@ -65,10 +65,15 @@ func doCmdDel(args *skel.CmdArgs, n *NetConf) error {
return err
}
// cleanup will work when no error happens
defer func() {
cleanup(err)
}()
nc := &types.NetConf{}
if err = json.Unmarshal(netconfBytes, nc); err != nil {
if err = json.Unmarshal(netConfBytes, nc); err != nil {
return fmt.Errorf("failed to parse netconf: %v", err)
}
return invoke.DelegateDel(context.TODO(), nc.Type, netconfBytes, nil)
return invoke.DelegateDel(context.TODO(), nc.Type, netConfBytes, nil)
}

View File

@@ -1,133 +1,5 @@
## Port-mapping plugin
This plugin will forward traffic from one or more ports on the host to the
container. It expects to be run as a chained plugin.
This document has moved to the [containernetworking/cni.dev](https://github.com/containernetworking/cni.dev) repo.
## Usage
You should use this plugin as part of a network configuration list. It accepts
the following configuration options:
You can find it online here: https://cni.dev/plugins/meta/portmap/
* `snat` - boolean, default true. If true or omitted, set up the SNAT chains
* `markMasqBit` - int, (0-31), default 13. The mark bit to use for masquerading (see section SNAT). Cannot be set when `externalSetMarkChain` is used.
* `externalSetMarkChain` - string, default nil. If you already have a Masquerade mark chain (e.g. Kubernetes), specify it here. This will use that instead of creating a separate chain. When this is set, `markMasqBit` must be unspecified.
* `conditionsV4`, `conditionsV6` - array of strings. A list of arbitrary `iptables`
matches to add to the per-container rule. This may be useful if you wish to
exclude specific IPs from port-mapping
The plugin expects to receive the actual list of port mappings via the
`portMappings` [capability argument](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md)
A sample standalone config list for Kubernetes (with the file extension .conflist) might
look like:
```json
{
"cniVersion": "0.3.1",
"name": "mynet",
"plugins": [
{
"type": "ptp",
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "172.16.30.0/24",
"routes": [
{
"dst": "0.0.0.0/0"
}
]
}
},
{
"type": "portmap",
"capabilities": {"portMappings": true},
"externalSetMarkChain": "KUBE-MARK-MASQ"
}
]
}
```
A configuration file with all options set:
```json
{
"type": "portmap",
"capabilities": {"portMappings": true},
"snat": true,
"markMasqBit": 13,
"externalSetMarkChain": "CNI-HOSTPORT-SETMARK",
"conditionsV4": ["!", "-d", "192.0.2.0/24"],
"conditionsV6": ["!", "-d", "fc00::/7"]
}
```
## Rule structure
The plugin sets up two sequences of chains and rules - one "primary" DNAT
sequence to rewrite the destination, and one additional SNAT sequence that
will masquerade traffic as needed.
### DNAT
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
8080 and 8043 on the host forwarded to ports 80 and 443 in the container, the
rules look like this:
`PREROUTING`, `OUTPUT` chains:
- `--dst-type LOCAL -j CNI-HOSTPORT-DNAT`
`CNI-HOSTPORT-DNAT` chain:
- `${ConditionsV4/6} -p tcp --destination-ports 8080,8043 -j CNI-DN-xxxxxx` (where xxxxxx is a function of the ContainerID and network name)
`CNI-HOSTPORT-SETMARK` chain:
- `-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 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 127.0.0.1 --dport 8043 -j CNI-HOSTPORT-SETMARK`
- `-p tcp --dport 8043 -j DNAT --to-destination 172.16.30.2:443`
New connections to the host will have to traverse every rule, so large numbers
of port forwards may have a performance impact. This won't affect established
connections, just the first packet.
### SNAT (Masquerade)
Some packets also need to have the source address rewritten:
* connections from localhost
* Hairpin traffic back to the container.
In the DNAT chain, a bit is set on the mark for packets that need snat. This
chain performs that masquerading. By default, bit 13 is set, but this is
configurable. If you are using other tools that also use the iptables mark,
you should make sure this doesn't conflict.
Some container runtimes, most notably Kubernetes, already have a set of rules
for masquerading when a specific mark bit is set. If so enabled, the plugin
will use that chain instead.
`POSTROUTING`:
- `-j CNI-HOSTPORT-MASQ`
`CNI-HOSTPORT-MASQ`:
- `--mark 0x2000 -j MASQUERADE`
Because MASQUERADE happens in POSTROUTING, it means that packets with source ip
127.0.0.1 need to first pass a routing boundary before being masqueraded. By
default, that is not allowed in Linux. So, the plugin needs to enable the sysctl
`net.ipv4.conf.IFNAME.route_localnet`, where IFNAME is the name of the host-side
interface that routes traffic to the container.
There is no equivalent to `route_localnet` for ipv6, so connections to ::1
will not be portmapped for ipv6. If you need port forwarding from localhost,
your container must have an ipv4 address.
## Known issues
- ipsets could improve efficiency
- forwarding from localhost does not work with ipv6.

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
}

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