blob: 7f523b993833dab14f98a85fb8a314055ed1e08c [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
Saurav Das45861d42020-10-07 00:03:23 -070018import static java.util.concurrent.Executors.newFixedThreadPool;
19import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
20import static org.onlab.packet.MacAddress.valueOf;
21import static org.onlab.util.Tools.groupedThreads;
22import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
23import static org.opencord.dhcpl2relay.impl.OsgiPropertyConstants.ENABLE_DHCP_BROADCAST_REPLIES;
24import static org.opencord.dhcpl2relay.impl.OsgiPropertyConstants.ENABLE_DHCP_BROADCAST_REPLIES_DEFAULT;
25import static org.opencord.dhcpl2relay.impl.OsgiPropertyConstants.OPTION_82;
26import static org.opencord.dhcpl2relay.impl.OsgiPropertyConstants.OPTION_82_DEFAULT;
27import static org.opencord.dhcpl2relay.impl.OsgiPropertyConstants.PACKET_PROCESSOR_THREADS;
28import static org.opencord.dhcpl2relay.impl.OsgiPropertyConstants.PACKET_PROCESSOR_THREADS_DEFAULT;
29
30import java.io.ByteArrayOutputStream;
31import java.nio.ByteBuffer;
32import java.time.Instant;
33import java.util.ArrayList;
34import java.util.Dictionary;
35import java.util.List;
36import java.util.Map;
37import java.util.Optional;
38import java.util.Set;
39import java.util.UUID;
40import java.util.concurrent.ExecutorService;
41import java.util.concurrent.Executors;
42import java.util.concurrent.ScheduledExecutorService;
43import java.util.concurrent.ScheduledFuture;
44import java.util.concurrent.atomic.AtomicReference;
45import java.util.function.Predicate;
46import java.util.stream.Collectors;
47
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -030048import org.apache.commons.io.HexDump;
Amit Ghosh47243cb2017-07-26 05:08:53 +010049import org.onlab.packet.DHCP;
Deepa vaddireddy0060f532017-08-04 06:46:05 +000050import org.onlab.packet.Ethernet;
Amit Ghosh47243cb2017-07-26 05:08:53 +010051import org.onlab.packet.IPv4;
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +053052import org.onlab.packet.IpAddress;
Deepa vaddireddy0060f532017-08-04 06:46:05 +000053import org.onlab.packet.MacAddress;
Amit Ghosh47243cb2017-07-26 05:08:53 +010054import org.onlab.packet.TpPort;
55import org.onlab.packet.UDP;
56import org.onlab.packet.VlanId;
Jonathan Hartedbf6422018-05-02 17:30:05 -070057import org.onlab.packet.dhcp.DhcpOption;
Saurav Das45861d42020-10-07 00:03:23 -070058import org.onlab.packet.dhcp.DhcpRelayAgentOption;
Jonathan Hart617bc3e2020-02-14 10:42:23 -080059import org.onlab.util.KryoNamespace;
Amit Ghosh47243cb2017-07-26 05:08:53 +010060import org.onlab.util.Tools;
61import org.onosproject.cfg.ComponentConfigService;
Jonathan Hart617bc3e2020-02-14 10:42:23 -080062import org.onosproject.cluster.ClusterService;
63import org.onosproject.cluster.LeadershipService;
Andrea Campanella6f45a1b2020-05-08 17:50:12 +020064import org.onosproject.cluster.NodeId;
Amit Ghosh47243cb2017-07-26 05:08:53 +010065import org.onosproject.core.ApplicationId;
66import org.onosproject.core.CoreService;
Jonathan Hartc36c9552018-07-31 15:07:53 -040067import org.onosproject.event.AbstractListenerManager;
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +053068import org.onosproject.mastership.MastershipEvent;
69import org.onosproject.mastership.MastershipListener;
70import org.onosproject.mastership.MastershipService;
Amit Ghosh47243cb2017-07-26 05:08:53 +010071import org.onosproject.net.AnnotationKeys;
72import org.onosproject.net.ConnectPoint;
Amit Ghosh83c8c892017-11-09 11:08:27 +000073import org.onosproject.net.Device;
74import org.onosproject.net.DeviceId;
Amit Ghosh47243cb2017-07-26 05:08:53 +010075import org.onosproject.net.Host;
76import org.onosproject.net.Port;
Amit Ghosh83c8c892017-11-09 11:08:27 +000077import org.onosproject.net.PortNumber;
Amit Ghosh47243cb2017-07-26 05:08:53 +010078import org.onosproject.net.config.ConfigFactory;
79import org.onosproject.net.config.NetworkConfigEvent;
80import org.onosproject.net.config.NetworkConfigListener;
81import org.onosproject.net.config.NetworkConfigRegistry;
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +053082import org.onosproject.net.device.DeviceEvent;
83import org.onosproject.net.device.DeviceListener;
Amit Ghosh47243cb2017-07-26 05:08:53 +010084import org.onosproject.net.device.DeviceService;
85import org.onosproject.net.flow.DefaultTrafficSelector;
86import org.onosproject.net.flow.DefaultTrafficTreatment;
87import org.onosproject.net.flow.TrafficSelector;
88import org.onosproject.net.flow.TrafficTreatment;
Saurav Dasb4e3e102018-10-02 15:31:17 -070089import org.onosproject.net.flowobjective.FlowObjectiveService;
Amit Ghosh47243cb2017-07-26 05:08:53 +010090import org.onosproject.net.host.HostService;
91import org.onosproject.net.packet.DefaultOutboundPacket;
92import org.onosproject.net.packet.OutboundPacket;
93import org.onosproject.net.packet.PacketContext;
94import org.onosproject.net.packet.PacketPriority;
95import org.onosproject.net.packet.PacketProcessor;
96import org.onosproject.net.packet.PacketService;
Jonathan Hart617bc3e2020-02-14 10:42:23 -080097import org.onosproject.store.serializers.KryoNamespaces;
98import org.onosproject.store.service.ConsistentMap;
99import org.onosproject.store.service.Serializer;
100import org.onosproject.store.service.StorageService;
101import org.onosproject.store.service.Versioned;
Matteo Scandolo57af5d12019-04-29 17:11:41 -0700102import org.opencord.dhcpl2relay.DhcpAllocationInfo;
103import org.opencord.dhcpl2relay.DhcpL2RelayEvent;
104import org.opencord.dhcpl2relay.DhcpL2RelayListener;
105import org.opencord.dhcpl2relay.DhcpL2RelayService;
Jonathan Hart77ca3152020-02-21 14:31:21 -0800106import org.opencord.dhcpl2relay.DhcpL2RelayStoreDelegate;
Saurav Das45861d42020-10-07 00:03:23 -0700107import org.opencord.dhcpl2relay.impl.packet.DhcpOption82Data;
Gamze Abakac806c6c2018-12-03 12:49:46 +0000108import org.opencord.sadis.BaseInformationService;
109import org.opencord.sadis.SadisService;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100110import org.opencord.sadis.SubscriberAndDeviceInformation;
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000111import org.opencord.sadis.UniTagInformation;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100112import org.osgi.service.component.ComponentContext;
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700113import org.osgi.service.component.annotations.Activate;
114import org.osgi.service.component.annotations.Component;
115import org.osgi.service.component.annotations.Deactivate;
116import org.osgi.service.component.annotations.Modified;
117import org.osgi.service.component.annotations.Reference;
118import org.osgi.service.component.annotations.ReferenceCardinality;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100119import org.slf4j.Logger;
120import org.slf4j.LoggerFactory;
121
Saurav Das45861d42020-10-07 00:03:23 -0700122import com.google.common.base.Strings;
123import com.google.common.collect.ImmutableMap;
124import com.google.common.collect.ImmutableSet;
125import com.google.common.collect.Lists;
126import com.google.common.collect.Sets;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100127
128/**
129 * DHCP Relay Agent Application Component.
130 */
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700131@Component(immediate = true,
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700132 property = {
133 OPTION_82 + ":Boolean=" + OPTION_82_DEFAULT,
134 ENABLE_DHCP_BROADCAST_REPLIES + ":Boolean=" + ENABLE_DHCP_BROADCAST_REPLIES_DEFAULT,
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800135 PACKET_PROCESSOR_THREADS + ":Integer=" + PACKET_PROCESSOR_THREADS_DEFAULT,
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700136 })
Jonathan Hartc36c9552018-07-31 15:07:53 -0400137public class DhcpL2Relay
138 extends AbstractListenerManager<DhcpL2RelayEvent, DhcpL2RelayListener>
139 implements DhcpL2RelayService {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100140
141 public static final String DHCP_L2RELAY_APP = "org.opencord.dhcpl2relay";
Saurav Dasb4e3e102018-10-02 15:31:17 -0700142 private static final String HOST_LOC_PROVIDER =
143 "org.onosproject.provider.host.impl.HostLocationProvider";
Amit Ghosh47243cb2017-07-26 05:08:53 +0100144 private final Logger log = LoggerFactory.getLogger(getClass());
145 private final InternalConfigListener cfgListener =
146 new InternalConfigListener();
147
148 private final Set<ConfigFactory> factories = ImmutableSet.of(
149 new ConfigFactory<ApplicationId, DhcpL2RelayConfig>(APP_SUBJECT_FACTORY,
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700150 DhcpL2RelayConfig.class,
151 "dhcpl2relay") {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100152 @Override
153 public DhcpL2RelayConfig createConfig() {
154 return new DhcpL2RelayConfig();
155 }
156 }
157 );
158
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700159 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh47243cb2017-07-26 05:08:53 +0100160 protected NetworkConfigRegistry cfgService;
161
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700162 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh47243cb2017-07-26 05:08:53 +0100163 protected CoreService coreService;
164
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700165 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh47243cb2017-07-26 05:08:53 +0100166 protected PacketService packetService;
167
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700168 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh47243cb2017-07-26 05:08:53 +0100169 protected HostService hostService;
170
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700171 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh47243cb2017-07-26 05:08:53 +0100172 protected ComponentConfigService componentConfigService;
173
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700174 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Gamze Abakac806c6c2018-12-03 12:49:46 +0000175 protected SadisService sadisService;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100176
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700177 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh47243cb2017-07-26 05:08:53 +0100178 protected DeviceService deviceService;
179
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700180 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh8951f042017-08-10 13:48:10 +0100181 protected MastershipService mastershipService;
182
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700183 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jonathan Hart617bc3e2020-02-14 10:42:23 -0800184 protected StorageService storageService;
185
186 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Saurav Dasb4e3e102018-10-02 15:31:17 -0700187 protected FlowObjectiveService flowObjectiveService;
188
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300189 @Reference(cardinality = ReferenceCardinality.MANDATORY)
190 protected DhcpL2RelayCountersStore dhcpL2RelayCounters;
191
Jonathan Hart617bc3e2020-02-14 10:42:23 -0800192 @Reference(cardinality = ReferenceCardinality.MANDATORY)
193 protected LeadershipService leadershipService;
194
195 @Reference(cardinality = ReferenceCardinality.MANDATORY)
196 protected ClusterService clusterService;
197
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300198 // OSGi Properties
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700199 /**
200 * Add option 82 to relayed packets.
201 */
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700202 protected boolean option82 = OPTION_82_DEFAULT;
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700203 /**
204 * Ask the DHCP Server to send back replies as L2 broadcast.
205 */
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700206 protected boolean enableDhcpBroadcastReplies = ENABLE_DHCP_BROADCAST_REPLIES_DEFAULT;
Amit Ghosha17354e2017-08-23 12:56:04 +0100207
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800208 /**
209 * Number of threads used to process the packet.
210 */
211 protected int packetProcessorThreads = PACKET_PROCESSOR_THREADS_DEFAULT;
212
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300213 ScheduledFuture<?> refreshTask;
214 ScheduledExecutorService refreshService = Executors.newSingleThreadScheduledExecutor();
215
Amit Ghosh47243cb2017-07-26 05:08:53 +0100216 private DhcpRelayPacketProcessor dhcpRelayPacketProcessor =
217 new DhcpRelayPacketProcessor();
218
Amit Ghosh8951f042017-08-10 13:48:10 +0100219 private InnerMastershipListener changeListener = new InnerMastershipListener();
220 private InnerDeviceListener deviceListener = new InnerDeviceListener();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100221
Amit Ghosh8951f042017-08-10 13:48:10 +0100222 // connect points to the DHCP server
223 Set<ConnectPoint> dhcpConnectPoints;
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300224 protected AtomicReference<ConnectPoint> dhcpServerConnectPoint = new AtomicReference<>();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100225 private MacAddress dhcpConnectMac = MacAddress.BROADCAST;
226 private ApplicationId appId;
227
Jonathan Hart617bc3e2020-02-14 10:42:23 -0800228 private ConsistentMap<String, DhcpAllocationInfo> allocations;
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300229 protected boolean modifyClientPktsSrcDstMac = false;
Amit Ghosh83c8c892017-11-09 11:08:27 +0000230 //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 -0300231 protected boolean useOltUplink = false;
Amit Ghosha17354e2017-08-23 12:56:04 +0100232
Gamze Abakac806c6c2018-12-03 12:49:46 +0000233 private BaseInformationService<SubscriberAndDeviceInformation> subsService;
234
Jonathan Hart77ca3152020-02-21 14:31:21 -0800235 private DhcpL2RelayStoreDelegate delegate = new InnerDhcpL2RelayStoreDelegate();
236
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800237 protected ExecutorService packetProcessorExecutor;
238
Amit Ghosh47243cb2017-07-26 05:08:53 +0100239 @Activate
240 protected void activate(ComponentContext context) {
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800241
Amit Ghosh47243cb2017-07-26 05:08:53 +0100242 //start the dhcp relay agent
243 appId = coreService.registerApplication(DHCP_L2RELAY_APP);
244 componentConfigService.registerProperties(getClass());
Jonathan Hartc36c9552018-07-31 15:07:53 -0400245 eventDispatcher.addSink(DhcpL2RelayEvent.class, listenerRegistry);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100246
Jonathan Hart617bc3e2020-02-14 10:42:23 -0800247 KryoNamespace serializer = KryoNamespace.newBuilder()
248 .register(KryoNamespaces.API)
249 .register(Instant.class)
250 .register(DHCP.MsgType.class)
251 .register(DhcpAllocationInfo.class)
252 .build();
253
254 allocations = storageService.<String, DhcpAllocationInfo>consistentMapBuilder()
255 .withName("dhcpl2relay-allocations")
256 .withSerializer(Serializer.using(serializer))
257 .withApplicationId(appId)
258 .build();
259
Jonathan Hart77ca3152020-02-21 14:31:21 -0800260 dhcpL2RelayCounters.setDelegate(delegate);
261
Amit Ghosh47243cb2017-07-26 05:08:53 +0100262 cfgService.addListener(cfgListener);
Amit Ghosh8951f042017-08-10 13:48:10 +0100263 mastershipService.addListener(changeListener);
264 deviceService.addListener(deviceListener);
265
Matteo Scandolo45e5a272019-09-30 09:30:32 -0700266 subsService = sadisService.getSubscriberInfoService();
267
Amit Ghosh47243cb2017-07-26 05:08:53 +0100268 factories.forEach(cfgService::registerConfigFactory);
269 //update the dhcp server configuration.
270 updateConfig();
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800271
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000272 if (context != null) {
273 modified(context);
274 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100275
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800276 //add the packet services.
277 packetService.addProcessor(dhcpRelayPacketProcessor,
278 PacketProcessor.director(0));
279
Amit Ghosh47243cb2017-07-26 05:08:53 +0100280 log.info("DHCP-L2-RELAY Started");
281 }
282
283 @Deactivate
284 protected void deactivate() {
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300285 if (refreshTask != null) {
286 refreshTask.cancel(true);
287 }
288 if (refreshService != null) {
289 refreshService.shutdownNow();
290 }
Jonathan Hart77ca3152020-02-21 14:31:21 -0800291 dhcpL2RelayCounters.unsetDelegate(delegate);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100292 cfgService.removeListener(cfgListener);
293 factories.forEach(cfgService::unregisterConfigFactory);
294 packetService.removeProcessor(dhcpRelayPacketProcessor);
Saurav Dasb4e3e102018-10-02 15:31:17 -0700295 cancelDhcpPktsFromServer();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100296
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800297 packetProcessorExecutor.shutdown();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100298 componentConfigService.unregisterProperties(getClass(), false);
Deepa Vaddireddy77a6ac72017-09-20 20:36:52 +0530299 deviceService.removeListener(deviceListener);
300 mastershipService.removeListener(changeListener);
Jonathan Hartc36c9552018-07-31 15:07:53 -0400301 eventDispatcher.removeSink(DhcpL2RelayEvent.class);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100302 log.info("DHCP-L2-RELAY Stopped");
303 }
304
305 @Modified
306 protected void modified(ComponentContext context) {
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000307
Amit Ghosh47243cb2017-07-26 05:08:53 +0100308 Dictionary<?, ?> properties = context.getProperties();
309
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700310 Boolean o = Tools.isPropertyEnabled(properties, OPTION_82);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100311 if (o != null) {
312 option82 = o;
313 }
Amit Ghosh2095dc62017-09-25 20:56:55 +0100314
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700315 o = Tools.isPropertyEnabled(properties, ENABLE_DHCP_BROADCAST_REPLIES);
Amit Ghosh2095dc62017-09-25 20:56:55 +0100316 if (o != null) {
317 enableDhcpBroadcastReplies = o;
318 }
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800319
320 String s = Tools.get(properties, PACKET_PROCESSOR_THREADS);
321 int oldpacketProcessorThreads = packetProcessorThreads;
322 packetProcessorThreads = Strings.isNullOrEmpty(s) ? oldpacketProcessorThreads
323 : Integer.parseInt(s.trim());
324 if (packetProcessorExecutor == null || oldpacketProcessorThreads != packetProcessorThreads) {
325 if (packetProcessorExecutor != null) {
326 packetProcessorExecutor.shutdown();
327 }
328 packetProcessorExecutor = newFixedThreadPool(packetProcessorThreads,
329 groupedThreads("onos/dhcp",
330 "dhcp-packet-%d", log));
331 }
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300332 }
333
Jonathan Hart617bc3e2020-02-14 10:42:23 -0800334 @Override
335 public Map<String, DhcpAllocationInfo> getAllocationInfo() {
336 return ImmutableMap.copyOf(allocations.asJavaMap());
337 }
338
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300339 /**
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700340 * Generates a unique UUID from a string.
341 *
342 * @return true if all information we need have been initialized
343 */
344 private static String getUniqueUuidFromString(String value) {
345 return UUID.nameUUIDFromBytes(value.getBytes()).toString();
346 }
347
348 /**
Amit Ghosh47243cb2017-07-26 05:08:53 +0100349 * Checks if this app has been configured.
350 *
351 * @return true if all information we need have been initialized
352 */
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300353 protected boolean configured() {
Amit Ghosh83c8c892017-11-09 11:08:27 +0000354 if (!useOltUplink) {
355 return dhcpServerConnectPoint.get() != null;
356 }
357 return true;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100358 }
359
Amit Ghosh8951f042017-08-10 13:48:10 +0100360 /**
361 * Selects a connect point through an available device for which it is the master.
362 */
363 private void selectServerConnectPoint() {
364 synchronized (this) {
365 dhcpServerConnectPoint.set(null);
366 if (dhcpConnectPoints != null) {
367 // find a connect point through a device for which we are master
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700368 for (ConnectPoint cp : dhcpConnectPoints) {
Andrea Campanella6f45a1b2020-05-08 17:50:12 +0200369 if (isLocalLeader(cp.deviceId())) {
Amit Ghosh8951f042017-08-10 13:48:10 +0100370 if (deviceService.isAvailable(cp.deviceId())) {
371 dhcpServerConnectPoint.set(cp);
372 }
373 log.info("DHCP connectPoint selected is {}", cp);
374 break;
375 }
376 }
377 }
378
379 log.info("DHCP Server connectPoint is {}", dhcpServerConnectPoint.get());
380
381 if (dhcpServerConnectPoint.get() == null) {
382 log.error("Master of none, can't relay DHCP Message to server");
383 }
384 }
385 }
386
387 /**
388 * Updates the network configuration.
389 */
Amit Ghosh47243cb2017-07-26 05:08:53 +0100390 private void updateConfig() {
391 DhcpL2RelayConfig cfg = cfgService.getConfig(appId, DhcpL2RelayConfig.class);
392 if (cfg == null) {
393 log.warn("Dhcp Server info not available");
394 return;
395 }
Amit Ghosh8951f042017-08-10 13:48:10 +0100396
397 dhcpConnectPoints = Sets.newConcurrentHashSet(cfg.getDhcpServerConnectPoint());
Amit Ghosh83c8c892017-11-09 11:08:27 +0000398 modifyClientPktsSrcDstMac = cfg.getModifySrcDstMacAddresses();
Saurav Dasb4e3e102018-10-02 15:31:17 -0700399 boolean prevUseOltUplink = useOltUplink;
Amit Ghosh83c8c892017-11-09 11:08:27 +0000400 useOltUplink = cfg.getUseOltUplinkForServerPktInOut();
Amit Ghosh8951f042017-08-10 13:48:10 +0100401
Saurav Dasb4e3e102018-10-02 15:31:17 -0700402 if (useOltUplink) {
403 for (ConnectPoint cp : getUplinkPortsOfOlts()) {
404 log.debug("requestDhcpPackets: ConnectPoint: {}", cp);
Matteo Scandolo45e5a272019-09-30 09:30:32 -0700405 requestDhcpPacketsFromConnectPoint(cp, Optional.ofNullable(null));
Saurav Dasb4e3e102018-10-02 15:31:17 -0700406 }
407 // check if previous config was different and so trap flows may
Saurav Dasb14f08a2019-02-22 16:34:15 -0800408 // need to be removed from other places like AGG switches
Saurav Dasb4e3e102018-10-02 15:31:17 -0700409 if (!prevUseOltUplink) {
Saurav Dasb14f08a2019-02-22 16:34:15 -0800410 addOrRemoveDhcpTrapFromServer(false);
Saurav Dasb4e3e102018-10-02 15:31:17 -0700411 }
Saurav Dasb4e3e102018-10-02 15:31:17 -0700412 } else {
Saurav Dasb14f08a2019-02-22 16:34:15 -0800413 // uplink on AGG switch
414 addOrRemoveDhcpTrapFromServer(true);
Saurav Dasb4e3e102018-10-02 15:31:17 -0700415 }
416 }
417
418 private void cancelDhcpPktsFromServer() {
419 if (useOltUplink) {
420 for (ConnectPoint cp : getUplinkPortsOfOlts()) {
421 log.debug("cancelDhcpPackets: ConnectPoint: {}", cp);
Matteo Scandolo45e5a272019-09-30 09:30:32 -0700422 cancelDhcpPacketsFromConnectPoint(cp, Optional.ofNullable(null));
Saurav Dasb4e3e102018-10-02 15:31:17 -0700423 }
424 } else {
Saurav Dasb14f08a2019-02-22 16:34:15 -0800425 // uplink on AGG switch
426 addOrRemoveDhcpTrapFromServer(false);
Amit Ghosh83c8c892017-11-09 11:08:27 +0000427 }
Saurav Dasb4e3e102018-10-02 15:31:17 -0700428 }
429
Saurav Dasb14f08a2019-02-22 16:34:15 -0800430 /**
431 * Used to add or remove DHCP trap flow for packets received from DHCP server.
432 * Typically used on a non OLT device, like an AGG switch. When adding, a
433 * new dhcp server connect point is selected from the configured options.
434 *
435 * @param add true if dhcp trap flow is to be added, false to remove the
436 * trap flow
437 */
438 private void addOrRemoveDhcpTrapFromServer(boolean add) {
439 if (add) {
440 selectServerConnectPoint();
441 log.debug("dhcp server connect point: " + dhcpServerConnectPoint);
442 }
443 if (dhcpServerConnectPoint.get() == null) {
444 log.warn("No dhcpServer connectPoint found, cannot {} dhcp trap flows",
445 (add) ? "install" : "remove");
446 return;
447 }
448 if (add) {
449 log.info("Adding trap to dhcp server connect point: "
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700450 + dhcpServerConnectPoint);
Saurav Dasb14f08a2019-02-22 16:34:15 -0800451 requestDhcpPacketsFromConnectPoint(dhcpServerConnectPoint.get(),
452 Optional.of(PacketPriority.HIGH1));
453 } else {
454 log.info("Removing trap from dhcp server connect point: "
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700455 + dhcpServerConnectPoint);
Saurav Dasb14f08a2019-02-22 16:34:15 -0800456 cancelDhcpPacketsFromConnectPoint(dhcpServerConnectPoint.get(),
457 Optional.of(PacketPriority.HIGH1));
458 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100459 }
460
461 /**
Amit Ghosh83c8c892017-11-09 11:08:27 +0000462 * Returns all the uplink ports of OLTs configured in SADIS.
463 * Only ports visible in ONOS and for which this instance is master
464 * are returned
465 */
466 private List<ConnectPoint> getUplinkPortsOfOlts() {
467 List<ConnectPoint> cps = new ArrayList<>();
468
469 // find all the olt devices and if their uplink ports are visible
470 Iterable<Device> devices = deviceService.getDevices();
471 for (Device d : devices) {
472 // check if this device is provisioned in Sadis
473
474 log.debug("getUplinkPortsOfOlts: Checking mastership of {}", d);
475 // do only for devices for which we are the master
Andrea Campanella6f45a1b2020-05-08 17:50:12 +0200476 if (!isLocalLeader(d.id())) {
Amit Ghosh83c8c892017-11-09 11:08:27 +0000477 continue;
478 }
479
480 String devSerialNo = d.serialNumber();
481 SubscriberAndDeviceInformation deviceInfo = subsService.get(devSerialNo);
482 log.debug("getUplinkPortsOfOlts: Found device: {}", deviceInfo);
483 if (deviceInfo != null) {
484 // check if the uplink port with that number is available on the device
485 PortNumber pNum = PortNumber.portNumber(deviceInfo.uplinkPort());
486 Port port = deviceService.getPort(d.id(), pNum);
487 log.debug("getUplinkPortsOfOlts: Found port: {}", port);
488 if (port != null) {
489 cps.add(new ConnectPoint(d.id(), pNum));
490 }
491 }
492 }
493 return cps;
494 }
495
496 /**
497 * Returns whether the passed port is the uplink port of the olt device.
498 */
499 private boolean isUplinkPortOfOlt(DeviceId dId, Port p) {
500 log.debug("isUplinkPortOfOlt: DeviceId: {} Port: {}", dId, p);
Amit Ghosh83c8c892017-11-09 11:08:27 +0000501
502 Device d = deviceService.getDevice(dId);
503 SubscriberAndDeviceInformation deviceInfo = subsService.get(d.serialNumber());
504
505 if (deviceInfo != null) {
506 return (deviceInfo.uplinkPort() == p.number().toLong());
507 }
508
509 return false;
510 }
511
512 /**
513 * Returns the connectPoint which is the uplink port of the OLT.
514 */
515 private ConnectPoint getUplinkConnectPointOfOlt(DeviceId dId) {
516
517 Device d = deviceService.getDevice(dId);
518 SubscriberAndDeviceInformation deviceInfo = subsService.get(d.serialNumber());
519 log.debug("getUplinkConnectPointOfOlt DeviceId: {} devInfo: {}", dId, deviceInfo);
520 if (deviceInfo != null) {
521 PortNumber pNum = PortNumber.portNumber(deviceInfo.uplinkPort());
522 Port port = deviceService.getPort(d.id(), pNum);
523 if (port != null) {
524 return new ConnectPoint(d.id(), pNum);
525 }
526 }
527
528 return null;
529 }
530
531 /**
532 * Request DHCP packet from particular connect point via PacketService.
Saurav Dasb14f08a2019-02-22 16:34:15 -0800533 * Optionally provide a priority for the trap flow. If no such priority is
534 * provided, the default priority will be used.
535 *
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700536 * @param cp the connect point to trap dhcp packets from
Saurav Dasb14f08a2019-02-22 16:34:15 -0800537 * @param priority of the trap flow, null to use default priority
Amit Ghosh83c8c892017-11-09 11:08:27 +0000538 */
Saurav Dasb14f08a2019-02-22 16:34:15 -0800539 private void requestDhcpPacketsFromConnectPoint(ConnectPoint cp,
540 Optional<PacketPriority> priority) {
Amit Ghosh83c8c892017-11-09 11:08:27 +0000541 TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
542 .matchEthType(Ethernet.TYPE_IPV4)
543 .matchInPort(cp.port())
544 .matchIPProtocol(IPv4.PROTOCOL_UDP)
545 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
546 packetService.requestPackets(selectorServer.build(),
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700547 priority.isPresent() ? priority.get() : PacketPriority.CONTROL,
548 appId, Optional.of(cp.deviceId()));
Amit Ghosh83c8c892017-11-09 11:08:27 +0000549 }
550
551 /**
Saurav Dasb14f08a2019-02-22 16:34:15 -0800552 * Cancel DHCP packet from particular connect point via PacketService. If
553 * the request was made with a specific packet priority, then the same
554 * priority should be used in this call.
555 *
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700556 * @param cp the connect point for the trap flow
Saurav Dasb14f08a2019-02-22 16:34:15 -0800557 * @param priority with which the trap flow was requested; if request
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700558 * priority was not specified, this param should also be null
Amit Ghosh83c8c892017-11-09 11:08:27 +0000559 */
Saurav Dasb14f08a2019-02-22 16:34:15 -0800560 private void cancelDhcpPacketsFromConnectPoint(ConnectPoint cp,
561 Optional<PacketPriority> priority) {
Amit Ghosh83c8c892017-11-09 11:08:27 +0000562 TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
563 .matchEthType(Ethernet.TYPE_IPV4)
564 .matchInPort(cp.port())
565 .matchIPProtocol(IPv4.PROTOCOL_UDP)
566 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
567 packetService.cancelPackets(selectorServer.build(),
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700568 priority.isPresent() ? priority.get() : PacketPriority.CONTROL,
569 appId, Optional.of(cp.deviceId()));
Amit Ghosh83c8c892017-11-09 11:08:27 +0000570 }
571
Saurav Das45861d42020-10-07 00:03:23 -0700572 /**
573 * Main packet-processing engine for dhcp l2 relay agent.
574 */
Amit Ghosh47243cb2017-07-26 05:08:53 +0100575 private class DhcpRelayPacketProcessor implements PacketProcessor {
Saurav Das45861d42020-10-07 00:03:23 -0700576 private static final String VLAN_KEYWORD = ":vlan";
577 private static final String PCP_KEYWORD = ":pcp";
Amit Ghosh47243cb2017-07-26 05:08:53 +0100578
579 @Override
580 public void process(PacketContext context) {
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800581 packetProcessorExecutor.execute(() -> {
582 processInternal(context);
583 });
584 }
585
586 private void processInternal(PacketContext context) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100587 if (!configured()) {
588 log.warn("Missing DHCP relay config. Abort packet processing");
589 return;
590 }
591
592 // process the packet and get the payload
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530593 Ethernet packet = context.inPacket().parsed();
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000594
Amit Ghosh47243cb2017-07-26 05:08:53 +0100595 if (packet == null) {
596 log.warn("Packet is null");
597 return;
598 }
599
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530600 if (packet.getEtherType() == Ethernet.TYPE_IPV4) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100601 IPv4 ipv4Packet = (IPv4) packet.getPayload();
602
603 if (ipv4Packet.getProtocol() == IPv4.PROTOCOL_UDP) {
604 UDP udpPacket = (UDP) ipv4Packet.getPayload();
605 if (udpPacket.getSourcePort() == UDP.DHCP_CLIENT_PORT ||
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000606 udpPacket.getSourcePort() == UDP.DHCP_SERVER_PORT) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100607 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800608 if (log.isTraceEnabled()) {
609 log.trace("Processing packet with type {} from MAC {}",
610 getDhcpPacketType(dhcpPayload),
611 MacAddress.valueOf(dhcpPayload.getClientHardwareAddress()));
612 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100613 //This packet is dhcp.
614 processDhcpPacket(context, packet, dhcpPayload);
615 }
616 }
617 }
618 }
619
Saurav Dasbd5ce9c2020-09-04 18:46:45 -0700620 // process the dhcp packet before relaying to server or client
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530621 private void processDhcpPacket(PacketContext context, Ethernet packet,
Amit Ghosh47243cb2017-07-26 05:08:53 +0100622 DHCP dhcpPayload) {
623 if (dhcpPayload == null) {
624 log.warn("DHCP payload is null");
625 return;
626 }
627
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700628 DHCP.MsgType incomingPacketType = getDhcpPacketType(dhcpPayload);
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300629 if (incomingPacketType == null) {
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700630 log.warn("DHCP Packet type not found. Dump of ethernet pkt in hex format for troubleshooting.");
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300631 byte[] array = packet.serialize();
632 ByteArrayOutputStream buf = new ByteArrayOutputStream();
633 try {
634 HexDump.dump(array, 0, buf, 0);
635 log.trace(buf.toString());
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700636 } catch (Exception e) {
637 }
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300638 return;
639 }
640
641 SubscriberAndDeviceInformation entry = null;
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000642
Matteo Scandoloeb5a0dc2020-09-15 14:54:28 -0700643 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
644
645 log.info("Received DHCP Packet of type {} from {} with Client MacAddress {} and vlan {}",
646 incomingPacketType, context.inPacket().receivedFrom(),
647 clientMacAddress, packet.getVlanID());
Amit Ghosh47243cb2017-07-26 05:08:53 +0100648
649 switch (incomingPacketType) {
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000650 case DHCPDISCOVER:
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530651 Ethernet ethernetPacketDiscover =
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000652 processDhcpPacketFromClient(context, packet);
653 if (ethernetPacketDiscover != null) {
Saurav Das45861d42020-10-07 00:03:23 -0700654 relayPacketToServer(ethernetPacketDiscover, context);
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000655 }
Saurav Das45861d42020-10-07 00:03:23 -0700656 entry = getSubscriber(context);
Jonathan Hart77ca3152020-02-21 14:31:21 -0800657 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames.valueOf("DHCPDISCOVER"));
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000658 break;
659 case DHCPOFFER:
Saurav Das45861d42020-10-07 00:03:23 -0700660 RelayToClientInfo r2cDataOffer =
Saurav Das15626a02018-09-27 18:36:45 -0700661 processDhcpPacketFromServer(context, packet);
Saurav Das45861d42020-10-07 00:03:23 -0700662 if (r2cDataOffer != null) {
663 relayPacketToClient(r2cDataOffer, clientMacAddress);
664 entry = getSubscriber(r2cDataOffer.cp);
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000665 }
Jonathan Hart77ca3152020-02-21 14:31:21 -0800666 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames.valueOf("DHCPOFFER"));
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000667 break;
668 case DHCPREQUEST:
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530669 Ethernet ethernetPacketRequest =
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000670 processDhcpPacketFromClient(context, packet);
671 if (ethernetPacketRequest != null) {
Saurav Das45861d42020-10-07 00:03:23 -0700672 relayPacketToServer(ethernetPacketRequest, context);
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000673 }
Saurav Das45861d42020-10-07 00:03:23 -0700674 entry = getSubscriber(context);
Jonathan Hart77ca3152020-02-21 14:31:21 -0800675 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames.valueOf("DHCPREQUEST"));
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000676 break;
677 case DHCPACK:
Saurav Das45861d42020-10-07 00:03:23 -0700678 RelayToClientInfo r2cDataAck =
Saurav Das15626a02018-09-27 18:36:45 -0700679 processDhcpPacketFromServer(context, packet);
Saurav Das45861d42020-10-07 00:03:23 -0700680 if (r2cDataAck != null) {
681 relayPacketToClient(r2cDataAck, clientMacAddress);
682 entry = getSubscriber(r2cDataAck.cp);
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000683 }
Jonathan Hart77ca3152020-02-21 14:31:21 -0800684 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames.valueOf("DHCPACK"));
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300685 break;
686 case DHCPDECLINE:
Arjun E K05ad20b2020-03-13 13:25:17 +0000687 Ethernet ethernetPacketDecline =
688 processDhcpPacketFromClient(context, packet);
689 if (ethernetPacketDecline != null) {
Saurav Das45861d42020-10-07 00:03:23 -0700690 relayPacketToServer(ethernetPacketDecline, context);
Arjun E K05ad20b2020-03-13 13:25:17 +0000691 }
Saurav Das45861d42020-10-07 00:03:23 -0700692 entry = getSubscriber(context);
Jonathan Hart77ca3152020-02-21 14:31:21 -0800693 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames.valueOf("DHCPDECLINE"));
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300694 break;
695 case DHCPNAK:
Saurav Das45861d42020-10-07 00:03:23 -0700696 RelayToClientInfo r2cDataNack =
Arjun E K05ad20b2020-03-13 13:25:17 +0000697 processDhcpPacketFromServer(context, packet);
Saurav Das45861d42020-10-07 00:03:23 -0700698 if (r2cDataNack != null) {
699 relayPacketToClient(r2cDataNack, clientMacAddress);
700 entry = getSubscriber(r2cDataNack.cp);
Arjun E K05ad20b2020-03-13 13:25:17 +0000701 }
Jonathan Hart77ca3152020-02-21 14:31:21 -0800702 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames.valueOf("DHCPNACK"));
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300703 break;
704 case DHCPRELEASE:
Thomas Lee S0dc9a3b2020-01-14 10:42:29 +0530705 Ethernet ethernetPacketRelease =
706 processDhcpPacketFromClient(context, packet);
707 if (ethernetPacketRelease != null) {
Saurav Das45861d42020-10-07 00:03:23 -0700708 relayPacketToServer(ethernetPacketRelease, context);
Thomas Lee S0dc9a3b2020-01-14 10:42:29 +0530709 }
Saurav Das45861d42020-10-07 00:03:23 -0700710 entry = getSubscriber(context);
Jonathan Hart77ca3152020-02-21 14:31:21 -0800711 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames.valueOf("DHCPRELEASE"));
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000712 break;
713 default:
714 break;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100715 }
716 }
717
Saurav Das45861d42020-10-07 00:03:23 -0700718 /**
719 * Processes dhcp packets from clients.
720 *
721 * @param context the packet context
722 * @param ethernetPacket the dhcp packet from client
723 * @return the packet to relay to the server
724 */
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530725 private Ethernet processDhcpPacketFromClient(PacketContext context,
726 Ethernet ethernetPacket) {
Saurav Das15626a02018-09-27 18:36:45 -0700727 if (log.isTraceEnabled()) {
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700728 log.trace("DHCP Packet received from client at {} {}",
Saurav Das15626a02018-09-27 18:36:45 -0700729 context.inPacket().receivedFrom(), ethernetPacket);
730 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100731
732 MacAddress relayAgentMac = relayAgentMacAddress(context);
733 if (relayAgentMac == null) {
734 log.warn("RelayAgent MAC not found ");
Amit Ghosh47243cb2017-07-26 05:08:53 +0100735 return null;
736 }
737
pier9e0efbe2020-10-28 20:33:56 +0100738 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100739
740 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
741 UDP udpPacket = (UDP) ipv4Packet.getPayload();
742 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
Saurav Dasbd5ce9c2020-09-04 18:46:45 -0700743 ConnectPoint inPort = context.inPacket().receivedFrom();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100744
Amit Ghosha17354e2017-08-23 12:56:04 +0100745 if (enableDhcpBroadcastReplies) {
746 // We want the reply to come back as a L2 broadcast
747 dhcpPacket.setFlags((short) 0x8000);
748 }
749
Jonathan Hartc36c9552018-07-31 15:07:53 -0400750 MacAddress clientMac = MacAddress.valueOf(dhcpPacket.getClientHardwareAddress());
Saurav Dasbd5ce9c2020-09-04 18:46:45 -0700751 VlanId clientVlan = VlanId.vlanId(ethernetPacket.getVlanID());
Jonathan Hartc36c9552018-07-31 15:07:53 -0400752 IpAddress clientIp = IpAddress.valueOf(dhcpPacket.getClientIPAddress());
Amit Ghosha17354e2017-08-23 12:56:04 +0100753
Jonathan Hartc36c9552018-07-31 15:07:53 -0400754 SubscriberAndDeviceInformation entry = getSubscriber(context);
755 if (entry == null) {
Saurav Das15626a02018-09-27 18:36:45 -0700756 log.warn("Dropping packet as subscriber entry is not available");
Jonathan Hartc36c9552018-07-31 15:07:53 -0400757 return null;
758 }
759
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000760 UniTagInformation uniTagInformation = getUnitagInformationFromPacketContext(context, entry);
761 if (uniTagInformation == null) {
762 log.warn("Missing service information for connectPoint {} / cTag {}",
Saurav Dasbd5ce9c2020-09-04 18:46:45 -0700763 inPort, clientVlan);
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000764 return null;
765 }
Saurav Das45861d42020-10-07 00:03:23 -0700766 DhcpOption82Data d82 = null;
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000767 if (option82) {
Saurav Das45861d42020-10-07 00:03:23 -0700768 DHCP dhcpPacketWithOption82 = addOption82(dhcpPacket, entry,
769 inPort, clientVlan,
770 uniTagInformation
771 .getDsPonCTagPriority());
772 byte[] d82b = dhcpPacketWithOption82
773 .getOption(DHCP.DHCPOptionCode.OptionCode_CircuitID)
774 .getData();
775 d82 = new DhcpOption82Data(d82b);
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000776 udpPacket.setPayload(dhcpPacketWithOption82);
777 }
778
779 ipv4Packet.setPayload(udpPacket);
780 etherReply.setPayload(ipv4Packet);
Amit Ghosh83c8c892017-11-09 11:08:27 +0000781 if (modifyClientPktsSrcDstMac) {
782 etherReply.setSourceMACAddress(relayAgentMac);
783 etherReply.setDestinationMACAddress(dhcpConnectMac);
784 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100785
Amit Ghosh8951f042017-08-10 13:48:10 +0100786 etherReply.setPriorityCode(ethernetPacket.getPriorityCode());
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000787 etherReply.setVlanID(uniTagInformation.getPonCTag().toShort());
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530788 etherReply.setQinQTPID(Ethernet.TYPE_VLAN);
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000789 etherReply.setQinQVID(uniTagInformation.getPonSTag().toShort());
790 if (uniTagInformation.getUsPonSTagPriority() != -1) {
791 etherReply.setQinQPriorityCode((byte) uniTagInformation.getUsPonSTagPriority());
792 }
Saurav Das45861d42020-10-07 00:03:23 -0700793 if (uniTagInformation.getUsPonCTagPriority() != -1) {
794 etherReply.setPriorityCode((byte) uniTagInformation
795 .getUsPonCTagPriority());
796 }
797
798 DhcpAllocationInfo info = new DhcpAllocationInfo(inPort,
799 dhcpPacket.getPacketType(),
800 (d82 == null)
801 ? entry.circuitId()
802 : d82.getAgentCircuitId(),
803 clientMac, clientIp,
804 clientVlan, entry.id());
805 String key = getUniqueUuidFromString(entry.id() + clientMac
806 + clientVlan);
807 allocations.put(key, info);
808 post(new DhcpL2RelayEvent(DhcpL2RelayEvent.Type.UPDATED, info, inPort));
809 if (log.isTraceEnabled()) {
Andrea Campanellad79a6792020-10-26 14:44:46 +0100810 log.trace("Finished processing DHCP Packet of type {} with MAC {} from {} "
Saurav Das45861d42020-10-07 00:03:23 -0700811 + "... relaying to dhcpServer",
Andrea Campanellad79a6792020-10-26 14:44:46 +0100812 dhcpPacket.getPacketType(), clientMac, entry.id());
Saurav Das45861d42020-10-07 00:03:23 -0700813 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100814 return etherReply;
815 }
816
Saurav Das45861d42020-10-07 00:03:23 -0700817 /**
818 * Processes dhcp packets from the server.
819 *
820 * @param context the packet context
821 * @param ethernetPacket the dhcp packet
822 * @return returns information necessary for relaying packet to client
823 */
824 private RelayToClientInfo processDhcpPacketFromServer(PacketContext context,
825 Ethernet ethernetPacket) {
Saurav Das15626a02018-09-27 18:36:45 -0700826 if (log.isTraceEnabled()) {
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700827 log.trace("DHCP Packet received from server at {} {}",
Saurav Das15626a02018-09-27 18:36:45 -0700828 context.inPacket().receivedFrom(), ethernetPacket);
829 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100830 // get dhcp header.
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530831 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100832 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
833 UDP udpPacket = (UDP) ipv4Packet.getPayload();
Saurav Das45861d42020-10-07 00:03:23 -0700834 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
Saurav Dasbd5ce9c2020-09-04 18:46:45 -0700835 VlanId innerVlan = VlanId.vlanId(ethernetPacket.getVlanID());
Saurav Das45861d42020-10-07 00:03:23 -0700836 MacAddress dstMac = valueOf(dhcpPacket.getClientHardwareAddress());
Amit Ghosh47243cb2017-07-26 05:08:53 +0100837
Saurav Das45861d42020-10-07 00:03:23 -0700838 // we leave the srcMac from the original packet.
839 // TODO remove S-VLAN
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000840 etherReply.setQinQVID(VlanId.NO_VID);
841 etherReply.setQinQPriorityCode((byte) 0);
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530842 etherReply.setDestinationMACAddress(dstMac);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100843
Saurav Das45861d42020-10-07 00:03:23 -0700844 // TODO deserialization of dhcp option82 leaves 'data' field null
845 // As a result we need to retrieve suboption data
846 RelayToClientInfo r2cData = null;
847 boolean usedOption82 = false;
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000848 if (option82) {
Saurav Das45861d42020-10-07 00:03:23 -0700849 // retrieve connectPoint and vlan from option82, if it is in expected format
850 DhcpOption opt = dhcpPacket
851 .getOption(DHCP.DHCPOptionCode.OptionCode_CircuitID);
852 if (opt != null && opt instanceof DhcpRelayAgentOption) {
853 DhcpRelayAgentOption d82 = (DhcpRelayAgentOption) opt;
854 DhcpOption d82ckt = d82.getSubOption(DhcpOption82Data.CIRCUIT_ID_CODE);
855 if (d82ckt.getData() != null) {
856 r2cData = decodeCircuitId(new String(d82ckt.getData()));
857 }
858 }
859 if (r2cData != null) {
860 usedOption82 = true;
861 etherReply.setVlanID(r2cData.cvid.toShort());
862 if (r2cData.pcp != -1) {
863 etherReply.setPriorityCode((byte) r2cData.pcp);
864 }
865 }
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000866 }
Saurav Das45861d42020-10-07 00:03:23 -0700867 // always remove option82 if present
868 DHCP remDhcpPacket = removeOption82(dhcpPacket);
869 udpPacket.setPayload(remDhcpPacket);
870
Amit Ghosh47243cb2017-07-26 05:08:53 +0100871 ipv4Packet.setPayload(udpPacket);
872 etherReply.setPayload(ipv4Packet);
873
Saurav Das45861d42020-10-07 00:03:23 -0700874 if (!usedOption82) {
875 // option 82 data not present or not used, we need to
876 // lookup host store with client dstmac and vlan from context
877 r2cData = new RelayToClientInfo();
878 r2cData.cp = getConnectPointOfClient(dstMac, context);
879 if (r2cData.cp == null) {
880 log.warn("Couldn't find subscriber, service or host info for mac"
881 + " address {} and vlan {} .. DHCP packet can't be"
882 + " delivered to client", dstMac, innerVlan);
883 return null;
884 }
885 }
886
887 // always need the subscriber entry
888 SubscriberAndDeviceInformation entry = getSubscriber(r2cData.cp);
889 if (entry == null) {
890 log.warn("Couldn't find subscriber info for cp {}.. DHCP packet"
891 + " can't be delivered to client mac {} and vlan {}",
892 r2cData.cp, dstMac, innerVlan);
893 return null;
894 }
895
896 if (!usedOption82) {
897 UniTagInformation uniTagInformation =
898 getUnitagInformationFromPacketContext(context, entry);
899 if (uniTagInformation == null) {
900 log.warn("Missing service information for connectPoint {} "
901 + " cTag {} .. DHCP packet can't be delivered to client",
902 r2cData.cp, innerVlan);
903 return null;
904 }
905 r2cData.cvid = uniTagInformation.getPonCTag();
906 r2cData.pcp = uniTagInformation.getDsPonCTagPriority();
907 r2cData.cktId = entry.circuitId();
908 etherReply.setVlanID(r2cData.cvid.toShort());
909 if (r2cData.pcp != -1) {
910 etherReply.setPriorityCode((byte) r2cData.pcp);
911 }
912 }
913
914 // update stats and events
915 IpAddress ip = IpAddress.valueOf(dhcpPacket.getYourIPAddress());
916 DhcpAllocationInfo info =
917 new DhcpAllocationInfo(r2cData.cp, dhcpPacket.getPacketType(),
918 r2cData.cktId, dstMac, ip, innerVlan,
919 entry.id());
920 String key = getUniqueUuidFromString(entry.id() + info.macAddress()
921 + innerVlan);
922 allocations.put(key, info);
923 post(new DhcpL2RelayEvent(DhcpL2RelayEvent.Type.UPDATED, info,
924 r2cData.cp));
925 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames
926 .valueOf("PACKETS_FROM_SERVER"));
927 if (log.isTraceEnabled()) {
928 log.trace("Finished processing packet.. relaying to client at {}",
929 r2cData.cp);
930 }
931 r2cData.ethernetPkt = etherReply;
932 return r2cData;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100933 }
934
Saurav Das45861d42020-10-07 00:03:23 -0700935 // forward the packet to ConnectPoint where the DHCP server is attached.
936 private void relayPacketToServer(Ethernet packet, PacketContext context) {
Andrea Campanellad79a6792020-10-26 14:44:46 +0100937 SubscriberAndDeviceInformation entry = getSubscriber(context);
Saurav Das45861d42020-10-07 00:03:23 -0700938 if (log.isTraceEnabled()) {
939 IPv4 ipv4Packet = (IPv4) packet.getPayload();
940 UDP udpPacket = (UDP) ipv4Packet.getPayload();
941 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
Andrea Campanellad79a6792020-10-26 14:44:46 +0100942 log.trace("Emitting packet to server: packet {}, with MAC {} from {}",
Saurav Das45861d42020-10-07 00:03:23 -0700943 getDhcpPacketType(dhcpPayload),
Andrea Campanellad79a6792020-10-26 14:44:46 +0100944 MacAddress.valueOf(dhcpPayload.getClientHardwareAddress()),
945 entry.id());
Saurav Das45861d42020-10-07 00:03:23 -0700946 }
947 ConnectPoint toSendTo = null;
948 if (!useOltUplink) {
949 toSendTo = dhcpServerConnectPoint.get();
950 } else {
951 toSendTo = getUplinkConnectPointOfOlt(context.inPacket().receivedFrom()
952 .deviceId());
953 }
954
955 if (toSendTo != null) {
956 TrafficTreatment t = DefaultTrafficTreatment.builder()
957 .setOutput(toSendTo.port()).build();
958 OutboundPacket o = new DefaultOutboundPacket(toSendTo
959 .deviceId(), t, ByteBuffer.wrap(packet.serialize()));
960 if (log.isTraceEnabled()) {
961 log.trace("Relaying packet to dhcp server at {} {}", toSendTo,
962 packet);
963 }
964 packetService.emit(o);
965
Saurav Das45861d42020-10-07 00:03:23 -0700966 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames
967 .valueOf("PACKETS_TO_SERVER"));
968 } else {
969 log.error("No connect point to send msg to DHCP Server");
970 }
971 }
972
973 // send the response to the requester host (client)
974 private void relayPacketToClient(RelayToClientInfo r2cData,
975 MacAddress dstMac) {
976 ConnectPoint subCp = r2cData.cp;
977 Ethernet ethPacket = r2cData.ethernetPkt;
978 // Send packet out to requester if the host information is available
979 if (subCp != null) {
980 TrafficTreatment t = DefaultTrafficTreatment.builder()
981 .setOutput(subCp.port()).build();
982 OutboundPacket o = new DefaultOutboundPacket(subCp.deviceId(),
983 t, ByteBuffer.wrap(ethPacket.serialize()));
984 if (log.isTraceEnabled()) {
985 log.trace("Relaying packet to DHCP client at {} with "
986 + "MacAddress {}, {} given {}", subCp, dstMac,
987 ethPacket, r2cData);
988 }
989 packetService.emit(o);
990 } else {
991 log.error("Dropping DHCP Packet because unknown connectPoint for {}",
992 dstMac);
993 }
994 }
995
996 /**
997 * Option 82 includes circuitId and remoteId data configured by an
998 * operator in sadis for a subscriber, and can be a string in any form
999 * relevant to the operator's dhcp-server. When circuitId is configured
1000 * in sadis, the relay agent adds the option, but does not use the
1001 * information for forwarding packets back to client.
1002 * <p>
1003 * If circuitId is not configured in sadis, this relay-agent adds
1004 * circuitId information in the form
1005 * "{@literal<}connectPoint>:vlan{@literal<}clientVlanId>:pcp{@literal<}downstreamPcp>"
1006 * for example, "of:0000000000000001/32:vlan200:pcp7". When the packet
1007 * is received back from the server with circuitId in this form, this
1008 * relay agent will use this information to forward packets to the
1009 * client.
1010 *
1011 * @param dhcpPacket the DHCP packet to transform
1012 * @param entry sadis information for the subscriber
1013 * @param cp the connectPoint to set if sadis entry has no circuitId
1014 * @param clientVlan the vlan to set if sadis entry has no circuitId
1015 * @param downstreamPbits the pbits to set if sadis entry has no
1016 * circuitId
1017 * @return the modified dhcp packet with option82 added
Amit Ghosha17354e2017-08-23 12:56:04 +01001018 */
Saurav Das45861d42020-10-07 00:03:23 -07001019 private DHCP addOption82(DHCP dhcpPacket, SubscriberAndDeviceInformation entry,
1020 ConnectPoint cp, VlanId clientVlan,
1021 int downstreamPbits) {
1022 List<DhcpOption> options = Lists.newArrayList(dhcpPacket.getOptions());
1023 DhcpOption82Data option82 = new DhcpOption82Data();
1024 if (entry.circuitId() == null || entry.circuitId().isBlank()) {
1025 option82.setAgentCircuitId(cp + VLAN_KEYWORD + clientVlan
1026 + PCP_KEYWORD
1027 + downstreamPbits);
1028 } else {
1029 option82.setAgentCircuitId(entry.circuitId());
1030 }
Saurav Dasd935f452020-10-29 14:34:53 -07001031 if (entry.remoteId() != null && !entry.remoteId().isBlank()) {
1032 option82.setAgentRemoteId(entry.remoteId());
1033 }
Saurav Das45861d42020-10-07 00:03:23 -07001034 if (log.isTraceEnabled()) {
1035 log.trace("adding option82 {} ", option82);
1036 }
1037 DhcpOption option = new DhcpOption()
1038 .setCode(DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue())
1039 .setData(option82.toByteArray())
1040 .setLength(option82.length());
1041
1042 options.add(options.size() - 1, option);
1043 dhcpPacket.setOptions(options);
1044
1045 return dhcpPacket;
1046 }
1047
1048 private DHCP removeOption82(DHCP dhcpPacket) {
1049 List<DhcpOption> options = dhcpPacket.getOptions();
1050 List<DhcpOption> newoptions = options.stream()
1051 .filter(option -> option
1052 .getCode() != DHCP.DHCPOptionCode.OptionCode_CircuitID
1053 .getValue())
1054 .collect(Collectors.toList());
1055
1056 return dhcpPacket.setOptions(newoptions);
1057 }
1058
1059 /**
1060 * Returns the circuit Id values decoded from the option 82 data. Decoding
1061 * is performed if and only if the circuit id format is in the form
1062 * "{@literal<}connectPoint>:vlan{@literal<}clientVlanId>:pcp{@literal<}downstreamPcp>"
1063 *
1064 * @param cktId the circuitId string from option 82 data
1065 * @return decoded circuit id data if it is in the expected format or
1066 * null
1067 */
1068 private RelayToClientInfo decodeCircuitId(String cktId) {
1069 if (cktId.contains(VLAN_KEYWORD) && cktId.contains(PCP_KEYWORD)) {
1070 ConnectPoint cp = ConnectPoint
1071 .fromString(cktId
1072 .substring(0, cktId.indexOf(VLAN_KEYWORD)));
1073 VlanId cvid = VlanId
1074 .vlanId(cktId.substring(
1075 cktId.indexOf(VLAN_KEYWORD)
1076 + VLAN_KEYWORD.length(),
1077 cktId.indexOf(PCP_KEYWORD)));
1078 int pcp = Integer
1079 .valueOf(cktId.substring(cktId.indexOf(PCP_KEYWORD)
1080 + PCP_KEYWORD.length()))
1081 .intValue();
1082 log.debug("retrieved from option82-> cp={} cvlan={} down-pcp={}"
1083 + " for relaying to client ", cp, cvid, pcp);
1084 return new RelayToClientInfo(cp, cvid, pcp, cktId);
1085 } else {
1086 log.debug("Option 82 circuitId {} is operator defined and will "
1087 + "not be used for forwarding", cktId);
1088 return null;
1089 }
1090 }
1091
1092 private class RelayToClientInfo {
1093 Ethernet ethernetPkt;
1094 ConnectPoint cp;
1095 VlanId cvid;
1096 int pcp;
1097 String cktId;
1098
1099 public RelayToClientInfo(ConnectPoint cp, VlanId cvid, int pcp,
1100 String cktId) {
1101 this.cp = cp;
1102 this.cvid = cvid;
1103 this.pcp = pcp;
1104 this.cktId = cktId;
1105 }
1106
1107 public RelayToClientInfo() {
1108 }
1109
1110 @Override
1111 public String toString() {
1112 return "RelayToClientInfo: {connectPoint=" + cp + " clientVlan="
1113 + cvid + " clientPcp=" + pcp + " circuitId=" + cktId + "}";
1114 }
1115
1116 }
1117
1118 // get the type of the DHCP packet
1119 private DHCP.MsgType getDhcpPacketType(DHCP dhcpPayload) {
1120 for (DhcpOption option : dhcpPayload.getOptions()) {
1121 if (option.getCode() == OptionCode_MessageType.getValue()) {
1122 byte[] data = option.getData();
1123 return DHCP.MsgType.getType(data[0]);
1124 }
1125 }
1126 return null;
1127 }
1128
1129 private void updateDhcpRelayCountersStore(SubscriberAndDeviceInformation entry,
1130 DhcpL2RelayCounterNames counterType) {
1131 // Update global counter stats
1132 dhcpL2RelayCounters.incrementCounter(DhcpL2RelayEvent.GLOBAL_COUNTER,
1133 counterType);
1134 if (entry == null) {
1135 log.warn("Counter not updated as subscriber info not found.");
1136 } else {
1137 // Update subscriber counter stats
1138 dhcpL2RelayCounters.incrementCounter(entry.id(), counterType);
1139 }
1140 }
1141
1142 /**
1143 * Get subscriber information based on subscriber's connectPoint.
1144 *
1145 * @param subsCp the subscriber's connectPoint
1146 * @return subscriber sadis info or null if not found
1147 */
1148 private SubscriberAndDeviceInformation getSubscriber(ConnectPoint subsCp) {
1149 if (subsCp != null) {
1150 String portName = getPortName(subsCp);
1151 return subsService.get(portName);
1152 }
1153 return null;
1154 }
1155
1156 /**
1157 * Returns sadis info for subscriber based on incoming packet context.
1158 * The packet context must refer to a packet coming from a subscriber
1159 * port.
1160 *
1161 * @param context incoming packet context from subscriber port (UNI)
1162 * @return sadis info for the subscriber or null
1163 */
1164 private SubscriberAndDeviceInformation getSubscriber(PacketContext context) {
1165 String portName = getPortName(context.inPacket().receivedFrom());
1166 return subsService.get(portName);
1167 }
1168
1169 /**
1170 * Returns ConnectPoint of the Client based on MAC address and C-VLAN.
1171 * Verifies that returned connect point has service defined in sadis.
1172 *
1173 * @param dstMac client dstMac
1174 * @param context context for incoming packet, parsed for C-vlan id
1175 * @return connect point information for client or null if connect point
1176 * not found or service cannot be verified for client info
1177 */
1178 private ConnectPoint getConnectPointOfClient(MacAddress dstMac,
1179 PacketContext context) {
Amit Ghosha17354e2017-08-23 12:56:04 +01001180 Set<Host> hosts = hostService.getHostsByMac(dstMac);
1181 if (hosts == null || hosts.isEmpty()) {
1182 log.warn("Cannot determine host for DHCP client: {}. Aborting "
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001183 + "relay for DHCP Packet from server", dstMac);
Amit Ghosha17354e2017-08-23 12:56:04 +01001184 return null;
1185 }
1186 for (Host h : hosts) {
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001187 // if more than one (for example, multiple services with same
1188 // mac-address but different service VLANs (inner/C vlans)
Amit Ghosha17354e2017-08-23 12:56:04 +01001189 // find the connect point which has an valid entry in SADIS
1190 ConnectPoint cp = new ConnectPoint(h.location().deviceId(),
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001191 h.location().port());
Amit Ghosha17354e2017-08-23 12:56:04 +01001192
Saurav Das45861d42020-10-07 00:03:23 -07001193 SubscriberAndDeviceInformation sub = getSubscriber(cp);
Gamze Abakaa64b3bc2020-01-31 06:51:43 +00001194 if (sub == null) {
Saurav Das45861d42020-10-07 00:03:23 -07001195 log.warn("Subscriber info not found for {} for host {}", cp, h);
1196 continue;
Amit Ghosha17354e2017-08-23 12:56:04 +01001197 }
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001198 // check for cvlan in subscriber's uniTagInfo list
Saurav Das45861d42020-10-07 00:03:23 -07001199 UniTagInformation uniTagInformation =
1200 getUnitagInformationFromPacketContext(context, sub);
Jonathan Hartb4fbc922020-04-14 12:17:44 -07001201 if (uniTagInformation != null) {
1202 return cp;
Gamze Abakaa64b3bc2020-01-31 06:51:43 +00001203 }
Gamze Abakaa64b3bc2020-01-31 06:51:43 +00001204 }
Jonathan Hartb4fbc922020-04-14 12:17:44 -07001205 // no sadis config found for this connectPoint/vlan
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001206 log.warn("Missing service information for dhcp packet received from"
1207 + " {} with cTag {} .. cannot relay to client",
Saurav Das45861d42020-10-07 00:03:23 -07001208 context.inPacket().receivedFrom(),
1209 context.inPacket().parsed().getVlanID());
1210 return null;
1211 }
1212
1213 /**
1214 * Returns the port-name for the given connectPoint port.
1215 *
1216 * @param cp the given connect point
1217 * @return the port-name for the connect point port
1218 */
1219 private String getPortName(ConnectPoint cp) {
1220 Port p = deviceService.getPort(cp);
1221 return p.annotations().value(AnnotationKeys.PORT_NAME);
1222 }
1223
1224 /**
1225 * Return's uniTagInformation (service information) if incoming packet's
1226 * client VLAN id matches the subscriber's service info, and dhcp is
1227 * required for this service.
1228 *
1229 * @param context
1230 * @param sub
1231 * @return
1232 */
1233 private UniTagInformation getUnitagInformationFromPacketContext(PacketContext context,
1234 SubscriberAndDeviceInformation sub) {
1235 // If the ctag is defined in the tagList and dhcp is required,
1236 // return the service info
1237 List<UniTagInformation> tagList = sub.uniTagList();
1238 for (UniTagInformation uniServiceInformation : tagList) {
1239 if (uniServiceInformation.getPonCTag().toShort() == context.inPacket()
1240 .parsed().getVlanID()) {
1241 if (uniServiceInformation.getIsDhcpRequired()) {
1242 return uniServiceInformation;
1243 }
1244 }
1245 }
Jonathan Hartb4fbc922020-04-14 12:17:44 -07001246
Amit Ghosha17354e2017-08-23 12:56:04 +01001247 return null;
1248 }
1249
Saurav Das45861d42020-10-07 00:03:23 -07001250
1251 private MacAddress relayAgentMacAddress(PacketContext context) {
1252 SubscriberAndDeviceInformation device = this.getDevice(context);
1253 if (device == null) {
1254 log.warn("Device not found for {}", context.inPacket().receivedFrom());
1255 return null;
Amit Ghosh47243cb2017-07-26 05:08:53 +01001256 }
Saurav Das45861d42020-10-07 00:03:23 -07001257 return device.hardwareIdentifier();
Amit Ghosh47243cb2017-07-26 05:08:53 +01001258 }
Amit Ghosh47243cb2017-07-26 05:08:53 +01001259
Saurav Das45861d42020-10-07 00:03:23 -07001260 /**
1261 * Returns sadis information for device from which packet was received.
1262 *
1263 * @param context the packet context
1264 * @return sadis information for device
1265 */
1266 private SubscriberAndDeviceInformation getDevice(PacketContext context) {
1267 String serialNo = deviceService
1268 .getDevice(context.inPacket().receivedFrom().deviceId())
1269 .serialNumber();
1270 return subsService.get(serialNo);
1271 }
Deepa vaddireddy0060f532017-08-04 06:46:05 +00001272
Deepa vaddireddy0060f532017-08-04 06:46:05 +00001273 }
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001274
Amit Ghosh47243cb2017-07-26 05:08:53 +01001275 /**
1276 * Listener for network config events.
1277 */
1278 private class InternalConfigListener implements NetworkConfigListener {
1279
1280 @Override
1281 public void event(NetworkConfigEvent event) {
1282
1283 if ((event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
1284 event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED) &&
1285 event.configClass().equals(DhcpL2RelayConfig.class)) {
1286 updateConfig();
1287 log.info("Reconfigured");
1288 }
1289 }
1290 }
Deepa vaddireddy0060f532017-08-04 06:46:05 +00001291
Amit Ghosh8951f042017-08-10 13:48:10 +01001292 /**
1293 * Handles Mastership changes for the devices which connect
1294 * to the DHCP server.
1295 */
1296 private class InnerMastershipListener implements MastershipListener {
1297 @Override
1298 public void event(MastershipEvent event) {
Amit Ghosh83c8c892017-11-09 11:08:27 +00001299 if (!useOltUplink) {
1300 if (dhcpServerConnectPoint.get() != null &&
1301 dhcpServerConnectPoint.get().deviceId().
1302 equals(event.subject())) {
1303 log.trace("Mastership Event recevived for {}", event.subject());
1304 // mastership of the device for our connect point has changed
1305 // reselect
1306 selectServerConnectPoint();
1307 }
Amit Ghosh8951f042017-08-10 13:48:10 +01001308 }
1309 }
1310 }
Deepa vaddireddy0060f532017-08-04 06:46:05 +00001311
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001312 private void removeAllocations(Predicate<Map.Entry<String, Versioned<DhcpAllocationInfo>>> pred) {
1313 allocations.stream()
1314 .filter(pred)
1315 .map(Map.Entry::getKey)
1316 .collect(Collectors.toList())
1317 .forEach(allocations::remove);
1318 }
1319
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001320 @Override
Matteo Scandoloab346512020-04-17 13:39:55 -07001321 public void clearAllocations() {
1322 allocations.clear();
1323 }
1324
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001325 @Override
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001326 public boolean removeAllocationsByConnectPoint(ConnectPoint cp) {
1327 boolean removed = false;
Matteo Scandoloab346512020-04-17 13:39:55 -07001328 for (String key : allocations.keySet()) {
1329 DhcpAllocationInfo entry = allocations.asJavaMap().get(key);
1330 if (entry.location().equals(cp)) {
1331 allocations.remove(key);
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001332 removed = true;
Matteo Scandoloab346512020-04-17 13:39:55 -07001333 }
1334 }
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001335 return removed;
Matteo Scandoloab346512020-04-17 13:39:55 -07001336 }
1337
Andrea Campanella6f45a1b2020-05-08 17:50:12 +02001338 /**
1339 * Checks for mastership or falls back to leadership on deviceId.
1340 * If the node is not master and device is available
1341 * or the device is not available and the leader is different
1342 * we let master or leader else handle it
1343 * Leadership on the device topic is needed because the master can be NONE
1344 * in case the device went away, we still need to handle events
1345 * consistently
1346 */
1347 private boolean isLocalLeader(DeviceId deviceId) {
1348 if (!mastershipService.isLocalMaster(deviceId)) {
1349 // When the device is available we just check the mastership
1350 if (deviceService.isAvailable(deviceId)) {
1351 return false;
1352 }
1353 // Fallback with Leadership service - device id is used as topic
1354 NodeId leader = leadershipService.runForLeadership(
1355 deviceId.toString()).leaderNodeId();
1356 // Verify if this node is the leader
1357 return clusterService.getLocalNode().id().equals(leader);
1358 }
1359 return true;
1360 }
1361
Amit Ghosh8951f042017-08-10 13:48:10 +01001362 /**
1363 * Handles Device status change for the devices which connect
1364 * to the DHCP server.
1365 */
1366 private class InnerDeviceListener implements DeviceListener {
1367 @Override
1368 public void event(DeviceEvent event) {
Andrea Campanella6f45a1b2020-05-08 17:50:12 +02001369 final DeviceId deviceId = event.subject().id();
1370
1371 // Ensure only one instance handles the event
1372 if (!isLocalLeader(deviceId)) {
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001373 return;
1374 }
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001375 // ignore stats
1376 if (event.type().equals(DeviceEvent.Type.PORT_STATS_UPDATED)) {
1377 return;
1378 }
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001379
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001380 log.debug("Device Event received for {} event {}", event.subject(),
1381 event.type());
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001382
Thomas Lee S9df15082019-12-23 11:31:15 +05301383 switch (event.type()) {
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001384 case DEVICE_REMOVED:
1385 log.info("Device removed {}", event.subject().id());
1386 removeAllocations(e -> e.getValue().value().location().deviceId().equals(deviceId));
1387 break;
Thomas Lee S9df15082019-12-23 11:31:15 +05301388 case DEVICE_AVAILABILITY_CHANGED:
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001389 boolean available = deviceService.isAvailable(deviceId);
1390 log.info("Device Avail Changed {} to {}", event.subject().id(), available);
1391
1392 if (!available && deviceService.getPorts(deviceId).isEmpty()) {
1393 removeAllocations(e -> e.getValue().value().location().deviceId().equals(deviceId));
1394 log.info("Device {} is removed from DHCP allocationmap ", deviceId);
Thomas Lee S9df15082019-12-23 11:31:15 +05301395 }
1396 break;
Thomas Lee S6b77ad22020-01-10 11:27:43 +05301397 case PORT_REMOVED:
1398 Port port = event.port();
Thomas Lee S6b77ad22020-01-10 11:27:43 +05301399 log.info("Port {} is deleted on device {}", port, deviceId);
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001400
1401 ConnectPoint cp = new ConnectPoint(deviceId, port.number());
1402 removeAllocations(e -> e.getValue().value().location().equals(cp));
1403
Thomas Lee S6b77ad22020-01-10 11:27:43 +05301404 log.info("Port {} on device {} is removed from DHCP allocationmap", event.port(), deviceId);
1405 break;
Thomas Lee S9df15082019-12-23 11:31:15 +05301406 default:
1407 break;
1408 }
Amit Ghosh83c8c892017-11-09 11:08:27 +00001409 if (!useOltUplink) {
1410 if (dhcpServerConnectPoint.get() == null) {
1411 switch (event.type()) {
1412 case DEVICE_ADDED:
1413 case DEVICE_AVAILABILITY_CHANGED:
Saurav Dasb14f08a2019-02-22 16:34:15 -08001414 // some device is available check if we can get a
1415 // connect point we can use
1416 addOrRemoveDhcpTrapFromServer(true);
Amit Ghosh83c8c892017-11-09 11:08:27 +00001417 break;
1418 default:
1419 break;
1420 }
1421 return;
Amit Ghosh8951f042017-08-10 13:48:10 +01001422 }
Amit Ghosh83c8c892017-11-09 11:08:27 +00001423 if (dhcpServerConnectPoint.get().deviceId().
1424 equals(event.subject().id())) {
1425 switch (event.type()) {
1426 case DEVICE_AVAILABILITY_CHANGED:
1427 case DEVICE_REMOVED:
1428 case DEVICE_SUSPENDED:
1429 // state of our device has changed, check if we need
Saurav Dasb14f08a2019-02-22 16:34:15 -08001430 // to re-select a connectpoint
1431 addOrRemoveDhcpTrapFromServer(true);
Amit Ghosh83c8c892017-11-09 11:08:27 +00001432 break;
1433 default:
1434 break;
1435 }
1436 }
1437 } else {
Amit Ghosh8951f042017-08-10 13:48:10 +01001438 switch (event.type()) {
Amit Ghosh83c8c892017-11-09 11:08:27 +00001439 case PORT_ADDED:
Saurav Dasb4e3e102018-10-02 15:31:17 -07001440 if (useOltUplink && isUplinkPortOfOlt(event.subject().id(), event.port())) {
Saurav Dasb14f08a2019-02-22 16:34:15 -08001441 requestDhcpPacketsFromConnectPoint(
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001442 new ConnectPoint(event.subject().id(), event.port().number()),
1443 Optional.empty());
Amit Ghosh83c8c892017-11-09 11:08:27 +00001444 }
Amit Ghosh8951f042017-08-10 13:48:10 +01001445 break;
1446 default:
1447 break;
1448 }
1449 }
1450 }
1451 }
Jonathan Hart77ca3152020-02-21 14:31:21 -08001452
1453 private class InnerDhcpL2RelayStoreDelegate implements DhcpL2RelayStoreDelegate {
1454 @Override
1455 public void notify(DhcpL2RelayEvent event) {
1456 if (event.type().equals(DhcpL2RelayEvent.Type.STATS_UPDATE)) {
1457 DhcpL2RelayEvent toPost = event;
1458 if (event.getSubscriberId() != null) {
1459 // infuse the event with the allocation info before posting
1460 DhcpAllocationInfo info = Versioned.valueOrNull(allocations.get(event.getSubscriberId()));
1461 toPost = new DhcpL2RelayEvent(event.type(), info, event.connectPoint(),
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001462 event.getCountersEntry(), event.getSubscriberId());
Jonathan Hart77ca3152020-02-21 14:31:21 -08001463 }
1464 post(toPost);
1465 }
1466
1467 }
1468 }
Amit Ghosh47243cb2017-07-26 05:08:53 +01001469}