summaryrefslogtreecommitdiffstats
path: root/script/traffic_summary.pl
blob: 295a320aa0a07b14b0808da1319bc2003a30f7ab (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
#! /usr/bin/perl
#
# Summarise tshark pdml output into a form suitable for the load test tool
#
# Copyright (C) Catalyst.Net Ltd 2017
#
# Catalyst.Net's contribution was written by Gary Lockyer
# <gary@catalyst.net.nz>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# 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.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

use warnings;
use strict;

use Getopt::Long;
use Pod::Usage;

BEGIN {
    unless (eval "require XML::Twig") {
        warn "traffic_summary requires the perl module XML::Twig\n" .
             "on Ubuntu/Debian releases run\n".
             "  sudo apt install libxml-twig-perl \n".
             "or install from CPAN\n".
             "\nThe reported error was:\n$@";
        exit(1);
    }
}


my %ip_map;              # Map of IP address to sequence number
my $ip_sequence = 0;     # count of unique IP addresses seen


my $timestamp;           # Packet timestamp
my $stream;              # Wireshark stream number
my $ip_proto;            # IP protocol (IANA protocol number)
my $source;              # source IP address
my $dest;                # destination address
my $proto;               # application protocol name
my $description;         # protocol specific description
my %proto_data;          # protocol specific data captured for the current packet
my $malformed_packet;    # Indicates the current packet has errors
my $ldap_filter;         # cleaned ldap filter
my $ldap_attributes;     # attributes requested in an ldap query



# Dispatch table mapping the wireshark variables of interest to the
# functions responsible for processing them
my %field_dispatch_table = (
    'timestamp'                       => \&timestamp,
    'ip.src'                          => \&ip_src,
    'ipv6.src'                        => \&ip_src,
    'ip.dst'                          => \&ip_dst,
    'ipv6.dst'                        => \&ip_dst,
    'ip.proto'                        => \&ip_proto,
    'udp.stream'                      => \&stream,
    'tcp.stream'                      => \&stream,
    'dns.flags.opcode'                => \&field_data,
    'dns.flags.response'              => \&field_data,
    'netlogon.opnum'                  => \&field_data,
    'kerberos.msg_type'               => \&field_data,
    'smb.cmd'                         => \&field_data,
    'smb2.cmd'                        => \&field_data,
    'ldap.protocolOp'                 => \&field_data,
    'gss-api.OID'                     => \&field_data,
    'ldap.gssapi_encrypted_payload'   => \&field_data,
    'ldap.baseObject'                 => \&field_data,
    'ldap.scope'                      => \&field_data,
    'ldap.AttributeDescription'       => \&ldap_attribute,
    'ldap.modification_element'       => \&ldap_add_modify,
    'ldap.AttributeList_item_element' => \&ldap_add_modify,
    'ldap.operation'                  => \&field_data,
    'ldap.authentication'             => \&field_data,
    'lsarpc.opnum'                    => \&field_data,
    'samr.opnum'                      => \&field_data,
    'dcerpc.pkt_type'                 => \&field_data,
    'epm.opnum'                       => \&field_data,
    'dnsserver.opnum'                 => \&field_data,
    'drsuapi.opnum'                   => \&field_data,
    'browser.command'                 => \&field_data,
    'smb_netlogon.command'            => \&field_data,
    'srvsvc.opnum'                    => \&field_data,
    'nbns.flags.opcode'               => \&field_data,
    'nbns.flags.response'             => \&field_data,
    '_ws.expert.message'              => \&field_data,
);

# Dispatch table mapping protocols to the routine responsible for formatting
# their output.  Protocols not in this table are ignored.
#
my %proto_dispatch_table = (
    'dns'          => sub { return format_opcode( 'dns.flags.response')},
    'rpc_netlogon' => sub { return format_opcode( 'netlogon.opnum')},
    'kerberos'     => \&format_kerberos,
    'smb'          => sub { return format_opcode( 'smb.cmd')},
    'smb2'         => sub { return format_opcode( 'smb2.cmd')},
    'ldap'         => \&format_ldap,
    'cldap'        => \&format_ldap,
    'lsarpc'       => sub { return format_opcode( 'lsarpc.opnum')},
    'samr'         => sub { return format_opcode( 'samr.opnum')},
    'dcerpc'       => sub { return format_opcode( 'dcerpc.pkt_type')},
    'epm'          => sub { return format_opcode( 'epm.opnum')},
    'dnsserver'    => sub { return format_opcode( 'dnsserver.opnum')},
    'drsuapi'      => sub { return format_opcode( 'drsuapi.opnum')},
    'browser'      => sub { return format_opcode( 'browser.command')},
    'smb_netlogon' => sub { return format_opcode( 'smb_netlogon.command')},
    'srvsvc'       => sub { return format_opcode( 'srvsvc.opnum')},
    'nbns'         => sub { return format_opcode( 'nbns.flags.response')},
);

# XPath entry to extract the kerberos cname
my $kerberos_cname_path =
      'packet/proto/field[@name = "kerberos.as_req_element"]'
    . '/field[@name = "kerberos.req_body_element"]'
    . '/field[@name = "kerberos.cname_element"]'
    . '/field[@name = "kerberos.name_string"]'
    . '/field[@name = "kerberos.KerberosString"]';

# XPath entry to extract the ldap filter
my $ldap_filter_path =
      'field[@name = "ldap.searchRequest_element"]/field';


# Create an XML Twig parser and register the event handlers.
#
my $t = XML::Twig->new(
    start_tag_handlers => {
        'packet'       => \&packet_start,
    },
    twig_handlers => {
         'packet'              => \&packet,
         'proto'               => \&protocol,
         'field'               => \&field,
         $kerberos_cname_path  => \&kerberos_cname,
         $ldap_filter_path     => \&ldap_filter,
    },
);

#------------------------------------------------------------------------------
# Main loop
#
#------------------------------------------------------------------------------
my $help = 0;
GetOptions( 'help|h' => \$help) or pod2usage(2);
pod2usage(1) if $help;

if (@ARGV) {
    foreach my $file (@ARGV) {
        eval {
            $t->parsefile( $file);
        };
        if ($@) {
            print STDERR "Unable to process $file, ".
                         "did you run tshark with the -T pdml option?";
        }
    }
} else {
    pod2usage(1) if -t STDIN;
    eval {
        $t->parse( \*STDIN);
    };
    if ($@) {
        print STDERR "Unable to process input, ".
                     "are you running tshark with the -T pdml option?";
    }
}


#------------------------------------------------------------------------------
# New packet detected reset the globals
#------------------------------------------------------------------------------
sub packet_start
{
    my ($t, $packet) = @_;
    $timestamp           = "";
    $stream              = "";
    $ip_proto            = "";
    $source              = "";
    $dest                = "";
    $description         = undef;
    %proto_data          = ();
    $malformed_packet    = undef;
    $ldap_filter         = "";
    $ldap_attributes     = "";
}

#------------------------------------------------------------------------------
# Complete packet element parsed from the XML feed
# output the protocol summary if required
#------------------------------------------------------------------------------
sub packet
{
    my ($t, $packet) = @_;

    my $data;
    if (exists $proto_dispatch_table{$proto}) {
        if ($malformed_packet) {
            $data = "\t\t** Malformed Packet ** " . ($proto_data{'_ws.expert.message.show'} || '');
        } else {
            my $rsub = $proto_dispatch_table{$proto};
            $data = &$rsub();
        }
        print "$timestamp\t$ip_proto\t$stream\t$source\t$dest\t$proto\t$data\n";
    }
    $t->purge;
}

#------------------------------------------------------------------------------
# Complete protocol element parsed from the XML input
# Update the protocol name
#------------------------------------------------------------------------------
sub protocol
{
    my ($t, $protocol) = @_;
    if ($protocol->{att}->{showname}) {
    }
    # Tag a packet as malformed if the protocol is _ws.malformed
    # and the hide attribute is not 'yes'
    if ($protocol->{att}->{name} eq '_ws.malformed'
        && !($protocol->{att}->{hide} && $protocol->{att}->{hide} eq 'yes')
    ) {
        $malformed_packet = 1;
    }
    # Don't set the protocol name if it's a wireshark malformed
    # protocol entry, or the packet was truncated during capture
    my $p = $protocol->{att}->{name};
    if ($p ne '_ws.malformed' && $p ne '_ws.short')  {
        $proto = $p;
    }
}


#------------------------------------------------------------------------------
# Complete field element parsed, extract any data of interest
#------------------------------------------------------------------------------
sub field
{
    my ($t, $field) = @_;
    my $name = $field->{att}->{name};

    # Only process the field if it has a corresponding entry in
    # %field_dispatch_table
    if (exists $field_dispatch_table{$name}) {
        my $rsub = $field_dispatch_table{$name};
        &$rsub( $field);
    }
}

#------------------------------------------------------------------------------
# Process a timestamp field element
#------------------------------------------------------------------------------
sub timestamp
{
    my ($field) = @_;
    $timestamp = $field->{att}->{value};
}

#------------------------------------------------------------------------------
# Process a wireshark stream element, used to group a sequence of requests
# and responses between two IP addresses
#------------------------------------------------------------------------------
sub stream
{
    my ($field) = @_;
    $stream = $field->{att}->{show};
}

#------------------------------------------------------------------------------
# Process a source ip address field, mapping the IP address to it's
# corresponding sequence number.
#------------------------------------------------------------------------------
sub ip_src
{
    my ($field) = @_;
    $source = map_ip( $field);
}

#------------------------------------------------------------------------------
# Process a destination ip address field, mapping the IP address to it's
# corresponding sequence number.
#------------------------------------------------------------------------------
sub ip_dst
{
    my ($field) = @_;
    $dest = map_ip( $field);
}

#------------------------------------------------------------------------------
# Process an ip protocol element, extracting IANA protocol number
#------------------------------------------------------------------------------
sub ip_proto
{
    my ($field) = @_;
    $ip_proto = $field->{att}->{value};
}



#------------------------------------------------------------------------------
# Extract an ldap attribute and append it to ldap_attributes
#------------------------------------------------------------------------------
sub ldap_attribute
{
    my ($field) = @_;
    my $attribute =  $field->{att}->{show};

    if (defined $attribute) {
        $ldap_attributes .= "," if $ldap_attributes;
        $ldap_attributes .= $attribute;
    }
}

#------------------------------------------------------------------------------
# Process a field element, extract the value, show and showname attributes
# and store them in the %proto_data hash.
#
#------------------------------------------------------------------------------
sub field_data
{
    my ($field) = @_;
    my $name  = $field->{att}->{name};
    $proto_data{$name.'.value'}    = $field->{att}->{value};
    $proto_data{$name.'.show'}     = $field->{att}->{show};
    $proto_data{$name.'.showname'} = $field->{att}->{showname};
}

#------------------------------------------------------------------------------
# Process a kerberos cname element, if the cname ends with a $ it's a machine
# name. Otherwise it's a user name.
#
#------------------------------------------------------------------------------
sub kerberos_cname
{
    my ($t, $field) = @_;
    my $cname =  $field->{att}->{show};
    my  $type;
    if( $cname =~ /\$$/) {
        $type = 'machine';
     } else {
        $type = 'user';
     }
     $proto_data{'kerberos.cname.type'} = $type;
}


#------------------------------------------------------------------------------
# Process an ldap filter, remove the values but keep the attribute names
#------------------------------------------------------------------------------
sub ldap_filter
{
    my ($t, $field) = @_;
    if ( $field->{att}->{show} && $field->{att}->{show} =~ /^Filter:/) {
        my $filter = $field->{att}->{show};

        # extract and save the objectClass to keep the value
        my @object_classes;
        while ( $filter =~ m/\((objectClass=.*?)\)/g) {
           push  @object_classes, $1;
        }

        # extract and save objectCategory and the top level value
        my @object_categories;
        while ( $filter =~ m/(\(objectCategory=.*?,|\(objectCategory=.*?\))/g
        ) {
            push @object_categories, $1;
        }

        # Remove all the values from the attributes
        # Input
        #     Filter: (nCName=DC=DomainDnsZones,DC=sub1,DC=ad,DC=rh,DC=at,DC=net)
        # Output
        #     (nCName)
        $filter =~ s/^Filter:\s*//; # Remove the 'Filter: ' prefix
        $filter =~ s/=.*?\)/\)/g;   # Remove from the = to the first )

        # Now restore the parts of objectClass and objectCategory that are being
        # retained
        #
        for my $cat (@object_categories) {
            $filter =~ s/\(objectCategory\)/$cat/;
        }

        for my $class (@object_classes) {
            $filter =~ s/\(objectClass\)/($class)/;
        }

        $ldap_filter = $filter;
    } else {
        # Ok not an ldap filter so call the default field handler
        field( $t, $field);
    }
}


#------------------------------------------------------------------------------
# Extract the attributes from ldap modification and add requests
#------------------------------------------------------------------------------
sub ldap_add_modify
{
    my ($field) = @_;
    my $type      = $field->first_child('field[@name="ldap.type"]');
    my $attribute = $type->{att}->{show} if $type;
    if (defined $attribute) {
        $ldap_attributes .= "," if $ldap_attributes;
        $ldap_attributes .= $attribute;
    }
}
#------------------------------------------------------------------------------
# Map an IP address to a unique sequence number. Assigning it a sequence number
# if one has not already been assigned.
#
#------------------------------------------------------------------------------
sub map_ip
{
    my ($field) = @_;
    my $ip = $field->{att}->{show};
    if ( !exists( $ip_map{$ip})) {
        $ip_sequence++;
        $ip_map{$ip} = $ip_sequence;
    }
    return $ip_map{$ip};
}

#------------------------------------------------------------------------------
# Format a protocol operation code for output.
#
#------------------------------------------------------------------------------
sub format_opcode
{
    my ($name) = @_;
    my $operation   = $proto_data{$name.'.show'};
    my $description = $proto_data{$name.'.showname'} || '';

    # Strip off the common prefix text, and the trailing (n).
    # This tidies up most but not all descriptions.
    $description =~ s/^[^:]*?: ?//     if $description;
    $description =~ s/^Message is a // if $description;
    $description =~ s/\(\d+\)\s*$//    if $description;
    $description =~ s/\s*$//           if $description;

    return "$operation\t$description";
}

