Hello all, the present patch is a continuation of [1] and is also a completion of the task to implement IPv6 support for the TFTP server and client in GNU Inetutils.
Taken together, the present patch and the patch contained in [1] obsolete the suggestions presented in [2]. The changes are fully inspired by the efforts to resolve the very keen observations put forward by Guillem Jover in [3]. The only dependency between [1] and the present patch is the elementary change to use a representation "struct sockaddr_storage" in "libinetutils/tftpsubs.c" (synchnet). No other additions to "libinetutils" are necessary, in contrast to the code in [2]. As I have been forced into more substantial changes of the inner functionality in "src/tftp.c", I should shed some light on the reasons, since they are by no mean obvious. The major obstacle is that the client must keep records of two ports: the server listening port (usually tftp/udp, i.e., 69), and any random TID port that the server care to allocate for actual payload. In the old implementation this was achieved by a repeated need for manipulating the port in a recorded "struct sockaddr_in peeraddr". I blindly kept that mechanism in the first effort [2]. The rewritten code now reserves the recorded structure static struct sockaddr_storage peeraddr for tracking the remote listening socket address. This address is then copied at the beginning of every get/put to static struct sockaddr_storage from in order to send the initial RRQ message. Since the response from the server is detected in recvfrom( ..., &from, ...) the correct TID port is made accessible without destroying "peeraddr". It is important to have nak() respond at this TID, thus using "from". This motivates the upgrading of the local variable "from" in recvfile() and send_file() into now being static of file scope. In the process some improved tracking of "peeraddrlen" and "fromlen" copes with the need in BSD of exact sizes in select system calls. The parsing of arguments of the form "host:file", demanding the possibility to use numerical IPv6 addresses for the host part, made it necessary to look for the rightmost ':' in such combined arguments, thus the migration to strrchr(3) in two instances. The remaining changes ought to be more or less obvious. Best regards, Mats E A [1] http://lists.gnu.org/archive/html/bug-inetutils/2010-09/msg00026.html [2] http://lists.gnu.org/archive/html/bug-inetutils/2010-08/msg00083.html [3] http://lists.gnu.org/archive/html/bug-inetutils/2010-09/msg00006.html
From 1e5db504b478265ffcfb535388bfbe7365bad6b4 Mon Sep 17 00:00:00 2001 From: Mats Erik Andersson <deb...@gisladisker.se> Date: Fri, 17 Sep 2010 18:20:49 +0200 Subject: [PATCH] src/tftp.c: Migration to IPv6 supporting code. --- ChangeLog | 15 +++++ src/tftp.c | 181 +++++++++++++++++++++++++++++++++++------------------------- 2 files changed, 121 insertions(+), 75 deletions(-) diff --git a/ChangeLog b/ChangeLog index 9cdbaaa..ca5bc5f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,20 @@ 2010-09-12 Mats Erik Andersson <g...@gisladisker.se> + * src/tftp.c: Implementation of IPv6 support. Consistent use + of "struct sockaddr_storage". + (from, fromlen): New static variables of file scope. + (port, service): String value "service" replaces numeric "port". + (resolve_name): New signature adding service string. Coding now + uses getaddrinfo(3). Host and service are resolved in one go. + (send_file, put_file): Splitting into "peeraddr" and "from" + keeps track of the remotely allocated TID port. + (send_file, get_file): Explicit port manipulation is removed. + (put, get): Likewise. + (put, get): Split on rightmost colon when hosts are involved. + (nak): Message is sent to "from", being the most recent address. + +2010-09-12 Mats Erik Andersson <g...@gisladisker.se> + * libinetutils/tftpsubs.c (synchnet): Upgrade the variable "from" to be "struct sockaddr_storage". * src/tftpd.c: Upgrade all "sockaddr_in" to "sockaddr_storage". diff --git a/src/tftp.c b/src/tftp.c index 6515976..09671df 100644 --- a/src/tftp.c +++ b/src/tftp.c @@ -97,9 +97,13 @@ static void tpacket (const char *, struct tftphdr *, int); static int rexmtval = TIMEOUT; static int maxtimeout = 5 * TIMEOUT; -static struct sockaddr_in peeraddr; /* filled in by main */ -static int f; /* the opened socket */ -static short port; +static struct sockaddr_storage peeraddr; /* filled in by main */ +static socklen_t peeraddrlen; +static struct sockaddr_storage from; /* remote address with TID */ +static socklen_t fromlen; + +static int f = -1; /* the opened socket */ +static char service[NI_MAXSERV] = "tftp"; /* string for port selection */ static int trace; static int verbose; static int connected; @@ -220,7 +224,7 @@ static struct argp argp = {argp_options, parse_opt, args_doc, doc}; int main (int argc, char *argv[]) { - struct sockaddr_in sin; + struct sockaddr_storage sin; set_program_name (argv[0]); iu_argp_init ("tftp", default_program_authors); @@ -232,19 +236,7 @@ main (int argc, char *argv[]) fprintf (stderr, "tftp: udp/tftp: unknown service\n"); exit (EXIT_FAILURE); } - f = socket (AF_INET, SOCK_DGRAM, 0); - if (f < 0) - { - perror ("tftp: socket"); - exit (EXIT_FAILURE); - } - memset (&sin, 0, sizeof (sin)); - sin.sin_family = AF_INET; - if (bind (f, (struct sockaddr *) &sin, sizeof (sin)) < 0) - { - perror ("tftp: bind"); - exit (EXIT_FAILURE); - } + strcpy (mode, "netascii"); signal (SIGINT, intr); if (hostport_argc > 1) @@ -270,28 +262,64 @@ char *hostname; RESOLVE_NOT_RESOLVED name is not resolved and ALLOW_NULL is true */ static int -resolve_name (char *name, int allow_null) +resolve_name (char *name, char *serv, int allow_null) { - struct hostent *hp = gethostbyname (name); - if (hp == NULL) + int rc; + struct sockaddr_storage ss; + struct addrinfo hints, *ai, *aiptr; + + memset (&hints, 0, sizeof (hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_CANONNAME; +#ifdef AI_ADDRCONFIG + hints.ai_flags |= AI_ADDRCONFIG; +#endif + + rc = getaddrinfo (name, serv, &hints, &aiptr); + if (rc) { if (allow_null) return RESOLVE_NOT_RESOLVED; - fprintf (stderr, "tftp: %s: ", name); - herror ((char *) NULL); + fprintf (stderr, "tftp: %s: %s\n", name, gai_strerror (rc)); return RESOLVE_FAIL; } - else if (hp->h_length != sizeof peeraddr.sin_addr) + + if (f >= 0) { - fprintf (stderr, "tftp: resolving %s returns unexpected length", name); - return RESOLVE_FAIL; + close (f); + f = -1; } - memcpy (&peeraddr.sin_addr, hp->h_addr, hp->h_length); - peeraddr.sin_family = hp->h_addrtype; - connected = 1; - free (hostname); - hostname = xstrdup (hp->h_name); - return RESOLVE_OK; + + for (ai = aiptr; ai; ai = ai->ai_next) + { + f = socket (ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (f < 0) + continue; + + memset(&ss, 0, sizeof (ss)); + ss.ss_family = ai->ai_family; + if (bind (f, (struct sockaddr *) &ss, ai->ai_addrlen)) + { + close (f); + f = -1; + continue; + } + + /* Successfully resolved hostname. */ + memcpy (&peeraddr, ai->ai_addr, ai->ai_addrlen); + peeraddrlen = ai->ai_addrlen; + connected = 1; + free (hostname); + hostname = xstrdup (ai->ai_canonname); + break; + } + + freeaddrinfo(aiptr); + if (ai == NULL) + return RESOLVE_FAIL; + else + return RESOLVE_OK; } /* Prompt for more arguments from the user with PROMPT, putting the results @@ -325,38 +353,21 @@ setpeer (int argc, char *argv[]) return; } - switch (resolve_name (argv[1], 1)) + if (argc == 3) + { + strncpy (service, argv[2], sizeof (service)); + service[sizeof (service) - 1] = '\0'; + } + + switch (resolve_name (argv[1], service, 0)) { case RESOLVE_OK: break; case RESOLVE_FAIL: return; - - case RESOLVE_NOT_RESOLVED: - peeraddr.sin_family = AF_INET; - peeraddr.sin_addr.s_addr = inet_addr (argv[1]); - if (peeraddr.sin_addr.s_addr == -1) - { - connected = 0; - printf ("%s: unknown host\n", argv[1]); - return; - } - hostname = xstrdup (argv[1]); } - port = sp->s_port; - if (argc == 3) - { - port = atoi (argv[2]); - if (port < 0) - { - printf ("%s: bad port number\n", argv[2]); - connected = 0; - return; - } - port = htons (port); - } connected = 1; } @@ -461,9 +472,12 @@ put (int argc, char *argv[]) return; } cp = argv[argc - 1]; - targ = strchr (cp, ':'); + /* The rightmost colon is used, since colon + * appears in numerical IPv6 addresses. + */ + targ = strrchr (cp, ':'); *targ++ = 0; - if (resolve_name (cp, 0) != RESOLVE_OK) + if (resolve_name (cp, service, 0) != RESOLVE_OK) return; } if (!connected) @@ -483,7 +497,7 @@ put (int argc, char *argv[]) } if (verbose) printf ("putting %s to %s:%s [%s]\n", cp, hostname, targ, mode); - peeraddr.sin_port = port ? port : sp->s_port; + send_file (fd, targ, mode); return; } @@ -503,7 +517,7 @@ put (int argc, char *argv[]) } if (verbose) printf ("putting %s to %s:%s [%s]\n", argv[n], hostname, targ, mode); - peeraddr.sin_port = port ? port : sp->s_port; + send_file (fd, targ, mode); } } @@ -545,13 +559,16 @@ get (int argc, char *argv[]) } for (n = 1; n < argc; n++) { - src = strchr (argv[n], ':'); + /* The rightmost colon is used, since colon + * appears in numerical IPv6 addresses. + */ + src = strrchr (argv[n], ':'); if (src == NULL) src = argv[n]; else { *src++ = 0; - if (resolve_name (argv[n], 0) != RESOLVE_OK) + if (resolve_name (argv[n], service, 0) != RESOLVE_OK) continue; } @@ -568,7 +585,7 @@ get (int argc, char *argv[]) if (verbose) printf ("getting from %s:%s to %s [%s]\n", hostname, src, cp, mode); - peeraddr.sin_port = port ? port : sp->s_port; + recvfile (fd, src, mode); break; } @@ -582,7 +599,7 @@ get (int argc, char *argv[]) } if (verbose) printf ("getting from %s:%s to %s [%s]\n", hostname, src, cp, mode); - peeraddr.sin_port = port ? port : sp->s_port; + recvfile (fd, src, mode); } } @@ -832,8 +849,6 @@ send_file (int fd, char *name, char *mode) register int n; volatile int block, size, convert; volatile unsigned long amount; - struct sockaddr_in from; - socklen_t fromlen; FILE *file; startclock (); /* start stat's clock */ @@ -844,6 +859,13 @@ send_file (int fd, char *name, char *mode) block = 0; amount = 0; + /* The server is first contacted at the client selected port. + * Later packet exchange will use an updated address reflecting + * the port allocated at the server end. + */ + memcpy(&from, &peeraddr, peeraddrlen); + fromlen = peeraddrlen; + signal (SIGALRM, timer); do { @@ -868,7 +890,7 @@ send_file (int fd, char *name, char *mode) if (trace) tpacket ("sent", dp, size + 4); n = sendto (f, (const char *) dp, size + 4, 0, - (struct sockaddr *) &peeraddr, sizeof (peeraddr)); + (struct sockaddr *) &from, fromlen); if (n != size + 4) { perror ("tftp: sendto"); @@ -882,6 +904,7 @@ send_file (int fd, char *name, char *mode) do { fromlen = sizeof (from); + /* Receive data and update remote port. */ n = recvfrom (f, ackbuf, sizeof (ackbuf), 0, (struct sockaddr *) &from, &fromlen); } @@ -892,7 +915,7 @@ send_file (int fd, char *name, char *mode) perror ("tftp: recvfrom"); goto abort; } - peeraddr.sin_port = from.sin_port; /* added */ + if (trace) tpacket ("received", ap, n); /* should verify packet came from server */ @@ -945,8 +968,6 @@ recvfile (int fd, char *name, char *mode) register int n; volatile int block, size, firsttrip; volatile unsigned long amount; - struct sockaddr_in from; - socklen_t fromlen; FILE *file; volatile int convert; /* true if converting crlf -> lf */ @@ -959,6 +980,13 @@ recvfile (int fd, char *name, char *mode) firsttrip = 1; amount = 0; + /* The server is first contacted at the client selected port. + * Later packet exchange will use an updated address reflecting + * the port allocated at the server end. + */ + memcpy(&from, &peeraddr, peeraddrlen); + fromlen = peeraddrlen; + signal (SIGALRM, timer); do { @@ -980,8 +1008,8 @@ recvfile (int fd, char *name, char *mode) send_ack: if (trace) tpacket ("sent", ap, size); - if (sendto (f, ackbuf, size, 0, (struct sockaddr *) &peeraddr, - sizeof (peeraddr)) != size) + if (sendto (f, ackbuf, size, 0, + (struct sockaddr *) &from, fromlen) != size) { alarm (0); perror ("tftp: sendto"); @@ -995,6 +1023,7 @@ recvfile (int fd, char *name, char *mode) do { fromlen = sizeof (from); + /* Receive data and update remote port. */ n = recvfrom (f, (char *) dp, PKTSIZE, 0, (struct sockaddr *) &from, &fromlen); } @@ -1006,7 +1035,7 @@ recvfile (int fd, char *name, char *mode) perror ("tftp: recvfrom"); goto abort; } - peeraddr.sin_port = from.sin_port; /* added */ + if (trace) tpacket ("received", dp, n); /* should verify client address */ @@ -1049,7 +1078,7 @@ recvfile (int fd, char *name, char *mode) abort: /* ok to ack, since user */ ap->th_opcode = htons ((u_short) ACK); /* has seen err msg */ ap->th_block = htons ((u_short) block); - sendto (f, ackbuf, 4, 0, (struct sockaddr *) &peeraddr, sizeof (peeraddr)); + sendto (f, ackbuf, 4, 0, (struct sockaddr *) &from, fromlen); write_behind (file, convert); /* flush last buffer */ fclose (file); stopclock (); @@ -1120,8 +1149,10 @@ nak (int error) length = strlen (pe->e_msg) + 4; if (trace) tpacket ("sent", tp, length); - if (sendto (f, ackbuf, length, 0, (struct sockaddr *) &peeraddr, - sizeof (peeraddr)) != length) + + /* Must send NAK to remote host at port of value TID! */ + if (sendto (f, ackbuf, length, 0, + (struct sockaddr *) &from, fromlen) != length) perror ("nak"); } -- 1.7.0
signature.asc
Description: Digital signature