VOL-3166 IgmpProxy should support "double-tagging" of ongoing IGMP messages.
Also, priority bits of IGMP query messages that are sent to UNI ports by
IgmpProxy should be configurable separately.

Change-Id: Ia125c43515d234134b81f1039cf2f1f170d47161
diff --git a/app/src/main/java/org/opencord/igmpproxy/impl/IgmpManager.java b/app/src/main/java/org/opencord/igmpproxy/impl/IgmpManager.java
index 97e97cc..edd23f6 100644
--- a/app/src/main/java/org/opencord/igmpproxy/impl/IgmpManager.java
+++ b/app/src/main/java/org/opencord/igmpproxy/impl/IgmpManager.java
@@ -129,7 +129,9 @@
     private static boolean withRADownlink = false;
     private static boolean periodicQuery = true;
     private static short mvlan = 4000;
+    private static short mvlanInner = VlanId.NONE.toShort();
     private static byte igmpCos = 7;
+    private static byte igmpUniCos = 7;
     public static boolean connectPointMode = true;
     public static ConnectPoint connectPoint = null;
     private static ConnectPoint sourceDeviceAndPort = null;
@@ -257,6 +259,8 @@
         if (config != null) {
             mvlan = config.egressVlan().toShort();
             IgmpSender.getInstance().setMvlan(mvlan);
+            mvlanInner = config.egressInnerVlan().toShort();
+            IgmpSender.getInstance().setMvlanInner(mvlanInner);
         }
         deviceService.addListener(deviceListener);
         scheduledExecutorService.scheduleAtFixedRate(new IgmpProxyTimerTask(), 1000, 1000, TimeUnit.MILLISECONDS);
@@ -487,10 +491,14 @@
         Ethernet ethpkt;
         Ip4Address srcIp = getDeviceIp(groupMember.getDeviceId());
         if (groupMember.getv2()) {
-            ethpkt = IgmpSender.getInstance().buildIgmpV2Query(groupMember.getGroupIp(), srcIp);
+            ethpkt = IgmpSender.getInstance().buildIgmpV2Query(groupMember.getGroupIp(),
+                    srcIp, groupMember.getvlan().toShort());
         } else {
-            ethpkt = IgmpSender.getInstance().buildIgmpV3Query(groupMember.getGroupIp(), srcIp);
+            ethpkt = IgmpSender.getInstance().buildIgmpV3Query(groupMember.getGroupIp(),
+                    srcIp, groupMember.getvlan().toShort());
         }
+        log.debug("Sending IGMP query to {}/{} for group {}: {}",
+                groupMember.getDeviceId(), groupMember.getPortNumber(), groupMember.getGroupIp(), ethpkt);
         IgmpSender.getInstance().sendIgmpPacket(ethpkt, groupMember.getDeviceId(), groupMember.getPortNumber());
     }
 
@@ -895,6 +903,7 @@
             withRAUplink = newCfg.withRAUplink();
             withRADownlink = newCfg.withRADownlink();
             igmpCos = newCfg.igmpCos();
+            igmpUniCos = newCfg.igmpUniCos(); // defines priority bit of IGMP query message sent to UNI ports
             periodicQuery = newCfg.periodicQuery();
             fastLeave = newCfg.fastLeave();
             pimSSmInterworking = newCfg.pimSsmInterworking();
@@ -925,8 +934,10 @@
             getSourceConnectPoint(newCfg);
 
             IgmpSender.getInstance().setIgmpCos(igmpCos);
+            IgmpSender.getInstance().setIgmpUniCos(igmpUniCos);
             IgmpSender.getInstance().setMaxResp(maxResp);
             IgmpSender.getInstance().setMvlan(mvlan);
+            IgmpSender.getInstance().setMvlanInner(mvlanInner);
             IgmpSender.getInstance().setWithRADownlink(withRADownlink);
             IgmpSender.getInstance().setWithRAUplink(withRAUplink);
         }
