-
Notifications
You must be signed in to change notification settings - Fork 18.9k
Add gateway mode "nat-unprotected" #48597
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| ## Container on a nat-unprotected network, with a published port | ||
|
|
||
| Running the daemon with the userland proxy disable then, as before, adding a network running a container with a mapped port, equivalent to: | ||
|
|
||
| docker network create \ | ||
| -o com.docker.network.bridge.name=bridge1 \ | ||
| -o com.docker.network.bridge.gateway_mode_ipv4=nat-unprotected \ | ||
| --subnet 192.0.2.0/24 --gateway 192.0.2.1 bridge1 | ||
| docker run --network bridge1 -p 8080:80 --name c1 busybox | ||
|
|
||
| The filter table is: | ||
|
|
||
| Chain INPUT (policy ACCEPT 0 packets, 0 bytes) | ||
| num pkts bytes target prot opt in out source destination | ||
|
|
||
| Chain FORWARD (policy ACCEPT 0 packets, 0 bytes) | ||
| num pkts bytes target prot opt in out source destination | ||
| 1 0 0 DOCKER-USER 0 -- * * 0.0.0.0/0 0.0.0.0/0 | ||
| 2 0 0 ACCEPT 0 -- * * 0.0.0.0/0 0.0.0.0/0 match-set docker-ext-bridges-v4 dst ctstate RELATED,ESTABLISHED | ||
| 3 0 0 DOCKER-ISOLATION-STAGE-1 0 -- * * 0.0.0.0/0 0.0.0.0/0 | ||
| 4 0 0 DOCKER 0 -- * * 0.0.0.0/0 0.0.0.0/0 match-set docker-ext-bridges-v4 dst | ||
| 5 0 0 ACCEPT 0 -- bridge1 !bridge1 0.0.0.0/0 0.0.0.0/0 | ||
| 6 0 0 ACCEPT 0 -- bridge1 bridge1 0.0.0.0/0 0.0.0.0/0 | ||
| 7 0 0 ACCEPT 0 -- docker0 !docker0 0.0.0.0/0 0.0.0.0/0 | ||
| 8 0 0 ACCEPT 0 -- docker0 docker0 0.0.0.0/0 0.0.0.0/0 | ||
|
|
||
| Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes) | ||
| num pkts bytes target prot opt in out source destination | ||
|
|
||
| Chain DOCKER (1 references) | ||
| num pkts bytes target prot opt in out source destination | ||
| 1 0 0 DROP 0 -- !docker0 docker0 0.0.0.0/0 0.0.0.0/0 | ||
| 2 0 0 ACCEPT 0 -- !bridge1 bridge1 0.0.0.0/0 0.0.0.0/0 | ||
|
|
||
| Chain DOCKER-ISOLATION-STAGE-1 (1 references) | ||
| num pkts bytes target prot opt in out source destination | ||
| 1 0 0 DOCKER-ISOLATION-STAGE-2 0 -- docker0 !docker0 0.0.0.0/0 0.0.0.0/0 | ||
| 2 0 0 DOCKER-ISOLATION-STAGE-2 0 -- bridge1 !bridge1 0.0.0.0/0 0.0.0.0/0 | ||
|
|
||
| Chain DOCKER-ISOLATION-STAGE-2 (2 references) | ||
| num pkts bytes target prot opt in out source destination | ||
| 1 0 0 DROP 0 -- * bridge1 0.0.0.0/0 0.0.0.0/0 | ||
| 2 0 0 DROP 0 -- * docker0 0.0.0.0/0 0.0.0.0/0 | ||
|
|
||
| Chain DOCKER-USER (1 references) | ||
| num pkts bytes target prot opt in out source destination | ||
| 1 0 0 RETURN 0 -- * * 0.0.0.0/0 0.0.0.0/0 | ||
|
|
||
|
|
||
| <details> | ||
| <summary>iptables commands</summary> | ||
|
|
||
| -P INPUT ACCEPT | ||
| -P FORWARD ACCEPT | ||
| -P OUTPUT ACCEPT | ||
| -N DOCKER | ||
| -N DOCKER-ISOLATION-STAGE-1 | ||
| -N DOCKER-ISOLATION-STAGE-2 | ||
| -N DOCKER-USER | ||
| -A FORWARD -j DOCKER-USER | ||
| -A FORWARD -m set --match-set docker-ext-bridges-v4 dst -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT | ||
| -A FORWARD -j DOCKER-ISOLATION-STAGE-1 | ||
| -A FORWARD -m set --match-set docker-ext-bridges-v4 dst -j DOCKER | ||
| -A FORWARD -i bridge1 ! -o bridge1 -j ACCEPT | ||
| -A FORWARD -i bridge1 -o bridge1 -j ACCEPT | ||
| -A FORWARD -i docker0 ! -o docker0 -j ACCEPT | ||
| -A FORWARD -i docker0 -o docker0 -j ACCEPT | ||
| -A DOCKER ! -i docker0 -o docker0 -j DROP | ||
| -A DOCKER ! -i bridge1 -o bridge1 -j ACCEPT | ||
| -A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2 | ||
| -A DOCKER-ISOLATION-STAGE-1 -i bridge1 ! -o bridge1 -j DOCKER-ISOLATION-STAGE-2 | ||
| -A DOCKER-ISOLATION-STAGE-2 -o bridge1 -j DROP | ||
| -A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP | ||
| -A DOCKER-USER -j RETURN | ||
|
|
||
|
|
||
| </details> | ||
|
|
||
| Differences from [nat mode][400]: | ||
|
|
||
| - In the DOCKER chain: | ||
| - Where `nat` mode appended a default-DROP rule for any packets not accepted | ||
| by the per-port/protocol rules, `nat-unprotected` appends a default-ACCEPT | ||
| rule. [setDefaultForwardRule][402] | ||
| - The ACCEPT rule is needed in case the filter-FORWARD chain's default | ||
| policy is DROP. | ||
| - Because the default for this network is ACCEPT, there is no per-port/protocol | ||
| rule to ACCEPT packets for the published port `80/tcp`, [setPerPortIptables][401] | ||
| doesn't set it up. | ||
| - _If the userland proxy is enabled, it is still started._ | ||
|
|
||
| The nat table is identical to [nat mode][400]. | ||
|
|
||
| <details> | ||
| <summary>nat table</summary> | ||
|
|
||
| Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes) | ||
| num pkts bytes target prot opt in out source destination | ||
| 1 0 0 DOCKER 0 -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL | ||
|
|
||
| Chain INPUT (policy ACCEPT 0 packets, 0 bytes) | ||
| num pkts bytes target prot opt in out source destination | ||
|
|
||
| Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes) | ||
| num pkts bytes target prot opt in out source destination | ||
| 1 0 0 DOCKER 0 -- * * 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL | ||
|
|
||
| Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes) | ||
| num pkts bytes target prot opt in out source destination | ||
| 1 0 0 MASQUERADE 0 -- * !bridge1 192.0.2.0/24 0.0.0.0/0 | ||
| 2 0 0 MASQUERADE 0 -- * !docker0 172.17.0.0/16 0.0.0.0/0 | ||
|
|
||
| Chain DOCKER (2 references) | ||
| num pkts bytes target prot opt in out source destination | ||
| 1 0 0 RETURN 0 -- bridge1 * 0.0.0.0/0 0.0.0.0/0 | ||
| 2 0 0 RETURN 0 -- docker0 * 0.0.0.0/0 0.0.0.0/0 | ||
| 3 0 0 DNAT 6 -- !bridge1 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:192.0.2.2:80 | ||
|
|
||
|
|
||
| -P PREROUTING ACCEPT | ||
| -P INPUT ACCEPT | ||
| -P OUTPUT ACCEPT | ||
| -P POSTROUTING ACCEPT | ||
| -N DOCKER | ||
| -A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER | ||
| -A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER | ||
| -A POSTROUTING -s 192.0.2.0/24 ! -o bridge1 -j MASQUERADE | ||
| -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE | ||
| -A DOCKER -i bridge1 -j RETURN | ||
| -A DOCKER -i docker0 -j RETURN | ||
| -A DOCKER ! -i bridge1 -p tcp -m tcp --dport 8080 -j DNAT --to-destination 192.0.2.2:80 | ||
|
|
||
|
|
||
| </details> | ||
|
|
||
| [400]: usernet-portmap.md | ||
| [401]: https://github.com/robmry/moby/blob/52c89d467fc5326149e4bbb8903d23589b66ff0d/libnetwork/drivers/bridge/port_mapping_linux.go#L747 | ||
| [402]: https://github.com/robmry/moby/blob/52c89d467fc5326149e4bbb8903d23589b66ff0d/libnetwork/drivers/bridge/setup_ip_tables_linux.go#L261-L266 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| ## Container on a nat-unprotected network, with a published port | ||
|
|
||
| Running the daemon with the userland proxy disable then, as before, adding a network running a container with a mapped port, equivalent to: | ||
|
|
||
| docker network create \ | ||
| -o com.docker.network.bridge.name=bridge1 \ | ||
| -o com.docker.network.bridge.gateway_mode_ipv4=nat-unprotected \ | ||
| --subnet 192.0.2.0/24 --gateway 192.0.2.1 bridge1 | ||
| docker run --network bridge1 -p 8080:80 --name c1 busybox | ||
|
|
||
| The filter table is: | ||
|
|
||
| {{index . "LFilter4"}} | ||
|
|
||
| <details> | ||
| <summary>iptables commands</summary> | ||
|
|
||
| {{index . "SFilter4"}} | ||
|
|
||
| </details> | ||
|
|
||
| Differences from [nat mode][400]: | ||
|
|
||
| - In the DOCKER chain: | ||
| - Where `nat` mode appended a default-DROP rule for any packets not accepted | ||
| by the per-port/protocol rules, `nat-unprotected` appends a default-ACCEPT | ||
| rule. [setDefaultForwardRule][402] | ||
| - The ACCEPT rule is needed in case the filter-FORWARD chain's default | ||
| policy is DROP. | ||
| - Because the default for this network is ACCEPT, there is no per-port/protocol | ||
| rule to ACCEPT packets for the published port `80/tcp`, [setPerPortIptables][401] | ||
| doesn't set it up. | ||
| - _If the userland proxy is enabled, it is still started._ | ||
|
|
||
| The nat table is identical to [nat mode][400]. | ||
|
|
||
| <details> | ||
| <summary>nat table</summary> | ||
|
|
||
| {{index . "LNat4"}} | ||
|
|
||
| {{index . "SNat4"}} | ||
|
|
||
| </details> | ||
|
|
||
| [400]: usernet-portmap.md | ||
| [401]: https://github.com/robmry/moby/blob/52c89d467fc5326149e4bbb8903d23589b66ff0d/libnetwork/drivers/bridge/port_mapping_linux.go#L747 | ||
| [402]: https://github.com/robmry/moby/blob/52c89d467fc5326149e4bbb8903d23589b66ff0d/libnetwork/drivers/bridge/setup_ip_tables_linux.go#L261-L266 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -167,9 +167,10 @@ type driver struct { | |
| type gwMode string | ||
|
|
||
| const ( | ||
| gwModeDefault gwMode = "" | ||
| gwModeNAT gwMode = "nat" | ||
| gwModeRouted gwMode = "routed" | ||
| gwModeDefault gwMode = "" | ||
| gwModeNAT gwMode = "nat" | ||
| gwModeNATUnprot gwMode = "nat-unprotected" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we need to make
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Noting our discussion here ... Setting nat-unprotected for existing IPv4 networks would be right if the host had previously been running with the filter-FORWARD chain's policy set to But, when it's set to So, the change to the default |
||
| gwModeRouted gwMode = "routed" | ||
| ) | ||
|
|
||
| // New constructs a new bridge driver | ||
|
|
@@ -366,6 +367,8 @@ func newGwMode(gwMode string) (gwMode, error) { | |
| switch gwMode { | ||
| case "nat": | ||
| return gwModeNAT, nil | ||
| case "nat-unprotected": | ||
| return gwModeNATUnprot, nil | ||
| case "routed": | ||
| return gwModeRouted, nil | ||
| } | ||
|
|
@@ -376,6 +379,10 @@ func (m gwMode) routed() bool { | |
| return m == gwModeRouted | ||
| } | ||
|
|
||
| func (m gwMode) unprotected() bool { | ||
| return m == gwModeNATUnprot | ||
| } | ||
|
|
||
| func parseErr(label, value, errString string) error { | ||
| return types.InvalidParameterErrorf("failed to parse %s value: %v (%s)", label, value, errString) | ||
| } | ||
|
|
@@ -428,6 +435,15 @@ func (n *bridgeNetwork) getNATDisabled() (ipv4, ipv6 bool) { | |
| return n.config.GwModeIPv4.routed(), n.config.GwModeIPv6.routed() | ||
| } | ||
|
|
||
| func (n *bridgeNetwork) gwMode(v iptables.IPVersion) gwMode { | ||
| n.Lock() | ||
| defer n.Unlock() | ||
|
Comment on lines
+439
to
+440
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mostly curious; we have so much locking everywhere; do we have good insight what exactly is it synchronising? (was contemplating if it's purely
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I think it's protecting concurrent access to But, when adding new accessors, I'm sticking with the scheme others us - it needs proper review and tidy-up and, for me, the cost of acquiring the mutex is outweighed by the risk of making it crashy by missing something! |
||
| if v == iptables.IPv4 { | ||
| return n.config.GwModeIPv4 | ||
| } | ||
| return n.config.GwModeIPv6 | ||
| } | ||
|
|
||
| func (n *bridgeNetwork) userlandProxyPath() string { | ||
| n.Lock() | ||
| defer n.Unlock() | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason we need an option to explicitly set this for IPv4/IPv6 is that we changed the default for IPv6 already (but not IPv4 for backward-compat), correct?
(Mostly trying to get my head around what's needed to make the overall experience more aligned in future, and possibly reduce some complexity in codepaths)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Defaults haven't changed ... in 27.0 (#47871) we introduced the IPv4 and IPv6 gateway mode options -
natbecame a new name for the old behaviour, androutedmode was added. The default is stillnatfor both IPv4 and IPv6.For IPv4, NAT is normal, so you might want to publish ports to the host. For IPv6, direct routing is preferred and, if you have the routing set up in your network, you might want to disable NAT. So, they're configurable separately.
In 28.0, we're changing the default behaviour (
natmode) by blocking access to unpublished ports from other hosts on the same layer-2 network that have set up a route to the container network ("direct routing"). Previously, these ports were only blocked if the filter-FORWARD chain's default policy wasDROP. But, docker only sets toDROPif it enables IP forwarding itself.The old behaviour for a host with filter-FORWARD policy
ACCEPTis used as a feature, so we're addingnat-unprotectedas a way to restore the oldnatbehaviour.It's possible to use
nat-unprotectedfor IPv6, but its filter-FORWARD policy was always set toDROPwhenip6tableswas enabled - so the new restrictions innatmode are less likely to be breaking changes.