[CORD-1434][CORD-1112] DHCP relay manager

Change-Id: I2e4d8fc8e85ed66b33ac517660ee72a1c0183597
diff --git a/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java b/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java
index 996bd43..e197a0d 100644
--- a/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java
+++ b/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java
@@ -43,6 +43,7 @@
 import org.onosproject.cfg.ComponentConfigService;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
+import org.onosproject.incubator.net.intf.InterfaceService;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
@@ -77,7 +78,10 @@
 
 import java.nio.ByteBuffer;
 import java.util.Dictionary;
+import java.util.Objects;
+import java.util.Optional;
 import java.util.concurrent.ExecutorService;
+import java.util.stream.Stream;
 
 import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
 import static org.onlab.util.Tools.groupedThreads;
@@ -89,7 +93,6 @@
  */
 @Component(immediate = true)
 public class HostLocationProvider extends AbstractProvider implements HostProvider {
-
     private final Logger log = getLogger(getClass());
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
@@ -113,6 +116,9 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected ComponentConfigService cfgService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected InterfaceService interfaceService;
+
     private HostProviderService providerService;
 
     private final InternalHostProvider processor = new InternalHostProvider();
@@ -124,18 +130,18 @@
             label = "Enable host removal on port/device down events")
     private boolean hostRemovalEnabled = true;
 
