Statistics
| Branch: | Tag: | Revision:

root / ncclient / transport / ssh.py @ 0c608b53

History | View | Annotate | Download (12.5 kB)

1 d095a59e Shikhar Bhushan
# Copyright 2009 Shikhar Bhushan
2 d095a59e Shikhar Bhushan
#
3 d095a59e Shikhar Bhushan
# Licensed under the Apache License, Version 2.0 (the "License");
4 d095a59e Shikhar Bhushan
# you may not use this file except in compliance with the License.
5 d095a59e Shikhar Bhushan
# You may obtain a copy of the License at
6 d095a59e Shikhar Bhushan
#
7 d095a59e Shikhar Bhushan
#    http://www.apache.org/licenses/LICENSE-2.0
8 d095a59e Shikhar Bhushan
#
9 d095a59e Shikhar Bhushan
# Unless required by applicable law or agreed to in writing, software
10 d095a59e Shikhar Bhushan
# distributed under the License is distributed on an "AS IS" BASIS,
11 d095a59e Shikhar Bhushan
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 d095a59e Shikhar Bhushan
# See the License for the specific language governing permissions and
13 d095a59e Shikhar Bhushan
# limitations under the License.
14 d095a59e Shikhar Bhushan
15 d095a59e Shikhar Bhushan
import os
16 d095a59e Shikhar Bhushan
import socket
17 d095a59e Shikhar Bhushan
from binascii import hexlify
18 d095a59e Shikhar Bhushan
from cStringIO import StringIO
19 d095a59e Shikhar Bhushan
from select import select
20 d095a59e Shikhar Bhushan
21 d095a59e Shikhar Bhushan
import paramiko
22 d095a59e Shikhar Bhushan
23 c35cebbf Shikhar Bhushan
from errors import AuthenticationError, SessionCloseError, SSHError, SSHUnknownHostError
24 d095a59e Shikhar Bhushan
from session import Session
25 d095a59e Shikhar Bhushan
26 41e2ed46 Shikhar Bhushan
import logging
27 41e2ed46 Shikhar Bhushan
logger = logging.getLogger('ncclient.transport.ssh')
28 41e2ed46 Shikhar Bhushan
29 d095a59e Shikhar Bhushan
BUF_SIZE = 4096
30 d095a59e Shikhar Bhushan
MSG_DELIM = ']]>]]>'
31 d095a59e Shikhar Bhushan
TICK = 0.1
32 d095a59e Shikhar Bhushan
33 9a9af391 Shikhar Bhushan
def default_unknown_host_cb(host, fingerprint):
34 4f650d54 Shikhar Bhushan
    """An `unknown host callback` returns :const:`True` if it finds the key
35 4f650d54 Shikhar Bhushan
    acceptable, and :const:`False` if not.
36 4f650d54 Shikhar Bhushan

37 216bb34c Shikhar Bhushan
    This default callback always returns :const:`False`, which would lead to
38 216bb34c Shikhar Bhushan
    :meth:`connect` raising a :exc:`SSHUnknownHost` exception.
39 4f650d54 Shikhar Bhushan

40 216bb34c Shikhar Bhushan
    Supply another valid callback if you need to verify the host key
41 216bb34c Shikhar Bhushan
    programatically.
42 216bb34c Shikhar Bhushan

43 9a9af391 Shikhar Bhushan
    :arg host: the hostname that needs to be verified
44 216bb34c Shikhar Bhushan
    :type host: string
45 4f650d54 Shikhar Bhushan

46 9a9af391 Shikhar Bhushan
    :arg fingerprint: a hex string representing the host key fingerprint
47 9a9af391 Shikhar Bhushan
    :type fingerprint: string
48 4f650d54 Shikhar Bhushan
    """
49 4f650d54 Shikhar Bhushan
    return False
50 4f650d54 Shikhar Bhushan
51 9a9af391 Shikhar Bhushan
def _colonify(fp):
52 9a9af391 Shikhar Bhushan
    finga = fp[:2]
53 9a9af391 Shikhar Bhushan
    for idx  in range(2, len(fp), 2):
54 9a9af391 Shikhar Bhushan
        finga += ":" + fp[idx:idx+2]
55 9a9af391 Shikhar Bhushan
    return finga
56 4f650d54 Shikhar Bhushan
57 d095a59e Shikhar Bhushan
class SSHSession(Session):
58 4f650d54 Shikhar Bhushan
59 4f650d54 Shikhar Bhushan
    "Implements a :rfc:`4742` NETCONF session over SSH."
