Networking

An overview of internetworking in general.
  • IP
  • TCP
  • UDP

Networking in Java

The specifics of network programming in Java.
  • java.io, java.net
  • UDP clients and servers
  • TCP clients and servers
  • Streams

A Cryptographic Example

An example of a crypto networking application in Java.
  • Symmetric and public key crypto, hashing
  • DES and SHA streams
  • A secure server

Networking


Networks

A network is simply a collection of interconnected machines or devices which speak the same data transmission protocol.

Networks are typically classified based on their geographic span and the protocols they utilise to transport data.

  • LAN (Local Area Network)
    • Ethernet
    • Token Ring>
  • WAN (Wide Area Network)
    • X.25
    • ISDN
    • ATM

TCP/IP Suite

The TCP/IP family of networking protocols and services subdivides networking functions into five conceptual layers.
  1. Physical layer (e.g. Ethernet cabling and hardware)
  2. Datalink layer (e.g. Ethernet frames)
  3. Network layer (IP datagrams)
  4. Transport layer (TCP segments, UDP)
  5. Application layer (e.g. ftp, telnet, http)

Internet

An "internet" is a collection of more than one network with any-to-any connectivity based on a layer 3 protocol. The "Internet" is the worldwide "network of networks" that run TCP/IP using IP addresses under the jurisdiction of the Internet Architecture Board.

The Internet is composed of independent routing units called autonomous systems. These systems used to be primarily educational and military networks connected via the National Science Foundation's high-speed routing backbone. Today the Internet "backbone" is large commercial telco providers such as MCI, UUNET and Sprint that interface at "peering points" such as MAE-east in Washington, DC.

IP
the Internet Protocol


IP is a protocol for addressing hosts and routing data packets (datagrams) between them. These hosts may be on networks with different implementations.

IP Addresses

IP addresses take the form x.x.x.x, where each x is a number between 0 and 255. Every IP address falls into a network. IP networks are classified into 5 categories.
  • Class A: 1-126 (16M hosts each)
  • Class B: 128-191 (65536 hosts each)
  • Class C: 192-223 (256 hosts each)
  • Class D: 224-239 (multicast)
  • Class E: 240-255 (reserved)

IP Routing

The key to IP is its routing capability. An IP router:
  • Has network interfaces
  • Examines each packet that appears on its interfaces
  • Calculates a least-cost route to the destination
  • Forwards the packet down the appropriate interface

Internal Routing Protocols

Within each autonomous system, IP uses internal routing protocols to determine how to route packets.
  • RIP
  • IGRP
  • OSPF
Between autonomous systems, external routing protocols are used for routing.
  • BGP
  • EGP
IP uses ICMP to correct routing problems on the fly. ICMP support various messages for managing packet flow.
  • Destination unreachable
  • Redirect
  • Echo

IP Datagrams

An IP datagram consists of a header and a Protocol Data Unit (PDU). The header contains information about fragmentation, length, time to live (TTL) and similar information. The PDU contains the data being encapsulated, which usually means a TCP or UDP packet.

TCP
the Transmission Control Protocol


TCP is a connection-oriented transport protocol that sits on top of IP, meaning that TCP packets (segments) are encapsulated into IP datagrams. TCP encoding and decoding occur at the endpoints of the data transmission.

TCP enables data streams in which all transmitted data is guaranteed to appear at the destination host in the order it was transmitted.

TCP's Features

  • Connections (streams)
  • Congestion control (slow start, fallback)
  • Full-duplex transmission
  • Multiplexing (ports)

Ports and Sockets

A socket represents a TCP connection between two processes across the network, and is specified by IP addresses at both ends of the connection and a port number at the server.

Uses for TCP Sockets

TCP sockets are the basis of most applications-layer network communications. TCP is useful when:
  • Network overhead is not important
  • Reliable data transfer is required

UDP
the User Datagram Protocol


UDP is a "connectionless" transport layer protocol that sits on top of IP and provides no state management and data integrity functions except for a checksum.

UDP's Characteristics

  • No congestion avoidance
  • No packet delivery guarantees
  • Fragmentation problems
  • Low overhead

UDP Sockets

UDP provides sockets in much the way that TCP does, however a UDP socket is not bound to a particular connection. A UDP transmission is identified by its two endpoint ports and IP numbers.

Uses for UDP

UDP is useful when:
  • Transport-layer overhead must be minimized
  • Data reliability is not crucial
  • Small, independent application packets needed (NFS, DNS, SNMP)

Java Networking


Relevant Packages

  • java.lang.* - threading and class access
  • java.io.* - files and streams
  • java.net.* - networking