@@ -972,10 +983,22 @@
 
                     if (event.configClass().equals(MCAST_CONFIG_CLASS)) {
                         McastConfig config = networkConfig.getConfig(coreAppId, MCAST_CONFIG_CLASS);
-                        if (config != null && mvlan != config.egressVlan().toShort()) {
-                            mvlan = config.egressVlan().toShort();
-                            IgmpSender.getInstance().setMvlan(mvlan);
+                        boolean vlanConfigChanged = config != null && mvlan != config.egressVlan().toShort();
+                        boolean innerVlanConfigChanged = config != null &&
+                                mvlanInner != config.egressInnerVlan().toShort();
+
+                        if (vlanConfigChanged || innerVlanConfigChanged) {
+                            log.info("igmpproxy vlan config received. {}", config);
+                            //at least one of the vlan configs has changed. Call leave before setting new values
                             groupMemberStore.getAllGroupMembers().forEach(m -> leaveAction(m));
+                            if (vlanConfigChanged) {
+                                mvlan = config.egressVlan().toShort();
+                                IgmpSender.getInstance().setMvlan(mvlan);
+                            }
+                            if (innerVlanConfigChanged) {
+                                mvlanInner = config.egressInnerVlan().toShort();
+                                IgmpSender.getInstance().setMvlanInner(mvlanInner);
+                            }
                         }
                     }
 
diff --git a/app/src/main/java/org/opencord/igmpproxy/impl/IgmpSender.java b/app/src/main/java/org/opencord/igmpproxy/impl/IgmpSender.java
index adf0e9e..e6584ad 100644
--- a/app/src/main/java/org/opencord/igmpproxy/impl/IgmpSender.java
+++ b/app/src/main/java/org/opencord/igmpproxy/impl/IgmpSender.java
@@ -24,6 +24,7 @@
 import org.onlab.packet.IPv4;
 import org.onlab.packet.Ip4Address;
 import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.flow.DefaultTrafficTreatment;
@@ -57,7 +58,9 @@
     private boolean withRAUplink = true;
     private boolean withRADownlink = false;
     private short mvlan = DEFAULT_MVLAN;
+    private short mvlanInner = VlanId.NONE.toShort();
     private byte igmpCos = DEFAULT_COS;
+    private byte igmpUniCos = DEFAULT_COS;
     private int maxResp = DEFAULT_MEX_RESP;
     private Logger log = LoggerFactory.getLogger(getClass());
 
@@ -89,9 +92,16 @@
         this.mvlan = mvlan;
     }
 
+    public void setMvlanInner(short mvlanInner) {
+        this.mvlanInner = mvlanInner;
+    }
+
     public void setIgmpCos(byte igmpCos) {
         this.igmpCos = igmpCos;
     }
+    public void setIgmpUniCos(byte igmpUniCos) {
+        this.igmpUniCos = igmpUniCos;
+    }
 
     public void setMaxResp(int maxResp) {
         this.maxResp = maxResp;
@@ -101,12 +111,14 @@
         IGMPMembership igmpMembership = new IGMPMembership(groupIp);
         igmpMembership.setRecordType(IGMPMembership.CHANGE_TO_EXCLUDE_MODE);
 
-        return buildIgmpPacket(IGMP.TYPE_IGMPV3_MEMBERSHIP_REPORT, groupIp, igmpMembership, sourceIp, false);
+        return buildIgmpPacket(IGMP.TYPE_IGMPV3_MEMBERSHIP_REPORT, groupIp, igmpMembership,
+                sourceIp, false, mvlan, mvlanInner, igmpCos);
     }
 
     public Ethernet buildIgmpV2Join(Ip4Address groupIp, Ip4Address sourceIp) {
         IGMPMembership igmpMembership = new IGMPMembership(groupIp);
-        return buildIgmpPacket(IGMP.TYPE_IGMPV2_MEMBERSHIP_REPORT, groupIp, igmpMembership, sourceIp, true);
+        return buildIgmpPacket(IGMP.TYPE_IGMPV2_MEMBERSHIP_REPORT, groupIp, igmpMembership,
+                sourceIp, true, mvlan, mvlanInner, igmpCos);
     }
 
     public Ethernet buildIgmpV2ResponseQuery(Ip4Address groupIp, Ip4Address sourceIp) {
@@ -117,31 +129,37 @@
         IGMPMembership igmpMembership = new IGMPMembership(groupIp);
         igmpMembership.setRecordType(IGMPMembership.MODE_IS_EXCLUDE);
 
-        return buildIgmpPacket(IGMP.TYPE_IGMPV3_MEMBERSHIP_REPORT, groupIp, igmpMembership, sourceIp, false);
+        return buildIgmpPacket(IGMP.TYPE_IGMPV3_MEMBERSHIP_REPORT, groupIp, igmpMembership,
+                sourceIp, false, mvlan, mvlanInner, igmpCos);
     }
 
     public Ethernet buildIgmpV3Leave(Ip4Address groupIp, Ip4Address sourceIp) {
         IGMPMembership igmpMembership = new IGMPMembership(groupIp);
         igmpMembership.setRecordType(IGMPMembership.CHANGE_TO_INCLUDE_MODE);
 
-        return buildIgmpPacket(IGMP.TYPE_IGMPV3_MEMBERSHIP_REPORT, groupIp, igmpMembership, sourceIp, false);
+        return buildIgmpPacket(IGMP.TYPE_IGMPV3_MEMBERSHIP_REPORT, groupIp, igmpMembership,
+                sourceIp, false, mvlan, mvlanInner, igmpCos);
     }
 
     public Ethernet buildIgmpV2Leave(Ip4Address groupIp, Ip4Address sourceIp) {
         IGMPMembership igmpMembership = new IGMPMembership(groupIp);
-        return buildIgmpPacket(IGMP.TYPE_IGMPV2_LEAVE_GROUP, groupIp, igmpMembership, sourceIp, true);
+        return buildIgmpPacket(IGMP.TYPE_IGMPV2_LEAVE_GROUP, groupIp, igmpMembership,
+                sourceIp, true, mvlan, mvlanInner, igmpCos);
     }
 
