summaryrefslogtreecommitdiffstats
path: root/src/proto/tftpcore.c
blob: c7673bdbff303fef7745b46dfe881d227e535512 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
#include "tftp.h"
#include "old_tcp.h" /* for struct tcphdr */
#include "errno.h"
#include "etherboot.h"
#include "tftpcore.h"

/** @file */

/**
 * await_reply() filter for TFTP packets
 *
 * @v ptr				Pointer to a struct tftp_state
 * @v tftp_state::server::sin_addr	TFTP server IP address
 * @v tftp_state::lport			Client UDP port
 * @v tftp_state::multicast::sin_addr	Multicast IP address, or 0.0.0.0
 * @v tftp_state::multicast::sin_port	Multicast UDP port, or 0
 * @v ip				IP header
 * @v udp				UDP header
 * @ret True				This is our TFTP packet
 * @ret False				This is not one of our TFTP packets
 *
 * Wait for a TFTP packet that is part of the current connection
 * (i.e. comes from the TFTP server, has the correct destination port,
 * and is addressed either to our IP address and UDP port, or to our
 * multicast listening address and UDP port).
 *
 * Use await_tftp() in code such as
 *
 * @code
 *
 * if ( await_reply ( await_tftp, 0, &tftp_state, timeout ) ) {
 *	...
 * }
 *
 * @endcode
 */
static int await_tftp ( int ival __unused, void *ptr,
			unsigned short ptype __unused, struct iphdr *ip,
			struct udphdr *udp, struct tcphdr *tcp __unused ) {
	struct tftp_state *state = ptr;

	/* Must have valid UDP (and, therefore, also IP) headers */
	if ( ! udp ) {
		DBG2 ( "TFTPCORE: not UDP\n" );
		return 0;
	}
	/* Packet must come from the TFTP server */
	if ( ip->src.s_addr != state->server.sin_addr.s_addr ) {
		DBG2 ( "TFTPCORE: from %@, not from TFTP server %@\n",
		       ip->src.s_addr, state->server.sin_addr.s_addr );
		return 0;
	}
	/* Packet may be addressed to our IP address and unicast UDP
	 * port
	 */
	if ( ( ip->dest.s_addr == arptable[ARP_CLIENT].ipaddr.s_addr ) &&
	     ( ntohs ( udp->dest ) == state->lport ) ) {
		return 1;
	}
	/* Packet may be addressed to our multicast IP address and UDP
	 * port, if we have one
	 */
	if ( ( state->multicast.sin_addr.s_addr ) && 
	     ( ip->dest.s_addr == state->multicast.sin_addr.s_addr ) &&
	     ( ntohs ( udp->dest ) == state->multicast.sin_port ) ) {
		return 1;
	}
	DBG2 ( "TFTPCORE: to %@:%d, not to %@:%d (or %@:%d)\n",
	       ip->dest.s_addr, ntohs ( udp->dest ),
	       arptable[ARP_CLIENT].ipaddr.s_addr, state->lport,
	       state->multicast.sin_addr.s_addr, state->multicast.sin_port );
	return 0;
}

/**
 * Retrieve a TFTP packet
 *
 * @v state				TFTP transfer state
 * @v tftp_state::server::sin_addr	TFTP server IP address
 * @v tftp_state::lport			Client UDP port
 * @v tftp_state::multicast::sin_addr	Multicast IP address, or 0.0.0.0
 * @v tftp_state::multicast::sin_port	Multicast UDP port, or 0
 * @v timeout				Time to wait for a response
 * @ret True				Received a non-error response
 * @ret False				Received error response / no response
 * @ret *reply				The server's response, if any
 * @err #PXENV_STATUS_TFTP_READ_TIMEOUT	No response received in time
 * @err other				As set by tftp_set_errno()
 *
 * Retrieve the next packet sent by the TFTP server, if any is sent
 * within the specified timeout period.  The packet is returned via
 * #reply.  If no packet is received within the timeout period, a NULL
 * value will be stored in #reply.
 *
 * If the response from the server is a TFTP ERROR packet, tftp_get()
 * will return False and #errno will be set accordingly.
 *
 * You can differentiate between "received no response" and "received
 * an error response" by checking #reply; if #reply is NULL then no
 * response was received.
 */
