mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Adding IPv6 network support to docker
Signed-off-by: Malte Janduda <mail@janduda.net>
This commit is contained in:
parent
273472a5c2
commit
813ff7f19d
19 changed files with 448 additions and 65 deletions
|
@ -23,6 +23,7 @@ type Config struct {
|
|||
AutoRestart bool
|
||||
Dns []string
|
||||
DnsSearch []string
|
||||
EnableIPv6 bool
|
||||
EnableIptables bool
|
||||
EnableIpForward bool
|
||||
EnableIpMasq bool
|
||||
|
@ -30,6 +31,7 @@ type Config struct {
|
|||
BridgeIface string
|
||||
BridgeIP string
|
||||
FixedCIDR string
|
||||
FixedCIDRv6 string
|
||||
InterContainerCommunication bool
|
||||
GraphDriver string
|
||||
GraphOptions []string
|
||||
|
@ -51,11 +53,13 @@ func (config *Config) InstallFlags() {
|
|||
flag.StringVar(&config.Root, []string{"g", "-graph"}, "/var/lib/docker", "Path to use as the root of the Docker runtime")
|
||||
flag.BoolVar(&config.AutoRestart, []string{"#r", "#-restart"}, true, "--restart on the daemon has been deprecated in favor of --restart policies on docker run")
|
||||
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.EnableIpForward, []string{"#ip-forward", "-ip-forward"}, true, "Enable net.ipv4.ip_forward and IPv6 forwarding if --fixed-cidr-v6 is defined. IPv6 forwarding may interfere with your existing IPv6 configuration when using Router Advertisement.")
|
||||
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)")
|
||||
flag.BoolVar(&config.InterContainerCommunication, []string{"#icc", "-icc"}, true, "Allow unrestricted inter-container and Docker daemon host communication")
|
||||
flag.StringVar(&config.GraphDriver, []string{"s", "-storage-driver"}, "", "Force the Docker runtime to use a specific storage driver")
|
||||
flag.StringVar(&config.ExecDriver, []string{"e", "-exec-driver"}, "native", "Force the Docker runtime to use a specific exec driver")
|
||||
|
|
|
@ -217,11 +217,15 @@ func populateCommand(c *Container, env []string) error {
|
|||
if !c.Config.NetworkDisabled {
|
||||
network := c.NetworkSettings
|
||||
en.Interface = &execdriver.NetworkInterface{
|
||||
Gateway: network.Gateway,
|
||||
Bridge: network.Bridge,
|
||||
IPAddress: network.IPAddress,
|
||||
IPPrefixLen: network.IPPrefixLen,
|
||||
MacAddress: network.MacAddress,
|
||||
Gateway: network.Gateway,
|
||||
Bridge: network.Bridge,
|
||||
IPAddress: network.IPAddress,
|
||||
IPPrefixLen: network.IPPrefixLen,
|
||||
MacAddress: network.MacAddress,
|
||||
LinkLocalIPv6Address: network.LinkLocalIPv6Address,
|
||||
GlobalIPv6Address: network.GlobalIPv6Address,
|
||||
GlobalIPv6PrefixLen: network.GlobalIPv6PrefixLen,
|
||||
IPv6Gateway: network.IPv6Gateway,
|
||||
}
|
||||
}
|
||||
case "container":
|
||||
|
@ -540,6 +544,12 @@ func (container *Container) AllocateNetwork() error {
|
|||
container.NetworkSettings.IPPrefixLen = env.GetInt("IPPrefixLen")
|
||||
container.NetworkSettings.MacAddress = env.Get("MacAddress")
|
||||
container.NetworkSettings.Gateway = env.Get("Gateway")
|
||||
container.NetworkSettings.MacAddress = env.Get("MacAddress")
|
||||
container.NetworkSettings.LinkLocalIPv6Address = env.Get("LinkLocalIPv6")
|
||||
container.NetworkSettings.LinkLocalIPv6PrefixLen = 64
|
||||
container.NetworkSettings.GlobalIPv6Address = env.Get("GlobalIPv6")
|
||||
container.NetworkSettings.GlobalIPv6PrefixLen = env.GetInt("GlobalIPv6PrefixLen")
|
||||
container.NetworkSettings.IPv6Gateway = env.Get("IPv6Gateway")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -919,9 +919,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 {
|
||||
|
|
|
@ -78,11 +78,15 @@ type Ipc struct {
|
|||
}
|
||||
|
||||
type NetworkInterface struct {
|
||||
Gateway string `json:"gateway"`
|
||||
IPAddress string `json:"ip"`
|
||||
IPPrefixLen int `json:"ip_prefix_len"`
|
||||
MacAddress string `json:"mac_address"`
|
||||
Bridge string `json:"bridge"`
|
||||
Gateway string `json:"gateway"`
|
||||
IPAddress string `json:"ip"`
|
||||
IPPrefixLen int `json:"ip_prefix_len"`
|
||||
MacAddress string `json:"mac"`
|
||||
Bridge string `json:"bridge"`
|
||||
GlobalIPv6Address string `json:"global_ipv6"`
|
||||
LinkLocalIPv6Address string `json:"link_local_ipv6"`
|
||||
GlobalIPv6PrefixLen int `json:"global_ipv6_prefix_len"`
|
||||
IPv6Gateway string `json:"ipv6_gateway"`
|
||||
}
|
||||
|
||||
type Resources struct {
|
||||
|
|
|
@ -105,6 +105,10 @@ func (d *driver) createNetwork(container *libcontainer.Config, c *execdriver.Com
|
|||
Bridge: c.Network.Interface.Bridge,
|
||||
VethPrefix: "veth",
|
||||
}
|
||||
if c.Network.Interface.GlobalIPv6Address != "" {
|
||||
vethNetwork.IPv6Address = fmt.Sprintf("%s/%d", c.Network.Interface.GlobalIPv6Address, c.Network.Interface.GlobalIPv6PrefixLen)
|
||||
vethNetwork.IPv6Gateway = c.Network.Interface.IPv6Gateway
|
||||
}
|
||||
container.Networks = append(container.Networks, &vethNetwork)
|
||||
}
|
||||
|
||||
|
|
|
@ -9,13 +9,18 @@ import (
|
|||
type PortMapping map[string]string // Deprecated
|
||||
|
||||
type NetworkSettings struct {
|
||||
IPAddress string
|
||||
IPPrefixLen int
|
||||
MacAddress string
|
||||
Gateway string
|
||||
Bridge string
|
||||
PortMapping map[string]PortMapping // Deprecated
|
||||
Ports nat.PortMap
|
||||
IPAddress string
|
||||
IPPrefixLen int
|
||||
MacAddress string
|
||||
LinkLocalIPv6Address string
|
||||
LinkLocalIPv6PrefixLen int
|
||||
GlobalIPv6Address string
|
||||
GlobalIPv6PrefixLen int
|
||||
Gateway string
|
||||
IPv6Gateway string
|
||||
Bridge string
|
||||
PortMapping map[string]PortMapping // Deprecated
|
||||
Ports nat.PortMap
|
||||
}
|
||||
|
||||
func (settings *NetworkSettings) PortMappingAPI() *engine.Table {
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
package bridge
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
@ -27,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
|
||||
}
|
||||
|
||||
|
@ -69,8 +73,10 @@ var (
|
|||
"192.168.44.1/24",
|
||||
}
|
||||
|
||||
bridgeIface string
|
||||
bridgeNetwork *net.IPNet
|
||||
bridgeIface string
|
||||
bridgeIPv4Network *net.IPNet
|
||||
bridgeIPv6Addr net.IP
|
||||
globalIPv6Network *net.IPNet
|
||||
|
||||
defaultBindingIP = net.ParseIP("0.0.0.0")
|
||||
currentInterfaces = ifaces{c: make(map[string]*networkInterface)}
|
||||
|
@ -78,13 +84,19 @@ var (
|
|||
|
||||
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 != "" {
|
||||
|
@ -98,41 +110,83 @@ 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 existent. 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)
|
||||
|
||||
if fixedCIDRv6 != "" {
|
||||
// Setting route to global IPv6 subnet
|
||||
log.Infof("Adding route to IPv6 network %q via device %q", fixedCIDRv6, bridgeIface)
|
||||
if err := netlink.AddRoute(fixedCIDRv6, "", "", bridgeIface); err != nil {
|
||||
log.Fatalf("Could not add route to IPv6 network %q via device %q", fixedCIDRv6, bridgeIface)
|
||||
}
|
||||
}
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Check if route to fixedCIDRv6 is set
|
||||
}
|
||||
|
||||
if 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
|
||||
break
|
||||
}
|
||||
}
|
||||
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"))
|
||||
}
|
||||
bridgeIPv6Addr = networkv6.IP
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
@ -140,6 +194,16 @@ func InitDriver(job *engine.Job) engine.Status {
|
|||
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 fixedCIDRv6 != "" {
|
||||
// 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
|
||||
|
@ -159,23 +223,35 @@ 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
|
||||
}
|
||||
|
||||
// Block BridgeIP in IP allocator
|
||||
ipallocator.RequestIP(bridgeNetwork, bridgeNetwork.IP)
|
||||
ipallocator.RequestIP(bridgeIPv4Network, bridgeIPv4Network.IP)
|
||||
|
||||
// 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,
|
||||
|
@ -263,7 +339,7 @@ func setupIPTables(addr net.Addr, icc, ipmasq bool) error {
|
|||
// 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) 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
|
||||
|
@ -323,6 +399,25 @@ 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 {
|
||||
// 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: %s\n", err)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
@ -363,20 +458,34 @@ func generateMacAddr(ip net.IP) net.HardwareAddr {
|
|||
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(job *engine.Job) engine.Status {
|
||||
var (
|
||||
ip net.IP
|
||||
mac net.HardwareAddr
|
||||
err error
|
||||
id = job.Args[0]
|
||||
requestedIP = net.ParseIP(job.Getenv("RequestedIP"))
|
||||
ip net.IP
|
||||
mac net.HardwareAddr
|
||||
err error
|
||||
id = job.Args[0]
|
||||
requestedIP = net.ParseIP(job.Getenv("RequestedIP"))
|
||||
requestedIPv6 = net.ParseIP(job.Getenv("RequestedIPv6"))
|
||||
globalIPv6 net.IP
|
||||
)
|
||||
|
||||
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)
|
||||
|
@ -387,18 +496,53 @@ func Allocate(job *engine.Job) engine.Status {
|
|||
mac = generateMacAddr(ip)
|
||||
}
|
||||
|
||||
if globalIPv6Network != nil {
|
||||
// if globalIPv6Network Size is at least a /80 subnet generate IPv6 address from MAC address
|
||||
netmask_ones, _ := globalIPv6Network.Mask.Size()
|
||||
if requestedIPv6 == nil && netmask_ones <= 80 {
|
||||
requestedIPv6 = globalIPv6Network.IP
|
||||
for i, h := range mac {
|
||||
requestedIPv6[i+10] = h
|
||||
}
|
||||
}
|
||||
|
||||
globalIPv6, err = ipallocator.RequestIP(globalIPv6Network, requestedIPv6)
|
||||
if err != nil {
|
||||
log.Errorf("Allocator: RequestIP v6: %s", err.Error())
|
||||
return job.Error(err)
|
||||
}
|
||||
log.Infof("Allocated IPv6 %s", globalIPv6)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
// if linklocal IPv6
|
||||
localIPv6Net, err := linkLocalIPv6FromMac(mac.String())
|
||||
if err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
localIPv6, _, _ := net.ParseCIDR(localIPv6Net)
|
||||
out.Set("LinkLocalIPv6", localIPv6.String())
|
||||
out.Set("MacAddress", mac.String())
|
||||
|
||||
if globalIPv6Network != nil {
|
||||
out.Set("GlobalIPv6", globalIPv6.String())
|
||||
sizev6, _ := globalIPv6Network.Mask.Size()
|
||||
out.SetInt("GlobalIPv6PrefixLen", sizev6)
|
||||
out.Set("IPv6Gateway", bridgeIPv6Addr.String())
|
||||
}
|
||||
|
||||
currentInterfaces.Set(id, &networkInterface{
|
||||
IP: ip,
|
||||
IP: ip,
|
||||
IPv6: globalIPv6,
|
||||
})
|
||||
|
||||
out.WriteTo(job.Stdout)
|
||||
|
@ -423,8 +567,13 @@ func Release(job *engine.Job) engine.Status {
|
|||
}
|
||||
}
|
||||
|
||||
if err := ipallocator.ReleaseIP(bridgeNetwork, containerInterface.IP); err != nil {
|
||||
log.Infof("Unable to release ip %s", err)
|
||||
if err := ipallocator.ReleaseIP(bridgeIPv4Network, containerInterface.IP); err != nil {
|
||||
log.Infof("Unable to release IPv4 %s", err)
|
||||
}
|
||||
if globalIPv6Network != nil {
|
||||
if err := ipallocator.ReleaseIP(globalIPv6Network, containerInterface.IPv6); err != nil {
|
||||
log.Infof("Unable to release IPv6 %s", err)
|
||||
}
|
||||
}
|
||||
return engine.StatusOK
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
|
|
@ -52,9 +52,11 @@ unix://[/path/to/socket] to use.
|
|||
**-g**=""
|
||||
Path to use as the root of the Docker runtime. Default is `/var/lib/docker`.
|
||||
|
||||
|
||||
**--fixed-cidr**=""
|
||||
IPv4 subnet for fixed IPs (ex: 10.20.0.0/16); this subnet must be nested in the bridge subnet (which is defined by \-b or \-\-bip)
|
||||
IPv4 subnet for fixed IPs (e.g., 10.20.0.0/16); this subnet must be nested in the bridge subnet (which is defined by \-b or \-\-bip)
|
||||
|
||||
**--fixed-cidr-v6**=""
|
||||
IPv6 subnet for global IPv6 addresses (e.g., 2a00:1450::/64)
|
||||
|
||||
**--icc**=*true*|*false*
|
||||
Allow unrestricted inter\-container and Docker daemon host communication. If disabled, containers can still be linked together using **--link** option (see **docker-run(1)**). Default is true.
|
||||
|
@ -62,12 +64,18 @@ unix://[/path/to/socket] to use.
|
|||
**--ip**=""
|
||||
Default IP address to use when binding container ports. Default is `0.0.0.0`.
|
||||
|
||||
**--ip-forward**=*true*|*false*
|
||||
Docker will enable IP forwarding. Default is true. If `--fixed-cidr-v6` is set. IPv6 forwarding will be activated, too. This may reject Router Advertisements and interfere with the host's existing IPv6 configuration. For more information please consult the documentation about "Advanced Networking - IPv6".
|
||||
|
||||
**--ip-masq**=*true*|*false*
|
||||
Enable IP masquerading for bridge's IP range. Default is true.
|
||||
|
||||
**--iptables**=*true*|*false*
|
||||
Disable Docker's addition of iptables rules. Default is true.
|
||||
|
||||
**--ipv6**=*true*|*false*
|
||||
Enable IPv6 support. Default is false. Docker will create an IPv6-enabled bridge with address fe80::1 which will allow you to create IPv6-enabled containers. Use together with `--fixed-cidr-v6` to provide globally routable IPv6 addresses. IPv6 forwarding will be enabled if not used with `--ip-forward=false`. This may collide with your host's current IPv6 settings. For more information please consult the documentation about "Advanced Networking - IPv6".
|
||||
|
||||
**-l**, **--log-level**="*debug*|*info*|*error*|*fatal*""
|
||||
Set the logging level. Default is `info`.
|
||||
|
||||
|
|
1
docs/sources/article-img/ipv6_basic_host_config.gliffy
Normal file
1
docs/sources/article-img/ipv6_basic_host_config.gliffy
Normal file
File diff suppressed because one or more lines are too long
1
docs/sources/article-img/ipv6_basic_host_config.svg
Normal file
1
docs/sources/article-img/ipv6_basic_host_config.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 19 KiB |
File diff suppressed because one or more lines are too long
1
docs/sources/article-img/ipv6_routed_network_example.svg
Normal file
1
docs/sources/article-img/ipv6_routed_network_example.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 80 KiB |
File diff suppressed because one or more lines are too long
1
docs/sources/article-img/ipv6_slash64_subnet_config.svg
Normal file
1
docs/sources/article-img/ipv6_slash64_subnet_config.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 30 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 72 KiB |
|
@ -57,6 +57,9 @@ server when it starts up, and cannot be changed once it is running:
|
|||
* `--fixed-cidr` — see
|
||||
[Customizing docker0](#docker0)
|
||||
|
||||
* `--fixed-cidr-v6` — see
|
||||
[IPv6](#ipv6)
|
||||
|
||||
* `-H SOCKET...` or `--host=SOCKET...` —
|
||||
This might sound like it would affect container networking,
|
||||
but it actually faces in the other direction:
|
||||
|
@ -70,8 +73,11 @@ server when it starts up, and cannot be changed once it is running:
|
|||
* `--ip=IP_ADDRESS` — see
|
||||
[Binding container ports](#binding-ports)
|
||||
|
||||
* `--ipv6=true|false` — see
|
||||
[IPv6](#ipv6)
|
||||
|
||||
* `--ip-forward=true|false` — see
|
||||
[Communication between containers](#between-containers)
|
||||
[Communication between containers and the wider world](#the-world)
|
||||
|
||||
* `--iptables=true|false` — see
|
||||
[Communication between containers](#between-containers)
|
||||
|
@ -204,7 +210,7 @@ Whether a container can talk to the world is governed by two factors.
|
|||
containers if this parameter is `1`. Usually you will simply leave
|
||||
the Docker server at its default setting `--ip-forward=true` and
|
||||
Docker will go set `ip_forward` to `1` for you when the server
|
||||
starts up. To check the setting or turn it on manually:
|
||||
starts up. To check the setting or turn it on manually:
|
||||
|
||||
```
|
||||
$ cat /proc/sys/net/ipv4/ip_forward
|
||||
|
@ -397,6 +403,182 @@ Again, this topic is covered without all of these low-level networking
|
|||
details in the [Docker User Guide](/userguide/dockerlinks/) document if you
|
||||
would like to use that as your port redirection reference instead.
|
||||
|
||||
## IPv6
|
||||
|
||||
<a name="ipv6"></a>
|
||||
|
||||
As we are [running out of IPv4 addresses](http://en.wikipedia.org/wiki/IPv4_address_exhaustion)
|
||||
the IETF has standardized an IPv4 successor, [Internet Protocol Version 6](http://en.wikipedia.org/wiki/IPv6)
|
||||
, in [RFC 2460](https://www.ietf.org/rfc/rfc2460.txt). Both protocols, IPv4 and
|
||||
IPv6, reside on layer 3 of the [OSI model](http://en.wikipedia.org/wiki/OSI_model).
|
||||
|
||||
|
||||
### IPv6 with Docker
|
||||
By default, the Docker server configures the container network for IPv4 only.
|
||||
You can enable IPv4/IPv6 dualstack support by running the Docker daemon with the
|
||||
`--ipv6` flag. Docker will set up the bridge `docker0` with the IPv6
|
||||
[link-local address](http://en.wikipedia.org/wiki/Link-local_address) `fe80::1`.
|
||||
|
||||
By default, containers that are created will only get a link-local IPv6 address.
|
||||
To assign globally routable IPv6 addresses to your containers you have to
|
||||
specify an IPv6 subnet to pick the addresses from. Set the IPv6 subnet via the
|
||||
`--fixed-cidr-v6` parameter when starting Docker daemon:
|
||||
|
||||
docker -d --ipv6 --fixed-cidr-v6="2001:db8:0:2:/64"
|
||||
|
||||
The subnet for Docker containers should at least have a size of `/80`. This way
|
||||
an IPv6 address can end with the container's MAC address and you prevent NDP
|
||||
neighbor cache invalidation issues in the Docker layer.
|
||||
|
||||
With the `--fixed-cidr-v6` parameter set Docker will add a new route to the
|
||||
routing table. Further IPv6 routing will be enabled (you may prevent this by
|
||||
starting Docker daemon with `--ip-forward=false`):
|
||||
|
||||
$ route -A inet6 add 2001:db8:0:2/64 dev docker0
|
||||
$ echo 1 > /proc/sys/net/ipv6/conf/default/forwarding
|
||||
$ echo 1 > /proc/sys/net/ipv6/conf/all/forwarding
|
||||
|
||||
All traffic to the subnet `2001:db8:0:2/64` will now be routed
|
||||
via the `docker0` interface.
|
||||
|
||||
Be aware that IPv6 forwarding may interfere with your existing IPv6
|
||||
configuration: If you are using Router Advertisements to get IPv6 settings for
|
||||
your host's interfaces you should set `accept_ra` to `2`. Otherwise IPv6
|
||||
enabled forwarding will result in rejecting Router Advertisements. E.g., if you
|
||||
want to configure `eth0` via Router Advertisements you should set:
|
||||
|
||||
```
|
||||
$ echo 2 > /proc/sys/net/ipv6/conf/eth0/accept_ra
|
||||
```
|
||||
|
||||
![](/article-img/ipv6_basic_host_config.svg)
|
||||
|
||||
Every new container will get an IPv6 address from the defined subnet. Further
|
||||
a default route will be added via the gateway `fe80::1` on `eth0`:
|
||||
|
||||
docker run -it ubuntu bash -c "ifconfig eth0; route -A inet6"
|
||||
|
||||
eth0 Link encap:Ethernet HWaddr 02:42:ac:11:00:02
|
||||
inet addr:172.17.0.2 Bcast:0.0.0.0 Mask:255.255.0.0
|
||||
inet6 addr: 2001:db8:0:2::1/64 Scope:Global
|
||||
inet6 addr: fe80::42:acff:fe11:2/64 Scope:Link
|
||||
UP BROADCAST MTU:1500 Metric:1
|
||||
RX packets:1 errors:0 dropped:0 overruns:0 frame:0
|
||||
TX packets:1 errors:0 dropped:0 overruns:0 carrier:0
|
||||
collisions:0 txqueuelen:0
|
||||
RX bytes:110 (110.0 B) TX bytes:110 (110.0 B)
|
||||
|
||||
Kernel IPv6 routing table
|
||||
Destination Next Hop Flag Met Ref Use If
|
||||
2001:db8:0:2::/64 :: U 256 0 0 eth0
|
||||
fe80::/64 :: U 256 0 0 eth0
|
||||
::/0 fe80::1 UG 1024 0 0 eth0
|
||||
::/0 :: !n -1 1 1 lo
|
||||
::1/128 :: Un 0 1 0 lo
|
||||
ff00::/8 :: U 256 1 0 eth0
|
||||
::/0 :: !n -1 1 1 lo
|
||||
|
||||
In this example the Docker container is assigned a link-local address with the
|
||||
network suffix `/64` (here: `fe80::42:acff:fe11:2/64`) and a globally routable
|
||||
IPv6 address (here: `2001:db8:0:2::1/64`). The container will create connections
|
||||
to addresses outside of the `2001:db8:0:2::/64` network via the link-local
|
||||
gateway at `fe80::1` on `eth0`.
|
||||
|
||||
Often servers or virtual machines get a `/64` IPv6 subnet assigned. In this case
|
||||
you can split it up further and provide Docker a `/80` subnet while using a
|
||||
separate `/80` subnet for other applications on the host:
|
||||
|
||||
![](/article-img/ipv6_slash64_subnet_config.svg)
|
||||
|
||||
In this setup the subnet `2001:db8::/80` with a range from `2001:db8::0:0:0:0`
|
||||
to `2001:db8::0:ffff:ffff:ffff` is attached to `eth0`, with the host listening
|
||||
at `2001:db8::1`. The subnet `2001:db8:0:0:0:1::/80` with an address range from
|
||||
`2001:db8::1:0:0:0` to `2001:db8::1:ffff:ffff:ffff` is attached to `docker0` and
|
||||
will be used by containers.
|
||||
|
||||
### Docker IPv6 Cluster
|
||||
|
||||
#### Switched Network Environment
|
||||
Using routable IPv6 addresses allows you to realize communication between
|
||||
containers on different hosts. Let's have a look at a simple Docker IPv6 cluster
|
||||
example:
|
||||
|
||||
![](/article-img/ipv6_switched_network_example.svg)
|
||||
|
||||
The Docker hosts are in the `2000::/64` subnet. Host1 is configured
|
||||
to provide addresses from the `2001::/64` subnet to its containers. It has three
|
||||
routes configured:
|
||||
|
||||
- Route all traffic to `2000::/64` via `eth0`
|
||||
- Route all traffic to `2001::/64` via `docker0`
|
||||
- Route all traffic to `2002::/64` via Host2 with IP `2000::2`
|
||||
|
||||
Host1 also acts as a router on OSI layer 3. When one of the network clients
|
||||
tries to contact a target that is specified in Host1's routing table Host1 will
|
||||
forward the traffic accordingly. It acts as a router for all networks it knows:
|
||||
`2000:/64`, `2001:/64` and `2002::/64`.
|
||||
|
||||
On Host2 we have nearly the same configuration. Host2's containers will get IPv6
|
||||
addresses from `2002::/64`. Host2 has three routes configured:
|
||||
|
||||
- Route all traffic to `2000::/64` via `eth0`
|
||||
- Route all traffic to `2002::/64` via `docker0`
|
||||
- Route all traffic to `2001::/64` via Host1 with IP `2000::1`
|
||||
|
||||
The difference to Host1 is that the network `2002::/64` is directly attached to
|
||||
the host via its `docker0` interface whereas it reaches `2001::/64` via Host1's
|
||||
IPv6 address `2000::1`.
|
||||
|
||||
This way every container is able to contact every other container. The
|
||||
containers `Container1-*` share the same subnet and contact each other directly.
|
||||
The traffic between `Container1-*` and `Container2-*` will be routed via Host1
|
||||
and Host2 because those containers do not share the same subnet.
|
||||
|
||||
In a switched environment every host has to know all routes to every subnet. You
|
||||
always have to update the hosts' routing tables once you add or remove a host
|
||||
to the cluster.
|
||||
|
||||
Every configuration in the diagram that is shown below the dashed line is
|
||||
handled by Docker: The `docker0` bridge IP address configuration, the route to
|
||||
the Docker subnet on the host, the container IP addresses and the routes on the
|
||||
containers. The configuration above the line is up to the user and can be
|
||||
adapted to the individual environment.
|
||||
|
||||
#### Routed Network Environment
|
||||
|
||||
In a routed network environment you replace the level 2 switch with a level 3
|
||||
router. Now the hosts just have to know their default gateway (the router) and
|
||||
the route to their own containers (managed by Docker). The router holds all
|
||||
routing information about the Docker subnets. When you add or remove a host to
|
||||
this environment you just have to update the routing table in the router - not
|
||||
on every host.
|
||||
|
||||
![](/article-img/ipv6_routed_network_example.svg)
|
||||
|
||||
In this scenario containers of the same host can communicate directly with each
|
||||
other. The traffic between containers on different hosts will be routed via
|
||||
their hosts and the router. For example packet from `Container1-1` to
|
||||
`Container2-1` will be routed through `Host1`, `Router` and `Host2` until it
|
||||
arrives at `Container2-1`.
|
||||
|
||||
To keep the IPv6 addresses short in this example a `/48` network is assigned to
|
||||
every host. The hosts use a `/64` subnet of this for its own services and one
|
||||
for Docker. When adding a third host you would add a route for the subnet
|
||||
`2001:db8:3::/48` in the router and configure Docker on Host3 with
|
||||
`--fixed-cidr-v6=2001:db8:3:1::/64`.
|
||||
|
||||
Remember the subnet for Docker containers should at least have a size of `/80`.
|
||||
This way an IPv6 address can end with the container's MAC address and you
|
||||
prevent ARP cache invalidation issues in the Docker layer. So if you have a
|
||||
`/64` for your whole environment use `/68` subnets for the hosts and `/80` for
|
||||
the containers. This way you can use 4096 hosts with 16 `/80` subnets each.
|
||||
|
||||
Every configuration in the diagram that is visualized below the dashed line is
|
||||
handled by Docker: The `docker0` bridge IP address configuration, the route to
|
||||
the Docker subnet on the host, the container IP addresses and the routes on the
|
||||
containers. The configuration above the line is up to the user and can be
|
||||
adapted to the individual environment.
|
||||
|
||||
## Customizing docker0
|
||||
|
||||
<a name="docker0"></a>
|
||||
|
|
|
@ -76,8 +76,9 @@ expect an integer, and they can only be specified once.
|
|||
--dns=[] Force Docker to use specific DNS servers
|
||||
--dns-search=[] Force Docker to use specific DNS search domains
|
||||
-e, --exec-driver="native" Force the Docker runtime to use a specific exec driver
|
||||
--fixed-cidr="" IPv4 subnet for fixed IPs (ex: 10.20.0.0/16)
|
||||
--fixed-cidr="" IPv4 subnet for fixed IPs (e.g.: 10.20.0.0/16)
|
||||
this subnet must be nested in the bridge subnet (which is defined by -b or --bip)
|
||||
--fixed-cidr-v6="" IPv6 subnet for global IPs (e.g.: 2a00:1450::/64)
|
||||
-G, --group="docker" Group to assign the unix socket specified by -H when running in daemon mode
|
||||
use '' (the empty string) to disable setting of a group
|
||||
-g, --graph="/var/lib/docker" Path to use as the root of the Docker runtime
|
||||
|
@ -85,9 +86,10 @@ expect an integer, and they can only be specified once.
|
|||
--icc=true Allow unrestricted inter-container and Docker daemon host communication
|
||||
--insecure-registry=[] Enable insecure communication with specified registries (disables certificate verification for HTTPS and enables HTTP fallback) (e.g., localhost:5000 or 10.20.0.0/16)
|
||||
--ip=0.0.0.0 Default IP address to use when binding container ports
|
||||
--ip-forward=true Enable net.ipv4.ip_forward
|
||||
--ip-forward=true Enable net.ipv4.ip_forward and IPv6 forwarding if --fixed-cidr-v6 is defined. IPv6 forwarding may interfere with your existing IPv6 configuration when using Router Advertisement.
|
||||
--ip-masq=true Enable IP masquerading for bridge's IP range
|
||||
--iptables=true Enable Docker's addition of iptables rules
|
||||
--ipv6=false Enable Docker IPv6 support
|
||||
-l, --log-level="info" Set the logging level
|
||||
--label=[] Set key=value labels to the daemon (displayed in `docker info`)
|
||||
--mtu=0 Set the containers network MTU
|
||||
|
|
Loading…
Add table
Reference in a new issue