From 9621cb802559e9cfc01b080c1d9379d67d7cfa85 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Mon, 22 May 2023 20:49:46 +0000 Subject: [PATCH 1/3] feat(cli): allow specifying the listen address in `coder port-forward` --- cli/portforward.go | 63 +++++++++++++++++++++++++++++++---------- cli/portforward_test.go | 19 +++++++++++++ 2 files changed, 67 insertions(+), 15 deletions(-) diff --git a/cli/portforward.go b/cli/portforward.go index dad82381bfb5b..dad19bf866d3a 100644 --- a/cli/portforward.go +++ b/cli/portforward.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net" + "net/netip" "os" "os/signal" "strconv" @@ -47,6 +48,10 @@ func (r *RootCmd) portForward() *clibase.Cmd { Description: "Port forward multiple ports (TCP or UDP) in condensed syntax", Command: "coder port-forward --tcp 8080,9000:3000,9090-9092,10000-10002:10010-10012", }, + example{ + Description: "Port forward specifying the local address to bind to", + Command: "coder port-forward --tcp 1.2.3.4:8080:8080", + }, ), Middleware: clibase.Chain( clibase.RequireNArgs(1), @@ -255,9 +260,9 @@ func parsePortForwards(tcpSpecs, udpSpecs []string) ([]portForwardSpec, error) { for _, port := range ports { specs = append(specs, portForwardSpec{ listenNetwork: "tcp", - listenAddress: fmt.Sprintf("127.0.0.1:%v", port.local), + listenAddress: port.local.String(), dialNetwork: "tcp", - dialAddress: fmt.Sprintf("127.0.0.1:%v", port.remote), + dialAddress: port.remote.String(), }) } } @@ -273,9 +278,9 @@ func parsePortForwards(tcpSpecs, udpSpecs []string) ([]portForwardSpec, error) { for _, port := range ports { specs = append(specs, portForwardSpec{ listenNetwork: "udp", - listenAddress: fmt.Sprintf("127.0.0.1:%v", port.local), + listenAddress: port.local.String(), dialNetwork: "udp", - dialAddress: fmt.Sprintf("127.0.0.1:%v", port.remote), + dialAddress: port.remote.String(), }) } } @@ -307,29 +312,57 @@ func parsePort(in string) (uint16, error) { } type parsedSrcDestPort struct { - local, remote uint16 + local, remote netip.AddrPort } func parseSrcDestPorts(in string) ([]parsedSrcDestPort, error) { - parts := strings.Split(in, ":") - if len(parts) > 2 { - return nil, xerrors.Errorf("invalid port specification %q", in) - } - if len(parts) == 1 { + var ( + err error + parts = strings.Split(in, ":") + localAddr = netip.AddrFrom4([4]byte{127, 0, 0, 1}) + remoteAddr = netip.AddrFrom4([4]byte{127, 0, 0, 1}) + ) + + switch len(parts) { + case 1: // Duplicate the single part parts = append(parts, parts[0]) + case 2: + // Check to see if the first part is an IP address. + _localAddr, err := netip.ParseAddr(parts[0]) + if err != nil { + break + } + // The first part is the local address, so duplicate the port. + localAddr = _localAddr + parts = []string{parts[1], parts[1]} + + case 3: + _localAddr, err := netip.ParseAddr(parts[0]) + if err != nil { + return nil, xerrors.Errorf("invalid port specification %q; invalid ip %q: %w", in, parts[0], err) + } + localAddr = _localAddr + parts = parts[1:] + + default: + return nil, xerrors.Errorf("invalid port specification %q", in) } + if !strings.Contains(parts[0], "-") { - local, err := parsePort(parts[0]) + localPort, err := parsePort(parts[0]) if err != nil { return nil, xerrors.Errorf("parse local port from %q: %w", in, err) } - remote, err := parsePort(parts[1]) + remotePort, err := parsePort(parts[1]) if err != nil { return nil, xerrors.Errorf("parse remote port from %q: %w", in, err) } - return []parsedSrcDestPort{{local: local, remote: remote}}, nil + return []parsedSrcDestPort{{ + local: netip.AddrPortFrom(localAddr, localPort), + remote: netip.AddrPortFrom(remoteAddr, remotePort), + }}, nil } local, err := parsePortRange(parts[0]) @@ -346,8 +379,8 @@ func parseSrcDestPorts(in string) ([]parsedSrcDestPort, error) { var out []parsedSrcDestPort for i := range local { out = append(out, parsedSrcDestPort{ - local: local[i], - remote: remote[i], + local: netip.AddrPortFrom(localAddr, local[i]), + remote: netip.AddrPortFrom(netip.IPv4Unspecified(), remote[i]), }) } return out, nil diff --git a/cli/portforward_test.go b/cli/portforward_test.go index c73788725a67d..5ae1997285ab7 100644 --- a/cli/portforward_test.go +++ b/cli/portforward_test.go @@ -104,6 +104,25 @@ func TestPortForward(t *testing.T) { return l.Addr().String(), port }, }, + { + name: "TCPWithAddress", + network: "tcp", + flag: "--tcp=%v:%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 + }, + setupLocal: func(t *testing.T) (string, string) { + l, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err, "create TCP listener to generate random port") + defer l.Close() + + _, port, err := net.SplitHostPort(l.Addr().String()) + require.NoErrorf(t, err, "split TCP address %q", l.Addr().String()) + return l.Addr().String(), fmt.Sprint("0.0.0.0:", port) + }, + }, } // Setup agent once to be shared between test-cases (avoid expensive From ae1aeaa5a134b1b4e54360c17cbe458f166fde32 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Mon, 22 May 2023 21:19:14 +0000 Subject: [PATCH 2/3] fixup! feat(cli): allow specifying the listen address in `coder port-forward` --- cli/testdata/coder_port-forward_--help.golden | 4 ++++ docs/cli/port-forward.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/cli/testdata/coder_port-forward_--help.golden b/cli/testdata/coder_port-forward_--help.golden index fd87e6b4fbe2d..6efd95c0e89cf 100644 --- a/cli/testdata/coder_port-forward_--help.golden +++ b/cli/testdata/coder_port-forward_--help.golden @@ -22,6 +22,10 @@ Aliases: tunnel  $ coder port-forward --tcp 8080,9000:3000,9090-9092,10000-10002:10010-10012  + - Port forward specifying the local address to bind to: + +  $ coder port-forward --tcp 1.2.3.4:8080:8080  + Options -p, --tcp string-array, $CODER_PORT_FORWARD_TCP Forward TCP port(s) from the workspace to the local machine. diff --git a/docs/cli/port-forward.md b/docs/cli/port-forward.md index 9a79ebc29cf68..5c3ac14c1b126 100644 --- a/docs/cli/port-forward.md +++ b/docs/cli/port-forward.md @@ -34,6 +34,10 @@ coder port-forward [flags] - Port forward multiple ports (TCP or UDP) in condensed syntax: $ coder port-forward --tcp 8080,9000:3000,9090-9092,10000-10002:10010-10012 + + - Port forward specifying the local address to bind to: + + $ coder port-forward --tcp 1.2.3.4:8080:8080 ``` ## Options From 451ccc6fbc4e7c3f1649f6f3fdeebe73c346f391 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Tue, 23 May 2023 02:02:18 +0000 Subject: [PATCH 3/3] fixup! feat(cli): allow specifying the listen address in `coder port-forward` --- cli/portforward.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/portforward.go b/cli/portforward.go index dad19bf866d3a..fd0f2bcfab57f 100644 --- a/cli/portforward.go +++ b/cli/portforward.go @@ -380,7 +380,7 @@ func parseSrcDestPorts(in string) ([]parsedSrcDestPort, error) { for i := range local { out = append(out, parsedSrcDestPort{ local: netip.AddrPortFrom(localAddr, local[i]), - remote: netip.AddrPortFrom(netip.IPv4Unspecified(), remote[i]), + remote: netip.AddrPortFrom(remoteAddr, remote[i]), }) } return out, nil