blob: 8d48a27ac30fe6269127f5d82933e438f9f64b2d [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 */
Jonathan Hartab63aac2014-10-16 08:52:55 -070016package org.onlab.onos.sdnip.bgp;
17
18import static com.google.common.base.Preconditions.checkNotNull;
19
20import java.util.ArrayList;
21import java.util.Objects;
22
23import org.onlab.onos.sdnip.RouteEntry;
24import org.onlab.packet.IpAddress;
25import org.onlab.packet.IpPrefix;
26
27import com.google.common.base.MoreObjects;
28
29/**
30 * Represents a route in BGP.
31 */
32public class BgpRouteEntry extends RouteEntry {
33 private final BgpSession bgpSession; // The BGP Session the route was
34 // received on
35 private final byte origin; // Route ORIGIN: IGP, EGP, INCOMPLETE
36 private final AsPath asPath; // The AS Path
37 private final long localPref; // The local preference for the route
38 private long multiExitDisc =
39 BgpConstants.Update.MultiExitDisc.LOWEST_MULTI_EXIT_DISC;
40
41 /**
42 * Class constructor.
43 *
44 * @param bgpSession the BGP Session the route was received on
45 * @param prefix the prefix of the route
46 * @param nextHop the next hop of the route
47 * @param origin the route origin: 0=IGP, 1=EGP, 2=INCOMPLETE
48 * @param asPath the AS path
49 * @param localPref the route local preference
50 */
51 public BgpRouteEntry(BgpSession bgpSession, IpPrefix prefix,
52 IpAddress nextHop, byte origin,
53 BgpRouteEntry.AsPath asPath, long localPref) {
54 super(prefix, nextHop);
55 this.bgpSession = checkNotNull(bgpSession);
56 this.origin = origin;
57 this.asPath = checkNotNull(asPath);
58 this.localPref = localPref;
59 }
60
61 /**
62 * Gets the BGP Session the route was received on.
63 *
64 * @return the BGP Session the route was received on
65 */
66 public BgpSession getBgpSession() {
67 return bgpSession;
68 }
69
70 /**
71 * Gets the route origin: 0=IGP, 1=EGP, 2=INCOMPLETE.
72 *
73 * @return the route origin: 0=IGP, 1=EGP, 2=INCOMPLETE
74 */
75 public byte getOrigin() {
76 return origin;
77 }
78
79 /**
80 * Gets the route AS path.
81 *
82 * @return the route AS path
83 */
84 public BgpRouteEntry.AsPath getAsPath() {
85 return asPath;
86 }
87
88 /**
89 * Gets the route local preference.
90 *
91 * @return the route local preference
92 */
93 public long getLocalPref() {
94 return localPref;
95 }
96
97 /**
98 * Gets the route MED (Multi-Exit Discriminator).
99 *
100 * @return the route MED (Multi-Exit Discriminator)
101 */
102 public long getMultiExitDisc() {
103 return multiExitDisc;
104 }
105
106 /**
107 * Sets the route MED (Multi-Exit Discriminator).
108 *
109 * @param multiExitDisc the route MED (Multi-Exit Discriminator) to set
110 */
111 void setMultiExitDisc(long multiExitDisc) {
112 this.multiExitDisc = multiExitDisc;
113 }
114
115 /**
116 * Tests whether the route is originated from the local AS.
Thomas Vachuska4b420772014-10-30 16:46:17 -0700117 * <p>
Jonathan Hartab63aac2014-10-16 08:52:55 -0700118 * The route is considered originated from the local AS if the AS Path
119 * is empty or if it begins with an AS_SET.
Thomas Vachuska4b420772014-10-30 16:46:17 -0700120 * </p>
Jonathan Hartab63aac2014-10-16 08:52:55 -0700121 *
122 * @return true if the route is originated from the local AS, otherwise
123 * false
124 */
125 boolean isLocalRoute() {
126 if (asPath.getPathSegments().isEmpty()) {
127 return true;
128 }
129 PathSegment firstPathSegment = asPath.getPathSegments().get(0);
130 if (firstPathSegment.getType() == BgpConstants.Update.AsPath.AS_SET) {
131 return true;
132 }
133 return false;
134 }
135
136 /**
137 * Gets the BGP Neighbor AS number the route was received from.
Thomas Vachuska4b420772014-10-30 16:46:17 -0700138 * <p>
Jonathan Hartab63aac2014-10-16 08:52:55 -0700139 * If the router is originated from the local AS, the return value is
140 * zero (BGP_AS_0).
Thomas Vachuska4b420772014-10-30 16:46:17 -0700141 * </p>
Jonathan Hartab63aac2014-10-16 08:52:55 -0700142 *
143 * @return the BGP Neighbor AS number the route was received from.
144 */
145 long getNeighborAs() {
146 if (isLocalRoute()) {
147 return BgpConstants.BGP_AS_0;
148 }
149 PathSegment firstPathSegment = asPath.getPathSegments().get(0);
150 if (firstPathSegment.getSegmentAsNumbers().isEmpty()) {
151 // TODO: Shouldn't happen. Should check during the parsing.
152 return BgpConstants.BGP_AS_0;
153 }
154 return firstPathSegment.getSegmentAsNumbers().get(0);
155 }
156
157 /**
158 * Tests whether the AS Path contains a loop.
Thomas Vachuska4b420772014-10-30 16:46:17 -0700159 * <p>
Jonathan Hartab63aac2014-10-16 08:52:55 -0700160 * The test is done by comparing whether the AS Path contains the
161 * local AS number.
Thomas Vachuska4b420772014-10-30 16:46:17 -0700162 * </p>
Jonathan Hartab63aac2014-10-16 08:52:55 -0700163 *
164 * @param localAsNumber the local AS number to compare against
165 * @return true if the AS Path contains a loop, otherwise false
166 */
167 boolean hasAsPathLoop(long localAsNumber) {
168 for (PathSegment pathSegment : asPath.getPathSegments()) {
169 for (Long asNumber : pathSegment.getSegmentAsNumbers()) {
170 if (asNumber.equals(localAsNumber)) {
171 return true;
172 }
173 }
174 }
175 return false;
176 }
177
178 /**
179 * Compares this BGP route against another BGP route by using the
180 * BGP Decision Process.
Thomas Vachuska4b420772014-10-30 16:46:17 -0700181 * <p>
Jonathan Hartab63aac2014-10-16 08:52:55 -0700182 * NOTE: The comparison needs to be performed only on routes that have
183 * same IP Prefix.
Thomas Vachuska4b420772014-10-30 16:46:17 -0700184 * </p>
Jonathan Hartab63aac2014-10-16 08:52:55 -0700185 *
186 * @param other the BGP route to compare against
187 * @return true if this BGP route is better than the other BGP route
188 * or same, otherwise false
189 */
190 boolean isBetterThan(BgpRouteEntry other) {
191 if (this == other) {
192 return true; // Return true if same route
193 }
194
195 // Compare the LOCAL_PREF values: larger is better
196 if (getLocalPref() != other.getLocalPref()) {
197 return (getLocalPref() > other.getLocalPref());
198 }
199
200 // Compare the AS number in the path: smaller is better
201 if (getAsPath().getAsPathLength() !=
202 other.getAsPath().getAsPathLength()) {
203 return getAsPath().getAsPathLength() <
204 other.getAsPath().getAsPathLength();
205 }
206
207 // Compare the Origin number: lower is better
208 if (getOrigin() != other.getOrigin()) {
209 return (getOrigin() < other.getOrigin());
210 }
211
212 // Compare the MED if the neighbor AS is same: larger is better
213 medLabel: {
214 boolean thisIsLocalRoute = isLocalRoute();
215 if (thisIsLocalRoute != other.isLocalRoute()) {
216 break medLabel; // AS number is different
217 }
218 if (!thisIsLocalRoute) {
219 long thisNeighborAs = getNeighborAs();
220 if (thisNeighborAs != other.getNeighborAs()) {
221 break medLabel; // AS number is different
222 }
223 if (thisNeighborAs == BgpConstants.BGP_AS_0) {
224 break medLabel; // Invalid AS number
225 }
226 }
227
228 // Compare the MED
229 if (getMultiExitDisc() != other.getMultiExitDisc()) {
230 return (getMultiExitDisc() > other.getMultiExitDisc());
231 }
232 }
233
234 // Compare the peer BGP ID: lower is better
235 IpAddress peerBgpId = getBgpSession().getRemoteBgpId();
236 IpAddress otherPeerBgpId = other.getBgpSession().getRemoteBgpId();
237 if (!peerBgpId.equals(otherPeerBgpId)) {
238 return (peerBgpId.compareTo(otherPeerBgpId) < 0);
239 }
240
241 // Compare the peer BGP address: lower is better
242 IpAddress peerAddress = getBgpSession().getRemoteIp4Address();
243 IpAddress otherPeerAddress =
244 other.getBgpSession().getRemoteIp4Address();
245 if (!peerAddress.equals(otherPeerAddress)) {
246 return (peerAddress.compareTo(otherPeerAddress) < 0);
247 }
248
249 return true; // Routes are same. Shouldn't happen?
250 }
251
252 /**
253 * A class to represent AS Path Segment.
254 */
255 public static class PathSegment {
256 private final byte type; // Segment type: AS_SET, AS_SEQUENCE
257 private final ArrayList<Long> segmentAsNumbers; // Segment AS numbers
258
259 /**
260 * Constructor.
261 *
262 * @param type the Path Segment Type: 1=AS_SET, 2=AS_SEQUENCE
263 * @param segmentAsNumbers the Segment AS numbers
264 */
265 PathSegment(byte type, ArrayList<Long> segmentAsNumbers) {
266 this.type = type;
267 this.segmentAsNumbers = checkNotNull(segmentAsNumbers);
268 }
269
270 /**
271 * Gets the Path Segment Type: AS_SET, AS_SEQUENCE.
272 *
273 * @return the Path Segment Type: AS_SET, AS_SEQUENCE
274 */
275 public byte getType() {
276 return type;
277 }
278
279 /**
280 * Gets the Path Segment AS Numbers.
281 *
282 * @return the Path Segment AS Numbers
283 */
284 public ArrayList<Long> getSegmentAsNumbers() {
285 return segmentAsNumbers;
286 }
287
288 @Override
289 public boolean equals(Object other) {
290 if (this == other) {
291 return true;
292 }
293
294 if (!(other instanceof PathSegment)) {
295 return false;
296 }
297
298 PathSegment otherPathSegment = (PathSegment) other;
299 return Objects.equals(this.type, otherPathSegment.type) &&
300 Objects.equals(this.segmentAsNumbers,
301 otherPathSegment.segmentAsNumbers);
302 }
303
304 @Override
305 public int hashCode() {
306 return Objects.hash(type, segmentAsNumbers);
307 }
308
309 @Override
310 public String toString() {
311 return MoreObjects.toStringHelper(getClass())
312 .add("type", this.type)
313 .add("segmentAsNumbers", this.segmentAsNumbers)
314 .toString();
315 }
316 }
317
318 /**
319 * A class to represent AS Path.
320 */
321 public static class AsPath {
322 private final ArrayList<PathSegment> pathSegments;
323 private final int asPathLength; // Precomputed AS Path Length
324
325 /**
326 * Constructor.
327 *
328 * @param pathSegments the Path Segments of the Path
329 */
330 AsPath(ArrayList<PathSegment> pathSegments) {
331 this.pathSegments = checkNotNull(pathSegments);
332
333 //
334 // Precompute the AS Path Length:
335 // - AS_SET counts as 1
336 //
337 int pl = 0;
338 for (PathSegment pathSegment : pathSegments) {
339 if (pathSegment.getType() ==
340 BgpConstants.Update.AsPath.AS_SET) {
341 pl++;
342 continue;
343 }
344 pl += pathSegment.getSegmentAsNumbers().size();
345 }
346 asPathLength = pl;
347 }
348
349 /**
350 * Gets the AS Path Segments.
351 *
352 * @return the AS Path Segments
353 */
354 public ArrayList<PathSegment> getPathSegments() {
355 return pathSegments;
356 }
357
358 /**
359 * Gets the AS Path Length as considered by the BGP Decision Process.
360 *
361 * @return the AS Path Length as considered by the BGP Decision Process
362 */
363 int getAsPathLength() {
364 return asPathLength;
365 }
366
367 @Override
368 public boolean equals(Object other) {
369 if (this == other) {
370 return true;
371 }
372
373 if (!(other instanceof AsPath)) {
374 return false;
375 }
376
377 AsPath otherAsPath = (AsPath) other;
378 return Objects.equals(this.pathSegments, otherAsPath.pathSegments);
379 }
380
381 @Override
382 public int hashCode() {
383 return Objects.hash(pathSegments);
384 }
385
386 @Override
387 public String toString() {
388 return MoreObjects.toStringHelper(getClass())
389 .add("pathSegments", this.pathSegments)
390 .toString();
391 }
392 }
393
394 /**
395 * Compares whether two objects are equal.
Thomas Vachuska4b420772014-10-30 16:46:17 -0700396 * <p>
Jonathan Hartab63aac2014-10-16 08:52:55 -0700397 * NOTE: The bgpSession field is excluded from the comparison.
Thomas Vachuska4b420772014-10-30 16:46:17 -0700398 * </p>
Jonathan Hartab63aac2014-10-16 08:52:55 -0700399 *
400 * @return true if the two objects are equal, otherwise false.
401 */
402 @Override
403 public boolean equals(Object other) {
404 if (this == other) {
405 return true;
406 }
407
408 //
409 // NOTE: Subclasses are considered as change of identity, hence
410 // equals() will return false if the class type doesn't match.
411 //
412 if (other == null || getClass() != other.getClass()) {
413 return false;
414 }
415
416 if (!super.equals(other)) {
417 return false;
418 }
419
420 // NOTE: The bgpSession field is excluded from the comparison
421 BgpRouteEntry otherRoute = (BgpRouteEntry) other;
422 return (this.origin == otherRoute.origin) &&
423 Objects.equals(this.asPath, otherRoute.asPath) &&
424 (this.localPref == otherRoute.localPref) &&
425 (this.multiExitDisc == otherRoute.multiExitDisc);
426 }
427
428 /**
429 * Computes the hash code.
Thomas Vachuska4b420772014-10-30 16:46:17 -0700430 * <p>
Jonathan Hartab63aac2014-10-16 08:52:55 -0700431 * NOTE: We return the base class hash code to avoid expensive computation
Thomas Vachuska4b420772014-10-30 16:46:17 -0700432 * </p>
Jonathan Hartab63aac2014-10-16 08:52:55 -0700433 *
434 * @return the object hash code
435 */
436 @Override
437 public int hashCode() {
438 return super.hashCode();
439 }
440
441 @Override
442 public String toString() {
443 return MoreObjects.toStringHelper(getClass())
444 .add("prefix", prefix())
445 .add("nextHop", nextHop())
446 .add("bgpId", bgpSession.getRemoteBgpId())
447 .add("origin", origin)
448 .add("asPath", asPath)
449 .add("localPref", localPref)
450 .add("multiExitDisc", multiExitDisc)
451 .toString();
452 }
453}