blob: 26d0dfed05e223cd9c220f118f406ef0d3b393d9 [file] [log] [blame]
Yi Tseng7a38f9a2017-06-09 14:36:40 -07001/*
2 * Copyright 2016-present Open Networking Laboratory
3 *
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
361 /**
362 * Gets output interface of a dhcp packet.
363 * If option 82 exists in the dhcp packet and the option was sent by
364 * ONOS (gateway address exists in ONOS interfaces), use the connect
365 * point and vlan id from circuit id; otherwise, find host by destination
366 * address and use vlan id from sender (dhcp server).
367 *
368 * @param ethPacket the ethernet packet
369 * @param dhcpPayload the dhcp packet
370 * @return an interface represent the output port and vlan; empty value
371 * if the host or circuit id not found
372 */
373 private Optional<Interface> getOutputInterface(Ethernet ethPacket, DHCP dhcpPayload) {
374 VlanId originalPacketVlanId = VlanId.vlanId(ethPacket.getVlanID());
375 IpAddress gatewayIpAddress = Ip4Address.valueOf(dhcpPayload.getGatewayIPAddress());
376 Set<Interface> gatewayInterfaces = interfaceService.getInterfacesByIp(gatewayIpAddress);
377 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
378
379 // Sent by ONOS, and contains circuit id
380 if (!gatewayInterfaces.isEmpty() && option != null) {
381 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
382 try {
383 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
384 ConnectPoint connectPoint = ConnectPoint.deviceConnectPoint(circuitId.connectPoint());
385 VlanId vlanId = circuitId.vlanId();
386 return Optional.of(new Interface(null, connectPoint, null, null, vlanId));
387 } catch (IllegalArgumentException ex) {
388 // invalid circuit format, didn't sent by ONOS
389 log.debug("Invalid circuit {}, use information from dhcp payload",
390 circuitIdSubOption.getData());
391 }
392 }
393
394 // Use Vlan Id from DHCP server if DHCP relay circuit id was not
395 // sent by ONOS or circuit Id can't be parsed
396 MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
397 Optional<DhcpRecord> dhcpRecord = dhcpRelayStore.getDhcpRecord(HostId.hostId(dstMac, originalPacketVlanId));
398 return dhcpRecord
399 .map(DhcpRecord::locations)
400 .orElse(Collections.emptySet())
401 .stream()
402 .reduce((hl1, hl2) -> {
403 if (hl1 == null || hl2 == null) {
404 return hl1 == null ? hl2 : hl1;
405 }
406 return hl1.time() > hl2.time() ? hl1 : hl2;
407 })
408 .map(lastLocation -> new Interface(null, lastLocation, null, null, originalPacketVlanId));
409 }
410
411 /**
412 * Send the response DHCP to the requester host.
413 *
414 * @param ethPacket the packet
415 * @param dhcpPayload the DHCP data
416 */
417 private void handleDhcpOffer(Ethernet ethPacket, DHCP dhcpPayload) {
418 Optional<Interface> outInterface = getOutputInterface(ethPacket, dhcpPayload);
419 outInterface.ifPresent(theInterface -> {
420 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
421 .setOutput(theInterface.connectPoint().port())
422 .build();
423 OutboundPacket o = new DefaultOutboundPacket(
424 theInterface.connectPoint().deviceId(),
425 treatment,
426 ByteBuffer.wrap(ethPacket.serialize()));
427 if (log.isTraceEnabled()) {
428 log.trace("Relaying packet to DHCP client {} via {}, vlan {}",
429 ethPacket,
430 theInterface.connectPoint(),
431 theInterface.vlan());
432 }
433 packetService.emit(o);
434 });
435 }
436
437 /**
438 * Send the DHCP ack to the requester host.
439 *
440 * @param ethernetPacketAck the packet
441 * @param dhcpPayload the DHCP data
442 */
443 private void handleDhcpAck(Ethernet ethernetPacketAck, DHCP dhcpPayload) {
444 Optional<Interface> outInterface = getOutputInterface(ethernetPacketAck, dhcpPayload);
445 if (!outInterface.isPresent()) {
446 log.warn("Can't find output interface for dhcp: {}", dhcpPayload);
447 return;
448 }
449
450 Interface outIface = outInterface.get();
451 HostLocation hostLocation = new HostLocation(outIface.connectPoint(), System.currentTimeMillis());
452 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
453 VlanId vlanId = outIface.vlan();
454 HostId hostId = HostId.hostId(macAddress, vlanId);
455 Ip4Address ip = Ip4Address.valueOf(dhcpPayload.getYourIPAddress());
456
457 if (directlyConnected(dhcpPayload)) {
458 // Add to host store if it connect to network directly
459 Set<IpAddress> ips = Sets.newHashSet(ip);
460 HostDescription desc = new DefaultHostDescription(macAddress, vlanId,
461 hostLocation, ips);
462
463 // Replace the ip when dhcp server give the host new ip address
464 hostStore.createOrUpdateHost(PROVIDER_ID, hostId, desc, true);
465 } else {
466 // Add to route store if it does not connect to network directly
467 // Get gateway host IP according to host mac address
468 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
469
470 if (record == null) {
471 log.warn("Can't find DHCP record of host {}", hostId);
472 return;
473 }
474
475 MacAddress gwMac = record.nextHop().orElse(null);
476 if (gwMac == null) {
477 log.warn("Can't find gateway mac address from record {}", record);
478 return;
479 }
480
481 HostId gwHostId = HostId.hostId(gwMac, record.vlanId());
482 Host gwHost = hostService.getHost(gwHostId);
483
484 if (gwHost == null) {
485 log.warn("Can't find gateway host {}", gwHostId);
486 return;
487 }
488
489 Ip4Address nextHopIp = gwHost.ipAddresses()
490 .stream()
491 .filter(IpAddress::isIp4)
492 .map(IpAddress::getIp4Address)
493 .findFirst()
494 .orElse(null);
495
496 if (nextHopIp == null) {
497 log.warn("Can't find IP address of gateway {}", gwHost);
498 return;
499 }
500
501 Route route = new Route(Route.Source.STATIC, ip.toIpPrefix(), nextHopIp);
502 routeStore.updateRoute(route);
503 }
504 handleDhcpOffer(ethernetPacketAck, dhcpPayload);
505 }
506
507 /**
508 * forward the packet to ConnectPoint where the DHCP server is attached.
509 *
510 * @param packet the packet
511 */
512 private void handleDhcpDiscoverAndRequest(Ethernet packet) {
513 // send packet to dhcp server connect point.
514 if (dhcpServerConnectPoint != null) {
515 TrafficTreatment t = DefaultTrafficTreatment.builder()
516 .setOutput(dhcpServerConnectPoint.port()).build();
517 OutboundPacket o = new DefaultOutboundPacket(
518 dhcpServerConnectPoint.deviceId(), t, ByteBuffer.wrap(packet.serialize()));
519 if (log.isTraceEnabled()) {
520 log.trace("Relaying packet to dhcp server {}", packet);
521 }
522 packetService.emit(o);
523 } else {
524 log.warn("Can't find DHCP server connect point, abort.");
525 }
526 }
527
528 /**
529 * Gets DHCP data from a packet.
530 *
531 * @param packet the packet
532 * @return the DHCP data; empty if it is not a DHCP packet
533 */
534 private Optional<DHCP> findDhcp(Ethernet packet) {
535 return Stream.of(packet)
536 .filter(Objects::nonNull)
537 .map(Ethernet::getPayload)
538 .filter(p -> p instanceof IPv4)
539 .map(IPacket::getPayload)
540 .filter(Objects::nonNull)
541 .filter(p -> p instanceof UDP)
542 .map(IPacket::getPayload)
543 .filter(Objects::nonNull)
544 .filter(p -> p instanceof DHCP)
545 .map(p -> (DHCP) p)
546 .findFirst();
547 }
548
549 /**
550 * Gets DHCPv6 data from a packet.
551 *
552 * @param packet the packet
553 * @return the DHCPv6 data; empty if it is not a DHCPv6 packet
554 */
555 private Optional<DHCP6> findDhcp6(Ethernet packet) {
556 return Stream.of(packet)
557 .filter(Objects::nonNull)
558 .map(Ethernet::getPayload)
559 .filter(p -> p instanceof IPv6)
560 .map(IPacket::getPayload)
561 .filter(Objects::nonNull)
562 .filter(p -> p instanceof UDP)
563 .map(IPacket::getPayload)
564 .filter(Objects::nonNull)
565 .filter(p -> p instanceof DHCP6)
566 .map(p -> (DHCP6) p)
567 .findFirst();
568 }
569
570 /**
571 * Check if the host is directly connected to the network or not.
572 *
573 * @param dhcpPayload the dhcp payload
574 * @return true if the host is directly connected to the network; false otherwise
575 */
576 private boolean directlyConnected(DHCP dhcpPayload) {
577 DhcpOption relayAgentOption = dhcpPayload.getOption(OptionCode_CircuitID);
578
579 // Doesn't contains relay option
580 if (relayAgentOption == null) {
581 return true;
582 }
583
584 IpAddress gatewayIp = IpAddress.valueOf(dhcpPayload.getGatewayIPAddress());
585 Set<Interface> gatewayInterfaces = interfaceService.getInterfacesByIp(gatewayIp);
586
587 // Contains relay option, and added by ONOS
588 if (!gatewayInterfaces.isEmpty()) {
589 return true;
590 }
591
592 // Relay option added by other relay agent
593 return false;
594 }
595
596 private class DhcpRelayPacketProcessor implements PacketProcessor {
597
598 @Override
599 public void process(PacketContext context) {
600 if (!configured()) {
601 log.warn("Missing DHCP relay server config. Abort packet processing");
602 return;
603 }
604
605 // process the packet and get the payload
606 Ethernet packet = context.inPacket().parsed();
607 if (packet == null) {
608 return;
609 }
610
611 findDhcp(packet).ifPresent(dhcpPayload -> {
612 processDhcpPacket(context, dhcpPayload);
613 });
614
615 findDhcp6(packet).ifPresent(dhcp6Payload -> {
616 // TODO: handle DHCPv6 packet
617 log.warn("DHCPv6 unsupported.");
618 });
619
620 if (packet.getEtherType() == Ethernet.TYPE_ARP && arpEnabled) {
621 ARP arpPacket = (ARP) packet.getPayload();
622 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
623 Set<Interface> interfaces = interfaceService.
624 getInterfacesByPort(context.inPacket().receivedFrom());
625 //ignore the packets if dhcp server interface is not configured on onos.
626 if (interfaces.isEmpty()) {
627 log.warn("server virtual interface not configured");
628 return;
629 }
630 if ((arpPacket.getOpCode() != ARP.OP_REQUEST)) {
631 // handle request only
632 return;
633 }
634 MacAddress interfaceMac = interfaces.stream()
635 .filter(iface -> iface.vlan().equals(vlanId))
636 .map(Interface::mac)
637 .filter(mac -> !mac.equals(MacAddress.NONE))
638 .findFirst()
639 .orElse(null);
640
641 if (interfaceMac == null) {
642 // can't find interface mac address
643 return;
644 }
645 processArpPacket(context, packet, interfaceMac);
646 }
647 }
648
649 /**
650 * Processes the ARP Payload and initiates a reply to the client.
651 *
652 * @param context the packet context
653 * @param packet the ethernet payload
654 * @param replyMac mac address to be replied
655 */
656 private void processArpPacket(PacketContext context, Ethernet packet, MacAddress replyMac) {
657 ARP arpPacket = (ARP) packet.getPayload();
658 ARP arpReply = (ARP) arpPacket.clone();
659 arpReply.setOpCode(ARP.OP_REPLY);
660
661 arpReply.setTargetProtocolAddress(arpPacket.getSenderProtocolAddress());
662 arpReply.setTargetHardwareAddress(arpPacket.getSenderHardwareAddress());
663 arpReply.setSenderProtocolAddress(arpPacket.getTargetProtocolAddress());
664 arpReply.setSenderHardwareAddress(replyMac.toBytes());
665
666 // Ethernet Frame.
667 Ethernet ethReply = new Ethernet();
668 ethReply.setSourceMACAddress(replyMac.toBytes());
669 ethReply.setDestinationMACAddress(packet.getSourceMAC());
670 ethReply.setEtherType(Ethernet.TYPE_ARP);
671 ethReply.setVlanID(packet.getVlanID());
672 ethReply.setPayload(arpReply);
673
674 ConnectPoint targetPort = context.inPacket().receivedFrom();
675 TrafficTreatment t = DefaultTrafficTreatment.builder()
676 .setOutput(targetPort.port()).build();
677 OutboundPacket o = new DefaultOutboundPacket(
678 targetPort.deviceId(), t, ByteBuffer.wrap(ethReply.serialize()));
679 if (log.isTraceEnabled()) {
680 log.trace("Relaying ARP packet {} to {}", packet, targetPort);
681 }
682 packetService.emit(o);
683 }
684
685 // process the dhcp packet before sending to server
686 private void processDhcpPacket(PacketContext context, DHCP dhcpPayload) {
687 ConnectPoint inPort = context.inPacket().receivedFrom();
688 Set<Interface> clientServerInterfaces = interfaceService.getInterfacesByPort(inPort);
689 // ignore the packets if dhcp client interface is not configured on onos.
690 if (clientServerInterfaces.isEmpty()) {
691 log.warn("Virtual interface is not configured on {}", inPort);
692 return;
693 }
694 checkNotNull(dhcpPayload, "Can't find DHCP payload");
695 Ethernet packet = context.inPacket().parsed();
696 DHCP.MsgType incomingPacketType = dhcpPayload.getOptions().stream()
697 .filter(dhcpOption -> dhcpOption.getCode() == OptionCode_MessageType.getValue())
698 .map(DhcpOption::getData)
699 .map(data -> DHCP.MsgType.getType(data[0]))
700 .findFirst()
701 .orElse(null);
702 checkNotNull(incomingPacketType, "Can't get message type from DHCP payload {}", dhcpPayload);
703 switch (incomingPacketType) {
704 case DHCPDISCOVER:
705 // add the gatewayip as virtual interface ip for server to understand
706 // the lease to be assigned and forward the packet to dhcp server.
707 Ethernet ethernetPacketDiscover =
708 processDhcpPacketFromClient(context, packet, clientServerInterfaces);
709
710 if (ethernetPacketDiscover != null) {
711 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
712 handleDhcpDiscoverAndRequest(ethernetPacketDiscover);
713 }
714 break;
715 case DHCPOFFER:
716 //reply to dhcp client.
717 Ethernet ethernetPacketOffer = processDhcpPacketFromServer(packet);
718 if (ethernetPacketOffer != null) {
719 writeResponseDhcpRecord(ethernetPacketOffer, dhcpPayload);
720 handleDhcpOffer(ethernetPacketOffer, dhcpPayload);
721 }
722 break;
723 case DHCPREQUEST:
724 // add the gateway ip as virtual interface ip for server to understand
725 // the lease to be assigned and forward the packet to dhcp server.
726 Ethernet ethernetPacketRequest =
727 processDhcpPacketFromClient(context, packet, clientServerInterfaces);
728 if (ethernetPacketRequest != null) {
729 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
730 handleDhcpDiscoverAndRequest(ethernetPacketRequest);
731 }
732 break;
733 case DHCPACK:
734 // reply to dhcp client.
735 Ethernet ethernetPacketAck = processDhcpPacketFromServer(packet);
736 if (ethernetPacketAck != null) {
737 writeResponseDhcpRecord(ethernetPacketAck, dhcpPayload);
738 handleDhcpAck(ethernetPacketAck, dhcpPayload);
739 }
740 break;
741 case DHCPRELEASE:
742 // TODO: release the ip address from client
743 break;
744 default:
745 break;
746 }
747 }
748
749 private void writeRequestDhcpRecord(ConnectPoint location,
750 Ethernet ethernet,
751 DHCP dhcpPayload) {
752 VlanId vlanId = VlanId.vlanId(ethernet.getVlanID());
753 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
754 HostId hostId = HostId.hostId(macAddress, vlanId);
755 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
756 if (record == null) {
757 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
758 } else {
759 record = record.clone();
760 }
761 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
762 record.ip4Status(dhcpPayload.getPacketType());
763 record.setDirectlyConnected(directlyConnected(dhcpPayload));
764 if (!directlyConnected(dhcpPayload)) {
765 // Update gateway mac address if the host is not directly connected
766 record.nextHop(ethernet.getSourceMAC());
767 }
768 record.updateLastSeen();
769 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
770 }
771
772 private void writeResponseDhcpRecord(Ethernet ethernet,
773 DHCP dhcpPayload) {
774 Optional<Interface> outInterface = getOutputInterface(ethernet, dhcpPayload);
Charles Chanc760f382017-07-24 15:56:10 -0700775 if (!outInterface.isPresent()) {
776 log.warn("Failed to determine where to send {}", dhcpPayload.getPacketType());
777 return;
778 }
779
780 Interface outIface = outInterface.get();
781 ConnectPoint location = outIface.connectPoint();
782 VlanId vlanId = outIface.vlan();
783 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
784 HostId hostId = HostId.hostId(macAddress, vlanId);
785 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
786 if (record == null) {
787 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
788 } else {
789 record = record.clone();
790 }
791 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
792 if (dhcpPayload.getPacketType() == DHCP.MsgType.DHCPACK) {
793 record.ip4Address(Ip4Address.valueOf(dhcpPayload.getYourIPAddress()));
794 }
795 record.ip4Status(dhcpPayload.getPacketType());
796 record.setDirectlyConnected(directlyConnected(dhcpPayload));
797 record.updateLastSeen();
798 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
Yi Tseng7a38f9a2017-06-09 14:36:40 -0700799 }
800
801 //build the DHCP discover/request packet with gatewayip(unicast packet)
802 private Ethernet processDhcpPacketFromClient(PacketContext context,
803 Ethernet ethernetPacket, Set<Interface> clientInterfaces) {
804 Ip4Address relayAgentIp = getRelayAgentIPv4Address(clientInterfaces);
805 MacAddress relayAgentMac = clientInterfaces.iterator().next().mac();
806 if (relayAgentIp == null || relayAgentMac == null) {
807 log.warn("Missing DHCP relay agent interface Ipv4 addr config for "
808 + "packet from client on port: {}. Aborting packet processing",
809 clientInterfaces.iterator().next().connectPoint());
810 return null;
811 }
812 if (dhcpConnectMac == null) {
813 log.warn("DHCP {} not yet resolved .. Aborting DHCP "
814 + "packet processing from client on port: {}",
815 (dhcpGatewayIp == null) ? "server IP " + dhcpServerIp
816 : "gateway IP " + dhcpGatewayIp,
817 clientInterfaces.iterator().next().connectPoint());
818 return null;
819 }
820 // get dhcp header.
821 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
822 etherReply.setSourceMACAddress(relayAgentMac);
823 etherReply.setDestinationMACAddress(dhcpConnectMac);
824 etherReply.setVlanID(dhcpConnectVlan.toShort());
825 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
826 ipv4Packet.setSourceAddress(relayAgentIp.toInt());
827 ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
828 UDP udpPacket = (UDP) ipv4Packet.getPayload();
829 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
830
831 // If there is no relay agent option(option 82), add one to DHCP payload
832 boolean containsRelayAgentOption = dhcpPacket.getOptions().stream()
833 .map(DhcpOption::getCode)
834 .anyMatch(code -> code == OptionCode_CircuitID.getValue());
835
836 if (!containsRelayAgentOption) {
837 ConnectPoint inPort = context.inPacket().receivedFrom();
838 VlanId vlanId = VlanId.vlanId(ethernetPacket.getVlanID());
839 // add connected in port and vlan
840 CircuitId cid = new CircuitId(inPort.toString(), vlanId);
841 byte[] circuitId = cid.serialize();
842
843 if (circuitId != null) {
844 DhcpOption circuitIdSubOpt = new DhcpOption();
845 circuitIdSubOpt
846 .setCode(CIRCUIT_ID.getValue())
847 .setLength((byte) circuitId.length)
848 .setData(circuitId);
849
850 DhcpRelayAgentOption newRelayAgentOpt = new DhcpRelayAgentOption();
851 newRelayAgentOpt.setCode(OptionCode_CircuitID.getValue());
852 newRelayAgentOpt.addSubOption(circuitIdSubOpt);
853
854 // push new circuit id to last
855 List<DhcpOption> options = dhcpPacket.getOptions();
856 options.add(newRelayAgentOpt);
857
858 // make sure option 255(End) is the last option
859 options.sort((opt1, opt2) -> opt2.getCode() - opt1.getCode());
860 dhcpPacket.setOptions(options);
861
862 dhcpPacket.setGatewayIPAddress(relayAgentIp.toInt());
863 } else {
864 log.warn("Can't generate circuit id for port {}, vlan {}", inPort, vlanId);
865 }
866 } else {
867 // TODO: if it contains a relay agent information, sets gateway ip
868 // according to the information it provided
869 }
870
871 udpPacket.setPayload(dhcpPacket);
872 udpPacket.setSourcePort(UDP.DHCP_CLIENT_PORT);
873 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
874 ipv4Packet.setPayload(udpPacket);
875 etherReply.setPayload(ipv4Packet);
876 return etherReply;
877 }
878
879 // Returns the first v4 interface ip out of a set of interfaces or null.
880 // Checks all interfaces, and ignores v6 interface ips
881 private Ip4Address getRelayAgentIPv4Address(Set<Interface> intfs) {
882 for (Interface intf : intfs) {
883 for (InterfaceIpAddress ip : intf.ipAddressesList()) {
884 Ip4Address relayAgentIp = ip.ipAddress().getIp4Address();
885 if (relayAgentIp != null) {
886 return relayAgentIp;
887 }
888 }
889 }
890 return null;
891 }
892
893 //build the DHCP offer/ack with proper client port.
894 private Ethernet processDhcpPacketFromServer(Ethernet ethernetPacket) {
895 // get dhcp header.
896 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
897 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
898 UDP udpPacket = (UDP) ipv4Packet.getPayload();
899 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
900
901 // determine the vlanId of the client host - note that this vlan id
902 // could be different from the vlan in the packet from the server
903 Interface outInterface = getOutputInterface(ethernetPacket, dhcpPayload).orElse(null);
904
905 if (outInterface == null) {
906 log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
907 return null;
908 }
909
Charles Chanc760f382017-07-24 15:56:10 -0700910 etherReply.setDestinationMACAddress(dhcpPayload.getClientHardwareAddress());
Yi Tseng7a38f9a2017-06-09 14:36:40 -0700911 etherReply.setVlanID(outInterface.vlan().toShort());
912 // we leave the srcMac from the original packet
913
914 // figure out the relay agent IP corresponding to the original request
915 Ip4Address relayAgentIP = getRelayAgentIPv4Address(
916 interfaceService.getInterfacesByPort(outInterface.connectPoint()));
917 if (relayAgentIP == null) {
918 log.warn("Cannot determine relay agent interface Ipv4 addr for host {}/{}. "
919 + "Aborting relay for dhcp packet from server {}",
920 etherReply.getDestinationMAC(), outInterface.vlan(),
921 ethernetPacket);
922 return null;
923 }
924 // SRC_IP: relay agent IP
925 // DST_IP: offered IP
926 ipv4Packet.setSourceAddress(relayAgentIP.toInt());
927 ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
928 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
929 if (directlyConnected(dhcpPayload)) {
930 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
931 } else {
932 // forward to another dhcp relay
933 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
934 }
935
936 udpPacket.setPayload(dhcpPayload);
937 ipv4Packet.setPayload(udpPacket);
938 etherReply.setPayload(ipv4Packet);
939 return etherReply;
940 }
941 }
942
943 /**
944 * Listener for network config events.
945 */
946 private class InternalConfigListener implements NetworkConfigListener {
947 @Override
948 public void event(NetworkConfigEvent event) {
949 if ((event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
950 event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED) &&
951 event.configClass().equals(DhcpRelayConfig.class)) {
952 updateConfig();
953 log.info("Reconfigured");
954 }
955 }
956 }
957
958 /**
959 * Internal listener for host events.
960 */
961 private class InternalHostListener implements HostListener {
962 @Override
963 public void event(HostEvent event) {
964 switch (event.type()) {
965 case HOST_ADDED:
966 case HOST_UPDATED:
967 hostUpdated(event.subject());
968 break;
969 case HOST_REMOVED:
970 hostRemoved(event.subject());
971 break;
972 case HOST_MOVED:
973 // XXX todo -- moving dhcp server
974 break;
975 default:
976 break;
977 }
978 }
979 }
980}