#------------------------------------------------------------------------------
# Format ldap protocol details for output
#------------------------------------------------------------------------------
sub format_ldap
{
    my ($name) = @_;
    if (   exists( $proto_data{'ldap.protocolOp.show'})
        || exists( $proto_data{'gss-api.OID.show'})
    ) {
        my $operation   = $proto_data{'ldap.protocolOp.show'};
        my $description = $proto_data{'ldap.protocolOp.showname'} || '';
        my $oid         = $proto_data{'gss-api.OID.show'}         || '';
        my $base_object = $proto_data{'ldap.baseObject.show'}     || '';
        my $scope       = $proto_data{'ldap.scope.show'}          || '';

        # Now extract operation specific data
        my $extra;
        my $extra_desc;
        $operation = '' if !defined $operation;
        if ($operation eq 6) {
            # Modify operation
            $extra         = $proto_data{'ldap.operation.show'};
            $extra_desc    = $proto_data{'ldap.operation.showname'};
        } elsif ($operation eq 0) {
            # Bind operation
            $extra         = $proto_data{'ldap.authentication.show'};
            $extra_desc    = $proto_data{'ldap.authentication.showname'};
        }
        $extra      = '' if !defined $extra;
        $extra_desc = '' if !defined $extra_desc;


        # strip the values out of the base object
        if ($base_object) {
            $base_object =~ s/^<//;       # leading '<' if present
            $base_object =~ s/>$//;       # trailing '>' if present
            $base_object =~ s/=.*?,/,/g;  # from = up to the next comma
            $base_object =~ s/=.*?$//;    # from = up to the end of string
        }

        # strip off the leading prefix on the extra_description
        # and the trailing (n);
        $extra_desc =~ s/^[^:]*?: ?//  if $extra_desc;
        $extra_desc =~ s/\(\d+\)\s*$// if $extra_desc;
        $extra_desc =~ s/\s*$//        if $extra_desc;

        # strip off the common prefix on the description
        # and the trailing (n);
        $description =~ s/^[^:]*?: ?//  if $description;
        $description =~ s/\(\d+\)\s*$// if $description;
        $description =~ s/\s*$//        if $description;

        return "$operation\t$description\t$scope\t$base_object"
              ."\t$ldap_filter\t$ldap_attributes\t$extra\t$extra_desc\t$oid";
    } else {
        return "\t*** Unknown ***";
    }
}

