Skip to content

Commit ac1e2ea

Browse files
author
Flavio Crisciani
committed
Flush container flows in conntrack (Bug #8795)
Flush all the endpoint flows when the external connectivity is removed. This will prevent issues where if there is a flow in conntrack this will have precedence and will let the packet skip the POSTROUTING chain. Signed-off-by: Flavio Crisciani <flavio.crisciani@docker.com>
1 parent f1cd58a commit ac1e2ea

5 files changed

Lines changed: 118 additions & 4 deletions

File tree

‎drivers/bridge/bridge.go‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1346,6 +1346,13 @@ func (d *driver) RevokeExternalConnectivity(nid, eid string) error {
13461346

13471347
endpoint.portMapping = nil
13481348

1349+
// Clean the connection tracker state of the host for the specific endpoint
1350+
// The host kernel keeps track of the connections (TCP and UDP), so if a new endpoint gets the same IP of
1351+
// this one (that is going down), is possible that some of the packets would not be routed correctly inside
1352+
// the new endpoint
1353+
// Deeper details: https://github.com/docker/docker/issues/8795
1354+
clearEndpointConnections(d.nlh, endpoint)
1355+
13491356
if err = d.storeUpdate(endpoint); err != nil {
13501357
return fmt.Errorf("failed to update bridge endpoint %s to store: %v", endpoint.id[0:7], err)
13511358
}

‎drivers/bridge/setup_ip_tables.go‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/Sirupsen/logrus"
99
"github.com/docker/libnetwork/iptables"
10+
"github.com/vishvananda/netlink"
1011
)
1112

1213
// DockerChain: DOCKER iptable chain name
@@ -348,3 +349,15 @@ func setupInternalNetworkRules(bridgeIface string, addr net.Addr, icc, insert bo
348349
}
349350
return nil
350351
}
352+
353+
func clearEndpointConnections(nlh *netlink.Handle, ep *bridgeEndpoint) {
354+
var ipv4List []net.IP
355+
var ipv6List []net.IP
356+
if ep.addr != nil {
357+
ipv4List = append(ipv4List, ep.addr.IP)
358+
}
359+
if ep.addrv6 != nil {
360+
ipv6List = append(ipv6List, ep.addrv6.IP)
361+
}
362+
iptables.DeleteConntrackEntries(nlh, ipv4List, ipv6List)
363+
}

‎iptables/conntrack.go‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package iptables
2+
3+
import (
4+
"errors"
5+
"net"
6+
"syscall"
7+
8+
"github.com/Sirupsen/logrus"
9+
"github.com/vishvananda/netlink"
10+
)
11+
12+
var (
13+
// ErrConntrackNotConfigurable means that conntrack module is not loaded or does not have the netlink module loaded
14+
ErrConntrackNotConfigurable = errors.New("conntrack is not available")
15+
)
16+
17+
// IsConntrackProgrammable returns true if the handle supports the NETLINK_NETFILTER and the base modules are loaded
18+
func IsConntrackProgrammable(nlh *netlink.Handle) bool {
19+
return nlh.SupportsNetlinkFamily(syscall.NETLINK_NETFILTER)
20+
}
21+
22+
// DeleteConntrackEntries deletes all the conntrack connections on the host for the specified IP
23+
// Returns the number of flows deleted for IPv4, IPv6 else error
24+
func DeleteConntrackEntries(nlh *netlink.Handle, ipv4List []net.IP, ipv6List []net.IP) (uint, uint, error) {
25+
if !IsConntrackProgrammable(nlh) {
26+
return 0, 0, ErrConntrackNotConfigurable
27+
}
28+
29+
var totalIPv4FlowPurged uint
30+
for _, ipAddress := range ipv4List {
31+
flowPurged, err := purgeConntrackState(nlh, syscall.AF_INET, ipAddress)
32+
if err != nil {
33+
logrus.Warnf("Failed to delete conntrack state for %s: %v", ipAddress, err)
34+
continue
35+
}
36+
totalIPv4FlowPurged += flowPurged
37+
}
38+
39+
var totalIPv6FlowPurged uint
40+
for _, ipAddress := range ipv6List {
41+
flowPurged, err := purgeConntrackState(nlh, syscall.AF_INET6, ipAddress)
42+
if err != nil {
43+
logrus.Warnf("Failed to delete conntrack state for %s: %v", ipAddress, err)
44+
continue
45+
}
46+
totalIPv6FlowPurged += flowPurged
47+
}
48+
49+
logrus.Debugf("DeleteConntrackEntries purged ipv4:%d, ipv6:%d", totalIPv4FlowPurged, totalIPv6FlowPurged)
50+
return totalIPv4FlowPurged, totalIPv6FlowPurged, nil
51+
}
52+
53+
func purgeConntrackState(nlh *netlink.Handle, family netlink.InetFamily, ipAddress net.IP) (uint, error) {
54+
filter := &netlink.ConntrackFilter{}
55+
// NOTE: doing the flush using the ipAddress is safe because today there cannot be multiple networks with the same subnet
56+
// so it will not be possible to flush flows that are of other containers
57+
filter.AddIP(netlink.ConntrackNatAnyIP, ipAddress)
58+
return nlh.ConntrackDeleteFilter(netlink.ConntrackTable, family, filter)
59+
}

