Adding mfwd, igmp and mfwd apps
Change-Id: Ie7187716db36b754e4cd687a8f9de004e27c7825
adding mfwd, pim, igmp apps
Change-Id: Iddd2dcee24dc905d5ff0efe1d1d798fc83a7c736
diff --git a/apps/dhcp/src/test/java/org/onosproject/dhcp/impl/DhcpManagerTest.java b/apps/dhcp/src/test/java/org/onosproject/dhcp/impl/DhcpManagerTest.java
index 3ea3b1b..20b002a 100644
--- a/apps/dhcp/src/test/java/org/onosproject/dhcp/impl/DhcpManagerTest.java
+++ b/apps/dhcp/src/test/java/org/onosproject/dhcp/impl/DhcpManagerTest.java
@@ -331,6 +331,7 @@
@Override
public void hostDetected(HostId hostId, HostDescription hostDescription, boolean replaceIps) {
+
}
@Override
diff --git a/apps/igmp/pom.xml b/apps/igmp/pom.xml
new file mode 100644
index 0000000..7980d2c
--- /dev/null
+++ b/apps/igmp/pom.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2014 Open Networking Laboratory
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-apps</artifactId>
+ <version>1.4.0-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>onos-app-igmp</artifactId>
+ <packaging>bundle</packaging>
+
+ <description>Internet Group Message Protocol</description>
+
+ <properties>
+ <onos.app.name>org.onosproject.igmp</onos.app.name>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-cli</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onlab-osgi</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onlab-junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- This is needed by ComponentContext, used for tunable configuration -->
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.scr.annotations</artifactId>
+ <version>1.9.8</version>
+ <scope>provided</scope>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-SymbolicName>
+ ${project.groupId}.${project.artifactId}
+ </Bundle-SymbolicName>
+ <Import-Package>
+ org.slf4j,
+ org.osgi.framework,
+ org.apache.commons.lang.math.*,
+ com.google.common.*,
+ org.onlab.packet.*,
+ org.onlab.rest.*,
+ org.onosproject.*,
+ org.onosproject.mfwd.impl.*;
+ org.onlab.util.*,
+ org.jboss.netty.util.*
+ </Import-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>2.5.1</version>
+ <configuration>
+ <source>1.8</source>
+ <target>1.8</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/apps/igmp/src/main/java/org/onosproject/igmp/impl/IGMPComponent.java b/apps/igmp/src/main/java/org/onosproject/igmp/impl/IGMPComponent.java
new file mode 100644
index 0000000..ae539c6
--- /dev/null
+++ b/apps/igmp/src/main/java/org/onosproject/igmp/impl/IGMPComponent.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.igmp.impl;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.IGMP;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketPriority;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.slf4j.Logger;
+
+/**
+ * Internet Group Management Protocol.
+ */
+@Component(immediate = true)
+public class IGMPComponent {
+ private final Logger log = getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected PacketService packetService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ private IGMPPacketProcessor processor = new IGMPPacketProcessor();
+ private static ApplicationId appId;
+
+ @Activate
+ public void activate() {
+ appId = coreService.registerApplication("org.onosproject.igmp");
+
+ packetService.addProcessor(processor, PacketProcessor.director(1));
+
+ // Build a traffic selector for all multicast traffic
+ TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+ selector.matchEthType(Ethernet.TYPE_IPV4);
+ selector.matchIPProtocol(IPv4.PROTOCOL_IGMP);
+ packetService.requestPackets(selector.build(), PacketPriority.REACTIVE, appId);
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ packetService.removeProcessor(processor);
+ processor = null;
+ log.info("Stopped");
+ }
+
+ /**
+ * Packet processor responsible for handling IGMP packets.
+ */
+ private class IGMPPacketProcessor implements PacketProcessor {
+
+ @Override
+ public void process(PacketContext context) {
+ // Stop processing if the packet has been handled, since we
+ // can't do any more to it.
+ if (context.isHandled()) {
+ return;
+ }
+
+ InboundPacket pkt = context.inPacket();
+ Ethernet ethPkt = pkt.parsed();
+ if (ethPkt == null) {
+ return;
+ }
+
+ /*
+ * IPv6 MLD packets are handled by ICMP6. We'll only deal
+ * with IPv4.
+ */
+ if (ethPkt.getEtherType() != Ethernet.TYPE_IPV4) {
+ return;
+ }
+
+ IPv4 ip = (IPv4) ethPkt.getPayload();
+ IpAddress gaddr = IpAddress.valueOf(ip.getDestinationAddress());
+ IpAddress saddr = Ip4Address.valueOf(ip.getSourceAddress());
+ log.debug("Packet (" + saddr.toString() + ", " + gaddr.toString() +
+ "\tingress port: " + context.inPacket().receivedFrom().toString());
+
+ if (ip.getProtocol() != IPv4.PROTOCOL_IGMP) {
+ log.error("IGMP Picked up a non IGMP packet.");
+ return;
+ }
+
+ IpPrefix mcast = IpPrefix.valueOf("224.0.0.0/4");
+ if (!mcast.contains(gaddr)) {
+ log.error("IGMP Picked up a non multicast packet.");
+ return;
+ }
+
+ if (mcast.contains(saddr)) {
+ log.error("IGMP Picked up a packet with a multicast source address.");
+ return;
+ }
+ IpPrefix spfx = IpPrefix.valueOf(saddr, 32);
+ IpPrefix gpfx = IpPrefix.valueOf(gaddr, 32);
+
+ IGMP igmp = (IGMP) ip.getPayload();
+ switch (igmp.getIgmpType()) {
+
+ case IGMP.TYPE_IGMPV3_MEMBERSHIP_REPORT:
+ IGMPProcessMembership.processMembership(igmp, pkt.receivedFrom());
+ break;
+
+ case IGMP.TYPE_IGMPV3_MEMBERSHIP_QUERY:
+ IGMPProcessQuery.processQuery(igmp, pkt.receivedFrom());
+ break;
+
+ case IGMP.TYPE_IGMPV1_MEMBERSHIP_REPORT:
+ case IGMP.TYPE_IGMPV2_MEMBERSHIP_REPORT:
+ case IGMP.TYPE_IGMPV2_LEAVE_GROUP:
+ log.debug("IGMP version 1 & 2 message types are not currently supported. Message type: " +
+ igmp.getIgmpType());
+ break;
+
+ default:
+ log.debug("Unkown IGMP message type: " + igmp.getIgmpType());
+ break;
+ }
+ }
+ }
+}
diff --git a/apps/igmp/src/main/java/org/onosproject/igmp/impl/IGMPProcessMembership.java b/apps/igmp/src/main/java/org/onosproject/igmp/impl/IGMPProcessMembership.java
new file mode 100644
index 0000000..4c08a74
--- /dev/null
+++ b/apps/igmp/src/main/java/org/onosproject/igmp/impl/IGMPProcessMembership.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.igmp.impl;
+
+import org.onlab.packet.IGMP;
+import org.onosproject.net.ConnectPoint;
+
+/**
+ * Process an IGMP Membership Report.
+ */
+public final class IGMPProcessMembership {
+
+ // Hide the default constructor.
+ private IGMPProcessMembership() {
+ }
+
+ /**
+ * Process the IGMP Membership report.
+ *
+ * @param igmp the deserialized IGMP message.
+ */
+ public static void processMembership(IGMP igmp, ConnectPoint recievedFrom) {
+ }
+
+}
diff --git a/apps/igmp/src/main/java/org/onosproject/igmp/impl/IGMPProcessQuery.java b/apps/igmp/src/main/java/org/onosproject/igmp/impl/IGMPProcessQuery.java
new file mode 100644
index 0000000..8407bb1
--- /dev/null
+++ b/apps/igmp/src/main/java/org/onosproject/igmp/impl/IGMPProcessQuery.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.igmp.impl;
+
+import org.onlab.packet.IGMP;
+import org.onosproject.net.ConnectPoint;
+
+/**
+ * Process IGMP Query messages.
+ */
+public final class IGMPProcessQuery {
+
+ // Hide the default constructor.
+ private IGMPProcessQuery() {
+ }
+
+ /**
+ * Process the IGMP Membership Query message.
+ *
+ * @param igmp The deserialzed IGMP message
+ */
+ public static void processQuery(IGMP igmp, ConnectPoint receivedFrom) {
+ }
+
+}
diff --git a/apps/mfwd/pom.xml b/apps/mfwd/pom.xml
new file mode 100644
index 0000000..489c315
--- /dev/null
+++ b/apps/mfwd/pom.xml
@@ -0,0 +1,140 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2014 Open Networking Laboratory
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-apps</artifactId>
+ <version>1.4.0-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>onos-app-mfwd</artifactId>
+ <packaging>bundle</packaging>
+
+ <description>Multicast forwarding application</description>
+
+ <properties>
+ <onos.app.name>org.onosproject.mfwd</onos.app.name>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-cli</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.netty</groupId>
+ <artifactId>netty</artifactId>
+ <version>3.9.0.Final</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.karaf.shell</groupId>
+ <artifactId>org.apache.karaf.shell.console</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onlab-rest</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-rest</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-servlet</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onlab-junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.easymock</groupId>
+ <artifactId>easymock</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <_wab>src/main/webapp/</_wab>
+ <Bundle-SymbolicName>
+ ${project.groupId}.${project.artifactId}
+ </Bundle-SymbolicName>
+ <Import-Package>
+ org.slf4j,
+ org.osgi.framework,
+ javax.ws.rs,javax.ws.rs.core,
+ com.sun.jersey.api.core,
+ com.sun.jersey.spi.container.servlet,
+ com.sun.jersey.server.impl.container.servlet,
+ com.fasterxml.jackson.databind,
+ com.fasterxml.jackson.databind.node,
+ org.apache.commons.lang.math.*,
+ com.google.common.*,
+ org.onlab.packet.*,
+ org.onlab.rest.*,
+ org.onosproject.*,
+ org.onlab.util.*,
+ org.jboss.netty.util.*
+ </Import-Package>
+ <Web-ContextPath>${web.context}</Web-ContextPath>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
+
diff --git a/apps/mfwd/src/main/java/org/onosproject/mfwd/cli/McastDeleteCommand.java b/apps/mfwd/src/main/java/org/onosproject/mfwd/cli/McastDeleteCommand.java
new file mode 100644
index 0000000..ded5a1a
--- /dev/null
+++ b/apps/mfwd/src/main/java/org/onosproject/mfwd/cli/McastDeleteCommand.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.mfwd.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+
+import org.onosproject.mfwd.impl.McastRouteTable;
+
+/**
+ * Delete a multicast route.
+ */
+@Command(scope = "onos", name = "mcast-delete",
+ description = "Delete a multicast route flow")
+public class McastDeleteCommand extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "sAddr",
+ description = "IP Address of the multicast source. '*' can be used for any source (*, G) entry",
+ required = true, multiValued = false)
+ String sAddr = null;
+
+ @Argument(index = 1, name = "gAddr",
+ description = "IP Address of the multicast group",
+ required = true, multiValued = false)
+ String gAddr = null;
+
+ @Override
+ protected void execute() {
+ McastRouteTable mrib = McastRouteTable.getInstance();
+ mrib.removeRoute(sAddr, gAddr);
+ }
+}
+
diff --git a/apps/mfwd/src/main/java/org/onosproject/mfwd/cli/McastJoinCommand.java b/apps/mfwd/src/main/java/org/onosproject/mfwd/cli/McastJoinCommand.java
new file mode 100644
index 0000000..5172691
--- /dev/null
+++ b/apps/mfwd/src/main/java/org/onosproject/mfwd/cli/McastJoinCommand.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.mfwd.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.packet.IpPrefix;
+import org.onosproject.cli.AbstractShellCommand;
+
+import org.onosproject.mfwd.impl.McastRouteBase;
+import org.onosproject.mfwd.impl.McastRouteTable;
+
+/**
+ * Installs a source, multicast group flow.
+ */
+@Command(scope = "onos", name = "mcast-join",
+ description = "Installs a source, multicast group flow")
+public class McastJoinCommand extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "sAddr",
+ description = "IP Address of the multicast source. '*' can be used for any source (*, G) entry",
+ required = true, multiValued = false)
+ String sAddr = null;
+
+ @Argument(index = 1, name = "gAddr",
+ description = "IP Address of the multicast group",
+ required = true, multiValued = false)
+ String gAddr = null;
+
+ @Argument(index = 2, name = "ingressPort",
+ description = "Ingress port and Egress ports",
+ required = false, multiValued = false)
+ String ingressPort = null;
+
+ @Argument(index = 3, name = "ports",
+ description = "Ingress port and Egress ports",
+ required = false, multiValued = true)
+ String[] ports = null;
+
+ @Override
+ protected void execute() {
+ McastRouteTable mrib = McastRouteTable.getInstance();
+ IpPrefix mcast = IpPrefix.valueOf("224.0.0.0/4");
+ IpPrefix saddr = IpPrefix.valueOf(sAddr);
+ if (mcast.contains(saddr)) {
+ print("Error: the source address " + sAddr + " must be an IPv4 unicast address");
+ return;
+ }
+
+ IpPrefix gaddr = IpPrefix.valueOf(gAddr);
+ if (!mcast.contains(gaddr)) {
+ print("Error: " + gAddr + " must be a multicast group address");
+ return;
+ }
+
+ McastRouteBase mr = mrib.addRoute(sAddr, gAddr);
+ if (mr == null) {
+ print("Error: unable to save the multicast state");
+ return;
+ }
+
+ // Port format "of:0000000000000023/4"
+ if (ingressPort != null) {
+ String inCP = ingressPort;
+ log.debug("Ingress port provided: " + inCP);
+ String [] cp = inCP.split("/");
+ mr.addIngressPoint(cp[0], Long.parseLong(cp[1]));
+ } else {
+ return;
+ }
+
+ if (ports == null) {
+ return;
+ }
+
+ for (int i = 0; i < ports.length; i++) {
+ String egCP = ports[i];
+ log.debug("Egress port provided: " + egCP);
+ String [] cp = egCP.split("/");
+ mr.addEgressPoint(cp[0], Long.parseLong(cp[1]));
+ }
+ print("Added the mcast route");
+ }
+}
diff --git a/apps/mfwd/src/main/java/org/onosproject/mfwd/cli/McastShowCommand.java b/apps/mfwd/src/main/java/org/onosproject/mfwd/cli/McastShowCommand.java
new file mode 100644
index 0000000..750fac9
--- /dev/null
+++ b/apps/mfwd/src/main/java/org/onosproject/mfwd/cli/McastShowCommand.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.mfwd.cli;
+
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.ConnectPoint;
+import org.onlab.packet.IpPrefix;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.onosproject.mfwd.impl.McastRouteTable;
+import org.onosproject.mfwd.impl.McastRouteGroup;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Displays the source, multicast group flows entries.
+ */
+@Command(scope = "onos", name = "mcast-show", description = "Displays the source, multicast group flows")
+public class McastShowCommand extends AbstractShellCommand {
+
+ private final Logger log = getLogger(getClass());
+
+ @Override
+ protected void execute() {
+ McastRouteTable mrt = McastRouteTable.getInstance();
+ if (outputJson()) {
+ print("%s", json(mrt));
+ } else {
+ printMrib4(mrt);
+ }
+ }
+
+ public JsonNode json(McastRouteTable mrt) {
+ ObjectMapper mapper = new ObjectMapper();
+ ArrayNode result = mapper.createArrayNode();
+ Map<IpPrefix, McastRouteGroup> mrib4 = mrt.getMrib4();
+ for (McastRouteGroup mg : mrib4.values()) {
+ String sAddr = "";
+ String gAddr = "";
+ String inPort = "";
+ String outPorts = "";
+ if (mg.getSaddr() != null) {
+ sAddr = mg.getSaddr().toString();
+ log.info("Multicast Source: " + sAddr);
+ }
+ if (mg.getGaddr() != null) {
+ gAddr = mg.getGaddr().toString();
+ log.info("Multicast Group: " + gAddr);
+ }
+ if (mg.getIngressPoint() != null) {
+ inPort = mg.getIngressPoint().toString();
+ log.info("Multicast Ingress: " + inPort);
+ }
+ Set<ConnectPoint> eps = mg.getEgressPoints();
+ if (eps != null && !eps.isEmpty()) {
+ outPorts = eps.toString();
+ }
+ result.add(mapper.createObjectNode()
+ .put("src", sAddr)
+ .put("grp", gAddr)
+ .put("inPort", inPort)
+ .put("outPorts", outPorts));
+ }
+ return result;
+ }
+
+ /**
+ * Displays multicast route table entries.
+ *
+ * @param mrt route table
+ */
+ protected void printMrib4(McastRouteTable mrt) {
+ print(mrt.printMcastRouteTable());
+ }
+}
diff --git a/apps/mfwd/src/main/java/org/onosproject/mfwd/cli/package-info.java b/apps/mfwd/src/main/java/org/onosproject/mfwd/cli/package-info.java
new file mode 100644
index 0000000..7b5ed39
--- /dev/null
+++ b/apps/mfwd/src/main/java/org/onosproject/mfwd/cli/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Sample Multicast forwarding framework using intents.
+ */
+package org.onosproject.mfwd.cli;
+
diff --git a/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastForwarding.java b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastForwarding.java
new file mode 100644
index 0000000..bcba060
--- /dev/null
+++ b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastForwarding.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.mfwd.impl;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IPv4;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketPriority;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.slf4j.Logger;
+
+/**
+ * WORK-IN-PROGRESS: The multicast forwarding application using intent framework.
+ */
+@Component(immediate = true)
+public class McastForwarding {
+
+ private final Logger log = getLogger(getClass());
+ private final IpPrefix mcast = IpPrefix.valueOf("224.0.0.0/4");
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected PacketService packetService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ private ReactivePacketProcessor processor = new ReactivePacketProcessor();
+ private McastRouteTable mrib;
+ private static ApplicationId appId;
+
+ /**
+ * Active MulticastForwardingIntent.
+ */
+ @Activate
+ public void activate() {
+ appId = coreService.registerApplication("org.onosproject.mfwd");
+
+ packetService.addProcessor(processor, PacketProcessor.director(2));
+
+ // Build a traffic selector for all multicast traffic
+ TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+ selector.matchEthType(Ethernet.TYPE_IPV4);
+ selector.matchIPDst(mcast);
+ packetService.requestPackets(selector.build(), PacketPriority.REACTIVE, appId);
+
+ mrib = McastRouteTable.getInstance();
+ log.info("Started");
+ }
+
+ /**
+ * Deactivate Multicast Forwarding Intent.
+ */
+ @Deactivate
+ public void deactivate() {
+ packetService.removeProcessor(processor);
+ processor = null;
+ log.info("Stopped");
+ }
+
+ /**
+ * Get the application ID, used by the McastIntentManager.
+ *
+ * @return the application ID
+ */
+ public static ApplicationId getAppId() {
+ return appId;
+ }
+
+ /**
+ * Packet processor responsible for forwarding packets along their paths.
+ */
+ private class ReactivePacketProcessor implements PacketProcessor {
+
+ /**
+ * Process incoming packets.
+ *
+ * @param context packet processing context
+ */
+ @Override
+ public void process(PacketContext context) {
+ // Stop processing if the packet has been handled, since we
+ // can't do any more to it.
+ if (context.isHandled()) {
+ return;
+ }
+
+ InboundPacket pkt = context.inPacket();
+ Ethernet ethPkt = pkt.parsed();
+
+ if (ethPkt == null) {
+ return;
+ }
+
+ if (ethPkt.getEtherType() != Ethernet.TYPE_IPV4 &&
+ ethPkt.getEtherType() != Ethernet.TYPE_IPV6) {
+ return;
+ }
+
+ if (ethPkt.getEtherType() == Ethernet.TYPE_IPV6) {
+ // Ignore ipv6 at the moment.
+ return;
+ }
+
+ IPv4 ip = (IPv4) ethPkt.getPayload();
+ IpAddress gaddr = IpAddress.valueOf(ip.getDestinationAddress());
+ IpAddress saddr = Ip4Address.valueOf(ip.getSourceAddress());
+
+ log.debug("Packet ({}, {}) has been punted\n" +
+ "\tingress port: {}\n",
+ saddr.toString(),
+ gaddr.toString(),
+ context.inPacket().receivedFrom().toString());
+
+ if (!mcast.contains(gaddr)) {
+ // Yikes, this is a bad group address
+ return;
+ }
+
+ if (mcast.contains(saddr)) {
+ // Yikes, the source address is multicast
+ return;
+ }
+
+ IpPrefix spfx = IpPrefix.valueOf(saddr, 32);
+ IpPrefix gpfx = IpPrefix.valueOf(gaddr, 32);
+
+ /*
+ * Do a best match lookup on the (s, g) of the packet. If an entry does
+ * not exist create one and store it's incoming connect point.
+ *
+ * The connect point is deviceId / portId that the packet entered
+ * the SDN network. This differs from traditional mcast where the
+ * ingress port would be a specific device.
+ */
+ McastRoute entry = mrib.findBestMatch(spfx, gpfx);
+ if (entry == null || entry.getSaddr().equals(IPv4.fromIPv4Address(0))) {
+
+ /*
+ * Create an entry that we can fast drop.
+ */
+ entry = mrib.addRoute(spfx, gpfx);
+ entry.addIngressPoint(context.inPacket().receivedFrom());
+ }
+
+ /*
+ * TODO: If we do not have an ingress or any egress connect points we
+ * should set up a fast drop entry.
+ */
+ if (entry.getIngressPoint() == null) {
+ return;
+ }
+
+ if (entry.getEgressPoints().isEmpty()) {
+ return;
+ }
+
+ /*
+ * This is odd, we should not have received a punted packet if an
+ * intent was installed unless the intent was not installed
+ * correctly. However, we are seeing packets get punted after
+ * the intent has been installed.
+ *
+ * Therefore we are going to forward the packets even if they
+ * should have already been forwarded by the intent fabric.
+ */
+ if (entry.getIntentKey() != null) {
+ return;
+ }
+
+ entry.setIntent();
+ McastIntentManager im = McastIntentManager.getInstance();
+ im.setIntent(entry);
+
+ entry.incrementPuntCount();
+
+ // Send the pack out each of the egress devices & port
+ forwardPacketToDst(context, entry);
+ }
+ }
+
+ /**
+ * Forward the packet to it's multicast destinations.
+ *
+ * @param context The packet context
+ * @param entry The multicast route entry matching this packet
+ */
+ private void forwardPacketToDst(PacketContext context, McastRoute entry) {
+
+ // Send the pack out each of the respective egress ports
+ for (ConnectPoint egress : entry.getEgressPoints()) {
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(egress.port()).build();
+
+ OutboundPacket packet = new DefaultOutboundPacket(
+ egress.deviceId(),
+ treatment,
+ context.inPacket().unparsed());
+
+ packetService.emit(packet);
+ }
+ }
+}
diff --git a/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastIntentManager.java b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastIntentManager.java
new file mode 100644
index 0000000..c3709e6
--- /dev/null
+++ b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastIntentManager.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.mfwd.impl;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.Ethernet;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.SinglePointToMultiPointIntent;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+
+@Component(immediate = true)
+@Service(value = org.onosproject.mfwd.impl.McastIntentManager.class)
+public class McastIntentManager {
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected IntentService intentService;
+
+ private static McastIntentManager instance;
+
+ public McastIntentManager() {
+ instance = this;
+ }
+
+ /**
+ * Active this component.
+ */
+ @Activate
+ public void activate() { }
+
+ /**
+ * Deactivate this component.
+ */
+ @Deactivate
+ public void deactivate() {
+ withdrawAllIntents();
+ }
+
+ /**
+ * Get instance of this intentManager.
+ *
+ * @return the instance of this intent manager.
+ */
+ public static McastIntentManager getInstance() {
+ if (instance == null) {
+ instance = new McastIntentManager();
+ }
+ return instance;
+ }
+
+ /**
+ * Install the PointToMultipoint forwarding intent.
+ *
+ * @param mroute multicast route entry
+ * @return the intent that has been set or null otherwise
+ */
+ public SinglePointToMultiPointIntent setIntent(McastRoute mroute) {
+ TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+ TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment();
+
+ if (mroute.getIngressPoint() == null ||
+ mroute.getEgressPoints().isEmpty()) {
+ return null;
+ }
+
+ /*
+ * Match the group AND source addresses. We will also check ether type to
+ * determine if we are doing ipv4 or ipv6.
+ *
+ * If we really wanted to be pendantic we could put in a
+ * condition to make sure the ethernet MAC address was also
+ * mcast.
+ */
+ selector.matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPDst(mroute.getGaddr())
+ .matchIPSrc(mroute.getSaddr());
+
+ SinglePointToMultiPointIntent intent =
+ SinglePointToMultiPointIntent.builder()
+ .appId(McastForwarding.getAppId())
+ .selector(selector.build())
+ .treatment(treatment)
+ .ingressPoint(mroute.getIngressPoint())
+ .egressPoints(mroute.getEgressPoints()).
+ build();
+
+ intentService.submit(intent);
+ return intent;
+ }
+
+ /**
+ * Withdraw the intent represented by this route.
+ *
+ * @param mroute the mcast route whose intent we want to remove
+ */
+ public void withdrawIntent(McastRouteBase mroute) {
+ Intent intent = intentService.getIntent(mroute.getIntentKey());
+ intentService.withdraw(intent);
+ }
+
+ /**
+ * Withdraw all intents.
+ *
+ * This will be called from the deactivate method so we don't leave
+ * a mess behind us after we leave.
+ */
+ public void withdrawAllIntents() {
+ for (Intent intent : intentService.getIntents()) {
+ intentService.withdraw(intent);
+ }
+ }
+}
diff --git a/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRoute.java b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRoute.java
new file mode 100644
index 0000000..d56488e
--- /dev/null
+++ b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRoute.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.mfwd.impl;
+
+import org.onlab.packet.IpPrefix;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.intent.Key;
+import org.onosproject.net.intent.SinglePointToMultiPointIntent;
+
+import java.util.Set;
+
+/**
+ * This McastRouteBase interface is implemented by the McastRouteBase class which
+ * in turn acts as the base class for both the McastRouteGroup and McastRouteSource.
+ */
+interface McastRoute {
+
+ /**
+ * Gets the group addresses.
+ *
+ * @return group address
+ */
+ public IpPrefix getGaddr();
+
+ /**
+ * Gets the source address.
+ *
+ * @return the source address
+ */
+ public IpPrefix getSaddr();
+
+ /**
+ * Determines if this is an IPv4 multicast route.
+ *
+ * @return true if it is an IPv4 route
+ */
+ public boolean isIp4();
+
+ /**
+ * Determines if this is an IPv6 multicast route.
+ *
+ * @return true if it is an IPv6 route
+ */
+ public boolean isIp6();
+
+ /**
+ * Add the ingress ConnectPoint with a ConnectPoint.
+ *
+ * @param ingress ingress point
+ */
+ public void addIngressPoint(ConnectPoint ingress);
+
+ /**
+ * Add the ingress Connect Point using. ..
+ *
+ * @param deviceId device ID
+ * @param portNum port number
+ */
+ public void addIngressPoint(String deviceId, long portNum);
+
+ /**
+ * Get the ingress connect point.
+ *
+ * @return the ingress connect point
+ */
+ public ConnectPoint getIngressPoint();
+
+ /**
+ * Add an egress connect point.
+ *
+ * @param member the egress ConnectPoint to be added
+ */
+ public void addEgressPoint(ConnectPoint member);
+
+ /**
+ * Add an egress connect point.
+ *
+ * @param deviceId the device ID of the connect point
+ * @param portNum the port number of the connect point
+ */
+ public void addEgressPoint(String deviceId, long portNum);
+
+ /**
+ * Get the egress connect points.
+ *
+ * @return a set of egress connect points
+ */
+ public Set<ConnectPoint> getEgressPoints();
+
+ /**
+ * Increment the punt count.
+ */
+ public void incrementPuntCount();
+
+ /**
+ * Get the punt count.
+ *
+ * @return the punt count
+ */
+ public int getPuntCount();
+
+ /**
+ * Have the McastIntentManager create an intent, attempt to
+ * install the intent and then save the key.
+ */
+ public void setIntent();
+
+ /**
+ * Set the Intent key.
+ *
+ * @param intent intent
+ */
+ public void setIntent(SinglePointToMultiPointIntent intent);
+
+ /**
+ * Withdraw the intent if it has been installed.
+ */
+ public void withdrawIntent();
+
+ /**
+ * Get the intent key.
+ *
+ * @return the intentKey
+ */
+ public Key getIntentKey();
+
+ /**
+ * Pretty print the the route.
+ *
+ * @return a pretty string
+ */
+ public String toString();
+}
\ No newline at end of file
diff --git a/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteBase.java b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteBase.java
new file mode 100644
index 0000000..36582ad
--- /dev/null
+++ b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteBase.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.mfwd.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import org.onlab.packet.IpPrefix;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.intent.SinglePointToMultiPointIntent;
+import org.onosproject.net.intent.Key;
+
+import java.util.Set;
+import java.util.HashSet;
+
+/**
+ * McastRouteBase base class for McastRouteGroup and McastRouteSource.
+ */
+public class McastRouteBase implements McastRoute {
+ protected final IpPrefix gaddr;
+ protected final IpPrefix saddr;
+
+ protected ConnectPoint ingressPoint;
+ protected Set<ConnectPoint> egressPoints;
+
+ protected boolean isGroup = false;
+
+ /**
+ * How may times has this packet been punted.
+ */
+ private int puntCount = 0;
+
+ /**
+ * If the intentKey is null that means no intent has
+ * been installed.
+ */
+ protected Key intentKey = null;
+
+ /**
+ * Create a multicast route. This is the parent class for both the Group
+ * and the source.
+ *
+ * @param saddr source address
+ * @param gaddr multicast group address
+ */
+ public McastRouteBase(String saddr, String gaddr) {
+ this.gaddr = IpPrefix.valueOf(checkNotNull(gaddr));
+ if (saddr == null || saddr.equals("*")) {
+ this.saddr = IpPrefix.valueOf(0, 0);
+ } else {
+ this.saddr = IpPrefix.valueOf(checkNotNull(gaddr));
+ }
+ this.init();
+ }
+
+ /**
+ * Create a multicast group table entry.
+ * @param gaddr multicast group address
+ */
+ public McastRouteBase(String gaddr) {
+ this("*", gaddr);
+ }
+
+ /**
+ * Set the source and group address value of a (*, G) group.
+ *
+ * @param gpfx the group prefix address
+ */
+ public McastRouteBase(IpPrefix gpfx) {
+ this(IpPrefix.valueOf(0, 0), gpfx);
+ }
+
+ /**
+ * Create a multicast route constructor.
+ *
+ * @param saddr source address
+ * @param gaddr group address
+ */
+ public McastRouteBase(IpPrefix saddr, IpPrefix gaddr) {
+ this.saddr = checkNotNull(saddr);
+ this.gaddr = checkNotNull(gaddr);
+
+ this.init();
+ }
+
+ private void init() {
+ this.isGroup = (this.saddr.prefixLength() == 0);
+ this.ingressPoint = null;
+ this.egressPoints = new HashSet();
+ }
+
+ /**
+ * Get the multicast group address.
+ *
+ * @return the multicast group address
+ */
+ @Override
+ public IpPrefix getGaddr() {
+ return gaddr;
+ }
+
+ /**
+ * Get the multicast source address.
+ *
+ * @return the multicast source address
+ */
+ @Override
+ public IpPrefix getSaddr() {
+ return saddr;
+ }
+
+ /**
+ * Is this an IPv4 multicast route.
+ *
+ * @return true if it is an IPv4 route
+ */
+ @Override
+ public boolean isIp4() {
+ return gaddr.isIp4();
+ }
+
+ /**
+ * Is this an IPv6 multicast route.
+ *
+ * @return true if it is an IPv6 route
+ */
+ @Override
+ public boolean isIp6() {
+ return gaddr.isIp6();
+ }
+
+ /**
+ * Is this a multicast group route?
+ *
+ * @return true if it is a multicast group route.
+ */
+ public boolean isGroup() {
+ return isGroup;
+ }
+
+ /**
+ * @return true if this is (S, G) false if it (*, G).
+ */
+ public boolean isSource() {
+ return (!isGroup);
+ }
+
+ /**
+ * Add an ingress point to this route.
+ *
+ * @param ingress incoming connect point
+ */
+ @Override
+ public void addIngressPoint(ConnectPoint ingress) {
+ ingressPoint = checkNotNull(ingress);
+ }
+
+ /**
+ * Add or modify the ingress connect point.
+ *
+ * @param deviceId the switch device Id
+ * @param portNum the ingress port number
+ */
+ @Override
+ public void addIngressPoint(String deviceId, long portNum) {
+ ingressPoint = new ConnectPoint(
+ DeviceId.deviceId(deviceId),
+ PortNumber.portNumber(portNum));
+ }
+
+ /**
+ * Get the ingress ConnectPoint.
+ *
+ * @return the ingress ConnectPoint
+ */
+ @Override
+ public ConnectPoint getIngressPoint() {
+ return this.ingressPoint;
+ }
+
+ /**
+ * Add an egress ConnectPoint.
+ *
+ * @param member member egress connect point
+ */
+ @Override
+ public void addEgressPoint(ConnectPoint member) {
+ egressPoints.add(checkNotNull(member));
+ }
+
+ /**
+ * Add an egress ConnectPoint.
+ *
+ * @param deviceId deviceId of the connect point
+ * @param portNum portNum of the connect point
+ */
+ @Override
+ public void addEgressPoint(String deviceId, long portNum) {
+ ConnectPoint cp = new ConnectPoint(DeviceId.deviceId(deviceId), PortNumber.portNumber(portNum));
+ this.egressPoints.add(cp);
+ }
+
+ /**
+ * Get egress connect points for the route.
+ *
+ * @return Set of egress connect points
+ */
+ @Override
+ public Set<ConnectPoint> getEgressPoints() {
+ return egressPoints;
+ }
+
+ /**
+ * Get the number of times the packet has been punted.
+ *
+ * @return the punt count
+ */
+ @Override
+ public int getPuntCount() {
+ return puntCount;
+ }
+
+ /**
+ * Increment the punt count.
+ *
+ * TODO: we need to handle wrapping.
+ */
+ @Override
+ public void incrementPuntCount() {
+ puntCount++;
+ }
+
+ /**
+ * Have the McastIntentManager create and set the intent, then save the intent key.
+ *
+ * If we already have an intent, we will first withdraw the existing intent and
+ * replace it with a new one. This will support the case where the ingress connectPoint
+ * or group of egress connectPoints change.
+ */
+ @Override
+ public void setIntent() {
+ if (this.intentKey != null) {
+ this.withdrawIntent();
+ }
+ McastIntentManager im = McastIntentManager.getInstance();
+ SinglePointToMultiPointIntent intent = im.setIntent(this);
+ this.intentKey = intent.key();
+ }
+
+ /**
+ * Set the Intent key.
+ *
+ * @param intent intent
+ */
+ @Override
+ public void setIntent(SinglePointToMultiPointIntent intent) {
+ intentKey = intent.key();
+ }
+
+ /**
+ * Get the intent key represented by this route.
+ *
+ * @return intentKey
+ */
+ @Override
+ public Key getIntentKey() {
+ return this.intentKey;
+ }
+
+
+ /**
+ * Withdraw the intent and set the key to null.
+ */
+ @Override
+ public void withdrawIntent() {
+ if (intentKey == null) {
+ // nothing to withdraw
+ return;
+ }
+ McastIntentManager im = McastIntentManager.getInstance();
+ im.withdrawIntent(this);
+ this.intentKey = null;
+ }
+
+ /**
+ * Pretty Print this Multicast Route. Works for McastRouteSource and McastRouteGroup.
+ *
+ * @return pretty string of the multicast route
+ */
+ @Override
+ public String toString() {
+ String out = String.format("(%s, %s)\n\t",
+ saddr.toString(), gaddr.toString());
+
+ out += "intent: ";
+ out += (intentKey == null) ? "not installed" : this.intentKey.toString();
+ out += "\n\tingress: ";
+ out += (ingressPoint == null) ? "NULL" : ingressPoint.toString();
+ out += "\n\tegress: {\n";
+ if (egressPoints != null && !egressPoints.isEmpty()) {
+ for (ConnectPoint eg : egressPoints) {
+ out += "\t\t" + eg.toString() + "\n";
+ }
+ }
+ out += ("\t}\n");
+ out += ("\tpunted: " + this.getPuntCount() + "\n");
+ return out;
+ }
+}
diff --git a/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteGroup.java b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteGroup.java
new file mode 100644
index 0000000..4a58e1b
--- /dev/null
+++ b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteGroup.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.mfwd.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import java.util.HashMap;
+import org.onlab.packet.IpPrefix;
+
+/**
+ * The McastRouteGroup extends the McastRouteBase class and serves two purposes:
+ * first it represents a (*, G) multicast route entry. Second it serves
+ * as a container for all (S, G) multicast route entries that belong
+ * to the same group address.
+ */
+public class McastRouteGroup extends McastRouteBase {
+ private HashMap<IpPrefix, McastRouteSource> sources;
+
+ /**
+ * Class constructor.
+ *
+ * @param gaddr - String representation of group address.
+ */
+ public McastRouteGroup(String gaddr) {
+ super(checkNotNull(gaddr));
+ this.init();
+ }
+
+ /**
+ * Create a multicast group.
+ *
+ * @param gpfx - Group address
+ */
+ public McastRouteGroup(IpPrefix gpfx) {
+ super(checkNotNull(gpfx));
+ this.init();
+ }
+
+ /**
+ * Common initialization used by constructors.
+ */
+ private void init() {
+ this.sources = new HashMap();
+ super.isGroup = true;
+ }
+
+ /**
+ * Find a specific multicast source address for this group.
+ *
+ * @param saddr the source address
+ * @return the multicast source route or null if it does not exist
+ */
+ public McastRouteSource findSource(IpPrefix saddr) {
+ return this.sources.get(checkNotNull(saddr));
+ }
+
+ /**
+ * Return the entire set of multicast sources for this group.
+ *
+ * @return the set of multicast sources
+ */
+ public HashMap<IpPrefix, McastRouteSource> getSources() {
+ return this.sources;
+ }
+
+ /**
+ * Add a new McastRouteSource to this group.
+ *
+ * @param src the multicast source
+ */
+ public void addSource(McastRouteSource src) {
+ checkNotNull(src);
+ this.sources.put(src.getSaddr(), src);
+ }
+
+ /**
+ * Remove the source with this specific IpPrefix from this group entry.
+ *
+ * @param spfx IP Prefix of the source to be removed
+ * @return the source route that was just removed
+ */
+ public McastRouteSource removeSource(IpPrefix spfx) {
+ McastRouteSource src = this.sources.remove(spfx);
+ src.withdrawIntent();
+ return src;
+ }
+
+ /**
+ * Remove all sources from this.
+ */
+ public void removeSources() {
+ for (McastRouteSource src : this.sources.values()) {
+ src.withdrawIntent();
+ this.sources.remove(src.getSaddr());
+ }
+ }
+
+}
diff --git a/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteSource.java b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteSource.java
new file mode 100644
index 0000000..68edc2e
--- /dev/null
+++ b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteSource.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.mfwd.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import org.onlab.packet.IpPrefix;
+
+/**
+ * This class represents and specific multicast senders source address. Objects from
+ * this class will belong to the sources collection of the multicast group.
+ */
+public class McastRouteSource extends McastRouteBase {
+
+ // A reference to our parent group
+ private McastRouteGroup group;
+
+ /**
+ * Create a multicast source with IpPrefixes.
+ *
+ * @param source the source address
+ * @param group the group address
+ */
+ public McastRouteSource(IpPrefix source, IpPrefix group) {
+ super(checkNotNull(source), checkNotNull(group));
+ }
+
+ /**
+ * Set our parent multicast group.
+ *
+ * @param group the group this source belongs to
+ */
+ public void setGroup(McastRouteGroup group) {
+ this.group = group;
+ }
+}
\ No newline at end of file
diff --git a/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteTable.java b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteTable.java
new file mode 100644
index 0000000..ff7a026
--- /dev/null
+++ b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/McastRouteTable.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.mfwd.impl;
+
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.IpPrefix;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * The Mcast Route Table holds all multicast state for the controller.
+ *
+ * State for IPv4 and IPv6 are maintained. The tables are sets of McastRouteGroup
+ * structures that represent (*, G) state with a series of egress ConnectPoints.
+ * Each (*, G) may also have a set of (S, G) that may have there own set of
+ * ingress and egress ConnectPoints.
+ *
+ * TODO: perhaps should probably create two separate singleton for IPv4 and IPv6 respectively.
+ */
+@Service(value = org.onosproject.mfwd.impl.McastRouteTable.class)
+public final class McastRouteTable {
+
+ /*
+ * Create a map of the McastGroups indexed by the multicast group prefix.
+ * We may choose to change the map data structure in to some form a radix trie
+ * depending on the type of real world usage we see.
+ */
+ private final Map<IpPrefix, McastRouteGroup> mrib4;
+ private final Map<IpPrefix, McastRouteGroup> mrib6;
+ private static McastRouteTable instance = null;
+
+ private Boolean ipv6Enabled = false;
+
+ /**
+ * Create the two v4 & v6 tables.
+ */
+ private McastRouteTable() {
+ mrib4 = new ConcurrentHashMap<IpPrefix, McastRouteGroup>();
+ if (ipv6Enabled) {
+ mrib6 = new ConcurrentHashMap<IpPrefix, McastRouteGroup>();
+ } else {
+ mrib6 = null;
+ }
+ }
+
+ /**
+ * Get the single instance of this multicast group address.
+ *
+ * @return the multicast route table
+ */
+ public static McastRouteTable getInstance() {
+ if (instance == null) {
+ instance = new McastRouteTable();
+ }
+ return instance;
+ }
+
+ /**
+ * Get the IPv4 MRIB.
+ *
+ * @return the IPv4 MRIB
+ */
+ public Map<IpPrefix, McastRouteGroup> getMrib4() {
+ return mrib4;
+ }
+
+ /**
+ * Get the IPv6 MRIB.
+ *
+ * @return Return the set of prefix keyed McastGroups
+ */
+ public Map<IpPrefix, McastRouteGroup> getMrib6() {
+ return mrib6;
+ }
+
+ /**
+ * Save the McastRouteGroup in the address family appropriate mrib.
+ *
+ * @param group The McastRouteGroup to save
+ */
+ private void storeGroup(McastRouteGroup group) {
+ if (group.isIp4()) {
+ mrib4.put(group.getGaddr(), group);
+ } else if (group.isIp6() && ipv6Enabled) {
+ mrib6.put(group.getGaddr(), group);
+ }
+ }
+
+ /**
+ * Remove the group.
+ *
+ * @param group the group to be removed
+ */
+ private void removeGroup(McastRouteGroup group) {
+ IpPrefix gpfx = group.getGaddr();
+ if (gpfx.isIp4()) {
+ mrib4.remove(gpfx);
+ } else if (gpfx.isIp6() && ipv6Enabled) {
+ mrib6.remove(gpfx);
+ }
+ }
+
+ /**
+ * Add a multicast route to the MRIB. This function will.
+ *
+ * @param saddr source address * or x.x.x.x or x.x.x.x/y
+ * @param gaddr group address x.x.x.x or x.x.x.x/y
+ * @return the multicast route
+ */
+ public McastRouteBase addRoute(String saddr, String gaddr) {
+ IpPrefix gpfx = IpPrefix.valueOf(gaddr);
+ IpPrefix spfx = IpPrefix.valueOf(0, 0);
+ if (saddr != null && !saddr.equals("*")) {
+ spfx = IpPrefix.valueOf(saddr);
+ }
+ return addRoute(spfx, gpfx);
+ }
+
+ /**
+ * Add a multicast route to the MRIB. This function will store either
+ * (S, G) or (*, G) in the mrib if an entry does not already exist. If
+ * an entry does exist it is returned to the caller.
+ *
+ * Every (S, G) is stored as part of it's parent group entry which also represents
+ * (*, G) routes. In the case of a (S, G) we will also create the (*, G) entry if needed
+ * then save the (S, G) to the (*, G).
+ *
+ * @param spfx the source prefix
+ * @param gpfx the group prefix
+ * @return the resulting McastRouteSource or McastRouteGroup accordingly.
+ */
+ public McastRouteBase addRoute(IpPrefix spfx, IpPrefix gpfx) {
+
+ /**
+ * If a group route (*, g) does not exist we will need to make so we
+ * can start attaching our sources to the group entry.
+ */
+ McastRouteGroup group = findMcastGroup(gpfx);
+ if (group == null) {
+ group = new McastRouteGroup(gpfx);
+
+ // Save it for later
+ if (gpfx.isIp4()) {
+ this.mrib4.put(gpfx, group);
+ } else if (gpfx.isIp6() && ipv6Enabled) {
+ this.mrib6.put(gpfx, group);
+ }
+ }
+
+ /**
+ * If the source prefix length is 0 then we have our (*, g) entry, we can
+ * just return now.
+ */
+ if (spfx.prefixLength() == 0) {
+ return group;
+ }
+
+ // See if the source already exists. If so just return it.
+ McastRouteSource source = group.findSource(spfx);
+ if (source != null) {
+ return source;
+ }
+
+ /**
+ * We have the group but no source. We need to create the source then add it
+ * to the group.
+ */
+ source = new McastRouteSource(spfx, gpfx);
+
+ // Have the source save it's parent
+ source.setGroup(group);
+
+ // Save this source as part of this group
+ group.addSource(source);
+
+ return source;
+ }
+
+ /**
+ * Delete a multicast route from the MRIB.
+ *
+ * @param saddr source address * or x.x.x.x or x.x.x.x/y
+ * @param gaddr group address x.x.x.x or x.x.x.x/y
+ */
+ public void removeRoute(String saddr, String gaddr) {
+ IpPrefix gpfx = IpPrefix.valueOf(gaddr);
+ IpPrefix spfx = IpPrefix.valueOf(0, 0);
+ if (saddr != null && !saddr.equals("*")) {
+ spfx = IpPrefix.valueOf(saddr);
+ }
+ removeRoute(spfx, gpfx);
+ }
+
+ /**
+ * Remove a multicast route.
+ *
+ * @param spfx the source prefix
+ * @param gpfx the group prefix
+ */
+ public void removeRoute(IpPrefix spfx, IpPrefix gpfx) {
+
+ /**
+ * If a group route (*, g) does not exist we will need to make so we
+ * can start attaching our sources to the group entry.
+ */
+ McastRouteGroup group = findMcastGroup(gpfx);
+ if (group == null) {
+ // The group does not exist, we can't remove it.
+ return;
+ }
+
+ /**
+ * If the source prefix length is 0 then we have a (*, g) entry, which
+ * means we will remove this group and all of it's sources. We will
+ * also withdraw it's intent if need be.
+ */
+ if (spfx.prefixLength() > 0) {
+ group.removeSource(spfx);
+
+ /*
+ * Now a little house keeping. If this group has no more sources
+ * nor egress connectPoints git rid of it.
+ */
+ if (group.getSources().size() == 0 &&
+ group.getEgressPoints().size() == 0) {
+ removeGroup(group);
+ }
+
+ } else {
+ // Group remove has been explicitly requested.
+ group.removeSources();
+ group.withdrawIntent();
+ removeGroup(group);
+ }
+ }
+
+ /**
+ * Find the specific multicast group entry.
+ *
+ * @param group the group address
+ * @return McastRouteGroup the multicast (*, G) group route
+ */
+ public McastRouteGroup findMcastGroup(IpPrefix group) {
+ McastRouteGroup g = null;
+ if (group.isIp4()) {
+ g = mrib4.get(group);
+ } else if (group.isIp6() && ipv6Enabled) {
+ g = mrib6.get(group);
+ }
+ return g;
+ }
+
+ /**
+ * Find the multicast (S, G) entry if it exists.
+ *
+ * @param saddr the source address
+ * @param gaddr the group address
+ * @return The multicast source route entry if it exists, null if it does not.
+ */
+ public McastRouteSource findMcastSource(IpPrefix saddr, IpPrefix gaddr) {
+ McastRouteGroup grp = findMcastGroup(checkNotNull(gaddr));
+ if (grp == null) {
+ return null;
+ }
+ return grp.findSource(saddr);
+ }
+
+ /**
+ * This will first look up a Group entry. If no group entry was found null will
+ * be returned. If the group entry has been found we will then look up the (s, g) entry.
+ * If the (s, g) entry has been found, that will be returned. If no (s, g) was found
+ * the (*, g) group entry will be returned.
+ *
+ * @param saddr the source address
+ * @param gaddr the group address
+ * @return return the best matching McastRouteSource or McastRouteGroup
+ */
+ public McastRoute findBestMatch(IpPrefix saddr, IpPrefix gaddr) {
+ McastRouteGroup grp = this.findMcastGroup(checkNotNull(gaddr));
+ if (grp == null) {
+ return null;
+ }
+
+ // Found a group now look for a source
+ McastRouteSource src = grp.findSource(checkNotNull(saddr));
+ if (src == null) {
+ return grp;
+ }
+
+ return src;
+ }
+
+ /**
+ * Print out the multicast route table in it's entirety.
+ *
+ * TODO: Eventually we will have to implement paging and how to handle large tables.
+ * @return String
+ */
+ public String printMcastRouteTable() {
+ String out = this.toString() + "\n";
+
+ for (McastRouteGroup grp : mrib4.values()) {
+ out += grp.toString() + "\n";
+ for (McastRouteSource src : grp.getSources().values()) {
+ out += src.toString() + "\n";
+ }
+ }
+ return out;
+ }
+
+ /**
+ * Print out a summary of groups in the MRIB.
+ *
+ * @return String
+ */
+ public String toString() {
+ String out = "Mcast Route Table: ";
+ out += mrib4.size() + " IPv4 Multicast Groups\n";
+ if (ipv6Enabled) {
+ out += mrib6.size() + " IPv6 Multicast Groups\n";
+ }
+ return out;
+ }
+}
diff --git a/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/package-info.java b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/package-info.java
new file mode 100644
index 0000000..eaef5fc
--- /dev/null
+++ b/apps/mfwd/src/main/java/org/onosproject/mfwd/impl/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Sample Multicast forwarding framework using intents.
+ */
+package org.onosproject.mfwd.impl;
diff --git a/apps/mfwd/src/main/java/org/onosproject/mfwd/rest/McastResource.java b/apps/mfwd/src/main/java/org/onosproject/mfwd/rest/McastResource.java
new file mode 100644
index 0000000..878e21d
--- /dev/null
+++ b/apps/mfwd/src/main/java/org/onosproject/mfwd/rest/McastResource.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.mfwd.rest;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.onlab.packet.IpPrefix;
+import org.onlab.rest.BaseResource;
+import org.onosproject.mfwd.impl.McastRouteGroup;
+import org.onosproject.mfwd.impl.McastRouteTable;
+
+/**
+ * Rest API for Multicast Forwarding.
+ */
+@Path("mcast")
+public class McastResource extends BaseResource {
+
+ /**
+ * Retrieve the multicast route table.
+ * @return the multicast route table.
+ * @throws IOException if an error occurs
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response showAll() throws IOException {
+ ObjectMapper mapper = new ObjectMapper();
+ McastRouteTable mcastRouteTable = McastRouteTable.getInstance();
+ Map<IpPrefix, McastRouteGroup> map = mcastRouteTable.getMrib4();
+ return Response.ok(mapper.createObjectNode().toString()).build();
+ }
+
+ /**
+ * Static join of a multicast flow.
+ * @param input source, group, ingress connectPoint egress connectPoints
+ * @return status of static join
+ * @throws IOException if an error occurs
+ */
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response join(InputStream input) throws IOException {
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode cfg = mapper.readTree(input);
+ return null;
+ }
+}
diff --git a/apps/mfwd/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/mfwd/src/main/resources/OSGI-INF/blueprint/shell-config.xml
new file mode 100644
index 0000000..966cb4f
--- /dev/null
+++ b/apps/mfwd/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -0,0 +1,30 @@
+<!--
+ ~ Copyright 2014 Open Networking Laboratory
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
+
+ <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
+ <command>
+ <action class="org.onosproject.mfwd.cli.McastJoinCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.mfwd.cli.McastDeleteCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.mfwd.cli.McastShowCommand"/>
+ </command>
+ </command-bundle>
+
+</blueprint>
diff --git a/apps/mfwd/src/main/webapp/WEB-INF/web.xml b/apps/mfwd/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..c4c4f45
--- /dev/null
+++ b/apps/mfwd/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+~ Copyright 2014 Open Networking Laboratory
+~
+~ Licensed under the Apache License, Version 2.0 (the "License");
+~ you may not use this file except in compliance with the License.
+~ You may obtain a copy of the License at
+~
+~ http://www.apache.org/licenses/LICENSE-2.0
+~
+~ Unless required by applicable law or agreed to in writing, software
+~ distributed under the License is distributed on an "AS IS" BASIS,
+~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+~ See the License for the specific language governing permissions and
+~ limitations under the License.
+-->
+<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+ id="ONOS" version="2.5">
+ <display-name>ONOS APP MFWD</display-name>
+
+ <servlet>
+ <servlet-name>JAX-RS Service</servlet-name>
+ <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
+ <init-param>
+ <param-name>com.sun.jersey.config.property.resourceConfigClass</param-name>
+ <param-value>com.sun.jersey.api.core.ClassNamesResourceConfig</param-value>
+ </init-param>
+ <init-param>
+ <param-name>com.sun.jersey.config.property.classnames</param-name>
+ <param-value>
+ org.onosproject.mfwd.rest.McastResource
+ </param-value>
+ </init-param>
+ <load-on-startup>1</load-on-startup>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>JAX-RS Service</servlet-name>
+ <url-pattern>/*</url-pattern>
+ </servlet-mapping>
+
+</web-app>
diff --git a/apps/pim/pom.xml b/apps/pim/pom.xml
new file mode 100644
index 0000000..d28423c
--- /dev/null
+++ b/apps/pim/pom.xml
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2015 Open Networking Laboratory
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-apps</artifactId>
+ <version>1.4.0-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>onos-app-pim</artifactId>
+ <packaging>bundle</packaging>
+
+ <description>Protocol Independent Multicast Emulation</description>
+
+ <properties>
+ <onos.app.name>org.onosproject.pim</onos.app.name>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-cli</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onlab-osgi</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onlab-junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- This is needed by ComponentContext, used for tunable configuration -->
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.karaf.shell</groupId>
+ <artifactId>org.apache.karaf.shell.console</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.scr.annotations</artifactId>
+ <version>1.9.8</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-SymbolicName>
+ ${project.groupId}.${project.artifactId}
+ </Bundle-SymbolicName>
+ <Import-Package>
+ org.slf4j,
+ org.osgi.framework,
+ org.apache.commons.lang.math.*,
+ com.google.common.*,
+ org.onlab.packet.*,
+ org.onlab.rest.*,
+ org.onosproject.*,
+ org.onosproject.mfwd.impl.*;
+ org.onlab.util.*,
+ org.jboss.netty.util.*
+ </Import-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>2.5.1</version>
+ <configuration>
+ <source>1.8</source>
+ <target>1.8</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/apps/pim/src/main/java/org/onosproject/pim/cli/PIMShowCommand.java b/apps/pim/src/main/java/org/onosproject/pim/cli/PIMShowCommand.java
new file mode 100644
index 0000000..5effe45
--- /dev/null
+++ b/apps/pim/src/main/java/org/onosproject/pim/cli/PIMShowCommand.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.pim.cli;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.pim.impl.PIMNeighbors;
+import org.onosproject.pim.impl.PIMNeighborsCodec;
+
+import java.util.HashMap;
+
+@Command(scope = "onos", name = "pim-show", description = "Displays the pim neighbors")
+public class PIMShowCommand extends AbstractShellCommand {
+
+ // prints either the json or cli version of the hash map connect point
+ // neighbors from the PIMNeighbors class.
+ @Override
+ protected void execute() {
+ // grab connect point neighbors hash map to send in to json encoder.
+ HashMap<ConnectPoint, PIMNeighbors> pimNbrs = PIMNeighbors.getConnectPointNeighbors();
+ if (outputJson()) {
+ print("%s", json(pimNbrs));
+ } else {
+ print(PIMNeighbors.printPimNeighbors());
+ }
+ }
+
+ private JsonNode json(HashMap<ConnectPoint, PIMNeighbors> pimNbrs) {
+ return new PIMNeighborsCodec().encode(pimNbrs, this);
+ }
+
+}
\ No newline at end of file
diff --git a/apps/pim/src/main/java/org/onosproject/pim/cli/package-info.java b/apps/pim/src/main/java/org/onosproject/pim/cli/package-info.java
new file mode 100644
index 0000000..954dacb
--- /dev/null
+++ b/apps/pim/src/main/java/org/onosproject/pim/cli/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * PIM Multicast forwarding framework using intents.
+ */
+package org.onosproject.pim.cli;
\ No newline at end of file
diff --git a/apps/pim/src/main/java/org/onosproject/pim/impl/PIMComponent.java b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMComponent.java
new file mode 100644
index 0000000..bd5e148
--- /dev/null
+++ b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMComponent.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.pim.impl;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.PIM;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketPriority;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.slf4j.Logger;
+
+/**
+ * Protocol Independent Multicast Emulation.
+ */
+@Component(immediate = true)
+public class PIMComponent {
+ private final Logger log = getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected PacketService packetService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ private PIMPacketProcessor processor = new PIMPacketProcessor();
+ private static ApplicationId appId;
+
+ @Activate
+ public void activate() {
+ appId = coreService.registerApplication("org.onosproject.pim");
+
+ packetService.addProcessor(processor, PacketProcessor.director(1));
+
+ // Build a traffic selector for all multicast traffic
+ TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+ selector.matchEthType(Ethernet.TYPE_IPV4);
+ selector.matchIPProtocol(IPv4.PROTOCOL_PIM);
+ packetService.requestPackets(selector.build(), PacketPriority.REACTIVE, appId);
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ packetService.removeProcessor(processor);
+ processor = null;
+ log.info("Stopped");
+ }
+
+ /**
+ * Packet processor responsible for handling IGMP packets.
+ */
+ private class PIMPacketProcessor implements PacketProcessor {
+
+ @Override
+ public void process(PacketContext context) {
+ // Stop processing if the packet has been handled, since we
+ // can't do any more to it.
+ if (context.isHandled()) {
+ return;
+ }
+
+ InboundPacket pkt = context.inPacket();
+ if (pkt == null) {
+ return;
+ }
+
+ Ethernet ethPkt = pkt.parsed();
+ if (ethPkt == null) {
+ return;
+ }
+
+ /*
+ * IPv6 MLD packets are handled by ICMP6. We'll only deal
+ * with IPv4.
+ */
+ if (ethPkt.getEtherType() != Ethernet.TYPE_IPV4) {
+ return;
+ }
+
+ IPv4 ip = (IPv4) ethPkt.getPayload();
+ IpAddress gaddr = IpAddress.valueOf(ip.getDestinationAddress());
+ IpAddress saddr = Ip4Address.valueOf(ip.getSourceAddress());
+ log.debug("Packet (" + saddr.toString() + ", " + gaddr.toString() +
+ "\tingress port: " + context.inPacket().receivedFrom().toString());
+
+ if (ip.getProtocol() != IPv4.PROTOCOL_PIM) {
+ log.debug("PIM Picked up a non PIM packet: IP protocol: " + ip.getProtocol());
+ return;
+ }
+
+ // TODO: check incoming to be PIM.PIM_ADDRESS or "Our" address.
+ IpPrefix spfx = IpPrefix.valueOf(saddr, 32);
+ IpPrefix gpfx = IpPrefix.valueOf(gaddr, 32);
+
+ PIM pim = (PIM) ip.getPayload();
+ switch (pim.getPimMsgType()) {
+
+ case PIM.TYPE_HELLO:
+ PIMNeighbors.processHello(ethPkt, context.inPacket().receivedFrom());
+ break;
+
+ case PIM.TYPE_JOIN_PRUNE_REQUEST:
+ // Create the function
+ break;
+
+ case PIM.TYPE_ASSERT:
+ case PIM.TYPE_BOOTSTRAP:
+ case PIM.TYPE_CANDIDATE_RP_ADV:
+ case PIM.TYPE_GRAFT:
+ case PIM.TYPE_GRAFT_ACK:
+ case PIM.TYPE_REGISTER:
+ case PIM.TYPE_REGISTER_STOP:
+ log.debug("Unsupported PIM message type: " + pim.getPimMsgType());
+ break;
+
+ default:
+ log.debug("Unkown PIM message type: " + pim.getPimMsgType());
+ break;
+ }
+ }
+ }
+}
diff --git a/apps/pim/src/main/java/org/onosproject/pim/impl/PIMNeighbor.java b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMNeighbor.java
new file mode 100644
index 0000000..1a96138
--- /dev/null
+++ b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMNeighbor.java
@@ -0,0 +1,340 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in reliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.pim.impl;
+
+import org.jboss.netty.util.Timeout;
+import org.jboss.netty.util.TimerTask;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.pim.PIMHello;
+import org.onlab.packet.pim.PIMHelloOption;
+import org.onosproject.net.ConnectPoint;
+import org.slf4j.Logger;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.TimeUnit;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * PIMNeighbor represents all the PIM routers that have sent us
+ * hello messages, or that possibly have been statically configured.
+ */
+public class PIMNeighbor {
+ private final Logger log = getLogger(getClass());
+
+ // The primary address of this PIM neighbor
+ private IpAddress primaryAddr;
+
+ // The MacAddress of this neighbor
+ private MacAddress macAddress;
+
+ // The ConnectPoint this PIM neighbor is connected to.
+ private ConnectPoint connectPoint;
+
+ // Is this neighbor us?
+ private boolean isThisUs = false;
+
+ // The option values this neighbor has sent us.
+ private int priority = 0;
+ private int genId = 0;
+ private short holdtime = 0;
+
+ // Is this pim neighbor the DR?
+ private boolean isDr = false;
+
+ // Timeout for this neighbor
+ private volatile Timeout timeout;
+
+ private boolean reelect = false;
+
+ // A back pointer the neighbors list this neighbor belongs to.
+ private PIMNeighbors neighbors;
+
+ /**
+ * Construct this neighbor from the address and connect point.
+ *
+ * @param ipaddr IP Address of neighbor
+ * @param macaddr MAC Address of the neighbor
+ * @param cp The ConnectPoint of this neighbor
+ */
+ public PIMNeighbor(IpAddress ipaddr, MacAddress macaddr, ConnectPoint cp) {
+ this.macAddress = macaddr;
+ this.primaryAddr = ipaddr;
+ this.connectPoint = cp;
+ this.resetTimeout();
+ }
+
+ /**
+ * Get the primary address of this neighbor.
+ *
+ * @return the primary IP address.
+ */
+ public IpAddress getPrimaryAddr() {
+ return primaryAddr;
+ }
+
+ /**
+ * Set the primary address of this neighbor.
+ *
+ * @param primaryAddr the address we'll use when sending hello messages
+ */
+ public void setPrimaryAddr(IpAddress primaryAddr) {
+ this.primaryAddr = primaryAddr;
+ }
+
+ /**
+ * Get the priority this neighbor has advertised to us.
+ *
+ * @return the priority
+ */
+ public int getPriority() {
+ return priority;
+ }
+
+ /**
+ * Set the priority for this neighbor.
+ *
+ * @param priority This neighbors priority.
+ */
+ public void setPriority(int priority) {
+ this.priority = priority;
+ }
+
+ /**
+ * Get the generation ID.
+ *
+ * @return the generation ID.
+ */
+ public int getGenId() {
+ return genId;
+ }
+
+ /**
+ * Set the generation ID.
+ *
+ * @param genId the generation ID.
+ */
+ public void setGenId(int genId) {
+ this.genId = genId;
+ }
+
+ /**
+ * Get the holdtime for this neighbor.
+ *
+ * @return the holdtime
+ */
+ public short getHoldtime() {
+ return holdtime;
+ }
+
+ /**
+ * Set the holdtime for this neighbor.
+ *
+ * @param holdtime the holdtime.
+ */
+ public void setholdtime(short holdtime) {
+ this.holdtime = holdtime;
+ }
+
+ /**
+ * Is this neighbor the designated router on this connect point?
+ *
+ * @return true if so, false if not.
+ */
+ public boolean isDr() {
+ return isDr;
+ }
+
+ /**
+ * Set this router as the designated router on this connect point.
+ *
+ * @param isDr True is this neighbor is the DR false otherwise
+ */
+ public void setIsDr(boolean isDr) {
+ this.isDr = isDr;
+ }
+
+ /**
+ * The ConnectPoint this neighbor is connected to.
+ *
+ * @return the ConnectPoint
+ */
+ public ConnectPoint getConnectPoint() {
+ return connectPoint;
+ }
+
+ /**
+ * Set the ConnectPoint this router is connected to.
+ *
+ * @param connectPoint the ConnectPoint this router is connected to.
+ */
+ public void setConnectPoint(ConnectPoint connectPoint) {
+ this.connectPoint = connectPoint;
+ }
+
+ /**
+ * Set a back pointer to the neighbors list this neighbor is a member of.
+ *
+ * @param neighbors the neighbor list this neighbor belongs to
+ */
+ public void setNeighbors(PIMNeighbors neighbors) {
+ this.neighbors = neighbors;
+ }
+
+ /**
+ * We have received a fresh hello from a neighbor, now we need to process it.
+ * Depending on the values received in the the hello options may force a
+ * re-election process.
+ *
+ * We will also refresh the timeout for this neighbor.
+ *
+ * @param hello copy of the hello we'll be able to extract options from.
+ */
+ public void refresh(PIMHello hello) {
+ checkNotNull(hello);
+
+ for (PIMHelloOption opt : hello.getOptions().values()) {
+
+ int len = opt.getOptLength();
+ byte [] value = new byte[len];
+ ByteBuffer bb = ByteBuffer.wrap(value);
+
+ switch (opt.getOptType()) {
+ case PIMHelloOption.OPT_GENID:
+ int newid = bb.getInt();
+ if (this.genId != newid) {
+ // TODO: we have a newly rebooted neighbor. Send them our joins.
+ this.genId = newid;
+ }
+ break;
+
+ case PIMHelloOption.OPT_PRIORITY:
+ int newpri = bb.getInt();
+ if (this.priority != newpri) {
+
+ // The priorities have changed. We may need to re-elect a new DR?
+ if (this.isDr || this.neighbors.getDesignatedRouter().getPriority() < priority) {
+ reelect = true;
+ }
+ this.priority = newpri;
+ }
+ break;
+
+ case PIMHelloOption.OPT_HOLDTIME:
+ short holdtime = bb.getShort();
+ if (this.holdtime != holdtime) {
+ this.holdtime = holdtime;
+ if (holdtime == 0) {
+ // We have a neighbor going down. We can remove all joins
+ // we have learned from them.
+ // TODO: What else do we need to do when a neighbor goes down?
+
+ log.debug("PIM Neighbor has timed out: {}", this.primaryAddr.toString());
+ return;
+ }
+ }
+ break;
+
+ case PIMHelloOption.OPT_PRUNEDELAY:
+ case PIMHelloOption.OPT_ADDRLIST:
+ // TODO: implement prune delay and addr list. Fall through for now.
+
+ default:
+ log.debug("PIM Hello option type: {} not yet supported or unknown.", opt.getOptType());
+ break;
+ }
+ }
+
+ if (reelect) {
+ this.neighbors.electDR(this);
+ }
+
+ // Reset the next timeout timer
+ this.resetTimeout();
+ }
+
+ /* --------------------------------------- Timer functions -------------------------- */
+
+ /**
+ * Restart the timeout task for this neighbor.
+ */
+ private void resetTimeout() {
+
+ if (this.holdtime == 0) {
+
+ // Prepare to die.
+ log.debug("shutting down timer for nbr {}", this.primaryAddr.toString());
+ if (this.timeout != null) {
+ this.timeout.cancel();
+ this.timeout = null;
+ }
+ return;
+ }
+
+ // Cancel the existing timeout and start a fresh new one.
+ if (this.timeout != null) {
+ this.timeout.cancel();
+ }
+
+ this.timeout = PIMTimer.getTimer().newTimeout(new NeighborTimeoutTask(this), holdtime, TimeUnit.SECONDS);
+ }
+
+ /**
+ * The task to run when a neighbor timeout expires.
+ */
+ private final class NeighborTimeoutTask implements TimerTask {
+ PIMNeighbor nbr;
+
+ NeighborTimeoutTask(PIMNeighbor nbr) {
+ this.nbr = nbr;
+ }
+
+ @Override
+ public void run(Timeout timeout) throws Exception {
+
+ // TODO: log.debug;
+ PIMNeighbors neighbors = nbr.neighbors;
+ neighbors.removeNeighbor(nbr.getPrimaryAddr());
+ }
+ }
+
+ /**
+ * Stop the timeout timer.
+ *
+ * This happens when we remove the neighbor.
+ */
+ private final void stopTimeout() {
+ this.timeout.cancel();
+ this.timeout = null;
+ }
+
+ @Override
+ public String toString() {
+ String out = "";
+ if (this.isDr) {
+ out += "*NBR:";
+ } else {
+ out += "NBR:";
+ }
+ out += "\tIP: " + this.primaryAddr.toString();
+ out += "\tPr: " + String.valueOf(this.priority);
+ out += "\tHoldTime: " + String.valueOf(this.holdtime);
+ out += "\tGenID: " + String.valueOf(this.genId) + "\n";
+ return out;
+ }
+}
\ No newline at end of file
diff --git a/apps/pim/src/main/java/org/onosproject/pim/impl/PIMNeighbors.java b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMNeighbors.java
new file mode 100644
index 0000000..cad9076
--- /dev/null
+++ b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMNeighbors.java
@@ -0,0 +1,395 @@
+
+package org.onosproject.pim.impl;
+
+import org.jboss.netty.util.Timeout;
+import org.jboss.netty.util.TimerTask;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.PIM;
+import org.onlab.packet.pim.PIMHello;
+import org.onosproject.net.ConnectPoint;
+import java.util.HashMap;
+import java.util.concurrent.TimeUnit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * PIMNeighbors is a collection of all neighbors we have received
+ * PIM hello messages from. The main structure is a HashMap indexed
+ * by ConnectPoint with another HashMap indexed on the PIM neighbors
+ * IPAddress, it contains all PIM neighbors attached on that ConnectPoint.
+ */
+public final class PIMNeighbors {
+
+ private static Logger log = LoggerFactory.getLogger("PIMNeighbors");
+
+ /**
+ * This is the global container for all PIM neighbors indexed by ConnectPoints.
+ *
+ * NOTE: We'll have a problem if the same neighbor can show up on two interfaces
+ * but that should never happen.
+ */
+ private static HashMap<ConnectPoint, PIMNeighbors> connectPointNeighbors = new HashMap<>();
+
+ // The connect point these neighbors are connected to.
+ private ConnectPoint connectPoint;
+
+ // Pointer to the current designated router on this ConnectPoint.
+ private PIMNeighbor designatedRouter;
+
+ // The list of neighbors we have learned on this ConnectPoint.
+ private HashMap<IpAddress, PIMNeighbor> neighbors = new HashMap<>();
+
+ /*
+ * TODO: turn ourIpAddress, ourPriority and OurHoldTime into config options.
+ */
+ // The IP address we are using to source our PIM hello messages on this connect Point.
+ private IpAddress ourIpAddress;
+
+ // The priority we use on this ConnectPoint.
+ private int ourPriority = 1;
+
+ // The holdtime we are sending out.
+ private int ourHoldtime = 105;
+
+ // Then generation ID we are sending out. 0 means we need to generate a new random ID
+ private int ourGenid = 0;
+
+ // Hello Timer for sending hello messages per ConnectPoint with neighbors.
+ private volatile Timeout helloTimer;
+
+ // The period of which we will be sending out PIM hello messages.
+ private final int defaultPimHelloInterval = 30; // seconds
+
+ /**
+ * Create PIMNeighbors object per ConnectPoint.
+ *
+ * @param cp the ConnectPoint.
+ * @return PIMNeighbors structure
+ */
+ public static PIMNeighbors getConnectPointNeighbors(ConnectPoint cp) {
+ return connectPointNeighbors.get(cp);
+ }
+
+ /**
+ * Process incoming hello message, we will need the Macaddress and IP address of the sender.
+ *
+ * @param ethPkt the ethernet header
+ * @param receivedFrom the connect point we recieved this message from
+ */
+ public static void processHello(Ethernet ethPkt, ConnectPoint receivedFrom) {
+ checkNotNull(ethPkt);
+ checkNotNull(ethPkt);
+
+ MacAddress srcmac = ethPkt.getSourceMAC();
+ IPv4 ip = (IPv4) ethPkt.getPayload();
+ Ip4Address srcip = Ip4Address.valueOf(ip.getSourceAddress());
+
+ PIM pim = (PIM) ip.getPayload();
+ checkNotNull(pim);
+
+ PIMHello hello = (PIMHello) pim.getPayload();
+ checkNotNull(hello);
+
+ PIMNeighbor nbr = PIMNeighbors.findOrCreate(srcip, srcmac, receivedFrom);
+ if (nbr == null) {
+ log.error("Could not create a neighbor for: {1}", srcip.toString());
+ return;
+ }
+
+ nbr.setConnectPoint(receivedFrom);
+ nbr.refresh(hello);
+ }
+
+ /**
+ * Create a PIM Neighbor.
+ *
+ * @param cp The ConnectPoint this neighbor was found on
+ */
+ public PIMNeighbors(ConnectPoint cp) {
+ this.connectPoint = cp;
+
+ // TODO: use network config to assign address.
+ this.ourIpAddress = IpAddress.valueOf("10.2.2.2");
+ this.addIpAddress(this.ourIpAddress);
+ }
+
+ /**
+ * Create a PIM neighbor.
+ *
+ * @param cp the ConnectPoint this neighbor was found on
+ * @param ourIp the IP address of this neighbor
+ */
+ public PIMNeighbors(ConnectPoint cp, IpAddress ourIp) {
+ this.connectPoint = cp;
+ this.addIpAddress(ourIp);
+ }
+
+ /**
+ * Start the hello timer when we have been given an IP address.
+ *
+ * @param ourIp our IP address.
+ */
+ public void addIpAddress(IpAddress ourIp) {
+ this.startHelloTimer();
+
+ // Kick off the first pim hello packet
+ this.sendHelloPacket();
+ }
+
+ /**
+ * Getter for our IP address.
+ *
+ * @return our IP address.
+ */
+ public IpAddress getOurIpAddress() {
+ return this.ourIpAddress;
+ }
+
+ /**
+ * Get our priority.
+ *
+ * @return our priority.
+ */
+ public int getOurPriority() {
+ return this.ourPriority;
+ }
+
+ /**
+ * Get the neighbor list for this specific connectPoint.
+ *
+ * @return PIM neighbors on this ConnectPoint
+ */
+ public HashMap<IpAddress, PIMNeighbor> getOurNeighborsList() {
+ return this.neighbors;
+ }
+
+ /**
+ * Get the designated router on this connection.
+ *
+ * @return the PIMNeighbor representing the DR
+ */
+ public PIMNeighbor getDesignatedRouter() {
+ return designatedRouter;
+ }
+
+ /**
+ * Are we the DR on this CP?
+ *
+ * @return true if we are, false if not
+ */
+ public boolean weAreTheDr() {
+ return (designatedRouter != null &&
+ designatedRouter.getPrimaryAddr().equals(ourIpAddress));
+ }
+
+ /**
+ * Find the neighbor with the given IP address on this CP.
+ *
+ * @param ipaddr the IP address of the neighbor we are interested in
+ * @return the pim neighbor if it exists
+ */
+ public PIMNeighbor findNeighbor(IpAddress ipaddr) {
+ PIMNeighbor nbr = neighbors.get(ipaddr);
+ return nbr;
+ }
+
+ /**
+ * Add a new PIM neighbor to this list.
+ *
+ * @param nbr the neighbor to be added.
+ */
+ public void addNeighbor(PIMNeighbor nbr) {
+ if (neighbors.containsKey(nbr.getPrimaryAddr())) {
+
+ // TODO: Hmmm, how should this be handled?
+ log.debug("We are adding a neighbor that already exists: {}", nbr.toString());
+ neighbors.remove(nbr.getPrimaryAddr(), nbr);
+ }
+ nbr.setNeighbors(this);
+ neighbors.put(nbr.getPrimaryAddr(), nbr);
+ }
+
+ /**
+ * Remove the neighbor from our neighbor list.
+ *
+ * @param ipaddr the IP address of the neighbor to remove
+ */
+ public void removeNeighbor(IpAddress ipaddr) {
+
+ boolean reelect = (designatedRouter == null || designatedRouter.getPrimaryAddr().equals(ipaddr));
+ if (neighbors.containsKey(ipaddr)) {
+ neighbors.remove(ipaddr);
+ }
+ this.electDR();
+ }
+
+ /**
+ * Remove the given neighbor from the neighbor list.
+ *
+ * @param nbr the nbr to be removed.
+ */
+ public void removeNeighbor(PIMNeighbor nbr) {
+
+ boolean reelect = (designatedRouter == null || nbr.isDr());
+ neighbors.remove(nbr.getPrimaryAddr(), nbr);
+ this.electDR();
+ }
+
+ /**
+ * Elect a new DR on this ConnectPoint.
+ *
+ * @return the PIM Neighbor that wins
+ */
+ public PIMNeighbor electDR() {
+
+ for (PIMNeighbor nbr : this.neighbors.values()) {
+ if (this.designatedRouter == null) {
+ this.designatedRouter = nbr;
+ continue;
+ }
+
+ if (nbr.getPriority() > this.designatedRouter.getPriority()) {
+ this.designatedRouter = nbr;
+ continue;
+ }
+
+ // We could sort in ascending order
+ if (this.designatedRouter.getPrimaryAddr().compareTo(nbr.getPrimaryAddr()) > 0) {
+ this.designatedRouter = nbr;
+ continue;
+ }
+ }
+
+ return this.designatedRouter;
+ }
+
+ /**
+ * Elect a new DR given the new neighbor.
+ *
+ * @param nbr the new neighbor to use in DR election.
+ * @return the PIM Neighbor that wins DR election
+ */
+ public PIMNeighbor electDR(PIMNeighbor nbr) {
+
+ // Make sure I have
+ if (this.designatedRouter == null ||
+ this.designatedRouter.getPriority() < nbr.getPriority() ||
+ this.designatedRouter.getPrimaryAddr().compareTo(nbr.getPrimaryAddr()) > 0) {
+ this.designatedRouter = nbr;
+ }
+ return this.designatedRouter;
+ }
+
+ /**
+ * Find or create a pim neighbor with a given ip address and connect point.
+ *
+ * @param ipaddr of the pim neighbor
+ * @param mac The mac address of our sending neighbor
+ * @param cp the connect point the neighbor was learned from
+ * @return an existing or new PIM neighbor
+ */
+ public static PIMNeighbor findOrCreate(IpAddress ipaddr, MacAddress mac, ConnectPoint cp) {
+ PIMNeighbors neighbors = connectPointNeighbors.get(cp);
+ if (neighbors == null) {
+ neighbors = new PIMNeighbors(cp);
+ connectPointNeighbors.put(cp, neighbors);
+ }
+
+ PIMNeighbor nbr = neighbors.findNeighbor(ipaddr);
+ if (nbr == null) {
+ nbr = new PIMNeighbor(ipaddr, mac, cp);
+ neighbors.addNeighbor(nbr);
+ neighbors.electDR(nbr);
+ }
+ return nbr;
+ }
+
+ // Returns the connect point neighbors hash map
+ public static HashMap<ConnectPoint, PIMNeighbors> getConnectPointNeighbors() {
+ return connectPointNeighbors;
+ }
+
+ /* ---------------------------------- PIM Hello Timer ----------------------------------- */
+
+ /**
+ * Start a new hello timer for this ConnectPoint.
+ */
+ private void startHelloTimer() {
+ this.helloTimer = PIMTimer.getTimer().newTimeout(
+ new HelloTimer(this),
+ this.defaultPimHelloInterval,
+ TimeUnit.SECONDS);
+
+ log.trace("Started Hello Timer: " + this.ourIpAddress.toString());
+ }
+
+ /**
+ * This inner class handles transmitting a PIM hello message on this ConnectPoint.
+ */
+ private final class HelloTimer implements TimerTask {
+ PIMNeighbors neighbors;
+
+ HelloTimer(PIMNeighbors neighbors) {
+ this.neighbors = neighbors;
+ }
+
+ @Override
+ public void run(Timeout timeout) throws Exception {
+
+ // Send off a hello packet
+ sendHelloPacket();
+
+ // restart the hello timer
+ neighbors.startHelloTimer();
+ }
+ }
+
+ private void sendHelloPacket() {
+ PIMHello hello = new PIMHello();
+
+ // TODO: we will need to implement the network config service to assign ip addresses & options
+ /*
+ hello.createDefaultOptions();
+
+ Ethernet eth = hello.createPIMHello(this.ourIpAddress);
+ hello.sendPacket(this.connectPoint);
+ */
+ }
+
+ /**
+ * prints the connectPointNeighbors list with each neighbor list.
+ *
+ * @return string of neighbors.
+ */
+ public static String printPimNeighbors() {
+ String out = "PIM Neighbors Table: \n";
+
+ for (PIMNeighbors pn: connectPointNeighbors.values()) {
+
+ out += "CP:\n " + pn.toString();
+ for (PIMNeighbor nbr : pn.neighbors.values()) {
+ out += "\t" + nbr.toString();
+ }
+ }
+ return out;
+ }
+
+ @Override
+ public String toString() {
+ String out = "PIM Neighbors: ";
+ if (this.ourIpAddress != null) {
+ out += "IP: " + this.ourIpAddress.toString();
+ } else {
+ out += "IP: *Null*";
+ }
+ out += "\tPR: " + String.valueOf(this.ourPriority) + "\n";
+ return out;
+ }
+}
\ No newline at end of file
diff --git a/apps/pim/src/main/java/org/onosproject/pim/impl/PIMNeighborsCodec.java b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMNeighborsCodec.java
new file mode 100644
index 0000000..ee62eb7
--- /dev/null
+++ b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMNeighborsCodec.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.pim.impl;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.ConnectPoint;
+
+import java.util.HashMap;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * PIM neighbors Codec.
+ */
+public class PIMNeighborsCodec extends JsonCodec<HashMap<ConnectPoint, PIMNeighbors>> {
+ // JSON field names
+ //Return Name
+ private static final String CPNBRLIST = "connect_point_list";
+
+ // PIM Neightbors Fields
+ private static final String IP = "ip";
+ private static final String PRIORITY = "priority";
+ private static final String NBRLIST = "neighbor_list";
+
+ // PIM neighbor Files
+ private static final String DR = "designated";
+ private static final String NBR_IP = "ip";
+ private static final String PR = "priority";
+ private static final String HOLDTIME = "hold_time";
+
+ /**
+ * Encode the PIM Neighbors.
+ *
+ * @param cpn ConnectPoint neighbors
+ * @param context encoding context
+ *
+ * @return Encoded neighbors used by CLI and REST
+ */
+ @Override
+ public ObjectNode encode(HashMap<ConnectPoint, PIMNeighbors> cpn, CodecContext context) {
+ checkNotNull(cpn, "Pim Neighbors cannot be null");
+
+ ObjectNode pimNbrJsonCodec = context.mapper().createObjectNode();
+ ArrayNode cpnList = context.mapper().createArrayNode();
+
+ for (PIMNeighbors pn: cpn.values()) {
+ // get the PimNeighbors Obj, contains Neighbors list
+ // create the json object for a single Entry in the Neighbors list
+ ObjectNode cp = context.mapper().createObjectNode();
+ cp.put(IP, pn.getOurIpAddress().toString());
+ cp.put(PRIORITY, String.valueOf(pn.getOurPriority()));
+
+ // create the array for the neighbors list
+ ArrayNode nbrsList = context.mapper().createArrayNode();
+ for (PIMNeighbor nbr : pn.getOurNeighborsList().values()) {
+ nbrsList.add(neighbor(nbr, context));
+ }
+ // adds pim neighbor to list
+ cp.set(NBRLIST, nbrsList);
+ // adds to arraynode which will represent the connect point neighbors hash map.
+ cpnList.add(cp);
+ }
+ pimNbrJsonCodec.set(CPNBRLIST, cpnList);
+ return pimNbrJsonCodec;
+ }
+
+ /**
+ * Encode a single PIM Neighbor.
+ *
+ * @param nbr the neighbor to be encoded
+ * @param context encoding context
+ * @return the encoded neighbor
+ */
+ private ObjectNode neighbor(PIMNeighbor nbr, CodecContext context) {
+ return context.mapper().createObjectNode()
+ .put(DR, Boolean.toString(nbr.isDr()))
+ .put(NBR_IP, nbr.getPrimaryAddr().toString())
+ .put(PR, String.valueOf(nbr.getPriority()))
+ .put(HOLDTIME, String.valueOf(nbr.getHoldtime()));
+ }
+}
diff --git a/apps/pim/src/main/java/org/onosproject/pim/impl/PIMTimer.java b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMTimer.java
new file mode 100644
index 0000000..c131a53
--- /dev/null
+++ b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMTimer.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.pim.impl;
+
+import org.jboss.netty.util.HashedWheelTimer;
+
+/**
+ * PIM Timer used for PIM Neighbors.
+ */
+public final class PIMTimer {
+
+ private static volatile HashedWheelTimer timer;
+
+ // Ban public construction
+ private PIMTimer() {
+ }
+
+ /**
+ * Returns the singleton hashed-wheel timer.
+ *
+ * @return hashed-wheel timer
+ */
+ public static HashedWheelTimer getTimer() {
+ if (PIMTimer.timer == null) {
+ initTimer();
+ }
+ return PIMTimer.timer;
+ }
+
+ // Start the PIM timer.
+ private static synchronized void initTimer() {
+ if (PIMTimer.timer == null) {
+
+ // Create and start a new hashed wheel timer, if it does not exist.
+ HashedWheelTimer hwTimer = new HashedWheelTimer();
+ hwTimer.start();
+ PIMTimer.timer = hwTimer;
+ }
+ }
+}
diff --git a/apps/pim/src/main/java/org/onosproject/pim/impl/package-info.java b/apps/pim/src/main/java/org/onosproject/pim/impl/package-info.java
new file mode 100644
index 0000000..29d1ce4
--- /dev/null
+++ b/apps/pim/src/main/java/org/onosproject/pim/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * PIM Emulation speak hello messages and listen to Join/Prunes.
+ */
+package org.onosproject.pim.impl;
diff --git a/apps/pim/src/main/resources/OSGI-INF.blueprint/shell-config.xml b/apps/pim/src/main/resources/OSGI-INF.blueprint/shell-config.xml
new file mode 100644
index 0000000..c30e379
--- /dev/null
+++ b/apps/pim/src/main/resources/OSGI-INF.blueprint/shell-config.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright 2015 Open Networking Laboratory
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
+
+ <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
+ <command>
+ <action class="org.onosproject.pim.cli.PIMShowCommand"/>
+ </command>
+ </command-bundle>
+
+</blueprint>
diff --git a/apps/pom.xml b/apps/pom.xml
index 334c690..611ac0b 100644
--- a/apps/pom.xml
+++ b/apps/pom.xml
@@ -58,6 +58,9 @@
<module>vtnweb</module>
<module>dhcp</module>
<module>cordvtn</module>
+ <module>mfwd</module>
+ <module>igmp</module>
+ <module>pim</module>
</modules>
<properties>