@@ -11,6 +11,7 @@ import (
1111"io"
1212"math/rand"
1313"net"
14+ "net/netip"
1415"strconv"
1516"strings"
1617"sync"
@@ -22,14 +23,21 @@ import (
2223// the returned net.Listener. The listener must be serviced, or the
2324// SSH connection may hang.
2425// N must be "tcp", "tcp4", "tcp6", or "unix".
26+ //
27+ // If the address is a hostname, it is sent to the remote peer as-is, without
28+ // being resolved locally, and the Listener Addr method will return a zero IP.
2529func (c * Client )Listen (n ,addr string ) (net.Listener ,error ) {
2630switch n {
2731case "tcp" ,"tcp4" ,"tcp6" :
28- laddr ,err := net .ResolveTCPAddr (n ,addr )
32+ host ,portStr ,err := net .SplitHostPort (addr )
33+ if err != nil {
34+ return nil ,err
35+ }
36+ port ,err := strconv .ParseInt (portStr ,10 ,32 )
2937if err != nil {
3038return nil ,err
3139}
32- return c .ListenTCP ( laddr )
40+ return c .listenTCPInternal ( host , int ( port ) )
3341case "unix" :
3442return c .ListenUnix (addr )
3543default :
@@ -102,15 +110,24 @@ func (c *Client) handleForwards() {
102110// ListenTCP requests the remote peer open a listening socket
103111// on laddr. Incoming connections will be available by calling
104112// Accept on the returned net.Listener.
113+ //
114+ // ListenTCP accepts an IP address, to provide a hostname use [Client.Listen]
115+ // with "tcp", "tcp4", or "tcp6" network instead.
105116func (c * Client )ListenTCP (laddr * net.TCPAddr ) (net.Listener ,error ) {
106117c .handleForwardsOnce .Do (c .handleForwards )
107118if laddr .Port == 0 && isBrokenOpenSSHVersion (string (c .ServerVersion ())) {
108119return c .autoPortListenWorkaround (laddr )
109120}
110121
122+ return c .listenTCPInternal (laddr .IP .String (),laddr .Port )
123+ }
124+
125+ func (c * Client )listenTCPInternal (host string ,port int ) (net.Listener ,error ) {
126+ c .handleForwardsOnce .Do (c .handleForwards )
127+
111128m := channelForwardMsg {
112- laddr . IP . String () ,
113- uint32 (laddr . Port ),
129+ host ,
130+ uint32 (port ),
114131}
115132// send message
116133ok ,resp ,err := c .SendRequest ("tcpip-forward" ,true ,Marshal (& m ))
@@ -123,20 +140,33 @@ func (c *Client) ListenTCP(laddr *net.TCPAddr) (net.Listener, error) {
123140
124141// If the original port was 0, then the remote side will
125142// supply a real port number in the response.
126- if laddr . Port == 0 {
143+ if port == 0 {
127144var p struct {
128145Port uint32
129146}
130147if err := Unmarshal (resp ,& p );err != nil {
131148return nil ,err
132149}
133- laddr . Port = int (p .Port )
150+ port = int (p .Port )
134151}
152+ // Construct a local address placeholder for the remote listener. If the
153+ // original host is an IP address, preserve it so that Listener.Addr()
154+ // reports the same IP. If the host is a hostname or cannot be parsed as an
155+ // IP, fall back to IPv4zero. The port field is always set, even if the
156+ // original port was 0, because in that case the remote server will assign
157+ // one, allowing callers to determine which port was selected.
158+ ip := net .IPv4zero
159+ if parsed ,err := netip .ParseAddr (host );err == nil {
160+ ip = net .IP (parsed .AsSlice ())
161+ }
162+ laddr := & net.TCPAddr {
163+ IP :ip ,
164+ Port :port ,
165+ }
166+ addr := net .JoinHostPort (host ,strconv .FormatInt (int64 (port ),10 ))
167+ ch := c .forwards .add ("tcp" ,addr )
135168
136- // Register this forward, using the port number we obtained.
137- ch := c .forwards .add (laddr )
138-
139- return & tcpListener {laddr ,c ,ch },nil
169+ return & tcpListener {laddr ,addr ,c ,ch },nil
140170}
141171
142172// forwardList stores a mapping between remote
@@ -149,8 +179,9 @@ type forwardList struct {
149179// forwardEntry represents an established mapping of a laddr on a
150180// remote ssh server to a channel connected to a tcpListener.
151181type forwardEntry struct {
152- laddr net.Addr
153- c chan forward
182+ addr string // host:port or socket path
183+ network string // tcp or unix
184+ c chan forward
154185}
155186
156187// forward represents an incoming forwarded tcpip connection. The
@@ -161,12 +192,13 @@ type forward struct {
161192raddr net.Addr // the raddr of the incoming connection
162193}
163194
164- func (l * forwardList )add (addr net. Addr )chan forward {
195+ func (l * forwardList )add (n , addr string )chan forward {
165196l .Lock ()
166197defer l .Unlock ()
167198f := forwardEntry {
168- laddr :addr ,
169- c :make (chan forward ,1 ),
199+ addr :addr ,
200+ network :n ,
201+ c :make (chan forward ,1 ),
170202}
171203l .entries = append (l .entries ,f )
172204return f .c
@@ -185,19 +217,20 @@ func parseTCPAddr(addr string, port uint32) (*net.TCPAddr, error) {
185217if port == 0 || port > 65535 {
186218return nil ,fmt .Errorf ("ssh: port number out of range: %d" ,port )
187219}
188- ip := net . ParseIP ( string ( addr ) )
189- if ip = =nil {
220+ ip , err := netip . ParseAddr ( addr )
221+ if err ! =nil {
190222return nil ,fmt .Errorf ("ssh: cannot parse IP address %q" ,addr )
191223}
192- return & net.TCPAddr {IP :ip ,Port :int (port )},nil
224+ return & net.TCPAddr {IP :net . IP ( ip . AsSlice ()) ,Port :int (port )},nil
193225}
194226
195227func (l * forwardList )handleChannels (in <- chan NewChannel ) {
196228for ch := range in {
197229var (
198- laddr net.Addr
199- raddr net.Addr
200- err error
230+ addr string
231+ network string
232+ raddr net.Addr
233+ err error
201234)
202235switch channelType := ch .ChannelType ();channelType {
203236case "forwarded-tcpip" :
@@ -207,40 +240,34 @@ func (l *forwardList) handleChannels(in <-chan NewChannel) {
207240continue
208241}
209242
210- // RFC 4254 section 7.2 specifies that incoming
211- // addresses should list the address, in string
212- // format. It is implied that this should be an IP
213- // address, as it would be impossible to connect to it
214- // otherwise.
215- laddr ,err = parseTCPAddr (payload .Addr ,payload .Port )
216- if err != nil {
217- ch .Reject (ConnectionFailed ,err .Error ())
218- continue
219- }
243+ // RFC 4254 section 7.2 specifies that incoming addresses should
244+ // list the address that was connected, in string format. It is the
245+ // same address used in the tcpip-forward request. The originator
246+ // address is an IP address instead.
247+ addr = net .JoinHostPort (payload .Addr ,strconv .FormatUint (uint64 (payload .Port ),10 ))
248+
220249raddr ,err = parseTCPAddr (payload .OriginAddr ,payload .OriginPort )
221250if err != nil {
222251ch .Reject (ConnectionFailed ,err .Error ())
223252continue
224253}
225-
254+ network = "tcp"
226255case "forwarded-streamlocal@openssh.com" :
227256var payload forwardedStreamLocalPayload
228257if err = Unmarshal (ch .ExtraData (),& payload );err != nil {
229258ch .Reject (ConnectionFailed ,"could not parse forwarded-streamlocal@openssh.com payload: " + err .Error ())
230259continue
231260}
232- laddr = & net.UnixAddr {
233- Name :payload .SocketPath ,
234- Net :"unix" ,
235- }
261+ addr = payload .SocketPath
236262raddr = & net.UnixAddr {
237263Name :"@" ,
238264Net :"unix" ,
239265}
266+ network = "unix"
240267default :
241268panic (fmt .Errorf ("ssh: unknown channel type %s" ,channelType ))
242269}
243- if ok := l .forward (laddr ,raddr ,ch );! ok {
270+ if ok := l .forward (network , addr ,raddr ,ch );! ok {
244271// Section 7.2, implementations MUST reject spurious incoming
245272// connections.
246273ch .Reject (Prohibited ,"no forward for address" )
@@ -252,11 +279,11 @@ func (l *forwardList) handleChannels(in <-chan NewChannel) {
252279
253280// remove removes the forward entry, and the channel feeding its
254281// listener.
255- func (l * forwardList )remove (addr net. Addr ) {
282+ func (l * forwardList )remove (n , addr string ) {
256283l .Lock ()
257284defer l .Unlock ()
258285for i ,f := range l .entries {
259- if addr . Network () == f .laddr . Network () && addr . String () == f .laddr . String () {
286+ if n == f .network && addr == f .addr {
260287l .entries = append (l .entries [:i ],l .entries [i + 1 :]... )
261288close (f .c )
262289return
@@ -274,11 +301,11 @@ func (l *forwardList) closeAll() {
274301l .entries = nil
275302}
276303
277- func (l * forwardList )forward (laddr ,raddr net.Addr ,ch NewChannel )bool {
304+ func (l * forwardList )forward (n , addr string ,raddr net.Addr ,ch NewChannel )bool {
278305l .Lock ()
279306defer l .Unlock ()
280307for _ ,f := range l .entries {
281- if laddr . Network () == f .laddr . Network () && laddr . String () == f .laddr . String () {
308+ if n == f .network && addr == f .addr {
282309f .c <- forward {newCh :ch ,raddr :raddr }
283310return true
284311}
@@ -288,6 +315,7 @@ func (l *forwardList) forward(laddr, raddr net.Addr, ch NewChannel) bool {
288315
289316type tcpListener struct {
290317laddr * net.TCPAddr
318+ addr string
291319
292320conn * Client
293321in <- chan forward
@@ -314,13 +342,21 @@ func (l *tcpListener) Accept() (net.Conn, error) {
314342
315343// Close closes the listener.
316344func (l * tcpListener )Close ()error {
345+ host ,port ,err := net .SplitHostPort (l .addr )
346+ if err != nil {
347+ return err
348+ }
349+ rport ,err := strconv .ParseUint (port ,10 ,32 )
350+ if err != nil {
351+ return err
352+ }
317353m := channelForwardMsg {
318- l . laddr . IP . String () ,
319- uint32 (l . laddr . Port ),
354+ host ,
355+ uint32 (rport ),
320356}
321357
322358// this also closes the listener.
323- l .conn .forwards .remove (l . laddr )
359+ l .conn .forwards .remove ("tcp" , l . addr )
324360ok ,_ ,err := l .conn .SendRequest ("cancel-tcpip-forward" ,true ,Marshal (& m ))
325361if err == nil && ! ok {
326362err = errors .New ("ssh: cancel-tcpip-forward failed" )