blob: 0305cd8eb98aff62f9c72de4d0ab7e7cb99ac40a [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;
Ray Milkeyfacf2862017-08-03 11:58:29 -070041import org.onosproject.net.intf.Interface;
42import org.onosproject.net.intf.InterfaceService;
Ray Milkey69ec8712017-08-08 13:00:43 -070043import org.onosproject.routeservice.Route;
44import org.onosproject.routeservice.RouteStore;
Yi Tseng51301292017-07-28 13:02:59 -070045import 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;
Yi Tsengdcef2c22017-08-05 20:34:06 -070064import java.util.Collection;
Yi Tseng51301292017-07-28 13:02:59 -070065import java.util.Collections;
66import java.util.List;
67import java.util.Optional;
68import java.util.Set;
69import java.util.stream.Collectors;
70
71import static com.google.common.base.Preconditions.checkNotNull;
72import static com.google.common.base.Preconditions.checkState;
73import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_CircuitID;
74import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_END;
75import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
76import static org.onlab.packet.MacAddress.valueOf;
77import static org.onlab.packet.dhcp.DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID;
78
79@Component
80@Service
81@Property(name = "version", value = "4")
82public class Dhcp4HandlerImpl implements DhcpHandler {
83 private static Logger log = LoggerFactory.getLogger(Dhcp4HandlerImpl.class);
84
85 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
86 protected DhcpRelayStore dhcpRelayStore;
87
88 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
89 protected PacketService packetService;
90
91 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
92 protected HostStore hostStore;
93
94 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
95 protected RouteStore routeStore;
96
97 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
98 protected InterfaceService interfaceService;
99
100 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
101 protected HostService hostService;
102
103 private Ip4Address dhcpServerIp = null;
104 // dhcp server may be connected directly to the SDN network or
105 // via an external gateway. When connected directly, the dhcpConnectPoint, dhcpConnectMac,
106 // and dhcpConnectVlan refer to the server. When connected via the gateway, they refer
107 // to the gateway.
108 private ConnectPoint dhcpServerConnectPoint = null;
109 private MacAddress dhcpConnectMac = null;
110 private VlanId dhcpConnectVlan = null;
111 private Ip4Address dhcpGatewayIp = null;
112
113 @Override
114 public void setDhcpServerIp(IpAddress dhcpServerIp) {
115 checkNotNull(dhcpServerIp, "DHCP server IP can't be null");
116 checkState(dhcpServerIp.isIp4(), "Invalid server IP for DHCPv4 relay handler");
117 this.dhcpServerIp = dhcpServerIp.getIp4Address();
118 }
119
120 @Override
121 public void setDhcpServerConnectPoint(ConnectPoint dhcpServerConnectPoint) {
122 checkNotNull(dhcpServerConnectPoint, "Server connect point can't null");
123 this.dhcpServerConnectPoint = dhcpServerConnectPoint;
124 }
125
126 @Override
127 public void setDhcpConnectMac(MacAddress dhcpConnectMac) {
128 this.dhcpConnectMac = dhcpConnectMac;
129 }
130
131 @Override
132 public void setDhcpConnectVlan(VlanId dhcpConnectVlan) {
133 this.dhcpConnectVlan = dhcpConnectVlan;
134 }
135
136 @Override
137 public void setDhcpGatewayIp(IpAddress dhcpGatewayIp) {
138 if (dhcpGatewayIp != null) {
139 checkState(dhcpGatewayIp.isIp4(), "Invalid gateway IP for DHCPv4 relay handler");
140 this.dhcpGatewayIp = dhcpGatewayIp.getIp4Address();
141 } else {
142 // removes gateway config
143 this.dhcpGatewayIp = null;
144 }
145 }
146
147 @Override
148 public Optional<IpAddress> getDhcpServerIp() {
149 return Optional.ofNullable(dhcpServerIp);
150 }
151
152 @Override
153 public Optional<IpAddress> getDhcpGatewayIp() {
154 return Optional.ofNullable(dhcpGatewayIp);
155 }
156
157 @Override
158 public Optional<MacAddress> getDhcpConnectMac() {
159 return Optional.ofNullable(dhcpConnectMac);
160 }
161
162 @Override
163 public void processDhcpPacket(PacketContext context, BasePacket payload) {
164 checkNotNull(payload, "DHCP payload can't be null");
165 checkState(payload instanceof DHCP, "Payload is not a DHCP");
166 DHCP dhcpPayload = (DHCP) payload;
167 if (!configured()) {
168 log.warn("Missing DHCP relay server config. Abort packet processing");
169 return;
170 }
171
172 ConnectPoint inPort = context.inPacket().receivedFrom();
Yi Tseng51301292017-07-28 13:02:59 -0700173 checkNotNull(dhcpPayload, "Can't find DHCP payload");
174 Ethernet packet = context.inPacket().parsed();
175 DHCP.MsgType incomingPacketType = dhcpPayload.getOptions().stream()
176 .filter(dhcpOption -> dhcpOption.getCode() == OptionCode_MessageType.getValue())
177 .map(DhcpOption::getData)
178 .map(data -> DHCP.MsgType.getType(data[0]))
179 .findFirst()
180 .orElse(null);
181 checkNotNull(incomingPacketType, "Can't get message type from DHCP payload {}", dhcpPayload);
182 switch (incomingPacketType) {
183 case DHCPDISCOVER:
Yi Tsengdcef2c22017-08-05 20:34:06 -0700184 // Try update host if it is directly connected.
185 if (directlyConnected(dhcpPayload)) {
186 updateHost(context, dhcpPayload);
187 }
188
189 // Add the gateway IP as virtual interface IP for server to understand
Yi Tseng51301292017-07-28 13:02:59 -0700190 // the lease to be assigned and forward the packet to dhcp server.
191 Ethernet ethernetPacketDiscover =
Yi Tsengdcef2c22017-08-05 20:34:06 -0700192 processDhcpPacketFromClient(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700193 if (ethernetPacketDiscover != null) {
194 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
195 handleDhcpDiscoverAndRequest(ethernetPacketDiscover);
196 }
197 break;
198 case DHCPOFFER:
199 //reply to dhcp client.
200 Ethernet ethernetPacketOffer = processDhcpPacketFromServer(packet);
201 if (ethernetPacketOffer != null) {
202 writeResponseDhcpRecord(ethernetPacketOffer, dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700203 sendResponseToClient(ethernetPacketOffer, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700204 }
205 break;
206 case DHCPREQUEST:
207 // add the gateway ip as virtual interface ip for server to understand
208 // the lease to be assigned and forward the packet to dhcp server.
209 Ethernet ethernetPacketRequest =
Yi Tsengdcef2c22017-08-05 20:34:06 -0700210 processDhcpPacketFromClient(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700211 if (ethernetPacketRequest != null) {
212 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
213 handleDhcpDiscoverAndRequest(ethernetPacketRequest);
214 }
215 break;
216 case DHCPACK:
217 // reply to dhcp client.
218 Ethernet ethernetPacketAck = processDhcpPacketFromServer(packet);
219 if (ethernetPacketAck != null) {
220 writeResponseDhcpRecord(ethernetPacketAck, dhcpPayload);
221 handleDhcpAck(ethernetPacketAck, dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700222 sendResponseToClient(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700223 }
224 break;
225 case DHCPRELEASE:
226 // TODO: release the ip address from client
227 break;
228 default:
229 break;
230 }
231 }
232
233 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -0700234 * Updates host to host store according to DHCP payload.
235 *
236 * @param context the packet context
237 * @param dhcpPayload the DHCP payload
238 */
239 private void updateHost(PacketContext context, DHCP dhcpPayload) {
240 ConnectPoint location = context.inPacket().receivedFrom();
241 HostLocation hostLocation = new HostLocation(location, System.currentTimeMillis());
242 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
243 VlanId vlanId = VlanId.vlanId(context.inPacket().parsed().getVlanID());
244 HostId hostId = HostId.hostId(macAddress, vlanId);
245 HostDescription desc = new DefaultHostDescription(macAddress, vlanId, hostLocation);
246 hostStore.createOrUpdateHost(DhcpRelayManager.PROVIDER_ID, hostId, desc, false);
247 }
248
249 /**
Yi Tseng51301292017-07-28 13:02:59 -0700250 * Checks if this app has been configured.
251 *
252 * @return true if all information we need have been initialized
253 */
254 public boolean configured() {
255 return dhcpServerConnectPoint != null && dhcpServerIp != null;
256 }
257
258 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -0700259 * Returns the first interface ip from interface.
Yi Tseng51301292017-07-28 13:02:59 -0700260 *
Yi Tsengdcef2c22017-08-05 20:34:06 -0700261 * @param iface interface of one connect point
Yi Tseng51301292017-07-28 13:02:59 -0700262 * @return the first interface IP; null if not exists an IP address in
263 * these interfaces
264 */
Yi Tsengdcef2c22017-08-05 20:34:06 -0700265 private Ip4Address getRelayAgentIPv4Address(Interface iface) {
266 checkNotNull(iface, "Interface can't be null");
267 return iface.ipAddressesList().stream()
Yi Tseng51301292017-07-28 13:02:59 -0700268 .map(InterfaceIpAddress::ipAddress)
269 .filter(IpAddress::isIp4)
270 .map(IpAddress::getIp4Address)
271 .findFirst()
272 .orElse(null);
273 }
274
275 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -0700276 * Gets Interface facing to the server.
277 *
278 * @return the Interface facing to the server; null if not found
279 */
280 public Interface getServerInterface() {
281 if (dhcpServerConnectPoint == null || dhcpConnectVlan == null) {
282 return null;
283 }
284 return interfaceService.getInterfacesByPort(dhcpServerConnectPoint)
285 .stream()
286 .filter(iface -> iface.vlan().equals(dhcpConnectVlan) ||
287 iface.vlanUntagged().equals(dhcpConnectVlan) ||
288 iface.vlanTagged().contains(dhcpConnectVlan) ||
289 iface.vlanNative().equals(dhcpConnectVlan))
290 .findFirst()
291 .orElse(null);
292 }
293
294 /**
Yi Tseng51301292017-07-28 13:02:59 -0700295 * Build the DHCP discover/request packet with gateway IP(unicast packet).
296 *
297 * @param context the packet context
298 * @param ethernetPacket the ethernet payload to process
Yi Tseng51301292017-07-28 13:02:59 -0700299 * @return processed packet
300 */
301 private Ethernet processDhcpPacketFromClient(PacketContext context,
Yi Tsengdcef2c22017-08-05 20:34:06 -0700302 Ethernet ethernetPacket) {
303 Ip4Address clientInterfaceIp =
304 interfaceService.getInterfacesByPort(context.inPacket().receivedFrom())
305 .stream()
306 .map(Interface::ipAddressesList)
307 .flatMap(Collection::stream)
308 .map(InterfaceIpAddress::ipAddress)
309 .filter(IpAddress::isIp4)
310 .map(IpAddress::getIp4Address)
311 .findFirst()
312 .orElse(null);
313 if (clientInterfaceIp == null) {
314 log.warn("Can't find interface IP for client interface for port {}",
315 context.inPacket().receivedFrom());
316 return null;
317 }
318 Interface serverInterface = getServerInterface();
319 if (serverInterface == null) {
320 log.warn("Can't get server interface, ignore");
321 return null;
322 }
323 Ip4Address relayAgentIp = getRelayAgentIPv4Address(serverInterface);
324 MacAddress relayAgentMac = serverInterface.mac();
Yi Tseng51301292017-07-28 13:02:59 -0700325 if (relayAgentIp == null || relayAgentMac == null) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700326 log.warn("No IP address for server Interface {}", serverInterface);
Yi Tseng51301292017-07-28 13:02:59 -0700327 return null;
328 }
329 if (dhcpConnectMac == null) {
330 log.warn("DHCP {} not yet resolved .. Aborting DHCP "
331 + "packet processing from client on port: {}",
332 (dhcpGatewayIp == null) ? "server IP " + dhcpServerIp
333 : "gateway IP " + dhcpGatewayIp,
Yi Tsengdcef2c22017-08-05 20:34:06 -0700334 context.inPacket().receivedFrom());
Yi Tseng51301292017-07-28 13:02:59 -0700335 return null;
336 }
337 // get dhcp header.
338 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
339 etherReply.setSourceMACAddress(relayAgentMac);
340 etherReply.setDestinationMACAddress(dhcpConnectMac);
341 etherReply.setVlanID(dhcpConnectVlan.toShort());
342 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
343 ipv4Packet.setSourceAddress(relayAgentIp.toInt());
344 ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
345 UDP udpPacket = (UDP) ipv4Packet.getPayload();
346 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
347
Yi Tsengdcef2c22017-08-05 20:34:06 -0700348 if (directlyConnected(dhcpPacket)) {
Yi Tseng51301292017-07-28 13:02:59 -0700349 ConnectPoint inPort = context.inPacket().receivedFrom();
350 VlanId vlanId = VlanId.vlanId(ethernetPacket.getVlanID());
351 // add connected in port and vlan
352 CircuitId cid = new CircuitId(inPort.toString(), vlanId);
353 byte[] circuitId = cid.serialize();
354 DhcpOption circuitIdSubOpt = new DhcpOption();
355 circuitIdSubOpt
356 .setCode(CIRCUIT_ID.getValue())
357 .setLength((byte) circuitId.length)
358 .setData(circuitId);
359
360 DhcpRelayAgentOption newRelayAgentOpt = new DhcpRelayAgentOption();
361 newRelayAgentOpt.setCode(OptionCode_CircuitID.getValue());
362 newRelayAgentOpt.addSubOption(circuitIdSubOpt);
363
364 // Removes END option first
365 List<DhcpOption> options = dhcpPacket.getOptions().stream()
366 .filter(opt -> opt.getCode() != OptionCode_END.getValue())
367 .collect(Collectors.toList());
368
369 // push relay agent option
370 options.add(newRelayAgentOpt);
371
372 // make sure option 255(End) is the last option
373 DhcpOption endOption = new DhcpOption();
374 endOption.setCode(OptionCode_END.getValue());
375 options.add(endOption);
376
377 dhcpPacket.setOptions(options);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700378
379 // Sets giaddr to IP address from the Interface which facing to
380 // DHCP client
381 dhcpPacket.setGatewayIPAddress(clientInterfaceIp.toInt());
Yi Tseng51301292017-07-28 13:02:59 -0700382 }
383
384 udpPacket.setPayload(dhcpPacket);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700385 // As a DHCP relay, the source port should be server port(67) instead
386 // of client port(68)
387 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
Yi Tseng51301292017-07-28 13:02:59 -0700388 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
389 ipv4Packet.setPayload(udpPacket);
390 etherReply.setPayload(ipv4Packet);
391 return etherReply;
392 }
393
394 /**
395 * Writes DHCP record to the store according to the request DHCP packet (Discover, Request).
396 *
397 * @param location the location which DHCP packet comes from
398 * @param ethernet the DHCP packet
399 * @param dhcpPayload the DHCP payload
400 */
401 private void writeRequestDhcpRecord(ConnectPoint location,
402 Ethernet ethernet,
403 DHCP dhcpPayload) {
404 VlanId vlanId = VlanId.vlanId(ethernet.getVlanID());
405 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
406 HostId hostId = HostId.hostId(macAddress, vlanId);
407 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
408 if (record == null) {
409 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
410 } else {
411 record = record.clone();
412 }
413 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
414 record.ip4Status(dhcpPayload.getPacketType());
415 record.setDirectlyConnected(directlyConnected(dhcpPayload));
416 if (!directlyConnected(dhcpPayload)) {
417 // Update gateway mac address if the host is not directly connected
418 record.nextHop(ethernet.getSourceMAC());
419 }
420 record.updateLastSeen();
421 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
422 }
423
424 /**
425 * Writes DHCP record to the store according to the response DHCP packet (Offer, Ack).
426 *
427 * @param ethernet the DHCP packet
428 * @param dhcpPayload the DHCP payload
429 */
430 private void writeResponseDhcpRecord(Ethernet ethernet,
431 DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700432 Optional<Interface> outInterface = getClientInterface(ethernet, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700433 if (!outInterface.isPresent()) {
434 log.warn("Failed to determine where to send {}", dhcpPayload.getPacketType());
435 return;
436 }
437
438 Interface outIface = outInterface.get();
439 ConnectPoint location = outIface.connectPoint();
Yi Tsengdcef2c22017-08-05 20:34:06 -0700440 VlanId vlanId = getVlanIdFromOption(dhcpPayload);
441 if (vlanId == null) {
442 vlanId = outIface.vlan();
443 }
Yi Tseng51301292017-07-28 13:02:59 -0700444 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
445 HostId hostId = HostId.hostId(macAddress, vlanId);
446 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
447 if (record == null) {
448 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
449 } else {
450 record = record.clone();
451 }
452 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
453 if (dhcpPayload.getPacketType() == DHCP.MsgType.DHCPACK) {
454 record.ip4Address(Ip4Address.valueOf(dhcpPayload.getYourIPAddress()));
455 }
456 record.ip4Status(dhcpPayload.getPacketType());
457 record.setDirectlyConnected(directlyConnected(dhcpPayload));
458 record.updateLastSeen();
459 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
460 }
461
462 /**
463 * Build the DHCP offer/ack with proper client port.
464 *
465 * @param ethernetPacket the original packet comes from server
466 * @return new packet which will send to the client
467 */
468 private Ethernet processDhcpPacketFromServer(Ethernet ethernetPacket) {
469 // get dhcp header.
470 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
471 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
472 UDP udpPacket = (UDP) ipv4Packet.getPayload();
473 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
474
475 // determine the vlanId of the client host - note that this vlan id
476 // could be different from the vlan in the packet from the server
Yi Tsengdcef2c22017-08-05 20:34:06 -0700477 Interface clientInterface = getClientInterface(ethernetPacket, dhcpPayload).orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -0700478
Yi Tsengdcef2c22017-08-05 20:34:06 -0700479 if (clientInterface == null) {
Yi Tseng51301292017-07-28 13:02:59 -0700480 log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
481 return null;
482 }
Yi Tsengdcef2c22017-08-05 20:34:06 -0700483 VlanId vlanId;
484 if (clientInterface.vlanTagged().isEmpty()) {
485 vlanId = clientInterface.vlan();
486 } else {
487 // might be multiple vlan in same interface
488 vlanId = getVlanIdFromOption(dhcpPayload);
489 }
490 if (vlanId == null) {
491 vlanId = VlanId.NONE;
492 }
493 etherReply.setVlanID(vlanId.toShort());
494 etherReply.setSourceMACAddress(clientInterface.mac());
Yi Tseng51301292017-07-28 13:02:59 -0700495
Yi Tsengdcef2c22017-08-05 20:34:06 -0700496 if (!directlyConnected(dhcpPayload)) {
497 // if client is indirectly connected, try use next hop mac address
498 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
499 HostId hostId = HostId.hostId(macAddress, vlanId);
500 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
501 if (record != null) {
502 // if next hop can be found, use mac address of next hop
503 record.nextHop().ifPresent(etherReply::setDestinationMACAddress);
504 } else {
505 // otherwise, discard the packet
506 log.warn("Can't find record for host id {}, discard packet", hostId);
507 return null;
508 }
Yi Tsengc03fa242017-08-17 17:43:38 -0700509 } else {
510 etherReply.setDestinationMACAddress(dhcpPayload.getClientHardwareAddress());
Yi Tsengdcef2c22017-08-05 20:34:06 -0700511 }
512
Yi Tseng51301292017-07-28 13:02:59 -0700513 // we leave the srcMac from the original packet
Yi Tseng51301292017-07-28 13:02:59 -0700514 // figure out the relay agent IP corresponding to the original request
Yi Tsengdcef2c22017-08-05 20:34:06 -0700515 Ip4Address relayAgentIP = getRelayAgentIPv4Address(clientInterface);
Yi Tseng51301292017-07-28 13:02:59 -0700516 if (relayAgentIP == null) {
517 log.warn("Cannot determine relay agent interface Ipv4 addr for host {}/{}. "
518 + "Aborting relay for dhcp packet from server {}",
Yi Tsengdcef2c22017-08-05 20:34:06 -0700519 etherReply.getDestinationMAC(), clientInterface.vlan(),
Yi Tseng51301292017-07-28 13:02:59 -0700520 ethernetPacket);
521 return null;
522 }
523 // SRC_IP: relay agent IP
524 // DST_IP: offered IP
525 ipv4Packet.setSourceAddress(relayAgentIP.toInt());
526 ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
527 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
528 if (directlyConnected(dhcpPayload)) {
529 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
530 } else {
531 // forward to another dhcp relay
532 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
533 }
534
535 udpPacket.setPayload(dhcpPayload);
536 ipv4Packet.setPayload(udpPacket);
537 etherReply.setPayload(ipv4Packet);
538 return etherReply;
539 }
540
Yi Tsengdcef2c22017-08-05 20:34:06 -0700541 /**
542 * Extracts VLAN ID from relay agent option.
543 *
544 * @param dhcpPayload the DHCP payload
545 * @return VLAN ID from DHCP payload; null if not exists
546 */
547 private VlanId getVlanIdFromOption(DHCP dhcpPayload) {
548 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
549 if (option == null) {
550 return null;
551 }
552 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
553 if (circuitIdSubOption == null) {
554 return null;
555 }
556 try {
557 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
558 return circuitId.vlanId();
559 } catch (IllegalArgumentException e) {
560 // can't deserialize the circuit ID
561 return null;
562 }
563 }
564
565 /**
566 * Removes DHCP relay agent information option (option 82) from DHCP payload.
567 * Also reset giaddr to 0
568 *
569 * @param ethPacket the Ethernet packet to be processed
570 * @return Ethernet packet processed
571 */
572 private Ethernet removeRelayAgentOption(Ethernet ethPacket) {
573 Ethernet ethernet = (Ethernet) ethPacket.clone();
574 IPv4 ipv4 = (IPv4) ethernet.getPayload();
575 UDP udp = (UDP) ipv4.getPayload();
576 DHCP dhcpPayload = (DHCP) udp.getPayload();
577
578 // removes relay agent information option
579 List<DhcpOption> options = dhcpPayload.getOptions();
580 options = options.stream()
581 .filter(option -> option.getCode() != OptionCode_CircuitID.getValue())
582 .collect(Collectors.toList());
583 dhcpPayload.setOptions(options);
584 dhcpPayload.setGatewayIPAddress(0);
585
586 udp.setPayload(dhcpPayload);
587 ipv4.setPayload(udp);
588 ethernet.setPayload(ipv4);
589 return ethernet;
590 }
591
Yi Tseng51301292017-07-28 13:02:59 -0700592
593 /**
594 * Check if the host is directly connected to the network or not.
595 *
596 * @param dhcpPayload the dhcp payload
597 * @return true if the host is directly connected to the network; false otherwise
598 */
599 private boolean directlyConnected(DHCP dhcpPayload) {
600 DhcpOption relayAgentOption = dhcpPayload.getOption(OptionCode_CircuitID);
601
602 // Doesn't contains relay option
603 if (relayAgentOption == null) {
604 return true;
605 }
606
607 IpAddress gatewayIp = IpAddress.valueOf(dhcpPayload.getGatewayIPAddress());
608 Set<Interface> gatewayInterfaces = interfaceService.getInterfacesByIp(gatewayIp);
609
610 // Contains relay option, and added by ONOS
611 if (!gatewayInterfaces.isEmpty()) {
612 return true;
613 }
614
615 // Relay option added by other relay agent
616 return false;
617 }
618
619
620 /**
621 * Send the DHCP ack to the requester host.
622 * Modify Host or Route store according to the type of DHCP.
623 *
624 * @param ethernetPacketAck the packet
625 * @param dhcpPayload the DHCP data
626 */
627 private void handleDhcpAck(Ethernet ethernetPacketAck, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700628 Optional<Interface> outInterface = getClientInterface(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700629 if (!outInterface.isPresent()) {
630 log.warn("Can't find output interface for dhcp: {}", dhcpPayload);
631 return;
632 }
633
634 Interface outIface = outInterface.get();
635 HostLocation hostLocation = new HostLocation(outIface.connectPoint(), System.currentTimeMillis());
636 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
Yi Tsengdcef2c22017-08-05 20:34:06 -0700637 VlanId vlanId = getVlanIdFromOption(dhcpPayload);
638 if (vlanId == null) {
639 vlanId = outIface.vlan();
640 }
Yi Tseng51301292017-07-28 13:02:59 -0700641 HostId hostId = HostId.hostId(macAddress, vlanId);
642 Ip4Address ip = Ip4Address.valueOf(dhcpPayload.getYourIPAddress());
643
644 if (directlyConnected(dhcpPayload)) {
645 // Add to host store if it connect to network directly
646 Set<IpAddress> ips = Sets.newHashSet(ip);
647 HostDescription desc = new DefaultHostDescription(macAddress, vlanId,
648 hostLocation, ips);
649
650 // Replace the ip when dhcp server give the host new ip address
651 hostStore.createOrUpdateHost(DhcpRelayManager.PROVIDER_ID, hostId, desc, false);
652 } else {
653 // Add to route store if it does not connect to network directly
654 // Get gateway host IP according to host mac address
Yi Tsengdcef2c22017-08-05 20:34:06 -0700655 // TODO: remove relay store here
Yi Tseng51301292017-07-28 13:02:59 -0700656 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
657
658 if (record == null) {
659 log.warn("Can't find DHCP record of host {}", hostId);
660 return;
661 }
662
663 MacAddress gwMac = record.nextHop().orElse(null);
664 if (gwMac == null) {
665 log.warn("Can't find gateway mac address from record {}", record);
666 return;
667 }
668
669 HostId gwHostId = HostId.hostId(gwMac, record.vlanId());
670 Host gwHost = hostService.getHost(gwHostId);
671
672 if (gwHost == null) {
673 log.warn("Can't find gateway host {}", gwHostId);
674 return;
675 }
676
677 Ip4Address nextHopIp = gwHost.ipAddresses()
678 .stream()
679 .filter(IpAddress::isIp4)
680 .map(IpAddress::getIp4Address)
681 .findFirst()
682 .orElse(null);
683
684 if (nextHopIp == null) {
685 log.warn("Can't find IP address of gateway {}", gwHost);
686 return;
687 }
688
689 Route route = new Route(Route.Source.STATIC, ip.toIpPrefix(), nextHopIp);
690 routeStore.updateRoute(route);
691 }
Yi Tseng51301292017-07-28 13:02:59 -0700692 }
693
694 /**
695 * forward the packet to ConnectPoint where the DHCP server is attached.
696 *
697 * @param packet the packet
698 */
699 private void handleDhcpDiscoverAndRequest(Ethernet packet) {
700 // send packet to dhcp server connect point.
701 if (dhcpServerConnectPoint != null) {
702 TrafficTreatment t = DefaultTrafficTreatment.builder()
703 .setOutput(dhcpServerConnectPoint.port()).build();
704 OutboundPacket o = new DefaultOutboundPacket(
705 dhcpServerConnectPoint.deviceId(), t, ByteBuffer.wrap(packet.serialize()));
706 if (log.isTraceEnabled()) {
707 log.trace("Relaying packet to dhcp server {}", packet);
708 }
709 packetService.emit(o);
710 } else {
711 log.warn("Can't find DHCP server connect point, abort.");
712 }
713 }
714
715
716 /**
717 * Gets output interface of a dhcp packet.
718 * If option 82 exists in the dhcp packet and the option was sent by
719 * ONOS (gateway address exists in ONOS interfaces), use the connect
720 * point and vlan id from circuit id; otherwise, find host by destination
721 * address and use vlan id from sender (dhcp server).
722 *
723 * @param ethPacket the ethernet packet
724 * @param dhcpPayload the dhcp packet
725 * @return an interface represent the output port and vlan; empty value
726 * if the host or circuit id not found
727 */
Yi Tsengdcef2c22017-08-05 20:34:06 -0700728 private Optional<Interface> getClientInterface(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tseng51301292017-07-28 13:02:59 -0700729 VlanId originalPacketVlanId = VlanId.vlanId(ethPacket.getVlanID());
730 IpAddress gatewayIpAddress = Ip4Address.valueOf(dhcpPayload.getGatewayIPAddress());
Yi Tsengdcef2c22017-08-05 20:34:06 -0700731
732 // get all possible interfaces for client
733 Set<Interface> clientInterfaces = interfaceService.getInterfacesByIp(gatewayIpAddress);
Yi Tseng51301292017-07-28 13:02:59 -0700734 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
735
736 // Sent by ONOS, and contains circuit id
Yi Tsengdcef2c22017-08-05 20:34:06 -0700737 if (!clientInterfaces.isEmpty() && option != null) {
Yi Tseng51301292017-07-28 13:02:59 -0700738 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
739 try {
740 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
741 ConnectPoint connectPoint = ConnectPoint.deviceConnectPoint(circuitId.connectPoint());
742 VlanId vlanId = circuitId.vlanId();
Yi Tsengdcef2c22017-08-05 20:34:06 -0700743 return clientInterfaces.stream()
744 .filter(iface -> iface.vlanUntagged().equals(vlanId) ||
745 iface.vlan().equals(vlanId) ||
746 iface.vlanNative().equals(vlanId) ||
747 iface.vlanTagged().contains(vlanId))
748 .filter(iface -> iface.connectPoint().equals(connectPoint))
749 .findFirst();
Yi Tseng51301292017-07-28 13:02:59 -0700750 } catch (IllegalArgumentException ex) {
751 // invalid circuit format, didn't sent by ONOS
752 log.debug("Invalid circuit {}, use information from dhcp payload",
753 circuitIdSubOption.getData());
754 }
755 }
756
757 // Use Vlan Id from DHCP server if DHCP relay circuit id was not
758 // sent by ONOS or circuit Id can't be parsed
Yi Tsengdcef2c22017-08-05 20:34:06 -0700759 // TODO: remove relay store from this method
Yi Tseng51301292017-07-28 13:02:59 -0700760 MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
761 Optional<DhcpRecord> dhcpRecord = dhcpRelayStore.getDhcpRecord(HostId.hostId(dstMac, originalPacketVlanId));
Yi Tsengdcef2c22017-08-05 20:34:06 -0700762 ConnectPoint clientConnectPoint = dhcpRecord
Yi Tseng51301292017-07-28 13:02:59 -0700763 .map(DhcpRecord::locations)
764 .orElse(Collections.emptySet())
765 .stream()
766 .reduce((hl1, hl2) -> {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700767 // find latest host connect point
Yi Tseng51301292017-07-28 13:02:59 -0700768 if (hl1 == null || hl2 == null) {
769 return hl1 == null ? hl2 : hl1;
770 }
771 return hl1.time() > hl2.time() ? hl1 : hl2;
772 })
Yi Tsengdcef2c22017-08-05 20:34:06 -0700773 .orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -0700774
Yi Tsengdcef2c22017-08-05 20:34:06 -0700775 if (clientConnectPoint != null) {
776 return interfaceService.getInterfacesByPort(clientConnectPoint)
777 .stream()
778 .filter(iface -> iface.vlan().equals(originalPacketVlanId) ||
779 iface.vlanUntagged().equals(originalPacketVlanId))
780 .findFirst();
781 }
782 return Optional.empty();
Yi Tseng51301292017-07-28 13:02:59 -0700783 }
784
785 /**
786 * Send the response DHCP to the requester host.
787 *
788 * @param ethPacket the packet
789 * @param dhcpPayload the DHCP data
790 */
791 private void sendResponseToClient(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700792 Optional<Interface> outInterface = getClientInterface(ethPacket, dhcpPayload);
793 if (directlyConnected(dhcpPayload)) {
794 ethPacket = removeRelayAgentOption(ethPacket);
795 }
796 if (!outInterface.isPresent()) {
797 log.warn("Can't find output interface for client, ignore");
798 return;
799 }
800 Interface outIface = outInterface.get();
801 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
802 .setOutput(outIface.connectPoint().port())
803 .build();
804 OutboundPacket o = new DefaultOutboundPacket(
805 outIface.connectPoint().deviceId(),
806 treatment,
807 ByteBuffer.wrap(ethPacket.serialize()));
808 if (log.isTraceEnabled()) {
809 log.trace("Relaying packet to DHCP client {} via {}, vlan {}",
810 ethPacket,
811 outIface.connectPoint(),
812 outIface.vlan());
813 }
814 packetService.emit(o);
Yi Tseng51301292017-07-28 13:02:59 -0700815 }
816}