Skip to content

Commit 79ec3a5

Browse files
drakkangopherbot
authored andcommitted
ssh: allow to bind to a hostname in remote forwarding
To avoid breaking backwards compatibility, we fix Listen, which receives the address as a string, while ListenTCP can still only be used with IP addresses. Fixes golang/go#33227 Fixes golang/go#37239 Change-Id: I4d45b40fdcb0d6012ed8da59a02149fa37e7db50 Reviewed-on: https://go-review.googlesource.com/c/crypto/+/599995 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Junyang Shao <[email protected]> Reviewed-by: Bishakh Ghosh <[email protected]> Reviewed-by: Filippo Valsorda <[email protected]> Auto-Submit: Nicola Murino <[email protected]> Reviewed-by: Michael Pratt <[email protected]>
1 parent 122a78f commit 79ec3a5

File tree

3 files changed

+86
-46
lines changed

3 files changed

+86
-46
lines changed

ssh/streamlocal.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func (c *Client) ListenUnix(socketPath string) (net.Listener, error) {
4444
if !ok {
4545
return nil, errors.New("ssh: [email protected] request denied by peer")
4646
}
47-
ch := c.forwards.add(&net.UnixAddr{Name: socketPath, Net: "unix"})
47+
ch := c.forwards.add("unix", socketPath)
4848

4949
return &unixListener{socketPath, c, ch}, nil
5050
}
@@ -96,7 +96,7 @@ func (l *unixListener) Accept() (net.Conn, error) {
9696
// Close closes the listener.
9797
func (l *unixListener) Close() error {
9898
// this also closes the listener.
99-
l.conn.forwards.remove(&net.UnixAddr{Name: l.socketPath, Net: "unix"})
99+
l.conn.forwards.remove("unix", l.socketPath)
100100
m := streamLocalChannelForwardMsg{
101101
l.socketPath,
102102
}

ssh/tcpip.go

Lines changed: 80 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
2529
func (c *Client) Listen(n, addr string) (net.Listener, error) {
2630
switch n {
2731
case "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)
2937
if err != nil {
3038
return nil, err
3139
}
32-
return c.ListenTCP(laddr)
40+
return c.listenTCPInternal(host, int(port))
3341
case "unix":
3442
return c.ListenUnix(addr)
3543
default:
@@ -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.
105116
func (c *Client) ListenTCP(laddr *net.TCPAddr) (net.Listener, error) {
106117
c.handleForwardsOnce.Do(c.handleForwards)
107118
if laddr.Port == 0 && isBrokenOpenSSHVersion(string(c.ServerVersion())) {
108119
return 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+
111128
m := channelForwardMsg{
112-
laddr.IP.String(),
113-
uint32(laddr.Port),
129+
host,
130+
uint32(port),
114131
}
115132
// send message
116133
ok, 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 {
127144
var p struct {
128145
Port uint32
129146
}
130147
if err := Unmarshal(resp, &p); err != nil {
131148
return 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.
151181
type 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 {
161192
raddr 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 {
165196
l.Lock()
166197
defer l.Unlock()
167198
f := forwardEntry{
168-
laddr: addr,
169-
c: make(chan forward, 1),
199+
addr: addr,
200+
network: n,
201+
c: make(chan forward, 1),
170202
}
171203
l.entries = append(l.entries, f)
172204
return f.c
@@ -185,19 +217,20 @@ func parseTCPAddr(addr string, port uint32) (*net.TCPAddr, error) {
185217
if port == 0 || port > 65535 {
186218
return 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 {
190222
return 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

195227
func (l *forwardList) handleChannels(in <-chan NewChannel) {
196228
for ch := range in {
197229
var (
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
)
202235
switch channelType := ch.ChannelType(); channelType {
203236
case "forwarded-tcpip":
@@ -207,40 +240,34 @@ func (l *forwardList) handleChannels(in <-chan NewChannel) {
207240
continue
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+
220249
raddr, err = parseTCPAddr(payload.OriginAddr, payload.OriginPort)
221250
if err != nil {
222251
ch.Reject(ConnectionFailed, err.Error())
223252
continue
224253
}
225-
254+
network = "tcp"
226255
227256
var payload forwardedStreamLocalPayload
228257
if err = Unmarshal(ch.ExtraData(), &payload); err != nil {
229258
ch.Reject(ConnectionFailed, "could not parse [email protected] payload: "+err.Error())
230259
continue
231260
}
232-
laddr = &net.UnixAddr{
233-
Name: payload.SocketPath,
234-
Net: "unix",
235-
}
261+
addr = payload.SocketPath
236262
raddr = &net.UnixAddr{
237263
Name: "@",
238264
Net: "unix",
239265
}
266+
network = "unix"
240267
default:
241268
panic(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.
246273
ch.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) {
256283
l.Lock()
257284
defer l.Unlock()
258285
for 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 {
260287
l.entries = append(l.entries[:i], l.entries[i+1:]...)
261288
close(f.c)
262289
return
@@ -274,11 +301,11 @@ func (l *forwardList) closeAll() {
274301
l.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 {
278305
l.Lock()
279306
defer l.Unlock()
280307
for _, f := range l.entries {
281-
if laddr.Network() == f.laddr.Network() && laddr.String() == f.laddr.String() {
308+
if n == f.network && addr == f.addr {
282309
f.c <- forward{newCh: ch, raddr: raddr}
283310
return true
284311
}
@@ -288,6 +315,7 @@ func (l *forwardList) forward(laddr, raddr net.Addr, ch NewChannel) bool {
288315

289316
type tcpListener struct {
290317
laddr *net.TCPAddr
318+
addr string
291319

292320
conn *Client
293321
in <-chan forward
@@ -314,13 +342,21 @@ func (l *tcpListener) Accept() (net.Conn, error) {
314342

315343
// Close closes the listener.
316344
func (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+
}
317353
m := 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)
324360
ok, _, err := l.conn.SendRequest("cancel-tcpip-forward", true, Marshal(&m))
325361
if err == nil && !ok {
326362
err = errors.New("ssh: cancel-tcpip-forward failed")

ssh/test/forward_unix_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ func testPortForward(t *testing.T, n, listenAddr string) {
5151
}
5252
}()
5353

54+
// The forwarded address match the listen address because we run the tests
55+
// on the same host.
5456
forwardedAddr := sshListener.Addr().String()
5557
netConn, err := net.Dial(n, forwardedAddr)
5658
if err != nil {
@@ -111,6 +113,8 @@ func testPortForward(t *testing.T, n, listenAddr string) {
111113
}
112114

113115
func TestPortForwardTCP(t *testing.T) {
116+
testPortForward(t, "tcp", ":0")
117+
testPortForward(t, "tcp", "[::]:0")
114118
testPortForward(t, "tcp", "localhost:0")
115119
}
116120

0 commit comments

Comments
 (0)