60 4f650d54 Shikhar Bhushan
61 583c11f6 Shikhar Bhushan
    def __init__(self, capabilities):
62 583c11f6 Shikhar Bhushan
        Session.__init__(self, capabilities)
63 d095a59e Shikhar Bhushan
        self._host_keys = paramiko.HostKeys()
64 d095a59e Shikhar Bhushan
        self._transport = None
65 d095a59e Shikhar Bhushan
        self._connected = False
66 d095a59e Shikhar Bhushan
        self._channel = None
67 d095a59e Shikhar Bhushan
        self._buffer = StringIO() # for incoming data
68 d095a59e Shikhar Bhushan
        # parsing-related, see _parse()
69 4f650d54 Shikhar Bhushan
        self._parsing_state = 0
70 d095a59e Shikhar Bhushan
        self._parsing_pos = 0
71 9a9af391 Shikhar Bhushan
    
72 d095a59e Shikhar Bhushan
    def _parse(self):
73 d095a59e Shikhar Bhushan
        '''Messages ae delimited by MSG_DELIM. The buffer could have grown by a
74 d095a59e Shikhar Bhushan
        maximum of BUF_SIZE bytes everytime this method is called. Retains state
75 4f650d54 Shikhar Bhushan
        across method calls and if a byte has been read it will not be
76 4f650d54 Shikhar Bhushan
        considered again. '''
77 d095a59e Shikhar Bhushan
        delim = MSG_DELIM
78 d095a59e Shikhar Bhushan
        n = len(delim) - 1
79 d095a59e Shikhar Bhushan
        expect = self._parsing_state
80 d095a59e Shikhar Bhushan
        buf = self._buffer
81 d095a59e Shikhar Bhushan
        buf.seek(self._parsing_pos)
82 d095a59e Shikhar Bhushan
        while True:
83 d095a59e Shikhar Bhushan
            x = buf.read(1)
84 d095a59e Shikhar Bhushan
            if not x: # done reading
85 d095a59e Shikhar Bhushan
                break
86 d095a59e Shikhar Bhushan
            elif x == delim[expect]: # what we expected
87 d095a59e Shikhar Bhushan
                expect += 1 # expect the next delim char
88 d095a59e Shikhar Bhushan
            else:
89 0c608b53 Shikhar Bhushan
                expect = 0
90 d095a59e Shikhar Bhushan
                continue
91 d095a59e Shikhar Bhushan
            # loop till last delim char expected, break if other char encountered
92 d095a59e Shikhar Bhushan
            for i in range(expect, n):
93 d095a59e Shikhar Bhushan
                x = buf.read(1)
94 d095a59e Shikhar Bhushan
                if not x: # done reading
95 d095a59e Shikhar Bhushan
                    break
96 d095a59e Shikhar Bhushan
                if x == delim[expect]: # what we expected
97 d095a59e Shikhar Bhushan
                    expect += 1 # expect the next delim char
98 d095a59e Shikhar Bhushan
                else:
99 d095a59e Shikhar Bhushan
                    expect = 0 # reset
100 d095a59e Shikhar Bhushan
                    break
101 d095a59e Shikhar Bhushan
            else: # if we didn't break out of the loop, full delim was parsed
102 d095a59e Shikhar Bhushan
                msg_till = buf.tell() - n
103 d095a59e Shikhar Bhushan
                buf.seek(0)
104 41e2ed46 Shikhar Bhushan
                logger.debug('parsed new message')
105 41e2ed46 Shikhar Bhushan
                self._dispatch_message(buf.read(msg_till).strip())
106 d095a59e Shikhar Bhushan
                buf.seek(n+1, os.SEEK_CUR)
107 d095a59e Shikhar Bhushan
                rest = buf.read()
108 d095a59e Shikhar Bhushan
                buf = StringIO()
109 d095a59e Shikhar Bhushan
                buf.write(rest)
110 d095a59e Shikhar Bhushan
                buf.seek(0)
111 4ba5e843 Shikhar Bhushan
                expect = 0
112 d095a59e Shikhar Bhushan
        self._buffer = buf
113 d095a59e Shikhar Bhushan
        self._parsing_state = expect
114 d095a59e Shikhar Bhushan
        self._parsing_pos = self._buffer.tell()
115 4f650d54 Shikhar Bhushan
116 216bb34c Shikhar Bhushan
    def load_known_hosts(self, filename=None):
