bgp4r 0.0.3

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.
Files changed (55) hide show
  1. data/COPYING +674 -0
  2. data/LICENSE.txt +53 -0
  3. data/README.rdoc +259 -0
  4. data/bgp/aggregator.rb +104 -0
  5. data/bgp/as_path.rb +223 -0
  6. data/bgp/atomic_aggregate.rb +42 -0
  7. data/bgp/attribute.rb +181 -0
  8. data/bgp/attributes.rb +34 -0
  9. data/bgp/cluster_list.rb +117 -0
  10. data/bgp/common.rb +204 -0
  11. data/bgp/communities.rb +139 -0
  12. data/bgp/extended_communities.rb +107 -0
  13. data/bgp/extended_community.rb +254 -0
  14. data/bgp/iana.rb +269 -0
  15. data/bgp/io.rb +116 -0
  16. data/bgp/label.rb +94 -0
  17. data/bgp/local_pref.rb +75 -0
  18. data/bgp/message.rb +894 -0
  19. data/bgp/mp_reach.rb +208 -0
  20. data/bgp/multi_exit_disc.rb +76 -0
  21. data/bgp/neighbor.rb +291 -0
  22. data/bgp/next_hop.rb +63 -0
  23. data/bgp/nlri.rb +303 -0
  24. data/bgp/orf.rb +88 -0
  25. data/bgp/origin.rb +88 -0
  26. data/bgp/originator_id.rb +73 -0
  27. data/bgp/path_attribute.rb +210 -0
  28. data/bgp/prefix_orf.rb +263 -0
  29. data/bgp/rd.rb +107 -0
  30. data/examples/bgp +65 -0
  31. data/examples/routegen +85 -0
  32. data/examples/routegen.yml +50 -0
  33. data/test/aggregator_test.rb +66 -0
  34. data/test/as_path_test.rb +149 -0
  35. data/test/atomic_aggregate_test.rb +35 -0
  36. data/test/attribute_test.rb +57 -0
  37. data/test/cluster_list_test.rb +39 -0
  38. data/test/common_test.rb +68 -0
  39. data/test/communities_test.rb +75 -0
  40. data/test/extended_communities_test.rb +111 -0
  41. data/test/extended_community_test.rb +93 -0
  42. data/test/label_test.rb +50 -0
  43. data/test/local_pref_test.rb +43 -0
  44. data/test/message_test.rb +294 -0
  45. data/test/mp_reach_test.rb +143 -0
  46. data/test/multi_exit_disc_test.rb +46 -0
  47. data/test/neighbor_test.rb +50 -0
  48. data/test/next_hop_test.rb +37 -0
  49. data/test/nlri_test.rb +189 -0
  50. data/test/origin_test.rb +57 -0
  51. data/test/originator_id_test.rb +38 -0
  52. data/test/path_attribute_test.rb +127 -0
  53. data/test/prefix_orf_test.rb +97 -0
  54. data/test/rd_test.rb +44 -0
  55. metadata +133 -0
