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+
329350func 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 )
0 commit comments