Package foolscap :: Module negotiate :: Class Negotiation
[hide private]
[frames] | no frames]

Class Negotiation

source code

twisted.internet.protocol.BaseProtocol --+    
                                         |    
        twisted.internet.protocol.Protocol --+
                                             |
                                            Negotiation

This is the first protocol to speak over the wire. It is responsible for negotiating the connection parameters, then switching the connection over to the actual Banana protocol. This removes all the details of negotiation from Banana, and makes it easier to use a more complex scheme (including a STARTTLS transition) in PB.

Negotiation consists of three phases. In the PLAINTEXT phase, the client side (i.e. the one which initiated the connection) sends an HTTP-compatible GET request for the target Tub ID. This request includes an Connection: Upgrade header. The GET request serves a couple of purposes: if a PB client is accidentally pointed at an HTTP server, it will trigger a sensible 404 Error instead of getting confused. A regular HTTP server can be used to send back a 303 Redirect, allowing Apache (or whatever) to be used as a redirection server.

After sending the GET request, the client waits for the server to send a 101 Switching Protocols command, then starts the TLS session. It may also receive a 303 Redirect command, in which case it drops the connection and tries again with the new target.

In the PLAINTEXT phase, the server side (i.e. the one which accepted the connection) waits for the client's GET request, extracts the TubID from the first line, consults the local Listener object to locate the appropriate Tub (and its certificate), sends back a 101 Switching Protocols response, then starts the TLS session with the Tub's certificate. If the Listener reports that the requested Tub is listening elsewhere, the server sends back a 303 Redirect instead, and then drops the connection.

By the end of the PLAINTEXT phase, both ends know which Tub they are using (self.tub has been set).

Both sides send a Hello Block upon entering the ENCRYPTED phase, which in practice means just after starting the TLS session. The Hello block contains the negotiation offer, as a series of Key: Value lines separated by \r\n delimiters and terminated by a blank line. Upon receiving the other end's Hello block, each side switches to the DECIDING phase, and then evaluates the received Hello message.

Each side compares TubIDs, and the side with the lexicographically higher value becomes the Master. (If, for some reason, one side does not claim a TubID, its value is treated as None, which always compares *less* than any actual TubID, so the non-TubID side will probably not be the Master. Any possible ties are resolved by having the server side be the master). Both sides know the other's TubID, so both sides know whether they are the Master or not.

The Master has two jobs to do. The first is that it compares the negotiation offer against its own capabilities, and comes to a decision about what the connection parameters shall be. It may decide that the two sides are not compatible, in which case it will abandon the connection. The second job is to decide whether to continue to use the connection at all: if the Master already has a connection to the other Tub, it will drop this new one. This decision must be made by the Master (as opposed to the Server) because it is possible for both Tubs to connect to each other simultaneously, and this design avoids a race condition that could otherwise drop *both* connections.

If the Master decides to continue with the connection, it sends the Decision block to the non-master side. It then swaps out the Negotiation protocol for a new Banana protocol instance that has been created with the same parameters that were used to create the Decision block.

The non-master side is waiting in the DECIDING phase for this block. Upon receiving it, the non-master side evaluates the connection parameters and either drops the connection or swaps in a new Banana protocol instance with the same parameters. At this point, negotiation is complete and the Negotiation instances are dropped.

Nested Classes [hide private]
  brokerClass
I manage a connection to a remote Broker.
Instance Methods [hide private]
 
__init__(self, logparent=None) source code
 
log(self, *args, **kwargs) source code
 
initClient(self, connector, targetHost) source code
 
initServer(self, listener) source code
 
parseLines(self, header) source code
 
sendBlock(self, block) source code
 
debug_doTimer(self, name, timeout, call, *args) source code
 
debug_addTimerCallback(self, name, call, *args) source code
 
debug_forceTimer(self, name) source code
 
debug_forceAllTimers(self) source code
 
debug_cancelAllTimers(self) source code
 
debug_fireTimer(self, name) source code
 
connectionMade(self)
Called when a connection is made.
source code
 
connectionMadeClient(self) source code
 
sendPlaintextClient(self) source code
 
connectionMadeServer(self) source code
 
sendError(self, why) source code
 
negotiationTimedOut(self) source code
 
stopNegotiationTimer(self) source code
 
dataReceived(self, chunk)
Called whenever data is received.
source code
 
sendBananaError(self, msg) source code
 
connectionLost(self, reason)
Called when the connection is shut down.
source code
 
handlePLAINTEXTServer(self, header) source code
 
sendPlaintextServerAndStartENCRYPTED(self, encrypted) source code
 
sendRedirect(self, redirect) source code
 
handlePLAINTEXTClient(self, header) source code
 
startENCRYPTED(self, encrypted) source code
 
