summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xAPI.md6
-rw-r--r--Makefile4
-rw-r--r--cmd/podman/README.md4
-rw-r--r--cmd/podman/cliconfig/config.go13
-rw-r--r--cmd/podman/network.go2
-rw-r--r--cmd/podman/network_create.go70
-rw-r--r--cmd/podman/shared/volumes_shared.go47
-rw-r--r--cmd/podman/varlink/io.podman.varlink4
-rw-r--r--cmd/podman/volume_rm.go18
-rw-r--r--commands.md1
-rw-r--r--completions/bash/podman24
-rw-r--r--contrib/cirrus/README.md4
-rw-r--r--contrib/podmanimage/README.md2
-rw-r--r--contrib/podmanimage/stable/Dockerfile2
-rw-r--r--contrib/podmanimage/testing/Dockerfile2
-rw-r--r--contrib/podmanimage/upstream/Dockerfile2
-rw-r--r--dependencies/analyses/README.md2
-rw-r--r--docs/podman-build.1.md14
-rw-r--r--docs/podman-commit.1.md2
-rw-r--r--docs/podman-create.1.md48
-rw-r--r--docs/podman-events.1.md10
-rw-r--r--docs/podman-exec.1.md2
-rw-r--r--docs/podman-generate-kube.1.md2
-rw-r--r--docs/podman-image-sign.1.md2
-rw-r--r--docs/podman-image-tree.1.md2
-rw-r--r--docs/podman-network-create.1.md70
-rw-r--r--docs/podman-network-ls.1.md2
-rw-r--r--docs/podman-network.1.md1
-rw-r--r--docs/podman-run.1.md94
-rw-r--r--docs/podman-volume-inspect.1.md1
-rw-r--r--docs/podman-volume-rm.1.md8
-rw-r--r--docs/tutorials/rootless_tutorial.md2
-rw-r--r--go.mod2
-rw-r--r--go.sum4
-rwxr-xr-xhack/man-page-checker23
-rw-r--r--libpod/boltdb_state.go68
-rw-r--r--libpod/container_internal.go100
-rw-r--r--libpod/container_internal_linux.go4
-rw-r--r--libpod/in_memory_state.go35
-rw-r--r--libpod/runtime_ctr.go4
-rw-r--r--libpod/runtime_volume.go46
-rw-r--r--libpod/state.go3
-rw-r--r--libpod/volume.go7
-rw-r--r--libpod/volume_internal.go2
-rw-r--r--pkg/adapter/network.go135
-rw-r--r--pkg/adapter/runtime.go6
-rw-r--r--pkg/adapter/runtime_remote.go9
-rw-r--r--pkg/network/config.go99
-rw-r--r--pkg/network/devices.go41
-rw-r--r--pkg/network/files.go107
-rw-r--r--pkg/network/ip.go14
-rw-r--r--pkg/network/netconflist.go113
-rw-r--r--pkg/network/network.go146
-rw-r--r--pkg/network/network_test.go34
-rw-r--r--pkg/network/subnet.go78
-rw-r--r--pkg/network/subnet_test.go34
-rw-r--r--pkg/varlinkapi/volumes.go10
-rw-r--r--test/e2e/network_create_test.go211
-rw-r--r--test/e2e/run_volume_test.go21
-rw-r--r--test/e2e/volume_rm_test.go34
-rw-r--r--troubleshooting.md14
-rw-r--r--vendor/github.com/containernetworking/plugins/pkg/ip/addr_linux.go68
-rw-r--r--vendor/github.com/containernetworking/plugins/pkg/ip/cidr.go61
-rw-r--r--vendor/github.com/containernetworking/plugins/pkg/ip/ipforward_linux.go61
-rw-r--r--vendor/github.com/containernetworking/plugins/pkg/ip/ipmasq_linux.go126
-rw-r--r--vendor/github.com/containernetworking/plugins/pkg/ip/link_linux.go275
-rw-r--r--vendor/github.com/containernetworking/plugins/pkg/ip/route_linux.go47
-rw-r--r--vendor/github.com/containernetworking/plugins/pkg/ip/utils_linux.go120
-rw-r--r--vendor/github.com/containernetworking/plugins/pkg/utils/hwaddr/hwaddr.go63
-rw-r--r--vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/allocator.go217
-rw-r--r--vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/config.go160
-rw-r--r--vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/range.go166
-rw-r--r--vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/range_set.go97
-rw-r--r--vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/store.go27
-rw-r--r--vendor/github.com/coreos/go-iptables/LICENSE191
-rw-r--r--vendor/github.com/coreos/go-iptables/NOTICE5
-rw-r--r--vendor/github.com/coreos/go-iptables/iptables/iptables.go598
-rw-r--r--vendor/github.com/coreos/go-iptables/iptables/lock.go84
-rw-r--r--vendor/github.com/safchain/ethtool/.gitignore27
-rw-r--r--vendor/github.com/safchain/ethtool/.travis.yml1
-rw-r--r--vendor/github.com/safchain/ethtool/LICENSE202
-rw-r--r--vendor/github.com/safchain/ethtool/Makefile4
-rw-r--r--vendor/github.com/safchain/ethtool/README.md60
-rw-r--r--vendor/github.com/safchain/ethtool/ethtool.go541
-rw-r--r--vendor/github.com/safchain/ethtool/ethtool_cmd.go207
-rw-r--r--vendor/github.com/safchain/ethtool/ethtool_msglvl.go113
-rw-r--r--vendor/modules.txt10
87 files changed, 5143 insertions, 259 deletions
diff --git a/API.md b/API.md
index 4e61b7c9f..f0e5cf18f 100755
--- a/API.md
+++ b/API.md
@@ -177,7 +177,7 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in
[func VolumeCreate(options: VolumeCreateOpts) string](#VolumeCreate)
-[func VolumeRemove(options: VolumeRemoveOpts) []string](#VolumeRemove)
+[func VolumeRemove(options: VolumeRemoveOpts) []string, map[string]](#VolumeRemove)
[func VolumesPrune() []string, []string](#VolumesPrune)
@@ -1151,7 +1151,7 @@ VolumeCreate creates a volume on a remote host
### <a name="VolumeRemove"></a>func VolumeRemove
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">
-method VolumeRemove(options: [VolumeRemoveOpts](#VolumeRemoveOpts)) [[]string](#[]string)</div>
+method VolumeRemove(options: [VolumeRemoveOpts](#VolumeRemoveOpts)) [[]string](#[]string), [map[string]](#map[string])</div>
VolumeRemove removes a volume on a remote host
### <a name="VolumesPrune"></a>func VolumesPrune
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">
@@ -1732,7 +1732,7 @@ uptime [string](https://godoc.org/builtin#string)
eventlogger [string](https://godoc.org/builtin#string)
### <a name="InfoPodmanBinary"></a>type InfoPodmanBinary
-InfoPodman provides details on the podman binary
+InfoPodman provides details on the Podman binary
compiler [string](https://godoc.org/builtin#string)
diff --git a/Makefile b/Makefile
index 4f51adb67..e32f42631 100644
--- a/Makefile
+++ b/Makefile
@@ -136,8 +136,8 @@ help:
.gopathok:
ifeq ("$(wildcard $(GOPKGDIR))","")
mkdir -p "$(GOPKGBASEDIR)"
- ln -sfnT "$(CURDIR)" "$(GOPKGDIR)"
- ln -sfnT "$(CURDIR)/vendor/github.com/varlink" "$(FIRST_GOPATH)/src/github.com/varlink"
+ ln -sfn "$(CURDIR)" "$(GOPKGDIR)"
+ ln -sfn "$(CURDIR)/vendor/github.com/varlink" "$(FIRST_GOPATH)/src/github.com/varlink"
endif
touch $@
diff --git a/cmd/podman/README.md b/cmd/podman/README.md
index 0fee7eafa..937eef510 100644
--- a/cmd/podman/README.md
+++ b/cmd/podman/README.md
@@ -1,5 +1,5 @@
-# podman - Simple debugging tool for pods and images
-podman is a daemonless container runtime for managing containers, pods, and container images.
+# Podman - Simple debugging tool for pods and images
+Podman is a daemonless container runtime for managing containers, pods, and container images.
It is intended as a counterpart to CRI-O, to provide low-level debugging not available through the CRI interface used by Kubernetes.
It can also act as a container runtime independent of CRI-O, creating and managing its own set of containers.
diff --git a/cmd/podman/cliconfig/config.go b/cmd/podman/cliconfig/config.go
index 98e7aed4b..812cc1f51 100644
--- a/cmd/podman/cliconfig/config.go
+++ b/cmd/podman/cliconfig/config.go
@@ -1,6 +1,8 @@
package cliconfig
import (
+ "net"
+
"github.com/spf13/cobra"
)
@@ -259,6 +261,17 @@ type MountValues struct {
Latest bool
}
+type NetworkCreateValues struct {
+ PodmanCommand
+ Driver string
+ Gateway net.IP
+ Internal bool
+ IPamDriver string
+ IPRange net.IPNet
+ IPV6 bool
+ Network net.IPNet
+}
+
type NetworkListValues struct {
PodmanCommand
Filter []string
diff --git a/cmd/podman/network.go b/cmd/podman/network.go
index 83a5e71ab..702593e5c 100644
--- a/cmd/podman/network.go
+++ b/cmd/podman/network.go
@@ -17,8 +17,8 @@ var networkcheckCommand = cliconfig.PodmanCommand{
},
}
-// Commands that are universally implemented
var networkcheckCommands = []*cobra.Command{
+ _networkCreateCommand,
_networkinspectCommand,
_networklistCommand,
_networkrmCommand,
diff --git a/cmd/podman/network_create.go b/cmd/podman/network_create.go
new file mode 100644
index 000000000..378a92568
--- /dev/null
+++ b/cmd/podman/network_create.go
@@ -0,0 +1,70 @@
+// +build !remoteclient
+
+package main
+
+import (
+ "fmt"
+ "github.com/containers/libpod/pkg/network"
+ "net"
+
+ "github.com/containers/libpod/cmd/podman/cliconfig"
+ "github.com/containers/libpod/pkg/adapter"
+ "github.com/containers/libpod/pkg/rootless"
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+)
+
+var (
+ networkCreateCommand cliconfig.NetworkCreateValues
+ networkCreateDescription = `create CNI networks for containers and pods`
+ _networkCreateCommand = &cobra.Command{
+ Use: "create [flags] [NETWORK]",
+ Short: "network create",
+ Long: networkCreateDescription,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ networkCreateCommand.InputArgs = args
+ networkCreateCommand.GlobalFlags = MainGlobalOpts
+ networkCreateCommand.Remote = remoteclient
+ return networkcreateCmd(&networkCreateCommand)
+ },
+ Example: `podman network create podman1`,
+ }
+)
+
+func init() {
+ networkCreateCommand.Command = _networkCreateCommand
+ networkCreateCommand.SetHelpTemplate(HelpTemplate())
+ networkCreateCommand.SetUsageTemplate(UsageTemplate())
+ flags := networkCreateCommand.Flags()
+ flags.StringVarP(&networkCreateCommand.Driver, "driver", "d", "bridge", "driver to manage the network")
+ flags.IPVar(&networkCreateCommand.Gateway, "gateway", nil, "IPv4 or IPv6 gateway for the subnet")
+ flags.BoolVar(&networkCreateCommand.Internal, "internal", false, "restrict external access from this network")
+ flags.IPNetVar(&networkCreateCommand.IPRange, "ip-range", net.IPNet{}, "allocate container IP from range")
+ // TODO not supported yet
+ //flags.StringVar(&networkCreateCommand.IPamDriver, "ipam-driver", "", "IP Address Management Driver")
+ // TODO enable when IPv6 is working
+ //flags.BoolVar(&networkCreateCommand.IPV6, "IPv6", false, "enable IPv6 networking")
+ flags.IPNetVar(&networkCreateCommand.Network, "subnet", net.IPNet{}, "subnet in CIDR format")
+
+}
+
+func networkcreateCmd(c *cliconfig.NetworkCreateValues) error {
+ if err := network.IsSupportedDriver(c.Driver); err != nil {
+ return err
+ }
+ if rootless.IsRootless() && !remoteclient {
+ return errors.New("network create is not supported for rootless mode")
+ }
+ if len(c.InputArgs) > 1 {
+ return errors.Errorf("only one network can be created at a time")
+ }
+ runtime, err := adapter.GetRuntimeNoStore(getContext(), &c.PodmanCommand)
+ if err != nil {
+ return err
+ }
+ fileName, err := runtime.NetworkCreate(c)
+ if err == nil {
+ fmt.Println(fileName)
+ }
+ return err
+}
diff --git a/cmd/podman/shared/volumes_shared.go b/cmd/podman/shared/volumes_shared.go
new file mode 100644
index 000000000..912615cad
--- /dev/null
+++ b/cmd/podman/shared/volumes_shared.go
@@ -0,0 +1,47 @@
+package shared
+
+import (
+ "context"
+
+ "github.com/containers/libpod/libpod"
+)
+
+// Remove given set of volumes
+func SharedRemoveVolumes(ctx context.Context, runtime *libpod.Runtime, vols []string, all, force bool) ([]string, map[string]error, error) {
+ var (
+ toRemove []*libpod.Volume
+ success []string
+ failed map[string]error
+ )
+
+ failed = make(map[string]error)
+
+ if all {
+ vols, err := runtime.Volumes()
+ if err != nil {
+ return nil, nil, err
+ }
+ toRemove = vols
+ } else {
+ for _, v := range vols {
+ vol, err := runtime.LookupVolume(v)
+ if err != nil {
+ failed[v] = err
+ continue
+ }
+ toRemove = append(toRemove, vol)
+ }
+ }
+
+ // We could parallelize this, but I haven't heard anyone complain about
+ // performance here yet, so hold off.
+ for _, vol := range toRemove {
+ if err := runtime.RemoveVolume(ctx, vol, force); err != nil {
+ failed[vol.Name()] = err
+ continue
+ }
+ success = append(success, vol.Name())
+ }
+
+ return success, failed, nil
+}
diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink
index 752e28256..4692525e3 100644
--- a/cmd/podman/varlink/io.podman.varlink
+++ b/cmd/podman/varlink/io.podman.varlink
@@ -249,7 +249,7 @@ type InfoStore (
run_root: string
)
-# InfoPodman provides details on the podman binary
+# InfoPodman provides details on the Podman binary
type InfoPodmanBinary (
compiler: string,
go_version: string,
@@ -1212,7 +1212,7 @@ method ReceiveFile(path: string, delete: bool) -> (len: int)
method VolumeCreate(options: VolumeCreateOpts) -> (volumeName: string)
# VolumeRemove removes a volume on a remote host
-method VolumeRemove(options: VolumeRemoveOpts) -> (volumeNames: []string)
+method VolumeRemove(options: VolumeRemoveOpts) -> (successes: []string, failures: [string]string)
# GetVolumes gets slice of the volumes on a remote host
method GetVolumes(args: []string, all: bool) -> (volumes: []Volume)
diff --git a/cmd/podman/volume_rm.go b/cmd/podman/volume_rm.go
index 0141d06da..2fa6a8d03 100644
--- a/cmd/podman/volume_rm.go
+++ b/cmd/podman/volume_rm.go
@@ -1,8 +1,6 @@
package main
import (
- "fmt"
-
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/pkg/adapter"
"github.com/pkg/errors"
@@ -52,19 +50,9 @@ func volumeRmCmd(c *cliconfig.VolumeRmValues) error {
return errors.Wrapf(err, "error creating libpod runtime")
}
defer runtime.DeferredShutdown(false)
- deletedVolumeNames, err := runtime.RemoveVolumes(getContext(), c)
+ deletedVolumeNames, deletedVolumeErrors, err := runtime.RemoveVolumes(getContext(), c)
if err != nil {
- if len(deletedVolumeNames) > 0 {
- printDeleteVolumes(deletedVolumeNames)
- return err
- }
- }
- printDeleteVolumes(deletedVolumeNames)
- return err
-}
-
-func printDeleteVolumes(volumes []string) {
- for _, v := range volumes {
- fmt.Println(v)
+ return err
}
+ return printCmdResults(deletedVolumeNames, deletedVolumeErrors)
}
diff --git a/commands.md b/commands.md
index 4d3bea439..c035b011f 100644
--- a/commands.md
+++ b/commands.md
@@ -45,6 +45,7 @@
| [podman-logs(1)](/docs/podman-logs.1.md) | Display the logs of a container |
| [podman-mount(1)](/docs/podman-mount.1.md) | Mount a working container's root filesystem |
| [podman-network(1)](/docs/podman-network.1.md) | Manage Podman CNI networks |
+| [podman-network-create(1)](/docs/podman-network-create.1.md) | Create a CNI network |
| [podman-network-inspect(1)](/docs/podman-network-inspect.1.md) | Inspect one or more Podman networks |
| [podman-network-ls(1)](/docs/podman-network-ls.1.md) | Display a summary of Podman networks |
| [podman-network-rm(1)](/docs/podman-network-rm.1.md) | Remove one or more Podman networks |
diff --git a/completions/bash/podman b/completions/bash/podman
index 6ec700f11..041703810 100644
--- a/completions/bash/podman
+++ b/completions/bash/podman
@@ -956,6 +956,7 @@ _podman_network() {
-h
"
subcommands="
+ create
inspect
ls
rm
@@ -972,6 +973,27 @@ _podman_network() {
esac
}
+_podman_network_create() {
+ local options_with_args="
+ -d
+ --driver
+ --gateway
+ --ip-range
+ --subnet
+ "
+ local boolean_options="
+ --help
+ -h
+ --internal
+ "
+ _complete_ "$options_with_args" "$boolean_options"
+
+ case "$cur" in
+ -*)
+ COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
+ ;;
+ esac
+}
_podman_network_inspect() {
local options_with_args="
"
@@ -1006,7 +1028,7 @@ _podman_network_ls() {
esac
}
-_podman_network_ls() {
+_podman_network_rm() {
local options_with_args="
"
local boolean_options="
diff --git a/contrib/cirrus/README.md b/contrib/cirrus/README.md
index ada362d95..7aa8881d6 100644
--- a/contrib/cirrus/README.md
+++ b/contrib/cirrus/README.md
@@ -72,7 +72,7 @@ and `darwin` targets.
### ``special_testing_cgroupv2`` Task
Use the latest Fedora release with the required kernel options pre-set for
-exercising cgroups v2 with podman integration tests. Also depends on
+exercising cgroups v2 with Podman integration tests. Also depends on
having `SPECIALMODE` set to 'cgroupv2`
@@ -272,7 +272,7 @@ values follows:
* `rootless`: Causes a random, ordinary user account to be created
and utilized for testing.
* `in_podman`: Causes testing to occur within a container executed by
- podman on the host.
+ Podman on the host.
* `cgroupv2`: The kernel on this VM was prepared with options to enable v2 cgroups
* `windows`: See **darwin**
* `darwin`: Signals the ``special_testing_cross`` task to cross-compile the remote client.
diff --git a/contrib/podmanimage/README.md b/contrib/podmanimage/README.md
index 3dc07ad63..ab55f3189 100644
--- a/contrib/podmanimage/README.md
+++ b/contrib/podmanimage/README.md
@@ -5,7 +5,7 @@
## Overview
This directory contains the Dockerfiles necessary to create the three podmanimage container
-images that are housed on quay.io under the podman account. All three repositories where
+images that are housed on quay.io under the Podman account. All three repositories where
the images live are public and can be pulled without credentials. These container images are secured and the
resulting containers can run safely with privileges within the container. The container images are built
using the latest Fedora and then Podman is installed into them:
diff --git a/contrib/podmanimage/stable/Dockerfile b/contrib/podmanimage/stable/Dockerfile
index 056f62624..6b4eb2220 100644
--- a/contrib/podmanimage/stable/Dockerfile
+++ b/contrib/podmanimage/stable/Dockerfile
@@ -18,7 +18,7 @@ RUN sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var
RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock
# Adjust libpod.conf to write logging to a file
-RUN sed -i 's/events_logger = "journald"/events_logger = "file"/g' /usr/share/containers/libpod.conf
+RUN sed -i 's/# events_logger = "journald"/events_logger = "file"/g' /usr/share/containers/libpod.conf
# Set up environment variables to note that this is
# not starting with usernamespace and default to
diff --git a/contrib/podmanimage/testing/Dockerfile b/contrib/podmanimage/testing/Dockerfile
index 50d8ed7f2..4dffc8911 100644
--- a/contrib/podmanimage/testing/Dockerfile
+++ b/contrib/podmanimage/testing/Dockerfile
@@ -20,7 +20,7 @@ RUN sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var
RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock
# Adjust libpod.conf to write logging to a file
-RUN sed -i 's/events_logger = "journald"/events_logger = "file"/g' /usr/share/containers/libpod.conf
+RUN sed -i 's/# events_logger = "journald"/events_logger = "file"/g' /usr/share/containers/libpod.conf
# Set up environment variables to note that this is
# not starting with usernamespace and default to
diff --git a/contrib/podmanimage/upstream/Dockerfile b/contrib/podmanimage/upstream/Dockerfile
index 3583e1c54..82b88b50b 100644
--- a/contrib/podmanimage/upstream/Dockerfile
+++ b/contrib/podmanimage/upstream/Dockerfile
@@ -62,7 +62,7 @@ RUN dnf -y install --exclude container-selinux \
mkdir -p /usr/share/containers; \
cp $GOPATH/src/github.com/containers/libpod/libpod.conf /usr/share/containers; \
# Adjust libpod.conf to write logging to a file
- sed -i 's/events_logger = "journald"/events_logger = "file"/g' /usr/share/containers/libpod.conf; \
+ sed -i 's/# events_logger = "journald"/events_logger = "file"/g' /usr/share/containers/libpod.conf; \
rm -rf /root/podman/*; \
dnf -y remove git golang go-md2man make; \
dnf clean all;
diff --git a/dependencies/analyses/README.md b/dependencies/analyses/README.md
index a440a0ebd..67dab6f75 100644
--- a/dependencies/analyses/README.md
+++ b/dependencies/analyses/README.md
@@ -13,7 +13,7 @@ The analysis script will then read and parse the build data and print a sorted t
Running such an analysis on libpod may look as follows:
```
-# 1) Build the podman binary with `-work -a`.
+# 1) Build the Podman binary with `-work -a`.
[libpod]$ BUILDFLAGS="-work -a" make podman
[...]
WORK=/tmp/go-build794287815
diff --git a/docs/podman-build.1.md b/docs/podman-build.1.md
index 20f4d6aab..1a04f8224 100644
--- a/docs/podman-build.1.md
+++ b/docs/podman-build.1.md
@@ -521,8 +521,8 @@ process.
**--volume**, **-v**[=*[HOST-DIR:CONTAINER-DIR[:OPTIONS]]*]
- Create a bind mount. If you specify, ` -v /HOST-DIR:/CONTAINER-DIR`, podman
- bind mounts `/HOST-DIR` in the host to `/CONTAINER-DIR` in the podman
+ Create a bind mount. If you specify, ` -v /HOST-DIR:/CONTAINER-DIR`, Podman
+ bind mounts `/HOST-DIR` in the host to `/CONTAINER-DIR` in the Podman
container. The `OPTIONS` are a comma delimited list and can be:
* [rw|ro]
@@ -547,14 +547,14 @@ See examples.
Labeling systems like SELinux require that proper labels are placed on volume
content mounted into a container. Without a label, the security system might
prevent the processes running inside the container from using the content. By
-default, podman does not change the labels set by the OS.
+default, Podman does not change the labels set by the OS.
To change a label in the container context, you can add either of two suffixes
-`:z` or `:Z` to the volume mount. These suffixes tell podman to relabel file
-objects on the shared volumes. The `z` option tells podman that two containers
-share the volume content. As a result, podman labels the content with a shared
+`:z` or `:Z` to the volume mount. These suffixes tell Podman to relabel file
+objects on the shared volumes. The `z` option tells Podman that two containers
+share the volume content. As a result, Podman labels the content with a shared
content label. Shared volume labels allow all containers to read/write content.
-The `Z` option tells podman to label the content with a private unshared label.
+The `Z` option tells Podman to label the content with a private unshared label.
Only the current container can use a private volume.
`Overlay Volume Mounts`
diff --git a/docs/podman-commit.1.md b/docs/podman-commit.1.md
index 5b0ba48aa..07a885ae2 100644
--- a/docs/podman-commit.1.md
+++ b/docs/podman-commit.1.md
@@ -15,7 +15,7 @@ configured with the `--change` flag and a commit message can be set using the
`--message` flag. The container and its processes are paused while the image is
committed. This minimizes the likelihood of data corruption when creating the new
image. If this is not desired, the `--pause` flag can be set to false. When the commit
-is complete, podman will print out the ID of the new image.
+is complete, Podman will print out the ID of the new image.
If *image* does not begin with a registry name component, `localhost` will be added to the name.
diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md
index 3bd5ed44c..996ef3863 100644
--- a/docs/podman-create.1.md
+++ b/docs/podman-create.1.md
@@ -328,7 +328,7 @@ Print usage statement
**--http-proxy**=*true|false*
By default proxy environment variables are passed into the container if set
-for the podman process. This can be disabled by setting the `--http-proxy`
+for the Podman process. This can be disabled by setting the `--http-proxy`
option to `false`. The environment variables passed in include `http_proxy`,
`https_proxy`, `ftp_proxy`, `no_proxy`, and also the upper case versions of
those. This option is only needed when the host system must use a proxy but
@@ -347,7 +347,7 @@ Defaults to `true`
**--image-volume**, **builtin-volume**=*bind|tmpfs|ignore*
-Tells podman how to handle the builtin image volumes. The options are: 'bind', 'tmpfs', or 'ignore' (default 'bind').
+Tells Podman how to handle the builtin image volumes. The options are: 'bind', 'tmpfs', or 'ignore' (default 'bind').
bind: A directory is created inside the container state directory and bind mounted into
the container for the volumes.
tmpfs: The volume is mounted onto the container as a tmpfs, which allows the users to create
@@ -511,7 +511,7 @@ Set the Network mode for the container. Invalid if using **--dns**, **--dns-opti
'bridge': create a network stack on the default bridge
'none': no networking
'container:<name|id>': reuse another container's network stack
- 'host': use the podman host network stack. Note: the host mode gives the container full access to local system services such as D-bus and is therefore considered insecure.
+ 'host': use the Podman host network stack. Note: the host mode gives the container full access to local system services such as D-bus and is therefore considered insecure.
'<network-name>|<network-id>': connect to a user-defined network
'ns:<path>': path to a network namespace to join
'slirp4netns': use slirp4netns to create a user network stack. This is the default for rootless containers
@@ -549,19 +549,19 @@ Tune the container's pids limit. Set `-1` to have unlimited pids for the contain
**--pod**=*name*
-Run container in an existing pod. If you want podman to make the pod for you, preference the pod name with `new:`.
+Run container in an existing pod. If you want Podman to make the pod for you, preference the pod name with `new:`.
To make a pod with more granular options, use the `podman pod create` command before creating a container.
**--privileged**=*true|false*
Give extended privileges to this container. The default is *false*.
-By default, podman containers are
+By default, Podman containers are
“unprivileged” (=false) and cannot, for example, modify parts of the kernel.
This is because by default a container is not allowed to access any devices.
A “privileged” container is given access to all devices.
-When the operator executes a privileged container, podman enables access
+When the operator executes a privileged container, Podman enables access
to all devices on the host, turns off graphdriver mount options, as well as
turning off most of the security measures protecting the host from the
container.
@@ -583,9 +583,9 @@ Use `podman port` to see the actual mapping: `podman port CONTAINER $CONTAINERPO
Publish all exposed ports to random ports on the host interfaces. The default is *false*.
When set to true publish all exposed ports to the host interfaces. The
-default is false. If the operator uses -P (or -p) then podman will make the
+default is false. If the operator uses -P (or -p) then Podman will make the
exposed port accessible on the host and the ports will be available to any
-client that can reach the host. When using -P, podman will bind any exposed
+client that can reach the host. When using -P, Podman will bind any exposed
port to a random port on the host within an *ephemeral port range* defined by
`/proc/sys/net/ipv4/ip_local_port_range`. To find the mapping between the host
ports and the exposed ports, use `podman port`.
@@ -739,11 +739,11 @@ any options, the systems uses the following options:
Allocate a pseudo-TTY. The default is *false*.
-When set to true podman will allocate a pseudo-tty and attach to the standard
+When set to true Podman will allocate a pseudo-tty and attach to the standard
input of the container. This can be used, for example, to run a throwaway
interactive shell. The default is false.
-Note: The **-t** option is incompatible with a redirection of the podman client
+Note: The **-t** option is incompatible with a redirection of the Podman client
standard input.
**--uidmap**=*container_uid:host_uid:amount*
@@ -799,8 +799,8 @@ container. The `OPTIONS` are a comma delimited list and can be:
* [`[r]shared`|`[r]slave`|`[r]private`]
The `CONTAINER-DIR` must be an absolute path such as `/src/docs`. The `HOST-DIR`
-must be an absolute path as well. podman bind-mounts the `HOST-DIR` to the
-path you specify. For example, if you supply the `/foo` value, podman creates a bind-mount.
+must be an absolute path as well. Podman bind-mounts the `HOST-DIR` to the
+path you specify. For example, if you supply the `/foo` value, Podman creates a bind-mount.
You can specify multiple **-v** options to mount one or more mounts to a
container.
@@ -812,14 +812,14 @@ See examples.
Labeling systems like SELinux require that proper labels are placed on volume
content mounted into a container. Without a label, the security system might
prevent the processes running inside the container from using the content. By
-default, podman does not change the labels set by the OS.
+default, Podman does not change the labels set by the OS.
To change a label in the container context, you can add either of two suffixes
-`:z` or `:Z` to the volume mount. These suffixes tell podman to relabel file
-objects on the shared volumes. The `z` option tells podman that two containers
-share the volume content. As a result, podman labels the content with a shared
+`:z` or `:Z` to the volume mount. These suffixes tell Podman to relabel file
+objects on the shared volumes. The `z` option tells Podman that two containers
+share the volume content. As a result, Podman labels the content with a shared
content label. Shared volume labels allow all containers to read/write content.
-The `Z` option tells podman to label the content with a private unshared label.
+The `Z` option tells Podman to label the content with a private unshared label.
Only the current container can use a private volume.
By default bind mounted volumes are `private`. That means any mounts done
@@ -867,7 +867,7 @@ To share a volume, use the --volumes-from option when running
the target container. You can share volumes even if the source container
is not running.
-By default, podman mounts the volumes in the same mode (read-write or
+By default, Podman mounts the volumes in the same mode (read-write or
read-only) as it is mounted in the source container. Optionally, you
can change this by suffixing the container-id with either the `ro` or
`rw` keyword.
@@ -875,11 +875,11 @@ can change this by suffixing the container-id with either the `ro` or
Labeling systems like SELinux require that proper labels are placed on volume
content mounted into a container. Without a label, the security system might
prevent the processes running inside the container from using the content. By
-default, podman does not change the labels set by the OS.
+default, Podman does not change the labels set by the OS.
To change a label in the container context, you can add `z` to the volume mount.
-This suffix tells podman to relabel file objects on the shared volumes. The `z`
-option tells podman that two containers share the volume content. As a result,
+This suffix tells Podman to relabel file objects on the shared volumes. The `z`
+option tells Podman that two containers share the volume content. As a result,
podman labels the content with a shared content label. Shared volume labels allow
all containers to read/write content.
@@ -933,7 +933,7 @@ Note: RHEL7 and Centos 7 will not have this feature until RHEL7.7 is released.
In order for users to run rootless, there must be an entry for their username in /etc/subuid and /etc/subgid which lists the UIDs for their user namespace.
-Rootless podman works better if the fuse-overlayfs and slirp4netns packages are installed.
+Rootless Podman works better if the fuse-overlayfs and slirp4netns packages are installed.
The fuse-overlay package provides a userspace overlay storage driver, otherwise users need to use
the vfs storage driver, which is diskspace expensive and does not perform well. slirp4netns is
required for VPN, without it containers need to be run with the --net=host flag.
@@ -943,7 +943,7 @@ required for VPN, without it containers need to be run with the --net=host flag.
Environment variables within containers can be set using multiple different options: This section describes the precedence.
Precedence Order:
- **--env-host** : Host environment of the process executing podman is added.
+ **--env-host** : Host environment of the process executing Podman is added.
Container image : Any environment variables specified in the container image.
@@ -973,7 +973,7 @@ b
subgid(5), subuid(5), libpod.conf(5), systemd.unit(5), setsebool(8), slirp4netns(1), fuse-overlayfs(1)
## HISTORY
-October 2017, converted from Docker documentation to podman by Dan Walsh for podman <dwalsh@redhat.com>
+October 2017, converted from Docker documentation to Podman by Dan Walsh for Podman <dwalsh@redhat.com>
November 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
diff --git a/docs/podman-events.1.md b/docs/podman-events.1.md
index ed3faedfd..a5a715098 100644
--- a/docs/podman-events.1.md
+++ b/docs/podman-events.1.md
@@ -98,7 +98,7 @@ The *since* and *until* values can be RFC3339Nano time stamps or a Go duration s
## EXAMPLES
-Showing podman events
+Showing Podman events
```
$ podman events
2019-03-02 10:33:42.312377447 -0600 CST container create 34503c192940 (image=docker.io/library/alpine:latest, name=friendly_allen)
@@ -108,7 +108,7 @@ $ podman events
2019-03-02 10:33:51.047104966 -0600 CST container cleanup 34503c192940 (image=docker.io/library/alpine:latest, name=friendly_allen)
```
-Show only podman create events
+Show only Podman create events
```
$ podman events --filter event=create
2019-03-02 10:36:01.375685062 -0600 CST container create 20dc581f6fbf (image=docker.io/library/alpine:latest, name=sharp_morse)
@@ -117,7 +117,7 @@ $ podman events --filter event=create
2019-03-02 10:36:29.978806894 -0600 CST container create d81e30f1310f (image=docker.io/library/busybox:latest, name=musing_newton)
```
-Show only podman pod create events
+Show only Podman pod create events
```
$ podman events --filter event=create --filter type=pod
2019-03-02 10:44:29.601746633 -0600 CST pod create 1df5ebca7b44 (image=, name=confident_hawking)
@@ -125,7 +125,7 @@ $ podman events --filter event=create --filter type=pod
2019-03-02 10:44:47.486759133 -0600 CST pod create 71e807fc3a8e (image=, name=reverent_swanson)
```
-Show only podman events created in the last five minutes:
+Show only Podman events created in the last five minutes:
```
$ sudo podman events --since 5m
2019-03-02 10:44:29.598835409 -0600 CST container create b629d10d3831 (image=k8s.gcr.io/pause:3.1, name=1df5ebca7b44-infra)
@@ -134,7 +134,7 @@ $ sudo podman events --since 5m
2019-03-02 10:44:42.374637304 -0600 CST pod create ca731231718e (image=, name=webapp)
```
-Show podman events in JSON Lines format
+Show Podman events in JSON Lines format
```
events --format json
{"ID":"683b0909d556a9c02fa8cd2b61c3531a965db42158627622d1a67b391964d519","Image":"localhost/myshdemo:latest","Name":"agitated_diffie","Status":"cleanup","Time":"2019-04-27T22:47:00.849932843-04:00","Type":"container"}
diff --git a/docs/podman-exec.1.md b/docs/podman-exec.1.md
index f71b21126..4c17c056a 100644
--- a/docs/podman-exec.1.md
+++ b/docs/podman-exec.1.md
@@ -64,7 +64,7 @@ when creating the container.
The exit code from `podman exec` gives information about why the command within the container failed to run or why it exited. When `podman exec` exits with a
non-zero code, the exit codes follow the `chroot` standard, see below:
-**_125_** if the error is with podman **_itself_**
+**_125_** if the error is with Podman **_itself_**
$ podman exec --foo ctrID /bin/sh; echo $?
Error: unknown flag: --foo
diff --git a/docs/podman-generate-kube.1.md b/docs/podman-generate-kube.1.md
index 8f15e14ba..f4b4cd482 100644
--- a/docs/podman-generate-kube.1.md
+++ b/docs/podman-generate-kube.1.md
@@ -6,7 +6,7 @@ podman-generate-kube - Generate Kubernetes YAML based on a pod or container
**podman generate kube** [*options*] *container* | *pod*
## DESCRIPTION
-**podman generate kube** will generate Kubernetes Pod YAML (v1 specification) from a podman container or pod. Whether
+**podman generate kube** will generate Kubernetes Pod YAML (v1 specification) from a Podman container or pod. Whether
the input is for a container or pod, Podman will always generate the specification as a Pod. The input may be in the form
of a pod or container name or ID.
diff --git a/docs/podman-image-sign.1.md b/docs/podman-image-sign.1.md
index ca438b438..62845e715 100644
--- a/docs/podman-image-sign.1.md
+++ b/docs/podman-image-sign.1.md
@@ -39,7 +39,7 @@ Sign the busybox image with the identify of foo@bar.com with a user's keyring an
The write (and read) location for signatures is defined in YAML-based
configuration files in /etc/containers/registries.d/. When you sign
-an image, podman will use those configuration files to determine
+an image, Podman will use those configuration files to determine
where to write the signature based on the the name of the originating
registry or a default storage value unless overriden with the --directory
option. For example, consider the following configuration file.
diff --git a/docs/podman-image-tree.1.md b/docs/podman-image-tree.1.md
index 5ffd995f6..c4624e05c 100644
--- a/docs/podman-image-tree.1.md
+++ b/docs/podman-image-tree.1.md
@@ -9,7 +9,7 @@ podman\-image\-tree - Prints layer hierarchy of an image in a tree format
## DESCRIPTION
Prints layer hierarchy of an image in a tree format.
-If you do not provide a *tag*, podman will default to `latest` for the *image*.
+If you do not provide a *tag*, Podman will default to `latest` for the *image*.
Layers are indicated with image tags as `Top Layer of`, when the tag is known locally.
## OPTIONS
diff --git a/docs/podman-network-create.1.md b/docs/podman-network-create.1.md
new file mode 100644
index 000000000..0679d8ee2
--- /dev/null
+++ b/docs/podman-network-create.1.md
@@ -0,0 +1,70 @@
+% podman-network-create(1)
+
+## NAME
+podman\-network-create - Create a Podman CNI network
+
+## SYNOPSIS
+**podman network create** [*options*] name
+
+## DESCRIPTION
+Create a CNI-network configuration for use with Podman. At the time of this writing, the only network
+type that can be created is a *bridge* network.
+
+If no options are provided, Podman will assign a free subnet and name for your network.
+
+Upon completion of creating the network, Podman will display the path to the newly added network file.
+
+## OPTIONS
+**-d**, , **--driver**
+
+Driver to manage the network (default "bridge"). Currently on `bridge` is supported.
+
+**--gateway**
+
+Define a gateway for the subnet. If you want to provide a gateway address, you must also provide a
+*subnet* option.
+
+**--internal**
+
+Restrict external access of this network
+
+**--ip-range**
+
+Allocate container IP from a range. The range must be a complete subnet and in CIDR notation. The *ip-range* option
+must be used with a *subnet* option.
+
+**--subnet**
+
+The subnet in CIDR notation.
+
+## EXAMPLE
+
+Create a network with no options
+```
+# podman network create
+/etc/cni/net.d/cni-podman-4.conflist
+```
+
+Create a network named *newnet* that uses *192.5.0.0/16* for its subnet.
+```
+# podman network create --subnet 192.5.0.0/16 newnet
+/etc/cni/net.d/newnet.conflist
+```
+
+Create a network named *newnet* that uses *192.168.33.0/24* and defines a gateway as *192.168.133.3*
+```
+# podman network create --subnet 192.168.33.0/24 --gateway 192.168.33.3 newnet
+/etc/cni/net.d/newnet.conflist
+```
+
+Create a network that uses a *192.168.55.0/24** subnet and has an IP address range of *192.168.55.129 - 192.168.55.254*.
+```
+# podman network create --subnet 192.168.55.0/24 --ip-range 192.168.55.128/25
+/etc/cni/net.d/cni-podman-5.conflist
+```
+
+## SEE ALSO
+podman(1), podman-network(1), podman-network-inspect(1)
+
+## HISTORY
+August 2019, Originally compiled by Brent Baude <bbaude@redhat.com>
diff --git a/docs/podman-network-ls.1.md b/docs/podman-network-ls.1.md
index 658b86c21..46e424593 100644
--- a/docs/podman-network-ls.1.md
+++ b/docs/podman-network-ls.1.md
@@ -12,7 +12,7 @@ Displays a list of existing podman networks. This command is not available for r
## OPTIONS
**--quiet**, **-q**
-The `quiet` options will restrict the output to only the network names
+The `quiet` option will restrict the output to only the network names
## EXAMPLE
diff --git a/docs/podman-network.1.md b/docs/podman-network.1.md
index c9f6725a3..f05b2b78f 100644
--- a/docs/podman-network.1.md
+++ b/docs/podman-network.1.md
@@ -13,6 +13,7 @@ The network command manages CNI networks for Podman. It is not supported for roo
| Command | Man Page | Description |
| ------- | --------------------------------------------------- | ---------------------------------------------------------------------------- |
+| create | [podman-network-create(1)](podman-network-create.1.md)| Create a Podman CNI network|
| inspect | [podman-network-inspect(1)](podman-network-inspect.1.md)| Displays the raw CNI network configuration for one or more networks|
| ls | [podman-network-ls(1)](podman-network-ls.1.md)| Display a summary of CNI networks |
| rm | [podman-network-rm(1)](podman-network-rm.1.md)| Remove one or more CNI networks |
diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md
index cb5a96098..0dbd4ea6f 100644
--- a/docs/podman-run.1.md
+++ b/docs/podman-run.1.md
@@ -288,7 +288,7 @@ on the host system.
**--gidmap**=*container_gid:host_gid:amount*
Run the container in a new user namespace using the supplied mapping. This option conflicts with the --userns and --subgidname flags.
-This option can be passed several times to map different ranges. If calling podman run as an unprivileged user, the user needs to have the right to use the mapping. See `subuid(5)`.
+This option can be passed several times to map different ranges. If calling Podman run as an unprivileged user, the user needs to have the right to use the mapping. See `subuid(5)`.
The example maps gids 0-2000 in the container to the gids 30000-31999 on the host. `--gidmap=0:30000:2000`
**--group-add**=*group*
@@ -335,7 +335,7 @@ Sets the container host name that is available inside the container.
**--http-proxy**=*true|false*
By default proxy environment variables are passed into the container if set
-for the podman process. This can be disabled by setting the `--http-proxy`
+for the Podman process. This can be disabled by setting the `--http-proxy`
option to `false`. The environment variables passed in include `http_proxy`,
`https_proxy`, `ftp_proxy`, `no_proxy`, and also the upper case versions of
those. This option is only needed when the host system must use a proxy but
@@ -354,7 +354,7 @@ Defaults to `true`
**--image-volume**, **builtin-volume**=*bind|tmpfs|ignore*
-Tells podman how to handle the builtin image volumes.
+Tells Podman how to handle the builtin image volumes.
The options are: `bind`, `tmpfs`, or `ignore` (default `bind`)
@@ -481,6 +481,8 @@ Current supported mount TYPES are bind, and tmpfs.
type=bind,source=/path/on/host,destination=/path/in/container
+ type=bind,source=volume-name,destination=/path/in/container
+
type=tmpfs,tmpfs-size=512M,destination=/path/in/container
Common Options:
@@ -522,7 +524,7 @@ Set the Network mode for the container. Invalid if using **--dns**, **--dns-opti
- `bridge`: create a network stack on the default bridge
- `none`: no networking
- `container:<name|id>`: reuse another container's network stack
-- `host`: use the podman host network stack. Note: the host mode gives the container full access to local system services such as D-bus and is therefore considered insecure.
+- `host`: use the Podman host network stack. Note: the host mode gives the container full access to local system services such as D-bus and is therefore considered insecure.
- `<network-name>|<network-id>`: connect to a user-defined network
- `ns:<path>`: path to a network namespace to join
- `slirp4netns`: use slirp4netns to create a user network stack. This is the default for rootless containers
@@ -562,7 +564,7 @@ Tune the container's pids limit. Set `-1` to have unlimited pids for the contain
**--pod**=*name*
-Run container in an existing pod. If you want podman to make the pod for you, preference the pod name with `new:`.
+Run container in an existing pod. If you want Podman to make the pod for you, preference the pod name with `new:`.
To make a pod with more granular options, use the `podman pod create` command before creating a container.
If a container is run with a pod, and the pod has an infra-container, the infra-container will be started before the container is.
@@ -570,12 +572,12 @@ If a container is run with a pod, and the pod has an infra-container, the infra-
Give extended privileges to this container. The default is *false*.
-By default, podman containers are “unprivileged” (=false) and cannot,
+By default, Podman containers are “unprivileged” (=false) and cannot,
for example, modify parts of the kernel. This is because by default a
container is not allowed to access any devices. A “privileged” container
is given access to all devices.
-When the operator executes **podman run --privileged**, podman enables access
+When the operator executes **podman run --privileged**, Podman enables access
to all devices on the host, turns off graphdriver mount options, as well as
turning off most of the security measures protecting the host from the
container.
@@ -601,11 +603,11 @@ Use `podman port` to see the actual mapping: `podman port CONTAINER $CONTAINERPO
Publish all exposed ports to random ports on the host interfaces. The default is *false*.
When set to true publish all exposed ports to the host interfaces. The
-default is false. If the operator uses -P (or -p) then podman will make the
+default is false. If the operator uses -P (or -p) then Podman will make the
exposed port accessible on the host and the ports will be available to any
client that can reach the host.
-When using -P, podman will bind any exposed port to a random port on the host
+When using -P, Podman will bind any exposed port to a random port on the host
within an *ephemeral port range* defined by `/proc/sys/net/ipv4/ip_local_port_range`.
To find the mapping between the host ports and the exposed ports, use `podman port`.
@@ -708,13 +710,13 @@ Timeout (in seconds) to stop a container. Default is 10.
**--subgidname**=*name*
Run the container in a new user namespace using the map with 'name' in the `/etc/subgid` file.
-If calling podman run as an unprivileged user, the user needs to have the right to use the mapping. See `subgid(5)`.
+If calling Podman run as an unprivileged user, the user needs to have the right to use the mapping. See `subgid(5)`.
This flag conflicts with `--userns` and `--gidmap`.
**--subuidname**=*name*
Run the container in a new user namespace using the map with 'name' in the `/etc/subuid` file.
-If calling podman run as an unprivileged user, the user needs to have the right to use the mapping. See `subuid(5)`.
+If calling Podman run as an unprivileged user, the user needs to have the right to use the mapping. See `subuid(5)`.
This flag conflicts with `--userns` and `--uidmap`.
**--sysctl**=SYSCTL
@@ -744,7 +746,7 @@ Note: if you use the `--network=host` option these sysctls will not be allowed.
Run container in systemd mode. The default is *true*.
-If the command you are running inside of the container is systemd or init, podman
+If the command you are running inside of the container is systemd or init, Podman
will setup tmpfs mount points in the following directories:
/run, /run/lock, /tmp, /sys/fs/cgroup/systemd, /var/lib/journal
@@ -776,17 +778,17 @@ any options, the systems uses the following options:
Allocate a pseudo-TTY. The default is *false*.
-When set to true podman will allocate a pseudo-tty and attach to the standard
+When set to true Podman will allocate a pseudo-tty and attach to the standard
input of the container. This can be used, for example, to run a throwaway
interactive shell. The default is false.
-**NOTE**: The **-t** option is incompatible with a redirection of the podman client
+**NOTE**: The **-t** option is incompatible with a redirection of the Podman client
standard input.
**--uidmap**=*container_uid:host_uid:amount*
Run the container in a new user namespace using the supplied mapping. This option conflicts with the --userns and --subuidname flags.
-This option can be passed several times to map different ranges. If calling podman run as an unprivileged user, the user needs to have the right to use the mapping. See `subuid(5)`.
+This option can be passed several times to map different ranges. If calling Podman run as an unprivileged user, the user needs to have the right to use the mapping. See `subuid(5)`.
The example maps uids 0-2000 in the container to the uids 30000-31999 on the host. `--uidmap=0:30000:2000`
**--ulimit**=*option*
@@ -827,19 +829,23 @@ Set the UTS mode for the container
**NOTE**: the host mode gives the container access to changing the host's hostname and is therefore considered insecure.
-**--volume**, **-v**[=*[HOST-DIR:CONTAINER-DIR[:OPTIONS]]*]
+**--volume**, **-v**[=*[HOST-DIR-OR-VOUME-NAME:CONTAINER-DIR[:OPTIONS]]*]
+
+Create a bind mount. If you specify, ` -v /HOST-DIR:/CONTAINER-DIR`, Podman
+bind mounts `/HOST-DIR` in the host to `/CONTAINER-DIR` in the Podman
+container. Similarly, `-v VOLUME-NAME:/CONTAINER-DIR` will mount the volume
+in the host to the container. If no such named volume exists, Podman will
+create one.
-Create a bind mount. If you specify, ` -v /HOST-DIR:/CONTAINER-DIR`, podman
-bind mounts `/HOST-DIR` in the host to `/CONTAINER-DIR` in the podman
-container. The `OPTIONS` are a comma delimited list and can be:
+ The `OPTIONS` are a comma delimited list and can be:
* [`rw`|`ro`]
* [`z`|`Z`]
* [`[r]shared`|`[r]slave`|`[r]private`]
-The `CONTAINER-DIR` must be an absolute path such as `/src/docs`. The `HOST-DIR`
-must be an absolute path as well. podman bind-mounts the `HOST-DIR` to the
-path you specify. For example, if you supply the `/foo` value, podman creates a bind-mount.
+The `/CONTAINER-DIR` must be an absolute path such as `/src/docs`. The `/HOST-DIR`
+must be an absolute path as well. Podman bind-mounts the `HOST-DIR` to the
+path you specify. For example, if you supply the `/foo` value, Podman creates a bind-mount.
You can specify multiple **-v** options to mount one or more mounts to a
container.
@@ -851,14 +857,14 @@ See examples.
Labeling systems like SELinux require that proper labels are placed on volume
content mounted into a container. Without a label, the security system might
prevent the processes running inside the container from using the content. By
-default, podman does not change the labels set by the OS.
+default, Podman does not change the labels set by the OS.
To change a label in the container context, you can add either of two suffixes
-`:z` or `:Z` to the volume mount. These suffixes tell podman to relabel file
-objects on the shared volumes. The `z` option tells podman that two containers
-share the volume content. As a result, podman labels the content with a shared
+`:z` or `:Z` to the volume mount. These suffixes tell Podman to relabel file
+objects on the shared volumes. The `z` option tells Podman that two containers
+share the volume content. As a result, Podman labels the content with a shared
content label. Shared volume labels allow all containers to read/write content.
-The `Z` option tells podman to label the content with a private unshared label.
+The `Z` option tells Podman to label the content with a private unshared label.
Only the current container can use a private volume.
By default bind mounted volumes are `private`. That means any mounts done
@@ -906,7 +912,7 @@ To share a volume, use the --volumes-from option when running
the target container. You can share volumes even if the source container
is not running.
-By default, podman mounts the volumes in the same mode (read-write or
+By default, Podman mounts the volumes in the same mode (read-write or
read-only) as it is mounted in the source container. Optionally, you
can change this by suffixing the container-id with either the `ro` or
`rw` keyword.
@@ -914,11 +920,11 @@ can change this by suffixing the container-id with either the `ro` or
Labeling systems like SELinux require that proper labels are placed on volume
content mounted into a container. Without a label, the security system might
prevent the processes running inside the container from using the content. By
-default, podman does not change the labels set by the OS.
+default, Podman does not change the labels set by the OS.
To change a label in the container context, you can add `z` to the volume mount.
-This suffix tells podman to relabel file objects on the shared volumes. The `z`
-option tells podman that two containers share the volume content. As a result,
+This suffix tells Podman to relabel file objects on the shared volumes. The `z`
+option tells Podman that two containers share the volume content. As a result,
podman labels the content with a shared content label. Shared volume labels allow
all containers to read/write content.
@@ -940,7 +946,7 @@ The exit code from `podman run` gives information about why the container
failed to run or why it exited. When `podman run` exits with a non-zero code,
the exit codes follow the `chroot` standard, see below:
-**_125_** if the error is with podman **_itself_**
+**_125_** if the error is with Podman **_itself_**
$ podman run --foo busybox; echo $?
Error: unknown flag: --foo
@@ -1011,7 +1017,7 @@ This should list the message sent to logger.
### Attaching to one or more from STDIN, STDOUT, STDERR
-If you do not specify -a then podman will attach everything (stdin,stdout,stderr).
+If you do not specify -a then Podman will attach everything (stdin,stdout,stderr).
You can specify to which of the three standard streams (stdin, stdout, stderr)
you'd like to connect instead, as in:
@@ -1098,18 +1104,26 @@ $ podman run -p 8080:80 -d -i -t fedora/httpd
To mount a host directory as a container volume, specify the absolute path to
the directory and the absolute path for the container directory separated by a
-colon:
+colon. If the source is a named volume maintained by Podman, it's recommended to
+use it's name rather than the path to the volume. Otherwise the volume will be
+considered as an orphan and wiped if you execute `podman volume prune`:
```
$ podman run -v /var/db:/data1 -i -t fedora bash
+
+$ podman run -v data:/data2 -i -t fedora bash
```
Using --mount flags, To mount a host directory as a container folder, specify
-the absolute path to the directory and the absolute path for the container
-directory:
+the absolute path to the directory or the volume name, and the absolute path
+within the container directory:
+````
$ podman run --mount type=bind,src=/var/db,target=/data1 busybox sh
+$ podman run --mount type=bind,src=volume-name,target=/data1 busybox sh
+````
+
When using SELinux, be aware that the host has no knowledge of container SELinux
policy. Therefore, in the above example, if SELinux policy is enforced, the
`/var/db` directory is not writable to the container. A "Permission Denied"
@@ -1184,7 +1198,7 @@ $ podman run --sysctl net.ipv4.ip_forward=1 someimage
Note:
-Not all sysctls are namespaced. podman does not support changing sysctls
+Not all sysctls are namespaced. Podman does not support changing sysctls
inside of a container that also modify the host system. As the kernel
evolves we expect to see more sysctls become namespaced.
@@ -1218,7 +1232,7 @@ Note: RHEL7 and Centos 7 will not have this feature until RHEL7.7 is released.
In order for users to run rootless, there must be an entry for their username in /etc/subuid and /etc/subgid which lists the UIDs for their user namespace.
-Rootless podman works better if the fuse-overlayfs and slirp4netns packages are installed.
+Rootless Podman works better if the fuse-overlayfs and slirp4netns packages are installed.
The fuse-overlay package provides a userspace overlay storage driver, otherwise users need to use
the vfs storage driver, which is diskspace expensive and does not perform well. slirp4netns is
required for VPN, without it containers need to be run with the --net=host flag.
@@ -1229,7 +1243,7 @@ Environment variables within containers can be set using multiple different opti
Precedence Order:
- **--env-host** : Host environment of the process executing podman is added.
+ **--env-host** : Host environment of the process executing Podman is added.
Container image : Any environment variables specified in the container image.
@@ -1259,7 +1273,7 @@ subgid(5), subuid(5), libpod.conf(5), systemd.unit(5), setsebool(8), slirp4netns
## HISTORY
September 2018, updated by Kunal Kushwaha <kushwaha_kunal_v7@lab.ntt.co.jp>
-October 2017, converted from Docker documentation to podman by Dan Walsh for podman <dwalsh@redhat.com>
+October 2017, converted from Docker documentation to Podman by Dan Walsh for Podman <dwalsh@redhat.com>
November 2015, updated by Sally O'Malley <somalley@redhat.com>
diff --git a/docs/podman-volume-inspect.1.md b/docs/podman-volume-inspect.1.md
index a6c99f6c8..ac5b6c977 100644
--- a/docs/podman-volume-inspect.1.md
+++ b/docs/podman-volume-inspect.1.md
@@ -11,6 +11,7 @@ podman\-volume\-inspect - Get detailed information on one or more volumes
Display detailed information on one or more volumes. The output can be formatted using
the **--format** flag and a Go template. To get detailed information about all the
existing volumes, use the **--all** flag.
+Volumes can be queried individually by providing their full name or a unique partial name.
## OPTIONS
diff --git a/docs/podman-volume-rm.1.md b/docs/podman-volume-rm.1.md
index fe047e7da..9a2fe8c99 100644
--- a/docs/podman-volume-rm.1.md
+++ b/docs/podman-volume-rm.1.md
@@ -4,14 +4,14 @@
podman\-volume\-rm - Remove one or more volumes
## SYNOPSIS
-**podman volume rm** [*options*]
+**podman volume rm** [*options*] *volume* [...]
## DESCRIPTION
-Removes one ore more volumes. Only volumes that are not being used will be removed.
+Removes one or more volumes. Only volumes that are not being used will be removed.
If a volume is being used by a container, an error will be returned unless the **--force**
-flag is being used. To remove all the volumes, use the **--all** flag.
-
+flag is being used. To remove all volumes, use the **--all** flag.
+Volumes can be removed individually by providing their full name or a unique partial name.
## OPTIONS
diff --git a/docs/tutorials/rootless_tutorial.md b/docs/tutorials/rootless_tutorial.md
index 91962fead..c98e74c96 100644
--- a/docs/tutorials/rootless_tutorial.md
+++ b/docs/tutorials/rootless_tutorial.md
@@ -22,6 +22,8 @@ The [slirp4netns](https://github.com/rootless-containers/slirp4netns) package pr
When using Podman in a rootless environment, it is recommended to use fuse-overlayfs rather than the VFS file system. Installing the fuse3-devel package gives Podman the dependencies it needs to install, build and use fuse-overlayfs in a rootless environment for you. The fuse-overlayfs project is also available from [GitHub](https://github.com/containers/fuse-overlayfs). This especially needs to be checked on Ubuntu distributions as fuse-overlayfs is not generally installed by default.
+If podman is installed before fuse-overlayfs, it may be necessary to change the `driver` option under `[storage]` to `"overlay"`.
+
### Enable user namespaces (on RHEL7 machines)
The number of user namespaces that are allowed on the system is specified in the file `/proc/sys/user/max_user_namespaces`. On most Linux platforms this is preset by default and no adjustment is necessary. However on RHEL7 machines a user with root privileges may need to set that to a reasonable value by using this command: `sysctl user.max_user_namespaces=15000`.
diff --git a/go.mod b/go.mod
index ff1668260..c2c148158 100644
--- a/go.mod
+++ b/go.mod
@@ -20,6 +20,7 @@ require (
github.com/containers/storage v1.13.2
github.com/coreos/bbolt v1.3.3 // indirect
github.com/coreos/etcd v3.3.13+incompatible // indirect
+ github.com/coreos/go-iptables v0.4.2 // indirect
github.com/coreos/go-semver v0.3.0 // indirect
github.com/coreos/go-systemd v0.0.0-20190620071333-e64a0ec8b42a
github.com/cri-o/ocicni v0.1.1-0.20190702175919-7762645d18ca
@@ -75,6 +76,7 @@ require (
github.com/pmezard/go-difflib v1.0.0
github.com/prometheus/common v0.6.0 // indirect
github.com/rogpeppe/fastuuid v1.1.0 // indirect
+ github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8 // indirect
github.com/seccomp/containers-golang v0.0.0-20190312124753-8ca8945ccf5f // indirect
github.com/seccomp/libseccomp-golang v0.9.1 // indirect
github.com/sirupsen/logrus v1.4.2
diff --git a/go.sum b/go.sum
index 7b865df23..14a869498 100644
--- a/go.sum
+++ b/go.sum
@@ -114,6 +114,8 @@ github.com/coreos/go-iptables v0.4.0 h1:wh4UbVs8DhLUbpyq97GLJDKrQMjEDD63T1xE4Crs
github.com/coreos/go-iptables v0.4.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
github.com/coreos/go-iptables v0.4.1 h1:TyEMaK2xD/EcB0385QcvX/OvI2XI7s4SJEI2EhZFfEU=
github.com/coreos/go-iptables v0.4.1/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
+github.com/coreos/go-iptables v0.4.2 h1:KH0EwId05JwWIfb96gWvkiT2cbuOu8ygqUaB+yPAwIg=
+github.com/coreos/go-iptables v0.4.2/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7 h1:u9SHYsPQNyt5tgDm3YN7+9dYrpK96E5wFilTFWIDZOM=
@@ -461,6 +463,8 @@ github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
+github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8 h1:2c1EFnZHIPCW8qKWgHMH/fX2PkSabFc5mrVzfUNdg5U=
+github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
github.com/seccomp/containers-golang v0.0.0-20180629143253-cdfdaa7543f4 h1:rOG9oHVIndNR14f3HRyBy9UPQYmIPniWqTU1TDdHhq4=
github.com/seccomp/containers-golang v0.0.0-20180629143253-cdfdaa7543f4/go.mod h1:f/98/SnvAzhAEFQJ3u836FePXvcbE8BS0YGMQNn4mhA=
github.com/seccomp/containers-golang v0.0.0-20190312124753-8ca8945ccf5f h1:OtU/w6sBKmXYaw2KEODxjcYi3oPSyyslhgGFgIJVGAI=
diff --git a/hack/man-page-checker b/hack/man-page-checker
index ab1921b86..30d0b2113 100755
--- a/hack/man-page-checker
+++ b/hack/man-page-checker
@@ -30,8 +30,11 @@ for md in *.1.md;do
# care only about the first.
name=$(egrep -A1 '^#* NAME' $md|tail -1|awk '{print $1}' | tr -d \\\\)
- if [ "$name" != "$(basename $md .1.md)" ]; then
- printf "%-32s NAME= %s\n" $md $name
+ expect=$(basename $md .1.md)
+ if [ "$name" != "$expect" ]; then
+ echo
+ printf "Inconsistent program NAME in %s:\n" $md
+ printf " NAME= %s (expected: %s)\n" $name $expect
rc=1
fi
done
@@ -57,8 +60,11 @@ for md in $(ls -1 *-*.1.md | grep -v remote);do
if [ "$desc" != "$parent_desc" ]; then
echo
+ printf "Inconsistent subcommand descriptions:\n"
printf " %-32s = '%s'\n" $md "$desc"
printf " %-32s = '%s'\n" $parent "$parent_desc"
+ printf "Please ensure that the NAME section of $md\n"
+ printf "matches the subcommand description in $parent\n"
rc=1
fi
done
@@ -82,16 +88,21 @@ for md in *.1.md;do
# arguments are bracketed by single ones.
# E.g. '**podman volume inspect** [*options*] *volume*...'
# Get the command name, and confirm that it matches the md file name.
- cmd=$(echo "$synopsis" | sed -e 's/\(.*\)\*\*.*/\1/' | tr -d \* | tr ' ' '-')
- if [ "$md" != "$cmd.1.md" ]; then
- printf " %-32s SYNOPSIS = %s\n" $md "$cmd"
+ cmd=$(echo "$synopsis" | sed -e 's/\(.*\)\*\*.*/\1/' | tr -d \*)
+ md_nodash=$(basename "$md" .1.md | tr '-' ' ')
+ if [ "$cmd" != "$md_nodash" -a "$cmd" != "podman-remote" ]; then
+ echo
+ printf "Inconsistent program name in SYNOPSIS in %s:\n" $md
+ printf " SYNOPSIS = %s (expected: '%s')\n" "$cmd" "$md_nodash"
rc=1
fi
# The convention is to use UPPER CASE in 'podman foo --help',
# but *lower case bracketed by asterisks* in the man page
if expr "$synopsis" : ".*[A-Z]" >/dev/null; then
- printf " %-32s UPPER-CASE '%s'\n" $md "$synopsis"
+ echo
+ printf "Inconsistent capitalization in SYNOPSIS in %s\n" $md
+ printf " '%s' should not contain upper-case characters\n" "$synopsis"
rc=1
fi
diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go
index 31b551b36..4e7f78f13 100644
--- a/libpod/boltdb_state.go
+++ b/libpod/boltdb_state.go
@@ -1735,6 +1735,74 @@ func (s *BoltState) Volume(name string) (*Volume, error) {
return volume, nil
}
+// LookupVolume locates a volume from a partial name.
+func (s *BoltState) LookupVolume(name string) (*Volume, error) {
+ if name == "" {
+ return nil, define.ErrEmptyID
+ }
+
+ if !s.valid {
+ return nil, define.ErrDBClosed
+ }
+
+ volName := []byte(name)
+
+ volume := new(Volume)
+ volume.config = new(VolumeConfig)
+
+ db, err := s.getDBCon()
+ if err != nil {
+ return nil, err
+ }
+ defer s.deferredCloseDBCon(db)
+
+ err = db.View(func(tx *bolt.Tx) error {
+ volBkt, err := getVolBucket(tx)
+ if err != nil {
+ return err
+ }
+
+ allVolsBkt, err := getAllVolsBucket(tx)
+ if err != nil {
+ return err
+ }
+
+ // Check for exact match on name
+ volDB := volBkt.Bucket(volName)
+ if volDB != nil {
+ return s.getVolumeFromDB(volName, volume, volBkt)
+ }
+
+ // No exact match. Search all names.
+ foundMatch := false
+ err = allVolsBkt.ForEach(func(checkName, checkName2 []byte) error {
+ if strings.HasPrefix(string(checkName), name) {
+ if foundMatch {
+ return errors.Wrapf(define.ErrVolumeExists, "more than one result for volume name %q", name)
+ }
+ foundMatch = true
+ volName = checkName
+ }
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+
+ if !foundMatch {
+ return errors.Wrapf(define.ErrNoSuchVolume, "no volume with name %q found", name)
+ }
+
+ return s.getVolumeFromDB(volName, volume, volBkt)
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ return volume, nil
+
+}
+
// HasVolume returns true if the given volume exists in the state, otherwise it returns false
func (s *BoltState) HasVolume(name string) (bool, error) {
if name == "" {
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index 1ca20a05d..ac565fdad 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -21,6 +21,7 @@ import (
"github.com/containers/storage"
"github.com/containers/storage/pkg/archive"
"github.com/containers/storage/pkg/mount"
+ "github.com/cyphar/filepath-securejoin"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate"
"github.com/opencontainers/selinux/go-selinux/label"
@@ -1242,43 +1243,82 @@ func (c *Container) mountStorage() (_ string, Err error) {
}()
}
+ // We need to mount the container before volumes - to ensure the copyup
+ // works properly.
+ mountPoint := c.config.Rootfs
+ if mountPoint == "" {
+ mountPoint, err = c.mount()
+ if err != nil {
+ return "", err
+ }
+ defer func() {
+ if Err != nil {
+ if err := c.unmount(false); err != nil {
+ logrus.Errorf("Error unmounting container %s after mount error: %v", c.ID(), err)
+ }
+ }
+ }()
+ }
+
// Request a mount of all named volumes
for _, v := range c.config.NamedVolumes {
- vol, err := c.runtime.state.Volume(v.Name)
+ vol, err := c.mountNamedVolume(v, mountPoint)
if err != nil {
- return "", errors.Wrapf(err, "error retrieving named volume %s for container %s", v.Name, c.ID())
+ return "", err
}
-
- if vol.needsMount() {
+ defer func() {
+ if Err == nil {
+ return
+ }
vol.lock.Lock()
- if err := vol.mount(); err != nil {
- vol.lock.Unlock()
- return "", errors.Wrapf(err, "error mounting volume %s for container %s", vol.Name(), c.ID())
+ if err := vol.unmount(false); err != nil {
+ logrus.Errorf("Error unmounting volume %s after error mounting container %s: %v", vol.Name(), c.ID(), err)
}
vol.lock.Unlock()
- defer func() {
- if Err == nil {
- return
- }
- vol.lock.Lock()
- if err := vol.unmount(false); err != nil {
- logrus.Errorf("Error unmounting volume %s after error mounting container %s: %v", vol.Name(), c.ID(), err)
- }
- vol.lock.Unlock()
- }()
- }
+ }()
}
- // TODO: generalize this mount code so it will mount every mount in ctr.config.Mounts
- mountPoint := c.config.Rootfs
- if mountPoint == "" {
- mountPoint, err = c.mount()
- if err != nil {
- return "", err
+ return mountPoint, nil
+}
+
+// Mount a single named volume into the container.
+// If necessary, copy up image contents into the volume.
+// Does not verify that the name volume given is actually present in container
+// config.
+// Returns the volume that was mounted.
+func (c *Container) mountNamedVolume(v *ContainerNamedVolume, mountpoint string) (*Volume, error) {
+ vol, err := c.runtime.state.Volume(v.Name)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error retrieving named volume %s for container %s", v.Name, c.ID())
+ }
+
+ vol.lock.Lock()
+ defer vol.lock.Unlock()
+ if vol.needsMount() {
+ if err := vol.mount(); err != nil {
+ return nil, errors.Wrapf(err, "error mounting volume %s for container %s", vol.Name(), c.ID())
}
}
+ // The volume may need a copy-up. Check the state.
+ if err := vol.update(); err != nil {
+ return nil, err
+ }
+ if vol.state.NeedsCopyUp {
+ logrus.Debugf("Copying up contents from container %s to volume %s", c.ID(), vol.Name())
+ srcDir, err := securejoin.SecureJoin(mountpoint, v.Dest)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error calculating destination path to copy up container %s volume %s", c.ID(), vol.Name())
+ }
+ if err := c.copyWithTarFromImage(srcDir, vol.MountPoint()); err != nil && !os.IsNotExist(err) {
+ return nil, errors.Wrapf(err, "error copying content from container %s into volume %s", c.ID(), vol.Name())
+ }
- return mountPoint, nil
+ vol.state.NeedsCopyUp = false
+ if err := vol.save(); err != nil {
+ return nil, err
+ }
+ }
+ return vol, nil
}
// cleanupStorage unmounts and cleans up the container's root filesystem
@@ -1622,15 +1662,11 @@ func (c *Container) unmount(force bool) error {
}
// this should be from chrootarchive.
-func (c *Container) copyWithTarFromImage(src, dest string) error {
- mountpoint, err := c.mount()
- if err != nil {
- return err
- }
+// Container MUST be mounted before calling.
+func (c *Container) copyWithTarFromImage(source, dest string) error {
a := archive.NewDefaultArchiver()
- source := filepath.Join(mountpoint, src)
- if err = c.copyOwnerAndPerms(source, dest); err != nil {
+ if err := c.copyOwnerAndPerms(source, dest); err != nil {
return err
}
return a.CopyWithTar(source, dest)
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index 5dc53582f..21429c09e 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -115,7 +115,9 @@ func (c *Container) prepare() (Err error) {
createErr = createNetNSErr
}
if mountStorageErr != nil {
- logrus.Errorf("Error preparing container %s: %v", c.ID(), createErr)
+ if createErr != nil {
+ logrus.Errorf("Error preparing container %s: %v", c.ID(), createErr)
+ }
createErr = mountStorageErr
}
diff --git a/libpod/in_memory_state.go b/libpod/in_memory_state.go
index 280ae5f5c..a008fcb39 100644
--- a/libpod/in_memory_state.go
+++ b/libpod/in_memory_state.go
@@ -459,6 +459,41 @@ func (s *InMemoryState) Volume(name string) (*Volume, error) {
return vol, nil
}
+// LookupVolume finds a volume from an unambiguous partial ID.
+func (s *InMemoryState) LookupVolume(name string) (*Volume, error) {
+ if name == "" {
+ return nil, define.ErrEmptyID
+ }
+
+ vol, ok := s.volumes[name]
+ if ok {
+ return vol, nil
+ }
+
+ // Alright, we've failed to find by full name. Now comes the expensive
+ // part.
+ // Loop through all volumes and look for matches.
+ var (
+ foundMatch bool
+ candidate *Volume
+ )
+ for volName, vol := range s.volumes {
+ if strings.HasPrefix(volName, name) {
+ if foundMatch {
+ return nil, errors.Wrapf(define.ErrVolumeExists, "more than one result for volume name %q", name)
+ }
+ candidate = vol
+ foundMatch = true
+ }
+ }
+
+ if !foundMatch {
+ return nil, errors.Wrapf(define.ErrNoSuchVolume, "no volume with name %q found", name)
+ }
+
+ return candidate, nil
+}
+
// HasVolume checks if a volume with the given name is present in the state
func (s *InMemoryState) HasVolume(name string) (bool, error) {
if name == "" {
diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go
index e421c09f0..bffce7bca 100644
--- a/libpod/runtime_ctr.go
+++ b/libpod/runtime_ctr.go
@@ -306,10 +306,6 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (c *Contai
return nil, errors.Wrapf(err, "error creating named volume %q", vol.Name)
}
- if err := ctr.copyWithTarFromImage(vol.Dest, newVol.MountPoint()); err != nil && !os.IsNotExist(err) {
- return nil, errors.Wrapf(err, "Failed to copy content into new volume mount %q", vol.Name)
- }
-
ctrNamedVolumes = append(ctrNamedVolumes, newVol)
}
diff --git a/libpod/runtime_volume.go b/libpod/runtime_volume.go
index 512e778a1..a6ab748e5 100644
--- a/libpod/runtime_volume.go
+++ b/libpod/runtime_volume.go
@@ -6,7 +6,6 @@ import (
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/events"
"github.com/pkg/errors"
- "github.com/sirupsen/logrus"
)
// Contains the public Runtime API for volumes
@@ -43,40 +42,25 @@ func (r *Runtime) RemoveVolume(ctx context.Context, v *Volume, force bool) error
return r.removeVolume(ctx, v, force)
}
-// RemoveVolumes removes a slice of volumes or all with a force bool
-func (r *Runtime) RemoveVolumes(ctx context.Context, volumes []string, all, force bool) ([]string, error) {
- var (
- vols []*Volume
- err error
- deletedVols []string
- )
- if all {
- vols, err = r.Volumes()
- if err != nil {
- return nil, errors.Wrapf(err, "unable to get all volumes")
- }
- } else {
- for _, i := range volumes {
- vol, err := r.GetVolume(i)
- if err != nil {
- return nil, err
- }
- vols = append(vols, vol)
- }
+// GetVolume retrieves a volume given its full name.
+func (r *Runtime) GetVolume(name string) (*Volume, error) {
+ r.lock.RLock()
+ defer r.lock.RUnlock()
+
+ if !r.valid {
+ return nil, define.ErrRuntimeStopped
}
- for _, vol := range vols {
- if err := r.RemoveVolume(ctx, vol, force); err != nil {
- return deletedVols, err
- }
- logrus.Debugf("removed volume %s", vol.Name())
- deletedVols = append(deletedVols, vol.Name())
+ vol, err := r.state.Volume(name)
+ if err != nil {
+ return nil, err
}
- return deletedVols, nil
+
+ return vol, nil
}
-// GetVolume retrieves a volume given its full name.
-func (r *Runtime) GetVolume(name string) (*Volume, error) {
+// LookupVolume retrieves a volume by unambigious partial name.
+func (r *Runtime) LookupVolume(name string) (*Volume, error) {
r.lock.RLock()
defer r.lock.RUnlock()
@@ -84,7 +68,7 @@ func (r *Runtime) GetVolume(name string) (*Volume, error) {
return nil, define.ErrRuntimeStopped
}
- vol, err := r.state.Volume(name)
+ vol, err := r.state.LookupVolume(name)
if err != nil {
return nil, err
}
diff --git a/libpod/state.go b/libpod/state.go
index db4667ad6..40080d2cc 100644
--- a/libpod/state.go
+++ b/libpod/state.go
@@ -190,6 +190,9 @@ type State interface {
// Volume accepts full name of volume
// If the volume doesn't exist, an error will be returned
Volume(volName string) (*Volume, error)
+ // LookupVolume accepts an unambiguous partial name or full name of a
+ // volume. Ambiguous names will result in an error.
+ LookupVolume(name string) (*Volume, error)
// HasVolume returns true if volName exists in the state,
// otherwise it returns false
HasVolume(volName string) (bool, error)
diff --git a/libpod/volume.go b/libpod/volume.go
index b4de3aedc..c4771bbb8 100644
--- a/libpod/volume.go
+++ b/libpod/volume.go
@@ -57,6 +57,13 @@ type VolumeState struct {
// On incrementing from 0, the volume will be mounted on the host.
// On decrementing to 0, the volume will be unmounted on the host.
MountCount uint `json:"mountCount"`
+ // NeedsCopyUp indicates that the next time the volume is mounted into
+ // a container, the container will "copy up" the contents of the
+ // mountpoint into the volume.
+ // This should only be done once. As such, this is set at container
+ // create time, then cleared after the copy up is done and never set
+ // again.
+ NeedsCopyUp bool `json:"notYetMounted,omitempty"`
}
// Name retrieves the volume's name
diff --git a/libpod/volume_internal.go b/libpod/volume_internal.go
index 2e886e1b0..42b935e7c 100644
--- a/libpod/volume_internal.go
+++ b/libpod/volume_internal.go
@@ -11,9 +11,11 @@ import (
func newVolume(runtime *Runtime) (*Volume, error) {
volume := new(Volume)
volume.config = new(VolumeConfig)
+ volume.state = new(VolumeState)
volume.runtime = runtime
volume.config.Labels = make(map[string]string)
volume.config.Options = make(map[string]string)
+ volume.state.NeedsCopyUp = true
return volume, nil
}
diff --git a/pkg/adapter/network.go b/pkg/adapter/network.go
index cf3a1dfdd..e4a160767 100644
--- a/pkg/adapter/network.go
+++ b/pkg/adapter/network.go
@@ -5,12 +5,13 @@ package adapter
import (
"encoding/json"
"fmt"
+ "github.com/containers/libpod/pkg/util"
"io/ioutil"
"os"
- "strings"
+ "path/filepath"
"text/tabwriter"
- "github.com/containernetworking/cni/libcni"
+ cniversion "github.com/containernetworking/cni/pkg/version"
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/pkg/network"
"github.com/pkg/errors"
@@ -51,7 +52,7 @@ func (r *LocalRuntime) NetworkList(cli *cliconfig.NetworkListValues) error {
return err
}
for _, cniNetwork := range networks {
- if _, err := fmt.Fprintf(w, "%s\t%s\t%s\n", cniNetwork.Name, cniNetwork.CNIVersion, getCNIPlugins(cniNetwork)); err != nil {
+ if _, err := fmt.Fprintf(w, "%s\t%s\t%s\n", cniNetwork.Name, cniNetwork.CNIVersion, network.GetCNIPlugins(cniNetwork)); err != nil {
return err
}
}
@@ -64,12 +65,8 @@ func (r *LocalRuntime) NetworkInspect(cli *cliconfig.NetworkInspectValues) error
var (
rawCNINetworks []map[string]interface{}
)
- cniConfigPath, err := getCNIConfDir(r)
- if err != nil {
- return err
- }
for _, name := range cli.InputArgs {
- b, err := readRawCNIConfByName(name, cniConfigPath)
+ b, err := network.ReadRawCNIConfByName(name)
if err != nil {
return err
}
@@ -89,12 +86,8 @@ func (r *LocalRuntime) NetworkInspect(cli *cliconfig.NetworkInspectValues) error
// NetworkRemove deletes one or more CNI networks
func (r *LocalRuntime) NetworkRemove(cli *cliconfig.NetworkRmValues) error {
- cniConfigPath, err := getCNIConfDir(r)
- if err != nil {
- return err
- }
for _, name := range cli.InputArgs {
- cniPath, err := getCNIConfigPathByName(name, cniConfigPath)
+ cniPath, err := network.GetCNIConfigPathByName(name)
if err != nil {
return err
}
@@ -106,42 +99,108 @@ func (r *LocalRuntime) NetworkRemove(cli *cliconfig.NetworkRmValues) error {
return nil
}
-// getCNIConfigPathByName finds a CNI network by name and
-// returns its configuration file path
-func getCNIConfigPathByName(name, cniConfigPath string) (string, error) {
- files, err := libcni.ConfFiles(cniConfigPath, []string{".conflist"})
+// NetworkCreate creates a CNI network
+func (r *LocalRuntime) NetworkCreate(cli *cliconfig.NetworkCreateValues) (string, error) {
+ var (
+ err error
+ )
+
+ isGateway := true
+ ipMasq := true
+ subnet := &cli.Network
+ ipRange := cli.IPRange
+
+ // if range is provided, make sure it is "in" network
+ if cli.IsSet("subnet") {
+ // if network is provided, does it conflict with existing CNI or live networks
+ err = network.ValidateUserNetworkIsAvailable(subnet)
+ } else {
+ // if no network is provided, figure out network
+ subnet, err = network.GetFreeNetwork()
+ }
if err != nil {
return "", err
}
- for _, confFile := range files {
- conf, err := libcni.ConfListFromFile(confFile)
+
+ gateway := cli.Gateway
+ if gateway == nil {
+ // if no gateway is provided, provide it as first ip of network
+ gateway = network.CalcGatewayIP(subnet)
+ }
+ // if network is provided and if gateway is provided, make sure it is "in" network
+ if cli.IsSet("subnet") && cli.IsSet("gateway") {
+ if !subnet.Contains(gateway) {
+ return "", errors.Errorf("gateway %s is not in valid for subnet %s", gateway.String(), subnet.String())
+ }
+ }
+ if cli.Internal {
+ isGateway = false
+ ipMasq = false
+ }
+
+ // if a range is given, we need to ensure it is "in" the network range.
+ if cli.IsSet("ip-range") {
+ if !cli.IsSet("subnet") {
+ return "", errors.New("you must define a subnet range to define an ip-range")
+ }
+ firstIP, err := network.FirstIPInSubnet(&cli.IPRange)
+ if err != nil {
+ return "", err
+ }
+ lastIP, err := network.LastIPInSubnet(&cli.IPRange)
if err != nil {
return "", err
}
- if conf.Name == name {
- return confFile, nil
+ if !subnet.Contains(firstIP) || !subnet.Contains(lastIP) {
+ return "", errors.Errorf("the ip range %s does not fall within the subnet range %s", cli.IPRange.String(), subnet.String())
+ }
+ }
+ bridgeDeviceName, err := network.GetFreeDeviceName()
+ if err != nil {
+ return "", err
+ }
+ // If no name is given, we give the name of the bridge device
+ name := bridgeDeviceName
+ if len(cli.InputArgs) > 0 {
+ name = cli.InputArgs[0]
+ netNames, err := network.GetNetworkNamesFromFileSystem()
+ if err != nil {
+ return "", err
+ }
+ if util.StringInSlice(name, netNames) {
+ return "", errors.Errorf("the network name %s is already used", name)
}
}
- return "", errors.Errorf("unable to find network configuration for %s", name)
-}
-// readRawCNIConfByName reads the raw CNI configuration for a CNI
-// network by name
-func readRawCNIConfByName(name, cniConfigPath string) ([]byte, error) {
- confFile, err := getCNIConfigPathByName(name, cniConfigPath)
+ ncList := network.NewNcList(name, cniversion.Current())
+ var plugins []network.CNIPlugins
+ var routes []network.IPAMRoute
+
+ defaultRoute, err := network.NewIPAMDefaultRoute()
if err != nil {
- return nil, err
+ return "", err
+ }
+ routes = append(routes, defaultRoute)
+ ipamConfig, err := network.NewIPAMHostLocalConf(subnet, routes, ipRange, gateway)
+ if err != nil {
+ return "", err
}
- b, err := ioutil.ReadFile(confFile)
- return b, err
-}
-// getCNIPlugins returns a list of plugins that a given network
-// has in the form of a string
-func getCNIPlugins(list *libcni.NetworkConfigList) string {
- var plugins []string
- for _, plug := range list.Plugins {
- plugins = append(plugins, plug.Network.Type)
+ // TODO need to iron out the role of isDefaultGW and IPMasq
+ bridge := network.NewHostLocalBridge(bridgeDeviceName, isGateway, false, ipMasq, ipamConfig)
+ plugins = append(plugins, bridge)
+ plugins = append(plugins, network.NewPortMapPlugin())
+ plugins = append(plugins, network.NewFirewallPlugin())
+ ncList["plugins"] = plugins
+ b, err := json.MarshalIndent(ncList, "", " ")
+ if err != nil {
+ return "", err
+ }
+ cniConfigPath, err := getCNIConfDir(r)
+ if err != nil {
+ return "", err
}
- return strings.Join(plugins, ",")
+ cniPathName := filepath.Join(cniConfigPath, fmt.Sprintf("%s.conflist", name))
+ err = ioutil.WriteFile(cniPathName, b, 0644)
+ return cniPathName, err
}
diff --git a/pkg/adapter/runtime.go b/pkg/adapter/runtime.go
index dd15e1d15..fd6587505 100644
--- a/pkg/adapter/runtime.go
+++ b/pkg/adapter/runtime.go
@@ -196,8 +196,8 @@ func (r *LocalRuntime) CreateVolume(ctx context.Context, c *cliconfig.VolumeCrea
}
// RemoveVolumes is a wrapper to remove volumes
-func (r *LocalRuntime) RemoveVolumes(ctx context.Context, c *cliconfig.VolumeRmValues) ([]string, error) {
- return r.Runtime.RemoveVolumes(ctx, c.InputArgs, c.All, c.Force)
+func (r *LocalRuntime) RemoveVolumes(ctx context.Context, c *cliconfig.VolumeRmValues) ([]string, map[string]error, error) {
+ return shared.SharedRemoveVolumes(ctx, r.Runtime, c.InputArgs, c.All, c.Force)
}
// Push is a wrapper to push an image to a registry
@@ -220,7 +220,7 @@ func (r *LocalRuntime) InspectVolumes(ctx context.Context, c *cliconfig.VolumeIn
volumes, err = r.GetAllVolumes()
} else {
for _, v := range c.InputArgs {
- vol, err := r.GetVolume(v)
+ vol, err := r.LookupVolume(v)
if err != nil {
return nil, err
}
diff --git a/pkg/adapter/runtime_remote.go b/pkg/adapter/runtime_remote.go
index 718a6d542..f079b914a 100644
--- a/pkg/adapter/runtime_remote.go
+++ b/pkg/adapter/runtime_remote.go
@@ -622,13 +622,18 @@ func (r *LocalRuntime) CreateVolume(ctx context.Context, c *cliconfig.VolumeCrea
}
// RemoveVolumes removes volumes over a varlink connection for the remote client
-func (r *LocalRuntime) RemoveVolumes(ctx context.Context, c *cliconfig.VolumeRmValues) ([]string, error) {
+func (r *LocalRuntime) RemoveVolumes(ctx context.Context, c *cliconfig.VolumeRmValues) ([]string, map[string]error, error) {
rmOpts := iopodman.VolumeRemoveOpts{
All: c.All,
Force: c.Force,
Volumes: c.InputArgs,
}
- return iopodman.VolumeRemove().Call(r.Conn, rmOpts)
+ success, failures, err := iopodman.VolumeRemove().Call(r.Conn, rmOpts)
+ stringsToErrors := make(map[string]error)
+ for k, v := range failures {
+ stringsToErrors[k] = errors.New(v)
+ }
+ return success, stringsToErrors, err
}
func (r *LocalRuntime) Push(ctx context.Context, srcName, destination, manifestMIMEType, authfile, digestfile, signaturePolicyPath string, writer io.Writer, forceCompress bool, signingOptions image.SigningOptions, dockerRegistryOptions *image.DockerRegistryOptions, additionalDockerArchiveTags []reference.NamedTagged) error {
diff --git a/pkg/network/config.go b/pkg/network/config.go
index d282f66b6..7eaa83833 100644
--- a/pkg/network/config.go
+++ b/pkg/network/config.go
@@ -1,4 +1,99 @@
package network
-// CNIConfigDir is the path where CNI config files exist
-const CNIConfigDir = "/etc/cni/net.d"
+import (
+ "encoding/json"
+ "net"
+)
+
+// TODO once the libpod.conf file stuff is worked out, this should be modified
+// to honor defines in the libpod.conf as well as overrides?
+
+const (
+ // CNIConfigDir is the path where CNI config files exist
+ CNIConfigDir = "/etc/cni/net.d"
+ // CNIDeviceName is the default network device name and in
+ // reality should have an int appended to it (cni-podman4)
+ CNIDeviceName = "cni-podman"
+)
+
+// GetDefaultPodmanNetwork outputs the default network for podman
+func GetDefaultPodmanNetwork() (*net.IPNet, error) {
+ _, n, err := net.ParseCIDR("10.88.1.0/24")
+ return n, err
+}
+
+// CNIPlugins is a way of marshalling a CNI network configuration to disk
+type CNIPlugins interface {
+ Bytes() ([]byte, error)
+}
+
+// HostLocalBridge describes a configuration for a bridge plugin
+// https://github.com/containernetworking/plugins/tree/master/plugins/main/bridge#network-configuration-reference
+type HostLocalBridge struct {
+ PluginType string `json:"type"`
+ BrName string `json:"bridge,omitempty"`
+ IsGW bool `json:"isGateway"`
+ IsDefaultGW bool `json:"isDefaultGateway,omitempty"`
+ ForceAddress bool `json:"forceAddress,omitempty"`
+ IPMasq bool `json:"ipMasq,omitempty"`
+ MTU int `json:"mtu,omitempty"`
+ HairpinMode bool `json:"hairpinMode,omitempty"`
+ PromiscMode bool `json:"promiscMode,omitempty"`
+ Vlan int `json:"vlan,omitempty"`
+ IPAM IPAMHostLocalConf `json:"ipam"`
+}
+
+// Bytes outputs []byte
+func (h *HostLocalBridge) Bytes() ([]byte, error) {
+ return json.MarshalIndent(h, "", "\t")
+}
+
+// IPAMHostLocalConf describes an IPAM configuration
+// https://github.com/containernetworking/plugins/tree/master/plugins/ipam/host-local#network-configuration-reference
+type IPAMHostLocalConf struct {
+ PluginType string `json:"type"`
+ Routes []IPAMRoute `json:"routes,omitempty"`
+ ResolveConf string `json:"resolveConf,omitempty"`
+ DataDir string `json:"dataDir,omitempty"`
+ Ranges [][]IPAMLocalHostRangeConf `json:"ranges,omitempty"`
+}
+
+// IPAMLocalHostRangeConf describes the new style IPAM ranges
+type IPAMLocalHostRangeConf struct {
+ Subnet string `json:"subnet"`
+ RangeStart string `json:"rangeStart,omitempty"`
+ RangeEnd string `json:"rangeEnd,omitempty"`
+ Gateway string `json:"gateway,omitempty"`
+}
+
+// Bytes outputs the configuration as []byte
+func (i IPAMHostLocalConf) Bytes() ([]byte, error) {
+ return json.MarshalIndent(i, "", "\t")
+}
+
+// IPAMRoute describes a route in an ipam config
+type IPAMRoute struct {
+ Dest string `json:"dst"`
+}
+
+// PortMapConfig describes the default portmapping config
+type PortMapConfig struct {
+ PluginType string `json:"type"`
+ Capabilities map[string]bool `json:"capabilities"`
+}
+
+// Bytes outputs the configuration as []byte
+func (p PortMapConfig) Bytes() ([]byte, error) {
+ return json.MarshalIndent(p, "", "\t")
+}
+
+// FirewallConfig describes the firewall plugin
+type FirewallConfig struct {
+ PluginType string `json:"type"`
+ Backend string `json:"backend"`
+}
+
+// Bytes outputs the configuration as []byte
+func (f FirewallConfig) Bytes() ([]byte, error) {
+ return json.MarshalIndent(f, "", "\t")
+}
diff --git a/pkg/network/devices.go b/pkg/network/devices.go
new file mode 100644
index 000000000..26101b6f7
--- /dev/null
+++ b/pkg/network/devices.go
@@ -0,0 +1,41 @@
+package network
+
+import (
+ "fmt"
+ "github.com/containers/libpod/pkg/util"
+
+ "github.com/sirupsen/logrus"
+)
+
+// GetFreeDeviceName returns a device name that is unused; used when no network
+// name is provided by user
+func GetFreeDeviceName() (string, error) {
+ var (
+ deviceNum uint
+ deviceName string
+ )
+ networkNames, err := GetNetworkNamesFromFileSystem()
+ if err != nil {
+ return "", err
+ }
+ liveNetworksNames, err := GetLiveNetworkNames()
+ if err != nil {
+ return "", err
+ }
+ for {
+ deviceName = fmt.Sprintf("%s%d", CNIDeviceName, deviceNum)
+ logrus.Debugf("checking if device name %s exists in other cni networks", deviceName)
+ if util.StringInSlice(deviceName, networkNames) {
+ deviceNum++
+ continue
+ }
+ logrus.Debugf("checking if device name %s exists in live networks", deviceName)
+ if !util.StringInSlice(deviceName, liveNetworksNames) {
+ break
+ }
+ // TODO Still need to check the bridge names for a conflict but I dont know
+ // how to get them yet!
+ deviceNum++
+ }
+ return deviceName, nil
+}
diff --git a/pkg/network/files.go b/pkg/network/files.go
new file mode 100644
index 000000000..80fde5e17
--- /dev/null
+++ b/pkg/network/files.go
@@ -0,0 +1,107 @@
+package network
+
+import (
+ "encoding/json"
+ "io/ioutil"
+ "sort"
+ "strings"
+
+ "github.com/containernetworking/cni/libcni"
+ "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
+ "github.com/pkg/errors"
+)
+
+// LoadCNIConfsFromDir loads all the CNI configurations from a dir
+func LoadCNIConfsFromDir(dir string) ([]*libcni.NetworkConfigList, error) {
+ var configs []*libcni.NetworkConfigList
+ files, err := libcni.ConfFiles(dir, []string{".conflist"})
+ if err != nil {
+ return nil, err
+ }
+ sort.Strings(files)
+
+ for _, confFile := range files {
+ conf, err := libcni.ConfListFromFile(confFile)
+ if err != nil {
+ return nil, err
+ }
+ configs = append(configs, conf)
+ }
+ return configs, nil
+}
+
+// GetCNIConfigPathByName finds a CNI network by name and
+// returns its configuration file path
+func GetCNIConfigPathByName(name string) (string, error) {
+ files, err := libcni.ConfFiles(CNIConfigDir, []string{".conflist"})
+ if err != nil {
+ return "", err
+ }
+ for _, confFile := range files {
+ conf, err := libcni.ConfListFromFile(confFile)
+ if err != nil {
+ return "", err
+ }
+ if conf.Name == name {
+ return confFile, nil
+ }
+ }
+ return "", errors.Errorf("unable to find network configuration for %s", name)
+}
+
+// ReadRawCNIConfByName reads the raw CNI configuration for a CNI
+// network by name
+func ReadRawCNIConfByName(name string) ([]byte, error) {
+ confFile, err := GetCNIConfigPathByName(name)
+ if err != nil {
+ return nil, err
+ }
+ b, err := ioutil.ReadFile(confFile)
+ return b, err
+}
+
+// GetCNIPlugins returns a list of plugins that a given network
+// has in the form of a string
+func GetCNIPlugins(list *libcni.NetworkConfigList) string {
+ var plugins []string
+ for _, plug := range list.Plugins {
+ plugins = append(plugins, plug.Network.Type)
+ }
+ return strings.Join(plugins, ",")
+}
+
+// GetNetworksFromFilesystem gets all the networks from the cni configuration
+// files
+func GetNetworksFromFilesystem() ([]*allocator.Net, error) {
+ var cniNetworks []*allocator.Net
+ networks, err := LoadCNIConfsFromDir(CNIConfigDir)
+ if err != nil {
+ return nil, err
+ }
+ for _, n := range networks {
+ for _, cniplugin := range n.Plugins {
+ if cniplugin.Network.Type == "bridge" {
+ ipamConf := allocator.Net{}
+ if err := json.Unmarshal(cniplugin.Bytes, &ipamConf); err != nil {
+ return nil, err
+ }
+ cniNetworks = append(cniNetworks, &ipamConf)
+ }
+ }
+ }
+ return cniNetworks, nil
+}
+
+// GetNetworkNamesFromFileSystem gets all the names from the cni network
+// configuration files
+func GetNetworkNamesFromFileSystem() ([]string, error) {
+ var networkNames []string
+ networks, err := LoadCNIConfsFromDir(CNIConfigDir)
+ if err != nil {
+ return nil, err
+ }
+ for _, n := range networks {
+ networkNames = append(networkNames, n.Name)
+ }
+ return networkNames, nil
+}
diff --git a/pkg/network/ip.go b/pkg/network/ip.go
new file mode 100644
index 000000000..1798cd939
--- /dev/null
+++ b/pkg/network/ip.go
@@ -0,0 +1,14 @@
+package network
+
+import (
+ "net"
+
+ "github.com/containernetworking/plugins/pkg/ip"
+)
+
+// CalcGatewayIP takes a network and returns the first IP in it.
+func CalcGatewayIP(ipn *net.IPNet) net.IP {
+ // taken from cni bridge plugin as it is not exported
+ nid := ipn.IP.Mask(ipn.Mask)
+ return ip.NextIP(nid)
+}
diff --git a/pkg/network/netconflist.go b/pkg/network/netconflist.go
new file mode 100644
index 000000000..c3b11b409
--- /dev/null
+++ b/pkg/network/netconflist.go
@@ -0,0 +1,113 @@
+package network
+
+import (
+ "net"
+)
+
+// NcList describes a generic map
+type NcList map[string]interface{}
+
+// NewNcList creates a generic map of values with string
+// keys and adds in version and network name
+func NewNcList(name, version string) NcList {
+ n := NcList{}
+ n["cniVersion"] = version
+ n["name"] = name
+ return n
+}
+
+// NewHostLocalBridge creates a new LocalBridge for host-local
+func NewHostLocalBridge(name string, isGateWay, isDefaultGW, ipMasq bool, ipamConf IPAMHostLocalConf) *HostLocalBridge {
+ hostLocalBridge := HostLocalBridge{
+ PluginType: "bridge",
+ BrName: name,
+ IPMasq: ipMasq,
+ IPAM: ipamConf,
+ }
+ if isGateWay {
+ hostLocalBridge.IsGW = true
+ }
+ if isDefaultGW {
+ hostLocalBridge.IsDefaultGW = true
+ }
+ return &hostLocalBridge
+}
+
+// NewIPAMHostLocalConf creates a new IPAMHostLocal configfuration
+func NewIPAMHostLocalConf(subnet *net.IPNet, routes []IPAMRoute, ipRange net.IPNet, gw net.IP) (IPAMHostLocalConf, error) {
+ var ipamRanges [][]IPAMLocalHostRangeConf
+ ipamConf := IPAMHostLocalConf{
+ PluginType: "host-local",
+ Routes: routes,
+ // Possible future support ? Leaving for clues
+ //ResolveConf: "",
+ //DataDir: ""
+ }
+ IPAMRange, err := newIPAMLocalHostRange(subnet, &ipRange, &gw)
+ if err != nil {
+ return ipamConf, err
+ }
+ ipamRanges = append(ipamRanges, IPAMRange)
+ ipamConf.Ranges = ipamRanges
+ return ipamConf, nil
+}
+
+func newIPAMLocalHostRange(subnet *net.IPNet, ipRange *net.IPNet, gw *net.IP) ([]IPAMLocalHostRangeConf, error) { //nolint:interfacer
+ var ranges []IPAMLocalHostRangeConf
+ hostRange := IPAMLocalHostRangeConf{
+ Subnet: subnet.String(),
+ }
+ // an user provided a range, we add it here
+ if ipRange.IP != nil {
+ first, err := FirstIPInSubnet(ipRange)
+ if err != nil {
+ return nil, err
+ }
+ last, err := LastIPInSubnet(ipRange)
+ if err != nil {
+ return nil, err
+ }
+ hostRange.RangeStart = first.String()
+ hostRange.RangeEnd = last.String()
+ }
+ if gw != nil {
+ hostRange.Gateway = gw.String()
+ }
+ ranges = append(ranges, hostRange)
+ return ranges, nil
+}
+
+// NewIPAMRoute creates a new IPAM route configuration
+func NewIPAMRoute(r *net.IPNet) IPAMRoute { //nolint:interfacer
+ return IPAMRoute{Dest: r.String()}
+}
+
+// NewIPAMDefaultRoute creates a new IPAMDefault route of
+// 0.0.0.0/0
+func NewIPAMDefaultRoute() (IPAMRoute, error) {
+ _, n, err := net.ParseCIDR("0.0.0.0/0")
+ if err != nil {
+ return IPAMRoute{}, err
+ }
+ return NewIPAMRoute(n), nil
+}
+
+// NewPortMapPlugin creates a predefined, default portmapping
+// configuration
+func NewPortMapPlugin() PortMapConfig {
+ caps := make(map[string]bool)
+ caps["portMappings"] = true
+ p := PortMapConfig{
+ PluginType: "portmap",
+ Capabilities: caps,
+ }
+ return p
+}
+
+// NewFirewallPlugin creates a generic firewall plugin
+func NewFirewallPlugin() FirewallConfig {
+ return FirewallConfig{
+ PluginType: "firewall",
+ Backend: "iptables",
+ }
+}
diff --git a/pkg/network/network.go b/pkg/network/network.go
index 9d04340a3..b241a66c0 100644
--- a/pkg/network/network.go
+++ b/pkg/network/network.go
@@ -1,26 +1,150 @@
package network
import (
- "sort"
+ "github.com/containers/libpod/pkg/util"
+ "net"
- "github.com/containernetworking/cni/libcni"
+ "github.com/containernetworking/cni/pkg/types"
+ "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
)
-// LoadCNIConfsFromDir loads all the CNI configurations from a dir
-func LoadCNIConfsFromDir(dir string) ([]*libcni.NetworkConfigList, error) {
- var configs []*libcni.NetworkConfigList
- files, err := libcni.ConfFiles(dir, []string{".conflist"})
+// SupportedNetworkDrivers describes the list of supported drivers
+var SupportedNetworkDrivers = []string{"bridge"}
+
+// IsSupportedDriver checks if the user provided driver is supported
+func IsSupportedDriver(driver string) error {
+ if util.StringInSlice(driver, SupportedNetworkDrivers) {
+ return nil
+ }
+ return errors.Errorf("driver '%s' is not supported", driver)
+}
+
+// GetLiveNetworks returns a slice of networks representing what the system
+// has defined as network interfaces
+func GetLiveNetworks() ([]*net.IPNet, error) {
+ var nets []*net.IPNet
+ addrs, err := net.InterfaceAddrs()
+ if err != nil {
+ return nil, err
+ }
+ for _, address := range addrs {
+ _, n, err := net.ParseCIDR(address.String())
+ if err != nil {
+ return nil, err
+ }
+ nets = append(nets, n)
+ }
+ return nets, nil
+}
+
+// GetLiveNetworkNames returns a list of network interfaces on the system
+func GetLiveNetworkNames() ([]string, error) {
+ var interfaceNames []string
+ liveInterfaces, err := net.Interfaces()
if err != nil {
return nil, err
}
- sort.Strings(files)
+ for _, i := range liveInterfaces {
+ interfaceNames = append(interfaceNames, i.Name)
+ }
+ return interfaceNames, nil
+}
- for _, confFile := range files {
- conf, err := libcni.ConfListFromFile(confFile)
+// GetFreeNetwork looks for a free network according to existing cni configuration
+// files and network interfaces.
+func GetFreeNetwork() (*net.IPNet, error) {
+ networks, err := GetNetworksFromFilesystem()
+ if err != nil {
+ return nil, err
+ }
+ liveNetworks, err := GetLiveNetworks()
+ if err != nil {
+ return nil, err
+ }
+ nextNetwork, err := GetDefaultPodmanNetwork()
+ if err != nil {
+ return nil, err
+ }
+ logrus.Debugf("default network is %s", nextNetwork.String())
+ for {
+ newNetwork, err := NextSubnet(nextNetwork)
if err != nil {
return nil, err
}
- configs = append(configs, conf)
+ logrus.Debugf("checking if network %s intersects with other cni networks", nextNetwork.String())
+ if intersectsConfig, _ := networkIntersectsWithNetworks(newNetwork, allocatorToIPNets(networks)); intersectsConfig {
+ logrus.Debugf("network %s is already being used by a cni configuration", nextNetwork.String())
+ nextNetwork = newNetwork
+ continue
+ }
+ logrus.Debugf("checking if network %s intersects with any network interfaces", nextNetwork.String())
+ if intersectsLive, _ := networkIntersectsWithNetworks(newNetwork, liveNetworks); !intersectsLive {
+ break
+ }
+ logrus.Debugf("network %s is being used by a network interface", nextNetwork.String())
+ nextNetwork = newNetwork
+ }
+ return nextNetwork, nil
+}
+
+func allocatorToIPNets(networks []*allocator.Net) []*net.IPNet {
+ var nets []*net.IPNet
+ for _, network := range networks {
+ if len(network.IPAM.Ranges) > 0 {
+ // this is the new IPAM range style
+ // append each subnet from ipam the rangeset
+ for _, r := range network.IPAM.Ranges[0] {
+ nets = append(nets, newIPNetFromSubnet(r.Subnet))
+ }
+ } else {
+ // looks like the old, deprecated style
+ nets = append(nets, newIPNetFromSubnet(network.IPAM.Subnet))
+ }
+ }
+ return nets
+}
+
+func newIPNetFromSubnet(subnet types.IPNet) *net.IPNet {
+ n := net.IPNet{
+ IP: subnet.IP,
+ Mask: subnet.Mask,
+ }
+ return &n
+}
+
+func networkIntersectsWithNetworks(n *net.IPNet, networklist []*net.IPNet) (bool, *net.IPNet) {
+ for _, nw := range networklist {
+ if networkIntersect(n, nw) {
+ return true, nw
+ }
+ }
+ return false, nil
+}
+
+func networkIntersect(n1, n2 *net.IPNet) bool {
+ return n2.Contains(n1.IP) || n1.Contains(n2.IP)
+}
+
+// ValidateUserNetworkIsAvailable returns via an error if a network is available
+// to be used
+func ValidateUserNetworkIsAvailable(userNet *net.IPNet) error {
+ networks, err := GetNetworksFromFilesystem()
+ if err != nil {
+ return err
+ }
+ liveNetworks, err := GetLiveNetworks()
+ if err != nil {
+ return err
+ }
+ logrus.Debugf("checking if network %s exists in cni networks", userNet.String())
+ if intersectsConfig, _ := networkIntersectsWithNetworks(userNet, allocatorToIPNets(networks)); intersectsConfig {
+ return errors.Errorf("network %s is already being used by a cni configuration", userNet.String())
+ }
+ logrus.Debugf("checking if network %s exists in any network interfaces", userNet.String())
+ if intersectsLive, _ := networkIntersectsWithNetworks(userNet, liveNetworks); intersectsLive {
+ return errors.Errorf("network %s is being used by a network interface", userNet.String())
}
- return configs, nil
+ return nil
}
diff --git a/pkg/network/network_test.go b/pkg/network/network_test.go
new file mode 100644
index 000000000..dbffc33ad
--- /dev/null
+++ b/pkg/network/network_test.go
@@ -0,0 +1,34 @@
+package network
+
+import (
+ "net"
+ "testing"
+)
+
+func parseCIDR(n string) *net.IPNet {
+ _, parsedNet, _ := net.ParseCIDR(n)
+ return parsedNet
+}
+
+func Test_networkIntersect(t *testing.T) {
+ type args struct {
+ n1 *net.IPNet
+ n2 *net.IPNet
+ }
+ tests := []struct {
+ name string
+ args args
+ want bool
+ }{
+ {"16 and 24 intersects", args{n1: parseCIDR("192.168.0.0/16"), n2: parseCIDR("192.168.1.0/24")}, true},
+ {"24 and 25 intersects", args{n1: parseCIDR("192.168.1.0/24"), n2: parseCIDR("192.168.1.0/25")}, true},
+ {"Two 24s", args{n1: parseCIDR("192.168.1.0/24"), n2: parseCIDR("192.168.2.0/24")}, false},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := networkIntersect(tt.args.n1, tt.args.n2); got != tt.want {
+ t.Errorf("networkIntersect() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/pkg/network/subnet.go b/pkg/network/subnet.go
new file mode 100644
index 000000000..82ab9a8c8
--- /dev/null
+++ b/pkg/network/subnet.go
@@ -0,0 +1,78 @@
+package network
+
+/*
+ The code in this was kindly contributed by Dan Williams(dcbw@redhat.com). Many thanks
+ for his contributions.
+*/
+
+import (
+ "fmt"
+ "net"
+)
+
+func incByte(subnet *net.IPNet, idx int, shift uint) error {
+ if idx < 0 {
+ return fmt.Errorf("no more subnets left")
+ }
+ if subnet.IP[idx] == 255 {
+ subnet.IP[idx] = 0
+ return incByte(subnet, idx-1, 0)
+ }
+ subnet.IP[idx] += (1 << shift)
+ return nil
+}
+
+// NextSubnet returns subnet incremented by 1
+func NextSubnet(subnet *net.IPNet) (*net.IPNet, error) {
+ newSubnet := &net.IPNet{
+ IP: subnet.IP,
+ Mask: subnet.Mask,
+ }
+ ones, bits := newSubnet.Mask.Size()
+ if ones == 0 {
+ return nil, fmt.Errorf("%s has only one subnet", subnet.String())
+ }
+ zeroes := uint(bits - ones)
+ shift := zeroes % 8
+ idx := ones/8 - 1
+ if idx < 0 {
+ idx = 0
+ }
+ if err := incByte(newSubnet, idx, shift); err != nil {
+ return nil, err
+ }
+ return newSubnet, nil
+}
+
+// LastIPInSubnet gets the last IP in a subnet
+func LastIPInSubnet(addr *net.IPNet) (net.IP, error) { //nolint:interfacer
+ // re-parse to ensure clean network address
+ _, cidr, err := net.ParseCIDR(addr.String())
+ if err != nil {
+ return nil, err
+ }
+
+ ones, bits := cidr.Mask.Size()
+ if ones == bits {
+ return FirstIPInSubnet(cidr)
+ }
+ hostStart := ones / 8
+ // Handle the first host byte
+ cidr.IP[hostStart] |= (0xff & cidr.Mask[hostStart])
+ // Fill the rest with ones
+ for i := hostStart; i < len(cidr.IP); i++ {
+ cidr.IP[i] = 0xff
+ }
+ return cidr.IP, nil
+}
+
+// FirstIPInSubnet gets the first IP in a subnet
+func FirstIPInSubnet(addr *net.IPNet) (net.IP, error) { //nolint:interfacer
+ // re-parse to ensure clean network address
+ _, cidr, err := net.ParseCIDR(addr.String())
+ if err != nil {
+ return nil, err
+ }
+ cidr.IP[len(cidr.IP)-1]++
+ return cidr.IP, nil
+}
diff --git a/pkg/network/subnet_test.go b/pkg/network/subnet_test.go
new file mode 100644
index 000000000..6ecfd2d17
--- /dev/null
+++ b/pkg/network/subnet_test.go
@@ -0,0 +1,34 @@
+package network
+
+import (
+ "net"
+ "reflect"
+ "testing"
+)
+
+func TestNextSubnet(t *testing.T) {
+ type args struct {
+ subnet *net.IPNet
+ }
+ tests := []struct {
+ name string
+ args args
+ want *net.IPNet
+ wantErr bool
+ }{
+ {"class b", args{subnet: parseCIDR("192.168.0.0/16")}, parseCIDR("192.169.0.0/16"), false},
+ {"class c", args{subnet: parseCIDR("192.168.1.0/24")}, parseCIDR("192.168.2.0/24"), false},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := NextSubnet(tt.args.subnet)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("NextSubnet() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("NextSubnet() got = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/pkg/varlinkapi/volumes.go b/pkg/varlinkapi/volumes.go
index 6dd86d831..b41eb5086 100644
--- a/pkg/varlinkapi/volumes.go
+++ b/pkg/varlinkapi/volumes.go
@@ -3,6 +3,7 @@
package varlinkapi
import (
+ "github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/cmd/podman/varlink"
"github.com/containers/libpod/libpod"
)
@@ -32,11 +33,16 @@ func (i *LibpodAPI) VolumeCreate(call iopodman.VarlinkCall, options iopodman.Vol
// VolumeRemove removes volumes by options.All or options.Volumes
func (i *LibpodAPI) VolumeRemove(call iopodman.VarlinkCall, options iopodman.VolumeRemoveOpts) error {
- deletedVolumes, err := i.Runtime.RemoveVolumes(getContext(), options.Volumes, options.All, options.Force)
+ success, failed, err := shared.SharedRemoveVolumes(getContext(), i.Runtime, options.Volumes, options.All, options.Force)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
- return call.ReplyVolumeRemove(deletedVolumes)
+ // Convert map[string]string to map[string]error
+ errStrings := make(map[string]string)
+ for k, v := range failed {
+ errStrings[k] = v.Error()
+ }
+ return call.ReplyVolumeRemove(success, errStrings)
}
// GetVolumes returns all the volumes known to the remote system
diff --git a/test/e2e/network_create_test.go b/test/e2e/network_create_test.go
new file mode 100644
index 000000000..410d0b97c
--- /dev/null
+++ b/test/e2e/network_create_test.go
@@ -0,0 +1,211 @@
+// +build !remoteclient
+
+package integration
+
+import (
+ "encoding/json"
+ "io/ioutil"
+ "net"
+ "os"
+ "strings"
+
+ cniversion "github.com/containernetworking/cni/pkg/version"
+ "github.com/containers/libpod/pkg/network"
+ . "github.com/containers/libpod/test/utils"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ "github.com/pkg/errors"
+)
+
+var ErrPluginNotFound = errors.New("plugin not found")
+
+func findPluginByName(plugins interface{}, pluginType string) (interface{}, error) {
+ for _, p := range plugins.([]interface{}) {
+ r := p.(map[string]interface{})
+ if pluginType == r["type"] {
+ return p, nil
+ }
+ }
+ return nil, errors.Wrap(ErrPluginNotFound, pluginType)
+}
+
+func genericPluginsToBridge(plugins interface{}, pluginType string) (network.HostLocalBridge, error) {
+ var bridge network.HostLocalBridge
+ generic, err := findPluginByName(plugins, pluginType)
+ if err != nil {
+ return bridge, err
+ }
+ b, err := json.Marshal(generic)
+ if err != nil {
+ return bridge, err
+ }
+ err = json.Unmarshal(b, &bridge)
+ return bridge, err
+}
+
+func genericPluginsToPortMap(plugins interface{}, pluginType string) (network.PortMapConfig, error) {
+ var portMap network.PortMapConfig
+ generic, err := findPluginByName(plugins, "portmap")
+ if err != nil {
+ return portMap, err
+ }
+ b, err := json.Marshal(generic)
+ if err != nil {
+ return portMap, err
+ }
+ err = json.Unmarshal(b, &portMap)
+ return portMap, err
+}
+
+func (p *PodmanTestIntegration) removeCNINetwork(name string) {
+ session := p.Podman([]string{"network", "rm", name})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(BeZero())
+}
+
+func removeNetworkDevice(name string) {
+ session := SystemExec("ip", []string{"link", "delete", name})
+ session.WaitWithDefaultTimeout()
+}
+
+var _ = Describe("Podman network create", func() {
+ var (
+ tempdir string
+ err error
+ podmanTest *PodmanTestIntegration
+ )
+
+ BeforeEach(func() {
+ SkipIfRootless()
+ tempdir, err = CreateTempDirInTempDir()
+ if err != nil {
+ os.Exit(1)
+ }
+ podmanTest = PodmanTestCreate(tempdir)
+ podmanTest.Setup()
+ podmanTest.SeedImages()
+ })
+
+ AfterEach(func() {
+ podmanTest.Cleanup()
+ f := CurrentGinkgoTestDescription()
+ processTestResult(f)
+ })
+
+ It("podman network create with no input", func() {
+ var result network.NcList
+
+ nc := podmanTest.Podman([]string{"network", "create"})
+ nc.WaitWithDefaultTimeout()
+ Expect(nc.ExitCode()).To(BeZero())
+
+ fileContent, err := ioutil.ReadFile(nc.OutputToString())
+ Expect(err).To(BeNil())
+ err = json.Unmarshal(fileContent, &result)
+ Expect(err).To(BeNil())
+ defer podmanTest.removeCNINetwork(result["name"].(string))
+ Expect(result["cniVersion"]).To(Equal(cniversion.Current()))
+ Expect(strings.HasPrefix(result["name"].(string), "cni-podman")).To(BeTrue())
+
+ bridgePlugin, err := genericPluginsToBridge(result["plugins"], "bridge")
+ Expect(err).To(BeNil())
+ portMapPlugin, err := genericPluginsToPortMap(result["plugins"], "portmap")
+ Expect(err).To(BeNil())
+
+ Expect(bridgePlugin.IPAM.Routes[0].Dest).To(Equal("0.0.0.0/0"))
+ Expect(bridgePlugin.IsGW).To(BeTrue())
+ Expect(bridgePlugin.IPMasq).To(BeTrue())
+ Expect(portMapPlugin.Capabilities["portMappings"]).To(BeTrue())
+
+ })
+
+ It("podman network create with name", func() {
+ var (
+ results []network.NcList
+ )
+
+ nc := podmanTest.Podman([]string{"network", "create", "newname"})
+ nc.WaitWithDefaultTimeout()
+ Expect(nc.ExitCode()).To(BeZero())
+ defer podmanTest.removeCNINetwork("newname")
+
+ inspect := podmanTest.Podman([]string{"network", "inspect", "newname"})
+ inspect.WaitWithDefaultTimeout()
+
+ err := json.Unmarshal([]byte(inspect.OutputToString()), &results)
+ Expect(err).To(BeNil())
+ result := results[0]
+ Expect(result["name"]).To(Equal("newname"))
+
+ })
+
+ It("podman network create with name and subnet", func() {
+ var (
+ results []network.NcList
+ )
+ nc := podmanTest.Podman([]string{"network", "create", "--subnet", "10.11.12.0/24", "newnetwork"})
+ nc.WaitWithDefaultTimeout()
+ Expect(nc.ExitCode()).To(BeZero())
+
+ defer podmanTest.removeCNINetwork("newnetwork")
+
+ // Inspect the network configuration
+ inspect := podmanTest.Podman([]string{"network", "inspect", "newnetwork"})
+ inspect.WaitWithDefaultTimeout()
+
+ // JSON the network configuration into something usable
+ err := json.Unmarshal([]byte(inspect.OutputToString()), &results)
+ Expect(err).To(BeNil())
+ result := results[0]
+ Expect(result["name"]).To(Equal("newnetwork"))
+
+ // JSON the bridge info
+ bridgePlugin, err := genericPluginsToBridge(result["plugins"], "bridge")
+ Expect(err).To(BeNil())
+
+ // Once a container executes a new network, the nic will be created. We should clean those up
+ // best we can
+ defer removeNetworkDevice(bridgePlugin.BrName)
+
+ try := podmanTest.Podman([]string{"run", "-it", "--rm", "--network", "newnetwork", ALPINE, "sh", "-c", "ip addr show eth0 | awk ' /inet / {print $2}'"})
+ try.WaitWithDefaultTimeout()
+
+ _, subnet, err := net.ParseCIDR("10.11.12.0/24")
+ Expect(err).To(BeNil())
+ // Note this is an IPv4 test only!
+ containerIP, _, err := net.ParseCIDR(try.OutputToString())
+ Expect(err).To(BeNil())
+ // Ensure that the IP the container got is within the subnet the user asked for
+ Expect(subnet.Contains(containerIP)).To(BeTrue())
+ })
+
+ It("podman network create with invalid subnet", func() {
+ nc := podmanTest.Podman([]string{"network", "create", "--subnet", "10.11.12.0/17000", "fail"})
+ nc.WaitWithDefaultTimeout()
+ Expect(nc.ExitCode()).ToNot(BeZero())
+ })
+
+ It("podman network create with invalid IP", func() {
+ nc := podmanTest.Podman([]string{"network", "create", "--subnet", "10.11.0/17000", "fail"})
+ nc.WaitWithDefaultTimeout()
+ Expect(nc.ExitCode()).ToNot(BeZero())
+ })
+
+ It("podman network create with invalid gateway for subnet", func() {
+ nc := podmanTest.Podman([]string{"network", "create", "--subnet", "10.11.12.0/24", "--gateway", "192.168.1.1", "fail"})
+ nc.WaitWithDefaultTimeout()
+ Expect(nc.ExitCode()).ToNot(BeZero())
+ })
+
+ It("podman network create two networks with same name should fail", func() {
+ nc := podmanTest.Podman([]string{"network", "create", "samename"})
+ nc.WaitWithDefaultTimeout()
+ Expect(nc.ExitCode()).To(BeZero())
+ defer podmanTest.removeCNINetwork("samename")
+
+ ncFail := podmanTest.Podman([]string{"network", "create", "samename"})
+ ncFail.WaitWithDefaultTimeout()
+ Expect(ncFail.ExitCode()).ToNot(BeZero())
+ })
+
+})
diff --git a/test/e2e/run_volume_test.go b/test/e2e/run_volume_test.go
index 551e86b93..fc1998ab2 100644
--- a/test/e2e/run_volume_test.go
+++ b/test/e2e/run_volume_test.go
@@ -249,4 +249,25 @@ var _ = Describe("Podman run with volumes", func() {
fmt.Printf("Output: %s", mountOut3)
Expect(strings.Contains(mountOut3, volName)).To(BeFalse())
})
+
+ It("podman named volume copyup", func() {
+ baselineSession := podmanTest.Podman([]string{"run", "--rm", "-t", "-i", ALPINE, "ls", "/etc/apk/"})
+ baselineSession.WaitWithDefaultTimeout()
+ Expect(baselineSession.ExitCode()).To(Equal(0))
+ baselineOutput := baselineSession.OutputToString()
+
+ inlineVolumeSession := podmanTest.Podman([]string{"run", "--rm", "-t", "-i", "-v", "testvol1:/etc/apk", ALPINE, "ls", "/etc/apk/"})
+ inlineVolumeSession.WaitWithDefaultTimeout()
+ Expect(inlineVolumeSession.ExitCode()).To(Equal(0))
+ Expect(inlineVolumeSession.OutputToString()).To(Equal(baselineOutput))
+
+ makeVolumeSession := podmanTest.Podman([]string{"volume", "create", "testvol2"})
+ makeVolumeSession.WaitWithDefaultTimeout()
+ Expect(makeVolumeSession.ExitCode()).To(Equal(0))
+
+ separateVolumeSession := podmanTest.Podman([]string{"run", "--rm", "-t", "-i", "-v", "testvol2:/etc/apk", ALPINE, "ls", "/etc/apk/"})
+ separateVolumeSession.WaitWithDefaultTimeout()
+ Expect(separateVolumeSession.ExitCode()).To(Equal(0))
+ Expect(separateVolumeSession.OutputToString()).To(Equal(baselineOutput))
+ })
})
diff --git a/test/e2e/volume_rm_test.go b/test/e2e/volume_rm_test.go
index 5dcf51ccd..61cf9b893 100644
--- a/test/e2e/volume_rm_test.go
+++ b/test/e2e/volume_rm_test.go
@@ -89,4 +89,38 @@ var _ = Describe("Podman volume rm", func() {
Expect(session.ExitCode()).To(Equal(0))
Expect(len(session.OutputToStringArray())).To(Equal(0))
})
+
+ It("podman volume rm by partial name", func() {
+ session := podmanTest.Podman([]string{"volume", "create", "myvol"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"volume", "rm", "myv"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"volume", "ls"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(len(session.OutputToStringArray())).To(Equal(0))
+ })
+
+ It("podman volume rm by nonunique partial name", func() {
+ session := podmanTest.Podman([]string{"volume", "create", "myvol1"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"volume", "create", "myvol2"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"volume", "rm", "myv"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Not(Equal(0)))
+
+ session = podmanTest.Podman([]string{"volume", "ls"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(len(session.OutputToStringArray()) >= 2).To(BeTrue())
+ })
})
diff --git a/troubleshooting.md b/troubleshooting.md
index 9a5b38e01..89c850356 100644
--- a/troubleshooting.md
+++ b/troubleshooting.md
@@ -146,11 +146,11 @@ If the entry in the Dockerfile looked like: RUN useradd -u 99999000 -g users new
### 7) Permission denied when running Podman commands
-When rootless podman attempts to execute a container on a non exec home directory a permission error will be raised.
+When rootless Podman attempts to execute a container on a non exec home directory a permission error will be raised.
#### Symptom
-If you are running podman or buildah on a home directory that is mounted noexec,
+If you are running Podman or buildah on a home directory that is mounted noexec,
then they will fail. With a message like:
```
@@ -194,11 +194,11 @@ processes to write to the cgroup file system. Turn on this boolean, on SELinux s
### 9) Newuidmap missing when running rootless Podman commands
-Rootless podman requires the newuidmap and newgidmap programs to be installed.
+Rootless Podman requires the newuidmap and newgidmap programs to be installed.
#### Symptom
-If you are running podman or buildah as a not root user, you get an error complaining about
+If you are running Podman or buildah as a not root user, you get an error complaining about
a missing newuidmap executable.
```
@@ -212,7 +212,7 @@ Install a version of shadow-utils that includes these executables. Note RHEL7 a
### 10) rootless setup user: invalid argument
-Rootless podman requires the user running it to have a range of UIDs listed in /etc/subuid and /etc/subgid.
+Rootless Podman requires the user running it to have a range of UIDs listed in /etc/subuid and /etc/subgid.
#### Symptom
@@ -262,7 +262,7 @@ grep johndoe /etc/subuid /etc/subgid
### 11) Changing the location of the Graphroot leads to permission denied
When I change the graphroot storage location in storage.conf, the next time I
-run podman I get an error like:
+run Podman I get an error like:
```
# podman run -p 5000:5000 -it centos bash
@@ -360,7 +360,7 @@ Choose one of the following:
* Setup containers/storage in a different directory, not on an NFS share.
* Create a directory on a local file system.
* Edit `~/.config/containers/libpod.conf` and point the `volume_path` option to that local directory.
- * Otherwise just run podman as root, via `sudo podman`
+ * Otherwise just run Podman as root, via `sudo podman`
### 15) Rootless 'podman build' fails when using OverlayFS:
diff --git a/vendor/github.com/containernetworking/plugins/pkg/ip/addr_linux.go b/vendor/github.com/containernetworking/plugins/pkg/ip/addr_linux.go
new file mode 100644
index 000000000..b4db50b9a
--- /dev/null
+++ b/vendor/github.com/containernetworking/plugins/pkg/ip/addr_linux.go
@@ -0,0 +1,68 @@
+// Copyright 2017 CNI authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package ip
+
+import (
+ "fmt"
+ "syscall"
+ "time"
+
+ "github.com/vishvananda/netlink"
+)
+
+const SETTLE_INTERVAL = 50 * time.Millisecond
+
+// SettleAddresses waits for all addresses on a link to leave tentative state.
+// This is particularly useful for ipv6, where all addresses need to do DAD.
+// There is no easy way to wait for this as an event, so just loop until the
+// addresses are no longer tentative.
+// If any addresses are still tentative after timeout seconds, then error.
+func SettleAddresses(ifName string, timeout int) error {
+ link, err := netlink.LinkByName(ifName)
+ if err != nil {
+ return fmt.Errorf("failed to retrieve link: %v", err)
+ }
+
+ deadline := time.Now().Add(time.Duration(timeout) * time.Second)
+ for {
+ addrs, err := netlink.AddrList(link, netlink.FAMILY_ALL)
+ if err != nil {
+ return fmt.Errorf("could not list addresses: %v", err)
+ }
+
+ if len(addrs) == 0 {
+ return nil
+ }
+
+ ok := true
+ for _, addr := range addrs {
+ if addr.Flags&(syscall.IFA_F_TENTATIVE|syscall.IFA_F_DADFAILED) > 0 {
+ ok = false
+ break // Break out of the `range addrs`, not the `for`
+ }
+ }
+
+ if ok {
+ return nil
+ }
+ if time.Now().After(deadline) {
+ return fmt.Errorf("link %s still has tentative addresses after %d seconds",
+ ifName,
+ timeout)
+ }
+
+ time.Sleep(SETTLE_INTERVAL)
+ }
+}
diff --git a/vendor/github.com/containernetworking/plugins/pkg/ip/cidr.go b/vendor/github.com/containernetworking/plugins/pkg/ip/cidr.go
new file mode 100644
index 000000000..7acc2d47c
--- /dev/null
+++ b/vendor/github.com/containernetworking/plugins/pkg/ip/cidr.go
@@ -0,0 +1,61 @@
+// Copyright 2015 CNI authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package ip
+
+import (
+ "math/big"
+ "net"
+)
+
+// NextIP returns IP incremented by 1
+func NextIP(ip net.IP) net.IP {
+ i := ipToInt(ip)
+ return intToIP(i.Add(i, big.NewInt(1)))
+}
+
+// PrevIP returns IP decremented by 1
+func PrevIP(ip net.IP) net.IP {
+ i := ipToInt(ip)
+ return intToIP(i.Sub(i, big.NewInt(1)))
+}
+
+// Cmp compares two IPs, returning the usual ordering:
+// a < b : -1
+// a == b : 0
+// a > b : 1
+func Cmp(a, b net.IP) int {
+ aa := ipToInt(a)
+ bb := ipToInt(b)
+ return aa.Cmp(bb)
+}
+
+func ipToInt(ip net.IP) *big.Int {
+ if v := ip.To4(); v != nil {
+ return big.NewInt(0).SetBytes(v)
+ }
+ return big.NewInt(0).SetBytes(ip.To16())
+}
+
+func intToIP(i *big.Int) net.IP {
+ return net.IP(i.Bytes())
+}
+
+// Network masks off the host portion of the IP
+func Network(ipn *net.IPNet) *net.IPNet {
+ return &net.IPNet{
+ IP: ipn.IP.Mask(ipn.Mask),
+ Mask: ipn.Mask,
+ }
+}
diff --git a/vendor/github.com/containernetworking/plugins/pkg/ip/ipforward_linux.go b/vendor/github.com/containernetworking/plugins/pkg/ip/ipforward_linux.go
new file mode 100644
index 000000000..8216a2c38
--- /dev/null
+++ b/vendor/github.com/containernetworking/plugins/pkg/ip/ipforward_linux.go
@@ -0,0 +1,61 @@
+// Copyright 2015 CNI authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package ip
+
+import (
+ "bytes"
+ "io/ioutil"
+
+ "github.com/containernetworking/cni/pkg/types/current"
+)
+
+func EnableIP4Forward() error {
+ return echo1("/proc/sys/net/ipv4/ip_forward")
+}
+
+func EnableIP6Forward() error {
+ return echo1("/proc/sys/net/ipv6/conf/all/forwarding")
+}
+
+// EnableForward will enable forwarding for all configured
+// address families
+func EnableForward(ips []*current.IPConfig) error {
+ v4 := false
+ v6 := false
+
+ for _, ip := range ips {
+ if ip.Version == "4" && !v4 {
+ if err := EnableIP4Forward(); err != nil {
+ return err
+ }
+ v4 = true
+ } else if ip.Version == "6" && !v6 {
+ if err := EnableIP6Forward(); err != nil {
+ return err
+ }
+ v6 = true
+ }
+ }
+ return nil
+}
+
+func echo1(f string) error {
+ if content, err := ioutil.ReadFile(f); err == nil {
+ if bytes.Equal(bytes.TrimSpace(content), []byte("1")) {
+ return nil
+ }
+ }
+ return ioutil.WriteFile(f, []byte("1"), 0644)
+}
diff --git a/vendor/github.com/containernetworking/plugins/pkg/ip/ipmasq_linux.go b/vendor/github.com/containernetworking/plugins/pkg/ip/ipmasq_linux.go
new file mode 100644
index 000000000..cc640a605
--- /dev/null
+++ b/vendor/github.com/containernetworking/plugins/pkg/ip/ipmasq_linux.go
@@ -0,0 +1,126 @@
+// Copyright 2015 CNI authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package ip
+
+import (
+ "fmt"
+ "net"
+
+ "github.com/coreos/go-iptables/iptables"
+)
+
+// SetupIPMasq installs iptables rules to masquerade traffic
+// coming from ip of ipn and going outside of ipn
+func SetupIPMasq(ipn *net.IPNet, chain string, comment string) error {
+ isV6 := ipn.IP.To4() == nil
+
+ var ipt *iptables.IPTables
+ var err error
+ var multicastNet string
+
+ if isV6 {
+ ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv6)
+ multicastNet = "ff00::/8"
+ } else {
+ ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
+ multicastNet = "224.0.0.0/4"
+ }
+ if err != nil {
+ return fmt.Errorf("failed to locate iptables: %v", err)
+ }
+
+ // Create chain if doesn't exist
+ exists := false
+ chains, err := ipt.ListChains("nat")
+ if err != nil {
+ return fmt.Errorf("failed to list chains: %v", err)
+ }
+ for _, ch := range chains {
+ if ch == chain {
+ exists = true
+ break
+ }
+ }
+ if !exists {
+ if err = ipt.NewChain("nat", chain); err != nil {
+ return err
+ }
+ }
+
+ // Packets to this network should not be touched
+ if err := ipt.AppendUnique("nat", chain, "-d", ipn.String(), "-j", "ACCEPT", "-m", "comment", "--comment", comment); err != nil {
+ return err
+ }
+
+ // Don't masquerade multicast - pods should be able to talk to other pods
+ // on the local network via multicast.
+ if err := ipt.AppendUnique("nat", chain, "!", "-d", multicastNet, "-j", "MASQUERADE", "-m", "comment", "--comment", comment); err != nil {
+ return err
+ }
+
+ // Packets from the specific IP of this network will hit the chain
+ return ipt.AppendUnique("nat", "POSTROUTING", "-s", ipn.IP.String(), "-j", chain, "-m", "comment", "--comment", comment)
+}
+
+// TeardownIPMasq undoes the effects of SetupIPMasq
+func TeardownIPMasq(ipn *net.IPNet, chain string, comment string) error {
+ isV6 := ipn.IP.To4() == nil
+
+ var ipt *iptables.IPTables
+ var err error
+
+ if isV6 {
+ ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv6)
+ } else {
+ ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
+ }
+ if err != nil {
+ return fmt.Errorf("failed to locate iptables: %v", err)
+ }
+
+ err = ipt.Delete("nat", "POSTROUTING", "-s", ipn.IP.String(), "-j", chain, "-m", "comment", "--comment", comment)
+ if err != nil && !isNotExist(err) {
+ return err
+ }
+
+ // for downward compatibility
+ err = ipt.Delete("nat", "POSTROUTING", "-s", ipn.String(), "-j", chain, "-m", "comment", "--comment", comment)
+ if err != nil && !isNotExist(err) {
+ return err
+ }
+
+ err = ipt.ClearChain("nat", chain)
+ if err != nil && !isNotExist(err) {
+ return err
+
+ }
+
+ err = ipt.DeleteChain("nat", chain)
+ if err != nil && !isNotExist(err) {
+ return err
+ }
+
+ return nil
+}
+
+// isNotExist returnst true if the error is from iptables indicating
+// that the target does not exist.
+func isNotExist(err error) bool {
+ e, ok := err.(*iptables.Error)
+ if !ok {
+ return false
+ }
+ return e.IsNotExist()
+}
diff --git a/vendor/github.com/containernetworking/plugins/pkg/ip/link_linux.go b/vendor/github.com/containernetworking/plugins/pkg/ip/link_linux.go
new file mode 100644
index 000000000..909afd04e
--- /dev/null
+++ b/vendor/github.com/containernetworking/plugins/pkg/ip/link_linux.go
@@ -0,0 +1,275 @@
+// Copyright 2015 CNI authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package ip
+
+import (
+ "crypto/rand"
+ "errors"
+ "fmt"
+ "net"
+ "os"
+
+ "github.com/containernetworking/plugins/pkg/ns"
+ "github.com/containernetworking/plugins/pkg/utils/hwaddr"
+ "github.com/safchain/ethtool"
+ "github.com/vishvananda/netlink"
+)
+
+var (
+ ErrLinkNotFound = errors.New("link not found")
+)
+
+func makeVethPair(name, peer string, mtu int) (netlink.Link, error) {
+ veth := &netlink.Veth{
+ LinkAttrs: netlink.LinkAttrs{
+ Name: name,
+ Flags: net.FlagUp,
+ MTU: mtu,
+ },
+ PeerName: peer,
+ }
+ if err := netlink.LinkAdd(veth); err != nil {
+ return nil, err
+ }
+ // Re-fetch the link to get its creation-time parameters, e.g. index and mac
+ veth2, err := netlink.LinkByName(name)
+ if err != nil {
+ netlink.LinkDel(veth) // try and clean up the link if possible.
+ return nil, err
+ }
+
+ return veth2, nil
+}
+
+func peerExists(name string) bool {
+ if _, err := netlink.LinkByName(name); err != nil {
+ return false
+ }
+ return true
+}
+
+func makeVeth(name string, mtu int) (peerName string, veth netlink.Link, err error) {
+ for i := 0; i < 10; i++ {
+ peerName, err = RandomVethName()
+ if err != nil {
+ return
+ }
+
+ veth, err = makeVethPair(name, peerName, mtu)
+ switch {
+ case err == nil:
+ return
+
+ case os.IsExist(err):
+ if peerExists(peerName) {
+ continue
+ }
+ err = fmt.Errorf("container veth name provided (%v) already exists", name)
+ return
+
+ default:
+ err = fmt.Errorf("failed to make veth pair: %v", err)
+ return
+ }
+ }
+
+ // should really never be hit
+ err = fmt.Errorf("failed to find a unique veth name")
+ return
+}
+
+// RandomVethName returns string "veth" with random prefix (hashed from entropy)
+func RandomVethName() (string, error) {
+ entropy := make([]byte, 4)
+ _, err := rand.Reader.Read(entropy)
+ if err != nil {
+ return "", fmt.Errorf("failed to generate random veth name: %v", err)
+ }
+
+ // NetworkManager (recent versions) will ignore veth devices that start with "veth"
+ return fmt.Sprintf("veth%x", entropy), nil
+}
+
+func RenameLink(curName, newName string) error {
+ link, err := netlink.LinkByName(curName)
+ if err == nil {
+ err = netlink.LinkSetName(link, newName)
+ }
+ return err
+}
+
+func ifaceFromNetlinkLink(l netlink.Link) net.Interface {
+ a := l.Attrs()
+ return net.Interface{
+ Index: a.Index,
+ MTU: a.MTU,
+ Name: a.Name,
+ HardwareAddr: a.HardwareAddr,
+ Flags: a.Flags,
+ }
+}
+
+// SetupVeth sets up a pair of virtual ethernet devices.
+// Call SetupVeth from inside the container netns. It will create both veth
+// devices and move the host-side veth into the provided hostNS namespace.
+// On success, SetupVeth returns (hostVeth, containerVeth, nil)
+func SetupVeth(contVethName string, mtu int, hostNS ns.NetNS) (net.Interface, net.Interface, error) {
+ hostVethName, contVeth, err := makeVeth(contVethName, mtu)
+ if err != nil {
+ return net.Interface{}, net.Interface{}, err
+ }
+
+ if err = netlink.LinkSetUp(contVeth); err != nil {
+ return net.Interface{}, net.Interface{}, fmt.Errorf("failed to set %q up: %v", contVethName, err)
+ }
+
+ hostVeth, err := netlink.LinkByName(hostVethName)
+ if err != nil {
+ return net.Interface{}, net.Interface{}, fmt.Errorf("failed to lookup %q: %v", hostVethName, err)
+ }
+
+ if err = netlink.LinkSetNsFd(hostVeth, int(hostNS.Fd())); err != nil {
+ return net.Interface{}, net.Interface{}, fmt.Errorf("failed to move veth to host netns: %v", err)
+ }
+
+ err = hostNS.Do(func(_ ns.NetNS) error {
+ hostVeth, err = netlink.LinkByName(hostVethName)
+ if err != nil {
+ return fmt.Errorf("failed to lookup %q in %q: %v", hostVethName, hostNS.Path(), err)
+ }
+
+ if err = netlink.LinkSetUp(hostVeth); err != nil {
+ return fmt.Errorf("failed to set %q up: %v", hostVethName, err)
+ }
+ return nil
+ })
+ if err != nil {
+ return net.Interface{}, net.Interface{}, err
+ }
+ return ifaceFromNetlinkLink(hostVeth), ifaceFromNetlinkLink(contVeth), nil
+}
+
+// DelLinkByName removes an interface link.
+func DelLinkByName(ifName string) error {
+ iface, err := netlink.LinkByName(ifName)
+ if err != nil {
+ if err.Error() == "Link not found" {
+ return ErrLinkNotFound
+ }
+ return fmt.Errorf("failed to lookup %q: %v", ifName, err)
+ }
+
+ if err = netlink.LinkDel(iface); err != nil {
+ return fmt.Errorf("failed to delete %q: %v", ifName, err)
+ }
+
+ return nil
+}
+
+// DelLinkByNameAddr remove an interface and returns its addresses
+func DelLinkByNameAddr(ifName string) ([]*net.IPNet, error) {
+ iface, err := netlink.LinkByName(ifName)
+ if err != nil {
+ if err != nil && err.Error() == "Link not found" {
+ return nil, ErrLinkNotFound
+ }
+ return nil, fmt.Errorf("failed to lookup %q: %v", ifName, err)
+ }
+
+ addrs, err := netlink.AddrList(iface, netlink.FAMILY_ALL)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get IP addresses for %q: %v", ifName, err)
+ }
+
+ if err = netlink.LinkDel(iface); err != nil {
+ return nil, fmt.Errorf("failed to delete %q: %v", ifName, err)
+ }
+
+ out := []*net.IPNet{}
+ for _, addr := range addrs {
+ if addr.IP.IsGlobalUnicast() {
+ out = append(out, addr.IPNet)
+ }
+ }
+
+ return out, nil
+}
+
+func SetHWAddrByIP(ifName string, ip4 net.IP, ip6 net.IP) error {
+ iface, err := netlink.LinkByName(ifName)
+ if err != nil {
+ return fmt.Errorf("failed to lookup %q: %v", ifName, err)
+ }
+
+ switch {
+ case ip4 == nil && ip6 == nil:
+ return fmt.Errorf("neither ip4 or ip6 specified")
+
+ case ip4 != nil:
+ {
+ hwAddr, err := hwaddr.GenerateHardwareAddr4(ip4, hwaddr.PrivateMACPrefix)
+ if err != nil {
+ return fmt.Errorf("failed to generate hardware addr: %v", err)
+ }
+ if err = netlink.LinkSetHardwareAddr(iface, hwAddr); err != nil {
+ return fmt.Errorf("failed to add hardware addr to %q: %v", ifName, err)
+ }
+ }
+ case ip6 != nil:
+ // TODO: IPv6
+ }
+
+ return nil
+}
+
+// GetVethPeerIfindex returns the veth link object, the peer ifindex of the
+// veth, or an error. This peer ifindex will only be valid in the peer's
+// network namespace.
+func GetVethPeerIfindex(ifName string) (netlink.Link, int, error) {
+ link, err := netlink.LinkByName(ifName)
+ if err != nil {
+ return nil, -1, fmt.Errorf("could not look up %q: %v", ifName, err)
+ }
+ if _, ok := link.(*netlink.Veth); !ok {
+ return nil, -1, fmt.Errorf("interface %q was not a veth interface", ifName)
+ }
+
+ // veth supports IFLA_LINK (what vishvananda/netlink calls ParentIndex)
+ // on 4.1 and higher kernels
+ peerIndex := link.Attrs().ParentIndex
+ if peerIndex <= 0 {
+ // Fall back to ethtool for 4.0 and earlier kernels
+ e, err := ethtool.NewEthtool()
+ if err != nil {
+ return nil, -1, fmt.Errorf("failed to initialize ethtool: %v", err)
+ }
+ defer e.Close()
+
+ stats, err := e.Stats(link.Attrs().Name)
+ if err != nil {
+ return nil, -1, fmt.Errorf("failed to request ethtool stats: %v", err)
+ }
+ n, ok := stats["peer_ifindex"]
+ if !ok {
+ return nil, -1, fmt.Errorf("failed to find 'peer_ifindex' in ethtool stats")
+ }
+ if n > 32767 || n == 0 {
+ return nil, -1, fmt.Errorf("invalid 'peer_ifindex' %d", n)
+ }
+ peerIndex = int(n)
+ }
+
+ return link, peerIndex, nil
+}
diff --git a/vendor/github.com/containernetworking/plugins/pkg/ip/route_linux.go b/vendor/github.com/containernetworking/plugins/pkg/ip/route_linux.go
new file mode 100644
index 000000000..f5c0d0803
--- /dev/null
+++ b/vendor/github.com/containernetworking/plugins/pkg/ip/route_linux.go
@@ -0,0 +1,47 @@
+// Copyright 2015-2017 CNI authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package ip
+
+import (
+ "net"
+
+ "github.com/vishvananda/netlink"
+)
+
+// AddRoute adds a universally-scoped route to a device.
+func AddRoute(ipn *net.IPNet, gw net.IP, dev netlink.Link) error {
+ return netlink.RouteAdd(&netlink.Route{
+ LinkIndex: dev.Attrs().Index,
+ Scope: netlink.SCOPE_UNIVERSE,
+ Dst: ipn,
+ Gw: gw,
+ })
+}
+
+// AddHostRoute adds a host-scoped route to a device.
+func AddHostRoute(ipn *net.IPNet, gw net.IP, dev netlink.Link) error {
+ return netlink.RouteAdd(&netlink.Route{
+ LinkIndex: dev.Attrs().Index,
+ Scope: netlink.SCOPE_HOST,
+ Dst: ipn,
+ Gw: gw,
+ })
+}
+
+// AddDefaultRoute sets the default route on the given gateway.
+func AddDefaultRoute(gw net.IP, dev netlink.Link) error {
+ _, defNet, _ := net.ParseCIDR("0.0.0.0/0")
+ return AddRoute(defNet, gw, dev)
+}
diff --git a/vendor/github.com/containernetworking/plugins/pkg/ip/utils_linux.go b/vendor/github.com/containernetworking/plugins/pkg/ip/utils_linux.go
new file mode 100644
index 000000000..7623c5e13
--- /dev/null
+++ b/vendor/github.com/containernetworking/plugins/pkg/ip/utils_linux.go
@@ -0,0 +1,120 @@
+// +build linux
+
+// Copyright 2016 CNI authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package ip
+
+import (
+ "fmt"
+ "net"
+
+ "github.com/containernetworking/cni/pkg/types"
+ "github.com/containernetworking/cni/pkg/types/current"
+ "github.com/vishvananda/netlink"
+)
+
+func ValidateExpectedInterfaceIPs(ifName string, resultIPs []*current.IPConfig) error {
+
+ // Ensure ips
+ for _, ips := range resultIPs {
+ ourAddr := netlink.Addr{IPNet: &ips.Address}
+ match := false
+
+ link, err := netlink.LinkByName(ifName)
+ if err != nil {
+ return fmt.Errorf("Cannot find container link %v", ifName)
+ }
+
+ addrList, err := netlink.AddrList(link, netlink.FAMILY_ALL)
+ if err != nil {
+ return fmt.Errorf("Cannot obtain List of IP Addresses")
+ }
+
+ for _, addr := range addrList {
+ if addr.Equal(ourAddr) {
+ match = true
+ break
+ }
+ }
+ if match == false {
+ return fmt.Errorf("Failed to match addr %v on interface %v", ourAddr, ifName)
+ }
+
+ // Convert the host/prefixlen to just prefix for route lookup.
+ _, ourPrefix, err := net.ParseCIDR(ourAddr.String())
+
+ findGwy := &netlink.Route{Dst: ourPrefix}
+ routeFilter := netlink.RT_FILTER_DST
+ var family int
+
+ switch {
+ case ips.Version == "4":
+ family = netlink.FAMILY_V4
+ case ips.Version == "6":
+ family = netlink.FAMILY_V6
+ default:
+ return fmt.Errorf("Invalid IP Version %v for interface %v", ips.Version, ifName)
+ }
+
+ gwy, err := netlink.RouteListFiltered(family, findGwy, routeFilter)
+ if err != nil {
+ return fmt.Errorf("Error %v trying to find Gateway %v for interface %v", err, ips.Gateway, ifName)
+ }
+ if gwy == nil {
+ return fmt.Errorf("Failed to find Gateway %v for interface %v", ips.Gateway, ifName)
+ }
+ }
+
+ return nil
+}
+
+func ValidateExpectedRoute(resultRoutes []*types.Route) error {
+
+ // Ensure that each static route in prevResults is found in the routing table
+ for _, route := range resultRoutes {
+ find := &netlink.Route{Dst: &route.Dst, Gw: route.GW}
+ routeFilter := netlink.RT_FILTER_DST | netlink.RT_FILTER_GW
+ var family int
+
+ switch {
+ case route.Dst.IP.To4() != nil:
+ family = netlink.FAMILY_V4
+ // Default route needs Dst set to nil
+ if route.Dst.String() == "0.0.0.0/0" {
+ find = &netlink.Route{Dst: nil, Gw: route.GW}
+ routeFilter = netlink.RT_FILTER_DST
+ }
+ case len(route.Dst.IP) == net.IPv6len:
+ family = netlink.FAMILY_V6
+ // Default route needs Dst set to nil
+ if route.Dst.String() == "::/0" {
+ find = &netlink.Route{Dst: nil, Gw: route.GW}
+ routeFilter = netlink.RT_FILTER_DST
+ }
+ default:
+ return fmt.Errorf("Invalid static route found %v", route)
+ }
+
+ wasFound, err := netlink.RouteListFiltered(family, find, routeFilter)
+ if err != nil {
+ return fmt.Errorf("Expected Route %v not route table lookup error %v", route, err)
+ }
+ if wasFound == nil {
+ return fmt.Errorf("Expected Route %v not found in routing table", route)
+ }
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/containernetworking/plugins/pkg/utils/hwaddr/hwaddr.go b/vendor/github.com/containernetworking/plugins/pkg/utils/hwaddr/hwaddr.go
new file mode 100644
index 000000000..aaf3b8a02
--- /dev/null
+++ b/vendor/github.com/containernetworking/plugins/pkg/utils/hwaddr/hwaddr.go
@@ -0,0 +1,63 @@
+// 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 hwaddr
+
+import (
+ "fmt"
+ "net"
+)
+
+const (
+ ipRelevantByteLen = 4
+ PrivateMACPrefixString = "0a:58"
+)
+
+var (
+ // private mac prefix safe to use
+ PrivateMACPrefix = []byte{0x0a, 0x58}
+)
+
+type SupportIp4OnlyErr struct{ msg string }
+
+func (e SupportIp4OnlyErr) Error() string { return e.msg }
+
+type MacParseErr struct{ msg string }
+
+func (e MacParseErr) Error() string { return e.msg }
+
+type InvalidPrefixLengthErr struct{ msg string }
+
+func (e InvalidPrefixLengthErr) Error() string { return e.msg }
+
+// GenerateHardwareAddr4 generates 48 bit virtual mac addresses based on the IP4 input.
+func GenerateHardwareAddr4(ip net.IP, prefix []byte) (net.HardwareAddr, error) {
+ switch {
+
+ case ip.To4() == nil:
+ return nil, SupportIp4OnlyErr{msg: "GenerateHardwareAddr4 only supports valid IPv4 address as input"}
+
+ case len(prefix) != len(PrivateMACPrefix):
+ return nil, InvalidPrefixLengthErr{msg: fmt.Sprintf(
+ "Prefix has length %d instead of %d", len(prefix), len(PrivateMACPrefix)),
+ }
+ }
+
+ ipByteLen := len(ip)
+ return (net.HardwareAddr)(
+ append(
+ prefix,
+ ip[ipByteLen-ipRelevantByteLen:ipByteLen]...),
+ ), nil
+}
diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/allocator.go b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/allocator.go
new file mode 100644
index 000000000..d1c2b1018
--- /dev/null
+++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/allocator.go
@@ -0,0 +1,217 @@
+// 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 allocator
+
+import (
+ "fmt"
+ "log"
+ "net"
+ "os"
+ "strconv"
+
+ "github.com/containernetworking/cni/pkg/types/current"
+ "github.com/containernetworking/plugins/pkg/ip"
+ "github.com/containernetworking/plugins/plugins/ipam/host-local/backend"
+)
+
+type IPAllocator struct {
+ rangeset *RangeSet
+ store backend.Store
+ rangeID string // Used for tracking last reserved ip
+}
+
+func NewIPAllocator(s *RangeSet, store backend.Store, id int) *IPAllocator {
+ return &IPAllocator{
+ rangeset: s,
+ store: store,
+ rangeID: strconv.Itoa(id),
+ }
+}
+
+// Get alocates an IP
+func (a *IPAllocator) Get(id string, ifname string, requestedIP net.IP) (*current.IPConfig, error) {
+ a.store.Lock()
+ defer a.store.Unlock()
+
+ var reservedIP *net.IPNet
+ var gw net.IP
+
+ if requestedIP != nil {
+ if err := canonicalizeIP(&requestedIP); err != nil {
+ return nil, err
+ }
+
+ r, err := a.rangeset.RangeFor(requestedIP)
+ if err != nil {
+ return nil, err
+ }
+
+ if requestedIP.Equal(r.Gateway) {
+ return nil, fmt.Errorf("requested ip %s is subnet's gateway", requestedIP.String())
+ }
+
+ reserved, err := a.store.Reserve(id, ifname, requestedIP, a.rangeID)
+ if err != nil {
+ return nil, err
+ }
+ if !reserved {
+ return nil, fmt.Errorf("requested IP address %s is not available in range set %s", requestedIP, a.rangeset.String())
+ }
+ reservedIP = &net.IPNet{IP: requestedIP, Mask: r.Subnet.Mask}
+ gw = r.Gateway
+
+ } else {
+ iter, err := a.GetIter()
+ if err != nil {
+ return nil, err
+ }
+ for {
+ reservedIP, gw = iter.Next()
+ if reservedIP == nil {
+ break
+ }
+
+ reserved, err := a.store.Reserve(id, ifname, reservedIP.IP, a.rangeID)
+ if err != nil {
+ return nil, err
+ }
+
+ if reserved {
+ break
+ }
+ }
+ }
+
+ if reservedIP == nil {
+ return nil, fmt.Errorf("no IP addresses available in range set: %s", a.rangeset.String())
+ }
+ version := "4"
+ if reservedIP.IP.To4() == nil {
+ version = "6"
+ }
+
+ return &current.IPConfig{
+ Version: version,
+ Address: *reservedIP,
+ Gateway: gw,
+ }, nil
+}
+
+// Release clears all IPs allocated for the container with given ID
+func (a *IPAllocator) Release(id string, ifname string) error {
+ a.store.Lock()
+ defer a.store.Unlock()
+
+ return a.store.ReleaseByID(id, ifname)
+}
+
+type RangeIter struct {
+ rangeset *RangeSet
+
+ // The current range id
+ rangeIdx int
+
+ // Our current position
+ cur net.IP
+
+ // The IP and range index where we started iterating; if we hit this again, we're done.
+ startIP net.IP
+ startRange int
+}
+
+// GetIter encapsulates the strategy for this allocator.
+// We use a round-robin strategy, attempting to evenly use the whole set.
+// More specifically, a crash-looping container will not see the same IP until
+// the entire range has been run through.
+// We may wish to consider avoiding recently-released IPs in the future.
+func (a *IPAllocator) GetIter() (*RangeIter, error) {
+ iter := RangeIter{
+ rangeset: a.rangeset,
+ }
+
+ // Round-robin by trying to allocate from the last reserved IP + 1
+ startFromLastReservedIP := false
+
+ // We might get a last reserved IP that is wrong if the range indexes changed.
+ // This is not critical, we just lose round-robin this one time.
+ lastReservedIP, err := a.store.LastReservedIP(a.rangeID)
+ if err != nil && !os.IsNotExist(err) {
+ log.Printf("Error retrieving last reserved ip: %v", err)
+ } else if lastReservedIP != nil {
+ startFromLastReservedIP = a.rangeset.Contains(lastReservedIP)
+ }
+
+ // Find the range in the set with this IP
+ if startFromLastReservedIP {
+ for i, r := range *a.rangeset {
+ if r.Contains(lastReservedIP) {
+ iter.rangeIdx = i
+ iter.startRange = i
+
+ // We advance the cursor on every Next(), so the first call
+ // to next() will return lastReservedIP + 1
+ iter.cur = lastReservedIP
+ break
+ }
+ }
+ } else {
+ iter.rangeIdx = 0
+ iter.startRange = 0
+ iter.startIP = (*a.rangeset)[0].RangeStart
+ }
+ return &iter, nil
+}
+
+// Next returns the next IP, its mask, and its gateway. Returns nil
+// if the iterator has been exhausted
+func (i *RangeIter) Next() (*net.IPNet, net.IP) {
+ r := (*i.rangeset)[i.rangeIdx]
+
+ // If this is the first time iterating and we're not starting in the middle
+ // of the range, then start at rangeStart, which is inclusive
+ if i.cur == nil {
+ i.cur = r.RangeStart
+ i.startIP = i.cur
+ if i.cur.Equal(r.Gateway) {
+ return i.Next()
+ }
+ return &net.IPNet{IP: i.cur, Mask: r.Subnet.Mask}, r.Gateway
+ }
+
+ // If we've reached the end of this range, we need to advance the range
+ // RangeEnd is inclusive as well
+ if i.cur.Equal(r.RangeEnd) {
+ i.rangeIdx += 1
+ i.rangeIdx %= len(*i.rangeset)
+ r = (*i.rangeset)[i.rangeIdx]
+
+ i.cur = r.RangeStart
+ } else {
+ i.cur = ip.NextIP(i.cur)
+ }
+
+ if i.startIP == nil {
+ i.startIP = i.cur
+ } else if i.rangeIdx == i.startRange && i.cur.Equal(i.startIP) {
+ // IF we've looped back to where we started, give up
+ return nil, nil
+ }
+
+ if i.cur.Equal(r.Gateway) {
+ return i.Next()
+ }
+
+ return &net.IPNet{IP: i.cur, Mask: r.Subnet.Mask}, r.Gateway
+}
diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/config.go b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/config.go
new file mode 100644
index 000000000..c8cb2a746
--- /dev/null
+++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/config.go
@@ -0,0 +1,160 @@
+// 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 allocator
+
+import (
+ "encoding/json"
+ "fmt"
+ "net"
+
+ "github.com/containernetworking/cni/pkg/types"
+ "github.com/containernetworking/cni/pkg/types/020"
+)
+
+// The top-level network config - IPAM plugins are passed the full configuration
+// of the calling plugin, not just the IPAM section.
+type Net struct {
+ Name string `json:"name"`
+ CNIVersion string `json:"cniVersion"`
+ IPAM *IPAMConfig `json:"ipam"`
+ RuntimeConfig struct { // The capability arg
+ IPRanges []RangeSet `json:"ipRanges,omitempty"`
+ } `json:"runtimeConfig,omitempty"`
+ Args *struct {
+ A *IPAMArgs `json:"cni"`
+ } `json:"args"`
+}
+
+// IPAMConfig represents the IP related network configuration.
+// This nests Range because we initially only supported a single
+// range directly, and wish to preserve backwards compatability
+type IPAMConfig struct {
+ *Range
+ Name string
+ Type string `json:"type"`
+ Routes []*types.Route `json:"routes"`
+ DataDir string `json:"dataDir"`
+ ResolvConf string `json:"resolvConf"`
+ Ranges []RangeSet `json:"ranges"`
+ IPArgs []net.IP `json:"-"` // Requested IPs from CNI_ARGS and args
+}
+
+type IPAMEnvArgs struct {
+ types.CommonArgs
+ IP net.IP `json:"ip,omitempty"`
+}
+
+type IPAMArgs struct {
+ IPs []net.IP `json:"ips"`
+}
+
+type RangeSet []Range
+
+type Range struct {
+ RangeStart net.IP `json:"rangeStart,omitempty"` // The first ip, inclusive
+ RangeEnd net.IP `json:"rangeEnd,omitempty"` // The last ip, inclusive
+ Subnet types.IPNet `json:"subnet"`
+ Gateway net.IP `json:"gateway,omitempty"`
+}
+
+// NewIPAMConfig creates a NetworkConfig from the given network name.
+func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
+ n := Net{}
+ if err := json.Unmarshal(bytes, &n); err != nil {
+ return nil, "", err
+ }
+
+ if n.IPAM == nil {
+ return nil, "", fmt.Errorf("IPAM config missing 'ipam' key")
+ }
+
+ // Parse custom IP from both env args *and* the top-level args config
+ if envArgs != "" {
+ e := IPAMEnvArgs{}
+ err := types.LoadArgs(envArgs, &e)
+ if err != nil {
+ return nil, "", err
+ }
+
+ if e.IP != nil {
+ n.IPAM.IPArgs = []net.IP{e.IP}
+ }
+ }
+
+ if n.Args != nil && n.Args.A != nil && len(n.Args.A.IPs) != 0 {
+ n.IPAM.IPArgs = append(n.IPAM.IPArgs, n.Args.A.IPs...)
+ }
+
+ for idx := range n.IPAM.IPArgs {
+ if err := canonicalizeIP(&n.IPAM.IPArgs[idx]); err != nil {
+ return nil, "", fmt.Errorf("cannot understand ip: %v", err)
+ }
+ }
+
+ // If a single range (old-style config) is specified, prepend it to
+ // the Ranges array
+ if n.IPAM.Range != nil && n.IPAM.Range.Subnet.IP != nil {
+ n.IPAM.Ranges = append([]RangeSet{{*n.IPAM.Range}}, n.IPAM.Ranges...)
+ }
+ n.IPAM.Range = nil
+
+ // If a range is supplied as a runtime config, prepend it to the Ranges
+ if len(n.RuntimeConfig.IPRanges) > 0 {
+ n.IPAM.Ranges = append(n.RuntimeConfig.IPRanges, n.IPAM.Ranges...)
+ }
+
+ if len(n.IPAM.Ranges) == 0 {
+ return nil, "", fmt.Errorf("no IP ranges specified")
+ }
+
+ // Validate all ranges
+ numV4 := 0
+ numV6 := 0
+ for i := range n.IPAM.Ranges {
+ if err := n.IPAM.Ranges[i].Canonicalize(); err != nil {
+ return nil, "", fmt.Errorf("invalid range set %d: %s", i, err)
+ }
+
+ if n.IPAM.Ranges[i][0].RangeStart.To4() != nil {
+ numV4++
+ } else {
+ numV6++
+ }
+ }
+
+ // CNI spec 0.2.0 and below supported only one v4 and v6 address
+ if numV4 > 1 || numV6 > 1 {
+ for _, v := range types020.SupportedVersions {
+ if n.CNIVersion == v {
+ return nil, "", fmt.Errorf("CNI version %v does not support more than 1 address per family", n.CNIVersion)
+ }
+ }
+ }
+
+ // Check for overlaps
+ l := len(n.IPAM.Ranges)
+ for i, p1 := range n.IPAM.Ranges[:l-1] {
+ for j, p2 := range n.IPAM.Ranges[i+1:] {
+ if p1.Overlaps(&p2) {
+ return nil, "", fmt.Errorf("range set %d overlaps with %d", i, (i + j + 1))
+ }
+ }
+ }
+
+ // Copy net name into IPAM so not to drag Net struct around
+ n.IPAM.Name = n.Name
+
+ return n.IPAM, n.CNIVersion, nil
+}
diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/range.go b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/range.go
new file mode 100644
index 000000000..9bf389e80
--- /dev/null
+++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/range.go
@@ -0,0 +1,166 @@
+// Copyright 2017 CNI authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package allocator
+
+import (
+ "fmt"
+ "net"
+
+ "github.com/containernetworking/cni/pkg/types"
+ "github.com/containernetworking/plugins/pkg/ip"
+)
+
+// Canonicalize takes a given range and ensures that all information is consistent,
+// filling out Start, End, and Gateway with sane values if missing
+func (r *Range) Canonicalize() error {
+ if err := canonicalizeIP(&r.Subnet.IP); err != nil {
+ return err
+ }
+
+ // Can't create an allocator for a network with no addresses, eg
+ // a /32 or /31
+ ones, masklen := r.Subnet.Mask.Size()
+ if ones > masklen-2 {
+ return fmt.Errorf("Network %s too small to allocate from", (*net.IPNet)(&r.Subnet).String())
+ }
+
+ if len(r.Subnet.IP) != len(r.Subnet.Mask) {
+ return fmt.Errorf("IPNet IP and Mask version mismatch")
+ }
+
+ // Ensure Subnet IP is the network address, not some other address
+ networkIP := r.Subnet.IP.Mask(r.Subnet.Mask)
+ if !r.Subnet.IP.Equal(networkIP) {
+ return fmt.Errorf("Network has host bits set. For a subnet mask of length %d the network address is %s", ones, networkIP.String())
+ }
+
+ // If the gateway is nil, claim .1
+ if r.Gateway == nil {
+ r.Gateway = ip.NextIP(r.Subnet.IP)
+ } else {
+ if err := canonicalizeIP(&r.Gateway); err != nil {
+ return err
+ }
+ }
+
+ // RangeStart: If specified, make sure it's sane (inside the subnet),
+ // otherwise use the first free IP (i.e. .1) - this will conflict with the
+ // gateway but we skip it in the iterator
+ if r.RangeStart != nil {
+ if err := canonicalizeIP(&r.RangeStart); err != nil {
+ return err
+ }
+
+ if !r.Contains(r.RangeStart) {
+ return fmt.Errorf("RangeStart %s not in network %s", r.RangeStart.String(), (*net.IPNet)(&r.Subnet).String())
+ }
+ } else {
+ r.RangeStart = ip.NextIP(r.Subnet.IP)
+ }
+
+ // RangeEnd: If specified, verify sanity. Otherwise, add a sensible default
+ // (e.g. for a /24: .254 if IPv4, ::255 if IPv6)
+ if r.RangeEnd != nil {
+ if err := canonicalizeIP(&r.RangeEnd); err != nil {
+ return err
+ }
+
+ if !r.Contains(r.RangeEnd) {
+ return fmt.Errorf("RangeEnd %s not in network %s", r.RangeEnd.String(), (*net.IPNet)(&r.Subnet).String())
+ }
+ } else {
+ r.RangeEnd = lastIP(r.Subnet)
+ }
+
+ return nil
+}
+
+// IsValidIP checks if a given ip is a valid, allocatable address in a given Range
+func (r *Range) Contains(addr net.IP) bool {
+ if err := canonicalizeIP(&addr); err != nil {
+ return false
+ }
+
+ subnet := (net.IPNet)(r.Subnet)
+
+ // Not the same address family
+ if len(addr) != len(r.Subnet.IP) {
+ return false
+ }
+
+ // Not in network
+ if !subnet.Contains(addr) {
+ return false
+ }
+
+ // We ignore nils here so we can use this function as we initialize the range.
+ if r.RangeStart != nil {
+ // Before the range start
+ if ip.Cmp(addr, r.RangeStart) < 0 {
+ return false
+ }
+ }
+
+ if r.RangeEnd != nil {
+ if ip.Cmp(addr, r.RangeEnd) > 0 {
+ // After the range end
+ return false
+ }
+ }
+
+ return true
+}
+
+// Overlaps returns true if there is any overlap between ranges
+func (r *Range) Overlaps(r1 *Range) bool {
+ // different familes
+ if len(r.RangeStart) != len(r1.RangeStart) {
+ return false
+ }
+
+ return r.Contains(r1.RangeStart) ||
+ r.Contains(r1.RangeEnd) ||
+ r1.Contains(r.RangeStart) ||
+ r1.Contains(r.RangeEnd)
+}
+
+func (r *Range) String() string {
+ return fmt.Sprintf("%s-%s", r.RangeStart.String(), r.RangeEnd.String())
+}
+
+// canonicalizeIP makes sure a provided ip is in standard form
+func canonicalizeIP(ip *net.IP) error {
+ if ip.To4() != nil {
+ *ip = ip.To4()
+ return nil
+ } else if ip.To16() != nil {
+ *ip = ip.To16()
+ return nil
+ }
+ return fmt.Errorf("IP %s not v4 nor v6", *ip)
+}
+
+// Determine the last IP of a subnet, excluding the broadcast if IPv4
+func lastIP(subnet types.IPNet) net.IP {
+ var end net.IP
+ for i := 0; i < len(subnet.IP); i++ {
+ end = append(end, subnet.IP[i]|^subnet.Mask[i])
+ }
+ if subnet.IP.To4() != nil {
+ end[3]--
+ }
+
+ return end
+}
diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/range_set.go b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/range_set.go
new file mode 100644
index 000000000..da957f535
--- /dev/null
+++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator/range_set.go
@@ -0,0 +1,97 @@
+// Copyright 2017 CNI authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package allocator
+
+import (
+ "fmt"
+ "net"
+ "strings"
+)
+
+// Contains returns true if any range in this set contains an IP
+func (s *RangeSet) Contains(addr net.IP) bool {
+ r, _ := s.RangeFor(addr)
+ return r != nil
+}
+
+// RangeFor finds the range that contains an IP, or nil if not found
+func (s *RangeSet) RangeFor(addr net.IP) (*Range, error) {
+ if err := canonicalizeIP(&addr); err != nil {
+ return nil, err
+ }
+
+ for _, r := range *s {
+ if r.Contains(addr) {
+ return &r, nil
+ }
+ }
+
+ return nil, fmt.Errorf("%s not in range set %s", addr.String(), s.String())
+}
+
+// Overlaps returns true if any ranges in any set overlap with this one
+func (s *RangeSet) Overlaps(p1 *RangeSet) bool {
+ for _, r := range *s {
+ for _, r1 := range *p1 {
+ if r.Overlaps(&r1) {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+// Canonicalize ensures the RangeSet is in a standard form, and detects any
+// invalid input. Call Range.Canonicalize() on every Range in the set
+func (s *RangeSet) Canonicalize() error {
+ if len(*s) == 0 {
+ return fmt.Errorf("empty range set")
+ }
+
+ fam := 0
+ for i := range *s {
+ if err := (*s)[i].Canonicalize(); err != nil {
+ return err
+ }
+ if i == 0 {
+ fam = len((*s)[i].RangeStart)
+ } else {
+ if fam != len((*s)[i].RangeStart) {
+ return fmt.Errorf("mixed address families")
+ }
+ }
+ }
+
+ // Make sure none of the ranges in the set overlap
+ l := len(*s)
+ for i, r1 := range (*s)[:l-1] {
+ for _, r2 := range (*s)[i+1:] {
+ if r1.Overlaps(&r2) {
+ return fmt.Errorf("subnets %s and %s overlap", r1.String(), r2.String())
+ }
+ }
+ }
+
+ return nil
+}
+
+func (s *RangeSet) String() string {
+ out := []string{}
+ for _, r := range *s {
+ out = append(out, r.String())
+ }
+
+ return strings.Join(out, ",")
+}
diff --git a/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/store.go b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/store.go
new file mode 100644
index 000000000..4ea845da7
--- /dev/null
+++ b/vendor/github.com/containernetworking/plugins/plugins/ipam/host-local/backend/store.go
@@ -0,0 +1,27 @@
+// 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 backend
+
+import "net"
+
+type Store interface {
+ Lock() error
+ Unlock() error
+ Close() error
+ Reserve(id string, ifname string, ip net.IP, rangeID string) (bool, error)
+ LastReservedIP(rangeID string) (net.IP, error)
+ Release(ip net.IP) error
+ ReleaseByID(id string, ifname string) error
+}
diff --git a/vendor/github.com/coreos/go-iptables/LICENSE b/vendor/github.com/coreos/go-iptables/LICENSE
new file mode 100644
index 000000000..37ec93a14
--- /dev/null
+++ b/vendor/github.com/coreos/go-iptables/LICENSE
@@ -0,0 +1,191 @@
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and
+distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright
+owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities
+that control, are controlled by, or are under common control with that entity.
+For the purposes of this definition, "control" means (i) the power, direct or
+indirect, to cause the direction or management of such entity, whether by
+contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
+outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising
+permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including
+but not limited to software source code, documentation source, and configuration
+files.
+
+"Object" form shall mean any form resulting from mechanical transformation or
+translation of a Source form, including but not limited to compiled object code,
+generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made
+available under the License, as indicated by a copyright notice that is included
+in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that
+is based on (or derived from) the Work and for which the editorial revisions,
+annotations, elaborations, or other modifications represent, as a whole, an
+original work of authorship. For the purposes of this License, Derivative Works
+shall not include works that remain separable from, or merely link (or bind by
+name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version
+of the Work and any modifications or additions to that Work or Derivative Works
+thereof, that is intentionally submitted to Licensor for inclusion in the Work
+by the copyright owner or by an individual or Legal Entity authorized to submit
+on behalf of the copyright owner. For the purposes of this definition,
+"submitted" means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems, and
+issue tracking systems that are managed by, or on behalf of, the Licensor for
+the purpose of discussing and improving the Work, but excluding communication
+that is conspicuously marked or otherwise designated in writing by the copyright
+owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
+of whom a Contribution has been received by Licensor and subsequently
+incorporated within the Work.
+
+2. Grant of Copyright License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the Work and such
+Derivative Works in Source or Object form.
+
+3. Grant of Patent License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable (except as stated in this section) patent license to make, have
+made, use, offer to sell, sell, import, and otherwise transfer the Work, where
+such license applies only to those patent claims licensable by such Contributor
+that are necessarily infringed by their Contribution(s) alone or by combination
+of their Contribution(s) with the Work to which such Contribution(s) was
+submitted. If You institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work or a
+Contribution incorporated within the Work constitutes direct or contributory
+patent infringement, then any patent licenses granted to You under this License
+for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution.
+
+You may reproduce and distribute copies of the Work or Derivative Works thereof
+in any medium, with or without modifications, and in Source or Object form,
+provided that You meet the following conditions:
+
+You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+You must retain, in the Source form of any Derivative Works that You distribute,
+all copyright, patent, trademark, and attribution notices from the Source form
+of the Work, excluding those notices that do not pertain to any part of the
+Derivative Works; and
+If the Work includes a "NOTICE" text file as part of its distribution, then any
+Derivative Works that You distribute must include a readable copy of the
+attribution notices contained within such NOTICE file, excluding those notices
+that do not pertain to any part of the Derivative Works, in at least one of the
+following places: within a NOTICE text file distributed as part of the
+Derivative Works; within the Source form or documentation, if provided along
+with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents of
+the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works that
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
+provided that such additional attribution notices cannot be construed as
+modifying the License.
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works as a whole,
+provided Your use, reproduction, and distribution of the Work otherwise complies
+with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted
+for inclusion in the Work by You to the Licensor shall be under the terms and
+conditions of this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
+any separate license agreement you may have executed with Licensor regarding
+such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks,
+service marks, or product names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+including, without limitation, any warranties or conditions of TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+solely responsible for determining the appropriateness of using or
+redistributing the Work and assume any risks associated with Your exercise of
+permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence),
+contract, or otherwise, unless required by applicable law (such as deliberate
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License or
+out of the use or inability to use the Work (including but not limited to
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
+any and all other commercial damages or losses), even if such Contributor has
+been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+other liability obligations and/or rights consistent with this License. However,
+in accepting such obligations, You may act only on Your own behalf and on Your
+sole responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason of your
+accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets "[]" replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same "printed page" as the copyright notice for easier identification within
+third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/vendor/github.com/coreos/go-iptables/NOTICE b/vendor/github.com/coreos/go-iptables/NOTICE
new file mode 100644
index 000000000..23a0ada2f
--- /dev/null
+++ b/vendor/github.com/coreos/go-iptables/NOTICE
@@ -0,0 +1,5 @@
+CoreOS Project
+Copyright 2018 CoreOS, Inc
+
+This product includes software developed at CoreOS, Inc.
+(http://www.coreos.com/).
diff --git a/vendor/github.com/coreos/go-iptables/iptables/iptables.go b/vendor/github.com/coreos/go-iptables/iptables/iptables.go
new file mode 100644
index 000000000..2ed875bb5
--- /dev/null
+++ b/vendor/github.com/coreos/go-iptables/iptables/iptables.go
@@ -0,0 +1,598 @@
+// Copyright 2015 CoreOS, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package iptables
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "net"
+ "os/exec"
+ "regexp"
+ "strconv"
+ "strings"
+ "syscall"
+)
+
+// Adds the output of stderr to exec.ExitError
+type Error struct {
+ exec.ExitError
+ cmd exec.Cmd
+ msg string
+ proto Protocol
+ exitStatus *int //for overriding
+}
+
+func (e *Error) ExitStatus() int {
+ if e.exitStatus != nil {
+ return *e.exitStatus
+ }
+ return e.Sys().(syscall.WaitStatus).ExitStatus()
+}
+
+func (e *Error) Error() string {
+ return fmt.Sprintf("running %v: exit status %v: %v", e.cmd.Args, e.ExitStatus(), e.msg)
+}
+
+// IsNotExist returns true if the error is due to the chain or rule not existing
+func (e *Error) IsNotExist() bool {
+ return e.ExitStatus() == 1 &&
+ (e.msg == fmt.Sprintf("%s: Bad rule (does a matching rule exist in that chain?).\n", getIptablesCommand(e.proto)) ||
+ e.msg == fmt.Sprintf("%s: No chain/target/match by that name.\n", getIptablesCommand(e.proto)))
+}
+
+// Protocol to differentiate between IPv4 and IPv6
+type Protocol byte
+
+const (
+ ProtocolIPv4 Protocol = iota
+ ProtocolIPv6
+)
+
+type IPTables struct {
+ path string
+ proto Protocol
+ hasCheck bool
+ hasWait bool
+ hasRandomFully bool
+ v1 int
+ v2 int
+ v3 int
+ mode string // the underlying iptables operating mode, e.g. nf_tables
+}
+
+// Stat represents a structured statistic entry.
+type Stat struct {
+ Packets uint64 `json:"pkts"`
+ Bytes uint64 `json:"bytes"`
+ Target string `json:"target"`
+ Protocol string `json:"prot"`
+ Opt string `json:"opt"`
+ Input string `json:"in"`
+ Output string `json:"out"`
+ Source *net.IPNet `json:"source"`
+ Destination *net.IPNet `json:"destination"`
+ Options string `json:"options"`
+}
+
+// New creates a new IPTables.
+// For backwards compatibility, this always uses IPv4, i.e. "iptables".
+func New() (*IPTables, error) {
+ return NewWithProtocol(ProtocolIPv4)
+}
+
+// New creates a new IPTables for the given proto.
+// The proto will determine which command is used, either "iptables" or "ip6tables".
+func NewWithProtocol(proto Protocol) (*IPTables, error) {
+ path, err := exec.LookPath(getIptablesCommand(proto))
+ if err != nil {
+ return nil, err
+ }
+ vstring, err := getIptablesVersionString(path)
+ v1, v2, v3, mode, err := extractIptablesVersion(vstring)
+
+ checkPresent, waitPresent, randomFullyPresent := getIptablesCommandSupport(v1, v2, v3)
+
+ ipt := IPTables{
+ path: path,
+ proto: proto,
+ hasCheck: checkPresent,
+ hasWait: waitPresent,
+ hasRandomFully: randomFullyPresent,
+ v1: v1,
+ v2: v2,
+ v3: v3,
+ mode: mode,
+ }
+ return &ipt, nil
+}
+
+// Proto returns the protocol used by this IPTables.
+func (ipt *IPTables) Proto() Protocol {
+ return ipt.proto
+}
+
+// Exists checks if given rulespec in specified table/chain exists
+func (ipt *IPTables) Exists(table, chain string, rulespec ...string) (bool, error) {
+ if !ipt.hasCheck {
+ return ipt.existsForOldIptables(table, chain, rulespec)
+
+ }
+ cmd := append([]string{"-t", table, "-C", chain}, rulespec...)
+ err := ipt.run(cmd...)
+ eerr, eok := err.(*Error)
+ switch {
+ case err == nil:
+ return true, nil
+ case eok && eerr.ExitStatus() == 1:
+ return false, nil
+ default:
+ return false, err
+ }
+}
+
+// Insert inserts rulespec to specified table/chain (in specified pos)
+func (ipt *IPTables) Insert(table, chain string, pos int, rulespec ...string) error {
+ cmd := append([]string{"-t", table, "-I", chain, strconv.Itoa(pos)}, rulespec...)
+ return ipt.run(cmd...)
+}
+
+// Append appends rulespec to specified table/chain
+func (ipt *IPTables) Append(table, chain string, rulespec ...string) error {
+ cmd := append([]string{"-t", table, "-A", chain}, rulespec...)
+ return ipt.run(cmd...)
+}
+
+// AppendUnique acts like Append except that it won't add a duplicate
+func (ipt *IPTables) AppendUnique(table, chain string, rulespec ...string) error {
+ exists, err := ipt.Exists(table, chain, rulespec...)
+ if err != nil {
+ return err
+ }
+
+ if !exists {
+ return ipt.Append(table, chain, rulespec...)
+ }
+
+ return nil
+}
+
+// Delete removes rulespec in specified table/chain
+func (ipt *IPTables) Delete(table, chain string, rulespec ...string) error {
+ cmd := append([]string{"-t", table, "-D", chain}, rulespec...)
+ return ipt.run(cmd...)
+}
+
+// List rules in specified table/chain
+func (ipt *IPTables) List(table, chain string) ([]string, error) {
+ args := []string{"-t", table, "-S", chain}
+ return ipt.executeList(args)
+}
+
+// List rules (with counters) in specified table/chain
+func (ipt *IPTables) ListWithCounters(table, chain string) ([]string, error) {
+ args := []string{"-t", table, "-v", "-S", chain}
+ return ipt.executeList(args)
+}
+
+// ListChains returns a slice containing the name of each chain in the specified table.
+func (ipt *IPTables) ListChains(table string) ([]string, error) {
+ args := []string{"-t", table, "-S"}
+
+ result, err := ipt.executeList(args)
+ if err != nil {
+ return nil, err
+ }
+
+ // Iterate over rules to find all default (-P) and user-specified (-N) chains.
+ // Chains definition always come before rules.
+ // Format is the following:
+ // -P OUTPUT ACCEPT
+ // -N Custom
+ var chains []string
+ for _, val := range result {
+ if strings.HasPrefix(val, "-P") || strings.HasPrefix(val, "-N") {
+ chains = append(chains, strings.Fields(val)[1])
+ } else {
+ break
+ }
+ }
+ return chains, nil
+}
+
+// Stats lists rules including the byte and packet counts
+func (ipt *IPTables) Stats(table, chain string) ([][]string, error) {
+ args := []string{"-t", table, "-L", chain, "-n", "-v", "-x"}
+ lines, err := ipt.executeList(args)
+ if err != nil {
+ return nil, err
+ }
+
+ appendSubnet := func(addr string) string {
+ if strings.IndexByte(addr, byte('/')) < 0 {
+ if strings.IndexByte(addr, '.') < 0 {
+ return addr + "/128"
+ }
+ return addr + "/32"
+ }
+ return addr
+ }
+
+ ipv6 := ipt.proto == ProtocolIPv6
+
+ rows := [][]string{}
+ for i, line := range lines {
+ // Skip over chain name and field header
+ if i < 2 {
+ continue
+ }
+
+ // Fields:
+ // 0=pkts 1=bytes 2=target 3=prot 4=opt 5=in 6=out 7=source 8=destination 9=options
+ line = strings.TrimSpace(line)
+ fields := strings.Fields(line)
+
+ // The ip6tables verbose output cannot be naively split due to the default "opt"
+ // field containing 2 single spaces.
+ if ipv6 {
+ // Check if field 6 is "opt" or "source" address
+ dest := fields[6]
+ ip, _, _ := net.ParseCIDR(dest)
+ if ip == nil {
+ ip = net.ParseIP(dest)
+ }
+
+ // If we detected a CIDR or IP, the "opt" field is empty.. insert it.
+ if ip != nil {
+ f := []string{}
+ f = append(f, fields[:4]...)
+ f = append(f, " ") // Empty "opt" field for ip6tables
+ f = append(f, fields[4:]...)
+ fields = f
+ }
+ }
+
+ // Adjust "source" and "destination" to include netmask, to match regular
+ // List output
+ fields[7] = appendSubnet(fields[7])
+ fields[8] = appendSubnet(fields[8])
+
+ // Combine "options" fields 9... into a single space-delimited field.
+ options := fields[9:]
+ fields = fields[:9]
+ fields = append(fields, strings.Join(options, " "))
+ rows = append(rows, fields)
+ }
+ return rows, nil
+}
+
+// ParseStat parses a single statistic row into a Stat struct. The input should
+// be a string slice that is returned from calling the Stat method.
+func (ipt *IPTables) ParseStat(stat []string) (parsed Stat, err error) {
+ // For forward-compatibility, expect at least 10 fields in the stat
+ if len(stat) < 10 {
+ return parsed, fmt.Errorf("stat contained fewer fields than expected")
+ }
+
+ // Convert the fields that are not plain strings
+ parsed.Packets, err = strconv.ParseUint(stat[0], 0, 64)
+ if err != nil {
+ return parsed, fmt.Errorf(err.Error(), "could not parse packets")
+ }
+ parsed.Bytes, err = strconv.ParseUint(stat[1], 0, 64)
+ if err != nil {
+ return parsed, fmt.Errorf(err.Error(), "could not parse bytes")
+ }
+ _, parsed.Source, err = net.ParseCIDR(stat[7])
+ if err != nil {
+ return parsed, fmt.Errorf(err.Error(), "could not parse source")
+ }
+ _, parsed.Destination, err = net.ParseCIDR(stat[8])
+ if err != nil {
+ return parsed, fmt.Errorf(err.Error(), "could not parse destination")
+ }
+
+ // Put the fields that are strings
+ parsed.Target = stat[2]
+ parsed.Protocol = stat[3]
+ parsed.Opt = stat[4]
+ parsed.Input = stat[5]
+ parsed.Output = stat[6]
+ parsed.Options = stat[9]
+
+ return parsed, nil
+}
+
+// StructuredStats returns statistics as structured data which may be further
+// parsed and marshaled.
+func (ipt *IPTables) StructuredStats(table, chain string) ([]Stat, error) {
+ rawStats, err := ipt.Stats(table, chain)
+ if err != nil {
+ return nil, err
+ }
+
+ structStats := []Stat{}
+ for _, rawStat := range rawStats {
+ stat, err := ipt.ParseStat(rawStat)
+ if err != nil {
+ return nil, err
+ }
+ structStats = append(structStats, stat)
+ }
+
+ return structStats, nil
+}
+
+func (ipt *IPTables) executeList(args []string) ([]string, error) {
+ var stdout bytes.Buffer
+ if err := ipt.runWithOutput(args, &stdout); err != nil {
+ return nil, err
+ }
+
+ rules := strings.Split(stdout.String(), "\n")
+
+ // strip trailing newline
+ if len(rules) > 0 && rules[len(rules)-1] == "" {
+ rules = rules[:len(rules)-1]
+ }
+
+ // nftables mode doesn't return an error code when listing a non-existent
+ // chain. Patch that up.
+ if len(rules) == 0 && ipt.mode == "nf_tables" {
+ v := 1
+ return nil, &Error{
+ cmd: exec.Cmd{Args: args},
+ msg: fmt.Sprintf("%s: No chain/target/match by that name.\n", getIptablesCommand(ipt.proto)),
+ proto: ipt.proto,
+ exitStatus: &v,
+ }
+ }
+
+ for i, rule := range rules {
+ rules[i] = filterRuleOutput(rule)
+ }
+
+ return rules, nil
+}
+
+// NewChain creates a new chain in the specified table.
+// If the chain already exists, it will result in an error.
+func (ipt *IPTables) NewChain(table, chain string) error {
+ return ipt.run("-t", table, "-N", chain)
+}
+
+const existsErr = 1
+
+// ClearChain flushed (deletes all rules) in the specified table/chain.
+// If the chain does not exist, a new one will be created
+func (ipt *IPTables) ClearChain(table, chain string) error {
+ err := ipt.NewChain(table, chain)
+
+ eerr, eok := err.(*Error)
+ switch {
+ case err == nil:
+ return nil
+ case eok && eerr.ExitStatus() == existsErr:
+ // chain already exists. Flush (clear) it.
+ return ipt.run("-t", table, "-F", chain)
+ default:
+ return err
+ }
+}
+
+// RenameChain renames the old chain to the new one.
+func (ipt *IPTables) RenameChain(table, oldChain, newChain string) error {
+ return ipt.run("-t", table, "-E", oldChain, newChain)
+}
+
+// DeleteChain deletes the chain in the specified table.
+// The chain must be empty
+func (ipt *IPTables) DeleteChain(table, chain string) error {
+ return ipt.run("-t", table, "-X", chain)
+}
+
+// ChangePolicy changes policy on chain to target
+func (ipt *IPTables) ChangePolicy(table, chain, target string) error {
+ return ipt.run("-t", table, "-P", chain, target)
+}
+
+// Check if the underlying iptables command supports the --random-fully flag
+func (ipt *IPTables) HasRandomFully() bool {
+ return ipt.hasRandomFully
+}
+
+// Return version components of the underlying iptables command
+func (ipt *IPTables) GetIptablesVersion() (int, int, int) {
+ return ipt.v1, ipt.v2, ipt.v3
+}
+
+// run runs an iptables command with the given arguments, ignoring
+// any stdout output
+func (ipt *IPTables) run(args ...string) error {
+ return ipt.runWithOutput(args, nil)
+}
+
+// runWithOutput runs an iptables command with the given arguments,
+// writing any stdout output to the given writer
+func (ipt *IPTables) runWithOutput(args []string, stdout io.Writer) error {
+ args = append([]string{ipt.path}, args...)
+ if ipt.hasWait {
+ args = append(args, "--wait")
+ } else {
+ fmu, err := newXtablesFileLock()
+ if err != nil {
+ return err
+ }
+ ul, err := fmu.tryLock()
+ if err != nil {
+ return err
+ }
+ defer ul.Unlock()
+ }
+
+ var stderr bytes.Buffer
+ cmd := exec.Cmd{
+ Path: ipt.path,
+ Args: args,
+ Stdout: stdout,
+ Stderr: &stderr,
+ }
+
+ if err := cmd.Run(); err != nil {
+ switch e := err.(type) {
+ case *exec.ExitError:
+ return &Error{*e, cmd, stderr.String(), ipt.proto, nil}
+ default:
+ return err
+ }
+ }
+
+ return nil
+}
+
+// getIptablesCommand returns the correct command for the given protocol, either "iptables" or "ip6tables".
+func getIptablesCommand(proto Protocol) string {
+ if proto == ProtocolIPv6 {
+ return "ip6tables"
+ } else {
+ return "iptables"
+ }
+}
+
+// Checks if iptables has the "-C" and "--wait" flag
+func getIptablesCommandSupport(v1 int, v2 int, v3 int) (bool, bool, bool) {
+ return iptablesHasCheckCommand(v1, v2, v3), iptablesHasWaitCommand(v1, v2, v3), iptablesHasRandomFully(v1, v2, v3)
+}
+
+// getIptablesVersion returns the first three components of the iptables version
+// and the operating mode (e.g. nf_tables or legacy)
+// e.g. "iptables v1.3.66" would return (1, 3, 66, legacy, nil)
+func extractIptablesVersion(str string) (int, int, int, string, error) {
+ versionMatcher := regexp.MustCompile(`v([0-9]+)\.([0-9]+)\.([0-9]+)(?:\s+\((\w+))?`)
+ result := versionMatcher.FindStringSubmatch(str)
+ if result == nil {
+ return 0, 0, 0, "", fmt.Errorf("no iptables version found in string: %s", str)
+ }
+
+ v1, err := strconv.Atoi(result[1])
+ if err != nil {
+ return 0, 0, 0, "", err
+ }
+
+ v2, err := strconv.Atoi(result[2])
+ if err != nil {
+ return 0, 0, 0, "", err
+ }
+
+ v3, err := strconv.Atoi(result[3])
+ if err != nil {
+ return 0, 0, 0, "", err
+ }
+
+ mode := "legacy"
+ if result[4] != "" {
+ mode = result[4]
+ }
+ return v1, v2, v3, mode, nil
+}
+
+// Runs "iptables --version" to get the version string
+func getIptablesVersionString(path string) (string, error) {
+ cmd := exec.Command(path, "--version")
+ var out bytes.Buffer
+ cmd.Stdout = &out
+ err := cmd.Run()
+ if err != nil {
+ return "", err
+ }
+ return out.String(), nil
+}
+
+// Checks if an iptables version is after 1.4.11, when --check was added
+func iptablesHasCheckCommand(v1 int, v2 int, v3 int) bool {
+ if v1 > 1 {
+ return true
+ }
+ if v1 == 1 && v2 > 4 {
+ return true
+ }
+ if v1 == 1 && v2 == 4 && v3 >= 11 {
+ return true
+ }
+ return false
+}
+
+// Checks if an iptables version is after 1.4.20, when --wait was added
+func iptablesHasWaitCommand(v1 int, v2 int, v3 int) bool {
+ if v1 > 1 {
+ return true
+ }
+ if v1 == 1 && v2 > 4 {
+ return true
+ }
+ if v1 == 1 && v2 == 4 && v3 >= 20 {
+ return true
+ }
+ return false
+}
+
+// Checks if an iptables version is after 1.6.2, when --random-fully was added
+func iptablesHasRandomFully(v1 int, v2 int, v3 int) bool {
+ if v1 > 1 {
+ return true
+ }
+ if v1 == 1 && v2 > 6 {
+ return true
+ }
+ if v1 == 1 && v2 == 6 && v3 >= 2 {
+ return true
+ }
+ return false
+}
+
+// Checks if a rule specification exists for a table
+func (ipt *IPTables) existsForOldIptables(table, chain string, rulespec []string) (bool, error) {
+ rs := strings.Join(append([]string{"-A", chain}, rulespec...), " ")
+ args := []string{"-t", table, "-S"}
+ var stdout bytes.Buffer
+ err := ipt.runWithOutput(args, &stdout)
+ if err != nil {
+ return false, err
+ }
+ return strings.Contains(stdout.String(), rs), nil
+}
+
+// counterRegex is the regex used to detect nftables counter format
+var counterRegex = regexp.MustCompile(`^\[([0-9]+):([0-9]+)\] `)
+
+// filterRuleOutput works around some inconsistencies in output.
+// For example, when iptables is in legacy vs. nftables mode, it produces
+// different results.
+func filterRuleOutput(rule string) string {
+ out := rule
+
+ // work around an output difference in nftables mode where counters
+ // are output in iptables-save format, rather than iptables -S format
+ // The string begins with "[0:0]"
+ //
+ // Fixes #49
+ if groups := counterRegex.FindStringSubmatch(out); groups != nil {
+ // drop the brackets
+ out = out[len(groups[0]):]
+ out = fmt.Sprintf("%s -c %s %s", out, groups[1], groups[2])
+ }
+
+ return out
+}
diff --git a/vendor/github.com/coreos/go-iptables/iptables/lock.go b/vendor/github.com/coreos/go-iptables/iptables/lock.go
new file mode 100644
index 000000000..a88e92b4e
--- /dev/null
+++ b/vendor/github.com/coreos/go-iptables/iptables/lock.go
@@ -0,0 +1,84 @@
+// Copyright 2015 CoreOS, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package iptables
+
+import (
+ "os"
+ "sync"
+ "syscall"
+)
+
+const (
+ // In earlier versions of iptables, the xtables lock was implemented
+ // via a Unix socket, but now flock is used via this lockfile:
+ // http://git.netfilter.org/iptables/commit/?id=aa562a660d1555b13cffbac1e744033e91f82707
+ // Note the LSB-conforming "/run" directory does not exist on old
+ // distributions, so assume "/var" is symlinked
+ xtablesLockFilePath = "/var/run/xtables.lock"
+
+ defaultFilePerm = 0600
+)
+
+type Unlocker interface {
+ Unlock() error
+}
+
+type nopUnlocker struct{}
+
+func (_ nopUnlocker) Unlock() error { return nil }
+
+type fileLock struct {
+ // mu is used to protect against concurrent invocations from within this process
+ mu sync.Mutex
+ fd int
+}
+
+// tryLock takes an exclusive lock on the xtables lock file without blocking.
+// This is best-effort only: if the exclusive lock would block (i.e. because
+// another process already holds it), no error is returned. Otherwise, any
+// error encountered during the locking operation is returned.
+// The returned Unlocker should be used to release the lock when the caller is
+// done invoking iptables commands.
+func (l *fileLock) tryLock() (Unlocker, error) {
+ l.mu.Lock()
+ err := syscall.Flock(l.fd, syscall.LOCK_EX|syscall.LOCK_NB)
+ switch err {
+ case syscall.EWOULDBLOCK:
+ l.mu.Unlock()
+ return nopUnlocker{}, nil
+ case nil:
+ return l, nil
+ default:
+ l.mu.Unlock()
+ return nil, err
+ }
+}
+
+// Unlock closes the underlying file, which implicitly unlocks it as well. It
+// also unlocks the associated mutex.
+func (l *fileLock) Unlock() error {
+ defer l.mu.Unlock()
+ return syscall.Close(l.fd)
+}
+
+// newXtablesFileLock opens a new lock on the xtables lockfile without
+// acquiring the lock
+func newXtablesFileLock() (*fileLock, error) {
+ fd, err := syscall.Open(xtablesLockFilePath, os.O_CREATE, defaultFilePerm)
+ if err != nil {
+ return nil, err
+ }
+ return &fileLock{fd: fd}, nil
+}
diff --git a/vendor/github.com/safchain/ethtool/.gitignore b/vendor/github.com/safchain/ethtool/.gitignore
new file mode 100644
index 000000000..db6cadffd
--- /dev/null
+++ b/vendor/github.com/safchain/ethtool/.gitignore
@@ -0,0 +1,27 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof
+
+# Skip compiled example binary file
+/example/example
diff --git a/vendor/github.com/safchain/ethtool/.travis.yml b/vendor/github.com/safchain/ethtool/.travis.yml
new file mode 100644
index 000000000..4f2ee4d97
--- /dev/null
+++ b/vendor/github.com/safchain/ethtool/.travis.yml
@@ -0,0 +1 @@
+language: go
diff --git a/vendor/github.com/safchain/ethtool/LICENSE b/vendor/github.com/safchain/ethtool/LICENSE
new file mode 100644
index 000000000..8f71f43fe
--- /dev/null
+++ b/vendor/github.com/safchain/ethtool/LICENSE
@@ -0,0 +1,202 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ 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.
+
diff --git a/vendor/github.com/safchain/ethtool/Makefile b/vendor/github.com/safchain/ethtool/Makefile
new file mode 100644
index 000000000..67d2da395
--- /dev/null
+++ b/vendor/github.com/safchain/ethtool/Makefile
@@ -0,0 +1,4 @@
+all: build
+
+build:
+ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build
diff --git a/vendor/github.com/safchain/ethtool/README.md b/vendor/github.com/safchain/ethtool/README.md
new file mode 100644
index 000000000..1f146229c
--- /dev/null
+++ b/vendor/github.com/safchain/ethtool/README.md
@@ -0,0 +1,60 @@
+# ethtool go package #
+
+[![Build Status](https://travis-ci.org/safchain/ethtool.png?branch=master)](https://travis-ci.org/safchain/ethtool)
+[![GoDoc](https://godoc.org/github.com/safchain/ethtool?status.svg)](https://godoc.org/github.com/safchain/ethtool)
+
+The ethtool package aims to provide a library giving a simple access to the Linux SIOCETHTOOL ioctl operations. It can be used to retrieve informations from a network device like statistics, driver related informations or even the peer of a VETH interface.
+
+## Build and Test ##
+
+go get command:
+
+ go get github.com/safchain/ethtool
+
+Testing
+
+In order to run te
+
+ go test github.com/safchain/ethtool
+
+## Examples ##
+
+```go
+package main
+
+import (
+ "fmt"
+
+ "github.com/safchain/ethtool"
+)
+
+func main() {
+ ethHandle, err := ethtool.NewEthtool()
+ if err != nil {
+ panic(err.Error())
+ }
+ defer ethHandle.Close()
+
+ // Retrieve tx from eth0
+ stats, err := ethHandle.Stats("eth0")
+ if err != nil {
+ panic(err.Error())
+ }
+ fmt.Printf("TX: %d\n", stats["tx_bytes"])
+
+ // Retrieve peer index of a veth interface
+ stats, err = ethHandle.Stats("veth0")
+ if err != nil {
+ panic(err.Error())
+ }
+ fmt.Printf("Peer Index: %d\n", stats["peer_ifindex"])
+}
+```
+
+## LICENSE ##
+
+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.
diff --git a/vendor/github.com/safchain/ethtool/ethtool.go b/vendor/github.com/safchain/ethtool/ethtool.go
new file mode 100644
index 000000000..8dcc78c05
--- /dev/null
+++ b/vendor/github.com/safchain/ethtool/ethtool.go
@@ -0,0 +1,541 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 ethtool aims to provide a library giving a simple access to the
+// Linux SIOCETHTOOL ioctl operations. It can be used to retrieve informations
+// from a network device like statistics, driver related informations or
+// even the peer of a VETH interface.
+package ethtool
+
+import (
+ "bytes"
+ "encoding/hex"
+ "fmt"
+ "strings"
+ "syscall"
+ "unsafe"
+)
+
+// Maximum size of an interface name
+const (
+ IFNAMSIZ = 16
+)
+
+// ioctl ethtool request
+const (
+ SIOCETHTOOL = 0x8946
+)
+
+// ethtool stats related constants.
+const (
+ ETH_GSTRING_LEN = 32
+ ETH_SS_STATS = 1
+ ETH_SS_FEATURES = 4
+ ETHTOOL_GDRVINFO = 0x00000003
+ ETHTOOL_GSTRINGS = 0x0000001b
+ ETHTOOL_GSTATS = 0x0000001d
+ // other CMDs from ethtool-copy.h of ethtool-3.5 package
+ ETHTOOL_GSET = 0x00000001 /* Get settings. */
+ ETHTOOL_SSET = 0x00000002 /* Set settings. */
+ ETHTOOL_GMSGLVL = 0x00000007 /* Get driver message level */
+ ETHTOOL_SMSGLVL = 0x00000008 /* Set driver msg level. */
+ /* Get link status for host, i.e. whether the interface *and* the
+ * physical port (if there is one) are up (ethtool_value). */
+ ETHTOOL_GLINK = 0x0000000a
+ ETHTOOL_GMODULEINFO = 0x00000042 /* Get plug-in module information */
+ ETHTOOL_GMODULEEEPROM = 0x00000043 /* Get plug-in module eeprom */
+ ETHTOOL_GPERMADDR = 0x00000020
+ ETHTOOL_GFEATURES = 0x0000003a /* Get device offload settings */
+ ETHTOOL_SFEATURES = 0x0000003b /* Change device offload settings */
+ ETHTOOL_GFLAGS = 0x00000025 /* Get flags bitmap(ethtool_value) */
+ ETHTOOL_GSSET_INFO = 0x00000037 /* Get string set info */
+)
+
+// MAX_GSTRINGS maximum number of stats entries that ethtool can
+// retrieve currently.
+const (
+ MAX_GSTRINGS = 1000
+ MAX_FEATURE_BLOCKS = (MAX_GSTRINGS + 32 - 1) / 32
+ EEPROM_LEN = 640
+ PERMADDR_LEN = 32
+)
+
+type ifreq struct {
+ ifr_name [IFNAMSIZ]byte
+ ifr_data uintptr
+}
+
+// following structures comes from uapi/linux/ethtool.h
+type ethtoolSsetInfo struct {
+ cmd uint32
+ reserved uint32
+ sset_mask uint32
+ data uintptr
+}
+
+type ethtoolGetFeaturesBlock struct {
+ available uint32
+ requested uint32
+ active uint32
+ never_changed uint32
+}
+
+type ethtoolGfeatures struct {
+ cmd uint32
+ size uint32
+ blocks [MAX_FEATURE_BLOCKS]ethtoolGetFeaturesBlock
+}
+
+type ethtoolSetFeaturesBlock struct {
+ valid uint32
+ requested uint32
+}
+
+type ethtoolSfeatures struct {
+ cmd uint32
+ size uint32
+ blocks [MAX_FEATURE_BLOCKS]ethtoolSetFeaturesBlock
+}
+
+type ethtoolDrvInfo struct {
+ cmd uint32
+ driver [32]byte
+ version [32]byte
+ fw_version [32]byte
+ bus_info [32]byte
+ erom_version [32]byte
+ reserved2 [12]byte
+ n_priv_flags uint32
+ n_stats uint32
+ testinfo_len uint32
+ eedump_len uint32
+ regdump_len uint32
+}
+
+type ethtoolGStrings struct {
+ cmd uint32
+ string_set uint32
+ len uint32
+ data [MAX_GSTRINGS * ETH_GSTRING_LEN]byte
+}
+
+type ethtoolStats struct {
+ cmd uint32
+ n_stats uint32
+ data [MAX_GSTRINGS]uint64
+}
+
+type ethtoolEeprom struct {
+ cmd uint32
+ magic uint32
+ offset uint32
+ len uint32
+ data [EEPROM_LEN]byte
+}
+
+type ethtoolModInfo struct {
+ cmd uint32
+ tpe uint32
+ eeprom_len uint32
+ reserved [8]uint32
+}
+
+type ethtoolLink struct {
+ cmd uint32
+ data uint32
+}
+
+type ethtoolPermAddr struct {
+ cmd uint32
+ size uint32
+ data [PERMADDR_LEN]byte
+}
+
+type Ethtool struct {
+ fd int
+}
+
+// DriverName returns the driver name of the given interface name.
+func (e *Ethtool) DriverName(intf string) (string, error) {
+ info, err := e.getDriverInfo(intf)
+ if err != nil {
+ return "", err
+ }
+ return string(bytes.Trim(info.driver[:], "\x00")), nil
+}
+
+// BusInfo returns the bus information of the given interface name.
+func (e *Ethtool) BusInfo(intf string) (string, error) {
+ info, err := e.getDriverInfo(intf)
+ if err != nil {
+ return "", err
+ }
+ return string(bytes.Trim(info.bus_info[:], "\x00")), nil
+}
+
+// ModuleEeprom returns Eeprom information of the given interface name.
+func (e *Ethtool) ModuleEeprom(intf string) ([]byte, error) {
+ eeprom, _, err := e.getModuleEeprom(intf)
+ if err != nil {
+ return nil, err
+ }
+
+ return eeprom.data[:eeprom.len], nil
+}
+
+// ModuleEeprom returns Eeprom information of the given interface name.
+func (e *Ethtool) ModuleEepromHex(intf string) (string, error) {
+ eeprom, _, err := e.getModuleEeprom(intf)
+ if err != nil {
+ return "", err
+ }
+
+ return hex.EncodeToString(eeprom.data[:eeprom.len]), nil
+}
+
+// DriverInfo returns driver information of the given interface name.
+func (e *Ethtool) DriverInfo(intf string) (ethtoolDrvInfo, error) {
+ drvInfo, err := e.getDriverInfo(intf)
+ if err != nil {
+ return ethtoolDrvInfo{}, err
+ }
+
+ return drvInfo, nil
+}
+
+// PermAddr returns permanent address of the given interface name.
+func (e *Ethtool) PermAddr(intf string) (string, error) {
+ permAddr, err := e.getPermAddr(intf)
+ if err != nil {
+ return "", err
+ }
+
+ if permAddr.data[0] == 0 && permAddr.data[1] == 0 &&
+ permAddr.data[2] == 0 && permAddr.data[3] == 0 &&
+ permAddr.data[4] == 0 && permAddr.data[5] == 0 {
+ return "", nil
+ }
+
+ return fmt.Sprintf("%x:%x:%x:%x:%x:%x",
+ permAddr.data[0:1],
+ permAddr.data[1:2],
+ permAddr.data[2:3],
+ permAddr.data[3:4],
+ permAddr.data[4:5],
+ permAddr.data[5:6],
+ ), nil
+}
+
+func (e *Ethtool) ioctl(intf string, data uintptr) error {
+ var name [IFNAMSIZ]byte
+ copy(name[:], []byte(intf))
+
+ ifr := ifreq{
+ ifr_name: name,
+ ifr_data: data,
+ }
+
+ _, _, ep := syscall.Syscall(syscall.SYS_IOCTL, uintptr(e.fd), SIOCETHTOOL, uintptr(unsafe.Pointer(&ifr)))
+ if ep != 0 {
+ return syscall.Errno(ep)
+ }
+
+ return nil
+}
+
+func (e *Ethtool) getDriverInfo(intf string) (ethtoolDrvInfo, error) {
+ drvinfo := ethtoolDrvInfo{
+ cmd: ETHTOOL_GDRVINFO,
+ }
+
+ if err := e.ioctl(intf, uintptr(unsafe.Pointer(&drvinfo))); err != nil {
+ return ethtoolDrvInfo{}, err
+ }
+
+ return drvinfo, nil
+}
+
+func (e *Ethtool) getPermAddr(intf string) (ethtoolPermAddr, error) {
+ permAddr := ethtoolPermAddr{
+ cmd: ETHTOOL_GPERMADDR,
+ size: PERMADDR_LEN,
+ }
+
+ if err := e.ioctl(intf, uintptr(unsafe.Pointer(&permAddr))); err != nil {
+ return ethtoolPermAddr{}, err
+ }
+
+ return permAddr, nil
+}
+
+func (e *Ethtool) getModuleEeprom(intf string) (ethtoolEeprom, ethtoolModInfo, error) {
+ modInfo := ethtoolModInfo{
+ cmd: ETHTOOL_GMODULEINFO,
+ }
+
+ if err := e.ioctl(intf, uintptr(unsafe.Pointer(&modInfo))); err != nil {
+ return ethtoolEeprom{}, ethtoolModInfo{}, err
+ }
+
+ eeprom := ethtoolEeprom{
+ cmd: ETHTOOL_GMODULEEEPROM,
+ len: modInfo.eeprom_len,
+ offset: 0,
+ }
+
+ if modInfo.eeprom_len > EEPROM_LEN {
+ return ethtoolEeprom{}, ethtoolModInfo{}, fmt.Errorf("eeprom size: %d is larger than buffer size: %d", modInfo.eeprom_len, EEPROM_LEN)
+ }
+
+ if err := e.ioctl(intf, uintptr(unsafe.Pointer(&eeprom))); err != nil {
+ return ethtoolEeprom{}, ethtoolModInfo{}, err
+ }
+
+ return eeprom, modInfo, nil
+}
+
+func isFeatureBitSet(blocks [MAX_FEATURE_BLOCKS]ethtoolGetFeaturesBlock, index uint) bool {
+ return (blocks)[index/32].active&(1<<(index%32)) != 0
+}
+
+func setFeatureBit(blocks *[MAX_FEATURE_BLOCKS]ethtoolSetFeaturesBlock, index uint, value bool) {
+ blockIndex, bitIndex := index/32, index%32
+
+ blocks[blockIndex].valid |= 1 << bitIndex
+
+ if value {
+ blocks[blockIndex].requested |= 1 << bitIndex
+ } else {
+ blocks[blockIndex].requested &= ^(1 << bitIndex)
+ }
+}
+
+// FeatureNames shows supported features by their name.
+func (e *Ethtool) FeatureNames(intf string) (map[string]uint, error) {
+ ssetInfo := ethtoolSsetInfo{
+ cmd: ETHTOOL_GSSET_INFO,
+ sset_mask: 1 << ETH_SS_FEATURES,
+ }
+
+ if err := e.ioctl(intf, uintptr(unsafe.Pointer(&ssetInfo))); err != nil {
+ return nil, err
+ }
+
+ length := uint32(ssetInfo.data)
+ if length == 0 {
+ return map[string]uint{}, nil
+ } else if length > MAX_GSTRINGS {
+ return nil, fmt.Errorf("ethtool currently doesn't support more than %d entries, received %d", MAX_GSTRINGS, length)
+ }
+
+ gstrings := ethtoolGStrings{
+ cmd: ETHTOOL_GSTRINGS,
+ string_set: ETH_SS_FEATURES,
+ len: length,
+ data: [MAX_GSTRINGS * ETH_GSTRING_LEN]byte{},
+ }
+
+ if err := e.ioctl(intf, uintptr(unsafe.Pointer(&gstrings))); err != nil {
+ return nil, err
+ }
+
+ var result = make(map[string]uint)
+ for i := 0; i != int(length); i++ {
+ b := gstrings.data[i*ETH_GSTRING_LEN : i*ETH_GSTRING_LEN+ETH_GSTRING_LEN]
+ key := string(bytes.Trim(b, "\x00"))
+ if key != "" {
+ result[key] = uint(i)
+ }
+ }
+
+ return result, nil
+}
+
+// Features retrieves features of the given interface name.
+func (e *Ethtool) Features(intf string) (map[string]bool, error) {
+ names, err := e.FeatureNames(intf)
+ if err != nil {
+ return nil, err
+ }
+
+ length := uint32(len(names))
+ if length == 0 {
+ return map[string]bool{}, nil
+ }
+
+ features := ethtoolGfeatures{
+ cmd: ETHTOOL_GFEATURES,
+ size: (length + 32 - 1) / 32,
+ }
+
+ if err := e.ioctl(intf, uintptr(unsafe.Pointer(&features))); err != nil {
+ return nil, err
+ }
+
+ var result = make(map[string]bool, length)
+ for key, index := range names {
+ result[key] = isFeatureBitSet(features.blocks, index)
+ }
+
+ return result, nil
+}
+
+// Change requests a change in the given device's features.
+func (e *Ethtool) Change(intf string, config map[string]bool) error {
+ names, err := e.FeatureNames(intf)
+ if err != nil {
+ return err
+ }
+
+ length := uint32(len(names))
+
+ features := ethtoolSfeatures{
+ cmd: ETHTOOL_SFEATURES,
+ size: (length + 32 - 1) / 32,
+ }
+
+ for key, value := range config {
+ if index, ok := names[key]; ok {
+ setFeatureBit(&features.blocks, index, value)
+ } else {
+ return fmt.Errorf("unsupported feature %q", key)
+ }
+ }
+
+ return e.ioctl(intf, uintptr(unsafe.Pointer(&features)))
+}
+
+// Get state of a link.
+func (e *Ethtool) LinkState(intf string) (uint32, error) {
+ x := ethtoolLink{
+ cmd: ETHTOOL_GLINK,
+ }
+
+ if err := e.ioctl(intf, uintptr(unsafe.Pointer(&x))); err != nil {
+ return 0, err
+ }
+
+ return x.data, nil
+}
+
+// Stats retrieves stats of the given interface name.
+func (e *Ethtool) Stats(intf string) (map[string]uint64, error) {
+ drvinfo := ethtoolDrvInfo{
+ cmd: ETHTOOL_GDRVINFO,
+ }
+
+ if err := e.ioctl(intf, uintptr(unsafe.Pointer(&drvinfo))); err != nil {
+ return nil, err
+ }
+
+ if drvinfo.n_stats*ETH_GSTRING_LEN > MAX_GSTRINGS*ETH_GSTRING_LEN {
+ return nil, fmt.Errorf("ethtool currently doesn't support more than %d entries, received %d", MAX_GSTRINGS, drvinfo.n_stats)
+ }
+
+ gstrings := ethtoolGStrings{
+ cmd: ETHTOOL_GSTRINGS,
+ string_set: ETH_SS_STATS,
+ len: drvinfo.n_stats,
+ data: [MAX_GSTRINGS * ETH_GSTRING_LEN]byte{},
+ }
+
+ if err := e.ioctl(intf, uintptr(unsafe.Pointer(&gstrings))); err != nil {
+ return nil, err
+ }
+
+ stats := ethtoolStats{
+ cmd: ETHTOOL_GSTATS,
+ n_stats: drvinfo.n_stats,
+ data: [MAX_GSTRINGS]uint64{},
+ }
+
+ if err := e.ioctl(intf, uintptr(unsafe.Pointer(&stats))); err != nil {
+ return nil, err
+ }
+
+ var result = make(map[string]uint64)
+ for i := 0; i != int(drvinfo.n_stats); i++ {
+ b := gstrings.data[i*ETH_GSTRING_LEN : i*ETH_GSTRING_LEN+ETH_GSTRING_LEN]
+ key := string(b[:strings.Index(string(b), "\x00")])
+ if len(key) != 0 {
+ result[key] = stats.data[i]
+ }
+ }
+
+ return result, nil
+}
+
+// Close closes the ethool handler
+func (e *Ethtool) Close() {
+ syscall.Close(e.fd)
+}
+
+// NewEthtool returns a new ethtool handler
+func NewEthtool() (*Ethtool, error) {
+ fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_IP)
+ if err != nil {
+ return nil, err
+ }
+
+ return &Ethtool{
+ fd: int(fd),
+ }, nil
+}
+
+// BusInfo returns bus information of the given interface name.
+func BusInfo(intf string) (string, error) {
+ e, err := NewEthtool()
+ if err != nil {
+ return "", err
+ }
+ defer e.Close()
+ return e.BusInfo(intf)
+}
+
+// DriverName returns the driver name of the given interface name.
+func DriverName(intf string) (string, error) {
+ e, err := NewEthtool()
+ if err != nil {
+ return "", err
+ }
+ defer e.Close()
+ return e.DriverName(intf)
+}
+
+// Stats retrieves stats of the given interface name.
+func Stats(intf string) (map[string]uint64, error) {
+ e, err := NewEthtool()
+ if err != nil {
+ return nil, err
+ }
+ defer e.Close()
+ return e.Stats(intf)
+}
+
+// PermAddr returns permanent address of the given interface name.
+func PermAddr(intf string) (string, error) {
+ e, err := NewEthtool()
+ if err != nil {
+ return "", err
+ }
+ defer e.Close()
+ return e.PermAddr(intf)
+}
diff --git a/vendor/github.com/safchain/ethtool/ethtool_cmd.go b/vendor/github.com/safchain/ethtool/ethtool_cmd.go
new file mode 100644
index 000000000..d0c35e476
--- /dev/null
+++ b/vendor/github.com/safchain/ethtool/ethtool_cmd.go
@@ -0,0 +1,207 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 ethtool aims to provide a library giving a simple access to the
+// Linux SIOCETHTOOL ioctl operations. It can be used to retrieve informations
+// from a network device like statistics, driver related informations or
+// even the peer of a VETH interface.
+package ethtool
+
+import (
+ "math"
+ "reflect"
+ "syscall"
+ "unsafe"
+)
+
+type EthtoolCmd struct { /* ethtool.c: struct ethtool_cmd */
+ Cmd uint32
+ Supported uint32
+ Advertising uint32
+ Speed uint16
+ Duplex uint8
+ Port uint8
+ Phy_address uint8
+ Transceiver uint8
+ Autoneg uint8
+ Mdio_support uint8
+ Maxtxpkt uint32
+ Maxrxpkt uint32
+ Speed_hi uint16
+ Eth_tp_mdix uint8
+ Reserved2 uint8
+ Lp_advertising uint32
+ Reserved [2]uint32
+}
+
+// CmdGet returns the interface settings in the receiver struct
+// and returns speed
+func (ecmd *EthtoolCmd) CmdGet(intf string) (uint32, error) {
+ e, err := NewEthtool()
+ if err != nil {
+ return 0, err
+ }
+ defer e.Close()
+ return e.CmdGet(ecmd, intf)
+}
+
+// CmdSet sets and returns the settings in the receiver struct
+// and returns speed
+func (ecmd *EthtoolCmd) CmdSet(intf string) (uint32, error) {
+ e, err := NewEthtool()
+ if err != nil {
+ return 0, err
+ }
+ defer e.Close()
+ return e.CmdSet(ecmd, intf)
+}
+
+func (f *EthtoolCmd) reflect(retv *map[string]uint64) {
+ val := reflect.ValueOf(f).Elem()
+
+ for i := 0; i < val.NumField(); i++ {
+ valueField := val.Field(i)
+ typeField := val.Type().Field(i)
+
+ t := valueField.Interface()
+ //tt := reflect.TypeOf(t)
+ //fmt.Printf(" t %T %v tt %T %v\n", t, t, tt, tt)
+ switch t.(type) {
+ case uint32:
+ //fmt.Printf(" t is uint32\n")
+ (*retv)[typeField.Name] = uint64(t.(uint32))
+ case uint16:
+ (*retv)[typeField.Name] = uint64(t.(uint16))
+ case uint8:
+ (*retv)[typeField.Name] = uint64(t.(uint8))
+ case int32:
+ (*retv)[typeField.Name] = uint64(t.(int32))
+ case int16:
+ (*retv)[typeField.Name] = uint64(t.(int16))
+ case int8:
+ (*retv)[typeField.Name] = uint64(t.(int8))
+ default:
+ (*retv)[typeField.Name+"_unknown_type"] = 0
+ }
+
+ //tag := typeField.Tag
+ //fmt.Printf("Field Name: %s,\t Field Value: %v,\t Tag Value: %s\n",
+ // typeField.Name, valueField.Interface(), tag.Get("tag_name"))
+ }
+}
+
+// CmdGet returns the interface settings in the receiver struct
+// and returns speed
+func (e *Ethtool) CmdGet(ecmd *EthtoolCmd, intf string) (uint32, error) {
+ ecmd.Cmd = ETHTOOL_GSET
+
+ var name [IFNAMSIZ]byte
+ copy(name[:], []byte(intf))
+
+ ifr := ifreq{
+ ifr_name: name,
+ ifr_data: uintptr(unsafe.Pointer(ecmd)),
+ }
+
+ _, _, ep := syscall.Syscall(syscall.SYS_IOCTL, uintptr(e.fd),
+ SIOCETHTOOL, uintptr(unsafe.Pointer(&ifr)))
+ if ep != 0 {
+ return 0, syscall.Errno(ep)
+ }
+
+ var speedval uint32 = (uint32(ecmd.Speed_hi) << 16) |
+ (uint32(ecmd.Speed) & 0xffff)
+ if speedval == math.MaxUint16 {
+ speedval = math.MaxUint32
+ }
+
+ return speedval, nil
+}
+
+// CmdSet sets and returns the settings in the receiver struct
+// and returns speed
+func (e *Ethtool) CmdSet(ecmd *EthtoolCmd, intf string) (uint32, error) {
+ ecmd.Cmd = ETHTOOL_SSET
+
+ var name [IFNAMSIZ]byte
+ copy(name[:], []byte(intf))
+
+ ifr := ifreq{
+ ifr_name: name,
+ ifr_data: uintptr(unsafe.Pointer(ecmd)),
+ }
+
+ _, _, ep := syscall.Syscall(syscall.SYS_IOCTL, uintptr(e.fd),
+ SIOCETHTOOL, uintptr(unsafe.Pointer(&ifr)))
+ if ep != 0 {
+ return 0, syscall.Errno(ep)
+ }
+
+ var speedval uint32 = (uint32(ecmd.Speed_hi) << 16) |
+ (uint32(ecmd.Speed) & 0xffff)
+ if speedval == math.MaxUint16 {
+ speedval = math.MaxUint32
+ }
+
+ return speedval, nil
+}
+
+// CmdGetMapped returns the interface settings in a map
+func (e *Ethtool) CmdGetMapped(intf string) (map[string]uint64, error) {
+ ecmd := EthtoolCmd{
+ Cmd: ETHTOOL_GSET,
+ }
+
+ var name [IFNAMSIZ]byte
+ copy(name[:], []byte(intf))
+
+ ifr := ifreq{
+ ifr_name: name,
+ ifr_data: uintptr(unsafe.Pointer(&ecmd)),
+ }
+
+ _, _, ep := syscall.Syscall(syscall.SYS_IOCTL, uintptr(e.fd),
+ SIOCETHTOOL, uintptr(unsafe.Pointer(&ifr)))
+ if ep != 0 {
+ return nil, syscall.Errno(ep)
+ }
+
+ var result = make(map[string]uint64)
+
+ // ref https://gist.github.com/drewolson/4771479
+ // Golang Reflection Example
+ ecmd.reflect(&result)
+
+ var speedval uint32 = (uint32(ecmd.Speed_hi) << 16) |
+ (uint32(ecmd.Speed) & 0xffff)
+ result["speed"] = uint64(speedval)
+
+ return result, nil
+}
+
+func CmdGetMapped(intf string) (map[string]uint64, error) {
+ e, err := NewEthtool()
+ if err != nil {
+ return nil, err
+ }
+ defer e.Close()
+ return e.CmdGetMapped(intf)
+}
diff --git a/vendor/github.com/safchain/ethtool/ethtool_msglvl.go b/vendor/github.com/safchain/ethtool/ethtool_msglvl.go
new file mode 100644
index 000000000..91836f019
--- /dev/null
+++ b/vendor/github.com/safchain/ethtool/ethtool_msglvl.go
@@ -0,0 +1,113 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 ethtool aims to provide a library giving a simple access to the
+// Linux SIOCETHTOOL ioctl operations. It can be used to retrieve informations
+// from a network device like statistics, driver related informations or
+// even the peer of a VETH interface.
+package ethtool
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+type ethtoolValue struct { /* ethtool.c: struct ethtool_value */
+ cmd uint32
+ data uint32
+}
+
+// MsglvlGet returns the msglvl of the given interface.
+func (e *Ethtool) MsglvlGet(intf string) (uint32, error) {
+ edata := ethtoolValue{
+ cmd: ETHTOOL_GMSGLVL,
+ }
+
+ var name [IFNAMSIZ]byte
+ copy(name[:], []byte(intf))
+
+ ifr := ifreq{
+ ifr_name: name,
+ ifr_data: uintptr(unsafe.Pointer(&edata)),
+ }
+
+ _, _, ep := syscall.Syscall(syscall.SYS_IOCTL, uintptr(e.fd),
+ SIOCETHTOOL, uintptr(unsafe.Pointer(&ifr)))
+ if ep != 0 {
+ return 0, syscall.Errno(ep)
+ }
+
+ return edata.data, nil
+}
+
+// MsglvlSet returns the read-msglvl, post-set-msglvl of the given interface.
+func (e *Ethtool) MsglvlSet(intf string, valset uint32) (uint32, uint32, error) {
+ edata := ethtoolValue{
+ cmd: ETHTOOL_GMSGLVL,
+ }
+
+ var name [IFNAMSIZ]byte
+ copy(name[:], []byte(intf))
+
+ ifr := ifreq{
+ ifr_name: name,
+ ifr_data: uintptr(unsafe.Pointer(&edata)),
+ }
+
+ _, _, ep := syscall.Syscall(syscall.SYS_IOCTL, uintptr(e.fd),
+ SIOCETHTOOL, uintptr(unsafe.Pointer(&ifr)))
+ if ep != 0 {
+ return 0, 0, syscall.Errno(ep)
+ }
+
+ readval := edata.data
+
+ edata.cmd = ETHTOOL_SMSGLVL
+ edata.data = valset
+
+ _, _, ep = syscall.Syscall(syscall.SYS_IOCTL, uintptr(e.fd),
+ SIOCETHTOOL, uintptr(unsafe.Pointer(&ifr)))
+ if ep != 0 {
+ return 0, 0, syscall.Errno(ep)
+ }
+
+ return readval, edata.data, nil
+}
+
+// MsglvlGet returns the msglvl of the given interface.
+func MsglvlGet(intf string) (uint32, error) {
+ e, err := NewEthtool()
+ if err != nil {
+ return 0, err
+ }
+ defer e.Close()
+ return e.MsglvlGet(intf)
+}
+
+// MsglvlSet returns the read-msglvl, post-set-msglvl of the given interface.
+func MsglvlSet(intf string, valset uint32) (uint32, uint32, error) {
+ e, err := NewEthtool()
+ if err != nil {
+ return 0, 0, err
+ }
+ defer e.Close()
+ return e.MsglvlSet(intf, valset)
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 3459e8d52..d0fcf879a 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -39,12 +39,16 @@ github.com/containerd/continuity/pathdriver
# github.com/containernetworking/cni v0.7.1
github.com/containernetworking/cni/pkg/types
github.com/containernetworking/cni/pkg/types/current
+github.com/containernetworking/cni/pkg/version
github.com/containernetworking/cni/libcni
github.com/containernetworking/cni/pkg/invoke
-github.com/containernetworking/cni/pkg/version
github.com/containernetworking/cni/pkg/types/020
# github.com/containernetworking/plugins v0.8.1
github.com/containernetworking/plugins/pkg/ns
+github.com/containernetworking/plugins/pkg/ip
+github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator
+github.com/containernetworking/plugins/pkg/utils/hwaddr
+github.com/containernetworking/plugins/plugins/ipam/host-local/backend
# github.com/containers/buildah v1.11.0
github.com/containers/buildah
github.com/containers/buildah/imagebuildah
@@ -145,6 +149,8 @@ github.com/containers/storage/drivers/quota
github.com/containers/storage/pkg/fsutils
github.com/containers/storage/pkg/ostree
github.com/containers/storage/drivers/copy
+# github.com/coreos/go-iptables v0.4.2
+github.com/coreos/go-iptables/iptables
# github.com/coreos/go-systemd v0.0.0-20190620071333-e64a0ec8b42a
github.com/coreos/go-systemd/activation
github.com/coreos/go-systemd/dbus
@@ -406,6 +412,8 @@ github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg
# github.com/prometheus/procfs v0.0.2
github.com/prometheus/procfs
github.com/prometheus/procfs/internal/fs
+# github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8
+github.com/safchain/ethtool
# github.com/seccomp/containers-golang v0.0.0-20190312124753-8ca8945ccf5f
github.com/seccomp/containers-golang
# github.com/seccomp/libseccomp-golang v0.9.1