Current Networking Options

Built-in API support for regular internet protocols.
  • UDP/IP
  • TCP/IP

Future Networking Options

These are available for testing now and due out finally later this year, but will be commercial products.
  • IDL - ORB access
  • RMI - remote method invocation (RPC)
No longer at http://splash.javasoft.com/pages/intro.html.

java.io.*


Relevant Classes

  • InputStream - the generic input stream
  • OutputStream - the generic output stream
  • BufferedInputStream - a buffered input stream
  • BufferedOutputStream - a buffered output stream
  • PipedInputStream - a pipe for inter-thread communication
  • PipedOutputStream - a pipe for inter-thread communication
  • ByteArrayInputStream - stream from a byte buffer
  • ByteArrayOutputStrema - stream into a byte buffer
  • DataInputStream - higher-level data input
  • DataOutputStream - higher-leve data output

Streams

Streams are the building blocks of all interesting communcations. You can get streams from files, from the console, from network sockets and so forth. You can then attach filter streams to these to achieve higher-level communications.

We'll work with streams to do fun stuff later.

java.net.*


Relevant Classes

These are the classes that we'll look at today.
  • DatagramPacket - a UDP packet (receive/send)
  • DatagramSocket - a UDP socket (send/receive)
  • InetAddress - an internet address
  • ServerSocket - a TCP server socket
  • Socket - a TCP connection socket

Other Classes

These are some of the other networking classes.
  • URL - a URL processor, useful for parsing URLs
  • ContentHandler - a class which handles a MIME type
  • URLConnection - a connection through the URL class
There are other URL-related classes which are mostly of interest if you're writing a browser of some kind.

Java and UDP


Conceptual

UDP is packet-based; it revolves around constructing packets of information and dispatching them. Receipt is not guaranteed, but receipts are guaranteed to not be corrupt.

Sending

  • Construct a DatagramPacket with data and destination address, port
  • Construct a DatagramSocket on a specific port (or let OS choose)
  • Send the packet through the socket
  • Sending is complete once the packet enters the network

Receiving

  • Construct a DatagramSocket on a specific port
  • Wait for receipt of a packet
  • Interrogate the packet for data and source address, port

Notes

Packet length is stored in sixteen bits, so limit your data size. Fragmentation will reduce your chances of delivery, so limit your data size. Fragmentation occurs when the packet is bigger than a datalink maximum transmission unit along the way. Ethernet has a 1500 byte MTU; CSLIP 512 bytes; SLIP 256 bytes. Deliveries may be reordered and duplicated but not corrupted so be prepared.

UDP Transmission


Code

This is a code fragment for UDP transmission.
// we don't care about our port
DatagramSocket socket = new DatagramSocket ();

// our data is an array of bytes
byte data[] = new byte[64];
// fill in the data

// we send to port 13666 of www.nsa.gov
DatagramPacket packet = new DatagramPacket (data, data.length,
  InetAddress.getByName ("www.nsa.gov"), 13666);

// send the packet
socket.send (packet);

// close the socket when we're done
socket.close ();

Notes

Stay tuned for how to produce a byte array of useful data. We can keep this socket around if we want to send more UDP packets. Note that we can use one socket to send to anyone anywhere on the net and we can use this one socket to both send and receive.

UDP Reception


Code

This is a code fragment for UDP reception.
// we want to receive on port 13666
DatagramSocket socket = new DatagramSocket (13666);

// we supply a reception buffer
byte buffer[] = new byte[65536];

// we supply a reception packet
DatagramPacket packet = new DatagramPacket (data, data.length);

// block for a packet to arrive
socket.receive (packet);

// pull the packet apart
InetAddress fromAddr = packet.getAddress ();
int fromPort = packet.getPort ();
int length = packet.getLength ();
byte data[] = packet.getData (); // == buffer

// close the socket when we're done
socket.close ();

Notes

Stay tuned for how to decode a byte array of useful data. We can keep this socket around if we want to send or receive more UDP packets; we can use the source info to target a reply.

Wrapping up UDP


Problems

UDP has some problems which make it unsuited for many simple applications.
  • Limited packet size
  • Unreliable delivery
  • Packet based
  • No congestion control

Advantages

There will always be a place for UDP in applications where latency and overhead are an issue, and absolute reliability is not required. Simple query servers are often well served by UDP.
  • Low overhead
  • Low latency
  • Multicast

Solution

A solution to many of the problems is to combine the use of TCP and UDP in a single application.
  • Mix TCP and UDP
  • TCP for essential data (text communications)
  • UDP for non-essential data and multicast (video)

Introducing Streams


Streams

