ONOS-5463 OFAgent - handle LLDP (based on OVX's handling of LLDP)

Change-Id: Ie1f78ba0e4f632b9566a007aa63fcc16d0976f4d
diff --git a/apps/ofagent/src/main/java/org/onosproject/ofagent/api/OFSwitchOperationService.java b/apps/ofagent/src/main/java/org/onosproject/ofagent/api/OFSwitchOperationService.java
index ea71fa0..640c5d6 100644
--- a/apps/ofagent/src/main/java/org/onosproject/ofagent/api/OFSwitchOperationService.java
+++ b/apps/ofagent/src/main/java/org/onosproject/ofagent/api/OFSwitchOperationService.java
@@ -17,9 +17,11 @@
 
 import io.netty.channel.Channel;
 import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
 import org.onosproject.net.flow.FlowRule;
 import org.onosproject.net.packet.InboundPacket;
 import org.projectfloodlight.openflow.protocol.OFMessage;
+import org.projectfloodlight.openflow.protocol.OFPacketOut;
 
 /**
  * Service for providing OpenFlow operations.
@@ -121,6 +123,14 @@
     void processLldp(Channel channel, OFMessage msg);
 
     /**
+     * Sends lldp response to the controller.
+     *
+     * @param ofPacketOut packet out message with lldp
+     * @param inPort      in port to be used for packet in message
+     */
+    void sendLldpResponse(OFPacketOut ofPacketOut, PortNumber inPort);
+
+    /**
      * Sends hello to the controller.
      *
      * @param channel received channel
diff --git a/apps/ofagent/src/main/java/org/onosproject/ofagent/api/OFSwitchService.java b/apps/ofagent/src/main/java/org/onosproject/ofagent/api/OFSwitchService.java
index 30f6d37..395f08e 100644
--- a/apps/ofagent/src/main/java/org/onosproject/ofagent/api/OFSwitchService.java
+++ b/apps/ofagent/src/main/java/org/onosproject/ofagent/api/OFSwitchService.java
@@ -16,8 +16,10 @@
 package org.onosproject.ofagent.api;
 
 import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
 import org.onosproject.net.device.PortStatistics;
 
 import java.util.List;
@@ -70,4 +72,13 @@
      */
     List<PortStatistics> getPortStatistics(NetworkId networkId, DeviceId deviceId);
 
+    /**
+     * Returns neighbour port of the specified port in the specified network.
+     *
+     * @param networkId network id
+     * @param deviceId device id
+     * @param portNumber port number
+     * @return connect point; null if none exists
+     */
+    ConnectPoint neighbour(NetworkId networkId, DeviceId deviceId, PortNumber portNumber);
 }
diff --git a/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/DefaultOFSwitch.java b/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/DefaultOFSwitch.java
index 8efcf1c..72a0c2f 100644
--- a/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/DefaultOFSwitch.java
+++ b/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/DefaultOFSwitch.java
@@ -19,14 +19,17 @@
 import io.netty.channel.Channel;
 import org.onlab.osgi.ServiceDirectory;
 import org.onosproject.incubator.net.virtual.NetworkId;
+import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Port;
 import org.onosproject.net.device.PortStatistics;
 import org.onosproject.net.flow.FlowRule;
 import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.PortNumber;
 import org.onosproject.ofagent.api.OFSwitch;
 import org.onosproject.ofagent.api.OFSwitchCapabilities;
 import org.onosproject.ofagent.api.OFSwitchService;
+import org.projectfloodlight.openflow.protocol.OFActionType;
 import org.projectfloodlight.openflow.protocol.OFBarrierReply;
 import org.projectfloodlight.openflow.protocol.OFControllerRole;
 import org.projectfloodlight.openflow.protocol.OFEchoReply;
@@ -38,6 +41,9 @@
 import org.projectfloodlight.openflow.protocol.OFHello;
 import org.projectfloodlight.openflow.protocol.OFMessage;
 import org.projectfloodlight.openflow.protocol.OFMeterFeatures;
+import org.projectfloodlight.openflow.protocol.OFPacketIn;
+import org.projectfloodlight.openflow.protocol.OFPacketInReason;
+import org.projectfloodlight.openflow.protocol.OFPacketOut;
 import org.projectfloodlight.openflow.protocol.OFPortDesc;
 import org.projectfloodlight.openflow.protocol.OFPortReason;
 import org.projectfloodlight.openflow.protocol.OFPortStatsEntry;
@@ -50,6 +56,10 @@
 import org.projectfloodlight.openflow.protocol.OFStatsRequest;
 import org.projectfloodlight.openflow.protocol.OFType;
 import org.projectfloodlight.openflow.protocol.OFVersion;
