Recipe 16.8 Program: TFTP UDP Client


This program implements the client half of the TFTP application protocol, a well-known service that has been used in the Unix world for network booting of workstations since before Windows 3.1. I chose this protocol because it's widely implemented on the server side, so it's easy to find a test server for it.

The TFTP protocol is a bit odd. The client contacts the server on the well-known UDP port number 69, from a generated port number,[3] and the server responds to the client from a generated port number. Further communication is on the two generated port numbers.

[3] When the application doesn't care, these port numbers are usually made up by the operating system. For example, when you call a company from a pay phone or cell phone, the company doesn't usually care what number you are calling from, and if it does, there are ways to find out. Generated port numbers generally range from 1024 (the first nonprivileged port; see Chapter 17) to 65535 (the largest value that can be held in a 16-bit port number).

Getting into more detail, as shown in Figure 16-1, the client initially sends a read request with the filename and reads the first packet of data. The read request consists of two bytes (a short) with the read request code (short integer with a value of 1, defined as OP_RRQ), two bytes for the sequence number, then the ASCII filename, null terminated, and the string octet, also null terminated. The server reads the read request from the client, verifies that it can open the file and, if so, sends the first data packet (OP_DATA), and then reads again. This read-acknowledge cycle is repeated until all the data is read. Note that each packet is 516 bytes (512 bytes of data, plus 2 bytes for the packet type and 2 more for the packet number) except the last, which can be any length from 4 (zero bytes of data) to 515 (511 bytes of data). If a network I/O error occurs, the packet is resent. If a given packet goes astray, both client and server are supposed to perform a timeout cycle. This client does not, but the server does. You could add timeouts using a thread; see Recipe 24.4. The client code is shown in Example 16-9.

Figure 16-1. The TFTP protocol packet formats
figs/jcb2_1601.gif