#------------------------------------------------------------------------------
# Format kerberos protocol details for output.
#------------------------------------------------------------------------------
sub format_kerberos
{
    my $msg_type    = $proto_data{'kerberos.msg_type.show'} || '';
    my $cname_type  = $proto_data{'kerberos.cname.type'} || '';
    my $description = $proto_data{'kerberos.msg_type.showname'} || '';

    # Tidy up the description
    $description =~ s/^[^:]*?: ?//  if $description;
    $description =~ s/\(\d+\)\s*$// if $description;
    $description =~ s/\s*$//        if $description;
    return "$msg_type\t$description\t$cname_type";
}

=pod

=head1 NAME

traffic_summary.pl - summarise tshark pdml output

=head1 USAGE

B<traffic_summary.pl> [FILE...]

Summarise samba network traffic from tshark pdml output.  Produces a tsv
delimited summary of samba activity.

To process unencrypted traffic

 tshark -r capture.file -T pdml | traffic_summary.pl

To process encrypted kerberos traffic

 tshark -r capture.file -K krb5.keytab -o kerberos.decrypt:true -T pdml | traffic_summary.pl

To display more detailed documentation, including details of the output format

 perldoc traffic_summary.pl

 NOTE: tshark pdml output is very verbose, so it's better to pipe the tshark
       output directly to traffic_summary, rather than generating
       intermediate pdml format files.

