blob: 84765a9ea4ff9fb2aa15dd32b3b867b76f574d2d [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;
Ilayda Ozdemir6b623ea2021-02-23 21:06:38 +0000119import org.osgi.service.component.annotations.ReferencePolicy;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100120import org.slf4j.Logger;
121import org.slf4j.LoggerFactory;
122
Saurav Das45861d42020-10-07 00:03:23 -0700123import com.google.common.base.Strings;
124import com.google.common.collect.ImmutableMap;
125import com.google.common.collect.ImmutableSet;
126import com.google.common.collect.Lists;
127import com.google.common.collect.Sets;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100128
129/**
130 * DHCP Relay Agent Application Component.
131 */
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700132@Component(immediate = true,
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700133 property = {
134 OPTION_82 + ":Boolean=" + OPTION_82_DEFAULT,
135 ENABLE_DHCP_BROADCAST_REPLIES + ":Boolean=" + ENABLE_DHCP_BROADCAST_REPLIES_DEFAULT,
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800136 PACKET_PROCESSOR_THREADS + ":Integer=" + PACKET_PROCESSOR_THREADS_DEFAULT,
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700137 })
Jonathan Hartc36c9552018-07-31 15:07:53 -0400138public class DhcpL2Relay
139 extends AbstractListenerManager<DhcpL2RelayEvent, DhcpL2RelayListener>
140 implements DhcpL2RelayService {
Ilayda Ozdemir6b623ea2021-02-23 21:06:38 +0000141 private static final String SADIS_NOT_RUNNING = "Sadis is not running.";
Amit Ghosh47243cb2017-07-26 05:08:53 +0100142 public static final String DHCP_L2RELAY_APP = "org.opencord.dhcpl2relay";
Saurav Dasb4e3e102018-10-02 15:31:17 -0700143 private static final String HOST_LOC_PROVIDER =
144 "org.onosproject.provider.host.impl.HostLocationProvider";
Amit Ghosh47243cb2017-07-26 05:08:53 +0100145 private final Logger log = LoggerFactory.getLogger(getClass());
146 private final InternalConfigListener cfgListener =
147 new InternalConfigListener();
148
149 private final Set<ConfigFactory> factories = ImmutableSet.of(
150 new ConfigFactory<ApplicationId, DhcpL2RelayConfig>(APP_SUBJECT_FACTORY,
Ilayda Ozdemir6b623ea2021-02-23 21:06:38 +0000151 DhcpL2RelayConfig.class,
152 "dhcpl2relay") {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100153 @Override
154 public DhcpL2RelayConfig createConfig() {
155 return new DhcpL2RelayConfig();
156 }
157 }
158 );
159
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700160 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh47243cb2017-07-26 05:08:53 +0100161 protected NetworkConfigRegistry cfgService;
162
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700163 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh47243cb2017-07-26 05:08:53 +0100164 protected CoreService coreService;
165
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700166 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh47243cb2017-07-26 05:08:53 +0100167 protected PacketService packetService;
168
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700169 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh47243cb2017-07-26 05:08:53 +0100170 protected HostService hostService;
171
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700172 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh47243cb2017-07-26 05:08:53 +0100173 protected ComponentConfigService componentConfigService;
174
Ilayda Ozdemir6b623ea2021-02-23 21:06:38 +0000175 @Reference(cardinality = ReferenceCardinality.OPTIONAL,
176 bind = "bindSadisService",
177 unbind = "unbindSadisService",
178 policy = ReferencePolicy.DYNAMIC)
179 protected volatile SadisService sadisService;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100180
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700181 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh47243cb2017-07-26 05:08:53 +0100182 protected DeviceService deviceService;
183
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700184 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh8951f042017-08-10 13:48:10 +0100185 protected MastershipService mastershipService;
186
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700187 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jonathan Hart617bc3e2020-02-14 10:42:23 -0800188 protected StorageService storageService;
189
190 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Saurav Dasb4e3e102018-10-02 15:31:17 -0700191 protected FlowObjectiveService flowObjectiveService;
192
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300193 @Reference(cardinality = ReferenceCardinality.MANDATORY)
194 protected DhcpL2RelayCountersStore dhcpL2RelayCounters;
195
Jonathan Hart617bc3e2020-02-14 10:42:23 -0800196 @Reference(cardinality = ReferenceCardinality.MANDATORY)
197 protected LeadershipService leadershipService;
198
199 @Reference(cardinality = ReferenceCardinality.MANDATORY)
200 protected ClusterService clusterService;
201
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300202 // OSGi Properties
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700203 /**
204 * Add option 82 to relayed packets.
205 */
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700206 protected boolean option82 = OPTION_82_DEFAULT;
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700207 /**
208 * Ask the DHCP Server to send back replies as L2 broadcast.
209 */
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700210 protected boolean enableDhcpBroadcastReplies = ENABLE_DHCP_BROADCAST_REPLIES_DEFAULT;
Amit Ghosha17354e2017-08-23 12:56:04 +0100211
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800212 /**
213 * Number of threads used to process the packet.
214 */
215 protected int packetProcessorThreads = PACKET_PROCESSOR_THREADS_DEFAULT;
216
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300217 ScheduledFuture<?> refreshTask;
218 ScheduledExecutorService refreshService = Executors.newSingleThreadScheduledExecutor();
219
Amit Ghosh47243cb2017-07-26 05:08:53 +0100220 private DhcpRelayPacketProcessor dhcpRelayPacketProcessor =
221 new DhcpRelayPacketProcessor();
222
Amit Ghosh8951f042017-08-10 13:48:10 +0100223 private InnerMastershipListener changeListener = new InnerMastershipListener();
224 private InnerDeviceListener deviceListener = new InnerDeviceListener();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100225
Amit Ghosh8951f042017-08-10 13:48:10 +0100226 // connect points to the DHCP server
227 Set<ConnectPoint> dhcpConnectPoints;
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300228 protected AtomicReference<ConnectPoint> dhcpServerConnectPoint = new AtomicReference<>();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100229 private MacAddress dhcpConnectMac = MacAddress.BROADCAST;
230 private ApplicationId appId;
231
Jonathan Hart617bc3e2020-02-14 10:42:23 -0800232 private ConsistentMap<String, DhcpAllocationInfo> allocations;
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300233 protected boolean modifyClientPktsSrcDstMac = false;
Amit Ghosh83c8c892017-11-09 11:08:27 +0000234 //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 -0300235 protected boolean useOltUplink = false;
Amit Ghosha17354e2017-08-23 12:56:04 +0100236
Gamze Abakac806c6c2018-12-03 12:49:46 +0000237 private BaseInformationService<SubscriberAndDeviceInformation> subsService;
238
Jonathan Hart77ca3152020-02-21 14:31:21 -0800239 private DhcpL2RelayStoreDelegate delegate = new InnerDhcpL2RelayStoreDelegate();
240
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800241 protected ExecutorService packetProcessorExecutor;
242
Amit Ghosh47243cb2017-07-26 05:08:53 +0100243 @Activate
244 protected void activate(ComponentContext context) {
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800245
Amit Ghosh47243cb2017-07-26 05:08:53 +0100246 //start the dhcp relay agent
247 appId = coreService.registerApplication(DHCP_L2RELAY_APP);
248 componentConfigService.registerProperties(getClass());
Jonathan Hartc36c9552018-07-31 15:07:53 -0400249 eventDispatcher.addSink(DhcpL2RelayEvent.class, listenerRegistry);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100250
Jonathan Hart617bc3e2020-02-14 10:42:23 -0800251 KryoNamespace serializer = KryoNamespace.newBuilder()
252 .register(KryoNamespaces.API)
253 .register(Instant.class)
254 .register(DHCP.MsgType.class)
255 .register(DhcpAllocationInfo.class)
256 .build();
257
258 allocations = storageService.<String, DhcpAllocationInfo>consistentMapBuilder()
259 .withName("dhcpl2relay-allocations")
260 .withSerializer(Serializer.using(serializer))
261 .withApplicationId(appId)
262 .build();
263
Jonathan Hart77ca3152020-02-21 14:31:21 -0800264 dhcpL2RelayCounters.setDelegate(delegate);
265
Amit Ghosh47243cb2017-07-26 05:08:53 +0100266 cfgService.addListener(cfgListener);
Amit Ghosh8951f042017-08-10 13:48:10 +0100267 mastershipService.addListener(changeListener);
268 deviceService.addListener(deviceListener);
269
Ilayda Ozdemir6b623ea2021-02-23 21:06:38 +0000270 if (sadisService != null) {
271 subsService = sadisService.getSubscriberInfoService();
272 } else {
273 log.warn(SADIS_NOT_RUNNING);
274 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100275 factories.forEach(cfgService::registerConfigFactory);
276 //update the dhcp server configuration.
277 updateConfig();
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800278
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000279 if (context != null) {
280 modified(context);
281 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100282
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800283 //add the packet services.
284 packetService.addProcessor(dhcpRelayPacketProcessor,
285 PacketProcessor.director(0));
286
Amit Ghosh47243cb2017-07-26 05:08:53 +0100287 log.info("DHCP-L2-RELAY Started");
288 }
289
290 @Deactivate
291 protected void deactivate() {
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300292 if (refreshTask != null) {
293 refreshTask.cancel(true);
294 }
295 if (refreshService != null) {
296 refreshService.shutdownNow();
297 }
Jonathan Hart77ca3152020-02-21 14:31:21 -0800298 dhcpL2RelayCounters.unsetDelegate(delegate);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100299 cfgService.removeListener(cfgListener);
300 factories.forEach(cfgService::unregisterConfigFactory);
301 packetService.removeProcessor(dhcpRelayPacketProcessor);
Saurav Dasb4e3e102018-10-02 15:31:17 -0700302 cancelDhcpPktsFromServer();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100303
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800304 packetProcessorExecutor.shutdown();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100305 componentConfigService.unregisterProperties(getClass(), false);
Deepa Vaddireddy77a6ac72017-09-20 20:36:52 +0530306 deviceService.removeListener(deviceListener);
307 mastershipService.removeListener(changeListener);
Jonathan Hartc36c9552018-07-31 15:07:53 -0400308 eventDispatcher.removeSink(DhcpL2RelayEvent.class);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100309 log.info("DHCP-L2-RELAY Stopped");
310 }
311
312 @Modified
313 protected void modified(ComponentContext context) {
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000314
Amit Ghosh47243cb2017-07-26 05:08:53 +0100315 Dictionary<?, ?> properties = context.getProperties();
316
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700317 Boolean o = Tools.isPropertyEnabled(properties, OPTION_82);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100318 if (o != null) {
319 option82 = o;
320 }
Amit Ghosh2095dc62017-09-25 20:56:55 +0100321
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700322 o = Tools.isPropertyEnabled(properties, ENABLE_DHCP_BROADCAST_REPLIES);
Amit Ghosh2095dc62017-09-25 20:56:55 +0100323 if (o != null) {
324 enableDhcpBroadcastReplies = o;
325 }
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800326
327 String s = Tools.get(properties, PACKET_PROCESSOR_THREADS);
328 int oldpacketProcessorThreads = packetProcessorThreads;
329 packetProcessorThreads = Strings.isNullOrEmpty(s) ? oldpacketProcessorThreads
330 : Integer.parseInt(s.trim());
331 if (packetProcessorExecutor == null || oldpacketProcessorThreads != packetProcessorThreads) {
332 if (packetProcessorExecutor != null) {
333 packetProcessorExecutor.shutdown();
334 }
335 packetProcessorExecutor = newFixedThreadPool(packetProcessorThreads,
Ilayda Ozdemir6b623ea2021-02-23 21:06:38 +0000336 groupedThreads("onos/dhcp",
337 "dhcp-packet-%d", log));
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800338 }
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300339 }
340
Ilayda Ozdemir6b623ea2021-02-23 21:06:38 +0000341 protected void bindSadisService(SadisService service) {
342 sadisService = service;
343 subsService = sadisService.getSubscriberInfoService();
344 log.info("Sadis-service binds to onos.");
345 }
346
347 protected void unbindSadisService(SadisService service) {
348 sadisService = null;
349 subsService = null;
350 log.info("Sadis-service unbinds from onos.");
351 }
352
Jonathan Hart617bc3e2020-02-14 10:42:23 -0800353 @Override
354 public Map<String, DhcpAllocationInfo> getAllocationInfo() {
355 return ImmutableMap.copyOf(allocations.asJavaMap());
356 }
357
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300358 /**
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700359 * Generates a unique UUID from a string.
360 *
361 * @return true if all information we need have been initialized
362 */
363 private static String getUniqueUuidFromString(String value) {
364 return UUID.nameUUIDFromBytes(value.getBytes()).toString();
365 }
366
367 /**
Amit Ghosh47243cb2017-07-26 05:08:53 +0100368 * Checks if this app has been configured.
369 *
370 * @return true if all information we need have been initialized
371 */
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300372 protected boolean configured() {
Amit Ghosh83c8c892017-11-09 11:08:27 +0000373 if (!useOltUplink) {
374 return dhcpServerConnectPoint.get() != null;
375 }
376 return true;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100377 }
378
Amit Ghosh8951f042017-08-10 13:48:10 +0100379 /**
380 * Selects a connect point through an available device for which it is the master.
381 */
382 private void selectServerConnectPoint() {
383 synchronized (this) {
384 dhcpServerConnectPoint.set(null);
385 if (dhcpConnectPoints != null) {
386 // find a connect point through a device for which we are master
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700387 for (ConnectPoint cp : dhcpConnectPoints) {
Andrea Campanella6f45a1b2020-05-08 17:50:12 +0200388 if (isLocalLeader(cp.deviceId())) {
Amit Ghosh8951f042017-08-10 13:48:10 +0100389 if (deviceService.isAvailable(cp.deviceId())) {
390 dhcpServerConnectPoint.set(cp);
391 }
392 log.info("DHCP connectPoint selected is {}", cp);
393 break;
394 }
395 }
396 }
397
398 log.info("DHCP Server connectPoint is {}", dhcpServerConnectPoint.get());
399
400 if (dhcpServerConnectPoint.get() == null) {
401 log.error("Master of none, can't relay DHCP Message to server");
402 }
403 }
404 }
405
406 /**
407 * Updates the network configuration.
408 */
Amit Ghosh47243cb2017-07-26 05:08:53 +0100409 private void updateConfig() {
410 DhcpL2RelayConfig cfg = cfgService.getConfig(appId, DhcpL2RelayConfig.class);
411 if (cfg == null) {
412 log.warn("Dhcp Server info not available");
413 return;
414 }
Amit Ghosh8951f042017-08-10 13:48:10 +0100415
416 dhcpConnectPoints = Sets.newConcurrentHashSet(cfg.getDhcpServerConnectPoint());
Amit Ghosh83c8c892017-11-09 11:08:27 +0000417 modifyClientPktsSrcDstMac = cfg.getModifySrcDstMacAddresses();
Saurav Dasb4e3e102018-10-02 15:31:17 -0700418 boolean prevUseOltUplink = useOltUplink;
Amit Ghosh83c8c892017-11-09 11:08:27 +0000419 useOltUplink = cfg.getUseOltUplinkForServerPktInOut();
Amit Ghosh8951f042017-08-10 13:48:10 +0100420
Saurav Dasb4e3e102018-10-02 15:31:17 -0700421 if (useOltUplink) {
422 for (ConnectPoint cp : getUplinkPortsOfOlts()) {
423 log.debug("requestDhcpPackets: ConnectPoint: {}", cp);
Matteo Scandolo45e5a272019-09-30 09:30:32 -0700424 requestDhcpPacketsFromConnectPoint(cp, Optional.ofNullable(null));
Saurav Dasb4e3e102018-10-02 15:31:17 -0700425 }
426 // check if previous config was different and so trap flows may
Saurav Dasb14f08a2019-02-22 16:34:15 -0800427 // need to be removed from other places like AGG switches
Saurav Dasb4e3e102018-10-02 15:31:17 -0700428 if (!prevUseOltUplink) {
Saurav Dasb14f08a2019-02-22 16:34:15 -0800429 addOrRemoveDhcpTrapFromServer(false);
Saurav Dasb4e3e102018-10-02 15:31:17 -0700430 }
Saurav Dasb4e3e102018-10-02 15:31:17 -0700431 } else {
Saurav Dasb14f08a2019-02-22 16:34:15 -0800432 // uplink on AGG switch
433 addOrRemoveDhcpTrapFromServer(true);
Saurav Dasb4e3e102018-10-02 15:31:17 -0700434 }
435 }
436
437 private void cancelDhcpPktsFromServer() {
438 if (useOltUplink) {
439 for (ConnectPoint cp : getUplinkPortsOfOlts()) {
440 log.debug("cancelDhcpPackets: ConnectPoint: {}", cp);
Matteo Scandolo45e5a272019-09-30 09:30:32 -0700441 cancelDhcpPacketsFromConnectPoint(cp, Optional.ofNullable(null));
Saurav Dasb4e3e102018-10-02 15:31:17 -0700442 }
443 } else {
Saurav Dasb14f08a2019-02-22 16:34:15 -0800444 // uplink on AGG switch
445 addOrRemoveDhcpTrapFromServer(false);
Amit Ghosh83c8c892017-11-09 11:08:27 +0000446 }
Saurav Dasb4e3e102018-10-02 15:31:17 -0700447 }
448
Saurav Dasb14f08a2019-02-22 16:34:15 -0800449 /**
450 * Used to add or remove DHCP trap flow for packets received from DHCP server.
451 * Typically used on a non OLT device, like an AGG switch. When adding, a
452 * new dhcp server connect point is selected from the configured options.
453 *
454 * @param add true if dhcp trap flow is to be added, false to remove the
455 * trap flow
456 */
457 private void addOrRemoveDhcpTrapFromServer(boolean add) {
458 if (add) {
459 selectServerConnectPoint();
460 log.debug("dhcp server connect point: " + dhcpServerConnectPoint);
461 }
462 if (dhcpServerConnectPoint.get() == null) {
463 log.warn("No dhcpServer connectPoint found, cannot {} dhcp trap flows",
464 (add) ? "install" : "remove");
465 return;
466 }
467 if (add) {
468 log.info("Adding trap to dhcp server connect point: "
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700469 + dhcpServerConnectPoint);
Saurav Dasb14f08a2019-02-22 16:34:15 -0800470 requestDhcpPacketsFromConnectPoint(dhcpServerConnectPoint.get(),
471 Optional.of(PacketPriority.HIGH1));
472 } else {
473 log.info("Removing trap from dhcp server connect point: "
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700474 + dhcpServerConnectPoint);
Saurav Dasb14f08a2019-02-22 16:34:15 -0800475 cancelDhcpPacketsFromConnectPoint(dhcpServerConnectPoint.get(),
476 Optional.of(PacketPriority.HIGH1));
477 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100478 }
479
480 /**
Amit Ghosh83c8c892017-11-09 11:08:27 +0000481 * Returns all the uplink ports of OLTs configured in SADIS.
482 * Only ports visible in ONOS and for which this instance is master
483 * are returned
484 */
485 private List<ConnectPoint> getUplinkPortsOfOlts() {
Ilayda Ozdemir6b623ea2021-02-23 21:06:38 +0000486 if (subsService == null) {
487 log.warn(SADIS_NOT_RUNNING);
488 return Lists.newArrayList();
489 }
Amit Ghosh83c8c892017-11-09 11:08:27 +0000490 List<ConnectPoint> cps = new ArrayList<>();
491
492 // find all the olt devices and if their uplink ports are visible
493 Iterable<Device> devices = deviceService.getDevices();
494 for (Device d : devices) {
495 // check if this device is provisioned in Sadis
496
497 log.debug("getUplinkPortsOfOlts: Checking mastership of {}", d);
498 // do only for devices for which we are the master
Andrea Campanella6f45a1b2020-05-08 17:50:12 +0200499 if (!isLocalLeader(d.id())) {
Amit Ghosh83c8c892017-11-09 11:08:27 +0000500 continue;
501 }
502
503 String devSerialNo = d.serialNumber();
Ilayda Ozdemir6b623ea2021-02-23 21:06:38 +0000504 SubscriberAndDeviceInformation deviceInfo = getSubscriberAndDeviceInfo(devSerialNo);
Amit Ghosh83c8c892017-11-09 11:08:27 +0000505 log.debug("getUplinkPortsOfOlts: Found device: {}", deviceInfo);
506 if (deviceInfo != null) {
507 // check if the uplink port with that number is available on the device
508 PortNumber pNum = PortNumber.portNumber(deviceInfo.uplinkPort());
509 Port port = deviceService.getPort(d.id(), pNum);
510 log.debug("getUplinkPortsOfOlts: Found port: {}", port);
511 if (port != null) {
512 cps.add(new ConnectPoint(d.id(), pNum));
513 }
514 }
515 }
516 return cps;
517 }
518
Ilayda Ozdemir6b623ea2021-02-23 21:06:38 +0000519 private SubscriberAndDeviceInformation getSubscriberAndDeviceInfo(String portOrDevice) {
520 if (subsService == null) {
521 log.warn(SADIS_NOT_RUNNING);
522 return null;
523 }
524 return subsService.get(portOrDevice);
525 }
526
Amit Ghosh83c8c892017-11-09 11:08:27 +0000527 /**
528 * Returns whether the passed port is the uplink port of the olt device.
529 */
530 private boolean isUplinkPortOfOlt(DeviceId dId, Port p) {
531 log.debug("isUplinkPortOfOlt: DeviceId: {} Port: {}", dId, p);
Amit Ghosh83c8c892017-11-09 11:08:27 +0000532
533 Device d = deviceService.getDevice(dId);
Ilayda Ozdemir6b623ea2021-02-23 21:06:38 +0000534 SubscriberAndDeviceInformation deviceInfo = getSubscriberAndDeviceInfo(d.serialNumber());
Amit Ghosh83c8c892017-11-09 11:08:27 +0000535
536 if (deviceInfo != null) {
537 return (deviceInfo.uplinkPort() == p.number().toLong());
538 }
539
540 return false;
541 }
542
543 /**
544 * Returns the connectPoint which is the uplink port of the OLT.
545 */
546 private ConnectPoint getUplinkConnectPointOfOlt(DeviceId dId) {
547
548 Device d = deviceService.getDevice(dId);
Ilayda Ozdemir6b623ea2021-02-23 21:06:38 +0000549 SubscriberAndDeviceInformation deviceInfo = getSubscriberAndDeviceInfo(d.serialNumber());
Amit Ghosh83c8c892017-11-09 11:08:27 +0000550 log.debug("getUplinkConnectPointOfOlt DeviceId: {} devInfo: {}", dId, deviceInfo);
551 if (deviceInfo != null) {
552 PortNumber pNum = PortNumber.portNumber(deviceInfo.uplinkPort());
553 Port port = deviceService.getPort(d.id(), pNum);
554 if (port != null) {
555 return new ConnectPoint(d.id(), pNum);
556 }
557 }
558
559 return null;
560 }
561
562 /**
563 * Request DHCP packet from particular connect point via PacketService.
Saurav Dasb14f08a2019-02-22 16:34:15 -0800564 * Optionally provide a priority for the trap flow. If no such priority is
565 * provided, the default priority will be used.
566 *
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700567 * @param cp the connect point to trap dhcp packets from
Saurav Dasb14f08a2019-02-22 16:34:15 -0800568 * @param priority of the trap flow, null to use default priority
Amit Ghosh83c8c892017-11-09 11:08:27 +0000569 */
Saurav Dasb14f08a2019-02-22 16:34:15 -0800570 private void requestDhcpPacketsFromConnectPoint(ConnectPoint cp,
571 Optional<PacketPriority> priority) {
Amit Ghosh83c8c892017-11-09 11:08:27 +0000572 TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
573 .matchEthType(Ethernet.TYPE_IPV4)
574 .matchInPort(cp.port())
575 .matchIPProtocol(IPv4.PROTOCOL_UDP)
576 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
577 packetService.requestPackets(selectorServer.build(),
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700578 priority.isPresent() ? priority.get() : PacketPriority.CONTROL,
579 appId, Optional.of(cp.deviceId()));
Amit Ghosh83c8c892017-11-09 11:08:27 +0000580 }
581
582 /**
Saurav Dasb14f08a2019-02-22 16:34:15 -0800583 * Cancel DHCP packet from particular connect point via PacketService. If
584 * the request was made with a specific packet priority, then the same
585 * priority should be used in this call.
586 *
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700587 * @param cp the connect point for the trap flow
Saurav Dasb14f08a2019-02-22 16:34:15 -0800588 * @param priority with which the trap flow was requested; if request
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700589 * priority was not specified, this param should also be null
Amit Ghosh83c8c892017-11-09 11:08:27 +0000590 */
Saurav Dasb14f08a2019-02-22 16:34:15 -0800591 private void cancelDhcpPacketsFromConnectPoint(ConnectPoint cp,
592 Optional<PacketPriority> priority) {
Amit Ghosh83c8c892017-11-09 11:08:27 +0000593 TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
594 .matchEthType(Ethernet.TYPE_IPV4)
595 .matchInPort(cp.port())
596 .matchIPProtocol(IPv4.PROTOCOL_UDP)
597 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
598 packetService.cancelPackets(selectorServer.build(),
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700599 priority.isPresent() ? priority.get() : PacketPriority.CONTROL,
600 appId, Optional.of(cp.deviceId()));
Amit Ghosh83c8c892017-11-09 11:08:27 +0000601 }
602
Saurav Das45861d42020-10-07 00:03:23 -0700603 /**
604 * Main packet-processing engine for dhcp l2 relay agent.
605 */
Amit Ghosh47243cb2017-07-26 05:08:53 +0100606 private class DhcpRelayPacketProcessor implements PacketProcessor {
Saurav Das45861d42020-10-07 00:03:23 -0700607 private static final String VLAN_KEYWORD = ":vlan";
608 private static final String PCP_KEYWORD = ":pcp";
Amit Ghosh47243cb2017-07-26 05:08:53 +0100609
610 @Override
611 public void process(PacketContext context) {
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800612 packetProcessorExecutor.execute(() -> {
613 processInternal(context);
614 });
615 }
616
617 private void processInternal(PacketContext context) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100618 if (!configured()) {
619 log.warn("Missing DHCP relay config. Abort packet processing");
620 return;
621 }
622
623 // process the packet and get the payload
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530624 Ethernet packet = context.inPacket().parsed();
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000625
Amit Ghosh47243cb2017-07-26 05:08:53 +0100626 if (packet == null) {
627 log.warn("Packet is null");
628 return;
629 }
630
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530631 if (packet.getEtherType() == Ethernet.TYPE_IPV4) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100632 IPv4 ipv4Packet = (IPv4) packet.getPayload();
633
634 if (ipv4Packet.getProtocol() == IPv4.PROTOCOL_UDP) {
635 UDP udpPacket = (UDP) ipv4Packet.getPayload();
636 if (udpPacket.getSourcePort() == UDP.DHCP_CLIENT_PORT ||
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000637 udpPacket.getSourcePort() == UDP.DHCP_SERVER_PORT) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100638 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800639 if (log.isTraceEnabled()) {
640 log.trace("Processing packet with type {} from MAC {}",
641 getDhcpPacketType(dhcpPayload),
642 MacAddress.valueOf(dhcpPayload.getClientHardwareAddress()));
643 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100644 //This packet is dhcp.
645 processDhcpPacket(context, packet, dhcpPayload);
646 }
647 }
648 }
649 }
650
Saurav Dasbd5ce9c2020-09-04 18:46:45 -0700651 // process the dhcp packet before relaying to server or client
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530652 private void processDhcpPacket(PacketContext context, Ethernet packet,
Amit Ghosh47243cb2017-07-26 05:08:53 +0100653 DHCP dhcpPayload) {
654 if (dhcpPayload == null) {
655 log.warn("DHCP payload is null");
656 return;
657 }
658
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700659 DHCP.MsgType incomingPacketType = getDhcpPacketType(dhcpPayload);
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300660 if (incomingPacketType == null) {
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700661 log.warn("DHCP Packet type not found. Dump of ethernet pkt in hex format for troubleshooting.");
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300662 byte[] array = packet.serialize();
663 ByteArrayOutputStream buf = new ByteArrayOutputStream();
664 try {
665 HexDump.dump(array, 0, buf, 0);
666 log.trace(buf.toString());
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700667 } catch (Exception e) {
668 }
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300669 return;
670 }
671
672 SubscriberAndDeviceInformation entry = null;
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000673
Matteo Scandoloeb5a0dc2020-09-15 14:54:28 -0700674 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
675
676 log.info("Received DHCP Packet of type {} from {} with Client MacAddress {} and vlan {}",
677 incomingPacketType, context.inPacket().receivedFrom(),
678 clientMacAddress, packet.getVlanID());
Amit Ghosh47243cb2017-07-26 05:08:53 +0100679
680 switch (incomingPacketType) {
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000681 case DHCPDISCOVER:
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530682 Ethernet ethernetPacketDiscover =
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000683 processDhcpPacketFromClient(context, packet);
684 if (ethernetPacketDiscover != null) {
Saurav Das45861d42020-10-07 00:03:23 -0700685 relayPacketToServer(ethernetPacketDiscover, context);
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000686 }
Saurav Das45861d42020-10-07 00:03:23 -0700687 entry = getSubscriber(context);
Jonathan Hart77ca3152020-02-21 14:31:21 -0800688 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames.valueOf("DHCPDISCOVER"));
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000689 break;
690 case DHCPOFFER:
Saurav Das45861d42020-10-07 00:03:23 -0700691 RelayToClientInfo r2cDataOffer =
Saurav Das15626a02018-09-27 18:36:45 -0700692 processDhcpPacketFromServer(context, packet);
Saurav Das45861d42020-10-07 00:03:23 -0700693 if (r2cDataOffer != null) {
694 relayPacketToClient(r2cDataOffer, clientMacAddress);
695 entry = getSubscriber(r2cDataOffer.cp);
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000696 }
Jonathan Hart77ca3152020-02-21 14:31:21 -0800697 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames.valueOf("DHCPOFFER"));
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000698 break;
699 case DHCPREQUEST:
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530700 Ethernet ethernetPacketRequest =
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000701 processDhcpPacketFromClient(context, packet);
702 if (ethernetPacketRequest != null) {
Saurav Das45861d42020-10-07 00:03:23 -0700703 relayPacketToServer(ethernetPacketRequest, context);
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000704 }
Saurav Das45861d42020-10-07 00:03:23 -0700705 entry = getSubscriber(context);
Jonathan Hart77ca3152020-02-21 14:31:21 -0800706 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames.valueOf("DHCPREQUEST"));
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000707 break;
708 case DHCPACK:
Saurav Das45861d42020-10-07 00:03:23 -0700709 RelayToClientInfo r2cDataAck =
Saurav Das15626a02018-09-27 18:36:45 -0700710 processDhcpPacketFromServer(context, packet);
Saurav Das45861d42020-10-07 00:03:23 -0700711 if (r2cDataAck != null) {
712 relayPacketToClient(r2cDataAck, clientMacAddress);
713 entry = getSubscriber(r2cDataAck.cp);
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000714 }
Jonathan Hart77ca3152020-02-21 14:31:21 -0800715 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames.valueOf("DHCPACK"));
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300716 break;
717 case DHCPDECLINE:
Arjun E K05ad20b2020-03-13 13:25:17 +0000718 Ethernet ethernetPacketDecline =
719 processDhcpPacketFromClient(context, packet);
720 if (ethernetPacketDecline != null) {
Saurav Das45861d42020-10-07 00:03:23 -0700721 relayPacketToServer(ethernetPacketDecline, context);
Arjun E K05ad20b2020-03-13 13:25:17 +0000722 }
Saurav Das45861d42020-10-07 00:03:23 -0700723 entry = getSubscriber(context);
Jonathan Hart77ca3152020-02-21 14:31:21 -0800724 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames.valueOf("DHCPDECLINE"));
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300725 break;
726 case DHCPNAK:
Saurav Das45861d42020-10-07 00:03:23 -0700727 RelayToClientInfo r2cDataNack =
Arjun E K05ad20b2020-03-13 13:25:17 +0000728 processDhcpPacketFromServer(context, packet);
Saurav Das45861d42020-10-07 00:03:23 -0700729 if (r2cDataNack != null) {
730 relayPacketToClient(r2cDataNack, clientMacAddress);
731 entry = getSubscriber(r2cDataNack.cp);
Arjun E K05ad20b2020-03-13 13:25:17 +0000732 }
Jonathan Hart77ca3152020-02-21 14:31:21 -0800733 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames.valueOf("DHCPNACK"));
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300734 break;
735 case DHCPRELEASE:
Thomas Lee S0dc9a3b2020-01-14 10:42:29 +0530736 Ethernet ethernetPacketRelease =
737 processDhcpPacketFromClient(context, packet);
738 if (ethernetPacketRelease != null) {
Saurav Das45861d42020-10-07 00:03:23 -0700739 relayPacketToServer(ethernetPacketRelease, context);
Thomas Lee S0dc9a3b2020-01-14 10:42:29 +0530740 }
Saurav Das45861d42020-10-07 00:03:23 -0700741 entry = getSubscriber(context);
Jonathan Hart77ca3152020-02-21 14:31:21 -0800742 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames.valueOf("DHCPRELEASE"));
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000743 break;
744 default:
745 break;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100746 }
747 }
748
Saurav Das45861d42020-10-07 00:03:23 -0700749 /**
750 * Processes dhcp packets from clients.
751 *
752 * @param context the packet context
753 * @param ethernetPacket the dhcp packet from client
754 * @return the packet to relay to the server
755 */
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530756 private Ethernet processDhcpPacketFromClient(PacketContext context,
757 Ethernet ethernetPacket) {
Saurav Das15626a02018-09-27 18:36:45 -0700758 if (log.isTraceEnabled()) {
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700759 log.trace("DHCP Packet received from client at {} {}",
Saurav Das15626a02018-09-27 18:36:45 -0700760 context.inPacket().receivedFrom(), ethernetPacket);
761 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100762
763 MacAddress relayAgentMac = relayAgentMacAddress(context);
764 if (relayAgentMac == null) {
765 log.warn("RelayAgent MAC not found ");
Amit Ghosh47243cb2017-07-26 05:08:53 +0100766 return null;
767 }
768
pier9e0efbe2020-10-28 20:33:56 +0100769 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100770
771 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
772 UDP udpPacket = (UDP) ipv4Packet.getPayload();
773 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
Saurav Dasbd5ce9c2020-09-04 18:46:45 -0700774 ConnectPoint inPort = context.inPacket().receivedFrom();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100775
Amit Ghosha17354e2017-08-23 12:56:04 +0100776 if (enableDhcpBroadcastReplies) {
777 // We want the reply to come back as a L2 broadcast
778 dhcpPacket.setFlags((short) 0x8000);
779 }
780
Jonathan Hartc36c9552018-07-31 15:07:53 -0400781 MacAddress clientMac = MacAddress.valueOf(dhcpPacket.getClientHardwareAddress());
Saurav Dasbd5ce9c2020-09-04 18:46:45 -0700782 VlanId clientVlan = VlanId.vlanId(ethernetPacket.getVlanID());
Jonathan Hartc36c9552018-07-31 15:07:53 -0400783 IpAddress clientIp = IpAddress.valueOf(dhcpPacket.getClientIPAddress());
Amit Ghosha17354e2017-08-23 12:56:04 +0100784
Jonathan Hartc36c9552018-07-31 15:07:53 -0400785 SubscriberAndDeviceInformation entry = getSubscriber(context);
786 if (entry == null) {
Saurav Das15626a02018-09-27 18:36:45 -0700787 log.warn("Dropping packet as subscriber entry is not available");
Jonathan Hartc36c9552018-07-31 15:07:53 -0400788 return null;
789 }
790
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000791 UniTagInformation uniTagInformation = getUnitagInformationFromPacketContext(context, entry);
792 if (uniTagInformation == null) {
793 log.warn("Missing service information for connectPoint {} / cTag {}",
Saurav Dasbd5ce9c2020-09-04 18:46:45 -0700794 inPort, clientVlan);
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000795 return null;
796 }
Saurav Das45861d42020-10-07 00:03:23 -0700797 DhcpOption82Data d82 = null;
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000798 if (option82) {
Saurav Das45861d42020-10-07 00:03:23 -0700799 DHCP dhcpPacketWithOption82 = addOption82(dhcpPacket, entry,
800 inPort, clientVlan,
801 uniTagInformation
802 .getDsPonCTagPriority());
803 byte[] d82b = dhcpPacketWithOption82
804 .getOption(DHCP.DHCPOptionCode.OptionCode_CircuitID)
805 .getData();
806 d82 = new DhcpOption82Data(d82b);
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000807 udpPacket.setPayload(dhcpPacketWithOption82);
808 }
809
810 ipv4Packet.setPayload(udpPacket);
811 etherReply.setPayload(ipv4Packet);
Amit Ghosh83c8c892017-11-09 11:08:27 +0000812 if (modifyClientPktsSrcDstMac) {
813 etherReply.setSourceMACAddress(relayAgentMac);
814 etherReply.setDestinationMACAddress(dhcpConnectMac);
815 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100816
Amit Ghosh8951f042017-08-10 13:48:10 +0100817 etherReply.setPriorityCode(ethernetPacket.getPriorityCode());
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000818 etherReply.setVlanID(uniTagInformation.getPonCTag().toShort());
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530819 etherReply.setQinQTPID(Ethernet.TYPE_VLAN);
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000820 etherReply.setQinQVID(uniTagInformation.getPonSTag().toShort());
821 if (uniTagInformation.getUsPonSTagPriority() != -1) {
822 etherReply.setQinQPriorityCode((byte) uniTagInformation.getUsPonSTagPriority());
823 }
Saurav Das45861d42020-10-07 00:03:23 -0700824 if (uniTagInformation.getUsPonCTagPriority() != -1) {
825 etherReply.setPriorityCode((byte) uniTagInformation
826 .getUsPonCTagPriority());
827 }
828
829 DhcpAllocationInfo info = new DhcpAllocationInfo(inPort,
830 dhcpPacket.getPacketType(),
831 (d82 == null)
832 ? entry.circuitId()
833 : d82.getAgentCircuitId(),
834 clientMac, clientIp,
835 clientVlan, entry.id());
836 String key = getUniqueUuidFromString(entry.id() + clientMac
837 + clientVlan);
838 allocations.put(key, info);
839 post(new DhcpL2RelayEvent(DhcpL2RelayEvent.Type.UPDATED, info, inPort));
840 if (log.isTraceEnabled()) {
Andrea Campanellad79a6792020-10-26 14:44:46 +0100841 log.trace("Finished processing DHCP Packet of type {} with MAC {} from {} "
Saurav Das45861d42020-10-07 00:03:23 -0700842 + "... relaying to dhcpServer",
Andrea Campanellad79a6792020-10-26 14:44:46 +0100843 dhcpPacket.getPacketType(), clientMac, entry.id());
Saurav Das45861d42020-10-07 00:03:23 -0700844 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100845 return etherReply;
846 }
847
Saurav Das45861d42020-10-07 00:03:23 -0700848 /**
849 * Processes dhcp packets from the server.
850 *
851 * @param context the packet context
852 * @param ethernetPacket the dhcp packet
853 * @return returns information necessary for relaying packet to client
854 */
855 private RelayToClientInfo processDhcpPacketFromServer(PacketContext context,
856 Ethernet ethernetPacket) {
Saurav Das15626a02018-09-27 18:36:45 -0700857 if (log.isTraceEnabled()) {
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700858 log.trace("DHCP Packet received from server at {} {}",
Saurav Das15626a02018-09-27 18:36:45 -0700859 context.inPacket().receivedFrom(), ethernetPacket);
860 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100861 // get dhcp header.
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530862 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100863 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
864 UDP udpPacket = (UDP) ipv4Packet.getPayload();
Saurav Das45861d42020-10-07 00:03:23 -0700865 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
Saurav Dasbd5ce9c2020-09-04 18:46:45 -0700866 VlanId innerVlan = VlanId.vlanId(ethernetPacket.getVlanID());
Saurav Das45861d42020-10-07 00:03:23 -0700867 MacAddress dstMac = valueOf(dhcpPacket.getClientHardwareAddress());
Amit Ghosh47243cb2017-07-26 05:08:53 +0100868
Saurav Das45861d42020-10-07 00:03:23 -0700869 // we leave the srcMac from the original packet.
870 // TODO remove S-VLAN
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000871 etherReply.setQinQVID(VlanId.NO_VID);
872 etherReply.setQinQPriorityCode((byte) 0);
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530873 etherReply.setDestinationMACAddress(dstMac);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100874
Saurav Das45861d42020-10-07 00:03:23 -0700875 // TODO deserialization of dhcp option82 leaves 'data' field null
876 // As a result we need to retrieve suboption data
877 RelayToClientInfo r2cData = null;
878 boolean usedOption82 = false;
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000879 if (option82) {
Saurav Das45861d42020-10-07 00:03:23 -0700880 // retrieve connectPoint and vlan from option82, if it is in expected format
881 DhcpOption opt = dhcpPacket
882 .getOption(DHCP.DHCPOptionCode.OptionCode_CircuitID);
883 if (opt != null && opt instanceof DhcpRelayAgentOption) {
884 DhcpRelayAgentOption d82 = (DhcpRelayAgentOption) opt;
885 DhcpOption d82ckt = d82.getSubOption(DhcpOption82Data.CIRCUIT_ID_CODE);
886 if (d82ckt.getData() != null) {
887 r2cData = decodeCircuitId(new String(d82ckt.getData()));
888 }
889 }
890 if (r2cData != null) {
891 usedOption82 = true;
892 etherReply.setVlanID(r2cData.cvid.toShort());
893 if (r2cData.pcp != -1) {
894 etherReply.setPriorityCode((byte) r2cData.pcp);
895 }
896 }
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000897 }
Saurav Das45861d42020-10-07 00:03:23 -0700898 // always remove option82 if present
899 DHCP remDhcpPacket = removeOption82(dhcpPacket);
900 udpPacket.setPayload(remDhcpPacket);
901
Amit Ghosh47243cb2017-07-26 05:08:53 +0100902 ipv4Packet.setPayload(udpPacket);
903 etherReply.setPayload(ipv4Packet);
904
Saurav Das45861d42020-10-07 00:03:23 -0700905 if (!usedOption82) {
906 // option 82 data not present or not used, we need to
907 // lookup host store with client dstmac and vlan from context
908 r2cData = new RelayToClientInfo();
909 r2cData.cp = getConnectPointOfClient(dstMac, context);
910 if (r2cData.cp == null) {
911 log.warn("Couldn't find subscriber, service or host info for mac"
912 + " address {} and vlan {} .. DHCP packet can't be"
913 + " delivered to client", dstMac, innerVlan);
914 return null;
915 }
916 }
917
918 // always need the subscriber entry
919 SubscriberAndDeviceInformation entry = getSubscriber(r2cData.cp);
920 if (entry == null) {
921 log.warn("Couldn't find subscriber info for cp {}.. DHCP packet"
922 + " can't be delivered to client mac {} and vlan {}",
923 r2cData.cp, dstMac, innerVlan);
924 return null;
925 }
926
927 if (!usedOption82) {
928 UniTagInformation uniTagInformation =
929 getUnitagInformationFromPacketContext(context, entry);
930 if (uniTagInformation == null) {
931 log.warn("Missing service information for connectPoint {} "
932 + " cTag {} .. DHCP packet can't be delivered to client",
933 r2cData.cp, innerVlan);
934 return null;
935 }
936 r2cData.cvid = uniTagInformation.getPonCTag();
937 r2cData.pcp = uniTagInformation.getDsPonCTagPriority();
938 r2cData.cktId = entry.circuitId();
939 etherReply.setVlanID(r2cData.cvid.toShort());
940 if (r2cData.pcp != -1) {
941 etherReply.setPriorityCode((byte) r2cData.pcp);
942 }
943 }
944
945 // update stats and events
946 IpAddress ip = IpAddress.valueOf(dhcpPacket.getYourIPAddress());
947 DhcpAllocationInfo info =
948 new DhcpAllocationInfo(r2cData.cp, dhcpPacket.getPacketType(),
949 r2cData.cktId, dstMac, ip, innerVlan,
950 entry.id());
951 String key = getUniqueUuidFromString(entry.id() + info.macAddress()
952 + innerVlan);
953 allocations.put(key, info);
954 post(new DhcpL2RelayEvent(DhcpL2RelayEvent.Type.UPDATED, info,
955 r2cData.cp));
956 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames
957 .valueOf("PACKETS_FROM_SERVER"));
958 if (log.isTraceEnabled()) {
959 log.trace("Finished processing packet.. relaying to client at {}",
960 r2cData.cp);
961 }
962 r2cData.ethernetPkt = etherReply;
963 return r2cData;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100964 }
965
Saurav Das45861d42020-10-07 00:03:23 -0700966 // forward the packet to ConnectPoint where the DHCP server is attached.
967 private void relayPacketToServer(Ethernet packet, PacketContext context) {
Andrea Campanellad79a6792020-10-26 14:44:46 +0100968 SubscriberAndDeviceInformation entry = getSubscriber(context);
Saurav Das45861d42020-10-07 00:03:23 -0700969 if (log.isTraceEnabled()) {
970 IPv4 ipv4Packet = (IPv4) packet.getPayload();
971 UDP udpPacket = (UDP) ipv4Packet.getPayload();
972 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
Andrea Campanellad79a6792020-10-26 14:44:46 +0100973 log.trace("Emitting packet to server: packet {}, with MAC {} from {}",
Saurav Das45861d42020-10-07 00:03:23 -0700974 getDhcpPacketType(dhcpPayload),
Andrea Campanellad79a6792020-10-26 14:44:46 +0100975 MacAddress.valueOf(dhcpPayload.getClientHardwareAddress()),
976 entry.id());
Saurav Das45861d42020-10-07 00:03:23 -0700977 }
978 ConnectPoint toSendTo = null;
979 if (!useOltUplink) {
980 toSendTo = dhcpServerConnectPoint.get();
981 } else {
982 toSendTo = getUplinkConnectPointOfOlt(context.inPacket().receivedFrom()
983 .deviceId());
984 }
985
986 if (toSendTo != null) {
987 TrafficTreatment t = DefaultTrafficTreatment.builder()
988 .setOutput(toSendTo.port()).build();
989 OutboundPacket o = new DefaultOutboundPacket(toSendTo
990 .deviceId(), t, ByteBuffer.wrap(packet.serialize()));
991 if (log.isTraceEnabled()) {
992 log.trace("Relaying packet to dhcp server at {} {}", toSendTo,
993 packet);
994 }
995 packetService.emit(o);
996
Saurav Das45861d42020-10-07 00:03:23 -0700997 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames
998 .valueOf("PACKETS_TO_SERVER"));
999 } else {
1000 log.error("No connect point to send msg to DHCP Server");
1001 }
1002 }
1003
1004 // send the response to the requester host (client)
1005 private void relayPacketToClient(RelayToClientInfo r2cData,
1006 MacAddress dstMac) {
1007 ConnectPoint subCp = r2cData.cp;
1008 Ethernet ethPacket = r2cData.ethernetPkt;
1009 // Send packet out to requester if the host information is available
1010 if (subCp != null) {
1011 TrafficTreatment t = DefaultTrafficTreatment.builder()
1012 .setOutput(subCp.port()).build();
1013 OutboundPacket o = new DefaultOutboundPacket(subCp.deviceId(),
1014 t, ByteBuffer.wrap(ethPacket.serialize()));
1015 if (log.isTraceEnabled()) {
1016 log.trace("Relaying packet to DHCP client at {} with "
1017 + "MacAddress {}, {} given {}", subCp, dstMac,
1018 ethPacket, r2cData);
1019 }
1020 packetService.emit(o);
1021 } else {
1022 log.error("Dropping DHCP Packet because unknown connectPoint for {}",
1023 dstMac);
1024 }
1025 }
1026
1027 /**
1028 * Option 82 includes circuitId and remoteId data configured by an
1029 * operator in sadis for a subscriber, and can be a string in any form
1030 * relevant to the operator's dhcp-server. When circuitId is configured
1031 * in sadis, the relay agent adds the option, but does not use the
1032 * information for forwarding packets back to client.
1033 * <p>
1034 * If circuitId is not configured in sadis, this relay-agent adds
1035 * circuitId information in the form
1036 * "{@literal<}connectPoint>:vlan{@literal<}clientVlanId>:pcp{@literal<}downstreamPcp>"
1037 * for example, "of:0000000000000001/32:vlan200:pcp7". When the packet
1038 * is received back from the server with circuitId in this form, this
1039 * relay agent will use this information to forward packets to the
1040 * client.
1041 *
1042 * @param dhcpPacket the DHCP packet to transform
1043 * @param entry sadis information for the subscriber
1044 * @param cp the connectPoint to set if sadis entry has no circuitId
1045 * @param clientVlan the vlan to set if sadis entry has no circuitId
1046 * @param downstreamPbits the pbits to set if sadis entry has no
1047 * circuitId
1048 * @return the modified dhcp packet with option82 added
Amit Ghosha17354e2017-08-23 12:56:04 +01001049 */
Saurav Das45861d42020-10-07 00:03:23 -07001050 private DHCP addOption82(DHCP dhcpPacket, SubscriberAndDeviceInformation entry,
1051 ConnectPoint cp, VlanId clientVlan,
1052 int downstreamPbits) {
1053 List<DhcpOption> options = Lists.newArrayList(dhcpPacket.getOptions());
1054 DhcpOption82Data option82 = new DhcpOption82Data();
1055 if (entry.circuitId() == null || entry.circuitId().isBlank()) {
1056 option82.setAgentCircuitId(cp + VLAN_KEYWORD + clientVlan
1057 + PCP_KEYWORD
1058 + downstreamPbits);
1059 } else {
1060 option82.setAgentCircuitId(entry.circuitId());
1061 }
Saurav Dasd935f452020-10-29 14:34:53 -07001062 if (entry.remoteId() != null && !entry.remoteId().isBlank()) {
1063 option82.setAgentRemoteId(entry.remoteId());
1064 }
Saurav Das45861d42020-10-07 00:03:23 -07001065 if (log.isTraceEnabled()) {
1066 log.trace("adding option82 {} ", option82);
1067 }
1068 DhcpOption option = new DhcpOption()
1069 .setCode(DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue())
1070 .setData(option82.toByteArray())
1071 .setLength(option82.length());
1072
1073 options.add(options.size() - 1, option);
1074 dhcpPacket.setOptions(options);
1075
1076 return dhcpPacket;
1077 }
1078
1079 private DHCP removeOption82(DHCP dhcpPacket) {
1080 List<DhcpOption> options = dhcpPacket.getOptions();
1081 List<DhcpOption> newoptions = options.stream()
1082 .filter(option -> option
1083 .getCode() != DHCP.DHCPOptionCode.OptionCode_CircuitID
1084 .getValue())
1085 .collect(Collectors.toList());
1086
1087 return dhcpPacket.setOptions(newoptions);
1088 }
1089
1090 /**
1091 * Returns the circuit Id values decoded from the option 82 data. Decoding
1092 * is performed if and only if the circuit id format is in the form
1093 * "{@literal<}connectPoint>:vlan{@literal<}clientVlanId>:pcp{@literal<}downstreamPcp>"
1094 *
1095 * @param cktId the circuitId string from option 82 data
1096 * @return decoded circuit id data if it is in the expected format or
1097 * null
1098 */
1099 private RelayToClientInfo decodeCircuitId(String cktId) {
1100 if (cktId.contains(VLAN_KEYWORD) && cktId.contains(PCP_KEYWORD)) {
1101 ConnectPoint cp = ConnectPoint
1102 .fromString(cktId
1103 .substring(0, cktId.indexOf(VLAN_KEYWORD)));
1104 VlanId cvid = VlanId
1105 .vlanId(cktId.substring(
1106 cktId.indexOf(VLAN_KEYWORD)
1107 + VLAN_KEYWORD.length(),
1108 cktId.indexOf(PCP_KEYWORD)));
1109 int pcp = Integer
1110 .valueOf(cktId.substring(cktId.indexOf(PCP_KEYWORD)
1111 + PCP_KEYWORD.length()))
1112 .intValue();
1113 log.debug("retrieved from option82-> cp={} cvlan={} down-pcp={}"
1114 + " for relaying to client ", cp, cvid, pcp);
1115 return new RelayToClientInfo(cp, cvid, pcp, cktId);
1116 } else {
1117 log.debug("Option 82 circuitId {} is operator defined and will "
1118 + "not be used for forwarding", cktId);
1119 return null;
1120 }
1121 }
1122
1123 private class RelayToClientInfo {
1124 Ethernet ethernetPkt;
1125 ConnectPoint cp;
1126 VlanId cvid;
1127 int pcp;
1128 String cktId;
1129
1130 public RelayToClientInfo(ConnectPoint cp, VlanId cvid, int pcp,
1131 String cktId) {
1132 this.cp = cp;
1133 this.cvid = cvid;
1134 this.pcp = pcp;
1135 this.cktId = cktId;
1136 }
1137
1138 public RelayToClientInfo() {
1139 }
1140
1141 @Override
1142 public String toString() {
1143 return "RelayToClientInfo: {connectPoint=" + cp + " clientVlan="
1144 + cvid + " clientPcp=" + pcp + " circuitId=" + cktId + "}";
1145 }
1146
1147 }
1148
1149 // get the type of the DHCP packet
1150 private DHCP.MsgType getDhcpPacketType(DHCP dhcpPayload) {
1151 for (DhcpOption option : dhcpPayload.getOptions()) {
1152 if (option.getCode() == OptionCode_MessageType.getValue()) {
1153 byte[] data = option.getData();
1154 return DHCP.MsgType.getType(data[0]);
1155 }
1156 }
1157 return null;
1158 }
1159
1160 private void updateDhcpRelayCountersStore(SubscriberAndDeviceInformation entry,
1161 DhcpL2RelayCounterNames counterType) {
1162 // Update global counter stats
1163 dhcpL2RelayCounters.incrementCounter(DhcpL2RelayEvent.GLOBAL_COUNTER,
1164 counterType);
1165 if (entry == null) {
1166 log.warn("Counter not updated as subscriber info not found.");
1167 } else {
1168 // Update subscriber counter stats
1169 dhcpL2RelayCounters.incrementCounter(entry.id(), counterType);
1170 }
1171 }
1172
1173 /**
1174 * Get subscriber information based on subscriber's connectPoint.
1175 *
1176 * @param subsCp the subscriber's connectPoint
1177 * @return subscriber sadis info or null if not found
1178 */
1179 private SubscriberAndDeviceInformation getSubscriber(ConnectPoint subsCp) {
1180 if (subsCp != null) {
1181 String portName = getPortName(subsCp);
Ilayda Ozdemir6b623ea2021-02-23 21:06:38 +00001182 return getSubscriberAndDeviceInfo(portName);
Saurav Das45861d42020-10-07 00:03:23 -07001183 }
1184 return null;
1185 }
1186
1187 /**
1188 * Returns sadis info for subscriber based on incoming packet context.
1189 * The packet context must refer to a packet coming from a subscriber
1190 * port.
1191 *
1192 * @param context incoming packet context from subscriber port (UNI)
1193 * @return sadis info for the subscriber or null
1194 */
1195 private SubscriberAndDeviceInformation getSubscriber(PacketContext context) {
1196 String portName = getPortName(context.inPacket().receivedFrom());
Ilayda Ozdemir6b623ea2021-02-23 21:06:38 +00001197 return getSubscriberAndDeviceInfo(portName);
Saurav Das45861d42020-10-07 00:03:23 -07001198 }
1199
1200 /**
1201 * Returns ConnectPoint of the Client based on MAC address and C-VLAN.
1202 * Verifies that returned connect point has service defined in sadis.
1203 *
1204 * @param dstMac client dstMac
1205 * @param context context for incoming packet, parsed for C-vlan id
1206 * @return connect point information for client or null if connect point
1207 * not found or service cannot be verified for client info
1208 */
1209 private ConnectPoint getConnectPointOfClient(MacAddress dstMac,
1210 PacketContext context) {
Amit Ghosha17354e2017-08-23 12:56:04 +01001211 Set<Host> hosts = hostService.getHostsByMac(dstMac);
1212 if (hosts == null || hosts.isEmpty()) {
1213 log.warn("Cannot determine host for DHCP client: {}. Aborting "
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001214 + "relay for DHCP Packet from server", dstMac);
Amit Ghosha17354e2017-08-23 12:56:04 +01001215 return null;
1216 }
1217 for (Host h : hosts) {
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001218 // if more than one (for example, multiple services with same
1219 // mac-address but different service VLANs (inner/C vlans)
Amit Ghosha17354e2017-08-23 12:56:04 +01001220 // find the connect point which has an valid entry in SADIS
1221 ConnectPoint cp = new ConnectPoint(h.location().deviceId(),
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001222 h.location().port());
Amit Ghosha17354e2017-08-23 12:56:04 +01001223
Saurav Das45861d42020-10-07 00:03:23 -07001224 SubscriberAndDeviceInformation sub = getSubscriber(cp);
Gamze Abakaa64b3bc2020-01-31 06:51:43 +00001225 if (sub == null) {
Saurav Das45861d42020-10-07 00:03:23 -07001226 log.warn("Subscriber info not found for {} for host {}", cp, h);
1227 continue;
Amit Ghosha17354e2017-08-23 12:56:04 +01001228 }
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001229 // check for cvlan in subscriber's uniTagInfo list
Saurav Das45861d42020-10-07 00:03:23 -07001230 UniTagInformation uniTagInformation =
1231 getUnitagInformationFromPacketContext(context, sub);
Jonathan Hartb4fbc922020-04-14 12:17:44 -07001232 if (uniTagInformation != null) {
1233 return cp;
Gamze Abakaa64b3bc2020-01-31 06:51:43 +00001234 }
Gamze Abakaa64b3bc2020-01-31 06:51:43 +00001235 }
Jonathan Hartb4fbc922020-04-14 12:17:44 -07001236 // no sadis config found for this connectPoint/vlan
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001237 log.warn("Missing service information for dhcp packet received from"
1238 + " {} with cTag {} .. cannot relay to client",
Saurav Das45861d42020-10-07 00:03:23 -07001239 context.inPacket().receivedFrom(),
1240 context.inPacket().parsed().getVlanID());
1241 return null;
1242 }
1243
1244 /**
1245 * Returns the port-name for the given connectPoint port.
1246 *
1247 * @param cp the given connect point
1248 * @return the port-name for the connect point port
1249 */
1250 private String getPortName(ConnectPoint cp) {
1251 Port p = deviceService.getPort(cp);
1252 return p.annotations().value(AnnotationKeys.PORT_NAME);
1253 }
1254
1255 /**
1256 * Return's uniTagInformation (service information) if incoming packet's
1257 * client VLAN id matches the subscriber's service info, and dhcp is
1258 * required for this service.
1259 *
1260 * @param context
1261 * @param sub
1262 * @return
1263 */
1264 private UniTagInformation getUnitagInformationFromPacketContext(PacketContext context,
1265 SubscriberAndDeviceInformation sub) {
1266 // If the ctag is defined in the tagList and dhcp is required,
1267 // return the service info
1268 List<UniTagInformation> tagList = sub.uniTagList();
1269 for (UniTagInformation uniServiceInformation : tagList) {
1270 if (uniServiceInformation.getPonCTag().toShort() == context.inPacket()
1271 .parsed().getVlanID()) {
1272 if (uniServiceInformation.getIsDhcpRequired()) {
1273 return uniServiceInformation;
1274 }
1275 }
1276 }
Jonathan Hartb4fbc922020-04-14 12:17:44 -07001277
Amit Ghosha17354e2017-08-23 12:56:04 +01001278 return null;
1279 }
1280
Saurav Das45861d42020-10-07 00:03:23 -07001281
1282 private MacAddress relayAgentMacAddress(PacketContext context) {
1283 SubscriberAndDeviceInformation device = this.getDevice(context);
1284 if (device == null) {
1285 log.warn("Device not found for {}", context.inPacket().receivedFrom());
1286 return null;
Amit Ghosh47243cb2017-07-26 05:08:53 +01001287 }
Saurav Das45861d42020-10-07 00:03:23 -07001288 return device.hardwareIdentifier();
Amit Ghosh47243cb2017-07-26 05:08:53 +01001289 }
Amit Ghosh47243cb2017-07-26 05:08:53 +01001290
Saurav Das45861d42020-10-07 00:03:23 -07001291 /**
1292 * Returns sadis information for device from which packet was received.
1293 *
1294 * @param context the packet context
1295 * @return sadis information for device
1296 */
1297 private SubscriberAndDeviceInformation getDevice(PacketContext context) {
1298 String serialNo = deviceService
1299 .getDevice(context.inPacket().receivedFrom().deviceId())
1300 .serialNumber();
Ilayda Ozdemir6b623ea2021-02-23 21:06:38 +00001301 return getSubscriberAndDeviceInfo(serialNo);
Saurav Das45861d42020-10-07 00:03:23 -07001302 }
Deepa vaddireddy0060f532017-08-04 06:46:05 +00001303
Deepa vaddireddy0060f532017-08-04 06:46:05 +00001304 }
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001305
Amit Ghosh47243cb2017-07-26 05:08:53 +01001306 /**
1307 * Listener for network config events.
1308 */
1309 private class InternalConfigListener implements NetworkConfigListener {
1310
1311 @Override
1312 public void event(NetworkConfigEvent event) {
1313
1314 if ((event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
1315 event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED) &&
1316 event.configClass().equals(DhcpL2RelayConfig.class)) {
1317 updateConfig();
1318 log.info("Reconfigured");
1319 }
1320 }
1321 }
Deepa vaddireddy0060f532017-08-04 06:46:05 +00001322
Amit Ghosh8951f042017-08-10 13:48:10 +01001323 /**
1324 * Handles Mastership changes for the devices which connect
1325 * to the DHCP server.
1326 */
1327 private class InnerMastershipListener implements MastershipListener {
1328 @Override
1329 public void event(MastershipEvent event) {
Amit Ghosh83c8c892017-11-09 11:08:27 +00001330 if (!useOltUplink) {
1331 if (dhcpServerConnectPoint.get() != null &&
1332 dhcpServerConnectPoint.get().deviceId().
1333 equals(event.subject())) {
1334 log.trace("Mastership Event recevived for {}", event.subject());
1335 // mastership of the device for our connect point has changed
1336 // reselect
1337 selectServerConnectPoint();
1338 }
Amit Ghosh8951f042017-08-10 13:48:10 +01001339 }
1340 }
1341 }
Deepa vaddireddy0060f532017-08-04 06:46:05 +00001342
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001343 private void removeAllocations(Predicate<Map.Entry<String, Versioned<DhcpAllocationInfo>>> pred) {
1344 allocations.stream()
1345 .filter(pred)
1346 .map(Map.Entry::getKey)
1347 .collect(Collectors.toList())
1348 .forEach(allocations::remove);
1349 }
1350
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001351 @Override
Matteo Scandoloab346512020-04-17 13:39:55 -07001352 public void clearAllocations() {
1353 allocations.clear();
1354 }
1355
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001356 @Override
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001357 public boolean removeAllocationsByConnectPoint(ConnectPoint cp) {
1358 boolean removed = false;
Matteo Scandoloab346512020-04-17 13:39:55 -07001359 for (String key : allocations.keySet()) {
1360 DhcpAllocationInfo entry = allocations.asJavaMap().get(key);
1361 if (entry.location().equals(cp)) {
1362 allocations.remove(key);
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001363 removed = true;
Matteo Scandoloab346512020-04-17 13:39:55 -07001364 }
1365 }
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001366 return removed;
Matteo Scandoloab346512020-04-17 13:39:55 -07001367 }
1368
Andrea Campanella6f45a1b2020-05-08 17:50:12 +02001369 /**
1370 * Checks for mastership or falls back to leadership on deviceId.
1371 * If the node is not master and device is available
1372 * or the device is not available and the leader is different
1373 * we let master or leader else handle it
1374 * Leadership on the device topic is needed because the master can be NONE
1375 * in case the device went away, we still need to handle events
1376 * consistently
1377 */
1378 private boolean isLocalLeader(DeviceId deviceId) {
1379 if (!mastershipService.isLocalMaster(deviceId)) {
1380 // When the device is available we just check the mastership
1381 if (deviceService.isAvailable(deviceId)) {
1382 return false;
1383 }
1384 // Fallback with Leadership service - device id is used as topic
1385 NodeId leader = leadershipService.runForLeadership(
1386 deviceId.toString()).leaderNodeId();
1387 // Verify if this node is the leader
1388 return clusterService.getLocalNode().id().equals(leader);
1389 }
1390 return true;
1391 }
1392
Amit Ghosh8951f042017-08-10 13:48:10 +01001393 /**
1394 * Handles Device status change for the devices which connect
1395 * to the DHCP server.
1396 */
1397 private class InnerDeviceListener implements DeviceListener {
1398 @Override
1399 public void event(DeviceEvent event) {
Andrea Campanella6f45a1b2020-05-08 17:50:12 +02001400 final DeviceId deviceId = event.subject().id();
1401
1402 // Ensure only one instance handles the event
1403 if (!isLocalLeader(deviceId)) {
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001404 return;
1405 }
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001406 // ignore stats
1407 if (event.type().equals(DeviceEvent.Type.PORT_STATS_UPDATED)) {
1408 return;
1409 }
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001410
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001411 log.debug("Device Event received for {} event {}", event.subject(),
1412 event.type());
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001413
Thomas Lee S9df15082019-12-23 11:31:15 +05301414 switch (event.type()) {
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001415 case DEVICE_REMOVED:
1416 log.info("Device removed {}", event.subject().id());
1417 removeAllocations(e -> e.getValue().value().location().deviceId().equals(deviceId));
1418 break;
Thomas Lee S9df15082019-12-23 11:31:15 +05301419 case DEVICE_AVAILABILITY_CHANGED:
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001420 boolean available = deviceService.isAvailable(deviceId);
1421 log.info("Device Avail Changed {} to {}", event.subject().id(), available);
1422
1423 if (!available && deviceService.getPorts(deviceId).isEmpty()) {
1424 removeAllocations(e -> e.getValue().value().location().deviceId().equals(deviceId));
1425 log.info("Device {} is removed from DHCP allocationmap ", deviceId);
Thomas Lee S9df15082019-12-23 11:31:15 +05301426 }
1427 break;
Thomas Lee S6b77ad22020-01-10 11:27:43 +05301428 case PORT_REMOVED:
1429 Port port = event.port();
Thomas Lee S6b77ad22020-01-10 11:27:43 +05301430 log.info("Port {} is deleted on device {}", port, deviceId);
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001431
1432 ConnectPoint cp = new ConnectPoint(deviceId, port.number());
1433 removeAllocations(e -> e.getValue().value().location().equals(cp));
1434
Thomas Lee S6b77ad22020-01-10 11:27:43 +05301435 log.info("Port {} on device {} is removed from DHCP allocationmap", event.port(), deviceId);
1436 break;
Thomas Lee S9df15082019-12-23 11:31:15 +05301437 default:
1438 break;
1439 }
Amit Ghosh83c8c892017-11-09 11:08:27 +00001440 if (!useOltUplink) {
1441 if (dhcpServerConnectPoint.get() == null) {
1442 switch (event.type()) {
1443 case DEVICE_ADDED:
1444 case DEVICE_AVAILABILITY_CHANGED:
Saurav Dasb14f08a2019-02-22 16:34:15 -08001445 // some device is available check if we can get a
1446 // connect point we can use
1447 addOrRemoveDhcpTrapFromServer(true);
Amit Ghosh83c8c892017-11-09 11:08:27 +00001448 break;
1449 default:
1450 break;
1451 }
1452 return;
Amit Ghosh8951f042017-08-10 13:48:10 +01001453 }
Amit Ghosh83c8c892017-11-09 11:08:27 +00001454 if (dhcpServerConnectPoint.get().deviceId().
1455 equals(event.subject().id())) {
1456 switch (event.type()) {
1457 case DEVICE_AVAILABILITY_CHANGED:
1458 case DEVICE_REMOVED:
1459 case DEVICE_SUSPENDED:
1460 // state of our device has changed, check if we need
Saurav Dasb14f08a2019-02-22 16:34:15 -08001461 // to re-select a connectpoint
1462 addOrRemoveDhcpTrapFromServer(true);
Amit Ghosh83c8c892017-11-09 11:08:27 +00001463 break;
1464 default:
1465 break;
1466 }
1467 }
1468 } else {
Amit Ghosh8951f042017-08-10 13:48:10 +01001469 switch (event.type()) {
Amit Ghosh83c8c892017-11-09 11:08:27 +00001470 case PORT_ADDED:
Saurav Dasb4e3e102018-10-02 15:31:17 -07001471 if (useOltUplink && isUplinkPortOfOlt(event.subject().id(), event.port())) {
Saurav Dasb14f08a2019-02-22 16:34:15 -08001472 requestDhcpPacketsFromConnectPoint(
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001473 new ConnectPoint(event.subject().id(), event.port().number()),
1474 Optional.empty());
Amit Ghosh83c8c892017-11-09 11:08:27 +00001475 }
Amit Ghosh8951f042017-08-10 13:48:10 +01001476 break;
1477 default:
1478 break;
1479 }
1480 }
1481 }
1482 }
Jonathan Hart77ca3152020-02-21 14:31:21 -08001483
1484 private class InnerDhcpL2RelayStoreDelegate implements DhcpL2RelayStoreDelegate {
1485 @Override
1486 public void notify(DhcpL2RelayEvent event) {
1487 if (event.type().equals(DhcpL2RelayEvent.Type.STATS_UPDATE)) {
1488 DhcpL2RelayEvent toPost = event;
1489 if (event.getSubscriberId() != null) {
1490 // infuse the event with the allocation info before posting
1491 DhcpAllocationInfo info = Versioned.valueOrNull(allocations.get(event.getSubscriberId()));
1492 toPost = new DhcpL2RelayEvent(event.type(), info, event.connectPoint(),
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001493 event.getCountersEntry(), event.getSubscriberId());
Jonathan Hart77ca3152020-02-21 14:31:21 -08001494 }
1495 post(toPost);
1496 }
1497
1498 }
1499 }
Amit Ghosh47243cb2017-07-26 05:08:53 +01001500}