blob: 5d264be137525b0788b2ed51298484e81ab8819e [file] [log] [blame]
Amit Ghosh47243cb2017-07-26 05:08:53 +01001/*
Brian O'Connor10570622017-08-03 22:45:53 -07002 * 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;
19import org.apache.felix.scr.annotations.Activate;
20import org.apache.felix.scr.annotations.Component;
21import org.apache.felix.scr.annotations.Deactivate;
22import org.apache.felix.scr.annotations.Modified;
23import org.apache.felix.scr.annotations.Property;
24import org.apache.felix.scr.annotations.Reference;
25import org.apache.felix.scr.annotations.ReferenceCardinality;
26import org.onlab.packet.Ethernet;
27import org.onlab.packet.MacAddress;
28import org.onlab.packet.DHCP;
29import org.onlab.packet.DHCPOption;
30import org.onlab.packet.DHCPPacketType;
31import org.onlab.packet.Ip4Address;
32import org.onlab.packet.IPv4;
33import org.onlab.packet.TpPort;
34import org.onlab.packet.UDP;
35import org.onlab.packet.VlanId;
36
37import org.onlab.util.Tools;
38import org.onosproject.cfg.ComponentConfigService;
39import org.onosproject.core.ApplicationId;
40import org.onosproject.core.CoreService;
41import org.onosproject.net.AnnotationKeys;
42import org.onosproject.net.ConnectPoint;
43import org.onosproject.net.Host;
44import org.onosproject.net.Port;
45import org.onosproject.net.config.ConfigFactory;
46import org.onosproject.net.config.NetworkConfigEvent;
47import org.onosproject.net.config.NetworkConfigListener;
48import org.onosproject.net.config.NetworkConfigRegistry;
49import org.onosproject.net.device.DeviceService;
50import org.onosproject.net.flow.DefaultTrafficSelector;
51import org.onosproject.net.flow.DefaultTrafficTreatment;
52import org.onosproject.net.flow.TrafficSelector;
53import org.onosproject.net.flow.TrafficTreatment;
54import org.onosproject.net.host.HostService;
55import org.onosproject.net.packet.DefaultOutboundPacket;
56import org.onosproject.net.packet.OutboundPacket;
57import org.onosproject.net.packet.PacketContext;
58import org.onosproject.net.packet.PacketPriority;
59import org.onosproject.net.packet.PacketProcessor;
60import org.onosproject.net.packet.PacketService;
61
62import org.opencord.sadis.SubscriberAndDeviceInformation;
63import org.opencord.sadis.SubscriberAndDeviceInformationService;
64
65import org.osgi.service.component.ComponentContext;
66import org.slf4j.Logger;
67import org.slf4j.LoggerFactory;
68
69import java.nio.ByteBuffer;
70import java.util.Dictionary;
71import java.util.Set;
72import java.util.Optional;
73
74import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
75import static org.onlab.packet.MacAddress.valueOf;
76import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
77
78/**
79 * DHCP Relay Agent Application Component.
80 */
81@Component(immediate = true)
82public class DhcpL2Relay {
83
84 public static final String DHCP_L2RELAY_APP = "org.opencord.dhcpl2relay";
85 private final Logger log = LoggerFactory.getLogger(getClass());
86 private final InternalConfigListener cfgListener =
87 new InternalConfigListener();
88
89 private final Set<ConfigFactory> factories = ImmutableSet.of(
90 new ConfigFactory<ApplicationId, DhcpL2RelayConfig>(APP_SUBJECT_FACTORY,
91 DhcpL2RelayConfig.class,
92 "dhcpl2relay") {
93 @Override
94 public DhcpL2RelayConfig createConfig() {
95 return new DhcpL2RelayConfig();
96 }
97 }
98 );
99
100 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
101 protected NetworkConfigRegistry cfgService;
102
103 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
104 protected CoreService coreService;
105
106 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
107 protected PacketService packetService;
108
109 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
110 protected HostService hostService;
111
112 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
113 protected ComponentConfigService componentConfigService;
114
115 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
116 protected SubscriberAndDeviceInformationService subsService;
117
118 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
119 protected DeviceService deviceService;
120
121 @Property(name = "option82", boolValue = true,
122 label = "Add option 82 to relayed packets")
123 protected boolean option82 = true;
124
125 private DhcpRelayPacketProcessor dhcpRelayPacketProcessor =
126 new DhcpRelayPacketProcessor();
127
128
129 private ConnectPoint dhcpServerConnectPoint = null;
130 private MacAddress dhcpConnectMac = MacAddress.BROADCAST;
131 private ApplicationId appId;
132
133 @Activate
134 protected void activate(ComponentContext context) {
135 //start the dhcp relay agent
136 appId = coreService.registerApplication(DHCP_L2RELAY_APP);
137 componentConfigService.registerProperties(getClass());
138
139 cfgService.addListener(cfgListener);
140 factories.forEach(cfgService::registerConfigFactory);
141 //update the dhcp server configuration.
142 updateConfig();
143 //add the packet services.
144 packetService.addProcessor(dhcpRelayPacketProcessor,
145 PacketProcessor.director(0));
146 requestDhcpPackets();
147 modified(context);
148
149 log.info("DHCP-L2-RELAY Started");
150 }
151
152 @Deactivate
153 protected void deactivate() {
154 cfgService.removeListener(cfgListener);
155 factories.forEach(cfgService::unregisterConfigFactory);
156 packetService.removeProcessor(dhcpRelayPacketProcessor);
157 cancelDhcpPackets();
158
159 componentConfigService.unregisterProperties(getClass(), false);
160
161 log.info("DHCP-L2-RELAY Stopped");
162 }
163
164 @Modified
165 protected void modified(ComponentContext context) {
166 Dictionary<?, ?> properties = context.getProperties();
167
168 Boolean o = Tools.isPropertyEnabled(properties, "option82");
169 if (o != null) {
170 option82 = o;
171 }
172 }
173
174 /**
175 * Checks if this app has been configured.
176 *
177 * @return true if all information we need have been initialized
178 */
179 private boolean configured() {
180 return dhcpServerConnectPoint != null;
181 }
182
183 private void updateConfig() {
184 DhcpL2RelayConfig cfg = cfgService.getConfig(appId, DhcpL2RelayConfig.class);
185 if (cfg == null) {
186 log.warn("Dhcp Server info not available");
187 return;
188 }
189
190 dhcpServerConnectPoint = cfg.getDhcpServerConnectPoint();
191 log.info("dhcp server connect point: " + dhcpServerConnectPoint);
192 }
193
194 /**
195 * Request DHCP packet in via PacketService.
196 */
197 private void requestDhcpPackets() {
198 if (dhcpServerConnectPoint != null) {
199 TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
200 .matchEthType(Ethernet.TYPE_IPV4)
201 .matchIPProtocol(IPv4.PROTOCOL_UDP)
202 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
203 packetService.requestPackets(selectorServer.build(),
204 PacketPriority.CONTROL, appId,
205 Optional.of(dhcpServerConnectPoint.deviceId()));
206
207 TrafficSelector.Builder selectorClient = DefaultTrafficSelector.builder()
208 .matchEthType(Ethernet.TYPE_IPV4)
209 .matchIPProtocol(IPv4.PROTOCOL_UDP)
210 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT));
211 packetService.requestPackets(selectorClient.build(),
212 PacketPriority.CONTROL, appId,
213 Optional.of(dhcpServerConnectPoint.deviceId()));
214 }
215 }
216
217 /**
218 * Cancel requested DHCP packets in via packet service.
219 */
220 private void cancelDhcpPackets() {
221 if (dhcpServerConnectPoint != null) {
222 TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
223 .matchEthType(Ethernet.TYPE_IPV4)
224 .matchIPProtocol(IPv4.PROTOCOL_UDP)
225 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
226 packetService.cancelPackets(selectorServer.build(),
227 PacketPriority.CONTROL, appId,
228 Optional.of(dhcpServerConnectPoint.deviceId()));
229
230 TrafficSelector.Builder selectorClient = DefaultTrafficSelector.builder()
231 .matchEthType(Ethernet.TYPE_IPV4)
232 .matchIPProtocol(IPv4.PROTOCOL_UDP)
233 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT));
234 packetService.cancelPackets(selectorClient.build(),
235 PacketPriority.CONTROL, appId,
236 Optional.of(dhcpServerConnectPoint.deviceId()));
237 }
238 }
239
240 private SubscriberAndDeviceInformation getDevice(PacketContext context) {
241 String serialNo = deviceService.getDevice(context.inPacket().
242 receivedFrom().deviceId()).serialNumber();
243
244 return subsService.get(serialNo);
245 }
246
247 private SubscriberAndDeviceInformation getDevice(ConnectPoint cp) {
248 String serialNo = deviceService.getDevice(cp.deviceId()).
249 serialNumber();
250
251 return subsService.get(serialNo);
252 }
253 private Ip4Address relayAgentIPv4Address(ConnectPoint cp) {
254
255 SubscriberAndDeviceInformation device = getDevice(cp);
256 if (device == null) {
257 log.warn("Device not found for {}", cp);
258 return null;
259 }
260
261 return device.ipAddress();
262 }
263
264 private MacAddress relayAgentMacAddress(PacketContext context) {
265
266 SubscriberAndDeviceInformation device = getDevice(context);
267 if (device == null) {
268 log.warn("Device not found for {}", context.inPacket().
269 receivedFrom());
270 return null;
271 }
272
273 return device.hardwareIdentifier();
274 }
275
276 private String nasPortId(PacketContext context) {
277 Port p = deviceService.getPort(context.inPacket().receivedFrom());
278
279 return p.annotations().value(AnnotationKeys.PORT_NAME);
280 }
281
282 private SubscriberAndDeviceInformation getSubscriber(PacketContext context) {
283
284 return subsService.get(nasPortId(context));
285 }
286
287 private VlanId cTag(PacketContext context) {
288 SubscriberAndDeviceInformation sub = getSubscriber(context);
289 if (sub == null) {
290 log.warn("Subscriber info not found for {}", context.inPacket().
291 receivedFrom());
292 return VlanId.NONE;
293 }
294 return sub.cTag();
295 }
296
297 private VlanId sTag(PacketContext context) {
298 SubscriberAndDeviceInformation sub = getSubscriber(context);
299 if (sub == null) {
300 log.warn("Subscriber info not found for {}", context.inPacket().
301 receivedFrom());
302 return VlanId.NONE;
303 }
304 return sub.sTag();
305 }
306
307 private class DhcpRelayPacketProcessor implements PacketProcessor {
308
309 @Override
310 public void process(PacketContext context) {
311 if (!configured()) {
312 log.warn("Missing DHCP relay config. Abort packet processing");
313 return;
314 }
315
316 // process the packet and get the payload
317 Ethernet packet = context.inPacket().parsed();
318 if (packet == null) {
319 log.warn("Packet is null");
320 return;
321 }
322
323 //log.info("Got a packet of type {}", packet.getEtherType());
324
325 if (packet.getEtherType() == Ethernet.TYPE_IPV4) {
326 IPv4 ipv4Packet = (IPv4) packet.getPayload();
327
328 if (ipv4Packet.getProtocol() == IPv4.PROTOCOL_UDP) {
329 UDP udpPacket = (UDP) ipv4Packet.getPayload();
330 if (udpPacket.getSourcePort() == UDP.DHCP_CLIENT_PORT ||
331 udpPacket.getSourcePort() == UDP.DHCP_SERVER_PORT) {
332 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
333 //This packet is dhcp.
334 processDhcpPacket(context, packet, dhcpPayload);
335 }
336 }
337 }
338 }
339
340 //forward the packet to ConnectPoint where the DHCP server is attached.
341 private void forwardPacket(Ethernet packet) {
342
343 if (dhcpServerConnectPoint != null) {
344 TrafficTreatment t = DefaultTrafficTreatment.builder()
345 .setOutput(dhcpServerConnectPoint.port()).build();
346 OutboundPacket o = new DefaultOutboundPacket(
347 dhcpServerConnectPoint.deviceId(), t,
348 ByteBuffer.wrap(packet.serialize()));
349 if (log.isTraceEnabled()) {
350 log.trace("Relaying packet to dhcp server {} at {}",
351 packet, dhcpServerConnectPoint);
352 }
353 packetService.emit(o);
354 } else {
355 log.warn("No dhcp server connect point");
356 }
357 }
358
359 //process the dhcp packet before sending to server
360 private void processDhcpPacket(PacketContext context, Ethernet packet,
361 DHCP dhcpPayload) {
362 if (dhcpPayload == null) {
363 log.warn("DHCP payload is null");
364 return;
365 }
366
367 DHCPPacketType incomingPacketType = null;
368 for (DHCPOption option : dhcpPayload.getOptions()) {
369 if (option.getCode() == OptionCode_MessageType.getValue()) {
370 byte[] data = option.getData();
371 incomingPacketType = DHCPPacketType.getType(data[0]);
372 }
373 }
374 log.info("Received DHCP Packet of type {}", incomingPacketType);
375 log.trace("Processing Packet {}", packet);
376
377 switch (incomingPacketType) {
378 case DHCPDISCOVER:
379 Ethernet ethernetPacketDiscover =
380 processDhcpPacketFromClient(context, packet);
381 if (ethernetPacketDiscover != null) {
382 forwardPacket(ethernetPacketDiscover);
383 }
384 break;
385 case DHCPOFFER:
386 //reply to dhcp client.
387 Ethernet ethernetPacketOffer = processDhcpPacketFromServer(packet);
388 if (ethernetPacketOffer != null) {
389 sendReply(ethernetPacketOffer, dhcpPayload);
390 }
391 break;
392 case DHCPREQUEST:
393 Ethernet ethernetPacketRequest =
394 processDhcpPacketFromClient(context, packet);
395 if (ethernetPacketRequest != null) {
396 forwardPacket(ethernetPacketRequest);
397 }
398 break;
399 case DHCPACK:
400 //reply to dhcp client.
401 Ethernet ethernetPacketAck = processDhcpPacketFromServer(packet);
402 if (ethernetPacketAck != null) {
403 sendReply(ethernetPacketAck, dhcpPayload);
404 }
405 break;
406 default:
407 break;
408 }
409 }
410
411 private Ethernet processDhcpPacketFromClient(PacketContext context,
412 Ethernet ethernetPacket) {
413 log.info("Processing packet from client");
414
415 MacAddress relayAgentMac = relayAgentMacAddress(context);
416 if (relayAgentMac == null) {
417 log.warn("RelayAgent MAC not found ");
418
419 return null;
420 }
421
422 Ethernet etherReply = ethernetPacket;
423
424 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
425 UDP udpPacket = (UDP) ipv4Packet.getPayload();
426 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
427
428 etherReply.setSourceMACAddress(relayAgentMac);
429 etherReply.setDestinationMACAddress(dhcpConnectMac);
430
431 etherReply.setVlanID(cTag(context).toShort());
432 etherReply.setQinQTPID(Ethernet.TYPE_VLAN);
433 etherReply.setQinQVID(sTag(context).toShort());
434
435 log.info("Finished processing");
436 return etherReply;
437 }
438
439 //build the DHCP offer/ack with proper client port.
440 private Ethernet processDhcpPacketFromServer(Ethernet ethernetPacket) {
441 log.warn("Processing DHCP packet from server");
442 // get dhcp header.
443 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
444 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
445 UDP udpPacket = (UDP) ipv4Packet.getPayload();
446 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
447
448
449 MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
450 Set<Host> hosts = hostService.getHostsByMac(dstMac);
451 if (hosts == null || hosts.isEmpty()) {
452 log.warn("Cannot determine host for DHCP client: {}. Aborting "
453 + "relay for dhcp packet from server {}",
454 dstMac, ethernetPacket);
455 return null;
456 } else if (hosts.size() > 1) {
457 // XXX redo to send reply to all hosts found
458 log.warn("Multiple hosts found for mac:{}. Picking one "
459 + "host out of {}", dstMac, hosts);
460 }
461 Host host = hosts.iterator().next();
462
463 etherReply.setDestinationMACAddress(dstMac);
464 etherReply.setQinQVID(Ethernet.VLAN_UNTAGGED);
465 etherReply.setPriorityCode(ethernetPacket.getPriorityCode());
466 etherReply.setVlanID((short) 0);
467
468 // we leave the srcMac from the original packet
469
470 // figure out the relay agent IP corresponding to the original request
471 Ip4Address relayAgentIP = relayAgentIPv4Address(
472 new ConnectPoint(host.location().deviceId(),
473 host.location().port()));
474 if (relayAgentIP == null) {
475 log.warn("Cannot determine relay agent Ipv4 addr for host {}. "
476 + "Aborting relay for dhcp packet from server {}",
477 host, ethernetPacket);
478 return null;
479 }
480
481 ipv4Packet.setSourceAddress(relayAgentIP.toInt());
482 ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
483
484 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
485 udpPacket.setPayload(dhcpPayload);
486 ipv4Packet.setPayload(udpPacket);
487 etherReply.setPayload(ipv4Packet);
488
489 log.info("Finished processing packet");
490 return etherReply;
491 }
492
493 //send the response to the requester host.
494 private void sendReply(Ethernet ethPacket, DHCP dhcpPayload) {
495 MacAddress descMac = valueOf(dhcpPayload.getClientHardwareAddress());
496 Host host = hostService.getHostsByMac(descMac).stream().findFirst().orElse(null);
497
498 // Send packet out to requester if the host information is available
499 if (host != null) {
500 log.info("Sending DHCP packet to host: {}", host);
501 TrafficTreatment t = DefaultTrafficTreatment.builder()
502 .setOutput(host.location().port()).build();
503 OutboundPacket o = new DefaultOutboundPacket(
504 host.location().deviceId(), t, ByteBuffer.wrap(ethPacket.serialize()));
505 if (log.isTraceEnabled()) {
506 log.trace("Relaying packet to dhcp client {}", ethPacket);
507 }
508 packetService.emit(o);
509 log.error("DHCP Packet sent to {}", host.location());
510 } else {
511 log.info("Dropping DHCP packet because can't find host for {}", descMac);
512 }
513 }
514 }
515
516 /**
517 * Listener for network config events.
518 */
519 private class InternalConfigListener implements NetworkConfigListener {
520
521 @Override
522 public void event(NetworkConfigEvent event) {
523
524 if ((event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
525 event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED) &&
526 event.configClass().equals(DhcpL2RelayConfig.class)) {
527 updateConfig();
528 log.info("Reconfigured");
529 }
530 }
531 }
532}