blob: 072870ee595f6ee7837699051c6ec6e2aa96acb2 [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 }
509
510 }
511
Yi Tseng51301292017-07-28 13:02:59 -0700512 // we leave the srcMac from the original packet
Yi Tseng51301292017-07-28 13:02:59 -0700513 // figure out the relay agent IP corresponding to the original request
Yi Tsengdcef2c22017-08-05 20:34:06 -0700514 Ip4Address relayAgentIP = getRelayAgentIPv4Address(clientInterface);
Yi Tseng51301292017-07-28 13:02:59 -0700515 if (relayAgentIP == null) {
516 log.warn("Cannot determine relay agent interface Ipv4 addr for host {}/{}. "
517 + "Aborting relay for dhcp packet from server {}",
Yi Tsengdcef2c22017-08-05 20:34:06 -0700518 etherReply.getDestinationMAC(), clientInterface.vlan(),
Yi Tseng51301292017-07-28 13:02:59 -0700519 ethernetPacket);
520 return null;
521 }
522 // SRC_IP: relay agent IP
523 // DST_IP: offered IP
524 ipv4Packet.setSourceAddress(relayAgentIP.toInt());
525 ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
526 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
527 if (directlyConnected(dhcpPayload)) {
528 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
529 } else {
530 // forward to another dhcp relay
531 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
532 }
533
534 udpPacket.setPayload(dhcpPayload);
535 ipv4Packet.setPayload(udpPacket);
536 etherReply.setPayload(ipv4Packet);
537 return etherReply;
538 }
539
Yi Tsengdcef2c22017-08-05 20:34:06 -0700540 /**
541 * Extracts VLAN ID from relay agent option.
542 *
543 * @param dhcpPayload the DHCP payload
544 * @return VLAN ID from DHCP payload; null if not exists
545 */
546 private VlanId getVlanIdFromOption(DHCP dhcpPayload) {
547 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
548 if (option == null) {
549 return null;
550 }
551 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
552 if (circuitIdSubOption == null) {
553 return null;
554 }
555 try {
556 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
557 return circuitId.vlanId();
558 } catch (IllegalArgumentException e) {
559 // can't deserialize the circuit ID
560 return null;
561 }
562 }
563
564 /**
565 * Removes DHCP relay agent information option (option 82) from DHCP payload.
566 * Also reset giaddr to 0
567 *
568 * @param ethPacket the Ethernet packet to be processed
569 * @return Ethernet packet processed
570 */
571 private Ethernet removeRelayAgentOption(Ethernet ethPacket) {
572 Ethernet ethernet = (Ethernet) ethPacket.clone();
573 IPv4 ipv4 = (IPv4) ethernet.getPayload();
574 UDP udp = (UDP) ipv4.getPayload();
575 DHCP dhcpPayload = (DHCP) udp.getPayload();
576
577 // removes relay agent information option
578 List<DhcpOption> options = dhcpPayload.getOptions();
579 options = options.stream()
580 .filter(option -> option.getCode() != OptionCode_CircuitID.getValue())
581 .collect(Collectors.toList());
582 dhcpPayload.setOptions(options);
583 dhcpPayload.setGatewayIPAddress(0);
584
585 udp.setPayload(dhcpPayload);
586 ipv4.setPayload(udp);
587 ethernet.setPayload(ipv4);
588 return ethernet;
589 }
590
Yi Tseng51301292017-07-28 13:02:59 -0700591
592 /**
593 * Check if the host is directly connected to the network or not.
594 *
595 * @param dhcpPayload the dhcp payload
596 * @return true if the host is directly connected to the network; false otherwise
597 */
598 private boolean directlyConnected(DHCP dhcpPayload) {
599 DhcpOption relayAgentOption = dhcpPayload.getOption(OptionCode_CircuitID);
600
601 // Doesn't contains relay option
602 if (relayAgentOption == null) {
603 return true;
604 }
605
606 IpAddress gatewayIp = IpAddress.valueOf(dhcpPayload.getGatewayIPAddress());
607 Set<Interface> gatewayInterfaces = interfaceService.getInterfacesByIp(gatewayIp);
608
609 // Contains relay option, and added by ONOS
610 if (!gatewayInterfaces.isEmpty()) {
611 return true;
612 }
613
614 // Relay option added by other relay agent
615 return false;
616 }
617
618
619 /**
620 * Send the DHCP ack to the requester host.
621 * Modify Host or Route store according to the type of DHCP.
622 *
623 * @param ethernetPacketAck the packet
624 * @param dhcpPayload the DHCP data
625 */
626 private void handleDhcpAck(Ethernet ethernetPacketAck, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700627 Optional<Interface> outInterface = getClientInterface(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700628 if (!outInterface.isPresent()) {
629 log.warn("Can't find output interface for dhcp: {}", dhcpPayload);
630 return;
631 }
632
633 Interface outIface = outInterface.get();
634 HostLocation hostLocation = new HostLocation(outIface.connectPoint(), System.currentTimeMillis());
635 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
Yi Tsengdcef2c22017-08-05 20:34:06 -0700636 VlanId vlanId = getVlanIdFromOption(dhcpPayload);
637 if (vlanId == null) {
638 vlanId = outIface.vlan();
639 }
Yi Tseng51301292017-07-28 13:02:59 -0700640 HostId hostId = HostId.hostId(macAddress, vlanId);
641 Ip4Address ip = Ip4Address.valueOf(dhcpPayload.getYourIPAddress());
642
643 if (directlyConnected(dhcpPayload)) {
644 // Add to host store if it connect to network directly
645 Set<IpAddress> ips = Sets.newHashSet(ip);
646 HostDescription desc = new DefaultHostDescription(macAddress, vlanId,
647 hostLocation, ips);
648
649 // Replace the ip when dhcp server give the host new ip address
650 hostStore.createOrUpdateHost(DhcpRelayManager.PROVIDER_ID, hostId, desc, false);
651 } else {
652 // Add to route store if it does not connect to network directly
653 // Get gateway host IP according to host mac address
Yi Tsengdcef2c22017-08-05 20:34:06 -0700654 // TODO: remove relay store here
Yi Tseng51301292017-07-28 13:02:59 -0700655 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
656
657 if (record == null) {
658 log.warn("Can't find DHCP record of host {}", hostId);
659 return;
660 }
661
662 MacAddress gwMac = record.nextHop().orElse(null);
663 if (gwMac == null) {
664 log.warn("Can't find gateway mac address from record {}", record);
665 return;
666 }
667
668 HostId gwHostId = HostId.hostId(gwMac, record.vlanId());
669 Host gwHost = hostService.getHost(gwHostId);
670
671 if (gwHost == null) {
672 log.warn("Can't find gateway host {}", gwHostId);
673 return;
674 }
675
676 Ip4Address nextHopIp = gwHost.ipAddresses()
677 .stream()
678 .filter(IpAddress::isIp4)
679 .map(IpAddress::getIp4Address)
680 .findFirst()
681 .orElse(null);
682
683 if (nextHopIp == null) {
684 log.warn("Can't find IP address of gateway {}", gwHost);
685 return;
686 }
687
688 Route route = new Route(Route.Source.STATIC, ip.toIpPrefix(), nextHopIp);
689 routeStore.updateRoute(route);
690 }
Yi Tseng51301292017-07-28 13:02:59 -0700691 }
692
693 /**
694 * forward the packet to ConnectPoint where the DHCP server is attached.
695 *
696 * @param packet the packet
697 */
698 private void handleDhcpDiscoverAndRequest(Ethernet packet) {
699 // send packet to dhcp server connect point.
700 if (dhcpServerConnectPoint != null) {
701 TrafficTreatment t = DefaultTrafficTreatment.builder()
702 .setOutput(dhcpServerConnectPoint.port()).build();
703 OutboundPacket o = new DefaultOutboundPacket(
704 dhcpServerConnectPoint.deviceId(), t, ByteBuffer.wrap(packet.serialize()));
705 if (log.isTraceEnabled()) {
706 log.trace("Relaying packet to dhcp server {}", packet);
707 }
708 packetService.emit(o);
709 } else {
710 log.warn("Can't find DHCP server connect point, abort.");
711 }
712 }
713
714
715 /**
716 * Gets output interface of a dhcp packet.
717 * If option 82 exists in the dhcp packet and the option was sent by
718 * ONOS (gateway address exists in ONOS interfaces), use the connect
719 * point and vlan id from circuit id; otherwise, find host by destination
720 * address and use vlan id from sender (dhcp server).
721 *
722 * @param ethPacket the ethernet packet
723 * @param dhcpPayload the dhcp packet
724 * @return an interface represent the output port and vlan; empty value
725 * if the host or circuit id not found
726 */
Yi Tsengdcef2c22017-08-05 20:34:06 -0700727 private Optional<Interface> getClientInterface(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tseng51301292017-07-28 13:02:59 -0700728 VlanId originalPacketVlanId = VlanId.vlanId(ethPacket.getVlanID());
729 IpAddress gatewayIpAddress = Ip4Address.valueOf(dhcpPayload.getGatewayIPAddress());
Yi Tsengdcef2c22017-08-05 20:34:06 -0700730
731 // get all possible interfaces for client
732 Set<Interface> clientInterfaces = interfaceService.getInterfacesByIp(gatewayIpAddress);
Yi Tseng51301292017-07-28 13:02:59 -0700733 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
734
735 // Sent by ONOS, and contains circuit id
Yi Tsengdcef2c22017-08-05 20:34:06 -0700736 if (!clientInterfaces.isEmpty() && option != null) {
Yi Tseng51301292017-07-28 13:02:59 -0700737 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
738 try {
739 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
740 ConnectPoint connectPoint = ConnectPoint.deviceConnectPoint(circuitId.connectPoint());
741 VlanId vlanId = circuitId.vlanId();
Yi Tsengdcef2c22017-08-05 20:34:06 -0700742 return clientInterfaces.stream()
743 .filter(iface -> iface.vlanUntagged().equals(vlanId) ||
744 iface.vlan().equals(vlanId) ||
745 iface.vlanNative().equals(vlanId) ||
746 iface.vlanTagged().contains(vlanId))
747 .filter(iface -> iface.connectPoint().equals(connectPoint))
748 .findFirst();
Yi Tseng51301292017-07-28 13:02:59 -0700749 } catch (IllegalArgumentException ex) {
750 // invalid circuit format, didn't sent by ONOS
751 log.debug("Invalid circuit {}, use information from dhcp payload",
752 circuitIdSubOption.getData());
753 }
754 }
755
756 // Use Vlan Id from DHCP server if DHCP relay circuit id was not
757 // sent by ONOS or circuit Id can't be parsed
Yi Tsengdcef2c22017-08-05 20:34:06 -0700758 // TODO: remove relay store from this method
Yi Tseng51301292017-07-28 13:02:59 -0700759 MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
760 Optional<DhcpRecord> dhcpRecord = dhcpRelayStore.getDhcpRecord(HostId.hostId(dstMac, originalPacketVlanId));
Yi Tsengdcef2c22017-08-05 20:34:06 -0700761 ConnectPoint clientConnectPoint = dhcpRecord
Yi Tseng51301292017-07-28 13:02:59 -0700762 .map(DhcpRecord::locations)
763 .orElse(Collections.emptySet())
764 .stream()
765 .reduce((hl1, hl2) -> {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700766 // find latest host connect point
Yi Tseng51301292017-07-28 13:02:59 -0700767 if (hl1 == null || hl2 == null) {
768 return hl1 == null ? hl2 : hl1;
769 }
770 return hl1.time() > hl2.time() ? hl1 : hl2;
771 })
Yi Tsengdcef2c22017-08-05 20:34:06 -0700772 .orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -0700773
Yi Tsengdcef2c22017-08-05 20:34:06 -0700774 if (clientConnectPoint != null) {
775 return interfaceService.getInterfacesByPort(clientConnectPoint)
776 .stream()
777 .filter(iface -> iface.vlan().equals(originalPacketVlanId) ||
778 iface.vlanUntagged().equals(originalPacketVlanId))
779 .findFirst();
780 }
781 return Optional.empty();
Yi Tseng51301292017-07-28 13:02:59 -0700782 }
783
784 /**
785 * Send the response DHCP to the requester host.
786 *
787 * @param ethPacket the packet
788 * @param dhcpPayload the DHCP data
789 */
790 private void sendResponseToClient(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700791 Optional<Interface> outInterface = getClientInterface(ethPacket, dhcpPayload);
792 if (directlyConnected(dhcpPayload)) {
793 ethPacket = removeRelayAgentOption(ethPacket);
794 }
795 if (!outInterface.isPresent()) {
796 log.warn("Can't find output interface for client, ignore");
797 return;
798 }
799 Interface outIface = outInterface.get();
800 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
801 .setOutput(outIface.connectPoint().port())
802 .build();
803 OutboundPacket o = new DefaultOutboundPacket(
804 outIface.connectPoint().deviceId(),
805 treatment,
806 ByteBuffer.wrap(ethPacket.serialize()));
807 if (log.isTraceEnabled()) {
808 log.trace("Relaying packet to DHCP client {} via {}, vlan {}",
809 ethPacket,
810 outIface.connectPoint(),
811 outIface.vlan());
812 }
813 packetService.emit(o);
Yi Tseng51301292017-07-28 13:02:59 -0700814 }
815}