#!/usr/bin/env perl ################################################################################ # # netrun # # This program provides a simple interface for running a script across # multiple hosts in parallel. # # Author: David C. Snyder - 12/17/2003 # ################################################################################ use Parallel::ForkManager; use Getopt::Std; use File::Copy; use File::Path; use Time::HiRes qw( gettimeofday tv_interval usleep ); use Socket; use warnings; use strict; ######################################## # # Misc Globals # my %opts; my $ssh = "ssh -x -o BatchMode=yes -o StrictHostKeyChecking=no"; $|=1; # Make stdout unbuffered ######################################## # # ssh_host # # This subroutine attempts to connect # to port 22 (the SSH port) on the # host given in the parameter list ( # or localhost by default). It then # prints the SSH version string or # other status string to stderr. # It returns undef on failure and # the SSH version string on success. # sub ssh_host { my ($remote, $port, $iaddr, $paddr, $proto, $line); $remote = shift || 'localhost'; $port = 22; $iaddr = inet_aton( $remote ) or do { warn "SSH CONNECT STATUS: Could not resolve $remote to an address\n"; return undef; }; $paddr = sockaddr_in( $port, $iaddr ); $proto = getprotobyname('tcp'); TRY: eval { $SIG{'ALRM'} = sub { die "connect: timeout\n" }; alarm $opts{'c'}; socket( SOCK, PF_INET, SOCK_STREAM, $proto ) or die "socket: $!\n"; connect( SOCK, $paddr ) or die "connect: $!\n"; chomp( $line = ); print SOCK "quit\r\n"; close( SOCK ) or die "close: $!\n"; alarm 0; }; CATCH: do { chomp( $@ ); warn "SSH CONNECT STATUS: $@\n"; return undef; } if $@; if ( not defined $line ) { warn "SSH CONNECT STATUS: connection blocked by hosts.allow?\n"; return undef; } if ( $line =~ /^SSH-([\d.]+)-([\w+\d.]+)/ ) { warn "SSH CONNECT STATUS: $line\n"; return $line; } else { warn "SSH CONNECT STATUS: Failed to read SSH Version for $remote.\n"; return undef; } } ######################################## # # fisher_yates_shuffle # # This subroutine came from the Perl # Cookbook. It just randomizes # the elements of an array # in-place. # sub fisher_yates_shuffle { my $array = shift; return () unless @$array; for (my $i = @$array; --$i; ) { my $j = int rand ($i+1); next if $i == $j; @$array[$i,$j] = @$array[$j,$i]; } } ######################################## # # by_fields # # A reference to this function is # passed to sort to sort results # records by specific fields. The # names of the fields are defined in # the @fields list. # my %results; my @fields = qw( SSH_STATUS EXIT_STATUS RUN_TIME OUTPUT_LINES FIRST_LINE ); sub by_fields { no warnings; $results{ $a }{ EXIT_STATUS } cmp $results{ $b }{ EXIT_STATUS } or $results{ $a }{ SSH_STATUS } <=> $results{ $b }{ SSH_STATUS } or $results{ $a }{ OUTPUT_LINES } <=> $results{ $b }{ OUTPUT_LINES } or $results{ $a }{ RUN_TIME } <=> $results{ $b }{ RUN_TIME }; } my @address_list; ######################################## # # lookup_addresses # # This function does DNS lookups of # addresses from the global # @address_list. It accepts a maximum # number of lookups to return (default # is 10) and returns the reverse # lookups as a list. # sub lookup_addresses { my $max = (shift or 10); my @names = (); my ($iaddr, $name); for my $address ( @address_list ) { if ( $address =~ /^\d+\.\d+\.\d+\.\d+$/ ) { $iaddr = inet_aton( $address ); if ( defined ($name = gethostbyaddr( $iaddr, AF_INET )) ) { $name =~ s/([^.]+).*/$1/; push @names, $name; } } else { $address =~ s/([^.]+).*/$1/; push @names, $address; } last if $max <= @names; } @names; } =head1 NAME netrun - run a script over multiple hosts in parallel =head1 SYNOPSIS B [B<-hqRS]>] [B<-c> I] [B<-f> I] [B<-i> I] [B<-s> I