Support double-tagged host

Change-Id: Ie4041a0b5159e7a8b3a9ed82b55ce3c26b520a3b
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 0633d12..45ed92d 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
@@ -28,6 +28,7 @@
 import org.onlab.packet.BasePacket;
 import org.onlab.packet.DHCP;
 import org.onlab.packet.DHCP6;
+import org.onlab.packet.EthType;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.ICMP6;
 import org.onlab.packet.IPacket;
@@ -441,15 +442,17 @@
          * Create or update host information.
          * Will not update IP if IP is null, all zero or self-assigned.
          *
-         * @param hid  host ID
-         * @param mac  source Mac address
-         * @param vlan VLAN ID
-         * @param hloc host location
-         * @param ip   source IP address or null if not updating
+         * @param hid       host ID
+         * @param mac       source Mac address
+         * @param vlan      VLAN ID
+         * @param innerVlan inner VLAN ID
+         * @param outerTpid outer TPID
+         * @param hloc      host location
+         * @param ip        source IP address or null if not updating
          */
-        private void createOrUpdateHost(HostId hid, MacAddress mac,
-                                        VlanId vlan, HostLocation hloc,
-                                        IpAddress ip) {
+        private void createOrUpdateHost(HostId hid, MacAddress mac, VlanId vlan,
+                                        VlanId innerVlan, EthType outerTpid,
+                                        HostLocation hloc, IpAddress ip) {
             Set<HostLocation> newLocations = Sets.newHashSet(hloc);
 
             if (multihomingEnabled) {
@@ -473,8 +476,10 @@
             }
 
             HostDescription desc = ip == null || ip.isZero() || ip.isSelfAssigned() ?
-                    new DefaultHostDescription(mac, vlan, newLocations, Sets.newHashSet(), false) :
-                    new DefaultHostDescription(mac, vlan, newLocations, Sets.newHashSet(ip), false);
+                    new DefaultHostDescription(mac, vlan, newLocations, Sets.newHashSet(),
+                                               innerVlan, outerTpid, false) :
+                    new DefaultHostDescription(mac, vlan, newLocations, Sets.newHashSet(ip),
+                                               innerVlan, outerTpid, false);
             try {
                 providerService.hostDetected(hid, desc, false);
             } catch (IllegalStateException e) {
@@ -521,6 +526,15 @@
             }
 
             VlanId vlan = VlanId.vlanId(eth.getVlanID());
+            VlanId outerVlan = VlanId.vlanId(eth.getQinQVID());
+            VlanId innerVlan = VlanId.NONE;
+            EthType outerTpid = EthType.EtherType.UNKNOWN.ethType();
+            // Set up values for double-tagged hosts
+            if (outerVlan.toShort() != Ethernet.VLAN_UNTAGGED) {
+                innerVlan = vlan;
+                vlan = outerVlan;
+                outerTpid = EthType.EtherType.lookup(eth.getQinQTPID()).ethType();
+            }
             ConnectPoint heardOn = context.inPacket().receivedFrom();
 
             // If this arrived on control port, bail out.
@@ -550,12 +564,12 @@
                 ARP arp = (ARP) eth.getPayload();
                 IpAddress ip = IpAddress.valueOf(IpAddress.Version.INET,
                                                  arp.getSenderProtocolAddress());
-                createOrUpdateHost(hid, srcMac, vlan, hloc, ip);
+                createOrUpdateHost(hid, srcMac, vlan, innerVlan, outerTpid, hloc, ip);
 
             // IPv4: update location only
             } else if (eth.getEtherType() == Ethernet.TYPE_IPV4) {
                 // Update host location
-                createOrUpdateHost(hid, srcMac, vlan, hloc, null);
+                createOrUpdateHost(hid, srcMac, vlan, innerVlan, outerTpid, hloc, null);
                 if (useDhcp) {
                     DHCP dhcp = findDhcp(eth).orElse(null);
                     // DHCP ACK: additionally update IP of DHCP client
@@ -586,7 +600,7 @@
                 // DHCPv6 protocol
                 DHCP6 dhcp6 = findDhcp6(pkt).orElse(null);
                 if (dhcp6 != null && useDhcp6) {
-                    createOrUpdateHost(hid, srcMac, vlan, hloc, null);
+                    createOrUpdateHost(hid, srcMac, vlan, innerVlan, outerTpid, hloc, null);
                     handleDhcp6(dhcp6, vlan);
                     return;
                 }
@@ -605,13 +619,13 @@
                                 return;
                             }
                             // NeighborSolicitation, NeighborAdvertisement
-                            createOrUpdateHost(hid, srcMac, vlan, hloc, ip);
+                            createOrUpdateHost(hid, srcMac, vlan, innerVlan, outerTpid, hloc, ip);
 
                             // Also learn from the target address of NeighborAdvertisement
                             if (pkt instanceof NeighborAdvertisement) {
                                 NeighborAdvertisement na = (NeighborAdvertisement) pkt;
                                 Ip6Address targetAddr = Ip6Address.valueOf(na.getTargetAddress());
-                                createOrUpdateHost(hid, srcMac, vlan, hloc, targetAddr);
+                                createOrUpdateHost(hid, srcMac, vlan, innerVlan, outerTpid, hloc, targetAddr);
                             }
                             return;
                         }
@@ -624,7 +638,7 @@
                 }
 
                 // normal IPv6 packets
