blob: 4d2bebba6b6aeddefee307cab6d13849ae2d5f34 [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
Yi Tsenge72fbb52017-08-02 15:03:31 -070020import com.google.common.base.MoreObjects;
Yi Tseng51301292017-07-28 13:02:59 -070021import com.google.common.collect.Sets;
Yi Tsenge72fbb52017-08-02 15:03:31 -070022import org.apache.felix.scr.annotations.Activate;
Yi Tseng51301292017-07-28 13:02:59 -070023import org.apache.felix.scr.annotations.Component;
Yi Tsenge72fbb52017-08-02 15:03:31 -070024import org.apache.felix.scr.annotations.Deactivate;
Yi Tseng51301292017-07-28 13:02:59 -070025import org.apache.felix.scr.annotations.Property;
26import org.apache.felix.scr.annotations.Reference;
27import org.apache.felix.scr.annotations.ReferenceCardinality;
28import org.apache.felix.scr.annotations.Service;
29import org.onlab.packet.BasePacket;
30import org.onlab.packet.DHCP;
31import org.onlab.packet.Ethernet;
32import org.onlab.packet.IPv4;
33import org.onlab.packet.Ip4Address;
34import org.onlab.packet.IpAddress;
35import org.onlab.packet.MacAddress;
36import org.onlab.packet.UDP;
37import org.onlab.packet.VlanId;
38import org.onlab.packet.dhcp.CircuitId;
39import org.onlab.packet.dhcp.DhcpOption;
40import org.onlab.packet.dhcp.DhcpRelayAgentOption;
41import org.onosproject.dhcprelay.api.DhcpHandler;
Yi Tsenge72fbb52017-08-02 15:03:31 -070042import org.onosproject.dhcprelay.config.DhcpServerConfig;
Yi Tseng51301292017-07-28 13:02:59 -070043import org.onosproject.dhcprelay.store.DhcpRecord;
44import org.onosproject.dhcprelay.store.DhcpRelayStore;
Yi Tsenge72fbb52017-08-02 15:03:31 -070045import org.onosproject.net.host.HostEvent;
46import org.onosproject.net.host.HostListener;
Ray Milkeyfacf2862017-08-03 11:58:29 -070047import org.onosproject.net.intf.Interface;
48import org.onosproject.net.intf.InterfaceService;
Ray Milkey69ec8712017-08-08 13:00:43 -070049import org.onosproject.routeservice.Route;
50import org.onosproject.routeservice.RouteStore;
Yi Tseng51301292017-07-28 13:02:59 -070051import org.onosproject.net.ConnectPoint;
52import org.onosproject.net.Host;
53import org.onosproject.net.HostId;
54import org.onosproject.net.HostLocation;
55import org.onosproject.net.flow.DefaultTrafficTreatment;
56import org.onosproject.net.flow.TrafficTreatment;
57import org.onosproject.net.host.DefaultHostDescription;
58import org.onosproject.net.host.HostDescription;
59import org.onosproject.net.host.HostService;
60import org.onosproject.net.host.HostStore;
61import org.onosproject.net.host.InterfaceIpAddress;
62import org.onosproject.net.packet.DefaultOutboundPacket;
63import org.onosproject.net.packet.OutboundPacket;
64import org.onosproject.net.packet.PacketContext;
65import org.onosproject.net.packet.PacketService;
66import org.slf4j.Logger;
67import org.slf4j.LoggerFactory;
68
69import java.nio.ByteBuffer;
Yi Tsengdcef2c22017-08-05 20:34:06 -070070import java.util.Collection;
Yi Tseng51301292017-07-28 13:02:59 -070071import java.util.Collections;
72import java.util.List;
73import java.util.Optional;
74import java.util.Set;
75import java.util.stream.Collectors;
76
77import static com.google.common.base.Preconditions.checkNotNull;
78import static com.google.common.base.Preconditions.checkState;
79import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_CircuitID;
80import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_END;
81import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
82import static org.onlab.packet.MacAddress.valueOf;
83import static org.onlab.packet.dhcp.DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID;
84
85@Component
86@Service
87@Property(name = "version", value = "4")
88public class Dhcp4HandlerImpl implements DhcpHandler {
89 private static Logger log = LoggerFactory.getLogger(Dhcp4HandlerImpl.class);
90
91 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
92 protected DhcpRelayStore dhcpRelayStore;
93
94 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
95 protected PacketService packetService;
96
97 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
98 protected HostStore hostStore;
99
100 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
101 protected RouteStore routeStore;
102
103 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
104 protected InterfaceService interfaceService;
105
106 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
107 protected HostService hostService;
108
Yi Tsenge72fbb52017-08-02 15:03:31 -0700109 private InternalHostListener hostListener = new InternalHostListener();
110
Yi Tseng51301292017-07-28 13:02:59 -0700111 private Ip4Address dhcpServerIp = null;
112 // dhcp server may be connected directly to the SDN network or
113 // via an external gateway. When connected directly, the dhcpConnectPoint, dhcpConnectMac,
114 // and dhcpConnectVlan refer to the server. When connected via the gateway, they refer
115 // to the gateway.
116 private ConnectPoint dhcpServerConnectPoint = null;
117 private MacAddress dhcpConnectMac = null;
118 private VlanId dhcpConnectVlan = null;
119 private Ip4Address dhcpGatewayIp = null;
120
Yi Tsenge72fbb52017-08-02 15:03:31 -0700121 @Activate
122 protected void activate() {
123 hostService.addListener(hostListener);
124 }
125
126 @Deactivate
127 protected void deactivate() {
128 hostService.removeListener(hostListener);
129 this.dhcpConnectMac = null;
130 this.dhcpConnectVlan = null;
131 }
132
Yi Tseng51301292017-07-28 13:02:59 -0700133 @Override
134 public void setDhcpServerIp(IpAddress dhcpServerIp) {
135 checkNotNull(dhcpServerIp, "DHCP server IP can't be null");
136 checkState(dhcpServerIp.isIp4(), "Invalid server IP for DHCPv4 relay handler");
137 this.dhcpServerIp = dhcpServerIp.getIp4Address();
138 }
139
140 @Override
141 public void setDhcpServerConnectPoint(ConnectPoint dhcpServerConnectPoint) {
142 checkNotNull(dhcpServerConnectPoint, "Server connect point can't null");
143 this.dhcpServerConnectPoint = dhcpServerConnectPoint;
144 }
145
146 @Override
147 public void setDhcpConnectMac(MacAddress dhcpConnectMac) {
148 this.dhcpConnectMac = dhcpConnectMac;
149 }
150
151 @Override
152 public void setDhcpConnectVlan(VlanId dhcpConnectVlan) {
153 this.dhcpConnectVlan = dhcpConnectVlan;
154 }
155
156 @Override
157 public void setDhcpGatewayIp(IpAddress dhcpGatewayIp) {
158 if (dhcpGatewayIp != null) {
159 checkState(dhcpGatewayIp.isIp4(), "Invalid gateway IP for DHCPv4 relay handler");
160 this.dhcpGatewayIp = dhcpGatewayIp.getIp4Address();
161 } else {
162 // removes gateway config
163 this.dhcpGatewayIp = null;
164 }
165 }
166
167 @Override
168 public Optional<IpAddress> getDhcpServerIp() {
169 return Optional.ofNullable(dhcpServerIp);
170 }
171
172 @Override
173 public Optional<IpAddress> getDhcpGatewayIp() {
174 return Optional.ofNullable(dhcpGatewayIp);
175 }
176
177 @Override
178 public Optional<MacAddress> getDhcpConnectMac() {
179 return Optional.ofNullable(dhcpConnectMac);
180 }
181
182 @Override
Yi Tsenge72fbb52017-08-02 15:03:31 -0700183 public void setDefaultDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
184 if (configs.size() == 0) {
185 // no config to update
186 return;
187 }
188
189 // TODO: currently we pick up first DHCP server config.
190 // Will use other server configs in the future for HA.
191 DhcpServerConfig serverConfig = configs.iterator().next();
192 checkState(serverConfig.getDhcpServerConnectPoint().isPresent(),
193 "Connect point not exists");
194 checkState(serverConfig.getDhcpServerIp4().isPresent(),
195 "IP of DHCP server not exists");
196 Ip4Address oldServerIp = this.dhcpServerIp;
197 Ip4Address oldGatewayIp = this.dhcpGatewayIp;
198
199 // stop monitoring gateway or server
200 if (oldGatewayIp != null) {
201 hostService.stopMonitoringIp(oldGatewayIp);
202 } else if (oldServerIp != null) {
203 hostService.stopMonitoringIp(oldServerIp);
204 }
205
206 this.dhcpServerConnectPoint = serverConfig.getDhcpServerConnectPoint().get();
207 this.dhcpServerIp = serverConfig.getDhcpServerIp4().get();
208 this.dhcpGatewayIp = serverConfig.getDhcpGatewayIp4().orElse(null);
209
210 // reset server mac and vlan
211 this.dhcpConnectMac = null;
212 this.dhcpConnectVlan = null;
213
214 log.info("DHCP server connect point: " + this.dhcpServerConnectPoint);
215 log.info("DHCP server IP: " + this.dhcpServerIp);
216
217 IpAddress ipToProbe = MoreObjects.firstNonNull(this.dhcpGatewayIp, this.dhcpServerIp);
218 String hostToProbe = this.dhcpGatewayIp != null ? "gateway" : "DHCP server";
219
220 if (ipToProbe == null) {
221 log.warn("Server IP not set, can't probe it");
222 return;
223 }
224
225 log.info("Probing to resolve {} IP {}", hostToProbe, ipToProbe);
226 hostService.startMonitoringIp(ipToProbe);
227
228 Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
229 if (!hosts.isEmpty()) {
230 Host host = hosts.iterator().next();
231 this.dhcpConnectVlan = host.vlan();
232 this.dhcpConnectMac = host.mac();
233 }
234 }
235
236 @Override
237 public void setIndirectDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
238 log.warn("Indirect config feature for DHCPv4 handler not implement yet");
239 }
240
Yi Tseng51301292017-07-28 13:02:59 -0700241 public void processDhcpPacket(PacketContext context, BasePacket payload) {
242 checkNotNull(payload, "DHCP payload can't be null");
243 checkState(payload instanceof DHCP, "Payload is not a DHCP");
244 DHCP dhcpPayload = (DHCP) payload;
245 if (!configured()) {
246 log.warn("Missing DHCP relay server config. Abort packet processing");
247 return;
248 }
249
250 ConnectPoint inPort = context.inPacket().receivedFrom();
Yi Tseng51301292017-07-28 13:02:59 -0700251 checkNotNull(dhcpPayload, "Can't find DHCP payload");
252 Ethernet packet = context.inPacket().parsed();
253 DHCP.MsgType incomingPacketType = dhcpPayload.getOptions().stream()
254 .filter(dhcpOption -> dhcpOption.getCode() == OptionCode_MessageType.getValue())
255 .map(DhcpOption::getData)
256 .map(data -> DHCP.MsgType.getType(data[0]))
257 .findFirst()
258 .orElse(null);
259 checkNotNull(incomingPacketType, "Can't get message type from DHCP payload {}", dhcpPayload);
260 switch (incomingPacketType) {
261 case DHCPDISCOVER:
Yi Tsengdcef2c22017-08-05 20:34:06 -0700262 // Try update host if it is directly connected.
263 if (directlyConnected(dhcpPayload)) {
264 updateHost(context, dhcpPayload);
265 }
266
267 // Add the gateway IP as virtual interface IP for server to understand
Yi Tseng51301292017-07-28 13:02:59 -0700268 // the lease to be assigned and forward the packet to dhcp server.
269 Ethernet ethernetPacketDiscover =
Yi Tsengdcef2c22017-08-05 20:34:06 -0700270 processDhcpPacketFromClient(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700271 if (ethernetPacketDiscover != null) {
272 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
273 handleDhcpDiscoverAndRequest(ethernetPacketDiscover);
274 }
275 break;
276 case DHCPOFFER:
277 //reply to dhcp client.
278 Ethernet ethernetPacketOffer = processDhcpPacketFromServer(packet);
279 if (ethernetPacketOffer != null) {
280 writeResponseDhcpRecord(ethernetPacketOffer, dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700281 sendResponseToClient(ethernetPacketOffer, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700282 }
283 break;
284 case DHCPREQUEST:
285 // add the gateway ip as virtual interface ip for server to understand
286 // the lease to be assigned and forward the packet to dhcp server.
287 Ethernet ethernetPacketRequest =
Yi Tsengdcef2c22017-08-05 20:34:06 -0700288 processDhcpPacketFromClient(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700289 if (ethernetPacketRequest != null) {
290 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
291 handleDhcpDiscoverAndRequest(ethernetPacketRequest);
292 }
293 break;
294 case DHCPACK:
295 // reply to dhcp client.
296 Ethernet ethernetPacketAck = processDhcpPacketFromServer(packet);
297 if (ethernetPacketAck != null) {
298 writeResponseDhcpRecord(ethernetPacketAck, dhcpPayload);
299 handleDhcpAck(ethernetPacketAck, dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700300 sendResponseToClient(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700301 }
302 break;
303 case DHCPRELEASE:
304 // TODO: release the ip address from client
305 break;
306 default:
307 break;
308 }
309 }
310
311 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -0700312 * Updates host to host store according to DHCP payload.
313 *
314 * @param context the packet context
315 * @param dhcpPayload the DHCP payload
316 */
317 private void updateHost(PacketContext context, DHCP dhcpPayload) {
318 ConnectPoint location = context.inPacket().receivedFrom();
319 HostLocation hostLocation = new HostLocation(location, System.currentTimeMillis());
320 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
321 VlanId vlanId = VlanId.vlanId(context.inPacket().parsed().getVlanID());
322 HostId hostId = HostId.hostId(macAddress, vlanId);
323 HostDescription desc = new DefaultHostDescription(macAddress, vlanId, hostLocation);
324 hostStore.createOrUpdateHost(DhcpRelayManager.PROVIDER_ID, hostId, desc, false);
325 }
326
327 /**
Yi Tseng51301292017-07-28 13:02:59 -0700328 * Checks if this app has been configured.
329 *
330 * @return true if all information we need have been initialized
331 */
332 public boolean configured() {
333 return dhcpServerConnectPoint != null && dhcpServerIp != null;
334 }
335
336 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -0700337 * Returns the first interface ip from interface.
Yi Tseng51301292017-07-28 13:02:59 -0700338 *
Yi Tsengdcef2c22017-08-05 20:34:06 -0700339 * @param iface interface of one connect point
Yi Tseng51301292017-07-28 13:02:59 -0700340 * @return the first interface IP; null if not exists an IP address in
341 * these interfaces
342 */
Yi Tsengdcef2c22017-08-05 20:34:06 -0700343 private Ip4Address getRelayAgentIPv4Address(Interface iface) {
344 checkNotNull(iface, "Interface can't be null");
345 return iface.ipAddressesList().stream()
Yi Tseng51301292017-07-28 13:02:59 -0700346 .map(InterfaceIpAddress::ipAddress)
347 .filter(IpAddress::isIp4)
348 .map(IpAddress::getIp4Address)
349 .findFirst()
350 .orElse(null);
351 }
352
353 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -0700354 * Gets Interface facing to the server.
355 *
356 * @return the Interface facing to the server; null if not found
357 */
358 public Interface getServerInterface() {
359 if (dhcpServerConnectPoint == null || dhcpConnectVlan == null) {
360 return null;
361 }
362 return interfaceService.getInterfacesByPort(dhcpServerConnectPoint)
363 .stream()
364 .filter(iface -> iface.vlan().equals(dhcpConnectVlan) ||
365 iface.vlanUntagged().equals(dhcpConnectVlan) ||
366 iface.vlanTagged().contains(dhcpConnectVlan) ||
367 iface.vlanNative().equals(dhcpConnectVlan))
368 .findFirst()
369 .orElse(null);
370 }
371
372 /**
Yi Tseng51301292017-07-28 13:02:59 -0700373 * Build the DHCP discover/request packet with gateway IP(unicast packet).
374 *
375 * @param context the packet context
376 * @param ethernetPacket the ethernet payload to process
Yi Tseng51301292017-07-28 13:02:59 -0700377 * @return processed packet
378 */
379 private Ethernet processDhcpPacketFromClient(PacketContext context,
Yi Tsengdcef2c22017-08-05 20:34:06 -0700380 Ethernet ethernetPacket) {
381 Ip4Address clientInterfaceIp =
382 interfaceService.getInterfacesByPort(context.inPacket().receivedFrom())
383 .stream()
384 .map(Interface::ipAddressesList)
385 .flatMap(Collection::stream)
386 .map(InterfaceIpAddress::ipAddress)
387 .filter(IpAddress::isIp4)
388 .map(IpAddress::getIp4Address)
389 .findFirst()
390 .orElse(null);
391 if (clientInterfaceIp == null) {
392 log.warn("Can't find interface IP for client interface for port {}",
393 context.inPacket().receivedFrom());
394 return null;
395 }
396 Interface serverInterface = getServerInterface();
397 if (serverInterface == null) {
398 log.warn("Can't get server interface, ignore");
399 return null;
400 }
401 Ip4Address relayAgentIp = getRelayAgentIPv4Address(serverInterface);
402 MacAddress relayAgentMac = serverInterface.mac();
Yi Tseng51301292017-07-28 13:02:59 -0700403 if (relayAgentIp == null || relayAgentMac == null) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700404 log.warn("No IP address for server Interface {}", serverInterface);
Yi Tseng51301292017-07-28 13:02:59 -0700405 return null;
406 }
407 if (dhcpConnectMac == null) {
408 log.warn("DHCP {} not yet resolved .. Aborting DHCP "
409 + "packet processing from client on port: {}",
410 (dhcpGatewayIp == null) ? "server IP " + dhcpServerIp
411 : "gateway IP " + dhcpGatewayIp,
Yi Tsengdcef2c22017-08-05 20:34:06 -0700412 context.inPacket().receivedFrom());
Yi Tseng51301292017-07-28 13:02:59 -0700413 return null;
414 }
415 // get dhcp header.
416 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
417 etherReply.setSourceMACAddress(relayAgentMac);
418 etherReply.setDestinationMACAddress(dhcpConnectMac);
419 etherReply.setVlanID(dhcpConnectVlan.toShort());
420 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
421 ipv4Packet.setSourceAddress(relayAgentIp.toInt());
422 ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
423 UDP udpPacket = (UDP) ipv4Packet.getPayload();
424 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
425
Yi Tsengdcef2c22017-08-05 20:34:06 -0700426 if (directlyConnected(dhcpPacket)) {
Yi Tseng51301292017-07-28 13:02:59 -0700427 ConnectPoint inPort = context.inPacket().receivedFrom();
428 VlanId vlanId = VlanId.vlanId(ethernetPacket.getVlanID());
429 // add connected in port and vlan
430 CircuitId cid = new CircuitId(inPort.toString(), vlanId);
431 byte[] circuitId = cid.serialize();
432 DhcpOption circuitIdSubOpt = new DhcpOption();
433 circuitIdSubOpt
434 .setCode(CIRCUIT_ID.getValue())
435 .setLength((byte) circuitId.length)
436 .setData(circuitId);
437
438 DhcpRelayAgentOption newRelayAgentOpt = new DhcpRelayAgentOption();
439 newRelayAgentOpt.setCode(OptionCode_CircuitID.getValue());
440 newRelayAgentOpt.addSubOption(circuitIdSubOpt);
441
442 // Removes END option first
443 List<DhcpOption> options = dhcpPacket.getOptions().stream()
444 .filter(opt -> opt.getCode() != OptionCode_END.getValue())
445 .collect(Collectors.toList());
446
447 // push relay agent option
448 options.add(newRelayAgentOpt);
449
450 // make sure option 255(End) is the last option
451 DhcpOption endOption = new DhcpOption();
452 endOption.setCode(OptionCode_END.getValue());
453 options.add(endOption);
454
455 dhcpPacket.setOptions(options);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700456
457 // Sets giaddr to IP address from the Interface which facing to
458 // DHCP client
459 dhcpPacket.setGatewayIPAddress(clientInterfaceIp.toInt());
Yi Tseng51301292017-07-28 13:02:59 -0700460 }
461
462 udpPacket.setPayload(dhcpPacket);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700463 // As a DHCP relay, the source port should be server port(67) instead
464 // of client port(68)
465 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
Yi Tseng51301292017-07-28 13:02:59 -0700466 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
467 ipv4Packet.setPayload(udpPacket);
468 etherReply.setPayload(ipv4Packet);
469 return etherReply;
470 }
471
472 /**
473 * Writes DHCP record to the store according to the request DHCP packet (Discover, Request).
474 *
475 * @param location the location which DHCP packet comes from
476 * @param ethernet the DHCP packet
477 * @param dhcpPayload the DHCP payload
478 */
479 private void writeRequestDhcpRecord(ConnectPoint location,
480 Ethernet ethernet,
481 DHCP dhcpPayload) {
482 VlanId vlanId = VlanId.vlanId(ethernet.getVlanID());
483 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
484 HostId hostId = HostId.hostId(macAddress, vlanId);
485 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
486 if (record == null) {
487 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
488 } else {
489 record = record.clone();
490 }
491 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
492 record.ip4Status(dhcpPayload.getPacketType());
493 record.setDirectlyConnected(directlyConnected(dhcpPayload));
494 if (!directlyConnected(dhcpPayload)) {
495 // Update gateway mac address if the host is not directly connected
496 record.nextHop(ethernet.getSourceMAC());
497 }
498 record.updateLastSeen();
499 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
500 }
501
502 /**
503 * Writes DHCP record to the store according to the response DHCP packet (Offer, Ack).
504 *
505 * @param ethernet the DHCP packet
506 * @param dhcpPayload the DHCP payload
507 */
508 private void writeResponseDhcpRecord(Ethernet ethernet,
509 DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700510 Optional<Interface> outInterface = getClientInterface(ethernet, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700511 if (!outInterface.isPresent()) {
512 log.warn("Failed to determine where to send {}", dhcpPayload.getPacketType());
513 return;
514 }
515
516 Interface outIface = outInterface.get();
517 ConnectPoint location = outIface.connectPoint();
Yi Tsengdcef2c22017-08-05 20:34:06 -0700518 VlanId vlanId = getVlanIdFromOption(dhcpPayload);
519 if (vlanId == null) {
520 vlanId = outIface.vlan();
521 }
Yi Tseng51301292017-07-28 13:02:59 -0700522 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
523 HostId hostId = HostId.hostId(macAddress, vlanId);
524 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
525 if (record == null) {
526 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
527 } else {
528 record = record.clone();
529 }
530 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
531 if (dhcpPayload.getPacketType() == DHCP.MsgType.DHCPACK) {
532 record.ip4Address(Ip4Address.valueOf(dhcpPayload.getYourIPAddress()));
533 }
534 record.ip4Status(dhcpPayload.getPacketType());
535 record.setDirectlyConnected(directlyConnected(dhcpPayload));
536 record.updateLastSeen();
537 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
538 }
539
540 /**
541 * Build the DHCP offer/ack with proper client port.
542 *
543 * @param ethernetPacket the original packet comes from server
544 * @return new packet which will send to the client
545 */
546 private Ethernet processDhcpPacketFromServer(Ethernet ethernetPacket) {
547 // get dhcp header.
548 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
549 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
550 UDP udpPacket = (UDP) ipv4Packet.getPayload();
551 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
552
553 // determine the vlanId of the client host - note that this vlan id
554 // could be different from the vlan in the packet from the server
Yi Tsengdcef2c22017-08-05 20:34:06 -0700555 Interface clientInterface = getClientInterface(ethernetPacket, dhcpPayload).orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -0700556
Yi Tsengdcef2c22017-08-05 20:34:06 -0700557 if (clientInterface == null) {
Yi Tseng51301292017-07-28 13:02:59 -0700558 log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
559 return null;
560 }
Yi Tsengdcef2c22017-08-05 20:34:06 -0700561 VlanId vlanId;
562 if (clientInterface.vlanTagged().isEmpty()) {
563 vlanId = clientInterface.vlan();
564 } else {
565 // might be multiple vlan in same interface
566 vlanId = getVlanIdFromOption(dhcpPayload);
567 }
568 if (vlanId == null) {
569 vlanId = VlanId.NONE;
570 }
571 etherReply.setVlanID(vlanId.toShort());
572 etherReply.setSourceMACAddress(clientInterface.mac());
Yi Tseng51301292017-07-28 13:02:59 -0700573
Yi Tsengdcef2c22017-08-05 20:34:06 -0700574 if (!directlyConnected(dhcpPayload)) {
575 // if client is indirectly connected, try use next hop mac address
576 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
577 HostId hostId = HostId.hostId(macAddress, vlanId);
578 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
579 if (record != null) {
580 // if next hop can be found, use mac address of next hop
581 record.nextHop().ifPresent(etherReply::setDestinationMACAddress);
582 } else {
583 // otherwise, discard the packet
584 log.warn("Can't find record for host id {}, discard packet", hostId);
585 return null;
586 }
Yi Tsengc03fa242017-08-17 17:43:38 -0700587 } else {
588 etherReply.setDestinationMACAddress(dhcpPayload.getClientHardwareAddress());
Yi Tsengdcef2c22017-08-05 20:34:06 -0700589 }
590
Yi Tseng51301292017-07-28 13:02:59 -0700591 // we leave the srcMac from the original packet
Yi Tseng51301292017-07-28 13:02:59 -0700592 // figure out the relay agent IP corresponding to the original request
Yi Tsengdcef2c22017-08-05 20:34:06 -0700593 Ip4Address relayAgentIP = getRelayAgentIPv4Address(clientInterface);
Yi Tseng51301292017-07-28 13:02:59 -0700594 if (relayAgentIP == null) {
595 log.warn("Cannot determine relay agent interface Ipv4 addr for host {}/{}. "
596 + "Aborting relay for dhcp packet from server {}",
Yi Tsengdcef2c22017-08-05 20:34:06 -0700597 etherReply.getDestinationMAC(), clientInterface.vlan(),
Yi Tseng51301292017-07-28 13:02:59 -0700598 ethernetPacket);
599 return null;
600 }
601 // SRC_IP: relay agent IP
602 // DST_IP: offered IP
603 ipv4Packet.setSourceAddress(relayAgentIP.toInt());
604 ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
605 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
606 if (directlyConnected(dhcpPayload)) {
607 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
608 } else {
609 // forward to another dhcp relay
610 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
611 }
612
613 udpPacket.setPayload(dhcpPayload);
614 ipv4Packet.setPayload(udpPacket);
615 etherReply.setPayload(ipv4Packet);
616 return etherReply;
617 }
618
Yi Tsengdcef2c22017-08-05 20:34:06 -0700619 /**
620 * Extracts VLAN ID from relay agent option.
621 *
622 * @param dhcpPayload the DHCP payload
623 * @return VLAN ID from DHCP payload; null if not exists
624 */
625 private VlanId getVlanIdFromOption(DHCP dhcpPayload) {
626 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
627 if (option == null) {
628 return null;
629 }
630 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
631 if (circuitIdSubOption == null) {
632 return null;
633 }
634 try {
635 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
636 return circuitId.vlanId();
637 } catch (IllegalArgumentException e) {
638 // can't deserialize the circuit ID
639 return null;
640 }
641 }
642
643 /**
644 * Removes DHCP relay agent information option (option 82) from DHCP payload.
645 * Also reset giaddr to 0
646 *
647 * @param ethPacket the Ethernet packet to be processed
648 * @return Ethernet packet processed
649 */
650 private Ethernet removeRelayAgentOption(Ethernet ethPacket) {
651 Ethernet ethernet = (Ethernet) ethPacket.clone();
652 IPv4 ipv4 = (IPv4) ethernet.getPayload();
653 UDP udp = (UDP) ipv4.getPayload();
654 DHCP dhcpPayload = (DHCP) udp.getPayload();
655
656 // removes relay agent information option
657 List<DhcpOption> options = dhcpPayload.getOptions();
658 options = options.stream()
659 .filter(option -> option.getCode() != OptionCode_CircuitID.getValue())
660 .collect(Collectors.toList());
661 dhcpPayload.setOptions(options);
662 dhcpPayload.setGatewayIPAddress(0);
663
664 udp.setPayload(dhcpPayload);
665 ipv4.setPayload(udp);
666 ethernet.setPayload(ipv4);
667 return ethernet;
668 }
669
Yi Tseng51301292017-07-28 13:02:59 -0700670
671 /**
672 * Check if the host is directly connected to the network or not.
673 *
674 * @param dhcpPayload the dhcp payload
675 * @return true if the host is directly connected to the network; false otherwise
676 */
677 private boolean directlyConnected(DHCP dhcpPayload) {
Yi Tseng2cf59912017-08-24 14:47:34 -0700678 DhcpRelayAgentOption relayAgentOption =
679 (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
Yi Tseng51301292017-07-28 13:02:59 -0700680
681 // Doesn't contains relay option
682 if (relayAgentOption == null) {
683 return true;
684 }
685
Yi Tseng2cf59912017-08-24 14:47:34 -0700686 // check circuit id, if circuit id is invalid, we say it is an indirect host
687 DhcpOption circuitIdOpt = relayAgentOption.getSubOption(CIRCUIT_ID.getValue());
Yi Tseng51301292017-07-28 13:02:59 -0700688
Yi Tseng2cf59912017-08-24 14:47:34 -0700689 try {
690 CircuitId.deserialize(circuitIdOpt.getData());
Yi Tseng51301292017-07-28 13:02:59 -0700691 return true;
Yi Tseng2cf59912017-08-24 14:47:34 -0700692 } catch (Exception e) {
693 // invalid circuit id
694 return false;
Yi Tseng51301292017-07-28 13:02:59 -0700695 }
Yi Tseng51301292017-07-28 13:02:59 -0700696 }
697
698
699 /**
700 * Send the DHCP ack to the requester host.
701 * Modify Host or Route store according to the type of DHCP.
702 *
703 * @param ethernetPacketAck the packet
704 * @param dhcpPayload the DHCP data
705 */
706 private void handleDhcpAck(Ethernet ethernetPacketAck, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700707 Optional<Interface> outInterface = getClientInterface(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700708 if (!outInterface.isPresent()) {
709 log.warn("Can't find output interface for dhcp: {}", dhcpPayload);
710 return;
711 }
712
713 Interface outIface = outInterface.get();
714 HostLocation hostLocation = new HostLocation(outIface.connectPoint(), System.currentTimeMillis());
715 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
Yi Tsengdcef2c22017-08-05 20:34:06 -0700716 VlanId vlanId = getVlanIdFromOption(dhcpPayload);
717 if (vlanId == null) {
718 vlanId = outIface.vlan();
719 }
Yi Tseng51301292017-07-28 13:02:59 -0700720 HostId hostId = HostId.hostId(macAddress, vlanId);
721 Ip4Address ip = Ip4Address.valueOf(dhcpPayload.getYourIPAddress());
722
723 if (directlyConnected(dhcpPayload)) {
724 // Add to host store if it connect to network directly
725 Set<IpAddress> ips = Sets.newHashSet(ip);
726 HostDescription desc = new DefaultHostDescription(macAddress, vlanId,
727 hostLocation, ips);
728
729 // Replace the ip when dhcp server give the host new ip address
730 hostStore.createOrUpdateHost(DhcpRelayManager.PROVIDER_ID, hostId, desc, false);
731 } else {
732 // Add to route store if it does not connect to network directly
733 // Get gateway host IP according to host mac address
Yi Tsengdcef2c22017-08-05 20:34:06 -0700734 // TODO: remove relay store here
Yi Tseng51301292017-07-28 13:02:59 -0700735 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
736
737 if (record == null) {
738 log.warn("Can't find DHCP record of host {}", hostId);
739 return;
740 }
741
742 MacAddress gwMac = record.nextHop().orElse(null);
743 if (gwMac == null) {
744 log.warn("Can't find gateway mac address from record {}", record);
745 return;
746 }
747
748 HostId gwHostId = HostId.hostId(gwMac, record.vlanId());
749 Host gwHost = hostService.getHost(gwHostId);
750
751 if (gwHost == null) {
752 log.warn("Can't find gateway host {}", gwHostId);
753 return;
754 }
755
756 Ip4Address nextHopIp = gwHost.ipAddresses()
757 .stream()
758 .filter(IpAddress::isIp4)
759 .map(IpAddress::getIp4Address)
760 .findFirst()
761 .orElse(null);
762
763 if (nextHopIp == null) {
764 log.warn("Can't find IP address of gateway {}", gwHost);
765 return;
766 }
767
768 Route route = new Route(Route.Source.STATIC, ip.toIpPrefix(), nextHopIp);
769 routeStore.updateRoute(route);
770 }
Yi Tseng51301292017-07-28 13:02:59 -0700771 }
772
773 /**
774 * forward the packet to ConnectPoint where the DHCP server is attached.
775 *
776 * @param packet the packet
777 */
778 private void handleDhcpDiscoverAndRequest(Ethernet packet) {
779 // send packet to dhcp server connect point.
780 if (dhcpServerConnectPoint != null) {
781 TrafficTreatment t = DefaultTrafficTreatment.builder()
782 .setOutput(dhcpServerConnectPoint.port()).build();
783 OutboundPacket o = new DefaultOutboundPacket(
784 dhcpServerConnectPoint.deviceId(), t, ByteBuffer.wrap(packet.serialize()));
785 if (log.isTraceEnabled()) {
786 log.trace("Relaying packet to dhcp server {}", packet);
787 }
788 packetService.emit(o);
789 } else {
790 log.warn("Can't find DHCP server connect point, abort.");
791 }
792 }
793
794
795 /**
796 * Gets output interface of a dhcp packet.
797 * If option 82 exists in the dhcp packet and the option was sent by
798 * ONOS (gateway address exists in ONOS interfaces), use the connect
799 * point and vlan id from circuit id; otherwise, find host by destination
800 * address and use vlan id from sender (dhcp server).
801 *
802 * @param ethPacket the ethernet packet
803 * @param dhcpPayload the dhcp packet
804 * @return an interface represent the output port and vlan; empty value
805 * if the host or circuit id not found
806 */
Yi Tsengdcef2c22017-08-05 20:34:06 -0700807 private Optional<Interface> getClientInterface(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tseng51301292017-07-28 13:02:59 -0700808 VlanId originalPacketVlanId = VlanId.vlanId(ethPacket.getVlanID());
809 IpAddress gatewayIpAddress = Ip4Address.valueOf(dhcpPayload.getGatewayIPAddress());
Yi Tsengdcef2c22017-08-05 20:34:06 -0700810
811 // get all possible interfaces for client
812 Set<Interface> clientInterfaces = interfaceService.getInterfacesByIp(gatewayIpAddress);
Yi Tseng51301292017-07-28 13:02:59 -0700813 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
814
815 // Sent by ONOS, and contains circuit id
Yi Tsengdcef2c22017-08-05 20:34:06 -0700816 if (!clientInterfaces.isEmpty() && option != null) {
Yi Tseng51301292017-07-28 13:02:59 -0700817 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
818 try {
819 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
820 ConnectPoint connectPoint = ConnectPoint.deviceConnectPoint(circuitId.connectPoint());
821 VlanId vlanId = circuitId.vlanId();
Yi Tsengdcef2c22017-08-05 20:34:06 -0700822 return clientInterfaces.stream()
823 .filter(iface -> iface.vlanUntagged().equals(vlanId) ||
824 iface.vlan().equals(vlanId) ||
825 iface.vlanNative().equals(vlanId) ||
826 iface.vlanTagged().contains(vlanId))
827 .filter(iface -> iface.connectPoint().equals(connectPoint))
828 .findFirst();
Yi Tseng51301292017-07-28 13:02:59 -0700829 } catch (IllegalArgumentException ex) {
830 // invalid circuit format, didn't sent by ONOS
831 log.debug("Invalid circuit {}, use information from dhcp payload",
832 circuitIdSubOption.getData());
833 }
834 }
835
836 // Use Vlan Id from DHCP server if DHCP relay circuit id was not
837 // sent by ONOS or circuit Id can't be parsed
Yi Tsengdcef2c22017-08-05 20:34:06 -0700838 // TODO: remove relay store from this method
Yi Tseng51301292017-07-28 13:02:59 -0700839 MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
840 Optional<DhcpRecord> dhcpRecord = dhcpRelayStore.getDhcpRecord(HostId.hostId(dstMac, originalPacketVlanId));
Yi Tsengdcef2c22017-08-05 20:34:06 -0700841 ConnectPoint clientConnectPoint = dhcpRecord
Yi Tseng51301292017-07-28 13:02:59 -0700842 .map(DhcpRecord::locations)
843 .orElse(Collections.emptySet())
844 .stream()
845 .reduce((hl1, hl2) -> {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700846 // find latest host connect point
Yi Tseng51301292017-07-28 13:02:59 -0700847 if (hl1 == null || hl2 == null) {
848 return hl1 == null ? hl2 : hl1;
849 }
850 return hl1.time() > hl2.time() ? hl1 : hl2;
851 })
Yi Tsengdcef2c22017-08-05 20:34:06 -0700852 .orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -0700853
Yi Tsengdcef2c22017-08-05 20:34:06 -0700854 if (clientConnectPoint != null) {
855 return interfaceService.getInterfacesByPort(clientConnectPoint)
856 .stream()
857 .filter(iface -> iface.vlan().equals(originalPacketVlanId) ||
858 iface.vlanUntagged().equals(originalPacketVlanId))
859 .findFirst();
860 }
861 return Optional.empty();
Yi Tseng51301292017-07-28 13:02:59 -0700862 }
863
864 /**
865 * Send the response DHCP to the requester host.
866 *
867 * @param ethPacket the packet
868 * @param dhcpPayload the DHCP data
869 */
870 private void sendResponseToClient(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700871 Optional<Interface> outInterface = getClientInterface(ethPacket, dhcpPayload);
872 if (directlyConnected(dhcpPayload)) {
873 ethPacket = removeRelayAgentOption(ethPacket);
874 }
875 if (!outInterface.isPresent()) {
876 log.warn("Can't find output interface for client, ignore");
877 return;
878 }
879 Interface outIface = outInterface.get();
880 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
881 .setOutput(outIface.connectPoint().port())
882 .build();
883 OutboundPacket o = new DefaultOutboundPacket(
884 outIface.connectPoint().deviceId(),
885 treatment,
886 ByteBuffer.wrap(ethPacket.serialize()));
887 if (log.isTraceEnabled()) {
888 log.trace("Relaying packet to DHCP client {} via {}, vlan {}",
889 ethPacket,
890 outIface.connectPoint(),
891 outIface.vlan());
892 }
893 packetService.emit(o);
Yi Tseng51301292017-07-28 13:02:59 -0700894 }
Yi Tsenge72fbb52017-08-02 15:03:31 -0700895
896 class InternalHostListener implements HostListener {
897 @Override
898 public void event(HostEvent event) {
899 switch (event.type()) {
900 case HOST_ADDED:
901 case HOST_UPDATED:
902 hostUpdated(event.subject());
903 break;
904 case HOST_REMOVED:
905 hostRemoved(event.subject());
906 break;
907 case HOST_MOVED:
908 hostMoved(event.subject());
909 break;
910 default:
911 break;
912 }
913 }
914 }
915
916 /**
917 * Handle host move.
918 * If the host DHCP server or gateway and it moved to the location different
919 * to user configured, unsets the connect mac and vlan
920 *
921 * @param host the host
922 */
923 private void hostMoved(Host host) {
924 if (this.dhcpServerConnectPoint == null) {
925 return;
926 }
927 if (this.dhcpGatewayIp != null) {
928 if (host.ipAddresses().contains(this.dhcpGatewayIp) &&
929 !host.locations().contains(this.dhcpServerConnectPoint)) {
930 this.dhcpConnectMac = null;
931 this.dhcpConnectVlan = null;
932 }
933 return;
934 }
935 if (this.dhcpServerIp != null) {
936 if (host.ipAddresses().contains(this.dhcpServerIp) &&
937 !host.locations().contains(this.dhcpServerConnectPoint)) {
938 this.dhcpConnectMac = null;
939 this.dhcpConnectVlan = null;
940 }
941 }
942 }
943
944 /**
945 * Handle host updated.
946 * If the host is DHCP server or gateway, update connect mac and vlan.
947 *
948 * @param host the host
949 */
950 private void hostUpdated(Host host) {
951 if (this.dhcpGatewayIp != null) {
952 if (host.ipAddresses().contains(this.dhcpGatewayIp)) {
953 this.dhcpConnectMac = host.mac();
954 this.dhcpConnectVlan = host.vlan();
955 }
956 return;
957 }
958 if (this.dhcpServerIp != null) {
959 if (host.ipAddresses().contains(this.dhcpServerIp)) {
960 this.dhcpConnectMac = host.mac();
961 this.dhcpConnectVlan = host.vlan();
962 }
963 }
964 }
965
966 /**
967 * Handle host removed.
968 * If the host is DHCP server or gateway, unset connect mac and vlan.
969 *
970 * @param host the host
971 */
972 private void hostRemoved(Host host) {
973 if (this.dhcpGatewayIp != null) {
974 if (host.ipAddresses().contains(this.dhcpGatewayIp)) {
975 this.dhcpConnectMac = null;
976 this.dhcpConnectVlan = null;
977 }
978 return;
979 }
980 if (this.dhcpServerIp != null) {
981 if (host.ipAddresses().contains(this.dhcpServerIp)) {
982 this.dhcpConnectMac = null;
983 this.dhcpConnectVlan = null;
984 }
985 }
986 }
Yi Tseng51301292017-07-28 13:02:59 -0700987}