Use LLDP for inter-instance link discovery.

Brought in OVXLLDP.java (renamed to OnosLldp) from OVX to encapsulate
LLDP building and verifying logic. There's also some small changes to
LLDP.java and LLDPTLV.java from OVX.

LinkDiscoveryManager.java has been changed to use the new OnosLldp class.
All links are now discovered using LLDPs, the BDDP code has been disabled.

Note: LinkDiscoveryManager still quite messy and contains unneeded
functionality which will be cleaned up in later patches.

OK to merge now (passed nightly tests).

Change-Id: Ifd8773f7531dc0f462c31aff1854a0dcd384c153
diff --git a/src/main/java/net/onrc/onos/core/linkdiscovery/internal/LinkDiscoveryManager.java b/src/main/java/net/onrc/onos/core/linkdiscovery/internal/LinkDiscoveryManager.java
index ed57586..33dbf57 100644
--- a/src/main/java/net/onrc/onos/core/linkdiscovery/internal/LinkDiscoveryManager.java
+++ b/src/main/java/net/onrc/onos/core/linkdiscovery/internal/LinkDiscoveryManager.java
@@ -27,6 +27,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -67,7 +68,9 @@
 import net.onrc.onos.core.packet.IPv4;
 import net.onrc.onos.core.packet.LLDP;
 import net.onrc.onos.core.packet.LLDPTLV;
+import net.onrc.onos.core.packet.OnosLldp;
 import net.onrc.onos.core.registry.IControllerRegistryService;
+import net.onrc.onos.core.util.SwitchPort;
 
 import org.openflow.protocol.OFMessage;
 import org.openflow.protocol.OFPacketIn;
@@ -81,13 +84,14 @@
 import org.openflow.protocol.OFType;
 import org.openflow.protocol.action.OFAction;
 import org.openflow.protocol.action.OFActionOutput;
+import org.openflow.protocol.action.OFActionType;
 import org.openflow.util.HexString;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
  * This class sends out LLDP messages containing the sending switch's datapath
- * id as well as the outgoing port number.  Received LLrescDP messages that
+ * id as well as the outgoing port number.  Received LLDP messages that
  * match a known switch cause a new LinkTuple to be created according to the
  * invariant rules listed below.  This new LinkTuple is also passed to routing
  * if it exists to trigger updates.
@@ -252,7 +256,7 @@
     /**
      * Quarantine task.
      */
-    protected SingletonTask bddpTask;
+    //protected SingletonTask bddpTask;
     protected static final int BDDP_TASK_INTERVAL = 100; // 100 ms.
     protected static final int BDDP_TASK_SIZE = 5;       // # of ports per iteration
 
@@ -352,6 +356,7 @@
     /**
      * Quarantine Ports.
      */
+    /*
     protected class QuarantineWorker implements Runnable {
         @Override
         public void run() {
@@ -365,6 +370,7 @@
             }
         }
     }
+    */
 
     /**
      * Add a switch port to the quarantine queue. Schedule the
@@ -604,89 +610,8 @@
                     sw, port);
         }
 
-        // using "nearest customer bridge" MAC address for broadest possible propagation
-        // through provider and TPMR bridges (see IEEE 802.1AB-2009 and 802.1Q-2011),
-        // in particular the Linux bridge which behaves mostly like a provider bridge
-        byte[] chassisId = new byte[]{4, 0, 0, 0, 0, 0, 0}; // filled in later
-        byte[] portId = new byte[]{2, 0, 0}; // filled in later
-        byte[] ttlValue = new byte[]{0, 0x78};
-        // OpenFlow OUI - 00-26-E1
-        byte[] dpidTLVValue = new byte[]{0x0, 0x26, (byte) 0xe1, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        OFPacketOut po = createLLDPPacketOut(sw, ofpPort, isReverse);
 
-        byte[] dpidArray = new byte[8];
-        ByteBuffer dpidBB = ByteBuffer.wrap(dpidArray);
-        ByteBuffer portBB = ByteBuffer.wrap(portId, 1, 2);
-
-        Long dpid = sw;
-        dpidBB.putLong(dpid);
-        // set the ethernet source mac to last 6 bytes of dpid
-        byte[] hardwareAddress = new byte[6];
-        System.arraycopy(dpidArray, 2, hardwareAddress, 0, 6);
-        ofpPort.setHardwareAddress(hardwareAddress);
-        // set the chassis id's value to last 6 bytes of dpid
-        System.arraycopy(dpidArray, 2, chassisId, 1, 6);
-        // set the optional tlv to the full dpid
-        System.arraycopy(dpidArray, 0, dpidTLVValue, 4, 8);
-        LLDPTLV dpidTLV = new LLDPTLV().setType((byte) 127)
-                .setLength((short) dpidTLVValue.length).setValue(dpidTLVValue);
-
-        // set the portId to the outgoing port
-        portBB.putShort(port);
-        if (log.isTraceEnabled()) {
-            log.trace("Sending LLDP out of interface: {}/{}",
-                    HexString.toHexString(sw), port);
-        }
-
-        LLDP lldp = new LLDP();
-        lldp.setChassisId(new LLDPTLV().setType((byte) 1).setLength((short) chassisId.length).setValue(chassisId));
-        lldp.setPortId(new LLDPTLV().setType((byte) 2).setLength((short) portId.length).setValue(portId));
-        lldp.setTtl(new LLDPTLV().setType((byte) 3).setLength((short) ttlValue.length).setValue(ttlValue));
-        lldp.getOptionalTLVList().add(dpidTLV);
-
-        // Add the controller identifier to the TLV value.
-        lldp.getOptionalTLVList().add(controllerTLV);
-        if (isReverse) {
-            lldp.getOptionalTLVList().add(REVERSE_TLV);
-        } else {
-            lldp.getOptionalTLVList().add(FORWARD_TLV);
-        }
-
-        Ethernet ethernet;
-        if (isStandard) {
-            ethernet = new Ethernet()
-                    .setSourceMACAddress(ofpPort.getHardwareAddress())
-                    .setDestinationMACAddress(LLDP_STANDARD_DST_MAC_STRING)
-                    .setEtherType(Ethernet.TYPE_LLDP);
-            ethernet.setPayload(lldp);
-        } else {
-            BSN bsn = new BSN(BSN.BSN_TYPE_BDDP);
-            bsn.setPayload(lldp);
-
-            ethernet = new Ethernet()
-                    .setSourceMACAddress(ofpPort.getHardwareAddress())
-                    .setDestinationMACAddress(LLDP_BSN_DST_MAC_STRING)
-                    .setEtherType(Ethernet.TYPE_BSN);
-            ethernet.setPayload(bsn);
-        }
-
-
-        // serialize and wrap in a packet out
-        byte[] data = ethernet.serialize();
-        OFPacketOut po = (OFPacketOut) floodlightProvider.getOFMessageFactory().getMessage(OFType.PACKET_OUT);
-        po.setBufferId(OFPacketOut.BUFFER_ID_NONE);
-        po.setInPort(OFPort.OFPP_NONE);
-
-        // set actions
-        List<OFAction> actions = new ArrayList<OFAction>();
-        actions.add(new OFActionOutput(port, (short) 0));
-        po.setActions(actions);
-        po.setActionsLength((short) OFActionOutput.MINIMUM_LENGTH);
-
-        // set data
-        po.setLengthU(OFPacketOut.MINIMUM_LENGTH + po.getActionsLength() + data.length);
-        po.setPacketData(data);
-
-        // send
         try {
             iofSwitch.write(po, null);
             iofSwitch.flush();
@@ -697,6 +622,51 @@
     }
 
     /**
+     * Creates packet_out LLDP for specified output port.
+     *
+     * @param dpid the dpid of the outgoing switch
+     * @param port the outgoing port
+     * @param isReverse whether this is a reverse LLDP or not
+     * @return Packet_out message with LLDP data
+     */
+    private OFPacketOut createLLDPPacketOut(long dpid,
+            final OFPhysicalPort port, boolean isReverse) {
+        // Set up packets
+        // TODO optimize by not creating new packets each time
+        OnosLldp lldpPacket = new OnosLldp();
+
+        Ethernet ethPacket = new Ethernet();
+        ethPacket.setEtherType(Ethernet.TYPE_LLDP);
+        ethPacket.setDestinationMACAddress(LLDP_STANDARD_DST_MAC_STRING);
+        ethPacket.setPayload(lldpPacket);
+        ethPacket.setPad(true);
+
+        final OFPacketOut packetOut = (OFPacketOut) floodlightProvider.getOFMessageFactory()
+                .getMessage(OFType.PACKET_OUT);
+        packetOut.setBufferId(OFPacketOut.BUFFER_ID_NONE);
+
+        final List<OFAction> actionsList = new LinkedList<OFAction>();
+        final OFActionOutput out = (OFActionOutput) floodlightProvider.getOFMessageFactory()
+                .getAction(OFActionType.OUTPUT);
+        out.setPort(port.getPortNumber());
+        actionsList.add(out);
+        packetOut.setActions(actionsList);
+        final short alen = (short) OFActionOutput.MINIMUM_LENGTH;
+
+        lldpPacket.setSwitch(dpid);
+        lldpPacket.setPort(port.getPortNumber());
+        lldpPacket.setReverse(isReverse);
+        ethPacket.setSourceMACAddress(port.getHardwareAddress());
+
+        final byte[] lldp = ethPacket.serialize();
+        packetOut.setActionsLength(alen);
+        packetOut.setPacketData(lldp);
+        packetOut
+                .setLength((short) (OFPacketOut.MINIMUM_LENGTH + alen + lldp.length));
+        return packetOut;
+    }
+
+    /**
      * Send LLDPs to all switch-ports.
      */
     protected void discoverOnAllPorts() {
@@ -724,8 +694,8 @@
 
                     // If the switch port is not alreayd in the maintenance
                     // queue, add it.
-                    NodePortTuple npt = new NodePortTuple(sw, ofp.getPortNumber());
-                    addToMaintenanceQueue(npt);
+                    //NodePortTuple npt = new NodePortTuple(sw, ofp.getPortNumber());
+                    //addToMaintenanceQueue(npt);
                 }
             }
         }
