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