sendHello(self)
This is called on both sides as soon as the encrypted connection is established.
source code
 
handleENCRYPTED(self, header) source code
 
evaluateHello(self, offer)
Evaluate the HELLO message sent by the other side.
source code
 
evaluateNegotiationVersion1(self, offer) source code
 
evaluateNegotiationVersion2(self, offer) source code
 
evaluateNegotiationVersion3(self, offer) source code
 
compareOfferAndExisting(self, offer, existing, lp)
Compare the new offer against the existing connection, and decide which to keep.
source code
 
handle_old(self, offer, existing, threshold, lp) source code
 
sendDecision(self, decision, params) source code
 
handleDECIDING(self, header) source code
 
acceptDecision(self, decision)
This is called on the client end when it receives the results of the negotiation from the server.
source code
 
acceptDecisionVersion1(self, decision) source code
 
acceptDecisionVersion2(self, decision) source code
 
acceptDecisionVersion3(self, decision) source code
 
loopbackDecision(self) source code
 
startTLS(self, cert) source code
 
switchToBanana(self, params) source code
 
negotiationFailed(self) source code

Inherited from twisted.internet.protocol.BaseProtocol: __providedBy__, makeConnection

Class Variables [hide private]
  myTubID = None
  tub = None
  theirTubID = None
  receive_phase = 0
  send_phase = 0
  encrypted = False
  doNegotiation = True
  debugNegotiation = False
  forceNegotiation = None
  minVersion = 3
  maxVersion = 3
  initialVocabTableRange = (0, 1)
  SERVER_TIMEOUT = 60
  negotiationTimer = None

Inherited from twisted.internet.protocol.Protocol: __implemented__, __provides__

Inherited from twisted.internet.protocol.BaseProtocol: connected, transport

Instance Variables [hide private]
  negotationOffer
a dict which describes what we will offer to the far side.
  negotiationResults
a dict which describes what the two ends have agreed upon.
Method Details [hide private]

connectionMade(self)

source code 

Called when a connection is made.

This may be considered the initializer of the protocol, because it is called when the connection is completed. For clients, this is called once the connection to the server has been established; for servers, this is called after an accept() call stops blocking and a socket has been received. If you need to send any greeting or initial message, do it here.

Overrides: twisted.internet.protocol.BaseProtocol.connectionMade
(inherited documentation)

dataReceived(self, chunk)

source code 

Called whenever data is received.

Use this method to translate to a higher-level message. Usually, some callback will be made upon the receipt of each complete protocol message.

Parameters:
  • data - a string of indeterminate length. Please keep in mind that you will probably need to buffer some data, as partial (or multiple) protocol messages may be received! I recommend that unit tests for protocols call through to this method with differing chunk sizes, down to one byte at a time.
Overrides: twisted.internet.protocol.Protocol.dataReceived
(inherited documentation)

connectionLost(self, reason)

source code 

Called when the connection is shut down.

Clear any circular references here, and any external references to this Protocol. The connection has been closed.

Overrides: twisted.internet.protocol.Protocol.connectionLost
(inherited documentation)

sendHello(self)

source code 

This is called on both sides as soon as the encrypted connection is established. This causes a negotiation block to be sent to the other side as an offer.

evaluateHello(self, offer)

source code 

Evaluate the HELLO message sent by the other side. We compare TubIDs, and the higher value becomes the 'master' and makes the negotiation decisions.

This method returns a tuple of DECISION,PARAMS. There are a few different possibilities:

   - We are the master, we make a negotiation decision: DECISION is
   the block of data to send back to the non-master side, PARAMS are
   the connection parameters we will use ourselves.

   - We are the master, we can't accomodate their request: raise
   NegotiationError

   - We are not the master: DECISION is None

compareOfferAndExisting(self, offer, existing, lp)

source code 

Compare the new offer against the existing connection, and decide which to keep.

Returns:
True to accept the new offer, False to stick with the existing connection.

acceptDecision(self, decision)

source code 

This is called on the client end when it receives the results of the negotiation from the server. The client must accept this decision (and return the connection parameters dict), or raise NegotiationError to hang up.negotiationResults.


Instance Variable Details [hide private]

negotationOffer

a dict which describes what we will offer to the far side. Each key/value pair will be put into a rfc822-style header and sent from the client to the server when the connection is established. On the server side, handleNegotiation() uses negotationOffer to indicate what we are locally capable of.

Subclasses may influence the negotiation process by modifying this dictionary before connectionMade() is called.

negotiationResults

a dict which describes what the two ends have agreed upon. This is computed by the server, stored locally, and sent down to the client. The client receives it and stores it without modification (server chooses).

In general, the negotiationResults are the same on both sides of the same connection. However there may be certain parameters which are sent as part of the negotiation block (the PB TubID, for example) which will not.