Streams are conceptual pipes, or FIFOs. You can write data into an output stream and read data from an input stream. An output stream is usually associated in some manner with an input stream, although not always directly. Data is read in the same order in which it was written.

Sources

Streams can be constructed from many different sources; they provide a uniform means of access to a variety of devices:
  • Files
  • TCP connections
  • Memory buffers

Output Stream

OutputStream, the most basic output stream class, only lets you write bytes, either one at a time, or a bunch at a time: write()

Input Stream

Similarly, InputStream, the most basic input stream class, only lets you read bytes, whether one at a time or many: read()

Java and TCP


Conceptual

TCP is stream-based; the concept is a virtual stream through the network. Transparently, you can write data in to one end of a TCP connection and read it out the other end. At the server, a process listens on a particular port. Every time a connection is made to the port, a TCP stream is created between the server and the client; this connection is abstracted by the Socket class.

The Server

  • Construct a ServerSocket on a specified port (or let OS choose)
  • Wait to accept a connection
  • Communicate through the new Socket

The Client

  • Construct a Socket to a specified host and port
  • Communicate through the Socket

The Socket

  • Communicate using the socket's InputStream and OutputStream

Notes

There are no visible packets. TCP internally fragments data into segments and transmits these over IP. It is constructive to know that packets are sent; if you don't buffer data and transmit in chunks, you may waste bandwidth, if that is an issue.

A TCP Server


The Code

// create the server socket
ServerSocket server = new ServerSocket (8000);

// accept a connection
Socket s = server.accept ();

// get the streams
InputStream i = s.getInputStream ();
OutputStream o = s.getOutputStream ();

// echo all input back to the client
try {
  while (true)
    o.write (i.read ()); // one byte at a time
} catch (IOException e) {
  // close the socket when client quits
  s.close ();
}

// close the server socket
server.close ();

Notes

We can accept multiple sockets from the server socket, however we will need threads, as accept() and read() both block. There is no non-blocking I/O in Java. We will get an IOException if the client closes the socket. All of these commands can throw IOException. Use getInetAddress() to find the client's address.

A TCP Client


The Code

// open a socket to the server
Socket s = new Socket ("gore.cs.tcd.ie", 8000);

// get the streams
InputStream i = s.getInputStream ();
OutputStream o = s.getOutputStream ();

// build some data
byte data[] = {0,1,2,3}

// send the data
o.write (data);

// read back the data echoed by the server
while (i.read () != 3);

// close the socket
s.close ();

Notes

The socket automatically connects when we create it. An IOException is thrown if the connection fails. We'll work on some more useful data streams a little bit later.

Non-Blocking TCP


Blocking

The previous code blocked (waited) on reading from a socket. If we want to service more than one socket at a time, we need either threads or a non-blocking read of some kind. There is no non-blocking read, however there is a non-blocking query of the number of bytes available.

Code

please wait

Notes

The example code accepts two connections and echoes data between the clients.
  • available() - this method immediately returns the number of bytes available to read. Note that if the client closes the socket, this call will not indicate that anything unusual has happend. The server will only find the socket closed when it tries to write.
  • read() - here, we read an array of bytes. This method will read as many bytes as are available, up to the length of the array, and return the number of bytes read.

A Multithreaded TCP Server


Threads

Threads are independent flows of execution within the one process. Threads can share data with each other through the field variables of a class and are a very powerful language mechanism.

Code

please wait

Notes

In the threaded server, our mainline sits accepting new server connections. For every connection received, a new Handler object is created for the socket. The Handler starts a thread which sits blocking and reading input from the client. Whenever input is received, we run through all current connections and write the data to the OutputStream.
  • We use finally to ensure that our Handler is removed from the Vector of connections when an error occurs (for example, the client closes the socket).
  • Our broadcast method must synchronize on the Vector to ensure multithread coherency.
  • Writing to a stream may block, so the broadcast method may become a bottleneck.
We're just dealing with bytes here, so data from the various clients will be interleaved in the broadcasts. This is just an example. We'll develop a better multithreaded server later.

Wrapping up TCP


TCP provides reliable streams-based communications between clients and servers. Clients may, of course, open server sockets and communicate among themselves in a peer-to-peer manner, but it is usually easiest to operate a client/server architecture.

At the level of reading and writing bytes, TCP is awkward to use from Java. As we'll see next, streams allow us to abstract away from the bytes and deal with high-level data communications.

To implement useful servers, it is necessary to work with multithreading and to understand when and where to use synchronization.

Java Streams


Classifications

