#!/usr/bin/perl # IBM(c) 2016 EPL license http://www.eclipse.org/legal/epl-v10.html BEGIN { $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : -d '/opt/xcat' ? '/opt/xcat' : '/usr'; } use lib "$::XCATROOT/probe/lib/perl"; use probe_utils; use File::Basename; use IO::Socket::INET; use Time::HiRes qw(gettimeofday sleep); use Getopt::Long; Getopt::Long::Configure("bundling"); Getopt::Long::Configure("pass_through"); my $program_name = basename("$0"); my $output = "stdout"; my $duration = 10; my $test = 0; my $dumpfile = "/tmp/dhcpdumpfile.log"; my $nic; $::USAGE = "Usage: $program_name -i interface [-m macaddress] [-d duration] [-V] Description: This command can be used to detect the dhcp server in a network for a specific mac address. Options: -i interface: Required. The interface facing the target network. -m macaddress: The mac that will be used to detect dhcp server. Use the real mac of the node that will be netboot. If not specified, the mac specified by -i will be used. -d duration: The time to wait to detect the dhcp messages. The default value is 10s. -V verbose: Print additional debug information. "; #--------------------------- # main #--------------------------- if (!GetOptions( 'i=s' => \$::IF, 'm=s' => \$::MACADD, 'd=s' => \$::DURATION, 'T' => \$::TEST, 'V|verbose' => \$::VERBOSE, 'h|help' => \$::HELP,)) { probe_utils->send_msg("$output", "f", "Invalid parameter for $program_name"); probe_utils->send_msg("$output", "d", "$::USAGE"); exit 1; } if ($::HELP) { if ($output ne "stdout") { probe_utils->send_msg("$output", "d", "$::USAGE"); } else { print "$::USAGE"; } exit 0; } if ($::TEST) { probe_utils->send_msg("$output", "o", "$program_name can be used to detect the dhcp server in a network for a specific mac address. Before using this command, install 'tcpdump' command. The operating system supported are RedHat, SLES and Ubuntu."); exit 0; } unless (-x "/usr/sbin/tcpdump") { probe_utils->send_msg("$output", "f", "Tool 'tcpdump' is installed on current server"); probe_utils->send_msg("$output", "d", "$program_name needs to leverage 'tcpdump', please install 'tcpdump' first"); exit 1; } if ($::IF) { $nic = $::IF; } else { probe_utils->send_msg("$output", "f", "Option '-i' needs to be assigned a value for $program_name"); probe_utils->send_msg("$output", "d", "$::USAGE"); exit 1; } my $msg = "Find correct IP/MAC to do dhcp discover"; my $IP = `ip addr show dev $nic | awk -F" " '/inet / {print \$2}' | head -n 1 |awk -F"/" '{print \$1}'`; chomp($IP); my $MAC; if ($::MACADD) { $MAC = $::MACADD; } else { $MAC = `ip link show $nic | awk -F" " '/ether/ {print \$2}'`; } chomp($MAC); probe_utils->send_msg("$output", "d", "Send dhcp discover from: NIC = $nic, IP = $IP, MAC = $MAC") if ($::VERBOSE); if (!$IP || !$MAC) { probe_utils->send_msg("$output", "f", $msg); exit 1; } # check the distro $msg = "The operating system on current server is supported"; my $os; if (-f "/etc/redhat-release") { $os = "rh"; } elsif (-f "/etc/SuSE-release") { $os = "sles"; } elsif (-f "/etc/lsb-release") { $os = "ubuntu"; #} elsif (-f "/etc/debian_version") { # $os = "debian"; } else { probe_utils->send_msg("$output", "f", $msg); probe_utils->send_msg("$output", "d", "Only support the RedHat, SLES and Ubuntu."); exit 1; } probe_utils->send_msg("$output", "d", "Current operating system is $os") if ($::VERBOSE); if ($::DURATION) { $duration = $::DURATION; } probe_utils->send_msg("$output", "d", "The duration of capturing DHCP package is $duration second(s)") if ($::VERBOSE); # send out the package $msg = "Build the socket to send out DHCP request"; my $sock = IO::Socket::INET->new(Proto => 'udp', Broadcast => 1, PeerPort => '67', LocalAddr => $IP, LocalPort => '68', PeerAddr => inet_ntoa(INADDR_BROADCAST)); # try the any port if localport 68 has been used unless ($sock) { $sock = IO::Socket::INET->new(Proto => 'udp', Broadcast => 1, PeerPort => '67', LocalAddr => $IP, PeerAddr => inet_ntoa(INADDR_BROADCAST)); } unless ($sock) { probe_utils->send_msg("$output", "d", "Create socket error: $@") if ($::VERBOSE); probe_utils->send_msg("$output", "f", $msg); exit 1; } my $package = packdhcppkg($MAC); probe_utils->send_msg("$output", "i", "Start to detect DHCP, please wait $duration seconds"); $msg = "fork a process to capture the packet by tcpdump"; my $pid = fork; if (!defined $pid) { probe_utils->send_msg("$output", "f", $msg); exit 1; } elsif ($pid == 0) { # Child process my $cmd = "tcpdump -i $nic port 68 -n -vvvvvv > $dumpfile 2>/dev/null"; `$cmd`; exit 0; } probe_utils->send_msg("$output", "d", "The id of process which is used to capture the packet by tcpdump is $pid") if ($::VERBOSE); my $start = Time::HiRes::gettimeofday(); $start =~ s/(\d.*)\.(\d.*)/$1/; my $end = $start; while ($end - $start <= $duration) { $sock->send($package); probe_utils->send_msg("$output", "d", "Send DHCP rquest result: $@") if ($::VERBOSE && $@); sleep 2; $end = Time::HiRes::gettimeofday(); $end =~ s/(\d.*)\.(\d.*)/$1/; } $msg = "Kill the process which is used to capture the packet by tcpdump"; kill_child(); waitpid($pid, 0); sleep 1; `ps aux|grep -v grep |grep $pid > /dev/null 2>&1`; if (!$?) { probe_utils->send_msg("$output", "f", $msg); } $msg = "Dump test result"; unless (open(FILE, "<$dumpfile")) { probe_utils->send_msg("$output", "d", "Open dump file $dumpfile failed") if ($::VERBOSE); probe_utils->send_msg("$output", "f", $msg); `rm -f $dumpfile` if (-e "$dumpfile"); exit 1; } my %output; my @snack = (); my @siaddr = (); my $newsection = 0; my $offer = 0; $chaddr = (); $ciaddr = (); $siaddr = (); probe_utils->send_msg("$output", "d", "Dump all the information captured during last $duration seconds") if ($::VERBOSE); while () { $line = $_; if ($line =~ /^\d\d:\d\d:\d\d/) { # A new packet was captured. Parse the last one. probe_utils->send_msg("$output", "d", "The server found: mac = $chaddr, clientip = $ciaddr, serverip = $siaddr, offer = $offer") if ($::VERBOSE); if ($os eq "sles") { $offer = 1; } if ($chaddr =~ /$MAC/i && $offer && $ciaddr && $siaddr && $rsiaddr) { $output{$rsiaddr}{'client'} = $ciaddr; $output{$rsiaddr}{'nextsv'} = $siaddr; } elsif ($nack && $siaddr && !grep(/^$siaddr$/, @snack)) { push @snack, $siaddr; } elsif ($siaddr && !grep(/^$siaddr$/, @server)) { push @server, $siaddr; } $offer = 0; $nack = 0; $chaddr = (); $ciaddr = (); $siaddr = (); $rsiaddr = (); # the server which responsing the dhcp request } if ($line =~ /(\d+\.\d+\.\d+\.\d+)\.[\d\w]+ > \d+\./) { $rsiaddr = $1; } if ($line =~ /\s*DHCP-Message.*: Offer/) { $offer = 1; } elsif ($line =~ /\s*DHCP-Message.*: NACK/) { $nack = 1; } if ($line =~ /\s*Client-Ethernet-Address (..:..:..:..:..:..)/) { $chaddr = $1; } if ($line =~ /\s*Your-IP (\d+\.\d+\.\d+.\d+)/) { $ciaddr = $1; } if ($line =~ /\s*Server-IP (\d+\.\d+\.\d+.\d+)/) { $siaddr = $1; } } close(FILE); my $sn = scalar(keys %output); probe_utils->send_msg("$output", "i", "++++++++++++++++++++++++++++++++++"); probe_utils->send_msg("$output", "i", "There are $sn servers replied to dhcp discover."); foreach my $server (keys %output) { probe_utils->send_msg("$output", "i", " Server:$server assign IP [$output{$server}{'client'}]. The next server is [$output{$server}{'nextsv'}]!"); } probe_utils->send_msg("$output", "i", "++++++++++++++++++++++++++++++++++"); if (scalar(@snack)) { probe_utils->send_msg("$output", "i", "==================================="); probe_utils->send_msg("$output", "i", "The dhcp servers sending out NACK in present network:"); foreach my $nack (@snack) { probe_utils->send_msg("$output", "i", " $nack"); } } if (scalar(@server)) { probe_utils->send_msg("$output", "i", "==================================="); probe_utils->send_msg("$output", "i", "The dhcp servers in present network:"); foreach my $s (@server) { probe_utils->send_msg("$output", "i", " $s"); } } `rm -f $dumpfile` if (-e "$dumpfile"); exit 0; sub packdhcppkg { my $mymac = shift; my $package; # add the operation type. 1 - request $package .= pack("C*", 1); # add the hardware type. 1 - ethernet $package .= pack("C*", 1); # add the length of hardware add $package .= pack("C*", 6); # add the hops $package .= pack("C*", 0); # add the transaction id $package .= pack("C*", 60, 61, 62, 63); # add the elapsed time $package .= pack("C*", 0, 0); # add the flag 00 - broadcast $package .= pack("C*", 128, 0); # add the IP of client $package .= pack("C*", 0, 0, 0, 0); # add the your IP $package .= pack("C*", 0, 0, 0, 0); # add the next server IP $package .= pack("C*", 0, 0, 0, 0); # add the relay agent IP $package .= pack("C*", 0, 0, 0, 0); # add the mac address of the client my @macval; if ($mymac) { my @strmac = split(/:/, $mymac); foreach (@strmac) { push @macval, hex($_); } $package .= pack("C*", @macval); } else { @macval = ('0', '0', '50', '51', '52', '53'); $package .= pack("C*", @macval); } # add 10 padding for mac my @macpad; foreach (1 .. 10) { push @macpad, "0"; } $package .= pack("C*", @macpad); # add the hostname of server my @hs; foreach (1 .. 64) { push @hs, "0"; } $package .= pack("C*", @hs); # add the file name my @fn; foreach (1 .. 128) { push @fn, "0"; } $package .= pack("C*", @fn); # add the magic cookie $package .= pack("C*", 99, 130, 83, 99); # add the dhcp message type. The last num: 1 - dhcp discover $package .= pack("C*", 53, 1, 1); # add the client identifier $package .= pack("C*", 61, 7, 1); #type, length, hwtype $package .= pack("C*", @macval); # add the parameter request list $package .= pack("C*", 55, 10); #type, length $package .= pack("C*", 1, 3, 6, 12, 15, 28, 40, 41, 42, 119); # add the end option $package .= pack("C*", 255); # pad the package to 300 @strpack = unpack("W*", $package); my $curleng = length($strpack); my @padding; foreach (1 .. 35) { push @padding, '0'; } $package .= pack("C*", @padding); return $package; } sub kill_child { kill 15, $pid; my @pidoftcpdump = `ps -ef | grep -E "[0-9]+:[0-9]+:[0-9]+ tcpdump -i $nic" | awk -F' ' '{print \$2}'`; foreach my $cpid (@pidoftcpdump) { kill 15, $cpid; } probe_utils->send_msg("$output", "d", "Kill process $pid used to capture the packet by 'tcpdump'") if ($::VERBOSE); }