Statistics
| Branch: | Tag: | Revision:

root / ncclient / transport / ssh.py @ dd225c7a

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 4f650d54 Shikhar Bhushan
def default_unknown_host_cb(host, key):
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 216bb34c Shikhar Bhushan
    :arg host: the host for whom key needs to be verified
44 216bb34c Shikhar Bhushan
    :type host: string
45 4f650d54 Shikhar Bhushan

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

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

143 216bb34c Shikhar Bhushan
        To disable attemting publickey authentication altogether, call with
144 216bb34c Shikhar Bhushan
        *allow_agent* and *look_for_keys* as :const:`False`. This may be needed
145 216bb34c Shikhar Bhushan
        for Cisco devices which immediately disconnect on an incorrect
146 216bb34c Shikhar Bhushan
        authentication attempt.
147 4f650d54 Shikhar Bhushan

148 4f650d54 Shikhar Bhushan
        :arg host: the hostname or IP address to connect to
149 216bb34c Shikhar Bhushan
        :type host: `string`
150 4f650d54 Shikhar Bhushan

151 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
152 216bb34c Shikhar Bhushan
        :type port: `int`
153 4f650d54 Shikhar Bhushan

154 4f650d54 Shikhar Bhushan
        :arg timeout: an optional timeout for the TCP handshake
155 216bb34c Shikhar Bhushan
        :type timeout: `int`
156 4f650d54 Shikhar Bhushan

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

160 4f650d54 Shikhar Bhushan
        :arg username: the username to use for SSH authentication
161 216bb34c Shikhar Bhushan
        :type username: `string`
162 4f650d54 Shikhar Bhushan

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

166 4f650d54 Shikhar Bhushan
        :arg key_filename: a filename where a the private key to be used can be found
167 216bb34c Shikhar Bhushan
        :type key_filename: `string`
168 4f650d54 Shikhar Bhushan

169 4f650d54 Shikhar Bhushan
        :arg allow_agent: enables querying SSH agent (if found) for keys
170 216bb34c Shikhar Bhushan
        :type allow_agent: `bool`
171 4f650d54 Shikhar Bhushan

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