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 /vendor/github.com/containernetworking | |
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>
Diffstat (limited to 'vendor/github.com/containernetworking')
13 files changed, 1488 insertions, 0 deletions
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 +} |