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

Lines changed: 48 additions & 15 deletions
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

Lines changed: 19 additions & 0 deletions
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

Lines changed: 4 additions & 0 deletions
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

Lines changed: 4 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy