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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| debug_doTimer(self,
name,
timeout,
call,
*args) |
source code
|
|
|
|
| debug_addTimerCallback(self,
name,
call,
*args) |
source code
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| sendPlaintextServerAndStartENCRYPTED(self,
encrypted) |
source code
|
|
|
|
|
|
|
|
|
|
|
|
|
sendHello(self)
This is called on both sides as soon as the encrypted connection is
established. |
source code
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| handle_old(self,
offer,
existing,
threshold,
lp) |
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Inherited from twisted.internet.protocol.BaseProtocol:
__providedBy__,
makeConnection
|
|
|
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
|
|
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)
|
|
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)
|
|
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)
|
|
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.
|
|
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.
|
|
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.
|
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.
|