Skip to content

Instantly share code, notes, and snippets.

@arr2036
Created February 28, 2014 14:31
Show Gist options
  • Save arr2036/9272050 to your computer and use it in GitHub Desktop.
Save arr2036/9272050 to your computer and use it in GitHub Desktop.
/** IPv6 address manipulation utility
*
* This utility is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*
* @note Much of this code was derived from code in the FreeRADIUS project
* @note hence using the GPL.
*/
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
/* May be #include <in.h> on some systems */
#include <netinet/in.h>
#include <arpa/inet.h>
static char *name; //!< Program name
/* lets pretend 128bit integers are ANSI C */
#define uint128_t __uint128_t
/* These should cover clang and gcc */
#if !defined(LITTLE_ENDIAN) && !defined(BIG_ENDIAN)
# if defined(__LITTLE_ENDIAN__) || \
(defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
# define LITTLE_ENDIAN 1
# elif defined(__BIG_ENDIAN__) || \
(defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))
# define BIG_ENDIAN 1
# else
# error Failed determining endianness of system
# endif
#endif
/* abcd efgh -> dcba hgfe -> hgfe dcba */
#ifdef LITTLE_ENDIAN
# define ntohll(x) (((uint64_t)ntohl((uint32_t)(x >> 32))) | (((uint64_t)ntohl(((uint32_t) x)) << 32)))
# define ntohlll(x) (((uint128_t)ntohll((uint64_t)(x >> 64))) | (((uint128_t)ntohll(((uint64_t) x)) << 64)))
#else
# define ntohll(x) x
# define ntohlll(x) x
#endif
#define htonll(x) ntohll(x)
#define htonlll(x) htohlll(x)
/** Write 128bit unsigned integer to buffer
*
* @author Alexey Frunze
*
* @param out where to write result to.
* @param outlen size of out.
* @param num 128 bit integer.
*/
static size_t prints_uint128(char *out, size_t outlen, uint128_t const num)
{
char buff[128 / 3 + 1 + 1];
uint64_t n[2];
char *p = buff;
int i;
memset(buff, '0', sizeof(buff) - 1);
buff[sizeof(buff) - 1] = '\0';
memcpy(n, &num, sizeof(n));
for (i = 0; i < 128; i++) {
ssize_t j;
int carry;
carry = (n[1] >= 0x8000000000000000);
// Shift n[] left, doubling it
n[1] = ((n[1] << 1) & 0xffffffffffffffff) + (n[0] >= 0x8000000000000000);
n[0] = ((n[0] << 1) & 0xffffffffffffffff);
// Add s[] to itself in decimal, doubling it
for (j = sizeof(buff) - 2; j >= 0; j--) {
buff[j] += buff[j] - '0' + carry;
carry = (buff[j] > '9');
if (carry) {
buff[j] -= 10;
}
}
}
while ((*p == '0') && (p < &buff[sizeof(buff) - 2])) {
p++;
}
return strlcpy(out, p, outlen);
}
/** Return binary data as hex
*
* @param hex Where to write the hex output (must be double inlen).
* @param bin to convert.
* @param inlen Length of bin data.
* @return inlen * 2.
*/
static size_t bin_to_hex(char *hex, uint8_t const *bin, size_t inlen)
{
static char const *hextab = "0123456789abcdef";
size_t i;
for (i = 0; i < inlen; i++) {
hex[0] = hextab[((*bin) >> 4) & 0x0f];
hex[1] = hextab[*bin & 0x0f];
hex += 2;
bin++;
}
*hex = '\0';
return inlen * 2;
}
/** Mask off the host portion of an IPv6 address
*
* @param[in] ipaddr to mask.
* @param[in] prefix (in bits).
* @return in6_addr with host bits set to 0.
*/
static struct in6_addr ipv6_mask(struct in6_addr const *ipaddr, uint8_t prefix)
{
uint64_t const *p = (uint64_t const *) ipaddr;
uint64_t ret[2], *o = ret;
if (prefix > 128) {
prefix = 128;
}
/* Short circuit */
if (prefix == 128) {
return *ipaddr;
}
if (prefix >= 64) {
prefix -= 64;
*o++ = 0xffffffffffffffffULL & *p++;
} else {
ret[1] = 0;
}
*o = htonll(~((0x0000000000000001ULL << (64 - prefix)) - 1)) & *p;
return *(struct in6_addr *) &ret;
}
/** Parse a 'presentation' format IPv6 address into it's binary form
*
* @param[out] out_ipaddr Where to write the resulting address.
* @param[out] out_prefix Where to write the prefix length.
* @param[in] value The value to parse.
* @return 0 on success -1 on failure.
*/
static int ipv6_parse(struct in6_addr *out_ipaddr, uint8_t *out_prefix, char const *value)
{
char const *p, *addr = value;
char buffer[INET6_ADDRSTRLEN + 4], *eptr;
struct in6_addr tmp;
unsigned long prefix;
p = strchr(value, '/');
if (p) {
if ((p - value) >= sizeof(buffer)) {
fprintf(stderr, "Invalid IPv6 prefix string \"%s\"", value);
return -1;
}
memcpy(buffer, value, p - value);
buffer[p - value] = '\0';
addr = buffer;
}
/* inet_pton is the standard POSIX function for parsing IP addresses */
if (inet_pton(AF_INET6, addr, &tmp) <= 0) {
fprintf(stderr, "Failed to parse IPv6 address string \"%s\"", value);
return -1;
}
/* ipv6 prefix, set the host bits to 0 */
if (p) {
prefix = strtoul(p + 1, &eptr, 10);
if ((prefix > 128) || *eptr) {
fprintf(stderr, "Failed to parse IPv6 address string prefix \"%s\"", p + 1);
return -1;
}
if (prefix < 128) {
*out_ipaddr = ipv6_mask(&tmp, (uint8_t) prefix);
*out_prefix = (uint8_t) prefix;
return 0;
}
}
/* single ipv6_address with no range */
*out_prefix = 128;
*out_ipaddr = tmp;
return 0;
}
static void usage(int status)
{
FILE *output = status ? stderr : stdout;
fprintf(output, "Usage: %s [options] -- <ipv6addr> [ipv6addr]\n", name);
fprintf(output, "options:\n");
fprintf(output, " -w Width of output 8, 16, 32, 64, 128\n");
fprintf(output, " -h This help text\n");
fprintf(output, " -H Hexify output\n");
exit(status);
}
int main(int argc, char *argv[])
{
int opt;
unsigned long width = 128;
bool hexify = false;
name = argv[0];
while ((opt = getopt(argc, argv, "w:hH")) != EOF) {
switch (opt) {
case 'w':
width = strtoul(optarg, NULL, 10);
switch (width) {
case 8:
case 16:
case 32:
case 64:
case 128:
break;
default:
fprintf(stderr, "Width %lu not supported\n", width);
usage(64);
}
break;
case 'H':
hexify = true;
break;
case 'h':
usage(0);
break;
default:
usage(64);
}
}
if (optind >= argc) {
fprintf(stderr, "At least one ipv6 address must be specified\n");
usage(64);
}
while (optind < argc) {
struct in6_addr addr;
uint8_t prefix;
uint128_t mask = 0, masked, shifted;
int i;
char buffer[1024];
if (ipv6_parse(&addr, &prefix, argv[optind]) < 0) {
exit(1);
}
mask = ~mask;
mask >>= (128 - width);
shifted = *(uint128_t *)&addr;
for (i = (128 / width); i > 0; i--) {
masked = shifted & mask;
if (!hexify) {
uint128_t host;
switch (width) {
case 128:
host = ntohlll(masked);
break;
case 64:
host = ntohll(masked);
break;
case 32:
host = ntohl(masked);
break;
case 16:
host = ntohs(masked);
break;
case 8:
host = masked;
break;
}
prints_uint128(buffer, sizeof(buffer), host);
} else {
bin_to_hex(buffer, (uint8_t *) &masked, width / 8);
}
shifted >>= width;
fprintf(stdout, "%s", buffer);
if (i > 1) fputs(" ", stdout);
}
fputs("\n", stdout);
optind++;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment