blob: ba4bf1a73a08569b6302da1641dddfec6559363a [file] [log] [blame]
Amit Ghosh47243cb2017-07-26 05:08:53 +01001/*
Deepa vaddireddy0060f532017-08-04 06:46:05 +00002 * Copyright 2017-present Open Networking Foundation
Amit Ghosh47243cb2017-07-26 05:08:53 +01003 *
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 */
Matteo Scandolo57af5d12019-04-29 17:11:41 -070016package org.opencord.dhcpl2relay.impl;
Amit Ghosh47243cb2017-07-26 05:08:53 +010017
Carmelo Casconede1e6e32019-07-15 19:39:08 -070018import com.google.common.collect.ImmutableSet;
19import com.google.common.collect.Lists;
20import com.google.common.collect.Maps;
21import com.google.common.collect.Sets;
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -030022import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
23import static org.onlab.packet.MacAddress.valueOf;
24import static org.onlab.util.Tools.get;
25import static org.onlab.util.Tools.getIntegerProperty;
26import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
27
28import java.io.ByteArrayOutputStream;
29import java.nio.ByteBuffer;
30import java.util.AbstractMap;
31import java.util.ArrayList;
32import java.util.Arrays;
33import java.util.Dictionary;
34import java.util.List;
35import java.util.Map;
36import java.util.Optional;
37import java.util.Set;
38import java.util.concurrent.Executors;
39import java.util.concurrent.atomic.AtomicLong;
40import java.util.concurrent.atomic.AtomicReference;
41import java.util.concurrent.ScheduledExecutorService;
42import java.util.concurrent.ScheduledFuture;
43import java.util.concurrent.TimeUnit;
44import java.util.stream.Collectors;
45
46import org.apache.commons.io.HexDump;
Amit Ghosh47243cb2017-07-26 05:08:53 +010047import org.onlab.packet.DHCP;
Deepa vaddireddy0060f532017-08-04 06:46:05 +000048import org.onlab.packet.Ethernet;
Amit Ghosh47243cb2017-07-26 05:08:53 +010049import org.onlab.packet.IPv4;
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +053050import org.onlab.packet.IpAddress;
Deepa vaddireddy0060f532017-08-04 06:46:05 +000051import org.onlab.packet.MacAddress;
Amit Ghosh47243cb2017-07-26 05:08:53 +010052import org.onlab.packet.TpPort;
53import org.onlab.packet.UDP;
54import org.onlab.packet.VlanId;
Jonathan Hartedbf6422018-05-02 17:30:05 -070055import org.onlab.packet.dhcp.DhcpOption;
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -030056import org.onlab.util.SafeRecurringTask;
Amit Ghosh47243cb2017-07-26 05:08:53 +010057import org.onlab.util.Tools;
58import org.onosproject.cfg.ComponentConfigService;
59import org.onosproject.core.ApplicationId;
60import org.onosproject.core.CoreService;
Jonathan Hartc36c9552018-07-31 15:07:53 -040061import org.onosproject.event.AbstractListenerManager;
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +053062import org.onosproject.mastership.MastershipEvent;
63import org.onosproject.mastership.MastershipListener;
64import org.onosproject.mastership.MastershipService;
Amit Ghosh47243cb2017-07-26 05:08:53 +010065import org.onosproject.net.AnnotationKeys;
66import org.onosproject.net.ConnectPoint;
Amit Ghosh83c8c892017-11-09 11:08:27 +000067import org.onosproject.net.Device;
68import org.onosproject.net.DeviceId;
Amit Ghosh47243cb2017-07-26 05:08:53 +010069import org.onosproject.net.Host;
70import org.onosproject.net.Port;
Amit Ghosh83c8c892017-11-09 11:08:27 +000071import org.onosproject.net.PortNumber;
Amit Ghosh47243cb2017-07-26 05:08:53 +010072import org.onosproject.net.config.ConfigFactory;
73import org.onosproject.net.config.NetworkConfigEvent;
74import org.onosproject.net.config.NetworkConfigListener;
75import org.onosproject.net.config.NetworkConfigRegistry;
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +053076import org.onosproject.net.device.DeviceEvent;
77import org.onosproject.net.device.DeviceListener;
Amit Ghosh47243cb2017-07-26 05:08:53 +010078import org.onosproject.net.device.DeviceService;
79import org.onosproject.net.flow.DefaultTrafficSelector;
80import org.onosproject.net.flow.DefaultTrafficTreatment;
81import org.onosproject.net.flow.TrafficSelector;
82import org.onosproject.net.flow.TrafficTreatment;
Saurav Dasb4e3e102018-10-02 15:31:17 -070083import org.onosproject.net.flowobjective.FlowObjectiveService;
Amit Ghosh47243cb2017-07-26 05:08:53 +010084import org.onosproject.net.host.HostService;
85import org.onosproject.net.packet.DefaultOutboundPacket;
86import org.onosproject.net.packet.OutboundPacket;
87import org.onosproject.net.packet.PacketContext;
88import org.onosproject.net.packet.PacketPriority;
89import org.onosproject.net.packet.PacketProcessor;
90import org.onosproject.net.packet.PacketService;
Matteo Scandolo57af5d12019-04-29 17:11:41 -070091import org.opencord.dhcpl2relay.DhcpAllocationInfo;
92import org.opencord.dhcpl2relay.DhcpL2RelayEvent;
93import org.opencord.dhcpl2relay.DhcpL2RelayListener;
94import org.opencord.dhcpl2relay.DhcpL2RelayService;
95import org.opencord.dhcpl2relay.impl.packet.DhcpOption82;
Gamze Abakac806c6c2018-12-03 12:49:46 +000096import org.opencord.sadis.BaseInformationService;
97import org.opencord.sadis.SadisService;
Amit Ghosh47243cb2017-07-26 05:08:53 +010098import org.opencord.sadis.SubscriberAndDeviceInformation;
Gamze Abakaa64b3bc2020-01-31 06:51:43 +000099import org.opencord.sadis.UniTagInformation;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100100import org.osgi.service.component.ComponentContext;
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700101import org.osgi.service.component.annotations.Activate;
102import org.osgi.service.component.annotations.Component;
103import org.osgi.service.component.annotations.Deactivate;
104import org.osgi.service.component.annotations.Modified;
105import org.osgi.service.component.annotations.Reference;
106import org.osgi.service.component.annotations.ReferenceCardinality;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100107import org.slf4j.Logger;
108import org.slf4j.LoggerFactory;
109
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300110import static org.opencord.dhcpl2relay.impl.OsgiPropertyConstants.*;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100111
112/**
113 * DHCP Relay Agent Application Component.
114 */
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700115@Component(immediate = true,
116property = {
117 OPTION_82 + ":Boolean=" + OPTION_82_DEFAULT,
118 ENABLE_DHCP_BROADCAST_REPLIES + ":Boolean=" + ENABLE_DHCP_BROADCAST_REPLIES_DEFAULT,
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300119 PUBLISH_COUNTERS_RATE + ":Integer=" + PUBLISH_COUNTERS_RATE_DEFAULT,
120 DHCP_COUNTERS_TOPIC + ":String=" + DHCP_COUNTERS_TOPIC_DEFAULT
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700121})
Jonathan Hartc36c9552018-07-31 15:07:53 -0400122public class DhcpL2Relay
123 extends AbstractListenerManager<DhcpL2RelayEvent, DhcpL2RelayListener>
124 implements DhcpL2RelayService {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100125
126 public static final String DHCP_L2RELAY_APP = "org.opencord.dhcpl2relay";
Saurav Dasb4e3e102018-10-02 15:31:17 -0700127 private static final String HOST_LOC_PROVIDER =
128 "org.onosproject.provider.host.impl.HostLocationProvider";
Amit Ghosh47243cb2017-07-26 05:08:53 +0100129 private final Logger log = LoggerFactory.getLogger(getClass());
130 private final InternalConfigListener cfgListener =
131 new InternalConfigListener();
132
133 private final Set<ConfigFactory> factories = ImmutableSet.of(
134 new ConfigFactory<ApplicationId, DhcpL2RelayConfig>(APP_SUBJECT_FACTORY,
135 DhcpL2RelayConfig.class,
136 "dhcpl2relay") {
137 @Override
138 public DhcpL2RelayConfig createConfig() {
139 return new DhcpL2RelayConfig();
140 }
141 }
142 );
143
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700144 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh47243cb2017-07-26 05:08:53 +0100145 protected NetworkConfigRegistry cfgService;
146
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700147 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh47243cb2017-07-26 05:08:53 +0100148 protected CoreService coreService;
149
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700150 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh47243cb2017-07-26 05:08:53 +0100151 protected PacketService packetService;
152
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700153 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh47243cb2017-07-26 05:08:53 +0100154 protected HostService hostService;
155
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700156 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh47243cb2017-07-26 05:08:53 +0100157 protected ComponentConfigService componentConfigService;
158
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700159 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Gamze Abakac806c6c2018-12-03 12:49:46 +0000160 protected SadisService sadisService;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100161
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700162 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh47243cb2017-07-26 05:08:53 +0100163 protected DeviceService deviceService;
164
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700165 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh8951f042017-08-10 13:48:10 +0100166 protected MastershipService mastershipService;
167
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700168 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Saurav Dasb4e3e102018-10-02 15:31:17 -0700169 protected FlowObjectiveService flowObjectiveService;
170
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300171 @Reference(cardinality = ReferenceCardinality.MANDATORY)
172 protected DhcpL2RelayCountersStore dhcpL2RelayCounters;
173
174 // OSGi Properties
Carmelo Cascone4330cf12019-11-15 21:34:02 -0800175 /** Add option 82 to relayed packets. */
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700176 protected boolean option82 = OPTION_82_DEFAULT;
Carmelo Cascone4330cf12019-11-15 21:34:02 -0800177 /** Ask the DHCP Server to send back replies as L2 broadcast. */
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700178 protected boolean enableDhcpBroadcastReplies = ENABLE_DHCP_BROADCAST_REPLIES_DEFAULT;
Amit Ghosha17354e2017-08-23 12:56:04 +0100179
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300180 protected int publishCountersRate = PUBLISH_COUNTERS_RATE_DEFAULT;
181 private String dhcpCountersTopic = DHCP_COUNTERS_TOPIC_DEFAULT;
182
183
184 protected PublishCountersToKafka publishCountersToKafka;
185
186 ScheduledFuture<?> refreshTask;
187 ScheduledExecutorService refreshService = Executors.newSingleThreadScheduledExecutor();
188
Amit Ghosh47243cb2017-07-26 05:08:53 +0100189 private DhcpRelayPacketProcessor dhcpRelayPacketProcessor =
190 new DhcpRelayPacketProcessor();
191
Amit Ghosh8951f042017-08-10 13:48:10 +0100192 private InnerMastershipListener changeListener = new InnerMastershipListener();
193 private InnerDeviceListener deviceListener = new InnerDeviceListener();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100194
Amit Ghosh8951f042017-08-10 13:48:10 +0100195 // connect points to the DHCP server
196 Set<ConnectPoint> dhcpConnectPoints;
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300197 protected AtomicReference<ConnectPoint> dhcpServerConnectPoint = new AtomicReference<>();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100198 private MacAddress dhcpConnectMac = MacAddress.BROADCAST;
199 private ApplicationId appId;
200
Amit Ghosha17354e2017-08-23 12:56:04 +0100201 static Map<String, DhcpAllocationInfo> allocationMap = Maps.newConcurrentMap();
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300202 protected boolean modifyClientPktsSrcDstMac = false;
Amit Ghosh83c8c892017-11-09 11:08:27 +0000203 //Whether to use the uplink port of the OLTs to send/receive messages to the DHCP server
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300204 protected boolean useOltUplink = false;
Amit Ghosha17354e2017-08-23 12:56:04 +0100205
Gamze Abakac806c6c2018-12-03 12:49:46 +0000206 private BaseInformationService<SubscriberAndDeviceInformation> subsService;
207
Amit Ghosh47243cb2017-07-26 05:08:53 +0100208 @Activate
209 protected void activate(ComponentContext context) {
210 //start the dhcp relay agent
211 appId = coreService.registerApplication(DHCP_L2RELAY_APP);
Saurav Dasb4e3e102018-10-02 15:31:17 -0700212 // ensure that host-learning via dhcp includes IP addresses
213 componentConfigService.preSetProperty(HOST_LOC_PROVIDER,
214 "useDhcp", Boolean.TRUE.toString());
Amit Ghosh47243cb2017-07-26 05:08:53 +0100215 componentConfigService.registerProperties(getClass());
Jonathan Hartc36c9552018-07-31 15:07:53 -0400216 eventDispatcher.addSink(DhcpL2RelayEvent.class, listenerRegistry);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100217
218 cfgService.addListener(cfgListener);
Amit Ghosh8951f042017-08-10 13:48:10 +0100219 mastershipService.addListener(changeListener);
220 deviceService.addListener(deviceListener);
221
Matteo Scandolo45e5a272019-09-30 09:30:32 -0700222 subsService = sadisService.getSubscriberInfoService();
223
Amit Ghosh47243cb2017-07-26 05:08:53 +0100224 factories.forEach(cfgService::registerConfigFactory);
225 //update the dhcp server configuration.
226 updateConfig();
227 //add the packet services.
228 packetService.addProcessor(dhcpRelayPacketProcessor,
229 PacketProcessor.director(0));
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000230 if (context != null) {
231 modified(context);
232 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100233
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300234 publishCountersToKafka = new PublishCountersToKafka();
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300235 restartPublishCountersTask();
Gamze Abakac806c6c2018-12-03 12:49:46 +0000236
Amit Ghosh47243cb2017-07-26 05:08:53 +0100237 log.info("DHCP-L2-RELAY Started");
238 }
239
240 @Deactivate
241 protected void deactivate() {
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300242 if (refreshTask != null) {
243 refreshTask.cancel(true);
244 }
245 if (refreshService != null) {
246 refreshService.shutdownNow();
247 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100248 cfgService.removeListener(cfgListener);
249 factories.forEach(cfgService::unregisterConfigFactory);
250 packetService.removeProcessor(dhcpRelayPacketProcessor);
Saurav Dasb4e3e102018-10-02 15:31:17 -0700251 cancelDhcpPktsFromServer();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100252
253 componentConfigService.unregisterProperties(getClass(), false);
Deepa Vaddireddy77a6ac72017-09-20 20:36:52 +0530254 deviceService.removeListener(deviceListener);
255 mastershipService.removeListener(changeListener);
Jonathan Hartc36c9552018-07-31 15:07:53 -0400256 eventDispatcher.removeSink(DhcpL2RelayEvent.class);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100257 log.info("DHCP-L2-RELAY Stopped");
258 }
259
260 @Modified
261 protected void modified(ComponentContext context) {
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000262
Amit Ghosh47243cb2017-07-26 05:08:53 +0100263 Dictionary<?, ?> properties = context.getProperties();
264
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700265 Boolean o = Tools.isPropertyEnabled(properties, OPTION_82);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100266 if (o != null) {
267 option82 = o;
268 }
Amit Ghosh2095dc62017-09-25 20:56:55 +0100269
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700270 o = Tools.isPropertyEnabled(properties, ENABLE_DHCP_BROADCAST_REPLIES);
Amit Ghosh2095dc62017-09-25 20:56:55 +0100271 if (o != null) {
272 enableDhcpBroadcastReplies = o;
273 }
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300274
275 Integer newPublishCountersRate = getIntegerProperty(properties, "publishCountersRate");
276 if (newPublishCountersRate != null) {
277 if (newPublishCountersRate != publishCountersRate && newPublishCountersRate >= 0) {
278 log.info("publishCountersRate modified from {} to {}", publishCountersRate, newPublishCountersRate);
279 publishCountersRate = newPublishCountersRate;
280 } else if (newPublishCountersRate < 0) {
281 log.error("Invalid newPublishCountersRate : {}, defaulting to 0", newPublishCountersRate);
282 publishCountersRate = 0;
283 }
284 restartPublishCountersTask();
285 }
286
287 String newDhcpCountersTopic = get(properties, "dhcpCountersTopic");
288 if (newDhcpCountersTopic != null && !newDhcpCountersTopic.equals(dhcpCountersTopic)) {
289 log.info("Property dhcpCountersTopic modified from {} to {}", dhcpCountersTopic, newDhcpCountersTopic);
290 dhcpCountersTopic = newDhcpCountersTopic;
291 }
292 }
293
294 /**
295 * Starts a thread to publish the counters to kafka at a certain rate time.
296 */
297 private void restartPublishCountersTask() {
298 if (refreshTask != null) {
299 refreshTask.cancel(true);
300 }
301 if (publishCountersRate > 0) {
302 log.info("Refresh Rate set to {}, publishCountersToKafka will be called every {} seconds",
303 publishCountersRate, publishCountersRate);
304 refreshTask = refreshService.scheduleWithFixedDelay(SafeRecurringTask.wrap(publishCountersToKafka),
305 publishCountersRate, publishCountersRate, TimeUnit.SECONDS);
306 } else {
307 log.info("Refresh Rate set to 0, disabling calls to publishCountersToKafka");
308 }
309 }
310
311 /**
312 * Publish the counters to kafka.
313 */
314 private class PublishCountersToKafka implements Runnable {
315 public void run() {
316 dhcpL2RelayCounters.getCountersMap().forEach((counterKey, counterValue) -> {
317 // Publish the global counters
318 if (counterKey.counterClassKey.equals(DhcpL2RelayEvent.GLOBAL_COUNTER)) {
319 post(new DhcpL2RelayEvent(DhcpL2RelayEvent.Type.STATS_UPDATE, null, null,
320 new AbstractMap.SimpleEntry<String, AtomicLong>(counterKey.counterTypeKey.toString(),
321 counterValue), dhcpCountersTopic, null));
322 } else { // Publish the counters per subscriber
323 DhcpAllocationInfo info = allocationMap.get(counterKey.counterClassKey);
324 post(new DhcpL2RelayEvent(DhcpL2RelayEvent.Type.STATS_UPDATE, info, null,
325 new AbstractMap.SimpleEntry<String, AtomicLong>(counterKey.counterTypeKey.toString(),
326 counterValue), dhcpCountersTopic, counterKey.counterClassKey));
327 }
328 });
329 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100330 }
331
332 /**
333 * Checks if this app has been configured.
334 *
335 * @return true if all information we need have been initialized
336 */
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300337 protected boolean configured() {
Amit Ghosh83c8c892017-11-09 11:08:27 +0000338 if (!useOltUplink) {
339 return dhcpServerConnectPoint.get() != null;
340 }
341 return true;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100342 }
343
Amit Ghosh8951f042017-08-10 13:48:10 +0100344 /**
345 * Selects a connect point through an available device for which it is the master.
346 */
347 private void selectServerConnectPoint() {
348 synchronized (this) {
349 dhcpServerConnectPoint.set(null);
350 if (dhcpConnectPoints != null) {
351 // find a connect point through a device for which we are master
352 for (ConnectPoint cp: dhcpConnectPoints) {
353 if (mastershipService.isLocalMaster(cp.deviceId())) {
354 if (deviceService.isAvailable(cp.deviceId())) {
355 dhcpServerConnectPoint.set(cp);
356 }
357 log.info("DHCP connectPoint selected is {}", cp);
358 break;
359 }
360 }
361 }
362
363 log.info("DHCP Server connectPoint is {}", dhcpServerConnectPoint.get());
364
365 if (dhcpServerConnectPoint.get() == null) {
366 log.error("Master of none, can't relay DHCP Message to server");
367 }
368 }
369 }
370
371 /**
372 * Updates the network configuration.
373 */
Amit Ghosh47243cb2017-07-26 05:08:53 +0100374 private void updateConfig() {
375 DhcpL2RelayConfig cfg = cfgService.getConfig(appId, DhcpL2RelayConfig.class);
376 if (cfg == null) {
377 log.warn("Dhcp Server info not available");
378 return;
379 }
Amit Ghosh8951f042017-08-10 13:48:10 +0100380
381 dhcpConnectPoints = Sets.newConcurrentHashSet(cfg.getDhcpServerConnectPoint());
Amit Ghosh83c8c892017-11-09 11:08:27 +0000382 modifyClientPktsSrcDstMac = cfg.getModifySrcDstMacAddresses();
Saurav Dasb4e3e102018-10-02 15:31:17 -0700383 boolean prevUseOltUplink = useOltUplink;
Amit Ghosh83c8c892017-11-09 11:08:27 +0000384 useOltUplink = cfg.getUseOltUplinkForServerPktInOut();
Amit Ghosh8951f042017-08-10 13:48:10 +0100385
Saurav Dasb4e3e102018-10-02 15:31:17 -0700386 if (useOltUplink) {
387 for (ConnectPoint cp : getUplinkPortsOfOlts()) {
388 log.debug("requestDhcpPackets: ConnectPoint: {}", cp);
Matteo Scandolo45e5a272019-09-30 09:30:32 -0700389 requestDhcpPacketsFromConnectPoint(cp, Optional.ofNullable(null));
Saurav Dasb4e3e102018-10-02 15:31:17 -0700390 }
391 // check if previous config was different and so trap flows may
Saurav Dasb14f08a2019-02-22 16:34:15 -0800392 // need to be removed from other places like AGG switches
Saurav Dasb4e3e102018-10-02 15:31:17 -0700393 if (!prevUseOltUplink) {
Saurav Dasb14f08a2019-02-22 16:34:15 -0800394 addOrRemoveDhcpTrapFromServer(false);
Saurav Dasb4e3e102018-10-02 15:31:17 -0700395 }
Saurav Dasb4e3e102018-10-02 15:31:17 -0700396 } else {
Saurav Dasb14f08a2019-02-22 16:34:15 -0800397 // uplink on AGG switch
398 addOrRemoveDhcpTrapFromServer(true);
Saurav Dasb4e3e102018-10-02 15:31:17 -0700399 }
400 }
401
402 private void cancelDhcpPktsFromServer() {
403 if (useOltUplink) {
404 for (ConnectPoint cp : getUplinkPortsOfOlts()) {
405 log.debug("cancelDhcpPackets: ConnectPoint: {}", cp);
Matteo Scandolo45e5a272019-09-30 09:30:32 -0700406 cancelDhcpPacketsFromConnectPoint(cp, Optional.ofNullable(null));
Saurav Dasb4e3e102018-10-02 15:31:17 -0700407 }
408 } else {
Saurav Dasb14f08a2019-02-22 16:34:15 -0800409 // uplink on AGG switch
410 addOrRemoveDhcpTrapFromServer(false);
Amit Ghosh83c8c892017-11-09 11:08:27 +0000411 }
Saurav Dasb4e3e102018-10-02 15:31:17 -0700412 }
413
Saurav Dasb14f08a2019-02-22 16:34:15 -0800414 /**
415 * Used to add or remove DHCP trap flow for packets received from DHCP server.
416 * Typically used on a non OLT device, like an AGG switch. When adding, a
417 * new dhcp server connect point is selected from the configured options.
418 *
419 * @param add true if dhcp trap flow is to be added, false to remove the
420 * trap flow
421 */
422 private void addOrRemoveDhcpTrapFromServer(boolean add) {
423 if (add) {
424 selectServerConnectPoint();
425 log.debug("dhcp server connect point: " + dhcpServerConnectPoint);
426 }
427 if (dhcpServerConnectPoint.get() == null) {
428 log.warn("No dhcpServer connectPoint found, cannot {} dhcp trap flows",
429 (add) ? "install" : "remove");
430 return;
431 }
432 if (add) {
433 log.info("Adding trap to dhcp server connect point: "
434 + dhcpServerConnectPoint);
435 requestDhcpPacketsFromConnectPoint(dhcpServerConnectPoint.get(),
436 Optional.of(PacketPriority.HIGH1));
437 } else {
438 log.info("Removing trap from dhcp server connect point: "
439 + dhcpServerConnectPoint);
440 cancelDhcpPacketsFromConnectPoint(dhcpServerConnectPoint.get(),
441 Optional.of(PacketPriority.HIGH1));
442 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100443 }
444
445 /**
Amit Ghosh83c8c892017-11-09 11:08:27 +0000446 * Returns all the uplink ports of OLTs configured in SADIS.
447 * Only ports visible in ONOS and for which this instance is master
448 * are returned
449 */
450 private List<ConnectPoint> getUplinkPortsOfOlts() {
451 List<ConnectPoint> cps = new ArrayList<>();
452
453 // find all the olt devices and if their uplink ports are visible
454 Iterable<Device> devices = deviceService.getDevices();
455 for (Device d : devices) {
456 // check if this device is provisioned in Sadis
457
458 log.debug("getUplinkPortsOfOlts: Checking mastership of {}", d);
459 // do only for devices for which we are the master
460 if (!mastershipService.isLocalMaster(d.id())) {
461 continue;
462 }
463
464 String devSerialNo = d.serialNumber();
465 SubscriberAndDeviceInformation deviceInfo = subsService.get(devSerialNo);
466 log.debug("getUplinkPortsOfOlts: Found device: {}", deviceInfo);
467 if (deviceInfo != null) {
468 // check if the uplink port with that number is available on the device
469 PortNumber pNum = PortNumber.portNumber(deviceInfo.uplinkPort());
470 Port port = deviceService.getPort(d.id(), pNum);
471 log.debug("getUplinkPortsOfOlts: Found port: {}", port);
472 if (port != null) {
473 cps.add(new ConnectPoint(d.id(), pNum));
474 }
475 }
476 }
477 return cps;
478 }
479
480 /**
481 * Returns whether the passed port is the uplink port of the olt device.
482 */
483 private boolean isUplinkPortOfOlt(DeviceId dId, Port p) {
484 log.debug("isUplinkPortOfOlt: DeviceId: {} Port: {}", dId, p);
485 // do only for devices for which we are the master
486 if (!mastershipService.isLocalMaster(dId)) {
487 return false;
488 }
489
490 Device d = deviceService.getDevice(dId);
491 SubscriberAndDeviceInformation deviceInfo = subsService.get(d.serialNumber());
492
493 if (deviceInfo != null) {
494 return (deviceInfo.uplinkPort() == p.number().toLong());
495 }
496
497 return false;
498 }
499
500 /**
501 * Returns the connectPoint which is the uplink port of the OLT.
502 */
503 private ConnectPoint getUplinkConnectPointOfOlt(DeviceId dId) {
504
505 Device d = deviceService.getDevice(dId);
506 SubscriberAndDeviceInformation deviceInfo = subsService.get(d.serialNumber());
507 log.debug("getUplinkConnectPointOfOlt DeviceId: {} devInfo: {}", dId, deviceInfo);
508 if (deviceInfo != null) {
509 PortNumber pNum = PortNumber.portNumber(deviceInfo.uplinkPort());
510 Port port = deviceService.getPort(d.id(), pNum);
511 if (port != null) {
512 return new ConnectPoint(d.id(), pNum);
513 }
514 }
515
516 return null;
517 }
518
519 /**
520 * Request DHCP packet from particular connect point via PacketService.
Saurav Dasb14f08a2019-02-22 16:34:15 -0800521 * Optionally provide a priority for the trap flow. If no such priority is
522 * provided, the default priority will be used.
523 *
524 * @param cp the connect point to trap dhcp packets from
525 * @param priority of the trap flow, null to use default priority
Amit Ghosh83c8c892017-11-09 11:08:27 +0000526 */
Saurav Dasb14f08a2019-02-22 16:34:15 -0800527 private void requestDhcpPacketsFromConnectPoint(ConnectPoint cp,
528 Optional<PacketPriority> priority) {
Amit Ghosh83c8c892017-11-09 11:08:27 +0000529 TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
530 .matchEthType(Ethernet.TYPE_IPV4)
531 .matchInPort(cp.port())
532 .matchIPProtocol(IPv4.PROTOCOL_UDP)
533 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
534 packetService.requestPackets(selectorServer.build(),
Saurav Dasb14f08a2019-02-22 16:34:15 -0800535 priority.isPresent() ? priority.get() : PacketPriority.CONTROL,
536 appId, Optional.of(cp.deviceId()));
Amit Ghosh83c8c892017-11-09 11:08:27 +0000537 }
538
539 /**
Saurav Dasb14f08a2019-02-22 16:34:15 -0800540 * Cancel DHCP packet from particular connect point via PacketService. If
541 * the request was made with a specific packet priority, then the same
542 * priority should be used in this call.
543 *
544 * @param cp the connect point for the trap flow
545 * @param priority with which the trap flow was requested; if request
546 * priority was not specified, this param should also be null
Amit Ghosh83c8c892017-11-09 11:08:27 +0000547 */
Saurav Dasb14f08a2019-02-22 16:34:15 -0800548 private void cancelDhcpPacketsFromConnectPoint(ConnectPoint cp,
549 Optional<PacketPriority> priority) {
Amit Ghosh83c8c892017-11-09 11:08:27 +0000550 TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
551 .matchEthType(Ethernet.TYPE_IPV4)
552 .matchInPort(cp.port())
553 .matchIPProtocol(IPv4.PROTOCOL_UDP)
554 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
555 packetService.cancelPackets(selectorServer.build(),
Saurav Dasb14f08a2019-02-22 16:34:15 -0800556 priority.isPresent() ? priority.get() : PacketPriority.CONTROL,
557 appId, Optional.of(cp.deviceId()));
Amit Ghosh83c8c892017-11-09 11:08:27 +0000558 }
559
Amit Ghosha17354e2017-08-23 12:56:04 +0100560 public static Map<String, DhcpAllocationInfo> allocationMap() {
561 return allocationMap;
562 }
563
Amit Ghosh47243cb2017-07-26 05:08:53 +0100564 private SubscriberAndDeviceInformation getDevice(PacketContext context) {
565 String serialNo = deviceService.getDevice(context.inPacket().
566 receivedFrom().deviceId()).serialNumber();
567
568 return subsService.get(serialNo);
569 }
570
Amit Ghosh47243cb2017-07-26 05:08:53 +0100571 private MacAddress relayAgentMacAddress(PacketContext context) {
572
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000573 SubscriberAndDeviceInformation device = this.getDevice(context);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100574 if (device == null) {
575 log.warn("Device not found for {}", context.inPacket().
576 receivedFrom());
577 return null;
578 }
579
580 return device.hardwareIdentifier();
581 }
582
583 private String nasPortId(PacketContext context) {
Amit Ghosh8951f042017-08-10 13:48:10 +0100584 return nasPortId(context.inPacket().receivedFrom());
585 }
586
587 private String nasPortId(ConnectPoint cp) {
588 Port p = deviceService.getPort(cp);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100589 return p.annotations().value(AnnotationKeys.PORT_NAME);
590 }
591
592 private SubscriberAndDeviceInformation getSubscriber(PacketContext context) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100593 return subsService.get(nasPortId(context));
594 }
595
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000596 private UniTagInformation getUnitagInformationFromPacketContext(PacketContext context,
597 SubscriberAndDeviceInformation sub) {
598 //If the ctag is defined in the tagList and dhcp is required, return the service info
599 List<UniTagInformation> tagList = sub.uniTagList();
600 for (UniTagInformation uniServiceInformation : tagList) {
601 if (uniServiceInformation.getPonCTag().toShort() == context.inPacket().parsed().getVlanID()) {
602 if (uniServiceInformation.getIsDhcpRequired()) {
603 return uniServiceInformation;
604 }
605 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100606 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100607
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000608 return null;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100609 }
610
611 private class DhcpRelayPacketProcessor implements PacketProcessor {
612
613 @Override
614 public void process(PacketContext context) {
615 if (!configured()) {
616 log.warn("Missing DHCP relay config. Abort packet processing");
617 return;
618 }
619
620 // process the packet and get the payload
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530621 Ethernet packet = context.inPacket().parsed();
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000622
Amit Ghosh47243cb2017-07-26 05:08:53 +0100623 if (packet == null) {
624 log.warn("Packet is null");
625 return;
626 }
627
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530628 if (packet.getEtherType() == Ethernet.TYPE_IPV4) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100629 IPv4 ipv4Packet = (IPv4) packet.getPayload();
630
631 if (ipv4Packet.getProtocol() == IPv4.PROTOCOL_UDP) {
632 UDP udpPacket = (UDP) ipv4Packet.getPayload();
633 if (udpPacket.getSourcePort() == UDP.DHCP_CLIENT_PORT ||
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000634 udpPacket.getSourcePort() == UDP.DHCP_SERVER_PORT) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100635 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
636 //This packet is dhcp.
637 processDhcpPacket(context, packet, dhcpPayload);
638 }
639 }
640 }
641 }
642
643 //forward the packet to ConnectPoint where the DHCP server is attached.
Amit Ghosh83c8c892017-11-09 11:08:27 +0000644 private void forwardPacket(Ethernet packet, PacketContext context) {
645 ConnectPoint toSendTo = null;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100646
Amit Ghosh83c8c892017-11-09 11:08:27 +0000647 if (!useOltUplink) {
648 toSendTo = dhcpServerConnectPoint.get();
649 } else {
650 toSendTo = getUplinkConnectPointOfOlt(context.inPacket().
651 receivedFrom().deviceId());
652 }
653
654 if (toSendTo != null) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100655 TrafficTreatment t = DefaultTrafficTreatment.builder()
Amit Ghosh83c8c892017-11-09 11:08:27 +0000656 .setOutput(toSendTo.port()).build();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100657 OutboundPacket o = new DefaultOutboundPacket(
Amit Ghosh83c8c892017-11-09 11:08:27 +0000658 toSendTo.deviceId(), t,
Amit Ghosh47243cb2017-07-26 05:08:53 +0100659 ByteBuffer.wrap(packet.serialize()));
660 if (log.isTraceEnabled()) {
Saurav Das15626a02018-09-27 18:36:45 -0700661 log.trace("Relaying packet to dhcp server at {} {}",
662 toSendTo, packet);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100663 }
664 packetService.emit(o);
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300665
666 SubscriberAndDeviceInformation entry = getSubscriberInfoFromClient(context);
667 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounters.valueOf("PACKETS_TO_SERVER"));
Amit Ghosh47243cb2017-07-26 05:08:53 +0100668 } else {
Amit Ghosh83c8c892017-11-09 11:08:27 +0000669 log.error("No connect point to send msg to DHCP Server");
Amit Ghosh47243cb2017-07-26 05:08:53 +0100670 }
671 }
672
Amit Ghosha17354e2017-08-23 12:56:04 +0100673 // get the type of the DHCP packet
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700674 private DHCP.MsgType getDhcpPacketType(DHCP dhcpPayload) {
Amit Ghosha17354e2017-08-23 12:56:04 +0100675
Jonathan Hartedbf6422018-05-02 17:30:05 -0700676 for (DhcpOption option : dhcpPayload.getOptions()) {
Amit Ghosha17354e2017-08-23 12:56:04 +0100677 if (option.getCode() == OptionCode_MessageType.getValue()) {
678 byte[] data = option.getData();
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700679 return DHCP.MsgType.getType(data[0]);
Amit Ghosha17354e2017-08-23 12:56:04 +0100680 }
681 }
682 return null;
683 }
684
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300685 private void updateDhcpRelayCountersStore(SubscriberAndDeviceInformation entry,
686 DhcpL2RelayCounters counterType) {
687 // Update global counter stats
688 dhcpL2RelayCounters.incrementCounter(DhcpL2RelayEvent.GLOBAL_COUNTER, counterType);
689 if (entry == null) {
690 log.warn("Counter not updated as subscriber info not found.");
691 } else {
692 // Update subscriber counter stats
693 dhcpL2RelayCounters.incrementCounter(entry.id(), counterType);
694 }
695 }
696
697 /*
698 * Get subscriber information based on it's context packet.
699 */
700 private SubscriberAndDeviceInformation getSubscriberInfoFromClient(PacketContext context) {
701 if (context != null) {
702 return getSubscriber(context);
703 }
704 return null;
705 }
706
707 /*
708 * Get subscriber information based on it's DHCP payload.
709 */
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000710 private SubscriberAndDeviceInformation getSubscriberInfoFromServer(DHCP dhcpPayload, PacketContext context) {
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300711 if (dhcpPayload != null) {
712 MacAddress descMac = valueOf(dhcpPayload.getClientHardwareAddress());
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000713 ConnectPoint subsCp = getConnectPointOfClient(descMac, context);
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300714
715 if (subsCp != null) {
716 String portId = nasPortId(subsCp);
717 return subsService.get(portId);
718 }
719 }
720 return null;
721 }
722
Amit Ghosh47243cb2017-07-26 05:08:53 +0100723 //process the dhcp packet before sending to server
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530724 private void processDhcpPacket(PacketContext context, Ethernet packet,
Amit Ghosh47243cb2017-07-26 05:08:53 +0100725 DHCP dhcpPayload) {
726 if (dhcpPayload == null) {
727 log.warn("DHCP payload is null");
728 return;
729 }
730
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700731 DHCP.MsgType incomingPacketType = getDhcpPacketType(dhcpPayload);
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300732 if (incomingPacketType == null) {
733 log.warn("DHCP packet type not found. Dump of ethernet pkt in hex format for troubleshooting.");
734 byte[] array = packet.serialize();
735 ByteArrayOutputStream buf = new ByteArrayOutputStream();
736 try {
737 HexDump.dump(array, 0, buf, 0);
738 log.trace(buf.toString());
739 } catch (Exception e) { }
740 return;
741 }
742
743 SubscriberAndDeviceInformation entry = null;
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000744
Saurav Das15626a02018-09-27 18:36:45 -0700745 log.info("Received DHCP Packet of type {} from {}",
746 incomingPacketType, context.inPacket().receivedFrom());
Amit Ghosh47243cb2017-07-26 05:08:53 +0100747
748 switch (incomingPacketType) {
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000749 case DHCPDISCOVER:
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530750 Ethernet ethernetPacketDiscover =
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000751 processDhcpPacketFromClient(context, packet);
752 if (ethernetPacketDiscover != null) {
Amit Ghosh83c8c892017-11-09 11:08:27 +0000753 forwardPacket(ethernetPacketDiscover, context);
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000754 }
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300755 entry = getSubscriberInfoFromClient(context);
756 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounters.valueOf("DHCPDISCOVER"));
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000757 break;
758 case DHCPOFFER:
759 //reply to dhcp client.
Saurav Das15626a02018-09-27 18:36:45 -0700760 Ethernet ethernetPacketOffer =
761 processDhcpPacketFromServer(context, packet);
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000762 if (ethernetPacketOffer != null) {
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000763 sendReply(ethernetPacketOffer, dhcpPayload, context);
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000764 }
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000765 entry = getSubscriberInfoFromServer(dhcpPayload, context);
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300766 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounters.valueOf("DHCPOFFER"));
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000767 break;
768 case DHCPREQUEST:
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530769 Ethernet ethernetPacketRequest =
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000770 processDhcpPacketFromClient(context, packet);
771 if (ethernetPacketRequest != null) {
Amit Ghosh83c8c892017-11-09 11:08:27 +0000772 forwardPacket(ethernetPacketRequest, context);
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000773 }
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300774 entry = getSubscriberInfoFromClient(context);
775 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounters.valueOf("DHCPREQUEST"));
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000776 break;
777 case DHCPACK:
778 //reply to dhcp client.
Saurav Das15626a02018-09-27 18:36:45 -0700779 Ethernet ethernetPacketAck =
780 processDhcpPacketFromServer(context, packet);
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000781 if (ethernetPacketAck != null) {
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000782 sendReply(ethernetPacketAck, dhcpPayload, context);
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000783 }
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000784 entry = getSubscriberInfoFromServer(dhcpPayload, context);
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300785 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounters.valueOf("DHCPACK"));
786 break;
787 case DHCPDECLINE:
788 entry = getSubscriberInfoFromClient(context);
789 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounters.valueOf("DHCPDECLINE"));
790 break;
791 case DHCPNAK:
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000792 entry = getSubscriberInfoFromServer(dhcpPayload, context);
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300793 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounters.valueOf("DHCPNACK"));
794 break;
795 case DHCPRELEASE:
796 entry = getSubscriberInfoFromClient(context);
797 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounters.valueOf("DHCPRELEASE"));
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000798 break;
799 default:
800 break;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100801 }
802 }
803
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530804 private Ethernet processDhcpPacketFromClient(PacketContext context,
805 Ethernet ethernetPacket) {
Saurav Das15626a02018-09-27 18:36:45 -0700806 if (log.isTraceEnabled()) {
807 log.trace("DHCP packet received from client at {} {}",
808 context.inPacket().receivedFrom(), ethernetPacket);
809 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100810
811 MacAddress relayAgentMac = relayAgentMacAddress(context);
812 if (relayAgentMac == null) {
813 log.warn("RelayAgent MAC not found ");
Amit Ghosh47243cb2017-07-26 05:08:53 +0100814 return null;
815 }
816
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530817 Ethernet etherReply = ethernetPacket;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100818
819 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
820 UDP udpPacket = (UDP) ipv4Packet.getPayload();
821 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
822
Amit Ghosha17354e2017-08-23 12:56:04 +0100823 if (enableDhcpBroadcastReplies) {
824 // We want the reply to come back as a L2 broadcast
825 dhcpPacket.setFlags((short) 0x8000);
826 }
827
Jonathan Hartc36c9552018-07-31 15:07:53 -0400828 MacAddress clientMac = MacAddress.valueOf(dhcpPacket.getClientHardwareAddress());
829 IpAddress clientIp = IpAddress.valueOf(dhcpPacket.getClientIPAddress());
Amit Ghosha17354e2017-08-23 12:56:04 +0100830
Jonathan Hartc36c9552018-07-31 15:07:53 -0400831 SubscriberAndDeviceInformation entry = getSubscriber(context);
832 if (entry == null) {
Saurav Das15626a02018-09-27 18:36:45 -0700833 log.warn("Dropping packet as subscriber entry is not available");
Jonathan Hartc36c9552018-07-31 15:07:53 -0400834 return null;
835 }
836
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000837 UniTagInformation uniTagInformation = getUnitagInformationFromPacketContext(context, entry);
838 if (uniTagInformation == null) {
839 log.warn("Missing service information for connectPoint {} / cTag {}",
840 context.inPacket().receivedFrom(), context.inPacket().parsed().getVlanID());
841 return null;
842 }
843
844
Jonathan Hartc36c9552018-07-31 15:07:53 -0400845 DhcpAllocationInfo info = new DhcpAllocationInfo(
846 context.inPacket().receivedFrom(), dhcpPacket.getPacketType(),
847 entry.nasPortId(), clientMac, clientIp);
848
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300849 allocationMap.put(entry.id(), info);
Jonathan Hartc36c9552018-07-31 15:07:53 -0400850
Saurav Das15626a02018-09-27 18:36:45 -0700851 post(new DhcpL2RelayEvent(DhcpL2RelayEvent.Type.UPDATED, info,
852 context.inPacket().receivedFrom()));
Amit Ghosha17354e2017-08-23 12:56:04 +0100853
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000854 if (option82) {
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000855 DHCP dhcpPacketWithOption82 = addOption82(dhcpPacket, entry);
856 udpPacket.setPayload(dhcpPacketWithOption82);
857 }
858
859 ipv4Packet.setPayload(udpPacket);
860 etherReply.setPayload(ipv4Packet);
Amit Ghosh83c8c892017-11-09 11:08:27 +0000861 if (modifyClientPktsSrcDstMac) {
862 etherReply.setSourceMACAddress(relayAgentMac);
863 etherReply.setDestinationMACAddress(dhcpConnectMac);
864 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100865
Amit Ghosh8951f042017-08-10 13:48:10 +0100866 etherReply.setPriorityCode(ethernetPacket.getPriorityCode());
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000867 etherReply.setVlanID(uniTagInformation.getPonCTag().toShort());
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530868 etherReply.setQinQTPID(Ethernet.TYPE_VLAN);
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000869 etherReply.setQinQVID(uniTagInformation.getPonSTag().toShort());
870 if (uniTagInformation.getUsPonSTagPriority() != -1) {
871 etherReply.setQinQPriorityCode((byte) uniTagInformation.getUsPonSTagPriority());
872 }
873 log.info("Finished processing packet.. relaying to dhcpServer {}");
Amit Ghosh47243cb2017-07-26 05:08:53 +0100874 return etherReply;
875 }
876
877 //build the DHCP offer/ack with proper client port.
Saurav Das15626a02018-09-27 18:36:45 -0700878 private Ethernet processDhcpPacketFromServer(PacketContext context,
879 Ethernet ethernetPacket) {
880 if (log.isTraceEnabled()) {
881 log.trace("DHCP packet received from server at {} {}",
882 context.inPacket().receivedFrom(), ethernetPacket);
883 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100884 // get dhcp header.
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530885 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100886 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
887 UDP udpPacket = (UDP) ipv4Packet.getPayload();
888 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
889
Amit Ghosh47243cb2017-07-26 05:08:53 +0100890 MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000891 ConnectPoint subsCp = getConnectPointOfClient(dstMac, context);
Amit Ghosh2095dc62017-09-25 20:56:55 +0100892 // If we can't find the subscriber, can't process further
893 if (subsCp == null) {
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700894 log.warn("Couldn't find connection point for mac address {} DHCPOFFERs won't be delivered", dstMac);
Amit Ghosh2095dc62017-09-25 20:56:55 +0100895 return null;
896 }
Amit Ghosha17354e2017-08-23 12:56:04 +0100897 // if it's an ACK packet store the information for display purpose
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700898 if (getDhcpPacketType(dhcpPayload) == DHCP.MsgType.DHCPACK) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100899
Amit Ghosha17354e2017-08-23 12:56:04 +0100900 String portId = nasPortId(subsCp);
901 SubscriberAndDeviceInformation sub = subsService.get(portId);
902 if (sub != null) {
Jonathan Hartedbf6422018-05-02 17:30:05 -0700903 List<DhcpOption> options = dhcpPayload.getOptions();
904 List<DhcpOption> circuitIds = options.stream()
Amit Ghosha17354e2017-08-23 12:56:04 +0100905 .filter(option -> option.getCode() == DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue())
906 .collect(Collectors.toList());
907
908 String circuitId = "None";
909 if (circuitIds.size() == 1) {
Amit Ghosh2095dc62017-09-25 20:56:55 +0100910 byte[] array = circuitIds.get(0).getData();
911
912 try {
913 // we leave the first two bytes as they are the id and length
914 circuitId = new String(Arrays.copyOfRange(array, 2, array.length), "UTF-8");
915 } catch (Exception e) { }
Amit Ghosha17354e2017-08-23 12:56:04 +0100916 }
917
918 IpAddress ip = IpAddress.valueOf(dhcpPayload.getYourIPAddress());
919
920 //storeDHCPAllocationInfo
Jonathan Hartc36c9552018-07-31 15:07:53 -0400921 DhcpAllocationInfo info = new DhcpAllocationInfo(subsCp,
922 dhcpPayload.getPacketType(), circuitId, dstMac, ip);
Amit Ghosha17354e2017-08-23 12:56:04 +0100923
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300924 allocationMap.put(sub.id(), info);
Jonathan Hartc36c9552018-07-31 15:07:53 -0400925
926 post(new DhcpL2RelayEvent(DhcpL2RelayEvent.Type.UPDATED, info, subsCp));
Amit Ghosha17354e2017-08-23 12:56:04 +0100927 }
928 } // end storing of info
Amit Ghosh47243cb2017-07-26 05:08:53 +0100929
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000930 SubscriberAndDeviceInformation entry = getSubscriberInfoFromServer(dhcpPayload, context);
931
932 UniTagInformation uniTagInformation = getUnitagInformationFromPacketContext(context, entry);
933 if (uniTagInformation == null) {
934 log.warn("Missing service information for connectPoint {} / cTag {}",
935 context.inPacket().receivedFrom(), context.inPacket().parsed().getVlanID());
936 return null;
937 }
938
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300939 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounters.valueOf("PACKETS_FROM_SERVER"));
940
Amit Ghosh47243cb2017-07-26 05:08:53 +0100941 // we leave the srcMac from the original packet
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000942 etherReply.setQinQVID(VlanId.NO_VID);
943 etherReply.setQinQPriorityCode((byte) 0);
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530944 etherReply.setDestinationMACAddress(dstMac);
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000945 etherReply.setVlanID(uniTagInformation.getPonCTag().toShort());
946 if (uniTagInformation.getUsPonCTagPriority() != -1) {
947 etherReply.setPriorityCode((byte) uniTagInformation.getUsPonCTagPriority());
948 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100949
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000950 if (option82) {
951 udpPacket.setPayload(removeOption82(dhcpPayload));
952 } else {
953 udpPacket.setPayload(dhcpPayload);
954 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100955 ipv4Packet.setPayload(udpPacket);
956 etherReply.setPayload(ipv4Packet);
957
Saurav Das15626a02018-09-27 18:36:45 -0700958 log.info("Finished processing packet.. relaying to client");
Amit Ghosh47243cb2017-07-26 05:08:53 +0100959 return etherReply;
960 }
961
Amit Ghosha17354e2017-08-23 12:56:04 +0100962 /*
963 * Get ConnectPoint of the Client based on it's MAC address
964 */
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000965 private ConnectPoint getConnectPointOfClient(MacAddress dstMac, PacketContext context) {
Amit Ghosha17354e2017-08-23 12:56:04 +0100966 Set<Host> hosts = hostService.getHostsByMac(dstMac);
967 if (hosts == null || hosts.isEmpty()) {
968 log.warn("Cannot determine host for DHCP client: {}. Aborting "
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530969 + "relay for dhcp packet from server",
Amit Ghosha17354e2017-08-23 12:56:04 +0100970 dstMac);
971 return null;
972 }
973 for (Host h : hosts) {
974 // if more than one,
975 // find the connect point which has an valid entry in SADIS
976 ConnectPoint cp = new ConnectPoint(h.location().deviceId(),
977 h.location().port());
978
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000979 String portId = nasPortId(cp);
980 SubscriberAndDeviceInformation sub = subsService.get(portId);
981 if (sub == null) {
982 log.warn("Subscriber info not found for {}", cp);
983 return null;
Amit Ghosha17354e2017-08-23 12:56:04 +0100984 }
Amit Ghosha17354e2017-08-23 12:56:04 +0100985
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000986 UniTagInformation uniTagInformation = getUnitagInformationFromPacketContext(context, sub);
987 if (uniTagInformation == null) {
988 log.warn("Missing service information for connectPoint {} / cTag {}",
989 context.inPacket().receivedFrom(), context.inPacket().parsed().getVlanID());
990 return null;
991 }
992 return cp;
993 }
Amit Ghosha17354e2017-08-23 12:56:04 +0100994 return null;
995 }
996
Amit Ghosh47243cb2017-07-26 05:08:53 +0100997 //send the response to the requester host.
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000998 private void sendReply(Ethernet ethPacket, DHCP dhcpPayload, PacketContext context) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100999 MacAddress descMac = valueOf(dhcpPayload.getClientHardwareAddress());
Gamze Abakaa64b3bc2020-01-31 06:51:43 +00001000 ConnectPoint subCp = getConnectPointOfClient(descMac, context);
Amit Ghosh47243cb2017-07-26 05:08:53 +01001001
1002 // Send packet out to requester if the host information is available
Amit Ghosha17354e2017-08-23 12:56:04 +01001003 if (subCp != null) {
Saurav Das15626a02018-09-27 18:36:45 -07001004 log.info("Sending DHCP packet to client at {}", subCp);
Amit Ghosh47243cb2017-07-26 05:08:53 +01001005 TrafficTreatment t = DefaultTrafficTreatment.builder()
Amit Ghosha17354e2017-08-23 12:56:04 +01001006 .setOutput(subCp.port()).build();
Amit Ghosh47243cb2017-07-26 05:08:53 +01001007 OutboundPacket o = new DefaultOutboundPacket(
Amit Ghosha17354e2017-08-23 12:56:04 +01001008 subCp.deviceId(), t, ByteBuffer.wrap(ethPacket.serialize()));
Amit Ghosh47243cb2017-07-26 05:08:53 +01001009 if (log.isTraceEnabled()) {
Saurav Das15626a02018-09-27 18:36:45 -07001010 log.trace("Relaying packet to dhcp client at {} {}", subCp,
1011 ethPacket);
Amit Ghosh47243cb2017-07-26 05:08:53 +01001012 }
1013 packetService.emit(o);
Amit Ghosh47243cb2017-07-26 05:08:53 +01001014 } else {
Deepa vaddireddy0060f532017-08-04 06:46:05 +00001015 log.error("Dropping DHCP packet because can't find host for {}", descMac);
Amit Ghosh47243cb2017-07-26 05:08:53 +01001016 }
1017 }
1018 }
1019
Deepa vaddireddy0060f532017-08-04 06:46:05 +00001020 private DHCP addOption82(DHCP dhcpPacket, SubscriberAndDeviceInformation entry) {
1021 log.debug("option82data {} ", entry);
1022
Jonathan Hartedbf6422018-05-02 17:30:05 -07001023 List<DhcpOption> options = Lists.newArrayList(dhcpPacket.getOptions());
Deepa vaddireddy0060f532017-08-04 06:46:05 +00001024 DhcpOption82 option82 = new DhcpOption82();
1025 option82.setAgentCircuitId(entry.circuitId());
1026 option82.setAgentRemoteId(entry.remoteId());
Jonathan Hartedbf6422018-05-02 17:30:05 -07001027 DhcpOption option = new DhcpOption()
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +05301028 .setCode(DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue())
1029 .setData(option82.toByteArray())
1030 .setLength(option82.length());
Deepa vaddireddy0060f532017-08-04 06:46:05 +00001031
1032 options.add(options.size() - 1, option);
1033 dhcpPacket.setOptions(options);
Amit Ghosh8951f042017-08-10 13:48:10 +01001034
Deepa vaddireddy0060f532017-08-04 06:46:05 +00001035 return dhcpPacket;
1036
1037 }
1038
1039 private DHCP removeOption82(DHCP dhcpPacket) {
Jonathan Hartedbf6422018-05-02 17:30:05 -07001040 List<DhcpOption> options = dhcpPacket.getOptions();
1041 List<DhcpOption> newoptions = options.stream()
Deepa vaddireddy0060f532017-08-04 06:46:05 +00001042 .filter(option -> option.getCode() != DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue())
1043 .collect(Collectors.toList());
1044
1045 return dhcpPacket.setOptions(newoptions);
1046 }
Amit Ghosh47243cb2017-07-26 05:08:53 +01001047 /**
1048 * Listener for network config events.
1049 */
1050 private class InternalConfigListener implements NetworkConfigListener {
1051
1052 @Override
1053 public void event(NetworkConfigEvent event) {
1054
1055 if ((event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
1056 event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED) &&
1057 event.configClass().equals(DhcpL2RelayConfig.class)) {
1058 updateConfig();
1059 log.info("Reconfigured");
1060 }
1061 }
1062 }
Deepa vaddireddy0060f532017-08-04 06:46:05 +00001063
Amit Ghosh8951f042017-08-10 13:48:10 +01001064 /**
1065 * Handles Mastership changes for the devices which connect
1066 * to the DHCP server.
1067 */
1068 private class InnerMastershipListener implements MastershipListener {
1069 @Override
1070 public void event(MastershipEvent event) {
Amit Ghosh83c8c892017-11-09 11:08:27 +00001071 if (!useOltUplink) {
1072 if (dhcpServerConnectPoint.get() != null &&
1073 dhcpServerConnectPoint.get().deviceId().
1074 equals(event.subject())) {
1075 log.trace("Mastership Event recevived for {}", event.subject());
1076 // mastership of the device for our connect point has changed
1077 // reselect
1078 selectServerConnectPoint();
1079 }
Amit Ghosh8951f042017-08-10 13:48:10 +01001080 }
1081 }
1082 }
Deepa vaddireddy0060f532017-08-04 06:46:05 +00001083
Amit Ghosh8951f042017-08-10 13:48:10 +01001084 /**
1085 * Handles Device status change for the devices which connect
1086 * to the DHCP server.
1087 */
1088 private class InnerDeviceListener implements DeviceListener {
1089 @Override
1090 public void event(DeviceEvent event) {
Thomas Lee S9df15082019-12-23 11:31:15 +05301091 switch (event.type()) {
1092 case DEVICE_AVAILABILITY_CHANGED:
1093 log.info("Device Avail Changed {}", event.subject().id());
1094 DeviceId deviceId = event.subject().id();
1095 if (!deviceService.isAvailable(deviceId)) {
1096 log.warn("Device {} is not available ", deviceId);
1097 allocationMap.entrySet().removeIf(entry -> deviceId.equals(entry.getValue().
1098 location().deviceId()));
1099 log.info("Device {} is removed from DHCP allocationmap ", deviceId);
1100 }
1101 break;
Thomas Lee S6b77ad22020-01-10 11:27:43 +05301102 case PORT_REMOVED:
1103 Port port = event.port();
1104 deviceId = event.subject().id();
1105 log.info("Port {} is deleted on device {}", port, deviceId);
1106 allocationMap.entrySet().removeIf(entry -> port.number().equals(entry.getValue().
1107 location().port()) && deviceId.equals(entry.getValue().location().deviceId()));
1108 log.info("Port {} on device {} is removed from DHCP allocationmap", event.port(), deviceId);
1109 break;
Thomas Lee S9df15082019-12-23 11:31:15 +05301110 default:
1111 break;
1112 }
Saurav Das15626a02018-09-27 18:36:45 -07001113 if (log.isTraceEnabled() &&
1114 !event.type().equals(DeviceEvent.Type.PORT_STATS_UPDATED)) {
1115 log.trace("Device Event received for {} event {}",
1116 event.subject(), event.type());
1117 }
Amit Ghosh83c8c892017-11-09 11:08:27 +00001118 if (!useOltUplink) {
1119 if (dhcpServerConnectPoint.get() == null) {
1120 switch (event.type()) {
1121 case DEVICE_ADDED:
1122 case DEVICE_AVAILABILITY_CHANGED:
Saurav Dasb14f08a2019-02-22 16:34:15 -08001123 // some device is available check if we can get a
1124 // connect point we can use
1125 addOrRemoveDhcpTrapFromServer(true);
Amit Ghosh83c8c892017-11-09 11:08:27 +00001126 break;
1127 default:
1128 break;
1129 }
1130 return;
Amit Ghosh8951f042017-08-10 13:48:10 +01001131 }
Amit Ghosh83c8c892017-11-09 11:08:27 +00001132 if (dhcpServerConnectPoint.get().deviceId().
1133 equals(event.subject().id())) {
1134 switch (event.type()) {
1135 case DEVICE_AVAILABILITY_CHANGED:
1136 case DEVICE_REMOVED:
1137 case DEVICE_SUSPENDED:
1138 // state of our device has changed, check if we need
Saurav Dasb14f08a2019-02-22 16:34:15 -08001139 // to re-select a connectpoint
1140 addOrRemoveDhcpTrapFromServer(true);
Amit Ghosh83c8c892017-11-09 11:08:27 +00001141 break;
1142 default:
1143 break;
1144 }
1145 }
1146 } else {
Amit Ghosh8951f042017-08-10 13:48:10 +01001147 switch (event.type()) {
Amit Ghosh83c8c892017-11-09 11:08:27 +00001148 case PORT_ADDED:
Saurav Dasb4e3e102018-10-02 15:31:17 -07001149 if (useOltUplink && isUplinkPortOfOlt(event.subject().id(), event.port())) {
Saurav Dasb14f08a2019-02-22 16:34:15 -08001150 requestDhcpPacketsFromConnectPoint(
1151 new ConnectPoint(event.subject().id(), event.port().number()),
1152 null);
Amit Ghosh83c8c892017-11-09 11:08:27 +00001153 }
Amit Ghosh8951f042017-08-10 13:48:10 +01001154 break;
1155 default:
1156 break;
1157 }
1158 }
1159 }
1160 }
Amit Ghosh47243cb2017-07-26 05:08:53 +01001161}