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