blob: 235b4c52ef05154928d49c4b8432f1517c9b4dc7 [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;
Yi Tseng4b013202017-09-08 17:22:51 -070047import org.onosproject.net.host.HostProvider;
48import org.onosproject.net.host.HostProviderRegistry;
49import org.onosproject.net.host.HostProviderService;
Ray Milkeyfacf2862017-08-03 11:58:29 -070050import org.onosproject.net.intf.Interface;
51import org.onosproject.net.intf.InterfaceService;
Yi Tseng4b013202017-09-08 17:22:51 -070052import org.onosproject.net.provider.ProviderId;
Ray Milkey69ec8712017-08-08 13:00:43 -070053import org.onosproject.routeservice.Route;
54import org.onosproject.routeservice.RouteStore;
Yi Tseng51301292017-07-28 13:02:59 -070055import org.onosproject.net.ConnectPoint;
56import org.onosproject.net.Host;
57import org.onosproject.net.HostId;
58import org.onosproject.net.HostLocation;
59import org.onosproject.net.flow.DefaultTrafficTreatment;
60import org.onosproject.net.flow.TrafficTreatment;
61import org.onosproject.net.host.DefaultHostDescription;
62import org.onosproject.net.host.HostDescription;
63import org.onosproject.net.host.HostService;
Yi Tseng51301292017-07-28 13:02:59 -070064import org.onosproject.net.host.InterfaceIpAddress;
65import org.onosproject.net.packet.DefaultOutboundPacket;
66import org.onosproject.net.packet.OutboundPacket;
67import org.onosproject.net.packet.PacketContext;
68import org.onosproject.net.packet.PacketService;
69import org.slf4j.Logger;
70import org.slf4j.LoggerFactory;
71
72import java.nio.ByteBuffer;
Yi Tsengdcef2c22017-08-05 20:34:06 -070073import java.util.Collection;
Yi Tseng51301292017-07-28 13:02:59 -070074import java.util.Collections;
75import java.util.List;
76import java.util.Optional;
77import java.util.Set;
78import java.util.stream.Collectors;
79
80import static com.google.common.base.Preconditions.checkNotNull;
81import static com.google.common.base.Preconditions.checkState;
82import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_CircuitID;
83import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_END;
84import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
85import static org.onlab.packet.MacAddress.valueOf;
86import static org.onlab.packet.dhcp.DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID;
87
88@Component
89@Service
90@Property(name = "version", value = "4")
Yi Tseng4b013202017-09-08 17:22:51 -070091public class Dhcp4HandlerImpl implements DhcpHandler, HostProvider {
Yi Tseng51301292017-07-28 13:02:59 -070092 private static Logger log = LoggerFactory.getLogger(Dhcp4HandlerImpl.class);
93
94 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
95 protected DhcpRelayStore dhcpRelayStore;
96
97 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
98 protected PacketService packetService;
99
100 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Yi Tseng51301292017-07-28 13:02:59 -0700101 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 Tseng4b013202017-09-08 17:22:51 -0700109 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
110 protected HostProviderRegistry providerRegistry;
111
112 protected HostProviderService providerService;
Yi Tsenge72fbb52017-08-02 15:03:31 -0700113 private InternalHostListener hostListener = new InternalHostListener();
114
Yi Tseng51301292017-07-28 13:02:59 -0700115 private Ip4Address dhcpServerIp = null;
116 // dhcp server may be connected directly to the SDN network or
117 // via an external gateway. When connected directly, the dhcpConnectPoint, dhcpConnectMac,
118 // and dhcpConnectVlan refer to the server. When connected via the gateway, they refer
119 // to the gateway.
120 private ConnectPoint dhcpServerConnectPoint = null;
121 private MacAddress dhcpConnectMac = null;
122 private VlanId dhcpConnectVlan = null;
123 private Ip4Address dhcpGatewayIp = null;
Yi Tseng4fa05832017-08-17 13:08:31 -0700124 private Ip4Address relayAgentIp = null;
Yi Tseng51301292017-07-28 13:02:59 -0700125
Yi Tseng4ec727d2017-08-31 11:21:00 -0700126 // Indirect case DHCP server
127 private Ip4Address indirectDhcpServerIp = null;
128 private ConnectPoint indirectDhcpServerConnectPoint = null;
129 private MacAddress indirectDhcpConnectMac = null;
130 private VlanId indirectDhcpConnectVlan = null;
131 private Ip4Address indirectDhcpGatewayIp = null;
132 private Ip4Address indirectRelayAgentIp = null;
133
Yi Tsenge72fbb52017-08-02 15:03:31 -0700134 @Activate
135 protected void activate() {
136 hostService.addListener(hostListener);
Yi Tseng4b013202017-09-08 17:22:51 -0700137 providerService = providerRegistry.register(this);
Yi Tsenge72fbb52017-08-02 15:03:31 -0700138 }
139
140 @Deactivate
141 protected void deactivate() {
Yi Tseng4b013202017-09-08 17:22:51 -0700142 providerRegistry.unregister(this);
143 hostService.removeListener(hostListener);
144 this.dhcpConnectMac = null;
145 this.dhcpConnectVlan = null;
146
Yi Tseng4ec727d2017-08-31 11:21:00 -0700147 if (dhcpGatewayIp != null) {
148 hostService.stopMonitoringIp(dhcpGatewayIp);
149 }
150 if (dhcpServerIp != null) {
151 hostService.stopMonitoringIp(dhcpServerIp);
152 }
153
154 if (indirectDhcpGatewayIp != null) {
155 hostService.stopMonitoringIp(indirectDhcpGatewayIp);
156 }
157 if (indirectDhcpServerIp != null) {
158 hostService.stopMonitoringIp(indirectDhcpServerIp);
159 }
160
Yi Tsenge72fbb52017-08-02 15:03:31 -0700161 hostService.removeListener(hostListener);
162 this.dhcpConnectMac = null;
163 this.dhcpConnectVlan = null;
Yi Tseng4ec727d2017-08-31 11:21:00 -0700164 this.indirectDhcpConnectMac = null;
165 this.indirectDhcpConnectVlan = null;
Yi Tsenge72fbb52017-08-02 15:03:31 -0700166 }
167
Yi Tseng51301292017-07-28 13:02:59 -0700168 @Override
169 public void setDhcpServerIp(IpAddress dhcpServerIp) {
170 checkNotNull(dhcpServerIp, "DHCP server IP can't be null");
171 checkState(dhcpServerIp.isIp4(), "Invalid server IP for DHCPv4 relay handler");
172 this.dhcpServerIp = dhcpServerIp.getIp4Address();
173 }
174
175 @Override
176 public void setDhcpServerConnectPoint(ConnectPoint dhcpServerConnectPoint) {
177 checkNotNull(dhcpServerConnectPoint, "Server connect point can't null");
178 this.dhcpServerConnectPoint = dhcpServerConnectPoint;
179 }
180
181 @Override
182 public void setDhcpConnectMac(MacAddress dhcpConnectMac) {
183 this.dhcpConnectMac = dhcpConnectMac;
184 }
185
186 @Override
187 public void setDhcpConnectVlan(VlanId dhcpConnectVlan) {
188 this.dhcpConnectVlan = dhcpConnectVlan;
189 }
190
191 @Override
192 public void setDhcpGatewayIp(IpAddress dhcpGatewayIp) {
193 if (dhcpGatewayIp != null) {
194 checkState(dhcpGatewayIp.isIp4(), "Invalid gateway IP for DHCPv4 relay handler");
195 this.dhcpGatewayIp = dhcpGatewayIp.getIp4Address();
196 } else {
197 // removes gateway config
198 this.dhcpGatewayIp = null;
199 }
200 }
201
202 @Override
203 public Optional<IpAddress> getDhcpServerIp() {
204 return Optional.ofNullable(dhcpServerIp);
205 }
206
207 @Override
208 public Optional<IpAddress> getDhcpGatewayIp() {
209 return Optional.ofNullable(dhcpGatewayIp);
210 }
211
212 @Override
213 public Optional<MacAddress> getDhcpConnectMac() {
214 return Optional.ofNullable(dhcpConnectMac);
215 }
216
217 @Override
Yi Tsenge72fbb52017-08-02 15:03:31 -0700218 public void setDefaultDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
219 if (configs.size() == 0) {
220 // no config to update
221 return;
222 }
223
224 // TODO: currently we pick up first DHCP server config.
225 // Will use other server configs in the future for HA.
226 DhcpServerConfig serverConfig = configs.iterator().next();
Yi Tsengaefbb002017-09-08 16:23:32 -0700227 if (!serverConfig.getDhcpServerConnectPoint().isPresent()) {
228 log.warn("Connect point from server config not exists");
229 return;
230 }
231 if (!serverConfig.getDhcpServerIp4().isPresent()) {
232 log.warn("IP from DHCP server config not exists");
233 return;
234 }
Yi Tsenge72fbb52017-08-02 15:03:31 -0700235 Ip4Address oldServerIp = this.dhcpServerIp;
236 Ip4Address oldGatewayIp = this.dhcpGatewayIp;
237
238 // stop monitoring gateway or server
239 if (oldGatewayIp != null) {
240 hostService.stopMonitoringIp(oldGatewayIp);
241 } else if (oldServerIp != null) {
242 hostService.stopMonitoringIp(oldServerIp);
243 }
244
245 this.dhcpServerConnectPoint = serverConfig.getDhcpServerConnectPoint().get();
246 this.dhcpServerIp = serverConfig.getDhcpServerIp4().get();
247 this.dhcpGatewayIp = serverConfig.getDhcpGatewayIp4().orElse(null);
248
249 // reset server mac and vlan
250 this.dhcpConnectMac = null;
251 this.dhcpConnectVlan = null;
252
253 log.info("DHCP server connect point: " + this.dhcpServerConnectPoint);
254 log.info("DHCP server IP: " + this.dhcpServerIp);
255
256 IpAddress ipToProbe = MoreObjects.firstNonNull(this.dhcpGatewayIp, this.dhcpServerIp);
257 String hostToProbe = this.dhcpGatewayIp != null ? "gateway" : "DHCP server";
258
259 if (ipToProbe == null) {
260 log.warn("Server IP not set, can't probe it");
261 return;
262 }
263
264 log.info("Probing to resolve {} IP {}", hostToProbe, ipToProbe);
265 hostService.startMonitoringIp(ipToProbe);
266
267 Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
268 if (!hosts.isEmpty()) {
269 Host host = hosts.iterator().next();
270 this.dhcpConnectVlan = host.vlan();
271 this.dhcpConnectMac = host.mac();
272 }
Yi Tseng4fa05832017-08-17 13:08:31 -0700273
274 this.relayAgentIp = serverConfig.getRelayAgentIp4().orElse(null);
Yi Tsenge72fbb52017-08-02 15:03:31 -0700275 }
276
277 @Override
278 public void setIndirectDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
Yi Tseng4ec727d2017-08-31 11:21:00 -0700279 if (configs.size() == 0) {
280 // no config to update
281 return;
282 }
283
284 // TODO: currently we pick up first indirect DHCP server config.
285 // Will use other server configs in the future for HA.
286 DhcpServerConfig serverConfig = configs.iterator().next();
287 checkState(serverConfig.getDhcpServerConnectPoint().isPresent(),
288 "Connect point not exists");
289 checkState(serverConfig.getDhcpServerIp4().isPresent(),
290 "IP of DHCP server not exists");
291 Ip4Address oldServerIp = this.indirectDhcpServerIp;
292 Ip4Address oldGatewayIp = this.indirectDhcpGatewayIp;
293
294 // stop monitoring gateway or server
295 if (oldGatewayIp != null) {
296 hostService.stopMonitoringIp(oldGatewayIp);
297 } else if (oldServerIp != null) {
298 hostService.stopMonitoringIp(oldServerIp);
299 }
300
301 this.indirectDhcpServerConnectPoint = serverConfig.getDhcpServerConnectPoint().get();
302 this.indirectDhcpServerIp = serverConfig.getDhcpServerIp4().get();
303 this.indirectDhcpGatewayIp = serverConfig.getDhcpGatewayIp4().orElse(null);
304
305 // reset server mac and vlan
306 this.indirectDhcpConnectMac = null;
307 this.indirectDhcpConnectVlan = null;
308
309 log.info("Indirect DHCP server connect point: " + this.indirectDhcpServerConnectPoint);
310 log.info("Indirect DHCP server IP: " + this.indirectDhcpServerIp);
311
312 IpAddress ipToProbe = MoreObjects.firstNonNull(this.indirectDhcpGatewayIp, this.indirectDhcpServerIp);
313 String hostToProbe = this.indirectDhcpGatewayIp != null ? "gateway" : "DHCP server";
314
315 log.info("Probing to resolve {} IP {}", hostToProbe, ipToProbe);
316 hostService.startMonitoringIp(ipToProbe);
317
318 Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
319 if (!hosts.isEmpty()) {
320 Host host = hosts.iterator().next();
321 this.indirectDhcpConnectVlan = host.vlan();
322 this.indirectDhcpConnectMac = host.mac();
323 }
324
325 this.indirectRelayAgentIp = serverConfig.getRelayAgentIp4().orElse(null);
Yi Tsenge72fbb52017-08-02 15:03:31 -0700326 }
327
Yi Tseng4fa05832017-08-17 13:08:31 -0700328 @Override
Yi Tseng51301292017-07-28 13:02:59 -0700329 public void processDhcpPacket(PacketContext context, BasePacket payload) {
330 checkNotNull(payload, "DHCP payload can't be null");
331 checkState(payload instanceof DHCP, "Payload is not a DHCP");
332 DHCP dhcpPayload = (DHCP) payload;
333 if (!configured()) {
334 log.warn("Missing DHCP relay server config. Abort packet processing");
335 return;
336 }
337
338 ConnectPoint inPort = context.inPacket().receivedFrom();
Yi Tseng51301292017-07-28 13:02:59 -0700339 checkNotNull(dhcpPayload, "Can't find DHCP payload");
340 Ethernet packet = context.inPacket().parsed();
341 DHCP.MsgType incomingPacketType = dhcpPayload.getOptions().stream()
342 .filter(dhcpOption -> dhcpOption.getCode() == OptionCode_MessageType.getValue())
343 .map(DhcpOption::getData)
344 .map(data -> DHCP.MsgType.getType(data[0]))
345 .findFirst()
346 .orElse(null);
347 checkNotNull(incomingPacketType, "Can't get message type from DHCP payload {}", dhcpPayload);
348 switch (incomingPacketType) {
349 case DHCPDISCOVER:
Yi Tsengdcef2c22017-08-05 20:34:06 -0700350 // Add the gateway IP as virtual interface IP for server to understand
Yi Tseng51301292017-07-28 13:02:59 -0700351 // the lease to be assigned and forward the packet to dhcp server.
352 Ethernet ethernetPacketDiscover =
Yi Tsengdcef2c22017-08-05 20:34:06 -0700353 processDhcpPacketFromClient(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700354 if (ethernetPacketDiscover != null) {
355 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700356 handleDhcpDiscoverAndRequest(ethernetPacketDiscover, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700357 }
358 break;
359 case DHCPOFFER:
360 //reply to dhcp client.
361 Ethernet ethernetPacketOffer = processDhcpPacketFromServer(packet);
362 if (ethernetPacketOffer != null) {
363 writeResponseDhcpRecord(ethernetPacketOffer, dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700364 sendResponseToClient(ethernetPacketOffer, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700365 }
366 break;
367 case DHCPREQUEST:
368 // add the gateway ip as virtual interface ip for server to understand
369 // the lease to be assigned and forward the packet to dhcp server.
370 Ethernet ethernetPacketRequest =
Yi Tsengdcef2c22017-08-05 20:34:06 -0700371 processDhcpPacketFromClient(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700372 if (ethernetPacketRequest != null) {
373 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700374 handleDhcpDiscoverAndRequest(ethernetPacketRequest, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700375 }
376 break;
377 case DHCPACK:
378 // reply to dhcp client.
379 Ethernet ethernetPacketAck = processDhcpPacketFromServer(packet);
380 if (ethernetPacketAck != null) {
381 writeResponseDhcpRecord(ethernetPacketAck, dhcpPayload);
382 handleDhcpAck(ethernetPacketAck, dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700383 sendResponseToClient(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700384 }
385 break;
386 case DHCPRELEASE:
387 // TODO: release the ip address from client
388 break;
389 default:
390 break;
391 }
392 }
393
394 /**
395 * Checks if this app has been configured.
396 *
397 * @return true if all information we need have been initialized
398 */
Yi Tseng4ec727d2017-08-31 11:21:00 -0700399 private boolean configured() {
Yi Tseng51301292017-07-28 13:02:59 -0700400 return dhcpServerConnectPoint != null && dhcpServerIp != null;
401 }
402
403 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -0700404 * Returns the first interface ip from interface.
Yi Tseng51301292017-07-28 13:02:59 -0700405 *
Yi Tsengdcef2c22017-08-05 20:34:06 -0700406 * @param iface interface of one connect point
Yi Tseng51301292017-07-28 13:02:59 -0700407 * @return the first interface IP; null if not exists an IP address in
408 * these interfaces
409 */
Yi Tseng4fa05832017-08-17 13:08:31 -0700410 private Ip4Address getFirstIpFromInterface(Interface iface) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700411 checkNotNull(iface, "Interface can't be null");
412 return iface.ipAddressesList().stream()
Yi Tseng51301292017-07-28 13:02:59 -0700413 .map(InterfaceIpAddress::ipAddress)
414 .filter(IpAddress::isIp4)
415 .map(IpAddress::getIp4Address)
416 .findFirst()
417 .orElse(null);
418 }
419
420 /**
Yi Tseng4ec727d2017-08-31 11:21:00 -0700421 * Gets Interface facing to the server for default host.
Yi Tsengdcef2c22017-08-05 20:34:06 -0700422 *
423 * @return the Interface facing to the server; null if not found
424 */
Yi Tseng4ec727d2017-08-31 11:21:00 -0700425 private Interface getServerInterface() {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700426 if (dhcpServerConnectPoint == null || dhcpConnectVlan == null) {
427 return null;
428 }
429 return interfaceService.getInterfacesByPort(dhcpServerConnectPoint)
430 .stream()
Yi Tseng4ec727d2017-08-31 11:21:00 -0700431 .filter(iface -> interfaceContainsVlan(iface, dhcpConnectVlan))
Yi Tsengdcef2c22017-08-05 20:34:06 -0700432 .findFirst()
433 .orElse(null);
434 }
435
436 /**
Yi Tseng4ec727d2017-08-31 11:21:00 -0700437 * Gets Interface facing to the server for indirect hosts.
438 * Use default server Interface if indirect server not configured.
439 *
440 * @return the Interface facing to the server; null if not found
441 */
442 private Interface getIndirectServerInterface() {
443 if (indirectDhcpServerConnectPoint == null || indirectDhcpConnectVlan == null) {
444 return getServerInterface();
445 }
446 return interfaceService.getInterfacesByPort(indirectDhcpServerConnectPoint)
447 .stream()
448 .filter(iface -> interfaceContainsVlan(iface, indirectDhcpConnectVlan))
449 .findFirst()
450 .orElse(null);
451 }
452
453 /**
454 * Determind if an Interface contains a vlan id.
455 *
456 * @param iface the Interface
457 * @param vlanId the vlan id
458 * @return true if the Interface contains the vlan id
459 */
460 private boolean interfaceContainsVlan(Interface iface, VlanId vlanId) {
461 return iface.vlan().equals(vlanId) ||
462 iface.vlanUntagged().equals(vlanId) ||
463 iface.vlanTagged().contains(vlanId) ||
464 iface.vlanNative().equals(vlanId);
465 }
466
467 /**
Yi Tseng51301292017-07-28 13:02:59 -0700468 * Build the DHCP discover/request packet with gateway IP(unicast packet).
469 *
470 * @param context the packet context
471 * @param ethernetPacket the ethernet payload to process
Yi Tseng51301292017-07-28 13:02:59 -0700472 * @return processed packet
473 */
474 private Ethernet processDhcpPacketFromClient(PacketContext context,
Yi Tsengdcef2c22017-08-05 20:34:06 -0700475 Ethernet ethernetPacket) {
Yi Tseng4ec727d2017-08-31 11:21:00 -0700476 // get dhcp header.
477 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
478 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
479 UDP udpPacket = (UDP) ipv4Packet.getPayload();
480 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
481
Yi Tsengdcef2c22017-08-05 20:34:06 -0700482 Ip4Address clientInterfaceIp =
483 interfaceService.getInterfacesByPort(context.inPacket().receivedFrom())
484 .stream()
485 .map(Interface::ipAddressesList)
486 .flatMap(Collection::stream)
487 .map(InterfaceIpAddress::ipAddress)
488 .filter(IpAddress::isIp4)
489 .map(IpAddress::getIp4Address)
490 .findFirst()
491 .orElse(null);
492 if (clientInterfaceIp == null) {
493 log.warn("Can't find interface IP for client interface for port {}",
494 context.inPacket().receivedFrom());
495 return null;
496 }
Yi Tseng4ec727d2017-08-31 11:21:00 -0700497 boolean isDirectlyConnected = directlyConnected(dhcpPacket);
498 Interface serverInterface = isDirectlyConnected ? getServerInterface() : getIndirectServerInterface();
Yi Tsengdcef2c22017-08-05 20:34:06 -0700499 if (serverInterface == null) {
Yi Tseng4ec727d2017-08-31 11:21:00 -0700500 log.warn("Can't get {} server interface, ignore", isDirectlyConnected ? "direct" : "indirect");
Yi Tsengdcef2c22017-08-05 20:34:06 -0700501 return null;
502 }
Yi Tseng4fa05832017-08-17 13:08:31 -0700503 Ip4Address ipFacingServer = getFirstIpFromInterface(serverInterface);
504 MacAddress macFacingServer = serverInterface.mac();
505 if (ipFacingServer == null || macFacingServer == null) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700506 log.warn("No IP address for server Interface {}", serverInterface);
Yi Tseng51301292017-07-28 13:02:59 -0700507 return null;
508 }
509 if (dhcpConnectMac == null) {
510 log.warn("DHCP {} not yet resolved .. Aborting DHCP "
511 + "packet processing from client on port: {}",
512 (dhcpGatewayIp == null) ? "server IP " + dhcpServerIp
513 : "gateway IP " + dhcpGatewayIp,
Yi Tsengdcef2c22017-08-05 20:34:06 -0700514 context.inPacket().receivedFrom());
Yi Tseng51301292017-07-28 13:02:59 -0700515 return null;
516 }
Yi Tseng4ec727d2017-08-31 11:21:00 -0700517
Yi Tseng4fa05832017-08-17 13:08:31 -0700518 etherReply.setSourceMACAddress(macFacingServer);
Yi Tseng51301292017-07-28 13:02:59 -0700519 etherReply.setDestinationMACAddress(dhcpConnectMac);
520 etherReply.setVlanID(dhcpConnectVlan.toShort());
Yi Tseng4fa05832017-08-17 13:08:31 -0700521 ipv4Packet.setSourceAddress(ipFacingServer.toInt());
Yi Tseng51301292017-07-28 13:02:59 -0700522 ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
Yi Tseng51301292017-07-28 13:02:59 -0700523
Yi Tseng4ec727d2017-08-31 11:21:00 -0700524 if (isDirectlyConnected) {
Yi Tseng51301292017-07-28 13:02:59 -0700525 ConnectPoint inPort = context.inPacket().receivedFrom();
526 VlanId vlanId = VlanId.vlanId(ethernetPacket.getVlanID());
527 // add connected in port and vlan
528 CircuitId cid = new CircuitId(inPort.toString(), vlanId);
529 byte[] circuitId = cid.serialize();
530 DhcpOption circuitIdSubOpt = new DhcpOption();
531 circuitIdSubOpt
532 .setCode(CIRCUIT_ID.getValue())
533 .setLength((byte) circuitId.length)
534 .setData(circuitId);
535
536 DhcpRelayAgentOption newRelayAgentOpt = new DhcpRelayAgentOption();
537 newRelayAgentOpt.setCode(OptionCode_CircuitID.getValue());
538 newRelayAgentOpt.addSubOption(circuitIdSubOpt);
539
540 // Removes END option first
541 List<DhcpOption> options = dhcpPacket.getOptions().stream()
542 .filter(opt -> opt.getCode() != OptionCode_END.getValue())
543 .collect(Collectors.toList());
544
545 // push relay agent option
546 options.add(newRelayAgentOpt);
547
548 // make sure option 255(End) is the last option
549 DhcpOption endOption = new DhcpOption();
550 endOption.setCode(OptionCode_END.getValue());
551 options.add(endOption);
552
553 dhcpPacket.setOptions(options);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700554
555 // Sets giaddr to IP address from the Interface which facing to
556 // DHCP client
557 dhcpPacket.setGatewayIPAddress(clientInterfaceIp.toInt());
Yi Tseng51301292017-07-28 13:02:59 -0700558
Yi Tseng4ec727d2017-08-31 11:21:00 -0700559 // replace giaddr if relay agent IP is set
560 if (relayAgentIp != null) {
561 dhcpPacket.setGatewayIPAddress(relayAgentIp.toInt());
562 }
563 } else if (indirectDhcpServerIp != null) {
564 // Indirect case, replace destination to indirect dhcp server if exist
565 etherReply.setDestinationMACAddress(indirectDhcpConnectMac);
566 etherReply.setVlanID(indirectDhcpConnectVlan.toShort());
567 ipv4Packet.setDestinationAddress(indirectDhcpServerIp.toInt());
568
569 // replace giaddr if relay agent IP is set
570 if (indirectRelayAgentIp != null) {
571 dhcpPacket.setGatewayIPAddress(relayAgentIp.toInt());
572 }
Yi Tseng4fa05832017-08-17 13:08:31 -0700573 }
574
Yi Tseng51301292017-07-28 13:02:59 -0700575 udpPacket.setPayload(dhcpPacket);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700576 // As a DHCP relay, the source port should be server port( instead
577 // of client port.
Yi Tsengdcef2c22017-08-05 20:34:06 -0700578 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
Yi Tseng51301292017-07-28 13:02:59 -0700579 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
580 ipv4Packet.setPayload(udpPacket);
581 etherReply.setPayload(ipv4Packet);
582 return etherReply;
583 }
584
585 /**
586 * Writes DHCP record to the store according to the request DHCP packet (Discover, Request).
587 *
588 * @param location the location which DHCP packet comes from
589 * @param ethernet the DHCP packet
590 * @param dhcpPayload the DHCP payload
591 */
592 private void writeRequestDhcpRecord(ConnectPoint location,
593 Ethernet ethernet,
594 DHCP dhcpPayload) {
595 VlanId vlanId = VlanId.vlanId(ethernet.getVlanID());
596 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
597 HostId hostId = HostId.hostId(macAddress, vlanId);
598 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
599 if (record == null) {
600 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
601 } else {
602 record = record.clone();
603 }
604 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
605 record.ip4Status(dhcpPayload.getPacketType());
606 record.setDirectlyConnected(directlyConnected(dhcpPayload));
607 if (!directlyConnected(dhcpPayload)) {
608 // Update gateway mac address if the host is not directly connected
609 record.nextHop(ethernet.getSourceMAC());
610 }
611 record.updateLastSeen();
612 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
613 }
614
615 /**
616 * Writes DHCP record to the store according to the response DHCP packet (Offer, Ack).
617 *
618 * @param ethernet the DHCP packet
619 * @param dhcpPayload the DHCP payload
620 */
621 private void writeResponseDhcpRecord(Ethernet ethernet,
622 DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700623 Optional<Interface> outInterface = getClientInterface(ethernet, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700624 if (!outInterface.isPresent()) {
625 log.warn("Failed to determine where to send {}", dhcpPayload.getPacketType());
626 return;
627 }
628
629 Interface outIface = outInterface.get();
630 ConnectPoint location = outIface.connectPoint();
Yi Tseng4ec727d2017-08-31 11:21:00 -0700631 VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700632 if (vlanId == null) {
633 vlanId = outIface.vlan();
634 }
Yi Tseng51301292017-07-28 13:02:59 -0700635 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
636 HostId hostId = HostId.hostId(macAddress, vlanId);
637 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
638 if (record == null) {
639 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
640 } else {
641 record = record.clone();
642 }
643 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
644 if (dhcpPayload.getPacketType() == DHCP.MsgType.DHCPACK) {
645 record.ip4Address(Ip4Address.valueOf(dhcpPayload.getYourIPAddress()));
646 }
647 record.ip4Status(dhcpPayload.getPacketType());
648 record.setDirectlyConnected(directlyConnected(dhcpPayload));
649 record.updateLastSeen();
650 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
651 }
652
653 /**
654 * Build the DHCP offer/ack with proper client port.
655 *
656 * @param ethernetPacket the original packet comes from server
657 * @return new packet which will send to the client
658 */
659 private Ethernet processDhcpPacketFromServer(Ethernet ethernetPacket) {
660 // get dhcp header.
661 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
662 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
663 UDP udpPacket = (UDP) ipv4Packet.getPayload();
664 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
665
666 // determine the vlanId of the client host - note that this vlan id
667 // could be different from the vlan in the packet from the server
Yi Tsengdcef2c22017-08-05 20:34:06 -0700668 Interface clientInterface = getClientInterface(ethernetPacket, dhcpPayload).orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -0700669
Yi Tsengdcef2c22017-08-05 20:34:06 -0700670 if (clientInterface == null) {
Yi Tseng51301292017-07-28 13:02:59 -0700671 log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
672 return null;
673 }
Yi Tsengdcef2c22017-08-05 20:34:06 -0700674 VlanId vlanId;
675 if (clientInterface.vlanTagged().isEmpty()) {
676 vlanId = clientInterface.vlan();
677 } else {
678 // might be multiple vlan in same interface
Yi Tseng4ec727d2017-08-31 11:21:00 -0700679 vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700680 }
681 if (vlanId == null) {
682 vlanId = VlanId.NONE;
683 }
684 etherReply.setVlanID(vlanId.toShort());
685 etherReply.setSourceMACAddress(clientInterface.mac());
Yi Tseng51301292017-07-28 13:02:59 -0700686
Yi Tsengdcef2c22017-08-05 20:34:06 -0700687 if (!directlyConnected(dhcpPayload)) {
688 // if client is indirectly connected, try use next hop mac address
689 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
690 HostId hostId = HostId.hostId(macAddress, vlanId);
691 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
692 if (record != null) {
693 // if next hop can be found, use mac address of next hop
694 record.nextHop().ifPresent(etherReply::setDestinationMACAddress);
695 } else {
696 // otherwise, discard the packet
697 log.warn("Can't find record for host id {}, discard packet", hostId);
698 return null;
699 }
Yi Tsengc03fa242017-08-17 17:43:38 -0700700 } else {
701 etherReply.setDestinationMACAddress(dhcpPayload.getClientHardwareAddress());
Yi Tsengdcef2c22017-08-05 20:34:06 -0700702 }
703
Yi Tseng51301292017-07-28 13:02:59 -0700704 // we leave the srcMac from the original packet
Yi Tseng51301292017-07-28 13:02:59 -0700705 // figure out the relay agent IP corresponding to the original request
Yi Tseng4fa05832017-08-17 13:08:31 -0700706 Ip4Address ipFacingClient = getFirstIpFromInterface(clientInterface);
707 if (ipFacingClient == null) {
Yi Tseng51301292017-07-28 13:02:59 -0700708 log.warn("Cannot determine relay agent interface Ipv4 addr for host {}/{}. "
709 + "Aborting relay for dhcp packet from server {}",
Yi Tsengdcef2c22017-08-05 20:34:06 -0700710 etherReply.getDestinationMAC(), clientInterface.vlan(),
Yi Tseng51301292017-07-28 13:02:59 -0700711 ethernetPacket);
712 return null;
713 }
714 // SRC_IP: relay agent IP
715 // DST_IP: offered IP
Yi Tseng4fa05832017-08-17 13:08:31 -0700716 ipv4Packet.setSourceAddress(ipFacingClient.toInt());
Yi Tseng51301292017-07-28 13:02:59 -0700717 ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
718 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
719 if (directlyConnected(dhcpPayload)) {
720 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
721 } else {
722 // forward to another dhcp relay
723 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
724 }
725
726 udpPacket.setPayload(dhcpPayload);
727 ipv4Packet.setPayload(udpPacket);
728 etherReply.setPayload(ipv4Packet);
729 return etherReply;
730 }
731
Yi Tsengdcef2c22017-08-05 20:34:06 -0700732 /**
733 * Extracts VLAN ID from relay agent option.
734 *
735 * @param dhcpPayload the DHCP payload
736 * @return VLAN ID from DHCP payload; null if not exists
737 */
Yi Tseng4ec727d2017-08-31 11:21:00 -0700738 private VlanId getVlanIdFromRelayAgentOption(DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700739 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
740 if (option == null) {
741 return null;
742 }
743 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
744 if (circuitIdSubOption == null) {
745 return null;
746 }
747 try {
748 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
749 return circuitId.vlanId();
750 } catch (IllegalArgumentException e) {
751 // can't deserialize the circuit ID
752 return null;
753 }
754 }
755
756 /**
757 * Removes DHCP relay agent information option (option 82) from DHCP payload.
758 * Also reset giaddr to 0
759 *
760 * @param ethPacket the Ethernet packet to be processed
761 * @return Ethernet packet processed
762 */
763 private Ethernet removeRelayAgentOption(Ethernet ethPacket) {
764 Ethernet ethernet = (Ethernet) ethPacket.clone();
765 IPv4 ipv4 = (IPv4) ethernet.getPayload();
766 UDP udp = (UDP) ipv4.getPayload();
767 DHCP dhcpPayload = (DHCP) udp.getPayload();
768
769 // removes relay agent information option
770 List<DhcpOption> options = dhcpPayload.getOptions();
771 options = options.stream()
772 .filter(option -> option.getCode() != OptionCode_CircuitID.getValue())
773 .collect(Collectors.toList());
774 dhcpPayload.setOptions(options);
775 dhcpPayload.setGatewayIPAddress(0);
776
777 udp.setPayload(dhcpPayload);
778 ipv4.setPayload(udp);
779 ethernet.setPayload(ipv4);
780 return ethernet;
781 }
782
Yi Tseng51301292017-07-28 13:02:59 -0700783
784 /**
785 * Check if the host is directly connected to the network or not.
786 *
787 * @param dhcpPayload the dhcp payload
788 * @return true if the host is directly connected to the network; false otherwise
789 */
790 private boolean directlyConnected(DHCP dhcpPayload) {
Yi Tseng2cf59912017-08-24 14:47:34 -0700791 DhcpRelayAgentOption relayAgentOption =
792 (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
Yi Tseng51301292017-07-28 13:02:59 -0700793
794 // Doesn't contains relay option
795 if (relayAgentOption == null) {
796 return true;
797 }
798
Yi Tseng2cf59912017-08-24 14:47:34 -0700799 // check circuit id, if circuit id is invalid, we say it is an indirect host
800 DhcpOption circuitIdOpt = relayAgentOption.getSubOption(CIRCUIT_ID.getValue());
Yi Tseng51301292017-07-28 13:02:59 -0700801
Yi Tseng2cf59912017-08-24 14:47:34 -0700802 try {
803 CircuitId.deserialize(circuitIdOpt.getData());
Yi Tseng51301292017-07-28 13:02:59 -0700804 return true;
Yi Tseng2cf59912017-08-24 14:47:34 -0700805 } catch (Exception e) {
806 // invalid circuit id
807 return false;
Yi Tseng51301292017-07-28 13:02:59 -0700808 }
Yi Tseng51301292017-07-28 13:02:59 -0700809 }
810
811
812 /**
813 * Send the DHCP ack to the requester host.
814 * Modify Host or Route store according to the type of DHCP.
815 *
816 * @param ethernetPacketAck the packet
817 * @param dhcpPayload the DHCP data
818 */
819 private void handleDhcpAck(Ethernet ethernetPacketAck, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700820 Optional<Interface> outInterface = getClientInterface(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700821 if (!outInterface.isPresent()) {
822 log.warn("Can't find output interface for dhcp: {}", dhcpPayload);
823 return;
824 }
825
826 Interface outIface = outInterface.get();
827 HostLocation hostLocation = new HostLocation(outIface.connectPoint(), System.currentTimeMillis());
828 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
Yi Tseng4ec727d2017-08-31 11:21:00 -0700829 VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700830 if (vlanId == null) {
831 vlanId = outIface.vlan();
832 }
Yi Tseng51301292017-07-28 13:02:59 -0700833 HostId hostId = HostId.hostId(macAddress, vlanId);
834 Ip4Address ip = Ip4Address.valueOf(dhcpPayload.getYourIPAddress());
835
836 if (directlyConnected(dhcpPayload)) {
837 // Add to host store if it connect to network directly
838 Set<IpAddress> ips = Sets.newHashSet(ip);
Yi Tseng4b013202017-09-08 17:22:51 -0700839 Host host = hostService.getHost(hostId);
Yi Tseng51301292017-07-28 13:02:59 -0700840
Yi Tseng4b013202017-09-08 17:22:51 -0700841 Set<HostLocation> hostLocations = Sets.newHashSet(hostLocation);
842 if (host != null) {
843 // Dual homing support:
844 // if host exists, use old locations and new location
845 hostLocations.addAll(host.locations());
846 }
847 HostDescription desc = new DefaultHostDescription(macAddress, vlanId,
848 hostLocations, ips, false);
849 // Add IP address when dhcp server give the host new ip address
850 providerService.hostDetected(hostId, desc, false);
Yi Tseng51301292017-07-28 13:02:59 -0700851 } else {
852 // Add to route store if it does not connect to network directly
853 // Get gateway host IP according to host mac address
Yi Tsengdcef2c22017-08-05 20:34:06 -0700854 // TODO: remove relay store here
Yi Tseng51301292017-07-28 13:02:59 -0700855 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
856
857 if (record == null) {
858 log.warn("Can't find DHCP record of host {}", hostId);
859 return;
860 }
861
862 MacAddress gwMac = record.nextHop().orElse(null);
863 if (gwMac == null) {
864 log.warn("Can't find gateway mac address from record {}", record);
865 return;
866 }
867
868 HostId gwHostId = HostId.hostId(gwMac, record.vlanId());
869 Host gwHost = hostService.getHost(gwHostId);
870
871 if (gwHost == null) {
872 log.warn("Can't find gateway host {}", gwHostId);
873 return;
874 }
875
876 Ip4Address nextHopIp = gwHost.ipAddresses()
877 .stream()
878 .filter(IpAddress::isIp4)
879 .map(IpAddress::getIp4Address)
880 .findFirst()
881 .orElse(null);
882
883 if (nextHopIp == null) {
884 log.warn("Can't find IP address of gateway {}", gwHost);
885 return;
886 }
887
888 Route route = new Route(Route.Source.STATIC, ip.toIpPrefix(), nextHopIp);
889 routeStore.updateRoute(route);
890 }
Yi Tseng51301292017-07-28 13:02:59 -0700891 }
892
893 /**
894 * forward the packet to ConnectPoint where the DHCP server is attached.
895 *
896 * @param packet the packet
897 */
Yi Tseng4ec727d2017-08-31 11:21:00 -0700898 private void handleDhcpDiscoverAndRequest(Ethernet packet, DHCP dhcpPayload) {
899 ConnectPoint portToFotward = dhcpServerConnectPoint;
900 if (!directlyConnected(dhcpPayload) && indirectDhcpServerConnectPoint != null) {
901 portToFotward = indirectDhcpServerConnectPoint;
902 }
Yi Tseng51301292017-07-28 13:02:59 -0700903 // send packet to dhcp server connect point.
Yi Tseng4ec727d2017-08-31 11:21:00 -0700904 if (portToFotward != null) {
Yi Tseng51301292017-07-28 13:02:59 -0700905 TrafficTreatment t = DefaultTrafficTreatment.builder()
Yi Tseng4ec727d2017-08-31 11:21:00 -0700906 .setOutput(portToFotward.port()).build();
Yi Tseng51301292017-07-28 13:02:59 -0700907 OutboundPacket o = new DefaultOutboundPacket(
Yi Tseng4ec727d2017-08-31 11:21:00 -0700908 portToFotward.deviceId(), t, ByteBuffer.wrap(packet.serialize()));
Yi Tseng51301292017-07-28 13:02:59 -0700909 if (log.isTraceEnabled()) {
910 log.trace("Relaying packet to dhcp server {}", packet);
911 }
912 packetService.emit(o);
913 } else {
914 log.warn("Can't find DHCP server connect point, abort.");
915 }
916 }
917
918
919 /**
920 * Gets output interface of a dhcp packet.
921 * If option 82 exists in the dhcp packet and the option was sent by
Yi Tseng4ec727d2017-08-31 11:21:00 -0700922 * ONOS (circuit format is correct), use the connect
Yi Tseng51301292017-07-28 13:02:59 -0700923 * point and vlan id from circuit id; otherwise, find host by destination
924 * address and use vlan id from sender (dhcp server).
925 *
926 * @param ethPacket the ethernet packet
927 * @param dhcpPayload the dhcp packet
928 * @return an interface represent the output port and vlan; empty value
929 * if the host or circuit id not found
930 */
Yi Tsengdcef2c22017-08-05 20:34:06 -0700931 private Optional<Interface> getClientInterface(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tseng51301292017-07-28 13:02:59 -0700932 VlanId originalPacketVlanId = VlanId.vlanId(ethPacket.getVlanID());
Yi Tseng51301292017-07-28 13:02:59 -0700933 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
934
Yi Tseng4ec727d2017-08-31 11:21:00 -0700935 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
936 try {
937 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
938 ConnectPoint connectPoint = ConnectPoint.deviceConnectPoint(circuitId.connectPoint());
939 VlanId vlanId = circuitId.vlanId();
940 return interfaceService.getInterfacesByPort(connectPoint)
941 .stream()
942 .filter(iface -> interfaceContainsVlan(iface, vlanId))
943 .findFirst();
944 } catch (IllegalArgumentException ex) {
945 // invalid circuit format, didn't sent by ONOS
946 log.debug("Invalid circuit {}, use information from dhcp payload",
947 circuitIdSubOption.getData());
Yi Tseng51301292017-07-28 13:02:59 -0700948 }
949
950 // Use Vlan Id from DHCP server if DHCP relay circuit id was not
951 // sent by ONOS or circuit Id can't be parsed
Yi Tsengdcef2c22017-08-05 20:34:06 -0700952 // TODO: remove relay store from this method
Yi Tseng51301292017-07-28 13:02:59 -0700953 MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
954 Optional<DhcpRecord> dhcpRecord = dhcpRelayStore.getDhcpRecord(HostId.hostId(dstMac, originalPacketVlanId));
Yi Tsengdcef2c22017-08-05 20:34:06 -0700955 ConnectPoint clientConnectPoint = dhcpRecord
Yi Tseng51301292017-07-28 13:02:59 -0700956 .map(DhcpRecord::locations)
957 .orElse(Collections.emptySet())
958 .stream()
959 .reduce((hl1, hl2) -> {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700960 // find latest host connect point
Yi Tseng51301292017-07-28 13:02:59 -0700961 if (hl1 == null || hl2 == null) {
962 return hl1 == null ? hl2 : hl1;
963 }
964 return hl1.time() > hl2.time() ? hl1 : hl2;
965 })
Yi Tsengdcef2c22017-08-05 20:34:06 -0700966 .orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -0700967
Yi Tsengdcef2c22017-08-05 20:34:06 -0700968 if (clientConnectPoint != null) {
969 return interfaceService.getInterfacesByPort(clientConnectPoint)
970 .stream()
Yi Tseng4ec727d2017-08-31 11:21:00 -0700971 .filter(iface -> interfaceContainsVlan(iface, originalPacketVlanId))
Yi Tsengdcef2c22017-08-05 20:34:06 -0700972 .findFirst();
973 }
974 return Optional.empty();
Yi Tseng51301292017-07-28 13:02:59 -0700975 }
976
977 /**
978 * Send the response DHCP to the requester host.
979 *
980 * @param ethPacket the packet
981 * @param dhcpPayload the DHCP data
982 */
983 private void sendResponseToClient(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700984 Optional<Interface> outInterface = getClientInterface(ethPacket, dhcpPayload);
985 if (directlyConnected(dhcpPayload)) {
986 ethPacket = removeRelayAgentOption(ethPacket);
987 }
988 if (!outInterface.isPresent()) {
989 log.warn("Can't find output interface for client, ignore");
990 return;
991 }
992 Interface outIface = outInterface.get();
993 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
994 .setOutput(outIface.connectPoint().port())
995 .build();
996 OutboundPacket o = new DefaultOutboundPacket(
997 outIface.connectPoint().deviceId(),
998 treatment,
999 ByteBuffer.wrap(ethPacket.serialize()));
1000 if (log.isTraceEnabled()) {
1001 log.trace("Relaying packet to DHCP client {} via {}, vlan {}",
1002 ethPacket,
1003 outIface.connectPoint(),
1004 outIface.vlan());
1005 }
1006 packetService.emit(o);
Yi Tseng51301292017-07-28 13:02:59 -07001007 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001008
Yi Tseng4b013202017-09-08 17:22:51 -07001009 @Override
1010 public void triggerProbe(Host host) {
1011 // Do nothing here
1012 }
1013
1014 @Override
1015 public ProviderId id() {
1016 return DhcpRelayManager.PROVIDER_ID;
1017 }
1018
Yi Tsenge72fbb52017-08-02 15:03:31 -07001019 class InternalHostListener implements HostListener {
1020 @Override
1021 public void event(HostEvent event) {
1022 switch (event.type()) {
1023 case HOST_ADDED:
1024 case HOST_UPDATED:
1025 hostUpdated(event.subject());
1026 break;
1027 case HOST_REMOVED:
1028 hostRemoved(event.subject());
1029 break;
1030 case HOST_MOVED:
1031 hostMoved(event.subject());
1032 break;
1033 default:
1034 break;
1035 }
1036 }
1037 }
1038
1039 /**
1040 * Handle host move.
1041 * If the host DHCP server or gateway and it moved to the location different
1042 * to user configured, unsets the connect mac and vlan
1043 *
1044 * @param host the host
1045 */
1046 private void hostMoved(Host host) {
Yi Tsenge72fbb52017-08-02 15:03:31 -07001047 if (this.dhcpGatewayIp != null) {
1048 if (host.ipAddresses().contains(this.dhcpGatewayIp) &&
1049 !host.locations().contains(this.dhcpServerConnectPoint)) {
1050 this.dhcpConnectMac = null;
1051 this.dhcpConnectVlan = null;
1052 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001053 }
1054 if (this.dhcpServerIp != null) {
1055 if (host.ipAddresses().contains(this.dhcpServerIp) &&
1056 !host.locations().contains(this.dhcpServerConnectPoint)) {
1057 this.dhcpConnectMac = null;
1058 this.dhcpConnectVlan = null;
1059 }
1060 }
Yi Tseng4ec727d2017-08-31 11:21:00 -07001061 if (this.indirectDhcpGatewayIp != null) {
1062 if (host.ipAddresses().contains(this.indirectDhcpGatewayIp) &&
1063 !host.locations().contains(this.indirectDhcpServerConnectPoint)) {
1064 this.indirectDhcpConnectMac = null;
1065 this.indirectDhcpConnectVlan = null;
1066 }
1067 }
1068 if (this.indirectDhcpServerIp != null) {
1069 if (host.ipAddresses().contains(this.indirectDhcpServerIp) &&
1070 !host.locations().contains(this.indirectDhcpServerConnectPoint)) {
1071 this.indirectDhcpConnectMac = null;
1072 this.indirectDhcpConnectVlan = null;
1073 }
1074 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001075 }
1076
1077 /**
1078 * Handle host updated.
1079 * If the host is DHCP server or gateway, update connect mac and vlan.
1080 *
1081 * @param host the host
1082 */
1083 private void hostUpdated(Host host) {
1084 if (this.dhcpGatewayIp != null) {
1085 if (host.ipAddresses().contains(this.dhcpGatewayIp)) {
1086 this.dhcpConnectMac = host.mac();
1087 this.dhcpConnectVlan = host.vlan();
1088 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001089 }
1090 if (this.dhcpServerIp != null) {
1091 if (host.ipAddresses().contains(this.dhcpServerIp)) {
1092 this.dhcpConnectMac = host.mac();
1093 this.dhcpConnectVlan = host.vlan();
1094 }
1095 }
Yi Tseng4ec727d2017-08-31 11:21:00 -07001096 if (this.indirectDhcpGatewayIp != null) {
1097 if (host.ipAddresses().contains(this.indirectDhcpGatewayIp)) {
1098 this.indirectDhcpConnectMac = host.mac();
1099 this.indirectDhcpConnectVlan = host.vlan();
1100 }
1101 }
1102 if (this.indirectDhcpServerIp != null) {
1103 if (host.ipAddresses().contains(this.indirectDhcpServerIp)) {
1104 this.indirectDhcpConnectMac = host.mac();
1105 this.indirectDhcpConnectVlan = host.vlan();
1106 }
1107 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001108 }
1109
1110 /**
1111 * Handle host removed.
1112 * If the host is DHCP server or gateway, unset connect mac and vlan.
1113 *
1114 * @param host the host
1115 */
1116 private void hostRemoved(Host host) {
1117 if (this.dhcpGatewayIp != null) {
1118 if (host.ipAddresses().contains(this.dhcpGatewayIp)) {
1119 this.dhcpConnectMac = null;
1120 this.dhcpConnectVlan = null;
1121 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001122 }
1123 if (this.dhcpServerIp != null) {
1124 if (host.ipAddresses().contains(this.dhcpServerIp)) {
1125 this.dhcpConnectMac = null;
1126 this.dhcpConnectVlan = null;
1127 }
1128 }
Yi Tseng4ec727d2017-08-31 11:21:00 -07001129 if (this.indirectDhcpGatewayIp != null) {
1130 if (host.ipAddresses().contains(this.indirectDhcpGatewayIp)) {
1131 this.indirectDhcpConnectMac = null;
1132 this.indirectDhcpConnectVlan = null;
1133 }
1134 }
1135 if (this.indirectDhcpServerIp != null) {
1136 if (host.ipAddresses().contains(this.indirectDhcpServerIp)) {
1137 this.indirectDhcpConnectMac = null;
1138 this.indirectDhcpConnectVlan = null;
1139 }
1140 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001141 }
Yi Tseng51301292017-07-28 13:02:59 -07001142}