data/bgp/message.rb ADDED
@@ -0,0 +1,894 @@
1
+ #--
2
+ # Copyright 2008, 2009 Jean-Michel Esnault.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #
6
+ #
7
+ # This file is part of BGP4R.
8
+ #
9
+ # BGP4R is free software: you can redistribute it and/or modify
10
+ # it under the terms of the GNU General Public License as published by
11
+ # the Free Software Foundation, either version 3 of the License, or
12
+ # (at your option) any later version.
13
+ #
14
+ # BGP4R is distributed in the hope that it will be useful,
15
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ # GNU General Public License for more details.
18
+ #
19
+ # You should have received a copy of the GNU General Public License
20
+ # along with BGP4R. If not, see <http://www.gnu.org/licenses/>.
21
+ #++
22
+
23
+ require 'bgp/path_attribute'
24
+ require 'bgp/nlri'
25
+ require 'timeout'
26
+
27
+ class UnknownBGPCapability < RuntimeError
28
+ end
29
+
30
+ module BGP
31
+
32
+ module OPT_PARM
33
+
34
+ CAPABILITY = 2
35
+
36
+ #TODO module CAP
37
+ #TODO module CAP::ORF
38
+
39
+ CAP_MBGP = 1
40
+ CAP_ROUTE_REFRESH = 2
41
+ CAP_ORF = 3
42
+ CAP_AS4 = 65
43
+ CAP_ROUTE_REFRESH_CISCO = 128
44
+ CAP_ORF_CISCO = 130
45
+
46
+ ORF_NLRI = 1
47
+ ORF_COMMUNITIES = 2
48
+ ORF_EXTENDED_COMMUNITIES = 3
49
+ ORF_PREFIX_LIST = 129
50
+
51
+ ##########################################################
52
+ # BGP OPEN OPTION PARAMETERS #
53
+ ##########################################################
54
+
55
+ class Optional_parameter
56
+
57
+ def initialize(parm_type)
58
+ @parm_type=parm_type
59
+ end
60
+
61
+ def encode(value)
62
+ [@parm_type, value.size, value].pack('CCa*')
63
+ end
64
+
65
+ def parse(s)
66
+ @parm_type, len = s.slice!(0,2).unpack('CC')
67
+ s.slice!(0,len).is_packed
68
+ end
69
+
70
+ def self.factory(s)
71
+ parm_type, len = s.unpack('CC')
72
+ opt_parm = s.slice!(0,len+2).is_packed
73
+ case parm_type
74
+ when CAPABILITY
75
+ Capability.factory(opt_parm)
76
+ else
77
+ raise RuntimeError, "Optional parameter type '#{parm_type}' not implemented"
78
+ end
79
+ end
80
+
81
+ end
82
+ end
83
+
84
+
85
+ class Capability < OPT_PARM::Optional_parameter
86
+ include OPT_PARM
87
+ def initialize(code)
88
+ super(OPT_PARM::CAPABILITY)
89
+ @code=code
90
+ end
91
+ def encode(value='')
92
+ super([@code,value.size, value].pack('CCa*'))
93
+ end
94
+ def parse(_s)
95
+ s = super(_s)
96
+ @code, len = s.slice!(0,2).unpack('CC')
97
+ s.slice!(0,len).is_packed
98
+ end
99
+ def to_s
100
+ "Option Capabilities Advertisement (#{@parm_type}): [#{to_shex}]"
101
+ end
102
+ def self.factory(s)
103
+ code = s.slice(2,1).unpack('C')[0]
104
+ case code
105
+ when CAP_AS4
106
+ As4_cap.new(s)
107
+ when CAP_MBGP
108
+ Mbgp_cap.new(s)
109
+ when CAP_ROUTE_REFRESH, CAP_ROUTE_REFRESH_CISCO
110
+ Route_refresh_cap.new(code)
111
+ when CAP_ORF,CAP_ORF_CISCO
112
+ Orf_cap.new(s)
113
+ else
114
+ raise UnknownBGPCapability, "Capability (#{code}), length: #{s.size} not implemented: [#{s.unpack('H*')[0]}]"
115
+ end
116
+ end
117
+ def to_hash(h={})
118
+ if h.empty?
119
+ {:code=> @code}
120
+ else
121
+ {:code=> @code, :capability=> h}
122
+ end
123
+ end
124
+ end
125
+
126
+ class Mbgp_cap < Capability
127
+
128
+ def self.ipv4_unicast
129
+ Mbgp_cap.new(1,1)
130
+ end
131
+
132
+ def self.ipv4_multicast
133
+ Mbgp_cap.new(1,2)
134
+ end
135
+
136
+ def self.ipv6_unicast
137
+ Mbgp_cap.new(2,2)
138
+ end
139
+
140
+ def self.ipv6_multicast
141
+ Mbgp_cap.new(2,2)
142
+ end
143
+
144
+ def initialize(afi,safi=nil)
145
+ if safi.nil? and afi.is_a?(String) and afi.is_packed?
146
+ parse(afi)
147
+ else
148
+ super(OPT_PARM::CAP_MBGP)
149
+ self.safi, self.afi = safi, afi
150
+ end
151
+ end
152
+
153
+ def afi=(val)
154
+ @afi = if val.is_a?(Fixnum)
155
+ val
156
+ elsif val == :ipv4
157
+ 1
158
+ elsif val == :ipv6
159
+ 2
160
+ end
161
+ end
162
+
163
+ def safi=(val)
164
+ @safi = if val.is_a?(Fixnum)
165
+ val
166
+ elsif val == :unicast
167
+ 1
168
+ elsif val == :multicast
169
+ 2
170
+ end
171
+ end
172
+
173
+ def encode
174
+ super([@afi,0,@safi].pack('nCC'))
175
+ end
176
+
177
+ def parse(s)
178
+ @afi, ignore, @safi = super(s).unpack('nCC')
179
+ end
180
+
181
+ def to_s
182
+ super + "\n Multiprotocol Extensions (#{CAP_MBGP}), length: 4" +
183
+ "\n AFI #{IANA.afi(@afi)} (#{@afi}), SAFI #{IANA.safi(@safi)} (#{@safi})"
184
+ end
185
+
186
+ def to_hash
187
+ super({:afi => @afi, :safi => @safi, })
188
+ end
189
+ end
190
+
191
+ class As4_cap < Capability
192
+ def initialize(s)
193
+ if s.is_a?(String) and s.is_packed?
194
+ parse(s)
195
+ else
196
+ super(OPT_PARM::CAP_AS4)
197
+ @as=s
198
+ end
199
+ end
200
+
201
+ def encode
202
+ super([@as].pack('N'))
203
+ end
204
+
205
+ def parse(s)
206
+ @as = super(s).unpack('N')[0]
207
+ end
208
+
209
+ def to_s
210
+ "Capability(#{CAP_AS4}): 4-octet AS number: " + @as.to_s
211
+ end
212
+
213
+ def to_hash
214
+ super({:as => @as})
215
+ end
216
+ end
217
+
218
+ class Route_refresh_cap < Capability
219
+ def initialize(code=OPT_PARM::CAP_ROUTE_REFRESH)
220
+ super(code)
221
+ end
222
+
223
+ def encode
224
+ super()
225
+ end
226
+
227
+ def to_s
228
+ super + "\n Route Refresh #{@code==128 ? "(Cisco) " : ""}(#{@code}), length: 2"
229
+ end
230
+
231
+ def to_hash
232
+ super()
233
+ end
234
+ end
235
+
236
+ class Orf_cap < Capability
237
+
238
+ class Entry
239
+
240
+ def initialize(*args)
241
+ if args.size==1 and args[0].is_a?(String) and args[0].is_packed?
242
+ parse(args[0])
243
+ elsif args[0].is_a?(Hash)
244
+ else
245
+ @afi, @safi, *@types = *args
246
+ end
247
+ end
248
+
249
+ def encode
250
+ [@afi, 0, @safi, @types.size,@types.collect { |e| e.pack('CC')}.join].pack('nCCCa*')
251
+ end
252
+
253
+ def parse(s)
254
+ @afi, __, @safi, n = s.slice!(0,5).unpack('nCCC')
255
+ @types=[]
256
+ types = s.slice!(0, 2*n)
257
+ while types.size>0
258
+ @types<< types. slice!(0,2).unpack('CC')
259
+ end
260
+ end
261
+
262
+ def to_s
263
+ "AFI #{IANA.afi(@afi)} (#{@afi}), SAFI #{IANA.safi(@safi)} (#{@safi}): #{@types.inspect}"
264
+ end
265
+ end
266
+
267
+ def initialize(*args)
268
+ @entries=[]
269
+ if args.size==1 and args[0].is_a?(String) and args[0].is_packed?
270
+ parse(args[0])
271
+ else
272
+ super(OPT_PARM::CAP_ORF)
273
+ end
274
+ end
275
+
276
+ def add(entry)
277
+ @entries << entry
278
+ end
279
+
280
+ def encode
281
+ super(@entries.collect { |e| e.encode }.join)
282
+ end
283
+
284
+ def parse(s)
285
+ entries = super(s)
286
+ while entries.size>0
287
+ @entries << Entry.new(entries)
288
+ end
289
+ end
290
+
291
+ def to_s
292
+ super + "\n Outbound Route Filtering (#{@code}), length: #{encode.size}" +
293
+ (['']+@entries.collect { |e| e.to_s }).join("\n ")
294
+ end
295
+
296
+ end
297
+
298
+ # http://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#bgp-parameters-1
299
+ module MESSAGE
300
+
301
+ OPEN = 1
302
+ UPDATE = 2
303
+ NOTIFICATION = 3
304
+ KEEPALIVE = 4
305
+ ROUTE_REFRESH = 5
306
+
307
+ def encode(message='')
308
+ len = message.size+19
309
+ [[255]*16,len,@msg_type,message].flatten.pack('C16nCa*')
310
+ end
311
+
312
+ def parse(s)
313
+ len, @msg_type, message = s[16..-1].unpack('nCa*')
314
+ message.is_packed
315
+ end
316
+
317
+ end
318
+
319
+ class Message
320
+ include MESSAGE
321
+ def self.factory(_s, as4byte=false)
322
+ s = [_s].pack('a*')
323
+ s.slice(18,1).unpack('C')[0]
324
+ case s.slice(18,1).unpack('C')[0]
325
+ when OPEN
326
+ Open.new(s)
327
+ when UPDATE
328
+ Update.new(s, as4byte)
329
+ when KEEPALIVE
330
+ Keepalive.new
331
+ when NOTIFICATION
332
+ Notification.new(s)
333
+ when ROUTE_REFRESH
334
+ if s.size > 23
335
+ Orf_route_refresh.new(s)
336
+ else
337
+ Route_refresh.new(s)
338
+ end
339
+ else
340
+ puts "don't know what kind of bgp messgage this is #{s.slice(18,1).unpack('C')}"
341
+ end
342
+ end
343
+ def self.keepalive
344
+ Keepalive.new.encode
345
+ end
346
+ def self.route_refresh(afi,safi)
347
+ Route_refresh.new(afi,safi).encode
348
+ end
349
+ end
350
+
351
+ # KEEPALIVE Message Format
352
+ #
353
+ # BGP does not use any TCP-based, keep-alive mechanism to determine if
354
+ # peers are reachable. Instead, KEEPALIVE messages are exchanged
355
+ # between peers often enough not to cause the Hold Timer to expire. A
356
+ # reasonable maximum time between KEEPALIVE messages would be one third
357
+ # of the Hold Time interval. KEEPALIVE messages MUST NOT be sent more
358
+ # frequently than one per second. An implementation MAY adjust the
359
+ # rate at which it sends KEEPALIVE messages as a function of the Hold
360
+ # Time interval.
361
+ #
362
+ # If the negotiated Hold Time interval is zero, then periodic KEEPALIVE
363
+ # messages MUST NOT be sent.
364
+ #
365
+ # A KEEPALIVE message consists of only the message header and has a
366
+ # length of 19 octets.
367
+ #
368
+
369
+ class Keepalive < Message
370
+ def initialize
371
+ @msg_type=KEEPALIVE
372
+ end
373
+
374
+ def to_s
375
+ "Keepalive Message (#{MESSAGE::KEEPALIVE}), length: 19" + ", [#{self.to_shex[32..-1]}]"
376
+ end
377
+ end
378
+
379
+ # UPDATE Message Format
380
+ #
381
+ # UPDATE messages are used to transfer routing information between BGP
382
+ # peers. The information in the UPDATE message can be used to
383
+ # construct a graph that describes the relationships of the various
384
+ # Autonomous Systems. By applying rules to be discussed, routing
385
+ #
386
+ #
387
+ #
388
+ # Rekhter, et al. Standards Track [Page 14]
389
+ #
390
+ # RFC 4271 BGP-4 January 2006
391
+ #
392
+ #
393
+ # information loops and some other anomalies may be detected and
394
+ # removed from inter-AS routing.
395
+ #
396
+ # An UPDATE message is used to advertise feasible routes that share
397
+ # common path Copyright 2008, 2009 to a peer, or to withdraw multiple unfeasible
398
+ # routes from service (see 3.1). An UPDATE message MAY simultaneously
399
+ # advertise a feasible route and withdraw multiple unfeasible routes
400
+ # from service. The UPDATE message always includes the fixed-size BGP
401
+ # header, and also includes the other fields, as shown below (note,
402
+ # some of the shown fields may not be present in every UPDATE message):
403
+ #
404
+ # +-----------------------------------------------------+
405
+ # | Withdrawn Routes Length (2 octets) |
406
+ # +-----------------------------------------------------+
407
+ # | Withdrawn Routes (variable) |
408
+ # +-----------------------------------------------------+
409
+ # | Total Path Attribute Length (2 octets) |
410
+ # +-----------------------------------------------------+
411
+ # | Path Attributes (variable) |
412
+ # +-----------------------------------------------------+
413
+ # | Network Layer Reachability Information (variable) |
414
+ # +-----------------------------------------------------+
415
+ #
416
+
417
+
418
+ class Update < Message
419
+ def initialize(*args)
420
+ if args[0].is_a?(String) and args[0].is_packed?
421
+ parse(*args)
422
+ elsif args[0].is_a?(self.class)
423
+ parse(args[0].encode, *args[1..-1])
424
+ else
425
+ @msg_type=UPDATE
426
+ set(*args)
427
+ end
428
+ end
429
+
430
+ def set(*args)
431
+ args.each { |arg|
432
+ if arg.is_a?(Withdrawn)
433
+ self.withdrawn=arg
434
+ elsif arg.is_a?(Path_attribute)
435
+ self.path_attribute = arg
436
+ elsif arg.is_a?(Nlri)
437
+ self.nlri = arg
438
+ end
439
+ }
440
+ end
441
+
442
+ def withdrawn=(val)
443
+ @withdrawn=val if val.is_a?(Withdrawn)
444
+ end
445
+
446
+ def nlri=(val)
447
+ @nlri=val if val.is_a?(Nlri)
448
+ end
449
+
450
+ def path_attribute=(val)
451
+ @path_attribute=val if val.is_a?(Path_attribute)
452
+ end
453
+
454
+ def encode(as4byte=false)
455
+ withdrawn, path_attribute, nlri = '', '', ''
456
+ withdrawn = @withdrawn.encode(false) if defined? @withdrawn and @withdrawn
457
+ path_attribute = @path_attribute.encode(as4byte) if defined?(@path_attribute) and @path_attribute
458
+ nlri = @nlri.encode if defined? @nlri and @nlri
459
+ super([withdrawn.size, withdrawn, path_attribute.size, path_attribute, nlri].pack('na*na*a*'))
460
+ end
461
+
462
+ def encode4
463
+ encode(true)
464
+ end
465
+
466
+ attr_reader :path_attribute, :nlri, :withdrawn
467
+
468
+ def parse(s, as4byte=false)
469
+ update = super(s)
470
+ len = update.slice!(0,2).unpack('n')[0]
471
+ self.withdrawn=Withdrawn.new(update.slice!(0,len).is_packed) if len>0
472
+ len = update.slice!(0,2).unpack('n')[0]
473
+ enc_path_attribute = update.slice!(0,len).is_packed
474
+ self.path_attribute=Path_attribute.new(enc_path_attribute, as4byte) if len>0
475
+ self.nlri = Nlri.new(update) if update.size>0
476
+ end
477
+
478
+ def <<(val)
479
+ #TODO add attr or nlri
480
+ end
481
+
482
+ def to_s(as4byte=false, fmt=:tcpdump)
483
+ if as4byte
484
+ msg = self.encode(true)
485
+ else
486
+ msg = self.encode
487
+ end
488
+ s = []
489
+ s << @withdrawn.to_s if defined?(@withdrawn) and @withdrawn
490
+ s << @path_attribute.to_s(fmt, as4byte) if defined?(@path_attribute) and @path_attribute
491
+ s << @nlri.to_s if defined?(@nlri) and @nlri
492
+ "Update Message (#{MESSAGE::UPDATE}), #{as4byte ? "4 bytes AS, " : ''}length: #{msg.size}\n " + s.join("\n") + "\n" + msg.hexlify.join("\n") + "\n"
493
+ end
494
+
495
+ def self.withdrawn(u)
496
+ if u.nlri and u.nlri.size>0
497
+ Update.new(Withdrawn.new(*(u.nlri.nlris.collect { |n| n.to_s})))
498
+ elsif u.path_attribute.has?(Mp_reach)
499
+ pa = Path_attribute.new
500
+ pa << u.path_attribute[ATTR::MP_REACH].new_unreach
501
+ Update.new(pa)
502
+ end
503
+ end
504
+ end
505
+
506
+
507
+ # OPEN Message Format
508
+ #
509
+ # After a TCP connection is established, the first message sent by each
510
+ # side is an OPEN message. If the OPEN message is acceptable, a
511
+ # KEEPALIVE message confirming the OPEN is sent back.
512
+ #
513
+ # In addition to the fixed-size BGP header, the OPEN message contains
514
+ # the following fields:
515
+ #
516
+ # 0 1 2 3
517
+ # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
518
+ # +-+-+-+-+-+-+-+-+
519
+ # | Version |
520
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
521
+ # | My Autonomous System |
522
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
523
+ # | Hold Time |
524
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
525
+ # | BGP Identifier |
526
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
527
+ # | Opt Parm Len |
528
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
529
+ # | |
530
+ # | Optional Parameters (variable) |
531
+ # | |
532
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
533
+ #
534
+ #
535
+
536
+ class Open < Message
537
+
538
+ include OPT_PARM
539
+
540
+ attr_reader :version, :local_as, :holdtime, :opt_parms
541
+
542
+ def initialize(*args)
543
+ if args.size==1 and args[0].is_a?(String) and args[0].is_packed?
544
+ @opt_parms=[] # FIMXE: should not have ot init here
545
+ parse(args[0])
546
+ elsif args[0].is_a?(self.class)
547
+ parse(args[0].encode, *args[1..-1])
548
+ else
549
+ @msg_type=OPEN
550
+ @version, @local_as, @holdtime, bgp_id, *@opt_parms=args
551
+ @bgp_id = IPAddr.new(bgp_id)
552
+ end
553
+ end
554
+
555
+ def <<(arg)
556
+ raise ArgumentError, "arg is not an Optional_parameter" unless arg.is_a?(Optional_parameter)
557
+ @opt_parms << arg
558
+ end
559
+
560
+ def encode
561
+ opt_parms = @opt_parms.flatten.compact.collect { |cap| cap.encode }.join
562
+ super([@version, @local_as, @holdtime, @bgp_id.hton, opt_parms.size, opt_parms].pack('Cnna4Ca*'))
563
+ end
564
+
565
+ def parse(s)
566
+ @version, @local_as, @holdtime, bgp_id, opt_parm_len, opt_parms = super(s).unpack('Cnna4Ca*')
567
+ while opt_parms.size>0
568
+ begin
569
+ @opt_parms << Optional_parameter.factory(opt_parms)
570
+ rescue UnknownBGPCapability => e
571
+ puts "#{e}"
572
+ end
573
+ end
574
+ @bgp_id = IPAddr.new_ntoh(bgp_id)
575
+ end
576
+
577
+ def bgp_id
578
+ @bgp_id.to_s
579
+ end
580
+
581
+ def to_s
582
+ msg = self.encode
583
+ "Open Message (#{MESSAGE::OPEN}), length: #{msg.size}\n" +
584
+ " Version #{@version}, my AS #{@local_as}, Holdtime #{@holdtime}s, ID #{@bgp_id}" +
585
+ ([""] + @opt_parms.compact.collect { |cap| cap.to_s } + [""]).join("\n ") +
586
+ msg.hexlify.join("\n") + "\n"
587
+ end
588
+
589
+ def find(klass)
590
+ @opt_parms.find { |a| a.is_a?(klass) }
591
+ end
592
+
593
+ def has?(klass)
594
+ @opt_parms.find { |a| a.is_a?(klass) }.nil? ? false : true
595
+ end
596
+
597
+ def to_hash
598
+ h = {:version => @version, :my_as => @local_as, :holdtime => @holdtime, :bgp_id => bgp_id }
599
+ unless @opt_parms.empty?
600
+ h[:capabilities] = @opt_parms.collect { |opt| opt.to_hash }
601
+ end
602
+ h
603
+ end
604
+ end
605
+
606
+ # Route-REFRESH Message
607
+ #
608
+ # The ROUTE-REFRESH message is a new BGP message type defined as
609
+ # follows:
610
+ #
611
+ # Type: 5 - ROUTE-REFRESH
612
+ #
613
+ # Message Format: One <AFI, SAFI> encoded as
614
+ #
615
+ # 0 7 15 23 31
616
+ # +-------+-------+-------+-------+
617
+ # | AFI | Res. | SAFI |
618
+ # +-------+-------+-------+-------+
619
+ #
620
+
621
+ class Route_refresh < Message
622
+ attr_reader :afi, :safi
623
+ def initialize(*args)
624
+ @msg_type=ROUTE_REFRESH
625
+ if args.size==1 and args[0].is_a?(String) and args[0].is_packed?
626
+ parse(args[0])
627
+ elsif args[0].is_a?(self.class)
628
+ parse(args[0].encode, *args[1..-1])
629
+ elsif args.size==1 and args[0].is_a?(Hash)
630
+ self.afi = args[0][:afi] if args[0][:afi]
631
+ self.safi = args[0][:safi] if args[0][:safi]
632
+ else
633
+ @afi, @safi=args
634
+ end
635
+ end
636
+
637
+ def afi=(val)
638
+ raise ArgumentError, "invalid argument" unless val.is_a?(Fixnum) and (0..0xffff) === val
639
+ @afi=val
640
+ end
641
+
642
+ def safi=(val)
643
+ raise ArgumentError, "invalid argument" unless val.is_a?(Fixnum) and (0..0xff) === val
644
+ @safi=val
645
+ end
646
+
647
+ def encode
648
+ # default to ipv4 and unicast when not set
649
+ @afi ||=1
650
+ @safi ||=1
651
+ super([@afi, 0, @safi].pack('nCC'))
652
+ end
653
+
654
+ def parse(s)
655
+ @afi, reserved, @safi= super(s).unpack('nCC')
656
+ end
657
+
658
+ def to_hash
659
+ {:afi=> @afi, :safi=> @safi}
660
+ end
661
+
662
+ def to_s
663
+ msg = self.encode
664
+ "Route Refresh (#{MESSAGE::ROUTE_REFRESH}), length: #{msg.size}\n" + "AFI #{IANA.afi(@afi)} (#{@afi}), SAFI #{IANA.safi(@safi)} (#{@safi})"
665
+ end
666
+ end
667
+
668
+ #
669
+ # Carrying ORF Entries in BGP
670
+ #
671
+ # ORF entries are carried in the BGP ROUTE-REFRESH message [BGP-RR].
672
+ #
673
+ # A BGP speaker can distinguish an incoming ROUTE-REFRESH message that
674
+ # carries one or more ORF entries from an incoming plain ROUTE-REFRESH
675
+ # message by using the Message Length field in the BGP message header.
676
+ #
677
+
678
+ class Orf
679
+ def self.factory(s)
680
+ type, len = s.unpack('Cn')
681
+ case type
682
+ when ORF::PREFIX
683
+ Prefix_orf.new(s.slice!(0,len+3).is_packed)
684
+ else
685
+ raise RuntimeError, "orf type #{type} not implemented"
686
+ end
687
+ end
688
+ end
689
+
690
+ #FIXME: Unit-test
691
+ class Orf_route_refresh < Message
692
+
693
+ attr_accessor :orfs
694
+
695
+ def initialize(*args)
696
+ @msg_type=ROUTE_REFRESH
697
+ @orfs=[]
698
+ if args.size==1 and args[0].is_a?(String) and args[0].is_packed?
699
+ parse(args[0])
700
+ elsif args[0].is_a?(self.class)
701
+ parse(args[0].encode, *args[1..-1])
702
+ elsif args.size==1 and args[0].is_a?(Hash)
703
+ self.afi = args[0][:afi] if args[0][:afi]
704
+ self.safi = args[0][:safi] if args[0][:safi]
705
+ else
706
+ @afi, @safi=args
707
+ end
708
+ end
709
+
710
+ def afi_to_i
711
+ @afi
712
+ end
713
+
714
+ def safi_to_i
715
+ @safi
716
+ end
717
+
718
+ def afi
719
+ IANA.afi(@afi)
720
+ end
721
+
722
+ def safi
723
+ IANA.safi(@safi)
724
+ end
725
+
726
+ def afi=(val)
727
+ raise ArgumentError, "invalid argument" unless val.is_a?(Fixnum) and (0..0xffff) === val
728
+ @afi=val
729
+ end
730
+
731
+ def safi=(val)
732
+ raise ArgumentError, "invalid argument" unless val.is_a?(Fixnum) and (0..0xff) === val
733
+ @safi=val
734
+ end
735
+
736
+ def when_to_s
737
+ case @when
738
+ when 1 ; 'defer (1)'
739
+ when 2 ; 'immediate (2)'
740
+ else
741
+ "bogus (#{@when})"
742
+ end
743
+ end
744
+
745
+ def encode
746
+ super([@afi, 0, @safi, @orfs.collect { |o| o.encode }.join].pack('nCCa*'))
747
+ end
748
+
749
+ def parse(s)
750
+ @afi, reserved, @safi, orfs= super(s).unpack('nCCa*')
751
+ while orfs.size>0
752
+ #puts "orfs before factory: #{orfs.unpack('H*')}"
753
+ @orfs << Orf.factory(orfs.is_packed)
754
+ #puts "orfs after factory: #{orfs.unpack('H*')}"
755
+ end
756
+ end
757
+
758
+ def to_hash
759
+ {:afi=> @afi, :safi=> @safi}
760
+ end
761
+
762
+ def to_s
763
+ msg = self.encode
764
+ "ORF Route Refresh (#{MESSAGE::ROUTE_REFRESH}), length: #{msg.size}\n" +
765
+ "AFI #{IANA.afi(@afi)} (#{@afi}), SAFI #{IANA.safi(@safi)} (#{@safi}):\n" +
766
+ @orfs.collect { |orf| orf.to_s}.join("\n")
767
+ end
768
+
769
+ def add(*args)
770
+ @orfs ||=[]
771
+ args.each { |arg| @orfs << arg if arg.is_a?(Orf) }
772
+ end
773
+ alias << add
774
+
775
+ def communities
776
+ @communities.collect { |comm| comm.to_s }.join(' ')
777
+ end
778
+
779
+ end
780
+
781
+ # NOTIFICATION Message Format
782
+ #
783
+ # A NOTIFICATION message is sent when an error condition is detected.
784
+ # The BGP connection is closed immediately after it is sent.
785
+ #
786
+ #
787
+ #
788
+ #
789
+ # Rekhter, et al. Standards Track [Page 21]
790
+ #
791
+ # RFC 4271 BGP-4 January 2006
792
+ #
793
+ #
794
+ # In addition to the fixed-size BGP header, the NOTIFICATION message
795
+ # contains the following fields:
796
+ #
797
+ # 0 1 2 3
798
+ # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
799
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
800
+ # | Error code | Error subcode | Data (variable) |
801
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
802
+ #
803
+
804
+
805
+ class Notification < Message
806
+
807
+ def self.code_to_s(*args)
808
+ @code_to_s[args]
809
+ end
810
+
811
+ @code_to_s=Hash.new("Undefined")
812
+ @code_to_s[[1]] = "Header Error"
813
+ @code_to_s[[2]] = "OPEN msg error"
814
+ @code_to_s[[3]] = "UPDATE msg error"
815
+ @code_to_s[[4]] = "Hold Timer Expired"
816
+ @code_to_s[[5]] = "Finite State Machine Error"
817
+ @code_to_s[[6]] = "Cease"
818
+ @code_to_s[[1,1]] = "Connection Not Synchronized"
819
+ @code_to_s[[1,2]] = "Bad Message Length"
820
+ @code_to_s[[1,3]] = "Bad Message Type"
821
+ @code_to_s[[2,1]] = "Unsupported Version Number"
822
+ @code_to_s[[2,2]] = "Bad Peer AS"
823
+ @code_to_s[[2,3]] = "Bad BGP Identifier"
824
+ @code_to_s[[2,4]] = "Unsupported Optional Parameter"
825
+ @code_to_s[[2,5]] = "[Deprecated]"
826
+ @code_to_s[[2,6]] = "Unacceptable Hold Time"
827
+ @code_to_s[[2,7]] = "Unsupported Optional Parameter"
828
+ @code_to_s[[3,1]] = "Malformed Attribute List"
829
+ @code_to_s[[3,2]] = "Unrecognized Well-known Attribute"
830
+ @code_to_s[[3,3]] = "Missing Well-known Attribute"
831
+ @code_to_s[[3,4]] = "Attribute Flags Error"
832
+ @code_to_s[[3,5]] = "Attribute Length Error"
833
+ @code_to_s[[3,6]] = "Invalid ORIGIN Attribute"
834
+ @code_to_s[[3,7]] = "Deprecated"
835
+ @code_to_s[[3,8]] = "Invalid NEXT_HOP Attribute"
836
+ @code_to_s[[3,9]] = "Optional Attribute Error"
837
+ @code_to_s[[3,10]] = "Invalid Network Field"
838
+ @code_to_s[[3,11]] = "Malformed AS_PATH"
839
+ @code_to_s[[6,1]] = "Maximum Number of Prefixes Reached"
840
+ @code_to_s[[6,2]] = "Administrative Shutdown"
841
+ @code_to_s[[6,3]] = "Peer De-configured"
842
+ @code_to_s[[6,4]] = "Administrative Reset"
843
+ @code_to_s[[6,5]] = "Connection Rejected"
844
+ @code_to_s[[6,6]] = "Other Configuration Change"
845
+ @code_to_s[[6,7]] = "Connection Collision Resolution"
846
+ @code_to_s[[6,8]] = "Out of Resources"
847
+
848
+ def initialize(*args)
849
+ @msg_type=NOTIFICATION
850
+ if args.size==1 and args[0].is_a?(String) and args[0].is_packed?
851
+ parse(args[0])
852
+ elsif args[0].is_a?(self.class)
853
+ parse(args[0].encode, *args[1..-1])
854
+ elsif args.size==1 and args[0].is_a?(Notification) and args[0].respond_to?(:encode)
855
+ parse(args[0].encode)
856
+ else
857
+ @code, @subcode, @data=args
858
+ end
859
+ end
860
+
861
+ def encode
862
+ # default to ipv4 and unicast when not set
863
+ super([@code,@subcode, @data].pack('CCa*'))
864
+ end
865
+
866
+ def parse(s)
867
+ @code, @subcode, @data= super(s).unpack('CCa*')
868
+ end
869
+
870
+ def to_hash
871
+ {:code=> @code, :subcode=> @subcode, :data=>@data}
872
+ end
873
+
874
+ def to_s
875
+ msg = self.encode
876
+ s = "Notification (#{MESSAGE::NOTIFICATION}), length: #{msg.size}: "
877
+ s += "#{Notification.code_to_s(@code)} (#{@code}), #{Notification.code_to_s(@code, @subcode)} (#{@subcode}) "
878
+ s += "\ndata: [#{@data}]" if @data.size>0
879
+ s
880
+ end
881
+
882
+ end
883
+
884
+ # s = 'ffffffffffffffffffffffffffffffff0050020000002f40010101400304c0a80105800404000000644005040000006440020402010064c0080c051f00010137003b0af50040200a0a0a0a2020202020'
885
+ # m = Message.factory([s].pack('H*'))
886
+ # puts m.to_s(true, :tcpdump)
887
+ # puts m.to_s(true, :default)
888
+ # puts m.to_s(false, :default)
889
+
890
+ end
891
+
892
+ load "../test/#{ File.basename($0.gsub(/.rb/,'_test.rb'))}" if __FILE__ == $0
893
+
894
+