Getting all hostnames from IP address in Perl - perl

I'm trying to find a way to get all hostnames that resolve to an IP address.
The gethostbyaddr function appears to only retrieve the first record from DNS (no matter if it's in scalar or list context).
Example:
my $hostname = gethostbyaddr(inet_aton($ip_to_check), AF_INET);
$print($hostname); //output: joe.example.com
my #hostnames = gethostbyaddr(inet_aton($ip_to_check), AF_INET);
foreach my $hostname (#hostnames){
print "(", join(',',#hostnames), ")"; //output: (joe.example.com,,2,4,?)
}
From the terminal:
$ host 192.168.1.5
5.1.168.192.in-addr.arpa domain name pointer joe.example.com.
5.1.168.192.in-addr.arpa domain name pointer john.example.com.
I've heard that Net::DNS is a little more robust, but I haven't had any luck getting that to pull all entries as well.

I used a combination of answers given here and elsewhere on stack overflow to find the answer I was looking for.
# create new Resolver Object
my $res = Net::DNS::Resolver->new;
# change IP from 192.168.1.15 to 15.1.168.192.in-addr.arpa for searching
my $target_IP = join('.', reverse split(/\./, $ip_to_check)).".in-addr.arpa";
# query DNS
my $query = $res->query("$target_IP", "PTR");
# if a result is found
if ($query){
print("Resolves to:\n");
# for every result, print the IP address
foreach my $rr ($query->answer){
# show all unless the type is PTR (pointer to a canonical name)
next unless $rr->type eq "PTR";
# remove the period at the end
printf(substr($rr->rdatastr, 0, -1));
}
}

The gethostby... interface is quite old and clunky, being defined back in primeval times before Perl got references and pretensions to OO. And it doesn't work the way you're trying to use it. When used in list context, it returns the primary name as the first element and a space-separated(!) list of aliases as the second:
my ($hostname, $aliases) = gethostbyaddr($addr, AF_INET);
my #hostname = ($hostname, split ' ', $aliases);
say join ' ', #hostname;
Now that's the theory; I didn't locate any IP addresses with multiple PTR records offhand, so I can't test if gethostbyaddr will actually return them -- it probably depends on your underlying C runtime as well -- but it does work if you use gethostbyname with a CNAMEd name, for instance.

Here's a small program I use to lookup all PTR records for a netmask (for example 192.0.2.0/28 ) when doing abuse tracking tasks. It sends up to 15 queries a second and when they are all sent then starts reading the responses (so it'd need a little work to function properly for bigger net blocks).
#!/usr/bin/env perl
use strict;
use warnings;
use Net::Netmask;
use Net::DNS;
#ARGV or die "$0 ip/cidr\n";
my $block = Net::Netmask->new(shift);
my $res = Net::DNS::Resolver->new;
my %sockets;
my $i = 0;
for my $i (1 .. $block->size - 1) {
my $ip = $block->nth($i);
my $reverse_ip = join ".", reverse split m/\./, $ip;
$reverse_ip .= ".in-addr.arpa";
#print "$ip\n";
my $bgsock = $res->bgsend($reverse_ip, 'PTR');
$sockets{$ip} = $bgsock;
sleep 1 unless $i % 15;
}
$i = 0;
for my $i (1 .. $block->size - 1) {
my $ip = $block->nth($i);
my $socket = $sockets{$ip};
my $wait = 0;
until ($res->bgisready($socket)) {
print "waiting for $ip\n" if $wait > 0;
sleep 1 + $wait;
$wait++;
}
my $packet = $res->bgread($socket);
my #rr = $packet->answer;
printf "%-15s %s\n", $ip, $res->errorstring
unless #rr;
for my $rr (#rr) {
printf "%-15s %s\n", $ip, $rr->string;
}
}

I don't think this is a well-formed problem statement. In the general case, there's a nearly infinite number of DNS names that could resolve to any IP address, even unknown to the party that holds the address. Reverse-lookups are fundamentally unreliable, and are not capable of answering the question the poster would like, since all names for an IP do not need to be in the visible reverse map.
The first answer, which enumerates the reverse map, is the best one can do, but it will miss any names that have not been entered in the map.

This is what I have used:
sub getauthoritivename
{
my ($printerdns)=#_;
my $res = Net::DNS::Resolver->new(searchlist=>$config->{searchlist});
my $query = $res->search($printerdns);
if ($query)
{
foreach my $rr ($query->answer)
{
next unless $rr->type eq "A";
print $rr->name;
}
}
else
{
warn "query failed: ", $res->errorstring, "\n";
return 0;
}
}
As long as $rr->name finds names, it keeps adding them.

Related

Reversing the IP address in Linux using perl

I have a job where I need to do nslookup on an IP address. If it matches then I need to print the name of the host. The problem is that the IP address comes reversed when running the command.
nslookup 10.11.12.13
13.12.11.10.in-addr.arpa
I tried to use reverse but that reversed everything which is not what I want.
my $ip = '13.12.11.10';
$result = reverse($ip);
print $result;
which then prints 01.11.21.31
I do not want to reverse everything, just the full numbers.
Please can someone help?
Simply split the IP address on .s using split, reverse the resulting array, then rejoin it:
join(".", reverse(split(/\./, $ip)))
This will give you the "reversed" IP address, which you can then compare to the nslookup result.
So what we need to do is to just split the actual address, reorder them in reverse and then match the IP to the reversed IP.
use strict;
use warnings;
my $ipaddress = '10.11.12.13';
my #ip = split /\./,$ipaddress; #split the IP by .
my $sserddapi = "$ip[3].$ip[2].$ip[1].$ip[0]"; #reverse it
my #lookup = `nslookup $ipaddress`; #do the match
$lookup[3] =~ s/\s+//g; #remove all whitespace
my #device = split /=/, $lookup[3]; #get the hostname
if ($lookup[3] =~ /^$sserddapi/) { #see if it matches
$lookup[3] =~ s/$sserddapi.in-addr.arpaname=//g; #Remove the unwanted stuff
print "$ipaddress = $lookup[3]\n"; #print the result
}

How to extract all Ping Parameters in Perl

I am trying to ping around 100 hosts from a column in sql database
I am using the
use Net::Ping;
use Array::Average; modules
Is there an alternative such that i can extract the packet loss,rtt(min,max,avg) parameters directly from a perl script?
Here is my pseudo code
$p = Net::Ping->new('icmp');
$p->hires();
$host = "www.xyz.com";
print "$host \n";
#rtt= 0;
$j=0;
for ($i=0;$i<5;$i++) {
($ret, $duration, $ip) = $p->ping($host, 5);# wait time 5
if($ret){
printf("$host [ip: $ip] $duration ms\n");
$rtt[$i] = $duration;
}
else{
$j++;
#$p->nack( $failed_ack_host );
}}
print " #rtt\n";
$rtt= average(#rtt);
print "The average rtt is $rtt \n";
$Packet_Loss = ((5-$j)/5)*100;
print "$Packet_Loss\%\n";
`Net::Ping' does not seem to return RTT, etc. You might try issuing the system ping command, and processing the results yourself:
$result = `ping -c 10 $host`;
...then parse $result for the fields you need.
Looking at the documentation and source code, the Net::Ping module carries out just one "ping" each time you call the ping() method. It does not keep any internal "state" based on the success or otherwise of previous calls. The advantage of this approach is that it's simple and no-nonsense. However, if you want aggregated results, you're going to have to do them yourself, as you've discovered.
While it's tempting to call an external ping command, you need to be sure that's what you want: you gain not having to do some maths, but you then become more system-specific (not all systems have a ping command), you are dependent on some assumptions about what options to pass to the command and what format the output will be in, you've added the overhead of creating a new process, and you're running the risk that you may never get control back (some ping commands simply run until you interrupt them). Personally, I'd stick with the approach you're taking.
Also, in your particular code above, have a look at how you're storing your results. If the first three and last ping succeed, say, but the last-but-one doesn't, you're passing an array to average() with an undefined value, which is probably not what you want. I'd suggest something like this instead:
my #rtt;
my $attempts = 5;
foreach (1 .. $attempts) {
my ($ret, $duration, $ip) = $p->ping($host, 5); # timeout after 5 seconds
if ($ret) {
print "$host [ip: $ip] $duration ms\n";
push #rtt, $duration;
} else {
$j++;
}
}
⋮
if (#rtt) {
my $rtt= average(#rtt);
print "The average rtt is $rtt \n";
} else {
print "No responses to ping\n";
}
This fixes one or two other corner cases as well.

Perl: the 'semi' infinite loop?

I have here a working code, It works fine with 8 or 10 emails, but if you just put 20 emails it never finishes computing. That is, it is not an infinite loop because otherwise it would never compute anything. Also, if you use just 10 emails but ask it to make lists of more than 2, same thing happens. Yes, as pointed out, there is a while(#address) and somewhere in there, a push into address, that is the reason. I tried to replace that array into which it was pushed by another name, but i get weird errors like it picks one email from the list and it will complain that while strict references are on, i cant use that ...
I understand 100% the code up until the 'map' line. After that, not so much...
If we look at this part:
push #addresses, $address;
$moved{$address}++;
# say "pushing $address to moved"; # debug
one would say that the variable $address would have to be pushed, not into the #addresses, as that is the source of data (hence the loop as pointed out) but onto ..'moved' but, sorry, 'moved' is a hash. You can't push a variable into a hash, can you ? should then 'moved' be actually an array and not a hash? this is where i get lost
I was thinking about this instead, but ...it is just intuition, not real knowledge
push #{ $moved[$i] }, $address
I think I have solved it, taking as departure point the remark from 'Konerak'. Indeed the issue was a never decreasing list. Because I am not knowledgeable in reference arrays I was kind of lost, but somehow reading the code I tried to find similarity in a expected behaviour.
Therefore I created another array called #reserva and I wrote this:
push # {$reserva [$i]}, $address
instead of
push #addresses, $address;
Now, I get lists the size I want regardless of how many emails I enter. I tried with 1000 and had no problem in less than one second.
So, here is the full code
use strict;
use warnings;
use feature 'say';
use Data::Dumper;
my $only_index = 3; # Read from command line with $ARGV[0] or use Getopt::Long
my %blacklist = ( # Each key in this hash represents one index/day
'2' => [ 'a', 'b' ], # and has an arrayref of domains that have replied on
'3' => [ 'c' ], # that day. We look at all keys smaller than the current
); # index in each iteration and ignore all these domains
my #domains; # holds the domains we have already seen for each list
my #lists = ([]); # Holds all the lists
my %moved; # the addresses we moved to the back
my $i = 0;
my #addresses = <DATA>;
while (#addresses) {
my $address = shift #addresses;
chomp $address;
$address =~ m/#([a-zA-Z0-9\-.]*)\b/;
my $domain = $1;
# If the domain has answered, do not do it again
next if
grep { /$domain/ }
map { exists $blacklist{$_} ? #{ $blacklist{$_} } : () } (0..$i);
$i++ if (#{ $lists[$i] } == 2
|| (exists $moved{$address} && #addresses < 1));
if (exists $domains[$i]->{$domain}) {
push #addresses, $address;
$moved{$address}++;
# say "pushing $address to moved"; # debug
} else {
$domains[$i]->{$domain}++;
# send the email
# say "added $address to $i"; # debug
push #{ $lists[$i] }, $address;
}
}
# print Dumper \#lists; # Show all lists
print Dumper $lists[$only_index]; # Only show the selected list
1;
__DATA__
1#a
2#a
3#a
1#b
2#b
1#c
2#c
3#c
1#d
2#d
3#d
4#d
1#e
1#f
1#g
1#h
4#a
5#a
4#c
That's some twisty code, it's no wonder you're having trouble following it. I'm not actually sure what the body of the code is supposed to accomplish, but you can at least avoid the infinite loop by not using while (#array) - use foreach my $item (#array) instead and you'll iterate over it and avoid and bizarre behavior that will arise from modifying the array inside the loop.
chomp(#addresses); # chomp called on an array chomps each element
foreach my $address (#addresses) {
# Do work here
}

Perl need the right grep operator to match value of variable

I want to see if I have repeated items in my array, there are over 16.000 so will automate it
There may be other ways but I started with this and, well, would like to finish it unless there is a straightforward command. What I am doing is shifting and pushing from one array into another and this way, check the destination array to see if it is "in array" (like there is such a command in PHP).
So, I got this sub routine and it works with literals, but it doesn't with variables. It is because of the 'eq' or whatever I should need. The 'sourcefile' will contain one or more of the words of the destination array.
// Here I just fetch my file
$listamails = <STDIN>;
# Remove the newlines filename
chomp $listamails;
# open the file, or exit
unless ( open(MAILS, $listamails) ) {
print "Cannot open file \"$listamails\"\n\n";
exit;
}
# Read the list of mails from the file, and store it
# into the array variable #sourcefile
#sourcefile = <MAILS>;
# Close the handle - we've read all the data into #sourcefile now.
close MAILS;
my #destination = ('hi', 'bye');
sub in_array
{
my ($destination,$search_for) = #_;
return grep {$search_for eq $_} #$destination;
}
for($i = 0; $i <=100; $i ++)
{
$elemento = shift #sourcefile;
if(in_array(\#destination, $elemento))
{
print "it is";
}
else
{
print "it aint there";
}
}
Well, if instead of including the $elemento in there I put a 'hi' it does work and also I have printed the value of $elemento which is also 'hi', but when I put the variable, it does not work, and that is because of the 'eq', but I don't know what else to put. If I put == it complains that 'hi' is not a numeric value.
When you want distinct values think hash.
my %seen;
#seen{ #array } = ();
if (keys %seen == #array) {
print "\#array has no duplicate values\n";
}
It's not clear what you want. If your first sentence is the only one that matters ("I want to see if I have repeated items in my array"), then you could use:
my %seen;
if (grep ++$seen{$_} >= 2, #array) {
say "Has duplicates";
}
You said you have a large array, so it might be faster to stop as soon as you find a duplicate.
my %seen;
for (#array) {
if (++$seen{$_} == 2) {
say "Has duplicates";
last;
}
}
By the way, when looking for duplicates in a large number of items, it's much faster to use a strategy based on sorting. After sorting the items, all duplicates will be right next to each other, so to tell if something is a duplicate, all you have to do is compare it with the previous one:
#sorted = sort #sourcefile;
for (my $i = 1; $i < #sorted; ++$i) { # Start at 1 because we'll check the previous one
print "$sorted[$i] is a duplicate!\n" if $sorted[$i] eq $sorted[$i - 1];
}
This will print multiple dupe messages if there are multiple dupes, but you can clean it up.
As eugene y said, hashes are definitely the way to go here. Here's a direct translation of the code you posted to a hash-based method (with a little more Perlishness added along the way):
my #destination = ('hi', 'bye');
my %in_array = map { $_ => 1 } #destination;
for my $i (0 .. 100) {
$elemento = shift #sourcefile;
if(exists $in_array{$elemento})
{
print "it is";
}
else
{
print "it aint there";
}
}
Also, if you mean to check all elements of #sourcefile (as opposed to testing the first 101 elements) against #destination, you should replace the for line with
while (#sourcefile) {
Also also, don't forget to chomp any values read from a file! Lines read from a file have a linebreak at the end of them (the \r\n or \n mentioned in comments on the initial question), which will cause both eq and hash lookups to report that otherwise-matching values are different. This is, most likely, the reason why your code is failing to work correctly in the first place and changing to use sort or hashes won't fix that. First chomp your input to make it work, then use sort or hashes to make it efficient.

Reverse DNS lookup in perl

How do I perform a reverse DNS lookup, that is how do I resolve an IP address to its DNS hostname in Perl?
If you need more detailed DNS info use the Net::DNS module, here is an example:
use Net::DNS;
my $res = Net::DNS::Resolver->new;
# create the reverse lookup DNS name (note that the octets in the IP address need to be reversed).
my $IP = "209.85.173.103";
my $target_IP = join('.', reverse split(/\./, $IP)).".in-addr.arpa";
my $query = $res->query("$target_IP", "PTR");
if ($query) {
foreach my $rr ($query->answer) {
next unless $rr->type eq "PTR";
print $rr->rdatastr, "\n";
}
} else {
warn "query failed: ", $res->errorstring, "\n";
}
Original Source EliteHackers.info, more details there as well.
gethostbyaddr and similar calls. See http://perldoc.perl.org/functions/gethostbyaddr.html
use Socket;
$iaddr = inet_aton("127.0.0.1"); # or whatever address
$name = gethostbyaddr($iaddr, AF_INET);
perl -MSocket -E 'say scalar gethostbyaddr(inet_aton("69.89.27.250"), AF_INET)'
Returns: Can't find string terminator "'" anywhere before EOF at -e line 1.
perl -MSocket -E "say scalar gethostbyaddr(inet_aton(\"69.89.27.250\"), AF_INET)"
Returns: box250.bluehost.com
I have to change the line to use double quotes and then escape out the quotes around the IP address
one-liner:
perl -MSocket -E 'say scalar gethostbyaddr(inet_aton("79.81.152.79"), AF_INET)'
There may be an easier way, but for IPv4, if you can perform ordinary DNS lookups, you can always construct the reverse query yourself. For the IPv4 address A.B.C.D, look up any PTR records at D.C.B.A.in-addr.arpa. For IPv6, you take the 128 hex nibbles and flip them around and append ipv6.arpa. and do the same thing.
If gethostbyaddr doesn't fit your needs, Net::DNS is more flexible.
This might be useful...
$ip = "XXX.XXX.XXX.XXX" # IPV4 address.
my #numbers = split (/\./, $ip);
if (scalar(#numbers) != 4)
{
print "$ip is not a valid IP address.\n";
next;
}
my $ip_addr = pack("C4", #numbers);
# First element of the array returned by gethostbyaddr is host name.
my ($name) = (gethostbyaddr($ip_addr, 2))[0];