feat: change port-forward to opportunistically listen on IPv6 (#15640)

If the local IP address is not explicitly set, previously we assumed 127.0.0.1 (that is, IPv4 only localhost). This PR adds support to opportunistically _also_ listen on IPv6 ::1.
This commit is contained in:
Spike Curtis
2024-11-25 16:33:28 +04:00
committed by GitHub
parent 1cdc3e8921
commit e6506f0679
3 changed files with 169 additions and 67 deletions

View File

@ -67,6 +67,17 @@ func TestPortForward(t *testing.T) {
},
localAddress: []string{"127.0.0.1:5555", "127.0.0.1:6666"},
},
{
name: "TCP-opportunistic-ipv6",
network: "tcp",
flag: []string{"--tcp=5566:%v", "--tcp=6655:%v"},
setupRemote: func(t *testing.T) net.Listener {
l, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err, "create TCP listener")
return l
},
localAddress: []string{"[::1]:5566", "[::1]:6655"},
},
{
name: "UDP",
network: "udp",
@ -82,6 +93,21 @@ func TestPortForward(t *testing.T) {
},
localAddress: []string{"127.0.0.1:7777", "127.0.0.1:8888"},
},
{
name: "UDP-opportunistic-ipv6",
network: "udp",
flag: []string{"--udp=7788:%v", "--udp=8877:%v"},
setupRemote: func(t *testing.T) net.Listener {
addr := net.UDPAddr{
IP: net.ParseIP("127.0.0.1"),
Port: 0,
}
l, err := udp.Listen("udp", &addr)
require.NoError(t, err, "create UDP listener")
return l
},
localAddress: []string{"[::1]:7788", "[::1]:8877"},
},
{
name: "TCPWithAddress",
network: "tcp", flag: []string{"--tcp=10.10.10.99:9999:%v", "--tcp=10.10.10.10:1010:%v"},
@ -295,6 +321,63 @@ func TestPortForward(t *testing.T) {
require.NoError(t, err)
require.Greater(t, updated.LastUsedAt, workspace.LastUsedAt)
})
t.Run("IPv6Busy", func(t *testing.T) {
t.Parallel()
remoteLis, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err, "create TCP listener")
p1 := setupTestListener(t, remoteLis)
// Create a flag that forwards from local 5555 to remote listener port.
flag := fmt.Sprintf("--tcp=5555:%v", p1)
// Launch port-forward in a goroutine so we can start dialing
// the "local" listener.
inv, root := clitest.New(t, "-v", "port-forward", workspace.Name, flag)
clitest.SetupConfig(t, member, root)
pty := ptytest.New(t)
inv.Stdin = pty.Input()
inv.Stdout = pty.Output()
inv.Stderr = pty.Output()
iNet := newInProcNet()
inv.Net = iNet
// listen on port 5555 on IPv6 so it's busy when we try to port forward
busyLis, err := iNet.Listen("tcp", "[::1]:5555")
require.NoError(t, err)
defer busyLis.Close()
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
errC := make(chan error)
go func() {
err := inv.WithContext(ctx).Run()
t.Logf("command complete; err=%s", err.Error())
errC <- err
}()
pty.ExpectMatchContext(ctx, "Ready!")
// Test IPv4 still works
dialCtx, dialCtxCancel := context.WithTimeout(ctx, testutil.WaitShort)
defer dialCtxCancel()
c1, err := iNet.dial(dialCtx, addr{"tcp", "127.0.0.1:5555"})
require.NoError(t, err, "open connection 1 to 'local' listener")
defer c1.Close()
testDial(t, c1)
cancel()
err = <-errC
require.ErrorIs(t, err, context.Canceled)
flushCtx := testutil.Context(t, testutil.WaitShort)
testutil.RequireSendCtx(flushCtx, t, wuTick, dbtime.Now())
_ = testutil.RequireRecvCtx(flushCtx, t, wuFlush)
updated, err := client.Workspace(context.Background(), workspace.ID)
require.NoError(t, err)
require.Greater(t, updated.LastUsedAt, workspace.LastUsedAt)
})
}
// runAgent creates a fake workspace and starts an agent locally for that