~jamsek/netcalc

3a58488ba84f3602bef99905cff2109153a3de9d — Mark Jamsek 11 months ago master
Initial commit
A  => README.md +5 -0
@@ 1,5 @@
# netcalc
IPv4 & IPv6 CIDR Subnet Calculator

required:   
    - C math library for compilation

A  => macos/netcalc/doc/netcalc.1 +86 -0
@@ 1,86 @@
.\"
.\" Copyright (c) 2019 Mark Jamsek <mark@jamsek.com>
.\"
.\" Permission to use, copy, modify, and distribute this software for any
.\" purpose with or without fee is hereby granted, provided that the above
.\" copyright notice and this permission notice appear in all copies.
.\"
.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
.\"
.Dd $Mdocdate: September 25 2019 $
.Dt NETCALC 1
.Os
.Sh NAME
.Nm netcalc
.Nd IPv4 & IPv6 CIDR Subnet Calculator
.Sh SYNOPSIS
.Nm netcalc
.Op Fl s Ar subnets
.Ar ipaddr/prefix
.Sh DESCRIPTION
With an argument specifying an
.Ar ipaddr
in CIDR notation,
.Nm
extrapolates and returns network information for the
corresponding network and subnet(s). Command line
supplied argument specifies whether subnet division
is requested. If no arguments are given, usage details
are displayed.
.Pp
.Nm
returns the following information:
.Bd -literal -offset indent
Network address
Address range (i.e., host min/max)
Broadcast address
Subnet mask
Address block (i.e., useable hosts)
.Ed
.Pp
The available options are as follows:
.Bl -tag -width Dssmacro=value
.It Fl s Ar subnets
Specify number of subnets requested.
.El
.Sh EXAMPLES
To request four subnets from the 
.Pa /24
block of 
.Pa 192.168.100.20
run:
.Pp
.Dl $ Nm netcalc -s4 192.168.100.20/24
.Pp
To extract network information for a given
.Pa IPv6
address
run:
.Pp
.Dl $ Nm netcalc b084:4ce2:eb94:ded1:e5b0:a8be:2f22:b9d6/110
.Pp
To find an address group within the
.Pa 10.0.0.10
to
.Pa 10.0.0.110
range run:
.Pp
.Dl $ Nm netcalc 10.0.0.10/25
.Pp
.Sh HISTORY
The
.Nm
utility was first developed in 2018 and expanded to include IPv6
in 2019.
.Sh AUTHORS
The
.Nm
utility was written by
.An Mark Jamsek Aq Mt mark@jamsek.com .

A  => macos/netcalc/include/netcalc.h +95 -0
@@ 1,95 @@
/*
 * Copyright (c) 2019 Mark Jamsek <mark@jamsek.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <arpa/inet.h>
#include <netinet/in.h>

#include <ctype.h>
#include <errno.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#if defined(HAVE_BYTESWAP_H)
#   include <byteswap.h>
#   define bswap16(n) bswap_16(n)
#   define bswap32(n) bswap_32(n)
#   define bswap64(n) bswap_64(n)
#else
#  if defined(HAVE_SYS_ENDIAN)
#   include <sys/_endian.h>
#else
static inline uint16_t
bswap16(uint16_t n)
{
        return (n << 8) | (n >> 8);
}

static inline uint32_t
bswap32(uint32_t n)
{
        return (bswap16(n & 0xffff) << 16) | (bswap16(n >> 16));
}

static inline uint_fast64_t
bswap64(uint_fast64_t n)
{
        return (((uint_fast64_t)bswap32(n & 0xffffffff)) << 32)
            | (bswap32(n >> 32));
}
  #endif /* HAVE_SYS_ENDIAN */
#endif  /* HAVE_BYTESWAP_H */

#ifndef AF_INET6
# define AF_INET6 24
#endif

/* IPv4 constants */
#define ADDRSPACE   32       /* 32-bit ipv4 address space */
#define BYTE_LEN     8       /* byte length for bit shifting of octets */
#define OCTET_LEN    4       /* number of octets (integers) in ipaddr */
#define OCTET_MIN    0       /* minimum IP octet value */
#define OCTET_MAX  255       /* maximum IP octet value */

/* IPv6 constants */
#define ADDRSPACE6 128       /* 128-bit ipv6 address space */
#define IPV6STRLEN  39       /* number of characters in full IPv6 address */
#define IPV6HEXLEN   4       /* number of hexadecimal characters per hextet */

#define PROGRAM      "netcalc 0.3 IPv4 & IPv6 CIDR Subnet Calculator"

/*
 * Data type for 128-bit IPv6 address
 */
struct split {
        uint_fast64_t   first64;
        uint_fast64_t   last64;
};

__dead void         usage(char *);

int                 getipint(int *);
int                 getmaskint(int);
void                getoctets(int, int *);
void                ipv6addr_info(unsigned char *, int,
                        unsigned char *, unsigned char *);
void                makesubnets(int, int, int, int *);
void                parseipv6(char *);
void                printsep(uintmax_t n);
void                showinfo(int, int, int *);
void                validateip(char *, int *, int *);
\ No newline at end of file

A  => macos/netcalc/src/Makefile +27 -0
@@ 1,27 @@
IDIR=			../include
CC=				cc
CFLAGS=			-I$(IDIR)
CFLAGS+=		-Werror -Wall

ODIR=			obj
LDIR=			../lib

LIBS=			-lm

_DEPS=			netcalc.h
DEPS=			$(patsubst %,$(IDIR)/%,$(_DEPS))

_OBJ=			main.o netcalc.o 
OBJ=			$(patsubst %,$(ODIR)/%,$(_OBJ))


$(ODIR)/%.o: %.c $(DEPS)
	$(CC) -c -o $@ $< $(CFLAGS)

netcalc: $(OBJ)
	$(CC) -o $@ $^ $(CFLAGS) $(LIBS)

.PHONY: clean

clean:
	rm -i $(ODIR)/*.o *~ core $(INCDIR)/*~
\ No newline at end of file

A  => macos/netcalc/src/main.c +79 -0
@@ 1,79 @@
/*
 * Copyright (c) 2019 Mark Jamsek <mark@jamsek.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "netcalc.h"

/*
 * Find and display network details from user specified IPv4 or IPv6 address in
 * CIDR notation, including: network address; host min/max; broadcast & netmask
 * addresses; and useable hosts per subnet. Optionally, compute required subnet
 * bits and resultant subnets subsequent to user request for multiple subnets.
 */
