Writing

Feed Software, technology, sysadmin war stories, and more.

Sunday, October 20, 2013

ping and inet_aton, revisited

Last year, I wrote about dissecting ping and glibc to find out why the one on my system (and many others) supports things like 192.168.01234 as a valid address. If you haven't read that post (or that chapter in the second book), go have a look for context.

Anyway, it came up again today: someone linked to a big number on Hacker News and said it was a valid IPv4 address. I commented, linking back to my old post to help explain why it works... sometimes. For at least one person, this wasn't good enough, and so I clarified that it's up to the tool and perhaps even the system's libraries.

I made this point back in the original post, too: that your ping and C library implementation will likely affect whether this works or not. For most people, I suspect they'll be on a BSD or Linux stack, and it will work as advertised. So, when exactly will it not work? It took some digging, but I came up with some scenarios: dietlibc and uClibc. They both have their own inet_aton implementations, and they have their own takes on the weird corner cases.

Try compiling and running this on your system:

#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <arpa/inet.h>
 
/* from uClibc 0.9.33, which is licensed under LGPL v2.1 *
 * source file: libc/inet/addr.c                         *
 * sha1 sum: d79acfd917172f4644ab8ac444578b962cdcaeda    */
int uclibc_inet_aton(const char *cp, struct in_addr *addrptr)
{
  in_addr_t addr;
  int value;
  int part;
 
  if (cp == NULL) {
    return 0;
  }
 
  addr = 0;
  for (part = 1; part <= 4; part++) {
 
    if (!isdigit(*cp))
      return 0;
 
    value = 0;
    while (isdigit(*cp)) {
      value *= 10;
      value += *cp++ - '0';
      if (value > 255)
        return 0;
    }
 
    if (part < 4) {
      if (*cp++ != '.')
        return 0;
    } else {
      char c = *cp++;
      if (c != '\0' && !isspace(c))
        return 0;
    }
 
    addr <<= 8;
    addr |= value;
  }
 
  /*  W. Richard Stevens in his book UNIX Network Programming,
   *  Volume 1, second edition, on page 71 says:
   *
   *  An undocumented feature of inet_aton is that if addrptr is
   *  a null pointer, the function still performs it validation
   *  of the input string, but does not store the result.
   */
  if (addrptr) {
    addrptr->s_addr = htonl(addr);
  }
 
  return 1;
}
 
/* from dietlibc 0.33, which is licensed under GPL v2  *
 * source file: libcruft/inet_aton.c                   *
 * sha1 sum: 1aa2676401b8c5bb86fc21bd4889dcc3766137ef  */
int dietlibc_inet_aton(const char *cp, struct in_addr *inp) {
  int i;
  unsigned int ip=0;
  char *tmp=(char*)cp;
  for (i=24; ;) {
    long j;
    j=strtoul(tmp,&tmp,0);
    if (*tmp==0) {
      ip|=j;
      break;
    }
    if (*tmp=='.') {
      if (j>255) return 0;
      ip|=(j<<i);
      if (i>0) i-=8;
      ++tmp;
      continue;
    }
    return 0;
  }
  inp->s_addr=htonl(ip);
  return 1;
}
 
void run_test(const char* input, int (*func)(const char*, struct in_addr*),
              const char* label) {
  struct in_addr addr = { 0 };
  int ret = func(input, &addr);
 
  printf("%30s: ret=%d addr=%08x\n", label, ret, addr.s_addr);
}
 
int main(int argc, char** argv) {
  if (argc != 2) {
    printf("usage: %s <address to test via inet_aton>\n", argv[0]);
    exit(1);
  }
 
  printf("input: [%s]\n\n", argv[1]);
 
  run_test(argv[1], inet_aton, "stock system inet_aton");
  run_test(argv[1], dietlibc_inet_aton, "dietlibc inet_aton");
  run_test(argv[1], uclibc_inet_aton, "uclibc inet_aton");
 
  return 0;
}

Try some of these and watch the differences:

You get the idea. Some will work and some won't. The implication is clear: the same ping program could work or fail with those weirdo addresses depending on which C library you happened to use.

Now imagine using a different ping (or any other program which intends to speak TCP/IP) which doesn't use inet_aton at all. Will it support all of these strange addresses? Considering that you have to deliberately act to support this behavior, I'd expect it to be missing more often than not.