117 216bb34c Shikhar Bhushan
        """Load host keys from a :file:`known_hosts`-style file. Can be called multiple
118 216bb34c Shikhar Bhushan
        times.
119 216bb34c Shikhar Bhushan

120 216bb34c Shikhar Bhushan
        If *filename* is not specified, looks in the default locations i.e.
121 216bb34c Shikhar Bhushan
        :file:`~/.ssh/known_hosts` and :file:`~/ssh/known_hosts` for Windows.
122 216bb34c Shikhar Bhushan
        """
123 d095a59e Shikhar Bhushan
        if filename is None:
124 d095a59e Shikhar Bhushan
            filename = os.path.expanduser('~/.ssh/known_hosts')
125 d095a59e Shikhar Bhushan
            try:
126 216bb34c Shikhar Bhushan
                self._host_keys.load(filename)
127 d095a59e Shikhar Bhushan
            except IOError:
128 4ba5e843 Shikhar Bhushan
                # for windows
129 4ba5e843 Shikhar Bhushan
                filename = os.path.expanduser('~/ssh/known_hosts')
130 4ba5e843 Shikhar Bhushan
                try:
131 216bb34c Shikhar Bhushan
                    self._host_keys.load(filename)
132 4ba5e843 Shikhar Bhushan
                except IOError:
133 4ba5e843 Shikhar Bhushan
                    pass
134 216bb34c Shikhar Bhushan
        else:
135 216bb34c Shikhar Bhushan
            self._host_keys.load(filename)
136 4f650d54 Shikhar Bhushan
137 d095a59e Shikhar Bhushan
    def close(self):
138 d095a59e Shikhar Bhushan
        if self._transport.is_active():
139 d095a59e Shikhar Bhushan
            self._transport.close()
140 d095a59e Shikhar Bhushan
        self._connected = False
141 4f650d54 Shikhar Bhushan
142 583c11f6 Shikhar Bhushan
    def connect(self, host, port=830, timeout=None,
143 4f650d54 Shikhar Bhushan
                unknown_host_cb=default_unknown_host_cb,
144 4f650d54 Shikhar Bhushan
                username=None, password=None,
145 d095a59e Shikhar Bhushan
                key_filename=None, allow_agent=True, look_for_keys=True):
146 4f650d54 Shikhar Bhushan
        """Connect via SSH and initialize the NETCONF session. First attempts
147 4f650d54 Shikhar Bhushan
        the publickey authentication method and then password authentication.
148 4f650d54 Shikhar Bhushan

149 216bb34c Shikhar Bhushan
        To disable attemting publickey authentication altogether, call with
150 216bb34c Shikhar Bhushan
        *allow_agent* and *look_for_keys* as :const:`False`. This may be needed
151 216bb34c Shikhar Bhushan
        for Cisco devices which immediately disconnect on an incorrect
152 216bb34c Shikhar Bhushan
        authentication attempt.
153 4f650d54 Shikhar Bhushan

154 4f650d54 Shikhar Bhushan
        :arg host: the hostname or IP address to connect to
155 216bb34c Shikhar Bhushan
        :type host: `string`
156 4f650d54 Shikhar Bhushan

157 4f650d54 Shikhar Bhushan
        :arg port: by default 830, but some devices use the default SSH port of 22 so this may need to be specified
158 216bb34c Shikhar Bhushan
        :type port: `int`
159 4f650d54 Shikhar Bhushan

160 4f650d54 Shikhar Bhushan
        :arg timeout: an optional timeout for the TCP handshake
161 216bb34c Shikhar Bhushan
        :type timeout: `int`
162 4f650d54 Shikhar Bhushan

163 216bb34c Shikhar Bhushan
        :arg unknown_host_cb: called when a host key is not recognized
164 216bb34c Shikhar Bhushan
        :type unknown_host_cb: see :meth:`signature <ssh.default_unknown_host_cb>`
165 4f650d54 Shikhar Bhushan

166 4f650d54 Shikhar Bhushan
        :arg username: the username to use for SSH authentication
167 216bb34c Shikhar Bhushan
        :type username: `string`
168 4f650d54 Shikhar Bhushan

169 216bb34c Shikhar Bhushan
        :arg password: the password used if using password authentication, or the passphrase to use for unlocking keys that require it
170 216bb34c Shikhar Bhushan
        :type password: `string`
171 4f650d54 Shikhar Bhushan

172 4f650d54 Shikhar Bhushan
        :arg key_filename: a filename where a the private key to be used can be found
173 216bb34c Shikhar Bhushan
        :type key_filename: `string`
174 4f650d54 Shikhar Bhushan

175 4f650d54 Shikhar Bhushan
        :arg allow_agent: enables querying SSH agent (if found) for keys
176 216bb34c Shikhar Bhushan
        :type allow_agent: `bool`
177 4f650d54 Shikhar Bhushan

178 4f650d54 Shikhar Bhushan
        :arg look_for_keys: enables looking in the usual locations for ssh keys (e.g. :file:`~/.ssh/id_*`)
179 216bb34c Shikhar Bhushan
        :type look_for_keys: `bool`
180 4f650d54 Shikhar Bhushan
        """
