undns 0.4.0f → 0.4.0h

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,72 @@
1
+ #include "nscommon.h"
2
+ #include <sys/types.h>
3
+ #include <sys/socket.h>
4
+ #include <netinet/in.h>
5
+ #ifndef __LCLINT__
6
+ #include <arpa/inet.h>
7
+ #endif
8
+ #include <stdio.h>
9
+
10
+ // #ifdef __linux__
11
+ // #define ADDR_T in_addr_t
12
+ // #else
13
+ #define ADDR6_T struct in6_addr
14
+ // #endif
15
+
16
+ typedef ADDR6_T address6;
17
+ /* in radix.h typedef unsigned char prefixlen_t; */
18
+ /* typedef struct patricia6_entry *patricia6_entry; */
19
+ /* typedef struct patricia6_table *patricia6_table; */
20
+
21
+ struct patricia6_entry {
22
+ address6 destination;
23
+ prefixlen_t prefixlen;
24
+ unsigned char position;
25
+ void *data;
26
+ struct patricia6_entry *on,*off;
27
+ };
28
+
29
+ struct patricia6_table {
30
+ /*@null@*/ struct patricia6_entry *routes;
31
+ /*@null@*/ struct patricia6_entry *default_route;
32
+ };
33
+
34
+ /* externally useful bits */
35
+ struct patricia6_table *patricia6_new(void);
36
+ void patricia6_delete(/*@only@*/ struct patricia6_table * p, void (*data_free)(void *));
37
+ void patricia6_insert(struct patricia6_table *pt, address6 a,
38
+ unsigned char prefixlen, /*@owned@*/void *data);
39
+ /*@null@*//* just get the data *//*@dependent@*/
40
+ void *patricia6_lookup(struct patricia6_table *pt, address6 a);
41
+ /* get it all */
42
+ boolean patricia6_lookup_all(struct patricia6_table * pt,
43
+ address6 a,
44
+ /*@out@*/ address6 *prefix,
45
+ /*@out@*/ prefixlen_t *prefixlen,
46
+ /*@out@*/ void **data);
47
+ void patricia6_dump(struct patricia6_table *pt, const char *filename);
48
+
49
+ /* slightly less useful */
50
+ /*@null@*//*@dependent@*/
51
+ struct patricia6_entry *
52
+ radix6_resolve_route(/*@null@*/
53
+ struct patricia6_entry *top,
54
+ address6 destination);
55
+ void *get_data6(const struct patricia6_entry *r);
56
+
57
+ /* somewhat useful bits */
58
+ void set_data6(struct patricia6_entry *r, void *data);
59
+
60
+ void radix6_insert(struct patricia6_entry **p, /*@owned@*//*@partial@*/struct patricia6_entry *r);
61
+
62
+ void radix6_remove_route(struct patricia6_table *n,
63
+ address6 destination,
64
+ unsigned char prefixlen);
65
+
66
+ void radix6_print_routing_table_text(/*@null@*/const struct patricia6_entry *r,
67
+ FILE *fdout);
68
+ void radix6_print_routing_table_dot(/*@null@*/const struct patricia6_entry *r,
69
+ int fd);
70
+
71
+ address6 radix6_mask(address6 addr, prefixlen_t prefix);
72
+ boolean radix6_test_export(address6 addr, prefixlen_t x); // used by check_radix, real thing is static so as to be inlined.
@@ -396,8 +396,9 @@ const char *get_attribute(int asn, const char *hostname, enum attributetype attr
396
396
  assert(hostname != NULL);
397
397
  memset(&f, 0, sizeof(f)); /* needed when debugging. */
398
398
  if(find_host_convention(&f, asn, hostname)) {
399
+ const char *returnme;
399
400
  DM("found match for %s\n", hostname);
400
- const char *returnme = kdlookup((attr == attr_loc) ? f.match->loc : f.match->type,
401
+ returnme = kdlookup((attr == attr_loc) ? f.match->loc : f.match->type,
401
402
  f.posix_regs,
402
403
  hostname);
403
404
  if(returnme == NULL) {
@@ -1,18 +1,67 @@
1
1
  require 'undns/undns'
2
2
 
3
- # not clear how to accomplish this inside the .so, so at least this component
4
- # has to be in ruby.
5
3
  module Undns
4
+ # sets the ruleset path based on the current directory location.
6
5
  Undns.ruleset_path = File.expand_path('..', __FILE__) + ':' + Undns.ruleset_path
6
+
7
+ # not clear how to accomplish this inside the .so, so at least this component
8
+ # has to be in ruby.
9
+
10
+ # The Path namespace separates the file path names
11
+ # associated with data files that are installed with the
12
+ # gem (or generated by update_origins_dat).
13
+ #
14
+ # You may want to use these path names to record features
15
+ # of the files used to interpret network measurement data.
16
+ # For example, tracking the date, size, or md5 of the file
17
+ # used to process data may be handy, since the network
18
+ # changes and these files are likely to need revision.
19
+ #
20
+ # Note that the Undns module includes {#Ruleset_In_Use}
21
+ # and {#Origins_In_Use} to find the actual file used, in
22
+ # case the search path finds such a file in, for example,
23
+ # your current working directory that will override the
24
+ # system installed version.
7
25
  module Path
26
+ # Determines where origins.dat installed with this gem
27
+ # is or should be. Prints a warning to stderr if not
28
+ # found in the expected place.
29
+ #
30
+ # @return [String] the path of the origins.dat file installed with this gem.
8
31
  def Path.origins_dat
9
- File.expand_path('../origins.dat', __FILE__)
32
+ ret = File.expand_path('../undns/origins.dat', __FILE__)
33
+ $stderr.puts "WARNING: origins.dat (#{ret}) is missing; run update_origins_dat" unless test(?e, ret) or $0 =~ /update_origins_dat/
34
+ ret
35
+ end
36
+ # Determines where origins6.dat installed with this gem
37
+ # is or should be. Prints a warning to stderr if not
38
+ # found in the expected place.
39
+ #
40
+ # @return [String] the path of the origins6.dat file installed with this gem.
41
+ def Path.origins6_dat
42
+ ret = File.expand_path('../undns/origins6.dat', __FILE__)
43
+ $stderr.puts "WARNING: origins6.dat (#{ret}) is missing; run update_origins6_dat" unless test(?e, ret) or $0 =~ /update_origins6_dat/
44
+ ret
10
45
  end
46
+ # Determines where Conventions.dat installed with this
47
+ # gem is or should be. Prints a warning to stderr if
48
+ # not found in the expected place.
49
+ #
50
+ # @return [String] the path of the Conventions.dat file installed with this gem.
11
51
  def Path.conventions_dat
12
- File.expand_path('../Conventions.dat', __FILE__)
52
+ ret = File.expand_path('../undns/Conventions.dat', __FILE__)
53
+ $stderr.puts "WARNING: Conventions.dat (#{ret}) is missing" unless test(?e, ret)
54
+ ret
13
55
  end
56
+ # Determines where LocationTable installed with this gem
57
+ # is or should be. Prints a warning to stderr if not
58
+ # found in the expected place.
59
+ #
60
+ # @return [String] the path of the LocationTable file installed with this gem.
14
61
  def Path.location_table
15
- File.expand_path('../undns/LocationTable', __FILE__)
62
+ ret = File.expand_path('../undns/LocationTable', __FILE__)
63
+ $stderr.puts "WARNING: LocationTable (#{ret}) is missing" unless test(?e, ret)
64
+ ret
16
65
  end
17
66
  end
18
67
  end
@@ -0,0 +1,592 @@
1
+ #!/usr/bin/perl
2
+ #
3
+ # This program is free software; you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License version 2 or later
5
+ # as published by the Free Software Foundation.
6
+ #
7
+ # This work is inspired by the route_btoa.pl program by Craig Labovitz
8
+ # <labovit@merit.edu>, which is part of the MRT distribution.
9
+ #
10
+ # Documentation about the zebra/MRT packet format:
11
+ # http://www.iana.org/assignments/mrt/mrt.xml
12
+ # http://tools.ietf.org/html/rfc6396
13
+
14
+ use warnings;
15
+ use strict;
16
+ require 5.008;
17
+
18
+ # only meaningful for message types TABLE_DUMP and TABLE_DUMP_V2
19
+ # 1: verbose dump 2: AS path 3: origin AS
20
+ my $format = 3;
21
+ my $ignore_v6_routes = 0;
22
+
23
+ ##############################################################################
24
+ use constant {
25
+ MSG_BGP => 5, # MRT only
26
+ MSG_BGP4PLUS => 9, # MRT only
27
+ MSG_TABLE_DUMP => 12, # dump bgp routes-mrt
28
+ MSG_TABLE_DUMP_V2 => 13, # RIPE RIS
29
+ MSG_BGP4MP => 16, # dump bgp all
30
+
31
+ BGP4MP_STATE_CHANGE => 0,
32
+ BGP4MP_MESSAGE => 1,
33
+ BGP4MP_ENTRY => 2, # deprecated
34
+ BGP4MP_SNAPSHOT => 3, # deprecated
35
+ BGP4MP_MESSAGE_AS4 => 4,
36
+ BGP4MP_STATE_CHANGE_AS4 => 5,
37
+ BGP4MP_MESSAGE_LOCAL => 6,
38
+ BGP4MP_MESSAGE_AS4_LOCAL => 7,
39
+
40
+ AFI_IP => 1,
41
+ AFI_IP6 => 2,
42
+
43
+ # for TABLE_DUMP_V2
44
+ INDEX_TABLE => 1,
45
+ RIB_IPV4_UNICAST => 2,
46
+ RIB_IPV4_MULTICAST => 3,
47
+ RIB_IPV6_UNICAST => 4,
48
+ RIB_IPV6_MULTICAST => 5,
49
+ RIB_GENERIC => 6,
50
+
51
+ BGP_TYPE_OPEN => 1,
52
+ BGP_TYPE_UPDATE => 2,
53
+ BGP_TYPE_NOTIFICATION => 3,
54
+ BGP_TYPE_KEEPALIVE => 4,
55
+
56
+ BGP_ATTR_FLAG_EXTLEN => 0x10,
57
+
58
+ AS_SET => 1,
59
+
60
+ BGP_ATTR_ORIGIN => 1,
61
+ BGP_ATTR_AS_PATH => 2,
62
+ BGP_ATTR_NEXT_HOP => 3,
63
+ BGP_ATTR_MULTI_EXIT_DISC => 4,
64
+ BGP_ATTR_LOCAL_PREF => 5,
65
+ BGP_ATTR_ATOMIC_AGGREGATE => 6,
66
+ BGP_ATTR_AGGREGATOR => 7,
67
+ BGP_ATTR_COMMUNITIES => 8,
68
+ BGP_ATTR_ORIGINATOR_ID => 9,
69
+ BGP_ATTR_CLUSTER_LIST => 10,
70
+ ## BGP_ATTR_DPA => 11,
71
+ # BGP_ATTR_ADVERTISER => 12,
72
+ ## BGP_ATTR_RCID_PATH => 13,
73
+ BGP_ATTR_MP_REACH_NLRI => 14,
74
+ BGP_ATTR_MP_UNREACH_NLRI => 15,
75
+ BGP_ATTR_EXT_COMMUNITIES => 16,
76
+ };
77
+
78
+ my @BGP_ORIGIN = qw(IGP EGP Incomplete);
79
+
80
+ ##############################################################################
81
+ open(INPUT, '-') or die "Could not open INPUT $!\n";
82
+
83
+ use constant BUF_READ_SIZE => 4096 * 8;
84
+ my $buf = '';
85
+ my $read_done = 0;
86
+
87
+ my @BGP_Peers; # used by the TABLE_DUMP_V2 parser
88
+
89
+ while (1) {
90
+ if ($read_done) {
91
+ last if length $buf == 0;
92
+ } elsif (length $buf < BUF_READ_SIZE) {
93
+ my $tmp = '';
94
+ my $n = sysread(INPUT, $tmp, BUF_READ_SIZE * 2);
95
+ die "sysread: $!" if not defined $n;
96
+ $read_done = 1 if $n == 0;
97
+ $buf .= $tmp;
98
+ }
99
+
100
+ die "short file (empty packet)" if not $buf;
101
+ my $header = substr($buf, 0, 12, '');
102
+ my ($time, $type, $subtype, $packet_length) = unpack('N n n N', $header);
103
+ my $packet = substr($buf, 0, $packet_length, '');
104
+ die "short file (got " . (length $packet) . " of $packet_length bytes)"
105
+ if $packet_length != length $packet;
106
+
107
+ if ($format == 1) {
108
+ my ($sec, $min, $hour, $mday, $mon, $year, @junk) = localtime($time);
109
+ $mon++;
110
+ $year += 1900;
111
+ printf("\nTIME: $year-$mon-$mday %02d:%02d:%02d\n", $hour, $min, $sec);
112
+ }
113
+
114
+ decode_mrt_packet(\$packet, $type, $subtype);
115
+ }
116
+ exit 0;
117
+
118
+ ##############################################################################
119
+ sub decode_mrt_packet {
120
+ my ($pkt, $type, $subtype) = @_;
121
+
122
+ if ($type == MSG_TABLE_DUMP) { ###########################################
123
+ my $af = $subtype;
124
+ my $header_format;
125
+
126
+ if ($af == AFI_IP) {
127
+ $header_format = 'n n a4 C C N a4 n n/a';
128
+ } elsif ($af == AFI_IP6) {
129
+ return if $ignore_v6_routes;
130
+ $header_format = 'n n a16 C C N a16 n n/a';
131
+ } else {
132
+ warn "TYPE: MSG_TABLE_DUMP/AFI_UNKNOWN_$af\n";
133
+ return;
134
+ }
135
+
136
+ my ($viewno, $seq_num, $prefix, $prefixlen, undef, $originated,
137
+ $peerip, $peer_as, $attributes) = unpack($header_format, $$pkt);
138
+ my $attr = parse_attributes($attributes);
139
+
140
+ if ($format == 1) {
141
+ print "TYPE: MSG_TABLE_DUMP/" .
142
+ ($af == AFI_IP ? 'AFI_IP' : 'AFI_IP6') . "\n"
143
+ . "VIEW: $viewno SEQUENCE: $seq_num\n"
144
+ . 'PREFIX: ' . inet_ntop($af, $prefix) . "/$prefixlen\n"
145
+ . "ORIGINATED: " . localtime($originated) . "\n";
146
+ print 'FROM: ' . inet_ntop($af, $peerip) . " AS$peer_as\n"
147
+ if $peer_as;
148
+ print_verbose_attributes($attr);
149
+ } elsif ($format == 2) {
150
+ print inet_ntop($af, $prefix) . "/$prefixlen"
151
+ . print_aspath($attr->[BGP_ATTR_AS_PATH]) . "\n";
152
+ } elsif ($format == 3) {
153
+ print inet_ntop($af, $prefix) . "/$prefixlen "
154
+ . unpack('n', origin_as($attr->[BGP_ATTR_AS_PATH])) . "\n"
155
+ if @{$attr->[BGP_ATTR_AS_PATH]};
156
+ } else { die }
157
+ } elsif ($type == MSG_TABLE_DUMP_V2) { ###################################
158
+ my $af;
159
+
160
+ if ($subtype == INDEX_TABLE) {
161
+ my ($collector_id, $view_name, $peers_count, $rest)
162
+ = unpack('a4 n/a n a*', $$pkt);
163
+ $view_name ||= '';
164
+ print "TYPE: MSG_TABLE_DUMP_V2/INDEX_TABLE\n".
165
+ "ID: " . inet_ntoa($collector_id) .
166
+ "\nVIEW_NAME: \"$view_name\", PEERS: $peers_count\n"
167
+ if $format == 1;
168
+
169
+ # unpack each peer
170
+ my $peer_index = 0;
171
+ while (length $rest > 0) {
172
+ # parse only the first byte (peer type, a bit field) to
173
+ # known the length of the other fields of $rest
174
+ my ($addrv6, $as32) = split(//, unpack('b8', $rest));
175
+ my $as_size = $as32 ? 4 : 2;
176
+ my $mformat = 'x N' . ($addrv6 ? 'a16' : 'a4') . "a$as_size a*";
177
+
178
+ my ($bgp_id, $peer_ip, $peer_as);
179
+ ($bgp_id, $peer_ip, $peer_as, $rest) = unpack($mformat, $rest);
180
+ $peer_as = pretty_as($peer_as);
181
+
182
+ my $afi = $addrv6 ? AFI_IP6 : AFI_IP;
183
+ print " PEER $peer_index: ID: $bgp_id, "
184
+ . inet_ntop($afi, $peer_ip) . ", AS$peer_as\n"
185
+ if $format == 1;
186
+ $BGP_Peers[$peer_index++] = [
187
+ $bgp_id, $afi, $peer_ip, $peer_as, $as_size,
188
+ ];
189
+ }
190
+
191
+ return;
192
+ } elsif ($subtype == RIB_IPV4_UNICAST) {
193
+ $af = AFI_IP;
194
+ } elsif ($subtype == RIB_IPV4_MULTICAST) {
195
+ return if $ignore_v6_routes;
196
+ $af = AFI_IP;
197
+ } elsif ($subtype == RIB_IPV6_UNICAST) {
198
+ return if $ignore_v6_routes;
199
+ $af = AFI_IP6;
200
+ } elsif ($subtype == RIB_IPV6_MULTICAST) {
201
+ return if $ignore_v6_routes;
202
+ $af = AFI_IP6;
203
+ } elsif ($subtype == RIB_GENERIC) {
204
+ my ($seq_num, $afi, $safi, $nlri) = unpack('N n C a*', $$pkt);
205
+ #my (?, $entry_count, $rest) = unpack("? n a*", $nlri);
206
+ print "TYPE: MSG_TABLE_DUMP_V2/RIB_GENERIC\n"
207
+ . "SEQUENCE: $seq_num\n"
208
+ . "AFI: $afi, SAFI: $safi, NLRI(" . length($nlri) ."):\n";
209
+ hexdump($nlri);
210
+ return;
211
+ } else {
212
+ warn "TYPE: MSG_TABLE_DUMP_V2/UNKNOWN_SUBTYPE_$subtype\n";
213
+ return;
214
+ }
215
+
216
+ my ($seq_num, $prefixlen, $nlri) = unpack('N C a*', $$pkt);
217
+ my $bytes = int($prefixlen / 8) + ($prefixlen % 8 ? 1 : 0);
218
+ my ($prefix, $entry_count, $rest) = unpack("a$bytes n a*", $nlri);
219
+ $prefix .= "\0" x (($af == AFI_IP ? 4 : 16) - $bytes); # pad with NULs
220
+
221
+ while (length $rest > 0) {
222
+ my ($peer_index, $originated, $attributes);
223
+ ($peer_index, $originated, $attributes, $rest)
224
+ = unpack('n N n/a a*', $rest);
225
+ my $attr = parse_attributes($attributes,
226
+ $BGP_Peers[$peer_index]->[4]);
227
+
228
+ if ($format == 1) {
229
+ print "TYPE: MSG_TABLE_DUMP_V2/ENTRY_" .
230
+ ($af == AFI_IP ? 'AFI_IP' : 'AFI_IP6') . "\n"
231
+ . "SEQUENCE: $seq_num\n"
232
+ . 'PREFIX: ' . inet_ntop($af, $prefix) . "/$prefixlen\n"
233
+ . "ORIGINATED: " . localtime($originated) . "\n";
234
+ my $peer_as = $BGP_Peers[$peer_index]->[3];
235
+ print 'FROM: ' . inet_ntop($af, $BGP_Peers[$peer_index]->[2])
236
+ . " AS$peer_as\n";
237
+ print_verbose_attributes($attr);
238
+ print "-\n";
239
+ } elsif ($format == 2) {
240
+ print inet_ntop($af, $prefix) . "/$prefixlen"
241
+ . print_aspath($attr->[BGP_ATTR_AS_PATH]) . "\n";
242
+ } elsif ($format == 3) {
243
+ print inet_ntop($af, $prefix) . "/$prefixlen "
244
+ . unpack($BGP_Peers[$peer_index]->[4] == 2 ? 'n' : 'N',
245
+ origin_as($attr->[BGP_ATTR_AS_PATH])) . "\n"
246
+ if @{$attr->[BGP_ATTR_AS_PATH]};
247
+ } else { die }
248
+ }
249
+
250
+ } elsif ($type == MSG_BGP4MP) { ##########################################
251
+ if ($subtype == BGP4MP_STATE_CHANGE) { #------------------------------
252
+ my ($srcas, $dstas, $ifidx, $af, $rest) = unpack('nnnn a*', $$pkt);
253
+ my $unpack_format;
254
+
255
+ if ($af == AFI_IP) {
256
+ $unpack_format = 'a4 a4 n n';
257
+ } elsif ($af == AFI_IP6) {
258
+ $unpack_format = 'a16 a16 n n';
259
+ } else {
260
+ warn "TYPE: BGP4MP/BGP4MP_STATE_CHANGE AFI_UNKNOWN_$af\n";
261
+ return;
262
+ }
263
+
264
+ my ($srcip, $dstip, $old_state, $new_state)
265
+ = unpack($unpack_format, $rest);
266
+ print "TYPE: BGP4MP/BGP4MP_STATE_CHANGE " .
267
+ ($af == AFI_IP ? 'AFI_IP' : 'AFI_IP6' ) . "\n";
268
+ print "FROM: " . inet_ntop($af, $srcip) . "\n" if notnull($srcip);
269
+ print "TO: " . inet_ntop($af, $dstip) . "\n" if notnull($dstip);
270
+ print "OLD STATE: $old_state NEW STATE: $new_state\n";
271
+ # state numbers: see RFC 4271, Appendix 1
272
+ # 1:IDLE 2:CONNECT 3:ACTIVE 4:OPENSENT 5:OPENCONFIRM 6:ESTABLISHED
273
+ } elsif ($subtype == BGP4MP_MESSAGE or #------------------------------
274
+ $subtype == BGP4MP_MESSAGE_AS4) {
275
+ my ($subtype_str, $asn_unpack_format, $asn_length);
276
+ if ($subtype == BGP4MP_MESSAGE) {
277
+ $subtype_str = 'BGP4MP_MESSAGE';
278
+ $asn_unpack_format = 'nnnn a*'; # 16 bit ASNs
279
+ } else {
280
+ $subtype_str = 'BGP4MP_MESSAGE_AS4';
281
+ $asn_unpack_format = 'NNnn a*'; # 32 bit ASNs
282
+ $asn_length = 4;
283
+ }
284
+ my ($srcas, $dstas, $ifidx, $af, $rest) =
285
+ unpack($asn_unpack_format, $$pkt);
286
+
287
+ my $unpack_format;
288
+ if ($af == AFI_IP) {
289
+ $unpack_format = 'a4 a4 a*';
290
+ } elsif ($af == AFI_IP6) {
291
+ $unpack_format = 'a16 a16 a*';
292
+ } else {
293
+ warn "TYPE: BGP4MP/$subtype_str AFI_UNKNOWN_$af\n";
294
+ return;
295
+ }
296
+
297
+ my ($srcip, $dstip, $bgppkt) = unpack($unpack_format, $rest);
298
+ print "TYPE: BGP4MP/$subtype_str " .
299
+ ($af == AFI_IP ? 'AFI_IP' : 'AFI_IP6' ) . "\n";
300
+ print "FROM: " . inet_ntop($af, $srcip) . "\n" if notnull($srcip);
301
+ print "TO: " . inet_ntop($af, $dstip) . "\n" if notnull($dstip);
302
+ parse_bgp_packet($bgppkt, $asn_length);
303
+ } elsif ($subtype == BGP4MP_ENTRY) { #--------------------------------
304
+ warn "NOT TESTED"; # XXX
305
+ my ($view, $status, $time_change, $afi, $safi, $next_hop, $prefix,
306
+ $attributes) = unpack('n n N n C C/a C/a n/a', $$pkt);
307
+ my $attr = parse_attributes($attributes);
308
+
309
+ print "TYPE: BGP4MP/BGP4MP_ENTRY "
310
+ . ($afi == AFI_IP ? 'AFI_IP' : 'AFI_IP6' ) . "\n";
311
+ print_verbose_attributes($attr);
312
+ } else { #------------------------------------------------------------
313
+ print "TYPE: BGP4MP/BGP4MP_UNKNOWN-$subtype\n";
314
+ hexdump($$pkt);
315
+ }
316
+ } elsif ($type == MSG_BGP4PLUS ###########################################
317
+ or $type == MSG_BGP) { #############################################
318
+ my $unpack_format = ($type == MSG_BGP4PLUS)
319
+ ? 'n a16 n a16 n/a n/a a*' : 'n a4 n a4 n/a n/a a*';
320
+ my $afi = ($type == MSG_BGP4PLUS) ? AFI_IP6 : AFI_IP;
321
+ my ($srcas, $srcip, $dstas, $dstip, $unf_routes, $attributes, $nlri)
322
+ = unpack($unpack_format, $$pkt);
323
+ my $attr = parse_attributes($attributes);
324
+
325
+ print "TYPE: TYPE: BGP4MP/BGP4"
326
+ . ($afi == AFI_IP ? '' : 'PLUS') . "/UPDATE\n";
327
+ print "FROM: " . inet_ntop($afi, $srcip) . " AS$srcas\n" if $srcas;
328
+ print "TO: " . inet_ntop($afi, $dstip) . " AS$dstas\n" if $dstas;
329
+ print_verbose_attributes($attr);
330
+ print "WITHDRAWN: $_\n" foreach (parse_nlri_prefixes($unf_routes));
331
+ print "ANNOUNCE: $_\n" foreach (parse_nlri_prefixes($nlri));
332
+ } else { ################################################################
333
+ warn "UNKNOWN TYPE: $type SUBTYPE: $subtype\n";
334
+ }
335
+ }
336
+
337
+ sub parse_attributes {
338
+ my ($attributes, $as_size) = @_;
339
+ my @attr;
340
+
341
+ while (length $attributes > 0) {
342
+ my ($flags, $type);
343
+ ($flags, $type, $attributes) = unpack('C C a*', $attributes);
344
+
345
+ my $attrib; # content of the next attribute
346
+ if ($flags & BGP_ATTR_FLAG_EXTLEN) {
347
+ ($attrib, $attributes) = unpack('n/a a*', $attributes);
348
+ } else {
349
+ ($attrib, $attributes) = unpack('C/a a*', $attributes);
350
+ }
351
+
352
+ if ($type == BGP_ATTR_ORIGIN) {
353
+ $attr[BGP_ATTR_ORIGIN] = unpack('C', $attrib);
354
+ } elsif ($type == BGP_ATTR_AS_PATH) {
355
+ $attr[BGP_ATTR_AS_PATH] = [ ];
356
+ $as_size ||= 2;
357
+ while (length $attrib > 0) {
358
+ my ($seg_type, $seg_length);
359
+ ($seg_type, $seg_length, $attrib) = unpack('C C a*', $attrib);
360
+ my $seg_value = substr($attrib, 0, $seg_length * $as_size, '');
361
+ push(@{$attr[BGP_ATTR_AS_PATH]},
362
+ [ $seg_type, [ unpack("(a$as_size)*", $seg_value) ] ]);
363
+ }
364
+ } elsif ($type == BGP_ATTR_NEXT_HOP) {
365
+ $attr[BGP_ATTR_NEXT_HOP] = $attrib; # IPv4
366
+ } elsif ($type == BGP_ATTR_MULTI_EXIT_DISC) {
367
+ $attr[BGP_ATTR_MULTI_EXIT_DISC] = $attrib; # 'N'
368
+ } elsif ($type == BGP_ATTR_LOCAL_PREF) {
369
+ $attr[BGP_ATTR_LOCAL_PREF] = $attrib; # 'N'
370
+ } elsif ($type == BGP_ATTR_ATOMIC_AGGREGATE) {
371
+ $attr[BGP_ATTR_ATOMIC_AGGREGATE] = 1;
372
+ } elsif ($type == BGP_ATTR_AGGREGATOR) {
373
+ $attr[BGP_ATTR_AGGREGATOR] = [ unpack('a2 a4', $attrib) ];# N, IPv4
374
+ } elsif ($type == BGP_ATTR_COMMUNITIES) {
375
+ $attr[BGP_ATTR_COMMUNITIES] = [ ];
376
+ while (length $attrib > 0) {
377
+ my $community = substr($attrib, 0, 4, '');
378
+ push(@{$attr[BGP_ATTR_COMMUNITIES]}, $community);
379
+ }
380
+ } elsif ($type == BGP_ATTR_MP_REACH_NLRI) {
381
+ # FIXME v2 uses a different format
382
+ my ($afi, $safi, $next_hop, $rest) = unpack('n C C/a a*', $attrib);
383
+
384
+ # XXX how should I deal with all these cases?
385
+ my $next_hop_len = length $next_hop;
386
+ my ($next_hop_global_in, $next_hop_global, $next_hop_local);
387
+ if ($next_hop_len == 4) {
388
+ $next_hop_global_in = $next_hop;
389
+ } elsif ($next_hop_len == 12) {
390
+ my ($rd_high, $rd_low);
391
+ ($rd_high, $rd_low, $next_hop_global_in)
392
+ = unpack('N N a4', $next_hop);
393
+ } elsif ($next_hop_len == 16) {
394
+ $next_hop_global = $next_hop;
395
+ } elsif ($next_hop_len == 32) {
396
+ ($next_hop_global, $next_hop_local)
397
+ = unpack('a16 a16', $next_hop);
398
+ } else { die }
399
+
400
+ my $num_snpa;
401
+ ($num_snpa, $rest) = unpack('C a*', $rest);
402
+ while ($num_snpa-- > 0) {
403
+ my $snpa;
404
+ ($snpa, $rest) = unpack('C/a a*', $rest);
405
+ print "|SNPA: "; hexdump($snpa); # XXX
406
+ }
407
+
408
+ if ($format == 1) { # XXX should not print here...
409
+ print "|AFI: $afi ($safi)\n";
410
+ print "|NEXT_HOP: " . inet6_ntoa($next_hop_global)
411
+ . " (LENGTH: $next_hop_len)\n";
412
+ print "|ANNOUNCE: $_\n" foreach (parse_nlri_prefixes($rest, $afi));
413
+ }
414
+ } elsif ($type == BGP_ATTR_MP_UNREACH_NLRI) {
415
+ my ($afi, $safi, $nlri) = unpack('n C a*', $attrib);
416
+
417
+ if ($format == 1) { # XXX
418
+ print "|WITHDRAWN: $_\n" foreach(parse_nlri_prefixes($nlri, $afi));
419
+ }
420
+ } elsif ($type == BGP_ATTR_ORIGINATOR_ID) {
421
+ $attr[BGP_ATTR_ORIGINATOR_ID] = $attrib; # IPv4
422
+ } elsif ($type == BGP_ATTR_CLUSTER_LIST) {
423
+ $attr[BGP_ATTR_CLUSTER_LIST] = $attrib; # IPv4
424
+ } else {
425
+ warn "Unknown BGP attribute $type (flags: $flags)\n";
426
+ }
427
+ }
428
+
429
+ return \@attr;
430
+ }
431
+
432
+ sub parse_bgp_packet {
433
+ my ($bgppkt, $asn_length) = @_;
434
+
435
+ my ($marker, $length, $type, $data) = unpack('a16 n C a*', $bgppkt);
436
+
437
+ if ($type == BGP_TYPE_OPEN) {
438
+ my ($version, $as, $hold_time, $bgp_id, $params)
439
+ = unpack('C n n a4 C/a', $data);
440
+ # die if $version != 4;
441
+ print "BGP PACKET TYPE: OPEN\n"
442
+ . "AS: $as ID: " . inet_ntoa($bgp_id) . "\n"
443
+ . "HOLD TIME: ${hold_time}s\n";
444
+
445
+ # parse BGP OPEN parameters
446
+ while (length $params > 0) {
447
+ my ($par_type, $par_value);
448
+ ($par_type, $par_value, $params) = unpack('C C/a a*', $params);
449
+ if ($par_type == 1) {
450
+ my ($code, $data) = unpack('C a*', $par_value);
451
+ print "PARAMETER: AUTH code $code\n";
452
+ } elsif ($par_type == 2) {
453
+ my $caps = $par_value;
454
+ while (length $caps > 0) {
455
+ my ($cap_code, $cap_value);
456
+ ($cap_code, $cap_value, $caps) = unpack('C C/a a*', $caps);
457
+ # see http://www.iana.org/assignments/capability-codes
458
+ print "PARAMETER: CAPABILITY $cap_code\n";
459
+ }
460
+ } else {
461
+ print "PARAMETER: TYPE $par_type (UNKNOWN) "
462
+ . "LEN: " . length($params) . "\n";
463
+ hexdump($params);
464
+ }
465
+ }
466
+ } elsif ($type == BGP_TYPE_UPDATE) {
467
+ print "BGP PACKET TYPE: UPDATE\n";
468
+ my ($unf_routes, $attributes, $nlri) = unpack('n/a n/a a*', $data);
469
+ my $attr = parse_attributes($attributes, $asn_length);
470
+ print_verbose_attributes($attr);
471
+ print "WITHDRAWN: $_\n" foreach (parse_nlri_prefixes($unf_routes));
472
+ print "ANNOUNCED: $_\n" foreach (parse_nlri_prefixes($nlri));
473
+ } elsif ($type == BGP_TYPE_NOTIFICATION) {
474
+ print "BGP PACKET TYPE: NOTIFICATION\n";
475
+ my ($error, $suberror, $data) = unpack('C C a*', $data);
476
+ print "BGP PACKET TYPE: ERROR $error (subcode $suberror)\n";
477
+ } elsif ($type == BGP_TYPE_KEEPALIVE) {
478
+ print "BGP PACKET TYPE: KEEPALIVE\n";
479
+ } else { die }
480
+ }
481
+
482
+ sub parse_nlri_prefixes {
483
+ my ($nlri, $afi) = @_;
484
+ $afi ||= AFI_IP;
485
+
486
+ my @prefixes;
487
+ while ($nlri and length $nlri > 0) {
488
+ my ($len, $prefix);
489
+ ($len, $nlri) = unpack('C a*', $nlri);
490
+ my $bytes = int($len / 8) + ($len % 8 ? 1 : 0);
491
+ ($prefix, $nlri) = unpack("a$bytes a*", $nlri);
492
+ $prefix .= "\0" x (($afi == AFI_IP ? 4 : 16) - $bytes); # pad with NULs
493
+ push(@prefixes, inet_ntop($afi, $prefix) . "/$len");
494
+ }
495
+ return @prefixes;
496
+ }
497
+
498
+ sub print_verbose_attributes {
499
+ my ($attr) = @_;
500
+
501
+ print 'ORIGIN: ' . $BGP_ORIGIN[$attr->[BGP_ATTR_ORIGIN]] . "\n"
502
+ if defined $attr->[BGP_ATTR_ORIGIN];
503
+ print 'AS_PATH:' . print_aspath($attr->[BGP_ATTR_AS_PATH]) . "\n"
504
+ if $attr->[BGP_ATTR_AS_PATH];
505
+ print 'NEXT_HOP: ' . inet_ntoa($attr->[BGP_ATTR_NEXT_HOP])."\n"
506
+ if notnull($attr->[BGP_ATTR_NEXT_HOP]);
507
+ print 'MULTI_EXIT_DISC: ' . unpack('N', $attr->[BGP_ATTR_MULTI_EXIT_DISC])
508
+ . "\n" if $attr->[BGP_ATTR_MULTI_EXIT_DISC];
509
+ print "ATOMIC_AGGREGATE\n" if $attr->[BGP_ATTR_ATOMIC_AGGREGATE];
510
+ print 'AGGREGATOR: ' . unpack('n', ${$attr->[BGP_ATTR_AGGREGATOR]}[0])
511
+ . ' ' . inet_ntoa(${$attr->[BGP_ATTR_AGGREGATOR]}[1]) . "\n"
512
+ if $attr->[BGP_ATTR_AGGREGATOR];
513
+ print 'ORIGINATOR_ID: ' . inet_ntoa($attr->[BGP_ATTR_ORIGINATOR_ID])."\n"
514
+ if notnull($attr->[BGP_ATTR_ORIGINATOR_ID]);
515
+ print 'CLUSTER_LIST: ' . inet_ntoa($attr->[BGP_ATTR_CLUSTER_LIST])."\n"
516
+ if notnull($attr->[BGP_ATTR_CLUSTER_LIST]);
517
+ print "COMMUNITIES: "
518
+ . print_communities(@{$attr->[BGP_ATTR_COMMUNITIES]}) . "\n"
519
+ if $attr->[BGP_ATTR_COMMUNITIES];
520
+ }
521
+
522
+ sub print_communities {
523
+ my @communities;
524
+ foreach my $community (@_) {
525
+ my ($hi, $low) = unpack('n n', $community);
526
+ push(@communities, "${hi}:${low}");
527
+ }
528
+
529
+ return join(' ', @communities);
530
+ }
531
+
532
+ sub pretty_as {
533
+ my ($as_hi, $as_lo) = unpack('nn', $_[0]);
534
+ return defined $as_lo ? ($as_hi ? unpack('N', $_[0]) : $as_lo) : $as_hi;
535
+ }
536
+
537
+ sub print_aspath {
538
+ my ($aspath) = @_;
539
+
540
+ my $s = '';
541
+ foreach (@$aspath) {
542
+ # 1 AS_SET 2 AS_SEQUENCE 3 AS_CONFED_SEQUENCE 4 AS_CONFED_SET
543
+ my ($type, $segment) = @$_;
544
+ my $s1 = $type == AS_SET ? '{' : '';
545
+ my $s2 = $type == AS_SET ? '}' : '';
546
+ my $s3 = $type == AS_SET ? ',' : ' ';
547
+ $s .= " $s1" . join($s3, map { pretty_as($_) } @$segment) . $s2;
548
+ }
549
+ return $s;
550
+ }
551
+
552
+ sub origin_as {
553
+ my ($aspath) = @_;
554
+
555
+ # BEWARE: in presence of an AS_SET the first AS of the set is returned
556
+ return pop @{ @{pop @{$aspath}}[1] };
557
+ }
558
+
559
+ sub notnull {
560
+ return 1 if $_[0] and $_[0] ne "\0\0\0\0";
561
+ return 0;
562
+ }
563
+
564
+ sub inet_ntop {
565
+ my ($af, $addr) = @_;
566
+
567
+ if ($af == AFI_IP) {
568
+ return inet_ntoa($addr);
569
+ } elsif ($af == AFI_IP6) {
570
+ return inet6_ntoa($addr);
571
+ } else { die }
572
+ }
573
+
574
+ sub inet_ntoa {
575
+ join('.', unpack('C4', $_[0]));
576
+ }
577
+
578
+ sub inet6_ntoa {
579
+ local $_ = sprintf("%0*v2x", ':', $_[0]);
580
+ s/(..):(..)/${1}${2}/g;
581
+ s/(:0000)+$/::/;
582
+ return '::' if $_ eq '0000::';
583
+ return $_;
584
+ }
585
+
586
+ sub hexdump {
587
+ local $_ = sprintf("%0*v2X", ' ', $_[0]);
588
+ s/((?:.. ){16})/${1}\n /g;
589
+ s/((?:.. ){8})/${1} /g;
590
+ print " $_\n";
591
+ }
592
+