streamlocal.go raw
1 package ssh
2
3 import (
4 "errors"
5 "io"
6 "net"
7 )
8
9 // streamLocalChannelOpenDirectMsg is a struct used for SSH_MSG_CHANNEL_OPEN message
10 // with "direct-streamlocal@openssh.com" string.
11 //
12 // See openssh-portable/PROTOCOL, section 2.4. connection: Unix domain socket forwarding
13 // https://github.com/openssh/openssh-portable/blob/master/PROTOCOL#L235
14 type streamLocalChannelOpenDirectMsg struct {
15 socketPath string
16 reserved0 string
17 reserved1 uint32
18 }
19
20 // forwardedStreamLocalPayload is a struct used for SSH_MSG_CHANNEL_OPEN message
21 // with "forwarded-streamlocal@openssh.com" string.
22 type forwardedStreamLocalPayload struct {
23 SocketPath string
24 Reserved0 string
25 }
26
27 // streamLocalChannelForwardMsg is a struct used for SSH2_MSG_GLOBAL_REQUEST message
28 // with "streamlocal-forward@openssh.com"/"cancel-streamlocal-forward@openssh.com" string.
29 type streamLocalChannelForwardMsg struct {
30 socketPath string
31 }
32
33 // ListenUnix is similar to ListenTCP but uses a Unix domain socket.
34 func (c *Client) ListenUnix(socketPath string) (net.Listener, error) {
35 c.handleForwardsOnce.Do(c.handleForwards)
36 m := streamLocalChannelForwardMsg{
37 socketPath,
38 }
39 // send message
40 ok, _, err := c.SendRequest("streamlocal-forward@openssh.com", true, Marshal(&m))
41 if err != nil {
42 return nil, err
43 }
44 if !ok {
45 return nil, errors.New("ssh: streamlocal-forward@openssh.com request denied by peer")
46 }
47 ch := c.forwards.add("unix", socketPath)
48
49 return &unixListener{socketPath, c, ch}, nil
50 }
51
52 func (c *Client) dialStreamLocal(socketPath string) (Channel, error) {
53 msg := streamLocalChannelOpenDirectMsg{
54 socketPath: socketPath,
55 }
56 ch, in, err := c.OpenChannel("direct-streamlocal@openssh.com", Marshal(&msg))
57 if err != nil {
58 return nil, err
59 }
60 go DiscardRequests(in)
61 return ch, err
62 }
63
64 type unixListener struct {
65 socketPath string
66
67 conn *Client
68 in <-chan forward
69 }
70
71 // Accept waits for and returns the next connection to the listener.
72 func (l *unixListener) Accept() (net.Conn, error) {
73 s, ok := <-l.in
74 if !ok {
75 return nil, io.EOF
76 }
77 ch, incoming, err := s.newCh.Accept()
78 if err != nil {
79 return nil, err
80 }
81 go DiscardRequests(incoming)
82
83 return &chanConn{
84 Channel: ch,
85 laddr: &net.UnixAddr{
86 Name: l.socketPath,
87 Net: "unix",
88 },
89 raddr: &net.UnixAddr{
90 Name: "@",
91 Net: "unix",
92 },
93 }, nil
94 }
95
96 // Close closes the listener.
97 func (l *unixListener) Close() error {
98 // this also closes the listener.
99 l.conn.forwards.remove("unix", l.socketPath)
100 m := streamLocalChannelForwardMsg{
101 l.socketPath,
102 }
103 ok, _, err := l.conn.SendRequest("cancel-streamlocal-forward@openssh.com", true, Marshal(&m))
104 if err == nil && !ok {
105 err = errors.New("ssh: cancel-streamlocal-forward@openssh.com failed")
106 }
107 return err
108 }
109
110 // Addr returns the listener's network address.
111 func (l *unixListener) Addr() net.Addr {
112 return &net.UnixAddr{
113 Name: l.socketPath,
114 Net: "unix",
115 }
116 }
117