test_connection.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730
  1. # Copyright (c) 2007-2010 Twisted Matrix Laboratories.
  2. # See LICENSE for details
  3. """
  4. This module tests twisted.conch.ssh.connection.
  5. """
  6. from __future__ import division, absolute_import
  7. import struct
  8. from twisted.conch import error
  9. from twisted.conch.ssh import channel, common, connection
  10. from twisted.python.compat import long
  11. from twisted.trial import unittest
  12. from twisted.conch.test import test_userauth
  13. class TestChannel(channel.SSHChannel):
  14. """
  15. A mocked-up version of twisted.conch.ssh.channel.SSHChannel.
  16. @ivar gotOpen: True if channelOpen has been called.
  17. @type gotOpen: L{bool}
  18. @ivar specificData: the specific channel open data passed to channelOpen.
  19. @type specificData: L{bytes}
  20. @ivar openFailureReason: the reason passed to openFailed.
  21. @type openFailed: C{error.ConchError}
  22. @ivar inBuffer: a C{list} of strings received by the channel.
  23. @type inBuffer: C{list}
  24. @ivar extBuffer: a C{list} of 2-tuples (type, extended data) of received by
  25. the channel.
  26. @type extBuffer: C{list}
  27. @ivar numberRequests: the number of requests that have been made to this
  28. channel.
  29. @type numberRequests: L{int}
  30. @ivar gotEOF: True if the other side sent EOF.
  31. @type gotEOF: L{bool}
  32. @ivar gotOneClose: True if the other side closed the connection.
  33. @type gotOneClose: L{bool}
  34. @ivar gotClosed: True if the channel is closed.
  35. @type gotClosed: L{bool}
  36. """
  37. name = b"TestChannel"
  38. gotOpen = False
  39. def logPrefix(self):
  40. return "TestChannel %i" % self.id
  41. def channelOpen(self, specificData):
  42. """
  43. The channel is open. Set up the instance variables.
  44. """
  45. self.gotOpen = True
  46. self.specificData = specificData
  47. self.inBuffer = []
  48. self.extBuffer = []
  49. self.numberRequests = 0
  50. self.gotEOF = False
  51. self.gotOneClose = False
  52. self.gotClosed = False
  53. def openFailed(self, reason):
  54. """
  55. Opening the channel failed. Store the reason why.
  56. """
  57. self.openFailureReason = reason
  58. def request_test(self, data):
  59. """
  60. A test request. Return True if data is 'data'.
  61. @type data: L{bytes}
  62. """
  63. self.numberRequests += 1
  64. return data == b'data'
  65. def dataReceived(self, data):
  66. """
  67. Data was received. Store it in the buffer.
  68. """
  69. self.inBuffer.append(data)
  70. def extReceived(self, code, data):
  71. """
  72. Extended data was received. Store it in the buffer.
  73. """
  74. self.extBuffer.append((code, data))
  75. def eofReceived(self):
  76. """
  77. EOF was received. Remember it.
  78. """
  79. self.gotEOF = True
  80. def closeReceived(self):
  81. """
  82. Close was received. Remember it.
  83. """
  84. self.gotOneClose = True
  85. def closed(self):
  86. """
  87. The channel is closed. Rembember it.
  88. """
  89. self.gotClosed = True
  90. class TestAvatar:
  91. """
  92. A mocked-up version of twisted.conch.avatar.ConchUser
  93. """
  94. _ARGS_ERROR_CODE = 123
  95. def lookupChannel(self, channelType, windowSize, maxPacket, data):
  96. """
  97. The server wants us to return a channel. If the requested channel is
  98. our TestChannel, return it, otherwise return None.
  99. """
  100. if channelType == TestChannel.name:
  101. return TestChannel(remoteWindow=windowSize,
  102. remoteMaxPacket=maxPacket,
  103. data=data, avatar=self)
  104. elif channelType == b"conch-error-args":
  105. # Raise a ConchError with backwards arguments to make sure the
  106. # connection fixes it for us. This case should be deprecated and
  107. # deleted eventually, but only after all of Conch gets the argument
  108. # order right.
  109. raise error.ConchError(
  110. self._ARGS_ERROR_CODE, "error args in wrong order")
  111. def gotGlobalRequest(self, requestType, data):
  112. """
  113. The client has made a global request. If the global request is
  114. 'TestGlobal', return True. If the global request is 'TestData',
  115. return True and the request-specific data we received. Otherwise,
  116. return False.
  117. """
  118. if requestType == b'TestGlobal':
  119. return True
  120. elif requestType == b'TestData':
  121. return True, data
  122. else:
  123. return False
  124. class TestConnection(connection.SSHConnection):
  125. """
  126. A subclass of SSHConnection for testing.
  127. @ivar channel: the current channel.
  128. @type channel. C{TestChannel}
  129. """
  130. def logPrefix(self):
  131. return "TestConnection"
  132. def global_TestGlobal(self, data):
  133. """
  134. The other side made the 'TestGlobal' global request. Return True.
  135. """
  136. return True
  137. def global_Test_Data(self, data):
  138. """
  139. The other side made the 'Test-Data' global request. Return True and
  140. the data we received.
  141. """
  142. return True, data
  143. def channel_TestChannel(self, windowSize, maxPacket, data):
  144. """
  145. The other side is requesting the TestChannel. Create a C{TestChannel}
  146. instance, store it, and return it.
  147. """
  148. self.channel = TestChannel(remoteWindow=windowSize,
  149. remoteMaxPacket=maxPacket, data=data)
  150. return self.channel
  151. def channel_ErrorChannel(self, windowSize, maxPacket, data):
  152. """
  153. The other side is requesting the ErrorChannel. Raise an exception.
  154. """
  155. raise AssertionError('no such thing')
  156. class ConnectionTests(unittest.TestCase):
  157. if test_userauth.transport is None:
  158. skip = "Cannot run without both cryptography and pyasn1"
  159. def setUp(self):
  160. self.transport = test_userauth.FakeTransport(None)
  161. self.transport.avatar = TestAvatar()
  162. self.conn = TestConnection()
  163. self.conn.transport = self.transport
  164. self.conn.serviceStarted()
  165. def _openChannel(self, channel):
  166. """
  167. Open the channel with the default connection.
  168. """
  169. self.conn.openChannel(channel)
  170. self.transport.packets = self.transport.packets[:-1]
  171. self.conn.ssh_CHANNEL_OPEN_CONFIRMATION(struct.pack('>2L',
  172. channel.id, 255) + b'\x00\x02\x00\x00\x00\x00\x80\x00')
  173. def tearDown(self):
  174. self.conn.serviceStopped()
  175. def test_linkAvatar(self):
  176. """
  177. Test that the connection links itself to the avatar in the
  178. transport.
  179. """
  180. self.assertIs(self.transport.avatar.conn, self.conn)
  181. def test_serviceStopped(self):
  182. """
  183. Test that serviceStopped() closes any open channels.
  184. """
  185. channel1 = TestChannel()
  186. channel2 = TestChannel()
  187. self.conn.openChannel(channel1)
  188. self.conn.openChannel(channel2)
  189. self.conn.ssh_CHANNEL_OPEN_CONFIRMATION(b'\x00\x00\x00\x00' * 4)
  190. self.assertTrue(channel1.gotOpen)
  191. self.assertFalse(channel2.gotOpen)
  192. self.conn.serviceStopped()
  193. self.assertTrue(channel1.gotClosed)
  194. def test_GLOBAL_REQUEST(self):
  195. """
  196. Test that global request packets are dispatched to the global_*
  197. methods and the return values are translated into success or failure
  198. messages.
  199. """
  200. self.conn.ssh_GLOBAL_REQUEST(common.NS(b'TestGlobal') + b'\xff')
  201. self.assertEqual(self.transport.packets,
  202. [(connection.MSG_REQUEST_SUCCESS, b'')])
  203. self.transport.packets = []
  204. self.conn.ssh_GLOBAL_REQUEST(common.NS(b'TestData') + b'\xff' +
  205. b'test data')
  206. self.assertEqual(self.transport.packets,
  207. [(connection.MSG_REQUEST_SUCCESS, b'test data')])
  208. self.transport.packets = []
  209. self.conn.ssh_GLOBAL_REQUEST(common.NS(b'TestBad') + b'\xff')
  210. self.assertEqual(self.transport.packets,
  211. [(connection.MSG_REQUEST_FAILURE, b'')])
  212. self.transport.packets = []
  213. self.conn.ssh_GLOBAL_REQUEST(common.NS(b'TestGlobal') + b'\x00')
  214. self.assertEqual(self.transport.packets, [])
  215. def test_REQUEST_SUCCESS(self):
  216. """
  217. Test that global request success packets cause the Deferred to be
  218. called back.
  219. """
  220. d = self.conn.sendGlobalRequest(b'request', b'data', True)
  221. self.conn.ssh_REQUEST_SUCCESS(b'data')
  222. def check(data):
  223. self.assertEqual(data, b'data')
  224. d.addCallback(check)
  225. d.addErrback(self.fail)
  226. return d
  227. def test_REQUEST_FAILURE(self):
  228. """
  229. Test that global request failure packets cause the Deferred to be
  230. erred back.
  231. """
  232. d = self.conn.sendGlobalRequest(b'request', b'data', True)
  233. self.conn.ssh_REQUEST_FAILURE(b'data')
  234. def check(f):
  235. self.assertEqual(f.value.data, b'data')
  236. d.addCallback(self.fail)
  237. d.addErrback(check)
  238. return d
  239. def test_CHANNEL_OPEN(self):
  240. """
  241. Test that open channel packets cause a channel to be created and
  242. opened or a failure message to be returned.
  243. """
  244. del self.transport.avatar
  245. self.conn.ssh_CHANNEL_OPEN(common.NS(b'TestChannel') +
  246. b'\x00\x00\x00\x01' * 4)
  247. self.assertTrue(self.conn.channel.gotOpen)
  248. self.assertEqual(self.conn.channel.conn, self.conn)
  249. self.assertEqual(self.conn.channel.data, b'\x00\x00\x00\x01')
  250. self.assertEqual(self.conn.channel.specificData, b'\x00\x00\x00\x01')
  251. self.assertEqual(self.conn.channel.remoteWindowLeft, 1)
  252. self.assertEqual(self.conn.channel.remoteMaxPacket, 1)
  253. self.assertEqual(self.transport.packets,
  254. [(connection.MSG_CHANNEL_OPEN_CONFIRMATION,
  255. b'\x00\x00\x00\x01\x00\x00\x00\x00\x00\x02\x00\x00'
  256. b'\x00\x00\x80\x00')])
  257. self.transport.packets = []
  258. self.conn.ssh_CHANNEL_OPEN(common.NS(b'BadChannel') +
  259. b'\x00\x00\x00\x02' * 4)
  260. self.flushLoggedErrors()
  261. self.assertEqual(self.transport.packets,
  262. [(connection.MSG_CHANNEL_OPEN_FAILURE,
  263. b'\x00\x00\x00\x02\x00\x00\x00\x03' + common.NS(
  264. b'unknown channel') + common.NS(b''))])
  265. self.transport.packets = []
  266. self.conn.ssh_CHANNEL_OPEN(common.NS(b'ErrorChannel') +
  267. b'\x00\x00\x00\x02' * 4)
  268. self.flushLoggedErrors()
  269. self.assertEqual(self.transport.packets,
  270. [(connection.MSG_CHANNEL_OPEN_FAILURE,
  271. b'\x00\x00\x00\x02\x00\x00\x00\x02' + common.NS(
  272. b'unknown failure') + common.NS(b''))])
  273. def _lookupChannelErrorTest(self, code):
  274. """
  275. Deliver a request for a channel open which will result in an exception
  276. being raised during channel lookup. Assert that an error response is
  277. delivered as a result.
  278. """
  279. self.transport.avatar._ARGS_ERROR_CODE = code
  280. self.conn.ssh_CHANNEL_OPEN(
  281. common.NS(b'conch-error-args') + b'\x00\x00\x00\x01' * 4)
  282. errors = self.flushLoggedErrors(error.ConchError)
  283. self.assertEqual(
  284. len(errors), 1, "Expected one error, got: %r" % (errors,))
  285. self.assertEqual(errors[0].value.args, (long(123), "error args in wrong order"))
  286. self.assertEqual(
  287. self.transport.packets,
  288. [(connection.MSG_CHANNEL_OPEN_FAILURE,
  289. # The response includes some bytes which identifying the
  290. # associated request, as well as the error code (7b in hex) and
  291. # the error message.
  292. b'\x00\x00\x00\x01\x00\x00\x00\x7b' + common.NS(
  293. b'error args in wrong order') + common.NS(b''))])
  294. def test_lookupChannelError(self):
  295. """
  296. If a C{lookupChannel} implementation raises L{error.ConchError} with the
  297. arguments in the wrong order, a C{MSG_CHANNEL_OPEN} failure is still
  298. sent in response to the message.
  299. This is a temporary work-around until L{error.ConchError} is given
  300. better attributes and all of the Conch code starts constructing
  301. instances of it properly. Eventually this functionality should be
  302. deprecated and then removed.
  303. """
  304. self._lookupChannelErrorTest(123)
  305. def test_lookupChannelErrorLongCode(self):
  306. """
  307. Like L{test_lookupChannelError}, but for the case where the failure code
  308. is represented as a L{long} instead of a L{int}.
  309. """
  310. self._lookupChannelErrorTest(long(123))
  311. def test_CHANNEL_OPEN_CONFIRMATION(self):
  312. """
  313. Test that channel open confirmation packets cause the channel to be
  314. notified that it's open.
  315. """
  316. channel = TestChannel()
  317. self.conn.openChannel(channel)
  318. self.conn.ssh_CHANNEL_OPEN_CONFIRMATION(b'\x00\x00\x00\x00'*5)
  319. self.assertEqual(channel.remoteWindowLeft, 0)
  320. self.assertEqual(channel.remoteMaxPacket, 0)
  321. self.assertEqual(channel.specificData, b'\x00\x00\x00\x00')
  322. self.assertEqual(self.conn.channelsToRemoteChannel[channel],
  323. 0)
  324. self.assertEqual(self.conn.localToRemoteChannel[0], 0)
  325. def test_CHANNEL_OPEN_FAILURE(self):
  326. """
  327. Test that channel open failure packets cause the channel to be
  328. notified that its opening failed.
  329. """
  330. channel = TestChannel()
  331. self.conn.openChannel(channel)
  332. self.conn.ssh_CHANNEL_OPEN_FAILURE(b'\x00\x00\x00\x00\x00\x00\x00'
  333. b'\x01' + common.NS(b'failure!'))
  334. self.assertEqual(channel.openFailureReason.args, (b'failure!', 1))
  335. self.assertIsNone(self.conn.channels.get(channel))
  336. def test_CHANNEL_WINDOW_ADJUST(self):
  337. """
  338. Test that channel window adjust messages add bytes to the channel
  339. window.
  340. """
  341. channel = TestChannel()
  342. self._openChannel(channel)
  343. oldWindowSize = channel.remoteWindowLeft
  344. self.conn.ssh_CHANNEL_WINDOW_ADJUST(b'\x00\x00\x00\x00\x00\x00\x00'
  345. b'\x01')
  346. self.assertEqual(channel.remoteWindowLeft, oldWindowSize + 1)
  347. def test_CHANNEL_DATA(self):
  348. """
  349. Test that channel data messages are passed up to the channel, or
  350. cause the channel to be closed if the data is too large.
  351. """
  352. channel = TestChannel(localWindow=6, localMaxPacket=5)
  353. self._openChannel(channel)
  354. self.conn.ssh_CHANNEL_DATA(b'\x00\x00\x00\x00' + common.NS(b'data'))
  355. self.assertEqual(channel.inBuffer, [b'data'])
  356. self.assertEqual(self.transport.packets,
  357. [(connection.MSG_CHANNEL_WINDOW_ADJUST, b'\x00\x00\x00\xff'
  358. b'\x00\x00\x00\x04')])
  359. self.transport.packets = []
  360. longData = b'a' * (channel.localWindowLeft + 1)
  361. self.conn.ssh_CHANNEL_DATA(b'\x00\x00\x00\x00' + common.NS(longData))
  362. self.assertEqual(channel.inBuffer, [b'data'])
  363. self.assertEqual(self.transport.packets,
  364. [(connection.MSG_CHANNEL_CLOSE, b'\x00\x00\x00\xff')])
  365. channel = TestChannel()
  366. self._openChannel(channel)
  367. bigData = b'a' * (channel.localMaxPacket + 1)
  368. self.transport.packets = []
  369. self.conn.ssh_CHANNEL_DATA(b'\x00\x00\x00\x01' + common.NS(bigData))
  370. self.assertEqual(channel.inBuffer, [])
  371. self.assertEqual(self.transport.packets,
  372. [(connection.MSG_CHANNEL_CLOSE, b'\x00\x00\x00\xff')])
  373. def test_CHANNEL_EXTENDED_DATA(self):
  374. """
  375. Test that channel extended data messages are passed up to the channel,
  376. or cause the channel to be closed if they're too big.
  377. """
  378. channel = TestChannel(localWindow=6, localMaxPacket=5)
  379. self._openChannel(channel)
  380. self.conn.ssh_CHANNEL_EXTENDED_DATA(b'\x00\x00\x00\x00\x00\x00\x00'
  381. b'\x00' + common.NS(b'data'))
  382. self.assertEqual(channel.extBuffer, [(0, b'data')])
  383. self.assertEqual(self.transport.packets,
  384. [(connection.MSG_CHANNEL_WINDOW_ADJUST, b'\x00\x00\x00\xff'
  385. b'\x00\x00\x00\x04')])
  386. self.transport.packets = []
  387. longData = b'a' * (channel.localWindowLeft + 1)
  388. self.conn.ssh_CHANNEL_EXTENDED_DATA(b'\x00\x00\x00\x00\x00\x00\x00'
  389. b'\x00' + common.NS(longData))
  390. self.assertEqual(channel.extBuffer, [(0, b'data')])
  391. self.assertEqual(self.transport.packets,
  392. [(connection.MSG_CHANNEL_CLOSE, b'\x00\x00\x00\xff')])
  393. channel = TestChannel()
  394. self._openChannel(channel)
  395. bigData = b'a' * (channel.localMaxPacket + 1)
  396. self.transport.packets = []
  397. self.conn.ssh_CHANNEL_EXTENDED_DATA(b'\x00\x00\x00\x01\x00\x00\x00'
  398. b'\x00' + common.NS(bigData))
  399. self.assertEqual(channel.extBuffer, [])
  400. self.assertEqual(self.transport.packets,
  401. [(connection.MSG_CHANNEL_CLOSE, b'\x00\x00\x00\xff')])
  402. def test_CHANNEL_EOF(self):
  403. """
  404. Test that channel eof messages are passed up to the channel.
  405. """
  406. channel = TestChannel()
  407. self._openChannel(channel)
  408. self.conn.ssh_CHANNEL_EOF(b'\x00\x00\x00\x00')
  409. self.assertTrue(channel.gotEOF)
  410. def test_CHANNEL_CLOSE(self):
  411. """
  412. Test that channel close messages are passed up to the channel. Also,
  413. test that channel.close() is called if both sides are closed when this
  414. message is received.
  415. """
  416. channel = TestChannel()
  417. self._openChannel(channel)
  418. self.conn.sendClose(channel)
  419. self.conn.ssh_CHANNEL_CLOSE(b'\x00\x00\x00\x00')
  420. self.assertTrue(channel.gotOneClose)
  421. self.assertTrue(channel.gotClosed)
  422. def test_CHANNEL_REQUEST_success(self):
  423. """
  424. Test that channel requests that succeed send MSG_CHANNEL_SUCCESS.
  425. """
  426. channel = TestChannel()
  427. self._openChannel(channel)
  428. self.conn.ssh_CHANNEL_REQUEST(b'\x00\x00\x00\x00' + common.NS(b'test')
  429. + b'\x00')
  430. self.assertEqual(channel.numberRequests, 1)
  431. d = self.conn.ssh_CHANNEL_REQUEST(b'\x00\x00\x00\x00' + common.NS(
  432. b'test') + b'\xff' + b'data')
  433. def check(result):
  434. self.assertEqual(self.transport.packets,
  435. [(connection.MSG_CHANNEL_SUCCESS, b'\x00\x00\x00\xff')])
  436. d.addCallback(check)
  437. return d
  438. def test_CHANNEL_REQUEST_failure(self):
  439. """
  440. Test that channel requests that fail send MSG_CHANNEL_FAILURE.
  441. """
  442. channel = TestChannel()
  443. self._openChannel(channel)
  444. d = self.conn.ssh_CHANNEL_REQUEST(b'\x00\x00\x00\x00' + common.NS(
  445. b'test') + b'\xff')
  446. def check(result):
  447. self.assertEqual(self.transport.packets,
  448. [(connection.MSG_CHANNEL_FAILURE, b'\x00\x00\x00\xff'
  449. )])
  450. d.addCallback(self.fail)
  451. d.addErrback(check)
  452. return d
  453. def test_CHANNEL_REQUEST_SUCCESS(self):
  454. """
  455. Test that channel request success messages cause the Deferred to be
  456. called back.
  457. """
  458. channel = TestChannel()
  459. self._openChannel(channel)
  460. d = self.conn.sendRequest(channel, b'test', b'data', True)
  461. self.conn.ssh_CHANNEL_SUCCESS(b'\x00\x00\x00\x00')
  462. def check(result):
  463. self.assertTrue(result)
  464. return d
  465. def test_CHANNEL_REQUEST_FAILURE(self):
  466. """
  467. Test that channel request failure messages cause the Deferred to be
  468. erred back.
  469. """
  470. channel = TestChannel()
  471. self._openChannel(channel)
  472. d = self.conn.sendRequest(channel, b'test', b'', True)
  473. self.conn.ssh_CHANNEL_FAILURE(b'\x00\x00\x00\x00')
  474. def check(result):
  475. self.assertEqual(result.value.value, 'channel request failed')
  476. d.addCallback(self.fail)
  477. d.addErrback(check)
  478. return d
  479. def test_sendGlobalRequest(self):
  480. """
  481. Test that global request messages are sent in the right format.
  482. """
  483. d = self.conn.sendGlobalRequest(b'wantReply', b'data', True)
  484. # must be added to prevent errbacking during teardown
  485. d.addErrback(lambda failure: None)
  486. self.conn.sendGlobalRequest(b'noReply', b'', False)
  487. self.assertEqual(self.transport.packets,
  488. [(connection.MSG_GLOBAL_REQUEST, common.NS(b'wantReply') +
  489. b'\xffdata'),
  490. (connection.MSG_GLOBAL_REQUEST, common.NS(b'noReply') +
  491. b'\x00')])
  492. self.assertEqual(self.conn.deferreds, {'global':[d]})
  493. def test_openChannel(self):
  494. """
  495. Test that open channel messages are sent in the right format.
  496. """
  497. channel = TestChannel()
  498. self.conn.openChannel(channel, b'aaaa')
  499. self.assertEqual(self.transport.packets,
  500. [(connection.MSG_CHANNEL_OPEN, common.NS(b'TestChannel') +
  501. b'\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x80\x00aaaa')])
  502. self.assertEqual(channel.id, 0)
  503. self.assertEqual(self.conn.localChannelID, 1)
  504. def test_sendRequest(self):
  505. """
  506. Test that channel request messages are sent in the right format.
  507. """
  508. channel = TestChannel()
  509. self._openChannel(channel)
  510. d = self.conn.sendRequest(channel, b'test', b'test', True)
  511. # needed to prevent errbacks during teardown.
  512. d.addErrback(lambda failure: None)
  513. self.conn.sendRequest(channel, b'test2', b'', False)
  514. channel.localClosed = True # emulate sending a close message
  515. self.conn.sendRequest(channel, b'test3', b'', True)
  516. self.assertEqual(self.transport.packets,
  517. [(connection.MSG_CHANNEL_REQUEST, b'\x00\x00\x00\xff' +
  518. common.NS(b'test') + b'\x01test'),
  519. (connection.MSG_CHANNEL_REQUEST, b'\x00\x00\x00\xff' +
  520. common.NS(b'test2') + b'\x00')])
  521. self.assertEqual(self.conn.deferreds[0], [d])
  522. def test_adjustWindow(self):
  523. """
  524. Test that channel window adjust messages cause bytes to be added
  525. to the window.
  526. """
  527. channel = TestChannel(localWindow=5)
  528. self._openChannel(channel)
  529. channel.localWindowLeft = 0
  530. self.conn.adjustWindow(channel, 1)
  531. self.assertEqual(channel.localWindowLeft, 1)
  532. channel.localClosed = True
  533. self.conn.adjustWindow(channel, 2)
  534. self.assertEqual(channel.localWindowLeft, 1)
  535. self.assertEqual(self.transport.packets,
  536. [(connection.MSG_CHANNEL_WINDOW_ADJUST, b'\x00\x00\x00\xff'
  537. b'\x00\x00\x00\x01')])
  538. def test_sendData(self):
  539. """
  540. Test that channel data messages are sent in the right format.
  541. """
  542. channel = TestChannel()
  543. self._openChannel(channel)
  544. self.conn.sendData(channel, b'a')
  545. channel.localClosed = True
  546. self.conn.sendData(channel, b'b')
  547. self.assertEqual(self.transport.packets,
  548. [(connection.MSG_CHANNEL_DATA, b'\x00\x00\x00\xff' +
  549. common.NS(b'a'))])
  550. def test_sendExtendedData(self):
  551. """
  552. Test that channel extended data messages are sent in the right format.
  553. """
  554. channel = TestChannel()
  555. self._openChannel(channel)
  556. self.conn.sendExtendedData(channel, 1, b'test')
  557. channel.localClosed = True
  558. self.conn.sendExtendedData(channel, 2, b'test2')
  559. self.assertEqual(self.transport.packets,
  560. [(connection.MSG_CHANNEL_EXTENDED_DATA, b'\x00\x00\x00\xff' +
  561. b'\x00\x00\x00\x01' + common.NS(b'test'))])
  562. def test_sendEOF(self):
  563. """
  564. Test that channel EOF messages are sent in the right format.
  565. """
  566. channel = TestChannel()
  567. self._openChannel(channel)
  568. self.conn.sendEOF(channel)
  569. self.assertEqual(self.transport.packets,
  570. [(connection.MSG_CHANNEL_EOF, b'\x00\x00\x00\xff')])
  571. channel.localClosed = True
  572. self.conn.sendEOF(channel)
  573. self.assertEqual(self.transport.packets,
  574. [(connection.MSG_CHANNEL_EOF, b'\x00\x00\x00\xff')])
  575. def test_sendClose(self):
  576. """
  577. Test that channel close messages are sent in the right format.
  578. """
  579. channel = TestChannel()
  580. self._openChannel(channel)
  581. self.conn.sendClose(channel)
  582. self.assertTrue(channel.localClosed)
  583. self.assertEqual(self.transport.packets,
  584. [(connection.MSG_CHANNEL_CLOSE, b'\x00\x00\x00\xff')])
  585. self.conn.sendClose(channel)
  586. self.assertEqual(self.transport.packets,
  587. [(connection.MSG_CHANNEL_CLOSE, b'\x00\x00\x00\xff')])
  588. channel2 = TestChannel()
  589. self._openChannel(channel2)
  590. channel2.remoteClosed = True
  591. self.conn.sendClose(channel2)
  592. self.assertTrue(channel2.gotClosed)
  593. def test_getChannelWithAvatar(self):
  594. """
  595. Test that getChannel dispatches to the avatar when an avatar is
  596. present. Correct functioning without the avatar is verified in
  597. test_CHANNEL_OPEN.
  598. """
  599. channel = self.conn.getChannel(b'TestChannel', 50, 30, b'data')
  600. self.assertEqual(channel.data, b'data')
  601. self.assertEqual(channel.remoteWindowLeft, 50)
  602. self.assertEqual(channel.remoteMaxPacket, 30)
  603. self.assertRaises(error.ConchError, self.conn.getChannel,
  604. b'BadChannel', 50, 30, b'data')
  605. def test_gotGlobalRequestWithoutAvatar(self):
  606. """
  607. Test that gotGlobalRequests dispatches to global_* without an avatar.
  608. """
  609. del self.transport.avatar
  610. self.assertTrue(self.conn.gotGlobalRequest(b'TestGlobal', b'data'))
  611. self.assertEqual(self.conn.gotGlobalRequest(b'Test-Data', b'data'),
  612. (True, b'data'))
  613. self.assertFalse(self.conn.gotGlobalRequest(b'BadGlobal', b'data'))
  614. def test_channelClosedCausesLeftoverChannelDeferredsToErrback(self):
  615. """
  616. Whenever an SSH channel gets closed any Deferred that was returned by a
  617. sendRequest() on its parent connection must be errbacked.
  618. """
  619. channel = TestChannel()
  620. self._openChannel(channel)
  621. d = self.conn.sendRequest(
  622. channel, b"dummyrequest", b"dummydata", wantReply=1)
  623. d = self.assertFailure(d, error.ConchError)
  624. self.conn.channelClosed(channel)
  625. return d
  626. class CleanConnectionShutdownTests(unittest.TestCase):
  627. """
  628. Check whether correct cleanup is performed on connection shutdown.
  629. """
  630. if test_userauth.transport is None:
  631. skip = "Cannot run without both cryptography and pyasn1"
  632. def setUp(self):
  633. self.transport = test_userauth.FakeTransport(None)
  634. self.transport.avatar = TestAvatar()
  635. self.conn = TestConnection()
  636. self.conn.transport = self.transport
  637. def test_serviceStoppedCausesLeftoverGlobalDeferredsToErrback(self):
  638. """
  639. Once the service is stopped any leftover global deferred returned by
  640. a sendGlobalRequest() call must be errbacked.
  641. """
  642. self.conn.serviceStarted()
  643. d = self.conn.sendGlobalRequest(
  644. b"dummyrequest", b"dummydata", wantReply=1)
  645. d = self.assertFailure(d, error.ConchError)
  646. self.conn.serviceStopped()
  647. return d