kern/85650: [patch] modifications to tftp-based PXE booting

Ben Thomas bthomas at virtualiron.com
Fri Sep 2 18:00:40 GMT 2005


>Number:         85650
>Category:       kern
>Synopsis:       [patch] modifications to tftp-based PXE booting
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          change-request
>Submitter-Id:   current-users
>Arrival-Date:   Fri Sep 02 18:00:34 GMT 2005
>Closed-Date:
>Last-Modified:
>Originator:     Ben Thomas
>Release:        FreeBSD 5.4-RELEASE i386
>Organization:
Virtual Iron Software
>Environment:
System: FreeBSD bthomas4.katana-technology.com 5.4-RELEASE FreeBSD 5.4-RELEASE #10: Sun Aug 28 13:48:00 EDT 2005 ben at bthomas4.katana-technology.com:/usr/obj/usr/home/ben/BSD/RELENG_5_4_0_RELEASE/src/sys/BEN i386

>Description:

The current tftp booting code is modified in the following ways:

- use the root path name from the DHCP server response. This allows
  booting from different tftp roots and is quite useful

- start sending ACK/NAK at the end of transfer or on errors.  Currently,
  the code just drops the session, which is visible by the tftp server
  continuing to retry packets for some time.  By sending the ACK/NAK
  codes, the tftp server will drop the session and move on to
  something else.

It might have been more useful to rewrite the code along a different
design model, but I chose instead to make the smaller set of changes
to the existing code.

The patch is against the 5_4_0_RELEASE code


>How-To-Repeat:
>Fix:

--- tftp.c-DIFF begins here ---
--- /usr/src.original/lib/libstand/tftp.c	Sun Mar  2 19:58:47 2003
+++ /usr/src/lib/libstand/tftp.c	Thu Aug 11 16:53:36 2005
@@ -166,6 +166,63 @@
 	}
 }
 
+/*
+ * Routine to send a TFTP ACK response for the current block
+ * This is only used to send the final ACK, which the current
+ * code, due to its structure, won't send.
+ */
+
+static ssize_t
+tftp_sendack(struct tftp_handle *h)
+{
+	ssize_t	size;
+	struct {
+		u_char header[HEADER_SIZE];
+		struct tftphdr	hdr;
+	} msg;
+	
+	msg.hdr.th_opcode = htons((u_short)ACK);
+	msg.hdr.th_block  = htons((u_short)h->currblock);
+	size = sendudp(h->iodesc, &msg.hdr, 4);
+#ifdef DEBUG
+	printf("tftp - sent final ACK for block %d\n", h->currblock);
+#endif
+	return size;
+}
+
+/*
+ * Routine to send a TFTP NAK response for the current block
+ * We use errorcode 0 (undefined) and fill in the ASCII. This
+ * seems to be the friendliest way to terminate a connection.
+ * This routine is used when this code wants to abandon the
+ * connection.  As TFTP is UDP, this means that unless you tell the
+ * server to give up, it will keep trying for some time. Sending
+ * this NAK will stop this.
+ */
+
+static ssize_t
+tftp_sendnak(struct tftp_handle *h)
+{
+	ssize_t	size;
+	struct {
+		u_char header[HEADER_SIZE];
+		struct tftphdr	hdr;
+		u_char text[32];
+	} msg;
+	
+	msg.hdr.th_opcode = htons((u_short)ERROR);
+	msg.hdr.th_block  = htons((u_short)h->currblock);
+	msg.hdr.th_code   = htons((u_short)EUNDEF);
+	strcpy(msg.hdr.th_msg, "Terminate");
+	/* Note that the hdr size include and extra byte, so we don't */
+	/* explicitly include the size of the NULL at the end of the string */
+	size = sendudp(h->iodesc, &msg.hdr, (sizeof(msg.hdr) + strlen("Terminate")));
+#ifdef DEBUG
+	printf("tftp - sent NAK for block %d\n", h->currblock);
+#endif
+	return size;
+}
+
 /* send request, expect first block (or error) */
 static int 
 tftp_makereq(h)
@@ -205,8 +262,14 @@
 	h->currblock = 1;
 	h->validsize = res;
 	h->islastblock = 0;
-	if (res < SEGSIZE)
+	/* 
+         * At the end of the transfer, set the flag indicating this and
+	 * send the final ACK to close the transfer.
+         */
+	if (res < SEGSIZE) {
 		h->islastblock = 1;	/* very short file */
+		tftp_sendack(h);
+	}
 	return (0);
 }
 
@@ -240,8 +303,14 @@
 
 	h->currblock++;
 	h->validsize = res;
-	if (res < SEGSIZE)
+	/*
+         * At the end of the transfer, set the flag indicating this and
+	 * send the final ACK to close the transfer.
+         */
+	if (res < SEGSIZE) {
 		h->islastblock = 1;	/* EOF */
+		tftp_sendack(h);
+	}
 	return (0);
 }
 
@@ -253,6 +322,7 @@
 	struct tftp_handle *tftpfile;
 	struct iodesc  *io;
 	int             res;
+	int		length;
 
 #ifndef __i386__
 	if (strcmp(f->f_dev->dv_name, "net") != 0)
@@ -269,11 +339,27 @@
 
 	io->destip = servip;
 	tftpfile->off = 0;
-	tftpfile->path = strdup(path);
+	/* 
+         * Add in the root path name from the server response.
+	 * This is typically the DHCP root-path option.
+	 * This change allows TFTP booting from different roots,
+	 * which is both quite useful and possibly expected based
+	 * upon the DHCP options available.
+         */
+
+	length = strlen(path);
+	if (rootpath != NULL)
+	  length += strlen(rootpath);
+	tftpfile->path = malloc(length + 1);
 	if (tftpfile->path == NULL) {
 	    free(tftpfile);
 	    return(ENOMEM);
 	}
+	if (rootpath != NULL)
+	  strcpy(tftpfile->path, rootpath);
+	else
+	  tftpfile->path[0] = '\0';
+	strcat(tftpfile->path, path);
 
 	res = tftp_makereq(tftpfile, path);
 
@@ -305,9 +391,18 @@
 
 		needblock = tftpfile->off / SEGSIZE + 1;
 
-		if (tftpfile->currblock > needblock)	/* seek backwards */
+		/* 
+                 * If we're making a new request, and we see that this
+		 * block isn't (apriori) at the start of the file, then
+		 * close the prior connection nicely rather than letting it
+		 * dangle.
+                 */
+		
+		if (tftpfile->currblock > needblock) {	/* seek backwards */
+			tftp_sendnak(tftpfile);
 			tftp_makereq(tftpfile);	/* no error check, it worked
 						 * for open */
+		}
 
 		while (tftpfile->currblock < needblock) {
 			int res;
@@ -370,6 +465,14 @@
 	/* let it time out ... */
 
 	if (tftpfile) {
+		/*
+                 * If we're not at the last block, then be sure to
+		 * close the connection with a NAK.  If we were at
+		 * the last block, we probably already sent the final
+		 * ACK, which would close the connection.
+                 */
+		if (tftpfile->islastblock == 0)
+			tftp_sendnak(tftpfile);
 		free(tftpfile->path);
 		free(tftpfile);
 	}
--- tftp.c-DIFF ends here ---
>Release-Note:
>Audit-Trail:
>Unformatted:


More information about the freebsd-bugs mailing list