blob: e32bc4a23cfe86387feb56f1ac23d92e42b605a1 [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;
Ilayda Ozdemir0872abd2020-06-03 20:20:20 +030021import org.opencord.igmpproxy.IgmpStatisticType;
developere400c582020-03-24 19:42:08 +010022import org.opencord.igmpproxy.IgmpStatisticsService;
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +000023import org.opencord.igmpproxy.impl.store.groupmember.GroupMember;
24import org.opencord.igmpproxy.GroupMemberId;
25import org.opencord.igmpproxy.impl.store.groupmember.GroupMemberStore;
26import org.opencord.igmpproxy.statemachine.StateMachineService;
Matteo Scandolo7482bbe2020-02-12 14:37:09 -080027import org.opencord.sadis.BaseInformationService;
28import org.opencord.sadis.SadisService;
29import org.opencord.sadis.SubscriberAndDeviceInformation;
Carmelo Casconebef302e2019-11-14 19:58:20 -080030import org.osgi.service.component.annotations.Activate;
31import org.osgi.service.component.annotations.Component;
32import org.osgi.service.component.annotations.Deactivate;
33import org.osgi.service.component.annotations.Reference;
34import org.osgi.service.component.annotations.ReferenceCardinality;
ke han81a38b92017-03-10 18:41:44 +080035import org.onlab.packet.EthType;
36import org.onlab.packet.Ethernet;
37import org.onlab.packet.IGMP;
38import org.onlab.packet.IGMPGroup;
39import org.onlab.packet.IGMPMembership;
40import org.onlab.packet.IGMPQuery;
41import org.onlab.packet.IPv4;
42import org.onlab.packet.Ip4Address;
43import org.onlab.packet.IpAddress;
44import org.onlab.packet.VlanId;
45import org.onosproject.core.ApplicationId;
46import org.onosproject.core.CoreService;
ke han81a38b92017-03-10 18:41:44 +080047import org.onosproject.mastership.MastershipService;
48import org.onosproject.net.AnnotationKeys;
49import org.onosproject.net.ConnectPoint;
50import org.onosproject.net.DeviceId;
51import org.onosproject.net.Port;
52import org.onosproject.net.PortNumber;
53import org.onosproject.net.config.ConfigFactory;
54import org.onosproject.net.config.NetworkConfigEvent;
55import org.onosproject.net.config.NetworkConfigListener;
56import org.onosproject.net.config.NetworkConfigRegistry;
Jonathan Hart488e1142018-05-02 17:30:05 -070057import org.onosproject.net.config.basics.McastConfig;
ke han81a38b92017-03-10 18:41:44 +080058import org.onosproject.net.config.basics.SubjectFactories;
59import org.onosproject.net.device.DeviceEvent;
60import org.onosproject.net.device.DeviceListener;
61import org.onosproject.net.device.DeviceService;
62import org.onosproject.net.flow.DefaultTrafficTreatment;
63import org.onosproject.net.flow.FlowRuleService;
64import org.onosproject.net.flow.criteria.Criteria;
65import org.onosproject.net.flowobjective.DefaultFilteringObjective;
66import org.onosproject.net.flowobjective.FilteringObjective;
67import org.onosproject.net.flowobjective.FlowObjectiveService;
68import org.onosproject.net.flowobjective.Objective;
69import org.onosproject.net.flowobjective.ObjectiveContext;
70import org.onosproject.net.flowobjective.ObjectiveError;
Esin Karamaneff10392019-06-27 18:09:13 +000071import org.onosproject.mcast.api.McastRoute;
72import org.onosproject.mcast.api.MulticastRouteService;
ke han81a38b92017-03-10 18:41:44 +080073import org.onosproject.net.packet.InboundPacket;
74import org.onosproject.net.packet.PacketContext;
75import org.onosproject.net.packet.PacketProcessor;
76import org.onosproject.net.packet.PacketService;
ke han81a38b92017-03-10 18:41:44 +080077import org.slf4j.Logger;
78import org.slf4j.LoggerFactory;
79
80import java.util.ArrayList;
Sonal Kasliwalf11c0672020-03-18 11:11:50 +000081import java.util.Arrays;
82import java.util.List;
ke han81a38b92017-03-10 18:41:44 +080083import java.util.Collection;
Esin Karamaneff10392019-06-27 18:09:13 +000084import java.util.HashSet;
ke han81a38b92017-03-10 18:41:44 +080085import java.util.Iterator;
ke han81a38b92017-03-10 18:41:44 +080086import java.util.Map;
Esin Karamaneff10392019-06-27 18:09:13 +000087import java.util.Optional;
ke han81a38b92017-03-10 18:41:44 +080088import java.util.Set;
89import java.util.TimerTask;
90import java.util.concurrent.ConcurrentHashMap;
Esin Karamanb38700c2019-09-17 13:01:25 +000091import java.util.concurrent.ExecutorService;
ke han81a38b92017-03-10 18:41:44 +080092import java.util.concurrent.Executors;
93import java.util.concurrent.ScheduledExecutorService;
94import java.util.concurrent.TimeUnit;
95
Sonal Kasliwalf11c0672020-03-18 11:11:50 +000096import static org.onlab.packet.IGMPMembership.MODE_IS_INCLUDE;
97import static org.onlab.packet.IGMPMembership.MODE_IS_EXCLUDE;
98import static org.onlab.packet.IGMPMembership.CHANGE_TO_INCLUDE_MODE;
99import static org.onlab.packet.IGMPMembership.CHANGE_TO_EXCLUDE_MODE;
100import static org.onlab.packet.IGMPMembership.ALLOW_NEW_SOURCES;
101import static org.onlab.packet.IGMPMembership.BLOCK_OLD_SOURCES;
102
Esin Karamanb38700c2019-09-17 13:01:25 +0000103import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
104import static org.onlab.util.Tools.groupedThreads;
105
ke han81a38b92017-03-10 18:41:44 +0800106/**
107 * Igmp process application, use proxy mode, support first join/ last leave , fast leave
108 * period query and keep alive, packet out igmp message to uplink port features.
109 */
110@Component(immediate = true)
111public class IgmpManager {
112
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800113 private static final String APP_NAME = "org.opencord.igmpproxy";
114
ke han81a38b92017-03-10 18:41:44 +0800115 private static final Class<IgmpproxyConfig> IGMPPROXY_CONFIG_CLASS =
116 IgmpproxyConfig.class;
117 private static final Class<IgmpproxySsmTranslateConfig> IGMPPROXY_SSM_CONFIG_CLASS =
118 IgmpproxySsmTranslateConfig.class;
119 private static final Class<McastConfig> MCAST_CONFIG_CLASS =
120 McastConfig.class;
Esin Karamaneff10392019-06-27 18:09:13 +0000121
ke han81a38b92017-03-10 18:41:44 +0800122 private static ApplicationId appId;
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800123
ke han81a38b92017-03-10 18:41:44 +0800124 private static int unSolicitedTimeout = 3; // unit is 1 sec
125 private static int keepAliveCount = 3;
126 private static int lastQueryInterval = 2; //unit is 1 sec
127 private static int lastQueryCount = 2;
128 private static boolean fastLeave = true;
129 private static boolean withRAUplink = true;
130 private static boolean withRADownlink = false;
131 private static boolean periodicQuery = true;
132 private static short mvlan = 4000;
Esin Karaman586f1d62020-06-04 10:15:34 +0000133 private static short mvlanInner = VlanId.NONE.toShort();
ke han81a38b92017-03-10 18:41:44 +0800134 private static byte igmpCos = 7;
Esin Karaman586f1d62020-06-04 10:15:34 +0000135 private static byte igmpUniCos = 7;
ke han81a38b92017-03-10 18:41:44 +0800136 public static boolean connectPointMode = true;
137 public static ConnectPoint connectPoint = null;
Esin Karamaneff10392019-06-27 18:09:13 +0000138 private static ConnectPoint sourceDeviceAndPort = null;
139 private static boolean enableIgmpProvisioning = false;
Esin Karamanb38700c2019-09-17 13:01:25 +0000140 private static boolean igmpOnPodBasis = false;
Arjun E Kb0018fd2020-04-07 13:26:40 +0000141 private static boolean outgoingIgmpWithV3 = true;
Esin Karamaneff10392019-06-27 18:09:13 +0000142
143 private static final Integer MAX_PRIORITY = 10000;
144 private static final String INSTALLED = "installed";
145 private static final String REMOVED = "removed";
146 private static final String INSTALLATION = "installation";
147 private static final String REMOVAL = "removal";
Esin Karaman00e16b72020-02-21 10:32:39 +0000148 private static final String NNI_PREFIX = "nni";
ke han81a38b92017-03-10 18:41:44 +0800149
ke han29af27b2017-09-08 10:29:12 +0800150 private static boolean pimSSmInterworking = false;
151 private static final String DEFAULT_PIMSSM_HOST = "127.0.0.1";
ke han81a38b92017-03-10 18:41:44 +0800152 private final ScheduledExecutorService scheduledExecutorService =
153 Executors.newScheduledThreadPool(1);
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800154
Carmelo Casconebef302e2019-11-14 19:58:20 -0800155 @Reference(cardinality = ReferenceCardinality.MANDATORY)
ke han81a38b92017-03-10 18:41:44 +0800156 protected CoreService coreService;
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800157
Carmelo Casconebef302e2019-11-14 19:58:20 -0800158 @Reference(cardinality = ReferenceCardinality.MANDATORY)
ke han81a38b92017-03-10 18:41:44 +0800159 protected PacketService packetService;
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800160
Carmelo Casconebef302e2019-11-14 19:58:20 -0800161 @Reference(cardinality = ReferenceCardinality.MANDATORY)
ke han81a38b92017-03-10 18:41:44 +0800162 protected MastershipService mastershipService;
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800163
Carmelo Casconebef302e2019-11-14 19:58:20 -0800164 @Reference(cardinality = ReferenceCardinality.MANDATORY)
ke han81a38b92017-03-10 18:41:44 +0800165 protected FlowRuleService flowRuleService;
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800166
Carmelo Casconebef302e2019-11-14 19:58:20 -0800167 @Reference(cardinality = ReferenceCardinality.MANDATORY)
ke han81a38b92017-03-10 18:41:44 +0800168 protected DeviceService deviceService;
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800169
Carmelo Casconebef302e2019-11-14 19:58:20 -0800170 @Reference(cardinality = ReferenceCardinality.MANDATORY)
ke han81a38b92017-03-10 18:41:44 +0800171 protected FlowObjectiveService flowObjectiveService;
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800172
Carmelo Casconebef302e2019-11-14 19:58:20 -0800173 @Reference(cardinality = ReferenceCardinality.MANDATORY)
ke han81a38b92017-03-10 18:41:44 +0800174 protected NetworkConfigRegistry networkConfig;
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800175
Carmelo Casconebef302e2019-11-14 19:58:20 -0800176 @Reference(cardinality = ReferenceCardinality.MANDATORY)
ke han81a38b92017-03-10 18:41:44 +0800177 protected MulticastRouteService multicastService;
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800178
179 @Reference(cardinality = ReferenceCardinality.MANDATORY)
180 protected SadisService sadisService;
181
Shubham Sharma47f2caf2020-02-18 12:13:40 +0000182 @Reference(cardinality = ReferenceCardinality.MANDATORY)
183 protected IgmpStatisticsService igmpStatisticsManager;
184
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000185 @Reference(cardinality = ReferenceCardinality.MANDATORY)
186 protected GroupMemberStore groupMemberStore;
187
188 @Reference(cardinality = ReferenceCardinality.MANDATORY)
189 protected StateMachineService stateMachineService;
190
191 @Reference(cardinality = ReferenceCardinality.MANDATORY)
192 protected IgmpLeadershipService igmpLeadershipService;
193
ke han81a38b92017-03-10 18:41:44 +0800194 private IgmpPacketProcessor processor = new IgmpPacketProcessor();
195 private Logger log = LoggerFactory.getLogger(getClass());
196 private ApplicationId coreAppId;
197 private Map<Ip4Address, Ip4Address> ssmTranslateTable = new ConcurrentHashMap<>();
Esin Karamaneff10392019-06-27 18:09:13 +0000198
ke han81a38b92017-03-10 18:41:44 +0800199 private InternalNetworkConfigListener configListener =
200 new InternalNetworkConfigListener();
201 private DeviceListener deviceListener = new InternalDeviceListener();
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800202
ke han81a38b92017-03-10 18:41:44 +0800203 private ConfigFactory<ApplicationId, IgmpproxyConfig> igmpproxyConfigFactory =
204 new ConfigFactory<ApplicationId, IgmpproxyConfig>(
205 SubjectFactories.APP_SUBJECT_FACTORY, IGMPPROXY_CONFIG_CLASS, "igmpproxy") {
206 @Override
207 public IgmpproxyConfig createConfig() {
208 return new IgmpproxyConfig();
209 }
210 };
211 private ConfigFactory<ApplicationId, IgmpproxySsmTranslateConfig> igmpproxySsmConfigFactory =
212 new ConfigFactory<ApplicationId, IgmpproxySsmTranslateConfig>(
213 SubjectFactories.APP_SUBJECT_FACTORY, IGMPPROXY_SSM_CONFIG_CLASS, "ssmTranslate", true) {
214 @Override
215 public IgmpproxySsmTranslateConfig createConfig() {
216 return new IgmpproxySsmTranslateConfig();
217 }
218 };
Esin Karamaneff10392019-06-27 18:09:13 +0000219
ke han81a38b92017-03-10 18:41:44 +0800220 private int maxResp = 10; //unit is 1 sec
221 private int keepAliveInterval = 120; //unit is 1 sec
222
Esin Karamanb38700c2019-09-17 13:01:25 +0000223 private ExecutorService eventExecutor;
224
ke han81a38b92017-03-10 18:41:44 +0800225 public static int getUnsolicitedTimeout() {
226 return unSolicitedTimeout;
227 }
228
Arjun E Kb0018fd2020-04-07 13:26:40 +0000229 public static boolean outgoingIgmpWithV3() {
230 return outgoingIgmpWithV3;
231 }
232
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800233 protected BaseInformationService<SubscriberAndDeviceInformation> subsService;
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800234
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000235 private List<Byte> validMembershipModes = Arrays.asList(MODE_IS_INCLUDE, MODE_IS_EXCLUDE, CHANGE_TO_INCLUDE_MODE,
236 CHANGE_TO_EXCLUDE_MODE, ALLOW_NEW_SOURCES, BLOCK_OLD_SOURCES);
Sonal Kasliwalf11c0672020-03-18 11:11:50 +0000237
ke han81a38b92017-03-10 18:41:44 +0800238 @Activate
239 protected void activate() {
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800240 appId = coreService.registerApplication(APP_NAME);
ke han81a38b92017-03-10 18:41:44 +0800241 coreAppId = coreService.registerApplication(CoreService.CORE_APP_NAME);
242 packetService.addProcessor(processor, PacketProcessor.director(4));
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000243 IgmpSender.init(packetService, igmpLeadershipService, igmpStatisticsManager);
ke han81a38b92017-03-10 18:41:44 +0800244
ke han81a38b92017-03-10 18:41:44 +0800245 networkConfig.registerConfigFactory(igmpproxySsmConfigFactory);
246 networkConfig.registerConfigFactory(igmpproxyConfigFactory);
247 networkConfig.addListener(configListener);
248
249 configListener.reconfigureNetwork(networkConfig.getConfig(appId, IGMPPROXY_CONFIG_CLASS));
250 configListener.reconfigureSsmTable(networkConfig.getConfig(appId, IGMPPROXY_SSM_CONFIG_CLASS));
251
Matteo Scandolo7482bbe2020-02-12 14:37:09 -0800252 subsService = sadisService.getSubscriberInfoService();
ke han81a38b92017-03-10 18:41:44 +0800253 if (connectPointMode) {
254 provisionConnectPointFlows();
255 } else {
256 provisionUplinkFlows();
257 }
258
259 McastConfig config = networkConfig.getConfig(coreAppId, MCAST_CONFIG_CLASS);
260 if (config != null) {
261 mvlan = config.egressVlan().toShort();
Deepa Vaddireddy88134a42017-07-05 12:16:03 +0530262 IgmpSender.getInstance().setMvlan(mvlan);
Esin Karaman586f1d62020-06-04 10:15:34 +0000263 mvlanInner = config.egressInnerVlan().toShort();
264 IgmpSender.getInstance().setMvlanInner(mvlanInner);
ke han81a38b92017-03-10 18:41:44 +0800265 }
266 deviceService.addListener(deviceListener);
Sonal Kasliwalddc3ff22019-11-18 11:52:49 +0000267 scheduledExecutorService.scheduleAtFixedRate(new IgmpProxyTimerTask(), 1000, 1000, TimeUnit.MILLISECONDS);
Esin Karamanb38700c2019-09-17 13:01:25 +0000268 eventExecutor = newSingleThreadScheduledExecutor(groupedThreads("cord/igmpproxy",
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000269 "events-igmp-%d", log));
ke han81a38b92017-03-10 18:41:44 +0800270 log.info("Started");
271 }
272
273 @Deactivate
274 protected void deactivate() {
275 scheduledExecutorService.shutdown();
Esin Karamanb38700c2019-09-17 13:01:25 +0000276 eventExecutor.shutdown();
ke han81a38b92017-03-10 18:41:44 +0800277
278 // de-register and null our handler
279 networkConfig.removeListener(configListener);
ke han81a38b92017-03-10 18:41:44 +0800280 networkConfig.unregisterConfigFactory(igmpproxyConfigFactory);
281 networkConfig.unregisterConfigFactory(igmpproxySsmConfigFactory);
282 deviceService.removeListener(deviceListener);
283 packetService.removeProcessor(processor);
284 flowRuleService.removeFlowRulesById(appId);
ke han81a38b92017-03-10 18:41:44 +0800285 log.info("Stopped");
286 }
287
288 protected Ip4Address getDeviceIp(DeviceId ofDeviceId) {
289 try {
290 String[] mgmtAddress = deviceService.getDevice(ofDeviceId)
291 .annotations().value(AnnotationKeys.MANAGEMENT_ADDRESS).split(":");
292 return Ip4Address.valueOf(mgmtAddress[0]);
293 } catch (Exception ex) {
294 log.info("No valid Ipaddress for " + ofDeviceId.toString());
295 return null;
296 }
297 }
298
299 private void processIgmpQuery(IGMPQuery igmpQuery, ConnectPoint cp, int maxResp) {
300
301 DeviceId deviceId = cp.deviceId();
302 Ip4Address gAddr = igmpQuery.getGaddr().getIp4Address();
Deepa Vaddireddybcd52352017-09-21 05:04:48 +0000303 maxResp = calculateMaxResp(maxResp);
304 if (gAddr != null && !gAddr.isZero()) {
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000305 stateMachineService.specialQuery(deviceId, gAddr, maxResp);
Ilayda Ozdemir0872abd2020-06-03 20:20:20 +0300306 igmpStatisticsManager.increaseStat(IgmpStatisticType.IGMP_GRP_SPECIFIC_MEMBERSHIP_QUERY);
Deepa Vaddireddybcd52352017-09-21 05:04:48 +0000307 } else {
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000308 stateMachineService.generalQuery(deviceId, maxResp);
Ilayda Ozdemir0872abd2020-06-03 20:20:20 +0300309 igmpStatisticsManager.increaseStat(IgmpStatisticType.IGMP_GENERAL_MEMBERSHIP_QUERY);
Deepa Vaddireddybcd52352017-09-21 05:04:48 +0000310 }
311 }
ke han81a38b92017-03-10 18:41:44 +0800312
Deepa Vaddireddybcd52352017-09-21 05:04:48 +0000313 private void processIgmpConnectPointQuery(IGMPQuery igmpQuery, ConnectPoint cp, int maxResp) {
314
Deepa Vaddireddybcd52352017-09-21 05:04:48 +0000315 Ip4Address gAddr = igmpQuery.getGaddr().getIp4Address();
Esin Karaman00e16b72020-02-21 10:32:39 +0000316 final int maxResponseTime = calculateMaxResp(maxResp);
Deepa Vaddireddybcd52352017-09-21 05:04:48 +0000317 //The query is received on the ConnectPoint
318 // send query accordingly to the registered OLT devices.
319 if (gAddr != null && !gAddr.isZero()) {
Esin Karaman00e16b72020-02-21 10:32:39 +0000320 deviceService.getAvailableDevices().forEach(device -> {
321 Optional<SubscriberAndDeviceInformation> accessDevice = getSubscriberAndDeviceInformation(device.id());
322 if (accessDevice.isPresent()) {
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000323 stateMachineService.specialQuery(device.id(), gAddr, maxResponseTime);
Ilayda Ozdemir0872abd2020-06-03 20:20:20 +0300324 igmpStatisticsManager.increaseStat(IgmpStatisticType.IGMP_GRP_AND_SRC_SPESIFIC_MEMBERSHIP_QUERY);
Esin Karaman00e16b72020-02-21 10:32:39 +0000325 }
326 });
Ilayda Ozdemir0872abd2020-06-03 20:20:20 +0300327 igmpStatisticsManager.increaseStat(IgmpStatisticType.CURRENT_GRP_NUMBER_COUNTER);
Deepa Vaddireddybcd52352017-09-21 05:04:48 +0000328 } else {
329 //Don't know which group is targeted by the query
330 //So query all the members(in all the OLTs) and proxy their reports
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000331 stateMachineService.generalQuery(maxResponseTime);
Ilayda Ozdemir0872abd2020-06-03 20:20:20 +0300332 igmpStatisticsManager.increaseStat(IgmpStatisticType.IGMP_GENERAL_MEMBERSHIP_QUERY);
Deepa Vaddireddybcd52352017-09-21 05:04:48 +0000333 }
334 }
335
336
337 private int calculateMaxResp(int maxResp) {
ke han81a38b92017-03-10 18:41:44 +0800338 if (maxResp >= 128) {
339 int mant = maxResp & 0xf;
340 int exp = (maxResp >> 4) & 0x7;
341 maxResp = (mant | 0x10) << (exp + 3);
342 }
343
Deepa Vaddireddybcd52352017-09-21 05:04:48 +0000344 return (maxResp + 5) / 10;
ke han81a38b92017-03-10 18:41:44 +0800345 }
346
347 private Ip4Address ssmTranslateRoute(IpAddress group) {
348 return ssmTranslateTable.get(group);
349 }
350
351 private void processIgmpReport(IGMPMembership igmpGroup, VlanId vlan, ConnectPoint cp, byte igmpType) {
352 DeviceId deviceId = cp.deviceId();
353 PortNumber portNumber = cp.port();
354
355 Ip4Address groupIp = igmpGroup.getGaddr().getIp4Address();
356 if (!groupIp.isMulticast()) {
357 log.info(groupIp.toString() + " is not a valid group address");
Ilayda Ozdemir0872abd2020-06-03 20:20:20 +0300358 igmpStatisticsManager.increaseStat(IgmpStatisticType.FAIL_JOIN_REQ_UNKNOWN_MULTICAST_IP_COUNTER);
ke han81a38b92017-03-10 18:41:44 +0800359 return;
360 }
361 Ip4Address srcIp = getDeviceIp(deviceId);
362
363 byte recordType = igmpGroup.getRecordType();
364 boolean join = false;
365
366 ArrayList<Ip4Address> sourceList = new ArrayList<>();
367
Sonal Kasliwalf11c0672020-03-18 11:11:50 +0000368 if (!validMembershipModes.contains(recordType)) {
Ilayda Ozdemir0872abd2020-06-03 20:20:20 +0300369 igmpStatisticsManager.increaseStat(IgmpStatisticType.REPORTS_RX_WITH_WRONG_MODE_COUNTER);
Sonal Kasliwalf11c0672020-03-18 11:11:50 +0000370 }
ke han81a38b92017-03-10 18:41:44 +0800371 if (igmpGroup.getSources().size() > 0) {
372 igmpGroup.getSources().forEach(source -> sourceList.add(source.getIp4Address()));
373 if (recordType == IGMPMembership.CHANGE_TO_EXCLUDE_MODE ||
374 recordType == IGMPMembership.MODE_IS_EXCLUDE ||
375 recordType == IGMPMembership.BLOCK_OLD_SOURCES) {
376 join = false;
377 } else if (recordType == IGMPMembership.CHANGE_TO_INCLUDE_MODE ||
378 recordType == IGMPMembership.MODE_IS_INCLUDE ||
379 recordType == IGMPMembership.ALLOW_NEW_SOURCES) {
380 join = true;
381 }
382 } else {
ke han29af27b2017-09-08 10:29:12 +0800383 IpAddress src = null;
384 if (pimSSmInterworking) {
385 src = ssmTranslateRoute(groupIp);
386 if (src == null) {
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000387 log.info("no ssm translate for group {}", groupIp);
ke han29af27b2017-09-08 10:29:12 +0800388 return;
389 }
390 } else {
391 src = IpAddress.valueOf(DEFAULT_PIMSSM_HOST);
ke han81a38b92017-03-10 18:41:44 +0800392 }
393 sourceList.add(src.getIp4Address());
394 if (recordType == IGMPMembership.CHANGE_TO_EXCLUDE_MODE ||
395 recordType == IGMPMembership.MODE_IS_EXCLUDE ||
396 recordType == IGMPMembership.BLOCK_OLD_SOURCES) {
397 join = true;
398 } else if (recordType == IGMPMembership.CHANGE_TO_INCLUDE_MODE ||
399 recordType == IGMPMembership.MODE_IS_INCLUDE ||
400 recordType == IGMPMembership.ALLOW_NEW_SOURCES) {
401 join = false;
402 }
403 }
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000404 GroupMemberId groupMemberKey = GroupMemberId.of(groupIp, deviceId, portNumber);
405 GroupMember groupMember = groupMemberStore.getGroupMember(groupMemberKey);
ke han81a38b92017-03-10 18:41:44 +0800406
407 if (join) {
Ilayda Ozdemir0872abd2020-06-03 20:20:20 +0300408 igmpStatisticsManager.increaseStat(IgmpStatisticType.IGMP_JOIN_REQ);
ke han81a38b92017-03-10 18:41:44 +0800409 if (groupMember == null) {
Esin Karamaneff10392019-06-27 18:09:13 +0000410 Optional<ConnectPoint> sourceConfigured = getSource();
411 if (!sourceConfigured.isPresent()) {
Ilayda Ozdemir0872abd2020-06-03 20:20:20 +0300412 igmpStatisticsManager.increaseStat(IgmpStatisticType.IGMP_FAIL_JOIN_REQ);
Esin Karamaneff10392019-06-27 18:09:13 +0000413 log.warn("Unable to process IGMP Join from {} since no source " +
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000414 "configuration is found.", deviceId);
Ilayda Ozdemir0872abd2020-06-03 20:20:20 +0300415 igmpStatisticsManager
416 .increaseStat(IgmpStatisticType.FAIL_JOIN_REQ_INSUFF_PERMISSION_ACCESS_COUNTER);
Esin Karamaneff10392019-06-27 18:09:13 +0000417 return;
418 }
Esin Karaman00e16b72020-02-21 10:32:39 +0000419
420 Optional<PortNumber> deviceUplink = getDeviceUplink(deviceId);
421 if (deviceUplink.isEmpty()) {
422 log.warn("Unable to process IGMP Join since uplink port " +
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000423 "of the device {} is not found.", deviceId);
Esin Karaman00e16b72020-02-21 10:32:39 +0000424 return;
425 }
426
427 if (igmpType == IGMP.TYPE_IGMPV2_MEMBERSHIP_REPORT) {
428 groupMember = new GroupMember(groupIp, vlan, deviceId, portNumber, true);
429 } else {
430 groupMember = new GroupMember(groupIp, vlan, deviceId, portNumber, false);
431 }
432
Esin Karamaneff10392019-06-27 18:09:13 +0000433 HashSet<ConnectPoint> sourceConnectPoints = Sets.newHashSet(sourceConfigured.get());
434
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000435 boolean isJoined = stateMachineService.join(deviceId, groupIp, srcIp, deviceUplink.get());
Shubham Sharma47f2caf2020-02-18 12:13:40 +0000436 if (isJoined) {
Ilayda Ozdemir0872abd2020-06-03 20:20:20 +0300437 igmpStatisticsManager.increaseStat(IgmpStatisticType.IGMP_SUCCESS_JOIN_RE_JOIN_REQ);
438 igmpStatisticsManager.increaseStat(IgmpStatisticType.IGMP_CHANNEL_JOIN_COUNTER);
Shubham Sharma47f2caf2020-02-18 12:13:40 +0000439 } else {
Ilayda Ozdemir0872abd2020-06-03 20:20:20 +0300440 igmpStatisticsManager.increaseStat(IgmpStatisticType.IGMP_FAIL_JOIN_REQ);
Shubham Sharma47f2caf2020-02-18 12:13:40 +0000441 }
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000442 groupMemberStore.putGroupMember(groupMember);
443 log.debug("Group member created with id: {}", groupMember.getGroupMemberId());
ke han81a38b92017-03-10 18:41:44 +0800444 groupMember.updateList(recordType, sourceList);
Esin Karamaneff10392019-06-27 18:09:13 +0000445 groupMember.getSourceList().forEach(source -> {
446 McastRoute route = new McastRoute(source, groupIp, McastRoute.Type.IGMP);
447 //add route
448 multicastService.add(route);
449 //add source to the route
450 multicastService.addSources(route, Sets.newHashSet(sourceConnectPoints));
451 //add sink to the route
452 multicastService.addSinks(route, Sets.newHashSet(cp));
453 });
Ilayda Ozdemir0872abd2020-06-03 20:20:20 +0300454 igmpStatisticsManager.increaseStat(IgmpStatisticType.UNCONFIGURED_GROUP_COUNTER);
Esin Karamaneff10392019-06-27 18:09:13 +0000455
ke han81a38b92017-03-10 18:41:44 +0800456 }
457 groupMember.resetAllTimers();
458 groupMember.updateList(recordType, sourceList);
459 groupMember.setLeave(false);
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000460 //put updated member to the store
461 groupMemberStore.putGroupMember(groupMember);
ke han81a38b92017-03-10 18:41:44 +0800462 } else {
Ilayda Ozdemir0872abd2020-06-03 20:20:20 +0300463 igmpStatisticsManager.increaseStat(IgmpStatisticType.IGMP_LEAVE_REQ);
ke han81a38b92017-03-10 18:41:44 +0800464 if (groupMember == null) {
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000465 log.info("receive leave but no instance, group {} device: {} port:{}",
466 groupIp, deviceId, portNumber);
Ilayda Ozdemir0872abd2020-06-03 20:20:20 +0300467 igmpStatisticsManager.increaseStat(IgmpStatisticType.UNCONFIGURED_GROUP_COUNTER);
ke han81a38b92017-03-10 18:41:44 +0800468 return;
469 } else {
470 groupMember.setLeave(true);
471 if (fastLeave) {
472 leaveAction(groupMember);
473 } else {
474 sendQuery(groupMember);
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000475 //put modified group member object to the store
476 groupMemberStore.updateGroupMember(groupMember);
ke han81a38b92017-03-10 18:41:44 +0800477 }
478 }
479 }
480 }
481
482 private void leaveAction(GroupMember groupMember) {
Ilayda Ozdemir0872abd2020-06-03 20:20:20 +0300483 igmpStatisticsManager.increaseStat(IgmpStatisticType.IGMP_DISCONNECT);
ke han81a38b92017-03-10 18:41:44 +0800484 ConnectPoint cp = new ConnectPoint(groupMember.getDeviceId(), groupMember.getPortNumber());
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000485 stateMachineService.leave(groupMember.getDeviceId(), groupMember.getGroupIp());
Esin Karamaneff10392019-06-27 18:09:13 +0000486 groupMember.getSourceList().forEach(source -> multicastService.removeSinks(
ke han81a38b92017-03-10 18:41:44 +0800487 new McastRoute(source, groupMember.getGroupIp(),
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000488 McastRoute.Type.IGMP), Sets.newHashSet(cp)));
489 groupMemberStore.removeGroupMember(groupMember.getGroupMemberId());
ke han81a38b92017-03-10 18:41:44 +0800490 }
491
492 private void sendQuery(GroupMember groupMember) {
493 Ethernet ethpkt;
494 Ip4Address srcIp = getDeviceIp(groupMember.getDeviceId());
495 if (groupMember.getv2()) {
Esin Karaman586f1d62020-06-04 10:15:34 +0000496 ethpkt = IgmpSender.getInstance().buildIgmpV2Query(groupMember.getGroupIp(),
497 srcIp, groupMember.getvlan().toShort());
ke han81a38b92017-03-10 18:41:44 +0800498 } else {
Esin Karaman586f1d62020-06-04 10:15:34 +0000499 ethpkt = IgmpSender.getInstance().buildIgmpV3Query(groupMember.getGroupIp(),
500 srcIp, groupMember.getvlan().toShort());
ke han81a38b92017-03-10 18:41:44 +0800501 }
Esin Karaman586f1d62020-06-04 10:15:34 +0000502 log.debug("Sending IGMP query to {}/{} for group {}: {}",
503 groupMember.getDeviceId(), groupMember.getPortNumber(), groupMember.getGroupIp(), ethpkt);
ke han81a38b92017-03-10 18:41:44 +0800504 IgmpSender.getInstance().sendIgmpPacket(ethpkt, groupMember.getDeviceId(), groupMember.getPortNumber());
505 }
506
Esin Karamaneff10392019-06-27 18:09:13 +0000507 /**
508 * @return connect point of the source if configured; and empty Optional otherwise.
509 */
510 public static Optional<ConnectPoint> getSource() {
511 return sourceDeviceAndPort == null ? Optional.empty() :
512 Optional.of(sourceDeviceAndPort);
ke han81a38b92017-03-10 18:41:44 +0800513 }
514
515 /**
516 * Packet processor responsible for forwarding packets along their paths.
517 */
518 private class IgmpPacketProcessor implements PacketProcessor {
519 @Override
520 public void process(PacketContext context) {
Shubham Sharma47f2caf2020-02-18 12:13:40 +0000521
Esin Karamanb38700c2019-09-17 13:01:25 +0000522 eventExecutor.execute(() -> {
523 try {
524 InboundPacket pkt = context.inPacket();
525 Ethernet ethPkt = pkt.parsed();
526 if (ethPkt == null) {
527 return;
528 }
Ilayda Ozdemir0872abd2020-06-03 20:20:20 +0300529 igmpStatisticsManager.increaseStat(IgmpStatisticType.TOTAL_MSG_RECEIVED);
ke han81a38b92017-03-10 18:41:44 +0800530
Esin Karamanb38700c2019-09-17 13:01:25 +0000531 if (ethPkt.getEtherType() != Ethernet.TYPE_IPV4) {
532 return;
533 }
ke han81a38b92017-03-10 18:41:44 +0800534
Esin Karamanb38700c2019-09-17 13:01:25 +0000535 IPv4 ipv4Pkt = (IPv4) ethPkt.getPayload();
ke han81a38b92017-03-10 18:41:44 +0800536
Esin Karamanb38700c2019-09-17 13:01:25 +0000537 if (ipv4Pkt.getProtocol() != IPv4.PROTOCOL_IGMP) {
538 return;
539 }
ke han81a38b92017-03-10 18:41:44 +0800540
Ilayda Ozdemir0872abd2020-06-03 20:20:20 +0300541 igmpStatisticsManager.increaseStat(IgmpStatisticType.IGMP_VALID_CHECKSUM_COUNTER);
Esin Karamanb38700c2019-09-17 13:01:25 +0000542 short vlan = ethPkt.getVlanID();
543 DeviceId deviceId = pkt.receivedFrom().deviceId();
ke han81a38b92017-03-10 18:41:44 +0800544
Esin Karaman00e16b72020-02-21 10:32:39 +0000545 if (!isConnectPoint(deviceId, pkt.receivedFrom().port()) &&
546 !getSubscriberAndDeviceInformation(deviceId).isPresent()) {
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000547 log.error("Device not registered in netcfg : {}", deviceId);
Ilayda Ozdemir0872abd2020-06-03 20:20:20 +0300548 igmpStatisticsManager
549 .increaseStat(IgmpStatisticType.FAIL_JOIN_REQ_INSUFF_PERMISSION_ACCESS_COUNTER);
Esin Karamanb38700c2019-09-17 13:01:25 +0000550 return;
551 }
ke han81a38b92017-03-10 18:41:44 +0800552
Esin Karamanb38700c2019-09-17 13:01:25 +0000553 IGMP igmp = (IGMP) ipv4Pkt.getPayload();
Esin Karamance5ce512020-02-25 15:58:14 +0000554
555 Optional<PortNumber> deviceUpLinkOpt = getDeviceUplink(deviceId);
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000556 PortNumber upLinkPort = deviceUpLinkOpt.isPresent() ? deviceUpLinkOpt.get() : null;
Esin Karamanb38700c2019-09-17 13:01:25 +0000557 switch (igmp.getIgmpType()) {
558 case IGMP.TYPE_IGMPV3_MEMBERSHIP_QUERY:
Ilayda Ozdemir0872abd2020-06-03 20:20:20 +0300559 igmpStatisticsManager.increaseStat(IgmpStatisticType.IGMP_V3_MEMBERSHIP_QUERY);
Esin Karamanb38700c2019-09-17 13:01:25 +0000560 //Discard Query from OLT’s non-uplink port’s
Esin Karamance5ce512020-02-25 15:58:14 +0000561 if (!pkt.receivedFrom().port().equals(upLinkPort)) {
Esin Karamanb38700c2019-09-17 13:01:25 +0000562 if (isConnectPoint(deviceId, pkt.receivedFrom().port())) {
563 log.info("IGMP Picked up query from connectPoint");
564 //OK to process packet
565 processIgmpConnectPointQuery((IGMPQuery) igmp.getGroups().get(0),
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000566 pkt.receivedFrom(),
567 0xff & igmp.getMaxRespField());
Esin Karamanb38700c2019-09-17 13:01:25 +0000568 break;
569 } else {
570 //Not OK to process packet
Esin Karamance5ce512020-02-25 15:58:14 +0000571 log.warn("IGMP Picked up query from non-uplink port {}", upLinkPort);
Esin Karamanb38700c2019-09-17 13:01:25 +0000572 return;
573 }
574 }
ke han81a38b92017-03-10 18:41:44 +0800575
Esin Karamanb38700c2019-09-17 13:01:25 +0000576 processIgmpQuery((IGMPQuery) igmp.getGroups().get(0), pkt.receivedFrom(),
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000577 0xff & igmp.getMaxRespField());
Esin Karamanb38700c2019-09-17 13:01:25 +0000578 break;
579 case IGMP.TYPE_IGMPV1_MEMBERSHIP_REPORT:
Ilayda Ozdemir0872abd2020-06-03 20:20:20 +0300580 igmpStatisticsManager.increaseStat(IgmpStatisticType.IGMP_V1_MEMBERSHIP_REPORT);
Esin Karamanb38700c2019-09-17 13:01:25 +0000581 log.debug("IGMP version 1 message types are not currently supported.");
582 break;
583 case IGMP.TYPE_IGMPV3_MEMBERSHIP_REPORT:
Ilayda Ozdemir0872abd2020-06-03 20:20:20 +0300584 igmpStatisticsManager.increaseStat(IgmpStatisticType.IGMP_V3_MEMBERSHIP_REPORT);
Shubham Sharma47f2caf2020-02-18 12:13:40 +0000585 processIgmpMessage(pkt, igmp, upLinkPort, vlan);
586 break;
Esin Karamanb38700c2019-09-17 13:01:25 +0000587 case IGMP.TYPE_IGMPV2_MEMBERSHIP_REPORT:
Ilayda Ozdemir0872abd2020-06-03 20:20:20 +0300588 igmpStatisticsManager.increaseStat(IgmpStatisticType.IGMP_V2_MEMBERSHIP_REPORT);
Shubham Sharma47f2caf2020-02-18 12:13:40 +0000589 processIgmpMessage(pkt, igmp, upLinkPort, vlan);
590 break;
Esin Karamanb38700c2019-09-17 13:01:25 +0000591 case IGMP.TYPE_IGMPV2_LEAVE_GROUP:
Ilayda Ozdemir0872abd2020-06-03 20:20:20 +0300592 igmpStatisticsManager.increaseStat(IgmpStatisticType.IGMP_V2_LEAVE_GROUP);
Shubham Sharma47f2caf2020-02-18 12:13:40 +0000593 processIgmpMessage(pkt, igmp, upLinkPort, vlan);
Esin Karamanb38700c2019-09-17 13:01:25 +0000594 break;
ke han81a38b92017-03-10 18:41:44 +0800595
Esin Karamanb38700c2019-09-17 13:01:25 +0000596 default:
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000597 log.warn("Unknown IGMP message type: {}", igmp.getIgmpType());
Ilayda Ozdemir0872abd2020-06-03 20:20:20 +0300598 igmpStatisticsManager.increaseStat(IgmpStatisticType.INVALID_IGMP_MSG_RECEIVED);
599 igmpStatisticsManager.increaseStat(IgmpStatisticType.UNKNOWN_IGMP_TYPE_PACKETS_RX_COUNTER);
Esin Karamanb38700c2019-09-17 13:01:25 +0000600 break;
601 }
602
603 } catch (Exception ex) {
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000604 log.error("igmp process error : ", ex);
ke han81a38b92017-03-10 18:41:44 +0800605 }
Esin Karamanb38700c2019-09-17 13:01:25 +0000606 });
ke han81a38b92017-03-10 18:41:44 +0800607 }
608 }
609
Shubham Sharma47f2caf2020-02-18 12:13:40 +0000610 private void processIgmpMessage(InboundPacket pkt, IGMP igmp, PortNumber upLinkPort, short vlan) {
611 //Discard join/leave from OLT’s uplink port’s
612 if (pkt.receivedFrom().port().equals(upLinkPort) ||
613 isConnectPoint(pkt.receivedFrom().deviceId(), pkt.receivedFrom().port())) {
614 log.info("IGMP Picked up join/leave from uplink/connectPoint port");
615 return;
616 }
617
618 Iterator<IGMPGroup> itr = igmp.getGroups().iterator();
619 while (itr.hasNext()) {
620 IGMPGroup group = itr.next();
621 if (group instanceof IGMPMembership) {
622 processIgmpReport((IGMPMembership) group, VlanId.vlanId(vlan),
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000623 pkt.receivedFrom(), igmp.getIgmpType());
Shubham Sharma47f2caf2020-02-18 12:13:40 +0000624 } else {
625 IGMPMembership mgroup = new IGMPMembership(group.getGaddr().getIp4Address());
626 mgroup.setRecordType(igmp.getIgmpType() == IGMP.TYPE_IGMPV2_MEMBERSHIP_REPORT ?
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000627 IGMPMembership.MODE_IS_EXCLUDE :
628 IGMPMembership.MODE_IS_INCLUDE);
Shubham Sharma47f2caf2020-02-18 12:13:40 +0000629 processIgmpReport(mgroup, VlanId.vlanId(vlan),
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000630 pkt.receivedFrom(), igmp.getIgmpType());
Shubham Sharma47f2caf2020-02-18 12:13:40 +0000631 }
632 }
633
634 }
635
ke han81a38b92017-03-10 18:41:44 +0800636 private class IgmpProxyTimerTask extends TimerTask {
637 public void run() {
638 try {
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000639 stateMachineService.timeOut1s();
ke han81a38b92017-03-10 18:41:44 +0800640 queryMembers();
641 } catch (Exception ex) {
642 log.warn("Igmp timer task error : {}", ex.getMessage());
643 }
644 }
645
646 private void queryMembers() {
647 GroupMember groupMember;
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000648 Set<GroupMemberId> keySet = groupMemberStore.getAllGroupMemberIds();
649 for (GroupMemberId key : keySet) {
650 groupMember = groupMemberStore.getGroupMember(key);
651 if (groupMember == null) {
652 continue;
653 }
ke han81a38b92017-03-10 18:41:44 +0800654 DeviceId did = groupMember.getDeviceId();
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000655 if (igmpLeadershipService.isLocalLeader(did)) {
ke han81a38b92017-03-10 18:41:44 +0800656 if (groupMember.isLeave()) {
657 lastQuery(groupMember);
658 } else if (periodicQuery) {
659 periodicQuery(groupMember);
660 }
661 }
662 }
663 }
664
665 private void lastQuery(GroupMember groupMember) {
666 if (groupMember.getLastQueryInterval() < lastQueryInterval) {
667 groupMember.lastQueryInterval(true); // count times
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000668 //put modified group member object to the store
669 groupMemberStore.updateGroupMember(groupMember);
ke han81a38b92017-03-10 18:41:44 +0800670 } else if (groupMember.getLastQueryCount() < lastQueryCount - 1) {
671 sendQuery(groupMember);
672 groupMember.lastQueryInterval(false); // reset count number
673 groupMember.lastQueryCount(true); //count times
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000674 //put modified group member object to the store
675 groupMemberStore.updateGroupMember(groupMember);
ke han81a38b92017-03-10 18:41:44 +0800676 } else if (groupMember.getLastQueryCount() == lastQueryCount - 1) {
677 leaveAction(groupMember);
678 }
679 }
680
681 private void periodicQuery(GroupMember groupMember) {
682 if (groupMember.getKeepAliveQueryInterval() < keepAliveInterval) {
683 groupMember.keepAliveInterval(true);
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000684 //put modified group member object to the store
685 groupMemberStore.updateGroupMember(groupMember);
ke han81a38b92017-03-10 18:41:44 +0800686 } else if (groupMember.getKeepAliveQueryCount() < keepAliveCount) {
687 sendQuery(groupMember);
688 groupMember.keepAliveInterval(false);
689 groupMember.keepAliveQueryCount(true);
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000690 //put modified group member object to the store
691 groupMemberStore.updateGroupMember(groupMember);
ke han81a38b92017-03-10 18:41:44 +0800692 } else if (groupMember.getKeepAliveQueryCount() == keepAliveCount) {
693 leaveAction(groupMember);
694 }
695 }
696
697 }
698
Esin Karaman00e16b72020-02-21 10:32:39 +0000699 public Optional<PortNumber> getDeviceUplink(DeviceId devId) {
700 Device device = deviceService.getDevice(devId);
701 if (device == null || device.serialNumber() == null) {
702 return Optional.empty();
Deepa Vaddireddybcd52352017-09-21 05:04:48 +0000703 }
Esin Karaman00e16b72020-02-21 10:32:39 +0000704 Optional<SubscriberAndDeviceInformation> olt = getSubscriberAndDeviceInformation(device.serialNumber());
705 if (olt.isEmpty()) {
706 return Optional.empty();
707 }
708 PortNumber portNumber = PortNumber.portNumber(olt.get().uplinkPort());
709 return validateUpLinkPort(device.id(), portNumber) ?
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000710 Optional.of(portNumber) : Optional.empty();
Esin Karaman00e16b72020-02-21 10:32:39 +0000711 }
712
713 /**
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000714 * @param deviceId device id
Esin Karaman00e16b72020-02-21 10:32:39 +0000715 * @param portNumber port number
716 * @return true if the port name starts with NNI_PREFIX; false otherwise.
717 */
718 public boolean validateUpLinkPort(DeviceId deviceId, PortNumber portNumber) {
719 Port port = deviceService.getPort(deviceId, portNumber);
720 if (port == null) {
721 //port is not discovered by ONOS; so cannot validate it.
722 return false;
723 }
Esin Karamance5ce512020-02-25 15:58:14 +0000724 boolean isValid = port.annotations().value(AnnotationKeys.PORT_NAME) != null &&
Esin Karaman00e16b72020-02-21 10:32:39 +0000725 port.annotations().value(AnnotationKeys.PORT_NAME).startsWith(NNI_PREFIX);
Esin Karamance5ce512020-02-25 15:58:14 +0000726 if (!isValid) {
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000727 log.warn("Port cannot be validated; it is not configured as an NNI port. Device/port: {}/{}",
728 deviceId, portNumber);
Esin Karamance5ce512020-02-25 15:58:14 +0000729 }
730 return isValid;
ke han81a38b92017-03-10 18:41:44 +0800731 }
732
Esin Karamanb38700c2019-09-17 13:01:25 +0000733 public static boolean isIgmpOnPodBasis() {
734 return igmpOnPodBasis;
735 }
736
ke han81a38b92017-03-10 18:41:44 +0800737 private void processFilterObjective(DeviceId devId, PortNumber port, boolean remove) {
Esin Karamaneff10392019-06-27 18:09:13 +0000738 if (!enableIgmpProvisioning) {
739 log.debug("IGMP trap rules won't be installed since enableIgmpProvisioning flag is not set");
740 return;
741 }
ke han81a38b92017-03-10 18:41:44 +0800742 //TODO migrate to packet requests when packet service uses filtering objectives
743 DefaultFilteringObjective.Builder builder = DefaultFilteringObjective.builder();
744
745 builder = remove ? builder.deny() : builder.permit();
746
747 FilteringObjective igmp = builder
748 .withKey(Criteria.matchInPort(port))
749 .addCondition(Criteria.matchEthType(EthType.EtherType.IPV4.ethType()))
750 .addCondition(Criteria.matchIPProtocol(IPv4.PROTOCOL_IGMP))
751 .withMeta(DefaultTrafficTreatment.builder().setOutput(PortNumber.CONTROLLER).build())
752 .fromApp(appId)
Esin Karamaneff10392019-06-27 18:09:13 +0000753 .withPriority(MAX_PRIORITY)
ke han81a38b92017-03-10 18:41:44 +0800754 .add(new ObjectiveContext() {
755 @Override
756 public void onSuccess(Objective objective) {
Esin Karamaneff10392019-06-27 18:09:13 +0000757 log.info("Igmp filter for {} on {} {}.",
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000758 devId, port, (remove) ? REMOVED : INSTALLED);
ke han81a38b92017-03-10 18:41:44 +0800759 }
760
761 @Override
762 public void onError(Objective objective, ObjectiveError error) {
Esin Karamaneff10392019-06-27 18:09:13 +0000763 log.info("Igmp filter {} for device {} on port {} failed because of {}",
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +0000764 (remove) ? INSTALLATION : REMOVAL, devId, port,
765 error);
ke han81a38b92017-03-10 18:41:44 +0800766 }
767 });
768
769 flowObjectiveService.filter(devId, igmp);
Esin Karamaneff10392019-06-27 18:09:13 +0000770
ke han81a38b92017-03-10 18:41:44 +0800771 }
772
773 private boolean isConnectPoint(DeviceId device, PortNumber port) {
Deepa Vaddireddy01f33b42017-07-02 13:26:53 +0530774 if (connectPoint != null) {
775 return (connectPointMode && connectPoint.deviceId().equals(device)
776 && connectPoint.port().equals(port));
777 } else {
778 log.info("connectPoint not configured for device {}", device);
779 return false;
780 }
ke han81a38b92017-03-10 18:41:44 +0800781 }
Deepa Vaddireddy01f33b42017-07-02 13:26:53 +0530782
ke han81a38b92017-03-10 18:41:44 +0800783 private boolean isUplink(DeviceId device, PortNumber port) {
Esin Karamance5ce512020-02-25 15:58:14 +0000784 if (connectPointMode) {
785 return false;
786 }
787 Optional<PortNumber> upLinkPort = getDeviceUplink(device);
788 return upLinkPort.isPresent() && upLinkPort.get().equals(port);
ke han81a38b92017-03-10 18:41:44 +0800789 }
790
Esin Karaman00e16b72020-02-21 10:32:39 +0000791 /**
792 * Fetches device information associated with the device serial number from SADIS.
793 *
794 * @param serialNumber serial number of a device
795 * @return device information; an empty Optional otherwise.
796 */
797 private Optional<SubscriberAndDeviceInformation> getSubscriberAndDeviceInformation(String serialNumber) {
798 long start = System.currentTimeMillis();
799 try {
800 return Optional.ofNullable(sadisService.getSubscriberInfoService().get(serialNumber));
801 } finally {
802 if (log.isDebugEnabled()) {
803 // SADIS can call remote systems to fetch device data and this calls can take a long time.
804 // This measurement is just for monitoring these kinds of situations.
805 log.debug("Device fetched from SADIS. Elapsed {} msec", System.currentTimeMillis() - start);
806 }
807
808 }
809 }
810
811 /**
812 * Fetches device information associated with the device serial number from SADIS.
813 *
814 * @param deviceId device id
815 * @return device information; an empty Optional otherwise.
816 */
817 private Optional<SubscriberAndDeviceInformation> getSubscriberAndDeviceInformation(DeviceId deviceId) {
818 Device device = deviceService.getDevice(deviceId);
819 if (device == null || device.serialNumber() == null) {
820 return Optional.empty();
821 }
822 return getSubscriberAndDeviceInformation(device.serialNumber());
823 }
824
ke han81a38b92017-03-10 18:41:44 +0800825 private class InternalDeviceListener implements DeviceListener {
826 @Override
827 public void event(DeviceEvent event) {
Esin Karaman09b41e52020-06-01 10:52:55 +0000828 eventExecutor.execute(() -> {
829 DeviceId devId = event.subject().id();
830 Port p = event.port();
Deepa Vaddireddyca7b25d2017-09-28 13:47:18 +0000831
Esin Karaman09b41e52020-06-01 10:52:55 +0000832 if (!igmpLeadershipService.isLocalLeader(devId)) {
833 return;
834 }
835 if (getSubscriberAndDeviceInformation(devId).isEmpty() &&
836 !(p != null && isConnectPoint(devId, p.number()))) {
837 return;
838 }
839 PortNumber port;
ke han81a38b92017-03-10 18:41:44 +0800840
Esin Karaman09b41e52020-06-01 10:52:55 +0000841 switch (event.type()) {
842
843 case DEVICE_ADDED:
844 case DEVICE_UPDATED:
845 case DEVICE_REMOVED:
846 case DEVICE_SUSPENDED:
847 case DEVICE_AVAILABILITY_CHANGED:
848 case PORT_STATS_UPDATED:
849 break;
850 case PORT_ADDED:
851 port = p.number();
852 if (getSubscriberAndDeviceInformation(devId).isPresent() &&
853 !isUplink(devId, port) && !isConnectPoint(devId, port)) {
ke han81a38b92017-03-10 18:41:44 +0800854 processFilterObjective(devId, port, false);
Esin Karaman09b41e52020-06-01 10:52:55 +0000855 } else if (isUplink(devId, port)) {
856 provisionUplinkFlows();
857 } else if (isConnectPoint(devId, port)) {
ke han81a38b92017-03-10 18:41:44 +0800858 provisionConnectPointFlows();
ke han81a38b92017-03-10 18:41:44 +0800859 }
Esin Karaman09b41e52020-06-01 10:52:55 +0000860 onSourceStateChanged(devId, port, true);
861 break;
862 case PORT_UPDATED:
863 port = p.number();
864 if (getSubscriberAndDeviceInformation(devId).isPresent() &&
865 !isUplink(devId, port) && !isConnectPoint(devId, port)) {
866 processFilterObjective(devId, port, !event.port().isEnabled());
867 } else if (isUplink(devId, port)) {
868 if (event.port().isEnabled()) {
869 provisionUplinkFlows(devId);
870 } else {
871 processFilterObjective(devId, port, true);
872 }
873 } else if (isConnectPoint(devId, port)) {
874 if (event.port().isEnabled()) {
875 provisionConnectPointFlows();
876 } else {
877 unprovisionConnectPointFlows();
878 }
879 }
880 onSourceStateChanged(devId, port, event.port().isEnabled());
881 break;
882 case PORT_REMOVED:
883 port = p.number();
884 processFilterObjective(devId, port, true);
885 onSourceStateChanged(devId, port, false);
886 break;
887 default:
888 log.info("Unknown device event {}", event.type());
889 break;
890 }
891 });
ke han81a38b92017-03-10 18:41:44 +0800892 }
893
894 @Override
895 public boolean isRelevant(DeviceEvent event) {
896 return true;
897 }
898 }
899
Esin Karaman09b41e52020-06-01 10:52:55 +0000900 private Set<McastRoute> multicastRoutesOfIgmpProxy() {
901 Set<McastRoute> routes = new HashSet<>(); //using set to eliminate multiple entities
902 groupMemberStore.getAllGroupMemberIds().forEach(groupMemberId -> {
903 GroupMember groupMember = groupMemberStore.getGroupMember(groupMemberId);
904 if (groupMember != null) {
905 groupMember.getSourceList().forEach(source -> {
906 //regenerate the routes created by this application
907 routes.add(new McastRoute(source, groupMemberId.getGroupIp(), McastRoute.Type.IGMP));
908 });
909 }
910 });
911 return routes;
912 }
913
914 private void onSourceStateChanged(DeviceId deviceId, PortNumber portNumber, boolean enabled) {
915 if (!(getSource().isPresent() &&
916 getSource().get().deviceId().equals(deviceId) &&
917 getSource().get().port().equals(portNumber))) {
918 //connect point is not configured as the source
919 log.debug("{}/{} is not the source cp. Stopped processing it further", deviceId, portNumber);
920 return;
921 }
922 log.info("source device:port is {}. DeviceId={}, portNumber={}",
923 (enabled ? "enabled. Restoring the source" :
924 "disabled. Deleting it from multicast routes"), deviceId, portNumber);
925
926 Set<McastRoute> routes = multicastRoutesOfIgmpProxy();
927 routes.forEach(route -> {
928 if (enabled) {
929 //add source to the route
930 multicastService.addSources(route, Sets.newHashSet(new ConnectPoint(deviceId, portNumber)));
931 } else {
932 //remove the source from the route
933 multicastService.removeSources(route, Sets.newHashSet(new ConnectPoint(deviceId, portNumber)));
934 }
935 });
936 }
937
ke han81a38b92017-03-10 18:41:44 +0800938 private class InternalNetworkConfigListener implements NetworkConfigListener {
939
940 private void reconfigureNetwork(IgmpproxyConfig cfg) {
Esin Karamaneff10392019-06-27 18:09:13 +0000941 IgmpproxyConfig newCfg = cfg == null ? new IgmpproxyConfig() : cfg;
ke han81a38b92017-03-10 18:41:44 +0800942
943 unSolicitedTimeout = newCfg.unsolicitedTimeOut();
944 maxResp = newCfg.maxResp();
945 keepAliveInterval = newCfg.keepAliveInterval();
946 keepAliveCount = newCfg.keepAliveCount();
947 lastQueryInterval = newCfg.lastQueryInterval();
948 lastQueryCount = newCfg.lastQueryCount();
949 withRAUplink = newCfg.withRAUplink();
950 withRADownlink = newCfg.withRADownlink();
951 igmpCos = newCfg.igmpCos();
Esin Karaman586f1d62020-06-04 10:15:34 +0000952 igmpUniCos = newCfg.igmpUniCos(); // defines priority bit of IGMP query message sent to UNI ports
ke han81a38b92017-03-10 18:41:44 +0800953 periodicQuery = newCfg.periodicQuery();
954 fastLeave = newCfg.fastLeave();
ke han29af27b2017-09-08 10:29:12 +0800955 pimSSmInterworking = newCfg.pimSsmInterworking();
Esin Karamaneff10392019-06-27 18:09:13 +0000956 enableIgmpProvisioning = newCfg.enableIgmpProvisioning();
Esin Karamanb38700c2019-09-17 13:01:25 +0000957 igmpOnPodBasis = newCfg.igmpOnPodBasis();
Arjun E Kb0018fd2020-04-07 13:26:40 +0000958 if (newCfg.outgoingIgmpWithV3() != null &&
959 outgoingIgmpWithV3 != newCfg.outgoingIgmpWithV3()) {
960 outgoingIgmpWithV3 = newCfg.outgoingIgmpWithV3();
961 }
Deepa Vaddireddyca7b25d2017-09-28 13:47:18 +0000962
963 if (connectPointMode != newCfg.connectPointMode() ||
964 connectPoint != newCfg.connectPoint()) {
ke han81a38b92017-03-10 18:41:44 +0800965 connectPointMode = newCfg.connectPointMode();
Deepa Vaddireddyca7b25d2017-09-28 13:47:18 +0000966 connectPoint = newCfg.connectPoint();
ke han81a38b92017-03-10 18:41:44 +0800967 if (connectPointMode) {
968 unprovisionUplinkFlows();
969 provisionConnectPointFlows();
970 } else {
971 unprovisionConnectPointFlows();
972 provisionUplinkFlows();
973 }
974 }
975 if (connectPoint != null) {
Esin Karamaneff10392019-06-27 18:09:13 +0000976 log.info("connect point : {}", connectPoint);
ke han81a38b92017-03-10 18:41:44 +0800977 }
Esin Karamaneff10392019-06-27 18:09:13 +0000978 log.info("mode: {}", connectPointMode);
979
980 getSourceConnectPoint(newCfg);
ke han81a38b92017-03-10 18:41:44 +0800981
982 IgmpSender.getInstance().setIgmpCos(igmpCos);
Esin Karaman586f1d62020-06-04 10:15:34 +0000983 IgmpSender.getInstance().setIgmpUniCos(igmpUniCos);
ke han81a38b92017-03-10 18:41:44 +0800984 IgmpSender.getInstance().setMaxResp(maxResp);
985 IgmpSender.getInstance().setMvlan(mvlan);
Esin Karaman586f1d62020-06-04 10:15:34 +0000986 IgmpSender.getInstance().setMvlanInner(mvlanInner);
ke han81a38b92017-03-10 18:41:44 +0800987 IgmpSender.getInstance().setWithRADownlink(withRADownlink);
988 IgmpSender.getInstance().setWithRAUplink(withRAUplink);
Esin Karamaneff10392019-06-27 18:09:13 +0000989 }
ke han81a38b92017-03-10 18:41:44 +0800990
Esin Karamaneff10392019-06-27 18:09:13 +0000991 void getSourceConnectPoint(IgmpproxyConfig cfg) {
Esin Karaman09b41e52020-06-01 10:52:55 +0000992 ConnectPoint oldSourceDevPort = sourceDeviceAndPort;
Esin Karamaneff10392019-06-27 18:09:13 +0000993 sourceDeviceAndPort = cfg.getSourceDeviceAndPort();
994 if (sourceDeviceAndPort != null) {
995 log.debug("source parameter configured to {}", sourceDeviceAndPort);
996 }
Esin Karaman09b41e52020-06-01 10:52:55 +0000997 if (oldSourceDevPort != null && !oldSourceDevPort.equals(sourceDeviceAndPort)) {
998 //source config has changed, remove the old source from multicast routes
999 onSourceStateChanged(oldSourceDevPort.deviceId(), oldSourceDevPort.port(), false);
1000 }
1001 if (sourceDeviceAndPort != null && !sourceDeviceAndPort.equals(oldSourceDevPort)) {
1002 //add new source to the existing routes
1003 onSourceStateChanged(sourceDeviceAndPort.deviceId(), sourceDeviceAndPort.port(), true);
1004 }
ke han81a38b92017-03-10 18:41:44 +08001005 }
1006
1007 public void reconfigureSsmTable(IgmpproxySsmTranslateConfig cfg) {
1008 if (cfg == null) {
1009 return;
1010 }
1011 Collection<McastRoute> translations = cfg.getSsmTranslations();
1012 for (McastRoute route : translations) {
Esin Karamaneff10392019-06-27 18:09:13 +00001013 ssmTranslateTable.put(route.group().getIp4Address(), route.source().get().getIp4Address());
ke han81a38b92017-03-10 18:41:44 +08001014 }
1015 }
1016
1017 @Override
1018 public void event(NetworkConfigEvent event) {
1019 switch (event.type()) {
1020 case CONFIG_ADDED:
1021 case CONFIG_UPDATED:
Matteo Scandolo7482bbe2020-02-12 14:37:09 -08001022 // NOTE how to know if something has changed in sadis?
ke han81a38b92017-03-10 18:41:44 +08001023
1024 if (event.configClass().equals(IGMPPROXY_CONFIG_CLASS)) {
1025 IgmpproxyConfig config = networkConfig.getConfig(appId, IGMPPROXY_CONFIG_CLASS);
1026 if (config != null) {
Esin Karamaneff10392019-06-27 18:09:13 +00001027 log.info("igmpproxy config received. {}", config);
ke han81a38b92017-03-10 18:41:44 +08001028 reconfigureNetwork(config);
1029 }
1030 }
1031
1032 if (event.configClass().equals(IGMPPROXY_SSM_CONFIG_CLASS)) {
1033 IgmpproxySsmTranslateConfig config = networkConfig.getConfig(appId, IGMPPROXY_SSM_CONFIG_CLASS);
1034 if (config != null) {
1035 reconfigureSsmTable(config);
1036 }
1037 }
1038
1039 if (event.configClass().equals(MCAST_CONFIG_CLASS)) {
1040 McastConfig config = networkConfig.getConfig(coreAppId, MCAST_CONFIG_CLASS);
Esin Karaman586f1d62020-06-04 10:15:34 +00001041 boolean vlanConfigChanged = config != null && mvlan != config.egressVlan().toShort();
1042 boolean innerVlanConfigChanged = config != null &&
1043 mvlanInner != config.egressInnerVlan().toShort();
1044
1045 if (vlanConfigChanged || innerVlanConfigChanged) {
1046 log.info("igmpproxy vlan config received. {}", config);
1047 //at least one of the vlan configs has changed. Call leave before setting new values
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +00001048 groupMemberStore.getAllGroupMembers().forEach(m -> leaveAction(m));
Esin Karaman586f1d62020-06-04 10:15:34 +00001049 if (vlanConfigChanged) {
1050 mvlan = config.egressVlan().toShort();
1051 IgmpSender.getInstance().setMvlan(mvlan);
1052 }
1053 if (innerVlanConfigChanged) {
1054 mvlanInner = config.egressInnerVlan().toShort();
1055 IgmpSender.getInstance().setMvlanInner(mvlanInner);
1056 }
ke han81a38b92017-03-10 18:41:44 +08001057 }
1058 }
1059
1060 log.info("Reconfigured");
1061 break;
1062 case CONFIG_REGISTERED:
1063 case CONFIG_UNREGISTERED:
1064 break;
1065 case CONFIG_REMOVED:
Matteo Scandolo7482bbe2020-02-12 14:37:09 -08001066 // NOTE how to know if something has changed in sadis?
ke han81a38b92017-03-10 18:41:44 +08001067 default:
1068 break;
1069 }
1070 }
1071 }
1072
ke han81a38b92017-03-10 18:41:44 +08001073 private void provisionUplinkFlows(DeviceId deviceId) {
1074 if (connectPointMode) {
1075 return;
1076 }
1077
Esin Karaman00e16b72020-02-21 10:32:39 +00001078 Optional<PortNumber> upLink = getDeviceUplink(deviceId);
1079 if (upLink.isPresent()) {
1080 processFilterObjective(deviceId, upLink.get(), false);
1081 }
ke han81a38b92017-03-10 18:41:44 +08001082 }
1083
1084 private void provisionUplinkFlows() {
1085 if (connectPointMode) {
1086 return;
1087 }
Esin Karaman00e16b72020-02-21 10:32:39 +00001088 deviceService.getAvailableDevices().forEach(device -> {
1089 Optional<SubscriberAndDeviceInformation> accessDevice = getSubscriberAndDeviceInformation(device.id());
1090 if (accessDevice.isPresent()) {
1091 provisionUplinkFlows(device.id());
1092 }
1093 });
ke han81a38b92017-03-10 18:41:44 +08001094 }
Esin Karaman00e16b72020-02-21 10:32:39 +00001095
ke han81a38b92017-03-10 18:41:44 +08001096 private void unprovisionUplinkFlows() {
Esin Karaman00e16b72020-02-21 10:32:39 +00001097 deviceService.getAvailableDevices().forEach(device -> {
1098 Optional<SubscriberAndDeviceInformation> accessDevices = getSubscriberAndDeviceInformation(device.id());
1099 if (accessDevices.isPresent()) {
1100 Optional<PortNumber> upLink = getDeviceUplink(device.id());
1101 if (upLink.isPresent()) {
1102 processFilterObjective(device.id(), upLink.get(), true);
1103 }
1104 }
1105 });
ke han81a38b92017-03-10 18:41:44 +08001106 }
1107
1108 private void provisionConnectPointFlows() {
1109 if ((!connectPointMode) || connectPoint == null) {
1110 return;
1111 }
1112
1113 processFilterObjective(connectPoint.deviceId(), connectPoint.port(), false);
1114 }
Ilayda Ozdemir4c5947c2020-05-05 13:14:32 +00001115
ke han81a38b92017-03-10 18:41:44 +08001116 private void unprovisionConnectPointFlows() {
1117 if (connectPoint == null) {
1118 return;
1119 }
1120 processFilterObjective(connectPoint.deviceId(), connectPoint.port(), true);
1121 }
Shubham Sharma47f2caf2020-02-18 12:13:40 +00001122
ke han81a38b92017-03-10 18:41:44 +08001123}