1
# This program is copyright 2007-2011 Percona Inc.
2
# Feedback and improvements are welcome.
4
# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
5
# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
6
# MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
8
# This program is free software; you can redistribute it and/or modify it under
9
# the terms of the GNU General Public License as published by the Free Software
10
# Foundation, version 2; OR the Perl Artistic License. On UNIX and similar
11
# systems, you can issue `man perlgpl' or `man perlartistic' to read these
14
# You should have received a copy of the GNU General Public License along with
15
# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
16
# Place, Suite 330, Boston, MA 02111-1307 USA.
17
# ###########################################################################
18
# MySQLProtocolParser package $Revision: 7522 $
19
# ###########################################################################
20
package MySQLProtocolParser;
22
# This creates events suitable for mk-query-digest from raw MySQL packets.
23
# The packets come from TcpdumpParser. MySQLProtocolParse::parse_packet()
24
# should be first in the callback chain because it creates events for
25
# subsequent callbacks. So the sequence is:
26
# 1. mk-query-digest calls TcpdumpParser::parse_event($fh, ..., @callbacks)
27
# 2. TcpdumpParser::parse_event() extracts raw MySQL packets from $fh and
28
# passes them to the callbacks, the first of which is
29
# MySQLProtocolParser::parse_packet().
30
# 3. MySQLProtocolParser::parse_packet() makes events from the packets
31
# and returns them to TcpdumpParser::parse_event().
32
# 4. TcpdumpParser::parse_event() passes the newly created events to
33
# the subsequent callbacks.
34
# At times MySQLProtocolParser::parse_packet() will not return an event
35
# because it usually takes a few packets to create one event. In such
36
# cases, TcpdumpParser::parse_event() will not call the other callbacks.
39
use warnings FATAL => 'all';
40
use English qw(-no_match_vars);
43
require IO::Uncompress::Inflate;
44
IO::Uncompress::Inflate->import(qw(inflate $InflateError));
48
$Data::Dumper::Indent = 1;
49
$Data::Dumper::Sortkeys = 1;
50
$Data::Dumper::Quotekeys = 0;
53
our @ISA = qw(Exporter);
54
our %EXPORT_TAGS = ();
59
parse_ok_prepared_statement_packet
60
parse_server_handshake_packet
61
parse_client_handshake_packet
66
use constant MKDEBUG => $ENV{MKDEBUG} || 0;
72
COM_FIELD_LIST => '04',
73
COM_CREATE_DB => '05',
77
COM_STATISTICS => '09',
78
COM_PROCESS_INFO => '0a',
80
COM_PROCESS_KILL => '0c',
84
COM_DELAYED_INSERT => '10',
85
COM_CHANGE_USER => '11',
86
COM_BINLOG_DUMP => '12',
87
COM_TABLE_DUMP => '13',
88
COM_CONNECT_OUT => '14',
89
COM_REGISTER_SLAVE => '15',
90
COM_STMT_PREPARE => '16',
91
COM_STMT_EXECUTE => '17',
92
COM_STMT_SEND_LONG_DATA => '18',
93
COM_STMT_CLOSE => '19',
94
COM_STMT_RESET => '1a',
95
COM_SET_OPTION => '1b',
96
COM_STMT_FETCH => '1c',
97
SERVER_QUERY_NO_GOOD_INDEX_USED => 16,
98
SERVER_QUERY_NO_INDEX_USED => 32,
104
'02' => 'COM_INIT_DB',
106
'04' => 'COM_FIELD_LIST',
107
'05' => 'COM_CREATE_DB',
108
'06' => 'COM_DROP_DB',
109
'07' => 'COM_REFRESH',
110
'08' => 'COM_SHUTDOWN',
111
'09' => 'COM_STATISTICS',
112
'0a' => 'COM_PROCESS_INFO',
113
'0b' => 'COM_CONNECT',
114
'0c' => 'COM_PROCESS_KILL',
118
'10' => 'COM_DELAYED_INSERT',
119
'11' => 'COM_CHANGE_USER',
120
'12' => 'COM_BINLOG_DUMP',
121
'13' => 'COM_TABLE_DUMP',
122
'14' => 'COM_CONNECT_OUT',
123
'15' => 'COM_REGISTER_SLAVE',
124
'16' => 'COM_STMT_PREPARE',
125
'17' => 'COM_STMT_EXECUTE',
126
'18' => 'COM_STMT_SEND_LONG_DATA',
127
'19' => 'COM_STMT_CLOSE',
128
'1a' => 'COM_STMT_RESET',
129
'1b' => 'COM_SET_OPTION',
130
'1c' => 'COM_STMT_FETCH',
134
'CLIENT_LONG_PASSWORD' => 1, # new more secure passwords
135
'CLIENT_FOUND_ROWS' => 2, # Found instead of affected rows
136
'CLIENT_LONG_FLAG' => 4, # Get all column flags
137
'CLIENT_CONNECT_WITH_DB' => 8, # One can specify db on connect
138
'CLIENT_NO_SCHEMA' => 16, # Don't allow database.table.column
139
'CLIENT_COMPRESS' => 32, # Can use compression protocol
140
'CLIENT_ODBC' => 64, # Odbc client
141
'CLIENT_LOCAL_FILES' => 128, # Can use LOAD DATA LOCAL
142
'CLIENT_IGNORE_SPACE' => 256, # Ignore spaces before '('
143
'CLIENT_PROTOCOL_41' => 512, # New 4.1 protocol
144
'CLIENT_INTERACTIVE' => 1024, # This is an interactive client
145
'CLIENT_SSL' => 2048, # Switch to SSL after handshake
146
'CLIENT_IGNORE_SIGPIPE' => 4096, # IGNORE sigpipes
147
'CLIENT_TRANSACTIONS' => 8192, # Client knows about transactions
148
'CLIENT_RESERVED' => 16384, # Old flag for 4.1 protocol
149
'CLIENT_SECURE_CONNECTION' => 32768, # New 4.1 authentication
150
'CLIENT_MULTI_STATEMENTS' => 65536, # Enable/disable multi-stmt support
151
'CLIENT_MULTI_RESULTS' => 131072, # Enable/disable multi-results
155
MYSQL_TYPE_DECIMAL => 0,
156
MYSQL_TYPE_TINY => 1,
157
MYSQL_TYPE_SHORT => 2,
158
MYSQL_TYPE_LONG => 3,
159
MYSQL_TYPE_FLOAT => 4,
160
MYSQL_TYPE_DOUBLE => 5,
161
MYSQL_TYPE_NULL => 6,
162
MYSQL_TYPE_TIMESTAMP => 7,
163
MYSQL_TYPE_LONGLONG => 8,
164
MYSQL_TYPE_INT24 => 9,
165
MYSQL_TYPE_DATE => 10,
166
MYSQL_TYPE_TIME => 11,
167
MYSQL_TYPE_DATETIME => 12,
168
MYSQL_TYPE_YEAR => 13,
169
MYSQL_TYPE_NEWDATE => 14,
170
MYSQL_TYPE_VARCHAR => 15,
171
MYSQL_TYPE_BIT => 16,
172
MYSQL_TYPE_NEWDECIMAL => 246,
173
MYSQL_TYPE_ENUM => 247,
174
MYSQL_TYPE_SET => 248,
175
MYSQL_TYPE_TINY_BLOB => 249,
176
MYSQL_TYPE_MEDIUM_BLOB => 250,
177
MYSQL_TYPE_LONG_BLOB => 251,
178
MYSQL_TYPE_BLOB => 252,
179
MYSQL_TYPE_VAR_STRING => 253,
180
MYSQL_TYPE_STRING => 254,
181
MYSQL_TYPE_GEOMETRY => 255,
185
0 => 'MYSQL_TYPE_DECIMAL',
186
1 => 'MYSQL_TYPE_TINY',
187
2 => 'MYSQL_TYPE_SHORT',
188
3 => 'MYSQL_TYPE_LONG',
189
4 => 'MYSQL_TYPE_FLOAT',
190
5 => 'MYSQL_TYPE_DOUBLE',
191
6 => 'MYSQL_TYPE_NULL',
192
7 => 'MYSQL_TYPE_TIMESTAMP',
193
8 => 'MYSQL_TYPE_LONGLONG',
194
9 => 'MYSQL_TYPE_INT24',
195
10 => 'MYSQL_TYPE_DATE',
196
11 => 'MYSQL_TYPE_TIME',
197
12 => 'MYSQL_TYPE_DATETIME',
198
13 => 'MYSQL_TYPE_YEAR',
199
14 => 'MYSQL_TYPE_NEWDATE',
200
15 => 'MYSQL_TYPE_VARCHAR',
201
16 => 'MYSQL_TYPE_BIT',
202
246 => 'MYSQL_TYPE_NEWDECIMAL',
203
247 => 'MYSQL_TYPE_ENUM',
204
248 => 'MYSQL_TYPE_SET',
205
249 => 'MYSQL_TYPE_TINY_BLOB',
206
250 => 'MYSQL_TYPE_MEDIUM_BLOB',
207
251 => 'MYSQL_TYPE_LONG_BLOB',
208
252 => 'MYSQL_TYPE_BLOB',
209
253 => 'MYSQL_TYPE_VAR_STRING',
210
254 => 'MYSQL_TYPE_STRING',
211
255 => 'MYSQL_TYPE_GEOMETRY',
215
MYSQL_TYPE_NULL => sub { return 'NULL', 0; },
216
MYSQL_TYPE_TINY => sub { return to_num(@_, 1), 1; },
217
MySQL_TYPE_SHORT => sub { return to_num(@_, 2), 2; },
218
MYSQL_TYPE_LONG => sub { return to_num(@_, 4), 4; },
219
MYSQL_TYPE_LONGLONG => sub { return to_num(@_, 8), 8; },
220
MYSQL_TYPE_DOUBLE => sub { return to_double(@_), 8; },
221
MYSQL_TYPE_VARCHAR => \&unpack_string,
222
MYSQL_TYPE_VAR_STRING => \&unpack_string,
223
MYSQL_TYPE_STRING => \&unpack_string,
226
# server is the "host:port" of the sever being watched. It's auto-guessed if
227
# not specified. version is a placeholder for handling differences between
228
# MySQL v4.0 and older and v4.1 and newer. Currently, we only handle v4.1.
230
my ( $class, %args ) = @_;
233
server => $args{server},
234
port => $args{port} || '3306',
235
version => '41', # MySQL proto version; not used yet
238
fake_thread_id => 2**32, # see _make_event()
240
MKDEBUG && $self->{server} && _d('Watching only server', $self->{server});
241
return bless $self, $class;
244
# The packet arg should be a hashref from TcpdumpParser::parse_event().
245
# misc is a placeholder for future features.
247
my ( $self, %args ) = @_;
248
my @required_args = qw(event);
249
foreach my $arg ( @required_args ) {
250
die "I need a $arg argument" unless $args{$arg};
252
my $packet = @args{@required_args};
254
my $src_host = "$packet->{src_host}:$packet->{src_port}";
255
my $dst_host = "$packet->{dst_host}:$packet->{dst_port}";
257
if ( my $server = $self->{server} ) { # Watch only the given server.
258
$server .= ":$self->{port}";
259
if ( $src_host ne $server && $dst_host ne $server ) {
260
MKDEBUG && _d('Packet is not to or from', $server);
265
# Auto-detect the server by looking for port 3306 or port "mysql" (sometimes
266
# tcpdump will substitute the port by a lookup in /etc/protocols).
269
if ( $src_host =~ m/:$self->{port}$/ ) {
270
$packet_from = 'server';
273
elsif ( $dst_host =~ m/:$self->{port}$/ ) {
274
$packet_from = 'client';
278
MKDEBUG && _d('Packet is not to or from a MySQL server');
281
MKDEBUG && _d('Client', $client);
283
# Get the client's session info or create a new session if
284
# we catch the TCP SYN sequence or the packetno is 0.
286
if ( $packet->{data_len} >= 5 ) {
287
# 5 bytes is the minimum length of any valid MySQL packet.
288
# If there's less, it's probably some TCP control packet
289
# with other data. Peek at the MySQL packet number. The
290
# only time a server sends packetno 0 is for its handshake.
291
# Client packetno 0 marks start of new query.
292
$packetno = to_num(substr($packet->{data}, 6, 2));
294
if ( !exists $self->{sessions}->{$client} ) {
295
if ( $packet->{syn} ) {
296
MKDEBUG && _d('New session (SYN)');
298
elsif ( $packetno == 0 ) {
299
MKDEBUG && _d('New session (packetno 0)');
302
MKDEBUG && _d('Ignoring mid-stream', $packet_from, 'data,',
303
'packetno', $packetno);
307
$self->{sessions}->{$client} = {
319
my $session = $self->{sessions}->{$client};
320
MKDEBUG && _d('Client state:', $session->{state});
322
# Save raw packets to dump later in case something fails.
323
push @{$session->{raw_packets}}, $packet->{raw_packet};
325
# Check client port reuse.
326
# http://code.google.com/p/maatkit/issues/detail?id=794
327
if ( $packet->{syn} && ($session->{n_queries} > 0 || $session->{state}) ) {
328
MKDEBUG && _d('Client port reuse and last session did not quit');
329
# Fail the session so we can see the last thing the previous
331
$self->fail_session($session,
332
'client port reuse and last session did not quit');
333
# Then recurse to create a New session.
334
return $self->parse_event(%args);
337
# Return early if there's no TCP/MySQL data. These are usually
338
# TCP control packets: SYN, ACK, FIN, etc.
339
if ( $packet->{data_len} == 0 ) {
340
MKDEBUG && _d('TCP control:',
341
map { uc $_ } grep { $packet->{$_} } qw(syn ack fin rst));
345
# Return unless the compressed packet can be uncompressed.
346
# If it cannot, then we're helpless and must return.
347
if ( $session->{compress} ) {
348
return unless $self->uncompress_packet($packet, $session);
351
if ( $session->{buff} && $packet_from eq 'client' ) {
352
# Previous packets were not complete so append this data
353
# to what we've been buffering. Afterwards, do *not* attempt
354
# to remove_mysql_header() because it was already done (from
356
$session->{buff} .= $packet->{data};
357
$packet->{data} = $session->{buff};
358
$session->{buff_left} -= $packet->{data_len};
360
# We didn't remove_mysql_header(), so mysql_data_len isn't set.
361
# So set it to the real, complete data len (from the first
362
# packet's MySQL header).
363
$packet->{mysql_data_len} = $session->{mysql_data_len};
364
$packet->{number} = $session->{number};
366
MKDEBUG && _d('Appending data to buff; expecting',
367
$session->{buff_left}, 'more bytes');
370
# Remove the first MySQL header. A single TCP packet can contain many
371
# MySQL packets, but we only look at the first. The 2nd and subsequent
372
# packets are usually parts of a result set returned by the server, but
373
# we're not interested in result sets.
375
remove_mysql_header($packet);
378
MKDEBUG && _d('remove_mysql_header() failed; failing session');
379
$session->{EVAL_ERROR} = $EVAL_ERROR;
380
$self->fail_session($session, 'remove_mysql_header() failed');
385
# Finally, parse the packet and maybe create an event.
386
# The returned event may be empty if no event was ready to be created.
388
if ( $packet_from eq 'server' ) {
389
$event = $self->_packet_from_server($packet, $session, $args{misc});
391
elsif ( $packet_from eq 'client' ) {
392
if ( $session->{buff} ) {
393
if ( $session->{buff_left} <= 0 ) {
394
MKDEBUG && _d('Data is complete');
395
$self->_delete_buff($session);
398
return; # waiting for more data; buff_left was reported earlier
401
elsif ( $packet->{mysql_data_len} > ($packet->{data_len} - 4) ) {
403
# http://code.google.com/p/maatkit/issues/detail?id=832
404
if ( $session->{cmd} && ($session->{state} || '') eq 'awaiting_reply' ) {
405
MKDEBUG && _d('No server OK to previous command (frag)');
406
$self->fail_session($session, 'no server OK to previous command');
407
# The MySQL header is removed by this point, so put it back.
408
$packet->{data} = $packet->{mysql_hdr} . $packet->{data};
409
return $self->parse_event(%args);
412
# There is more MySQL data than this packet contains.
413
# Save the data and the original MySQL header values
414
# then wait for the rest of the data.
415
$session->{buff} = $packet->{data};
416
$session->{mysql_data_len} = $packet->{mysql_data_len};
417
$session->{number} = $packet->{number};
419
# Do this just once here. For the next packets, buff_left
420
# will be decremented above.
421
$session->{buff_left}
422
||= $packet->{mysql_data_len} - ($packet->{data_len} - 4);
424
MKDEBUG && _d('Data not complete; expecting',
425
$session->{buff_left}, 'more bytes');
429
if ( $session->{cmd} && ($session->{state} || '') eq 'awaiting_reply' ) {
430
# Buffer handling above should ensure that by this point we have
431
# the full client query. If there's a previous client query for
432
# which we're "awaiting_reply" and then we get another client
433
# query, chances are we missed the server's OK response to the
434
# first query. So fail the first query and re-parse this second
436
MKDEBUG && _d('No server OK to previous command');
437
$self->fail_session($session, 'no server OK to previous command');
438
# The MySQL header is removed by this point, so put it back.
439
$packet->{data} = $packet->{mysql_hdr} . $packet->{data};
440
return $self->parse_event(%args);
443
$event = $self->_packet_from_client($packet, $session, $args{misc});
446
# Should not get here.
447
die 'Packet origin unknown';
450
MKDEBUG && _d('Done parsing packet; client state:', $session->{state});
451
if ( $session->{closed} ) {
452
delete $self->{sessions}->{$session->{client}};
453
MKDEBUG && _d('Session deleted');
456
$args{stats}->{events_parsed}++ if $args{stats};
460
# Handles a packet from the server given the state of the session.
461
# The server can send back a lot of different stuff, but luckily
462
# we're only interested in
463
# * Connection handshake packets for the thread_id
464
# * OK and Error packets for errors, warnings, etc.
465
# Anything else is ignored. Returns an event if one was ready to be
466
# created, otherwise returns nothing.
467
sub _packet_from_server {
468
my ( $self, $packet, $session, $misc ) = @_;
469
die "I need a packet" unless $packet;
470
die "I need a session" unless $session;
472
MKDEBUG && _d('Packet is from server; client state:', $session->{state});
474
if ( ($session->{server_seq} || '') eq $packet->{seq} ) {
475
push @{ $session->{server_retransmissions} }, $packet->{seq};
476
MKDEBUG && _d('TCP retransmission');
479
$session->{server_seq} = $packet->{seq};
481
my $data = $packet->{data};
483
# The first byte in the packet indicates whether it's an OK,
484
# ERROR, EOF packet. If it's not one of those, we test
485
# whether it's an initialization packet (the first thing the
486
# server ever sends the client). If it's not that, it could
487
# be a result set header, field, row data, etc.
489
my ( $first_byte ) = substr($data, 0, 2, '');
490
MKDEBUG && _d('First byte of packet:', $first_byte);
491
if ( !$first_byte ) {
492
$self->fail_session($session, 'no first byte');
496
# If there's no session state, then we're catching a server response
497
# mid-stream. It's only safe to wait until the client sends a command
498
# or to look for the server handshake.
499
if ( !$session->{state} ) {
500
if ( $first_byte eq '0a' && length $data >= 33 && $data =~ m/00{13}/ ) {
501
# It's the handshake packet from the server to the client.
502
# 0a is protocol v10 which is essentially the only version used
503
# today. 33 is the minimum possible length for a valid server
504
# handshake packet. It's probably a lot longer. Other packets
505
# may start with 0a, but none that can would be >= 33. The 13-byte
506
# 00 scramble buffer is another indicator.
507
my $handshake = parse_server_handshake_packet($data);
509
$self->fail_session($session, 'failed to parse server handshake');
512
$session->{state} = 'server_handshake';
513
$session->{thread_id} = $handshake->{thread_id};
515
# See http://code.google.com/p/maatkit/issues/detail?id=794
516
$session->{ts} = $packet->{ts} unless $session->{ts};
518
elsif ( $session->{buff} ) {
519
$self->fail_session($session,
520
'got server response before full buffer');
524
MKDEBUG && _d('Ignoring mid-stream server response');
529
if ( $first_byte eq '00' ) {
530
if ( ($session->{state} || '') eq 'client_auth' ) {
531
# We logged in OK! Trigger an admin Connect command.
533
$session->{compress} = $session->{will_compress};
534
delete $session->{will_compress};
535
MKDEBUG && $session->{compress} && _d('Packets will be compressed');
537
MKDEBUG && _d('Admin command: Connect');
538
return $self->_make_event(
540
arg => 'administrator command: Connect',
541
ts => $packet->{ts}, # Events are timestamped when they end
546
elsif ( $session->{cmd} ) {
547
# This OK should be ack'ing a query or something sent earlier
548
# by the client. OK for prepared statement are special.
549
my $com = $session->{cmd}->{cmd};
551
if ( $com eq COM_STMT_PREPARE ) {
552
MKDEBUG && _d('OK for prepared statement');
553
$ok = parse_ok_prepared_statement_packet($data);
555
$self->fail_session($session,
556
'failed to parse OK prepared statement packet');
559
my $sth_id = $ok->{sth_id};
560
$session->{attribs}->{Statement_id} = $sth_id;
562
# Save all sth info, used in parse_execute_packet().
563
$session->{sths}->{$sth_id} = $ok;
564
$session->{sths}->{$sth_id}->{statement}
565
= $session->{cmd}->{arg};
568
$ok = parse_ok_packet($data);
570
$self->fail_session($session, 'failed to parse OK packet');
576
if ( $com eq COM_QUERY
577
|| $com eq COM_STMT_EXECUTE || $com eq COM_STMT_RESET ) {
579
$arg = $session->{cmd}->{arg};
581
elsif ( $com eq COM_STMT_PREPARE ) {
583
$arg = "PREPARE $session->{cmd}->{arg}";
586
$arg = 'administrator command: '
587
. ucfirst(lc(substr($com_for{$com}, 4)));
591
return $self->_make_event(
595
Insert_id => $ok->{insert_id},
596
Warning_count => $ok->{warnings},
597
Rows_affected => $ok->{affected_rows},
603
MKDEBUG && _d('Looks like an OK packet but session has no cmd');
606
elsif ( $first_byte eq 'ff' ) {
607
my $error = parse_error_packet($data);
609
$self->fail_session($session, 'failed to parse error packet');
614
if ( $session->{state} eq 'client_auth' ) {
615
MKDEBUG && _d('Connection failed');
618
arg => 'administrator command: Connect',
620
Error_no => $error->{errno},
622
$session->{attribs}->{Error_msg} = $error->{message};
623
$session->{closed} = 1; # delete session when done
624
return $self->_make_event($event, $packet, $session);
626
elsif ( $session->{cmd} ) {
627
# This error should be in response to a query or something
628
# sent earlier by the client.
629
my $com = $session->{cmd}->{cmd};
632
if ( $com eq COM_QUERY || $com eq COM_STMT_EXECUTE ) {
634
$arg = $session->{cmd}->{arg};
637
$arg = 'administrator command: '
638
. ucfirst(lc(substr($com_for{$com}, 4)));
646
Error_no => $error->{errno} ? "#$error->{errno}" : 'none',
648
$session->{attribs}->{Error_msg} = $error->{message};
649
return $self->_make_event($event, $packet, $session);
652
MKDEBUG && _d('Looks like an error packet but client is not '
653
. 'authenticating and session has no cmd');
656
elsif ( $first_byte eq 'fe' && $packet->{mysql_data_len} < 9 ) {
658
if ( $packet->{mysql_data_len} == 1
659
&& $session->{state} eq 'client_auth'
660
&& $packet->{number} == 2 )
662
MKDEBUG && _d('Server has old password table;',
663
'client will resend password using old algorithm');
664
$session->{state} = 'client_auth_resend';
667
MKDEBUG && _d('Got an EOF packet');
668
$self->fail_session($session, 'got an unexpected EOF packet');
669
# ^^^ We shouldn't reach this because EOF should come after a
670
# header, field, or row data packet; and we should be firing the
671
# event and returning when we see that. See SVN history for some
672
# good stuff we could do if we wanted to handle EOF packets.
676
# Since we do NOT always have all the data the server sent to the
677
# client, we can't always do any processing of results. So when
678
# we get one of these, we just fire the event even if the query
679
# is not done. This means we will NOT process EOF packets
680
# themselves (see above).
681
if ( $session->{cmd} ) {
682
MKDEBUG && _d('Got a row/field/result packet');
683
my $com = $session->{cmd}->{cmd};
684
MKDEBUG && _d('Responding to client', $com_for{$com});
685
my $event = { ts => $packet->{ts} };
686
if ( $com eq COM_QUERY || $com eq COM_STMT_EXECUTE ) {
687
$event->{cmd} = 'Query';
688
$event->{arg} = $session->{cmd}->{arg};
691
$event->{arg} = 'administrator command: '
692
. ucfirst(lc(substr($com_for{$com}, 4)));
693
$event->{cmd} = 'Admin';
696
# We DID get all the data in the packet.
697
if ( $packet->{complete} ) {
698
# Look to see if the end of the data appears to be an EOF
700
my ( $warning_count, $status_flags )
701
= $data =~ m/fe(.{4})(.{4})\Z/;
702
if ( $warning_count ) {
703
$event->{Warnings} = to_num($warning_count);
704
my $flags = to_num($status_flags); # TODO set all flags?
705
$event->{No_good_index_used}
706
= $flags & SERVER_QUERY_NO_GOOD_INDEX_USED ? 1 : 0;
707
$event->{No_index_used}
708
= $flags & SERVER_QUERY_NO_INDEX_USED ? 1 : 0;
712
return $self->_make_event($event, $packet, $session);
715
MKDEBUG && _d('Unknown in-stream server response');
723
# Handles a packet from the client given the state of the session.
724
# The client doesn't send a wide and exotic array of packets like
725
# the server. Even so, we're only interested in:
726
# * Users and dbs from connection handshake packets
727
# * SQL statements from COM_QUERY commands
728
# Anything else is ignored. Returns an event if one was ready to be
729
# created, otherwise returns nothing.
730
sub _packet_from_client {
731
my ( $self, $packet, $session, $misc ) = @_;
732
die "I need a packet" unless $packet;
733
die "I need a session" unless $session;
735
MKDEBUG && _d('Packet is from client; state:', $session->{state});
737
if ( ($session->{client_seq} || '') eq $packet->{seq} ) {
738
push @{ $session->{client_retransmissions} }, $packet->{seq};
739
MKDEBUG && _d('TCP retransmission');
742
$session->{client_seq} = $packet->{seq};
744
my $data = $packet->{data};
745
my $ts = $packet->{ts};
747
if ( ($session->{state} || '') eq 'server_handshake' ) {
748
MKDEBUG && _d('Expecting client authentication packet');
749
# The connection is a 3-way handshake:
750
# server > client (protocol version, thread id, etc.)
751
# client > server (user, pass, default db, etc.)
752
# server > client OK if login succeeds
753
# pos_in_log refers to 2nd handshake from the client.
754
# A connection is logged even if the client fails to
755
# login (bad password, etc.).
756
my $handshake = parse_client_handshake_packet($data);
758
$self->fail_session($session, 'failed to parse client handshake');
761
$session->{state} = 'client_auth';
762
$session->{pos_in_log} = $packet->{pos_in_log};
763
$session->{user} = $handshake->{user};
764
$session->{db} = $handshake->{db};
766
# $session->{will_compress} will become $session->{compress} when
767
# the server's final handshake packet is received. This prevents
768
# parse_packet() from trying to decompress that final packet.
769
# Compressed packets can only begin after the full handshake is done.
770
$session->{will_compress} = $handshake->{flags}->{CLIENT_COMPRESS};
772
elsif ( ($session->{state} || '') eq 'client_auth_resend' ) {
773
# Don't know how to parse this packet.
774
MKDEBUG && _d('Client resending password using old algorithm');
775
$session->{state} = 'client_auth';
777
elsif ( ($session->{state} || '') eq 'awaiting_reply' ) {
778
my $arg = $session->{cmd}->{arg} ? substr($session->{cmd}->{arg}, 0, 50)
780
MKDEBUG && _d('More data for previous command:', $arg, '...');
784
# Otherwise, it should be a query if its the first packet (number 0).
785
# We ignore the commands that take arguments (COM_CHANGE_USER,
787
if ( $packet->{number} != 0 ) {
788
$self->fail_session($session, 'client cmd not packet 0');
792
# Detect compression in-stream only if $session->{compress} is
793
# not defined. This means we didn't see the client handshake.
794
# If we had seen it, $session->{compress} would be defined as 0 or 1.
795
if ( !defined $session->{compress} ) {
796
return unless $self->detect_compression($packet, $session);
797
$data = $packet->{data};
800
my $com = parse_com_packet($data, $packet->{mysql_data_len});
802
$self->fail_session($session, 'failed to parse COM packet');
806
if ( $com->{code} eq COM_STMT_EXECUTE ) {
807
MKDEBUG && _d('Execute prepared statement');
808
my $exec = parse_execute_packet($com->{data}, $session->{sths});
810
# This does not signal a failure, it could just be that
811
# the statement handle ID is unknown.
812
MKDEBUG && _d('Failed to parse execute packet');
813
$session->{state} = undef;
816
$com->{data} = $exec->{arg};
817
$session->{attribs}->{Statement_id} = $exec->{sth_id};
819
elsif ( $com->{code} eq COM_STMT_RESET ) {
820
my $sth_id = get_sth_id($com->{data});
822
$self->fail_session($session,
823
'failed to parse prepared statement reset packet');
826
$com->{data} = "RESET $sth_id";
827
$session->{attribs}->{Statement_id} = $sth_id;
830
$session->{state} = 'awaiting_reply';
831
$session->{pos_in_log} = $packet->{pos_in_log};
832
$session->{ts} = $ts;
838
if ( $com->{code} eq COM_QUIT ) { # Fire right away; will cleanup later.
839
MKDEBUG && _d('Got a COM_QUIT');
841
# See http://code.google.com/p/maatkit/issues/detail?id=794
842
$session->{closed} = 1; # delete session when done
844
return $self->_make_event(
846
arg => 'administrator command: Quit',
852
elsif ( $com->{code} eq COM_STMT_CLOSE ) {
853
# Apparently, these are not acknowledged by the server.
854
my $sth_id = get_sth_id($com->{data});
856
$self->fail_session($session,
857
'failed to parse prepared statement close packet');
860
delete $session->{sths}->{$sth_id};
861
return $self->_make_event(
863
arg => "DEALLOCATE PREPARE $sth_id",
874
# Make and return an event from the given packet and session.
876
my ( $self, $event, $packet, $session ) = @_;
877
MKDEBUG && _d('Making event');
879
# Clear packets that preceded this event.
880
$session->{raw_packets} = [];
881
$self->_delete_buff($session);
883
if ( !$session->{thread_id} ) {
884
# Only the server handshake packet gives the thread id, so for
885
# sessions caught mid-stream we assign a fake thread id.
886
MKDEBUG && _d('Giving session fake thread id', $self->{fake_thread_id});
887
$session->{thread_id} = $self->{fake_thread_id}++;
890
my ($host, $port) = $session->{client} =~ m/((?:\d+\.){3}\d+)\:(\w+)/;
892
cmd => $event->{cmd},
893
arg => $event->{arg},
894
bytes => length( $event->{arg} ),
895
ts => tcp_timestamp( $event->{ts} ),
899
db => $session->{db},
900
user => $session->{user},
901
Thread_id => $session->{thread_id},
902
pos_in_log => $session->{pos_in_log},
903
Query_time => timestamp_diff($session->{ts}, $packet->{ts}),
904
Error_no => $event->{Error_no} || 'none',
905
Rows_affected => ($event->{Rows_affected} || 0),
906
Warning_count => ($event->{Warning_count} || 0),
907
No_good_index_used => ($event->{No_good_index_used} ? 'Yes' : 'No'),
908
No_index_used => ($event->{No_index_used} ? 'Yes' : 'No'),
910
@{$new_event}{keys %{$session->{attribs}}} = values %{$session->{attribs}};
911
MKDEBUG && _d('Properties of event:', Dumper($new_event));
913
# Delete cmd to prevent re-making the same event if the
914
# server sends extra stuff that looks like a result set, etc.
915
delete $session->{cmd};
917
# Undef the session state so that we ignore everything from
918
# the server and wait until the client says something again.
919
$session->{state} = undef;
921
# Clear the attribs for this event.
922
$session->{attribs} = {};
924
$session->{n_queries}++;
925
$session->{server_retransmissions} = [];
926
$session->{client_retransmissions} = [];
931
# Extracts a slow-log-formatted timestamp from the tcpdump timestamp format.
934
$ts =~ s/^\d\d(\d\d)-(\d\d)-(\d\d)/$1$2$3/;
938
# Returns the difference between two tcpdump timestamps.
940
my ( $start, $end ) = @_;
941
my $sd = substr($start, 0, 11, '');
942
my $ed = substr($end, 0, 11, '');
943
my ( $sh, $sm, $ss ) = split(/:/, $start);
944
my ( $eh, $em, $es ) = split(/:/, $end);
945
my $esecs = ($eh * 3600 + $em * 60 + $es);
946
my $ssecs = ($sh * 3600 + $sm * 60 + $ss);
948
return sprintf '%.6f', $esecs - $ssecs;
950
else { # Assume only one day boundary has been crossed, no DST, etc
951
return sprintf '%.6f', ( 86_400 - $ssecs ) + $esecs;
955
# Converts hexadecimal to string.
958
return pack('H*', $data);
965
($data, $len, $encode_len) = decode_len($data);
966
my $t = 'H' . ($len ? $len * 2 : '*');
967
$data = pack($t, $data);
968
return "\"$data\"", $encode_len + $len;
976
# ========== ==== =============
978
# 252 FC Len in next 2
979
# 253 FD Len in next 4
980
# 254 FE Len in next 8
981
my $first_byte = to_num(substr($data, 0, 2, ''));
985
if ( $first_byte <= 251 ) {
989
elsif ( $first_byte == 252 ) {
990
$len = to_num(substr($data, 4, ''));
993
elsif ( $first_byte == 253 ) {
994
$len = to_num(substr($data, 6, ''));
997
elsif ( $first_byte == 254 ) {
998
$len = to_num(substr($data, 16, ''));
1002
# This shouldn't happen, but it may if we're passed data
1003
# that isn't length encoded.
1004
MKDEBUG && _d('data:', $data, 'first byte:', $first_byte);
1005
die "Invalid length encoded byte: $first_byte";
1008
MKDEBUG && _d('len:', $len, 'encode len', $encode_len);
1009
return $data, $len, $encode_len;
1012
# All numbers are stored with the least significant byte first in the MySQL
1015
my ( $str, $len ) = @_;
1017
$str = substr($str, 0, $len * 2);
1019
my @bytes = $str =~ m/(..)/g;
1021
foreach my $i ( 0 .. $#bytes ) {
1022
$result += hex($bytes[$i]) * (16 ** ($i * 2));
1029
return unpack('d', pack('H*', $str));
1032
# Accepts a reference to a string, which it will modify. Extracts a
1033
# length-coded binary off the front of the string and returns that value as an
1036
my ( $string ) = @_;
1037
my $first_byte = hex(substr($$string, 0, 2, ''));
1038
if ( $first_byte < 251 ) {
1041
elsif ( $first_byte == 252 ) {
1042
return to_num(substr($$string, 0, 4, ''));
1044
elsif ( $first_byte == 253 ) {
1045
return to_num(substr($$string, 0, 6, ''));
1047
elsif ( $first_byte == 254 ) {
1048
return to_num(substr($$string, 0, 16, ''));
1052
# Error packet structure:
1053
# Offset Bytes Field
1054
# ====== ================= ====================================
1055
# 00 00 00 01 MySQL proto header (already removed)
1056
# ff Error (already removed)
1057
# 0 00 00 Error number
1058
# 4 23 SQL state marker, always '#'
1059
# 6 00 00 00 00 00 SQL state
1060
# 16 00 ... Error message
1061
# The sqlstate marker and actual sqlstate are combined into one value.
1062
sub parse_error_packet {
1064
return unless $data;
1065
MKDEBUG && _d('ERROR data:', $data);
1066
if ( length $data < 16 ) {
1067
MKDEBUG && _d('Error packet is too short:', $data);
1070
my $errno = to_num(substr($data, 0, 4));
1071
my $marker = to_string(substr($data, 4, 2));
1072
return unless $marker eq '#';
1073
my $sqlstate = to_string(substr($data, 6, 10));
1074
my $message = to_string(substr($data, 16));
1077
sqlstate => $marker . $sqlstate,
1078
message => $message,
1080
MKDEBUG && _d('Error packet:', Dumper($pkt));
1084
# OK packet structure:
1086
# =========== ====================================
1087
# 00 00 00 01 MySQL proto header (already removed)
1088
# 00 OK/Field count (already removed)
1089
# 1-9 Affected rows (LCB)
1090
# 1-9 Insert ID (LCB)
1091
# 00 00 Server status
1092
# 00 00 Warning count
1093
# 00 ... Message (optional)
1094
sub parse_ok_packet {
1096
return unless $data;
1097
MKDEBUG && _d('OK data:', $data);
1098
if ( length $data < 12 ) {
1099
MKDEBUG && _d('OK packet is too short:', $data);
1102
my $affected_rows = get_lcb(\$data);
1103
my $insert_id = get_lcb(\$data);
1104
my $status = to_num(substr($data, 0, 4, ''));
1105
my $warnings = to_num(substr($data, 0, 4, ''));
1106
my $message = to_string($data);
1107
# Note: $message is discarded. It might be something like
1108
# Records: 2 Duplicates: 0 Warnings: 0
1110
affected_rows => $affected_rows,
1111
insert_id => $insert_id,
1113
warnings => $warnings,
1114
message => $message,
1116
MKDEBUG && _d('OK packet:', Dumper($pkt));
1120
# OK prepared statement packet structure:
1122
# =========== ====================================
1123
# 00 OK (already removed)
1124
# 00 00 00 00 Statement handler ID
1125
# 00 00 Number of columns in result set
1126
# 00 00 Number of parameters (?) in query
1127
sub parse_ok_prepared_statement_packet {
1129
return unless $data;
1130
MKDEBUG && _d('OK prepared statement data:', $data);
1131
if ( length $data < 8 ) {
1132
MKDEBUG && _d('OK prepared statement packet is too short:', $data);
1135
my $sth_id = to_num(substr($data, 0, 8, ''));
1136
my $num_cols = to_num(substr($data, 0, 4, ''));
1137
my $num_params = to_num(substr($data, 0, 4, ''));
1140
num_cols => $num_cols,
1141
num_params => $num_params,
1143
MKDEBUG && _d('OK prepared packet:', Dumper($pkt));
1147
# Currently we only capture and return the thread id.
1148
sub parse_server_handshake_packet {
1150
return unless $data;
1151
MKDEBUG && _d('Server handshake data:', $data);
1152
my $handshake_pattern = qr{
1155
(.+?)00 # n Null-Term String server_version
1156
(.{8}) # 4 thread_id
1157
.{16} # 8 scramble_buff
1158
.{2} # 1 filler: always 0x00
1159
(.{4}) # 2 server_capabilities
1160
.{2} # 1 server_language
1161
.{4} # 2 server_status
1162
.{26} # 13 filler: always 0x00
1163
# 13 rest of scramble_buff
1165
my ( $server_version, $thread_id, $flags ) = $data =~ m/$handshake_pattern/;
1167
server_version => to_string($server_version),
1168
thread_id => to_num($thread_id),
1169
flags => parse_flags($flags),
1171
MKDEBUG && _d('Server handshake packet:', Dumper($pkt));
1175
# Currently we only capture and return the user and default database.
1176
sub parse_client_handshake_packet {
1178
return unless $data;
1179
MKDEBUG && _d('Client handshake data:', $data);
1180
my ( $flags, $user, $buff_len ) = $data =~ m{
1182
(.{8}) # Client flags
1183
.{10} # Max packet size, charset
1185
((?:..)+?)00 # Null-terminated user name
1186
(..) # Length-coding byte for scramble buff
1189
# This packet is easy to detect because it's the only case where
1190
# the server sends the client a packet first (its handshake) and
1191
# then the client only and ever sends back its handshake.
1193
MKDEBUG && _d('Did not match client handshake packet');
1197
# This length-coded binary doesn't seem to be a normal one, it
1198
# seems more like a length-coded string actually.
1199
my $code_len = hex($buff_len);
1200
my ( $db ) = $data =~ m!
1201
^.{64}${user}00.. # Everything matched before
1202
(?:..){$code_len} # The scramble buffer
1203
(.*)00\Z # The database name
1206
user => to_string($user),
1207
db => $db ? to_string($db) : '',
1208
flags => parse_flags($flags),
1210
MKDEBUG && _d('Client handshake packet:', Dumper($pkt));
1214
# COM data is not 00-terminated, but the the MySQL client appends \0,
1215
# so we have to use the packet length to know where the data ends.
1216
sub parse_com_packet {
1217
my ( $data, $len ) = @_;
1218
return unless $data && $len;
1219
MKDEBUG && _d('COM data:',
1220
(substr($data, 0, 100).(length $data > 100 ? '...' : '')),
1222
my $code = substr($data, 0, 2);
1223
my $com = $com_for{$code};
1225
MKDEBUG && _d('Did not match COM packet');
1228
if ( $code ne COM_STMT_EXECUTE
1229
&& $code ne COM_STMT_CLOSE
1230
&& $code ne COM_STMT_RESET )
1232
# Data for the most common COM, e.g. COM_QUERY, is text.
1233
# COM_STMT_EXECUTE is not, so we leave it binary; it can
1234
# be parsed by parse_execute_packet().
1235
$data = to_string(substr($data, 2, ($len - 1) * 2));
1242
MKDEBUG && _d('COM packet:', Dumper($pkt));
1246
# Execute prepared statement packet structure:
1248
# =========== ========================================
1249
# 00 Code 17, COM_STMT_EXECUTE
1250
# 00 00 00 00 Statement handler ID
1252
# 00 00 00 00 Iteration count (reserved, always 1)
1253
# (param_count+7)/8 NULL bitmap
1254
# 00 1 if new parameters, else 0
1255
# n*2 Parameter types (only if new parameters)
1256
sub parse_execute_packet {
1257
my ( $data, $sths ) = @_;
1258
return unless $data && $sths;
1260
my $sth_id = to_num(substr($data, 2, 8));
1261
return unless defined $sth_id;
1263
my $sth = $sths->{$sth_id};
1265
MKDEBUG && _d('Skipping unknown statement handle', $sth_id);
1268
my $null_count = int(($sth->{num_params} + 7) / 8) || 1;
1269
my $null_bitmap = to_num(substr($data, 20, $null_count * 2));
1270
MKDEBUG && _d('NULL bitmap:', $null_bitmap, 'count:', $null_count);
1272
# This chops off everything up to the byte for new params.
1273
substr($data, 0, 20 + ($null_count * 2), '');
1275
my $new_params = to_num(substr($data, 0, 2, ''));
1277
if ( $new_params ) {
1278
MKDEBUG && _d('New param types');
1279
# It seems all params are type 254, MYSQL_TYPE_STRING. Perhaps
1280
# this depends on the client. If we ever need these types, they
1281
# can be saved here. Otherwise for now I just want to see the
1282
# types in debug output.
1283
for my $i ( 0..($sth->{num_params}-1) ) {
1284
my $type = to_num(substr($data, 0, 4, ''));
1285
push @types, $type_for{$type};
1286
MKDEBUG && _d('Param', $i, 'type:', $type, $type_for{$type});
1288
$sth->{types} = \@types;
1291
# Retrieve previous param types if there are param vals (data).
1292
@types = @{$sth->{types}} if $data;
1295
# $data should now be truncated up to the parameter values.
1297
my $arg = $sth->{statement};
1298
MKDEBUG && _d('Statement:', $arg);
1299
for my $i ( 0..($sth->{num_params}-1) ) {
1302
if ( $null_bitmap & (2**$i) ) {
1303
MKDEBUG && _d('Param', $i, 'is NULL (bitmap)');
1308
if ( $unpack_type{$types[$i]} ) {
1309
($val, $len) = $unpack_type{$types[$i]}->($data);
1312
# TODO: this is probably going to break parsing other param vals
1313
MKDEBUG && _d('No handler for param', $i, 'type', $types[$i]);
1319
# Replace ? in prepared statement with value.
1320
MKDEBUG && _d('Param', $i, 'val:', $val);
1323
# Remove this param val from the data, putting us at the next one.
1324
substr($data, 0, $len * 2, '') if $len;
1329
arg => "EXECUTE $arg",
1331
MKDEBUG && _d('Execute packet:', Dumper($pkt));
1337
return unless $data;
1338
my $sth_id = to_num(substr($data, 2, 8));
1344
die "I need flags" unless $flags;
1345
MKDEBUG && _d('Flag data:', $flags);
1346
my %flags = %flag_for;
1347
my $flags_dec = to_num($flags);
1348
foreach my $flag ( keys %flag_for ) {
1349
my $flagno = $flag_for{$flag};
1350
$flags{$flag} = ($flags_dec & $flagno ? 1 : 0);
1355
# Takes a scalarref to a hex string of compressed data.
1356
# Returns a scalarref to a hex string of the uncompressed data.
1357
# The given hex string of compressed data is not modified.
1358
sub uncompress_data {
1359
my ( $data, $len ) = @_;
1360
die "I need data" unless $data;
1361
die "I need a len argument" unless $len;
1362
die "I need a scalar reference to data" unless ref $data eq 'SCALAR';
1363
MKDEBUG && _d('Uncompressing data');
1366
# Pack hex string into compressed binary data.
1367
my $comp_bin_data = pack('H*', $$data);
1369
# Uncompress the compressed binary data.
1370
my $uncomp_bin_data = '';
1371
my $z = new IO::Uncompress::Inflate(
1373
) or die "IO::Uncompress::Inflate failed: $InflateError";
1374
my $status = $z->read(\$uncomp_bin_data, $len)
1375
or die "IO::Uncompress::Inflate failed: $InflateError";
1377
# Unpack the uncompressed binary data back into a hex string.
1378
# This is the original MySQL packet(s).
1379
my $uncomp_data = unpack('H*', $uncomp_bin_data);
1381
return \$uncomp_data;
1384
# Returns 1 on success or 0 on failure. Failure is probably
1385
# detecting compression but not being able to uncompress
1386
# (uncompress_packet() returns 0).
1387
sub detect_compression {
1388
my ( $self, $packet, $session ) = @_;
1389
MKDEBUG && _d('Checking for client compression');
1390
# This is a necessary hack for detecting compression in-stream without
1391
# having seen the client handshake and CLIENT_COMPRESS flag. If the
1392
# client is compressing packets, there will be an extra 7 bytes before
1393
# the regular MySQL header. For short COM_QUERY commands, these 7 bytes
1394
# are usually zero where we'd expect to see 03 for COM_QUERY. So if we
1395
# parse this packet and it looks like a COM_SLEEP (00) which is not a
1396
# command that the client can send, then chances are the client is using
1398
my $com = parse_com_packet($packet->{data}, $packet->{mysql_data_len});
1399
if ( $com && $com->{code} eq COM_SLEEP ) {
1400
MKDEBUG && _d('Client is using compression');
1401
$session->{compress} = 1;
1403
# Since parse_packet() didn't know the packet was compressed, it
1404
# called remove_mysql_header() which removed the first 4 of 7 bytes
1405
# of the compression header. We must restore these 4 bytes, then
1406
# uncompress and remove the MySQL header. We only do this once.
1407
$packet->{data} = $packet->{mysql_hdr} . $packet->{data};
1408
return 0 unless $self->uncompress_packet($packet, $session);
1409
remove_mysql_header($packet);
1412
MKDEBUG && _d('Client is NOT using compression');
1413
$session->{compress} = 0;
1418
# Returns 1 if the packet was uncompressed or 0 if we can't uncompress.
1419
# Failure is usually due to IO::Uncompress not being available.
1420
sub uncompress_packet {
1421
my ( $self, $packet, $session ) = @_;
1422
die "I need a packet" unless $packet;
1423
die "I need a session" unless $session;
1425
# From the doc: "A compressed packet header is:
1426
# packet length (3 bytes),
1427
# packet number (1 byte),
1428
# and Uncompressed Packet Length (3 bytes).
1429
# The Uncompressed Packet Length is the number of bytes
1430
# in the original, uncompressed packet. If this is zero
1431
# then the data is not compressed."
1437
my $uncomp_data_len;
1439
$data = \$packet->{data};
1440
$comp_hdr = substr($$data, 0, 14, '');
1441
$comp_data_len = to_num(substr($comp_hdr, 0, 6));
1442
$pkt_num = to_num(substr($comp_hdr, 6, 2));
1443
$uncomp_data_len = to_num(substr($comp_hdr, 8, 6));
1444
MKDEBUG && _d('Compression header data:', $comp_hdr,
1445
'compressed data len (bytes)', $comp_data_len,
1447
'uncompressed data len (bytes)', $uncomp_data_len);
1449
if ( $EVAL_ERROR ) {
1450
$session->{EVAL_ERROR} = $EVAL_ERROR;
1451
$self->fail_session($session, 'failed to parse compression header');
1455
if ( $uncomp_data_len ) {
1457
$data = uncompress_data($data, $uncomp_data_len);
1458
$packet->{data} = $$data;
1460
if ( $EVAL_ERROR ) {
1461
$session->{EVAL_ERROR} = $EVAL_ERROR;
1462
$self->fail_session($session, 'failed to uncompress data');
1463
die "Cannot uncompress packet. Check that IO::Uncompress::Inflate "
1464
. "is installed.\nError: $EVAL_ERROR";
1468
MKDEBUG && _d('Packet is not really compressed');
1469
$packet->{data} = $$data;
1475
# Removes the first 4 bytes of the packet data which should be
1476
# a MySQL header: 3 bytes packet length, 1 byte packet number.
1477
sub remove_mysql_header {
1478
my ( $packet ) = @_;
1479
die "I need a packet" unless $packet;
1481
# NOTE: the data is modified by the inmost substr call here! If we
1482
# had all the data in the TCP packets, we could change this to a while
1483
# loop; while get-a-packet-from-$data, do stuff, etc. But we don't,
1484
# and we don't want to either.
1485
my $mysql_hdr = substr($packet->{data}, 0, 8, '');
1486
my $mysql_data_len = to_num(substr($mysql_hdr, 0, 6));
1487
my $pkt_num = to_num(substr($mysql_hdr, 6, 2));
1488
MKDEBUG && _d('MySQL packet: header data', $mysql_hdr,
1489
'data len (bytes)', $mysql_data_len, 'number', $pkt_num);
1491
$packet->{mysql_hdr} = $mysql_hdr;
1492
$packet->{mysql_data_len} = $mysql_data_len;
1493
$packet->{number} = $pkt_num;
1498
sub _get_errors_fh {
1500
my $errors_fh = $self->{errors_fh};
1501
return $errors_fh if $errors_fh;
1503
# Errors file isn't open yet; try to open it.
1505
if ( $o && $o->has('tcpdump-errors') && $o->got('tcpdump-errors') ) {
1506
my $errors_file = $o->get('tcpdump-errors');
1507
MKDEBUG && _d('tcpdump-errors file:', $errors_file);
1508
open $errors_fh, '>>', $errors_file
1509
or die "Cannot open tcpdump-errors file $errors_file: $OS_ERROR";
1512
$self->{errors_fh} = $errors_fh;
1517
my ( $self, $session, $reason ) = @_;
1518
MKDEBUG && _d('Client', $session->{client}, 'failed because', $reason);
1519
my $errors_fh = $self->_get_errors_fh();
1521
my $raw_packets = $session->{raw_packets};
1522
delete $session->{raw_packets}; # Don't dump, it's printed below.
1523
$session->{reason_for_failure} = $reason;
1524
my $session_dump = '# ' . Dumper($session);
1525
chomp $session_dump;
1526
$session_dump =~ s/\n/\n# /g;
1527
print $errors_fh "$session_dump\n";
1529
local $LIST_SEPARATOR = "\n";
1530
print $errors_fh "@$raw_packets";
1531
print $errors_fh "\n";
1534
delete $self->{sessions}->{$session->{client}};
1538
# Delete anything we added to the session related to
1539
# buffering a large query received in multiple packets.
1541
my ( $self, $session ) = @_;
1542
map { delete $session->{$_} } qw(buff buff_left mysql_data_len);
1547
my ($package, undef, $line) = caller 0;
1548
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
1549
map { defined $_ ? $_ : 'undef' }
1551
print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
1556
# ###########################################################################
1557
# End MySQLProtocolParser package
1558
# ###########################################################################