blob: 48a4748e75e250587cd63b9be1ead1da41589ed2 [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 Tseng525ff402017-10-23 19:39:39 -070020import com.google.common.collect.HashMultimap;
21import com.google.common.collect.ImmutableSet;
Charles Chane5e0c9a2018-03-30 12:11:34 -070022import com.google.common.collect.Lists;
Yi Tseng525ff402017-10-23 19:39:39 -070023import com.google.common.collect.Multimap;
Charles Chan70fdd492018-03-07 17:36:06 -080024import com.google.common.collect.Multimaps;
Yi Tseng51301292017-07-28 13:02:59 -070025import com.google.common.collect.Sets;
Yi Tseng483ac6f2017-08-02 15:03:31 -070026import org.apache.felix.scr.annotations.Activate;
Yi Tseng51301292017-07-28 13:02:59 -070027import org.apache.felix.scr.annotations.Component;
Yi Tseng483ac6f2017-08-02 15:03:31 -070028import org.apache.felix.scr.annotations.Deactivate;
Taras Lemkin41785912018-03-26 14:52:58 +000029import org.apache.felix.scr.annotations.Modified;
Yi Tseng51301292017-07-28 13:02:59 -070030import org.apache.felix.scr.annotations.Property;
31import org.apache.felix.scr.annotations.Reference;
32import org.apache.felix.scr.annotations.ReferenceCardinality;
33import org.apache.felix.scr.annotations.Service;
34import org.onlab.packet.BasePacket;
35import org.onlab.packet.DHCP;
36import org.onlab.packet.Ethernet;
37import org.onlab.packet.IPv4;
38import org.onlab.packet.Ip4Address;
39import org.onlab.packet.IpAddress;
40import org.onlab.packet.MacAddress;
Yi Tseng525ff402017-10-23 19:39:39 -070041import org.onlab.packet.TpPort;
Yi Tseng51301292017-07-28 13:02:59 -070042import org.onlab.packet.UDP;
43import org.onlab.packet.VlanId;
44import org.onlab.packet.dhcp.CircuitId;
45import org.onlab.packet.dhcp.DhcpOption;
46import org.onlab.packet.dhcp.DhcpRelayAgentOption;
Taras Lemkin41785912018-03-26 14:52:58 +000047import org.onlab.util.Tools;
48import org.onosproject.cfg.ComponentConfigService;
Yi Tseng525ff402017-10-23 19:39:39 -070049import org.onosproject.core.ApplicationId;
50import org.onosproject.core.CoreService;
Yi Tseng51301292017-07-28 13:02:59 -070051import org.onosproject.dhcprelay.api.DhcpHandler;
Yi Tseng919b2df2017-09-07 16:22:51 -070052import org.onosproject.dhcprelay.api.DhcpServerInfo;
Yi Tseng483ac6f2017-08-02 15:03:31 -070053import org.onosproject.dhcprelay.config.DhcpServerConfig;
Yi Tseng525ff402017-10-23 19:39:39 -070054import org.onosproject.dhcprelay.config.IgnoreDhcpConfig;
Yi Tseng51301292017-07-28 13:02:59 -070055import org.onosproject.dhcprelay.store.DhcpRecord;
56import org.onosproject.dhcprelay.store.DhcpRelayStore;
Yi Tseng525ff402017-10-23 19:39:39 -070057import org.onosproject.net.Device;
58import org.onosproject.net.DeviceId;
59import org.onosproject.net.behaviour.Pipeliner;
60import org.onosproject.net.device.DeviceService;
61import org.onosproject.net.flow.DefaultTrafficSelector;
62import org.onosproject.net.flow.TrafficSelector;
63import org.onosproject.net.flowobjective.DefaultForwardingObjective;
64import org.onosproject.net.flowobjective.FlowObjectiveService;
65import org.onosproject.net.flowobjective.ForwardingObjective;
66import org.onosproject.net.flowobjective.Objective;
67import org.onosproject.net.flowobjective.ObjectiveContext;
68import org.onosproject.net.flowobjective.ObjectiveError;
Yi Tseng483ac6f2017-08-02 15:03:31 -070069import org.onosproject.net.host.HostEvent;
70import org.onosproject.net.host.HostListener;
Yi Tsengaa417a62017-09-08 17:22:51 -070071import org.onosproject.net.host.HostProvider;
72import org.onosproject.net.host.HostProviderRegistry;
73import org.onosproject.net.host.HostProviderService;
Ray Milkeyfacf2862017-08-03 11:58:29 -070074import org.onosproject.net.intf.Interface;
75import org.onosproject.net.intf.InterfaceService;
Yi Tseng525ff402017-10-23 19:39:39 -070076import org.onosproject.net.packet.PacketPriority;
Yi Tsengaa417a62017-09-08 17:22:51 -070077import org.onosproject.net.provider.ProviderId;
Ray Milkey69ec8712017-08-08 13:00:43 -070078import org.onosproject.routeservice.Route;
79import org.onosproject.routeservice.RouteStore;
Yi Tseng51301292017-07-28 13:02:59 -070080import org.onosproject.net.ConnectPoint;
81import org.onosproject.net.Host;
82import org.onosproject.net.HostId;
83import org.onosproject.net.HostLocation;
84import org.onosproject.net.flow.DefaultTrafficTreatment;
85import org.onosproject.net.flow.TrafficTreatment;
86import org.onosproject.net.host.DefaultHostDescription;
87import org.onosproject.net.host.HostDescription;
88import org.onosproject.net.host.HostService;
Yi Tseng51301292017-07-28 13:02:59 -070089import org.onosproject.net.host.InterfaceIpAddress;
90import org.onosproject.net.packet.DefaultOutboundPacket;
91import org.onosproject.net.packet.OutboundPacket;
92import org.onosproject.net.packet.PacketContext;
93import org.onosproject.net.packet.PacketService;
Taras Lemkin41785912018-03-26 14:52:58 +000094import org.osgi.service.component.ComponentContext;
Yi Tseng51301292017-07-28 13:02:59 -070095import org.slf4j.Logger;
96import org.slf4j.LoggerFactory;
97
98import java.nio.ByteBuffer;
Taras Lemkin41785912018-03-26 14:52:58 +000099import java.util.ArrayList;
Yi Tsengdcef2c22017-08-05 20:34:06 -0700100import java.util.Collection;
Yi Tseng51301292017-07-28 13:02:59 -0700101import java.util.Collections;
Taras Lemkin41785912018-03-26 14:52:58 +0000102import java.util.Dictionary;
Yi Tseng51301292017-07-28 13:02:59 -0700103import java.util.List;
104import java.util.Optional;
105import java.util.Set;
Charles Chan909cff82018-03-05 13:14:02 -0800106import java.util.concurrent.CopyOnWriteArrayList;
Jordan Halterman6328db72018-04-10 13:34:50 -0400107import java.util.concurrent.Executor;
Yi Tseng525ff402017-10-23 19:39:39 -0700108import java.util.concurrent.atomic.AtomicInteger;
Yi Tseng51301292017-07-28 13:02:59 -0700109import java.util.stream.Collectors;
110
111import static com.google.common.base.Preconditions.checkNotNull;
112import static com.google.common.base.Preconditions.checkState;
Jordan Halterman6328db72018-04-10 13:34:50 -0400113import static java.util.concurrent.Executors.newSingleThreadExecutor;
Yi Tseng51301292017-07-28 13:02:59 -0700114import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_CircuitID;
115import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_END;
116import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
117import static org.onlab.packet.MacAddress.valueOf;
118import static org.onlab.packet.dhcp.DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID;
Jordan Halterman6328db72018-04-10 13:34:50 -0400119import static org.onlab.util.Tools.groupedThreads;
Yi Tseng525ff402017-10-23 19:39:39 -0700120import static org.onosproject.net.flowobjective.Objective.Operation.ADD;
121import static org.onosproject.net.flowobjective.Objective.Operation.REMOVE;
Yi Tseng51301292017-07-28 13:02:59 -0700122
123@Component
124@Service
125@Property(name = "version", value = "4")
Yi Tsengaa417a62017-09-08 17:22:51 -0700126public class Dhcp4HandlerImpl implements DhcpHandler, HostProvider {
Charles Chand988c282017-09-12 17:09:32 -0700127 public static final String DHCP_V4_RELAY_APP = "org.onosproject.Dhcp4HandlerImpl";
128 public static final ProviderId PROVIDER_ID = new ProviderId("dhcp4", DHCP_V4_RELAY_APP);
Yi Tseng525ff402017-10-23 19:39:39 -0700129 private static final String BROADCAST_IP = "255.255.255.255";
130 private static final int IGNORE_CONTROL_PRIORITY = PacketPriority.CONTROL.priorityValue() + 1000;
131
Taras Lemkin41785912018-03-26 14:52:58 +0000132 private static final String LQ_ROUTE_PROPERTY_NAME = "learnRouteFromLeasequery";
133
Yi Tseng525ff402017-10-23 19:39:39 -0700134 private static final TrafficSelector CLIENT_SERVER_SELECTOR = DefaultTrafficSelector.builder()
135 .matchEthType(Ethernet.TYPE_IPV4)
136 .matchIPProtocol(IPv4.PROTOCOL_UDP)
137 .matchIPSrc(Ip4Address.ZERO.toIpPrefix())
138 .matchIPDst(Ip4Address.valueOf(BROADCAST_IP).toIpPrefix())
139 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
140 .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
141 .build();
142 private static final TrafficSelector SERVER_RELAY_SELECTOR = DefaultTrafficSelector.builder()
143 .matchEthType(Ethernet.TYPE_IPV4)
144 .matchIPProtocol(IPv4.PROTOCOL_UDP)
145 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
146 .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
147 .build();
148 static final Set<TrafficSelector> DHCP_SELECTORS = ImmutableSet.of(
149 CLIENT_SERVER_SELECTOR,
150 SERVER_RELAY_SELECTOR
151 );
Yi Tseng51301292017-07-28 13:02:59 -0700152 private static Logger log = LoggerFactory.getLogger(Dhcp4HandlerImpl.class);
153
154 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
155 protected DhcpRelayStore dhcpRelayStore;
156
157 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
158 protected PacketService packetService;
159
160 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Yi Tseng51301292017-07-28 13:02:59 -0700161 protected RouteStore routeStore;
162
163 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
164 protected InterfaceService interfaceService;
165
166 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
167 protected HostService hostService;
168
Yi Tsengaa417a62017-09-08 17:22:51 -0700169 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
170 protected HostProviderRegistry providerRegistry;
171
Yi Tseng525ff402017-10-23 19:39:39 -0700172 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
173 protected CoreService coreService;
174
175 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
176 protected DeviceService deviceService;
177
178 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
179 protected FlowObjectiveService flowObjectiveService;
180
Taras Lemkin41785912018-03-26 14:52:58 +0000181 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
182 protected ComponentConfigService cfgService;
183
Yi Tsengaa417a62017-09-08 17:22:51 -0700184 protected HostProviderService providerService;
Yi Tseng525ff402017-10-23 19:39:39 -0700185 protected ApplicationId appId;
Charles Chan70fdd492018-03-07 17:36:06 -0800186 protected Multimap<DeviceId, VlanId> ignoredVlans = Multimaps.synchronizedMultimap(HashMultimap.create());
Yi Tseng483ac6f2017-08-02 15:03:31 -0700187 private InternalHostListener hostListener = new InternalHostListener();
188
Charles Chan909cff82018-03-05 13:14:02 -0800189 private List<DhcpServerInfo> defaultServerInfoList = new CopyOnWriteArrayList<>();
190 private List<DhcpServerInfo> indirectServerInfoList = new CopyOnWriteArrayList<>();
Taras Lemkin41785912018-03-26 14:52:58 +0000191
192 @Property(name = Dhcp4HandlerImpl.LQ_ROUTE_PROPERTY_NAME, boolValue = false,
193 label = "Enable learning routing information from LQ")
194 private Boolean learnRouteFromLeasequery = Boolean.TRUE;
Yi Tseng4f2a0462017-08-31 11:21:00 -0700195
Jordan Halterman6328db72018-04-10 13:34:50 -0400196 private Executor hostEventExecutor = newSingleThreadExecutor(
197 groupedThreads("dhcp4-event-host", "%d", log));
198
Yi Tseng483ac6f2017-08-02 15:03:31 -0700199 @Activate
Taras Lemkin41785912018-03-26 14:52:58 +0000200 protected void activate(ComponentContext context) {
201 cfgService.registerProperties(getClass());
202 modified(context);
Yi Tseng525ff402017-10-23 19:39:39 -0700203 appId = coreService.registerApplication(DHCP_V4_RELAY_APP);
Yi Tseng483ac6f2017-08-02 15:03:31 -0700204 hostService.addListener(hostListener);
Yi Tsengaa417a62017-09-08 17:22:51 -0700205 providerService = providerRegistry.register(this);
Yi Tseng483ac6f2017-08-02 15:03:31 -0700206 }
207
208 @Deactivate
209 protected void deactivate() {
Taras Lemkin41785912018-03-26 14:52:58 +0000210 cfgService.unregisterProperties(getClass(), false);
Yi Tsengaa417a62017-09-08 17:22:51 -0700211 providerRegistry.unregister(this);
212 hostService.removeListener(hostListener);
Yi Tseng919b2df2017-09-07 16:22:51 -0700213 defaultServerInfoList.forEach(this::stopMonitoringIps);
Charles Chana134ee32018-07-19 09:52:01 -0700214 defaultServerInfoList.forEach(info -> info.getDhcpServerIp4().ifPresent(this::cancelDhcpPacket));
Yi Tseng919b2df2017-09-07 16:22:51 -0700215 defaultServerInfoList.clear();
216 indirectServerInfoList.forEach(this::stopMonitoringIps);
Charles Chana134ee32018-07-19 09:52:01 -0700217 indirectServerInfoList.forEach(info -> info.getDhcpServerIp4().ifPresent(this::cancelDhcpPacket));
Yi Tseng919b2df2017-09-07 16:22:51 -0700218 indirectServerInfoList.clear();
Yi Tseng483ac6f2017-08-02 15:03:31 -0700219 }
220
Taras Lemkin41785912018-03-26 14:52:58 +0000221 @Modified
222 protected void modified(ComponentContext context) {
223 Dictionary<?, ?> properties = context.getProperties();
224 Boolean flag;
225 flag = Tools.isPropertyEnabled(properties, Dhcp4HandlerImpl.LQ_ROUTE_PROPERTY_NAME);
226 if (flag != null) {
227 learnRouteFromLeasequery = flag;
228 log.info("Learning routes from DHCP leasequery is {}",
229 learnRouteFromLeasequery ? "enabled" : "disabled");
230 }
231 }
232
Yi Tseng919b2df2017-09-07 16:22:51 -0700233 private void stopMonitoringIps(DhcpServerInfo serverInfo) {
234 serverInfo.getDhcpGatewayIp4().ifPresent(gatewayIp -> {
235 hostService.stopMonitoringIp(gatewayIp);
236 });
237 serverInfo.getDhcpServerIp4().ifPresent(serverIp -> {
238 hostService.stopMonitoringIp(serverIp);
239 });
Yi Tseng51301292017-07-28 13:02:59 -0700240 }
241
242 @Override
Yi Tseng483ac6f2017-08-02 15:03:31 -0700243 public void setDefaultDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
Yi Tseng919b2df2017-09-07 16:22:51 -0700244 setDhcpServerConfigs(configs, defaultServerInfoList);
245 }
246
247 @Override
248 public void setIndirectDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
249 setDhcpServerConfigs(configs, indirectServerInfoList);
250 }
251
252 @Override
253 public List<DhcpServerInfo> getDefaultDhcpServerInfoList() {
254 return defaultServerInfoList;
255 }
256
257 @Override
258 public List<DhcpServerInfo> getIndirectDhcpServerInfoList() {
259 return indirectServerInfoList;
260 }
261
Yi Tseng525ff402017-10-23 19:39:39 -0700262 @Override
263 public void updateIgnoreVlanConfig(IgnoreDhcpConfig config) {
264 if (config == null) {
265 ignoredVlans.forEach(((deviceId, vlanId) -> {
266 processIgnoreVlanRule(deviceId, vlanId, REMOVE);
267 }));
268 return;
269 }
270 config.ignoredVlans().forEach((deviceId, vlanId) -> {
271 if (ignoredVlans.get(deviceId).contains(vlanId)) {
272 // don't need to process if it already ignored
273 return;
274 }
275 processIgnoreVlanRule(deviceId, vlanId, ADD);
276 });
277
278 ignoredVlans.forEach((deviceId, vlanId) -> {
279 if (!config.ignoredVlans().get(deviceId).contains(vlanId)) {
280 // not contains in new config, remove it
281 processIgnoreVlanRule(deviceId, vlanId, REMOVE);
282 }
283 });
284 }
285
Saurav Dasb805f1a2017-12-13 16:19:35 -0800286 @Override
287 public void removeIgnoreVlanState(IgnoreDhcpConfig config) {
288 if (config == null) {
289 ignoredVlans.clear();
290 return;
291 }
292 config.ignoredVlans().forEach((deviceId, vlanId) -> {
293 ignoredVlans.remove(deviceId, vlanId);
294 });
295 }
296
Yi Tseng919b2df2017-09-07 16:22:51 -0700297 public void setDhcpServerConfigs(Collection<DhcpServerConfig> configs, List<DhcpServerInfo> serverInfoList) {
Yi Tseng483ac6f2017-08-02 15:03:31 -0700298 if (configs.size() == 0) {
299 // no config to update
300 return;
301 }
302
rsahot036620655b2018-02-26 15:01:37 -0500303 Boolean isConfigValid = false;
304 for (DhcpServerConfig serverConfig : configs) {
305 if (serverConfig.getDhcpServerIp4().isPresent()) {
306 isConfigValid = true;
307 break;
308 }
Yi Tseng483ac6f2017-08-02 15:03:31 -0700309 }
rsahot036620655b2018-02-26 15:01:37 -0500310 if (!isConfigValid) {
311 log.warn("No IP V4 server address found.");
312 return; // No IP V6 address found
313 }
314 // if (!serverInfoList.isEmpty()) {
315 for (DhcpServerInfo oldServerInfo : serverInfoList) {
316 log.info("In for (DhcpServerInfo oldServerInfo : serverInfoList) {");
Yi Tseng919b2df2017-09-07 16:22:51 -0700317 // remove old server info
rsahot036620655b2018-02-26 15:01:37 -0500318 //DhcpServerInfo oldServerInfo = serverInfoList.remove(0);
Yi Tseng483ac6f2017-08-02 15:03:31 -0700319
Yi Tseng919b2df2017-09-07 16:22:51 -0700320 // stop monitoring gateway or server
321 oldServerInfo.getDhcpGatewayIp4().ifPresent(gatewayIp -> {
322 hostService.stopMonitoringIp(gatewayIp);
323 });
324 oldServerInfo.getDhcpServerIp4().ifPresent(serverIp -> {
325 hostService.stopMonitoringIp(serverIp);
Yi Tseng525ff402017-10-23 19:39:39 -0700326 cancelDhcpPacket(serverIp);
Yi Tseng919b2df2017-09-07 16:22:51 -0700327 });
Yi Tseng483ac6f2017-08-02 15:03:31 -0700328 }
Yi Tseng3df7f9d2017-08-17 13:08:31 -0700329
Yi Tseng919b2df2017-09-07 16:22:51 -0700330 // Create new server info according to the config
rsahot036620655b2018-02-26 15:01:37 -0500331 serverInfoList.clear();
332 for (DhcpServerConfig serverConfig : configs) {
Taras Lemkin41785912018-03-26 14:52:58 +0000333 log.debug("Create new server info according to the config");
rsahot036620655b2018-02-26 15:01:37 -0500334 DhcpServerInfo newServerInfo = new DhcpServerInfo(serverConfig,
335 DhcpServerInfo.Version.DHCP_V4);
336 checkState(newServerInfo.getDhcpServerConnectPoint().isPresent(),
337 "Connect point not exists");
338 checkState(newServerInfo.getDhcpServerIp4().isPresent(),
339 "IP of DHCP server not exists");
Yi Tseng4f2a0462017-08-31 11:21:00 -0700340
rsahot036620655b2018-02-26 15:01:37 -0500341 log.debug("DHCP server connect point: {}", newServerInfo.getDhcpServerConnectPoint().orElse(null));
342 log.debug("DHCP server IP: {}", newServerInfo.getDhcpServerIp4().orElse(null));
Yi Tseng919b2df2017-09-07 16:22:51 -0700343
rsahot036620655b2018-02-26 15:01:37 -0500344 Ip4Address serverIp = newServerInfo.getDhcpServerIp4().get();
345 Ip4Address ipToProbe;
346 if (newServerInfo.getDhcpGatewayIp4().isPresent()) {
347 ipToProbe = newServerInfo.getDhcpGatewayIp4().get();
348 } else {
349 ipToProbe = newServerInfo.getDhcpServerIp4().orElse(null);
350 }
351 log.info("Probe_IP {}", ipToProbe);
Harshada Chaundkar6c54f202019-05-06 20:16:13 +0000352 String hostToProbe = newServerInfo.getDhcpGatewayIp4().map(ip -> "gateway").orElse("server");
rsahot036620655b2018-02-26 15:01:37 -0500353 log.debug("Probing to resolve {} IP {}", hostToProbe, ipToProbe);
354 hostService.startMonitoringIp(ipToProbe);
355
356 Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
357 if (!hosts.isEmpty()) {
358 Host host = hosts.iterator().next();
359 newServerInfo.setDhcpConnectVlan(host.vlan());
360 newServerInfo.setDhcpConnectMac(host.mac());
361 }
362
363 // Add new server info
364 synchronized (this) {
365 //serverInfoList.clear();
366 serverInfoList.add(newServerInfo);
367 }
368
369 requestDhcpPacket(serverIp);
Yi Tseng4f2a0462017-08-31 11:21:00 -0700370 }
Yi Tseng483ac6f2017-08-02 15:03:31 -0700371 }
372
Yi Tseng3df7f9d2017-08-17 13:08:31 -0700373 @Override
Yi Tseng51301292017-07-28 13:02:59 -0700374 public void processDhcpPacket(PacketContext context, BasePacket payload) {
375 checkNotNull(payload, "DHCP payload can't be null");
376 checkState(payload instanceof DHCP, "Payload is not a DHCP");
377 DHCP dhcpPayload = (DHCP) payload;
378 if (!configured()) {
Yi Tseng919b2df2017-09-07 16:22:51 -0700379 log.warn("Missing default DHCP relay server config. Abort packet processing");
Yi Tseng51301292017-07-28 13:02:59 -0700380 return;
381 }
382
383 ConnectPoint inPort = context.inPacket().receivedFrom();
Yi Tseng51301292017-07-28 13:02:59 -0700384 checkNotNull(dhcpPayload, "Can't find DHCP payload");
385 Ethernet packet = context.inPacket().parsed();
386 DHCP.MsgType incomingPacketType = dhcpPayload.getOptions().stream()
387 .filter(dhcpOption -> dhcpOption.getCode() == OptionCode_MessageType.getValue())
388 .map(DhcpOption::getData)
389 .map(data -> DHCP.MsgType.getType(data[0]))
390 .findFirst()
391 .orElse(null);
392 checkNotNull(incomingPacketType, "Can't get message type from DHCP payload {}", dhcpPayload);
rsahot036620655b2018-02-26 15:01:37 -0500393 Set<Interface> receivingInterfaces = interfaceService.getInterfacesByPort(inPort);
394 //ignore the packets if dhcp client interface is not configured on onos.
395 if (receivingInterfaces.isEmpty()) {
396 log.warn("Virtual interface is not configured on {}", inPort);
397 return;
398 }
Yi Tseng51301292017-07-28 13:02:59 -0700399 switch (incomingPacketType) {
400 case DHCPDISCOVER:
Yi Tsengdcef2c22017-08-05 20:34:06 -0700401 // Add the gateway IP as virtual interface IP for server to understand
Yi Tseng51301292017-07-28 13:02:59 -0700402 // the lease to be assigned and forward the packet to dhcp server.
rsahot036620655b2018-02-26 15:01:37 -0500403 List<InternalPacket> ethernetClientPacket =
404 processDhcpPacketFromClient(context, packet, receivingInterfaces);
405 for (InternalPacket internalPacket : ethernetClientPacket) {
406 log.debug("DHCPDISCOVER from {} Forward to server", inPort);
Yi Tseng51301292017-07-28 13:02:59 -0700407 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
rsahot036620655b2018-02-26 15:01:37 -0500408 forwardPacket(internalPacket);
Yi Tseng51301292017-07-28 13:02:59 -0700409 }
410 break;
411 case DHCPOFFER:
412 //reply to dhcp client.
Taras Lemkin41785912018-03-26 14:52:58 +0000413 InternalPacket ethernetPacketOffer = processDhcpPacketFromServer(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700414 if (ethernetPacketOffer != null) {
Taras Lemkin41785912018-03-26 14:52:58 +0000415 writeResponseDhcpRecord(ethernetPacketOffer.getPacket(), dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700416 sendResponseToClient(ethernetPacketOffer, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700417 }
418 break;
419 case DHCPREQUEST:
420 // add the gateway ip as virtual interface ip for server to understand
421 // the lease to be assigned and forward the packet to dhcp server.
rsahot036620655b2018-02-26 15:01:37 -0500422 List<InternalPacket> ethernetPacketRequest =
423 processDhcpPacketFromClient(context, packet, receivingInterfaces);
424 for (InternalPacket internalPacket : ethernetPacketRequest) {
425 log.debug("DHCPDISCOVER from {} Forward to server", inPort);
Yi Tseng51301292017-07-28 13:02:59 -0700426 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
rsahot036620655b2018-02-26 15:01:37 -0500427 forwardPacket(internalPacket);
Yi Tseng51301292017-07-28 13:02:59 -0700428 }
429 break;
Charles Chand0dd7002017-10-08 23:53:36 -0400430 case DHCPDECLINE:
431 break;
Yi Tseng51301292017-07-28 13:02:59 -0700432 case DHCPACK:
433 // reply to dhcp client.
Taras Lemkin41785912018-03-26 14:52:58 +0000434 InternalPacket ethernetPacketAck = processDhcpPacketFromServer(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700435 if (ethernetPacketAck != null) {
Taras Lemkin41785912018-03-26 14:52:58 +0000436 writeResponseDhcpRecord(ethernetPacketAck.getPacket(), dhcpPayload);
437 handleDhcpAck(ethernetPacketAck.getPacket(), dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700438 sendResponseToClient(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700439 }
440 break;
Charles Chand0dd7002017-10-08 23:53:36 -0400441 case DHCPNAK:
442 break;
Yi Tseng51301292017-07-28 13:02:59 -0700443 case DHCPRELEASE:
444 // TODO: release the ip address from client
445 break;
Charles Chand0dd7002017-10-08 23:53:36 -0400446 case DHCPINFORM:
447 break;
448 case DHCPFORCERENEW:
449 break;
450 case DHCPLEASEQUERY:
451 handleLeaseQueryMsg(context, packet, dhcpPayload);
452 break;
453 case DHCPLEASEACTIVE:
454 handleLeaseQueryActivateMsg(packet, dhcpPayload);
455 break;
456 case DHCPLEASEUNASSIGNED:
457 case DHCPLEASEUNKNOWN:
458 handleLeaseQueryUnknown(packet, dhcpPayload);
459 break;
Yi Tseng51301292017-07-28 13:02:59 -0700460 default:
461 break;
462 }
463 }
464
465 /**
466 * Checks if this app has been configured.
467 *
468 * @return true if all information we need have been initialized
469 */
Yi Tseng4f2a0462017-08-31 11:21:00 -0700470 private boolean configured() {
Yi Tseng919b2df2017-09-07 16:22:51 -0700471 return !defaultServerInfoList.isEmpty();
Yi Tseng51301292017-07-28 13:02:59 -0700472 }
473
474 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -0700475 * Returns the first interface ip from interface.
Yi Tseng51301292017-07-28 13:02:59 -0700476 *
Yi Tsengdcef2c22017-08-05 20:34:06 -0700477 * @param iface interface of one connect point
Yi Tseng51301292017-07-28 13:02:59 -0700478 * @return the first interface IP; null if not exists an IP address in
479 * these interfaces
480 */
Yi Tseng3df7f9d2017-08-17 13:08:31 -0700481 private Ip4Address getFirstIpFromInterface(Interface iface) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700482 checkNotNull(iface, "Interface can't be null");
483 return iface.ipAddressesList().stream()
Yi Tseng51301292017-07-28 13:02:59 -0700484 .map(InterfaceIpAddress::ipAddress)
485 .filter(IpAddress::isIp4)
486 .map(IpAddress::getIp4Address)
487 .findFirst()
488 .orElse(null);
489 }
490
491 /**
Yi Tseng4f2a0462017-08-31 11:21:00 -0700492 * Gets Interface facing to the server for default host.
Yi Tsengdcef2c22017-08-05 20:34:06 -0700493 *
494 * @return the Interface facing to the server; null if not found
495 */
Yi Tseng919b2df2017-09-07 16:22:51 -0700496 private Interface getDefaultServerInterface() {
497 return getServerInterface(defaultServerInfoList);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700498 }
499
500 /**
Yi Tseng4f2a0462017-08-31 11:21:00 -0700501 * Gets Interface facing to the server for indirect hosts.
502 * Use default server Interface if indirect server not configured.
503 *
504 * @return the Interface facing to the server; null if not found
505 */
506 private Interface getIndirectServerInterface() {
Yi Tseng919b2df2017-09-07 16:22:51 -0700507 return getServerInterface(indirectServerInfoList);
508 }
509
510 private Interface getServerInterface(List<DhcpServerInfo> serverInfos) {
Yi Tseng3bd57ac2017-11-29 14:39:18 -0800511 return serverInfos.stream()
Yi Tseng4f2a0462017-08-31 11:21:00 -0700512 .findFirst()
Yi Tseng3bd57ac2017-11-29 14:39:18 -0800513 .map(serverInfo -> {
Harshada Chaundkar6c54f202019-05-06 20:16:13 +0000514 ConnectPoint dhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
Yi Tseng3bd57ac2017-11-29 14:39:18 -0800515 VlanId dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
516 if (dhcpServerConnectPoint == null || dhcpConnectVlan == null) {
517 return null;
518 }
519 return interfaceService.getInterfacesByPort(dhcpServerConnectPoint)
520 .stream()
521 .filter(iface -> interfaceContainsVlan(iface, dhcpConnectVlan))
522 .findFirst()
523 .orElse(null);
524 })
Yi Tseng4f2a0462017-08-31 11:21:00 -0700525 .orElse(null);
526 }
527
528 /**
529 * Determind if an Interface contains a vlan id.
530 *
531 * @param iface the Interface
532 * @param vlanId the vlan id
533 * @return true if the Interface contains the vlan id
534 */
535 private boolean interfaceContainsVlan(Interface iface, VlanId vlanId) {
Yi Tseng4025a102017-09-30 11:35:42 +0800536 if (vlanId.equals(VlanId.NONE)) {
537 // untagged packet, check if vlan untagged or vlan native is not NONE
Harshada Chaundkar6c54f202019-05-06 20:16:13 +0000538 return !iface.vlanUntagged().equals(VlanId.NONE) || !iface.vlanNative().equals(VlanId.NONE);
Yi Tseng4025a102017-09-30 11:35:42 +0800539 }
540 // tagged packet, check if the interface contains the vlan
541 return iface.vlanTagged().contains(vlanId);
Yi Tseng4f2a0462017-08-31 11:21:00 -0700542 }
543
Charles Chand0dd7002017-10-08 23:53:36 -0400544 private void handleLeaseQueryActivateMsg(Ethernet packet, DHCP dhcpPayload) {
545 log.debug("LQ: Got DHCPLEASEACTIVE packet!");
546
Taras Lemkin41785912018-03-26 14:52:58 +0000547 if (learnRouteFromLeasequery) {
548 // TODO: release the ip address from client
549 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
550 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
551 HostId hostId = HostId.hostId(clientMacAddress, vlanId);
552 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
Charles Chand0dd7002017-10-08 23:53:36 -0400553
Taras Lemkin41785912018-03-26 14:52:58 +0000554 if (record == null) {
555 log.warn("Can't find record for host {} when processing DHCPLEASEACTIVE", hostId);
556 return;
557 }
558
559 // need to update routes
560 log.debug("Lease Query for Client results in DHCPLEASEACTIVE - route needs to be modified");
561 // get current route
562 // find the ip of that client with the DhcpRelay store
563
564 Ip4Address clientIP = record.ip4Address().orElse(null);
565 log.debug("LQ: IP of host is " + clientIP.getIp4Address());
566
567 MacAddress nextHopMac = record.nextHop().orElse(null);
568 log.debug("LQ: MAC of resulting *OLD* NH for that host is " + nextHopMac.toString());
569
570 // find the new NH by looking at the src MAC of the dhcp request
571 // from the LQ store
572 MacAddress newNextHopMac = record.nextHopTemp().orElse(null);
573 log.debug("LQ: MAC of resulting *NEW* NH for that host is " + newNextHopMac.toString());
574
575 log.debug("LQ: updating dhcp relay record with new NH");
576 record.nextHop(newNextHopMac);
577
578 // find the next hop IP from its mac
579 HostId gwHostId = HostId.hostId(newNextHopMac, vlanId);
580 Host gwHost = hostService.getHost(gwHostId);
581
582 if (gwHost == null) {
583 log.warn("Can't find gateway for new NH host " + gwHostId);
584 return;
585 }
586
587 Ip4Address nextHopIp = gwHost.ipAddresses()
588 .stream()
589 .filter(IpAddress::isIp4)
590 .map(IpAddress::getIp4Address)
591 .findFirst()
592 .orElse(null);
593
594 if (nextHopIp == null) {
595 log.warn("Can't find IP address of gateway " + gwHost);
596 return;
597 }
598
599 log.debug("LQ: *NEW* NH IP for host is " + nextHopIp.getIp4Address());
600 Route route = new Route(Route.Source.DHCP, clientIP.toIpPrefix(), nextHopIp);
601 routeStore.updateRoute(route);
Charles Chand0dd7002017-10-08 23:53:36 -0400602 }
603
Charles Chand0dd7002017-10-08 23:53:36 -0400604 // and forward to client
Taras Lemkin41785912018-03-26 14:52:58 +0000605 InternalPacket ethernetPacket = processLeaseQueryFromServer(packet);
Charles Chand0dd7002017-10-08 23:53:36 -0400606 if (ethernetPacket != null) {
607 sendResponseToClient(ethernetPacket, dhcpPayload);
608 }
609 }
610
Charles Chand0dd7002017-10-08 23:53:36 -0400611 private void handleLeaseQueryMsg(PacketContext context, Ethernet packet, DHCP dhcpPayload) {
Taras Lemkin41785912018-03-26 14:52:58 +0000612 // If this flag is enabled we expect that DHCPLEASEQUERY-ies are sent from an access concentrator
613 // where queried client is connected to. Otherwise, DHCPLEASEQUERY source may be a separate connected agent
614 if (learnRouteFromLeasequery) {
615 log.debug("LQ: Got DHCPLEASEQUERY packet!");
616 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
617 log.debug("LQ: got DHCPLEASEQUERY with MAC " + clientMacAddress.toString());
618 // add the client mac (hostid) of this request to a store (the entry will be removed with
619 // the reply sent to the originator)
620 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
621 HostId hId = HostId.hostId(clientMacAddress, vlanId);
622 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hId).orElse(null);
623 if (record != null) {
624 //new NH is to be taken from src mac of LQ packet
625 MacAddress newNextHop = packet.getSourceMAC();
626 record.nextHopTemp(newNextHop);
627 record.ip4Status(dhcpPayload.getPacketType());
628 record.updateLastSeen();
629
630 // do a basic routing of the packet (this is unicast routing
631 // not a relay operation like for other broadcast dhcp packets
632 List<InternalPacket> ethernetPacketRequest = processLeaseQueryFromAgent(context, packet);
633 // and forward to server
634 for (InternalPacket internalPacket : ethernetPacketRequest) {
635 log.debug("LeaseQueryMsg forward to server");
636 forwardPacket(internalPacket);
637 }
638 } else {
639 log.warn("LQ: Error! - DHCP relay record for that client not found - ignoring LQ!");
640 }
641 } else {
642 log.debug("LQ: Got DHCPLEASEQUERY packet!");
643
644 int giaddr = dhcpPayload.getGatewayIPAddress();
645
646 log.debug("DHCPLEASEQUERY giaddr: {} ({}). Originators connectPoint: {}", giaddr,
647 Ip4Address.valueOf(giaddr), context.inPacket().receivedFrom());
Charles Chand0dd7002017-10-08 23:53:36 -0400648
649 // do a basic routing of the packet (this is unicast routing
650 // not a relay operation like for other broadcast dhcp packets
rsahot036620655b2018-02-26 15:01:37 -0500651 List<InternalPacket> ethernetPacketRequest = processLeaseQueryFromAgent(context, packet);
Charles Chand0dd7002017-10-08 23:53:36 -0400652 // and forward to server
rsahot036620655b2018-02-26 15:01:37 -0500653 for (InternalPacket internalPacket : ethernetPacketRequest) {
Taras Lemkin41785912018-03-26 14:52:58 +0000654 log.trace("LeaseQueryMsg forward to server connected to {}", internalPacket.getDestLocation());
rsahot036620655b2018-02-26 15:01:37 -0500655 forwardPacket(internalPacket);
656 }
Charles Chand0dd7002017-10-08 23:53:36 -0400657 }
658 }
659
660 private void handleLeaseQueryUnknown(Ethernet packet, DHCP dhcpPayload) {
661 log.debug("Lease Query for Client results in DHCPLEASEUNASSIGNED or " +
662 "DHCPLEASEUNKNOWN - removing route & forwarding reply to originator");
Taras Lemkin41785912018-03-26 14:52:58 +0000663 if (learnRouteFromLeasequery) {
664 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
665 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
666 HostId hostId = HostId.hostId(clientMacAddress, vlanId);
667 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
Charles Chand0dd7002017-10-08 23:53:36 -0400668
Taras Lemkin41785912018-03-26 14:52:58 +0000669 if (record == null) {
670 log.warn("Can't find record for host {} when handling LQ UNKNOWN/UNASSIGNED message", hostId);
671 return;
672 }
673
674 Ip4Address clientIP = record.ip4Address().orElse(null);
675 log.debug("LQ: IP of host is " + clientIP.getIp4Address());
676
677 // find the new NH by looking at the src MAC of the dhcp request
678 // from the LQ store
679 MacAddress nextHopMac = record.nextHop().orElse(null);
680 log.debug("LQ: MAC of resulting *Existing* NH for that route is " + nextHopMac.toString());
681
682 // find the next hop IP from its mac
683 HostId gwHostId = HostId.hostId(nextHopMac, vlanId);
684 Host gwHost = hostService.getHost(gwHostId);
685
686 if (gwHost == null) {
687 log.warn("Can't find gateway for new NH host " + gwHostId);
688 return;
689 }
690
691 Ip4Address nextHopIp = gwHost.ipAddresses()
692 .stream()
693 .filter(IpAddress::isIp4)
694 .map(IpAddress::getIp4Address)
695 .findFirst()
696 .orElse(null);
697
698 if (nextHopIp == null) {
699 log.warn("Can't find IP address of gateway {}", gwHost);
700 return;
701 }
702
703 log.debug("LQ: *Existing* NH IP for host is " + nextHopIp.getIp4Address() + " removing route for it");
704 Route route = new Route(Route.Source.DHCP, clientIP.toIpPrefix(), nextHopIp);
705 routeStore.removeRoute(route);
706
707 // remove from temp store
708 dhcpRelayStore.removeDhcpRecord(hostId);
Charles Chand0dd7002017-10-08 23:53:36 -0400709 }
Charles Chand0dd7002017-10-08 23:53:36 -0400710 // and forward to client
Taras Lemkin41785912018-03-26 14:52:58 +0000711 InternalPacket ethernetPacket = processLeaseQueryFromServer(packet);
Charles Chand0dd7002017-10-08 23:53:36 -0400712 if (ethernetPacket != null) {
713 sendResponseToClient(ethernetPacket, dhcpPayload);
714 }
715 }
716
Yi Tseng4f2a0462017-08-31 11:21:00 -0700717 /**
Yi Tseng51301292017-07-28 13:02:59 -0700718 * Build the DHCP discover/request packet with gateway IP(unicast packet).
719 *
720 * @param context the packet context
721 * @param ethernetPacket the ethernet payload to process
Yi Tseng51301292017-07-28 13:02:59 -0700722 * @return processed packet
723 */
rsahot036620655b2018-02-26 15:01:37 -0500724 private List<InternalPacket> processDhcpPacketFromClient(PacketContext context,
725 Ethernet ethernetPacket,
726 Set<Interface> clientInterfaces) {
Yi Tseng25bfe372017-11-03 16:27:32 -0700727 ConnectPoint receivedFrom = context.inPacket().receivedFrom();
728 DeviceId receivedFromDevice = receivedFrom.deviceId();
rsahot036620655b2018-02-26 15:01:37 -0500729 Ip4Address relayAgentIp = null;
Taras Lemkin41785912018-03-26 14:52:58 +0000730 relayAgentIp = Dhcp4HandlerUtil.getRelayAgentIPv4Address(clientInterfaces);
rsahot036620655b2018-02-26 15:01:37 -0500731 MacAddress relayAgentMac = clientInterfaces.iterator().next().mac();
732 if (relayAgentIp == null || relayAgentMac == null) {
733 log.warn("Missing DHCP relay agent interface Ipv4 addr config for "
734 + "packet from client on port: {}. Aborting packet processing",
735 clientInterfaces.iterator().next().connectPoint());
Charles Chane5e0c9a2018-03-30 12:11:34 -0700736 return Lists.newArrayList();
rsahot036620655b2018-02-26 15:01:37 -0500737 }
738 log.debug("Multi DHCP V4 processDhcpPacketFromClient on port {}",
739 clientInterfaces.iterator().next().connectPoint());
Yi Tseng25bfe372017-11-03 16:27:32 -0700740
Yi Tseng4f2a0462017-08-31 11:21:00 -0700741 // get dhcp header.
rsahot036620655b2018-02-26 15:01:37 -0500742 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
Yi Tseng4f2a0462017-08-31 11:21:00 -0700743 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
744 UDP udpPacket = (UDP) ipv4Packet.getPayload();
745 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
Yi Tsengdcef2c22017-08-05 20:34:06 -0700746 Ip4Address clientInterfaceIp =
747 interfaceService.getInterfacesByPort(context.inPacket().receivedFrom())
748 .stream()
749 .map(Interface::ipAddressesList)
750 .flatMap(Collection::stream)
751 .map(InterfaceIpAddress::ipAddress)
752 .filter(IpAddress::isIp4)
753 .map(IpAddress::getIp4Address)
754 .findFirst()
755 .orElse(null);
756 if (clientInterfaceIp == null) {
757 log.warn("Can't find interface IP for client interface for port {}",
rsahot036620655b2018-02-26 15:01:37 -0500758 context.inPacket().receivedFrom());
Charles Chane5e0c9a2018-03-30 12:11:34 -0700759 return Lists.newArrayList();
Yi Tsengdcef2c22017-08-05 20:34:06 -0700760 }
rsahot036620655b2018-02-26 15:01:37 -0500761
Yi Tseng4f2a0462017-08-31 11:21:00 -0700762 boolean isDirectlyConnected = directlyConnected(dhcpPacket);
rsahot036620655b2018-02-26 15:01:37 -0500763 boolean directConnFlag = directlyConnected(dhcpPacket);
764
765 // Multi DHCP Start
766 ConnectPoint clientConnectionPoint = context.inPacket().receivedFrom();
767 VlanId vlanIdInUse = VlanId.vlanId(ethernetPacket.getVlanID());
768 Interface clientInterface = interfaceService.getInterfacesByPort(clientConnectionPoint)
Taras Lemkin41785912018-03-26 14:52:58 +0000769 .stream().filter(iface -> Dhcp4HandlerUtil.interfaceContainsVlan(iface, vlanIdInUse))
rsahot036620655b2018-02-26 15:01:37 -0500770 .findFirst()
771 .orElse(null);
772
773 List<InternalPacket> internalPackets = new ArrayList<>();
774 List<DhcpServerInfo> serverInfoList = findValidServerInfo(directConnFlag);
775 List<DhcpServerInfo> copyServerInfoList = new ArrayList<DhcpServerInfo>(serverInfoList);
Harshada Chaundkar6c54f202019-05-06 20:16:13 +0000776 boolean serverFound = false;
rsahot036620655b2018-02-26 15:01:37 -0500777
778 for (DhcpServerInfo serverInfo : copyServerInfoList) {
Harshada Chaundkar6c54f202019-05-06 20:16:13 +0000779
rsahot036620655b2018-02-26 15:01:37 -0500780 etherReply = (Ethernet) ethernetPacket.clone();
Taras Lemkin41785912018-03-26 14:52:58 +0000781 ipv4Packet = (IPv4) etherReply.getPayload();
782 udpPacket = (UDP) ipv4Packet.getPayload();
783 dhcpPacket = (DHCP) udpPacket.getPayload();
rsahot036620655b2018-02-26 15:01:37 -0500784 if (!checkDhcpServerConnPt(directConnFlag, serverInfo)) {
785 log.warn("Can't get server connect point, ignore");
786 continue;
787 }
788 DhcpServerInfo newServerInfo = getHostInfoForServerInfo(serverInfo, serverInfoList);
789 if (newServerInfo == null) {
Harshada Chaundkar6c54f202019-05-06 20:16:13 +0000790 log.debug("Can't get server interface with host info resolved, ignore serverInfo {} serverInfoList {}",
791 serverInfo, serverInfoList);
rsahot036620655b2018-02-26 15:01:37 -0500792 continue;
793 }
rsahot036620655b2018-02-26 15:01:37 -0500794 Interface serverInterface = getServerInterface(newServerInfo);
Yi Tseng3bd57ac2017-11-29 14:39:18 -0800795 if (serverInterface == null) {
Harshada Chaundkar6c54f202019-05-06 20:16:13 +0000796 log.debug("Can't get server interface, ignore for serverInfo {}, serverInfoList {}",
797 serverInfo, serverInfoList);
rsahot036620655b2018-02-26 15:01:37 -0500798 continue;
Yi Tseng3bd57ac2017-11-29 14:39:18 -0800799 }
Yi Tseng4f2a0462017-08-31 11:21:00 -0700800
rsahot036620655b2018-02-26 15:01:37 -0500801 Ip4Address ipFacingServer = getFirstIpFromInterface(serverInterface);
802 MacAddress macFacingServer = serverInterface.mac();
803 log.debug("Interfacing server {} Mac : {} ", ipFacingServer, macFacingServer);
804 if (ipFacingServer == null || macFacingServer == null) {
Harshada Chaundkar6c54f202019-05-06 20:16:13 +0000805 log.debug("No IP address for server Interface {}", serverInterface);
rsahot036620655b2018-02-26 15:01:37 -0500806 continue;
807 }
Yi Tseng51301292017-07-28 13:02:59 -0700808
Harshada Chaundkar6c54f202019-05-06 20:16:13 +0000809 serverFound = true;
810 log.debug("Server Info Found {}", serverInfo.getDhcpConnectMac());
rsahot036620655b2018-02-26 15:01:37 -0500811 etherReply.setSourceMACAddress(macFacingServer);
812 // set default info and replace with indirect if available later on.
813 if (newServerInfo.getDhcpConnectMac().isPresent()) {
814 etherReply.setDestinationMACAddress(newServerInfo.getDhcpConnectMac().get());
815 }
816 if (newServerInfo.getDhcpConnectVlan().isPresent()) {
817 etherReply.setVlanID(newServerInfo.getDhcpConnectVlan().get().toShort());
818 }
819 ipv4Packet.setSourceAddress(ipFacingServer.toInt());
820 ipv4Packet.setDestinationAddress(newServerInfo.getDhcpServerIp4().get().toInt());
Charles Chan2de55302018-03-02 18:27:59 -0800821 log.debug("Directly connected {}", isDirectlyConnected);
822 log.debug("DHCP server IP: {}", newServerInfo.getDhcpServerIp4().get());
rsahot036620655b2018-02-26 15:01:37 -0500823 if (isDirectlyConnected) {
Yi Tseng51301292017-07-28 13:02:59 -0700824
Charles Chan2de55302018-03-02 18:27:59 -0800825 log.debug("Default DHCP server IP: {}", newServerInfo.getDhcpServerIp4().get());
rsahot036620655b2018-02-26 15:01:37 -0500826 if (newServerInfo.getDhcpConnectMac().isPresent()) {
827 etherReply.setDestinationMACAddress(newServerInfo.getDhcpConnectMac().get());
Charles Chand0d1e332017-10-10 16:53:32 -0400828 }
rsahot036620655b2018-02-26 15:01:37 -0500829 if (newServerInfo.getDhcpConnectVlan().isPresent()) {
830 etherReply.setVlanID(newServerInfo.getDhcpConnectVlan().get().toShort());
831 }
832
833 ipv4Packet.setDestinationAddress(newServerInfo.getDhcpServerIp4().get().toInt());
834
rsahot036620655b2018-02-26 15:01:37 -0500835 ConnectPoint inPort = context.inPacket().receivedFrom();
836 VlanId vlanId = VlanId.vlanId(ethernetPacket.getVlanID());
837 // add connected in port and vlan
838 CircuitId cid = new CircuitId(inPort.toString(), vlanId);
839 byte[] circuitId = cid.serialize();
840 DhcpOption circuitIdSubOpt = new DhcpOption();
Harshada Chaundkar6c54f202019-05-06 20:16:13 +0000841 circuitIdSubOpt.setCode(CIRCUIT_ID.getValue())
rsahot036620655b2018-02-26 15:01:37 -0500842 .setLength((byte) circuitId.length)
843 .setData(circuitId);
844
845 DhcpRelayAgentOption newRelayAgentOpt = new DhcpRelayAgentOption();
846 newRelayAgentOpt.setCode(OptionCode_CircuitID.getValue());
847 newRelayAgentOpt.addSubOption(circuitIdSubOpt);
848
849 // Removes END option first
850 List<DhcpOption> options = dhcpPacket.getOptions().stream()
851 .filter(opt -> opt.getCode() != OptionCode_END.getValue())
852 .collect(Collectors.toList());
853
854 // push relay agent option
855 options.add(newRelayAgentOpt);
856
857 // make sure option 255(End) is the last option
858 DhcpOption endOption = new DhcpOption();
859 endOption.setCode(OptionCode_END.getValue());
860 options.add(endOption);
861
862 dhcpPacket.setOptions(options);
863
864 relayAgentIp = serverInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
865
866 // Sets relay agent IP
867 int effectiveRelayAgentIp = relayAgentIp != null ?
868 relayAgentIp.toInt() : clientInterfaceIp.toInt();
869 dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
Charles Chan2de55302018-03-02 18:27:59 -0800870 log.debug("In Default, Relay Agent IP {}", effectiveRelayAgentIp);
Charles Chand0d1e332017-10-10 16:53:32 -0400871 } else {
rsahot036620655b2018-02-26 15:01:37 -0500872 if (!newServerInfo.getDhcpServerIp4().isPresent()) {
873 // do nothing
874 } else if (!newServerInfo.getDhcpConnectMac().isPresent()) {
875 continue;
876 } else {
877 relayAgentIp = newServerInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
878 // Sets relay agent IP
879 int effectiveRelayAgentIp = relayAgentIp != null ?
880 relayAgentIp.toInt() : clientInterfaceIp.toInt();
Mayank Tiwari742e94c2018-10-19 12:12:44 -0400881 Ip4Address effectiveRealRealyAgentIP = relayAgentIp != null ?
882 relayAgentIp : clientInterfaceIp;
rsahot036620655b2018-02-26 15:01:37 -0500883 dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
Mayank Tiwari742e94c2018-10-19 12:12:44 -0400884 ipv4Packet.setSourceAddress(effectiveRealRealyAgentIP.toInt());
885 log.debug("Source IP address set as relay agent IP with value: {}", effectiveRealRealyAgentIP);
Charles Chand0d1e332017-10-10 16:53:32 -0400886 }
Yi Tseng4f2a0462017-08-31 11:21:00 -0700887 }
Yi Tseng3df7f9d2017-08-17 13:08:31 -0700888
rsahot036620655b2018-02-26 15:01:37 -0500889 // Remove broadcast flag
890 dhcpPacket.setFlags((short) 0);
rsahot036620655b2018-02-26 15:01:37 -0500891 udpPacket.setPayload(dhcpPacket);
892 // As a DHCP relay, the source port should be server port( instead
893 // of client port.
894 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
895 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
896 ipv4Packet.setPayload(udpPacket);
897 ipv4Packet.setTtl((byte) 64);
898 etherReply.setPayload(ipv4Packet);
Taras Lemkin41785912018-03-26 14:52:58 +0000899 InternalPacket internalPacket = InternalPacket.internalPacket(etherReply,
rsahot036620655b2018-02-26 15:01:37 -0500900 serverInfo.getDhcpServerConnectPoint().get());
Taras Lemkin41785912018-03-26 14:52:58 +0000901
rsahot036620655b2018-02-26 15:01:37 -0500902 internalPackets.add(internalPacket);
903 }
Harshada Chaundkar6c54f202019-05-06 20:16:13 +0000904 if (!serverFound) {
905 log.warn("ProcessDhcp4PacketFromClient No Server Found");
906 }
rsahot036620655b2018-02-26 15:01:37 -0500907 return internalPackets;
Yi Tseng51301292017-07-28 13:02:59 -0700908 }
909
Charles Chand0dd7002017-10-08 23:53:36 -0400910
911 /**
912 * Do a basic routing for a packet from client (used for LQ processing).
913 *
914 * @param context the packet context
915 * @param ethernetPacket the ethernet payload to process
916 * @return processed packet
917 */
rsahot036620655b2018-02-26 15:01:37 -0500918 private List<InternalPacket> processLeaseQueryFromAgent(PacketContext context,
919 Ethernet ethernetPacket) {
920 ConnectPoint receivedFrom = context.inPacket().receivedFrom();
921 DeviceId receivedFromDevice = receivedFrom.deviceId();
922
Charles Chand0dd7002017-10-08 23:53:36 -0400923 // get dhcp header.
924 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
925 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
926 UDP udpPacket = (UDP) ipv4Packet.getPayload();
927 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
928
Charles Chan2de55302018-03-02 18:27:59 -0800929 Ip4Address relayAgentIp;
Charles Chand0dd7002017-10-08 23:53:36 -0400930
Charles Chand0dd7002017-10-08 23:53:36 -0400931 Ip4Address clientInterfaceIp =
932 interfaceService.getInterfacesByPort(context.inPacket().receivedFrom())
933 .stream()
934 .map(Interface::ipAddressesList)
935 .flatMap(Collection::stream)
936 .map(InterfaceIpAddress::ipAddress)
937 .filter(IpAddress::isIp4)
938 .map(IpAddress::getIp4Address)
939 .findFirst()
940 .orElse(null);
941 if (clientInterfaceIp == null) {
942 log.warn("Can't find interface IP for client interface for port {}",
rsahot036620655b2018-02-26 15:01:37 -0500943 context.inPacket().receivedFrom());
Charles Chand0dd7002017-10-08 23:53:36 -0400944 return null;
945 }
rsahot036620655b2018-02-26 15:01:37 -0500946
Charles Chand0dd7002017-10-08 23:53:36 -0400947 boolean isDirectlyConnected = directlyConnected(dhcpPacket);
rsahot036620655b2018-02-26 15:01:37 -0500948 boolean directConnFlag = directlyConnected(dhcpPacket);
949
950 // Multi DHCP Start
rsahot036620655b2018-02-26 15:01:37 -0500951 List<InternalPacket> internalPackets = new ArrayList<>();
952 List<DhcpServerInfo> serverInfoList = findValidServerInfo(directConnFlag);
Charles Chan2de55302018-03-02 18:27:59 -0800953 List<DhcpServerInfo> copyServerInfoList = new ArrayList<>(serverInfoList);
rsahot036620655b2018-02-26 15:01:37 -0500954
955 for (DhcpServerInfo serverInfo : copyServerInfoList) {
956 // get dhcp header.
957 etherReply = (Ethernet) ethernetPacket.clone();
958 ipv4Packet = (IPv4) etherReply.getPayload();
959 udpPacket = (UDP) ipv4Packet.getPayload();
960 dhcpPacket = (DHCP) udpPacket.getPayload();
961
962 if (!checkDhcpServerConnPt(directConnFlag, serverInfo)) {
963 log.warn("Can't get server connect point, ignore");
964 continue;
Yi Tseng3bd57ac2017-11-29 14:39:18 -0800965 }
rsahot036620655b2018-02-26 15:01:37 -0500966 DhcpServerInfo newServerInfo = getHostInfoForServerInfo(serverInfo, serverInfoList);
967 if (newServerInfo == null) {
968 log.warn("Can't get server interface with host info resolved, ignore");
969 continue;
970 }
Charles Chand0dd7002017-10-08 23:53:36 -0400971
rsahot036620655b2018-02-26 15:01:37 -0500972 Interface serverInterface = getServerInterface(newServerInfo);
973 if (serverInterface == null) {
974 log.warn("Can't get server interface, ignore");
975 continue;
976 }
977 Ip4Address ipFacingServer = getFirstIpFromInterface(serverInterface);
978 MacAddress macFacingServer = serverInterface.mac();
979 if (ipFacingServer == null || macFacingServer == null) {
980 log.warn("No IP address for server Interface {}", serverInterface);
981 continue;
982 }
Charles Chand0dd7002017-10-08 23:53:36 -0400983
rsahot036620655b2018-02-26 15:01:37 -0500984 etherReply.setSourceMACAddress(macFacingServer);
Charles Chan2de55302018-03-02 18:27:59 -0800985 etherReply.setDestinationMACAddress(newServerInfo.getDhcpConnectMac().get());
986 etherReply.setVlanID(newServerInfo.getDhcpConnectVlan().get().toShort());
rsahot036620655b2018-02-26 15:01:37 -0500987 ipv4Packet.setSourceAddress(ipFacingServer.toInt());
Charles Chan2de55302018-03-02 18:27:59 -0800988 ipv4Packet.setDestinationAddress(newServerInfo.getDhcpServerIp4().get().toInt());
rsahot036620655b2018-02-26 15:01:37 -0500989 if (isDirectlyConnected) {
990 // set default info and replace with indirect if available later on.
991 if (newServerInfo.getDhcpConnectMac().isPresent()) {
992 etherReply.setDestinationMACAddress(newServerInfo.getDhcpConnectMac().get());
993 }
994 if (newServerInfo.getDhcpConnectVlan().isPresent()) {
995 etherReply.setVlanID(serverInfo.getDhcpConnectVlan().get().toShort());
996 }
Taras Lemkin41785912018-03-26 14:52:58 +0000997 if (learnRouteFromLeasequery) {
rsahot036620655b2018-02-26 15:01:37 -0500998 relayAgentIp = newServerInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
999 // Sets relay agent IP
1000 int effectiveRelayAgentIp = relayAgentIp != null ?
1001 relayAgentIp.toInt() : clientInterfaceIp.toInt();
1002 dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
Taras Lemkin41785912018-03-26 14:52:58 +00001003 }
1004 } else {
1005 if (!newServerInfo.getDhcpServerIp4().isPresent()) {
1006 //do nothing
1007 } else if (!newServerInfo.getDhcpConnectMac().isPresent()) {
1008 continue;
1009 } else if (learnRouteFromLeasequery) {
1010 relayAgentIp = newServerInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
1011 // Sets relay agent IP
1012 int effectiveRelayAgentIp = relayAgentIp != null ?
1013 relayAgentIp.toInt() : clientInterfaceIp.toInt();
rsahot036620655b2018-02-26 15:01:37 -05001014 dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
Taras Lemkin41785912018-03-26 14:52:58 +00001015 log.debug("Relay Agent IP {}", relayAgentIp);
rsahot036620655b2018-02-26 15:01:37 -05001016 }
1017
Taras Lemkin41785912018-03-26 14:52:58 +00001018 log.trace("Indirect");
rsahot036620655b2018-02-26 15:01:37 -05001019 }
1020
1021 // Remove broadcast flag
1022 dhcpPacket.setFlags((short) 0);
1023
1024 udpPacket.setPayload(dhcpPacket);
1025 // As a DHCP relay, the source port should be server port( instead
1026 // of client port.
1027 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
1028 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
1029 ipv4Packet.setPayload(udpPacket);
1030 ipv4Packet.setTtl((byte) 64);
1031 etherReply.setPayload(ipv4Packet);
Taras Lemkin41785912018-03-26 14:52:58 +00001032 udpPacket.resetChecksum();
rsahot036620655b2018-02-26 15:01:37 -05001033 ////return etherReply;
Taras Lemkin41785912018-03-26 14:52:58 +00001034 InternalPacket internalPacket = InternalPacket.internalPacket(etherReply,
rsahot036620655b2018-02-26 15:01:37 -05001035 newServerInfo.getDhcpServerConnectPoint().get());
Taras Lemkin41785912018-03-26 14:52:58 +00001036
rsahot036620655b2018-02-26 15:01:37 -05001037 internalPackets.add(internalPacket);
Charles Chand0dd7002017-10-08 23:53:36 -04001038 }
Taras Lemkin41785912018-03-26 14:52:58 +00001039 log.debug("num of processLeaseQueryFromAgent packets to send is: {}", internalPackets.size());
Charles Chand0dd7002017-10-08 23:53:36 -04001040
rsahot036620655b2018-02-26 15:01:37 -05001041 return internalPackets;
Charles Chand0dd7002017-10-08 23:53:36 -04001042 }
1043
1044
Yi Tseng51301292017-07-28 13:02:59 -07001045 /**
1046 * Writes DHCP record to the store according to the request DHCP packet (Discover, Request).
1047 *
1048 * @param location the location which DHCP packet comes from
1049 * @param ethernet the DHCP packet
1050 * @param dhcpPayload the DHCP payload
1051 */
1052 private void writeRequestDhcpRecord(ConnectPoint location,
1053 Ethernet ethernet,
1054 DHCP dhcpPayload) {
1055 VlanId vlanId = VlanId.vlanId(ethernet.getVlanID());
1056 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1057 HostId hostId = HostId.hostId(macAddress, vlanId);
1058 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1059 if (record == null) {
1060 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
1061 } else {
1062 record = record.clone();
1063 }
1064 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
1065 record.ip4Status(dhcpPayload.getPacketType());
1066 record.setDirectlyConnected(directlyConnected(dhcpPayload));
1067 if (!directlyConnected(dhcpPayload)) {
1068 // Update gateway mac address if the host is not directly connected
1069 record.nextHop(ethernet.getSourceMAC());
1070 }
1071 record.updateLastSeen();
1072 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
1073 }
1074
1075 /**
1076 * Writes DHCP record to the store according to the response DHCP packet (Offer, Ack).
1077 *
1078 * @param ethernet the DHCP packet
1079 * @param dhcpPayload the DHCP payload
1080 */
1081 private void writeResponseDhcpRecord(Ethernet ethernet,
1082 DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001083 Optional<Interface> outInterface = getClientInterface(ethernet, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -07001084 if (!outInterface.isPresent()) {
1085 log.warn("Failed to determine where to send {}", dhcpPayload.getPacketType());
1086 return;
1087 }
1088
1089 Interface outIface = outInterface.get();
1090 ConnectPoint location = outIface.connectPoint();
Yi Tseng4f2a0462017-08-31 11:21:00 -07001091 VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001092 if (vlanId == null) {
1093 vlanId = outIface.vlan();
1094 }
Yi Tseng51301292017-07-28 13:02:59 -07001095 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1096 HostId hostId = HostId.hostId(macAddress, vlanId);
1097 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1098 if (record == null) {
1099 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
1100 } else {
1101 record = record.clone();
1102 }
1103 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
1104 if (dhcpPayload.getPacketType() == DHCP.MsgType.DHCPACK) {
1105 record.ip4Address(Ip4Address.valueOf(dhcpPayload.getYourIPAddress()));
1106 }
1107 record.ip4Status(dhcpPayload.getPacketType());
1108 record.setDirectlyConnected(directlyConnected(dhcpPayload));
1109 record.updateLastSeen();
1110 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
1111 }
1112
1113 /**
1114 * Build the DHCP offer/ack with proper client port.
1115 *
1116 * @param ethernetPacket the original packet comes from server
1117 * @return new packet which will send to the client
1118 */
Taras Lemkin41785912018-03-26 14:52:58 +00001119 private InternalPacket processDhcpPacketFromServer(PacketContext context, Ethernet ethernetPacket) {
Yi Tseng51301292017-07-28 13:02:59 -07001120 // get dhcp header.
rsahot036620655b2018-02-26 15:01:37 -05001121 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
Yi Tseng51301292017-07-28 13:02:59 -07001122 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
1123 UDP udpPacket = (UDP) ipv4Packet.getPayload();
1124 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
1125
1126 // determine the vlanId of the client host - note that this vlan id
1127 // could be different from the vlan in the packet from the server
Yi Tsengdcef2c22017-08-05 20:34:06 -07001128 Interface clientInterface = getClientInterface(ethernetPacket, dhcpPayload).orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -07001129
Yi Tsengdcef2c22017-08-05 20:34:06 -07001130 if (clientInterface == null) {
Yi Tseng51301292017-07-28 13:02:59 -07001131 log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
1132 return null;
1133 }
Yi Tsengdcef2c22017-08-05 20:34:06 -07001134 VlanId vlanId;
rsahot036620655b2018-02-26 15:01:37 -05001135 ConnectPoint inPort = context.inPacket().receivedFrom();
1136 boolean directConnFlag = directlyConnected(dhcpPayload);
1137 DhcpServerInfo foundServerInfo = findServerInfoFromServer(directConnFlag, inPort);
1138
1139 if (foundServerInfo == null) {
jayakumarthazhath27aa7722018-09-25 15:56:51 -04001140 log.warn("Cannot find server info for {} server, inPort {}",
1141 directConnFlag ? "direct" : "indirect", inPort);
rsahot036620655b2018-02-26 15:01:37 -05001142 return null;
1143 } else {
Taras Lemkin41785912018-03-26 14:52:58 +00001144 if (Dhcp4HandlerUtil.isServerIpEmpty(foundServerInfo)) {
rsahot036620655b2018-02-26 15:01:37 -05001145 log.warn("Cannot find server info's ipaddress");
1146 return null;
1147 }
1148 }
Yi Tsengdcef2c22017-08-05 20:34:06 -07001149 if (clientInterface.vlanTagged().isEmpty()) {
1150 vlanId = clientInterface.vlan();
1151 } else {
1152 // might be multiple vlan in same interface
Yi Tseng4f2a0462017-08-31 11:21:00 -07001153 vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001154 }
1155 if (vlanId == null) {
1156 vlanId = VlanId.NONE;
1157 }
1158 etherReply.setVlanID(vlanId.toShort());
1159 etherReply.setSourceMACAddress(clientInterface.mac());
Yi Tseng51301292017-07-28 13:02:59 -07001160
Yi Tsengdcef2c22017-08-05 20:34:06 -07001161 if (!directlyConnected(dhcpPayload)) {
1162 // if client is indirectly connected, try use next hop mac address
1163 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1164 HostId hostId = HostId.hostId(macAddress, vlanId);
Daniel Ginsburgb1dec912018-01-29 11:40:22 -08001165 if (((int) dhcpPayload.getFlags() & 0x8000) == 0x0000) {
1166 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1167 if (record != null) {
1168 // if next hop can be found, use mac address of next hop
1169 record.nextHop().ifPresent(etherReply::setDestinationMACAddress);
1170 } else {
1171 // otherwise, discard the packet
1172 log.warn("Can't find record for host id {}, discard packet", hostId);
1173 return null;
1174 }
Yi Tsengdcef2c22017-08-05 20:34:06 -07001175 } else {
Daniel Ginsburgb1dec912018-01-29 11:40:22 -08001176 etherReply.setDestinationMACAddress(MacAddress.BROADCAST);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001177 }
Yi Tseng1696f562017-08-17 17:43:38 -07001178 } else {
1179 etherReply.setDestinationMACAddress(dhcpPayload.getClientHardwareAddress());
Yi Tsengdcef2c22017-08-05 20:34:06 -07001180 }
1181
Yi Tseng3df7f9d2017-08-17 13:08:31 -07001182 Ip4Address ipFacingClient = getFirstIpFromInterface(clientInterface);
Mayank Tiwari2171bc02018-11-14 15:41:40 -05001183 if (directlyConnected(dhcpPayload)) {
1184 // we leave the srcMac from the original packet
1185 // figure out the relay agent IP corresponding to the original request
1186 if (ipFacingClient == null) {
1187 log.warn("Cannot determine relay agent interface Ipv4 addr for host {}/{}. "
1188 + "Aborting relay for dhcp packet from server {}",
1189 etherReply.getDestinationMAC(), clientInterface.vlan(),
1190 ethernetPacket);
1191 return null;
1192 }
1193 // SRC_IP: IP facing client
1194 ipv4Packet.setSourceAddress(ipFacingClient.toInt());
1195 } else {
1196 // Get the IP address of the relay agent
1197 Ip4Address relayAgentIp = foundServerInfo
1198 .getRelayAgentIp4(clientInterface.connectPoint().deviceId()).orElse(null);
1199 if (relayAgentIp == null) {
1200 if (ipFacingClient == null) {
1201 log.warn("Cannot determine relay agent interface Ipv4 addr for host {}/{}. "
1202 + "Aborting relay for dhcp packet from server for indirect host {}",
1203 etherReply.getDestinationMAC(), clientInterface.vlan(),
1204 ethernetPacket);
1205 return null;
1206 } else {
1207 // SRC_IP: IP facing client
1208 ipv4Packet.setSourceAddress(ipFacingClient.toInt());
1209 }
1210 } else {
1211 // SRC_IP: relay agent IP
1212 ipv4Packet.setSourceAddress(relayAgentIp.toInt());
1213 }
Yi Tseng51301292017-07-28 13:02:59 -07001214 }
Yi Tseng51301292017-07-28 13:02:59 -07001215 // DST_IP: offered IP
Daniel Ginsburgb1dec912018-01-29 11:40:22 -08001216 if (((int) dhcpPayload.getFlags() & 0x8000) == 0x0000) {
1217 ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
1218 } else {
1219 ipv4Packet.setDestinationAddress(BROADCAST_IP);
1220 }
Yi Tseng51301292017-07-28 13:02:59 -07001221 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
1222 if (directlyConnected(dhcpPayload)) {
1223 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
1224 } else {
1225 // forward to another dhcp relay
Yi Tseng93ba53c2017-09-14 13:24:21 -07001226 // FIXME: Currently we assume the DHCP comes from a L2 relay with
1227 // Option 82, this might not work if DHCP message comes from
1228 // L3 relay.
1229 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
Yi Tseng51301292017-07-28 13:02:59 -07001230 }
1231
1232 udpPacket.setPayload(dhcpPayload);
1233 ipv4Packet.setPayload(udpPacket);
1234 etherReply.setPayload(ipv4Packet);
Taras Lemkin41785912018-03-26 14:52:58 +00001235 return InternalPacket.internalPacket(etherReply, clientInterface.connectPoint());
Yi Tseng51301292017-07-28 13:02:59 -07001236 }
1237
Yi Tsengdcef2c22017-08-05 20:34:06 -07001238 /**
Charles Chand0dd7002017-10-08 23:53:36 -04001239 * Build the DHCP offer/ack with proper client port.
1240 *
1241 * @param ethernetPacket the original packet comes from server
1242 * @return new packet which will send to the client
1243 */
Taras Lemkin41785912018-03-26 14:52:58 +00001244 private InternalPacket processLeaseQueryFromServer(Ethernet ethernetPacket) {
Charles Chand0dd7002017-10-08 23:53:36 -04001245 // get dhcp header.
1246 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
1247 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
1248 UDP udpPacket = (UDP) ipv4Packet.getPayload();
1249 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
1250
1251 // determine the vlanId of the client host - note that this vlan id
1252 // could be different from the vlan in the packet from the server
Taras Lemkin41785912018-03-26 14:52:58 +00001253 Interface clientInterface = null;
1254 MacAddress destinationMac = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1255
1256 if (!learnRouteFromLeasequery) {
1257 int giaddr = ipv4Packet.getDestinationAddress();
1258 IpAddress destinationAddress = Ip4Address.valueOf(giaddr);
1259 log.debug("DHCPLEASEQUERYRESP giaddr: {}({})", giaddr, destinationAddress);
1260
1261 Host destinationHost = hostService.getHostsByIp(destinationAddress).stream().findFirst().orElse(null);
1262 if (destinationHost != null) {
1263 destinationMac = destinationHost.mac();
1264 log.trace("DHCPLEASEQUERYRESP destination mac is: {}", destinationMac);
1265 ConnectPoint destinationLocation = destinationHost.location();
1266 log.trace("Lookup for client interface by destination location {}", destinationLocation);
1267 clientInterface = interfaceService.getInterfacesByPort(destinationLocation)
1268 .stream()
1269 .filter(iface -> interfaceContainsVlan(iface, VlanId.vlanId(etherReply.getVlanID())))
1270 .findFirst()
1271 .orElse(null);
1272 log.trace("Found Host {} by ip {}", destinationHost, destinationAddress);
1273 log.debug("DHCPLEASEQUERYRESP Client interface: {}",
1274 (clientInterface != null ? clientInterface : "not resolved"));
1275
1276 }
1277 } else {
1278 clientInterface = getClientInterface(ethernetPacket, dhcpPayload).orElse(null);
1279 }
Charles Chand0dd7002017-10-08 23:53:36 -04001280
1281 if (clientInterface == null) {
1282 log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
1283 return null;
1284 }
1285 VlanId vlanId;
1286 if (clientInterface.vlanTagged().isEmpty()) {
1287 vlanId = clientInterface.vlan();
1288 } else {
1289 // might be multiple vlan in same interface
1290 vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
1291 }
1292 if (vlanId == null) {
1293 vlanId = VlanId.NONE;
1294 }
1295 etherReply.setVlanID(vlanId.toShort());
1296 etherReply.setSourceMACAddress(clientInterface.mac());
1297
Taras Lemkin41785912018-03-26 14:52:58 +00001298 if (!directlyConnected(dhcpPayload) && learnRouteFromLeasequery) {
Charles Chand0dd7002017-10-08 23:53:36 -04001299 // if client is indirectly connected, try use next hop mac address
1300 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1301 HostId hostId = HostId.hostId(macAddress, vlanId);
1302 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1303 if (record != null) {
1304 // if next hop can be found, use mac address of next hop
Taras Lemkin41785912018-03-26 14:52:58 +00001305 Optional<MacAddress> nextHop = record.nextHopTemp();
1306 if (!nextHop.isPresent()) {
1307 nextHop = record.nextHop();
1308 }
1309 nextHop.ifPresent(etherReply::setDestinationMACAddress);
Charles Chand0dd7002017-10-08 23:53:36 -04001310 } else {
1311 // otherwise, discard the packet
1312 log.warn("Can't find record for host id {}, discard packet", hostId);
1313 return null;
1314 }
1315 } else {
Taras Lemkin41785912018-03-26 14:52:58 +00001316 etherReply.setDestinationMACAddress(destinationMac);
Charles Chand0dd7002017-10-08 23:53:36 -04001317 }
1318
Charles Chand0dd7002017-10-08 23:53:36 -04001319 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
Taras Lemkin41785912018-03-26 14:52:58 +00001320 if (directlyConnected(dhcpPayload)) {
1321 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
1322 } else {
1323 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
1324 }
Charles Chand0dd7002017-10-08 23:53:36 -04001325
1326 udpPacket.setPayload(dhcpPayload);
1327 ipv4Packet.setPayload(udpPacket);
1328 etherReply.setPayload(ipv4Packet);
Taras Lemkin41785912018-03-26 14:52:58 +00001329 udpPacket.resetChecksum();
1330
1331 return InternalPacket.internalPacket(etherReply, clientInterface.connectPoint());
Charles Chand0dd7002017-10-08 23:53:36 -04001332 }
1333 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -07001334 * Extracts VLAN ID from relay agent option.
1335 *
1336 * @param dhcpPayload the DHCP payload
1337 * @return VLAN ID from DHCP payload; null if not exists
1338 */
Yi Tseng4f2a0462017-08-31 11:21:00 -07001339 private VlanId getVlanIdFromRelayAgentOption(DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001340 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
1341 if (option == null) {
1342 return null;
1343 }
1344 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
1345 if (circuitIdSubOption == null) {
1346 return null;
1347 }
1348 try {
1349 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
1350 return circuitId.vlanId();
1351 } catch (IllegalArgumentException e) {
1352 // can't deserialize the circuit ID
1353 return null;
1354 }
1355 }
1356
1357 /**
1358 * Removes DHCP relay agent information option (option 82) from DHCP payload.
1359 * Also reset giaddr to 0
1360 *
1361 * @param ethPacket the Ethernet packet to be processed
1362 * @return Ethernet packet processed
1363 */
1364 private Ethernet removeRelayAgentOption(Ethernet ethPacket) {
rsahot036620655b2018-02-26 15:01:37 -05001365 Ethernet ethernet = (Ethernet) ethPacket.duplicate();
Yi Tsengdcef2c22017-08-05 20:34:06 -07001366 IPv4 ipv4 = (IPv4) ethernet.getPayload();
1367 UDP udp = (UDP) ipv4.getPayload();
1368 DHCP dhcpPayload = (DHCP) udp.getPayload();
1369
1370 // removes relay agent information option
1371 List<DhcpOption> options = dhcpPayload.getOptions();
1372 options = options.stream()
1373 .filter(option -> option.getCode() != OptionCode_CircuitID.getValue())
1374 .collect(Collectors.toList());
1375 dhcpPayload.setOptions(options);
1376 dhcpPayload.setGatewayIPAddress(0);
1377
1378 udp.setPayload(dhcpPayload);
1379 ipv4.setPayload(udp);
1380 ethernet.setPayload(ipv4);
1381 return ethernet;
1382 }
1383
Taras Lemkin41785912018-03-26 14:52:58 +00001384 private boolean isDhcpPacketLeasequery(DHCP dhcpPacket) {
1385 switch (dhcpPacket.getPacketType()) {
1386 case DHCPLEASEACTIVE:
1387 case DHCPLEASEQUERY:
1388 case DHCPLEASEUNASSIGNED:
1389 case DHCPLEASEUNKNOWN:
1390 return true;
1391 default:
1392 return false;
1393 }
1394 }
Yi Tseng51301292017-07-28 13:02:59 -07001395
1396 /**
1397 * Check if the host is directly connected to the network or not.
1398 *
1399 * @param dhcpPayload the dhcp payload
1400 * @return true if the host is directly connected to the network; false otherwise
1401 */
1402 private boolean directlyConnected(DHCP dhcpPayload) {
Taras Lemkin41785912018-03-26 14:52:58 +00001403 // leasequery is always indirect
1404 if (isDhcpPacketLeasequery(dhcpPayload)) {
1405 return false;
1406 }
1407
Yi Tseng440e2b72017-08-24 14:47:34 -07001408 DhcpRelayAgentOption relayAgentOption =
1409 (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
Yi Tseng51301292017-07-28 13:02:59 -07001410
1411 // Doesn't contains relay option
1412 if (relayAgentOption == null) {
1413 return true;
1414 }
1415
Yi Tseng440e2b72017-08-24 14:47:34 -07001416 // check circuit id, if circuit id is invalid, we say it is an indirect host
1417 DhcpOption circuitIdOpt = relayAgentOption.getSubOption(CIRCUIT_ID.getValue());
Yi Tseng51301292017-07-28 13:02:59 -07001418
Yi Tseng440e2b72017-08-24 14:47:34 -07001419 try {
1420 CircuitId.deserialize(circuitIdOpt.getData());
Yi Tseng51301292017-07-28 13:02:59 -07001421 return true;
Yi Tseng440e2b72017-08-24 14:47:34 -07001422 } catch (Exception e) {
1423 // invalid circuit id
1424 return false;
Yi Tseng51301292017-07-28 13:02:59 -07001425 }
Yi Tseng51301292017-07-28 13:02:59 -07001426 }
1427
1428
1429 /**
1430 * Send the DHCP ack to the requester host.
1431 * Modify Host or Route store according to the type of DHCP.
1432 *
1433 * @param ethernetPacketAck the packet
1434 * @param dhcpPayload the DHCP data
1435 */
1436 private void handleDhcpAck(Ethernet ethernetPacketAck, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001437 Optional<Interface> outInterface = getClientInterface(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -07001438 if (!outInterface.isPresent()) {
1439 log.warn("Can't find output interface for dhcp: {}", dhcpPayload);
1440 return;
1441 }
1442
1443 Interface outIface = outInterface.get();
1444 HostLocation hostLocation = new HostLocation(outIface.connectPoint(), System.currentTimeMillis());
1445 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
Yi Tseng4f2a0462017-08-31 11:21:00 -07001446 VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001447 if (vlanId == null) {
1448 vlanId = outIface.vlan();
1449 }
Yi Tseng51301292017-07-28 13:02:59 -07001450 HostId hostId = HostId.hostId(macAddress, vlanId);
1451 Ip4Address ip = Ip4Address.valueOf(dhcpPayload.getYourIPAddress());
1452
1453 if (directlyConnected(dhcpPayload)) {
1454 // Add to host store if it connect to network directly
1455 Set<IpAddress> ips = Sets.newHashSet(ip);
Yi Tsengaa417a62017-09-08 17:22:51 -07001456 Host host = hostService.getHost(hostId);
Yi Tseng51301292017-07-28 13:02:59 -07001457
Yi Tsengaa417a62017-09-08 17:22:51 -07001458 Set<HostLocation> hostLocations = Sets.newHashSet(hostLocation);
1459 if (host != null) {
1460 // Dual homing support:
1461 // if host exists, use old locations and new location
1462 hostLocations.addAll(host.locations());
1463 }
1464 HostDescription desc = new DefaultHostDescription(macAddress, vlanId,
1465 hostLocations, ips, false);
1466 // Add IP address when dhcp server give the host new ip address
1467 providerService.hostDetected(hostId, desc, false);
Yi Tseng51301292017-07-28 13:02:59 -07001468 } else {
1469 // Add to route store if it does not connect to network directly
1470 // Get gateway host IP according to host mac address
Yi Tsengdcef2c22017-08-05 20:34:06 -07001471 // TODO: remove relay store here
Yi Tseng51301292017-07-28 13:02:59 -07001472 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1473
1474 if (record == null) {
1475 log.warn("Can't find DHCP record of host {}", hostId);
1476 return;
1477 }
1478
1479 MacAddress gwMac = record.nextHop().orElse(null);
1480 if (gwMac == null) {
1481 log.warn("Can't find gateway mac address from record {}", record);
1482 return;
1483 }
1484
1485 HostId gwHostId = HostId.hostId(gwMac, record.vlanId());
1486 Host gwHost = hostService.getHost(gwHostId);
1487
1488 if (gwHost == null) {
1489 log.warn("Can't find gateway host {}", gwHostId);
1490 return;
1491 }
1492
1493 Ip4Address nextHopIp = gwHost.ipAddresses()
1494 .stream()
1495 .filter(IpAddress::isIp4)
1496 .map(IpAddress::getIp4Address)
1497 .findFirst()
1498 .orElse(null);
1499
1500 if (nextHopIp == null) {
1501 log.warn("Can't find IP address of gateway {}", gwHost);
1502 return;
1503 }
1504
Charles Chan6305b692018-04-04 11:43:54 -07001505 Route route = new Route(Route.Source.DHCP, ip.toIpPrefix(), nextHopIp);
Daniel Ginsburgc1d6aaf2018-06-09 01:43:59 +03001506 routeStore.replaceRoute(route);
Yi Tseng51301292017-07-28 13:02:59 -07001507 }
Yi Tseng51301292017-07-28 13:02:59 -07001508 }
1509
1510 /**
Yi Tseng51301292017-07-28 13:02:59 -07001511 * Gets output interface of a dhcp packet.
1512 * If option 82 exists in the dhcp packet and the option was sent by
Yi Tseng4f2a0462017-08-31 11:21:00 -07001513 * ONOS (circuit format is correct), use the connect
Yi Tseng51301292017-07-28 13:02:59 -07001514 * point and vlan id from circuit id; otherwise, find host by destination
1515 * address and use vlan id from sender (dhcp server).
1516 *
1517 * @param ethPacket the ethernet packet
1518 * @param dhcpPayload the dhcp packet
1519 * @return an interface represent the output port and vlan; empty value
1520 * if the host or circuit id not found
1521 */
Yi Tsengdcef2c22017-08-05 20:34:06 -07001522 private Optional<Interface> getClientInterface(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tseng51301292017-07-28 13:02:59 -07001523 VlanId originalPacketVlanId = VlanId.vlanId(ethPacket.getVlanID());
Yi Tseng51301292017-07-28 13:02:59 -07001524 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
1525
Yi Tseng4f2a0462017-08-31 11:21:00 -07001526 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
1527 try {
1528 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
1529 ConnectPoint connectPoint = ConnectPoint.deviceConnectPoint(circuitId.connectPoint());
1530 VlanId vlanId = circuitId.vlanId();
1531 return interfaceService.getInterfacesByPort(connectPoint)
1532 .stream()
1533 .filter(iface -> interfaceContainsVlan(iface, vlanId))
1534 .findFirst();
1535 } catch (IllegalArgumentException ex) {
1536 // invalid circuit format, didn't sent by ONOS
1537 log.debug("Invalid circuit {}, use information from dhcp payload",
1538 circuitIdSubOption.getData());
Yi Tseng51301292017-07-28 13:02:59 -07001539 }
Yi Tseng51301292017-07-28 13:02:59 -07001540 // Use Vlan Id from DHCP server if DHCP relay circuit id was not
1541 // sent by ONOS or circuit Id can't be parsed
Yi Tsengdcef2c22017-08-05 20:34:06 -07001542 // TODO: remove relay store from this method
Yi Tseng51301292017-07-28 13:02:59 -07001543 MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
jayakumarthazhath27aa7722018-09-25 15:56:51 -04001544 VlanId filteredVlanId = getVlanIdFromDhcpRecord(dstMac, originalPacketVlanId);
1545 // Get the vlan from the dhcp record
1546 if (filteredVlanId == null) {
1547 log.debug("not find the matching DHCP record for mac: {} and vlan: {}", dstMac, originalPacketVlanId);
1548 return Optional.empty();
1549 }
jayakumarthazhath27aa7722018-09-25 15:56:51 -04001550 Optional<DhcpRecord> dhcpRecord = dhcpRelayStore.getDhcpRecord(HostId.hostId(dstMac, filteredVlanId));
Yi Tsengdcef2c22017-08-05 20:34:06 -07001551 ConnectPoint clientConnectPoint = dhcpRecord
Yi Tseng51301292017-07-28 13:02:59 -07001552 .map(DhcpRecord::locations)
1553 .orElse(Collections.emptySet())
1554 .stream()
1555 .reduce((hl1, hl2) -> {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001556 // find latest host connect point
Yi Tseng51301292017-07-28 13:02:59 -07001557 if (hl1 == null || hl2 == null) {
1558 return hl1 == null ? hl2 : hl1;
1559 }
1560 return hl1.time() > hl2.time() ? hl1 : hl2;
1561 })
Yi Tsengdcef2c22017-08-05 20:34:06 -07001562 .orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -07001563
Yi Tsengdcef2c22017-08-05 20:34:06 -07001564 if (clientConnectPoint != null) {
1565 return interfaceService.getInterfacesByPort(clientConnectPoint)
1566 .stream()
jayakumarthazhath27aa7722018-09-25 15:56:51 -04001567 .filter(iface -> interfaceContainsVlan(iface, filteredVlanId))
Yi Tsengdcef2c22017-08-05 20:34:06 -07001568 .findFirst();
1569 }
1570 return Optional.empty();
Yi Tseng51301292017-07-28 13:02:59 -07001571 }
1572
1573 /**
jayakumarthazhath27aa7722018-09-25 15:56:51 -04001574 * Get the required vlanId in case the DCHP record has more than one vlanId for a given MAC.
1575 *
1576 * @param mac MAC address of the DHCP client
1577 * @param vlan Expected vlan of the DHCP client
1578 */
1579 private VlanId getVlanIdFromDhcpRecord(MacAddress mac, VlanId vlan) {
1580 // Get all the DHCP records matching with the mac address
1581 // If only one entry is present then pick the vlan of that entry
1582 // If more then one entry is present then look for an entry with matching vlan
1583 // else return null
1584 Collection<DhcpRecord> records = dhcpRelayStore.getDhcpRecords();
1585 List<DhcpRecord> filteredRecords = new ArrayList<>();
1586 for (DhcpRecord e: records) {
1587 if (e.macAddress().equals(mac)) {
1588 filteredRecords.add(e);
1589 }
1590 }
1591 log.debug("getVlanIdFromDhcpRecord mac: {} vlan: {}", mac, vlan);
1592 log.debug("filteredRecords are: {}", filteredRecords);
1593 if (filteredRecords.size() == 1) {
1594 log.debug("Only one DHCP record entry. Returning back the vlan of that DHCP record: {}", filteredRecords);
1595 return filteredRecords.get(0).vlanId();
1596 }
1597 // Check in the DHCP filtered record for matching vlan
1598 for (DhcpRecord e: filteredRecords) {
1599 if (e.vlanId().equals(vlan)) {
1600 log.debug("Found a matching vlan entry in the DHCP record:{}", e);
1601 return vlan;
1602 }
1603 }
1604 // Found nothing return null
1605 log.debug("Returning null as no matching or more than one matching entry found");
1606 return null;
1607
1608 }
1609
jayakumarthazhath27aa7722018-09-25 15:56:51 -04001610 /**
Yi Tseng51301292017-07-28 13:02:59 -07001611 * Send the response DHCP to the requester host.
1612 *
Taras Lemkin41785912018-03-26 14:52:58 +00001613 * @param thePacket the packet
Yi Tseng51301292017-07-28 13:02:59 -07001614 * @param dhcpPayload the DHCP data
1615 */
Taras Lemkin41785912018-03-26 14:52:58 +00001616 private void sendResponseToClient(InternalPacket thePacket, DHCP dhcpPayload) {
1617 checkNotNull(thePacket, "Nothing to send");
1618 checkNotNull(thePacket.getPacket(), "Packet to send must not be empty");
1619 checkNotNull(thePacket.getDestLocation(), "Packet destination not be empty");
1620
1621 Ethernet ethPacket = thePacket.getPacket();
Yi Tsengdcef2c22017-08-05 20:34:06 -07001622 if (directlyConnected(dhcpPayload)) {
1623 ethPacket = removeRelayAgentOption(ethPacket);
1624 }
Taras Lemkin41785912018-03-26 14:52:58 +00001625
Yi Tsengdcef2c22017-08-05 20:34:06 -07001626 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
Taras Lemkin41785912018-03-26 14:52:58 +00001627 .setOutput(thePacket.getDestLocation().port())
Yi Tsengdcef2c22017-08-05 20:34:06 -07001628 .build();
1629 OutboundPacket o = new DefaultOutboundPacket(
Taras Lemkin41785912018-03-26 14:52:58 +00001630 thePacket.getDestLocation().deviceId(),
Yi Tsengdcef2c22017-08-05 20:34:06 -07001631 treatment,
1632 ByteBuffer.wrap(ethPacket.serialize()));
1633 if (log.isTraceEnabled()) {
Taras Lemkin41785912018-03-26 14:52:58 +00001634 log.trace("Relaying packet to DHCP client {} via {}",
Yi Tsengdcef2c22017-08-05 20:34:06 -07001635 ethPacket,
Taras Lemkin41785912018-03-26 14:52:58 +00001636 thePacket.getDestLocation());
Yi Tsengdcef2c22017-08-05 20:34:06 -07001637 }
1638 packetService.emit(o);
Yi Tseng51301292017-07-28 13:02:59 -07001639 }
Yi Tseng483ac6f2017-08-02 15:03:31 -07001640
Yi Tsengaa417a62017-09-08 17:22:51 -07001641 @Override
1642 public void triggerProbe(Host host) {
1643 // Do nothing here
1644 }
1645
1646 @Override
1647 public ProviderId id() {
Charles Chand988c282017-09-12 17:09:32 -07001648 return PROVIDER_ID;
Yi Tsengaa417a62017-09-08 17:22:51 -07001649 }
1650
Yi Tseng483ac6f2017-08-02 15:03:31 -07001651 class InternalHostListener implements HostListener {
1652 @Override
1653 public void event(HostEvent event) {
Yi Tseng919b2df2017-09-07 16:22:51 -07001654 if (!configured()) {
1655 return;
1656 }
Yi Tseng483ac6f2017-08-02 15:03:31 -07001657 switch (event.type()) {
1658 case HOST_ADDED:
1659 case HOST_UPDATED:
Charles Chanc7f63b62018-08-13 10:32:03 -07001660 case HOST_MOVED:
Jordan Halterman6328db72018-04-10 13:34:50 -04001661 log.trace("Scheduled host event {}", event);
1662 hostEventExecutor.execute(() -> hostUpdated(event.subject()));
Yi Tseng483ac6f2017-08-02 15:03:31 -07001663 break;
1664 case HOST_REMOVED:
Jordan Halterman6328db72018-04-10 13:34:50 -04001665 log.trace("Scheduled host event {}", event);
1666 hostEventExecutor.execute(() -> hostRemoved(event.subject()));
Yi Tseng483ac6f2017-08-02 15:03:31 -07001667 break;
Yi Tseng483ac6f2017-08-02 15:03:31 -07001668 default:
1669 break;
1670 }
1671 }
1672 }
1673
1674 /**
Yi Tseng483ac6f2017-08-02 15:03:31 -07001675 * Handle host updated.
1676 * If the host is DHCP server or gateway, update connect mac and vlan.
1677 *
1678 * @param host the host
1679 */
1680 private void hostUpdated(Host host) {
Yi Tseng525ff402017-10-23 19:39:39 -07001681 hostUpdated(host, defaultServerInfoList);
1682 hostUpdated(host, indirectServerInfoList);
Yi Tseng483ac6f2017-08-02 15:03:31 -07001683 }
1684
Yi Tseng525ff402017-10-23 19:39:39 -07001685 private void hostUpdated(Host host, List<DhcpServerInfo> srverInfoList) {
jjosep004c41357882018-08-21 09:01:10 -04001686 srverInfoList.stream().forEach(serverInfo -> {
1687 Ip4Address targetIp = serverInfo.getDhcpGatewayIp4().orElse(null);
Yi Tseng525ff402017-10-23 19:39:39 -07001688 Ip4Address serverIp = serverInfo.getDhcpServerIp4().orElse(null);
Yi Tseng525ff402017-10-23 19:39:39 -07001689 if (targetIp == null) {
1690 targetIp = serverIp;
1691 }
Yi Tseng525ff402017-10-23 19:39:39 -07001692 if (targetIp != null) {
1693 if (host.ipAddresses().contains(targetIp)) {
1694 serverInfo.setDhcpConnectMac(host.mac());
1695 serverInfo.setDhcpConnectVlan(host.vlan());
1696 requestDhcpPacket(serverIp);
1697 }
1698 }
jjosep004c41357882018-08-21 09:01:10 -04001699 });
Yi Tseng525ff402017-10-23 19:39:39 -07001700 }
1701
1702
Yi Tseng483ac6f2017-08-02 15:03:31 -07001703 /**
1704 * Handle host removed.
1705 * If the host is DHCP server or gateway, unset connect mac and vlan.
1706 *
1707 * @param host the host
1708 */
1709 private void hostRemoved(Host host) {
Yi Tseng525ff402017-10-23 19:39:39 -07001710 hostRemoved(host, defaultServerInfoList);
1711 hostRemoved(host, indirectServerInfoList);
1712 }
1713
1714 private void hostRemoved(Host host, List<DhcpServerInfo> serverInfoList) {
jjosep004c41357882018-08-21 09:01:10 -04001715 serverInfoList.stream().forEach(serverInfo -> {
1716 Ip4Address targetIp = serverInfo.getDhcpGatewayIp4().orElse(null);
Yi Tseng525ff402017-10-23 19:39:39 -07001717 Ip4Address serverIp = serverInfo.getDhcpServerIp4().orElse(null);
Yi Tseng525ff402017-10-23 19:39:39 -07001718 if (targetIp == null) {
1719 targetIp = serverIp;
Yi Tseng919b2df2017-09-07 16:22:51 -07001720 }
Yi Tseng525ff402017-10-23 19:39:39 -07001721
1722 if (targetIp != null) {
1723 if (host.ipAddresses().contains(targetIp)) {
Yi Tseng919b2df2017-09-07 16:22:51 -07001724 serverInfo.setDhcpConnectVlan(null);
1725 serverInfo.setDhcpConnectMac(null);
Yi Tseng525ff402017-10-23 19:39:39 -07001726 cancelDhcpPacket(serverIp);
Yi Tseng919b2df2017-09-07 16:22:51 -07001727 }
Yi Tseng483ac6f2017-08-02 15:03:31 -07001728 }
jjosep004c41357882018-08-21 09:01:10 -04001729 });
Yi Tseng525ff402017-10-23 19:39:39 -07001730 }
Yi Tseng919b2df2017-09-07 16:22:51 -07001731
Yi Tseng525ff402017-10-23 19:39:39 -07001732 private void requestDhcpPacket(Ip4Address serverIp) {
1733 requestServerDhcpPacket(serverIp);
1734 requestClientDhcpPacket(serverIp);
1735 }
Yi Tseng919b2df2017-09-07 16:22:51 -07001736
Yi Tseng525ff402017-10-23 19:39:39 -07001737 private void cancelDhcpPacket(Ip4Address serverIp) {
1738 cancelServerDhcpPacket(serverIp);
1739 cancelClientDhcpPacket(serverIp);
1740 }
1741
1742 private void cancelServerDhcpPacket(Ip4Address serverIp) {
1743 TrafficSelector serverSelector =
1744 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1745 .matchIPSrc(serverIp.toIpPrefix())
1746 .build();
1747 packetService.cancelPackets(serverSelector,
1748 PacketPriority.CONTROL,
1749 appId);
1750 }
1751
1752 private void requestServerDhcpPacket(Ip4Address serverIp) {
1753 TrafficSelector serverSelector =
1754 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1755 .matchIPSrc(serverIp.toIpPrefix())
1756 .build();
1757 packetService.requestPackets(serverSelector,
1758 PacketPriority.CONTROL,
1759 appId);
1760 }
1761
1762 private void cancelClientDhcpPacket(Ip4Address serverIp) {
1763 // Packet comes from relay
1764 TrafficSelector indirectClientSelector =
1765 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1766 .matchIPDst(serverIp.toIpPrefix())
1767 .build();
1768 packetService.cancelPackets(indirectClientSelector,
1769 PacketPriority.CONTROL,
1770 appId);
1771
1772 // Packet comes from client
1773 packetService.cancelPackets(CLIENT_SERVER_SELECTOR,
1774 PacketPriority.CONTROL,
1775 appId);
1776 }
1777
1778 private void requestClientDhcpPacket(Ip4Address serverIp) {
1779 // Packet comes from relay
1780 TrafficSelector indirectClientSelector =
1781 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1782 .matchIPDst(serverIp.toIpPrefix())
1783 .build();
1784 packetService.requestPackets(indirectClientSelector,
1785 PacketPriority.CONTROL,
1786 appId);
1787
1788 // Packet comes from client
1789 packetService.requestPackets(CLIENT_SERVER_SELECTOR,
1790 PacketPriority.CONTROL,
1791 appId);
1792 }
1793
1794 /**
1795 * Process the ignore rules.
1796 *
1797 * @param deviceId the device id
1798 * @param vlanId the vlan to be ignored
1799 * @param op the operation, ADD to install; REMOVE to uninstall rules
1800 */
1801 private void processIgnoreVlanRule(DeviceId deviceId, VlanId vlanId, Objective.Operation op) {
Yi Tseng525ff402017-10-23 19:39:39 -07001802 AtomicInteger installedCount = new AtomicInteger(DHCP_SELECTORS.size());
1803 DHCP_SELECTORS.forEach(trafficSelector -> {
1804 TrafficSelector selector = DefaultTrafficSelector.builder(trafficSelector)
1805 .matchVlanId(vlanId)
1806 .build();
1807
1808 ForwardingObjective.Builder builder = DefaultForwardingObjective.builder()
1809 .withFlag(ForwardingObjective.Flag.VERSATILE)
1810 .withSelector(selector)
1811 .withPriority(IGNORE_CONTROL_PRIORITY)
Yi Tsengdbabeed2017-11-29 10:49:20 -08001812 .withTreatment(DefaultTrafficTreatment.emptyTreatment())
Yi Tseng525ff402017-10-23 19:39:39 -07001813 .fromApp(appId);
1814
1815
1816 ObjectiveContext objectiveContext = new ObjectiveContext() {
1817 @Override
1818 public void onSuccess(Objective objective) {
1819 log.info("Ignore rule {} (Vlan id {}, device {}, selector {})",
1820 op, vlanId, deviceId, selector);
1821 int countDown = installedCount.decrementAndGet();
1822 if (countDown != 0) {
1823 return;
1824 }
1825 switch (op) {
1826 case ADD:
1827 ignoredVlans.put(deviceId, vlanId);
1828 break;
1829 case REMOVE:
1830 ignoredVlans.remove(deviceId, vlanId);
1831 break;
1832 default:
1833 log.warn("Unsupported objective operation {}", op);
1834 break;
1835 }
Yi Tseng919b2df2017-09-07 16:22:51 -07001836 }
Yi Tseng525ff402017-10-23 19:39:39 -07001837
1838 @Override
1839 public void onError(Objective objective, ObjectiveError error) {
1840 log.warn("Can't {} ignore rule (vlan id {}, selector {}, device {}) due to {}",
1841 op, vlanId, selector, deviceId, error);
Yi Tseng919b2df2017-09-07 16:22:51 -07001842 }
Yi Tseng525ff402017-10-23 19:39:39 -07001843 };
1844
1845 ForwardingObjective fwd;
1846 switch (op) {
1847 case ADD:
1848 fwd = builder.add(objectiveContext);
1849 break;
1850 case REMOVE:
1851 fwd = builder.remove(objectiveContext);
1852 break;
1853 default:
1854 log.warn("Unsupported objective operation {}", op);
1855 return;
Yi Tseng4f2a0462017-08-31 11:21:00 -07001856 }
Yi Tseng525ff402017-10-23 19:39:39 -07001857
1858 Device device = deviceService.getDevice(deviceId);
1859 if (device == null || !device.is(Pipeliner.class)) {
1860 log.warn("Device {} is not available now, wait until device is available", deviceId);
1861 return;
1862 }
1863 flowObjectiveService.apply(deviceId, fwd);
1864 });
Yi Tseng483ac6f2017-08-02 15:03:31 -07001865 }
Kalhee Kimba366062017-11-07 16:32:09 +00001866
1867 @Override
1868 public void setDhcpFpmEnabled(Boolean enabled) {
1869 // v4 does not use fpm. Do nothing.
1870 }
rsahot036620655b2018-02-26 15:01:37 -05001871 private List<DhcpServerInfo> findValidServerInfo(boolean directConnFlag) {
1872 List<DhcpServerInfo> validServerInfo;
1873
1874 if (directConnFlag || indirectServerInfoList.isEmpty()) {
1875 validServerInfo = new ArrayList<DhcpServerInfo>(defaultServerInfoList);
1876 } else {
1877 validServerInfo = new ArrayList<DhcpServerInfo>(indirectServerInfoList);
1878 }
1879 return validServerInfo;
1880 }
1881
rsahot036620655b2018-02-26 15:01:37 -05001882 private boolean checkDhcpServerConnPt(boolean directConnFlag,
1883 DhcpServerInfo serverInfo) {
1884 if (serverInfo.getDhcpServerConnectPoint() == null) {
1885 log.warn("DHCP4 server connect point for {} connPt {}",
1886 directConnFlag ? "direct" : "indirect", serverInfo.getDhcpServerConnectPoint());
1887 return false;
1888 }
1889 return true;
1890 }
1891
1892 /**
1893 * Checks if serverInfo's host info (mac and vlan) is filled in; if not, fills in.
1894 *
1895 * @param serverInfo server information
1896 * @return newServerInfo if host info can be either found or filled in.
1897 */
1898 private DhcpServerInfo getHostInfoForServerInfo(DhcpServerInfo serverInfo, List<DhcpServerInfo> sererInfoList) {
1899 DhcpServerInfo newServerInfo = null;
1900 MacAddress dhcpServerConnectMac = serverInfo.getDhcpConnectMac().orElse(null);
1901 VlanId dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
1902 ConnectPoint dhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
1903
1904 if (dhcpServerConnectMac != null && dhcpConnectVlan != null) {
1905 newServerInfo = serverInfo;
Charles Chan2de55302018-03-02 18:27:59 -08001906 log.debug("DHCP server {} host info found. ConnectPt{} Mac {} vlan {}", serverInfo.getDhcpServerIp4(),
rsahot036620655b2018-02-26 15:01:37 -05001907 dhcpServerConnectPoint, dhcpServerConnectMac, dhcpConnectVlan);
1908 } else {
Harshada Chaundkar6c54f202019-05-06 20:16:13 +00001909 log.debug("DHCP server {} not resolve yet connectPt {} mac {} vlan {}", serverInfo.getDhcpServerIp4(),
rsahot036620655b2018-02-26 15:01:37 -05001910 dhcpServerConnectPoint, dhcpServerConnectMac, dhcpConnectVlan);
1911
1912 Ip4Address ipToProbe;
1913 if (serverInfo.getDhcpGatewayIp4().isPresent()) {
1914 ipToProbe = serverInfo.getDhcpGatewayIp4().get();
1915 } else {
1916 ipToProbe = serverInfo.getDhcpServerIp4().orElse(null);
1917 }
1918 String hostToProbe = serverInfo.getDhcpGatewayIp6()
1919 .map(ip -> "gateway").orElse("server");
1920
Harshada Chaundkar6c54f202019-05-06 20:16:13 +00001921 log.debug("Dynamically probing to resolve {} IP {}", hostToProbe, ipToProbe);
rsahot036620655b2018-02-26 15:01:37 -05001922 hostService.startMonitoringIp(ipToProbe);
1923
1924 Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
1925 if (!hosts.isEmpty()) {
1926 int serverInfoIndex = sererInfoList.indexOf(serverInfo);
1927 Host host = hosts.iterator().next();
1928 serverInfo.setDhcpConnectVlan(host.vlan());
1929 serverInfo.setDhcpConnectMac(host.mac());
1930 // replace the serverInfo in the list
1931 sererInfoList.set(serverInfoIndex, serverInfo);
1932 newServerInfo = serverInfo;
1933 log.warn("Dynamically host found host {}", host);
1934 } else {
Harshada Chaundkar6c54f202019-05-06 20:16:13 +00001935 log.debug("No host found host ip {} dynamically", ipToProbe);
rsahot036620655b2018-02-26 15:01:37 -05001936 }
1937 }
1938 return newServerInfo;
1939 }
1940
1941 /**
1942 * Gets Interface facing to the server for default host.
1943 *
1944 * @param serverInfo server information
1945 * @return the Interface facing to the server; null if not found
1946 */
1947 private Interface getServerInterface(DhcpServerInfo serverInfo) {
1948 Interface serverInterface = null;
1949
1950 ConnectPoint dhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
1951 VlanId dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
1952
1953 if (dhcpServerConnectPoint != null && dhcpConnectVlan != null) {
1954 serverInterface = interfaceService.getInterfacesByPort(dhcpServerConnectPoint)
1955 .stream()
Taras Lemkin41785912018-03-26 14:52:58 +00001956 .filter(iface -> Dhcp4HandlerUtil.interfaceContainsVlan(iface, dhcpConnectVlan))
rsahot036620655b2018-02-26 15:01:37 -05001957 .findFirst()
1958 .orElse(null);
1959 } else {
1960 log.warn("DHCP server {} not resolve yet connectPoint {} vlan {}", serverInfo.getDhcpServerIp6(),
1961 dhcpServerConnectPoint, dhcpConnectVlan);
1962 }
1963
1964 return serverInterface;
1965 }
1966
1967 //forward the packet to ConnectPoint where the DHCP server is attached.
1968 private void forwardPacket(InternalPacket packet) {
1969 //send Packetout to dhcp server connectpoint.
Taras Lemkin41785912018-03-26 14:52:58 +00001970 if (packet.getDestLocation() != null) {
rsahot036620655b2018-02-26 15:01:37 -05001971 TrafficTreatment t = DefaultTrafficTreatment.builder()
Taras Lemkin41785912018-03-26 14:52:58 +00001972 .setOutput(packet.getDestLocation().port()).build();
rsahot036620655b2018-02-26 15:01:37 -05001973 OutboundPacket o = new DefaultOutboundPacket(
Taras Lemkin41785912018-03-26 14:52:58 +00001974 packet.getDestLocation().deviceId(), t, ByteBuffer.wrap(packet.getPacket().serialize()));
rsahot036620655b2018-02-26 15:01:37 -05001975 if (log.isTraceEnabled()) {
Taras Lemkin41785912018-03-26 14:52:58 +00001976 log.trace("Relaying packet to destination {}", packet.getDestLocation());
rsahot036620655b2018-02-26 15:01:37 -05001977 }
Taras Lemkin41785912018-03-26 14:52:58 +00001978 log.debug("packetService.emit(o) to port {}", packet.getDestLocation());
rsahot036620655b2018-02-26 15:01:37 -05001979 packetService.emit(o);
1980 }
1981 }
1982
rsahot036620655b2018-02-26 15:01:37 -05001983 private DhcpServerInfo findServerInfoFromServer(boolean directConnFlag, ConnectPoint inPort) {
1984 List<DhcpServerInfo> validServerInfoList = findValidServerInfo(directConnFlag);
1985 DhcpServerInfo foundServerInfo = null;
1986 for (DhcpServerInfo serverInfo : validServerInfoList) {
1987 if (inPort.equals(serverInfo.getDhcpServerConnectPoint().get())) {
1988 foundServerInfo = serverInfo;
Charles Chan2de55302018-03-02 18:27:59 -08001989 log.debug("ServerInfo found for Rcv port {} Server Connect Point {} for {}",
rsahot036620655b2018-02-26 15:01:37 -05001990 inPort, serverInfo.getDhcpServerConnectPoint(), directConnFlag ? "direct" : "indirect");
1991 break;
rsahot036620655b2018-02-26 15:01:37 -05001992 }
1993 }
1994 return foundServerInfo;
1995 }
Yi Tseng51301292017-07-28 13:02:59 -07001996}