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:
parent
d18919e304
commit
f12f51b8b9
25 changed files with 0 additions and 4585 deletions
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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())
|
||||
}
|
|
@ -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.1–127.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
)
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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{}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
Package resolvconf provides utility code to query and update DNS configuration in /etc/resolv.conf
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue