Compare commits

..

104 Commits

Author SHA1 Message Date
35f3a090b2 pkg/ns: introduce error types indicate NS verification 2016-05-27 13:50:16 +02:00
131ecc4055 Merge pull request #230 from steveeJ/netns-optional-on-del
plugins: don't require CNI_NETNS for DEL command
2016-05-27 13:49:05 +02:00
d582c9ce8f skel/test: add case for empty NETNS 2016-05-27 12:26:42 +02:00
72337159c1 plugins: don't require CNI_NETNS for DEL command
This will allow to free up the IPAM allocations when the caller doesn't
have access to the network namespace anymore, e.g. due to a reboot.
2016-05-27 10:57:39 +02:00
7f90f9d559 pkg/skel: allow arg requriements specified by CMD 2016-05-27 10:56:24 +02:00
6f63d9d707 Merge pull request #227 from steveeJ/ns-verify
pkg/ns: consider PROCFS during NS verification
2016-05-26 13:22:29 +02:00
3bab8a2805 pkg/ns: consider PROCFS during NS verification
This is an attempt to bring compatibility with Kernel <3.19, where NSFS
where PROCFS was used for network namespaces.
2016-05-26 12:42:50 +02:00
6fb30a6700 Merge pull request #222 from steveeJ/ns-check-path
pkg/ns: verify netns when initialized with GetNS
2016-05-25 08:54:10 +02:00
d6751cea24 pkg/ns: test IsNSFS() 2016-05-24 22:30:49 +02:00
c43ccc703a pkg/ns: test case for rejecting a non-ns nspath 2016-05-24 22:30:49 +02:00
76ea259ff9 pkg/ns: verify netns when initialized with GetNS 2016-05-24 22:30:49 +02:00
c29cd52628 Merge pull request #223 from steveeJ/ns-respect-close
pkg/ns: don't allow operations after Close()
2016-05-24 22:16:09 +02:00
2de97b7e98 pkg/ns: add tests cases for Close()'d NS 2016-05-24 21:15:51 +02:00
b23895a7c7 pkg/ns: don't allow operations after Close() 2016-05-24 20:52:00 +02:00
984ef8117a Merge pull request #217 from steveeJ/fix-go-tip
build: don't be verbose / test: fix bash variable escaping in sudo command
2016-05-21 01:55:57 +02:00
54c0cd2cad build: remove -x bash flag 2016-05-21 01:48:26 +02:00
232d7c095d test: fix bash variable escaping in sudo command 2016-05-21 01:48:26 +02:00
20fa3d35e8 Merge pull request #50 from steveeJ/gwfix
bridge: add isDefaultGateway field
2016-05-21 01:34:21 +02:00
8afda5faed tests/bridge: ensure isDefaultGateway works 2016-05-21 01:07:05 +02:00
534768fcd4 bridge: add isDefaultGateway field
When isDefaultGateway is true it automatically sets isGateway to true.
The default route will be added via the (bridge's) gateway IP.
If a default gateway has been configured via IPAM in the same
configuration file, the plugin will error out.
2016-05-21 00:38:42 +02:00
d30040f9f7 Merge pull request #211 from dcbw/e2e-testing
Fix namespace switch issues and add ipvlan, macvlan, and bridge e2e testing
2016-05-21 00:17:45 +02:00
ebf83ffde5 bridge: add e2e testing 2016-05-20 17:10:25 -05:00
552dc43168 macvlan: add e2e testing 2016-05-20 17:10:25 -05:00
185e33ebe9 ipvlan: add e2e testing 2016-05-20 17:10:25 -05:00
31da322f6e testutils: add e2e testing helper code 2016-05-20 17:10:25 -05:00
c0d34c692f ns: add interface, use it, and fix thread-related namespace switch issues
Add a namespace object interface for somewhat cleaner code when
creating and switching between network namespaces.  All created
namespaces are now mounted in /var/run/netns to ensure they
have persistent inodes and paths that can be passed around
between plugin components without relying on the current namespace
being correct.

Also remove the thread-locking arguments from the ns package
per https://github.com/appc/cni/issues/183 by doing all the namespace
changes in a separate goroutine that locks/unlocks itself, instead of
the caller having to track OS thread locking.
2016-05-20 17:10:25 -05:00
3e1c3c60da Merge pull request #199 from dcbw/fix-bridge-tx-queue-length
bridge: leave TX queue length as kernel default, not 0
2016-05-19 23:05:08 +02:00
d29b895932 Merge pull request #212 from squaremo/more-contrib
docs: Mention license headers and doc contributions
2016-05-19 18:38:57 +02:00
398710b3c4 Merge pull request #214 from steveeJ/merge-v0.2
Merge v0.2
2016-05-19 15:45:07 +02:00
5fa06a39f0 release script: don't run the tests
For two reasons:

1. They're not functional within rkt
2. They rebuild the binaries dynamically
2016-05-19 15:05:29 +02:00
c72dea5a20 build/release: link all release binaries statically 2016-05-19 15:05:29 +02:00
4aaffc2069 scripts: build static releases and create an ACI
* use SHA1 instead of MD5
2016-05-19 15:05:29 +02:00
e145c60337 scripts: add "release with rkt"
This script uses rkt and a fedora image to build release tarballs.
2016-05-19 15:05:29 +02:00
bcc189e1e6 docs: Mention license headers and doc contributions
Closes #174.
2016-05-19 13:42:36 +01:00
991176a64a Merge pull request #200 from squaremo/simplify-readme
Simplify README preamble
2016-05-19 13:21:40 +01:00
8db1f0d503 README: Simplify preamble 2016-05-19 12:52:09 +01:00
74790e55ef Merge pull request #175. 2016-05-19 12:08:41 +02:00
89ab9299ab plugins/bridge: add support to set hairpin mode 2016-05-19 12:06:37 +02:00
f4eb76318e macvlan: sysctl must be set in macvlan interface's namespace
The macvlan is initially created in a separate network namespace
and the sysctl must be set in that namespace too.
2016-05-19 12:06:37 +02:00
a80bcc0371 pkg/ns: evaluate syscall number at compile-time
Previously this code used a run-time map lookup keyed by
runtime.GOOS/GOARCH.  This version uses conditional compilation to make
this choice at compile time, giving immediate feedback for unsupported
platforms.
2016-05-19 12:06:37 +02:00
979044ad2f pkg/ns: use correct syscall number on arm 2016-05-19 12:06:37 +02:00
ec78e7cf1e MAINTAINERS: add Tom Denham and Gabe Rosenhouse 2016-05-19 12:06:37 +02:00
5d67d22caa plugins/{ptp,bridge}: teardown first
This will allow the IPAM allocations to be cleared in case the
interfaces and iptables rules are non-existent.
2016-05-19 12:06:37 +02:00
2c482f433c *: appc/cni -> containernetworking/cni
The project has been moved so internally we simply rename everything.
Consumers are recommended to update their vendored version of cni.
2016-05-19 12:06:36 +02:00
419711fee3 README: fix badge links 2016-05-19 12:06:36 +02:00
04dbf7ce45 README: fix badges 2016-05-19 12:06:36 +02:00
61f20da4f2 README: add contiv to cni supported networking 2016-05-19 12:06:36 +02:00
a993ef5f7f plugins/bridge: clean masquerading rules
In the Add command we set up masquerading rules that didn't have a
corresponding clean-up code in Del.

Add the clean-up code.
2016-05-19 12:06:36 +02:00
41a5dfac33 Remove reference to policy on accepting PRs
There is to policy
2016-05-19 12:06:36 +02:00
a1dab0aa40 macvlan: set proxy_arp in time of creating interface
Resolves CNI part of https://github.com/coreos/rkt/issues/1765
Second part would be adding similar lines into kvm flavored macvlan
support (in time of creating macvtap device).
2016-05-19 12:06:16 +02:00
4298aa94a5 pkg/utils: add functions to work with sysctl 2016-05-19 12:06:16 +02:00
d53ffe1b40 README: clarify what CNI is
The word "standard" does not represent what CNI is actually trying to
be.
2016-05-19 12:06:16 +02:00
b5e56b3dab Document use of goroutine and lockosthread in test helpers 2016-05-19 12:06:16 +02:00
260c7f96bf ns: fix reading net namespace in multi-threaded processes
/proc/self/ns/net gives the main thread's namespace, not necessarily
the namespace of the thread that's running the testcases.  This causes
sporadic failures of the tests.

For example, with a testcase reading inodes after switching netns:

/proc/27686/task/27689/ns/net 4026532565
/proc/self/ns/net 4026531969
/proc/27686/task/27689/ns/net 4026532565

See also:
008d17ae00

Running Suite: pkg/ns Suite
===========================
Random Seed: 1459953577
Will run 6 of 6 specs

• Failure [0.028 seconds]
Linux namespace operations
/cni/gopath/src/github.com/appc/cni/pkg/ns/ns_test.go:167
  WithNetNS
  /cni/gopath/src/github.com/appc/cni/pkg/ns/ns_test.go:166
    executes the callback within the target network namespace [It]
    /cni/gopath/src/github.com/appc/cni/pkg/ns/ns_test.go:97

    Expected
        <uint64>: 4026531969
    to equal
        <uint64>: 4026532565

    /cni/gopath/src/github.com/appc/cni/pkg/ns/ns_test.go:96
------------------------------
•••••

Summarizing 1 Failure:

[Fail] Linux namespace operations WithNetNS [It] executes the callback within the target network namespace
/cni/gopath/src/github.com/appc/cni/pkg/ns/ns_test.go:96

Ran 6 of 6 Specs in 0.564 seconds
FAIL! -- 5 Passed | 1 Failed | 0 Pending | 0 Skipped --- FAIL: TestNs (0.56s)
FAIL
2016-05-19 12:06:16 +02:00
b09af2ff91 Fix issues with MakeNetworkNS test helper 2016-05-19 12:06:16 +02:00
1bbc87ab54 Add basic unit tests of testhelpers 2016-05-19 12:06:16 +02:00
4fc8a90277 Extract inode inspection functions into testhelpers 2016-05-19 12:06:16 +02:00
9044dc2831 Extract testhelpers from loopback test suite 2016-05-19 12:06:16 +02:00
5019e283b1 README: add some material on what CNI isn't
We get some questions about this stuff from time to time and I want to
acknowledge we have thought about them but aren't currently tackling
them.
2016-05-19 12:06:16 +02:00
e7ae43e517 README: add some more details on what CNI is
We needed a bit more intro material to scope what CNI does. Here is my
attempt.
2016-05-19 12:06:16 +02:00
72e98b97d6 The macvlan is initially created in a separate network namespaceand the sysctl must be set in that namespace too.Fixes #208.
The macvlan is initially created in a separate network namespace
and the sysctl must be set in that namespace too.

Fixes #208.
2016-05-17 21:11:21 +02:00
8f5194d9b5 pkg/ns: setns syscall number on arm and evaluate them at build time.
Dummy merge due to previous manual merge.
2016-05-17 18:44:33 +02:00
31379b8a41 Merge branch 'anguslees-master' 2016-05-17 18:36:38 +02:00
2b019bfde1 pkg/ns: evaluate syscall number at compile-time
Previously this code used a run-time map lookup keyed by
runtime.GOOS/GOARCH.  This version uses conditional compilation to make
this choice at compile time, giving immediate feedback for unsupported
platforms.
2016-05-17 18:36:29 +02:00
a2f23f5a84 pkg/ns: use correct syscall number on arm 2016-05-17 18:36:22 +02:00
899c0efd42 bridge: leave TX queue length as kernel default, not 0
Not using NewLinkAttrs() or not initializing TxQLen leaves
the value as 0, which tells the kernel to set a zero-length
tx_queue_len.  That messes up FIFO traffic shapers (like pfifo)
that use the device TX queue length as the default packet
limit.  This leads to a default packet limit of 0, which drops
all packets.
2016-05-12 10:35:50 -05:00
e1907bdab7 Merge pull request #201 from jonboulle/master
MAINTAINERS: add Tom Denham and Gabe Rosenhouse
2016-05-12 14:51:37 +01:00
463e171614 Merge pull request #205 from steveeJ/teardown-fix
plugins/{ptp,bridge}: teardown first
2016-05-12 10:54:47 +02:00
d91971cbe0 plugins/{ptp,bridge}: teardown first
This will allow the IPAM allocations to be cleared in case the
interfaces and iptables rules are non-existent.
2016-05-11 19:47:55 +02:00
bf8bde8256 MAINTAINERS: add Tom Denham and Gabe Rosenhouse 2016-05-10 12:12:05 +02:00
64791dd233 Merge pull request #198 from steveeJ/org-move
*: appc/cni -> containernetworking/cni
2016-05-06 17:58:44 +02:00
e021964819 README: fix badge links 2016-05-06 08:39:55 -07:00
56ddbfd522 README: fix badges 2016-05-06 08:39:21 -07:00
ed5f8dfeb2 *: appc/cni -> containernetworking/cni
The project has been moved so internally we simply rename everything.
Consumers are recommended to update their vendored version of cni.
2016-05-06 16:40:27 +02:00
fb34e4a21d Merge pull request #197 from jainvipin/master
Add Contiv to CNI supported networking
2016-05-05 09:15:15 +01:00
914a85c544 README: add contiv to cni supported networking 2016-05-04 20:29:13 -07:00
dbcecbcec6 Merge branch 'pr195' (clean up masq rules) 2016-05-04 13:50:09 +01:00
6aa26f9fd2 plugins/bridge: clean masquerading rules
In the Add command we set up masquerading rules that didn't have a
corresponding clean-up code in Del.

Add the clean-up code.
2016-05-03 12:21:58 +02:00
99ca41457e macvlan: sysctl must be set in macvlan interface's namespace
The macvlan is initially created in a separate network namespace
and the sysctl must be set in that namespace too.
2016-04-28 11:26:47 -05:00
cae660409f Merge pull request #189 from tomdee/patch-2
Remove reference to policy on accepting PRs
2016-04-27 15:47:42 +01:00
902ee8b992 Remove reference to policy on accepting PRs
There is to policy
2016-04-22 12:52:28 -07:00
911d941794 Merge pull request #187 from appc/v0.3.0
merge unneeded branch v0.3.0 into master
2016-04-22 19:25:54 +02:00
8bb41b9d6f Merge pull request #186 from steveeJ/project-clarification
README: clarify what CNI is
2016-04-21 11:54:52 +01:00
ef251cc6ce README: clarify what CNI is
The word "standard" does not represent what CNI is actually trying to
be.
2016-04-20 20:00:50 +02:00
57b28f3936 Merge pull request #182 from rosenhouse/document-lockosthread
Document use of goroutine and LockOSThread in test helpers
2016-04-18 10:45:46 -07:00
7383809048 Merge pull request #176 from dcbw/ns-test-fix
ns: fix reading net namespace in multi-threaded processes
2016-04-18 10:44:08 -07:00
07a993dc7d ns: fix reading net namespace in multi-threaded processes
/proc/self/ns/net gives the main thread's namespace, not necessarily
the namespace of the thread that's running the testcases.  This causes
sporadic failures of the tests.

For example, with a testcase reading inodes after switching netns:

/proc/27686/task/27689/ns/net 4026532565
/proc/self/ns/net 4026531969
/proc/27686/task/27689/ns/net 4026532565

See also:
008d17ae00

Running Suite: pkg/ns Suite
===========================
Random Seed: 1459953577
Will run 6 of 6 specs

• Failure [0.028 seconds]
Linux namespace operations
/cni/gopath/src/github.com/appc/cni/pkg/ns/ns_test.go:167
  WithNetNS
  /cni/gopath/src/github.com/appc/cni/pkg/ns/ns_test.go:166
    executes the callback within the target network namespace [It]
    /cni/gopath/src/github.com/appc/cni/pkg/ns/ns_test.go:97

    Expected
        <uint64>: 4026531969
    to equal
        <uint64>: 4026532565

    /cni/gopath/src/github.com/appc/cni/pkg/ns/ns_test.go:96
------------------------------
•••••

Summarizing 1 Failure:

[Fail] Linux namespace operations WithNetNS [It] executes the callback within the target network namespace
/cni/gopath/src/github.com/appc/cni/pkg/ns/ns_test.go:96

Ran 6 of 6 Specs in 0.564 seconds
FAIL! -- 5 Passed | 1 Failed | 0 Pending | 0 Skipped --- FAIL: TestNs (0.56s)
FAIL
2016-04-18 10:35:23 -05:00
94c4b25698 Document use of goroutine and lockosthread in test helpers 2016-04-17 20:27:02 -07:00
616702bcf3 Merge pull request #181 from rosenhouse/testhelpers-extraction
Extract and improve test helpers
2016-04-17 20:20:43 -07:00
e3fb7ce3a7 Fix issues with MakeNetworkNS test helper 2016-04-17 19:47:54 -07:00
dd3b85c9cc Add basic unit tests of testhelpers 2016-04-17 19:25:39 -07:00
54d7f73092 Extract inode inspection functions into testhelpers 2016-04-17 18:35:49 -07:00
5d932e4716 Extract testhelpers from loopback test suite 2016-04-17 18:28:10 -07:00
9a2801085b Merge pull request #180 from philips/add-more-intro
README: add some more introductory material
2016-04-11 10:12:22 +01:00
4c778a63a7 README: add some material on what CNI isn't
We get some questions about this stuff from time to time and I want to
acknowledge we have thought about them but aren't currently tackling
them.
2016-04-10 18:23:06 -07:00
d5a8ec9029 README: add some more details on what CNI is
We needed a bit more intro material to scope what CNI does. Here is my
attempt.
2016-04-10 18:22:38 -07:00
41ee449fb2 Merge pull request #177 from steveeJ/macvlan-arp-proxy
Macvlan arp proxy
2016-04-08 14:53:48 +02:00
a06a464235 Merge pull request #178 from steveeJ/master
travis: don't go get vet
2016-04-08 14:35:58 +02:00
27d37cbdf1 travis: don't go get vet 2016-04-08 14:29:04 +02:00
bc482febfb travis: don't go get vet 2016-04-08 14:22:20 +02:00
b2f492e8b5 macvlan: set proxy_arp in time of creating interface
Resolves CNI part of https://github.com/coreos/rkt/issues/1765
Second part would be adding similar lines into kvm flavored macvlan
support (in time of creating macvtap device).
2016-04-07 21:40:58 +02:00
72bec92851 pkg/utils: add functions to work with sysctl 2016-04-07 21:40:51 +02:00
a15a372021 Merge pull request #173 from jonboulle/master
*: add missing license headers + check
2016-04-02 15:22:40 +02:00
f6135c7ba3 *: add missing license headers + check 2016-04-01 15:35:21 +02:00
54 changed files with 1794 additions and 326 deletions

View File

@ -5,7 +5,7 @@ pull requests. This document outlines some of the conventions on development
workflow, commit message formatting, contact points and other resources to make
it easier to get your contribution accepted.
For more information on the policy for accepting contributions, see [POLICY](POLICY.md)
We welcome improvements to documentation as well as to code.
# Certificate of Origin
@ -16,9 +16,9 @@ contribution. See the [DCO](DCO) file for details.
# Email and Chat
The project uses the the cni-dev email list and #appc on Freenode for chat:
The project uses the the cni-dev email list and IRC chat:
- Email: [cni-dev](https://groups.google.com/forum/#!forum/cni-dev)
- IRC: #[appc](irc://irc.freenode.org:6667/#appc) IRC channel on freenode.org
- IRC: #[containernetworking](irc://irc.freenode.org:6667/#containernetworking) channel on freenode.org
Please avoid emailing maintainers found in the MAINTAINERS file directly. They
are very busy and read the mailing lists.
@ -27,17 +27,18 @@ are very busy and read the mailing lists.
- Fork the repository on GitHub
- Read the [README](README.md) for build and test instructions
- Play with the project, submit bugs, submit patches!
- Play with the project, submit bugs, submit pull requests!
## Contribution Flow
## Contribution workflow
This is a rough outline of what a contributor's workflow looks like:
This is a rough outline of how to prepare a contribution:
- Create a topic branch from where you want to base your work (usually master).
- Create a topic branch from where you want to base your work (usually branched from master).
- Make commits of logical units.
- Make sure your commit messages are in the proper format (see below).
- Push your changes to a topic branch in your fork of the repository.
- Make sure the tests pass, and add any new tests as appropriate.
- If you changed code, make sure the tests pass, and add any new tests as appropriate.
- Make sure any new code files have a license header.
- Submit a pull request to the original repository.
Thanks for your contributions!

View File

@ -17,8 +17,9 @@ If the bridge is missing, the plugin will create one on first use and, if gatewa
"name": "mynet",
"type": "bridge",
"bridge": "mynet0",
"isGateway": true,
"isDefaultGateway": true,
"ipMasq": true,
"hairpinMode": true,
"ipam": {
"type": "host-local",
"subnet": "10.10.0.0/16"
@ -32,6 +33,8 @@ If the bridge is missing, the plugin will create one on first use and, if gatewa
* `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.
* `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.

View File

@ -3,7 +3,7 @@
## Overview
With dhcp plugin the containers can get an IP allocated by a DHCP server already running on your network.
This can be especially useful with plugin types such as [macvlan](https://github.com/appc/cni/blob/master/Documentation/macvlan.md).
This can be especially useful with plugin types such as [macvlan](https://github.com/containernetworking/cni/blob/master/Documentation/macvlan.md).
Because a DHCP lease must be periodically renewed for the duration of container lifetime, a separate daemon is required to be running.
The same plugin binary can also be run in the daemon mode.

View File

@ -32,7 +32,7 @@ It stores the state locally on the host filesystem, therefore ensuring uniquenes
* `routes` (string, optional): list of routes to add to the container namespace. Each route is a dictionary with "dst" and optional "gw" fields. If "gw" is omitted, value of "gateway" will be used.
## Supported arguments
The following [CNI_ARGS](https://github.com/appc/cni/blob/master/SPEC.md#parameters) are supported:
The following [CNI_ARGS](https://github.com/containernetworking/cni/blob/master/SPEC.md#parameters) are supported:
* `ip`: request a specific IP address from the subnet. If it's not available, the plugin will exit with an error

2
Godeps/Godeps.json generated
View File

@ -1,5 +1,5 @@
{
"ImportPath": "github.com/appc/cni",
"ImportPath": "github.com/containernetworking/cni",
"GoVersion": "go1.6",
"Packages": [
"./..."

View File

@ -1,3 +1,5 @@
Gabe Rosenhouse <grosenhouse@pivotal.io> (@rosenhouse)
Michael Bridgen <michael@weave.works> (@squaremo)
Stefan Junker <stefan.junker@coreos.com> (@steveeJ)
Tom Denham <tom.denham@metaswitch.com> (@tomdee)
Zach Gershman <zachgersh@gmail.com> (@zachgersh)

View File

@ -1,22 +1,25 @@
[![Build Status](https://travis-ci.org/appc/cni.svg?branch=master)](https://travis-ci.org/appc/cni)
[![Coverage Status](https://coveralls.io/repos/github/appc/cni/badge.svg?branch=master)](https://coveralls.io/github/appc/cni?branch=master)
[![Build Status](https://travis-ci.org/containernetworking/cni.svg?branch=master)](https://travis-ci.org/containernetworking/cni)
[![Coverage Status](https://coveralls.io/repos/github/containernetworking/cni/badge.svg?branch=master)](https://coveralls.io/github/containernetworking/cni?branch=master)
# CNI - the Container Network Interface
## What is CNI?
CNI, the _Container Network Interface_, is a proposed standard for configuring network interfaces for Linux application containers.
The standard consists of a simple specification for how executable plugins can be used to configure network namespaces; this repository also contains a go library implementing that specification.
The CNI (_Container Network Interface_) project consists of a specification and libraries for writing plugins to configure network interfaces in Linux containers, along with a number of supported plugins.
CNI concerns itself only with network connectivity of containers and removing allocated resources when the container is deleted.
Because of this focus CNI has a wide range of support and the specification is simple to implement.
The specification itself is contained in [SPEC.md](SPEC.md).
As well as the [specification](SPEC.md), this repository contains the Go source code of a library for integrating CNI into applications, an example command-line tool, a template for making new plugins, and the supported plugins.
The template code makes it straight-forward to create a CNI plugin for an existing container networking project.
CNI also makes a good framework for creating a new container networking project from scratch.
## Why develop CNI?
Application containers on Linux are a rapidly evolving area, and within this space networking is a particularly unsolved problem, as it is highly environment-specific.
We believe that every container runtime will seek to solve the same problem of making the network layer pluggable.
Application containers on Linux are a rapidly evolving area, and within this area networking is not well addressed as it is highly environment-specific.
We believe that many container runtimes and orchestrators will seek to solve the same problem of making the network layer pluggable.
To avoid duplication, we think it is prudent to define a common interface between the network plugins and container execution.
Hence we are proposing this specification, along with an initial set of plugins that can be used by different container runtime systems.
To avoid duplication, we think it is prudent to define a common interface between the network plugins and container execution: hence we put forward this specification, along with libraries for Go and a set of plugins.
## Who is using CNI?
@ -26,25 +29,29 @@ Hence we are proposing this specification, along with an initial set of plugins
- [Cloud Foundry - a platform for cloud applications](https://github.com/cloudfoundry-incubator/guardian-cni-adapter)
- [Weave - a multi-host Docker network](https://github.com/weaveworks/weave)
- [Project Calico - a layer 3 virtual network](https://github.com/projectcalico/calico-cni)
- [Contiv Networking - policy networking for various use cases](https://github.com/contiv/netplugin)
## Contributing to CNI
We welcome contributions, including [bug reports](https://github.com/appc/cni/issues), and code and documentation improvements.
We welcome contributions, including [bug reports](https://github.com/containernetworking/cni/issues), and code and documentation improvements.
If you intend to contribute to code or documentation, please read [CONTRIBUTING.md](CONTRIBUTING.md). Also see the [contact section](#contact) in this README.
## How do I use CNI?
### Requirements
CNI requires Go 1.5+ to build.
Go 1.5 users will need to set GO15VENDOREXPERIMENT=1 to get vendored
dependencies. This flag is set by default in 1.6.
### Included Plugins
This repository includes a number of common plugins in the `plugins/` directory.
Please see the [Documentation/](Documentation/) directory for documentation about particular plugins.
### Running the plugins
The scripts/ directory contains two scripts, `priv-net-run.sh` and `docker-run.sh`, that can be used to exercise the plugins.
**note - priv-net-run.sh depends on `jq`**
@ -139,8 +146,18 @@ lo Link encap:Local Loopback
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
```
## What might CNI do in the future?
CNI currently covers a wide range of needs for network configuration due to it simple model and API.
However, in the future CNI might want to branch out into other directions:
- Dynamic updates to existing network configuration
- Dynamic policies for network bandwidth and firewall rules
If these topics of are interest please contact the team via the mailing list or IRC and find some like minded people in the community to put a proposal together.
## Contact
For any questions about CNI, please reach out on the mailing list or IRC:
For any questions about CNI, please reach out on the mailing list:
- Email: [cni-dev](https://groups.google.com/forum/#!forum/cni-dev)
- IRC: #[appc](irc://irc.freenode.org:6667/#appc) IRC channel on freenode.org
- IRC: #[containernetworking](irc://irc.freenode.org:6667/#containernetworking) channel on freenode.org

4
build
View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -xe
set -e
ORG_PATH="github.com/appc"
ORG_PATH="github.com/containernetworking"
REPO_PATH="${ORG_PATH}/cni"
if [ ! -h gopath/src/${REPO_PATH} ]; then

View File

@ -20,7 +20,7 @@ import (
"path/filepath"
"strings"
"github.com/appc/cni/libcni"
"github.com/containernetworking/cni/libcni"
)
const (

View File

@ -17,8 +17,8 @@ package libcni
import (
"strings"
"github.com/appc/cni/pkg/invoke"
"github.com/appc/cni/pkg/types"
"github.com/containernetworking/cni/pkg/invoke"
"github.com/containernetworking/cni/pkg/types"
)
type RuntimeConf struct {

View File

@ -1,3 +1,17 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package invoke
import (
@ -5,7 +19,7 @@ import (
"os"
"strings"
"github.com/appc/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types"
)
func DelegateAdd(delegatePlugin string, netconf []byte) (*types.Result, error) {

View File

@ -21,7 +21,7 @@ import (
"os"
"os/exec"
"github.com/appc/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types"
)
func pluginErr(err error, output []byte) error {

View File

@ -1,3 +1,17 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package invoke_test
import (
@ -5,7 +19,7 @@ import (
"io/ioutil"
"path/filepath"
"github.com/appc/cni/pkg/invoke"
"github.com/containernetworking/cni/pkg/invoke"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

View File

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

View File

@ -20,7 +20,7 @@ import (
"net"
"os"
"github.com/appc/cni/pkg/ns"
"github.com/containernetworking/cni/pkg/ns"
"github.com/vishvananda/netlink"
)
@ -81,7 +81,7 @@ func RandomVethName() (string, error) {
// SetupVeth sets up a virtual ethernet link.
// Should be in container netns, and will switch back to hostNS to set the host
// veth end up.
func SetupVeth(contVethName string, mtu int, hostNS *os.File) (hostVeth, contVeth netlink.Link, err error) {
func SetupVeth(contVethName string, mtu int, hostNS ns.NetNS) (hostVeth, contVeth netlink.Link, err error) {
var hostVethName string
hostVethName, contVeth, err = makeVeth(contVethName, mtu)
if err != nil {
@ -104,10 +104,10 @@ func SetupVeth(contVethName string, mtu int, hostNS *os.File) (hostVeth, contVet
return
}
err = ns.WithNetNS(hostNS, false, func(_ *os.File) error {
err = hostNS.Do(func(_ ns.NetNS) error {
hostVeth, err := netlink.LinkByName(hostVethName)
if err != nil {
return fmt.Errorf("failed to lookup %q in %q: %v", hostVethName, hostNS.Name(), err)
return fmt.Errorf("failed to lookup %q in %q: %v", hostVethName, hostNS.Path(), err)
}
if err = netlink.LinkSetUp(hostVeth); err != nil {

View File

@ -18,9 +18,9 @@ import (
"fmt"
"os"
"github.com/appc/cni/pkg/invoke"
"github.com/appc/cni/pkg/ip"
"github.com/appc/cni/pkg/types"
"github.com/containernetworking/cni/pkg/invoke"
"github.com/containernetworking/cni/pkg/ip"
"github.com/containernetworking/cni/pkg/types"
"github.com/vishvananda/netlink"
)

31
pkg/ns/README.md Normal file
View File

@ -0,0 +1,31 @@
### Namespaces, Threads, and Go
On Linux each OS thread can have a different network namespace. Go's thread scheduling model switches goroutines between OS threads based on OS thread load and whether the goroutine would block other goroutines. This can result in a goroutine switching network namespaces without notice and lead to errors in your code.
### Namespace Switching
Switching namespaces with the `ns.Set()` method is not recommended without additional strategies to prevent unexpected namespace changes when your goroutines switch OS threads.
Go provides the `runtime.LockOSThread()` function to ensure a specific goroutine executes on its current OS thread and prevents any other goroutine from running in that thread until the locked one exits. Careful usage of `LockOSThread()` and goroutines can provide good control over which network namespace a given goroutine executes in.
For example, you cannot rely on the `ns.Set()` namespace being the current namespace after the `Set()` call unless you do two things. First, the goroutine calling `Set()` must have previously called `LockOSThread()`. Second, you must ensure `runtime.UnlockOSThread()` is not called somewhere in-between. You also cannot rely on the initial network namespace remaining the current network namespace if any other code in your program switches namespaces, unless you have already called `LockOSThread()` in that goroutine. Note that `LockOSThread()` prevents the Go scheduler from optimally scheduling goroutines for best performance, so `LockOSThread()` should only be used in small, isolated goroutines that release the lock quickly.
### Do() The Recommended Thing
The `ns.Do()` method provides control over network namespaces for you by implementing these strategies. All code dependent on a particular network namespace should be wrapped in the `ns.Do()` method to ensure the correct namespace is selected for the duration of your code. For example:
```go
targetNs, err := ns.NewNS()
if err != nil {
return err
}
err = targetNs.Do(func(hostNs ns.NetNS) error {
dummy := &netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: "dummy0",
},
}
return netlink.LinkAdd(dummy)
})
```
### Further Reading
- https://github.com/golang/go/wiki/LockOSThread
- http://morsmachine.dk/go-scheduler

View File

@ -15,79 +15,301 @@
package ns
import (
"crypto/rand"
"fmt"
"os"
"path"
"runtime"
"strings"
"sync"
"syscall"
"golang.org/x/sys/unix"
)
var setNsMap = map[string]uintptr{
"386": 346,
"amd64": 308,
"arm": 374,
type NetNS interface {
// Executes the passed closure in this object's network namespace,
// attemtping to restore the original namespace before returning.
// However, since each OS thread can have a different network namespace,
// and Go's thread scheduling is highly variable, callers cannot
// guarantee any specific namespace is set unless operations that
// require that namespace are wrapped with Do(). Also, no code called
// from Do() should call runtime.UnlockOSThread(), or the risk
// of executing code in an incorrect namespace will be greater. See
// https://github.com/golang/go/wiki/LockOSThread for further details.
Do(toRun func(NetNS) error) error
// Sets the current network namespace to this object's network namespace.
// Note that since Go's thread scheduling is highly variable, callers
// cannot guarantee the requested namespace will be the current namespace
// after this function is called; to ensure this wrap operations that
// require the namespace with Do() instead.
Set() error
// Returns the filesystem path representing this object's network namespace
Path() string
// Returns a file descriptor representing this object's network namespace
Fd() uintptr
// Cleans up this instance of the network namespace; if this instance
// is the last user the namespace will be destroyed
Close() error
}
// SetNS sets the network namespace on a target file.
func SetNS(f *os.File, flags uintptr) error {
if runtime.GOOS != "linux" {
return fmt.Errorf("unsupported OS: %s", runtime.GOOS)
}
type netNS struct {
file *os.File
mounted bool
closed bool
}
trap, ok := setNsMap[runtime.GOARCH]
if !ok {
return fmt.Errorf("unsupported arch: %s", runtime.GOARCH)
}
func getCurrentThreadNetNSPath() string {
// /proc/self/ns/net returns the namespace of the main thread, not
// of whatever thread this goroutine is running on. Make sure we
// use the thread's net namespace since the thread is switching around
return fmt.Sprintf("/proc/%d/task/%d/ns/net", os.Getpid(), unix.Gettid())
}
_, _, err := syscall.RawSyscall(trap, f.Fd(), flags, 0)
if err != 0 {
// Returns an object representing the current OS thread's network namespace
func GetCurrentNS() (NetNS, error) {
return GetNS(getCurrentThreadNetNSPath())
}
const (
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/magic.h
NSFS_MAGIC = 0x6e736673
PROCFS_MAGIC = 0x9fa0
)
type NSPathNotExistErr struct{ msg string }
func (e NSPathNotExistErr) Error() string { return e.msg }
type NSPathNotNSErr struct{ msg string }
func (e NSPathNotNSErr) Error() string { return e.msg }
func IsNSorErr(nspath string) error {
stat := syscall.Statfs_t{}
if err := syscall.Statfs(nspath, &stat); err != nil {
if os.IsNotExist(err) {
err = NSPathNotExistErr{msg: fmt.Sprintf("failed to Statfs %q: %v", nspath, err)}
} else {
err = fmt.Errorf("failed to Statfs %q: %v", nspath, err)
}
return err
}
switch stat.Type {
case PROCFS_MAGIC:
// Kernel < 3.19
validPathContent := "ns/"
validName := strings.Contains(nspath, validPathContent)
if !validName {
return NSPathNotNSErr{msg: fmt.Sprintf("path %q doesn't contain %q", nspath, validPathContent)}
}
return nil
case NSFS_MAGIC:
// Kernel >= 3.19
return nil
default:
return NSPathNotNSErr{msg: fmt.Sprintf("unknown FS magic on %q: %x", nspath, stat.Type)}
}
}
// Returns an object representing the namespace referred to by @path
func GetNS(nspath string) (NetNS, error) {
err := IsNSorErr(nspath)
if err != nil {
return nil, err
}
fd, err := os.Open(nspath)
if err != nil {
return nil, err
}
return &netNS{file: fd}, nil
}
// Creates a new persistent network namespace and returns an object
// representing that namespace, without switching to it
func NewNS() (NetNS, error) {
const nsRunDir = "/var/run/netns"
b := make([]byte, 16)
_, err := rand.Reader.Read(b)
if err != nil {
return nil, fmt.Errorf("failed to generate random netns name: %v", err)
}
err = os.MkdirAll(nsRunDir, 0755)
if err != nil {
return nil, err
}
// create an empty file at the mount point
nsName := fmt.Sprintf("cni-%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
nsPath := path.Join(nsRunDir, nsName)
mountPointFd, err := os.Create(nsPath)
if err != nil {
return nil, err
}
mountPointFd.Close()
// Ensure the mount point is cleaned up on errors; if the namespace
// was successfully mounted this will have no effect because the file
// is in-use
defer os.RemoveAll(nsPath)
var wg sync.WaitGroup
wg.Add(1)
// do namespace work in a dedicated goroutine, so that we can safely
// Lock/Unlock OSThread without upsetting the lock/unlock state of
// the caller of this function
var fd *os.File
go (func() {
defer wg.Done()
runtime.LockOSThread()
var origNS NetNS
origNS, err = GetNS(getCurrentThreadNetNSPath())
if err != nil {
return
}
defer origNS.Close()
// create a new netns on the current thread
err = unix.Unshare(unix.CLONE_NEWNET)
if err != nil {
return
}
defer origNS.Set()
// bind mount the new netns from the current thread onto the mount point
err = unix.Mount(getCurrentThreadNetNSPath(), nsPath, "none", unix.MS_BIND, "")
if err != nil {
return
}
fd, err = os.Open(nsPath)
if err != nil {
return
}
})()
wg.Wait()
if err != nil {
unix.Unmount(nsPath, unix.MNT_DETACH)
return nil, fmt.Errorf("failed to create namespace: %v", err)
}
return &netNS{file: fd, mounted: true}, nil
}
func (ns *netNS) Path() string {
return ns.file.Name()
}
func (ns *netNS) Fd() uintptr {
return ns.file.Fd()
}
func (ns *netNS) errorIfClosed() error {
if ns.closed {
return fmt.Errorf("%q has already been closed", ns.file.Name())
}
return nil
}
func (ns *netNS) Close() error {
if err := ns.errorIfClosed(); err != nil {
return err
}
if err := ns.file.Close(); err != nil {
return fmt.Errorf("Failed to close %q: %v", ns.file.Name(), err)
}
ns.closed = true
if ns.mounted {
if err := unix.Unmount(ns.file.Name(), unix.MNT_DETACH); err != nil {
return fmt.Errorf("Failed to unmount namespace %s: %v", ns.file.Name(), err)
}
if err := os.RemoveAll(ns.file.Name()); err != nil {
return fmt.Errorf("Failed to clean up namespace %s: %v", ns.file.Name(), err)
}
ns.mounted = false
}
return nil
}
func (ns *netNS) Do(toRun func(NetNS) error) error {
if err := ns.errorIfClosed(); err != nil {
return err
}
containedCall := func(hostNS NetNS) error {
threadNS, err := GetNS(getCurrentThreadNetNSPath())
if err != nil {
return fmt.Errorf("failed to open current netns: %v", err)
}
defer threadNS.Close()
// switch to target namespace
if err = ns.Set(); err != nil {
return fmt.Errorf("error switching to ns %v: %v", ns.file.Name(), err)
}
defer threadNS.Set() // switch back
return toRun(hostNS)
}
// save a handle to current network namespace
hostNS, err := GetNS(getCurrentThreadNetNSPath())
if err != nil {
return fmt.Errorf("Failed to open current namespace: %v", err)
}
defer hostNS.Close()
var wg sync.WaitGroup
wg.Add(1)
var innerError error
go func() {
defer wg.Done()
runtime.LockOSThread()
innerError = containedCall(hostNS)
}()
wg.Wait()
return innerError
}
func (ns *netNS) Set() error {
if err := ns.errorIfClosed(); err != nil {
return err
}
if _, _, err := unix.Syscall(unix.SYS_SETNS, ns.Fd(), uintptr(unix.CLONE_NEWNET), 0); err != 0 {
return fmt.Errorf("Error switching to ns %v: %v", ns.file.Name(), err)
}
return nil
}
// WithNetNSPath executes the passed closure under the given network
// namespace, restoring the original namespace afterwards.
// Changing namespaces must be done on a goroutine that has been
// locked to an OS thread. If lockThread arg is true, this function
// locks the goroutine prior to change namespace and unlocks before
// returning
func WithNetNSPath(nspath string, lockThread bool, f func(*os.File) error) error {
ns, err := os.Open(nspath)
func WithNetNSPath(nspath string, toRun func(NetNS) error) error {
ns, err := GetNS(nspath)
if err != nil {
return fmt.Errorf("Failed to open %v: %v", nspath, err)
}
defer ns.Close()
return WithNetNS(ns, lockThread, f)
}
// WithNetNS executes the passed closure under the given network
// namespace, restoring the original namespace afterwards.
// Changing namespaces must be done on a goroutine that has been
// locked to an OS thread. If lockThread arg is true, this function
// locks the goroutine prior to change namespace and unlocks before
// returning. If the closure returns an error, WithNetNS attempts to
// restore the original namespace before returning.
func WithNetNS(ns *os.File, lockThread bool, f func(*os.File) error) error {
if lockThread {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
}
// save a handle to current (host) network namespace
thisNS, err := os.Open("/proc/self/ns/net")
if err != nil {
return fmt.Errorf("Failed to open /proc/self/ns/net: %v", err)
}
defer thisNS.Close()
if err = SetNS(ns, syscall.CLONE_NEWNET); err != nil {
return fmt.Errorf("Error switching to ns %v: %v", ns.Name(), err)
}
defer SetNS(thisNS, syscall.CLONE_NEWNET) // switch back
if err = f(thisNS); err != nil {
return err
}
return nil
defer ns.Close()
return ns.Do(toRun)
}

View File

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

View File

@ -1,135 +1,140 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ns_test
import (
"errors"
"fmt"
"math/rand"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"golang.org/x/sys/unix"
"github.com/appc/cni/pkg/ns"
"github.com/containernetworking/cni/pkg/ns"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"golang.org/x/sys/unix"
)
func getInodeCurNetNS() (uint64, error) {
curNS, err := ns.GetCurrentNS()
if err != nil {
return 0, err
}
defer curNS.Close()
return getInodeNS(curNS)
}
func getInodeNS(netns ns.NetNS) (uint64, error) {
return getInodeFd(int(netns.Fd()))
}
func getInode(path string) (uint64, error) {
file, err := os.Open(path)
if err != nil {
return 0, err
}
defer file.Close()
return getInodeF(file)
return getInodeFd(int(file.Fd()))
}
func getInodeF(file *os.File) (uint64, error) {
func getInodeFd(fd int) (uint64, error) {
stat := &unix.Stat_t{}
err := unix.Fstat(int(file.Fd()), stat)
err := unix.Fstat(fd, stat)
return stat.Ino, err
}
const CurrentNetNS = "/proc/self/ns/net"
var _ = Describe("Linux namespace operations", func() {
Describe("WithNetNS", func() {
var (
originalNetNS *os.File
targetNetNSName string
targetNetNSPath string
targetNetNS *os.File
originalNetNS ns.NetNS
targetNetNS ns.NetNS
)
BeforeEach(func() {
var err error
originalNetNS, err = os.Open(CurrentNetNS)
originalNetNS, err = ns.NewNS()
Expect(err).NotTo(HaveOccurred())
targetNetNSName = fmt.Sprintf("test-netns-%d", rand.Int())
err = exec.Command("ip", "netns", "add", targetNetNSName).Run()
Expect(err).NotTo(HaveOccurred())
targetNetNSPath = filepath.Join("/var/run/netns/", targetNetNSName)
targetNetNS, err = os.Open(targetNetNSPath)
targetNetNS, err = ns.NewNS()
Expect(err).NotTo(HaveOccurred())
})
AfterEach(func() {
Expect(targetNetNS.Close()).To(Succeed())
err := exec.Command("ip", "netns", "del", targetNetNSName).Run()
Expect(err).NotTo(HaveOccurred())
Expect(originalNetNS.Close()).To(Succeed())
})
It("executes the callback within the target network namespace", func() {
expectedInode, err := getInode(targetNetNSPath)
expectedInode, err := getInodeNS(targetNetNS)
Expect(err).NotTo(HaveOccurred())
var actualInode uint64
var innerErr error
err = ns.WithNetNS(targetNetNS, false, func(*os.File) error {
actualInode, innerErr = getInode(CurrentNetNS)
err = targetNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
actualInode, err := getInodeCurNetNS()
Expect(err).NotTo(HaveOccurred())
Expect(actualInode).To(Equal(expectedInode))
return nil
})
Expect(err).NotTo(HaveOccurred())
Expect(innerErr).NotTo(HaveOccurred())
Expect(actualInode).To(Equal(expectedInode))
})
It("provides the original namespace as the argument to the callback", func() {
hostNSInode, err := getInode(CurrentNetNS)
Expect(err).NotTo(HaveOccurred())
// Ensure we start in originalNetNS
err := originalNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
var inputNSInode uint64
var innerErr error
err = ns.WithNetNS(targetNetNS, false, func(inputNS *os.File) error {
inputNSInode, err = getInodeF(inputNS)
origNSInode, err := getInodeNS(originalNetNS)
Expect(err).NotTo(HaveOccurred())
err = targetNetNS.Do(func(hostNS ns.NetNS) error {
defer GinkgoRecover()
hostNSInode, err := getInodeNS(hostNS)
Expect(err).NotTo(HaveOccurred())
Expect(hostNSInode).To(Equal(origNSInode))
return nil
})
return nil
})
Expect(err).NotTo(HaveOccurred())
Expect(innerErr).NotTo(HaveOccurred())
Expect(inputNSInode).To(Equal(hostNSInode))
})
It("restores the calling thread to the original network namespace", func() {
preTestInode, err := getInode(CurrentNetNS)
Expect(err).NotTo(HaveOccurred())
err = ns.WithNetNS(targetNetNS, false, func(*os.File) error {
return nil
})
Expect(err).NotTo(HaveOccurred())
postTestInode, err := getInode(CurrentNetNS)
Expect(err).NotTo(HaveOccurred())
Expect(postTestInode).To(Equal(preTestInode))
})
Context("when the callback returns an error", func() {
It("restores the calling thread to the original namespace before returning", func() {
preTestInode, err := getInode(CurrentNetNS)
Expect(err).NotTo(HaveOccurred())
err := originalNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_ = ns.WithNetNS(targetNetNS, false, func(*os.File) error {
return errors.New("potato")
preTestInode, err := getInodeCurNetNS()
Expect(err).NotTo(HaveOccurred())
_ = targetNetNS.Do(func(ns.NetNS) error {
return errors.New("potato")
})
postTestInode, err := getInodeCurNetNS()
Expect(err).NotTo(HaveOccurred())
Expect(postTestInode).To(Equal(preTestInode))
return nil
})
postTestInode, err := getInode(CurrentNetNS)
Expect(err).NotTo(HaveOccurred())
Expect(postTestInode).To(Equal(preTestInode))
})
It("returns the error from the callback", func() {
err := ns.WithNetNS(targetNetNS, false, func(*os.File) error {
err := targetNetNS.Do(func(ns.NetNS) error {
return errors.New("potato")
})
Expect(err).To(MatchError("potato"))
@ -138,16 +143,110 @@ var _ = Describe("Linux namespace operations", func() {
Describe("validating inode mapping to namespaces", func() {
It("checks that different namespaces have different inodes", func() {
hostNSInode, err := getInode(CurrentNetNS)
origNSInode, err := getInodeNS(originalNetNS)
Expect(err).NotTo(HaveOccurred())
testNsInode, err := getInode(targetNetNSPath)
testNsInode, err := getInodeNS(targetNetNS)
Expect(err).NotTo(HaveOccurred())
Expect(hostNSInode).NotTo(Equal(0))
Expect(testNsInode).NotTo(Equal(0))
Expect(testNsInode).NotTo(Equal(hostNSInode))
Expect(testNsInode).NotTo(Equal(origNSInode))
})
It("should not leak a closed netns onto any threads in the process", func() {
By("creating a new netns")
createdNetNS, err := ns.NewNS()
Expect(err).NotTo(HaveOccurred())
By("discovering the inode of the created netns")
createdNetNSInode, err := getInodeNS(createdNetNS)
Expect(err).NotTo(HaveOccurred())
createdNetNS.Close()
By("comparing against the netns inode of every thread in the process")
for _, netnsPath := range allNetNSInCurrentProcess() {
netnsInode, err := getInode(netnsPath)
Expect(err).NotTo(HaveOccurred())
Expect(netnsInode).NotTo(Equal(createdNetNSInode))
}
})
It("fails when the path is not a namespace", func() {
tempFile, err := ioutil.TempFile("", "nstest")
Expect(err).NotTo(HaveOccurred())
defer tempFile.Close()
nspath := tempFile.Name()
defer os.Remove(nspath)
_, err = ns.GetNS(nspath)
Expect(err).To(HaveOccurred())
Expect(err).To(BeAssignableToTypeOf(ns.NSPathNotNSErr{}))
Expect(err).NotTo(BeAssignableToTypeOf(ns.NSPathNotExistErr{}))
})
})
Describe("closing a network namespace", func() {
It("should prevent further operations", func() {
createdNetNS, err := ns.NewNS()
Expect(err).NotTo(HaveOccurred())
err = createdNetNS.Close()
Expect(err).NotTo(HaveOccurred())
err = createdNetNS.Do(func(ns.NetNS) error { return nil })
Expect(err).To(HaveOccurred())
err = createdNetNS.Set()
Expect(err).To(HaveOccurred())
})
It("should only work once", func() {
createdNetNS, err := ns.NewNS()
Expect(err).NotTo(HaveOccurred())
err = createdNetNS.Close()
Expect(err).NotTo(HaveOccurred())
err = createdNetNS.Close()
Expect(err).To(HaveOccurred())
})
})
})
Describe("IsNSorErr", func() {
It("should detect a namespace", func() {
createdNetNS, err := ns.NewNS()
err = ns.IsNSorErr(createdNetNS.Path())
Expect(err).NotTo(HaveOccurred())
})
It("should refuse other paths", func() {
tempFile, err := ioutil.TempFile("", "nstest")
Expect(err).NotTo(HaveOccurred())
defer tempFile.Close()
nspath := tempFile.Name()
defer os.Remove(nspath)
err = ns.IsNSorErr(nspath)
Expect(err).To(HaveOccurred())
Expect(err).To(BeAssignableToTypeOf(ns.NSPathNotNSErr{}))
Expect(err).NotTo(BeAssignableToTypeOf(ns.NSPathNotExistErr{}))
})
It("should error on non-existing paths", func() {
err := ns.IsNSorErr("/tmp/IDoNotExist")
Expect(err).To(HaveOccurred())
Expect(err).To(BeAssignableToTypeOf(ns.NSPathNotExistErr{}))
Expect(err).NotTo(BeAssignableToTypeOf(ns.NSPathNotNSErr{}))
})
})
})
func allNetNSInCurrentProcess() []string {
pid := unix.Getpid()
paths, err := filepath.Glob(fmt.Sprintf("/proc/%d/task/*/ns/net", pid))
Expect(err).NotTo(HaveOccurred())
return paths
}

View File

@ -22,7 +22,7 @@ import (
"log"
"os"
"github.com/appc/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types"
)
// CmdArgs captures all the arguments passed in to the plugin
@ -36,28 +36,72 @@ type CmdArgs struct {
StdinData []byte
}
type reqForCmdEntry map[string]bool
// PluginMain is the "main" for a plugin. It accepts
// two callback functions for add and del commands.
func PluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error) {
var cmd, contID, netns, ifName, args, path string
vars := []struct {
name string
val *string
req bool
name string
val *string
reqForCmd reqForCmdEntry
}{
{"CNI_COMMAND", &cmd, true},
{"CNI_CONTAINERID", &contID, false},
{"CNI_NETNS", &netns, true},
{"CNI_IFNAME", &ifName, true},
{"CNI_ARGS", &args, false},
{"CNI_PATH", &path, true},
{
"CNI_COMMAND",
&cmd,
reqForCmdEntry{
"ADD": true,
"DEL": true,
},
},
{
"CNI_CONTAINERID",
&contID,
reqForCmdEntry{
"ADD": false,
"DEL": false,
},
},
{
"CNI_NETNS",
&netns,
reqForCmdEntry{
"ADD": true,
"DEL": false,
},
},
{
"CNI_IFNAME",
&ifName,
reqForCmdEntry{
"ADD": true,
"DEL": true,
},
},
{
"CNI_ARGS",
&args,
reqForCmdEntry{
"ADD": false,
"DEL": false,
},
},
{
"CNI_PATH",
&path,
reqForCmdEntry{
"ADD": true,
"DEL": true,
},
},
}
argsMissing := false
for _, v := range vars {
*v.val = os.Getenv(v.name)
if v.req && *v.val == "" {
if v.reqForCmd[cmd] && *v.val == "" {
log.Printf("%v env variable missing", v.name)
argsMissing = true
}

View File

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

View File

@ -1,3 +1,17 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package skel
import (
@ -57,5 +71,14 @@ var _ = Describe("Skel", func() {
// Expect(err).NotTo(HaveOccurred())
// PluginMain(fErr, nil)
// })
It("should not fail with DEL and no NETNS and noop callback", func() {
err := os.Setenv("CNI_COMMAND", "DEL")
Expect(err).NotTo(HaveOccurred())
err = os.Unsetenv("CNI_NETNS")
Expect(err).NotTo(HaveOccurred())
PluginMain(nil, fNoop)
})
})
})

77
pkg/testutils/cmd.go Normal file
View File

@ -0,0 +1,77 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package testutils
import (
"encoding/json"
"io/ioutil"
"os"
"github.com/containernetworking/cni/pkg/types"
)
func envCleanup() {
os.Unsetenv("CNI_COMMAND")
os.Unsetenv("CNI_PATH")
os.Unsetenv("CNI_NETNS")
os.Unsetenv("CNI_IFNAME")
}
func CmdAddWithResult(cniNetns, cniIfname string, f func() error) (*types.Result, error) {
os.Setenv("CNI_COMMAND", "ADD")
os.Setenv("CNI_PATH", os.Getenv("PATH"))
os.Setenv("CNI_NETNS", cniNetns)
os.Setenv("CNI_IFNAME", cniIfname)
defer envCleanup()
// Redirect stdout to capture plugin result
oldStdout := os.Stdout
r, w, err := os.Pipe()
if err != nil {
return nil, err
}
os.Stdout = w
err = f()
w.Close()
if err != nil {
return nil, err
}
// parse the result
out, err := ioutil.ReadAll(r)
os.Stdout = oldStdout
if err != nil {
return nil, err
}
result := types.Result{}
err = json.Unmarshal(out, &result)
if err != nil {
return nil, err
}
return &result, nil
}
func CmdDelWithResult(cniNetns, cniIfname string, f func() error) error {
os.Setenv("CNI_COMMAND", "DEL")
os.Setenv("CNI_PATH", os.Getenv("PATH"))
os.Setenv("CNI_NETNS", cniNetns)
os.Setenv("CNI_IFNAME", cniIfname)
defer envCleanup()
return f()
}

View File

@ -1,9 +1,23 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types_test
import (
"reflect"
. "github.com/appc/cni/pkg/types"
. "github.com/containernetworking/cni/pkg/types"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -27,8 +27,8 @@ import (
"runtime"
"sync"
"github.com/appc/cni/pkg/skel"
"github.com/appc/cni/pkg/types"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/coreos/go-systemd/activation"
)

View File

@ -19,7 +19,6 @@ import (
"log"
"math/rand"
"net"
"os"
"sync"
"time"
@ -27,8 +26,8 @@ import (
"github.com/d2g/dhcp4client"
"github.com/vishvananda/netlink"
"github.com/appc/cni/pkg/ns"
"github.com/appc/cni/pkg/types"
"github.com/containernetworking/cni/pkg/ns"
"github.com/containernetworking/cni/pkg/types"
)
// RFC 2131 suggests using exponential backoff, starting with 4sec
@ -74,7 +73,7 @@ func AcquireLease(clientID, netns, ifName string) (*DHCPLease, error) {
l.wg.Add(1)
go func() {
errCh <- ns.WithNetNSPath(netns, true, func(_ *os.File) error {
errCh <- ns.WithNetNSPath(netns, func(_ ns.NetNS) error {
defer l.wg.Done()
link, err := netlink.LinkByName(ifName)

View File

@ -20,8 +20,8 @@ import (
"os"
"path/filepath"
"github.com/appc/cni/pkg/skel"
"github.com/appc/cni/pkg/types"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
)
const socketPath = "/run/cni/dhcp.sock"

View File

@ -20,7 +20,7 @@ import (
"net"
"time"
"github.com/appc/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types"
"github.com/d2g/dhcp4"
)

View File

@ -18,7 +18,7 @@ import (
"net"
"testing"
"github.com/appc/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types"
"github.com/d2g/dhcp4"
)

View File

@ -18,9 +18,9 @@ import (
"fmt"
"net"
"github.com/appc/cni/pkg/ip"
"github.com/appc/cni/pkg/types"
"github.com/appc/cni/plugins/ipam/host-local/backend"
"github.com/containernetworking/cni/pkg/ip"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/plugins/ipam/host-local/backend"
)
type IPAllocator struct {

View File

@ -19,7 +19,7 @@ import (
"fmt"
"net"
"github.com/appc/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types"
)
// IPAMConfig represents the IP related network configuration.

View File

@ -15,10 +15,10 @@
package main
import (
"github.com/appc/cni/plugins/ipam/host-local/backend/disk"
"github.com/containernetworking/cni/plugins/ipam/host-local/backend/disk"
"github.com/appc/cni/pkg/skel"
"github.com/appc/cni/pkg/types"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
)
func main() {

View File

@ -19,16 +19,15 @@ import (
"errors"
"fmt"
"net"
"os"
"runtime"
"syscall"
"github.com/appc/cni/pkg/ip"
"github.com/appc/cni/pkg/ipam"
"github.com/appc/cni/pkg/ns"
"github.com/appc/cni/pkg/skel"
"github.com/appc/cni/pkg/types"
"github.com/appc/cni/pkg/utils"
"github.com/containernetworking/cni/pkg/ip"
"github.com/containernetworking/cni/pkg/ipam"
"github.com/containernetworking/cni/pkg/ns"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/utils"
"github.com/vishvananda/netlink"
)
@ -36,10 +35,12 @@ const defaultBrName = "cni0"
type NetConf struct {
types.NetConf
BrName string `json:"bridge"`
IsGW bool `json:"isGateway"`
IPMasq bool `json:"ipMasq"`
MTU int `json:"mtu"`
BrName string `json:"bridge"`
IsGW bool `json:"isGateway"`
IsDefaultGW bool `json:"isDefaultGateway"`
IPMasq bool `json:"ipMasq"`
MTU int `json:"mtu"`
HairpinMode bool `json:"hairpinMode"`
}
func init() {
@ -101,6 +102,11 @@ func ensureBridge(brName string, mtu int) (*netlink.Bridge, error) {
LinkAttrs: netlink.LinkAttrs{
Name: brName,
MTU: mtu,
// Let kernel use default txqueuelen; leaving it unset
// means 0, and a zero-length TX queue messes up FIFO
// traffic shapers which use TX queue length as the
// default packet limit
TxQLen: -1,
},
}
@ -123,10 +129,10 @@ func ensureBridge(brName string, mtu int) (*netlink.Bridge, error) {
return br, nil
}
func setupVeth(netns string, br *netlink.Bridge, ifName string, mtu int) error {
func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairpinMode bool) error {
var hostVethName string
err := ns.WithNetNSPath(netns, false, func(hostNS *os.File) error {
err := netns.Do(func(hostNS ns.NetNS) error {
// create the veth pair in the container and move host end into host netns
hostVeth, _, err := ip.SetupVeth(ifName, mtu, hostNS)
if err != nil {
@ -151,6 +157,11 @@ func setupVeth(netns string, br *netlink.Bridge, ifName string, mtu int) error {
return fmt.Errorf("failed to connect %q to bridge %v: %v", hostVethName, br.Attrs().Name, err)
}
// set hairpin mode
if err = netlink.LinkSetHairpin(hostVeth, hairpinMode); err != nil {
return fmt.Errorf("failed to setup hairpin mode for %v: %v", hostVethName, err)
}
return nil
}
@ -175,12 +186,22 @@ func cmdAdd(args *skel.CmdArgs) error {
return err
}
if n.IsDefaultGW {
n.IsGW = true
}
br, err := setupBridge(n)
if err != nil {
return err
}
if err = setupVeth(args.Netns, br, args.IfName, n.MTU); err != nil {
netns, err := ns.GetNS(args.Netns)
if err != nil {
return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
}
defer netns.Close()
if err = setupVeth(netns, br, args.IfName, n.MTU, n.HairpinMode); err != nil {
return err
}
@ -190,6 +211,7 @@ func cmdAdd(args *skel.CmdArgs) error {
return err
}
// TODO: make this optional when IPv6 is supported
if result.IP4 == nil {
return errors.New("IPAM plugin returned missing IPv4 config")
}
@ -198,10 +220,35 @@ func cmdAdd(args *skel.CmdArgs) error {
result.IP4.Gateway = calcGatewayIP(&result.IP4.IP)
}
err = ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error {
if err := netns.Do(func(_ ns.NetNS) error {
// set the default gateway if requested
if n.IsDefaultGW {
_, defaultNet, err := net.ParseCIDR("0.0.0.0/0")
if err != nil {
return err
}
for _, route := range result.IP4.Routes {
if defaultNet.String() == route.Dst.String() {
if route.GW != nil && !route.GW.Equal(result.IP4.Gateway) {
return fmt.Errorf(
"isDefaultGateway ineffective because IPAM sets default route via %q",
route.GW,
)
}
}
}
result.IP4.Routes = append(
result.IP4.Routes,
types.Route{Dst: *defaultNet, GW: result.IP4.Gateway},
)
// TODO: IPV6
}
return ipam.ConfigureIface(args.IfName, result)
})
if err != nil {
}); err != nil {
return err
}
@ -238,14 +285,33 @@ func cmdDel(args *skel.CmdArgs) error {
return err
}
err = ipam.ExecDel(n.IPAM.Type, args.StdinData)
if err := ipam.ExecDel(n.IPAM.Type, args.StdinData); err != nil {
return err
}
if args.Netns == "" {
return nil
}
var ipn *net.IPNet
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
var err error
ipn, err = ip.DelLinkByNameAddr(args.IfName, netlink.FAMILY_V4)
return err
})
if err != nil {
return err
}
return ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error {
return ip.DelLinkByName(args.IfName)
})
if n.IPMasq {
chain := utils.FormatChainName(n.Name, args.ContainerID)
comment := utils.FormatComment(n.Name, args.ContainerID)
if err = ip.TeardownIPMasq(ipn, chain, comment); err != nil {
return err
}
}
return nil
}
func main() {

View File

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

View File

@ -0,0 +1,239 @@
// Copyright 2015 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"net"
"syscall"
"github.com/containernetworking/cni/pkg/ns"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/testutils"
"github.com/containernetworking/cni/pkg/types"
"github.com/vishvananda/netlink"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("bridge Operations", func() {
var originalNS ns.NetNS
BeforeEach(func() {
// Create a new NetNS so we don't modify the host
var err error
originalNS, err = ns.NewNS()
Expect(err).NotTo(HaveOccurred())
})
AfterEach(func() {
Expect(originalNS.Close()).To(Succeed())
})
It("creates a bridge", func() {
const IFNAME = "bridge0"
conf := &NetConf{
NetConf: types.NetConf{
Name: "testConfig",
Type: "bridge",
},
BrName: IFNAME,
IsGW: false,
IPMasq: false,
MTU: 5000,
}
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
bridge, err := setupBridge(conf)
Expect(err).NotTo(HaveOccurred())
Expect(bridge.Attrs().Name).To(Equal(IFNAME))
// Double check that the link was added
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal(IFNAME))
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("handles an existing bridge", func() {
const IFNAME = "bridge0"
err := originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := netlink.LinkAdd(&netlink.Bridge{
LinkAttrs: netlink.LinkAttrs{
Name: IFNAME,
},
})
Expect(err).NotTo(HaveOccurred())
link, err := netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal(IFNAME))
ifindex := link.Attrs().Index
conf := &NetConf{
NetConf: types.NetConf{
Name: "testConfig",
Type: "bridge",
},
BrName: IFNAME,
IsGW: false,
IPMasq: false,
}
bridge, err := setupBridge(conf)
Expect(err).NotTo(HaveOccurred())
Expect(bridge.Attrs().Name).To(Equal(IFNAME))
Expect(bridge.Attrs().Index).To(Equal(ifindex))
// Double check that the link has the same ifindex
link, err = netlink.LinkByName(IFNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal(IFNAME))
Expect(link.Attrs().Index).To(Equal(ifindex))
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("configures and deconfigures a bridge and veth with default route with ADD/DEL", func() {
const BRNAME = "cni0"
const IFNAME = "eth0"
gwaddr, subnet, err := net.ParseCIDR("10.1.2.1/24")
Expect(err).NotTo(HaveOccurred())
conf := fmt.Sprintf(`{
"name": "mynet",
"type": "bridge",
"bridge": "%s",
"isDefaultGateway": true,
"ipMasq": false,
"ipam": {
"type": "host-local",
"subnet": "%s"
}
}`, BRNAME, subnet.String())
targetNs, err := ns.NewNS()
Expect(err).NotTo(HaveOccurred())
defer targetNs.Close()
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNs.Path(),
IfName: IFNAME,
StdinData: []byte(conf),
}
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, err := testutils.CmdAddWithResult(targetNs.Path(), IFNAME, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
// Make sure bridge link exists
link, err := netlink.LinkByName(BRNAME)
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal(BRNAME))
// Ensure bridge has gateway address
addrs, err := netlink.AddrList(link, syscall.AF_INET)
Expect(err).NotTo(HaveOccurred())
Expect(len(addrs)).To(BeNumerically(">", 0))
found := false
subnetPrefix, subnetBits := subnet.Mask.Size()
for _, a := range addrs {
aPrefix, aBits := a.IPNet.Mask.Size()
if a.IPNet.IP.Equal(gwaddr) && aPrefix == subnetPrefix && aBits == subnetBits {
found = true
break
}
}
Expect(found).To(Equal(true))
// Check for the veth link in the main namespace
links, err := netlink.LinkList()
Expect(err).NotTo(HaveOccurred())
Expect(len(links)).To(Equal(3)) // Bridge, veth, and loopback
for _, l := range links {
if l.Attrs().Name != BRNAME && l.Attrs().Name != "lo" {
_, isVeth := l.(*netlink.Veth)
Expect(isVeth).To(Equal(true))
}
}
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// Find the veth peer in the container namespace and the default route
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))
// Ensure the default route
routes, err := netlink.RouteList(link, 0)
Expect(err).NotTo(HaveOccurred())
var defaultRouteFound bool
for _, route := range routes {
defaultRouteFound = (route.Dst == nil && route.Src == nil && route.Gw.Equal(gwaddr))
if defaultRouteFound {
break
}
}
Expect(defaultRouteFound).To(Equal(true))
return nil
})
Expect(err).NotTo(HaveOccurred())
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := testutils.CmdDelWithResult(targetNs.Path(), IFNAME, 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

@ -18,14 +18,13 @@ import (
"encoding/json"
"errors"
"fmt"
"os"
"runtime"
"github.com/appc/cni/pkg/ip"
"github.com/appc/cni/pkg/ipam"
"github.com/appc/cni/pkg/ns"
"github.com/appc/cni/pkg/skel"
"github.com/appc/cni/pkg/types"
"github.com/containernetworking/cni/pkg/ip"
"github.com/containernetworking/cni/pkg/ipam"
"github.com/containernetworking/cni/pkg/ns"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/vishvananda/netlink"
)
@ -65,7 +64,7 @@ func modeFromString(s string) (netlink.IPVlanMode, error) {
}
}
func createIpvlan(conf *NetConf, ifName string, netns *os.File) error {
func createIpvlan(conf *NetConf, ifName string, netns ns.NetNS) error {
mode, err := modeFromString(conf.Mode)
if err != nil {
return err
@ -97,7 +96,7 @@ func createIpvlan(conf *NetConf, ifName string, netns *os.File) error {
return fmt.Errorf("failed to create ipvlan: %v", err)
}
return ns.WithNetNS(netns, false, func(_ *os.File) error {
return netns.Do(func(_ ns.NetNS) error {
err := renameLink(tmpName, ifName)
if err != nil {
return fmt.Errorf("failed to rename ipvlan to %q: %v", ifName, err)
@ -112,9 +111,9 @@ func cmdAdd(args *skel.CmdArgs) error {
return err
}
netns, err := os.Open(args.Netns)
netns, err := ns.GetNS(args.Netns)
if err != nil {
return fmt.Errorf("failed to open netns %q: %v", netns, err)
return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
}
defer netns.Close()
@ -131,7 +130,7 @@ func cmdAdd(args *skel.CmdArgs) error {
return errors.New("IPAM plugin returned missing IPv4 config")
}
err = ns.WithNetNS(netns, false, func(_ *os.File) error {
err = netns.Do(func(_ ns.NetNS) error {
return ipam.ConfigureIface(args.IfName, result)
})
if err != nil {
@ -153,7 +152,11 @@ func cmdDel(args *skel.CmdArgs) error {
return err
}
return ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error {
if args.Netns == "" {
return nil
}
return ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
return ip.DelLinkByName(args.IfName)
})
}

View File

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

View File

@ -0,0 +1,168 @@
// Copyright 2015 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"github.com/containernetworking/cni/pkg/ns"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/testutils"
"github.com/containernetworking/cni/pkg/types"
"github.com/vishvananda/netlink"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
const MASTER_NAME = "eth0"
var _ = Describe("ipvlan Operations", func() {
var originalNS ns.NetNS
BeforeEach(func() {
// Create a new NetNS so we don't modify the host
var err error
originalNS, err = ns.NewNS()
Expect(err).NotTo(HaveOccurred())
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
// Add master
err = netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: MASTER_NAME,
},
})
Expect(err).NotTo(HaveOccurred())
_, err = netlink.LinkByName(MASTER_NAME)
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
AfterEach(func() {
Expect(originalNS.Close()).To(Succeed())
})
It("creates an ipvlan link in a non-default namespace", func() {
conf := &NetConf{
NetConf: types.NetConf{
Name: "testConfig",
Type: "ipvlan",
},
Master: MASTER_NAME,
Mode: "l2",
MTU: 1500,
}
// Create ipvlan in other namespace
targetNs, err := ns.NewNS()
Expect(err).NotTo(HaveOccurred())
defer targetNs.Close()
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := createIpvlan(conf, "foobar0", targetNs)
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// Make sure ipvlan link exists in the target namespace
err = targetNs.Do(func(ns.NetNS) error {
defer GinkgoRecover()
link, err := netlink.LinkByName("foobar0")
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal("foobar0"))
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("configures and deconfigures an iplvan link with ADD/DEL", func() {
const IFNAME = "ipvl0"
conf := fmt.Sprintf(`{
"name": "mynet",
"type": "ipvlan",
"master": "%s",
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24"
}
}`, MASTER_NAME)
targetNs, err := ns.NewNS()
Expect(err).NotTo(HaveOccurred())
defer targetNs.Close()
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNs.Path(),
IfName: IFNAME,
StdinData: []byte(conf),
}
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, err := testutils.CmdAddWithResult(targetNs.Path(), IFNAME, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// Make sure ipvlan 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))
return nil
})
Expect(err).NotTo(HaveOccurred())
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = testutils.CmdDelWithResult(targetNs.Path(), IFNAME, func() error {
return cmdDel(args)
})
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
// Make sure ipvlan 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,17 +1,29 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"os"
"github.com/appc/cni/pkg/ns"
"github.com/appc/cni/pkg/skel"
"github.com/appc/cni/pkg/types"
"github.com/containernetworking/cni/pkg/ns"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/vishvananda/netlink"
)
func cmdAdd(args *skel.CmdArgs) error {
args.IfName = "lo" // ignore config, this only works for loopback
err := ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error {
err := ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
link, err := netlink.LinkByName(args.IfName)
if err != nil {
return err // not tested
@ -34,7 +46,7 @@ func cmdAdd(args *skel.CmdArgs) error {
func cmdDel(args *skel.CmdArgs) error {
args.IfName = "lo" // ignore config, this only works for loopback
err := ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error {
err := ns.WithNetNSPath(args.Netns, func(ns.NetNS) error {
link, err := netlink.LinkByName(args.IfName)
if err != nil {
return err // not tested

View File

@ -1,18 +1,26 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main_test
import (
"fmt"
"os"
"runtime"
"github.com/onsi/gomega/gexec"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
"golang.org/x/sys/unix"
)
var pathToLoPlugin string
@ -24,54 +32,10 @@ func TestLoopback(t *testing.T) {
var _ = BeforeSuite(func() {
var err error
pathToLoPlugin, err = gexec.Build("github.com/appc/cni/plugins/main/loopback")
pathToLoPlugin, err = gexec.Build("github.com/containernetworking/cni/plugins/main/loopback")
Expect(err).NotTo(HaveOccurred())
})
var _ = AfterSuite(func() {
gexec.CleanupBuildArtifacts()
})
func makeNetworkNS(containerID string) string {
namespace := "/var/run/netns/" + containerID
pid := unix.Getpid()
tid := unix.Gettid()
err := os.MkdirAll("/var/run/netns", 0600)
Expect(err).NotTo(HaveOccurred())
runtime.LockOSThread()
defer runtime.UnlockOSThread()
go (func() {
defer GinkgoRecover()
err = unix.Unshare(unix.CLONE_NEWNET)
Expect(err).NotTo(HaveOccurred())
fd, err := os.Create(namespace)
Expect(err).NotTo(HaveOccurred())
defer fd.Close()
err = unix.Mount("/proc/self/ns/net", namespace, "none", unix.MS_BIND, "")
Expect(err).NotTo(HaveOccurred())
})()
Eventually(namespace).Should(BeAnExistingFile())
fd, err := unix.Open(fmt.Sprintf("/proc/%d/task/%d/ns/net", pid, tid), unix.O_RDONLY, 0)
Expect(err).NotTo(HaveOccurred())
defer unix.Close(fd)
_, _, e1 := unix.Syscall(unix.SYS_SETNS, uintptr(fd), uintptr(unix.CLONE_NEWNET), 0)
Expect(e1).To(BeZero())
return namespace
}
func removeNetworkNS(networkNS string) error {
err := unix.Unmount(networkNS, unix.MNT_DETACH)
err = os.RemoveAll(networkNS)
return err
}

View File

@ -1,13 +1,26 @@
// Copyright 2016 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main_test
import (
"fmt"
"net"
"os"
"os/exec"
"strings"
"github.com/appc/cni/pkg/ns"
"github.com/containernetworking/cni/pkg/ns"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gbytes"
@ -16,7 +29,7 @@ import (
var _ = Describe("Loopback", func() {
var (
networkNS string
networkNS ns.NetNS
containerID string
command *exec.Cmd
environ []string
@ -24,12 +37,14 @@ var _ = Describe("Loopback", func() {
BeforeEach(func() {
command = exec.Command(pathToLoPlugin)
containerID = "some-container-id"
networkNS = makeNetworkNS(containerID)
var err error
networkNS, err = ns.NewNS()
Expect(err).NotTo(HaveOccurred())
environ = []string{
fmt.Sprintf("CNI_CONTAINERID=%s", containerID),
fmt.Sprintf("CNI_NETNS=%s", networkNS),
fmt.Sprintf("CNI_NETNS=%s", networkNS.Path()),
fmt.Sprintf("CNI_IFNAME=%s", "this is ignored"),
fmt.Sprintf("CNI_ARGS=%s", "none"),
fmt.Sprintf("CNI_PATH=%s", "/some/test/path"),
@ -38,7 +53,7 @@ var _ = Describe("Loopback", func() {
})
AfterEach(func() {
Expect(removeNetworkNS(networkNS)).To(Succeed())
Expect(networkNS.Close()).To(Succeed())
})
Context("when given a network namespace", func() {
@ -52,7 +67,7 @@ var _ = Describe("Loopback", func() {
Eventually(session).Should(gexec.Exit(0))
var lo *net.Interface
err = ns.WithNetNSPath(networkNS, true, func(hostNS *os.File) error {
err = networkNS.Do(func(ns.NetNS) error {
var err error
lo, err = net.InterfaceByName("lo")
return err
@ -72,7 +87,7 @@ var _ = Describe("Loopback", func() {
Eventually(session).Should(gexec.Exit(0))
var lo *net.Interface
err = ns.WithNetNSPath(networkNS, true, func(hostNS *os.File) error {
err = networkNS.Do(func(ns.NetNS) error {
var err error
lo, err = net.InterfaceByName("lo")
return err

View File

@ -18,17 +18,21 @@ import (
"encoding/json"
"errors"
"fmt"
"os"
"runtime"
"github.com/appc/cni/pkg/ip"
"github.com/appc/cni/pkg/ipam"
"github.com/appc/cni/pkg/ns"
"github.com/appc/cni/pkg/skel"
"github.com/appc/cni/pkg/types"
"github.com/containernetworking/cni/pkg/ip"
"github.com/containernetworking/cni/pkg/ipam"
"github.com/containernetworking/cni/pkg/ns"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/utils/sysctl"
"github.com/vishvananda/netlink"
)
const (
IPv4InterfaceArpProxySysctlTemplate = "net.ipv4.conf.%s.proxy_arp"
)
type NetConf struct {
types.NetConf
Master string `json:"master"`
@ -69,7 +73,7 @@ func modeFromString(s string) (netlink.MacvlanMode, error) {
}
}
func createMacvlan(conf *NetConf, ifName string, netns *os.File) error {
func createMacvlan(conf *NetConf, ifName string, netns ns.NetNS) error {
mode, err := modeFromString(conf.Mode)
if err != nil {
return err
@ -80,7 +84,7 @@ func createMacvlan(conf *NetConf, ifName string, netns *os.File) error {
return fmt.Errorf("failed to lookup master %q: %v", conf.Master, err)
}
// due to kernel bug we have to create with tmpname or it might
// due to kernel bug we have to create with tmpName or it might
// collide with the name on the host and error out
tmpName, err := ip.RandomVethName()
if err != nil {
@ -101,9 +105,18 @@ func createMacvlan(conf *NetConf, ifName string, netns *os.File) error {
return fmt.Errorf("failed to create macvlan: %v", err)
}
return ns.WithNetNS(netns, false, func(_ *os.File) error {
return netns.Do(func(_ ns.NetNS) error {
// TODO: duplicate following lines for ipv6 support, when it will be added in other places
ipv4SysctlValueName := fmt.Sprintf(IPv4InterfaceArpProxySysctlTemplate, tmpName)
if _, err := sysctl.Sysctl(ipv4SysctlValueName, "1"); err != nil {
// remove the newly added link and ignore errors, because we already are in a failed state
_ = netlink.LinkDel(mv)
return fmt.Errorf("failed to set proxy_arp on newly added interface %q: %v", tmpName, err)
}
err := renameLink(tmpName, ifName)
if err != nil {
_ = netlink.LinkDel(mv)
return fmt.Errorf("failed to rename macvlan to %q: %v", ifName, err)
}
return nil
@ -116,7 +129,7 @@ func cmdAdd(args *skel.CmdArgs) error {
return err
}
netns, err := os.Open(args.Netns)
netns, err := ns.GetNS(args.Netns)
if err != nil {
return fmt.Errorf("failed to open netns %q: %v", netns, err)
}
@ -135,7 +148,7 @@ func cmdAdd(args *skel.CmdArgs) error {
return errors.New("IPAM plugin returned missing IPv4 config")
}
err = ns.WithNetNS(netns, false, func(_ *os.File) error {
err = netns.Do(func(_ ns.NetNS) error {
return ipam.ConfigureIface(args.IfName, result)
})
if err != nil {
@ -157,7 +170,11 @@ func cmdDel(args *skel.CmdArgs) error {
return err
}
return ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error {
if args.Netns == "" {
return nil
}
return ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
return ip.DelLinkByName(args.IfName)
})
}

View File

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

View File

@ -0,0 +1,168 @@
// Copyright 2015 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"github.com/containernetworking/cni/pkg/ns"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/testutils"
"github.com/containernetworking/cni/pkg/types"
"github.com/vishvananda/netlink"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
const MASTER_NAME = "eth0"
var _ = Describe("macvlan Operations", func() {
var originalNS ns.NetNS
BeforeEach(func() {
// Create a new NetNS so we don't modify the host
var err error
originalNS, err = ns.NewNS()
Expect(err).NotTo(HaveOccurred())
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
// Add master
err = netlink.LinkAdd(&netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: MASTER_NAME,
},
})
Expect(err).NotTo(HaveOccurred())
_, err = netlink.LinkByName(MASTER_NAME)
Expect(err).NotTo(HaveOccurred())
return nil
})
Expect(err).NotTo(HaveOccurred())
})
AfterEach(func() {
Expect(originalNS.Close()).To(Succeed())
})
It("creates an macvlan link in a non-default namespace", func() {
conf := &NetConf{
NetConf: types.NetConf{
Name: "testConfig",
Type: "macvlan",
},
Master: MASTER_NAME,
Mode: "bridge",
MTU: 1500,
}
targetNs, err := ns.NewNS()
Expect(err).NotTo(HaveOccurred())
defer targetNs.Close()
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err = createMacvlan(conf, "foobar0", targetNs)
Expect(err).NotTo(HaveOccurred())
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("foobar0")
Expect(err).NotTo(HaveOccurred())
Expect(link.Attrs().Name).To(Equal("foobar0"))
return nil
})
Expect(err).NotTo(HaveOccurred())
})
It("configures and deconfigures a macvlan link with ADD/DEL", func() {
const IFNAME = "macvl0"
conf := fmt.Sprintf(`{
"name": "mynet",
"type": "macvlan",
"master": "%s",
"ipam": {
"type": "host-local",
"subnet": "10.1.2.0/24"
}
}`, MASTER_NAME)
targetNs, err := ns.NewNS()
Expect(err).NotTo(HaveOccurred())
defer targetNs.Close()
args := &skel.CmdArgs{
ContainerID: "dummy",
Netns: targetNs.Path(),
IfName: IFNAME,
StdinData: []byte(conf),
}
// Make sure macvlan link exists in the target namespace
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
_, err := testutils.CmdAddWithResult(targetNs.Path(), IFNAME, func() error {
return cmdAdd(args)
})
Expect(err).NotTo(HaveOccurred())
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))
return nil
})
Expect(err).NotTo(HaveOccurred())
err = originalNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
err := testutils.CmdDelWithResult(targetNs.Path(), IFNAME, 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

@ -24,12 +24,12 @@ import (
"github.com/vishvananda/netlink"
"github.com/appc/cni/pkg/ip"
"github.com/appc/cni/pkg/ipam"
"github.com/appc/cni/pkg/ns"
"github.com/appc/cni/pkg/skel"
"github.com/appc/cni/pkg/types"
"github.com/appc/cni/pkg/utils"
"github.com/containernetworking/cni/pkg/ip"
"github.com/containernetworking/cni/pkg/ipam"
"github.com/containernetworking/cni/pkg/ns"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/utils"
)
func init() {
@ -58,7 +58,7 @@ func setupContainerVeth(netns, ifName string, mtu int, pr *types.Result) (string
// In other words we force all traffic to ARP via the gateway except for GW itself.
var hostVethName string
err := ns.WithNetNSPath(netns, false, func(hostNS *os.File) error {
err := ns.WithNetNSPath(netns, func(hostNS ns.NetNS) error {
hostVeth, _, err := ip.SetupVeth(ifName, mtu, hostNS)
if err != nil {
return err
@ -195,8 +195,16 @@ func cmdDel(args *skel.CmdArgs) error {
return fmt.Errorf("failed to load netconf: %v", err)
}
if err := ipam.ExecDel(conf.IPAM.Type, args.StdinData); err != nil {
return err
}
if args.Netns == "" {
return nil
}
var ipn *net.IPNet
err := ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error {
err := ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
var err error
ipn, err = ip.DelLinkByNameAddr(args.IfName, netlink.FAMILY_V4)
return err
@ -213,7 +221,7 @@ func cmdDel(args *skel.CmdArgs) error {
}
}
return ipam.ExecDel(conf.IPAM.Type, args.StdinData)
return nil
}
func main() {

View File

@ -29,9 +29,9 @@ import (
"strconv"
"strings"
"github.com/appc/cni/pkg/invoke"
"github.com/appc/cni/pkg/skel"
"github.com/appc/cni/pkg/types"
"github.com/containernetworking/cni/pkg/invoke"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
)
const (

View File

@ -21,13 +21,12 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/appc/cni/pkg/ns"
"github.com/appc/cni/pkg/skel"
"github.com/appc/cni/pkg/types"
"github.com/containernetworking/cni/pkg/ns"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
)
// TuningConf represents the network tuning configuration.
@ -45,7 +44,7 @@ func cmdAdd(args *skel.CmdArgs) error {
// The directory /proc/sys/net is per network namespace. Enter in the
// network namespace before writing on it.
err := ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error {
err := ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
for key, value := range tuningConf.SysCtl {
fileName := filepath.Join("/proc/sys", strings.Replace(key, ".", "/", -1))
fileName = filepath.Clean(fileName)

18
test
View File

@ -11,8 +11,8 @@ set -e
source ./build
TESTABLE="plugins/ipam/dhcp plugins/main/loopback pkg/invoke pkg/ns pkg/skel pkg/types pkg/utils"
FORMATTABLE="$TESTABLE libcni pkg/ip pkg/ns pkg/types pkg/ipam plugins/ipam/host-local plugins/main/bridge plugins/meta/flannel plugins/meta/tuning"
TESTABLE="plugins/ipam/dhcp plugins/main/loopback pkg/invoke pkg/ns pkg/skel pkg/types pkg/utils plugins/main/ipvlan plugins/main/macvlan plugins/main/bridge"
FORMATTABLE="$TESTABLE libcni pkg/ip pkg/ipam pkg/testutils plugins/ipam/host-local plugins/main/bridge plugins/meta/flannel plugins/meta/tuning"
# user has not provided PKG override
if [ -z "$PKG" ]; then
@ -35,7 +35,7 @@ TEST=${split[@]/#/${REPO_PATH}/}
echo -n "Running tests "
function testrun {
sudo -E bash -c "umask 0; PATH=\$GOROOT/bin:\$PATH go test -covermode set $@"
sudo -E bash -c "umask 0; PATH=$GOROOT/bin:$GOBIN:$PATH go test -covermode set $@"
}
if [ ! -z "${COVERALLS}" ]; then
echo "with coverage profile generation..."
@ -65,4 +65,16 @@ if [ -n "${vetRes}" ]; then
exit 255
fi
echo "Checking license header..."
licRes=$(
for file in $(find . -type f -iname '*.go' ! -path './vendor/*'); do
head -n1 "${file}" | grep -Eq "(Copyright|generated)" || echo -e " ${file}"
done
)
if [ -n "${licRes}" ]; then
echo -e "license header checking failed:\n${licRes}"
exit 255
fi
echo "Success"