Skip to content

Commit

Permalink
Adding IPv6 support to docker daemon
Browse files Browse the repository at this point in the history
Signed-off-by: Malte Janduda <mail@janduda.net>
  • Loading branch information
MalteJ committed Nov 4, 2014
1 parent edae883 commit 5e67651
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 33 deletions.
6 changes: 5 additions & 1 deletion daemon/config.go
Expand Up @@ -24,13 +24,15 @@ type Config struct {
Dns []string
DnsSearch []string
Mirrors []string
EnableIPv6 bool
EnableIptables bool
EnableIpForward bool
EnableIpMasq bool
DefaultIp net.IP
BridgeIface string
BridgeIP string
FixedCIDR string
FixedCIDRv6 string
InsecureRegistries []string
InterContainerCommunication bool
GraphDriver string
Expand All @@ -53,9 +55,11 @@ func (config *Config) InstallFlags() {
flag.BoolVar(&config.EnableIptables, []string{"#iptables", "-iptables"}, true, "Enable Docker's addition of iptables rules")
flag.BoolVar(&config.EnableIpForward, []string{"#ip-forward", "-ip-forward"}, true, "Enable net.ipv4.ip_forward")
flag.BoolVar(&config.EnableIpMasq, []string{"-ip-masq"}, true, "Enable IP masquerading for bridge's IP range")
flag.BoolVar(&config.EnableIPv6, []string{"-ipv6"}, false, "Enable IPv6 networking")
flag.StringVar(&config.BridgeIP, []string{"#bip", "-bip"}, "", "Use this CIDR notation address for the network bridge's IP, not compatible with -b")
flag.StringVar(&config.BridgeIface, []string{"b", "-bridge"}, "", "Attach containers to a pre-existing network bridge\nuse 'none' to disable container networking")
flag.StringVar(&config.FixedCIDR, []string{"-fixed-cidr"}, "", "IPv4 subnet for fixed IPs (ex: 10.20.0.0/16)\nthis subnet must be nested in the bridge subnet (which is defined by -b or --bip)")
flag.StringVar(&config.FixedCIDR, []string{"-fixed-cidr"}, "", "IPv4 subnet for fixed IPs (e.g. 10.20.0.0/16)\nthis subnet must be nested in the bridge subnet (which is defined by -b or --bip)")
flag.StringVar(&config.FixedCIDRv6, []string{"-fixed-cidr-v6"}, "", "IPv6 subnet for fixed IPs (e.g.: 2001:a02b/48)")
opts.ListVar(&config.InsecureRegistries, []string{"-insecure-registry"}, "Enable insecure communication with specified registries (no certificate verification for HTTPS and enable HTTP fallback)")
flag.BoolVar(&config.InterContainerCommunication, []string{"#icc", "-icc"}, true, "Enable inter-container communication")
flag.StringVar(&config.GraphDriver, []string{"s", "-storage-driver"}, "", "Force the Docker runtime to use a specific storage driver")
Expand Down
2 changes: 2 additions & 0 deletions daemon/daemon.go
Expand Up @@ -853,9 +853,11 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
job.SetenvBool("InterContainerCommunication", config.InterContainerCommunication)
job.SetenvBool("EnableIpForward", config.EnableIpForward)
job.SetenvBool("EnableIpMasq", config.EnableIpMasq)
job.SetenvBool("EnableIPv6", config.EnableIPv6)
job.Setenv("BridgeIface", config.BridgeIface)
job.Setenv("BridgeIP", config.BridgeIP)
job.Setenv("FixedCIDR", config.FixedCIDR)
job.Setenv("FixedCIDRv6", config.FixedCIDRv6)
job.Setenv("DefaultBindingIP", config.DefaultIp.String())

if err := job.Run(); err != nil {
Expand Down
117 changes: 95 additions & 22 deletions daemon/networkdriver/bridge/driver.go
@@ -1,6 +1,7 @@
package bridge

import (
"errors"
"fmt"
"io/ioutil"
"net"
Expand Down Expand Up @@ -29,6 +30,7 @@ const (
// 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
}

Expand Down Expand Up @@ -71,22 +73,29 @@ var (
"192.168.44.1/24",
}

bridgeIface string
bridgeNetwork *net.IPNet
bridgeIface string
bridgeIPv4Network *net.IPNet
globalIPv6Network *net.IPNet

defaultBindingIP = net.ParseIP("0.0.0.0")
currentInterfaces = ifaces{c: make(map[string]*networkInterface)}
)

func InitDriver(job *engine.Job) engine.Status {
var (
network *net.IPNet
networkv4 *net.IPNet
networkv6 *net.IPNet
addrv4 net.Addr
addrsv6 []net.Addr
enableIPTables = job.GetenvBool("EnableIptables")
enableIPv6 = job.GetenvBool("EnableIPv6")
icc = job.GetenvBool("InterContainerCommunication")
ipMasq = job.GetenvBool("EnableIpMasq")
ipForward = job.GetenvBool("EnableIpForward")
bridgeIP = job.Getenv("BridgeIP")
bridgeIPv6 = "fe80::1/64"
fixedCIDR = job.Getenv("FixedCIDR")
fixedCIDRv6 = job.Getenv("FixedCIDRv6")
)

if defaultIP := job.Getenv("DefaultBindingIP"); defaultIP != "" {
Expand All @@ -100,48 +109,87 @@ func InitDriver(job *engine.Job) engine.Status {
bridgeIface = DefaultNetworkBridge
}

addr, err := networkdriver.GetIfaceAddr(bridgeIface)
addrv4, addrsv6, err := networkdriver.GetIfaceAddr(bridgeIface)

if err != nil {
// No Bridge existant. Create one
// If we're not using the default bridge, fail without trying to create it
if !usingDefaultBridge {
return job.Error(err)
}
// If the bridge interface is not found (or has no address), try to create it and/or add an address
if err := configureBridge(bridgeIP); err != nil {

// If the iface is not found, try to create it
if err := configureBridge(bridgeIP, bridgeIPv6, enableIPv6); err != nil {
return job.Error(err)
}

addr, err = networkdriver.GetIfaceAddr(bridgeIface)
addrv4, addrsv6, err = networkdriver.GetIfaceAddr(bridgeIface)
if err != nil {
return job.Error(err)
}
network = addr.(*net.IPNet)
} else {
network = addr.(*net.IPNet)
// Bridge exists already. Getting info...
// validate that the bridge ip matches the ip specified by BridgeIP
if bridgeIP != "" {
networkv4 = addrv4.(*net.IPNet)
bip, _, err := net.ParseCIDR(bridgeIP)
if err != nil {
return job.Error(err)
}
if !network.IP.Equal(bip) {
return job.Errorf("bridge ip (%s) does not match existing bridge configuration %s", network.IP, bip)
if !networkv4.IP.Equal(bip) {
return job.Errorf("bridge ip (%s) does not match existing bridge configuration %s", networkv4.IP, bip)
}
}
if bridgeIPv6 != "" && enableIPv6 {
bip6, _, err := net.ParseCIDR(bridgeIPv6)
if err != nil {
return job.Error(err)
}
found := false
for _, addrv6 := range addrsv6 {
networkv6 = addrv6.(*net.IPNet)
if networkv6.IP.Equal(bip6) {
found = true
}
}
if !found {
return job.Errorf("bridge IPv6 does not match existing bridge configuration %s", bip6)
}
}
}

networkv4 = addrv4.(*net.IPNet)

log.Infof("enableIPv6 = %t", enableIPv6)
if enableIPv6 {
if len(addrsv6) == 0 {
return job.Error(errors.New("IPv6 enabled but no IPv6 detected"))
}
}

// Configure iptables for link support
if enableIPTables {
if err := setupIPTables(addr, icc, ipMasq); err != nil {
if err := setupIPTables(addrv4, icc, ipMasq); err != nil {
return job.Error(err)
}

}

if ipForward {
// Enable IPv4 forwarding
if err := ioutil.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte{'1', '\n'}, 0644); err != nil {
job.Logf("WARNING: unable to enable IPv4 forwarding: %s\n", err)
}

if enableIPv6 {
// Enable IPv6 forwarding
if err := ioutil.WriteFile("/proc/sys/net/ipv6/conf/default/forwarding", []byte{'1', '\n'}, 0644); err != nil {
job.Logf("WARNING: unable to enable IPv6 default forwarding: %s\n", err)
}
if err := ioutil.WriteFile("/proc/sys/net/ipv6/conf/all/forwarding", []byte{'1', '\n'}, 0644); err != nil {
job.Logf("WARNING: unable to enable IPv6 all forwarding: %s\n", err)
}
}
}

// We can always try removing the iptables
Expand All @@ -157,20 +205,32 @@ func InitDriver(job *engine.Job) engine.Status {
portmapper.SetIptablesChain(chain)
}

bridgeNetwork = network
bridgeIPv4Network = networkv4
if fixedCIDR != "" {
_, subnet, err := net.ParseCIDR(fixedCIDR)
if err != nil {
return job.Error(err)
}
log.Debugf("Subnet: %v", subnet)
if err := ipallocator.RegisterSubnet(bridgeNetwork, subnet); err != nil {
if err := ipallocator.RegisterSubnet(bridgeIPv4Network, subnet); err != nil {
return job.Error(err)
}
}

if fixedCIDRv6 != "" {
_, subnet, err := net.ParseCIDR(fixedCIDRv6)
if err != nil {
return job.Error(err)
}
log.Debugf("Subnet: %v", subnet)
if err := ipallocator.RegisterSubnet(subnet, subnet); err != nil {
return job.Error(err)
}
globalIPv6Network = subnet
}

// https://github.com/docker/docker/issues/2768
job.Eng.Hack_SetGlobalVar("httpapi.bridgeIP", bridgeNetwork.IP)
job.Eng.Hack_SetGlobalVar("httpapi.bridgeIP", bridgeIPv4Network.IP)

for name, f := range map[string]engine.Handler{
"allocate_interface": Allocate,
Expand Down Expand Up @@ -258,7 +318,7 @@ func setupIPTables(addr net.Addr, icc, ipmasq bool) error {
// If the bridge `ifaceName` 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) error {
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
Expand Down Expand Up @@ -318,6 +378,19 @@ func configureBridge(bridgeIP string) error {
if netlink.NetworkLinkAddIp(iface, ipAddr, ipNet); err != nil {
return fmt.Errorf("Unable to add private network: %s", err)
}

if enableIPv6 {
ipAddr6, ipNet6, err := net.ParseCIDR(bridgeIPv6)
if err != nil {
log.Errorf("BridgeIPv6 parsing failed")
return err
}

if netlink.NetworkLinkAddIp(iface, ipAddr6, ipNet6); err != nil {
return fmt.Errorf("Unable to add private IPv6 network: %s", err)
}
}

if err := netlink.NetworkLinkUp(iface); err != nil {
return fmt.Errorf("Unable to start network bridge: %s", err)
}
Expand Down Expand Up @@ -369,9 +442,9 @@ func Allocate(job *engine.Job) engine.Status {
)

if requestedIP != nil {
ip, err = ipallocator.RequestIP(bridgeNetwork, requestedIP)
ip, err = ipallocator.RequestIP(bridgeIPv4Network, requestedIP)
} else {
ip, err = ipallocator.RequestIP(bridgeNetwork, nil)
ip, err = ipallocator.RequestIP(bridgeIPv4Network, nil)
}
if err != nil {
return job.Error(err)
Expand All @@ -384,12 +457,12 @@ func Allocate(job *engine.Job) engine.Status {

out := engine.Env{}
out.Set("IP", ip.String())
out.Set("Mask", bridgeNetwork.Mask.String())
out.Set("Gateway", bridgeNetwork.IP.String())
out.Set("Mask", bridgeIPv4Network.Mask.String())
out.Set("Gateway", bridgeIPv4Network.IP.String())
out.Set("MacAddress", mac.String())
out.Set("Bridge", bridgeIface)

size, _ := bridgeNetwork.Mask.Size()
size, _ := bridgeIPv4Network.Mask.Size()
out.SetInt("IPPrefixLen", size)

currentInterfaces.Set(id, &networkInterface{
Expand Down Expand Up @@ -418,7 +491,7 @@ func Release(job *engine.Job) engine.Status {
}
}

if err := ipallocator.ReleaseIP(bridgeNetwork, containerInterface.IP); err != nil {
if err := ipallocator.ReleaseIP(bridgeIPv4Network, containerInterface.IP); err != nil {
log.Infof("Unable to release ip %s", err)
}
return engine.StatusOK
Expand Down
25 changes: 15 additions & 10 deletions daemon/networkdriver/utils.go
Expand Up @@ -44,11 +44,13 @@ func CheckRouteOverlaps(toCheck *net.IPNet) error {

// Detects overlap between one IPNet and another
func NetworkOverlaps(netX *net.IPNet, netY *net.IPNet) bool {
if firstIP, _ := NetworkRange(netX); netY.Contains(firstIP) {
return true
}
if firstIP, _ := NetworkRange(netY); netX.Contains(firstIP) {
return true
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
}
Expand All @@ -73,30 +75,33 @@ func NetworkRange(network *net.IPNet) (net.IP, net.IP) {
}

// Return the IPv4 address of a network interface
func GetIfaceAddr(name string) (net.Addr, error) {
func GetIfaceAddr(name string) (net.Addr, []net.Addr, error) {
iface, err := net.InterfaceByName(name)
if err != nil {
return nil, err
return nil, nil, err
}
addrs, err := iface.Addrs()
if err != nil {
return nil, err
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, fmt.Errorf("Interface %v has no IP addresses", name)
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], nil
return addrs4[0], addrs6, nil
}

func GetDefaultRouteIface() (*net.Interface, error) {
Expand Down

0 comments on commit 5e67651

Please sign in to comment.