Adding mfwd, igmp and mfwd apps

Change-Id: Ie7187716db36b754e4cd687a8f9de004e27c7825

adding mfwd, pim, igmp apps

Change-Id: Iddd2dcee24dc905d5ff0efe1d1d798fc83a7c736
diff --git a/apps/dhcp/src/test/java/org/onosproject/dhcp/impl/DhcpManagerTest.java b/apps/dhcp/src/test/java/org/onosproject/dhcp/impl/DhcpManagerTest.java
index 3ea3b1b..20b002a 100644
--- a/apps/dhcp/src/test/java/org/onosproject/dhcp/impl/DhcpManagerTest.java
+++ b/apps/dhcp/src/test/java/org/onosproject/dhcp/impl/DhcpManagerTest.java
@@ -331,6 +331,7 @@
 
         @Override
         public void hostDetected(HostId hostId, HostDescription hostDescription, boolean replaceIps) {
+
         }
 
         @Override
diff --git a/apps/igmp/pom.xml b/apps/igmp/pom.xml
new file mode 100644
index 0000000..7980d2c
--- /dev/null
+++ b/apps/igmp/pom.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2014 Open Networking Laboratory
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.onosproject</groupId>
+        <artifactId>onos-apps</artifactId>
+        <version>1.4.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>onos-app-igmp</artifactId>
+    <packaging>bundle</packaging>
+
+    <description>Internet Group Message Protocol</description>
+
+    <properties>
+        <onos.app.name>org.onosproject.igmp</onos.app.name>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-cli</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onlab-osgi</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onlab-junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <!-- This is needed by ComponentContext, used for tunable configuration -->
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.scr.annotations</artifactId>
+            <version>1.9.8</version>
+            <scope>provided</scope>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Bundle-SymbolicName>
+                            ${project.groupId}.${project.artifactId}
+                        </Bundle-SymbolicName>
+                        <Import-Package>
+                            org.slf4j,
+                            org.osgi.framework,
+                            org.apache.commons.lang.math.*,
+                            com.google.common.*,
+                            org.onlab.packet.*,
+                            org.onlab.rest.*,
+                            org.onosproject.*,
+                            org.onosproject.mfwd.impl.*;
+                            org.onlab.util.*,
+                            org.jboss.netty.util.*
+                        </Import-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>2.5.1</version>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/apps/igmp/src/main/java/org/onosproject/igmp/impl/IGMPComponent.java b/apps/igmp/src/main/java/org/onosproject/igmp/impl/IGMPComponent.java
new file mode 100644
index 0000000..ae539c6
--- /dev/null
+++ b/apps/igmp/src/main/java/org/onosproject/igmp/impl/IGMPComponent.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.igmp.impl;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+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.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.IGMP;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketPriority;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.slf4j.Logger;
+
+/**
+ * Internet Group Management Protocol.
+ */
+@Component(immediate = true)
+public class IGMPComponent {
+    private final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    private IGMPPacketProcessor processor = new IGMPPacketProcessor();
+    private static ApplicationId appId;
+
+    @Activate
+    public void activate() {
+        appId = coreService.registerApplication("org.onosproject.igmp");
+
+        packetService.addProcessor(processor, PacketProcessor.director(1));
+
+        // Build a traffic selector for all multicast traffic
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+        selector.matchEthType(Ethernet.TYPE_IPV4);
+        selector.matchIPProtocol(IPv4.PROTOCOL_IGMP);
+        packetService.requestPackets(selector.build(), PacketPriority.REACTIVE, appId);
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        packetService.removeProcessor(processor);
+        processor = null;
+        log.info("Stopped");
+    }
+
+    /**
+     * Packet processor responsible for handling IGMP packets.
+     */
+    private class IGMPPacketProcessor implements PacketProcessor {
+
+        @Override
+        public void process(PacketContext context) {
+            // Stop processing if the packet has been handled, since we
+            // can't do any more to it.
+            if (context.isHandled()) {
+                return;
+            }
+
+            InboundPacket pkt = context.inPacket();
+            Ethernet ethPkt = pkt.parsed();
+            if (ethPkt == null) {
+                return;
+            }
+
+            /*
+             * IPv6 MLD packets are handled by ICMP6. We'll only deal
+             * with IPv4.
+             */
+            if (ethPkt.getEtherType() != Ethernet.TYPE_IPV4) {
+                return;
+            }
+
+            IPv4 ip = (IPv4) ethPkt.getPayload();
+            IpAddress gaddr = IpAddress.valueOf(ip.getDestinationAddress());
+            IpAddress saddr = Ip4Address.valueOf(ip.getSourceAddress());
+            log.debug("Packet (" + saddr.toString() + ", " + gaddr.toString() +
+                    "\tingress port: " + context.inPacket().receivedFrom().toString());
+
+            if (ip.getProtocol() != IPv4.PROTOCOL_IGMP) {
+                log.error("IGMP Picked up a non IGMP packet.");
+                return;
+            }
+
+            IpPrefix mcast = IpPrefix.valueOf("224.0.0.0/4");
+            if (!mcast.contains(gaddr)) {
+                log.error("IGMP Picked up a non multicast packet.");
+                return;
+            }
+
+            if (mcast.contains(saddr)) {
+                log.error("IGMP Picked up a packet with a multicast source address.");
+                return;
+            }
+            IpPrefix spfx = IpPrefix.valueOf(saddr, 32);
+            IpPrefix gpfx = IpPrefix.valueOf(gaddr, 32);
+
+            IGMP igmp = (IGMP) ip.getPayload();
+            switch (igmp.getIgmpType()) {
+
+                case IGMP.TYPE_IGMPV3_MEMBERSHIP_REPORT:
+                    IGMPProcessMembership.processMembership(igmp, pkt.receivedFrom());
+                    break;
+
+                case IGMP.TYPE_IGMPV3_MEMBERSHIP_QUERY:
+                    IGMPProcessQuery.processQuery(igmp, pkt.receivedFrom());
+                    break;
+
+                case IGMP.TYPE_IGMPV1_MEMBERSHIP_REPORT:
+                case IGMP.TYPE_IGMPV2_MEMBERSHIP_REPORT:
+                case IGMP.TYPE_IGMPV2_LEAVE_GROUP:
+                    log.debug("IGMP version 1 & 2 message types are not currently supported. Message type: " +
+                            igmp.getIgmpType());
+                    break;
+
+                default:
+                    log.debug("Unkown IGMP message type: " + igmp.getIgmpType());
+                    break;
+            }
+        }
+    }
+}
diff --git a/apps/igmp/src/main/java/org/onosproject/igmp/impl/IGMPProcessMembership.java b/apps/igmp/src/main/java/org/onosproject/igmp/impl/IGMPProcessMembership.java
new file mode 100644
index 0000000..4c08a74
--- /dev/null
+++ b/apps/igmp/src/main/java/org/onosproject/igmp/impl/IGMPProcessMembership.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.igmp.impl;
+
+import org.onlab.packet.IGMP;
+import org.onosproject.net.ConnectPoint;
+
+/**
+ * Process an IGMP Membership Report.
+ */
+public final class IGMPProcessMembership {
+
+    // Hide the default constructor.
+    private IGMPProcessMembership() {
+    }
+
+    /**
+     * Process the IGMP Membership report.
+     *
+     * @param igmp the deserialized IGMP message.
+     */
+    public static void processMembership(IGMP igmp, ConnectPoint recievedFrom) {
+    }
+
+}
diff --git a/apps/igmp/src/main/java/org/onosproject/igmp/impl/IGMPProcessQuery.java b/apps/igmp/src/main/java/org/onosproject/igmp/impl/IGMPProcessQuery.java
new file mode 100644
index 0000000..8407bb1
--- /dev/null
+++ b/apps/igmp/src/main/java/org/onosproject/igmp/impl/IGMPProcessQuery.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.igmp.impl;
+
+import org.onlab.packet.IGMP;
+import org.onosproject.net.ConnectPoint;
+
+/**
+ * Process IGMP Query messages.
+ */
+public final class IGMPProcessQuery {
+
+    // Hide the default constructor.
+    private IGMPProcessQuery() {
+    }
+
+    /**
+     * Process the IGMP Membership Query message.
+     *
+     * @param igmp The deserialzed IGMP message
+     */
+    public static void processQuery(IGMP igmp, ConnectPoint receivedFrom) {
+    }
+
+}
diff --git a/apps/mfwd/pom.xml b/apps/mfwd/pom.xml
new file mode 100644
index 0000000..489c315
--- /dev/null
+++ b/apps/mfwd/pom.xml
@@ -0,0 +1,140 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2014 Open Networking Laboratory
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.onosproject</groupId>
+        <artifactId>onos-apps</artifactId>
+        <version>1.4.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>onos-app-mfwd</artifactId>
+    <packaging>bundle</packaging>
+
+    <description>Multicast forwarding application</description>
+
+    <properties>
+        <onos.app.name>org.onosproject.mfwd</onos.app.name>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-cli</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty</artifactId>
+            <version>3.9.0.Final</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.karaf.shell</groupId>
+            <artifactId>org.apache.karaf.shell.console</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onlab-rest</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-rest</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.jersey</groupId>
+            <artifactId>jersey-servlet</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-annotations</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onlab-junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.easymock</groupId>
+            <artifactId>easymock</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+    </dependencies>
+
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <_wab>src/main/webapp/</_wab>
+                        <Bundle-SymbolicName>
+                            ${project.groupId}.${project.artifactId}
+                        </Bundle-SymbolicName>
+                        <Import-Package>
+                            org.slf4j,
+                            org.osgi.framework,
+                            javax.ws.rs,javax.ws.rs.core,
+                            com.sun.jersey.api.core,
+                            com.sun.jersey.spi.container.servlet,
+                            com.sun.jersey.server.impl.container.servlet,
+                            com.fasterxml.jackson.databind,
+                            com.fasterxml.jackson.databind.node,
+                            org.apache.commons.lang.math.*,
+                            com.google.common.*,
+                            org.onlab.packet.*,
+                            org.onlab.rest.*,
+                            org.onosproject.*,
+                            org.onlab.util.*,
+                            org.jboss.netty.util.*
+                        </Import-Package>
+                        <Web-ContextPath>${web.context}</Web-ContextPath>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
+
diff --git a/apps/mfwd/src/main/java/org/onosproject/mfwd/cli/McastDeleteCommand.java b/apps/mfwd/src/main/java/org/onosproject/mfwd/cli/McastDeleteCommand.java
new file mode 100644
index 0000000..ded5a1a
--- /dev/null
+++ b/apps/mfwd/src/main/java/org/onosproject/mfwd/cli/McastDeleteCommand.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.mfwd.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+
+import org.onosproject.mfwd.impl.McastRouteTable;
+
+/**
+ * Delete a multicast route.
+ */
+@Command(scope = "onos", name = "mcast-delete",
+        description = "Delete a multicast route flow")
+public class McastDeleteCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "sAddr",
+            description = "IP Address of the multicast source. '*' can be used for any source (*, G) entry",
+            required = true, multiValued = false)
+    String sAddr = null;
+
+    @Argument(index = 1, name = "gAddr",
+            description = "IP Address of the multicast group",
+            required = true, multiValued = false)
+    String gAddr = null;
+
+    @Override
+    protected void execute() {
+        McastRouteTable mrib = McastRouteTable.getInstance();
+        mrib.removeRoute(sAddr, gAddr);
+    }
+}
+
diff --git a/apps/mfwd/src/main/java/org/onosproject/mfwd/cli/McastJoinCommand.java b/apps/mfwd/src/main/java/org/onosproject/mfwd/cli/McastJoinCommand.java
new file mode 100644
index 0000000..5172691
--- /dev/null
+++ b/apps/mfwd/src/main/java/org/onosproject/mfwd/cli/McastJoinCommand.java
@@ -0,0 +1,97 @@
+/*

+ * Copyright 2014-2015 Open Networking Laboratory

+ *

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *     http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+package org.onosproject.mfwd.cli;

+

+import org.apache.karaf.shell.commands.Argument;

+import org.apache.karaf.shell.commands.Command;

+import org.onlab.packet.IpPrefix;

+import org.onosproject.cli.AbstractShellCommand;

+

+import org.onosproject.mfwd.impl.McastRouteBase;

+import org.onosproject.mfwd.impl.McastRouteTable;

+

+/**

+ * Installs a source, multicast group flow.

+ */

