source: bootcd/isolinux/syslinux-6.03/core/fs/pxe/tftp.c

Last change on this file was e16e8f2, checked in by Edwin Eefting <edwin@datux.nl>, 3 years ago

bootstuff

  • Property mode set to 100644
File size: 10.8 KB
Line 
1#include <minmax.h>
2#include <net.h>
3#include "pxe.h"
4#include "url.h"
5#include "tftp.h"
6
7const 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
10};
11struct tftp_packet {
12    uint16_t opcode;
13    uint16_t serial;
14    char data[];
15};
16
17static void tftp_error(struct inode *file, uint16_t errnum,
18                       const char *errstr);
19
20static void tftp_close_file(struct inode *inode)
21{
22    struct pxe_pvt_inode *socket = PVT(inode);
23    if (!socket->tftp_goteof) {
24        tftp_error(inode, 0, "No error, file close");
25    }
26    core_udp_close(socket);
27}
28
29/**
30 * Send an ERROR packet.  This is used to terminate a connection.
31 *
32 * @inode:      Inode structure
33 * @errnum:     Error number (network byte order)
34 * @errstr:     Error string (included in packet)
35 */
36static void tftp_error(struct inode *inode, uint16_t errnum,
37                       const char *errstr)
38{
39    static struct {
40        uint16_t err_op;
41        uint16_t err_num;
42        char err_msg[64];
43    } __packed err_buf;
44    int len = min(strlen(errstr), sizeof(err_buf.err_msg)-1);
45    struct pxe_pvt_inode *socket = PVT(inode);
46
47    err_buf.err_op  = TFTP_ERROR;
48    err_buf.err_num = errnum;
49    memcpy(err_buf.err_msg, errstr, len);
50    err_buf.err_msg[len] = '\0';
51
52    core_udp_send(socket, &err_buf, 4 + len + 1);
53}
54
55/**
56 * Send ACK packet. This is a common operation and so is worth canning.
57 *
58 * @param: inode,   Inode pointer
59 * @param: ack_num, Packet # to ack (host byte order)
60 *
61 */
62static void ack_packet(struct inode *inode, uint16_t ack_num)
63{
64    static uint16_t ack_packet_buf[2];
65    struct pxe_pvt_inode *socket = PVT(inode);
66
67    /* Packet number to ack */
68    ack_packet_buf[0]     = TFTP_ACK;
69    ack_packet_buf[1]     = htons(ack_num);
70
71    core_udp_send(socket, ack_packet_buf, 4);
72}
73
74/*
75 * Get a fresh packet if the buffer is drained, and we haven't hit
76 * EOF yet.  The buffer should be filled immediately after draining!
77 */
78static void tftp_get_packet(struct inode *inode)
79{
80    uint16_t last_pkt;
81    const uint8_t *timeout_ptr;
82    uint8_t timeout;
83    uint16_t buffersize;
84    uint16_t serial;
85    jiffies_t oldtime;
86    struct tftp_packet *pkt = NULL;
87    uint16_t buf_len;
88    struct pxe_pvt_inode *socket = PVT(inode);
89    uint16_t src_port;
90    uint32_t src_ip;
91    int err;
92
93    /*
94     * Start by ACKing the previous packet; this should cause
95     * the next packet to be sent.
96     */
97    timeout_ptr = TimeoutTable;
98    timeout = *timeout_ptr++;
99    oldtime = jiffies();
100
101 ack_again:
102    ack_packet(inode, socket->tftp_lastpkt);
103
104    while (timeout) {
105        buf_len = socket->tftp_blksize + 4;
106        err = core_udp_recv(socket, socket->tftp_pktbuf, &buf_len,
107                            &src_ip, &src_port);
108        if (err) {
109            jiffies_t now = jiffies();
110
111            if (now-oldtime >= timeout) {
112                oldtime = now;
113                timeout = *timeout_ptr++;
114                if (!timeout)
115                    break;
116                goto ack_again;
117            }
118            continue;
119        }
120
121        if (buf_len < 4)        /* Bad size for a DATA packet */
122            continue;
123
124        pkt = (struct tftp_packet *)(socket->tftp_pktbuf);
125        if (pkt->opcode != TFTP_DATA)    /* Not a data packet */
126            continue;
127
128        /* If goes here, recevie OK, break */
129        break;
130    }
131
132    /* time runs out */
133    if (timeout == 0)
134        kaboom();
135
136    last_pkt = socket->tftp_lastpkt;
137    last_pkt++;
138    serial = ntohs(pkt->serial);
139    if (serial != last_pkt) {
140        /*
141         * Wrong packet, ACK the packet and try again.
142         * This is presumably because the ACK got lost,
143         * so the server just resent the previous packet.
144         */
145#if 0
146        printf("Wrong packet, wanted %04x, got %04x\n", \
147               htons(last_pkt), htons(*(uint16_t *)(data+2)));
148#endif
149        goto ack_again;
150    }
151
152    /* It's the packet we want.  We're also EOF if the size < blocksize */
153    socket->tftp_lastpkt = last_pkt;    /* Update last packet number */
154    buffersize = buf_len - 4;           /* Skip TFTP header */
155    socket->tftp_dataptr = socket->tftp_pktbuf + 4;
156    socket->tftp_filepos += buffersize;
157    socket->tftp_bytesleft = buffersize;
158    if (buffersize < socket->tftp_blksize) {
159        /* it's the last block, ACK packet immediately */
160        ack_packet(inode, serial);
161
162        /* Make sure we know we are at end of file */
163        inode->size             = socket->tftp_filepos;
164        socket->tftp_goteof     = 1;
165        tftp_close_file(inode);
166    }
167}
168
169const struct pxe_conn_ops tftp_conn_ops = {
170    .fill_buffer        = tftp_get_packet,
171    .close              = tftp_close_file,
172};
173
174/**
175 * Open a TFTP connection to the server
176 *
177 * @param:inode, the inode to store our state in
178 * @param:ip, the ip to contact to get the file
179 * @param:filename, the file we wanna open
180 *
181 * @out: open_file_t structure, stores in file->open_file
182 * @out: the lenght of this file, stores in file->file_len
183 *
184 */
185void tftp_open(struct url_info *url, int flags, struct inode *inode,
186               const char **redir)
187{
188    struct pxe_pvt_inode *socket = PVT(inode);
189    char *buf;
190    uint16_t buf_len;
191    char *p;
192    char *options;
193    char *data;
194    static const char rrq_tail[] = "octet\0""tsize\0""0\0""blksize\0""1408";
195    char rrq_packet_buf[2+2*FILENAME_MAX+sizeof rrq_tail];
196    char reply_packet_buf[PKTBUF_SIZE];
197    int err;
198    int buffersize;
199    int rrq_len;
200    const uint8_t  *timeout_ptr;
201    jiffies_t timeout;
202    jiffies_t oldtime;
203    uint16_t opcode;
204    uint16_t blk_num;
205    uint64_t opdata;
206    uint16_t src_port;
207    uint32_t src_ip;
208
209    (void)redir;                /* TFTP does not redirect */
210    (void)flags;
211
212    if (url->type != URL_OLD_TFTP) {
213        /*
214         * The TFTP URL specification allows the TFTP to end with a
215         * ;mode= which we just ignore.
216         */
217        url_unescape(url->path, ';');
218    }
219
220    if (!url->port)
221        url->port = TFTP_PORT;
222
223    socket->ops = &tftp_conn_ops;
224    if (core_udp_open(socket))
225        return;
226
227    buf = rrq_packet_buf;
228    *(uint16_t *)buf = TFTP_RRQ;  /* TFTP opcode */
229    buf += 2;
230
231    buf = stpcpy(buf, url->path);
232
233    buf++;                      /* Point *past* the final NULL */
234    memcpy(buf, rrq_tail, sizeof rrq_tail);
235    buf += sizeof rrq_tail;
236
237    rrq_len = buf - rrq_packet_buf;
238
239    timeout_ptr = TimeoutTable;   /* Reset timeout */
240sendreq:
241    timeout = *timeout_ptr++;
242    if (!timeout)
243        return;                 /* No file available... */
244    oldtime = jiffies();
245
246    core_udp_sendto(socket, rrq_packet_buf, rrq_len, url->ip, url->port);
247
248    /* If the WRITE call fails, we let the timeout take care of it... */
249wait_pkt:
250    for (;;) {
251        buf_len = sizeof(reply_packet_buf);
252
253        err = core_udp_recv(socket, reply_packet_buf, &buf_len,
254                            &src_ip, &src_port);
255        if (err) {
256            jiffies_t now = jiffies();
257            if (now - oldtime >= timeout)
258                 goto sendreq;
259        } else {
260            /* Make sure the packet actually came from the server and
261               is long enough for a TFTP opcode */
262            dprintf("tftp_open: got packet buflen=%d\n", buf_len);
263            if ((src_ip == url->ip) && (buf_len >= 2))
264                break;
265        }
266    }
267
268    core_udp_disconnect(socket);
269    core_udp_connect(socket, src_ip, src_port);
270
271    /* filesize <- -1 == unknown */
272    inode->size = -1;
273    socket->tftp_blksize = TFTP_BLOCKSIZE;
274    buffersize = buf_len - 2;     /* bytes after opcode */
275
276    /*
277     * Get the opcode type, and parse it
278     */
279    opcode = *(uint16_t *)reply_packet_buf;
280    switch (opcode) {
281    case TFTP_ERROR:
282        inode->size = 0;
283        goto done;        /* ERROR reply; don't try again */
284
285    case TFTP_DATA:
286        /*
287         * If the server doesn't support any options, we'll get a
288         * DATA reply instead of OACK. Stash the data in the file
289         * buffer and go with the default value for all options...
290         *
291         * We got a DATA packet, meaning no options are
292         * suported. Save the data away and consider the
293         * length undefined, *unless* this is the only
294         * data packet...
295         */
296        buffersize -= 2;
297        if (buffersize < 0)
298            goto wait_pkt;
299        data = reply_packet_buf + 2;
300        blk_num = ntohs(*(uint16_t *)data);
301        data += 2;
302        if (blk_num != 1)
303            goto wait_pkt;
304        socket->tftp_lastpkt = blk_num;
305        if (buffersize > TFTP_BLOCKSIZE)
306            goto err_reply;     /* Corrupt */
307
308        socket->tftp_pktbuf = malloc(TFTP_BLOCKSIZE + 4);
309        if (!socket->tftp_pktbuf)
310            goto err_reply;     /* Internal error */
311
312        if (buffersize < TFTP_BLOCKSIZE) {
313            /*
314             * This is the final EOF packet, already...
315             * We know the filesize, but we also want to
316             * ack the packet and set the EOF flag.
317             */
318            inode->size = buffersize;
319            socket->tftp_goteof = 1;
320            ack_packet(inode, blk_num);
321        }
322
323        socket->tftp_bytesleft = buffersize;
324        socket->tftp_dataptr = socket->tftp_pktbuf;
325        memcpy(socket->tftp_pktbuf, data, buffersize);
326        goto done;
327
328    case TFTP_OACK:
329        /*
330         * Now we need to parse the OACK packet to get the transfer
331         * and packet sizes.
332         */
333
334        options = reply_packet_buf + 2;
335        p = options;
336
337        while (buffersize) {
338            const char *opt = p;
339
340            /*
341             * If we find an option which starts with a NUL byte,
342             * (a null option), we're either seeing garbage that some
343             * TFTP servers add to the end of the packet, or we have
344             * no clue how to parse the rest of the packet (what is
345             * an option name and what is a value?)  In either case,
346             * discard the rest.
347             */
348            if (!*opt)
349                goto done;
350
351            while (buffersize) {
352                if (!*p)
353                    break;      /* Found a final null */
354                *p++ |= 0x20;
355                buffersize--;
356            }
357            if (!buffersize)
358                break;          /* Unterminated option */
359
360            /* Consume the terminal null */
361            p++;
362            buffersize--;
363
364            if (!buffersize)
365                break;          /* No option data */
366
367            opdata = 0;
368
369            /* do convert a number-string to decimal number, just like atoi */
370            while (buffersize--) {
371                uint8_t d = *p++;
372                if (d == '\0')
373                    break;              /* found a final null */
374                d -= '0';
375                if (d > 9)
376                    goto err_reply;     /* Not a decimal digit */
377                opdata = opdata*10 + d;
378            }
379
380            if (!strcmp(opt, "tsize"))
381                inode->size = opdata;
382            else if (!strcmp(opt, "blksize"))
383                socket->tftp_blksize = opdata;
384            else
385                goto err_reply; /* Non-negotitated option returned,
386                                   no idea what it means ...*/
387
388
389        }
390
391        if (socket->tftp_blksize < 64 || socket->tftp_blksize > PKTBUF_SIZE)
392            goto err_reply;
393
394        /* Parsing successful, allocate buffer */
395        socket->tftp_pktbuf = malloc(socket->tftp_blksize + 4);
396        if (!socket->tftp_pktbuf)
397            goto err_reply;
398        else
399            goto done;
400
401    default:
402        printf("TFTP unknown opcode %d\n", ntohs(opcode));
403        goto err_reply;
404    }
405
406err_reply:
407    /* Build the TFTP error packet */
408    tftp_error(inode, TFTP_EOPTNEG, "TFTP protocol error");
409    inode->size = 0;
410
411done:
412    if (!inode->size)
413        core_udp_close(socket);
414
415    return;
416}
Note: See TracBrowser for help on using the repository browser.