‎iptables/iptables.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,14 +100,14 @@ func detectIptables() {
100100
supportsCOpt = supportsCOption(mj, mn, mc)
101101
}
102102

103-
func initIptables() {
103+
func initDependencies() {
104104
probe()
105105
initFirewalld()
106106
detectIptables()
107107
}
108108

109109
func initCheck() error {
110-
initOnce.Do(initIptables)
110+
initOnce.Do(initDependencies)
111111

112112
if iptablesPath == "" {
113113
return ErrIptablesNotFound

‎ns/init_linux.go‎

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,28 @@ func NlHandle() *netlink.Handle {
7575

7676
func getSupportedNlFamilies() []int {
7777
fams := []int{syscall.NETLINK_ROUTE}
78+
// NETLINK_XFRM test
7879
if err := loadXfrmModules(); err != nil {
7980
if checkXfrmSocket() != nil {
8081
logrus.Warnf("Could not load necessary modules for IPSEC rules: %v", err)
81-
return fams
82+
} else {
83+
fams = append(fams, syscall.NETLINK_XFRM)
8284
}
85+
} else {
86+
fams = append(fams, syscall.NETLINK_XFRM)
8387
}
84-
return append(fams, syscall.NETLINK_XFRM)
88+
// NETLINK_NETFILTER test
89+
if err := loadNfConntrackModules(); err != nil {
90+
if checkNfSocket() != nil {
91+
logrus.Warnf("Could not load necessary modules for Conntrack: %v", err)
92+
} else {
93+
fams = append(fams, syscall.NETLINK_NETFILTER)
94+
}
95+
} else {
96+
fams = append(fams, syscall.NETLINK_NETFILTER)
97+
}
98+
99+
return fams
85100
}
86101

87102
func loadXfrmModules() error {
@@ -103,3 +118,23 @@ func checkXfrmSocket() error {
103118
syscall.Close(fd)
104119
return nil
105120
}
121+
122+
func loadNfConntrackModules() error {
123+
if out, err := exec.Command("modprobe", "-va", "nf_conntrack").CombinedOutput(); err != nil {
124+
return fmt.Errorf("Running modprobe nf_conntrack failed with message: `%s`, error: %v", strings.TrimSpace(string(out)), err)
125+
}
126+
if out, err := exec.Command("modprobe", "-va", "nf_conntrack_netlink").CombinedOutput(); err != nil {
127+
return fmt.Errorf("Running modprobe nf_conntrack_netlink failed with message: `%s`, error: %v", strings.TrimSpace(string(out)), err)
128+
}
129+
return nil
130+
}
131+
132+
// API check on required nf_conntrack* modules (nf_conntrack, nf_conntrack_netlink)
133+
func checkNfSocket() error {
134+
fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, syscall.NETLINK_NETFILTER)
135+
if err != nil {
136+
return err
137+
}
138+
syscall.Close(fd)
139+
return nil
140+
}

0 commit comments

Comments
 (0)