Added TFTP test code (currently just dumps file to console).
[people/xl0/gpxe.git] / src / net / udp / tftp.c
1 /*
2  * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #include <stdint.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <strings.h>
23 #include <byteswap.h>
24 #include <errno.h>
25 #include <assert.h>
26 #include <vsprintf.h>
27 #include <gpxe/async.h>
28 #include <gpxe/tftp.h>
29
30 /** @file
31  *
32  * TFTP protocol
33  *
34  */
35
36 /** A TFTP option */
37 struct tftp_option {
38         /** Option name */
39         const char *name;
40         /** Option processor
41          *
42          * @v tftp      TFTP connection
43          * @v value     Option value
44          * @ret rc      Return status code
45          */
46         int ( * process ) ( struct tftp_session *tftp, const char *value );
47 };
48
49 /**
50  * Process TFTP "blksize" option
51  *
52  * @v tftp              TFTP connection
53  * @v value             Option value
54  * @ret rc              Return status code
55  */
56 static int tftp_process_blksize ( struct tftp_session *tftp,
57                                   const char *value ) {
58         char *end;
59
60         tftp->blksize = strtoul ( value, &end, 10 );
61         if ( *end ) {
62                 DBG ( "TFTP %p got invalid blksize \"%s\"\n", tftp, value );
63                 return -EINVAL;
64         }
65         DBG ( "TFTP %p blksize=%d\n", tftp, tftp->blksize );
66
67         return 0;
68 }
69
70 /**
71  * Process TFTP "tsize" option
72  *
73  * @v tftp              TFTP connection
74  * @v value             Option value
75  * @ret rc              Return status code
76  */
77 static int tftp_process_tsize ( struct tftp_session *tftp,
78                                 const char *value ) {
79         char *end;
80
81         tftp->tsize = strtoul ( value, &end, 10 );
82         if ( *end ) {
83                 DBG ( "TFTP %p got invalid tsize \"%s\"\n", tftp, value );
84                 return -EINVAL;
85         }
86         DBG ( "TFTP %p tsize=%ld\n", tftp, tftp->tsize );
87
88         return 0;
89 }
90
91 /** Recognised TFTP options */
92 static struct tftp_option tftp_options[] = {
93         { "blksize", tftp_process_blksize },
94         { "tsize", tftp_process_tsize },
95         { NULL, NULL }
96 };
97
98 /**
99  * Process TFTP option
100  *
101  * @v tftp              TFTP connection
102  * @v name              Option name
103  * @v value             Option value
104  * @ret rc              Return status code
105  */
106 static int tftp_process_option ( struct tftp_session *tftp,
107                                  const char *name, const char *value ) {
108         struct tftp_option *option;
109
110         for ( option = tftp_options ; option->name ; option++ ) {
111                 if ( strcasecmp ( name, option->name ) == 0 )
112                         return option->process ( tftp, value );
113         }
114
115         DBG ( "TFTP %p received unknown option \"%s\" = \"%s\"\n",
116               tftp, name, value );
117
118         return -EINVAL;
119 }
120
121 /** Translation between TFTP errors and internal error numbers */
122 static const uint8_t tftp_errors[] = {
123         [TFTP_ERR_FILE_NOT_FOUND]       = PXENV_STATUS_TFTP_FILE_NOT_FOUND,
124         [TFTP_ERR_ACCESS_DENIED]        = PXENV_STATUS_TFTP_ACCESS_VIOLATION,
125         [TFTP_ERR_ILLEGAL_OP]           = PXENV_STATUS_TFTP_UNKNOWN_OPCODE,
126 };
127
128 /**
129  * Mark TFTP session as complete
130  *
131  * @v tftp              TFTP connection
132  * @v rc                Return status code
133  */
134 static void tftp_done ( struct tftp_session *tftp, int rc ) {
135
136         /* Stop the retry timer */
137         stop_timer ( &tftp->timer );
138
139         /* Close UDP connection */
140         udp_close ( &tftp->udp );
141
142         /* Mark async operation as complete */
143         async_done ( &tftp->aop, rc );
144 }
145
146 /**
147  * Send next packet in TFTP session
148  *
149  * @v tftp              TFTP connection
150  */
151 static void tftp_send_packet ( struct tftp_session *tftp ) {
152         start_timer ( &tftp->timer );
153         udp_senddata ( &tftp->udp );
154 }
155
156 /**
157  * Handle TFTP retransmission timer expiry
158  *
159  * @v timer             Retry timer
160  * @v fail              Failure indicator
161  */
162 static void tftp_timer_expired ( struct retry_timer *timer, int fail ) {
163         struct tftp_session *tftp =
164                 container_of ( timer, struct tftp_session, timer );
165
166         if ( fail ) {
167                 tftp_done ( tftp, -ETIMEDOUT );
168         } else {
169                 tftp_send_packet ( tftp );
170         }
171 }
172
173 /**
174  * Mark TFTP block as received
175  *
176  * @v tftp              TFTP connection
177  * @v block             Block number
178  */
179 static void tftp_received ( struct tftp_session *tftp, unsigned int block ) {
180
181         /* Stop the retry timer */
182         stop_timer ( &tftp->timer );
183
184         /* Update state to indicate which block we're now waiting for */
185         tftp->state = block;
186
187         /* Send next packet */
188         tftp_send_packet ( tftp );
189 }
190
191 /**
192  * Transmit RRQ
193  *
194  * @v tftp              TFTP connection
195  * @v buf               Temporary data buffer
196  * @v len               Length of temporary data buffer
197  * @ret rc              Return status code
198  */
199 static int tftp_send_rrq ( struct tftp_session *tftp, void *buf, size_t len ) {
200         struct tftp_rrq *rrq = buf;
201         void *data;
202         void *end;
203
204         DBG ( "TFTP %p requesting \"%s\"\n", tftp, tftp->filename );
205
206         data = rrq->data;
207         end = ( buf + len );
208         if ( data > end )
209                 goto overflow;
210         data += ( snprintf ( data, ( end - data ),
211                              "%s%coctet%cblksize%c%d%ctsize%c0",
212                              tftp->filename, 0, 0, 0,
213                              tftp->request_blksize, 0, 0 ) + 1 );
214         if ( data > end )
215                 goto overflow;
216         rrq->opcode = htons ( TFTP_RRQ );
217
218         return udp_send ( &tftp->udp, buf, ( data - buf ) );
219
220  overflow:
221         DBG ( "TFTP %p RRQ out of space\n", tftp );
222         return -ENOBUFS;
223 }
224
225 /**
226  * Receive OACK
227  *
228  * @v tftp              TFTP connection
229  * @v buf               Temporary data buffer
230  * @v len               Length of temporary data buffer
231  * @ret rc              Return status code
232  */
233 static int tftp_rx_oack ( struct tftp_session *tftp, void *buf, size_t len ) {
234         struct tftp_oack *oack = buf;
235         char *end = buf + len;
236         char *name;
237         char *value;
238         int rc;
239
240         /* Sanity check */
241         if ( len < sizeof ( *oack ) ) {
242                 DBG ( "TFTP %p received underlength OACK packet length %d\n",
243                       tftp, len );
244                 return -EINVAL;
245         }
246         if ( end[-1] != '\0' ) {
247                 DBG ( "TFTP %p received OACK missing final NUL\n", tftp );
248                 return -EINVAL;
249         }
250
251         /* Process each option in turn */
252         name = oack->data;
253         while ( name < end ) {
254                 value = ( name + strlen ( name ) + 1 );
255                 if ( value == end ) {
256                         DBG ( "TFTP %p received OACK missing value for option "
257                               "\"%s\"\n", tftp, name );
258                         return -EINVAL;
259                 }
260                 if ( ( rc = tftp_process_option ( tftp, name, value ) ) != 0 )
261                         return rc;
262                 name = ( value + strlen ( value ) + 1 );
263         }
264
265         /* Mark as received block 0 (the OACK) */
266         tftp_received ( tftp, 0 );
267
268         return 0;
269 }
270
271 /**
272  * Receive DATA
273  *
274  * @v tftp              TFTP connection
275  * @v buf               Temporary data buffer
276  * @v len               Length of temporary data buffer
277  * @ret rc              Return status code
278  */
279 static int tftp_rx_data ( struct tftp_session *tftp, void *buf, size_t len ) {
280         struct tftp_data *data = buf;
281         unsigned int block;
282         size_t data_len;
283
284         /* Sanity check */
285         if ( len < sizeof ( *data ) ) {
286                 DBG ( "TFTP %p received underlength DATA packet length %d\n",
287                       tftp, len );
288                 return -EINVAL;
289         }
290
291         /* Pass to callback */
292         block = ntohs ( data->block );
293         data_len = ( len - offsetof ( typeof ( *data ), data ) );
294         tftp->callback ( tftp, block, data->data, data_len );
295
296         /* Mark block as received */
297         tftp_received ( tftp, block );
298
299         /* Finish when final block received */
300         if ( data_len < tftp->blksize )
301                 tftp_done ( tftp, 0 );
302
303         return 0;
304 }
305
306 /**
307  * Transmit ACK
308  *
309  * @v tftp              TFTP connection
310  * @v buf               Temporary data buffer
311  * @v len               Length of temporary data buffer
312  * @ret rc              Return status code
313  */
314 static int tftp_send_ack ( struct tftp_session *tftp ) {
315         struct tftp_ack ack;
316
317         ack.opcode = htons ( TFTP_ACK );
318         ack.block = htons ( tftp->state );
319         return udp_send ( &tftp->udp, &ack, sizeof ( ack ) );
320 }
321
322 /**
323  * Receive ERROR
324  *
325  * @v tftp              TFTP connection
326  * @v buf               Temporary data buffer
327  * @v len               Length of temporary data buffer
328  * @ret rc              Return status code
329  */
330 static int tftp_rx_error ( struct tftp_session *tftp, void *buf, size_t len ) {
331         struct tftp_error *error = buf;
332         unsigned int err;
333         int rc = 0;
334
335         /* Sanity check */
336         if ( len < sizeof ( *error ) ) {
337                 DBG ( "TFTP %p received underlength ERROR packet length %d\n",
338                       tftp, len );
339                 return -EINVAL;
340         }
341
342         DBG ( "TFTP %p received ERROR packet with code %d, message \"%s\"\n",
343               tftp, ntohs ( error->errcode ), error->errmsg );
344         
345         /* Determine final operation result */
346         err = ntohs ( error->errcode );
347         if ( err < ( sizeof ( tftp_errors ) / sizeof ( tftp_errors[0] ) ) )
348                 rc = -tftp_errors[err];
349         if ( ! rc )
350                 rc = -PXENV_STATUS_TFTP_CANNOT_OPEN_CONNECTION;
351
352         /* Close TFTP session */
353         tftp_done ( tftp, rc );
354
355         return 0;
356 }
357
358 /**
359  * Transmit data
360  *
361  * @v conn              UDP connection
362  * @v buf               Temporary data buffer
363  * @v len               Length of temporary data buffer
364  * @ret rc              Return status code
365  */
366 static int tftp_senddata ( struct udp_connection *conn,
367                            void *buf, size_t len ) {
368         struct tftp_session *tftp = 
369                 container_of ( conn, struct tftp_session, udp );
370
371         if ( tftp->state < 0 ) {
372                 return tftp_send_rrq ( tftp, buf, len );
373         } else {
374                 return tftp_send_ack ( tftp );
375         }
376 }
377
378 /**
379  * Receive new data
380  *
381  * @v udp               UDP connection
382  * @v data              Received data
383  * @v len               Length of received data
384  * @v st_src            Partially-filled source address
385  * @v st_dest           Partially-filled destination address
386  */
387 static int tftp_newdata ( struct udp_connection *conn, void *data, size_t len,
388                           struct sockaddr_tcpip *st_src __unused,
389                           struct sockaddr_tcpip *st_dest __unused ) {
390         struct tftp_session *tftp = 
391                 container_of ( conn, struct tftp_session, udp );
392         struct tftp_common *common = data;
393         
394         if ( len < sizeof ( *common ) ) {
395                 DBG ( "TFTP %p received underlength packet length %d\n",
396                       tftp, len );
397                 return -EINVAL;
398         }
399
400         /* Filter by TID.  Set TID on first response received */
401         if ( tftp->tid ) {
402                 if ( tftp->tid != st_src->st_port ) {
403                         DBG ( "TFTP %p received packet from wrong port "
404                               "(got %d, wanted %d)\n", tftp,
405                               ntohs ( st_src->st_port ), ntohs ( tftp->tid ) );
406                         return -EINVAL;
407                 }
408         } else {
409                 tftp->tid = st_src->st_port;
410                 DBG ( "TFTP %p using remote port %d\n", tftp,
411                       ntohs ( tftp->tid ) );
412                 udp_connect_port ( &tftp->udp, tftp->tid );
413         }
414
415         /* Filter by source address */
416         if ( memcmp ( st_src, udp_peer ( &tftp->udp ),
417                       sizeof ( *st_src ) ) != 0 ) {
418                 DBG ( "TFTP %p received packet from foreign source\n", tftp );
419                 return -EINVAL;
420         }
421
422         switch ( common->opcode ) {
423         case htons ( TFTP_OACK ):
424                 return tftp_rx_oack ( tftp, data, len );
425         case htons ( TFTP_DATA ):
426                 return tftp_rx_data ( tftp, data, len );
427         case htons ( TFTP_ERROR ):
428                 return tftp_rx_error ( tftp, data, len );
429         default:
430                 DBG ( "TFTP %p received strange packet type %d\n", tftp,
431                       ntohs ( common->opcode ) );
432                 return -EINVAL;
433         };
434 }
435
436 /** TFTP UDP operations */
437 static struct udp_operations tftp_udp_operations = {
438         .senddata = tftp_senddata,
439         .newdata = tftp_newdata,
440 };
441
442 /**
443  * Initiate TFTP download
444  *
445  * @v tftp              TFTP session
446  * @ret aop             Asynchronous operation
447  */
448 struct async_operation * tftp_get ( struct tftp_session *tftp ) {
449         int rc;
450
451         assert ( tftp->filename != NULL );
452         assert ( tftp->callback != NULL );
453         assert ( tftp->udp.peer.st_family != 0 );
454
455         /* Initialise TFTP session */
456         tftp->udp.udp_op = &tftp_udp_operations;
457         tftp->timer.expired = tftp_timer_expired;
458         tftp->state = -1;
459         tftp->blksize = TFTP_DEFAULT_BLKSIZE;
460         if ( ! tftp->request_blksize )
461                 tftp->request_blksize = TFTP_MAX_BLKSIZE;
462
463         /* Open UDP connection */
464         if ( ( rc = udp_open ( &tftp->udp, 0 ) ) != 0 ) {
465                 async_done ( &tftp->aop, rc );
466                 goto out;
467         }
468
469         /* Transmit initial RRQ */
470         tftp_send_packet ( tftp );
471
472  out:
473         return &tftp->aop;
474 }