Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f6c3f0a

Browse files
committed
fix: fix parsing of IPv6 addresses in coder port-forward
1 parent a8becfb commit f6c3f0a

File tree

3 files changed

+107
-79
lines changed

3 files changed

+107
-79
lines changed

cli/portforward.go

+44-54
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"net/netip"
88
"os"
99
"os/signal"
10+
"regexp"
1011
"strconv"
1112
"strings"
1213
"sync"
@@ -263,7 +264,7 @@ func parsePortForwards(tcpSpecs, udpSpecs []string) ([]portForwardSpec, error) {
263264

264265
for _, specEntry := range tcpSpecs {
265266
for _, spec := range strings.Split(specEntry, ",") {
266-
ports, err := parseSrcDestPorts(spec)
267+
ports, err := parseSrcDestPorts(strings.TrimSpace(spec))
267268
if err != nil {
268269
return nil, xerrors.Errorf("failed to parse TCP port-forward specification %q: %w", spec, err)
269270
}
@@ -281,7 +282,7 @@ func parsePortForwards(tcpSpecs, udpSpecs []string) ([]portForwardSpec, error) {
281282

282283
for _, specEntry := range udpSpecs {
283284
for _, spec := range strings.Split(specEntry, ",") {
284-
ports, err := parseSrcDestPorts(spec)
285+
ports, err := parseSrcDestPorts(strings.TrimSpace(spec))
285286
if err != nil {
286287
return nil, xerrors.Errorf("failed to parse UDP port-forward specification %q: %w", spec, err)
287288
}
@@ -326,63 +327,53 @@ type parsedSrcDestPort struct {
326327
local, remote netip.AddrPort
327328
}
328329

330+
// specRegexp matches port specs. It handles all the following formats:
331+
//
332+
// 8000
333+
// 8888:9999
334+
// 1-5:6-10
335+
// 8000-8005
336+
// 127.0.0.1:4000:4000
337+
// [::1]:8080:8081
338+
// 127.0.0.1:4000-4005
339+
// [::1]:4000-4001:5000-5001
340+
//
341+
// Important capturing groups:
342+
//
343+
// 2: local IP address (including [] for IPv6)
344+
// 3: local port, or start of local port range
345+
// 5: end of local port range
346+
// 7: remote port, or start of remote port range
347+
// 9: end or remote port range
348+
var specRegexp = regexp.MustCompile(`^((\[[0-9a-fA-F:]+]|\d+\.\d+\.\d+\.\d+):)?(\d+)(-(\d+))?(:(\d+)(-(\d+))?)?$`)
349+
329350
func parseSrcDestPorts(in string) ([]parsedSrcDestPort, error) {
330351
var (
331352
err error
332-
parts = strings.Split(in, ":")
333353
localAddr = netip.AddrFrom4([4]byte{127, 0, 0, 1})
334354
remoteAddr = netip.AddrFrom4([4]byte{127, 0, 0, 1})
335355
)
336-
337-
switch len(parts) {
338-
case 1:
339-
// Duplicate the single part
340-
parts = append(parts, parts[0])
341-
case 2:
342-
// Check to see if the first part is an IP address.
343-
_localAddr, err := netip.ParseAddr(parts[0])
344-
if err != nil {
345-
break
346-
}
347-
// The first part is the local address, so duplicate the port.
348-
localAddr = _localAddr
349-
parts = []string{parts[1], parts[1]}
350-
351-
case 3:
352-
_localAddr, err := netip.ParseAddr(parts[0])
353-
if err != nil {
354-
return nil, xerrors.Errorf("invalid port specification %q; invalid ip %q: %w", in, parts[0], err)
355-
}
356-
localAddr = _localAddr
357-
parts = parts[1:]
358-
359-
default:
356+
groups := specRegexp.FindStringSubmatch(in)
357+
if len(groups) == 0 {
360358
return nil, xerrors.Errorf("invalid port specification %q", in)
361359
}
362-
363-
if !strings.Contains(parts[0], "-") {
364-
localPort, err := parsePort(parts[0])
360+
if groups[2] != "" {
361+
localAddr, err = netip.ParseAddr(strings.Trim(groups[2], "[]"))
365362
if err != nil {
366-
return nil, xerrors.Errorf("parse local port from %q: %w", in, err)
363+
return nil, xerrors.Errorf("invalid IP address %q", groups[2])
367364
}
368-
remotePort, err := parsePort(parts[1])
369-
if err != nil {
370-
return nil, xerrors.Errorf("parse remote port from %q: %w", in, err)
371-
}
372-
373-
return []parsedSrcDestPort{{
374-
local: netip.AddrPortFrom(localAddr, localPort),
375-
remote: netip.AddrPortFrom(remoteAddr, remotePort),
376-
}}, nil
377365
}
378366

379-
local, err := parsePortRange(parts[0])
367+
local, err := parsePortRange(groups[3], groups[5])
380368
if err != nil {
381369
return nil, xerrors.Errorf("parse local port range from %q: %w", in, err)
382370
}
383-
remote, err := parsePortRange(parts[1])
384-
if err != nil {
385-
return nil, xerrors.Errorf("parse remote port range from %q: %w", in, err)
371+
remote := local
372+
if groups[7] != "" {
373+
remote, err = parsePortRange(groups[7], groups[9])
374+
if err != nil {
375+
return nil, xerrors.Errorf("parse remote port range from %q: %w", in, err)
376+
}
386377
}
387378
if len(local) != len(remote) {
388379
return nil, xerrors.Errorf("port ranges must be the same length, got %d ports forwarded to %d ports", len(local), len(remote))
@@ -397,18 +388,17 @@ func parseSrcDestPorts(in string) ([]parsedSrcDestPort, error) {
397388
return out, nil
398389
}
399390

400-
func parsePortRange(in string) ([]uint16, error) {
401-
parts := strings.Split(in, "-")
402-
if len(parts) != 2 {
403-
return nil, xerrors.Errorf("invalid port range specification %q", in)
404-
}
405-
start, err := parsePort(parts[0])
391+
func parsePortRange(s, e string) ([]uint16, error) {
392+
start, err := parsePort(s)
406393
if err != nil {
407-
return nil, xerrors.Errorf("parse range start port from %q: %w", in, err)
394+
return nil, xerrors.Errorf("parse range start port from %q: %w", s, err)
408395
}
409-
end, err := parsePort(parts[1])
410-
if err != nil {
411-
return nil, xerrors.Errorf("parse range end port from %q: %w", in, err)
396+
end := start
397+
if len(e) != 0 {
398+
end, err = parsePort(e)
399+
if err != nil {
400+
return nil, xerrors.Errorf("parse range end port from %q: %w", e, err)
401+
}
412402
}
413403
if end < start {
414404
return nil, xerrors.Errorf("range end port %v is less than start port %v", end, start)

cli/portforward_internal_test.go

+53-25
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package cli
22

33
import (
4-
"fmt"
5-
"strings"
64
"testing"
75

86
"github.com/stretchr/testify/require"
@@ -11,21 +9,14 @@ import (
119
func Test_parsePortForwards(t *testing.T) {
1210
t.Parallel()
1311

14-
portForwardSpecToString := func(v []portForwardSpec) (out []string) {
15-
for _, p := range v {
16-
require.Equal(t, p.listenNetwork, p.dialNetwork)
17-
out = append(out, fmt.Sprintf("%s:%s", strings.Replace(p.listenAddress, "127.0.0.1:", "", 1), strings.Replace(p.dialAddress, "127.0.0.1:", "", 1)))
18-
}
19-
return out
20-
}
2112
type args struct {
2213
tcpSpecs []string
2314
udpSpecs []string
2415
}
2516
tests := []struct {
2617
name string
2718
args args
28-
want []string
19+
want []portForwardSpec
2920
wantErr bool
3021
}{
3122
{
@@ -34,28 +25,66 @@ func Test_parsePortForwards(t *testing.T) {
3425
tcpSpecs: []string{
3526
"8000,8080:8081,9000-9002,9003-9004:9005-9006",
3627
"10000",
28+
"4444-4444",
3729
},
3830
},
39-
want: []string{
40-
"8000:8000",
41-
"8080:8081",
42-
"9000:9000",
43-
"9001:9001",
44-
"9002:9002",
45-
"9003:9005",
46-
"9004:9006",
47-
"10000:10000",
31+
want: []portForwardSpec{
32+
{"tcp", "127.0.0.1:8000", "tcp", "127.0.0.1:8000"},
33+
{"tcp", "127.0.0.1:8080", "tcp", "127.0.0.1:8081"},
34+
{"tcp", "127.0.0.1:9000", "tcp", "127.0.0.1:9000"},
35+
{"tcp", "127.0.0.1:9001", "tcp", "127.0.0.1:9001"},
36+
{"tcp", "127.0.0.1:9002", "tcp", "127.0.0.1:9002"},
37+
{"tcp", "127.0.0.1:9003", "tcp", "127.0.0.1:9005"},
38+
{"tcp", "127.0.0.1:9004", "tcp", "127.0.0.1:9006"},
39+
{"tcp", "127.0.0.1:10000", "tcp", "127.0.0.1:10000"},
40+
{"tcp", "127.0.0.1:4444", "tcp", "127.0.0.1:4444"},
41+
},
42+
},
43+
{
44+
name: "TCP IPv4 local",
45+
args: args{
46+
tcpSpecs: []string{"127.0.0.1:8080:8081"},
47+
},
48+
want: []portForwardSpec{
49+
{"tcp", "127.0.0.1:8080", "tcp", "127.0.0.1:8081"},
50+
},
51+
},
52+
{
53+
name: "TCP IPv6 local",
54+
args: args{
55+
tcpSpecs: []string{"[::1]:8080:8081"},
56+
},
57+
want: []portForwardSpec{
58+
{"tcp", "[::1]:8080", "tcp", "127.0.0.1:8081"},
4859
},
4960
},
5061
{
5162
name: "UDP with port range",
5263
args: args{
5364
udpSpecs: []string{"8000,8080-8081"},
5465
},
55-
want: []string{
56-
"8000:8000",
57-
"8080:8080",
58-
"8081:8081",
66+
want: []portForwardSpec{
67+
{"udp", "127.0.0.1:8000", "udp", "127.0.0.1:8000"},
68+
{"udp", "127.0.0.1:8080", "udp", "127.0.0.1:8080"},
69+
{"udp", "127.0.0.1:8081", "udp", "127.0.0.1:8081"},
70+
},
71+
},
72+
{
73+
name: "UDP IPv4 local",
74+
args: args{
75+
udpSpecs: []string{"127.0.0.1:8080:8081"},
76+
},
77+
want: []portForwardSpec{
78+
{"udp", "127.0.0.1:8080", "udp", "127.0.0.1:8081"},
79+
},
80+
},
81+
{
82+
name: "UDP IPv6 local",
83+
args: args{
84+
udpSpecs: []string{"[::1]:8080:8081"},
85+
},
86+
want: []portForwardSpec{
87+
{"udp", "[::1]:8080", "udp", "127.0.0.1:8081"},
5988
},
6089
},
6190
{
@@ -83,8 +112,7 @@ func Test_parsePortForwards(t *testing.T) {
83112
t.Fatalf("parsePortForwards() error = %v, wantErr %v", err, tt.wantErr)
84113
return
85114
}
86-
gotStrings := portForwardSpecToString(got)
87-
require.Equal(t, tt.want, gotStrings)
115+
require.Equal(t, tt.want, got)
88116
})
89117
}
90118
}

cli/portforward_test.go

+10
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,16 @@ func TestPortForward(t *testing.T) {
9292
},
9393
localAddress: []string{"10.10.10.99:9999", "10.10.10.10:1010"},
9494
},
95+
{
96+
name: "TCP-IPv6",
97+
network: "tcp", flag: []string{"--tcp=[fe80::99]:9999:%v", "--tcp=[fe80::10]:1010:%v"},
98+
setupRemote: func(t *testing.T) net.Listener {
99+
l, err := net.Listen("tcp", "127.0.0.1:0")
100+
require.NoError(t, err, "create TCP listener")
101+
return l
102+
},
103+
localAddress: []string{"[fe80::99]:9999", "[fe80::10]:1010"},
104+
},
95105
}
96106

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

0 commit comments

Comments
 (0)