Use DHCP ACK to learn the DHCP client

This feature can be turn on/off via component config.
In addition, ARP interception can also be turned on/off now.

Change-Id: Ia3310fa3fb06821051fbf3363e51096d00781dbf
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 acf0a13..10a12ab 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
@@ -26,13 +26,19 @@
 import org.onlab.osgi.ComponentContextAdapter;
 import org.onlab.packet.ARP;
 import org.onlab.packet.ChassisId;
+import org.onlab.packet.DHCP;
+import org.onlab.packet.DHCPOption;
+import org.onlab.packet.DHCPPacketType;
 import org.onlab.packet.Ethernet;
+import org.onlab.packet.ICMP;
 import org.onlab.packet.ICMP6;
 import org.onlab.packet.IPv4;
 import org.onlab.packet.IPv6;
+import org.onlab.packet.Ip4Address;
 import org.onlab.packet.Ip6Address;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.MacAddress;
+import org.onlab.packet.UDP;
 import org.onlab.packet.VlanId;
 import org.onlab.packet.ndp.NeighborAdvertisement;
 import org.onlab.packet.ndp.NeighborSolicitation;
@@ -93,6 +99,7 @@
             new ProviderId("of", "org.onosproject.provider.host");
 
     private static final Integer INPORT = 10;
+    private static final Integer INPORT2 = 11;
     private static final String DEV1 = "of:1";
     private static final String DEV2 = "of:2";
     private static final String DEV3 = "of:3";
@@ -112,8 +119,8 @@
             new HostLocation(deviceId(DEV1), portNumber(INPORT), 0L);
     private static final DefaultHost HOST =
             new DefaultHost(PROVIDER_ID, hostId(MAC), MAC,
-                            vlanId(VlanId.UNTAGGED), LOCATION,
-                            ImmutableSet.of(IP_ADDRESS));
+                    VLAN, LOCATION,
+                    ImmutableSet.of(IP_ADDRESS));
 
     // IPv6 Host
     private static final MacAddress MAC2 = MacAddress.valueOf("00:00:22:00:00:02");
@@ -125,8 +132,21 @@
             new HostLocation(deviceId(DEV4), portNumber(INPORT), 0L);
     private static final DefaultHost HOST2 =
             new DefaultHost(PROVIDER_ID, hostId(MAC2), MAC2,
-                            vlanId(VlanId.UNTAGGED), LOCATION2,
-                            ImmutableSet.of(IP_ADDRESS2));
+                    VLAN, LOCATION2,
+                    ImmutableSet.of(IP_ADDRESS2));
+
+    // DHCP Server
+    private static final MacAddress MAC3 = MacAddress.valueOf("00:00:33:00:00:03");
+    private static final byte[] IP3 = new byte[]{10, 0, 0, 2};
+    private static final IpAddress IP_ADDRESS3 =
+            IpAddress.valueOf(IpAddress.Version.INET, IP3);
+
+    private static final HostLocation LOCATION3 =
+            new HostLocation(deviceId(DEV1), portNumber(INPORT2), 0L);
+    private static final DefaultHost HOST3 =
+            new DefaultHost(PROVIDER_ID, hostId(MAC3), MAC3,
+                    VLAN, LOCATION3,
+                    ImmutableSet.of(IP_ADDRESS3));
 
     private static final ComponentContextAdapter CTX_FOR_REMOVE =
             new ComponentContextAdapter() {
@@ -234,12 +254,12 @@
         Device device = new DefaultDevice(ProviderId.NONE, deviceId(DEV1), SWITCH,
                                           "m", "h", "s", "n", new ChassisId(0L));
         deviceService.listener.event(new DeviceEvent(DEVICE_REMOVED, device));
-        assertEquals("incorrect remove count", 1, providerService.removeCount);
+        assertEquals("incorrect remove count", 2, providerService.removeCount);
 
         device = new DefaultDevice(ProviderId.NONE, deviceId(DEV4), SWITCH,
                                           "m", "h", "s", "n", new ChassisId(0L));
         deviceService.listener.event(new DeviceEvent(DEVICE_REMOVED, device));
-        assertEquals("incorrect remove count", 2, providerService.removeCount);
+        assertEquals("incorrect remove count", 3, providerService.removeCount);
     }
 
     @Test
@@ -251,12 +271,12 @@
         Device device = new DefaultDevice(ProviderId.NONE, deviceId(DEV1), SWITCH,
                                           "m", "h", "s", "n", new ChassisId(0L));
         deviceService.listener.event(new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device));