int
main(int argc, char *argv[])
{
        int         oct[OCTET_LEN];
        int         idx, ipint, o1, o2, o3, o4, p;
        int         offset = 0, prefix = 0;
        char        *ptr, *subnetreq = NULL;
        
        if (argc < 2)
                usage(PROGRAM);
        
        /* Check version of address or if subnet (-s) argument is given */
        for (idx = 0; idx < argc; ++idx) {
                if (strchr(argv[idx], ':') != NULL) {
                        parseipv6(argv[idx]);
                        return 0;
                } else if (strncmp(argv[idx], "-s", 2) == 0) {
                        if (strlen(argv[idx]) == 2 && argc > 3) {
                                subnetreq = argv[idx + 1];
                                if (idx < 2)
                                        offset = idx + 1;
                        } else if (strlen(argv[idx]) > 2 && argc > 2) {
                                subnetreq = &argv[idx][2];  /* Magic to make */
                                if (idx < 2)                /* pre & postfix */
                                        offset = idx;       /* arguments on  */
                        } else                              /* cmd line work */
                                usage("error: insufficient arguments");
                }
        
        }

        /* Ensure ip4addr is provided in correct CIDR notation */
        if (sscanf(argv[1 + offset], "%3d.%3d.%3d.%3d/%2d",
            &o1, &o2, &o3, &o4, &p) != 5)
                usage("error: invalid ipv4 CIDR notation");
        else
                validateip(ptr = strtok(argv[1 + offset], "."), oct, &prefix);
        
        /* Logical AND ip address with netmask to get start address of block */
        ipint = getipint(oct) & getmaskint(prefix);

        printf("\nAddress           :   %d.%d.%d.%d\n",\
            oct[0], oct[1], oct[2], oct[3]);
        showinfo(prefix, ipint, oct);

        /* If subnet arg, check value consists of digits before proceeding */
        if ((subnetreq != NULL && (isdigit(*subnetreq) == 0
            || (strlen(subnetreq) > 1 && isdigit(subnetreq[1]) == 0))))
                usage("error: illegal subnet request value");
        else if (subnetreq != NULL && *subnetreq != '0') {
                printf("[+] subnets requested: %s\n", subnetreq);
                makesubnets(strtol(subnetreq, &ptr, 10), ipint, prefix, oct);
        }
        return 0;
}
\ No newline at end of file

A  => macos/netcalc/src/netcalc.c +342 -0
@@ 1,342 @@
/*
 * Copyright (c) 2019 Mark Jamsek <mark@jamsek.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "netcalc.h"

/* Verify that prefix and ipaddr octets are all integers and in legal range */
void
validateip(char *ipaddr, int *oct, int *prefix)
{       
        for (int idx = 0; ipaddr != NULL; ++idx) {

                /* Validate prefix after collecting four octets */
                if (idx >= OCTET_LEN) {
                        *prefix = strtol(ipaddr, &ipaddr, 10);
                        if ((!(*prefix <= ADDRSPACE && *prefix > 0)
                            || strlen(ipaddr) > 2) || *ipaddr != '\0')
                                usage("error: illegal CIDR prefix");
                } else if (!((oct[idx] = strtol(ipaddr, &ipaddr, 10))
                    >= OCTET_MIN && oct[idx] < OCTET_MAX))
                        usage("error: illegal octet number");
                ipaddr = strtok(NULL, "./");
        }
}

/*
 * Convert CIDR prefix into integer representation of netmask
 */
int
getmaskint(int bits)
{       
        /* Left shift for number of host bits to unset host- & set mask-bits */
        return (~0) << (ADDRSPACE - bits);
}

/*
 * Convert octets of IP address into 32-bit integer representation
 */
int
getipint(int oct[])
{
        int ipint = 0;
        
        /* Pack each octet's bits into int representation of 32-bit address */
        for (int idx = 0; idx < OCTET_LEN; ++idx)
                ipint += oct[idx] << ((BYTE_LEN * (3-idx)));

        return ipint;
}

/*
 * Convert integer representation of IP address into four 8-bit octets
 */
void
getoctets(int ipint, int oct[])
{
        /* Unpack int representation of 32-bit address into 4x 8-bit octets */
        for (int idx = 0; idx < OCTET_LEN; ++idx)
                oct[idx] = (ipint >> ((BYTE_LEN * (3-idx)))) & 0xFF;
}

/*
 * Display IPv4 subnet details to console
 */
void
showinfo(int prefix, int ipint, int oct[])
{
        int         hosts, mask, end;
        
        /* OR start address with hostmask to find end address */
        mask = getmaskint(prefix);
        end = ipint | ~mask;

        /* Print CIDR notation first */
        getoctets(ipint, oct);
        printf("CIDR notation     :   %d.%d.%d.%d/%d\n",
            oct[0], oct[1], oct[2], oct[3], prefix);
        
        /* 
         * (2^host bits) - 1 for useable hosts up to and including /30 block
         * (2^host bits) + 1 in /31–32 blocks. In the former case, adjust host
         * min/max accordingly by adding and subtracting one to and from the
         * start and end addresses, respectively.
         */
        hosts = (prefix < 31) ? ~mask - 1 : ~mask + 1;

        /* Print address range second */
        if (prefix < 31) {
                printf("Address range     :   %d.%d.%d.%d —— ",
                    oct[0], oct[1], oct[2], oct[3] + 1);
                getoctets(end, oct);
                printf("%d.%d.%d.%d\n", oct[0], oct[1], oct[2], oct[3] - 1);
        } else {
                printf("Address range     :   %d.%d.%d.%d —— ",
                    oct[0], oct[1], oct[2], oct[3]);
                getoctets(end, oct);
                printf("%d.%d.%d.%d\n", oct[0], oct[1], oct[2], oct[3]);
        }

        /* Print broadcast address third */
        printf("Broadcast address :   %d.%d.%d.%d\n",
            oct[0], oct[1], oct[2], oct[3]);
        
        /* Print subnet mask and useable hosts last */
        getoctets(mask, oct);
        printf("Subnet mask       :   %d.%d.%d.%d [0x%x]\n",
            oct[0], oct[1], oct[2], oct[3], mask);
        printf("Address block     :   ");
        printsep(hosts);
        printf(" contiguous hosts\n\n");
}

/*
 * If user makes request for multiple subnets, determine bits needed for
 * requested subnets to find subnet mask and compute subnet address range
 */
void
makesubnets(int subnetreq, int ipint, int prefix, int *oct)
{
        int         netmaskbits, subnets, subnetbits;
        
        /* Find minimum bits to make at least number of subnets requested */
        subnetbits = ceil(log2(subnetreq));

        /* Then add subnet bits to original netmask bits to make subnet mask */
        netmaskbits = prefix + subnetbits;

        /* Check total bits required don't exceed 32-bit address space */
        if (netmaskbits >= ADDRSPACE)
                fprintf(stderr, "error: subnet request [%d] too large.\n",
                    subnetreq);
        else {
                /*
                 * Find start address of each subnet by adding total number of
                 * hosts of each preceding subnet to the current start address
                 */
                subnets = pow(2, subnetbits);
                printf("[+] subnets delivered: %d\n\n", subnets);
                for (int i = 0; i < subnets; ++i) {
                        printf("-> subnet %d\n", i + 1);
                        showinfo(netmaskbits,
                            ipint | i << (ADDRSPACE - netmaskbits), oct);
                }
        }
}

/*
 * Parse user supplied IPv6 address
 */