+@Command(scope = "onos", name = "mcast-join",

+         description = "Installs a source, multicast group flow")

+public class McastJoinCommand extends AbstractShellCommand {

+

+    @Argument(index = 0, name = "sAddr",

+              description = "IP Address of the multicast source. '*' can be used for any source (*, G) entry",

+              required = true, multiValued = false)

+    String sAddr = null;

+

+    @Argument(index = 1, name = "gAddr",

+              description = "IP Address of the multicast group",

+              required = true, multiValued = false)

+    String gAddr = null;

+

+    @Argument(index = 2, name = "ingressPort",

+            description = "Ingress port and Egress ports",

+            required = false, multiValued = false)

+    String ingressPort = null;

+

+    @Argument(index = 3, name = "ports",

+              description = "Ingress port and Egress ports",

+              required = false, multiValued = true)

+    String[] ports = null;

+

+    @Override

+    protected void execute() {

+        McastRouteTable mrib = McastRouteTable.getInstance();

+        IpPrefix mcast = IpPrefix.valueOf("224.0.0.0/4");

+        IpPrefix saddr = IpPrefix.valueOf(sAddr);

+        if (mcast.contains(saddr)) {

+            print("Error: the source address " + sAddr + " must be an IPv4 unicast address");

+            return;

+        }

+

+        IpPrefix gaddr = IpPrefix.valueOf(gAddr);

+        if (!mcast.contains(gaddr)) {

+            print("Error: " + gAddr + " must be a multicast group address");

+            return;

+        }

+

+        McastRouteBase mr = mrib.addRoute(sAddr, gAddr);

+        if (mr == null) {

+            print("Error: unable to save the multicast state");

+            return;

+        }

+

+        // Port format "of:0000000000000023/4"

+        if (ingressPort != null) {

+            String inCP = ingressPort;

+            log.debug("Ingress port provided: " + inCP);

+            String [] cp = inCP.split("/");

+            mr.addIngressPoint(cp[0], Long.parseLong(cp[1]));

+        } else {

+            return;

+        }

+

+        if (ports == null) {

+            return;

+        }

+

+        for (int i = 0; i < ports.length; i++) {

+            String egCP = ports[i];

+            log.debug("Egress port provided: " + egCP);

+            String [] cp = egCP.split("/");

+            mr.addEgressPoint(cp[0], Long.parseLong(cp[1]));

+        }

+        print("Added the mcast route");

+    }

+}

diff --git a/apps/mfwd/src/main/java/org/onosproject/mfwd/cli/McastShowCommand.java b/apps/mfwd/src/main/java/org/onosproject/mfwd/cli/McastShowCommand.java
new file mode 100644
index 0000000..750fac9
--- /dev/null
+++ b/apps/mfwd/src/main/java/org/onosproject/mfwd/cli/McastShowCommand.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.mfwd.cli;
+
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.ConnectPoint;
+import org.onlab.packet.IpPrefix;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.onosproject.mfwd.impl.McastRouteTable;
+import org.onosproject.mfwd.impl.McastRouteGroup;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Displays the source, multicast group flows entries.
+ */
+@Command(scope = "onos", name = "mcast-show", description = "Displays the source, multicast group flows")
+public class McastShowCommand extends AbstractShellCommand {
+
+    private final Logger log = getLogger(getClass());
+
+    @Override
+    protected void execute() {
+        McastRouteTable mrt = McastRouteTable.getInstance();
+        if (outputJson()) {
+            print("%s", json(mrt));
+        } else {
+            printMrib4(mrt);
+        }
+    }
+
+    public JsonNode json(McastRouteTable mrt) {
+        ObjectMapper mapper = new ObjectMapper();
+        ArrayNode result = mapper.createArrayNode();
+        Map<IpPrefix, McastRouteGroup> mrib4 = mrt.getMrib4();
+        for (McastRouteGroup mg : mrib4.values()) {
+            String sAddr = "";
+            String gAddr = "";
+            String inPort = "";
+            String outPorts = "";
+            if (mg.getSaddr() != null) {
+                sAddr = mg.getSaddr().toString();
+                log.info("Multicast Source: " + sAddr);
+            }
+            if (mg.getGaddr() != null) {
+                gAddr = mg.getGaddr().toString();
+                log.info("Multicast Group: " + gAddr);
+            }
+            if (mg.getIngressPoint() != null) {
+                inPort = mg.getIngressPoint().toString();
+                log.info("Multicast Ingress: " + inPort);
+            }
+            Set<ConnectPoint> eps = mg.getEgressPoints();
+            if (eps != null && !eps.isEmpty()) {
+                outPorts = eps.toString();
+            }
+            result.add(mapper.createObjectNode()
+                                    .put("src", sAddr)
+                                    .put("grp", gAddr)
+                                    .put("inPort", inPort)
+                                    .put("outPorts", outPorts));
+        }
+        return result;
+    }
+
+    /**
+     * Displays multicast route table entries.
+     *
+     * @param mrt route table
+     */
+    protected void printMrib4(McastRouteTable mrt) {
+        print(mrt.printMcastRouteTable());
+    }
+}
diff --git a/apps/mfwd/src/main/java/org/onosproject/mfwd/cli/package-info.java b/apps/mfwd/src/main/java/org/onosproject/mfwd/cli/package-info.java
new file mode 100644
index 0000000..7b5ed39
--- /dev/null
+++ b/apps/mfwd/src/main/java/org/onosproject/mfwd/cli/package-info.java
@@ -0,0 +1,5 @@
+/**

+ * Sample Multicast forwarding framework using intents.

+ */

+package org.onosproject.mfwd.cli;

+

