Adding mfwd, igmp and mfwd apps

Change-Id: Ie7187716db36b754e4cd687a8f9de004e27c7825

adding mfwd, pim, igmp apps

Change-Id: Iddd2dcee24dc905d5ff0efe1d1d798fc83a7c736
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>