|
5 | 5 | "errors"
|
6 | 6 | "fmt"
|
7 | 7 | "net"
|
| 8 | + "os" |
8 | 9 | "strings"
|
9 | 10 |
|
10 | 11 | "github.com/containerd/log"
|
@@ -32,6 +33,11 @@ const (
|
32 | 33 | IsolationChain2 = "DOCKER-ISOLATION-STAGE-2"
|
33 | 34 | )
|
34 | 35 |
|
| 36 | +// Path to the executable installed in Linux under WSL2 that reports on |
| 37 | +// WSL config. https://github.com/microsoft/WSL/releases/tag/2.0.4 |
| 38 | +// Can be modified by tests. |
| 39 | +var wslinfoPath = "/usr/bin/wslinfo" |
| 40 | + |
35 | 41 | func setupIPChains(config configuration, version iptables.IPVersion) (natChain *iptables.ChainInfo, filterChain *iptables.ChainInfo, isolationChain1 *iptables.ChainInfo, isolationChain2 *iptables.ChainInfo, retErr error) {
|
36 | 42 | // Sanity check.
|
37 | 43 | if version == iptables.IPv4 && !config.EnableIPTables {
|
@@ -99,6 +105,10 @@ func setupIPChains(config configuration, version iptables.IPVersion) (natChain *
|
99 | 105 | return nil, nil, nil, nil, err
|
100 | 106 | }
|
101 | 107 |
|
| 108 | + if err := mirroredWSL2Workaround(config, version); err != nil { |
| 109 | + return nil, nil, nil, nil, err |
| 110 | + } |
| 111 | + |
102 | 112 | return natChain, filterChain, isolationChain1, isolationChain2, nil
|
103 | 113 | }
|
104 | 114 |
|
@@ -502,3 +512,81 @@ func clearConntrackEntries(nlh *netlink.Handle, ep *bridgeEndpoint) {
|
502 | 512 | iptables.DeleteConntrackEntries(nlh, ipv4List, ipv6List)
|
503 | 513 | iptables.DeleteConntrackEntriesByPort(nlh, types.UDP, udpPorts)
|
504 | 514 | }
|
| 515 | + |
| 516 | +// mirroredWSL2Workaround adds or removes an IPv4 NAT rule, depending on whether |
| 517 | +// docker's host Linux appears to be a guest running under WSL2 in with mirrored |
| 518 | +// mode networking. |
| 519 | +// https://learn.microsoft.com/en-us/windows/wsl/networking#mirrored-mode-networking |
| 520 | +// |
| 521 | +// Without mirrored mode networking, or for a packet sent from Linux, packets |
| 522 | +// sent to 127.0.0.1 are processed as outgoing - they hit the nat-OUTPUT chain, |
| 523 | +// which does not jump to the nat-DOCKER chain because the rule has an exception |
| 524 | +// for "-d 127.0.0.0/8". The default action on the nat-OUTPUT chain is ACCEPT (by |
| 525 | +// default), so the packet is delivered to 127.0.0.1 on lo, where docker-proxy |
| 526 | +// picks it up and acts as a man-in-the-middle; it receives the packet and |
| 527 | +// re-sends it to the container (or acks a SYN and sets up a second TCP |
| 528 | +// connection to the container). So, the container sees packets arrive with a |
| 529 | +// source address belonging to the network's bridge, and it is able to reply to |
| 530 | +// that address. |
| 531 | +// |
| 532 | +// In WSL2's mirrored networking mode, Linux has a loopback0 device as well as lo |
| 533 | +// (which owns 127.0.0.1 as normal). Packets sent to 127.0.0.1 from Windows to a |
| 534 | +// server listening on Linux's 127.0.0.1 are delivered via loopback0, and |
| 535 | +// processed as packets arriving from outside the Linux host (which they are). |
| 536 | +// |
| 537 | +// So, these packets hit the nat-PREROUTING chain instead of nat-OUTPUT. It would |
| 538 | +// normally be impossible for a packet ->127.0.0.1 to arrive from outside the |
| 539 | +// host, so the nat-PREROUTING jump to nat-DOCKER has no exception for it. The |
| 540 | +// packet is processed by a per-bridge DNAT rule in that chain, so it is |
| 541 | +// delivered directly to the container (not via docker-proxy) with source address |
| 542 | +// 127.0.0.1, so the container can't respond. |
| 543 | +// |
| 544 | +// DNAT is normally skipped by RETURN rules in the nat-DOCKER chain for packets |
| 545 | +// arriving from any other bridge network. Similarly, this function adds (or |
| 546 | +// removes) a rule to RETURN early for packets delivered via loopback0 with |
| 547 | +// destination 127.0.0.0/8. |
| 548 | +func mirroredWSL2Workaround(config configuration, ipv iptables.IPVersion) error { |
| 549 | + // WSL2 does not (currently) support Windows<->Linux communication via ::1. |
| 550 | + if ipv != iptables.IPv4 { |
| 551 | + return nil |
| 552 | + } |
| 553 | + return programChainRule(mirroredWSL2Rule(), "WSL2 loopback", insertMirroredWSL2Rule(config)) |
| 554 | +} |
| 555 | + |
| 556 | +// insertMirroredWSL2Rule returns true if the NAT rule for mirrored WSL2 workaround |
| 557 | +// is required. It is required if: |
| 558 | +// - the userland proxy is running. If not, there's nothing on the host to catch |
| 559 | +// the packet, so the loopback0 rule as wouldn't be useful. However, without |
| 560 | +// the workaround, with improvements in WSL2 v2.3.11, and without userland proxy |
| 561 | +// running - no workaround is needed, the normal DNAT/masquerading works. |
| 562 | +// - and, the host Linux appears to be running under Windows WSL2 with mirrored |
| 563 | +// mode networking. If a loopback0 device exists, and there's an executable at |
| 564 | +// /usr/bin/wslinfo, infer that this is WSL2 with mirrored networking. ("wslinfo |
| 565 | +// --networking-mode" reports "mirrored", but applying the workaround for WSL2's |
| 566 | +// loopback device when it's not needed is low risk, compared with executing |
| 567 | +// wslinfo with dockerd's elevated permissions.) |
| 568 | +func insertMirroredWSL2Rule(config configuration) bool { |
| 569 | + if !config.EnableUserlandProxy || config.UserlandProxyPath == "" { |
| 570 | + return false |
| 571 | + } |
| 572 | + if _, err := netlink.LinkByName("loopback0"); err != nil { |
| 573 | + if !errors.As(err, &netlink.LinkNotFoundError{}) { |
| 574 | + log.G(context.TODO()).WithError(err).Warn("Failed to check for WSL interface") |
| 575 | + } |
| 576 | + return false |
| 577 | + } |
| 578 | + stat, err := os.Stat(wslinfoPath) |
| 579 | + if err != nil { |
| 580 | + return false |
| 581 | + } |
| 582 | + return stat.Mode().IsRegular() && (stat.Mode().Perm()&0111) != 0 |
| 583 | +} |
| 584 | + |
| 585 | +func mirroredWSL2Rule() iptRule { |
| 586 | + return iptRule{ |
| 587 | + ipv: iptables.IPv4, |
| 588 | + table: iptables.Nat, |
| 589 | + chain: DockerChain, |
| 590 | + args: []string{"-i", "loopback0", "-d", "127.0.0.0/8", "-j", "RETURN"}, |
| 591 | + } |
| 592 | +} |
0 commit comments