VOL-2770 IGMPv2 should stay v2, not get converted to v3 for TT use-cases

"outgoingIgmpWithV3" config property added to igmpproxy config parameters.
When it is set to true, outgoing IGMP messages are converted to IGMPv3;
IGMPv2 otherwise.

Change-Id: I9f2f5c15d1073c91f7bd11677e3dd09896321e9e
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 11e28e5..b985b14 100644
--- a/app/src/main/java/org/opencord/igmpproxy/impl/IgmpManager.java
+++ b/app/src/main/java/org/opencord/igmpproxy/impl/IgmpManager.java
@@ -132,6 +132,7 @@
     private static ConnectPoint sourceDeviceAndPort = null;
     private static boolean enableIgmpProvisioning = false;
     private static boolean igmpOnPodBasis = false;
+    private static boolean outgoingIgmpWithV3 = true;
 
     private static final Integer MAX_PRIORITY = 10000;
     private static final String INSTALLED = "installed";
@@ -210,6 +211,10 @@
         return unSolicitedTimeout;
     }
 
+    public static boolean outgoingIgmpWithV3() {
+        return outgoingIgmpWithV3;
+    }
+
     protected BaseInformationService<SubscriberAndDeviceInformation> subsService;
 
     private List<Byte> validMembershipModes = Arrays.asList(MODE_IS_INCLUDE,  MODE_IS_EXCLUDE, CHANGE_TO_INCLUDE_MODE,
@@ -561,7 +566,7 @@
                             break;
 
                         default:
-                            log.warn("wrong IGMP v3 type:" + igmp.getIgmpType());
+                            log.warn("Unknown IGMP message type:" + igmp.getIgmpType());
                             igmpStatisticsManager.getIgmpStats().increaseInvalidIgmpMsgReceived();
                             igmpStatisticsManager.getIgmpStats().increaseUnknownIgmpTypePacketsRxCounter();
                             break;
@@ -870,6 +875,10 @@
             pimSSmInterworking = newCfg.pimSsmInterworking();
             enableIgmpProvisioning = newCfg.enableIgmpProvisioning();
             igmpOnPodBasis = newCfg.igmpOnPodBasis();
+            if (newCfg.outgoingIgmpWithV3() != null &&
+                    outgoingIgmpWithV3 != newCfg.outgoingIgmpWithV3()) {
+                outgoingIgmpWithV3 = newCfg.outgoingIgmpWithV3();
+            }
 
             if (connectPointMode != newCfg.connectPointMode() ||
                     connectPoint != newCfg.connectPoint()) {
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 a4cbaa5..ec958f4 100644
--- a/app/src/main/java/org/opencord/igmpproxy/impl/IgmpSender.java
+++ b/app/src/main/java/org/opencord/igmpproxy/impl/IgmpSender.java
@@ -43,6 +43,7 @@
  */
 public final class IgmpSender {
     static final String V3_REPORT_ADDRESS = "224.0.0.22";
+    static final String V2_LEAVE_DST = "224.0.0.2";
     static final String MAC_ADDRESS = "DE:AD:BE:EF:BA:11";
     static final short DEFAULT_MVLAN = 4000;
     static final byte DEFAULT_COS = 7;
@@ -103,6 +104,15 @@
         return buildIgmpPacket(IGMP.TYPE_IGMPV3_MEMBERSHIP_REPORT, groupIp, igmpMembership, sourceIp, false);
     }
 
+    public Ethernet buildIgmpV2Join(Ip4Address groupIp, Ip4Address sourceIp) {
+        IGMPMembership igmpMembership = new IGMPMembership(groupIp);
+        return buildIgmpPacket(IGMP.TYPE_IGMPV2_MEMBERSHIP_REPORT, groupIp, igmpMembership, sourceIp, true);
+    }
+
+    public Ethernet buildIgmpV2ResponseQuery(Ip4Address groupIp, Ip4Address sourceIp) {
+        return buildIgmpV2Join(groupIp, sourceIp);
+    }
+
     public Ethernet buildIgmpV3ResponseQuery(Ip4Address groupIp, Ip4Address sourceIp) {
         IGMPMembership igmpMembership = new IGMPMembership(groupIp);
         igmpMembership.setRecordType(IGMPMembership.MODE_IS_EXCLUDE);
@@ -117,6 +127,11 @@
         return buildIgmpPacket(IGMP.TYPE_IGMPV3_MEMBERSHIP_REPORT, groupIp, igmpMembership, sourceIp, false);
     }
 
+    public Ethernet buildIgmpV2Leave(Ip4Address groupIp, Ip4Address sourceIp) {
+        IGMPMembership igmpMembership = new IGMPMembership(groupIp);
+        return buildIgmpPacket(IGMP.TYPE_IGMPV2_LEAVE_GROUP, groupIp, igmpMembership, sourceIp, true);
+    }
+
     public Ethernet buildIgmpV2Query(Ip4Address groupIp, Ip4Address sourceIp) {
         return buildIgmpPacket(IGMP.TYPE_IGMPV3_MEMBERSHIP_QUERY, groupIp, null, sourceIp, true);
     }
@@ -157,11 +172,8 @@
                     return null;
                 }
                 igmpPacket.addGroup(igmpMembership);
-                if (type == IGMP.TYPE_IGMPV3_MEMBERSHIP_REPORT) {
-                    ip4Packet.setDestinationAddress(Ip4Address.valueOf(V3_REPORT_ADDRESS).toInt());
-                } else {
-                    ip4Packet.setDestinationAddress(groupIp.toInt());
-                }
+                ip4Packet.setDestinationAddress(Ip4Address.valueOf(V3_REPORT_ADDRESS).toInt());
+
                 if (withRAUplink) {
                     ip4Packet.setOptions(RA_BYTES);
                 }
@@ -169,7 +181,15 @@
 
             case IGMP.TYPE_IGMPV2_MEMBERSHIP_REPORT:
             case IGMP.TYPE_IGMPV2_LEAVE_GROUP:
-                return null;
+                if (igmpMembership == null) {
+                    return null;
+                }
+                igmpPacket.addGroup(igmpMembership);
+                int dst = (type == IGMP.TYPE_IGMPV2_MEMBERSHIP_REPORT ?
+                        groupIp.toInt() :
+                        Ip4Address.valueOf(V2_LEAVE_DST).toInt());
+                ip4Packet.setDestinationAddress(dst);
+                break;
             default:
                 igmpStatisticsService.getIgmpStats().increaseUnknownIgmpTypePacketsRxCounter();
                 return null;
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 e8297c1..95beea8 100644
--- a/app/src/main/java/org/opencord/igmpproxy/impl/IgmpproxyConfig.java
+++ b/app/src/main/java/org/opencord/igmpproxy/impl/IgmpproxyConfig.java
@@ -39,6 +39,7 @@
     private static final Boolean DEFAULT_PIMSSM_INTERWORKING = false;
     private static final Boolean DEFAULT_IGMP_PROVISIONING_SUPPORT = Boolean.FALSE;
     private static final Boolean DEFAULT_IGMP_ON_POD_BASIS = Boolean.FALSE;
+    private static final Boolean DEFAULT_OUTGOING_IGMP_WITH_V3 = Boolean.TRUE;
 
     protected static final String CONNECT_POINT_MODE = "globalConnectPointMode";
     protected static final String CONNECT_POINT = "globalConnectPoint";
@@ -57,6 +58,7 @@
     private static final String SOURCE_DEV_PORT = "sourceDeviceAndPort";
     private static final String ENABLE_IGMP_PROVISIONING = "enableIgmpProvisioning";
     private static final String IGMP_ON_POD_BASIS = "igmpOnPodBasis";
+    private static final String OUTGOING_IGMP_WITH_V3 = "outgoingIgmpWithV3";
 
     /**
      * Gets the value of a string property, protecting for an empty
@@ -214,4 +216,11 @@
         return Boolean.parseBoolean(getStringProperty(IGMP_ON_POD_BASIS,
                                                       DEFAULT_IGMP_ON_POD_BASIS.toString()));
     }
+
+    public Boolean outgoingIgmpWithV3() {
+        if (object == null || object.path(OUTGOING_IGMP_WITH_V3) == null) {
+            return null;
+        }
+        return Boolean.parseBoolean(getStringProperty(OUTGOING_IGMP_WITH_V3, DEFAULT_OUTGOING_IGMP_WITH_V3.toString()));
+    }
 }
diff --git a/app/src/main/java/org/opencord/igmpproxy/impl/SingleStateMachine.java b/app/src/main/java/org/opencord/igmpproxy/impl/SingleStateMachine.java
index c4d73a8..ca68bf6 100644
--- a/app/src/main/java/org/opencord/igmpproxy/impl/SingleStateMachine.java
+++ b/app/src/main/java/org/opencord/igmpproxy/impl/SingleStateMachine.java
@@ -139,7 +139,9 @@
 
         public void leave(boolean messageOutAllowed) {
             if (messageOutAllowed) {
-                Ethernet eth = IgmpSender.getInstance().buildIgmpV3Leave(groupIp, srcIp);
+                Ethernet eth = IgmpManager.outgoingIgmpWithV3() ?
+                        IgmpSender.getInstance().buildIgmpV3Leave(groupIp, srcIp) :
+                        IgmpSender.getInstance().buildIgmpV2Leave(groupIp, srcIp);
                 IgmpSender.getInstance().sendIgmpPacketUplink(eth, devId, upLinkPort);
             }
         }
@@ -155,7 +157,9 @@
     class NonMember extends State {
         public void join(boolean messageOutAllowed) {
             if (messageOutAllowed) {
-                Ethernet eth = IgmpSender.getInstance().buildIgmpV3Join(groupIp, srcIp);
+                Ethernet eth = IgmpManager.outgoingIgmpWithV3() ?
+                        IgmpSender.getInstance().buildIgmpV3Join(groupIp, srcIp) :
+                        IgmpSender.getInstance().buildIgmpV2Join(groupIp, srcIp);
                 IgmpSender.getInstance().sendIgmpPacketUplink(eth, devId, upLinkPort);
                 timeOut = getTimeOut(IgmpManager.getUnsolicitedTimeout());
                 timerId = IgmpTimer.start(SingleStateMachine.this, timeOut);
@@ -173,7 +177,9 @@
 
         public void timeOut() {
             if (sendQuery) {
-                Ethernet eth = IgmpSender.getInstance().buildIgmpV3ResponseQuery(groupIp, srcIp);
+                Ethernet eth = IgmpManager.outgoingIgmpWithV3() ?
+                        IgmpSender.getInstance().buildIgmpV3ResponseQuery(groupIp, srcIp) :
+                        IgmpSender.getInstance().buildIgmpV2ResponseQuery(groupIp, srcIp);
                 IgmpSender.getInstance().sendIgmpPacketUplink(eth, devId, upLinkPort);
                 timeOut = DEFAULT_MAX_RESP;
             }