-        assertEquals("incorrect remove count", 1, providerService.removeCount);
+        assertEquals("incorrect remove count", 2, providerService.removeCount);
 
         device = new DefaultDevice(ProviderId.NONE, deviceId(DEV4), SWITCH,
                                           "m", "h", "s", "n", new ChassisId(0L));
         deviceService.listener.event(new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device));
-        assertEquals("incorrect remove count", 2, providerService.removeCount);
+        assertEquals("incorrect remove count", 3, providerService.removeCount);
     }
 
     @Test
@@ -309,6 +329,43 @@
     }
 
     /**
+     * When receiving DHCP REQUEST, update MAC, location of client.
+     * When receiving DHCP ACK, update MAC, location of server and IP of client.
+     */
+    @Test
+    public void testReceiveDhcp() {
+        // DHCP Request
+        testProcessor.process(new TestDhcpRequestPacketContext(DEV1));
+        assertThat("testReceiveDhcpRequest. One host description expected",
+                providerService.descriptions.size(), is(1));
+        // Should learn the MAC and location of DHCP client
+        HostDescription descr = providerService.descriptions.get(0);
+        assertThat(descr.location(), is(LOCATION));
+        assertThat(descr.hwAddress(), is(MAC));
+        assertThat(descr.ipAddress().size(), is(0));
+        assertThat(descr.vlan(), is(VLAN));
+
+        // DHCP Ack
+        testProcessor.process(new TestDhcpAckPacketContext(DEV1));
+        assertThat("testReceiveDhcpAck. 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));
+
+    }
+
+    /**
      * When receiving NeighborAdvertisement, updates location and IP.
      */
     @Test
@@ -533,7 +590,7 @@
     }
 
     /**
-     * Generates IPv6 Unicast packet.
+     * Generates IPv4 Unicast packet.
      */
     private class TestIpv4PacketContext implements PacketContext {
         private final String deviceId;
@@ -589,6 +646,152 @@
             return false;
         }
     }
