tales from the darkstar
Of protocols and transports
As mentioned in my previous post I worked on the Darkchat server which was going to be demoed and used at JavaOne 2009. The server would be on the public network so that attendees could access it during the convention. For this to work we needed to to be able to limit logins to authorized clients (JavaOne attendees). The details of creating and distributing account usernames and passwords were being worked on and early-on it looked like we would have a way for attendees to register for an account, and for them to create a username and password (the final solution was way cooler than this scheme, perhaps a story for later). The default protocol and transport that comes with Darkstar provides very little security for client logins. The Simple SGS protocol sends usernames and passwords in the clear over TCP. I was concerned that passwords could be snooped (humm.. a conference full of computer geeks, what were the chances? - for answer see below1) and though this was only a demo, what if someone used an important password thinking that it was being protected. In looking at the options and the time that was available two solutions seemed possible, to create an SSL transport to replace TCP, or create a new protocol that included some level encryption. To me, creating a new protocol seemed the easier of the two.
In the Darkstar server are two services that handle the bulk of the communication duties with the client, the client session service and the channel service. These two services implement the most of the ClientSession and Channel semantics that the application sees. They are built on top of a protocol layer. This layer determines the format and content of the messages exchanged between the client and the server. The protocol layer is, in turn, built on top of a transport layer. The transport layer deals with the network connection with the client.
The ComponentRegistry and TransactionProxy objects provide access to the components within the Darkstar kernel such as the data service or the task scheduler.
The ProtocolAcceptor object is responsible for initial client connection and login, including authentication. The client session service registers its interest with the acceptor by calling accept(), passing in a ProtocolListener object. When a successful client connection and login is made the acceptor should call ProtocolListener.newLogin() passing in the authenticated Identity of the client, a SessionProtocol object, and a completion handler. It is the SessionProtocol object which implements the outgoing messages needed by the session and channel services. The implementation must be able to handle sending a session and channel message, a channel join and leave message, and be able to disconnect the client. Exactly how these messages are sent is up to the SessionProtocol implementation and the client. When done processing the new login, the ProtocolListener will call the completion handler with a SessionProtocolHandler object. This object handles the incoming session and channel messages as well as logout and disconnect. It is the responsibility of the protocol implementation to call the SessionProtocolHandler object as needed. Piece of cake.
Now the protocol layer needs only be responsible for the when and what of the messages exchanged between the server and the client. It does not need to worry about the underlying network transport. It can, but it does not need to. There is a set of interfaces in the internal APIs that define a network transport layer. Through these APIs the protocol layer can use existing implementations of network transports and transport providers can code against the API to create new transports. But I will leave the discussion of transports for another post.
There is one item that deserves extra explanation. One of the methods that the protocol acceptor must implement is getDescriptor(). This method returns a ProtocolDescriptor object. The information in this object is used when a client connection must be redirected to another node. In particular, the getConnectionData() returns an array of data that contains the protocol and transport specific information needed by the client to connect to the node described by the descriptor. For example the byte array might contain the IP address and port number needed for a TCP connection. The server does not look at the data, but the client protocol must be able to interpret it. In general, if using the TCP transport supplied with Darkstar, getConnectionData() needs simply to return the data from the transport descriptor's getConnectionData() method. (see either the SGS simple protocol, or the challenge-response descriptor implementations).
For what Darkchat needed, the above steps were rendered trivial by simply copying the SGS simple protocol sources (found in the com.sun.sgs.impl.protocol.simple package) and modifying them to add in a couple of new protocol messages. The new protocol implements a challenge-response login exchange where the server sends a randomly generated challenge to the client and the client responds with their password encrypted using the challenge. The new messages were the client's request for challenge, and the server's message containing the challenge. The new protocol can be found in the Darkchat server sub-project in the com.sun.sgs.impl.protocol.challengeresponse package.
A similar tact was used to create the client-side protocol handler. The client challenge-response code was based on the sources from the sgs-java-client project. Unfortunately the client code in Darkchat is a little hard to follow for two reasons. First, the sgs-java-client code is hard to follow in the first place. Second, there is an extra two layers in the client side stack, one to allow replacement of the underlying Java SE client with one for Java ME, and the second to handle the conversion from Java to JavaFX. The Java ME client was never completed and does not exist in the source tree. Maybe something for the future!
Posted at 05:09PM Jul 27, 2009 by Keith Thompson in Project Darkstar | Comments[0]
Today's Page Hits: 15