int tftp_get ( struct tftp_state *state, long timeout,
	       union tftp_any **reply ) {

	*reply = NULL;

	if ( ! await_reply ( await_tftp, 0, state, timeout ) ) {
		errno = PXENV_STATUS_TFTP_READ_TIMEOUT;
		return 0;
	}

	*reply = ( union tftp_any * ) &nic.packet[ETH_HLEN];
	DBG ( "TFTPCORE: got reply (type %d)\n",
	      ntohs ( (*reply)->common.opcode ) );
	if ( ntohs ( (*reply)->common.opcode ) == TFTP_ERROR ){
		tftp_set_errno ( &(*reply)->error );
		return 0;
	}
	return 1;
}

/**
 * Issue a TFTP open request (RRQ)
 *
 * @v state				TFTP transfer state
 * @v tftp_state::server::sin_addr	TFTP server IP address
 * @v tftp_state::server::sin_port	TFTP server UDP port, or 0
 * @v tftp_state::lport			Client UDP port, or 0
 * @v tftp_state::multicast::sin_addr	Multicast IP address, or 0.0.0.0
 * @v tftp_state::multicast::sin_port	Multicast UDP port, or 0
 * @v tftp_state::blksize		Requested blksize, or 0
 * @v filename				File name
 * @v multicast				Enable/disable rfc2090 multicast TFTP
 * @ret True				Received a non-error response
 * @ret False				Received error response / no response
 * @ret tftp_state::server::sin_port	TFTP server UDP port
 * @ret tftp_state::lport		Client UDP port
 * @ret tftp_state::blksize		Always #TFTP_DEFAULT_BLKSIZE
 * @ret *reply				The server's response, if any
 * @err #PXENV_STATUS_TFTP_OPEN_TIMEOUT	TFTP open timed out
 * @err other				As returned by udp_transmit()
 * @err other				As set by tftp_set_errno()
 *
 * Send a TFTP/TFTM/MTFTP RRQ (read request) to a TFTP server, and
 * return the server's reply (which may be an OACK, DATA or ERROR
 * packet).  The server's reply will not be acknowledged, or processed
 * in any way.
 *
 * If tftp_state::server::sin_port is 0, the standard TFTP server port
 * (#TFTP_PORT) will be used.
 *
 * If tftp_state::lport is 0, the standard mechanism of
 * using a new, unique port number for each TFTP request will be used.
 * 
 * If tftp_state::multicast::sin_addr is not 0.0.0.0, it (and
 * tftp_state::multicast::sin_port) will be used as a multicast
 * listening address for replies from the TFTP server.
 *
 * For the various different types of TFTP server, you should treat
 * tftp_state::lport and tftp_state::multicast as follows:
 *
 *   - Standard TFTP server: set tftp_state::lport to 0,
 *     tftp_state::multicast::sin_addr to 0.0.0.0 and
 *     tftp_state::multicast::sin_port to 0.  tftp_open() will set
 *     tftp_state::lport to the assigned local UDP port.
 *
 *   - TFTM server: set tftp_state::lport to 0,
 *     tftp_state::multicast::sin_addr to 0.0.0.0 and
 *     tftp_state::multicast::sin_port to 0.  tftp_open() will set
 *     tftp_state::lport to the assigned local UDP port.  (Your call
 *     to tftp_process_opts() will then overwrite both
 *     tftp_state::multicast::sin_addr and
 *     tftp_state::multicast::sin_port with the values specified in
 *     the OACK packet.)
 *
 *   - MTFTP server: set tftp_state::multicast::sin_addr to the
 *     multicast address and both tftp_state::lport and
 *     tftp_state::multicast::sin_port to the multicast port (both of
 *     which must be previously known, e.g. provided by a DHCP
 *     server).  tftp_open() will not alter these values.
 *
 * If tftp_state::blksize is 0, the maximum blocksize
 * (#TFTP_MAX_BLKSIZE) will be requested.
 *
 * On exit, tftp_state::blksize will always contain
 * #TFTP_DEFAULT_BLKSIZE, since this is the blocksize value that must
 * be assumed until the OACK packet is processed (by a subsequent call
 * to tftp_process_opts()).
 *
 * tftp_state::server::sin_port will be set to the UDP port from which
 * the server's response originated.  This may or may not be the port
 * to which the open request was sent.
 *
 * The options "blksize" and "tsize" will always be appended to a TFTP
 * open request.  The option "multicast" will be appended to the
 * request if #multicast is True.  Servers that do not understand any
 * of these options should simply ignore them.
 *
 * tftp_open() will not automatically join or leave multicast groups;
 * the caller is responsible for calling join_group() and
 * leave_group() at appropriate times.
 *
 * If the response from the server is a TFTP ERROR packet, tftp_open()
 * will return False and #errno will be set accordingly.
 */
