Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit64265cb

Browse files
committed
feat: opportunistically listen on IPv6 in port-forward
1 parentf6c3f0a commit64265cb

File tree

3 files changed

+170
-67
lines changed

3 files changed

+170
-67
lines changed

‎cli/portforward.go‎

Lines changed: 71 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ import (
2525
"github.com/coder/serpent"
2626
)
2727

28+
var (
29+
// noAddr is the zero-value of netip.Addr, and is not a valid address. We use it to identify
30+
// when the local address is not specified in port-forward flags.
31+
noAddr netip.Addr
32+
ipv6Loopback=netip.MustParseAddr("::1")
33+
ipv4Loopback=netip.MustParseAddr("127.0.0.1")
34+
)
35+
2836
func (r*RootCmd)portForward()*serpent.Command {
2937
var (
3038
tcpForwards []string// <port>:<port>
@@ -122,7 +130,7 @@ func (r *RootCmd) portForward() *serpent.Command {
122130
// Start all listeners.
123131
var (
124132
wg=new(sync.WaitGroup)
125-
listeners=make([]net.Listener,len(specs))
133+
listeners=make([]net.Listener,0,len(specs)*2)
126134
closeAllListeners=func() {
127135
logger.Debug(ctx,"closing all listeners")
128136
for_,l:=rangelisteners {
@@ -135,13 +143,26 @@ func (r *RootCmd) portForward() *serpent.Command {
135143
)
136144
defercloseAllListeners()
137145

138-
fori,spec:=rangespecs {
146+
for_,spec:=rangespecs {
147+
148+
ifspec.listenHost==noAddr {
149+
// first, opportunistically try to listen on IPv6
150+
spec6:=spec
151+
spec6.listenHost=ipv6Loopback
152+
l6,err6:=listenAndPortForward(ctx,inv,conn,wg,spec6,logger)
153+
iferr6!=nil {
154+
logger.Info(ctx,"failed to opportunistically listen on IPv6",slog.F("spec",spec),slog.Error(err6))
155+
}else {
156+
listeners=append(listeners,l6)
157+
}
158+
spec.listenHost=ipv4Loopback
159+
}
139160
l,err:=listenAndPortForward(ctx,inv,conn,wg,spec,logger)
140161
iferr!=nil {
141162
logger.Error(ctx,"failed to listen",slog.F("spec",spec),slog.Error(err))
142163
returnerr
143164
}
144-
listeners[i]=l
165+
listeners=append(listeners,l)
145166
}
146167

147168
stopUpdating:=client.UpdateWorkspaceUsageContext(ctx,workspace.ID)
@@ -206,12 +227,19 @@ func listenAndPortForward(
206227
specportForwardSpec,
207228
logger slog.Logger,
208229
) (net.Listener,error) {
209-
logger=logger.With(slog.F("network",spec.listenNetwork),slog.F("address",spec.listenAddress))
210-
_,_=fmt.Fprintf(inv.Stderr,"Forwarding '%v://%v' locally to '%v://%v' in the workspace\n",spec.listenNetwork,spec.listenAddress,spec.dialNetwork,spec.dialAddress)
230+
logger=logger.With(
231+
slog.F("network",spec.network),
232+
slog.F("listen_host",spec.listenHost),
233+
slog.F("listen_port",spec.listenPort),
234+
)
235+
listenAddress:=netip.AddrPortFrom(spec.listenHost,spec.listenPort)
236+
dialAddress:=fmt.Sprintf("127.0.0.1:%d",spec.dialPort)
237+
_,_=fmt.Fprintf(inv.Stderr,"Forwarding '%s://%s' locally to '%s://%s' in the workspace\n",
238+
spec.network,listenAddress,spec.network,dialAddress)
211239

212-
l,err:=inv.Net.Listen(spec.listenNetwork,spec.listenAddress)
240+
l,err:=inv.Net.Listen(spec.network,listenAddress.String())
213241
iferr!=nil {
214-
returnnil,xerrors.Errorf("listen '%v://%v': %w",spec.listenNetwork,spec.listenAddress,err)
242+
returnnil,xerrors.Errorf("listen '%s://%s': %w",spec.network,listenAddress.String(),err)
215243
}
216244
logger.Debug(ctx,"listening")
217245

@@ -226,24 +254,31 @@ func listenAndPortForward(
226254
logger.Debug(ctx,"listener closed")
227255
return
228256
}
229-
_,_=fmt.Fprintf(inv.Stderr,"Error accepting connection from '%v://%v': %v\n",spec.listenNetwork,spec.listenAddress,err)
257+
_,_=fmt.Fprintf(inv.Stderr,
258+
"Error accepting connection from '%s://%s': %v\n",
259+
spec.network,listenAddress.String(),err)
230260
_,_=fmt.Fprintln(inv.Stderr,"Killing listener")
231261
return
232262
}
233-
logger.Debug(ctx,"accepted connection",slog.F("remote_addr",netConn.RemoteAddr()))
263+
logger.Debug(ctx,"accepted connection",
264+
slog.F("remote_addr",netConn.RemoteAddr()))
234265

235266
gofunc(netConn net.Conn) {
236267
defernetConn.Close()
237-
remoteConn,err:=conn.DialContext(ctx,spec.dialNetwork,spec.dialAddress)
268+
remoteConn,err:=conn.DialContext(ctx,spec.network,dialAddress)
238269
iferr!=nil {
239-
_,_=fmt.Fprintf(inv.Stderr,"Failed to dial '%v://%v' in workspace: %s\n",spec.dialNetwork,spec.dialAddress,err)
270+
_,_=fmt.Fprintf(inv.Stderr,
271+
"Failed to dial '%s://%s' in workspace: %s\n",
272+
spec.network,dialAddress,err)
240273
return
241274
}
242275
deferremoteConn.Close()
243-
logger.Debug(ctx,"dialed remote",slog.F("remote_addr",netConn.RemoteAddr()))
276+
logger.Debug(ctx,
277+
"dialed remote",slog.F("remote_addr",netConn.RemoteAddr()))
244278

245279
agentssh.Bicopy(ctx,netConn,remoteConn)
246-
logger.Debug(ctx,"connection closing",slog.F("remote_addr",netConn.RemoteAddr()))
280+
logger.Debug(ctx,
281+
"connection closing",slog.F("remote_addr",netConn.RemoteAddr()))
247282
}(netConn)
248283
}
249284
}(spec)
@@ -252,58 +287,48 @@ func listenAndPortForward(
252287
}
253288

254289
typeportForwardSpecstruct {
255-
listenNetworkstring// tcp, udp
256-
listenAddressstring// <ip>:<port> or path
257-
258-
dialNetworkstring// tcp, udp
259-
dialAddressstring// <ip>:<port> or path
290+
networkstring// tcp, udp
291+
listenHost netip.Addr
292+
listenPort,dialPortuint16
260293
}
261294

262295
funcparsePortForwards(tcpSpecs,udpSpecs []string) ([]portForwardSpec,error) {
263296
specs:= []portForwardSpec{}
264297

265298
for_,specEntry:=rangetcpSpecs {
266299
for_,spec:=rangestrings.Split(specEntry,",") {
267-
ports,err:=parseSrcDestPorts(strings.TrimSpace(spec))
300+
pfSpecs,err:=parseSrcDestPorts(strings.TrimSpace(spec))
268301
iferr!=nil {
269302
returnnil,xerrors.Errorf("failed to parse TCP port-forward specification %q: %w",spec,err)
270303
}
271304

272-
for_,port:=rangeports {
273-
specs=append(specs,portForwardSpec{
274-
listenNetwork:"tcp",
275-
listenAddress:port.local.String(),
276-
dialNetwork:"tcp",
277-
dialAddress:port.remote.String(),
278-
})
305+
for_,pfSpec:=rangepfSpecs {
306+
pfSpec.network="tcp"
307+
specs=append(specs,pfSpec)
279308
}
280309
}
281310
}
282311

283312
for_,specEntry:=rangeudpSpecs {
284313
for_,spec:=rangestrings.Split(specEntry,",") {
285-
ports,err:=parseSrcDestPorts(strings.TrimSpace(spec))
314+
pfSpecs,err:=parseSrcDestPorts(strings.TrimSpace(spec))
286315
iferr!=nil {
287316
returnnil,xerrors.Errorf("failed to parse UDP port-forward specification %q: %w",spec,err)
288317
}
289318

290-
for_,port:=rangeports {
291-
specs=append(specs,portForwardSpec{
292-
listenNetwork:"udp",
293-
listenAddress:port.local.String(),
294-
dialNetwork:"udp",
295-
dialAddress:port.remote.String(),
296-
})
319+
for_,pfSpec:=rangepfSpecs {
320+
pfSpec.network="udp"
321+
specs=append(specs,pfSpec)
297322
}
298323
}
299324
}
300325

301326
// Check for duplicate entries.
302327
locals:=map[string]struct{}{}
303328
for_,spec:=rangespecs {
304-
localStr:=fmt.Sprintf("%v:%v",spec.listenNetwork,spec.listenAddress)
329+
localStr:=fmt.Sprintf("%s:%s:%d",spec.network,spec.listenHost,spec.listenPort)
305330
if_,ok:=locals[localStr];ok {
306-
returnnil,xerrors.Errorf("local %v %vis specified twice",spec.listenNetwork,spec.listenAddress)
331+
returnnil,xerrors.Errorf("local %s host:%s port:%dis specified twice",spec.network,spec.listenHost,spec.listenPort)
307332
}
308333
locals[localStr]=struct{}{}
309334
}
@@ -323,10 +348,6 @@ func parsePort(in string) (uint16, error) {
323348
returnuint16(port),nil
324349
}
325350

326-
typeparsedSrcDestPortstruct {
327-
local,remote netip.AddrPort
328-
}
329-
330351
// specRegexp matches port specs. It handles all the following formats:
331352
//
332353
// 8000
@@ -347,21 +368,19 @@ type parsedSrcDestPort struct {
347368
// 9: end or remote port range
348369
varspecRegexp=regexp.MustCompile(`^((\[[0-9a-fA-F:]+]|\d+\.\d+\.\d+\.\d+):)?(\d+)(-(\d+))?(:(\d+)(-(\d+))?)?$`)
349370

350-
funcparseSrcDestPorts(instring) ([]parsedSrcDestPort,error) {
351-
var (
352-
errerror
353-
localAddr=netip.AddrFrom4([4]byte{127,0,0,1})
354-
remoteAddr=netip.AddrFrom4([4]byte{127,0,0,1})
355-
)
371+
funcparseSrcDestPorts(instring) ([]portForwardSpec,error) {
356372
groups:=specRegexp.FindStringSubmatch(in)
357373
iflen(groups)==0 {
358374
returnnil,xerrors.Errorf("invalid port specification %q",in)
359375
}
376+
377+
varlocalAddr netip.Addr
360378
ifgroups[2]!="" {
361-
localAddr,err=netip.ParseAddr(strings.Trim(groups[2],"[]"))
379+
parsedAddr,err:=netip.ParseAddr(strings.Trim(groups[2],"[]"))
362380
iferr!=nil {
363381
returnnil,xerrors.Errorf("invalid IP address %q",groups[2])
364382
}
383+
localAddr=parsedAddr
365384
}
366385

367386
local,err:=parsePortRange(groups[3],groups[5])
@@ -378,11 +397,12 @@ func parseSrcDestPorts(in string) ([]parsedSrcDestPort, error) {
378397
iflen(local)!=len(remote) {
379398
returnnil,xerrors.Errorf("port ranges must be the same length, got %d ports forwarded to %d ports",len(local),len(remote))
380399
}
381-
varout []parsedSrcDestPort
400+
varout []portForwardSpec
382401
fori:=rangelocal {
383-
out=append(out,parsedSrcDestPort{
384-
local:netip.AddrPortFrom(localAddr,local[i]),
385-
remote:netip.AddrPortFrom(remoteAddr,remote[i]),
402+
out=append(out,portForwardSpec{
403+
listenHost:localAddr,
404+
listenPort:local[i],
405+
dialPort:remote[i],
386406
})
387407
}
388408
returnout,nil

‎cli/portforward_internal_test.go‎

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,15 @@ func Test_parsePortForwards(t *testing.T) {
2929
},
3030
},
3131
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"},
32+
{"tcp",noAddr,8000,8000},
33+
{"tcp",noAddr,8080,8081},
34+
{"tcp",noAddr,9000,9000},
35+
{"tcp",noAddr,9001,9001},
36+
{"tcp",noAddr,9002,9002},
37+
{"tcp",noAddr,9003,9005},
38+
{"tcp",noAddr,9004,9006},
39+
{"tcp",noAddr,10000,10000},
40+
{"tcp",noAddr,4444,4444},
4141
},
4242
},
4343
{
@@ -46,7 +46,7 @@ func Test_parsePortForwards(t *testing.T) {
4646
tcpSpecs: []string{"127.0.0.1:8080:8081"},
4747
},
4848
want: []portForwardSpec{
49-
{"tcp","127.0.0.1:8080","tcp","127.0.0.1:8081"},
49+
{"tcp",ipv4Loopback,8080,8081},
5050
},
5151
},
5252
{
@@ -55,7 +55,7 @@ func Test_parsePortForwards(t *testing.T) {
5555
tcpSpecs: []string{"[::1]:8080:8081"},
5656
},
5757
want: []portForwardSpec{
58-
{"tcp","[::1]:8080","tcp","127.0.0.1:8081"},
58+
{"tcp",ipv6Loopback,8080,8081},
5959
},
6060
},
6161
{
@@ -64,9 +64,9 @@ func Test_parsePortForwards(t *testing.T) {
6464
udpSpecs: []string{"8000,8080-8081"},
6565
},
6666
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"},
67+
{"udp",noAddr,8000,8000},
68+
{"udp",noAddr,8080,8080},
69+
{"udp",noAddr,8081,8081},
7070
},
7171
},
7272
{
@@ -75,7 +75,7 @@ func Test_parsePortForwards(t *testing.T) {
7575
udpSpecs: []string{"127.0.0.1:8080:8081"},
7676
},
7777
want: []portForwardSpec{
78-
{"udp","127.0.0.1:8080","udp","127.0.0.1:8081"},
78+
{"udp",ipv4Loopback,8080,8081},
7979
},
8080
},
8181
{
@@ -84,7 +84,7 @@ func Test_parsePortForwards(t *testing.T) {
8484
udpSpecs: []string{"[::1]:8080:8081"},
8585
},
8686
want: []portForwardSpec{
87-
{"udp","[::1]:8080","udp","127.0.0.1:8081"},
87+
{"udp",ipv6Loopback,8080,8081},
8888
},
8989
},
9090
{

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp