1
# -*- test-case-name: twisted.conch.test.test_channel -*-
2
# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
3
# See LICENSE for details.
7
The parent class for all the SSH Channels. Currently implemented channels
8
are session. direct-tcp, and forwarded-tcp.
10
Maintainer: Paul Swartz
13
from twisted.python import log
14
from twisted.internet import interfaces
15
from zope.interface import implements
18
class SSHChannel(log.Logger):
20
A class that represents a multiplexed channel over an SSH connection.
21
The channel has a local window which is the maximum amount of data it will
22
receive, and a remote which is the maximum amount of data the remote side
23
will accept. There is also a maximum packet size for any individual data
24
packet going each way.
26
@ivar name: the name of the channel.
28
@ivar localWindowSize: the maximum size of the local window in bytes.
29
@type localWindowSize: C{int}
30
@ivar localWindowLeft: how many bytes are left in the local window.
31
@type localWindowLeft: C{int}
32
@ivar localMaxPacket: the maximum size of packet we will accept in bytes.
33
@type localMaxPacket: C{int}
34
@ivar remoteWindowLeft: how many bytes are left in the remote window.
35
@type remoteWindowLeft: C{int}
36
@ivar remoteMaxPacket: the maximum size of a packet the remote side will
38
@type remoteMaxPacket: C{int}
39
@ivar conn: the connection this channel is multiplexed through.
40
@type conn: L{SSHConnection}
41
@ivar data: any data to send to the other size when the channel is
44
@ivar avatar: an avatar for the logged-in user (if a server channel)
45
@ivar localClosed: True if we aren't accepting more data.
46
@type localClosed: C{bool}
47
@ivar remoteClosed: True if the other size isn't accepting more data.
48
@type remoteClosed: C{bool}
51
implements(interfaces.ITransport)
53
name = None # only needed for client channels
55
def __init__(self, localWindow = 0, localMaxPacket = 0,
56
remoteWindow = 0, remoteMaxPacket = 0,
57
conn = None, data=None, avatar = None):
58
self.localWindowSize = localWindow or 131072
59
self.localWindowLeft = self.localWindowSize
60
self.localMaxPacket = localMaxPacket or 32768
61
self.remoteWindowLeft = remoteWindow
62
self.remoteMaxPacket = remoteMaxPacket
67
self.specificData = ''
73
self.id = None # gets set later by SSHConnection
76
return '<SSHChannel %s (lw %i rw %i)>' % (self.name,
77
self.localWindowLeft, self.remoteWindowLeft)
80
id = (self.id is not None and str(self.id)) or "unknown"
81
return "SSHChannel %s (%s) on %s" % (self.name, id,
82
self.conn.logPrefix())
84
def channelOpen(self, specificData):
86
Called when the channel is opened. specificData is any data that the
87
other side sent us when opening the channel.
89
@type specificData: C{str}
91
log.msg('channel open')
93
def openFailed(self, reason):
95
Called when the the open failed for some reason.
96
reason.desc is a string descrption, reason.code the the SSH error code.
98
@type reason: L{error.ConchError}
100
log.msg('other side refused open\nreason: %s'% reason)
102
def addWindowBytes(self, bytes):
104
Called when bytes are added to the remote window. By default it clears
109
self.remoteWindowLeft = self.remoteWindowLeft+bytes
110
if not self.areWriting and not self.closing:
111
self.areWriting = True
120
for (type, data) in b:
121
self.writeExtended(type, data)
123
def requestReceived(self, requestType, data):
125
Called when a request is sent to this channel. By default it delegates
126
to self.request_<requestType>.
127
If this function returns true, the request succeeded, otherwise it
130
@type requestType: C{str}
134
foo = requestType.replace('-', '_')
135
f = getattr(self, 'request_%s'%foo, None)
138
log.msg('unhandled request for %s'%requestType)
141
def dataReceived(self, data):
143
Called when we receive data.
147
log.msg('got data %s'%repr(data))
149
def extReceived(self, dataType, data):
151
Called when we receive extended data (usually standard error).
153
@type dataType: C{int}
156
log.msg('got extended data %s %s'%(dataType, repr(data)))
158
def eofReceived(self):
160
Called when the other side will send no more data.
162
log.msg('remote eof')
164
def closeReceived(self):
166
Called when the other side has closed the channel.
168
log.msg('remote close')
169
self.loseConnection()
173
Called when the channel is closed. This means that both our side and
174
the remote side have closed the channel.
179
def write(self, data):
181
Write some data to the channel. If there is not enough remote window
182
available, buffer until it is. Otherwise, split the data into
183
packets of length remoteMaxPacket and send them.
191
if top > self.remoteWindowLeft:
192
data, self.buf = (data[:self.remoteWindowLeft],
193
data[self.remoteWindowLeft:])
196
top = self.remoteWindowLeft
197
rmp = self.remoteMaxPacket
198
write = self.conn.sendData
199
r = range(0, top, rmp)
201
write(self, data[offset: offset+rmp])
202
self.remoteWindowLeft -= top
203
if self.closing and not self.buf:
204
self.loseConnection() # try again
206
def writeExtended(self, dataType, data):
208
Send extended data to this channel. If there is not enough remote
209
window available, buffer until there is. Otherwise, split the data
210
into packets of length remoteMaxPacket and send them.
212
@type dataType: C{int}
216
if self.extBuf[-1][0] == dataType:
217
self.extBuf[-1][1] += data
219
self.extBuf.append([dataType, data])
221
if len(data) > self.remoteWindowLeft:
222
data, self.extBuf = (data[:self.remoteWindowLeft],
223
[[dataType, data[self.remoteWindowLeft:]]])
226
while len(data) > self.remoteMaxPacket:
227
self.conn.sendExtendedData(self, dataType,
228
data[:self.remoteMaxPacket])
229
data = data[self.remoteMaxPacket:]
230
self.remoteWindowLeft -= self.remoteMaxPacket
232
self.conn.sendExtendedData(self, dataType, data)
233
self.remoteWindowLeft -= len(data)
235
self.loseConnection() # try again
237
def writeSequence(self, data):
239
Part of the Transport interface. Write a list of strings to the
242
@type data: C{list} of C{str}
244
self.write(''.join(data))
246
def loseConnection(self):
248
Close the channel if there is no buferred data. Otherwise, note the
252
if not self.buf and not self.extBuf:
253
self.conn.sendClose(self)
257
Return a tuple describing the other side of the connection.
261
return('SSH', )+self.conn.transport.getPeer()
265
Return a tuple describing our side of the connection.
269
return('SSH', )+self.conn.transport.getHost()
271
def stopWriting(self):
273
Called when the remote buffer is full, as a hint to stop writing.
274
This can be ignored, but it can be helpful.
277
def startWriting(self):
279
Called when the remote buffer has more room, as a hint to continue