-    public Ethernet buildIgmpV2Query(Ip4Address groupIp, Ip4Address sourceIp) {
-        return buildIgmpPacket(IGMP.TYPE_IGMPV3_MEMBERSHIP_QUERY, groupIp, null, sourceIp, true);
+    public Ethernet buildIgmpV2Query(Ip4Address groupIp, Ip4Address sourceIp, short vlan) {
+        return buildIgmpPacket(IGMP.TYPE_IGMPV3_MEMBERSHIP_QUERY, groupIp, null,
+                sourceIp, true, vlan, VlanId.NONE.toShort(), igmpUniCos);
     }
 
-    public Ethernet buildIgmpV3Query(Ip4Address groupIp, Ip4Address sourceIp) {
-        return buildIgmpPacket(IGMP.TYPE_IGMPV3_MEMBERSHIP_QUERY, groupIp, null, sourceIp, false);
+    public Ethernet buildIgmpV3Query(Ip4Address groupIp, Ip4Address sourceIp, short vlan) {
+        return buildIgmpPacket(IGMP.TYPE_IGMPV3_MEMBERSHIP_QUERY, groupIp, null,
+                sourceIp, false, vlan, VlanId.NONE.toShort(), igmpUniCos);
     }
 
     protected Ethernet buildIgmpPacket(byte type, Ip4Address groupIp, IGMPMembership igmpMembership,
-                                     Ip4Address sourceIp, boolean isV2Query) {
+                                     Ip4Address sourceIp, boolean isV2Query, short vlan,
+                                       short innerVlan, byte igmpCos) {
 
         IGMP igmpPacket;
         if (isV2Query) {
@@ -210,9 +228,16 @@
         ethPkt.setSourceMACAddress(MAC_ADDRESS);
         ethPkt.setEtherType(Ethernet.TYPE_IPV4);
         ethPkt.setPayload(ip4Packet);
-        ethPkt.setVlanID(mvlan);
+        ethPkt.setVlanID(vlan);
         ethPkt.setPriorityCode(igmpCos);
 
+        if (innerVlan != VlanId.NONE.toShort()) {
+            ethPkt.setQinQTPID(Ethernet.TYPE_VLAN);
+            ethPkt.setQinQVID(vlan);
+            ethPkt.setVlanID(innerVlan);
+            ethPkt.setQinQPriorityCode(igmpCos);
+        }
+
         return ethPkt;
     }
 
diff --git a/app/src/main/java/org/opencord/igmpproxy/impl/IgmpproxyConfig.java b/app/src/main/java/org/opencord/igmpproxy/impl/IgmpproxyConfig.java
index 95beea8..6d8591a 100644
--- a/app/src/main/java/org/opencord/igmpproxy/impl/IgmpproxyConfig.java
+++ b/app/src/main/java/org/opencord/igmpproxy/impl/IgmpproxyConfig.java
@@ -31,6 +31,7 @@
     protected static final String DEFAULT_LAST_QUERY_INTERVAL = "2";
     protected static final String DEFAULT_LAST_QUERY_COUNT = "2";
     protected static final String DEFAULT_IGMP_COS = "7";
+    protected static final String DEFAULT_UNI_IGMP_COS = "7";
     protected static final Boolean DEFAULT_FAST_LEAVE = false;
     protected static final Boolean DEFAULT_PERIODIC_QUERY = true;
     protected static final String DEFAULT_WITH_RA_UPLINK = "true";
@@ -52,6 +53,7 @@
     private static final String FAST_LEAVE = "FastLeave";
     private static final String PERIODIC_QUERY = "PeriodicQuery";
     private static final String IGMP_COS = "IgmpCos";
+    private static final String IGMP_UNI_COS = "IgmpUniCos";
     private static final String WITH_RA_UPLINK = "withRAUpLink";
     private static final String WITH_RA_DOWN_LINK = "withRADownLink";
     private static final String PIMSSM_INTERWORKING = "pimSSmInterworking";
@@ -149,6 +151,10 @@
         return Byte.parseByte(getStringProperty(IGMP_COS, DEFAULT_IGMP_COS));
     }
 
+    public byte igmpUniCos() {
+        return Byte.parseByte(getStringProperty(IGMP_UNI_COS, DEFAULT_UNI_IGMP_COS));
+    }
+
     public boolean withRAUplink() {
         if (object == null || object.path(WITH_RA_UPLINK) == null) {
             return true;
diff --git a/app/src/test/java/org/opencord/igmpproxy/impl/IgmpManagerBase.java b/app/src/test/java/org/opencord/igmpproxy/impl/IgmpManagerBase.java
index 6283c08..a33ab15 100644
--- a/app/src/test/java/org/opencord/igmpproxy/impl/IgmpManagerBase.java
+++ b/app/src/test/java/org/opencord/igmpproxy/impl/IgmpManagerBase.java
@@ -23,6 +23,7 @@
 import org.onlab.packet.Ip4Address;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
 import org.onosproject.cfg.ComponentConfigService;
 import org.onosproject.cfg.ConfigProperty;
 import org.onosproject.core.ApplicationId;
@@ -686,14 +687,15 @@
         igmpMembership.setRecordType((byte) 0x33);
 
         return IgmpSender.getInstance().buildIgmpPacket(IGMP.TYPE_IGMPV3_MEMBERSHIP_REPORT, groupIp,
-                igmpMembership, sourceIp, false);
+                igmpMembership, sourceIp, false, VlanId.ANY_VALUE, VlanId.NO_VID, IgmpSender.DEFAULT_COS);
     }
 
     Ethernet buildUnknownIgmpPacket(Ip4Address groupIp, Ip4Address sourceIp) {
         IGMPMembership igmpMembership = new IGMPMembership(groupIp);
         igmpMembership.setRecordType((byte) 0x33);
 
-        return IgmpSender.getInstance().buildIgmpPacket((byte) 0x44, groupIp, igmpMembership, sourceIp, false);
+        return IgmpSender.getInstance().buildIgmpPacket((byte) 0x44, groupIp, igmpMembership, sourceIp, false,
+                VlanId.ANY_VALUE, VlanId.NO_VID, IgmpSender.DEFAULT_COS);
     }
 
     class TestStateMachineStoreService extends AbstractStateMachineStore {
diff --git a/app/src/test/java/org/opencord/igmpproxy/impl/IgmpStatisticsTest.java b/app/src/test/java/org/opencord/igmpproxy/impl/IgmpStatisticsTest.java
index b2e8271..4421bde 100644
--- a/app/src/test/java/org/opencord/igmpproxy/impl/IgmpStatisticsTest.java
+++ b/app/src/test/java/org/opencord/igmpproxy/impl/IgmpStatisticsTest.java
@@ -28,6 +28,7 @@
 import org.onlab.junit.TestUtils;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.Ip4Address;
+import org.onlab.packet.VlanId;
 import org.onosproject.core.CoreServiceAdapter;
 import org.onosproject.net.flow.FlowRuleServiceAdapter;
 import org.onosproject.net.flowobjective.FlowObjectiveServiceAdapter;
@@ -147,12 +148,13 @@
 
         flagForQueryPacket = true;
         //IGMPV3 Group Specific Membership Query packet
-        Ethernet igmpv3MembershipQueryPkt = IgmpSender.getInstance().buildIgmpV3Query(GROUP_IP, SOURCE_IP_OF_A);
+        Ethernet igmpv3MembershipQueryPkt = IgmpSender.getInstance().
+                buildIgmpV3Query(GROUP_IP, SOURCE_IP_OF_A, VlanId.MAX_VLAN);
         sendPacket(igmpv3MembershipQueryPkt);
 
         //IGMPV3 General Membership Query packet
         Ethernet igmpv3MembershipQueryPkt1 =
-                IgmpSender.getInstance().buildIgmpV3Query(Ip4Address.valueOf(0), SOURCE_IP_OF_A);
+                IgmpSender.getInstance().buildIgmpV3Query(Ip4Address.valueOf(0), SOURCE_IP_OF_A, VlanId.MAX_VLAN);
         sendPacket(igmpv3MembershipQueryPkt1);
         assertAfter(WAIT_TIMEOUT, WAIT_TIMEOUT * 2, () ->
                 assertEquals(igmpStatisticsManager.getIgmpStats()