blob: 018ac6961aa2eec9a5705c73c3b433c26b665045 [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
Deepa Vaddireddy5f278d62017-08-30 05:59:39 +0530741 Ethernet etherReply = ethernetPacket;
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()) {
813 log.trace("Finished processing DHCP Packet of type {} from {} "
814 + "... relaying to dhcpServer",
815 dhcpPacket.getPacketType(), entry.id());
816 }
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) {
940 if (log.isTraceEnabled()) {
941 IPv4 ipv4Packet = (IPv4) packet.getPayload();
942 UDP udpPacket = (UDP) ipv4Packet.getPayload();
943 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
944 log.trace("Emitting packet to server: packet {}, with MAC {}",
945 getDhcpPacketType(dhcpPayload),
946 MacAddress.valueOf(dhcpPayload.getClientHardwareAddress()));
947 }
948 ConnectPoint toSendTo = null;
949 if (!useOltUplink) {
950 toSendTo = dhcpServerConnectPoint.get();
951 } else {
952 toSendTo = getUplinkConnectPointOfOlt(context.inPacket().receivedFrom()
953 .deviceId());
954 }
955
956 if (toSendTo != null) {
957 TrafficTreatment t = DefaultTrafficTreatment.builder()
958 .setOutput(toSendTo.port()).build();
959 OutboundPacket o = new DefaultOutboundPacket(toSendTo
960 .deviceId(), t, ByteBuffer.wrap(packet.serialize()));
961 if (log.isTraceEnabled()) {
962 log.trace("Relaying packet to dhcp server at {} {}", toSendTo,
963 packet);
964 }
965 packetService.emit(o);
966
967 SubscriberAndDeviceInformation entry = getSubscriber(context);
968 updateDhcpRelayCountersStore(entry, DhcpL2RelayCounterNames
969 .valueOf("PACKETS_TO_SERVER"));
970 } else {
971 log.error("No connect point to send msg to DHCP Server");
972 }
973 }
974
975 // send the response to the requester host (client)
976 private void relayPacketToClient(RelayToClientInfo r2cData,
977 MacAddress dstMac) {
978 ConnectPoint subCp = r2cData.cp;
979 Ethernet ethPacket = r2cData.ethernetPkt;
980 // Send packet out to requester if the host information is available
981 if (subCp != null) {
982 TrafficTreatment t = DefaultTrafficTreatment.builder()
983 .setOutput(subCp.port()).build();
984 OutboundPacket o = new DefaultOutboundPacket(subCp.deviceId(),
985 t, ByteBuffer.wrap(ethPacket.serialize()));
986 if (log.isTraceEnabled()) {
987 log.trace("Relaying packet to DHCP client at {} with "
988 + "MacAddress {}, {} given {}", subCp, dstMac,
989 ethPacket, r2cData);
990 }
991 packetService.emit(o);
992 } else {
993 log.error("Dropping DHCP Packet because unknown connectPoint for {}",
994 dstMac);
995 }
996 }
997
998 /**
999 * Option 82 includes circuitId and remoteId data configured by an
1000 * operator in sadis for a subscriber, and can be a string in any form
1001 * relevant to the operator's dhcp-server. When circuitId is configured
1002 * in sadis, the relay agent adds the option, but does not use the
1003 * information for forwarding packets back to client.
1004 * <p>
1005 * If circuitId is not configured in sadis, this relay-agent adds
1006 * circuitId information in the form
1007 * "{@literal<}connectPoint>:vlan{@literal<}clientVlanId>:pcp{@literal<}downstreamPcp>"
1008 * for example, "of:0000000000000001/32:vlan200:pcp7". When the packet
1009 * is received back from the server with circuitId in this form, this
1010 * relay agent will use this information to forward packets to the
1011 * client.
1012 *
1013 * @param dhcpPacket the DHCP packet to transform
1014 * @param entry sadis information for the subscriber
1015 * @param cp the connectPoint to set if sadis entry has no circuitId
1016 * @param clientVlan the vlan to set if sadis entry has no circuitId
1017 * @param downstreamPbits the pbits to set if sadis entry has no
1018 * circuitId
1019 * @return the modified dhcp packet with option82 added
Amit Ghosha17354e2017-08-23 12:56:04 +01001020 */
Saurav Das45861d42020-10-07 00:03:23 -07001021 private DHCP addOption82(DHCP dhcpPacket, SubscriberAndDeviceInformation entry,
1022 ConnectPoint cp, VlanId clientVlan,
1023 int downstreamPbits) {
1024 List<DhcpOption> options = Lists.newArrayList(dhcpPacket.getOptions());
1025 DhcpOption82Data option82 = new DhcpOption82Data();
1026 if (entry.circuitId() == null || entry.circuitId().isBlank()) {
1027 option82.setAgentCircuitId(cp + VLAN_KEYWORD + clientVlan
1028 + PCP_KEYWORD
1029 + downstreamPbits);
1030 } else {
1031 option82.setAgentCircuitId(entry.circuitId());
1032 }
1033 option82.setAgentRemoteId(entry.remoteId());
1034 if (log.isTraceEnabled()) {
1035 log.trace("adding option82 {} ", option82);
1036 }
1037 DhcpOption option = new DhcpOption()
1038 .setCode(DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue())
1039 .setData(option82.toByteArray())
1040 .setLength(option82.length());
1041
1042 options.add(options.size() - 1, option);
1043 dhcpPacket.setOptions(options);
1044
1045 return dhcpPacket;
1046 }
1047
1048 private DHCP removeOption82(DHCP dhcpPacket) {
1049 List<DhcpOption> options = dhcpPacket.getOptions();
1050 List<DhcpOption> newoptions = options.stream()
1051 .filter(option -> option
1052 .getCode() != DHCP.DHCPOptionCode.OptionCode_CircuitID
1053 .getValue())
1054 .collect(Collectors.toList());
1055
1056 return dhcpPacket.setOptions(newoptions);
1057 }
1058
1059 /**
1060 * Returns the circuit Id values decoded from the option 82 data. Decoding
1061 * is performed if and only if the circuit id format is in the form
1062 * "{@literal<}connectPoint>:vlan{@literal<}clientVlanId>:pcp{@literal<}downstreamPcp>"
1063 *
1064 * @param cktId the circuitId string from option 82 data
1065 * @return decoded circuit id data if it is in the expected format or
1066 * null
1067 */
1068 private RelayToClientInfo decodeCircuitId(String cktId) {
1069 if (cktId.contains(VLAN_KEYWORD) && cktId.contains(PCP_KEYWORD)) {
1070 ConnectPoint cp = ConnectPoint
1071 .fromString(cktId
1072 .substring(0, cktId.indexOf(VLAN_KEYWORD)));
1073 VlanId cvid = VlanId
1074 .vlanId(cktId.substring(
1075 cktId.indexOf(VLAN_KEYWORD)
1076 + VLAN_KEYWORD.length(),
1077 cktId.indexOf(PCP_KEYWORD)));
1078 int pcp = Integer
1079 .valueOf(cktId.substring(cktId.indexOf(PCP_KEYWORD)
1080 + PCP_KEYWORD.length()))
1081 .intValue();
1082 log.debug("retrieved from option82-> cp={} cvlan={} down-pcp={}"
1083 + " for relaying to client ", cp, cvid, pcp);
1084 return new RelayToClientInfo(cp, cvid, pcp, cktId);
1085 } else {
1086 log.debug("Option 82 circuitId {} is operator defined and will "
1087 + "not be used for forwarding", cktId);
1088 return null;
1089 }
1090 }
1091
1092 private class RelayToClientInfo {
1093 Ethernet ethernetPkt;
1094 ConnectPoint cp;
1095 VlanId cvid;
1096 int pcp;
1097 String cktId;
1098
1099 public RelayToClientInfo(ConnectPoint cp, VlanId cvid, int pcp,
1100 String cktId) {
1101 this.cp = cp;
1102 this.cvid = cvid;
1103 this.pcp = pcp;
1104 this.cktId = cktId;
1105 }
1106
1107 public RelayToClientInfo() {
1108 }
1109
1110 @Override
1111 public String toString() {
1112 return "RelayToClientInfo: {connectPoint=" + cp + " clientVlan="
1113 + cvid + " clientPcp=" + pcp + " circuitId=" + cktId + "}";
1114 }
1115
1116 }
1117
1118 // get the type of the DHCP packet
1119 private DHCP.MsgType getDhcpPacketType(DHCP dhcpPayload) {
1120 for (DhcpOption option : dhcpPayload.getOptions()) {
1121 if (option.getCode() == OptionCode_MessageType.getValue()) {
1122 byte[] data = option.getData();
1123 return DHCP.MsgType.getType(data[0]);
1124 }
1125 }
1126 return null;
1127 }
1128
1129 private void updateDhcpRelayCountersStore(SubscriberAndDeviceInformation entry,
1130 DhcpL2RelayCounterNames counterType) {
1131 // Update global counter stats
1132 dhcpL2RelayCounters.incrementCounter(DhcpL2RelayEvent.GLOBAL_COUNTER,
1133 counterType);
1134 if (entry == null) {
1135 log.warn("Counter not updated as subscriber info not found.");
1136 } else {
1137 // Update subscriber counter stats
1138 dhcpL2RelayCounters.incrementCounter(entry.id(), counterType);
1139 }
1140 }
1141
1142 /**
1143 * Get subscriber information based on subscriber's connectPoint.
1144 *
1145 * @param subsCp the subscriber's connectPoint
1146 * @return subscriber sadis info or null if not found
1147 */
1148 private SubscriberAndDeviceInformation getSubscriber(ConnectPoint subsCp) {
1149 if (subsCp != null) {
1150 String portName = getPortName(subsCp);
1151 return subsService.get(portName);
1152 }
1153 return null;
1154 }
1155
1156 /**
1157 * Returns sadis info for subscriber based on incoming packet context.
1158 * The packet context must refer to a packet coming from a subscriber
1159 * port.
1160 *
1161 * @param context incoming packet context from subscriber port (UNI)
1162 * @return sadis info for the subscriber or null
1163 */
1164 private SubscriberAndDeviceInformation getSubscriber(PacketContext context) {
1165 String portName = getPortName(context.inPacket().receivedFrom());
1166 return subsService.get(portName);
1167 }
1168
1169 /**
1170 * Returns ConnectPoint of the Client based on MAC address and C-VLAN.
1171 * Verifies that returned connect point has service defined in sadis.
1172 *
1173 * @param dstMac client dstMac
1174 * @param context context for incoming packet, parsed for C-vlan id
1175 * @return connect point information for client or null if connect point
1176 * not found or service cannot be verified for client info
1177 */
1178 private ConnectPoint getConnectPointOfClient(MacAddress dstMac,
1179 PacketContext context) {
Amit Ghosha17354e2017-08-23 12:56:04 +01001180 Set<Host> hosts = hostService.getHostsByMac(dstMac);
1181 if (hosts == null || hosts.isEmpty()) {
1182 log.warn("Cannot determine host for DHCP client: {}. Aborting "
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001183 + "relay for DHCP Packet from server", dstMac);
Amit Ghosha17354e2017-08-23 12:56:04 +01001184 return null;
1185 }
1186 for (Host h : hosts) {
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001187 // if more than one (for example, multiple services with same
1188 // mac-address but different service VLANs (inner/C vlans)
Amit Ghosha17354e2017-08-23 12:56:04 +01001189 // find the connect point which has an valid entry in SADIS
1190 ConnectPoint cp = new ConnectPoint(h.location().deviceId(),
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001191 h.location().port());
Amit Ghosha17354e2017-08-23 12:56:04 +01001192
Saurav Das45861d42020-10-07 00:03:23 -07001193 SubscriberAndDeviceInformation sub = getSubscriber(cp);
Gamze Abakaa64b3bc2020-01-31 06:51:43 +00001194 if (sub == null) {
Saurav Das45861d42020-10-07 00:03:23 -07001195 log.warn("Subscriber info not found for {} for host {}", cp, h);
1196 continue;
Amit Ghosha17354e2017-08-23 12:56:04 +01001197 }
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001198 // check for cvlan in subscriber's uniTagInfo list
Saurav Das45861d42020-10-07 00:03:23 -07001199 UniTagInformation uniTagInformation =
1200 getUnitagInformationFromPacketContext(context, sub);
Jonathan Hartb4fbc922020-04-14 12:17:44 -07001201 if (uniTagInformation != null) {
1202 return cp;
Gamze Abakaa64b3bc2020-01-31 06:51:43 +00001203 }
Gamze Abakaa64b3bc2020-01-31 06:51:43 +00001204 }
Jonathan Hartb4fbc922020-04-14 12:17:44 -07001205 // no sadis config found for this connectPoint/vlan
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001206 log.warn("Missing service information for dhcp packet received from"
1207 + " {} with cTag {} .. cannot relay to client",
Saurav Das45861d42020-10-07 00:03:23 -07001208 context.inPacket().receivedFrom(),
1209 context.inPacket().parsed().getVlanID());
1210 return null;
1211 }
1212
1213 /**
1214 * Returns the port-name for the given connectPoint port.
1215 *
1216 * @param cp the given connect point
1217 * @return the port-name for the connect point port
1218 */
1219 private String getPortName(ConnectPoint cp) {
1220 Port p = deviceService.getPort(cp);
1221 return p.annotations().value(AnnotationKeys.PORT_NAME);
1222 }
1223
1224 /**
1225 * Return's uniTagInformation (service information) if incoming packet's
1226 * client VLAN id matches the subscriber's service info, and dhcp is
1227 * required for this service.
1228 *
1229 * @param context
1230 * @param sub
1231 * @return
1232 */
1233 private UniTagInformation getUnitagInformationFromPacketContext(PacketContext context,
1234 SubscriberAndDeviceInformation sub) {
1235 // If the ctag is defined in the tagList and dhcp is required,
1236 // return the service info
1237 List<UniTagInformation> tagList = sub.uniTagList();
1238 for (UniTagInformation uniServiceInformation : tagList) {
1239 if (uniServiceInformation.getPonCTag().toShort() == context.inPacket()
1240 .parsed().getVlanID()) {
1241 if (uniServiceInformation.getIsDhcpRequired()) {
1242 return uniServiceInformation;
1243 }
1244 }
1245 }
Jonathan Hartb4fbc922020-04-14 12:17:44 -07001246
Amit Ghosha17354e2017-08-23 12:56:04 +01001247 return null;
1248 }
1249
Saurav Das45861d42020-10-07 00:03:23 -07001250
1251 private MacAddress relayAgentMacAddress(PacketContext context) {
1252 SubscriberAndDeviceInformation device = this.getDevice(context);
1253 if (device == null) {
1254 log.warn("Device not found for {}", context.inPacket().receivedFrom());
1255 return null;
Amit Ghosh47243cb2017-07-26 05:08:53 +01001256 }
Saurav Das45861d42020-10-07 00:03:23 -07001257 return device.hardwareIdentifier();
Amit Ghosh47243cb2017-07-26 05:08:53 +01001258 }
Amit Ghosh47243cb2017-07-26 05:08:53 +01001259
Saurav Das45861d42020-10-07 00:03:23 -07001260 /**
1261 * Returns sadis information for device from which packet was received.
1262 *
1263 * @param context the packet context
1264 * @return sadis information for device
1265 */
1266 private SubscriberAndDeviceInformation getDevice(PacketContext context) {
1267 String serialNo = deviceService
1268 .getDevice(context.inPacket().receivedFrom().deviceId())
1269 .serialNumber();
1270 return subsService.get(serialNo);
1271 }
Deepa vaddireddy0060f532017-08-04 06:46:05 +00001272
Deepa vaddireddy0060f532017-08-04 06:46:05 +00001273 }
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001274
Amit Ghosh47243cb2017-07-26 05:08:53 +01001275 /**
1276 * Listener for network config events.
1277 */
1278 private class InternalConfigListener implements NetworkConfigListener {
1279
1280 @Override
1281 public void event(NetworkConfigEvent event) {
1282
1283 if ((event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
1284 event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED) &&
1285 event.configClass().equals(DhcpL2RelayConfig.class)) {
1286 updateConfig();
1287 log.info("Reconfigured");
1288 }
1289 }
1290 }
Deepa vaddireddy0060f532017-08-04 06:46:05 +00001291
Amit Ghosh8951f042017-08-10 13:48:10 +01001292 /**
1293 * Handles Mastership changes for the devices which connect
1294 * to the DHCP server.
1295 */
1296 private class InnerMastershipListener implements MastershipListener {
1297 @Override
1298 public void event(MastershipEvent event) {
Amit Ghosh83c8c892017-11-09 11:08:27 +00001299 if (!useOltUplink) {
1300 if (dhcpServerConnectPoint.get() != null &&
1301 dhcpServerConnectPoint.get().deviceId().
1302 equals(event.subject())) {
1303 log.trace("Mastership Event recevived for {}", event.subject());
1304 // mastership of the device for our connect point has changed
1305 // reselect
1306 selectServerConnectPoint();
1307 }
Amit Ghosh8951f042017-08-10 13:48:10 +01001308 }
1309 }
1310 }
Deepa vaddireddy0060f532017-08-04 06:46:05 +00001311
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001312 private void removeAllocations(Predicate<Map.Entry<String, Versioned<DhcpAllocationInfo>>> pred) {
1313 allocations.stream()
1314 .filter(pred)
1315 .map(Map.Entry::getKey)
1316 .collect(Collectors.toList())
1317 .forEach(allocations::remove);
1318 }
1319
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001320 @Override
Matteo Scandoloab346512020-04-17 13:39:55 -07001321 public void clearAllocations() {
1322 allocations.clear();
1323 }
1324
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001325 @Override
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001326 public boolean removeAllocationsByConnectPoint(ConnectPoint cp) {
1327 boolean removed = false;
Matteo Scandoloab346512020-04-17 13:39:55 -07001328 for (String key : allocations.keySet()) {
1329 DhcpAllocationInfo entry = allocations.asJavaMap().get(key);
1330 if (entry.location().equals(cp)) {
1331 allocations.remove(key);
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001332 removed = true;
Matteo Scandoloab346512020-04-17 13:39:55 -07001333 }
1334 }
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001335 return removed;
Matteo Scandoloab346512020-04-17 13:39:55 -07001336 }
1337
Andrea Campanella6f45a1b2020-05-08 17:50:12 +02001338 /**
1339 * Checks for mastership or falls back to leadership on deviceId.
1340 * If the node is not master and device is available
1341 * or the device is not available and the leader is different
1342 * we let master or leader else handle it
1343 * Leadership on the device topic is needed because the master can be NONE
1344 * in case the device went away, we still need to handle events
1345 * consistently
1346 */
1347 private boolean isLocalLeader(DeviceId deviceId) {
1348 if (!mastershipService.isLocalMaster(deviceId)) {
1349 // When the device is available we just check the mastership
1350 if (deviceService.isAvailable(deviceId)) {
1351 return false;
1352 }
1353 // Fallback with Leadership service - device id is used as topic
1354 NodeId leader = leadershipService.runForLeadership(
1355 deviceId.toString()).leaderNodeId();
1356 // Verify if this node is the leader
1357 return clusterService.getLocalNode().id().equals(leader);
1358 }
1359 return true;
1360 }
1361
Amit Ghosh8951f042017-08-10 13:48:10 +01001362 /**
1363 * Handles Device status change for the devices which connect
1364 * to the DHCP server.
1365 */
1366 private class InnerDeviceListener implements DeviceListener {
1367 @Override
1368 public void event(DeviceEvent event) {
Andrea Campanella6f45a1b2020-05-08 17:50:12 +02001369 final DeviceId deviceId = event.subject().id();
1370
1371 // Ensure only one instance handles the event
1372 if (!isLocalLeader(deviceId)) {
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001373 return;
1374 }
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001375 // ignore stats
1376 if (event.type().equals(DeviceEvent.Type.PORT_STATS_UPDATED)) {
1377 return;
1378 }
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001379
Saurav Dasbd5ce9c2020-09-04 18:46:45 -07001380 log.debug("Device Event received for {} event {}", event.subject(),
1381 event.type());
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001382
Thomas Lee S9df15082019-12-23 11:31:15 +05301383 switch (event.type()) {
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001384 case DEVICE_REMOVED:
1385 log.info("Device removed {}", event.subject().id());
1386 removeAllocations(e -> e.getValue().value().location().deviceId().equals(deviceId));
1387 break;
Thomas Lee S9df15082019-12-23 11:31:15 +05301388 case DEVICE_AVAILABILITY_CHANGED:
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001389 boolean available = deviceService.isAvailable(deviceId);
1390 log.info("Device Avail Changed {} to {}", event.subject().id(), available);
1391
1392 if (!available && deviceService.getPorts(deviceId).isEmpty()) {
1393 removeAllocations(e -> e.getValue().value().location().deviceId().equals(deviceId));
1394 log.info("Device {} is removed from DHCP allocationmap ", deviceId);
Thomas Lee S9df15082019-12-23 11:31:15 +05301395 }
1396 break;
Thomas Lee S6b77ad22020-01-10 11:27:43 +05301397 case PORT_REMOVED:
1398 Port port = event.port();
Thomas Lee S6b77ad22020-01-10 11:27:43 +05301399 log.info("Port {} is deleted on device {}", port, deviceId);
Jonathan Hart617bc3e2020-02-14 10:42:23 -08001400
1401 ConnectPoint cp = new ConnectPoint(deviceId, port.number());
1402 removeAllocations(e -> e.getValue().value().location().equals(cp));
1403
Thomas Lee S6b77ad22020-01-10 11:27:43 +05301404 log.info("Port {} on device {} is removed from DHCP allocationmap", event.port(), deviceId);
1405 break;
Thomas Lee S9df15082019-12-23 11:31:15 +05301406 default:
1407 break;
1408 }
Amit Ghosh83c8c892017-11-09 11:08:27 +00001409 if (!useOltUplink) {
1410 if (dhcpServerConnectPoint.get() == null) {
1411 switch (event.type()) {
1412 case DEVICE_ADDED:
1413 case DEVICE_AVAILABILITY_CHANGED:
Saurav Dasb14f08a2019-02-22 16:34:15 -08001414 // some device is available check if we can get a
1415 // connect point we can use
1416 addOrRemoveDhcpTrapFromServer(true);
Amit Ghosh83c8c892017-11-09 11:08:27 +00001417 break;
1418 default:
1419 break;
1420 }
1421 return;
Amit Ghosh8951f042017-08-10 13:48:10 +01001422 }
Amit Ghosh83c8c892017-11-09 11:08:27 +00001423 if (dhcpServerConnectPoint.get().deviceId().
1424 equals(event.subject().id())) {
1425 switch (event.type()) {
1426 case DEVICE_AVAILABILITY_CHANGED:
1427 case DEVICE_REMOVED:
1428 case DEVICE_SUSPENDED:
1429 // state of our device has changed, check if we need
Saurav Dasb14f08a2019-02-22 16:34:15 -08001430 // to re-select a connectpoint
1431 addOrRemoveDhcpTrapFromServer(true);
Amit Ghosh83c8c892017-11-09 11:08:27 +00001432 break;
1433 default:
1434 break;
1435 }
1436 }
1437 } else {
Amit Ghosh8951f042017-08-10 13:48:10 +01001438 switch (event.type()) {
Amit Ghosh83c8c892017-11-09 11:08:27 +00001439 case PORT_ADDED:
Saurav Dasb4e3e102018-10-02 15:31:17 -07001440 if (useOltUplink && isUplinkPortOfOlt(event.subject().id(), event.port())) {
Saurav Dasb14f08a2019-02-22 16:34:15 -08001441 requestDhcpPacketsFromConnectPoint(
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001442 new ConnectPoint(event.subject().id(), event.port().number()),
1443 Optional.empty());
Amit Ghosh83c8c892017-11-09 11:08:27 +00001444 }
Amit Ghosh8951f042017-08-10 13:48:10 +01001445 break;
1446 default:
1447 break;
1448 }
1449 }
1450 }
1451 }
Jonathan Hart77ca3152020-02-21 14:31:21 -08001452
1453 private class InnerDhcpL2RelayStoreDelegate implements DhcpL2RelayStoreDelegate {
1454 @Override
1455 public void notify(DhcpL2RelayEvent event) {
1456 if (event.type().equals(DhcpL2RelayEvent.Type.STATS_UPDATE)) {
1457 DhcpL2RelayEvent toPost = event;
1458 if (event.getSubscriberId() != null) {
1459 // infuse the event with the allocation info before posting
1460 DhcpAllocationInfo info = Versioned.valueOrNull(allocations.get(event.getSubscriberId()));
1461 toPost = new DhcpL2RelayEvent(event.type(), info, event.connectPoint(),
Matteo Scandolo64bba8c2020-08-19 11:50:33 -07001462 event.getCountersEntry(), event.getSubscriberId());
Jonathan Hart77ca3152020-02-21 14:31:21 -08001463 }
1464 post(toPost);
1465 }
1466
1467 }
1468 }
Amit Ghosh47243cb2017-07-26 05:08:53 +01001469}