blob: 804059b138723b4d535a167b3dbc2fe4248b4611 [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 {
Charles Chan75edab72017-09-12 17:09:32 -070092 public static final String DHCP_V4_RELAY_APP = "org.onosproject.Dhcp4HandlerImpl";
93 public static final ProviderId PROVIDER_ID = new ProviderId("dhcp4", DHCP_V4_RELAY_APP);
Yi Tseng51301292017-07-28 13:02:59 -070094 private static Logger log = LoggerFactory.getLogger(Dhcp4HandlerImpl.class);
95
96 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
97 protected DhcpRelayStore dhcpRelayStore;
98
99 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
100 protected PacketService packetService;
101
102 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Yi Tseng51301292017-07-28 13:02:59 -0700103 protected RouteStore routeStore;
104
105 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
106 protected InterfaceService interfaceService;
107
108 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
109 protected HostService hostService;
110
Yi Tseng4b013202017-09-08 17:22:51 -0700111 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
112 protected HostProviderRegistry providerRegistry;
113
114 protected HostProviderService providerService;
Yi Tsenge72fbb52017-08-02 15:03:31 -0700115 private InternalHostListener hostListener = new InternalHostListener();
116
Yi Tseng51301292017-07-28 13:02:59 -0700117 private Ip4Address dhcpServerIp = null;
118 // dhcp server may be connected directly to the SDN network or
119 // via an external gateway. When connected directly, the dhcpConnectPoint, dhcpConnectMac,
120 // and dhcpConnectVlan refer to the server. When connected via the gateway, they refer
121 // to the gateway.
122 private ConnectPoint dhcpServerConnectPoint = null;
123 private MacAddress dhcpConnectMac = null;
124 private VlanId dhcpConnectVlan = null;
125 private Ip4Address dhcpGatewayIp = null;
Yi Tseng4fa05832017-08-17 13:08:31 -0700126 private Ip4Address relayAgentIp = null;
Yi Tseng51301292017-07-28 13:02:59 -0700127
Yi Tseng4ec727d2017-08-31 11:21:00 -0700128 // Indirect case DHCP server
129 private Ip4Address indirectDhcpServerIp = null;
130 private ConnectPoint indirectDhcpServerConnectPoint = null;
131 private MacAddress indirectDhcpConnectMac = null;
132 private VlanId indirectDhcpConnectVlan = null;
133 private Ip4Address indirectDhcpGatewayIp = null;
134 private Ip4Address indirectRelayAgentIp = null;
135
Yi Tsenge72fbb52017-08-02 15:03:31 -0700136 @Activate
137 protected void activate() {
138 hostService.addListener(hostListener);
Yi Tseng4b013202017-09-08 17:22:51 -0700139 providerService = providerRegistry.register(this);
Yi Tsenge72fbb52017-08-02 15:03:31 -0700140 }
141
142 @Deactivate
143 protected void deactivate() {
Yi Tseng4b013202017-09-08 17:22:51 -0700144 providerRegistry.unregister(this);
145 hostService.removeListener(hostListener);
146 this.dhcpConnectMac = null;
147 this.dhcpConnectVlan = null;
148
Yi Tseng4ec727d2017-08-31 11:21:00 -0700149 if (dhcpGatewayIp != null) {
150 hostService.stopMonitoringIp(dhcpGatewayIp);
151 }
152 if (dhcpServerIp != null) {
153 hostService.stopMonitoringIp(dhcpServerIp);
154 }
155
156 if (indirectDhcpGatewayIp != null) {
157 hostService.stopMonitoringIp(indirectDhcpGatewayIp);
158 }
159 if (indirectDhcpServerIp != null) {
160 hostService.stopMonitoringIp(indirectDhcpServerIp);
161 }
162
Yi Tsenge72fbb52017-08-02 15:03:31 -0700163 hostService.removeListener(hostListener);
164 this.dhcpConnectMac = null;
165 this.dhcpConnectVlan = null;
Yi Tseng4ec727d2017-08-31 11:21:00 -0700166 this.indirectDhcpConnectMac = null;
167 this.indirectDhcpConnectVlan = null;
Yi Tsenge72fbb52017-08-02 15:03:31 -0700168 }
169
Yi Tseng51301292017-07-28 13:02:59 -0700170 @Override
171 public void setDhcpServerIp(IpAddress dhcpServerIp) {
172 checkNotNull(dhcpServerIp, "DHCP server IP can't be null");
173 checkState(dhcpServerIp.isIp4(), "Invalid server IP for DHCPv4 relay handler");
174 this.dhcpServerIp = dhcpServerIp.getIp4Address();
175 }
176
177 @Override
178 public void setDhcpServerConnectPoint(ConnectPoint dhcpServerConnectPoint) {
179 checkNotNull(dhcpServerConnectPoint, "Server connect point can't null");
180 this.dhcpServerConnectPoint = dhcpServerConnectPoint;
181 }
182
183 @Override
184 public void setDhcpConnectMac(MacAddress dhcpConnectMac) {
185 this.dhcpConnectMac = dhcpConnectMac;
186 }
187
188 @Override
189 public void setDhcpConnectVlan(VlanId dhcpConnectVlan) {
190 this.dhcpConnectVlan = dhcpConnectVlan;
191 }
192
193 @Override
194 public void setDhcpGatewayIp(IpAddress dhcpGatewayIp) {
195 if (dhcpGatewayIp != null) {
196 checkState(dhcpGatewayIp.isIp4(), "Invalid gateway IP for DHCPv4 relay handler");
197 this.dhcpGatewayIp = dhcpGatewayIp.getIp4Address();
198 } else {
199 // removes gateway config
200 this.dhcpGatewayIp = null;
201 }
202 }
203
204 @Override
205 public Optional<IpAddress> getDhcpServerIp() {
206 return Optional.ofNullable(dhcpServerIp);
207 }
208
209 @Override
210 public Optional<IpAddress> getDhcpGatewayIp() {
211 return Optional.ofNullable(dhcpGatewayIp);
212 }
213
214 @Override
215 public Optional<MacAddress> getDhcpConnectMac() {
216 return Optional.ofNullable(dhcpConnectMac);
217 }
218
219 @Override
Yi Tsenge72fbb52017-08-02 15:03:31 -0700220 public void setDefaultDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
221 if (configs.size() == 0) {
222 // no config to update
223 return;
224 }
225
226 // TODO: currently we pick up first DHCP server config.
227 // Will use other server configs in the future for HA.
228 DhcpServerConfig serverConfig = configs.iterator().next();
Yi Tsengaefbb002017-09-08 16:23:32 -0700229 if (!serverConfig.getDhcpServerConnectPoint().isPresent()) {
230 log.warn("Connect point from server config not exists");
231 return;
232 }
233 if (!serverConfig.getDhcpServerIp4().isPresent()) {
234 log.warn("IP from DHCP server config not exists");
235 return;
236 }
Yi Tsenge72fbb52017-08-02 15:03:31 -0700237 Ip4Address oldServerIp = this.dhcpServerIp;
238 Ip4Address oldGatewayIp = this.dhcpGatewayIp;
239
240 // stop monitoring gateway or server
241 if (oldGatewayIp != null) {
242 hostService.stopMonitoringIp(oldGatewayIp);
243 } else if (oldServerIp != null) {
244 hostService.stopMonitoringIp(oldServerIp);
245 }
246
247 this.dhcpServerConnectPoint = serverConfig.getDhcpServerConnectPoint().get();
248 this.dhcpServerIp = serverConfig.getDhcpServerIp4().get();
249 this.dhcpGatewayIp = serverConfig.getDhcpGatewayIp4().orElse(null);
250
251 // reset server mac and vlan
252 this.dhcpConnectMac = null;
253 this.dhcpConnectVlan = null;
254
255 log.info("DHCP server connect point: " + this.dhcpServerConnectPoint);
256 log.info("DHCP server IP: " + this.dhcpServerIp);
257
258 IpAddress ipToProbe = MoreObjects.firstNonNull(this.dhcpGatewayIp, this.dhcpServerIp);
259 String hostToProbe = this.dhcpGatewayIp != null ? "gateway" : "DHCP server";
260
261 if (ipToProbe == null) {
262 log.warn("Server IP not set, can't probe it");
263 return;
264 }
265
266 log.info("Probing to resolve {} IP {}", hostToProbe, ipToProbe);
267 hostService.startMonitoringIp(ipToProbe);
268
269 Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
270 if (!hosts.isEmpty()) {
271 Host host = hosts.iterator().next();
272 this.dhcpConnectVlan = host.vlan();
273 this.dhcpConnectMac = host.mac();
274 }
Yi Tseng4fa05832017-08-17 13:08:31 -0700275
276 this.relayAgentIp = serverConfig.getRelayAgentIp4().orElse(null);
Yi Tsenge72fbb52017-08-02 15:03:31 -0700277 }
278
279 @Override
280 public void setIndirectDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
Yi Tseng4ec727d2017-08-31 11:21:00 -0700281 if (configs.size() == 0) {
282 // no config to update
283 return;
284 }
285
286 // TODO: currently we pick up first indirect DHCP server config.
287 // Will use other server configs in the future for HA.
288 DhcpServerConfig serverConfig = configs.iterator().next();
289 checkState(serverConfig.getDhcpServerConnectPoint().isPresent(),
290 "Connect point not exists");
291 checkState(serverConfig.getDhcpServerIp4().isPresent(),
292 "IP of DHCP server not exists");
293 Ip4Address oldServerIp = this.indirectDhcpServerIp;
294 Ip4Address oldGatewayIp = this.indirectDhcpGatewayIp;
295
296 // stop monitoring gateway or server
297 if (oldGatewayIp != null) {
298 hostService.stopMonitoringIp(oldGatewayIp);
299 } else if (oldServerIp != null) {
300 hostService.stopMonitoringIp(oldServerIp);
301 }
302
303 this.indirectDhcpServerConnectPoint = serverConfig.getDhcpServerConnectPoint().get();
304 this.indirectDhcpServerIp = serverConfig.getDhcpServerIp4().get();
305 this.indirectDhcpGatewayIp = serverConfig.getDhcpGatewayIp4().orElse(null);
306
307 // reset server mac and vlan
308 this.indirectDhcpConnectMac = null;
309 this.indirectDhcpConnectVlan = null;
310
311 log.info("Indirect DHCP server connect point: " + this.indirectDhcpServerConnectPoint);
312 log.info("Indirect DHCP server IP: " + this.indirectDhcpServerIp);
313
314 IpAddress ipToProbe = MoreObjects.firstNonNull(this.indirectDhcpGatewayIp, this.indirectDhcpServerIp);
315 String hostToProbe = this.indirectDhcpGatewayIp != null ? "gateway" : "DHCP server";
316
317 log.info("Probing to resolve {} IP {}", hostToProbe, ipToProbe);
318 hostService.startMonitoringIp(ipToProbe);
319
320 Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
321 if (!hosts.isEmpty()) {
322 Host host = hosts.iterator().next();
323 this.indirectDhcpConnectVlan = host.vlan();
324 this.indirectDhcpConnectMac = host.mac();
325 }
326
327 this.indirectRelayAgentIp = serverConfig.getRelayAgentIp4().orElse(null);
Yi Tsenge72fbb52017-08-02 15:03:31 -0700328 }
329
Yi Tseng4fa05832017-08-17 13:08:31 -0700330 @Override
Yi Tseng51301292017-07-28 13:02:59 -0700331 public void processDhcpPacket(PacketContext context, BasePacket payload) {
332 checkNotNull(payload, "DHCP payload can't be null");
333 checkState(payload instanceof DHCP, "Payload is not a DHCP");
334 DHCP dhcpPayload = (DHCP) payload;
335 if (!configured()) {
336 log.warn("Missing DHCP relay server config. Abort packet processing");
337 return;
338 }
339
340 ConnectPoint inPort = context.inPacket().receivedFrom();
Yi Tseng51301292017-07-28 13:02:59 -0700341 checkNotNull(dhcpPayload, "Can't find DHCP payload");
342 Ethernet packet = context.inPacket().parsed();
343 DHCP.MsgType incomingPacketType = dhcpPayload.getOptions().stream()
344 .filter(dhcpOption -> dhcpOption.getCode() == OptionCode_MessageType.getValue())
345 .map(DhcpOption::getData)
346 .map(data -> DHCP.MsgType.getType(data[0]))
347 .findFirst()
348 .orElse(null);
349 checkNotNull(incomingPacketType, "Can't get message type from DHCP payload {}", dhcpPayload);
350 switch (incomingPacketType) {
351 case DHCPDISCOVER:
Yi Tsengdcef2c22017-08-05 20:34:06 -0700352 // Add the gateway IP as virtual interface IP for server to understand
Yi Tseng51301292017-07-28 13:02:59 -0700353 // the lease to be assigned and forward the packet to dhcp server.
354 Ethernet ethernetPacketDiscover =
Yi Tsengdcef2c22017-08-05 20:34:06 -0700355 processDhcpPacketFromClient(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700356 if (ethernetPacketDiscover != null) {
357 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700358 handleDhcpDiscoverAndRequest(ethernetPacketDiscover, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700359 }
360 break;
361 case DHCPOFFER:
362 //reply to dhcp client.
363 Ethernet ethernetPacketOffer = processDhcpPacketFromServer(packet);
364 if (ethernetPacketOffer != null) {
365 writeResponseDhcpRecord(ethernetPacketOffer, dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700366 sendResponseToClient(ethernetPacketOffer, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700367 }
368 break;
369 case DHCPREQUEST:
370 // add the gateway ip as virtual interface ip for server to understand
371 // the lease to be assigned and forward the packet to dhcp server.
372 Ethernet ethernetPacketRequest =
Yi Tsengdcef2c22017-08-05 20:34:06 -0700373 processDhcpPacketFromClient(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700374 if (ethernetPacketRequest != null) {
375 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700376 handleDhcpDiscoverAndRequest(ethernetPacketRequest, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700377 }
378 break;
379 case DHCPACK:
380 // reply to dhcp client.
381 Ethernet ethernetPacketAck = processDhcpPacketFromServer(packet);
382 if (ethernetPacketAck != null) {
383 writeResponseDhcpRecord(ethernetPacketAck, dhcpPayload);
384 handleDhcpAck(ethernetPacketAck, dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700385 sendResponseToClient(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700386 }
387 break;
388 case DHCPRELEASE:
389 // TODO: release the ip address from client
390 break;
391 default:
392 break;
393 }
394 }
395
396 /**
397 * Checks if this app has been configured.
398 *
399 * @return true if all information we need have been initialized
400 */
Yi Tseng4ec727d2017-08-31 11:21:00 -0700401 private boolean configured() {
Yi Tseng51301292017-07-28 13:02:59 -0700402 return dhcpServerConnectPoint != null && dhcpServerIp != null;
403 }
404
405 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -0700406 * Returns the first interface ip from interface.
Yi Tseng51301292017-07-28 13:02:59 -0700407 *
Yi Tsengdcef2c22017-08-05 20:34:06 -0700408 * @param iface interface of one connect point
Yi Tseng51301292017-07-28 13:02:59 -0700409 * @return the first interface IP; null if not exists an IP address in
410 * these interfaces
411 */
Yi Tseng4fa05832017-08-17 13:08:31 -0700412 private Ip4Address getFirstIpFromInterface(Interface iface) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700413 checkNotNull(iface, "Interface can't be null");
414 return iface.ipAddressesList().stream()
Yi Tseng51301292017-07-28 13:02:59 -0700415 .map(InterfaceIpAddress::ipAddress)
416 .filter(IpAddress::isIp4)
417 .map(IpAddress::getIp4Address)
418 .findFirst()
419 .orElse(null);
420 }
421
422 /**
Yi Tseng4ec727d2017-08-31 11:21:00 -0700423 * Gets Interface facing to the server for default host.
Yi Tsengdcef2c22017-08-05 20:34:06 -0700424 *
425 * @return the Interface facing to the server; null if not found
426 */
Yi Tseng4ec727d2017-08-31 11:21:00 -0700427 private Interface getServerInterface() {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700428 if (dhcpServerConnectPoint == null || dhcpConnectVlan == null) {
429 return null;
430 }
431 return interfaceService.getInterfacesByPort(dhcpServerConnectPoint)
432 .stream()
Yi Tseng4ec727d2017-08-31 11:21:00 -0700433 .filter(iface -> interfaceContainsVlan(iface, dhcpConnectVlan))
Yi Tsengdcef2c22017-08-05 20:34:06 -0700434 .findFirst()
435 .orElse(null);
436 }
437
438 /**
Yi Tseng4ec727d2017-08-31 11:21:00 -0700439 * Gets Interface facing to the server for indirect hosts.
440 * Use default server Interface if indirect server not configured.
441 *
442 * @return the Interface facing to the server; null if not found
443 */
444 private Interface getIndirectServerInterface() {
445 if (indirectDhcpServerConnectPoint == null || indirectDhcpConnectVlan == null) {
446 return getServerInterface();
447 }
448 return interfaceService.getInterfacesByPort(indirectDhcpServerConnectPoint)
449 .stream()
450 .filter(iface -> interfaceContainsVlan(iface, indirectDhcpConnectVlan))
451 .findFirst()
452 .orElse(null);
453 }
454
455 /**
456 * Determind if an Interface contains a vlan id.
457 *
458 * @param iface the Interface
459 * @param vlanId the vlan id
460 * @return true if the Interface contains the vlan id
461 */
462 private boolean interfaceContainsVlan(Interface iface, VlanId vlanId) {
Yi Tseng58e74312017-09-30 11:35:42 +0800463 if (vlanId.equals(VlanId.NONE)) {
464 // untagged packet, check if vlan untagged or vlan native is not NONE
465 return !iface.vlanUntagged().equals(VlanId.NONE) ||
466 !iface.vlanNative().equals(VlanId.NONE);
467 }
468 // tagged packet, check if the interface contains the vlan
469 return iface.vlanTagged().contains(vlanId);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700470 }
471
472 /**
Yi Tseng51301292017-07-28 13:02:59 -0700473 * Build the DHCP discover/request packet with gateway IP(unicast packet).
474 *
475 * @param context the packet context
476 * @param ethernetPacket the ethernet payload to process
Yi Tseng51301292017-07-28 13:02:59 -0700477 * @return processed packet
478 */
479 private Ethernet processDhcpPacketFromClient(PacketContext context,
Yi Tsengdcef2c22017-08-05 20:34:06 -0700480 Ethernet ethernetPacket) {
Yi Tseng4ec727d2017-08-31 11:21:00 -0700481 // get dhcp header.
482 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
483 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
484 UDP udpPacket = (UDP) ipv4Packet.getPayload();
485 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
486
Yi Tsengdcef2c22017-08-05 20:34:06 -0700487 Ip4Address clientInterfaceIp =
488 interfaceService.getInterfacesByPort(context.inPacket().receivedFrom())
489 .stream()
490 .map(Interface::ipAddressesList)
491 .flatMap(Collection::stream)
492 .map(InterfaceIpAddress::ipAddress)
493 .filter(IpAddress::isIp4)
494 .map(IpAddress::getIp4Address)
495 .findFirst()
496 .orElse(null);
497 if (clientInterfaceIp == null) {
498 log.warn("Can't find interface IP for client interface for port {}",
499 context.inPacket().receivedFrom());
500 return null;
501 }
Yi Tseng4ec727d2017-08-31 11:21:00 -0700502 boolean isDirectlyConnected = directlyConnected(dhcpPacket);
503 Interface serverInterface = isDirectlyConnected ? getServerInterface() : getIndirectServerInterface();
Yi Tsengdcef2c22017-08-05 20:34:06 -0700504 if (serverInterface == null) {
Yi Tseng4ec727d2017-08-31 11:21:00 -0700505 log.warn("Can't get {} server interface, ignore", isDirectlyConnected ? "direct" : "indirect");
Yi Tsengdcef2c22017-08-05 20:34:06 -0700506 return null;
507 }
Yi Tseng4fa05832017-08-17 13:08:31 -0700508 Ip4Address ipFacingServer = getFirstIpFromInterface(serverInterface);
509 MacAddress macFacingServer = serverInterface.mac();
510 if (ipFacingServer == null || macFacingServer == null) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700511 log.warn("No IP address for server Interface {}", serverInterface);
Yi Tseng51301292017-07-28 13:02:59 -0700512 return null;
513 }
514 if (dhcpConnectMac == null) {
515 log.warn("DHCP {} not yet resolved .. Aborting DHCP "
516 + "packet processing from client on port: {}",
517 (dhcpGatewayIp == null) ? "server IP " + dhcpServerIp
518 : "gateway IP " + dhcpGatewayIp,
Yi Tsengdcef2c22017-08-05 20:34:06 -0700519 context.inPacket().receivedFrom());
Yi Tseng51301292017-07-28 13:02:59 -0700520 return null;
521 }
Yi Tseng4ec727d2017-08-31 11:21:00 -0700522
Yi Tseng4fa05832017-08-17 13:08:31 -0700523 etherReply.setSourceMACAddress(macFacingServer);
Yi Tseng4fa05832017-08-17 13:08:31 -0700524 ipv4Packet.setSourceAddress(ipFacingServer.toInt());
Yi Tseng51301292017-07-28 13:02:59 -0700525
Yi Tseng4ec727d2017-08-31 11:21:00 -0700526 if (isDirectlyConnected) {
Charles Chanb5d24822017-10-10 16:53:32 -0400527 etherReply.setDestinationMACAddress(dhcpConnectMac);
528 etherReply.setVlanID(dhcpConnectVlan.toShort());
529 ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
530
Yi Tseng51301292017-07-28 13:02:59 -0700531 ConnectPoint inPort = context.inPacket().receivedFrom();
532 VlanId vlanId = VlanId.vlanId(ethernetPacket.getVlanID());
533 // add connected in port and vlan
534 CircuitId cid = new CircuitId(inPort.toString(), vlanId);
535 byte[] circuitId = cid.serialize();
536 DhcpOption circuitIdSubOpt = new DhcpOption();
537 circuitIdSubOpt
538 .setCode(CIRCUIT_ID.getValue())
539 .setLength((byte) circuitId.length)
540 .setData(circuitId);
541
542 DhcpRelayAgentOption newRelayAgentOpt = new DhcpRelayAgentOption();
543 newRelayAgentOpt.setCode(OptionCode_CircuitID.getValue());
544 newRelayAgentOpt.addSubOption(circuitIdSubOpt);
545
Charles Chanb5d24822017-10-10 16:53:32 -0400546 // Removes END option first
Yi Tseng51301292017-07-28 13:02:59 -0700547 List<DhcpOption> options = dhcpPacket.getOptions().stream()
548 .filter(opt -> opt.getCode() != OptionCode_END.getValue())
549 .collect(Collectors.toList());
550
551 // push relay agent option
552 options.add(newRelayAgentOpt);
553
554 // make sure option 255(End) is the last option
555 DhcpOption endOption = new DhcpOption();
556 endOption.setCode(OptionCode_END.getValue());
557 options.add(endOption);
558
559 dhcpPacket.setOptions(options);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700560
Charles Chanb5d24822017-10-10 16:53:32 -0400561 // Sets relay agent IP
562 int effectiveRelayAgentIp = relayAgentIp != null ?
563 relayAgentIp.toInt() : clientInterfaceIp.toInt();
564 dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
565 } else {
566 if (indirectDhcpServerIp != null) {
567 // Use indirect server config for indirect packets if configured
568 etherReply.setDestinationMACAddress(indirectDhcpConnectMac);
569 etherReply.setVlanID(indirectDhcpConnectVlan.toShort());
570 ipv4Packet.setDestinationAddress(indirectDhcpServerIp.toInt());
Yi Tseng51301292017-07-28 13:02:59 -0700571
Charles Chanb5d24822017-10-10 16:53:32 -0400572 // Set giaddr if indirect relay agent IP is configured
573 if (indirectRelayAgentIp != null) {
574 dhcpPacket.setGatewayIPAddress(indirectRelayAgentIp.toInt());
575 }
576 } else {
577 // Otherwise, use default server config for indirect packets
578 etherReply.setDestinationMACAddress(dhcpConnectMac);
579 etherReply.setVlanID(dhcpConnectVlan.toShort());
580 ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
Yi Tseng4ec727d2017-08-31 11:21:00 -0700581
Charles Chanb5d24822017-10-10 16:53:32 -0400582 // Set giaddr if direct relay agent IP is configured
583 if (relayAgentIp != null) {
584 dhcpPacket.setGatewayIPAddress(relayAgentIp.toInt());
585 }
Yi Tseng4ec727d2017-08-31 11:21:00 -0700586 }
Yi Tseng4fa05832017-08-17 13:08:31 -0700587 }
588
Yi Tsengf41681e2017-10-03 09:58:19 -0700589 // Remove broadcast flag
590 dhcpPacket.setFlags((short) 0);
591
Yi Tseng51301292017-07-28 13:02:59 -0700592 udpPacket.setPayload(dhcpPacket);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700593 // As a DHCP relay, the source port should be server port( instead
594 // of client port.
Yi Tsengdcef2c22017-08-05 20:34:06 -0700595 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
Yi Tseng51301292017-07-28 13:02:59 -0700596 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
597 ipv4Packet.setPayload(udpPacket);
Charles Chan7edf7642017-10-09 11:07:25 -0400598 ipv4Packet.setTtl((byte) 64);
Yi Tseng51301292017-07-28 13:02:59 -0700599 etherReply.setPayload(ipv4Packet);
600 return etherReply;
601 }
602
603 /**
604 * Writes DHCP record to the store according to the request DHCP packet (Discover, Request).
605 *
606 * @param location the location which DHCP packet comes from
607 * @param ethernet the DHCP packet
608 * @param dhcpPayload the DHCP payload
609 */
610 private void writeRequestDhcpRecord(ConnectPoint location,
611 Ethernet ethernet,
612 DHCP dhcpPayload) {
613 VlanId vlanId = VlanId.vlanId(ethernet.getVlanID());
614 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
615 HostId hostId = HostId.hostId(macAddress, vlanId);
616 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
617 if (record == null) {
618 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
619 } else {
620 record = record.clone();
621 }
622 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
623 record.ip4Status(dhcpPayload.getPacketType());
624 record.setDirectlyConnected(directlyConnected(dhcpPayload));
625 if (!directlyConnected(dhcpPayload)) {
626 // Update gateway mac address if the host is not directly connected
627 record.nextHop(ethernet.getSourceMAC());
628 }
629 record.updateLastSeen();
630 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
631 }
632
633 /**
634 * Writes DHCP record to the store according to the response DHCP packet (Offer, Ack).
635 *
636 * @param ethernet the DHCP packet
637 * @param dhcpPayload the DHCP payload
638 */
639 private void writeResponseDhcpRecord(Ethernet ethernet,
640 DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700641 Optional<Interface> outInterface = getClientInterface(ethernet, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700642 if (!outInterface.isPresent()) {
643 log.warn("Failed to determine where to send {}", dhcpPayload.getPacketType());
644 return;
645 }
646
647 Interface outIface = outInterface.get();
648 ConnectPoint location = outIface.connectPoint();
Yi Tseng4ec727d2017-08-31 11:21:00 -0700649 VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700650 if (vlanId == null) {
651 vlanId = outIface.vlan();
652 }
Yi Tseng51301292017-07-28 13:02:59 -0700653 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
654 HostId hostId = HostId.hostId(macAddress, vlanId);
655 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
656 if (record == null) {
657 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
658 } else {
659 record = record.clone();
660 }
661 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
662 if (dhcpPayload.getPacketType() == DHCP.MsgType.DHCPACK) {
663 record.ip4Address(Ip4Address.valueOf(dhcpPayload.getYourIPAddress()));
664 }
665 record.ip4Status(dhcpPayload.getPacketType());
666 record.setDirectlyConnected(directlyConnected(dhcpPayload));
667 record.updateLastSeen();
668 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
669 }
670
671 /**
672 * Build the DHCP offer/ack with proper client port.
673 *
674 * @param ethernetPacket the original packet comes from server
675 * @return new packet which will send to the client
676 */
677 private Ethernet processDhcpPacketFromServer(Ethernet ethernetPacket) {
678 // get dhcp header.
679 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
680 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
681 UDP udpPacket = (UDP) ipv4Packet.getPayload();
682 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
683
684 // determine the vlanId of the client host - note that this vlan id
685 // could be different from the vlan in the packet from the server
Yi Tsengdcef2c22017-08-05 20:34:06 -0700686 Interface clientInterface = getClientInterface(ethernetPacket, dhcpPayload).orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -0700687
Yi Tsengdcef2c22017-08-05 20:34:06 -0700688 if (clientInterface == null) {
Yi Tseng51301292017-07-28 13:02:59 -0700689 log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
690 return null;
691 }
Yi Tsengdcef2c22017-08-05 20:34:06 -0700692 VlanId vlanId;
693 if (clientInterface.vlanTagged().isEmpty()) {
694 vlanId = clientInterface.vlan();
695 } else {
696 // might be multiple vlan in same interface
Yi Tseng4ec727d2017-08-31 11:21:00 -0700697 vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700698 }
699 if (vlanId == null) {
700 vlanId = VlanId.NONE;
701 }
702 etherReply.setVlanID(vlanId.toShort());
703 etherReply.setSourceMACAddress(clientInterface.mac());
Yi Tseng51301292017-07-28 13:02:59 -0700704
Yi Tsengdcef2c22017-08-05 20:34:06 -0700705 if (!directlyConnected(dhcpPayload)) {
706 // if client is indirectly connected, try use next hop mac address
707 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
708 HostId hostId = HostId.hostId(macAddress, vlanId);
709 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
710 if (record != null) {
711 // if next hop can be found, use mac address of next hop
712 record.nextHop().ifPresent(etherReply::setDestinationMACAddress);
713 } else {
714 // otherwise, discard the packet
715 log.warn("Can't find record for host id {}, discard packet", hostId);
716 return null;
717 }
Yi Tsengc03fa242017-08-17 17:43:38 -0700718 } else {
719 etherReply.setDestinationMACAddress(dhcpPayload.getClientHardwareAddress());
Yi Tsengdcef2c22017-08-05 20:34:06 -0700720 }
721
Yi Tseng51301292017-07-28 13:02:59 -0700722 // we leave the srcMac from the original packet
Yi Tseng51301292017-07-28 13:02:59 -0700723 // figure out the relay agent IP corresponding to the original request
Yi Tseng4fa05832017-08-17 13:08:31 -0700724 Ip4Address ipFacingClient = getFirstIpFromInterface(clientInterface);
725 if (ipFacingClient == null) {
Yi Tseng51301292017-07-28 13:02:59 -0700726 log.warn("Cannot determine relay agent interface Ipv4 addr for host {}/{}. "
727 + "Aborting relay for dhcp packet from server {}",
Yi Tsengdcef2c22017-08-05 20:34:06 -0700728 etherReply.getDestinationMAC(), clientInterface.vlan(),
Yi Tseng51301292017-07-28 13:02:59 -0700729 ethernetPacket);
730 return null;
731 }
732 // SRC_IP: relay agent IP
733 // DST_IP: offered IP
Yi Tseng4fa05832017-08-17 13:08:31 -0700734 ipv4Packet.setSourceAddress(ipFacingClient.toInt());
Yi Tseng51301292017-07-28 13:02:59 -0700735 ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
736 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
737 if (directlyConnected(dhcpPayload)) {
738 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
739 } else {
740 // forward to another dhcp relay
Yi Tseng72b599a2017-09-14 13:24:21 -0700741 // FIXME: Currently we assume the DHCP comes from a L2 relay with
742 // Option 82, this might not work if DHCP message comes from
743 // L3 relay.
744 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
Yi Tseng51301292017-07-28 13:02:59 -0700745 }
746
747 udpPacket.setPayload(dhcpPayload);
748 ipv4Packet.setPayload(udpPacket);
749 etherReply.setPayload(ipv4Packet);
750 return etherReply;
751 }
752
Yi Tsengdcef2c22017-08-05 20:34:06 -0700753 /**
754 * Extracts VLAN ID from relay agent option.
755 *
756 * @param dhcpPayload the DHCP payload
757 * @return VLAN ID from DHCP payload; null if not exists
758 */
Yi Tseng4ec727d2017-08-31 11:21:00 -0700759 private VlanId getVlanIdFromRelayAgentOption(DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700760 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
761 if (option == null) {
762 return null;
763 }
764 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
765 if (circuitIdSubOption == null) {
766 return null;
767 }
768 try {
769 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
770 return circuitId.vlanId();
771 } catch (IllegalArgumentException e) {
772 // can't deserialize the circuit ID
773 return null;
774 }
775 }
776
777 /**
778 * Removes DHCP relay agent information option (option 82) from DHCP payload.
779 * Also reset giaddr to 0
780 *
781 * @param ethPacket the Ethernet packet to be processed
782 * @return Ethernet packet processed
783 */
784 private Ethernet removeRelayAgentOption(Ethernet ethPacket) {
785 Ethernet ethernet = (Ethernet) ethPacket.clone();
786 IPv4 ipv4 = (IPv4) ethernet.getPayload();
787 UDP udp = (UDP) ipv4.getPayload();
788 DHCP dhcpPayload = (DHCP) udp.getPayload();
789
790 // removes relay agent information option
791 List<DhcpOption> options = dhcpPayload.getOptions();
792 options = options.stream()
793 .filter(option -> option.getCode() != OptionCode_CircuitID.getValue())
794 .collect(Collectors.toList());
795 dhcpPayload.setOptions(options);
796 dhcpPayload.setGatewayIPAddress(0);
797
798 udp.setPayload(dhcpPayload);
799 ipv4.setPayload(udp);
800 ethernet.setPayload(ipv4);
801 return ethernet;
802 }
803
Yi Tseng51301292017-07-28 13:02:59 -0700804
805 /**
806 * Check if the host is directly connected to the network or not.
807 *
808 * @param dhcpPayload the dhcp payload
809 * @return true if the host is directly connected to the network; false otherwise
810 */
811 private boolean directlyConnected(DHCP dhcpPayload) {
Yi Tseng2cf59912017-08-24 14:47:34 -0700812 DhcpRelayAgentOption relayAgentOption =
813 (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
Yi Tseng51301292017-07-28 13:02:59 -0700814
815 // Doesn't contains relay option
816 if (relayAgentOption == null) {
817 return true;
818 }
819
Yi Tseng2cf59912017-08-24 14:47:34 -0700820 // check circuit id, if circuit id is invalid, we say it is an indirect host
821 DhcpOption circuitIdOpt = relayAgentOption.getSubOption(CIRCUIT_ID.getValue());
Yi Tseng51301292017-07-28 13:02:59 -0700822
Yi Tseng2cf59912017-08-24 14:47:34 -0700823 try {
824 CircuitId.deserialize(circuitIdOpt.getData());
Yi Tseng51301292017-07-28 13:02:59 -0700825 return true;
Yi Tseng2cf59912017-08-24 14:47:34 -0700826 } catch (Exception e) {
827 // invalid circuit id
828 return false;
Yi Tseng51301292017-07-28 13:02:59 -0700829 }
Yi Tseng51301292017-07-28 13:02:59 -0700830 }
831
832
833 /**
834 * Send the DHCP ack to the requester host.
835 * Modify Host or Route store according to the type of DHCP.
836 *
837 * @param ethernetPacketAck the packet
838 * @param dhcpPayload the DHCP data
839 */
840 private void handleDhcpAck(Ethernet ethernetPacketAck, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700841 Optional<Interface> outInterface = getClientInterface(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700842 if (!outInterface.isPresent()) {
843 log.warn("Can't find output interface for dhcp: {}", dhcpPayload);
844 return;
845 }
846
847 Interface outIface = outInterface.get();
848 HostLocation hostLocation = new HostLocation(outIface.connectPoint(), System.currentTimeMillis());
849 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
Yi Tseng4ec727d2017-08-31 11:21:00 -0700850 VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700851 if (vlanId == null) {
852 vlanId = outIface.vlan();
853 }
Yi Tseng51301292017-07-28 13:02:59 -0700854 HostId hostId = HostId.hostId(macAddress, vlanId);
855 Ip4Address ip = Ip4Address.valueOf(dhcpPayload.getYourIPAddress());
856
857 if (directlyConnected(dhcpPayload)) {
858 // Add to host store if it connect to network directly
859 Set<IpAddress> ips = Sets.newHashSet(ip);
Yi Tseng4b013202017-09-08 17:22:51 -0700860 Host host = hostService.getHost(hostId);
Yi Tseng51301292017-07-28 13:02:59 -0700861
Yi Tseng4b013202017-09-08 17:22:51 -0700862 Set<HostLocation> hostLocations = Sets.newHashSet(hostLocation);
863 if (host != null) {
864 // Dual homing support:
865 // if host exists, use old locations and new location
866 hostLocations.addAll(host.locations());
867 }
868 HostDescription desc = new DefaultHostDescription(macAddress, vlanId,
869 hostLocations, ips, false);
870 // Add IP address when dhcp server give the host new ip address
871 providerService.hostDetected(hostId, desc, false);
Yi Tseng51301292017-07-28 13:02:59 -0700872 } else {
873 // Add to route store if it does not connect to network directly
874 // Get gateway host IP according to host mac address
Yi Tsengdcef2c22017-08-05 20:34:06 -0700875 // TODO: remove relay store here
Yi Tseng51301292017-07-28 13:02:59 -0700876 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
877
878 if (record == null) {
879 log.warn("Can't find DHCP record of host {}", hostId);
880 return;
881 }
882
883 MacAddress gwMac = record.nextHop().orElse(null);
884 if (gwMac == null) {
885 log.warn("Can't find gateway mac address from record {}", record);
886 return;
887 }
888
889 HostId gwHostId = HostId.hostId(gwMac, record.vlanId());
890 Host gwHost = hostService.getHost(gwHostId);
891
892 if (gwHost == null) {
893 log.warn("Can't find gateway host {}", gwHostId);
894 return;
895 }
896
897 Ip4Address nextHopIp = gwHost.ipAddresses()
898 .stream()
899 .filter(IpAddress::isIp4)
900 .map(IpAddress::getIp4Address)
901 .findFirst()
902 .orElse(null);
903
904 if (nextHopIp == null) {
905 log.warn("Can't find IP address of gateway {}", gwHost);
906 return;
907 }
908
909 Route route = new Route(Route.Source.STATIC, ip.toIpPrefix(), nextHopIp);
910 routeStore.updateRoute(route);
911 }
Yi Tseng51301292017-07-28 13:02:59 -0700912 }
913
914 /**
915 * forward the packet to ConnectPoint where the DHCP server is attached.
916 *
917 * @param packet the packet
918 */
Yi Tseng4ec727d2017-08-31 11:21:00 -0700919 private void handleDhcpDiscoverAndRequest(Ethernet packet, DHCP dhcpPayload) {
920 ConnectPoint portToFotward = dhcpServerConnectPoint;
921 if (!directlyConnected(dhcpPayload) && indirectDhcpServerConnectPoint != null) {
922 portToFotward = indirectDhcpServerConnectPoint;
923 }
Yi Tseng51301292017-07-28 13:02:59 -0700924 // send packet to dhcp server connect point.
Yi Tseng4ec727d2017-08-31 11:21:00 -0700925 if (portToFotward != null) {
Yi Tseng51301292017-07-28 13:02:59 -0700926 TrafficTreatment t = DefaultTrafficTreatment.builder()
Yi Tseng4ec727d2017-08-31 11:21:00 -0700927 .setOutput(portToFotward.port()).build();
Yi Tseng51301292017-07-28 13:02:59 -0700928 OutboundPacket o = new DefaultOutboundPacket(
Yi Tseng4ec727d2017-08-31 11:21:00 -0700929 portToFotward.deviceId(), t, ByteBuffer.wrap(packet.serialize()));
Yi Tseng51301292017-07-28 13:02:59 -0700930 if (log.isTraceEnabled()) {
931 log.trace("Relaying packet to dhcp server {}", packet);
932 }
933 packetService.emit(o);
934 } else {
935 log.warn("Can't find DHCP server connect point, abort.");
936 }
937 }
938
939
940 /**
941 * Gets output interface of a dhcp packet.
942 * If option 82 exists in the dhcp packet and the option was sent by
Yi Tseng4ec727d2017-08-31 11:21:00 -0700943 * ONOS (circuit format is correct), use the connect
Yi Tseng51301292017-07-28 13:02:59 -0700944 * point and vlan id from circuit id; otherwise, find host by destination
945 * address and use vlan id from sender (dhcp server).
946 *
947 * @param ethPacket the ethernet packet
948 * @param dhcpPayload the dhcp packet
949 * @return an interface represent the output port and vlan; empty value
950 * if the host or circuit id not found
951 */
Yi Tsengdcef2c22017-08-05 20:34:06 -0700952 private Optional<Interface> getClientInterface(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tseng51301292017-07-28 13:02:59 -0700953 VlanId originalPacketVlanId = VlanId.vlanId(ethPacket.getVlanID());
Yi Tseng51301292017-07-28 13:02:59 -0700954 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
955
Yi Tseng4ec727d2017-08-31 11:21:00 -0700956 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
957 try {
958 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
959 ConnectPoint connectPoint = ConnectPoint.deviceConnectPoint(circuitId.connectPoint());
960 VlanId vlanId = circuitId.vlanId();
961 return interfaceService.getInterfacesByPort(connectPoint)
962 .stream()
963 .filter(iface -> interfaceContainsVlan(iface, vlanId))
964 .findFirst();
965 } catch (IllegalArgumentException ex) {
966 // invalid circuit format, didn't sent by ONOS
967 log.debug("Invalid circuit {}, use information from dhcp payload",
968 circuitIdSubOption.getData());
Yi Tseng51301292017-07-28 13:02:59 -0700969 }
970
971 // Use Vlan Id from DHCP server if DHCP relay circuit id was not
972 // sent by ONOS or circuit Id can't be parsed
Yi Tsengdcef2c22017-08-05 20:34:06 -0700973 // TODO: remove relay store from this method
Yi Tseng51301292017-07-28 13:02:59 -0700974 MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
975 Optional<DhcpRecord> dhcpRecord = dhcpRelayStore.getDhcpRecord(HostId.hostId(dstMac, originalPacketVlanId));
Yi Tsengdcef2c22017-08-05 20:34:06 -0700976 ConnectPoint clientConnectPoint = dhcpRecord
Yi Tseng51301292017-07-28 13:02:59 -0700977 .map(DhcpRecord::locations)
978 .orElse(Collections.emptySet())
979 .stream()
980 .reduce((hl1, hl2) -> {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700981 // find latest host connect point
Yi Tseng51301292017-07-28 13:02:59 -0700982 if (hl1 == null || hl2 == null) {
983 return hl1 == null ? hl2 : hl1;
984 }
985 return hl1.time() > hl2.time() ? hl1 : hl2;
986 })
Yi Tsengdcef2c22017-08-05 20:34:06 -0700987 .orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -0700988
Yi Tsengdcef2c22017-08-05 20:34:06 -0700989 if (clientConnectPoint != null) {
990 return interfaceService.getInterfacesByPort(clientConnectPoint)
991 .stream()
Yi Tseng4ec727d2017-08-31 11:21:00 -0700992 .filter(iface -> interfaceContainsVlan(iface, originalPacketVlanId))
Yi Tsengdcef2c22017-08-05 20:34:06 -0700993 .findFirst();
994 }
995 return Optional.empty();
Yi Tseng51301292017-07-28 13:02:59 -0700996 }
997
998 /**
999 * Send the response DHCP to the requester host.
1000 *
1001 * @param ethPacket the packet
1002 * @param dhcpPayload the DHCP data
1003 */
1004 private void sendResponseToClient(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001005 Optional<Interface> outInterface = getClientInterface(ethPacket, dhcpPayload);
1006 if (directlyConnected(dhcpPayload)) {
1007 ethPacket = removeRelayAgentOption(ethPacket);
1008 }
1009 if (!outInterface.isPresent()) {
1010 log.warn("Can't find output interface for client, ignore");
1011 return;
1012 }
1013 Interface outIface = outInterface.get();
1014 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
1015 .setOutput(outIface.connectPoint().port())
1016 .build();
1017 OutboundPacket o = new DefaultOutboundPacket(
1018 outIface.connectPoint().deviceId(),
1019 treatment,
1020 ByteBuffer.wrap(ethPacket.serialize()));
1021 if (log.isTraceEnabled()) {
1022 log.trace("Relaying packet to DHCP client {} via {}, vlan {}",
1023 ethPacket,
1024 outIface.connectPoint(),
1025 outIface.vlan());
1026 }
1027 packetService.emit(o);
Yi Tseng51301292017-07-28 13:02:59 -07001028 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001029
Yi Tseng4b013202017-09-08 17:22:51 -07001030 @Override
1031 public void triggerProbe(Host host) {
1032 // Do nothing here
1033 }
1034
1035 @Override
1036 public ProviderId id() {
Charles Chan75edab72017-09-12 17:09:32 -07001037 return PROVIDER_ID;
Yi Tseng4b013202017-09-08 17:22:51 -07001038 }
1039
Yi Tsenge72fbb52017-08-02 15:03:31 -07001040 class InternalHostListener implements HostListener {
1041 @Override
1042 public void event(HostEvent event) {
1043 switch (event.type()) {
1044 case HOST_ADDED:
1045 case HOST_UPDATED:
1046 hostUpdated(event.subject());
1047 break;
1048 case HOST_REMOVED:
1049 hostRemoved(event.subject());
1050 break;
1051 case HOST_MOVED:
1052 hostMoved(event.subject());
1053 break;
1054 default:
1055 break;
1056 }
1057 }
1058 }
1059
1060 /**
1061 * Handle host move.
1062 * If the host DHCP server or gateway and it moved to the location different
1063 * to user configured, unsets the connect mac and vlan
1064 *
1065 * @param host the host
1066 */
1067 private void hostMoved(Host host) {
Yi Tsenge72fbb52017-08-02 15:03:31 -07001068 if (this.dhcpGatewayIp != null) {
1069 if (host.ipAddresses().contains(this.dhcpGatewayIp) &&
1070 !host.locations().contains(this.dhcpServerConnectPoint)) {
1071 this.dhcpConnectMac = null;
1072 this.dhcpConnectVlan = null;
1073 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001074 }
1075 if (this.dhcpServerIp != null) {
1076 if (host.ipAddresses().contains(this.dhcpServerIp) &&
1077 !host.locations().contains(this.dhcpServerConnectPoint)) {
1078 this.dhcpConnectMac = null;
1079 this.dhcpConnectVlan = null;
1080 }
1081 }
Yi Tseng4ec727d2017-08-31 11:21:00 -07001082 if (this.indirectDhcpGatewayIp != null) {
1083 if (host.ipAddresses().contains(this.indirectDhcpGatewayIp) &&
1084 !host.locations().contains(this.indirectDhcpServerConnectPoint)) {
1085 this.indirectDhcpConnectMac = null;
1086 this.indirectDhcpConnectVlan = null;
1087 }
1088 }
1089 if (this.indirectDhcpServerIp != null) {
1090 if (host.ipAddresses().contains(this.indirectDhcpServerIp) &&
1091 !host.locations().contains(this.indirectDhcpServerConnectPoint)) {
1092 this.indirectDhcpConnectMac = null;
1093 this.indirectDhcpConnectVlan = null;
1094 }
1095 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001096 }
1097
1098 /**
1099 * Handle host updated.
1100 * If the host is DHCP server or gateway, update connect mac and vlan.
1101 *
1102 * @param host the host
1103 */
1104 private void hostUpdated(Host host) {
1105 if (this.dhcpGatewayIp != null) {
1106 if (host.ipAddresses().contains(this.dhcpGatewayIp)) {
1107 this.dhcpConnectMac = host.mac();
1108 this.dhcpConnectVlan = host.vlan();
1109 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001110 }
1111 if (this.dhcpServerIp != null) {
1112 if (host.ipAddresses().contains(this.dhcpServerIp)) {
1113 this.dhcpConnectMac = host.mac();
1114 this.dhcpConnectVlan = host.vlan();
1115 }
1116 }
Yi Tseng4ec727d2017-08-31 11:21:00 -07001117 if (this.indirectDhcpGatewayIp != null) {
1118 if (host.ipAddresses().contains(this.indirectDhcpGatewayIp)) {
1119 this.indirectDhcpConnectMac = host.mac();
1120 this.indirectDhcpConnectVlan = host.vlan();
1121 }
1122 }
1123 if (this.indirectDhcpServerIp != null) {
1124 if (host.ipAddresses().contains(this.indirectDhcpServerIp)) {
1125 this.indirectDhcpConnectMac = host.mac();
1126 this.indirectDhcpConnectVlan = host.vlan();
1127 }
1128 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001129 }
1130
1131 /**
1132 * Handle host removed.
1133 * If the host is DHCP server or gateway, unset connect mac and vlan.
1134 *
1135 * @param host the host
1136 */
1137 private void hostRemoved(Host host) {
1138 if (this.dhcpGatewayIp != null) {
1139 if (host.ipAddresses().contains(this.dhcpGatewayIp)) {
1140 this.dhcpConnectMac = null;
1141 this.dhcpConnectVlan = null;
1142 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001143 }
1144 if (this.dhcpServerIp != null) {
1145 if (host.ipAddresses().contains(this.dhcpServerIp)) {
1146 this.dhcpConnectMac = null;
1147 this.dhcpConnectVlan = null;
1148 }
1149 }
Yi Tseng4ec727d2017-08-31 11:21:00 -07001150 if (this.indirectDhcpGatewayIp != null) {
1151 if (host.ipAddresses().contains(this.indirectDhcpGatewayIp)) {
1152 this.indirectDhcpConnectMac = null;
1153 this.indirectDhcpConnectVlan = null;
1154 }
1155 }
1156 if (this.indirectDhcpServerIp != null) {
1157 if (host.ipAddresses().contains(this.indirectDhcpServerIp)) {
1158 this.indirectDhcpConnectMac = null;
1159 this.indirectDhcpConnectVlan = null;
1160 }
1161 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001162 }
Yi Tseng51301292017-07-28 13:02:59 -07001163}