Showing
9 changed files
with
3178 additions
and
1 deletions
... | @@ -126,6 +126,8 @@ public class Router implements RouteListener { | ... | @@ -126,6 +126,8 @@ public class Router implements RouteListener { |
126 | bgpIntentsSynchronizerExecutor = Executors.newSingleThreadExecutor( | 126 | bgpIntentsSynchronizerExecutor = Executors.newSingleThreadExecutor( |
127 | new ThreadFactoryBuilder() | 127 | new ThreadFactoryBuilder() |
128 | .setNameFormat("bgp-intents-synchronizer-%d").build()); | 128 | .setNameFormat("bgp-intents-synchronizer-%d").build()); |
129 | + | ||
130 | + this.hostService.addListener(new InternalHostListener()); | ||
129 | } | 131 | } |
130 | 132 | ||
131 | /** | 133 | /** | ... | ... |
... | @@ -10,6 +10,7 @@ import org.apache.felix.scr.annotations.ReferenceCardinality; | ... | @@ -10,6 +10,7 @@ import org.apache.felix.scr.annotations.ReferenceCardinality; |
10 | import org.onlab.onos.net.host.HostService; | 10 | import org.onlab.onos.net.host.HostService; |
11 | import org.onlab.onos.net.intent.IntentService; | 11 | import org.onlab.onos.net.intent.IntentService; |
12 | import org.onlab.onos.sdnip.RouteUpdate.Type; | 12 | import org.onlab.onos.sdnip.RouteUpdate.Type; |
13 | +import org.onlab.onos.sdnip.bgp.BgpSessionManager; | ||
13 | import org.onlab.onos.sdnip.config.SdnIpConfigReader; | 14 | import org.onlab.onos.sdnip.config.SdnIpConfigReader; |
14 | import org.onlab.packet.IpAddress; | 15 | import org.onlab.packet.IpAddress; |
15 | import org.onlab.packet.IpPrefix; | 16 | import org.onlab.packet.IpPrefix; |
... | @@ -32,6 +33,7 @@ public class SdnIp { | ... | @@ -32,6 +33,7 @@ public class SdnIp { |
32 | private SdnIpConfigReader config; | 33 | private SdnIpConfigReader config; |
33 | private PeerConnectivity peerConnectivity; | 34 | private PeerConnectivity peerConnectivity; |
34 | private Router router; | 35 | private Router router; |
36 | + private BgpSessionManager bgpSessionManager; | ||
35 | 37 | ||
36 | @Activate | 38 | @Activate |
37 | protected void activate() { | 39 | protected void activate() { |
... | @@ -48,6 +50,9 @@ public class SdnIp { | ... | @@ -48,6 +50,9 @@ public class SdnIp { |
48 | router = new Router(intentService, hostService, config, interfaceService); | 50 | router = new Router(intentService, hostService, config, interfaceService); |
49 | router.start(); | 51 | router.start(); |
50 | 52 | ||
53 | + bgpSessionManager = new BgpSessionManager(router); | ||
54 | + bgpSessionManager.startUp(2000); // TODO | ||
55 | + | ||
51 | // TODO need to disable link discovery on external ports | 56 | // TODO need to disable link discovery on external ports |
52 | 57 | ||
53 | router.update(new RouteUpdate(Type.UPDATE, new RouteEntry( | 58 | router.update(new RouteUpdate(Type.UPDATE, new RouteEntry( | ... | ... |
1 | +package org.onlab.onos.sdnip.bgp; | ||
2 | + | ||
3 | +/** | ||
4 | + * BGP related constants. | ||
5 | + */ | ||
6 | +public final class BgpConstants { | ||
7 | + /** | ||
8 | + * Default constructor. | ||
9 | + * <p> | ||
10 | + * The constructor is private to prevent creating an instance of | ||
11 | + * this utility class. | ||
12 | + */ | ||
13 | + private BgpConstants() { | ||
14 | + } | ||
15 | + | ||
16 | + /** BGP port number (RFC 4271). */ | ||
17 | + public static final int BGP_PORT = 179; | ||
18 | + | ||
19 | + /** BGP version. */ | ||
20 | + public static final int BGP_VERSION = 4; | ||
21 | + | ||
22 | + /** BGP OPEN message type. */ | ||
23 | + public static final int BGP_TYPE_OPEN = 1; | ||
24 | + | ||
25 | + /** BGP UPDATE message type. */ | ||
26 | + public static final int BGP_TYPE_UPDATE = 2; | ||
27 | + | ||
28 | + /** BGP NOTIFICATION message type. */ | ||
29 | + public static final int BGP_TYPE_NOTIFICATION = 3; | ||
30 | + | ||
31 | + /** BGP KEEPALIVE message type. */ | ||
32 | + public static final int BGP_TYPE_KEEPALIVE = 4; | ||
33 | + | ||
34 | + /** BGP Header Marker field length. */ | ||
35 | + public static final int BGP_HEADER_MARKER_LENGTH = 16; | ||
36 | + | ||
37 | + /** BGP Header length. */ | ||
38 | + public static final int BGP_HEADER_LENGTH = 19; | ||
39 | + | ||
40 | + /** BGP message maximum length. */ | ||
41 | + public static final int BGP_MESSAGE_MAX_LENGTH = 4096; | ||
42 | + | ||
43 | + /** BGP OPEN message minimum length (BGP Header included). */ | ||
44 | + public static final int BGP_OPEN_MIN_LENGTH = 29; | ||
45 | + | ||
46 | + /** BGP UPDATE message minimum length (BGP Header included). */ | ||
47 | + public static final int BGP_UPDATE_MIN_LENGTH = 23; | ||
48 | + | ||
49 | + /** BGP NOTIFICATION message minimum length (BGP Header included). */ | ||
50 | + public static final int BGP_NOTIFICATION_MIN_LENGTH = 21; | ||
51 | + | ||
52 | + /** BGP KEEPALIVE message expected length (BGP Header included). */ | ||
53 | + public static final int BGP_KEEPALIVE_EXPECTED_LENGTH = 19; | ||
54 | + | ||
55 | + /** BGP KEEPALIVE messages transmitted per Hold interval. */ | ||
56 | + public static final int BGP_KEEPALIVE_PER_HOLD_INTERVAL = 3; | ||
57 | + | ||
58 | + /** BGP KEEPALIVE messages minimum Holdtime (in seconds). */ | ||
59 | + public static final int BGP_KEEPALIVE_MIN_HOLDTIME = 3; | ||
60 | + | ||
61 | + /** BGP KEEPALIVE messages minimum transmission interval (in seconds). */ | ||
62 | + public static final int BGP_KEEPALIVE_MIN_INTERVAL = 1; | ||
63 | + | ||
64 | + /** BGP AS 0 (zero) value. See draft-ietf-idr-as0-06.txt Internet Draft. */ | ||
65 | + public static final long BGP_AS_0 = 0; | ||
66 | + | ||
67 | + /** | ||
68 | + * BGP UPDATE related constants. | ||
69 | + */ | ||
70 | + public static final class Update { | ||
71 | + /** | ||
72 | + * Default constructor. | ||
73 | + * <p> | ||
74 | + * The constructor is private to prevent creating an instance of | ||
75 | + * this utility class. | ||
76 | + */ | ||
77 | + private Update() { | ||
78 | + } | ||
79 | + | ||
80 | + /** | ||
81 | + * BGP UPDATE: ORIGIN related constants. | ||
82 | + */ | ||
83 | + public static final class Origin { | ||
84 | + /** | ||
85 | + * Default constructor. | ||
86 | + * <p> | ||
87 | + * The constructor is private to prevent creating an instance of | ||
88 | + * this utility class. | ||
89 | + */ | ||
90 | + private Origin() { | ||
91 | + } | ||
92 | + | ||
93 | + /** BGP UPDATE Attributes Type Code ORIGIN. */ | ||
94 | + public static final int TYPE = 1; | ||
95 | + | ||
96 | + /** BGP UPDATE Attributes Type Code ORIGIN length. */ | ||
97 | + public static final int LENGTH = 1; | ||
98 | + | ||
99 | + /** BGP UPDATE ORIGIN: IGP. */ | ||
100 | + public static final int IGP = 0; | ||
101 | + | ||
102 | + /** BGP UPDATE ORIGIN: EGP. */ | ||
103 | + public static final int EGP = 1; | ||
104 | + | ||
105 | + /** BGP UPDATE ORIGIN: INCOMPLETE. */ | ||
106 | + public static final int INCOMPLETE = 2; | ||
107 | + } | ||
108 | + | ||
109 | + /** | ||
110 | + * BGP UPDATE: AS_PATH related constants. | ||
111 | + */ | ||
112 | + public static final class AsPath { | ||
113 | + /** | ||
114 | + * Default constructor. | ||
115 | + * <p> | ||
116 | + * The constructor is private to prevent creating an instance of | ||
117 | + * this utility class. | ||
118 | + */ | ||
119 | + private AsPath() { | ||
120 | + } | ||
121 | + | ||
122 | + /** BGP UPDATE Attributes Type Code AS_PATH. */ | ||
123 | + public static final int TYPE = 2; | ||
124 | + | ||
125 | + /** BGP UPDATE AS_PATH Type: AS_SET. */ | ||
126 | + public static final int AS_SET = 1; | ||
127 | + | ||
128 | + /** BGP UPDATE AS_PATH Type: AS_SEQUENCE. */ | ||
129 | + public static final int AS_SEQUENCE = 2; | ||
130 | + } | ||
131 | + | ||
132 | + /** | ||
133 | + * BGP UPDATE: NEXT_HOP related constants. | ||
134 | + */ | ||
135 | + public static final class NextHop { | ||
136 | + /** | ||
137 | + * Default constructor. | ||
138 | + * <p> | ||
139 | + * The constructor is private to prevent creating an instance of | ||
140 | + * this utility class. | ||
141 | + */ | ||
142 | + private NextHop() { | ||
143 | + } | ||
144 | + | ||
145 | + /** BGP UPDATE Attributes Type Code NEXT_HOP. */ | ||
146 | + public static final int TYPE = 3; | ||
147 | + | ||
148 | + /** BGP UPDATE Attributes Type Code NEXT_HOP length. */ | ||
149 | + public static final int LENGTH = 4; | ||
150 | + } | ||
151 | + | ||
152 | + /** | ||
153 | + * BGP UPDATE: MULTI_EXIT_DISC related constants. | ||
154 | + */ | ||
155 | + public static final class MultiExitDisc { | ||
156 | + /** | ||
157 | + * Default constructor. | ||
158 | + * <p> | ||
159 | + * The constructor is private to prevent creating an instance of | ||
160 | + * this utility class. | ||
161 | + */ | ||
162 | + private MultiExitDisc() { | ||
163 | + } | ||
164 | + | ||
165 | + /** BGP UPDATE Attributes Type Code MULTI_EXIT_DISC. */ | ||
166 | + public static final int TYPE = 4; | ||
167 | + | ||
168 | + /** BGP UPDATE Attributes Type Code MULTI_EXIT_DISC length. */ | ||
169 | + public static final int LENGTH = 4; | ||
170 | + | ||
171 | + /** BGP UPDATE Attributes lowest MULTI_EXIT_DISC value. */ | ||
172 | + public static final int LOWEST_MULTI_EXIT_DISC = 0; | ||
173 | + } | ||
174 | + | ||
175 | + /** | ||
176 | + * BGP UPDATE: LOCAL_PREF related constants. | ||
177 | + */ | ||
178 | + public static final class LocalPref { | ||
179 | + /** | ||
180 | + * Default constructor. | ||
181 | + * <p> | ||
182 | + * The constructor is private to prevent creating an instance of | ||
183 | + * this utility class. | ||
184 | + */ | ||
185 | + private LocalPref() { | ||
186 | + } | ||
187 | + | ||
188 | + /** BGP UPDATE Attributes Type Code LOCAL_PREF. */ | ||
189 | + public static final int TYPE = 5; | ||
190 | + | ||
191 | + /** BGP UPDATE Attributes Type Code LOCAL_PREF length. */ | ||
192 | + public static final int LENGTH = 4; | ||
193 | + } | ||
194 | + | ||
195 | + /** | ||
196 | + * BGP UPDATE: ATOMIC_AGGREGATE related constants. | ||
197 | + */ | ||
198 | + public static final class AtomicAggregate { | ||
199 | + /** | ||
200 | + * Default constructor. | ||
201 | + * <p> | ||
202 | + * The constructor is private to prevent creating an instance of | ||
203 | + * this utility class. | ||
204 | + */ | ||
205 | + private AtomicAggregate() { | ||
206 | + } | ||
207 | + | ||
208 | + /** BGP UPDATE Attributes Type Code ATOMIC_AGGREGATE. */ | ||
209 | + public static final int TYPE = 6; | ||
210 | + | ||
211 | + /** BGP UPDATE Attributes Type Code ATOMIC_AGGREGATE length. */ | ||
212 | + public static final int LENGTH = 0; | ||
213 | + } | ||
214 | + | ||
215 | + /** | ||
216 | + * BGP UPDATE: AGGREGATOR related constants. | ||
217 | + */ | ||
218 | + public static final class Aggregator { | ||
219 | + /** | ||
220 | + * Default constructor. | ||
221 | + * <p> | ||
222 | + * The constructor is private to prevent creating an instance of | ||
223 | + * this utility class. | ||
224 | + */ | ||
225 | + private Aggregator() { | ||
226 | + } | ||
227 | + | ||
228 | + /** BGP UPDATE Attributes Type Code AGGREGATOR. */ | ||
229 | + public static final int TYPE = 7; | ||
230 | + | ||
231 | + /** BGP UPDATE Attributes Type Code AGGREGATOR length. */ | ||
232 | + public static final int LENGTH = 6; | ||
233 | + } | ||
234 | + } | ||
235 | + | ||
236 | + /** | ||
237 | + * BGP NOTIFICATION related constants. | ||
238 | + */ | ||
239 | + public static final class Notifications { | ||
240 | + /** | ||
241 | + * Default constructor. | ||
242 | + * <p> | ||
243 | + * The constructor is private to prevent creating an instance of | ||
244 | + * this utility class. | ||
245 | + */ | ||
246 | + private Notifications() { | ||
247 | + } | ||
248 | + | ||
249 | + /** | ||
250 | + * BGP NOTIFICATION: Message Header Error constants. | ||
251 | + */ | ||
252 | + public static final class MessageHeaderError { | ||
253 | + /** | ||
254 | + * Default constructor. | ||
255 | + * <p> | ||
256 | + * The constructor is private to prevent creating an instance of | ||
257 | + * this utility class. | ||
258 | + */ | ||
259 | + private MessageHeaderError() { | ||
260 | + } | ||
261 | + | ||
262 | + /** Message Header Error code. */ | ||
263 | + public static final int ERROR_CODE = 1; | ||
264 | + | ||
265 | + /** Message Header Error subcode: Connection Not Synchronized. */ | ||
266 | + public static final int CONNECTION_NOT_SYNCHRONIZED = 1; | ||
267 | + | ||
268 | + /** Message Header Error subcode: Bad Message Length. */ | ||
269 | + public static final int BAD_MESSAGE_LENGTH = 2; | ||
270 | + | ||
271 | + /** Message Header Error subcode: Bad Message Type. */ | ||
272 | + public static final int BAD_MESSAGE_TYPE = 3; | ||
273 | + } | ||
274 | + | ||
275 | + /** | ||
276 | + * BGP NOTIFICATION: OPEN Message Error constants. | ||
277 | + */ | ||
278 | + public static final class OpenMessageError { | ||
279 | + /** | ||
280 | + * Default constructor. | ||
281 | + * <p> | ||
282 | + * The constructor is private to prevent creating an instance of | ||
283 | + * this utility class. | ||
284 | + */ | ||
285 | + private OpenMessageError() { | ||
286 | + } | ||
287 | + | ||
288 | + /** OPEN Message Error code. */ | ||
289 | + public static final int ERROR_CODE = 2; | ||
290 | + | ||
291 | + /** OPEN Message Error subcode: Unsupported Version Number. */ | ||
292 | + public static final int UNSUPPORTED_VERSION_NUMBER = 1; | ||
293 | + | ||
294 | + /** OPEN Message Error subcode: Bad PEER AS. */ | ||
295 | + public static final int BAD_PEER_AS = 2; | ||
296 | + | ||
297 | + /** OPEN Message Error subcode: Unacceptable Hold Time. */ | ||
298 | + public static final int UNACCEPTABLE_HOLD_TIME = 6; | ||
299 | + } | ||
300 | + | ||
301 | + /** | ||
302 | + * BGP NOTIFICATION: UPDATE Message Error constants. | ||
303 | + */ | ||
304 | + public static final class UpdateMessageError { | ||
305 | + /** | ||
306 | + * Default constructor. | ||
307 | + * <p> | ||
308 | + * The constructor is private to prevent creating an instance of | ||
309 | + * this utility class. | ||
310 | + */ | ||
311 | + private UpdateMessageError() { | ||
312 | + } | ||
313 | + | ||
314 | + /** UPDATE Message Error code. */ | ||
315 | + public static final int ERROR_CODE = 3; | ||
316 | + | ||
317 | + /** UPDATE Message Error subcode: Malformed Attribute List. */ | ||
318 | + public static final int MALFORMED_ATTRIBUTE_LIST = 1; | ||
319 | + | ||
320 | + /** UPDATE Message Error subcode: Unrecognized Well-known Attribute. */ | ||
321 | + public static final int UNRECOGNIZED_WELL_KNOWN_ATTRIBUTE = 2; | ||
322 | + | ||
323 | + /** UPDATE Message Error subcode: Missing Well-known Attribute. */ | ||
324 | + public static final int MISSING_WELL_KNOWN_ATTRIBUTE = 3; | ||
325 | + | ||
326 | + /** UPDATE Message Error subcode: Attribute Flags Error. */ | ||
327 | + public static final int ATTRIBUTE_FLAGS_ERROR = 4; | ||
328 | + | ||
329 | + /** UPDATE Message Error subcode: Attribute Length Error. */ | ||
330 | + public static final int ATTRIBUTE_LENGTH_ERROR = 5; | ||
331 | + | ||
332 | + /** UPDATE Message Error subcode: Invalid ORIGIN Attribute. */ | ||
333 | + public static final int INVALID_ORIGIN_ATTRIBUTE = 6; | ||
334 | + | ||
335 | + /** UPDATE Message Error subcode: Invalid NEXT_HOP Attribute. */ | ||
336 | + public static final int INVALID_NEXT_HOP_ATTRIBUTE = 8; | ||
337 | + | ||
338 | + /** UPDATE Message Error subcode: Optional Attribute Error. Unused. */ | ||
339 | + public static final int OPTIONAL_ATTRIBUTE_ERROR = 9; | ||
340 | + | ||
341 | + /** UPDATE Message Error subcode: Invalid Network Field. */ | ||
342 | + public static final int INVALID_NETWORK_FIELD = 10; | ||
343 | + | ||
344 | + /** UPDATE Message Error subcode: Malformed AS_PATH. */ | ||
345 | + public static final int MALFORMED_AS_PATH = 11; | ||
346 | + } | ||
347 | + | ||
348 | + /** | ||
349 | + * BGP NOTIFICATION: Hold Timer Expired constants. | ||
350 | + */ | ||
351 | + public static final class HoldTimerExpired { | ||
352 | + /** | ||
353 | + * Default constructor. | ||
354 | + * <p> | ||
355 | + * The constructor is private to prevent creating an instance of | ||
356 | + * this utility class. | ||
357 | + */ | ||
358 | + private HoldTimerExpired() { | ||
359 | + } | ||
360 | + | ||
361 | + /** Hold Timer Expired code. */ | ||
362 | + public static final int ERROR_CODE = 4; | ||
363 | + } | ||
364 | + | ||
365 | + /** BGP NOTIFICATION message Error subcode: Unspecific. */ | ||
366 | + public static final int ERROR_SUBCODE_UNSPECIFIC = 0; | ||
367 | + } | ||
368 | +} |
1 | +package org.onlab.onos.sdnip.bgp; | ||
2 | + | ||
3 | +import org.jboss.netty.buffer.ChannelBuffer; | ||
4 | +import org.jboss.netty.buffer.ChannelBuffers; | ||
5 | +import org.jboss.netty.channel.Channel; | ||
6 | +import org.jboss.netty.channel.ChannelHandlerContext; | ||
7 | +import org.jboss.netty.handler.codec.frame.FrameDecoder; | ||
8 | +import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications.MessageHeaderError; | ||
9 | +import org.slf4j.Logger; | ||
10 | +import org.slf4j.LoggerFactory; | ||
11 | + | ||
12 | +/** | ||
13 | + * Class for handling the decoding of the BGP messages. | ||
14 | + */ | ||
15 | +class BgpFrameDecoder extends FrameDecoder { | ||
16 | + private static final Logger log = | ||
17 | + LoggerFactory.getLogger(BgpFrameDecoder.class); | ||
18 | + | ||
19 | + private final BgpSession bgpSession; | ||
20 | + | ||
21 | + /** | ||
22 | + * Constructor for a given BGP Session. | ||
23 | + * | ||
24 | + * @param bgpSession the BGP session state to use. | ||
25 | + */ | ||
26 | + BgpFrameDecoder(BgpSession bgpSession) { | ||
27 | + this.bgpSession = bgpSession; | ||
28 | + } | ||
29 | + | ||
30 | + @Override | ||
31 | + protected Object decode(ChannelHandlerContext ctx, | ||
32 | + Channel channel, | ||
33 | + ChannelBuffer buf) throws Exception { | ||
34 | + // | ||
35 | + // NOTE: If we close the channel during the decoding, we might still | ||
36 | + // see some incoming messages while the channel closing is completed. | ||
37 | + // | ||
38 | + if (bgpSession.isClosed()) { | ||
39 | + return null; | ||
40 | + } | ||
41 | + | ||
42 | + log.trace("BGP Peer: decode(): remoteAddr = {} localAddr = {} " + | ||
43 | + "messageSize = {}", | ||
44 | + ctx.getChannel().getRemoteAddress(), | ||
45 | + ctx.getChannel().getLocalAddress(), | ||
46 | + buf.readableBytes()); | ||
47 | + | ||
48 | + // Test for minimum length of the BGP message | ||
49 | + if (buf.readableBytes() < BgpConstants.BGP_HEADER_LENGTH) { | ||
50 | + // No enough data received | ||
51 | + return null; | ||
52 | + } | ||
53 | + | ||
54 | + // | ||
55 | + // Mark the current buffer position in case we haven't received | ||
56 | + // the whole message. | ||
57 | + // | ||
58 | + buf.markReaderIndex(); | ||
59 | + | ||
60 | + // | ||
61 | + // Read and check the BGP message Marker field: it must be all ones | ||
62 | + // (See RFC 4271, Section 4.1) | ||
63 | + // | ||
64 | + byte[] marker = new byte[BgpConstants.BGP_HEADER_MARKER_LENGTH]; | ||
65 | + buf.readBytes(marker); | ||
66 | + for (int i = 0; i < marker.length; i++) { | ||
67 | + if (marker[i] != (byte) 0xff) { | ||
68 | + log.debug("BGP RX Error: invalid marker {} at position {}", | ||
69 | + marker[i], i); | ||
70 | + // | ||
71 | + // ERROR: Connection Not Synchronized | ||
72 | + // | ||
73 | + // Send NOTIFICATION and close the connection | ||
74 | + int errorCode = MessageHeaderError.ERROR_CODE; | ||
75 | + int errorSubcode = | ||
76 | + MessageHeaderError.CONNECTION_NOT_SYNCHRONIZED; | ||
77 | + ChannelBuffer txMessage = | ||
78 | + bgpSession.prepareBgpNotification(errorCode, errorSubcode, | ||
79 | + null); | ||
80 | + ctx.getChannel().write(txMessage); | ||
81 | + bgpSession.closeChannel(ctx); | ||
82 | + return null; | ||
83 | + } | ||
84 | + } | ||
85 | + | ||
86 | + // | ||
87 | + // Read and check the BGP message Length field | ||
88 | + // | ||
89 | + int length = buf.readUnsignedShort(); | ||
90 | + if ((length < BgpConstants.BGP_HEADER_LENGTH) || | ||
91 | + (length > BgpConstants.BGP_MESSAGE_MAX_LENGTH)) { | ||
92 | + log.debug("BGP RX Error: invalid Length field {}. " + | ||
93 | + "Must be between {} and {}", | ||
94 | + length, | ||
95 | + BgpConstants.BGP_HEADER_LENGTH, | ||
96 | + BgpConstants.BGP_MESSAGE_MAX_LENGTH); | ||
97 | + // | ||
98 | + // ERROR: Bad Message Length | ||
99 | + // | ||
100 | + // Send NOTIFICATION and close the connection | ||
101 | + ChannelBuffer txMessage = | ||
102 | + bgpSession.prepareBgpNotificationBadMessageLength(length); | ||
103 | + ctx.getChannel().write(txMessage); | ||
104 | + bgpSession.closeChannel(ctx); | ||
105 | + return null; | ||
106 | + } | ||
107 | + | ||
108 | + // | ||
109 | + // Test whether the rest of the message is received: | ||
110 | + // So far we have read the Marker (16 octets) and the | ||
111 | + // Length (2 octets) fields. | ||
112 | + // | ||
113 | + int remainingMessageLen = | ||
114 | + length - BgpConstants.BGP_HEADER_MARKER_LENGTH - 2; | ||
115 | + if (buf.readableBytes() < remainingMessageLen) { | ||
116 | + // No enough data received | ||
117 | + buf.resetReaderIndex(); | ||
118 | + return null; | ||
119 | + } | ||
120 | + | ||
121 | + // | ||
122 | + // Read the BGP message Type field, and process based on that type | ||
123 | + // | ||
124 | + int type = buf.readUnsignedByte(); | ||
125 | + remainingMessageLen--; // Adjust after reading the type | ||
126 | + ChannelBuffer message = buf.readBytes(remainingMessageLen); | ||
127 | + | ||
128 | + // | ||
129 | + // Process the remaining of the message based on the message type | ||
130 | + // | ||
131 | + switch (type) { | ||
132 | + case BgpConstants.BGP_TYPE_OPEN: | ||
133 | + bgpSession.processBgpOpen(ctx, message); | ||
134 | + break; | ||
135 | + case BgpConstants.BGP_TYPE_UPDATE: | ||
136 | + bgpSession.processBgpUpdate(ctx, message); | ||
137 | + break; | ||
138 | + case BgpConstants.BGP_TYPE_NOTIFICATION: | ||
139 | + bgpSession.processBgpNotification(ctx, message); | ||
140 | + break; | ||
141 | + case BgpConstants.BGP_TYPE_KEEPALIVE: | ||
142 | + bgpSession.processBgpKeepalive(ctx, message); | ||
143 | + break; | ||
144 | + default: | ||
145 | + // | ||
146 | + // ERROR: Bad Message Type | ||
147 | + // | ||
148 | + // Send NOTIFICATION and close the connection | ||
149 | + int errorCode = MessageHeaderError.ERROR_CODE; | ||
150 | + int errorSubcode = MessageHeaderError.BAD_MESSAGE_TYPE; | ||
151 | + ChannelBuffer data = ChannelBuffers.buffer(1); | ||
152 | + data.writeByte(type); | ||
153 | + ChannelBuffer txMessage = | ||
154 | + bgpSession.prepareBgpNotification(errorCode, errorSubcode, | ||
155 | + data); | ||
156 | + ctx.getChannel().write(txMessage); | ||
157 | + bgpSession.closeChannel(ctx); | ||
158 | + return null; | ||
159 | + } | ||
160 | + return null; | ||
161 | + } | ||
162 | +} |
1 | +package org.onlab.onos.sdnip.bgp; | ||
2 | + | ||
3 | +import static com.google.common.base.Preconditions.checkNotNull; | ||
4 | + | ||
5 | +import java.util.ArrayList; | ||
6 | +import java.util.Objects; | ||
7 | + | ||
8 | +import org.onlab.onos.sdnip.RouteEntry; | ||
9 | +import org.onlab.packet.IpAddress; | ||
10 | +import org.onlab.packet.IpPrefix; | ||
11 | + | ||
12 | +import com.google.common.base.MoreObjects; | ||
13 | + | ||
14 | +/** | ||
15 | + * Represents a route in BGP. | ||
16 | + */ | ||
17 | +public class BgpRouteEntry extends RouteEntry { | ||
18 | + private final BgpSession bgpSession; // The BGP Session the route was | ||
19 | + // received on | ||
20 | + private final byte origin; // Route ORIGIN: IGP, EGP, INCOMPLETE | ||
21 | + private final AsPath asPath; // The AS Path | ||
22 | + private final long localPref; // The local preference for the route | ||
23 | + private long multiExitDisc = | ||
24 | + BgpConstants.Update.MultiExitDisc.LOWEST_MULTI_EXIT_DISC; | ||
25 | + | ||
26 | + /** | ||
27 | + * Class constructor. | ||
28 | + * | ||
29 | + * @param bgpSession the BGP Session the route was received on | ||
30 | + * @param prefix the prefix of the route | ||
31 | + * @param nextHop the next hop of the route | ||
32 | + * @param origin the route origin: 0=IGP, 1=EGP, 2=INCOMPLETE | ||
33 | + * @param asPath the AS path | ||
34 | + * @param localPref the route local preference | ||
35 | + */ | ||
36 | + public BgpRouteEntry(BgpSession bgpSession, IpPrefix prefix, | ||
37 | + IpAddress nextHop, byte origin, | ||
38 | + BgpRouteEntry.AsPath asPath, long localPref) { | ||
39 | + super(prefix, nextHop); | ||
40 | + this.bgpSession = checkNotNull(bgpSession); | ||
41 | + this.origin = origin; | ||
42 | + this.asPath = checkNotNull(asPath); | ||
43 | + this.localPref = localPref; | ||
44 | + } | ||
45 | + | ||
46 | + /** | ||
47 | + * Gets the BGP Session the route was received on. | ||
48 | + * | ||
49 | + * @return the BGP Session the route was received on | ||
50 | + */ | ||
51 | + public BgpSession getBgpSession() { | ||
52 | + return bgpSession; | ||
53 | + } | ||
54 | + | ||
55 | + /** | ||
56 | + * Gets the route origin: 0=IGP, 1=EGP, 2=INCOMPLETE. | ||
57 | + * | ||
58 | + * @return the route origin: 0=IGP, 1=EGP, 2=INCOMPLETE | ||
59 | + */ | ||
60 | + public byte getOrigin() { | ||
61 | + return origin; | ||
62 | + } | ||
63 | + | ||
64 | + /** | ||
65 | + * Gets the route AS path. | ||
66 | + * | ||
67 | + * @return the route AS path | ||
68 | + */ | ||
69 | + public BgpRouteEntry.AsPath getAsPath() { | ||
70 | + return asPath; | ||
71 | + } | ||
72 | + | ||
73 | + /** | ||
74 | + * Gets the route local preference. | ||
75 | + * | ||
76 | + * @return the route local preference | ||
77 | + */ | ||
78 | + public long getLocalPref() { | ||
79 | + return localPref; | ||
80 | + } | ||
81 | + | ||
82 | + /** | ||
83 | + * Gets the route MED (Multi-Exit Discriminator). | ||
84 | + * | ||
85 | + * @return the route MED (Multi-Exit Discriminator) | ||
86 | + */ | ||
87 | + public long getMultiExitDisc() { | ||
88 | + return multiExitDisc; | ||
89 | + } | ||
90 | + | ||
91 | + /** | ||
92 | + * Sets the route MED (Multi-Exit Discriminator). | ||
93 | + * | ||
94 | + * @param multiExitDisc the route MED (Multi-Exit Discriminator) to set | ||
95 | + */ | ||
96 | + void setMultiExitDisc(long multiExitDisc) { | ||
97 | + this.multiExitDisc = multiExitDisc; | ||
98 | + } | ||
99 | + | ||
100 | + /** | ||
101 | + * Tests whether the route is originated from the local AS. | ||
102 | + * <p/> | ||
103 | + * The route is considered originated from the local AS if the AS Path | ||
104 | + * is empty or if it begins with an AS_SET. | ||
105 | + * | ||
106 | + * @return true if the route is originated from the local AS, otherwise | ||
107 | + * false | ||
108 | + */ | ||
109 | + boolean isLocalRoute() { | ||
110 | + if (asPath.getPathSegments().isEmpty()) { | ||
111 | + return true; | ||
112 | + } | ||
113 | + PathSegment firstPathSegment = asPath.getPathSegments().get(0); | ||
114 | + if (firstPathSegment.getType() == BgpConstants.Update.AsPath.AS_SET) { | ||
115 | + return true; | ||
116 | + } | ||
117 | + return false; | ||
118 | + } | ||
119 | + | ||
120 | + /** | ||
121 | + * Gets the BGP Neighbor AS number the route was received from. | ||
122 | + * <p/> | ||
123 | + * If the router is originated from the local AS, the return value is | ||
124 | + * zero (BGP_AS_0). | ||
125 | + * | ||
126 | + * @return the BGP Neighbor AS number the route was received from. | ||
127 | + */ | ||
128 | + long getNeighborAs() { | ||
129 | + if (isLocalRoute()) { | ||
130 | + return BgpConstants.BGP_AS_0; | ||
131 | + } | ||
132 | + PathSegment firstPathSegment = asPath.getPathSegments().get(0); | ||
133 | + if (firstPathSegment.getSegmentAsNumbers().isEmpty()) { | ||
134 | + // TODO: Shouldn't happen. Should check during the parsing. | ||
135 | + return BgpConstants.BGP_AS_0; | ||
136 | + } | ||
137 | + return firstPathSegment.getSegmentAsNumbers().get(0); | ||
138 | + } | ||
139 | + | ||
140 | + /** | ||
141 | + * Tests whether the AS Path contains a loop. | ||
142 | + * <p/> | ||
143 | + * The test is done by comparing whether the AS Path contains the | ||
144 | + * local AS number. | ||
145 | + * | ||
146 | + * @param localAsNumber the local AS number to compare against | ||
147 | + * @return true if the AS Path contains a loop, otherwise false | ||
148 | + */ | ||
149 | + boolean hasAsPathLoop(long localAsNumber) { | ||
150 | + for (PathSegment pathSegment : asPath.getPathSegments()) { | ||
151 | + for (Long asNumber : pathSegment.getSegmentAsNumbers()) { | ||
152 | + if (asNumber.equals(localAsNumber)) { | ||
153 | + return true; | ||
154 | + } | ||
155 | + } | ||
156 | + } | ||
157 | + return false; | ||
158 | + } | ||
159 | + | ||
160 | + /** | ||
161 | + * Compares this BGP route against another BGP route by using the | ||
162 | + * BGP Decision Process. | ||
163 | + * <p/> | ||
164 | + * NOTE: The comparison needs to be performed only on routes that have | ||
165 | + * same IP Prefix. | ||
166 | + * | ||
167 | + * @param other the BGP route to compare against | ||
168 | + * @return true if this BGP route is better than the other BGP route | ||
169 | + * or same, otherwise false | ||
170 | + */ | ||
171 | + boolean isBetterThan(BgpRouteEntry other) { | ||
172 | + if (this == other) { | ||
173 | + return true; // Return true if same route | ||
174 | + } | ||
175 | + | ||
176 | + // Compare the LOCAL_PREF values: larger is better | ||
177 | + if (getLocalPref() != other.getLocalPref()) { | ||
178 | + return (getLocalPref() > other.getLocalPref()); | ||
179 | + } | ||
180 | + | ||
181 | + // Compare the AS number in the path: smaller is better | ||
182 | + if (getAsPath().getAsPathLength() != | ||
183 | + other.getAsPath().getAsPathLength()) { | ||
184 | + return getAsPath().getAsPathLength() < | ||
185 | + other.getAsPath().getAsPathLength(); | ||
186 | + } | ||
187 | + | ||
188 | + // Compare the Origin number: lower is better | ||
189 | + if (getOrigin() != other.getOrigin()) { | ||
190 | + return (getOrigin() < other.getOrigin()); | ||
191 | + } | ||
192 | + | ||
193 | + // Compare the MED if the neighbor AS is same: larger is better | ||
194 | + medLabel: { | ||
195 | + boolean thisIsLocalRoute = isLocalRoute(); | ||
196 | + if (thisIsLocalRoute != other.isLocalRoute()) { | ||
197 | + break medLabel; // AS number is different | ||
198 | + } | ||
199 | + if (!thisIsLocalRoute) { | ||
200 | + long thisNeighborAs = getNeighborAs(); | ||
201 | + if (thisNeighborAs != other.getNeighborAs()) { | ||
202 | + break medLabel; // AS number is different | ||
203 | + } | ||
204 | + if (thisNeighborAs == BgpConstants.BGP_AS_0) { | ||
205 | + break medLabel; // Invalid AS number | ||
206 | + } | ||
207 | + } | ||
208 | + | ||
209 | + // Compare the MED | ||
210 | + if (getMultiExitDisc() != other.getMultiExitDisc()) { | ||
211 | + return (getMultiExitDisc() > other.getMultiExitDisc()); | ||
212 | + } | ||
213 | + } | ||
214 | + | ||
215 | + // Compare the peer BGP ID: lower is better | ||
216 | + IpAddress peerBgpId = getBgpSession().getRemoteBgpId(); | ||
217 | + IpAddress otherPeerBgpId = other.getBgpSession().getRemoteBgpId(); | ||
218 | + if (!peerBgpId.equals(otherPeerBgpId)) { | ||
219 | + return (peerBgpId.compareTo(otherPeerBgpId) < 0); | ||
220 | + } | ||
221 | + | ||
222 | + // Compare the peer BGP address: lower is better | ||
223 | + IpAddress peerAddress = getBgpSession().getRemoteIp4Address(); | ||
224 | + IpAddress otherPeerAddress = | ||
225 | + other.getBgpSession().getRemoteIp4Address(); | ||
226 | + if (!peerAddress.equals(otherPeerAddress)) { | ||
227 | + return (peerAddress.compareTo(otherPeerAddress) < 0); | ||
228 | + } | ||
229 | + | ||
230 | + return true; // Routes are same. Shouldn't happen? | ||
231 | + } | ||
232 | + | ||
233 | + /** | ||
234 | + * A class to represent AS Path Segment. | ||
235 | + */ | ||
236 | + public static class PathSegment { | ||
237 | + private final byte type; // Segment type: AS_SET, AS_SEQUENCE | ||
238 | + private final ArrayList<Long> segmentAsNumbers; // Segment AS numbers | ||
239 | + | ||
240 | + /** | ||
241 | + * Constructor. | ||
242 | + * | ||
243 | + * @param type the Path Segment Type: 1=AS_SET, 2=AS_SEQUENCE | ||
244 | + * @param segmentAsNumbers the Segment AS numbers | ||
245 | + */ | ||
246 | + PathSegment(byte type, ArrayList<Long> segmentAsNumbers) { | ||
247 | + this.type = type; | ||
248 | + this.segmentAsNumbers = checkNotNull(segmentAsNumbers); | ||
249 | + } | ||
250 | + | ||
251 | + /** | ||
252 | + * Gets the Path Segment Type: AS_SET, AS_SEQUENCE. | ||
253 | + * | ||
254 | + * @return the Path Segment Type: AS_SET, AS_SEQUENCE | ||
255 | + */ | ||
256 | + public byte getType() { | ||
257 | + return type; | ||
258 | + } | ||
259 | + | ||
260 | + /** | ||
261 | + * Gets the Path Segment AS Numbers. | ||
262 | + * | ||
263 | + * @return the Path Segment AS Numbers | ||
264 | + */ | ||
265 | + public ArrayList<Long> getSegmentAsNumbers() { | ||
266 | + return segmentAsNumbers; | ||
267 | + } | ||
268 | + | ||
269 | + @Override | ||
270 | + public boolean equals(Object other) { | ||
271 | + if (this == other) { | ||
272 | + return true; | ||
273 | + } | ||
274 | + | ||
275 | + if (!(other instanceof PathSegment)) { | ||
276 | + return false; | ||
277 | + } | ||
278 | + | ||
279 | + PathSegment otherPathSegment = (PathSegment) other; | ||
280 | + return Objects.equals(this.type, otherPathSegment.type) && | ||
281 | + Objects.equals(this.segmentAsNumbers, | ||
282 | + otherPathSegment.segmentAsNumbers); | ||
283 | + } | ||
284 | + | ||
285 | + @Override | ||
286 | + public int hashCode() { | ||
287 | + return Objects.hash(type, segmentAsNumbers); | ||
288 | + } | ||
289 | + | ||
290 | + @Override | ||
291 | + public String toString() { | ||
292 | + return MoreObjects.toStringHelper(getClass()) | ||
293 | + .add("type", this.type) | ||
294 | + .add("segmentAsNumbers", this.segmentAsNumbers) | ||
295 | + .toString(); | ||
296 | + } | ||
297 | + } | ||
298 | + | ||
299 | + /** | ||
300 | + * A class to represent AS Path. | ||
301 | + */ | ||
302 | + public static class AsPath { | ||
303 | + private final ArrayList<PathSegment> pathSegments; | ||
304 | + private final int asPathLength; // Precomputed AS Path Length | ||
305 | + | ||
306 | + /** | ||
307 | + * Constructor. | ||
308 | + * | ||
309 | + * @param pathSegments the Path Segments of the Path | ||
310 | + */ | ||
311 | + AsPath(ArrayList<PathSegment> pathSegments) { | ||
312 | + this.pathSegments = checkNotNull(pathSegments); | ||
313 | + | ||
314 | + // | ||
315 | + // Precompute the AS Path Length: | ||
316 | + // - AS_SET counts as 1 | ||
317 | + // | ||
318 | + int pl = 0; | ||
319 | + for (PathSegment pathSegment : pathSegments) { | ||
320 | + if (pathSegment.getType() == | ||
321 | + BgpConstants.Update.AsPath.AS_SET) { | ||
322 | + pl++; | ||
323 | + continue; | ||
324 | + } | ||
325 | + pl += pathSegment.getSegmentAsNumbers().size(); | ||
326 | + } | ||
327 | + asPathLength = pl; | ||
328 | + } | ||
329 | + | ||
330 | + /** | ||
331 | + * Gets the AS Path Segments. | ||
332 | + * | ||
333 | + * @return the AS Path Segments | ||
334 | + */ | ||
335 | + public ArrayList<PathSegment> getPathSegments() { | ||
336 | + return pathSegments; | ||
337 | + } | ||
338 | + | ||
339 | + /** | ||
340 | + * Gets the AS Path Length as considered by the BGP Decision Process. | ||
341 | + * | ||
342 | + * @return the AS Path Length as considered by the BGP Decision Process | ||
343 | + */ | ||
344 | + int getAsPathLength() { | ||
345 | + return asPathLength; | ||
346 | + } | ||
347 | + | ||
348 | + @Override | ||
349 | + public boolean equals(Object other) { | ||
350 | + if (this == other) { | ||
351 | + return true; | ||
352 | + } | ||
353 | + | ||
354 | + if (!(other instanceof AsPath)) { | ||
355 | + return false; | ||
356 | + } | ||
357 | + | ||
358 | + AsPath otherAsPath = (AsPath) other; | ||
359 | + return Objects.equals(this.pathSegments, otherAsPath.pathSegments); | ||
360 | + } | ||
361 | + | ||
362 | + @Override | ||
363 | + public int hashCode() { | ||
364 | + return Objects.hash(pathSegments); | ||
365 | + } | ||
366 | + | ||
367 | + @Override | ||
368 | + public String toString() { | ||
369 | + return MoreObjects.toStringHelper(getClass()) | ||
370 | + .add("pathSegments", this.pathSegments) | ||
371 | + .toString(); | ||
372 | + } | ||
373 | + } | ||
374 | + | ||
375 | + /** | ||
376 | + * Compares whether two objects are equal. | ||
377 | + * <p/> | ||
378 | + * NOTE: The bgpSession field is excluded from the comparison. | ||
379 | + * | ||
380 | + * @return true if the two objects are equal, otherwise false. | ||
381 | + */ | ||
382 | + @Override | ||
383 | + public boolean equals(Object other) { | ||
384 | + if (this == other) { | ||
385 | + return true; | ||
386 | + } | ||
387 | + | ||
388 | + // | ||
389 | + // NOTE: Subclasses are considered as change of identity, hence | ||
390 | + // equals() will return false if the class type doesn't match. | ||
391 | + // | ||
392 | + if (other == null || getClass() != other.getClass()) { | ||
393 | + return false; | ||
394 | + } | ||
395 | + | ||
396 | + if (!super.equals(other)) { | ||
397 | + return false; | ||
398 | + } | ||
399 | + | ||
400 | + // NOTE: The bgpSession field is excluded from the comparison | ||
401 | + BgpRouteEntry otherRoute = (BgpRouteEntry) other; | ||
402 | + return (this.origin == otherRoute.origin) && | ||
403 | + Objects.equals(this.asPath, otherRoute.asPath) && | ||
404 | + (this.localPref == otherRoute.localPref) && | ||
405 | + (this.multiExitDisc == otherRoute.multiExitDisc); | ||
406 | + } | ||
407 | + | ||
408 | + /** | ||
409 | + * Computes the hash code. | ||
410 | + * <p/> | ||
411 | + * NOTE: We return the base class hash code to avoid expensive computation | ||
412 | + * | ||
413 | + * @return the object hash code | ||
414 | + */ | ||
415 | + @Override | ||
416 | + public int hashCode() { | ||
417 | + return super.hashCode(); | ||
418 | + } | ||
419 | + | ||
420 | + @Override | ||
421 | + public String toString() { | ||
422 | + return MoreObjects.toStringHelper(getClass()) | ||
423 | + .add("prefix", prefix()) | ||
424 | + .add("nextHop", nextHop()) | ||
425 | + .add("bgpId", bgpSession.getRemoteBgpId()) | ||
426 | + .add("origin", origin) | ||
427 | + .add("asPath", asPath) | ||
428 | + .add("localPref", localPref) | ||
429 | + .add("multiExitDisc", multiExitDisc) | ||
430 | + .toString(); | ||
431 | + } | ||
432 | +} |
1 | +package org.onlab.onos.sdnip.bgp; | ||
2 | + | ||
3 | +import java.net.InetAddress; | ||
4 | +import java.net.InetSocketAddress; | ||
5 | +import java.net.SocketAddress; | ||
6 | +import java.util.ArrayList; | ||
7 | +import java.util.Collection; | ||
8 | +import java.util.Collections; | ||
9 | +import java.util.HashMap; | ||
10 | +import java.util.Map; | ||
11 | +import java.util.concurrent.ConcurrentHashMap; | ||
12 | +import java.util.concurrent.ConcurrentMap; | ||
13 | +import java.util.concurrent.TimeUnit; | ||
14 | + | ||
15 | +import org.apache.commons.lang3.tuple.Pair; | ||
16 | +import org.jboss.netty.buffer.ChannelBuffer; | ||
17 | +import org.jboss.netty.buffer.ChannelBuffers; | ||
18 | +import org.jboss.netty.channel.ChannelHandlerContext; | ||
19 | +import org.jboss.netty.channel.ChannelStateEvent; | ||
20 | +import org.jboss.netty.channel.SimpleChannelHandler; | ||
21 | +import org.jboss.netty.util.HashedWheelTimer; | ||
22 | +import org.jboss.netty.util.Timeout; | ||
23 | +import org.jboss.netty.util.Timer; | ||
24 | +import org.jboss.netty.util.TimerTask; | ||
25 | +import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications; | ||
26 | +import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications.HoldTimerExpired; | ||
27 | +import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications.MessageHeaderError; | ||
28 | +import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications.OpenMessageError; | ||
29 | +import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications.UpdateMessageError; | ||
30 | +import org.onlab.packet.IpAddress; | ||
31 | +import org.onlab.packet.IpPrefix; | ||
32 | +import org.slf4j.Logger; | ||
33 | +import org.slf4j.LoggerFactory; | ||
34 | + | ||
35 | +/** | ||
36 | + * Class for handling the BGP peer sessions. | ||
37 | + * There is one instance per each BGP peer session. | ||
38 | + */ | ||
39 | +public class BgpSession extends SimpleChannelHandler { | ||
40 | + private static final Logger log = | ||
41 | + LoggerFactory.getLogger(BgpSession.class); | ||
42 | + | ||
43 | + private final BgpSessionManager bgpSessionManager; | ||
44 | + | ||
45 | + // Local flag to indicate the session is closed. | ||
46 | + // It is used to avoid the Netty's asynchronous closing of a channel. | ||
47 | + private boolean isClosed = false; | ||
48 | + | ||
49 | + private SocketAddress remoteAddress; // Peer IP addr/port | ||
50 | + private IpAddress remoteIp4Address; // Peer IPv4 address | ||
51 | + private int remoteBgpVersion; // 1 octet | ||
52 | + private long remoteAs; // 2 octets | ||
53 | + private long remoteHoldtime; // 2 octets | ||
54 | + private IpAddress remoteBgpId; // 4 octets -> IPv4 address | ||
55 | + // | ||
56 | + private SocketAddress localAddress; // Local IP addr/port | ||
57 | + private IpAddress localIp4Address; // Local IPv4 address | ||
58 | + private int localBgpVersion; // 1 octet | ||
59 | + private long localAs; // 2 octets | ||
60 | + private long localHoldtime; // 2 octets | ||
61 | + private IpAddress localBgpId; // 4 octets -> IPv4 address | ||
62 | + // | ||
63 | + private long localKeepaliveInterval; // Keepalive interval | ||
64 | + | ||
65 | + // Timers state | ||
66 | + private Timer timer = new HashedWheelTimer(); | ||
67 | + private volatile Timeout keepaliveTimeout; // Periodic KEEPALIVE | ||
68 | + private volatile Timeout sessionTimeout; // Session timeout | ||
69 | + | ||
70 | + // BGP RIB-IN routing entries from this peer | ||
71 | + private ConcurrentMap<IpPrefix, BgpRouteEntry> bgpRibIn = | ||
72 | + new ConcurrentHashMap<>(); | ||
73 | + | ||
74 | + /** | ||
75 | + * Constructor for a given BGP Session Manager. | ||
76 | + * | ||
77 | + * @param bgpSessionManager the BGP Session Manager to use | ||
78 | + */ | ||
79 | + BgpSession(BgpSessionManager bgpSessionManager) { | ||
80 | + this.bgpSessionManager = bgpSessionManager; | ||
81 | + } | ||
82 | + | ||
83 | + /** | ||
84 | + * Gets the BGP RIB-IN routing entries. | ||
85 | + * | ||
86 | + * @return the BGP RIB-IN routing entries | ||
87 | + */ | ||
88 | + public Collection<BgpRouteEntry> getBgpRibIn() { | ||
89 | + return bgpRibIn.values(); | ||
90 | + } | ||
91 | + | ||
92 | + /** | ||
93 | + * Finds a BGP routing entry in the BGP RIB-IN. | ||
94 | + * | ||
95 | + * @param prefix the prefix of the route to search for | ||
96 | + * @return the BGP routing entry if found, otherwise null | ||
97 | + */ | ||
98 | + public BgpRouteEntry findBgpRouteEntry(IpPrefix prefix) { | ||
99 | + return bgpRibIn.get(prefix); | ||
100 | + } | ||
101 | + | ||
102 | + /** | ||
103 | + * Gets the BGP session remote address. | ||
104 | + * | ||
105 | + * @return the BGP session remote address | ||
106 | + */ | ||
107 | + public SocketAddress getRemoteAddress() { | ||
108 | + return remoteAddress; | ||
109 | + } | ||
110 | + | ||
111 | + /** | ||
112 | + * Gets the BGP session remote IPv4 address. | ||
113 | + * | ||
114 | + * @return the BGP session remote IPv4 address | ||
115 | + */ | ||
116 | + public IpAddress getRemoteIp4Address() { | ||
117 | + return remoteIp4Address; | ||
118 | + } | ||
119 | + | ||
120 | + /** | ||
121 | + * Gets the BGP session remote BGP version. | ||
122 | + * | ||
123 | + * @return the BGP session remote BGP version | ||
124 | + */ | ||
125 | + public int getRemoteBgpVersion() { | ||
126 | + return remoteBgpVersion; | ||
127 | + } | ||
128 | + | ||
129 | + /** | ||
130 | + * Gets the BGP session remote AS number. | ||
131 | + * | ||
132 | + * @return the BGP session remote AS number | ||
133 | + */ | ||
134 | + public long getRemoteAs() { | ||
135 | + return remoteAs; | ||
136 | + } | ||
137 | + | ||
138 | + /** | ||
139 | + * Gets the BGP session remote Holdtime. | ||
140 | + * | ||
141 | + * @return the BGP session remote Holdtime | ||
142 | + */ | ||
143 | + public long getRemoteHoldtime() { | ||
144 | + return remoteHoldtime; | ||
145 | + } | ||
146 | + | ||
147 | + /** | ||
148 | + * Gets the BGP session remote BGP Identifier as an IPv4 address. | ||
149 | + * | ||
150 | + * @return the BGP session remote BGP Identifier as an IPv4 address | ||
151 | + */ | ||
152 | + public IpAddress getRemoteBgpId() { | ||
153 | + return remoteBgpId; | ||
154 | + } | ||
155 | + | ||
156 | + /** | ||
157 | + * Gets the BGP session local address. | ||
158 | + * | ||
159 | + * @return the BGP session local address | ||
160 | + */ | ||
161 | + public SocketAddress getLocalAddress() { | ||
162 | + return localAddress; | ||
163 | + } | ||
164 | + | ||
165 | + /** | ||
166 | + * Gets the BGP session local BGP version. | ||
167 | + * | ||
168 | + * @return the BGP session local BGP version | ||
169 | + */ | ||
170 | + public int getLocalBgpVersion() { | ||
171 | + return localBgpVersion; | ||
172 | + } | ||
173 | + | ||
174 | + /** | ||
175 | + * Gets the BGP session local AS number. | ||
176 | + * | ||
177 | + * @return the BGP session local AS number | ||
178 | + */ | ||
179 | + public long getLocalAs() { | ||
180 | + return localAs; | ||
181 | + } | ||
182 | + | ||
183 | + /** | ||
184 | + * Gets the BGP session local Holdtime. | ||
185 | + * | ||
186 | + * @return the BGP session local Holdtime | ||
187 | + */ | ||
188 | + public long getLocalHoldtime() { | ||
189 | + return localHoldtime; | ||
190 | + } | ||
191 | + | ||
192 | + /** | ||
193 | + * Gets the BGP session local BGP Identifier as an IPv4 address. | ||
194 | + * | ||
195 | + * @return the BGP session local BGP Identifier as an IPv4 address | ||
196 | + */ | ||
197 | + public IpAddress getLocalBgpId() { | ||
198 | + return localBgpId; | ||
199 | + } | ||
200 | + | ||
201 | + /** | ||
202 | + * Tests whether the session is closed. | ||
203 | + * <p/> | ||
204 | + * NOTE: We use this method to avoid the Netty's asynchronous closing | ||
205 | + * of a channel. | ||
206 | + * | ||
207 | + * @param return true if the session is closed | ||
208 | + */ | ||
209 | + boolean isClosed() { | ||
210 | + return isClosed; | ||
211 | + } | ||
212 | + | ||
213 | + /** | ||
214 | + * Closes the channel. | ||
215 | + * | ||
216 | + * @param ctx the Channel Handler Context | ||
217 | + */ | ||
218 | + void closeChannel(ChannelHandlerContext ctx) { | ||
219 | + isClosed = true; | ||
220 | + timer.stop(); | ||
221 | + ctx.getChannel().close(); | ||
222 | + } | ||
223 | + | ||
224 | + @Override | ||
225 | + public void channelConnected(ChannelHandlerContext ctx, | ||
226 | + ChannelStateEvent channelEvent) { | ||
227 | + localAddress = ctx.getChannel().getLocalAddress(); | ||
228 | + remoteAddress = ctx.getChannel().getRemoteAddress(); | ||
229 | + | ||
230 | + // Assign the local and remote IPv4 addresses | ||
231 | + InetAddress inetAddr; | ||
232 | + if (localAddress instanceof InetSocketAddress) { | ||
233 | + inetAddr = ((InetSocketAddress) localAddress).getAddress(); | ||
234 | + localIp4Address = IpAddress.valueOf(inetAddr.getAddress()); | ||
235 | + } | ||
236 | + if (remoteAddress instanceof InetSocketAddress) { | ||
237 | + inetAddr = ((InetSocketAddress) remoteAddress).getAddress(); | ||
238 | + remoteIp4Address = IpAddress.valueOf(inetAddr.getAddress()); | ||
239 | + } | ||
240 | + | ||
241 | + log.debug("BGP Session Connected from {} on {}", | ||
242 | + remoteAddress, localAddress); | ||
243 | + if (!bgpSessionManager.peerConnected(this)) { | ||
244 | + log.debug("Cannot setup BGP Session Connection from {}. Closing...", | ||
245 | + remoteAddress); | ||
246 | + ctx.getChannel().close(); | ||
247 | + } | ||
248 | + } | ||
249 | + | ||
250 | + @Override | ||
251 | + public void channelDisconnected(ChannelHandlerContext ctx, | ||
252 | + ChannelStateEvent channelEvent) { | ||
253 | + log.debug("BGP Session Disconnected from {} on {}", | ||
254 | + ctx.getChannel().getRemoteAddress(), | ||
255 | + ctx.getChannel().getLocalAddress()); | ||
256 | + | ||
257 | + // | ||
258 | + // Withdraw the routes advertised by this BGP peer | ||
259 | + // | ||
260 | + // NOTE: We must initialize the RIB-IN before propagating the withdraws | ||
261 | + // for further processing. Otherwise, the BGP Decision Process | ||
262 | + // will use those routes again. | ||
263 | + // | ||
264 | + Collection<BgpRouteEntry> deletedRoutes = bgpRibIn.values(); | ||
265 | + bgpRibIn = new ConcurrentHashMap<>(); | ||
266 | + | ||
267 | + // Push the updates to the BGP Merged RIB | ||
268 | + BgpSessionManager.BgpRouteSelector bgpRouteSelector = | ||
269 | + bgpSessionManager.getBgpRouteSelector(); | ||
270 | + Collection<BgpRouteEntry> addedRoutes = Collections.emptyList(); | ||
271 | + bgpRouteSelector.routeUpdates(this, addedRoutes, deletedRoutes); | ||
272 | + | ||
273 | + bgpSessionManager.peerDisconnected(this); | ||
274 | + } | ||
275 | + | ||
276 | + /** | ||
277 | + * Processes BGP OPEN message. | ||
278 | + * | ||
279 | + * @param ctx the Channel Handler Context | ||
280 | + * @param message the message to process | ||
281 | + */ | ||
282 | + void processBgpOpen(ChannelHandlerContext ctx, ChannelBuffer message) { | ||
283 | + int minLength = | ||
284 | + BgpConstants.BGP_OPEN_MIN_LENGTH - BgpConstants.BGP_HEADER_LENGTH; | ||
285 | + if (message.readableBytes() < minLength) { | ||
286 | + log.debug("BGP RX OPEN Error from {}: " + | ||
287 | + "Message length {} too short. Must be at least {}", | ||
288 | + remoteAddress, message.readableBytes(), minLength); | ||
289 | + // | ||
290 | + // ERROR: Bad Message Length | ||
291 | + // | ||
292 | + // Send NOTIFICATION and close the connection | ||
293 | + ChannelBuffer txMessage = prepareBgpNotificationBadMessageLength( | ||
294 | + message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH); | ||
295 | + ctx.getChannel().write(txMessage); | ||
296 | + closeChannel(ctx); | ||
297 | + return; | ||
298 | + } | ||
299 | + | ||
300 | + // | ||
301 | + // Parse the OPEN message | ||
302 | + // | ||
303 | + // Remote BGP version | ||
304 | + remoteBgpVersion = message.readUnsignedByte(); | ||
305 | + if (remoteBgpVersion != BgpConstants.BGP_VERSION) { | ||
306 | + log.debug("BGP RX OPEN Error from {}: " + | ||
307 | + "Unsupported BGP version {}. Should be {}", | ||
308 | + remoteAddress, remoteBgpVersion, | ||
309 | + BgpConstants.BGP_VERSION); | ||
310 | + // | ||
311 | + // ERROR: Unsupported Version Number | ||
312 | + // | ||
313 | + // Send NOTIFICATION and close the connection | ||
314 | + int errorCode = OpenMessageError.ERROR_CODE; | ||
315 | + int errorSubcode = OpenMessageError.UNSUPPORTED_VERSION_NUMBER; | ||
316 | + ChannelBuffer data = ChannelBuffers.buffer(2); | ||
317 | + data.writeShort(BgpConstants.BGP_VERSION); | ||
318 | + ChannelBuffer txMessage = | ||
319 | + prepareBgpNotification(errorCode, errorSubcode, data); | ||
320 | + ctx.getChannel().write(txMessage); | ||
321 | + closeChannel(ctx); | ||
322 | + return; | ||
323 | + } | ||
324 | + | ||
325 | + // Remote AS number | ||
326 | + remoteAs = message.readUnsignedShort(); | ||
327 | + // | ||
328 | + // Verify that the AS number is same for all other BGP Sessions | ||
329 | + // NOTE: This check applies only for our use-case where all BGP | ||
330 | + // sessions are iBGP. | ||
331 | + // | ||
332 | + for (BgpSession bgpSession : bgpSessionManager.getBgpSessions()) { | ||
333 | + if (remoteAs != bgpSession.getRemoteAs()) { | ||
334 | + log.debug("BGP RX OPEN Error from {}: Bad Peer AS {}. " + | ||
335 | + "Expected {}", | ||
336 | + remoteAddress, remoteAs, bgpSession.getRemoteAs()); | ||
337 | + // | ||
338 | + // ERROR: Bad Peer AS | ||
339 | + // | ||
340 | + // Send NOTIFICATION and close the connection | ||
341 | + int errorCode = OpenMessageError.ERROR_CODE; | ||
342 | + int errorSubcode = OpenMessageError.BAD_PEER_AS; | ||
343 | + ChannelBuffer txMessage = | ||
344 | + prepareBgpNotification(errorCode, errorSubcode, null); | ||
345 | + ctx.getChannel().write(txMessage); | ||
346 | + closeChannel(ctx); | ||
347 | + return; | ||
348 | + } | ||
349 | + } | ||
350 | + | ||
351 | + // Remote Hold Time | ||
352 | + remoteHoldtime = message.readUnsignedShort(); | ||
353 | + if ((remoteHoldtime != 0) && | ||
354 | + (remoteHoldtime < BgpConstants.BGP_KEEPALIVE_MIN_HOLDTIME)) { | ||
355 | + log.debug("BGP RX OPEN Error from {}: " + | ||
356 | + "Unacceptable Hold Time field {}. " + | ||
357 | + "Should be 0 or at least {}", | ||
358 | + remoteAddress, remoteHoldtime, | ||
359 | + BgpConstants.BGP_KEEPALIVE_MIN_HOLDTIME); | ||
360 | + // | ||
361 | + // ERROR: Unacceptable Hold Time | ||
362 | + // | ||
363 | + // Send NOTIFICATION and close the connection | ||
364 | + int errorCode = OpenMessageError.ERROR_CODE; | ||
365 | + int errorSubcode = OpenMessageError.UNACCEPTABLE_HOLD_TIME; | ||
366 | + ChannelBuffer txMessage = | ||
367 | + prepareBgpNotification(errorCode, errorSubcode, null); | ||
368 | + ctx.getChannel().write(txMessage); | ||
369 | + closeChannel(ctx); | ||
370 | + return; | ||
371 | + } | ||
372 | + | ||
373 | + // Remote BGP Identifier | ||
374 | + remoteBgpId = IpAddress.valueOf((int) message.readUnsignedInt()); | ||
375 | + | ||
376 | + // Optional Parameters | ||
377 | + int optParamLen = message.readUnsignedByte(); | ||
378 | + if (message.readableBytes() < optParamLen) { | ||
379 | + log.debug("BGP RX OPEN Error from {}: " + | ||
380 | + "Invalid Optional Parameter Length field {}. " + | ||
381 | + "Remaining Optional Parameters {}", | ||
382 | + remoteAddress, optParamLen, message.readableBytes()); | ||
383 | + // | ||
384 | + // ERROR: Invalid Optional Parameter Length field: Unspecific | ||
385 | + // | ||
386 | + // Send NOTIFICATION and close the connection | ||
387 | + int errorCode = OpenMessageError.ERROR_CODE; | ||
388 | + int errorSubcode = Notifications.ERROR_SUBCODE_UNSPECIFIC; | ||
389 | + ChannelBuffer txMessage = | ||
390 | + prepareBgpNotification(errorCode, errorSubcode, null); | ||
391 | + ctx.getChannel().write(txMessage); | ||
392 | + closeChannel(ctx); | ||
393 | + return; | ||
394 | + } | ||
395 | + // TODO: Parse the optional parameters (if needed) | ||
396 | + message.readBytes(optParamLen); // NOTE: data ignored | ||
397 | + | ||
398 | + // | ||
399 | + // Copy some of the remote peer's state/setup to the local setup: | ||
400 | + // - BGP version | ||
401 | + // - AS number (NOTE: the peer setup is always iBGP) | ||
402 | + // - Holdtime | ||
403 | + // Also, assign the local BGP ID based on the local setup | ||
404 | + // | ||
405 | + localBgpVersion = remoteBgpVersion; | ||
406 | + localAs = remoteAs; | ||
407 | + localHoldtime = remoteHoldtime; | ||
408 | + localBgpId = bgpSessionManager.getMyBgpId(); | ||
409 | + | ||
410 | + // Set the Keepalive interval | ||
411 | + if (localHoldtime == 0) { | ||
412 | + localKeepaliveInterval = 0; | ||
413 | + } else { | ||
414 | + localKeepaliveInterval = Math.max(localHoldtime / | ||
415 | + BgpConstants.BGP_KEEPALIVE_PER_HOLD_INTERVAL, | ||
416 | + BgpConstants.BGP_KEEPALIVE_MIN_INTERVAL); | ||
417 | + } | ||
418 | + | ||
419 | + log.debug("BGP RX OPEN message from {}: " + | ||
420 | + "BGPv{} AS {} BGP-ID {} Holdtime {}", | ||
421 | + remoteAddress, remoteBgpVersion, remoteAs, | ||
422 | + remoteBgpId, remoteHoldtime); | ||
423 | + | ||
424 | + // Send my OPEN followed by KEEPALIVE | ||
425 | + ChannelBuffer txMessage = prepareBgpOpen(); | ||
426 | + ctx.getChannel().write(txMessage); | ||
427 | + // | ||
428 | + txMessage = prepareBgpKeepalive(); | ||
429 | + ctx.getChannel().write(txMessage); | ||
430 | + | ||
431 | + // Start the KEEPALIVE timer | ||
432 | + restartKeepaliveTimer(ctx); | ||
433 | + | ||
434 | + // Start the Session Timeout timer | ||
435 | + restartSessionTimeoutTimer(ctx); | ||
436 | + } | ||
437 | + | ||
438 | + /** | ||
439 | + * Processes BGP UPDATE message. | ||
440 | + * | ||
441 | + * @param ctx the Channel Handler Context | ||
442 | + * @param message the message to process | ||
443 | + */ | ||
444 | + void processBgpUpdate(ChannelHandlerContext ctx, ChannelBuffer message) { | ||
445 | + Collection<BgpRouteEntry> addedRoutes = null; | ||
446 | + Map<IpPrefix, BgpRouteEntry> deletedRoutes = new HashMap<>(); | ||
447 | + | ||
448 | + int minLength = | ||
449 | + BgpConstants.BGP_UPDATE_MIN_LENGTH - BgpConstants.BGP_HEADER_LENGTH; | ||
450 | + if (message.readableBytes() < minLength) { | ||
451 | + log.debug("BGP RX UPDATE Error from {}: " + | ||
452 | + "Message length {} too short. Must be at least {}", | ||
453 | + remoteAddress, message.readableBytes(), minLength); | ||
454 | + // | ||
455 | + // ERROR: Bad Message Length | ||
456 | + // | ||
457 | + // Send NOTIFICATION and close the connection | ||
458 | + ChannelBuffer txMessage = prepareBgpNotificationBadMessageLength( | ||
459 | + message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH); | ||
460 | + ctx.getChannel().write(txMessage); | ||
461 | + closeChannel(ctx); | ||
462 | + return; | ||
463 | + } | ||
464 | + | ||
465 | + log.debug("BGP RX UPDATE message from {}", remoteAddress); | ||
466 | + | ||
467 | + // | ||
468 | + // Parse the UPDATE message | ||
469 | + // | ||
470 | + | ||
471 | + // | ||
472 | + // Parse the Withdrawn Routes | ||
473 | + // | ||
474 | + int withdrawnRoutesLength = message.readUnsignedShort(); | ||
475 | + if (withdrawnRoutesLength > message.readableBytes()) { | ||
476 | + // ERROR: Malformed Attribute List | ||
477 | + actionsBgpUpdateMalformedAttributeList(ctx); | ||
478 | + return; | ||
479 | + } | ||
480 | + Collection<IpPrefix> withdrawnPrefixes = null; | ||
481 | + try { | ||
482 | + withdrawnPrefixes = parsePackedPrefixes(withdrawnRoutesLength, | ||
483 | + message); | ||
484 | + } catch (BgpParseException e) { | ||
485 | + // ERROR: Invalid Network Field | ||
486 | + log.debug("Exception parsing Withdrawn Prefixes from BGP peer {}: ", | ||
487 | + remoteBgpId, e); | ||
488 | + actionsBgpUpdateInvalidNetworkField(ctx); | ||
489 | + return; | ||
490 | + } | ||
491 | + for (IpPrefix prefix : withdrawnPrefixes) { | ||
492 | + log.debug("BGP RX UPDATE message WITHDRAWN from {}: {}", | ||
493 | + remoteAddress, prefix); | ||
494 | + BgpRouteEntry bgpRouteEntry = bgpRibIn.get(prefix); | ||
495 | + if (bgpRouteEntry != null) { | ||
496 | + deletedRoutes.put(prefix, bgpRouteEntry); | ||
497 | + } | ||
498 | + } | ||
499 | + | ||
500 | + // | ||
501 | + // Parse the Path Attributes | ||
502 | + // | ||
503 | + try { | ||
504 | + addedRoutes = parsePathAttributes(ctx, message); | ||
505 | + } catch (BgpParseException e) { | ||
506 | + log.debug("Exception parsing Path Attributes from BGP peer {}: ", | ||
507 | + remoteBgpId, e); | ||
508 | + // NOTE: The session was already closed, so nothing else to do | ||
509 | + return; | ||
510 | + } | ||
511 | + // Ignore WITHDRAWN routes that are ADDED | ||
512 | + for (BgpRouteEntry bgpRouteEntry : addedRoutes) { | ||
513 | + deletedRoutes.remove(bgpRouteEntry.prefix()); | ||
514 | + } | ||
515 | + | ||
516 | + // Update the BGP RIB-IN | ||
517 | + for (BgpRouteEntry bgpRouteEntry : deletedRoutes.values()) { | ||
518 | + bgpRibIn.remove(bgpRouteEntry.prefix()); | ||
519 | + } | ||
520 | + for (BgpRouteEntry bgpRouteEntry : addedRoutes) { | ||
521 | + bgpRibIn.put(bgpRouteEntry.prefix(), bgpRouteEntry); | ||
522 | + } | ||
523 | + | ||
524 | + // Push the updates to the BGP Merged RIB | ||
525 | + BgpSessionManager.BgpRouteSelector bgpRouteSelector = | ||
526 | + bgpSessionManager.getBgpRouteSelector(); | ||
527 | + bgpRouteSelector.routeUpdates(this, addedRoutes, | ||
528 | + deletedRoutes.values()); | ||
529 | + | ||
530 | + // Start the Session Timeout timer | ||
531 | + restartSessionTimeoutTimer(ctx); | ||
532 | + } | ||
533 | + | ||
534 | + /** | ||
535 | + * Parse BGP Path Attributes from the BGP UPDATE message. | ||
536 | + * | ||
537 | + * @param ctx the Channel Handler Context | ||
538 | + * @param message the message to parse | ||
539 | + * @return a collection of the result BGP Route Entries | ||
540 | + * @throws BgpParseException | ||
541 | + */ | ||
542 | + private Collection<BgpRouteEntry> parsePathAttributes( | ||
543 | + ChannelHandlerContext ctx, | ||
544 | + ChannelBuffer message) | ||
545 | + throws BgpParseException { | ||
546 | + Map<IpPrefix, BgpRouteEntry> addedRoutes = new HashMap<>(); | ||
547 | + | ||
548 | + // | ||
549 | + // Parsed values | ||
550 | + // | ||
551 | + Short origin = -1; // Mandatory | ||
552 | + BgpRouteEntry.AsPath asPath = null; // Mandatory | ||
553 | + IpAddress nextHop = null; // Mandatory | ||
554 | + long multiExitDisc = // Optional | ||
555 | + BgpConstants.Update.MultiExitDisc.LOWEST_MULTI_EXIT_DISC; | ||
556 | + Long localPref = null; // Mandatory | ||
557 | + Long aggregatorAsNumber = null; // Optional: unused | ||
558 | + IpAddress aggregatorIpAddress = null; // Optional: unused | ||
559 | + | ||
560 | + // | ||
561 | + // Get and verify the Path Attributes Length | ||
562 | + // | ||
563 | + int pathAttributeLength = message.readUnsignedShort(); | ||
564 | + if (pathAttributeLength > message.readableBytes()) { | ||
565 | + // ERROR: Malformed Attribute List | ||
566 | + actionsBgpUpdateMalformedAttributeList(ctx); | ||
567 | + String errorMsg = "Malformed Attribute List"; | ||
568 | + throw new BgpParseException(errorMsg); | ||
569 | + } | ||
570 | + if (pathAttributeLength == 0) { | ||
571 | + return addedRoutes.values(); | ||
572 | + } | ||
573 | + | ||
574 | + // | ||
575 | + // Parse the Path Attributes | ||
576 | + // | ||
577 | + int pathAttributeEnd = message.readerIndex() + pathAttributeLength; | ||
578 | + while (message.readerIndex() < pathAttributeEnd) { | ||
579 | + int attrFlags = message.readUnsignedByte(); | ||
580 | + if (message.readerIndex() >= pathAttributeEnd) { | ||
581 | + // ERROR: Malformed Attribute List | ||
582 | + actionsBgpUpdateMalformedAttributeList(ctx); | ||
583 | + String errorMsg = "Malformed Attribute List"; | ||
584 | + throw new BgpParseException(errorMsg); | ||
585 | + } | ||
586 | + int attrTypeCode = message.readUnsignedByte(); | ||
587 | + | ||
588 | + // The Attribute Flags | ||
589 | + boolean optionalBit = ((0x80 & attrFlags) != 0); | ||
590 | + boolean transitiveBit = ((0x40 & attrFlags) != 0); | ||
591 | + boolean partialBit = ((0x20 & attrFlags) != 0); | ||
592 | + boolean extendedLengthBit = ((0x10 & attrFlags) != 0); | ||
593 | + | ||
594 | + // The Attribute Length | ||
595 | + int attrLen = 0; | ||
596 | + int attrLenOctets = 1; | ||
597 | + if (extendedLengthBit) { | ||
598 | + attrLenOctets = 2; | ||
599 | + } | ||
600 | + if (message.readerIndex() + attrLenOctets > pathAttributeEnd) { | ||
601 | + // ERROR: Malformed Attribute List | ||
602 | + actionsBgpUpdateMalformedAttributeList(ctx); | ||
603 | + String errorMsg = "Malformed Attribute List"; | ||
604 | + throw new BgpParseException(errorMsg); | ||
605 | + } | ||
606 | + if (extendedLengthBit) { | ||
607 | + attrLen = message.readUnsignedShort(); | ||
608 | + } else { | ||
609 | + attrLen = message.readUnsignedByte(); | ||
610 | + } | ||
611 | + if (message.readerIndex() + attrLen > pathAttributeEnd) { | ||
612 | + // ERROR: Malformed Attribute List | ||
613 | + actionsBgpUpdateMalformedAttributeList(ctx); | ||
614 | + String errorMsg = "Malformed Attribute List"; | ||
615 | + throw new BgpParseException(errorMsg); | ||
616 | + } | ||
617 | + | ||
618 | + // | ||
619 | + // Verify the Attribute Flags | ||
620 | + // | ||
621 | + verifyBgpUpdateAttributeFlags(ctx, attrTypeCode, attrLen, | ||
622 | + attrFlags, message); | ||
623 | + | ||
624 | + // | ||
625 | + // Extract the Attribute Value based on the Attribute Type Code | ||
626 | + // | ||
627 | + switch (attrTypeCode) { | ||
628 | + | ||
629 | + case BgpConstants.Update.Origin.TYPE: | ||
630 | + // Attribute Type Code ORIGIN | ||
631 | + origin = parseAttributeTypeOrigin(ctx, attrTypeCode, attrLen, | ||
632 | + attrFlags, message); | ||
633 | + break; | ||
634 | + | ||
635 | + case BgpConstants.Update.AsPath.TYPE: | ||
636 | + // Attribute Type Code AS_PATH | ||
637 | + asPath = parseAttributeTypeAsPath(ctx, attrTypeCode, attrLen, | ||
638 | + attrFlags, message); | ||
639 | + break; | ||
640 | + | ||
641 | + case BgpConstants.Update.NextHop.TYPE: | ||
642 | + // Attribute Type Code NEXT_HOP | ||
643 | + nextHop = parseAttributeTypeNextHop(ctx, attrTypeCode, attrLen, | ||
644 | + attrFlags, message); | ||
645 | + break; | ||
646 | + | ||
647 | + case BgpConstants.Update.MultiExitDisc.TYPE: | ||
648 | + // Attribute Type Code MULTI_EXIT_DISC | ||
649 | + multiExitDisc = | ||
650 | + parseAttributeTypeMultiExitDisc(ctx, attrTypeCode, attrLen, | ||
651 | + attrFlags, message); | ||
652 | + break; | ||
653 | + | ||
654 | + case BgpConstants.Update.LocalPref.TYPE: | ||
655 | + // Attribute Type Code LOCAL_PREF | ||
656 | + localPref = | ||
657 | + parseAttributeTypeLocalPref(ctx, attrTypeCode, attrLen, | ||
658 | + attrFlags, message); | ||
659 | + break; | ||
660 | + | ||
661 | + case BgpConstants.Update.AtomicAggregate.TYPE: | ||
662 | + // Attribute Type Code ATOMIC_AGGREGATE | ||
663 | + parseAttributeTypeAtomicAggregate(ctx, attrTypeCode, attrLen, | ||
664 | + attrFlags, message); | ||
665 | + // Nothing to do: this attribute is primarily informational | ||
666 | + break; | ||
667 | + | ||
668 | + case BgpConstants.Update.Aggregator.TYPE: | ||
669 | + // Attribute Type Code AGGREGATOR | ||
670 | + Pair<Long, IpAddress> aggregator = | ||
671 | + parseAttributeTypeAggregator(ctx, attrTypeCode, attrLen, | ||
672 | + attrFlags, message); | ||
673 | + aggregatorAsNumber = aggregator.getLeft(); | ||
674 | + aggregatorIpAddress = aggregator.getRight(); | ||
675 | + break; | ||
676 | + | ||
677 | + default: | ||
678 | + // TODO: Parse any new Attribute Types if needed | ||
679 | + if (!optionalBit) { | ||
680 | + // ERROR: Unrecognized Well-known Attribute | ||
681 | + actionsBgpUpdateUnrecognizedWellKnownAttribute( | ||
682 | + ctx, attrTypeCode, attrLen, attrFlags, message); | ||
683 | + String errorMsg = "Unrecognized Well-known Attribute: " + | ||
684 | + attrTypeCode; | ||
685 | + throw new BgpParseException(errorMsg); | ||
686 | + } | ||
687 | + | ||
688 | + // Skip the data from the unrecognized attribute | ||
689 | + log.debug("BGP RX UPDATE message from {}: " + | ||
690 | + "Unrecognized Attribute Type {}", | ||
691 | + remoteAddress, attrTypeCode); | ||
692 | + message.skipBytes(attrLen); | ||
693 | + break; | ||
694 | + } | ||
695 | + } | ||
696 | + | ||
697 | + // | ||
698 | + // Verify the Well-known Attributes | ||
699 | + // | ||
700 | + verifyBgpUpdateWellKnownAttributes(ctx, origin, asPath, nextHop, | ||
701 | + localPref); | ||
702 | + | ||
703 | + // | ||
704 | + // Parse the NLRI (Network Layer Reachability Information) | ||
705 | + // | ||
706 | + Collection<IpPrefix> addedPrefixes = null; | ||
707 | + int nlriLength = message.readableBytes(); | ||
708 | + try { | ||
709 | + addedPrefixes = parsePackedPrefixes(nlriLength, message); | ||
710 | + } catch (BgpParseException e) { | ||
711 | + // ERROR: Invalid Network Field | ||
712 | + log.debug("Exception parsing NLRI from BGP peer {}: ", | ||
713 | + remoteBgpId, e); | ||
714 | + actionsBgpUpdateInvalidNetworkField(ctx); | ||
715 | + // Rethrow the exception | ||
716 | + throw e; | ||
717 | + } | ||
718 | + | ||
719 | + // Generate the added routes | ||
720 | + for (IpPrefix prefix : addedPrefixes) { | ||
721 | + BgpRouteEntry bgpRouteEntry = | ||
722 | + new BgpRouteEntry(this, prefix, nextHop, | ||
723 | + origin.byteValue(), asPath, localPref); | ||
724 | + bgpRouteEntry.setMultiExitDisc(multiExitDisc); | ||
725 | + if (bgpRouteEntry.hasAsPathLoop(localAs)) { | ||
726 | + log.debug("BGP RX UPDATE message IGNORED from {}: {} " + | ||
727 | + "nextHop {}: contains AS Path loop", | ||
728 | + remoteAddress, prefix, nextHop); | ||
729 | + continue; | ||
730 | + } else { | ||
731 | + log.debug("BGP RX UPDATE message ADDED from {}: {} nextHop {}", | ||
732 | + remoteAddress, prefix, nextHop); | ||
733 | + } | ||
734 | + addedRoutes.put(prefix, bgpRouteEntry); | ||
735 | + } | ||
736 | + | ||
737 | + return addedRoutes.values(); | ||
738 | + } | ||
739 | + | ||
740 | + /** | ||
741 | + * Verifies BGP UPDATE Well-known Attributes. | ||
742 | + * | ||
743 | + * @param ctx the Channel Handler Context | ||
744 | + * @param origin the ORIGIN well-known mandatory attribute | ||
745 | + * @param asPath the AS_PATH well-known mandatory attribute | ||
746 | + * @param nextHop the NEXT_HOP well-known mandatory attribute | ||
747 | + * @param localPref the LOCAL_PREF required attribute | ||
748 | + * @throws BgpParseException | ||
749 | + */ | ||
750 | + private void verifyBgpUpdateWellKnownAttributes( | ||
751 | + ChannelHandlerContext ctx, | ||
752 | + Short origin, | ||
753 | + BgpRouteEntry.AsPath asPath, | ||
754 | + IpAddress nextHop, | ||
755 | + Long localPref) | ||
756 | + throws BgpParseException { | ||
757 | + // | ||
758 | + // Check for Missing Well-known Attributes | ||
759 | + // | ||
760 | + if ((origin == null) || (origin == -1)) { | ||
761 | + // Missing Attribute Type Code ORIGIN | ||
762 | + int type = BgpConstants.Update.Origin.TYPE; | ||
763 | + actionsBgpUpdateMissingWellKnownAttribute(ctx, type); | ||
764 | + String errorMsg = "Missing Well-known Attribute: ORIGIN"; | ||
765 | + throw new BgpParseException(errorMsg); | ||
766 | + } | ||
767 | + if (asPath == null) { | ||
768 | + // Missing Attribute Type Code AS_PATH | ||
769 | + int type = BgpConstants.Update.AsPath.TYPE; | ||
770 | + actionsBgpUpdateMissingWellKnownAttribute(ctx, type); | ||
771 | + String errorMsg = "Missing Well-known Attribute: AS_PATH"; | ||
772 | + throw new BgpParseException(errorMsg); | ||
773 | + } | ||
774 | + if (nextHop == null) { | ||
775 | + // Missing Attribute Type Code NEXT_HOP | ||
776 | + int type = BgpConstants.Update.NextHop.TYPE; | ||
777 | + actionsBgpUpdateMissingWellKnownAttribute(ctx, type); | ||
778 | + String errorMsg = "Missing Well-known Attribute: NEXT_HOP"; | ||
779 | + throw new BgpParseException(errorMsg); | ||
780 | + } | ||
781 | + if (localPref == null) { | ||
782 | + // Missing Attribute Type Code LOCAL_PREF | ||
783 | + // NOTE: Required for iBGP | ||
784 | + int type = BgpConstants.Update.LocalPref.TYPE; | ||
785 | + actionsBgpUpdateMissingWellKnownAttribute(ctx, type); | ||
786 | + String errorMsg = "Missing Well-known Attribute: LOCAL_PREF"; | ||
787 | + throw new BgpParseException(errorMsg); | ||
788 | + } | ||
789 | + } | ||
790 | + | ||
791 | + /** | ||
792 | + * Verifies the BGP UPDATE Attribute Flags. | ||
793 | + * | ||
794 | + * @param ctx the Channel Handler Context | ||
795 | + * @param attrTypeCode the attribute type code | ||
796 | + * @param attrLen the attribute length (in octets) | ||
797 | + * @param attrFlags the attribute flags | ||
798 | + * @param message the message to parse | ||
799 | + * @throws BgpParseException | ||
800 | + */ | ||
801 | + private void verifyBgpUpdateAttributeFlags( | ||
802 | + ChannelHandlerContext ctx, | ||
803 | + int attrTypeCode, | ||
804 | + int attrLen, | ||
805 | + int attrFlags, | ||
806 | + ChannelBuffer message) | ||
807 | + throws BgpParseException { | ||
808 | + | ||
809 | + // | ||
810 | + // Assign the Attribute Type Name and the Well-known flag | ||
811 | + // | ||
812 | + String typeName = "UNKNOWN"; | ||
813 | + boolean isWellKnown = false; | ||
814 | + switch (attrTypeCode) { | ||
815 | + case BgpConstants.Update.Origin.TYPE: | ||
816 | + isWellKnown = true; | ||
817 | + typeName = "ORIGIN"; | ||
818 | + break; | ||
819 | + case BgpConstants.Update.AsPath.TYPE: | ||
820 | + isWellKnown = true; | ||
821 | + typeName = "AS_PATH"; | ||
822 | + break; | ||
823 | + case BgpConstants.Update.NextHop.TYPE: | ||
824 | + isWellKnown = true; | ||
825 | + typeName = "NEXT_HOP"; | ||
826 | + break; | ||
827 | + case BgpConstants.Update.MultiExitDisc.TYPE: | ||
828 | + isWellKnown = false; | ||
829 | + typeName = "MULTI_EXIT_DISC"; | ||
830 | + break; | ||
831 | + case BgpConstants.Update.LocalPref.TYPE: | ||
832 | + isWellKnown = true; | ||
833 | + typeName = "LOCAL_PREF"; | ||
834 | + break; | ||
835 | + case BgpConstants.Update.AtomicAggregate.TYPE: | ||
836 | + isWellKnown = true; | ||
837 | + typeName = "ATOMIC_AGGREGATE"; | ||
838 | + break; | ||
839 | + case BgpConstants.Update.Aggregator.TYPE: | ||
840 | + isWellKnown = false; | ||
841 | + typeName = "AGGREGATOR"; | ||
842 | + break; | ||
843 | + default: | ||
844 | + isWellKnown = false; | ||
845 | + typeName = "UNKNOWN(" + attrTypeCode + ")"; | ||
846 | + break; | ||
847 | + } | ||
848 | + | ||
849 | + // | ||
850 | + // Verify the Attribute Flags | ||
851 | + // | ||
852 | + boolean optionalBit = ((0x80 & attrFlags) != 0); | ||
853 | + boolean transitiveBit = ((0x40 & attrFlags) != 0); | ||
854 | + boolean partialBit = ((0x20 & attrFlags) != 0); | ||
855 | + if ((isWellKnown && optionalBit) || | ||
856 | + (isWellKnown && (!transitiveBit)) || | ||
857 | + (isWellKnown && partialBit) || | ||
858 | + (optionalBit && (!transitiveBit) && partialBit)) { | ||
859 | + // | ||
860 | + // ERROR: The Optional bit cannot be set for Well-known attributes | ||
861 | + // ERROR: The Transtive bit MUST be 1 for well-known attributes | ||
862 | + // ERROR: The Partial bit MUST be 0 for well-known attributes | ||
863 | + // ERROR: The Partial bit MUST be 0 for optional non-transitive | ||
864 | + // attributes | ||
865 | + // | ||
866 | + actionsBgpUpdateAttributeFlagsError( | ||
867 | + ctx, attrTypeCode, attrLen, attrFlags, message); | ||
868 | + String errorMsg = "Attribute Flags Error for " + typeName + ": " + | ||
869 | + attrFlags; | ||
870 | + throw new BgpParseException(errorMsg); | ||
871 | + } | ||
872 | + } | ||
873 | + | ||
874 | + /** | ||
875 | + * Parses BGP UPDATE Attribute Type ORIGIN. | ||
876 | + * | ||
877 | + * @param ctx the Channel Handler Context | ||
878 | + * @param attrTypeCode the attribute type code | ||
879 | + * @param attrLen the attribute length (in octets) | ||
880 | + * @param attrFlags the attribute flags | ||
881 | + * @param message the message to parse | ||
882 | + * @return the parsed ORIGIN value | ||
883 | + * @throws BgpParseException | ||
884 | + */ | ||
885 | + private short parseAttributeTypeOrigin( | ||
886 | + ChannelHandlerContext ctx, | ||
887 | + int attrTypeCode, | ||
888 | + int attrLen, | ||
889 | + int attrFlags, | ||
890 | + ChannelBuffer message) | ||
891 | + throws BgpParseException { | ||
892 | + | ||
893 | + // Check the Attribute Length | ||
894 | + if (attrLen != BgpConstants.Update.Origin.LENGTH) { | ||
895 | + // ERROR: Attribute Length Error | ||
896 | + actionsBgpUpdateAttributeLengthError( | ||
897 | + ctx, attrTypeCode, attrLen, attrFlags, message); | ||
898 | + String errorMsg = "Attribute Length Error"; | ||
899 | + throw new BgpParseException(errorMsg); | ||
900 | + } | ||
901 | + | ||
902 | + message.markReaderIndex(); | ||
903 | + short origin = message.readUnsignedByte(); | ||
904 | + switch (origin) { | ||
905 | + case BgpConstants.Update.Origin.IGP: | ||
906 | + // FALLTHROUGH | ||
907 | + case BgpConstants.Update.Origin.EGP: | ||
908 | + // FALLTHROUGH | ||
909 | + case BgpConstants.Update.Origin.INCOMPLETE: | ||
910 | + break; | ||
911 | + default: | ||
912 | + // ERROR: Invalid ORIGIN Attribute | ||
913 | + message.resetReaderIndex(); | ||
914 | + actionsBgpUpdateInvalidOriginAttribute( | ||
915 | + ctx, attrTypeCode, attrLen, attrFlags, message, origin); | ||
916 | + String errorMsg = "Invalid ORIGIN Attribute: " + origin; | ||
917 | + throw new BgpParseException(errorMsg); | ||
918 | + } | ||
919 | + | ||
920 | + return origin; | ||
921 | + } | ||
922 | + | ||
923 | + /** | ||
924 | + * Parses BGP UPDATE Attribute AS Path. | ||
925 | + * | ||
926 | + * @param ctx the Channel Handler Context | ||
927 | + * @param attrTypeCode the attribute type code | ||
928 | + * @param attrLen the attribute length (in octets) | ||
929 | + * @param attrFlags the attribute flags | ||
930 | + * @param message the message to parse | ||
931 | + * @return the parsed AS Path | ||
932 | + * @throws BgpParseException | ||
933 | + */ | ||
934 | + private BgpRouteEntry.AsPath parseAttributeTypeAsPath( | ||
935 | + ChannelHandlerContext ctx, | ||
936 | + int attrTypeCode, | ||
937 | + int attrLen, | ||
938 | + int attrFlags, | ||
939 | + ChannelBuffer message) | ||
940 | + throws BgpParseException { | ||
941 | + ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>(); | ||
942 | + | ||
943 | + // | ||
944 | + // Parse the message | ||
945 | + // | ||
946 | + while (attrLen > 0) { | ||
947 | + if (attrLen < 2) { | ||
948 | + // ERROR: Malformed AS_PATH | ||
949 | + actionsBgpUpdateMalformedAsPath(ctx); | ||
950 | + String errorMsg = "Malformed AS Path"; | ||
951 | + throw new BgpParseException(errorMsg); | ||
952 | + } | ||
953 | + // Get the Path Segment Type and Length (in number of ASes) | ||
954 | + short pathSegmentType = message.readUnsignedByte(); | ||
955 | + short pathSegmentLength = message.readUnsignedByte(); | ||
956 | + attrLen -= 2; | ||
957 | + | ||
958 | + // Verify the Path Segment Type | ||
959 | + switch (pathSegmentType) { | ||
960 | + case BgpConstants.Update.AsPath.AS_SET: | ||
961 | + // FALLTHROUGH | ||
962 | + case BgpConstants.Update.AsPath.AS_SEQUENCE: | ||
963 | + break; | ||
964 | + default: | ||
965 | + // ERROR: Invalid Path Segment Type | ||
966 | + // | ||
967 | + // NOTE: The BGP Spec (RFC 4271) doesn't contain Error Subcode | ||
968 | + // for "Invalid Path Segment Type", hence we return | ||
969 | + // the error as "Malformed AS_PATH". | ||
970 | + // | ||
971 | + actionsBgpUpdateMalformedAsPath(ctx); | ||
972 | + String errorMsg = | ||
973 | + "Invalid AS Path Segment Type: " + pathSegmentType; | ||
974 | + throw new BgpParseException(errorMsg); | ||
975 | + } | ||
976 | + | ||
977 | + // Parse the AS numbers | ||
978 | + if (2 * pathSegmentLength > attrLen) { | ||
979 | + // ERROR: Malformed AS_PATH | ||
980 | + actionsBgpUpdateMalformedAsPath(ctx); | ||
981 | + String errorMsg = "Malformed AS Path"; | ||
982 | + throw new BgpParseException(errorMsg); | ||
983 | + } | ||
984 | + attrLen -= (2 * pathSegmentLength); | ||
985 | + ArrayList<Long> segmentAsNumbers = new ArrayList<>(); | ||
986 | + while (pathSegmentLength-- > 0) { | ||
987 | + long asNumber = message.readUnsignedShort(); | ||
988 | + segmentAsNumbers.add(asNumber); | ||
989 | + } | ||
990 | + | ||
991 | + BgpRouteEntry.PathSegment pathSegment = | ||
992 | + new BgpRouteEntry.PathSegment((byte) pathSegmentType, | ||
993 | + segmentAsNumbers); | ||
994 | + pathSegments.add(pathSegment); | ||
995 | + } | ||
996 | + | ||
997 | + return new BgpRouteEntry.AsPath(pathSegments); | ||
998 | + } | ||
999 | + | ||
1000 | + /** | ||
1001 | + * Parses BGP UPDATE Attribute Type NEXT_HOP. | ||
1002 | + * | ||
1003 | + * @param ctx the Channel Handler Context | ||
1004 | + * @param attrTypeCode the attribute type code | ||
1005 | + * @param attrLen the attribute length (in octets) | ||
1006 | + * @param attrFlags the attribute flags | ||
1007 | + * @param message the message to parse | ||
1008 | + * @return the parsed NEXT_HOP value | ||
1009 | + * @throws BgpParseException | ||
1010 | + */ | ||
1011 | + private IpAddress parseAttributeTypeNextHop( | ||
1012 | + ChannelHandlerContext ctx, | ||
1013 | + int attrTypeCode, | ||
1014 | + int attrLen, | ||
1015 | + int attrFlags, | ||
1016 | + ChannelBuffer message) | ||
1017 | + throws BgpParseException { | ||
1018 | + | ||
1019 | + // Check the Attribute Length | ||
1020 | + if (attrLen != BgpConstants.Update.NextHop.LENGTH) { | ||
1021 | + // ERROR: Attribute Length Error | ||
1022 | + actionsBgpUpdateAttributeLengthError( | ||
1023 | + ctx, attrTypeCode, attrLen, attrFlags, message); | ||
1024 | + String errorMsg = "Attribute Length Error"; | ||
1025 | + throw new BgpParseException(errorMsg); | ||
1026 | + } | ||
1027 | + | ||
1028 | + message.markReaderIndex(); | ||
1029 | + long address = message.readUnsignedInt(); | ||
1030 | + IpAddress nextHopAddress = IpAddress.valueOf((int) address); | ||
1031 | + // | ||
1032 | + // Check whether the NEXT_HOP IP address is semantically correct. | ||
1033 | + // As per RFC 4271, Section 6.3: | ||
1034 | + // | ||
1035 | + // a) It MUST NOT be the IP address of the receiving speaker | ||
1036 | + // b) In the case of an EBGP .... | ||
1037 | + // | ||
1038 | + // Here we check only (a), because (b) doesn't apply for us: all our | ||
1039 | + // peers are iBGP. | ||
1040 | + // | ||
1041 | + if (nextHopAddress.equals(localIp4Address)) { | ||
1042 | + // ERROR: Invalid NEXT_HOP Attribute | ||
1043 | + message.resetReaderIndex(); | ||
1044 | + actionsBgpUpdateInvalidNextHopAttribute( | ||
1045 | + ctx, attrTypeCode, attrLen, attrFlags, message, | ||
1046 | + nextHopAddress); | ||
1047 | + String errorMsg = "Invalid NEXT_HOP Attribute: " + nextHopAddress; | ||
1048 | + throw new BgpParseException(errorMsg); | ||
1049 | + } | ||
1050 | + | ||
1051 | + return nextHopAddress; | ||
1052 | + } | ||
1053 | + | ||
1054 | + /** | ||
1055 | + * Parses BGP UPDATE Attribute Type MULTI_EXIT_DISC. | ||
1056 | + * | ||
1057 | + * @param ctx the Channel Handler Context | ||
1058 | + * @param attrTypeCode the attribute type code | ||
1059 | + * @param attrLen the attribute length (in octets) | ||
1060 | + * @param attrFlags the attribute flags | ||
1061 | + * @param message the message to parse | ||
1062 | + * @return the parsed MULTI_EXIT_DISC value | ||
1063 | + * @throws BgpParseException | ||
1064 | + */ | ||
1065 | + private long parseAttributeTypeMultiExitDisc( | ||
1066 | + ChannelHandlerContext ctx, | ||
1067 | + int attrTypeCode, | ||
1068 | + int attrLen, | ||
1069 | + int attrFlags, | ||
1070 | + ChannelBuffer message) | ||
1071 | + throws BgpParseException { | ||
1072 | + | ||
1073 | + // Check the Attribute Length | ||
1074 | + if (attrLen != BgpConstants.Update.MultiExitDisc.LENGTH) { | ||
1075 | + // ERROR: Attribute Length Error | ||
1076 | + actionsBgpUpdateAttributeLengthError( | ||
1077 | + ctx, attrTypeCode, attrLen, attrFlags, message); | ||
1078 | + String errorMsg = "Attribute Length Error"; | ||
1079 | + throw new BgpParseException(errorMsg); | ||
1080 | + } | ||
1081 | + | ||
1082 | + long multiExitDisc = message.readUnsignedInt(); | ||
1083 | + return multiExitDisc; | ||
1084 | + } | ||
1085 | + | ||
1086 | + /** | ||
1087 | + * Parses BGP UPDATE Attribute Type LOCAL_PREF. | ||
1088 | + * | ||
1089 | + * @param ctx the Channel Handler Context | ||
1090 | + * @param attrTypeCode the attribute type code | ||
1091 | + * @param attrLen the attribute length (in octets) | ||
1092 | + * @param attrFlags the attribute flags | ||
1093 | + * @param message the message to parse | ||
1094 | + * @return the parsed LOCAL_PREF value | ||
1095 | + * @throws BgpParseException | ||
1096 | + */ | ||
1097 | + private long parseAttributeTypeLocalPref( | ||
1098 | + ChannelHandlerContext ctx, | ||
1099 | + int attrTypeCode, | ||
1100 | + int attrLen, | ||
1101 | + int attrFlags, | ||
1102 | + ChannelBuffer message) | ||
1103 | + throws BgpParseException { | ||
1104 | + | ||
1105 | + // Check the Attribute Length | ||
1106 | + if (attrLen != BgpConstants.Update.LocalPref.LENGTH) { | ||
1107 | + // ERROR: Attribute Length Error | ||
1108 | + actionsBgpUpdateAttributeLengthError( | ||
1109 | + ctx, attrTypeCode, attrLen, attrFlags, message); | ||
1110 | + String errorMsg = "Attribute Length Error"; | ||
1111 | + throw new BgpParseException(errorMsg); | ||
1112 | + } | ||
1113 | + | ||
1114 | + long localPref = message.readUnsignedInt(); | ||
1115 | + return localPref; | ||
1116 | + } | ||
1117 | + | ||
1118 | + /** | ||
1119 | + * Parses BGP UPDATE Attribute Type ATOMIC_AGGREGATE. | ||
1120 | + * | ||
1121 | + * @param ctx the Channel Handler Context | ||
1122 | + * @param attrTypeCode the attribute type code | ||
1123 | + * @param attrLen the attribute length (in octets) | ||
1124 | + * @param attrFlags the attribute flags | ||
1125 | + * @param message the message to parse | ||
1126 | + * @throws BgpParseException | ||
1127 | + */ | ||
1128 | + private void parseAttributeTypeAtomicAggregate( | ||
1129 | + ChannelHandlerContext ctx, | ||
1130 | + int attrTypeCode, | ||
1131 | + int attrLen, | ||
1132 | + int attrFlags, | ||
1133 | + ChannelBuffer message) | ||
1134 | + throws BgpParseException { | ||
1135 | + | ||
1136 | + // Check the Attribute Length | ||
1137 | + if (attrLen != BgpConstants.Update.AtomicAggregate.LENGTH) { | ||
1138 | + // ERROR: Attribute Length Error | ||
1139 | + actionsBgpUpdateAttributeLengthError( | ||
1140 | + ctx, attrTypeCode, attrLen, attrFlags, message); | ||
1141 | + String errorMsg = "Attribute Length Error"; | ||
1142 | + throw new BgpParseException(errorMsg); | ||
1143 | + } | ||
1144 | + | ||
1145 | + // Nothing to do: this attribute is primarily informational | ||
1146 | + } | ||
1147 | + | ||
1148 | + /** | ||
1149 | + * Parses BGP UPDATE Attribute Type AGGREGATOR. | ||
1150 | + * | ||
1151 | + * @param ctx the Channel Handler Context | ||
1152 | + * @param attrTypeCode the attribute type code | ||
1153 | + * @param attrLen the attribute length (in octets) | ||
1154 | + * @param attrFlags the attribute flags | ||
1155 | + * @param message the message to parse | ||
1156 | + * @return the parsed AGGREGATOR value: a tuple of <AS-Number, IP-Address> | ||
1157 | + * @throws BgpParseException | ||
1158 | + */ | ||
1159 | + private Pair<Long, IpAddress> parseAttributeTypeAggregator( | ||
1160 | + ChannelHandlerContext ctx, | ||
1161 | + int attrTypeCode, | ||
1162 | + int attrLen, | ||
1163 | + int attrFlags, | ||
1164 | + ChannelBuffer message) | ||
1165 | + throws BgpParseException { | ||
1166 | + | ||
1167 | + // Check the Attribute Length | ||
1168 | + if (attrLen != BgpConstants.Update.Aggregator.LENGTH) { | ||
1169 | + // ERROR: Attribute Length Error | ||
1170 | + actionsBgpUpdateAttributeLengthError( | ||
1171 | + ctx, attrTypeCode, attrLen, attrFlags, message); | ||
1172 | + String errorMsg = "Attribute Length Error"; | ||
1173 | + throw new BgpParseException(errorMsg); | ||
1174 | + } | ||
1175 | + | ||
1176 | + // The AGGREGATOR AS number | ||
1177 | + long aggregatorAsNumber = message.readUnsignedShort(); | ||
1178 | + // The AGGREGATOR IP address | ||
1179 | + long aggregatorAddress = message.readUnsignedInt(); | ||
1180 | + IpAddress aggregatorIpAddress = | ||
1181 | + IpAddress.valueOf((int) aggregatorAddress); | ||
1182 | + | ||
1183 | + Pair<Long, IpAddress> aggregator = Pair.of(aggregatorAsNumber, | ||
1184 | + aggregatorIpAddress); | ||
1185 | + return aggregator; | ||
1186 | + } | ||
1187 | + | ||
1188 | + /** | ||
1189 | + * Parses a message that contains encoded IPv4 network prefixes. | ||
1190 | + * <p> | ||
1191 | + * The IPv4 prefixes are encoded in the form: | ||
1192 | + * <Length, Prefix> where Length is the length in bits of the IPv4 prefix, | ||
1193 | + * and Prefix is the IPv4 prefix (padded with trailing bits to the end | ||
1194 | + * of an octet). | ||
1195 | + * | ||
1196 | + * @param totalLength the total length of the data to parse | ||
1197 | + * @param message the message with data to parse | ||
1198 | + * @return a collection of parsed IPv4 network prefixes | ||
1199 | + * @throws BgpParseException | ||
1200 | + */ | ||
1201 | + private Collection<IpPrefix> parsePackedPrefixes(int totalLength, | ||
1202 | + ChannelBuffer message) | ||
1203 | + throws BgpParseException { | ||
1204 | + Collection<IpPrefix> result = new ArrayList<>(); | ||
1205 | + | ||
1206 | + if (totalLength == 0) { | ||
1207 | + return result; | ||
1208 | + } | ||
1209 | + | ||
1210 | + // Parse the data | ||
1211 | + int dataEnd = message.readerIndex() + totalLength; | ||
1212 | + while (message.readerIndex() < dataEnd) { | ||
1213 | + int prefixBitlen = message.readUnsignedByte(); | ||
1214 | + int prefixBytelen = (prefixBitlen + 7) / 8; // Round-up | ||
1215 | + if (message.readerIndex() + prefixBytelen > dataEnd) { | ||
1216 | + String errorMsg = "Malformed Network Prefixes"; | ||
1217 | + throw new BgpParseException(errorMsg); | ||
1218 | + } | ||
1219 | + | ||
1220 | + long address = 0; | ||
1221 | + long extraShift = (4 - prefixBytelen) * 8; | ||
1222 | + while (prefixBytelen > 0) { | ||
1223 | + address <<= 8; | ||
1224 | + address |= message.readUnsignedByte(); | ||
1225 | + prefixBytelen--; | ||
1226 | + } | ||
1227 | + address <<= extraShift; | ||
1228 | + IpPrefix prefix = | ||
1229 | + IpPrefix.valueOf(IpAddress.valueOf((int) address).toRealInt(), | ||
1230 | + (short) prefixBitlen); | ||
1231 | + result.add(prefix); | ||
1232 | + } | ||
1233 | + | ||
1234 | + return result; | ||
1235 | + } | ||
1236 | + | ||
1237 | + /** | ||
1238 | + * Applies the appropriate actions after detecting BGP UPDATE | ||
1239 | + * Invalid Network Field Error: send NOTIFICATION and close the channel. | ||
1240 | + * | ||
1241 | + * @param ctx the Channel Handler Context | ||
1242 | + */ | ||
1243 | + private void actionsBgpUpdateInvalidNetworkField( | ||
1244 | + ChannelHandlerContext ctx) { | ||
1245 | + log.debug("BGP RX UPDATE Error from {}: Invalid Network Field", | ||
1246 | + remoteAddress); | ||
1247 | + | ||
1248 | + // | ||
1249 | + // ERROR: Invalid Network Field | ||
1250 | + // | ||
1251 | + // Send NOTIFICATION and close the connection | ||
1252 | + int errorCode = UpdateMessageError.ERROR_CODE; | ||
1253 | + int errorSubcode = UpdateMessageError.INVALID_NETWORK_FIELD; | ||
1254 | + ChannelBuffer txMessage = | ||
1255 | + prepareBgpNotification(errorCode, errorSubcode, null); | ||
1256 | + ctx.getChannel().write(txMessage); | ||
1257 | + closeChannel(ctx); | ||
1258 | + } | ||
1259 | + | ||
1260 | + /** | ||
1261 | + * Applies the appropriate actions after detecting BGP UPDATE | ||
1262 | + * Malformed Attribute List Error: send NOTIFICATION and close the channel. | ||
1263 | + * | ||
1264 | + * @param ctx the Channel Handler Context | ||
1265 | + */ | ||
1266 | + private void actionsBgpUpdateMalformedAttributeList( | ||
1267 | + ChannelHandlerContext ctx) { | ||
1268 | + log.debug("BGP RX UPDATE Error from {}: Malformed Attribute List", | ||
1269 | + remoteAddress); | ||
1270 | + | ||
1271 | + // | ||
1272 | + // ERROR: Malformed Attribute List | ||
1273 | + // | ||
1274 | + // Send NOTIFICATION and close the connection | ||
1275 | + int errorCode = UpdateMessageError.ERROR_CODE; | ||
1276 | + int errorSubcode = UpdateMessageError.MALFORMED_ATTRIBUTE_LIST; | ||
1277 | + ChannelBuffer txMessage = | ||
1278 | + prepareBgpNotification(errorCode, errorSubcode, null); | ||
1279 | + ctx.getChannel().write(txMessage); | ||
1280 | + closeChannel(ctx); | ||
1281 | + } | ||
1282 | + | ||
1283 | + /** | ||
1284 | + * Applies the appropriate actions after detecting BGP UPDATE | ||
1285 | + * Missing Well-known Attribute Error: send NOTIFICATION and close the | ||
1286 | + * channel. | ||
1287 | + * | ||
1288 | + * @param ctx the Channel Handler Context | ||
1289 | + * @param missingAttrTypeCode the missing attribute type code | ||
1290 | + */ | ||
1291 | + private void actionsBgpUpdateMissingWellKnownAttribute( | ||
1292 | + ChannelHandlerContext ctx, | ||
1293 | + int missingAttrTypeCode) { | ||
1294 | + log.debug("BGP RX UPDATE Error from {}: Missing Well-known Attribute: {}", | ||
1295 | + remoteAddress, missingAttrTypeCode); | ||
1296 | + | ||
1297 | + // | ||
1298 | + // ERROR: Missing Well-known Attribute | ||
1299 | + // | ||
1300 | + // Send NOTIFICATION and close the connection | ||
1301 | + int errorCode = UpdateMessageError.ERROR_CODE; | ||
1302 | + int errorSubcode = UpdateMessageError.MISSING_WELL_KNOWN_ATTRIBUTE; | ||
1303 | + ChannelBuffer data = ChannelBuffers.buffer(1); | ||
1304 | + data.writeByte(missingAttrTypeCode); | ||
1305 | + ChannelBuffer txMessage = | ||
1306 | + prepareBgpNotification(errorCode, errorSubcode, data); | ||
1307 | + ctx.getChannel().write(txMessage); | ||
1308 | + closeChannel(ctx); | ||
1309 | + } | ||
1310 | + | ||
1311 | + /** | ||
1312 | + * Applies the appropriate actions after detecting BGP UPDATE | ||
1313 | + * Invalid ORIGIN Attribute Error: send NOTIFICATION and close the channel. | ||
1314 | + * | ||
1315 | + * @param ctx the Channel Handler Context | ||
1316 | + * @param attrTypeCode the attribute type code | ||
1317 | + * @param attrLen the attribute length (in octets) | ||
1318 | + * @param attrFlags the attribute flags | ||
1319 | + * @param message the message with the data | ||
1320 | + * @param origin the ORIGIN attribute value | ||
1321 | + */ | ||
1322 | + private void actionsBgpUpdateInvalidOriginAttribute( | ||
1323 | + ChannelHandlerContext ctx, | ||
1324 | + int attrTypeCode, | ||
1325 | + int attrLen, | ||
1326 | + int attrFlags, | ||
1327 | + ChannelBuffer message, | ||
1328 | + short origin) { | ||
1329 | + log.debug("BGP RX UPDATE Error from {}: Invalid ORIGIN Attribute", | ||
1330 | + remoteAddress); | ||
1331 | + | ||
1332 | + // | ||
1333 | + // ERROR: Invalid ORIGIN Attribute | ||
1334 | + // | ||
1335 | + // Send NOTIFICATION and close the connection | ||
1336 | + int errorCode = UpdateMessageError.ERROR_CODE; | ||
1337 | + int errorSubcode = UpdateMessageError.INVALID_ORIGIN_ATTRIBUTE; | ||
1338 | + ChannelBuffer data = | ||
1339 | + prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen, | ||
1340 | + attrFlags, message); | ||
1341 | + ChannelBuffer txMessage = | ||
1342 | + prepareBgpNotification(errorCode, errorSubcode, data); | ||
1343 | + ctx.getChannel().write(txMessage); | ||
1344 | + closeChannel(ctx); | ||
1345 | + } | ||
1346 | + | ||
1347 | + /** | ||
1348 | + * Applies the appropriate actions after detecting BGP UPDATE | ||
1349 | + * Attribute Flags Error: send NOTIFICATION and close the channel. | ||
1350 | + * | ||
1351 | + * @param ctx the Channel Handler Context | ||
1352 | + * @param attrTypeCode the attribute type code | ||
1353 | + * @param attrLen the attribute length (in octets) | ||
1354 | + * @param attrFlags the attribute flags | ||
1355 | + * @param message the message with the data | ||
1356 | + */ | ||
1357 | + private void actionsBgpUpdateAttributeFlagsError( | ||
1358 | + ChannelHandlerContext ctx, | ||
1359 | + int attrTypeCode, | ||
1360 | + int attrLen, | ||
1361 | + int attrFlags, | ||
1362 | + ChannelBuffer message) { | ||
1363 | + log.debug("BGP RX UPDATE Error from {}: Attribute Flags Error", | ||
1364 | + remoteAddress); | ||
1365 | + | ||
1366 | + // | ||
1367 | + // ERROR: Attribute Flags Error | ||
1368 | + // | ||
1369 | + // Send NOTIFICATION and close the connection | ||
1370 | + int errorCode = UpdateMessageError.ERROR_CODE; | ||
1371 | + int errorSubcode = UpdateMessageError.ATTRIBUTE_FLAGS_ERROR; | ||
1372 | + ChannelBuffer data = | ||
1373 | + prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen, | ||
1374 | + attrFlags, message); | ||
1375 | + ChannelBuffer txMessage = | ||
1376 | + prepareBgpNotification(errorCode, errorSubcode, data); | ||
1377 | + ctx.getChannel().write(txMessage); | ||
1378 | + closeChannel(ctx); | ||
1379 | + } | ||
1380 | + | ||
1381 | + /** | ||
1382 | + * Applies the appropriate actions after detecting BGP UPDATE | ||
1383 | + * Invalid NEXT_HOP Attribute Error: send NOTIFICATION and close the | ||
1384 | + * channel. | ||
1385 | + * | ||
1386 | + * @param ctx the Channel Handler Context | ||
1387 | + * @param attrTypeCode the attribute type code | ||
1388 | + * @param attrLen the attribute length (in octets) | ||
1389 | + * @param attrFlags the attribute flags | ||
1390 | + * @param message the message with the data | ||
1391 | + * @param nextHop the NEXT_HOP attribute value | ||
1392 | + */ | ||
1393 | + private void actionsBgpUpdateInvalidNextHopAttribute( | ||
1394 | + ChannelHandlerContext ctx, | ||
1395 | + int attrTypeCode, | ||
1396 | + int attrLen, | ||
1397 | + int attrFlags, | ||
1398 | + ChannelBuffer message, | ||
1399 | + IpAddress nextHop) { | ||
1400 | + log.debug("BGP RX UPDATE Error from {}: Invalid NEXT_HOP Attribute {}", | ||
1401 | + remoteAddress, nextHop); | ||
1402 | + | ||
1403 | + // | ||
1404 | + // ERROR: Invalid ORIGIN Attribute | ||
1405 | + // | ||
1406 | + // Send NOTIFICATION and close the connection | ||
1407 | + int errorCode = UpdateMessageError.ERROR_CODE; | ||
1408 | + int errorSubcode = UpdateMessageError.INVALID_NEXT_HOP_ATTRIBUTE; | ||
1409 | + ChannelBuffer data = | ||
1410 | + prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen, | ||
1411 | + attrFlags, message); | ||
1412 | + ChannelBuffer txMessage = | ||
1413 | + prepareBgpNotification(errorCode, errorSubcode, data); | ||
1414 | + ctx.getChannel().write(txMessage); | ||
1415 | + closeChannel(ctx); | ||
1416 | + } | ||
1417 | + | ||
1418 | + /** | ||
1419 | + * Applies the appropriate actions after detecting BGP UPDATE | ||
1420 | + * Unrecognized Well-known Attribute Error: send NOTIFICATION and close | ||
1421 | + * the channel. | ||
1422 | + * | ||
1423 | + * @param ctx the Channel Handler Context | ||
1424 | + * @param attrTypeCode the attribute type code | ||
1425 | + * @param attrLen the attribute length (in octets) | ||
1426 | + * @param attrFlags the attribute flags | ||
1427 | + * @param message the message with the data | ||
1428 | + */ | ||
1429 | + private void actionsBgpUpdateUnrecognizedWellKnownAttribute( | ||
1430 | + ChannelHandlerContext ctx, | ||
1431 | + int attrTypeCode, | ||
1432 | + int attrLen, | ||
1433 | + int attrFlags, | ||
1434 | + ChannelBuffer message) { | ||
1435 | + log.debug("BGP RX UPDATE Error from {}: " + | ||
1436 | + "Unrecognized Well-known Attribute Error: {}", | ||
1437 | + remoteAddress, attrTypeCode); | ||
1438 | + | ||
1439 | + // | ||
1440 | + // ERROR: Unrecognized Well-known Attribute | ||
1441 | + // | ||
1442 | + // Send NOTIFICATION and close the connection | ||
1443 | + int errorCode = UpdateMessageError.ERROR_CODE; | ||
1444 | + int errorSubcode = | ||
1445 | + UpdateMessageError.UNRECOGNIZED_WELL_KNOWN_ATTRIBUTE; | ||
1446 | + ChannelBuffer data = | ||
1447 | + prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen, | ||
1448 | + attrFlags, message); | ||
1449 | + ChannelBuffer txMessage = | ||
1450 | + prepareBgpNotification(errorCode, errorSubcode, data); | ||
1451 | + ctx.getChannel().write(txMessage); | ||
1452 | + closeChannel(ctx); | ||
1453 | + } | ||
1454 | + | ||
1455 | + /** | ||
1456 | + * Applies the appropriate actions after detecting BGP UPDATE | ||
1457 | + * Attribute Length Error: send NOTIFICATION and close the channel. | ||
1458 | + * | ||
1459 | + * @param ctx the Channel Handler Context | ||
1460 | + * @param attrTypeCode the attribute type code | ||
1461 | + * @param attrLen the attribute length (in octets) | ||
1462 | + * @param attrFlags the attribute flags | ||
1463 | + * @param message the message with the data | ||
1464 | + */ | ||
1465 | + private void actionsBgpUpdateAttributeLengthError( | ||
1466 | + ChannelHandlerContext ctx, | ||
1467 | + int attrTypeCode, | ||
1468 | + int attrLen, | ||
1469 | + int attrFlags, | ||
1470 | + ChannelBuffer message) { | ||
1471 | + log.debug("BGP RX UPDATE Error from {}: Attribute Length Error", | ||
1472 | + remoteAddress); | ||
1473 | + | ||
1474 | + // | ||
1475 | + // ERROR: Attribute Length Error | ||
1476 | + // | ||
1477 | + // Send NOTIFICATION and close the connection | ||
1478 | + int errorCode = UpdateMessageError.ERROR_CODE; | ||
1479 | + int errorSubcode = UpdateMessageError.ATTRIBUTE_LENGTH_ERROR; | ||
1480 | + ChannelBuffer data = | ||
1481 | + prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen, | ||
1482 | + attrFlags, message); | ||
1483 | + ChannelBuffer txMessage = | ||
1484 | + prepareBgpNotification(errorCode, errorSubcode, data); | ||
1485 | + ctx.getChannel().write(txMessage); | ||
1486 | + closeChannel(ctx); | ||
1487 | + } | ||
1488 | + | ||
1489 | + /** | ||
1490 | + * Applies the appropriate actions after detecting BGP UPDATE | ||
1491 | + * Malformed AS_PATH Error: send NOTIFICATION and close the channel. | ||
1492 | + * | ||
1493 | + * @param ctx the Channel Handler Context | ||
1494 | + */ | ||
1495 | + private void actionsBgpUpdateMalformedAsPath( | ||
1496 | + ChannelHandlerContext ctx) { | ||
1497 | + log.debug("BGP RX UPDATE Error from {}: Malformed AS Path", | ||
1498 | + remoteAddress); | ||
1499 | + | ||
1500 | + // | ||
1501 | + // ERROR: Malformed AS_PATH | ||
1502 | + // | ||
1503 | + // Send NOTIFICATION and close the connection | ||
1504 | + int errorCode = UpdateMessageError.ERROR_CODE; | ||
1505 | + int errorSubcode = UpdateMessageError.MALFORMED_AS_PATH; | ||
1506 | + ChannelBuffer txMessage = | ||
1507 | + prepareBgpNotification(errorCode, errorSubcode, null); | ||
1508 | + ctx.getChannel().write(txMessage); | ||
1509 | + closeChannel(ctx); | ||
1510 | + } | ||
1511 | + | ||
1512 | + /** | ||
1513 | + * Processes BGP NOTIFICATION message. | ||
1514 | + * | ||
1515 | + * @param ctx the Channel Handler Context | ||
1516 | + * @param message the message to process | ||
1517 | + */ | ||
1518 | + void processBgpNotification(ChannelHandlerContext ctx, | ||
1519 | + ChannelBuffer message) { | ||
1520 | + int minLength = | ||
1521 | + BgpConstants.BGP_NOTIFICATION_MIN_LENGTH - BgpConstants.BGP_HEADER_LENGTH; | ||
1522 | + if (message.readableBytes() < minLength) { | ||
1523 | + log.debug("BGP RX NOTIFICATION Error from {}: " + | ||
1524 | + "Message length {} too short. Must be at least {}", | ||
1525 | + remoteAddress, message.readableBytes(), minLength); | ||
1526 | + // | ||
1527 | + // ERROR: Bad Message Length | ||
1528 | + // | ||
1529 | + // NOTE: We do NOT send NOTIFICATION in response to a notification | ||
1530 | + return; | ||
1531 | + } | ||
1532 | + | ||
1533 | + // | ||
1534 | + // Parse the NOTIFICATION message | ||
1535 | + // | ||
1536 | + int errorCode = message.readUnsignedByte(); | ||
1537 | + int errorSubcode = message.readUnsignedByte(); | ||
1538 | + int dataLength = message.readableBytes(); | ||
1539 | + | ||
1540 | + log.debug("BGP RX NOTIFICATION message from {}: Error Code {} " + | ||
1541 | + "Error Subcode {} Data Length {}", | ||
1542 | + remoteAddress, errorCode, errorSubcode, dataLength); | ||
1543 | + | ||
1544 | + // | ||
1545 | + // NOTE: If the peer sent a NOTIFICATION, we leave it to the peer to | ||
1546 | + // close the connection. | ||
1547 | + // | ||
1548 | + | ||
1549 | + // Start the Session Timeout timer | ||
1550 | + restartSessionTimeoutTimer(ctx); | ||
1551 | + } | ||
1552 | + | ||
1553 | + /** | ||
1554 | + * Processes BGP KEEPALIVE message. | ||
1555 | + * | ||
1556 | + * @param ctx the Channel Handler Context | ||
1557 | + * @param message the message to process | ||
1558 | + */ | ||
1559 | + void processBgpKeepalive(ChannelHandlerContext ctx, | ||
1560 | + ChannelBuffer message) { | ||
1561 | + if (message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH != | ||
1562 | + BgpConstants.BGP_KEEPALIVE_EXPECTED_LENGTH) { | ||
1563 | + log.debug("BGP RX KEEPALIVE Error from {}: " + | ||
1564 | + "Invalid total message length {}. Expected {}", | ||
1565 | + remoteAddress, | ||
1566 | + message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH, | ||
1567 | + BgpConstants.BGP_KEEPALIVE_EXPECTED_LENGTH); | ||
1568 | + // | ||
1569 | + // ERROR: Bad Message Length | ||
1570 | + // | ||
1571 | + // Send NOTIFICATION and close the connection | ||
1572 | + ChannelBuffer txMessage = prepareBgpNotificationBadMessageLength( | ||
1573 | + message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH); | ||
1574 | + ctx.getChannel().write(txMessage); | ||
1575 | + closeChannel(ctx); | ||
1576 | + return; | ||
1577 | + } | ||
1578 | + | ||
1579 | + // | ||
1580 | + // Parse the KEEPALIVE message: nothing to do | ||
1581 | + // | ||
1582 | + log.debug("BGP RX KEEPALIVE message from {}", remoteAddress); | ||
1583 | + | ||
1584 | + // Start the Session Timeout timer | ||
1585 | + restartSessionTimeoutTimer(ctx); | ||
1586 | + } | ||
1587 | + | ||
1588 | + /** | ||
1589 | + * Prepares BGP OPEN message. | ||
1590 | + * | ||
1591 | + * @return the message to transmit (BGP header included) | ||
1592 | + */ | ||
1593 | + private ChannelBuffer prepareBgpOpen() { | ||
1594 | + ChannelBuffer message = | ||
1595 | + ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH); | ||
1596 | + | ||
1597 | + // | ||
1598 | + // Prepare the OPEN message payload | ||
1599 | + // | ||
1600 | + message.writeByte(localBgpVersion); | ||
1601 | + message.writeShort((int) localAs); | ||
1602 | + message.writeShort((int) localHoldtime); | ||
1603 | + message.writeInt(bgpSessionManager.getMyBgpId().toRealInt()); | ||
1604 | + message.writeByte(0); // No Optional Parameters | ||
1605 | + return prepareBgpMessage(BgpConstants.BGP_TYPE_OPEN, message); | ||
1606 | + } | ||
1607 | + | ||
1608 | + /** | ||
1609 | + * Prepares BGP KEEPALIVE message. | ||
1610 | + * | ||
1611 | + * @return the message to transmit (BGP header included) | ||
1612 | + */ | ||
1613 | + private ChannelBuffer prepareBgpKeepalive() { | ||
1614 | + ChannelBuffer message = | ||
1615 | + ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH); | ||
1616 | + | ||
1617 | + // | ||
1618 | + // Prepare the KEEPALIVE message payload: nothing to do | ||
1619 | + // | ||
1620 | + return prepareBgpMessage(BgpConstants.BGP_TYPE_KEEPALIVE, message); | ||
1621 | + } | ||
1622 | + | ||
1623 | + /** | ||
1624 | + * Prepares BGP NOTIFICATION message. | ||
1625 | + * | ||
1626 | + * @param errorCode the BGP NOTIFICATION Error Code | ||
1627 | + * @param errorSubcode the BGP NOTIFICATION Error Subcode if applicable, | ||
1628 | + * otherwise BgpConstants.Notifications.ERROR_SUBCODE_UNSPECIFIC | ||
1629 | + * @param payload the BGP NOTIFICATION Data if applicable, otherwise null | ||
1630 | + * @return the message to transmit (BGP header included) | ||
1631 | + */ | ||
1632 | + ChannelBuffer prepareBgpNotification(int errorCode, int errorSubcode, | ||
1633 | + ChannelBuffer data) { | ||
1634 | + ChannelBuffer message = | ||
1635 | + ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH); | ||
1636 | + | ||
1637 | + // | ||
1638 | + // Prepare the NOTIFICATION message payload | ||
1639 | + // | ||
1640 | + message.writeByte(errorCode); | ||
1641 | + message.writeByte(errorSubcode); | ||
1642 | + if (data != null) { | ||
1643 | + message.writeBytes(data); | ||
1644 | + } | ||
1645 | + return prepareBgpMessage(BgpConstants.BGP_TYPE_NOTIFICATION, message); | ||
1646 | + } | ||
1647 | + | ||
1648 | + /** | ||
1649 | + * Prepares BGP NOTIFICATION message: Bad Message Length. | ||
1650 | + * | ||
1651 | + * @param length the erroneous Length field | ||
1652 | + * @return the message to transmit (BGP header included) | ||
1653 | + */ | ||
1654 | + ChannelBuffer prepareBgpNotificationBadMessageLength(int length) { | ||
1655 | + int errorCode = MessageHeaderError.ERROR_CODE; | ||
1656 | + int errorSubcode = MessageHeaderError.BAD_MESSAGE_LENGTH; | ||
1657 | + ChannelBuffer data = ChannelBuffers.buffer(2); | ||
1658 | + data.writeShort(length); | ||
1659 | + | ||
1660 | + return prepareBgpNotification(errorCode, errorSubcode, data); | ||
1661 | + } | ||
1662 | + | ||
1663 | + /** | ||
1664 | + * Prepares BGP UPDATE Notification data payload. | ||
1665 | + * | ||
1666 | + * @param attrTypeCode the attribute type code | ||
1667 | + * @param attrLen the attribute length (in octets) | ||
1668 | + * @param attrFlags the attribute flags | ||
1669 | + * @param message the message with the data | ||
1670 | + * @return the buffer with the data payload for the BGP UPDATE Notification | ||
1671 | + */ | ||
1672 | + private ChannelBuffer prepareBgpUpdateNotificationDataPayload( | ||
1673 | + int attrTypeCode, | ||
1674 | + int attrLen, | ||
1675 | + int attrFlags, | ||
1676 | + ChannelBuffer message) { | ||
1677 | + // Compute the attribute length field octets | ||
1678 | + boolean extendedLengthBit = ((0x10 & attrFlags) != 0); | ||
1679 | + int attrLenOctets = 1; | ||
1680 | + if (extendedLengthBit) { | ||
1681 | + attrLenOctets = 2; | ||
1682 | + } | ||
1683 | + ChannelBuffer data = | ||
1684 | + ChannelBuffers.buffer(attrLen + attrLenOctets + 1); | ||
1685 | + data.writeByte(attrTypeCode); | ||
1686 | + if (extendedLengthBit) { | ||
1687 | + data.writeShort(attrLen); | ||
1688 | + } else { | ||
1689 | + data.writeByte(attrLen); | ||
1690 | + } | ||
1691 | + data.writeBytes(message, attrLen); | ||
1692 | + return data; | ||
1693 | + } | ||
1694 | + | ||
1695 | + /** | ||
1696 | + * Prepares BGP message. | ||
1697 | + * | ||
1698 | + * @param type the BGP message type | ||
1699 | + * @param payload the message payload to transmit (BGP header excluded) | ||
1700 | + * @return the message to transmit (BGP header included) | ||
1701 | + */ | ||
1702 | + private ChannelBuffer prepareBgpMessage(int type, ChannelBuffer payload) { | ||
1703 | + ChannelBuffer message = | ||
1704 | + ChannelBuffers.buffer(BgpConstants.BGP_HEADER_LENGTH + | ||
1705 | + payload.readableBytes()); | ||
1706 | + | ||
1707 | + // Write the marker | ||
1708 | + for (int i = 0; i < BgpConstants.BGP_HEADER_MARKER_LENGTH; i++) { | ||
1709 | + message.writeByte(0xff); | ||
1710 | + } | ||
1711 | + | ||
1712 | + // Write the rest of the BGP header | ||
1713 | + message.writeShort(BgpConstants.BGP_HEADER_LENGTH + | ||
1714 | + payload.readableBytes()); | ||
1715 | + message.writeByte(type); | ||
1716 | + | ||
1717 | + // Write the payload | ||
1718 | + message.writeBytes(payload); | ||
1719 | + return message; | ||
1720 | + } | ||
1721 | + | ||
1722 | + /** | ||
1723 | + * Restarts the BGP KeepaliveTimer. | ||
1724 | + */ | ||
1725 | + private void restartKeepaliveTimer(ChannelHandlerContext ctx) { | ||
1726 | + if (localKeepaliveInterval == 0) { | ||
1727 | + return; // Nothing to do | ||
1728 | + } | ||
1729 | + keepaliveTimeout = timer.newTimeout(new TransmitKeepaliveTask(ctx), | ||
1730 | + localKeepaliveInterval, | ||
1731 | + TimeUnit.SECONDS); | ||
1732 | + } | ||
1733 | + | ||
1734 | + /** | ||
1735 | + * Task class for transmitting KEEPALIVE messages. | ||
1736 | + */ | ||
1737 | + private final class TransmitKeepaliveTask implements TimerTask { | ||
1738 | + private final ChannelHandlerContext ctx; | ||
1739 | + | ||
1740 | + /** | ||
1741 | + * Constructor for given Channel Handler Context. | ||
1742 | + * | ||
1743 | + * @param ctx the Channel Handler Context to use | ||
1744 | + */ | ||
1745 | + TransmitKeepaliveTask(ChannelHandlerContext ctx) { | ||
1746 | + this.ctx = ctx; | ||
1747 | + } | ||
1748 | + | ||
1749 | + @Override | ||
1750 | + public void run(Timeout timeout) throws Exception { | ||
1751 | + if (timeout.isCancelled()) { | ||
1752 | + return; | ||
1753 | + } | ||
1754 | + if (!ctx.getChannel().isOpen()) { | ||
1755 | + return; | ||
1756 | + } | ||
1757 | + | ||
1758 | + // Transmit the KEEPALIVE | ||
1759 | + ChannelBuffer txMessage = prepareBgpKeepalive(); | ||
1760 | + ctx.getChannel().write(txMessage); | ||
1761 | + | ||
1762 | + // Restart the KEEPALIVE timer | ||
1763 | + restartKeepaliveTimer(ctx); | ||
1764 | + } | ||
1765 | + } | ||
1766 | + | ||
1767 | + /** | ||
1768 | + * Restarts the BGP Session Timeout Timer. | ||
1769 | + */ | ||
1770 | + private void restartSessionTimeoutTimer(ChannelHandlerContext ctx) { | ||
1771 | + if (remoteHoldtime == 0) { | ||
1772 | + return; // Nothing to do | ||
1773 | + } | ||
1774 | + if (sessionTimeout != null) { | ||
1775 | + sessionTimeout.cancel(); | ||
1776 | + } | ||
1777 | + sessionTimeout = timer.newTimeout(new SessionTimeoutTask(ctx), | ||
1778 | + remoteHoldtime, | ||
1779 | + TimeUnit.SECONDS); | ||
1780 | + } | ||
1781 | + | ||
1782 | + /** | ||
1783 | + * Task class for BGP Session timeout. | ||
1784 | + */ | ||
1785 | + private final class SessionTimeoutTask implements TimerTask { | ||
1786 | + private final ChannelHandlerContext ctx; | ||
1787 | + | ||
1788 | + /** | ||
1789 | + * Constructor for given Channel Handler Context. | ||
1790 | + * | ||
1791 | + * @param ctx the Channel Handler Context to use | ||
1792 | + */ | ||
1793 | + SessionTimeoutTask(ChannelHandlerContext ctx) { | ||
1794 | + this.ctx = ctx; | ||
1795 | + } | ||
1796 | + | ||
1797 | + @Override | ||
1798 | + public void run(Timeout timeout) throws Exception { | ||
1799 | + if (timeout.isCancelled()) { | ||
1800 | + return; | ||
1801 | + } | ||
1802 | + if (!ctx.getChannel().isOpen()) { | ||
1803 | + return; | ||
1804 | + } | ||
1805 | + | ||
1806 | + log.debug("BGP Session Timeout: peer {}", remoteAddress); | ||
1807 | + // | ||
1808 | + // ERROR: Invalid Optional Parameter Length field: Unspecific | ||
1809 | + // | ||
1810 | + // Send NOTIFICATION and close the connection | ||
1811 | + int errorCode = HoldTimerExpired.ERROR_CODE; | ||
1812 | + int errorSubcode = Notifications.ERROR_SUBCODE_UNSPECIFIC; | ||
1813 | + ChannelBuffer txMessage = | ||
1814 | + prepareBgpNotification(errorCode, errorSubcode, null); | ||
1815 | + ctx.getChannel().write(txMessage); | ||
1816 | + closeChannel(ctx); | ||
1817 | + } | ||
1818 | + } | ||
1819 | + | ||
1820 | + /** | ||
1821 | + * An exception indicating a parsing error of the BGP message. | ||
1822 | + */ | ||
1823 | + private static class BgpParseException extends Exception { | ||
1824 | + /** | ||
1825 | + * Default constructor. | ||
1826 | + */ | ||
1827 | + public BgpParseException() { | ||
1828 | + super(); | ||
1829 | + } | ||
1830 | + | ||
1831 | + /** | ||
1832 | + * Constructor for a specific exception details message. | ||
1833 | + * | ||
1834 | + * @param message the message with the exception details | ||
1835 | + */ | ||
1836 | + public BgpParseException(String message) { | ||
1837 | + super(message); | ||
1838 | + } | ||
1839 | + } | ||
1840 | +} |
1 | +package org.onlab.onos.sdnip.bgp; | ||
2 | + | ||
3 | +import static com.google.common.base.Preconditions.checkNotNull; | ||
4 | + | ||
5 | +import java.net.InetAddress; | ||
6 | +import java.net.InetSocketAddress; | ||
7 | +import java.net.SocketAddress; | ||
8 | +import java.util.Collection; | ||
9 | +import java.util.concurrent.ConcurrentHashMap; | ||
10 | +import java.util.concurrent.ConcurrentMap; | ||
11 | +import java.util.concurrent.Executors; | ||
12 | + | ||
13 | +import org.jboss.netty.bootstrap.ServerBootstrap; | ||
14 | +import org.jboss.netty.channel.Channel; | ||
15 | +import org.jboss.netty.channel.ChannelException; | ||
16 | +import org.jboss.netty.channel.ChannelFactory; | ||
17 | +import org.jboss.netty.channel.ChannelPipeline; | ||
18 | +import org.jboss.netty.channel.ChannelPipelineFactory; | ||
19 | +import org.jboss.netty.channel.Channels; | ||
20 | +import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; | ||
21 | +import org.onlab.onos.sdnip.RouteListener; | ||
22 | +import org.onlab.onos.sdnip.RouteUpdate; | ||
23 | +import org.onlab.packet.IpAddress; | ||
24 | +import org.onlab.packet.IpPrefix; | ||
25 | +import org.slf4j.Logger; | ||
26 | +import org.slf4j.LoggerFactory; | ||
27 | + | ||
28 | +/** | ||
29 | + * BGP Session Manager class. | ||
30 | + */ | ||
31 | +public class BgpSessionManager { | ||
32 | + private static final Logger log = | ||
33 | + LoggerFactory.getLogger(BgpSessionManager.class); | ||
34 | + private Channel serverChannel; // Listener for incoming BGP connections | ||
35 | + private ConcurrentMap<SocketAddress, BgpSession> bgpSessions = | ||
36 | + new ConcurrentHashMap<>(); | ||
37 | + private IpAddress myBgpId; // Same BGP ID for all peers | ||
38 | + | ||
39 | + private BgpRouteSelector bgpRouteSelector = new BgpRouteSelector(); | ||
40 | + private ConcurrentMap<IpPrefix, BgpRouteEntry> bgpRoutes = | ||
41 | + new ConcurrentHashMap<>(); | ||
42 | + | ||
43 | + private final RouteListener routeListener; | ||
44 | + | ||
45 | + /** | ||
46 | + * Constructor for given route listener. | ||
47 | + * | ||
48 | + * @param routeListener the route listener to use | ||
49 | + */ | ||
50 | + public BgpSessionManager(RouteListener routeListener) { | ||
51 | + this.routeListener = checkNotNull(routeListener); | ||
52 | + } | ||
53 | + | ||
54 | + /** | ||
55 | + * Gets the BGP sessions. | ||
56 | + * | ||
57 | + * @return the BGP sessions | ||
58 | + */ | ||
59 | + public Collection<BgpSession> getBgpSessions() { | ||
60 | + return bgpSessions.values(); | ||
61 | + } | ||
62 | + | ||
63 | + /** | ||
64 | + * Gets the BGP routes. | ||
65 | + * | ||
66 | + * @return the BGP routes | ||
67 | + */ | ||
68 | + public Collection<BgpRouteEntry> getBgpRoutes() { | ||
69 | + return bgpRoutes.values(); | ||
70 | + } | ||
71 | + | ||
72 | + /** | ||
73 | + * Processes the connection from a BGP peer. | ||
74 | + * | ||
75 | + * @param bgpSession the BGP session for the peer | ||
76 | + * @return true if the connection can be established, otherwise false | ||
77 | + */ | ||
78 | + boolean peerConnected(BgpSession bgpSession) { | ||
79 | + | ||
80 | + // Test whether there is already a session from the same remote | ||
81 | + if (bgpSessions.get(bgpSession.getRemoteAddress()) != null) { | ||
82 | + return false; // Duplicate BGP session | ||
83 | + } | ||
84 | + bgpSessions.put(bgpSession.getRemoteAddress(), bgpSession); | ||
85 | + | ||
86 | + // | ||
87 | + // If the first connection, set my BGP ID to the local address | ||
88 | + // of the socket. | ||
89 | + // | ||
90 | + if (bgpSession.getLocalAddress() instanceof InetSocketAddress) { | ||
91 | + InetAddress inetAddr = | ||
92 | + ((InetSocketAddress) bgpSession.getLocalAddress()).getAddress(); | ||
93 | + IpAddress ip4Address = IpAddress.valueOf(inetAddr.getAddress()); | ||
94 | + updateMyBgpId(ip4Address); | ||
95 | + } | ||
96 | + return true; | ||
97 | + } | ||
98 | + | ||
99 | + /** | ||
100 | + * Processes the disconnection from a BGP peer. | ||
101 | + * | ||
102 | + * @param bgpSession the BGP session for the peer | ||
103 | + */ | ||
104 | + void peerDisconnected(BgpSession bgpSession) { | ||
105 | + bgpSessions.remove(bgpSession.getRemoteAddress()); | ||
106 | + } | ||
107 | + | ||
108 | + /** | ||
109 | + * Conditionally updates the local BGP ID if it wasn't set already. | ||
110 | + * <p/> | ||
111 | + * NOTE: A BGP instance should use same BGP ID across all BGP sessions. | ||
112 | + * | ||
113 | + * @param ip4Address the IPv4 address to use as BGP ID | ||
114 | + */ | ||
115 | + private synchronized void updateMyBgpId(IpAddress ip4Address) { | ||
116 | + if (myBgpId == null) { | ||
117 | + myBgpId = ip4Address; | ||
118 | + log.debug("BGP: My BGP ID is {}", myBgpId); | ||
119 | + } | ||
120 | + } | ||
121 | + | ||
122 | + /** | ||
123 | + * Gets the local BGP Identifier as an IPv4 address. | ||
124 | + * | ||
125 | + * @return the local BGP Identifier as an IPv4 address | ||
126 | + */ | ||
127 | + IpAddress getMyBgpId() { | ||
128 | + return myBgpId; | ||
129 | + } | ||
130 | + | ||
131 | + /** | ||
132 | + * Gets the BGP Route Selector. | ||
133 | + * | ||
134 | + * @return the BGP Route Selector | ||
135 | + */ | ||
136 | + BgpRouteSelector getBgpRouteSelector() { | ||
137 | + return bgpRouteSelector; | ||
138 | + } | ||
139 | + | ||
140 | + /** | ||
141 | + * Starts up BGP Session Manager operation. | ||
142 | + * | ||
143 | + * @param listenPortNumber the port number to listen on. By default | ||
144 | + * it should be BgpConstants.BGP_PORT (179) | ||
145 | + */ | ||
146 | + public void startUp(int listenPortNumber) { | ||
147 | + log.debug("BGP Session Manager startUp()"); | ||
148 | + | ||
149 | + ChannelFactory channelFactory = | ||
150 | + new NioServerSocketChannelFactory(Executors.newCachedThreadPool(), | ||
151 | + Executors.newCachedThreadPool()); | ||
152 | + ChannelPipelineFactory pipelineFactory = new ChannelPipelineFactory() { | ||
153 | + @Override | ||
154 | + public ChannelPipeline getPipeline() throws Exception { | ||
155 | + // Allocate a new session per connection | ||
156 | + BgpSession bgpSessionHandler = | ||
157 | + new BgpSession(BgpSessionManager.this); | ||
158 | + BgpFrameDecoder bgpFrameDecoder = | ||
159 | + new BgpFrameDecoder(bgpSessionHandler); | ||
160 | + | ||
161 | + // Setup the processing pipeline | ||
162 | + ChannelPipeline pipeline = Channels.pipeline(); | ||
163 | + pipeline.addLast("BgpFrameDecoder", bgpFrameDecoder); | ||
164 | + pipeline.addLast("BgpSession", bgpSessionHandler); | ||
165 | + return pipeline; | ||
166 | + } | ||
167 | + }; | ||
168 | + InetSocketAddress listenAddress = | ||
169 | + new InetSocketAddress(listenPortNumber); | ||
170 | + | ||
171 | + ServerBootstrap serverBootstrap = new ServerBootstrap(channelFactory); | ||
172 | + // serverBootstrap.setOptions("reuseAddr", true); | ||
173 | + serverBootstrap.setOption("child.keepAlive", true); | ||
174 | + serverBootstrap.setOption("child.tcpNoDelay", true); | ||
175 | + serverBootstrap.setPipelineFactory(pipelineFactory); | ||
176 | + try { | ||
177 | + serverChannel = serverBootstrap.bind(listenAddress); | ||
178 | + } catch (ChannelException e) { | ||
179 | + log.debug("Exception binding to BGP port {}: ", | ||
180 | + listenAddress.getPort(), e); | ||
181 | + } | ||
182 | + } | ||
183 | + | ||
184 | + /** | ||
185 | + * Shuts down the BGP Session Manager operation. | ||
186 | + */ | ||
187 | + public void shutDown() { | ||
188 | + // TODO: Complete the implementation: remove routes, etc. | ||
189 | + if (serverChannel != null) { | ||
190 | + serverChannel.close(); | ||
191 | + } | ||
192 | + } | ||
193 | + | ||
194 | + /** | ||
195 | + * Class to receive and process the BGP routes from each BGP Session/Peer. | ||
196 | + */ | ||
197 | + class BgpRouteSelector { | ||
198 | + /** | ||
199 | + * Processes route entry updates: added/updated and deleted route | ||
200 | + * entries. | ||
201 | + * | ||
202 | + * @param bgpSession the BGP session the route entry updates were | ||
203 | + * received on | ||
204 | + * @param addedBgpRouteEntries the added/updated route entries to | ||
205 | + * process | ||
206 | + * @param deletedBgpRouteEntries the deleted route entries to process | ||
207 | + */ | ||
208 | + synchronized void routeUpdates(BgpSession bgpSession, | ||
209 | + Collection<BgpRouteEntry> addedBgpRouteEntries, | ||
210 | + Collection<BgpRouteEntry> deletedBgpRouteEntries) { | ||
211 | + // | ||
212 | + // TODO: Merge the updates from different BGP Peers, | ||
213 | + // by choosing the best route. | ||
214 | + // | ||
215 | + | ||
216 | + // Process the deleted route entries | ||
217 | + for (BgpRouteEntry bgpRouteEntry : deletedBgpRouteEntries) { | ||
218 | + processDeletedRoute(bgpSession, bgpRouteEntry); | ||
219 | + } | ||
220 | + | ||
221 | + // Process the added/updated route entries | ||
222 | + for (BgpRouteEntry bgpRouteEntry : addedBgpRouteEntries) { | ||
223 | + processAddedRoute(bgpSession, bgpRouteEntry); | ||
224 | + } | ||
225 | + } | ||
226 | + | ||
227 | + /** | ||
228 | + * Processes an added/updated route entry. | ||
229 | + * | ||
230 | + * @param bgpSession the BGP session the route entry update was | ||
231 | + * received on | ||
232 | + * @param bgpRouteEntry the added/updated route entry | ||
233 | + */ | ||
234 | + private void processAddedRoute(BgpSession bgpSession, | ||
235 | + BgpRouteEntry bgpRouteEntry) { | ||
236 | + RouteUpdate routeUpdate; | ||
237 | + BgpRouteEntry bestBgpRouteEntry = | ||
238 | + bgpRoutes.get(bgpRouteEntry.prefix()); | ||
239 | + | ||
240 | + // | ||
241 | + // Install the new route entry if it is better than the | ||
242 | + // current best route. | ||
243 | + // | ||
244 | + if ((bestBgpRouteEntry == null) || | ||
245 | + bgpRouteEntry.isBetterThan(bestBgpRouteEntry)) { | ||
246 | + bgpRoutes.put(bgpRouteEntry.prefix(), bgpRouteEntry); | ||
247 | + routeUpdate = | ||
248 | + new RouteUpdate(RouteUpdate.Type.UPDATE, bgpRouteEntry); | ||
249 | + // Forward the result route updates to the Route Listener | ||
250 | + routeListener.update(routeUpdate); | ||
251 | + return; | ||
252 | + } | ||
253 | + | ||
254 | + // | ||
255 | + // If the route entry arrived on the same BGP Session as | ||
256 | + // the current best route, then elect the next best route | ||
257 | + // and install it. | ||
258 | + // | ||
259 | + if (bestBgpRouteEntry.getBgpSession() != | ||
260 | + bgpRouteEntry.getBgpSession()) { | ||
261 | + return; | ||
262 | + } | ||
263 | + | ||
264 | + // Find the next best route | ||
265 | + bestBgpRouteEntry = findBestBgpRoute(bgpRouteEntry.prefix()); | ||
266 | + if (bestBgpRouteEntry == null) { | ||
267 | + // | ||
268 | + // TODO: Shouldn't happen. Install the new route as a | ||
269 | + // pre-caution. | ||
270 | + // | ||
271 | + log.debug("BGP next best route for prefix {} is missing. " + | ||
272 | + "Adding the route that is currently processed.", | ||
273 | + bgpRouteEntry.prefix()); | ||
274 | + bestBgpRouteEntry = bgpRouteEntry; | ||
275 | + } | ||
276 | + // Install the next best route | ||
277 | + bgpRoutes.put(bestBgpRouteEntry.prefix(), bestBgpRouteEntry); | ||
278 | + routeUpdate = new RouteUpdate(RouteUpdate.Type.UPDATE, | ||
279 | + bestBgpRouteEntry); | ||
280 | + // Forward the result route updates to the Route Listener | ||
281 | + routeListener.update(routeUpdate); | ||
282 | + } | ||
283 | + | ||
284 | + /** | ||
285 | + * Processes a deleted route entry. | ||
286 | + * | ||
287 | + * @param bgpSession the BGP session the route entry update was | ||
288 | + * received on | ||
289 | + * @param bgpRouteEntry the deleted route entry | ||
290 | + */ | ||
291 | + private void processDeletedRoute(BgpSession bgpSession, | ||
292 | + BgpRouteEntry bgpRouteEntry) { | ||
293 | + RouteUpdate routeUpdate; | ||
294 | + BgpRouteEntry bestBgpRouteEntry = | ||
295 | + bgpRoutes.get(bgpRouteEntry.prefix()); | ||
296 | + | ||
297 | + // | ||
298 | + // Remove the route entry only if it was the best one. | ||
299 | + // Install the the next best route if it exists. | ||
300 | + // | ||
301 | + // NOTE: We intentionally use "==" instead of method equals(), | ||
302 | + // because we need to check whether this is same object. | ||
303 | + // | ||
304 | + if (bgpRouteEntry != bestBgpRouteEntry) { | ||
305 | + return; // Nothing to do | ||
306 | + } | ||
307 | + | ||
308 | + // | ||
309 | + // Find the next best route | ||
310 | + // | ||
311 | + bestBgpRouteEntry = findBestBgpRoute(bgpRouteEntry.prefix()); | ||
312 | + if (bestBgpRouteEntry != null) { | ||
313 | + // Install the next best route | ||
314 | + bgpRoutes.put(bestBgpRouteEntry.prefix(), | ||
315 | + bestBgpRouteEntry); | ||
316 | + routeUpdate = new RouteUpdate(RouteUpdate.Type.UPDATE, | ||
317 | + bestBgpRouteEntry); | ||
318 | + // Forward the result route updates to the Route Listener | ||
319 | + routeListener.update(routeUpdate); | ||
320 | + return; | ||
321 | + } | ||
322 | + | ||
323 | + // | ||
324 | + // No route found. Remove the route entry | ||
325 | + // | ||
326 | + bgpRoutes.remove(bgpRouteEntry.prefix()); | ||
327 | + routeUpdate = new RouteUpdate(RouteUpdate.Type.DELETE, | ||
328 | + bgpRouteEntry); | ||
329 | + // Forward the result route updates to the Route Listener | ||
330 | + routeListener.update(routeUpdate); | ||
331 | + } | ||
332 | + | ||
333 | + /** | ||
334 | + * Finds the best route entry among all BGP Sessions. | ||
335 | + * | ||
336 | + * @param prefix the prefix of the route | ||
337 | + * @return the best route if found, otherwise null | ||
338 | + */ | ||
339 | + private BgpRouteEntry findBestBgpRoute(IpPrefix prefix) { | ||
340 | + BgpRouteEntry bestRoute = null; | ||
341 | + | ||
342 | + // Iterate across all BGP Sessions and select the best route | ||
343 | + for (BgpSession bgpSession : bgpSessions.values()) { | ||
344 | + BgpRouteEntry route = bgpSession.findBgpRouteEntry(prefix); | ||
345 | + if (route == null) { | ||
346 | + continue; | ||
347 | + } | ||
348 | + if ((bestRoute == null) || route.isBetterThan(bestRoute)) { | ||
349 | + bestRoute = route; | ||
350 | + } | ||
351 | + } | ||
352 | + return bestRoute; | ||
353 | + } | ||
354 | + } | ||
355 | +} |
... | @@ -2,13 +2,15 @@ package org.onlab.packet; | ... | @@ -2,13 +2,15 @@ package org.onlab.packet; |
2 | 2 | ||
3 | import java.util.Arrays; | 3 | import java.util.Arrays; |
4 | 4 | ||
5 | + | ||
6 | + | ||
5 | /** | 7 | /** |
6 | * A class representing an IPv4 address. | 8 | * A class representing an IPv4 address. |
7 | * <p/> | 9 | * <p/> |
8 | * TODO this class is a clone of IpPrefix and still needs to be modified to | 10 | * TODO this class is a clone of IpPrefix and still needs to be modified to |
9 | * look more like an IpAddress. | 11 | * look more like an IpAddress. |
10 | */ | 12 | */ |
11 | -public final class IpAddress { | 13 | +public final class IpAddress implements Comparable<IpAddress> { |
12 | 14 | ||
13 | // TODO a comparator for netmasks? E.g. for sorting by prefix match order. | 15 | // TODO a comparator for netmasks? E.g. for sorting by prefix match order. |
14 | 16 | ||
... | @@ -289,6 +291,13 @@ public final class IpAddress { | ... | @@ -289,6 +291,13 @@ public final class IpAddress { |
289 | } | 291 | } |
290 | 292 | ||
291 | @Override | 293 | @Override |
294 | + public int compareTo(IpAddress o) { | ||
295 | + Long lv = ((long) this.toRealInt()) & 0xffffffffL; | ||
296 | + Long rv = ((long) o.toRealInt()) & 0xffffffffL; | ||
297 | + return lv.compareTo(rv); | ||
298 | + } | ||
299 | + | ||
300 | + @Override | ||
292 | public int hashCode() { | 301 | public int hashCode() { |
293 | final int prime = 31; | 302 | final int prime = 31; |
294 | int result = 1; | 303 | int result = 1; | ... | ... |
-
Please register or login to post a comment