Example 16-9. RemCat.java
import java.io.*; import java.net.*; /**  * RemCat - remotely cat (DOS type) a file, using the TFTP protocol.  * Inspired by the "rcat" exercise in Learning Tree Course 363,   * <I>UNIX Network Programming</I>, by Dr. Chris Brown.  *  * Note that the TFTP server is NOT "internationalized"; the name and  * mode in the protocol are defined in terms of ASCII, not UniCode.  */ public class RemCat {     /** The UDP port number */     public final static int TFTP_PORT = 69;     /** The mode we will use - octet for everything. */     protected final String MODE = "octet";     /** The offset for the code/response as a byte */     protected final int OFFSET_REQUEST = 1;     /** The offset for the packet number as a byte */     protected final int OFFSET_PACKETNUM = 3;     /** Debugging flag */     protected static boolean debug = false;     /** TFTP op-code for a read request */     public final int OP_RRQ = 1;     /** TFTP op-code for a write request */     public final int OP_WRQ = 2;     /** TFTP op-code for a data packet */     public final int OP_DATA     = 3;     /** TFTP op-code for an acknowledgement */     public final int OP_ACK     = 4;     /** TFTP op-code for an error packet */     public final int OP_ERROR = 5;     protected final static int PACKET_SIZE = 516;    // == 2 + 2 + 512     protected String host;     protected InetAddress servAddr;     protected DatagramSocket sock;     protected byte buffer[];     protected DatagramPacket inp, outp;     /** The main program that drives this network client.      * @param argv[0] hostname, running TFTP server      * @param argv[1..n] filename(s), must be at least one      */     public static void main(String[] argv) throws IOException {         if (argv.length < 2) {             System.err.println("usage: java RemCat host filename[...]");             System.exit(1);         }         if (debug)             System.err.println("Java RemCat starting");         RemCat rc = new RemCat(argv[0]);         for (int i = 1; i<argv.length; i++) {             if (debug)                 System.err.println("-- Starting file " +                      argv[0] + ":" + argv[i] + "---");             rc.readFile(argv[i]);         }     }     RemCat(String host) throws IOException {         super( );         this.host = host;         servAddr = InetAddress.getByName(host);         sock = new DatagramSocket( );         buffer = new byte[PACKET_SIZE];         inp = new DatagramPacket(buffer, PACKET_SIZE);         outp = new DatagramPacket(buffer, PACKET_SIZE, servAddr, TFTP_PORT);     }     void readFile(String path) throws IOException {         /* Build a tftp Read Request packet. This is messy because the          * fields have variable length. Numbers must be in          * network order, too; fortunately Java just seems           * naturally smart enough :-) to use network byte order.          */         buffer[0] = 0;         buffer[OFFSET_REQUEST] = OP_RRQ;        // read request         int p = 2;            // number of chars into buffer         // Convert filename String to bytes in buffer , using "p" as an         // offset indicator to get all the bits of this request         // in exactly the right spot.         byte[] bTemp = path.getBytes( ); // i.e., ASCII         p += path.length( );         buffer[p++] = 0;        // null byte terminates string         // Similarly, convert MODE ("octet") to bytes in buffer         MODE.getBytes(0, MODE.length( ), buffer, p);         p += MODE.length( );         buffer[p++] = 0;        // null terminate         /* Send Read Request to tftp server */         outp.setLength(p);         sock.send(outp);         /* Loop reading data packets from the server until a short          * packet arrives; this indicates the end of the file.          */         int len = 0;         do {             sock.receive(inp);             if (debug)                 System.err.println(                     "Packet # " + Byte.toString(buffer[OFFSET_PACKETNUM])+                     "RESPONSE CODE " + Byte.toString(buffer[OFFSET_REQUEST]));             if (buffer[OFFSET_REQUEST] == OP_ERROR) {                 System.err.println("remcat ERROR: " +                     new String(buffer, 4, inp.getLength( )-4));                 return;             }             if (debug)                 System.err.println("Got packet of size " +                     inp.getLength( ));             /* Print the data from the packet */             System.out.write(buffer, 4, inp.getLength( )-4);             /* Ack the packet. The block number we               * want to ack is already in buffer so               * we just change the opcode. The ACK is               * sent to the port number which the server               * just sent the data from, NOT to port               * TFTP_PORT.              */             buffer[OFFSET_REQUEST] = OP_ACK;             outp.setLength(4);             outp.setPort(inp.getPort( ));             sock.send(outp);         } while (inp.getLength( ) == PACKET_SIZE);         if (debug)             System.err.println("** ALL DONE** Leaving loop, last size " +                 inp.getLength( ));     } }

To test this client, you would need a TFTP server. If you are on a Unix system that you administer, you can enable the TFTP server to test this client just by editing the file /etc/inetd.conf and restarting (or just reloading, with kill -HUP) the inetd server. inetd is a program that listens for a wide range of connections and starts the servers only when a connection from a client comes along (a kind of lazy evaluation). Beware of security holes; don't turn a TFTP server loose on the Internet without first reading a good security book, such as O'Reilly's Building Internet Firewalls. I set up the traditional /tftpboot directory, put this line in my inetd.conf, and reloaded inetd:

tftp  dgram udp wait root /usr/libexec/tftpd tftpd -s /tftpboot

Then I put a few test files, one named foo, into the /tftpboot directory. Running:

$ java RemCat localhost foo

produced what looked like the file. But just to be safe, I tested the output of RemCat against the original file, using the Unix diff comparison program. No news is good news:

$ java RemCat localhost foo | diff - /tftpboot/foo

So far so good. Let's not slip this program on an unsuspecting network without exercising the error handling at least briefly:

$ java RemCat localhost nosuchfile  remcat ERROR: File not found  $



Java Cookbook
Java Cookbook, Second Edition
ISBN: 0596007019
EAN: 2147483647
Year: 2003
Pages: 409
Authors: Ian F Darwin

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net