181 4f650d54 Shikhar Bhushan
182 bb700ea5 Shikhar Bhushan
        if username is None:
183 bb700ea5 Shikhar Bhushan
            raise SSHError("No username specified")
184 4f650d54 Shikhar Bhushan
185 bb700ea5 Shikhar Bhushan
        sock = None
186 bb700ea5 Shikhar Bhushan
        for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM):
187 bb700ea5 Shikhar Bhushan
            af, socktype, proto, canonname, sa = res
188 bb700ea5 Shikhar Bhushan
            try:
189 bb700ea5 Shikhar Bhushan
                sock = socket.socket(af, socktype, proto)
190 bb700ea5 Shikhar Bhushan
                sock.settimeout(timeout)
191 bb700ea5 Shikhar Bhushan
            except socket.error:
192 bb700ea5 Shikhar Bhushan
                continue
193 bb700ea5 Shikhar Bhushan
            try:
194 bb700ea5 Shikhar Bhushan
                sock.connect(sa)
195 bb700ea5 Shikhar Bhushan
            except socket.error:
196 bb700ea5 Shikhar Bhushan
                sock.close()
197 bb700ea5 Shikhar Bhushan
                continue
198 bb700ea5 Shikhar Bhushan
            break
199 d095a59e Shikhar Bhushan
        else:
200 bb700ea5 Shikhar Bhushan
            raise SSHError("Could not open socket")
201 bb700ea5 Shikhar Bhushan
202 d095a59e Shikhar Bhushan
        t = self._transport = paramiko.Transport(sock)
203 d095a59e Shikhar Bhushan
        t.set_log_channel(logger.name)
204 4f650d54 Shikhar Bhushan
205 d095a59e Shikhar Bhushan
        try:
206 d095a59e Shikhar Bhushan
            t.start_client()
207 d095a59e Shikhar Bhushan
        except paramiko.SSHException:
208 d095a59e Shikhar Bhushan
            raise SSHError('Negotiation failed')
209 4f650d54 Shikhar Bhushan
210 d095a59e Shikhar Bhushan
        # host key verification
211 d095a59e Shikhar Bhushan
        server_key = t.get_remote_server_key()
212 216bb34c Shikhar Bhushan
        known_host = self._host_keys.check(host, server_key)
213 216bb34c Shikhar Bhushan
214 9a9af391 Shikhar Bhushan
        fingerprint = _colonify(hexlify(server_key.get_fingerprint()))
215 4f650d54 Shikhar Bhushan
216 216bb34c Shikhar Bhushan
        if not known_host and not unknown_host_cb(host, fingerprint):
217 216bb34c Shikhar Bhushan
            raise SSHUnknownHostError(host, fingerprint)
218 4f650d54 Shikhar Bhushan
219 d095a59e Shikhar Bhushan
        if key_filename is None:
220 d095a59e Shikhar Bhushan
            key_filenames = []
221 d095a59e Shikhar Bhushan
        elif isinstance(key_filename, basestring):
222 d095a59e Shikhar Bhushan
            key_filenames = [ key_filename ]
223 d095a59e Shikhar Bhushan
        else:
224 d095a59e Shikhar Bhushan
            key_filenames = key_filename
225 4f650d54 Shikhar Bhushan
226 d095a59e Shikhar Bhushan
        self._auth(username, password, key_filenames, allow_agent, look_for_keys)
227 4f650d54 Shikhar Bhushan
228 d095a59e Shikhar Bhushan
        self._connected = True # there was no error authenticating
229 4f650d54 Shikhar Bhushan
230 d095a59e Shikhar Bhushan
        c = self._channel = self._transport.open_session()
231 d095a59e Shikhar Bhushan
        c.set_name('netconf')