int tftp_open ( struct tftp_state *state, const char *filename,
		union tftp_any **reply, int multicast ) {
	static unsigned short lport = 2000; /* local port */
	int fixed_lport;
	struct tftp_rrq rrq;
	char *p;
	unsigned int rrqlen;
	int retry;

	/* Flush receive queue */
	rx_qdrain();

	/* Default to blksize of TFTP_MAX_BLKSIZE if none specified */
	if ( ! state->blksize )
		state->blksize = TFTP_MAX_BLKSIZE;

	/* Use default TFTP server port if none specified */
	if ( ! state->server.sin_port )
		state->server.sin_port = TFTP_PORT;

	/* Determine whether or not to use lport */
	fixed_lport = state->lport;

	/* Set up RRQ */
	rrq.opcode = htons ( TFTP_RRQ );
	p = rrq.data;
	p += sprintf ( p, "%s%coctet%cblksize%c%d%ctsize%c0",
		       filename, 0, 0, 0, state->blksize, 0, 0 ) + 1;
	if ( multicast ) {
		p += sprintf ( p, "multicast%c", 0 ) + 1;
	}
	rrqlen = ( p - ( char * ) &rrq );

	/* Set negotiated blksize to default value */
	state->blksize = TFTP_DEFAULT_BLKSIZE;
	
	/* Nullify received packet pointer */
	*reply = NULL;

	/* Transmit RRQ until we get a response */
	for ( retry = 0 ; retry < MAX_TFTP_RETRIES ; retry++ ) {
		long timeout = rfc2131_sleep_interval ( TIMEOUT, retry );

		/* Set client UDP port, if not already fixed */
		if ( ! fixed_lport )
			state->lport = ++lport;
		
		/* Send the RRQ */
		DBG ( "TFTPCORE: requesting %@:%d/%s from port %d\n",
		      state->server.sin_addr.s_addr, state->server.sin_port,
		      rrq.data, state->lport );
		if ( ! udp_transmit ( state->server.sin_addr.s_addr,
				      state->lport, state->server.sin_port,
				      rrqlen, &rrq ) )
			return 0;
		
		/* Wait for response */
		if ( tftp_get ( state, timeout, reply ) ) {
			/* We got a non-error response */
			state->server.sin_port
				= ntohs ( (*reply)->common.udp.src );
			DBG ( "TFTP server is at %@:%d\n",
			      state->server.sin_addr.s_addr,
			      state->server.sin_port );
			return 1;
		}
		if ( *reply ) {
			/* We got an error response; abort */
			return 0;
		}
	}

	DBG ( "TFTPCORE: open request timed out\n" );
	errno = PXENV_STATUS_TFTP_OPEN_TIMEOUT;
	return 0;
}

/**
 * Process a TFTP OACK packet
 *
 * @v state				TFTP transfer state
 * @v oack				The TFTP OACK packet
 * @ret True				Options were processed successfully
 * @ret False				Options were not processed successfully
 * @ret tftp_state::blksize		Negotiated blksize
 * @ret tftp_state::tsize		File size (if known), or 0
 * @ret tftp_state::multicast::sin_addr	Multicast IP address, or 0.0.0.0
 * @ret tftp_state::multicast::sin_port	Multicast UDP port, or 0
 * @ret tftp_state::master		Client is master
 * @err EINVAL				An invalid option value was encountered
 *
 * Process the options returned by the TFTP server in an rfc2347 OACK
 * packet.  The options "blksize" (rfc2348), "tsize" (rfc2349) and
 * "multicast" (rfc2090) are recognised and processed; any other
 * options are silently ignored.
 *
 * Where an option is not present in the OACK packet, the
 * corresponding field(s) in #state will be left unaltered.
 *
 * Calling tftp_process_opts() does not send an acknowledgement for
 * the OACK packet; this is the responsibility of the caller.
 *
 * @note If the "blksize" option is not present, tftp_state::blksize
 * will @b not be implicitly set to #TFTP_DEFAULT_BLKSIZE.  However,
 * since tftp_open() always sets tftp_state::blksize to
 * #TFTP_DEFAULT_BLKSIZE before returning, you probably don't need to
 * worry about this.
 */
