Merge "Adding explicit DROP instruction due to change in default behavior of DefaultTrafficTreatment.build"
diff --git a/mfwd/pom.xml b/mfwd/pom.xml
new file mode 100644
index 0000000..d2760ce
--- /dev/null
+++ b/mfwd/pom.xml
@@ -0,0 +1,149 @@
+<?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-app-samples</artifactId>
+        <version>1.2.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.onosproject</groupId>
+                <artifactId>onos-maven-plugin</artifactId>
+                <version>1.4-SNAPSHOT</version>
+                <executions>
+                   <execution>
+                      <id>app</id>
+                      <phase>package</phase>
+                      <goals>
+                         <goal>app</goal>
+                      </goals>
+                  </execution>
+                </executions>
+                <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/mfwd/src/main/java/org/onosproject/mfwd/cli/McastJoinCommand.java b/mfwd/src/main/java/org/onosproject/mfwd/cli/McastJoinCommand.java
new file mode 100644
index 0000000..7016c58
--- /dev/null
+++ b/mfwd/src/main/java/org/onosproject/mfwd/cli/McastJoinCommand.java
@@ -0,0 +1,59 @@
+/*

+ * 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.onosproject.mfwd.impl.McastRouteTable;

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

+//import org.onosproject.mfwd.impl.McastRouteSource;

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

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

+import org.onosproject.cli.AbstractShellCommand;

+

+/**

+ * 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 = "sourceIp",

+              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 = "multicastIp",

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

+              required = true, multiValued = false)

+    String maddr = null;

+

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

+            description = "Ingress ports",

+            required = false, multiValued = false)

+    String[] ingressPorts = null;

+

+    @Argument(index = 4, name = "egressPorts",

+              description = "Egress ports",

+              required = false, multiValued = true)

+    String[] egressPorts = null;

+

+    /**

+     * All static multicast joins.

+     * TODO: Make this function work!

+     */

+    @Override

+    protected void execute() {

+    }

+}