diff --git a/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastForwarding.java b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastForwarding.java
new file mode 100644
index 0000000..bcba060
--- /dev/null
+++ b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastForwarding.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.mfwd.impl;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+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.Ethernet;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IPv4;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketPriority;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.slf4j.Logger;
+
+/**
+ * WORK-IN-PROGRESS: The multicast forwarding application using intent framework.
+ */
+@Component(immediate = true)
+public class McastForwarding {
+
+    private final Logger log = getLogger(getClass());
+    private final IpPrefix mcast = IpPrefix.valueOf("224.0.0.0/4");
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    private ReactivePacketProcessor processor = new ReactivePacketProcessor();
+    private McastRouteTable mrib;
+    private static ApplicationId appId;
+
+    /**
+     * Active MulticastForwardingIntent.
+     */
+    @Activate
+    public void activate() {
+        appId = coreService.registerApplication("org.onosproject.mfwd");
+
+        packetService.addProcessor(processor, PacketProcessor.director(2));
+
+        // Build a traffic selector for all multicast traffic
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+        selector.matchEthType(Ethernet.TYPE_IPV4);
+        selector.matchIPDst(mcast);
+        packetService.requestPackets(selector.build(), PacketPriority.REACTIVE, appId);
+
+        mrib = McastRouteTable.getInstance();
+        log.info("Started");
+    }
+
+    /**
+     * Deactivate Multicast Forwarding Intent.
+     */
+    @Deactivate
+    public void deactivate() {
+        packetService.removeProcessor(processor);
+        processor = null;
+        log.info("Stopped");
+    }
+
+    /**
+     * Get the application ID, used by the McastIntentManager.
+     *
+     * @return the application ID
+     */
+    public static ApplicationId getAppId() {
+        return appId;
+    }
+
+    /**
+     * Packet processor responsible for forwarding packets along their paths.
+     */
+    private class ReactivePacketProcessor implements PacketProcessor {
+
+        /**
+         * Process incoming packets.
+         *
+         * @param context packet processing context
+         */
+        @Override
+        public void process(PacketContext context) {
+            // Stop processing if the packet has been handled, since we
+            // can't do any more to it.
+            if (context.isHandled()) {
+                return;
+            }
+
+            InboundPacket pkt = context.inPacket();
+            Ethernet ethPkt = pkt.parsed();
+
+            if (ethPkt == null) {
+                return;
+            }
+
+            if (ethPkt.getEtherType() != Ethernet.TYPE_IPV4 &&
+                    ethPkt.getEtherType() != Ethernet.TYPE_IPV6) {
+                return;
+            }
+
+            if (ethPkt.getEtherType() == Ethernet.TYPE_IPV6) {
+                // Ignore ipv6 at the moment.
+                return;
+            }
+
+            IPv4 ip = (IPv4) ethPkt.getPayload();
+            IpAddress gaddr = IpAddress.valueOf(ip.getDestinationAddress());
+            IpAddress saddr = Ip4Address.valueOf(ip.getSourceAddress());
+
+            log.debug("Packet ({}, {}) has been punted\n" +
+                            "\tingress port: {}\n",
+                    saddr.toString(),
+                    gaddr.toString(),
+                    context.inPacket().receivedFrom().toString());
+
+            if (!mcast.contains(gaddr)) {
+                // Yikes, this is a bad group address
+                return;
+            }
+
+            if (mcast.contains(saddr)) {
+                // Yikes, the source address is multicast
+                return;
+            }
+
+            IpPrefix spfx = IpPrefix.valueOf(saddr, 32);
+            IpPrefix gpfx = IpPrefix.valueOf(gaddr, 32);
+
+            /*
+             * Do a best match lookup on the (s, g) of the packet. If an entry does
+             * not exist create one and store it's incoming connect point.
+             *
+             * The connect point is deviceId / portId that the packet entered
+             * the SDN network.  This differs from traditional mcast where the
+             * ingress port would be a specific device.
+             */
+            McastRoute entry = mrib.findBestMatch(spfx, gpfx);
+            if (entry == null || entry.getSaddr().equals(IPv4.fromIPv4Address(0))) {
+
+                /*
+                 * Create an entry that we can fast drop.
+                 */
+                entry = mrib.addRoute(spfx, gpfx);
+                entry.addIngressPoint(context.inPacket().receivedFrom());
+            }
+
+            /*
+             * TODO: If we do not have an ingress or any egress connect points we
+             * should set up a fast drop entry.
+             */
+            if (entry.getIngressPoint() == null) {
+                return;
+            }
+
+            if (entry.getEgressPoints().isEmpty()) {
+                return;
+            }
+
+            /*
+             * This is odd, we should not have received a punted packet if an
+             * intent was installed unless the intent was not installed
+             * correctly.  However, we are seeing packets get punted after
+             * the intent has been installed.
+             *
+             * Therefore we are going to forward the packets even if they
+             * should have already been forwarded by the intent fabric.
+             */
+            if (entry.getIntentKey() != null) {
+                return;
+            }
+
+            entry.setIntent();
+            McastIntentManager im = McastIntentManager.getInstance();
+            im.setIntent(entry);
+
+            entry.incrementPuntCount();
+
+            // Send the pack out each of the egress devices & port
+            forwardPacketToDst(context, entry);
+        }
+    }
+
+    /**
+     * Forward the packet to it's multicast destinations.
+     *
+     * @param context The packet context
+     * @param entry The multicast route entry matching this packet
+     */
+    private void forwardPacketToDst(PacketContext context, McastRoute entry) {
+
+        // Send the pack out each of the respective egress ports
+        for (ConnectPoint egress : entry.getEgressPoints()) {
+            TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                    .setOutput(egress.port()).build();
+
+            OutboundPacket packet = new DefaultOutboundPacket(
+                    egress.deviceId(),
+                    treatment,
+                    context.inPacket().unparsed());
+
+            packetService.emit(packet);
+        }
+    }
+}
diff --git a/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastIntentManager.java b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastIntentManager.java
new file mode 100644
index 0000000..c3709e6
--- /dev/null
+++ b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastIntentManager.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.mfwd.impl;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+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.Ethernet;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.SinglePointToMultiPointIntent;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+
+@Component(immediate = true)
+@Service(value = org.onosproject.mfwd.impl.McastIntentManager.class)
+public class McastIntentManager {
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentService intentService;
+
+    private static McastIntentManager instance;
+
+    public McastIntentManager() {
+        instance = this;
+    }
+
+    /**
+     * Active this component.
+     */
+    @Activate
+    public void activate() { }
+
+    /**
+     * Deactivate this component.
+     */
+    @Deactivate
+    public void deactivate() {
+        withdrawAllIntents();
+    }
+
+    /**
+     * Get instance of this intentManager.
+     *
+     * @return the instance of this intent manager.
+     */
+    public static McastIntentManager getInstance() {
+        if (instance == null) {
+            instance = new McastIntentManager();
+        }
+        return instance;
+    }
+
+    /**
+     * Install the PointToMultipoint forwarding intent.
+     *
+     * @param mroute multicast route entry
+     * @return the intent that has been set or null otherwise
+     */
+    public SinglePointToMultiPointIntent setIntent(McastRoute mroute) {
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+        TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment();
+
+        if (mroute.getIngressPoint() == null ||
+                mroute.getEgressPoints().isEmpty()) {
+            return null;
+        }
+
+        /*
+         * Match the group AND source addresses.  We will also check ether type to
+         * determine if we are doing ipv4 or ipv6.
+         *
+         * If we really wanted to be pendantic we could put in a
+         * condition to make sure the ethernet MAC address was also
+         * mcast.
+         */
+        selector.matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(mroute.getGaddr())
+                .matchIPSrc(mroute.getSaddr());
+
+        SinglePointToMultiPointIntent intent =
+                SinglePointToMultiPointIntent.builder()
+                        .appId(McastForwarding.getAppId())
+                        .selector(selector.build())
+                        .treatment(treatment)
+                        .ingressPoint(mroute.getIngressPoint())
+                        .egressPoints(mroute.getEgressPoints()).
+                        build();
+
+        intentService.submit(intent);
+        return intent;
+    }
+
+    /**
+     * Withdraw the intent represented by this route.
+     *
+     * @param mroute the mcast route whose intent we want to remove
+     */
+    public void withdrawIntent(McastRouteBase mroute) {
+        Intent intent = intentService.getIntent(mroute.getIntentKey());
+        intentService.withdraw(intent);
+    }
+
+    /**
+     * Withdraw all intents.
+     *
+     * This will be called from the deactivate method so we don't leave
+     * a mess behind us after we leave.
+     */
+    public void withdrawAllIntents() {
+        for (Intent intent : intentService.getIntents()) {
+            intentService.withdraw(intent);
+        }
+    }
+}
diff --git a/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRoute.java b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRoute.java
new file mode 100644
index 0000000..d56488e
--- /dev/null
+++ b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRoute.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.mfwd.impl;
+
+import org.onlab.packet.IpPrefix;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.intent.Key;
+import org.onosproject.net.intent.SinglePointToMultiPointIntent;
+
+import java.util.Set;
+
+/**
+ * This McastRouteBase interface is implemented by the McastRouteBase class which
+ * in turn acts as the base class for both the McastRouteGroup and McastRouteSource.
+ */
+interface McastRoute {
+
+    /**
+     * Gets the group addresses.
+     *
+     * @return group address
+     */
+    public IpPrefix getGaddr();
+
+    /**
+     * Gets the source address.
+     *
+     * @return the source address
+     */
+    public IpPrefix getSaddr();
+
+    /**
+     * Determines if this is an IPv4 multicast route.
+     *
+     * @return true if it is an IPv4 route
+     */
+    public boolean isIp4();
+
+    /**
+     * Determines if this is an IPv6 multicast route.
+     *
+     * @return true if it is an IPv6 route
+     */
+    public boolean isIp6();
+
+    /**
+     * Add the ingress ConnectPoint with a ConnectPoint.
+     *
+     * @param ingress ingress point
+     */
+    public void addIngressPoint(ConnectPoint ingress);
+
+    /**
+     * Add the ingress Connect Point using. ..
+     *
+     * @param deviceId device ID
+     * @param portNum port number
+     */
+    public void addIngressPoint(String deviceId, long portNum);
+
+    /**
+     * Get the ingress connect point.
+     *
+     * @return the ingress connect point
+     */
+    public ConnectPoint getIngressPoint();
+
+    /**
+     * Add an egress connect point.
+     *
+     * @param member the egress ConnectPoint to be added
+     */
+    public void addEgressPoint(ConnectPoint member);
+
+    /**
+     * Add an egress connect point.
+     *
+     * @param deviceId the device ID of the connect point
+     * @param portNum the port number of the connect point
+     */
+    public void addEgressPoint(String deviceId, long portNum);
+
+    /**
+     * Get the egress connect points.
+     *
+     * @return a set of egress connect points
+     */
+    public Set<ConnectPoint> getEgressPoints();
+
+    /**
+     * Increment the punt count.
+     */
+    public void incrementPuntCount();
+
+    /**
+     * Get the punt count.
+     *
+     * @return the punt count
+     */
+    public int getPuntCount();
+
+    /**
+     * Have the McastIntentManager create an intent, attempt to
+     * install the intent and then save the key.
+     */
+    public void setIntent();
+
+    /**
+     * Set the Intent key.
+     *
+     * @param intent intent
+     */
+    public void setIntent(SinglePointToMultiPointIntent intent);
+
+    /**
+     * Withdraw the intent if it has been installed.
+     */
+    public void withdrawIntent();
+
+    /**
+     * Get the intent key.
+     *
+     * @return the intentKey
+     */
+    public Key getIntentKey();
+
+    /**
+     * Pretty print the the route.
+     *
+     * @return a pretty string
+     */
+    public String toString();
+}
\ No newline at end of file
diff --git a/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteBase.java b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteBase.java
new file mode 100644
index 0000000..36582ad
--- /dev/null
+++ b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteBase.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.mfwd.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import org.onlab.packet.IpPrefix;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.intent.SinglePointToMultiPointIntent;
+import org.onosproject.net.intent.Key;
+
+import java.util.Set;
+import java.util.HashSet;
+
+/**
+ * McastRouteBase base class for McastRouteGroup and McastRouteSource.
+ */
+public class McastRouteBase implements McastRoute {
+    protected final IpPrefix gaddr;
+    protected final IpPrefix saddr;
+
+    protected ConnectPoint ingressPoint;
+    protected Set<ConnectPoint> egressPoints;
+
+    protected boolean isGroup = false;
+
+    /**
+     * How may times has this packet been punted.
+     */
+    private int puntCount = 0;
+
+    /**
+     * If the intentKey is null that means no intent has
+     * been installed.
+     */
+    protected Key intentKey = null;
+
+    /**
+     * Create a multicast route. This is the parent class for both the Group
+     * and the source.
+     *
+     * @param saddr source address
+     * @param gaddr multicast group address
+     */
+    public McastRouteBase(String saddr, String gaddr) {
+        this.gaddr = IpPrefix.valueOf(checkNotNull(gaddr));
+        if (saddr == null || saddr.equals("*")) {
+            this.saddr = IpPrefix.valueOf(0, 0);
+        } else {
+            this.saddr = IpPrefix.valueOf(checkNotNull(gaddr));
+        }
+        this.init();
+    }
+
+    /**
+     * Create a multicast group table entry.
+     * @param gaddr multicast group address
+     */
+    public McastRouteBase(String gaddr) {
+        this("*", gaddr);
+    }
+
+    /**
+     * Set the source and group address value of a (*, G) group.
+     *
+     * @param gpfx the group prefix address
+     */
+    public McastRouteBase(IpPrefix gpfx) {
+        this(IpPrefix.valueOf(0, 0), gpfx);
+    }
+
+    /**
+     * Create a multicast route constructor.
+     *
+     * @param saddr source address
+     * @param gaddr group address
+     */
+    public McastRouteBase(IpPrefix saddr, IpPrefix gaddr) {
+        this.saddr = checkNotNull(saddr);
+        this.gaddr = checkNotNull(gaddr);
+
+        this.init();
+    }
+
+    private void init() {
+        this.isGroup = (this.saddr.prefixLength() == 0);
+        this.ingressPoint = null;
+        this.egressPoints = new HashSet();
+    }
+
+    /**
+     * Get the multicast group address.
+     *
+     * @return the multicast group address
+     */
+    @Override
+    public IpPrefix getGaddr() {
+        return gaddr;
+    }
+
+    /**
+     * Get the multicast source address.
+     *
+     * @return the multicast source address
+     */
+    @Override
+    public IpPrefix getSaddr() {
+        return saddr;
+    }
+
+    /**
+     * Is this an IPv4 multicast route.
+     *
+     * @return true if it is an IPv4 route
+     */
+    @Override
+    public boolean isIp4() {
+        return gaddr.isIp4();
+    }
+
+    /**
+     * Is this an IPv6 multicast route.
+     *
+     * @return true if it is an IPv6 route
+     */
+    @Override
+    public boolean isIp6() {
+        return gaddr.isIp6();
+    }
+
+    /**
+     * Is this a multicast group route?
+     *
+     * @return true if it is a multicast group route.
+     */
+    public boolean isGroup() {
+        return isGroup;
+    }
+
+    /**
+     * @return true if this is (S, G) false if it (*, G).
+     */
+    public boolean isSource() {
+        return (!isGroup);
+    }
+
+    /**
+     * Add an ingress point to this route.
+     *
+     * @param ingress incoming connect point
+     */
+    @Override
+    public void addIngressPoint(ConnectPoint ingress) {
+        ingressPoint = checkNotNull(ingress);
+    }
+
+    /**
+     * Add or modify the ingress connect point.
+     *
+     * @param deviceId the switch device Id
+     * @param portNum the ingress port number
+     */
+    @Override
+    public void addIngressPoint(String deviceId, long portNum) {
+        ingressPoint = new ConnectPoint(
+                DeviceId.deviceId(deviceId),
+                PortNumber.portNumber(portNum));
+    }
+
+    /**
+     * Get the ingress ConnectPoint.
+     *
+     * @return the ingress ConnectPoint
+     */
+    @Override
+    public ConnectPoint getIngressPoint() {
+        return this.ingressPoint;
+    }
+
+    /**
+     * Add an egress ConnectPoint.
+     *
+     * @param member member egress connect point
+     */
+    @Override
+    public void addEgressPoint(ConnectPoint member) {
+        egressPoints.add(checkNotNull(member));
+    }
+
+    /**
+     * Add an egress ConnectPoint.
+     *
+     * @param deviceId deviceId of the connect point
+     * @param portNum portNum of the connect point
+     */
+    @Override
+    public void addEgressPoint(String deviceId, long portNum) {
+        ConnectPoint cp = new ConnectPoint(DeviceId.deviceId(deviceId), PortNumber.portNumber(portNum));
+        this.egressPoints.add(cp);
+    }
+
+    /**
+     * Get egress connect points for the route.
+     *
+     * @return Set of egress connect points
+     */
+    @Override
+    public Set<ConnectPoint> getEgressPoints() {
+        return egressPoints;
+    }
+
+    /**
+     * Get the number of times the packet has been punted.
+     *
+     * @return the punt count
+     */
+    @Override
+    public int getPuntCount() {
+        return puntCount;
+    }
+
+    /**
+     * Increment the punt count.
+     *
+     * TODO: we need to handle wrapping.
+     */
+    @Override
+    public void incrementPuntCount() {
+        puntCount++;
+    }
+
+    /**
+     * Have the McastIntentManager create and set the intent, then save the intent key.
+     *
+     * If we already have an intent, we will first withdraw the existing intent and
+     * replace it with a new one.  This will support the case where the ingress connectPoint
+     * or group of egress connectPoints change.
+     */
+    @Override
+    public void setIntent() {
+        if (this.intentKey != null) {
+            this.withdrawIntent();
+        }
+        McastIntentManager im = McastIntentManager.getInstance();
+        SinglePointToMultiPointIntent intent = im.setIntent(this);
+        this.intentKey = intent.key();
+    }
+
+    /**
+     * Set the Intent key.
+     *
+     * @param intent intent
+     */
+    @Override
+    public void setIntent(SinglePointToMultiPointIntent intent) {
+        intentKey = intent.key();
+    }
+
+    /**
+     * Get the intent key represented by this route.
+     *
+     * @return intentKey
+     */
+    @Override
+    public Key getIntentKey() {
+        return this.intentKey;
+    }
+
+
+    /**
+     * Withdraw the intent and set the key to null.
+     */
+    @Override
+    public void withdrawIntent() {
+        if (intentKey == null) {
+            // nothing to withdraw
+            return;
+        }
+        McastIntentManager im = McastIntentManager.getInstance();
+        im.withdrawIntent(this);
+        this.intentKey = null;
+    }
+
+    /**
+     * Pretty Print this Multicast Route.  Works for McastRouteSource and McastRouteGroup.
+     *
+     * @return pretty string of the multicast route
+     */
+    @Override
+    public String toString() {
+        String out = String.format("(%s, %s)\n\t",
+                saddr.toString(), gaddr.toString());
+
+        out += "intent: ";
+        out += (intentKey == null) ? "not installed" : this.intentKey.toString();
+        out += "\n\tingress: ";
+        out += (ingressPoint == null) ? "NULL" : ingressPoint.toString();
+        out += "\n\tegress: {\n";
+        if (egressPoints != null && !egressPoints.isEmpty()) {
+            for (ConnectPoint eg : egressPoints) {
+                out += "\t\t" + eg.toString() + "\n";
+            }
+        }
+        out += ("\t}\n");
+        out += ("\tpunted: " + this.getPuntCount() + "\n");
+        return out;
+    }
+}
diff --git a/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteGroup.java b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteGroup.java
new file mode 100644
index 0000000..4a58e1b
--- /dev/null
+++ b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteGroup.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.mfwd.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import java.util.HashMap;
+import org.onlab.packet.IpPrefix;
+
+/**
+ * The McastRouteGroup extends the McastRouteBase class and serves two purposes:
+ * first it represents a (*, G) multicast route entry.  Second it serves
+ * as a container for all (S, G) multicast route entries that belong
+ * to the same group address.
+ */
+public class McastRouteGroup extends McastRouteBase {
+    private HashMap<IpPrefix, McastRouteSource> sources;
+
+    /**
+     * Class constructor.
+     *
+     * @param gaddr - String representation of group address.
+     */
+    public McastRouteGroup(String gaddr) {
+        super(checkNotNull(gaddr));
+        this.init();
+    }
+
+    /**
+     * Create a multicast group.
+     *
+     * @param gpfx - Group address
+     */
+    public McastRouteGroup(IpPrefix gpfx) {
+        super(checkNotNull(gpfx));
+        this.init();
+    }
+
+    /**
+     * Common initialization used by constructors.
+     */
+    private void init() {
+        this.sources = new HashMap();
+        super.isGroup = true;
+    }
+
+    /**
+     * Find a specific multicast source address for this group.
+     *
+     * @param saddr the source address
+     * @return the multicast source route or null if it does not exist
+     */
+    public McastRouteSource findSource(IpPrefix saddr) {
+        return this.sources.get(checkNotNull(saddr));
+    }
+
+    /**
+     * Return the entire set of multicast sources for this group.
+     *
+     * @return the set of multicast sources
+     */
+    public HashMap<IpPrefix, McastRouteSource> getSources() {
+        return this.sources;
+    }
+
+    /**
+     * Add a new McastRouteSource to this group.
+     *
+     * @param src the multicast source
+     */
+    public void addSource(McastRouteSource src) {
+        checkNotNull(src);
+        this.sources.put(src.getSaddr(), src);
+    }
+
+    /**
+     * Remove the source with this specific IpPrefix from this group entry.
+     *
+     * @param spfx IP Prefix of the source to be removed
+     * @return the source route that was just removed
+     */
+    public McastRouteSource removeSource(IpPrefix spfx) {
+        McastRouteSource src = this.sources.remove(spfx);
+        src.withdrawIntent();
+        return src;
+    }
+
+    /**
+     * Remove all sources from this.
+     */
+    public void removeSources() {
+        for (McastRouteSource src : this.sources.values()) {
+            src.withdrawIntent();
+            this.sources.remove(src.getSaddr());
+        }
+    }
+
+}
diff --git a/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteSource.java b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteSource.java
new file mode 100644
index 0000000..68edc2e
--- /dev/null
+++ b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteSource.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.mfwd.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import org.onlab.packet.IpPrefix;
+
+/**
+ * This class represents and specific multicast senders source address.  Objects from
+ * this class will belong to the sources collection of the multicast group.
+ */
+public class McastRouteSource extends McastRouteBase {
+
+    // A reference to our parent group
+    private McastRouteGroup group;
+
+    /**
+     * Create a multicast source with IpPrefixes.
+     *
+     * @param source the source address
+     * @param group the group address
+     */
+    public McastRouteSource(IpPrefix source, IpPrefix group) {
+        super(checkNotNull(source), checkNotNull(group));
+    }
+
+    /**
+     * Set our parent multicast group.
+     *
+     * @param group the group this source belongs to
+     */
+    public void setGroup(McastRouteGroup group) {
+        this.group = group;
+    }
+}
\ No newline at end of file
diff --git a/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteTable.java b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteTable.java
new file mode 100644
index 0000000..ff7a026
--- /dev/null
+++ b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteTable.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.mfwd.impl;
+
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.IpPrefix;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * The Mcast Route Table holds all multicast state for the controller.
+ *
+ * State for IPv4 and IPv6 are maintained.  The tables are sets of McastRouteGroup
+ * structures that represent (*, G) state with a series of egress ConnectPoints.
+ * Each (*, G) may also have a set of (S, G) that may have there own set of
+ * ingress and egress ConnectPoints.
+ *
+ * TODO: perhaps should probably create two separate singleton for IPv4 and IPv6 respectively.
+ */
+@Service(value = org.onosproject.mfwd.impl.McastRouteTable.class)
+public final class McastRouteTable {
+
+    /*
+     * Create a map of the McastGroups indexed by the multicast group prefix.
+     * We may choose to change the map data structure in to some form a radix trie
+     * depending on the type of real world usage we see.
+     */
+    private final Map<IpPrefix, McastRouteGroup> mrib4;
+    private final Map<IpPrefix, McastRouteGroup> mrib6;
+    private static McastRouteTable instance = null;
+
+    private Boolean ipv6Enabled = false;
+
+    /**
+     * Create the two v4 & v6 tables.
+     */
+    private McastRouteTable() {
+        mrib4 = new ConcurrentHashMap<IpPrefix, McastRouteGroup>();
+        if (ipv6Enabled) {
+            mrib6 = new ConcurrentHashMap<IpPrefix, McastRouteGroup>();
+        } else {
+            mrib6 = null;
+        }
+    }
+
+    /**
+     * Get the single instance of this multicast group address.
+     *
+     * @return the multicast route table
+     */
+    public static McastRouteTable getInstance() {
+        if (instance == null) {
+            instance = new McastRouteTable();
+        }
+        return instance;
+    }
+
+    /**
+     * Get the IPv4 MRIB.
+     *
+     * @return the IPv4 MRIB
+     */
+    public Map<IpPrefix, McastRouteGroup> getMrib4() {
+        return mrib4;
+    }
+
+    /**
+     * Get the IPv6 MRIB.
+     *
+     * @return Return the set of prefix keyed McastGroups
+     */
+    public Map<IpPrefix, McastRouteGroup> getMrib6() {
+        return mrib6;
+    }
+
+    /**
+     * Save the McastRouteGroup in the address family appropriate mrib.
+     *
+     * @param group The McastRouteGroup to save
+     */
+    private void storeGroup(McastRouteGroup group) {
+        if (group.isIp4()) {
+            mrib4.put(group.getGaddr(), group);
+        } else if (group.isIp6() && ipv6Enabled) {
+            mrib6.put(group.getGaddr(), group);
+        }
+    }
+
+    /**
+     * Remove the group.
+     *
+     * @param group the group to be removed
+     */
+    private void removeGroup(McastRouteGroup group) {
+        IpPrefix gpfx = group.getGaddr();
+        if (gpfx.isIp4()) {
+            mrib4.remove(gpfx);
+        } else if (gpfx.isIp6() && ipv6Enabled) {
+            mrib6.remove(gpfx);
+        }
+    }
+
+    /**
+     * Add a multicast route to the MRIB.  This function will.
+     *
+     * @param saddr source address * or x.x.x.x or x.x.x.x/y
+     * @param gaddr group address x.x.x.x or x.x.x.x/y
+     * @return the multicast route
+     */
+    public McastRouteBase addRoute(String saddr, String gaddr) {
+        IpPrefix gpfx = IpPrefix.valueOf(gaddr);
+        IpPrefix spfx = IpPrefix.valueOf(0, 0);
+        if (saddr != null && !saddr.equals("*")) {
+            spfx = IpPrefix.valueOf(saddr);
+        }
+        return addRoute(spfx, gpfx);
+    }
+
+    /**
+     * Add a multicast route to the MRIB.  This function will store either
+     * (S, G) or (*, G) in the mrib if an entry does not already exist. If
+     * an entry does exist it is returned to the caller.
+     *
+     * Every (S, G) is stored as part of it's parent group entry which also represents
+     * (*, G) routes.  In the case of a (S, G) we will also create the (*, G) entry if needed
+     * then save the (S, G) to the (*, G).
+     *
+     * @param spfx the source prefix
+     * @param gpfx the group prefix
+     * @return the resulting McastRouteSource or McastRouteGroup accordingly.
+     */
+    public McastRouteBase addRoute(IpPrefix spfx, IpPrefix gpfx) {
+
+        /**
+         * If a group route (*, g) does not exist we will need to make so we
+         * can start attaching our sources to the group entry.
+         */
+        McastRouteGroup group = findMcastGroup(gpfx);
+        if (group == null) {
+            group = new McastRouteGroup(gpfx);
+
+            // Save it for later
+            if (gpfx.isIp4()) {
+                this.mrib4.put(gpfx, group);
+            } else if (gpfx.isIp6() && ipv6Enabled) {
+                    this.mrib6.put(gpfx, group);
+            }
+        }
+
+        /**
+         * If the source prefix length is 0 then we have our (*, g) entry, we can
+         * just return now.
+         */
+        if (spfx.prefixLength() == 0) {
+            return group;
+        }
+
+        // See if the source already exists.  If so just return it.
+        McastRouteSource source = group.findSource(spfx);
+        if (source != null) {
+            return source;
+        }
+
+        /**
+         * We have the group but no source.  We need to create the source then add it
+         * to the group.
+         */
+        source = new McastRouteSource(spfx, gpfx);
+
+        // Have the source save it's parent
+        source.setGroup(group);
+
+        // Save this source as part of this group
+        group.addSource(source);
+
+        return source;
+    }
+
+    /**
+     * Delete a multicast route from the MRIB.
+     *
+     * @param saddr source address * or x.x.x.x or x.x.x.x/y
+     * @param gaddr group address x.x.x.x or x.x.x.x/y
+     */
+    public void removeRoute(String saddr, String gaddr) {
+        IpPrefix gpfx = IpPrefix.valueOf(gaddr);
+        IpPrefix spfx = IpPrefix.valueOf(0, 0);
+        if (saddr != null && !saddr.equals("*")) {
+            spfx = IpPrefix.valueOf(saddr);
+        }
+        removeRoute(spfx, gpfx);
+    }
+
+    /**
+     * Remove a multicast route.
+     *
+     * @param spfx the source prefix
+     * @param gpfx the group prefix
+     */
+    public void removeRoute(IpPrefix spfx, IpPrefix gpfx) {
+
+        /**
+         * If a group route (*, g) does not exist we will need to make so we
+         * can start attaching our sources to the group entry.
+         */
+        McastRouteGroup group = findMcastGroup(gpfx);
+        if (group == null) {
+            // The group does not exist, we can't remove it.
+            return;
+        }
+
+        /**
+         * If the source prefix length is 0 then we have a (*, g) entry, which
+         * means we will remove this group and all of it's sources. We will
+         * also withdraw it's intent if need be.
+         */
+        if (spfx.prefixLength() > 0) {
+            group.removeSource(spfx);
+
+            /*
+             * Now a little house keeping. If this group has no more sources
+             * nor egress connectPoints git rid of it.
+             */
+            if (group.getSources().size() == 0 &&
+                    group.getEgressPoints().size() == 0) {
+                removeGroup(group);
+            }
+
+        } else {
+            // Group remove has been explicitly requested.
+            group.removeSources();
+            group.withdrawIntent();
+            removeGroup(group);
+        }
+    }
+
+    /**
+     * Find the specific multicast group entry.
+     *
+     * @param group the group address
+     * @return McastRouteGroup the multicast (*, G) group route
+     */
+    public McastRouteGroup findMcastGroup(IpPrefix group) {
+        McastRouteGroup g = null;
+        if (group.isIp4()) {
+            g = mrib4.get(group);
+        } else if (group.isIp6() && ipv6Enabled) {
+                g = mrib6.get(group);
+        }
+        return g;
+    }
+
+    /**
+     * Find the multicast (S, G) entry if it exists.
+     *
+     * @param saddr the source address
+     * @param gaddr the group address
+     * @return The multicast source route entry if it exists, null if it does not.
+     */
+    public McastRouteSource findMcastSource(IpPrefix saddr, IpPrefix gaddr) {
+        McastRouteGroup grp = findMcastGroup(checkNotNull(gaddr));
+        if (grp == null) {
+            return null;
+        }
+        return grp.findSource(saddr);
+    }
+
+    /**
+     * This will first look up a Group entry. If no group entry was found null will
+     * be returned. If the group entry has been found we will then look up the (s, g) entry.
+     * If the (s, g) entry has been found, that will be returned.  If no (s, g) was found
+     * the (*, g) group entry will be returned.
+     *
+     * @param saddr the source address
+     * @param gaddr the group address
+     * @return return the best matching McastRouteSource or McastRouteGroup
+     */
+    public McastRoute findBestMatch(IpPrefix saddr, IpPrefix gaddr) {
+        McastRouteGroup grp = this.findMcastGroup(checkNotNull(gaddr));
+        if (grp == null) {
+            return null;
+        }
+
+        // Found a group now look for a source
+        McastRouteSource src = grp.findSource(checkNotNull(saddr));
+        if (src == null) {
+            return grp;
+        }
+
+        return src;
+    }
+
+    /**
+     * Print out the multicast route table in it's entirety.
+     *
+     * TODO: Eventually we will have to implement paging and how to handle large tables.
+     * @return String
+     */
+    public String printMcastRouteTable() {
+        String out = this.toString() + "\n";
+
+        for (McastRouteGroup grp : mrib4.values()) {
+            out += grp.toString() + "\n";
+            for (McastRouteSource src : grp.getSources().values()) {
+                out += src.toString() + "\n";
+            }
+        }
+        return out;
+    }
+
+    /**
+     * Print out a summary of groups in the MRIB.
+     *
+     * @return String
+     */
+    public String toString() {
+        String out = "Mcast Route Table: ";
+        out += mrib4.size() + " IPv4 Multicast Groups\n";
+        if (ipv6Enabled) {
+            out += mrib6.size() + " IPv6 Multicast Groups\n";
+        }
+        return out;
+    }
+}
diff --git a/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/package-info.java b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/package-info.java
new file mode 100644
index 0000000..eaef5fc
--- /dev/null
+++ b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Sample Multicast forwarding framework using intents.
+ */
+package org.onosproject.mfwd.impl;
diff --git a/apps/mfwd/src/main/java/org/onosproject/mfwd/rest/McastResource.java b/apps/mfwd/src/main/java/org/onosproject/mfwd/rest/McastResource.java
new file mode 100644
index 0000000..878e21d
--- /dev/null
+++ b/apps/mfwd/src/main/java/org/onosproject/mfwd/rest/McastResource.java
@@ -0,0 +1,72 @@
+/*

+ * Copyright 2015 Open Networking Laboratory

+ *

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *     http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+package org.onosproject.mfwd.rest;

+

+import com.fasterxml.jackson.databind.JsonNode;

+import com.fasterxml.jackson.databind.ObjectMapper;

+

+import java.io.IOException;

+import java.io.InputStream;

+import java.util.Map;

+

+import javax.ws.rs.Consumes;

+import javax.ws.rs.GET;

+import javax.ws.rs.POST;

+import javax.ws.rs.Path;

+import javax.ws.rs.Produces;

+import javax.ws.rs.core.MediaType;

+import javax.ws.rs.core.Response;

+

+import org.onlab.packet.IpPrefix;

+import org.onlab.rest.BaseResource;

+import org.onosproject.mfwd.impl.McastRouteGroup;

+import org.onosproject.mfwd.impl.McastRouteTable;

+

+/**

+ * Rest API for Multicast Forwarding.

+ */

