Search code examples
cnetwork-programmingcidrnetmask

All IP's in a subnet (C)


Does someone have a good example of a way I could take a IP address with a CIDR such as 192.168.1.1/24 and return all the ip addresses inside of that range such as 192.168.1.1, 192.168.1.2, 192.168.1.3 ...

I'm fine with it being returned in a array of unsigned long, a char or just something like

/* Pseudocode */
while(currnetip <= finalip) { 
    print(currnetip); 
    currnetip++; 
}

As long as I can understand it its fine.

Feel free to comment a link to a post if you think it could help me.

Edit: Probably worth mentioning I've found lots of stuff that calculated the broadcast address ect I'm just not sure how to link all those functions together.


Solution

  • First, pack your IPv4 address into an uint32_t (defined in <stdint.h>), putting the leftmost octet in dotted-decimal notation into the most significat bits. For example,

    uint32_t ipv4_pack(const uint8_t octet1,
                       const uint8_t octet2,
                       const uint8_t octet3,
                       const uint8_t octet4)
    {
        return (((uint32_t)octet1) << 24)
             | (((uint32_t)octet2) << 16)
             | (((uint32_t)octet3) <<  8)
             |  ((uint32_t)octet4);
    }
    

    and its inverse,

    unsigned char *ipv4_unpack(unsigned char *addr, const uint32_t packed)
    {
        addr[3] = (uint8_t)(packed);
        addr[2] = (uint8_t)(packed >> 8);
        addr[1] = (uint8_t)(packed >> 16);
        addr[0] = (uint8_t)(packed >> 24);
        return addr;
    }
    

    An address like 128.64.32.16 is packed into 0x80402010 (128 == 8016, 64 == 4016, 32 == 2016, and 16 == 1016).


    You need to also convert the CIDR prefix size (1 to 32) into a binary mask of that many highest bits set:

    uint32_t ipv4_mask(const int prefix_size)
    {
        if (prefix_size > 31)
            return (uint32_t)0xFFFFFFFFU;
        else
        if (prefix_size > 0)
            return ((uint32_t)0xFFFFFFFFU) << (32 - prefix_size);
        else
            return (uint32_t)0U;
    }
    

    Prefix 24 corresponds to a mask of 11111111111111111111111100000000 in binary, and 0xFFFFFF00 in hexadecimal.

    Prefix 28 corresponds to a mask of 11111111111111111111111111110000 in binary, and 0xFFFFFFF0 in hexadecimal.

    For address addr1.addr2.addr3.addr4/prefix, the first address in a range (typically the gateway address for said range) is

    uint32_t first = ipv4_pack(addr1, addr2, addr3, addr4) & ipv4_mask(prefix);
    

    and the last address (typically the broadcast address for said range) is

    uint32_t last = ipv4_pack(addr1, addr2, addr3, addr4) | (~ipv4_mask(prefix));
    

    In all cases, first <= last, and iterating from first to last, inclusive, and calling ipv4_unpack() to unpack the value into dotted-decimal notation), yields all IPv4 addresses within the range.


    The same would work for IPv6, but requires something like an uint128_t type. (It can be emulated with smaller unsigned integer types, of course, taking a few more instructions, but the logic stays the same.)