blob: 9abe49e6ab7f3913ab4328a90a0d7ccf9fddbc5b [file] [log] [blame]
Yi Tseng51301292017-07-28 13:02:59 -07001/*
2 * Copyright 2017-present Open Networking Foundation
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 *
16 */
17
18package org.onosproject.dhcprelay;
19
20import com.google.common.collect.Sets;
21import org.apache.felix.scr.annotations.Component;
22import org.apache.felix.scr.annotations.Property;
23import org.apache.felix.scr.annotations.Reference;
24import org.apache.felix.scr.annotations.ReferenceCardinality;
25import org.apache.felix.scr.annotations.Service;
26import org.onlab.packet.BasePacket;
27import org.onlab.packet.DHCP;
28import org.onlab.packet.Ethernet;
29import org.onlab.packet.IPv4;
30import org.onlab.packet.Ip4Address;
31import org.onlab.packet.IpAddress;
32import org.onlab.packet.MacAddress;
33import org.onlab.packet.UDP;
34import org.onlab.packet.VlanId;
35import org.onlab.packet.dhcp.CircuitId;
36import org.onlab.packet.dhcp.DhcpOption;
37import org.onlab.packet.dhcp.DhcpRelayAgentOption;
38import org.onosproject.dhcprelay.api.DhcpHandler;
39import org.onosproject.dhcprelay.store.DhcpRecord;
40import org.onosproject.dhcprelay.store.DhcpRelayStore;
41import org.onosproject.incubator.net.intf.Interface;
42import org.onosproject.incubator.net.intf.InterfaceService;
43import org.onosproject.incubator.net.routing.Route;
44import org.onosproject.incubator.net.routing.RouteStore;
45import org.onosproject.net.ConnectPoint;
46import org.onosproject.net.Host;
47import org.onosproject.net.HostId;
48import org.onosproject.net.HostLocation;
49import org.onosproject.net.flow.DefaultTrafficTreatment;
50import org.onosproject.net.flow.TrafficTreatment;
51import org.onosproject.net.host.DefaultHostDescription;
52import org.onosproject.net.host.HostDescription;
53import org.onosproject.net.host.HostService;
54import org.onosproject.net.host.HostStore;
55import org.onosproject.net.host.InterfaceIpAddress;
56import org.onosproject.net.packet.DefaultOutboundPacket;
57import org.onosproject.net.packet.OutboundPacket;
58import org.onosproject.net.packet.PacketContext;
59import org.onosproject.net.packet.PacketService;
60import org.slf4j.Logger;
61import org.slf4j.LoggerFactory;
62
63import java.nio.ByteBuffer;
64import java.util.Collections;
65import java.util.List;
66import java.util.Optional;
67import java.util.Set;
68import java.util.stream.Collectors;
69
70import static com.google.common.base.Preconditions.checkNotNull;
71import static com.google.common.base.Preconditions.checkState;
72import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_CircuitID;
73import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_END;
74import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
75import static org.onlab.packet.MacAddress.valueOf;
76import static org.onlab.packet.dhcp.DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID;
77
78@Component
79@Service
80@Property(name = "version", value = "4")
81public class Dhcp4HandlerImpl implements DhcpHandler {
82 private static Logger log = LoggerFactory.getLogger(Dhcp4HandlerImpl.class);
83
84 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
85 protected DhcpRelayStore dhcpRelayStore;
86
87 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
88 protected PacketService packetService;
89
90 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
91 protected HostStore hostStore;
92
93 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
94 protected RouteStore routeStore;
95
96 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
97 protected InterfaceService interfaceService;
98
99 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
100 protected HostService hostService;
101
102 private Ip4Address dhcpServerIp = null;
103 // dhcp server may be connected directly to the SDN network or
104 // via an external gateway. When connected directly, the dhcpConnectPoint, dhcpConnectMac,
105 // and dhcpConnectVlan refer to the server. When connected via the gateway, they refer
106 // to the gateway.
107 private ConnectPoint dhcpServerConnectPoint = null;
108 private MacAddress dhcpConnectMac = null;
109 private VlanId dhcpConnectVlan = null;
110 private Ip4Address dhcpGatewayIp = null;
111
112 @Override
113 public void setDhcpServerIp(IpAddress dhcpServerIp) {
114 checkNotNull(dhcpServerIp, "DHCP server IP can't be null");
115 checkState(dhcpServerIp.isIp4(), "Invalid server IP for DHCPv4 relay handler");
116 this.dhcpServerIp = dhcpServerIp.getIp4Address();
117 }
118
119 @Override
120 public void setDhcpServerConnectPoint(ConnectPoint dhcpServerConnectPoint) {
121 checkNotNull(dhcpServerConnectPoint, "Server connect point can't null");
122 this.dhcpServerConnectPoint = dhcpServerConnectPoint;
123 }
124
125 @Override
126 public void setDhcpConnectMac(MacAddress dhcpConnectMac) {
127 this.dhcpConnectMac = dhcpConnectMac;
128 }
129
130 @Override
131 public void setDhcpConnectVlan(VlanId dhcpConnectVlan) {
132 this.dhcpConnectVlan = dhcpConnectVlan;
133 }
134
135 @Override
136 public void setDhcpGatewayIp(IpAddress dhcpGatewayIp) {
137 if (dhcpGatewayIp != null) {
138 checkState(dhcpGatewayIp.isIp4(), "Invalid gateway IP for DHCPv4 relay handler");
139 this.dhcpGatewayIp = dhcpGatewayIp.getIp4Address();
140 } else {
141 // removes gateway config
142 this.dhcpGatewayIp = null;
143 }
144 }
145
146 @Override
147 public Optional<IpAddress> getDhcpServerIp() {
148 return Optional.ofNullable(dhcpServerIp);
149 }
150
151 @Override
152 public Optional<IpAddress> getDhcpGatewayIp() {
153 return Optional.ofNullable(dhcpGatewayIp);
154 }
155
156 @Override
157 public Optional<MacAddress> getDhcpConnectMac() {
158 return Optional.ofNullable(dhcpConnectMac);
159 }
160
161 @Override
162 public void processDhcpPacket(PacketContext context, BasePacket payload) {
163 checkNotNull(payload, "DHCP payload can't be null");
164 checkState(payload instanceof DHCP, "Payload is not a DHCP");
165 DHCP dhcpPayload = (DHCP) payload;
166 if (!configured()) {
167 log.warn("Missing DHCP relay server config. Abort packet processing");
168 return;
169 }
170
171 ConnectPoint inPort = context.inPacket().receivedFrom();
172 Set<Interface> clientServerInterfaces = interfaceService.getInterfacesByPort(inPort);
173 // ignore the packets if dhcp client interface is not configured on onos.
174 if (clientServerInterfaces.isEmpty()) {
175 log.warn("Virtual interface is not configured on {}", inPort);
176 return;
177 }
178 checkNotNull(dhcpPayload, "Can't find DHCP payload");
179 Ethernet packet = context.inPacket().parsed();
180 DHCP.MsgType incomingPacketType = dhcpPayload.getOptions().stream()
181 .filter(dhcpOption -> dhcpOption.getCode() == OptionCode_MessageType.getValue())
182 .map(DhcpOption::getData)
183 .map(data -> DHCP.MsgType.getType(data[0]))
184 .findFirst()
185 .orElse(null);
186 checkNotNull(incomingPacketType, "Can't get message type from DHCP payload {}", dhcpPayload);
187 switch (incomingPacketType) {
188 case DHCPDISCOVER:
189 // add the gatewayip as virtual interface ip for server to understand
190 // the lease to be assigned and forward the packet to dhcp server.
191 Ethernet ethernetPacketDiscover =
192 processDhcpPacketFromClient(context, packet, clientServerInterfaces);
193
194 if (ethernetPacketDiscover != null) {
195 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
196 handleDhcpDiscoverAndRequest(ethernetPacketDiscover);
197 }
198 break;
199 case DHCPOFFER:
200 //reply to dhcp client.
201 Ethernet ethernetPacketOffer = processDhcpPacketFromServer(packet);
202 if (ethernetPacketOffer != null) {
203 writeResponseDhcpRecord(ethernetPacketOffer, dhcpPayload);
204 handleDhcpOffer(ethernetPacketOffer, dhcpPayload);
205 }
206 break;
207 case DHCPREQUEST:
208 // add the gateway ip as virtual interface ip for server to understand
209 // the lease to be assigned and forward the packet to dhcp server.
210 Ethernet ethernetPacketRequest =
211 processDhcpPacketFromClient(context, packet, clientServerInterfaces);
212 if (ethernetPacketRequest != null) {
213 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
214 handleDhcpDiscoverAndRequest(ethernetPacketRequest);
215 }
216 break;
217 case DHCPACK:
218 // reply to dhcp client.
219 Ethernet ethernetPacketAck = processDhcpPacketFromServer(packet);
220 if (ethernetPacketAck != null) {
221 writeResponseDhcpRecord(ethernetPacketAck, dhcpPayload);
222 handleDhcpAck(ethernetPacketAck, dhcpPayload);
223 }
224 break;
225 case DHCPRELEASE:
226 // TODO: release the ip address from client
227 break;
228 default:
229 break;
230 }
231 }
232
233 /**
234 * Checks if this app has been configured.
235 *
236 * @return true if all information we need have been initialized
237 */
238 public boolean configured() {
239 return dhcpServerConnectPoint != null && dhcpServerIp != null;
240 }
241
242 /**
243 * Returns the first interface ip out of a set of interfaces or null.
244 *
245 * @param intfs interfaces of one connect port
246 * @return the first interface IP; null if not exists an IP address in
247 * these interfaces
248 */
249 private Ip4Address getRelayAgentIPv4Address(Set<Interface> intfs) {
250 return intfs.stream()
251 .map(Interface::ipAddressesList)
252 .flatMap(List::stream)
253 .map(InterfaceIpAddress::ipAddress)
254 .filter(IpAddress::isIp4)
255 .map(IpAddress::getIp4Address)
256 .findFirst()
257 .orElse(null);
258 }
259
260 /**
261 * Build the DHCP discover/request packet with gateway IP(unicast packet).
262 *
263 * @param context the packet context
264 * @param ethernetPacket the ethernet payload to process
265 * @param clientInterfaces interfaces which belongs to input port
266 * @return processed packet
267 */
268 private Ethernet processDhcpPacketFromClient(PacketContext context,
269 Ethernet ethernetPacket,
270 Set<Interface> clientInterfaces) {
271 Ip4Address relayAgentIp = getRelayAgentIPv4Address(clientInterfaces);
272 MacAddress relayAgentMac = clientInterfaces.iterator().next().mac();
273 if (relayAgentIp == null || relayAgentMac == null) {
274 log.warn("Missing DHCP relay agent interface Ipv4 addr config for "
275 + "packet from client on port: {}. Aborting packet processing",
276 clientInterfaces.iterator().next().connectPoint());
277 return null;
278 }
279 if (dhcpConnectMac == null) {
280 log.warn("DHCP {} not yet resolved .. Aborting DHCP "
281 + "packet processing from client on port: {}",
282 (dhcpGatewayIp == null) ? "server IP " + dhcpServerIp
283 : "gateway IP " + dhcpGatewayIp,
284 clientInterfaces.iterator().next().connectPoint());
285 return null;
286 }
287 // get dhcp header.
288 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
289 etherReply.setSourceMACAddress(relayAgentMac);
290 etherReply.setDestinationMACAddress(dhcpConnectMac);
291 etherReply.setVlanID(dhcpConnectVlan.toShort());
292 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
293 ipv4Packet.setSourceAddress(relayAgentIp.toInt());
294 ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
295 UDP udpPacket = (UDP) ipv4Packet.getPayload();
296 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
297
298 // If there is no relay agent option(option 82), add one to DHCP payload
299 boolean containsRelayAgentOption = dhcpPacket.getOptions().stream()
300 .map(DhcpOption::getCode)
301 .anyMatch(code -> code == OptionCode_CircuitID.getValue());
302
303 if (!containsRelayAgentOption) {
304 ConnectPoint inPort = context.inPacket().receivedFrom();
305 VlanId vlanId = VlanId.vlanId(ethernetPacket.getVlanID());
306 // add connected in port and vlan
307 CircuitId cid = new CircuitId(inPort.toString(), vlanId);
308 byte[] circuitId = cid.serialize();
309 DhcpOption circuitIdSubOpt = new DhcpOption();
310 circuitIdSubOpt
311 .setCode(CIRCUIT_ID.getValue())
312 .setLength((byte) circuitId.length)
313 .setData(circuitId);
314
315 DhcpRelayAgentOption newRelayAgentOpt = new DhcpRelayAgentOption();
316 newRelayAgentOpt.setCode(OptionCode_CircuitID.getValue());
317 newRelayAgentOpt.addSubOption(circuitIdSubOpt);
318
319 // Removes END option first
320 List<DhcpOption> options = dhcpPacket.getOptions().stream()
321 .filter(opt -> opt.getCode() != OptionCode_END.getValue())
322 .collect(Collectors.toList());
323
324 // push relay agent option
325 options.add(newRelayAgentOpt);
326
327 // make sure option 255(End) is the last option
328 DhcpOption endOption = new DhcpOption();
329 endOption.setCode(OptionCode_END.getValue());
330 options.add(endOption);
331
332 dhcpPacket.setOptions(options);
333 dhcpPacket.setGatewayIPAddress(relayAgentIp.toInt());
334 }
335
336 udpPacket.setPayload(dhcpPacket);
337 udpPacket.setSourcePort(UDP.DHCP_CLIENT_PORT);
338 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
339 ipv4Packet.setPayload(udpPacket);
340 etherReply.setPayload(ipv4Packet);
341 return etherReply;
342 }
343
344 /**
345 * Writes DHCP record to the store according to the request DHCP packet (Discover, Request).
346 *
347 * @param location the location which DHCP packet comes from
348 * @param ethernet the DHCP packet
349 * @param dhcpPayload the DHCP payload
350 */
351 private void writeRequestDhcpRecord(ConnectPoint location,
352 Ethernet ethernet,
353 DHCP dhcpPayload) {
354 VlanId vlanId = VlanId.vlanId(ethernet.getVlanID());
355 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
356 HostId hostId = HostId.hostId(macAddress, vlanId);
357 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
358 if (record == null) {
359 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
360 } else {
361 record = record.clone();
362 }
363 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
364 record.ip4Status(dhcpPayload.getPacketType());
365 record.setDirectlyConnected(directlyConnected(dhcpPayload));
366 if (!directlyConnected(dhcpPayload)) {
367 // Update gateway mac address if the host is not directly connected
368 record.nextHop(ethernet.getSourceMAC());
369 }
370 record.updateLastSeen();
371 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
372 }
373
374 /**
375 * Writes DHCP record to the store according to the response DHCP packet (Offer, Ack).
376 *
377 * @param ethernet the DHCP packet
378 * @param dhcpPayload the DHCP payload
379 */
380 private void writeResponseDhcpRecord(Ethernet ethernet,
381 DHCP dhcpPayload) {
382 Optional<Interface> outInterface = getOutputInterface(ethernet, dhcpPayload);
383 if (!outInterface.isPresent()) {
384 log.warn("Failed to determine where to send {}", dhcpPayload.getPacketType());
385 return;
386 }
387
388 Interface outIface = outInterface.get();
389 ConnectPoint location = outIface.connectPoint();
390 VlanId vlanId = outIface.vlan();
391 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
392 HostId hostId = HostId.hostId(macAddress, vlanId);
393 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
394 if (record == null) {
395 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
396 } else {
397 record = record.clone();
398 }
399 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
400 if (dhcpPayload.getPacketType() == DHCP.MsgType.DHCPACK) {
401 record.ip4Address(Ip4Address.valueOf(dhcpPayload.getYourIPAddress()));
402 }
403 record.ip4Status(dhcpPayload.getPacketType());
404 record.setDirectlyConnected(directlyConnected(dhcpPayload));
405 record.updateLastSeen();
406 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
407 }
408
409 /**
410 * Build the DHCP offer/ack with proper client port.
411 *
412 * @param ethernetPacket the original packet comes from server
413 * @return new packet which will send to the client
414 */
415 private Ethernet processDhcpPacketFromServer(Ethernet ethernetPacket) {
416 // get dhcp header.
417 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
418 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
419 UDP udpPacket = (UDP) ipv4Packet.getPayload();
420 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
421
422 // determine the vlanId of the client host - note that this vlan id
423 // could be different from the vlan in the packet from the server
424 Interface outInterface = getOutputInterface(ethernetPacket, dhcpPayload).orElse(null);
425
426 if (outInterface == null) {
427 log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
428 return null;
429 }
430
431 etherReply.setDestinationMACAddress(dhcpPayload.getClientHardwareAddress());
432 etherReply.setVlanID(outInterface.vlan().toShort());
433 // we leave the srcMac from the original packet
434
435 // figure out the relay agent IP corresponding to the original request
436 Ip4Address relayAgentIP = getRelayAgentIPv4Address(
437 interfaceService.getInterfacesByPort(outInterface.connectPoint()));
438 if (relayAgentIP == null) {
439 log.warn("Cannot determine relay agent interface Ipv4 addr for host {}/{}. "
440 + "Aborting relay for dhcp packet from server {}",
441 etherReply.getDestinationMAC(), outInterface.vlan(),
442 ethernetPacket);
443 return null;
444 }
445 // SRC_IP: relay agent IP
446 // DST_IP: offered IP
447 ipv4Packet.setSourceAddress(relayAgentIP.toInt());
448 ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
449 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
450 if (directlyConnected(dhcpPayload)) {
451 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
452 } else {
453 // forward to another dhcp relay
454 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
455 }
456
457 udpPacket.setPayload(dhcpPayload);
458 ipv4Packet.setPayload(udpPacket);
459 etherReply.setPayload(ipv4Packet);
460 return etherReply;
461 }
462
463
464 /**
465 * Check if the host is directly connected to the network or not.
466 *
467 * @param dhcpPayload the dhcp payload
468 * @return true if the host is directly connected to the network; false otherwise
469 */
470 private boolean directlyConnected(DHCP dhcpPayload) {
471 DhcpOption relayAgentOption = dhcpPayload.getOption(OptionCode_CircuitID);
472
473 // Doesn't contains relay option
474 if (relayAgentOption == null) {
475 return true;
476 }
477
478 IpAddress gatewayIp = IpAddress.valueOf(dhcpPayload.getGatewayIPAddress());
479 Set<Interface> gatewayInterfaces = interfaceService.getInterfacesByIp(gatewayIp);
480
481 // Contains relay option, and added by ONOS
482 if (!gatewayInterfaces.isEmpty()) {
483 return true;
484 }
485
486 // Relay option added by other relay agent
487 return false;
488 }
489
490
491 /**
492 * Send the DHCP ack to the requester host.
493 * Modify Host or Route store according to the type of DHCP.
494 *
495 * @param ethernetPacketAck the packet
496 * @param dhcpPayload the DHCP data
497 */
498 private void handleDhcpAck(Ethernet ethernetPacketAck, DHCP dhcpPayload) {
499 Optional<Interface> outInterface = getOutputInterface(ethernetPacketAck, dhcpPayload);
500 if (!outInterface.isPresent()) {
501 log.warn("Can't find output interface for dhcp: {}", dhcpPayload);
502 return;
503 }
504
505 Interface outIface = outInterface.get();
506 HostLocation hostLocation = new HostLocation(outIface.connectPoint(), System.currentTimeMillis());
507 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
508 VlanId vlanId = outIface.vlan();
509 HostId hostId = HostId.hostId(macAddress, vlanId);
510 Ip4Address ip = Ip4Address.valueOf(dhcpPayload.getYourIPAddress());
511
512 if (directlyConnected(dhcpPayload)) {
513 // Add to host store if it connect to network directly
514 Set<IpAddress> ips = Sets.newHashSet(ip);
515 HostDescription desc = new DefaultHostDescription(macAddress, vlanId,
516 hostLocation, ips);
517
518 // Replace the ip when dhcp server give the host new ip address
519 hostStore.createOrUpdateHost(DhcpRelayManager.PROVIDER_ID, hostId, desc, false);
520 } else {
521 // Add to route store if it does not connect to network directly
522 // Get gateway host IP according to host mac address
523 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
524
525 if (record == null) {
526 log.warn("Can't find DHCP record of host {}", hostId);
527 return;
528 }
529
530 MacAddress gwMac = record.nextHop().orElse(null);
531 if (gwMac == null) {
532 log.warn("Can't find gateway mac address from record {}", record);
533 return;
534 }
535
536 HostId gwHostId = HostId.hostId(gwMac, record.vlanId());
537 Host gwHost = hostService.getHost(gwHostId);
538
539 if (gwHost == null) {
540 log.warn("Can't find gateway host {}", gwHostId);
541 return;
542 }
543
544 Ip4Address nextHopIp = gwHost.ipAddresses()
545 .stream()
546 .filter(IpAddress::isIp4)
547 .map(IpAddress::getIp4Address)
548 .findFirst()
549 .orElse(null);
550
551 if (nextHopIp == null) {
552 log.warn("Can't find IP address of gateway {}", gwHost);
553 return;
554 }
555
556 Route route = new Route(Route.Source.STATIC, ip.toIpPrefix(), nextHopIp);
557 routeStore.updateRoute(route);
558 }
559 sendResponseToClient(ethernetPacketAck, dhcpPayload);
560 }
561
562 /**
563 * forward the packet to ConnectPoint where the DHCP server is attached.
564 *
565 * @param packet the packet
566 */
567 private void handleDhcpDiscoverAndRequest(Ethernet packet) {
568 // send packet to dhcp server connect point.
569 if (dhcpServerConnectPoint != null) {
570 TrafficTreatment t = DefaultTrafficTreatment.builder()
571 .setOutput(dhcpServerConnectPoint.port()).build();
572 OutboundPacket o = new DefaultOutboundPacket(
573 dhcpServerConnectPoint.deviceId(), t, ByteBuffer.wrap(packet.serialize()));
574 if (log.isTraceEnabled()) {
575 log.trace("Relaying packet to dhcp server {}", packet);
576 }
577 packetService.emit(o);
578 } else {
579 log.warn("Can't find DHCP server connect point, abort.");
580 }
581 }
582
583
584 /**
585 * Gets output interface of a dhcp packet.
586 * If option 82 exists in the dhcp packet and the option was sent by
587 * ONOS (gateway address exists in ONOS interfaces), use the connect
588 * point and vlan id from circuit id; otherwise, find host by destination
589 * address and use vlan id from sender (dhcp server).
590 *
591 * @param ethPacket the ethernet packet
592 * @param dhcpPayload the dhcp packet
593 * @return an interface represent the output port and vlan; empty value
594 * if the host or circuit id not found
595 */
596 private Optional<Interface> getOutputInterface(Ethernet ethPacket, DHCP dhcpPayload) {
597 VlanId originalPacketVlanId = VlanId.vlanId(ethPacket.getVlanID());
598 IpAddress gatewayIpAddress = Ip4Address.valueOf(dhcpPayload.getGatewayIPAddress());
599 Set<Interface> gatewayInterfaces = interfaceService.getInterfacesByIp(gatewayIpAddress);
600 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
601
602 // Sent by ONOS, and contains circuit id
603 if (!gatewayInterfaces.isEmpty() && option != null) {
604 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
605 try {
606 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
607 ConnectPoint connectPoint = ConnectPoint.deviceConnectPoint(circuitId.connectPoint());
608 VlanId vlanId = circuitId.vlanId();
609 return Optional.of(new Interface(null, connectPoint, null, null, vlanId));
610 } catch (IllegalArgumentException ex) {
611 // invalid circuit format, didn't sent by ONOS
612 log.debug("Invalid circuit {}, use information from dhcp payload",
613 circuitIdSubOption.getData());
614 }
615 }
616
617 // Use Vlan Id from DHCP server if DHCP relay circuit id was not
618 // sent by ONOS or circuit Id can't be parsed
619 MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
620 Optional<DhcpRecord> dhcpRecord = dhcpRelayStore.getDhcpRecord(HostId.hostId(dstMac, originalPacketVlanId));
621 return dhcpRecord
622 .map(DhcpRecord::locations)
623 .orElse(Collections.emptySet())
624 .stream()
625 .reduce((hl1, hl2) -> {
626 if (hl1 == null || hl2 == null) {
627 return hl1 == null ? hl2 : hl1;
628 }
629 return hl1.time() > hl2.time() ? hl1 : hl2;
630 })
631 .map(lastLocation -> new Interface(null, lastLocation, null, null, originalPacketVlanId));
632 }
633
634 /**
635 * Handles DHCP offer packet.
636 *
637 * @param ethPacket the packet
638 * @param dhcpPayload the DHCP data
639 */
640 private void handleDhcpOffer(Ethernet ethPacket, DHCP dhcpPayload) {
641 // TODO: removes option 82 if necessary
642 sendResponseToClient(ethPacket, dhcpPayload);
643 }
644
645 /**
646 * Send the response DHCP to the requester host.
647 *
648 * @param ethPacket the packet
649 * @param dhcpPayload the DHCP data
650 */
651 private void sendResponseToClient(Ethernet ethPacket, DHCP dhcpPayload) {
652 Optional<Interface> outInterface = getOutputInterface(ethPacket, dhcpPayload);
653 outInterface.ifPresent(theInterface -> {
654 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
655 .setOutput(theInterface.connectPoint().port())
656 .build();
657 OutboundPacket o = new DefaultOutboundPacket(
658 theInterface.connectPoint().deviceId(),
659 treatment,
660 ByteBuffer.wrap(ethPacket.serialize()));
661 if (log.isTraceEnabled()) {
662 log.trace("Relaying packet to DHCP client {} via {}, vlan {}",
663 ethPacket,
664 theInterface.connectPoint(),
665 theInterface.vlan());
666 }
667 packetService.emit(o);
668 });
669 }
670}