| Gilles Peskine | d50177f | 2017-05-16 17:53:03 +0200 | [diff] [blame] | 1 | #!/usr/bin/env perl | 
|  | 2 |  | 
|  | 3 | # A simple TCP client that sends some data and expects a response. | 
|  | 4 | # Usage: tcp_client.pl HOSTNAME PORT DATA1 RESPONSE1 | 
|  | 5 | #   DATA: hex-encoded data to send to the server | 
|  | 6 | #   RESPONSE: regexp that must match the server's response | 
|  | 7 |  | 
|  | 8 | use warnings; | 
|  | 9 | use strict; | 
|  | 10 | use IO::Socket::INET; | 
|  | 11 |  | 
|  | 12 | # Pack hex digits into a binary string, ignoring whitespace. | 
|  | 13 | sub parse_hex { | 
|  | 14 | my ($hex) = @_; | 
|  | 15 | $hex =~ s/\s+//g; | 
|  | 16 | return pack('H*', $hex); | 
|  | 17 | } | 
|  | 18 |  | 
|  | 19 | ## Open a TCP connection to the specified host and port. | 
|  | 20 | sub open_connection { | 
|  | 21 | my ($host, $port) = @_; | 
|  | 22 | my $socket = IO::Socket::INET->new(PeerAddr => $host, | 
|  | 23 | PeerPort => $port, | 
|  | 24 | Proto => 'tcp', | 
|  | 25 | Timeout => 1); | 
|  | 26 | die "Cannot connect to $host:$port: $!" unless $socket; | 
|  | 27 | return $socket; | 
|  | 28 | } | 
|  | 29 |  | 
|  | 30 | ## Close the TCP connection. | 
|  | 31 | sub close_connection { | 
|  | 32 | my ($connection) = @_; | 
|  | 33 | $connection->shutdown(2); | 
|  | 34 | # Ignore shutdown failures (at least for now) | 
|  | 35 | return 1; | 
|  | 36 | } | 
|  | 37 |  | 
|  | 38 | ## Write the given data, expressed as hexadecimal | 
|  | 39 | sub write_data { | 
|  | 40 | my ($connection, $hexdata) = @_; | 
|  | 41 | my $data = parse_hex($hexdata); | 
|  | 42 | my $total_sent = 0; | 
|  | 43 | while ($total_sent < length($data)) { | 
|  | 44 | my $sent = $connection->send($data, 0); | 
|  | 45 | if (!defined $sent) { | 
|  | 46 | die "Unable to send data: $!"; | 
|  | 47 | } | 
|  | 48 | $total_sent += $sent; | 
|  | 49 | } | 
|  | 50 | return 1; | 
|  | 51 | } | 
|  | 52 |  | 
|  | 53 | ## Read a response and check it against an expected prefix | 
|  | 54 | sub read_response { | 
|  | 55 | my ($connection, $expected_hex) = @_; | 
|  | 56 | my $expected_data = parse_hex($expected_hex); | 
|  | 57 | my $start_offset = 0; | 
|  | 58 | while ($start_offset < length($expected_data)) { | 
|  | 59 | my $actual_data; | 
|  | 60 | my $ok = $connection->recv($actual_data, length($expected_data)); | 
|  | 61 | if (!defined $ok) { | 
|  | 62 | die "Unable to receive data: $!"; | 
|  | 63 | } | 
|  | 64 | if (($actual_data ^ substr($expected_data, $start_offset)) =~ /[^\000]/) { | 
|  | 65 | printf STDERR ("Received \\x%02x instead of \\x%02x at offset %d\n", | 
|  | 66 | ord(substr($actual_data, $-[0], 1)), | 
|  | 67 | ord(substr($expected_data, $start_offset + $-[0], 1)), | 
|  | 68 | $start_offset + $-[0]); | 
|  | 69 | return 0; | 
|  | 70 | } | 
|  | 71 | $start_offset += length($actual_data); | 
|  | 72 | } | 
|  | 73 | return 1; | 
|  | 74 | } | 
|  | 75 |  | 
|  | 76 | if (@ARGV != 4) { | 
|  | 77 | print STDERR "Usage: $0 HOSTNAME PORT DATA1 RESPONSE1\n"; | 
|  | 78 | exit(3); | 
|  | 79 | } | 
|  | 80 | my ($host, $port, $data1, $response1) = @ARGV; | 
|  | 81 | my $connection = open_connection($host, $port); | 
|  | 82 | write_data($connection, $data1); | 
|  | 83 | if (!read_response($connection, $response1)) { | 
|  | 84 | exit(1); | 
|  | 85 | } | 
|  | 86 | close_connection($connection); |