int tftp_process_opts ( struct tftp_state *state, struct tftp_oack *oack ) {
	const char *p;
	const char *end;

	DBG ( "TFTPCORE: processing OACK\n" );

	/* End of options */
	end = ( ( char * ) &oack->udp ) + ntohs ( oack->udp.len );

	/* Only possible error */
	errno = EINVAL;

	for ( p = oack->data ; p < end ; ) {
		if ( strcasecmp ( "blksize", p ) == 0 ) {
			p += 8;
			state->blksize = strtoul ( p, &p, 10 );
			if ( *p ) {
				DBG ( "TFTPCORE: garbage \"%s\" "
				      "after blksize\n", p );
				return 0;
			}
			p++;
			DBG ( "TFTPCORE: got blksize %d\n", state->blksize );
		} else if ( strcasecmp ( "tsize", p ) == 0 ) {
			p += 6;
			state->tsize = strtoul ( p, &p, 10 );
			if ( *p ) {
				DBG ( "TFTPCORE: garbage \"%s\" "
				      "after tsize\n", p );
				return 0;
			}
			p++;
			DBG ( "TFTPCORE: got tsize %d\n", state->tsize );
		} else if ( strcasecmp ( "multicast", p ) == 0 ) {
			p += 10;
			char *e = strchr ( p, ',' );
			if ( ( ! e ) || ( e >= end ) ) {
				DBG ( "TFTPCORE: malformed multicast field "
				      "\"%s\"\n", p );
				return 0;
			}
			/* IP address may be missing, in which case we
			 * should leave state->multicast.sin_addr
			 * unaltered.
			 */
			if ( e != p ) {
				int rc;
				*e = '\0';
				rc = inet_aton ( p,
						 &state->multicast.sin_addr );
				*e = ',';
				if ( ! rc ) {
					DBG ( "TFTPCORE: malformed multicast "
					      "IP address \"%s\"\n", p );
					return 0;
				}
			}
			p = e + 1;
			/* UDP port may also be missing */
			if ( *p != ',' ) {
				state->multicast.sin_port
					= strtoul ( p, &p, 10 );
				if ( *p != ',' ) {
					DBG ( "TFTPCORE: garbage \"%s\" "
					      "after multicast port\n", p );
					return 0;
				}
			}
			p++;
			/* "Master Client" must always be present */
			state->master = strtoul ( p, &p, 10 );
			if ( *p ) {
				DBG ( "TFTPCORE: garbage \"%s\" "
				      "after multicast mc\n", p );
				return 0;
			}
			p++;
			DBG ( "TFTPCORE: got multicast %@:%d (%s)\n",
			      state->multicast.sin_addr.s_addr,
			      state->multicast.sin_port,
			      ( state->master ? "master" : "not master" ) );
		} else {
			DBG ( "TFTPCORE: unknown option \"%s\"\n", p );
			p += strlen ( p ) + 1; /* skip option name */
			p += strlen ( p ) + 1; /* skip option value */
		}
	}

	if ( p > end ) {
		DBG ( "TFTPCORE: overran options in OACK\n" );
		return 0;
	}

	return 1;
}

/**
 * Acknowledge a TFTP packet
 *
 * @v state				TFTP transfer state
 * @v tftp_state::server::sin_addr	TFTP server IP address
 * @v tftp_state::server::sin_port	TFTP server UDP port
 * @v tftp_state::lport			Client UDP port
 * @v tftp_state::block			Most recently received block number
 * @ret True				Acknowledgement packet was sent
 * @ret False				Acknowledgement packet was not sent
 * @err other				As returned by udp_transmit()
 * 
 * Send a TFTP ACK packet for the most recently received block.
 *
 * This sends only a single ACK packet; it does not wait for the
 * server's response.
 */
int tftp_ack_nowait ( struct tftp_state *state ) {
	struct tftp_ack ack;

	DBG ( "TFTPCORE: acknowledging data block %d\n", state->block );
	ack.opcode = htons ( TFTP_ACK );
	ack.block = htons ( state->block );
	return udp_transmit ( state->server.sin_addr.s_addr,
			      state->lport, state->server.sin_port,
			      sizeof ( ack ), &ack );
}

