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