Reputation: 1179
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
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 1
s 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.
Upvotes: 2