VOL-535 : Use OLT uplink port for packet-in/packet-out towards DHCP server

It is configurable to use this approach or to use an OVS/switch for the same

Change-Id: Iafbdcf3d70eea029c7949622a7048639c333a4d9
diff --git a/README.md b/README.md
index 5a1676c..259705d 100644
--- a/README.md
+++ b/README.md
@@ -6,17 +6,24 @@
 
 DHCP Option 82 with CircuitId and RemoteId are added to packets sent to the DHCP server and  Option 82 received from the server are removed before relaying back to the client. The CircuitId and Remote Id are retrieved from `Sadis` Service.
 
+There are two options to packet-in/packet-out to the DHCP Server.
+* To use a SDN controlled switch which can connect to the DHCP Server; using the configuration parameter `dhcpserverConnectPoints`
+* To use the uplink NNI port of the OLT (from which the DHCP Discover/Request was received) for doing the packet-out/packet-in; using the configuration parameter `useOltUplinkForServerPktInOut`
+
 # Configuration
 ```sh
 "org.opencord.dhcpl2relay" : {
       "dhcpl2relay" : {
-        "dhcpserverConnectPoints" : [ "of:00000000000000b2/2" ]
+        "dhcpServerConnectPoints" : [ "of:00000000000000b2/2" ],
+        "useOltUplinkForServerPktInOut" : true
       }
     }
  ```
  ### Configuration Parameters
 ##### dhcpServerConnectPoints
 Port on the switch through which the DHCP Server is reachable
