Port the BGP implementation of SDN-IP.
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpRouteEntry.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpRouteEntry.java
new file mode 100644
index 0000000..890328a
--- /dev/null
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpRouteEntry.java
@@ -0,0 +1,432 @@
+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();
+ }
+}