blob: f3835c0cd22086a138decaa877ad2f0a0f3ac34e [file] [log] [blame]
Yi Tseng51301292017-07-28 13:02:59 -07001/*
2 * Copyright 2017-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17
18package org.onosproject.dhcprelay;
19
Yi Tsenge72fbb52017-08-02 15:03:31 -070020import com.google.common.base.MoreObjects;
Yi Tseng51301292017-07-28 13:02:59 -070021import com.google.common.collect.Sets;
Yi Tsenge72fbb52017-08-02 15:03:31 -070022import org.apache.felix.scr.annotations.Activate;
Yi Tseng51301292017-07-28 13:02:59 -070023import org.apache.felix.scr.annotations.Component;
Yi Tsenge72fbb52017-08-02 15:03:31 -070024import org.apache.felix.scr.annotations.Deactivate;
Yi Tseng51301292017-07-28 13:02:59 -070025import org.apache.felix.scr.annotations.Property;
26import org.apache.felix.scr.annotations.Reference;
27import org.apache.felix.scr.annotations.ReferenceCardinality;
28import org.apache.felix.scr.annotations.Service;
29import org.onlab.packet.BasePacket;
30import org.onlab.packet.DHCP;
31import org.onlab.packet.Ethernet;
32import org.onlab.packet.IPv4;
33import org.onlab.packet.Ip4Address;
34import org.onlab.packet.IpAddress;
35import org.onlab.packet.MacAddress;
36import org.onlab.packet.UDP;
37import org.onlab.packet.VlanId;
38import org.onlab.packet.dhcp.CircuitId;
39import org.onlab.packet.dhcp.DhcpOption;
40import org.onlab.packet.dhcp.DhcpRelayAgentOption;
41import org.onosproject.dhcprelay.api.DhcpHandler;
Yi Tsenge72fbb52017-08-02 15:03:31 -070042import org.onosproject.dhcprelay.config.DhcpServerConfig;
Yi Tseng51301292017-07-28 13:02:59 -070043import org.onosproject.dhcprelay.store.DhcpRecord;
44import org.onosproject.dhcprelay.store.DhcpRelayStore;
Yi Tsenge72fbb52017-08-02 15:03:31 -070045import org.onosproject.net.host.HostEvent;
46import org.onosproject.net.host.HostListener;
Ray Milkeyfacf2862017-08-03 11:58:29 -070047import org.onosproject.net.intf.Interface;
48import org.onosproject.net.intf.InterfaceService;
Ray Milkey69ec8712017-08-08 13:00:43 -070049import org.onosproject.routeservice.Route;
50import org.onosproject.routeservice.RouteStore;
Yi Tseng51301292017-07-28 13:02:59 -070051import org.onosproject.net.ConnectPoint;
52import org.onosproject.net.Host;
53import org.onosproject.net.HostId;
54import org.onosproject.net.HostLocation;
55import org.onosproject.net.flow.DefaultTrafficTreatment;
56import org.onosproject.net.flow.TrafficTreatment;
57import org.onosproject.net.host.DefaultHostDescription;
58import org.onosproject.net.host.HostDescription;
59import org.onosproject.net.host.HostService;
60import org.onosproject.net.host.HostStore;
61import org.onosproject.net.host.InterfaceIpAddress;
62import org.onosproject.net.packet.DefaultOutboundPacket;
63import org.onosproject.net.packet.OutboundPacket;
64import org.onosproject.net.packet.PacketContext;
65import org.onosproject.net.packet.PacketService;
66import org.slf4j.Logger;
67import org.slf4j.LoggerFactory;
68
69import java.nio.ByteBuffer;
Yi Tsengdcef2c22017-08-05 20:34:06 -070070import java.util.Collection;
Yi Tseng51301292017-07-28 13:02:59 -070071import java.util.Collections;
72import java.util.List;
73import java.util.Optional;
74import java.util.Set;
75import java.util.stream.Collectors;
76
77import static com.google.common.base.Preconditions.checkNotNull;
78import static com.google.common.base.Preconditions.checkState;
79import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_CircuitID;
80import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_END;
81import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
82import static org.onlab.packet.MacAddress.valueOf;
83import static org.onlab.packet.dhcp.DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID;
84
85@Component
86@Service
87@Property(name = "version", value = "4")
88public class Dhcp4HandlerImpl implements DhcpHandler {
89 private static Logger log = LoggerFactory.getLogger(Dhcp4HandlerImpl.class);
90
91 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
92 protected DhcpRelayStore dhcpRelayStore;
93
94 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
95 protected PacketService packetService;
96
97 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
98 protected HostStore hostStore;
99
100 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
101 protected RouteStore routeStore;
102
103 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
104 protected InterfaceService interfaceService;
105
106 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
107 protected HostService hostService;
108
Yi Tsenge72fbb52017-08-02 15:03:31 -0700109 private InternalHostListener hostListener = new InternalHostListener();
110
Yi Tseng51301292017-07-28 13:02:59 -0700111 private Ip4Address dhcpServerIp = null;
112 // dhcp server may be connected directly to the SDN network or
113 // via an external gateway. When connected directly, the dhcpConnectPoint, dhcpConnectMac,
114 // and dhcpConnectVlan refer to the server. When connected via the gateway, they refer
115 // to the gateway.
116 private ConnectPoint dhcpServerConnectPoint = null;
117 private MacAddress dhcpConnectMac = null;
118 private VlanId dhcpConnectVlan = null;
119 private Ip4Address dhcpGatewayIp = null;
Yi Tseng4fa05832017-08-17 13:08:31 -0700120 private Ip4Address relayAgentIp = null;
Yi Tseng51301292017-07-28 13:02:59 -0700121
Yi Tsenge72fbb52017-08-02 15:03:31 -0700122 @Activate
123 protected void activate() {
124 hostService.addListener(hostListener);
125 }
126
127 @Deactivate
128 protected void deactivate() {
129 hostService.removeListener(hostListener);
130 this.dhcpConnectMac = null;
131 this.dhcpConnectVlan = null;
Yi Tseng4fa05832017-08-17 13:08:31 -0700132
133 if (dhcpGatewayIp != null) {
134 hostService.stopMonitoringIp(dhcpGatewayIp);
135 } else if (dhcpServerIp != null) {
136 hostService.stopMonitoringIp(dhcpServerIp);
137 }
Yi Tsenge72fbb52017-08-02 15:03:31 -0700138 }
139
Yi Tseng51301292017-07-28 13:02:59 -0700140 @Override
141 public void setDhcpServerIp(IpAddress dhcpServerIp) {
142 checkNotNull(dhcpServerIp, "DHCP server IP can't be null");
143 checkState(dhcpServerIp.isIp4(), "Invalid server IP for DHCPv4 relay handler");
144 this.dhcpServerIp = dhcpServerIp.getIp4Address();
145 }
146
147 @Override
148 public void setDhcpServerConnectPoint(ConnectPoint dhcpServerConnectPoint) {
149 checkNotNull(dhcpServerConnectPoint, "Server connect point can't null");
150 this.dhcpServerConnectPoint = dhcpServerConnectPoint;
151 }
152
153 @Override
154 public void setDhcpConnectMac(MacAddress dhcpConnectMac) {
155 this.dhcpConnectMac = dhcpConnectMac;
156 }
157
158 @Override
159 public void setDhcpConnectVlan(VlanId dhcpConnectVlan) {
160 this.dhcpConnectVlan = dhcpConnectVlan;
161 }
162
163 @Override
164 public void setDhcpGatewayIp(IpAddress dhcpGatewayIp) {
165 if (dhcpGatewayIp != null) {
166 checkState(dhcpGatewayIp.isIp4(), "Invalid gateway IP for DHCPv4 relay handler");
167 this.dhcpGatewayIp = dhcpGatewayIp.getIp4Address();
168 } else {
169 // removes gateway config
170 this.dhcpGatewayIp = null;
171 }
172 }
173
174 @Override
175 public Optional<IpAddress> getDhcpServerIp() {
176 return Optional.ofNullable(dhcpServerIp);
177 }
178
179 @Override
180 public Optional<IpAddress> getDhcpGatewayIp() {
181 return Optional.ofNullable(dhcpGatewayIp);
182 }
183
184 @Override
185 public Optional<MacAddress> getDhcpConnectMac() {
186 return Optional.ofNullable(dhcpConnectMac);
187 }
188
189 @Override
Yi Tsenge72fbb52017-08-02 15:03:31 -0700190 public void setDefaultDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
191 if (configs.size() == 0) {
192 // no config to update
193 return;
194 }
195
196 // TODO: currently we pick up first DHCP server config.
197 // Will use other server configs in the future for HA.
198 DhcpServerConfig serverConfig = configs.iterator().next();
Yi Tsengaefbb002017-09-08 16:23:32 -0700199 if (!serverConfig.getDhcpServerConnectPoint().isPresent()) {
200 log.warn("Connect point from server config not exists");
201 return;
202 }
203 if (!serverConfig.getDhcpServerIp4().isPresent()) {
204 log.warn("IP from DHCP server config not exists");
205 return;
206 }
Yi Tsenge72fbb52017-08-02 15:03:31 -0700207 Ip4Address oldServerIp = this.dhcpServerIp;
208 Ip4Address oldGatewayIp = this.dhcpGatewayIp;
209
210 // stop monitoring gateway or server
211 if (oldGatewayIp != null) {
212 hostService.stopMonitoringIp(oldGatewayIp);
213 } else if (oldServerIp != null) {
214 hostService.stopMonitoringIp(oldServerIp);
215 }
216
217 this.dhcpServerConnectPoint = serverConfig.getDhcpServerConnectPoint().get();
218 this.dhcpServerIp = serverConfig.getDhcpServerIp4().get();
219 this.dhcpGatewayIp = serverConfig.getDhcpGatewayIp4().orElse(null);
220
221 // reset server mac and vlan
222 this.dhcpConnectMac = null;
223 this.dhcpConnectVlan = null;
224
225 log.info("DHCP server connect point: " + this.dhcpServerConnectPoint);
226 log.info("DHCP server IP: " + this.dhcpServerIp);
227
228 IpAddress ipToProbe = MoreObjects.firstNonNull(this.dhcpGatewayIp, this.dhcpServerIp);
229 String hostToProbe = this.dhcpGatewayIp != null ? "gateway" : "DHCP server";
230
231 if (ipToProbe == null) {
232 log.warn("Server IP not set, can't probe it");
233 return;
234 }
235
236 log.info("Probing to resolve {} IP {}", hostToProbe, ipToProbe);
237 hostService.startMonitoringIp(ipToProbe);
238
239 Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
240 if (!hosts.isEmpty()) {
241 Host host = hosts.iterator().next();
242 this.dhcpConnectVlan = host.vlan();
243 this.dhcpConnectMac = host.mac();
244 }
Yi Tseng4fa05832017-08-17 13:08:31 -0700245
246 this.relayAgentIp = serverConfig.getRelayAgentIp4().orElse(null);
Yi Tsenge72fbb52017-08-02 15:03:31 -0700247 }
248
249 @Override
250 public void setIndirectDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
251 log.warn("Indirect config feature for DHCPv4 handler not implement yet");
252 }
253
Yi Tseng4fa05832017-08-17 13:08:31 -0700254 @Override
Yi Tseng51301292017-07-28 13:02:59 -0700255 public void processDhcpPacket(PacketContext context, BasePacket payload) {
256 checkNotNull(payload, "DHCP payload can't be null");
257 checkState(payload instanceof DHCP, "Payload is not a DHCP");
258 DHCP dhcpPayload = (DHCP) payload;
259 if (!configured()) {
260 log.warn("Missing DHCP relay server config. Abort packet processing");
261 return;
262 }
263
264 ConnectPoint inPort = context.inPacket().receivedFrom();
Yi Tseng51301292017-07-28 13:02:59 -0700265 checkNotNull(dhcpPayload, "Can't find DHCP payload");
266 Ethernet packet = context.inPacket().parsed();
267 DHCP.MsgType incomingPacketType = dhcpPayload.getOptions().stream()
268 .filter(dhcpOption -> dhcpOption.getCode() == OptionCode_MessageType.getValue())
269 .map(DhcpOption::getData)
270 .map(data -> DHCP.MsgType.getType(data[0]))
271 .findFirst()
272 .orElse(null);
273 checkNotNull(incomingPacketType, "Can't get message type from DHCP payload {}", dhcpPayload);
274 switch (incomingPacketType) {
275 case DHCPDISCOVER:
Yi Tsengdcef2c22017-08-05 20:34:06 -0700276 // Try update host if it is directly connected.
277 if (directlyConnected(dhcpPayload)) {
278 updateHost(context, dhcpPayload);
279 }
280
281 // Add the gateway IP as virtual interface IP for server to understand
Yi Tseng51301292017-07-28 13:02:59 -0700282 // the lease to be assigned and forward the packet to dhcp server.
283 Ethernet ethernetPacketDiscover =
Yi Tsengdcef2c22017-08-05 20:34:06 -0700284 processDhcpPacketFromClient(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700285 if (ethernetPacketDiscover != null) {
286 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
287 handleDhcpDiscoverAndRequest(ethernetPacketDiscover);
288 }
289 break;
290 case DHCPOFFER:
291 //reply to dhcp client.
292 Ethernet ethernetPacketOffer = processDhcpPacketFromServer(packet);
293 if (ethernetPacketOffer != null) {
294 writeResponseDhcpRecord(ethernetPacketOffer, dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700295 sendResponseToClient(ethernetPacketOffer, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700296 }
297 break;
298 case DHCPREQUEST:
299 // add the gateway ip as virtual interface ip for server to understand
300 // the lease to be assigned and forward the packet to dhcp server.
301 Ethernet ethernetPacketRequest =
Yi Tsengdcef2c22017-08-05 20:34:06 -0700302 processDhcpPacketFromClient(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700303 if (ethernetPacketRequest != null) {
304 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
305 handleDhcpDiscoverAndRequest(ethernetPacketRequest);
306 }
307 break;
308 case DHCPACK:
309 // reply to dhcp client.
310 Ethernet ethernetPacketAck = processDhcpPacketFromServer(packet);
311 if (ethernetPacketAck != null) {
312 writeResponseDhcpRecord(ethernetPacketAck, dhcpPayload);
313 handleDhcpAck(ethernetPacketAck, dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700314 sendResponseToClient(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700315 }
316 break;
317 case DHCPRELEASE:
318 // TODO: release the ip address from client
319 break;
320 default:
321 break;
322 }
323 }
324
325 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -0700326 * Updates host to host store according to DHCP payload.
327 *
328 * @param context the packet context
329 * @param dhcpPayload the DHCP payload
330 */
331 private void updateHost(PacketContext context, DHCP dhcpPayload) {
332 ConnectPoint location = context.inPacket().receivedFrom();
333 HostLocation hostLocation = new HostLocation(location, System.currentTimeMillis());
334 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
335 VlanId vlanId = VlanId.vlanId(context.inPacket().parsed().getVlanID());
336 HostId hostId = HostId.hostId(macAddress, vlanId);
337 HostDescription desc = new DefaultHostDescription(macAddress, vlanId, hostLocation);
338 hostStore.createOrUpdateHost(DhcpRelayManager.PROVIDER_ID, hostId, desc, false);
339 }
340
341 /**
Yi Tseng51301292017-07-28 13:02:59 -0700342 * Checks if this app has been configured.
343 *
344 * @return true if all information we need have been initialized
345 */
346 public boolean configured() {
347 return dhcpServerConnectPoint != null && dhcpServerIp != null;
348 }
349
350 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -0700351 * Returns the first interface ip from interface.
Yi Tseng51301292017-07-28 13:02:59 -0700352 *
Yi Tsengdcef2c22017-08-05 20:34:06 -0700353 * @param iface interface of one connect point
Yi Tseng51301292017-07-28 13:02:59 -0700354 * @return the first interface IP; null if not exists an IP address in
355 * these interfaces
356 */
Yi Tseng4fa05832017-08-17 13:08:31 -0700357 private Ip4Address getFirstIpFromInterface(Interface iface) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700358 checkNotNull(iface, "Interface can't be null");
359 return iface.ipAddressesList().stream()
Yi Tseng51301292017-07-28 13:02:59 -0700360 .map(InterfaceIpAddress::ipAddress)
361 .filter(IpAddress::isIp4)
362 .map(IpAddress::getIp4Address)
363 .findFirst()
364 .orElse(null);
365 }
366
367 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -0700368 * Gets Interface facing to the server.
369 *
370 * @return the Interface facing to the server; null if not found
371 */
372 public Interface getServerInterface() {
373 if (dhcpServerConnectPoint == null || dhcpConnectVlan == null) {
374 return null;
375 }
376 return interfaceService.getInterfacesByPort(dhcpServerConnectPoint)
377 .stream()
378 .filter(iface -> iface.vlan().equals(dhcpConnectVlan) ||
379 iface.vlanUntagged().equals(dhcpConnectVlan) ||
380 iface.vlanTagged().contains(dhcpConnectVlan) ||
381 iface.vlanNative().equals(dhcpConnectVlan))
382 .findFirst()
383 .orElse(null);
384 }
385
386 /**
Yi Tseng51301292017-07-28 13:02:59 -0700387 * Build the DHCP discover/request packet with gateway IP(unicast packet).
388 *
389 * @param context the packet context
390 * @param ethernetPacket the ethernet payload to process
Yi Tseng51301292017-07-28 13:02:59 -0700391 * @return processed packet
392 */
393 private Ethernet processDhcpPacketFromClient(PacketContext context,
Yi Tsengdcef2c22017-08-05 20:34:06 -0700394 Ethernet ethernetPacket) {
395 Ip4Address clientInterfaceIp =
396 interfaceService.getInterfacesByPort(context.inPacket().receivedFrom())
397 .stream()
398 .map(Interface::ipAddressesList)
399 .flatMap(Collection::stream)
400 .map(InterfaceIpAddress::ipAddress)
401 .filter(IpAddress::isIp4)
402 .map(IpAddress::getIp4Address)
403 .findFirst()
404 .orElse(null);
405 if (clientInterfaceIp == null) {
406 log.warn("Can't find interface IP for client interface for port {}",
407 context.inPacket().receivedFrom());
408 return null;
409 }
410 Interface serverInterface = getServerInterface();
411 if (serverInterface == null) {
412 log.warn("Can't get server interface, ignore");
413 return null;
414 }
Yi Tseng4fa05832017-08-17 13:08:31 -0700415 Ip4Address ipFacingServer = getFirstIpFromInterface(serverInterface);
416 MacAddress macFacingServer = serverInterface.mac();
417 if (ipFacingServer == null || macFacingServer == null) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700418 log.warn("No IP address for server Interface {}", serverInterface);
Yi Tseng51301292017-07-28 13:02:59 -0700419 return null;
420 }
421 if (dhcpConnectMac == null) {
422 log.warn("DHCP {} not yet resolved .. Aborting DHCP "
423 + "packet processing from client on port: {}",
424 (dhcpGatewayIp == null) ? "server IP " + dhcpServerIp
425 : "gateway IP " + dhcpGatewayIp,
Yi Tsengdcef2c22017-08-05 20:34:06 -0700426 context.inPacket().receivedFrom());
Yi Tseng51301292017-07-28 13:02:59 -0700427 return null;
428 }
429 // get dhcp header.
430 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
Yi Tseng4fa05832017-08-17 13:08:31 -0700431 etherReply.setSourceMACAddress(macFacingServer);
Yi Tseng51301292017-07-28 13:02:59 -0700432 etherReply.setDestinationMACAddress(dhcpConnectMac);
433 etherReply.setVlanID(dhcpConnectVlan.toShort());
434 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
Yi Tseng4fa05832017-08-17 13:08:31 -0700435 ipv4Packet.setSourceAddress(ipFacingServer.toInt());
Yi Tseng51301292017-07-28 13:02:59 -0700436 ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
437 UDP udpPacket = (UDP) ipv4Packet.getPayload();
438 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
439
Yi Tsengdcef2c22017-08-05 20:34:06 -0700440 if (directlyConnected(dhcpPacket)) {
Yi Tseng51301292017-07-28 13:02:59 -0700441 ConnectPoint inPort = context.inPacket().receivedFrom();
442 VlanId vlanId = VlanId.vlanId(ethernetPacket.getVlanID());
443 // add connected in port and vlan
444 CircuitId cid = new CircuitId(inPort.toString(), vlanId);
445 byte[] circuitId = cid.serialize();
446 DhcpOption circuitIdSubOpt = new DhcpOption();
447 circuitIdSubOpt
448 .setCode(CIRCUIT_ID.getValue())
449 .setLength((byte) circuitId.length)
450 .setData(circuitId);
451
452 DhcpRelayAgentOption newRelayAgentOpt = new DhcpRelayAgentOption();
453 newRelayAgentOpt.setCode(OptionCode_CircuitID.getValue());
454 newRelayAgentOpt.addSubOption(circuitIdSubOpt);
455
456 // Removes END option first
457 List<DhcpOption> options = dhcpPacket.getOptions().stream()
458 .filter(opt -> opt.getCode() != OptionCode_END.getValue())
459 .collect(Collectors.toList());
460
461 // push relay agent option
462 options.add(newRelayAgentOpt);
463
464 // make sure option 255(End) is the last option
465 DhcpOption endOption = new DhcpOption();
466 endOption.setCode(OptionCode_END.getValue());
467 options.add(endOption);
468
469 dhcpPacket.setOptions(options);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700470
471 // Sets giaddr to IP address from the Interface which facing to
472 // DHCP client
473 dhcpPacket.setGatewayIPAddress(clientInterfaceIp.toInt());
Yi Tseng51301292017-07-28 13:02:59 -0700474 }
475
Yi Tseng4fa05832017-08-17 13:08:31 -0700476 // replace giaddr if relay agent IP is set
477 // FIXME for both direct and indirect case now, should be separated
478 if (relayAgentIp != null) {
479 dhcpPacket.setGatewayIPAddress(relayAgentIp.toInt());
480 }
481
Yi Tseng51301292017-07-28 13:02:59 -0700482 udpPacket.setPayload(dhcpPacket);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700483 // As a DHCP relay, the source port should be server port(67) instead
484 // of client port(68)
485 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
Yi Tseng51301292017-07-28 13:02:59 -0700486 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
487 ipv4Packet.setPayload(udpPacket);
488 etherReply.setPayload(ipv4Packet);
489 return etherReply;
490 }
491
492 /**
493 * Writes DHCP record to the store according to the request DHCP packet (Discover, Request).
494 *
495 * @param location the location which DHCP packet comes from
496 * @param ethernet the DHCP packet
497 * @param dhcpPayload the DHCP payload
498 */
499 private void writeRequestDhcpRecord(ConnectPoint location,
500 Ethernet ethernet,
501 DHCP dhcpPayload) {
502 VlanId vlanId = VlanId.vlanId(ethernet.getVlanID());
503 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
504 HostId hostId = HostId.hostId(macAddress, vlanId);
505 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
506 if (record == null) {
507 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
508 } else {
509 record = record.clone();
510 }
511 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
512 record.ip4Status(dhcpPayload.getPacketType());
513 record.setDirectlyConnected(directlyConnected(dhcpPayload));
514 if (!directlyConnected(dhcpPayload)) {
515 // Update gateway mac address if the host is not directly connected
516 record.nextHop(ethernet.getSourceMAC());
517 }
518 record.updateLastSeen();
519 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
520 }
521
522 /**
523 * Writes DHCP record to the store according to the response DHCP packet (Offer, Ack).
524 *
525 * @param ethernet the DHCP packet
526 * @param dhcpPayload the DHCP payload
527 */
528 private void writeResponseDhcpRecord(Ethernet ethernet,
529 DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700530 Optional<Interface> outInterface = getClientInterface(ethernet, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700531 if (!outInterface.isPresent()) {
532 log.warn("Failed to determine where to send {}", dhcpPayload.getPacketType());
533 return;
534 }
535
536 Interface outIface = outInterface.get();
537 ConnectPoint location = outIface.connectPoint();
Yi Tsengdcef2c22017-08-05 20:34:06 -0700538 VlanId vlanId = getVlanIdFromOption(dhcpPayload);
539 if (vlanId == null) {
540 vlanId = outIface.vlan();
541 }
Yi Tseng51301292017-07-28 13:02:59 -0700542 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
543 HostId hostId = HostId.hostId(macAddress, vlanId);
544 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
545 if (record == null) {
546 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
547 } else {
548 record = record.clone();
549 }
550 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
551 if (dhcpPayload.getPacketType() == DHCP.MsgType.DHCPACK) {
552 record.ip4Address(Ip4Address.valueOf(dhcpPayload.getYourIPAddress()));
553 }
554 record.ip4Status(dhcpPayload.getPacketType());
555 record.setDirectlyConnected(directlyConnected(dhcpPayload));
556 record.updateLastSeen();
557 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
558 }
559
560 /**
561 * Build the DHCP offer/ack with proper client port.
562 *
563 * @param ethernetPacket the original packet comes from server
564 * @return new packet which will send to the client
565 */
566 private Ethernet processDhcpPacketFromServer(Ethernet ethernetPacket) {
567 // get dhcp header.
568 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
569 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
570 UDP udpPacket = (UDP) ipv4Packet.getPayload();
571 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
572
573 // determine the vlanId of the client host - note that this vlan id
574 // could be different from the vlan in the packet from the server
Yi Tsengdcef2c22017-08-05 20:34:06 -0700575 Interface clientInterface = getClientInterface(ethernetPacket, dhcpPayload).orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -0700576
Yi Tsengdcef2c22017-08-05 20:34:06 -0700577 if (clientInterface == null) {
Yi Tseng51301292017-07-28 13:02:59 -0700578 log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
579 return null;
580 }
Yi Tsengdcef2c22017-08-05 20:34:06 -0700581 VlanId vlanId;
582 if (clientInterface.vlanTagged().isEmpty()) {
583 vlanId = clientInterface.vlan();
584 } else {
585 // might be multiple vlan in same interface
586 vlanId = getVlanIdFromOption(dhcpPayload);
587 }
588 if (vlanId == null) {
589 vlanId = VlanId.NONE;
590 }
591 etherReply.setVlanID(vlanId.toShort());
592 etherReply.setSourceMACAddress(clientInterface.mac());
Yi Tseng51301292017-07-28 13:02:59 -0700593
Yi Tsengdcef2c22017-08-05 20:34:06 -0700594 if (!directlyConnected(dhcpPayload)) {
595 // if client is indirectly connected, try use next hop mac address
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 // if next hop can be found, use mac address of next hop
601 record.nextHop().ifPresent(etherReply::setDestinationMACAddress);
602 } else {
603 // otherwise, discard the packet
604 log.warn("Can't find record for host id {}, discard packet", hostId);
605 return null;
606 }
Yi Tsengc03fa242017-08-17 17:43:38 -0700607 } else {
608 etherReply.setDestinationMACAddress(dhcpPayload.getClientHardwareAddress());
Yi Tsengdcef2c22017-08-05 20:34:06 -0700609 }
610
Yi Tseng51301292017-07-28 13:02:59 -0700611 // we leave the srcMac from the original packet
Yi Tseng51301292017-07-28 13:02:59 -0700612 // figure out the relay agent IP corresponding to the original request
Yi Tseng4fa05832017-08-17 13:08:31 -0700613 Ip4Address ipFacingClient = getFirstIpFromInterface(clientInterface);
614 if (ipFacingClient == null) {
Yi Tseng51301292017-07-28 13:02:59 -0700615 log.warn("Cannot determine relay agent interface Ipv4 addr for host {}/{}. "
616 + "Aborting relay for dhcp packet from server {}",
Yi Tsengdcef2c22017-08-05 20:34:06 -0700617 etherReply.getDestinationMAC(), clientInterface.vlan(),
Yi Tseng51301292017-07-28 13:02:59 -0700618 ethernetPacket);
619 return null;
620 }
621 // SRC_IP: relay agent IP
622 // DST_IP: offered IP
Yi Tseng4fa05832017-08-17 13:08:31 -0700623 ipv4Packet.setSourceAddress(ipFacingClient.toInt());
Yi Tseng51301292017-07-28 13:02:59 -0700624 ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
625 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
626 if (directlyConnected(dhcpPayload)) {
627 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
628 } else {
629 // forward to another dhcp relay
630 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
631 }
632
633 udpPacket.setPayload(dhcpPayload);
634 ipv4Packet.setPayload(udpPacket);
635 etherReply.setPayload(ipv4Packet);
636 return etherReply;
637 }
638
Yi Tsengdcef2c22017-08-05 20:34:06 -0700639 /**
640 * Extracts VLAN ID from relay agent option.
641 *
642 * @param dhcpPayload the DHCP payload
643 * @return VLAN ID from DHCP payload; null if not exists
644 */
645 private VlanId getVlanIdFromOption(DHCP dhcpPayload) {
646 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
647 if (option == null) {
648 return null;
649 }
650 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
651 if (circuitIdSubOption == null) {
652 return null;
653 }
654 try {
655 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
656 return circuitId.vlanId();
657 } catch (IllegalArgumentException e) {
658 // can't deserialize the circuit ID
659 return null;
660 }
661 }
662
663 /**
664 * Removes DHCP relay agent information option (option 82) from DHCP payload.
665 * Also reset giaddr to 0
666 *
667 * @param ethPacket the Ethernet packet to be processed
668 * @return Ethernet packet processed
669 */
670 private Ethernet removeRelayAgentOption(Ethernet ethPacket) {
671 Ethernet ethernet = (Ethernet) ethPacket.clone();
672 IPv4 ipv4 = (IPv4) ethernet.getPayload();
673 UDP udp = (UDP) ipv4.getPayload();
674 DHCP dhcpPayload = (DHCP) udp.getPayload();
675
676 // removes relay agent information option
677 List<DhcpOption> options = dhcpPayload.getOptions();
678 options = options.stream()
679 .filter(option -> option.getCode() != OptionCode_CircuitID.getValue())
680 .collect(Collectors.toList());
681 dhcpPayload.setOptions(options);
682 dhcpPayload.setGatewayIPAddress(0);
683
684 udp.setPayload(dhcpPayload);
685 ipv4.setPayload(udp);
686 ethernet.setPayload(ipv4);
687 return ethernet;
688 }
689
Yi Tseng51301292017-07-28 13:02:59 -0700690
691 /**
692 * Check if the host is directly connected to the network or not.
693 *
694 * @param dhcpPayload the dhcp payload
695 * @return true if the host is directly connected to the network; false otherwise
696 */
697 private boolean directlyConnected(DHCP dhcpPayload) {
Yi Tseng2cf59912017-08-24 14:47:34 -0700698 DhcpRelayAgentOption relayAgentOption =
699 (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
Yi Tseng51301292017-07-28 13:02:59 -0700700
701 // Doesn't contains relay option
702 if (relayAgentOption == null) {
703 return true;
704 }
705
Yi Tseng2cf59912017-08-24 14:47:34 -0700706 // check circuit id, if circuit id is invalid, we say it is an indirect host
707 DhcpOption circuitIdOpt = relayAgentOption.getSubOption(CIRCUIT_ID.getValue());
Yi Tseng51301292017-07-28 13:02:59 -0700708
Yi Tseng2cf59912017-08-24 14:47:34 -0700709 try {
710 CircuitId.deserialize(circuitIdOpt.getData());
Yi Tseng51301292017-07-28 13:02:59 -0700711 return true;
Yi Tseng2cf59912017-08-24 14:47:34 -0700712 } catch (Exception e) {
713 // invalid circuit id
714 return false;
Yi Tseng51301292017-07-28 13:02:59 -0700715 }
Yi Tseng51301292017-07-28 13:02:59 -0700716 }
717
718
719 /**
720 * Send the DHCP ack to the requester host.
721 * Modify Host or Route store according to the type of DHCP.
722 *
723 * @param ethernetPacketAck the packet
724 * @param dhcpPayload the DHCP data
725 */
726 private void handleDhcpAck(Ethernet ethernetPacketAck, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700727 Optional<Interface> outInterface = getClientInterface(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700728 if (!outInterface.isPresent()) {
729 log.warn("Can't find output interface for dhcp: {}", dhcpPayload);
730 return;
731 }
732
733 Interface outIface = outInterface.get();
734 HostLocation hostLocation = new HostLocation(outIface.connectPoint(), System.currentTimeMillis());
735 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
Yi Tsengdcef2c22017-08-05 20:34:06 -0700736 VlanId vlanId = getVlanIdFromOption(dhcpPayload);
737 if (vlanId == null) {
738 vlanId = outIface.vlan();
739 }
Yi Tseng51301292017-07-28 13:02:59 -0700740 HostId hostId = HostId.hostId(macAddress, vlanId);
741 Ip4Address ip = Ip4Address.valueOf(dhcpPayload.getYourIPAddress());
742
743 if (directlyConnected(dhcpPayload)) {
744 // Add to host store if it connect to network directly
745 Set<IpAddress> ips = Sets.newHashSet(ip);
746 HostDescription desc = new DefaultHostDescription(macAddress, vlanId,
747 hostLocation, ips);
748
749 // Replace the ip when dhcp server give the host new ip address
750 hostStore.createOrUpdateHost(DhcpRelayManager.PROVIDER_ID, hostId, desc, false);
751 } else {
752 // Add to route store if it does not connect to network directly
753 // Get gateway host IP according to host mac address
Yi Tsengdcef2c22017-08-05 20:34:06 -0700754 // TODO: remove relay store here
Yi Tseng51301292017-07-28 13:02:59 -0700755 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
756
757 if (record == null) {
758 log.warn("Can't find DHCP record of host {}", hostId);
759 return;
760 }
761
762 MacAddress gwMac = record.nextHop().orElse(null);
763 if (gwMac == null) {
764 log.warn("Can't find gateway mac address from record {}", record);
765 return;
766 }
767
768 HostId gwHostId = HostId.hostId(gwMac, record.vlanId());
769 Host gwHost = hostService.getHost(gwHostId);
770
771 if (gwHost == null) {
772 log.warn("Can't find gateway host {}", gwHostId);
773 return;
774 }
775
776 Ip4Address nextHopIp = gwHost.ipAddresses()
777 .stream()
778 .filter(IpAddress::isIp4)
779 .map(IpAddress::getIp4Address)
780 .findFirst()
781 .orElse(null);
782
783 if (nextHopIp == null) {
784 log.warn("Can't find IP address of gateway {}", gwHost);
785 return;
786 }
787
788 Route route = new Route(Route.Source.STATIC, ip.toIpPrefix(), nextHopIp);
789 routeStore.updateRoute(route);
790 }
Yi Tseng51301292017-07-28 13:02:59 -0700791 }
792
793 /**
794 * forward the packet to ConnectPoint where the DHCP server is attached.
795 *
796 * @param packet the packet
797 */
798 private void handleDhcpDiscoverAndRequest(Ethernet packet) {
799 // send packet to dhcp server connect point.
800 if (dhcpServerConnectPoint != null) {
801 TrafficTreatment t = DefaultTrafficTreatment.builder()
802 .setOutput(dhcpServerConnectPoint.port()).build();
803 OutboundPacket o = new DefaultOutboundPacket(
804 dhcpServerConnectPoint.deviceId(), t, ByteBuffer.wrap(packet.serialize()));
805 if (log.isTraceEnabled()) {
806 log.trace("Relaying packet to dhcp server {}", packet);
807 }
808 packetService.emit(o);
809 } else {
810 log.warn("Can't find DHCP server connect point, abort.");
811 }
812 }
813
814
815 /**
816 * Gets output interface of a dhcp packet.
817 * If option 82 exists in the dhcp packet and the option was sent by
818 * ONOS (gateway address exists in ONOS interfaces), use the connect
819 * point and vlan id from circuit id; otherwise, find host by destination
820 * address and use vlan id from sender (dhcp server).
821 *
822 * @param ethPacket the ethernet packet
823 * @param dhcpPayload the dhcp packet
824 * @return an interface represent the output port and vlan; empty value
825 * if the host or circuit id not found
826 */
Yi Tsengdcef2c22017-08-05 20:34:06 -0700827 private Optional<Interface> getClientInterface(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tseng51301292017-07-28 13:02:59 -0700828 VlanId originalPacketVlanId = VlanId.vlanId(ethPacket.getVlanID());
829 IpAddress gatewayIpAddress = Ip4Address.valueOf(dhcpPayload.getGatewayIPAddress());
Yi Tsengdcef2c22017-08-05 20:34:06 -0700830
831 // get all possible interfaces for client
832 Set<Interface> clientInterfaces = interfaceService.getInterfacesByIp(gatewayIpAddress);
Yi Tseng51301292017-07-28 13:02:59 -0700833 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
834
835 // Sent by ONOS, and contains circuit id
Yi Tsengdcef2c22017-08-05 20:34:06 -0700836 if (!clientInterfaces.isEmpty() && option != null) {
Yi Tseng51301292017-07-28 13:02:59 -0700837 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
838 try {
839 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
840 ConnectPoint connectPoint = ConnectPoint.deviceConnectPoint(circuitId.connectPoint());
841 VlanId vlanId = circuitId.vlanId();
Yi Tsengdcef2c22017-08-05 20:34:06 -0700842 return clientInterfaces.stream()
843 .filter(iface -> iface.vlanUntagged().equals(vlanId) ||
844 iface.vlan().equals(vlanId) ||
845 iface.vlanNative().equals(vlanId) ||
846 iface.vlanTagged().contains(vlanId))
847 .filter(iface -> iface.connectPoint().equals(connectPoint))
848 .findFirst();
Yi Tseng51301292017-07-28 13:02:59 -0700849 } catch (IllegalArgumentException ex) {
850 // invalid circuit format, didn't sent by ONOS
851 log.debug("Invalid circuit {}, use information from dhcp payload",
852 circuitIdSubOption.getData());
853 }
854 }
855
856 // Use Vlan Id from DHCP server if DHCP relay circuit id was not
857 // sent by ONOS or circuit Id can't be parsed
Yi Tsengdcef2c22017-08-05 20:34:06 -0700858 // TODO: remove relay store from this method
Yi Tseng51301292017-07-28 13:02:59 -0700859 MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
860 Optional<DhcpRecord> dhcpRecord = dhcpRelayStore.getDhcpRecord(HostId.hostId(dstMac, originalPacketVlanId));
Yi Tsengdcef2c22017-08-05 20:34:06 -0700861 ConnectPoint clientConnectPoint = dhcpRecord
Yi Tseng51301292017-07-28 13:02:59 -0700862 .map(DhcpRecord::locations)
863 .orElse(Collections.emptySet())
864 .stream()
865 .reduce((hl1, hl2) -> {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700866 // find latest host connect point
Yi Tseng51301292017-07-28 13:02:59 -0700867 if (hl1 == null || hl2 == null) {
868 return hl1 == null ? hl2 : hl1;
869 }
870 return hl1.time() > hl2.time() ? hl1 : hl2;
871 })
Yi Tsengdcef2c22017-08-05 20:34:06 -0700872 .orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -0700873
Yi Tsengdcef2c22017-08-05 20:34:06 -0700874 if (clientConnectPoint != null) {
875 return interfaceService.getInterfacesByPort(clientConnectPoint)
876 .stream()
877 .filter(iface -> iface.vlan().equals(originalPacketVlanId) ||
878 iface.vlanUntagged().equals(originalPacketVlanId))
879 .findFirst();
880 }
881 return Optional.empty();
Yi Tseng51301292017-07-28 13:02:59 -0700882 }
883
884 /**
885 * Send the response DHCP to the requester host.
886 *
887 * @param ethPacket the packet
888 * @param dhcpPayload the DHCP data
889 */
890 private void sendResponseToClient(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700891 Optional<Interface> outInterface = getClientInterface(ethPacket, dhcpPayload);
892 if (directlyConnected(dhcpPayload)) {
893 ethPacket = removeRelayAgentOption(ethPacket);
894 }
895 if (!outInterface.isPresent()) {
896 log.warn("Can't find output interface for client, ignore");
897 return;
898 }
899 Interface outIface = outInterface.get();
900 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
901 .setOutput(outIface.connectPoint().port())
902 .build();
903 OutboundPacket o = new DefaultOutboundPacket(
904 outIface.connectPoint().deviceId(),
905 treatment,
906 ByteBuffer.wrap(ethPacket.serialize()));
907 if (log.isTraceEnabled()) {
908 log.trace("Relaying packet to DHCP client {} via {}, vlan {}",
909 ethPacket,
910 outIface.connectPoint(),
911 outIface.vlan());
912 }
913 packetService.emit(o);
Yi Tseng51301292017-07-28 13:02:59 -0700914 }
Yi Tsenge72fbb52017-08-02 15:03:31 -0700915
916 class InternalHostListener implements HostListener {
917 @Override
918 public void event(HostEvent event) {
919 switch (event.type()) {
920 case HOST_ADDED:
921 case HOST_UPDATED:
922 hostUpdated(event.subject());
923 break;
924 case HOST_REMOVED:
925 hostRemoved(event.subject());
926 break;
927 case HOST_MOVED:
928 hostMoved(event.subject());
929 break;
930 default:
931 break;
932 }
933 }
934 }
935
936 /**
937 * Handle host move.
938 * If the host DHCP server or gateway and it moved to the location different
939 * to user configured, unsets the connect mac and vlan
940 *
941 * @param host the host
942 */
943 private void hostMoved(Host host) {
944 if (this.dhcpServerConnectPoint == null) {
945 return;
946 }
947 if (this.dhcpGatewayIp != null) {
948 if (host.ipAddresses().contains(this.dhcpGatewayIp) &&
949 !host.locations().contains(this.dhcpServerConnectPoint)) {
950 this.dhcpConnectMac = null;
951 this.dhcpConnectVlan = null;
952 }
953 return;
954 }
955 if (this.dhcpServerIp != null) {
956 if (host.ipAddresses().contains(this.dhcpServerIp) &&
957 !host.locations().contains(this.dhcpServerConnectPoint)) {
958 this.dhcpConnectMac = null;
959 this.dhcpConnectVlan = null;
960 }
961 }
962 }
963
964 /**
965 * Handle host updated.
966 * If the host is DHCP server or gateway, update connect mac and vlan.
967 *
968 * @param host the host
969 */
970 private void hostUpdated(Host host) {
971 if (this.dhcpGatewayIp != null) {
972 if (host.ipAddresses().contains(this.dhcpGatewayIp)) {
973 this.dhcpConnectMac = host.mac();
974 this.dhcpConnectVlan = host.vlan();
975 }
976 return;
977 }
978 if (this.dhcpServerIp != null) {
979 if (host.ipAddresses().contains(this.dhcpServerIp)) {
980 this.dhcpConnectMac = host.mac();
981 this.dhcpConnectVlan = host.vlan();
982 }
983 }
984 }
985
986 /**
987 * Handle host removed.
988 * If the host is DHCP server or gateway, unset connect mac and vlan.
989 *
990 * @param host the host
991 */
992 private void hostRemoved(Host host) {
993 if (this.dhcpGatewayIp != null) {
994 if (host.ipAddresses().contains(this.dhcpGatewayIp)) {
995 this.dhcpConnectMac = null;
996 this.dhcpConnectVlan = null;
997 }
998 return;
999 }
1000 if (this.dhcpServerIp != null) {
1001 if (host.ipAddresses().contains(this.dhcpServerIp)) {
1002 this.dhcpConnectMac = null;
1003 this.dhcpConnectVlan = null;
1004 }
1005 }
1006 }
Yi Tseng51301292017-07-28 13:02:59 -07001007}