void
parseipv6(char *argv)
{
        unsigned char       endip[sizeof(struct in6_addr)];
        unsigned char       netaddr[sizeof(struct in6_addr)];
        unsigned char       startip[sizeof(struct in6_addr)];
        uintmax_t           blocksize;
        char                endaddr[INET6_ADDRSTRLEN];
        char                startaddr[INET6_ADDRSTRLEN];
        char                *ptr, msg[50];
        int                 addrlen = 0, hext = 0;
        int                 conv = 0, prefix = 0;
        
        errno = 0;

        /* Verify correct slash '/' CIDR format to convert prefix to integer */
        if ((ptr = strchr(&argv[1], '/'))) {
                *ptr++ = '\0';

                /* Check that prefix is an integer */
                prefix = strtol(ptr, &ptr, 10);
                if (errno == ERANGE) {
                        usage("error: overflow");
                } else if (errno != 0) {
                        sprintf(msg, "error: prefix not integer [%s]", ptr);
                        usage(msg);
                } else if (*ptr != '\0') {
                        sprintf(msg, "error: illegal char in prefix [%s]", ptr);
                        usage(msg);
                }
        } else
                usage("error: invalid ipv6 CIDR notation");

        /* Verify prefix is within range */
        if (!(prefix > 0 && prefix <= ADDRSPACE6)) {
                sprintf(msg, "error: prefix outside legal range [%d]", prefix);
                usage(msg);
        }

        /* Convert address character string into network address strucure */
        if ((conv = inet_pton(AF_INET6, argv, netaddr)) == -1)
                perror("error: inet_pton");
        else if (conv == 0)
                usage("error: invalid presentation format");
        
        /* Send network address structure to find start and end addresses */
        ipv6addr_info(netaddr, prefix, startip, endip);

        /* Convert start and end network address structures into strings */
        inet_ntop(AF_INET6, startip, startaddr, INET6_ADDRSTRLEN);
        inet_ntop(AF_INET6, endip, endaddr, INET6_ADDRSTRLEN);

        printf("\nAddress        : %s\n", argv);
        printf("CIDR notation  : %s/%d\n", startaddr, prefix);
        printf("Start address  : %s\n", startaddr);

        /* if ntop() returns shortened IPv6 address, pad with 0xf to end */
        if ((addrlen = strlen(endaddr)) < IPV6STRLEN) {
                hext = ((IPV6STRLEN - addrlen) / IPV6HEXLEN);
                endaddr[strlen(endaddr) - 2] = 0;
                printf("End address    : %s", endaddr);
                for (;hext > 0; --hext)
                        printf(":ffff");
                printf("\n");
        } else
            printf("End address    : %s\n", endaddr);

        /* TODO: consider making lookup table with strings of values 2^64+ */
        if (prefix > (ADDRSPACE6 / 2)) {
                /* 
                 * Print pretty integer of useable hosts if blocksize
                 * fits in 64 bits; otherwise print as power of 2
                 */
                blocksize = pow(2, (ADDRSPACE6 - prefix));
                printf("Block size:    : ");
                printsep(blocksize);
                printf(" contiguous usable hosts\n\n");
        } else
                printf("Block size:    : 2^%d contiguous usable hosts\n\n",\
                    ADDRSPACE6 - prefix);
}

/*
 * Extrapolate start and end of subnet from IPv6 network address structure
 */
void
ipv6addr_info(unsigned char *netaddr, int prefix,
    unsigned char *startip, unsigned char *endip)
{
        struct split            bits;
        uint_fast64_t           first64_swapped = 0, last64_swapped = 0;
        uint_fast64_t           masked = 0, maskbits = 0;
        unsigned char           *endp = endip;
        unsigned char           *startp = startip;
        
        /* Set all 128 bits of IPv6 start and end addresses to 0 */
        memset(startip, 0, sizeof(struct in6_addr));
        memset(endip, 0, sizeof(struct in6_addr));

        /* Copy network address to bits struct for parsing in 64-bit chunks */
        memcpy(&bits, netaddr, sizeof(bits));
        
        /*
         * Only first 64 bits are needed to define subnet if netmask is 64 bits
         * or less because entire second 64-bit chunk belongs to hostmask
         */
        if (prefix <= (ADDRSPACE6 / 2)) {
                /*
                 * Swap first 64-bit chunk to big-endian (network byte) order
                 * and subtract prefix from 64 bits only to define netmask bits
                 */
                first64_swapped = bswap64(bits.first64);
                maskbits = (uint_fast64_t) (~0) << ((ADDRSPACE6 / 2) - prefix);

                /* AND first 64-bit chunk with netmask to get start address */
                masked = bswap64(first64_swapped & maskbits);
                memcpy(startip, &masked, sizeof(uint_fast64_t));

                /* OR first 64-bit chunk with hostmask to get end address */
                masked = bswap64(first64_swapped | ~maskbits);
                memcpy(endip, &masked, sizeof(uint_fast64_t));
                
                return;
        }

        /*
         * If netmask is > 64 bits, subtract prefix from entire 128-bit address
         * space, and swap last 64-bit chunk to big-endian (network) byte order
         */
        last64_swapped = bswap64(bits.last64);
        maskbits = (uint_fast64_t) (~0) << (ADDRSPACE6 - prefix);

        /* Write first 64-bit chunk of address to start & end address string */
        memcpy(startip, &(bits.first64), sizeof(uint_fast64_t));
        memcpy(endip, &(bits.first64), sizeof(uint_fast64_t));

        /* Move pointers forward 64 bits to start of the second 64-bit chunk */
        startp += sizeof(uint_fast64_t);
        endp += sizeof(uint_fast64_t);

        /*
         * AND last 64-bit chunk with netmask to get subnet start address
         * and write to last 64 bits of start address string
         */
        masked = bswap64(last64_swapped & maskbits);
        memcpy(startp, &masked, sizeof(uint_fast64_t));

        /*
         * OR last 64-bit chunk with hostmask to get subnet end address
         * and write to last 64 bits of end address string
         */
        masked = bswap64(last64_swapped | ~maskbits);
        memcpy(endp, &masked, sizeof(uint_fast64_t));
}

/* Use a comma to separate groups of thousands and print pretty numbers */
void
printsep(uintmax_t n) {
        if (n < 1000) {
                printf ("%ju", n);
                return;
        }
        printsep(n / 1000);
        printf(",%03ju", n % 1000);
}

/*
 * Display program usage to console
 */
void
usage(char *msg)
{
        extern char     *__progname;

        fprintf(stderr, "\n%s\nusage: %s [-s subnets] ipaddr/prefix\
            \n e.g.: %s -s 4 192.168.0.1/24\n\
       %s 8c6b:dbfd:5c73:8f14:f815:a4a2:5dab:38b0/110\n\n",
            msg, __progname, __progname, __progname);
        
        exit(1);
}
\ No newline at end of file

A  => netcalc.c +461 -0
@@ 1,461 @@
/*
 * Copyright (c) 2019 Mark Jamsek <mark@jamsek.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <arpa/inet.h>
#include <netinet/in.h>

#include <ctype.h>
#include <errno.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#if defined(HAVE_BYTESWAP_H)
#   include <byteswap.h>
#   define bswap16(n) bswap_16(n)
#   define bswap32(n) bswap_32(n)
#   define bswap64(n) bswap_64(n)
#else
#  if defined(HAVE_SYS_ENDIAN)
#   include <sys/_endian.h>
#else
static inline uint16_t
bswap16(uint16_t n)
{
        return (n << 8) | (n >> 8);
}

static inline uint32_t
bswap32(uint32_t n)
{
        return (bswap16(n & 0xffff) << 16) | (bswap16(n >> 16));
}

static inline uint_fast64_t
bswap64(uint_fast64_t n)
{
        return (((uint_fast64_t)bswap32(n & 0xffffffff)) << 32)
            | (bswap32(n >> 32));
}
  #endif /* HAVE_SYS_ENDIAN */
