summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xAPI.md4
-rw-r--r--cmd/podman/build.go9
-rw-r--r--cmd/podman/cliconfig/config.go13
-rw-r--r--cmd/podman/common.go11
-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.varlink2
-rw-r--r--cmd/podman/volume_rm.go18
-rw-r--r--commands.md1
-rw-r--r--completions/bash/podman44
-rw-r--r--docs/podman-build.1.md2
-rw-r--r--docs/podman-create.1.md10
-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.md10
-rw-r--r--docs/podman-volume-inspect.1.md1
-rw-r--r--docs/podman-volume-rm.1.md8
-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/in_memory_state.go35
-rw-r--r--libpod/runtime_volume.go46
-rw-r--r--libpod/state.go3
-rw-r--r--pkg/adapter/network.go135
-rw-r--r--pkg/adapter/runtime.go6
-rw-r--r--pkg/adapter/runtime_remote.go21
-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/util/utils_supported.go2
-rw-r--r--pkg/varlinkapi/volumes.go10
-rw-r--r--test/e2e/network_create_test.go211
-rw-r--r--test/e2e/volume_rm_test.go34
-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
68 files changed, 4978 insertions, 144 deletions
diff --git a/API.md b/API.md
index 4e61b7c9f..b9f3bd3d5 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;">
diff --git a/cmd/podman/build.go b/cmd/podman/build.go
index 14ac51889..8eb12cacd 100644
--- a/cmd/podman/build.go
+++ b/cmd/podman/build.go
@@ -10,6 +10,7 @@ import (
"github.com/containers/buildah/imagebuildah"
buildahcli "github.com/containers/buildah/pkg/cli"
"github.com/containers/libpod/cmd/podman/cliconfig"
+ "github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/adapter"
"github.com/docker/go-units"
"github.com/opencontainers/runtime-spec/specs-go"
@@ -225,6 +226,14 @@ func buildCmd(c *cliconfig.BuildValues) error {
for _, arg := range c.RuntimeFlags {
runtimeFlags = append(runtimeFlags, "--"+arg)
}
+
+ conf, err := runtime.GetConfig()
+ if err != nil {
+ return err
+ }
+ if conf != nil && conf.CgroupManager == libpod.SystemdCgroupsManager {
+ runtimeFlags = append(runtimeFlags, "--systemd-cgroup")
+ }
// end from buildah
defer runtime.DeferredShutdown(false)
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/common.go b/cmd/podman/common.go
index 32478bb51..9724d18c6 100644
--- a/cmd/podman/common.go
+++ b/cmd/podman/common.go
@@ -22,7 +22,8 @@ var (
)
const (
- idTruncLength = 12
+ idTruncLength = 12
+ sizeWithUnitFormat = "(format: `<number>[<unit>]`, where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes))"
)
func splitCamelCase(src string) string {
@@ -302,7 +303,7 @@ func getCreateFlags(c *cliconfig.PodmanCommand) {
)
createFlags.String(
"kernel-memory", "",
- "Kernel memory limit (format: `<number>[<unit>]`, where unit = b, k, m or g)",
+ "Kernel memory limit "+sizeWithUnitFormat,
)
createFlags.StringArrayP(
"label", "l", []string{},
@@ -326,11 +327,11 @@ func getCreateFlags(c *cliconfig.PodmanCommand) {
)
createFlags.StringP(
"memory", "m", "",
- "Memory limit (format: <number>[<unit>], where unit = b, k, m or g)",
+ "Memory limit "+sizeWithUnitFormat,
)
createFlags.String(
"memory-reservation", "",
- "Memory soft limit (format: <number>[<unit>], where unit = b, k, m or g)",
+ "Memory soft limit "+sizeWithUnitFormat,
)
createFlags.String(
"memory-swap", "",
@@ -422,7 +423,7 @@ func getCreateFlags(c *cliconfig.PodmanCommand) {
)
createFlags.String(
"shm-size", cliconfig.DefaultShmSize,
- "Size of `/dev/shm`. The format is `<number><unit>`",
+ "Size of /dev/shm "+sizeWithUnitFormat,
)
createFlags.String(
"stop-signal", "",
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..2e46b31ce 100644
--- a/cmd/podman/varlink/io.podman.varlink
+++ b/cmd/podman/varlink/io.podman.varlink
@@ -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 e6ffb135f..041703810 100644
--- a/completions/bash/podman
+++ b/completions/bash/podman
@@ -1,3 +1,7 @@
+#
+# This bash script was originally copied and converted from the upstream
+# github.com:docker/docker project
+#
: ${PROG:=$(basename ${BASH_SOURCE})}
@@ -952,6 +956,7 @@ _podman_network() {
-h
"
subcommands="
+ create
inspect
ls
rm
@@ -968,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="
"
@@ -1002,7 +1028,7 @@ _podman_network_ls() {
esac
}
-_podman_network_ls() {
+_podman_network_rm() {
local options_with_args="
"
local boolean_options="
@@ -1390,20 +1416,24 @@ _podman_import() {
--quiet
-q
"
- _complete_ "$options_with_args" "$boolean_options"
+ case "$prev" in
+ --change|-c|--message|-m)
+ return
+ ;;
+ esac
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
*)
- local counter=$( __podman_pos_first_nonflag "$options_with_args" )
- if [ "$cword" -eq "$((counter))" ]; then
- __podman_complete_images --repo --tag
- return
- else
+ local counter=$(__podman_pos_first_nonflag '--change|-c|--message|-m')
+ if [ "$cword" -eq "$counter" ]; then
_filedir
return
+ elif [ "$cword" -eq "$((counter + 1))" ]; then
+ __podman_complete_images --repo --tag
+ return
fi
;;
esac
diff --git a/docs/podman-build.1.md b/docs/podman-build.1.md
index 74c07ab73..20f4d6aab 100644
--- a/docs/podman-build.1.md
+++ b/docs/podman-build.1.md
@@ -281,7 +281,7 @@ with 3 being roughly equivalent to using the global *--debug* option, and
values below 0 omitting even error messages which accompany fatal errors.
**--memory**, **-m**=*LIMIT*
-Memory limit (format: <number>[<unit>], where unit = b, k, m or g)
+Memory limit (format: <number>[<unit>], where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes))
Allows you to constrain the memory available to a container. If the host
supports swap memory, then the **-m** memory setting can be larger than physical
diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md
index cee9a1640..9924e7dff 100644
--- a/docs/podman-create.1.md
+++ b/docs/podman-create.1.md
@@ -379,7 +379,7 @@ Default is to create a private IPC namespace (POSIX SysV IPC) for the container
**--kernel-memory**=*number[unit]*
-Kernel memory limit (format: `<number>[<unit>]`, where unit = b, k, m or g)
+Kernel memory limit (format: `<number>[<unit>]`, where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes))
Constrains the kernel memory available to a container. If a limit of 0
is specified (not using `--kernel-memory`), the container's kernel memory
@@ -421,7 +421,7 @@ Not currently supported
**--memory**, **-m**=*limit*
-Memory limit (format: <number>[<unit>], where unit = b, k, m or g)
+Memory limit (format: <number>[<unit>], where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes))
Allows you to constrain the memory available to a container. If the host
supports swap memory, then the **-m** memory setting can be larger than physical
@@ -431,7 +431,7 @@ system's page size (the value would be very large, that's millions of trillions)
**--memory-reservation**=*limit*
-Memory soft limit (format: <number>[<unit>], where unit = b, k, m or g)
+Memory soft limit (format: <number>[<unit>], where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes))
After setting memory reservation, when the system detects memory contention
or low memory, containers are forced to restrict their consumption to their
@@ -661,9 +661,9 @@ Note: Labeling can be disabled for all containers by setting label=false in the
**--shm-size**=*size*
-Size of `/dev/shm`. The format is `<number><unit>`. `number` must be greater than `0`.
-Unit is optional and can be `b` (bytes), `k` (kilobytes), `m`(megabytes), or `g` (gigabytes).
+Size of `/dev/shm` (format: <number>[<unit>], where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes))
If you omit the unit, the system uses bytes. If you omit the size entirely, the system uses `64m`.
+When size is `0`, there is no limit on the amount of memory used for IPC by the container.
**--stop-signal**=*SIGTERM*
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 8fb7453fe..c642b50b6 100644
--- a/docs/podman-run.1.md
+++ b/docs/podman-run.1.md
@@ -390,7 +390,7 @@ Default is to create a private IPC namespace (POSIX SysV IPC) for the container
**--kernel-memory**=*number[unit]*
-Kernel memory limit (format: `<number>[<unit>]`, where unit = b, k, m or g)
+Kernel memory limit (format: `<number>[<unit>]`, where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes))
Constrains the kernel memory available to a container. If a limit of 0
is specified (not using `--kernel-memory`), the container's kernel memory
@@ -432,7 +432,7 @@ Not currently supported
**--memory**, **-m**=*limit*
-Memory limit (format: <number>[<unit>], where unit = b, k, m or g)
+Memory limit (format: <number>[<unit>], where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes))
Allows you to constrain the memory available to a container. If the host
supports swap memory, then the **-m** memory setting can be larger than physical
@@ -442,7 +442,7 @@ system's page size (the value would be very large, that's millions of trillions)
**--memory-reservation**=*limit*
-Memory soft limit (format: <number>[<unit>], where unit = b, k, m or g)
+Memory soft limit (format: <number>[<unit>], where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes))
After setting memory reservation, when the system detects memory contention
or low memory, containers are forced to restrict their consumption to their
@@ -683,9 +683,9 @@ Note: Labeling can be disabled for all containers by setting label=false in the
**--shm-size**=*size*
-Size of `/dev/shm`. The format is `<number><unit>`. `number` must be greater than `0`.
-Unit is optional and can be `b` (bytes), `k` (kilobytes), `m`(megabytes), or `g` (gigabytes).
+Size of `/dev/shm` (format: <number>[<unit>], where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes))
If you omit the unit, the system uses bytes. If you omit the size entirely, the system uses `64m`.
+When size is `0`, there is no limit on the amount of memory used for IPC by the container.
**--sig-proxy**=*true|false*
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/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/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_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/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 8588966b6..f079b914a 100644
--- a/pkg/adapter/runtime_remote.go
+++ b/pkg/adapter/runtime_remote.go
@@ -113,6 +113,18 @@ func (r RemoteRuntime) DeferredShutdown(force bool) {
}
}
+// RuntimeConfig is a bogus wrapper for compat with the libpod runtime
+type RuntimeConfig struct {
+ // CGroupManager is the CGroup Manager to use
+ // Valid values are "cgroupfs" and "systemd"
+ CgroupManager string
+}
+
+// Shutdown is a bogus wrapper for compat with the libpod runtime
+func (r *RemoteRuntime) GetConfig() (*RuntimeConfig, error) {
+ return nil, nil
+}
+
// Shutdown is a bogus wrapper for compat with the libpod runtime
func (r RemoteRuntime) Shutdown(force bool) error {
return nil
@@ -610,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/util/utils_supported.go b/pkg/util/utils_supported.go
index 707e193e9..253460686 100644
--- a/pkg/util/utils_supported.go
+++ b/pkg/util/utils_supported.go
@@ -83,7 +83,7 @@ func GetRootlessConfigHomeDir() (string, error) {
logrus.Errorf("unable to make temp dir %s", tmpDir)
}
st, err := os.Stat(tmpDir)
- if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Geteuid() && st.Mode().Perm() == 0755 {
+ if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Geteuid() && st.Mode().Perm() >= 0700 {
cfgHomeDir = tmpDir
}
}
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/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/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