pxe, tftp: Handle block number wraparound
[syslinux/sherbszt.git] / core / fs / pxe / tftp.c
blob429e8ffe836cd1d6ca2b21e62d11f22df29bf823
1 #include <minmax.h>
2 #include <lwip/api.h>
3 #include "pxe.h"
4 #include "url.h"
5 #include "tftp.h"
7 static const uint8_t TimeoutTable[] = {
8 2, 2, 3, 3, 4, 5, 6, 7, 9, 10, 12, 15, 18, 21, 26, 31, 37, 44,
9 53, 64, 77, 92, 110, 132, 159, 191, 229, 255, 255, 255, 255, 0
11 struct tftp_options {
12 const char *str_ptr; /* string pointer */
13 size_t offset; /* offset into socket structre */
15 struct tftp_packet {
16 uint16_t opcode;
17 uint16_t serial;
18 char data[];
21 #define IFIELD(x) offsetof(struct inode, x)
22 #define PFIELD(x) (offsetof(struct inode, pvt) + \
23 offsetof(struct pxe_pvt_inode, x))
25 static const struct tftp_options tftp_options[] =
27 { "tsize", IFIELD(size) },
28 { "blksize", PFIELD(tftp_blksize) },
30 static const int tftp_nopts = sizeof tftp_options / sizeof tftp_options[0];
32 static void tftp_error(struct inode *file, uint16_t errnum,
33 const char *errstr);
35 static void tftp_close_file(struct inode *inode)
37 struct pxe_pvt_inode *socket = PVT(inode);
38 if (!socket->tftp_goteof) {
39 tftp_error(inode, 0, "No error, file close");
41 if (socket->conn) {
42 netconn_delete(socket->conn);
43 socket->conn = NULL;
48 * Send a UDP packet.
50 static void udp_send(struct netconn *conn, const void *data, size_t len)
52 struct netbuf *nbuf;
53 void *pbuf;
54 int err;
56 nbuf = netbuf_new();
57 if (!nbuf) {
58 printf("netbuf allocation error\n");
59 return;
62 pbuf = netbuf_alloc(nbuf, len);
63 if (!pbuf) {
64 printf("pbuf allocation error\n");
65 goto out;
68 memcpy(pbuf, data, len);
70 err = netconn_send(conn, nbuf);
71 if (err) {
72 printf("netconn_send error %d\n", err);
73 goto out;
76 out:
77 netbuf_delete(nbuf);
80 /**
81 * Send an ERROR packet. This is used to terminate a connection.
83 * @inode: Inode structure
84 * @errnum: Error number (network byte order)
85 * @errstr: Error string (included in packet)
87 static void tftp_error(struct inode *inode, uint16_t errnum,
88 const char *errstr)
90 static struct {
91 uint16_t err_op;
92 uint16_t err_num;
93 char err_msg[64];
94 } __packed err_buf;
95 int len = min(strlen(errstr), sizeof(err_buf.err_msg)-1);
96 struct pxe_pvt_inode *socket = PVT(inode);
98 err_buf.err_op = TFTP_ERROR;
99 err_buf.err_num = errnum;
100 memcpy(err_buf.err_msg, errstr, len);
101 err_buf.err_msg[len] = '\0';
103 udp_send(socket->conn, &err_buf, 4 + len + 1);
107 * Send ACK packet. This is a common operation and so is worth canning.
109 * @param: inode, Inode pointer
110 * @param: ack_num, Packet # to ack (host byte order)
113 static void ack_packet(struct inode *inode, uint16_t ack_num)
115 static uint16_t ack_packet_buf[2];
116 struct pxe_pvt_inode *socket = PVT(inode);
118 /* Packet number to ack */
119 ack_packet_buf[0] = TFTP_ACK;
120 ack_packet_buf[1] = htons(ack_num);
122 udp_send(socket->conn, ack_packet_buf, 4);
126 * Get a fresh packet if the buffer is drained, and we haven't hit
127 * EOF yet. The buffer should be filled immediately after draining!
129 static void tftp_get_packet(struct inode *inode)
131 uint16_t last_pkt;
132 const uint8_t *timeout_ptr;
133 uint8_t timeout;
134 uint16_t buffersize;
135 uint16_t serial;
136 jiffies_t oldtime;
137 struct tftp_packet *pkt = NULL;
138 struct netbuf *nbuf;
139 u16_t nbuf_len;
140 struct pxe_pvt_inode *socket = PVT(inode);
143 * Start by ACKing the previous packet; this should cause
144 * the next packet to be sent.
146 timeout_ptr = TimeoutTable;
147 timeout = *timeout_ptr++;
148 oldtime = jiffies();
150 ack_again:
151 ack_packet(inode, socket->tftp_lastpkt);
153 while (timeout) {
154 nbuf = netconn_recv(socket->conn);
155 if (!nbuf) {
156 jiffies_t now = jiffies();
158 if (now-oldtime >= timeout) {
159 oldtime = now;
160 timeout = *timeout_ptr++;
161 if (!timeout)
162 break;
163 goto ack_again;
165 continue;
168 netbuf_first(nbuf);
169 nbuf_len = 0;
170 nbuf_len = netbuf_len(nbuf);
171 if (nbuf_len <= socket->tftp_blksize + 4)
172 netbuf_copy(nbuf, socket->tftp_pktbuf, nbuf_len);
173 else
174 nbuf_len = 0; /* invalid packet */
175 netbuf_delete(nbuf);
176 if (nbuf_len < 4) /* Bad size for a DATA packet */
177 continue;
179 pkt = (struct tftp_packet *)(socket->tftp_pktbuf);
180 if (pkt->opcode != TFTP_DATA) /* Not a data packet */
181 continue;
183 /* If goes here, recevie OK, break */
184 break;
187 /* time runs out */
188 if (timeout == 0)
189 kaboom();
191 last_pkt = socket->tftp_lastpkt;
192 last_pkt++;
193 serial = ntohs(pkt->serial);
194 if (serial != last_pkt) {
196 * Wrong packet, ACK the packet and try again.
197 * This is presumably because the ACK got lost,
198 * so the server just resent the previous packet.
200 #if 0
201 printf("Wrong packet, wanted %04x, got %04x\n", \
202 htons(last_pkt), htons(*(uint16_t *)(data+2)));
203 #endif
204 goto ack_again;
207 /* It's the packet we want. We're also EOF if the size < blocksize */
208 socket->tftp_lastpkt = last_pkt; /* Update last packet number */
209 buffersize = nbuf_len - 4; /* Skip TFTP header */
210 socket->tftp_dataptr = socket->tftp_pktbuf + 4;
211 socket->tftp_filepos += buffersize;
212 socket->tftp_bytesleft = buffersize;
213 if (buffersize < socket->tftp_blksize) {
214 /* it's the last block, ACK packet immediately */
215 ack_packet(inode, serial);
217 /* Make sure we know we are at end of file */
218 inode->size = socket->tftp_filepos;
219 socket->tftp_goteof = 1;
220 tftp_close_file(inode);
224 const struct pxe_conn_ops tftp_conn_ops = {
225 .fill_buffer = tftp_get_packet,
226 .close = tftp_close_file,
230 * Open a TFTP connection to the server
232 * @param:inode, the inode to store our state in
233 * @param:ip, the ip to contact to get the file
234 * @param:filename, the file we wanna open
236 * @out: open_file_t structure, stores in file->open_file
237 * @out: the lenght of this file, stores in file->file_len
240 void tftp_open(struct url_info *url, int flags, struct inode *inode,
241 const char **redir)
243 struct pxe_pvt_inode *socket = PVT(inode);
244 char *buf;
245 struct netbuf *nbuf;
246 u16_t nbuf_len;
247 char *p;
248 char *options;
249 char *data;
250 static const char rrq_tail[] = "octet\0""tsize\0""0\0""blksize\0""1408";
251 char rrq_packet_buf[2+2*FILENAME_MAX+sizeof rrq_tail];
252 char reply_packet_buf[PKTBUF_SIZE];
253 const struct tftp_options *tftp_opt;
254 int i = 0;
255 int err;
256 int buffersize;
257 int rrq_len;
258 const uint8_t *timeout_ptr;
259 jiffies_t timeout;
260 jiffies_t oldtime;
261 uint16_t opcode;
262 uint16_t blk_num;
263 uint32_t opdata, *opdata_ptr;
264 struct ip_addr addr;
266 (void)redir; /* TFTP does not redirect */
267 (void)flags;
269 if (url->type != URL_OLD_TFTP) {
271 * The TFTP URL specification allows the TFTP to end with a
272 * ;mode= which we just ignore.
274 url_unescape(url->path, ';');
277 if (!url->port)
278 url->port = TFTP_PORT;
280 socket->ops = &tftp_conn_ops;
281 socket->conn = netconn_new(NETCONN_UDP);
282 if (!socket->conn)
283 return;
285 socket->conn->recv_timeout = 15; /* A 15 ms recv timeout... */
286 err = netconn_bind(socket->conn, NULL, 0);
287 if (err) {
288 printf("netconn_bind error %d\n", err);
289 return;
292 buf = rrq_packet_buf;
293 *(uint16_t *)buf = TFTP_RRQ; /* TFTP opcode */
294 buf += 2;
296 buf = stpcpy(buf, url->path);
298 buf++; /* Point *past* the final NULL */
299 memcpy(buf, rrq_tail, sizeof rrq_tail);
300 buf += sizeof rrq_tail;
302 rrq_len = buf - rrq_packet_buf;
304 timeout_ptr = TimeoutTable; /* Reset timeout */
306 sendreq:
307 netconn_disconnect(socket->conn);
308 timeout = *timeout_ptr++;
309 if (!timeout)
310 return; /* No file available... */
311 oldtime = jiffies();
313 addr.addr = url->ip;
314 netconn_connect(socket->conn, &addr, url->port);
315 udp_send(socket->conn, rrq_packet_buf, rrq_len);
317 /* If the WRITE call fails, we let the timeout take care of it... */
318 wait_pkt:
319 netconn_disconnect(socket->conn);
320 for (;;) {
321 nbuf = netconn_recv(socket->conn);
322 if (!nbuf) {
323 jiffies_t now = jiffies();
324 if (now - oldtime >= timeout)
325 goto sendreq;
326 } else {
327 /* Make sure the packet actually came from the server */
328 bool ok_source;
329 ok_source = netbuf_fromaddr(nbuf)->addr == url->ip;
330 nbuf_len = netbuf_len(nbuf);
331 if (nbuf_len <= PKTBUF_SIZE)
332 netbuf_copy(nbuf, reply_packet_buf, nbuf_len);
333 else
334 nbuf_len = 0; /* impossible mtu < PKTBUF_SIZE */
335 netbuf_delete(nbuf);
336 if (ok_source)
337 break;
341 netconn_connect(socket->conn, netbuf_fromaddr(nbuf), netbuf_fromport(nbuf));
343 /* filesize <- -1 == unknown */
344 inode->size = -1;
345 socket->tftp_blksize = TFTP_BLOCKSIZE;
346 buffersize = nbuf_len - 2; /* bytes after opcode */
347 if (buffersize < 0)
348 goto wait_pkt; /* Garbled reply */
351 * Get the opcode type, and parse it
353 opcode = *(uint16_t *)reply_packet_buf;
354 switch (opcode) {
355 case TFTP_ERROR:
356 inode->size = 0;
357 goto done; /* ERROR reply; don't try again */
359 case TFTP_DATA:
361 * If the server doesn't support any options, we'll get a
362 * DATA reply instead of OACK. Stash the data in the file
363 * buffer and go with the default value for all options...
365 * We got a DATA packet, meaning no options are
366 * suported. Save the data away and consider the
367 * length undefined, *unless* this is the only
368 * data packet...
370 buffersize -= 2;
371 if (buffersize < 0)
372 goto wait_pkt;
373 data = reply_packet_buf + 2;
374 blk_num = ntohs(*(uint16_t *)data);
375 data += 2;
376 if (blk_num != 1)
377 goto wait_pkt;
378 socket->tftp_lastpkt = blk_num;
379 if (buffersize > TFTP_BLOCKSIZE)
380 goto err_reply; /* Corrupt */
382 socket->tftp_pktbuf = malloc(TFTP_BLOCKSIZE + 4);
383 if (!socket->tftp_pktbuf)
384 goto err_reply; /* Internal error */
386 if (buffersize < TFTP_BLOCKSIZE) {
388 * This is the final EOF packet, already...
389 * We know the filesize, but we also want to
390 * ack the packet and set the EOF flag.
392 inode->size = buffersize;
393 socket->tftp_goteof = 1;
394 ack_packet(inode, blk_num);
397 socket->tftp_bytesleft = buffersize;
398 socket->tftp_dataptr = socket->tftp_pktbuf;
399 memcpy(socket->tftp_pktbuf, data, buffersize);
400 goto done;
402 case TFTP_OACK:
404 * Now we need to parse the OACK packet to get the transfer
405 * and packet sizes.
408 options = reply_packet_buf + 2;
409 p = options;
411 while (buffersize) {
412 const char *opt = p;
415 * If we find an option which starts with a NUL byte,
416 * (a null option), we're either seeing garbage that some
417 * TFTP servers add to the end of the packet, or we have
418 * no clue how to parse the rest of the packet (what is
419 * an option name and what is a value?) In either case,
420 * discard the rest.
422 if (!*opt)
423 goto done;
425 while (buffersize) {
426 if (!*p)
427 break; /* Found a final null */
428 *p++ |= 0x20;
429 buffersize--;
431 if (!buffersize)
432 break; /* Unterminated option */
434 /* Consume the terminal null */
435 p++;
436 buffersize--;
438 if (!buffersize)
439 break; /* No option data */
442 * Parse option pointed to by options; guaranteed to be
443 * null-terminated
445 tftp_opt = tftp_options;
446 for (i = 0; i < tftp_nopts; i++) {
447 if (!strcmp(opt, tftp_opt->str_ptr))
448 break;
449 tftp_opt++;
451 if (i == tftp_nopts)
452 goto err_reply; /* Non-negotitated option returned,
453 no idea what it means ...*/
455 /* get the address of the filed that we want to write on */
456 opdata_ptr = (uint32_t *)((char *)inode + tftp_opt->offset);
457 opdata = 0;
459 /* do convert a number-string to decimal number, just like atoi */
460 while (buffersize--) {
461 uint8_t d = *p++;
462 if (d == '\0')
463 break; /* found a final null */
464 d -= '0';
465 if (d > 9)
466 goto err_reply; /* Not a decimal digit */
467 opdata = opdata*10 + d;
469 *opdata_ptr = opdata;
472 if (socket->tftp_blksize < 64 || socket->tftp_blksize > PKTBUF_SIZE)
473 goto err_reply;
475 /* Parsing successful, allocate buffer */
476 socket->tftp_pktbuf = malloc(socket->tftp_blksize + 4);
477 if (!socket->tftp_pktbuf)
478 goto err_reply;
479 else
480 goto done;
482 default:
483 printf("TFTP unknown opcode %d\n", ntohs(opcode));
484 goto err_reply;
487 err_reply:
488 /* Build the TFTP error packet */
489 tftp_error(inode, TFTP_EOPTNEG, "TFTP protocol error");
490 inode->size = 0;
492 done:
493 if (!inode->size) {
494 netconn_delete(socket->conn);
495 socket->conn = NULL;
497 return;