1 # -*- test-case-name: twisted.conch.test.test_channel -*-
2 # Copyright (c) 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