diff options
author | baude <bbaude@redhat.com> | 2019-08-08 06:01:00 -0500 |
---|---|---|
committer | baude <bbaude@redhat.com> | 2019-09-09 09:32:43 -0500 |
commit | ee432cf2792c5dbe81953007f1fd5c87beb3ebd5 (patch) | |
tree | dc0646e4b2faeadf9cca58bf80f1e90d98c50165 | |
parent | 30cbb0091515a7f802f0f3f3ee486be6ff98f645 (diff) | |
download | podman-ee432cf2792c5dbe81953007f1fd5c87beb3ebd5.tar.gz podman-ee432cf2792c5dbe81953007f1fd5c87beb3ebd5.tar.bz2 podman-ee432cf2792c5dbe81953007f1fd5c87beb3ebd5.zip |
podman network create
initial implementation of network create. we only support bridging
networks with this first pass.
Signed-off-by: baude <bbaude@redhat.com>
47 files changed, 4677 insertions, 55 deletions
diff --git a/cmd/podman/cliconfig/config.go b/cmd/podman/cliconfig/config.go index 98e7aed4b..812cc1f51 100644 --- a/cmd/podman/cliconfig/config.go +++ b/cmd/podman/cliconfig/config.go @@ -1,6 +1,8 @@ package cliconfig import ( + "net" + "github.com/spf13/cobra" ) @@ -259,6 +261,17 @@ type MountValues struct { Latest bool } +type NetworkCreateValues struct { + PodmanCommand + Driver string + Gateway net.IP + Internal bool + IPamDriver string + IPRange net.IPNet + IPV6 bool + Network net.IPNet +} + type NetworkListValues struct { PodmanCommand Filter []string diff --git a/cmd/podman/network.go b/cmd/podman/network.go index 83a5e71ab..702593e5c 100644 --- a/cmd/podman/network.go +++ b/cmd/podman/network.go @@ -17,8 +17,8 @@ var networkcheckCommand = cliconfig.PodmanCommand{ }, } -// Commands that are universally implemented var networkcheckCommands = []*cobra.Command{ + _networkCreateCommand, _networkinspectCommand, _networklistCommand, _networkrmCommand, diff --git a/cmd/podman/network_create.go b/cmd/podman/network_create.go new file mode 100644 index 000000000..378a92568 --- /dev/null +++ b/cmd/podman/network_create.go @@ -0,0 +1,70 @@ +// +build !remoteclient + +package main + +import ( + "fmt" + "github.com/containers/libpod/pkg/network" + "net" + + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/pkg/adapter" + "github.com/containers/libpod/pkg/rootless" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + networkCreateCommand cliconfig.NetworkCreateValues + networkCreateDescription = `create CNI networks for containers and pods` + _networkCreateCommand = &cobra.Command{ + Use: "create [flags] [NETWORK]", + Short: "network create", + Long: networkCreateDescription, + RunE: func(cmd *cobra.Command, args []string) error { + networkCreateCommand.InputArgs = args + networkCreateCommand.GlobalFlags = MainGlobalOpts + networkCreateCommand.Remote = remoteclient + return networkcreateCmd(&networkCreateCommand) + }, + Example: `podman network create podman1`, + } +) + +func init() { + networkCreateCommand.Command = _networkCreateCommand + networkCreateCommand.SetHelpTemplate(HelpTemplate()) + networkCreateCommand.SetUsageTemplate(UsageTemplate()) + flags := networkCreateCommand.Flags() + flags.StringVarP(&networkCreateCommand.Driver, "driver", "d", "bridge", "driver to manage the network") + flags.IPVar(&networkCreateCommand.Gateway, "gateway", nil, "IPv4 or IPv6 gateway for the subnet") + flags.BoolVar(&networkCreateCommand.Internal, "internal", false, "restrict external access from this network") + flags.IPNetVar(&networkCreateCommand.IPRange, "ip-range", net.IPNet{}, "allocate container IP from range") + // TODO not supported yet + //flags.StringVar(&networkCreateCommand.IPamDriver, "ipam-driver", "", "IP Address Management Driver") + // TODO enable when IPv6 is working + //flags.BoolVar(&networkCreateCommand.IPV6, "IPv6", false, "enable IPv6 networking") + flags.IPNetVar(&networkCreateCommand.Network, "subnet", net.IPNet{}, "subnet in CIDR format") + +} + +func networkcreateCmd(c *cliconfig.NetworkCreateValues) error { + if err := network.IsSupportedDriver(c.Driver); err != nil { + return err + } + if rootless.IsRootless() && !remoteclient { + return errors.New("network create is not supported for rootless mode") + } + if len(c.InputArgs) > 1 { + return errors.Errorf("only one network can be created at a time") + } + runtime, err := adapter.GetRuntimeNoStore(getContext(), &c.PodmanCommand) + if err != nil { + return err + } + fileName, err := runtime.NetworkCreate(c) + if err == nil { + fmt.Println(fileName) + } + return err +} diff --git a/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..ec660712b 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -952,6 +952,7 @@ _podman_network() { -h " subcommands=" + create inspect ls rm @@ -968,6 +969,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 +1024,7 @@ _podman_network_ls() { esac } -_podman_network_ls() { +_podman_network_rm() { local options_with_args=" " local boolean_options=" diff --git a/docs/podman-network-create.1.md b/docs/podman-network-create.1.md new file mode 100644 index 000000000..0679d8ee2 --- /dev/null +++ b/docs/podman-network-create.1.md @@ -0,0 +1,70 @@ +% podman-network-create(1) + +## NAME +podman\-network-create - Create a Podman CNI network + +## SYNOPSIS +**podman network create** [*options*] name + +## DESCRIPTION +Create a CNI-network configuration for use with Podman. At the time of this writing, the only network +type that can be created is a *bridge* network. + +If no options are provided, Podman will assign a free subnet and name for your network. + +Upon completion of creating the network, Podman will display the path to the newly added network file. + +## OPTIONS +**-d**, , **--driver** + +Driver to manage the network (default "bridge"). Currently on `bridge` is supported. + +**--gateway** + +Define a gateway for the subnet. If you want to provide a gateway address, you must also provide a +*subnet* option. + +**--internal** + +Restrict external access of this network + +**--ip-range** + +Allocate container IP from a range. The range must be a complete subnet and in CIDR notation. The *ip-range* option +must be used with a *subnet* option. + +**--subnet** + +The subnet in CIDR notation. + +## EXAMPLE + +Create a network with no options +``` +# podman network create +/etc/cni/net.d/cni-podman-4.conflist +``` + +Create a network named *newnet* that uses *192.5.0.0/16* for its subnet. +``` +# podman network create --subnet 192.5.0.0/16 newnet +/etc/cni/net.d/newnet.conflist +``` + +Create a network named *newnet* that uses *192.168.33.0/24* and defines a gateway as *192.168.133.3* +``` +# podman network create --subnet 192.168.33.0/24 --gateway 192.168.33.3 newnet +/etc/cni/net.d/newnet.conflist +``` + +Create a network that uses a *192.168.55.0/24** subnet and has an IP address range of *192.168.55.129 - 192.168.55.254*. +``` +# podman network create --subnet 192.168.55.0/24 --ip-range 192.168.55.128/25 +/etc/cni/net.d/cni-podman-5.conflist +``` + +## SEE ALSO +podman(1), podman-network(1), podman-network-inspect(1) + +## HISTORY +August 2019, Originally compiled by Brent Baude <bbaude@redhat.com> diff --git a/docs/podman-network-ls.1.md b/docs/podman-network-ls.1.md index 658b86c21..46e424593 100644 --- a/docs/podman-network-ls.1.md +++ b/docs/podman-network-ls.1.md @@ -12,7 +12,7 @@ Displays a list of existing podman networks. This command is not available for r ## OPTIONS **--quiet**, **-q** -The `quiet` options will restrict the output to only the network names +The `quiet` option will restrict the output to only the network names ## EXAMPLE diff --git a/docs/podman-network.1.md b/docs/podman-network.1.md index c9f6725a3..f05b2b78f 100644 --- a/docs/podman-network.1.md +++ b/docs/podman-network.1.md @@ -13,6 +13,7 @@ The network command manages CNI networks for Podman. It is not supported for roo | Command | Man Page | Description | | ------- | --------------------------------------------------- | ---------------------------------------------------------------------------- | +| create | [podman-network-create(1)](podman-network-create.1.md)| Create a Podman CNI network| | inspect | [podman-network-inspect(1)](podman-network-inspect.1.md)| Displays the raw CNI network configuration for one or more networks| | ls | [podman-network-ls(1)](podman-network-ls.1.md)| Display a summary of CNI networks | | rm | [podman-network-rm(1)](podman-network-rm.1.md)| Remove one or more CNI networks | @@ -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 @@ -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/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/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/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/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 ¤t.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 |