Compare commits

..

3 Commits

Author SHA1 Message Date
5ab94d6e50 scripts: build static releases and create an ACI
* use SHA1 instead of MD5
2016-04-23 00:53:20 +02:00
2ea9379fa4 travis: don't go get vet 2016-04-22 20:13:37 +02:00
cf43d2f78f scripts: add "release with rkt"
This script uses rkt and a fedora image to build release tarballs.
2016-04-22 19:36:38 +02:00
56 changed files with 352 additions and 1705 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.
We welcome improvements to documentation as well as to code.
For more information on the policy for accepting contributions, see [POLICY](POLICY.md)
# Certificate of Origin
@ -16,9 +16,9 @@ contribution. See the [DCO](DCO) file for details.
# Email and Chat
The project uses the the cni-dev email list and IRC chat:
The project uses the the cni-dev email list and #appc on Freenode for chat:
- Email: [cni-dev](https://groups.google.com/forum/#!forum/cni-dev)
- IRC: #[containernetworking](irc://irc.freenode.org:6667/#containernetworking) channel on freenode.org
- IRC: #[appc](irc://irc.freenode.org:6667/#appc) IRC channel on freenode.org
Please avoid emailing maintainers found in the MAINTAINERS file directly. They
are very busy and read the mailing lists.
@ -27,18 +27,17 @@ 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 pull requests!
- Play with the project, submit bugs, submit patches!
## Contribution workflow
## Contribution Flow
This is a rough outline of how to prepare a contribution:
This is a rough outline of what a contributor's workflow looks like:
- Create a topic branch from where you want to base your work (usually branched from master).
- Create a topic branch from where you want to base your work (usually master).
- Make commits of logical units.
- Make sure your commit messages are in the proper format (see below).
- Push your changes to a topic branch in your fork of the repository.
- If you changed code, make sure the tests pass, and add any new tests as appropriate.
- Make sure any new code files have a license header.
- Make sure the tests pass, and add any new tests as appropriate.
- Submit a pull request to the original repository.
Thanks for your contributions!

View File

@ -17,9 +17,8 @@ If the bridge is missing, the plugin will create one on first use and, if gatewa
"name": "mynet",
"type": "bridge",
"bridge": "mynet0",
"isDefaultGateway": true,
"isGateway": true,
"ipMasq": true,
"hairpinMode": true,
"ipam": {
"type": "host-local",
"subnet": "10.10.0.0/16"
@ -33,8 +32,6 @@ 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/containernetworking/cni/blob/master/Documentation/macvlan.md).
This can be especially useful with plugin types such as [macvlan](https://github.com/appc/cni/blob/master/Documentation/macvlan.md).
Because a DHCP lease must be periodically renewed for the duration of container lifetime, a separate daemon is required to be running.
The same plugin binary can also be run in the daemon mode.

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/containernetworking/cni/blob/master/SPEC.md#parameters) are supported:
The following [CNI_ARGS](https://github.com/appc/cni/blob/master/SPEC.md#parameters) are supported:
* `ip`: request a specific IP address from the subnet. If it's not available, the plugin will exit with an error

2
Godeps/Godeps.json generated
View File

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

View File

@ -1,5 +1,3 @@
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,25 +1,22 @@
[![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)
[![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)
# CNI - the Container Network Interface
## What is CNI?
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.
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.
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.
The specification itself is contained in [SPEC.md](SPEC.md).
## Why develop CNI?
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.
Application containers on Linux are a rapidly evolving area, and within this space networking is a particularly unsolved problem, as it is highly environment-specific.
We believe that every container runtime will seek to solve the same problem of making the network layer pluggable.
To avoid duplication, we think it is prudent to define a common interface between the network plugins and container execution: hence we put forward this specification, along with libraries for Go and a set of plugins.
To avoid duplication, we think it is prudent to define a common interface between the network plugins and container execution.
Hence we are proposing this specification, along with an initial set of plugins that can be used by different container runtime systems.
## Who is using CNI?
@ -29,29 +26,25 @@ To avoid duplication, we think it is prudent to define a common interface betwee
- [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/containernetworking/cni/issues), and code and documentation improvements.
We welcome contributions, including [bug reports](https://github.com/appc/cni/issues), and code and documentation improvements.
If you intend to contribute to code or documentation, please read [CONTRIBUTING.md](CONTRIBUTING.md). Also see the [contact section](#contact) in this README.
## How do I use CNI?
### Requirements
CNI requires Go 1.5+ to build.
Go 1.5 users will need to set GO15VENDOREXPERIMENT=1 to get vendored
dependencies. This flag is set by default in 1.6.
### Included Plugins
This repository includes a number of common plugins in the `plugins/` directory.
Please see the [Documentation/](Documentation/) directory for documentation about particular plugins.
### Running the plugins
The scripts/ directory contains two scripts, `priv-net-run.sh` and `docker-run.sh`, that can be used to exercise the plugins.
**note - priv-net-run.sh depends on `jq`**
@ -146,18 +139,8 @@ 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:
For any questions about CNI, please reach out on the mailing list or IRC:
- Email: [cni-dev](https://groups.google.com/forum/#!forum/cni-dev)
- IRC: #[containernetworking](irc://irc.freenode.org:6667/#containernetworking) channel on freenode.org
- IRC: #[appc](irc://irc.freenode.org:6667/#appc) IRC channel on freenode.org

9
build
View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -e
ORG_PATH="github.com/containernetworking"
ORG_PATH="github.com/appc"
REPO_PATH="${ORG_PATH}/cni"
if [ ! -h gopath/src/${REPO_PATH} ]; then
@ -14,17 +14,18 @@ export GOBIN=${PWD}/bin
export GOPATH=${PWD}/gopath
echo "Building API"
go build "$@" ${REPO_PATH}/libcni
go build ${REPO_PATH}/libcni
echo "Building reference CLI"
go install "$@" ${REPO_PATH}/cnitool
go install ${REPO_PATH}/cnitool
echo "Building plugins"
PLUGINS="plugins/meta/* plugins/main/* plugins/ipam/*"
for d in $PLUGINS; do
if [ -d $d ]; then
plugin=$(basename $d)
echo " " $plugin
go install "$@" ${REPO_PATH}/$d
go install ${REPO_PATH}/$d
fi
done

32
build-static Executable file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,7 +20,7 @@ import (
"net"
"os"
"github.com/containernetworking/cni/pkg/ns"
"github.com/appc/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 ns.NetNS) (hostVeth, contVeth netlink.Link, err error) {
func SetupVeth(contVethName string, mtu int, hostNS *os.File) (hostVeth, contVeth netlink.Link, err error) {
var hostVethName string
hostVethName, contVeth, err = makeVeth(contVethName, mtu)
if err != nil {
@ -104,10 +104,10 @@ func SetupVeth(contVethName string, mtu int, hostNS ns.NetNS) (hostVeth, contVet
return
}
err = hostNS.Do(func(_ ns.NetNS) error {
err = ns.WithNetNS(hostNS, false, func(_ *os.File) error {
hostVeth, err := netlink.LinkByName(hostVethName)
if err != nil {
return fmt.Errorf("failed to lookup %q in %q: %v", hostVethName, hostNS.Path(), err)
return fmt.Errorf("failed to lookup %q in %q: %v", hostVethName, hostNS.Name(), err)
}
if err = netlink.LinkSetUp(hostVeth); err != nil {

View File

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

View File

@ -1,31 +0,0 @@
### Namespaces, Threads, and Go
On Linux each OS thread can have a different network namespace. Go's thread scheduling model switches goroutines between OS threads based on OS thread load and whether the goroutine would block other goroutines. This can result in a goroutine switching network namespaces without notice and lead to errors in your code.
### Namespace Switching
Switching namespaces with the `ns.Set()` method is not recommended without additional strategies to prevent unexpected namespace changes when your goroutines switch OS threads.
Go provides the `runtime.LockOSThread()` function to ensure a specific goroutine executes on its current OS thread and prevents any other goroutine from running in that thread until the locked one exits. Careful usage of `LockOSThread()` and goroutines can provide good control over which network namespace a given goroutine executes in.
For example, you cannot rely on the `ns.Set()` namespace being the current namespace after the `Set()` call unless you do two things. First, the goroutine calling `Set()` must have previously called `LockOSThread()`. Second, you must ensure `runtime.UnlockOSThread()` is not called somewhere in-between. You also cannot rely on the initial network namespace remaining the current network namespace if any other code in your program switches namespaces, unless you have already called `LockOSThread()` in that goroutine. Note that `LockOSThread()` prevents the Go scheduler from optimally scheduling goroutines for best performance, so `LockOSThread()` should only be used in small, isolated goroutines that release the lock quickly.
### Do() The Recommended Thing
The `ns.Do()` method provides 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,293 +15,79 @@
package ns
import (
"crypto/rand"
"fmt"
"os"
"path"
"runtime"
"strings"
"sync"
"syscall"
"golang.org/x/sys/unix"
)
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
var setNsMap = map[string]uintptr{
"386": 346,
"amd64": 308,
"arm": 374,
}
type netNS struct {
file *os.File
mounted bool
closed bool
}
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())
}
// 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
)
func IsNS(nspath string) (isNS bool, msg string, err error) {
stat := syscall.Statfs_t{}
if err = syscall.Statfs(nspath, &stat); err != nil {
err = fmt.Errorf("failed to Statfs %s: %v", nspath, err)
return
// 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)
}
switch stat.Type {
case PROCFS_MAGIC:
// Kernel < 3.19
validPathContent := "ns/"
validName := strings.Contains(nspath, validPathContent)
if !validName {
msg = fmt.Sprintf("path doesn't contain %q", validPathContent)
return
}
isNS = true
case NSFS_MAGIC:
// Kernel >= 3.19
isNS = true
default:
msg = fmt.Sprintf("unknown FS magic: %x", stat.Type)
}
return
}
// Returns an object representing the namespace referred to by @path
func GetNS(nspath string) (NetNS, error) {
isNS, msg, err := IsNS(nspath)
if err != nil {
return nil, err
}
if !isNS {
return nil, fmt.Errorf("no network namespace detected on %s: %s", nspath, msg)
trap, ok := setNsMap[runtime.GOARCH]
if !ok {
return fmt.Errorf("unsupported arch: %s", runtime.GOARCH)
}
fd, err := os.Open(nspath)
if err != nil {
return nil, fmt.Errorf("Failed to open %v: %v", nspath, 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 {
_, _, err := syscall.RawSyscall(trap, f.Fd(), flags, 0)
if err != 0 {
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.
func WithNetNSPath(nspath string, toRun func(NetNS) error) error {
ns, err := GetNS(nspath)
// Changing namespaces must be done on a goroutine that has been
// locked to an OS thread. If lockThread arg is true, this function
// locks the goroutine prior to change namespace and unlocks before
// returning
func WithNetNSPath(nspath string, lockThread bool, f func(*os.File) error) error {
ns, err := os.Open(nspath)
if err != nil {
return err
return fmt.Errorf("Failed to open %v: %v", nspath, err)
}
defer ns.Close()
return ns.Do(toRun)
return WithNetNS(ns, lockThread, f)
}
// WithNetNS executes the passed closure under the given network
// namespace, restoring the original namespace afterwards.
// Changing namespaces must be done on a goroutine that has been
// locked to an OS thread. If lockThread arg is true, this function
// locks the goroutine prior to change namespace and unlocks before
// returning. If the closure returns an error, WithNetNS attempts to
// restore the original namespace before returning.
func WithNetNS(ns *os.File, lockThread bool, f func(*os.File) error) error {
if lockThread {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
}
// save a handle to current (host) network namespace
thisNS, err := os.Open("/proc/self/ns/net")
if err != nil {
return fmt.Errorf("Failed to open /proc/self/ns/net: %v", err)
}
defer thisNS.Close()
if err = SetNS(ns, syscall.CLONE_NEWNET); err != nil {
return fmt.Errorf("Error switching to ns %v: %v", ns.Name(), err)
}
defer SetNS(thisNS, syscall.CLONE_NEWNET) // switch back
if err = f(thisNS); err != nil {
return err
}
return nil
}

View File

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

View File

@ -1,140 +1,135 @@
// 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"
"io/ioutil"
"math/rand"
"os"
"os/exec"
"path/filepath"
"github.com/containernetworking/cni/pkg/ns"
"golang.org/x/sys/unix"
"github.com/appc/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 getInodeFd(int(file.Fd()))
return getInodeF(file)
}
func getInodeFd(fd int) (uint64, error) {
func getInodeF(file *os.File) (uint64, error) {
stat := &unix.Stat_t{}
err := unix.Fstat(fd, stat)
err := unix.Fstat(int(file.Fd()), stat)
return stat.Ino, err
}
const CurrentNetNS = "/proc/self/ns/net"
var _ = Describe("Linux namespace operations", func() {
Describe("WithNetNS", func() {
var (
originalNetNS ns.NetNS
targetNetNS ns.NetNS
originalNetNS *os.File
targetNetNSName string
targetNetNSPath string
targetNetNS *os.File
)
BeforeEach(func() {
var err error
originalNetNS, err = ns.NewNS()
originalNetNS, err = os.Open(CurrentNetNS)
Expect(err).NotTo(HaveOccurred())
targetNetNS, err = ns.NewNS()
targetNetNSName = fmt.Sprintf("test-netns-%d", rand.Int())
err = exec.Command("ip", "netns", "add", targetNetNSName).Run()
Expect(err).NotTo(HaveOccurred())
targetNetNSPath = filepath.Join("/var/run/netns/", targetNetNSName)
targetNetNS, err = os.Open(targetNetNSPath)
Expect(err).NotTo(HaveOccurred())
})
AfterEach(func() {
Expect(targetNetNS.Close()).To(Succeed())
err := exec.Command("ip", "netns", "del", targetNetNSName).Run()
Expect(err).NotTo(HaveOccurred())
Expect(originalNetNS.Close()).To(Succeed())
})
It("executes the callback within the target network namespace", func() {
expectedInode, err := getInodeNS(targetNetNS)
expectedInode, err := getInode(targetNetNSPath)
Expect(err).NotTo(HaveOccurred())
err = targetNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
actualInode, err := getInodeCurNetNS()
Expect(err).NotTo(HaveOccurred())
Expect(actualInode).To(Equal(expectedInode))
var actualInode uint64
var innerErr error
err = ns.WithNetNS(targetNetNS, false, func(*os.File) error {
actualInode, innerErr = getInode(CurrentNetNS)
return nil
})
Expect(err).NotTo(HaveOccurred())
Expect(innerErr).NotTo(HaveOccurred())
Expect(actualInode).To(Equal(expectedInode))
})
It("provides the original namespace as the argument to the callback", func() {
// Ensure we start in originalNetNS
err := originalNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
hostNSInode, err := getInode(CurrentNetNS)
Expect(err).NotTo(HaveOccurred())
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
})
var inputNSInode uint64
var innerErr error
err = ns.WithNetNS(targetNetNS, false, func(inputNS *os.File) error {
inputNSInode, err = getInodeF(inputNS)
return nil
})
Expect(err).NotTo(HaveOccurred())
Expect(innerErr).NotTo(HaveOccurred())
Expect(inputNSInode).To(Equal(hostNSInode))
})
It("restores the calling thread to the original network namespace", func() {
preTestInode, err := getInode(CurrentNetNS)
Expect(err).NotTo(HaveOccurred())
err = ns.WithNetNS(targetNetNS, false, func(*os.File) error {
return nil
})
Expect(err).NotTo(HaveOccurred())
postTestInode, err := getInode(CurrentNetNS)
Expect(err).NotTo(HaveOccurred())
Expect(postTestInode).To(Equal(preTestInode))
})
Context("when the callback returns an error", func() {
It("restores the calling thread to the original namespace before returning", func() {
err := originalNetNS.Do(func(ns.NetNS) error {
defer GinkgoRecover()
preTestInode, err := getInodeCurNetNS()
Expect(err).NotTo(HaveOccurred())
_ = targetNetNS.Do(func(ns.NetNS) error {
return errors.New("potato")
})
postTestInode, err := getInodeCurNetNS()
Expect(err).NotTo(HaveOccurred())
Expect(postTestInode).To(Equal(preTestInode))
return nil
})
preTestInode, err := getInode(CurrentNetNS)
Expect(err).NotTo(HaveOccurred())
_ = ns.WithNetNS(targetNetNS, false, func(*os.File) error {
return errors.New("potato")
})
postTestInode, err := getInode(CurrentNetNS)
Expect(err).NotTo(HaveOccurred())
Expect(postTestInode).To(Equal(preTestInode))
})
It("returns the error from the callback", func() {
err := targetNetNS.Do(func(ns.NetNS) error {
err := ns.WithNetNS(targetNetNS, false, func(*os.File) error {
return errors.New("potato")
})
Expect(err).To(MatchError("potato"))
@ -143,104 +138,16 @@ var _ = Describe("Linux namespace operations", func() {
Describe("validating inode mapping to namespaces", func() {
It("checks that different namespaces have different inodes", func() {
origNSInode, err := getInodeNS(originalNetNS)
hostNSInode, err := getInode(CurrentNetNS)
Expect(err).NotTo(HaveOccurred())
testNsInode, err := getInodeNS(targetNetNS)
testNsInode, err := getInode(targetNetNSPath)
Expect(err).NotTo(HaveOccurred())
Expect(hostNSInode).NotTo(Equal(0))
Expect(testNsInode).NotTo(Equal(0))
Expect(testNsInode).NotTo(Equal(origNSInode))
Expect(testNsInode).NotTo(Equal(hostNSInode))
})
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())
errString := fmt.Sprintf("%v", err)
Expect(errString).To(HavePrefix("no network namespace detected on %s", nspath))
})
})
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("IsNS", func() {
It("should detect a namespace", func() {
createdNetNS, err := ns.NewNS()
isNSFS, msg, err := ns.IsNS(createdNetNS.Path())
Expect(err).NotTo(HaveOccurred())
Expect(msg).To(Equal(""))
Expect(isNSFS).To(Equal(true))
})
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)
isNSFS, _, err := ns.IsNS(nspath)
Expect(err).NotTo(HaveOccurred())
Expect(isNSFS).To(Equal(false))
})
})
})
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/containernetworking/cni/pkg/types"
"github.com/appc/cni/pkg/types"
)
// CmdArgs captures all the arguments passed in to the plugin

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -19,15 +19,16 @@ import (
"errors"
"fmt"
"net"
"os"
"runtime"
"syscall"
"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/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/vishvananda/netlink"
)
@ -35,12 +36,10 @@ const defaultBrName = "cni0"
type NetConf struct {
types.NetConf
BrName string `json:"bridge"`
IsGW bool `json:"isGateway"`
IsDefaultGW bool `json:"isDefaultGateway"`
IPMasq bool `json:"ipMasq"`
MTU int `json:"mtu"`
HairpinMode bool `json:"hairpinMode"`
BrName string `json:"bridge"`
IsGW bool `json:"isGateway"`
IPMasq bool `json:"ipMasq"`
MTU int `json:"mtu"`
}
func init() {
@ -102,11 +101,6 @@ 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,
},
}
@ -129,10 +123,10 @@ func ensureBridge(brName string, mtu int) (*netlink.Bridge, error) {
return br, nil
}
func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairpinMode bool) error {
func setupVeth(netns string, br *netlink.Bridge, ifName string, mtu int) error {
var hostVethName string
err := netns.Do(func(hostNS ns.NetNS) error {
err := ns.WithNetNSPath(netns, false, func(hostNS *os.File) 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 {
@ -157,11 +151,6 @@ func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairp
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
}
@ -186,22 +175,12 @@ func cmdAdd(args *skel.CmdArgs) error {
return err
}
if n.IsDefaultGW {
n.IsGW = true
}
br, err := setupBridge(n)
if err != nil {
return err
}
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 {
if err = setupVeth(args.Netns, br, args.IfName, n.MTU); err != nil {
return err
}
@ -211,7 +190,6 @@ 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")
}
@ -220,35 +198,10 @@ func cmdAdd(args *skel.CmdArgs) error {
result.IP4.Gateway = calcGatewayIP(&result.IP4.IP)
}
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
}
err = ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error {
return ipam.ConfigureIface(args.IfName, result)
}); err != nil {
})
if err != nil {
return err
}
@ -285,29 +238,14 @@ func cmdDel(args *skel.CmdArgs) error {
return err
}
if err := ipam.ExecDel(n.IPAM.Type, args.StdinData); err != nil {
return err
}
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
})
err = ipam.ExecDel(n.IPAM.Type, args.StdinData)
if err != nil {
return err
}
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
return ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error {
return ip.DelLinkByName(args.IfName)
})
}
func main() {

View File

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

View File

@ -1,239 +0,0 @@
// Copyright 2015 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package 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,13 +18,14 @@ import (
"encoding/json"
"errors"
"fmt"
"os"
"runtime"
"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/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/vishvananda/netlink"
)
@ -64,7 +65,7 @@ func modeFromString(s string) (netlink.IPVlanMode, error) {
}
}
func createIpvlan(conf *NetConf, ifName string, netns ns.NetNS) error {
func createIpvlan(conf *NetConf, ifName string, netns *os.File) error {
mode, err := modeFromString(conf.Mode)
if err != nil {
return err
@ -96,7 +97,7 @@ func createIpvlan(conf *NetConf, ifName string, netns ns.NetNS) error {
return fmt.Errorf("failed to create ipvlan: %v", err)
}
return netns.Do(func(_ ns.NetNS) error {
return ns.WithNetNS(netns, false, func(_ *os.File) error {
err := renameLink(tmpName, ifName)
if err != nil {
return fmt.Errorf("failed to rename ipvlan to %q: %v", ifName, err)
@ -111,9 +112,9 @@ func cmdAdd(args *skel.CmdArgs) error {
return err
}
netns, err := ns.GetNS(args.Netns)
netns, err := os.Open(args.Netns)
if err != nil {
return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
return fmt.Errorf("failed to open netns %q: %v", netns, err)
}
defer netns.Close()
@ -130,7 +131,7 @@ func cmdAdd(args *skel.CmdArgs) error {
return errors.New("IPAM plugin returned missing IPv4 config")
}
err = netns.Do(func(_ ns.NetNS) error {
err = ns.WithNetNS(netns, false, func(_ *os.File) error {
return ipam.ConfigureIface(args.IfName, result)
})
if err != nil {
@ -152,7 +153,7 @@ func cmdDel(args *skel.CmdArgs) error {
return err
}
return ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
return ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error {
return ip.DelLinkByName(args.IfName)
})
}

View File

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

View File

@ -1,168 +0,0 @@
// Copyright 2015 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package 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,29 +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 main
import (
"github.com/containernetworking/cni/pkg/ns"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"os"
"github.com/appc/cni/pkg/ns"
"github.com/appc/cni/pkg/skel"
"github.com/appc/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, func(_ ns.NetNS) error {
err := ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error {
link, err := netlink.LinkByName(args.IfName)
if err != nil {
return err // not tested
@ -46,7 +34,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, func(ns.NetNS) error {
err := ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error {
link, err := netlink.LinkByName(args.IfName)
if err != nil {
return err // not tested

View File

@ -1,26 +1,18 @@
// 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
@ -32,10 +24,54 @@ func TestLoopback(t *testing.T) {
var _ = BeforeSuite(func() {
var err error
pathToLoPlugin, err = gexec.Build("github.com/containernetworking/cni/plugins/main/loopback")
pathToLoPlugin, err = gexec.Build("github.com/appc/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,26 +1,13 @@
// 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/containernetworking/cni/pkg/ns"
"github.com/appc/cni/pkg/ns"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gbytes"
@ -29,7 +16,7 @@ import (
var _ = Describe("Loopback", func() {
var (
networkNS ns.NetNS
networkNS string
containerID string
command *exec.Cmd
environ []string
@ -37,14 +24,12 @@ var _ = Describe("Loopback", func() {
BeforeEach(func() {
command = exec.Command(pathToLoPlugin)
var err error
networkNS, err = ns.NewNS()
Expect(err).NotTo(HaveOccurred())
containerID = "some-container-id"
networkNS = makeNetworkNS(containerID)
environ = []string{
fmt.Sprintf("CNI_CONTAINERID=%s", containerID),
fmt.Sprintf("CNI_NETNS=%s", networkNS.Path()),
fmt.Sprintf("CNI_NETNS=%s", networkNS),
fmt.Sprintf("CNI_IFNAME=%s", "this is ignored"),
fmt.Sprintf("CNI_ARGS=%s", "none"),
fmt.Sprintf("CNI_PATH=%s", "/some/test/path"),
@ -53,7 +38,7 @@ var _ = Describe("Loopback", func() {
})
AfterEach(func() {
Expect(networkNS.Close()).To(Succeed())
Expect(removeNetworkNS(networkNS)).To(Succeed())
})
Context("when given a network namespace", func() {
@ -67,7 +52,7 @@ var _ = Describe("Loopback", func() {
Eventually(session).Should(gexec.Exit(0))
var lo *net.Interface
err = networkNS.Do(func(ns.NetNS) error {
err = ns.WithNetNSPath(networkNS, true, func(hostNS *os.File) error {
var err error
lo, err = net.InterfaceByName("lo")
return err
@ -87,7 +72,7 @@ var _ = Describe("Loopback", func() {
Eventually(session).Should(gexec.Exit(0))
var lo *net.Interface
err = networkNS.Do(func(ns.NetNS) error {
err = ns.WithNetNSPath(networkNS, true, func(hostNS *os.File) error {
var err error
lo, err = net.InterfaceByName("lo")
return err

View File

@ -18,21 +18,17 @@ import (
"encoding/json"
"errors"
"fmt"
"os"
"runtime"
"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/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/vishvananda/netlink"
)
const (
IPv4InterfaceArpProxySysctlTemplate = "net.ipv4.conf.%s.proxy_arp"
)
type NetConf struct {
types.NetConf
Master string `json:"master"`
@ -73,7 +69,7 @@ func modeFromString(s string) (netlink.MacvlanMode, error) {
}
}
func createMacvlan(conf *NetConf, ifName string, netns ns.NetNS) error {
func createMacvlan(conf *NetConf, ifName string, netns *os.File) error {
mode, err := modeFromString(conf.Mode)
if err != nil {
return err
@ -84,7 +80,7 @@ func createMacvlan(conf *NetConf, ifName string, netns ns.NetNS) 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 {
@ -105,18 +101,9 @@ func createMacvlan(conf *NetConf, ifName string, netns ns.NetNS) error {
return fmt.Errorf("failed to create macvlan: %v", err)
}
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)
}
return ns.WithNetNS(netns, false, func(_ *os.File) error {
err := renameLink(tmpName, ifName)
if err != nil {
_ = netlink.LinkDel(mv)
return fmt.Errorf("failed to rename macvlan to %q: %v", ifName, err)
}
return nil
@ -129,7 +116,7 @@ func cmdAdd(args *skel.CmdArgs) error {
return err
}
netns, err := ns.GetNS(args.Netns)
netns, err := os.Open(args.Netns)
if err != nil {
return fmt.Errorf("failed to open netns %q: %v", netns, err)
}
@ -148,7 +135,7 @@ func cmdAdd(args *skel.CmdArgs) error {
return errors.New("IPAM plugin returned missing IPv4 config")
}
err = netns.Do(func(_ ns.NetNS) error {
err = ns.WithNetNS(netns, false, func(_ *os.File) error {
return ipam.ConfigureIface(args.IfName, result)
})
if err != nil {
@ -170,7 +157,7 @@ func cmdDel(args *skel.CmdArgs) error {
return err
}
return ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
return ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error {
return ip.DelLinkByName(args.IfName)
})
}

View File

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

View File

@ -1,168 +0,0 @@
// Copyright 2015 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package 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/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/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"
)
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, func(hostNS ns.NetNS) error {
err := ns.WithNetNSPath(netns, false, func(hostNS *os.File) error {
hostVeth, _, err := ip.SetupVeth(ifName, mtu, hostNS)
if err != nil {
return err
@ -195,12 +195,8 @@ 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
}
var ipn *net.IPNet
err := ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
err := ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error {
var err error
ipn, err = ip.DelLinkByNameAddr(args.IfName, netlink.FAMILY_V4)
return err
@ -217,7 +213,7 @@ func cmdDel(args *skel.CmdArgs) error {
}
}
return nil
return ipam.ExecDel(conf.IPAM.Type, args.StdinData)
}
func main() {

View File

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

View File

@ -21,12 +21,13 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/containernetworking/cni/pkg/ns"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/appc/cni/pkg/ns"
"github.com/appc/cni/pkg/skel"
"github.com/appc/cni/pkg/types"
)
// TuningConf represents the network tuning configuration.
@ -44,7 +45,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, func(_ ns.NetNS) error {
err := ns.WithNetNSPath(args.Netns, false, func(hostNS *os.File) error {
for key, value := range tuningConf.SysCtl {
fileName := filepath.Join("/proc/sys", strings.Replace(key, ".", "/", -1))
fileName = filepath.Clean(fileName)

View File

@ -7,7 +7,6 @@ FEDORA_INSTALL="dnf install -y golang tar xz bzip2 gzip sudo iproute wget"
FEDORA_IMAGE="docker://fedora:23"
ACBUILD_URL="https://github.com/appc/acbuild/releases/download/v0.2.2/acbuild.tar.gz"
ACBUILD="acbuild --debug"
BUILDFLAGS="-a --ldflags '-extldflags \"-static\"'"
TAG=$(git describe --exact-match --abbrev=0) || TAG=$(git describe)
RELEASE_DIR=release-${TAG}
@ -26,7 +25,7 @@ sudo -E rkt run \
${FEDORA_IMAGE} \
--exec /bin/bash \
-- -xe -c "\
${FEDORA_INSTALL}; cd /opt/src; umask 0022; CGO_ENABLED=0 ./build ${BUILDFLAGS}; \
${FEDORA_INSTALL}; cd /opt/src; umask 0022; ./build-static; ./test || true; \
for format in txz tbz2 tgz; do \
FILENAME=cni-${TAG}.\$format; \
FILEPATH=${RELEASE_DIR}/\$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 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"
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"
# 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:$GOBIN:$PATH go test -covermode set $@"
sudo -E bash -c "umask 0; PATH=\$GOROOT/bin:\$PATH go test -covermode set $@"
}
if [ ! -z "${COVERALLS}" ]; then
echo "with coverage profile generation..."
@ -65,16 +65,4 @@ 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"