@@ -812,18 +782,8 @@
             return Command.CONTINUE;
         }
 
-        long myId = ByteBuffer.wrap(controllerTLV.getValue()).getLong();
-        long otherId = 0;
-        boolean myLLDP = false;
-        Boolean isReverse = null;
-
-        ByteBuffer portBB = ByteBuffer.wrap(lldp.getPortId().getValue());
-        portBB.position(1);
-
-        Short remotePort = portBB.getShort();
-        IOFSwitch remoteSwitch = null;
-
         // Verify this LLDP packet matches what we're looking for
+        /*
         for (LLDPTLV lldptlv : lldp.getOptionalTLVList()) {
             if (lldptlv.getType() == 127 && lldptlv.getLength() == 12 &&
                     lldptlv.getValue()[0] == 0x0 && lldptlv.getValue()[1] == 0x26 &&
@@ -850,66 +810,37 @@
                 }
             }
         }
+        */
 
-        if (!myLLDP) {
-            // This is not the LLDP sent by this controller.
-            // If the LLDP message has multicast bit set, then we need to broadcast
-            // the packet as a regular packet.
-            if (isStandard) {
-                if (log.isTraceEnabled()) {
-                    log.trace("Getting standard LLDP from a different controller and quelching it.");
-                }
-                return Command.STOP;
-            }
-            // XXX ONOS: Don't disregard any BDDP messages from other
-            // controllers because they're used for inter-instance link detection
-
-            /*else if (sw <= remoteSwitch.getId()) {
-                if (log.isTraceEnabled()) {
-                    log.trace("Getting BBDP from a different controller. myId {}: remoteId {}", myId, otherId);
-                    log.trace("and my controller id is smaller than the other, so quelching it. myPort {}: rPort {}",
-                    pi.getInPort(), remotePort);
-                }
-                //XXX ONOS: Fix the BDDP broadcast issue
-                //return Command.CONTINUE;
-                return Command.STOP;
-            }*/
-            /*
-            else if (myId < otherId)  {
-                if (log.isTraceEnabled()) {
-                    log.trace("Getting BDDP packets from a different controller" +
-                            "and letting it go through normal processing chain.");
-                }
-                //XXX ONOS: Fix the BDDP broadcast issue
-                //return Command.CONTINUE;
-                return Command.STOP;
-            }
-            */
-        }
-
-
-        if (remoteSwitch == null) {
-            // Ignore LLDPs not generated by Floodlight, or from a switch that has recently
-            // disconnected, or from a switch connected to another Floodlight instance
-            if (log.isTraceEnabled()) {
-                log.trace("Received LLDP from remote switch not connected to the controller");
-            }
+        byte[] packetData = pi.getPacketData();
+        if (!OnosLldp.isOnosLldp(packetData)) {
+            log.trace("Dropping LLDP that wasn't sent by ONOS");
             return Command.STOP;
         }
 
-        if (!remoteSwitch.portEnabled(remotePort)) {
-            if (log.isTraceEnabled()) {
-                log.trace("Ignoring link with disabled source port: switch {} port {}", remoteSwitch, remotePort);
+        SwitchPort switchPort = OnosLldp.extractSwitchPort(packetData);
+        long remoteDpid = switchPort.dpid().value();
+        short remotePort = switchPort.port().value();
+        IOFSwitch remoteSwitch = floodlightProvider.getSwitches().get(switchPort.dpid().value());
+
+
+        OFPhysicalPort physicalPort = null;
+        if (remoteSwitch != null) {
+            physicalPort = remoteSwitch.getPort(remotePort);
+            if (!remoteSwitch.portEnabled(remotePort)) {
+                if (log.isTraceEnabled()) {
+                    log.trace("Ignoring link with disabled source port: switch {} port {}", remoteSwitch, remotePort);
+                }
+                return Command.STOP;
             }
-            return Command.STOP;
-        }
-        if (suppressLinkDiscovery.contains(new NodePortTuple(remoteSwitch.getId(),
-                remotePort))) {
-            if (log.isTraceEnabled()) {
-                log.trace("Ignoring link with suppressed src port: switch {} port {}",
-                        remoteSwitch, remotePort);
+            if (suppressLinkDiscovery.contains(new NodePortTuple(remoteSwitch.getId(),
+                    remotePort))) {
+                if (log.isTraceEnabled()) {
+                    log.trace("Ignoring link with suppressed src port: switch {} port {}",
+                            remoteSwitch, remotePort);
+                }
+                return Command.STOP;
             }
-            return Command.STOP;
         }
         if (!iofSwitch.portEnabled(pi.getInPort())) {
             if (log.isTraceEnabled()) {
@@ -918,13 +849,12 @@
             return Command.STOP;
         }
 
-        OFPhysicalPort physicalPort = remoteSwitch.getPort(remotePort);
         int srcPortState = (physicalPort != null) ? physicalPort.getState() : 0;
         physicalPort = iofSwitch.getPort(pi.getInPort());
         int dstPortState = (physicalPort != null) ? physicalPort.getState() : 0;
 
         // Store the time of update to this link, and push it out to routingEngine
-        Link lt = new Link(remoteSwitch.getId(), remotePort, iofSwitch.getId(), pi.getInPort());
+        Link lt = new Link(remoteDpid, remotePort, iofSwitch.getId(), pi.getInPort());
 
 
         Long lastLldpTime = null;
@@ -949,6 +879,8 @@
         // first seen within a small interval, send probe on the
         // reverse link.
 
+        boolean isReverse = OnosLldp.isReverse(lldp);
+
         newLinkInfo = links.get(lt);
         if (newLinkInfo != null && isStandard && !isReverse) {
             Link reverseLink = new Link(lt.getDst(), lt.getDstPort(),
@@ -962,37 +894,6 @@
             }
         }
 
-        // XXX ONOS: Don't do this:
-        //   If the received packet is a BDDP packet, then create a reverse BDDP
-        //   link as well.
-        // We want to preserve our semantic of the instance that controls the
-        // destination switch is the one who adds the link to the database.
-        /*
-        if (!isStandard) {
-            Link reverseLink = new Link(lt.getDst(), lt.getDstPort(),
-                    lt.getSrc(), lt.getSrcPort());
-
-            // srcPortState and dstPort state are reversed.
-            LinkInfo reverseInfo =
-                    new LinkInfo(firstSeenTime, lastLldpTime, lastBddpTime,
-                            dstPortState, srcPortState);
-
-            addOrUpdateLink(reverseLink, reverseInfo);
-        }
-        */
-
-        // Remove the node ports from the quarantine and maintenance queues.
-        NodePortTuple nptSrc = new NodePortTuple(lt.getSrc(), lt.getSrcPort());
-        NodePortTuple nptDst = new NodePortTuple(lt.getDst(), lt.getDstPort());
-        removeFromQuarantineQueue(nptSrc);
-        removeFromQuarantineQueue(nptDst);
-
-        // XXX ONOS: Don't remove the port from the maintenance queue here
-        // because it sometimes prevents BDDPs from being sent and causes
-        // links to flap
-        // removeFromMaintenanceQueue(nptSrc);
-        // removeFromMaintenanceQueue(nptDst);
-
         // Consume this message
         return Command.STOP;
     }
@@ -1772,8 +1673,8 @@
 
         // Setup the BDDP task.  It is invoked whenever switch port tuples
         // are added to the quarantine list.
-        bddpTask = new SingletonTask(ses, new QuarantineWorker());
-        bddpTask.reschedule(BDDP_TASK_INTERVAL, TimeUnit.MILLISECONDS);
+        //bddpTask = new SingletonTask(ses, new QuarantineWorker());
+        //bddpTask.reschedule(BDDP_TASK_INTERVAL, TimeUnit.MILLISECONDS);
 
 
         // Register for the OpenFlow messages we want to receive
diff --git a/src/main/java/net/onrc/onos/core/packet/LLDPTLV.java b/src/main/java/net/onrc/onos/core/packet/LLDPTLV.java
index 262cb24..d5bb7fb 100644
--- a/src/main/java/net/onrc/onos/core/packet/LLDPTLV.java
+++ b/src/main/java/net/onrc/onos/core/packet/LLDPTLV.java
@@ -1,18 +1,33 @@
+/*******************************************************************************
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ******************************************************************************/
 /**
- *    Copyright 2011, Big Switch Networks, Inc.
- *    Originally created by David Erickson, Stanford University
+ * Copyright 2011, Big Switch Networks, Inc.
+ * Originally created by David Erickson, Stanford University
  *
- *    Licensed under the Apache License, Version 2.0 (the "License"); you may
- *    not use this file except in compliance with the License. You may obtain
- *    a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License. You may obtain
+ * a copy of the License at
  *
- *         http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
  *
- *    Unless required by applicable law or agreed to in writing, software
- *    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- *    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- *    License for the specific language governing permissions and limitations
- *    under the License.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
  **/
 
 package net.onrc.onos.core.packet;
@@ -20,8 +35,6 @@
 import java.nio.ByteBuffer;
 import java.util.Arrays;
 
-import org.apache.commons.lang.ArrayUtils;
-
 // CHECKSTYLE IGNORE WriteTag FOR NEXT 2 LINES
 /**
  * @author David Erickson (daviderickson@cs.stanford.edu)
@@ -65,14 +78,14 @@
      * @return the value
      */
     public byte[] getValue() {
-        return ArrayUtils.clone(this.value);
+        return Arrays.copyOf(value, value.length);
     }
 
     /**
      * @param value the value to set
      */
     public LLDPTLV setValue(byte[] value) {
-        this.value = ArrayUtils.clone(value);
+        this.value = Arrays.copyOf(value, value.length);
         return this;
     }
 
@@ -107,7 +120,9 @@
         return this;
     }
 
-    /* (non-Javadoc)
+    /*
+     * (non-Javadoc)
+     *
      * @see java.lang.Object#hashCode()
      */
     @Override
@@ -120,7 +135,9 @@
         return result;
     }
 
-    /* (non-Javadoc)
+    /*
+     * (non-Javadoc)
+     *
      * @see java.lang.Object#equals(java.lang.Object)
      */
     @Override
diff --git a/src/main/java/net/onrc/onos/core/packet/OnosLldp.java b/src/main/java/net/onrc/onos/core/packet/OnosLldp.java
new file mode 100644
index 0000000..7a53f0b
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/packet/OnosLldp.java
@@ -0,0 +1,454 @@
+/*******************************************************************************
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ******************************************************************************/
+package net.onrc.onos.core.packet;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+import net.onrc.onos.core.util.SwitchPort;
+
+import org.apache.commons.lang.ArrayUtils;
+
+import com.google.common.base.Charsets;
+
+/**
+ * LLDP packets ONOS uses for discovery of physical network topology.
+ * Refer to IEEE Std 802.1ABTM-2009 for more information.
+ *
+ */
+public class OnosLldp extends LLDP {
+
+    // ON.Lab OUI and ONOS name for organizationally specific TLVs
+    static final byte[] ONLAB_OUI = {(byte) 0xa4, 0x23, 0x05};
+    public static final String ONOS_NAME = "ONOSVirteX";
+    static final byte[] LLDP_NICIRA = {0x01, 0x23, 0x20, 0x00, 0x00,
+            0x01};
+    static final byte[] LLDP_MULTICAST = {0x01, (byte) 0x80,
+            (byte) 0xc2, 0x00, 0x00, 0x0e};
+    static final byte[] BDDP_MULTICAST = {(byte) 0xff, (byte) 0xff,
+            (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff};
+    public static final short ETHERTYPE_VLAN = (short) 0x8100;
+
+    // TLV constants: type, size and subtype
+    // Organizationally specific TLV also have packet offset and contents of TLV
+    // header
+    private static final byte CHASSIS_TLV_TYPE = 1;
+    private static final byte CHASSIS_TLV_SIZE = 7;
+    private static final byte CHASSIS_TLV_SUBTYPE = 4;
+
+    private static final byte PORT_TLV_TYPE = 2;
+    private static final byte PORT_TLV_SIZE = 3;
+    private static final byte PORT_TLV_SUBTYPE = 2;
+
+    private static final byte TTL_TLV_TYPE = 3;
+    private static final byte TTL_TLV_SIZE = 2;
+
+    private static final byte NAME_TLV_TYPE = 127;
+    // 4 = OUI (3) + subtype (1)
+    private static final byte NAME_TLV_SIZE = (byte) (4 + OnosLldp.ONOS_NAME.length());
+    private static final byte NAME_TLV_SUBTYPE = 1;
+    private static final short NAME_TLV_OFFSET = 32;
+    private static final short NAME_TLV_HEADER = (short) ((NAME_TLV_TYPE << 9) | (NAME_TLV_SIZE & 0xff));
+    // Contents of full name TLV
+    private static final byte[] NAME_TLV = ByteBuffer.allocate(NAME_TLV_SIZE + 2)
+            .putShort(NAME_TLV_HEADER).put(ONLAB_OUI).put(NAME_TLV_SUBTYPE)
+            .put(ONOS_NAME.getBytes(Charsets.UTF_8)).array();
+
+    private static final byte DPID_TLV_TYPE = 127;
+    private static final byte DPID_TLV_SIZE = (byte) (12); // 12 = OUI (3) + subtype
+                                                     // (1) + dpid (8)
+    private static final byte DPID_TLV_SUBTYPE = 2;
+    private static final short DPID_TLV_HEADER = (short) ((DPID_TLV_TYPE << 9) | DPID_TLV_SIZE);
+    // Contents of dpid TLV
+    // Note that this does *not* contain the actual dpid since we cannot match
+    // on it
+    private static final byte[] DPID_TLV = ByteBuffer.allocate(DPID_TLV_SIZE + 2 - 8)
+            .putShort(DPID_TLV_HEADER).put(ONLAB_OUI).put(DPID_TLV_SUBTYPE)
+            .array();
+
+    // Pre-built contents of both organizationally specific TLVs
+    private static final byte[] OUI_TLV = ArrayUtils.addAll(NAME_TLV, DPID_TLV);
+
+    // Default switch, port number and TTL
+    private static final byte[] DEFAULT_DPID = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00 };
+    private static final short DEFAULT_PORT = 0;
+    private static final short DEFAULT_TTL = 120; // in seconds
+
+    // Minimum and ONOS-generated LLDP packet sizes
+    private static final short MINIMUM_LLDP_SIZE = 61;
+    // Add 12 for 2-byte header of each TLV and a single EndOfLLDPTLV
+    private static final short ONOS_LLDP_SIZE = (short) (CHASSIS_TLV_SIZE
+            + PORT_TLV_SIZE + TTL_TLV_SIZE + NAME_TLV_SIZE + DPID_TLV_SIZE + 12);
+
+    // Direction TLVs are used to indicate if the LLDPs were sent
+    // periodically or in response to a received LLDP
+    private static final byte TLV_DIRECTION_TYPE = 0x73;
+    private static final short TLV_DIRECTION_LENGTH = 1;  // 1 byte
+    private static final byte[] TLV_DIRECTION_VALUE_FORWARD = {0x01};
+    private static final byte[] TLV_DIRECTION_VALUE_REVERSE = {0x02};
+    private static final LLDPTLV FORWARD_TLV
+            = new LLDPTLV().
+            setType(TLV_DIRECTION_TYPE).
+            setLength(TLV_DIRECTION_LENGTH).
+            setValue(TLV_DIRECTION_VALUE_FORWARD);
+
+    private static final LLDPTLV REVERSE_TLV
+            = new LLDPTLV().
+            setType(TLV_DIRECTION_TYPE).
+            setLength(TLV_DIRECTION_LENGTH).
+            setValue(TLV_DIRECTION_VALUE_REVERSE);
+
+    // Field offsets in ONOS-generated LLDP
+    private static final short ETHERTYPE_OFFSET = 12;
+    private static final short PORT_OFFSET = 26;
+    private static final short DPID_OFFSET = 54;
+
+    // Private member fields
+    // Byte arrays for TLV information string
+    private byte[] chassisId = new byte[CHASSIS_TLV_SIZE];
+    private byte[] portId = new byte[PORT_TLV_SIZE];
+    private byte[] ttl = new byte[TTL_TLV_SIZE];
+    private byte[] ouiName = new byte[NAME_TLV_SIZE];
+    private byte[] ouiDpid = new byte[DPID_TLV_SIZE];
+
+    // TLVs
+    private LLDPTLV chassisTLV;
+    private LLDPTLV portTLV;
+    private LLDPTLV ttlTLV;
+    private LLDPTLV ouiNameTLV;
+    private LLDPTLV ouiDpidTLV;
+    private List<LLDPTLV> optionalTLVList;
+
+    /**
+     * Instantiates a new ONOS LDDP message.
+     */
+    public OnosLldp() {
+        // Create TLVs
+        this.chassisTLV = new LLDPTLV();
+        this.portTLV = new LLDPTLV();
+        this.ttlTLV = new LLDPTLV();
+        this.ouiNameTLV = new LLDPTLV();
+        this.ouiDpidTLV = new LLDPTLV();
+        this.optionalTLVList = new LinkedList<LLDPTLV>();
+        this.optionalTLVList.add(this.ouiNameTLV);
+        this.optionalTLVList.add(this.ouiDpidTLV);
+
+        // Add TLVs to LLDP packet
+        this.setChassisId(this.chassisTLV);
+        this.setPortId(this.portTLV);
+        this.setTtl(this.ttlTLV);
+        this.setOptionalTLVList(this.optionalTLVList);
+
+        // Set TLVs to default values
+        this.setChassisTLV(DEFAULT_DPID);
+        this.setPortTLV(DEFAULT_PORT);
+        this.setTTLTLV(DEFAULT_TTL);
+        this.setOUIName(OnosLldp.ONOS_NAME);
+        this.setOUIDpid(DEFAULT_DPID);
+    }
+
+    /**
+     * Sets chassis TLV. Note that we can only put 6 bytes in the chassis ID, so
+     * we use another organizationally specific TLV to put the full dpid (see
+     * setOUIDpid()).
+     *
+     * @param dpid the switch DPID
+     */
+    private void setChassisTLV(final byte[] dpid) {
+        ByteBuffer bb = ByteBuffer.wrap(this.chassisId);
+        bb.put(CHASSIS_TLV_SUBTYPE);
+        for (int i = 2; i < 8; i++) {
+            bb.put(dpid[i]);
+        }
+
+        this.chassisTLV.setLength(CHASSIS_TLV_SIZE);
+        this.chassisTLV.setType(CHASSIS_TLV_TYPE);
+        this.chassisTLV.setValue(this.chassisId);
+    }
+
+    /**
+     * Sets port TLV.
+     *
+     * @param portNumber the port number
+     */
+    private void setPortTLV(final short portNumber) {
+        ByteBuffer bb = ByteBuffer.wrap(this.portId);
+        bb.put(PORT_TLV_SUBTYPE);
+        bb.putShort(portNumber);
+
+        this.portTLV.setLength(PORT_TLV_SIZE);
+        this.portTLV.setType(PORT_TLV_TYPE);
+        this.portTLV.setValue(this.portId);
+    }
+
+    /**
+     * Sets Time To Live TLV.
+     *
+     * @param time the time to live
+     */
+    private void setTTLTLV(final short time) {
+        ByteBuffer bb = ByteBuffer.wrap(this.ttl);
+        bb.putShort(time);
+
+        this.ttlTLV.setLength(TTL_TLV_SIZE);
+        this.ttlTLV.setType(TTL_TLV_TYPE);
+        this.ttlTLV.setValue(this.ttl);
+    }
+
+    /**
+     * Sets organizationally specific TLV for ONOS name (subtype 1).
+     *
+     * @param name the name
+     */
+    private void setOUIName(final String name) {
+        ByteBuffer bb = ByteBuffer.wrap(ouiName);
+        bb.put(OnosLldp.ONLAB_OUI);
+        bb.put(NAME_TLV_SUBTYPE);
+        bb.put(name.getBytes(Charsets.UTF_8));
+
+        this.ouiNameTLV.setLength(NAME_TLV_SIZE);
+        this.ouiNameTLV.setType(NAME_TLV_TYPE);
+        this.ouiNameTLV.setValue(ouiName);
+    }
+
+    /**
+     * Sets organizationally specific TLV for ONOS full dpid (subtype 2).
+     *
+     * @param dpid the switch DPID
+     */
+    private void setOUIDpid(final byte[] dpid) {
+        ByteBuffer bb = ByteBuffer.wrap(ouiDpid);
+        bb.put(OnosLldp.ONLAB_OUI);
+        bb.put(DPID_TLV_SUBTYPE);
+        bb.put(dpid);
+
+        this.ouiDpidTLV.setLength(DPID_TLV_SIZE);
+        this.ouiDpidTLV.setType(DPID_TLV_TYPE);
+        this.ouiDpidTLV.setValue(ouiDpid);
+    }
+
+    /**
+     * Sets switch DPID in LLDP packet.
+     *
+     * @param sw the switch dpid
+     */
+    public void setSwitch(Long dpid) {
+        final byte[] byteDpid = ByteBuffer.allocate(8).putLong(dpid)
+                .array();
+        this.setChassisTLV(byteDpid);
+        this.setOUIDpid(byteDpid);
+    }
+
+
+    /**
+     * Sets the port number in LLDP packet.
+     *
+     * @param port the port number
+     */
+    public void setPort(short portNumber) {
+        this.setPortTLV(portNumber);
+    }
+
+    /**
+     * Sets whether this is a forward or reverse LLDP.
+     *
+     * @param isReverse true if reverse, false if forward
+     */
+    public void setReverse(boolean isReverse) {
+        optionalTLVList.add((isReverse) ? REVERSE_TLV : FORWARD_TLV);
+    }
+
+    /**
+     * Serializes full LLDP packet to byte array.
+     *
+     * @return the serialized packet
+     */
+    public byte[] serialize() {
+        return super.serialize();
+    }
+
+    /**
+     * Checks if LLDP packet has correct size, LLDP multicast address, and
+     * ethertype. Packet assumed to have Ethernet header.
+     *
+     * @param packet full packet starting from the Ethernet header
+     * @return true if packet is LLDP, false otherwise
+     */
+    public static boolean isLLDP(final byte[] packet) {
+        // Does packet exist and does it have the mininum size?
+        if (packet == null || packet.length < MINIMUM_LLDP_SIZE) {
+            return false;
+        }
+
+        // Packet has LLDP multicast destination address?
+        final ByteBuffer bb = ByteBuffer.wrap(packet);
+        final byte[] dst = new byte[6];
+        bb.get(dst);
+
+        if (!(Arrays.equals(dst, OnosLldp.LLDP_NICIRA)
+                || Arrays.equals(dst, OnosLldp.LLDP_MULTICAST) || Arrays.equals(
+                dst, OnosLldp.BDDP_MULTICAST))) {
+
+            return false;
+        }
+
+        // Fetch ethertype, skip VLAN tag if it's there
+        short etherType = bb.getShort(ETHERTYPE_OFFSET);
+        if (etherType == ETHERTYPE_VLAN) {
+            etherType = bb.getShort(ETHERTYPE_OFFSET + 4);
+        }
+
+        // Check ethertype
+        if (etherType == Ethernet.TYPE_LLDP) {
+            return true;
+        }
+        if (etherType == Ethernet.TYPE_BSN) {
+            return true;
+        }
+
+        return false;
+
+    }
+
+    /**
+     * Checks if packet has size of ONOS-generated LLDP, and correctness of two
+     * organizationally specific TLVs that use ON.Lab's OUI. Assumes packet is
+     * valid LLDP packet
+     *
+     * @param packet full packet starting from the Ethernet header
+     * @return true if this is an ONOS-generated LLDP, otherwise false
+     */
+    public static boolean isOnosLldp(byte[] packet) {
+        if (packet.length < ONOS_LLDP_SIZE) {
+            return false;
+        }
+
+        // Extra offset due to VLAN tag
+        final ByteBuffer bb = ByteBuffer.wrap(packet);
+        int offset = 0;
+        if (bb.getShort(ETHERTYPE_OFFSET) != Ethernet.TYPE_LLDP
+                && bb.getShort(ETHERTYPE_OFFSET) != Ethernet.TYPE_BSN) {
+            offset = 4;
+        }
+
+        // Compare packet's organizationally specific TLVs to the expected
+        // values
+        for (int i = 0; i < OUI_TLV.length; i++) {
+            if (packet[NAME_TLV_OFFSET + offset + i] != OUI_TLV[i]) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Extracts dpid and port from ONOS-generated LLDP packet.
+     *
+     * @param packet full packet started at the Ethernet header
+     * @return switchport switch and port info from the DPID and Port TLVs
+     */
+    public static SwitchPort extractSwitchPort(final byte[] packet) {
+        final ByteBuffer bb = ByteBuffer.wrap(packet);
+
+        // Extra offset due to VLAN tag
+        int offset = 0;
+        if (bb.getShort(ETHERTYPE_OFFSET) != Ethernet.TYPE_LLDP
+                && bb.getShort(ETHERTYPE_OFFSET) != Ethernet.TYPE_BSN) {
+            offset = 4;
+        }
+
+        final short port = bb.getShort(PORT_OFFSET + offset);
+        final long dpid = bb.getLong(DPID_OFFSET + offset);
+
+        return new SwitchPort(dpid, port);
+    }
+
+    /**
+     * Checks if the LLDP is a reverse LLDP (i.e. sent in response to receiving
+     * an LLDP on the link). This information is stored in the Direction TLV.
+     *
+     * @param lldp parsed LLDP packet
+     * @return true if the LLDP is a reverse LLDP, otherwise false
+     */
+    public static boolean isReverse(final LLDP lldp) {
+        for (LLDPTLV lldpTlv : lldp.getOptionalTLVList()) {
+            if ((lldpTlv.getType() == TLV_DIRECTION_TYPE) &&
+                    Arrays.equals(lldpTlv.getValue(), TLV_DIRECTION_VALUE_REVERSE)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (other == this) {
+            return true;
+        }
+        if (!super.equals(other)) {
+            return false;
+        }
+        //
+        // NOTE: Subclasses are are considered as change of identity, hence
+        // equals() will return false if the class type doesn't match.
+        //
+        if (getClass() != other.getClass()) {
+            return false;
+        }
+
+        OnosLldp otherLldp = (OnosLldp) other;
+
+        if (!this.chassisTLV.equals(otherLldp.chassisTLV)) {
+            return false;
+        }
+
+        if (!this.portTLV.equals(otherLldp.portTLV)) {
+            return false;
+        }
+
+        if (!this.ttlTLV.equals(otherLldp.ttlTLV)) {
+            return false;
+        }
+
+        if (!this.ouiNameTLV.equals(otherLldp.ouiNameTLV)) {
+            return false;
+        }
+
+        if (!this.ouiDpidTLV.equals(otherLldp.ouiDpidTLV)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + chassisTLV.hashCode();
+        result = prime * result + portTLV.hashCode();
+        result = prime * result + ttlTLV.hashCode();
+        result = prime * result + ouiNameTLV.hashCode();
+        result = prime * result + ouiDpidTLV.hashCode();
+
+        return result;
+    }
+}
diff --git a/src/test/java/net/onrc/onos/core/linkdiscovery/internal/LinkDiscoveryManagerTest.java b/src/test/java/net/onrc/onos/core/linkdiscovery/internal/LinkDiscoveryManagerTest.java
index cdcf010..4cebdaa 100644
--- a/src/test/java/net/onrc/onos/core/linkdiscovery/internal/LinkDiscoveryManagerTest.java
+++ b/src/test/java/net/onrc/onos/core/linkdiscovery/internal/LinkDiscoveryManagerTest.java
@@ -395,6 +395,8 @@
      */
     @Test
     public void testSendDiscoveryMessage() throws IOException {
+        byte[] macAddress = new byte[] {0x0, 0x0, 0x0, 0x0, 0x0, 0x1};
+
         LinkDiscoveryManager topology = getTopology();
 
         // Mock up our expected behavior
@@ -404,6 +406,7 @@
         short portNum = 1;
         OFPhysicalPort ofpPort = new OFPhysicalPort();
         ofpPort.setPortNumber(portNum);
+        ofpPort.setHardwareAddress(macAddress);
 
         /* sendDiscoverMessage() should perform the following actions on
          * IOFSwitch object