blob: 890328a3726919a74751628373d55f199d8ade69 [file] [log] [blame]
package org.onlab.onos.sdnip.bgp;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.ArrayList;
import java.util.Objects;
import org.onlab.onos.sdnip.RouteEntry;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import com.google.common.base.MoreObjects;
/**
* Represents a route in BGP.
*/
public class BgpRouteEntry extends RouteEntry {
private final BgpSession bgpSession; // The BGP Session the route was
// received on
private final byte origin; // Route ORIGIN: IGP, EGP, INCOMPLETE
private final AsPath asPath; // The AS Path
private final long localPref; // The local preference for the route
private long multiExitDisc =
BgpConstants.Update.MultiExitDisc.LOWEST_MULTI_EXIT_DISC;
/**
* Class constructor.
*
* @param bgpSession the BGP Session the route was received on
* @param prefix the prefix of the route
* @param nextHop the next hop of the route
* @param origin the route origin: 0=IGP, 1=EGP, 2=INCOMPLETE
* @param asPath the AS path
* @param localPref the route local preference
*/
public BgpRouteEntry(BgpSession bgpSession, IpPrefix prefix,
IpAddress nextHop, byte origin,
BgpRouteEntry.AsPath asPath, long localPref) {
super(prefix, nextHop);
this.bgpSession = checkNotNull(bgpSession);
this.origin = origin;
this.asPath = checkNotNull(asPath);
this.localPref = localPref;
}
/**
* Gets the BGP Session the route was received on.
*
* @return the BGP Session the route was received on
*/
public BgpSession getBgpSession() {
return bgpSession;
}
/**
* Gets the route origin: 0=IGP, 1=EGP, 2=INCOMPLETE.
*
* @return the route origin: 0=IGP, 1=EGP, 2=INCOMPLETE
*/
public byte getOrigin() {
return origin;
}
/**
* Gets the route AS path.
*
* @return the route AS path
*/
public BgpRouteEntry.AsPath getAsPath() {
return asPath;
}
/**
* Gets the route local preference.
*
* @return the route local preference
*/
public long getLocalPref() {
return localPref;
}
/**
* Gets the route MED (Multi-Exit Discriminator).
*
* @return the route MED (Multi-Exit Discriminator)
*/
public long getMultiExitDisc() {
return multiExitDisc;
}
/**
* Sets the route MED (Multi-Exit Discriminator).
*
* @param multiExitDisc the route MED (Multi-Exit Discriminator) to set
*/
void setMultiExitDisc(long multiExitDisc) {
this.multiExitDisc = multiExitDisc;
}
/**
* Tests whether the route is originated from the local AS.
* <p/>
* The route is considered originated from the local AS if the AS Path
* is empty or if it begins with an AS_SET.
*
* @return true if the route is originated from the local AS, otherwise
* false
*/
boolean isLocalRoute() {
if (asPath.getPathSegments().isEmpty()) {
return true;
}
PathSegment firstPathSegment = asPath.getPathSegments().get(0);
if (firstPathSegment.getType() == BgpConstants.Update.AsPath.AS_SET) {
return true;
}
return false;
}
/**
* Gets the BGP Neighbor AS number the route was received from.
* <p/>
* If the router is originated from the local AS, the return value is
* zero (BGP_AS_0).
*
* @return the BGP Neighbor AS number the route was received from.
*/
long getNeighborAs() {
if (isLocalRoute()) {
return BgpConstants.BGP_AS_0;
}
PathSegment firstPathSegment = asPath.getPathSegments().get(0);
if (firstPathSegment.getSegmentAsNumbers().isEmpty()) {
// TODO: Shouldn't happen. Should check during the parsing.
return BgpConstants.BGP_AS_0;
}
return firstPathSegment.getSegmentAsNumbers().get(0);
}
/**
* Tests whether the AS Path contains a loop.
* <p/>
* The test is done by comparing whether the AS Path contains the
* local AS number.
*
* @param localAsNumber the local AS number to compare against
* @return true if the AS Path contains a loop, otherwise false
*/
boolean hasAsPathLoop(long localAsNumber) {
for (PathSegment pathSegment : asPath.getPathSegments()) {
for (Long asNumber : pathSegment.getSegmentAsNumbers()) {
if (asNumber.equals(localAsNumber)) {
return true;
}
}
}
return false;
}
/**
* Compares this BGP route against another BGP route by using the
* BGP Decision Process.
* <p/>
* NOTE: The comparison needs to be performed only on routes that have
* same IP Prefix.
*
* @param other the BGP route to compare against
* @return true if this BGP route is better than the other BGP route
* or same, otherwise false
*/
boolean isBetterThan(BgpRouteEntry other) {
if (this == other) {
return true; // Return true if same route
}
// Compare the LOCAL_PREF values: larger is better
if (getLocalPref() != other.getLocalPref()) {
return (getLocalPref() > other.getLocalPref());
}
// Compare the AS number in the path: smaller is better
if (getAsPath().getAsPathLength() !=
other.getAsPath().getAsPathLength()) {
return getAsPath().getAsPathLength() <
other.getAsPath().getAsPathLength();
}
// Compare the Origin number: lower is better
if (getOrigin() != other.getOrigin()) {
return (getOrigin() < other.getOrigin());
}
// Compare the MED if the neighbor AS is same: larger is better
medLabel: {
boolean thisIsLocalRoute = isLocalRoute();
if (thisIsLocalRoute != other.isLocalRoute()) {
break medLabel; // AS number is different
}
if (!thisIsLocalRoute) {
long thisNeighborAs = getNeighborAs();
if (thisNeighborAs != other.getNeighborAs()) {
break medLabel; // AS number is different
}
if (thisNeighborAs == BgpConstants.BGP_AS_0) {
break medLabel; // Invalid AS number
}
}
// Compare the MED
if (getMultiExitDisc() != other.getMultiExitDisc()) {
return (getMultiExitDisc() > other.getMultiExitDisc());
}
}
// Compare the peer BGP ID: lower is better
IpAddress peerBgpId = getBgpSession().getRemoteBgpId();
IpAddress otherPeerBgpId = other.getBgpSession().getRemoteBgpId();
if (!peerBgpId.equals(otherPeerBgpId)) {
return (peerBgpId.compareTo(otherPeerBgpId) < 0);
}
// Compare the peer BGP address: lower is better
IpAddress peerAddress = getBgpSession().getRemoteIp4Address();
IpAddress otherPeerAddress =
other.getBgpSession().getRemoteIp4Address();
if (!peerAddress.equals(otherPeerAddress)) {
return (peerAddress.compareTo(otherPeerAddress) < 0);
}
return true; // Routes are same. Shouldn't happen?
}
/**
* A class to represent AS Path Segment.
*/
public static class PathSegment {
private final byte type; // Segment type: AS_SET, AS_SEQUENCE
private final ArrayList<Long> segmentAsNumbers; // Segment AS numbers
/**
* Constructor.
*
* @param type the Path Segment Type: 1=AS_SET, 2=AS_SEQUENCE
* @param segmentAsNumbers the Segment AS numbers
*/
PathSegment(byte type, ArrayList<Long> segmentAsNumbers) {
this.type = type;
this.segmentAsNumbers = checkNotNull(segmentAsNumbers);
}
/**
* Gets the Path Segment Type: AS_SET, AS_SEQUENCE.
*
* @return the Path Segment Type: AS_SET, AS_SEQUENCE
*/
public byte getType() {
return type;
}
/**
* Gets the Path Segment AS Numbers.
*
* @return the Path Segment AS Numbers
*/
public ArrayList<Long> getSegmentAsNumbers() {
return segmentAsNumbers;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof PathSegment)) {
return false;
}
PathSegment otherPathSegment = (PathSegment) other;
return Objects.equals(this.type, otherPathSegment.type) &&
Objects.equals(this.segmentAsNumbers,
otherPathSegment.segmentAsNumbers);
}
@Override
public int hashCode() {
return Objects.hash(type, segmentAsNumbers);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(getClass())
.add("type", this.type)
.add("segmentAsNumbers", this.segmentAsNumbers)
.toString();
}
}
/**
* A class to represent AS Path.
*/
public static class AsPath {
private final ArrayList<PathSegment> pathSegments;
private final int asPathLength; // Precomputed AS Path Length
/**
* Constructor.
*
* @param pathSegments the Path Segments of the Path
*/
AsPath(ArrayList<PathSegment> pathSegments) {
this.pathSegments = checkNotNull(pathSegments);
//
// Precompute the AS Path Length:
// - AS_SET counts as 1
//
int pl = 0;
for (PathSegment pathSegment : pathSegments) {
if (pathSegment.getType() ==
BgpConstants.Update.AsPath.AS_SET) {
pl++;
continue;
}
pl += pathSegment.getSegmentAsNumbers().size();
}
asPathLength = pl;
}
/**
* Gets the AS Path Segments.
*
* @return the AS Path Segments
*/
public ArrayList<PathSegment> getPathSegments() {
return pathSegments;
}
/**
* Gets the AS Path Length as considered by the BGP Decision Process.
*
* @return the AS Path Length as considered by the BGP Decision Process
*/
int getAsPathLength() {
return asPathLength;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof AsPath)) {
return false;
}
AsPath otherAsPath = (AsPath) other;
return Objects.equals(this.pathSegments, otherAsPath.pathSegments);
}
@Override
public int hashCode() {
return Objects.hash(pathSegments);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(getClass())
.add("pathSegments", this.pathSegments)
.toString();
}
}
/**
* Compares whether two objects are equal.
* <p/>
* NOTE: The bgpSession field is excluded from the comparison.
*
* @return true if the two objects are equal, otherwise false.
*/
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
//
// NOTE: Subclasses are considered as change of identity, hence
// equals() will return false if the class type doesn't match.
//
if (other == null || getClass() != other.getClass()) {
return false;
}
if (!super.equals(other)) {
return false;
}
// NOTE: The bgpSession field is excluded from the comparison
BgpRouteEntry otherRoute = (BgpRouteEntry) other;
return (this.origin == otherRoute.origin) &&
Objects.equals(this.asPath, otherRoute.asPath) &&
(this.localPref == otherRoute.localPref) &&
(this.multiExitDisc == otherRoute.multiExitDisc);
}
/**
* Computes the hash code.
* <p/>
* NOTE: We return the base class hash code to avoid expensive computation
*
* @return the object hash code
*/
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public String toString() {
return MoreObjects.toStringHelper(getClass())
.add("prefix", prefix())
.add("nextHop", nextHop())
.add("bgpId", bgpSession.getRemoteBgpId())
.add("origin", origin)
.add("asPath", asPath)
.add("localPref", localPref)
.add("multiExitDisc", multiExitDisc)
.toString();
}
}