Merge branch 'igmp-to-cord' of /Users/ash/work/onos-next
diff --git a/BUCK b/BUCK
new file mode 100644
index 0000000..9310e9e
--- /dev/null
+++ b/BUCK
@@ -0,0 +1,24 @@
+COMPILE_DEPS = [
+ '//lib:CORE_DEPS',
+ '//lib:org.apache.karaf.shell.console',
+ '//cli:onos-cli',
+ '//apps/olt:onos-apps-olt-api',
+ '//apps/cordconfig:onos-apps-cordconfig',
+]
+
+BUNDLES = [
+ '//apps/olt:onos-apps-olt-api',
+ '//apps/igmp:onos-apps-igmp',
+]
+
+osgi_jar_with_tests (
+ deps = COMPILE_DEPS,
+)
+
+onos_app (
+ title = 'IGMP App',
+ category = 'Traffic Steering',
+ url = 'http://onosproject.org',
+ description = 'Internet Group Message Protocol',
+ included_bundles = BUNDLES,
+)
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..416c483
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2015-present 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.7.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>
+ <onos.app.title>IGMP App</onos.app.title>
+ <onos.app.category>Traffic Steering</onos.app.category>
+ <onos.app.url>http://onosproject.org</onos.app.url>
+ <onos.app.readme>IGMP implementation.</onos.app.readme>
+ <onos.app.requires>org.onosproject.cord-config</onos.app.requires>
+ </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>onos-app-olt-api</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>onos-cord-config</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>
+ <scope>provided</scope>
+ </dependency>
+
+ </dependencies>
+
+</project>
diff --git a/src/main/java/org/onosproject/igmp/IgmpSnoop.java b/src/main/java/org/onosproject/igmp/IgmpSnoop.java
new file mode 100644
index 0000000..aba5bbf
--- /dev/null
+++ b/src/main/java/org/onosproject/igmp/IgmpSnoop.java
@@ -0,0 +1,577 @@
+/*
+ * Copyright 2015-present 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;
+
+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.Modified;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.EthType;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IGMP;
+import org.onlab.packet.IGMPMembership;
+import org.onlab.packet.IGMPQuery;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.util.SafeRecurringTask;
+import org.onlab.util.Tools;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.cordconfig.access.AccessDeviceConfig;
+import org.onosproject.cordconfig.access.AccessDeviceData;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.config.ConfigFactory;
+import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.config.NetworkConfigListener;
+import org.onosproject.net.config.NetworkConfigRegistry;
+import org.onosproject.net.config.basics.SubjectFactories;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criteria;
+import org.onosproject.net.flowobjective.DefaultFilteringObjective;
+import org.onosproject.net.flowobjective.FilteringObjective;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.Objective;
+import org.onosproject.net.flowobjective.ObjectiveContext;
+import org.onosproject.net.flowobjective.ObjectiveError;
+import org.onosproject.net.mcast.McastRoute;
+import org.onosproject.net.mcast.MulticastRouteService;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import static org.onlab.util.Tools.groupedThreads;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Internet Group Management Protocol.
+ */
+@Component(immediate = true)
+public class IgmpSnoop {
+
+ private final Logger log = getLogger(getClass());
+
+ private static final String DEST_MAC = "01:00:5E:00:00:01";
+ private static final String DEST_IP = "224.0.0.1";
+
+ private static final int DEFAULT_QUERY_PERIOD_SECS = 60;
+ private static final byte DEFAULT_IGMP_RESP_CODE = 100;
+
+ @Property(name = "multicastAddress",
+ label = "Define the multicast base range to listen to")
+ private String multicastAddress = IpPrefix.IPV4_MULTICAST_PREFIX.toString();
+
+ @Property(name = "queryPeriod", intValue = DEFAULT_QUERY_PERIOD_SECS,
+ label = "Delay in seconds between successive query runs")
+ private int queryPeriod = DEFAULT_QUERY_PERIOD_SECS;
+
+ @Property(name = "maxRespCode", byteValue = DEFAULT_IGMP_RESP_CODE,
+ label = "Maximum time allowed before sending a responding report")
+ private byte maxRespCode = DEFAULT_IGMP_RESP_CODE;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected FlowObjectiveService flowObjectiveService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected PacketService packetService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected NetworkConfigRegistry networkConfig;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ComponentConfigService componentConfigService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected MulticastRouteService multicastService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DeviceService deviceService;
+
+
+ private ScheduledFuture<?> queryTask;
+ private final ScheduledExecutorService queryService =
+ Executors.newSingleThreadScheduledExecutor(groupedThreads("onos/igmp-query",
+ "membership-query"));
+
+ private Map<DeviceId, AccessDeviceData> oltData = new ConcurrentHashMap<>();
+
+ private Map<IpAddress, IpAddress> ssmTranslateTable = new ConcurrentHashMap<>();
+
+ private DeviceListener deviceListener = new InternalDeviceListener();
+ private IgmpPacketProcessor processor = new IgmpPacketProcessor();
+ private static ApplicationId appId;
+
+ private InternalNetworkConfigListener configListener =
+ new InternalNetworkConfigListener();
+
+ private static final Class<AccessDeviceConfig> CONFIG_CLASS =
+ AccessDeviceConfig.class;
+
+ private static final Class<IgmpSsmTranslateConfig> SSM_TRANSLATE_CONFIG_CLASS =
+ IgmpSsmTranslateConfig.class;
+
+ private ConfigFactory<DeviceId, AccessDeviceConfig> configFactory =
+ new ConfigFactory<DeviceId, AccessDeviceConfig>(
+ SubjectFactories.DEVICE_SUBJECT_FACTORY, CONFIG_CLASS, "accessDevice") {
+ @Override
+ public AccessDeviceConfig createConfig() {
+ return new AccessDeviceConfig();
+ }
+ };
+
+ private ConfigFactory<ApplicationId, IgmpSsmTranslateConfig> ssmTranslateConfigFactory =
+ new ConfigFactory<ApplicationId, IgmpSsmTranslateConfig>(
+ SubjectFactories.APP_SUBJECT_FACTORY, SSM_TRANSLATE_CONFIG_CLASS, "ssmTranslate", true) {
+ @Override
+ public IgmpSsmTranslateConfig createConfig() {
+ return new IgmpSsmTranslateConfig();
+ }
+ };
+
+
+ private ByteBuffer queryPacket;
+
+
+ @Activate
+ public void activate(ComponentContext context) {
+ componentConfigService.registerProperties(getClass());
+ modified(context);
+
+ appId = coreService.registerApplication("org.onosproject.igmp");
+
+ packetService.addProcessor(processor, PacketProcessor.director(1));
+
+ networkConfig.registerConfigFactory(configFactory);
+ networkConfig.registerConfigFactory(ssmTranslateConfigFactory);
+ networkConfig.addListener(configListener);
+
+ networkConfig.getSubjects(DeviceId.class, AccessDeviceConfig.class).forEach(
+ subject -> {
+ AccessDeviceConfig config = networkConfig.getConfig(subject,
+ AccessDeviceConfig.class);
+ if (config != null) {
+ AccessDeviceData data = config.getOlt();
+ oltData.put(data.deviceId(), data);
+
+ }
+ }
+ );
+
+ IgmpSsmTranslateConfig ssmTranslateConfig =
+ networkConfig.getConfig(appId, IgmpSsmTranslateConfig.class);
+
+ if (ssmTranslateConfig != null) {
+ Collection<McastRoute> translations = ssmTranslateConfig.getSsmTranslations();
+ for (McastRoute route : translations) {
+ ssmTranslateTable.put(route.group(), route.source());
+ }
+ }
+
+ oltData.keySet().stream()
+ .flatMap(did -> deviceService.getPorts(did).stream())
+ .filter(p -> !oltData.get(p.element().id()).uplink().equals(p.number()))
+ .filter(p -> p.isEnabled())
+ .forEach(p -> processFilterObjective((DeviceId) p.element().id(), p, false));
+
+ deviceService.addListener(deviceListener);
+
+ restartQueryTask();
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ packetService.removeProcessor(processor);
+ processor = null;
+ deviceService.removeListener(deviceListener);
+ networkConfig.removeListener(configListener);
+ networkConfig.unregisterConfigFactory(configFactory);
+ networkConfig.unregisterConfigFactory(ssmTranslateConfigFactory);
+ queryTask.cancel(true);
+ queryService.shutdownNow();
+ componentConfigService.unregisterProperties(getClass(), false);
+ log.info("Stopped");
+ }
+
+ @Modified
+ protected void modified(ComponentContext context) {
+ Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();
+
+ // TODO read multicastAddress from config
+ String strQueryPeriod = Tools.get(properties, "queryPeriod");
+ String strResponseCode = Tools.get(properties, "maxRespCode");
+ try {
+ byte newMaxRespCode = Byte.parseByte(strResponseCode);
+ if (maxRespCode != newMaxRespCode) {
+ maxRespCode = newMaxRespCode;
+ queryPacket = buildQueryPacket();
+ }
+
+ int newQueryPeriod = Integer.parseInt(strQueryPeriod);
+ if (newQueryPeriod != queryPeriod) {
+ queryPeriod = newQueryPeriod;
+ restartQueryTask();
+ }
+
+ } catch (NumberFormatException e) {
+ log.warn("Error parsing config input", e);
+ }
+
+ log.info("queryPeriod set to {}", queryPeriod);
+ log.info("maxRespCode set to {}", maxRespCode);
+ }
+
+ private void restartQueryTask() {
+ if (queryTask != null) {
+ queryTask.cancel(true);
+ }
+ queryPacket = buildQueryPacket();
+ queryTask = queryService.scheduleWithFixedDelay(
+ SafeRecurringTask.wrap(this::querySubscribers),
+ 0,
+ queryPeriod,
+ TimeUnit.SECONDS);
+ }
+
+ private void processFilterObjective(DeviceId devId, Port port, boolean remove) {
+
+ //TODO migrate to packet requests when packet service uses filtering objectives
+ DefaultFilteringObjective.Builder builder = DefaultFilteringObjective.builder();
+
+ builder = remove ? builder.deny() : builder.permit();
+
+ FilteringObjective igmp = builder
+ .withKey(Criteria.matchInPort(port.number()))
+ .addCondition(Criteria.matchEthType(EthType.EtherType.IPV4.ethType()))
+ .addCondition(Criteria.matchIPProtocol(IPv4.PROTOCOL_IGMP))
+ .withMeta(DefaultTrafficTreatment.builder()
+ .setOutput(PortNumber.CONTROLLER).build())
+ .fromApp(appId)
+ .withPriority(10000)
+ .add(new ObjectiveContext() {
+ @Override
+ public void onSuccess(Objective objective) {
+ log.info("Igmp filter for {} on {} installed.",
+ devId, port);
+ }
+
+ @Override
+ public void onError(Objective objective, ObjectiveError error) {
+ log.info("Igmp filter for {} on {} failed because {}.",
+ devId, port, error);
+ }
+ });
+
+ flowObjectiveService.filter(devId, igmp);
+ }
+
+ private void processMembership(IGMP pkt, ConnectPoint location) {
+ pkt.getGroups().forEach(group -> {
+
+ if (!(group instanceof IGMPMembership)) {
+ log.warn("Wrong group type in IGMP membership");
+ return;
+ }
+
+ IGMPMembership membership = (IGMPMembership) group;
+
+ IpAddress groupAddress = membership.getGaddr();
+
+ if (membership.getRecordType() == IGMPMembership.MODE_IS_INCLUDE ||
+ membership.getRecordType() == IGMPMembership.CHANGE_TO_INCLUDE_MODE) {
+
+ if (membership.getSources().isEmpty()) {
+ McastRoute route = ssmTranslateRoute(groupAddress);
+ if (route != null) {
+ removeRoute(route, location);
+ }
+ } else {
+ membership.getSources().stream()
+ .map(source -> new McastRoute(source, groupAddress, McastRoute.Type.IGMP))
+ .forEach(route -> addRoute(route, location));
+ }
+ } else if (membership.getRecordType() == IGMPMembership.MODE_IS_EXCLUDE ||
+ membership.getRecordType() == IGMPMembership.CHANGE_TO_EXCLUDE_MODE) {
+
+ if (membership.getSources().isEmpty()) {
+ McastRoute route = ssmTranslateRoute(groupAddress);
+ if (route != null) {
+ addRoute(route, location);
+ }
+ } else {
+ membership.getSources().stream()
+ .map(source -> new McastRoute(source, groupAddress, McastRoute.Type.IGMP))
+ .forEach(route -> removeRoute(route, location));
+ }
+ }
+ });
+ }
+
+ private McastRoute ssmTranslateRoute(IpAddress group) {
+ IpAddress source = ssmTranslateTable.get(group);
+ if (source == null) {
+ log.warn("No SSM translate source found for group {}", group);
+ return null;
+ }
+ return new McastRoute(source, group, McastRoute.Type.IGMP);
+ }
+
+ private void addRoute(McastRoute route, ConnectPoint location) {
+ multicastService.add(route);
+ multicastService.addSink(route, location);
+ }
+
+ private void removeRoute(McastRoute route, ConnectPoint location) {
+ multicastService.removeSink(route, location);
+ // TODO remove route if all sinks are gone
+ }
+
+ private ByteBuffer buildQueryPacket() {
+ IGMP igmp = new IGMP();
+ igmp.setIgmpType(IGMP.TYPE_IGMPV3_MEMBERSHIP_QUERY);
+ igmp.setMaxRespCode(maxRespCode);
+
+ IGMPQuery query = new IGMPQuery(IpAddress.valueOf("0.0.0.0"), 0);
+ igmp.addGroup(query);
+
+ IPv4 ip = new IPv4();
+ ip.setDestinationAddress(DEST_IP);
+ ip.setProtocol(IPv4.PROTOCOL_IGMP);
+ ip.setSourceAddress("192.168.1.1");
+ ip.setTtl((byte) 1);
+ ip.setPayload(igmp);
+
+ Ethernet eth = new Ethernet();
+ eth.setDestinationMACAddress(DEST_MAC);
+ eth.setSourceMACAddress("DE:AD:BE:EF:BA:11");
+ eth.setEtherType(Ethernet.TYPE_IPV4);
+
+ eth.setPayload(ip);
+
+ eth.setPad(true);
+
+ return ByteBuffer.wrap(eth.serialize());
+ }
+
+ private void querySubscribers() {
+ oltData.keySet().stream()
+ .flatMap(did -> deviceService.getPorts(did).stream())
+ .filter(p -> !oltData.get(p.element().id()).uplink().equals(p.number()))
+ .filter(p -> p.isEnabled())
+ .forEach(p -> {
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(p.number()).build();
+ packetService.emit(new DefaultOutboundPacket((DeviceId) p.element().id(),
+ treatment, queryPacket));
+ });
+ }
+
+ /**
+ * 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.trace("Packet ({}, {}) -> ingress port: {}", saddr, gaddr,
+ context.inPacket().receivedFrom());
+
+
+ if (ip.getProtocol() != IPv4.PROTOCOL_IGMP ||
+ !IpPrefix.IPV4_MULTICAST_PREFIX.contains(gaddr)) {
+ return;
+ }
+
+ if (IpPrefix.IPV4_MULTICAST_PREFIX.contains(saddr)) {
+ log.debug("IGMP Picked up a packet with a multicast source address.");
+ return;
+ }
+
+ IGMP igmp = (IGMP) ip.getPayload();
+ switch (igmp.getIgmpType()) {
+ case IGMP.TYPE_IGMPV3_MEMBERSHIP_REPORT:
+ processMembership(igmp, pkt.receivedFrom());
+ break;
+
+ case IGMP.TYPE_IGMPV3_MEMBERSHIP_QUERY:
+ log.debug("Received a membership query {} from {}",
+ 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.warn("Unknown IGMP message type: {}", igmp.getIgmpType());
+ break;
+ }
+ }
+ }
+
+
+ private class InternalDeviceListener implements DeviceListener {
+ @Override
+ public void event(DeviceEvent event) {
+ DeviceId devId = event.subject().id();
+ switch (event.type()) {
+
+ case DEVICE_ADDED:
+ case DEVICE_UPDATED:
+ case DEVICE_REMOVED:
+ case DEVICE_SUSPENDED:
+ case DEVICE_AVAILABILITY_CHANGED:
+ case PORT_STATS_UPDATED:
+ break;
+ case PORT_ADDED:
+ if (!oltData.get(devId).uplink().equals(event.port().number()) &&
+ event.port().isEnabled()) {
+ processFilterObjective(event.subject().id(), event.port(), false);
+ }
+ break;
+ case PORT_UPDATED:
+ if (oltData.get(devId).uplink().equals(event.port().number())) {
+ break;
+ }
+ if (event.port().isEnabled()) {
+ processFilterObjective(event.subject().id(), event.port(), false);
+ } else {
+ processFilterObjective(event.subject().id(), event.port(), true);
+ }
+ break;
+ case PORT_REMOVED:
+ processFilterObjective(event.subject().id(), event.port(), true);
+ break;
+ default:
+ log.warn("Unknown device event {}", event.type());
+ break;
+ }
+ }
+
+ @Override
+ public boolean isRelevant(DeviceEvent event) {
+ return oltData.containsKey(event.subject().id());
+ }
+ }
+
+ private class InternalNetworkConfigListener implements NetworkConfigListener {
+ @Override
+ public void event(NetworkConfigEvent event) {
+ switch (event.type()) {
+
+ case CONFIG_ADDED:
+ case CONFIG_UPDATED:
+ if (event.configClass().equals(CONFIG_CLASS)) {
+ AccessDeviceConfig config =
+ networkConfig.getConfig((DeviceId) event.subject(), CONFIG_CLASS);
+ if (config != null) {
+ oltData.put(config.getOlt().deviceId(), config.getOlt());
+ provisionDefaultFlows((DeviceId) event.subject());
+ }
+ }
+ if (event.configClass().equals(SSM_TRANSLATE_CONFIG_CLASS)) {
+ IgmpSsmTranslateConfig config =
+ networkConfig.getConfig((ApplicationId) event.subject(),
+ SSM_TRANSLATE_CONFIG_CLASS);
+
+ if (config != null) {
+ ssmTranslateTable.clear();
+ config.getSsmTranslations().forEach(
+ route -> ssmTranslateTable.put(route.group(), route.source()));
+ }
+ }
+ break;
+ case CONFIG_REGISTERED:
+ case CONFIG_UNREGISTERED:
+ break;
+ case CONFIG_REMOVED:
+ if (event.configClass().equals(SSM_TRANSLATE_CONFIG_CLASS)) {
+ ssmTranslateTable.clear();
+ } else if (event.configClass().equals(CONFIG_CLASS)) {
+ oltData.remove(event.subject());
+ }
+
+ default:
+ break;
+ }
+ }
+ }
+
+ private void provisionDefaultFlows(DeviceId deviceId) {
+ List<Port> ports = deviceService.getPorts(deviceId);
+
+ ports.stream()
+ .filter(p -> !oltData.get(p.element().id()).uplink().equals(p.number()))
+ .filter(p -> p.isEnabled())
+ .forEach(p -> processFilterObjective((DeviceId) p.element().id(), p, false));
+ }
+}
diff --git a/src/main/java/org/onosproject/igmp/IgmpSsmTranslateConfig.java b/src/main/java/org/onosproject/igmp/IgmpSsmTranslateConfig.java
new file mode 100644
index 0000000..ddc6209
--- /dev/null
+++ b/src/main/java/org/onosproject/igmp/IgmpSsmTranslateConfig.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2016-present 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;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.packet.IpAddress;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.config.Config;
+import org.onosproject.net.mcast.McastRoute;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * IGMP SSM translate configuration.
+ */
+public class IgmpSsmTranslateConfig extends Config<ApplicationId> {
+
+ private static final String SOURCE = "source";
+ private static final String GROUP = "group";
+
+ @Override
+ public boolean isValid() {
+ for (JsonNode node : array) {
+ if (!hasOnlyFields((ObjectNode) node, SOURCE, GROUP)) {
+ return false;
+ }
+
+ if (!(isIpAddress((ObjectNode) node, SOURCE, FieldPresence.MANDATORY) &&
+ isIpAddress((ObjectNode) node, GROUP, FieldPresence.MANDATORY))) {
+ return false;
+ }
+
+ }
+ return true;
+ }
+
+ /**
+ * Gets the list of SSM translations.
+ *
+ * @return SSM translations
+ */
+ public List<McastRoute> getSsmTranslations() {
+ List<McastRoute> translations = new ArrayList();
+ for (JsonNode node : array) {
+ translations.add(
+ new McastRoute(
+ IpAddress.valueOf(node.path(SOURCE).asText().trim()),
+ IpAddress.valueOf(node.path(GROUP).asText().trim()),
+ McastRoute.Type.STATIC));
+ }
+
+ return translations;
+ }
+}
diff --git a/src/main/java/org/onosproject/igmp/package-info.java b/src/main/java/org/onosproject/igmp/package-info.java
new file mode 100644
index 0000000..f501339
--- /dev/null
+++ b/src/main/java/org/onosproject/igmp/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015-present 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.
+ */
+
+/**
+ * IGMP implementation.
+ */
+package org.onosproject.igmp;