mirror of
https://github.com/xcat2/xcat-core.git
synced 2026-09-21 16:39:30 +00:00
955713c4b2
Both copies wrote the capture to /tmp/dhcpdumpfile.log, so a file left by another user or an earlier root run blocked the probe and two runs overwrote each other. tcpdump ran behind a shell, so the script killed it by searching the process table for any tcpdump on the interface, and a tcpdump that failed to start left an empty file that read as zero DHCP servers. An interrupt left the capture running. The capture file is now a private temporary file removed on every exit. The child execs tcpdump itself, so the script stops and reaps exactly that pid, and a tcpdump that ended before the window did fails the run. A failed send and an INT or TERM stop the capture and exit 1 as well. Signed-off-by: Vinícius Ferrão <2031761+viniciusferrao@users.noreply.github.com>
431 lines
13 KiB
Perl
Executable File
431 lines
13 KiB
Perl
Executable File
#!/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 xCAT::CommandUtils;
|
|
use File::Basename;
|
|
use File::Temp qw(tempfile);
|
|
use POSIX qw(_exit sigprocmask WNOHANG SIG_BLOCK SIG_SETMASK SIGINT SIGTERM);
|
|
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 $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;
|
|
}
|
|
|
|
my $tcpdump = xCAT::CommandUtils::find_executable('tcpdump');
|
|
unless ($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 not supported";
|
|
my $os;
|
|
if (-f "/etc/redhat-release") {
|
|
$os = "rh";
|
|
} elsif (-f "/etc/SuSE-release") {
|
|
$os = "sles";
|
|
} elsif (-f "/etc/SUSE-brand") {
|
|
$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 supported on 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 ($dumpfh, $dumpfile) = tempfile("detect_dhcpd.XXXXXX", TMPDIR => 1, UNLINK => 1);
|
|
close($dumpfh);
|
|
# INT and TERM stay blocked from the fork until the handler that stops the capture is in place.
|
|
my $stop_signals = POSIX::SigSet->new(SIGINT, SIGTERM);
|
|
my $signal_mask = POSIX::SigSet->new();
|
|
sigprocmask(SIG_BLOCK, $stop_signals, $signal_mask);
|
|
my $pid = fork;
|
|
if (!defined $pid) {
|
|
sigprocmask(SIG_SETMASK, $signal_mask);
|
|
probe_utils->send_msg("$output", "f", $msg);
|
|
exit 1;
|
|
} elsif ($pid == 0) {
|
|
|
|
# Child process: tcpdump itself, so the parent owns exactly this pid.
|
|
sigprocmask(SIG_SETMASK, $signal_mask);
|
|
open(STDOUT, '>', $dumpfile) or _exit(1);
|
|
open(STDERR, '>', '/dev/null');
|
|
exec($tcpdump, '-i', $nic, 'port', '68', '-n', '-vvvvvv');
|
|
_exit(1);
|
|
}
|
|
$SIG{INT} = $SIG{TERM} = sub { kill_child(); exit 1; };
|
|
sigprocmask(SIG_SETMASK, $signal_mask);
|
|
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) {
|
|
unless ($sock->send($package)) {
|
|
probe_utils->send_msg("$output", "d", "Send DHCP discover error: $!") if ($::VERBOSE);
|
|
probe_utils->send_msg("$output", "f", "Send out DHCP discover");
|
|
kill_child();
|
|
exit 1;
|
|
}
|
|
sleep 2;
|
|
$end = Time::HiRes::gettimeofday();
|
|
$end =~ s/(\d.*)\.(\d.*)/$1/;
|
|
}
|
|
|
|
$msg = "Capture the packets by tcpdump";
|
|
my $capture_problem = kill_child();
|
|
if ($capture_problem) {
|
|
probe_utils->send_msg("$output", "d", "tcpdump $capture_problem") if ($::VERBOSE);
|
|
probe_utils->send_msg("$output", "f", $msg);
|
|
exit 1;
|
|
}
|
|
|
|
$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);
|
|
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 (<FILE>) {
|
|
$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*file ".+"\[\|bootp\]/){
|
|
$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");
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
# Stop the capture and reap it, once: INT and TERM are blocked while the pid is taken and until
|
|
# tcpdump is reaped, so an interrupt in that window cannot orphan it or reach a recycled pid. The
|
|
# handlers stay installed, so a later signal still leaves through exit and the END cleanup.
|
|
# Returns '' when tcpdump ran until this TERM and stopped cleanly, otherwise what went wrong: it
|
|
# ended on its own, ignored TERM and was killed, or left on another signal or with a non-zero
|
|
# status.
|
|
sub kill_child {
|
|
sigprocmask(SIG_BLOCK, $stop_signals);
|
|
my $child = $pid;
|
|
$pid = undef;
|
|
unless ($child) {
|
|
sigprocmask(SIG_SETMASK, $signal_mask);
|
|
return '';
|
|
}
|
|
my $reaped = waitpid($child, WNOHANG);
|
|
my $early = ($reaped == $child) ? 1 : 0;
|
|
if ($reaped == 0) {
|
|
kill 'TERM', $child;
|
|
foreach (1 .. 50) {
|
|
last if ($reaped = waitpid($child, WNOHANG)) != 0;
|
|
select(undef, undef, undef, 0.1);
|
|
}
|
|
if ($reaped == 0) {
|
|
kill 'KILL', $child;
|
|
$reaped = waitpid($child, 0);
|
|
}
|
|
}
|
|
sigprocmask(SIG_SETMASK, $signal_mask);
|
|
return "could not be reaped" if $reaped != $child;
|
|
my ($signal, $status) = ($? & 127, $? >> 8);
|
|
probe_utils->send_msg("$output", "d", "Kill process $child used to capture the packet by 'tcpdump'") if ($::VERBOSE);
|
|
my $how = $signal ? "on signal $signal" : "with status $status";
|
|
return "ended before the capture window did, $how" if $early;
|
|
return '' if $signal == 15 || (!$signal && !$status);
|
|
return "left the capture $how";
|
|
}
|