/**
 * Acknowledge a TFTP packet and wait for a response
 *
 * @v state				TFTP transfer state
 * @v tftp_state::server::sin_addr	TFTP server IP address
 * @v tftp_state::server::sin_port	TFTP server UDP port
 * @v tftp_state::lport			Client UDP port
 * @v tftp_state::block			Most recently received block number
 * @ret True				Received a non-error response
 * @ret False				Received error response / no response
 * @ret *reply				The server's response, if any
 * @err #PXENV_STATUS_TFTP_READ_TIMEOUT	Timed out waiting for a response
 * @err other				As returned by tftp_ack_nowait()
 * @err other				As set by tftp_set_errno()
 *
 * Send a TFTP ACK packet for the most recently received data block,
 * and keep transmitting this ACK until we get a response from the
 * server (e.g. a new data block).
 *
 * If the response is a TFTP DATA packet, no processing is done.
 * Specifically, the block number is not checked to ensure that this
 * is indeed the next data block in the sequence, nor is
 * tftp_state::block updated with the new block number.
 *
 * If the response from the server is a TFTP ERROR packet, tftp_open()
 * will return False and #errno will be set accordingly.
 */
int tftp_ack ( struct tftp_state *state, union tftp_any **reply ) {
	int retry;

	*reply = NULL;
	for ( retry = 0 ; retry < MAX_TFTP_RETRIES ; retry++ ) {
		long timeout = rfc2131_sleep_interval ( TFTP_REXMT, retry );
		/* ACK the last data block */
		if ( ! tftp_ack_nowait ( state ) ) {
			DBG ( "TFTP: could not send ACK: %m\n" );
			return 0;
		}
		if ( tftp_get ( state, timeout, reply ) ) {
			/* We got a non-error response */
			return 1;
		}
		if ( *reply ) {
			/* We got an error response */
			return 0;
		}
	}
	DBG ( "TFTP: timed out during read\n" );
	errno = PXENV_STATUS_TFTP_READ_TIMEOUT;
	return 0;
}

/**
 * Send a TFTP error
 *
 * @v state				TFTP transfer state
 * @v tftp_state::server::sin_addr	TFTP server IP address
 * @v tftp_state::server::sin_port	TFTP server UDP port
 * @v tftp_state::lport			Client UDP port
 * @v errcode				TFTP error code
 * @v errmsg				Descriptive error string, or NULL
 * @ret True				Error packet was sent
 * @ret False				Error packet was not sent
 *
 * Send a TFTP ERROR packet back to the server to terminate the
 * transfer.
 *
 * If #errmsg is NULL, the current error message string as returned by
 * strerror(errno) will be used as the error text.
 */
int tftp_error ( struct tftp_state *state, int errcode, const char *errmsg ) {
	struct tftp_error error;

	DBG ( "TFTPCORE: aborting with error %d (%s)\n", errcode, errmsg );
	error.opcode = htons ( TFTP_ERROR );
	error.errcode = htons ( errcode );
	strncpy ( error.errmsg, errmsg ? errmsg : strerror ( errno ),
		  sizeof ( error.errmsg ) );
	return udp_transmit ( state->server.sin_addr.s_addr,
			      state->lport, state->server.sin_port,
			      sizeof ( error ), &error );
}

/**
 * Interpret a TFTP error
 *
 * @v error				Pointer to a struct tftp_error
 *
 * Sets #errno based on the error code in a TFTP ERROR packet.
 */
void tftp_set_errno ( struct tftp_error *error ) {
	static int errmap[] = {
		[TFTP_ERR_FILE_NOT_FOUND] = PXENV_STATUS_TFTP_FILE_NOT_FOUND,
		[TFTP_ERR_ACCESS_DENIED] = PXENV_STATUS_TFTP_ACCESS_VIOLATION,
		[TFTP_ERR_ILLEGAL_OP] = PXENV_STATUS_TFTP_UNKNOWN_OPCODE,
	};
	unsigned int errcode = ntohs ( error->errcode );
	
	errno = 0;
	if ( errcode < ( sizeof(errmap) / sizeof(errmap[0]) ) )
		errno = errmap[errcode];
	if ( ! errno )
		errno = PXENV_STATUS_TFTP_ERROR_OPCODE;
}