IPv4 was created at a time when 32-bit systems were prevalent. The IPv4 dotted decimal address can be stored into a 32-bit unsigned integer, and bitwise operations are efficiently performed by network hardware. A bitmask for the 172.16.0.0/12 CIDR can be formed from a single left-shift, and checked against an address with a single bitwise-and.
There are three 'private' network address ranges defined by RFC-1918.
- CIDR/8, (A) single large network, (24-bit, 16M) address range at
10.x.y.z/8
- CIDR/12, (B) 16 contiguous networks (20-bit, 1M) address range at
172.16+x.y.z/12
, where x in [0..15]
- CIDR/16, (C) 256 contiguous networks (16-bit, 64K) address range at
192.168.y.z/16
Also, for carrier network subdivision,
- CIDR/10, (A) single large network, (24-bit, 16M) address range at
100.64+x.y.z/10
, where x in [0..63]
And for link-local addresses,
- CIDR/16, (B) a single network (16-bit, 64K) address range at
169.254.y.z/16
With a language that supports bitwise operations, you can convert a dotted decimal address into an integer easily,
//assume x[0],x[1],x[2],x[3] are the parts of a dotted ip address
unsigned int ipv4 = (( (( (x[0]<<8) |x[1])<<8) |x[2])<<8) |x[3]
Suppose you have defined constants for the above listed addresses,
CIDR8 = (( (( (10<<8) |0xff)<<8) |0xff)<<8) |0xff
CIDR12 = (( (( (172<<8) |16 |0xf)<<8) |0xff)<<8) |0xff
CIDR16 = (( (( (192<<8) |168)<<8) |0xff)<<8) |0xff
CIDR10 = (( (( (100<<8) |64 |0x3f)<<8) |0xff)<<8) |0xff
CIDRLL = (( (( (169<<8) |254)<<8) |0xff)<<8) |0xff
Checking whether your ipv4 address is one of these addresses is simple,
ipv4 == (ipv4 & CIDR8) //10.0.0.0/8
ipv4 == (ipv4 & CIDR12) //172.16.0.0/12
ipv4 == (ipv4 & CIDR16) //192.168.0.0/16
ipv4 == (ipv4 & CIDR10) //100.64.0.0/10
ipv4 == (ipv4 & CIDRLL) //169.254.0.0/16
Rather than check for 16 different 172.16.0.0/12 networks, one can use the above bitmask approach to directly check whether an ipv4 address is part of one of these private (NAT) networks. Choosing perl (python or ruby also work), instead of shell or awk, and using a single bit-wise and operation cuts down on the work considerably.
sub isprivate
{
my($inet) = @_;
if( $inet =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/ ) {
if( $1==10 ) { return 10; }
if( $1==172 && (($2 & 0x1f) == $2) ) { return 172; }
if( $1==192 && ($2==168) ) { return 192; }
}
return 0;
};
sub iscarrier
{
my($inet) = @_;
if( $inet =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/ ) {
if( $1==100 && (($2 & 0x7f) == $2) ) { return 100; }
}
return 0;
};
sub islinklocal
{
my($inet) = @_;
if( $inet =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/ ) {
if( $1==169 && ($2==254) ) { return 169; }
}
return 0;
};
How do you want to classify addresses?
sub ipaddr
{
my($inet) = @_;
{
if( isprivate($inet)>0 ) { $kind = "private"; }
elsif( isloop($inet)>0 ) { $kind = "loopback"; }
elsif( iscarrier($inet)>0 ) { $kind = "carrier"; }
elsif( islinklocal($inet)>0 ) { $kind = "linklocal"; }
else { $kind = ""; }
print "$iface: $inet $netmask $broadcast ($flagsdesc) $kind\n";
}
};
Run ifconfig from within a perl script,
$found = 0;
open($fh,"/sbin/ifconfig|");
while($line=<$fh>)
{
chomp($line); $line =~ s/^\s+//;
if( $line =~ /(\w+):\s+flags=(\d+)\s*\<(.*)\>\s+mtu\s+(\d+)\b/ ) {
if( $found ) { ipaddr($inet); }
$found = 1;
($iface,$flags,$flagsdesc,$mtu) = ($1,$2,$3,$4);
}
if( $line =~ /inet\s+(\d+\.\d+\.\d+\.\d+)\b/ ) {
($inet,$netmask,$broadcast) = ($1,"","");
if( $line =~ /netmask\s+([\d+\.]+)\b/ ) { ($netmask) = ($1); }
if( $line =~ /broadcast\s+([\d\.]+)\b/ ) { ($broadcast) = ($1); }
}
}
if( $found ) { ipaddr($inet); }