-    @Property(name = "useArp", boolValue = true,
-            label = "Enable using ARP for neighbor discovery by the " +
+    @Property(name = "requestArp", boolValue = true,
+            label = "Request ARP packets for neighbor discovery by the " +
                     "Host Location Provider; default is true")
-    private boolean useArp = true;
+    private boolean requestArp = true;
 
-    @Property(name = "useIpv6ND", boolValue = false,
-            label = "Enable using IPv6 Neighbor Discovery by the " +
+    @Property(name = "requestIpv6ND", boolValue = false,
+            label = "Requests IPv6 Neighbor Discovery by the " +
                     "Host Location Provider; default is false")
-    private boolean useIpv6ND = false;
+    private boolean requestIpv6ND = false;
 
     @Property(name = "useDhcp", boolValue = false,
-            label = "Enable using DHCP for neighbor discovery by the " +
+            label = "Use DHCP for neighbor discovery by the " +
                     "Host Location Provider; default is false")
     private boolean useDhcp = false;
 
@@ -202,7 +208,7 @@
         TrafficSelector arpSelector = DefaultTrafficSelector.builder()
                 .matchEthType(Ethernet.TYPE_ARP)
                 .build();
-        if (useArp) {
+        if (requestArp) {
             packetService.requestPackets(arpSelector, PacketPriority.CONTROL, appId);
         } else {
             packetService.cancelPackets(arpSelector, PacketPriority.CONTROL, appId);
@@ -219,7 +225,7 @@
                 .matchIPProtocol(IPv6.PROTOCOL_ICMP6)
                 .matchIcmpv6Type(ICMP6.NEIGHBOR_ADVERTISEMENT)
                 .build();
-        if (useIpv6ND) {
+        if (requestIpv6ND) {
             packetService.requestPackets(ipv6NsSelector, PacketPriority.CONTROL, appId);
             packetService.requestPackets(ipv6NaSelector, PacketPriority.CONTROL, appId);
         } else {
@@ -238,13 +244,6 @@
                 .matchIPProtocol(IPv4.PROTOCOL_UDP)
                 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
                 .build();
-        if (useDhcp) {
-            packetService.requestPackets(dhcpServerSelector, PacketPriority.CONTROL, appId);
-            packetService.requestPackets(dhcpClientSelector, PacketPriority.CONTROL, appId);
-        } else {
-            packetService.cancelPackets(dhcpServerSelector, PacketPriority.CONTROL, appId);
-            packetService.cancelPackets(dhcpClientSelector, PacketPriority.CONTROL, appId);
-        }
     }
 
     /**
@@ -278,51 +277,51 @@
         flag = Tools.isPropertyEnabled(properties, "hostRemovalEnabled");
         if (flag == null) {
             log.info("Host removal on port/device down events is not configured, " +
-                             "using current value of {}", hostRemovalEnabled);
+                     "using current value of {}", hostRemovalEnabled);
         } else {
             hostRemovalEnabled = flag;
             log.info("Configured. Host removal on port/device down events is {}",
                      hostRemovalEnabled ? "enabled" : "disabled");
         }
 
-        flag = Tools.isPropertyEnabled(properties, "useArp");
+        flag = Tools.isPropertyEnabled(properties, "requestArp");
         if (flag == null) {
             log.info("Using ARP is not configured, " +
-                    "using current value of {}", useArp);
+                     "using current value of {}", requestArp);
         } else {
-            useArp = flag;
+            requestArp = flag;
             log.info("Configured. Using ARP is {}",
-                    useArp ? "enabled" : "disabled");
+                     requestArp ? "enabled" : "disabled");
         }
 
-        flag = Tools.isPropertyEnabled(properties, "useIpv6ND");
+        flag = Tools.isPropertyEnabled(properties, "requestIpv6ND");
         if (flag == null) {
             log.info("Using IPv6 Neighbor Discovery is not configured, " +
-                             "using current value of {}", useIpv6ND);
+                             "using current value of {}", requestIpv6ND);
         } else {
-            useIpv6ND = flag;
+            requestIpv6ND = flag;
             log.info("Configured. Using IPv6 Neighbor Discovery is {}",
-                     useIpv6ND ? "enabled" : "disabled");
+                     requestIpv6ND ? "enabled" : "disabled");
         }
 
         flag = Tools.isPropertyEnabled(properties, "useDhcp");
         if (flag == null) {
             log.info("Using DHCP is not configured, " +
-                    "using current value of {}", useDhcp);
+                     "using current value of {}", useDhcp);
         } else {
             useDhcp = flag;
             log.info("Configured. Using DHCP is {}",
-                    useDhcp ? "enabled" : "disabled");
+                     useDhcp ? "enabled" : "disabled");
         }
 
         flag = Tools.isPropertyEnabled(properties, "requestInterceptsEnabled");
         if (flag == null) {
             log.info("Request intercepts is not configured, " +
-                    "using current value of {}", requestInterceptsEnabled);
+                     "using current value of {}", requestInterceptsEnabled);
         } else {
             requestInterceptsEnabled = flag;
             log.info("Configured. Request intercepts is {}",
-                    requestInterceptsEnabled ? "enabled" : "disabled");
+                     requestInterceptsEnabled ? "enabled" : "disabled");
         }
     }
 
@@ -475,30 +474,12 @@
                 createOrUpdateHost(hid, srcMac, vlan, hloc, ip);
 
             // IPv4: update location only
-            // DHCP ACK: additionally update IP of DHCP client
             } else if (eth.getEtherType() == Ethernet.TYPE_IPV4) {
-                IPacket pkt = eth.getPayload();
-                if (pkt != null && pkt instanceof IPv4) {
-                    pkt = pkt.getPayload();
-                    if (pkt != null && pkt instanceof UDP) {
-                        pkt = pkt.getPayload();
-                        if (pkt != null && pkt instanceof DHCP) {
-                            DHCP dhcp = (DHCP) pkt;
-                            if (dhcp.getOptions().stream()
-                                    .anyMatch(dhcpOption -> dhcpOption.getCode() ==
-                                            DHCP.DHCPOptionCode.OptionCode_MessageType.getValue() &&
-                                            dhcpOption.getLength() == 1 &&
-                                            dhcpOption.getData()[0] == DHCP.MsgType.DHCPACK.getValue())) {
-                                MacAddress hostMac = MacAddress.valueOf(dhcp.getClientHardwareAddress());
-                                VlanId hostVlan = VlanId.vlanId(eth.getVlanID());
-                                HostId hostId = HostId.hostId(hostMac, hostVlan);
-                                updateHostIp(hostId, IpAddress.valueOf(dhcp.getYourIPAddress()));
-                            }
-                        }
-                    }
+                // DHCP ACK: additionally update IP of DHCP client
+                Optional<DHCP> dhcp = findDhcp(eth);
+                if (useDhcp || !dhcp.isPresent()) {
+                    createOrUpdateHost(hid, srcMac, vlan, hloc, null);
                 }
-                createOrUpdateHost(hid, srcMac, vlan, hloc, null);
-
             //
             // NeighborAdvertisement and NeighborSolicitation: possible
             // new hosts, update both location and IP.
@@ -546,6 +527,21 @@
                 createOrUpdateHost(hid, srcMac, vlan, hloc, null);
             }
         }
+
+        private Optional<DHCP> findDhcp(Ethernet eth) {
+            IPacket pkt = eth.getPayload();
+            return Stream.of(pkt)
+                    .filter(Objects::nonNull)
+                    .filter(p -> p instanceof IPv4)
+                    .map(IPacket::getPayload)
+                    .filter(Objects::nonNull)
+                    .filter(p -> p instanceof UDP)
+                    .map(IPacket::getPayload)
+                    .filter(Objects::nonNull)
+                    .filter(p -> p instanceof DHCP)
+                    .map(p -> (DHCP) p)
+                    .findFirst();
+        }
     }
 
     // Auxiliary listener to device events.
diff --git a/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java b/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java
index b28ec17..080e0e4 100644
--- a/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java
+++ b/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java
@@ -15,16 +15,19 @@
  */
 package org.onosproject.provider.host.impl;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import com.google.common.util.concurrent.MoreExecutors;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.onlab.junit.TestUtils;
 import org.onlab.osgi.ComponentContextAdapter;
 import org.onlab.packet.ARP;
 import org.onlab.packet.ChassisId;
 import org.onlab.packet.DHCP;
+import org.onlab.packet.dhcp.CircuitId;
 import org.onlab.packet.dhcp.DhcpOption;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.ICMP6;
@@ -35,6 +38,7 @@
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.UDP;
 import org.onlab.packet.VlanId;
+import org.onlab.packet.dhcp.DhcpRelayAgentOption;
 import org.onlab.packet.ndp.NeighborAdvertisement;
 import org.onlab.packet.ndp.NeighborSolicitation;
 import org.onlab.packet.ndp.RouterAdvertisement;
@@ -43,6 +47,9 @@
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
 import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.incubator.net.intf.Interface;
+import org.onosproject.incubator.net.intf.InterfaceListener;
+import org.onosproject.incubator.net.intf.InterfaceService;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DefaultDevice;
 import org.onosproject.net.DefaultHost;
@@ -60,6 +67,7 @@
 import org.onosproject.net.host.HostProviderRegistry;
 import org.onosproject.net.host.HostProviderService;
 import org.onosproject.net.host.HostServiceAdapter;
+import org.onosproject.net.host.InterfaceIpAddress;
 import org.onosproject.net.packet.DefaultInboundPacket;
 import org.onosproject.net.packet.InboundPacket;
 import org.onosproject.net.packet.PacketContextAdapter;
@@ -85,7 +93,6 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
 import static org.onlab.packet.VlanId.vlanId;
 import static org.onosproject.net.Device.Type.SWITCH;
 import static org.onosproject.net.DeviceId.deviceId;
@@ -109,6 +116,7 @@
     private static final String DEV6 = "of:6";
 
     private static final VlanId VLAN = vlanId();
+    private static final VlanId VLAN_100 = VlanId.vlanId("100");
 
     // IPv4 Host
     private static final MacAddress MAC = MacAddress.valueOf("00:00:11:00:00:01");
@@ -122,6 +130,10 @@
             new DefaultHost(PROVIDER_ID, hostId(MAC), MAC,
                     VLAN, LOCATION,
                     ImmutableSet.of(IP_ADDRESS));
+    private static final DefaultHost HOST_VLAN_100 =
+            new DefaultHost(PROVIDER_ID, hostId(MAC, VLAN_100), MAC,
+                            VLAN_100, LOCATION,
+                            ImmutableSet.of(IP_ADDRESS));
 
     // IPv6 Host
     private static final MacAddress MAC2 = MacAddress.valueOf("00:00:22:00:00:02");
@@ -149,6 +161,14 @@
                     VLAN, LOCATION3,
                     ImmutableSet.of(IP_ADDRESS3));
 
+    // Gateway information for relay agent
+    private static final InterfaceIpAddress GW_IFACE_ADDR = InterfaceIpAddress.valueOf("10.0.1.1/32");
+    private static final Interface GW_IFACE = new Interface("gateway",
+                                                                    LOCATION,
+                                                                    ImmutableList.of(GW_IFACE_ADDR),
+                                                                    null,
+                                                                    VLAN_100);
+
     private static final ComponentContextAdapter CTX_FOR_REMOVE =
             new ComponentContextAdapter() {
                 @Override
@@ -173,6 +193,7 @@
     private final TestDeviceService deviceService = new TestDeviceService();
     private final TestHostService hostService = new TestHostService();
     private final TestPacketService packetService = new TestPacketService();
+    private final TestInterfaceService interfaceService = new TestInterfaceService();
 
     private PacketProcessor testProcessor;
     private CoreService coreService;
@@ -183,7 +204,6 @@
 
     @Before
     public void setUp() {
-
         coreService = createMock(CoreService.class);
         expect(coreService.registerApplication(appId.name()))
                 .andReturn(appId).anyTimes();
@@ -197,6 +217,7 @@
         provider.packetService = packetService;
         provider.deviceService = deviceService;
         provider.hostService = hostService;
+        provider.interfaceService = interfaceService;
 
         provider.activate(CTX_FOR_NO_REMOVE);
 
@@ -335,8 +356,9 @@
      */
     @Test
     public void receiveDhcp() {
+        TestUtils.setField(provider, "useDhcp", true);
         // DHCP Request
-        testProcessor.process(new TestDhcpRequestPacketContext(DEV1));
+        testProcessor.process(new TestDhcpRequestPacketContext(DEV1, VLAN));
         assertThat("receiveDhcpRequest. One host description expected",
                 providerService.descriptions.size(), is(1));
         // Should learn the MAC and location of DHCP client
@@ -347,23 +369,30 @@
         assertThat(descr.vlan(), is(VLAN));
 
         // DHCP Ack
-        testProcessor.process(new TestDhcpAckPacketContext(DEV1));
+        testProcessor.process(new TestDhcpAckPacketContext(DEV1, false));
         assertThat("receiveDhcpAck. Two additional host descriptions expected",
-                providerService.descriptions.size(), is(3));
-        // Should learn the IP of DHCP client
-        HostDescription descr2 = providerService.descriptions.get(1);
-        assertThat(descr2.location(), is(LOCATION));
-        assertThat(descr2.hwAddress(), is(MAC));
-        assertThat(descr2.ipAddress().size(), is(1));
-        assertTrue(descr2.ipAddress().contains(IP_ADDRESS));
-        assertThat(descr2.vlan(), is(VLAN));
-        // Should also learn the MAC, location of DHCP server
-        HostDescription descr3 = providerService.descriptions.get(2);
-        assertThat(descr3.location(), is(LOCATION3));
-        assertThat(descr3.hwAddress(), is(MAC3));
-        assertThat(descr3.ipAddress().size(), is(0));
-        assertThat(descr3.vlan(), is(VLAN));
+                providerService.descriptions.size(), is(2));
 
+        // Should also learn the MAC, location of DHCP server
+        HostDescription descr2 = providerService.descriptions.get(1);
+        assertThat(descr2.location(), is(LOCATION3));
+        assertThat(descr2.hwAddress(), is(MAC3));
+        assertThat(descr2.ipAddress().size(), is(0));
+        assertThat(descr2.vlan(), is(VLAN));
+
+        // Should not update the IP address of the host.
+    }
+
+    /**
+     * The host store should not updated when we disabled "useDhcp".
+     */
+    @Test
+    public void receiveDhcpButNotEnabled() {
+        TestUtils.setField(provider, "useDhcp", false);
+        // DHCP Request
+        testProcessor.process(new TestDhcpRequestPacketContext(DEV1, VLAN));
+        assertThat("receiveDhcpButNotEnabled. No host description expected",
+                   providerService.descriptions.size(), is(0));
     }
 
     /**
@@ -604,10 +633,12 @@
      */
     private class TestDhcpRequestPacketContext extends PacketContextAdapter {
         private final String deviceId;
+        private final VlanId vlanId;
 
-        public TestDhcpRequestPacketContext(String deviceId) {
+        public TestDhcpRequestPacketContext(String deviceId, VlanId vlanId) {
             super(0, null, null, false);
             this.deviceId = deviceId;
+            this.vlanId = vlanId;
         }
 
         @Override
@@ -632,7 +663,7 @@
             ipv4.setSourceAddress(IP_ADDRESS.toString());
             Ethernet eth = new Ethernet();
             eth.setEtherType(Ethernet.TYPE_IPV4)
-                    .setVlanID(VLAN.toShort())
+                    .setVlanID(this.vlanId.toShort())
                     .setSourceMACAddress(MAC)
                     .setDestinationMACAddress(MAC3)
                     .setPayload(ipv4);
@@ -648,10 +679,12 @@
      */
     private class TestDhcpAckPacketContext extends PacketContextAdapter {
         private final String deviceId;
+        private final boolean withRelayinfo;
 
-        public TestDhcpAckPacketContext(String deviceId) {
+        public TestDhcpAckPacketContext(String deviceId, boolean withRelayInfo) {
             super(0, null, null, false);
             this.deviceId = deviceId;
+            this.withRelayinfo = withRelayInfo;
         }
 
         @Override
@@ -663,10 +696,29 @@
             dhcpOption.setCode(DHCP.DHCPOptionCode.OptionCode_MessageType.getValue());
             dhcpOption.setData(dhcpMsgType);
             dhcpOption.setLength((byte) 1);
+
             DHCP dhcp = new DHCP();
-            dhcp.setOptions(Collections.singletonList(dhcpOption));
+
+            if (withRelayinfo) {
+                CircuitId cid = new CircuitId(LOCATION.toString(), VLAN_100);
+                byte[] circuitId = cid.serialize();
+                DhcpOption circuitIdSubOption = new DhcpOption();
+                circuitIdSubOption.setCode(DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID.getValue());
+                circuitIdSubOption.setData(circuitId);
+                circuitIdSubOption.setLength((byte) circuitId.length);
+
+                DhcpRelayAgentOption relayInfoOption = new DhcpRelayAgentOption();
+                relayInfoOption.setCode(DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue());
+                relayInfoOption.addSubOption(circuitIdSubOption);
+                dhcp.setOptions(ImmutableList.of(dhcpOption, relayInfoOption));
+                dhcp.setGatewayIPAddress(GW_IFACE_ADDR.ipAddress().getIp4Address().toInt());
+            } else {
+                dhcp.setOptions(ImmutableList.of(dhcpOption));
+            }
+
             dhcp.setClientHardwareAddress(MAC.toBytes());
             dhcp.setYourIPAddress(IP_ADDRESS.getIp4Address().toInt());
+
             UDP udp = new UDP();
             udp.setPayload(dhcp);
             udp.setSourcePort(UDP.DHCP_SERVER_PORT);
@@ -961,9 +1013,60 @@
                 return HOST2;
             } else if (hostId.equals(HostId.hostId(MAC3, VLAN))) {
                 return HOST3;
+            } else if (hostId.equals(HostId.hostId(MAC, VLAN_100))) {
+                return HOST_VLAN_100;
             }
             return null;
         }
+    }
 
+    private class TestInterfaceService implements InterfaceService {
+        @Override
+        public Set<Interface> getInterfaces() {
+            return null;
+        }
+
+        @Override
+        public Interface getInterfaceByName(ConnectPoint connectPoint, String name) {
+            return null;
+        }
+
+        @Override
+        public Set<Interface> getInterfacesByPort(ConnectPoint port) {
+            return null;
+        }
+
+        public Set<Interface> getInterfacesByIp(IpAddress ip) {
+            if (ip.equals(GW_IFACE_ADDR.ipAddress())) {
+                return ImmutableSet.of(GW_IFACE);
+            } else {
+                return ImmutableSet.of();
+            }
+        }
+
+        @Override
+        public Set<Interface> getInterfacesByVlan(VlanId vlan) {
+            return null;
+        }
+
+        @Override
+        public Interface getMatchingInterface(IpAddress ip) {
+            return null;
+        }
+
+        @Override
+        public Set<Interface> getMatchingInterfaces(IpAddress ip) {
+            return null;
+        }
+
+        @Override
+        public void addListener(InterfaceListener listener) {
+
+        }
+
+        @Override
+        public void removeListener(InterfaceListener listener) {
+
+        }
     }
 }