#!/usr/bin/perl # Start a traffic capture, wait and then download # NOTE: This is not the PAPI, the script just happens to be written in Perl to use the Infoblox Web API. use strict; use warnings; $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME}=0; use FindBin qw($Bin $Script); my ($BASE,$NAME)=($Bin,$Script) ; #!/ use lib "lib" ; use lib "$FindBin::Bin" ; use lib "$FindBin::Bin/lib" ; use lib '/Users/jim/perl5lib/lib/perl5'; use Pod::Usage; use Getopt::Long; use Data::Dumper; use MIME::Base64; use JSON; use REST::Client; # Script parameters my $DEBUG; my $SERVER; my $USER; my $PASS; my $MEMBER; my $LENGTH; # Minimum WAPI version, also tested on some earlier versions YMMV my $WAPIREV='v2.7'; GetOptions ( "d" => \$DEBUG, "s=s" => \$SERVER, "u=s" => \$USER, "p=s" => \$PASS, "m=s" => \$MEMBER, "l=s" => \$LENGTH, # Standard meta-options 'help|?' => sub { pod2usage(1); }, 'man' => sub { pod2usage(-exitstatus => 0, -verbose => 2); }, ); # Check params if ( ! $LENGTH ) { print "Use -l to supply a length of capture\n"; exit; } ### Set up rest clients my $client = REST::Client->new(); # Using setHost can be problematic as is is prepended to all requests # hence we'll create a base path we can choose to use or not. #my $server_url = "https://$SERVER"; #$client->setHost($server_url); my $base_path = "https://$SERVER/wapi/$WAPIREV"; $client->addHeader('Accept', 'application/json'); $client->addHeader('Authorization', 'Basic ' . encode_base64($USER . ':' . $PASS)); ### Connect to get the Grid and get the grid object as a connectivity test print "\n\n*** Connecting to the Grid ***\n\n"; $client->GET( "$base_path/grid" ); # Exit on a failed response if ( $client->responseCode != 200 ) { print_client_response_and_die( $client ); } else { # or print Grid obj reference print "Connected to the Grid at $SERVER ...\n"; if ( $DEBUG ) { my $response_array_ref = decode_json($client->responseContent()); my $reference_string_list_ref = get_wapi_obj_ref_strings( $response_array_ref ); for my $ref_string ( @$reference_string_list_ref ) { print_wapi_obj_reference($ref_string); } } } my $query_params; my $query_string; my $body_params_ref; my $json_body_ref; ### Start a traffic capture # Get a Grid member print "\n\n*** Starting traffic capture ***\n\n"; print "Getting Grid member $MEMBER\n"; $query_params = { 'host_name' => $MEMBER }; $query_string = $client->buildQuery( $query_params ); $client->GET( "$base_path/member" . $query_string ); my $member_key; # Check the return value and get member key if ( $client->responseCode != 200 ) { print_client_response_and_die( $client ); } else { # or confirm success and check/dump return $DEBUG && print_client_response_success( $client ); my $response_array_ref = decode_json($client->responseContent()); my $member_ref = $response_array_ref->[0]; if ( $member_ref ) { $DEBUG && print_dumper_wapi_obj( $member_ref ); $member_key = $response_array_ref->[0]->{_ref}; print "Retrieved Grid member $MEMBER\n"; } else { print "Exiting... Likely error is there is no Grid member with the name $MEMBER\n"; } } # Start traffic capture $body_params_ref = { 'action' => 'START', 'interface' => 'ALL', 'seconds_to_run' => int($LENGTH), }; $json_body_ref = encode_json( $body_params_ref ); $client->POST( "$base_path/$member_key?_function=capture_traffic_control", $json_body_ref ); # Check the traffic capture started OK if ( $client->responseCode != 200 ) { print_client_response_and_die( $client ); } else { $DEBUG && print_client_response_success( $client ); print "Traffic capture started OK, now waiting for it to finish...\n"; } ### Wait for the capture to finish sleep $LENGTH + 10; # allow some extra time, might need to increase this ### Download the traffic capture # Get the token and URL for the file to be downloaded $body_params_ref = { 'member' => $MEMBER, 'type' => 'TRAFFIC_CAPTURE_FILE', }; $json_body_ref = encode_json( $body_params_ref ); $client->POST( "$base_path/fileop?_function=getmemberdata", $json_body_ref ); # Proceed if there is a traffic capture to download if ( $client->responseCode != 200 ) { print_client_response_and_die( $client ); } else { $DEBUG && print_client_response_success( $client ); print "\n\n*** Downloading traffic capture ***\n\n"; # Download the file using the same filename as on NIOS my $response_content_ref = from_json( $client->responseContent() ); my $token = $response_content_ref->{token}; my $url = $response_content_ref->{url}; my ( $capture_filename ) = $url =~ m/([^\/]+?)$/; # Create a new rest client just for the download # Using the same REST::Client to signal that the download had completed # (using the token) caused the local download file to be truncated. Weird I know. # Waiting with a sleep did not fix this, but using a new REST::Client object does. # So decided to use a new REST::Client here for the download. my $download_client = REST::Client->new(); $download_client->addHeader('Accept', 'application/json'); $download_client->addHeader('Authorization', 'Basic ' . encode_base64($USER . ':' . $PASS)); $download_client->addHeader('Content-type', 'application/force-download'); # Undocumented magic in Rest::Client build accessors handles download # using method setContentFile and I cannot use what I thought would work i.e. ... # $client->GET( $url, { ':content_type' => $file } ) # This fails, and I thought it would work as LWP::UserAgent is used by REST::Client # and the documentation for REST::Client looks like it should work. $download_client->setContentFile( $capture_filename ); $download_client->GET( $url ); # If the traffic capture was downloaded, signal for this to be removed if ( $download_client->responseCode != 200 ) { print_client_response_and_die( $client ); } else { $DEBUG && print_client_response_success( $client ); print "File $capture_filename downloaded\n"; # Remove the file using the token $body_params_ref = { 'token' => $token, }; my $json_body_ref = encode_json( $body_params_ref ); $client->POST( "$base_path/fileop?_function=downloadcomplete", $json_body_ref ); if ( $client->responseCode != 200 ) { print "Failed to signal download complete\n"; print_client_response_and_die( $client ); } else { $DEBUG && print_client_response_success( $client ); print "Signaled download complete\n"; } } } print "Finished\n\n"; # WAPI success responce sub print_client_response_success { my $client = shift; print "Response Code: " . $client->responseCode . "\n"; print "Response Content: " . $client->responseContent . "\n"; return; } # Show WAPI connection error and die sub print_client_response_and_die { my $client = shift; print "Response Code: " . $client->responseCode . "\n"; print "Response Content: " . $client->responseContent . "\n"; die; } # Return Perl ref to a list of WAPI object reference strings sub get_wapi_obj_ref_strings { my $json_array_ref = shift; my @wapi_obj_refs; for my $wapi_obj_hash_ref ( @$json_array_ref) { push @wapi_obj_refs, $wapi_obj_hash_ref->{'_ref'}; } return \@wapi_obj_refs; } # Print a WAPI object reference string in its constituent parts sub print_wapi_obj_reference { my $object_ref_string = shift; my ($obj_type, $obj_ref, $obj_name) = $object_ref_string =~ m{^(\w+)/(\w+):(.+)}xms; print "Object type: '$obj_type'\n"; print "Object reference: '$obj_ref'\n"; print "Object name: '$obj_name'\n"; return; } # Print just the WAPI human readable reference sub print_wapi_obj_reference_readable { my $object_ref_string = shift; my ($obj_name) = $object_ref_string =~ m{:(.+)$}xms; print "Object name: '$obj_name'\n"; return; } # Print WAPI object as a data structure sub print_dumper_wapi_obj { my $wapi_object_ref = shift; # WAPI object hash ref print Dumper $wapi_object_ref; return; } __END__ =head1 NAME traffic_capture_to_file.pl - Start a traffic capture on a Grid member and download it to a local file. =head1 VERSION traffic_capture_to_file.pl version 0.0.1 =head1 USAGE Traffic capture of 60 seconds from Grid member ns2.ad.example.com: traffic_capture_to_file.pl -s=192.168.1.2 -u=admin -p=infoblox -m=ns2.ad.example.com -l=60 =head1 REQUIRED ARGUMENTS I have not put in extensive argument checking, you should use: -s= -u= -p= -m= -l= =head1 OPTIONS The options are as follows =over =item -s The IP address of the Grid Master. =item -u Valid username on the Grid that has persmission to run a traffic capture. =item -p Password of the user. =item -m Member name (FQDN) where you want to run the traffic capture. =item -l Length of the capture in seconds. =item -d Some debug output of the requests between client and server i.e. the script and the Grid Master. =item --help|? Print this summary =item --man Displays the complete manpage then exits gracefully. =back =head1 DESCRIPTION Connects to the GM and verifies the connection. Starts a traffic capture and then waits for that + 10 seconds. Obtains a URL to download the file and a token to signal to the GM when the download is complete. Downloads the file locally with the same name as the original capture; this includes the member name and a timestamp. Signals to the GM to remove the file. =head1 DIAGNOSTICS The script will die on any error. =head1 CONFIGURATION AND ENVIRONMENT In the script is a minimum WAPI version. This is set to 2.7, but I also tested this on 2.6.1. YMMV. I hardcoded a 10 second wait after the traffic capture should have finished before starting the download. It is possible this needs to be increased, perhaps if the captures are very large. I am unsure if the captures are copied to GM after completing or the data is streamed in some way, so I arbitrarily chose a 10 second wait. This could be changed to an option. =head1 DEPENDENCIES REST::Client =head1 INCOMPATIBILITIES None known. =head1 BUGS AND LIMITATIONS Wow! REST::Client, maybe not the best and the documentation leaves something to be desired. =head1 AUTHOR Jim Mozley ( jmozley@infoblox.com ) =head1 LICENCE AND COPYRIGHT This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.