#endif  /* HAVE_BYTESWAP_H */

#ifndef AF_INET6
# define AF_INET6 24
#endif

/* IPv4 constants */
#define ADDRSPACE   32       /* 32-bit ipv4 address space */
#define BYTE_LEN     8       /* byte length for bit shifting of octets */
#define OCTET_LEN    4       /* number of octets (integers) in ipaddr */
#define OCTET_MIN    0       /* minimum IP octet value */
#define OCTET_MAX  255       /* maximum IP octet value */

/* IPv6 constants */
#define ADDRSPACE6 128       /* 128-bit ipv6 address space */
#define IPV6STRLEN  39       /* number of characters in full IPv6 address */
#define IPV6HEXLEN   4       /* number of hexadecimal characters per hextet */

#define PROGRAM      "netcalc 0.3 IPv4 & IPv6 CIDR Subnet Calculator"

__dead void         usage(char *);

int                 getipint(int *);
int                 getmaskint(int);
void                getoctets(int, int *);
void                ipv6addr_info(unsigned char *, int,
                        unsigned char *, unsigned char *);
void                makesubnets(int, int, int, int *);
void                parseipv6(char *);
void                printsep(uintmax_t n);
void                showinfo(int, int, int *);
void                validateip(char *, int *, int *);

/*
 * Data type for 128-bit IPv6 address
 */
struct split {
        uint_fast64_t   first64;
        uint_fast64_t   last64;
};

/*
 * Find and display network details from user specified IPv4 or IPv6 address in
 * CIDR notation, including: network address; host min/max; broadcast & netmask
 * addresses; and useable hosts per subnet. Optionally, compute required subnet
 * bits and resultant subnets subsequent to user request for multiple subnets.
 */
int
main(int argc, char *argv[])
{
        int         oct[OCTET_LEN];
        int         idx, ipint, o1, o2, o3, o4, p;
        int         offset = 0, prefix = 0;
        char        *ptr, *subnetreq = NULL;
        
        if (argc < 2)
                usage(PROGRAM);
        
        /* Check version of address or if subnet (-s) argument is given */
        for (idx = 0; idx < argc; ++idx) {
                if (strchr(argv[idx], ':') != NULL) {
                        parseipv6(argv[idx]);
                        return 0;
                } else if (strncmp(argv[idx], "-s", 2) == 0) {
                        if (strlen(argv[idx]) == 2 && argc > 3) {
                                subnetreq = argv[idx + 1];
                                if (idx < 2)
                                        offset = idx + 1;
                        } else if (strlen(argv[idx]) > 2 && argc > 2) {
                                subnetreq = &argv[idx][2];  /* Magic to make */
                                if (idx < 2)                /* pre & postfix */
                                        offset = idx;       /* arguments on  */
                        } else                              /* cmd line work */
                                usage("error: insufficient arguments");
                }
        
        }

        /* Ensure ip4addr is provided in correct CIDR notation */
        if (sscanf(argv[1 + offset], "%3d.%3d.%3d.%3d/%2d",
            &o1, &o2, &o3, &o4, &p) != 5)
                usage("error: invalid ipv4 CIDR notation");
        else
                validateip(ptr = strtok(argv[1 + offset], "."), oct, &prefix);
        
        /* Logical AND ip address with netmask to get start address of block */
        ipint = getipint(oct) & getmaskint(prefix);

        printf("\nAddress           :   %d.%d.%d.%d\n",\
            oct[0], oct[1], oct[2], oct[3]);
        showinfo(prefix, ipint, oct);

        /* If subnet arg, check value consists of digits before proceeding */
        if ((subnetreq != NULL && (isdigit(*subnetreq) == 0
            || (strlen(subnetreq) > 1 && isdigit(subnetreq[1]) == 0))))
                usage("error: illegal subnet request value");
        else if (subnetreq != NULL && *subnetreq != '0') {
                printf("[+] subnets requested: %s\n", subnetreq);
                makesubnets(strtol(subnetreq, &ptr, 10), ipint, prefix, oct);
        }
        return 0;
}

/* Verify that prefix and ipaddr octets are all integers and in legal range */
void
validateip(char *ipaddr, int *oct, int *prefix)
{       
        for (int idx = 0; ipaddr != NULL; ++idx) {

                /* Validate prefix after collecting four octets */
                if (idx >= OCTET_LEN) {
                        *prefix = strtol(ipaddr, &ipaddr, 10);
                        if ((!(*prefix <= ADDRSPACE && *prefix > 0)
                            || strlen(ipaddr) > 2) || *ipaddr != '\0')
                                usage("error: illegal CIDR prefix");
                } else if (!((oct[idx] = strtol(ipaddr, &ipaddr, 10))
                    >= OCTET_MIN && oct[idx] < OCTET_MAX))
                        usage("error: illegal octet number");
                ipaddr = strtok(NULL, "./");
        }
}

int
getmaskint(int bits)
{       
        /* Left shift for number of host bits to unset host- & set mask-bits */
        return (~0) << (ADDRSPACE - bits);
}

int
getipint(int oct[])
{
        int ipint = 0;
        
        /* Pack each octet's bits into int representation of 32-bit address */
        for (int idx = 0; idx < OCTET_LEN; ++idx)
                ipint += oct[idx] << ((BYTE_LEN * (3-idx)));

        return ipint;
}

void
getoctets(int ipint, int oct[])
{
        /* Unpack int representation of 32-bit address into 4x 8-bit octets */
        for (int idx = 0; idx < OCTET_LEN; ++idx)
                oct[idx] = (ipint >> ((BYTE_LEN * (3-idx)))) & 0xFF;
}

void
showinfo(int prefix, int ipint, int oct[])
{
        int         hosts, mask, end;
        
        /* OR start address with hostmask to find end address */
        mask = getmaskint(prefix);
        end = ipint | ~mask;

        /* Print CIDR notation first */
        getoctets(ipint, oct);
        printf("CIDR notation     :   %d.%d.%d.%d/%d\n",
            oct[0], oct[1], oct[2], oct[3], prefix);
        
        /* 
         * (2^host bits) - 1 for useable hosts up to and including /30 block
         * (2^host bits) + 1 in /31–32 blocks. In the former case, adjust host
         * min/max accordingly by adding and subtracting one to and from the
         * start and end addresses, respectively.
         */
        hosts = (prefix < 31) ? ~mask - 1 : ~mask + 1;

        /* Print address range second */
        if (prefix < 31) {
                printf("Address range     :   %d.%d.%d.%d —— ",
                    oct[0], oct[1], oct[2], oct[3] + 1);
                getoctets(end, oct);
                printf("%d.%d.%d.%d\n", oct[0], oct[1], oct[2], oct[3] - 1);
        } else {
                printf("Address range     :   %d.%d.%d.%d —— ",
                    oct[0], oct[1], oct[2], oct[3]);
                getoctets(end, oct);
                printf("%d.%d.%d.%d\n", oct[0], oct[1], oct[2], oct[3]);
        }

        /* Print broadcast address third */
        printf("Broadcast address :   %d.%d.%d.%d\n",
            oct[0], oct[1], oct[2], oct[3]);
        
        /* Print subnet mask and useable hosts last */
        getoctets(mask, oct);
        printf("Subnet mask       :   %d.%d.%d.%d [0x%x]\n",
            oct[0], oct[1], oct[2], oct[3], mask);
        printf("Address block     :   ");
        printsep(hosts);
        printf(" contiguous hosts\n\n");
}

