blob: ec4a726083685351081c68c5cc1e051cdcd1d011 [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 */
16package org.opencord.dhcpl2relay;
17
18import com.google.common.collect.ImmutableSet;
Amit Ghosha17354e2017-08-23 12:56:04 +010019import com.google.common.collect.Maps;
Deepa vaddireddy0060f532017-08-04 06:46:05 +000020import com.google.common.collect.Lists;
Amit Ghosh8951f042017-08-10 13:48:10 +010021import com.google.common.collect.Sets;
Deepa vaddireddy0060f532017-08-04 06:46:05 +000022
Amit Ghosh47243cb2017-07-26 05:08:53 +010023import org.apache.felix.scr.annotations.Activate;
24import org.apache.felix.scr.annotations.Component;
25import org.apache.felix.scr.annotations.Deactivate;
26import org.apache.felix.scr.annotations.Modified;
27import org.apache.felix.scr.annotations.Property;
28import org.apache.felix.scr.annotations.Reference;
29import org.apache.felix.scr.annotations.ReferenceCardinality;
Deepa vaddireddy0060f532017-08-04 06:46:05 +000030import org.onlab.packet.DeserializationException;
Amit Ghosh47243cb2017-07-26 05:08:53 +010031import org.onlab.packet.DHCP;
32import org.onlab.packet.DHCPOption;
33import org.onlab.packet.DHCPPacketType;
Deepa vaddireddy0060f532017-08-04 06:46:05 +000034import org.onlab.packet.Ethernet;
Amit Ghosha17354e2017-08-23 12:56:04 +010035import org.onlab.packet.IpAddress;
Amit Ghosh47243cb2017-07-26 05:08:53 +010036import org.onlab.packet.IPv4;
Deepa vaddireddy0060f532017-08-04 06:46:05 +000037import org.onlab.packet.MacAddress;
Amit Ghosh47243cb2017-07-26 05:08:53 +010038import org.onlab.packet.TpPort;
39import org.onlab.packet.UDP;
40import org.onlab.packet.VlanId;
Amit Ghosh8951f042017-08-10 13:48:10 +010041import org.onosproject.mastership.MastershipEvent;
42import org.onosproject.mastership.MastershipListener;
43import org.onosproject.mastership.MastershipService;
44import org.onosproject.net.device.DeviceEvent;
45import org.onosproject.net.device.DeviceListener;
Deepa vaddireddy0060f532017-08-04 06:46:05 +000046import org.opencord.dhcpl2relay.packet.DhcpEthernet;
47import org.opencord.dhcpl2relay.packet.DhcpOption82;
Amit Ghosh47243cb2017-07-26 05:08:53 +010048import org.onlab.util.Tools;
49import org.onosproject.cfg.ComponentConfigService;
50import org.onosproject.core.ApplicationId;
51import org.onosproject.core.CoreService;
52import org.onosproject.net.AnnotationKeys;
53import org.onosproject.net.ConnectPoint;
54import org.onosproject.net.Host;
55import org.onosproject.net.Port;
56import org.onosproject.net.config.ConfigFactory;
57import org.onosproject.net.config.NetworkConfigEvent;
58import org.onosproject.net.config.NetworkConfigListener;
59import org.onosproject.net.config.NetworkConfigRegistry;
60import org.onosproject.net.device.DeviceService;
61import org.onosproject.net.flow.DefaultTrafficSelector;
62import org.onosproject.net.flow.DefaultTrafficTreatment;
63import org.onosproject.net.flow.TrafficSelector;
64import org.onosproject.net.flow.TrafficTreatment;
65import org.onosproject.net.host.HostService;
66import org.onosproject.net.packet.DefaultOutboundPacket;
67import org.onosproject.net.packet.OutboundPacket;
68import org.onosproject.net.packet.PacketContext;
69import org.onosproject.net.packet.PacketPriority;
70import org.onosproject.net.packet.PacketProcessor;
71import org.onosproject.net.packet.PacketService;
72
73import org.opencord.sadis.SubscriberAndDeviceInformation;
74import org.opencord.sadis.SubscriberAndDeviceInformationService;
Amit Ghosh47243cb2017-07-26 05:08:53 +010075import org.osgi.service.component.ComponentContext;
76import org.slf4j.Logger;
77import org.slf4j.LoggerFactory;
78
79import java.nio.ByteBuffer;
80import java.util.Dictionary;
Amit Ghosha17354e2017-08-23 12:56:04 +010081import java.util.Map;
Deepa vaddireddy0060f532017-08-04 06:46:05 +000082import java.util.List;
Deepa vaddireddy0060f532017-08-04 06:46:05 +000083import java.util.Set;
Amit Ghosh8951f042017-08-10 13:48:10 +010084import java.util.concurrent.atomic.AtomicReference;
Deepa vaddireddy0060f532017-08-04 06:46:05 +000085import java.util.stream.Collectors;
Amit Ghosh47243cb2017-07-26 05:08:53 +010086
87import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
88import static org.onlab.packet.MacAddress.valueOf;
89import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
90
91/**
92 * DHCP Relay Agent Application Component.
93 */
94@Component(immediate = true)
95public class DhcpL2Relay {
96
97 public static final String DHCP_L2RELAY_APP = "org.opencord.dhcpl2relay";
98 private final Logger log = LoggerFactory.getLogger(getClass());
99 private final InternalConfigListener cfgListener =
100 new InternalConfigListener();
101
102 private final Set<ConfigFactory> factories = ImmutableSet.of(
103 new ConfigFactory<ApplicationId, DhcpL2RelayConfig>(APP_SUBJECT_FACTORY,
104 DhcpL2RelayConfig.class,
105 "dhcpl2relay") {
106 @Override
107 public DhcpL2RelayConfig createConfig() {
108 return new DhcpL2RelayConfig();
109 }
110 }
111 );
112
113 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
114 protected NetworkConfigRegistry cfgService;
115
116 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
117 protected CoreService coreService;
118
119 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
120 protected PacketService packetService;
121
122 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
123 protected HostService hostService;
124
125 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
126 protected ComponentConfigService componentConfigService;
127
128 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
129 protected SubscriberAndDeviceInformationService subsService;
130
131 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
132 protected DeviceService deviceService;
133
Amit Ghosh8951f042017-08-10 13:48:10 +0100134 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
135 protected MastershipService mastershipService;
136
Amit Ghosh47243cb2017-07-26 05:08:53 +0100137 @Property(name = "option82", boolValue = true,
138 label = "Add option 82 to relayed packets")
139 protected boolean option82 = true;
140
Amit Ghosha17354e2017-08-23 12:56:04 +0100141 @Property(name = "enableDhcpBroadcastReplies", boolValue = false,
142 label = "Add option 82 to relayed packets")
143 protected boolean enableDhcpBroadcastReplies = false;
144
Amit Ghosh47243cb2017-07-26 05:08:53 +0100145 private DhcpRelayPacketProcessor dhcpRelayPacketProcessor =
146 new DhcpRelayPacketProcessor();
147
Amit Ghosh8951f042017-08-10 13:48:10 +0100148 private InnerMastershipListener changeListener = new InnerMastershipListener();
149 private InnerDeviceListener deviceListener = new InnerDeviceListener();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100150
Amit Ghosh8951f042017-08-10 13:48:10 +0100151 // connect points to the DHCP server
152 Set<ConnectPoint> dhcpConnectPoints;
153 private AtomicReference<ConnectPoint> dhcpServerConnectPoint = new AtomicReference<>();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100154 private MacAddress dhcpConnectMac = MacAddress.BROADCAST;
155 private ApplicationId appId;
156
Amit Ghosha17354e2017-08-23 12:56:04 +0100157 static Map<String, DhcpAllocationInfo> allocationMap = Maps.newConcurrentMap();
158
Amit Ghosh47243cb2017-07-26 05:08:53 +0100159 @Activate
160 protected void activate(ComponentContext context) {
161 //start the dhcp relay agent
162 appId = coreService.registerApplication(DHCP_L2RELAY_APP);
163 componentConfigService.registerProperties(getClass());
164
165 cfgService.addListener(cfgListener);
Amit Ghosh8951f042017-08-10 13:48:10 +0100166 mastershipService.addListener(changeListener);
167 deviceService.addListener(deviceListener);
168
Amit Ghosh47243cb2017-07-26 05:08:53 +0100169 factories.forEach(cfgService::registerConfigFactory);
170 //update the dhcp server configuration.
171 updateConfig();
172 //add the packet services.
173 packetService.addProcessor(dhcpRelayPacketProcessor,
174 PacketProcessor.director(0));
175 requestDhcpPackets();
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000176 if (context != null) {
177 modified(context);
178 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100179
180 log.info("DHCP-L2-RELAY Started");
181 }
182
183 @Deactivate
184 protected void deactivate() {
185 cfgService.removeListener(cfgListener);
186 factories.forEach(cfgService::unregisterConfigFactory);
187 packetService.removeProcessor(dhcpRelayPacketProcessor);
188 cancelDhcpPackets();
189
190 componentConfigService.unregisterProperties(getClass(), false);
Deepa Vaddireddy77a6ac72017-09-20 20:36:52 +0530191 deviceService.removeListener(deviceListener);
192 mastershipService.removeListener(changeListener);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100193 log.info("DHCP-L2-RELAY Stopped");
194 }
195
196 @Modified
197 protected void modified(ComponentContext context) {
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000198
Amit Ghosh47243cb2017-07-26 05:08:53 +0100199 Dictionary<?, ?> properties = context.getProperties();
200
201 Boolean o = Tools.isPropertyEnabled(properties, "option82");
202 if (o != null) {
203 option82 = o;
204 }
205 }
206
207 /**
208 * Checks if this app has been configured.
209 *
210 * @return true if all information we need have been initialized
211 */
212 private boolean configured() {
Amit Ghosh8951f042017-08-10 13:48:10 +0100213 return dhcpServerConnectPoint.get() != null;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100214 }
215
Amit Ghosh8951f042017-08-10 13:48:10 +0100216 /**
217 * Selects a connect point through an available device for which it is the master.
218 */
219 private void selectServerConnectPoint() {
220 synchronized (this) {
221 dhcpServerConnectPoint.set(null);
222 if (dhcpConnectPoints != null) {
223 // find a connect point through a device for which we are master
224 for (ConnectPoint cp: dhcpConnectPoints) {
225 if (mastershipService.isLocalMaster(cp.deviceId())) {
226 if (deviceService.isAvailable(cp.deviceId())) {
227 dhcpServerConnectPoint.set(cp);
228 }
229 log.info("DHCP connectPoint selected is {}", cp);
230 break;
231 }
232 }
233 }
234
235 log.info("DHCP Server connectPoint is {}", dhcpServerConnectPoint.get());
236
237 if (dhcpServerConnectPoint.get() == null) {
238 log.error("Master of none, can't relay DHCP Message to server");
239 }
240 }
241 }
242
243 /**
244 * Updates the network configuration.
245 */
Amit Ghosh47243cb2017-07-26 05:08:53 +0100246 private void updateConfig() {
247 DhcpL2RelayConfig cfg = cfgService.getConfig(appId, DhcpL2RelayConfig.class);
248 if (cfg == null) {
249 log.warn("Dhcp Server info not available");
250 return;
251 }
Amit Ghosh8951f042017-08-10 13:48:10 +0100252
253 dhcpConnectPoints = Sets.newConcurrentHashSet(cfg.getDhcpServerConnectPoint());
254
255 selectServerConnectPoint();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100256
Amit Ghosh47243cb2017-07-26 05:08:53 +0100257 log.info("dhcp server connect point: " + dhcpServerConnectPoint);
258 }
259
260 /**
261 * Request DHCP packet in via PacketService.
262 */
263 private void requestDhcpPackets() {
Amit Ghosh8951f042017-08-10 13:48:10 +0100264 TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
265 .matchEthType(Ethernet.TYPE_IPV4)
266 .matchIPProtocol(IPv4.PROTOCOL_UDP)
267 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
268 packetService.requestPackets(selectorServer.build(),
269 PacketPriority.CONTROL, appId);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100270
Amit Ghosh8951f042017-08-10 13:48:10 +0100271 TrafficSelector.Builder selectorClient = DefaultTrafficSelector.builder()
272 .matchEthType(Ethernet.TYPE_IPV4)
273 .matchIPProtocol(IPv4.PROTOCOL_UDP)
274 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT));
275 packetService.requestPackets(selectorClient.build(),
276 PacketPriority.CONTROL, appId);
277
Amit Ghosh47243cb2017-07-26 05:08:53 +0100278 }
279
280 /**
281 * Cancel requested DHCP packets in via packet service.
282 */
283 private void cancelDhcpPackets() {
Amit Ghosh8951f042017-08-10 13:48:10 +0100284 TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
285 .matchEthType(Ethernet.TYPE_IPV4)
286 .matchIPProtocol(IPv4.PROTOCOL_UDP)
287 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
288 packetService.cancelPackets(selectorServer.build(),
289 PacketPriority.CONTROL, appId);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100290
Amit Ghosh8951f042017-08-10 13:48:10 +0100291 TrafficSelector.Builder selectorClient = DefaultTrafficSelector.builder()
292 .matchEthType(Ethernet.TYPE_IPV4)
293 .matchIPProtocol(IPv4.PROTOCOL_UDP)
294 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT));
295 packetService.cancelPackets(selectorClient.build(),
296 PacketPriority.CONTROL, appId);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100297 }
298
Amit Ghosha17354e2017-08-23 12:56:04 +0100299 public static Map<String, DhcpAllocationInfo> allocationMap() {
300 return allocationMap;
301 }
302
Amit Ghosh47243cb2017-07-26 05:08:53 +0100303 private SubscriberAndDeviceInformation getDevice(PacketContext context) {
304 String serialNo = deviceService.getDevice(context.inPacket().
305 receivedFrom().deviceId()).serialNumber();
306
307 return subsService.get(serialNo);
308 }
309
310 private SubscriberAndDeviceInformation getDevice(ConnectPoint cp) {
311 String serialNo = deviceService.getDevice(cp.deviceId()).
312 serialNumber();
313
314 return subsService.get(serialNo);
315 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100316
317 private MacAddress relayAgentMacAddress(PacketContext context) {
318
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000319 SubscriberAndDeviceInformation device = this.getDevice(context);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100320 if (device == null) {
321 log.warn("Device not found for {}", context.inPacket().
322 receivedFrom());
323 return null;
324 }
325
326 return device.hardwareIdentifier();
327 }
328
329 private String nasPortId(PacketContext context) {
Amit Ghosh8951f042017-08-10 13:48:10 +0100330 return nasPortId(context.inPacket().receivedFrom());
331 }
332
333 private String nasPortId(ConnectPoint cp) {
334 Port p = deviceService.getPort(cp);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100335 return p.annotations().value(AnnotationKeys.PORT_NAME);
336 }
337
338 private SubscriberAndDeviceInformation getSubscriber(PacketContext context) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100339 return subsService.get(nasPortId(context));
340 }
341
342 private VlanId cTag(PacketContext context) {
343 SubscriberAndDeviceInformation sub = getSubscriber(context);
344 if (sub == null) {
345 log.warn("Subscriber info not found for {}", context.inPacket().
346 receivedFrom());
347 return VlanId.NONE;
348 }
349 return sub.cTag();
350 }
351
Amit Ghosh8951f042017-08-10 13:48:10 +0100352 private VlanId cTag(ConnectPoint cp) {
353 String portId = nasPortId(cp);
354 SubscriberAndDeviceInformation sub = subsService.get(portId);
355 if (sub == null) {
356 log.warn("Subscriber info not found for {} looking for C-TAG", cp);
357 return VlanId.NONE;
358 }
359 return sub.cTag();
360 }
361
362 private VlanId sTag(ConnectPoint cp) {
363 String portId = nasPortId(cp);
364 SubscriberAndDeviceInformation sub = subsService.get(portId);
365 if (sub == null) {
366 log.warn("Subscriber info not found for {} looking for S-TAG", cp);
367 return VlanId.NONE;
368 }
369 return sub.sTag();
370 }
371
Amit Ghosh47243cb2017-07-26 05:08:53 +0100372 private VlanId sTag(PacketContext context) {
373 SubscriberAndDeviceInformation sub = getSubscriber(context);
374 if (sub == null) {
375 log.warn("Subscriber info not found for {}", context.inPacket().
376 receivedFrom());
377 return VlanId.NONE;
378 }
379 return sub.sTag();
380 }
381
382 private class DhcpRelayPacketProcessor implements PacketProcessor {
383
384 @Override
385 public void process(PacketContext context) {
386 if (!configured()) {
387 log.warn("Missing DHCP relay config. Abort packet processing");
388 return;
389 }
390
391 // process the packet and get the payload
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000392 DhcpEthernet packet = null;
393 ByteBuffer byteBuffer = context.inPacket().unparsed();
394 try {
395 packet = DhcpEthernet.deserializer().deserialize(byteBuffer.array(), 0, byteBuffer.array().length);
396 } catch (DeserializationException e) {
397 log.warn("Unable to deserialize packet");
398 }
399
Amit Ghosh47243cb2017-07-26 05:08:53 +0100400 if (packet == null) {
401 log.warn("Packet is null");
402 return;
403 }
404
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000405 log.debug("Got a packet ", packet);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100406
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000407 if (packet.getEtherType() == DhcpEthernet.TYPE_IPV4) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100408 IPv4 ipv4Packet = (IPv4) packet.getPayload();
409
410 if (ipv4Packet.getProtocol() == IPv4.PROTOCOL_UDP) {
411 UDP udpPacket = (UDP) ipv4Packet.getPayload();
412 if (udpPacket.getSourcePort() == UDP.DHCP_CLIENT_PORT ||
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000413 udpPacket.getSourcePort() == UDP.DHCP_SERVER_PORT) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100414 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
415 //This packet is dhcp.
416 processDhcpPacket(context, packet, dhcpPayload);
417 }
418 }
419 }
420 }
421
422 //forward the packet to ConnectPoint where the DHCP server is attached.
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000423 private void forwardPacket(DhcpEthernet packet) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100424
Amit Ghosh8951f042017-08-10 13:48:10 +0100425 if (dhcpServerConnectPoint.get() != null) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100426 TrafficTreatment t = DefaultTrafficTreatment.builder()
Amit Ghosh8951f042017-08-10 13:48:10 +0100427 .setOutput(dhcpServerConnectPoint.get().port()).build();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100428 OutboundPacket o = new DefaultOutboundPacket(
Amit Ghosh8951f042017-08-10 13:48:10 +0100429 dhcpServerConnectPoint.get().deviceId(), t,
Amit Ghosh47243cb2017-07-26 05:08:53 +0100430 ByteBuffer.wrap(packet.serialize()));
431 if (log.isTraceEnabled()) {
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000432 log.trace("Relaying packet to dhcp server {} at {}",
Amit Ghosh8951f042017-08-10 13:48:10 +0100433 packet, dhcpServerConnectPoint.get());
Amit Ghosh47243cb2017-07-26 05:08:53 +0100434 }
435 packetService.emit(o);
436 } else {
437 log.warn("No dhcp server connect point");
438 }
439 }
440
Amit Ghosha17354e2017-08-23 12:56:04 +0100441 // get the type of the DHCP packet
442 private DHCPPacketType getDhcpPacketType(DHCP dhcpPayload) {
443
444 for (DHCPOption option : dhcpPayload.getOptions()) {
445 if (option.getCode() == OptionCode_MessageType.getValue()) {
446 byte[] data = option.getData();
447 return DHCPPacketType.getType(data[0]);
448 }
449 }
450 return null;
451 }
452
Amit Ghosh47243cb2017-07-26 05:08:53 +0100453 //process the dhcp packet before sending to server
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000454 private void processDhcpPacket(PacketContext context, DhcpEthernet packet,
Amit Ghosh47243cb2017-07-26 05:08:53 +0100455 DHCP dhcpPayload) {
456 if (dhcpPayload == null) {
457 log.warn("DHCP payload is null");
458 return;
459 }
460
Amit Ghosha17354e2017-08-23 12:56:04 +0100461 DHCPPacketType incomingPacketType = getDhcpPacketType(dhcpPayload);
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000462
Amit Ghosh47243cb2017-07-26 05:08:53 +0100463 log.info("Received DHCP Packet of type {}", incomingPacketType);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100464
465 switch (incomingPacketType) {
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000466 case DHCPDISCOVER:
467 DhcpEthernet ethernetPacketDiscover =
468 processDhcpPacketFromClient(context, packet);
469 if (ethernetPacketDiscover != null) {
470 forwardPacket(ethernetPacketDiscover);
471 }
472 break;
473 case DHCPOFFER:
474 //reply to dhcp client.
475 DhcpEthernet ethernetPacketOffer = processDhcpPacketFromServer(packet);
476 if (ethernetPacketOffer != null) {
477 sendReply(ethernetPacketOffer, dhcpPayload);
478 }
479 break;
480 case DHCPREQUEST:
481 DhcpEthernet ethernetPacketRequest =
482 processDhcpPacketFromClient(context, packet);
483 if (ethernetPacketRequest != null) {
484 forwardPacket(ethernetPacketRequest);
485 }
486 break;
487 case DHCPACK:
488 //reply to dhcp client.
489 DhcpEthernet ethernetPacketAck = processDhcpPacketFromServer(packet);
490 if (ethernetPacketAck != null) {
491 sendReply(ethernetPacketAck, dhcpPayload);
492 }
493 break;
494 default:
495 break;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100496 }
497 }
498
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000499 private DhcpEthernet processDhcpPacketFromClient(PacketContext context,
500 DhcpEthernet ethernetPacket) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100501
502 MacAddress relayAgentMac = relayAgentMacAddress(context);
503 if (relayAgentMac == null) {
504 log.warn("RelayAgent MAC not found ");
Amit Ghosh47243cb2017-07-26 05:08:53 +0100505 return null;
506 }
507
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000508 DhcpEthernet etherReply = ethernetPacket;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100509
510 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
511 UDP udpPacket = (UDP) ipv4Packet.getPayload();
512 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
513
Amit Ghosha17354e2017-08-23 12:56:04 +0100514 if (enableDhcpBroadcastReplies) {
515 // We want the reply to come back as a L2 broadcast
516 dhcpPacket.setFlags((short) 0x8000);
517 }
518
519 // remove from the allocation map (used for display) as it's is
520 // requesting a fresh allocation
521 if (getDhcpPacketType(dhcpPacket) == DHCPPacketType.DHCPREQUEST) {
522
523 String portId = nasPortId(context.inPacket().receivedFrom());
524 SubscriberAndDeviceInformation sub = subsService.get(portId);
525 if (sub != null) {
526 allocationMap.remove(sub.nasPortId());
527 }
528 } // end allocation for display
529
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000530 if (option82) {
531 SubscriberAndDeviceInformation entry = getSubscriber(context);
532 if (entry == null) {
533 log.info("Dropping packet as subscriber entry is not available");
534 return null;
535 }
536
537 DHCP dhcpPacketWithOption82 = addOption82(dhcpPacket, entry);
538 udpPacket.setPayload(dhcpPacketWithOption82);
539 }
540
541 ipv4Packet.setPayload(udpPacket);
542 etherReply.setPayload(ipv4Packet);
543 etherReply.setSourceMacAddress(relayAgentMac);
544 etherReply.setDestinationMacAddress(dhcpConnectMac);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100545
Amit Ghosh8951f042017-08-10 13:48:10 +0100546 etherReply.setPriorityCode(ethernetPacket.getPriorityCode());
Amit Ghosh47243cb2017-07-26 05:08:53 +0100547 etherReply.setVlanID(cTag(context).toShort());
Amit Ghosha17354e2017-08-23 12:56:04 +0100548 etherReply.setQinQtpid(DhcpEthernet.TYPE_VLAN);
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000549 etherReply.setQinQVid(sTag(context).toShort());
Amit Ghosh47243cb2017-07-26 05:08:53 +0100550
Amit Ghosh8951f042017-08-10 13:48:10 +0100551 log.info("Finished processing packet -- sending packet {}", etherReply);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100552 return etherReply;
553 }
554
555 //build the DHCP offer/ack with proper client port.
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000556 private DhcpEthernet processDhcpPacketFromServer(DhcpEthernet ethernetPacket) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100557 // get dhcp header.
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000558 DhcpEthernet etherReply = (DhcpEthernet) ethernetPacket.clone();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100559 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
560 UDP udpPacket = (UDP) ipv4Packet.getPayload();
561 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
562
Amit Ghosh47243cb2017-07-26 05:08:53 +0100563 MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
Amit Ghosha17354e2017-08-23 12:56:04 +0100564 ConnectPoint subsCp = getConnectPointOfClient(dstMac);
565 // if it's an ACK packet store the information for display purpose
566 if (getDhcpPacketType(dhcpPayload) == DHCPPacketType.DHCPACK) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100567
Amit Ghosha17354e2017-08-23 12:56:04 +0100568 String portId = nasPortId(subsCp);
569 SubscriberAndDeviceInformation sub = subsService.get(portId);
570 if (sub != null) {
571 List<DHCPOption> options = dhcpPayload.getOptions();
572 List<DHCPOption> circuitIds = options.stream()
573 .filter(option -> option.getCode() == DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue())
574 .collect(Collectors.toList());
575
576 String circuitId = "None";
577 if (circuitIds.size() == 1) {
578 circuitId = circuitIds.get(0).getData().toString();
579 }
580
581 IpAddress ip = IpAddress.valueOf(dhcpPayload.getYourIPAddress());
582
583 //storeDHCPAllocationInfo
584 DhcpAllocationInfo info = new DhcpAllocationInfo(circuitId, dstMac, ip);
585
586 allocationMap.put(sub.nasPortId(), info);
587 }
588 } // end storing of info
Amit Ghosh47243cb2017-07-26 05:08:53 +0100589
590 // we leave the srcMac from the original packet
Amit Ghosh8951f042017-08-10 13:48:10 +0100591 etherReply.setDestinationMacAddress(dstMac);
592 etherReply.setQinQVid(sTag(subsCp).toShort());
593 etherReply.setPriorityCode(ethernetPacket.getPriorityCode());
594 etherReply.setVlanID((cTag(subsCp).toShort()));
Amit Ghosh47243cb2017-07-26 05:08:53 +0100595
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000596 if (option82) {
597 udpPacket.setPayload(removeOption82(dhcpPayload));
598 } else {
599 udpPacket.setPayload(dhcpPayload);
600 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100601 ipv4Packet.setPayload(udpPacket);
602 etherReply.setPayload(ipv4Packet);
603
604 log.info("Finished processing packet");
605 return etherReply;
606 }
607
Amit Ghosha17354e2017-08-23 12:56:04 +0100608 /*
609 * Get ConnectPoint of the Client based on it's MAC address
610 */
611 private ConnectPoint getConnectPointOfClient(MacAddress dstMac) {
612 Set<Host> hosts = hostService.getHostsByMac(dstMac);
613 if (hosts == null || hosts.isEmpty()) {
614 log.warn("Cannot determine host for DHCP client: {}. Aborting "
615 + "relay for dhcp packet from server",
616 dstMac);
617 return null;
618 }
619 for (Host h : hosts) {
620 // if more than one,
621 // find the connect point which has an valid entry in SADIS
622 ConnectPoint cp = new ConnectPoint(h.location().deviceId(),
623 h.location().port());
624
625 if (sTag(cp) != VlanId.NONE) {
626 return cp;
627 }
628 }
629
630 return null;
631 }
632
Amit Ghosh47243cb2017-07-26 05:08:53 +0100633 //send the response to the requester host.
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000634 private void sendReply(DhcpEthernet ethPacket, DHCP dhcpPayload) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100635 MacAddress descMac = valueOf(dhcpPayload.getClientHardwareAddress());
Amit Ghosha17354e2017-08-23 12:56:04 +0100636 ConnectPoint subCp = getConnectPointOfClient(descMac);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100637
638 // Send packet out to requester if the host information is available
Amit Ghosha17354e2017-08-23 12:56:04 +0100639 if (subCp != null) {
640 log.info("Sending DHCP packet to cp: {}", subCp);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100641 TrafficTreatment t = DefaultTrafficTreatment.builder()
Amit Ghosha17354e2017-08-23 12:56:04 +0100642 .setOutput(subCp.port()).build();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100643 OutboundPacket o = new DefaultOutboundPacket(
Amit Ghosha17354e2017-08-23 12:56:04 +0100644 subCp.deviceId(), t, ByteBuffer.wrap(ethPacket.serialize()));
Amit Ghosh47243cb2017-07-26 05:08:53 +0100645 if (log.isTraceEnabled()) {
646 log.trace("Relaying packet to dhcp client {}", ethPacket);
647 }
648 packetService.emit(o);
Amit Ghosha17354e2017-08-23 12:56:04 +0100649 log.info("DHCP Packet sent to {}", subCp);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100650 } else {
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000651 log.error("Dropping DHCP packet because can't find host for {}", descMac);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100652 }
653 }
654 }
655
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000656 private DHCP addOption82(DHCP dhcpPacket, SubscriberAndDeviceInformation entry) {
657 log.debug("option82data {} ", entry);
658
659 List<DHCPOption> options = Lists.newArrayList(dhcpPacket.getOptions());
660 DhcpOption82 option82 = new DhcpOption82();
661 option82.setAgentCircuitId(entry.circuitId());
662 option82.setAgentRemoteId(entry.remoteId());
663 DHCPOption option = new DHCPOption()
664 .setCode(DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue())
665 .setData(option82.toByteArray())
666 .setLength(option82.length());
667
668 options.add(options.size() - 1, option);
669 dhcpPacket.setOptions(options);
Amit Ghosh8951f042017-08-10 13:48:10 +0100670
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000671 return dhcpPacket;
672
673 }
674
675 private DHCP removeOption82(DHCP dhcpPacket) {
676 List<DHCPOption> options = dhcpPacket.getOptions();
677 List<DHCPOption> newoptions = options.stream()
678 .filter(option -> option.getCode() != DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue())
679 .collect(Collectors.toList());
680
681 return dhcpPacket.setOptions(newoptions);
682 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100683 /**
684 * Listener for network config events.
685 */
686 private class InternalConfigListener implements NetworkConfigListener {
687
688 @Override
689 public void event(NetworkConfigEvent event) {
690
691 if ((event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
692 event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED) &&
693 event.configClass().equals(DhcpL2RelayConfig.class)) {
694 updateConfig();
695 log.info("Reconfigured");
696 }
697 }
698 }
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000699
Amit Ghosh8951f042017-08-10 13:48:10 +0100700 /**
701 * Handles Mastership changes for the devices which connect
702 * to the DHCP server.
703 */
704 private class InnerMastershipListener implements MastershipListener {
705 @Override
706 public void event(MastershipEvent event) {
707 if (dhcpServerConnectPoint.get() != null &&
708 dhcpServerConnectPoint.get().deviceId().
709 equals(event.subject())) {
710 log.trace("Mastership Event recevived for {}", event.subject());
711 // mastership of the device for our connect point has changed
712 // reselect
713 selectServerConnectPoint();
714 }
715 }
716 }
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000717
Amit Ghosh8951f042017-08-10 13:48:10 +0100718 /**
719 * Handles Device status change for the devices which connect
720 * to the DHCP server.
721 */
722 private class InnerDeviceListener implements DeviceListener {
723 @Override
724 public void event(DeviceEvent event) {
725 log.trace("Device Event recevived for {} event {}", event.subject(), event.type());
726 if (dhcpServerConnectPoint.get() == null) {
727 switch (event.type()) {
728 case DEVICE_ADDED:
729 case DEVICE_AVAILABILITY_CHANGED:
730 // some device is available check if we can get one
731 selectServerConnectPoint();
732 break;
733 default:
734 break;
735 }
736 return;
737 }
738 if (dhcpServerConnectPoint.get().deviceId().
739 equals(event.subject().id())) {
740 switch (event.type()) {
741 case DEVICE_AVAILABILITY_CHANGED:
742 case DEVICE_REMOVED:
743 case DEVICE_SUSPENDED:
744 // state of our device has changed, check if we need
745 // to re-select
746 selectServerConnectPoint();
747 break;
748 default:
749 break;
750 }
751 }
752 }
753 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100754}