1 | #include <minmax.h> |
---|
2 | #include <net.h> |
---|
3 | #include "pxe.h" |
---|
4 | #include "url.h" |
---|
5 | #include "tftp.h" |
---|
6 | |
---|
7 | 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 |
---|
10 | }; |
---|
11 | struct tftp_packet { |
---|
12 | uint16_t opcode; |
---|
13 | uint16_t serial; |
---|
14 | char data[]; |
---|
15 | }; |
---|
16 | |
---|
17 | static void tftp_error(struct inode *file, uint16_t errnum, |
---|
18 | const char *errstr); |
---|
19 | |
---|
20 | static 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 | */ |
---|
36 | static 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 | */ |
---|
62 | static 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 | */ |
---|
78 | static 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 | |
---|
169 | const 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 | */ |
---|
185 | void 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 */ |
---|
240 | sendreq: |
---|
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... */ |
---|
249 | wait_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 | |
---|
406 | err_reply: |
---|
407 | /* Build the TFTP error packet */ |
---|
408 | tftp_error(inode, TFTP_EOPTNEG, "TFTP protocol error"); |
---|
409 | inode->size = 0; |
---|
410 | |
---|
411 | done: |
---|
412 | if (!inode->size) |
---|
413 | core_udp_close(socket); |
---|
414 | |
---|
415 | return; |
---|
416 | } |
---|