blob: de682de367fdbaa540968ed1b58e90d46279beec [file] [log] [blame]
Yi Tseng7a38f9a2017-06-09 14:36:40 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2016-present Open Networking Foundation
Yi Tseng7a38f9a2017-06-09 14:36:40 -07003 *
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.onosproject.dhcprelay;
17
18import java.nio.ByteBuffer;
19import java.util.Collection;
20import java.util.Collections;
21import java.util.Dictionary;
22import java.util.List;
23import java.util.Objects;
24import java.util.Optional;
25import java.util.Set;
26import java.util.stream.Stream;
27
28import com.google.common.collect.Sets;
29import org.apache.felix.scr.annotations.Activate;
30import org.apache.felix.scr.annotations.Component;
31import org.apache.felix.scr.annotations.Deactivate;
32import org.apache.felix.scr.annotations.Modified;
33import org.apache.felix.scr.annotations.Property;
34import org.apache.felix.scr.annotations.Reference;
35import org.apache.felix.scr.annotations.ReferenceCardinality;
36import org.apache.felix.scr.annotations.Service;
37import org.onlab.packet.ARP;
38import org.onlab.packet.DHCP;
39import org.onlab.packet.DHCP6;
40import org.onlab.packet.IPacket;
41import org.onlab.packet.IPv6;
42import org.onlab.packet.dhcp.DhcpOption;
43import org.onlab.packet.dhcp.CircuitId;
44import org.onlab.packet.Ethernet;
45import org.onlab.packet.IPv4;
46import org.onlab.packet.Ip4Address;
47import org.onlab.packet.IpAddress;
48import org.onlab.packet.MacAddress;
49import org.onlab.packet.TpPort;
50import org.onlab.packet.UDP;
51import org.onlab.packet.VlanId;
52import org.onlab.packet.dhcp.DhcpRelayAgentOption;
53import org.onlab.util.Tools;
54import org.onosproject.cfg.ComponentConfigService;
55import org.onosproject.core.ApplicationId;
56import org.onosproject.core.CoreService;
57import org.onosproject.dhcprelay.store.DhcpRecord;
58import org.onosproject.dhcprelay.store.DhcpRelayStore;
59import org.onosproject.incubator.net.intf.Interface;
60import org.onosproject.incubator.net.intf.InterfaceService;
61import org.onosproject.incubator.net.routing.Route;
62import org.onosproject.incubator.net.routing.RouteStore;
63import org.onosproject.net.ConnectPoint;
64import org.onosproject.net.Host;
65import org.onosproject.net.HostId;
66import org.onosproject.net.HostLocation;
67import org.onosproject.net.config.ConfigFactory;
68import org.onosproject.net.config.NetworkConfigEvent;
69import org.onosproject.net.config.NetworkConfigListener;
70import org.onosproject.net.config.NetworkConfigRegistry;
71import org.onosproject.net.flow.DefaultTrafficSelector;
72import org.onosproject.net.flow.DefaultTrafficTreatment;
73import org.onosproject.net.flow.TrafficSelector;
74import org.onosproject.net.flow.TrafficTreatment;
75import org.onosproject.net.host.DefaultHostDescription;
76import org.onosproject.net.host.HostDescription;
77import org.onosproject.net.host.HostEvent;
78import org.onosproject.net.host.HostListener;
79import org.onosproject.net.host.HostService;
80import org.onosproject.net.host.HostStore;
81import org.onosproject.net.host.InterfaceIpAddress;
82import org.onosproject.net.packet.DefaultOutboundPacket;
83import org.onosproject.net.packet.OutboundPacket;
84import org.onosproject.net.packet.PacketContext;
85import org.onosproject.net.packet.PacketPriority;
86import org.onosproject.net.packet.PacketProcessor;
87import org.onosproject.net.packet.PacketService;
88import org.onosproject.net.provider.ProviderId;
89import org.osgi.service.component.ComponentContext;
90import org.slf4j.Logger;
91import org.slf4j.LoggerFactory;
92
93import com.google.common.collect.ImmutableSet;
94
95import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_CircuitID;
96import static com.google.common.base.Preconditions.checkNotNull;
97import static org.onlab.packet.dhcp.DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID;
98import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
99import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
100import static org.onlab.packet.MacAddress.valueOf;
101/**
102 * DHCP Relay Agent Application Component.
103 */
104@Component(immediate = true)
105@Service
106public class DhcpRelayManager implements DhcpRelayService {
107 public static final String DHCP_RELAY_APP = "org.onosproject.dhcp-relay";
108 protected static final ProviderId PROVIDER_ID = new ProviderId("dhcp", DHCP_RELAY_APP);
109 public static final String HOST_LOCATION_PROVIDER =
110 "org.onosproject.provider.host.impl.HostLocationProvider";
111 private final Logger log = LoggerFactory.getLogger(getClass());
112 private final InternalConfigListener cfgListener = new InternalConfigListener();
113
114 private final Set<ConfigFactory> factories = ImmutableSet.of(
115 new ConfigFactory<ApplicationId, DhcpRelayConfig>(APP_SUBJECT_FACTORY,
116 DhcpRelayConfig.class,
117 "dhcprelay") {
118 @Override
119 public DhcpRelayConfig createConfig() {
120 return new DhcpRelayConfig();
121 }
122 }
123 );
124
125 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
126 protected NetworkConfigRegistry cfgService;
127
128 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
129 protected CoreService coreService;
130
131 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
132 protected PacketService packetService;
133
134 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
135 protected HostService hostService;
136
137 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
138 protected HostStore hostStore;
139
140 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
141 protected RouteStore routeStore;
142
143 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
144 protected InterfaceService interfaceService;
145
146 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
147 protected DhcpRelayStore dhcpRelayStore;
148
149 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
150 protected ComponentConfigService compCfgService;
151
152 @Property(name = "arpEnabled", boolValue = true,
153 label = "Enable Address resolution protocol")
154 protected boolean arpEnabled = true;
155
156 private DhcpRelayPacketProcessor dhcpRelayPacketProcessor = new DhcpRelayPacketProcessor();
157 private InternalHostListener hostListener = new InternalHostListener();
158 private Ip4Address dhcpServerIp = null;
159 // dhcp server may be connected directly to the SDN network or
160 // via an external gateway. When connected directly, the dhcpConnectPoint, dhcpConnectMac,
161 // and dhcpConnectVlan refer to the server. When connected via the gateway, they refer
162 // to the gateway.
163 private ConnectPoint dhcpServerConnectPoint = null;
164 private MacAddress dhcpConnectMac = null;
165 private VlanId dhcpConnectVlan = null;
166 private Ip4Address dhcpGatewayIp = null;
167 private ApplicationId appId;
168
169 @Activate
170 protected void activate(ComponentContext context) {
171 //start the dhcp relay agent
172 appId = coreService.registerApplication(DHCP_RELAY_APP);
173
174 cfgService.addListener(cfgListener);
175 factories.forEach(cfgService::registerConfigFactory);
176 //update the dhcp server configuration.
177 updateConfig();
178 //add the packet services.
179 packetService.addProcessor(dhcpRelayPacketProcessor, PacketProcessor.director(0));
180 hostService.addListener(hostListener);
181 requestDhcpPackets();
182 modified(context);
183
184 // disable dhcp from host location provider
185 compCfgService.preSetProperty(HOST_LOCATION_PROVIDER,
186 "useDhcp", Boolean.FALSE.toString());
187 compCfgService.registerProperties(getClass());
188 log.info("DHCP-RELAY Started");
189 }
190
191 @Deactivate
192 protected void deactivate() {
193 cfgService.removeListener(cfgListener);
194 factories.forEach(cfgService::unregisterConfigFactory);
195 packetService.removeProcessor(dhcpRelayPacketProcessor);
196 hostService.removeListener(hostListener);
197 cancelDhcpPackets();
198 cancelArpPackets();
199 if (dhcpGatewayIp != null) {
200 hostService.stopMonitoringIp(dhcpGatewayIp);
201 } else {
202 hostService.stopMonitoringIp(dhcpServerIp);
203 }
204 compCfgService.unregisterProperties(getClass(), true);
205 log.info("DHCP-RELAY Stopped");
206 }
207
208 @Modified
209 protected void modified(ComponentContext context) {
210 Dictionary<?, ?> properties = context.getProperties();
211 Boolean flag;
212
213 flag = Tools.isPropertyEnabled(properties, "arpEnabled");
214 if (flag != null) {
215 arpEnabled = flag;
216 log.info("Address resolution protocol is {}",
217 arpEnabled ? "enabled" : "disabled");
218 }
219
220 if (arpEnabled) {
221 requestArpPackets();
222 } else {
223 cancelArpPackets();
224 }
225 }
226
227 /**
228 * Checks if this app has been configured.
229 *
230 * @return true if all information we need have been initialized
231 */
232 private boolean configured() {
233 return dhcpServerConnectPoint != null && dhcpServerIp != null;
234 }
235
236 private void updateConfig() {
237 DhcpRelayConfig cfg = cfgService.getConfig(appId, DhcpRelayConfig.class);
238 if (cfg == null) {
239 log.warn("Dhcp Server info not available");
240 return;
241 }
242
243 dhcpServerConnectPoint = cfg.getDhcpServerConnectPoint();
244 Ip4Address oldDhcpServerIp = dhcpServerIp;
245 Ip4Address oldDhcpGatewayIp = dhcpGatewayIp;
246 dhcpServerIp = cfg.getDhcpServerIp();
247 dhcpGatewayIp = cfg.getDhcpGatewayIp();
248 dhcpConnectMac = null; // reset for updated config
249 dhcpConnectVlan = null; // reset for updated config
250 log.info("DHCP server connect point: " + dhcpServerConnectPoint);
251 log.info("DHCP server ipaddress " + dhcpServerIp);
252
253 IpAddress ipToProbe = dhcpGatewayIp != null ? dhcpGatewayIp : dhcpServerIp;
254 String hostToProbe = dhcpGatewayIp != null ? "gateway" : "DHCP server";
255
256 Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
257 if (hosts.isEmpty()) {
258 log.info("Probing to resolve {} IP {}", hostToProbe, ipToProbe);
259 if (oldDhcpGatewayIp != null) {
260 hostService.stopMonitoringIp(oldDhcpGatewayIp);
261 }
262 if (oldDhcpServerIp != null) {
263 hostService.stopMonitoringIp(oldDhcpServerIp);
264 }
265 hostService.startMonitoringIp(ipToProbe);
266 } else {
267 // Probe target is known; There should be only 1 host with this ip
268 hostUpdated(hosts.iterator().next());
269 }
270 }
271
272 private void hostRemoved(Host host) {
273 if (host.ipAddresses().contains(dhcpServerIp)) {
274 log.warn("DHCP server {} removed", dhcpServerIp);
275 dhcpConnectMac = null;
276 dhcpConnectVlan = null;
277 }
278 if (dhcpGatewayIp != null && host.ipAddresses().contains(dhcpGatewayIp)) {
279 log.warn("DHCP gateway {} removed", dhcpGatewayIp);
280 dhcpConnectMac = null;
281 dhcpConnectVlan = null;
282 }
283 }
284
285 private void hostUpdated(Host host) {
286 if (dhcpGatewayIp != null && host.ipAddresses().contains(dhcpGatewayIp)) {
287 dhcpConnectMac = host.mac();
288 dhcpConnectVlan = host.vlan();
289 log.info("DHCP gateway {} resolved to Mac/Vlan:{}/{}", dhcpGatewayIp,
290 dhcpConnectMac, dhcpConnectVlan);
291 } else if (host.ipAddresses().contains(dhcpServerIp)) {
292 dhcpConnectMac = host.mac();
293 dhcpConnectVlan = host.vlan();
294 log.info("DHCP server {} resolved to Mac/Vlan:{}/{}", dhcpServerIp,
295 dhcpConnectMac, dhcpConnectVlan);
296 }
297 }
298
299 /**
300 * Request DHCP packet in via PacketService.
301 */
302 private void requestDhcpPackets() {
303 TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
304 .matchEthType(Ethernet.TYPE_IPV4)
305 .matchIPProtocol(IPv4.PROTOCOL_UDP)
306 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
307 packetService.requestPackets(selectorServer.build(), PacketPriority.CONTROL, appId);
308
309 TrafficSelector.Builder selectorClient = DefaultTrafficSelector.builder()
310 .matchEthType(Ethernet.TYPE_IPV4)
311 .matchIPProtocol(IPv4.PROTOCOL_UDP)
312 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT));
313 packetService.requestPackets(selectorClient.build(), PacketPriority.CONTROL, appId);
314 }
315
316 /**
317 * Cancel requested DHCP packets in via packet service.
318 */
319 private void cancelDhcpPackets() {
320 TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
321 .matchEthType(Ethernet.TYPE_IPV4)
322 .matchIPProtocol(IPv4.PROTOCOL_UDP)
323 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT));
324 packetService.cancelPackets(selectorServer.build(), PacketPriority.CONTROL, appId);
325
326 TrafficSelector.Builder selectorClient = DefaultTrafficSelector.builder()
327 .matchEthType(Ethernet.TYPE_IPV4)
328 .matchIPProtocol(IPv4.PROTOCOL_UDP)
329 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT));
330 packetService.cancelPackets(selectorClient.build(), PacketPriority.CONTROL, appId);
331 }
332
333 /**
334 * Request ARP packet in via PacketService.
335 */
336 private void requestArpPackets() {
337 TrafficSelector.Builder selectorArpServer = DefaultTrafficSelector.builder()
338 .matchEthType(Ethernet.TYPE_ARP);
339 packetService.requestPackets(selectorArpServer.build(), PacketPriority.CONTROL, appId);
340 }
341
342 /**
343 * Cancel requested ARP packets in via packet service.
344 */
345 private void cancelArpPackets() {
346 TrafficSelector.Builder selectorArpServer = DefaultTrafficSelector.builder()
347 .matchEthType(Ethernet.TYPE_ARP);
348 packetService.cancelPackets(selectorArpServer.build(), PacketPriority.CONTROL, appId);
349 }
350
351 @Override
352 public Optional<DhcpRecord> getDhcpRecord(HostId hostId) {
353 return dhcpRelayStore.getDhcpRecord(hostId);
354 }
355
356 @Override
357 public Collection<DhcpRecord> getDhcpRecords() {
358 return dhcpRelayStore.getDhcpRecords();
359 }
360
Yi Tseng13a41a12017-07-26 13:45:01 -0700361 @Override
362 public Optional<MacAddress> getDhcpServerMacAddress() {
363 return Optional.ofNullable(dhcpConnectMac);
364 }
365
Yi Tseng7a38f9a2017-06-09 14:36:40 -0700366 /**
367 * Gets output interface of a dhcp packet.
368 * If option 82 exists in the dhcp packet and the option was sent by
369 * ONOS (gateway address exists in ONOS interfaces), use the connect
370 * point and vlan id from circuit id; otherwise, find host by destination
371 * address and use vlan id from sender (dhcp server).
372 *
373 * @param ethPacket the ethernet packet
374 * @param dhcpPayload the dhcp packet
375 * @return an interface represent the output port and vlan; empty value
376 * if the host or circuit id not found
377 */
378 private Optional<Interface> getOutputInterface(Ethernet ethPacket, DHCP dhcpPayload) {
379 VlanId originalPacketVlanId = VlanId.vlanId(ethPacket.getVlanID());
380 IpAddress gatewayIpAddress = Ip4Address.valueOf(dhcpPayload.getGatewayIPAddress());
381 Set<Interface> gatewayInterfaces = interfaceService.getInterfacesByIp(gatewayIpAddress);
382 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
383
384 // Sent by ONOS, and contains circuit id
385 if (!gatewayInterfaces.isEmpty() && option != null) {
386 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
387 try {
388 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
389 ConnectPoint connectPoint = ConnectPoint.deviceConnectPoint(circuitId.connectPoint());
390 VlanId vlanId = circuitId.vlanId();
391 return Optional.of(new Interface(null, connectPoint, null, null, vlanId));
392 } catch (IllegalArgumentException ex) {
393 // invalid circuit format, didn't sent by ONOS
394 log.debug("Invalid circuit {}, use information from dhcp payload",
395 circuitIdSubOption.getData());
396 }
397 }
398
399 // Use Vlan Id from DHCP server if DHCP relay circuit id was not
400 // sent by ONOS or circuit Id can't be parsed
401 MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
402 Optional<DhcpRecord> dhcpRecord = dhcpRelayStore.getDhcpRecord(HostId.hostId(dstMac, originalPacketVlanId));
403 return dhcpRecord
404 .map(DhcpRecord::locations)
405 .orElse(Collections.emptySet())
406 .stream()
407 .reduce((hl1, hl2) -> {
408 if (hl1 == null || hl2 == null) {
409 return hl1 == null ? hl2 : hl1;
410 }
411 return hl1.time() > hl2.time() ? hl1 : hl2;
412 })
413 .map(lastLocation -> new Interface(null, lastLocation, null, null, originalPacketVlanId));
414 }
415
416 /**
417 * Send the response DHCP to the requester host.
418 *
419 * @param ethPacket the packet
420 * @param dhcpPayload the DHCP data
421 */
422 private void handleDhcpOffer(Ethernet ethPacket, DHCP dhcpPayload) {
423 Optional<Interface> outInterface = getOutputInterface(ethPacket, dhcpPayload);
424 outInterface.ifPresent(theInterface -> {
425 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
426 .setOutput(theInterface.connectPoint().port())
427 .build();
428 OutboundPacket o = new DefaultOutboundPacket(
429 theInterface.connectPoint().deviceId(),
430 treatment,
431 ByteBuffer.wrap(ethPacket.serialize()));
432 if (log.isTraceEnabled()) {
433 log.trace("Relaying packet to DHCP client {} via {}, vlan {}",
434 ethPacket,
435 theInterface.connectPoint(),
436 theInterface.vlan());
437 }
438 packetService.emit(o);
439 });
440 }
441
442 /**
443 * Send the DHCP ack to the requester host.
444 *
445 * @param ethernetPacketAck the packet
446 * @param dhcpPayload the DHCP data
447 */
448 private void handleDhcpAck(Ethernet ethernetPacketAck, DHCP dhcpPayload) {
449 Optional<Interface> outInterface = getOutputInterface(ethernetPacketAck, dhcpPayload);
450 if (!outInterface.isPresent()) {
451 log.warn("Can't find output interface for dhcp: {}", dhcpPayload);
452 return;
453 }
454
455 Interface outIface = outInterface.get();
456 HostLocation hostLocation = new HostLocation(outIface.connectPoint(), System.currentTimeMillis());
457 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
458 VlanId vlanId = outIface.vlan();
459 HostId hostId = HostId.hostId(macAddress, vlanId);
460 Ip4Address ip = Ip4Address.valueOf(dhcpPayload.getYourIPAddress());
461
462 if (directlyConnected(dhcpPayload)) {
463 // Add to host store if it connect to network directly
464 Set<IpAddress> ips = Sets.newHashSet(ip);
465 HostDescription desc = new DefaultHostDescription(macAddress, vlanId,
466 hostLocation, ips);
467
468 // Replace the ip when dhcp server give the host new ip address
469 hostStore.createOrUpdateHost(PROVIDER_ID, hostId, desc, true);
470 } else {
471 // Add to route store if it does not connect to network directly
472 // Get gateway host IP according to host mac address
473 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
474
475 if (record == null) {
476 log.warn("Can't find DHCP record of host {}", hostId);
477 return;
478 }
479
480 MacAddress gwMac = record.nextHop().orElse(null);
481 if (gwMac == null) {
482 log.warn("Can't find gateway mac address from record {}", record);
483 return;
484 }
485
486 HostId gwHostId = HostId.hostId(gwMac, record.vlanId());
487 Host gwHost = hostService.getHost(gwHostId);
488
489 if (gwHost == null) {
490 log.warn("Can't find gateway host {}", gwHostId);
491 return;
492 }
493
494 Ip4Address nextHopIp = gwHost.ipAddresses()
495 .stream()
496 .filter(IpAddress::isIp4)
497 .map(IpAddress::getIp4Address)
498 .findFirst()
499 .orElse(null);
500
501 if (nextHopIp == null) {
502 log.warn("Can't find IP address of gateway {}", gwHost);
503 return;
504 }
505
506 Route route = new Route(Route.Source.STATIC, ip.toIpPrefix(), nextHopIp);
507 routeStore.updateRoute(route);
508 }
509 handleDhcpOffer(ethernetPacketAck, dhcpPayload);
510 }
511
512 /**
513 * forward the packet to ConnectPoint where the DHCP server is attached.
514 *
515 * @param packet the packet
516 */
517 private void handleDhcpDiscoverAndRequest(Ethernet packet) {
518 // send packet to dhcp server connect point.
519 if (dhcpServerConnectPoint != null) {
520 TrafficTreatment t = DefaultTrafficTreatment.builder()
521 .setOutput(dhcpServerConnectPoint.port()).build();
522 OutboundPacket o = new DefaultOutboundPacket(
523 dhcpServerConnectPoint.deviceId(), t, ByteBuffer.wrap(packet.serialize()));
524 if (log.isTraceEnabled()) {
525 log.trace("Relaying packet to dhcp server {}", packet);
526 }
527 packetService.emit(o);
528 } else {
529 log.warn("Can't find DHCP server connect point, abort.");
530 }
531 }
532
533 /**
534 * Gets DHCP data from a packet.
535 *
536 * @param packet the packet
537 * @return the DHCP data; empty if it is not a DHCP packet
538 */
539 private Optional<DHCP> findDhcp(Ethernet packet) {
540 return Stream.of(packet)
541 .filter(Objects::nonNull)
542 .map(Ethernet::getPayload)
543 .filter(p -> p instanceof IPv4)
544 .map(IPacket::getPayload)
545 .filter(Objects::nonNull)
546 .filter(p -> p instanceof UDP)
547 .map(IPacket::getPayload)
548 .filter(Objects::nonNull)
549 .filter(p -> p instanceof DHCP)
550 .map(p -> (DHCP) p)
551 .findFirst();
552 }
553
554 /**
555 * Gets DHCPv6 data from a packet.
556 *
557 * @param packet the packet
558 * @return the DHCPv6 data; empty if it is not a DHCPv6 packet
559 */
560 private Optional<DHCP6> findDhcp6(Ethernet packet) {
561 return Stream.of(packet)
562 .filter(Objects::nonNull)
563 .map(Ethernet::getPayload)
564 .filter(p -> p instanceof IPv6)
565 .map(IPacket::getPayload)
566 .filter(Objects::nonNull)
567 .filter(p -> p instanceof UDP)
568 .map(IPacket::getPayload)
569 .filter(Objects::nonNull)
570 .filter(p -> p instanceof DHCP6)
571 .map(p -> (DHCP6) p)
572 .findFirst();
573 }
574
575 /**
576 * Check if the host is directly connected to the network or not.
577 *
578 * @param dhcpPayload the dhcp payload
579 * @return true if the host is directly connected to the network; false otherwise
580 */
581 private boolean directlyConnected(DHCP dhcpPayload) {
582 DhcpOption relayAgentOption = dhcpPayload.getOption(OptionCode_CircuitID);
583
584 // Doesn't contains relay option
585 if (relayAgentOption == null) {
586 return true;
587 }
588
589 IpAddress gatewayIp = IpAddress.valueOf(dhcpPayload.getGatewayIPAddress());
590 Set<Interface> gatewayInterfaces = interfaceService.getInterfacesByIp(gatewayIp);
591
592 // Contains relay option, and added by ONOS
593 if (!gatewayInterfaces.isEmpty()) {
594 return true;
595 }
596
597 // Relay option added by other relay agent
598 return false;
599 }
600
601 private class DhcpRelayPacketProcessor implements PacketProcessor {
602
603 @Override
604 public void process(PacketContext context) {
605 if (!configured()) {
606 log.warn("Missing DHCP relay server config. Abort packet processing");
607 return;
608 }
609
610 // process the packet and get the payload
611 Ethernet packet = context.inPacket().parsed();
612 if (packet == null) {
613 return;
614 }
615
616 findDhcp(packet).ifPresent(dhcpPayload -> {
617 processDhcpPacket(context, dhcpPayload);
618 });
619
620 findDhcp6(packet).ifPresent(dhcp6Payload -> {
621 // TODO: handle DHCPv6 packet
622 log.warn("DHCPv6 unsupported.");
623 });
624
625 if (packet.getEtherType() == Ethernet.TYPE_ARP && arpEnabled) {
626 ARP arpPacket = (ARP) packet.getPayload();
627 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
628 Set<Interface> interfaces = interfaceService.
629 getInterfacesByPort(context.inPacket().receivedFrom());
630 //ignore the packets if dhcp server interface is not configured on onos.
631 if (interfaces.isEmpty()) {
632 log.warn("server virtual interface not configured");
633 return;
634 }
635 if ((arpPacket.getOpCode() != ARP.OP_REQUEST)) {
636 // handle request only
637 return;
638 }
639 MacAddress interfaceMac = interfaces.stream()
640 .filter(iface -> iface.vlan().equals(vlanId))
641 .map(Interface::mac)
642 .filter(mac -> !mac.equals(MacAddress.NONE))
643 .findFirst()
644 .orElse(null);
645
646 if (interfaceMac == null) {
647 // can't find interface mac address
648 return;
649 }
650 processArpPacket(context, packet, interfaceMac);
651 }
652 }
653
654 /**
655 * Processes the ARP Payload and initiates a reply to the client.
656 *
657 * @param context the packet context
658 * @param packet the ethernet payload
659 * @param replyMac mac address to be replied
660 */
661 private void processArpPacket(PacketContext context, Ethernet packet, MacAddress replyMac) {
662 ARP arpPacket = (ARP) packet.getPayload();
663 ARP arpReply = (ARP) arpPacket.clone();
664 arpReply.setOpCode(ARP.OP_REPLY);
665
666 arpReply.setTargetProtocolAddress(arpPacket.getSenderProtocolAddress());
667 arpReply.setTargetHardwareAddress(arpPacket.getSenderHardwareAddress());
668 arpReply.setSenderProtocolAddress(arpPacket.getTargetProtocolAddress());
669 arpReply.setSenderHardwareAddress(replyMac.toBytes());
670
671 // Ethernet Frame.
672 Ethernet ethReply = new Ethernet();
673 ethReply.setSourceMACAddress(replyMac.toBytes());
674 ethReply.setDestinationMACAddress(packet.getSourceMAC());
675 ethReply.setEtherType(Ethernet.TYPE_ARP);
676 ethReply.setVlanID(packet.getVlanID());
677 ethReply.setPayload(arpReply);
678
679 ConnectPoint targetPort = context.inPacket().receivedFrom();
680 TrafficTreatment t = DefaultTrafficTreatment.builder()
681 .setOutput(targetPort.port()).build();
682 OutboundPacket o = new DefaultOutboundPacket(
683 targetPort.deviceId(), t, ByteBuffer.wrap(ethReply.serialize()));
684 if (log.isTraceEnabled()) {
685 log.trace("Relaying ARP packet {} to {}", packet, targetPort);
686 }
687 packetService.emit(o);
688 }
689
690 // process the dhcp packet before sending to server
691 private void processDhcpPacket(PacketContext context, DHCP dhcpPayload) {
692 ConnectPoint inPort = context.inPacket().receivedFrom();
693 Set<Interface> clientServerInterfaces = interfaceService.getInterfacesByPort(inPort);
694 // ignore the packets if dhcp client interface is not configured on onos.
695 if (clientServerInterfaces.isEmpty()) {
696 log.warn("Virtual interface is not configured on {}", inPort);
697 return;
698 }
699 checkNotNull(dhcpPayload, "Can't find DHCP payload");
700 Ethernet packet = context.inPacket().parsed();
701 DHCP.MsgType incomingPacketType = dhcpPayload.getOptions().stream()
702 .filter(dhcpOption -> dhcpOption.getCode() == OptionCode_MessageType.getValue())
703 .map(DhcpOption::getData)
704 .map(data -> DHCP.MsgType.getType(data[0]))
705 .findFirst()
706 .orElse(null);
707 checkNotNull(incomingPacketType, "Can't get message type from DHCP payload {}", dhcpPayload);
708 switch (incomingPacketType) {
709 case DHCPDISCOVER:
710 // add the gatewayip as virtual interface ip for server to understand
711 // the lease to be assigned and forward the packet to dhcp server.
712 Ethernet ethernetPacketDiscover =
713 processDhcpPacketFromClient(context, packet, clientServerInterfaces);
714
715 if (ethernetPacketDiscover != null) {
716 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
717 handleDhcpDiscoverAndRequest(ethernetPacketDiscover);
718 }
719 break;
720 case DHCPOFFER:
721 //reply to dhcp client.
722 Ethernet ethernetPacketOffer = processDhcpPacketFromServer(packet);
723 if (ethernetPacketOffer != null) {
724 writeResponseDhcpRecord(ethernetPacketOffer, dhcpPayload);
725 handleDhcpOffer(ethernetPacketOffer, dhcpPayload);
726 }
727 break;
728 case DHCPREQUEST:
729 // add the gateway ip as virtual interface ip for server to understand
730 // the lease to be assigned and forward the packet to dhcp server.
731 Ethernet ethernetPacketRequest =
732 processDhcpPacketFromClient(context, packet, clientServerInterfaces);
733 if (ethernetPacketRequest != null) {
734 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
735 handleDhcpDiscoverAndRequest(ethernetPacketRequest);
736 }
737 break;
738 case DHCPACK:
739 // reply to dhcp client.
740 Ethernet ethernetPacketAck = processDhcpPacketFromServer(packet);
741 if (ethernetPacketAck != null) {
742 writeResponseDhcpRecord(ethernetPacketAck, dhcpPayload);
743 handleDhcpAck(ethernetPacketAck, dhcpPayload);
744 }
745 break;
746 case DHCPRELEASE:
747 // TODO: release the ip address from client
748 break;
749 default:
750 break;
751 }
752 }
753
754 private void writeRequestDhcpRecord(ConnectPoint location,
755 Ethernet ethernet,
756 DHCP dhcpPayload) {
757 VlanId vlanId = VlanId.vlanId(ethernet.getVlanID());
758 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
759 HostId hostId = HostId.hostId(macAddress, vlanId);
760 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
761 if (record == null) {
762 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
763 } else {
764 record = record.clone();
765 }
766 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
767 record.ip4Status(dhcpPayload.getPacketType());
768 record.setDirectlyConnected(directlyConnected(dhcpPayload));
769 if (!directlyConnected(dhcpPayload)) {
770 // Update gateway mac address if the host is not directly connected
771 record.nextHop(ethernet.getSourceMAC());
772 }
773 record.updateLastSeen();
774 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
775 }
776
777 private void writeResponseDhcpRecord(Ethernet ethernet,
778 DHCP dhcpPayload) {
779 Optional<Interface> outInterface = getOutputInterface(ethernet, dhcpPayload);
Charles Chanc760f382017-07-24 15:56:10 -0700780 if (!outInterface.isPresent()) {
781 log.warn("Failed to determine where to send {}", dhcpPayload.getPacketType());
782 return;
783 }
784
785 Interface outIface = outInterface.get();
786 ConnectPoint location = outIface.connectPoint();
787 VlanId vlanId = outIface.vlan();
788 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
789 HostId hostId = HostId.hostId(macAddress, vlanId);
790 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
791 if (record == null) {
792 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
793 } else {
794 record = record.clone();
795 }
796 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
797 if (dhcpPayload.getPacketType() == DHCP.MsgType.DHCPACK) {
798 record.ip4Address(Ip4Address.valueOf(dhcpPayload.getYourIPAddress()));
799 }
800 record.ip4Status(dhcpPayload.getPacketType());
801 record.setDirectlyConnected(directlyConnected(dhcpPayload));
802 record.updateLastSeen();
803 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
Yi Tseng7a38f9a2017-06-09 14:36:40 -0700804 }
805
806 //build the DHCP discover/request packet with gatewayip(unicast packet)
807 private Ethernet processDhcpPacketFromClient(PacketContext context,
808 Ethernet ethernetPacket, Set<Interface> clientInterfaces) {
809 Ip4Address relayAgentIp = getRelayAgentIPv4Address(clientInterfaces);
810 MacAddress relayAgentMac = clientInterfaces.iterator().next().mac();
811 if (relayAgentIp == null || relayAgentMac == null) {
812 log.warn("Missing DHCP relay agent interface Ipv4 addr config for "
813 + "packet from client on port: {}. Aborting packet processing",
814 clientInterfaces.iterator().next().connectPoint());
815 return null;
816 }
817 if (dhcpConnectMac == null) {
818 log.warn("DHCP {} not yet resolved .. Aborting DHCP "
819 + "packet processing from client on port: {}",
820 (dhcpGatewayIp == null) ? "server IP " + dhcpServerIp
821 : "gateway IP " + dhcpGatewayIp,
822 clientInterfaces.iterator().next().connectPoint());
823 return null;
824 }
825 // get dhcp header.
826 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
827 etherReply.setSourceMACAddress(relayAgentMac);
828 etherReply.setDestinationMACAddress(dhcpConnectMac);
829 etherReply.setVlanID(dhcpConnectVlan.toShort());
830 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
831 ipv4Packet.setSourceAddress(relayAgentIp.toInt());
832 ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
833 UDP udpPacket = (UDP) ipv4Packet.getPayload();
834 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
835
836 // If there is no relay agent option(option 82), add one to DHCP payload
837 boolean containsRelayAgentOption = dhcpPacket.getOptions().stream()
838 .map(DhcpOption::getCode)
839 .anyMatch(code -> code == OptionCode_CircuitID.getValue());
840
841 if (!containsRelayAgentOption) {
842 ConnectPoint inPort = context.inPacket().receivedFrom();
843 VlanId vlanId = VlanId.vlanId(ethernetPacket.getVlanID());
844 // add connected in port and vlan
845 CircuitId cid = new CircuitId(inPort.toString(), vlanId);
846 byte[] circuitId = cid.serialize();
847
848 if (circuitId != null) {
849 DhcpOption circuitIdSubOpt = new DhcpOption();
850 circuitIdSubOpt
851 .setCode(CIRCUIT_ID.getValue())
852 .setLength((byte) circuitId.length)
853 .setData(circuitId);
854
855 DhcpRelayAgentOption newRelayAgentOpt = new DhcpRelayAgentOption();
856 newRelayAgentOpt.setCode(OptionCode_CircuitID.getValue());
857 newRelayAgentOpt.addSubOption(circuitIdSubOpt);
858
859 // push new circuit id to last
860 List<DhcpOption> options = dhcpPacket.getOptions();
861 options.add(newRelayAgentOpt);
862
863 // make sure option 255(End) is the last option
864 options.sort((opt1, opt2) -> opt2.getCode() - opt1.getCode());
865 dhcpPacket.setOptions(options);
866
867 dhcpPacket.setGatewayIPAddress(relayAgentIp.toInt());
868 } else {
869 log.warn("Can't generate circuit id for port {}, vlan {}", inPort, vlanId);
870 }
871 } else {
872 // TODO: if it contains a relay agent information, sets gateway ip
873 // according to the information it provided
874 }
875
876 udpPacket.setPayload(dhcpPacket);
877 udpPacket.setSourcePort(UDP.DHCP_CLIENT_PORT);
878 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
879 ipv4Packet.setPayload(udpPacket);
880 etherReply.setPayload(ipv4Packet);
881 return etherReply;
882 }
883
884 // Returns the first v4 interface ip out of a set of interfaces or null.
885 // Checks all interfaces, and ignores v6 interface ips
886 private Ip4Address getRelayAgentIPv4Address(Set<Interface> intfs) {
887 for (Interface intf : intfs) {
888 for (InterfaceIpAddress ip : intf.ipAddressesList()) {
889 Ip4Address relayAgentIp = ip.ipAddress().getIp4Address();
890 if (relayAgentIp != null) {
891 return relayAgentIp;
892 }
893 }
894 }
895 return null;
896 }
897
898 //build the DHCP offer/ack with proper client port.
899 private Ethernet processDhcpPacketFromServer(Ethernet ethernetPacket) {
900 // get dhcp header.
901 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
902 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
903 UDP udpPacket = (UDP) ipv4Packet.getPayload();
904 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
905
906 // determine the vlanId of the client host - note that this vlan id
907 // could be different from the vlan in the packet from the server
908 Interface outInterface = getOutputInterface(ethernetPacket, dhcpPayload).orElse(null);
909
910 if (outInterface == null) {
911 log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
912 return null;
913 }
914
Charles Chanc760f382017-07-24 15:56:10 -0700915 etherReply.setDestinationMACAddress(dhcpPayload.getClientHardwareAddress());
Yi Tseng7a38f9a2017-06-09 14:36:40 -0700916 etherReply.setVlanID(outInterface.vlan().toShort());
917 // we leave the srcMac from the original packet
918
919 // figure out the relay agent IP corresponding to the original request
920 Ip4Address relayAgentIP = getRelayAgentIPv4Address(
921 interfaceService.getInterfacesByPort(outInterface.connectPoint()));
922 if (relayAgentIP == null) {
923 log.warn("Cannot determine relay agent interface Ipv4 addr for host {}/{}. "
924 + "Aborting relay for dhcp packet from server {}",
925 etherReply.getDestinationMAC(), outInterface.vlan(),
926 ethernetPacket);
927 return null;
928 }
929 // SRC_IP: relay agent IP
930 // DST_IP: offered IP
931 ipv4Packet.setSourceAddress(relayAgentIP.toInt());
932 ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
933 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
934 if (directlyConnected(dhcpPayload)) {
935 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
936 } else {
937 // forward to another dhcp relay
938 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
939 }
940
941 udpPacket.setPayload(dhcpPayload);
942 ipv4Packet.setPayload(udpPacket);
943 etherReply.setPayload(ipv4Packet);
944 return etherReply;
945 }
946 }
947
948 /**
949 * Listener for network config events.
950 */
951 private class InternalConfigListener implements NetworkConfigListener {
952 @Override
953 public void event(NetworkConfigEvent event) {
954 if ((event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
955 event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED) &&
956 event.configClass().equals(DhcpRelayConfig.class)) {
957 updateConfig();
958 log.info("Reconfigured");
959 }
960 }
961 }
962
963 /**
964 * Internal listener for host events.
965 */
966 private class InternalHostListener implements HostListener {
967 @Override
968 public void event(HostEvent event) {
969 switch (event.type()) {
970 case HOST_ADDED:
971 case HOST_UPDATED:
972 hostUpdated(event.subject());
973 break;
974 case HOST_REMOVED:
975 hostRemoved(event.subject());
976 break;
977 case HOST_MOVED:
978 // XXX todo -- moving dhcp server
979 break;
980 default:
981 break;
982 }
983 }
984 }
985}