/*
 * If user makes request for multiple subnets, determine bits needed for
 * requested subnets to find subnet mask and compute subnet address range
 */
void
makesubnets(int subnetreq, int ipint, int prefix, int *oct)
{
        int         netmaskbits, subnets, subnetbits;
        
        /* Find minimum bits to make at least number of subnets requested */
        subnetbits = ceil(log2(subnetreq));

        /* Then add subnet bits to original netmask bits to make subnet mask */
        netmaskbits = prefix + subnetbits;

        /* Check total bits required don't exceed 32-bit address space */
        if (netmaskbits >= ADDRSPACE)
                fprintf(stderr, "error: subnet request [%d] too large.\n",
                    subnetreq);
        else {
                /*
                 * Find start address of each subnet by adding total number of
                 * hosts of each preceding subnet to the current start address
                 */
                subnets = pow(2, subnetbits);
                printf("[+] subnets delivered: %d\n\n", subnets);
                for (int i = 0; i < subnets; ++i) {
                        printf("-> subnet %d\n", i + 1);
                        showinfo(netmaskbits,
                            ipint | i << (ADDRSPACE - netmaskbits), oct);
                }
        }
}

void
parseipv6(char *argv)
{
        unsigned char       endip[sizeof(struct in6_addr)];
        unsigned char       netaddr[sizeof(struct in6_addr)];
        unsigned char       startip[sizeof(struct in6_addr)];
        uintmax_t           blocksize;
        char                endaddr[INET6_ADDRSTRLEN];
        char                startaddr[INET6_ADDRSTRLEN];
        char                *ptr, msg[50];
        int                 addrlen = 0, hext = 0;
        int                 conv = 0, prefix = 0;
        
        errno = 0;

        /* Verify correct slash '/' CIDR format to convert prefix to integer */
        if ((ptr = strchr(&argv[1], '/'))) {
                *ptr++ = '\0';

                /* Check that prefix is an integer */
                prefix = strtol(ptr, &ptr, 10);
                if (errno == ERANGE) {
                        usage("error: overflow");
                } else if (errno != 0) {
                        sprintf(msg, "error: prefix not integer [%s]", ptr);
                        usage(msg);
                } else if (*ptr != '\0') {
                        sprintf(msg, "error: illegal char in prefix [%s]", ptr);
                        usage(msg);
                }
        } else
                usage("error: invalid ipv6 CIDR notation");

        /* Verify prefix is within range */
        if (!(prefix > 0 && prefix <= ADDRSPACE6)) {
                sprintf(msg, "error: prefix outside legal range [%d]", prefix);
                usage(msg);
        }

        /* Convert address character string into network address strucure */
        if ((conv = inet_pton(AF_INET6, argv, netaddr)) == -1)
                perror("error: inet_pton");
        else if (conv == 0)
                usage("error: invalid presentation format");
        
        /* Send network address structure to find start and end addresses */
        ipv6addr_info(netaddr, prefix, startip, endip);

        /* Convert start and end network address structures into strings */
        inet_ntop(AF_INET6, startip, startaddr, INET6_ADDRSTRLEN);
        inet_ntop(AF_INET6, endip, endaddr, INET6_ADDRSTRLEN);

        printf("\nAddress        : %s\n", argv);
        printf("CIDR notation  : %s/%d\n", startaddr, prefix);
        printf("Start address  : %s\n", startaddr);

        /* if ntop() returns shortened IPv6 address, pad with 0xf to end */
        if ((addrlen = strlen(endaddr)) < IPV6STRLEN) {
                hext = ((IPV6STRLEN - addrlen) / IPV6HEXLEN);
                endaddr[strlen(endaddr) - 2] = 0;
                printf("End address    : %s", endaddr);
                for (;hext > 0; --hext)
                        printf(":ffff");
                printf("\n");
        } else
            printf("End address    : %s\n", endaddr);

        /* TODO: consider making lookup table with strings of values 2^64+ */
        if (prefix > (ADDRSPACE6 / 2)) {
                /* 
                 * Print pretty integer of useable hosts if blocksize
                 * fits in 64 bits; otherwise print as power of 2
                 */
                blocksize = pow(2, (ADDRSPACE6 - prefix));
                printf("Block size:    : ");
                printsep(blocksize);
                printf(" contiguous usable hosts\n\n");
        } else
                printf("Block size:    : 2^%d contiguous usable hosts\n\n",\
                    ADDRSPACE6 - prefix);
}

void
ipv6addr_info(unsigned char *netaddr, int prefix,
    unsigned char *startip, unsigned char *endip)
{
        struct split            bits;
        uint_fast64_t           first64_swapped = 0, last64_swapped = 0;
        uint_fast64_t           masked = 0, maskbits = 0;
        unsigned char           *endp = endip;
        unsigned char           *startp = startip;
        
        /* Set all 128 bits of IPv6 start and end addresses to 0 */
        memset(startip, 0, sizeof(struct in6_addr));
        memset(endip, 0, sizeof(struct in6_addr));

        /* Copy network address to bits struct for parsing in 64-bit chunks */
        memcpy(&bits, netaddr, sizeof(bits));
        
        /*
         * Only first 64 bits are needed to define subnet if netmask is 64 bits
         * or less because entire second 64-bit chunk belongs to hostmask
         */
        if (prefix <= (ADDRSPACE6 / 2)) {
                /*
                 * Swap first 64-bit chunk to big-endian (network byte) order
                 * and subtract prefix from 64 bits only to define netmask bits
                 */
                first64_swapped = bswap64(bits.first64);
                maskbits = (uint_fast64_t) (~0) << ((ADDRSPACE6 / 2) - prefix);

                /* AND first 64-bit chunk with netmask to get start address */
                masked = bswap64(first64_swapped & maskbits);
                memcpy(startip, &masked, sizeof(uint_fast64_t));

                /* OR first 64-bit chunk with hostmask to get end address */
                masked = bswap64(first64_swapped | ~maskbits);
                memcpy(endip, &masked, sizeof(uint_fast64_t));
                
                return;
        }

        /*
         * If netmask is > 64 bits, subtract prefix from entire 128-bit address
         * space, and swap last 64-bit chunk to big-endian (network) byte order
         */
        last64_swapped = bswap64(bits.last64);
        maskbits = (uint_fast64_t) (~0) << (ADDRSPACE6 - prefix);

        /* Write first 64-bit chunk of address to start & end address string */
        memcpy(startip, &(bits.first64), sizeof(uint_fast64_t));
        memcpy(endip, &(bits.first64), sizeof(uint_fast64_t));

        /* Move pointers forward 64 bits to start of the second 64-bit chunk */
        startp += sizeof(uint_fast64_t);
        endp += sizeof(uint_fast64_t);

        /*
         * AND last 64-bit chunk with netmask to get subnet start address
         * and write to last 64 bits of start address string
         */
        masked = bswap64(last64_swapped & maskbits);
        memcpy(startp, &masked, sizeof(uint_fast64_t));

        /*
         * OR last 64-bit chunk with hostmask to get subnet end address
         * and write to last 64 bits of end address string
         */
        masked = bswap64(last64_swapped | ~maskbits);
        memcpy(endp, &masked, sizeof(uint_fast64_t));
}

