Skip to content

Commit 5eb41e8

Browse files
authored
feat(cli): allow specifying the listen address in coder port-forward (#7635)
1 parent d413b26 commit 5eb41e8

File tree

4 files changed

+75
-15
lines changed

4 files changed

+75
-15
lines changed

cli/portforward.go

+48-15
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"net"
7+
"net/netip"
78
"os"
89
"os/signal"
910
"strconv"
@@ -47,6 +48,10 @@ func (r *RootCmd) portForward() *clibase.Cmd {
4748
Description: "Port forward multiple ports (TCP or UDP) in condensed syntax",
4849
Command: "coder port-forward <workspace> --tcp 8080,9000:3000,9090-9092,10000-10002:10010-10012",
4950
},
51+
example{
52+
Description: "Port forward specifying the local address to bind to",
53+
Command: "coder port-forward <workspace> --tcp 1.2.3.4:8080:8080",
54+
},
5055
),
5156
Middleware: clibase.Chain(
5257
clibase.RequireNArgs(1),
@@ -255,9 +260,9 @@ func parsePortForwards(tcpSpecs, udpSpecs []string) ([]portForwardSpec, error) {
255260
for _, port := range ports {
256261
specs = append(specs, portForwardSpec{
257262
listenNetwork: "tcp",
258-
listenAddress: fmt.Sprintf("127.0.0.1:%v", port.local),
263+
listenAddress: port.local.String(),
259264
dialNetwork: "tcp",
260-
dialAddress: fmt.Sprintf("127.0.0.1:%v", port.remote),
265+
dialAddress: port.remote.String(),
261266
})
262267
}
263268
}
@@ -273,9 +278,9 @@ func parsePortForwards(tcpSpecs, udpSpecs []string) ([]portForwardSpec, error) {
273278
for _, port := range ports {
274279
specs = append(specs, portForwardSpec{
275280
listenNetwork: "udp",
276-
listenAddress: fmt.Sprintf("127.0.0.1:%v", port.local),
281+
listenAddress: port.local.String(),
277282
dialNetwork: "udp",
278-
dialAddress: fmt.Sprintf("127.0.0.1:%v", port.remote),
283+
dialAddress: port.remote.String(),
279284
})
280285
}
281286
}
@@ -307,29 +312,57 @@ func parsePort(in string) (uint16, error) {
307312
}
308313

309314
type parsedSrcDestPort struct {
310-
local, remote uint16
315+
local, remote netip.AddrPort
311316
}
312317

313318
func parseSrcDestPorts(in string) ([]parsedSrcDestPort, error) {
314-
parts := strings.Split(in, ":")
315-
if len(parts) > 2 {
316-
return nil, xerrors.Errorf("invalid port specification %q", in)
317-
}
318-
if len(parts) == 1 {
319+
var (
320+
err error
321+
parts = strings.Split(in, ":")
322+
localAddr = netip.AddrFrom4([4]byte{127, 0, 0, 1})
323+
remoteAddr = netip.AddrFrom4([4]byte{127, 0, 0, 1})
324+
)
325+
326+
switch len(parts) {
327+
case 1:
319328
// Duplicate the single part
320329
parts = append(parts, parts[0])
330+
case 2:
331+
// Check to see if the first part is an IP address.
332+
_localAddr, err := netip.ParseAddr(parts[0])
333+
if err != nil {
334+
break
335+
}
336+
// The first part is the local address, so duplicate the port.
337+
localAddr = _localAddr
338+
parts = []string{parts[1], parts[1]}
339+
340+
case 3:
341+
_localAddr, err := netip.ParseAddr(parts[0])
342+
if err != nil {
343+
return nil, xerrors.Errorf("invalid port specification %q; invalid ip %q: %w", in, parts[0], err)
344+
}
345+
localAddr = _localAddr
346+
parts = parts[1:]
347+
348+
default:
349+
return nil, xerrors.Errorf("invalid port specification %q", in)
321350
}
351+
322352
if !strings.Contains(parts[0], "-") {
323-
local, err := parsePort(parts[0])
353+
localPort, err := parsePort(parts[0])
324354
if err != nil {
325355
return nil, xerrors.Errorf("parse local port from %q: %w", in, err)
326356
}
327-
remote, err := parsePort(parts[1])
357+
remotePort, err := parsePort(parts[1])
328358
if err != nil {
329359
return nil, xerrors.Errorf("parse remote port from %q: %w", in, err)
330360
}
331361

332-
return []parsedSrcDestPort{{local: local, remote: remote}}, nil
362+
return []parsedSrcDestPort{{
363+
local: netip.AddrPortFrom(localAddr, localPort),
364+
remote: netip.AddrPortFrom(remoteAddr, remotePort),
365+
}}, nil
333366
}
334367

335368
local, err := parsePortRange(parts[0])
@@ -346,8 +379,8 @@ func parseSrcDestPorts(in string) ([]parsedSrcDestPort, error) {
346379
var out []parsedSrcDestPort
347380
for i := range local {
348381
out = append(out, parsedSrcDestPort{
349-
local: local[i],
350-
remote: remote[i],
382+
local: netip.AddrPortFrom(localAddr, local[i]),
383+
remote: netip.AddrPortFrom(remoteAddr, remote[i]),
351384
})
352385
}
353386
return out, nil

cli/portforward_test.go

+19
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,25 @@ func TestPortForward(t *testing.T) {
104104
return l.Addr().String(), port
105105
},
106106
},
107+
{
108+
name: "TCPWithAddress",
109+
network: "tcp",
110+
flag: "--tcp=%v:%v",
111+
setupRemote: func(t *testing.T) net.Listener {
112+
l, err := net.Listen("tcp", "127.0.0.1:0")
113+
require.NoError(t, err, "create TCP listener")
114+
return l
115+
},
116+
setupLocal: func(t *testing.T) (string, string) {
117+
l, err := net.Listen("tcp", "127.0.0.1:0")
118+
require.NoError(t, err, "create TCP listener to generate random port")
119+
defer l.Close()
120+
121+
_, port, err := net.SplitHostPort(l.Addr().String())
122+
require.NoErrorf(t, err, "split TCP address %q", l.Addr().String())
123+
return l.Addr().String(), fmt.Sprint("0.0.0.0:", port)
124+
},
125+
},
107126
}
108127

109128
// Setup agent once to be shared between test-cases (avoid expensive

cli/testdata/coder_port-forward_--help.golden

+4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ Aliases: tunnel
2222

2323
 $ coder port-forward <workspace> --tcp 8080,9000:3000,9090-9092,10000-10002:10010-10012 
2424

25+
- Port forward specifying the local address to bind to:
26+
27+
 $ coder port-forward <workspace> --tcp 1.2.3.4:8080:8080 
28+
2529
Options
2630
-p, --tcp string-array, $CODER_PORT_FORWARD_TCP
2731
Forward TCP port(s) from the workspace to the local machine.

docs/cli/port-forward.md

+4
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ coder port-forward [flags] <workspace>
3434
- Port forward multiple ports (TCP or UDP) in condensed syntax:
3535

3636
$ coder port-forward <workspace> --tcp 8080,9000:3000,9090-9092,10000-10002:10010-10012
37+
38+
- Port forward specifying the local address to bind to:
39+
40+
$ coder port-forward <workspace> --tcp 1.2.3.4:8080:8080
3741
```
3842

3943
## Options

0 commit comments

Comments
 (0)