232 41e2ed46 Shikhar Bhushan
        c.invoke_subsystem('netconf')
233 4f650d54 Shikhar Bhushan
234 d095a59e Shikhar Bhushan
        self._post_connect()
235 9a9af391 Shikhar Bhushan
    
236 d095a59e Shikhar Bhushan
    # on the lines of paramiko.SSHClient._auth()
237 d095a59e Shikhar Bhushan
    def _auth(self, username, password, key_filenames, allow_agent,
238 d095a59e Shikhar Bhushan
              look_for_keys):
239 d095a59e Shikhar Bhushan
        saved_exception = None
240 4f650d54 Shikhar Bhushan
241 d095a59e Shikhar Bhushan
        for key_filename in key_filenames:
242 d095a59e Shikhar Bhushan
            for cls in (paramiko.RSAKey, paramiko.DSSKey):
243 d095a59e Shikhar Bhushan
                try:
244 d095a59e Shikhar Bhushan
                    key = cls.from_private_key_file(key_filename, password)
245 d095a59e Shikhar Bhushan
                    logger.debug('Trying key %s from %s' %
246 d095a59e Shikhar Bhushan
                              (hexlify(key.get_fingerprint()), key_filename))
247 d095a59e Shikhar Bhushan
                    self._transport.auth_publickey(username, key)
248 d095a59e Shikhar Bhushan
                    return
249 d095a59e Shikhar Bhushan
                except Exception as e:
250 d095a59e Shikhar Bhushan
                    saved_exception = e
251 d095a59e Shikhar Bhushan
                    logger.debug(e)
252 4f650d54 Shikhar Bhushan
253 d095a59e Shikhar Bhushan
        if allow_agent:
254 d095a59e Shikhar Bhushan
            for key in paramiko.Agent().get_keys():
255 d095a59e Shikhar Bhushan
                try:
256 d095a59e Shikhar Bhushan
                    logger.debug('Trying SSH agent key %s' %
257 d095a59e Shikhar Bhushan
                                 hexlify(key.get_fingerprint()))
258 d095a59e Shikhar Bhushan
                    self._transport.auth_publickey(username, key)
259 d095a59e Shikhar Bhushan
                    return
260 d095a59e Shikhar Bhushan
                except Exception as e:
261 d095a59e Shikhar Bhushan
                    saved_exception = e
262 d095a59e Shikhar Bhushan
                    logger.debug(e)
263 4f650d54 Shikhar Bhushan
264 d095a59e Shikhar Bhushan
        keyfiles = []
265 d095a59e Shikhar Bhushan
        if look_for_keys:
266 d095a59e Shikhar Bhushan
            rsa_key = os.path.expanduser('~/.ssh/id_rsa')
267 d095a59e Shikhar Bhushan
            dsa_key = os.path.expanduser('~/.ssh/id_dsa')
268 d095a59e Shikhar Bhushan
            if os.path.isfile(rsa_key):
269 d095a59e Shikhar Bhushan
                keyfiles.append((paramiko.RSAKey, rsa_key))
270 d095a59e Shikhar Bhushan
            if os.path.isfile(dsa_key):
271 d095a59e Shikhar Bhushan
                keyfiles.append((paramiko.DSSKey, dsa_key))
272 d095a59e Shikhar Bhushan
            # look in ~/ssh/ for windows users:
273 d095a59e Shikhar Bhushan
            rsa_key = os.path.expanduser('~/ssh/id_rsa')
274 d095a59e Shikhar Bhushan
            dsa_key = os.path.expanduser('~/ssh/id_dsa')
275 d095a59e Shikhar Bhushan
            if os.path.isfile(rsa_key):
276 d095a59e Shikhar Bhushan
                keyfiles.append((paramiko.RSAKey, rsa_key))
277 d095a59e Shikhar Bhushan
            if os.path.isfile(dsa_key):
278 d095a59e Shikhar Bhushan
                keyfiles.append((paramiko.DSSKey, dsa_key))
279 4f650d54 Shikhar Bhushan
280 d095a59e Shikhar Bhushan
        for cls, filename in keyfiles:
281 d095a59e Shikhar Bhushan
            try:
282 d095a59e Shikhar Bhushan
                key = cls.from_private_key_file(filename, password)
283 d095a59e Shikhar Bhushan
                logger.debug('Trying discovered key %s in %s' %
284 d095a59e Shikhar Bhushan
                          (hexlify(key.get_fingerprint()), filename))
