bgp4r 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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
+