Beefy Boxes and Bandwidth Generously Provided by pair Networks
Clear questions and runnable code
get the best and fastest answer
 
PerlMonks  

Need to calculate IP address

by ddrew78 (Beadle)
on Dec 28, 2017 at 18:57 UTC ( [id://1206349]=perlquestion: print w/replies, xml ) Need Help??

ddrew78 has asked for the wisdom of the Perl Monks concerning the following question:

Greetings monks,

As usual, I have a problem that has me a little stumped. I have been using the same code for years to calculate IP addresses. However, now I need to make that manipulation dependent on whether the last octet in a given IP address is odd or even. Basically, if the IP address is 10.1.1.6 for example, I need a new variable to be 10.1.1.5. If the IP address is 10.1.1.5, I need the new variable to be 10.1.1.6. Below is what I have thus far
#!/usr/bin/perl print "Enter the IP address\n"; $tunip = <STDIN>; chomp ($tunip); my $period="."; my @ip_fields = split(/\./, $tunip); my ($bgp_ip,$test_ip); if ($ip_fields[3] % 2) { $test_ip=$ip_fields[3]-1; } else { $test_ip=$ip_fields[3]+1; } $bgp_ip = $ip_fields[0].$period.$ip_fields[1].$period.$ip_fields[2].$p +eriod.$test_ip;
For some reason, this just leaves '$bgp_ip' blank. Any wisdom at all would be greatly appreciated.

Replies are listed 'Best First'.
Re: Need to calculate IP address
by kcott (Archbishop) on Dec 28, 2017 at 20:19 UTC

    G'day ddrew78,

    "Any wisdom at all would be greatly appreciated."

    It would be wise to:

    • ask Perl to do some basic checks for you (see strict and warnings);
    • do some basic checking yourself (e.g. does @ip_fields contain exactly four elements all of which match /^\d+$/);
    • use lexical variables (e.g. $tunip is not lexical); and,
    • show us actual input and output, which might include warning or error messages, such that we can comment on why particular input "just leaves '$bgp_ip' blank" (see "How do I post a question effectively?").

    Furthermore, your descriptive text and programmed logic are at odds with each other:

    $ perl -E 'my @x = split /\./, "10.1.1.5"; $x[3] % 2 ? --$x[3] : ++$x[ +3]; say join ".", @x' 10.1.1.4 $ perl -E 'my @x = split /\./, "10.1.1.6"; $x[3] % 2 ? --$x[3] : ++$x[ +3]; say join ".", @x' 10.1.1.7

    You could reverse the logic to match the text:

    $ perl -E 'my @x = split /\./, "10.1.1.5"; $x[3] % 2 ? ++$x[3] : --$x[ +3]; say join ".", @x' 10.1.1.6 $ perl -E 'my @x = split /\./, "10.1.1.6"; $x[3] % 2 ? ++$x[3] : --$x[ +3]; say join ".", @x' 10.1.1.5

    But then you'd have edge case problems not addressed by either your description or code:

    $ perl -E 'my @x = split /\./, "10.1.1.0"; $x[3] % 2 ? ++$x[3] : --$x[ +3]; say join ".", @x' 10.1.1.-1 $ perl -E 'my @x = split /\./, "10.1.1.255"; $x[3] % 2 ? ++$x[3] : --$ +x[3]; say join ".", @x' 10.1.1.256

    So, there's potentially issues at a fundamental level (e.g. problem analysis, solution design, coding logic); and there's clearly problems with a lack of basic checking and validation.

    — Ken

Re: Need to calculate IP address
by johngg (Canon) on Dec 29, 2017 at 00:39 UTC

    You could use inet_aton() and inet_ntoa() from the core Socket module for converting IPs. Use unpack to obtain a numeric value you can do an addition with. Then pack the result and convert back to a textual IP address.

    johngg@shiraz:~/perl/Monks > perl -Mstrict -Mwarnings -MSocket -E ' do { print qq{Old IP: $_ ... }; my $isOdd = substr( $_, -1 ) % 2; my $IPn = unpack q{N}, inet_aton( $_ ); $IPn += ( $isOdd ? 1 : -1 ); my $newIP = inet_ntoa( pack( q{N}, $IPn ) ); say qq{New IP: $newIP}; } for qw{ 10.1.1.5 10.1.1.6 };' Old IP: 10.1.1.5 ... New IP: 10.1.1.6 Old IP: 10.1.1.6 ... New IP: 10.1.1.5

    I hope this is of interest.

    Update: Turning the one-liner into a script and adding an error check for invalid IPs (inet_aton() returns undef if given an invalid address). Because each valid IPv4 quad is in the range 0 .. 255 the "numeric" value produced by inet_aton() is actually a 4-byte string, e.g.

    johngg@shiraz:~/perl/Monks > perl -Mstrict -Mwarnings -MSocket -E ' my $IP = q{10.1.1.255}; my $packed = inet_aton( $IP ); printf qq{%8d %8d %8d %8d\n}, map ord, split m{}, $packed; printf qq{%8s %8s %8s %8s\n}, unpack q{(B8)*}, $packed;' 10 1 1 255 00001010 00000001 00000001 11111111

    That's why unpacking the string to a network-order number allows us to increment or decrement and also takes care of the roll-over when incrementing 255 or decrementing 0. The script.

    use strict; use warnings; use Socket; my @IPs = qw{ 10.1.1.5 10.1.1.6 172.16.0.0 192.168.33.17 192.168.331.54 192.168.33.74 172.16.204.255 0.0.0.0 255.255.255.255 10.1.255.255 }; foreach my $IP ( @IPs ) { my $packed = inet_aton( $IP ); do { warn qq{Error: $IP is not a valid IPv4 address\n}; next; } unless defined $packed; my $numeric = unpack q{N}, $packed; my $isOdd = substr( $IP, -1 ) % 2; $numeric += ( $isOdd ? 1 : -1 ); my $newIP = inet_ntoa( pack( q{N}, $numeric ) ); printf qq{Old IP: %15s ... New IP: %15s\n}, $IP, $newIP; }

    The output.

    Old IP: 10.1.1.5 ... New IP: 10.1.1.6 Old IP: 10.1.1.6 ... New IP: 10.1.1.5 Old IP: 172.16.0.0 ... New IP: 172.15.255.255 Old IP: 192.168.33.17 ... New IP: 192.168.33.18 Error: 192.168.331.54 is not a valid IPv4 address Old IP: 192.168.33.74 ... New IP: 192.168.33.73 Old IP: 172.16.204.255 ... New IP: 172.16.205.0 Old IP: 0.0.0.0 ... New IP: 255.255.255.255 Old IP: 255.255.255.255 ... New IP: 0.0.0.0 Old IP: 10.1.255.255 ... New IP: 10.2.0.0

    Update 2: Used printf to make the explanatory one-liner output more legible.

    Update 3: Corrected spelling mistake s{adress}{address} d'oh.

    Cheers,

    JohnGG

Re: Need to calculate IP address
by tybalt89 (Monsignor) on Dec 28, 2017 at 19:09 UTC
    #!/usr/bin/perl # http://perlmonks.org/?node_id=1206349 use strict; use warnings; print "Enter the IP address\n"; my $tunip = <STDIN>; chomp ($tunip); print $tunip =~ s/\d+$/ ($& - 1 ^ 1) + 1 /er, "\n";

    Updated conversion

    Updated again

      s/\d+$/ $& ^ 1 /er

      A neat solution, of course, but unfortunately it doesn't meet the OP's specs: .5 becomes .4 and .6 becomes .7.

        I think the example is wrong. What should a 0 become? Or a 9?

Re: Need to calculate IP address
by haukex (Archbishop) on Dec 28, 2017 at 19:49 UTC
    this just leaves '$bgp_ip' blank

    Sorry, I can't reproduce this, your code actually works for me. Perhaps you just made a typo? Always Use strict and warnings! However, at the moment the code is doing the same as tybalt89's XOR solution (Update: at least on your sample input that is) - if you want the logic to be the way you describe (.5 becomes .6 and vice versa), you need to reverse the +/- operations. On the other hand, tybalt89 made a good point here - are you sure about the logic you described?

      I did notice that I had the + and - reversed by accident. This was incorrect in my initial code and has been corrected there as well. However, even with that correction, my variable '$bgp_ip' still has no value.
        my variable '$bgp_ip' still has no value

        I just re-downloaded the code from the root node and ran it, the only change I made was to add a print of $bgp_ip, and that variable is definitely not empty. So either the code you have posted here is not the code you are running, or there is some other mistake - for example, perhaps you have a typo in the variable name that is not being caught due to a missing strict. Please either post the actual code you are running or use the tips from the Basic debugging checklist to hunt down the problem in your code.

Re: Need to calculate IP address
by ikegami (Patriarch) on Dec 29, 2017 at 23:32 UTC

    If you want to do arithmetic on IPv4 addresses, it's easier to convert them into numbers first.

    my $ip_num = unpack 'N', pack 'C4', split /\./, $ip_dotted; $ip_num = ( ( $ip_num - 1 ) ^ 1 ) + 1; $ip_dotted = join '.', unpack 'C4', pack 'N', $ip_num;

    Note that inet_aton $ip_dotted (from Socket) can be used instead of pack 'C4', split /\./, $ip_dotted.
    Note that inet_ntoa $ip_packed (from Socket) can be used instead of join '.', unpack 'C4', $ip_packed.

Re: Need to calculate IP address
by thanos1983 (Parson) on Dec 29, 2017 at 15:38 UTC

    Hello ddrew78,

    It looks the fellow Monks, have answered your question. I just wanted to add something minor here.

    What you are trying to achieve is also possible to be done with the module NetAddr::IP.

    Sample of code:

    #!/usr/bin/perl use strict; use warnings; use NetAddr::IP; use feature 'say'; say NetAddr::IP->new('127.0.0.1/8') + 255; say NetAddr::IP->new('127.0.1.6/8') - 255; __END__ $ perl test.pl 127.0.1.0/8 127.0.0.7/8

    Hope this helps, BR.

    Seeking for Perl wisdom...on the process of learning...not there...yet!
Re: Need to calculate IP address
by Anonymous Monk on Dec 29, 2017 at 17:47 UTC
    Thanks everyone for the help. For some reason, none of the solutions below wanted to work within my script. I ended up having the script write the last octet to a file and then modifying it from there, which worked. Makes no sense as to why I would have to go thru that, but it is what it is. Below is what I ended up with that works.
    print "Enter the Tunnel Interface IP "; $tunip = <>; chomp ($tunip); $test2 = check_valid_ip2($tunip); print "\n"; my $period="."; my @ip_fields = split(/\./, $tunip); open (LASTOCTET, ">lastoctet"); print LASTOCTET "$ip_fields[3]\n"; close (LASTOCTET); open (MYINPUTFILE, "lastoctet"); while (<MYINPUTFILE>) { my($line) = $_; chomp($line); $ipcheck = $line; } close (MYINPUTFILE); if ($ipcheck % 2) { $test_ip=$ipcheck+1; } else { $test_ip=$ipcheck-1; } $bgpip = $ip_fields[0].$period.$ip_fields[1].$period.$ip_fields[2].$pe +riod.$test_ip;

      Hello Anonymous Monk,

      For some reason, none of the solutions below wanted to work within my script.. Can you try this one, if it is working?

      It is really strange that you need to do this much work, read and write to a file for IP manipulations.

      #!/usr/bin/env perl use strict; use warnings; use NetAddr::IP; use feature 'say'; my $final; my $input = '127.0.0.3'; my @ip = split /\./, $input; if ($ip[3] % 2) { $final = join '.', @ip; $final = NetAddr::IP->new($final.'/8') + 1; } else { $final = join '.', @ip; $final = NetAddr::IP->new($final.'/8') - 1; } $final = substr $final, 0, -2; say $final; __END__ $ perl test.pl 127.0.0.4

      BR / Thanos

      Seeking for Perl wisdom...on the process of learning...not there...yet!

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1206349]
Approved by 1nickt
Front-paged by 1nickt
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others romping around the Monastery: (6)
As of 2024-04-24 09:31 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found