Removed dead code from docker after libnetwork integration

As part of this some generic packages like iptables, etchosts and resolvconf
have also been moved to libnetwork. Even though they can still be
consumed in a generic fashion they will reside and be maintained
from within the libnetwork project.

Signed-off-by: Jana Radhakrishnan <mrjana@docker.com>
This commit is contained in:
Jana Radhakrishnan 2015-05-11 23:16:42 +00:00
parent d18919e304
commit f12f51b8b9
25 changed files with 0 additions and 4585 deletions

View file

@ -9,7 +9,6 @@ import (
"github.com/Sirupsen/logrus"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/promise"
"github.com/docker/docker/pkg/resolvconf/dns"
"github.com/docker/docker/pkg/signal"
"github.com/docker/docker/runconfig"
"github.com/docker/libnetwork/resolvconf/dns"

View file

@ -15,22 +15,6 @@ import (
"github.com/docker/libcontainer/label"
)
/* {{if .Network.Interface}}
# network configuration
lxc.network.type = veth
lxc.network.link = {{.Network.Interface.Bridge}}
lxc.network.name = eth0
lxc.network.mtu = {{.Network.Mtu}}
lxc.network.flags = up
{{else if .Network.HostNetworking}}
lxc.network.type = none
{{else}}
# network is disabled (-n=false)
lxc.network.type = empty
lxc.network.flags = up
lxc.network.mtu = {{.Network.Mtu}}
{{end}} */
const LxcTemplate = `
lxc.network.type = none
# root filesystem

View file

@ -1,811 +0,0 @@
package bridge
import (
"encoding/hex"
"errors"
"fmt"
"io/ioutil"
"net"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"sync"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/network"
"github.com/docker/docker/daemon/networkdriver"
"github.com/docker/docker/daemon/networkdriver/ipallocator"
"github.com/docker/docker/daemon/networkdriver/portmapper"
"github.com/docker/docker/nat"
"github.com/docker/docker/pkg/iptables"
"github.com/docker/docker/pkg/parsers/kernel"
"github.com/docker/docker/pkg/resolvconf"
"github.com/docker/libcontainer/netlink"
)
const (
DefaultNetworkBridge = "docker0"
MaxAllocatedPortAttempts = 10
)
// Network interface represents the networking stack of a container
type networkInterface struct {
IP net.IP
IPv6 net.IP
PortMappings []net.Addr // There are mappings to the host interfaces
}
type ifaces struct {
c map[string]*networkInterface
sync.Mutex
}
func (i *ifaces) Set(key string, n *networkInterface) {
i.Lock()
i.c[key] = n
i.Unlock()
}
func (i *ifaces) Get(key string) *networkInterface {
i.Lock()
res := i.c[key]
i.Unlock()
return res
}
var (
addrs = []string{
// Here we don't follow the convention of using the 1st IP of the range for the gateway.
// This is to use the same gateway IPs as the /24 ranges, which predate the /16 ranges.
// In theory this shouldn't matter - in practice there's bound to be a few scripts relying
// on the internal addressing or other things like that.
// They shouldn't, but hey, let's not break them unless we really have to.
"172.17.42.1/16", // Don't use 172.16.0.0/16, it conflicts with EC2 DNS 172.16.0.23
"10.0.42.1/16", // Don't even try using the entire /8, that's too intrusive
"10.1.42.1/16",
"10.42.42.1/16",
"172.16.42.1/24",
"172.16.43.1/24",
"172.16.44.1/24",
"10.0.42.1/24",
"10.0.43.1/24",
"192.168.42.1/24",
"192.168.43.1/24",
"192.168.44.1/24",
}
bridgeIface string
bridgeIPv4Network *net.IPNet
gatewayIPv4 net.IP
bridgeIPv6Addr net.IP
globalIPv6Network *net.IPNet
gatewayIPv6 net.IP
portMapper *portmapper.PortMapper
once sync.Once
hairpinMode bool
defaultBindingIP = net.ParseIP("0.0.0.0")
currentInterfaces = ifaces{c: make(map[string]*networkInterface)}
ipAllocator = ipallocator.New()
)
func initPortMapper() {
once.Do(func() {
portMapper = portmapper.New()
})
}
type Config struct {
EnableIPv6 bool
EnableIptables bool
EnableIpForward bool
EnableIpMasq bool
EnableUserlandProxy bool
DefaultIp net.IP
Iface string
IP string
FixedCIDR string
FixedCIDRv6 string
DefaultGatewayIPv4 string
DefaultGatewayIPv6 string
InterContainerCommunication bool
}
func InitDriver(config *Config) error {
var (
networkv4 *net.IPNet
networkv6 *net.IPNet
addrv4 net.Addr
addrsv6 []net.Addr
bridgeIPv6 = "fe80::1/64"
)
// try to modprobe bridge first
// see gh#12177
if out, err := exec.Command("modprobe", "-va", "bridge", "nf_nat", "br_netfilter").Output(); err != nil {
logrus.Warnf("Running modprobe bridge nf_nat failed with message: %s, error: %v", out, err)
}
initPortMapper()
if config.DefaultIp != nil {
defaultBindingIP = config.DefaultIp
}
hairpinMode = !config.EnableUserlandProxy
bridgeIface = config.Iface
usingDefaultBridge := false
if bridgeIface == "" {
usingDefaultBridge = true
bridgeIface = DefaultNetworkBridge
}
addrv4, addrsv6, err := networkdriver.GetIfaceAddr(bridgeIface)
if err != nil {
// No Bridge existent, create one
// If we're not using the default bridge, fail without trying to create it
if !usingDefaultBridge {
return err
}
logrus.Info("Bridge interface not found, trying to create it")
// If the iface is not found, try to create it
if err := configureBridge(config.IP, bridgeIPv6, config.EnableIPv6); err != nil {
logrus.Errorf("Could not configure Bridge: %s", err)
return err
}
addrv4, addrsv6, err = networkdriver.GetIfaceAddr(bridgeIface)
if err != nil {
return err
}
if config.FixedCIDRv6 != "" {
// Setting route to global IPv6 subnet
logrus.Infof("Adding route to IPv6 network %q via device %q", config.FixedCIDRv6, bridgeIface)
if err := netlink.AddRoute(config.FixedCIDRv6, "", "", bridgeIface); err != nil {
logrus.Fatalf("Could not add route to IPv6 network %q via device %q", config.FixedCIDRv6, bridgeIface)
}
}
} else {
// Bridge exists already, getting info...
// Validate that the bridge ip matches the ip specified by BridgeIP
if config.IP != "" {
networkv4 = addrv4.(*net.IPNet)
bip, _, err := net.ParseCIDR(config.IP)
if err != nil {
return err
}
if !networkv4.IP.Equal(bip) {
return fmt.Errorf("Bridge ip (%s) does not match existing bridge configuration %s", networkv4.IP, bip)
}
}
// A bridge might exist but not have any IPv6 addr associated with it yet
// (for example, an existing Docker installation that has only been used
// with IPv4 and docker0 already is set up) In that case, we can perform
// the bridge init for IPv6 here, else we will error out below if --ipv6=true
if len(addrsv6) == 0 && config.EnableIPv6 {
if err := setupIPv6Bridge(bridgeIPv6); err != nil {
return err
}
// Recheck addresses now that IPv6 is setup on the bridge
addrv4, addrsv6, err = networkdriver.GetIfaceAddr(bridgeIface)
if err != nil {
return err
}
}
// TODO: Check if route to config.FixedCIDRv6 is set
}
if config.EnableIPv6 {
bip6, _, err := net.ParseCIDR(bridgeIPv6)
if err != nil {
return err
}
found := false
for _, addrv6 := range addrsv6 {
networkv6 = addrv6.(*net.IPNet)
if networkv6.IP.Equal(bip6) {
found = true
break
}
}
if !found {
return fmt.Errorf("Bridge IPv6 does not match existing bridge configuration %s", bip6)
}
}
networkv4 = addrv4.(*net.IPNet)
if config.EnableIPv6 {
if len(addrsv6) == 0 {
return errors.New("IPv6 enabled but no IPv6 detected")
}
bridgeIPv6Addr = networkv6.IP
}
if config.EnableIptables {
if err := iptables.FirewalldInit(); err != nil {
logrus.Debugf("Error initializing firewalld: %v", err)
}
}
// Configure iptables for link support
if config.EnableIptables {
if err := setupIPTables(addrv4, config.InterContainerCommunication, config.EnableIpMasq); err != nil {
logrus.Errorf("Error configuring iptables: %s", err)
return err
}
// call this on Firewalld reload
iptables.OnReloaded(func() { setupIPTables(addrv4, config.InterContainerCommunication, config.EnableIpMasq) })
}
if config.EnableIpForward {
// Enable IPv4 forwarding
if err := ioutil.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte{'1', '\n'}, 0644); err != nil {
logrus.Warnf("Unable to enable IPv4 forwarding: %v", err)
}
if config.FixedCIDRv6 != "" {
// Enable IPv6 forwarding
if err := ioutil.WriteFile("/proc/sys/net/ipv6/conf/default/forwarding", []byte{'1', '\n'}, 0644); err != nil {
logrus.Warnf("Unable to enable IPv6 default forwarding: %v", err)
}
if err := ioutil.WriteFile("/proc/sys/net/ipv6/conf/all/forwarding", []byte{'1', '\n'}, 0644); err != nil {
logrus.Warnf("Unable to enable IPv6 all forwarding: %v", err)
}
}
}
if hairpinMode {
// Enable loopback adresses routing
sysPath := filepath.Join("/proc/sys/net/ipv4/conf", bridgeIface, "route_localnet")
if err := ioutil.WriteFile(sysPath, []byte{'1', '\n'}, 0644); err != nil {
logrus.Warnf("Unable to enable local routing for hairpin mode: %v", err)
}
}
// We can always try removing the iptables
if err := iptables.RemoveExistingChain("DOCKER", iptables.Nat); err != nil {
return err
}
if config.EnableIptables {
_, err := iptables.NewChain("DOCKER", bridgeIface, iptables.Nat, hairpinMode)
if err != nil {
return err
}
// call this on Firewalld reload
iptables.OnReloaded(func() { iptables.NewChain("DOCKER", bridgeIface, iptables.Nat, hairpinMode) })
chain, err := iptables.NewChain("DOCKER", bridgeIface, iptables.Filter, hairpinMode)
if err != nil {
return err
}
// call this on Firewalld reload
iptables.OnReloaded(func() { iptables.NewChain("DOCKER", bridgeIface, iptables.Filter, hairpinMode) })
portMapper.SetIptablesChain(chain)
}
bridgeIPv4Network = networkv4
if config.FixedCIDR != "" {
_, subnet, err := net.ParseCIDR(config.FixedCIDR)
if err != nil {
return err
}
logrus.Debugf("Subnet: %v", subnet)
if err := ipAllocator.RegisterSubnet(bridgeIPv4Network, subnet); err != nil {
logrus.Errorf("Error registering subnet for IPv4 bridge network: %s", err)
return err
}
}
gateway, err := requestDefaultGateway(config.DefaultGatewayIPv4, bridgeIPv4Network)
if err != nil {
return err
}
gatewayIPv4 = gateway
if config.FixedCIDRv6 != "" {
_, subnet, err := net.ParseCIDR(config.FixedCIDRv6)
if err != nil {
return err
}
logrus.Debugf("Subnet: %v", subnet)
if err := ipAllocator.RegisterSubnet(subnet, subnet); err != nil {
logrus.Errorf("Error registering subnet for IPv6 bridge network: %s", err)
return err
}
globalIPv6Network = subnet
gateway, err := requestDefaultGateway(config.DefaultGatewayIPv6, globalIPv6Network)
if err != nil {
return err
}
gatewayIPv6 = gateway
}
// Block BridgeIP in IP allocator
ipAllocator.RequestIP(bridgeIPv4Network, bridgeIPv4Network.IP)
if config.EnableIptables {
iptables.OnReloaded(portMapper.ReMapAll) // call this on Firewalld reload
}
return nil
}
func setupIPTables(addr net.Addr, icc, ipmasq bool) error {
// Enable NAT
if ipmasq {
natArgs := []string{"-s", addr.String(), "!", "-o", bridgeIface, "-j", "MASQUERADE"}
if !iptables.Exists(iptables.Nat, "POSTROUTING", natArgs...) {
if output, err := iptables.Raw(append([]string{
"-t", string(iptables.Nat), "-I", "POSTROUTING"}, natArgs...)...); err != nil {
return fmt.Errorf("Unable to enable network bridge NAT: %s", err)
} else if len(output) != 0 {
return iptables.ChainError{Chain: "POSTROUTING", Output: output}
}
}
}
var (
args = []string{"-i", bridgeIface, "-o", bridgeIface, "-j"}
acceptArgs = append(args, "ACCEPT")
dropArgs = append(args, "DROP")
)
if !icc {
iptables.Raw(append([]string{"-D", "FORWARD"}, acceptArgs...)...)
if !iptables.Exists(iptables.Filter, "FORWARD", dropArgs...) {
logrus.Debugf("Disable inter-container communication")
if output, err := iptables.Raw(append([]string{"-A", "FORWARD"}, dropArgs...)...); err != nil {
return fmt.Errorf("Unable to prevent intercontainer communication: %s", err)
} else if len(output) != 0 {
return fmt.Errorf("Error disabling intercontainer communication: %s", output)
}
}
} else {
iptables.Raw(append([]string{"-D", "FORWARD"}, dropArgs...)...)
if !iptables.Exists(iptables.Filter, "FORWARD", acceptArgs...) {
logrus.Debugf("Enable inter-container communication")
if output, err := iptables.Raw(append([]string{"-A", "FORWARD"}, acceptArgs...)...); err != nil {
return fmt.Errorf("Unable to allow intercontainer communication: %s", err)
} else if len(output) != 0 {
return fmt.Errorf("Error enabling intercontainer communication: %s", output)
}
}
}
// In hairpin mode, masquerade traffic from localhost
if hairpinMode {
masqueradeArgs := []string{"-t", "nat", "-m", "addrtype", "--src-type", "LOCAL", "-o", bridgeIface, "-j", "MASQUERADE"}
if !iptables.Exists(iptables.Filter, "POSTROUTING", masqueradeArgs...) {
if output, err := iptables.Raw(append([]string{"-I", "POSTROUTING"}, masqueradeArgs...)...); err != nil {
return fmt.Errorf("Unable to masquerade local traffic: %s", err)
} else if len(output) != 0 {
return fmt.Errorf("Error iptables masquerade local traffic: %s", output)
}
}
}
// Accept all non-intercontainer outgoing packets
outgoingArgs := []string{"-i", bridgeIface, "!", "-o", bridgeIface, "-j", "ACCEPT"}
if !iptables.Exists(iptables.Filter, "FORWARD", outgoingArgs...) {
if output, err := iptables.Raw(append([]string{"-I", "FORWARD"}, outgoingArgs...)...); err != nil {
return fmt.Errorf("Unable to allow outgoing packets: %s", err)
} else if len(output) != 0 {
return iptables.ChainError{Chain: "FORWARD outgoing", Output: output}
}
}
// Accept incoming packets for existing connections
existingArgs := []string{"-o", bridgeIface, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"}
if !iptables.Exists(iptables.Filter, "FORWARD", existingArgs...) {
if output, err := iptables.Raw(append([]string{"-I", "FORWARD"}, existingArgs...)...); err != nil {
return fmt.Errorf("Unable to allow incoming packets: %s", err)
} else if len(output) != 0 {
return iptables.ChainError{Chain: "FORWARD incoming", Output: output}
}
}
return nil
}
func RequestPort(ip net.IP, proto string, port int) (int, error) {
initPortMapper()
return portMapper.Allocator.RequestPort(ip, proto, port)
}
// configureBridge attempts to create and configure a network bridge interface named `bridgeIface` on the host
// If bridgeIP is empty, it will try to find a non-conflicting IP from the Docker-specified private ranges
// If the bridge `bridgeIface` already exists, it will only perform the IP address association with the existing
// bridge (fixes issue #8444)
// If an address which doesn't conflict with existing interfaces can't be found, an error is returned.
func configureBridge(bridgeIP string, bridgeIPv6 string, enableIPv6 bool) error {
nameservers := []string{}
resolvConf, _ := resolvconf.Get()
// We don't check for an error here, because we don't really care
// if we can't read /etc/resolv.conf. So instead we skip the append
// if resolvConf is nil. It either doesn't exist, or we can't read it
// for some reason.
if resolvConf != nil {
nameservers = append(nameservers, resolvconf.GetNameserversAsCIDR(resolvConf)...)
}
var ifaceAddr string
if len(bridgeIP) != 0 {
_, _, err := net.ParseCIDR(bridgeIP)
if err != nil {
return err
}
ifaceAddr = bridgeIP
} else {
for _, addr := range addrs {
_, dockerNetwork, err := net.ParseCIDR(addr)
if err != nil {
return err
}
if err := networkdriver.CheckNameserverOverlaps(nameservers, dockerNetwork); err == nil {
if err := networkdriver.CheckRouteOverlaps(dockerNetwork); err == nil {
ifaceAddr = addr
break
} else {
logrus.Debugf("%s %s", addr, err)
}
}
}
}
if ifaceAddr == "" {
return fmt.Errorf("Could not find a free IP address range for interface '%s'. Please configure its address manually and run 'docker -b %s'", bridgeIface, bridgeIface)
}
logrus.Debugf("Creating bridge %s with network %s", bridgeIface, ifaceAddr)
if err := createBridgeIface(bridgeIface); err != nil {
// The bridge may already exist, therefore we can ignore an "exists" error
if !os.IsExist(err) {
return err
}
}
iface, err := net.InterfaceByName(bridgeIface)
if err != nil {
return err
}
ipAddr, ipNet, err := net.ParseCIDR(ifaceAddr)
if err != nil {
return err
}
if err := netlink.NetworkLinkAddIp(iface, ipAddr, ipNet); err != nil {
return fmt.Errorf("Unable to add private network: %s", err)
}
if enableIPv6 {
if err := setupIPv6Bridge(bridgeIPv6); err != nil {
return err
}
}
if err := netlink.NetworkLinkUp(iface); err != nil {
return fmt.Errorf("Unable to start network bridge: %s", err)
}
return nil
}
func setupIPv6Bridge(bridgeIPv6 string) error {
iface, err := net.InterfaceByName(bridgeIface)
if err != nil {
return err
}
// Enable IPv6 on the bridge
procFile := "/proc/sys/net/ipv6/conf/" + iface.Name + "/disable_ipv6"
if err := ioutil.WriteFile(procFile, []byte{'0', '\n'}, 0644); err != nil {
return fmt.Errorf("Unable to enable IPv6 addresses on bridge: %v", err)
}
ipAddr6, ipNet6, err := net.ParseCIDR(bridgeIPv6)
if err != nil {
return fmt.Errorf("Unable to parse bridge IPv6 address: %q, error: %v", bridgeIPv6, err)
}
if err := netlink.NetworkLinkAddIp(iface, ipAddr6, ipNet6); err != nil {
return fmt.Errorf("Unable to add private IPv6 network: %v", err)
}
return nil
}
func requestDefaultGateway(requestedGateway string, network *net.IPNet) (gateway net.IP, err error) {
if requestedGateway != "" {
gateway = net.ParseIP(requestedGateway)
if gateway == nil {
return nil, fmt.Errorf("Bad parameter: invalid gateway ip %s", requestedGateway)
}
if !network.Contains(gateway) {
return nil, fmt.Errorf("Gateway ip %s must be part of the network %s", requestedGateway, network.String())
}
ipAllocator.RequestIP(network, gateway)
}
return gateway, nil
}
func createBridgeIface(name string) error {
kv, err := kernel.GetKernelVersion()
// Only set the bridge's mac address if the kernel version is > 3.3
// before that it was not supported
setBridgeMacAddr := err == nil && (kv.Kernel >= 3 && kv.Major >= 3)
logrus.Debugf("setting bridge mac address = %v", setBridgeMacAddr)
return netlink.CreateBridge(name, setBridgeMacAddr)
}
// Generate a IEEE802 compliant MAC address from the given IP address.
//
// The generator is guaranteed to be consistent: the same IP will always yield the same
// MAC address. This is to avoid ARP cache issues.
func generateMacAddr(ip net.IP) net.HardwareAddr {
hw := make(net.HardwareAddr, 6)
// The first byte of the MAC address has to comply with these rules:
// 1. Unicast: Set the least-significant bit to 0.
// 2. Address is locally administered: Set the second-least-significant bit (U/L) to 1.
// 3. As "small" as possible: The veth address has to be "smaller" than the bridge address.
hw[0] = 0x02
// The first 24 bits of the MAC represent the Organizationally Unique Identifier (OUI).
// Since this address is locally administered, we can do whatever we want as long as
// it doesn't conflict with other addresses.
hw[1] = 0x42
// Insert the IP address into the last 32 bits of the MAC address.
// This is a simple way to guarantee the address will be consistent and unique.
copy(hw[2:], ip.To4())
return hw
}
func linkLocalIPv6FromMac(mac string) (string, error) {
hx := strings.Replace(mac, ":", "", -1)
hw, err := hex.DecodeString(hx)
if err != nil {
return "", errors.New("Could not parse MAC address " + mac)
}
hw[0] ^= 0x2
return fmt.Sprintf("fe80::%x%x:%xff:fe%x:%x%x/64", hw[0], hw[1], hw[2], hw[3], hw[4], hw[5]), nil
}
// Allocate a network interface
func Allocate(id, requestedMac, requestedIP, requestedIPv6 string) (*network.Settings, error) {
var (
ip net.IP
mac net.HardwareAddr
err error
globalIPv6 net.IP
defaultGWIPv4 net.IP
defaultGWIPv6 net.IP
)
ip, err = ipAllocator.RequestIP(bridgeIPv4Network, net.ParseIP(requestedIP))
if err != nil {
return nil, err
}
// If no explicit mac address was given, generate one from the IP address.
if mac, err = net.ParseMAC(requestedMac); err != nil {
mac = generateMacAddr(ip)
}
if globalIPv6Network != nil {
// If globalIPv6Network Size is at least a /80 subnet generate IPv6 address from MAC address
netmaskOnes, _ := globalIPv6Network.Mask.Size()
ipv6 := net.ParseIP(requestedIPv6)
if ipv6 == nil && netmaskOnes <= 80 {
ipv6 = make(net.IP, len(globalIPv6Network.IP))
copy(ipv6, globalIPv6Network.IP)
for i, h := range mac {
ipv6[i+10] = h
}
}
globalIPv6, err = ipAllocator.RequestIP(globalIPv6Network, ipv6)
if err != nil {
logrus.Errorf("Allocator: RequestIP v6: %v", err)
return nil, err
}
logrus.Infof("Allocated IPv6 %s", globalIPv6)
}
maskSize, _ := bridgeIPv4Network.Mask.Size()
if gatewayIPv4 != nil {
defaultGWIPv4 = gatewayIPv4
} else {
defaultGWIPv4 = bridgeIPv4Network.IP
}
if gatewayIPv6 != nil {
defaultGWIPv6 = gatewayIPv6
} else {
defaultGWIPv6 = bridgeIPv6Addr
}
// If linklocal IPv6
localIPv6Net, err := linkLocalIPv6FromMac(mac.String())
if err != nil {
return nil, err
}
localIPv6, _, _ := net.ParseCIDR(localIPv6Net)
networkSettings := &network.Settings{
IPAddress: ip.String(),
Gateway: defaultGWIPv4.String(),
MacAddress: mac.String(),
Bridge: bridgeIface,
IPPrefixLen: maskSize,
LinkLocalIPv6Address: localIPv6.String(),
HairpinMode: hairpinMode,
}
if globalIPv6Network != nil {
networkSettings.GlobalIPv6Address = globalIPv6.String()
maskV6Size, _ := globalIPv6Network.Mask.Size()
networkSettings.GlobalIPv6PrefixLen = maskV6Size
networkSettings.IPv6Gateway = defaultGWIPv6.String()
}
currentInterfaces.Set(id, &networkInterface{
IP: ip,
IPv6: globalIPv6,
})
return networkSettings, nil
}
// Release an interface for a select ip
func Release(id string) {
var containerInterface = currentInterfaces.Get(id)
if containerInterface == nil {
logrus.Warnf("No network information to release for %s", id)
return
}
for _, nat := range containerInterface.PortMappings {
if err := portMapper.Unmap(nat); err != nil {
logrus.Infof("Unable to unmap port %s: %s", nat, err)
}
}
if err := ipAllocator.ReleaseIP(bridgeIPv4Network, containerInterface.IP); err != nil {
logrus.Infof("Unable to release IPv4 %s", err)
}
if globalIPv6Network != nil {
if err := ipAllocator.ReleaseIP(globalIPv6Network, containerInterface.IPv6); err != nil {
logrus.Infof("Unable to release IPv6 %s", err)
}
}
}
// Allocate an external port and map it to the interface
func AllocatePort(id string, port nat.Port, binding nat.PortBinding) (nat.PortBinding, error) {
var (
ip = defaultBindingIP
proto = port.Proto()
containerPort = port.Int()
network = currentInterfaces.Get(id)
)
if binding.HostIp != "" {
ip = net.ParseIP(binding.HostIp)
if ip == nil {
return nat.PortBinding{}, fmt.Errorf("Bad parameter: invalid host ip %s", binding.HostIp)
}
}
// host ip, proto, and host port
var container net.Addr
switch proto {
case "tcp":
container = &net.TCPAddr{IP: network.IP, Port: containerPort}
case "udp":
container = &net.UDPAddr{IP: network.IP, Port: containerPort}
default:
return nat.PortBinding{}, fmt.Errorf("unsupported address type %s", proto)
}
//
// Try up to 10 times to get a port that's not already allocated.
//
// In the event of failure to bind, return the error that portmapper.Map
// yields.
//
var (
host net.Addr
err error
)
hostPort, err := nat.ParsePort(binding.HostPort)
if err != nil {
return nat.PortBinding{}, err
}
for i := 0; i < MaxAllocatedPortAttempts; i++ {
if host, err = portMapper.Map(container, ip, hostPort, !hairpinMode); err == nil {
break
}
// There is no point in immediately retrying to map an explicitly
// chosen port.
if hostPort != 0 {
logrus.Warnf("Failed to allocate and map port %d: %s", hostPort, err)
break
}
logrus.Warnf("Failed to allocate and map port: %s, retry: %d", err, i+1)
}
if err != nil {
return nat.PortBinding{}, err
}
network.PortMappings = append(network.PortMappings, host)
switch netAddr := host.(type) {
case *net.TCPAddr:
return nat.PortBinding{HostIp: netAddr.IP.String(), HostPort: strconv.Itoa(netAddr.Port)}, nil
case *net.UDPAddr:
return nat.PortBinding{HostIp: netAddr.IP.String(), HostPort: strconv.Itoa(netAddr.Port)}, nil
default:
return nat.PortBinding{}, fmt.Errorf("unsupported address type %T", netAddr)
}
}
//TODO: should it return something more than just an error?
func LinkContainers(action, parentIP, childIP string, ports []nat.Port, ignoreErrors bool) error {
var nfAction iptables.Action
switch action {
case "-A":
nfAction = iptables.Append
case "-I":
nfAction = iptables.Insert
case "-D":
nfAction = iptables.Delete
default:
return fmt.Errorf("Invalid action '%s' specified", action)
}
ip1 := net.ParseIP(parentIP)
if ip1 == nil {
return fmt.Errorf("Parent IP '%s' is invalid", parentIP)
}
ip2 := net.ParseIP(childIP)
if ip2 == nil {
return fmt.Errorf("Child IP '%s' is invalid", childIP)
}
chain := iptables.Chain{Name: "DOCKER", Bridge: bridgeIface}
for _, port := range ports {
if err := chain.Link(nfAction, ip1, ip2, port.Int(), port.Proto()); !ignoreErrors && err != nil {
return err
}
}
return nil
}

View file

@ -1,193 +0,0 @@
package bridge
import (
"fmt"
"net"
"strconv"
"testing"
"github.com/docker/docker/daemon/network"
"github.com/docker/docker/daemon/networkdriver/portmapper"
"github.com/docker/docker/nat"
"github.com/docker/docker/pkg/iptables"
)
func init() {
// reset the new proxy command for mocking out the userland proxy in tests
portmapper.NewProxy = portmapper.NewMockProxyCommand
}
func findFreePort(t *testing.T) string {
l, err := net.Listen("tcp", ":0")
if err != nil {
t.Fatal("Failed to find a free port")
}
defer l.Close()
result, err := net.ResolveTCPAddr("tcp", l.Addr().String())
if err != nil {
t.Fatal("Failed to resolve address to identify free port")
}
return strconv.Itoa(result.Port)
}
func TestAllocatePortDetection(t *testing.T) {
freePort := findFreePort(t)
if err := InitDriver(new(Config)); err != nil {
t.Fatal("Failed to initialize network driver")
}
// Allocate interface
if _, err := Allocate("container_id", "", "", ""); err != nil {
t.Fatal("Failed to allocate network interface")
}
port := nat.Port(freePort + "/tcp")
binding := nat.PortBinding{HostIp: "127.0.0.1", HostPort: freePort}
// Allocate same port twice, expect failure on second call
if _, err := AllocatePort("container_id", port, binding); err != nil {
t.Fatal("Failed to find a free port to allocate")
}
if _, err := AllocatePort("container_id", port, binding); err == nil {
t.Fatal("Duplicate port allocation granted by AllocatePort")
}
}
func TestHostnameFormatChecking(t *testing.T) {
freePort := findFreePort(t)
if err := InitDriver(new(Config)); err != nil {
t.Fatal("Failed to initialize network driver")
}
// Allocate interface
if _, err := Allocate("container_id", "", "", ""); err != nil {
t.Fatal("Failed to allocate network interface")
}
port := nat.Port(freePort + "/tcp")
binding := nat.PortBinding{HostIp: "localhost", HostPort: freePort}
if _, err := AllocatePort("container_id", port, binding); err == nil {
t.Fatal("Failed to check invalid HostIP")
}
}
func newInterfaceAllocation(t *testing.T, globalIPv6 *net.IPNet, requestedMac, requestedIP, requestedIPv6 string, expectFail bool) *network.Settings {
// set IPv6 global if given
if globalIPv6 != nil {
globalIPv6Network = globalIPv6
}
networkSettings, err := Allocate("container_id", requestedMac, requestedIP, requestedIPv6)
if err == nil && expectFail {
t.Fatal("Doesn't fail to allocate network interface")
} else if err != nil && !expectFail {
t.Fatal("Failed to allocate network interface")
}
if globalIPv6 != nil {
// check for bug #11427
if globalIPv6Network.IP.String() != globalIPv6.IP.String() {
t.Fatal("globalIPv6Network was modified during allocation")
}
// clean up IPv6 global
globalIPv6Network = nil
}
return networkSettings
}
func TestIPv6InterfaceAllocationAutoNetmaskGt80(t *testing.T) {
_, subnet, _ := net.ParseCIDR("2001:db8:1234:1234:1234::/81")
networkSettings := newInterfaceAllocation(t, subnet, "", "", "", false)
// ensure low manually assigend global ip
ip := net.ParseIP(networkSettings.GlobalIPv6Address)
_, subnet, _ = net.ParseCIDR(fmt.Sprintf("%s/%d", subnet.IP.String(), 120))
if !subnet.Contains(ip) {
t.Fatalf("Error ip %s not in subnet %s", ip.String(), subnet.String())
}
}
func TestIPv6InterfaceAllocationAutoNetmaskLe80(t *testing.T) {
_, subnet, _ := net.ParseCIDR("2001:db8:1234:1234:1234::/80")
networkSettings := newInterfaceAllocation(t, subnet, "ab:cd:ab:cd:ab:cd", "", "", false)
// ensure global ip with mac
ip := net.ParseIP(networkSettings.GlobalIPv6Address)
expectedIP := net.ParseIP("2001:db8:1234:1234:1234:abcd:abcd:abcd")
if ip.String() != expectedIP.String() {
t.Fatalf("Error ip %s should be %s", ip.String(), expectedIP.String())
}
// ensure link local format
ip = net.ParseIP(networkSettings.LinkLocalIPv6Address)
expectedIP = net.ParseIP("fe80::a9cd:abff:fecd:abcd")
if ip.String() != expectedIP.String() {
t.Fatalf("Error ip %s should be %s", ip.String(), expectedIP.String())
}
}
func TestIPv6InterfaceAllocationRequest(t *testing.T) {
_, subnet, _ := net.ParseCIDR("2001:db8:1234:1234:1234::/80")
expectedIP := "2001:db8:1234:1234:1234::1328"
networkSettings := newInterfaceAllocation(t, subnet, "", "", expectedIP, false)
// ensure global ip with mac
ip := net.ParseIP(networkSettings.GlobalIPv6Address)
if ip.String() != expectedIP {
t.Fatalf("Error ip %s should be %s", ip.String(), expectedIP)
}
// retry -> fails for duplicated address
_ = newInterfaceAllocation(t, subnet, "", "", expectedIP, true)
}
func TestMacAddrGeneration(t *testing.T) {
ip := net.ParseIP("192.168.0.1")
mac := generateMacAddr(ip).String()
// Should be consistent.
if generateMacAddr(ip).String() != mac {
t.Fatal("Inconsistent MAC address")
}
// Should be unique.
ip2 := net.ParseIP("192.168.0.2")
if generateMacAddr(ip2).String() == mac {
t.Fatal("Non-unique MAC address")
}
}
func TestLinkContainers(t *testing.T) {
// Init driver
if err := InitDriver(new(Config)); err != nil {
t.Fatal("Failed to initialize network driver")
}
// Allocate interface
if _, err := Allocate("container_id", "", "", ""); err != nil {
t.Fatal("Failed to allocate network interface")
}
bridgeIface = "lo"
if _, err := iptables.NewChain("DOCKER", bridgeIface, iptables.Filter, false); err != nil {
t.Fatal(err)
}
if err := LinkContainers("-I", "172.17.0.1", "172.17.0.2", []nat.Port{nat.Port("1234")}, false); err != nil {
t.Fatal("LinkContainers failed")
}
// flush rules
if _, err := iptables.Raw([]string{"-F", "DOCKER"}...); err != nil {
t.Fatal(err)
}
}

View file

@ -1,167 +0,0 @@
package ipallocator
import (
"errors"
"math/big"
"net"
"sync"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/networkdriver"
)
// allocatedMap is thread-unsafe set of allocated IP
type allocatedMap struct {
p map[string]struct{}
last *big.Int
begin *big.Int
end *big.Int
}
func newAllocatedMap(network *net.IPNet) *allocatedMap {
firstIP, lastIP := networkdriver.NetworkRange(network)
begin := big.NewInt(0).Add(ipToBigInt(firstIP), big.NewInt(1))
end := big.NewInt(0).Sub(ipToBigInt(lastIP), big.NewInt(1))
return &allocatedMap{
p: make(map[string]struct{}),
begin: begin,
end: end,
last: big.NewInt(0).Sub(begin, big.NewInt(1)), // so first allocated will be begin
}
}
type networkSet map[string]*allocatedMap
var (
ErrNoAvailableIPs = errors.New("no available ip addresses on network")
ErrIPAlreadyAllocated = errors.New("ip already allocated")
ErrIPOutOfRange = errors.New("requested ip is out of range")
ErrNetworkAlreadyRegistered = errors.New("network already registered")
ErrBadSubnet = errors.New("network does not contain specified subnet")
)
type IPAllocator struct {
allocatedIPs networkSet
mutex sync.Mutex
}
func New() *IPAllocator {
return &IPAllocator{networkSet{}, sync.Mutex{}}
}
// RegisterSubnet registers network in global allocator with bounds
// defined by subnet. If you want to use network range you must call
// this method before first RequestIP, otherwise full network range will be used
func (a *IPAllocator) RegisterSubnet(network *net.IPNet, subnet *net.IPNet) error {
a.mutex.Lock()
defer a.mutex.Unlock()
key := network.String()
if _, ok := a.allocatedIPs[key]; ok {
return ErrNetworkAlreadyRegistered
}
n := newAllocatedMap(network)
beginIP, endIP := networkdriver.NetworkRange(subnet)
begin := big.NewInt(0).Add(ipToBigInt(beginIP), big.NewInt(1))
end := big.NewInt(0).Sub(ipToBigInt(endIP), big.NewInt(1))
// Check that subnet is within network
if !(begin.Cmp(n.begin) >= 0 && end.Cmp(n.end) <= 0 && begin.Cmp(end) == -1) {
return ErrBadSubnet
}
n.begin.Set(begin)
n.end.Set(end)
n.last.Sub(begin, big.NewInt(1))
a.allocatedIPs[key] = n
return nil
}
// RequestIP requests an available ip from the given network. It
// will return the next available ip if the ip provided is nil. If the
// ip provided is not nil it will validate that the provided ip is available
// for use or return an error
func (a *IPAllocator) RequestIP(network *net.IPNet, ip net.IP) (net.IP, error) {
a.mutex.Lock()
defer a.mutex.Unlock()
key := network.String()
allocated, ok := a.allocatedIPs[key]
if !ok {
allocated = newAllocatedMap(network)
a.allocatedIPs[key] = allocated
}
if ip == nil {
return allocated.getNextIP()
}
return allocated.checkIP(ip)
}
// ReleaseIP adds the provided ip back into the pool of
// available ips to be returned for use.
func (a *IPAllocator) ReleaseIP(network *net.IPNet, ip net.IP) error {
a.mutex.Lock()
defer a.mutex.Unlock()
if allocated, exists := a.allocatedIPs[network.String()]; exists {
delete(allocated.p, ip.String())
}
return nil
}
func (allocated *allocatedMap) checkIP(ip net.IP) (net.IP, error) {
if _, ok := allocated.p[ip.String()]; ok {
return nil, ErrIPAlreadyAllocated
}
pos := ipToBigInt(ip)
// Verify that the IP address is within our network range.
if pos.Cmp(allocated.begin) == -1 || pos.Cmp(allocated.end) == 1 {
return nil, ErrIPOutOfRange
}
// Register the IP.
allocated.p[ip.String()] = struct{}{}
return ip, nil
}
// return an available ip if one is currently available. If not,
// return the next available ip for the network
func (allocated *allocatedMap) getNextIP() (net.IP, error) {
pos := big.NewInt(0).Set(allocated.last)
allRange := big.NewInt(0).Sub(allocated.end, allocated.begin)
for i := big.NewInt(0); i.Cmp(allRange) <= 0; i.Add(i, big.NewInt(1)) {
pos.Add(pos, big.NewInt(1))
if pos.Cmp(allocated.end) == 1 {
pos.Set(allocated.begin)
}
if _, ok := allocated.p[bigIntToIP(pos).String()]; ok {
continue
}
allocated.p[bigIntToIP(pos).String()] = struct{}{}
allocated.last.Set(pos)
return bigIntToIP(pos), nil
}
return nil, ErrNoAvailableIPs
}
// Converts a 4 bytes IP into a 128 bit integer
func ipToBigInt(ip net.IP) *big.Int {
x := big.NewInt(0)
if ip4 := ip.To4(); ip4 != nil {
return x.SetBytes(ip4)
}
if ip6 := ip.To16(); ip6 != nil {
return x.SetBytes(ip6)
}
logrus.Errorf("ipToBigInt: Wrong IP length! %s", ip)
return nil
}
// Converts 128 bit integer into a 4 bytes IP address
func bigIntToIP(v *big.Int) net.IP {
return net.IP(v.Bytes())
}

View file

@ -1,690 +0,0 @@
package ipallocator
import (
"fmt"
"math/big"
"net"
"testing"
)
func TestConversion(t *testing.T) {
ip := net.ParseIP("127.0.0.1")
i := ipToBigInt(ip)
if i.Cmp(big.NewInt(0x7f000001)) != 0 {
t.Fatal("incorrect conversion")
}
conv := bigIntToIP(i)
if !ip.Equal(conv) {
t.Error(conv.String())
}
}
func TestConversionIPv6(t *testing.T) {
ip := net.ParseIP("2a00:1450::1")
ip2 := net.ParseIP("2a00:1450::2")
ip3 := net.ParseIP("2a00:1450::1:1")
i := ipToBigInt(ip)
val, success := big.NewInt(0).SetString("2a001450000000000000000000000001", 16)
if !success {
t.Fatal("Hex-String to BigInt conversion failed.")
}
if i.Cmp(val) != 0 {
t.Fatal("incorrent conversion")
}
conv := bigIntToIP(i)
conv2 := bigIntToIP(big.NewInt(0).Add(i, big.NewInt(1)))
conv3 := bigIntToIP(big.NewInt(0).Add(i, big.NewInt(0x10000)))
if !ip.Equal(conv) {
t.Error("2a00:1450::1 should be equal to " + conv.String())
}
if !ip2.Equal(conv2) {
t.Error("2a00:1450::2 should be equal to " + conv2.String())
}
if !ip3.Equal(conv3) {
t.Error("2a00:1450::1:1 should be equal to " + conv3.String())
}
}
func TestRequestNewIps(t *testing.T) {
a := New()
network := &net.IPNet{
IP: []byte{192, 168, 0, 1},
Mask: []byte{255, 255, 255, 0},
}
var ip net.IP
var err error
for i := 1; i < 10; i++ {
ip, err = a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
if expected := fmt.Sprintf("192.168.0.%d", i); ip.String() != expected {
t.Fatalf("Expected ip %s got %s", expected, ip.String())
}
}
value := bigIntToIP(big.NewInt(0).Add(ipToBigInt(ip), big.NewInt(1))).String()
if err := a.ReleaseIP(network, ip); err != nil {
t.Fatal(err)
}
ip, err = a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
if ip.String() != value {
t.Fatalf("Expected to receive the next ip %s got %s", value, ip.String())
}
}
func TestRequestNewIpV6(t *testing.T) {
a := New()
network := &net.IPNet{
IP: []byte{0x2a, 0x00, 0x14, 0x50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
Mask: []byte{255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}, // /64 netmask
}
var ip net.IP
var err error
for i := 1; i < 10; i++ {
ip, err = a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
if expected := fmt.Sprintf("2a00:1450::%d", i); ip.String() != expected {
t.Fatalf("Expected ip %s got %s", expected, ip.String())
}
}
value := bigIntToIP(big.NewInt(0).Add(ipToBigInt(ip), big.NewInt(1))).String()
if err := a.ReleaseIP(network, ip); err != nil {
t.Fatal(err)
}
ip, err = a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
if ip.String() != value {
t.Fatalf("Expected to receive the next ip %s got %s", value, ip.String())
}
}
func TestReleaseIp(t *testing.T) {
a := New()
network := &net.IPNet{
IP: []byte{192, 168, 0, 1},
Mask: []byte{255, 255, 255, 0},
}
ip, err := a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
if err := a.ReleaseIP(network, ip); err != nil {
t.Fatal(err)
}
}
func TestReleaseIpV6(t *testing.T) {
a := New()
network := &net.IPNet{
IP: []byte{0x2a, 0x00, 0x14, 0x50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
Mask: []byte{255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}, // /64 netmask
}
ip, err := a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
if err := a.ReleaseIP(network, ip); err != nil {
t.Fatal(err)
}
}
func TestGetReleasedIp(t *testing.T) {
a := New()
network := &net.IPNet{
IP: []byte{192, 168, 0, 1},
Mask: []byte{255, 255, 255, 0},
}
ip, err := a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
value := ip.String()
if err := a.ReleaseIP(network, ip); err != nil {
t.Fatal(err)
}
for i := 0; i < 253; i++ {
_, err = a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
err = a.ReleaseIP(network, ip)
if err != nil {
t.Fatal(err)
}
}
ip, err = a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
if ip.String() != value {
t.Fatalf("Expected to receive same ip %s got %s", value, ip.String())
}
}
func TestGetReleasedIpV6(t *testing.T) {
a := New()
network := &net.IPNet{
IP: []byte{0x2a, 0x00, 0x14, 0x50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
Mask: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0},
}
ip, err := a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
value := ip.String()
if err := a.ReleaseIP(network, ip); err != nil {
t.Fatal(err)
}
for i := 0; i < 253; i++ {
_, err = a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
err = a.ReleaseIP(network, ip)
if err != nil {
t.Fatal(err)
}
}
ip, err = a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
if ip.String() != value {
t.Fatalf("Expected to receive same ip %s got %s", value, ip.String())
}
}
func TestRequestSpecificIp(t *testing.T) {
a := New()
network := &net.IPNet{
IP: []byte{192, 168, 0, 1},
Mask: []byte{255, 255, 255, 224},
}
ip := net.ParseIP("192.168.0.5")
// Request a "good" IP.
if _, err := a.RequestIP(network, ip); err != nil {
t.Fatal(err)
}
// Request the same IP again.
if _, err := a.RequestIP(network, ip); err != ErrIPAlreadyAllocated {
t.Fatalf("Got the same IP twice: %#v", err)
}
// Request an out of range IP.
if _, err := a.RequestIP(network, net.ParseIP("192.168.0.42")); err != ErrIPOutOfRange {
t.Fatalf("Got an out of range IP: %#v", err)
}
}
func TestRequestSpecificIpV6(t *testing.T) {
a := New()
network := &net.IPNet{
IP: []byte{0x2a, 0x00, 0x14, 0x50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
Mask: []byte{255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}, // /64 netmask
}
ip := net.ParseIP("2a00:1450::5")
// Request a "good" IP.
if _, err := a.RequestIP(network, ip); err != nil {
t.Fatal(err)
}
// Request the same IP again.
if _, err := a.RequestIP(network, ip); err != ErrIPAlreadyAllocated {
t.Fatalf("Got the same IP twice: %#v", err)
}
// Request an out of range IP.
if _, err := a.RequestIP(network, net.ParseIP("2a00:1500::1")); err != ErrIPOutOfRange {
t.Fatalf("Got an out of range IP: %#v", err)
}
}
func TestIPAllocator(t *testing.T) {
a := New()
expectedIPs := []net.IP{
0: net.IPv4(127, 0, 0, 1),
1: net.IPv4(127, 0, 0, 2),
2: net.IPv4(127, 0, 0, 3),
3: net.IPv4(127, 0, 0, 4),
4: net.IPv4(127, 0, 0, 5),
5: net.IPv4(127, 0, 0, 6),
}
gwIP, n, _ := net.ParseCIDR("127.0.0.1/29")
network := &net.IPNet{IP: gwIP, Mask: n.Mask}
// Pool after initialisation (f = free, u = used)
// 1(f) - 2(f) - 3(f) - 4(f) - 5(f) - 6(f)
// ↑
// Check that we get 6 IPs, from 127.0.0.1127.0.0.6, in that
// order.
for i := 0; i < 6; i++ {
ip, err := a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
assertIPEquals(t, expectedIPs[i], ip)
}
// Before loop begin
// 1(f) - 2(f) - 3(f) - 4(f) - 5(f) - 6(f)
// ↑
// After i = 0
// 1(u) - 2(f) - 3(f) - 4(f) - 5(f) - 6(f)
// ↑
// After i = 1
// 1(u) - 2(u) - 3(f) - 4(f) - 5(f) - 6(f)
// ↑
// After i = 2
// 1(u) - 2(u) - 3(u) - 4(f) - 5(f) - 6(f)
// ↑
// After i = 3
// 1(u) - 2(u) - 3(u) - 4(u) - 5(f) - 6(f)
// ↑
// After i = 4
// 1(u) - 2(u) - 3(u) - 4(u) - 5(u) - 6(f)
// ↑
// After i = 5
// 1(u) - 2(u) - 3(u) - 4(u) - 5(u) - 6(u)
// ↑
// Check that there are no more IPs
ip, err := a.RequestIP(network, nil)
if err == nil {
t.Fatalf("There shouldn't be any IP addresses at this point, got %s\n", ip)
}
// Release some IPs in non-sequential order
if err := a.ReleaseIP(network, expectedIPs[3]); err != nil {
t.Fatal(err)
}
// 1(u) - 2(u) - 3(u) - 4(f) - 5(u) - 6(u)
// ↑
if err := a.ReleaseIP(network, expectedIPs[2]); err != nil {
t.Fatal(err)
}
// 1(u) - 2(u) - 3(f) - 4(f) - 5(u) - 6(u)
// ↑
if err := a.ReleaseIP(network, expectedIPs[4]); err != nil {
t.Fatal(err)
}
// 1(u) - 2(u) - 3(f) - 4(f) - 5(f) - 6(u)
// ↑
// Make sure that IPs are reused in sequential order, starting
// with the first released IP
newIPs := make([]net.IP, 3)
for i := 0; i < 3; i++ {
ip, err := a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
newIPs[i] = ip
}
assertIPEquals(t, expectedIPs[2], newIPs[0])
assertIPEquals(t, expectedIPs[3], newIPs[1])
assertIPEquals(t, expectedIPs[4], newIPs[2])
_, err = a.RequestIP(network, nil)
if err == nil {
t.Fatal("There shouldn't be any IP addresses at this point")
}
}
func TestAllocateFirstIP(t *testing.T) {
a := New()
network := &net.IPNet{
IP: []byte{192, 168, 0, 0},
Mask: []byte{255, 255, 255, 0},
}
firstIP := network.IP.To4().Mask(network.Mask)
first := big.NewInt(0).Add(ipToBigInt(firstIP), big.NewInt(1))
ip, err := a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
allocated := ipToBigInt(ip)
if allocated == first {
t.Fatalf("allocated ip should not equal first ip: %d == %d", first, allocated)
}
}
func TestAllocateAllIps(t *testing.T) {
a := New()
network := &net.IPNet{
IP: []byte{192, 168, 0, 1},
Mask: []byte{255, 255, 255, 0},
}
var (
current, first net.IP
err error
isFirst = true
)
for err == nil {
current, err = a.RequestIP(network, nil)
if isFirst {
first = current
isFirst = false
}
}
if err != ErrNoAvailableIPs {
t.Fatal(err)
}
if _, err := a.RequestIP(network, nil); err != ErrNoAvailableIPs {
t.Fatal(err)
}
if err := a.ReleaseIP(network, first); err != nil {
t.Fatal(err)
}
again, err := a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
assertIPEquals(t, first, again)
// ensure that alloc.last == alloc.begin won't result in dead loop
if _, err := a.RequestIP(network, nil); err != ErrNoAvailableIPs {
t.Fatal(err)
}
// Test by making alloc.last the only free ip and ensure we get it back
// #1. first of the range, (alloc.last == ipToInt(first) already)
if err := a.ReleaseIP(network, first); err != nil {
t.Fatal(err)
}
ret, err := a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
assertIPEquals(t, first, ret)
// #2. last of the range, note that current is the last one
last := net.IPv4(192, 168, 0, 254)
setLastTo(t, a, network, last)
ret, err = a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
assertIPEquals(t, last, ret)
// #3. middle of the range
mid := net.IPv4(192, 168, 0, 7)
setLastTo(t, a, network, mid)
ret, err = a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
assertIPEquals(t, mid, ret)
}
// make sure the pool is full when calling setLastTo.
// we don't cheat here
func setLastTo(t *testing.T, a *IPAllocator, network *net.IPNet, ip net.IP) {
if err := a.ReleaseIP(network, ip); err != nil {
t.Fatal(err)
}
ret, err := a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
assertIPEquals(t, ip, ret)
if err := a.ReleaseIP(network, ip); err != nil {
t.Fatal(err)
}
}
func TestAllocateDifferentSubnets(t *testing.T) {
a := New()
network1 := &net.IPNet{
IP: []byte{192, 168, 0, 1},
Mask: []byte{255, 255, 255, 0},
}
network2 := &net.IPNet{
IP: []byte{127, 0, 0, 1},
Mask: []byte{255, 255, 255, 0},
}
network3 := &net.IPNet{
IP: []byte{0x2a, 0x00, 0x14, 0x50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
Mask: []byte{255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}, // /64 netmask
}
network4 := &net.IPNet{
IP: []byte{0x2a, 0x00, 0x16, 0x32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
Mask: []byte{255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}, // /64 netmask
}
expectedIPs := []net.IP{
0: net.IPv4(192, 168, 0, 1),
1: net.IPv4(192, 168, 0, 2),
2: net.IPv4(127, 0, 0, 1),
3: net.IPv4(127, 0, 0, 2),
4: net.ParseIP("2a00:1450::1"),
5: net.ParseIP("2a00:1450::2"),
6: net.ParseIP("2a00:1450::3"),
7: net.ParseIP("2a00:1632::1"),
8: net.ParseIP("2a00:1632::2"),
}
ip11, err := a.RequestIP(network1, nil)
if err != nil {
t.Fatal(err)
}
ip12, err := a.RequestIP(network1, nil)
if err != nil {
t.Fatal(err)
}
ip21, err := a.RequestIP(network2, nil)
if err != nil {
t.Fatal(err)
}
ip22, err := a.RequestIP(network2, nil)
if err != nil {
t.Fatal(err)
}
ip31, err := a.RequestIP(network3, nil)
if err != nil {
t.Fatal(err)
}
ip32, err := a.RequestIP(network3, nil)
if err != nil {
t.Fatal(err)
}
ip33, err := a.RequestIP(network3, nil)
if err != nil {
t.Fatal(err)
}
ip41, err := a.RequestIP(network4, nil)
if err != nil {
t.Fatal(err)
}
ip42, err := a.RequestIP(network4, nil)
if err != nil {
t.Fatal(err)
}
assertIPEquals(t, expectedIPs[0], ip11)
assertIPEquals(t, expectedIPs[1], ip12)
assertIPEquals(t, expectedIPs[2], ip21)
assertIPEquals(t, expectedIPs[3], ip22)
assertIPEquals(t, expectedIPs[4], ip31)
assertIPEquals(t, expectedIPs[5], ip32)
assertIPEquals(t, expectedIPs[6], ip33)
assertIPEquals(t, expectedIPs[7], ip41)
assertIPEquals(t, expectedIPs[8], ip42)
}
func TestRegisterBadTwice(t *testing.T) {
a := New()
network := &net.IPNet{
IP: []byte{192, 168, 1, 1},
Mask: []byte{255, 255, 255, 0},
}
subnet := &net.IPNet{
IP: []byte{192, 168, 1, 8},
Mask: []byte{255, 255, 255, 248},
}
if err := a.RegisterSubnet(network, subnet); err != nil {
t.Fatal(err)
}
subnet = &net.IPNet{
IP: []byte{192, 168, 1, 16},
Mask: []byte{255, 255, 255, 248},
}
if err := a.RegisterSubnet(network, subnet); err != ErrNetworkAlreadyRegistered {
t.Fatalf("Expected ErrNetworkAlreadyRegistered error, got %v", err)
}
}
func TestRegisterBadRange(t *testing.T) {
a := New()
network := &net.IPNet{
IP: []byte{192, 168, 1, 1},
Mask: []byte{255, 255, 255, 0},
}
subnet := &net.IPNet{
IP: []byte{192, 168, 1, 1},
Mask: []byte{255, 255, 0, 0},
}
if err := a.RegisterSubnet(network, subnet); err != ErrBadSubnet {
t.Fatalf("Expected ErrBadSubnet error, got %v", err)
}
}
func TestAllocateFromRange(t *testing.T) {
a := New()
network := &net.IPNet{
IP: []byte{192, 168, 0, 1},
Mask: []byte{255, 255, 255, 0},
}
// 192.168.1.9 - 192.168.1.14
subnet := &net.IPNet{
IP: []byte{192, 168, 0, 8},
Mask: []byte{255, 255, 255, 248},
}
if err := a.RegisterSubnet(network, subnet); err != nil {
t.Fatal(err)
}
expectedIPs := []net.IP{
0: net.IPv4(192, 168, 0, 9),
1: net.IPv4(192, 168, 0, 10),
2: net.IPv4(192, 168, 0, 11),
3: net.IPv4(192, 168, 0, 12),
4: net.IPv4(192, 168, 0, 13),
5: net.IPv4(192, 168, 0, 14),
}
for _, ip := range expectedIPs {
rip, err := a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
assertIPEquals(t, ip, rip)
}
if _, err := a.RequestIP(network, nil); err != ErrNoAvailableIPs {
t.Fatalf("Expected ErrNoAvailableIPs error, got %v", err)
}
for _, ip := range expectedIPs {
a.ReleaseIP(network, ip)
rip, err := a.RequestIP(network, nil)
if err != nil {
t.Fatal(err)
}
assertIPEquals(t, ip, rip)
}
}
func assertIPEquals(t *testing.T, ip1, ip2 net.IP) {
if !ip1.Equal(ip2) {
t.Fatalf("Expected IP %s, got %s", ip1, ip2)
}
}
func BenchmarkRequestIP(b *testing.B) {
network := &net.IPNet{
IP: []byte{192, 168, 0, 1},
Mask: []byte{255, 255, 255, 0},
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
a := New()
for j := 0; j < 253; j++ {
_, err := a.RequestIP(network, nil)
if err != nil {
b.Fatal(err)
}
}
}
}

View file

@ -1,10 +0,0 @@
package networkdriver
import (
"errors"
)
var (
ErrNetworkOverlapsWithNameservers = errors.New("requested network overlaps with nameserver")
ErrNetworkOverlaps = errors.New("requested network overlaps with existing network")
)

View file

@ -1,175 +0,0 @@
package networkdriver
import (
"github.com/docker/libcontainer/netlink"
"net"
"testing"
)
func TestNonOverlapingNameservers(t *testing.T) {
network := &net.IPNet{
IP: []byte{192, 168, 0, 1},
Mask: []byte{255, 255, 255, 0},
}
nameservers := []string{
"127.0.0.1/32",
}
if err := CheckNameserverOverlaps(nameservers, network); err != nil {
t.Fatal(err)
}
}
func TestOverlapingNameservers(t *testing.T) {
network := &net.IPNet{
IP: []byte{192, 168, 0, 1},
Mask: []byte{255, 255, 255, 0},
}
nameservers := []string{
"192.168.0.1/32",
}
if err := CheckNameserverOverlaps(nameservers, network); err == nil {
t.Fatalf("Expected error %s got %s", ErrNetworkOverlapsWithNameservers, err)
}
}
func TestCheckRouteOverlaps(t *testing.T) {
orig := networkGetRoutesFct
defer func() {
networkGetRoutesFct = orig
}()
networkGetRoutesFct = func() ([]netlink.Route, error) {
routesData := []string{"10.0.2.0/32", "10.0.3.0/24", "10.0.42.0/24", "172.16.42.0/24", "192.168.142.0/24"}
routes := []netlink.Route{}
for _, addr := range routesData {
_, netX, _ := net.ParseCIDR(addr)
routes = append(routes, netlink.Route{IPNet: netX})
}
return routes, nil
}
_, netX, _ := net.ParseCIDR("172.16.0.1/24")
if err := CheckRouteOverlaps(netX); err != nil {
t.Fatal(err)
}
_, netX, _ = net.ParseCIDR("10.0.2.0/24")
if err := CheckRouteOverlaps(netX); err == nil {
t.Fatalf("10.0.2.0/24 and 10.0.2.0 should overlap but it doesn't")
}
}
func TestCheckNameserverOverlaps(t *testing.T) {
nameservers := []string{"10.0.2.3/32", "192.168.102.1/32"}
_, netX, _ := net.ParseCIDR("10.0.2.3/32")
if err := CheckNameserverOverlaps(nameservers, netX); err == nil {
t.Fatalf("%s should overlap 10.0.2.3/32 but doesn't", netX)
}
_, netX, _ = net.ParseCIDR("192.168.102.2/32")
if err := CheckNameserverOverlaps(nameservers, netX); err != nil {
t.Fatalf("%s should not overlap %v but it does", netX, nameservers)
}
}
func AssertOverlap(CIDRx string, CIDRy string, t *testing.T) {
_, netX, _ := net.ParseCIDR(CIDRx)
_, netY, _ := net.ParseCIDR(CIDRy)
if !NetworkOverlaps(netX, netY) {
t.Errorf("%v and %v should overlap", netX, netY)
}
}
func AssertNoOverlap(CIDRx string, CIDRy string, t *testing.T) {
_, netX, _ := net.ParseCIDR(CIDRx)
_, netY, _ := net.ParseCIDR(CIDRy)
if NetworkOverlaps(netX, netY) {
t.Errorf("%v and %v should not overlap", netX, netY)
}
}
func TestNetworkOverlaps(t *testing.T) {
//netY starts at same IP and ends within netX
AssertOverlap("172.16.0.1/24", "172.16.0.1/25", t)
//netY starts within netX and ends at same IP
AssertOverlap("172.16.0.1/24", "172.16.0.128/25", t)
//netY starts and ends within netX
AssertOverlap("172.16.0.1/24", "172.16.0.64/25", t)
//netY starts at same IP and ends outside of netX
AssertOverlap("172.16.0.1/24", "172.16.0.1/23", t)
//netY starts before and ends at same IP of netX
AssertOverlap("172.16.1.1/24", "172.16.0.1/23", t)
//netY starts before and ends outside of netX
AssertOverlap("172.16.1.1/24", "172.16.0.1/22", t)
//netY starts and ends before netX
AssertNoOverlap("172.16.1.1/25", "172.16.0.1/24", t)
//netX starts and ends before netY
AssertNoOverlap("172.16.1.1/25", "172.16.2.1/24", t)
}
func TestNetworkRange(t *testing.T) {
// Simple class C test
_, network, _ := net.ParseCIDR("192.168.0.1/24")
first, last := NetworkRange(network)
if !first.Equal(net.ParseIP("192.168.0.0")) {
t.Error(first.String())
}
if !last.Equal(net.ParseIP("192.168.0.255")) {
t.Error(last.String())
}
// Class A test
_, network, _ = net.ParseCIDR("10.0.0.1/8")
first, last = NetworkRange(network)
if !first.Equal(net.ParseIP("10.0.0.0")) {
t.Error(first.String())
}
if !last.Equal(net.ParseIP("10.255.255.255")) {
t.Error(last.String())
}
// Class A, random IP address
_, network, _ = net.ParseCIDR("10.1.2.3/8")
first, last = NetworkRange(network)
if !first.Equal(net.ParseIP("10.0.0.0")) {
t.Error(first.String())
}
if !last.Equal(net.ParseIP("10.255.255.255")) {
t.Error(last.String())
}
// 32bit mask
_, network, _ = net.ParseCIDR("10.1.2.3/32")
first, last = NetworkRange(network)
if !first.Equal(net.ParseIP("10.1.2.3")) {
t.Error(first.String())
}
if !last.Equal(net.ParseIP("10.1.2.3")) {
t.Error(last.String())
}
// 31bit mask
_, network, _ = net.ParseCIDR("10.1.2.3/31")
first, last = NetworkRange(network)
if !first.Equal(net.ParseIP("10.1.2.2")) {
t.Error(first.String())
}
if !last.Equal(net.ParseIP("10.1.2.3")) {
t.Error(last.String())
}
// 26bit mask
_, network, _ = net.ParseCIDR("10.1.2.3/26")
first, last = NetworkRange(network)
if !first.Equal(net.ParseIP("10.1.2.0")) {
t.Error(first.String())
}
if !last.Equal(net.ParseIP("10.1.2.63")) {
t.Error(last.String())
}
}

View file

@ -1,188 +0,0 @@
package portallocator
import (
"bufio"
"errors"
"fmt"
"net"
"os"
"sync"
"github.com/Sirupsen/logrus"
)
const (
DefaultPortRangeStart = 49153
DefaultPortRangeEnd = 65535
)
type ipMapping map[string]protoMap
var (
ErrAllPortsAllocated = errors.New("all ports are allocated")
ErrUnknownProtocol = errors.New("unknown protocol")
defaultIP = net.ParseIP("0.0.0.0")
)
type ErrPortAlreadyAllocated struct {
ip string
port int
}
func NewErrPortAlreadyAllocated(ip string, port int) ErrPortAlreadyAllocated {
return ErrPortAlreadyAllocated{
ip: ip,
port: port,
}
}
func (e ErrPortAlreadyAllocated) IP() string {
return e.ip
}
func (e ErrPortAlreadyAllocated) Port() int {
return e.port
}
func (e ErrPortAlreadyAllocated) IPPort() string {
return fmt.Sprintf("%s:%d", e.ip, e.port)
}
func (e ErrPortAlreadyAllocated) Error() string {
return fmt.Sprintf("Bind for %s:%d failed: port is already allocated", e.ip, e.port)
}
type (
PortAllocator struct {
mutex sync.Mutex
ipMap ipMapping
Begin int
End int
}
portMap struct {
p map[int]struct{}
begin, end int
last int
}
protoMap map[string]*portMap
)
func New() *PortAllocator {
start, end, err := getDynamicPortRange()
if err != nil {
logrus.Warn(err)
start, end = DefaultPortRangeStart, DefaultPortRangeEnd
}
return &PortAllocator{
ipMap: ipMapping{},
Begin: start,
End: end,
}
}
func getDynamicPortRange() (start int, end int, err error) {
const portRangeKernelParam = "/proc/sys/net/ipv4/ip_local_port_range"
portRangeFallback := fmt.Sprintf("using fallback port range %d-%d", DefaultPortRangeStart, DefaultPortRangeEnd)
file, err := os.Open(portRangeKernelParam)
if err != nil {
return 0, 0, fmt.Errorf("port allocator - %s due to error: %v", portRangeFallback, err)
}
n, err := fmt.Fscanf(bufio.NewReader(file), "%d\t%d", &start, &end)
if n != 2 || err != nil {
if err == nil {
err = fmt.Errorf("unexpected count of parsed numbers (%d)", n)
}
return 0, 0, fmt.Errorf("port allocator - failed to parse system ephemeral port range from %s - %s: %v", portRangeKernelParam, portRangeFallback, err)
}
return start, end, nil
}
// RequestPort requests new port from global ports pool for specified ip and proto.
// If port is 0 it returns first free port. Otherwise it cheks port availability
// in pool and return that port or error if port is already busy.
func (p *PortAllocator) RequestPort(ip net.IP, proto string, port int) (int, error) {
p.mutex.Lock()
defer p.mutex.Unlock()
if proto != "tcp" && proto != "udp" {
return 0, ErrUnknownProtocol
}
if ip == nil {
ip = defaultIP
}
ipstr := ip.String()
protomap, ok := p.ipMap[ipstr]
if !ok {
protomap = protoMap{
"tcp": p.newPortMap(),
"udp": p.newPortMap(),
}
p.ipMap[ipstr] = protomap
}
mapping := protomap[proto]
if port > 0 {
if _, ok := mapping.p[port]; !ok {
mapping.p[port] = struct{}{}
return port, nil
}
return 0, NewErrPortAlreadyAllocated(ipstr, port)
}
port, err := mapping.findPort()
if err != nil {
return 0, err
}
return port, nil
}
// ReleasePort releases port from global ports pool for specified ip and proto.
func (p *PortAllocator) ReleasePort(ip net.IP, proto string, port int) error {
p.mutex.Lock()
defer p.mutex.Unlock()
if ip == nil {
ip = defaultIP
}
protomap, ok := p.ipMap[ip.String()]
if !ok {
return nil
}
delete(protomap[proto].p, port)
return nil
}
func (p *PortAllocator) newPortMap() *portMap {
return &portMap{
p: map[int]struct{}{},
begin: p.Begin,
end: p.End,
last: p.End,
}
}
// ReleaseAll releases all ports for all ips.
func (p *PortAllocator) ReleaseAll() error {
p.mutex.Lock()
p.ipMap = ipMapping{}
p.mutex.Unlock()
return nil
}
func (pm *portMap) findPort() (int, error) {
port := pm.last
for i := 0; i <= pm.end-pm.begin; i++ {
port++
if port > pm.end {
port = pm.begin
}
if _, ok := pm.p[port]; !ok {
pm.p[port] = struct{}{}
pm.last = port
return port, nil
}
}
return 0, ErrAllPortsAllocated
}

View file

@ -1,239 +0,0 @@
package portallocator
import (
"net"
"testing"
)
func TestRequestNewPort(t *testing.T) {
p := New()
port, err := p.RequestPort(defaultIP, "tcp", 0)
if err != nil {
t.Fatal(err)
}
if expected := p.Begin; port != expected {
t.Fatalf("Expected port %d got %d", expected, port)
}
}
func TestRequestSpecificPort(t *testing.T) {
p := New()
port, err := p.RequestPort(defaultIP, "tcp", 5000)
if err != nil {
t.Fatal(err)
}
if port != 5000 {
t.Fatalf("Expected port 5000 got %d", port)
}
}
func TestReleasePort(t *testing.T) {
p := New()
port, err := p.RequestPort(defaultIP, "tcp", 5000)
if err != nil {
t.Fatal(err)
}
if port != 5000 {
t.Fatalf("Expected port 5000 got %d", port)
}
if err := p.ReleasePort(defaultIP, "tcp", 5000); err != nil {
t.Fatal(err)
}
}
func TestReuseReleasedPort(t *testing.T) {
p := New()
port, err := p.RequestPort(defaultIP, "tcp", 5000)
if err != nil {
t.Fatal(err)
}
if port != 5000 {
t.Fatalf("Expected port 5000 got %d", port)
}
if err := p.ReleasePort(defaultIP, "tcp", 5000); err != nil {
t.Fatal(err)
}
port, err = p.RequestPort(defaultIP, "tcp", 5000)
if err != nil {
t.Fatal(err)
}
}
func TestReleaseUnreadledPort(t *testing.T) {
p := New()
port, err := p.RequestPort(defaultIP, "tcp", 5000)
if err != nil {
t.Fatal(err)
}
if port != 5000 {
t.Fatalf("Expected port 5000 got %d", port)
}
port, err = p.RequestPort(defaultIP, "tcp", 5000)
switch err.(type) {
case ErrPortAlreadyAllocated:
default:
t.Fatalf("Expected port allocation error got %s", err)
}
}
func TestUnknowProtocol(t *testing.T) {
if _, err := New().RequestPort(defaultIP, "tcpp", 0); err != ErrUnknownProtocol {
t.Fatalf("Expected error %s got %s", ErrUnknownProtocol, err)
}
}
func TestAllocateAllPorts(t *testing.T) {
p := New()
for i := 0; i <= p.End-p.Begin; i++ {
port, err := p.RequestPort(defaultIP, "tcp", 0)
if err != nil {
t.Fatal(err)
}
if expected := p.Begin + i; port != expected {
t.Fatalf("Expected port %d got %d", expected, port)
}
}
if _, err := p.RequestPort(defaultIP, "tcp", 0); err != ErrAllPortsAllocated {
t.Fatalf("Expected error %s got %s", ErrAllPortsAllocated, err)
}
_, err := p.RequestPort(defaultIP, "udp", 0)
if err != nil {
t.Fatal(err)
}
// release a port in the middle and ensure we get another tcp port
port := p.Begin + 5
if err := p.ReleasePort(defaultIP, "tcp", port); err != nil {
t.Fatal(err)
}
newPort, err := p.RequestPort(defaultIP, "tcp", 0)
if err != nil {
t.Fatal(err)
}
if newPort != port {
t.Fatalf("Expected port %d got %d", port, newPort)
}
// now pm.last == newPort, release it so that it's the only free port of
// the range, and ensure we get it back
if err := p.ReleasePort(defaultIP, "tcp", newPort); err != nil {
t.Fatal(err)
}
port, err = p.RequestPort(defaultIP, "tcp", 0)
if err != nil {
t.Fatal(err)
}
if newPort != port {
t.Fatalf("Expected port %d got %d", newPort, port)
}
}
func BenchmarkAllocatePorts(b *testing.B) {
p := New()
for i := 0; i < b.N; i++ {
for i := 0; i <= p.End-p.Begin; i++ {
port, err := p.RequestPort(defaultIP, "tcp", 0)
if err != nil {
b.Fatal(err)
}
if expected := p.Begin + i; port != expected {
b.Fatalf("Expected port %d got %d", expected, port)
}
}
p.ReleaseAll()
}
}
func TestPortAllocation(t *testing.T) {
p := New()
ip := net.ParseIP("192.168.0.1")
ip2 := net.ParseIP("192.168.0.2")
if port, err := p.RequestPort(ip, "tcp", 80); err != nil {
t.Fatal(err)
} else if port != 80 {
t.Fatalf("Acquire(80) should return 80, not %d", port)
}
port, err := p.RequestPort(ip, "tcp", 0)
if err != nil {
t.Fatal(err)
}
if port <= 0 {
t.Fatalf("Acquire(0) should return a non-zero port")
}
if _, err := p.RequestPort(ip, "tcp", port); err == nil {
t.Fatalf("Acquiring a port already in use should return an error")
}
if newPort, err := p.RequestPort(ip, "tcp", 0); err != nil {
t.Fatal(err)
} else if newPort == port {
t.Fatalf("Acquire(0) allocated the same port twice: %d", port)
}
if _, err := p.RequestPort(ip, "tcp", 80); err == nil {
t.Fatalf("Acquiring a port already in use should return an error")
}
if _, err := p.RequestPort(ip2, "tcp", 80); err != nil {
t.Fatalf("It should be possible to allocate the same port on a different interface")
}
if _, err := p.RequestPort(ip2, "tcp", 80); err == nil {
t.Fatalf("Acquiring a port already in use should return an error")
}
if err := p.ReleasePort(ip, "tcp", 80); err != nil {
t.Fatal(err)
}
if _, err := p.RequestPort(ip, "tcp", 80); err != nil {
t.Fatal(err)
}
port, err = p.RequestPort(ip, "tcp", 0)
if err != nil {
t.Fatal(err)
}
port2, err := p.RequestPort(ip, "tcp", port+1)
if err != nil {
t.Fatal(err)
}
port3, err := p.RequestPort(ip, "tcp", 0)
if err != nil {
t.Fatal(err)
}
if port3 == port2 {
t.Fatal("Requesting a dynamic port should never allocate a used port")
}
}
func TestNoDuplicateBPR(t *testing.T) {
p := New()
if port, err := p.RequestPort(defaultIP, "tcp", p.Begin); err != nil {
t.Fatal(err)
} else if port != p.Begin {
t.Fatalf("Expected port %d got %d", p.Begin, port)
}
if port, err := p.RequestPort(defaultIP, "tcp", 0); err != nil {
t.Fatal(err)
} else if port == p.Begin {
t.Fatalf("Acquire(0) allocated the same port twice: %d", port)
}
}

View file

@ -1,210 +0,0 @@
package portmapper
import (
"errors"
"fmt"
"net"
"sync"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/networkdriver/portallocator"
"github.com/docker/docker/pkg/iptables"
)
type mapping struct {
proto string
userlandProxy UserlandProxy
host net.Addr
container net.Addr
}
var NewProxy = NewProxyCommand
var (
ErrUnknownBackendAddressType = errors.New("unknown container address type not supported")
ErrPortMappedForIP = errors.New("port is already mapped to ip")
ErrPortNotMapped = errors.New("port is not mapped")
)
type PortMapper struct {
chain *iptables.Chain
// udp:ip:port
currentMappings map[string]*mapping
lock sync.Mutex
Allocator *portallocator.PortAllocator
}
func New() *PortMapper {
return NewWithPortAllocator(portallocator.New())
}
func NewWithPortAllocator(allocator *portallocator.PortAllocator) *PortMapper {
return &PortMapper{
currentMappings: make(map[string]*mapping),
Allocator: allocator,
}
}
func (pm *PortMapper) SetIptablesChain(c *iptables.Chain) {
pm.chain = c
}
func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int, useProxy bool) (host net.Addr, err error) {
pm.lock.Lock()
defer pm.lock.Unlock()
var (
m *mapping
proto string
allocatedHostPort int
)
switch container.(type) {
case *net.TCPAddr:
proto = "tcp"
if allocatedHostPort, err = pm.Allocator.RequestPort(hostIP, proto, hostPort); err != nil {
return nil, err
}
m = &mapping{
proto: proto,
host: &net.TCPAddr{IP: hostIP, Port: allocatedHostPort},
container: container,
}
if useProxy {
m.userlandProxy = NewProxy(proto, hostIP, allocatedHostPort, container.(*net.TCPAddr).IP, container.(*net.TCPAddr).Port)
}
case *net.UDPAddr:
proto = "udp"
if allocatedHostPort, err = pm.Allocator.RequestPort(hostIP, proto, hostPort); err != nil {
return nil, err
}
m = &mapping{
proto: proto,
host: &net.UDPAddr{IP: hostIP, Port: allocatedHostPort},
container: container,
}
if useProxy {
m.userlandProxy = NewProxy(proto, hostIP, allocatedHostPort, container.(*net.UDPAddr).IP, container.(*net.UDPAddr).Port)
}
default:
return nil, ErrUnknownBackendAddressType
}
// release the allocated port on any further error during return.
defer func() {
if err != nil {
pm.Allocator.ReleasePort(hostIP, proto, allocatedHostPort)
}
}()
key := getKey(m.host)
if _, exists := pm.currentMappings[key]; exists {
return nil, ErrPortMappedForIP
}
containerIP, containerPort := getIPAndPort(m.container)
if err := pm.forward(iptables.Append, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil {
return nil, err
}
cleanup := func() error {
// need to undo the iptables rules before we return
if m.userlandProxy != nil {
m.userlandProxy.Stop()
}
pm.forward(iptables.Delete, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort)
if err := pm.Allocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil {
return err
}
return nil
}
if m.userlandProxy != nil {
if err := m.userlandProxy.Start(); err != nil {
if err := cleanup(); err != nil {
return nil, fmt.Errorf("Error during port allocation cleanup: %v", err)
}
return nil, err
}
}
pm.currentMappings[key] = m
return m.host, nil
}
// re-apply all port mappings
func (pm *PortMapper) ReMapAll() {
logrus.Debugln("Re-applying all port mappings.")
for _, data := range pm.currentMappings {
containerIP, containerPort := getIPAndPort(data.container)
hostIP, hostPort := getIPAndPort(data.host)
if err := pm.forward(iptables.Append, data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil {
logrus.Errorf("Error on iptables add: %s", err)
}
}
}
func (pm *PortMapper) Unmap(host net.Addr) error {
pm.lock.Lock()
defer pm.lock.Unlock()
key := getKey(host)
data, exists := pm.currentMappings[key]
if !exists {
return ErrPortNotMapped
}
if data.userlandProxy != nil {
data.userlandProxy.Stop()
}
delete(pm.currentMappings, key)
containerIP, containerPort := getIPAndPort(data.container)
hostIP, hostPort := getIPAndPort(data.host)
if err := pm.forward(iptables.Delete, data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil {
logrus.Errorf("Error on iptables delete: %s", err)
}
switch a := host.(type) {
case *net.TCPAddr:
return pm.Allocator.ReleasePort(a.IP, "tcp", a.Port)
case *net.UDPAddr:
return pm.Allocator.ReleasePort(a.IP, "udp", a.Port)
}
return nil
}
func getKey(a net.Addr) string {
switch t := a.(type) {
case *net.TCPAddr:
return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "tcp")
case *net.UDPAddr:
return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "udp")
}
return ""
}
func getIPAndPort(a net.Addr) (net.IP, int) {
switch t := a.(type) {
case *net.TCPAddr:
return t.IP, t.Port
case *net.UDPAddr:
return t.IP, t.Port
}
return nil, 0
}
func (pm *PortMapper) forward(action iptables.Action, proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error {
if pm.chain == nil {
return nil
}
return pm.chain.Forward(action, sourceIP, sourcePort, proto, containerIP, containerPort)
}

View file

@ -1,149 +0,0 @@
package portmapper
import (
"net"
"testing"
"github.com/docker/docker/pkg/iptables"
)
func init() {
// override this func to mock out the proxy server
NewProxy = NewMockProxyCommand
}
func TestSetIptablesChain(t *testing.T) {
pm := New()
c := &iptables.Chain{
Name: "TEST",
Bridge: "192.168.1.1",
}
if pm.chain != nil {
t.Fatal("chain should be nil at init")
}
pm.SetIptablesChain(c)
if pm.chain == nil {
t.Fatal("chain should not be nil after set")
}
}
func TestMapPorts(t *testing.T) {
pm := New()
dstIp1 := net.ParseIP("192.168.0.1")
dstIp2 := net.ParseIP("192.168.0.2")
dstAddr1 := &net.TCPAddr{IP: dstIp1, Port: 80}
dstAddr2 := &net.TCPAddr{IP: dstIp2, Port: 80}
srcAddr1 := &net.TCPAddr{Port: 1080, IP: net.ParseIP("172.16.0.1")}
srcAddr2 := &net.TCPAddr{Port: 1080, IP: net.ParseIP("172.16.0.2")}
addrEqual := func(addr1, addr2 net.Addr) bool {
return (addr1.Network() == addr2.Network()) && (addr1.String() == addr2.String())
}
if host, err := pm.Map(srcAddr1, dstIp1, 80, true); err != nil {
t.Fatalf("Failed to allocate port: %s", err)
} else if !addrEqual(dstAddr1, host) {
t.Fatalf("Incorrect mapping result: expected %s:%s, got %s:%s",
dstAddr1.String(), dstAddr1.Network(), host.String(), host.Network())
}
if _, err := pm.Map(srcAddr1, dstIp1, 80, true); err == nil {
t.Fatalf("Port is in use - mapping should have failed")
}
if _, err := pm.Map(srcAddr2, dstIp1, 80, true); err == nil {
t.Fatalf("Port is in use - mapping should have failed")
}
if _, err := pm.Map(srcAddr2, dstIp2, 80, true); err != nil {
t.Fatalf("Failed to allocate port: %s", err)
}
if pm.Unmap(dstAddr1) != nil {
t.Fatalf("Failed to release port")
}
if pm.Unmap(dstAddr2) != nil {
t.Fatalf("Failed to release port")
}
if pm.Unmap(dstAddr2) == nil {
t.Fatalf("Port already released, but no error reported")
}
}
func TestGetUDPKey(t *testing.T) {
addr := &net.UDPAddr{IP: net.ParseIP("192.168.1.5"), Port: 53}
key := getKey(addr)
if expected := "192.168.1.5:53/udp"; key != expected {
t.Fatalf("expected key %s got %s", expected, key)
}
}
func TestGetTCPKey(t *testing.T) {
addr := &net.TCPAddr{IP: net.ParseIP("192.168.1.5"), Port: 80}
key := getKey(addr)
if expected := "192.168.1.5:80/tcp"; key != expected {
t.Fatalf("expected key %s got %s", expected, key)
}
}
func TestGetUDPIPAndPort(t *testing.T) {
addr := &net.UDPAddr{IP: net.ParseIP("192.168.1.5"), Port: 53}
ip, port := getIPAndPort(addr)
if expected := "192.168.1.5"; ip.String() != expected {
t.Fatalf("expected ip %s got %s", expected, ip)
}
if ep := 53; port != ep {
t.Fatalf("expected port %d got %d", ep, port)
}
}
func TestMapAllPortsSingleInterface(t *testing.T) {
pm := New()
dstIp1 := net.ParseIP("0.0.0.0")
srcAddr1 := &net.TCPAddr{Port: 1080, IP: net.ParseIP("172.16.0.1")}
hosts := []net.Addr{}
var host net.Addr
var err error
defer func() {
for _, val := range hosts {
pm.Unmap(val)
}
}()
for i := 0; i < 10; i++ {
start, end := pm.Allocator.Begin, pm.Allocator.End
for i := start; i < end; i++ {
if host, err = pm.Map(srcAddr1, dstIp1, 0, true); err != nil {
t.Fatal(err)
}
hosts = append(hosts, host)
}
if _, err := pm.Map(srcAddr1, dstIp1, start, true); err == nil {
t.Fatalf("Port %d should be bound but is not", start)
}
for _, val := range hosts {
if err := pm.Unmap(val); err != nil {
t.Fatal(err)
}
}
hosts = []net.Addr{}
}
}

View file

@ -1,18 +0,0 @@
package portmapper
import "net"
func NewMockProxyCommand(proto string, hostIP net.IP, hostPort int, containerIP net.IP, containerPort int) UserlandProxy {
return &mockProxyCommand{}
}
type mockProxyCommand struct {
}
func (p *mockProxyCommand) Start() error {
return nil
}
func (p *mockProxyCommand) Stop() error {
return nil
}

View file

@ -1,161 +0,0 @@
package portmapper
import (
"flag"
"fmt"
"io/ioutil"
"log"
"net"
"os"
"os/exec"
"os/signal"
"strconv"
"syscall"
"time"
"github.com/docker/docker/pkg/proxy"
"github.com/docker/docker/pkg/reexec"
)
const userlandProxyCommandName = "docker-proxy-deprecated"
func init() {
reexec.Register(userlandProxyCommandName, execProxy)
}
type UserlandProxy interface {
Start() error
Stop() error
}
// proxyCommand wraps an exec.Cmd to run the userland TCP and UDP
// proxies as separate processes.
type proxyCommand struct {
cmd *exec.Cmd
}
// execProxy is the reexec function that is registered to start the userland proxies
func execProxy() {
f := os.NewFile(3, "signal-parent")
host, container := parseHostContainerAddrs()
p, err := proxy.NewProxy(host, container)
if err != nil {
fmt.Fprintf(f, "1\n%s", err)
f.Close()
os.Exit(1)
}
go handleStopSignals(p)
fmt.Fprint(f, "0\n")
f.Close()
// Run will block until the proxy stops
p.Run()
}
// parseHostContainerAddrs parses the flags passed on reexec to create the TCP or UDP
// net.Addrs to map the host and container ports
func parseHostContainerAddrs() (host net.Addr, container net.Addr) {
var (
proto = flag.String("proto", "tcp", "proxy protocol")
hostIP = flag.String("host-ip", "", "host ip")
hostPort = flag.Int("host-port", -1, "host port")
containerIP = flag.String("container-ip", "", "container ip")
containerPort = flag.Int("container-port", -1, "container port")
)
flag.Parse()
switch *proto {
case "tcp":
host = &net.TCPAddr{IP: net.ParseIP(*hostIP), Port: *hostPort}
container = &net.TCPAddr{IP: net.ParseIP(*containerIP), Port: *containerPort}
case "udp":
host = &net.UDPAddr{IP: net.ParseIP(*hostIP), Port: *hostPort}
container = &net.UDPAddr{IP: net.ParseIP(*containerIP), Port: *containerPort}
default:
log.Fatalf("unsupported protocol %s", *proto)
}
return host, container
}
func handleStopSignals(p proxy.Proxy) {
s := make(chan os.Signal, 10)
signal.Notify(s, os.Interrupt, syscall.SIGTERM, syscall.SIGSTOP)
for range s {
p.Close()
os.Exit(0)
}
}
func NewProxyCommand(proto string, hostIP net.IP, hostPort int, containerIP net.IP, containerPort int) UserlandProxy {
args := []string{
userlandProxyCommandName,
"-proto", proto,
"-host-ip", hostIP.String(),
"-host-port", strconv.Itoa(hostPort),
"-container-ip", containerIP.String(),
"-container-port", strconv.Itoa(containerPort),
}
return &proxyCommand{
cmd: &exec.Cmd{
Path: reexec.Self(),
Args: args,
SysProcAttr: &syscall.SysProcAttr{
Pdeathsig: syscall.SIGTERM, // send a sigterm to the proxy if the daemon process dies
},
},
}
}
func (p *proxyCommand) Start() error {
r, w, err := os.Pipe()
if err != nil {
return fmt.Errorf("proxy unable to open os.Pipe %s", err)
}
defer r.Close()
p.cmd.ExtraFiles = []*os.File{w}
if err := p.cmd.Start(); err != nil {
return err
}
w.Close()
errchan := make(chan error, 1)
go func() {
buf := make([]byte, 2)
r.Read(buf)
if string(buf) != "0\n" {
errStr, err := ioutil.ReadAll(r)
if err != nil {
errchan <- fmt.Errorf("Error reading exit status from userland proxy: %v", err)
return
}
errchan <- fmt.Errorf("Error starting userland proxy: %s", errStr)
return
}
errchan <- nil
}()
select {
case err := <-errchan:
return err
case <-time.After(16 * time.Second):
return fmt.Errorf("Timed out proxy starting the userland proxy")
}
}
func (p *proxyCommand) Stop() error {
if p.cmd.Process != nil {
if err := p.cmd.Process.Signal(os.Interrupt); err != nil {
return err
}
return p.cmd.Wait()
}
return nil
}

View file

@ -1,118 +0,0 @@
package networkdriver
import (
"errors"
"fmt"
"net"
"github.com/docker/libcontainer/netlink"
)
var (
networkGetRoutesFct = netlink.NetworkGetRoutes
ErrNoDefaultRoute = errors.New("no default route")
)
func CheckNameserverOverlaps(nameservers []string, toCheck *net.IPNet) error {
if len(nameservers) > 0 {
for _, ns := range nameservers {
_, nsNetwork, err := net.ParseCIDR(ns)
if err != nil {
return err
}
if NetworkOverlaps(toCheck, nsNetwork) {
return ErrNetworkOverlapsWithNameservers
}
}
}
return nil
}
func CheckRouteOverlaps(toCheck *net.IPNet) error {
networks, err := networkGetRoutesFct()
if err != nil {
return err
}
for _, network := range networks {
if network.IPNet != nil && NetworkOverlaps(toCheck, network.IPNet) {
return ErrNetworkOverlaps
}
}
return nil
}
// Detects overlap between one IPNet and another
func NetworkOverlaps(netX *net.IPNet, netY *net.IPNet) bool {
if len(netX.IP) == len(netY.IP) {
if firstIP, _ := NetworkRange(netX); netY.Contains(firstIP) {
return true
}
if firstIP, _ := NetworkRange(netY); netX.Contains(firstIP) {
return true
}
}
return false
}
// Calculates the first and last IP addresses in an IPNet
func NetworkRange(network *net.IPNet) (net.IP, net.IP) {
var netIP net.IP
if network.IP.To4() != nil {
netIP = network.IP.To4()
} else if network.IP.To16() != nil {
netIP = network.IP.To16()
} else {
return nil, nil
}
lastIP := make([]byte, len(netIP), len(netIP))
for i := 0; i < len(netIP); i++ {
lastIP[i] = netIP[i] | ^network.Mask[i]
}
return netIP.Mask(network.Mask), net.IP(lastIP)
}
// Return the first IPv4 address and slice of IPv6 addresses for the specified network interface
func GetIfaceAddr(name string) (net.Addr, []net.Addr, error) {
iface, err := net.InterfaceByName(name)
if err != nil {
return nil, nil, err
}
addrs, err := iface.Addrs()
if err != nil {
return nil, nil, err
}
var addrs4 []net.Addr
var addrs6 []net.Addr
for _, addr := range addrs {
ip := (addr.(*net.IPNet)).IP
if ip4 := ip.To4(); ip4 != nil {
addrs4 = append(addrs4, addr)
} else if ip6 := ip.To16(); len(ip6) == net.IPv6len {
addrs6 = append(addrs6, addr)
}
}
switch {
case len(addrs4) == 0:
return nil, nil, fmt.Errorf("Interface %v has no IPv4 addresses", name)
case len(addrs4) > 1:
fmt.Printf("Interface %v has more than 1 IPv4 address. Defaulting to using %v\n",
name, (addrs4[0].(*net.IPNet)).IP)
}
return addrs4[0], addrs6, nil
}
func GetDefaultRouteIface() (*net.Interface, error) {
rs, err := networkGetRoutesFct()
if err != nil {
return nil, fmt.Errorf("unable to get routes: %v", err)
}
for _, r := range rs {
if r.Default {
return r.Iface, nil
}
}
return nil, ErrNoDefaultRoute
}

View file

@ -1,79 +0,0 @@
package etchosts
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"regexp"
)
// Structure for a single host record
type Record struct {
Hosts string
IP string
}
// Writes record to file and returns bytes written or error
func (r Record) WriteTo(w io.Writer) (int64, error) {
n, err := fmt.Fprintf(w, "%s\t%s\n", r.IP, r.Hosts)
return int64(n), err
}
// Default hosts config records slice
var defaultContent = []Record{
{Hosts: "localhost", IP: "127.0.0.1"},
{Hosts: "localhost ip6-localhost ip6-loopback", IP: "::1"},
{Hosts: "ip6-localnet", IP: "fe00::0"},
{Hosts: "ip6-mcastprefix", IP: "ff00::0"},
{Hosts: "ip6-allnodes", IP: "ff02::1"},
{Hosts: "ip6-allrouters", IP: "ff02::2"},
}
// Build function
// path is path to host file string required
// IP, hostname, and domainname set main record leave empty for no master record
// extraContent is an array of extra host records.
func Build(path, IP, hostname, domainname string, extraContent []Record) error {
content := bytes.NewBuffer(nil)
if IP != "" {
//set main record
var mainRec Record
mainRec.IP = IP
if domainname != "" {
mainRec.Hosts = fmt.Sprintf("%s.%s %s", hostname, domainname, hostname)
} else {
mainRec.Hosts = hostname
}
if _, err := mainRec.WriteTo(content); err != nil {
return err
}
}
// Write defaultContent slice to buffer
for _, r := range defaultContent {
if _, err := r.WriteTo(content); err != nil {
return err
}
}
// Write extra content from function arguments
for _, r := range extraContent {
if _, err := r.WriteTo(content); err != nil {
return err
}
}
return ioutil.WriteFile(path, content.Bytes(), 0644)
}
// Update all IP addresses where hostname matches.
// path is path to host file
// IP is new IP address
// hostname is hostname to search for to replace IP
func Update(path, IP, hostname string) error {
old, err := ioutil.ReadFile(path)
if err != nil {
return err
}
var re = regexp.MustCompile(fmt.Sprintf("(\\S*)(\\t%s)", regexp.QuoteMeta(hostname)))
return ioutil.WriteFile(path, re.ReplaceAll(old, []byte(IP+"$2")), 0644)
}

View file

@ -1,134 +0,0 @@
package etchosts
import (
"bytes"
"io/ioutil"
"os"
"testing"
)
func TestBuildDefault(t *testing.T) {
file, err := ioutil.TempFile("", "")
if err != nil {
t.Fatal(err)
}
defer os.Remove(file.Name())
// check that /etc/hosts has consistent ordering
for i := 0; i <= 5; i++ {
err = Build(file.Name(), "", "", "", nil)
if err != nil {
t.Fatal(err)
}
content, err := ioutil.ReadFile(file.Name())
if err != nil {
t.Fatal(err)
}
expected := "127.0.0.1\tlocalhost\n::1\tlocalhost ip6-localhost ip6-loopback\nfe00::0\tip6-localnet\nff00::0\tip6-mcastprefix\nff02::1\tip6-allnodes\nff02::2\tip6-allrouters\n"
if expected != string(content) {
t.Fatalf("Expected to find '%s' got '%s'", expected, content)
}
}
}
func TestBuildHostnameDomainname(t *testing.T) {
file, err := ioutil.TempFile("", "")
if err != nil {
t.Fatal(err)
}
defer os.Remove(file.Name())
err = Build(file.Name(), "10.11.12.13", "testhostname", "testdomainname", nil)
if err != nil {
t.Fatal(err)
}
content, err := ioutil.ReadFile(file.Name())
if err != nil {
t.Fatal(err)
}
if expected := "10.11.12.13\ttesthostname.testdomainname testhostname\n"; !bytes.Contains(content, []byte(expected)) {
t.Fatalf("Expected to find '%s' got '%s'", expected, content)
}
}
func TestBuildHostname(t *testing.T) {
file, err := ioutil.TempFile("", "")
if err != nil {
t.Fatal(err)
}
defer os.Remove(file.Name())
err = Build(file.Name(), "10.11.12.13", "testhostname", "", nil)
if err != nil {
t.Fatal(err)
}
content, err := ioutil.ReadFile(file.Name())
if err != nil {
t.Fatal(err)
}
if expected := "10.11.12.13\ttesthostname\n"; !bytes.Contains(content, []byte(expected)) {
t.Fatalf("Expected to find '%s' got '%s'", expected, content)
}
}
func TestBuildNoIP(t *testing.T) {
file, err := ioutil.TempFile("", "")
if err != nil {
t.Fatal(err)
}
defer os.Remove(file.Name())
err = Build(file.Name(), "", "testhostname", "", nil)
if err != nil {
t.Fatal(err)
}
content, err := ioutil.ReadFile(file.Name())
if err != nil {
t.Fatal(err)
}
if expected := ""; !bytes.Contains(content, []byte(expected)) {
t.Fatalf("Expected to find '%s' got '%s'", expected, content)
}
}
func TestUpdate(t *testing.T) {
file, err := ioutil.TempFile("", "")
if err != nil {
t.Fatal(err)
}
defer os.Remove(file.Name())
if err := Build(file.Name(), "10.11.12.13", "testhostname", "testdomainname", nil); err != nil {
t.Fatal(err)
}
content, err := ioutil.ReadFile(file.Name())
if err != nil {
t.Fatal(err)
}
if expected := "10.11.12.13\ttesthostname.testdomainname testhostname\n"; !bytes.Contains(content, []byte(expected)) {
t.Fatalf("Expected to find '%s' got '%s'", expected, content)
}
if err := Update(file.Name(), "1.1.1.1", "testhostname"); err != nil {
t.Fatal(err)
}
content, err = ioutil.ReadFile(file.Name())
if err != nil {
t.Fatal(err)
}
if expected := "1.1.1.1\ttesthostname.testdomainname testhostname\n"; !bytes.Contains(content, []byte(expected)) {
t.Fatalf("Expected to find '%s' got '%s'", expected, content)
}
}

View file

@ -1,159 +0,0 @@
package iptables
import (
"fmt"
"strings"
"github.com/Sirupsen/logrus"
"github.com/godbus/dbus"
)
type IPV string
const (
Iptables IPV = "ipv4"
Ip6tables IPV = "ipv6"
Ebtables IPV = "eb"
)
const (
dbusInterface = "org.fedoraproject.FirewallD1"
dbusPath = "/org/fedoraproject/FirewallD1"
)
// Conn is a connection to firewalld dbus endpoint.
type Conn struct {
sysconn *dbus.Conn
sysobj *dbus.Object
signal chan *dbus.Signal
}
var (
connection *Conn
firewalldRunning bool // is Firewalld service running
onReloaded []*func() // callbacks when Firewalld has been reloaded
)
func FirewalldInit() error {
var err error
if connection, err = newConnection(); err != nil {
return fmt.Errorf("Failed to connect to D-Bus system bus: %v", err)
}
if connection != nil {
go signalHandler()
}
firewalldRunning = checkRunning()
return nil
}
// New() establishes a connection to the system bus.
func newConnection() (*Conn, error) {
c := new(Conn)
if err := c.initConnection(); err != nil {
return nil, err
}
return c, nil
}
// Innitialize D-Bus connection.
func (c *Conn) initConnection() error {
var err error
c.sysconn, err = dbus.SystemBus()
if err != nil {
return err
}
// This never fails, even if the service is not running atm.
c.sysobj = c.sysconn.Object(dbusInterface, dbus.ObjectPath(dbusPath))
rule := fmt.Sprintf("type='signal',path='%s',interface='%s',sender='%s',member='Reloaded'",
dbusPath, dbusInterface, dbusInterface)
c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule)
rule = fmt.Sprintf("type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',path='/org/freedesktop/DBus',sender='org.freedesktop.DBus',arg0='%s'",
dbusInterface)
c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule)
c.signal = make(chan *dbus.Signal, 10)
c.sysconn.Signal(c.signal)
return nil
}
func signalHandler() {
for signal := range connection.signal {
if strings.Contains(signal.Name, "NameOwnerChanged") {
firewalldRunning = checkRunning()
dbusConnectionChanged(signal.Body)
} else if strings.Contains(signal.Name, "Reloaded") {
reloaded()
}
}
}
func dbusConnectionChanged(args []interface{}) {
name := args[0].(string)
old_owner := args[1].(string)
new_owner := args[2].(string)
if name != dbusInterface {
return
}
if len(new_owner) > 0 {
connectionEstablished()
} else if len(old_owner) > 0 {
connectionLost()
}
}
func connectionEstablished() {
reloaded()
}
func connectionLost() {
// Doesn't do anything for now. Libvirt also doesn't react to this.
}
// call all callbacks
func reloaded() {
for _, pf := range onReloaded {
(*pf)()
}
}
// add callback
func OnReloaded(callback func()) {
for _, pf := range onReloaded {
if pf == &callback {
return
}
}
onReloaded = append(onReloaded, &callback)
}
// Call some remote method to see whether the service is actually running.
func checkRunning() bool {
var zone string
var err error
if connection != nil {
err = connection.sysobj.Call(dbusInterface+".getDefaultZone", 0).Store(&zone)
logrus.Infof("Firewalld running: %t", err == nil)
return err == nil
}
return false
}
// Firewalld's passthrough method simply passes args through to iptables/ip6tables
func Passthrough(ipv IPV, args ...string) ([]byte, error) {
var output string
logrus.Debugf("Firewalld passthrough: %s, %s", ipv, args)
if err := connection.sysobj.Call(dbusInterface+".direct.passthrough", 0, ipv, args).Store(&output); err != nil {
return nil, err
}
return []byte(output), nil
}

View file

@ -1,83 +0,0 @@
package iptables
import (
"net"
"strconv"
"testing"
)
func TestFirewalldInit(t *testing.T) {
if !checkRunning() {
t.Skip("firewalld is not running")
}
if err := FirewalldInit(); err != nil {
t.Fatal(err)
}
}
func TestReloaded(t *testing.T) {
var err error
var fwdChain *Chain
fwdChain, err = NewChain("FWD", "lo", Filter, false)
if err != nil {
t.Fatal(err)
}
defer fwdChain.Remove()
// copy-pasted from iptables_test:TestLink
ip1 := net.ParseIP("192.168.1.1")
ip2 := net.ParseIP("192.168.1.2")
port := 1234
proto := "tcp"
err = fwdChain.Link(Append, ip1, ip2, port, proto)
if err != nil {
t.Fatal(err)
} else {
// to be re-called again later
OnReloaded(func() { fwdChain.Link(Append, ip1, ip2, port, proto) })
}
rule1 := []string{
"-i", fwdChain.Bridge,
"-o", fwdChain.Bridge,
"-p", proto,
"-s", ip1.String(),
"-d", ip2.String(),
"--dport", strconv.Itoa(port),
"-j", "ACCEPT"}
if !Exists(fwdChain.Table, fwdChain.Name, rule1...) {
t.Fatalf("rule1 does not exist")
}
// flush all rules
fwdChain.Remove()
reloaded()
// make sure the rules have been recreated
if !Exists(fwdChain.Table, fwdChain.Name, rule1...) {
t.Fatalf("rule1 hasn't been recreated")
}
}
func TestPassthrough(t *testing.T) {
rule1 := []string{
"-i", "lo",
"-p", "udp",
"--dport", "123",
"-j", "ACCEPT"}
if firewalldRunning {
_, err := Passthrough(Iptables, append([]string{"-A"}, rule1...)...)
if err != nil {
t.Fatal(err)
}
if !Exists(Filter, "INPUT", rule1...) {
t.Fatalf("rule1 does not exist")
}
}
}

View file

@ -1,305 +0,0 @@
package iptables
import (
"errors"
"fmt"
"net"
"os/exec"
"strconv"
"strings"
"sync"
"github.com/Sirupsen/logrus"
)
type Action string
type Table string
const (
Append Action = "-A"
Delete Action = "-D"
Insert Action = "-I"
Nat Table = "nat"
Filter Table = "filter"
Mangle Table = "mangle"
)
var (
iptablesPath string
supportsXlock = false
// used to lock iptables commands if xtables lock is not supported
bestEffortLock sync.Mutex
ErrIptablesNotFound = errors.New("Iptables not found")
)
type Chain struct {
Name string
Bridge string
Table Table
}
type ChainError struct {
Chain string
Output []byte
}
func (e ChainError) Error() string {
return fmt.Sprintf("Error iptables %s: %s", e.Chain, string(e.Output))
}
func initCheck() error {
if iptablesPath == "" {
path, err := exec.LookPath("iptables")
if err != nil {
return ErrIptablesNotFound
}
iptablesPath = path
supportsXlock = exec.Command(iptablesPath, "--wait", "-L", "-n").Run() == nil
}
return nil
}
func NewChain(name, bridge string, table Table, hairpinMode bool) (*Chain, error) {
c := &Chain{
Name: name,
Bridge: bridge,
Table: table,
}
if string(c.Table) == "" {
c.Table = Filter
}
// Add chain if it doesn't exist
if _, err := Raw("-t", string(c.Table), "-n", "-L", c.Name); err != nil {
if output, err := Raw("-t", string(c.Table), "-N", c.Name); err != nil {
return nil, err
} else if len(output) != 0 {
return nil, fmt.Errorf("Could not create %s/%s chain: %s", c.Table, c.Name, output)
}
}
switch table {
case Nat:
preroute := []string{
"-m", "addrtype",
"--dst-type", "LOCAL"}
if !Exists(Nat, "PREROUTING", preroute...) {
if err := c.Prerouting(Append, preroute...); err != nil {
return nil, fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err)
}
}
output := []string{
"-m", "addrtype",
"--dst-type", "LOCAL"}
if !hairpinMode {
output = append(output, "!", "--dst", "127.0.0.0/8")
}
if !Exists(Nat, "OUTPUT", output...) {
if err := c.Output(Append, output...); err != nil {
return nil, fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err)
}
}
case Filter:
link := []string{
"-o", c.Bridge,
"-j", c.Name}
if !Exists(Filter, "FORWARD", link...) {
insert := append([]string{string(Insert), "FORWARD"}, link...)
if output, err := Raw(insert...); err != nil {
return nil, err
} else if len(output) != 0 {
return nil, fmt.Errorf("Could not create linking rule to %s/%s: %s", c.Table, c.Name, output)
}
}
}
return c, nil
}
func RemoveExistingChain(name string, table Table) error {
c := &Chain{
Name: name,
Table: table,
}
if string(c.Table) == "" {
c.Table = Filter
}
return c.Remove()
}
// Add forwarding rule to 'filter' table and corresponding nat rule to 'nat' table
func (c *Chain) Forward(action Action, ip net.IP, port int, proto, destAddr string, destPort int) error {
daddr := ip.String()
if ip.IsUnspecified() {
// iptables interprets "0.0.0.0" as "0.0.0.0/32", whereas we
// want "0.0.0.0/0". "0/0" is correctly interpreted as "any
// value" by both iptables and ip6tables.
daddr = "0/0"
}
if output, err := Raw("-t", string(Nat), string(action), c.Name,
"-p", proto,
"-d", daddr,
"--dport", strconv.Itoa(port),
"-j", "DNAT",
"--to-destination", net.JoinHostPort(destAddr, strconv.Itoa(destPort))); err != nil {
return err
} else if len(output) != 0 {
return ChainError{Chain: "FORWARD", Output: output}
}
if output, err := Raw("-t", string(Filter), string(action), c.Name,
"!", "-i", c.Bridge,
"-o", c.Bridge,
"-p", proto,
"-d", destAddr,
"--dport", strconv.Itoa(destPort),
"-j", "ACCEPT"); err != nil {
return err
} else if len(output) != 0 {
return ChainError{Chain: "FORWARD", Output: output}
}
if output, err := Raw("-t", string(Nat), string(action), "POSTROUTING",
"-p", proto,
"-s", destAddr,
"-d", destAddr,
"--dport", strconv.Itoa(destPort),
"-j", "MASQUERADE"); err != nil {
return err
} else if len(output) != 0 {
return ChainError{Chain: "FORWARD", Output: output}
}
return nil
}
// Add reciprocal ACCEPT rule for two supplied IP addresses.
// Traffic is allowed from ip1 to ip2 and vice-versa
func (c *Chain) Link(action Action, ip1, ip2 net.IP, port int, proto string) error {
if output, err := Raw("-t", string(Filter), string(action), c.Name,
"-i", c.Bridge, "-o", c.Bridge,
"-p", proto,
"-s", ip1.String(),
"-d", ip2.String(),
"--dport", strconv.Itoa(port),
"-j", "ACCEPT"); err != nil {
return err
} else if len(output) != 0 {
return fmt.Errorf("Error iptables forward: %s", output)
}
if output, err := Raw("-t", string(Filter), string(action), c.Name,
"-i", c.Bridge, "-o", c.Bridge,
"-p", proto,
"-s", ip2.String(),
"-d", ip1.String(),
"--sport", strconv.Itoa(port),
"-j", "ACCEPT"); err != nil {
return err
} else if len(output) != 0 {
return fmt.Errorf("Error iptables forward: %s", output)
}
return nil
}
// Add linking rule to nat/PREROUTING chain.
func (c *Chain) Prerouting(action Action, args ...string) error {
a := []string{"-t", string(Nat), string(action), "PREROUTING"}
if len(args) > 0 {
a = append(a, args...)
}
if output, err := Raw(append(a, "-j", c.Name)...); err != nil {
return err
} else if len(output) != 0 {
return ChainError{Chain: "PREROUTING", Output: output}
}
return nil
}
// Add linking rule to an OUTPUT chain
func (c *Chain) Output(action Action, args ...string) error {
a := []string{"-t", string(c.Table), string(action), "OUTPUT"}
if len(args) > 0 {
a = append(a, args...)
}
if output, err := Raw(append(a, "-j", c.Name)...); err != nil {
return err
} else if len(output) != 0 {
return ChainError{Chain: "OUTPUT", Output: output}
}
return nil
}
func (c *Chain) Remove() error {
// Ignore errors - This could mean the chains were never set up
if c.Table == Nat {
c.Prerouting(Delete, "-m", "addrtype", "--dst-type", "LOCAL")
c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8")
c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL") // Created in versions <= 0.1.6
c.Prerouting(Delete)
c.Output(Delete)
}
Raw("-t", string(c.Table), "-F", c.Name)
Raw("-t", string(c.Table), "-X", c.Name)
return nil
}
// Check if a rule exists
func Exists(table Table, chain string, rule ...string) bool {
if string(table) == "" {
table = Filter
}
// iptables -C, --check option was added in v.1.4.11
// http://ftp.netfilter.org/pub/iptables/changes-iptables-1.4.11.txt
// try -C
// if exit status is 0 then return true, the rule exists
if _, err := Raw(append([]string{
"-t", string(table), "-C", chain}, rule...)...); err == nil {
return true
}
// parse "iptables -S" for the rule (this checks rules in a specific chain
// in a specific table)
ruleString := strings.Join(rule, " ")
existingRules, _ := exec.Command(iptablesPath, "-t", string(table), "-S", chain).Output()
return strings.Contains(string(existingRules), ruleString)
}
// Call 'iptables' system command, passing supplied arguments
func Raw(args ...string) ([]byte, error) {
if firewalldRunning {
output, err := Passthrough(Iptables, args...)
if err == nil || !strings.Contains(err.Error(), "was not provided by any .service files") {
return output, err
}
}
if err := initCheck(); err != nil {
return nil, err
}
if supportsXlock {
args = append([]string{"--wait"}, args...)
} else {
bestEffortLock.Lock()
defer bestEffortLock.Unlock()
}
logrus.Debugf("%s, %v", iptablesPath, args)
output, err := exec.Command(iptablesPath, args...).CombinedOutput()
if err != nil {
return nil, fmt.Errorf("iptables failed: iptables %v: %s (%s)", strings.Join(args, " "), output, err)
}
// ignore iptables' message about xtables lock
if strings.Contains(string(output), "waiting for it to exit") {
output = []byte("")
}
return output, err
}

View file

@ -1,237 +0,0 @@
package iptables
import (
"net"
"os/exec"
"strconv"
"strings"
"sync"
"testing"
)
const chainName = "DOCKERTEST"
var natChain *Chain
var filterChain *Chain
func TestNewChain(t *testing.T) {
var err error
natChain, err = NewChain(chainName, "lo", Nat, false)
if err != nil {
t.Fatal(err)
}
filterChain, err = NewChain(chainName, "lo", Filter, false)
if err != nil {
t.Fatal(err)
}
}
func TestForward(t *testing.T) {
ip := net.ParseIP("192.168.1.1")
port := 1234
dstAddr := "172.17.0.1"
dstPort := 4321
proto := "tcp"
err := natChain.Forward(Insert, ip, port, proto, dstAddr, dstPort)
if err != nil {
t.Fatal(err)
}
dnatRule := []string{
"-d", ip.String(),
"-p", proto,
"--dport", strconv.Itoa(port),
"-j", "DNAT",
"--to-destination", dstAddr + ":" + strconv.Itoa(dstPort),
}
if !Exists(natChain.Table, natChain.Name, dnatRule...) {
t.Fatalf("DNAT rule does not exist")
}
filterRule := []string{
"!", "-i", filterChain.Bridge,
"-o", filterChain.Bridge,
"-d", dstAddr,
"-p", proto,
"--dport", strconv.Itoa(dstPort),
"-j", "ACCEPT",
}
if !Exists(filterChain.Table, filterChain.Name, filterRule...) {
t.Fatalf("filter rule does not exist")
}
masqRule := []string{
"-d", dstAddr,
"-s", dstAddr,
"-p", proto,
"--dport", strconv.Itoa(dstPort),
"-j", "MASQUERADE",
}
if !Exists(natChain.Table, "POSTROUTING", masqRule...) {
t.Fatalf("MASQUERADE rule does not exist")
}
}
func TestLink(t *testing.T) {
var err error
ip1 := net.ParseIP("192.168.1.1")
ip2 := net.ParseIP("192.168.1.2")
port := 1234
proto := "tcp"
err = filterChain.Link(Append, ip1, ip2, port, proto)
if err != nil {
t.Fatal(err)
}
rule1 := []string{
"-i", filterChain.Bridge,
"-o", filterChain.Bridge,
"-p", proto,
"-s", ip1.String(),
"-d", ip2.String(),
"--dport", strconv.Itoa(port),
"-j", "ACCEPT"}
if !Exists(filterChain.Table, filterChain.Name, rule1...) {
t.Fatalf("rule1 does not exist")
}
rule2 := []string{
"-i", filterChain.Bridge,
"-o", filterChain.Bridge,
"-p", proto,
"-s", ip2.String(),
"-d", ip1.String(),
"--sport", strconv.Itoa(port),
"-j", "ACCEPT"}
if !Exists(filterChain.Table, filterChain.Name, rule2...) {
t.Fatalf("rule2 does not exist")
}
}
func TestPrerouting(t *testing.T) {
args := []string{
"-i", "lo",
"-d", "192.168.1.1"}
err := natChain.Prerouting(Insert, args...)
if err != nil {
t.Fatal(err)
}
rule := []string{
"-j", natChain.Name}
rule = append(rule, args...)
if !Exists(natChain.Table, "PREROUTING", rule...) {
t.Fatalf("rule does not exist")
}
delRule := append([]string{"-D", "PREROUTING", "-t", string(Nat)}, rule...)
if _, err = Raw(delRule...); err != nil {
t.Fatal(err)
}
}
func TestOutput(t *testing.T) {
args := []string{
"-o", "lo",
"-d", "192.168.1.1"}
err := natChain.Output(Insert, args...)
if err != nil {
t.Fatal(err)
}
rule := []string{
"-j", natChain.Name}
rule = append(rule, args...)
if !Exists(natChain.Table, "OUTPUT", rule...) {
t.Fatalf("rule does not exist")
}
delRule := append([]string{"-D", "OUTPUT", "-t",
string(natChain.Table)}, rule...)
if _, err = Raw(delRule...); err != nil {
t.Fatal(err)
}
}
func TestConcurrencyWithWait(t *testing.T) {
RunConcurrencyTest(t, true)
}
func TestConcurrencyNoWait(t *testing.T) {
RunConcurrencyTest(t, false)
}
// Runs 10 concurrent rule additions. This will fail if iptables
// is actually invoked simultaneously without --wait.
// Note that if iptables does not support the xtable lock on this
// system, then allowXlock has no effect -- it will always be off.
func RunConcurrencyTest(t *testing.T, allowXlock bool) {
var wg sync.WaitGroup
if !allowXlock && supportsXlock {
supportsXlock = false
defer func() { supportsXlock = true }()
}
ip := net.ParseIP("192.168.1.1")
port := 1234
dstAddr := "172.17.0.1"
dstPort := 4321
proto := "tcp"
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
err := natChain.Forward(Append, ip, port, proto, dstAddr, dstPort)
if err != nil {
t.Fatal(err)
}
}()
}
wg.Wait()
}
func TestCleanup(t *testing.T) {
var err error
var rules []byte
// Cleanup filter/FORWARD first otherwise output of iptables-save is dirty
link := []string{"-t", string(filterChain.Table),
string(Delete), "FORWARD",
"-o", filterChain.Bridge,
"-j", filterChain.Name}
if _, err = Raw(link...); err != nil {
t.Fatal(err)
}
filterChain.Remove()
err = RemoveExistingChain(chainName, Nat)
if err != nil {
t.Fatal(err)
}
rules, err = exec.Command("iptables-save").Output()
if err != nil {
t.Fatal(err)
}
if strings.Contains(string(rules), chainName) {
t.Fatalf("Removing chain failed. %s found in iptables-save", chainName)
}
}

View file

@ -1 +0,0 @@
Package resolvconf provides utility code to query and update DNS configuration in /etc/resolv.conf

View file

@ -1,16 +0,0 @@
package dns
import (
"regexp"
)
const IpLocalhost = `((127\.([0-9]{1,3}.){2}[0-9]{1,3})|(::1))`
var localhostIPRegexp = regexp.MustCompile(IpLocalhost)
// IsLocalhost returns true if ip matches the localhost IP regular expression.
// Used for determining if nameserver settings are being passed which are
// localhost addresses
func IsLocalhost(ip string) bool {
return localhostIPRegexp.MatchString(ip)
}

View file

@ -1,187 +0,0 @@
// Package resolvconf provides utility code to query and update DNS configuration in /etc/resolv.conf
package resolvconf
import (
"bytes"
"io/ioutil"
"regexp"
"strings"
"sync"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/resolvconf/dns"
)
var (
// Note: the default IPv4 & IPv6 resolvers are set to Google's Public DNS
defaultIPv4Dns = []string{"nameserver 8.8.8.8", "nameserver 8.8.4.4"}
defaultIPv6Dns = []string{"nameserver 2001:4860:4860::8888", "nameserver 2001:4860:4860::8844"}
ipv4NumBlock = `(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)`
ipv4Address = `(` + ipv4NumBlock + `\.){3}` + ipv4NumBlock
// This is not an IPv6 address verifier as it will accept a super-set of IPv6, and also
// will *not match* IPv4-Embedded IPv6 Addresses (RFC6052), but that and other variants
// -- e.g. other link-local types -- either won't work in containers or are unnecessary.
// For readability and sufficiency for Docker purposes this seemed more reasonable than a
// 1000+ character regexp with exact and complete IPv6 validation
ipv6Address = `([0-9A-Fa-f]{0,4}:){2,7}([0-9A-Fa-f]{0,4})`
localhostNSRegexp = regexp.MustCompile(`(?m)^nameserver\s+` + dns.IpLocalhost + `\s*\n*`)
nsIPv6Regexp = regexp.MustCompile(`(?m)^nameserver\s+` + ipv6Address + `\s*\n*`)
nsRegexp = regexp.MustCompile(`^\s*nameserver\s*((` + ipv4Address + `)|(` + ipv6Address + `))\s*$`)
searchRegexp = regexp.MustCompile(`^\s*search\s*(([^\s]+\s*)*)$`)
)
var lastModified struct {
sync.Mutex
sha256 string
contents []byte
}
// Get returns the contents of /etc/resolv.conf
func Get() ([]byte, error) {
resolv, err := ioutil.ReadFile("/etc/resolv.conf")
if err != nil {
return nil, err
}
return resolv, nil
}
// GetIfChanged retrieves the host /etc/resolv.conf file, checks against the last hash
// and, if modified since last check, returns the bytes and new hash.
// This feature is used by the resolv.conf updater for containers
func GetIfChanged() ([]byte, string, error) {
lastModified.Lock()
defer lastModified.Unlock()
resolv, err := ioutil.ReadFile("/etc/resolv.conf")
if err != nil {
return nil, "", err
}
newHash, err := ioutils.HashData(bytes.NewReader(resolv))
if err != nil {
return nil, "", err
}
if lastModified.sha256 != newHash {
lastModified.sha256 = newHash
lastModified.contents = resolv
return resolv, newHash, nil
}
// nothing changed, so return no data
return nil, "", nil
}
// GetLastModified retrieves the last used contents and hash of the host resolv.conf.
// Used by containers updating on restart
func GetLastModified() ([]byte, string) {
lastModified.Lock()
defer lastModified.Unlock()
return lastModified.contents, lastModified.sha256
}
// FilterResolvDns cleans up the config in resolvConf. It has two main jobs:
// 1. It looks for localhost (127.*|::1) entries in the provided
// resolv.conf, removing local nameserver entries, and, if the resulting
// cleaned config has no defined nameservers left, adds default DNS entries
// 2. Given the caller provides the enable/disable state of IPv6, the filter
// code will remove all IPv6 nameservers if it is not enabled for containers
//
// It returns a boolean to notify the caller if changes were made at all
func FilterResolvDns(resolvConf []byte, ipv6Enabled bool) ([]byte, bool) {
changed := false
cleanedResolvConf := localhostNSRegexp.ReplaceAll(resolvConf, []byte{})
// if IPv6 is not enabled, also clean out any IPv6 address nameserver
if !ipv6Enabled {
cleanedResolvConf = nsIPv6Regexp.ReplaceAll(cleanedResolvConf, []byte{})
}
// if the resulting resolvConf has no more nameservers defined, add appropriate
// default DNS servers for IPv4 and (optionally) IPv6
if len(GetNameservers(cleanedResolvConf)) == 0 {
logrus.Infof("No non-localhost DNS nameservers are left in resolv.conf. Using default external servers : %v", defaultIPv4Dns)
dns := defaultIPv4Dns
if ipv6Enabled {
logrus.Infof("IPv6 enabled; Adding default IPv6 external servers : %v", defaultIPv6Dns)
dns = append(dns, defaultIPv6Dns...)
}
cleanedResolvConf = append(cleanedResolvConf, []byte("\n"+strings.Join(dns, "\n"))...)
}
if !bytes.Equal(resolvConf, cleanedResolvConf) {
changed = true
}
return cleanedResolvConf, changed
}
// getLines parses input into lines and strips away comments.
func getLines(input []byte, commentMarker []byte) [][]byte {
lines := bytes.Split(input, []byte("\n"))
var output [][]byte
for _, currentLine := range lines {
var commentIndex = bytes.Index(currentLine, commentMarker)
if commentIndex == -1 {
output = append(output, currentLine)
} else {
output = append(output, currentLine[:commentIndex])
}
}
return output
}
// GetNameservers returns nameservers (if any) listed in /etc/resolv.conf
func GetNameservers(resolvConf []byte) []string {
nameservers := []string{}
for _, line := range getLines(resolvConf, []byte("#")) {
var ns = nsRegexp.FindSubmatch(line)
if len(ns) > 0 {
nameservers = append(nameservers, string(ns[1]))
}
}
return nameservers
}
// GetNameserversAsCIDR returns nameservers (if any) listed in
// /etc/resolv.conf as CIDR blocks (e.g., "1.2.3.4/32")
// This function's output is intended for net.ParseCIDR
func GetNameserversAsCIDR(resolvConf []byte) []string {
nameservers := []string{}
for _, nameserver := range GetNameservers(resolvConf) {
nameservers = append(nameservers, nameserver+"/32")
}
return nameservers
}
// GetSearchDomains returns search domains (if any) listed in /etc/resolv.conf
// If more than one search line is encountered, only the contents of the last
// one is returned.
func GetSearchDomains(resolvConf []byte) []string {
domains := []string{}
for _, line := range getLines(resolvConf, []byte("#")) {
match := searchRegexp.FindSubmatch(line)
if match == nil {
continue
}
domains = strings.Fields(string(match[1]))
}
return domains
}
// Build writes a configuration file to path containing a "nameserver" entry
// for every element in dns, and a "search" entry for every element in
// dnsSearch.
func Build(path string, dns, dnsSearch []string) error {
content := bytes.NewBuffer(nil)
for _, dns := range dns {
if _, err := content.WriteString("nameserver " + dns + "\n"); err != nil {
return err
}
}
if len(dnsSearch) > 0 {
if searchString := strings.Join(dnsSearch, " "); strings.Trim(searchString, " ") != "." {
if _, err := content.WriteString("search " + searchString + "\n"); err != nil {
return err
}
}
}
return ioutil.WriteFile(path, content.Bytes(), 0644)
}

View file

@ -1,238 +0,0 @@
package resolvconf
import (
"bytes"
"io/ioutil"
"os"
"testing"
)
func TestGet(t *testing.T) {
resolvConfUtils, err := Get()
if err != nil {
t.Fatal(err)
}
resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf")
if err != nil {
t.Fatal(err)
}
if string(resolvConfUtils) != string(resolvConfSystem) {
t.Fatalf("/etc/resolv.conf and GetResolvConf have different content.")
}
}
func TestGetNameservers(t *testing.T) {
for resolv, result := range map[string][]string{`
nameserver 1.2.3.4
nameserver 40.3.200.10
search example.com`: {"1.2.3.4", "40.3.200.10"},
`search example.com`: {},
`nameserver 1.2.3.4
search example.com
nameserver 4.30.20.100`: {"1.2.3.4", "4.30.20.100"},
``: {},
` nameserver 1.2.3.4 `: {"1.2.3.4"},
`search example.com
nameserver 1.2.3.4
#nameserver 4.3.2.1`: {"1.2.3.4"},
`search example.com
nameserver 1.2.3.4 # not 4.3.2.1`: {"1.2.3.4"},
} {
test := GetNameservers([]byte(resolv))
if !strSlicesEqual(test, result) {
t.Fatalf("Wrong nameserver string {%s} should be %v. Input: %s", test, result, resolv)
}
}
}
func TestGetNameserversAsCIDR(t *testing.T) {
for resolv, result := range map[string][]string{`
nameserver 1.2.3.4
nameserver 40.3.200.10
search example.com`: {"1.2.3.4/32", "40.3.200.10/32"},
`search example.com`: {},
`nameserver 1.2.3.4
search example.com
nameserver 4.30.20.100`: {"1.2.3.4/32", "4.30.20.100/32"},
``: {},
` nameserver 1.2.3.4 `: {"1.2.3.4/32"},
`search example.com
nameserver 1.2.3.4
#nameserver 4.3.2.1`: {"1.2.3.4/32"},
`search example.com
nameserver 1.2.3.4 # not 4.3.2.1`: {"1.2.3.4/32"},
} {
test := GetNameserversAsCIDR([]byte(resolv))
if !strSlicesEqual(test, result) {
t.Fatalf("Wrong nameserver string {%s} should be %v. Input: %s", test, result, resolv)
}
}
}
func TestGetSearchDomains(t *testing.T) {
for resolv, result := range map[string][]string{
`search example.com`: {"example.com"},
`search example.com # ignored`: {"example.com"},
` search example.com `: {"example.com"},
` search example.com # ignored`: {"example.com"},
`search foo.example.com example.com`: {"foo.example.com", "example.com"},
` search foo.example.com example.com `: {"foo.example.com", "example.com"},
` search foo.example.com example.com # ignored`: {"foo.example.com", "example.com"},
``: {},
`# ignored`: {},
`nameserver 1.2.3.4
search foo.example.com example.com`: {"foo.example.com", "example.com"},
`nameserver 1.2.3.4
search dup1.example.com dup2.example.com
search foo.example.com example.com`: {"foo.example.com", "example.com"},
`nameserver 1.2.3.4
search foo.example.com example.com
nameserver 4.30.20.100`: {"foo.example.com", "example.com"},
} {
test := GetSearchDomains([]byte(resolv))
if !strSlicesEqual(test, result) {
t.Fatalf("Wrong search domain string {%s} should be %v. Input: %s", test, result, resolv)
}
}
}
func strSlicesEqual(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
func TestBuild(t *testing.T) {
file, err := ioutil.TempFile("", "")
if err != nil {
t.Fatal(err)
}
defer os.Remove(file.Name())
err = Build(file.Name(), []string{"ns1", "ns2", "ns3"}, []string{"search1"})
if err != nil {
t.Fatal(err)
}
content, err := ioutil.ReadFile(file.Name())
if err != nil {
t.Fatal(err)
}
if expected := "nameserver ns1\nnameserver ns2\nnameserver ns3\nsearch search1\n"; !bytes.Contains(content, []byte(expected)) {
t.Fatalf("Expected to find '%s' got '%s'", expected, content)
}
}
func TestBuildWithZeroLengthDomainSearch(t *testing.T) {
file, err := ioutil.TempFile("", "")
if err != nil {
t.Fatal(err)
}
defer os.Remove(file.Name())
err = Build(file.Name(), []string{"ns1", "ns2", "ns3"}, []string{"."})
if err != nil {
t.Fatal(err)
}
content, err := ioutil.ReadFile(file.Name())
if err != nil {
t.Fatal(err)
}
if expected := "nameserver ns1\nnameserver ns2\nnameserver ns3\n"; !bytes.Contains(content, []byte(expected)) {
t.Fatalf("Expected to find '%s' got '%s'", expected, content)
}
if notExpected := "search ."; bytes.Contains(content, []byte(notExpected)) {
t.Fatalf("Expected to not find '%s' got '%s'", notExpected, content)
}
}
func TestFilterResolvDns(t *testing.T) {
ns0 := "nameserver 10.16.60.14\nnameserver 10.16.60.21\n"
if result, _ := FilterResolvDns([]byte(ns0), false); result != nil {
if ns0 != string(result) {
t.Fatalf("Failed No Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
}
}
ns1 := "nameserver 10.16.60.14\nnameserver 10.16.60.21\nnameserver 127.0.0.1\n"
if result, _ := FilterResolvDns([]byte(ns1), false); result != nil {
if ns0 != string(result) {
t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
}
}
ns1 = "nameserver 10.16.60.14\nnameserver 127.0.0.1\nnameserver 10.16.60.21\n"
if result, _ := FilterResolvDns([]byte(ns1), false); result != nil {
if ns0 != string(result) {
t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
}
}
ns1 = "nameserver 127.0.1.1\nnameserver 10.16.60.14\nnameserver 10.16.60.21\n"
if result, _ := FilterResolvDns([]byte(ns1), false); result != nil {
if ns0 != string(result) {
t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
}
}
ns1 = "nameserver ::1\nnameserver 10.16.60.14\nnameserver 127.0.2.1\nnameserver 10.16.60.21\n"
if result, _ := FilterResolvDns([]byte(ns1), false); result != nil {
if ns0 != string(result) {
t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
}
}
ns1 = "nameserver 10.16.60.14\nnameserver ::1\nnameserver 10.16.60.21\nnameserver ::1"
if result, _ := FilterResolvDns([]byte(ns1), false); result != nil {
if ns0 != string(result) {
t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
}
}
// with IPv6 disabled (false param), the IPv6 nameserver should be removed
ns1 = "nameserver 10.16.60.14\nnameserver 2002:dead:beef::1\nnameserver 10.16.60.21\nnameserver ::1"
if result, _ := FilterResolvDns([]byte(ns1), false); result != nil {
if ns0 != string(result) {
t.Fatalf("Failed Localhost+IPv6 off: expected \n<%s> got \n<%s>", ns0, string(result))
}
}
// with IPv6 enabled, the IPv6 nameserver should be preserved
ns0 = "nameserver 10.16.60.14\nnameserver 2002:dead:beef::1\nnameserver 10.16.60.21\n"
ns1 = "nameserver 10.16.60.14\nnameserver 2002:dead:beef::1\nnameserver 10.16.60.21\nnameserver ::1"
if result, _ := FilterResolvDns([]byte(ns1), true); result != nil {
if ns0 != string(result) {
t.Fatalf("Failed Localhost+IPv6 on: expected \n<%s> got \n<%s>", ns0, string(result))
}
}
// with IPv6 enabled, and no non-localhost servers, Google defaults (both IPv4+IPv6) should be added
ns0 = "\nnameserver 8.8.8.8\nnameserver 8.8.4.4\nnameserver 2001:4860:4860::8888\nnameserver 2001:4860:4860::8844"
ns1 = "nameserver 127.0.0.1\nnameserver ::1\nnameserver 127.0.2.1"
if result, _ := FilterResolvDns([]byte(ns1), true); result != nil {
if ns0 != string(result) {
t.Fatalf("Failed no Localhost+IPv6 enabled: expected \n<%s> got \n<%s>", ns0, string(result))
}
}
// with IPv6 disabled, and no non-localhost servers, Google defaults (only IPv4) should be added
ns0 = "\nnameserver 8.8.8.8\nnameserver 8.8.4.4"
ns1 = "nameserver 127.0.0.1\nnameserver ::1\nnameserver 127.0.2.1"
if result, _ := FilterResolvDns([]byte(ns1), false); result != nil {
if ns0 != string(result) {
t.Fatalf("Failed no Localhost+IPv6 enabled: expected \n<%s> got \n<%s>", ns0, string(result))
}
}
}