+@Path("mcast")

+public class McastResource extends BaseResource {

+

+    /**

+     * Retrieve the multicast route table.

+     * @return the multicast route table.

+     * @throws IOException if an error occurs

+     */

+    @GET

+    @Produces(MediaType.APPLICATION_JSON)

+    public Response showAll() throws IOException {

+        ObjectMapper mapper = new ObjectMapper();

+        McastRouteTable mcastRouteTable = McastRouteTable.getInstance();

+        Map<IpPrefix, McastRouteGroup> map = mcastRouteTable.getMrib4();

+        return Response.ok(mapper.createObjectNode().toString()).build();

+    }

+

+    /**

+     * Static join of a multicast flow.

+     * @param input source, group, ingress connectPoint egress connectPoints

+     * @return status of static join

+     * @throws IOException if an error occurs

+     */

+    @POST

+    @Consumes(MediaType.APPLICATION_JSON)

+    @Produces(MediaType.APPLICATION_JSON)

+    public Response join(InputStream input) throws IOException {

+        ObjectMapper mapper = new ObjectMapper();

+        JsonNode cfg = mapper.readTree(input);

+        return null;

+    }

+}

diff --git a/apps/mfwd/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/mfwd/src/main/resources/OSGI-INF/blueprint/shell-config.xml
new file mode 100644
index 0000000..966cb4f
--- /dev/null
+++ b/apps/mfwd/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -0,0 +1,30 @@
+<!--