/* Use a comma to separate groups of thousands and print pretty numbers */
void
printsep(uintmax_t n) {
        if (n < 1000) {
                printf ("%ju", n);
                return;
        }
        printsep(n / 1000);
        printf(",%03ju", n % 1000);
}

void
usage(char *msg)
{
        extern char     *__progname;

        fprintf(stderr, "\n%s\nusage: %s [-s subnets] ipaddr/prefix\
            \n e.g.: %s -s 4 192.168.0.1/24\n\
       %s 8c6b:dbfd:5c73:8f14:f815:a4a2:5dab:38b0/110\n\n",
            msg, __progname, __progname, __progname);
        
        exit(1);
}
\ No newline at end of file

A  => openbsd/netcalc/doc/netcalc.1 +86 -0
@@ 1,86 @@
.\"
.\" Copyright (c) 2019 Mark Jamsek <mark@jamsek.com>
.\"
.\" Permission to use, copy, modify, and distribute this software for any
.\" purpose with or without fee is hereby granted, provided that the above
.\" copyright notice and this permission notice appear in all copies.
.\"
.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
.\"
.Dd $Mdocdate: September 25 2019 $
.Dt NETCALC 1
.Os
.Sh NAME
.Nm netcalc
.Nd IPv4 & IPv6 CIDR Subnet Calculator
.Sh SYNOPSIS
.Nm netcalc
.Op Fl s Ar subnets
.Ar ipaddr/prefix
.Sh DESCRIPTION
With an argument specifying an
.Ar ipaddr
in CIDR notation,
.Nm
extrapolates and returns network information for the
corresponding network and subnet(s). Command line
supplied argument specifies whether subnet division
is requested. If no arguments are given, usage details
are displayed.
.Pp
.Nm
returns the following information:
.Bd -literal -offset indent
Network address
Address range (i.e., host min/max)
Broadcast address
Subnet mask
Address block (i.e., useable hosts)
.Ed
.Pp
The available options are as follows:
.Bl -tag -width Dssmacro=value
.It Fl s Ar subnets
Specify number of subnets requested.
.El
.Sh EXAMPLES
To request four subnets from the 
.Pa /24
block of 
.Pa 192.168.100.20
run:
.Pp
.Dl $ Nm netcalc -s4 192.168.100.20/24
.Pp
To extract network information for a given
.Pa IPv6
address
run:
.Pp
.Dl $ Nm netcalc b084:4ce2:eb94:ded1:e5b0:a8be:2f22:b9d6/110
.Pp
To find an address group within the
.Pa 10.0.0.10
to
.Pa 10.0.0.110
range run:
.Pp
.Dl $ Nm netcalc 10.0.0.10/25
.Pp
.Sh HISTORY
The
.Nm
utility was first developed in 2018 and expanded to include IPv6
in 2019.
.Sh AUTHORS
The
.Nm
utility was written by
.An Mark Jamsek Aq Mt mark@jamsek.com .

A  => openbsd/netcalc/include/netcalc.h +95 -0
@@ 1,95 @@
/*
 * Copyright (c) 2019 Mark Jamsek <mark@jamsek.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <arpa/inet.h>
#include <netinet/in.h>

#include <ctype.h>
#include <errno.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#if defined(HAVE_BYTESWAP_H)
#   include <byteswap.h>
#   define bswap16(n) bswap_16(n)
#   define bswap32(n) bswap_32(n)
#   define bswap64(n) bswap_64(n)
#else
#  if defined(HAVE_SYS_ENDIAN)
#   include <sys/_endian.h>
#else
static inline uint16_t
bswap16(uint16_t n)
{
        return (n << 8) | (n >> 8);
}

static inline uint32_t
bswap32(uint32_t n)
{
        return (bswap16(n & 0xffff) << 16) | (bswap16(n >> 16));
}

static inline uint_fast64_t
bswap64(uint_fast64_t n)
{
        return (((uint_fast64_t)bswap32(n & 0xffffffff)) << 32)
            | (bswap32(n >> 32));
}
  #endif /* HAVE_SYS_ENDIAN */
#endif  /* HAVE_BYTESWAP_H */

#ifndef AF_INET6
# define AF_INET6 24
#endif

/* IPv4 constants */
#define ADDRSPACE   32       /* 32-bit ipv4 address space */
#define BYTE_LEN     8       /* byte length for bit shifting of octets */
#define OCTET_LEN    4       /* number of octets (integers) in ipaddr */
#define OCTET_MIN    0       /* minimum IP octet value */
#define OCTET_MAX  255       /* maximum IP octet value */

/* IPv6 constants */
#define ADDRSPACE6 128       /* 128-bit ipv6 address space */
#define IPV6STRLEN  39       /* number of characters in full IPv6 address */
#define IPV6HEXLEN   4       /* number of hexadecimal characters per hextet */

#define PROGRAM      "netcalc 0.3 IPv4 & IPv6 CIDR Subnet Calculator"

/*
 * Data type for 128-bit IPv6 address
 */
struct split {
        uint_fast64_t   first64;
        uint_fast64_t   last64;
};

__dead void         usage(char *);

int                 getipint(int *);
int                 getmaskint(int);
void                getoctets(int, int *);
void                ipv6addr_info(unsigned char *, int,
                        unsigned char *, unsigned char *);
void                makesubnets(int, int, int, int *);
void                parseipv6(char *);
void                printsep(uintmax_t n);
void                showinfo(int, int, int *);
void                validateip(char *, int *, int *);
\ No newline at end of file

A  => openbsd/netcalc/src/Makefile +9 -0
@@ 1,9 @@
PROG=   netcalc
SRCS=   netcalc.c main.c
LDADD=  -lm
DPADD=  ${LIBM}
MAN=	${.CURDIR}/../doc/netcalc.1
CFLAGS+=    -I${.CURDIR}
CFLAGS+=    -I${.CURDIR}/../include

.include <bsd.prog.mk>
\ No newline at end of file

A  => openbsd/netcalc/src/main.c +79 -0
@@ 1,79 @@
/*
 * Copyright (c) 2019 Mark Jamsek <mark@jamsek.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "netcalc.h"

/*
 * Find and display network details from user specified IPv4 or IPv6 address in
 * CIDR notation, including: network address; host min/max; broadcast & netmask
 * addresses; and useable hosts per subnet. Optionally, compute required subnet
 * bits and resultant subnets subsequent to user request for multiple subnets.
 */
