diff options
-rw-r--r-- | cmd/rootlessport/main.go | 24 | ||||
-rw-r--r-- | cmd/rootlessport/wsl.go | 37 | ||||
-rw-r--r-- | cmd/rootlessport/wsl_test.go | 89 |
3 files changed, 145 insertions, 5 deletions
diff --git a/cmd/rootlessport/main.go b/cmd/rootlessport/main.go index e9ab8b076..5bd35a985 100644 --- a/cmd/rootlessport/main.go +++ b/cmd/rootlessport/main.go @@ -1,3 +1,6 @@ +//go:build linux +// +build linux + package main import ( @@ -307,11 +310,11 @@ func exposePorts(pm rkport.Manager, portMappings []types.PortMapping, childIP st ChildPort: int(port.ContainerPort + i), ChildIP: childIP, } - if err := rkportutil.ValidatePortSpec(spec, nil); err != nil { - return err - } - if _, err := pm.AddPort(ctx, spec); err != nil { - return err + + for _, spec = range splitDualStackSpecIfWsl(spec) { + if err := validateAndAddPort(ctx, pm, spec); err != nil { + return err + } } } } @@ -319,6 +322,17 @@ func exposePorts(pm rkport.Manager, portMappings []types.PortMapping, childIP st return nil } +func validateAndAddPort(ctx context.Context, pm rkport.Manager, spec rkport.Spec) error { + if err := rkportutil.ValidatePortSpec(spec, nil); err != nil { + return err + } + if _, err := pm.AddPort(ctx, spec); err != nil { + return err + } + + return nil +} + func child() error { // load the config from the parent var opaque map[string]string diff --git a/cmd/rootlessport/wsl.go b/cmd/rootlessport/wsl.go new file mode 100644 index 000000000..c1e67ba87 --- /dev/null +++ b/cmd/rootlessport/wsl.go @@ -0,0 +1,37 @@ +package main + +import ( + "net" + "strings" + + "github.com/containers/common/pkg/machine" + rkport "github.com/rootless-containers/rootlesskit/pkg/port" +) + +// WSL machines do not relay ipv4 traffic to dual-stack ports, simulate instead +func splitDualStackSpecIfWsl(spec rkport.Spec) []rkport.Spec { + specs := []rkport.Spec{spec} + protocol := spec.Proto + if machine.MachineHostType() != machine.Wsl || strings.HasSuffix(protocol, "4") || strings.HasSuffix(protocol, "6") { + return specs + } + + ip := net.ParseIP(spec.ParentIP) + splitLoopback := ip.IsLoopback() && ip.To4() == nil + // Map ::1 and 0.0.0.0/:: to ipv4 + ipv6 to simulate dual-stack + if ip.IsUnspecified() || splitLoopback { + specs = append(specs, spec) + specs[0].Proto = protocol + "4" + specs[1].Proto = protocol + "6" + if splitLoopback { + // Hacky, but we will only have one ipv4 loopback with WSL config + specs[0].ParentIP = "127.0.0.1" + } + if ip.IsUnspecified() { + specs[0].ParentIP = "0.0.0.0" + specs[1].ParentIP = "::" + } + } + + return specs +} diff --git a/cmd/rootlessport/wsl_test.go b/cmd/rootlessport/wsl_test.go new file mode 100644 index 000000000..83d7e3717 --- /dev/null +++ b/cmd/rootlessport/wsl_test.go @@ -0,0 +1,89 @@ +package main + +import ( + "testing" + + "github.com/containers/common/pkg/machine" + "github.com/rootless-containers/rootlesskit/pkg/port" + "github.com/stretchr/testify/assert" +) + +type SpecData struct { + mach string + sourceProto string + sourceIP string + expectCount int + expectProto string + expectIP string + secondProto string + secondIP string +} + +func TestDualStackSplit(t *testing.T) { + //nolint + const ( + IP4_ALL = "0.0.0.0" + IP4__LO = "127.0.0.1" + IP6_ALL = "::" + IP6__LO = "::1" + TCP_ = "tcp" + TCP4 = "tcp4" + TCP6 = "tcp6" + WSL = "wsl" + ___ = "" + IP6_REG = "2001:0db8:85a3:0000:0000:8a2e:0370:7334" + IP4_REG = "10.0.0.1" + ) + + tests := []SpecData{ + // Split cases + {WSL, TCP_, IP4_ALL, 2, TCP4, IP4_ALL, TCP6, IP6_ALL}, + {WSL, TCP_, IP6_ALL, 2, TCP4, IP4_ALL, TCP6, IP6_ALL}, + {WSL, TCP_, IP6__LO, 2, TCP4, IP4__LO, TCP6, IP6__LO}, + + // Non-Split + {WSL, TCP_, IP4__LO, 1, TCP_, IP4__LO, "", ""}, + {WSL, TCP4, IP4_ALL, 1, TCP4, IP4_ALL, "", ""}, + {WSL, TCP6, IP6__LO, 1, TCP6, IP6__LO, "", ""}, + {WSL, TCP_, IP4_REG, 1, TCP_, IP4_REG, "", ""}, + {WSL, TCP_, IP6_REG, 1, TCP_, IP6_REG, "", ""}, + {___, TCP_, IP4_ALL, 1, TCP_, IP4_ALL, "", ""}, + {___, TCP_, IP6_ALL, 1, TCP_, IP6_ALL, "", ""}, + {___, TCP_, IP4__LO, 1, TCP_, IP4__LO, "", ""}, + {___, TCP_, IP6__LO, 1, TCP_, IP6__LO, "", ""}, + } + + for _, data := range tests { + verifySplit(t, data) + } +} + +func verifySplit(t *testing.T, data SpecData) { + machine := machine.GetMachineMarker() + oldEnable, oldType := machine.Enabled, machine.Type + machine.Enabled, machine.Type = len(data.mach) > 0, data.mach + + source := port.Spec{ + Proto: data.sourceProto, + ParentIP: data.sourceIP, + ParentPort: 100, + ChildIP: "1.1.1.1", + ChildPort: 200, + } + expect, second := source, source + specs := splitDualStackSpecIfWsl(source) + + assert.Equal(t, data.expectCount, len(specs)) + + expect.Proto = data.expectProto + expect.ParentIP = data.expectIP + assert.Equal(t, expect, specs[0]) + + if data.expectCount > 1 { + second.Proto = data.secondProto + second.ParentIP = data.secondIP + assert.Equal(t, second, specs[1]) + } + + machine.Enabled, machine.Type = oldEnable, oldType +} |