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
|
#include <minmax.h>
#include <net.h>
#include "pxe.h"
#include "url.h"
#include "tftp.h"
const uint8_t TimeoutTable[] = {
2, 2, 3, 3, 4, 5, 6, 7, 9, 10, 12, 15, 18, 21, 26, 31, 37, 44,
53, 64, 77, 92, 110, 132, 159, 191, 229, 255, 255, 255, 255, 0
};
struct tftp_packet {
uint16_t opcode;
uint16_t serial;
char data[];
};
static void tftp_error(struct inode *file, uint16_t errnum,
const char *errstr);
static void tftp_close_file(struct inode *inode)
{
struct pxe_pvt_inode *socket = PVT(inode);
if (!socket->tftp_goteof) {
tftp_error(inode, 0, "No error, file close");
}
core_udp_close(socket);
}
/**
* Send an ERROR packet. This is used to terminate a connection.
*
* @inode: Inode structure
* @errnum: Error number (network byte order)
* @errstr: Error string (included in packet)
*/
static void tftp_error(struct inode *inode, uint16_t errnum,
const char *errstr)
{
static struct {
uint16_t err_op;
uint16_t err_num;
char err_msg[64];
} __packed err_buf;
int len = min(strlen(errstr), sizeof(err_buf.err_msg)-1);
struct pxe_pvt_inode *socket = PVT(inode);
err_buf.err_op = TFTP_ERROR;
err_buf.err_num = errnum;
memcpy(err_buf.err_msg, errstr, len);
err_buf.err_msg[len] = '\0';
core_udp_send(socket, &err_buf, 4 + len + 1);
}
/**
* Send ACK packet. This is a common operation and so is worth canning.
*
* @param: inode, Inode pointer
* @param: ack_num, Packet # to ack (host byte order)
*
*/
static void ack_packet(struct inode *inode, uint16_t ack_num)
{
static uint16_t ack_packet_buf[2];
struct pxe_pvt_inode *socket = PVT(inode);
/* Packet number to ack */
ack_packet_buf[0] = TFTP_ACK;
ack_packet_buf[1] = htons(ack_num);
core_udp_send(socket, ack_packet_buf, 4);
}
/*
* Get a fresh packet if the buffer is drained, and we haven't hit
* EOF yet. The buffer should be filled immediately after draining!
*/
static void tftp_get_packet(struct inode *inode)
{
uint16_t last_pkt;
const uint8_t *timeout_ptr;
uint8_t timeout;
uint16_t buffersize;
uint16_t serial;
jiffies_t oldtime;
struct tftp_packet *pkt = NULL;
uint16_t buf_len;
struct pxe_pvt_inode *socket = PVT(inode);
uint16_t src_port;
uint32_t src_ip;
int err;
/*
* Start by ACKing the previous packet; this should cause
* the next packet to be sent.
*/
timeout_ptr = TimeoutTable;
timeout = *timeout_ptr++;
oldtime = jiffies();
ack_again:
ack_packet(inode, socket->tftp_lastpkt);
while (timeout) {
buf_len = socket->tftp_blksize + 4;
err = core_udp_recv(socket, socket->tftp_pktbuf, &buf_len,
&src_ip, &src_port);
if (err) {
jiffies_t now = jiffies();
if (now-oldtime >= timeout) {
oldtime = now;
timeout = *timeout_ptr++;
if (!timeout)
break;
goto ack_again;
}
continue;
}
if (buf_len < 4) /* Bad size for a DATA packet */
continue;
pkt = (struct tftp_packet *)(socket->tftp_pktbuf);
if (pkt->opcode != TFTP_DATA) /* Not a data packet */
continue;
/* If goes here, recevie OK, break */
break;
}
/* time runs out */
if (timeout == 0)
kaboom();
last_pkt = socket->tftp_lastpkt;
last_pkt++;
serial = ntohs(pkt->serial);
if (serial != last_pkt) {
/*
* Wrong packet, ACK the packet and try again.
* This is presumably because the ACK got lost,
* so the server just resent the previous packet.
*/
#if 0
printf("Wrong packet, wanted %04x, got %04x\n", \
htons(last_pkt), htons(*(uint16_t *)(data+2)));
#endif
goto ack_again;
}
/* It's the packet we want. We're also EOF if the size < blocksize */
socket->tftp_lastpkt = last_pkt; /* Update last packet number */
buffersize = buf_len - 4; /* Skip TFTP header */
socket->tftp_dataptr = socket->tftp_pktbuf + 4;
socket->tftp_filepos += buffersize;
socket->tftp_bytesleft = buffersize;
if (buffersize < socket->tftp_blksize) {
/* it's the last block, ACK packet immediately */
ack_packet(inode, serial);
/* Make sure we know we are at end of file */
inode->size = socket->tftp_filepos;
socket->tftp_goteof = 1;
tftp_close_file(inode);
}
}
const struct pxe_conn_ops tftp_conn_ops = {
.fill_buffer = tftp_get_packet,
.close = tftp_close_file,
};
/**
* Open a TFTP connection to the server
*
* @param:inode, the inode to store our state in
* @param:ip, the ip to contact to get the file
* @param:filename, the file we wanna open
*
* @out: open_file_t structure, stores in file->open_file
* @out: the lenght of this file, stores in file->file_len
*
*/
void tftp_open(struct url_info *url, int flags, struct inode *inode,
const char **redir)
{
struct pxe_pvt_inode *socket = PVT(inode);
char *buf;
uint16_t buf_len;
char *p;
char *options;
char *data;
static const char rrq_tail[] = "octet\0""tsize\0""0\0""blksize\0""1408";
char rrq_packet_buf[2+2*FILENAME_MAX+sizeof rrq_tail];
char reply_packet_buf[PKTBUF_SIZE];
int err;
int buffersize;
int rrq_len;
const uint8_t *timeout_ptr;
jiffies_t timeout;
jiffies_t oldtime;
uint16_t opcode;
uint16_t blk_num;
uint64_t opdata;
uint16_t src_port;
uint32_t src_ip;
(void)redir; /* TFTP does not redirect */
(void)flags;
if (url->type != URL_OLD_TFTP) {
/*
* The TFTP URL specification allows the TFTP to end with a
* ;mode= which we just ignore.
*/
url_unescape(url->path, ';');
}
if (!url->port)
url->port = TFTP_PORT;
socket->ops = &tftp_conn_ops;
if (core_udp_open(socket))
return;
buf = rrq_packet_buf;
*(uint16_t *)buf = TFTP_RRQ; /* TFTP opcode */
buf += 2;
buf = stpcpy(buf, url->path);
buf++; /* Point *past* the final NULL */
memcpy(buf, rrq_tail, sizeof rrq_tail);
buf += sizeof rrq_tail;
rrq_len = buf - rrq_packet_buf;
timeout_ptr = TimeoutTable; /* Reset timeout */
sendreq:
timeout = *timeout_ptr++;
if (!timeout)
return; /* No file available... */
oldtime = jiffies();
core_udp_sendto(socket, rrq_packet_buf, rrq_len, url->ip, url->port);
/* If the WRITE call fails, we let the timeout take care of it... */
wait_pkt:
for (;;) {
buf_len = sizeof(reply_packet_buf);
err = core_udp_recv(socket, reply_packet_buf, &buf_len,
&src_ip, &src_port);
if (err) {
jiffies_t now = jiffies();
if (now - oldtime >= timeout)
goto sendreq;
} else {
/* Make sure the packet actually came from the server and
is long enough for a TFTP opcode */
dprintf("tftp_open: got packet buflen=%d\n", buf_len);
if ((src_ip == url->ip) && (buf_len >= 2))
break;
}
}
core_udp_disconnect(socket);
core_udp_connect(socket, src_ip, src_port);
/* filesize <- -1 == unknown */
inode->size = -1;
socket->tftp_blksize = TFTP_BLOCKSIZE;
buffersize = buf_len - 2; /* bytes after opcode */
/*
* Get the opcode type, and parse it
*/
opcode = *(uint16_t *)reply_packet_buf;
switch (opcode) {
case TFTP_ERROR:
inode->size = 0;
goto done; /* ERROR reply; don't try again */
case TFTP_DATA:
/*
* If the server doesn't support any options, we'll get a
* DATA reply instead of OACK. Stash the data in the file
* buffer and go with the default value for all options...
*
* We got a DATA packet, meaning no options are
* suported. Save the data away and consider the
* length undefined, *unless* this is the only
* data packet...
*/
buffersize -= 2;
if (buffersize < 0)
goto wait_pkt;
data = reply_packet_buf + 2;
blk_num = ntohs(*(uint16_t *)data);
data += 2;
if (blk_num != 1)
goto wait_pkt;
socket->tftp_lastpkt = blk_num;
if (buffersize > TFTP_BLOCKSIZE)
goto err_reply; /* Corrupt */
socket->tftp_pktbuf = malloc(TFTP_BLOCKSIZE + 4);
if (!socket->tftp_pktbuf)
goto err_reply; /* Internal error */
if (buffersize < TFTP_BLOCKSIZE) {
/*
* This is the final EOF packet, already...
* We know the filesize, but we also want to
* ack the packet and set the EOF flag.
*/
inode->size = buffersize;
socket->tftp_goteof = 1;
ack_packet(inode, blk_num);
}
socket->tftp_bytesleft = buffersize;
socket->tftp_dataptr = socket->tftp_pktbuf;
memcpy(socket->tftp_pktbuf, data, buffersize);
goto done;
case TFTP_OACK:
/*
* Now we need to parse the OACK packet to get the transfer
* and packet sizes.
*/
options = reply_packet_buf + 2;
p = options;
while (buffersize) {
const char *opt = p;
/*
* If we find an option which starts with a NUL byte,
* (a null option), we're either seeing garbage that some
* TFTP servers add to the end of the packet, or we have
* no clue how to parse the rest of the packet (what is
* an option name and what is a value?) In either case,
* discard the rest.
*/
if (!*opt)
goto done;
while (buffersize) {
if (!*p)
break; /* Found a final null */
*p++ |= 0x20;
buffersize--;
}
if (!buffersize)
break; /* Unterminated option */
/* Consume the terminal null */
p++;
buffersize--;
if (!buffersize)
break; /* No option data */
opdata = 0;
/* do convert a number-string to decimal number, just like atoi */
while (buffersize--) {
uint8_t d = *p++;
if (d == '\0')
break; /* found a final null */
d -= '0';
if (d > 9)
goto err_reply; /* Not a decimal digit */
opdata = opdata*10 + d;
}
if (!strcmp(opt, "tsize"))
inode->size = opdata;
else if (!strcmp(opt, "blksize"))
socket->tftp_blksize = opdata;
else
goto err_reply; /* Non-negotitated option returned,
no idea what it means ...*/
}
if (socket->tftp_blksize < 64 || socket->tftp_blksize > PKTBUF_SIZE)
goto err_reply;
/* Parsing successful, allocate buffer */
socket->tftp_pktbuf = malloc(socket->tftp_blksize + 4);
if (!socket->tftp_pktbuf)
goto err_reply;
else
goto done;
default:
printf("TFTP unknown opcode %d\n", ntohs(opcode));
goto err_reply;
}
err_reply:
/* Build the TFTP error packet */
tftp_error(inode, TFTP_EOPTNEG, "TFTP protocol error");
inode->size = 0;
done:
if (!inode->size)
core_udp_close(socket);
return;
}
|