int
main(int argc, char *argv[])
{
        int         oct[OCTET_LEN];
        int         idx, ipint, o1, o2, o3, o4, p;
        int         offset = 0, prefix = 0;
        char        *ptr, *subnetreq = NULL;
        
        if (argc < 2)
                usage(PROGRAM);
        
        /* Check version of address or if subnet (-s) argument is given */
        for (idx = 0; idx < argc; ++idx) {
                if (strchr(argv[idx], ':') != NULL) {
                        parseipv6(argv[idx]);
                        return 0;
                } else if (strncmp(argv[idx], "-s", 2) == 0) {
                        if (strlen(argv[idx]) == 2 && argc > 3) {
                                subnetreq = argv[idx + 1];
                                if (idx < 2)
                                        offset = idx + 1;
                        } else if (strlen(argv[idx]) > 2 && argc > 2) {
                                subnetreq = &argv[idx][2];  /* Magic to make */
                                if (idx < 2)                /* pre & postfix */
                                        offset = idx;       /* arguments on  */
                        } else                              /* cmd line work */
                                usage("error: insufficient arguments");
                }
        
        }

        /* Ensure ip4addr is provided in correct CIDR notation */
        if (sscanf(argv[1 + offset], "%3d.%3d.%3d.%3d/%2d",
            &o1, &o2, &o3, &o4, &p) != 5)
                usage("error: invalid ipv4 CIDR notation");
        else
                validateip(ptr = strtok(argv[1 + offset], "."), oct, &prefix);
        
        /* Logical AND ip address with netmask to get start address of block */
        ipint = getipint(oct) & getmaskint(prefix);

        printf("\nAddress           :   %d.%d.%d.%d\n",\
            oct[0], oct[1], oct[2], oct[3]);
        showinfo(prefix, ipint, oct);

        /* If subnet arg, check value consists of digits before proceeding */
        if ((subnetreq != NULL && (isdigit(*subnetreq) == 0
            || (strlen(subnetreq) > 1 && isdigit(subnetreq[1]) == 0))))
                usage("error: illegal subnet request value");
        else if (subnetreq != NULL && *subnetreq != '0') {
                printf("[+] subnets requested: %s\n", subnetreq);
                makesubnets(strtol(subnetreq, &ptr, 10), ipint, prefix, oct);
        }
        return 0;
}
\ No newline at end of file

A  => openbsd/netcalc/src/netcalc.c +342 -0
@@ 1,342 @@
/*
 * Copyright (c) 2019 Mark Jamsek <mark@jamsek.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "netcalc.h"

/* Verify that prefix and ipaddr octets are all integers and in legal range */
void
validateip(char *ipaddr, int *oct, int *prefix)
{       
        for (int idx = 0; ipaddr != NULL; ++idx) {

                /* Validate prefix after collecting four octets */
                if (idx >= OCTET_LEN) {
                        *prefix = strtol(ipaddr, &ipaddr, 10);
                        if ((!(*prefix <= ADDRSPACE && *prefix > 0)
                            || strlen(ipaddr) > 2) || *ipaddr != '\0')
                                usage("error: illegal CIDR prefix");
                } else if (!((oct[idx] = strtol(ipaddr, &ipaddr, 10))
                    >= OCTET_MIN && oct[idx] < OCTET_MAX))
                        usage("error: illegal octet number");
                ipaddr = strtok(NULL, "./");
        }
}

/*
 * Convert CIDR prefix into integer representation of netmask
 */
int
getmaskint(int bits)
{       
        /* Left shift for number of host bits to unset host- & set mask-bits */
        return (~0) << (ADDRSPACE - bits);
}

/*
 * Convert octets of IP address into 32-bit integer representation
 */
int
getipint(int oct[])
{
        int ipint = 0;
        
        /* Pack each octet's bits into int representation of 32-bit address */
        for (int idx = 0; idx < OCTET_LEN; ++idx)
                ipint += oct[idx] << ((BYTE_LEN * (3-idx)));

        return ipint;
}

/*
 * Convert integer representation of IP address into four 8-bit octets
 */
void
getoctets(int ipint, int oct[])
{
        /* Unpack int representation of 32-bit address into 4x 8-bit octets */
        for (int idx = 0; idx < OCTET_LEN; ++idx)
                oct[idx] = (ipint >> ((BYTE_LEN * (3-idx)))) & 0xFF;
}

/*
 * Display IPv4 subnet details to console
 */
void
showinfo(int prefix, int ipint, int oct[])
{
        int         hosts, mask, end;
        
        /* OR start address with hostmask to find end address */
        mask = getmaskint(prefix);
        end = ipint | ~mask;

        /* Print CIDR notation first */
        getoctets(ipint, oct);
        printf("CIDR notation     :   %d.%d.%d.%d/%d\n",
            oct[0], oct[1], oct[2], oct[3], prefix);
        
        /* 
         * (2^host bits) - 1 for useable hosts up to and including /30 block
         * (2^host bits) + 1 in /31–32 blocks. In the former case, adjust host
         * min/max accordingly by adding and subtracting one to and from the
         * start and end addresses, respectively.
         */
        hosts = (prefix < 31) ? ~mask - 1 : ~mask + 1;

        /* Print address range second */
        if (prefix < 31) {
                printf("Address range     :   %d.%d.%d.%d —— ",
                    oct[0], oct[1], oct[2], oct[3] + 1);
                getoctets(end, oct);
                printf("%d.%d.%d.%d\n", oct[0], oct[1], oct[2], oct[3] - 1);
        } else {
                printf("Address range     :   %d.%d.%d.%d —— ",
                    oct[0], oct[1], oct[2], oct[3]);
                getoctets(end, oct);
                printf("%d.%d.%d.%d\n", oct[0], oct[1], oct[2], oct[3]);
        }

        /* Print broadcast address third */
        printf("Broadcast address :   %d.%d.%d.%d\n",
            oct[0], oct[1], oct[2], oct[3]);
        
        /* Print subnet mask and useable hosts last */
        getoctets(mask, oct);
        printf("Subnet mask       :   %d.%d.%d.%d [0x%x]\n",
            oct[0], oct[1], oct[2], oct[3], mask);
        printf("Address block     :   ");
        printsep(hosts);
        printf(" contiguous hosts\n\n");
}

/*
 * If user makes request for multiple subnets, determine bits needed for
 * requested subnets to find subnet mask and compute subnet address range
 */
void
makesubnets(int subnetreq, int ipint, int prefix, int *oct)
{
        int         netmaskbits, subnets, subnetbits;
        
        /* Find minimum bits to make at least number of subnets requested */
        subnetbits = ceil(log2(subnetreq));

        /* Then add subnet bits to original netmask bits to make subnet mask */
        netmaskbits = prefix + subnetbits;

        /* Check total bits required don't exceed 32-bit address space */
        if (netmaskbits >= ADDRSPACE)
                fprintf(stderr, "error: subnet request [%d] too large.\n",
                    subnetreq);
        else {
                /*
                 * Find start address of each subnet by adding total number of
                 * hosts of each preceding subnet to the current start address
                 */
                subnets = pow(2, subnetbits);
                printf("[+] subnets delivered: %d\n\n", subnets);
                for (int i = 0; i < subnets; ++i) {
                        printf("-> subnet %d\n", i + 1);
                        showinfo(netmaskbits,
                            ipint | i << (ADDRSPACE - netmaskbits), oct);
                }
        }
}

