blob: 7d919cf2baf4ae4752adf991e2227c614697390d [file] [log] [blame]
Amit Ghosh47243cb2017-07-26 05:08:53 +01001/*
Joey Armstrong7e08d2a2022-12-30 12:25:42 -05002 * Copyright 2017-2023 Open Networking Foundation (ONF) and the ONF Contributors
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;
Tunahan Sezenad640f82021-04-28 10:23:44 +000019import static java.util.concurrent.Executors.newSingleThreadExecutor;
Saurav Das45861d42020-10-07 00:03:23 -070020import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
21import static org.onlab.packet.MacAddress.valueOf;
22import static org.onlab.util.Tools.groupedThreads;
23import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
24import static org.opencord.dhcpl2relay.impl.OsgiPropertyConstants.ENABLE_DHCP_BROADCAST_REPLIES;
25import static org.opencord.dhcpl2relay.impl.OsgiPropertyConstants.ENABLE_DHCP_BROADCAST_REPLIES_DEFAULT;
26import static org.opencord.dhcpl2relay.impl.OsgiPropertyConstants.OPTION_82;
27import static org.opencord.dhcpl2relay.impl.OsgiPropertyConstants.OPTION_82_DEFAULT;
28import static org.opencord.dhcpl2relay.impl.OsgiPropertyConstants.PACKET_PROCESSOR_THREADS;
29import static org.opencord.dhcpl2relay.impl.OsgiPropertyConstants.PACKET_PROCESSOR_THREADS_DEFAULT;
30
31import java.io.ByteArrayOutputStream;
32import java.nio.ByteBuffer;
33import java.time.Instant;
34import java.util.ArrayList;
35import java.util.Dictionary;
36import java.util.List;
37import java.util.Map;
38import java.util.Optional;
39import java.util.Set;
40import java.util.UUID;
41import java.util.concurrent.ExecutorService;
42import java.util.concurrent.Executors;
43import java.util.concurrent.ScheduledExecutorService;
44import java.util.concurrent.ScheduledFuture;
45import java.util.concurrent.atomic.AtomicReference;
46import java.util.function.Predicate;
47import java.util.stream.Collectors;
48
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -030049import org.apache.commons.io.HexDump;
Amit Ghosh47243cb2017-07-26 05:08:53 +010050import org.onlab.packet.DHCP;
Deepa vaddireddy0060f532017-08-04 06:46:05 +000051import org.onlab.packet.Ethernet;
Amit Ghosh47243cb2017-07-26 05:08:53 +010052import org.onlab.packet.IPv4;
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +053053import org.onlab.packet.IpAddress;
Deepa vaddireddy0060f532017-08-04 06:46:05 +000054import org.onlab.packet.MacAddress;
Amit Ghosh47243cb2017-07-26 05:08:53 +010055import org.onlab.packet.TpPort;
56import org.onlab.packet.UDP;
57import org.onlab.packet.VlanId;
Jonathan Hartedbf6422018-05-02 17:30:05 -070058import org.onlab.packet.dhcp.DhcpOption;
Saurav Das45861d42020-10-07 00:03:23 -070059import org.onlab.packet.dhcp.DhcpRelayAgentOption;
Jonathan Hart617bc3e2020-02-14 10:42:23 -080060import org.onlab.util.KryoNamespace;
Amit Ghosh47243cb2017-07-26 05:08:53 +010061import org.onlab.util.Tools;
62import org.onosproject.cfg.ComponentConfigService;
Jonathan Hart617bc3e2020-02-14 10:42:23 -080063import org.onosproject.cluster.ClusterService;
64import org.onosproject.cluster.LeadershipService;
Andrea Campanella6f45a1b2020-05-08 17:50:12 +020065import org.onosproject.cluster.NodeId;
Amit Ghosh47243cb2017-07-26 05:08:53 +010066import org.onosproject.core.ApplicationId;
67import org.onosproject.core.CoreService;
Jonathan Hartc36c9552018-07-31 15:07:53 -040068import org.onosproject.event.AbstractListenerManager;
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +053069import org.onosproject.mastership.MastershipEvent;
70import org.onosproject.mastership.MastershipListener;
71import org.onosproject.mastership.MastershipService;
Amit Ghosh47243cb2017-07-26 05:08:53 +010072import org.onosproject.net.AnnotationKeys;
73import org.onosproject.net.ConnectPoint;
Amit Ghosh83c8c892017-11-09 11:08:27 +000074import org.onosproject.net.Device;
75import org.onosproject.net.DeviceId;
Amit Ghosh47243cb2017-07-26 05:08:53 +010076import org.onosproject.net.Host;
77import org.onosproject.net.Port;
Amit Ghosh83c8c892017-11-09 11:08:27 +000078import org.onosproject.net.PortNumber;
Amit Ghosh47243cb2017-07-26 05:08:53 +010079import org.onosproject.net.config.ConfigFactory;
80import org.onosproject.net.config.NetworkConfigEvent;
81import org.onosproject.net.config.NetworkConfigListener;
82import org.onosproject.net.config.NetworkConfigRegistry;
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +053083import org.onosproject.net.device.DeviceEvent;
84import org.onosproject.net.device.DeviceListener;
Amit Ghosh47243cb2017-07-26 05:08:53 +010085import org.onosproject.net.device.DeviceService;
86import org.onosproject.net.flow.DefaultTrafficSelector;
87import org.onosproject.net.flow.DefaultTrafficTreatment;
88import org.onosproject.net.flow.TrafficSelector;
89import org.onosproject.net.flow.TrafficTreatment;
Saurav Dasb4e3e102018-10-02 15:31:17 -070090import org.onosproject.net.flowobjective.FlowObjectiveService;
Amit Ghosh47243cb2017-07-26 05:08:53 +010091import org.onosproject.net.host.HostService;
92import org.onosproject.net.packet.DefaultOutboundPacket;
93import org.onosproject.net.packet.OutboundPacket;
94import org.onosproject.net.packet.PacketContext;
95import org.onosproject.net.packet.PacketPriority;
96import org.onosproject.net.packet.PacketProcessor;
97import org.onosproject.net.packet.PacketService;
Jonathan Hart617bc3e2020-02-14 10:42:23 -080098import org.onosproject.store.serializers.KryoNamespaces;
99import org.onosproject.store.service.ConsistentMap;
100import org.onosproject.store.service.Serializer;
101import org.onosproject.store.service.StorageService;
102import org.onosproject.store.service.Versioned;
Matteo Scandolo57af5d12019-04-29 17:11:41 -0700103import org.opencord.dhcpl2relay.DhcpAllocationInfo;
104import org.opencord.dhcpl2relay.DhcpL2RelayEvent;
105import org.opencord.dhcpl2relay.DhcpL2RelayListener;
106import org.opencord.dhcpl2relay.DhcpL2RelayService;
Jonathan Hart77ca3152020-02-21 14:31:21 -0800107import org.opencord.dhcpl2relay.DhcpL2RelayStoreDelegate;
Saurav Das45861d42020-10-07 00:03:23 -0700108import org.opencord.dhcpl2relay.impl.packet.DhcpOption82Data;
Gamze Abakac806c6c2018-12-03 12:49:46 +0000109import org.opencord.sadis.BaseInformationService;
110import org.opencord.sadis.SadisService;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100111import org.opencord.sadis.SubscriberAndDeviceInformation;
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000112import org.opencord.sadis.UniTagInformation;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100113import org.osgi.service.component.ComponentContext;
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700114import org.osgi.service.component.annotations.Activate;
115import org.osgi.service.component.annotations.Component;
116import org.osgi.service.component.annotations.Deactivate;
117import org.osgi.service.component.annotations.Modified;
118import org.osgi.service.component.annotations.Reference;
119import org.osgi.service.component.annotations.ReferenceCardinality;
Ilayda Ozdemir6b623ea2021-02-23 21:06:38 +0000120import org.osgi.service.component.annotations.ReferencePolicy;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100121import org.slf4j.Logger;
122import org.slf4j.LoggerFactory;
123
Saurav Das45861d42020-10-07 00:03:23 -0700124import com.google.common.base.Strings;
125import com.google.common.collect.ImmutableMap;
126import com.google.common.collect.ImmutableSet;
127import com.google.common.collect.Lists;
128import com.google.common.collect.Sets;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100129
130/**
131 * DHCP Relay Agent Application Component.
132 */
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700133@Component(immediate = true,
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700134 property = {
135 OPTION_82 + ":Boolean=" + OPTION_82_DEFAULT,
136 ENABLE_DHCP_BROADCAST_REPLIES + ":Boolean=" + ENABLE_DHCP_BROADCAST_REPLIES_DEFAULT,
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800137 PACKET_PROCESSOR_THREADS + ":Integer=" + PACKET_PROCESSOR_THREADS_DEFAULT,
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700138 })
Jonathan Hartc36c9552018-07-31 15:07:53 -0400139public class DhcpL2Relay
140 extends AbstractListenerManager<DhcpL2RelayEvent, DhcpL2RelayListener>
141 implements DhcpL2RelayService {
Ilayda Ozdemir6b623ea2021-02-23 21:06:38 +0000142 private static final String SADIS_NOT_RUNNING = "Sadis is not running.";
Amit Ghosh47243cb2017-07-26 05:08:53 +0100143 public static final String DHCP_L2RELAY_APP = "org.opencord.dhcpl2relay";
Saurav Dasb4e3e102018-10-02 15:31:17 -0700144 private static final String HOST_LOC_PROVIDER =
145 "org.onosproject.provider.host.impl.HostLocationProvider";
Amit Ghosh47243cb2017-07-26 05:08:53 +0100146 private final Logger log = LoggerFactory.getLogger(getClass());
147 private final InternalConfigListener cfgListener =
148 new InternalConfigListener();
149
150 private final Set<ConfigFactory> factories = ImmutableSet.of(
151 new ConfigFactory<ApplicationId, DhcpL2RelayConfig>(APP_SUBJECT_FACTORY,
Ilayda Ozdemir6b623ea2021-02-23 21:06:38 +0000152 DhcpL2RelayConfig.class,
153 "dhcpl2relay") {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100154 @Override
155 public DhcpL2RelayConfig createConfig() {
156 return new DhcpL2RelayConfig();
157 }
158 }
159 );
160
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700161 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh47243cb2017-07-26 05:08:53 +0100162 protected NetworkConfigRegistry cfgService;
163
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700164 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh47243cb2017-07-26 05:08:53 +0100165 protected CoreService coreService;
166
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700167 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh47243cb2017-07-26 05:08:53 +0100168 protected PacketService packetService;
169
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700170 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh47243cb2017-07-26 05:08:53 +0100171 protected HostService hostService;
172
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700173 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh47243cb2017-07-26 05:08:53 +0100174 protected ComponentConfigService componentConfigService;
175
Ilayda Ozdemir6b623ea2021-02-23 21:06:38 +0000176 @Reference(cardinality = ReferenceCardinality.OPTIONAL,
177 bind = "bindSadisService",
178 unbind = "unbindSadisService",
179 policy = ReferencePolicy.DYNAMIC)
180 protected volatile SadisService sadisService;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100181
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700182 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh47243cb2017-07-26 05:08:53 +0100183 protected DeviceService deviceService;
184
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700185 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh8951f042017-08-10 13:48:10 +0100186 protected MastershipService mastershipService;
187
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700188 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jonathan Hart617bc3e2020-02-14 10:42:23 -0800189 protected StorageService storageService;
190
191 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Saurav Dasb4e3e102018-10-02 15:31:17 -0700192 protected FlowObjectiveService flowObjectiveService;
193
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300194 @Reference(cardinality = ReferenceCardinality.MANDATORY)
195 protected DhcpL2RelayCountersStore dhcpL2RelayCounters;
196
Jonathan Hart617bc3e2020-02-14 10:42:23 -0800197 @Reference(cardinality = ReferenceCardinality.MANDATORY)
198 protected LeadershipService leadershipService;
199
200 @Reference(cardinality = ReferenceCardinality.MANDATORY)
201 protected ClusterService clusterService;
202
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300203 // OSGi Properties
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700204 /**
205 * Add option 82 to relayed packets.
206 */
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700207 protected boolean option82 = OPTION_82_DEFAULT;
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700208 /**
209 * Ask the DHCP Server to send back replies as L2 broadcast.
210 */
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700211 protected boolean enableDhcpBroadcastReplies = ENABLE_DHCP_BROADCAST_REPLIES_DEFAULT;
Amit Ghosha17354e2017-08-23 12:56:04 +0100212
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800213 /**
214 * Number of threads used to process the packet.
215 */
216 protected int packetProcessorThreads = PACKET_PROCESSOR_THREADS_DEFAULT;
217
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300218 ScheduledFuture<?> refreshTask;
219 ScheduledExecutorService refreshService = Executors.newSingleThreadScheduledExecutor();
220
Amit Ghosh47243cb2017-07-26 05:08:53 +0100221 private DhcpRelayPacketProcessor dhcpRelayPacketProcessor =
222 new DhcpRelayPacketProcessor();
223
Amit Ghosh8951f042017-08-10 13:48:10 +0100224 private InnerMastershipListener changeListener = new InnerMastershipListener();
225 private InnerDeviceListener deviceListener = new InnerDeviceListener();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100226
Amit Ghosh8951f042017-08-10 13:48:10 +0100227 // connect points to the DHCP server
228 Set<ConnectPoint> dhcpConnectPoints;
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300229 protected AtomicReference<ConnectPoint> dhcpServerConnectPoint = new AtomicReference<>();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100230 private MacAddress dhcpConnectMac = MacAddress.BROADCAST;
231 private ApplicationId appId;
232
Jonathan Hart617bc3e2020-02-14 10:42:23 -0800233 private ConsistentMap<String, DhcpAllocationInfo> allocations;
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300234 protected boolean modifyClientPktsSrcDstMac = false;
Amit Ghosh83c8c892017-11-09 11:08:27 +0000235 //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 -0300236 protected boolean useOltUplink = false;
Amit Ghosha17354e2017-08-23 12:56:04 +0100237
Gamze Abakac806c6c2018-12-03 12:49:46 +0000238 private BaseInformationService<SubscriberAndDeviceInformation> subsService;
239
Jonathan Hart77ca3152020-02-21 14:31:21 -0800240 private DhcpL2RelayStoreDelegate delegate = new InnerDhcpL2RelayStoreDelegate();
241
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800242 protected ExecutorService packetProcessorExecutor;
Tunahan Sezenad640f82021-04-28 10:23:44 +0000243 protected ExecutorService eventHandlerExecutor;
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800244
Amit Ghosh47243cb2017-07-26 05:08:53 +0100245 @Activate
246 protected void activate(ComponentContext context) {
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800247
Amit Ghosh47243cb2017-07-26 05:08:53 +0100248 //start the dhcp relay agent
249 appId = coreService.registerApplication(DHCP_L2RELAY_APP);
250 componentConfigService.registerProperties(getClass());
Jonathan Hartc36c9552018-07-31 15:07:53 -0400251 eventDispatcher.addSink(DhcpL2RelayEvent.class, listenerRegistry);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100252
Jonathan Hart617bc3e2020-02-14 10:42:23 -0800253 KryoNamespace serializer = KryoNamespace.newBuilder()
254 .register(KryoNamespaces.API)
255 .register(Instant.class)
256 .register(DHCP.MsgType.class)
257 .register(DhcpAllocationInfo.class)
258 .build();
259
260 allocations = storageService.<String, DhcpAllocationInfo>consistentMapBuilder()
261 .withName("dhcpl2relay-allocations")
262 .withSerializer(Serializer.using(serializer))
263 .withApplicationId(appId)
264 .build();
265
Jonathan Hart77ca3152020-02-21 14:31:21 -0800266 dhcpL2RelayCounters.setDelegate(delegate);
267
Tunahan Sezenad640f82021-04-28 10:23:44 +0000268 eventHandlerExecutor = newSingleThreadExecutor(groupedThreads("onos/dhcp", "dhcp-event-%d", log));
269
Amit Ghosh47243cb2017-07-26 05:08:53 +0100270 cfgService.addListener(cfgListener);
Amit Ghosh8951f042017-08-10 13:48:10 +0100271 mastershipService.addListener(changeListener);
272 deviceService.addListener(deviceListener);
273
Ilayda Ozdemir6b623ea2021-02-23 21:06:38 +0000274 if (sadisService != null) {
275 subsService = sadisService.getSubscriberInfoService();
276 } else {
277 log.warn(SADIS_NOT_RUNNING);
278 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100279 factories.forEach(cfgService::registerConfigFactory);
280 //update the dhcp server configuration.
281 updateConfig();
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800282
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000283 if (context != null) {
284 modified(context);
285 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100286
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800287 //add the packet services.
288 packetService.addProcessor(dhcpRelayPacketProcessor,
289 PacketProcessor.director(0));
290
Amit Ghosh47243cb2017-07-26 05:08:53 +0100291 log.info("DHCP-L2-RELAY Started");
292 }
293
294 @Deactivate
295 protected void deactivate() {
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300296 if (refreshTask != null) {
297 refreshTask.cancel(true);
298 }
299 if (refreshService != null) {
300 refreshService.shutdownNow();
301 }
Jonathan Hart77ca3152020-02-21 14:31:21 -0800302 dhcpL2RelayCounters.unsetDelegate(delegate);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100303 cfgService.removeListener(cfgListener);
304 factories.forEach(cfgService::unregisterConfigFactory);
305 packetService.removeProcessor(dhcpRelayPacketProcessor);
Saurav Dasb4e3e102018-10-02 15:31:17 -0700306 cancelDhcpPktsFromServer();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100307
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800308 packetProcessorExecutor.shutdown();
Tunahan Sezenad640f82021-04-28 10:23:44 +0000309 eventHandlerExecutor.shutdown();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100310 componentConfigService.unregisterProperties(getClass(), false);
Deepa Vaddireddy77a6ac72017-09-20 20:36:52 +0530311 deviceService.removeListener(deviceListener);
312 mastershipService.removeListener(changeListener);
Jonathan Hartc36c9552018-07-31 15:07:53 -0400313 eventDispatcher.removeSink(DhcpL2RelayEvent.class);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100314 log.info("DHCP-L2-RELAY Stopped");
315 }
316
317 @Modified
318 protected void modified(ComponentContext context) {
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000319
Amit Ghosh47243cb2017-07-26 05:08:53 +0100320 Dictionary<?, ?> properties = context.getProperties();
321
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700322 Boolean o = Tools.isPropertyEnabled(properties, OPTION_82);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100323 if (o != null) {
324 option82 = o;
325 }
Amit Ghosh2095dc62017-09-25 20:56:55 +0100326
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700327 o = Tools.isPropertyEnabled(properties, ENABLE_DHCP_BROADCAST_REPLIES);
Amit Ghosh2095dc62017-09-25 20:56:55 +0100328 if (o != null) {
329 enableDhcpBroadcastReplies = o;
330 }
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800331
332 String s = Tools.get(properties, PACKET_PROCESSOR_THREADS);
333 int oldpacketProcessorThreads = packetProcessorThreads;
334 packetProcessorThreads = Strings.isNullOrEmpty(s) ? oldpacketProcessorThreads
335 : Integer.parseInt(s.trim());
336 if (packetProcessorExecutor == null || oldpacketProcessorThreads != packetProcessorThreads) {
337 if (packetProcessorExecutor != null) {
338 packetProcessorExecutor.shutdown();
339 }
340 packetProcessorExecutor = newFixedThreadPool(packetProcessorThreads,
Ilayda Ozdemir6b623ea2021-02-23 21:06:38 +0000341 groupedThreads("onos/dhcp",
342 "dhcp-packet-%d", log));
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800343 }
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300344 }
345
Ilayda Ozdemir6b623ea2021-02-23 21:06:38 +0000346 protected void bindSadisService(SadisService service) {
347 sadisService = service;
348 subsService = sadisService.getSubscriberInfoService();
349 log.info("Sadis-service binds to onos.");
350 }
351
352 protected void unbindSadisService(SadisService service) {
353 sadisService = null;
354 subsService = null;
355 log.info("Sadis-service unbinds from onos.");
356 }
357
Jonathan Hart617bc3e2020-02-14 10:42:23 -0800358 @Override
359 public Map<String, DhcpAllocationInfo> getAllocationInfo() {
360 return ImmutableMap.copyOf(allocations.asJavaMap());
361 }
362
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300363 /**
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700364 * Generates a unique UUID from a string.
365 *
366 * @return true if all information we need have been initialized
367 */
368 private static String getUniqueUuidFromString(String value) {
369 return UUID.nameUUIDFromBytes(value.getBytes()).toString();
370 }
371
372 /**
Amit Ghosh47243cb2017-07-26 05:08:53 +0100373 * Checks if this app has been configured.
374 *
375 * @return true if all information we need have been initialized
376 */
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300377 protected boolean configured() {
Amit Ghosh83c8c892017-11-09 11:08:27 +0000378 if (!useOltUplink) {
379 return dhcpServerConnectPoint.get() != null;
380 }
381 return true;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100382 }
383
Amit Ghosh8951f042017-08-10 13:48:10 +0100384 /**
385 * Selects a connect point through an available device for which it is the master.
386 */
387 private void selectServerConnectPoint() {
388 synchronized (this) {
389 dhcpServerConnectPoint.set(null);
390 if (dhcpConnectPoints != null) {
391 // find a connect point through a device for which we are master
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700392 for (ConnectPoint cp : dhcpConnectPoints) {
Andrea Campanella6f45a1b2020-05-08 17:50:12 +0200393 if (isLocalLeader(cp.deviceId())) {
Amit Ghosh8951f042017-08-10 13:48:10 +0100394 if (deviceService.isAvailable(cp.deviceId())) {
395 dhcpServerConnectPoint.set(cp);
396 }
397 log.info("DHCP connectPoint selected is {}", cp);
398 break;
399 }
400 }
401 }
402
403 log.info("DHCP Server connectPoint is {}", dhcpServerConnectPoint.get());
404
405 if (dhcpServerConnectPoint.get() == null) {
406 log.error("Master of none, can't relay DHCP Message to server");
407 }
408 }
409 }
410
411 /**
412 * Updates the network configuration.
413 */
Amit Ghosh47243cb2017-07-26 05:08:53 +0100414 private void updateConfig() {
415 DhcpL2RelayConfig cfg = cfgService.getConfig(appId, DhcpL2RelayConfig.class);
416 if (cfg == null) {
417 log.warn("Dhcp Server info not available");
418 return;
419 }
Amit Ghosh8951f042017-08-10 13:48:10 +0100420
421 dhcpConnectPoints = Sets.newConcurrentHashSet(cfg.getDhcpServerConnectPoint());
Amit Ghosh83c8c892017-11-09 11:08:27 +0000422 modifyClientPktsSrcDstMac = cfg.getModifySrcDstMacAddresses();
Saurav Dasb4e3e102018-10-02 15:31:17 -0700423 boolean prevUseOltUplink = useOltUplink;
Amit Ghosh83c8c892017-11-09 11:08:27 +0000424 useOltUplink = cfg.getUseOltUplinkForServerPktInOut();
Amit Ghosh8951f042017-08-10 13:48:10 +0100425
Saurav Dasb4e3e102018-10-02 15:31:17 -0700426 if (useOltUplink) {
427 for (ConnectPoint cp : getUplinkPortsOfOlts()) {
428 log.debug("requestDhcpPackets: ConnectPoint: {}", cp);
Matteo Scandolo45e5a272019-09-30 09:30:32 -0700429 requestDhcpPacketsFromConnectPoint(cp, Optional.ofNullable(null));
Saurav Dasb4e3e102018-10-02 15:31:17 -0700430 }
431 // check if previous config was different and so trap flows may
Saurav Dasb14f08a2019-02-22 16:34:15 -0800432 // need to be removed from other places like AGG switches
Saurav Dasb4e3e102018-10-02 15:31:17 -0700433 if (!prevUseOltUplink) {
Saurav Dasb14f08a2019-02-22 16:34:15 -0800434 addOrRemoveDhcpTrapFromServer(false);
Saurav Dasb4e3e102018-10-02 15:31:17 -0700435 }
Saurav Dasb4e3e102018-10-02 15:31:17 -0700436 } else {
Saurav Dasb14f08a2019-02-22 16:34:15 -0800437 // uplink on AGG switch
438 addOrRemoveDhcpTrapFromServer(true);
Saurav Dasb4e3e102018-10-02 15:31:17 -0700439 }
440 }
441
442 private void cancelDhcpPktsFromServer() {
443 if (useOltUplink) {
444 for (ConnectPoint cp : getUplinkPortsOfOlts()) {
445 log.debug("cancelDhcpPackets: ConnectPoint: {}", cp);
Matteo Scandolo45e5a272019-09-30 09:30:32 -0700446 cancelDhcpPacketsFromConnectPoint(cp, Optional.ofNullable(null));
Saurav Dasb4e3e102018-10-02 15:31:17 -0700447 }
448 } else {
Saurav Dasb14f08a2019-02-22 16:34:15 -0800449 // uplink on AGG switch
450 addOrRemoveDhcpTrapFromServer(false);
Amit Ghosh83c8c892017-11-09 11:08:27 +0000451 }
Saurav Dasb4e3e102018-10-02 15:31:17 -0700452 }
453
Saurav Dasb14f08a2019-02-22 16:34:15 -0800454 /**
455 * Used to add or remove DHCP trap flow for packets received from DHCP server.
456 * Typically used on a non OLT device, like an AGG switch. When adding, a
457 * new dhcp server connect point is selected from the configured options.
458 *
459 * @param add true if dhcp trap flow is to be added, false to remove the
460 * trap flow
461 */
462 private void addOrRemoveDhcpTrapFromServer(boolean add) {
463 if (add) {
464 selectServerConnectPoint();
465 log.debug("dhcp server connect point: " + dhcpServerConnectPoint);
466 }
467 if (dhcpServerConnectPoint.get() == null) {
468 log.warn("No dhcpServer connectPoint found, cannot {} dhcp trap flows",
469 (add) ? "install" : "remove");
470 return;
471 }
472 if (add) {
473 log.info("Adding trap to dhcp server connect point: "
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700474 + dhcpServerConnectPoint);
Saurav Dasb14f08a2019-02-22 16:34:15 -0800475 requestDhcpPacketsFromConnectPoint(dhcpServerConnectPoint.get(),
476 Optional.of(PacketPriority.HIGH1));
477 } else {
478 log.info("Removing trap from dhcp server connect point: "
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700479 + dhcpServerConnectPoint);
Saurav Dasb14f08a2019-02-22 16:34:15 -0800480 cancelDhcpPacketsFromConnectPoint(dhcpServerConnectPoint.get(),
481 Optional.of(PacketPriority.HIGH1));
482 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100483 }
484
485 /**
Amit Ghosh83c8c892017-11-09 11:08:27 +0000486 * Returns all the uplink ports of OLTs configured in SADIS.
487 * Only ports visible in ONOS and for which this instance is master
488 * are returned
489 */
490 private List<ConnectPoint> getUplinkPortsOfOlts() {
Ilayda Ozdemir6b623ea2021-02-23 21:06:38 +0000491 if (subsService == null) {
492 log.warn(SADIS_NOT_RUNNING);
493 return Lists.newArrayList();
494 }
Amit Ghosh83c8c892017-11-09 11:08:27 +0000495 List<ConnectPoint> cps = new ArrayList<>();
496
497 // find all the olt devices and if their uplink ports are visible
498 Iterable<Device> devices = deviceService.getDevices();
499 for (Device d : devices) {
500 // check if this device is provisioned in Sadis
501
502 log.debug("getUplinkPortsOfOlts: Checking mastership of {}", d);
503 // do only for devices for which we are the master
Andrea Campanella6f45a1b2020-05-08 17:50:12 +0200504 if (!isLocalLeader(d.id())) {
Amit Ghosh83c8c892017-11-09 11:08:27 +0000505 continue;
506 }
507
508 String devSerialNo = d.serialNumber();
Ilayda Ozdemir6b623ea2021-02-23 21:06:38 +0000509 SubscriberAndDeviceInformation deviceInfo = getSubscriberAndDeviceInfo(devSerialNo);
Amit Ghosh83c8c892017-11-09 11:08:27 +0000510 log.debug("getUplinkPortsOfOlts: Found device: {}", deviceInfo);
511 if (deviceInfo != null) {
512 // check if the uplink port with that number is available on the device
513 PortNumber pNum = PortNumber.portNumber(deviceInfo.uplinkPort());
514 Port port = deviceService.getPort(d.id(), pNum);
515 log.debug("getUplinkPortsOfOlts: Found port: {}", port);
516 if (port != null) {
517 cps.add(new ConnectPoint(d.id(), pNum));
518 }
519 }
520 }
521 return cps;
522 }
523
Ilayda Ozdemir6b623ea2021-02-23 21:06:38 +0000524 private SubscriberAndDeviceInformation getSubscriberAndDeviceInfo(String portOrDevice) {
525 if (subsService == null) {
526 log.warn(SADIS_NOT_RUNNING);
527 return null;
528 }
529 return subsService.get(portOrDevice);
530 }
531
Amit Ghosh83c8c892017-11-09 11:08:27 +0000532 /**
533 * Returns whether the passed port is the uplink port of the olt device.
534 */
535 private boolean isUplinkPortOfOlt(DeviceId dId, Port p) {
536 log.debug("isUplinkPortOfOlt: DeviceId: {} Port: {}", dId, p);
Amit Ghosh83c8c892017-11-09 11:08:27 +0000537
538 Device d = deviceService.getDevice(dId);
Ilayda Ozdemir6b623ea2021-02-23 21:06:38 +0000539 SubscriberAndDeviceInformation deviceInfo = getSubscriberAndDeviceInfo(d.serialNumber());
Amit Ghosh83c8c892017-11-09 11:08:27 +0000540
541 if (deviceInfo != null) {
542 return (deviceInfo.uplinkPort() == p.number().toLong());
543 }
544
545 return false;
546 }
547
548 /**
549 * Returns the connectPoint which is the uplink port of the OLT.
550 */
551 private ConnectPoint getUplinkConnectPointOfOlt(DeviceId dId) {
552
553 Device d = deviceService.getDevice(dId);
Ilayda Ozdemir6b623ea2021-02-23 21:06:38 +0000554 SubscriberAndDeviceInformation deviceInfo = getSubscriberAndDeviceInfo(d.serialNumber());
Amit Ghosh83c8c892017-11-09 11:08:27 +0000555 log.debug("getUplinkConnectPointOfOlt DeviceId: {} devInfo: {}", dId, deviceInfo);
556 if (deviceInfo != null) {
557 PortNumber pNum = PortNumber.portNumber(deviceInfo.uplinkPort());
558 Port port = deviceService.getPort(d.id(), pNum);
559 if (port != null) {
560 return new ConnectPoint(d.id(), pNum);
561 }
562 }
563
564 return null;
565 }
566
567 /**
568 * Request DHCP packet from particular connect point via PacketService.
Saurav Dasb14f08a2019-02-22 16:34:15 -0800569 * Optionally provide a priority for the trap flow. If no such priority is
570 * provided, the default priority will be used.
571 *
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700572 * @param cp the connect point to trap dhcp packets from
Saurav Dasb14f08a2019-02-22 16:34:15 -0800573 * @param priority of the trap flow, null to use default priority
Amit Ghosh83c8c892017-11-09 11:08:27 +0000574 */
Saurav Dasb14f08a2019-02-22 16:34:15 -0800575 private void requestDhcpPacketsFromConnectPoint(ConnectPoint cp,
576 Optional<PacketPriority> priority) {
Amit Ghosh83c8c892017-11-09 11:08:27 +0000577 TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
578 .matchEthType(Ethernet.TYPE_IPV4)
579 .matchInPort(cp.port())
580 .matchIPProtocol(IPv4.PROTOCOL_UDP)
581 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
582 packetService.requestPackets(selectorServer.build(),
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700583 priority.isPresent() ? priority.get() : PacketPriority.CONTROL,
584 appId, Optional.of(cp.deviceId()));
Amit Ghosh83c8c892017-11-09 11:08:27 +0000585 }
586
587 /**
Saurav Dasb14f08a2019-02-22 16:34:15 -0800588 * Cancel DHCP packet from particular connect point via PacketService. If
589 * the request was made with a specific packet priority, then the same
590 * priority should be used in this call.
591 *
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700592 * @param cp the connect point for the trap flow
Saurav Dasb14f08a2019-02-22 16:34:15 -0800593 * @param priority with which the trap flow was requested; if request
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700594 * priority was not specified, this param should also be null
Amit Ghosh83c8c892017-11-09 11:08:27 +0000595 */
Saurav Dasb14f08a2019-02-22 16:34:15 -0800596 private void cancelDhcpPacketsFromConnectPoint(ConnectPoint cp,
597 Optional<PacketPriority> priority) {
Amit Ghosh83c8c892017-11-09 11:08:27 +0000598 TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
599 .matchEthType(Ethernet.TYPE_IPV4)
600 .matchInPort(cp.port())
601 .matchIPProtocol(IPv4.PROTOCOL_UDP)
602 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
603 packetService.cancelPackets(selectorServer.build(),
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700604 priority.isPresent() ? priority.get() : PacketPriority.CONTROL,
605 appId, Optional.of(cp.deviceId()));
Amit Ghosh83c8c892017-11-09 11:08:27 +0000606 }
607
Saurav Das45861d42020-10-07 00:03:23 -0700608 /**
609 * Main packet-processing engine for dhcp l2 relay agent.
610 */
Amit Ghosh47243cb2017-07-26 05:08:53 +0100611 private class DhcpRelayPacketProcessor implements PacketProcessor {
Saurav Das45861d42020-10-07 00:03:23 -0700612 private static final String VLAN_KEYWORD = ":vlan";
613 private static final String PCP_KEYWORD = ":pcp";
Amit Ghosh47243cb2017-07-26 05:08:53 +0100614
615 @Override
616 public void process(PacketContext context) {
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800617 packetProcessorExecutor.execute(() -> {
618 processInternal(context);
619 });
620 }
621
622 private void processInternal(PacketContext context) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100623 if (!configured()) {
624 log.warn("Missing DHCP relay config. Abort packet processing");
625 return;
626 }
627
628 // process the packet and get the payload
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530629 Ethernet packet = context.inPacket().parsed();
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000630
Amit Ghosh47243cb2017-07-26 05:08:53 +0100631 if (packet == null) {
632 log.warn("Packet is null");
633 return;
634 }
635
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530636 if (packet.getEtherType() == Ethernet.TYPE_IPV4) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100637 IPv4 ipv4Packet = (IPv4) packet.getPayload();
638
639 if (ipv4Packet.getProtocol() == IPv4.PROTOCOL_UDP) {
640 UDP udpPacket = (UDP) ipv4Packet.getPayload();
641 if (udpPacket.getSourcePort() == UDP.DHCP_CLIENT_PORT ||
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000642 udpPacket.getSourcePort() == UDP.DHCP_SERVER_PORT) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100643 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800644 if (log.isTraceEnabled()) {
645 log.trace("Processing packet with type {} from MAC {}",
646 getDhcpPacketType(dhcpPayload),
647 MacAddress.valueOf(dhcpPayload.getClientHardwareAddress()));
648 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100649 //This packet is dhcp.
650 processDhcpPacket(context, packet, dhcpPayload);
651 }
652 }
653 }
654 }
655
Saurav Dasbd5ce9c2020-09-04 18:46:45 -0700656 // process the dhcp packet before relaying to server or client
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530657 private void processDhcpPacket(PacketContext context, Ethernet packet,
Amit Ghosh47243cb2017-07-26 05:08:53 +0100658 DHCP dhcpPayload) {
659 if (dhcpPayload == null) {
660 log.warn("DHCP payload is null");
661 return;
662 }
663
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700664 DHCP.MsgType incomingPacketType = getDhcpPacketType(dhcpPayload);
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300665 if (incomingPacketType == null) {
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700666 log.warn("DHCP Packet type not found. Dump of ethernet pkt in hex format for troubleshooting.");
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300667 byte[] array = packet.serialize();
668 ByteArrayOutputStream buf = new ByteArrayOutputStream();
669 try {
670 HexDump.dump(array, 0, buf, 0);
671 log.trace(buf.toString());
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700672 } catch (Exception e) {
673 }
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300674 return;
675 }
676
677 SubscriberAndDeviceInformation entry = null;
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000678
Matteo Scandoloeb5a0dc2020-09-15 14:54:28 -0700679 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
680
Andrea Campanella6ef2b3f2021-04-01 10:40:44 +0200681 log.debug("Received DHCP Packet of type {} from {} with Client MacAddress {} and vlan {}",
Matteo Scandoloeb5a0dc2020-09-15 14:54:28 -0700682 incomingPacketType, context.inPacket().receivedFrom(),
683 clientMacAddress, packet.getVlanID());
Amit Ghosh47243cb2017-07-26 05:08:53 +0100684
685 switch (incomingPacketType) {
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000686 case DHCPDISCOVER:
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530687 Ethernet ethernetPacketDiscover =
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000688 processDhcpPacketFromClient(context, packet);
689 if (ethernetPacketDiscover != null) {
Saurav Das45861d42020-10-07 00:03:23 -0700690 relayPacketToServer(ethernetPacketDiscover, context);
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000691 }
Saurav Das45861d42020-10-07 00:03:23 -0700692 entry = getSubscriber(context);
Jonathan Hart77ca3152020-02-21 14:31:21 -0800693 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames.valueOf("DHCPDISCOVER"));
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000694 break;
695 case DHCPOFFER:
Saurav Das45861d42020-10-07 00:03:23 -0700696 RelayToClientInfo r2cDataOffer =
Saurav Das15626a02018-09-27 18:36:45 -0700697 processDhcpPacketFromServer(context, packet);
Saurav Das45861d42020-10-07 00:03:23 -0700698 if (r2cDataOffer != null) {
699 relayPacketToClient(r2cDataOffer, clientMacAddress);
700 entry = getSubscriber(r2cDataOffer.cp);
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000701 }
Jonathan Hart77ca3152020-02-21 14:31:21 -0800702 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames.valueOf("DHCPOFFER"));
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000703 break;
704 case DHCPREQUEST:
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530705 Ethernet ethernetPacketRequest =
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000706 processDhcpPacketFromClient(context, packet);
707 if (ethernetPacketRequest != null) {
Saurav Das45861d42020-10-07 00:03:23 -0700708 relayPacketToServer(ethernetPacketRequest, context);
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000709 }
Saurav Das45861d42020-10-07 00:03:23 -0700710 entry = getSubscriber(context);
Jonathan Hart77ca3152020-02-21 14:31:21 -0800711 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames.valueOf("DHCPREQUEST"));
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000712 break;
713 case DHCPACK:
Saurav Das45861d42020-10-07 00:03:23 -0700714 RelayToClientInfo r2cDataAck =
Saurav Das15626a02018-09-27 18:36:45 -0700715 processDhcpPacketFromServer(context, packet);
Saurav Das45861d42020-10-07 00:03:23 -0700716 if (r2cDataAck != null) {
717 relayPacketToClient(r2cDataAck, clientMacAddress);
718 entry = getSubscriber(r2cDataAck.cp);
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000719 }
Jonathan Hart77ca3152020-02-21 14:31:21 -0800720 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames.valueOf("DHCPACK"));
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300721 break;
722 case DHCPDECLINE:
Arjun E K05ad20b2020-03-13 13:25:17 +0000723 Ethernet ethernetPacketDecline =
724 processDhcpPacketFromClient(context, packet);
725 if (ethernetPacketDecline != null) {
Saurav Das45861d42020-10-07 00:03:23 -0700726 relayPacketToServer(ethernetPacketDecline, context);
Arjun E K05ad20b2020-03-13 13:25:17 +0000727 }
Saurav Das45861d42020-10-07 00:03:23 -0700728 entry = getSubscriber(context);
Jonathan Hart77ca3152020-02-21 14:31:21 -0800729 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames.valueOf("DHCPDECLINE"));
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300730 break;
731 case DHCPNAK:
Saurav Das45861d42020-10-07 00:03:23 -0700732 RelayToClientInfo r2cDataNack =
Arjun E K05ad20b2020-03-13 13:25:17 +0000733 processDhcpPacketFromServer(context, packet);
Saurav Das45861d42020-10-07 00:03:23 -0700734 if (r2cDataNack != null) {
735 relayPacketToClient(r2cDataNack, clientMacAddress);
736 entry = getSubscriber(r2cDataNack.cp);
Arjun E K05ad20b2020-03-13 13:25:17 +0000737 }
Jonathan Hart77ca3152020-02-21 14:31:21 -0800738 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames.valueOf("DHCPNACK"));
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300739 break;
740 case DHCPRELEASE:
Thomas Lee S0dc9a3b2020-01-14 10:42:29 +0530741 Ethernet ethernetPacketRelease =
742 processDhcpPacketFromClient(context, packet);
743 if (ethernetPacketRelease != null) {
Saurav Das45861d42020-10-07 00:03:23 -0700744 relayPacketToServer(ethernetPacketRelease, context);
Thomas Lee S0dc9a3b2020-01-14 10:42:29 +0530745 }
Saurav Das45861d42020-10-07 00:03:23 -0700746 entry = getSubscriber(context);
Jonathan Hart77ca3152020-02-21 14:31:21 -0800747 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames.valueOf("DHCPRELEASE"));
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000748 break;
749 default:
750 break;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100751 }
752 }
753
Saurav Das45861d42020-10-07 00:03:23 -0700754 /**
755 * Processes dhcp packets from clients.
756 *
757 * @param context the packet context
758 * @param ethernetPacket the dhcp packet from client
759 * @return the packet to relay to the server
760 */
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530761 private Ethernet processDhcpPacketFromClient(PacketContext context,
762 Ethernet ethernetPacket) {
Saurav Das15626a02018-09-27 18:36:45 -0700763 if (log.isTraceEnabled()) {
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700764 log.trace("DHCP Packet received from client at {} {}",
Saurav Das15626a02018-09-27 18:36:45 -0700765 context.inPacket().receivedFrom(), ethernetPacket);
766 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100767
pier9e0efbe2020-10-28 20:33:56 +0100768 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100769
770 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
771 UDP udpPacket = (UDP) ipv4Packet.getPayload();
772 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
Saurav Dasbd5ce9c2020-09-04 18:46:45 -0700773 ConnectPoint inPort = context.inPacket().receivedFrom();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100774
Amit Ghosha17354e2017-08-23 12:56:04 +0100775 if (enableDhcpBroadcastReplies) {
776 // We want the reply to come back as a L2 broadcast
777 dhcpPacket.setFlags((short) 0x8000);
778 }
779
Jonathan Hartc36c9552018-07-31 15:07:53 -0400780 MacAddress clientMac = MacAddress.valueOf(dhcpPacket.getClientHardwareAddress());
Saurav Dasbd5ce9c2020-09-04 18:46:45 -0700781 VlanId clientVlan = VlanId.vlanId(ethernetPacket.getVlanID());
Jonathan Hartc36c9552018-07-31 15:07:53 -0400782 IpAddress clientIp = IpAddress.valueOf(dhcpPacket.getClientIPAddress());
Amit Ghosha17354e2017-08-23 12:56:04 +0100783
Jonathan Hartc36c9552018-07-31 15:07:53 -0400784 SubscriberAndDeviceInformation entry = getSubscriber(context);
785 if (entry == null) {
Saurav Das15626a02018-09-27 18:36:45 -0700786 log.warn("Dropping packet as subscriber entry is not available");
Jonathan Hartc36c9552018-07-31 15:07:53 -0400787 return null;
788 }
789
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000790 UniTagInformation uniTagInformation = getUnitagInformationFromPacketContext(context, entry);
791 if (uniTagInformation == null) {
792 log.warn("Missing service information for connectPoint {} / cTag {}",
Saurav Dasbd5ce9c2020-09-04 18:46:45 -0700793 inPort, clientVlan);
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000794 return null;
795 }
Saurav Das45861d42020-10-07 00:03:23 -0700796 DhcpOption82Data d82 = null;
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000797 if (option82) {
Saurav Das45861d42020-10-07 00:03:23 -0700798 DHCP dhcpPacketWithOption82 = addOption82(dhcpPacket, entry,
799 inPort, clientVlan,
800 uniTagInformation
801 .getDsPonCTagPriority());
802 byte[] d82b = dhcpPacketWithOption82
803 .getOption(DHCP.DHCPOptionCode.OptionCode_CircuitID)
804 .getData();
805 d82 = new DhcpOption82Data(d82b);
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000806 udpPacket.setPayload(dhcpPacketWithOption82);
807 }
808
809 ipv4Packet.setPayload(udpPacket);
810 etherReply.setPayload(ipv4Packet);
Amit Ghosh83c8c892017-11-09 11:08:27 +0000811 if (modifyClientPktsSrcDstMac) {
Andrea Campanellad3d7f2c2021-06-16 18:47:56 +0200812 MacAddress relayAgentMac = relayAgentMacAddress(context);
813 if (relayAgentMac == null) {
814 log.warn("Rewriting src MAC failed because OLT MAC not found for {} in sadis. " +
815 "Packet is dropped, can't act as RelayAgent",
816 context.inPacket().receivedFrom().deviceId());
817 return null;
818 }
Amit Ghosh83c8c892017-11-09 11:08:27 +0000819 etherReply.setSourceMACAddress(relayAgentMac);
820 etherReply.setDestinationMACAddress(dhcpConnectMac);
821 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100822
Amit Ghosh8951f042017-08-10 13:48:10 +0100823 etherReply.setPriorityCode(ethernetPacket.getPriorityCode());
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000824 etherReply.setVlanID(uniTagInformation.getPonCTag().toShort());
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530825 etherReply.setQinQTPID(Ethernet.TYPE_VLAN);
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000826 etherReply.setQinQVID(uniTagInformation.getPonSTag().toShort());
827 if (uniTagInformation.getUsPonSTagPriority() != -1) {
828 etherReply.setQinQPriorityCode((byte) uniTagInformation.getUsPonSTagPriority());
829 }
Saurav Das45861d42020-10-07 00:03:23 -0700830 if (uniTagInformation.getUsPonCTagPriority() != -1) {
831 etherReply.setPriorityCode((byte) uniTagInformation
832 .getUsPonCTagPriority());
833 }
834
835 DhcpAllocationInfo info = new DhcpAllocationInfo(inPort,
836 dhcpPacket.getPacketType(),
837 (d82 == null)
838 ? entry.circuitId()
839 : d82.getAgentCircuitId(),
840 clientMac, clientIp,
841 clientVlan, entry.id());
842 String key = getUniqueUuidFromString(entry.id() + clientMac
843 + clientVlan);
844 allocations.put(key, info);
845 post(new DhcpL2RelayEvent(DhcpL2RelayEvent.Type.UPDATED, info, inPort));
846 if (log.isTraceEnabled()) {
Andrea Campanellad79a6792020-10-26 14:44:46 +0100847 log.trace("Finished processing DHCP Packet of type {} with MAC {} from {} "
Saurav Das45861d42020-10-07 00:03:23 -0700848 + "... relaying to dhcpServer",
Andrea Campanellad79a6792020-10-26 14:44:46 +0100849 dhcpPacket.getPacketType(), clientMac, entry.id());
Saurav Das45861d42020-10-07 00:03:23 -0700850 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100851 return etherReply;
852 }
853
Saurav Das45861d42020-10-07 00:03:23 -0700854 /**
855 * Processes dhcp packets from the server.
856 *
857 * @param context the packet context
858 * @param ethernetPacket the dhcp packet
859 * @return returns information necessary for relaying packet to client
860 */
861 private RelayToClientInfo processDhcpPacketFromServer(PacketContext context,
862 Ethernet ethernetPacket) {
Saurav Das15626a02018-09-27 18:36:45 -0700863 if (log.isTraceEnabled()) {
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700864 log.trace("DHCP Packet received from server at {} {}",
Saurav Das15626a02018-09-27 18:36:45 -0700865 context.inPacket().receivedFrom(), ethernetPacket);
866 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100867 // get dhcp header.
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530868 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100869 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
870 UDP udpPacket = (UDP) ipv4Packet.getPayload();
Saurav Das45861d42020-10-07 00:03:23 -0700871 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
Saurav Dasbd5ce9c2020-09-04 18:46:45 -0700872 VlanId innerVlan = VlanId.vlanId(ethernetPacket.getVlanID());
Saurav Das45861d42020-10-07 00:03:23 -0700873 MacAddress dstMac = valueOf(dhcpPacket.getClientHardwareAddress());
Amit Ghosh47243cb2017-07-26 05:08:53 +0100874
Saurav Das45861d42020-10-07 00:03:23 -0700875 // we leave the srcMac from the original packet.
876 // TODO remove S-VLAN
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000877 etherReply.setQinQVID(VlanId.NO_VID);
878 etherReply.setQinQPriorityCode((byte) 0);
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530879 etherReply.setDestinationMACAddress(dstMac);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100880
Saurav Das45861d42020-10-07 00:03:23 -0700881 // TODO deserialization of dhcp option82 leaves 'data' field null
882 // As a result we need to retrieve suboption data
883 RelayToClientInfo r2cData = null;
884 boolean usedOption82 = false;
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000885 if (option82) {
Saurav Das45861d42020-10-07 00:03:23 -0700886 // retrieve connectPoint and vlan from option82, if it is in expected format
887 DhcpOption opt = dhcpPacket
888 .getOption(DHCP.DHCPOptionCode.OptionCode_CircuitID);
889 if (opt != null && opt instanceof DhcpRelayAgentOption) {
890 DhcpRelayAgentOption d82 = (DhcpRelayAgentOption) opt;
891 DhcpOption d82ckt = d82.getSubOption(DhcpOption82Data.CIRCUIT_ID_CODE);
Andrea Campanellaaab2dae2022-03-17 16:01:12 +0100892 if (d82ckt != null && d82ckt.getData() != null) {
Saurav Das45861d42020-10-07 00:03:23 -0700893 r2cData = decodeCircuitId(new String(d82ckt.getData()));
894 }
895 }
896 if (r2cData != null) {
897 usedOption82 = true;
898 etherReply.setVlanID(r2cData.cvid.toShort());
899 if (r2cData.pcp != -1) {
900 etherReply.setPriorityCode((byte) r2cData.pcp);
901 }
902 }
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000903 }
Saurav Das45861d42020-10-07 00:03:23 -0700904 // always remove option82 if present
905 DHCP remDhcpPacket = removeOption82(dhcpPacket);
906 udpPacket.setPayload(remDhcpPacket);
907
Amit Ghosh47243cb2017-07-26 05:08:53 +0100908 ipv4Packet.setPayload(udpPacket);
909 etherReply.setPayload(ipv4Packet);
910
Saurav Das45861d42020-10-07 00:03:23 -0700911 if (!usedOption82) {
912 // option 82 data not present or not used, we need to
913 // lookup host store with client dstmac and vlan from context
914 r2cData = new RelayToClientInfo();
915 r2cData.cp = getConnectPointOfClient(dstMac, context);
916 if (r2cData.cp == null) {
917 log.warn("Couldn't find subscriber, service or host info for mac"
918 + " address {} and vlan {} .. DHCP packet can't be"
919 + " delivered to client", dstMac, innerVlan);
920 return null;
921 }
922 }
923
924 // always need the subscriber entry
925 SubscriberAndDeviceInformation entry = getSubscriber(r2cData.cp);
926 if (entry == null) {
927 log.warn("Couldn't find subscriber info for cp {}.. DHCP packet"
928 + " can't be delivered to client mac {} and vlan {}",
929 r2cData.cp, dstMac, innerVlan);
930 return null;
931 }
932
933 if (!usedOption82) {
934 UniTagInformation uniTagInformation =
935 getUnitagInformationFromPacketContext(context, entry);
936 if (uniTagInformation == null) {
937 log.warn("Missing service information for connectPoint {} "
938 + " cTag {} .. DHCP packet can't be delivered to client",
939 r2cData.cp, innerVlan);
940 return null;
941 }
942 r2cData.cvid = uniTagInformation.getPonCTag();
943 r2cData.pcp = uniTagInformation.getDsPonCTagPriority();
944 r2cData.cktId = entry.circuitId();
945 etherReply.setVlanID(r2cData.cvid.toShort());
946 if (r2cData.pcp != -1) {
947 etherReply.setPriorityCode((byte) r2cData.pcp);
948 }
949 }
950
951 // update stats and events
952 IpAddress ip = IpAddress.valueOf(dhcpPacket.getYourIPAddress());
953 DhcpAllocationInfo info =
954 new DhcpAllocationInfo(r2cData.cp, dhcpPacket.getPacketType(),
955 r2cData.cktId, dstMac, ip, innerVlan,
956 entry.id());
957 String key = getUniqueUuidFromString(entry.id() + info.macAddress()
958 + innerVlan);
959 allocations.put(key, info);
960 post(new DhcpL2RelayEvent(DhcpL2RelayEvent.Type.UPDATED, info,
961 r2cData.cp));
962 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames
963 .valueOf("PACKETS_FROM_SERVER"));
964 if (log.isTraceEnabled()) {
965 log.trace("Finished processing packet.. relaying to client at {}",
966 r2cData.cp);
967 }
968 r2cData.ethernetPkt = etherReply;
969 return r2cData;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100970 }
971
Saurav Das45861d42020-10-07 00:03:23 -0700972 // forward the packet to ConnectPoint where the DHCP server is attached.
973 private void relayPacketToServer(Ethernet packet, PacketContext context) {
Andrea Campanellad79a6792020-10-26 14:44:46 +0100974 SubscriberAndDeviceInformation entry = getSubscriber(context);
Saurav Das45861d42020-10-07 00:03:23 -0700975 if (log.isTraceEnabled()) {
976 IPv4 ipv4Packet = (IPv4) packet.getPayload();
977 UDP udpPacket = (UDP) ipv4Packet.getPayload();
978 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
Andrea Campanellad79a6792020-10-26 14:44:46 +0100979 log.trace("Emitting packet to server: packet {}, with MAC {} from {}",
Saurav Das45861d42020-10-07 00:03:23 -0700980 getDhcpPacketType(dhcpPayload),
Andrea Campanellad79a6792020-10-26 14:44:46 +0100981 MacAddress.valueOf(dhcpPayload.getClientHardwareAddress()),
982 entry.id());
Saurav Das45861d42020-10-07 00:03:23 -0700983 }
984 ConnectPoint toSendTo = null;
985 if (!useOltUplink) {
986 toSendTo = dhcpServerConnectPoint.get();
987 } else {
988 toSendTo = getUplinkConnectPointOfOlt(context.inPacket().receivedFrom()
989 .deviceId());
990 }
991
992 if (toSendTo != null) {
993 TrafficTreatment t = DefaultTrafficTreatment.builder()
994 .setOutput(toSendTo.port()).build();
995 OutboundPacket o = new DefaultOutboundPacket(toSendTo
996 .deviceId(), t, ByteBuffer.wrap(packet.serialize()));
997 if (log.isTraceEnabled()) {
998 log.trace("Relaying packet to dhcp server at {} {}", toSendTo,
999 packet);
1000 }
1001 packetService.emit(o);
1002
Saurav Das45861d42020-10-07 00:03:23 -07001003 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames
1004 .valueOf("PACKETS_TO_SERVER"));
1005 } else {
1006 log.error("No connect point to send msg to DHCP Server");
1007 }
1008 }
1009
1010 // send the response to the requester host (client)
1011 private void relayPacketToClient(RelayToClientInfo r2cData,
1012 MacAddress dstMac) {
1013 ConnectPoint subCp = r2cData.cp;
1014 Ethernet ethPacket = r2cData.ethernetPkt;
1015 // Send packet out to requester if the host information is available
1016 if (subCp != null) {
1017 TrafficTreatment t = DefaultTrafficTreatment.builder()
1018 .setOutput(subCp.port()).build();
1019 OutboundPacket o = new DefaultOutboundPacket(subCp.deviceId(),
1020 t, ByteBuffer.wrap(ethPacket.serialize()));
1021 if (log.isTraceEnabled()) {
1022 log.trace("Relaying packet to DHCP client at {} with "
1023 + "MacAddress {}, {} given {}", subCp, dstMac,
1024 ethPacket, r2cData);
1025 }
1026 packetService.emit(o);
1027 } else {
1028 log.error("Dropping DHCP Packet because unknown connectPoint for {}",
1029 dstMac);
1030 }
1031 }
1032
1033 /**
1034 * Option 82 includes circuitId and remoteId data configured by an
1035 * operator in sadis for a subscriber, and can be a string in any form
1036 * relevant to the operator's dhcp-server. When circuitId is configured
1037 * in sadis, the relay agent adds the option, but does not use the
1038 * information for forwarding packets back to client.
1039 * <p>
1040 * If circuitId is not configured in sadis, this relay-agent adds
1041 * circuitId information in the form
1042 * "{@literal<}connectPoint>:vlan{@literal<}clientVlanId>:pcp{@literal<}downstreamPcp>"
1043 * for example, "of:0000000000000001/32:vlan200:pcp7". When the packet
1044 * is received back from the server with circuitId in this form, this
1045 * relay agent will use this information to forward packets to the
1046 * client.
1047 *
1048 * @param dhcpPacket the DHCP packet to transform
1049 * @param entry sadis information for the subscriber
1050 * @param cp the connectPoint to set if sadis entry has no circuitId
1051 * @param clientVlan the vlan to set if sadis entry has no circuitId
1052 * @param downstreamPbits the pbits to set if sadis entry has no
1053 * circuitId
1054 * @return the modified dhcp packet with option82 added
Amit Ghosha17354e2017-08-23 12:56:04 +01001055 */
Saurav Das45861d42020-10-07 00:03:23 -07001056 private DHCP addOption82(DHCP dhcpPacket, SubscriberAndDeviceInformation entry,
1057 ConnectPoint cp, VlanId clientVlan,
1058 int downstreamPbits) {
1059 List<DhcpOption> options = Lists.newArrayList(dhcpPacket.getOptions());
1060 DhcpOption82Data option82 = new DhcpOption82Data();
1061 if (entry.circuitId() == null || entry.circuitId().isBlank()) {
1062 option82.setAgentCircuitId(cp + VLAN_KEYWORD + clientVlan
1063 + PCP_KEYWORD
1064 + downstreamPbits);
1065 } else {
1066 option82.setAgentCircuitId(entry.circuitId());
1067 }
Saurav Dasd935f452020-10-29 14:34:53 -07001068 if (entry.remoteId() != null && !entry.remoteId().isBlank()) {
1069 option82.setAgentRemoteId(entry.remoteId());
1070 }
Saurav Das45861d42020-10-07 00:03:23 -07001071 if (log.isTraceEnabled()) {
1072 log.trace("adding option82 {} ", option82);
1073 }
1074 DhcpOption option = new DhcpOption()
1075 .setCode(DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue())
1076 .setData(option82.toByteArray())
1077 .setLength(option82.length());
1078
1079 options.add(options.size() - 1, option);
1080 dhcpPacket.setOptions(options);
1081
1082 return dhcpPacket;
1083 }
1084
1085 private DHCP removeOption82(DHCP dhcpPacket) {
1086 List<DhcpOption> options = dhcpPacket.getOptions();
1087 List<DhcpOption> newoptions = options.stream()
1088 .filter(option -> option
1089 .getCode() != DHCP.DHCPOptionCode.OptionCode_CircuitID
1090 .getValue())
1091 .collect(Collectors.toList());
1092
1093 return dhcpPacket.setOptions(newoptions);
1094 }
1095
1096 /**
1097 * Returns the circuit Id values decoded from the option 82 data. Decoding
1098 * is performed if and only if the circuit id format is in the form
1099 * "{@literal<}connectPoint>:vlan{@literal<}clientVlanId>:pcp{@literal<}downstreamPcp>"
1100 *
1101 * @param cktId the circuitId string from option 82 data
1102 * @return decoded circuit id data if it is in the expected format or
1103 * null
1104 */
1105 private RelayToClientInfo decodeCircuitId(String cktId) {
1106 if (cktId.contains(VLAN_KEYWORD) && cktId.contains(PCP_KEYWORD)) {
1107 ConnectPoint cp = ConnectPoint
1108 .fromString(cktId
1109 .substring(0, cktId.indexOf(VLAN_KEYWORD)));
1110 VlanId cvid = VlanId
1111 .vlanId(cktId.substring(
1112 cktId.indexOf(VLAN_KEYWORD)
1113 + VLAN_KEYWORD.length(),
1114 cktId.indexOf(PCP_KEYWORD)));
1115 int pcp = Integer
1116 .valueOf(cktId.substring(cktId.indexOf(PCP_KEYWORD)
1117 + PCP_KEYWORD.length()))
1118 .intValue();
1119 log.debug("retrieved from option82-> cp={} cvlan={} down-pcp={}"
1120 + " for relaying to client ", cp, cvid, pcp);
1121 return new RelayToClientInfo(cp, cvid, pcp, cktId);
1122 } else {
1123 log.debug("Option 82 circuitId {} is operator defined and will "
1124 + "not be used for forwarding", cktId);
1125 return null;
1126 }
1127 }
1128
1129 private class RelayToClientInfo {
1130 Ethernet ethernetPkt;
1131 ConnectPoint cp;
1132 VlanId cvid;
1133 int pcp;
1134 String cktId;
1135
1136 public RelayToClientInfo(ConnectPoint cp, VlanId cvid, int pcp,
1137 String cktId) {
1138 this.cp = cp;
1139 this.cvid = cvid;
1140 this.pcp = pcp;
1141 this.cktId = cktId;
1142 }
1143
1144 public RelayToClientInfo() {
1145 }
1146
1147 @Override
1148 public String toString() {
1149 return "RelayToClientInfo: {connectPoint=" + cp + " clientVlan="
1150 + cvid + " clientPcp=" + pcp + " circuitId=" + cktId + "}";
1151 }
1152
1153 }
1154
1155 // get the type of the DHCP packet
1156 private DHCP.MsgType getDhcpPacketType(DHCP dhcpPayload) {
1157 for (DhcpOption option : dhcpPayload.getOptions()) {
1158 if (option.getCode() == OptionCode_MessageType.getValue()) {
1159 byte[] data = option.getData();
1160 return DHCP.MsgType.getType(data[0]);
1161 }
1162 }
1163 return null;
1164 }
1165
1166 private void updateDhcpRelayCountersStore(SubscriberAndDeviceInformation entry,
1167 DhcpL2RelayCounterNames counterType) {
1168 // Update global counter stats
1169 dhcpL2RelayCounters.incrementCounter(DhcpL2RelayEvent.GLOBAL_COUNTER,
1170 counterType);
1171 if (entry == null) {
1172 log.warn("Counter not updated as subscriber info not found.");
1173 } else {
1174 // Update subscriber counter stats
1175 dhcpL2RelayCounters.incrementCounter(entry.id(), counterType);
1176 }
1177 }
1178
1179 /**
1180 * Get subscriber information based on subscriber's connectPoint.
1181 *
1182 * @param subsCp the subscriber's connectPoint
1183 * @return subscriber sadis info or null if not found
1184 */
1185 private SubscriberAndDeviceInformation getSubscriber(ConnectPoint subsCp) {
1186 if (subsCp != null) {
1187 String portName = getPortName(subsCp);
Ilayda Ozdemir6b623ea2021-02-23 21:06:38 +00001188 return getSubscriberAndDeviceInfo(portName);
Saurav Das45861d42020-10-07 00:03:23 -07001189 }
1190 return null;
1191 }
1192
1193 /**
1194 * Returns sadis info for subscriber based on incoming packet context.
1195 * The packet context must refer to a packet coming from a subscriber
1196 * port.
1197 *
1198 * @param context incoming packet context from subscriber port (UNI)
1199 * @return sadis info for the subscriber or null
1200 */
1201 private SubscriberAndDeviceInformation getSubscriber(PacketContext context) {
1202 String portName = getPortName(context.inPacket().receivedFrom());
Ilayda Ozdemir6b623ea2021-02-23 21:06:38 +00001203 return getSubscriberAndDeviceInfo(portName);
Saurav Das45861d42020-10-07 00:03:23 -07001204 }
1205
1206 /**
1207 * Returns ConnectPoint of the Client based on MAC address and C-VLAN.
1208 * Verifies that returned connect point has service defined in sadis.
1209 *
1210 * @param dstMac client dstMac
1211 * @param context context for incoming packet, parsed for C-vlan id
1212 * @return connect point information for client or null if connect point
1213 * not found or service cannot be verified for client info
1214 */
1215 private ConnectPoint getConnectPointOfClient(MacAddress dstMac,
1216 PacketContext context) {
Amit Ghosha17354e2017-08-23 12:56:04 +01001217 Set<Host> hosts = hostService.getHostsByMac(dstMac);
1218 if (hosts == null || hosts.isEmpty()) {
1219 log.warn("Cannot determine host for DHCP client: {}. Aborting "
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001220 + "relay for DHCP Packet from server", dstMac);
Amit Ghosha17354e2017-08-23 12:56:04 +01001221 return null;
1222 }
1223 for (Host h : hosts) {
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001224 // if more than one (for example, multiple services with same
1225 // mac-address but different service VLANs (inner/C vlans)
Amit Ghosha17354e2017-08-23 12:56:04 +01001226 // find the connect point which has an valid entry in SADIS
1227 ConnectPoint cp = new ConnectPoint(h.location().deviceId(),
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001228 h.location().port());
Amit Ghosha17354e2017-08-23 12:56:04 +01001229
Saurav Das45861d42020-10-07 00:03:23 -07001230 SubscriberAndDeviceInformation sub = getSubscriber(cp);
Gamze Abakaa64b3bc2020-01-31 06:51:43 +00001231 if (sub == null) {
Saurav Das45861d42020-10-07 00:03:23 -07001232 log.warn("Subscriber info not found for {} for host {}", cp, h);
1233 continue;
Amit Ghosha17354e2017-08-23 12:56:04 +01001234 }
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001235 // check for cvlan in subscriber's uniTagInfo list
Saurav Das45861d42020-10-07 00:03:23 -07001236 UniTagInformation uniTagInformation =
1237 getUnitagInformationFromPacketContext(context, sub);
Jonathan Hartb4fbc922020-04-14 12:17:44 -07001238 if (uniTagInformation != null) {
1239 return cp;
Gamze Abakaa64b3bc2020-01-31 06:51:43 +00001240 }
Gamze Abakaa64b3bc2020-01-31 06:51:43 +00001241 }
Jonathan Hartb4fbc922020-04-14 12:17:44 -07001242 // no sadis config found for this connectPoint/vlan
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001243 log.warn("Missing service information for dhcp packet received from"
1244 + " {} with cTag {} .. cannot relay to client",
Saurav Das45861d42020-10-07 00:03:23 -07001245 context.inPacket().receivedFrom(),
1246 context.inPacket().parsed().getVlanID());
1247 return null;
1248 }
1249
1250 /**
1251 * Returns the port-name for the given connectPoint port.
1252 *
1253 * @param cp the given connect point
1254 * @return the port-name for the connect point port
1255 */
1256 private String getPortName(ConnectPoint cp) {
1257 Port p = deviceService.getPort(cp);
1258 return p.annotations().value(AnnotationKeys.PORT_NAME);
1259 }
1260
1261 /**
1262 * Return's uniTagInformation (service information) if incoming packet's
1263 * client VLAN id matches the subscriber's service info, and dhcp is
1264 * required for this service.
1265 *
1266 * @param context
1267 * @param sub
1268 * @return
1269 */
1270 private UniTagInformation getUnitagInformationFromPacketContext(PacketContext context,
1271 SubscriberAndDeviceInformation sub) {
1272 // If the ctag is defined in the tagList and dhcp is required,
1273 // return the service info
1274 List<UniTagInformation> tagList = sub.uniTagList();
1275 for (UniTagInformation uniServiceInformation : tagList) {
1276 if (uniServiceInformation.getPonCTag().toShort() == context.inPacket()
1277 .parsed().getVlanID()) {
1278 if (uniServiceInformation.getIsDhcpRequired()) {
1279 return uniServiceInformation;
1280 }
1281 }
1282 }
Jonathan Hartb4fbc922020-04-14 12:17:44 -07001283
Amit Ghosha17354e2017-08-23 12:56:04 +01001284 return null;
1285 }
1286
Saurav Das45861d42020-10-07 00:03:23 -07001287
1288 private MacAddress relayAgentMacAddress(PacketContext context) {
1289 SubscriberAndDeviceInformation device = this.getDevice(context);
1290 if (device == null) {
Andrea Campanellad3d7f2c2021-06-16 18:47:56 +02001291 log.warn("Information for Device {} not found in Sadis", context.inPacket().receivedFrom());
Saurav Das45861d42020-10-07 00:03:23 -07001292 return null;
Amit Ghosh47243cb2017-07-26 05:08:53 +01001293 }
Saurav Das45861d42020-10-07 00:03:23 -07001294 return device.hardwareIdentifier();
Amit Ghosh47243cb2017-07-26 05:08:53 +01001295 }
Amit Ghosh47243cb2017-07-26 05:08:53 +01001296
Saurav Das45861d42020-10-07 00:03:23 -07001297 /**
1298 * Returns sadis information for device from which packet was received.
1299 *
1300 * @param context the packet context
1301 * @return sadis information for device
1302 */
1303 private SubscriberAndDeviceInformation getDevice(PacketContext context) {
1304 String serialNo = deviceService
1305 .getDevice(context.inPacket().receivedFrom().deviceId())
1306 .serialNumber();
Ilayda Ozdemir6b623ea2021-02-23 21:06:38 +00001307 return getSubscriberAndDeviceInfo(serialNo);
Saurav Das45861d42020-10-07 00:03:23 -07001308 }
Deepa vaddireddy0060f532017-08-04 06:46:05 +00001309
Deepa vaddireddy0060f532017-08-04 06:46:05 +00001310 }
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001311
Amit Ghosh47243cb2017-07-26 05:08:53 +01001312 /**
1313 * Listener for network config events.
1314 */
1315 private class InternalConfigListener implements NetworkConfigListener {
1316
1317 @Override
1318 public void event(NetworkConfigEvent event) {
Tunahan Sezenad640f82021-04-28 10:23:44 +00001319 eventHandlerExecutor.submit(() -> handleNetworkConfigEventEvent(event));
1320 }
Amit Ghosh47243cb2017-07-26 05:08:53 +01001321
Tunahan Sezenad640f82021-04-28 10:23:44 +00001322 private void handleNetworkConfigEventEvent(NetworkConfigEvent event) {
Amit Ghosh47243cb2017-07-26 05:08:53 +01001323 if ((event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
1324 event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED) &&
1325 event.configClass().equals(DhcpL2RelayConfig.class)) {
1326 updateConfig();
1327 log.info("Reconfigured");
1328 }
1329 }
1330 }
Deepa vaddireddy0060f532017-08-04 06:46:05 +00001331
Amit Ghosh8951f042017-08-10 13:48:10 +01001332 /**
1333 * Handles Mastership changes for the devices which connect
1334 * to the DHCP server.
1335 */
1336 private class InnerMastershipListener implements MastershipListener {
1337 @Override
1338 public void event(MastershipEvent event) {
Tunahan Sezenad640f82021-04-28 10:23:44 +00001339 eventHandlerExecutor.submit(() -> handleMastershipEvent(event));
1340 }
1341
1342 private void handleMastershipEvent(MastershipEvent event) {
Amit Ghosh83c8c892017-11-09 11:08:27 +00001343 if (!useOltUplink) {
1344 if (dhcpServerConnectPoint.get() != null &&
1345 dhcpServerConnectPoint.get().deviceId().
1346 equals(event.subject())) {
Tunahan Sezenad640f82021-04-28 10:23:44 +00001347 log.trace("Mastership Event received for {}", event.subject());
Amit Ghosh83c8c892017-11-09 11:08:27 +00001348 // mastership of the device for our connect point has changed
1349 // reselect
1350 selectServerConnectPoint();
1351 }
Amit Ghosh8951f042017-08-10 13:48:10 +01001352 }
1353 }
1354 }
Deepa vaddireddy0060f532017-08-04 06:46:05 +00001355
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001356 private void removeAllocations(Predicate<Map.Entry<String, Versioned<DhcpAllocationInfo>>> pred) {
1357 allocations.stream()
1358 .filter(pred)
1359 .map(Map.Entry::getKey)
1360 .collect(Collectors.toList())
1361 .forEach(allocations::remove);
1362 }
1363
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001364 @Override
Matteo Scandoloab346512020-04-17 13:39:55 -07001365 public void clearAllocations() {
1366 allocations.clear();
1367 }
1368
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001369 @Override
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001370 public boolean removeAllocationsByConnectPoint(ConnectPoint cp) {
1371 boolean removed = false;
Matteo Scandoloab346512020-04-17 13:39:55 -07001372 for (String key : allocations.keySet()) {
1373 DhcpAllocationInfo entry = allocations.asJavaMap().get(key);
1374 if (entry.location().equals(cp)) {
1375 allocations.remove(key);
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001376 removed = true;
Matteo Scandoloab346512020-04-17 13:39:55 -07001377 }
1378 }
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001379 return removed;
Matteo Scandoloab346512020-04-17 13:39:55 -07001380 }
1381
Andrea Campanella6f45a1b2020-05-08 17:50:12 +02001382 /**
1383 * Checks for mastership or falls back to leadership on deviceId.
Andrea Campanelladed7fe72021-05-06 12:58:00 +02001384 * If the device is available use mastership,
1385 * otherwise fallback on leadership.
Andrea Campanella6f45a1b2020-05-08 17:50:12 +02001386 * Leadership on the device topic is needed because the master can be NONE
1387 * in case the device went away, we still need to handle events
1388 * consistently
1389 */
1390 private boolean isLocalLeader(DeviceId deviceId) {
Andrea Campanelladed7fe72021-05-06 12:58:00 +02001391 if (deviceService.isAvailable(deviceId)) {
1392 return mastershipService.isLocalMaster(deviceId);
1393 } else {
Andrea Campanella6f45a1b2020-05-08 17:50:12 +02001394 // Fallback with Leadership service - device id is used as topic
1395 NodeId leader = leadershipService.runForLeadership(
1396 deviceId.toString()).leaderNodeId();
1397 // Verify if this node is the leader
1398 return clusterService.getLocalNode().id().equals(leader);
1399 }
Andrea Campanella6f45a1b2020-05-08 17:50:12 +02001400 }
1401
Amit Ghosh8951f042017-08-10 13:48:10 +01001402 /**
1403 * Handles Device status change for the devices which connect
1404 * to the DHCP server.
1405 */
1406 private class InnerDeviceListener implements DeviceListener {
1407 @Override
1408 public void event(DeviceEvent event) {
Tunahan Sezenad640f82021-04-28 10:23:44 +00001409 eventHandlerExecutor.submit(() -> handleDeviceEvent(event));
1410 }
1411
1412 private void handleDeviceEvent(DeviceEvent event) {
Andrea Campanella6f45a1b2020-05-08 17:50:12 +02001413 final DeviceId deviceId = event.subject().id();
1414
1415 // Ensure only one instance handles the event
1416 if (!isLocalLeader(deviceId)) {
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001417 return;
1418 }
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001419 // ignore stats
1420 if (event.type().equals(DeviceEvent.Type.PORT_STATS_UPDATED)) {
1421 return;
1422 }
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001423
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001424 log.debug("Device Event received for {} event {}", event.subject(),
1425 event.type());
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001426
Thomas Lee S9df15082019-12-23 11:31:15 +05301427 switch (event.type()) {
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001428 case DEVICE_REMOVED:
1429 log.info("Device removed {}", event.subject().id());
1430 removeAllocations(e -> e.getValue().value().location().deviceId().equals(deviceId));
1431 break;
Thomas Lee S9df15082019-12-23 11:31:15 +05301432 case DEVICE_AVAILABILITY_CHANGED:
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001433 boolean available = deviceService.isAvailable(deviceId);
1434 log.info("Device Avail Changed {} to {}", event.subject().id(), available);
1435
1436 if (!available && deviceService.getPorts(deviceId).isEmpty()) {
1437 removeAllocations(e -> e.getValue().value().location().deviceId().equals(deviceId));
1438 log.info("Device {} is removed from DHCP allocationmap ", deviceId);
Thomas Lee S9df15082019-12-23 11:31:15 +05301439 }
1440 break;
Thomas Lee S6b77ad22020-01-10 11:27:43 +05301441 case PORT_REMOVED:
1442 Port port = event.port();
Thomas Lee S6b77ad22020-01-10 11:27:43 +05301443 log.info("Port {} is deleted on device {}", port, deviceId);
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001444
1445 ConnectPoint cp = new ConnectPoint(deviceId, port.number());
1446 removeAllocations(e -> e.getValue().value().location().equals(cp));
1447
Thomas Lee S6b77ad22020-01-10 11:27:43 +05301448 log.info("Port {} on device {} is removed from DHCP allocationmap", event.port(), deviceId);
1449 break;
Thomas Lee S9df15082019-12-23 11:31:15 +05301450 default:
1451 break;
1452 }
Amit Ghosh83c8c892017-11-09 11:08:27 +00001453 if (!useOltUplink) {
1454 if (dhcpServerConnectPoint.get() == null) {
1455 switch (event.type()) {
1456 case DEVICE_ADDED:
1457 case DEVICE_AVAILABILITY_CHANGED:
Saurav Dasb14f08a2019-02-22 16:34:15 -08001458 // some device is available check if we can get a
1459 // connect point we can use
1460 addOrRemoveDhcpTrapFromServer(true);
Amit Ghosh83c8c892017-11-09 11:08:27 +00001461 break;
1462 default:
1463 break;
1464 }
1465 return;
Amit Ghosh8951f042017-08-10 13:48:10 +01001466 }
Amit Ghosh83c8c892017-11-09 11:08:27 +00001467 if (dhcpServerConnectPoint.get().deviceId().
1468 equals(event.subject().id())) {
1469 switch (event.type()) {
1470 case DEVICE_AVAILABILITY_CHANGED:
1471 case DEVICE_REMOVED:
1472 case DEVICE_SUSPENDED:
1473 // state of our device has changed, check if we need
Saurav Dasb14f08a2019-02-22 16:34:15 -08001474 // to re-select a connectpoint
1475 addOrRemoveDhcpTrapFromServer(true);
Amit Ghosh83c8c892017-11-09 11:08:27 +00001476 break;
1477 default:
1478 break;
1479 }
1480 }
1481 } else {
Amit Ghosh8951f042017-08-10 13:48:10 +01001482 switch (event.type()) {
Amit Ghosh83c8c892017-11-09 11:08:27 +00001483 case PORT_ADDED:
Saurav Dasb4e3e102018-10-02 15:31:17 -07001484 if (useOltUplink && isUplinkPortOfOlt(event.subject().id(), event.port())) {
Saurav Dasb14f08a2019-02-22 16:34:15 -08001485 requestDhcpPacketsFromConnectPoint(
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001486 new ConnectPoint(event.subject().id(), event.port().number()),
1487 Optional.empty());
Amit Ghosh83c8c892017-11-09 11:08:27 +00001488 }
Amit Ghosh8951f042017-08-10 13:48:10 +01001489 break;
1490 default:
1491 break;
1492 }
1493 }
1494 }
1495 }
Jonathan Hart77ca3152020-02-21 14:31:21 -08001496
1497 private class InnerDhcpL2RelayStoreDelegate implements DhcpL2RelayStoreDelegate {
1498 @Override
1499 public void notify(DhcpL2RelayEvent event) {
1500 if (event.type().equals(DhcpL2RelayEvent.Type.STATS_UPDATE)) {
1501 DhcpL2RelayEvent toPost = event;
1502 if (event.getSubscriberId() != null) {
1503 // infuse the event with the allocation info before posting
1504 DhcpAllocationInfo info = Versioned.valueOrNull(allocations.get(event.getSubscriberId()));
1505 toPost = new DhcpL2RelayEvent(event.type(), info, event.connectPoint(),
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001506 event.getCountersEntry(), event.getSubscriberId());
Jonathan Hart77ca3152020-02-21 14:31:21 -08001507 }
1508 post(toPost);
1509 }
1510
1511 }
1512 }
Amit Ghosh47243cb2017-07-26 05:08:53 +01001513}