blob: 890328a3726919a74751628373d55f199d8ade69 [file] [log] [blame]
Jonathan Hartab63aac2014-10-16 08:52:55 -07001package org.onlab.onos.sdnip.bgp;
2
3import static com.google.common.base.Preconditions.checkNotNull;
4
5import java.util.ArrayList;
6import java.util.Objects;
7
8import org.onlab.onos.sdnip.RouteEntry;
9import org.onlab.packet.IpAddress;
10import org.onlab.packet.IpPrefix;
11
12import com.google.common.base.MoreObjects;
13
14/**
15 * Represents a route in BGP.
16 */
17public 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}