dev
dev

Reputation: 1179

Is this fastes way to check if IP (string) belongs to CIDR (string)?

Want to check if IP (as String) is in CIDR range (as String)

I know there are also some network functions to check it, but decided to use this approach and have this code so far:

    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;
    }
    
    uint32_t netip;
    uint32_t netmask;
    if (cidr_to_ip_and_mask("10.0.0.0/16", &netip, &netmask) < 0) {
        /* return some failure */
    } 
    uint8_t a, b, c, d;
    
    char *ipstr= "10.0.0.1" // value to check
    uint32_t ip = 0;
    if (sscanf(ipstr, "%hhu.%hhu.%hhu.%hhu",  a, b, c, d) < 4) {
            return -1; /* didn't convert enough of CIDR */
        }
       
    ip = (a << 24UL) |
      (b << 16UL) |
      (c << 8UL) |
      (d);
            
    uint32_t netstart = (netip & netmask); // first ip in subnet
    uint32_t netend = (netstart | ~netmask); // last ip in subnet
    if ((ip >= netstart) && (ip <= netend))
        // is in subnet range...
    else
        // not in subnet range...

Is this correct?

Is this the fastest way to achieve it?

Update

I did some experimenting and measurements using both codes. Had to slightly modify proposed answer code.

Final code I used:

t1.c

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>

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;
}

void main()
{    
    uint32_t netip;
    uint32_t netmask;


    for(int i=0;i<1000000;i++)
    {
        if (cidr_to_ip_and_mask("10.1.0.0/16", &netip, &netmask) < 0) {
            /* return some failure */
        } 
        uint8_t a, b, c, d;
        uint32_t ip;

        char *ipstr= "10.2.13.1"; // value to check
        if (sscanf(ipstr, "%hhu.%hhu.%hhu.%hhu",  &a, &b, &c, &d) < 4) {
        }

        ip = (a << 24UL) |
            (b << 16UL) |
            (c << 8UL) |
            (d);

        uint32_t netstart = (netip & netmask); // first ip in subnet
        uint32_t netend = (netstart | ~netmask); // last ip in subnet
        if ((ip >= netstart) && (ip <= netend))
            printf("in");
        else
            printf("out");
    }
}

t2.c

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void main(){

    for(int i=0;i<1000000;i++)
    {


        char ipAsString[16]="10.2.13.1";
        char cidrAsString[20]="10.1.0.0/16";
        char *ipSplit[4];
        unsigned char ipAddr[4];
        for(int i=0;i<4;i++){
            if(i==0)  
                ipSplit[i] =  (char *)strtok(ipAsString, ".");
            else 
                ipSplit[i] = (char *)strtok(NULL, ".");
        }
        ipAddr[0] = atoi(ipSplit[0]);
        ipAddr[1] = atoi(ipSplit[1]);
        ipAddr[2] = atoi(ipSplit[2]);
        ipAddr[3] = atoi(ipSplit[3]);
       
        int i=0;
        for(i=0;i<=16;i++)
        {
            if(cidrAsString[i]=='/')
                break;
        }
        char c[3];
        strncpy(c,cidrAsString+(i+1),2);

        int cirdIntVal = atoi(c);
        int numOfSetBits = 0;
        if (ipAddr[0] < 255) {
            // find number of set bits (1s) in ipAddr[0] and add them to numOfSetBits
        } else if (ipAddr[1] <= 255) {
            numOfSetBits += 8;
            // find number of set bits (1s) in ipAddr[1] and add them to numOfSetBits
        } else if (ipAddr[2] <= 255) {
            numOfSetBits += 16;
            // find number of set bits (1s) in ipAddr[2] and add them to numOfSetBits
        } else {
            numOfSetBits += 24;
            // find number of set bits (1s) in ipAddr[1] and add them to numOfSetBits
        }
        if (numOfSetBits == cirdIntVal) // will return true if ip is in cird
            printf("in");
        else
            printf("out");
    }
}

Performance results:

time ./t1     
./t1  0,00s user 0,00s system 80% cpu 0,001 total
time ./t2
./t2  0,00s user 0,00s system 81% cpu 0,001 total

