Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remaining steps for IPv6 support #8896

Closed
wants to merge 9 commits into from
2 changes: 2 additions & 0 deletions daemon/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type Config struct {
FixedCIDR string
InsecureRegistries []string
InterContainerCommunication bool
UseIpv6 bool
GraphDriver string
GraphOptions []string
ExecDriver string
Expand All @@ -58,6 +59,7 @@ func (config *Config) InstallFlags() {
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)")
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.BoolVar(&config.UseIpv6, []string{"#ipv6", "-ipv6"}, false, "Use ipv6")
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")
flag.BoolVar(&config.EnableSelinuxSupport, []string{"-selinux-enabled"}, false, "Enable selinux support. SELinux does not presently support the BTRFS storage driver")
Expand Down
1 change: 1 addition & 0 deletions daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)

job.SetenvBool("EnableIptables", config.EnableIptables)
job.SetenvBool("InterContainerCommunication", config.InterContainerCommunication)
job.SetenvBool("UseIpv6", config.UseIpv6)
job.SetenvBool("EnableIpForward", config.EnableIpForward)
job.SetenvBool("EnableIpMasq", config.EnableIpMasq)
job.Setenv("BridgeIface", config.BridgeIface)
Expand Down
59 changes: 37 additions & 22 deletions daemon/networkdriver/bridge/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ func InitDriver(job *engine.Job) engine.Status {
network *net.IPNet
enableIPTables = job.GetenvBool("EnableIptables")
icc = job.GetenvBool("InterContainerCommunication")
useIpv6 = job.GetenvBool("UseIpv6")
ipMasq = job.GetenvBool("EnableIpMasq")
ipForward = job.GetenvBool("EnableIpForward")
bridgeIP = job.Getenv("BridgeIP")
Expand All @@ -93,25 +94,27 @@ func InitDriver(job *engine.Job) engine.Status {
defaultBindingIP = net.ParseIP(defaultIP)
}

iptable := iptables.GetTable(useIpv6)

bridgeIface = job.Getenv("BridgeIface")
usingDefaultBridge := false
if bridgeIface == "" {
usingDefaultBridge = true
bridgeIface = DefaultNetworkBridge
}

addr, err := networkdriver.GetIfaceAddr(bridgeIface)
addr, err := networkdriver.GetIfaceAddr(bridgeIface, useIpv6)
if err != nil {
// 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 err := configureBridge(useIpv6, bridgeIP); err != nil {
return job.Error(err)
}

addr, err = networkdriver.GetIfaceAddr(bridgeIface)
addr, err = networkdriver.GetIfaceAddr(bridgeIface, useIpv6)
if err != nil {
return job.Error(err)
}
Expand All @@ -132,7 +135,7 @@ func InitDriver(job *engine.Job) engine.Status {

// Configure iptables for link support
if enableIPTables {
if err := setupIPTables(addr, icc, ipMasq); err != nil {
if err := setupIPTables(iptable, addr, icc, ipMasq); err != nil {
return job.Error(err)
}
}
Expand All @@ -145,12 +148,12 @@ func InitDriver(job *engine.Job) engine.Status {
}

// We can always try removing the iptables
if err := iptables.RemoveExistingChain("DOCKER"); err != nil {
if err := iptable.RemoveExistingChain("DOCKER"); err != nil {
return job.Error(err)
}

if enableIPTables {
chain, err := iptables.NewChain("DOCKER", bridgeIface)
chain, err := iptable.NewChain("DOCKER", bridgeIface)
if err != nil {
return job.Error(err)
}
Expand Down Expand Up @@ -185,14 +188,14 @@ func InitDriver(job *engine.Job) engine.Status {
return engine.StatusOK
}

func setupIPTables(addr net.Addr, icc, ipmasq bool) error {
func setupIPTables(iptable *iptables.Table, addr net.Addr, icc, ipmasq bool) error {
// Enable NAT

if ipmasq {
natArgs := []string{"POSTROUTING", "-t", "nat", "-s", addr.String(), "!", "-o", bridgeIface, "-j", "MASQUERADE"}

if !iptables.Exists(natArgs...) {
if output, err := iptables.Raw(append([]string{"-I"}, natArgs...)...); err != nil {
if !iptable.Exists(natArgs...) {
if output, err := iptable.Raw(append([]string{"-I"}, natArgs...)...); err != nil {
return fmt.Errorf("Unable to enable network bridge NAT: %s", err)
} else if len(output) != 0 {
return fmt.Errorf("Error iptables postrouting: %s", output)
Expand All @@ -207,22 +210,22 @@ func setupIPTables(addr net.Addr, icc, ipmasq bool) error {
)

if !icc {
iptables.Raw(append([]string{"-D"}, acceptArgs...)...)
iptable.Raw(append([]string{"-D"}, acceptArgs...)...)

if !iptables.Exists(dropArgs...) {
if !iptable.Exists(dropArgs...) {
log.Debugf("Disable inter-container communication")
if output, err := iptables.Raw(append([]string{"-I"}, dropArgs...)...); err != nil {
if output, err := iptable.Raw(append([]string{"-I"}, 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"}, dropArgs...)...)
iptable.Raw(append([]string{"-D"}, dropArgs...)...)

if !iptables.Exists(acceptArgs...) {
if !iptable.Exists(acceptArgs...) {
log.Debugf("Enable inter-container communication")
if output, err := iptables.Raw(append([]string{"-I"}, acceptArgs...)...); err != nil {
if output, err := iptable.Raw(append([]string{"-I"}, 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)
Expand All @@ -232,8 +235,8 @@ func setupIPTables(addr net.Addr, icc, ipmasq bool) error {

// Accept all non-intercontainer outgoing packets
outgoingArgs := []string{"FORWARD", "-i", bridgeIface, "!", "-o", bridgeIface, "-j", "ACCEPT"}
if !iptables.Exists(outgoingArgs...) {
if output, err := iptables.Raw(append([]string{"-I"}, outgoingArgs...)...); err != nil {
if !iptable.Exists(outgoingArgs...) {
if output, err := iptable.Raw(append([]string{"-I"}, outgoingArgs...)...); err != nil {
return fmt.Errorf("Unable to allow outgoing packets: %s", err)
} else if len(output) != 0 {
return fmt.Errorf("Error iptables allow outgoing: %s", output)
Expand All @@ -243,8 +246,8 @@ func setupIPTables(addr net.Addr, icc, ipmasq bool) error {
// Accept incoming packets for existing connections
existingArgs := []string{"FORWARD", "-o", bridgeIface, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"}

if !iptables.Exists(existingArgs...) {
if output, err := iptables.Raw(append([]string{"-I"}, existingArgs...)...); err != nil {
if !iptable.Exists(existingArgs...) {
if output, err := iptable.Raw(append([]string{"-I"}, existingArgs...)...); err != nil {
return fmt.Errorf("Unable to allow incoming packets: %s", err)
} else if len(output) != 0 {
return fmt.Errorf("Error iptables allow incoming: %s", output)
Expand All @@ -258,7 +261,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(useIpv6 bool, bridgeIP string) error {
nameservers := []string{}
resolvConf, _ := resolvconf.Get()
// we don't check for an error here, because we don't really care
Expand Down Expand Up @@ -315,6 +318,14 @@ func configureBridge(bridgeIP string) error {
return err
}

if useIpv6 {
// 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)
}
}

if netlink.NetworkLinkAddIp(iface, ipAddr, ipNet); err != nil {
return fmt.Errorf("Unable to add private network: %s", err)
}
Expand Down Expand Up @@ -515,10 +526,14 @@ func LinkContainers(job *engine.Job) engine.Status {
parentIP = job.Getenv("ParentIP")
ignoreErrors = job.GetenvBool("IgnoreErrors")
ports = job.GetenvList("Ports")
useIpv6 = job.GetenvBool("UseIpv6")
)

iptable := iptables.GetTable(useIpv6)

for _, value := range ports {
port := nat.Port(value)
if output, err := iptables.Raw(action, "FORWARD",
if output, err := iptable.Raw(action, "FORWARD",
"-i", bridgeIface, "-o", bridgeIface,
"-p", port.Proto(),
"-s", parentIP,
Expand All @@ -530,7 +545,7 @@ func LinkContainers(job *engine.Job) engine.Status {
return job.Errorf("Error toggle iptables forward: %s", output)
}

if output, err := iptables.Raw(action, "FORWARD",
if output, err := iptable.Raw(action, "FORWARD",
"-i", bridgeIface, "-o", bridgeIface,
"-p", port.Proto(),
"-s", childIP,
Expand Down
8 changes: 2 additions & 6 deletions daemon/networkdriver/ipallocator/allocator.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,10 @@ type allocatedMap struct {

func newAllocatedMap(network *net.IPNet) *allocatedMap {
firstIP, lastIP := networkdriver.NetworkRange(network)
begin := big.NewInt(0).Add(ipToBigInt(firstIP), big.NewInt(1))
// Skip the first IP, reserving it for the bridge
begin := big.NewInt(0).Add(ipToBigInt(firstIP), big.NewInt(2))
end := big.NewInt(0).Sub(ipToBigInt(lastIP), big.NewInt(1))

// if IPv4 network, then allocation range starts at begin + 1 because begin is bridge IP
if len(firstIP) == 4 {
begin = begin.Add(begin, big.NewInt(1))
}

return &allocatedMap{
p: make(map[string]struct{}),
begin: begin,
Expand Down
12 changes: 6 additions & 6 deletions daemon/networkdriver/ipallocator/allocator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func TestRequestNewIpV6(t *testing.T) {

var ip net.IP
var err error
for i := 1; i < 10; i++ {
for i := 2; i < 10; i++ {
ip, err = RequestIP(network, nil)
if err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -457,11 +457,11 @@ func TestAllocateDifferentSubnets(t *testing.T) {
1: net.IPv4(192, 168, 0, 3),
2: net.IPv4(127, 0, 0, 2),
3: net.IPv4(127, 0, 0, 3),
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"),
4: net.ParseIP("2a00:1450::2"),
5: net.ParseIP("2a00:1450::3"),
6: net.ParseIP("2a00:1450::4"),
7: net.ParseIP("2a00:1632::2"),
8: net.ParseIP("2a00:1632::3"),
}

ip11, err := RequestIP(network1, nil)
Expand Down
2 changes: 2 additions & 0 deletions daemon/networkdriver/portmapper/mapper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ func reset() {
func TestSetIptablesChain(t *testing.T) {
defer reset()

table := iptables.GetTable(false)
c := &iptables.Chain{
Table: table,
Name: "TEST",
Bridge: "192.168.1.1",
}
Expand Down
29 changes: 20 additions & 9 deletions daemon/networkdriver/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ 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, ipv6 bool) (net.Addr, error) {
iface, err := net.InterfaceByName(name)
if err != nil {
return nil, err
Expand All @@ -83,20 +83,31 @@ func GetIfaceAddr(name string) (net.Addr, error) {
return 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 {
isIpv4 := ip.To4() != nil
if isIpv4 {
addrs4 = append(addrs4, addr)
} else {
addrs6 = append(addrs6, addr)
}
}
switch {
case len(addrs4) == 0:
return nil, fmt.Errorf("Interface %v has no IP 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)
if !ipv6 && len(addrs4) != 0 {
if 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
}
if ipv6 && len(addrs6) != 0 {
if len(addrs6) > 1 {
fmt.Printf("Interface %v has more than 1 IPv6 address. Defaulting to using %v\n",
name, (addrs6[0].(*net.IPNet)).IP)
}
return addrs6[0], nil
}
return addrs4[0], nil
return nil, fmt.Errorf("Interface %v has no IP addresses", name)
}

func GetDefaultRouteIface() (*net.Interface, error) {
Expand Down
10 changes: 8 additions & 2 deletions integration-cli/docker_cli_links_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ func TestLinksPingLinkedContainers(t *testing.T) {
logDone("links - ping linked container")
}

func isIpv6(ip string) bool {
return strings.Contains(ip, ":")
}

func TestLinksIpTablesRulesWhenLinkAndUnlink(t *testing.T) {
cmd(t, "run", "-d", "--name", "child", "--publish", "8080:80", "busybox", "sleep", "10")
cmd(t, "run", "-d", "--name", "parent", "--link", "child:http", "busybox", "sleep", "10")
Expand All @@ -83,12 +87,14 @@ func TestLinksIpTablesRulesWhenLinkAndUnlink(t *testing.T) {

sourceRule := []string{"FORWARD", "-i", "docker0", "-o", "docker0", "-p", "tcp", "-s", childIP, "--sport", "80", "-d", parentIP, "-j", "ACCEPT"}
destinationRule := []string{"FORWARD", "-i", "docker0", "-o", "docker0", "-p", "tcp", "-s", parentIP, "--dport", "80", "-d", childIP, "-j", "ACCEPT"}
if !iptables.Exists(sourceRule...) || !iptables.Exists(destinationRule...) {

iptable := iptables.GetTable(isIpv6(childIP))
if !iptable.Exists(sourceRule...) || !iptable.Exists(destinationRule...) {
t.Fatal("Iptables rules not found")
}

cmd(t, "rm", "--link", "parent/http")
if iptables.Exists(sourceRule...) || iptables.Exists(destinationRule...) {
if iptable.Exists(sourceRule...) || iptable.Exists(destinationRule...) {
t.Fatal("Iptables rules should be removed when unlink")
}

Expand Down