Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C CIDR to address list

I'm writing a program where I need to iterate over a list of addresses derived from a user given cidr (eg. 75.24.64.0/24).

I took a look at some of the code, but that seemed overcomplicated.

In the end I decided to use a struct that looked something like this:

struct ip_iterator {
    unsigned int netmask;
    int bitcount;
    long long num_total;
    long long num_left;
    int current_ip[4];
};

I could then define ip_iterator_init ip_iterator_next, and ip_iterator_is_finished functions. However, I'm stuck on how to get from the cidr to the first IP. I learned network math a while ago but since my certification I've been using online calculators.

like image 700
735Tesla Avatar asked Mar 17 '23 00:03

735Tesla


2 Answers

Assuming you have the CIDR in a string, something like this would probably do it for you.

First, a function to convert a CIDR into IP and mask:

int cidr_to_ip_and_mask(const char *cidr, uint32_t *ip, uint32_t *mask)
{
    uint8_t a, b, c, d, bits;
    if (sscanf(cidr, "%hhu.%hhu.%hhu.%hhu/%hhu", a, b, c, d, bits) < 5) {
        return -1; /* didn't convert enough of CIDR */
    }
    if (bits > 32) {
        return -1; /* Invalid bit count */
    }
    *ip =
        (a << 24UL) |
        (b << 16UL) |
        (c << 8UL) |
        (d);
    *mask = (0xFFFFFFFFUL << (32 - bits)) & 0xFFFFFFFFUL;
}

Next, a snippet to get the first IP:

uint32_t ip;
uint32_t mask;
uint32_t first_ip;
if (cidr_to_ip_and_mask(cidr, &ip, &mask) < 0) {
    /* return some failure */
}
first_ip = ip & mask;

First up, I'm assuming C99 or an environment where stdint.h is available, such that I can use explicit bit-width data types (maximum portability since you don't specify architecture). I'm also assuming IPv4 since that's your example string.

Next I use sscanf to convert the string into components of the address. The combining of bytes into full 32bit value should be straight forward. I am marking my literals as unsigned long in an effort to make sure the results aren't truncated before assignment if we are on a small bitwidth machine.

The idea behind the expression for setting mask is that the bitcount in CIDR specifies how many most significant bits indicate the network, so if we subtract that from 32, thats how much we need to shift up a full set of bits to get that mask (after truncation). For example, a mask of 32 would be all bits, and 32-32 = 0 so we wouldn't shift at all, giving all 32 bits. A bitcount of 24 (like your example) would give 32-24=8, and 0xFFFFFFFF << 8 is 0xFFFFFF00 after truncation (or 255.255.255.0 in decimal notation)

Finally, to get the initial IP, I simply apply the mask to the IP address with a bitwise AND. Simple!

Since your title question discusses the entire list, you can get the final address by ORing the complement of the mask to the base:

uint32_t finalIP = first_ip | ~mask;

This should also equal the broadcast address. Then, you can iterate from the firstIP to finalIP in order, including or excluding finalIP depending on if you need the broadcast address or not (and including or excluding firstIP if you want the network address or not).

like image 196
John O'M. Avatar answered Mar 24 '23 13:03

John O'M.


int cidr_to_ip_and_mask(const char *cidr, uint32_t *ip, uint32_t *mask)
{
    uint8_t a, b, c, d, bits;
    if (sscanf(cidr, "%hhu.%hhu.%hhu.%hhu/%hhu", &a, &b, &c, &d, &bits) < 5) {
        return -1; /* didn't convert enough of CIDR */
    }
    if (bits > 32) {
        return -1; /* Invalid bit count */
    }
    *ip =
        (a << 24UL) |
        (b << 16UL) |
        (c << 8UL) |
        (d);
    *mask = (0xFFFFFFFFUL << (32 - bits)) & 0xFFFFFFFFUL;
}
like image 37
2018 with Avatar answered Mar 24 '23 14:03

2018 with