+    /**
+     * Generates DHCP REQUEST packet.
+     */
+    private class TestDhcpRequestPacketContext implements PacketContext {
+        private final String deviceId;
+
+        public TestDhcpRequestPacketContext(String deviceId) {
+            this.deviceId = deviceId;
+        }
+
+        @Override
+        public long time() {
+            return 0;
+        }
+
+        @Override
+        public InboundPacket inPacket() {
+            byte[] dhcpMsgType = new byte[1];
+            dhcpMsgType[0] = (byte) DHCPPacketType.DHCPREQUEST.getValue();
+
+            DHCPOption dhcpOption = new DHCPOption();
+            dhcpOption.setCode(DHCP.DHCPOptionCode.OptionCode_MessageType.getValue());
+            dhcpOption.setData(dhcpMsgType);
+            dhcpOption.setLength((byte) 1);
+            DHCP dhcp = new DHCP();
+            dhcp.setOptions(Collections.singletonList(dhcpOption));
+            dhcp.setClientHardwareAddress(MAC.toBytes());
+            UDP udp = new UDP();
+            udp.setPayload(dhcp);
+            udp.setSourcePort(UDP.DHCP_CLIENT_PORT);
+            udp.setDestinationPort(UDP.DHCP_SERVER_PORT);
+            IPv4 ipv4 = new IPv4();
+            ipv4.setPayload(udp);
+            ipv4.setDestinationAddress(IP_ADDRESS3.toString());
+            ipv4.setSourceAddress(IP_ADDRESS.toString());
+            Ethernet eth = new Ethernet();
+            eth.setEtherType(Ethernet.TYPE_IPV4)
+                    .setVlanID(VLAN.toShort())
+                    .setSourceMACAddress(MAC)
+                    .setDestinationMACAddress(MAC3)
+                    .setPayload(ipv4);
+            ConnectPoint receivedFrom = new ConnectPoint(deviceId(deviceId),
+                    portNumber(INPORT));
+            return new DefaultInboundPacket(receivedFrom, eth,
+                    ByteBuffer.wrap(eth.serialize()));
+        }
+
+        @Override
+        public OutboundPacket outPacket() {
+            return null;
+        }
+
+        @Override
+        public TrafficTreatment.Builder treatmentBuilder() {
+            return null;
+        }
+
+        @Override
+        public void send() {
+
+        }
+
+        @Override
+        public boolean block() {
+            return false;
+        }
+
+        @Override
+        public boolean isHandled() {
+            return false;
+        }
+    }
+
+    /**
+     * Generates DHCP ACK packet.
+     */
+    private class TestDhcpAckPacketContext implements PacketContext {
+        private final String deviceId;
+
+        public TestDhcpAckPacketContext(String deviceId) {
+            this.deviceId = deviceId;
+        }
+
+        @Override
+        public long time() {
+            return 0;
+        }
+
+        @Override
+        public InboundPacket inPacket() {
+            byte[] dhcpMsgType = new byte[1];
+            dhcpMsgType[0] = (byte) DHCPPacketType.DHCPACK.getValue();
+
+            DHCPOption dhcpOption = new DHCPOption();
+            dhcpOption.setCode(DHCP.DHCPOptionCode.OptionCode_MessageType.getValue());
+            dhcpOption.setData(dhcpMsgType);
+            dhcpOption.setLength((byte) 1);
+            DHCP dhcp = new DHCP();
+            dhcp.setOptions(Collections.singletonList(dhcpOption));
+            dhcp.setClientHardwareAddress(MAC.toBytes());
+            dhcp.setYourIPAddress(IP_ADDRESS.getIp4Address().toInt());
+            UDP udp = new UDP();
+            udp.setPayload(dhcp);
+            udp.setSourcePort(UDP.DHCP_SERVER_PORT);
+            udp.setDestinationPort(UDP.DHCP_CLIENT_PORT);
+            IPv4 ipv4 = new IPv4();
+            ipv4.setPayload(udp);
+            ipv4.setDestinationAddress(IP_ADDRESS.toString());
+            ipv4.setSourceAddress(IP_ADDRESS3.toString());
+            Ethernet eth = new Ethernet();
+            eth.setEtherType(Ethernet.TYPE_IPV4)
+                    .setVlanID(VLAN.toShort())
+                    .setSourceMACAddress(MAC3)
+                    .setDestinationMACAddress(MAC)
+                    .setPayload(ipv4);
+            ConnectPoint receivedFrom = new ConnectPoint(deviceId(deviceId),
+                    portNumber(INPORT2));
+            return new DefaultInboundPacket(receivedFrom, eth,
+                    ByteBuffer.wrap(eth.serialize()));
+        }
+
+        @Override
+        public OutboundPacket outPacket() {
+            return null;
+        }
+
+        @Override
+        public TrafficTreatment.Builder treatmentBuilder() {
+            return null;
+        }
+
+        @Override
+        public void send() {
+
+        }
+
+        @Override
+        public boolean block() {
+            return false;
+        }
+
+        @Override
+        public boolean isHandled() {
+            return false;
+        }
+    }
 
     /**
      * Generates NeighborAdvertisement packet.
@@ -1035,10 +1238,13 @@
         public Set<Host> getConnectedHosts(ConnectPoint connectPoint) {
             ConnectPoint cp1 = new ConnectPoint(deviceId(DEV1), portNumber(INPORT));
             ConnectPoint cp2 = new ConnectPoint(deviceId(DEV4), portNumber(INPORT));
+            ConnectPoint cp3 = new ConnectPoint(deviceId(DEV1), portNumber(INPORT2));
             if (connectPoint.equals(cp1)) {
                 return ImmutableSet.of(HOST);
             } else if (connectPoint.equals(cp2)) {
                 return ImmutableSet.of(HOST2);
+            } else if (connectPoint.equals(cp3)) {
+                return ImmutableSet.of(HOST3);
             } else {
                 return ImmutableSet.of();
             }
@@ -1047,7 +1253,7 @@
         @Override
         public Set<Host> getConnectedHosts(DeviceId deviceId) {
             if (deviceId.equals(deviceId(DEV1))) {
-                return ImmutableSet.of(HOST);
+                return ImmutableSet.of(HOST, HOST3);
             } else if (deviceId.equals(deviceId(DEV4))) {
                 return ImmutableSet.of(HOST2);
             } else {
@@ -1055,5 +1261,17 @@
             }
         }
 
+        @Override
+        public Host getHost(HostId hostId) {
+            if (hostId.equals(HostId.hostId(MAC, VLAN))) {
+                return HOST;
+            } else if (hostId.equals(HostId.hostId(MAC2, VLAN))) {
+                return HOST2;
+            } else if (hostId.equals(HostId.hostId(MAC3, VLAN))) {
+                return HOST3;
+            }
+            return null;
+        }
+
     }
 }