0

I have a log file that lists whitelisted IP ranges in this manner:

"217.29.0.0-217.29.255.255",
"204.12.0.0-204.12.255.255",
"198.54.223.0-198.54.223.255",

I am also working on a bash script which, amongst other things, needs to check this logfile and determine if a specified IP is whitelisted.

In the case of IP 204.12.5.10, for instance, how would I use the log file with the specified ranges to determine whether that IP is part of a range in that file?

  • Does it have to be bash? And do you need to support different address formats? – Sobrique Oct 5 '15 at 12:17
  • It has to be bash, or something called from bash (such as the ipcalc utility). The only format I will need to do this for is the above-given [range-start]-[range-end] format, as that is the format this specific logfile uses. – wbruan Oct 5 '15 at 12:19
  • Nopte that the aaa.bbb.ccc.ddd format is just custom, but you can trivially convert that to any other format. And if you convert it to decimal then you can simply check with the larger than and small than operators. (actually => and =<, since you probably want to include the edges). – Hennes Oct 5 '15 at 12:28
2

The useful thing to remember with IP addresses is that they're actually just representations of a 32bit number.

So you can convert them to a decimal by multiplying the octets as base 256.

E.g.:

217.29.0.0 == 216 * 256 ^ 3 + 29 * 256 ^2 + 0 * 256 ^1 + 0 * 256 ^ 0
           == 3640655872

So given a range like the above - first convert them both, such that you've got a start and end. Then compare any incoming IPs to see if they 'fit'.

So something like this (illustrating algorithm):

#!/usr/bin/env perl
use strict;
use warnings;

my $ip_to_check = "204.12.5.10";
my $dword       = 0;
for ( split( '\.', $ip_to_check ) ) { $dword *= 256; $dword += $_ }
print "Checking $dword\n";

while (<DATA>) {
    my ( $start, $finish ) = m/([\d\.]+)/g;
    my $start_dword = 0;
    for ( split '\.', $start ) { $start_dword *= 256; $start_dword += $_ }
    my $end_dword = 0;
    for ( split '\.', $finish ) { $end_dword *= 256; $end_dword += $_ }
    print "Range:\n";
    print "\t$start \t=> $start_dword\n";
    print "\t$finish \t=> $end_dword\n";

    print "$ip_to_check is in $_\n" if $dword >= $start_dword and $dword <= $end_dword;
}

__DATA__
"217.29.0.0-217.29.255.255",
"204.12.0.0-204.12.255.255",
"198.54.223.0-198.54.223.255",

Could quite easily do that as a 'check script' - by reading a file (rather than DATA) and getting $ip_to_check from STDIN).

(note - as pure perl could be turned into a one liner fairly easily, or a script to call. Or re-written - I daresay you could do this with expr easily enough).

  • Though perl and not bash, this did work for my purposes. Thank you. – wbruan Oct 7 '15 at 5:08
  • Yes, I'm afraid I defected to perl many years ago when I started to stretch the limits of what could be done in bash. Fortunately, it's almost as ubiquitous, so you're not really inhibiting yourself much. (And in some cases - it's more common, for example if you're ever having to interact with AIX or another OS that doesn't do bash by default) – Sobrique Oct 7 '15 at 9:09
1

Here's an awk version:

want=204.12.77.5
awk -v want=$want  -F- '
function canon(ip){
    split(" "ip,x,/[^0-9]+/)
    return sprintf("%03d%03d%03d%03d",x[2],x[3],x[4],x[5])
}
BEGIN { val = canon(want) }
{ low = canon($1); high = canon($2);
  if(val>=low && val<=high)print "in range " $0
}' mylogfile

The function canon takes an ip address, splits out the number fields, then extends each to 3 digits and returns the string. It is called for the 2 values on each line, and these are compared with the wanted ip address (set at the start), which has also been "canonified".

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.