Above is with return value ie return 1 or 0

Below is with printf()s

time ./t1  0,66s user 0,02s system 78% cpu 0,856 total

time ./t2  0,21s user 0,00s system 52% cpu 0,406 total


Second approch is ca 47% faster and easier on CPU

Upvotes: 0

Views: 686

Answers (1)

user5550963
user5550963

Reputation:

It is not clear from your question, but you if you are given ip address as string or char array, you can go store each "decimal value" of ip into a single unsigned char. You do not need to convert it to 32bit value, you check each individual char, and operate on it. For example if you have 10.252.1.154 as ip, store 10, 252, 1 and 154 in variables bits31_24, bits23_16, bits15_8, bits7_0, and than you count number of continuous 1s in each unsigned char from left.

int getCIDR (char *ipAsString, char* cird) {
    unsigned char ipAddr[4];
    char ipSplit[4][3] = strtok(ipAsString, '.');
    ipAddr[0] = atoi(ipSplit[0]);
    ipAddr[1] = atoi(ipSplit[1]);
    ipAddr[2] = atoi(ipSplit[2]);
    ipAddr[3] = atoi(ipSplit[3]);

    int numOfSetBits = 0;
    if (ipAddr[0] < 255) {
        // find number of set bits (1s) till first 0 in ipAddr[0] and add them to numOfSetBits
        return numOfSetBits;
    } else if (ipAddr[1] <= 255) {
        numOfSetBits += 8;
        // find number of set bits (1s) till first 0 in ipAddr[1] and add them to numOfSetBits
        return numOfSetBits;
    } else if (ipAddr[2] <= 255) {
        numOfSetBits += 16;
        // find number of set bits (1s) till first 0 in ipAddr[2] and add them to numOfSetBits
        return numOfSetBits;
    } else {
        numOfSetBits += 24;
        // find number of set bits (1s) in till first 0 ipAddr[1] and add them to numOfSetBits
        return numOfSetBits;
    }
}

Above code would return each CRDL from \0 to \32

than to convert it back to string:

char* CLDR = malloc (sizeof(char) * (numOfDigits(numOfSetBits) + 1)); // 1 for \ character
CLDR = itoa (numberOfSetBits);

EDIT: Per comment atoi to cut down on function calls could be re-written as

ipAddr[0] = (ipSplit[0][0] - '0') * 100 + (ipSplit[0][1] - '0') * 10 + ipSplit[0][0] - '0';

EDIT: To suit question better and not to disturb the original code, here is the revised code.

int getCIDR (char *ipAsString, char* cidr) {
    unsigned char ipAddr[4];
    char ipSplit[4][3] = strtok(ipAsString, '.');
    ipAddr[0] = atoi(ipSplit[0]);
    ipAddr[1] = atoi(ipSplit[1]);
    ipAddr[2] = atoi(ipSplit[2]);
    ipAddr[3] = atoi(ipSplit[3]);
    ++cidr; // to skip initial /
    int cirdIntVal = atoi(cidr);

    int numOfSetBits = 0;
    if (ipAddr[0] < 255) {
        // find number of set bits (1s) in ipAddr[0] and add them to numOfSetBits
    } else if (ipAddr[1] <= 255) {
        numOfSetBits += 8;
        // find number of set bits (1s) in ipAddr[1] and add them to numOfSetBits
    } else if (ipAddr[2] <= 255) {
        numOfSetBits += 16;
        // find number of set bits (1s) in ipAddr[2] and add them to numOfSetBits
    } else {
        numOfSetBits += 24;
        // find number of set bits (1s) in ipAddr[1] and add them to numOfSetBits
    }

    return (numberOfSetBits == cirdIntVal); // will return true if ip is in cird
}

You can also have for ranges instead of return (numberOfSetBits == cirdIntVal); you do the range checking. I am assuming CIRD is given as numberical value from (not intiger, but string representation) between 1 and 32.

Also one thing for CIRD

\0 => 0xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
\1 => 10xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
\2 => 110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
\3 => 1110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
... // and so on

this is what I am using in my algorithm, maybe I was not clear initially.

CIRDs

Upvotes: 2

Related Questions