This repository has been archived by the owner on Oct 13, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
session.go
198 lines (166 loc) · 5.52 KB
/
session.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
package netconf
import (
"fmt"
"io"
"strings"
"time"
"golang.org/x/crypto/ssh"
)
// Session wraps an *ssh.Session providing additional NETCONF functionality.
// An initialized Session is a io.ReadWriteCloser, with the io.Reader connected
// to the ssh.Session's stdout, and the io.WriteCloser connected to the
// ssh.Sessions's stdin.
type Session struct {
reader io.Reader
writeCloser io.WriteCloser
sshSession *ssh.Session
sshClient *ssh.Client
}
// NewSession creates a new session ready for use with the NETCONF SSH subsystem.
// It uses the credentials given by the ssh.ClientConfig argument to connect to
// the target.
// Hello messages are negotiated, and the server's hello message is returned along
// with a newly allocated Session pointer.
func NewSession(clientConfig *ssh.ClientConfig, target string) (*Session, *HelloMessage, error) {
var session Session
var err error
session.sshClient, err = ssh.Dial("tcp", target, clientConfig)
if err != nil {
return nil, nil, err
}
if session.sshSession, err = session.sshClient.NewSession(); err != nil {
_ = session.sshClient.Close()
return nil, nil, err
}
closeAll := func() {
_ = session.sshClient.Close()
_ = session.sshSession.Close()
}
if err := session.sshSession.RequestSubsystem("netconf"); err != nil {
closeAll()
return nil, nil, err
}
if session.reader, err = session.sshSession.StdoutPipe(); err != nil {
closeAll()
return nil, nil, err
}
if session.writeCloser, err = session.sshSession.StdinPipe(); err != nil {
closeAll()
return nil, nil, err
}
var helloMessage HelloMessage
if err := session.NewDecoder().DecodeHello(&helloMessage); err != nil {
closeAll()
return nil, nil, err
}
if _, err := io.Copy(&session, strings.NewReader(DefaultHelloMessage)); err != nil {
closeAll()
return nil, nil, err
}
return &session, &helloMessage, nil
}
// NewReplyReader returns a ReplyReader that reads exactly one
// NETCONF RPC Reply from the session's stdout stream. The ReplyReader
// strictly satisfies io.Reader interface by reading from the stream
// until the NETCONF message separator "]]>]]>" is reached, and an io.EOF
// error is returned. The io.EOF error is also returned on all subsequent
// calls.
//
// The ReplyReader does not close the underlying session. Multiple
// ReplyReaders are required to read multiple replies from the same session.
func (s *Session) NewReplyReader() *ReplyReader {
return NewReplyReader(s)
}
// Read is a partial implementation of the io.Reader interface.
// It reads directly from the session without any modifications.
// It is not compliant with the standard io.Reader interface
// because an EOF is only returned if the session is closed.
//
// Most will use ReplyReader or Decoder.
func (s *Session) Read(p []byte) (n int, err error) {
return s.reader.Read(p)
}
// Write is the most basic implementation of the io.Writer
// interface. It writes directly to the stdin stream of the
// NETCONF session, and does not write a NETCONF message
// separator "]]>]]>".
//
// Most will use Encoder.
func (s *Session) Write(p []byte) (n int, err error) {
return s.writeCloser.Write(p)
}
// Close closes all session resources in the following order:
//
// 1. stdin pipe
// 2. SSH session
// 3. SSH client
//
// Errors are returned with priority matching the same order.
func (s *Session) Close() error {
var (
writeCloseErr error
sshSessionCloseErr error
sshClientCloseErr error
)
if s.writeCloser != nil {
writeCloseErr = s.writeCloser.Close()
}
if s.sshSession != nil {
sshSessionCloseErr = s.sshSession.Close()
}
if s.sshClient != nil {
sshClientCloseErr = s.sshClient.Close()
}
if writeCloseErr != nil {
return writeCloseErr
}
if sshSessionCloseErr != nil {
return sshSessionCloseErr
}
return sshClientCloseErr
}
// NewDecoder returns a new Decoder object attached to the stdout pipe
// of the underlying SSH session.
func (s *Session) NewDecoder() *Decoder {
return NewDecoder(s.reader)
}
// NewTimeoutDecoder returns a new Decoder attached to the stdout pipe
// of the underlying SSH session. The Decoder's io.TrimReader is wrapped to set a read
// timeout on the underlying net.Conn before every read.
func (s *Session) NewTimeoutDecoder(timeout time.Duration) *Decoder {
return NewDecoder(s.NewDeadlineReader(timeout))
}
// DeadlineError is returned when a read or write deadline is reached.
type DeadlineError struct {
Op string
BeginTime time.Time
FailTime time.Time
Deadline time.Duration
}
// Error implements the error interface.
func (te *DeadlineError) Error() string {
return fmt.Sprintf("netconf: %s deadline %s began %s expired %s",
te.Op, te.Deadline, te.BeginTime, te.FailTime)
}
// NewDeadlineReader decorates the session's io.Reader with
// a new DeadlineReader.
//
// The DeadlineReader only adds a deadline when reading from
// the stream. It does not handle higher level functionality,
// like a complete implementation of an io.Reader, or discarding
// NETCONF message separators.
func (s *Session) NewDeadlineReader(deadline time.Duration) io.Reader {
return &DeadlineReader{
reader: s.reader,
deadline: deadline,
}
}
// NewEncoder returns a new Encoder object attached to the stdin pipe
// of the underlying SSH session.
func (s *Session) NewEncoder() *Encoder {
return NewEncoder(s.writeCloser)
}
// TODO: Make RPCWriter that handles writing NETCONF message separators.
// TODO: Make all other readers and writers start with the ReplyReader, and
// TODO: RPCWriter, which has the sole job of implementing the standard
// TODO: io.Reader and io.Writer interfaces.