blob: 8decb7fcae9b4c8d304d7483eef2fdb671c5e66d [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 Tseng51301292017-07-28 13:02:59 -070026import org.onlab.packet.BasePacket;
27import org.onlab.packet.DHCP;
28import org.onlab.packet.Ethernet;
29import org.onlab.packet.IPv4;
30import org.onlab.packet.Ip4Address;
31import org.onlab.packet.IpAddress;
32import org.onlab.packet.MacAddress;
Yi Tseng525ff402017-10-23 19:39:39 -070033import org.onlab.packet.TpPort;
Yi Tseng51301292017-07-28 13:02:59 -070034import org.onlab.packet.UDP;
35import org.onlab.packet.VlanId;
36import org.onlab.packet.dhcp.CircuitId;
37import org.onlab.packet.dhcp.DhcpOption;
38import org.onlab.packet.dhcp.DhcpRelayAgentOption;
Taras Lemkin96a0d342018-03-26 14:52:58 +000039import org.onlab.util.Tools;
40import org.onosproject.cfg.ComponentConfigService;
Yi Tseng525ff402017-10-23 19:39:39 -070041import org.onosproject.core.ApplicationId;
42import org.onosproject.core.CoreService;
Yi Tseng51301292017-07-28 13:02:59 -070043import org.onosproject.dhcprelay.api.DhcpHandler;
Yi Tseng919b2df2017-09-07 16:22:51 -070044import org.onosproject.dhcprelay.api.DhcpServerInfo;
Yi Tseng483ac6f2017-08-02 15:03:31 -070045import org.onosproject.dhcprelay.config.DhcpServerConfig;
Yi Tseng525ff402017-10-23 19:39:39 -070046import org.onosproject.dhcprelay.config.IgnoreDhcpConfig;
Yi Tseng51301292017-07-28 13:02:59 -070047import org.onosproject.dhcprelay.store.DhcpRecord;
48import org.onosproject.dhcprelay.store.DhcpRelayStore;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070049import org.onosproject.net.ConnectPoint;
Yi Tseng525ff402017-10-23 19:39:39 -070050import org.onosproject.net.Device;
51import org.onosproject.net.DeviceId;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070052import org.onosproject.net.Host;
53import org.onosproject.net.HostId;
54import org.onosproject.net.HostLocation;
Yi Tseng525ff402017-10-23 19:39:39 -070055import org.onosproject.net.behaviour.Pipeliner;
56import org.onosproject.net.device.DeviceService;
57import org.onosproject.net.flow.DefaultTrafficSelector;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070058import org.onosproject.net.flow.DefaultTrafficTreatment;
Yi Tseng525ff402017-10-23 19:39:39 -070059import org.onosproject.net.flow.TrafficSelector;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070060import org.onosproject.net.flow.TrafficTreatment;
Yi Tseng525ff402017-10-23 19:39:39 -070061import org.onosproject.net.flowobjective.DefaultForwardingObjective;
62import org.onosproject.net.flowobjective.FlowObjectiveService;
63import org.onosproject.net.flowobjective.ForwardingObjective;
64import org.onosproject.net.flowobjective.Objective;
65import org.onosproject.net.flowobjective.ObjectiveContext;
66import org.onosproject.net.flowobjective.ObjectiveError;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070067import org.onosproject.net.host.DefaultHostDescription;
68import org.onosproject.net.host.HostDescription;
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;
Yi Tseng51301292017-07-28 13:02:59 -070074import org.onosproject.net.host.HostService;
Yi Tseng51301292017-07-28 13:02:59 -070075import org.onosproject.net.host.InterfaceIpAddress;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070076import org.onosproject.net.intf.Interface;
77import org.onosproject.net.intf.InterfaceService;
Yi Tseng51301292017-07-28 13:02:59 -070078import org.onosproject.net.packet.DefaultOutboundPacket;
79import org.onosproject.net.packet.OutboundPacket;
80import org.onosproject.net.packet.PacketContext;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070081import org.onosproject.net.packet.PacketPriority;
Yi Tseng51301292017-07-28 13:02:59 -070082import org.onosproject.net.packet.PacketService;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070083import org.onosproject.net.provider.ProviderId;
84import org.onosproject.routeservice.Route;
85import org.onosproject.routeservice.RouteStore;
Taras Lemkin96a0d342018-03-26 14:52:58 +000086import org.osgi.service.component.ComponentContext;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070087import org.osgi.service.component.annotations.Activate;
88import org.osgi.service.component.annotations.Component;
89import org.osgi.service.component.annotations.Deactivate;
90import org.osgi.service.component.annotations.Modified;
91import org.osgi.service.component.annotations.Reference;
92import org.osgi.service.component.annotations.ReferenceCardinality;
Yi Tseng51301292017-07-28 13:02:59 -070093import org.slf4j.Logger;
94import org.slf4j.LoggerFactory;
95
96import java.nio.ByteBuffer;
Taras Lemkin96a0d342018-03-26 14:52:58 +000097import java.util.ArrayList;
Yi Tsengdcef2c22017-08-05 20:34:06 -070098import java.util.Collection;
Yi Tseng51301292017-07-28 13:02:59 -070099import java.util.Collections;
Taras Lemkin96a0d342018-03-26 14:52:58 +0000100import java.util.Dictionary;
Yi Tseng51301292017-07-28 13:02:59 -0700101import java.util.List;
102import java.util.Optional;
103import java.util.Set;
Charles Chan909cff82018-03-05 13:14:02 -0800104import java.util.concurrent.CopyOnWriteArrayList;
Jordan Halterman6328db72018-04-10 13:34:50 -0400105import java.util.concurrent.Executor;
Yi Tseng525ff402017-10-23 19:39:39 -0700106import java.util.concurrent.atomic.AtomicInteger;
Yi Tseng51301292017-07-28 13:02:59 -0700107import java.util.stream.Collectors;
108
109import static com.google.common.base.Preconditions.checkNotNull;
110import static com.google.common.base.Preconditions.checkState;
Jordan Halterman6328db72018-04-10 13:34:50 -0400111import static java.util.concurrent.Executors.newSingleThreadExecutor;
Yi Tseng51301292017-07-28 13:02:59 -0700112import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_CircuitID;
113import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_END;
114import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
115import static org.onlab.packet.MacAddress.valueOf;
116import static org.onlab.packet.dhcp.DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID;
Jordan Halterman6328db72018-04-10 13:34:50 -0400117import static org.onlab.util.Tools.groupedThreads;
Ray Milkey687c00c2018-10-31 10:18:41 -0700118import static org.onosproject.dhcprelay.OsgiPropertyConstants.LEARN_ROUTE_FROM_LEASE_QUERY;
119import static org.onosproject.dhcprelay.OsgiPropertyConstants.LEARN_ROUTE_FROM_LEASE_QUERY_DEFAULT;
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
rsahot036620655b2018-02-26 15:01:37 -0500123
Ray Milkey687c00c2018-10-31 10:18:41 -0700124@Component(
125 service = { DhcpHandler.class, HostProvider.class },
126 property = {
127 "version:Integer = 4",
128 LEARN_ROUTE_FROM_LEASE_QUERY + ":Boolean=" + LEARN_ROUTE_FROM_LEASE_QUERY_DEFAULT
129 }
130)
Yi Tsengaa417a62017-09-08 17:22:51 -0700131public class Dhcp4HandlerImpl implements DhcpHandler, HostProvider {
Charles Chand988c282017-09-12 17:09:32 -0700132 public static final String DHCP_V4_RELAY_APP = "org.onosproject.Dhcp4HandlerImpl";
133 public static final ProviderId PROVIDER_ID = new ProviderId("dhcp4", DHCP_V4_RELAY_APP);
Yi Tseng525ff402017-10-23 19:39:39 -0700134 private static final String BROADCAST_IP = "255.255.255.255";
135 private static final int IGNORE_CONTROL_PRIORITY = PacketPriority.CONTROL.priorityValue() + 1000;
136
137 private static final TrafficSelector CLIENT_SERVER_SELECTOR = DefaultTrafficSelector.builder()
138 .matchEthType(Ethernet.TYPE_IPV4)
139 .matchIPProtocol(IPv4.PROTOCOL_UDP)
140 .matchIPSrc(Ip4Address.ZERO.toIpPrefix())
141 .matchIPDst(Ip4Address.valueOf(BROADCAST_IP).toIpPrefix())
142 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
143 .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
144 .build();
145 private static final TrafficSelector SERVER_RELAY_SELECTOR = DefaultTrafficSelector.builder()
146 .matchEthType(Ethernet.TYPE_IPV4)
147 .matchIPProtocol(IPv4.PROTOCOL_UDP)
148 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
149 .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
150 .build();
151 static final Set<TrafficSelector> DHCP_SELECTORS = ImmutableSet.of(
152 CLIENT_SERVER_SELECTOR,
153 SERVER_RELAY_SELECTOR
154 );
Yi Tseng51301292017-07-28 13:02:59 -0700155 private static Logger log = LoggerFactory.getLogger(Dhcp4HandlerImpl.class);
156
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700157 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Yi Tseng51301292017-07-28 13:02:59 -0700158 protected DhcpRelayStore dhcpRelayStore;
159
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700160 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Yi Tseng51301292017-07-28 13:02:59 -0700161 protected PacketService packetService;
162
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700163 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Yi Tseng51301292017-07-28 13:02:59 -0700164 protected RouteStore routeStore;
165
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700166 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Yi Tseng51301292017-07-28 13:02:59 -0700167 protected InterfaceService interfaceService;
168
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700169 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Yi Tseng51301292017-07-28 13:02:59 -0700170 protected HostService hostService;
171
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700172 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Yi Tsengaa417a62017-09-08 17:22:51 -0700173 protected HostProviderRegistry providerRegistry;
174
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700175 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Yi Tseng525ff402017-10-23 19:39:39 -0700176 protected CoreService coreService;
177
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700178 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Yi Tseng525ff402017-10-23 19:39:39 -0700179 protected DeviceService deviceService;
180
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700181 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Yi Tseng525ff402017-10-23 19:39:39 -0700182 protected FlowObjectiveService flowObjectiveService;
183
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700184 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Taras Lemkin96a0d342018-03-26 14:52:58 +0000185 protected ComponentConfigService cfgService;
186
Yi Tsengaa417a62017-09-08 17:22:51 -0700187 protected HostProviderService providerService;
Yi Tseng525ff402017-10-23 19:39:39 -0700188 protected ApplicationId appId;
Charles Chan70fdd492018-03-07 17:36:06 -0800189 protected Multimap<DeviceId, VlanId> ignoredVlans = Multimaps.synchronizedMultimap(HashMultimap.create());
Yi Tseng483ac6f2017-08-02 15:03:31 -0700190 private InternalHostListener hostListener = new InternalHostListener();
191
Charles Chan909cff82018-03-05 13:14:02 -0800192 private List<DhcpServerInfo> defaultServerInfoList = new CopyOnWriteArrayList<>();
193 private List<DhcpServerInfo> indirectServerInfoList = new CopyOnWriteArrayList<>();
Taras Lemkin96a0d342018-03-26 14:52:58 +0000194
Ray Milkey687c00c2018-10-31 10:18:41 -0700195 /** Enable learning routing information from LQ. */
196 private Boolean learnRouteFromLeasequery = LEARN_ROUTE_FROM_LEASE_QUERY_DEFAULT;
Yi Tseng4f2a0462017-08-31 11:21:00 -0700197
Jordan Halterman6328db72018-04-10 13:34:50 -0400198 private Executor hostEventExecutor = newSingleThreadExecutor(
199 groupedThreads("dhcp4-event-host", "%d", log));
200
Yi Tseng483ac6f2017-08-02 15:03:31 -0700201 @Activate
Taras Lemkin96a0d342018-03-26 14:52:58 +0000202 protected void activate(ComponentContext context) {
203 cfgService.registerProperties(getClass());
204 modified(context);
Yi Tseng525ff402017-10-23 19:39:39 -0700205 appId = coreService.registerApplication(DHCP_V4_RELAY_APP);
Yi Tseng483ac6f2017-08-02 15:03:31 -0700206 hostService.addListener(hostListener);
Yi Tsengaa417a62017-09-08 17:22:51 -0700207 providerService = providerRegistry.register(this);
Yi Tseng483ac6f2017-08-02 15:03:31 -0700208 }
209
210 @Deactivate
211 protected void deactivate() {
Taras Lemkin96a0d342018-03-26 14:52:58 +0000212 cfgService.unregisterProperties(getClass(), false);
Yi Tsengaa417a62017-09-08 17:22:51 -0700213 providerRegistry.unregister(this);
214 hostService.removeListener(hostListener);
Yi Tseng919b2df2017-09-07 16:22:51 -0700215 defaultServerInfoList.forEach(this::stopMonitoringIps);
Charles Chanfc1c22e2018-07-19 09:52:01 -0700216 defaultServerInfoList.forEach(info -> info.getDhcpServerIp4().ifPresent(this::cancelDhcpPacket));
Yi Tseng919b2df2017-09-07 16:22:51 -0700217 defaultServerInfoList.clear();
218 indirectServerInfoList.forEach(this::stopMonitoringIps);
Charles Chanfc1c22e2018-07-19 09:52:01 -0700219 indirectServerInfoList.forEach(info -> info.getDhcpServerIp4().ifPresent(this::cancelDhcpPacket));
Yi Tseng919b2df2017-09-07 16:22:51 -0700220 indirectServerInfoList.clear();
Yi Tseng483ac6f2017-08-02 15:03:31 -0700221 }
222
Taras Lemkin96a0d342018-03-26 14:52:58 +0000223 @Modified
224 protected void modified(ComponentContext context) {
225 Dictionary<?, ?> properties = context.getProperties();
226 Boolean flag;
Ray Milkey687c00c2018-10-31 10:18:41 -0700227 flag = Tools.isPropertyEnabled(properties, LEARN_ROUTE_FROM_LEASE_QUERY);
Taras Lemkin96a0d342018-03-26 14:52:58 +0000228 if (flag != null) {
229 learnRouteFromLeasequery = flag;
230 log.info("Learning routes from DHCP leasequery is {}",
231 learnRouteFromLeasequery ? "enabled" : "disabled");
232 }
233 }
234
Yi Tseng919b2df2017-09-07 16:22:51 -0700235 private void stopMonitoringIps(DhcpServerInfo serverInfo) {
236 serverInfo.getDhcpGatewayIp4().ifPresent(gatewayIp -> {
237 hostService.stopMonitoringIp(gatewayIp);
238 });
239 serverInfo.getDhcpServerIp4().ifPresent(serverIp -> {
240 hostService.stopMonitoringIp(serverIp);
241 });
Yi Tseng51301292017-07-28 13:02:59 -0700242 }
243
244 @Override
Yi Tseng483ac6f2017-08-02 15:03:31 -0700245 public void setDefaultDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
Yi Tseng919b2df2017-09-07 16:22:51 -0700246 setDhcpServerConfigs(configs, defaultServerInfoList);
247 }
248
249 @Override
250 public void setIndirectDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
251 setDhcpServerConfigs(configs, indirectServerInfoList);
252 }
253
254 @Override
255 public List<DhcpServerInfo> getDefaultDhcpServerInfoList() {
256 return defaultServerInfoList;
257 }
258
259 @Override
260 public List<DhcpServerInfo> getIndirectDhcpServerInfoList() {
261 return indirectServerInfoList;
262 }
263
Yi Tseng525ff402017-10-23 19:39:39 -0700264 @Override
265 public void updateIgnoreVlanConfig(IgnoreDhcpConfig config) {
266 if (config == null) {
267 ignoredVlans.forEach(((deviceId, vlanId) -> {
268 processIgnoreVlanRule(deviceId, vlanId, REMOVE);
269 }));
270 return;
271 }
272 config.ignoredVlans().forEach((deviceId, vlanId) -> {
273 if (ignoredVlans.get(deviceId).contains(vlanId)) {
274 // don't need to process if it already ignored
275 return;
276 }
277 processIgnoreVlanRule(deviceId, vlanId, ADD);
278 });
279
280 ignoredVlans.forEach((deviceId, vlanId) -> {
281 if (!config.ignoredVlans().get(deviceId).contains(vlanId)) {
282 // not contains in new config, remove it
283 processIgnoreVlanRule(deviceId, vlanId, REMOVE);
284 }
285 });
286 }
287
Saurav Dasb805f1a2017-12-13 16:19:35 -0800288 @Override
289 public void removeIgnoreVlanState(IgnoreDhcpConfig config) {
290 if (config == null) {
291 ignoredVlans.clear();
292 return;
293 }
294 config.ignoredVlans().forEach((deviceId, vlanId) -> {
295 ignoredVlans.remove(deviceId, vlanId);
296 });
297 }
298
Yi Tseng919b2df2017-09-07 16:22:51 -0700299 public void setDhcpServerConfigs(Collection<DhcpServerConfig> configs, List<DhcpServerInfo> serverInfoList) {
Yi Tseng483ac6f2017-08-02 15:03:31 -0700300 if (configs.size() == 0) {
301 // no config to update
302 return;
303 }
304
rsahot036620655b2018-02-26 15:01:37 -0500305 Boolean isConfigValid = false;
306 for (DhcpServerConfig serverConfig : configs) {
307 if (serverConfig.getDhcpServerIp4().isPresent()) {
308 isConfigValid = true;
309 break;
310 }
Yi Tseng483ac6f2017-08-02 15:03:31 -0700311 }
rsahot036620655b2018-02-26 15:01:37 -0500312 if (!isConfigValid) {
313 log.warn("No IP V4 server address found.");
314 return; // No IP V6 address found
315 }
316 // if (!serverInfoList.isEmpty()) {
317 for (DhcpServerInfo oldServerInfo : serverInfoList) {
318 log.info("In for (DhcpServerInfo oldServerInfo : serverInfoList) {");
Yi Tseng919b2df2017-09-07 16:22:51 -0700319 // remove old server info
rsahot036620655b2018-02-26 15:01:37 -0500320 //DhcpServerInfo oldServerInfo = serverInfoList.remove(0);
Yi Tseng483ac6f2017-08-02 15:03:31 -0700321
Yi Tseng919b2df2017-09-07 16:22:51 -0700322 // stop monitoring gateway or server
323 oldServerInfo.getDhcpGatewayIp4().ifPresent(gatewayIp -> {
324 hostService.stopMonitoringIp(gatewayIp);
325 });
326 oldServerInfo.getDhcpServerIp4().ifPresent(serverIp -> {
327 hostService.stopMonitoringIp(serverIp);
Yi Tseng525ff402017-10-23 19:39:39 -0700328 cancelDhcpPacket(serverIp);
Yi Tseng919b2df2017-09-07 16:22:51 -0700329 });
Yi Tseng483ac6f2017-08-02 15:03:31 -0700330 }
Yi Tseng3df7f9d2017-08-17 13:08:31 -0700331
Yi Tseng919b2df2017-09-07 16:22:51 -0700332 // Create new server info according to the config
rsahot036620655b2018-02-26 15:01:37 -0500333 serverInfoList.clear();
334 for (DhcpServerConfig serverConfig : configs) {
Taras Lemkin96a0d342018-03-26 14:52:58 +0000335 log.debug("Create new server info according to the config");
rsahot036620655b2018-02-26 15:01:37 -0500336 DhcpServerInfo newServerInfo = new DhcpServerInfo(serverConfig,
337 DhcpServerInfo.Version.DHCP_V4);
338 checkState(newServerInfo.getDhcpServerConnectPoint().isPresent(),
339 "Connect point not exists");
340 checkState(newServerInfo.getDhcpServerIp4().isPresent(),
341 "IP of DHCP server not exists");
Yi Tseng4f2a0462017-08-31 11:21:00 -0700342
rsahot036620655b2018-02-26 15:01:37 -0500343 log.debug("DHCP server connect point: {}", newServerInfo.getDhcpServerConnectPoint().orElse(null));
344 log.debug("DHCP server IP: {}", newServerInfo.getDhcpServerIp4().orElse(null));
Yi Tseng919b2df2017-09-07 16:22:51 -0700345
rsahot036620655b2018-02-26 15:01:37 -0500346 Ip4Address serverIp = newServerInfo.getDhcpServerIp4().get();
347 Ip4Address ipToProbe;
348 if (newServerInfo.getDhcpGatewayIp4().isPresent()) {
349 ipToProbe = newServerInfo.getDhcpGatewayIp4().get();
350 } else {
351 ipToProbe = newServerInfo.getDhcpServerIp4().orElse(null);
352 }
353 log.info("Probe_IP {}", ipToProbe);
354 String hostToProbe = newServerInfo.getDhcpGatewayIp4()
355 .map(ip -> "gateway").orElse("server");
356
357 log.debug("Probing to resolve {} IP {}", hostToProbe, ipToProbe);
358 hostService.startMonitoringIp(ipToProbe);
359
360 Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
361 if (!hosts.isEmpty()) {
362 Host host = hosts.iterator().next();
363 newServerInfo.setDhcpConnectVlan(host.vlan());
364 newServerInfo.setDhcpConnectMac(host.mac());
365 }
366
367 // Add new server info
368 synchronized (this) {
369 //serverInfoList.clear();
370 serverInfoList.add(newServerInfo);
371 }
372
373 requestDhcpPacket(serverIp);
Yi Tseng4f2a0462017-08-31 11:21:00 -0700374 }
Yi Tseng483ac6f2017-08-02 15:03:31 -0700375 }
376
Yi Tseng3df7f9d2017-08-17 13:08:31 -0700377 @Override
Yi Tseng51301292017-07-28 13:02:59 -0700378 public void processDhcpPacket(PacketContext context, BasePacket payload) {
379 checkNotNull(payload, "DHCP payload can't be null");
380 checkState(payload instanceof DHCP, "Payload is not a DHCP");
381 DHCP dhcpPayload = (DHCP) payload;
382 if (!configured()) {
Yi Tseng919b2df2017-09-07 16:22:51 -0700383 log.warn("Missing default DHCP relay server config. Abort packet processing");
Yi Tseng51301292017-07-28 13:02:59 -0700384 return;
385 }
386
387 ConnectPoint inPort = context.inPacket().receivedFrom();
Yi Tseng51301292017-07-28 13:02:59 -0700388 checkNotNull(dhcpPayload, "Can't find DHCP payload");
389 Ethernet packet = context.inPacket().parsed();
390 DHCP.MsgType incomingPacketType = dhcpPayload.getOptions().stream()
391 .filter(dhcpOption -> dhcpOption.getCode() == OptionCode_MessageType.getValue())
392 .map(DhcpOption::getData)
393 .map(data -> DHCP.MsgType.getType(data[0]))
394 .findFirst()
395 .orElse(null);
396 checkNotNull(incomingPacketType, "Can't get message type from DHCP payload {}", dhcpPayload);
rsahot036620655b2018-02-26 15:01:37 -0500397 Set<Interface> receivingInterfaces = interfaceService.getInterfacesByPort(inPort);
398 //ignore the packets if dhcp client interface is not configured on onos.
399 if (receivingInterfaces.isEmpty()) {
400 log.warn("Virtual interface is not configured on {}", inPort);
401 return;
402 }
Yi Tseng51301292017-07-28 13:02:59 -0700403 switch (incomingPacketType) {
404 case DHCPDISCOVER:
Yi Tsengdcef2c22017-08-05 20:34:06 -0700405 // Add the gateway IP as virtual interface IP for server to understand
Yi Tseng51301292017-07-28 13:02:59 -0700406 // the lease to be assigned and forward the packet to dhcp server.
rsahot036620655b2018-02-26 15:01:37 -0500407 List<InternalPacket> ethernetClientPacket =
408 processDhcpPacketFromClient(context, packet, receivingInterfaces);
409 for (InternalPacket internalPacket : ethernetClientPacket) {
410 log.debug("DHCPDISCOVER from {} Forward to server", inPort);
Yi Tseng51301292017-07-28 13:02:59 -0700411 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
rsahot036620655b2018-02-26 15:01:37 -0500412 forwardPacket(internalPacket);
Yi Tseng51301292017-07-28 13:02:59 -0700413 }
414 break;
415 case DHCPOFFER:
416 //reply to dhcp client.
Taras Lemkin96a0d342018-03-26 14:52:58 +0000417 InternalPacket ethernetPacketOffer = processDhcpPacketFromServer(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700418 if (ethernetPacketOffer != null) {
Taras Lemkin96a0d342018-03-26 14:52:58 +0000419 writeResponseDhcpRecord(ethernetPacketOffer.getPacket(), dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700420 sendResponseToClient(ethernetPacketOffer, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700421 }
422 break;
423 case DHCPREQUEST:
424 // add the gateway ip as virtual interface ip for server to understand
425 // the lease to be assigned and forward the packet to dhcp server.
rsahot036620655b2018-02-26 15:01:37 -0500426 List<InternalPacket> ethernetPacketRequest =
427 processDhcpPacketFromClient(context, packet, receivingInterfaces);
428 for (InternalPacket internalPacket : ethernetPacketRequest) {
429 log.debug("DHCPDISCOVER from {} Forward to server", inPort);
Yi Tseng51301292017-07-28 13:02:59 -0700430 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
rsahot036620655b2018-02-26 15:01:37 -0500431 forwardPacket(internalPacket);
Yi Tseng51301292017-07-28 13:02:59 -0700432 }
433 break;
Charles Chand0dd7002017-10-08 23:53:36 -0400434 case DHCPDECLINE:
435 break;
Yi Tseng51301292017-07-28 13:02:59 -0700436 case DHCPACK:
437 // reply to dhcp client.
Taras Lemkin96a0d342018-03-26 14:52:58 +0000438 InternalPacket ethernetPacketAck = processDhcpPacketFromServer(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700439 if (ethernetPacketAck != null) {
Taras Lemkin96a0d342018-03-26 14:52:58 +0000440 writeResponseDhcpRecord(ethernetPacketAck.getPacket(), dhcpPayload);
441 handleDhcpAck(ethernetPacketAck.getPacket(), dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700442 sendResponseToClient(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700443 }
444 break;
Charles Chand0dd7002017-10-08 23:53:36 -0400445 case DHCPNAK:
446 break;
Yi Tseng51301292017-07-28 13:02:59 -0700447 case DHCPRELEASE:
448 // TODO: release the ip address from client
449 break;
Charles Chand0dd7002017-10-08 23:53:36 -0400450 case DHCPINFORM:
451 break;
452 case DHCPFORCERENEW:
453 break;
454 case DHCPLEASEQUERY:
455 handleLeaseQueryMsg(context, packet, dhcpPayload);
456 break;
457 case DHCPLEASEACTIVE:
458 handleLeaseQueryActivateMsg(packet, dhcpPayload);
459 break;
460 case DHCPLEASEUNASSIGNED:
461 case DHCPLEASEUNKNOWN:
462 handleLeaseQueryUnknown(packet, dhcpPayload);
463 break;
Yi Tseng51301292017-07-28 13:02:59 -0700464 default:
465 break;
466 }
467 }
468
469 /**
470 * Checks if this app has been configured.
471 *
472 * @return true if all information we need have been initialized
473 */
Yi Tseng4f2a0462017-08-31 11:21:00 -0700474 private boolean configured() {
Yi Tseng919b2df2017-09-07 16:22:51 -0700475 return !defaultServerInfoList.isEmpty();
Yi Tseng51301292017-07-28 13:02:59 -0700476 }
477
478 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -0700479 * Returns the first interface ip from interface.
Yi Tseng51301292017-07-28 13:02:59 -0700480 *
Yi Tsengdcef2c22017-08-05 20:34:06 -0700481 * @param iface interface of one connect point
Yi Tseng51301292017-07-28 13:02:59 -0700482 * @return the first interface IP; null if not exists an IP address in
483 * these interfaces
484 */
Yi Tseng3df7f9d2017-08-17 13:08:31 -0700485 private Ip4Address getFirstIpFromInterface(Interface iface) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700486 checkNotNull(iface, "Interface can't be null");
487 return iface.ipAddressesList().stream()
Yi Tseng51301292017-07-28 13:02:59 -0700488 .map(InterfaceIpAddress::ipAddress)
489 .filter(IpAddress::isIp4)
490 .map(IpAddress::getIp4Address)
491 .findFirst()
492 .orElse(null);
493 }
494
495 /**
Yi Tseng4f2a0462017-08-31 11:21:00 -0700496 * Gets Interface facing to the server for default host.
Yi Tsengdcef2c22017-08-05 20:34:06 -0700497 *
498 * @return the Interface facing to the server; null if not found
499 */
Yi Tseng919b2df2017-09-07 16:22:51 -0700500 private Interface getDefaultServerInterface() {
501 return getServerInterface(defaultServerInfoList);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700502 }
503
504 /**
Yi Tseng4f2a0462017-08-31 11:21:00 -0700505 * Gets Interface facing to the server for indirect hosts.
506 * Use default server Interface if indirect server not configured.
507 *
508 * @return the Interface facing to the server; null if not found
509 */
510 private Interface getIndirectServerInterface() {
Yi Tseng919b2df2017-09-07 16:22:51 -0700511 return getServerInterface(indirectServerInfoList);
512 }
513
514 private Interface getServerInterface(List<DhcpServerInfo> serverInfos) {
Yi Tseng3bd57ac2017-11-29 14:39:18 -0800515 return serverInfos.stream()
Yi Tseng4f2a0462017-08-31 11:21:00 -0700516 .findFirst()
Yi Tseng3bd57ac2017-11-29 14:39:18 -0800517 .map(serverInfo -> {
518 ConnectPoint dhcpServerConnectPoint =
519 serverInfo.getDhcpServerConnectPoint().orElse(null);
520 VlanId dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
521 if (dhcpServerConnectPoint == null || dhcpConnectVlan == null) {
522 return null;
523 }
524 return interfaceService.getInterfacesByPort(dhcpServerConnectPoint)
525 .stream()
526 .filter(iface -> interfaceContainsVlan(iface, dhcpConnectVlan))
527 .findFirst()
528 .orElse(null);
529 })
Yi Tseng4f2a0462017-08-31 11:21:00 -0700530 .orElse(null);
531 }
532
533 /**
534 * Determind if an Interface contains a vlan id.
535 *
536 * @param iface the Interface
537 * @param vlanId the vlan id
538 * @return true if the Interface contains the vlan id
539 */
540 private boolean interfaceContainsVlan(Interface iface, VlanId vlanId) {
Yi Tseng4025a102017-09-30 11:35:42 +0800541 if (vlanId.equals(VlanId.NONE)) {
542 // untagged packet, check if vlan untagged or vlan native is not NONE
543 return !iface.vlanUntagged().equals(VlanId.NONE) ||
544 !iface.vlanNative().equals(VlanId.NONE);
545 }
546 // tagged packet, check if the interface contains the vlan
547 return iface.vlanTagged().contains(vlanId);
Yi Tseng4f2a0462017-08-31 11:21:00 -0700548 }
549
Charles Chand0dd7002017-10-08 23:53:36 -0400550 private void handleLeaseQueryActivateMsg(Ethernet packet, DHCP dhcpPayload) {
551 log.debug("LQ: Got DHCPLEASEACTIVE packet!");
552
Taras Lemkin96a0d342018-03-26 14:52:58 +0000553 if (learnRouteFromLeasequery) {
554 // TODO: release the ip address from client
555 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
556 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
557 HostId hostId = HostId.hostId(clientMacAddress, vlanId);
558 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
Charles Chand0dd7002017-10-08 23:53:36 -0400559
Taras Lemkin96a0d342018-03-26 14:52:58 +0000560 if (record == null) {
561 log.warn("Can't find record for host {} when processing DHCPLEASEACTIVE", hostId);
562 return;
563 }
564
565 // need to update routes
566 log.debug("Lease Query for Client results in DHCPLEASEACTIVE - route needs to be modified");
567 // get current route
568 // find the ip of that client with the DhcpRelay store
569
570 Ip4Address clientIP = record.ip4Address().orElse(null);
571 log.debug("LQ: IP of host is " + clientIP.getIp4Address());
572
573 MacAddress nextHopMac = record.nextHop().orElse(null);
574 log.debug("LQ: MAC of resulting *OLD* NH for that host is " + nextHopMac.toString());
575
576 // find the new NH by looking at the src MAC of the dhcp request
577 // from the LQ store
578 MacAddress newNextHopMac = record.nextHopTemp().orElse(null);
579 log.debug("LQ: MAC of resulting *NEW* NH for that host is " + newNextHopMac.toString());
580
581 log.debug("LQ: updating dhcp relay record with new NH");
582 record.nextHop(newNextHopMac);
583
584 // find the next hop IP from its mac
585 HostId gwHostId = HostId.hostId(newNextHopMac, vlanId);
586 Host gwHost = hostService.getHost(gwHostId);
587
588 if (gwHost == null) {
589 log.warn("Can't find gateway for new NH host " + gwHostId);
590 return;
591 }
592
593 Ip4Address nextHopIp = gwHost.ipAddresses()
594 .stream()
595 .filter(IpAddress::isIp4)
596 .map(IpAddress::getIp4Address)
597 .findFirst()
598 .orElse(null);
599
600 if (nextHopIp == null) {
601 log.warn("Can't find IP address of gateway " + gwHost);
602 return;
603 }
604
605 log.debug("LQ: *NEW* NH IP for host is " + nextHopIp.getIp4Address());
606 Route route = new Route(Route.Source.DHCP, clientIP.toIpPrefix(), nextHopIp);
607 routeStore.updateRoute(route);
Charles Chand0dd7002017-10-08 23:53:36 -0400608 }
609
Charles Chand0dd7002017-10-08 23:53:36 -0400610 // and forward to client
Taras Lemkin96a0d342018-03-26 14:52:58 +0000611 InternalPacket ethernetPacket = processLeaseQueryFromServer(packet);
Charles Chand0dd7002017-10-08 23:53:36 -0400612 if (ethernetPacket != null) {
613 sendResponseToClient(ethernetPacket, dhcpPayload);
614 }
615 }
616
Charles Chand0dd7002017-10-08 23:53:36 -0400617 private void handleLeaseQueryMsg(PacketContext context, Ethernet packet, DHCP dhcpPayload) {
Taras Lemkin96a0d342018-03-26 14:52:58 +0000618 // If this flag is enabled we expect that DHCPLEASEQUERY-ies are sent from an access concentrator
619 // where queried client is connected to. Otherwise, DHCPLEASEQUERY source may be a separate connected agent
620 if (learnRouteFromLeasequery) {
621 log.debug("LQ: Got DHCPLEASEQUERY packet!");
622 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
623 log.debug("LQ: got DHCPLEASEQUERY with MAC " + clientMacAddress.toString());
624 // add the client mac (hostid) of this request to a store (the entry will be removed with
625 // the reply sent to the originator)
626 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
627 HostId hId = HostId.hostId(clientMacAddress, vlanId);
628 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hId).orElse(null);
629 if (record != null) {
630 //new NH is to be taken from src mac of LQ packet
631 MacAddress newNextHop = packet.getSourceMAC();
632 record.nextHopTemp(newNextHop);
633 record.ip4Status(dhcpPayload.getPacketType());
634 record.updateLastSeen();
635
636 // do a basic routing of the packet (this is unicast routing
637 // not a relay operation like for other broadcast dhcp packets
638 List<InternalPacket> ethernetPacketRequest = processLeaseQueryFromAgent(context, packet);
639 // and forward to server
640 for (InternalPacket internalPacket : ethernetPacketRequest) {
641 log.debug("LeaseQueryMsg forward to server");
642 forwardPacket(internalPacket);
643 }
644 } else {
645 log.warn("LQ: Error! - DHCP relay record for that client not found - ignoring LQ!");
646 }
647 } else {
648 log.debug("LQ: Got DHCPLEASEQUERY packet!");
649
650 int giaddr = dhcpPayload.getGatewayIPAddress();
651
652 log.debug("DHCPLEASEQUERY giaddr: {} ({}). Originators connectPoint: {}", giaddr,
653 Ip4Address.valueOf(giaddr), context.inPacket().receivedFrom());
Charles Chand0dd7002017-10-08 23:53:36 -0400654
655 // do a basic routing of the packet (this is unicast routing
656 // not a relay operation like for other broadcast dhcp packets
rsahot036620655b2018-02-26 15:01:37 -0500657 List<InternalPacket> ethernetPacketRequest = processLeaseQueryFromAgent(context, packet);
Charles Chand0dd7002017-10-08 23:53:36 -0400658 // and forward to server
rsahot036620655b2018-02-26 15:01:37 -0500659 for (InternalPacket internalPacket : ethernetPacketRequest) {
Taras Lemkin96a0d342018-03-26 14:52:58 +0000660 log.trace("LeaseQueryMsg forward to server connected to {}", internalPacket.getDestLocation());
rsahot036620655b2018-02-26 15:01:37 -0500661 forwardPacket(internalPacket);
662 }
Charles Chand0dd7002017-10-08 23:53:36 -0400663 }
664 }
665
666 private void handleLeaseQueryUnknown(Ethernet packet, DHCP dhcpPayload) {
667 log.debug("Lease Query for Client results in DHCPLEASEUNASSIGNED or " +
668 "DHCPLEASEUNKNOWN - removing route & forwarding reply to originator");
Taras Lemkin96a0d342018-03-26 14:52:58 +0000669 if (learnRouteFromLeasequery) {
670 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
671 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
672 HostId hostId = HostId.hostId(clientMacAddress, vlanId);
673 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
Charles Chand0dd7002017-10-08 23:53:36 -0400674
Taras Lemkin96a0d342018-03-26 14:52:58 +0000675 if (record == null) {
676 log.warn("Can't find record for host {} when handling LQ UNKNOWN/UNASSIGNED message", hostId);
677 return;
678 }
679
680 Ip4Address clientIP = record.ip4Address().orElse(null);
681 log.debug("LQ: IP of host is " + clientIP.getIp4Address());
682
683 // find the new NH by looking at the src MAC of the dhcp request
684 // from the LQ store
685 MacAddress nextHopMac = record.nextHop().orElse(null);
686 log.debug("LQ: MAC of resulting *Existing* NH for that route is " + nextHopMac.toString());
687
688 // find the next hop IP from its mac
689 HostId gwHostId = HostId.hostId(nextHopMac, vlanId);
690 Host gwHost = hostService.getHost(gwHostId);
691
692 if (gwHost == null) {
693 log.warn("Can't find gateway for new NH host " + gwHostId);
694 return;
695 }
696
697 Ip4Address nextHopIp = gwHost.ipAddresses()
698 .stream()
699 .filter(IpAddress::isIp4)
700 .map(IpAddress::getIp4Address)
701 .findFirst()
702 .orElse(null);
703
704 if (nextHopIp == null) {
705 log.warn("Can't find IP address of gateway {}", gwHost);
706 return;
707 }
708
709 log.debug("LQ: *Existing* NH IP for host is " + nextHopIp.getIp4Address() + " removing route for it");
710 Route route = new Route(Route.Source.DHCP, clientIP.toIpPrefix(), nextHopIp);
711 routeStore.removeRoute(route);
712
713 // remove from temp store
714 dhcpRelayStore.removeDhcpRecord(hostId);
Charles Chand0dd7002017-10-08 23:53:36 -0400715 }
Charles Chand0dd7002017-10-08 23:53:36 -0400716 // and forward to client
Taras Lemkin96a0d342018-03-26 14:52:58 +0000717 InternalPacket ethernetPacket = processLeaseQueryFromServer(packet);
Charles Chand0dd7002017-10-08 23:53:36 -0400718 if (ethernetPacket != null) {
719 sendResponseToClient(ethernetPacket, dhcpPayload);
720 }
721 }
722
Yi Tseng4f2a0462017-08-31 11:21:00 -0700723 /**
Yi Tseng51301292017-07-28 13:02:59 -0700724 * Build the DHCP discover/request packet with gateway IP(unicast packet).
725 *
726 * @param context the packet context
727 * @param ethernetPacket the ethernet payload to process
Yi Tseng51301292017-07-28 13:02:59 -0700728 * @return processed packet
729 */
rsahot036620655b2018-02-26 15:01:37 -0500730 private List<InternalPacket> processDhcpPacketFromClient(PacketContext context,
731 Ethernet ethernetPacket,
732 Set<Interface> clientInterfaces) {
Yi Tseng25bfe372017-11-03 16:27:32 -0700733 ConnectPoint receivedFrom = context.inPacket().receivedFrom();
734 DeviceId receivedFromDevice = receivedFrom.deviceId();
rsahot036620655b2018-02-26 15:01:37 -0500735 Ip4Address relayAgentIp = null;
Taras Lemkin96a0d342018-03-26 14:52:58 +0000736 relayAgentIp = Dhcp4HandlerUtil.getRelayAgentIPv4Address(clientInterfaces);
rsahot036620655b2018-02-26 15:01:37 -0500737 MacAddress relayAgentMac = clientInterfaces.iterator().next().mac();
738 if (relayAgentIp == null || relayAgentMac == null) {
739 log.warn("Missing DHCP relay agent interface Ipv4 addr config for "
740 + "packet from client on port: {}. Aborting packet processing",
741 clientInterfaces.iterator().next().connectPoint());
Charles Chane5e0c9a2018-03-30 12:11:34 -0700742 return Lists.newArrayList();
rsahot036620655b2018-02-26 15:01:37 -0500743 }
744 log.debug("Multi DHCP V4 processDhcpPacketFromClient on port {}",
745 clientInterfaces.iterator().next().connectPoint());
Yi Tseng25bfe372017-11-03 16:27:32 -0700746
Yi Tseng4f2a0462017-08-31 11:21:00 -0700747 // get dhcp header.
rsahot036620655b2018-02-26 15:01:37 -0500748 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
Yi Tseng4f2a0462017-08-31 11:21:00 -0700749 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
750 UDP udpPacket = (UDP) ipv4Packet.getPayload();
751 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
752
Yi Tseng919b2df2017-09-07 16:22:51 -0700753
Yi Tsengdcef2c22017-08-05 20:34:06 -0700754 Ip4Address clientInterfaceIp =
755 interfaceService.getInterfacesByPort(context.inPacket().receivedFrom())
756 .stream()
757 .map(Interface::ipAddressesList)
758 .flatMap(Collection::stream)
759 .map(InterfaceIpAddress::ipAddress)
760 .filter(IpAddress::isIp4)
761 .map(IpAddress::getIp4Address)
762 .findFirst()
763 .orElse(null);
764 if (clientInterfaceIp == null) {
765 log.warn("Can't find interface IP for client interface for port {}",
rsahot036620655b2018-02-26 15:01:37 -0500766 context.inPacket().receivedFrom());
Charles Chane5e0c9a2018-03-30 12:11:34 -0700767 return Lists.newArrayList();
Yi Tsengdcef2c22017-08-05 20:34:06 -0700768 }
rsahot036620655b2018-02-26 15:01:37 -0500769
Yi Tseng4f2a0462017-08-31 11:21:00 -0700770 boolean isDirectlyConnected = directlyConnected(dhcpPacket);
rsahot036620655b2018-02-26 15:01:37 -0500771 boolean directConnFlag = directlyConnected(dhcpPacket);
772
773 // Multi DHCP Start
774 ConnectPoint clientConnectionPoint = context.inPacket().receivedFrom();
775 VlanId vlanIdInUse = VlanId.vlanId(ethernetPacket.getVlanID());
776 Interface clientInterface = interfaceService.getInterfacesByPort(clientConnectionPoint)
Taras Lemkin96a0d342018-03-26 14:52:58 +0000777 .stream().filter(iface -> Dhcp4HandlerUtil.interfaceContainsVlan(iface, vlanIdInUse))
rsahot036620655b2018-02-26 15:01:37 -0500778 .findFirst()
779 .orElse(null);
780
781 List<InternalPacket> internalPackets = new ArrayList<>();
782 List<DhcpServerInfo> serverInfoList = findValidServerInfo(directConnFlag);
783 List<DhcpServerInfo> copyServerInfoList = new ArrayList<DhcpServerInfo>(serverInfoList);
784
785
786 for (DhcpServerInfo serverInfo : copyServerInfoList) {
787 etherReply = (Ethernet) ethernetPacket.clone();
Taras Lemkin96a0d342018-03-26 14:52:58 +0000788 ipv4Packet = (IPv4) etherReply.getPayload();
789 udpPacket = (UDP) ipv4Packet.getPayload();
790 dhcpPacket = (DHCP) udpPacket.getPayload();
rsahot036620655b2018-02-26 15:01:37 -0500791 if (!checkDhcpServerConnPt(directConnFlag, serverInfo)) {
792 log.warn("Can't get server connect point, ignore");
793 continue;
794 }
795 DhcpServerInfo newServerInfo = getHostInfoForServerInfo(serverInfo, serverInfoList);
796 if (newServerInfo == null) {
797 log.warn("Can't get server interface with host info resolved, ignore");
798 continue;
799 }
800
801 Interface serverInterface = getServerInterface(newServerInfo);
Yi Tseng3bd57ac2017-11-29 14:39:18 -0800802 if (serverInterface == null) {
rsahot036620655b2018-02-26 15:01:37 -0500803 log.warn("Can't get server interface, ignore");
804 continue;
Yi Tseng3bd57ac2017-11-29 14:39:18 -0800805 }
Yi Tseng4f2a0462017-08-31 11:21:00 -0700806
rsahot036620655b2018-02-26 15:01:37 -0500807 Ip4Address ipFacingServer = getFirstIpFromInterface(serverInterface);
808 MacAddress macFacingServer = serverInterface.mac();
809 log.debug("Interfacing server {} Mac : {} ", ipFacingServer, macFacingServer);
810 if (ipFacingServer == null || macFacingServer == null) {
811 log.warn("No IP address for server Interface {}", serverInterface);
rsahot036620655b2018-02-26 15:01:37 -0500812 continue;
813 }
Yi Tseng51301292017-07-28 13:02:59 -0700814
Charles Chand0d1e332017-10-10 16:53:32 -0400815
rsahot036620655b2018-02-26 15:01:37 -0500816 etherReply.setSourceMACAddress(macFacingServer);
817 // set default info and replace with indirect if available later on.
818 if (newServerInfo.getDhcpConnectMac().isPresent()) {
819 etherReply.setDestinationMACAddress(newServerInfo.getDhcpConnectMac().get());
820 }
821 if (newServerInfo.getDhcpConnectVlan().isPresent()) {
822 etherReply.setVlanID(newServerInfo.getDhcpConnectVlan().get().toShort());
823 }
824 ipv4Packet.setSourceAddress(ipFacingServer.toInt());
825 ipv4Packet.setDestinationAddress(newServerInfo.getDhcpServerIp4().get().toInt());
Charles Chan2de55302018-03-02 18:27:59 -0800826 log.debug("Directly connected {}", isDirectlyConnected);
827 log.debug("DHCP server IP: {}", newServerInfo.getDhcpServerIp4().get());
rsahot036620655b2018-02-26 15:01:37 -0500828 if (isDirectlyConnected) {
Yi Tseng51301292017-07-28 13:02:59 -0700829
Charles Chan2de55302018-03-02 18:27:59 -0800830 log.debug("Default DHCP server IP: {}", newServerInfo.getDhcpServerIp4().get());
rsahot036620655b2018-02-26 15:01:37 -0500831 if (newServerInfo.getDhcpConnectMac().isPresent()) {
832 etherReply.setDestinationMACAddress(newServerInfo.getDhcpConnectMac().get());
Charles Chand0d1e332017-10-10 16:53:32 -0400833 }
rsahot036620655b2018-02-26 15:01:37 -0500834 if (newServerInfo.getDhcpConnectVlan().isPresent()) {
835 etherReply.setVlanID(newServerInfo.getDhcpConnectVlan().get().toShort());
836 }
837
838 ipv4Packet.setDestinationAddress(newServerInfo.getDhcpServerIp4().get().toInt());
839
840
841 ConnectPoint inPort = context.inPacket().receivedFrom();
842 VlanId vlanId = VlanId.vlanId(ethernetPacket.getVlanID());
843 // add connected in port and vlan
844 CircuitId cid = new CircuitId(inPort.toString(), vlanId);
845 byte[] circuitId = cid.serialize();
846 DhcpOption circuitIdSubOpt = new DhcpOption();
847 circuitIdSubOpt
848 .setCode(CIRCUIT_ID.getValue())
849 .setLength((byte) circuitId.length)
850 .setData(circuitId);
851
852 DhcpRelayAgentOption newRelayAgentOpt = new DhcpRelayAgentOption();
853 newRelayAgentOpt.setCode(OptionCode_CircuitID.getValue());
854 newRelayAgentOpt.addSubOption(circuitIdSubOpt);
855
856 // Removes END option first
857 List<DhcpOption> options = dhcpPacket.getOptions().stream()
858 .filter(opt -> opt.getCode() != OptionCode_END.getValue())
859 .collect(Collectors.toList());
860
861 // push relay agent option
862 options.add(newRelayAgentOpt);
863
864 // make sure option 255(End) is the last option
865 DhcpOption endOption = new DhcpOption();
866 endOption.setCode(OptionCode_END.getValue());
867 options.add(endOption);
868
869 dhcpPacket.setOptions(options);
870
871 relayAgentIp = serverInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
872
873 // Sets relay agent IP
874 int effectiveRelayAgentIp = relayAgentIp != null ?
875 relayAgentIp.toInt() : clientInterfaceIp.toInt();
876 dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
Charles Chan2de55302018-03-02 18:27:59 -0800877 log.debug("In Default, Relay Agent IP {}", effectiveRelayAgentIp);
Charles Chand0d1e332017-10-10 16:53:32 -0400878 } else {
rsahot036620655b2018-02-26 15:01:37 -0500879 if (!newServerInfo.getDhcpServerIp4().isPresent()) {
880 // do nothing
881 } else if (!newServerInfo.getDhcpConnectMac().isPresent()) {
882 continue;
883 } else {
884 relayAgentIp = newServerInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
885 // Sets relay agent IP
886 int effectiveRelayAgentIp = relayAgentIp != null ?
887 relayAgentIp.toInt() : clientInterfaceIp.toInt();
Mayank Tiwari30149832018-10-19 12:12:44 -0400888 Ip4Address effectiveRealRealyAgentIP = relayAgentIp != null ?
889 relayAgentIp : clientInterfaceIp;
rsahot036620655b2018-02-26 15:01:37 -0500890 dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
Mayank Tiwari30149832018-10-19 12:12:44 -0400891 ipv4Packet.setSourceAddress(effectiveRealRealyAgentIP.toInt());
892 log.debug("Source IP address set as relay agent IP with value: {}", effectiveRealRealyAgentIP);
Charles Chand0d1e332017-10-10 16:53:32 -0400893 }
Yi Tseng4f2a0462017-08-31 11:21:00 -0700894 }
Yi Tseng3df7f9d2017-08-17 13:08:31 -0700895
rsahot036620655b2018-02-26 15:01:37 -0500896 // Remove broadcast flag
897 dhcpPacket.setFlags((short) 0);
898
899 udpPacket.setPayload(dhcpPacket);
900 // As a DHCP relay, the source port should be server port( instead
901 // of client port.
902 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
903 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
904 ipv4Packet.setPayload(udpPacket);
905 ipv4Packet.setTtl((byte) 64);
906 etherReply.setPayload(ipv4Packet);
Taras Lemkin96a0d342018-03-26 14:52:58 +0000907 InternalPacket internalPacket = InternalPacket.internalPacket(etherReply,
rsahot036620655b2018-02-26 15:01:37 -0500908 serverInfo.getDhcpServerConnectPoint().get());
Taras Lemkin96a0d342018-03-26 14:52:58 +0000909
rsahot036620655b2018-02-26 15:01:37 -0500910 internalPackets.add(internalPacket);
911 }
912 return internalPackets;
Yi Tseng51301292017-07-28 13:02:59 -0700913 }
914
Charles Chand0dd7002017-10-08 23:53:36 -0400915
916 /**
917 * Do a basic routing for a packet from client (used for LQ processing).
918 *
919 * @param context the packet context
920 * @param ethernetPacket the ethernet payload to process
921 * @return processed packet
922 */
rsahot036620655b2018-02-26 15:01:37 -0500923 private List<InternalPacket> processLeaseQueryFromAgent(PacketContext context,
924 Ethernet ethernetPacket) {
925 ConnectPoint receivedFrom = context.inPacket().receivedFrom();
926 DeviceId receivedFromDevice = receivedFrom.deviceId();
927
Charles Chand0dd7002017-10-08 23:53:36 -0400928 // get dhcp header.
929 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
930 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
931 UDP udpPacket = (UDP) ipv4Packet.getPayload();
932 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
933
Charles Chan2de55302018-03-02 18:27:59 -0800934 Ip4Address relayAgentIp;
Charles Chand0dd7002017-10-08 23:53:36 -0400935
Charles Chand0dd7002017-10-08 23:53:36 -0400936 Ip4Address clientInterfaceIp =
937 interfaceService.getInterfacesByPort(context.inPacket().receivedFrom())
938 .stream()
939 .map(Interface::ipAddressesList)
940 .flatMap(Collection::stream)
941 .map(InterfaceIpAddress::ipAddress)
942 .filter(IpAddress::isIp4)
943 .map(IpAddress::getIp4Address)
944 .findFirst()
945 .orElse(null);
946 if (clientInterfaceIp == null) {
947 log.warn("Can't find interface IP for client interface for port {}",
rsahot036620655b2018-02-26 15:01:37 -0500948 context.inPacket().receivedFrom());
Charles Chand0dd7002017-10-08 23:53:36 -0400949 return null;
950 }
rsahot036620655b2018-02-26 15:01:37 -0500951
Charles Chand0dd7002017-10-08 23:53:36 -0400952 boolean isDirectlyConnected = directlyConnected(dhcpPacket);
rsahot036620655b2018-02-26 15:01:37 -0500953 boolean directConnFlag = directlyConnected(dhcpPacket);
954
955 // Multi DHCP Start
rsahot036620655b2018-02-26 15:01:37 -0500956 List<InternalPacket> internalPackets = new ArrayList<>();
957 List<DhcpServerInfo> serverInfoList = findValidServerInfo(directConnFlag);
Charles Chan2de55302018-03-02 18:27:59 -0800958 List<DhcpServerInfo> copyServerInfoList = new ArrayList<>(serverInfoList);
rsahot036620655b2018-02-26 15:01:37 -0500959
960 for (DhcpServerInfo serverInfo : copyServerInfoList) {
961 // get dhcp header.
962 etherReply = (Ethernet) ethernetPacket.clone();
963 ipv4Packet = (IPv4) etherReply.getPayload();
964 udpPacket = (UDP) ipv4Packet.getPayload();
965 dhcpPacket = (DHCP) udpPacket.getPayload();
966
967 if (!checkDhcpServerConnPt(directConnFlag, serverInfo)) {
968 log.warn("Can't get server connect point, ignore");
969 continue;
Yi Tseng3bd57ac2017-11-29 14:39:18 -0800970 }
rsahot036620655b2018-02-26 15:01:37 -0500971 DhcpServerInfo newServerInfo = getHostInfoForServerInfo(serverInfo, serverInfoList);
972 if (newServerInfo == null) {
973 log.warn("Can't get server interface with host info resolved, ignore");
974 continue;
975 }
Charles Chand0dd7002017-10-08 23:53:36 -0400976
rsahot036620655b2018-02-26 15:01:37 -0500977 Interface serverInterface = getServerInterface(newServerInfo);
978 if (serverInterface == null) {
979 log.warn("Can't get server interface, ignore");
980 continue;
981 }
982 Ip4Address ipFacingServer = getFirstIpFromInterface(serverInterface);
983 MacAddress macFacingServer = serverInterface.mac();
984 if (ipFacingServer == null || macFacingServer == null) {
985 log.warn("No IP address for server Interface {}", serverInterface);
986 continue;
987 }
Charles Chand0dd7002017-10-08 23:53:36 -0400988
rsahot036620655b2018-02-26 15:01:37 -0500989 etherReply.setSourceMACAddress(macFacingServer);
Charles Chan2de55302018-03-02 18:27:59 -0800990 etherReply.setDestinationMACAddress(newServerInfo.getDhcpConnectMac().get());
991 etherReply.setVlanID(newServerInfo.getDhcpConnectVlan().get().toShort());
rsahot036620655b2018-02-26 15:01:37 -0500992 ipv4Packet.setSourceAddress(ipFacingServer.toInt());
Charles Chan2de55302018-03-02 18:27:59 -0800993 ipv4Packet.setDestinationAddress(newServerInfo.getDhcpServerIp4().get().toInt());
rsahot036620655b2018-02-26 15:01:37 -0500994 if (isDirectlyConnected) {
995 // set default info and replace with indirect if available later on.
996 if (newServerInfo.getDhcpConnectMac().isPresent()) {
997 etherReply.setDestinationMACAddress(newServerInfo.getDhcpConnectMac().get());
998 }
999 if (newServerInfo.getDhcpConnectVlan().isPresent()) {
1000 etherReply.setVlanID(serverInfo.getDhcpConnectVlan().get().toShort());
1001 }
Taras Lemkin96a0d342018-03-26 14:52:58 +00001002 if (learnRouteFromLeasequery) {
rsahot036620655b2018-02-26 15:01:37 -05001003 relayAgentIp = newServerInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
1004 // Sets relay agent IP
1005 int effectiveRelayAgentIp = relayAgentIp != null ?
1006 relayAgentIp.toInt() : clientInterfaceIp.toInt();
1007 dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
Taras Lemkin96a0d342018-03-26 14:52:58 +00001008 }
1009 } else {
1010 if (!newServerInfo.getDhcpServerIp4().isPresent()) {
1011 //do nothing
1012 } else if (!newServerInfo.getDhcpConnectMac().isPresent()) {
1013 continue;
1014 } else if (learnRouteFromLeasequery) {
1015 relayAgentIp = newServerInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
1016 // Sets relay agent IP
1017 int effectiveRelayAgentIp = relayAgentIp != null ?
1018 relayAgentIp.toInt() : clientInterfaceIp.toInt();
rsahot036620655b2018-02-26 15:01:37 -05001019 dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
Taras Lemkin96a0d342018-03-26 14:52:58 +00001020 log.debug("Relay Agent IP {}", relayAgentIp);
rsahot036620655b2018-02-26 15:01:37 -05001021 }
1022
Taras Lemkin96a0d342018-03-26 14:52:58 +00001023 log.trace("Indirect");
rsahot036620655b2018-02-26 15:01:37 -05001024 }
1025
1026 // Remove broadcast flag
1027 dhcpPacket.setFlags((short) 0);
1028
1029 udpPacket.setPayload(dhcpPacket);
1030 // As a DHCP relay, the source port should be server port( instead
1031 // of client port.
1032 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
1033 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
1034 ipv4Packet.setPayload(udpPacket);
1035 ipv4Packet.setTtl((byte) 64);
1036 etherReply.setPayload(ipv4Packet);
Taras Lemkin96a0d342018-03-26 14:52:58 +00001037 udpPacket.resetChecksum();
rsahot036620655b2018-02-26 15:01:37 -05001038 ////return etherReply;
Taras Lemkin96a0d342018-03-26 14:52:58 +00001039 InternalPacket internalPacket = InternalPacket.internalPacket(etherReply,
rsahot036620655b2018-02-26 15:01:37 -05001040 newServerInfo.getDhcpServerConnectPoint().get());
Taras Lemkin96a0d342018-03-26 14:52:58 +00001041
rsahot036620655b2018-02-26 15:01:37 -05001042 internalPackets.add(internalPacket);
Charles Chand0dd7002017-10-08 23:53:36 -04001043 }
Taras Lemkin96a0d342018-03-26 14:52:58 +00001044 log.debug("num of processLeaseQueryFromAgent packets to send is: {}", internalPackets.size());
Charles Chand0dd7002017-10-08 23:53:36 -04001045
rsahot036620655b2018-02-26 15:01:37 -05001046 return internalPackets;
Charles Chand0dd7002017-10-08 23:53:36 -04001047 }
1048
1049
Yi Tseng51301292017-07-28 13:02:59 -07001050 /**
1051 * Writes DHCP record to the store according to the request DHCP packet (Discover, Request).
1052 *
1053 * @param location the location which DHCP packet comes from
1054 * @param ethernet the DHCP packet
1055 * @param dhcpPayload the DHCP payload
1056 */
1057 private void writeRequestDhcpRecord(ConnectPoint location,
1058 Ethernet ethernet,
1059 DHCP dhcpPayload) {
1060 VlanId vlanId = VlanId.vlanId(ethernet.getVlanID());
1061 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1062 HostId hostId = HostId.hostId(macAddress, vlanId);
1063 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1064 if (record == null) {
1065 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
1066 } else {
1067 record = record.clone();
1068 }
1069 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
1070 record.ip4Status(dhcpPayload.getPacketType());
1071 record.setDirectlyConnected(directlyConnected(dhcpPayload));
1072 if (!directlyConnected(dhcpPayload)) {
1073 // Update gateway mac address if the host is not directly connected
1074 record.nextHop(ethernet.getSourceMAC());
1075 }
1076 record.updateLastSeen();
1077 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
1078 }
1079
1080 /**
1081 * Writes DHCP record to the store according to the response DHCP packet (Offer, Ack).
1082 *
1083 * @param ethernet the DHCP packet
1084 * @param dhcpPayload the DHCP payload
1085 */
1086 private void writeResponseDhcpRecord(Ethernet ethernet,
1087 DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001088 Optional<Interface> outInterface = getClientInterface(ethernet, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -07001089 if (!outInterface.isPresent()) {
1090 log.warn("Failed to determine where to send {}", dhcpPayload.getPacketType());
1091 return;
1092 }
1093
1094 Interface outIface = outInterface.get();
1095 ConnectPoint location = outIface.connectPoint();
Yi Tseng4f2a0462017-08-31 11:21:00 -07001096 VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001097 if (vlanId == null) {
1098 vlanId = outIface.vlan();
1099 }
Yi Tseng51301292017-07-28 13:02:59 -07001100 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1101 HostId hostId = HostId.hostId(macAddress, vlanId);
1102 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1103 if (record == null) {
1104 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
1105 } else {
1106 record = record.clone();
1107 }
1108 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
1109 if (dhcpPayload.getPacketType() == DHCP.MsgType.DHCPACK) {
1110 record.ip4Address(Ip4Address.valueOf(dhcpPayload.getYourIPAddress()));
1111 }
1112 record.ip4Status(dhcpPayload.getPacketType());
1113 record.setDirectlyConnected(directlyConnected(dhcpPayload));
1114 record.updateLastSeen();
1115 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
1116 }
1117
1118 /**
1119 * Build the DHCP offer/ack with proper client port.
1120 *
1121 * @param ethernetPacket the original packet comes from server
1122 * @return new packet which will send to the client
1123 */
Taras Lemkin96a0d342018-03-26 14:52:58 +00001124 private InternalPacket processDhcpPacketFromServer(PacketContext context, Ethernet ethernetPacket) {
Yi Tseng51301292017-07-28 13:02:59 -07001125 // get dhcp header.
rsahot036620655b2018-02-26 15:01:37 -05001126 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
Yi Tseng51301292017-07-28 13:02:59 -07001127 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
1128 UDP udpPacket = (UDP) ipv4Packet.getPayload();
1129 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
1130
1131 // determine the vlanId of the client host - note that this vlan id
1132 // could be different from the vlan in the packet from the server
Yi Tsengdcef2c22017-08-05 20:34:06 -07001133 Interface clientInterface = getClientInterface(ethernetPacket, dhcpPayload).orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -07001134
Yi Tsengdcef2c22017-08-05 20:34:06 -07001135 if (clientInterface == null) {
Yi Tseng51301292017-07-28 13:02:59 -07001136 log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
1137 return null;
1138 }
Yi Tsengdcef2c22017-08-05 20:34:06 -07001139 VlanId vlanId;
rsahot036620655b2018-02-26 15:01:37 -05001140 ConnectPoint inPort = context.inPacket().receivedFrom();
1141 boolean directConnFlag = directlyConnected(dhcpPayload);
1142 DhcpServerInfo foundServerInfo = findServerInfoFromServer(directConnFlag, inPort);
1143
1144 if (foundServerInfo == null) {
jayakumarthazhathed5f05d2018-09-25 15:56:51 -04001145 log.warn("Cannot find server info for {} server, inPort {}",
1146 directConnFlag ? "direct" : "indirect", inPort);
rsahot036620655b2018-02-26 15:01:37 -05001147 return null;
1148 } else {
Taras Lemkin96a0d342018-03-26 14:52:58 +00001149 if (Dhcp4HandlerUtil.isServerIpEmpty(foundServerInfo)) {
rsahot036620655b2018-02-26 15:01:37 -05001150 log.warn("Cannot find server info's ipaddress");
1151 return null;
1152 }
1153 }
Yi Tsengdcef2c22017-08-05 20:34:06 -07001154 if (clientInterface.vlanTagged().isEmpty()) {
1155 vlanId = clientInterface.vlan();
1156 } else {
1157 // might be multiple vlan in same interface
Yi Tseng4f2a0462017-08-31 11:21:00 -07001158 vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001159 }
1160 if (vlanId == null) {
1161 vlanId = VlanId.NONE;
1162 }
1163 etherReply.setVlanID(vlanId.toShort());
1164 etherReply.setSourceMACAddress(clientInterface.mac());
Yi Tseng51301292017-07-28 13:02:59 -07001165
Yi Tsengdcef2c22017-08-05 20:34:06 -07001166 if (!directlyConnected(dhcpPayload)) {
1167 // if client is indirectly connected, try use next hop mac address
1168 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1169 HostId hostId = HostId.hostId(macAddress, vlanId);
Daniel Ginsburgb1dec912018-01-29 11:40:22 -08001170 if (((int) dhcpPayload.getFlags() & 0x8000) == 0x0000) {
1171 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1172 if (record != null) {
1173 // if next hop can be found, use mac address of next hop
1174 record.nextHop().ifPresent(etherReply::setDestinationMACAddress);
1175 } else {
1176 // otherwise, discard the packet
1177 log.warn("Can't find record for host id {}, discard packet", hostId);
1178 return null;
1179 }
Yi Tsengdcef2c22017-08-05 20:34:06 -07001180 } else {
Daniel Ginsburgb1dec912018-01-29 11:40:22 -08001181 etherReply.setDestinationMACAddress(MacAddress.BROADCAST);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001182 }
Yi Tseng1696f562017-08-17 17:43:38 -07001183 } else {
1184 etherReply.setDestinationMACAddress(dhcpPayload.getClientHardwareAddress());
Yi Tsengdcef2c22017-08-05 20:34:06 -07001185 }
1186
Yi Tseng51301292017-07-28 13:02:59 -07001187 // we leave the srcMac from the original packet
Yi Tseng51301292017-07-28 13:02:59 -07001188 // figure out the relay agent IP corresponding to the original request
Yi Tseng3df7f9d2017-08-17 13:08:31 -07001189 Ip4Address ipFacingClient = getFirstIpFromInterface(clientInterface);
1190 if (ipFacingClient == null) {
Yi Tseng51301292017-07-28 13:02:59 -07001191 log.warn("Cannot determine relay agent interface Ipv4 addr for host {}/{}. "
1192 + "Aborting relay for dhcp packet from server {}",
Yi Tsengdcef2c22017-08-05 20:34:06 -07001193 etherReply.getDestinationMAC(), clientInterface.vlan(),
Yi Tseng51301292017-07-28 13:02:59 -07001194 ethernetPacket);
1195 return null;
1196 }
1197 // SRC_IP: relay agent IP
1198 // DST_IP: offered IP
Yi Tseng3df7f9d2017-08-17 13:08:31 -07001199 ipv4Packet.setSourceAddress(ipFacingClient.toInt());
Daniel Ginsburgb1dec912018-01-29 11:40:22 -08001200 if (((int) dhcpPayload.getFlags() & 0x8000) == 0x0000) {
1201 ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
1202 } else {
1203 ipv4Packet.setDestinationAddress(BROADCAST_IP);
1204 }
Yi Tseng51301292017-07-28 13:02:59 -07001205 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
1206 if (directlyConnected(dhcpPayload)) {
1207 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
1208 } else {
1209 // forward to another dhcp relay
Yi Tseng93ba53c2017-09-14 13:24:21 -07001210 // FIXME: Currently we assume the DHCP comes from a L2 relay with
1211 // Option 82, this might not work if DHCP message comes from
1212 // L3 relay.
1213 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
Yi Tseng51301292017-07-28 13:02:59 -07001214 }
1215
1216 udpPacket.setPayload(dhcpPayload);
1217 ipv4Packet.setPayload(udpPacket);
1218 etherReply.setPayload(ipv4Packet);
Taras Lemkin96a0d342018-03-26 14:52:58 +00001219 return InternalPacket.internalPacket(etherReply, clientInterface.connectPoint());
Yi Tseng51301292017-07-28 13:02:59 -07001220 }
1221
Yi Tsengdcef2c22017-08-05 20:34:06 -07001222 /**
Charles Chand0dd7002017-10-08 23:53:36 -04001223 * Build the DHCP offer/ack with proper client port.
1224 *
1225 * @param ethernetPacket the original packet comes from server
1226 * @return new packet which will send to the client
1227 */
Taras Lemkin96a0d342018-03-26 14:52:58 +00001228 private InternalPacket processLeaseQueryFromServer(Ethernet ethernetPacket) {
Charles Chand0dd7002017-10-08 23:53:36 -04001229 // get dhcp header.
1230 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
1231 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
1232 UDP udpPacket = (UDP) ipv4Packet.getPayload();
1233 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
1234
1235 // determine the vlanId of the client host - note that this vlan id
1236 // could be different from the vlan in the packet from the server
Taras Lemkin96a0d342018-03-26 14:52:58 +00001237 Interface clientInterface = null;
1238 MacAddress destinationMac = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1239
1240 if (!learnRouteFromLeasequery) {
1241 int giaddr = ipv4Packet.getDestinationAddress();
1242 IpAddress destinationAddress = Ip4Address.valueOf(giaddr);
1243 log.debug("DHCPLEASEQUERYRESP giaddr: {}({})", giaddr, destinationAddress);
1244
1245 Host destinationHost = hostService.getHostsByIp(destinationAddress).stream().findFirst().orElse(null);
1246 if (destinationHost != null) {
1247 destinationMac = destinationHost.mac();
1248 log.trace("DHCPLEASEQUERYRESP destination mac is: {}", destinationMac);
1249 ConnectPoint destinationLocation = destinationHost.location();
1250 log.trace("Lookup for client interface by destination location {}", destinationLocation);
1251 clientInterface = interfaceService.getInterfacesByPort(destinationLocation)
1252 .stream()
1253 .filter(iface -> interfaceContainsVlan(iface, VlanId.vlanId(etherReply.getVlanID())))
1254 .findFirst()
1255 .orElse(null);
1256 log.trace("Found Host {} by ip {}", destinationHost, destinationAddress);
1257 log.debug("DHCPLEASEQUERYRESP Client interface: {}",
1258 (clientInterface != null ? clientInterface : "not resolved"));
1259
1260 }
1261 } else {
1262 clientInterface = getClientInterface(ethernetPacket, dhcpPayload).orElse(null);
1263 }
Charles Chand0dd7002017-10-08 23:53:36 -04001264
1265 if (clientInterface == null) {
1266 log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
1267 return null;
1268 }
1269 VlanId vlanId;
1270 if (clientInterface.vlanTagged().isEmpty()) {
1271 vlanId = clientInterface.vlan();
1272 } else {
1273 // might be multiple vlan in same interface
1274 vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
1275 }
1276 if (vlanId == null) {
1277 vlanId = VlanId.NONE;
1278 }
1279 etherReply.setVlanID(vlanId.toShort());
1280 etherReply.setSourceMACAddress(clientInterface.mac());
1281
Taras Lemkin96a0d342018-03-26 14:52:58 +00001282 if (!directlyConnected(dhcpPayload) && learnRouteFromLeasequery) {
Charles Chand0dd7002017-10-08 23:53:36 -04001283 // if client is indirectly connected, try use next hop mac address
1284 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1285 HostId hostId = HostId.hostId(macAddress, vlanId);
1286 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1287 if (record != null) {
1288 // if next hop can be found, use mac address of next hop
Taras Lemkin96a0d342018-03-26 14:52:58 +00001289 Optional<MacAddress> nextHop = record.nextHopTemp();
1290 if (!nextHop.isPresent()) {
1291 nextHop = record.nextHop();
1292 }
1293 nextHop.ifPresent(etherReply::setDestinationMACAddress);
Charles Chand0dd7002017-10-08 23:53:36 -04001294 } else {
1295 // otherwise, discard the packet
1296 log.warn("Can't find record for host id {}, discard packet", hostId);
1297 return null;
1298 }
1299 } else {
Taras Lemkin96a0d342018-03-26 14:52:58 +00001300 etherReply.setDestinationMACAddress(destinationMac);
Charles Chand0dd7002017-10-08 23:53:36 -04001301 }
1302
Charles Chand0dd7002017-10-08 23:53:36 -04001303 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
Taras Lemkin96a0d342018-03-26 14:52:58 +00001304 if (directlyConnected(dhcpPayload)) {
1305 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
1306 } else {
1307 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
1308 }
Charles Chand0dd7002017-10-08 23:53:36 -04001309
1310 udpPacket.setPayload(dhcpPayload);
1311 ipv4Packet.setPayload(udpPacket);
1312 etherReply.setPayload(ipv4Packet);
Taras Lemkin96a0d342018-03-26 14:52:58 +00001313 udpPacket.resetChecksum();
1314
1315 return InternalPacket.internalPacket(etherReply, clientInterface.connectPoint());
Charles Chand0dd7002017-10-08 23:53:36 -04001316 }
1317 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -07001318 * Extracts VLAN ID from relay agent option.
1319 *
1320 * @param dhcpPayload the DHCP payload
1321 * @return VLAN ID from DHCP payload; null if not exists
1322 */
Yi Tseng4f2a0462017-08-31 11:21:00 -07001323 private VlanId getVlanIdFromRelayAgentOption(DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001324 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
1325 if (option == null) {
1326 return null;
1327 }
1328 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
1329 if (circuitIdSubOption == null) {
1330 return null;
1331 }
1332 try {
1333 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
1334 return circuitId.vlanId();
1335 } catch (IllegalArgumentException e) {
1336 // can't deserialize the circuit ID
1337 return null;
1338 }
1339 }
1340
1341 /**
1342 * Removes DHCP relay agent information option (option 82) from DHCP payload.
1343 * Also reset giaddr to 0
1344 *
1345 * @param ethPacket the Ethernet packet to be processed
1346 * @return Ethernet packet processed
1347 */
1348 private Ethernet removeRelayAgentOption(Ethernet ethPacket) {
rsahot036620655b2018-02-26 15:01:37 -05001349 Ethernet ethernet = (Ethernet) ethPacket.duplicate();
Yi Tsengdcef2c22017-08-05 20:34:06 -07001350 IPv4 ipv4 = (IPv4) ethernet.getPayload();
1351 UDP udp = (UDP) ipv4.getPayload();
1352 DHCP dhcpPayload = (DHCP) udp.getPayload();
1353
1354 // removes relay agent information option
1355 List<DhcpOption> options = dhcpPayload.getOptions();
1356 options = options.stream()
1357 .filter(option -> option.getCode() != OptionCode_CircuitID.getValue())
1358 .collect(Collectors.toList());
1359 dhcpPayload.setOptions(options);
1360 dhcpPayload.setGatewayIPAddress(0);
1361
1362 udp.setPayload(dhcpPayload);
1363 ipv4.setPayload(udp);
1364 ethernet.setPayload(ipv4);
1365 return ethernet;
1366 }
1367
Taras Lemkin96a0d342018-03-26 14:52:58 +00001368 private boolean isDhcpPacketLeasequery(DHCP dhcpPacket) {
1369 switch (dhcpPacket.getPacketType()) {
1370 case DHCPLEASEACTIVE:
1371 case DHCPLEASEQUERY:
1372 case DHCPLEASEUNASSIGNED:
1373 case DHCPLEASEUNKNOWN:
1374 return true;
1375 default:
1376 return false;
1377 }
1378 }
Yi Tseng51301292017-07-28 13:02:59 -07001379
1380 /**
1381 * Check if the host is directly connected to the network or not.
1382 *
1383 * @param dhcpPayload the dhcp payload
1384 * @return true if the host is directly connected to the network; false otherwise
1385 */
1386 private boolean directlyConnected(DHCP dhcpPayload) {
Taras Lemkin96a0d342018-03-26 14:52:58 +00001387 // leasequery is always indirect
1388 if (isDhcpPacketLeasequery(dhcpPayload)) {
1389 return false;
1390 }
1391
Yi Tseng440e2b72017-08-24 14:47:34 -07001392 DhcpRelayAgentOption relayAgentOption =
1393 (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
Yi Tseng51301292017-07-28 13:02:59 -07001394
1395 // Doesn't contains relay option
1396 if (relayAgentOption == null) {
1397 return true;
1398 }
1399
Yi Tseng440e2b72017-08-24 14:47:34 -07001400 // check circuit id, if circuit id is invalid, we say it is an indirect host
1401 DhcpOption circuitIdOpt = relayAgentOption.getSubOption(CIRCUIT_ID.getValue());
Yi Tseng51301292017-07-28 13:02:59 -07001402
Yi Tseng440e2b72017-08-24 14:47:34 -07001403 try {
1404 CircuitId.deserialize(circuitIdOpt.getData());
Yi Tseng51301292017-07-28 13:02:59 -07001405 return true;
Yi Tseng440e2b72017-08-24 14:47:34 -07001406 } catch (Exception e) {
1407 // invalid circuit id
1408 return false;
Yi Tseng51301292017-07-28 13:02:59 -07001409 }
Yi Tseng51301292017-07-28 13:02:59 -07001410 }
1411
1412
1413 /**
1414 * Send the DHCP ack to the requester host.
1415 * Modify Host or Route store according to the type of DHCP.
1416 *
1417 * @param ethernetPacketAck the packet
1418 * @param dhcpPayload the DHCP data
1419 */
1420 private void handleDhcpAck(Ethernet ethernetPacketAck, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001421 Optional<Interface> outInterface = getClientInterface(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -07001422 if (!outInterface.isPresent()) {
1423 log.warn("Can't find output interface for dhcp: {}", dhcpPayload);
1424 return;
1425 }
1426
1427 Interface outIface = outInterface.get();
1428 HostLocation hostLocation = new HostLocation(outIface.connectPoint(), System.currentTimeMillis());
1429 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
Yi Tseng4f2a0462017-08-31 11:21:00 -07001430 VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001431 if (vlanId == null) {
1432 vlanId = outIface.vlan();
1433 }
Yi Tseng51301292017-07-28 13:02:59 -07001434 HostId hostId = HostId.hostId(macAddress, vlanId);
1435 Ip4Address ip = Ip4Address.valueOf(dhcpPayload.getYourIPAddress());
1436
1437 if (directlyConnected(dhcpPayload)) {
1438 // Add to host store if it connect to network directly
1439 Set<IpAddress> ips = Sets.newHashSet(ip);
Yi Tsengaa417a62017-09-08 17:22:51 -07001440 Host host = hostService.getHost(hostId);
Yi Tseng51301292017-07-28 13:02:59 -07001441
Yi Tsengaa417a62017-09-08 17:22:51 -07001442 Set<HostLocation> hostLocations = Sets.newHashSet(hostLocation);
1443 if (host != null) {
1444 // Dual homing support:
1445 // if host exists, use old locations and new location
1446 hostLocations.addAll(host.locations());
1447 }
1448 HostDescription desc = new DefaultHostDescription(macAddress, vlanId,
1449 hostLocations, ips, false);
1450 // Add IP address when dhcp server give the host new ip address
1451 providerService.hostDetected(hostId, desc, false);
Yi Tseng51301292017-07-28 13:02:59 -07001452 } else {
1453 // Add to route store if it does not connect to network directly
1454 // Get gateway host IP according to host mac address
Yi Tsengdcef2c22017-08-05 20:34:06 -07001455 // TODO: remove relay store here
Yi Tseng51301292017-07-28 13:02:59 -07001456 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1457
1458 if (record == null) {
1459 log.warn("Can't find DHCP record of host {}", hostId);
1460 return;
1461 }
1462
1463 MacAddress gwMac = record.nextHop().orElse(null);
1464 if (gwMac == null) {
1465 log.warn("Can't find gateway mac address from record {}", record);
1466 return;
1467 }
1468
1469 HostId gwHostId = HostId.hostId(gwMac, record.vlanId());
1470 Host gwHost = hostService.getHost(gwHostId);
1471
1472 if (gwHost == null) {
1473 log.warn("Can't find gateway host {}", gwHostId);
1474 return;
1475 }
1476
1477 Ip4Address nextHopIp = gwHost.ipAddresses()
1478 .stream()
1479 .filter(IpAddress::isIp4)
1480 .map(IpAddress::getIp4Address)
1481 .findFirst()
1482 .orElse(null);
1483
1484 if (nextHopIp == null) {
1485 log.warn("Can't find IP address of gateway {}", gwHost);
1486 return;
1487 }
1488
Charles Chan6305b692018-04-04 11:43:54 -07001489 Route route = new Route(Route.Source.DHCP, ip.toIpPrefix(), nextHopIp);
Daniel Ginsburg83b76452018-06-09 01:43:59 +03001490 routeStore.replaceRoute(route);
Yi Tseng51301292017-07-28 13:02:59 -07001491 }
Yi Tseng51301292017-07-28 13:02:59 -07001492 }
1493
1494 /**
Yi Tseng51301292017-07-28 13:02:59 -07001495 * Gets output interface of a dhcp packet.
1496 * If option 82 exists in the dhcp packet and the option was sent by
Yi Tseng4f2a0462017-08-31 11:21:00 -07001497 * ONOS (circuit format is correct), use the connect
Yi Tseng51301292017-07-28 13:02:59 -07001498 * point and vlan id from circuit id; otherwise, find host by destination
1499 * address and use vlan id from sender (dhcp server).
1500 *
1501 * @param ethPacket the ethernet packet
1502 * @param dhcpPayload the dhcp packet
1503 * @return an interface represent the output port and vlan; empty value
1504 * if the host or circuit id not found
1505 */
Yi Tsengdcef2c22017-08-05 20:34:06 -07001506 private Optional<Interface> getClientInterface(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tseng51301292017-07-28 13:02:59 -07001507 VlanId originalPacketVlanId = VlanId.vlanId(ethPacket.getVlanID());
Yi Tseng51301292017-07-28 13:02:59 -07001508 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
1509
Yi Tseng4f2a0462017-08-31 11:21:00 -07001510 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
1511 try {
1512 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
1513 ConnectPoint connectPoint = ConnectPoint.deviceConnectPoint(circuitId.connectPoint());
1514 VlanId vlanId = circuitId.vlanId();
1515 return interfaceService.getInterfacesByPort(connectPoint)
1516 .stream()
1517 .filter(iface -> interfaceContainsVlan(iface, vlanId))
1518 .findFirst();
1519 } catch (IllegalArgumentException ex) {
1520 // invalid circuit format, didn't sent by ONOS
1521 log.debug("Invalid circuit {}, use information from dhcp payload",
1522 circuitIdSubOption.getData());
Yi Tseng51301292017-07-28 13:02:59 -07001523 }
1524
1525 // Use Vlan Id from DHCP server if DHCP relay circuit id was not
1526 // sent by ONOS or circuit Id can't be parsed
Yi Tsengdcef2c22017-08-05 20:34:06 -07001527 // TODO: remove relay store from this method
Yi Tseng51301292017-07-28 13:02:59 -07001528 MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
jayakumarthazhathed5f05d2018-09-25 15:56:51 -04001529 VlanId filteredVlanId = getVlanIdFromDhcpRecord(dstMac, originalPacketVlanId);
1530 // Get the vlan from the dhcp record
1531 if (filteredVlanId == null) {
1532 log.debug("not find the matching DHCP record for mac: {} and vlan: {}", dstMac, originalPacketVlanId);
1533 return Optional.empty();
1534 }
1535
1536 Optional<DhcpRecord> dhcpRecord = dhcpRelayStore.getDhcpRecord(HostId.hostId(dstMac, filteredVlanId));
Yi Tsengdcef2c22017-08-05 20:34:06 -07001537 ConnectPoint clientConnectPoint = dhcpRecord
Yi Tseng51301292017-07-28 13:02:59 -07001538 .map(DhcpRecord::locations)
1539 .orElse(Collections.emptySet())
1540 .stream()
1541 .reduce((hl1, hl2) -> {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001542 // find latest host connect point
Yi Tseng51301292017-07-28 13:02:59 -07001543 if (hl1 == null || hl2 == null) {
1544 return hl1 == null ? hl2 : hl1;
1545 }
1546 return hl1.time() > hl2.time() ? hl1 : hl2;
1547 })
Yi Tsengdcef2c22017-08-05 20:34:06 -07001548 .orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -07001549
Yi Tsengdcef2c22017-08-05 20:34:06 -07001550 if (clientConnectPoint != null) {
1551 return interfaceService.getInterfacesByPort(clientConnectPoint)
1552 .stream()
jayakumarthazhathed5f05d2018-09-25 15:56:51 -04001553 .filter(iface -> interfaceContainsVlan(iface, filteredVlanId))
Yi Tsengdcef2c22017-08-05 20:34:06 -07001554 .findFirst();
1555 }
1556 return Optional.empty();
Yi Tseng51301292017-07-28 13:02:59 -07001557 }
1558
1559 /**
jayakumarthazhathed5f05d2018-09-25 15:56:51 -04001560 * Get the required vlanId in case the DCHP record has more than one vlanId for a given MAC.
1561 *
1562 * @param mac MAC address of the DHCP client
1563 * @param vlan Expected vlan of the DHCP client
1564 */
1565 private VlanId getVlanIdFromDhcpRecord(MacAddress mac, VlanId vlan) {
1566 // Get all the DHCP records matching with the mac address
1567 // If only one entry is present then pick the vlan of that entry
1568 // If more then one entry is present then look for an entry with matching vlan
1569 // else return null
1570 Collection<DhcpRecord> records = dhcpRelayStore.getDhcpRecords();
1571 List<DhcpRecord> filteredRecords = new ArrayList<>();
1572 for (DhcpRecord e: records) {
1573 if (e.macAddress().equals(mac)) {
1574 filteredRecords.add(e);
1575 }
1576 }
1577 log.debug("getVlanIdFromDhcpRecord mac: {} vlan: {}", mac, vlan);
1578 log.debug("filteredRecords are: {}", filteredRecords);
1579 if (filteredRecords.size() == 1) {
1580 log.debug("Only one DHCP record entry. Returning back the vlan of that DHCP record: {}", filteredRecords);
1581 return filteredRecords.get(0).vlanId();
1582 }
1583 // Check in the DHCP filtered record for matching vlan
1584 for (DhcpRecord e: filteredRecords) {
1585 if (e.vlanId().equals(vlan)) {
1586 log.debug("Found a matching vlan entry in the DHCP record:{}", e);
1587 return vlan;
1588 }
1589 }
1590 // Found nothing return null
1591 log.debug("Returning null as no matching or more than one matching entry found");
1592 return null;
1593
1594 }
1595
1596
1597
1598 /**
Yi Tseng51301292017-07-28 13:02:59 -07001599 * Send the response DHCP to the requester host.
1600 *
Taras Lemkin96a0d342018-03-26 14:52:58 +00001601 * @param thePacket the packet
Yi Tseng51301292017-07-28 13:02:59 -07001602 * @param dhcpPayload the DHCP data
1603 */
Taras Lemkin96a0d342018-03-26 14:52:58 +00001604 private void sendResponseToClient(InternalPacket thePacket, DHCP dhcpPayload) {
1605 checkNotNull(thePacket, "Nothing to send");
1606 checkNotNull(thePacket.getPacket(), "Packet to send must not be empty");
1607 checkNotNull(thePacket.getDestLocation(), "Packet destination not be empty");
1608
1609 Ethernet ethPacket = thePacket.getPacket();
Yi Tsengdcef2c22017-08-05 20:34:06 -07001610 if (directlyConnected(dhcpPayload)) {
1611 ethPacket = removeRelayAgentOption(ethPacket);
1612 }
Taras Lemkin96a0d342018-03-26 14:52:58 +00001613
Yi Tsengdcef2c22017-08-05 20:34:06 -07001614 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
Taras Lemkin96a0d342018-03-26 14:52:58 +00001615 .setOutput(thePacket.getDestLocation().port())
Yi Tsengdcef2c22017-08-05 20:34:06 -07001616 .build();
1617 OutboundPacket o = new DefaultOutboundPacket(
Taras Lemkin96a0d342018-03-26 14:52:58 +00001618 thePacket.getDestLocation().deviceId(),
Yi Tsengdcef2c22017-08-05 20:34:06 -07001619 treatment,
1620 ByteBuffer.wrap(ethPacket.serialize()));
1621 if (log.isTraceEnabled()) {
Taras Lemkin96a0d342018-03-26 14:52:58 +00001622 log.trace("Relaying packet to DHCP client {} via {}",
Yi Tsengdcef2c22017-08-05 20:34:06 -07001623 ethPacket,
Taras Lemkin96a0d342018-03-26 14:52:58 +00001624 thePacket.getDestLocation());
Yi Tsengdcef2c22017-08-05 20:34:06 -07001625 }
1626 packetService.emit(o);
Yi Tseng51301292017-07-28 13:02:59 -07001627 }
Yi Tseng483ac6f2017-08-02 15:03:31 -07001628
Yi Tsengaa417a62017-09-08 17:22:51 -07001629 @Override
1630 public void triggerProbe(Host host) {
1631 // Do nothing here
1632 }
1633
1634 @Override
1635 public ProviderId id() {
Charles Chand988c282017-09-12 17:09:32 -07001636 return PROVIDER_ID;
Yi Tsengaa417a62017-09-08 17:22:51 -07001637 }
1638
Yi Tseng483ac6f2017-08-02 15:03:31 -07001639 class InternalHostListener implements HostListener {
1640 @Override
1641 public void event(HostEvent event) {
Yi Tseng919b2df2017-09-07 16:22:51 -07001642 if (!configured()) {
1643 return;
1644 }
Yi Tseng483ac6f2017-08-02 15:03:31 -07001645 switch (event.type()) {
1646 case HOST_ADDED:
1647 case HOST_UPDATED:
Charles Chan24a96ff2018-08-13 10:32:03 -07001648 case HOST_MOVED:
Jordan Halterman6328db72018-04-10 13:34:50 -04001649 log.trace("Scheduled host event {}", event);
1650 hostEventExecutor.execute(() -> hostUpdated(event.subject()));
Yi Tseng483ac6f2017-08-02 15:03:31 -07001651 break;
1652 case HOST_REMOVED:
Jordan Halterman6328db72018-04-10 13:34:50 -04001653 log.trace("Scheduled host event {}", event);
1654 hostEventExecutor.execute(() -> hostRemoved(event.subject()));
Yi Tseng483ac6f2017-08-02 15:03:31 -07001655 break;
Yi Tseng483ac6f2017-08-02 15:03:31 -07001656 default:
1657 break;
1658 }
1659 }
1660 }
1661
1662 /**
Yi Tseng483ac6f2017-08-02 15:03:31 -07001663 * Handle host updated.
1664 * If the host is DHCP server or gateway, update connect mac and vlan.
1665 *
1666 * @param host the host
1667 */
1668 private void hostUpdated(Host host) {
Yi Tseng525ff402017-10-23 19:39:39 -07001669 hostUpdated(host, defaultServerInfoList);
1670 hostUpdated(host, indirectServerInfoList);
Yi Tseng483ac6f2017-08-02 15:03:31 -07001671 }
1672
Yi Tseng525ff402017-10-23 19:39:39 -07001673 private void hostUpdated(Host host, List<DhcpServerInfo> srverInfoList) {
jjosep004c05f55ab2018-08-21 09:01:10 -04001674 srverInfoList.stream().forEach(serverInfo -> {
1675 Ip4Address targetIp = serverInfo.getDhcpGatewayIp4().orElse(null);
Yi Tseng525ff402017-10-23 19:39:39 -07001676 Ip4Address serverIp = serverInfo.getDhcpServerIp4().orElse(null);
Yi Tseng525ff402017-10-23 19:39:39 -07001677 if (targetIp == null) {
1678 targetIp = serverIp;
1679 }
Yi Tseng525ff402017-10-23 19:39:39 -07001680 if (targetIp != null) {
1681 if (host.ipAddresses().contains(targetIp)) {
1682 serverInfo.setDhcpConnectMac(host.mac());
1683 serverInfo.setDhcpConnectVlan(host.vlan());
1684 requestDhcpPacket(serverIp);
1685 }
1686 }
jjosep004c05f55ab2018-08-21 09:01:10 -04001687 });
Yi Tseng525ff402017-10-23 19:39:39 -07001688 }
1689
1690
Yi Tseng483ac6f2017-08-02 15:03:31 -07001691 /**
1692 * Handle host removed.
1693 * If the host is DHCP server or gateway, unset connect mac and vlan.
1694 *
1695 * @param host the host
1696 */
1697 private void hostRemoved(Host host) {
Yi Tseng525ff402017-10-23 19:39:39 -07001698 hostRemoved(host, defaultServerInfoList);
1699 hostRemoved(host, indirectServerInfoList);
1700 }
1701
1702 private void hostRemoved(Host host, List<DhcpServerInfo> serverInfoList) {
jjosep004c05f55ab2018-08-21 09:01:10 -04001703 serverInfoList.stream().forEach(serverInfo -> {
1704 Ip4Address targetIp = serverInfo.getDhcpGatewayIp4().orElse(null);
Yi Tseng525ff402017-10-23 19:39:39 -07001705 Ip4Address serverIp = serverInfo.getDhcpServerIp4().orElse(null);
Yi Tseng525ff402017-10-23 19:39:39 -07001706 if (targetIp == null) {
1707 targetIp = serverIp;
Yi Tseng919b2df2017-09-07 16:22:51 -07001708 }
Yi Tseng525ff402017-10-23 19:39:39 -07001709
1710 if (targetIp != null) {
1711 if (host.ipAddresses().contains(targetIp)) {
Yi Tseng919b2df2017-09-07 16:22:51 -07001712 serverInfo.setDhcpConnectVlan(null);
1713 serverInfo.setDhcpConnectMac(null);
Yi Tseng525ff402017-10-23 19:39:39 -07001714 cancelDhcpPacket(serverIp);
Yi Tseng919b2df2017-09-07 16:22:51 -07001715 }
Yi Tseng483ac6f2017-08-02 15:03:31 -07001716 }
jjosep004c05f55ab2018-08-21 09:01:10 -04001717 });
Yi Tseng525ff402017-10-23 19:39:39 -07001718 }
Yi Tseng919b2df2017-09-07 16:22:51 -07001719
Yi Tseng525ff402017-10-23 19:39:39 -07001720 private void requestDhcpPacket(Ip4Address serverIp) {
1721 requestServerDhcpPacket(serverIp);
1722 requestClientDhcpPacket(serverIp);
1723 }
Yi Tseng919b2df2017-09-07 16:22:51 -07001724
Yi Tseng525ff402017-10-23 19:39:39 -07001725 private void cancelDhcpPacket(Ip4Address serverIp) {
1726 cancelServerDhcpPacket(serverIp);
1727 cancelClientDhcpPacket(serverIp);
1728 }
1729
1730 private void cancelServerDhcpPacket(Ip4Address serverIp) {
1731 TrafficSelector serverSelector =
1732 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1733 .matchIPSrc(serverIp.toIpPrefix())
1734 .build();
1735 packetService.cancelPackets(serverSelector,
1736 PacketPriority.CONTROL,
1737 appId);
1738 }
1739
1740 private void requestServerDhcpPacket(Ip4Address serverIp) {
1741 TrafficSelector serverSelector =
1742 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1743 .matchIPSrc(serverIp.toIpPrefix())
1744 .build();
1745 packetService.requestPackets(serverSelector,
1746 PacketPriority.CONTROL,
1747 appId);
1748 }
1749
1750 private void cancelClientDhcpPacket(Ip4Address serverIp) {
1751 // Packet comes from relay
1752 TrafficSelector indirectClientSelector =
1753 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1754 .matchIPDst(serverIp.toIpPrefix())
1755 .build();
1756 packetService.cancelPackets(indirectClientSelector,
1757 PacketPriority.CONTROL,
1758 appId);
1759
1760 // Packet comes from client
1761 packetService.cancelPackets(CLIENT_SERVER_SELECTOR,
1762 PacketPriority.CONTROL,
1763 appId);
1764 }
1765
1766 private void requestClientDhcpPacket(Ip4Address serverIp) {
1767 // Packet comes from relay
1768 TrafficSelector indirectClientSelector =
1769 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1770 .matchIPDst(serverIp.toIpPrefix())
1771 .build();
1772 packetService.requestPackets(indirectClientSelector,
1773 PacketPriority.CONTROL,
1774 appId);
1775
1776 // Packet comes from client
1777 packetService.requestPackets(CLIENT_SERVER_SELECTOR,
1778 PacketPriority.CONTROL,
1779 appId);
1780 }
1781
1782 /**
1783 * Process the ignore rules.
1784 *
1785 * @param deviceId the device id
1786 * @param vlanId the vlan to be ignored
1787 * @param op the operation, ADD to install; REMOVE to uninstall rules
1788 */
1789 private void processIgnoreVlanRule(DeviceId deviceId, VlanId vlanId, Objective.Operation op) {
Yi Tseng525ff402017-10-23 19:39:39 -07001790 AtomicInteger installedCount = new AtomicInteger(DHCP_SELECTORS.size());
1791 DHCP_SELECTORS.forEach(trafficSelector -> {
1792 TrafficSelector selector = DefaultTrafficSelector.builder(trafficSelector)
1793 .matchVlanId(vlanId)
1794 .build();
1795
1796 ForwardingObjective.Builder builder = DefaultForwardingObjective.builder()
1797 .withFlag(ForwardingObjective.Flag.VERSATILE)
1798 .withSelector(selector)
1799 .withPriority(IGNORE_CONTROL_PRIORITY)
Yi Tsengdbabeed2017-11-29 10:49:20 -08001800 .withTreatment(DefaultTrafficTreatment.emptyTreatment())
Yi Tseng525ff402017-10-23 19:39:39 -07001801 .fromApp(appId);
1802
1803
1804 ObjectiveContext objectiveContext = new ObjectiveContext() {
1805 @Override
1806 public void onSuccess(Objective objective) {
1807 log.info("Ignore rule {} (Vlan id {}, device {}, selector {})",
1808 op, vlanId, deviceId, selector);
1809 int countDown = installedCount.decrementAndGet();
1810 if (countDown != 0) {
1811 return;
1812 }
1813 switch (op) {
1814 case ADD:
1815 ignoredVlans.put(deviceId, vlanId);
1816 break;
1817 case REMOVE:
1818 ignoredVlans.remove(deviceId, vlanId);
1819 break;
1820 default:
1821 log.warn("Unsupported objective operation {}", op);
1822 break;
1823 }
Yi Tseng919b2df2017-09-07 16:22:51 -07001824 }
Yi Tseng525ff402017-10-23 19:39:39 -07001825
1826 @Override
1827 public void onError(Objective objective, ObjectiveError error) {
1828 log.warn("Can't {} ignore rule (vlan id {}, selector {}, device {}) due to {}",
1829 op, vlanId, selector, deviceId, error);
Yi Tseng919b2df2017-09-07 16:22:51 -07001830 }
Yi Tseng525ff402017-10-23 19:39:39 -07001831 };
1832
1833 ForwardingObjective fwd;
1834 switch (op) {
1835 case ADD:
1836 fwd = builder.add(objectiveContext);
1837 break;
1838 case REMOVE:
1839 fwd = builder.remove(objectiveContext);
1840 break;
1841 default:
1842 log.warn("Unsupported objective operation {}", op);
1843 return;
Yi Tseng4f2a0462017-08-31 11:21:00 -07001844 }
Yi Tseng525ff402017-10-23 19:39:39 -07001845
1846 Device device = deviceService.getDevice(deviceId);
1847 if (device == null || !device.is(Pipeliner.class)) {
1848 log.warn("Device {} is not available now, wait until device is available", deviceId);
1849 return;
1850 }
1851 flowObjectiveService.apply(deviceId, fwd);
1852 });
Yi Tseng483ac6f2017-08-02 15:03:31 -07001853 }
Kalhee Kimba366062017-11-07 16:32:09 +00001854
1855 @Override
1856 public void setDhcpFpmEnabled(Boolean enabled) {
1857 // v4 does not use fpm. Do nothing.
1858 }
rsahot036620655b2018-02-26 15:01:37 -05001859 private List<DhcpServerInfo> findValidServerInfo(boolean directConnFlag) {
1860 List<DhcpServerInfo> validServerInfo;
1861
1862 if (directConnFlag || indirectServerInfoList.isEmpty()) {
1863 validServerInfo = new ArrayList<DhcpServerInfo>(defaultServerInfoList);
1864 } else {
1865 validServerInfo = new ArrayList<DhcpServerInfo>(indirectServerInfoList);
1866 }
1867 return validServerInfo;
1868 }
1869
1870
1871 private boolean checkDhcpServerConnPt(boolean directConnFlag,
1872 DhcpServerInfo serverInfo) {
1873 if (serverInfo.getDhcpServerConnectPoint() == null) {
1874 log.warn("DHCP4 server connect point for {} connPt {}",
1875 directConnFlag ? "direct" : "indirect", serverInfo.getDhcpServerConnectPoint());
1876 return false;
1877 }
1878 return true;
1879 }
1880
1881 /**
1882 * Checks if serverInfo's host info (mac and vlan) is filled in; if not, fills in.
1883 *
1884 * @param serverInfo server information
1885 * @return newServerInfo if host info can be either found or filled in.
1886 */
1887 private DhcpServerInfo getHostInfoForServerInfo(DhcpServerInfo serverInfo, List<DhcpServerInfo> sererInfoList) {
1888 DhcpServerInfo newServerInfo = null;
1889 MacAddress dhcpServerConnectMac = serverInfo.getDhcpConnectMac().orElse(null);
1890 VlanId dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
1891 ConnectPoint dhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
1892
1893 if (dhcpServerConnectMac != null && dhcpConnectVlan != null) {
1894 newServerInfo = serverInfo;
Charles Chan2de55302018-03-02 18:27:59 -08001895 log.debug("DHCP server {} host info found. ConnectPt{} Mac {} vlan {}", serverInfo.getDhcpServerIp4(),
rsahot036620655b2018-02-26 15:01:37 -05001896 dhcpServerConnectPoint, dhcpServerConnectMac, dhcpConnectVlan);
1897 } else {
1898 log.warn("DHCP server {} not resolve yet connectPt {} mac {} vlan {}", serverInfo.getDhcpServerIp4(),
1899 dhcpServerConnectPoint, dhcpServerConnectMac, dhcpConnectVlan);
1900
1901 Ip4Address ipToProbe;
1902 if (serverInfo.getDhcpGatewayIp4().isPresent()) {
1903 ipToProbe = serverInfo.getDhcpGatewayIp4().get();
1904 } else {
1905 ipToProbe = serverInfo.getDhcpServerIp4().orElse(null);
1906 }
1907 String hostToProbe = serverInfo.getDhcpGatewayIp6()
1908 .map(ip -> "gateway").orElse("server");
1909
1910 log.warn("Dynamically probing to resolve {} IP {}", hostToProbe, ipToProbe);
1911 hostService.startMonitoringIp(ipToProbe);
1912
1913 Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
1914 if (!hosts.isEmpty()) {
1915 int serverInfoIndex = sererInfoList.indexOf(serverInfo);
1916 Host host = hosts.iterator().next();
1917 serverInfo.setDhcpConnectVlan(host.vlan());
1918 serverInfo.setDhcpConnectMac(host.mac());
1919 // replace the serverInfo in the list
1920 sererInfoList.set(serverInfoIndex, serverInfo);
1921 newServerInfo = serverInfo;
1922 log.warn("Dynamically host found host {}", host);
1923 } else {
1924 log.warn("No host found host ip {} dynamically", ipToProbe);
1925 }
1926 }
1927 return newServerInfo;
1928 }
1929
1930 /**
1931 * Gets Interface facing to the server for default host.
1932 *
1933 * @param serverInfo server information
1934 * @return the Interface facing to the server; null if not found
1935 */
1936 private Interface getServerInterface(DhcpServerInfo serverInfo) {
1937 Interface serverInterface = null;
1938
1939 ConnectPoint dhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
1940 VlanId dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
1941
1942 if (dhcpServerConnectPoint != null && dhcpConnectVlan != null) {
1943 serverInterface = interfaceService.getInterfacesByPort(dhcpServerConnectPoint)
1944 .stream()
Taras Lemkin96a0d342018-03-26 14:52:58 +00001945 .filter(iface -> Dhcp4HandlerUtil.interfaceContainsVlan(iface, dhcpConnectVlan))
rsahot036620655b2018-02-26 15:01:37 -05001946 .findFirst()
1947 .orElse(null);
1948 } else {
1949 log.warn("DHCP server {} not resolve yet connectPoint {} vlan {}", serverInfo.getDhcpServerIp6(),
1950 dhcpServerConnectPoint, dhcpConnectVlan);
1951 }
1952
1953 return serverInterface;
1954 }
1955
1956 //forward the packet to ConnectPoint where the DHCP server is attached.
1957 private void forwardPacket(InternalPacket packet) {
1958 //send Packetout to dhcp server connectpoint.
Taras Lemkin96a0d342018-03-26 14:52:58 +00001959 if (packet.getDestLocation() != null) {
rsahot036620655b2018-02-26 15:01:37 -05001960 TrafficTreatment t = DefaultTrafficTreatment.builder()
Taras Lemkin96a0d342018-03-26 14:52:58 +00001961 .setOutput(packet.getDestLocation().port()).build();
rsahot036620655b2018-02-26 15:01:37 -05001962 OutboundPacket o = new DefaultOutboundPacket(
Taras Lemkin96a0d342018-03-26 14:52:58 +00001963 packet.getDestLocation().deviceId(), t, ByteBuffer.wrap(packet.getPacket().serialize()));
rsahot036620655b2018-02-26 15:01:37 -05001964 if (log.isTraceEnabled()) {
Taras Lemkin96a0d342018-03-26 14:52:58 +00001965 log.trace("Relaying packet to destination {}", packet.getDestLocation());
rsahot036620655b2018-02-26 15:01:37 -05001966 }
Taras Lemkin96a0d342018-03-26 14:52:58 +00001967 log.debug("packetService.emit(o) to port {}", packet.getDestLocation());
rsahot036620655b2018-02-26 15:01:37 -05001968 packetService.emit(o);
1969 }
1970 }
1971
1972
1973 private DhcpServerInfo findServerInfoFromServer(boolean directConnFlag, ConnectPoint inPort) {
1974 List<DhcpServerInfo> validServerInfoList = findValidServerInfo(directConnFlag);
1975 DhcpServerInfo foundServerInfo = null;
1976 for (DhcpServerInfo serverInfo : validServerInfoList) {
1977 if (inPort.equals(serverInfo.getDhcpServerConnectPoint().get())) {
1978 foundServerInfo = serverInfo;
Charles Chan2de55302018-03-02 18:27:59 -08001979 log.debug("ServerInfo found for Rcv port {} Server Connect Point {} for {}",
rsahot036620655b2018-02-26 15:01:37 -05001980 inPort, serverInfo.getDhcpServerConnectPoint(), directConnFlag ? "direct" : "indirect");
1981 break;
rsahot036620655b2018-02-26 15:01:37 -05001982 }
1983 }
1984 return foundServerInfo;
1985 }
Yi Tseng51301292017-07-28 13:02:59 -07001986}