+import org.projectfloodlight.openflow.protocol.action.OFAction;
+import org.projectfloodlight.openflow.protocol.action.OFActionOutput;
+import org.projectfloodlight.openflow.protocol.match.Match;
+import org.projectfloodlight.openflow.protocol.match.MatchField;
 import org.projectfloodlight.openflow.types.DatapathId;
 import org.projectfloodlight.openflow.types.OFPort;
 import org.projectfloodlight.openflow.types.U64;
@@ -349,7 +359,46 @@
 
     @Override
     public void processLldp(Channel channel, OFMessage msg) {
-        // TODO process lldp
+        log.trace("processLldp msg{}", msg);
+
+        // For each output port, look up neighbour port.
+        // If neighbour port exists, have the neighbour switch send lldp response.
+        // Modeled after how OpenVirtex handles lldp from external controller.
+        OFPacketOut ofPacketOut = (OFPacketOut) msg;
+        List<OFAction> actions = ofPacketOut.getActions();
+        for (final OFAction action : actions) {
+            OFActionType actionType = action.getType();
+            if (actionType.equals(OFActionType.OUTPUT)) {
+                OFActionOutput ofActionOutput = (OFActionOutput) action;
+                OFPort ofPort = ofActionOutput.getPort();
+                ConnectPoint neighbourCp =
+                        ofSwitchService.neighbour(networkId, deviceId,
+                                                   PortNumber.portNumber(ofPort.getPortNumber()));
+                if (neighbourCp == null) {
+                    log.trace("No neighbour found for {} {}", deviceId, ofPort);
+                    continue;
+                }
+                OFSwitch neighbourSwitch = ofSwitchService.ofSwitch(networkId,
+                                                                    neighbourCp.deviceId());
+                neighbourSwitch.sendLldpResponse(ofPacketOut, neighbourCp.port());
+            }
+        }
+    }
+
+    @Override
+    public void sendLldpResponse(OFPacketOut po, PortNumber inPort) {
+        Match.Builder matchB = FACTORY.buildMatch();
+        matchB.setExact(MatchField.IN_PORT, OFPort.of((int) inPort.toLong()));
+        OFPacketIn pi = FACTORY.buildPacketIn()
+                .setBufferId(po.getBufferId())
+                .setMatch(matchB.build())
+                .setReason(OFPacketInReason.NO_MATCH)
+                .setData(po.getData())
+                .build();
+        log.trace("Sending packet in {}", pi);
+        controllerChannels().forEach(channel -> {
+            channel.writeAndFlush(Collections.singletonList(pi));
+        });
     }
 
     @Override
diff --git a/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFChannelHandler.java b/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFChannelHandler.java
index cbf3569..aea97d1 100644
--- a/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFChannelHandler.java
+++ b/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFChannelHandler.java
@@ -124,6 +124,10 @@
                     case ROLE_REQUEST:
                         handler.ofSwitch.processRoleRequest(handler.channel, msg);
                         break;
+                    case PACKET_OUT:
+                        // TODO: check if this is lldp - ignore if it is not lldp
+                        handler.ofSwitch.processLldp(handler.channel, msg);
+                        break;
                     case ERROR:
                         handler.logErrorClose((OFErrorMsg) msg);
                         break;
diff --git a/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFSwitchManager.java b/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFSwitchManager.java
index 36be111..101e531 100644
--- a/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFSwitchManager.java
+++ b/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFSwitchManager.java
@@ -34,9 +34,12 @@
 import org.onosproject.incubator.net.virtual.VirtualNetworkListener;
 import org.onosproject.incubator.net.virtual.VirtualNetworkService;
 import org.onosproject.incubator.net.virtual.VirtualPort;
+import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
 import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
 import org.onosproject.net.device.DeviceEvent;
 import org.onosproject.net.device.DeviceListener;
 import org.onosproject.net.device.DeviceService;
@@ -44,6 +47,7 @@
 import org.onosproject.net.flow.FlowRuleEvent;
 import org.onosproject.net.flow.FlowRuleListener;
 import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.link.LinkService;
 import org.onosproject.net.packet.PacketContext;
 import org.onosproject.net.packet.PacketProcessor;
 import org.onosproject.net.packet.PacketService;
@@ -181,6 +185,19 @@
         return portStatistics;
     }
 
+    @Override
+    public ConnectPoint neighbour(NetworkId networkId, DeviceId deviceId, PortNumber portNumber) {
+        ConnectPoint cp = new ConnectPoint(deviceId, portNumber);
+        LinkService linkService = virtualNetService.get(networkId, LinkService.class);
+        Set<Link> links = linkService.getEgressLinks(cp);
+        log.trace("neighbour cp {} egressLinks {}", cp, links);
+        if (links != null && links.size() > 0) {
+            Link link = links.iterator().next();
+            return link.src();
+        }
+        return null;
+    }
+
     private void addOFSwitch(NetworkId networkId, DeviceId deviceId) {
         OFSwitch ofSwitch = DefaultOFSwitch.of(
                 dpidWithDeviceId(deviceId),