@@ -15,7 +15,7 @@ import (
1515 "golang.org/x/sync/errgroup"
1616)
1717
18- type RouterClient interface {
18+ type routerClient interface {
1919 AddPortMappingCtx (
2020 ctx context.Context ,
2121 NewRemoteHost string ,
@@ -43,7 +43,7 @@ type RouterClient interface {
4343 LocalAddr () net.IP
4444}
4545
46- func pickRouterClient (ctx context.Context ) (RouterClient , error ) {
46+ func pickRouterClient (ctx context.Context ) (routerClient , error ) {
4747 tasks , _ := errgroup .WithContext (ctx )
4848 // Request each type of client in parallel, and return what is found.
4949 var ip1Clients []* internetgateway2.WANIPConnection1
@@ -84,23 +84,27 @@ func pickRouterClient(ctx context.Context) (RouterClient, error) {
8484 }
8585}
8686
87+ type ForwardedPort struct {
88+ InternalPort uint16
89+ ExternalPort uint16
90+ Protocol string
91+ }
92+
8793type ForwardOpts struct {
8894 RemoteHost string
8995 ProgramName string
90- Protocol string
91- Ports []uint16
96+ Ports []* ForwardedPort
9297 LeaseDuration time.Duration
9398}
9499
95100type forwardedPort struct {
96- remoteHost string
101+ remoteHost string // ForwardOpts has no remote host
97102 externalPort uint16
98103 protocol string
99- endOfLease time.Time
100104}
101105
102106type Forwarder struct {
103- client RouterClient
107+ client routerClient
104108
105109 existingForwarding map [forwardedPort ]struct {}
106110}
@@ -129,24 +133,32 @@ func (f *Forwarder) ForwardPorts(ctx context.Context, opts ForwardOpts) error {
129133
130134 for _ , port := range opts .Ports {
131135 // Try to clean up first. That's not optimal, but simplifies workflow.
132- err := f .client .DeletePortMappingCtx (ctx , opts .RemoteHost , port , opts .Protocol )
136+ err := f .client .DeletePortMappingCtx (ctx , opts .RemoteHost , port . ExternalPort , port .Protocol )
133137 if err != nil {
134138 _ , _ = fmt .Fprintln (os .Stderr , "failed to delete before create" , err .Error ())
135139
136140 errs = errors .Join (errs , err )
137141 }
138142
143+ storedPort := forwardedPort {
144+ remoteHost : opts .RemoteHost ,
145+ externalPort : port .ExternalPort ,
146+ protocol : port .Protocol ,
147+ }
148+
149+ delete (f .existingForwarding , storedPort )
150+
139151 if err := f .client .AddPortMappingCtx (
140152 ctx ,
141153 opts .RemoteHost ,
142154 // External port number to expose to Internet:
143- port ,
155+ port . ExternalPort ,
144156 // Forward TCP (this could be "UDP" if we wanted that instead).
145- opts .Protocol ,
157+ port .Protocol ,
146158 // Internal port number on the LAN to forward to.
147159 // Some routers might not support this being different to the external
148160 // port number.
149- port , // symmetrical
161+ port . InternalPort ,
150162 // Internal address on the LAN we want to forward to.
151163 f .client .LocalAddr ().String (),
152164 // Enabled:
@@ -158,21 +170,17 @@ func (f *Forwarder) ForwardPorts(ctx context.Context, opts ForwardOpts) error {
158170 // resets, you might want to periodically request before this elapses.
159171 uint32 (opts .LeaseDuration .Seconds ()),
160172 ); err != nil {
161- extErr := fmt .Errorf ("error forwarding port %d : %w" , port , err )
173+ extErr := fmt .Errorf ("error forwarding port %+v : %w" , port , err )
162174
163175 _ , _ = fmt .Fprintln (os .Stderr , extErr .Error ())
164176
165177 errs = errors .Join (errs , extErr )
166178 }
167179
168- f .existingForwarding [forwardedPort {
169- remoteHost : opts .RemoteHost ,
170- externalPort : port ,
171- protocol : opts .Protocol ,
172- endOfLease : time .Now ().UTC ().Add (opts .LeaseDuration ),
173- }] = struct {}{}
180+ f .existingForwarding [storedPort ] = struct {}{}
174181
175- fmt .Printf ("Port %d forwarded\n " , port )
182+ fmt .Printf ("Port forwarding created: internal (%d), external (%d), proto (%s)\n " ,
183+ port .InternalPort , port .ExternalPort , port .Protocol )
176184 }
177185
178186 if errs != nil {
@@ -200,7 +208,7 @@ func (f *Forwarder) StopAllForwarding(ctx context.Context) error {
200208
201209 deleted = append (deleted , fwd )
202210
203- fmt .Printf ("Port forwarding for port %d stopped\n " , fwd .externalPort )
211+ fmt .Printf ("Port forwarding for external port %d stopped\n " , fwd .externalPort )
204212 }
205213
206214 for _ , deletedPort := range deleted {
0 commit comments