github.com/rawahars/moby@v24.0.4+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. If hairpin is disabled or if we're tearing down 248 // that bridge, make sure the iptables rule isn't lying around. 249 if err := programChainRule(ipVersion, hpNatRule, "MASQ LOCAL HOST", enable && hairpin); err != nil { 250 return err 251 } 252 253 // Set Inter Container Communication. 254 if err := setIcc(ipVersion, bridgeIface, icc, enable); err != nil { 255 return err 256 } 257 258 // Set Accept on all non-intercontainer outgoing packets. 259 return programChainRule(ipVersion, outRule, "ACCEPT NON_ICC OUTGOING", enable) 260 } 261 262 func programChainRule(version iptables.IPVersion, rule iptRule, ruleDescr string, insert bool) error { 263 264 iptable := iptables.GetIptable(version) 265 266 var ( 267 prefix []string 268 operation string 269 condition bool 270 doesExist = iptable.Exists(rule.table, rule.chain, rule.args...) 271 ) 272 273 if insert { 274 condition = !doesExist 275 prefix = []string{"-I", rule.chain} 276 operation = "enable" 277 } else { 278 condition = doesExist 279 prefix = []string{"-D", rule.chain} 280 operation = "disable" 281 } 282 if rule.preArgs != nil { 283 prefix = append(rule.preArgs, prefix...) 284 } 285 286 if condition { 287 if err := iptable.RawCombinedOutput(append(prefix, rule.args...)...); err != nil { 288 return fmt.Errorf("Unable to %s %s rule: %s", operation, ruleDescr, err.Error()) 289 } 290 } 291 292 return nil 293 } 294 295 func setIcc(version iptables.IPVersion, bridgeIface string, iccEnable, insert bool) error { 296 iptable := iptables.GetIptable(version) 297 var ( 298 table = iptables.Filter 299 chain = "FORWARD" 300 args = []string{"-i", bridgeIface, "-o", bridgeIface, "-j"} 301 acceptArgs = append(args, "ACCEPT") 302 dropArgs = append(args, "DROP") 303 ) 304 305 if insert { 306 if !iccEnable { 307 iptable.Raw(append([]string{"-D", chain}, acceptArgs...)...) 308 309 if !iptable.Exists(table, chain, dropArgs...) { 310 if err := iptable.RawCombinedOutput(append([]string{"-A", chain}, dropArgs...)...); err != nil { 311 return fmt.Errorf("Unable to prevent intercontainer communication: %s", err.Error()) 312 } 313 } 314 } else { 315 iptable.Raw(append([]string{"-D", chain}, dropArgs...)...) 316 317 if !iptable.Exists(table, chain, acceptArgs...) { 318 if err := iptable.RawCombinedOutput(append([]string{"-I", chain}, acceptArgs...)...); err != nil { 319 return fmt.Errorf("Unable to allow intercontainer communication: %s", err.Error()) 320 } 321 } 322 } 323 } else { 324 // Remove any ICC rule. 325 if !iccEnable { 326 if iptable.Exists(table, chain, dropArgs...) { 327 iptable.Raw(append([]string{"-D", chain}, dropArgs...)...) 328 } 329 } else { 330 if iptable.Exists(table, chain, acceptArgs...) { 331 iptable.Raw(append([]string{"-D", chain}, acceptArgs...)...) 332 } 333 } 334 } 335 336 return nil 337 } 338 339 // Control Inter Network Communication. Install[Remove] only if it is [not] present. 340 func setINC(version iptables.IPVersion, iface string, enable bool) error { 341 iptable := iptables.GetIptable(version) 342 var ( 343 action = iptables.Insert 344 actionMsg = "add" 345 chains = []string{IsolationChain1, IsolationChain2} 346 rules = [][]string{ 347 {"-i", iface, "!", "-o", iface, "-j", IsolationChain2}, 348 {"-o", iface, "-j", "DROP"}, 349 } 350 ) 351 352 if !enable { 353 action = iptables.Delete 354 actionMsg = "remove" 355 } 356 357 for i, chain := range chains { 358 if err := iptable.ProgramRule(iptables.Filter, chain, action, rules[i]); err != nil { 359 msg := fmt.Sprintf("unable to %s inter-network communication rule: %v", actionMsg, err) 360 if enable { 361 if i == 1 { 362 // Rollback the rule installed on first chain 363 if err2 := iptable.ProgramRule(iptables.Filter, chains[0], iptables.Delete, rules[0]); err2 != nil { 364 logrus.Warnf("Failed to rollback iptables rule after failure (%v): %v", err, err2) 365 } 366 } 367 return fmt.Errorf(msg) 368 } 369 logrus.Warn(msg) 370 } 371 } 372 373 return nil 374 } 375 376 // Obsolete chain from previous docker versions 377 const oldIsolationChain = "DOCKER-ISOLATION" 378 379 func removeIPChains(version iptables.IPVersion) { 380 ipt := iptables.IPTable{Version: version} 381 382 // Remove obsolete rules from default chains 383 ipt.ProgramRule(iptables.Filter, "FORWARD", iptables.Delete, []string{"-j", oldIsolationChain}) 384 385 // Remove chains 386 for _, chainInfo := range []iptables.ChainInfo{ 387 {Name: DockerChain, Table: iptables.Nat, IPTable: ipt}, 388 {Name: DockerChain, Table: iptables.Filter, IPTable: ipt}, 389 {Name: IsolationChain1, Table: iptables.Filter, IPTable: ipt}, 390 {Name: IsolationChain2, Table: iptables.Filter, IPTable: ipt}, 391 {Name: oldIsolationChain, Table: iptables.Filter, IPTable: ipt}, 392 } { 393 if err := chainInfo.Remove(); err != nil { 394 logrus.Warnf("Failed to remove existing iptables entries in table %s chain %s : %v", chainInfo.Table, chainInfo.Name, err) 395 } 396 } 397 } 398 399 func setupInternalNetworkRules(bridgeIface string, addr *net.IPNet, icc, insert bool) error { 400 var ( 401 inDropRule = iptRule{table: iptables.Filter, chain: IsolationChain1, args: []string{"-i", bridgeIface, "!", "-d", addr.String(), "-j", "DROP"}} 402 outDropRule = iptRule{table: iptables.Filter, chain: IsolationChain1, args: []string{"-o", bridgeIface, "!", "-s", addr.String(), "-j", "DROP"}} 403 ) 404 405 version := iptables.IPv4 406 407 if addr.IP.To4() == nil { 408 version = iptables.IPv6 409 } 410 411 if err := programChainRule(version, inDropRule, "DROP INCOMING", insert); err != nil { 412 return err 413 } 414 if err := programChainRule(version, outDropRule, "DROP OUTGOING", insert); err != nil { 415 return err 416 } 417 // Set Inter Container Communication. 418 return setIcc(version, bridgeIface, icc, insert) 419 } 420 421 // clearConntrackEntries flushes conntrack entries matching endpoint IP address 422 // or matching one of the exposed UDP port. 423 // In the first case, this could happen if packets were received by the host 424 // between userland proxy startup and iptables setup. 425 // In the latter case, this could happen if packets were received whereas there 426 // were nowhere to route them, as netfilter creates entries in such case. 427 // This is required because iptables NAT rules are evaluated by netfilter only 428 // when creating a new conntrack entry. When Docker latter adds NAT rules, 429 // netfilter ignore them for any packet matching a pre-existing conntrack entry. 430 // As such, we need to flush all those conntrack entries to make sure NAT rules 431 // are correctly applied to all packets. 432 // See: #8795, #44688 & #44742. 433 func clearConntrackEntries(nlh *netlink.Handle, ep *bridgeEndpoint) { 434 var ipv4List []net.IP 435 var ipv6List []net.IP 436 var udpPorts []uint16 437 438 if ep.addr != nil { 439 ipv4List = append(ipv4List, ep.addr.IP) 440 } 441 if ep.addrv6 != nil { 442 ipv6List = append(ipv6List, ep.addrv6.IP) 443 } 444 for _, pb := range ep.portMapping { 445 if pb.Proto == types.UDP { 446 udpPorts = append(udpPorts, pb.HostPort) 447 } 448 } 449 450 iptables.DeleteConntrackEntries(nlh, ipv4List, ipv6List) 451 iptables.DeleteConntrackEntriesByPort(nlh, types.UDP, udpPorts) 452 }