blob: 8911a8f779ca365523f5b58f24522197d37ad852 [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();
Amit Ghosh1125d932017-09-25 21:08:31 +0100158 private boolean modifyClientPktsSrcDstMac = false;
Amit Ghosha17354e2017-08-23 12:56:04 +0100159
Amit Ghosh47243cb2017-07-26 05:08:53 +0100160 @Activate
161 protected void activate(ComponentContext context) {
162 //start the dhcp relay agent
163 appId = coreService.registerApplication(DHCP_L2RELAY_APP);
164 componentConfigService.registerProperties(getClass());
165
166 cfgService.addListener(cfgListener);
Amit Ghosh8951f042017-08-10 13:48:10 +0100167 mastershipService.addListener(changeListener);
168 deviceService.addListener(deviceListener);
169
Amit Ghosh47243cb2017-07-26 05:08:53 +0100170 factories.forEach(cfgService::registerConfigFactory);
171 //update the dhcp server configuration.
172 updateConfig();
173 //add the packet services.
174 packetService.addProcessor(dhcpRelayPacketProcessor,
175 PacketProcessor.director(0));
176 requestDhcpPackets();
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000177 if (context != null) {
178 modified(context);
179 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100180
181 log.info("DHCP-L2-RELAY Started");
182 }
183
184 @Deactivate
185 protected void deactivate() {
186 cfgService.removeListener(cfgListener);
187 factories.forEach(cfgService::unregisterConfigFactory);
188 packetService.removeProcessor(dhcpRelayPacketProcessor);
189 cancelDhcpPackets();
190
191 componentConfigService.unregisterProperties(getClass(), false);
Deepa Vaddireddy81728d52017-09-20 20:36:52 +0530192 deviceService.removeListener(deviceListener);
193 mastershipService.removeListener(changeListener);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100194 log.info("DHCP-L2-RELAY Stopped");
195 }
196
197 @Modified
198 protected void modified(ComponentContext context) {
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000199
Amit Ghosh47243cb2017-07-26 05:08:53 +0100200 Dictionary<?, ?> properties = context.getProperties();
201
202 Boolean o = Tools.isPropertyEnabled(properties, "option82");
203 if (o != null) {
204 option82 = o;
205 }
206 }
207
208 /**
209 * Checks if this app has been configured.
210 *
211 * @return true if all information we need have been initialized
212 */
213 private boolean configured() {
Amit Ghosh8951f042017-08-10 13:48:10 +0100214 return dhcpServerConnectPoint.get() != null;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100215 }
216
Amit Ghosh8951f042017-08-10 13:48:10 +0100217 /**
218 * Selects a connect point through an available device for which it is the master.
219 */
220 private void selectServerConnectPoint() {
221 synchronized (this) {
222 dhcpServerConnectPoint.set(null);
223 if (dhcpConnectPoints != null) {
224 // find a connect point through a device for which we are master
225 for (ConnectPoint cp: dhcpConnectPoints) {
226 if (mastershipService.isLocalMaster(cp.deviceId())) {
227 if (deviceService.isAvailable(cp.deviceId())) {
228 dhcpServerConnectPoint.set(cp);
229 }
230 log.info("DHCP connectPoint selected is {}", cp);
231 break;
232 }
233 }
234 }
235
236 log.info("DHCP Server connectPoint is {}", dhcpServerConnectPoint.get());
237
238 if (dhcpServerConnectPoint.get() == null) {
239 log.error("Master of none, can't relay DHCP Message to server");
240 }
241 }
242 }
243
244 /**
245 * Updates the network configuration.
246 */
Amit Ghosh47243cb2017-07-26 05:08:53 +0100247 private void updateConfig() {
248 DhcpL2RelayConfig cfg = cfgService.getConfig(appId, DhcpL2RelayConfig.class);
249 if (cfg == null) {
250 log.warn("Dhcp Server info not available");
251 return;
252 }
Amit Ghosh8951f042017-08-10 13:48:10 +0100253
254 dhcpConnectPoints = Sets.newConcurrentHashSet(cfg.getDhcpServerConnectPoint());
Amit Ghosh1125d932017-09-25 21:08:31 +0100255 modifyClientPktsSrcDstMac = cfg.getModifySrcDstMacAddresses();
Amit Ghosh8951f042017-08-10 13:48:10 +0100256
257 selectServerConnectPoint();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100258
Amit Ghosh47243cb2017-07-26 05:08:53 +0100259 log.info("dhcp server connect point: " + dhcpServerConnectPoint);
260 }
261
262 /**
263 * Request DHCP packet in via PacketService.
264 */
265 private void requestDhcpPackets() {
Amit Ghosh8951f042017-08-10 13:48:10 +0100266 TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
267 .matchEthType(Ethernet.TYPE_IPV4)
268 .matchIPProtocol(IPv4.PROTOCOL_UDP)
269 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
270 packetService.requestPackets(selectorServer.build(),
271 PacketPriority.CONTROL, appId);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100272
Amit Ghosh8951f042017-08-10 13:48:10 +0100273 TrafficSelector.Builder selectorClient = DefaultTrafficSelector.builder()
274 .matchEthType(Ethernet.TYPE_IPV4)
275 .matchIPProtocol(IPv4.PROTOCOL_UDP)
276 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT));
277 packetService.requestPackets(selectorClient.build(),
278 PacketPriority.CONTROL, appId);
279
Amit Ghosh47243cb2017-07-26 05:08:53 +0100280 }
281
282 /**
283 * Cancel requested DHCP packets in via packet service.
284 */
285 private void cancelDhcpPackets() {
Amit Ghosh8951f042017-08-10 13:48:10 +0100286 TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
287 .matchEthType(Ethernet.TYPE_IPV4)
288 .matchIPProtocol(IPv4.PROTOCOL_UDP)
289 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
290 packetService.cancelPackets(selectorServer.build(),
291 PacketPriority.CONTROL, appId);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100292
Amit Ghosh8951f042017-08-10 13:48:10 +0100293 TrafficSelector.Builder selectorClient = DefaultTrafficSelector.builder()
294 .matchEthType(Ethernet.TYPE_IPV4)
295 .matchIPProtocol(IPv4.PROTOCOL_UDP)
296 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT));
297 packetService.cancelPackets(selectorClient.build(),
298 PacketPriority.CONTROL, appId);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100299 }
300
Amit Ghosha17354e2017-08-23 12:56:04 +0100301 public static Map<String, DhcpAllocationInfo> allocationMap() {
302 return allocationMap;
303 }
304
Amit Ghosh47243cb2017-07-26 05:08:53 +0100305 private SubscriberAndDeviceInformation getDevice(PacketContext context) {
306 String serialNo = deviceService.getDevice(context.inPacket().
307 receivedFrom().deviceId()).serialNumber();
308
309 return subsService.get(serialNo);
310 }
311
312 private SubscriberAndDeviceInformation getDevice(ConnectPoint cp) {
313 String serialNo = deviceService.getDevice(cp.deviceId()).
314 serialNumber();
315
316 return subsService.get(serialNo);
317 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100318
319 private MacAddress relayAgentMacAddress(PacketContext context) {
320
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000321 SubscriberAndDeviceInformation device = this.getDevice(context);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100322 if (device == null) {
323 log.warn("Device not found for {}", context.inPacket().
324 receivedFrom());
325 return null;
326 }
327
328 return device.hardwareIdentifier();
329 }
330
331 private String nasPortId(PacketContext context) {
Amit Ghosh8951f042017-08-10 13:48:10 +0100332 return nasPortId(context.inPacket().receivedFrom());
333 }
334
335 private String nasPortId(ConnectPoint cp) {
336 Port p = deviceService.getPort(cp);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100337 return p.annotations().value(AnnotationKeys.PORT_NAME);
338 }
339
340 private SubscriberAndDeviceInformation getSubscriber(PacketContext context) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100341 return subsService.get(nasPortId(context));
342 }
343
344 private VlanId cTag(PacketContext context) {
345 SubscriberAndDeviceInformation sub = getSubscriber(context);
346 if (sub == null) {
347 log.warn("Subscriber info not found for {}", context.inPacket().
348 receivedFrom());
349 return VlanId.NONE;
350 }
351 return sub.cTag();
352 }
353
Amit Ghosh8951f042017-08-10 13:48:10 +0100354 private VlanId cTag(ConnectPoint cp) {
355 String portId = nasPortId(cp);
356 SubscriberAndDeviceInformation sub = subsService.get(portId);
357 if (sub == null) {
358 log.warn("Subscriber info not found for {} looking for C-TAG", cp);
359 return VlanId.NONE;
360 }
361 return sub.cTag();
362 }
363
364 private VlanId sTag(ConnectPoint cp) {
365 String portId = nasPortId(cp);
366 SubscriberAndDeviceInformation sub = subsService.get(portId);
367 if (sub == null) {
368 log.warn("Subscriber info not found for {} looking for S-TAG", cp);
369 return VlanId.NONE;
370 }
371 return sub.sTag();
372 }
373
Amit Ghosh47243cb2017-07-26 05:08:53 +0100374 private VlanId sTag(PacketContext context) {
375 SubscriberAndDeviceInformation sub = getSubscriber(context);
376 if (sub == null) {
377 log.warn("Subscriber info not found for {}", context.inPacket().
378 receivedFrom());
379 return VlanId.NONE;
380 }
381 return sub.sTag();
382 }
383
384 private class DhcpRelayPacketProcessor implements PacketProcessor {
385
386 @Override
387 public void process(PacketContext context) {
388 if (!configured()) {
389 log.warn("Missing DHCP relay config. Abort packet processing");
390 return;
391 }
392
393 // process the packet and get the payload
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000394 DhcpEthernet packet = null;
395 ByteBuffer byteBuffer = context.inPacket().unparsed();
396 try {
397 packet = DhcpEthernet.deserializer().deserialize(byteBuffer.array(), 0, byteBuffer.array().length);
398 } catch (DeserializationException e) {
399 log.warn("Unable to deserialize packet");
400 }
401
Amit Ghosh47243cb2017-07-26 05:08:53 +0100402 if (packet == null) {
403 log.warn("Packet is null");
404 return;
405 }
406
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000407 log.debug("Got a packet ", packet);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100408
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000409 if (packet.getEtherType() == DhcpEthernet.TYPE_IPV4) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100410 IPv4 ipv4Packet = (IPv4) packet.getPayload();
411
412 if (ipv4Packet.getProtocol() == IPv4.PROTOCOL_UDP) {
413 UDP udpPacket = (UDP) ipv4Packet.getPayload();
414 if (udpPacket.getSourcePort() == UDP.DHCP_CLIENT_PORT ||
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000415 udpPacket.getSourcePort() == UDP.DHCP_SERVER_PORT) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100416 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
417 //This packet is dhcp.
418 processDhcpPacket(context, packet, dhcpPayload);
419 }
420 }
421 }
422 }
423
424 //forward the packet to ConnectPoint where the DHCP server is attached.
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000425 private void forwardPacket(DhcpEthernet packet) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100426
Amit Ghosh8951f042017-08-10 13:48:10 +0100427 if (dhcpServerConnectPoint.get() != null) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100428 TrafficTreatment t = DefaultTrafficTreatment.builder()
Amit Ghosh8951f042017-08-10 13:48:10 +0100429 .setOutput(dhcpServerConnectPoint.get().port()).build();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100430 OutboundPacket o = new DefaultOutboundPacket(
Amit Ghosh8951f042017-08-10 13:48:10 +0100431 dhcpServerConnectPoint.get().deviceId(), t,
Amit Ghosh47243cb2017-07-26 05:08:53 +0100432 ByteBuffer.wrap(packet.serialize()));
433 if (log.isTraceEnabled()) {
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000434 log.trace("Relaying packet to dhcp server {} at {}",
Amit Ghosh8951f042017-08-10 13:48:10 +0100435 packet, dhcpServerConnectPoint.get());
Amit Ghosh47243cb2017-07-26 05:08:53 +0100436 }
437 packetService.emit(o);
438 } else {
439 log.warn("No dhcp server connect point");
440 }
441 }
442
Amit Ghosha17354e2017-08-23 12:56:04 +0100443 // get the type of the DHCP packet
444 private DHCPPacketType getDhcpPacketType(DHCP dhcpPayload) {
445
446 for (DHCPOption option : dhcpPayload.getOptions()) {
447 if (option.getCode() == OptionCode_MessageType.getValue()) {
448 byte[] data = option.getData();
449 return DHCPPacketType.getType(data[0]);
450 }
451 }
452 return null;
453 }
454
Amit Ghosh47243cb2017-07-26 05:08:53 +0100455 //process the dhcp packet before sending to server
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000456 private void processDhcpPacket(PacketContext context, DhcpEthernet packet,
Amit Ghosh47243cb2017-07-26 05:08:53 +0100457 DHCP dhcpPayload) {
458 if (dhcpPayload == null) {
459 log.warn("DHCP payload is null");
460 return;
461 }
462
Amit Ghosha17354e2017-08-23 12:56:04 +0100463 DHCPPacketType incomingPacketType = getDhcpPacketType(dhcpPayload);
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000464
Amit Ghosh47243cb2017-07-26 05:08:53 +0100465 log.info("Received DHCP Packet of type {}", incomingPacketType);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100466
467 switch (incomingPacketType) {
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000468 case DHCPDISCOVER:
469 DhcpEthernet ethernetPacketDiscover =
470 processDhcpPacketFromClient(context, packet);
471 if (ethernetPacketDiscover != null) {
472 forwardPacket(ethernetPacketDiscover);
473 }
474 break;
475 case DHCPOFFER:
476 //reply to dhcp client.
477 DhcpEthernet ethernetPacketOffer = processDhcpPacketFromServer(packet);
478 if (ethernetPacketOffer != null) {
479 sendReply(ethernetPacketOffer, dhcpPayload);
480 }
481 break;
482 case DHCPREQUEST:
483 DhcpEthernet ethernetPacketRequest =
484 processDhcpPacketFromClient(context, packet);
485 if (ethernetPacketRequest != null) {
486 forwardPacket(ethernetPacketRequest);
487 }
488 break;
489 case DHCPACK:
490 //reply to dhcp client.
491 DhcpEthernet ethernetPacketAck = processDhcpPacketFromServer(packet);
492 if (ethernetPacketAck != null) {
493 sendReply(ethernetPacketAck, dhcpPayload);
494 }
495 break;
496 default:
497 break;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100498 }
499 }
500
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000501 private DhcpEthernet processDhcpPacketFromClient(PacketContext context,
502 DhcpEthernet ethernetPacket) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100503
504 MacAddress relayAgentMac = relayAgentMacAddress(context);
505 if (relayAgentMac == null) {
506 log.warn("RelayAgent MAC not found ");
Amit Ghosh47243cb2017-07-26 05:08:53 +0100507 return null;
508 }
509
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000510 DhcpEthernet etherReply = ethernetPacket;
Amit Ghosh47243cb2017-07-26 05:08:53 +0100511
512 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
513 UDP udpPacket = (UDP) ipv4Packet.getPayload();
514 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
515
Amit Ghosha17354e2017-08-23 12:56:04 +0100516 if (enableDhcpBroadcastReplies) {
517 // We want the reply to come back as a L2 broadcast
518 dhcpPacket.setFlags((short) 0x8000);
519 }
520
521 // remove from the allocation map (used for display) as it's is
522 // requesting a fresh allocation
523 if (getDhcpPacketType(dhcpPacket) == DHCPPacketType.DHCPREQUEST) {
524
525 String portId = nasPortId(context.inPacket().receivedFrom());
526 SubscriberAndDeviceInformation sub = subsService.get(portId);
527 if (sub != null) {
528 allocationMap.remove(sub.nasPortId());
529 }
530 } // end allocation for display
531
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000532 if (option82) {
533 SubscriberAndDeviceInformation entry = getSubscriber(context);
534 if (entry == null) {
535 log.info("Dropping packet as subscriber entry is not available");
536 return null;
537 }
538
539 DHCP dhcpPacketWithOption82 = addOption82(dhcpPacket, entry);
540 udpPacket.setPayload(dhcpPacketWithOption82);
541 }
542
543 ipv4Packet.setPayload(udpPacket);
544 etherReply.setPayload(ipv4Packet);
Amit Ghosh1125d932017-09-25 21:08:31 +0100545 if (modifyClientPktsSrcDstMac) {
546 etherReply.setSourceMacAddress(relayAgentMac);
547 etherReply.setDestinationMacAddress(dhcpConnectMac);
548 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100549
Amit Ghosh8951f042017-08-10 13:48:10 +0100550 etherReply.setPriorityCode(ethernetPacket.getPriorityCode());
Amit Ghosh47243cb2017-07-26 05:08:53 +0100551 etherReply.setVlanID(cTag(context).toShort());
Amit Ghosha17354e2017-08-23 12:56:04 +0100552 etherReply.setQinQtpid(DhcpEthernet.TYPE_VLAN);
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000553 etherReply.setQinQVid(sTag(context).toShort());
Amit Ghosh47243cb2017-07-26 05:08:53 +0100554
Amit Ghosh8951f042017-08-10 13:48:10 +0100555 log.info("Finished processing packet -- sending packet {}", etherReply);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100556 return etherReply;
557 }
558
559 //build the DHCP offer/ack with proper client port.
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000560 private DhcpEthernet processDhcpPacketFromServer(DhcpEthernet ethernetPacket) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100561 // get dhcp header.
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000562 DhcpEthernet etherReply = (DhcpEthernet) ethernetPacket.clone();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100563 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
564 UDP udpPacket = (UDP) ipv4Packet.getPayload();
565 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
566
Amit Ghosh47243cb2017-07-26 05:08:53 +0100567 MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
Amit Ghosha17354e2017-08-23 12:56:04 +0100568 ConnectPoint subsCp = getConnectPointOfClient(dstMac);
569 // if it's an ACK packet store the information for display purpose
570 if (getDhcpPacketType(dhcpPayload) == DHCPPacketType.DHCPACK) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100571
Amit Ghosha17354e2017-08-23 12:56:04 +0100572 String portId = nasPortId(subsCp);
573 SubscriberAndDeviceInformation sub = subsService.get(portId);
574 if (sub != null) {
575 List<DHCPOption> options = dhcpPayload.getOptions();
576 List<DHCPOption> circuitIds = options.stream()
577 .filter(option -> option.getCode() == DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue())
578 .collect(Collectors.toList());
579
580 String circuitId = "None";
581 if (circuitIds.size() == 1) {
582 circuitId = circuitIds.get(0).getData().toString();
583 }
584
585 IpAddress ip = IpAddress.valueOf(dhcpPayload.getYourIPAddress());
586
587 //storeDHCPAllocationInfo
588 DhcpAllocationInfo info = new DhcpAllocationInfo(circuitId, dstMac, ip);
589
590 allocationMap.put(sub.nasPortId(), info);
591 }
592 } // end storing of info
Amit Ghosh47243cb2017-07-26 05:08:53 +0100593
594 // we leave the srcMac from the original packet
Amit Ghosh8951f042017-08-10 13:48:10 +0100595 etherReply.setDestinationMacAddress(dstMac);
596 etherReply.setQinQVid(sTag(subsCp).toShort());
597 etherReply.setPriorityCode(ethernetPacket.getPriorityCode());
598 etherReply.setVlanID((cTag(subsCp).toShort()));
Amit Ghosh47243cb2017-07-26 05:08:53 +0100599
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000600 if (option82) {
601 udpPacket.setPayload(removeOption82(dhcpPayload));
602 } else {
603 udpPacket.setPayload(dhcpPayload);
604 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100605 ipv4Packet.setPayload(udpPacket);
606 etherReply.setPayload(ipv4Packet);
607
608 log.info("Finished processing packet");
609 return etherReply;
610 }
611
Amit Ghosha17354e2017-08-23 12:56:04 +0100612 /*
613 * Get ConnectPoint of the Client based on it's MAC address
614 */
615 private ConnectPoint getConnectPointOfClient(MacAddress dstMac) {
616 Set<Host> hosts = hostService.getHostsByMac(dstMac);
617 if (hosts == null || hosts.isEmpty()) {
618 log.warn("Cannot determine host for DHCP client: {}. Aborting "
619 + "relay for dhcp packet from server",
620 dstMac);
621 return null;
622 }
623 for (Host h : hosts) {
624 // if more than one,
625 // find the connect point which has an valid entry in SADIS
626 ConnectPoint cp = new ConnectPoint(h.location().deviceId(),
627 h.location().port());
628
629 if (sTag(cp) != VlanId.NONE) {
630 return cp;
631 }
632 }
633
634 return null;
635 }
636
Amit Ghosh47243cb2017-07-26 05:08:53 +0100637 //send the response to the requester host.
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000638 private void sendReply(DhcpEthernet ethPacket, DHCP dhcpPayload) {
Amit Ghosh47243cb2017-07-26 05:08:53 +0100639 MacAddress descMac = valueOf(dhcpPayload.getClientHardwareAddress());
Amit Ghosha17354e2017-08-23 12:56:04 +0100640 ConnectPoint subCp = getConnectPointOfClient(descMac);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100641
642 // Send packet out to requester if the host information is available
Amit Ghosha17354e2017-08-23 12:56:04 +0100643 if (subCp != null) {
644 log.info("Sending DHCP packet to cp: {}", subCp);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100645 TrafficTreatment t = DefaultTrafficTreatment.builder()
Amit Ghosha17354e2017-08-23 12:56:04 +0100646 .setOutput(subCp.port()).build();
Amit Ghosh47243cb2017-07-26 05:08:53 +0100647 OutboundPacket o = new DefaultOutboundPacket(
Amit Ghosha17354e2017-08-23 12:56:04 +0100648 subCp.deviceId(), t, ByteBuffer.wrap(ethPacket.serialize()));
Amit Ghosh47243cb2017-07-26 05:08:53 +0100649 if (log.isTraceEnabled()) {
650 log.trace("Relaying packet to dhcp client {}", ethPacket);
651 }
652 packetService.emit(o);
Amit Ghosha17354e2017-08-23 12:56:04 +0100653 log.info("DHCP Packet sent to {}", subCp);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100654 } else {
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000655 log.error("Dropping DHCP packet because can't find host for {}", descMac);
Amit Ghosh47243cb2017-07-26 05:08:53 +0100656 }
657 }
658 }
659
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000660 private DHCP addOption82(DHCP dhcpPacket, SubscriberAndDeviceInformation entry) {
661 log.debug("option82data {} ", entry);
662
663 List<DHCPOption> options = Lists.newArrayList(dhcpPacket.getOptions());
664 DhcpOption82 option82 = new DhcpOption82();
665 option82.setAgentCircuitId(entry.circuitId());
666 option82.setAgentRemoteId(entry.remoteId());
667 DHCPOption option = new DHCPOption()
668 .setCode(DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue())
669 .setData(option82.toByteArray())
670 .setLength(option82.length());
671
672 options.add(options.size() - 1, option);
673 dhcpPacket.setOptions(options);
Amit Ghosh8951f042017-08-10 13:48:10 +0100674
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000675 return dhcpPacket;
676
677 }
678
679 private DHCP removeOption82(DHCP dhcpPacket) {
680 List<DHCPOption> options = dhcpPacket.getOptions();
681 List<DHCPOption> newoptions = options.stream()
682 .filter(option -> option.getCode() != DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue())
683 .collect(Collectors.toList());
684
685 return dhcpPacket.setOptions(newoptions);
686 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100687 /**
688 * Listener for network config events.
689 */
690 private class InternalConfigListener implements NetworkConfigListener {
691
692 @Override
693 public void event(NetworkConfigEvent event) {
694
695 if ((event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
696 event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED) &&
697 event.configClass().equals(DhcpL2RelayConfig.class)) {
698 updateConfig();
699 log.info("Reconfigured");
700 }
701 }
702 }
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000703
Amit Ghosh8951f042017-08-10 13:48:10 +0100704 /**
705 * Handles Mastership changes for the devices which connect
706 * to the DHCP server.
707 */
708 private class InnerMastershipListener implements MastershipListener {
709 @Override
710 public void event(MastershipEvent event) {
711 if (dhcpServerConnectPoint.get() != null &&
712 dhcpServerConnectPoint.get().deviceId().
713 equals(event.subject())) {
714 log.trace("Mastership Event recevived for {}", event.subject());
715 // mastership of the device for our connect point has changed
716 // reselect
717 selectServerConnectPoint();
718 }
719 }
720 }
Deepa vaddireddy0060f532017-08-04 06:46:05 +0000721
Amit Ghosh8951f042017-08-10 13:48:10 +0100722 /**
723 * Handles Device status change for the devices which connect
724 * to the DHCP server.
725 */
726 private class InnerDeviceListener implements DeviceListener {
727 @Override
728 public void event(DeviceEvent event) {
729 log.trace("Device Event recevived for {} event {}", event.subject(), event.type());
730 if (dhcpServerConnectPoint.get() == null) {
731 switch (event.type()) {
732 case DEVICE_ADDED:
733 case DEVICE_AVAILABILITY_CHANGED:
734 // some device is available check if we can get one
735 selectServerConnectPoint();
736 break;
737 default:
738 break;
739 }
740 return;
741 }
742 if (dhcpServerConnectPoint.get().deviceId().
743 equals(event.subject().id())) {
744 switch (event.type()) {
745 case DEVICE_AVAILABILITY_CHANGED:
746 case DEVICE_REMOVED:
747 case DEVICE_SUSPENDED:
748 // state of our device has changed, check if we need
749 // to re-select
750 selectServerConnectPoint();
751 break;
752 default:
753 break;
754 }
755 }
756 }
757 }
Amit Ghosh47243cb2017-07-26 05:08:53 +0100758}