-                createOrUpdateHost(hid, srcMac, vlan, hloc, null);
+                createOrUpdateHost(hid, srcMac, vlan, innerVlan, outerTpid, hloc, null);
             }
         }
 
diff --git a/providers/netcfghost/src/main/java/org/onosproject/provider/netcfghost/NetworkConfigHostProvider.java b/providers/netcfghost/src/main/java/org/onosproject/provider/netcfghost/NetworkConfigHostProvider.java
index 42278d3..a0be2d9 100644
--- a/providers/netcfghost/src/main/java/org/onosproject/provider/netcfghost/NetworkConfigHostProvider.java
+++ b/providers/netcfghost/src/main/java/org/onosproject/provider/netcfghost/NetworkConfigHostProvider.java
@@ -21,6 +21,7 @@
 import org.apache.felix.scr.annotations.Deactivate;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.EthType;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
@@ -120,6 +121,28 @@
     }
 
     /**
+     * Adds host information.
+     * IP information will be appended if host exists.
+     *
+     * @param mac       MAC address of the host
+     * @param vlan      VLAN ID of the host
+     * @param locations Location of the host
+     * @param ips       Set of IP addresses of the host
+     * @param innerVlan host inner VLAN identifier
+     * @param outerTpid outer TPID of a host
+     */
+    protected void addHost(MacAddress mac, VlanId vlan, Set<HostLocation> locations, Set<IpAddress> ips,
+                           VlanId innerVlan, EthType outerTpid) {
+        HostId hid = HostId.hostId(mac, vlan);
+        HostDescription desc = (ips != null) ?
+                new DefaultHostDescription(mac, vlan, locations, ips,
+                                           innerVlan, outerTpid, true) :
+                new DefaultHostDescription(mac, vlan, locations, Collections.emptySet(),
+                                           innerVlan, outerTpid, true);
+        providerService.hostDetected(hid, desc, true);
+    }
+
+    /**
      * Updates host information.
      * IP information will be replaced if host exists.
      *
@@ -135,6 +158,25 @@
     }
 
     /**
+     * Updates host information.
+     * IP information will be replaced if host exists.
+     *
+     * @param mac       MAC address of the host
+     * @param vlan      VLAN ID of the host
+     * @param locations Location of the host
+     * @param ips       Set of IP addresses of the host
+     * @param innerVlan host inner VLAN identifier
+     * @param outerTpid outer TPID of a host
+     */
+    protected void updateHost(MacAddress mac, VlanId vlan, Set<HostLocation> locations, Set<IpAddress> ips,
+                              VlanId innerVlan, EthType outerTpid) {
+        HostId hid = HostId.hostId(mac, vlan);
+        HostDescription desc = new DefaultHostDescription(mac, vlan, locations, ips,
+                                                          innerVlan, outerTpid, true);
+        providerService.hostDetected(hid, desc, true);
+    }
+
+    /**
      * Removes host information.
      *
      * @param mac MAC address of the host
@@ -155,7 +197,9 @@
             Set<HostLocation> locations = hostConfig.locations().stream()
                     .map(hostLocation -> new HostLocation(hostLocation, System.currentTimeMillis()))
                     .collect(Collectors.toSet());
-            addHost(mac, vlan, locations, ipAddresses);
+            VlanId innerVlan = hostConfig.innerVlan();
+            EthType outerTpid = hostConfig.outerTpid();
+            addHost(mac, vlan, locations, ipAddresses, innerVlan, outerTpid);
         });
     }
 
@@ -175,6 +219,8 @@
             BasicHostConfig hostConfig = networkConfigRegistry.getConfig(hostId, BasicHostConfig.class);
             Set<IpAddress> ipAddresses = null;
             Set<HostLocation> locations = null;
+            VlanId innerVlan = VlanId.NONE;
+            EthType outerTpid = EthType.EtherType.UNKNOWN.ethType();
 
             // Note: There will be no config presented in the CONFIG_REMOVE case
             if (hostConfig != null) {
@@ -187,14 +233,16 @@
                 locations = locations.stream()
                         .map(hostLocation -> new HostLocation(hostLocation, System.currentTimeMillis()))
                         .collect(Collectors.toSet());
+                innerVlan = hostConfig.innerVlan();
+                outerTpid = hostConfig.outerTpid();
             }
 
             switch (event.type()) {
                 case CONFIG_ADDED:
-                    addHost(mac, vlan, locations, ipAddresses);
+                    addHost(mac, vlan, locations, ipAddresses, innerVlan, outerTpid);
                     break;
                 case CONFIG_UPDATED:
-                    updateHost(mac, vlan, locations, ipAddresses);
+                    updateHost(mac, vlan, locations, ipAddresses, innerVlan, outerTpid);
                     break;
                 case CONFIG_REMOVED:
                     removeHost(mac, vlan);
diff --git a/providers/netcfghost/src/test/java/org/onosproject/provider/netcfghost/NetworkConfigHostProviderTest.java b/providers/netcfghost/src/test/java/org/onosproject/provider/netcfghost/NetworkConfigHostProviderTest.java
index 36ad76d..36de211 100644
--- a/providers/netcfghost/src/test/java/org/onosproject/provider/netcfghost/NetworkConfigHostProviderTest.java
+++ b/providers/netcfghost/src/test/java/org/onosproject/provider/netcfghost/NetworkConfigHostProviderTest.java
@@ -19,6 +19,7 @@
 import com.google.common.collect.Sets;
 import org.junit.Before;
 import org.junit.Test;
+import org.onlab.packet.EthType;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
@@ -54,6 +55,8 @@
     private Set<IpAddress> ips = new HashSet<>();
     private HostId hostId = HostId.hostId(mac, vlan);
     private HostDescription hostDescription;
+    private VlanId innerVlan = VlanId.vlanId((short) 20);
+    private EthType outerTpid = EthType.EtherType.lookup((short) 0x88a8).ethType();
 
     @Before
     public void setUp() {
@@ -62,12 +65,13 @@
         // Initialize test variables
         ips.add(IpAddress.valueOf("10.0.0.1"));
         ips.add(IpAddress.valueOf("192.168.0.1"));
-        hostDescription = new DefaultHostDescription(mac, vlan, locations, ips, true);
+        hostDescription = new DefaultHostDescription(mac, vlan, locations, ips,
+                                                     innerVlan, outerTpid, true);
     }
 
     @Test
     public void testAddHost() throws Exception {
-        provider.addHost(mac, vlan, locations, ips);
+        provider.addHost(mac, vlan, locations, ips, innerVlan, outerTpid);
         assertThat(providerService.hostId, is(hostId));
         assertThat(providerService.hostDescription, is(hostDescription));
         assertThat(providerService.event, is("hostDetected"));
@@ -76,7 +80,7 @@
 
     @Test
     public void testUpdateHost() throws Exception {
-        provider.updateHost(mac, vlan, locations, ips);
+        provider.updateHost(mac, vlan, locations, ips, innerVlan, outerTpid);
         assertThat(providerService.hostId, is(hostId));
         assertThat(providerService.hostDescription, is(hostDescription));
         assertThat(providerService.event, is("hostDetected"));