2 * TFTP to HTTP proxy in Java
4 * Copyright Ken Yap 2003
7 import java
.io
.IOException
;
8 import java
.io
.InputStream
;
9 import java
.io
.FileInputStream
;
10 import java
.io
.BufferedInputStream
;
11 import java
.io
.UnsupportedEncodingException
;
12 import java
.lang
.String
;
13 import java
.lang
.StringBuffer
;
14 import java
.lang
.Thread
;
15 import java
.lang
.NumberFormatException
;
16 import java
.net
.DatagramPacket
;
17 import java
.net
.DatagramSocket
;
18 import java
.net
.InetAddress
;
19 import java
.net
.SocketException
;
20 import java
.net
.SocketTimeoutException
;
21 import java
.nio
.Buffer
;
22 import java
.nio
.ByteBuffer
;
23 import java
.nio
.BufferUnderflowException
;
24 import java
.util
.HashMap
;
25 import java
.util
.Properties
;
27 import org
.apache
.commons
.httpclient
.Credentials
;
28 import org
.apache
.commons
.httpclient
.Header
;
29 import org
.apache
.commons
.httpclient
.HostConfiguration
;
30 import org
.apache
.commons
.httpclient
.HttpClient
;
31 import org
.apache
.commons
.httpclient
.HttpException
;
32 import org
.apache
.commons
.httpclient
.HttpMethod
;
33 import org
.apache
.commons
.httpclient
.UsernamePasswordCredentials
;
34 import org
.apache
.commons
.httpclient
.methods
.GetMethod
;
36 import org
.apache
.commons
.logging
.Log
;
37 import org
.apache
.commons
.logging
.LogFactory
;
40 * Description of the Class
43 *@created 24 September 2003
45 public class T2hproxy
implements Runnable
{
47 * Description of the Field
49 public final static String NAME
= T2hproxy
.class.getName();
51 * Description of the Field
53 public final static String VERSION
= "0.1";
55 * Description of the Field
57 public final static int MTU
= 1500;
59 * Description of the Field
61 public final static short TFTP_RRQ
= 1;
63 * Description of the Field
65 public final static short TFTP_DATA
= 3;
67 * Description of the Field
69 public final static short TFTP_ACK
= 4;
71 * Description of the Field
73 public final static short TFTP_ERROR
= 5;
75 * Description of the Field
77 public final static short TFTP_OACK
= 6;
79 * Description of the Field
81 public final static short ERR_NOFILE
= 1;
83 * Description of the Field
85 public final static short ERR_ILLOP
= 4;
87 * Description of the Field
89 public final static int MAX_RETRIES
= 5;
91 * TFTP timeout in milliseconds
93 public final static int TFTP_ACK_TIMEOUT
= 2000;
95 * Description of the Field
97 public final static int DEFAULT_PROXY_PORT
= 3128;
99 private static Log log
= LogFactory
.getLog(T2hproxy
.class);
101 * The members below must be per thread and must not share any storage with
104 private DatagramSocket responsesocket
;
105 private DatagramPacket response
;
106 private InetAddress iaddr
;
109 private String prefix
;
110 private String proxy
= null;
112 private HashMap options
= new HashMap();
113 private int blocksize
= 512;
114 private HttpClient client
= new HttpClient();
115 private HttpMethod method
;
116 private BufferedInputStream bstream
= null;
117 private String message
;
121 * Constructor for the T2hproxy object
123 *@param i Description of the Parameter
124 *@param p Description of the Parameter
125 *@param b Description of the Parameter
126 *@param pf Description of the Parameter
127 *@param pr Description of the Parameter
128 *@param t Timeout for HTTP GET
130 public T2hproxy(InetAddress i
, int p
, byte[] b
, String pf
, String pr
, int t
) {
133 // make a copy of the request buffer
134 req
= new byte[b
.length
];
135 System
.arraycopy(b
, 0, req
, 0, b
.length
);
144 * Extract an asciz string from bufer
146 *@param buffer Description of the Parameter
147 *@return The asciz value
149 private String
getAsciz(ByteBuffer buffer
) {
150 StringBuffer s
= new StringBuffer();
153 while ((b
= buffer
.get()) != 0) {
156 } catch (BufferUnderflowException e
) {
158 return (s
.toString());
164 * Convert a string of digits to a number, invalid => 0
166 *@param s Description of the Parameter
167 *@return Description of the Return Value
169 private int atoi(String s
) {
175 value
= (new Integer(s
)).intValue();
176 } catch (NumberFormatException e
) {
183 * Wait for ack packet with timeout
185 *@return Return block number acked
187 private int waitForAck() {
188 DatagramPacket ack
= new DatagramPacket(new byte[MTU
], MTU
);
191 responsesocket
.setSoTimeout(TFTP_ACK_TIMEOUT
);
192 responsesocket
.receive(ack
);
193 } while (!ack
.getAddress().equals(iaddr
) || ack
.getPort() != port
);
194 } catch (SocketTimeoutException e
) {
196 } catch (Exception e
) {
197 log
.info(e
.toString(), e
);
199 ByteBuffer buffer
= ByteBuffer
.wrap(ack
.getData(), ack
.getOffset(), ack
.getLength() - ack
.getOffset());
201 if ((op
= buffer
.getShort()) == TFTP_ACK
) {
202 return ((int) buffer
.getShort());
203 } else if (op
== TFTP_ERROR
) {
211 * Description of the Method
213 *@param error Description of the Parameter
214 *@param message Description of the Parameter
216 private void sendError(short error
, String message
) {
217 ByteBuffer buffer
= ByteBuffer
.wrap(response
.getData());
218 buffer
.putShort(TFTP_ERROR
).putShort(error
).put(message
.getBytes());
219 response
.setLength(buffer
.position());
221 responsesocket
.send(response
);
222 } catch (Exception e
) {
223 log
.info(e
.toString(), e
);
229 * Description of the Method
231 *@return Description of the Return Value
233 private boolean sendOackRecvAck() {
234 ByteBuffer buffer
= ByteBuffer
.wrap(response
.getData());
235 buffer
.putShort(TFTP_OACK
).put("blksize".getBytes()).put((byte) 0).put(String
.valueOf(blocksize
).getBytes()).put((byte) 0);
236 response
.setLength(buffer
.position());
238 for (retry
= 0; retry
< MAX_RETRIES
; retry
++) {
240 responsesocket
.send(response
);
241 } catch (Exception e
) {
242 log
.info(e
.toString(), e
);
244 if (waitForAck() == 0) {
245 log
.debug("Ack received");
249 return (retry
< MAX_RETRIES
);
254 * Description of the Method
256 *@param block Description of the Parameter
257 *@return Description of the Return Value
259 private boolean sendDataBlock(int block
) {
261 for (retry
= 0; retry
< MAX_RETRIES
; retry
++) {
263 responsesocket
.send(response
);
264 } catch (Exception e
) {
265 log
.info(e
.toString(), e
);
268 if ((ablock
= waitForAck()) == block
) {
269 log
.debug("Ack received for " + ablock
);
271 } else if (ablock
== -1) {
272 log
.info("Timeout waiting for ack");
273 } else if (ablock
== -2) {
276 log
.info("Unknown opcode from ack");
279 return (retry
< MAX_RETRIES
);
284 * Description of the Method
286 *@param buffer Description of the Parameter
287 *@return Description of the Return Value
289 private boolean handleOptions(ByteBuffer buffer
) {
291 String option
= getAsciz(buffer
);
292 String value
= getAsciz(buffer
);
293 if (option
.equals("") || value
.equals("")) {
296 log
.info(option
+ " " + value
);
297 options
.put(option
, value
);
299 blocksize
= atoi((String
) options
.get("blksize"));
300 if (blocksize
< 512) {
303 if (blocksize
> 1432) {
306 return (sendOackRecvAck());
311 * Description of the Method
313 *@param url Description of the Parameter
315 private void makeStream(String url
) {
316 // establish a connection within timeout milliseconds
317 client
.setConnectionTimeout(timeout
);
319 String
[] hostport
= proxy
.split(":");
320 int port
= DEFAULT_PROXY_PORT
;
321 if (hostport
.length
> 1) {
322 port
= atoi(hostport
[1]);
324 port
= DEFAULT_PROXY_PORT
;
327 log
.info("Proxy is " + hostport
[0] + ":" + port
);
328 client
.getHostConfiguration().setProxy(hostport
[0], port
);
330 // create a method object
331 method
= new GetMethod(url
);
332 method
.setFollowRedirects(true);
333 method
.setStrictMode(false);
336 if ((status
= client
.executeMethod(method
)) != 200) {
337 log
.info(message
= method
.getStatusText());
340 bstream
= new BufferedInputStream(method
.getResponseBodyAsStream());
341 } catch (HttpException he
) {
342 message
= he
.getMessage();
343 } catch (IOException ioe
) {
344 message
= "Unable to get " + url
;
350 * Reads a block of data from URL stream
352 *@param stream Description of the Parameter
353 *@param data Description of the Parameter
354 *@param blocksize Description of the Parameter
355 *@param offset Description of the Parameter
356 *@return Number of bytes read
358 private int readBlock(BufferedInputStream stream
, byte[] data
, int offset
, int blocksize
) {
361 while (nread
< blocksize
) {
363 status
= stream
.read(data
, offset
+ nread
, blocksize
- nread
);
364 } catch (Exception e
) {
377 * Description of the Method
379 *@param filename Description of the Parameter
381 private void doRrq(String filename
) {
382 String url
= prefix
+ filename
;
383 log
.info("GET " + url
);
385 if (bstream
== null) {
387 sendError(ERR_NOFILE
, message
);
390 // read directly into send buffer to avoid buffer copying
392 ByteBuffer buffer
= ByteBuffer
.wrap(data
= response
.getData());
393 // dummy puts to get start position of data
394 buffer
.putShort(TFTP_DATA
).putShort((short) 0);
395 int start
= buffer
.position();
399 length
= readBlock(bstream
, data
, start
, blocksize
);
401 log
.debug("Block " + block
+ " " + length
);
402 // fill in the block number
404 buffer
.putShort(TFTP_DATA
).putShort((short) block
);
405 response
.setLength(start
+ length
);
406 if (!sendDataBlock(block
)) {
409 buffer
.position(start
);
411 } while (length
>= blocksize
);
412 log
.info("Closing TFTP session");
413 // clean up the connection resources
414 method
.releaseConnection();
420 * Main processing method for the T2hproxy object
423 ByteBuffer buffer
= ByteBuffer
.wrap(req
);
425 String filename
= getAsciz(buffer
);
426 String mode
= getAsciz(buffer
);
427 log
.info(filename
+ " " + mode
);
428 response
= new DatagramPacket(new byte[MTU
], MTU
, iaddr
, port
);
430 responsesocket
= new DatagramSocket();
431 } catch (SocketException e
) {
432 log
.info(e
.toString(), e
);
435 if (!handleOptions(buffer
)) {
443 * Description of the Method
445 *@param s Description of the Parameter
446 *@param r Description of the Parameter
447 *@param prefix Description of the Parameter
448 *@param proxy Description of the Parameter
449 *@param timeout Description of the Parameter
451 public static void handleRequest(DatagramSocket s
, DatagramPacket r
, String prefix
, String proxy
, int timeout
) {
452 log
.info("Connection from " + r
.getAddress().getCanonicalHostName() + ":" + r
.getPort());
453 ByteBuffer buffer
= ByteBuffer
.wrap(r
.getData(), r
.getOffset(), r
.getLength() - r
.getOffset());
454 if (buffer
.getShort() != TFTP_RRQ
) {
455 DatagramPacket error
= new DatagramPacket(new byte[MTU
], MTU
);
456 ByteBuffer rbuf
= ByteBuffer
.wrap(error
.getData());
457 rbuf
.putShort(TFTP_ERROR
).putShort(ERR_ILLOP
).put("Illegal operation".getBytes());
458 error
.setLength(rbuf
.position());
461 } catch (Exception e
) {
462 log
.info(e
.toString(), e
);
467 new Thread(new T2hproxy(r
.getAddress(), r
.getPort(), r
.getData(), prefix
, proxy
, timeout
)).start();
472 * The main program for the T2hproxy class
474 *@param argv The command line arguments
475 *@exception IOException Description of the Exception
477 public static void main(String
[] argv
) throws IOException
{
478 log
.info(T2hproxy
.NAME
+ "." + T2hproxy
.VERSION
);
479 int port
= Integer
.getInteger(T2hproxy
.NAME
+ ".port", 69).intValue();
480 String prefix
= System
.getProperty(T2hproxy
.NAME
+ ".prefix", "http://localhost/");
481 String proxy
= System
.getProperty(T2hproxy
.NAME
+ ".proxy");
482 int timeout
= Integer
.getInteger(T2hproxy
.NAME
+ ".timeout", 5000).intValue();
483 String propfile
= System
.getProperty(T2hproxy
.NAME
+ ".properties");
484 if (propfile
!= null) {
485 FileInputStream pf
= new FileInputStream(propfile
);
486 Properties p
= new Properties(System
.getProperties());
488 // set the system properties
489 System
.setProperties(p
);
491 DatagramSocket requestsocket
;
493 requestsocket
= new DatagramSocket(port
);
494 } catch (SocketException e
) {
495 log
.info(e
.toString(), e
);
498 DatagramPacket request
= new DatagramPacket(new byte[MTU
], MTU
);
501 requestsocket
.receive(request
);
502 handleRequest(requestsocket
, request
, prefix
, proxy
, timeout
);
503 } catch (Exception e
) {
504 log
.info(e
.toString(), e
);