+  ~ Copyright 2014 Open Networking Laboratory

+  ~

+  ~ Licensed under the Apache License, Version 2.0 (the "License");

+  ~ you may not use this file except in compliance with the License.

+  ~ You may obtain a copy of the License at

+  ~

+  ~     http://www.apache.org/licenses/LICENSE-2.0

+  ~

+  ~ Unless required by applicable law or agreed to in writing, software

+  ~ distributed under the License is distributed on an "AS IS" BASIS,

+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+  ~ See the License for the specific language governing permissions and

+  ~ limitations under the License.

+  -->

+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">

+

+    <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">

+        <command>

+            <action class="org.onosproject.mfwd.cli.McastJoinCommand"/>

+        </command>

+        <command>

+            <action class="org.onosproject.mfwd.cli.McastDeleteCommand"/>

+        </command>

+        <command>

+            <action class="org.onosproject.mfwd.cli.McastShowCommand"/>

+        </command>

+    </command-bundle>

+

+</blueprint>

diff --git a/apps/mfwd/src/main/webapp/WEB-INF/web.xml b/apps/mfwd/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..c4c4f45
--- /dev/null
+++ b/apps/mfwd/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+~ Copyright 2014 Open Networking Laboratory
+~
+~ Licensed under the Apache License, Version 2.0 (the "License");
+~ you may not use this file except in compliance with the License.
+~ You may obtain a copy of the License at
+~
+~     http://www.apache.org/licenses/LICENSE-2.0
+~
+~ Unless required by applicable law or agreed to in writing, software
+~ distributed under the License is distributed on an "AS IS" BASIS,
+~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+~ See the License for the specific language governing permissions and
+~ limitations under the License.
+-->
+<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
+         xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+         id="ONOS" version="2.5">
+    <display-name>ONOS APP MFWD</display-name>
+
+    <servlet>
+        <servlet-name>JAX-RS Service</servlet-name>
+        <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
+        <init-param>
+            <param-name>com.sun.jersey.config.property.resourceConfigClass</param-name>
+            <param-value>com.sun.jersey.api.core.ClassNamesResourceConfig</param-value>
+        </init-param>
+        <init-param>
+            <param-name>com.sun.jersey.config.property.classnames</param-name>
+            <param-value>
+                org.onosproject.mfwd.rest.McastResource
+            </param-value>
+        </init-param>
+        <load-on-startup>1</load-on-startup>
+    </servlet>
+
+    <servlet-mapping>
+        <servlet-name>JAX-RS Service</servlet-name>
+        <url-pattern>/*</url-pattern>
+    </servlet-mapping>
+
+</web-app>
diff --git a/apps/pim/pom.xml b/apps/pim/pom.xml
new file mode 100644
index 0000000..d28423c
--- /dev/null
+++ b/apps/pim/pom.xml
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2015 Open Networking Laboratory
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.onosproject</groupId>
+        <artifactId>onos-apps</artifactId>
+        <version>1.4.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>onos-app-pim</artifactId>
+    <packaging>bundle</packaging>
+
+    <description>Protocol Independent Multicast Emulation</description>
+
+    <properties>
+        <onos.app.name>org.onosproject.pim</onos.app.name>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-cli</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onlab-osgi</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onlab-junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <!-- This is needed by ComponentContext, used for tunable configuration -->
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.karaf.shell</groupId>
+            <artifactId>org.apache.karaf.shell.console</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.scr.annotations</artifactId>
+            <version>1.9.8</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Bundle-SymbolicName>
+                            ${project.groupId}.${project.artifactId}
+                        </Bundle-SymbolicName>
+                        <Import-Package>
+                            org.slf4j,
+                            org.osgi.framework,
+                            org.apache.commons.lang.math.*,
+                            com.google.common.*,
+                            org.onlab.packet.*,
+                            org.onlab.rest.*,
+                            org.onosproject.*,
+                            org.onosproject.mfwd.impl.*;
+                            org.onlab.util.*,
+                            org.jboss.netty.util.*
+                        </Import-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>2.5.1</version>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/apps/pim/src/main/java/org/onosproject/pim/cli/PIMShowCommand.java b/apps/pim/src/main/java/org/onosproject/pim/cli/PIMShowCommand.java
new file mode 100644
index 0000000..5effe45
--- /dev/null
+++ b/apps/pim/src/main/java/org/onosproject/pim/cli/PIMShowCommand.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.pim.cli;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.pim.impl.PIMNeighbors;
+import org.onosproject.pim.impl.PIMNeighborsCodec;
+
+import java.util.HashMap;
+
+@Command(scope = "onos", name = "pim-show", description = "Displays the pim neighbors")
+public class PIMShowCommand extends AbstractShellCommand {
+
+    // prints either the json or cli version of the hash map connect point
+    // neighbors from the PIMNeighbors class.
+    @Override
+    protected  void execute() {
+        // grab connect point neighbors hash map to send in to json encoder.
+        HashMap<ConnectPoint, PIMNeighbors> pimNbrs = PIMNeighbors.getConnectPointNeighbors();
+        if (outputJson()) {
+            print("%s", json(pimNbrs));
+        } else {
+            print(PIMNeighbors.printPimNeighbors());
+        }
+    }
+
+    private JsonNode json(HashMap<ConnectPoint, PIMNeighbors> pimNbrs) {
+        return new PIMNeighborsCodec().encode(pimNbrs, this);
+    }
+
+}
\ No newline at end of file
diff --git a/apps/pim/src/main/java/org/onosproject/pim/cli/package-info.java b/apps/pim/src/main/java/org/onosproject/pim/cli/package-info.java
new file mode 100644
index 0000000..954dacb
--- /dev/null
+++ b/apps/pim/src/main/java/org/onosproject/pim/cli/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * PIM Multicast forwarding framework using intents.
+ */
+package org.onosproject.pim.cli;
\ No newline at end of file
diff --git a/apps/pim/src/main/java/org/onosproject/pim/impl/PIMComponent.java b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMComponent.java
new file mode 100644
index 0000000..bd5e148
--- /dev/null
+++ b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMComponent.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.pim.impl;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+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.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.PIM;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketPriority;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.slf4j.Logger;
+
+/**
+ * Protocol Independent Multicast Emulation.
+ */
+@Component(immediate = true)
+public class PIMComponent {
+    private final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    private PIMPacketProcessor processor = new PIMPacketProcessor();
+    private static ApplicationId appId;
+
+    @Activate
+    public void activate() {
+        appId = coreService.registerApplication("org.onosproject.pim");
+
+        packetService.addProcessor(processor, PacketProcessor.director(1));
+
+        // Build a traffic selector for all multicast traffic
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+        selector.matchEthType(Ethernet.TYPE_IPV4);
+        selector.matchIPProtocol(IPv4.PROTOCOL_PIM);
+        packetService.requestPackets(selector.build(), PacketPriority.REACTIVE, appId);
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        packetService.removeProcessor(processor);
+        processor = null;
+        log.info("Stopped");
+    }
+
+    /**
+     * Packet processor responsible for handling IGMP packets.
+     */
+    private class PIMPacketProcessor implements PacketProcessor {
+
+        @Override
+        public void process(PacketContext context) {
+            // Stop processing if the packet has been handled, since we
+            // can't do any more to it.
+            if (context.isHandled()) {
+                return;
+            }
+
+            InboundPacket pkt = context.inPacket();
+            if (pkt == null) {
+                return;
+            }
+
+            Ethernet ethPkt = pkt.parsed();
+            if (ethPkt == null) {
+                return;
+            }
+
+            /*
+             * IPv6 MLD packets are handled by ICMP6. We'll only deal
+             * with IPv4.
+             */
+            if (ethPkt.getEtherType() != Ethernet.TYPE_IPV4) {
+                return;
+            }
+
+            IPv4 ip = (IPv4) ethPkt.getPayload();
+            IpAddress gaddr = IpAddress.valueOf(ip.getDestinationAddress());
+            IpAddress saddr = Ip4Address.valueOf(ip.getSourceAddress());
+            log.debug("Packet (" + saddr.toString() + ", " + gaddr.toString() +
+                    "\tingress port: " + context.inPacket().receivedFrom().toString());
+
+            if (ip.getProtocol() != IPv4.PROTOCOL_PIM) {
+                log.debug("PIM Picked up a non PIM packet: IP protocol: " + ip.getProtocol());
+                return;
+            }
+
+            // TODO: check incoming to be PIM.PIM_ADDRESS or "Our" address.
+            IpPrefix spfx = IpPrefix.valueOf(saddr, 32);
+            IpPrefix gpfx = IpPrefix.valueOf(gaddr, 32);
+
+            PIM pim = (PIM) ip.getPayload();
+            switch (pim.getPimMsgType()) {
+
+                case PIM.TYPE_HELLO:
+                    PIMNeighbors.processHello(ethPkt, context.inPacket().receivedFrom());
+                    break;
+
+                case PIM.TYPE_JOIN_PRUNE_REQUEST:
+                    // Create the function
+                    break;
+
+                case PIM.TYPE_ASSERT:
+                case PIM.TYPE_BOOTSTRAP:
+                case PIM.TYPE_CANDIDATE_RP_ADV:
+                case PIM.TYPE_GRAFT:
+                case PIM.TYPE_GRAFT_ACK:
+                case PIM.TYPE_REGISTER:
+                case PIM.TYPE_REGISTER_STOP:
+                    log.debug("Unsupported PIM message type: " + pim.getPimMsgType());
+                    break;
+
+                default:
+                    log.debug("Unkown PIM message type: " + pim.getPimMsgType());
+                    break;
+            }
+        }
+    }
+}
diff --git a/apps/pim/src/main/java/org/onosproject/pim/impl/PIMNeighbor.java b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMNeighbor.java
new file mode 100644
index 0000000..1a96138
--- /dev/null
+++ b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMNeighbor.java
@@ -0,0 +1,340 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in reliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.pim.impl;
+
+import org.jboss.netty.util.Timeout;
+import org.jboss.netty.util.TimerTask;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.pim.PIMHello;
+import org.onlab.packet.pim.PIMHelloOption;
+import org.onosproject.net.ConnectPoint;
+import org.slf4j.Logger;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.TimeUnit;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * PIMNeighbor represents all the PIM routers that have sent us
+ * hello messages, or that possibly have been statically configured.
+ */
+public class PIMNeighbor {
+    private final Logger log = getLogger(getClass());
+
+    // The primary address of this PIM neighbor
+    private IpAddress primaryAddr;
+
+    // The MacAddress of this neighbor
+    private MacAddress macAddress;
+
+    // The ConnectPoint this PIM neighbor is connected to.
+    private ConnectPoint connectPoint;
+
+    // Is this neighbor us?
+    private boolean isThisUs = false;
+
+    // The option values this neighbor has sent us.
+    private int priority = 0;
+    private int genId = 0;
+    private short holdtime = 0;
+
+    // Is this pim neighbor the DR?
+    private boolean isDr = false;
+
+    // Timeout for this neighbor
+    private volatile Timeout timeout;
+
+    private boolean reelect = false;
+
+    // A back pointer the neighbors list this neighbor belongs to.
+    private PIMNeighbors neighbors;
+
+    /**
+     * Construct this neighbor from the address and connect point.
+     *
+     * @param ipaddr IP Address of neighbor
+     * @param macaddr MAC Address of the neighbor
+     * @param cp The ConnectPoint of this neighbor
+     */
+    public PIMNeighbor(IpAddress ipaddr, MacAddress macaddr, ConnectPoint cp) {
+        this.macAddress = macaddr;
+        this.primaryAddr = ipaddr;
+        this.connectPoint = cp;
+        this.resetTimeout();
+    }
+
+    /**
+     * Get the primary address of this neighbor.
+     *
+     * @return the primary IP address.
+     */
+    public IpAddress getPrimaryAddr() {
+        return primaryAddr;
+    }
+
+    /**
+     * Set the primary address of this neighbor.
+     *
+     * @param primaryAddr the address we'll use when sending hello messages
+     */
+    public void setPrimaryAddr(IpAddress primaryAddr) {
+        this.primaryAddr = primaryAddr;
+    }
+
+    /**
+     * Get the priority this neighbor has advertised to us.
+     *
+     * @return the priority
+     */
+    public int getPriority() {
+        return priority;
+    }
+
+    /**
+     * Set the priority for this neighbor.
+     *
+     * @param priority This neighbors priority.
+     */
+    public void setPriority(int priority) {
+        this.priority = priority;
+    }
+
+    /**
+     * Get the generation ID.
+     *
+     * @return the generation ID.
+     */
+    public int getGenId() {
+        return genId;
+    }
+
+    /**
+     * Set the generation ID.
+     *
+     * @param genId the generation ID.
+     */
+    public void setGenId(int genId) {
+        this.genId = genId;
+    }
+
+    /**
+     * Get the holdtime for this neighbor.
+     *
+     * @return the holdtime
+     */
+    public short getHoldtime() {
+        return holdtime;
+    }
+
+    /**
+     * Set the holdtime for this neighbor.
+     *
+     * @param holdtime the holdtime.
+     */
+    public void setholdtime(short holdtime) {
+        this.holdtime = holdtime;
+    }
+
+    /**
+     * Is this neighbor the designated router on this connect point?
+     *
+     * @return true if so, false if not.
+     */
+    public boolean isDr() {
+        return isDr;
+    }
+
+    /**
+     * Set this router as the designated router on this connect point.
+     *
+     * @param isDr True is this neighbor is the DR false otherwise
+     */
+    public void setIsDr(boolean isDr) {
+        this.isDr = isDr;
+    }
+
+    /**
+     * The ConnectPoint this neighbor is connected to.
+     *
+     * @return the ConnectPoint
+     */
+    public ConnectPoint getConnectPoint() {
+        return connectPoint;
+    }
+
+    /**
+     * Set the ConnectPoint this router is connected to.
+     *
+     * @param connectPoint the ConnectPoint this router is connected to.
+     */
+    public void setConnectPoint(ConnectPoint connectPoint) {
+        this.connectPoint = connectPoint;
+    }
+
+    /**
+     * Set a back pointer to the neighbors list this neighbor is a member of.
+     *
+     * @param neighbors the neighbor list this neighbor belongs to
+     */
+    public void setNeighbors(PIMNeighbors neighbors) {
+        this.neighbors = neighbors;
+    }
+
+    /**
+     * We have received a fresh hello from a neighbor, now we need to process it.
+     * Depending on the values received in the the hello options may force a
+     * re-election process.
+     *
+     * We will also refresh the timeout for this neighbor.
+     *
+     * @param hello copy of the hello we'll be able to extract options from.
+     */
+    public void refresh(PIMHello hello) {
+        checkNotNull(hello);
+
+        for (PIMHelloOption opt : hello.getOptions().values()) {
+
+            int len = opt.getOptLength();
+            byte [] value = new byte[len];
+            ByteBuffer bb = ByteBuffer.wrap(value);
+
+            switch (opt.getOptType()) {
+                case PIMHelloOption.OPT_GENID:
+                    int newid = bb.getInt();
+                    if (this.genId != newid) {
+                        // TODO: we have a newly rebooted neighbor.  Send them our joins.
+                        this.genId = newid;
+                    }
+                    break;
+
+                case PIMHelloOption.OPT_PRIORITY:
+                    int newpri = bb.getInt();
+                    if (this.priority != newpri) {
+
+                        // The priorities have changed.  We may need to re-elect a new DR?
+                        if (this.isDr || this.neighbors.getDesignatedRouter().getPriority() < priority) {
+                            reelect = true;
+                        }
+                        this.priority = newpri;
+                    }
+                    break;
+
+                case PIMHelloOption.OPT_HOLDTIME:
+                    short holdtime = bb.getShort();
+                    if (this.holdtime != holdtime) {
+                        this.holdtime = holdtime;
+                        if (holdtime == 0) {
+                            // We have a neighbor going down.  We can remove all joins
+                            // we have learned from them.
+                            // TODO: What else do we need to do when a neighbor goes down?
+
+                            log.debug("PIM Neighbor has timed out: {}", this.primaryAddr.toString());
+                            return;
+                        }
+                    }
+                    break;
+
+                case PIMHelloOption.OPT_PRUNEDELAY:
+                case PIMHelloOption.OPT_ADDRLIST:
+                    // TODO: implement prune delay and addr list.  Fall through for now.
+
+                default:
+                    log.debug("PIM Hello option type: {} not yet supported or unknown.", opt.getOptType());
+                    break;
+            }
+        }
+
+        if (reelect) {
+            this.neighbors.electDR(this);
+        }
+
+        // Reset the next timeout timer
+        this.resetTimeout();
+    }
+
+    /* --------------------------------------- Timer functions -------------------------- */
+
+    /**
+     * Restart the timeout task for this neighbor.
+     */
+    private void resetTimeout() {
+
+        if (this.holdtime == 0) {
+
+            // Prepare to die.
+            log.debug("shutting down timer for nbr {}", this.primaryAddr.toString());
+            if (this.timeout != null) {
+                this.timeout.cancel();
+                this.timeout = null;
+            }
+            return;
+        }
+
+        // Cancel the existing timeout and start a fresh new one.
+        if (this.timeout != null) {
+            this.timeout.cancel();
+        }
+
+        this.timeout = PIMTimer.getTimer().newTimeout(new NeighborTimeoutTask(this), holdtime, TimeUnit.SECONDS);
+    }
+
+    /**
+     * The task to run when a neighbor timeout expires.
+     */
+    private final class NeighborTimeoutTask implements TimerTask {
+        PIMNeighbor nbr;
+
+        NeighborTimeoutTask(PIMNeighbor nbr) {
+            this.nbr = nbr;
+        }
+
+        @Override
+        public void run(Timeout timeout) throws Exception {
+
+            // TODO: log.debug;
+            PIMNeighbors neighbors = nbr.neighbors;
+            neighbors.removeNeighbor(nbr.getPrimaryAddr());
+        }
+    }
+
+    /**
+     * Stop the timeout timer.
+     *
+     * This happens when we remove the neighbor.
+     */
+    private final void stopTimeout() {
+        this.timeout.cancel();
+        this.timeout = null;
+    }
+
+    @Override
+    public String toString() {
+        String out = "";
+        if (this.isDr) {
+            out += "*NBR:";
+        } else {
+            out += "NBR:";
+        }
+        out += "\tIP: " + this.primaryAddr.toString();
+        out += "\tPr: " + String.valueOf(this.priority);
+        out += "\tHoldTime: " + String.valueOf(this.holdtime);
+        out += "\tGenID: " + String.valueOf(this.genId) + "\n";
+        return out;
+    }
+}
\ No newline at end of file
diff --git a/apps/pim/src/main/java/org/onosproject/pim/impl/PIMNeighbors.java b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMNeighbors.java
new file mode 100644
index 0000000..cad9076
--- /dev/null
+++ b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMNeighbors.java
@@ -0,0 +1,395 @@
+
+package org.onosproject.pim.impl;
+
+import org.jboss.netty.util.Timeout;
+import org.jboss.netty.util.TimerTask;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.PIM;
+import org.onlab.packet.pim.PIMHello;
+import org.onosproject.net.ConnectPoint;
+import java.util.HashMap;
+import java.util.concurrent.TimeUnit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * PIMNeighbors is a collection of all neighbors we have received
+ * PIM hello messages from.  The main structure is a HashMap indexed
+ * by ConnectPoint with another HashMap indexed on the PIM neighbors
+ * IPAddress, it contains all PIM neighbors attached on that ConnectPoint.
+ */
+public final class PIMNeighbors {
+
+    private static Logger log = LoggerFactory.getLogger("PIMNeighbors");
+
+    /**
+     * This is the global container for all PIM neighbors indexed by ConnectPoints.
+     *
+     * NOTE: We'll have a problem if the same neighbor can show up on two interfaces
+     * but that should never happen.
+     */
+    private static HashMap<ConnectPoint, PIMNeighbors> connectPointNeighbors = new HashMap<>();
+
+    // The connect point these neighbors are connected to.
+    private ConnectPoint connectPoint;
+
+    // Pointer to the current designated router on this ConnectPoint.
+    private PIMNeighbor designatedRouter;
+
+    // The list of neighbors we have learned on this ConnectPoint.
+    private HashMap<IpAddress, PIMNeighbor> neighbors = new HashMap<>();
+
+    /*
+     * TODO: turn ourIpAddress, ourPriority and OurHoldTime into config options.
+     */
+    // The IP address we are using to source our PIM hello messages on this connect Point.
+    private IpAddress ourIpAddress;
+
+    // The priority we use on this ConnectPoint.
+    private int ourPriority = 1;
+
+    // The holdtime we are sending out.
+    private int ourHoldtime = 105;
+
+    // Then generation ID we are sending out. 0 means we need to generate a new random ID
+    private int ourGenid = 0;
+
+    // Hello Timer for sending hello messages per ConnectPoint with neighbors.
+    private volatile Timeout helloTimer;
+
+    // The period of which we will be sending out PIM hello messages.
+    private final int defaultPimHelloInterval = 30; // seconds
+
+    /**
+     * Create PIMNeighbors object per ConnectPoint.
+     *
+     * @param cp the ConnectPoint.
+     * @return PIMNeighbors structure
+     */
+    public static PIMNeighbors getConnectPointNeighbors(ConnectPoint cp) {
+        return connectPointNeighbors.get(cp);
+    }
+
+    /**
+     * Process incoming hello message, we will need the Macaddress and IP address of the sender.
+     *
+     * @param ethPkt the ethernet header
+     * @param receivedFrom the connect point we recieved this message from
+     */
+    public static void processHello(Ethernet ethPkt, ConnectPoint receivedFrom) {
+        checkNotNull(ethPkt);
+        checkNotNull(ethPkt);
+
+        MacAddress srcmac = ethPkt.getSourceMAC();
+        IPv4 ip = (IPv4) ethPkt.getPayload();
+        Ip4Address srcip = Ip4Address.valueOf(ip.getSourceAddress());
+
+        PIM pim = (PIM) ip.getPayload();
+        checkNotNull(pim);
+
+        PIMHello hello = (PIMHello) pim.getPayload();
+        checkNotNull(hello);
+
+        PIMNeighbor nbr = PIMNeighbors.findOrCreate(srcip, srcmac, receivedFrom);
+        if (nbr == null) {
+            log.error("Could not create a neighbor for: {1}", srcip.toString());
+            return;
+        }
+
+        nbr.setConnectPoint(receivedFrom);
+        nbr.refresh(hello);
+    }
+
+    /**
+     * Create a PIM Neighbor.
+     *
+     * @param cp The ConnectPoint this neighbor was found on
+     */
+    public PIMNeighbors(ConnectPoint cp) {
+        this.connectPoint = cp;
+
+        // TODO: use network config to assign address.
+        this.ourIpAddress = IpAddress.valueOf("10.2.2.2");
+        this.addIpAddress(this.ourIpAddress);
+    }
+
+    /**
+     * Create a PIM neighbor.
+     *
+     * @param cp the ConnectPoint this neighbor was found on
+     * @param ourIp the IP address of this neighbor
+     */
+    public PIMNeighbors(ConnectPoint cp, IpAddress ourIp) {
+        this.connectPoint = cp;
+        this.addIpAddress(ourIp);
+    }
+
+    /**
+     * Start the hello timer when we have been given an IP address.
+     *
+     * @param ourIp our IP address.
+     */
+    public void addIpAddress(IpAddress ourIp) {
+        this.startHelloTimer();
+
+        // Kick off the first pim hello packet
+        this.sendHelloPacket();
+    }
+
+    /**
+     * Getter for our IP address.
+     *
+     * @return our IP address.
+     */
+    public IpAddress getOurIpAddress() {
+        return this.ourIpAddress;
+    }
+
+    /**
+     * Get our priority.
+     *
+     * @return our priority.
+     */
+    public int getOurPriority() {
+        return this.ourPriority;
+    }
+
+    /**
+     * Get the neighbor list for this specific connectPoint.
+     *
+     * @return PIM neighbors on this ConnectPoint
+     */
+    public HashMap<IpAddress, PIMNeighbor> getOurNeighborsList() {
+        return this.neighbors;
+    }
+
+    /**
+     * Get the designated router on this connection.
+     *
+     * @return the PIMNeighbor representing the DR
+     */
+    public PIMNeighbor getDesignatedRouter() {
+        return designatedRouter;
+    }
+
+    /**
+     * Are we the DR on this CP?
+     *
+     * @return true if we are, false if not
+     */
+    public boolean weAreTheDr() {
+        return (designatedRouter != null &&
+                designatedRouter.getPrimaryAddr().equals(ourIpAddress));
+    }
+
+    /**
+     * Find the neighbor with the given IP address on this CP.
+     *
+     * @param ipaddr the IP address of the neighbor we are interested in
+     * @return the pim neighbor if it exists
+     */
+    public PIMNeighbor findNeighbor(IpAddress ipaddr) {
+        PIMNeighbor nbr = neighbors.get(ipaddr);
+        return nbr;
+    }
+
+    /**
+     * Add a new PIM neighbor to this list.
+     *
+     * @param nbr the neighbor to be added.
+     */
+    public void addNeighbor(PIMNeighbor nbr) {
+        if (neighbors.containsKey(nbr.getPrimaryAddr())) {
+
+            // TODO: Hmmm, how should this be handled?
+            log.debug("We are adding a neighbor that already exists: {}", nbr.toString());
+            neighbors.remove(nbr.getPrimaryAddr(), nbr);
+        }
+        nbr.setNeighbors(this);
+        neighbors.put(nbr.getPrimaryAddr(), nbr);
+    }
+
+    /**
+     * Remove the neighbor from our neighbor list.
+     *
+     * @param ipaddr the IP address of the neighbor to remove
+     */
+    public void removeNeighbor(IpAddress ipaddr) {
+
+        boolean reelect = (designatedRouter == null || designatedRouter.getPrimaryAddr().equals(ipaddr));
+        if (neighbors.containsKey(ipaddr)) {
+            neighbors.remove(ipaddr);
+        }
+        this.electDR();
+    }
+
+    /**
+     * Remove the given neighbor from the neighbor list.
+     *
+     * @param nbr the nbr to be removed.
+     */
+    public void removeNeighbor(PIMNeighbor nbr) {
+
+        boolean reelect = (designatedRouter == null || nbr.isDr());
+        neighbors.remove(nbr.getPrimaryAddr(), nbr);
+        this.electDR();
+    }
+
+    /**
+     * Elect a new DR on this ConnectPoint.
+     *
+     * @return the PIM Neighbor that wins
+     */
+    public PIMNeighbor electDR() {
+
+        for (PIMNeighbor nbr : this.neighbors.values()) {
+            if (this.designatedRouter == null) {
+                this.designatedRouter = nbr;
+                continue;
+            }
+
+            if (nbr.getPriority() > this.designatedRouter.getPriority()) {
+                this.designatedRouter = nbr;
+                continue;
+            }
+
+            // We could sort in ascending order
+            if (this.designatedRouter.getPrimaryAddr().compareTo(nbr.getPrimaryAddr()) > 0) {
+                this.designatedRouter = nbr;
+                continue;
+            }
+        }
+
+        return this.designatedRouter;
+    }
+
+    /**
+     * Elect a new DR given the new neighbor.
+     *
+     * @param nbr the new neighbor to use in DR election.
+     * @return the PIM Neighbor that wins DR election
+     */
+    public PIMNeighbor electDR(PIMNeighbor nbr) {
+
+        // Make sure I have
+        if (this.designatedRouter == null ||
+                this.designatedRouter.getPriority() < nbr.getPriority() ||
+                this.designatedRouter.getPrimaryAddr().compareTo(nbr.getPrimaryAddr()) > 0) {
+            this.designatedRouter = nbr;
+        }
+        return this.designatedRouter;
+    }
+
+    /**
+     * Find or create a pim neighbor with a given ip address and connect point.
+     *
+     * @param ipaddr of the pim neighbor
+     * @param mac The mac address of our sending neighbor
+     * @param cp the connect point the neighbor was learned from
+     * @return an existing or new PIM neighbor
+     */
+    public static PIMNeighbor findOrCreate(IpAddress ipaddr, MacAddress mac, ConnectPoint cp) {
+        PIMNeighbors neighbors = connectPointNeighbors.get(cp);
+        if (neighbors == null) {
+            neighbors = new PIMNeighbors(cp);
+            connectPointNeighbors.put(cp, neighbors);
+        }
+
+        PIMNeighbor nbr = neighbors.findNeighbor(ipaddr);
+        if (nbr == null) {
+            nbr = new PIMNeighbor(ipaddr, mac, cp);
+            neighbors.addNeighbor(nbr);
+            neighbors.electDR(nbr);
+        }
+        return nbr;
+    }
+
+    // Returns the connect point neighbors hash map
+    public static HashMap<ConnectPoint, PIMNeighbors> getConnectPointNeighbors() {
+        return connectPointNeighbors;
+    }
+
+    /* ---------------------------------- PIM Hello Timer ----------------------------------- */
+
+    /**
+     * Start a new hello timer for this ConnectPoint.
+     */
+    private void startHelloTimer() {
+        this.helloTimer = PIMTimer.getTimer().newTimeout(
+                new HelloTimer(this),
+                this.defaultPimHelloInterval,
+                TimeUnit.SECONDS);
+
+        log.trace("Started Hello Timer: " + this.ourIpAddress.toString());
+    }
+
+    /**
+     * This inner class handles transmitting a PIM hello message on this ConnectPoint.
+     */
+    private final class HelloTimer implements TimerTask {
+        PIMNeighbors neighbors;
+
+        HelloTimer(PIMNeighbors neighbors) {
+            this.neighbors = neighbors;
+        }
+
+        @Override
+        public void run(Timeout timeout) throws Exception {
+
+            // Send off a hello packet
+            sendHelloPacket();
+
+            // restart the hello timer
+            neighbors.startHelloTimer();
+        }
+    }
+
+    private void sendHelloPacket() {
+        PIMHello hello = new PIMHello();
+
+        // TODO: we will need to implement the network config service to assign ip addresses & options
+        /*
+        hello.createDefaultOptions();
+
+        Ethernet eth = hello.createPIMHello(this.ourIpAddress);
+        hello.sendPacket(this.connectPoint);
+        */
+    }
+
+    /**
+     * prints the connectPointNeighbors list with each neighbor list.
+     *
+     * @return string of neighbors.
+     */
+    public static String printPimNeighbors() {
+        String out = "PIM Neighbors Table: \n";
+
+        for (PIMNeighbors pn: connectPointNeighbors.values()) {
+
+            out += "CP:\n " + pn.toString();
+            for (PIMNeighbor nbr : pn.neighbors.values()) {
+                out += "\t" + nbr.toString();
+            }
+        }
+        return out;
+    }
+
+    @Override
+    public String toString() {
+        String out = "PIM Neighbors: ";
+        if (this.ourIpAddress != null) {
+            out += "IP: " + this.ourIpAddress.toString();
+        } else {
+            out += "IP: *Null*";
+        }
+        out += "\tPR: " + String.valueOf(this.ourPriority) + "\n";
+        return out;
+    }
+}
\ No newline at end of file
diff --git a/apps/pim/src/main/java/org/onosproject/pim/impl/PIMNeighborsCodec.java b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMNeighborsCodec.java
new file mode 100644
index 0000000..ee62eb7
--- /dev/null
+++ b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMNeighborsCodec.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.pim.impl;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.ConnectPoint;
+
+import java.util.HashMap;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * PIM neighbors Codec.
+ */
+public class PIMNeighborsCodec extends JsonCodec<HashMap<ConnectPoint, PIMNeighbors>> {
+    // JSON field names
+    //Return Name
+    private static final String CPNBRLIST = "connect_point_list";
+
+    // PIM Neightbors Fields
+    private static final String IP = "ip";
+    private static final String PRIORITY = "priority";
+    private static final String NBRLIST = "neighbor_list";
+
+    // PIM neighbor Files
+    private static final String DR = "designated";
+    private static final String NBR_IP = "ip";
+    private static final String PR = "priority";
+    private static final String HOLDTIME = "hold_time";
+
+    /**
+     * Encode the PIM Neighbors.
+     *
+     * @param cpn ConnectPoint neighbors
+     * @param context encoding context
+     *
+     * @return Encoded neighbors used by CLI and REST
+     */
+    @Override
+    public ObjectNode encode(HashMap<ConnectPoint, PIMNeighbors> cpn, CodecContext context) {
+        checkNotNull(cpn, "Pim Neighbors cannot be null");
+
+        ObjectNode pimNbrJsonCodec = context.mapper().createObjectNode();
+        ArrayNode cpnList = context.mapper().createArrayNode();
+
+        for (PIMNeighbors pn: cpn.values()) {
+            // get the PimNeighbors Obj, contains Neighbors list
+            // create the json object for a single Entry in the Neighbors list
+            ObjectNode cp = context.mapper().createObjectNode();
+            cp.put(IP, pn.getOurIpAddress().toString());
+            cp.put(PRIORITY, String.valueOf(pn.getOurPriority()));
+
+            // create the array for the neighbors list
+            ArrayNode nbrsList = context.mapper().createArrayNode();
+            for (PIMNeighbor nbr : pn.getOurNeighborsList().values()) {
+                nbrsList.add(neighbor(nbr, context));
+            }
+            // adds pim neighbor to list
+            cp.set(NBRLIST, nbrsList);
+            // adds to arraynode which will represent the connect point neighbors hash map.
+            cpnList.add(cp);
+        }
+        pimNbrJsonCodec.set(CPNBRLIST, cpnList);
+        return pimNbrJsonCodec;
+    }
+
+    /**
+     * Encode a single PIM Neighbor.
+     *
+     * @param nbr the neighbor to be encoded
+     * @param context encoding context
+     * @return the encoded neighbor
+     */
+    private ObjectNode neighbor(PIMNeighbor nbr, CodecContext context) {
+        return context.mapper().createObjectNode()
+                .put(DR, Boolean.toString(nbr.isDr()))
+                .put(NBR_IP, nbr.getPrimaryAddr().toString())
+                .put(PR, String.valueOf(nbr.getPriority()))
+                .put(HOLDTIME, String.valueOf(nbr.getHoldtime()));
+    }
+}
diff --git a/apps/pim/src/main/java/org/onosproject/pim/impl/PIMTimer.java b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMTimer.java
new file mode 100644
index 0000000..c131a53
--- /dev/null
+++ b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMTimer.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.pim.impl;
+
+import org.jboss.netty.util.HashedWheelTimer;
+
+/**
+ * PIM Timer used for PIM Neighbors.
+ */
+public final class PIMTimer {
+
+    private static volatile HashedWheelTimer timer;
+
+    // Ban public construction
+    private PIMTimer() {
+    }
+
+    /**
+     * Returns the singleton hashed-wheel timer.
+     *
+     * @return hashed-wheel timer
+     */
+    public static HashedWheelTimer getTimer() {
+        if (PIMTimer.timer == null) {
+            initTimer();
+        }
+        return PIMTimer.timer;
+    }
+
+    // Start the PIM timer.
+    private static synchronized  void initTimer() {
+        if (PIMTimer.timer == null) {
+
+            // Create and start a new hashed wheel timer, if it does not exist.
+            HashedWheelTimer hwTimer = new HashedWheelTimer();
+            hwTimer.start();
+            PIMTimer.timer = hwTimer;
+        }
+    }
+}
diff --git a/apps/pim/src/main/java/org/onosproject/pim/impl/package-info.java b/apps/pim/src/main/java/org/onosproject/pim/impl/package-info.java
new file mode 100644
index 0000000..29d1ce4
--- /dev/null
+++ b/apps/pim/src/main/java/org/onosproject/pim/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * PIM Emulation speak hello messages and listen to Join/Prunes.
+ */
+package org.onosproject.pim.impl;
diff --git a/apps/pim/src/main/resources/OSGI-INF.blueprint/shell-config.xml b/apps/pim/src/main/resources/OSGI-INF.blueprint/shell-config.xml
new file mode 100644
index 0000000..c30e379
--- /dev/null
+++ b/apps/pim/src/main/resources/OSGI-INF.blueprint/shell-config.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright 2015 Open Networking Laboratory
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
+
+    <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
+        <command>
+            <action class="org.onosproject.pim.cli.PIMShowCommand"/>
+        </command>
+    </command-bundle>
+
+</blueprint>
diff --git a/apps/pom.xml b/apps/pom.xml
index 334c690..611ac0b 100644
--- a/apps/pom.xml
+++ b/apps/pom.xml
@@ -58,6 +58,9 @@
         <module>vtnweb</module>
         <module>dhcp</module>
         <module>cordvtn</module>
+        <module>mfwd</module>
+        <module>igmp</module>
+        <module>pim</module>
   </modules>
 
     <properties>