blob: ccdb407fecd8e083c06cfef19a4e0968144c9a5f [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;
Yi Tseng4fa05832017-08-17 13:08:31 -0700120 private Ip4Address relayAgentIp = null;
Yi Tseng51301292017-07-28 13:02:59 -0700121
Yi Tseng4ec727d2017-08-31 11:21:00 -0700122 // Indirect case DHCP server
123 private Ip4Address indirectDhcpServerIp = null;
124 private ConnectPoint indirectDhcpServerConnectPoint = null;
125 private MacAddress indirectDhcpConnectMac = null;
126 private VlanId indirectDhcpConnectVlan = null;
127 private Ip4Address indirectDhcpGatewayIp = null;
128 private Ip4Address indirectRelayAgentIp = null;
129
Yi Tsenge72fbb52017-08-02 15:03:31 -0700130 @Activate
131 protected void activate() {
132 hostService.addListener(hostListener);
133 }
134
135 @Deactivate
136 protected void deactivate() {
Yi Tseng4ec727d2017-08-31 11:21:00 -0700137 if (dhcpGatewayIp != null) {
138 hostService.stopMonitoringIp(dhcpGatewayIp);
139 }
140 if (dhcpServerIp != null) {
141 hostService.stopMonitoringIp(dhcpServerIp);
142 }
143
144 if (indirectDhcpGatewayIp != null) {
145 hostService.stopMonitoringIp(indirectDhcpGatewayIp);
146 }
147 if (indirectDhcpServerIp != null) {
148 hostService.stopMonitoringIp(indirectDhcpServerIp);
149 }
150
Yi Tsenge72fbb52017-08-02 15:03:31 -0700151 hostService.removeListener(hostListener);
152 this.dhcpConnectMac = null;
153 this.dhcpConnectVlan = null;
Yi Tseng4ec727d2017-08-31 11:21:00 -0700154 this.indirectDhcpConnectMac = null;
155 this.indirectDhcpConnectVlan = null;
Yi Tsenge72fbb52017-08-02 15:03:31 -0700156 }
157
Yi Tseng51301292017-07-28 13:02:59 -0700158 @Override
159 public void setDhcpServerIp(IpAddress dhcpServerIp) {
160 checkNotNull(dhcpServerIp, "DHCP server IP can't be null");
161 checkState(dhcpServerIp.isIp4(), "Invalid server IP for DHCPv4 relay handler");
162 this.dhcpServerIp = dhcpServerIp.getIp4Address();
163 }
164
165 @Override
166 public void setDhcpServerConnectPoint(ConnectPoint dhcpServerConnectPoint) {
167 checkNotNull(dhcpServerConnectPoint, "Server connect point can't null");
168 this.dhcpServerConnectPoint = dhcpServerConnectPoint;
169 }
170
171 @Override
172 public void setDhcpConnectMac(MacAddress dhcpConnectMac) {
173 this.dhcpConnectMac = dhcpConnectMac;
174 }
175
176 @Override
177 public void setDhcpConnectVlan(VlanId dhcpConnectVlan) {
178 this.dhcpConnectVlan = dhcpConnectVlan;
179 }
180
181 @Override
182 public void setDhcpGatewayIp(IpAddress dhcpGatewayIp) {
183 if (dhcpGatewayIp != null) {
184 checkState(dhcpGatewayIp.isIp4(), "Invalid gateway IP for DHCPv4 relay handler");
185 this.dhcpGatewayIp = dhcpGatewayIp.getIp4Address();
186 } else {
187 // removes gateway config
188 this.dhcpGatewayIp = null;
189 }
190 }
191
192 @Override
193 public Optional<IpAddress> getDhcpServerIp() {
194 return Optional.ofNullable(dhcpServerIp);
195 }
196
197 @Override
198 public Optional<IpAddress> getDhcpGatewayIp() {
199 return Optional.ofNullable(dhcpGatewayIp);
200 }
201
202 @Override
203 public Optional<MacAddress> getDhcpConnectMac() {
204 return Optional.ofNullable(dhcpConnectMac);
205 }
206
207 @Override
Yi Tsenge72fbb52017-08-02 15:03:31 -0700208 public void setDefaultDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
209 if (configs.size() == 0) {
210 // no config to update
211 return;
212 }
213
214 // TODO: currently we pick up first DHCP server config.
215 // Will use other server configs in the future for HA.
216 DhcpServerConfig serverConfig = configs.iterator().next();
Yi Tsengaefbb002017-09-08 16:23:32 -0700217 if (!serverConfig.getDhcpServerConnectPoint().isPresent()) {
218 log.warn("Connect point from server config not exists");
219 return;
220 }
221 if (!serverConfig.getDhcpServerIp4().isPresent()) {
222 log.warn("IP from DHCP server config not exists");
223 return;
224 }
Yi Tsenge72fbb52017-08-02 15:03:31 -0700225 Ip4Address oldServerIp = this.dhcpServerIp;
226 Ip4Address oldGatewayIp = this.dhcpGatewayIp;
227
228 // stop monitoring gateway or server
229 if (oldGatewayIp != null) {
230 hostService.stopMonitoringIp(oldGatewayIp);
231 } else if (oldServerIp != null) {
232 hostService.stopMonitoringIp(oldServerIp);
233 }
234
235 this.dhcpServerConnectPoint = serverConfig.getDhcpServerConnectPoint().get();
236 this.dhcpServerIp = serverConfig.getDhcpServerIp4().get();
237 this.dhcpGatewayIp = serverConfig.getDhcpGatewayIp4().orElse(null);
238
239 // reset server mac and vlan
240 this.dhcpConnectMac = null;
241 this.dhcpConnectVlan = null;
242
243 log.info("DHCP server connect point: " + this.dhcpServerConnectPoint);
244 log.info("DHCP server IP: " + this.dhcpServerIp);
245
246 IpAddress ipToProbe = MoreObjects.firstNonNull(this.dhcpGatewayIp, this.dhcpServerIp);
247 String hostToProbe = this.dhcpGatewayIp != null ? "gateway" : "DHCP server";
248
249 if (ipToProbe == null) {
250 log.warn("Server IP not set, can't probe it");
251 return;
252 }
253
254 log.info("Probing to resolve {} IP {}", hostToProbe, ipToProbe);
255 hostService.startMonitoringIp(ipToProbe);
256
257 Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
258 if (!hosts.isEmpty()) {
259 Host host = hosts.iterator().next();
260 this.dhcpConnectVlan = host.vlan();
261 this.dhcpConnectMac = host.mac();
262 }
Yi Tseng4fa05832017-08-17 13:08:31 -0700263
264 this.relayAgentIp = serverConfig.getRelayAgentIp4().orElse(null);
Yi Tsenge72fbb52017-08-02 15:03:31 -0700265 }
266
267 @Override
268 public void setIndirectDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
Yi Tseng4ec727d2017-08-31 11:21:00 -0700269 if (configs.size() == 0) {
270 // no config to update
271 return;
272 }
273
274 // TODO: currently we pick up first indirect DHCP server config.
275 // Will use other server configs in the future for HA.
276 DhcpServerConfig serverConfig = configs.iterator().next();
277 checkState(serverConfig.getDhcpServerConnectPoint().isPresent(),
278 "Connect point not exists");
279 checkState(serverConfig.getDhcpServerIp4().isPresent(),
280 "IP of DHCP server not exists");
281 Ip4Address oldServerIp = this.indirectDhcpServerIp;
282 Ip4Address oldGatewayIp = this.indirectDhcpGatewayIp;
283
284 // stop monitoring gateway or server
285 if (oldGatewayIp != null) {
286 hostService.stopMonitoringIp(oldGatewayIp);
287 } else if (oldServerIp != null) {
288 hostService.stopMonitoringIp(oldServerIp);
289 }
290
291 this.indirectDhcpServerConnectPoint = serverConfig.getDhcpServerConnectPoint().get();
292 this.indirectDhcpServerIp = serverConfig.getDhcpServerIp4().get();
293 this.indirectDhcpGatewayIp = serverConfig.getDhcpGatewayIp4().orElse(null);
294
295 // reset server mac and vlan
296 this.indirectDhcpConnectMac = null;
297 this.indirectDhcpConnectVlan = null;
298
299 log.info("Indirect DHCP server connect point: " + this.indirectDhcpServerConnectPoint);
300 log.info("Indirect DHCP server IP: " + this.indirectDhcpServerIp);
301
302 IpAddress ipToProbe = MoreObjects.firstNonNull(this.indirectDhcpGatewayIp, this.indirectDhcpServerIp);
303 String hostToProbe = this.indirectDhcpGatewayIp != null ? "gateway" : "DHCP server";
304
305 log.info("Probing to resolve {} IP {}", hostToProbe, ipToProbe);
306 hostService.startMonitoringIp(ipToProbe);
307
308 Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
309 if (!hosts.isEmpty()) {
310 Host host = hosts.iterator().next();
311 this.indirectDhcpConnectVlan = host.vlan();
312 this.indirectDhcpConnectMac = host.mac();
313 }
314
315 this.indirectRelayAgentIp = serverConfig.getRelayAgentIp4().orElse(null);
Yi Tsenge72fbb52017-08-02 15:03:31 -0700316 }
317
Yi Tseng4fa05832017-08-17 13:08:31 -0700318 @Override
Yi Tseng51301292017-07-28 13:02:59 -0700319 public void processDhcpPacket(PacketContext context, BasePacket payload) {
320 checkNotNull(payload, "DHCP payload can't be null");
321 checkState(payload instanceof DHCP, "Payload is not a DHCP");
322 DHCP dhcpPayload = (DHCP) payload;
323 if (!configured()) {
324 log.warn("Missing DHCP relay server config. Abort packet processing");
325 return;
326 }
327
328 ConnectPoint inPort = context.inPacket().receivedFrom();
Yi Tseng51301292017-07-28 13:02:59 -0700329 checkNotNull(dhcpPayload, "Can't find DHCP payload");
330 Ethernet packet = context.inPacket().parsed();
331 DHCP.MsgType incomingPacketType = dhcpPayload.getOptions().stream()
332 .filter(dhcpOption -> dhcpOption.getCode() == OptionCode_MessageType.getValue())
333 .map(DhcpOption::getData)
334 .map(data -> DHCP.MsgType.getType(data[0]))
335 .findFirst()
336 .orElse(null);
337 checkNotNull(incomingPacketType, "Can't get message type from DHCP payload {}", dhcpPayload);
338 switch (incomingPacketType) {
339 case DHCPDISCOVER:
Yi Tsengdcef2c22017-08-05 20:34:06 -0700340 // Try update host if it is directly connected.
341 if (directlyConnected(dhcpPayload)) {
342 updateHost(context, dhcpPayload);
343 }
344
345 // Add the gateway IP as virtual interface IP for server to understand
Yi Tseng51301292017-07-28 13:02:59 -0700346 // the lease to be assigned and forward the packet to dhcp server.
347 Ethernet ethernetPacketDiscover =
Yi Tsengdcef2c22017-08-05 20:34:06 -0700348 processDhcpPacketFromClient(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700349 if (ethernetPacketDiscover != null) {
350 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700351 handleDhcpDiscoverAndRequest(ethernetPacketDiscover, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700352 }
353 break;
354 case DHCPOFFER:
355 //reply to dhcp client.
356 Ethernet ethernetPacketOffer = processDhcpPacketFromServer(packet);
357 if (ethernetPacketOffer != null) {
358 writeResponseDhcpRecord(ethernetPacketOffer, dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700359 sendResponseToClient(ethernetPacketOffer, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700360 }
361 break;
362 case DHCPREQUEST:
363 // add the gateway ip as virtual interface ip for server to understand
364 // the lease to be assigned and forward the packet to dhcp server.
365 Ethernet ethernetPacketRequest =
Yi Tsengdcef2c22017-08-05 20:34:06 -0700366 processDhcpPacketFromClient(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700367 if (ethernetPacketRequest != null) {
368 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700369 handleDhcpDiscoverAndRequest(ethernetPacketRequest, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700370 }
371 break;
372 case DHCPACK:
373 // reply to dhcp client.
374 Ethernet ethernetPacketAck = processDhcpPacketFromServer(packet);
375 if (ethernetPacketAck != null) {
376 writeResponseDhcpRecord(ethernetPacketAck, dhcpPayload);
377 handleDhcpAck(ethernetPacketAck, dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700378 sendResponseToClient(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700379 }
380 break;
381 case DHCPRELEASE:
382 // TODO: release the ip address from client
383 break;
384 default:
385 break;
386 }
387 }
388
389 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -0700390 * Updates host to host store according to DHCP payload.
391 *
392 * @param context the packet context
393 * @param dhcpPayload the DHCP payload
394 */
395 private void updateHost(PacketContext context, DHCP dhcpPayload) {
396 ConnectPoint location = context.inPacket().receivedFrom();
397 HostLocation hostLocation = new HostLocation(location, System.currentTimeMillis());
398 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
399 VlanId vlanId = VlanId.vlanId(context.inPacket().parsed().getVlanID());
400 HostId hostId = HostId.hostId(macAddress, vlanId);
401 HostDescription desc = new DefaultHostDescription(macAddress, vlanId, hostLocation);
402 hostStore.createOrUpdateHost(DhcpRelayManager.PROVIDER_ID, hostId, desc, false);
403 }
404
405 /**
Yi Tseng51301292017-07-28 13:02:59 -0700406 * Checks if this app has been configured.
407 *
408 * @return true if all information we need have been initialized
409 */
Yi Tseng4ec727d2017-08-31 11:21:00 -0700410 private boolean configured() {
Yi Tseng51301292017-07-28 13:02:59 -0700411 return dhcpServerConnectPoint != null && dhcpServerIp != null;
412 }
413
414 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -0700415 * Returns the first interface ip from interface.
Yi Tseng51301292017-07-28 13:02:59 -0700416 *
Yi Tsengdcef2c22017-08-05 20:34:06 -0700417 * @param iface interface of one connect point
Yi Tseng51301292017-07-28 13:02:59 -0700418 * @return the first interface IP; null if not exists an IP address in
419 * these interfaces
420 */
Yi Tseng4fa05832017-08-17 13:08:31 -0700421 private Ip4Address getFirstIpFromInterface(Interface iface) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700422 checkNotNull(iface, "Interface can't be null");
423 return iface.ipAddressesList().stream()
Yi Tseng51301292017-07-28 13:02:59 -0700424 .map(InterfaceIpAddress::ipAddress)
425 .filter(IpAddress::isIp4)
426 .map(IpAddress::getIp4Address)
427 .findFirst()
428 .orElse(null);
429 }
430
431 /**
Yi Tseng4ec727d2017-08-31 11:21:00 -0700432 * Gets Interface facing to the server for default host.
Yi Tsengdcef2c22017-08-05 20:34:06 -0700433 *
434 * @return the Interface facing to the server; null if not found
435 */
Yi Tseng4ec727d2017-08-31 11:21:00 -0700436 private Interface getServerInterface() {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700437 if (dhcpServerConnectPoint == null || dhcpConnectVlan == null) {
438 return null;
439 }
440 return interfaceService.getInterfacesByPort(dhcpServerConnectPoint)
441 .stream()
Yi Tseng4ec727d2017-08-31 11:21:00 -0700442 .filter(iface -> interfaceContainsVlan(iface, dhcpConnectVlan))
Yi Tsengdcef2c22017-08-05 20:34:06 -0700443 .findFirst()
444 .orElse(null);
445 }
446
447 /**
Yi Tseng4ec727d2017-08-31 11:21:00 -0700448 * Gets Interface facing to the server for indirect hosts.
449 * Use default server Interface if indirect server not configured.
450 *
451 * @return the Interface facing to the server; null if not found
452 */
453 private Interface getIndirectServerInterface() {
454 if (indirectDhcpServerConnectPoint == null || indirectDhcpConnectVlan == null) {
455 return getServerInterface();
456 }
457 return interfaceService.getInterfacesByPort(indirectDhcpServerConnectPoint)
458 .stream()
459 .filter(iface -> interfaceContainsVlan(iface, indirectDhcpConnectVlan))
460 .findFirst()
461 .orElse(null);
462 }
463
464 /**
465 * Determind if an Interface contains a vlan id.
466 *
467 * @param iface the Interface
468 * @param vlanId the vlan id
469 * @return true if the Interface contains the vlan id
470 */
471 private boolean interfaceContainsVlan(Interface iface, VlanId vlanId) {
472 return iface.vlan().equals(vlanId) ||
473 iface.vlanUntagged().equals(vlanId) ||
474 iface.vlanTagged().contains(vlanId) ||
475 iface.vlanNative().equals(vlanId);
476 }
477
478 /**
Yi Tseng51301292017-07-28 13:02:59 -0700479 * Build the DHCP discover/request packet with gateway IP(unicast packet).
480 *
481 * @param context the packet context
482 * @param ethernetPacket the ethernet payload to process
Yi Tseng51301292017-07-28 13:02:59 -0700483 * @return processed packet
484 */
485 private Ethernet processDhcpPacketFromClient(PacketContext context,
Yi Tsengdcef2c22017-08-05 20:34:06 -0700486 Ethernet ethernetPacket) {
Yi Tseng4ec727d2017-08-31 11:21:00 -0700487 // get dhcp header.
488 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
489 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
490 UDP udpPacket = (UDP) ipv4Packet.getPayload();
491 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
492
Yi Tsengdcef2c22017-08-05 20:34:06 -0700493 Ip4Address clientInterfaceIp =
494 interfaceService.getInterfacesByPort(context.inPacket().receivedFrom())
495 .stream()
496 .map(Interface::ipAddressesList)
497 .flatMap(Collection::stream)
498 .map(InterfaceIpAddress::ipAddress)
499 .filter(IpAddress::isIp4)
500 .map(IpAddress::getIp4Address)
501 .findFirst()
502 .orElse(null);
503 if (clientInterfaceIp == null) {
504 log.warn("Can't find interface IP for client interface for port {}",
505 context.inPacket().receivedFrom());
506 return null;
507 }
Yi Tseng4ec727d2017-08-31 11:21:00 -0700508 boolean isDirectlyConnected = directlyConnected(dhcpPacket);
509 Interface serverInterface = isDirectlyConnected ? getServerInterface() : getIndirectServerInterface();
Yi Tsengdcef2c22017-08-05 20:34:06 -0700510 if (serverInterface == null) {
Yi Tseng4ec727d2017-08-31 11:21:00 -0700511 log.warn("Can't get {} server interface, ignore", isDirectlyConnected ? "direct" : "indirect");
Yi Tsengdcef2c22017-08-05 20:34:06 -0700512 return null;
513 }
Yi Tseng4fa05832017-08-17 13:08:31 -0700514 Ip4Address ipFacingServer = getFirstIpFromInterface(serverInterface);
515 MacAddress macFacingServer = serverInterface.mac();
516 if (ipFacingServer == null || macFacingServer == null) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700517 log.warn("No IP address for server Interface {}", serverInterface);
Yi Tseng51301292017-07-28 13:02:59 -0700518 return null;
519 }
520 if (dhcpConnectMac == null) {
521 log.warn("DHCP {} not yet resolved .. Aborting DHCP "
522 + "packet processing from client on port: {}",
523 (dhcpGatewayIp == null) ? "server IP " + dhcpServerIp
524 : "gateway IP " + dhcpGatewayIp,
Yi Tsengdcef2c22017-08-05 20:34:06 -0700525 context.inPacket().receivedFrom());
Yi Tseng51301292017-07-28 13:02:59 -0700526 return null;
527 }
Yi Tseng4ec727d2017-08-31 11:21:00 -0700528
Yi Tseng4fa05832017-08-17 13:08:31 -0700529 etherReply.setSourceMACAddress(macFacingServer);
Yi Tseng51301292017-07-28 13:02:59 -0700530 etherReply.setDestinationMACAddress(dhcpConnectMac);
531 etherReply.setVlanID(dhcpConnectVlan.toShort());
Yi Tseng4fa05832017-08-17 13:08:31 -0700532 ipv4Packet.setSourceAddress(ipFacingServer.toInt());
Yi Tseng51301292017-07-28 13:02:59 -0700533 ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
Yi Tseng51301292017-07-28 13:02:59 -0700534
Yi Tseng4ec727d2017-08-31 11:21:00 -0700535 if (isDirectlyConnected) {
Yi Tseng51301292017-07-28 13:02:59 -0700536 ConnectPoint inPort = context.inPacket().receivedFrom();
537 VlanId vlanId = VlanId.vlanId(ethernetPacket.getVlanID());
538 // add connected in port and vlan
539 CircuitId cid = new CircuitId(inPort.toString(), vlanId);
540 byte[] circuitId = cid.serialize();
541 DhcpOption circuitIdSubOpt = new DhcpOption();
542 circuitIdSubOpt
543 .setCode(CIRCUIT_ID.getValue())
544 .setLength((byte) circuitId.length)
545 .setData(circuitId);
546
547 DhcpRelayAgentOption newRelayAgentOpt = new DhcpRelayAgentOption();
548 newRelayAgentOpt.setCode(OptionCode_CircuitID.getValue());
549 newRelayAgentOpt.addSubOption(circuitIdSubOpt);
550
551 // Removes END option first
552 List<DhcpOption> options = dhcpPacket.getOptions().stream()
553 .filter(opt -> opt.getCode() != OptionCode_END.getValue())
554 .collect(Collectors.toList());
555
556 // push relay agent option
557 options.add(newRelayAgentOpt);
558
559 // make sure option 255(End) is the last option
560 DhcpOption endOption = new DhcpOption();
561 endOption.setCode(OptionCode_END.getValue());
562 options.add(endOption);
563
564 dhcpPacket.setOptions(options);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700565
566 // Sets giaddr to IP address from the Interface which facing to
567 // DHCP client
568 dhcpPacket.setGatewayIPAddress(clientInterfaceIp.toInt());
Yi Tseng51301292017-07-28 13:02:59 -0700569
Yi Tseng4ec727d2017-08-31 11:21:00 -0700570 // replace giaddr if relay agent IP is set
571 if (relayAgentIp != null) {
572 dhcpPacket.setGatewayIPAddress(relayAgentIp.toInt());
573 }
574 } else if (indirectDhcpServerIp != null) {
575 // Indirect case, replace destination to indirect dhcp server if exist
576 etherReply.setDestinationMACAddress(indirectDhcpConnectMac);
577 etherReply.setVlanID(indirectDhcpConnectVlan.toShort());
578 ipv4Packet.setDestinationAddress(indirectDhcpServerIp.toInt());
579
580 // replace giaddr if relay agent IP is set
581 if (indirectRelayAgentIp != null) {
582 dhcpPacket.setGatewayIPAddress(relayAgentIp.toInt());
583 }
Yi Tseng4fa05832017-08-17 13:08:31 -0700584 }
585
Yi Tseng51301292017-07-28 13:02:59 -0700586 udpPacket.setPayload(dhcpPacket);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700587 // As a DHCP relay, the source port should be server port( instead
588 // of client port.
Yi Tsengdcef2c22017-08-05 20:34:06 -0700589 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
Yi Tseng51301292017-07-28 13:02:59 -0700590 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
591 ipv4Packet.setPayload(udpPacket);
592 etherReply.setPayload(ipv4Packet);
593 return etherReply;
594 }
595
596 /**
597 * Writes DHCP record to the store according to the request DHCP packet (Discover, Request).
598 *
599 * @param location the location which DHCP packet comes from
600 * @param ethernet the DHCP packet
601 * @param dhcpPayload the DHCP payload
602 */
603 private void writeRequestDhcpRecord(ConnectPoint location,
604 Ethernet ethernet,
605 DHCP dhcpPayload) {
606 VlanId vlanId = VlanId.vlanId(ethernet.getVlanID());
607 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
608 HostId hostId = HostId.hostId(macAddress, vlanId);
609 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
610 if (record == null) {
611 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
612 } else {
613 record = record.clone();
614 }
615 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
616 record.ip4Status(dhcpPayload.getPacketType());
617 record.setDirectlyConnected(directlyConnected(dhcpPayload));
618 if (!directlyConnected(dhcpPayload)) {
619 // Update gateway mac address if the host is not directly connected
620 record.nextHop(ethernet.getSourceMAC());
621 }
622 record.updateLastSeen();
623 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
624 }
625
626 /**
627 * Writes DHCP record to the store according to the response DHCP packet (Offer, Ack).
628 *
629 * @param ethernet the DHCP packet
630 * @param dhcpPayload the DHCP payload
631 */
632 private void writeResponseDhcpRecord(Ethernet ethernet,
633 DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700634 Optional<Interface> outInterface = getClientInterface(ethernet, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700635 if (!outInterface.isPresent()) {
636 log.warn("Failed to determine where to send {}", dhcpPayload.getPacketType());
637 return;
638 }
639
640 Interface outIface = outInterface.get();
641 ConnectPoint location = outIface.connectPoint();
Yi Tseng4ec727d2017-08-31 11:21:00 -0700642 VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700643 if (vlanId == null) {
644 vlanId = outIface.vlan();
645 }
Yi Tseng51301292017-07-28 13:02:59 -0700646 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
647 HostId hostId = HostId.hostId(macAddress, vlanId);
648 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
649 if (record == null) {
650 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
651 } else {
652 record = record.clone();
653 }
654 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
655 if (dhcpPayload.getPacketType() == DHCP.MsgType.DHCPACK) {
656 record.ip4Address(Ip4Address.valueOf(dhcpPayload.getYourIPAddress()));
657 }
658 record.ip4Status(dhcpPayload.getPacketType());
659 record.setDirectlyConnected(directlyConnected(dhcpPayload));
660 record.updateLastSeen();
661 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
662 }
663
664 /**
665 * Build the DHCP offer/ack with proper client port.
666 *
667 * @param ethernetPacket the original packet comes from server
668 * @return new packet which will send to the client
669 */
670 private Ethernet processDhcpPacketFromServer(Ethernet ethernetPacket) {
671 // get dhcp header.
672 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
673 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
674 UDP udpPacket = (UDP) ipv4Packet.getPayload();
675 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
676
677 // determine the vlanId of the client host - note that this vlan id
678 // could be different from the vlan in the packet from the server
Yi Tsengdcef2c22017-08-05 20:34:06 -0700679 Interface clientInterface = getClientInterface(ethernetPacket, dhcpPayload).orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -0700680
Yi Tsengdcef2c22017-08-05 20:34:06 -0700681 if (clientInterface == null) {
Yi Tseng51301292017-07-28 13:02:59 -0700682 log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
683 return null;
684 }
Yi Tsengdcef2c22017-08-05 20:34:06 -0700685 VlanId vlanId;
686 if (clientInterface.vlanTagged().isEmpty()) {
687 vlanId = clientInterface.vlan();
688 } else {
689 // might be multiple vlan in same interface
Yi Tseng4ec727d2017-08-31 11:21:00 -0700690 vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700691 }
692 if (vlanId == null) {
693 vlanId = VlanId.NONE;
694 }
695 etherReply.setVlanID(vlanId.toShort());
696 etherReply.setSourceMACAddress(clientInterface.mac());
Yi Tseng51301292017-07-28 13:02:59 -0700697
Yi Tsengdcef2c22017-08-05 20:34:06 -0700698 if (!directlyConnected(dhcpPayload)) {
699 // if client is indirectly connected, try use next hop mac address
700 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
701 HostId hostId = HostId.hostId(macAddress, vlanId);
702 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
703 if (record != null) {
704 // if next hop can be found, use mac address of next hop
705 record.nextHop().ifPresent(etherReply::setDestinationMACAddress);
706 } else {
707 // otherwise, discard the packet
708 log.warn("Can't find record for host id {}, discard packet", hostId);
709 return null;
710 }
Yi Tsengc03fa242017-08-17 17:43:38 -0700711 } else {
712 etherReply.setDestinationMACAddress(dhcpPayload.getClientHardwareAddress());
Yi Tsengdcef2c22017-08-05 20:34:06 -0700713 }
714
Yi Tseng51301292017-07-28 13:02:59 -0700715 // we leave the srcMac from the original packet
Yi Tseng51301292017-07-28 13:02:59 -0700716 // figure out the relay agent IP corresponding to the original request
Yi Tseng4fa05832017-08-17 13:08:31 -0700717 Ip4Address ipFacingClient = getFirstIpFromInterface(clientInterface);
718 if (ipFacingClient == null) {
Yi Tseng51301292017-07-28 13:02:59 -0700719 log.warn("Cannot determine relay agent interface Ipv4 addr for host {}/{}. "
720 + "Aborting relay for dhcp packet from server {}",
Yi Tsengdcef2c22017-08-05 20:34:06 -0700721 etherReply.getDestinationMAC(), clientInterface.vlan(),
Yi Tseng51301292017-07-28 13:02:59 -0700722 ethernetPacket);
723 return null;
724 }
725 // SRC_IP: relay agent IP
726 // DST_IP: offered IP
Yi Tseng4fa05832017-08-17 13:08:31 -0700727 ipv4Packet.setSourceAddress(ipFacingClient.toInt());
Yi Tseng51301292017-07-28 13:02:59 -0700728 ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
729 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
730 if (directlyConnected(dhcpPayload)) {
731 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
732 } else {
733 // forward to another dhcp relay
734 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
735 }
736
737 udpPacket.setPayload(dhcpPayload);
738 ipv4Packet.setPayload(udpPacket);
739 etherReply.setPayload(ipv4Packet);
740 return etherReply;
741 }
742
Yi Tsengdcef2c22017-08-05 20:34:06 -0700743 /**
744 * Extracts VLAN ID from relay agent option.
745 *
746 * @param dhcpPayload the DHCP payload
747 * @return VLAN ID from DHCP payload; null if not exists
748 */
Yi Tseng4ec727d2017-08-31 11:21:00 -0700749 private VlanId getVlanIdFromRelayAgentOption(DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700750 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
751 if (option == null) {
752 return null;
753 }
754 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
755 if (circuitIdSubOption == null) {
756 return null;
757 }
758 try {
759 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
760 return circuitId.vlanId();
761 } catch (IllegalArgumentException e) {
762 // can't deserialize the circuit ID
763 return null;
764 }
765 }
766
767 /**
768 * Removes DHCP relay agent information option (option 82) from DHCP payload.
769 * Also reset giaddr to 0
770 *
771 * @param ethPacket the Ethernet packet to be processed
772 * @return Ethernet packet processed
773 */
774 private Ethernet removeRelayAgentOption(Ethernet ethPacket) {
775 Ethernet ethernet = (Ethernet) ethPacket.clone();
776 IPv4 ipv4 = (IPv4) ethernet.getPayload();
777 UDP udp = (UDP) ipv4.getPayload();
778 DHCP dhcpPayload = (DHCP) udp.getPayload();
779
780 // removes relay agent information option
781 List<DhcpOption> options = dhcpPayload.getOptions();
782 options = options.stream()
783 .filter(option -> option.getCode() != OptionCode_CircuitID.getValue())
784 .collect(Collectors.toList());
785 dhcpPayload.setOptions(options);
786 dhcpPayload.setGatewayIPAddress(0);
787
788 udp.setPayload(dhcpPayload);
789 ipv4.setPayload(udp);
790 ethernet.setPayload(ipv4);
791 return ethernet;
792 }
793
Yi Tseng51301292017-07-28 13:02:59 -0700794
795 /**
796 * Check if the host is directly connected to the network or not.
797 *
798 * @param dhcpPayload the dhcp payload
799 * @return true if the host is directly connected to the network; false otherwise
800 */
801 private boolean directlyConnected(DHCP dhcpPayload) {
Yi Tseng2cf59912017-08-24 14:47:34 -0700802 DhcpRelayAgentOption relayAgentOption =
803 (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
Yi Tseng51301292017-07-28 13:02:59 -0700804
805 // Doesn't contains relay option
806 if (relayAgentOption == null) {
807 return true;
808 }
809
Yi Tseng2cf59912017-08-24 14:47:34 -0700810 // check circuit id, if circuit id is invalid, we say it is an indirect host
811 DhcpOption circuitIdOpt = relayAgentOption.getSubOption(CIRCUIT_ID.getValue());
Yi Tseng51301292017-07-28 13:02:59 -0700812
Yi Tseng2cf59912017-08-24 14:47:34 -0700813 try {
814 CircuitId.deserialize(circuitIdOpt.getData());
Yi Tseng51301292017-07-28 13:02:59 -0700815 return true;
Yi Tseng2cf59912017-08-24 14:47:34 -0700816 } catch (Exception e) {
817 // invalid circuit id
818 return false;
Yi Tseng51301292017-07-28 13:02:59 -0700819 }
Yi Tseng51301292017-07-28 13:02:59 -0700820 }
821
822
823 /**
824 * Send the DHCP ack to the requester host.
825 * Modify Host or Route store according to the type of DHCP.
826 *
827 * @param ethernetPacketAck the packet
828 * @param dhcpPayload the DHCP data
829 */
830 private void handleDhcpAck(Ethernet ethernetPacketAck, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700831 Optional<Interface> outInterface = getClientInterface(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700832 if (!outInterface.isPresent()) {
833 log.warn("Can't find output interface for dhcp: {}", dhcpPayload);
834 return;
835 }
836
837 Interface outIface = outInterface.get();
838 HostLocation hostLocation = new HostLocation(outIface.connectPoint(), System.currentTimeMillis());
839 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
Yi Tseng4ec727d2017-08-31 11:21:00 -0700840 VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700841 if (vlanId == null) {
842 vlanId = outIface.vlan();
843 }
Yi Tseng51301292017-07-28 13:02:59 -0700844 HostId hostId = HostId.hostId(macAddress, vlanId);
845 Ip4Address ip = Ip4Address.valueOf(dhcpPayload.getYourIPAddress());
846
847 if (directlyConnected(dhcpPayload)) {
848 // Add to host store if it connect to network directly
849 Set<IpAddress> ips = Sets.newHashSet(ip);
850 HostDescription desc = new DefaultHostDescription(macAddress, vlanId,
851 hostLocation, ips);
852
853 // Replace the ip when dhcp server give the host new ip address
854 hostStore.createOrUpdateHost(DhcpRelayManager.PROVIDER_ID, hostId, desc, false);
855 } else {
856 // Add to route store if it does not connect to network directly
857 // Get gateway host IP according to host mac address
Yi Tsengdcef2c22017-08-05 20:34:06 -0700858 // TODO: remove relay store here
Yi Tseng51301292017-07-28 13:02:59 -0700859 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
860
861 if (record == null) {
862 log.warn("Can't find DHCP record of host {}", hostId);
863 return;
864 }
865
866 MacAddress gwMac = record.nextHop().orElse(null);
867 if (gwMac == null) {
868 log.warn("Can't find gateway mac address from record {}", record);
869 return;
870 }
871
872 HostId gwHostId = HostId.hostId(gwMac, record.vlanId());
873 Host gwHost = hostService.getHost(gwHostId);
874
875 if (gwHost == null) {
876 log.warn("Can't find gateway host {}", gwHostId);
877 return;
878 }
879
880 Ip4Address nextHopIp = gwHost.ipAddresses()
881 .stream()
882 .filter(IpAddress::isIp4)
883 .map(IpAddress::getIp4Address)
884 .findFirst()
885 .orElse(null);
886
887 if (nextHopIp == null) {
888 log.warn("Can't find IP address of gateway {}", gwHost);
889 return;
890 }
891
892 Route route = new Route(Route.Source.STATIC, ip.toIpPrefix(), nextHopIp);
893 routeStore.updateRoute(route);
894 }
Yi Tseng51301292017-07-28 13:02:59 -0700895 }
896
897 /**
898 * forward the packet to ConnectPoint where the DHCP server is attached.
899 *
900 * @param packet the packet
901 */
Yi Tseng4ec727d2017-08-31 11:21:00 -0700902 private void handleDhcpDiscoverAndRequest(Ethernet packet, DHCP dhcpPayload) {
903 ConnectPoint portToFotward = dhcpServerConnectPoint;
904 if (!directlyConnected(dhcpPayload) && indirectDhcpServerConnectPoint != null) {
905 portToFotward = indirectDhcpServerConnectPoint;
906 }
Yi Tseng51301292017-07-28 13:02:59 -0700907 // send packet to dhcp server connect point.
Yi Tseng4ec727d2017-08-31 11:21:00 -0700908 if (portToFotward != null) {
Yi Tseng51301292017-07-28 13:02:59 -0700909 TrafficTreatment t = DefaultTrafficTreatment.builder()
Yi Tseng4ec727d2017-08-31 11:21:00 -0700910 .setOutput(portToFotward.port()).build();
Yi Tseng51301292017-07-28 13:02:59 -0700911 OutboundPacket o = new DefaultOutboundPacket(
Yi Tseng4ec727d2017-08-31 11:21:00 -0700912 portToFotward.deviceId(), t, ByteBuffer.wrap(packet.serialize()));
Yi Tseng51301292017-07-28 13:02:59 -0700913 if (log.isTraceEnabled()) {
914 log.trace("Relaying packet to dhcp server {}", packet);
915 }
916 packetService.emit(o);
917 } else {
918 log.warn("Can't find DHCP server connect point, abort.");
919 }
920 }
921
922
923 /**
924 * Gets output interface of a dhcp packet.
925 * If option 82 exists in the dhcp packet and the option was sent by
Yi Tseng4ec727d2017-08-31 11:21:00 -0700926 * ONOS (circuit format is correct), use the connect
Yi Tseng51301292017-07-28 13:02:59 -0700927 * point and vlan id from circuit id; otherwise, find host by destination
928 * address and use vlan id from sender (dhcp server).
929 *
930 * @param ethPacket the ethernet packet
931 * @param dhcpPayload the dhcp packet
932 * @return an interface represent the output port and vlan; empty value
933 * if the host or circuit id not found
934 */
Yi Tsengdcef2c22017-08-05 20:34:06 -0700935 private Optional<Interface> getClientInterface(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tseng51301292017-07-28 13:02:59 -0700936 VlanId originalPacketVlanId = VlanId.vlanId(ethPacket.getVlanID());
Yi Tseng51301292017-07-28 13:02:59 -0700937 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
938
Yi Tseng4ec727d2017-08-31 11:21:00 -0700939 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
940 try {
941 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
942 ConnectPoint connectPoint = ConnectPoint.deviceConnectPoint(circuitId.connectPoint());
943 VlanId vlanId = circuitId.vlanId();
944 return interfaceService.getInterfacesByPort(connectPoint)
945 .stream()
946 .filter(iface -> interfaceContainsVlan(iface, vlanId))
947 .findFirst();
948 } catch (IllegalArgumentException ex) {
949 // invalid circuit format, didn't sent by ONOS
950 log.debug("Invalid circuit {}, use information from dhcp payload",
951 circuitIdSubOption.getData());
Yi Tseng51301292017-07-28 13:02:59 -0700952 }
953
954 // Use Vlan Id from DHCP server if DHCP relay circuit id was not
955 // sent by ONOS or circuit Id can't be parsed
Yi Tsengdcef2c22017-08-05 20:34:06 -0700956 // TODO: remove relay store from this method
Yi Tseng51301292017-07-28 13:02:59 -0700957 MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
958 Optional<DhcpRecord> dhcpRecord = dhcpRelayStore.getDhcpRecord(HostId.hostId(dstMac, originalPacketVlanId));
Yi Tsengdcef2c22017-08-05 20:34:06 -0700959 ConnectPoint clientConnectPoint = dhcpRecord
Yi Tseng51301292017-07-28 13:02:59 -0700960 .map(DhcpRecord::locations)
961 .orElse(Collections.emptySet())
962 .stream()
963 .reduce((hl1, hl2) -> {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700964 // find latest host connect point
Yi Tseng51301292017-07-28 13:02:59 -0700965 if (hl1 == null || hl2 == null) {
966 return hl1 == null ? hl2 : hl1;
967 }
968 return hl1.time() > hl2.time() ? hl1 : hl2;
969 })
Yi Tsengdcef2c22017-08-05 20:34:06 -0700970 .orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -0700971
Yi Tsengdcef2c22017-08-05 20:34:06 -0700972 if (clientConnectPoint != null) {
973 return interfaceService.getInterfacesByPort(clientConnectPoint)
974 .stream()
Yi Tseng4ec727d2017-08-31 11:21:00 -0700975 .filter(iface -> interfaceContainsVlan(iface, originalPacketVlanId))
Yi Tsengdcef2c22017-08-05 20:34:06 -0700976 .findFirst();
977 }
978 return Optional.empty();
Yi Tseng51301292017-07-28 13:02:59 -0700979 }
980
981 /**
982 * Send the response DHCP to the requester host.
983 *
984 * @param ethPacket the packet
985 * @param dhcpPayload the DHCP data
986 */
987 private void sendResponseToClient(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700988 Optional<Interface> outInterface = getClientInterface(ethPacket, dhcpPayload);
989 if (directlyConnected(dhcpPayload)) {
990 ethPacket = removeRelayAgentOption(ethPacket);
991 }
992 if (!outInterface.isPresent()) {
993 log.warn("Can't find output interface for client, ignore");
994 return;
995 }
996 Interface outIface = outInterface.get();
997 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
998 .setOutput(outIface.connectPoint().port())
999 .build();
1000 OutboundPacket o = new DefaultOutboundPacket(
1001 outIface.connectPoint().deviceId(),
1002 treatment,
1003 ByteBuffer.wrap(ethPacket.serialize()));
1004 if (log.isTraceEnabled()) {
1005 log.trace("Relaying packet to DHCP client {} via {}, vlan {}",
1006 ethPacket,
1007 outIface.connectPoint(),
1008 outIface.vlan());
1009 }
1010 packetService.emit(o);
Yi Tseng51301292017-07-28 13:02:59 -07001011 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001012
1013 class InternalHostListener implements HostListener {
1014 @Override
1015 public void event(HostEvent event) {
1016 switch (event.type()) {
1017 case HOST_ADDED:
1018 case HOST_UPDATED:
1019 hostUpdated(event.subject());
1020 break;
1021 case HOST_REMOVED:
1022 hostRemoved(event.subject());
1023 break;
1024 case HOST_MOVED:
1025 hostMoved(event.subject());
1026 break;
1027 default:
1028 break;
1029 }
1030 }
1031 }
1032
1033 /**
1034 * Handle host move.
1035 * If the host DHCP server or gateway and it moved to the location different
1036 * to user configured, unsets the connect mac and vlan
1037 *
1038 * @param host the host
1039 */
1040 private void hostMoved(Host host) {
Yi Tsenge72fbb52017-08-02 15:03:31 -07001041 if (this.dhcpGatewayIp != null) {
1042 if (host.ipAddresses().contains(this.dhcpGatewayIp) &&
1043 !host.locations().contains(this.dhcpServerConnectPoint)) {
1044 this.dhcpConnectMac = null;
1045 this.dhcpConnectVlan = null;
1046 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001047 }
1048 if (this.dhcpServerIp != null) {
1049 if (host.ipAddresses().contains(this.dhcpServerIp) &&
1050 !host.locations().contains(this.dhcpServerConnectPoint)) {
1051 this.dhcpConnectMac = null;
1052 this.dhcpConnectVlan = null;
1053 }
1054 }
Yi Tseng4ec727d2017-08-31 11:21:00 -07001055 if (this.indirectDhcpGatewayIp != null) {
1056 if (host.ipAddresses().contains(this.indirectDhcpGatewayIp) &&
1057 !host.locations().contains(this.indirectDhcpServerConnectPoint)) {
1058 this.indirectDhcpConnectMac = null;
1059 this.indirectDhcpConnectVlan = null;
1060 }
1061 }
1062 if (this.indirectDhcpServerIp != null) {
1063 if (host.ipAddresses().contains(this.indirectDhcpServerIp) &&
1064 !host.locations().contains(this.indirectDhcpServerConnectPoint)) {
1065 this.indirectDhcpConnectMac = null;
1066 this.indirectDhcpConnectVlan = null;
1067 }
1068 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001069 }
1070
1071 /**
1072 * Handle host updated.
1073 * If the host is DHCP server or gateway, update connect mac and vlan.
1074 *
1075 * @param host the host
1076 */
1077 private void hostUpdated(Host host) {
1078 if (this.dhcpGatewayIp != null) {
1079 if (host.ipAddresses().contains(this.dhcpGatewayIp)) {
1080 this.dhcpConnectMac = host.mac();
1081 this.dhcpConnectVlan = host.vlan();
1082 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001083 }
1084 if (this.dhcpServerIp != null) {
1085 if (host.ipAddresses().contains(this.dhcpServerIp)) {
1086 this.dhcpConnectMac = host.mac();
1087 this.dhcpConnectVlan = host.vlan();
1088 }
1089 }
Yi Tseng4ec727d2017-08-31 11:21:00 -07001090 if (this.indirectDhcpGatewayIp != null) {
1091 if (host.ipAddresses().contains(this.indirectDhcpGatewayIp)) {
1092 this.indirectDhcpConnectMac = host.mac();
1093 this.indirectDhcpConnectVlan = host.vlan();
1094 }
1095 }
1096 if (this.indirectDhcpServerIp != null) {
1097 if (host.ipAddresses().contains(this.indirectDhcpServerIp)) {
1098 this.indirectDhcpConnectMac = host.mac();
1099 this.indirectDhcpConnectVlan = host.vlan();
1100 }
1101 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001102 }
1103
1104 /**
1105 * Handle host removed.
1106 * If the host is DHCP server or gateway, unset connect mac and vlan.
1107 *
1108 * @param host the host
1109 */
1110 private void hostRemoved(Host host) {
1111 if (this.dhcpGatewayIp != null) {
1112 if (host.ipAddresses().contains(this.dhcpGatewayIp)) {
1113 this.dhcpConnectMac = null;
1114 this.dhcpConnectVlan = null;
1115 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001116 }
1117 if (this.dhcpServerIp != null) {
1118 if (host.ipAddresses().contains(this.dhcpServerIp)) {
1119 this.dhcpConnectMac = null;
1120 this.dhcpConnectVlan = null;
1121 }
1122 }
Yi Tseng4ec727d2017-08-31 11:21:00 -07001123 if (this.indirectDhcpGatewayIp != null) {
1124 if (host.ipAddresses().contains(this.indirectDhcpGatewayIp)) {
1125 this.indirectDhcpConnectMac = null;
1126 this.indirectDhcpConnectVlan = null;
1127 }
1128 }
1129 if (this.indirectDhcpServerIp != null) {
1130 if (host.ipAddresses().contains(this.indirectDhcpServerIp)) {
1131 this.indirectDhcpConnectMac = null;
1132 this.indirectDhcpConnectVlan = null;
1133 }
1134 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001135 }
Yi Tseng51301292017-07-28 13:02:59 -07001136}