blob: 3abdc7929f05d98e79e1edd2a6ba93345cce5188 [file] [log] [blame]
Thomas Vachuska781d18b2014-10-27 10:31:25 -07001/*
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07002 * Copyright 2014 Open Networking Laboratory
Thomas Vachuska781d18b2014-10-27 10:31:25 -07003 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07004 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
Thomas Vachuska781d18b2014-10-27 10:31:25 -07007 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07008 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
Thomas Vachuska781d18b2014-10-27 10:31:25 -070015 */
Brian O'Connorabafb502014-12-02 22:26:20 -080016package org.onosproject.sdnip.bgp;
Jonathan Hartab63aac2014-10-16 08:52:55 -070017
18import static com.google.common.base.Preconditions.checkNotNull;
19
20import java.util.ArrayList;
21import java.util.Objects;
22
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -080023import org.onlab.packet.IpAddress;
24import org.onlab.packet.IpPrefix;
Pavlin Radoslavov6b570732014-11-06 13:16:45 -080025import org.onlab.packet.Ip4Address;
Jonathan Harte9ef4f32014-12-04 17:49:22 -080026import org.onosproject.sdnip.RouteEntry;
27import org.onosproject.sdnip.bgp.BgpConstants.Update;
Jonathan Hartab63aac2014-10-16 08:52:55 -070028
29import com.google.common.base.MoreObjects;
30
31/**
32 * Represents a route in BGP.
33 */
34public class BgpRouteEntry extends RouteEntry {
35 private final BgpSession bgpSession; // The BGP Session the route was
36 // received on
37 private final byte origin; // Route ORIGIN: IGP, EGP, INCOMPLETE
38 private final AsPath asPath; // The AS Path
39 private final long localPref; // The local preference for the route
Pavlin Radoslavov49eb64d2014-11-10 17:03:19 -080040 private long multiExitDisc = Update.MultiExitDisc.LOWEST_MULTI_EXIT_DISC;
Jonathan Hartab63aac2014-10-16 08:52:55 -070041
42 /**
43 * Class constructor.
44 *
45 * @param bgpSession the BGP Session the route was received on
46 * @param prefix the prefix of the route
47 * @param nextHop the next hop of the route
48 * @param origin the route origin: 0=IGP, 1=EGP, 2=INCOMPLETE
49 * @param asPath the AS path
50 * @param localPref the route local preference
51 */
Pavlin Radoslavov3a0a52e2015-01-06 17:41:37 -080052 public BgpRouteEntry(BgpSession bgpSession, IpPrefix prefix,
53 IpAddress nextHop, byte origin,
Jonathan Hartab63aac2014-10-16 08:52:55 -070054 BgpRouteEntry.AsPath asPath, long localPref) {
55 super(prefix, nextHop);
56 this.bgpSession = checkNotNull(bgpSession);
57 this.origin = origin;
58 this.asPath = checkNotNull(asPath);
59 this.localPref = localPref;
60 }
61
62 /**
63 * Gets the BGP Session the route was received on.
64 *
65 * @return the BGP Session the route was received on
66 */
67 public BgpSession getBgpSession() {
68 return bgpSession;
69 }
70
71 /**
72 * Gets the route origin: 0=IGP, 1=EGP, 2=INCOMPLETE.
73 *
74 * @return the route origin: 0=IGP, 1=EGP, 2=INCOMPLETE
75 */
76 public byte getOrigin() {
77 return origin;
78 }
79
80 /**
81 * Gets the route AS path.
82 *
83 * @return the route AS path
84 */
85 public BgpRouteEntry.AsPath getAsPath() {
86 return asPath;
87 }
88
89 /**
90 * Gets the route local preference.
91 *
92 * @return the route local preference
93 */
94 public long getLocalPref() {
95 return localPref;
96 }
97
98 /**
99 * Gets the route MED (Multi-Exit Discriminator).
100 *
101 * @return the route MED (Multi-Exit Discriminator)
102 */
103 public long getMultiExitDisc() {
104 return multiExitDisc;
105 }
106
107 /**
108 * Sets the route MED (Multi-Exit Discriminator).
109 *
110 * @param multiExitDisc the route MED (Multi-Exit Discriminator) to set
111 */
112 void setMultiExitDisc(long multiExitDisc) {
113 this.multiExitDisc = multiExitDisc;
114 }
115
116 /**
117 * Tests whether the route is originated from the local AS.
Thomas Vachuska4b420772014-10-30 16:46:17 -0700118 * <p>
Jonathan Hartab63aac2014-10-16 08:52:55 -0700119 * The route is considered originated from the local AS if the AS Path
Pavlin Radoslavov49eb64d2014-11-10 17:03:19 -0800120 * is empty or if it begins with an AS_SET (after skipping
121 * AS_CONFED_SEQUENCE and AS_CONFED_SET).
Thomas Vachuska4b420772014-10-30 16:46:17 -0700122 * </p>
Jonathan Hartab63aac2014-10-16 08:52:55 -0700123 *
124 * @return true if the route is originated from the local AS, otherwise
125 * false
126 */
127 boolean isLocalRoute() {
Pavlin Radoslavov49eb64d2014-11-10 17:03:19 -0800128 PathSegment firstPathSegment = null;
129
130 // Find the first Path Segment by ignoring the AS_CONFED_* segments
131 for (PathSegment pathSegment : asPath.getPathSegments()) {
132 if ((pathSegment.getType() == Update.AsPath.AS_SET) ||
133 (pathSegment.getType() == Update.AsPath.AS_SEQUENCE)) {
134 firstPathSegment = pathSegment;
135 break;
136 }
137 }
138 if (firstPathSegment == null) {
139 return true; // Local route: no path segments
140 }
141 // If the first path segment is AS_SET, the route is considered local
142 if (firstPathSegment.getType() == Update.AsPath.AS_SET) {
Jonathan Hartab63aac2014-10-16 08:52:55 -0700143 return true;
144 }
Pavlin Radoslavov49eb64d2014-11-10 17:03:19 -0800145
146 return false; // The route is not local
Jonathan Hartab63aac2014-10-16 08:52:55 -0700147 }
148
149 /**
150 * Gets the BGP Neighbor AS number the route was received from.
Thomas Vachuska4b420772014-10-30 16:46:17 -0700151 * <p>
Jonathan Hartab63aac2014-10-16 08:52:55 -0700152 * If the router is originated from the local AS, the return value is
153 * zero (BGP_AS_0).
Thomas Vachuska4b420772014-10-30 16:46:17 -0700154 * </p>
Jonathan Hartab63aac2014-10-16 08:52:55 -0700155 *
156 * @return the BGP Neighbor AS number the route was received from.
157 */
158 long getNeighborAs() {
Pavlin Radoslavov49eb64d2014-11-10 17:03:19 -0800159 PathSegment firstPathSegment = null;
160
Jonathan Hartab63aac2014-10-16 08:52:55 -0700161 if (isLocalRoute()) {
162 return BgpConstants.BGP_AS_0;
163 }
Pavlin Radoslavov49eb64d2014-11-10 17:03:19 -0800164
165 // Find the first Path Segment by ignoring the AS_CONFED_* segments
166 for (PathSegment pathSegment : asPath.getPathSegments()) {
167 if ((pathSegment.getType() == Update.AsPath.AS_SET) ||
168 (pathSegment.getType() == Update.AsPath.AS_SEQUENCE)) {
169 firstPathSegment = pathSegment;
170 break;
171 }
172 }
173 if (firstPathSegment == null) {
174 // NOTE: Shouldn't happen - should be captured by isLocalRoute()
175 return BgpConstants.BGP_AS_0;
176 }
177
Jonathan Hartab63aac2014-10-16 08:52:55 -0700178 if (firstPathSegment.getSegmentAsNumbers().isEmpty()) {
Jonathan Harte9ef4f32014-12-04 17:49:22 -0800179 // NOTE: Shouldn't happen. Should check during the parsing.
Jonathan Hartab63aac2014-10-16 08:52:55 -0700180 return BgpConstants.BGP_AS_0;
181 }
182 return firstPathSegment.getSegmentAsNumbers().get(0);
183 }
184
185 /**
186 * Tests whether the AS Path contains a loop.
Thomas Vachuska4b420772014-10-30 16:46:17 -0700187 * <p>
Jonathan Hartab63aac2014-10-16 08:52:55 -0700188 * The test is done by comparing whether the AS Path contains the
189 * local AS number.
Thomas Vachuska4b420772014-10-30 16:46:17 -0700190 * </p>
Jonathan Hartab63aac2014-10-16 08:52:55 -0700191 *
192 * @param localAsNumber the local AS number to compare against
193 * @return true if the AS Path contains a loop, otherwise false
194 */
195 boolean hasAsPathLoop(long localAsNumber) {
196 for (PathSegment pathSegment : asPath.getPathSegments()) {
197 for (Long asNumber : pathSegment.getSegmentAsNumbers()) {
198 if (asNumber.equals(localAsNumber)) {
199 return true;
200 }
201 }
202 }
203 return false;
204 }
205
206 /**
207 * Compares this BGP route against another BGP route by using the
208 * BGP Decision Process.
Thomas Vachuska4b420772014-10-30 16:46:17 -0700209 * <p>
Jonathan Hartab63aac2014-10-16 08:52:55 -0700210 * NOTE: The comparison needs to be performed only on routes that have
211 * same IP Prefix.
Thomas Vachuska4b420772014-10-30 16:46:17 -0700212 * </p>
Jonathan Hartab63aac2014-10-16 08:52:55 -0700213 *
214 * @param other the BGP route to compare against
215 * @return true if this BGP route is better than the other BGP route
216 * or same, otherwise false
217 */
218 boolean isBetterThan(BgpRouteEntry other) {
219 if (this == other) {
220 return true; // Return true if same route
221 }
222
223 // Compare the LOCAL_PREF values: larger is better
224 if (getLocalPref() != other.getLocalPref()) {
225 return (getLocalPref() > other.getLocalPref());
226 }
227
228 // Compare the AS number in the path: smaller is better
229 if (getAsPath().getAsPathLength() !=
230 other.getAsPath().getAsPathLength()) {
231 return getAsPath().getAsPathLength() <
232 other.getAsPath().getAsPathLength();
233 }
234
235 // Compare the Origin number: lower is better
236 if (getOrigin() != other.getOrigin()) {
237 return (getOrigin() < other.getOrigin());
238 }
239
240 // Compare the MED if the neighbor AS is same: larger is better
241 medLabel: {
Pavlin Radoslavov49eb64d2014-11-10 17:03:19 -0800242 if (isLocalRoute() || other.isLocalRoute()) {
243 // Compare MEDs for non-local routes only
244 break medLabel;
Jonathan Hartab63aac2014-10-16 08:52:55 -0700245 }
Pavlin Radoslavov49eb64d2014-11-10 17:03:19 -0800246 long thisNeighborAs = getNeighborAs();
247 if (thisNeighborAs != other.getNeighborAs()) {
248 break medLabel; // AS number is different
249 }
250 if (thisNeighborAs == BgpConstants.BGP_AS_0) {
251 break medLabel; // Invalid AS number
Jonathan Hartab63aac2014-10-16 08:52:55 -0700252 }
253
254 // Compare the MED
255 if (getMultiExitDisc() != other.getMultiExitDisc()) {
256 return (getMultiExitDisc() > other.getMultiExitDisc());
257 }
258 }
259
260 // Compare the peer BGP ID: lower is better
Pavlin Radoslavov8a36ce32015-01-28 12:26:57 -0800261 Ip4Address peerBgpId = getBgpSession().remoteInfo().bgpId();
262 Ip4Address otherPeerBgpId = other.getBgpSession().remoteInfo().bgpId();
Jonathan Hartab63aac2014-10-16 08:52:55 -0700263 if (!peerBgpId.equals(otherPeerBgpId)) {
264 return (peerBgpId.compareTo(otherPeerBgpId) < 0);
265 }
266
267 // Compare the peer BGP address: lower is better
Pavlin Radoslavov8a36ce32015-01-28 12:26:57 -0800268 Ip4Address peerAddress = getBgpSession().remoteInfo().ip4Address();
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800269 Ip4Address otherPeerAddress =
Pavlin Radoslavov8a36ce32015-01-28 12:26:57 -0800270 other.getBgpSession().remoteInfo().ip4Address();
Jonathan Hartab63aac2014-10-16 08:52:55 -0700271 if (!peerAddress.equals(otherPeerAddress)) {
272 return (peerAddress.compareTo(otherPeerAddress) < 0);
273 }
274
275 return true; // Routes are same. Shouldn't happen?
276 }
277
278 /**
279 * A class to represent AS Path Segment.
280 */
281 public static class PathSegment {
Pavlin Radoslavov49eb64d2014-11-10 17:03:19 -0800282 // Segment type: AS_SET(1), AS_SEQUENCE(2), AS_CONFED_SEQUENCE(3),
283 // AS_CONFED_SET(4)
284 private final byte type;
Jonathan Hartab63aac2014-10-16 08:52:55 -0700285 private final ArrayList<Long> segmentAsNumbers; // Segment AS numbers
286
287 /**
288 * Constructor.
289 *
Pavlin Radoslavov49eb64d2014-11-10 17:03:19 -0800290 * @param type the Path Segment Type: AS_SET(1), AS_SEQUENCE(2),
291 * AS_CONFED_SEQUENCE(3), AS_CONFED_SET(4)
Jonathan Hartab63aac2014-10-16 08:52:55 -0700292 * @param segmentAsNumbers the Segment AS numbers
293 */
294 PathSegment(byte type, ArrayList<Long> segmentAsNumbers) {
295 this.type = type;
296 this.segmentAsNumbers = checkNotNull(segmentAsNumbers);
297 }
298
299 /**
Pavlin Radoslavov49eb64d2014-11-10 17:03:19 -0800300 * Gets the Path Segment Type: AS_SET(1), AS_SEQUENCE(2),
301 * AS_CONFED_SEQUENCE(3), AS_CONFED_SET(4).
Jonathan Hartab63aac2014-10-16 08:52:55 -0700302 *
Pavlin Radoslavov49eb64d2014-11-10 17:03:19 -0800303 * @return the Path Segment Type: AS_SET(1), AS_SEQUENCE(2),
304 * AS_CONFED_SEQUENCE(3), AS_CONFED_SET(4)
Jonathan Hartab63aac2014-10-16 08:52:55 -0700305 */
306 public byte getType() {
307 return type;
308 }
309
310 /**
311 * Gets the Path Segment AS Numbers.
312 *
313 * @return the Path Segment AS Numbers
314 */
315 public ArrayList<Long> getSegmentAsNumbers() {
316 return segmentAsNumbers;
317 }
318
319 @Override
320 public boolean equals(Object other) {
321 if (this == other) {
322 return true;
323 }
324
325 if (!(other instanceof PathSegment)) {
326 return false;
327 }
328
329 PathSegment otherPathSegment = (PathSegment) other;
330 return Objects.equals(this.type, otherPathSegment.type) &&
331 Objects.equals(this.segmentAsNumbers,
332 otherPathSegment.segmentAsNumbers);
333 }
334
335 @Override
336 public int hashCode() {
337 return Objects.hash(type, segmentAsNumbers);
338 }
339
340 @Override
341 public String toString() {
342 return MoreObjects.toStringHelper(getClass())
Pavlin Radoslavov49eb64d2014-11-10 17:03:19 -0800343 .add("type", Update.AsPath.typeToString(type))
Jonathan Hartab63aac2014-10-16 08:52:55 -0700344 .add("segmentAsNumbers", this.segmentAsNumbers)
345 .toString();
346 }
347 }
348
349 /**
350 * A class to represent AS Path.
351 */
352 public static class AsPath {
353 private final ArrayList<PathSegment> pathSegments;
354 private final int asPathLength; // Precomputed AS Path Length
355
356 /**
357 * Constructor.
358 *
359 * @param pathSegments the Path Segments of the Path
360 */
361 AsPath(ArrayList<PathSegment> pathSegments) {
362 this.pathSegments = checkNotNull(pathSegments);
363
364 //
365 // Precompute the AS Path Length:
366 // - AS_SET counts as 1
Pavlin Radoslavov49eb64d2014-11-10 17:03:19 -0800367 // - AS_SEQUENCE counts how many AS numbers are included
368 // - AS_CONFED_SEQUENCE and AS_CONFED_SET are ignored
Jonathan Hartab63aac2014-10-16 08:52:55 -0700369 //
370 int pl = 0;
371 for (PathSegment pathSegment : pathSegments) {
Pavlin Radoslavov49eb64d2014-11-10 17:03:19 -0800372 switch (pathSegment.getType()) {
373 case Update.AsPath.AS_SET:
374 pl++; // AS_SET counts as 1
375 break;
376 case Update.AsPath.AS_SEQUENCE:
377 // Count each AS number
378 pl += pathSegment.getSegmentAsNumbers().size();
379 break;
380 case Update.AsPath.AS_CONFED_SEQUENCE:
381 break; // Ignore
382 case Update.AsPath.AS_CONFED_SET:
383 break; // Ignore
384 default:
Jonathan Harte9ef4f32014-12-04 17:49:22 -0800385 // NOTE: What to do if the Path Segment type is unknown?
Pavlin Radoslavov49eb64d2014-11-10 17:03:19 -0800386 break;
Jonathan Hartab63aac2014-10-16 08:52:55 -0700387 }
Jonathan Hartab63aac2014-10-16 08:52:55 -0700388 }
389 asPathLength = pl;
390 }
391
392 /**
393 * Gets the AS Path Segments.
394 *
395 * @return the AS Path Segments
396 */
397 public ArrayList<PathSegment> getPathSegments() {
398 return pathSegments;
399 }
400
401 /**
402 * Gets the AS Path Length as considered by the BGP Decision Process.
403 *
404 * @return the AS Path Length as considered by the BGP Decision Process
405 */
406 int getAsPathLength() {
407 return asPathLength;
408 }
409
410 @Override
411 public boolean equals(Object other) {
412 if (this == other) {
413 return true;
414 }
415
416 if (!(other instanceof AsPath)) {
417 return false;
418 }
419
420 AsPath otherAsPath = (AsPath) other;
421 return Objects.equals(this.pathSegments, otherAsPath.pathSegments);
422 }
423
424 @Override
425 public int hashCode() {
426 return Objects.hash(pathSegments);
427 }
428
429 @Override
430 public String toString() {
431 return MoreObjects.toStringHelper(getClass())
432 .add("pathSegments", this.pathSegments)
433 .toString();
434 }
435 }
436
437 /**
438 * Compares whether two objects are equal.
Thomas Vachuska4b420772014-10-30 16:46:17 -0700439 * <p>
Jonathan Hartab63aac2014-10-16 08:52:55 -0700440 * NOTE: The bgpSession field is excluded from the comparison.
Thomas Vachuska4b420772014-10-30 16:46:17 -0700441 * </p>
Jonathan Hartab63aac2014-10-16 08:52:55 -0700442 *
443 * @return true if the two objects are equal, otherwise false.
444 */
445 @Override
446 public boolean equals(Object other) {
447 if (this == other) {
448 return true;
449 }
450
451 //
452 // NOTE: Subclasses are considered as change of identity, hence
453 // equals() will return false if the class type doesn't match.
454 //
455 if (other == null || getClass() != other.getClass()) {
456 return false;
457 }
458
459 if (!super.equals(other)) {
460 return false;
461 }
462
463 // NOTE: The bgpSession field is excluded from the comparison
464 BgpRouteEntry otherRoute = (BgpRouteEntry) other;
465 return (this.origin == otherRoute.origin) &&
466 Objects.equals(this.asPath, otherRoute.asPath) &&
467 (this.localPref == otherRoute.localPref) &&
468 (this.multiExitDisc == otherRoute.multiExitDisc);
469 }
470
471 /**
472 * Computes the hash code.
Thomas Vachuska4b420772014-10-30 16:46:17 -0700473 * <p>
Jonathan Hartab63aac2014-10-16 08:52:55 -0700474 * NOTE: We return the base class hash code to avoid expensive computation
Thomas Vachuska4b420772014-10-30 16:46:17 -0700475 * </p>
Jonathan Hartab63aac2014-10-16 08:52:55 -0700476 *
477 * @return the object hash code
478 */
479 @Override
480 public int hashCode() {
481 return super.hashCode();
482 }
483
484 @Override
485 public String toString() {
486 return MoreObjects.toStringHelper(getClass())
487 .add("prefix", prefix())
488 .add("nextHop", nextHop())
Pavlin Radoslavov8a36ce32015-01-28 12:26:57 -0800489 .add("bgpId", bgpSession.remoteInfo().bgpId())
Pavlin Radoslavov49eb64d2014-11-10 17:03:19 -0800490 .add("origin", Update.Origin.typeToString(origin))
Jonathan Hartab63aac2014-10-16 08:52:55 -0700491 .add("asPath", asPath)
492 .add("localPref", localPref)
493 .add("multiExitDisc", multiExitDisc)
494 .toString();
495 }
496}