/*
 * Parse user supplied IPv6 address
 */
void
parseipv6(char *argv)
{
        unsigned char       endip[sizeof(struct in6_addr)];
        unsigned char       netaddr[sizeof(struct in6_addr)];
        unsigned char       startip[sizeof(struct in6_addr)];
        uintmax_t           blocksize;
        char                endaddr[INET6_ADDRSTRLEN];
        char                startaddr[INET6_ADDRSTRLEN];
        char                *ptr, msg[50];
        int                 addrlen = 0, hext = 0;
        int                 conv = 0, prefix = 0;
        
        errno = 0;

        /* Verify correct slash '/' CIDR format to convert prefix to integer */
        if ((ptr = strchr(&argv[1], '/'))) {
                *ptr++ = '\0';

                /* Check that prefix is an integer */
                prefix = strtol(ptr, &ptr, 10);
                if (errno == ERANGE) {
                        usage("error: overflow");
                } else if (errno != 0) {
                        sprintf(msg, "error: prefix not integer [%s]", ptr);
                        usage(msg);
                } else if (*ptr != '\0') {
                        sprintf(msg, "error: illegal char in prefix [%s]", ptr);
                        usage(msg);
                }
        } else
                usage("error: invalid ipv6 CIDR notation");

        /* Verify prefix is within range */
        if (!(prefix > 0 && prefix <= ADDRSPACE6)) {
                sprintf(msg, "error: prefix outside legal range [%d]", prefix);
                usage(msg);
        }

        /* Convert address character string into network address strucure */
        if ((conv = inet_pton(AF_INET6, argv, netaddr)) == -1)
                perror("error: inet_pton");
        else if (conv == 0)
                usage("error: invalid presentation format");
        
        /* Send network address structure to find start and end addresses */
        ipv6addr_info(netaddr, prefix, startip, endip);

        /* Convert start and end network address structures into strings */
        inet_ntop(AF_INET6, startip, startaddr, INET6_ADDRSTRLEN);
        inet_ntop(AF_INET6, endip, endaddr, INET6_ADDRSTRLEN);

        printf("\nAddress        : %s\n", argv);
        printf("CIDR notation  : %s/%d\n", startaddr, prefix);
        printf("Start address  : %s\n", startaddr);

        /* if ntop() returns shortened IPv6 address, pad with 0xf to end */
        if ((addrlen = strlen(endaddr)) < IPV6STRLEN) {
                hext = ((IPV6STRLEN - addrlen) / IPV6HEXLEN);
                endaddr[strlen(endaddr) - 2] = 0;
                printf("End address    : %s", endaddr);
                for (;hext > 0; --hext)
                        printf(":ffff");
                printf("\n");
        } else
            printf("End address    : %s\n", endaddr);

        /* TODO: consider making lookup table with strings of values 2^64+ */
        if (prefix > (ADDRSPACE6 / 2)) {
                /* 
                 * Print pretty integer of useable hosts if blocksize
                 * fits in 64 bits; otherwise print as power of 2
                 */
                blocksize = pow(2, (ADDRSPACE6 - prefix));
                printf("Block size:    : ");
                printsep(blocksize);
                printf(" contiguous usable hosts\n\n");
        } else
                printf("Block size:    : 2^%d contiguous usable hosts\n\n",\
                    ADDRSPACE6 - prefix);
}

/*
 * Extrapolate start and end of subnet from IPv6 network address structure
 */
void
ipv6addr_info(unsigned char *netaddr, int prefix,
    unsigned char *startip, unsigned char *endip)
{
        struct split            bits;
        uint_fast64_t           first64_swapped = 0, last64_swapped = 0;
        uint_fast64_t           masked = 0, maskbits = 0;
        unsigned char           *endp = endip;
        unsigned char           *startp = startip;
        
        /* Set all 128 bits of IPv6 start and end addresses to 0 */
        memset(startip, 0, sizeof(struct in6_addr));
        memset(endip, 0, sizeof(struct in6_addr));

        /* Copy network address to bits struct for parsing in 64-bit chunks */
        memcpy(&bits, netaddr, sizeof(bits));
        
        /*
         * Only first 64 bits are needed to define subnet if netmask is 64 bits
         * or less because entire second 64-bit chunk belongs to hostmask
         */
        if (prefix <= (ADDRSPACE6 / 2)) {
                /*
                 * Swap first 64-bit chunk to big-endian (network byte) order
                 * and subtract prefix from 64 bits only to define netmask bits
                 */
                first64_swapped = bswap64(bits.first64);
                maskbits = (uint_fast64_t) (~0) << ((ADDRSPACE6 / 2) - prefix);

                /* AND first 64-bit chunk with netmask to get start address */
                masked = bswap64(first64_swapped & maskbits);
                memcpy(startip, &masked, sizeof(uint_fast64_t));

                /* OR first 64-bit chunk with hostmask to get end address */
                masked = bswap64(first64_swapped | ~maskbits);
                memcpy(endip, &masked, sizeof(uint_fast64_t));
                
                return;
        }

        /*
         * If netmask is > 64 bits, subtract prefix from entire 128-bit address
         * space, and swap last 64-bit chunk to big-endian (network) byte order
         */
        last64_swapped = bswap64(bits.last64);
        maskbits = (uint_fast64_t) (~0) << (ADDRSPACE6 - prefix);

        /* Write first 64-bit chunk of address to start & end address string */
        memcpy(startip, &(bits.first64), sizeof(uint_fast64_t));
        memcpy(endip, &(bits.first64), sizeof(uint_fast64_t));

        /* Move pointers forward 64 bits to start of the second 64-bit chunk */
        startp += sizeof(uint_fast64_t);
        endp += sizeof(uint_fast64_t);

        /*
         * AND last 64-bit chunk with netmask to get subnet start address
         * and write to last 64 bits of start address string
         */
        masked = bswap64(last64_swapped & maskbits);
        memcpy(startp, &masked, sizeof(uint_fast64_t));

        /*
         * OR last 64-bit chunk with hostmask to get subnet end address
         * and write to last 64 bits of end address string
         */
        masked = bswap64(last64_swapped | ~maskbits);
        memcpy(endp, &masked, sizeof(uint_fast64_t));
}

/* Use a comma to separate groups of thousands and print pretty numbers */
void
printsep(uintmax_t n) {
        if (n < 1000) {
                printf ("%ju", n);
                return;
        }
        printsep(n / 1000);
        printf(",%03ju", n % 1000);
}

/*
 * Display program usage to console
 */
void
usage(char *msg)
{
        extern char     *__progname;

        fprintf(stderr, "\n%s\nusage: %s [-s subnets] ipaddr/prefix\
            \n e.g.: %s -s 4 192.168.0.1/24\n\
       %s 8c6b:dbfd:5c73:8f14:f815:a4a2:5dab:38b0/110\n\n",
            msg, __progname, __progname, __progname);
        
        exit(1);
}
\ No newline at end of file