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.
- data/COPYING +674 -0
- data/LICENSE.txt +53 -0
- data/README.rdoc +259 -0
- data/bgp/aggregator.rb +104 -0
- data/bgp/as_path.rb +223 -0
- data/bgp/atomic_aggregate.rb +42 -0
- data/bgp/attribute.rb +181 -0
- data/bgp/attributes.rb +34 -0
- data/bgp/cluster_list.rb +117 -0
- data/bgp/common.rb +204 -0
- data/bgp/communities.rb +139 -0
- data/bgp/extended_communities.rb +107 -0
- data/bgp/extended_community.rb +254 -0
- data/bgp/iana.rb +269 -0
- data/bgp/io.rb +116 -0
- data/bgp/label.rb +94 -0
- data/bgp/local_pref.rb +75 -0
- data/bgp/message.rb +894 -0
- data/bgp/mp_reach.rb +208 -0
- data/bgp/multi_exit_disc.rb +76 -0
- data/bgp/neighbor.rb +291 -0
- data/bgp/next_hop.rb +63 -0
- data/bgp/nlri.rb +303 -0
- data/bgp/orf.rb +88 -0
- data/bgp/origin.rb +88 -0
- data/bgp/originator_id.rb +73 -0
- data/bgp/path_attribute.rb +210 -0
- data/bgp/prefix_orf.rb +263 -0
- data/bgp/rd.rb +107 -0
- data/examples/bgp +65 -0
- data/examples/routegen +85 -0
- data/examples/routegen.yml +50 -0
- data/test/aggregator_test.rb +66 -0
- data/test/as_path_test.rb +149 -0
- data/test/atomic_aggregate_test.rb +35 -0
- data/test/attribute_test.rb +57 -0
- data/test/cluster_list_test.rb +39 -0
- data/test/common_test.rb +68 -0
- data/test/communities_test.rb +75 -0
- data/test/extended_communities_test.rb +111 -0
- data/test/extended_community_test.rb +93 -0
- data/test/label_test.rb +50 -0
- data/test/local_pref_test.rb +43 -0
- data/test/message_test.rb +294 -0
- data/test/mp_reach_test.rb +143 -0
- data/test/multi_exit_disc_test.rb +46 -0
- data/test/neighbor_test.rb +50 -0
- data/test/next_hop_test.rb +37 -0
- data/test/nlri_test.rb +189 -0
- data/test/origin_test.rb +57 -0
- data/test/originator_id_test.rb +38 -0
- data/test/path_attribute_test.rb +127 -0
- data/test/prefix_orf_test.rb +97 -0
- data/test/rd_test.rb +44 -0
- 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
|
+
|