=head1 OPTIONS
 B<--help> Display usage message and exit.

=head1 DESCRIPTION

Summarises tshark pdml output into a format suitable for load analysis
and input into load generation tools.

It reads the pdml input from stdin or the list of files passed on the command line.


=head2 Output format
    The output is tab delimited fields and one line per summarised packet.

=head3 Fields
     B<timestamp>                Packet timestamp
     B<IP protocol>              The IANA protocol number
     B<Wireshark Stream Number>  Calculated by wireshark groups related requests and responses
     B<Source IP>                The unique sequence number for the source IP address
     B<Destination IP>           The unique sequence number for the destination IP address
     B<protocol>                 The protocol name
     B<opcode>                   The protocol operation code
     B<Description>              The protocol or operation description
     B<extra>                    Extra protocol specific data, may be more than one field


=head2 IP address mapping
    Rather than capturing and printing the IP addresses. Each unique IP address
    seen is assigned a sequence number. So the first IP address seen will be 1,
    the second 2 ...

=head2 Packets collected
    Packets containing the following protocol records are summarised:
      dns
      rpc_netlogon
      kerberos
      smb
      smb2
      ldap
      cldap
      lsarpc
      samr
      dcerpc
      epm
      dnsserver
      drsuapi
      browser
      smb_netlogon
      srvsvc
      nbns

   Any other packets are ignored.

   In addition to the standard elements extra data is returned for the following
   protocol record.

=head3 kerberos
     cname_type  machine cname ends with a $
                 user    cname does not end with a $

=head3 ldap

     scope                Query Scope
                            0 - Base
                            1 - One level
                            2 - sub tree
     base_object          ldap base object
     ldap_filter          the ldap filter, attribute names are retained but the values
                          are removed.
     ldap_attributes      ldap attributes, only the names are retained any values are
                          discarded, with the following two exceptions
                            objectClass    all the attribute values are retained
                            objectCategory the top level value is retained
                                           i.e. everything from the = to the first ,

=head3 ldap modifiyRequest
     In addition to the standard ldap fields the modification type is also captured

     modify_operator      for modifyRequests this contains the modify operation
                            0 - add
                            1 - delete
                            2 - replace
     modify_description   a description of the operation if available

=head3 modify bindRequest
     In addition to the standard ldap fields details of the authentication
     type are captured

     authentication type  0 - Simple
                          3 - SASL
     description          Description of the authentication mechanism
     oid                  GSS-API OID's
                            1.2.840.113554.1.2.2   - Kerberos v5
                            1.2.840.48018.1.2.2    - Kerberos V5
                               (incorrect, used by old Windows versions)
                            1.3.6.1.5.5.2          - SPNEGO
                            1.3.6.1.5.2.5          - IAKERB
                            1.3.6.1.4.1.311.2.2.10 - NTLM SSP
                            1.3.6.1.5.5.14         - SCRAM-SHA-1
                            1.3.6.1.5.5.18         - SCRAM-SHA-256
                            1.3.6.1.5.5.15.1.1.*   - GSS-EAP
                            1.3.6.1.5.2.7          - PKU2U
                            1.3.6.1.5.5.1.1        - SPKM-1
                            1.3.6.1.5.5.1.2        - SPKM-2
                            1.3.6.1.5.5.1.3        - SPKM-3
                            1.3.6.1.5.5.9          - LIPKEY
                            1.2.752.43.14.2        - NETLOGON

=head1 DEPENDENCIES
tshark
XML::Twig         For Ubuntu libxml-twig-perl, or from CPAN
use Getopt::Long
use Pod::Usage


=head1 Diagnostics

=head2 ** Unknown **
Unable to determine the operation being performed, for ldap it typically
indicates a kerberos encrypted operation.

=head2 ** Malformed Packet **
tshark indicated that the packet was malformed, for ldap it usually indicates TLS
encrypted traffic.

=head1 LICENSE AND COPYRIGHT

 Copyright (C) Catalyst.Net Ltd 2017

 Catalyst.Net's contribution was written by Gary Lockyer
 <gary@catalyst.net.nz>.

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 3 of the License, or
 (at your option) any later version.

 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.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.


=cut