OSPF protocol manual merge from 1.6, due to cherry pick merge conflict
Change-Id: I93653e745468722ce95533537a79e897b4292f5d
diff --git a/protocols/ospf/ctl/src/main/java/org/onosproject/ospf/controller/area/OspfAreaImpl.java b/protocols/ospf/ctl/src/main/java/org/onosproject/ospf/controller/area/OspfAreaImpl.java
index f4c70c0..9a7bd47 100644
--- a/protocols/ospf/ctl/src/main/java/org/onosproject/ospf/controller/area/OspfAreaImpl.java
+++ b/protocols/ospf/ctl/src/main/java/org/onosproject/ospf/controller/area/OspfAreaImpl.java
@@ -21,7 +21,6 @@
import org.onlab.packet.Ip4Address;
import org.onosproject.ospf.controller.LsaWrapper;
import org.onosproject.ospf.controller.OspfArea;
-import org.onosproject.ospf.controller.OspfAreaAddressRange;
import org.onosproject.ospf.controller.OspfInterface;
import org.onosproject.ospf.controller.OspfLsa;
import org.onosproject.ospf.controller.OspfLsaType;
@@ -54,28 +53,14 @@
public class OspfAreaImpl implements OspfArea {
private static final Logger log = LoggerFactory.getLogger(OspfAreaImpl.class);
/**
- * Address ranges in order to aggregate routing information at area.
- * boundaries. Each address range is specified by an [address,mask] pair and
- * a status indication of either Advertise or DoNotAdvertise
- */
- private List<OspfAreaAddressRange> addressRanges;
- /**
- * This parameter indicates whether the area can carry data traffic that.
- * neither originates nor terminates in the area itself.
- */
- private boolean transitCapability;
- /**
* Whether AS-external-LSAs will be flooded into/throughout the area.
*/
private boolean externalRoutingCapability;
- /**
- * Indicates the cost of the default summary-LSA.
- */
- private int stubCost;
+
/**
* Represents a list of all router's interfaces associated with this area.
*/
- private List<OspfInterface> interfacesLst;
+ private List<OspfInterface> ospfInterfaceList;
/**
* The LS Database for this area. It includes router-LSAs, network-LSAs and.
* summary-LSAs. AS-external-LSAs are hold in the OSPF class itself.
@@ -116,18 +101,15 @@
OspfAreaImpl that = (OspfAreaImpl) o;
return Objects.equal(areaId, that.areaId) &&
Objects.equal(routerId, that.routerId) &&
- Objects.equal(addressRanges.size(), that.addressRanges.size()) &&
- Objects.equal(transitCapability, that.transitCapability) &&
Objects.equal(externalRoutingCapability, that.externalRoutingCapability) &&
- Objects.equal(stubCost, that.stubCost) &&
- Objects.equal(interfacesLst.size(), that.interfacesLst.size()) &&
+ Objects.equal(ospfInterfaceList.size(), that.ospfInterfaceList.size()) &&
Objects.equal(database, that.database);
}
@Override
public int hashCode() {
- return Objects.hashCode(areaId, routerId, addressRanges, transitCapability, externalRoutingCapability,
- stubCost, interfacesLst, database);
+ return Objects.hashCode(areaId, routerId, externalRoutingCapability,
+ ospfInterfaceList, database);
}
/**
@@ -234,7 +216,7 @@
networkLsa.setNetworkMask(mask);
//Adding our own router.
networkLsa.addAttachedRouter(routerId());
- Iterator iter = interfacesLst.iterator();
+ Iterator iter = ospfInterfaceList.iterator();
OspfInterfaceImpl ospfInterface = null;
while (iter.hasNext()) {
ospfInterface = (OspfInterfaceImpl) iter.next();
@@ -310,7 +292,7 @@
*/
private void buildLinkForRouterLsa(RouterLsa routerLsa, OspfInterface ospfInterface) {
OspfInterfaceImpl nextInterface;
- Iterator interfaces = interfacesLst.iterator();
+ Iterator interfaces = ospfInterfaceList.iterator();
while (interfaces.hasNext()) {
nextInterface = (OspfInterfaceImpl) interfaces.next();
if (nextInterface.state() == OspfInterfaceState.DOWN) {
@@ -418,44 +400,6 @@
}
/**
- * Gets address range.
- *
- * @return list of area address ranges
- */
- public List<OspfAreaAddressRange> addressRanges() {
- return addressRanges;
- }
-
- /**
- * Sets the area address ranges.
- *
- * @param addressRanges list of area address range
- */
- @JsonProperty("addressRange")
- public void setAddressRanges(List<OspfAreaAddressRange> addressRanges) {
- this.addressRanges = addressRanges;
- }
-
- /**
- * Gets is transit capable or not.
- *
- * @return true if transit capable, else false
- */
- public boolean isTransitCapability() {
- return transitCapability;
- }
-
- /**
- * Sets transit capability.
- *
- * @param transitCapability true if transit capable, else false
- */
- @JsonProperty("transitCapability")
- public void setTransitCapability(boolean transitCapability) {
- this.transitCapability = transitCapability;
- }
-
- /**
* Gets external routing capability.
*
* @return true if external routing capable, else false
@@ -475,41 +419,22 @@
}
/**
- * Gets the stub cost.
- *
- * @return stub cost
- */
- public int stubCost() {
- return stubCost;
- }
-
- /**
- * Sets the stub cost.
- *
- * @param stubCost stub cost
- */
- @JsonProperty("stubCost")
- public void setStubCost(int stubCost) {
- this.stubCost = stubCost;
- }
-
- /**
* Gets the list of interfaces in this area.
*
* @return list of interfaces
*/
- public List<OspfInterface> getInterfacesLst() {
- return interfacesLst;
+ public List<OspfInterface> ospfInterfaceList() {
+ return ospfInterfaceList;
}
/**
* Sets the list of interfaces attached to the area.
*
- * @param interfacesLst list of OspfInterface instances
+ * @param ospfInterfaceList list of OspfInterface instances
*/
@JsonProperty("interface")
- public void setInterfacesLst(List<OspfInterface> interfacesLst) {
- this.interfacesLst = interfacesLst;
+ public void setOspfInterfaceList(List<OspfInterface> ospfInterfaceList) {
+ this.ospfInterfaceList = ospfInterfaceList;
}
/**
@@ -522,7 +447,7 @@
public boolean noNeighborInLsaExchangeProcess() {
OspfInterfaceImpl nextInterface;
OspfNeighborState nextNeighborState;
- Iterator interfaces = interfacesLst.iterator();
+ Iterator interfaces = ospfInterfaceList.iterator();
while (interfaces.hasNext()) {
nextInterface = (OspfInterfaceImpl) interfaces.next();
Iterator neighbors = nextInterface.listOfNeighbors().values().iterator();
@@ -659,19 +584,14 @@
return MoreObjects.toStringHelper(getClass())
.omitNullValues()
.add("areaID", areaId)
- .add("stubCost", stubCost)
- .add("addressRanges", addressRanges)
- .add("interfacesLst", interfacesLst)
- .add("transitCapability", transitCapability)
+ .add("ospfInterfaceList", ospfInterfaceList)
.add("externalRoutingCapability", externalRoutingCapability)
.toString();
}
/**
* Checks all Neighbors belonging to this Area whether they are in state lesser than the EXCHANGE.
- * <p>
* Creates list of such neighbors
- * <p>
* Returns list of neighbors who satisfy the conditions
*
* @param ospfInterface OSPF interface instance
@@ -716,7 +636,7 @@
public void addToOtherNeighborLsaTxList(LsaHeader recLsa) {
//Add the received LSA in other neighbors retransmission list.
log.debug("OspfAreaImpl: addToOtherNeighborLsaTxList");
- List<OspfInterface> ospfInterfaces = getInterfacesLst();
+ List<OspfInterface> ospfInterfaces = ospfInterfaceList();
for (OspfInterface ospfInterfaceFromArea : ospfInterfaces) {
Map neighbors = ospfInterfaceFromArea.listOfNeighbors();
for (Object neighborIP : neighbors.keySet()) {
diff --git a/protocols/ospf/ctl/src/main/java/org/onosproject/ospf/controller/area/OspfInterfaceImpl.java b/protocols/ospf/ctl/src/main/java/org/onosproject/ospf/controller/area/OspfInterfaceImpl.java
index 19f8ef5..b10cf33 100644
--- a/protocols/ospf/ctl/src/main/java/org/onosproject/ospf/controller/area/OspfInterfaceImpl.java
+++ b/protocols/ospf/ctl/src/main/java/org/onosproject/ospf/controller/area/OspfInterfaceImpl.java
@@ -18,47 +18,93 @@
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelFuture;
+import org.jboss.netty.channel.ChannelHandlerContext;
import org.onlab.packet.Ip4Address;
+import org.onosproject.ospf.controller.LsaWrapper;
+import org.onosproject.ospf.controller.OspfArea;
import org.onosproject.ospf.controller.OspfInterface;
+import org.onosproject.ospf.controller.OspfLinkTed;
+import org.onosproject.ospf.controller.OspfLsa;
+import org.onosproject.ospf.controller.OspfMessage;
import org.onosproject.ospf.controller.OspfNbr;
+import org.onosproject.ospf.controller.OspfNeighborState;
+import org.onosproject.ospf.controller.OspfPacketType;
+import org.onosproject.ospf.controller.OspfRouter;
+import org.onosproject.ospf.controller.TopologyForDeviceAndLink;
+import org.onosproject.ospf.controller.impl.Controller;
+import org.onosproject.ospf.controller.impl.OspfNbrImpl;
+import org.onosproject.ospf.controller.impl.TopologyForDeviceAndLinkImpl;
+import org.onosproject.ospf.controller.lsdb.LsaWrapperImpl;
+import org.onosproject.ospf.controller.lsdb.OspfLsdbImpl;
+import org.onosproject.ospf.controller.util.OspfEligibleRouter;
+import org.onosproject.ospf.controller.util.OspfInterfaceType;
import org.onosproject.ospf.protocol.lsa.LsaHeader;
import org.onosproject.ospf.protocol.lsa.OpaqueLsaHeader;
+import org.onosproject.ospf.protocol.ospfpacket.OspfMessageWriter;
+import org.onosproject.ospf.protocol.ospfpacket.OspfPacketHeader;
+import org.onosproject.ospf.protocol.ospfpacket.subtype.LsRequestPacket;
+import org.onosproject.ospf.protocol.ospfpacket.types.DdPacket;
+import org.onosproject.ospf.protocol.ospfpacket.types.HelloPacket;
+import org.onosproject.ospf.protocol.ospfpacket.types.LsAcknowledge;
+import org.onosproject.ospf.protocol.ospfpacket.types.LsRequest;
+import org.onosproject.ospf.protocol.ospfpacket.types.LsUpdate;
+import org.onosproject.ospf.protocol.util.ChecksumCalculator;
import org.onosproject.ospf.protocol.util.OspfInterfaceState;
import org.onosproject.ospf.protocol.util.OspfParameters;
+import org.onosproject.ospf.protocol.util.OspfUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
/**
* Representation of an OSPF interface.
*/
public class OspfInterfaceImpl implements OspfInterface {
private static final Logger log = LoggerFactory.getLogger(OspfInterfaceImpl.class);
+ private int interfaceIndex;
private Ip4Address ipAddress;
private Ip4Address ipNetworkMask;
- private int areaId;
+ private Channel channel = null;
private int helloIntervalTime;
private int routerDeadIntervalTime;
- private int transmitDelay;
private int routerPriority;
- private int systemInterfaceType;
private int interfaceType;
- private int interfaceCost;
- private String authType;
- private String authKey;
- private int pollInterval;
private int mtu;
private int reTransmitInterval;
private Ip4Address dr;
private Ip4Address bdr;
private OspfInterfaceState state;
private List<LsaHeader> linkStateHeaders = new ArrayList<>();
- private HashMap<String, OspfNbr> listOfNeighbors = new HashMap<>();
- private HashMap<String, LsaHeader> listOfNeighborMap = new HashMap<>();
+ private Map<String, OspfNbr> listOfNeighbors = new ConcurrentHashMap<>();
+ private Map<String, LsaHeader> listOfNeighborMap = new ConcurrentHashMap<>();
+ private long delay = 0;
+ private InternalHelloTimer helloTimerTask;
+ private InternalWaitTimer waitTimerTask;
+ private InternalDelayedAckTimer delayedAckTimerTask;
+ private ScheduledExecutorService exServiceHello;
+ private ScheduledExecutorService exServiceWait;
+ private ScheduledExecutorService exServiceDelayedAck;
+ private boolean isDelayedAckTimerScheduled = false;
+ private int delayedAckTimerInterval = 2500;
+ private int interfaceTypeOldValue = 0;
+ private TopologyForDeviceAndLink topologyForDeviceAndLink = new TopologyForDeviceAndLinkImpl();
+ private OspfArea ospfArea;
+ private Controller controller;
+
/**
* Gets the interface state.
@@ -79,6 +125,51 @@
}
/**
+ * Sets the netty channel.
+ *
+ * @param channel channel
+ */
+ public void setChannel(Channel channel) {
+ this.channel = channel;
+ }
+
+ /**
+ * Returns OSPF area instance.
+ *
+ * @return OSPF area instance
+ */
+ public OspfArea ospfArea() {
+ return ospfArea;
+ }
+
+ /**
+ * Sets OSPF controller instance.
+ *
+ * @param controller OSPF controller instance
+ */
+ public void setController(Controller controller) {
+ this.controller = controller;
+ }
+
+ /**
+ * Sets OSPF area instance.
+ *
+ * @param ospfArea OSPF area instance
+ */
+ public void setOspfArea(OspfArea ospfArea) {
+ this.ospfArea = ospfArea;
+ }
+
+ /**
+ * Gets interface state.
+ *
+ * @return interface state
+ */
+ public String interfaceState() {
+ return state.interfaceState();
+ }
+
+ /**
* Gets link state headers.
*
* @return get the list of lsa headers
@@ -130,9 +221,36 @@
return listOfNeighbors.get(neighborId);
}
+ /**
+ * Removes all the neighbors.
+ */
+ public void removeNeighbors() {
+ Set<String> neighbors = listOfNeighbors.keySet();
+ for (String neighborId : neighbors) {
+ removeNeighbor(listOfNeighbors.get(neighborId));
+ log.debug("Neighbor removed - {}", neighborId);
+ }
+ listOfNeighbors.clear();
+ }
/**
- * Adds LSAHeader to map.
+ * Removes neighbor from the interface neighbor map.
+ *
+ * @param ospfNeighbor OSPF neighbor instance
+ */
+ public void removeNeighbor(OspfNbr ospfNeighbor) {
+ log.debug("Neighbor removed - {}", ospfNeighbor.neighborId());
+ ospfNeighbor.stopInactivityTimeCheck();
+ ospfNeighbor.stopFloodingTimer();
+ ospfNeighbor.stopRxMtDdTimer();
+ ospfNeighbor.stopRxMtLsrTimer();
+
+ listOfNeighbors.remove(ospfNeighbor.neighborId());
+ }
+
+
+ /**
+ * Adds LSA header to map.
*
* @param lsaHeader LSA header instance
*/
@@ -175,7 +293,7 @@
*
* @return listOfNeighbors as key value pair
*/
- public HashMap<String, OspfNbr> listOfNeighbors() {
+ public Map<String, OspfNbr> listOfNeighbors() {
return listOfNeighbors;
}
@@ -189,6 +307,24 @@
}
/**
+ * Returns interface index.
+ *
+ * @return interface index
+ */
+ public int interfaceIndex() {
+ return interfaceIndex;
+ }
+
+ /**
+ * Set interface index.
+ *
+ * @param interfaceIndex interface index
+ */
+ public void setInterfaceIndex(int interfaceIndex) {
+ this.interfaceIndex = interfaceIndex;
+ }
+
+ /**
* Gets the IP address.
*
* @return IP address
@@ -225,25 +361,6 @@
}
/**
- * Gets the area id this interface belongs.
- *
- * @return area id this interface belongs
- */
- public int areaId() {
- return areaId;
- }
-
-
- /**
- * Sets the area id this interface belongs.
- *
- * @param areaId the area id this interface belongs
- */
- public void setAreaId(int areaId) {
- this.areaId = areaId;
- }
-
- /**
* Gets hello interval time.
*
* @return hello interval time
@@ -298,78 +415,6 @@
}
/**
- * Gets interface cost.
- *
- * @return interface cost
- */
- public int interfaceCost() {
- return interfaceCost;
- }
-
- /**
- * Sets interface cost.
- *
- * @param interfaceCost interface cost
- */
- public void setInterfaceCost(int interfaceCost) {
- this.interfaceCost = interfaceCost;
- }
-
- /**
- * Gets authentication type.
- *
- * @return authType represents authentication type
- */
- public String authType() {
- return authType;
- }
-
- /**
- * Sets authentication type.
- *
- * @param authType authType represents authentication type
- */
- public void setAuthType(String authType) {
- this.authType = authType;
- }
-
- /**
- * Gets authentication key.
- *
- * @return authKey represents authentication key
- */
- public String authKey() {
- return authKey;
- }
-
- /**
- * Sets authentication key.
- *
- * @param authKey represents authentication key
- */
- public void setAuthKey(String authKey) {
- this.authKey = authKey;
- }
-
- /**
- * Gets poll interval.
- *
- * @return pollInterval an integer represents poll interval
- */
- public int pollInterval() {
- return pollInterval;
- }
-
- /**
- * Sets poll interval.
- *
- * @param pollInterval an integer represents poll interval
- */
- public void setPollInterval(int pollInterval) {
- this.pollInterval = pollInterval;
- }
-
- /**
* Gets max transfer unit.
*
* @return mtu an integer represents max transfer unit
@@ -442,23 +487,1070 @@
}
/**
- * Get transmission delay.
+ * Represents an interface is up and connected.
*
- * @return transmission delay
+ * @throws Exception might throws exception
*/
- public int transmitDelay() {
- return transmitDelay;
+ public void interfaceUp() throws Exception {
+ log.debug("OSPFInterfaceChannelHandler::interfaceUp...!!!");
+ if (interfaceType() == OspfInterfaceType.POINT_TO_POINT.value()) {
+ setState(OspfInterfaceState.POINT2POINT);
+ interfaceTypeOldValue = interfaceType();
+ log.debug("OSPFInterfaceChannelHandler::InterfaceType {} state {} ",
+ interfaceType(), state());
+ } else if (interfaceType() == OspfInterfaceType.BROADCAST.value()) {
+ //if router priority is 0, move the state to DROther
+ interfaceTypeOldValue = interfaceType();
+ if (routerPriority() == 0) {
+ setState(OspfInterfaceState.DROTHER);
+ } else {
+ log.debug("OSPFInterfaceChannelHandler::InterfaceType {} state {} RouterPriority {}",
+ interfaceType(),
+ state(), routerPriority());
+ setState(OspfInterfaceState.WAITING);
+ //start wait timer - like inactivity timer with router deadInterval
+ startWaitTimer();
+ }
+ }
+ // Start hello timer with interval from config - convert seconds to milliseconds
+ startHelloTimer();
+ ospfArea.refreshArea(this);
+ }
+
+
+ /**
+ * Gets called when a BDR was detected before the wait timer expired.
+ *
+ * @param ch channel instance
+ * @throws Exception might throws exception
+ */
+ public void backupSeen(Channel ch) throws Exception {
+ log.debug("OSPFInterfaceChannelHandler::backupSeen ");
+ if (state() == OspfInterfaceState.WAITING) {
+ electRouter(ch);
+ }
}
/**
- * Sets transmission delay.
+ * Gets called when no hello message received for particular period.
*
- * @param transmitDelay transmission delay
+ * @param ch channel instance
+ * @throws Exception might throws exception
*/
- public void setTransmitDelay(int transmitDelay) {
- this.transmitDelay = transmitDelay;
+ public void waitTimer(Channel ch) throws Exception {
+ log.debug("OSPFInterfaceChannelHandler::waitTimer ");
+ //According to RFC-2328 section 9.4
+ if (state() == OspfInterfaceState.WAITING) {
+ electRouter(ch);
+ }
}
+ /**
+ * Initiates DR election process.
+ *
+ * @param ch netty channel instance
+ * @throws Exception might throws exception
+ */
+ public void callDrElection(Channel ch) throws Exception {
+ log.debug("OSPFInterfaceChannelHandler::callDrElection ");
+ //call when timer expired
+ //no hello message received for particular interval
+ //section 9.4
+ electRouter(ch);
+ interfaceTypeOldValue = interfaceType();
+ }
+
+ /**
+ * Neighbor change event is triggered when the router priority gets changed.
+ *
+ * @throws Exception might throws exception
+ */
+ public void neighborChange() throws Exception {
+ log.debug("OSPFInterfaceChannelHandler::neighborChange ");
+ if (state() == OspfInterfaceState.DR || state() == OspfInterfaceState.BDR ||
+ state() == OspfInterfaceState.DROTHER) {
+ electRouter(channel);
+ }
+ }
+
+ /**
+ * Gets called when an interface is down.
+ * All interface variables are reset, and interface timers disabled.
+ * Also all neighbor connections associated with the interface are destroyed.
+ */
+ public void interfaceDown() {
+ log.debug("OSPFInterfaceChannelHandler::interfaceDown ");
+ stopHelloTimer();
+ listOfNeighbors().clear();
+ setState(OspfInterfaceState.DOWN);
+ }
+
+ /**
+ * When an OSPF message received it is handed over to this method.
+ * Based on the type of the OSPF message received it will be handed over
+ * to corresponding message handler methods.
+ *
+ * @param ospfMessage received OSPF message
+ * @param ctx channel handler context instance.
+ * @throws Exception might throws exception
+ */
+ public void processOspfMessage(OspfMessage ospfMessage, ChannelHandlerContext ctx) throws Exception {
+ log.debug("OspfChannelHandler::processOspfMessage...!!!");
+
+ if (!validateMessage(ospfMessage)) {
+ return;
+ }
+
+ switch (ospfMessage.ospfMessageType().value()) {
+ case OspfParameters.HELLO:
+ processHelloMessage(ospfMessage, ctx);
+ break;
+ case OspfParameters.DD:
+ processDdMessage(ospfMessage, ctx);
+ break;
+ case OspfParameters.LSREQUEST:
+ processLsRequestMessage(ospfMessage, ctx);
+ break;
+ case OspfParameters.LSUPDATE:
+ processLsUpdateMessage(ospfMessage, ctx);
+ break;
+ case OspfParameters.LSACK:
+ processLsAckMessage(ospfMessage, ctx);
+ break;
+ default:
+ log.debug("Unknown packet to process...!!!");
+ break;
+ }
+ }
+
+ /**
+ * Validates the OSPF message received.
+ *
+ * @param ospfMessage OSPF message.
+ * @return true if it is a valid else false.
+ * @throws Exception might throws exception
+ */
+ private boolean validateMessage(OspfMessage ospfMessage) throws Exception {
+ boolean isValid = true;
+ OspfPacketHeader header = (OspfPacketHeader) ospfMessage;
+
+ //added the check to eliminate self origin packets also two interfaces on same router.
+ if (!header.sourceIp().equals(ipAddress()) && !header.routerId().equals(
+ ospfArea.routerId())) {
+ //Verify the checksum
+ ChecksumCalculator checksum = new ChecksumCalculator();
+ if (!checksum.isValidOspfCheckSum(ospfMessage, OspfUtil.OSPFPACKET_CHECKSUM_POS1,
+ OspfUtil.OSPFPACKET_CHECKSUM_POS2)) {
+ log.debug("Checksum mismatch. Received packet type {} ", ospfMessage.ospfMessageType());
+ return false;
+ }
+ if (((OspfPacketHeader) ospfMessage).ospfVersion() != OspfUtil.OSPF_VERSION_2) {
+ log.debug("Received osfpMessage Version should match with Interface Version ");
+ return false;
+ }
+ if (!((OspfPacketHeader) ospfMessage).areaId().equals(ospfArea.areaId())) {
+ log.debug("Received ospf packets are from different area than our Area ID. " +
+ "Received Area ID {}, Our AreaId {} ",
+ ((OspfPacketHeader) ospfMessage).areaId(), ospfArea.areaId());
+ return false;
+ }
+
+ //According to RFC-2328 (8.2)
+ /**
+ * ABR should receive packets from backbone 0.0.0.0 as we are not acting as ABR
+ * we are rejecting the packet.
+ */
+ if (((OspfPacketHeader) ospfMessage).areaId().equals(Ip4Address.valueOf("0.0.0.0"))) {
+ log.debug("ABR should receive packets from backbone 0.0.0.0 as we are not acting as " +
+ "ABR we are rejecting the ospf packet");
+ return false;
+ }
+ if (interfaceType() == OspfInterfaceType.BROADCAST.value() &&
+ !OspfUtil.sameNetwork(((OspfPacketHeader) ospfMessage).sourceIp(),
+ ipAddress(), ipNetworkMask())) {
+ log.debug("Received packets from different subnets. Discarding...!!!");
+ return false;
+ }
+ } else {
+ isValid = false;
+ }
+
+ return isValid;
+ }
+
+ /**
+ * Processes Hello message.
+ *
+ * @param ospfMessage OSPF message instance.
+ * @param ctx context instance.
+ * @throws Exception might throws exception
+ */
+ void processHelloMessage(OspfMessage ospfMessage, ChannelHandlerContext ctx) throws Exception {
+ Channel channel = ctx.getChannel();
+ log.debug("OspfChannelHandler::processHelloMessage...!!!");
+ HelloPacket helloPacket = (HelloPacket) ospfMessage;
+
+ // processing of hello packet as per RFC 2328 section 10.5
+ log.debug("OspfChannelHandler::processHelloMessage::Interface Type {} OSPFInterfaceState {} ",
+ interfaceType(), state());
+
+ if (interfaceType() != OspfInterfaceType.POINT_TO_POINT.value()) {
+ if (!helloPacket.networkMask().equals(ipNetworkMask())) {
+ log.debug("OspfChannelHandler::processHelloMessage::Hello Packet Received does not " +
+ "match the same network mask as the configure Interface");
+ return;
+ }
+ }
+ if (helloPacket.helloInterval() != helloIntervalTime()) {
+ log.debug("OspfChannelHandler::processHelloMessage::Hello Packet Received have the same " +
+ "hello interval as configured Interface");
+ return;
+ }
+ if (helloPacket.routerDeadInterval() != routerDeadIntervalTime()) {
+ log.debug("OspfChannelHandler::processHelloMessage::Hello Packet Received have the same " +
+ "Router Dead interval as configured Interface");
+ return;
+ }
+
+ if (interfaceType == OspfInterfaceType.POINT_TO_POINT.value() &&
+ !helloPacket.dr().equals(OspfUtil.DEFAULTIP)) {
+ log.debug("OspfChannelHandler::processHelloMessage:: Neighbor in broadcast network");
+ return;
+ }
+
+ if (interfaceType == OspfInterfaceType.POINT_TO_POINT.value()) {
+ // to verify if the neighbor which sent the hello is present in the OSPF Interface neighboring list .
+ OspfNbr nbr;
+ if (!isNeighborInList(helloPacket.routerId().toString())) {
+ nbr = new OspfNbrImpl(ospfArea, this, helloPacket.sourceIp(),
+ helloPacket.routerId(), helloPacket.options(), topologyForDeviceAndLink);
+ addNeighbouringRouter(nbr);
+ } else {
+ nbr = neighbouringRouter(helloPacket.routerId().toString());
+ nbr.setRouterPriority(helloPacket.routerPriority());
+ }
+ if (!helloPacket.containsNeighbour(ospfArea.routerId())) {
+ ((OspfNbrImpl) nbr).oneWayReceived(helloPacket, channel);
+ } else {
+ ((OspfNbrImpl) nbr).twoWayReceived(helloPacket, ctx.getChannel());
+ }
+ } else if (interfaceType == OspfInterfaceType.BROADCAST.value()) {
+
+ if (state() == OspfInterfaceState.WAITING) {
+ if ((!helloPacket.dr().equals(Ip4Address.valueOf("0.0.0.0"))) &&
+ (!helloPacket.bdr().equals(Ip4Address.valueOf("0.0.0.0")))) {
+ stopWaitTimer();
+ setDr(helloPacket.dr());
+ setBdr(helloPacket.bdr());
+ if (helloPacket.dr().equals(ipAddress())) {
+ setState(OspfInterfaceState.DR);
+ //refresh router Lsa
+ ospfArea.refreshArea(this);
+ } else if (helloPacket.bdr().equals(ipAddress())) {
+ setState(OspfInterfaceState.BDR);
+ //refresh router Lsa
+ ospfArea.refreshArea(this);
+ } else {
+ setState(OspfInterfaceState.DROTHER);
+ ospfArea.refreshArea(this);
+ }
+
+ } else if (!helloPacket.dr().equals(Ip4Address.valueOf("0.0.0.0")) ||
+ !helloPacket.bdr().equals(Ip4Address.valueOf("0.0.0.0"))) {
+ setDr(helloPacket.dr());
+ setBdr(helloPacket.bdr());
+ }
+ Ip4Address sourceIp = helloPacket.sourceIp();
+ OspfNbr nbr;
+ if (!isNeighborInList(helloPacket.routerId().toString())) {
+ nbr = new OspfNbrImpl(ospfArea, this, sourceIp, helloPacket.routerId(),
+ helloPacket.options(), topologyForDeviceAndLink);
+ nbr.setNeighborId(helloPacket.routerId());
+ nbr.setNeighborBdr(helloPacket.bdr());
+ nbr.setNeighborDr(helloPacket.dr());
+ nbr.setRouterPriority(helloPacket.routerPriority());
+ addNeighbouringRouter(nbr);
+ } else {
+ nbr = neighbouringRouter(helloPacket.routerId().toString());
+ nbr.setRouterPriority(helloPacket.routerPriority());
+ }
+ if (!helloPacket.containsNeighbour(ospfArea.routerId())) {
+ ((OspfNbrImpl) nbr).oneWayReceived(helloPacket, channel);
+ } else {
+ ((OspfNbrImpl) nbr).twoWayReceived(helloPacket, ctx.getChannel());
+ }
+
+ if (helloPacket.dr().equals(sourceIp)) {
+ if (helloPacket.bdr().equals(Ip4Address.valueOf("0.0.0.0"))) {
+ // call backup seen
+ stopWaitTimer();
+ backupSeen(ctx.getChannel());
+ }
+ }
+
+ if (helloPacket.bdr().equals(sourceIp)) {
+ // call backup seen
+ stopWaitTimer();
+ backupSeen(ctx.getChannel());
+ }
+ } else {
+
+ if ((!helloPacket.dr().equals(Ip4Address.valueOf("0.0.0.0")) ||
+ !helloPacket.bdr().equals(Ip4Address.valueOf("0.0.0.0")))
+ && routerPriority() == 0) {
+ setDr(helloPacket.dr());
+ setBdr(helloPacket.bdr());
+ }
+ //To verify if the neighbor which sent the hello is present in the OSPF Interface neighboring list .
+ Ip4Address sourceIp = helloPacket.sourceIp();
+ OspfNbr nbr;
+ if (!isNeighborInList(helloPacket.routerId().toString())) {
+ nbr = new OspfNbrImpl(ospfArea, this, sourceIp, helloPacket.routerId(),
+ helloPacket.options(), topologyForDeviceAndLink);
+ nbr.setNeighborId(helloPacket.routerId());
+ nbr.setNeighborBdr(helloPacket.bdr());
+ nbr.setNeighborDr(helloPacket.dr());
+ nbr.setRouterPriority(helloPacket.routerPriority());
+ addNeighbouringRouter(nbr);
+ ((OspfNbrImpl) nbr).oneWayReceived(helloPacket, channel);
+ } else {
+ log.debug("OspfChannelHandler::NeighborInList::helloPacket.bdr(): {}, " +
+ "helloPacket.dr(): {}", helloPacket.bdr(), helloPacket.dr());
+ nbr = neighbouringRouter(helloPacket.routerId().toString());
+ nbr.setRouterPriority(helloPacket.routerPriority());
+ if (!helloPacket.containsNeighbour(ospfArea.routerId())) {
+ ((OspfNbrImpl) nbr).oneWayReceived(helloPacket, channel);
+ } else {
+ ((OspfNbrImpl) nbr).twoWayReceived(helloPacket, ctx.getChannel());
+ }
+ if (nbr.routerPriority() != helloPacket.routerPriority()) {
+ nbr.setNeighborBdr(helloPacket.bdr());
+ nbr.setNeighborDr(helloPacket.dr());
+ neighborChange();
+ }
+
+
+ if (nbr.neighborIpAddr().equals(helloPacket.dr()) &&
+ !(nbr.neighborIpAddr().equals(nbr.neighborDr()))) {
+ nbr.setNeighborBdr(helloPacket.bdr());
+ nbr.setNeighborDr(helloPacket.dr());
+ neighborChange();
+ }
+
+ if (!(nbr.neighborIpAddr().equals(helloPacket.dr())) &&
+ (nbr.neighborIpAddr().equals(nbr.neighborDr()))) {
+ nbr.setNeighborBdr(helloPacket.bdr());
+ nbr.setNeighborDr(helloPacket.dr());
+ neighborChange();
+ }
+
+ if (nbr.neighborIpAddr().equals(helloPacket.bdr()) &&
+ !(nbr.neighborIpAddr().equals(nbr.neighborBdr()))) {
+ nbr.setNeighborBdr(helloPacket.bdr());
+ nbr.setNeighborDr(helloPacket.dr());
+ neighborChange();
+ }
+
+ if (!(nbr.neighborIpAddr().equals(helloPacket.bdr())) &&
+ (nbr.neighborIpAddr().equals(nbr.neighborBdr()))) {
+ nbr.setNeighborBdr(helloPacket.bdr());
+ nbr.setNeighborDr(helloPacket.dr());
+ neighborChange();
+ }
+
+ nbr.setNeighborBdr(helloPacket.bdr());
+ nbr.setNeighborDr(helloPacket.dr());
+ }
+ }
+ }
+ }
+
+ /**
+ * process the DD message which received.
+ *
+ * @param ospfMessage OSPF message instance.
+ * @param ctx channel handler context instance
+ * @throws Exception might throws exception
+ */
+ void processDdMessage(OspfMessage ospfMessage, ChannelHandlerContext ctx) throws Exception {
+ log.debug("OspfChannelHandler::processDdMessage...!!!");
+ Channel channel = ctx.getChannel();
+ DdPacket ddPacket = (DdPacket) ospfMessage;
+ log.debug("Got DD packet from {}", ddPacket.sourceIp());
+ //check it is present in listOfNeighbors
+ Ip4Address neighbourId = ddPacket.routerId();
+ OspfNbr nbr = neighbouringRouter(neighbourId.toString());
+
+ if (nbr != null) {
+ log.debug("OspfChannelHandler::processDdMessage:: OSPFNeighborState {}", nbr.getState());
+ // set options for the NBR
+ nbr.setIsOpaqueCapable(ddPacket.isOpaqueCapable());
+ if (ddPacket.imtu() > mtu()) {
+ log.debug("the MTU size is greater than the interface MTU");
+ return;
+ }
+ if (nbr.getState() == OspfNeighborState.DOWN) {
+ return;
+ }
+ if (nbr.getState() == OspfNeighborState.ATTEMPT) {
+ return;
+ }
+ if (nbr.getState() == OspfNeighborState.TWOWAY) {
+ nbr.adjOk(channel);
+ return;
+ }
+ //if init is the state call twoWayReceived
+ if (nbr.getState() == OspfNeighborState.INIT) {
+ ((OspfNbrImpl) nbr).twoWayReceived(ddPacket, ctx.getChannel());
+ } else if (nbr.getState() == OspfNeighborState.EXSTART) {
+ //get I,M,MS Bits
+ int initialize = ddPacket.isInitialize();
+ int more = ddPacket.isMore();
+ int masterOrSlave = ddPacket.isMaster();
+ int options = ddPacket.options();
+ nbr.setOptions(options);
+
+ if (initialize == OspfUtil.INITIALIZE_SET && more == OspfUtil.MORE_SET &&
+ masterOrSlave == OspfUtil.IS_MASTER) {
+ if (ddPacket.getLsaHeaderList().isEmpty()) {
+ if (OspfUtil.ipAddressToLong(ddPacket.routerId().toString()) >
+ OspfUtil.ipAddressToLong(ospfArea.routerId().toString())) {
+ nbr.setIsMaster(OspfUtil.IS_MASTER);
+ ((OspfNbrImpl) nbr).setLastDdPacket(ddPacket);
+ nbr.setDdSeqNum(ddPacket.sequenceNo());
+ nbr.setOptions(ddPacket.options());
+ ((OspfNbrImpl) nbr).negotiationDone(ddPacket, true, ddPacket.getLsaHeaderList(),
+ ctx.getChannel());
+ }
+ }
+ }
+ if (initialize == OspfUtil.INITIALIZE_NOTSET && masterOrSlave == OspfUtil.NOT_MASTER) {
+ if (nbr.ddSeqNum() == ddPacket.sequenceNo()) {
+ if (OspfUtil.ipAddressToLong(ddPacket.routerId().toString()) <
+ OspfUtil.ipAddressToLong(ospfArea.routerId().toString())) {
+ ((OspfNbrImpl) nbr).setLastDdPacket(ddPacket);
+ nbr.setOptions(ddPacket.options());
+ nbr.setDdSeqNum(nbr.ddSeqNum() + 1);
+ ((OspfNbrImpl) nbr).negotiationDone(ddPacket, false, ddPacket.getLsaHeaderList(),
+ ctx.getChannel());
+ }
+ }
+ }
+
+ } else if (nbr.getState() == OspfNeighborState.EXCHANGE) {
+ //get I,M,MS Bits
+ log.debug("Neighbor state:: EXCHANGE");
+ boolean isDuplicateDDPacket = compareDdPackets(ddPacket, ((OspfNbrImpl) nbr).lastDdPacket());
+ int initialize = ddPacket.isInitialize();
+ int more = ddPacket.isMore();
+ int masterOrSlave = ddPacket.isMaster();
+ int options = ddPacket.options();
+
+ if (!isDuplicateDDPacket) {
+ //if dd packet is not duplicate then continue
+ if (nbr.isMaster() != masterOrSlave) {
+ DdPacket newResPacket =
+ (DdPacket) ((OspfNbrImpl) nbr).seqNumMismatch("Master/Slave Inconsistency");
+ newResPacket.setDestinationIp(ddPacket.sourceIp());
+ log.debug("Sending back DDPacket to {}", ddPacket.sourceIp());
+ byte[] messageToWrite = getMessage(newResPacket);
+ ctx.getChannel().write(messageToWrite);
+ } else if (initialize == 1) {
+ DdPacket newResPacket =
+ (DdPacket) ((OspfNbrImpl) nbr).seqNumMismatch("Initialize bit inconsistency");
+ newResPacket.setDestinationIp(ddPacket.sourceIp());
+ log.debug("Sending back DDPacket to {}", ddPacket.sourceIp());
+ byte[] messageToWrite = getMessage(newResPacket);
+ ctx.getChannel().write(messageToWrite);
+ } else {
+
+ if (masterOrSlave == OspfUtil.NOT_MASTER) {
+ if (ddPacket.sequenceNo() == nbr.ddSeqNum()) {
+ //Process the DD Packet
+ ((OspfNbrImpl) nbr).processDdPacket(false, ddPacket, ctx.getChannel());
+ log.debug("Received DD Packet");
+ } else {
+ DdPacket newResPacket =
+ (DdPacket) ((OspfNbrImpl) nbr).seqNumMismatch("Sequence Number Mismatch");
+ newResPacket.setDestinationIp(ddPacket.sourceIp());
+ log.debug("Sending back DDPacket to {}", ddPacket.sourceIp());
+ byte[] messageToWrite = getMessage(newResPacket);
+ ctx.getChannel().write(messageToWrite);
+ }
+ } else {
+ //we are the slave
+ if (ddPacket.sequenceNo() == (nbr.ddSeqNum() + 1)) {
+ ((OspfNbrImpl) nbr).setLastDdPacket(ddPacket);
+ ((OspfNbrImpl) nbr).processDdPacket(true, ddPacket, ctx.getChannel());
+ log.debug("Process DD Packet");
+ } else {
+ DdPacket newResPacket =
+ (DdPacket) ((OspfNbrImpl) nbr).seqNumMismatch("options inconsistency");
+ newResPacket.setDestinationIp(ddPacket.sourceIp());
+ log.debug("Sending back DDPacket to {}", ddPacket.sourceIp());
+ byte[] messageToWrite = getMessage(newResPacket);
+ ctx.getChannel().write(messageToWrite);
+ }
+ }
+ }
+ } else {
+ if (masterOrSlave == OspfUtil.NOT_MASTER) {
+ return;
+ } else {
+ DdPacket newResPacket = ((OspfNbrImpl) nbr).lastSentDdPacket();
+ log.debug("Sending back DDPacket to {}", ddPacket.sourceIp());
+ byte[] messageToWrite = getMessage(newResPacket);
+ ctx.getChannel().write(messageToWrite);
+ }
+ }
+ } else if (nbr.getState() == OspfNeighborState.LOADING || nbr.getState() == OspfNeighborState.FULL) {
+ //In case if we are slave then we have to send the last received DD Packet
+ int options = ddPacket.options();
+ if (nbr.options() != options) {
+ OspfMessage newResPacket = ((OspfNbrImpl) nbr).seqNumMismatch("Initialize bit inconsistency");
+ newResPacket.setDestinationIp(ddPacket.sourceIp());
+ byte[] messageToWrite = getMessage(newResPacket);
+ ctx.getChannel().write(messageToWrite);
+ } else if (ddPacket.isInitialize() == OspfUtil.INITIALIZE_SET) {
+ OspfMessage newResPacket = ((OspfNbrImpl) nbr).seqNumMismatch("Initialize bit inconsistency");
+ newResPacket.setDestinationIp(ddPacket.sourceIp());
+ byte[] messageToWrite = getMessage(newResPacket);
+ ctx.getChannel().write(messageToWrite);
+ }
+ boolean isDuplicate = compareDdPackets(ddPacket, ((OspfNbrImpl) nbr).lastDdPacket());
+ //we are master
+ if (nbr.isMaster() != OspfUtil.IS_MASTER) {
+ // check if the packet is duplicate, duplicates should be discarded by the master
+ if (isDuplicate) {
+ log.debug("received a duplicate DD packet");
+ }
+ } else {
+ //The slave must respond to duplicates by repeating the last Database Description packet
+ //that it had sent.
+ if (isDuplicate) {
+ ddPacket.setDestinationIp(ddPacket.sourceIp());
+ byte[] messageToWrite = getMessage(((OspfNbrImpl) nbr).lastSentDdPacket());
+ ctx.getChannel().write(messageToWrite);
+ log.debug("Sending back the duplicate packet ");
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Process the Ls Request message.
+ *
+ * @param ospfMessage OSPF message instance.
+ * @param ctx channel handler context instance.
+ * @throws Exception might throws exception
+ */
+ void processLsRequestMessage(OspfMessage ospfMessage, ChannelHandlerContext ctx) throws Exception {
+ log.debug("OspfChannelHandler::processLsRequestMessage...!!!");
+ Channel channel = ctx.getChannel();
+ LsRequest lsrPacket = (LsRequest) ospfMessage;
+ OspfNbr nbr = neighbouringRouter(lsrPacket.routerId().toString());
+
+ if (nbr.getState() == OspfNeighborState.EXCHANGE || nbr.getState() == OspfNeighborState.LOADING ||
+ nbr.getState() == OspfNeighborState.FULL) {
+
+ LsRequest reqMsg = (LsRequest) ospfMessage;
+ if (reqMsg.getLinkStateRequests().isEmpty()) {
+ log.debug("Received Link State Request Vector is Empty ");
+ return;
+ } else {
+ //Send the LsUpdate back
+ ListIterator<LsRequestPacket> listItr = reqMsg.getLinkStateRequests().listIterator();
+ while (listItr.hasNext()) {
+ LsUpdate lsupdate = new LsUpdate();
+ lsupdate.setOspfVer(OspfUtil.OSPF_VERSION);
+ lsupdate.setOspftype(OspfPacketType.LSUPDATE.value());
+ lsupdate.setRouterId(ospfArea.routerId());
+ lsupdate.setAreaId(ospfArea.areaId());
+ lsupdate.setAuthType(OspfUtil.NOT_ASSIGNED);
+ lsupdate.setAuthentication(OspfUtil.NOT_ASSIGNED);
+ lsupdate.setOspfPacLength(OspfUtil.NOT_ASSIGNED); // to calculate packet length
+ lsupdate.setChecksum(OspfUtil.NOT_ASSIGNED);
+
+ //limit to mtu
+ int currentLength = OspfUtil.OSPF_HEADER_LENGTH + OspfUtil.FOUR_BYTES;
+ int maxSize = mtu() -
+ OspfUtil.LSA_HEADER_LENGTH; // subtract a normal IP header.
+ int noLsa = 0;
+ while (listItr.hasNext()) {
+ LsRequestPacket lsRequest = (LsRequestPacket) listItr.next();
+ // to verify length of the LSA
+ LsaWrapper wrapper = ospfArea.getLsa(lsRequest.lsType(), lsRequest.linkStateId(),
+ lsRequest.ownRouterId());
+ OspfLsa ospflsa = wrapper.ospfLsa();
+ if ((currentLength + ((LsaWrapperImpl) wrapper).lsaHeader().lsPacketLen()) >= maxSize) {
+ listItr.previous();
+ break;
+ }
+ if (ospflsa != null) {
+ lsupdate.addLsa(ospflsa);
+ noLsa++;
+
+ currentLength = currentLength + ((LsaWrapperImpl) wrapper).lsaHeader().lsPacketLen();
+ } else {
+ nbr.badLSReq(channel);
+ }
+ }
+ lsupdate.setNumberOfLsa(noLsa);
+ //set the destination
+ if (state() == OspfInterfaceState.DR ||
+ state() == OspfInterfaceState.BDR ||
+ state() == OspfInterfaceState.POINT2POINT) {
+ lsupdate.setDestinationIp(OspfUtil.ALL_SPF_ROUTERS);
+ } else if (state() == OspfInterfaceState.DROTHER) {
+ lsupdate.setDestinationIp(OspfUtil.ALL_DROUTERS);
+ }
+ byte[] messageToWrite = getMessage(lsupdate);
+ ctx.getChannel().write(messageToWrite);
+ }
+ }
+ }
+ }
+
+ /**
+ * Process the ls update message.
+ *
+ * @param ospfMessage OSPF message instance.
+ * @param ctx channel handler context instance.
+ * @throws Exception might throws exception
+ */
+ void processLsUpdateMessage(OspfMessage ospfMessage, ChannelHandlerContext ctx) throws Exception {
+ log.debug("OspfChannelHandler::processLsUpdateMessage");
+ LsUpdate lsUpdate = (LsUpdate) ospfMessage;
+ String neighbourId = lsUpdate.routerId().toString();
+ //LSUpdate packet has been associated with a particular neighbor.
+ //Neighbor should not be in lesser state than Exchange.
+ if (isNeighborInList(neighbourId)) {
+ OspfNbrImpl nbr = (OspfNbrImpl) neighbouringRouter(neighbourId);
+ if (nbr.getState() == OspfNeighborState.EXCHANGE ||
+ nbr.getState() == OspfNeighborState.LOADING) {
+ nbr.processLsUpdate(lsUpdate, ctx.getChannel());
+ } else if (nbr.getState() == OspfNeighborState.FULL) {
+ if (lsUpdate.noLsa() != 0) {
+ List<OspfLsa> list = lsUpdate.getLsaList();
+ Iterator itr = list.iterator();
+ while (itr.hasNext()) {
+ LsaHeader lsa = (LsaHeader) itr.next();
+ nbr.processReceivedLsa(lsa, true, ctx.getChannel(), lsUpdate.sourceIp());
+ }
+ } else {
+ return;
+ }
+ }
+ }
+ }
+
+ /**
+ * Process the ls acknowledge message.
+ *
+ * @param ospfMessage OSPF message instance.
+ * @param ctx channel handler context instance.
+ * @throws Exception might throws exception
+ */
+ void processLsAckMessage(OspfMessage ospfMessage, ChannelHandlerContext ctx) throws Exception {
+ log.debug("OspfChannelHandler::processLsAckMessage");
+ LsAcknowledge lsAckPacket = (LsAcknowledge) ospfMessage;
+ //check it is present in listOfNeighbors
+ OspfNbrImpl nbr = (OspfNbrImpl) neighbouringRouter(lsAckPacket.routerId().toString());
+ if (nbr != null) {
+ if (nbr.getState().getValue() < OspfNeighborState.EXCHANGE.getValue()) {
+ // discard the packet.
+ return;
+ } else {
+ // process ls acknowledgements
+ Iterator itr = lsAckPacket.getLinkStateHeaders().iterator();
+ while (itr.hasNext()) {
+ LsaHeader lsRequest = (LsaHeader) itr.next();
+
+ OspfLsa ospfLsa =
+ (OspfLsa) nbr.getPendingReTxList().get(((OspfAreaImpl) ospfArea).getLsaKey(lsRequest));
+ if (lsRequest != null && ospfLsa != null) {
+ String isSame = ((OspfLsdbImpl) ospfArea.database()).isNewerOrSameLsa(
+ lsRequest, (LsaHeader) ospfLsa);
+ if (isSame.equals("same")) {
+ nbr.getPendingReTxList().remove(((OspfAreaImpl) ospfArea).getLsaKey(lsRequest));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Compares two Dd Packets to check whether its duplicate or not.
+ *
+ * @param receivedDPacket received DD packet from network.
+ * @param lastDdPacket Last DdPacket which we sent.
+ * @return true if it is a duplicate packet else false.
+ */
+ public boolean compareDdPackets(DdPacket receivedDPacket, DdPacket lastDdPacket) {
+ if (receivedDPacket.isInitialize() == lastDdPacket.isInitialize()) {
+ if (receivedDPacket.isMaster() == lastDdPacket.isMaster()) {
+ if (receivedDPacket.isMore() == lastDdPacket.isMore()) {
+ if (receivedDPacket.options() == lastDdPacket.options()) {
+ if (receivedDPacket.sequenceNo() == lastDdPacket.sequenceNo()) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Starts the hello timer which sends hello packet every configured seconds.
+ */
+ public void startHelloTimer() {
+ log.debug("OSPFInterfaceChannelHandler::startHelloTimer");
+ exServiceHello = Executors.newSingleThreadScheduledExecutor();
+ helloTimerTask = new InternalHelloTimer();
+ final ScheduledFuture<?> helloHandle =
+ exServiceHello.scheduleAtFixedRate(helloTimerTask, delay, helloIntervalTime, TimeUnit.SECONDS);
+ }
+
+ /**
+ * Stops the hello timer.
+ */
+ public void stopHelloTimer() {
+ log.debug("OSPFInterfaceChannelHandler::stopHelloTimer ");
+ exServiceHello.shutdown();
+ }
+
+ /**
+ * Starts the wait timer.
+ */
+ public void startWaitTimer() {
+ log.debug("OSPFNbr::startWaitTimer");
+ exServiceWait = Executors.newSingleThreadScheduledExecutor();
+ waitTimerTask = new InternalWaitTimer();
+ final ScheduledFuture<?> waitTimerHandle =
+ exServiceWait.schedule(waitTimerTask, routerDeadIntervalTime(), TimeUnit.SECONDS);
+ }
+
+ /**
+ * Stops the wait timer.
+ */
+ public void stopWaitTimer() {
+ log.debug("OSPFNbr::stopWaitTimer ");
+ exServiceWait.shutdown();
+ }
+
+ /**
+ * Starts the timer which waits for configured seconds and sends Delayed Ack Packet.
+ */
+ public void startDelayedAckTimer() {
+ if (!isDelayedAckTimerScheduled) {
+ log.debug("Started DelayedAckTimer...!!!");
+ exServiceDelayedAck = Executors.newSingleThreadScheduledExecutor();
+ delayedAckTimerTask = new InternalDelayedAckTimer();
+ final ScheduledFuture<?> delayAckHandle =
+ exServiceDelayedAck.scheduleAtFixedRate(delayedAckTimerTask, delayedAckTimerInterval,
+ delayedAckTimerInterval, TimeUnit.MILLISECONDS);
+ isDelayedAckTimerScheduled = true;
+ }
+ }
+
+ /**
+ * Stops the delayed acknowledge timer.
+ */
+ public void stopDelayedAckTimer() {
+ if (isDelayedAckTimerScheduled) {
+ log.debug("Stopped DelayedAckTimer...!!!");
+ isDelayedAckTimerScheduled = false;
+ exServiceDelayedAck.shutdown();
+ }
+ }
+
+ /**
+ * Performs DR election.
+ *
+ * @param ch Netty Channel instance.
+ * @throws Exception might throws exception
+ */
+ public void electRouter(Channel ch) throws Exception {
+
+ Ip4Address currentDr = dr();
+ Ip4Address currentBdr = bdr();
+ OspfInterfaceState oldState = state();
+ OspfInterfaceState newState;
+
+ log.debug("OSPFInterfaceChannelHandler::electRouter -> currentDr: {}, currentBdr: {}",
+ currentDr, currentBdr);
+ List<OspfEligibleRouter> eligibleRouters = calculateListOfEligibleRouters(new OspfEligibleRouter());
+
+ log.debug("OSPFInterfaceChannelHandler::electRouter -> eligibleRouters: {}", eligibleRouters);
+ OspfEligibleRouter electedBdr = electBdr(eligibleRouters);
+ OspfEligibleRouter electedDr = electDr(eligibleRouters, electedBdr);
+
+ setBdr(electedBdr.getIpAddress());
+ setDr(electedDr.getIpAddress());
+
+ if (electedBdr.getIpAddress().equals(ipAddress()) &&
+ !electedBdr.getIpAddress().equals(currentBdr)) {
+ setState(OspfInterfaceState.BDR);
+ }
+
+ if (electedDr.getIpAddress().equals(ipAddress()) &&
+ !electedDr.getIpAddress().equals(currentDr)) {
+ setState(OspfInterfaceState.DR);
+ }
+
+ if (state() != oldState &&
+ !(state() == OspfInterfaceState.DROTHER &&
+ oldState.value() < OspfInterfaceState.DROTHER.value())) {
+ log.debug("Recalculating as the State is changed ");
+ log.debug("OSPFInterfaceChannelHandler::electRouter -> currentDr: {}, currentBdr: {}",
+ currentDr, currentBdr);
+ eligibleRouters = calculateListOfEligibleRouters(new OspfEligibleRouter());
+
+ log.debug("OSPFInterfaceChannelHandler::electRouter -> eligibleRouters: {}", eligibleRouters);
+ electedBdr = electBdr(eligibleRouters);
+ electedDr = electDr(eligibleRouters, electedBdr);
+
+ setBdr(electedBdr.getIpAddress());
+ setDr(electedDr.getIpAddress());
+ }
+
+ if (electedBdr.getIpAddress().equals(ipAddress()) &&
+ !electedBdr.getIpAddress().equals(currentBdr)) {
+ setState(OspfInterfaceState.BDR);
+ ospfArea.refreshArea(this);
+ }
+
+ if (electedDr.getIpAddress().equals(ipAddress()) &&
+ !electedDr.getIpAddress().equals(currentDr)) {
+ setState(OspfInterfaceState.DR);
+ //Refresh Router Lsa & Network Lsa
+ ospfArea.refreshArea(this);
+ }
+
+ if (currentDr != electedDr.getIpAddress() || currentBdr != electedBdr.getIpAddress()) {
+ Set<String> negibhorIdList;
+ negibhorIdList = listOfNeighbors().keySet();
+ for (String routerid : negibhorIdList) {
+ OspfNbrImpl nbr = (OspfNbrImpl) neighbouringRouter(routerid);
+ if (nbr.getState().getValue() >= OspfNeighborState.TWOWAY.getValue()) {
+ nbr.adjOk(ch);
+ }
+ }
+ }
+
+ log.debug("OSPFInterfaceChannelHandler::electRouter -> ElectedDR: {}, ElectedBDR: {}",
+ electedDr.getIpAddress(), electedBdr.getIpAddress());
+ }
+
+
+ /**
+ * BDR Election process. Find the list of eligible router to participate in the process.
+ *
+ * @param electedDr router elected as DR.
+ * @return list of eligible routers
+ */
+ public List<OspfEligibleRouter> calculateListOfEligibleRouters(OspfEligibleRouter electedDr) {
+ log.debug("OSPFNbr::calculateListOfEligibleRouters ");
+ Set<String> neighborIdList;
+ List<OspfEligibleRouter> eligibleRouters = new ArrayList<>();
+
+ neighborIdList = listOfNeighbors().keySet();
+ for (String routerId : neighborIdList) {
+ OspfNbrImpl nbr = (OspfNbrImpl) neighbouringRouter(routerId);
+ if (nbr.getState().getValue() >= OspfNeighborState.TWOWAY.getValue() &&
+ nbr.routerPriority() > 0) {
+ OspfEligibleRouter router = new OspfEligibleRouter();
+ router.setIpAddress(nbr.neighborIpAddr());
+ router.setRouterId(nbr.neighborId());
+ router.setRouterPriority(nbr.routerPriority());
+ if (nbr.neighborDr().equals(nbr.neighborIpAddr()) ||
+ electedDr.getIpAddress().equals(nbr.neighborIpAddr())) {
+ router.setIsDr(true);
+ } else if (nbr.neighborBdr().equals(nbr.neighborIpAddr())) {
+ router.setIsBdr(true);
+ }
+ eligibleRouters.add(router);
+ }
+ }
+ // interface does not have states like two and all
+ if (routerPriority() > 0) {
+ OspfEligibleRouter router = new OspfEligibleRouter();
+ router.setIpAddress(ipAddress());
+ router.setRouterId(ospfArea.routerId());
+ router.setRouterPriority(routerPriority());
+ if (dr().equals(ipAddress()) ||
+ electedDr.getIpAddress().equals(ipAddress())) {
+ router.setIsDr(true);
+ } else if (bdr().equals(ipAddress()) &&
+ !dr().equals(ipAddress())) {
+ router.setIsBdr(true);
+ }
+
+ eligibleRouters.add(router);
+ }
+
+ return eligibleRouters;
+ }
+
+ /**
+ * Based on router priority assigns BDR.
+ *
+ * @param eligibleRouters list of routers to participate in bdr election.
+ * @return OSPF Eligible router instance.
+ */
+ public OspfEligibleRouter electBdr(List<OspfEligibleRouter> eligibleRouters) {
+ log.debug("OSPFInterfaceChannelHandler::electBdr -> eligibleRouters: {}", eligibleRouters);
+ List<OspfEligibleRouter> declaredAsBdr = new ArrayList<>();
+ List<OspfEligibleRouter> notDrAndBdr = new ArrayList<>();
+ for (OspfEligibleRouter router : eligibleRouters) {
+ if (router.isBdr()) {
+ declaredAsBdr.add(router);
+ }
+ if (!router.isBdr() && !router.isDr()) {
+ notDrAndBdr.add(router);
+ }
+ }
+
+ OspfEligibleRouter electedBdr = new OspfEligibleRouter();
+ if (!declaredAsBdr.isEmpty()) {
+ if (declaredAsBdr.size() == 1) {
+ electedBdr = declaredAsBdr.get(0);
+ } else if (declaredAsBdr.size() > 1) {
+ electedBdr = selectRouterBasedOnPriority(declaredAsBdr);
+ }
+ } else {
+ if (notDrAndBdr.size() == 1) {
+ electedBdr = notDrAndBdr.get(0);
+ } else if (notDrAndBdr.size() > 1) {
+ electedBdr = selectRouterBasedOnPriority(notDrAndBdr);
+ }
+ }
+
+ electedBdr.setIsBdr(true);
+ electedBdr.setIsDr(false);
+
+ return electedBdr;
+ }
+
+ /**
+ * DR Election process.
+ *
+ * @param eligibleRouters list of eligible routers.
+ * @param electedBdr Elected Bdr, OSPF eligible router instance.
+ * @return OSPF eligible router instance.
+ */
+ public OspfEligibleRouter electDr(List<OspfEligibleRouter> eligibleRouters,
+ OspfEligibleRouter electedBdr) {
+
+ List<OspfEligibleRouter> declaredAsDr = new ArrayList<>();
+ for (OspfEligibleRouter router : eligibleRouters) {
+ if (router.isDr()) {
+ declaredAsDr.add(router);
+ }
+ }
+
+ OspfEligibleRouter electedDr = new OspfEligibleRouter();
+ if (!declaredAsDr.isEmpty()) {
+ if (declaredAsDr.size() == 1) {
+ electedDr = declaredAsDr.get(0);
+ } else if (eligibleRouters.size() > 1) {
+ electedDr = selectRouterBasedOnPriority(declaredAsDr);
+ }
+ } else {
+ electedDr = electedBdr;
+ electedDr.setIsDr(true);
+ electedDr.setIsBdr(false);
+ }
+
+ return electedDr;
+ }
+
+ /**
+ * DR election process.
+ *
+ * @param routersList list of eligible routers.
+ * @return OSPF eligible router instance.
+ */
+ public OspfEligibleRouter selectRouterBasedOnPriority(List<OspfEligibleRouter> routersList) {
+
+ OspfEligibleRouter initialRouter = routersList.get(0);
+
+ for (int i = 1; i < routersList.size(); i++) {
+ OspfEligibleRouter router = routersList.get(i);
+ if (router.getRouterPriority() > initialRouter.getRouterPriority()) {
+ initialRouter = router;
+ } else if (router.getRouterPriority() == initialRouter.getRouterPriority()) {
+ try {
+ if (OspfUtil.ipAddressToLong(router.getIpAddress().toString()) >
+ OspfUtil.ipAddressToLong(initialRouter.getIpAddress().toString())) {
+ initialRouter = router;
+ }
+ } catch (Exception e) {
+ log.debug("OSPFInterfaceChannelHandler::selectRouterBasedOnPriority ->" +
+ " eligibleRouters: {}", initialRouter);
+ }
+ }
+ }
+
+ return initialRouter;
+ }
+
+ /**
+ * Adds device information.
+ *
+ * @param ospfRouter OSPF router instance
+ */
+ public void addDeviceInformation(OspfRouter ospfRouter) {
+ controller.addDeviceDetails(ospfRouter);
+ }
+
+ /**
+ * removes device information.
+ *
+ * @param ospfRouter OSPF neighbor instance
+ */
+ public void removeDeviceInformation(OspfRouter ospfRouter) {
+ controller.removeDeviceDetails(ospfRouter);
+ }
+
+ /**
+ * Adds link information.
+ *
+ * @param ospfRouter OSPF router instance
+ * @param ospfLinkTed list link ted instances
+ */
+ public void addLinkInformation(OspfRouter ospfRouter, OspfLinkTed ospfLinkTed) {
+ controller.addLinkDetails(ospfRouter, ospfLinkTed);
+ }
+
+ /**
+ * Removes link information.
+ *
+ * @param ospfRouter OSPF router instance
+ * @param ospfLinkTed OSPF link TED instance
+ */
+ public void removeLinkInformation(OspfRouter ospfRouter, OspfLinkTed ospfLinkTed) {
+ controller.removeLinkDetails(ospfRouter, ospfLinkTed);
+ }
+
+ /**
+ * Gets message as bytes.
+ *
+ * @param ospfMessage OSPF message
+ * @return OSPF message
+ */
+ private byte[] getMessage(OspfMessage ospfMessage) {
+ OspfMessageWriter messageWriter = new OspfMessageWriter();
+ if (state().equals(OspfInterfaceState.POINT2POINT)) {
+ ospfMessage.setDestinationIp(OspfUtil.ALL_SPF_ROUTERS);
+ }
+ return (messageWriter.getMessage(ospfMessage, interfaceIndex, state.value()));
+ }
+
+
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -468,32 +1560,24 @@
return false;
}
OspfInterfaceImpl that = (OspfInterfaceImpl) o;
- return Objects.equal(areaId, that.areaId) &&
- Objects.equal(helloIntervalTime, that.helloIntervalTime) &&
+ return Objects.equal(helloIntervalTime, that.helloIntervalTime) &&
Objects.equal(routerDeadIntervalTime, that.routerDeadIntervalTime) &&
- Objects.equal(transmitDelay, that.transmitDelay) &&
Objects.equal(routerPriority, that.routerPriority) &&
- Objects.equal(systemInterfaceType, that.systemInterfaceType) &&
Objects.equal(interfaceType, that.interfaceType) &&
- Objects.equal(interfaceCost, that.interfaceCost) &&
- Objects.equal(pollInterval, that.pollInterval) &&
Objects.equal(mtu, that.mtu) &&
Objects.equal(reTransmitInterval, that.reTransmitInterval) &&
Objects.equal(ipAddress, that.ipAddress) &&
Objects.equal(ipNetworkMask, that.ipNetworkMask) &&
Objects.equal(listOfNeighbors, that.listOfNeighbors) &&
- Objects.equal(authType, that.authType) &&
- Objects.equal(authKey, that.authKey) &&
Objects.equal(dr, that.dr) &&
Objects.equal(bdr, that.bdr);
}
@Override
public int hashCode() {
- return Objects.hashCode(ipAddress, ipNetworkMask, areaId, helloIntervalTime,
- routerDeadIntervalTime, transmitDelay, routerPriority, listOfNeighbors,
- systemInterfaceType, interfaceType, interfaceCost, authType, authKey,
- pollInterval, mtu, reTransmitInterval, dr, bdr);
+ return Objects.hashCode(ipAddress, ipNetworkMask, helloIntervalTime,
+ routerDeadIntervalTime, routerPriority, listOfNeighbors,
+ interfaceType, mtu, reTransmitInterval, dr, bdr);
}
@Override
@@ -502,19 +1586,187 @@
.omitNullValues()
.add("ipAddress", ipAddress)
.add("routerPriority", routerPriority)
- .add("areaID", areaId)
.add("helloIntervalTime", helloIntervalTime)
.add("routerDeadIntervalTime", routerDeadIntervalTime)
.add("interfaceType", interfaceType)
- .add("interfaceCost", interfaceCost)
- .add("authType", authType)
- .add("authKey", authKey)
- .add("pollInterval", pollInterval)
.add("mtu", mtu)
.add("reTransmitInterval", reTransmitInterval)
.add("dr", dr)
.add("bdr", bdr)
- .add("transmitDelay", transmitDelay)
.toString();
}
+
+ /**
+ * Represents a Hello task which sent a hello message every configured time interval.
+ */
+ private class InternalHelloTimer implements Runnable {
+
+ /**
+ * Creates an instance of Hello Timer.
+ */
+ InternalHelloTimer() {
+ }
+
+ @Override
+ public void run() {
+ if (channel != null && channel.isOpen() && channel.isConnected()) {
+ if (interfaceType() == OspfInterfaceType.BROADCAST.value()) {
+ if (interfaceTypeOldValue != interfaceType()) {
+ try {
+ callDrElection(channel);
+ } catch (Exception e) {
+ log.debug("Error while calling interfaceUp {}", e.getMessage());
+ }
+ }
+ } else {
+ if (interfaceTypeOldValue != interfaceType()) {
+ interfaceTypeOldValue = interfaceType();
+ }
+ }
+ HelloPacket hellopacket = new HelloPacket();
+ //Headers
+ hellopacket.setOspfVer(OspfUtil.OSPF_VERSION);
+ hellopacket.setOspftype(OspfPacketType.HELLO.value());
+ hellopacket.setOspfPacLength(0); //will be modified while encoding
+ hellopacket.setRouterId(ospfArea.routerId());
+ hellopacket.setAreaId(ospfArea.areaId());
+ hellopacket.setChecksum(0); //will be modified while encoding
+ hellopacket.setAuthType(OspfUtil.NOT_ASSIGNED);
+ hellopacket.setAuthentication(OspfUtil.NOT_ASSIGNED);
+ //Body
+ hellopacket.setNetworkMask(ipNetworkMask());
+ hellopacket.setOptions(ospfArea.options());
+ hellopacket.setHelloInterval(helloIntervalTime());
+ hellopacket.setRouterPriority(routerPriority());
+ hellopacket.setRouterDeadInterval(routerDeadIntervalTime());
+ hellopacket.setDr(dr());
+ hellopacket.setBdr(bdr());
+
+ Map<String, OspfNbr> listOfNeighbors = listOfNeighbors();
+ Set<String> keys = listOfNeighbors.keySet();
+ Iterator itr = keys.iterator();
+ while (itr.hasNext()) {
+ String nbrKey = (String) itr.next();
+ OspfNbrImpl nbr = (OspfNbrImpl) listOfNeighbors.get(nbrKey);
+ if (nbr.getState() != OspfNeighborState.DOWN) {
+ hellopacket.addNeighbor(Ip4Address.valueOf(nbrKey));
+ }
+ }
+ // build a hello Packet
+ if (channel == null || !channel.isOpen() || !channel.isConnected()) {
+ log.debug("Hello Packet not sent !!.. Channel Issue...");
+ return;
+ }
+
+ hellopacket.setDestinationIp(OspfUtil.ALL_SPF_ROUTERS);
+ byte[] messageToWrite = getMessage(hellopacket);
+ ChannelFuture future = channel.write(messageToWrite);
+ if (future.isSuccess()) {
+ log.debug("Hello Packet successfully sent !!");
+ } else {
+ future.awaitUninterruptibly();
+ }
+
+ }
+ }
+ }
+
+ /**
+ * Represents a Wait Timer task which waits the interface state to become WAITING.
+ * It initiates DR election process.
+ */
+ private class InternalWaitTimer implements Runnable {
+ Channel ch;
+
+ /**
+ * Creates an instance of Wait Timer.
+ */
+ InternalWaitTimer() {
+ this.ch = channel;
+ }
+
+ @Override
+ public void run() {
+ log.debug("Wait timer expires...");
+ if (ch != null && ch.isConnected()) {
+ try {
+ waitTimer(ch);
+ } catch (Exception e) {
+ log.debug("Exception at wait timer ...!!!");
+ }
+ }
+ }
+ }
+
+ /**
+ * Represents a task which sent a LS Acknowledge from the link state headers list.
+ */
+ private class InternalDelayedAckTimer implements Runnable {
+ Channel ch;
+
+ /**
+ * Creates an instance of Delayed acknowledge timer.
+ */
+ InternalDelayedAckTimer() {
+ this.ch = channel;
+ }
+
+ @Override
+ public void run() {
+ if (!linkStateHeaders().isEmpty()) {
+ isDelayedAckTimerScheduled = true;
+ if (ch != null && ch.isConnected()) {
+
+ List<LsaHeader> listOfLsaHeadersAcknowledged = new ArrayList<>();
+ List<LsaHeader> listOfLsaHeaders = linkStateHeaders();
+ log.debug("Delayed Ack, Number of Lsa's to Ack {}", listOfLsaHeaders.size());
+ Iterator itr = listOfLsaHeaders.iterator();
+ while (itr.hasNext()) {
+ LsAcknowledge ackContent = new LsAcknowledge();
+ //Setting OSPF Header
+ ackContent.setOspfVer(OspfUtil.OSPF_VERSION);
+ ackContent.setOspftype(OspfPacketType.LSAACK.value());
+ ackContent.setRouterId(ospfArea.routerId());
+ ackContent.setAreaId(ospfArea.areaId());
+ ackContent.setAuthType(OspfUtil.NOT_ASSIGNED);
+ ackContent.setAuthentication(OspfUtil.NOT_ASSIGNED);
+ ackContent.setOspfPacLength(OspfUtil.NOT_ASSIGNED);
+ ackContent.setChecksum(OspfUtil.NOT_ASSIGNED);
+ //limit to mtu
+ int currentLength = OspfUtil.OSPF_HEADER_LENGTH;
+ int maxSize = mtu() -
+ OspfUtil.LSA_HEADER_LENGTH; // subtract a normal IP header.
+ while (itr.hasNext()) {
+ if ((currentLength + OspfUtil.LSA_HEADER_LENGTH) >= maxSize) {
+ break;
+ }
+ LsaHeader lsaHeader = (LsaHeader) itr.next();
+ ackContent.addLinkStateHeader(lsaHeader);
+ currentLength = currentLength + OspfUtil.LSA_HEADER_LENGTH;
+ listOfLsaHeadersAcknowledged.add(lsaHeader);
+ log.debug("Delayed Ack, Added Lsa's to Ack {}", lsaHeader);
+ }
+
+ log.debug("Delayed Ack, Number of Lsa's in LsAck packet {}",
+ ackContent.getLinkStateHeaders().size());
+
+ //set the destination
+ if (state() == OspfInterfaceState.DR || state() == OspfInterfaceState.BDR
+ || state() == OspfInterfaceState.POINT2POINT) {
+ ackContent.setDestinationIp(OspfUtil.ALL_SPF_ROUTERS);
+ } else if (state() == OspfInterfaceState.DROTHER) {
+ ackContent.setDestinationIp(OspfUtil.ALL_DROUTERS);
+ }
+ byte[] messageToWrite = getMessage(ackContent);
+ ch.write(messageToWrite);
+
+ for (LsaHeader lsa : listOfLsaHeadersAcknowledged) {
+ linkStateHeaders().remove(lsa);
+ removeLsaFromNeighborMap(((OspfAreaImpl) ospfArea).getLsaKey(lsa));
+ }
+ }
+ }
+ }
+ }
+ }
}
\ No newline at end of file