github.com/rumpl/bof@v23.0.0-rc.2+incompatible/libnetwork/drivers/bridge/setup_ip_tables.go (about) 1 //go:build linux 2 // +build linux 3 4 package bridge 5 6 import ( 7 "errors" 8 "fmt" 9 "net" 10 11 "github.com/docker/docker/libnetwork/iptables" 12 "github.com/docker/docker/libnetwork/types" 13 "github.com/sirupsen/logrus" 14 "github.com/vishvananda/netlink" 15 ) 16 17 // DockerChain: DOCKER iptable chain name 18 const ( 19 DockerChain = "DOCKER" 20 // Isolation between bridge networks is achieved in two stages by means 21 // of the following two chains in the filter table. The first chain matches 22 // on the source interface being a bridge network's bridge and the 23 // destination being a different interface. A positive match leads to the 24 // second isolation chain. No match returns to the parent chain. The second 25 // isolation chain matches on destination interface being a bridge network's 26 // bridge. A positive match identifies a packet originated from one bridge 27 // network's bridge destined to another bridge network's bridge and will 28 // result in the packet being dropped. No match returns to the parent chain. 29 IsolationChain1 = "DOCKER-ISOLATION-STAGE-1" 30 IsolationChain2 = "DOCKER-ISOLATION-STAGE-2" 31 ) 32 33 func setupIPChains(config *configuration, version iptables.IPVersion) (*iptables.ChainInfo, *iptables.ChainInfo, *iptables.ChainInfo, *iptables.ChainInfo, error) { 34 // Sanity check. 35 if !config.EnableIPTables { 36 return nil, nil, nil, nil, errors.New("cannot create new chains, EnableIPTable is disabled") 37 } 38 39 hairpinMode := !config.EnableUserlandProxy 40 41 iptable := iptables.GetIptable(version) 42 43 natChain, err := iptable.NewChain(DockerChain, iptables.Nat, hairpinMode) 44 if err != nil { 45 return nil, nil, nil, nil, fmt.Errorf("failed to create NAT chain %s: %v", DockerChain, err) 46 } 47 defer func() { 48 if err != nil { 49 if err := iptable.RemoveExistingChain(DockerChain, iptables.Nat); err != nil { 50 logrus.Warnf("failed on removing iptables NAT chain %s on cleanup: %v", DockerChain, err) 51 } 52 } 53 }() 54 55 filterChain, err := iptable.NewChain(DockerChain, iptables.Filter, false) 56 if err != nil { 57 return nil, nil, nil, nil, fmt.Errorf("failed to create FILTER chain %s: %v", DockerChain, err) 58 } 59 defer func() { 60 if err != nil { 61 if err := iptable.RemoveExistingChain(DockerChain, iptables.Filter); err != nil { 62 logrus.Warnf("failed on removing iptables FILTER chain %s on cleanup: %v", DockerChain, err) 63 } 64 } 65 }() 66 67 isolationChain1, err := iptable.NewChain(IsolationChain1, iptables.Filter, false) 68 if err != nil { 69 return nil, nil, nil, nil, fmt.Errorf("failed to create FILTER isolation chain: %v", err) 70 } 71 defer func() { 72 if err != nil { 73 if err := iptable.RemoveExistingChain(IsolationChain1, iptables.Filter); err != nil { 74 logrus.Warnf("failed on removing iptables FILTER chain %s on cleanup: %v", IsolationChain1, err) 75 } 76 } 77 }() 78 79 isolationChain2, err := iptable.NewChain(IsolationChain2, iptables.Filter, false) 80 if err != nil { 81 return nil, nil, nil, nil, fmt.Errorf("failed to create FILTER isolation chain: %v", err) 82 } 83 defer func() { 84 if err != nil { 85 if err := iptable.RemoveExistingChain(IsolationChain2, iptables.Filter); err != nil { 86 logrus.Warnf("failed on removing iptables FILTER chain %s on cleanup: %v", IsolationChain2, err) 87 } 88 } 89 }() 90 91 if err := iptable.AddReturnRule(IsolationChain1); err != nil { 92 return nil, nil, nil, nil, err 93 } 94 95 if err := iptable.AddReturnRule(IsolationChain2); err != nil { 96 return nil, nil, nil, nil, err 97 } 98 99 return natChain, filterChain, isolationChain1, isolationChain2, nil 100 } 101 102 func (n *bridgeNetwork) setupIP4Tables(config *networkConfiguration, i *bridgeInterface) error { 103 d := n.driver 104 d.Lock() 105 driverConfig := d.config 106 d.Unlock() 107 108 // Sanity check. 109 if !driverConfig.EnableIPTables { 110 return errors.New("Cannot program chains, EnableIPTable is disabled") 111 } 112 113 maskedAddrv4 := &net.IPNet{ 114 IP: i.bridgeIPv4.IP.Mask(i.bridgeIPv4.Mask), 115 Mask: i.bridgeIPv4.Mask, 116 } 117 return n.setupIPTables(iptables.IPv4, maskedAddrv4, config, i) 118 } 119 120 func (n *bridgeNetwork) setupIP6Tables(config *networkConfiguration, i *bridgeInterface) error { 121 d := n.driver 122 d.Lock() 123 driverConfig := d.config 124 d.Unlock() 125 126 // Sanity check. 127 if !driverConfig.EnableIP6Tables { 128 return errors.New("Cannot program chains, EnableIP6Tables is disabled") 129 } 130 131 maskedAddrv6 := &net.IPNet{ 132 IP: i.bridgeIPv6.IP.Mask(i.bridgeIPv6.Mask), 133 Mask: i.bridgeIPv6.Mask, 134 } 135 136 return n.setupIPTables(iptables.IPv6, maskedAddrv6, config, i) 137 } 138 139 func (n *bridgeNetwork) setupIPTables(ipVersion iptables.IPVersion, maskedAddr *net.IPNet, config *networkConfiguration, i *bridgeInterface) error { 140 var err error 141 142 d := n.driver 143 d.Lock() 144 driverConfig := d.config 145 d.Unlock() 146 147 // Pickup this configuration option from driver 148 hairpinMode := !driverConfig.EnableUserlandProxy 149 150 iptable := iptables.GetIptable(ipVersion) 151 152 if config.Internal { 153 if err = setupInternalNetworkRules(config.BridgeName, maskedAddr, config.EnableICC, true); err != nil { 154 return fmt.Errorf("Failed to Setup IP tables: %s", err.Error()) 155 } 156 n.registerIptCleanFunc(func() error { 157 return setupInternalNetworkRules(config.BridgeName, maskedAddr, config.EnableICC, false) 158 }) 159 } else { 160 if err = setupIPTablesInternal(config.HostIP, config.BridgeName, maskedAddr, config.EnableICC, config.EnableIPMasquerade, hairpinMode, true); err != nil { 161 return fmt.Errorf("Failed to Setup IP tables: %s", err.Error()) 162 } 163 n.registerIptCleanFunc(func() error { 164 return setupIPTablesInternal(config.HostIP, config.BridgeName, maskedAddr, config.EnableICC, config.EnableIPMasquerade, hairpinMode, false) 165 }) 166 natChain, filterChain, _, _, err := n.getDriverChains(ipVersion) 167 if err != nil { 168 return fmt.Errorf("Failed to setup IP tables, cannot acquire chain info %s", err.Error()) 169 } 170 171 err = iptable.ProgramChain(natChain, config.BridgeName, hairpinMode, true) 172 if err != nil { 173 return fmt.Errorf("Failed to program NAT chain: %s", err.Error()) 174 } 175 176 err = iptable.ProgramChain(filterChain, config.BridgeName, hairpinMode, true) 177 if err != nil { 178 return fmt.Errorf("Failed to program FILTER chain: %s", err.Error()) 179 } 180 181 n.registerIptCleanFunc(func() error { 182 return iptable.ProgramChain(filterChain, config.BridgeName, hairpinMode, false) 183 }) 184 185 if ipVersion == iptables.IPv4 { 186 n.portMapper.SetIptablesChain(natChain, n.getNetworkBridgeName()) 187 } else { 188 n.portMapperV6.SetIptablesChain(natChain, n.getNetworkBridgeName()) 189 } 190 } 191 192 d.Lock() 193 err = iptable.EnsureJumpRule("FORWARD", IsolationChain1) 194 d.Unlock() 195 return err 196 } 197 198 type iptRule struct { 199 table iptables.Table 200 chain string 201 preArgs []string 202 args []string 203 } 204 205 func setupIPTablesInternal(hostIP net.IP, bridgeIface string, addr *net.IPNet, icc, ipmasq, hairpin, enable bool) error { 206 207 var ( 208 address = addr.String() 209 skipDNAT = iptRule{table: iptables.Nat, chain: DockerChain, preArgs: []string{"-t", "nat"}, args: []string{"-i", bridgeIface, "-j", "RETURN"}} 210 outRule = iptRule{table: iptables.Filter, chain: "FORWARD", args: []string{"-i", bridgeIface, "!", "-o", bridgeIface, "-j", "ACCEPT"}} 211 natArgs []string 212 hpNatArgs []string 213 ) 214 // if hostIP is set use this address as the src-ip during SNAT 215 if hostIP != nil { 216 hostAddr := hostIP.String() 217 natArgs = []string{"-s", address, "!", "-o", bridgeIface, "-j", "SNAT", "--to-source", hostAddr} 218 hpNatArgs = []string{"-m", "addrtype", "--src-type", "LOCAL", "-o", bridgeIface, "-j", "SNAT", "--to-source", hostAddr} 219 // Else use MASQUERADE which picks the src-ip based on NH from the route table 220 } else { 221 natArgs = []string{"-s", address, "!", "-o", bridgeIface, "-j", "MASQUERADE"} 222 hpNatArgs = []string{"-m", "addrtype", "--src-type", "LOCAL", "-o", bridgeIface, "-j", "MASQUERADE"} 223 } 224 225 natRule := iptRule{table: iptables.Nat, chain: "POSTROUTING", preArgs: []string{"-t", "nat"}, args: natArgs} 226 hpNatRule := iptRule{table: iptables.Nat, chain: "POSTROUTING", preArgs: []string{"-t", "nat"}, args: hpNatArgs} 227 228 ipVersion := iptables.IPv4 229 230 if addr.IP.To4() == nil { 231 ipVersion = iptables.IPv6 232 } 233 234 // Set NAT. 235 if ipmasq { 236 if err := programChainRule(ipVersion, natRule, "NAT", enable); err != nil { 237 return err 238 } 239 } 240 241 if ipmasq && !hairpin { 242 if err := programChainRule(ipVersion, skipDNAT, "SKIP DNAT", enable); err != nil { 243 return err 244 } 245 } 246 247 // In hairpin mode, masquerade traffic from localhost 248 if hairpin { 249 if err := programChainRule(ipVersion, hpNatRule, "MASQ LOCAL HOST", enable); err != nil { 250 return err 251 } 252 } 253 254 // Set Inter Container Communication. 255 if err := setIcc(ipVersion, bridgeIface, icc, enable); err != nil { 256 return err 257 } 258 259 // Set Accept on all non-intercontainer outgoing packets. 260 return programChainRule(ipVersion, outRule, "ACCEPT NON_ICC OUTGOING", enable) 261 } 262 263 func programChainRule(version iptables.IPVersion, rule iptRule, ruleDescr string, insert bool) error { 264 265 iptable := iptables.GetIptable(version) 266 267 var ( 268 prefix []string 269 operation string 270 condition bool 271 doesExist = iptable.Exists(rule.table, rule.chain, rule.args...) 272 ) 273 274 if insert { 275 condition = !doesExist 276 prefix = []string{"-I", rule.chain} 277 operation = "enable" 278 } else { 279 condition = doesExist 280 prefix = []string{"-D", rule.chain} 281 operation = "disable" 282 } 283 if rule.preArgs != nil { 284 prefix = append(rule.preArgs, prefix...) 285 } 286 287 if condition { 288 if err := iptable.RawCombinedOutput(append(prefix, rule.args...)...); err != nil { 289 return fmt.Errorf("Unable to %s %s rule: %s", operation, ruleDescr, err.Error()) 290 } 291 } 292 293 return nil 294 } 295 296 func setIcc(version iptables.IPVersion, bridgeIface string, iccEnable, insert bool) error { 297 iptable := iptables.GetIptable(version) 298 var ( 299 table = iptables.Filter 300 chain = "FORWARD" 301 args = []string{"-i", bridgeIface, "-o", bridgeIface, "-j"} 302 acceptArgs = append(args, "ACCEPT") 303 dropArgs = append(args, "DROP") 304 ) 305 306 if insert { 307 if !iccEnable { 308 iptable.Raw(append([]string{"-D", chain}, acceptArgs...)...) 309 310 if !iptable.Exists(table, chain, dropArgs...) { 311 if err := iptable.RawCombinedOutput(append([]string{"-A", chain}, dropArgs...)...); err != nil { 312 return fmt.Errorf("Unable to prevent intercontainer communication: %s", err.Error()) 313 } 314 } 315 } else { 316 iptable.Raw(append([]string{"-D", chain}, dropArgs...)...) 317 318 if !iptable.Exists(table, chain, acceptArgs...) { 319 if err := iptable.RawCombinedOutput(append([]string{"-I", chain}, acceptArgs...)...); err != nil { 320 return fmt.Errorf("Unable to allow intercontainer communication: %s", err.Error()) 321 } 322 } 323 } 324 } else { 325 // Remove any ICC rule. 326 if !iccEnable { 327 if iptable.Exists(table, chain, dropArgs...) { 328 iptable.Raw(append([]string{"-D", chain}, dropArgs...)...) 329 } 330 } else { 331 if iptable.Exists(table, chain, acceptArgs...) { 332 iptable.Raw(append([]string{"-D", chain}, acceptArgs...)...) 333 } 334 } 335 } 336 337 return nil 338 } 339 340 // Control Inter Network Communication. Install[Remove] only if it is [not] present. 341 func setINC(version iptables.IPVersion, iface string, enable bool) error { 342 iptable := iptables.GetIptable(version) 343 var ( 344 action = iptables.Insert 345 actionMsg = "add" 346 chains = []string{IsolationChain1, IsolationChain2} 347 rules = [][]string{ 348 {"-i", iface, "!", "-o", iface, "-j", IsolationChain2}, 349 {"-o", iface, "-j", "DROP"}, 350 } 351 ) 352 353 if !enable { 354 action = iptables.Delete 355 actionMsg = "remove" 356 } 357 358 for i, chain := range chains { 359 if err := iptable.ProgramRule(iptables.Filter, chain, action, rules[i]); err != nil { 360 msg := fmt.Sprintf("unable to %s inter-network communication rule: %v", actionMsg, err) 361 if enable { 362 if i == 1 { 363 // Rollback the rule installed on first chain 364 if err2 := iptable.ProgramRule(iptables.Filter, chains[0], iptables.Delete, rules[0]); err2 != nil { 365 logrus.Warnf("Failed to rollback iptables rule after failure (%v): %v", err, err2) 366 } 367 } 368 return fmt.Errorf(msg) 369 } 370 logrus.Warn(msg) 371 } 372 } 373 374 return nil 375 } 376 377 // Obsolete chain from previous docker versions 378 const oldIsolationChain = "DOCKER-ISOLATION" 379 380 func removeIPChains(version iptables.IPVersion) { 381 ipt := iptables.IPTable{Version: version} 382 383 // Remove obsolete rules from default chains 384 ipt.ProgramRule(iptables.Filter, "FORWARD", iptables.Delete, []string{"-j", oldIsolationChain}) 385 386 // Remove chains 387 for _, chainInfo := range []iptables.ChainInfo{ 388 {Name: DockerChain, Table: iptables.Nat, IPTable: ipt}, 389 {Name: DockerChain, Table: iptables.Filter, IPTable: ipt}, 390 {Name: IsolationChain1, Table: iptables.Filter, IPTable: ipt}, 391 {Name: IsolationChain2, Table: iptables.Filter, IPTable: ipt}, 392 {Name: oldIsolationChain, Table: iptables.Filter, IPTable: ipt}, 393 } { 394 395 if err := chainInfo.Remove(); err != nil { 396 logrus.Warnf("Failed to remove existing iptables entries in table %s chain %s : %v", chainInfo.Table, chainInfo.Name, err) 397 } 398 } 399 } 400 401 func setupInternalNetworkRules(bridgeIface string, addr *net.IPNet, icc, insert bool) error { 402 var ( 403 inDropRule = iptRule{table: iptables.Filter, chain: IsolationChain1, args: []string{"-i", bridgeIface, "!", "-d", addr.String(), "-j", "DROP"}} 404 outDropRule = iptRule{table: iptables.Filter, chain: IsolationChain1, args: []string{"-o", bridgeIface, "!", "-s", addr.String(), "-j", "DROP"}} 405 ) 406 407 version := iptables.IPv4 408 409 if addr.IP.To4() == nil { 410 version = iptables.IPv6 411 } 412 413 if err := programChainRule(version, inDropRule, "DROP INCOMING", insert); err != nil { 414 return err 415 } 416 if err := programChainRule(version, outDropRule, "DROP OUTGOING", insert); err != nil { 417 return err 418 } 419 // Set Inter Container Communication. 420 return setIcc(version, bridgeIface, icc, insert) 421 } 422 423 // clearConntrackEntries flushes conntrack entries matching endpoint IP address 424 // or matching one of the exposed UDP port. 425 // In the first case, this could happen if packets were received by the host 426 // between userland proxy startup and iptables setup. 427 // In the latter case, this could happen if packets were received whereas there 428 // were nowhere to route them, as netfilter creates entries in such case. 429 // This is required because iptables NAT rules are evaluated by netfilter only 430 // when creating a new conntrack entry. When Docker latter adds NAT rules, 431 // netfilter ignore them for any packet matching a pre-existing conntrack entry. 432 // As such, we need to flush all those conntrack entries to make sure NAT rules 433 // are correctly applied to all packets. 434 // See: #8795, #44688 & #44742. 435 func clearConntrackEntries(nlh *netlink.Handle, ep *bridgeEndpoint) { 436 var ipv4List []net.IP 437 var ipv6List []net.IP 438 var udpPorts []uint16 439 440 if ep.addr != nil { 441 ipv4List = append(ipv4List, ep.addr.IP) 442 } 443 if ep.addrv6 != nil { 444 ipv6List = append(ipv6List, ep.addrv6.IP) 445 } 446 for _, pb := range ep.portMapping { 447 if pb.Proto == types.UDP { 448 udpPorts = append(udpPorts, pb.HostPort) 449 } 450 } 451 452 iptables.DeleteConntrackEntries(nlh, ipv4List, ipv6List) 453 iptables.DeleteConntrackEntriesByPort(nlh, types.UDP, udpPorts) 454 }