There are two main classifications of streams in Java:
  • Raw input or output streams which just support reading and writing bytes, and which usually directly interface with some other device. Examples of these include the streams which come from files and sockets. These usually subclass either InputStream or OutputStream.
  • Filter input and output streams which attach to other streams and provide some higher level function. Examples include buffered streams and high-level data communication streams. These usually subclass FilterInputStream or FilterOutputStream. Filter streams can attach to other filter streams in order to provide composite function.

Buffered Streams

BufferedInputStream and BufferedOutputStream make communications more efficient by buffering data in memory so that every I/O operation does not go through the operating system. In order to force a write, you must flush() a BufferedOutputStream.

Closing a filter stream performs any necessary operations to close the filter stream and then closes the stream to which it is attached, so you must only close the outermost filter of a series. This will flush a BufferedOutputStream.

Byte Array Streams

These are raw input and output streams which attach to memory buffers (byte arrays).

ByteArrayOutputStream places all data in an internal byte array which expands as more data arrives. The array can be extracted using the toByteArray() method. Call reset() to reset the internal buffer. ByteArrayInputStream reads from a supplied byte array.

You can use these streams to construct and destruct UDP packets.

Data Streams

These are used to transmit higher-level data; information is encoded and decoded in network format. Types that can be transmitted include:
  • boolean, byte, short, int, long, float, double, char
  • ASCII string (readLine, writeBytes)
  • UTF string (Unicode encoding)
Note that using a DataOutputStream attached to an unbuffered socket output stream will invariably result in two packets being sent for every new transmission. Attach the DataOutputStream to a BufferedOutputStream, and flush the DataOutputStream when you are done.

Using Streams


The Code

// make a socket
Socket s = new Socket ("localhost", 123);
// get the streams
InputStream i = s.getInputStream ();
OutputStream o = s.getOutputStream ();
// make a buffered output stream
BufferedOutputStream bO = new BufferedOutputStream (o);
// make some data streams
DataInputStream dI = new DataInputStream (i);
DataOutputStream dO = new DataOutputStream (bO);

// write some data
dO.writeUTF ("This is a query");
dO.flush ();

// read some data
String response = dI.readUTF ();
int value = dI.readInt ();

dO.close ();
s.close ();

Notes

We attach a DataOutputStream to a BufferedOutputStream attached to the socket's output stream. Our first write is buffered in the BufferedOutputStream until we flush() at which point it is sent through the socket. The BufferedOutputStream has a fixed capacity at which point it will automatically flush. If we were reading a lot, we might use a BufferedInputStream.

Using Streams with UDP


Constructing a Packet

// make a byte array stream
ByteArrayOutputStream o = new ByteArrayOutputStream ();
DataOutputStream dO = new DataOutputStream (o);
// fill it with some information
dO.writeUTF ("a message");
dO.writeInt (1234);
// extract the byte array
byte data[] = o.toByteArray ();
// construct a UDP packet
DatagramPacket p = new DatagramPacket
  (data, data.length, ...);
// ... send it

// so we can reuse the stream
o.reset ();

Destructing a Packet

// ... receive DatagramPacket p
// make a byte array stream
ByteArrayInputStream i = new ByteArrayInputStream
  (p.getData (), 0, p.getLength ());
DataInputStream dI = new DataInputStream (i);
// read some information
String message = dI.readUTF ();
int value = dI.readInt ();

Structured Data


Higher Level Data

Even with the ability to write the basic Java types, higher-level communication is still inconvenient. There are two ways to implement more useful communication streams. One option is to create a monolithic data stream which understands and can dissect all desired types.

The Monolithic Data Stream

public class MonolothicDataOutputStream 
                          extends DataOutputStream {
  public MonolithicDataOutputStream (OutputStream o) {
    super (o);
  }

  public void writeRectangle (Rectangle r)
                                  throws IOException {
    writeInt (r.x);
    writeInt (r.y);
    writeInt (r.width);
    writeInt (r.height);
  }
}

Notes

The corresponding input stream is obvious. This approach has some shortcomings; it must be extended to handle all desired types, and tightly couples the output stream to the internal structure of objects, a structure which may not even be visible. The chief alternative is for relevant classes to implement read() and write() functions with a stream parameter.

Encryption


There are many algorithms for encryption, however they most commonly fall into one of two major fields:
  • Symmetric Encryption: The same key is used to encrypt and decrypt.
  • Public Key Encryption: Different keys are used to encrypt and decrypt. Knowledge of key is not sufficient to determine the other. This is an instance of an asymmetric encryption algorithm. Not all asymmetric algorithms have these properties.