285 d095a59e Shikhar Bhushan
                self._transport.auth_publickey(username, key)
286 d095a59e Shikhar Bhushan
                return
287 d095a59e Shikhar Bhushan
            except Exception as e:
288 d095a59e Shikhar Bhushan
                saved_exception = e
289 d095a59e Shikhar Bhushan
                logger.debug(e)
290 4f650d54 Shikhar Bhushan
291 d095a59e Shikhar Bhushan
        if password is not None:
292 d095a59e Shikhar Bhushan
            try:
293 d095a59e Shikhar Bhushan
                self._transport.auth_password(username, password)
294 d095a59e Shikhar Bhushan
                return
295 d095a59e Shikhar Bhushan
            except Exception as e:
296 d095a59e Shikhar Bhushan
                saved_exception = e
297 d095a59e Shikhar Bhushan
                logger.debug(e)
298 4f650d54 Shikhar Bhushan
299 d095a59e Shikhar Bhushan
        if saved_exception is not None:
300 541247ba Shikhar Bhushan
            # need pep-3134 to do this right
301 a7cb58ce Shikhar Bhushan
            raise AuthenticationError(repr(saved_exception))
302 4f650d54 Shikhar Bhushan
303 a7cb58ce Shikhar Bhushan
        raise AuthenticationError('No authentication methods available')
304 4f650d54 Shikhar Bhushan
305 d095a59e Shikhar Bhushan
    def run(self):
306 d095a59e Shikhar Bhushan
        chan = self._channel
307 d095a59e Shikhar Bhushan
        chan.setblocking(0)
308 d095a59e Shikhar Bhushan
        q = self._q
309 d095a59e Shikhar Bhushan
        try:
310 d095a59e Shikhar Bhushan
            while True:
311 d095a59e Shikhar Bhushan
                # select on a paramiko ssh channel object does not ever return
312 4f650d54 Shikhar Bhushan
                # it in the writable list, so it channel's don't exactly emulate
313 d095a59e Shikhar Bhushan
                # the socket api
314 d095a59e Shikhar Bhushan
                r, w, e = select([chan], [], [], TICK)
315 d095a59e Shikhar Bhushan
                # will wakeup evey TICK seconds to check if something
316 d095a59e Shikhar Bhushan
                # to send, more if something to read (due to select returning
317 d095a59e Shikhar Bhushan
                # chan in readable list)
318 d095a59e Shikhar Bhushan
                if r:
319 d095a59e Shikhar Bhushan
                    data = chan.recv(BUF_SIZE)
320 d095a59e Shikhar Bhushan
                    if data:
321 d095a59e Shikhar Bhushan
                        self._buffer.write(data)
322 d095a59e Shikhar Bhushan
                        self._parse()
323 d095a59e Shikhar Bhushan
                    else:
324 94265508 Shikhar Bhushan
                        raise SessionCloseError(self._buffer.getvalue())
325 d095a59e Shikhar Bhushan
                if not q.empty() and chan.send_ready():
326 41e2ed46 Shikhar Bhushan
                    logger.debug('sending message')
327 d095a59e Shikhar Bhushan
                    data = q.get() + MSG_DELIM
328 d095a59e Shikhar Bhushan
                    while data:
329 d095a59e Shikhar Bhushan
                        n = chan.send(data)
330 d095a59e Shikhar Bhushan
                        if n <= 0:
331 94265508 Shikhar Bhushan
                            raise SessionCloseError(self._buffer.getvalue(), data)
332 d095a59e Shikhar Bhushan
                        data = data[n:]
333 d095a59e Shikhar Bhushan
        except Exception as e:
334 564bee4f Shikhar Bhushan
            logger.debug('broke out of main loop, error=%r', e)
335 1d540e60 Shikhar Bhushan
            self.close()
336 564bee4f Shikhar Bhushan
            self._dispatch_error(e)
337 4f650d54 Shikhar Bhushan
338 d095a59e Shikhar Bhushan
    @property
339 d095a59e Shikhar Bhushan
    def transport(self):
340 216bb34c Shikhar Bhushan
        """Underlying `paramiko.Transport
341 4f650d54 Shikhar Bhushan
        <http://www.lag.net/paramiko/docs/paramiko.Transport-class.html>`_
342 4f650d54 Shikhar Bhushan
        object. This makes it possible to call methods like set_keepalive on it.
343 4f650d54 Shikhar Bhushan
        """
344 d095a59e Shikhar Bhushan
        return self._transport