blob: edd23f61b7f745484185b965bd6e68931d4a3164 [file] [log] [blame]
David K. Bainbridged77028f2017-08-01 12:47:55 -07001/*
Brian O'Connor4d084702017-08-03 22:45:58 -07002 * Copyright 2017-present Open Networking Foundation
David K. Bainbridged77028f2017-08-01 12:47:55 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
developere400c582020-03-24 19:42:08 +010016package org.opencord.igmpproxy.impl;
ke han81a38b92017-03-10 18:41:44 +080017
Esin Karamaneff10392019-06-27 18:09:13 +000018import com.google.common.collect.Sets;
Matteo Scandolo7482bbe2020-02-12 14:37:09 -080019import org.onosproject.net.Device;
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +000020import org.opencord.igmpproxy.IgmpLeadershipService;
developere400c582020-03-24 19:42:08 +010021import org.opencord.igmpproxy.IgmpStatisticsService;
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +000022import org.opencord.igmpproxy.impl.store.groupmember.GroupMember;
23import org.opencord.igmpproxy.GroupMemberId;
24import org.opencord.igmpproxy.impl.store.groupmember.GroupMemberStore;
25import org.opencord.igmpproxy.statemachine.StateMachineService;
Matteo Scandolo7482bbe2020-02-12 14:37:09 -080026import org.opencord.sadis.BaseInformationService;
27import org.opencord.sadis.SadisService;
28import org.opencord.sadis.SubscriberAndDeviceInformation;
Carmelo Casconebef302e2019-11-14 19:58:20 -080029import org.osgi.service.component.annotations.Activate;
30import org.osgi.service.component.annotations.Component;
31import org.osgi.service.component.annotations.Deactivate;
32import org.osgi.service.component.annotations.Reference;
33import org.osgi.service.component.annotations.ReferenceCardinality;
ke han81a38b92017-03-10 18:41:44 +080034import org.onlab.packet.EthType;
35import org.onlab.packet.Ethernet;
36import org.onlab.packet.IGMP;
37import org.onlab.packet.IGMPGroup;
38import org.onlab.packet.IGMPMembership;
39import org.onlab.packet.IGMPQuery;
40import org.onlab.packet.IPv4;
41import org.onlab.packet.Ip4Address;
42import org.onlab.packet.IpAddress;
43import org.onlab.packet.VlanId;
44import org.onosproject.core.ApplicationId;
45import org.onosproject.core.CoreService;
ke han81a38b92017-03-10 18:41:44 +080046import org.onosproject.mastership.MastershipService;
47import org.onosproject.net.AnnotationKeys;
48import org.onosproject.net.ConnectPoint;
49import org.onosproject.net.DeviceId;
50import org.onosproject.net.Port;
51import org.onosproject.net.PortNumber;
52import org.onosproject.net.config.ConfigFactory;
53import org.onosproject.net.config.NetworkConfigEvent;
54import org.onosproject.net.config.NetworkConfigListener;
55import org.onosproject.net.config.NetworkConfigRegistry;
Jonathan Hart488e1142018-05-02 17:30:05 -070056import org.onosproject.net.config.basics.McastConfig;
ke han81a38b92017-03-10 18:41:44 +080057import org.onosproject.net.config.basics.SubjectFactories;
58import org.onosproject.net.device.DeviceEvent;
59import org.onosproject.net.device.DeviceListener;
60import org.onosproject.net.device.DeviceService;
61import org.onosproject.net.flow.DefaultTrafficTreatment;
62import org.onosproject.net.flow.FlowRuleService;
63import org.onosproject.net.flow.criteria.Criteria;
64import org.onosproject.net.flowobjective.DefaultFilteringObjective;
65import org.onosproject.net.flowobjective.FilteringObjective;
66import org.onosproject.net.flowobjective.FlowObjectiveService;
67import org.onosproject.net.flowobjective.Objective;
68import org.onosproject.net.flowobjective.ObjectiveContext;
69import org.onosproject.net.flowobjective.ObjectiveError;
Esin Karamaneff10392019-06-27 18:09:13 +000070import org.onosproject.mcast.api.McastRoute;
71import org.onosproject.mcast.api.MulticastRouteService;
ke han81a38b92017-03-10 18:41:44 +080072import org.onosproject.net.packet.InboundPacket;
73import org.onosproject.net.packet.PacketContext;
74import org.onosproject.net.packet.PacketProcessor;
75import org.onosproject.net.packet.PacketService;
ke han81a38b92017-03-10 18:41:44 +080076import org.slf4j.Logger;
77import org.slf4j.LoggerFactory;
78
79import java.util.ArrayList;
Sonal Kasliwalf11c0672020-03-18 11:11:50 +000080import java.util.Arrays;
81import java.util.List;
ke han81a38b92017-03-10 18:41:44 +080082import java.util.Collection;
Esin Karamaneff10392019-06-27 18:09:13 +000083import java.util.HashSet;
ke han81a38b92017-03-10 18:41:44 +080084import java.util.Iterator;
ke han81a38b92017-03-10 18:41:44 +080085import java.util.Map;
Esin Karamaneff10392019-06-27 18:09:13 +000086import java.util.Optional;
ke han81a38b92017-03-10 18:41:44 +080087import java.util.Set;
88import java.util.TimerTask;
89import java.util.concurrent.ConcurrentHashMap;
Esin Karamanb38700c2019-09-17 13:01:25 +000090import java.util.concurrent.ExecutorService;
ke han81a38b92017-03-10 18:41:44 +080091import java.util.concurrent.Executors;
92import java.util.concurrent.ScheduledExecutorService;
93import java.util.concurrent.TimeUnit;
94
Sonal Kasliwalf11c0672020-03-18 11:11:50 +000095import static org.onlab.packet.IGMPMembership.MODE_IS_INCLUDE;
96import static org.onlab.packet.IGMPMembership.MODE_IS_EXCLUDE;
97import static org.onlab.packet.IGMPMembership.CHANGE_TO_INCLUDE_MODE;
98import static org.onlab.packet.IGMPMembership.CHANGE_TO_EXCLUDE_MODE;
99import static org.onlab.packet.IGMPMembership.ALLOW_NEW_SOURCES;
100import static org.onlab.packet.IGMPMembership.BLOCK_OLD_SOURCES;
101
Esin Karamanb38700c2019-09-17 13:01:25 +0000102import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
103import static org.onlab.util.Tools.groupedThreads;
104
ke han81a38b92017-03-10 18:41:44 +0800105/**
106 * Igmp process application, use proxy mode, support first join/ last leave , fast leave
107 * period query and keep alive, packet out igmp message to uplink port features.
108 */
109@Component(immediate = true)
110public class IgmpManager {
111
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800112 private static final String APP_NAME = "org.opencord.igmpproxy";
113
ke han81a38b92017-03-10 18:41:44 +0800114 private static final Class<IgmpproxyConfig> IGMPPROXY_CONFIG_CLASS =
115 IgmpproxyConfig.class;
116 private static final Class<IgmpproxySsmTranslateConfig> IGMPPROXY_SSM_CONFIG_CLASS =
117 IgmpproxySsmTranslateConfig.class;
118 private static final Class<McastConfig> MCAST_CONFIG_CLASS =
119 McastConfig.class;
Esin Karamaneff10392019-06-27 18:09:13 +0000120
ke han81a38b92017-03-10 18:41:44 +0800121 private static ApplicationId appId;
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800122
ke han81a38b92017-03-10 18:41:44 +0800123 private static int unSolicitedTimeout = 3; // unit is 1 sec
124 private static int keepAliveCount = 3;
125 private static int lastQueryInterval = 2; //unit is 1 sec
126 private static int lastQueryCount = 2;
127 private static boolean fastLeave = true;
128 private static boolean withRAUplink = true;
129 private static boolean withRADownlink = false;
130 private static boolean periodicQuery = true;
131 private static short mvlan = 4000;
Esin Karaman586f1d62020-06-04 10:15:34 +0000132 private static short mvlanInner = VlanId.NONE.toShort();
ke han81a38b92017-03-10 18:41:44 +0800133 private static byte igmpCos = 7;
Esin Karaman586f1d62020-06-04 10:15:34 +0000134 private static byte igmpUniCos = 7;
ke han81a38b92017-03-10 18:41:44 +0800135 public static boolean connectPointMode = true;
136 public static ConnectPoint connectPoint = null;
Esin Karamaneff10392019-06-27 18:09:13 +0000137 private static ConnectPoint sourceDeviceAndPort = null;
138 private static boolean enableIgmpProvisioning = false;
Esin Karamanb38700c2019-09-17 13:01:25 +0000139 private static boolean igmpOnPodBasis = false;
Arjun E Kb0018fd2020-04-07 13:26:40 +0000140 private static boolean outgoingIgmpWithV3 = true;
Esin Karamaneff10392019-06-27 18:09:13 +0000141
142 private static final Integer MAX_PRIORITY = 10000;
143 private static final String INSTALLED = "installed";
144 private static final String REMOVED = "removed";
145 private static final String INSTALLATION = "installation";
146 private static final String REMOVAL = "removal";
Esin Karaman00e16b72020-02-21 10:32:39 +0000147 private static final String NNI_PREFIX = "nni";
ke han81a38b92017-03-10 18:41:44 +0800148
ke han29af27b2017-09-08 10:29:12 +0800149 private static boolean pimSSmInterworking = false;
150 private static final String DEFAULT_PIMSSM_HOST = "127.0.0.1";
ke han81a38b92017-03-10 18:41:44 +0800151 private final ScheduledExecutorService scheduledExecutorService =
152 Executors.newScheduledThreadPool(1);
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800153
Carmelo Casconebef302e2019-11-14 19:58:20 -0800154 @Reference(cardinality = ReferenceCardinality.MANDATORY)
ke han81a38b92017-03-10 18:41:44 +0800155 protected CoreService coreService;
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800156
Carmelo Casconebef302e2019-11-14 19:58:20 -0800157 @Reference(cardinality = ReferenceCardinality.MANDATORY)
ke han81a38b92017-03-10 18:41:44 +0800158 protected PacketService packetService;
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800159
Carmelo Casconebef302e2019-11-14 19:58:20 -0800160 @Reference(cardinality = ReferenceCardinality.MANDATORY)
ke han81a38b92017-03-10 18:41:44 +0800161 protected MastershipService mastershipService;
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800162
Carmelo Casconebef302e2019-11-14 19:58:20 -0800163 @Reference(cardinality = ReferenceCardinality.MANDATORY)
ke han81a38b92017-03-10 18:41:44 +0800164 protected FlowRuleService flowRuleService;
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800165
Carmelo Casconebef302e2019-11-14 19:58:20 -0800166 @Reference(cardinality = ReferenceCardinality.MANDATORY)
ke han81a38b92017-03-10 18:41:44 +0800167 protected DeviceService deviceService;
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800168
Carmelo Casconebef302e2019-11-14 19:58:20 -0800169 @Reference(cardinality = ReferenceCardinality.MANDATORY)
ke han81a38b92017-03-10 18:41:44 +0800170 protected FlowObjectiveService flowObjectiveService;
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800171
Carmelo Casconebef302e2019-11-14 19:58:20 -0800172 @Reference(cardinality = ReferenceCardinality.MANDATORY)
ke han81a38b92017-03-10 18:41:44 +0800173 protected NetworkConfigRegistry networkConfig;
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800174
Carmelo Casconebef302e2019-11-14 19:58:20 -0800175 @Reference(cardinality = ReferenceCardinality.MANDATORY)
ke han81a38b92017-03-10 18:41:44 +0800176 protected MulticastRouteService multicastService;
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800177
178 @Reference(cardinality = ReferenceCardinality.MANDATORY)
179 protected SadisService sadisService;
180
Shubham Sharma47f2caf2020-02-18 12:13:40 +0000181 @Reference(cardinality = ReferenceCardinality.MANDATORY)
182 protected IgmpStatisticsService igmpStatisticsManager;
183
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000184 @Reference(cardinality = ReferenceCardinality.MANDATORY)
185 protected GroupMemberStore groupMemberStore;
186
187 @Reference(cardinality = ReferenceCardinality.MANDATORY)
188 protected StateMachineService stateMachineService;
189
190 @Reference(cardinality = ReferenceCardinality.MANDATORY)
191 protected IgmpLeadershipService igmpLeadershipService;
192
ke han81a38b92017-03-10 18:41:44 +0800193 private IgmpPacketProcessor processor = new IgmpPacketProcessor();
194 private Logger log = LoggerFactory.getLogger(getClass());
195 private ApplicationId coreAppId;
196 private Map<Ip4Address, Ip4Address> ssmTranslateTable = new ConcurrentHashMap<>();
Esin Karamaneff10392019-06-27 18:09:13 +0000197
ke han81a38b92017-03-10 18:41:44 +0800198 private InternalNetworkConfigListener configListener =
199 new InternalNetworkConfigListener();
200 private DeviceListener deviceListener = new InternalDeviceListener();
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800201
ke han81a38b92017-03-10 18:41:44 +0800202 private ConfigFactory<ApplicationId, IgmpproxyConfig> igmpproxyConfigFactory =
203 new ConfigFactory<ApplicationId, IgmpproxyConfig>(
204 SubjectFactories.APP_SUBJECT_FACTORY, IGMPPROXY_CONFIG_CLASS, "igmpproxy") {
205 @Override
206 public IgmpproxyConfig createConfig() {
207 return new IgmpproxyConfig();
208 }
209 };
210 private ConfigFactory<ApplicationId, IgmpproxySsmTranslateConfig> igmpproxySsmConfigFactory =
211 new ConfigFactory<ApplicationId, IgmpproxySsmTranslateConfig>(
212 SubjectFactories.APP_SUBJECT_FACTORY, IGMPPROXY_SSM_CONFIG_CLASS, "ssmTranslate", true) {
213 @Override
214 public IgmpproxySsmTranslateConfig createConfig() {
215 return new IgmpproxySsmTranslateConfig();
216 }
217 };
Esin Karamaneff10392019-06-27 18:09:13 +0000218
ke han81a38b92017-03-10 18:41:44 +0800219 private int maxResp = 10; //unit is 1 sec
220 private int keepAliveInterval = 120; //unit is 1 sec
221
Esin Karamanb38700c2019-09-17 13:01:25 +0000222 private ExecutorService eventExecutor;
223
ke han81a38b92017-03-10 18:41:44 +0800224 public static int getUnsolicitedTimeout() {
225 return unSolicitedTimeout;
226 }
227
Arjun E Kb0018fd2020-04-07 13:26:40 +0000228 public static boolean outgoingIgmpWithV3() {
229 return outgoingIgmpWithV3;
230 }
231
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800232 protected BaseInformationService<SubscriberAndDeviceInformation> subsService;
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800233
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000234 private List<Byte> validMembershipModes = Arrays.asList(MODE_IS_INCLUDE, MODE_IS_EXCLUDE, CHANGE_TO_INCLUDE_MODE,
235 CHANGE_TO_EXCLUDE_MODE, ALLOW_NEW_SOURCES, BLOCK_OLD_SOURCES);
Sonal Kasliwalf11c0672020-03-18 11:11:50 +0000236
ke han81a38b92017-03-10 18:41:44 +0800237 @Activate
238 protected void activate() {
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800239 appId = coreService.registerApplication(APP_NAME);
ke han81a38b92017-03-10 18:41:44 +0800240 coreAppId = coreService.registerApplication(CoreService.CORE_APP_NAME);
241 packetService.addProcessor(processor, PacketProcessor.director(4));
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000242 IgmpSender.init(packetService, igmpLeadershipService, igmpStatisticsManager);
ke han81a38b92017-03-10 18:41:44 +0800243
ke han81a38b92017-03-10 18:41:44 +0800244 networkConfig.registerConfigFactory(igmpproxySsmConfigFactory);
245 networkConfig.registerConfigFactory(igmpproxyConfigFactory);
246 networkConfig.addListener(configListener);
247
248 configListener.reconfigureNetwork(networkConfig.getConfig(appId, IGMPPROXY_CONFIG_CLASS));
249 configListener.reconfigureSsmTable(networkConfig.getConfig(appId, IGMPPROXY_SSM_CONFIG_CLASS));
250
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800251 subsService = sadisService.getSubscriberInfoService();
ke han81a38b92017-03-10 18:41:44 +0800252 if (connectPointMode) {
253 provisionConnectPointFlows();
254 } else {
255 provisionUplinkFlows();
256 }
257
258 McastConfig config = networkConfig.getConfig(coreAppId, MCAST_CONFIG_CLASS);
259 if (config != null) {
260 mvlan = config.egressVlan().toShort();
Deepa Vaddireddy88134a42017-07-05 12:16:03 +0530261 IgmpSender.getInstance().setMvlan(mvlan);
Esin Karaman586f1d62020-06-04 10:15:34 +0000262 mvlanInner = config.egressInnerVlan().toShort();
263 IgmpSender.getInstance().setMvlanInner(mvlanInner);
ke han81a38b92017-03-10 18:41:44 +0800264 }
265 deviceService.addListener(deviceListener);
Sonal Kasliwalddc3ff22019-11-18 11:52:49 +0000266 scheduledExecutorService.scheduleAtFixedRate(new IgmpProxyTimerTask(), 1000, 1000, TimeUnit.MILLISECONDS);
Esin Karamanb38700c2019-09-17 13:01:25 +0000267 eventExecutor = newSingleThreadScheduledExecutor(groupedThreads("cord/igmpproxy",
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000268 "events-igmp-%d", log));
ke han81a38b92017-03-10 18:41:44 +0800269 log.info("Started");
270 }
271
272 @Deactivate
273 protected void deactivate() {
274 scheduledExecutorService.shutdown();
Esin Karamanb38700c2019-09-17 13:01:25 +0000275 eventExecutor.shutdown();
ke han81a38b92017-03-10 18:41:44 +0800276
277 // de-register and null our handler
278 networkConfig.removeListener(configListener);
ke han81a38b92017-03-10 18:41:44 +0800279 networkConfig.unregisterConfigFactory(igmpproxyConfigFactory);
280 networkConfig.unregisterConfigFactory(igmpproxySsmConfigFactory);
281 deviceService.removeListener(deviceListener);
282 packetService.removeProcessor(processor);
283 flowRuleService.removeFlowRulesById(appId);
ke han81a38b92017-03-10 18:41:44 +0800284 log.info("Stopped");
285 }
286
287 protected Ip4Address getDeviceIp(DeviceId ofDeviceId) {
288 try {
289 String[] mgmtAddress = deviceService.getDevice(ofDeviceId)
290 .annotations().value(AnnotationKeys.MANAGEMENT_ADDRESS).split(":");
291 return Ip4Address.valueOf(mgmtAddress[0]);
292 } catch (Exception ex) {
293 log.info("No valid Ipaddress for " + ofDeviceId.toString());
294 return null;
295 }
296 }
297
298 private void processIgmpQuery(IGMPQuery igmpQuery, ConnectPoint cp, int maxResp) {
299
300 DeviceId deviceId = cp.deviceId();
301 Ip4Address gAddr = igmpQuery.getGaddr().getIp4Address();
Deepa Vaddireddybcd52352017-09-21 05:04:48 +0000302 maxResp = calculateMaxResp(maxResp);
303 if (gAddr != null && !gAddr.isZero()) {
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000304 stateMachineService.specialQuery(deviceId, gAddr, maxResp);
Sonal Kasliwalf11c0672020-03-18 11:11:50 +0000305 igmpStatisticsManager.getIgmpStats().increaseIgmpGrpSpecificMembershipQuery();
Deepa Vaddireddybcd52352017-09-21 05:04:48 +0000306 } else {
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000307 stateMachineService.generalQuery(deviceId, maxResp);
Sonal Kasliwalf11c0672020-03-18 11:11:50 +0000308 igmpStatisticsManager.getIgmpStats().increaseIgmpGeneralMembershipQuery();
Deepa Vaddireddybcd52352017-09-21 05:04:48 +0000309 }
310 }
ke han81a38b92017-03-10 18:41:44 +0800311
Deepa Vaddireddybcd52352017-09-21 05:04:48 +0000312 private void processIgmpConnectPointQuery(IGMPQuery igmpQuery, ConnectPoint cp, int maxResp) {
313
Deepa Vaddireddybcd52352017-09-21 05:04:48 +0000314 Ip4Address gAddr = igmpQuery.getGaddr().getIp4Address();
Esin Karaman00e16b72020-02-21 10:32:39 +0000315 final int maxResponseTime = calculateMaxResp(maxResp);
Deepa Vaddireddybcd52352017-09-21 05:04:48 +0000316 //The query is received on the ConnectPoint
317 // send query accordingly to the registered OLT devices.
318 if (gAddr != null && !gAddr.isZero()) {
Esin Karaman00e16b72020-02-21 10:32:39 +0000319 deviceService.getAvailableDevices().forEach(device -> {
320 Optional<SubscriberAndDeviceInformation> accessDevice = getSubscriberAndDeviceInformation(device.id());
321 if (accessDevice.isPresent()) {
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000322 stateMachineService.specialQuery(device.id(), gAddr, maxResponseTime);
Sonal Kasliwalf11c0672020-03-18 11:11:50 +0000323 igmpStatisticsManager.getIgmpStats().increaseIgmpGrpAndSrcSpecificMembershipQuery();
Esin Karaman00e16b72020-02-21 10:32:39 +0000324 }
325 });
Sonal Kasliwalf11c0672020-03-18 11:11:50 +0000326 igmpStatisticsManager.getIgmpStats().increaseCurrentGrpNumCounter();
Deepa Vaddireddybcd52352017-09-21 05:04:48 +0000327 } else {
328 //Don't know which group is targeted by the query
329 //So query all the members(in all the OLTs) and proxy their reports
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000330 stateMachineService.generalQuery(maxResponseTime);
Sonal Kasliwalf11c0672020-03-18 11:11:50 +0000331 igmpStatisticsManager.getIgmpStats().increaseIgmpGeneralMembershipQuery();
Deepa Vaddireddybcd52352017-09-21 05:04:48 +0000332 }
333 }
334
335
336 private int calculateMaxResp(int maxResp) {
ke han81a38b92017-03-10 18:41:44 +0800337 if (maxResp >= 128) {
338 int mant = maxResp & 0xf;
339 int exp = (maxResp >> 4) & 0x7;
340 maxResp = (mant | 0x10) << (exp + 3);
341 }
342
Deepa Vaddireddybcd52352017-09-21 05:04:48 +0000343 return (maxResp + 5) / 10;
ke han81a38b92017-03-10 18:41:44 +0800344 }
345
346 private Ip4Address ssmTranslateRoute(IpAddress group) {
347 return ssmTranslateTable.get(group);
348 }
349
350 private void processIgmpReport(IGMPMembership igmpGroup, VlanId vlan, ConnectPoint cp, byte igmpType) {
351 DeviceId deviceId = cp.deviceId();
352 PortNumber portNumber = cp.port();
353
354 Ip4Address groupIp = igmpGroup.getGaddr().getIp4Address();
355 if (!groupIp.isMulticast()) {
356 log.info(groupIp.toString() + " is not a valid group address");
Sonal Kasliwalf11c0672020-03-18 11:11:50 +0000357 igmpStatisticsManager.getIgmpStats().increaseFailJoinReqUnknownMulticastIpCounter();
ke han81a38b92017-03-10 18:41:44 +0800358 return;
359 }
360 Ip4Address srcIp = getDeviceIp(deviceId);
361
362 byte recordType = igmpGroup.getRecordType();
363 boolean join = false;
364
365 ArrayList<Ip4Address> sourceList = new ArrayList<>();
366
Sonal Kasliwalf11c0672020-03-18 11:11:50 +0000367 if (!validMembershipModes.contains(recordType)) {
368 igmpStatisticsManager.getIgmpStats().increaseReportsRxWithWrongModeCounter();
369 }
ke han81a38b92017-03-10 18:41:44 +0800370 if (igmpGroup.getSources().size() > 0) {
371 igmpGroup.getSources().forEach(source -> sourceList.add(source.getIp4Address()));
372 if (recordType == IGMPMembership.CHANGE_TO_EXCLUDE_MODE ||
373 recordType == IGMPMembership.MODE_IS_EXCLUDE ||
374 recordType == IGMPMembership.BLOCK_OLD_SOURCES) {
375 join = false;
376 } else if (recordType == IGMPMembership.CHANGE_TO_INCLUDE_MODE ||
377 recordType == IGMPMembership.MODE_IS_INCLUDE ||
378 recordType == IGMPMembership.ALLOW_NEW_SOURCES) {
379 join = true;
380 }
381 } else {
ke han29af27b2017-09-08 10:29:12 +0800382 IpAddress src = null;
383 if (pimSSmInterworking) {
384 src = ssmTranslateRoute(groupIp);
385 if (src == null) {
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000386 log.info("no ssm translate for group {}", groupIp);
ke han29af27b2017-09-08 10:29:12 +0800387 return;
388 }
389 } else {
390 src = IpAddress.valueOf(DEFAULT_PIMSSM_HOST);
ke han81a38b92017-03-10 18:41:44 +0800391 }
392 sourceList.add(src.getIp4Address());
393 if (recordType == IGMPMembership.CHANGE_TO_EXCLUDE_MODE ||
394 recordType == IGMPMembership.MODE_IS_EXCLUDE ||
395 recordType == IGMPMembership.BLOCK_OLD_SOURCES) {
396 join = true;
397 } else if (recordType == IGMPMembership.CHANGE_TO_INCLUDE_MODE ||
398 recordType == IGMPMembership.MODE_IS_INCLUDE ||
399 recordType == IGMPMembership.ALLOW_NEW_SOURCES) {
400 join = false;
401 }
402 }
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000403 GroupMemberId groupMemberKey = GroupMemberId.of(groupIp, deviceId, portNumber);
404 GroupMember groupMember = groupMemberStore.getGroupMember(groupMemberKey);
ke han81a38b92017-03-10 18:41:44 +0800405
406 if (join) {
Shubham Sharma47f2caf2020-02-18 12:13:40 +0000407 igmpStatisticsManager.getIgmpStats().increaseIgmpJoinReq();
ke han81a38b92017-03-10 18:41:44 +0800408 if (groupMember == null) {
Esin Karamaneff10392019-06-27 18:09:13 +0000409 Optional<ConnectPoint> sourceConfigured = getSource();
410 if (!sourceConfigured.isPresent()) {
Shubham Sharma47f2caf2020-02-18 12:13:40 +0000411 igmpStatisticsManager.getIgmpStats().increaseIgmpFailJoinReq();
Esin Karamaneff10392019-06-27 18:09:13 +0000412 log.warn("Unable to process IGMP Join from {} since no source " +
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000413 "configuration is found.", deviceId);
Sonal Kasliwalf11c0672020-03-18 11:11:50 +0000414 igmpStatisticsManager.getIgmpStats().increaseFailJoinReqInsuffPermissionAccessCounter();
Esin Karamaneff10392019-06-27 18:09:13 +0000415 return;
416 }
Esin Karaman00e16b72020-02-21 10:32:39 +0000417
418 Optional<PortNumber> deviceUplink = getDeviceUplink(deviceId);
419 if (deviceUplink.isEmpty()) {
420 log.warn("Unable to process IGMP Join since uplink port " +
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000421 "of the device {} is not found.", deviceId);
Esin Karaman00e16b72020-02-21 10:32:39 +0000422 return;
423 }
424
425 if (igmpType == IGMP.TYPE_IGMPV2_MEMBERSHIP_REPORT) {
426 groupMember = new GroupMember(groupIp, vlan, deviceId, portNumber, true);
427 } else {
428 groupMember = new GroupMember(groupIp, vlan, deviceId, portNumber, false);
429 }
430
Esin Karamaneff10392019-06-27 18:09:13 +0000431 HashSet<ConnectPoint> sourceConnectPoints = Sets.newHashSet(sourceConfigured.get());
432
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000433 boolean isJoined = stateMachineService.join(deviceId, groupIp, srcIp, deviceUplink.get());
Shubham Sharma47f2caf2020-02-18 12:13:40 +0000434 if (isJoined) {
435 igmpStatisticsManager.getIgmpStats().increaseIgmpSuccessJoinRejoinReq();
Sonal Kasliwalf11c0672020-03-18 11:11:50 +0000436 igmpStatisticsManager.getIgmpStats().increaseIgmpChannelJoinCounter();
Shubham Sharma47f2caf2020-02-18 12:13:40 +0000437 } else {
438 igmpStatisticsManager.getIgmpStats().increaseIgmpFailJoinReq();
439 }
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000440 groupMemberStore.putGroupMember(groupMember);
441 log.debug("Group member created with id: {}", groupMember.getGroupMemberId());
ke han81a38b92017-03-10 18:41:44 +0800442 groupMember.updateList(recordType, sourceList);
Esin Karamaneff10392019-06-27 18:09:13 +0000443 groupMember.getSourceList().forEach(source -> {
444 McastRoute route = new McastRoute(source, groupIp, McastRoute.Type.IGMP);
445 //add route
446 multicastService.add(route);
447 //add source to the route
448 multicastService.addSources(route, Sets.newHashSet(sourceConnectPoints));
449 //add sink to the route
450 multicastService.addSinks(route, Sets.newHashSet(cp));
451 });
Sonal Kasliwalf11c0672020-03-18 11:11:50 +0000452 igmpStatisticsManager.getIgmpStats().increaseUnconfiguredGroupCounter();
Esin Karamaneff10392019-06-27 18:09:13 +0000453
ke han81a38b92017-03-10 18:41:44 +0800454 }
455 groupMember.resetAllTimers();
456 groupMember.updateList(recordType, sourceList);
457 groupMember.setLeave(false);
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000458 //put updated member to the store
459 groupMemberStore.putGroupMember(groupMember);
ke han81a38b92017-03-10 18:41:44 +0800460 } else {
Shubham Sharma47f2caf2020-02-18 12:13:40 +0000461 igmpStatisticsManager.getIgmpStats().increaseIgmpLeaveReq();
ke han81a38b92017-03-10 18:41:44 +0800462 if (groupMember == null) {
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000463 log.info("receive leave but no instance, group {} device: {} port:{}",
464 groupIp, deviceId, portNumber);
Sonal Kasliwalf11c0672020-03-18 11:11:50 +0000465 igmpStatisticsManager.getIgmpStats().increaseUnconfiguredGroupCounter();
ke han81a38b92017-03-10 18:41:44 +0800466 return;
467 } else {
468 groupMember.setLeave(true);
469 if (fastLeave) {
470 leaveAction(groupMember);
471 } else {
472 sendQuery(groupMember);
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000473 //put modified group member object to the store
474 groupMemberStore.updateGroupMember(groupMember);
ke han81a38b92017-03-10 18:41:44 +0800475 }
476 }
477 }
478 }
479
480 private void leaveAction(GroupMember groupMember) {
Shubham Sharma47f2caf2020-02-18 12:13:40 +0000481 igmpStatisticsManager.getIgmpStats().increaseIgmpDisconnect();
ke han81a38b92017-03-10 18:41:44 +0800482 ConnectPoint cp = new ConnectPoint(groupMember.getDeviceId(), groupMember.getPortNumber());
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000483 stateMachineService.leave(groupMember.getDeviceId(), groupMember.getGroupIp());
Esin Karamaneff10392019-06-27 18:09:13 +0000484 groupMember.getSourceList().forEach(source -> multicastService.removeSinks(
ke han81a38b92017-03-10 18:41:44 +0800485 new McastRoute(source, groupMember.getGroupIp(),
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000486 McastRoute.Type.IGMP), Sets.newHashSet(cp)));
487 groupMemberStore.removeGroupMember(groupMember.getGroupMemberId());
ke han81a38b92017-03-10 18:41:44 +0800488 }
489
490 private void sendQuery(GroupMember groupMember) {
491 Ethernet ethpkt;
492 Ip4Address srcIp = getDeviceIp(groupMember.getDeviceId());
493 if (groupMember.getv2()) {
Esin Karaman586f1d62020-06-04 10:15:34 +0000494 ethpkt = IgmpSender.getInstance().buildIgmpV2Query(groupMember.getGroupIp(),
495 srcIp, groupMember.getvlan().toShort());
ke han81a38b92017-03-10 18:41:44 +0800496 } else {
Esin Karaman586f1d62020-06-04 10:15:34 +0000497 ethpkt = IgmpSender.getInstance().buildIgmpV3Query(groupMember.getGroupIp(),
498 srcIp, groupMember.getvlan().toShort());
ke han81a38b92017-03-10 18:41:44 +0800499 }
Esin Karaman586f1d62020-06-04 10:15:34 +0000500 log.debug("Sending IGMP query to {}/{} for group {}: {}",
501 groupMember.getDeviceId(), groupMember.getPortNumber(), groupMember.getGroupIp(), ethpkt);
ke han81a38b92017-03-10 18:41:44 +0800502 IgmpSender.getInstance().sendIgmpPacket(ethpkt, groupMember.getDeviceId(), groupMember.getPortNumber());
503 }
504
Esin Karamaneff10392019-06-27 18:09:13 +0000505 /**
506 * @return connect point of the source if configured; and empty Optional otherwise.
507 */
508 public static Optional<ConnectPoint> getSource() {
509 return sourceDeviceAndPort == null ? Optional.empty() :
510 Optional.of(sourceDeviceAndPort);
ke han81a38b92017-03-10 18:41:44 +0800511 }
512
513 /**
514 * Packet processor responsible for forwarding packets along their paths.
515 */
516 private class IgmpPacketProcessor implements PacketProcessor {
517 @Override
518 public void process(PacketContext context) {
Shubham Sharma47f2caf2020-02-18 12:13:40 +0000519
Esin Karamanb38700c2019-09-17 13:01:25 +0000520 eventExecutor.execute(() -> {
521 try {
522 InboundPacket pkt = context.inPacket();
523 Ethernet ethPkt = pkt.parsed();
524 if (ethPkt == null) {
525 return;
526 }
Shubham Sharma47f2caf2020-02-18 12:13:40 +0000527 igmpStatisticsManager.getIgmpStats().increaseTotalMsgReceived();
ke han81a38b92017-03-10 18:41:44 +0800528
Esin Karamanb38700c2019-09-17 13:01:25 +0000529 if (ethPkt.getEtherType() != Ethernet.TYPE_IPV4) {
530 return;
531 }
ke han81a38b92017-03-10 18:41:44 +0800532
Esin Karamanb38700c2019-09-17 13:01:25 +0000533 IPv4 ipv4Pkt = (IPv4) ethPkt.getPayload();
ke han81a38b92017-03-10 18:41:44 +0800534
Esin Karamanb38700c2019-09-17 13:01:25 +0000535 if (ipv4Pkt.getProtocol() != IPv4.PROTOCOL_IGMP) {
536 return;
537 }
ke han81a38b92017-03-10 18:41:44 +0800538
Sonal Kasliwalf11c0672020-03-18 11:11:50 +0000539 igmpStatisticsManager.getIgmpStats().increaseIgmpValidChecksumCounter();
Esin Karamanb38700c2019-09-17 13:01:25 +0000540 short vlan = ethPkt.getVlanID();
541 DeviceId deviceId = pkt.receivedFrom().deviceId();
ke han81a38b92017-03-10 18:41:44 +0800542
Esin Karaman00e16b72020-02-21 10:32:39 +0000543 if (!isConnectPoint(deviceId, pkt.receivedFrom().port()) &&
544 !getSubscriberAndDeviceInformation(deviceId).isPresent()) {
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000545 log.error("Device not registered in netcfg : {}", deviceId);
Sonal Kasliwalf11c0672020-03-18 11:11:50 +0000546 igmpStatisticsManager.getIgmpStats().increaseFailJoinReqInsuffPermissionAccessCounter();
Esin Karamanb38700c2019-09-17 13:01:25 +0000547 return;
548 }
ke han81a38b92017-03-10 18:41:44 +0800549
Esin Karamanb38700c2019-09-17 13:01:25 +0000550 IGMP igmp = (IGMP) ipv4Pkt.getPayload();
Esin Karamance5ce512020-02-25 15:58:14 +0000551
552 Optional<PortNumber> deviceUpLinkOpt = getDeviceUplink(deviceId);
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000553 PortNumber upLinkPort = deviceUpLinkOpt.isPresent() ? deviceUpLinkOpt.get() : null;
Esin Karamanb38700c2019-09-17 13:01:25 +0000554 switch (igmp.getIgmpType()) {
555 case IGMP.TYPE_IGMPV3_MEMBERSHIP_QUERY:
Shubham Sharma47f2caf2020-02-18 12:13:40 +0000556 igmpStatisticsManager.getIgmpStats().increaseIgmpv3MembershipQuery();
Esin Karamanb38700c2019-09-17 13:01:25 +0000557 //Discard Query from OLT’s non-uplink port’s
Esin Karamance5ce512020-02-25 15:58:14 +0000558 if (!pkt.receivedFrom().port().equals(upLinkPort)) {
Esin Karamanb38700c2019-09-17 13:01:25 +0000559 if (isConnectPoint(deviceId, pkt.receivedFrom().port())) {
560 log.info("IGMP Picked up query from connectPoint");
561 //OK to process packet
562 processIgmpConnectPointQuery((IGMPQuery) igmp.getGroups().get(0),
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000563 pkt.receivedFrom(),
564 0xff & igmp.getMaxRespField());
Esin Karamanb38700c2019-09-17 13:01:25 +0000565 break;
566 } else {
567 //Not OK to process packet
Esin Karamance5ce512020-02-25 15:58:14 +0000568 log.warn("IGMP Picked up query from non-uplink port {}", upLinkPort);
Esin Karamanb38700c2019-09-17 13:01:25 +0000569 return;
570 }
571 }
ke han81a38b92017-03-10 18:41:44 +0800572
Esin Karamanb38700c2019-09-17 13:01:25 +0000573 processIgmpQuery((IGMPQuery) igmp.getGroups().get(0), pkt.receivedFrom(),
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000574 0xff & igmp.getMaxRespField());
Esin Karamanb38700c2019-09-17 13:01:25 +0000575 break;
576 case IGMP.TYPE_IGMPV1_MEMBERSHIP_REPORT:
Shubham Sharma47f2caf2020-02-18 12:13:40 +0000577 igmpStatisticsManager.getIgmpStats().increaseIgmpv1MembershipReport();
Esin Karamanb38700c2019-09-17 13:01:25 +0000578 log.debug("IGMP version 1 message types are not currently supported.");
579 break;
580 case IGMP.TYPE_IGMPV3_MEMBERSHIP_REPORT:
Shubham Sharma47f2caf2020-02-18 12:13:40 +0000581 igmpStatisticsManager.getIgmpStats().increaseIgmpv3MembershipReport();
582 processIgmpMessage(pkt, igmp, upLinkPort, vlan);
583 break;
Esin Karamanb38700c2019-09-17 13:01:25 +0000584 case IGMP.TYPE_IGMPV2_MEMBERSHIP_REPORT:
Shubham Sharma47f2caf2020-02-18 12:13:40 +0000585 igmpStatisticsManager.getIgmpStats().increaseIgmpv2MembershipReport();
586 processIgmpMessage(pkt, igmp, upLinkPort, vlan);
587 break;
Esin Karamanb38700c2019-09-17 13:01:25 +0000588 case IGMP.TYPE_IGMPV2_LEAVE_GROUP:
Shubham Sharma47f2caf2020-02-18 12:13:40 +0000589 igmpStatisticsManager.getIgmpStats().increaseIgmpv2LeaveGroup();
590 processIgmpMessage(pkt, igmp, upLinkPort, vlan);
Esin Karamanb38700c2019-09-17 13:01:25 +0000591 break;
ke han81a38b92017-03-10 18:41:44 +0800592
Esin Karamanb38700c2019-09-17 13:01:25 +0000593 default:
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000594 log.warn("Unknown IGMP message type: {}", igmp.getIgmpType());
Shubham Sharma47f2caf2020-02-18 12:13:40 +0000595 igmpStatisticsManager.getIgmpStats().increaseInvalidIgmpMsgReceived();
Sonal Kasliwalf11c0672020-03-18 11:11:50 +0000596 igmpStatisticsManager.getIgmpStats().increaseUnknownIgmpTypePacketsRxCounter();
Esin Karamanb38700c2019-09-17 13:01:25 +0000597 break;
598 }
599
600 } catch (Exception ex) {
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000601 log.error("igmp process error : ", ex);
ke han81a38b92017-03-10 18:41:44 +0800602 }
Esin Karamanb38700c2019-09-17 13:01:25 +0000603 });
ke han81a38b92017-03-10 18:41:44 +0800604 }
605 }
606
Shubham Sharma47f2caf2020-02-18 12:13:40 +0000607 private void processIgmpMessage(InboundPacket pkt, IGMP igmp, PortNumber upLinkPort, short vlan) {
608 //Discard join/leave from OLT’s uplink port’s
609 if (pkt.receivedFrom().port().equals(upLinkPort) ||
610 isConnectPoint(pkt.receivedFrom().deviceId(), pkt.receivedFrom().port())) {
611 log.info("IGMP Picked up join/leave from uplink/connectPoint port");
612 return;
613 }
614
615 Iterator<IGMPGroup> itr = igmp.getGroups().iterator();
616 while (itr.hasNext()) {
617 IGMPGroup group = itr.next();
618 if (group instanceof IGMPMembership) {
619 processIgmpReport((IGMPMembership) group, VlanId.vlanId(vlan),
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000620 pkt.receivedFrom(), igmp.getIgmpType());
Shubham Sharma47f2caf2020-02-18 12:13:40 +0000621 } else {
622 IGMPMembership mgroup = new IGMPMembership(group.getGaddr().getIp4Address());
623 mgroup.setRecordType(igmp.getIgmpType() == IGMP.TYPE_IGMPV2_MEMBERSHIP_REPORT ?
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000624 IGMPMembership.MODE_IS_EXCLUDE :
625 IGMPMembership.MODE_IS_INCLUDE);
Shubham Sharma47f2caf2020-02-18 12:13:40 +0000626 processIgmpReport(mgroup, VlanId.vlanId(vlan),
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000627 pkt.receivedFrom(), igmp.getIgmpType());
Shubham Sharma47f2caf2020-02-18 12:13:40 +0000628 }
629 }
630
631 }
632
ke han81a38b92017-03-10 18:41:44 +0800633 private class IgmpProxyTimerTask extends TimerTask {
634 public void run() {
635 try {
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000636 stateMachineService.timeOut1s();
ke han81a38b92017-03-10 18:41:44 +0800637 queryMembers();
638 } catch (Exception ex) {
639 log.warn("Igmp timer task error : {}", ex.getMessage());
640 }
641 }
642
643 private void queryMembers() {
644 GroupMember groupMember;
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000645 Set<GroupMemberId> keySet = groupMemberStore.getAllGroupMemberIds();
646 for (GroupMemberId key : keySet) {
647 groupMember = groupMemberStore.getGroupMember(key);
648 if (groupMember == null) {
649 continue;
650 }
ke han81a38b92017-03-10 18:41:44 +0800651 DeviceId did = groupMember.getDeviceId();
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000652 if (igmpLeadershipService.isLocalLeader(did)) {
ke han81a38b92017-03-10 18:41:44 +0800653 if (groupMember.isLeave()) {
654 lastQuery(groupMember);
655 } else if (periodicQuery) {
656 periodicQuery(groupMember);
657 }
658 }
659 }
660 }
661
662 private void lastQuery(GroupMember groupMember) {
663 if (groupMember.getLastQueryInterval() < lastQueryInterval) {
664 groupMember.lastQueryInterval(true); // count times
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000665 //put modified group member object to the store
666 groupMemberStore.updateGroupMember(groupMember);
ke han81a38b92017-03-10 18:41:44 +0800667 } else if (groupMember.getLastQueryCount() < lastQueryCount - 1) {
668 sendQuery(groupMember);
669 groupMember.lastQueryInterval(false); // reset count number
670 groupMember.lastQueryCount(true); //count times
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000671 //put modified group member object to the store
672 groupMemberStore.updateGroupMember(groupMember);
ke han81a38b92017-03-10 18:41:44 +0800673 } else if (groupMember.getLastQueryCount() == lastQueryCount - 1) {
674 leaveAction(groupMember);
675 }
676 }
677
678 private void periodicQuery(GroupMember groupMember) {
679 if (groupMember.getKeepAliveQueryInterval() < keepAliveInterval) {
680 groupMember.keepAliveInterval(true);
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000681 //put modified group member object to the store
682 groupMemberStore.updateGroupMember(groupMember);
ke han81a38b92017-03-10 18:41:44 +0800683 } else if (groupMember.getKeepAliveQueryCount() < keepAliveCount) {
684 sendQuery(groupMember);
685 groupMember.keepAliveInterval(false);
686 groupMember.keepAliveQueryCount(true);
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000687 //put modified group member object to the store
688 groupMemberStore.updateGroupMember(groupMember);
ke han81a38b92017-03-10 18:41:44 +0800689 } else if (groupMember.getKeepAliveQueryCount() == keepAliveCount) {
690 leaveAction(groupMember);
691 }
692 }
693
694 }
695
Esin Karaman00e16b72020-02-21 10:32:39 +0000696 public Optional<PortNumber> getDeviceUplink(DeviceId devId) {
697 Device device = deviceService.getDevice(devId);
698 if (device == null || device.serialNumber() == null) {
699 return Optional.empty();
Deepa Vaddireddybcd52352017-09-21 05:04:48 +0000700 }
Esin Karaman00e16b72020-02-21 10:32:39 +0000701 Optional<SubscriberAndDeviceInformation> olt = getSubscriberAndDeviceInformation(device.serialNumber());
702 if (olt.isEmpty()) {
703 return Optional.empty();
704 }
705 PortNumber portNumber = PortNumber.portNumber(olt.get().uplinkPort());
706 return validateUpLinkPort(device.id(), portNumber) ?
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000707 Optional.of(portNumber) : Optional.empty();
Esin Karaman00e16b72020-02-21 10:32:39 +0000708 }
709
710 /**
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000711 * @param deviceId device id
Esin Karaman00e16b72020-02-21 10:32:39 +0000712 * @param portNumber port number
713 * @return true if the port name starts with NNI_PREFIX; false otherwise.
714 */
715 public boolean validateUpLinkPort(DeviceId deviceId, PortNumber portNumber) {
716 Port port = deviceService.getPort(deviceId, portNumber);
717 if (port == null) {
718 //port is not discovered by ONOS; so cannot validate it.
719 return false;
720 }
Esin Karamance5ce512020-02-25 15:58:14 +0000721 boolean isValid = port.annotations().value(AnnotationKeys.PORT_NAME) != null &&
Esin Karaman00e16b72020-02-21 10:32:39 +0000722 port.annotations().value(AnnotationKeys.PORT_NAME).startsWith(NNI_PREFIX);
Esin Karamance5ce512020-02-25 15:58:14 +0000723 if (!isValid) {
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000724 log.warn("Port cannot be validated; it is not configured as an NNI port. Device/port: {}/{}",
725 deviceId, portNumber);
Esin Karamance5ce512020-02-25 15:58:14 +0000726 }
727 return isValid;
ke han81a38b92017-03-10 18:41:44 +0800728 }
729
Esin Karamanb38700c2019-09-17 13:01:25 +0000730 public static boolean isIgmpOnPodBasis() {
731 return igmpOnPodBasis;
732 }
733
ke han81a38b92017-03-10 18:41:44 +0800734 private void processFilterObjective(DeviceId devId, PortNumber port, boolean remove) {
Esin Karamaneff10392019-06-27 18:09:13 +0000735 if (!enableIgmpProvisioning) {
736 log.debug("IGMP trap rules won't be installed since enableIgmpProvisioning flag is not set");
737 return;
738 }
ke han81a38b92017-03-10 18:41:44 +0800739 //TODO migrate to packet requests when packet service uses filtering objectives
740 DefaultFilteringObjective.Builder builder = DefaultFilteringObjective.builder();
741
742 builder = remove ? builder.deny() : builder.permit();
743
744 FilteringObjective igmp = builder
745 .withKey(Criteria.matchInPort(port))
746 .addCondition(Criteria.matchEthType(EthType.EtherType.IPV4.ethType()))
747 .addCondition(Criteria.matchIPProtocol(IPv4.PROTOCOL_IGMP))
748 .withMeta(DefaultTrafficTreatment.builder().setOutput(PortNumber.CONTROLLER).build())
749 .fromApp(appId)
Esin Karamaneff10392019-06-27 18:09:13 +0000750 .withPriority(MAX_PRIORITY)
ke han81a38b92017-03-10 18:41:44 +0800751 .add(new ObjectiveContext() {
752 @Override
753 public void onSuccess(Objective objective) {
Esin Karamaneff10392019-06-27 18:09:13 +0000754 log.info("Igmp filter for {} on {} {}.",
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000755 devId, port, (remove) ? REMOVED : INSTALLED);
ke han81a38b92017-03-10 18:41:44 +0800756 }
757
758 @Override
759 public void onError(Objective objective, ObjectiveError error) {
Esin Karamaneff10392019-06-27 18:09:13 +0000760 log.info("Igmp filter {} for device {} on port {} failed because of {}",
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000761 (remove) ? INSTALLATION : REMOVAL, devId, port,
762 error);
ke han81a38b92017-03-10 18:41:44 +0800763 }
764 });
765
766 flowObjectiveService.filter(devId, igmp);
Esin Karamaneff10392019-06-27 18:09:13 +0000767
ke han81a38b92017-03-10 18:41:44 +0800768 }
769
770 private boolean isConnectPoint(DeviceId device, PortNumber port) {
Deepa Vaddireddy01f33b42017-07-02 13:26:53 +0530771 if (connectPoint != null) {
772 return (connectPointMode && connectPoint.deviceId().equals(device)
773 && connectPoint.port().equals(port));
774 } else {
775 log.info("connectPoint not configured for device {}", device);
776 return false;
777 }
ke han81a38b92017-03-10 18:41:44 +0800778 }
Deepa Vaddireddy01f33b42017-07-02 13:26:53 +0530779
ke han81a38b92017-03-10 18:41:44 +0800780 private boolean isUplink(DeviceId device, PortNumber port) {
Esin Karamance5ce512020-02-25 15:58:14 +0000781 if (connectPointMode) {
782 return false;
783 }
784 Optional<PortNumber> upLinkPort = getDeviceUplink(device);
785 return upLinkPort.isPresent() && upLinkPort.get().equals(port);
ke han81a38b92017-03-10 18:41:44 +0800786 }
787
Esin Karaman00e16b72020-02-21 10:32:39 +0000788 /**
789 * Fetches device information associated with the device serial number from SADIS.
790 *
791 * @param serialNumber serial number of a device
792 * @return device information; an empty Optional otherwise.
793 */
794 private Optional<SubscriberAndDeviceInformation> getSubscriberAndDeviceInformation(String serialNumber) {
795 long start = System.currentTimeMillis();
796 try {
797 return Optional.ofNullable(sadisService.getSubscriberInfoService().get(serialNumber));
798 } finally {
799 if (log.isDebugEnabled()) {
800 // SADIS can call remote systems to fetch device data and this calls can take a long time.
801 // This measurement is just for monitoring these kinds of situations.
802 log.debug("Device fetched from SADIS. Elapsed {} msec", System.currentTimeMillis() - start);
803 }
804
805 }
806 }
807
808 /**
809 * Fetches device information associated with the device serial number from SADIS.
810 *
811 * @param deviceId device id
812 * @return device information; an empty Optional otherwise.
813 */
814 private Optional<SubscriberAndDeviceInformation> getSubscriberAndDeviceInformation(DeviceId deviceId) {
815 Device device = deviceService.getDevice(deviceId);
816 if (device == null || device.serialNumber() == null) {
817 return Optional.empty();
818 }
819 return getSubscriberAndDeviceInformation(device.serialNumber());
820 }
821
ke han81a38b92017-03-10 18:41:44 +0800822 private class InternalDeviceListener implements DeviceListener {
823 @Override
824 public void event(DeviceEvent event) {
825 DeviceId devId = event.subject().id();
Deepa Vaddireddyca7b25d2017-09-28 13:47:18 +0000826 Port p = event.port();
Esin Karaman00e16b72020-02-21 10:32:39 +0000827 if (getSubscriberAndDeviceInformation(devId).isEmpty() &&
Deepa Vaddireddyca7b25d2017-09-28 13:47:18 +0000828 !(p != null && isConnectPoint(devId, p.number()))) {
ke han81a38b92017-03-10 18:41:44 +0800829 return;
830 }
Deepa Vaddireddyca7b25d2017-09-28 13:47:18 +0000831 PortNumber port;
832
ke han81a38b92017-03-10 18:41:44 +0800833 switch (event.type()) {
834
835 case DEVICE_ADDED:
836 case DEVICE_UPDATED:
837 case DEVICE_REMOVED:
838 case DEVICE_SUSPENDED:
839 case DEVICE_AVAILABILITY_CHANGED:
840 case PORT_STATS_UPDATED:
841 break;
842 case PORT_ADDED:
Deepa Vaddireddyca7b25d2017-09-28 13:47:18 +0000843 port = p.number();
Esin Karaman00e16b72020-02-21 10:32:39 +0000844 if (getSubscriberAndDeviceInformation(devId).isPresent() &&
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000845 !isUplink(devId, port) && !isConnectPoint(devId, port)) {
ke han81a38b92017-03-10 18:41:44 +0800846 processFilterObjective(devId, port, false);
847 } else if (isUplink(devId, port)) {
848 provisionUplinkFlows();
849 } else if (isConnectPoint(devId, port)) {
850 provisionConnectPointFlows();
851 }
852 break;
853 case PORT_UPDATED:
Deepa Vaddireddyca7b25d2017-09-28 13:47:18 +0000854 port = p.number();
Esin Karaman00e16b72020-02-21 10:32:39 +0000855 if (getSubscriberAndDeviceInformation(devId).isPresent() &&
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000856 !isUplink(devId, port) && !isConnectPoint(devId, port)) {
ke han81a38b92017-03-10 18:41:44 +0800857 if (event.port().isEnabled()) {
858 processFilterObjective(devId, port, false);
859 } else {
860 processFilterObjective(devId, port, true);
861 }
862 } else if (isUplink(devId, port)) {
863 if (event.port().isEnabled()) {
864 provisionUplinkFlows(devId);
865 } else {
866 processFilterObjective(devId, port, true);
867 }
868 } else if (isConnectPoint(devId, port)) {
869 if (event.port().isEnabled()) {
870 provisionConnectPointFlows();
871 } else {
872 unprovisionConnectPointFlows();
873 }
874 }
875 break;
876 case PORT_REMOVED:
Deepa Vaddireddyca7b25d2017-09-28 13:47:18 +0000877 port = p.number();
ke han81a38b92017-03-10 18:41:44 +0800878 processFilterObjective(devId, port, true);
879 break;
880 default:
881 log.info("Unknown device event {}", event.type());
882 break;
883 }
884 }
885
886 @Override
887 public boolean isRelevant(DeviceEvent event) {
888 return true;
889 }
890 }
891
892 private class InternalNetworkConfigListener implements NetworkConfigListener {
893
894 private void reconfigureNetwork(IgmpproxyConfig cfg) {
Esin Karamaneff10392019-06-27 18:09:13 +0000895 IgmpproxyConfig newCfg = cfg == null ? new IgmpproxyConfig() : cfg;
ke han81a38b92017-03-10 18:41:44 +0800896
897 unSolicitedTimeout = newCfg.unsolicitedTimeOut();
898 maxResp = newCfg.maxResp();
899 keepAliveInterval = newCfg.keepAliveInterval();
900 keepAliveCount = newCfg.keepAliveCount();
901 lastQueryInterval = newCfg.lastQueryInterval();
902 lastQueryCount = newCfg.lastQueryCount();
903 withRAUplink = newCfg.withRAUplink();
904 withRADownlink = newCfg.withRADownlink();
905 igmpCos = newCfg.igmpCos();
Esin Karaman586f1d62020-06-04 10:15:34 +0000906 igmpUniCos = newCfg.igmpUniCos(); // defines priority bit of IGMP query message sent to UNI ports
ke han81a38b92017-03-10 18:41:44 +0800907 periodicQuery = newCfg.periodicQuery();
908 fastLeave = newCfg.fastLeave();
ke han29af27b2017-09-08 10:29:12 +0800909 pimSSmInterworking = newCfg.pimSsmInterworking();
Esin Karamaneff10392019-06-27 18:09:13 +0000910 enableIgmpProvisioning = newCfg.enableIgmpProvisioning();
Esin Karamanb38700c2019-09-17 13:01:25 +0000911 igmpOnPodBasis = newCfg.igmpOnPodBasis();
Arjun E Kb0018fd2020-04-07 13:26:40 +0000912 if (newCfg.outgoingIgmpWithV3() != null &&
913 outgoingIgmpWithV3 != newCfg.outgoingIgmpWithV3()) {
914 outgoingIgmpWithV3 = newCfg.outgoingIgmpWithV3();
915 }
Deepa Vaddireddyca7b25d2017-09-28 13:47:18 +0000916
917 if (connectPointMode != newCfg.connectPointMode() ||
918 connectPoint != newCfg.connectPoint()) {
ke han81a38b92017-03-10 18:41:44 +0800919 connectPointMode = newCfg.connectPointMode();
Deepa Vaddireddyca7b25d2017-09-28 13:47:18 +0000920 connectPoint = newCfg.connectPoint();
ke han81a38b92017-03-10 18:41:44 +0800921 if (connectPointMode) {
922 unprovisionUplinkFlows();
923 provisionConnectPointFlows();
924 } else {
925 unprovisionConnectPointFlows();
926 provisionUplinkFlows();
927 }
928 }
929 if (connectPoint != null) {
Esin Karamaneff10392019-06-27 18:09:13 +0000930 log.info("connect point : {}", connectPoint);
ke han81a38b92017-03-10 18:41:44 +0800931 }
Esin Karamaneff10392019-06-27 18:09:13 +0000932 log.info("mode: {}", connectPointMode);
933
934 getSourceConnectPoint(newCfg);
ke han81a38b92017-03-10 18:41:44 +0800935
936 IgmpSender.getInstance().setIgmpCos(igmpCos);
Esin Karaman586f1d62020-06-04 10:15:34 +0000937 IgmpSender.getInstance().setIgmpUniCos(igmpUniCos);
ke han81a38b92017-03-10 18:41:44 +0800938 IgmpSender.getInstance().setMaxResp(maxResp);
939 IgmpSender.getInstance().setMvlan(mvlan);
Esin Karaman586f1d62020-06-04 10:15:34 +0000940 IgmpSender.getInstance().setMvlanInner(mvlanInner);
ke han81a38b92017-03-10 18:41:44 +0800941 IgmpSender.getInstance().setWithRADownlink(withRADownlink);
942 IgmpSender.getInstance().setWithRAUplink(withRAUplink);
Esin Karamaneff10392019-06-27 18:09:13 +0000943 }
ke han81a38b92017-03-10 18:41:44 +0800944
Esin Karamaneff10392019-06-27 18:09:13 +0000945 void getSourceConnectPoint(IgmpproxyConfig cfg) {
946 sourceDeviceAndPort = cfg.getSourceDeviceAndPort();
947 if (sourceDeviceAndPort != null) {
948 log.debug("source parameter configured to {}", sourceDeviceAndPort);
949 }
ke han81a38b92017-03-10 18:41:44 +0800950 }
951
952 public void reconfigureSsmTable(IgmpproxySsmTranslateConfig cfg) {
953 if (cfg == null) {
954 return;
955 }
956 Collection<McastRoute> translations = cfg.getSsmTranslations();
957 for (McastRoute route : translations) {
Esin Karamaneff10392019-06-27 18:09:13 +0000958 ssmTranslateTable.put(route.group().getIp4Address(), route.source().get().getIp4Address());
ke han81a38b92017-03-10 18:41:44 +0800959 }
960 }
961
962 @Override
963 public void event(NetworkConfigEvent event) {
964 switch (event.type()) {
965 case CONFIG_ADDED:
966 case CONFIG_UPDATED:
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800967 // NOTE how to know if something has changed in sadis?
ke han81a38b92017-03-10 18:41:44 +0800968
969 if (event.configClass().equals(IGMPPROXY_CONFIG_CLASS)) {
970 IgmpproxyConfig config = networkConfig.getConfig(appId, IGMPPROXY_CONFIG_CLASS);
971 if (config != null) {
Esin Karamaneff10392019-06-27 18:09:13 +0000972 log.info("igmpproxy config received. {}", config);
ke han81a38b92017-03-10 18:41:44 +0800973 reconfigureNetwork(config);
974 }
975 }
976
977 if (event.configClass().equals(IGMPPROXY_SSM_CONFIG_CLASS)) {
978 IgmpproxySsmTranslateConfig config = networkConfig.getConfig(appId, IGMPPROXY_SSM_CONFIG_CLASS);
979 if (config != null) {
980 reconfigureSsmTable(config);
981 }
982 }
983
984 if (event.configClass().equals(MCAST_CONFIG_CLASS)) {
985 McastConfig config = networkConfig.getConfig(coreAppId, MCAST_CONFIG_CLASS);
Esin Karaman586f1d62020-06-04 10:15:34 +0000986 boolean vlanConfigChanged = config != null && mvlan != config.egressVlan().toShort();
987 boolean innerVlanConfigChanged = config != null &&
988 mvlanInner != config.egressInnerVlan().toShort();
989
990 if (vlanConfigChanged || innerVlanConfigChanged) {
991 log.info("igmpproxy vlan config received. {}", config);
992 //at least one of the vlan configs has changed. Call leave before setting new values
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000993 groupMemberStore.getAllGroupMembers().forEach(m -> leaveAction(m));
Esin Karaman586f1d62020-06-04 10:15:34 +0000994 if (vlanConfigChanged) {
995 mvlan = config.egressVlan().toShort();
996 IgmpSender.getInstance().setMvlan(mvlan);
997 }
998 if (innerVlanConfigChanged) {
999 mvlanInner = config.egressInnerVlan().toShort();
1000 IgmpSender.getInstance().setMvlanInner(mvlanInner);
1001 }
ke han81a38b92017-03-10 18:41:44 +08001002 }
1003 }
1004
1005 log.info("Reconfigured");
1006 break;
1007 case CONFIG_REGISTERED:
1008 case CONFIG_UNREGISTERED:
1009 break;
1010 case CONFIG_REMOVED:
Matteo Scandolo7482bbe2020-02-12 14:37:09 -08001011 // NOTE how to know if something has changed in sadis?
ke han81a38b92017-03-10 18:41:44 +08001012 default:
1013 break;
1014 }
1015 }
1016 }
1017
ke han81a38b92017-03-10 18:41:44 +08001018 private void provisionUplinkFlows(DeviceId deviceId) {
1019 if (connectPointMode) {
1020 return;
1021 }
1022
Esin Karaman00e16b72020-02-21 10:32:39 +00001023 Optional<PortNumber> upLink = getDeviceUplink(deviceId);
1024 if (upLink.isPresent()) {
1025 processFilterObjective(deviceId, upLink.get(), false);
1026 }
ke han81a38b92017-03-10 18:41:44 +08001027 }
1028
1029 private void provisionUplinkFlows() {
1030 if (connectPointMode) {
1031 return;
1032 }
Esin Karaman00e16b72020-02-21 10:32:39 +00001033 deviceService.getAvailableDevices().forEach(device -> {
1034 Optional<SubscriberAndDeviceInformation> accessDevice = getSubscriberAndDeviceInformation(device.id());
1035 if (accessDevice.isPresent()) {
1036 provisionUplinkFlows(device.id());
1037 }
1038 });
ke han81a38b92017-03-10 18:41:44 +08001039 }
Esin Karaman00e16b72020-02-21 10:32:39 +00001040
ke han81a38b92017-03-10 18:41:44 +08001041 private void unprovisionUplinkFlows() {
Esin Karaman00e16b72020-02-21 10:32:39 +00001042 deviceService.getAvailableDevices().forEach(device -> {
1043 Optional<SubscriberAndDeviceInformation> accessDevices = getSubscriberAndDeviceInformation(device.id());
1044 if (accessDevices.isPresent()) {
1045 Optional<PortNumber> upLink = getDeviceUplink(device.id());
1046 if (upLink.isPresent()) {
1047 processFilterObjective(device.id(), upLink.get(), true);
1048 }
1049 }
1050 });
ke han81a38b92017-03-10 18:41:44 +08001051 }
1052
1053 private void provisionConnectPointFlows() {
1054 if ((!connectPointMode) || connectPoint == null) {
1055 return;
1056 }
1057
1058 processFilterObjective(connectPoint.deviceId(), connectPoint.port(), false);
1059 }
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +00001060
ke han81a38b92017-03-10 18:41:44 +08001061 private void unprovisionConnectPointFlows() {
1062 if (connectPoint == null) {
1063 return;
1064 }
1065 processFilterObjective(connectPoint.deviceId(), connectPoint.port(), true);
1066 }
Shubham Sharma47f2caf2020-02-18 12:13:40 +00001067
ke han81a38b92017-03-10 18:41:44 +08001068}