+##### useOltUplinkForServerPktInOut
+The default value of this parameter is **false**. Only if this parameter is false the dhcpServerConnectPoints parameter is used else not
 
 # Example configuration of Sadis
 ```sh
diff --git a/pom.xml b/pom.xml
index c671dd7..dbc76f6 100755
--- a/pom.xml
+++ b/pom.xml
@@ -44,7 +44,7 @@
             org.opencord.sadis
         </onos.app.requires>
 	<onos.version>1.10.9</onos.version>
-        <sadis.api.version>1.2.0-SNAPSHOT</sadis.api.version>
+        <sadis.api.version>2.0.0-SNAPSHOT</sadis.api.version>
     </properties>
 
     <dependencies>
diff --git a/src/main/java/org/opencord/dhcpl2relay/DhcpL2Relay.java b/src/main/java/org/opencord/dhcpl2relay/DhcpL2Relay.java
index 5516086..b25f615 100755
--- a/src/main/java/org/opencord/dhcpl2relay/DhcpL2Relay.java
+++ b/src/main/java/org/opencord/dhcpl2relay/DhcpL2Relay.java
@@ -45,8 +45,11 @@
 import org.onosproject.mastership.MastershipService;
 import org.onosproject.net.AnnotationKeys;
 import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
 import org.onosproject.net.Host;
 import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
 import org.onosproject.net.config.ConfigFactory;
 import org.onosproject.net.config.NetworkConfigEvent;
 import org.onosproject.net.config.NetworkConfigListener;
@@ -74,9 +77,11 @@
 
 import java.nio.ByteBuffer;
 import java.util.Arrays;
+import java.util.ArrayList;
 import java.util.Dictionary;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Collectors;
@@ -153,6 +158,9 @@
     private ApplicationId appId;
 
     static Map<String, DhcpAllocationInfo> allocationMap = Maps.newConcurrentMap();
+    private boolean modifyClientPktsSrcDstMac = false;
+    //Whether to use the uplink port of the OLTs to send/receive messages to the DHCP server
+    private boolean useOltUplink = false;
 
     @Activate
     protected void activate(ComponentContext context) {
@@ -213,7 +221,10 @@
      * @return true if all information we need have been initialized
      */
     private boolean configured() {
-        return dhcpServerConnectPoint.get() != null;
+        if (!useOltUplink) {
+            return dhcpServerConnectPoint.get() != null;
+        }
+        return true;
     }
 
     /**
@@ -254,29 +265,140 @@
         }
 
         dhcpConnectPoints = Sets.newConcurrentHashSet(cfg.getDhcpServerConnectPoint());
+        modifyClientPktsSrcDstMac = cfg.getModifySrcDstMacAddresses();
+        useOltUplink = cfg.getUseOltUplinkForServerPktInOut();
 
-        selectServerConnectPoint();
+        if (!useOltUplink) {
+            selectServerConnectPoint();
+        }
 
         log.info("dhcp server connect point: " + dhcpServerConnectPoint);
     }
 
     /**
+     * Returns all the uplink ports of OLTs configured in SADIS.
+     * Only ports visible in ONOS and for which this instance is master
+     * are returned
+     */
+    private List<ConnectPoint> getUplinkPortsOfOlts() {
+        List<ConnectPoint> cps = new ArrayList<>();
+
+        // find all the olt devices and if their uplink ports are visible
+        Iterable<Device> devices = deviceService.getDevices();
+        for (Device d : devices) {
+            // check if this device is provisioned in Sadis
+
+            log.debug("getUplinkPortsOfOlts: Checking mastership of {}", d);
+            // do only for devices for which we are the master
+            if (!mastershipService.isLocalMaster(d.id())) {
+                continue;
+            }
+
+            String devSerialNo = d.serialNumber();
+            SubscriberAndDeviceInformation deviceInfo = subsService.get(devSerialNo);
+            log.debug("getUplinkPortsOfOlts: Found device: {}", deviceInfo);
+            if (deviceInfo != null) {
+                // check if the uplink port with that number is available on the device
+                PortNumber pNum = PortNumber.portNumber(deviceInfo.uplinkPort());
+                Port port = deviceService.getPort(d.id(), pNum);
+                log.debug("getUplinkPortsOfOlts: Found port: {}", port);
+                if (port != null) {
+                    cps.add(new ConnectPoint(d.id(), pNum));
+                }
+            }
+        }
+        return cps;
+    }
+
+    /**
+     * Returns whether the passed port is the uplink port of the olt device.
+     */
+    private boolean isUplinkPortOfOlt(DeviceId dId, Port p) {
+        log.debug("isUplinkPortOfOlt: DeviceId: {} Port: {}", dId, p);
+        // do only for devices for which we are the master
+        if (!mastershipService.isLocalMaster(dId)) {
+            return false;
+        }
+
+        Device d = deviceService.getDevice(dId);
+        SubscriberAndDeviceInformation deviceInfo = subsService.get(d.serialNumber());
+
+        if (deviceInfo != null) {
+            return (deviceInfo.uplinkPort() == p.number().toLong());
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns the connectPoint which is the uplink port of the OLT.
+     */
+    private ConnectPoint getUplinkConnectPointOfOlt(DeviceId dId) {
+
+        Device d = deviceService.getDevice(dId);
+        SubscriberAndDeviceInformation deviceInfo = subsService.get(d.serialNumber());
+        log.debug("getUplinkConnectPointOfOlt DeviceId: {} devInfo: {}", dId, deviceInfo);
+        if (deviceInfo != null) {
+            PortNumber pNum = PortNumber.portNumber(deviceInfo.uplinkPort());
+            Port port = deviceService.getPort(d.id(), pNum);
+            if (port != null) {
+                return new ConnectPoint(d.id(), pNum);
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Request DHCP packet from particular connect point via PacketService.
+     */
+    private void requestDhcpPacketsFromConnectPoint(ConnectPoint cp) {
+        TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchInPort(cp.port())
+                .matchIPProtocol(IPv4.PROTOCOL_UDP)
+                .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
+        packetService.requestPackets(selectorServer.build(),
+                PacketPriority.CONTROL, appId, Optional.of(cp.deviceId()));
+    }
+
+    /**
+     * Cancel DHCP packet from particular connect point via PacketService.
+     */
+    private void cancelDhcpPacketsFromConnectPoint(ConnectPoint cp) {
+        TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchInPort(cp.port())
+                .matchIPProtocol(IPv4.PROTOCOL_UDP)
+                .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
+        packetService.cancelPackets(selectorServer.build(),
+                PacketPriority.CONTROL, appId, Optional.of(cp.deviceId()));
+    }
+
+    /**
      * Request DHCP packet in via PacketService.
      */
     private void requestDhcpPackets() {
-        TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
-                .matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPProtocol(IPv4.PROTOCOL_UDP)
-                .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
-        packetService.requestPackets(selectorServer.build(),
-                PacketPriority.CONTROL, appId);
+        if (!useOltUplink) {
+            TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
+                    .matchEthType(Ethernet.TYPE_IPV4)
+                    .matchIPProtocol(IPv4.PROTOCOL_UDP)
+                    .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
+            packetService.requestPackets(selectorServer.build(),
+                    PacketPriority.CONTROL, appId);
 
-        TrafficSelector.Builder selectorClient = DefaultTrafficSelector.builder()
-                .matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPProtocol(IPv4.PROTOCOL_UDP)
-                .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT));
-        packetService.requestPackets(selectorClient.build(),
-                PacketPriority.CONTROL, appId);
+            TrafficSelector.Builder selectorClient = DefaultTrafficSelector.builder()
+                    .matchEthType(Ethernet.TYPE_IPV4)
+                    .matchIPProtocol(IPv4.PROTOCOL_UDP)
+                    .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT));
+            packetService.requestPackets(selectorClient.build(),
+                    PacketPriority.CONTROL, appId);
+        } else {
+            for (ConnectPoint cp: getUplinkPortsOfOlts()) {
+                log.debug("requestDhcpPackets: ConnectPoint: {}", cp);
+                requestDhcpPacketsFromConnectPoint(cp);
+            }
+        }
 
     }
 
@@ -284,19 +406,25 @@
      * Cancel requested DHCP packets in via packet service.
      */
     private void cancelDhcpPackets() {
-        TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
-                .matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPProtocol(IPv4.PROTOCOL_UDP)
-                .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
-        packetService.cancelPackets(selectorServer.build(),
-                PacketPriority.CONTROL, appId);
+        if (!useOltUplink) {
+            TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
+                    .matchEthType(Ethernet.TYPE_IPV4)
+                    .matchIPProtocol(IPv4.PROTOCOL_UDP)
+                    .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
+            packetService.cancelPackets(selectorServer.build(),
+                    PacketPriority.CONTROL, appId);
 
-        TrafficSelector.Builder selectorClient = DefaultTrafficSelector.builder()
-                .matchEthType(Ethernet.TYPE_IPV4)
-                .matchIPProtocol(IPv4.PROTOCOL_UDP)
-                .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT));
-        packetService.cancelPackets(selectorClient.build(),
-                PacketPriority.CONTROL, appId);
+            TrafficSelector.Builder selectorClient = DefaultTrafficSelector.builder()
+                    .matchEthType(Ethernet.TYPE_IPV4)
+                    .matchIPProtocol(IPv4.PROTOCOL_UDP)
+                    .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT));
+            packetService.cancelPackets(selectorClient.build(),
+                    PacketPriority.CONTROL, appId);
+        } else {
+            for (ConnectPoint cp: getUplinkPortsOfOlts()) {
+                cancelDhcpPacketsFromConnectPoint(cp);
+            }
+        }
     }
 
     public static Map<String, DhcpAllocationInfo> allocationMap() {
@@ -417,21 +545,29 @@
         }
 
         //forward the packet to ConnectPoint where the DHCP server is attached.
-        private void forwardPacket(Ethernet packet) {
+        private void forwardPacket(Ethernet packet, PacketContext context) {
+            ConnectPoint toSendTo = null;
 
-            if (dhcpServerConnectPoint.get() != null) {
+            if (!useOltUplink) {
+                toSendTo = dhcpServerConnectPoint.get();
+            } else {
+                toSendTo = getUplinkConnectPointOfOlt(context.inPacket().
+                                                      receivedFrom().deviceId());
+            }
+
+            if (toSendTo != null) {
                 TrafficTreatment t = DefaultTrafficTreatment.builder()
-                        .setOutput(dhcpServerConnectPoint.get().port()).build();
+                        .setOutput(toSendTo.port()).build();
                 OutboundPacket o = new DefaultOutboundPacket(
-                        dhcpServerConnectPoint.get().deviceId(), t,
+                        toSendTo.deviceId(), t,
                         ByteBuffer.wrap(packet.serialize()));
                 if (log.isTraceEnabled()) {
                     log.trace("Relaying packet to dhcp server {} at {}",
-                            packet, dhcpServerConnectPoint.get());
+                            packet, toSendTo);
                 }
                 packetService.emit(o);
             } else {
-                log.warn("No dhcp server connect point");
+                log.error("No connect point to send msg to DHCP Server");
             }
         }
 
@@ -464,7 +600,7 @@
                     Ethernet ethernetPacketDiscover =
                             processDhcpPacketFromClient(context, packet);
                     if (ethernetPacketDiscover != null) {
-                        forwardPacket(ethernetPacketDiscover);
+                        forwardPacket(ethernetPacketDiscover, context);
                     }
                     break;
                 case DHCPOFFER:
@@ -478,7 +614,7 @@
                     Ethernet ethernetPacketRequest =
                             processDhcpPacketFromClient(context, packet);
                     if (ethernetPacketRequest != null) {
-                        forwardPacket(ethernetPacketRequest);
+                        forwardPacket(ethernetPacketRequest, context);
                     }
                     break;
                 case DHCPACK:
@@ -537,8 +673,10 @@
 
             ipv4Packet.setPayload(udpPacket);
             etherReply.setPayload(ipv4Packet);
-            etherReply.setSourceMACAddress(relayAgentMac);
-            etherReply.setDestinationMACAddress(dhcpConnectMac);
+            if (modifyClientPktsSrcDstMac) {
+                etherReply.setSourceMACAddress(relayAgentMac);
+                etherReply.setDestinationMACAddress(dhcpConnectMac);
+            }
 
             etherReply.setPriorityCode(ethernetPacket.getPriorityCode());
             etherReply.setVlanID(cTag(context).toShort());
@@ -710,13 +848,15 @@
     private class InnerMastershipListener implements MastershipListener {
         @Override
         public void event(MastershipEvent event) {
-            if (dhcpServerConnectPoint.get() != null &&
-                    dhcpServerConnectPoint.get().deviceId().
-                            equals(event.subject())) {
-                log.trace("Mastership Event recevived for {}", event.subject());
-                // mastership of the device for our connect point has changed
-                // reselect
-                selectServerConnectPoint();
+            if (!useOltUplink) {
+                if (dhcpServerConnectPoint.get() != null &&
+                        dhcpServerConnectPoint.get().deviceId().
+                                equals(event.subject())) {
+                    log.trace("Mastership Event recevived for {}", event.subject());
+                    // mastership of the device for our connect point has changed
+                    // reselect
+                    selectServerConnectPoint();
+                }
             }
         }
     }
@@ -729,27 +869,40 @@
         @Override
         public void event(DeviceEvent event) {
             log.trace("Device Event recevived for {} event {}", event.subject(), event.type());
-            if (dhcpServerConnectPoint.get() == null) {
-                switch (event.type()) {
-                    case DEVICE_ADDED:
-                    case DEVICE_AVAILABILITY_CHANGED:
-                        // some device is available check if we can get one
-                        selectServerConnectPoint();
-                        break;
-                    default:
-                        break;
+            if (!useOltUplink) {
+                if (dhcpServerConnectPoint.get() == null) {
+                    switch (event.type()) {
+                        case DEVICE_ADDED:
+                        case DEVICE_AVAILABILITY_CHANGED:
+                            // some device is available check if we can get one
+                            selectServerConnectPoint();
+                            break;
+                        default:
+                            break;
+                    }
+                    return;
                 }
-                return;
-            }
-            if (dhcpServerConnectPoint.get().deviceId().
-                    equals(event.subject().id())) {
+                if (dhcpServerConnectPoint.get().deviceId().
+                        equals(event.subject().id())) {
+                    switch (event.type()) {
+                        case DEVICE_AVAILABILITY_CHANGED:
+                        case DEVICE_REMOVED:
+                        case DEVICE_SUSPENDED:
+                            // state of our device has changed, check if we need
+                            // to re-select
+                            selectServerConnectPoint();
+                            break;
+                        default:
+                            break;
+                    }
+                }
+            } else {
                 switch (event.type()) {
-                    case DEVICE_AVAILABILITY_CHANGED:
-                    case DEVICE_REMOVED:
-                    case DEVICE_SUSPENDED:
-                        // state of our device has changed, check if we need
-                        // to re-select
-                        selectServerConnectPoint();
+                    case PORT_ADDED:
+                        if (isUplinkPortOfOlt(event.subject().id(), event.port())) {
+                            requestDhcpPacketsFromConnectPoint(new ConnectPoint(event.subject().id(),
+                                    event.port().number()));
+                        }
                         break;
                     default:
                         break;
diff --git a/src/main/java/org/opencord/dhcpl2relay/DhcpL2RelayConfig.java b/src/main/java/org/opencord/dhcpl2relay/DhcpL2RelayConfig.java
index eb60c7f..e939c78 100755
--- a/src/main/java/org/opencord/dhcpl2relay/DhcpL2RelayConfig.java
+++ b/src/main/java/org/opencord/dhcpl2relay/DhcpL2RelayConfig.java
@@ -34,11 +34,51 @@
 public class DhcpL2RelayConfig extends Config<ApplicationId> {
 
     private static final String DHCP_CONNECT_POINTS = "dhcpServerConnectPoints";
+    private static final String MODIFY_SRC_DST_MAC  = "modifyUlPacketsSrcDstMacAddresses";
+    private static final String USE_OLT_ULPORT_FOR_PKT_INOUT = "useOltUplinkForServerPktInOut";
+
+    private static final Boolean DEFAULT_MODIFY_SRC_DST_MAC = false;
+    private static final Boolean DEFAULT_USE_OLT_ULPORT_FOR_PKT_INOUT = false;
 
     @Override
     public boolean isValid() {
 
-        return hasOnlyFields(DHCP_CONNECT_POINTS);
+        return hasOnlyFields(DHCP_CONNECT_POINTS, MODIFY_SRC_DST_MAC,
+                USE_OLT_ULPORT_FOR_PKT_INOUT);
+    }
+
+    /**
+     * Returns whether the app would use the uplink port of OLT for sending/receving
+     * messages to/from the server.
+     *
+     * @return true if OLT uplink port is to be used, false otherwise
+     */
+    public boolean getUseOltUplinkForServerPktInOut() {
+        if (object == null) {
+            return DEFAULT_USE_OLT_ULPORT_FOR_PKT_INOUT;
+        }
+        if (!object.has(USE_OLT_ULPORT_FOR_PKT_INOUT)) {
+            return DEFAULT_USE_OLT_ULPORT_FOR_PKT_INOUT;
+        }
+
+        return object.path(USE_OLT_ULPORT_FOR_PKT_INOUT).asBoolean();
+    }
+
+    /**
+     * Returns whether the app would modify MAC address of uplink packets.
+     *
+     * @return whether app would modify src and dst MAC addresses or not of packets
+     *         sent to the DHCP server
+     */
+    public boolean getModifySrcDstMacAddresses() {
+        if (object == null) {
+            return DEFAULT_MODIFY_SRC_DST_MAC;
+        }
+        if (!object.has(MODIFY_SRC_DST_MAC)) {
+            return DEFAULT_MODIFY_SRC_DST_MAC;
+        }
+
+        return object.path(MODIFY_SRC_DST_MAC).asBoolean();
     }
 
     /**
diff --git a/src/test/java/org/opencord/dhcpl2relay/DhcpL2RelayTest.java b/src/test/java/org/opencord/dhcpl2relay/DhcpL2RelayTest.java
index 66c9b4e..ebde675 100755
--- a/src/test/java/org/opencord/dhcpl2relay/DhcpL2RelayTest.java
+++ b/src/test/java/org/opencord/dhcpl2relay/DhcpL2RelayTest.java
@@ -367,6 +367,11 @@
         public Set<ConnectPoint> getDhcpServerConnectPoint() {
             return ImmutableSet.of(SERVER_CONNECT_POINT);
         }
+
+        @Override
+        public boolean getModifySrcDstMacAddresses() {
+            return true;
+        }
     }
 
     /**