diff --git a/mfwd/src/main/java/org/onosproject/mfwd/cli/McastShowCommand.java b/mfwd/src/main/java/org/onosproject/mfwd/cli/McastShowCommand.java
new file mode 100644
index 0000000..cc7db29
--- /dev/null
+++ b/mfwd/src/main/java/org/onosproject/mfwd/cli/McastShowCommand.java
@@ -0,0 +1,44 @@
+/*

+ * 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.mfwd.impl.McastRouteTable;

+

+import static org.slf4j.LoggerFactory.getLogger;

+import org.slf4j.Logger;

+

+/**

+ * 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());

+    private static final String FMT = "(%s, %s) ingress=%s, egress=%s";

+

+    @Override

+    protected void execute() {

+        McastRouteTable mrt = McastRouteTable.getInstance();

+        if (outputJson()) {

+            log.error("Not implemented yet");

+        } else {

+            print(mrt.printMcastRouteTable());

+        }

+    }

+}

+

diff --git a/mfwd/src/main/java/org/onosproject/mfwd/cli/package-info.java b/mfwd/src/main/java/org/onosproject/mfwd/cli/package-info.java
new file mode 100644
index 0000000..7b5ed39
--- /dev/null
+++ b/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/mfwd/src/main/java/org/onosproject/mfwd/impl/McastForwarding.java b/mfwd/src/main/java/org/onosproject/mfwd/impl/McastForwarding.java
new file mode 100644
index 0000000..041efd7
--- /dev/null
+++ b/mfwd/src/main/java/org/onosproject/mfwd/impl/McastForwarding.java
@@ -0,0 +1,245 @@
+/*
+ * 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.intent.SinglePointToMultiPointIntent;
+import org.onosproject.net.intent.IntentService;
+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());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentService intentService;
+
+    private ReactivePacketProcessor processor = new ReactivePacketProcessor();
+    private McastRouteTable mrib;
+    private ApplicationId appId;
+
+    /**
+     * Active MulticastForwardingIntent.
+     */
+    @Activate
+    public void activate() {
+        appId = coreService.registerApplication("org.onosproject.mfwd");
+
+        packetService.addProcessor(processor, PacketProcessor.ADVISOR_MAX + 2);
+
+        // Build a traffic selector for all multicast traffic
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+        selector.matchEthType(Ethernet.TYPE_IPV4);
+        packetService.requestPackets(selector.build(), PacketPriority.REACTIVE, appId);
+        //selector.matchIPDst(IpPrefix.valueOf("224/4"));
+        //packetService.requestPackets(selector.build(), PacketPriority.REACTIVE, appId);
+
+        mrib = McastRouteTable.getInstance();
+        mrib.addStaticRoutes();
+        log.info("Started");
+
+        System.out.print(mrib.printMcastRouteTable());
+    }
+
+    /**
+     * Deactivate Multicast Forwarding Intent.
+     */
+    @Deactivate
+    public void deactivate() {
+        packetService.removeProcessor(processor);
+        processor = null;
+        log.info("Stopped");
+    }
+
+    /**
+     * 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());
+
+
+            // TODO: Add isMulticast to the Ip4Address checks
+            // if ( !gaddr.isMulticast() ) {
+            //    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) {
+
+                /*
+                 * 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;
+            }
+
+            // Otherwise forward and be done with it.
+            setIntent(context, entry);
+
+            // 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);
+        }
+    }
+
+    /**
+     * Install the PointToMultipoint forwarding intent.
+     * @param context packet context
+     * @param mroute multicast route entry
+     */
+    private void setIntent(PacketContext context, McastRoute mroute) {
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+        TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment();
+
+        if (mroute.getIngressPoint() == null ||
+                mroute.getEgressPoints().isEmpty()) {
+            return;
+        }
+
+        /*
+         * 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(appId)
+                        .selector(selector.build())
+                        .treatment(treatment)
+                        .ingressPoint(mroute.getIngressPoint())
+                        .egressPoints(mroute.getEgressPoints()).
+                        build();
+
+        intentService.submit(intent);
+    }
+}
diff --git a/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRoute.java b/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRoute.java
new file mode 100644
index 0000000..1c5f550
--- /dev/null
+++ b/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRoute.java
@@ -0,0 +1,88 @@
+/*
+ * 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 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 {
+
+    /**
+     * get the group addresses.
+     * @return group address
+     */
+    public IpPrefix getGaddr();
+
+    /**
+     * get the source address.
+     * @return the source address
+     */
+    public IpPrefix getSaddr();
+
+    /**
+     * Check for IPv4 or IPv6 as well as (S, G) or (*, G).
+     */
+    public boolean isIp4();
+
+    /**
+     * Add the ingress ConnectPoint with a ConnectPoint.
+     */
+    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();
+
+    /**
+     * Pretty print the the route.
+     * @return a pretty string
+     */
+    public String toString();
+}
\ No newline at end of file
diff --git a/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteBase.java b/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteBase.java
new file mode 100644
index 0000000..6ea9ce2
--- /dev/null
+++ b/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteBase.java
@@ -0,0 +1,212 @@
+/*
+ * 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 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;
+
+    /**
+     * 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
+     */
+    public IpPrefix getGaddr() {
+        return gaddr;
+    }
+
+    /**
+     * Get the multicast source address.
+     * @return the multicast source address
+     */
+    public IpPrefix getSaddr() {
+        return saddr;
+    }
+
+    /**
+     * Is this an IPv4 multicast route.
+     * @return true if it is an IPv4 route
+     */
+    public boolean isIp4() {
+        return gaddr.isIp4();
+    }
+
+    /**
+     * Is this an IPv6 multicast route.
+     * @return true if it is an IPv6 route
+     */
+    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
+     */
+    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
+     */
+    public void addIngressPoint(String deviceId, long portNum) {
+        ingressPoint = new ConnectPoint(
+                DeviceId.deviceId(deviceId),
+                PortNumber.portNumber(portNum));
+    }
+
+    /**
+     * Get the ingress ConnectPoint.
+     * @return the ingress ConnectPoint
+     */
+    public ConnectPoint getIngressPoint() {
+        return this.ingressPoint;
+    }
+
+    /**
+     * Add an egress ConnectPoint.
+     *
+     * @param member member egress connect point
+     */
+    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
+     */
+    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
+     */
+    public Set<ConnectPoint> getEgressPoints() {
+        return egressPoints;
+    }
+
+    /**
+     * Pretty Print this Multicast Route.  Works for McastRouteSource and McastRouteGroup.
+     * @return pretty string of the multicast route
+     */
+    public String toString() {
+        String out = String.format("(%s, %s) ingress: %s ",
+                saddr.toString(), gaddr.toString(),
+                (ingressPoint == null) ? "NULL" : ingressPoint.toString());
+        out += "egress: {";
+        if (egressPoints != null && !egressPoints.isEmpty()) {
+            for (ConnectPoint eg : egressPoints) {
+                out += " " + eg.toString();
+            }
+        }
+        out += ("}\n");
+        return out;
+    }
+}
\ No newline at end of file
diff --git a/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteGroup.java b/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteGroup.java
new file mode 100644
index 0000000..ccf6472
--- /dev/null
+++ b/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteGroup.java
@@ -0,0 +1,84 @@
+/*
+ * 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;
+    }
+
+    /**
+     * 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);
+    }
+
+    /**
+     * 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;
+    }
+}
diff --git a/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteSource.java b/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteSource.java
new file mode 100644
index 0000000..9dd9a3c
--- /dev/null
+++ b/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteSource.java
@@ -0,0 +1,47 @@
+/*
+ * 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/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteTable.java b/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteTable.java
new file mode 100644
index 0000000..01df321
--- /dev/null
+++ b/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteTable.java
@@ -0,0 +1,311 @@
+/*
+ * 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 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.
+ */
+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.
+     * XXX: should we throw an exception if somebody requests the ipv6 address when
+     * it has not been enabled, or just return null as we do now.
+     * @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 storeMcastGroup(McastRouteGroup group) {
+        if (group.isIp4()) {
+            mrib4.put(group.getGaddr(), group);
+        } else {
+            if (ipv6Enabled) {
+                mrib6.put(group.getGaddr(), group);
+            }
+        }
+    }
+
+    /**
+     */
+    /**
+     * Add a multicast route to the MRIB.  This function will.
+     *
+     * TODO: check the addresses to determine if the have a /mask if not add one.
+     *
+     * @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 (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;
+    }
+
+    /**
+     * Save the multicast group in the multicast route table.
+     * @param group the group address
+     */
+    private void storeGroup(McastRouteGroup group) {
+        if (group.isIp4()) {
+            mrib4.put(group.getGaddr(), group);
+        } else {
+
+            if (ipv6Enabled) {
+                mrib6.put(group.getGaddr(), 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 (ipv6Enabled) {
+                g = mrib6.get(group);
+            }
+        }
+        return g;
+    }
+
+    /**
+     * Find the multicast (S, G) entry if it exists.
+     * @param gaddr the group address
+     * @param saddr the source address
+     * @return The multicast source route entry if it exists, null if it does not.
+     */
+    public McastRouteSource findMcastSource(IpPrefix gaddr, IpPrefix saddr) {
+        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;
+    }
+
+    /**
+     * Temporary method to add routes to the mfwd module to test data flow.
+     *
+     * TODO: this function will be removed after we get the cli working.
+     */
+    public void addStaticRoutes() {
+
+        // Hard code group interest for a couple different groups and some receive points.
+        // XXX this will go away as soon as the mcast-join cli command has been added
+        McastRouteBase g1 = this.addRoute("*", "225.1.1.1/32");
+        g1.addIngressPoint("of:0000000000000023", 4);
+        g1.addEgressPoint("of:0000000000000023", 3);
+
+        McastRouteBase s1 = this.addRoute("10.1.1.1/32", "225.1.1.1/32");
+        s1.addIngressPoint("of:0000000000000011", 3);
+        s1.addEgressPoint("of:0000000000000023", 3);
+        s1.addEgressPoint("of:0000000000000023", 4);
+
+        McastRouteBase s2 = this.addRoute("10.1.1.2/32", "226.1.1.1/32");
+        s2.addIngressPoint("of:0000000000000012", 3);
+        s2.addEgressPoint("of:0000000000000023", 4);
+        s2.addEgressPoint("of:0000000000000023", 5);
+    }
+
+    /**
+     * 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/mfwd/src/main/java/org/onosproject/mfwd/impl/package-info.java b/mfwd/src/main/java/org/onosproject/mfwd/impl/package-info.java
new file mode 100644
index 0000000..eaef5fc
--- /dev/null
+++ b/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/mfwd/src/main/java/org/onosproject/mfwd/rest/McastResource.java b/mfwd/src/main/java/org/onosproject/mfwd/rest/McastResource.java
new file mode 100644
index 0000000..878e21d
--- /dev/null
+++ b/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/mfwd/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/mfwd/src/main/resources/OSGI-INF/blueprint/shell-config.xml
new file mode 100644
index 0000000..4048f36
--- /dev/null
+++ b/mfwd/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -0,0 +1,27 @@
+<!--

+  ~ 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.McastShowCommand"/>

+        </command>

+    </command-bundle>

+

+</blueprint>

diff --git a/mfwd/src/main/webapp/WEB-INF/web.xml b/mfwd/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..c4c4f45
--- /dev/null
+++ b/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/oneping/src/main/java/org/onos/oneping/OnePing.java b/oneping/src/main/java/org/onos/oneping/OnePing.java
index 714236a..273a416 100644
--- a/oneping/src/main/java/org/onos/oneping/OnePing.java
+++ b/oneping/src/main/java/org/onos/oneping/OnePing.java
@@ -26,7 +26,7 @@
 import org.onosproject.net.flow.FlowRuleService;
 import org.onosproject.net.flow.TrafficSelector;
 import org.onosproject.net.flow.TrafficTreatment;
-import org.onosproject.net.flow.criteria.Criteria.EthCriterion;
+import org.onosproject.net.flow.criteria.EthCriterion;
 import org.onosproject.net.packet.PacketContext;
 import org.onosproject.net.packet.PacketProcessor;
 import org.onosproject.net.packet.PacketService;
diff --git a/pom.xml b/pom.xml
index b9db4c9..4e6b52f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -45,6 +45,7 @@
         <module>calendar</module>
         <module>database-perf</module>
         <module>tvue</module>
+        <module>mfwd</module>
     </modules>
 
     <properties>