blob: c3916051de56b6fb7c462c9f591085e6ea6b8f79 [file] [log] [blame]
Amit Ghosh47243cb2017-07-26 05:08:53 +01001/*
Deepa vaddireddy0060f532017-08-04 06:46:05 +00002 * Copyright 2017-present Open Networking Foundation
Amit Ghosh47243cb2017-07-26 05:08:53 +01003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
Matteo Scandolo57af5d12019-04-29 17:11:41 -070016package org.opencord.dhcpl2relay.impl;
Amit Ghosh47243cb2017-07-26 05:08:53 +010017
Saurav Das45861d42020-10-07 00:03:23 -070018import static java.util.concurrent.Executors.newFixedThreadPool;
19import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
20import static org.onlab.packet.MacAddress.valueOf;
21import static org.onlab.util.Tools.groupedThreads;
22import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
23import static org.opencord.dhcpl2relay.impl.OsgiPropertyConstants.ENABLE_DHCP_BROADCAST_REPLIES;
24import static org.opencord.dhcpl2relay.impl.OsgiPropertyConstants.ENABLE_DHCP_BROADCAST_REPLIES_DEFAULT;
25import static org.opencord.dhcpl2relay.impl.OsgiPropertyConstants.OPTION_82;
26import static org.opencord.dhcpl2relay.impl.OsgiPropertyConstants.OPTION_82_DEFAULT;
27import static org.opencord.dhcpl2relay.impl.OsgiPropertyConstants.PACKET_PROCESSOR_THREADS;
28import static org.opencord.dhcpl2relay.impl.OsgiPropertyConstants.PACKET_PROCESSOR_THREADS_DEFAULT;
29
30import java.io.ByteArrayOutputStream;
31import java.nio.ByteBuffer;
32import java.time.Instant;
33import java.util.ArrayList;
34import java.util.Dictionary;
35import java.util.List;
36import java.util.Map;
37import java.util.Optional;
38import java.util.Set;
39import java.util.UUID;
40import java.util.concurrent.ExecutorService;
41import java.util.concurrent.Executors;
42import java.util.concurrent.ScheduledExecutorService;
43import java.util.concurrent.ScheduledFuture;
44import java.util.concurrent.atomic.AtomicReference;
45import java.util.function.Predicate;
46import java.util.stream.Collectors;
47
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -030048import org.apache.commons.io.HexDump;
Amit Ghosh47243cb2017-07-26 05:08:53 +010049import org.onlab.packet.DHCP;
Deepa vaddireddy0060f532017-08-04 06:46:05 +000050import org.onlab.packet.Ethernet;
Amit Ghosh47243cb2017-07-26 05:08:53 +010051import org.onlab.packet.IPv4;
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +053052import org.onlab.packet.IpAddress;
Deepa vaddireddy0060f532017-08-04 06:46:05 +000053import org.onlab.packet.MacAddress;
Amit Ghosh47243cb2017-07-26 05:08:53 +010054import org.onlab.packet.TpPort;
55import org.onlab.packet.UDP;
56import org.onlab.packet.VlanId;
Jonathan Hartedbf6422018-05-02 17:30:05 -070057import org.onlab.packet.dhcp.DhcpOption;
Saurav Das45861d42020-10-07 00:03:23 -070058import org.onlab.packet.dhcp.DhcpRelayAgentOption;
Jonathan Hart617bc3e2020-02-14 10:42:23 -080059import org.onlab.util.KryoNamespace;
Amit Ghosh47243cb2017-07-26 05:08:53 +010060import org.onlab.util.Tools;
61import org.onosproject.cfg.ComponentConfigService;
Jonathan Hart617bc3e2020-02-14 10:42:23 -080062import org.onosproject.cluster.ClusterService;
63import org.onosproject.cluster.LeadershipService;
Andrea Campanella6f45a1b2020-05-08 17:50:12 +020064import org.onosproject.cluster.NodeId;
Amit Ghosh47243cb2017-07-26 05:08:53 +010065import org.onosproject.core.ApplicationId;
66import org.onosproject.core.CoreService;
Jonathan Hartc36c9552018-07-31 15:07:53 -040067import org.onosproject.event.AbstractListenerManager;
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +053068import org.onosproject.mastership.MastershipEvent;
69import org.onosproject.mastership.MastershipListener;
70import org.onosproject.mastership.MastershipService;
Amit Ghosh47243cb2017-07-26 05:08:53 +010071import org.onosproject.net.AnnotationKeys;
72import org.onosproject.net.ConnectPoint;
Amit Ghosh83c8c892017-11-09 11:08:27 +000073import org.onosproject.net.Device;
74import org.onosproject.net.DeviceId;
Amit Ghosh47243cb2017-07-26 05:08:53 +010075import org.onosproject.net.Host;
76import org.onosproject.net.Port;
Amit Ghosh83c8c892017-11-09 11:08:27 +000077import org.onosproject.net.PortNumber;
Amit Ghosh47243cb2017-07-26 05:08:53 +010078import org.onosproject.net.config.ConfigFactory;
79import org.onosproject.net.config.NetworkConfigEvent;
80import org.onosproject.net.config.NetworkConfigListener;
81import org.onosproject.net.config.NetworkConfigRegistry;
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +053082import org.onosproject.net.device.DeviceEvent;
83import org.onosproject.net.device.DeviceListener;
Amit Ghosh47243cb2017-07-26 05:08:53 +010084import org.onosproject.net.device.DeviceService;
85import org.onosproject.net.flow.DefaultTrafficSelector;
86import org.onosproject.net.flow.DefaultTrafficTreatment;
87import org.onosproject.net.flow.TrafficSelector;
88import org.onosproject.net.flow.TrafficTreatment;
Saurav Dasb4e3e102018-10-02 15:31:17 -070089import org.onosproject.net.flowobjective.FlowObjectiveService;
Amit Ghosh47243cb2017-07-26 05:08:53 +010090import org.onosproject.net.host.HostService;
91import org.onosproject.net.packet.DefaultOutboundPacket;
92import org.onosproject.net.packet.OutboundPacket;
93import org.onosproject.net.packet.PacketContext;
94import org.onosproject.net.packet.PacketPriority;
95import org.onosproject.net.packet.PacketProcessor;
96import org.onosproject.net.packet.PacketService;
Jonathan Hart617bc3e2020-02-14 10:42:23 -080097import org.onosproject.store.serializers.KryoNamespaces;
98import org.onosproject.store.service.ConsistentMap;
99import org.onosproject.store.service.Serializer;
100import org.onosproject.store.service.StorageService;
101import org.onosproject.store.service.Versioned;
Matteo Scandolo57af5d12019-04-29 17:11:41 -0700102import org.opencord.dhcpl2relay.DhcpAllocationInfo;
103import org.opencord.dhcpl2relay.DhcpL2RelayEvent;
104import org.opencord.dhcpl2relay.DhcpL2RelayListener;
105import org.opencord.dhcpl2relay.DhcpL2RelayService;
Jonathan Hart77ca3152020-02-21 14:31:21 -0800106import org.opencord.dhcpl2relay.DhcpL2RelayStoreDelegate;
Saurav Das45861d42020-10-07 00:03:23 -0700107import org.opencord.dhcpl2relay.impl.packet.DhcpOption82Data;
Gamze Abakac806c6c2018-12-03 12:49:46 +0000108import org.opencord.sadis.BaseInformationService;
109import org.opencord.sadis.SadisService;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100110import org.opencord.sadis.SubscriberAndDeviceInformation;
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000111import org.opencord.sadis.UniTagInformation;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100112import org.osgi.service.component.ComponentContext;
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700113import org.osgi.service.component.annotations.Activate;
114import org.osgi.service.component.annotations.Component;
115import org.osgi.service.component.annotations.Deactivate;
116import org.osgi.service.component.annotations.Modified;
117import org.osgi.service.component.annotations.Reference;
118import org.osgi.service.component.annotations.ReferenceCardinality;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100119import org.slf4j.Logger;
120import org.slf4j.LoggerFactory;
121
Saurav Das45861d42020-10-07 00:03:23 -0700122import com.google.common.base.Strings;
123import com.google.common.collect.ImmutableMap;
124import com.google.common.collect.ImmutableSet;
125import com.google.common.collect.Lists;
126import com.google.common.collect.Sets;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100127
128/**
129 * DHCP Relay Agent Application Component.
130 */
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700131@Component(immediate = true,
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700132 property = {
133 OPTION_82 + ":Boolean=" + OPTION_82_DEFAULT,
134 ENABLE_DHCP_BROADCAST_REPLIES + ":Boolean=" + ENABLE_DHCP_BROADCAST_REPLIES_DEFAULT,
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800135 PACKET_PROCESSOR_THREADS + ":Integer=" + PACKET_PROCESSOR_THREADS_DEFAULT,
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700136 })
Jonathan Hartc36c9552018-07-31 15:07:53 -0400137public class DhcpL2Relay
138 extends AbstractListenerManager<DhcpL2RelayEvent, DhcpL2RelayListener>
139 implements DhcpL2RelayService {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100140
141 public static final String DHCP_L2RELAY_APP = "org.opencord.dhcpl2relay";
Saurav Dasb4e3e102018-10-02 15:31:17 -0700142 private static final String HOST_LOC_PROVIDER =
143 "org.onosproject.provider.host.impl.HostLocationProvider";
Amit Ghosh47243cb2017-07-26 05:08:53 +0100144 private final Logger log = LoggerFactory.getLogger(getClass());
145 private final InternalConfigListener cfgListener =
146 new InternalConfigListener();
147
148 private final Set<ConfigFactory> factories = ImmutableSet.of(
149 new ConfigFactory<ApplicationId, DhcpL2RelayConfig>(APP_SUBJECT_FACTORY,
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700150 DhcpL2RelayConfig.class,
151 "dhcpl2relay") {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100152 @Override
153 public DhcpL2RelayConfig createConfig() {
154 return new DhcpL2RelayConfig();
155 }
156 }
157 );
158
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700159 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh47243cb2017-07-26 05:08:53 +0100160 protected NetworkConfigRegistry cfgService;
161
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700162 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh47243cb2017-07-26 05:08:53 +0100163 protected CoreService coreService;
164
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700165 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh47243cb2017-07-26 05:08:53 +0100166 protected PacketService packetService;
167
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700168 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh47243cb2017-07-26 05:08:53 +0100169 protected HostService hostService;
170
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700171 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh47243cb2017-07-26 05:08:53 +0100172 protected ComponentConfigService componentConfigService;
173
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700174 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Gamze Abakac806c6c2018-12-03 12:49:46 +0000175 protected SadisService sadisService;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100176
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700177 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh47243cb2017-07-26 05:08:53 +0100178 protected DeviceService deviceService;
179
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700180 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Amit Ghosh8951f042017-08-10 13:48:10 +0100181 protected MastershipService mastershipService;
182
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700183 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jonathan Hart617bc3e2020-02-14 10:42:23 -0800184 protected StorageService storageService;
185
186 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Saurav Dasb4e3e102018-10-02 15:31:17 -0700187 protected FlowObjectiveService flowObjectiveService;
188
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300189 @Reference(cardinality = ReferenceCardinality.MANDATORY)
190 protected DhcpL2RelayCountersStore dhcpL2RelayCounters;
191
Jonathan Hart617bc3e2020-02-14 10:42:23 -0800192 @Reference(cardinality = ReferenceCardinality.MANDATORY)
193 protected LeadershipService leadershipService;
194
195 @Reference(cardinality = ReferenceCardinality.MANDATORY)
196 protected ClusterService clusterService;
197
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300198 // OSGi Properties
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700199 /**
200 * Add option 82 to relayed packets.
201 */
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700202 protected boolean option82 = OPTION_82_DEFAULT;
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700203 /**
204 * Ask the DHCP Server to send back replies as L2 broadcast.
205 */
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700206 protected boolean enableDhcpBroadcastReplies = ENABLE_DHCP_BROADCAST_REPLIES_DEFAULT;
Amit Ghosha17354e2017-08-23 12:56:04 +0100207
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800208 /**
209 * Number of threads used to process the packet.
210 */
211 protected int packetProcessorThreads = PACKET_PROCESSOR_THREADS_DEFAULT;
212
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300213 ScheduledFuture<?> refreshTask;
214 ScheduledExecutorService refreshService = Executors.newSingleThreadScheduledExecutor();
215
Amit Ghosh47243cb2017-07-26 05:08:53 +0100216 private DhcpRelayPacketProcessor dhcpRelayPacketProcessor =
217 new DhcpRelayPacketProcessor();
218
Amit Ghosh8951f042017-08-10 13:48:10 +0100219 private InnerMastershipListener changeListener = new InnerMastershipListener();
220 private InnerDeviceListener deviceListener = new InnerDeviceListener();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100221
Amit Ghosh8951f042017-08-10 13:48:10 +0100222 // connect points to the DHCP server
223 Set<ConnectPoint> dhcpConnectPoints;
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300224 protected AtomicReference<ConnectPoint> dhcpServerConnectPoint = new AtomicReference<>();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100225 private MacAddress dhcpConnectMac = MacAddress.BROADCAST;
226 private ApplicationId appId;
227
Jonathan Hart617bc3e2020-02-14 10:42:23 -0800228 private ConsistentMap<String, DhcpAllocationInfo> allocations;
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300229 protected boolean modifyClientPktsSrcDstMac = false;
Amit Ghosh83c8c892017-11-09 11:08:27 +0000230 //Whether to use the uplink port of the OLTs to send/receive messages to the DHCP server
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300231 protected boolean useOltUplink = false;
Amit Ghosha17354e2017-08-23 12:56:04 +0100232
Gamze Abakac806c6c2018-12-03 12:49:46 +0000233 private BaseInformationService<SubscriberAndDeviceInformation> subsService;
234
Jonathan Hart77ca3152020-02-21 14:31:21 -0800235 private DhcpL2RelayStoreDelegate delegate = new InnerDhcpL2RelayStoreDelegate();
236
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800237 protected ExecutorService packetProcessorExecutor;
238
Amit Ghosh47243cb2017-07-26 05:08:53 +0100239 @Activate
240 protected void activate(ComponentContext context) {
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800241
Amit Ghosh47243cb2017-07-26 05:08:53 +0100242 //start the dhcp relay agent
243 appId = coreService.registerApplication(DHCP_L2RELAY_APP);
Saurav Dasb4e3e102018-10-02 15:31:17 -0700244 // ensure that host-learning via dhcp includes IP addresses
245 componentConfigService.preSetProperty(HOST_LOC_PROVIDER,
246 "useDhcp", Boolean.TRUE.toString());
Amit Ghosh47243cb2017-07-26 05:08:53 +0100247 componentConfigService.registerProperties(getClass());
Jonathan Hartc36c9552018-07-31 15:07:53 -0400248 eventDispatcher.addSink(DhcpL2RelayEvent.class, listenerRegistry);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100249
Jonathan Hart617bc3e2020-02-14 10:42:23 -0800250 KryoNamespace serializer = KryoNamespace.newBuilder()
251 .register(KryoNamespaces.API)
252 .register(Instant.class)
253 .register(DHCP.MsgType.class)
254 .register(DhcpAllocationInfo.class)
255 .build();
256
257 allocations = storageService.<String, DhcpAllocationInfo>consistentMapBuilder()
258 .withName("dhcpl2relay-allocations")
259 .withSerializer(Serializer.using(serializer))
260 .withApplicationId(appId)
261 .build();
262
Jonathan Hart77ca3152020-02-21 14:31:21 -0800263 dhcpL2RelayCounters.setDelegate(delegate);
264
Amit Ghosh47243cb2017-07-26 05:08:53 +0100265 cfgService.addListener(cfgListener);
Amit Ghosh8951f042017-08-10 13:48:10 +0100266 mastershipService.addListener(changeListener);
267 deviceService.addListener(deviceListener);
268
Matteo Scandolo45e5a272019-09-30 09:30:32 -0700269 subsService = sadisService.getSubscriberInfoService();
270
Amit Ghosh47243cb2017-07-26 05:08:53 +0100271 factories.forEach(cfgService::registerConfigFactory);
272 //update the dhcp server configuration.
273 updateConfig();
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800274
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000275 if (context != null) {
276 modified(context);
277 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100278
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800279 //add the packet services.
280 packetService.addProcessor(dhcpRelayPacketProcessor,
281 PacketProcessor.director(0));
282
Amit Ghosh47243cb2017-07-26 05:08:53 +0100283 log.info("DHCP-L2-RELAY Started");
284 }
285
286 @Deactivate
287 protected void deactivate() {
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300288 if (refreshTask != null) {
289 refreshTask.cancel(true);
290 }
291 if (refreshService != null) {
292 refreshService.shutdownNow();
293 }
Jonathan Hart77ca3152020-02-21 14:31:21 -0800294 dhcpL2RelayCounters.unsetDelegate(delegate);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100295 cfgService.removeListener(cfgListener);
296 factories.forEach(cfgService::unregisterConfigFactory);
297 packetService.removeProcessor(dhcpRelayPacketProcessor);
Saurav Dasb4e3e102018-10-02 15:31:17 -0700298 cancelDhcpPktsFromServer();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100299
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800300 packetProcessorExecutor.shutdown();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100301 componentConfigService.unregisterProperties(getClass(), false);
Deepa Vaddireddy77a6ac72017-09-20 20:36:52 +0530302 deviceService.removeListener(deviceListener);
303 mastershipService.removeListener(changeListener);
Jonathan Hartc36c9552018-07-31 15:07:53 -0400304 eventDispatcher.removeSink(DhcpL2RelayEvent.class);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100305 log.info("DHCP-L2-RELAY Stopped");
306 }
307
308 @Modified
309 protected void modified(ComponentContext context) {
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000310
Amit Ghosh47243cb2017-07-26 05:08:53 +0100311 Dictionary<?, ?> properties = context.getProperties();
312
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700313 Boolean o = Tools.isPropertyEnabled(properties, OPTION_82);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100314 if (o != null) {
315 option82 = o;
316 }
Amit Ghosh2095dc62017-09-25 20:56:55 +0100317
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700318 o = Tools.isPropertyEnabled(properties, ENABLE_DHCP_BROADCAST_REPLIES);
Amit Ghosh2095dc62017-09-25 20:56:55 +0100319 if (o != null) {
320 enableDhcpBroadcastReplies = o;
321 }
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800322
323 String s = Tools.get(properties, PACKET_PROCESSOR_THREADS);
324 int oldpacketProcessorThreads = packetProcessorThreads;
325 packetProcessorThreads = Strings.isNullOrEmpty(s) ? oldpacketProcessorThreads
326 : Integer.parseInt(s.trim());
327 if (packetProcessorExecutor == null || oldpacketProcessorThreads != packetProcessorThreads) {
328 if (packetProcessorExecutor != null) {
329 packetProcessorExecutor.shutdown();
330 }
331 packetProcessorExecutor = newFixedThreadPool(packetProcessorThreads,
332 groupedThreads("onos/dhcp",
333 "dhcp-packet-%d", log));
334 }
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300335 }
336
Jonathan Hart617bc3e2020-02-14 10:42:23 -0800337 @Override
338 public Map<String, DhcpAllocationInfo> getAllocationInfo() {
339 return ImmutableMap.copyOf(allocations.asJavaMap());
340 }
341
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300342 /**
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700343 * Generates a unique UUID from a string.
344 *
345 * @return true if all information we need have been initialized
346 */
347 private static String getUniqueUuidFromString(String value) {
348 return UUID.nameUUIDFromBytes(value.getBytes()).toString();
349 }
350
351 /**
Amit Ghosh47243cb2017-07-26 05:08:53 +0100352 * Checks if this app has been configured.
353 *
354 * @return true if all information we need have been initialized
355 */
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300356 protected boolean configured() {
Amit Ghosh83c8c892017-11-09 11:08:27 +0000357 if (!useOltUplink) {
358 return dhcpServerConnectPoint.get() != null;
359 }
360 return true;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100361 }
362
Amit Ghosh8951f042017-08-10 13:48:10 +0100363 /**
364 * Selects a connect point through an available device for which it is the master.
365 */
366 private void selectServerConnectPoint() {
367 synchronized (this) {
368 dhcpServerConnectPoint.set(null);
369 if (dhcpConnectPoints != null) {
370 // find a connect point through a device for which we are master
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700371 for (ConnectPoint cp : dhcpConnectPoints) {
Andrea Campanella6f45a1b2020-05-08 17:50:12 +0200372 if (isLocalLeader(cp.deviceId())) {
Amit Ghosh8951f042017-08-10 13:48:10 +0100373 if (deviceService.isAvailable(cp.deviceId())) {
374 dhcpServerConnectPoint.set(cp);
375 }
376 log.info("DHCP connectPoint selected is {}", cp);
377 break;
378 }
379 }
380 }
381
382 log.info("DHCP Server connectPoint is {}", dhcpServerConnectPoint.get());
383
384 if (dhcpServerConnectPoint.get() == null) {
385 log.error("Master of none, can't relay DHCP Message to server");
386 }
387 }
388 }
389
390 /**
391 * Updates the network configuration.
392 */
Amit Ghosh47243cb2017-07-26 05:08:53 +0100393 private void updateConfig() {
394 DhcpL2RelayConfig cfg = cfgService.getConfig(appId, DhcpL2RelayConfig.class);
395 if (cfg == null) {
396 log.warn("Dhcp Server info not available");
397 return;
398 }
Amit Ghosh8951f042017-08-10 13:48:10 +0100399
400 dhcpConnectPoints = Sets.newConcurrentHashSet(cfg.getDhcpServerConnectPoint());
Amit Ghosh83c8c892017-11-09 11:08:27 +0000401 modifyClientPktsSrcDstMac = cfg.getModifySrcDstMacAddresses();
Saurav Dasb4e3e102018-10-02 15:31:17 -0700402 boolean prevUseOltUplink = useOltUplink;
Amit Ghosh83c8c892017-11-09 11:08:27 +0000403 useOltUplink = cfg.getUseOltUplinkForServerPktInOut();
Amit Ghosh8951f042017-08-10 13:48:10 +0100404
Saurav Dasb4e3e102018-10-02 15:31:17 -0700405 if (useOltUplink) {
406 for (ConnectPoint cp : getUplinkPortsOfOlts()) {
407 log.debug("requestDhcpPackets: ConnectPoint: {}", cp);
Matteo Scandolo45e5a272019-09-30 09:30:32 -0700408 requestDhcpPacketsFromConnectPoint(cp, Optional.ofNullable(null));
Saurav Dasb4e3e102018-10-02 15:31:17 -0700409 }
410 // check if previous config was different and so trap flows may
Saurav Dasb14f08a2019-02-22 16:34:15 -0800411 // need to be removed from other places like AGG switches
Saurav Dasb4e3e102018-10-02 15:31:17 -0700412 if (!prevUseOltUplink) {
Saurav Dasb14f08a2019-02-22 16:34:15 -0800413 addOrRemoveDhcpTrapFromServer(false);
Saurav Dasb4e3e102018-10-02 15:31:17 -0700414 }
Saurav Dasb4e3e102018-10-02 15:31:17 -0700415 } else {
Saurav Dasb14f08a2019-02-22 16:34:15 -0800416 // uplink on AGG switch
417 addOrRemoveDhcpTrapFromServer(true);
Saurav Dasb4e3e102018-10-02 15:31:17 -0700418 }
419 }
420
421 private void cancelDhcpPktsFromServer() {
422 if (useOltUplink) {
423 for (ConnectPoint cp : getUplinkPortsOfOlts()) {
424 log.debug("cancelDhcpPackets: ConnectPoint: {}", cp);
Matteo Scandolo45e5a272019-09-30 09:30:32 -0700425 cancelDhcpPacketsFromConnectPoint(cp, Optional.ofNullable(null));
Saurav Dasb4e3e102018-10-02 15:31:17 -0700426 }
427 } else {
Saurav Dasb14f08a2019-02-22 16:34:15 -0800428 // uplink on AGG switch
429 addOrRemoveDhcpTrapFromServer(false);
Amit Ghosh83c8c892017-11-09 11:08:27 +0000430 }
Saurav Dasb4e3e102018-10-02 15:31:17 -0700431 }
432
Saurav Dasb14f08a2019-02-22 16:34:15 -0800433 /**
434 * Used to add or remove DHCP trap flow for packets received from DHCP server.
435 * Typically used on a non OLT device, like an AGG switch. When adding, a
436 * new dhcp server connect point is selected from the configured options.
437 *
438 * @param add true if dhcp trap flow is to be added, false to remove the
439 * trap flow
440 */
441 private void addOrRemoveDhcpTrapFromServer(boolean add) {
442 if (add) {
443 selectServerConnectPoint();
444 log.debug("dhcp server connect point: " + dhcpServerConnectPoint);
445 }
446 if (dhcpServerConnectPoint.get() == null) {
447 log.warn("No dhcpServer connectPoint found, cannot {} dhcp trap flows",
448 (add) ? "install" : "remove");
449 return;
450 }
451 if (add) {
452 log.info("Adding trap to dhcp server connect point: "
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700453 + dhcpServerConnectPoint);
Saurav Dasb14f08a2019-02-22 16:34:15 -0800454 requestDhcpPacketsFromConnectPoint(dhcpServerConnectPoint.get(),
455 Optional.of(PacketPriority.HIGH1));
456 } else {
457 log.info("Removing trap from dhcp server connect point: "
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700458 + dhcpServerConnectPoint);
Saurav Dasb14f08a2019-02-22 16:34:15 -0800459 cancelDhcpPacketsFromConnectPoint(dhcpServerConnectPoint.get(),
460 Optional.of(PacketPriority.HIGH1));
461 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100462 }
463
464 /**
Amit Ghosh83c8c892017-11-09 11:08:27 +0000465 * Returns all the uplink ports of OLTs configured in SADIS.
466 * Only ports visible in ONOS and for which this instance is master
467 * are returned
468 */
469 private List<ConnectPoint> getUplinkPortsOfOlts() {
470 List<ConnectPoint> cps = new ArrayList<>();
471
472 // find all the olt devices and if their uplink ports are visible
473 Iterable<Device> devices = deviceService.getDevices();
474 for (Device d : devices) {
475 // check if this device is provisioned in Sadis
476
477 log.debug("getUplinkPortsOfOlts: Checking mastership of {}", d);
478 // do only for devices for which we are the master
Andrea Campanella6f45a1b2020-05-08 17:50:12 +0200479 if (!isLocalLeader(d.id())) {
Amit Ghosh83c8c892017-11-09 11:08:27 +0000480 continue;
481 }
482
483 String devSerialNo = d.serialNumber();
484 SubscriberAndDeviceInformation deviceInfo = subsService.get(devSerialNo);
485 log.debug("getUplinkPortsOfOlts: Found device: {}", deviceInfo);
486 if (deviceInfo != null) {
487 // check if the uplink port with that number is available on the device
488 PortNumber pNum = PortNumber.portNumber(deviceInfo.uplinkPort());
489 Port port = deviceService.getPort(d.id(), pNum);
490 log.debug("getUplinkPortsOfOlts: Found port: {}", port);
491 if (port != null) {
492 cps.add(new ConnectPoint(d.id(), pNum));
493 }
494 }
495 }
496 return cps;
497 }
498
499 /**
500 * Returns whether the passed port is the uplink port of the olt device.
501 */
502 private boolean isUplinkPortOfOlt(DeviceId dId, Port p) {
503 log.debug("isUplinkPortOfOlt: DeviceId: {} Port: {}", dId, p);
Amit Ghosh83c8c892017-11-09 11:08:27 +0000504
505 Device d = deviceService.getDevice(dId);
506 SubscriberAndDeviceInformation deviceInfo = subsService.get(d.serialNumber());
507
508 if (deviceInfo != null) {
509 return (deviceInfo.uplinkPort() == p.number().toLong());
510 }
511
512 return false;
513 }
514
515 /**
516 * Returns the connectPoint which is the uplink port of the OLT.
517 */
518 private ConnectPoint getUplinkConnectPointOfOlt(DeviceId dId) {
519
520 Device d = deviceService.getDevice(dId);
521 SubscriberAndDeviceInformation deviceInfo = subsService.get(d.serialNumber());
522 log.debug("getUplinkConnectPointOfOlt DeviceId: {} devInfo: {}", dId, deviceInfo);
523 if (deviceInfo != null) {
524 PortNumber pNum = PortNumber.portNumber(deviceInfo.uplinkPort());
525 Port port = deviceService.getPort(d.id(), pNum);
526 if (port != null) {
527 return new ConnectPoint(d.id(), pNum);
528 }
529 }
530
531 return null;
532 }
533
534 /**
535 * Request DHCP packet from particular connect point via PacketService.
Saurav Dasb14f08a2019-02-22 16:34:15 -0800536 * Optionally provide a priority for the trap flow. If no such priority is
537 * provided, the default priority will be used.
538 *
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700539 * @param cp the connect point to trap dhcp packets from
Saurav Dasb14f08a2019-02-22 16:34:15 -0800540 * @param priority of the trap flow, null to use default priority
Amit Ghosh83c8c892017-11-09 11:08:27 +0000541 */
Saurav Dasb14f08a2019-02-22 16:34:15 -0800542 private void requestDhcpPacketsFromConnectPoint(ConnectPoint cp,
543 Optional<PacketPriority> priority) {
Amit Ghosh83c8c892017-11-09 11:08:27 +0000544 TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
545 .matchEthType(Ethernet.TYPE_IPV4)
546 .matchInPort(cp.port())
547 .matchIPProtocol(IPv4.PROTOCOL_UDP)
548 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
549 packetService.requestPackets(selectorServer.build(),
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700550 priority.isPresent() ? priority.get() : PacketPriority.CONTROL,
551 appId, Optional.of(cp.deviceId()));
Amit Ghosh83c8c892017-11-09 11:08:27 +0000552 }
553
554 /**
Saurav Dasb14f08a2019-02-22 16:34:15 -0800555 * Cancel DHCP packet from particular connect point via PacketService. If
556 * the request was made with a specific packet priority, then the same
557 * priority should be used in this call.
558 *
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700559 * @param cp the connect point for the trap flow
Saurav Dasb14f08a2019-02-22 16:34:15 -0800560 * @param priority with which the trap flow was requested; if request
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700561 * priority was not specified, this param should also be null
Amit Ghosh83c8c892017-11-09 11:08:27 +0000562 */
Saurav Dasb14f08a2019-02-22 16:34:15 -0800563 private void cancelDhcpPacketsFromConnectPoint(ConnectPoint cp,
564 Optional<PacketPriority> priority) {
Amit Ghosh83c8c892017-11-09 11:08:27 +0000565 TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
566 .matchEthType(Ethernet.TYPE_IPV4)
567 .matchInPort(cp.port())
568 .matchIPProtocol(IPv4.PROTOCOL_UDP)
569 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
570 packetService.cancelPackets(selectorServer.build(),
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700571 priority.isPresent() ? priority.get() : PacketPriority.CONTROL,
572 appId, Optional.of(cp.deviceId()));
Amit Ghosh83c8c892017-11-09 11:08:27 +0000573 }
574
Saurav Das45861d42020-10-07 00:03:23 -0700575 /**
576 * Main packet-processing engine for dhcp l2 relay agent.
577 */
Amit Ghosh47243cb2017-07-26 05:08:53 +0100578 private class DhcpRelayPacketProcessor implements PacketProcessor {
Saurav Das45861d42020-10-07 00:03:23 -0700579 private static final String VLAN_KEYWORD = ":vlan";
580 private static final String PCP_KEYWORD = ":pcp";
Amit Ghosh47243cb2017-07-26 05:08:53 +0100581
582 @Override
583 public void process(PacketContext context) {
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800584 packetProcessorExecutor.execute(() -> {
585 processInternal(context);
586 });
587 }
588
589 private void processInternal(PacketContext context) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100590 if (!configured()) {
591 log.warn("Missing DHCP relay config. Abort packet processing");
592 return;
593 }
594
595 // process the packet and get the payload
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530596 Ethernet packet = context.inPacket().parsed();
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000597
Amit Ghosh47243cb2017-07-26 05:08:53 +0100598 if (packet == null) {
599 log.warn("Packet is null");
600 return;
601 }
602
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530603 if (packet.getEtherType() == Ethernet.TYPE_IPV4) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100604 IPv4 ipv4Packet = (IPv4) packet.getPayload();
605
606 if (ipv4Packet.getProtocol() == IPv4.PROTOCOL_UDP) {
607 UDP udpPacket = (UDP) ipv4Packet.getPayload();
608 if (udpPacket.getSourcePort() == UDP.DHCP_CLIENT_PORT ||
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000609 udpPacket.getSourcePort() == UDP.DHCP_SERVER_PORT) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100610 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
Matteo Scandoloa8b6eea2018-11-27 13:47:58 -0800611 if (log.isTraceEnabled()) {
612 log.trace("Processing packet with type {} from MAC {}",
613 getDhcpPacketType(dhcpPayload),
614 MacAddress.valueOf(dhcpPayload.getClientHardwareAddress()));
615 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100616 //This packet is dhcp.
617 processDhcpPacket(context, packet, dhcpPayload);
618 }
619 }
620 }
621 }
622
Saurav Dasbd5ce9c2020-09-04 18:46:45 -0700623 // process the dhcp packet before relaying to server or client
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530624 private void processDhcpPacket(PacketContext context, Ethernet packet,
Amit Ghosh47243cb2017-07-26 05:08:53 +0100625 DHCP dhcpPayload) {
626 if (dhcpPayload == null) {
627 log.warn("DHCP payload is null");
628 return;
629 }
630
Carmelo Casconede1e6e32019-07-15 19:39:08 -0700631 DHCP.MsgType incomingPacketType = getDhcpPacketType(dhcpPayload);
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300632 if (incomingPacketType == null) {
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700633 log.warn("DHCP Packet type not found. Dump of ethernet pkt in hex format for troubleshooting.");
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300634 byte[] array = packet.serialize();
635 ByteArrayOutputStream buf = new ByteArrayOutputStream();
636 try {
637 HexDump.dump(array, 0, buf, 0);
638 log.trace(buf.toString());
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700639 } catch (Exception e) {
640 }
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300641 return;
642 }
643
644 SubscriberAndDeviceInformation entry = null;
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000645
Matteo Scandoloeb5a0dc2020-09-15 14:54:28 -0700646 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
647
648 log.info("Received DHCP Packet of type {} from {} with Client MacAddress {} and vlan {}",
649 incomingPacketType, context.inPacket().receivedFrom(),
650 clientMacAddress, packet.getVlanID());
Amit Ghosh47243cb2017-07-26 05:08:53 +0100651
652 switch (incomingPacketType) {
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000653 case DHCPDISCOVER:
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530654 Ethernet ethernetPacketDiscover =
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000655 processDhcpPacketFromClient(context, packet);
656 if (ethernetPacketDiscover != null) {
Saurav Das45861d42020-10-07 00:03:23 -0700657 relayPacketToServer(ethernetPacketDiscover, context);
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000658 }
Saurav Das45861d42020-10-07 00:03:23 -0700659 entry = getSubscriber(context);
Jonathan Hart77ca3152020-02-21 14:31:21 -0800660 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames.valueOf("DHCPDISCOVER"));
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000661 break;
662 case DHCPOFFER:
Saurav Das45861d42020-10-07 00:03:23 -0700663 RelayToClientInfo r2cDataOffer =
Saurav Das15626a02018-09-27 18:36:45 -0700664 processDhcpPacketFromServer(context, packet);
Saurav Das45861d42020-10-07 00:03:23 -0700665 if (r2cDataOffer != null) {
666 relayPacketToClient(r2cDataOffer, clientMacAddress);
667 entry = getSubscriber(r2cDataOffer.cp);
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000668 }
Jonathan Hart77ca3152020-02-21 14:31:21 -0800669 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames.valueOf("DHCPOFFER"));
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000670 break;
671 case DHCPREQUEST:
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530672 Ethernet ethernetPacketRequest =
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000673 processDhcpPacketFromClient(context, packet);
674 if (ethernetPacketRequest != null) {
Saurav Das45861d42020-10-07 00:03:23 -0700675 relayPacketToServer(ethernetPacketRequest, context);
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000676 }
Saurav Das45861d42020-10-07 00:03:23 -0700677 entry = getSubscriber(context);
Jonathan Hart77ca3152020-02-21 14:31:21 -0800678 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames.valueOf("DHCPREQUEST"));
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000679 break;
680 case DHCPACK:
Saurav Das45861d42020-10-07 00:03:23 -0700681 RelayToClientInfo r2cDataAck =
Saurav Das15626a02018-09-27 18:36:45 -0700682 processDhcpPacketFromServer(context, packet);
Saurav Das45861d42020-10-07 00:03:23 -0700683 if (r2cDataAck != null) {
684 relayPacketToClient(r2cDataAck, clientMacAddress);
685 entry = getSubscriber(r2cDataAck.cp);
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000686 }
Jonathan Hart77ca3152020-02-21 14:31:21 -0800687 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames.valueOf("DHCPACK"));
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300688 break;
689 case DHCPDECLINE:
Arjun E K05ad20b2020-03-13 13:25:17 +0000690 Ethernet ethernetPacketDecline =
691 processDhcpPacketFromClient(context, packet);
692 if (ethernetPacketDecline != null) {
Saurav Das45861d42020-10-07 00:03:23 -0700693 relayPacketToServer(ethernetPacketDecline, context);
Arjun E K05ad20b2020-03-13 13:25:17 +0000694 }
Saurav Das45861d42020-10-07 00:03:23 -0700695 entry = getSubscriber(context);
Jonathan Hart77ca3152020-02-21 14:31:21 -0800696 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames.valueOf("DHCPDECLINE"));
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300697 break;
698 case DHCPNAK:
Saurav Das45861d42020-10-07 00:03:23 -0700699 RelayToClientInfo r2cDataNack =
Arjun E K05ad20b2020-03-13 13:25:17 +0000700 processDhcpPacketFromServer(context, packet);
Saurav Das45861d42020-10-07 00:03:23 -0700701 if (r2cDataNack != null) {
702 relayPacketToClient(r2cDataNack, clientMacAddress);
703 entry = getSubscriber(r2cDataNack.cp);
Arjun E K05ad20b2020-03-13 13:25:17 +0000704 }
Jonathan Hart77ca3152020-02-21 14:31:21 -0800705 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames.valueOf("DHCPNACK"));
Marcos Aurelio Carreroeaf02b82019-11-25 13:34:25 -0300706 break;
707 case DHCPRELEASE:
Thomas Lee S0dc9a3b2020-01-14 10:42:29 +0530708 Ethernet ethernetPacketRelease =
709 processDhcpPacketFromClient(context, packet);
710 if (ethernetPacketRelease != null) {
Saurav Das45861d42020-10-07 00:03:23 -0700711 relayPacketToServer(ethernetPacketRelease, context);
Thomas Lee S0dc9a3b2020-01-14 10:42:29 +0530712 }
Saurav Das45861d42020-10-07 00:03:23 -0700713 entry = getSubscriber(context);
Jonathan Hart77ca3152020-02-21 14:31:21 -0800714 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames.valueOf("DHCPRELEASE"));
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000715 break;
716 default:
717 break;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100718 }
719 }
720
Saurav Das45861d42020-10-07 00:03:23 -0700721 /**
722 * Processes dhcp packets from clients.
723 *
724 * @param context the packet context
725 * @param ethernetPacket the dhcp packet from client
726 * @return the packet to relay to the server
727 */
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530728 private Ethernet processDhcpPacketFromClient(PacketContext context,
729 Ethernet ethernetPacket) {
Saurav Das15626a02018-09-27 18:36:45 -0700730 if (log.isTraceEnabled()) {
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700731 log.trace("DHCP Packet received from client at {} {}",
Saurav Das15626a02018-09-27 18:36:45 -0700732 context.inPacket().receivedFrom(), ethernetPacket);
733 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100734
735 MacAddress relayAgentMac = relayAgentMacAddress(context);
736 if (relayAgentMac == null) {
737 log.warn("RelayAgent MAC not found ");
Amit Ghosh47243cb2017-07-26 05:08:53 +0100738 return null;
739 }
740
pier9e0efbe2020-10-28 20:33:56 +0100741 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100742
743 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
744 UDP udpPacket = (UDP) ipv4Packet.getPayload();
745 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
Saurav Dasbd5ce9c2020-09-04 18:46:45 -0700746 ConnectPoint inPort = context.inPacket().receivedFrom();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100747
Amit Ghosha17354e2017-08-23 12:56:04 +0100748 if (enableDhcpBroadcastReplies) {
749 // We want the reply to come back as a L2 broadcast
750 dhcpPacket.setFlags((short) 0x8000);
751 }
752
Jonathan Hartc36c9552018-07-31 15:07:53 -0400753 MacAddress clientMac = MacAddress.valueOf(dhcpPacket.getClientHardwareAddress());
Saurav Dasbd5ce9c2020-09-04 18:46:45 -0700754 VlanId clientVlan = VlanId.vlanId(ethernetPacket.getVlanID());
Jonathan Hartc36c9552018-07-31 15:07:53 -0400755 IpAddress clientIp = IpAddress.valueOf(dhcpPacket.getClientIPAddress());
Amit Ghosha17354e2017-08-23 12:56:04 +0100756
Jonathan Hartc36c9552018-07-31 15:07:53 -0400757 SubscriberAndDeviceInformation entry = getSubscriber(context);
758 if (entry == null) {
Saurav Das15626a02018-09-27 18:36:45 -0700759 log.warn("Dropping packet as subscriber entry is not available");
Jonathan Hartc36c9552018-07-31 15:07:53 -0400760 return null;
761 }
762
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000763 UniTagInformation uniTagInformation = getUnitagInformationFromPacketContext(context, entry);
764 if (uniTagInformation == null) {
765 log.warn("Missing service information for connectPoint {} / cTag {}",
Saurav Dasbd5ce9c2020-09-04 18:46:45 -0700766 inPort, clientVlan);
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000767 return null;
768 }
Saurav Das45861d42020-10-07 00:03:23 -0700769 DhcpOption82Data d82 = null;
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000770 if (option82) {
Saurav Das45861d42020-10-07 00:03:23 -0700771 DHCP dhcpPacketWithOption82 = addOption82(dhcpPacket, entry,
772 inPort, clientVlan,
773 uniTagInformation
774 .getDsPonCTagPriority());
775 byte[] d82b = dhcpPacketWithOption82
776 .getOption(DHCP.DHCPOptionCode.OptionCode_CircuitID)
777 .getData();
778 d82 = new DhcpOption82Data(d82b);
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000779 udpPacket.setPayload(dhcpPacketWithOption82);
780 }
781
782 ipv4Packet.setPayload(udpPacket);
783 etherReply.setPayload(ipv4Packet);
Amit Ghosh83c8c892017-11-09 11:08:27 +0000784 if (modifyClientPktsSrcDstMac) {
785 etherReply.setSourceMACAddress(relayAgentMac);
786 etherReply.setDestinationMACAddress(dhcpConnectMac);
787 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100788
Amit Ghosh8951f042017-08-10 13:48:10 +0100789 etherReply.setPriorityCode(ethernetPacket.getPriorityCode());
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000790 etherReply.setVlanID(uniTagInformation.getPonCTag().toShort());
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530791 etherReply.setQinQTPID(Ethernet.TYPE_VLAN);
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000792 etherReply.setQinQVID(uniTagInformation.getPonSTag().toShort());
793 if (uniTagInformation.getUsPonSTagPriority() != -1) {
794 etherReply.setQinQPriorityCode((byte) uniTagInformation.getUsPonSTagPriority());
795 }
Saurav Das45861d42020-10-07 00:03:23 -0700796 if (uniTagInformation.getUsPonCTagPriority() != -1) {
797 etherReply.setPriorityCode((byte) uniTagInformation
798 .getUsPonCTagPriority());
799 }
800
801 DhcpAllocationInfo info = new DhcpAllocationInfo(inPort,
802 dhcpPacket.getPacketType(),
803 (d82 == null)
804 ? entry.circuitId()
805 : d82.getAgentCircuitId(),
806 clientMac, clientIp,
807 clientVlan, entry.id());
808 String key = getUniqueUuidFromString(entry.id() + clientMac
809 + clientVlan);
810 allocations.put(key, info);
811 post(new DhcpL2RelayEvent(DhcpL2RelayEvent.Type.UPDATED, info, inPort));
812 if (log.isTraceEnabled()) {
Andrea Campanellad79a6792020-10-26 14:44:46 +0100813 log.trace("Finished processing DHCP Packet of type {} with MAC {} from {} "
Saurav Das45861d42020-10-07 00:03:23 -0700814 + "... relaying to dhcpServer",
Andrea Campanellad79a6792020-10-26 14:44:46 +0100815 dhcpPacket.getPacketType(), clientMac, entry.id());
Saurav Das45861d42020-10-07 00:03:23 -0700816 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100817 return etherReply;
818 }
819
Saurav Das45861d42020-10-07 00:03:23 -0700820 /**
821 * Processes dhcp packets from the server.
822 *
823 * @param context the packet context
824 * @param ethernetPacket the dhcp packet
825 * @return returns information necessary for relaying packet to client
826 */
827 private RelayToClientInfo processDhcpPacketFromServer(PacketContext context,
828 Ethernet ethernetPacket) {
Saurav Das15626a02018-09-27 18:36:45 -0700829 if (log.isTraceEnabled()) {
Matteo Scandolo64bba8c2020-08-19 11:50:33 -0700830 log.trace("DHCP Packet received from server at {} {}",
Saurav Das15626a02018-09-27 18:36:45 -0700831 context.inPacket().receivedFrom(), ethernetPacket);
832 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100833 // get dhcp header.
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530834 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100835 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
836 UDP udpPacket = (UDP) ipv4Packet.getPayload();
Saurav Das45861d42020-10-07 00:03:23 -0700837 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
Saurav Dasbd5ce9c2020-09-04 18:46:45 -0700838 VlanId innerVlan = VlanId.vlanId(ethernetPacket.getVlanID());
Saurav Das45861d42020-10-07 00:03:23 -0700839 MacAddress dstMac = valueOf(dhcpPacket.getClientHardwareAddress());
Amit Ghosh47243cb2017-07-26 05:08:53 +0100840
Saurav Das45861d42020-10-07 00:03:23 -0700841 // we leave the srcMac from the original packet.
842 // TODO remove S-VLAN
Gamze Abakaa64b3bc2020-01-31 06:51:43 +0000843 etherReply.setQinQVID(VlanId.NO_VID);
844 etherReply.setQinQPriorityCode((byte) 0);
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530845 etherReply.setDestinationMACAddress(dstMac);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100846
Saurav Das45861d42020-10-07 00:03:23 -0700847 // TODO deserialization of dhcp option82 leaves 'data' field null
848 // As a result we need to retrieve suboption data
849 RelayToClientInfo r2cData = null;
850 boolean usedOption82 = false;
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000851 if (option82) {
Saurav Das45861d42020-10-07 00:03:23 -0700852 // retrieve connectPoint and vlan from option82, if it is in expected format
853 DhcpOption opt = dhcpPacket
854 .getOption(DHCP.DHCPOptionCode.OptionCode_CircuitID);
855 if (opt != null && opt instanceof DhcpRelayAgentOption) {
856 DhcpRelayAgentOption d82 = (DhcpRelayAgentOption) opt;
857 DhcpOption d82ckt = d82.getSubOption(DhcpOption82Data.CIRCUIT_ID_CODE);
858 if (d82ckt.getData() != null) {
859 r2cData = decodeCircuitId(new String(d82ckt.getData()));
860 }
861 }
862 if (r2cData != null) {
863 usedOption82 = true;
864 etherReply.setVlanID(r2cData.cvid.toShort());
865 if (r2cData.pcp != -1) {
866 etherReply.setPriorityCode((byte) r2cData.pcp);
867 }
868 }
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000869 }
Saurav Das45861d42020-10-07 00:03:23 -0700870 // always remove option82 if present
871 DHCP remDhcpPacket = removeOption82(dhcpPacket);
872 udpPacket.setPayload(remDhcpPacket);
873
Amit Ghosh47243cb2017-07-26 05:08:53 +0100874 ipv4Packet.setPayload(udpPacket);
875 etherReply.setPayload(ipv4Packet);
876
Saurav Das45861d42020-10-07 00:03:23 -0700877 if (!usedOption82) {
878 // option 82 data not present or not used, we need to
879 // lookup host store with client dstmac and vlan from context
880 r2cData = new RelayToClientInfo();
881 r2cData.cp = getConnectPointOfClient(dstMac, context);
882 if (r2cData.cp == null) {
883 log.warn("Couldn't find subscriber, service or host info for mac"
884 + " address {} and vlan {} .. DHCP packet can't be"
885 + " delivered to client", dstMac, innerVlan);
886 return null;
887 }
888 }
889
890 // always need the subscriber entry
891 SubscriberAndDeviceInformation entry = getSubscriber(r2cData.cp);
892 if (entry == null) {
893 log.warn("Couldn't find subscriber info for cp {}.. DHCP packet"
894 + " can't be delivered to client mac {} and vlan {}",
895 r2cData.cp, dstMac, innerVlan);
896 return null;
897 }
898
899 if (!usedOption82) {
900 UniTagInformation uniTagInformation =
901 getUnitagInformationFromPacketContext(context, entry);
902 if (uniTagInformation == null) {
903 log.warn("Missing service information for connectPoint {} "
904 + " cTag {} .. DHCP packet can't be delivered to client",
905 r2cData.cp, innerVlan);
906 return null;
907 }
908 r2cData.cvid = uniTagInformation.getPonCTag();
909 r2cData.pcp = uniTagInformation.getDsPonCTagPriority();
910 r2cData.cktId = entry.circuitId();
911 etherReply.setVlanID(r2cData.cvid.toShort());
912 if (r2cData.pcp != -1) {
913 etherReply.setPriorityCode((byte) r2cData.pcp);
914 }
915 }
916
917 // update stats and events
918 IpAddress ip = IpAddress.valueOf(dhcpPacket.getYourIPAddress());
919 DhcpAllocationInfo info =
920 new DhcpAllocationInfo(r2cData.cp, dhcpPacket.getPacketType(),
921 r2cData.cktId, dstMac, ip, innerVlan,
922 entry.id());
923 String key = getUniqueUuidFromString(entry.id() + info.macAddress()
924 + innerVlan);
925 allocations.put(key, info);
926 post(new DhcpL2RelayEvent(DhcpL2RelayEvent.Type.UPDATED, info,
927 r2cData.cp));
928 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames
929 .valueOf("PACKETS_FROM_SERVER"));
930 if (log.isTraceEnabled()) {
931 log.trace("Finished processing packet.. relaying to client at {}",
932 r2cData.cp);
933 }
934 r2cData.ethernetPkt = etherReply;
935 return r2cData;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100936 }
937
Saurav Das45861d42020-10-07 00:03:23 -0700938 // forward the packet to ConnectPoint where the DHCP server is attached.
939 private void relayPacketToServer(Ethernet packet, PacketContext context) {
Andrea Campanellad79a6792020-10-26 14:44:46 +0100940 SubscriberAndDeviceInformation entry = getSubscriber(context);
Saurav Das45861d42020-10-07 00:03:23 -0700941 if (log.isTraceEnabled()) {
942 IPv4 ipv4Packet = (IPv4) packet.getPayload();
943 UDP udpPacket = (UDP) ipv4Packet.getPayload();
944 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
Andrea Campanellad79a6792020-10-26 14:44:46 +0100945 log.trace("Emitting packet to server: packet {}, with MAC {} from {}",
Saurav Das45861d42020-10-07 00:03:23 -0700946 getDhcpPacketType(dhcpPayload),
Andrea Campanellad79a6792020-10-26 14:44:46 +0100947 MacAddress.valueOf(dhcpPayload.getClientHardwareAddress()),
948 entry.id());
Saurav Das45861d42020-10-07 00:03:23 -0700949 }
950 ConnectPoint toSendTo = null;
951 if (!useOltUplink) {
952 toSendTo = dhcpServerConnectPoint.get();
953 } else {
954 toSendTo = getUplinkConnectPointOfOlt(context.inPacket().receivedFrom()
955 .deviceId());
956 }
957
958 if (toSendTo != null) {
959 TrafficTreatment t = DefaultTrafficTreatment.builder()
960 .setOutput(toSendTo.port()).build();
961 OutboundPacket o = new DefaultOutboundPacket(toSendTo
962 .deviceId(), t, ByteBuffer.wrap(packet.serialize()));
963 if (log.isTraceEnabled()) {
964 log.trace("Relaying packet to dhcp server at {} {}", toSendTo,
965 packet);
966 }
967 packetService.emit(o);
968
Saurav Das45861d42020-10-07 00:03:23 -0700969 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames
970 .valueOf("PACKETS_TO_SERVER"));
971 } else {
972 log.error("No connect point to send msg to DHCP Server");
973 }
974 }
975
976 // send the response to the requester host (client)
977 private void relayPacketToClient(RelayToClientInfo r2cData,
978 MacAddress dstMac) {
979 ConnectPoint subCp = r2cData.cp;
980 Ethernet ethPacket = r2cData.ethernetPkt;
981 // Send packet out to requester if the host information is available
982 if (subCp != null) {
983 TrafficTreatment t = DefaultTrafficTreatment.builder()
984 .setOutput(subCp.port()).build();
985 OutboundPacket o = new DefaultOutboundPacket(subCp.deviceId(),
986 t, ByteBuffer.wrap(ethPacket.serialize()));
987 if (log.isTraceEnabled()) {
988 log.trace("Relaying packet to DHCP client at {} with "
989 + "MacAddress {}, {} given {}", subCp, dstMac,
990 ethPacket, r2cData);
991 }
992 packetService.emit(o);
993 } else {
994 log.error("Dropping DHCP Packet because unknown connectPoint for {}",
995 dstMac);
996 }
997 }
998
999 /**
1000 * Option 82 includes circuitId and remoteId data configured by an
1001 * operator in sadis for a subscriber, and can be a string in any form
1002 * relevant to the operator's dhcp-server. When circuitId is configured
1003 * in sadis, the relay agent adds the option, but does not use the
1004 * information for forwarding packets back to client.
1005 * <p>
1006 * If circuitId is not configured in sadis, this relay-agent adds
1007 * circuitId information in the form
1008 * "{@literal<}connectPoint>:vlan{@literal<}clientVlanId>:pcp{@literal<}downstreamPcp>"
1009 * for example, "of:0000000000000001/32:vlan200:pcp7". When the packet
1010 * is received back from the server with circuitId in this form, this
1011 * relay agent will use this information to forward packets to the
1012 * client.
1013 *
1014 * @param dhcpPacket the DHCP packet to transform
1015 * @param entry sadis information for the subscriber
1016 * @param cp the connectPoint to set if sadis entry has no circuitId
1017 * @param clientVlan the vlan to set if sadis entry has no circuitId
1018 * @param downstreamPbits the pbits to set if sadis entry has no
1019 * circuitId
1020 * @return the modified dhcp packet with option82 added
Amit Ghosha17354e2017-08-23 12:56:04 +01001021 */
Saurav Das45861d42020-10-07 00:03:23 -07001022 private DHCP addOption82(DHCP dhcpPacket, SubscriberAndDeviceInformation entry,
1023 ConnectPoint cp, VlanId clientVlan,
1024 int downstreamPbits) {
1025 List<DhcpOption> options = Lists.newArrayList(dhcpPacket.getOptions());
1026 DhcpOption82Data option82 = new DhcpOption82Data();
1027 if (entry.circuitId() == null || entry.circuitId().isBlank()) {
1028 option82.setAgentCircuitId(cp + VLAN_KEYWORD + clientVlan
1029 + PCP_KEYWORD
1030 + downstreamPbits);
1031 } else {
1032 option82.setAgentCircuitId(entry.circuitId());
1033 }
1034 option82.setAgentRemoteId(entry.remoteId());
1035 if (log.isTraceEnabled()) {
1036 log.trace("adding option82 {} ", option82);
1037 }
1038 DhcpOption option = new DhcpOption()
1039 .setCode(DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue())
1040 .setData(option82.toByteArray())
1041 .setLength(option82.length());
1042
1043 options.add(options.size() - 1, option);
1044 dhcpPacket.setOptions(options);
1045
1046 return dhcpPacket;
1047 }
1048
1049 private DHCP removeOption82(DHCP dhcpPacket) {
1050 List<DhcpOption> options = dhcpPacket.getOptions();
1051 List<DhcpOption> newoptions = options.stream()
1052 .filter(option -> option
1053 .getCode() != DHCP.DHCPOptionCode.OptionCode_CircuitID
1054 .getValue())
1055 .collect(Collectors.toList());
1056
1057 return dhcpPacket.setOptions(newoptions);
1058 }
1059
1060 /**
1061 * Returns the circuit Id values decoded from the option 82 data. Decoding
1062 * is performed if and only if the circuit id format is in the form
1063 * "{@literal<}connectPoint>:vlan{@literal<}clientVlanId>:pcp{@literal<}downstreamPcp>"
1064 *
1065 * @param cktId the circuitId string from option 82 data
1066 * @return decoded circuit id data if it is in the expected format or
1067 * null
1068 */
1069 private RelayToClientInfo decodeCircuitId(String cktId) {
1070 if (cktId.contains(VLAN_KEYWORD) && cktId.contains(PCP_KEYWORD)) {
1071 ConnectPoint cp = ConnectPoint
1072 .fromString(cktId
1073 .substring(0, cktId.indexOf(VLAN_KEYWORD)));
1074 VlanId cvid = VlanId
1075 .vlanId(cktId.substring(
1076 cktId.indexOf(VLAN_KEYWORD)
1077 + VLAN_KEYWORD.length(),
1078 cktId.indexOf(PCP_KEYWORD)));
1079 int pcp = Integer
1080 .valueOf(cktId.substring(cktId.indexOf(PCP_KEYWORD)
1081 + PCP_KEYWORD.length()))
1082 .intValue();
1083 log.debug("retrieved from option82-> cp={} cvlan={} down-pcp={}"
1084 + " for relaying to client ", cp, cvid, pcp);
1085 return new RelayToClientInfo(cp, cvid, pcp, cktId);
1086 } else {
1087 log.debug("Option 82 circuitId {} is operator defined and will "
1088 + "not be used for forwarding", cktId);
1089 return null;
1090 }
1091 }
1092
1093 private class RelayToClientInfo {
1094 Ethernet ethernetPkt;
1095 ConnectPoint cp;
1096 VlanId cvid;
1097 int pcp;
1098 String cktId;
1099
1100 public RelayToClientInfo(ConnectPoint cp, VlanId cvid, int pcp,
1101 String cktId) {
1102 this.cp = cp;
1103 this.cvid = cvid;
1104 this.pcp = pcp;
1105 this.cktId = cktId;
1106 }
1107
1108 public RelayToClientInfo() {
1109 }
1110
1111 @Override
1112 public String toString() {
1113 return "RelayToClientInfo: {connectPoint=" + cp + " clientVlan="
1114 + cvid + " clientPcp=" + pcp + " circuitId=" + cktId + "}";
1115 }
1116
1117 }
1118
1119 // get the type of the DHCP packet
1120 private DHCP.MsgType getDhcpPacketType(DHCP dhcpPayload) {
1121 for (DhcpOption option : dhcpPayload.getOptions()) {
1122 if (option.getCode() == OptionCode_MessageType.getValue()) {
1123 byte[] data = option.getData();
1124 return DHCP.MsgType.getType(data[0]);
1125 }
1126 }
1127 return null;
1128 }
1129
1130 private void updateDhcpRelayCountersStore(SubscriberAndDeviceInformation entry,
1131 DhcpL2RelayCounterNames counterType) {
1132 // Update global counter stats
1133 dhcpL2RelayCounters.incrementCounter(DhcpL2RelayEvent.GLOBAL_COUNTER,
1134 counterType);
1135 if (entry == null) {
1136 log.warn("Counter not updated as subscriber info not found.");
1137 } else {
1138 // Update subscriber counter stats
1139 dhcpL2RelayCounters.incrementCounter(entry.id(), counterType);
1140 }
1141 }
1142
1143 /**
1144 * Get subscriber information based on subscriber's connectPoint.
1145 *
1146 * @param subsCp the subscriber's connectPoint
1147 * @return subscriber sadis info or null if not found
1148 */
1149 private SubscriberAndDeviceInformation getSubscriber(ConnectPoint subsCp) {
1150 if (subsCp != null) {
1151 String portName = getPortName(subsCp);
1152 return subsService.get(portName);
1153 }
1154 return null;
1155 }
1156
1157 /**
1158 * Returns sadis info for subscriber based on incoming packet context.
1159 * The packet context must refer to a packet coming from a subscriber
1160 * port.
1161 *
1162 * @param context incoming packet context from subscriber port (UNI)
1163 * @return sadis info for the subscriber or null
1164 */
1165 private SubscriberAndDeviceInformation getSubscriber(PacketContext context) {
1166 String portName = getPortName(context.inPacket().receivedFrom());
1167 return subsService.get(portName);
1168 }
1169
1170 /**
1171 * Returns ConnectPoint of the Client based on MAC address and C-VLAN.
1172 * Verifies that returned connect point has service defined in sadis.
1173 *
1174 * @param dstMac client dstMac
1175 * @param context context for incoming packet, parsed for C-vlan id
1176 * @return connect point information for client or null if connect point
1177 * not found or service cannot be verified for client info
1178 */
1179 private ConnectPoint getConnectPointOfClient(MacAddress dstMac,
1180 PacketContext context) {
Amit Ghosha17354e2017-08-23 12:56:04 +01001181 Set<Host> hosts = hostService.getHostsByMac(dstMac);
1182 if (hosts == null || hosts.isEmpty()) {
1183 log.warn("Cannot determine host for DHCP client: {}. Aborting "
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001184 + "relay for DHCP Packet from server", dstMac);
Amit Ghosha17354e2017-08-23 12:56:04 +01001185 return null;
1186 }
1187 for (Host h : hosts) {
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001188 // if more than one (for example, multiple services with same
1189 // mac-address but different service VLANs (inner/C vlans)
Amit Ghosha17354e2017-08-23 12:56:04 +01001190 // find the connect point which has an valid entry in SADIS
1191 ConnectPoint cp = new ConnectPoint(h.location().deviceId(),
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001192 h.location().port());
Amit Ghosha17354e2017-08-23 12:56:04 +01001193
Saurav Das45861d42020-10-07 00:03:23 -07001194 SubscriberAndDeviceInformation sub = getSubscriber(cp);
Gamze Abakaa64b3bc2020-01-31 06:51:43 +00001195 if (sub == null) {
Saurav Das45861d42020-10-07 00:03:23 -07001196 log.warn("Subscriber info not found for {} for host {}", cp, h);
1197 continue;
Amit Ghosha17354e2017-08-23 12:56:04 +01001198 }
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001199 // check for cvlan in subscriber's uniTagInfo list
Saurav Das45861d42020-10-07 00:03:23 -07001200 UniTagInformation uniTagInformation =
1201 getUnitagInformationFromPacketContext(context, sub);
Jonathan Hartb4fbc922020-04-14 12:17:44 -07001202 if (uniTagInformation != null) {
1203 return cp;
Gamze Abakaa64b3bc2020-01-31 06:51:43 +00001204 }
Gamze Abakaa64b3bc2020-01-31 06:51:43 +00001205 }
Jonathan Hartb4fbc922020-04-14 12:17:44 -07001206 // no sadis config found for this connectPoint/vlan
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001207 log.warn("Missing service information for dhcp packet received from"
1208 + " {} with cTag {} .. cannot relay to client",
Saurav Das45861d42020-10-07 00:03:23 -07001209 context.inPacket().receivedFrom(),
1210 context.inPacket().parsed().getVlanID());
1211 return null;
1212 }
1213
1214 /**
1215 * Returns the port-name for the given connectPoint port.
1216 *
1217 * @param cp the given connect point
1218 * @return the port-name for the connect point port
1219 */
1220 private String getPortName(ConnectPoint cp) {
1221 Port p = deviceService.getPort(cp);
1222 return p.annotations().value(AnnotationKeys.PORT_NAME);
1223 }
1224
1225 /**
1226 * Return's uniTagInformation (service information) if incoming packet's
1227 * client VLAN id matches the subscriber's service info, and dhcp is
1228 * required for this service.
1229 *
1230 * @param context
1231 * @param sub
1232 * @return
1233 */
1234 private UniTagInformation getUnitagInformationFromPacketContext(PacketContext context,
1235 SubscriberAndDeviceInformation sub) {
1236 // If the ctag is defined in the tagList and dhcp is required,
1237 // return the service info
1238 List<UniTagInformation> tagList = sub.uniTagList();
1239 for (UniTagInformation uniServiceInformation : tagList) {
1240 if (uniServiceInformation.getPonCTag().toShort() == context.inPacket()
1241 .parsed().getVlanID()) {
1242 if (uniServiceInformation.getIsDhcpRequired()) {
1243 return uniServiceInformation;
1244 }
1245 }
1246 }
Jonathan Hartb4fbc922020-04-14 12:17:44 -07001247
Amit Ghosha17354e2017-08-23 12:56:04 +01001248 return null;
1249 }
1250
Saurav Das45861d42020-10-07 00:03:23 -07001251
1252 private MacAddress relayAgentMacAddress(PacketContext context) {
1253 SubscriberAndDeviceInformation device = this.getDevice(context);
1254 if (device == null) {
1255 log.warn("Device not found for {}", context.inPacket().receivedFrom());
1256 return null;
Amit Ghosh47243cb2017-07-26 05:08:53 +01001257 }
Saurav Das45861d42020-10-07 00:03:23 -07001258 return device.hardwareIdentifier();
Amit Ghosh47243cb2017-07-26 05:08:53 +01001259 }
Amit Ghosh47243cb2017-07-26 05:08:53 +01001260
Saurav Das45861d42020-10-07 00:03:23 -07001261 /**
1262 * Returns sadis information for device from which packet was received.
1263 *
1264 * @param context the packet context
1265 * @return sadis information for device
1266 */
1267 private SubscriberAndDeviceInformation getDevice(PacketContext context) {
1268 String serialNo = deviceService
1269 .getDevice(context.inPacket().receivedFrom().deviceId())
1270 .serialNumber();
1271 return subsService.get(serialNo);
1272 }
Deepa vaddireddy0060f532017-08-04 06:46:05 +00001273
Deepa vaddireddy0060f532017-08-04 06:46:05 +00001274 }
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001275
Amit Ghosh47243cb2017-07-26 05:08:53 +01001276 /**
1277 * Listener for network config events.
1278 */
1279 private class InternalConfigListener implements NetworkConfigListener {
1280
1281 @Override
1282 public void event(NetworkConfigEvent event) {
1283
1284 if ((event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
1285 event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED) &&
1286 event.configClass().equals(DhcpL2RelayConfig.class)) {
1287 updateConfig();
1288 log.info("Reconfigured");
1289 }
1290 }
1291 }
Deepa vaddireddy0060f532017-08-04 06:46:05 +00001292
Amit Ghosh8951f042017-08-10 13:48:10 +01001293 /**
1294 * Handles Mastership changes for the devices which connect
1295 * to the DHCP server.
1296 */
1297 private class InnerMastershipListener implements MastershipListener {
1298 @Override
1299 public void event(MastershipEvent event) {
Amit Ghosh83c8c892017-11-09 11:08:27 +00001300 if (!useOltUplink) {
1301 if (dhcpServerConnectPoint.get() != null &&
1302 dhcpServerConnectPoint.get().deviceId().
1303 equals(event.subject())) {
1304 log.trace("Mastership Event recevived for {}", event.subject());
1305 // mastership of the device for our connect point has changed
1306 // reselect
1307 selectServerConnectPoint();
1308 }
Amit Ghosh8951f042017-08-10 13:48:10 +01001309 }
1310 }
1311 }
Deepa vaddireddy0060f532017-08-04 06:46:05 +00001312
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001313 private void removeAllocations(Predicate<Map.Entry<String, Versioned<DhcpAllocationInfo>>> pred) {
1314 allocations.stream()
1315 .filter(pred)
1316 .map(Map.Entry::getKey)
1317 .collect(Collectors.toList())
1318 .forEach(allocations::remove);
1319 }
1320
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001321 @Override
Matteo Scandoloab346512020-04-17 13:39:55 -07001322 public void clearAllocations() {
1323 allocations.clear();
1324 }
1325
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001326 @Override
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001327 public boolean removeAllocationsByConnectPoint(ConnectPoint cp) {
1328 boolean removed = false;
Matteo Scandoloab346512020-04-17 13:39:55 -07001329 for (String key : allocations.keySet()) {
1330 DhcpAllocationInfo entry = allocations.asJavaMap().get(key);
1331 if (entry.location().equals(cp)) {
1332 allocations.remove(key);
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001333 removed = true;
Matteo Scandoloab346512020-04-17 13:39:55 -07001334 }
1335 }
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001336 return removed;
Matteo Scandoloab346512020-04-17 13:39:55 -07001337 }
1338
Andrea Campanella6f45a1b2020-05-08 17:50:12 +02001339 /**
1340 * Checks for mastership or falls back to leadership on deviceId.
1341 * If the node is not master and device is available
1342 * or the device is not available and the leader is different
1343 * we let master or leader else handle it
1344 * Leadership on the device topic is needed because the master can be NONE
1345 * in case the device went away, we still need to handle events
1346 * consistently
1347 */
1348 private boolean isLocalLeader(DeviceId deviceId) {
1349 if (!mastershipService.isLocalMaster(deviceId)) {
1350 // When the device is available we just check the mastership
1351 if (deviceService.isAvailable(deviceId)) {
1352 return false;
1353 }
1354 // Fallback with Leadership service - device id is used as topic
1355 NodeId leader = leadershipService.runForLeadership(
1356 deviceId.toString()).leaderNodeId();
1357 // Verify if this node is the leader
1358 return clusterService.getLocalNode().id().equals(leader);
1359 }
1360 return true;
1361 }
1362
Amit Ghosh8951f042017-08-10 13:48:10 +01001363 /**
1364 * Handles Device status change for the devices which connect
1365 * to the DHCP server.
1366 */
1367 private class InnerDeviceListener implements DeviceListener {
1368 @Override
1369 public void event(DeviceEvent event) {
Andrea Campanella6f45a1b2020-05-08 17:50:12 +02001370 final DeviceId deviceId = event.subject().id();
1371
1372 // Ensure only one instance handles the event
1373 if (!isLocalLeader(deviceId)) {
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001374 return;
1375 }
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001376 // ignore stats
1377 if (event.type().equals(DeviceEvent.Type.PORT_STATS_UPDATED)) {
1378 return;
1379 }
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001380
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001381 log.debug("Device Event received for {} event {}", event.subject(),
1382 event.type());
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001383
Thomas Lee S9df15082019-12-23 11:31:15 +05301384 switch (event.type()) {
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001385 case DEVICE_REMOVED:
1386 log.info("Device removed {}", event.subject().id());
1387 removeAllocations(e -> e.getValue().value().location().deviceId().equals(deviceId));
1388 break;
Thomas Lee S9df15082019-12-23 11:31:15 +05301389 case DEVICE_AVAILABILITY_CHANGED:
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001390 boolean available = deviceService.isAvailable(deviceId);
1391 log.info("Device Avail Changed {} to {}", event.subject().id(), available);
1392
1393 if (!available && deviceService.getPorts(deviceId).isEmpty()) {
1394 removeAllocations(e -> e.getValue().value().location().deviceId().equals(deviceId));
1395 log.info("Device {} is removed from DHCP allocationmap ", deviceId);
Thomas Lee S9df15082019-12-23 11:31:15 +05301396 }
1397 break;
Thomas Lee S6b77ad22020-01-10 11:27:43 +05301398 case PORT_REMOVED:
1399 Port port = event.port();
Thomas Lee S6b77ad22020-01-10 11:27:43 +05301400 log.info("Port {} is deleted on device {}", port, deviceId);
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001401
1402 ConnectPoint cp = new ConnectPoint(deviceId, port.number());
1403 removeAllocations(e -> e.getValue().value().location().equals(cp));
1404
Thomas Lee S6b77ad22020-01-10 11:27:43 +05301405 log.info("Port {} on device {} is removed from DHCP allocationmap", event.port(), deviceId);
1406 break;
Thomas Lee S9df15082019-12-23 11:31:15 +05301407 default:
1408 break;
1409 }
Amit Ghosh83c8c892017-11-09 11:08:27 +00001410 if (!useOltUplink) {
1411 if (dhcpServerConnectPoint.get() == null) {
1412 switch (event.type()) {
1413 case DEVICE_ADDED:
1414 case DEVICE_AVAILABILITY_CHANGED:
Saurav Dasb14f08a2019-02-22 16:34:15 -08001415 // some device is available check if we can get a
1416 // connect point we can use
1417 addOrRemoveDhcpTrapFromServer(true);
Amit Ghosh83c8c892017-11-09 11:08:27 +00001418 break;
1419 default:
1420 break;
1421 }
1422 return;
Amit Ghosh8951f042017-08-10 13:48:10 +01001423 }
Amit Ghosh83c8c892017-11-09 11:08:27 +00001424 if (dhcpServerConnectPoint.get().deviceId().
1425 equals(event.subject().id())) {
1426 switch (event.type()) {
1427 case DEVICE_AVAILABILITY_CHANGED:
1428 case DEVICE_REMOVED:
1429 case DEVICE_SUSPENDED:
1430 // state of our device has changed, check if we need
Saurav Dasb14f08a2019-02-22 16:34:15 -08001431 // to re-select a connectpoint
1432 addOrRemoveDhcpTrapFromServer(true);
Amit Ghosh83c8c892017-11-09 11:08:27 +00001433 break;
1434 default:
1435 break;
1436 }
1437 }
1438 } else {
Amit Ghosh8951f042017-08-10 13:48:10 +01001439 switch (event.type()) {
Amit Ghosh83c8c892017-11-09 11:08:27 +00001440 case PORT_ADDED:
Saurav Dasb4e3e102018-10-02 15:31:17 -07001441 if (useOltUplink && isUplinkPortOfOlt(event.subject().id(), event.port())) {
Saurav Dasb14f08a2019-02-22 16:34:15 -08001442 requestDhcpPacketsFromConnectPoint(
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001443 new ConnectPoint(event.subject().id(), event.port().number()),
1444 Optional.empty());
Amit Ghosh83c8c892017-11-09 11:08:27 +00001445 }
Amit Ghosh8951f042017-08-10 13:48:10 +01001446 break;
1447 default:
1448 break;
1449 }
1450 }
1451 }
1452 }
Jonathan Hart77ca3152020-02-21 14:31:21 -08001453
1454 private class InnerDhcpL2RelayStoreDelegate implements DhcpL2RelayStoreDelegate {
1455 @Override
1456 public void notify(DhcpL2RelayEvent event) {
1457 if (event.type().equals(DhcpL2RelayEvent.Type.STATS_UPDATE)) {
1458 DhcpL2RelayEvent toPost = event;
1459 if (event.getSubscriberId() != null) {
1460 // infuse the event with the allocation info before posting
1461 DhcpAllocationInfo info = Versioned.valueOrNull(allocations.get(event.getSubscriberId()));
1462 toPost = new DhcpL2RelayEvent(event.type(), info, event.connectPoint(),
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001463 event.getCountersEntry(), event.getSubscriberId());
Jonathan Hart77ca3152020-02-21 14:31:21 -08001464 }
1465 post(toPost);
1466 }
1467
1468 }
1469 }
Amit Ghosh47243cb2017-07-26 05:08:53 +01001470}