DES, the most common symmetric algorithm, is relatively fast and relatively secure. The best currently known attack, linear cryptanalysis, requires 2^43 plaintexts (you are given both plaintext and ciphertext and must determine the key).

RSA, the most common public key algorithm, is relatively slow (1000 times slower than DES) but has more uses. Current algorithms would require a around 10^23 steps to factor 664 bit numbers. Commonly RSA is used to securely communicate a key for subsequent use with DES.

We're just going to use DES as an example of an interesting stream.

Hashing


Frequently we don't want to encrypt messages, we simply want to ensure their authenticity. We can use regular encryption to achieve this goal; if the sender encrypts the message in either a secret shared key, or a private key of a public key-pair, then our ability to decrypt the message guarantees its origin. This is slow.

Instead, we use a hashing algorithm. The idea is that we take the entire message, crunch it down to a digest which is couple of bits long (say 128 or 160) and just sign the digest. If the hashing algorithm is fast, we only have to encrypt a few bits and the whole process is relatively fast.

The security of this algorithm lies in the fact that it must be nearly impossible to find two messages which hash to the same digest. This is where secure hash algorithms come in. Current popular algorithms (MD5 and SHS) have been tested for years and not shown to be weak.

A DES Stream


Block Cipher

DES is a block cipher; it encrypts 64 bit chunks at a time. This is slightly awkward because streams typically operate in a byte-by-byte manner. In order to cope with this, we must pad chunks of data out to an eight-byte boundary, encrypt this and send it out. In order to achieve this, our DESStream buffers all output, and only sends data when flush() is called. Given a black box which performs DES, our basic stream is:
  • extend OutputStream
  • buffer all data in a ByteArrayOutputStream
  • when flush() is called pad, encrypt and transmit

Notes

Our black-box DES in Java on a fat Pentium encrypts 9Kb per second. It could be optimised a bit.

SHS Streams


SHS

SHS is a hashing algorithm which breaks a message up into 160-bit chunks, does lots of bit-twiddling and comes up with a 160-bit hash. Our SHS Stream is much like the DES Stream.
  • extend OutputStream
  • buffer all output
  • compute a hash on flush
  • dump the message in plaintext
  • dump the hash in ciphertext

CryptoStreams


Generic CryptoStreams

Because of the similarity of these functions, I implemented a generic CryptoStream which takes a block crypto function and does all the stream work for it. All the CryptoFunction needs to be able to do is write an encrypted chunk of data out a stream, or to read and decrypt a block of data from a stream.

BlockCryptoFunction Code

package venom.crypt;

import java.io.*;

public interface BlockCryptoFunction {
  public byte[] cryptoRead (DataInputStream o)
    throws IOException;
  public void cryptoWrite (DataOutputStream o, byte b[])
    throws IOException;
}

CryptoStream Code

please wait

Cipher Block Chaining


DES Problem

Raw DES is vulnerable to replay attacks. We use CBC mode to remove this vulnerability.

Code

public class DESCBC extends DES {

  long key[];
  long eOld = 0, dOld = 0;

  public DESCBC (long key) {
    super (key);
  }

  public long encrypt (long w) {
    return eOld = super.encrypt (w ^ eOld);
  } 
  
  public long decrypt (long w) {
    long z = decrypt (w) ^ dOld;
    dOld = w;
    
    return z;
  } 
} 

Notes

We must first send a random IV or the whole message start can be attacked.

A Secure Server


Authentication

When you connect to the server, the server must be able to send you a session key for symmetric crypto. Can't send this key in plaintext, can't send your password over the net.
  • Send your name and a random number
  • Server looks up your password
  • Server returns a key and your random number encrypted by your password
  • Decrypt your key and check the random number matches
You can then use DES with the session key to communicate.

Problems

People choose bad passwords. There is a solution other than requiring people to use ten or fifteen word pass-phrases. EKE (encrypted key exchange) is a six-step key exchange protocol which uses public-key crypto as well as symmetric crypto (although you do not need initial public-key distribution) to authenticate in a manner that requires an eavesdropper to crack public-key encryption in order to find the session key.

TunnelStreams


Client-Client Communications

It is slow for the server to have to decrypt and encrypt all messages. We allow pair or group communications to overcome this problem; the users instruct the server to generate a session key. For messages between each other, the users use this session key. The server only needs to forward the encrypted message bodies.

TunnelStream

A fun use of streams is to implement this tunneled stream transparently. We implement a stream constructed with the remote user name and the server stream. To communicate with the user, we just talk down this stream. Behind the scenes, this stream obtains a shared session key, encrypts the body in the session key and sends the packet down the server wire with a tunnel header. It looks like we have different streams to the server and to each user.