blob: e70be901e82414c3bcbac9ebd46f39d3f5d066de [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;
Yi Tseng525ff402017-10-23 19:39:39 -0700118import static org.onosproject.net.flowobjective.Objective.Operation.ADD;
119import static org.onosproject.net.flowobjective.Objective.Operation.REMOVE;
Yi Tseng51301292017-07-28 13:02:59 -0700120
rsahot036620655b2018-02-26 15:01:37 -0500121
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700122@Component(service = { DhcpHandler.class, HostProvider.class })
123//@Property(name = "version", value = "4")
Yi Tsengaa417a62017-09-08 17:22:51 -0700124public class Dhcp4HandlerImpl implements DhcpHandler, HostProvider {
Charles Chand988c282017-09-12 17:09:32 -0700125 public static final String DHCP_V4_RELAY_APP = "org.onosproject.Dhcp4HandlerImpl";
126 public static final ProviderId PROVIDER_ID = new ProviderId("dhcp4", DHCP_V4_RELAY_APP);
Yi Tseng525ff402017-10-23 19:39:39 -0700127 private static final String BROADCAST_IP = "255.255.255.255";
128 private static final int IGNORE_CONTROL_PRIORITY = PacketPriority.CONTROL.priorityValue() + 1000;
129
Taras Lemkin96a0d342018-03-26 14:52:58 +0000130 private static final String LQ_ROUTE_PROPERTY_NAME = "learnRouteFromLeasequery";
131
Yi Tseng525ff402017-10-23 19:39:39 -0700132 private static final TrafficSelector CLIENT_SERVER_SELECTOR = DefaultTrafficSelector.builder()
133 .matchEthType(Ethernet.TYPE_IPV4)
134 .matchIPProtocol(IPv4.PROTOCOL_UDP)
135 .matchIPSrc(Ip4Address.ZERO.toIpPrefix())
136 .matchIPDst(Ip4Address.valueOf(BROADCAST_IP).toIpPrefix())
137 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
138 .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
139 .build();
140 private static final TrafficSelector SERVER_RELAY_SELECTOR = DefaultTrafficSelector.builder()
141 .matchEthType(Ethernet.TYPE_IPV4)
142 .matchIPProtocol(IPv4.PROTOCOL_UDP)
143 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
144 .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
145 .build();
146 static final Set<TrafficSelector> DHCP_SELECTORS = ImmutableSet.of(
147 CLIENT_SERVER_SELECTOR,
148 SERVER_RELAY_SELECTOR
149 );
Yi Tseng51301292017-07-28 13:02:59 -0700150 private static Logger log = LoggerFactory.getLogger(Dhcp4HandlerImpl.class);
151
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700152 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Yi Tseng51301292017-07-28 13:02:59 -0700153 protected DhcpRelayStore dhcpRelayStore;
154
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700155 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Yi Tseng51301292017-07-28 13:02:59 -0700156 protected PacketService packetService;
157
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700158 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Yi Tseng51301292017-07-28 13:02:59 -0700159 protected RouteStore routeStore;
160
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700161 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Yi Tseng51301292017-07-28 13:02:59 -0700162 protected InterfaceService interfaceService;
163
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700164 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Yi Tseng51301292017-07-28 13:02:59 -0700165 protected HostService hostService;
166
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700167 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Yi Tsengaa417a62017-09-08 17:22:51 -0700168 protected HostProviderRegistry providerRegistry;
169
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700170 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Yi Tseng525ff402017-10-23 19:39:39 -0700171 protected CoreService coreService;
172
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700173 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Yi Tseng525ff402017-10-23 19:39:39 -0700174 protected DeviceService deviceService;
175
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700176 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Yi Tseng525ff402017-10-23 19:39:39 -0700177 protected FlowObjectiveService flowObjectiveService;
178
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700179 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Taras Lemkin96a0d342018-03-26 14:52:58 +0000180 protected ComponentConfigService cfgService;
181
Yi Tsengaa417a62017-09-08 17:22:51 -0700182 protected HostProviderService providerService;
Yi Tseng525ff402017-10-23 19:39:39 -0700183 protected ApplicationId appId;
Charles Chan70fdd492018-03-07 17:36:06 -0800184 protected Multimap<DeviceId, VlanId> ignoredVlans = Multimaps.synchronizedMultimap(HashMultimap.create());
Yi Tseng483ac6f2017-08-02 15:03:31 -0700185 private InternalHostListener hostListener = new InternalHostListener();
186
Charles Chan909cff82018-03-05 13:14:02 -0800187 private List<DhcpServerInfo> defaultServerInfoList = new CopyOnWriteArrayList<>();
188 private List<DhcpServerInfo> indirectServerInfoList = new CopyOnWriteArrayList<>();
Taras Lemkin96a0d342018-03-26 14:52:58 +0000189
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700190 //@Property(name = Dhcp4HandlerImpl.LQ_ROUTE_PROPERTY_NAME, boolValue = false,
191 // label = "Enable learning routing information from LQ")
Taras Lemkin96a0d342018-03-26 14:52:58 +0000192 private Boolean learnRouteFromLeasequery = Boolean.TRUE;
Yi Tseng4f2a0462017-08-31 11:21:00 -0700193
Jordan Halterman6328db72018-04-10 13:34:50 -0400194 private Executor hostEventExecutor = newSingleThreadExecutor(
195 groupedThreads("dhcp4-event-host", "%d", log));
196
Yi Tseng483ac6f2017-08-02 15:03:31 -0700197 @Activate
Taras Lemkin96a0d342018-03-26 14:52:58 +0000198 protected void activate(ComponentContext context) {
199 cfgService.registerProperties(getClass());
200 modified(context);
Yi Tseng525ff402017-10-23 19:39:39 -0700201 appId = coreService.registerApplication(DHCP_V4_RELAY_APP);
Yi Tseng483ac6f2017-08-02 15:03:31 -0700202 hostService.addListener(hostListener);
Yi Tsengaa417a62017-09-08 17:22:51 -0700203 providerService = providerRegistry.register(this);
Yi Tseng483ac6f2017-08-02 15:03:31 -0700204 }
205
206 @Deactivate
207 protected void deactivate() {
Taras Lemkin96a0d342018-03-26 14:52:58 +0000208 cfgService.unregisterProperties(getClass(), false);
Yi Tsengaa417a62017-09-08 17:22:51 -0700209 providerRegistry.unregister(this);
210 hostService.removeListener(hostListener);
Yi Tseng919b2df2017-09-07 16:22:51 -0700211 defaultServerInfoList.forEach(this::stopMonitoringIps);
Charles Chanfc1c22e2018-07-19 09:52:01 -0700212 defaultServerInfoList.forEach(info -> info.getDhcpServerIp4().ifPresent(this::cancelDhcpPacket));
Yi Tseng919b2df2017-09-07 16:22:51 -0700213 defaultServerInfoList.clear();
214 indirectServerInfoList.forEach(this::stopMonitoringIps);
Charles Chanfc1c22e2018-07-19 09:52:01 -0700215 indirectServerInfoList.forEach(info -> info.getDhcpServerIp4().ifPresent(this::cancelDhcpPacket));
Yi Tseng919b2df2017-09-07 16:22:51 -0700216 indirectServerInfoList.clear();
Yi Tseng483ac6f2017-08-02 15:03:31 -0700217 }
218
Taras Lemkin96a0d342018-03-26 14:52:58 +0000219 @Modified
220 protected void modified(ComponentContext context) {
221 Dictionary<?, ?> properties = context.getProperties();
222 Boolean flag;
223 flag = Tools.isPropertyEnabled(properties, Dhcp4HandlerImpl.LQ_ROUTE_PROPERTY_NAME);
224 if (flag != null) {
225 learnRouteFromLeasequery = flag;
226 log.info("Learning routes from DHCP leasequery is {}",
227 learnRouteFromLeasequery ? "enabled" : "disabled");
228 }
229 }
230
Yi Tseng919b2df2017-09-07 16:22:51 -0700231 private void stopMonitoringIps(DhcpServerInfo serverInfo) {
232 serverInfo.getDhcpGatewayIp4().ifPresent(gatewayIp -> {
233 hostService.stopMonitoringIp(gatewayIp);
234 });
235 serverInfo.getDhcpServerIp4().ifPresent(serverIp -> {
236 hostService.stopMonitoringIp(serverIp);
237 });
Yi Tseng51301292017-07-28 13:02:59 -0700238 }
239
240 @Override
Yi Tseng483ac6f2017-08-02 15:03:31 -0700241 public void setDefaultDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
Yi Tseng919b2df2017-09-07 16:22:51 -0700242 setDhcpServerConfigs(configs, defaultServerInfoList);
243 }
244
245 @Override
246 public void setIndirectDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
247 setDhcpServerConfigs(configs, indirectServerInfoList);
248 }
249
250 @Override
251 public List<DhcpServerInfo> getDefaultDhcpServerInfoList() {
252 return defaultServerInfoList;
253 }
254
255 @Override
256 public List<DhcpServerInfo> getIndirectDhcpServerInfoList() {
257 return indirectServerInfoList;
258 }
259
Yi Tseng525ff402017-10-23 19:39:39 -0700260 @Override
261 public void updateIgnoreVlanConfig(IgnoreDhcpConfig config) {
262 if (config == null) {
263 ignoredVlans.forEach(((deviceId, vlanId) -> {
264 processIgnoreVlanRule(deviceId, vlanId, REMOVE);
265 }));
266 return;
267 }
268 config.ignoredVlans().forEach((deviceId, vlanId) -> {
269 if (ignoredVlans.get(deviceId).contains(vlanId)) {
270 // don't need to process if it already ignored
271 return;
272 }
273 processIgnoreVlanRule(deviceId, vlanId, ADD);
274 });
275
276 ignoredVlans.forEach((deviceId, vlanId) -> {
277 if (!config.ignoredVlans().get(deviceId).contains(vlanId)) {
278 // not contains in new config, remove it
279 processIgnoreVlanRule(deviceId, vlanId, REMOVE);
280 }
281 });
282 }
283
Saurav Dasb805f1a2017-12-13 16:19:35 -0800284 @Override
285 public void removeIgnoreVlanState(IgnoreDhcpConfig config) {
286 if (config == null) {
287 ignoredVlans.clear();
288 return;
289 }
290 config.ignoredVlans().forEach((deviceId, vlanId) -> {
291 ignoredVlans.remove(deviceId, vlanId);
292 });
293 }
294
Yi Tseng919b2df2017-09-07 16:22:51 -0700295 public void setDhcpServerConfigs(Collection<DhcpServerConfig> configs, List<DhcpServerInfo> serverInfoList) {
Yi Tseng483ac6f2017-08-02 15:03:31 -0700296 if (configs.size() == 0) {
297 // no config to update
298 return;
299 }
300
rsahot036620655b2018-02-26 15:01:37 -0500301 Boolean isConfigValid = false;
302 for (DhcpServerConfig serverConfig : configs) {
303 if (serverConfig.getDhcpServerIp4().isPresent()) {
304 isConfigValid = true;
305 break;
306 }
Yi Tseng483ac6f2017-08-02 15:03:31 -0700307 }
rsahot036620655b2018-02-26 15:01:37 -0500308 if (!isConfigValid) {
309 log.warn("No IP V4 server address found.");
310 return; // No IP V6 address found
311 }
312 // if (!serverInfoList.isEmpty()) {
313 for (DhcpServerInfo oldServerInfo : serverInfoList) {
314 log.info("In for (DhcpServerInfo oldServerInfo : serverInfoList) {");
Yi Tseng919b2df2017-09-07 16:22:51 -0700315 // remove old server info
rsahot036620655b2018-02-26 15:01:37 -0500316 //DhcpServerInfo oldServerInfo = serverInfoList.remove(0);
Yi Tseng483ac6f2017-08-02 15:03:31 -0700317
Yi Tseng919b2df2017-09-07 16:22:51 -0700318 // stop monitoring gateway or server
319 oldServerInfo.getDhcpGatewayIp4().ifPresent(gatewayIp -> {
320 hostService.stopMonitoringIp(gatewayIp);
321 });
322 oldServerInfo.getDhcpServerIp4().ifPresent(serverIp -> {
323 hostService.stopMonitoringIp(serverIp);
Yi Tseng525ff402017-10-23 19:39:39 -0700324 cancelDhcpPacket(serverIp);
Yi Tseng919b2df2017-09-07 16:22:51 -0700325 });
Yi Tseng483ac6f2017-08-02 15:03:31 -0700326 }
Yi Tseng3df7f9d2017-08-17 13:08:31 -0700327
Yi Tseng919b2df2017-09-07 16:22:51 -0700328 // Create new server info according to the config
rsahot036620655b2018-02-26 15:01:37 -0500329 serverInfoList.clear();
330 for (DhcpServerConfig serverConfig : configs) {
Taras Lemkin96a0d342018-03-26 14:52:58 +0000331 log.debug("Create new server info according to the config");
rsahot036620655b2018-02-26 15:01:37 -0500332 DhcpServerInfo newServerInfo = new DhcpServerInfo(serverConfig,
333 DhcpServerInfo.Version.DHCP_V4);
334 checkState(newServerInfo.getDhcpServerConnectPoint().isPresent(),
335 "Connect point not exists");
336 checkState(newServerInfo.getDhcpServerIp4().isPresent(),
337 "IP of DHCP server not exists");
Yi Tseng4f2a0462017-08-31 11:21:00 -0700338
rsahot036620655b2018-02-26 15:01:37 -0500339 log.debug("DHCP server connect point: {}", newServerInfo.getDhcpServerConnectPoint().orElse(null));
340 log.debug("DHCP server IP: {}", newServerInfo.getDhcpServerIp4().orElse(null));
Yi Tseng919b2df2017-09-07 16:22:51 -0700341
rsahot036620655b2018-02-26 15:01:37 -0500342 Ip4Address serverIp = newServerInfo.getDhcpServerIp4().get();
343 Ip4Address ipToProbe;
344 if (newServerInfo.getDhcpGatewayIp4().isPresent()) {
345 ipToProbe = newServerInfo.getDhcpGatewayIp4().get();
346 } else {
347 ipToProbe = newServerInfo.getDhcpServerIp4().orElse(null);
348 }
349 log.info("Probe_IP {}", ipToProbe);
350 String hostToProbe = newServerInfo.getDhcpGatewayIp4()
351 .map(ip -> "gateway").orElse("server");
352
353 log.debug("Probing to resolve {} IP {}", hostToProbe, ipToProbe);
354 hostService.startMonitoringIp(ipToProbe);
355
356 Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
357 if (!hosts.isEmpty()) {
358 Host host = hosts.iterator().next();
359 newServerInfo.setDhcpConnectVlan(host.vlan());
360 newServerInfo.setDhcpConnectMac(host.mac());
361 }
362
363 // Add new server info
364 synchronized (this) {
365 //serverInfoList.clear();
366 serverInfoList.add(newServerInfo);
367 }
368
369 requestDhcpPacket(serverIp);
Yi Tseng4f2a0462017-08-31 11:21:00 -0700370 }
Yi Tseng483ac6f2017-08-02 15:03:31 -0700371 }
372
Yi Tseng3df7f9d2017-08-17 13:08:31 -0700373 @Override
Yi Tseng51301292017-07-28 13:02:59 -0700374 public void processDhcpPacket(PacketContext context, BasePacket payload) {
375 checkNotNull(payload, "DHCP payload can't be null");
376 checkState(payload instanceof DHCP, "Payload is not a DHCP");
377 DHCP dhcpPayload = (DHCP) payload;
378 if (!configured()) {
Yi Tseng919b2df2017-09-07 16:22:51 -0700379 log.warn("Missing default DHCP relay server config. Abort packet processing");
Yi Tseng51301292017-07-28 13:02:59 -0700380 return;
381 }
382
383 ConnectPoint inPort = context.inPacket().receivedFrom();
Yi Tseng51301292017-07-28 13:02:59 -0700384 checkNotNull(dhcpPayload, "Can't find DHCP payload");
385 Ethernet packet = context.inPacket().parsed();
386 DHCP.MsgType incomingPacketType = dhcpPayload.getOptions().stream()
387 .filter(dhcpOption -> dhcpOption.getCode() == OptionCode_MessageType.getValue())
388 .map(DhcpOption::getData)
389 .map(data -> DHCP.MsgType.getType(data[0]))
390 .findFirst()
391 .orElse(null);
392 checkNotNull(incomingPacketType, "Can't get message type from DHCP payload {}", dhcpPayload);
rsahot036620655b2018-02-26 15:01:37 -0500393 Set<Interface> receivingInterfaces = interfaceService.getInterfacesByPort(inPort);
394 //ignore the packets if dhcp client interface is not configured on onos.
395 if (receivingInterfaces.isEmpty()) {
396 log.warn("Virtual interface is not configured on {}", inPort);
397 return;
398 }
Yi Tseng51301292017-07-28 13:02:59 -0700399 switch (incomingPacketType) {
400 case DHCPDISCOVER:
Yi Tsengdcef2c22017-08-05 20:34:06 -0700401 // Add the gateway IP as virtual interface IP for server to understand
Yi Tseng51301292017-07-28 13:02:59 -0700402 // the lease to be assigned and forward the packet to dhcp server.
rsahot036620655b2018-02-26 15:01:37 -0500403 List<InternalPacket> ethernetClientPacket =
404 processDhcpPacketFromClient(context, packet, receivingInterfaces);
405 for (InternalPacket internalPacket : ethernetClientPacket) {
406 log.debug("DHCPDISCOVER from {} Forward to server", inPort);
Yi Tseng51301292017-07-28 13:02:59 -0700407 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
rsahot036620655b2018-02-26 15:01:37 -0500408 forwardPacket(internalPacket);
Yi Tseng51301292017-07-28 13:02:59 -0700409 }
410 break;
411 case DHCPOFFER:
412 //reply to dhcp client.
Taras Lemkin96a0d342018-03-26 14:52:58 +0000413 InternalPacket ethernetPacketOffer = processDhcpPacketFromServer(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700414 if (ethernetPacketOffer != null) {
Taras Lemkin96a0d342018-03-26 14:52:58 +0000415 writeResponseDhcpRecord(ethernetPacketOffer.getPacket(), dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700416 sendResponseToClient(ethernetPacketOffer, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700417 }
418 break;
419 case DHCPREQUEST:
420 // add the gateway ip as virtual interface ip for server to understand
421 // the lease to be assigned and forward the packet to dhcp server.
rsahot036620655b2018-02-26 15:01:37 -0500422 List<InternalPacket> ethernetPacketRequest =
423 processDhcpPacketFromClient(context, packet, receivingInterfaces);
424 for (InternalPacket internalPacket : ethernetPacketRequest) {
425 log.debug("DHCPDISCOVER from {} Forward to server", inPort);
Yi Tseng51301292017-07-28 13:02:59 -0700426 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
rsahot036620655b2018-02-26 15:01:37 -0500427 forwardPacket(internalPacket);
Yi Tseng51301292017-07-28 13:02:59 -0700428 }
429 break;
Charles Chand0dd7002017-10-08 23:53:36 -0400430 case DHCPDECLINE:
431 break;
Yi Tseng51301292017-07-28 13:02:59 -0700432 case DHCPACK:
433 // reply to dhcp client.
Taras Lemkin96a0d342018-03-26 14:52:58 +0000434 InternalPacket ethernetPacketAck = processDhcpPacketFromServer(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700435 if (ethernetPacketAck != null) {
Taras Lemkin96a0d342018-03-26 14:52:58 +0000436 writeResponseDhcpRecord(ethernetPacketAck.getPacket(), dhcpPayload);
437 handleDhcpAck(ethernetPacketAck.getPacket(), dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700438 sendResponseToClient(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700439 }
440 break;
Charles Chand0dd7002017-10-08 23:53:36 -0400441 case DHCPNAK:
442 break;
Yi Tseng51301292017-07-28 13:02:59 -0700443 case DHCPRELEASE:
444 // TODO: release the ip address from client
445 break;
Charles Chand0dd7002017-10-08 23:53:36 -0400446 case DHCPINFORM:
447 break;
448 case DHCPFORCERENEW:
449 break;
450 case DHCPLEASEQUERY:
451 handleLeaseQueryMsg(context, packet, dhcpPayload);
452 break;
453 case DHCPLEASEACTIVE:
454 handleLeaseQueryActivateMsg(packet, dhcpPayload);
455 break;
456 case DHCPLEASEUNASSIGNED:
457 case DHCPLEASEUNKNOWN:
458 handleLeaseQueryUnknown(packet, dhcpPayload);
459 break;
Yi Tseng51301292017-07-28 13:02:59 -0700460 default:
461 break;
462 }
463 }
464
465 /**
466 * Checks if this app has been configured.
467 *
468 * @return true if all information we need have been initialized
469 */
Yi Tseng4f2a0462017-08-31 11:21:00 -0700470 private boolean configured() {
Yi Tseng919b2df2017-09-07 16:22:51 -0700471 return !defaultServerInfoList.isEmpty();
Yi Tseng51301292017-07-28 13:02:59 -0700472 }
473
474 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -0700475 * Returns the first interface ip from interface.
Yi Tseng51301292017-07-28 13:02:59 -0700476 *
Yi Tsengdcef2c22017-08-05 20:34:06 -0700477 * @param iface interface of one connect point
Yi Tseng51301292017-07-28 13:02:59 -0700478 * @return the first interface IP; null if not exists an IP address in
479 * these interfaces
480 */
Yi Tseng3df7f9d2017-08-17 13:08:31 -0700481 private Ip4Address getFirstIpFromInterface(Interface iface) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700482 checkNotNull(iface, "Interface can't be null");
483 return iface.ipAddressesList().stream()
Yi Tseng51301292017-07-28 13:02:59 -0700484 .map(InterfaceIpAddress::ipAddress)
485 .filter(IpAddress::isIp4)
486 .map(IpAddress::getIp4Address)
487 .findFirst()
488 .orElse(null);
489 }
490
491 /**
Yi Tseng4f2a0462017-08-31 11:21:00 -0700492 * Gets Interface facing to the server for default host.
Yi Tsengdcef2c22017-08-05 20:34:06 -0700493 *
494 * @return the Interface facing to the server; null if not found
495 */
Yi Tseng919b2df2017-09-07 16:22:51 -0700496 private Interface getDefaultServerInterface() {
497 return getServerInterface(defaultServerInfoList);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700498 }
499
500 /**
Yi Tseng4f2a0462017-08-31 11:21:00 -0700501 * Gets Interface facing to the server for indirect hosts.
502 * Use default server Interface if indirect server not configured.
503 *
504 * @return the Interface facing to the server; null if not found
505 */
506 private Interface getIndirectServerInterface() {
Yi Tseng919b2df2017-09-07 16:22:51 -0700507 return getServerInterface(indirectServerInfoList);
508 }
509
510 private Interface getServerInterface(List<DhcpServerInfo> serverInfos) {
Yi Tseng3bd57ac2017-11-29 14:39:18 -0800511 return serverInfos.stream()
Yi Tseng4f2a0462017-08-31 11:21:00 -0700512 .findFirst()
Yi Tseng3bd57ac2017-11-29 14:39:18 -0800513 .map(serverInfo -> {
514 ConnectPoint dhcpServerConnectPoint =
515 serverInfo.getDhcpServerConnectPoint().orElse(null);
516 VlanId dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
517 if (dhcpServerConnectPoint == null || dhcpConnectVlan == null) {
518 return null;
519 }
520 return interfaceService.getInterfacesByPort(dhcpServerConnectPoint)
521 .stream()
522 .filter(iface -> interfaceContainsVlan(iface, dhcpConnectVlan))
523 .findFirst()
524 .orElse(null);
525 })
Yi Tseng4f2a0462017-08-31 11:21:00 -0700526 .orElse(null);
527 }
528
529 /**
530 * Determind if an Interface contains a vlan id.
531 *
532 * @param iface the Interface
533 * @param vlanId the vlan id
534 * @return true if the Interface contains the vlan id
535 */
536 private boolean interfaceContainsVlan(Interface iface, VlanId vlanId) {
Yi Tseng4025a102017-09-30 11:35:42 +0800537 if (vlanId.equals(VlanId.NONE)) {
538 // untagged packet, check if vlan untagged or vlan native is not NONE
539 return !iface.vlanUntagged().equals(VlanId.NONE) ||
540 !iface.vlanNative().equals(VlanId.NONE);
541 }
542 // tagged packet, check if the interface contains the vlan
543 return iface.vlanTagged().contains(vlanId);
Yi Tseng4f2a0462017-08-31 11:21:00 -0700544 }
545
Charles Chand0dd7002017-10-08 23:53:36 -0400546 private void handleLeaseQueryActivateMsg(Ethernet packet, DHCP dhcpPayload) {
547 log.debug("LQ: Got DHCPLEASEACTIVE packet!");
548
Taras Lemkin96a0d342018-03-26 14:52:58 +0000549 if (learnRouteFromLeasequery) {
550 // TODO: release the ip address from client
551 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
552 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
553 HostId hostId = HostId.hostId(clientMacAddress, vlanId);
554 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
Charles Chand0dd7002017-10-08 23:53:36 -0400555
Taras Lemkin96a0d342018-03-26 14:52:58 +0000556 if (record == null) {
557 log.warn("Can't find record for host {} when processing DHCPLEASEACTIVE", hostId);
558 return;
559 }
560
561 // need to update routes
562 log.debug("Lease Query for Client results in DHCPLEASEACTIVE - route needs to be modified");
563 // get current route
564 // find the ip of that client with the DhcpRelay store
565
566 Ip4Address clientIP = record.ip4Address().orElse(null);
567 log.debug("LQ: IP of host is " + clientIP.getIp4Address());
568
569 MacAddress nextHopMac = record.nextHop().orElse(null);
570 log.debug("LQ: MAC of resulting *OLD* NH for that host is " + nextHopMac.toString());
571
572 // find the new NH by looking at the src MAC of the dhcp request
573 // from the LQ store
574 MacAddress newNextHopMac = record.nextHopTemp().orElse(null);
575 log.debug("LQ: MAC of resulting *NEW* NH for that host is " + newNextHopMac.toString());
576
577 log.debug("LQ: updating dhcp relay record with new NH");
578 record.nextHop(newNextHopMac);
579
580 // find the next hop IP from its mac
581 HostId gwHostId = HostId.hostId(newNextHopMac, vlanId);
582 Host gwHost = hostService.getHost(gwHostId);
583
584 if (gwHost == null) {
585 log.warn("Can't find gateway for new NH host " + gwHostId);
586 return;
587 }
588
589 Ip4Address nextHopIp = gwHost.ipAddresses()
590 .stream()
591 .filter(IpAddress::isIp4)
592 .map(IpAddress::getIp4Address)
593 .findFirst()
594 .orElse(null);
595
596 if (nextHopIp == null) {
597 log.warn("Can't find IP address of gateway " + gwHost);
598 return;
599 }
600
601 log.debug("LQ: *NEW* NH IP for host is " + nextHopIp.getIp4Address());
602 Route route = new Route(Route.Source.DHCP, clientIP.toIpPrefix(), nextHopIp);
603 routeStore.updateRoute(route);
Charles Chand0dd7002017-10-08 23:53:36 -0400604 }
605
Charles Chand0dd7002017-10-08 23:53:36 -0400606 // and forward to client
Taras Lemkin96a0d342018-03-26 14:52:58 +0000607 InternalPacket ethernetPacket = processLeaseQueryFromServer(packet);
Charles Chand0dd7002017-10-08 23:53:36 -0400608 if (ethernetPacket != null) {
609 sendResponseToClient(ethernetPacket, dhcpPayload);
610 }
611 }
612
Charles Chand0dd7002017-10-08 23:53:36 -0400613 private void handleLeaseQueryMsg(PacketContext context, Ethernet packet, DHCP dhcpPayload) {
Taras Lemkin96a0d342018-03-26 14:52:58 +0000614 // If this flag is enabled we expect that DHCPLEASEQUERY-ies are sent from an access concentrator
615 // where queried client is connected to. Otherwise, DHCPLEASEQUERY source may be a separate connected agent
616 if (learnRouteFromLeasequery) {
617 log.debug("LQ: Got DHCPLEASEQUERY packet!");
618 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
619 log.debug("LQ: got DHCPLEASEQUERY with MAC " + clientMacAddress.toString());
620 // add the client mac (hostid) of this request to a store (the entry will be removed with
621 // the reply sent to the originator)
622 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
623 HostId hId = HostId.hostId(clientMacAddress, vlanId);
624 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hId).orElse(null);
625 if (record != null) {
626 //new NH is to be taken from src mac of LQ packet
627 MacAddress newNextHop = packet.getSourceMAC();
628 record.nextHopTemp(newNextHop);
629 record.ip4Status(dhcpPayload.getPacketType());
630 record.updateLastSeen();
631
632 // do a basic routing of the packet (this is unicast routing
633 // not a relay operation like for other broadcast dhcp packets
634 List<InternalPacket> ethernetPacketRequest = processLeaseQueryFromAgent(context, packet);
635 // and forward to server
636 for (InternalPacket internalPacket : ethernetPacketRequest) {
637 log.debug("LeaseQueryMsg forward to server");
638 forwardPacket(internalPacket);
639 }
640 } else {
641 log.warn("LQ: Error! - DHCP relay record for that client not found - ignoring LQ!");
642 }
643 } else {
644 log.debug("LQ: Got DHCPLEASEQUERY packet!");
645
646 int giaddr = dhcpPayload.getGatewayIPAddress();
647
648 log.debug("DHCPLEASEQUERY giaddr: {} ({}). Originators connectPoint: {}", giaddr,
649 Ip4Address.valueOf(giaddr), context.inPacket().receivedFrom());
Charles Chand0dd7002017-10-08 23:53:36 -0400650
651 // do a basic routing of the packet (this is unicast routing
652 // not a relay operation like for other broadcast dhcp packets
rsahot036620655b2018-02-26 15:01:37 -0500653 List<InternalPacket> ethernetPacketRequest = processLeaseQueryFromAgent(context, packet);
Charles Chand0dd7002017-10-08 23:53:36 -0400654 // and forward to server
rsahot036620655b2018-02-26 15:01:37 -0500655 for (InternalPacket internalPacket : ethernetPacketRequest) {
Taras Lemkin96a0d342018-03-26 14:52:58 +0000656 log.trace("LeaseQueryMsg forward to server connected to {}", internalPacket.getDestLocation());
rsahot036620655b2018-02-26 15:01:37 -0500657 forwardPacket(internalPacket);
658 }
Charles Chand0dd7002017-10-08 23:53:36 -0400659 }
660 }
661
662 private void handleLeaseQueryUnknown(Ethernet packet, DHCP dhcpPayload) {
663 log.debug("Lease Query for Client results in DHCPLEASEUNASSIGNED or " +
664 "DHCPLEASEUNKNOWN - removing route & forwarding reply to originator");
Taras Lemkin96a0d342018-03-26 14:52:58 +0000665 if (learnRouteFromLeasequery) {
666 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
667 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
668 HostId hostId = HostId.hostId(clientMacAddress, vlanId);
669 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
Charles Chand0dd7002017-10-08 23:53:36 -0400670
Taras Lemkin96a0d342018-03-26 14:52:58 +0000671 if (record == null) {
672 log.warn("Can't find record for host {} when handling LQ UNKNOWN/UNASSIGNED message", hostId);
673 return;
674 }
675
676 Ip4Address clientIP = record.ip4Address().orElse(null);
677 log.debug("LQ: IP of host is " + clientIP.getIp4Address());
678
679 // find the new NH by looking at the src MAC of the dhcp request
680 // from the LQ store
681 MacAddress nextHopMac = record.nextHop().orElse(null);
682 log.debug("LQ: MAC of resulting *Existing* NH for that route is " + nextHopMac.toString());
683
684 // find the next hop IP from its mac
685 HostId gwHostId = HostId.hostId(nextHopMac, vlanId);
686 Host gwHost = hostService.getHost(gwHostId);
687
688 if (gwHost == null) {
689 log.warn("Can't find gateway for new NH host " + gwHostId);
690 return;
691 }
692
693 Ip4Address nextHopIp = gwHost.ipAddresses()
694 .stream()
695 .filter(IpAddress::isIp4)
696 .map(IpAddress::getIp4Address)
697 .findFirst()
698 .orElse(null);
699
700 if (nextHopIp == null) {
701 log.warn("Can't find IP address of gateway {}", gwHost);
702 return;
703 }
704
705 log.debug("LQ: *Existing* NH IP for host is " + nextHopIp.getIp4Address() + " removing route for it");
706 Route route = new Route(Route.Source.DHCP, clientIP.toIpPrefix(), nextHopIp);
707 routeStore.removeRoute(route);
708
709 // remove from temp store
710 dhcpRelayStore.removeDhcpRecord(hostId);
Charles Chand0dd7002017-10-08 23:53:36 -0400711 }
Charles Chand0dd7002017-10-08 23:53:36 -0400712 // and forward to client
Taras Lemkin96a0d342018-03-26 14:52:58 +0000713 InternalPacket ethernetPacket = processLeaseQueryFromServer(packet);
Charles Chand0dd7002017-10-08 23:53:36 -0400714 if (ethernetPacket != null) {
715 sendResponseToClient(ethernetPacket, dhcpPayload);
716 }
717 }
718
Yi Tseng4f2a0462017-08-31 11:21:00 -0700719 /**
Yi Tseng51301292017-07-28 13:02:59 -0700720 * Build the DHCP discover/request packet with gateway IP(unicast packet).
721 *
722 * @param context the packet context
723 * @param ethernetPacket the ethernet payload to process
Yi Tseng51301292017-07-28 13:02:59 -0700724 * @return processed packet
725 */
rsahot036620655b2018-02-26 15:01:37 -0500726 private List<InternalPacket> processDhcpPacketFromClient(PacketContext context,
727 Ethernet ethernetPacket,
728 Set<Interface> clientInterfaces) {
Yi Tseng25bfe372017-11-03 16:27:32 -0700729 ConnectPoint receivedFrom = context.inPacket().receivedFrom();
730 DeviceId receivedFromDevice = receivedFrom.deviceId();
rsahot036620655b2018-02-26 15:01:37 -0500731 Ip4Address relayAgentIp = null;
Taras Lemkin96a0d342018-03-26 14:52:58 +0000732 relayAgentIp = Dhcp4HandlerUtil.getRelayAgentIPv4Address(clientInterfaces);
rsahot036620655b2018-02-26 15:01:37 -0500733 MacAddress relayAgentMac = clientInterfaces.iterator().next().mac();
734 if (relayAgentIp == null || relayAgentMac == null) {
735 log.warn("Missing DHCP relay agent interface Ipv4 addr config for "
736 + "packet from client on port: {}. Aborting packet processing",
737 clientInterfaces.iterator().next().connectPoint());
Charles Chane5e0c9a2018-03-30 12:11:34 -0700738 return Lists.newArrayList();
rsahot036620655b2018-02-26 15:01:37 -0500739 }
740 log.debug("Multi DHCP V4 processDhcpPacketFromClient on port {}",
741 clientInterfaces.iterator().next().connectPoint());
Yi Tseng25bfe372017-11-03 16:27:32 -0700742
Yi Tseng4f2a0462017-08-31 11:21:00 -0700743 // get dhcp header.
rsahot036620655b2018-02-26 15:01:37 -0500744 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
Yi Tseng4f2a0462017-08-31 11:21:00 -0700745 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
746 UDP udpPacket = (UDP) ipv4Packet.getPayload();
747 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
748
Yi Tseng919b2df2017-09-07 16:22:51 -0700749
Yi Tsengdcef2c22017-08-05 20:34:06 -0700750 Ip4Address clientInterfaceIp =
751 interfaceService.getInterfacesByPort(context.inPacket().receivedFrom())
752 .stream()
753 .map(Interface::ipAddressesList)
754 .flatMap(Collection::stream)
755 .map(InterfaceIpAddress::ipAddress)
756 .filter(IpAddress::isIp4)
757 .map(IpAddress::getIp4Address)
758 .findFirst()
759 .orElse(null);
760 if (clientInterfaceIp == null) {
761 log.warn("Can't find interface IP for client interface for port {}",
rsahot036620655b2018-02-26 15:01:37 -0500762 context.inPacket().receivedFrom());
Charles Chane5e0c9a2018-03-30 12:11:34 -0700763 return Lists.newArrayList();
Yi Tsengdcef2c22017-08-05 20:34:06 -0700764 }
rsahot036620655b2018-02-26 15:01:37 -0500765
Yi Tseng4f2a0462017-08-31 11:21:00 -0700766 boolean isDirectlyConnected = directlyConnected(dhcpPacket);
rsahot036620655b2018-02-26 15:01:37 -0500767 boolean directConnFlag = directlyConnected(dhcpPacket);
768
769 // Multi DHCP Start
770 ConnectPoint clientConnectionPoint = context.inPacket().receivedFrom();
771 VlanId vlanIdInUse = VlanId.vlanId(ethernetPacket.getVlanID());
772 Interface clientInterface = interfaceService.getInterfacesByPort(clientConnectionPoint)
Taras Lemkin96a0d342018-03-26 14:52:58 +0000773 .stream().filter(iface -> Dhcp4HandlerUtil.interfaceContainsVlan(iface, vlanIdInUse))
rsahot036620655b2018-02-26 15:01:37 -0500774 .findFirst()
775 .orElse(null);
776
777 List<InternalPacket> internalPackets = new ArrayList<>();
778 List<DhcpServerInfo> serverInfoList = findValidServerInfo(directConnFlag);
779 List<DhcpServerInfo> copyServerInfoList = new ArrayList<DhcpServerInfo>(serverInfoList);
780
781
782 for (DhcpServerInfo serverInfo : copyServerInfoList) {
783 etherReply = (Ethernet) ethernetPacket.clone();
Taras Lemkin96a0d342018-03-26 14:52:58 +0000784 ipv4Packet = (IPv4) etherReply.getPayload();
785 udpPacket = (UDP) ipv4Packet.getPayload();
786 dhcpPacket = (DHCP) udpPacket.getPayload();
rsahot036620655b2018-02-26 15:01:37 -0500787 if (!checkDhcpServerConnPt(directConnFlag, serverInfo)) {
788 log.warn("Can't get server connect point, ignore");
789 continue;
790 }
791 DhcpServerInfo newServerInfo = getHostInfoForServerInfo(serverInfo, serverInfoList);
792 if (newServerInfo == null) {
793 log.warn("Can't get server interface with host info resolved, ignore");
794 continue;
795 }
796
797 Interface serverInterface = getServerInterface(newServerInfo);
Yi Tseng3bd57ac2017-11-29 14:39:18 -0800798 if (serverInterface == null) {
rsahot036620655b2018-02-26 15:01:37 -0500799 log.warn("Can't get server interface, ignore");
800 continue;
Yi Tseng3bd57ac2017-11-29 14:39:18 -0800801 }
Yi Tseng4f2a0462017-08-31 11:21:00 -0700802
rsahot036620655b2018-02-26 15:01:37 -0500803 Ip4Address ipFacingServer = getFirstIpFromInterface(serverInterface);
804 MacAddress macFacingServer = serverInterface.mac();
805 log.debug("Interfacing server {} Mac : {} ", ipFacingServer, macFacingServer);
806 if (ipFacingServer == null || macFacingServer == null) {
807 log.warn("No IP address for server Interface {}", serverInterface);
rsahot036620655b2018-02-26 15:01:37 -0500808 continue;
809 }
Yi Tseng51301292017-07-28 13:02:59 -0700810
Charles Chand0d1e332017-10-10 16:53:32 -0400811
rsahot036620655b2018-02-26 15:01:37 -0500812 etherReply.setSourceMACAddress(macFacingServer);
813 // set default info and replace with indirect if available later on.
814 if (newServerInfo.getDhcpConnectMac().isPresent()) {
815 etherReply.setDestinationMACAddress(newServerInfo.getDhcpConnectMac().get());
816 }
817 if (newServerInfo.getDhcpConnectVlan().isPresent()) {
818 etherReply.setVlanID(newServerInfo.getDhcpConnectVlan().get().toShort());
819 }
820 ipv4Packet.setSourceAddress(ipFacingServer.toInt());
821 ipv4Packet.setDestinationAddress(newServerInfo.getDhcpServerIp4().get().toInt());
Charles Chan2de55302018-03-02 18:27:59 -0800822 log.debug("Directly connected {}", isDirectlyConnected);
823 log.debug("DHCP server IP: {}", newServerInfo.getDhcpServerIp4().get());
rsahot036620655b2018-02-26 15:01:37 -0500824 if (isDirectlyConnected) {
Yi Tseng51301292017-07-28 13:02:59 -0700825
Charles Chan2de55302018-03-02 18:27:59 -0800826 log.debug("Default DHCP server IP: {}", newServerInfo.getDhcpServerIp4().get());
rsahot036620655b2018-02-26 15:01:37 -0500827 if (newServerInfo.getDhcpConnectMac().isPresent()) {
828 etherReply.setDestinationMACAddress(newServerInfo.getDhcpConnectMac().get());
Charles Chand0d1e332017-10-10 16:53:32 -0400829 }
rsahot036620655b2018-02-26 15:01:37 -0500830 if (newServerInfo.getDhcpConnectVlan().isPresent()) {
831 etherReply.setVlanID(newServerInfo.getDhcpConnectVlan().get().toShort());
832 }
833
834 ipv4Packet.setDestinationAddress(newServerInfo.getDhcpServerIp4().get().toInt());
835
836
837 ConnectPoint inPort = context.inPacket().receivedFrom();
838 VlanId vlanId = VlanId.vlanId(ethernetPacket.getVlanID());
839 // add connected in port and vlan
840 CircuitId cid = new CircuitId(inPort.toString(), vlanId);
841 byte[] circuitId = cid.serialize();
842 DhcpOption circuitIdSubOpt = new DhcpOption();
843 circuitIdSubOpt
844 .setCode(CIRCUIT_ID.getValue())
845 .setLength((byte) circuitId.length)
846 .setData(circuitId);
847
848 DhcpRelayAgentOption newRelayAgentOpt = new DhcpRelayAgentOption();
849 newRelayAgentOpt.setCode(OptionCode_CircuitID.getValue());
850 newRelayAgentOpt.addSubOption(circuitIdSubOpt);
851
852 // Removes END option first
853 List<DhcpOption> options = dhcpPacket.getOptions().stream()
854 .filter(opt -> opt.getCode() != OptionCode_END.getValue())
855 .collect(Collectors.toList());
856
857 // push relay agent option
858 options.add(newRelayAgentOpt);
859
860 // make sure option 255(End) is the last option
861 DhcpOption endOption = new DhcpOption();
862 endOption.setCode(OptionCode_END.getValue());
863 options.add(endOption);
864
865 dhcpPacket.setOptions(options);
866
867 relayAgentIp = serverInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
868
869 // Sets relay agent IP
870 int effectiveRelayAgentIp = relayAgentIp != null ?
871 relayAgentIp.toInt() : clientInterfaceIp.toInt();
872 dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
Charles Chan2de55302018-03-02 18:27:59 -0800873 log.debug("In Default, Relay Agent IP {}", effectiveRelayAgentIp);
Charles Chand0d1e332017-10-10 16:53:32 -0400874 } else {
rsahot036620655b2018-02-26 15:01:37 -0500875 if (!newServerInfo.getDhcpServerIp4().isPresent()) {
876 // do nothing
877 } else if (!newServerInfo.getDhcpConnectMac().isPresent()) {
878 continue;
879 } else {
880 relayAgentIp = newServerInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
881 // Sets relay agent IP
882 int effectiveRelayAgentIp = relayAgentIp != null ?
883 relayAgentIp.toInt() : clientInterfaceIp.toInt();
Mayank Tiwari30149832018-10-19 12:12:44 -0400884 Ip4Address effectiveRealRealyAgentIP = relayAgentIp != null ?
885 relayAgentIp : clientInterfaceIp;
rsahot036620655b2018-02-26 15:01:37 -0500886 dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
Mayank Tiwari30149832018-10-19 12:12:44 -0400887 ipv4Packet.setSourceAddress(effectiveRealRealyAgentIP.toInt());
888 log.debug("Source IP address set as relay agent IP with value: {}", effectiveRealRealyAgentIP);
Charles Chand0d1e332017-10-10 16:53:32 -0400889 }
Yi Tseng4f2a0462017-08-31 11:21:00 -0700890 }
Yi Tseng3df7f9d2017-08-17 13:08:31 -0700891
rsahot036620655b2018-02-26 15:01:37 -0500892 // Remove broadcast flag
893 dhcpPacket.setFlags((short) 0);
894
895 udpPacket.setPayload(dhcpPacket);
896 // As a DHCP relay, the source port should be server port( instead
897 // of client port.
898 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
899 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
900 ipv4Packet.setPayload(udpPacket);
901 ipv4Packet.setTtl((byte) 64);
902 etherReply.setPayload(ipv4Packet);
Taras Lemkin96a0d342018-03-26 14:52:58 +0000903 InternalPacket internalPacket = InternalPacket.internalPacket(etherReply,
rsahot036620655b2018-02-26 15:01:37 -0500904 serverInfo.getDhcpServerConnectPoint().get());
Taras Lemkin96a0d342018-03-26 14:52:58 +0000905
rsahot036620655b2018-02-26 15:01:37 -0500906 internalPackets.add(internalPacket);
907 }
908 return internalPackets;
Yi Tseng51301292017-07-28 13:02:59 -0700909 }
910
Charles Chand0dd7002017-10-08 23:53:36 -0400911
912 /**
913 * Do a basic routing for a packet from client (used for LQ processing).
914 *
915 * @param context the packet context
916 * @param ethernetPacket the ethernet payload to process
917 * @return processed packet
918 */
rsahot036620655b2018-02-26 15:01:37 -0500919 private List<InternalPacket> processLeaseQueryFromAgent(PacketContext context,
920 Ethernet ethernetPacket) {
921 ConnectPoint receivedFrom = context.inPacket().receivedFrom();
922 DeviceId receivedFromDevice = receivedFrom.deviceId();
923
Charles Chand0dd7002017-10-08 23:53:36 -0400924 // get dhcp header.
925 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
926 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
927 UDP udpPacket = (UDP) ipv4Packet.getPayload();
928 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
929
Charles Chan2de55302018-03-02 18:27:59 -0800930 Ip4Address relayAgentIp;
Charles Chand0dd7002017-10-08 23:53:36 -0400931
Charles Chand0dd7002017-10-08 23:53:36 -0400932 Ip4Address clientInterfaceIp =
933 interfaceService.getInterfacesByPort(context.inPacket().receivedFrom())
934 .stream()
935 .map(Interface::ipAddressesList)
936 .flatMap(Collection::stream)
937 .map(InterfaceIpAddress::ipAddress)
938 .filter(IpAddress::isIp4)
939 .map(IpAddress::getIp4Address)
940 .findFirst()
941 .orElse(null);
942 if (clientInterfaceIp == null) {
943 log.warn("Can't find interface IP for client interface for port {}",
rsahot036620655b2018-02-26 15:01:37 -0500944 context.inPacket().receivedFrom());
Charles Chand0dd7002017-10-08 23:53:36 -0400945 return null;
946 }
rsahot036620655b2018-02-26 15:01:37 -0500947
Charles Chand0dd7002017-10-08 23:53:36 -0400948 boolean isDirectlyConnected = directlyConnected(dhcpPacket);
rsahot036620655b2018-02-26 15:01:37 -0500949 boolean directConnFlag = directlyConnected(dhcpPacket);
950
951 // Multi DHCP Start
rsahot036620655b2018-02-26 15:01:37 -0500952 List<InternalPacket> internalPackets = new ArrayList<>();
953 List<DhcpServerInfo> serverInfoList = findValidServerInfo(directConnFlag);
Charles Chan2de55302018-03-02 18:27:59 -0800954 List<DhcpServerInfo> copyServerInfoList = new ArrayList<>(serverInfoList);
rsahot036620655b2018-02-26 15:01:37 -0500955
956 for (DhcpServerInfo serverInfo : copyServerInfoList) {
957 // get dhcp header.
958 etherReply = (Ethernet) ethernetPacket.clone();
959 ipv4Packet = (IPv4) etherReply.getPayload();
960 udpPacket = (UDP) ipv4Packet.getPayload();
961 dhcpPacket = (DHCP) udpPacket.getPayload();
962
963 if (!checkDhcpServerConnPt(directConnFlag, serverInfo)) {
964 log.warn("Can't get server connect point, ignore");
965 continue;
Yi Tseng3bd57ac2017-11-29 14:39:18 -0800966 }
rsahot036620655b2018-02-26 15:01:37 -0500967 DhcpServerInfo newServerInfo = getHostInfoForServerInfo(serverInfo, serverInfoList);
968 if (newServerInfo == null) {
969 log.warn("Can't get server interface with host info resolved, ignore");
970 continue;
971 }
Charles Chand0dd7002017-10-08 23:53:36 -0400972
rsahot036620655b2018-02-26 15:01:37 -0500973 Interface serverInterface = getServerInterface(newServerInfo);
974 if (serverInterface == null) {
975 log.warn("Can't get server interface, ignore");
976 continue;
977 }
978 Ip4Address ipFacingServer = getFirstIpFromInterface(serverInterface);
979 MacAddress macFacingServer = serverInterface.mac();
980 if (ipFacingServer == null || macFacingServer == null) {
981 log.warn("No IP address for server Interface {}", serverInterface);
982 continue;
983 }
Charles Chand0dd7002017-10-08 23:53:36 -0400984
rsahot036620655b2018-02-26 15:01:37 -0500985 etherReply.setSourceMACAddress(macFacingServer);
Charles Chan2de55302018-03-02 18:27:59 -0800986 etherReply.setDestinationMACAddress(newServerInfo.getDhcpConnectMac().get());
987 etherReply.setVlanID(newServerInfo.getDhcpConnectVlan().get().toShort());
rsahot036620655b2018-02-26 15:01:37 -0500988 ipv4Packet.setSourceAddress(ipFacingServer.toInt());
Charles Chan2de55302018-03-02 18:27:59 -0800989 ipv4Packet.setDestinationAddress(newServerInfo.getDhcpServerIp4().get().toInt());
rsahot036620655b2018-02-26 15:01:37 -0500990 if (isDirectlyConnected) {
991 // set default info and replace with indirect if available later on.
992 if (newServerInfo.getDhcpConnectMac().isPresent()) {
993 etherReply.setDestinationMACAddress(newServerInfo.getDhcpConnectMac().get());
994 }
995 if (newServerInfo.getDhcpConnectVlan().isPresent()) {
996 etherReply.setVlanID(serverInfo.getDhcpConnectVlan().get().toShort());
997 }
Taras Lemkin96a0d342018-03-26 14:52:58 +0000998 if (learnRouteFromLeasequery) {
rsahot036620655b2018-02-26 15:01:37 -0500999 relayAgentIp = newServerInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
1000 // Sets relay agent IP
1001 int effectiveRelayAgentIp = relayAgentIp != null ?
1002 relayAgentIp.toInt() : clientInterfaceIp.toInt();
1003 dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
Taras Lemkin96a0d342018-03-26 14:52:58 +00001004 }
1005 } else {
1006 if (!newServerInfo.getDhcpServerIp4().isPresent()) {
1007 //do nothing
1008 } else if (!newServerInfo.getDhcpConnectMac().isPresent()) {
1009 continue;
1010 } else if (learnRouteFromLeasequery) {
1011 relayAgentIp = newServerInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
1012 // Sets relay agent IP
1013 int effectiveRelayAgentIp = relayAgentIp != null ?
1014 relayAgentIp.toInt() : clientInterfaceIp.toInt();
rsahot036620655b2018-02-26 15:01:37 -05001015 dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
Taras Lemkin96a0d342018-03-26 14:52:58 +00001016 log.debug("Relay Agent IP {}", relayAgentIp);
rsahot036620655b2018-02-26 15:01:37 -05001017 }
1018
Taras Lemkin96a0d342018-03-26 14:52:58 +00001019 log.trace("Indirect");
rsahot036620655b2018-02-26 15:01:37 -05001020 }
1021
1022 // Remove broadcast flag
1023 dhcpPacket.setFlags((short) 0);
1024
1025 udpPacket.setPayload(dhcpPacket);
1026 // As a DHCP relay, the source port should be server port( instead
1027 // of client port.
1028 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
1029 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
1030 ipv4Packet.setPayload(udpPacket);
1031 ipv4Packet.setTtl((byte) 64);
1032 etherReply.setPayload(ipv4Packet);
Taras Lemkin96a0d342018-03-26 14:52:58 +00001033 udpPacket.resetChecksum();
rsahot036620655b2018-02-26 15:01:37 -05001034 ////return etherReply;
Taras Lemkin96a0d342018-03-26 14:52:58 +00001035 InternalPacket internalPacket = InternalPacket.internalPacket(etherReply,
rsahot036620655b2018-02-26 15:01:37 -05001036 newServerInfo.getDhcpServerConnectPoint().get());
Taras Lemkin96a0d342018-03-26 14:52:58 +00001037
rsahot036620655b2018-02-26 15:01:37 -05001038 internalPackets.add(internalPacket);
Charles Chand0dd7002017-10-08 23:53:36 -04001039 }
Taras Lemkin96a0d342018-03-26 14:52:58 +00001040 log.debug("num of processLeaseQueryFromAgent packets to send is: {}", internalPackets.size());
Charles Chand0dd7002017-10-08 23:53:36 -04001041
rsahot036620655b2018-02-26 15:01:37 -05001042 return internalPackets;
Charles Chand0dd7002017-10-08 23:53:36 -04001043 }
1044
1045
Yi Tseng51301292017-07-28 13:02:59 -07001046 /**
1047 * Writes DHCP record to the store according to the request DHCP packet (Discover, Request).
1048 *
1049 * @param location the location which DHCP packet comes from
1050 * @param ethernet the DHCP packet
1051 * @param dhcpPayload the DHCP payload
1052 */
1053 private void writeRequestDhcpRecord(ConnectPoint location,
1054 Ethernet ethernet,
1055 DHCP dhcpPayload) {
1056 VlanId vlanId = VlanId.vlanId(ethernet.getVlanID());
1057 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1058 HostId hostId = HostId.hostId(macAddress, vlanId);
1059 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1060 if (record == null) {
1061 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
1062 } else {
1063 record = record.clone();
1064 }
1065 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
1066 record.ip4Status(dhcpPayload.getPacketType());
1067 record.setDirectlyConnected(directlyConnected(dhcpPayload));
1068 if (!directlyConnected(dhcpPayload)) {
1069 // Update gateway mac address if the host is not directly connected
1070 record.nextHop(ethernet.getSourceMAC());
1071 }
1072 record.updateLastSeen();
1073 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
1074 }
1075
1076 /**
1077 * Writes DHCP record to the store according to the response DHCP packet (Offer, Ack).
1078 *
1079 * @param ethernet the DHCP packet
1080 * @param dhcpPayload the DHCP payload
1081 */
1082 private void writeResponseDhcpRecord(Ethernet ethernet,
1083 DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001084 Optional<Interface> outInterface = getClientInterface(ethernet, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -07001085 if (!outInterface.isPresent()) {
1086 log.warn("Failed to determine where to send {}", dhcpPayload.getPacketType());
1087 return;
1088 }
1089
1090 Interface outIface = outInterface.get();
1091 ConnectPoint location = outIface.connectPoint();
Yi Tseng4f2a0462017-08-31 11:21:00 -07001092 VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001093 if (vlanId == null) {
1094 vlanId = outIface.vlan();
1095 }
Yi Tseng51301292017-07-28 13:02:59 -07001096 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1097 HostId hostId = HostId.hostId(macAddress, vlanId);
1098 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1099 if (record == null) {
1100 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
1101 } else {
1102 record = record.clone();
1103 }
1104 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
1105 if (dhcpPayload.getPacketType() == DHCP.MsgType.DHCPACK) {
1106 record.ip4Address(Ip4Address.valueOf(dhcpPayload.getYourIPAddress()));
1107 }
1108 record.ip4Status(dhcpPayload.getPacketType());
1109 record.setDirectlyConnected(directlyConnected(dhcpPayload));
1110 record.updateLastSeen();
1111 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
1112 }
1113
1114 /**
1115 * Build the DHCP offer/ack with proper client port.
1116 *
1117 * @param ethernetPacket the original packet comes from server
1118 * @return new packet which will send to the client
1119 */
Taras Lemkin96a0d342018-03-26 14:52:58 +00001120 private InternalPacket processDhcpPacketFromServer(PacketContext context, Ethernet ethernetPacket) {
Yi Tseng51301292017-07-28 13:02:59 -07001121 // get dhcp header.
rsahot036620655b2018-02-26 15:01:37 -05001122 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
Yi Tseng51301292017-07-28 13:02:59 -07001123 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
1124 UDP udpPacket = (UDP) ipv4Packet.getPayload();
1125 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
1126
1127 // determine the vlanId of the client host - note that this vlan id
1128 // could be different from the vlan in the packet from the server
Yi Tsengdcef2c22017-08-05 20:34:06 -07001129 Interface clientInterface = getClientInterface(ethernetPacket, dhcpPayload).orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -07001130
Yi Tsengdcef2c22017-08-05 20:34:06 -07001131 if (clientInterface == null) {
Yi Tseng51301292017-07-28 13:02:59 -07001132 log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
1133 return null;
1134 }
Yi Tsengdcef2c22017-08-05 20:34:06 -07001135 VlanId vlanId;
rsahot036620655b2018-02-26 15:01:37 -05001136 ConnectPoint inPort = context.inPacket().receivedFrom();
1137 boolean directConnFlag = directlyConnected(dhcpPayload);
1138 DhcpServerInfo foundServerInfo = findServerInfoFromServer(directConnFlag, inPort);
1139
1140 if (foundServerInfo == null) {
jayakumarthazhathed5f05d2018-09-25 15:56:51 -04001141 log.warn("Cannot find server info for {} server, inPort {}",
1142 directConnFlag ? "direct" : "indirect", inPort);
rsahot036620655b2018-02-26 15:01:37 -05001143 return null;
1144 } else {
Taras Lemkin96a0d342018-03-26 14:52:58 +00001145 if (Dhcp4HandlerUtil.isServerIpEmpty(foundServerInfo)) {
rsahot036620655b2018-02-26 15:01:37 -05001146 log.warn("Cannot find server info's ipaddress");
1147 return null;
1148 }
1149 }
Yi Tsengdcef2c22017-08-05 20:34:06 -07001150 if (clientInterface.vlanTagged().isEmpty()) {
1151 vlanId = clientInterface.vlan();
1152 } else {
1153 // might be multiple vlan in same interface
Yi Tseng4f2a0462017-08-31 11:21:00 -07001154 vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001155 }
1156 if (vlanId == null) {
1157 vlanId = VlanId.NONE;
1158 }
1159 etherReply.setVlanID(vlanId.toShort());
1160 etherReply.setSourceMACAddress(clientInterface.mac());
Yi Tseng51301292017-07-28 13:02:59 -07001161
Yi Tsengdcef2c22017-08-05 20:34:06 -07001162 if (!directlyConnected(dhcpPayload)) {
1163 // if client is indirectly connected, try use next hop mac address
1164 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1165 HostId hostId = HostId.hostId(macAddress, vlanId);
Daniel Ginsburgb1dec912018-01-29 11:40:22 -08001166 if (((int) dhcpPayload.getFlags() & 0x8000) == 0x0000) {
1167 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1168 if (record != null) {
1169 // if next hop can be found, use mac address of next hop
1170 record.nextHop().ifPresent(etherReply::setDestinationMACAddress);
1171 } else {
1172 // otherwise, discard the packet
1173 log.warn("Can't find record for host id {}, discard packet", hostId);
1174 return null;
1175 }
Yi Tsengdcef2c22017-08-05 20:34:06 -07001176 } else {
Daniel Ginsburgb1dec912018-01-29 11:40:22 -08001177 etherReply.setDestinationMACAddress(MacAddress.BROADCAST);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001178 }
Yi Tseng1696f562017-08-17 17:43:38 -07001179 } else {
1180 etherReply.setDestinationMACAddress(dhcpPayload.getClientHardwareAddress());
Yi Tsengdcef2c22017-08-05 20:34:06 -07001181 }
1182
Yi Tseng51301292017-07-28 13:02:59 -07001183 // we leave the srcMac from the original packet
Yi Tseng51301292017-07-28 13:02:59 -07001184 // figure out the relay agent IP corresponding to the original request
Yi Tseng3df7f9d2017-08-17 13:08:31 -07001185 Ip4Address ipFacingClient = getFirstIpFromInterface(clientInterface);
1186 if (ipFacingClient == null) {
Yi Tseng51301292017-07-28 13:02:59 -07001187 log.warn("Cannot determine relay agent interface Ipv4 addr for host {}/{}. "
1188 + "Aborting relay for dhcp packet from server {}",
Yi Tsengdcef2c22017-08-05 20:34:06 -07001189 etherReply.getDestinationMAC(), clientInterface.vlan(),
Yi Tseng51301292017-07-28 13:02:59 -07001190 ethernetPacket);
1191 return null;
1192 }
1193 // SRC_IP: relay agent IP
1194 // DST_IP: offered IP
Yi Tseng3df7f9d2017-08-17 13:08:31 -07001195 ipv4Packet.setSourceAddress(ipFacingClient.toInt());
Daniel Ginsburgb1dec912018-01-29 11:40:22 -08001196 if (((int) dhcpPayload.getFlags() & 0x8000) == 0x0000) {
1197 ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
1198 } else {
1199 ipv4Packet.setDestinationAddress(BROADCAST_IP);
1200 }
Yi Tseng51301292017-07-28 13:02:59 -07001201 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
1202 if (directlyConnected(dhcpPayload)) {
1203 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
1204 } else {
1205 // forward to another dhcp relay
Yi Tseng93ba53c2017-09-14 13:24:21 -07001206 // FIXME: Currently we assume the DHCP comes from a L2 relay with
1207 // Option 82, this might not work if DHCP message comes from
1208 // L3 relay.
1209 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
Yi Tseng51301292017-07-28 13:02:59 -07001210 }
1211
1212 udpPacket.setPayload(dhcpPayload);
1213 ipv4Packet.setPayload(udpPacket);
1214 etherReply.setPayload(ipv4Packet);
Taras Lemkin96a0d342018-03-26 14:52:58 +00001215 return InternalPacket.internalPacket(etherReply, clientInterface.connectPoint());
Yi Tseng51301292017-07-28 13:02:59 -07001216 }
1217
Yi Tsengdcef2c22017-08-05 20:34:06 -07001218 /**
Charles Chand0dd7002017-10-08 23:53:36 -04001219 * Build the DHCP offer/ack with proper client port.
1220 *
1221 * @param ethernetPacket the original packet comes from server
1222 * @return new packet which will send to the client
1223 */
Taras Lemkin96a0d342018-03-26 14:52:58 +00001224 private InternalPacket processLeaseQueryFromServer(Ethernet ethernetPacket) {
Charles Chand0dd7002017-10-08 23:53:36 -04001225 // get dhcp header.
1226 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
1227 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
1228 UDP udpPacket = (UDP) ipv4Packet.getPayload();
1229 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
1230
1231 // determine the vlanId of the client host - note that this vlan id
1232 // could be different from the vlan in the packet from the server
Taras Lemkin96a0d342018-03-26 14:52:58 +00001233 Interface clientInterface = null;
1234 MacAddress destinationMac = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1235
1236 if (!learnRouteFromLeasequery) {
1237 int giaddr = ipv4Packet.getDestinationAddress();
1238 IpAddress destinationAddress = Ip4Address.valueOf(giaddr);
1239 log.debug("DHCPLEASEQUERYRESP giaddr: {}({})", giaddr, destinationAddress);
1240
1241 Host destinationHost = hostService.getHostsByIp(destinationAddress).stream().findFirst().orElse(null);
1242 if (destinationHost != null) {
1243 destinationMac = destinationHost.mac();
1244 log.trace("DHCPLEASEQUERYRESP destination mac is: {}", destinationMac);
1245 ConnectPoint destinationLocation = destinationHost.location();
1246 log.trace("Lookup for client interface by destination location {}", destinationLocation);
1247 clientInterface = interfaceService.getInterfacesByPort(destinationLocation)
1248 .stream()
1249 .filter(iface -> interfaceContainsVlan(iface, VlanId.vlanId(etherReply.getVlanID())))
1250 .findFirst()
1251 .orElse(null);
1252 log.trace("Found Host {} by ip {}", destinationHost, destinationAddress);
1253 log.debug("DHCPLEASEQUERYRESP Client interface: {}",
1254 (clientInterface != null ? clientInterface : "not resolved"));
1255
1256 }
1257 } else {
1258 clientInterface = getClientInterface(ethernetPacket, dhcpPayload).orElse(null);
1259 }
Charles Chand0dd7002017-10-08 23:53:36 -04001260
1261 if (clientInterface == null) {
1262 log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
1263 return null;
1264 }
1265 VlanId vlanId;
1266 if (clientInterface.vlanTagged().isEmpty()) {
1267 vlanId = clientInterface.vlan();
1268 } else {
1269 // might be multiple vlan in same interface
1270 vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
1271 }
1272 if (vlanId == null) {
1273 vlanId = VlanId.NONE;
1274 }
1275 etherReply.setVlanID(vlanId.toShort());
1276 etherReply.setSourceMACAddress(clientInterface.mac());
1277
Taras Lemkin96a0d342018-03-26 14:52:58 +00001278 if (!directlyConnected(dhcpPayload) && learnRouteFromLeasequery) {
Charles Chand0dd7002017-10-08 23:53:36 -04001279 // if client is indirectly connected, try use next hop mac address
1280 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1281 HostId hostId = HostId.hostId(macAddress, vlanId);
1282 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1283 if (record != null) {
1284 // if next hop can be found, use mac address of next hop
Taras Lemkin96a0d342018-03-26 14:52:58 +00001285 Optional<MacAddress> nextHop = record.nextHopTemp();
1286 if (!nextHop.isPresent()) {
1287 nextHop = record.nextHop();
1288 }
1289 nextHop.ifPresent(etherReply::setDestinationMACAddress);
Charles Chand0dd7002017-10-08 23:53:36 -04001290 } else {
1291 // otherwise, discard the packet
1292 log.warn("Can't find record for host id {}, discard packet", hostId);
1293 return null;
1294 }
1295 } else {
Taras Lemkin96a0d342018-03-26 14:52:58 +00001296 etherReply.setDestinationMACAddress(destinationMac);
Charles Chand0dd7002017-10-08 23:53:36 -04001297 }
1298
Charles Chand0dd7002017-10-08 23:53:36 -04001299 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
Taras Lemkin96a0d342018-03-26 14:52:58 +00001300 if (directlyConnected(dhcpPayload)) {
1301 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
1302 } else {
1303 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
1304 }
Charles Chand0dd7002017-10-08 23:53:36 -04001305
1306 udpPacket.setPayload(dhcpPayload);
1307 ipv4Packet.setPayload(udpPacket);
1308 etherReply.setPayload(ipv4Packet);
Taras Lemkin96a0d342018-03-26 14:52:58 +00001309 udpPacket.resetChecksum();
1310
1311 return InternalPacket.internalPacket(etherReply, clientInterface.connectPoint());
Charles Chand0dd7002017-10-08 23:53:36 -04001312 }
1313 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -07001314 * Extracts VLAN ID from relay agent option.
1315 *
1316 * @param dhcpPayload the DHCP payload
1317 * @return VLAN ID from DHCP payload; null if not exists
1318 */
Yi Tseng4f2a0462017-08-31 11:21:00 -07001319 private VlanId getVlanIdFromRelayAgentOption(DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001320 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
1321 if (option == null) {
1322 return null;
1323 }
1324 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
1325 if (circuitIdSubOption == null) {
1326 return null;
1327 }
1328 try {
1329 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
1330 return circuitId.vlanId();
1331 } catch (IllegalArgumentException e) {
1332 // can't deserialize the circuit ID
1333 return null;
1334 }
1335 }
1336
1337 /**
1338 * Removes DHCP relay agent information option (option 82) from DHCP payload.
1339 * Also reset giaddr to 0
1340 *
1341 * @param ethPacket the Ethernet packet to be processed
1342 * @return Ethernet packet processed
1343 */
1344 private Ethernet removeRelayAgentOption(Ethernet ethPacket) {
rsahot036620655b2018-02-26 15:01:37 -05001345 Ethernet ethernet = (Ethernet) ethPacket.duplicate();
Yi Tsengdcef2c22017-08-05 20:34:06 -07001346 IPv4 ipv4 = (IPv4) ethernet.getPayload();
1347 UDP udp = (UDP) ipv4.getPayload();
1348 DHCP dhcpPayload = (DHCP) udp.getPayload();
1349
1350 // removes relay agent information option
1351 List<DhcpOption> options = dhcpPayload.getOptions();
1352 options = options.stream()
1353 .filter(option -> option.getCode() != OptionCode_CircuitID.getValue())
1354 .collect(Collectors.toList());
1355 dhcpPayload.setOptions(options);
1356 dhcpPayload.setGatewayIPAddress(0);
1357
1358 udp.setPayload(dhcpPayload);
1359 ipv4.setPayload(udp);
1360 ethernet.setPayload(ipv4);
1361 return ethernet;
1362 }
1363
Taras Lemkin96a0d342018-03-26 14:52:58 +00001364 private boolean isDhcpPacketLeasequery(DHCP dhcpPacket) {
1365 switch (dhcpPacket.getPacketType()) {
1366 case DHCPLEASEACTIVE:
1367 case DHCPLEASEQUERY:
1368 case DHCPLEASEUNASSIGNED:
1369 case DHCPLEASEUNKNOWN:
1370 return true;
1371 default:
1372 return false;
1373 }
1374 }
Yi Tseng51301292017-07-28 13:02:59 -07001375
1376 /**
1377 * Check if the host is directly connected to the network or not.
1378 *
1379 * @param dhcpPayload the dhcp payload
1380 * @return true if the host is directly connected to the network; false otherwise
1381 */
1382 private boolean directlyConnected(DHCP dhcpPayload) {
Taras Lemkin96a0d342018-03-26 14:52:58 +00001383 // leasequery is always indirect
1384 if (isDhcpPacketLeasequery(dhcpPayload)) {
1385 return false;
1386 }
1387
Yi Tseng440e2b72017-08-24 14:47:34 -07001388 DhcpRelayAgentOption relayAgentOption =
1389 (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
Yi Tseng51301292017-07-28 13:02:59 -07001390
1391 // Doesn't contains relay option
1392 if (relayAgentOption == null) {
1393 return true;
1394 }
1395
Yi Tseng440e2b72017-08-24 14:47:34 -07001396 // check circuit id, if circuit id is invalid, we say it is an indirect host
1397 DhcpOption circuitIdOpt = relayAgentOption.getSubOption(CIRCUIT_ID.getValue());
Yi Tseng51301292017-07-28 13:02:59 -07001398
Yi Tseng440e2b72017-08-24 14:47:34 -07001399 try {
1400 CircuitId.deserialize(circuitIdOpt.getData());
Yi Tseng51301292017-07-28 13:02:59 -07001401 return true;
Yi Tseng440e2b72017-08-24 14:47:34 -07001402 } catch (Exception e) {
1403 // invalid circuit id
1404 return false;
Yi Tseng51301292017-07-28 13:02:59 -07001405 }
Yi Tseng51301292017-07-28 13:02:59 -07001406 }
1407
1408
1409 /**
1410 * Send the DHCP ack to the requester host.
1411 * Modify Host or Route store according to the type of DHCP.
1412 *
1413 * @param ethernetPacketAck the packet
1414 * @param dhcpPayload the DHCP data
1415 */
1416 private void handleDhcpAck(Ethernet ethernetPacketAck, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001417 Optional<Interface> outInterface = getClientInterface(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -07001418 if (!outInterface.isPresent()) {
1419 log.warn("Can't find output interface for dhcp: {}", dhcpPayload);
1420 return;
1421 }
1422
1423 Interface outIface = outInterface.get();
1424 HostLocation hostLocation = new HostLocation(outIface.connectPoint(), System.currentTimeMillis());
1425 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
Yi Tseng4f2a0462017-08-31 11:21:00 -07001426 VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001427 if (vlanId == null) {
1428 vlanId = outIface.vlan();
1429 }
Yi Tseng51301292017-07-28 13:02:59 -07001430 HostId hostId = HostId.hostId(macAddress, vlanId);
1431 Ip4Address ip = Ip4Address.valueOf(dhcpPayload.getYourIPAddress());
1432
1433 if (directlyConnected(dhcpPayload)) {
1434 // Add to host store if it connect to network directly
1435 Set<IpAddress> ips = Sets.newHashSet(ip);
Yi Tsengaa417a62017-09-08 17:22:51 -07001436 Host host = hostService.getHost(hostId);
Yi Tseng51301292017-07-28 13:02:59 -07001437
Yi Tsengaa417a62017-09-08 17:22:51 -07001438 Set<HostLocation> hostLocations = Sets.newHashSet(hostLocation);
1439 if (host != null) {
1440 // Dual homing support:
1441 // if host exists, use old locations and new location
1442 hostLocations.addAll(host.locations());
1443 }
1444 HostDescription desc = new DefaultHostDescription(macAddress, vlanId,
1445 hostLocations, ips, false);
1446 // Add IP address when dhcp server give the host new ip address
1447 providerService.hostDetected(hostId, desc, false);
Yi Tseng51301292017-07-28 13:02:59 -07001448 } else {
1449 // Add to route store if it does not connect to network directly
1450 // Get gateway host IP according to host mac address
Yi Tsengdcef2c22017-08-05 20:34:06 -07001451 // TODO: remove relay store here
Yi Tseng51301292017-07-28 13:02:59 -07001452 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1453
1454 if (record == null) {
1455 log.warn("Can't find DHCP record of host {}", hostId);
1456 return;
1457 }
1458
1459 MacAddress gwMac = record.nextHop().orElse(null);
1460 if (gwMac == null) {
1461 log.warn("Can't find gateway mac address from record {}", record);
1462 return;
1463 }
1464
1465 HostId gwHostId = HostId.hostId(gwMac, record.vlanId());
1466 Host gwHost = hostService.getHost(gwHostId);
1467
1468 if (gwHost == null) {
1469 log.warn("Can't find gateway host {}", gwHostId);
1470 return;
1471 }
1472
1473 Ip4Address nextHopIp = gwHost.ipAddresses()
1474 .stream()
1475 .filter(IpAddress::isIp4)
1476 .map(IpAddress::getIp4Address)
1477 .findFirst()
1478 .orElse(null);
1479
1480 if (nextHopIp == null) {
1481 log.warn("Can't find IP address of gateway {}", gwHost);
1482 return;
1483 }
1484
Charles Chan6305b692018-04-04 11:43:54 -07001485 Route route = new Route(Route.Source.DHCP, ip.toIpPrefix(), nextHopIp);
Daniel Ginsburg83b76452018-06-09 01:43:59 +03001486 routeStore.replaceRoute(route);
Yi Tseng51301292017-07-28 13:02:59 -07001487 }
Yi Tseng51301292017-07-28 13:02:59 -07001488 }
1489
1490 /**
Yi Tseng51301292017-07-28 13:02:59 -07001491 * Gets output interface of a dhcp packet.
1492 * If option 82 exists in the dhcp packet and the option was sent by
Yi Tseng4f2a0462017-08-31 11:21:00 -07001493 * ONOS (circuit format is correct), use the connect
Yi Tseng51301292017-07-28 13:02:59 -07001494 * point and vlan id from circuit id; otherwise, find host by destination
1495 * address and use vlan id from sender (dhcp server).
1496 *
1497 * @param ethPacket the ethernet packet
1498 * @param dhcpPayload the dhcp packet
1499 * @return an interface represent the output port and vlan; empty value
1500 * if the host or circuit id not found
1501 */
Yi Tsengdcef2c22017-08-05 20:34:06 -07001502 private Optional<Interface> getClientInterface(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tseng51301292017-07-28 13:02:59 -07001503 VlanId originalPacketVlanId = VlanId.vlanId(ethPacket.getVlanID());
Yi Tseng51301292017-07-28 13:02:59 -07001504 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
1505
Yi Tseng4f2a0462017-08-31 11:21:00 -07001506 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
1507 try {
1508 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
1509 ConnectPoint connectPoint = ConnectPoint.deviceConnectPoint(circuitId.connectPoint());
1510 VlanId vlanId = circuitId.vlanId();
1511 return interfaceService.getInterfacesByPort(connectPoint)
1512 .stream()
1513 .filter(iface -> interfaceContainsVlan(iface, vlanId))
1514 .findFirst();
1515 } catch (IllegalArgumentException ex) {
1516 // invalid circuit format, didn't sent by ONOS
1517 log.debug("Invalid circuit {}, use information from dhcp payload",
1518 circuitIdSubOption.getData());
Yi Tseng51301292017-07-28 13:02:59 -07001519 }
1520
1521 // Use Vlan Id from DHCP server if DHCP relay circuit id was not
1522 // sent by ONOS or circuit Id can't be parsed
Yi Tsengdcef2c22017-08-05 20:34:06 -07001523 // TODO: remove relay store from this method
Yi Tseng51301292017-07-28 13:02:59 -07001524 MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
jayakumarthazhathed5f05d2018-09-25 15:56:51 -04001525 VlanId filteredVlanId = getVlanIdFromDhcpRecord(dstMac, originalPacketVlanId);
1526 // Get the vlan from the dhcp record
1527 if (filteredVlanId == null) {
1528 log.debug("not find the matching DHCP record for mac: {} and vlan: {}", dstMac, originalPacketVlanId);
1529 return Optional.empty();
1530 }
1531
1532 Optional<DhcpRecord> dhcpRecord = dhcpRelayStore.getDhcpRecord(HostId.hostId(dstMac, filteredVlanId));
Yi Tsengdcef2c22017-08-05 20:34:06 -07001533 ConnectPoint clientConnectPoint = dhcpRecord
Yi Tseng51301292017-07-28 13:02:59 -07001534 .map(DhcpRecord::locations)
1535 .orElse(Collections.emptySet())
1536 .stream()
1537 .reduce((hl1, hl2) -> {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001538 // find latest host connect point
Yi Tseng51301292017-07-28 13:02:59 -07001539 if (hl1 == null || hl2 == null) {
1540 return hl1 == null ? hl2 : hl1;
1541 }
1542 return hl1.time() > hl2.time() ? hl1 : hl2;
1543 })
Yi Tsengdcef2c22017-08-05 20:34:06 -07001544 .orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -07001545
Yi Tsengdcef2c22017-08-05 20:34:06 -07001546 if (clientConnectPoint != null) {
1547 return interfaceService.getInterfacesByPort(clientConnectPoint)
1548 .stream()
jayakumarthazhathed5f05d2018-09-25 15:56:51 -04001549 .filter(iface -> interfaceContainsVlan(iface, filteredVlanId))
Yi Tsengdcef2c22017-08-05 20:34:06 -07001550 .findFirst();
1551 }
1552 return Optional.empty();
Yi Tseng51301292017-07-28 13:02:59 -07001553 }
1554
1555 /**
jayakumarthazhathed5f05d2018-09-25 15:56:51 -04001556 * Get the required vlanId in case the DCHP record has more than one vlanId for a given MAC.
1557 *
1558 * @param mac MAC address of the DHCP client
1559 * @param vlan Expected vlan of the DHCP client
1560 */
1561 private VlanId getVlanIdFromDhcpRecord(MacAddress mac, VlanId vlan) {
1562 // Get all the DHCP records matching with the mac address
1563 // If only one entry is present then pick the vlan of that entry
1564 // If more then one entry is present then look for an entry with matching vlan
1565 // else return null
1566 Collection<DhcpRecord> records = dhcpRelayStore.getDhcpRecords();
1567 List<DhcpRecord> filteredRecords = new ArrayList<>();
1568 for (DhcpRecord e: records) {
1569 if (e.macAddress().equals(mac)) {
1570 filteredRecords.add(e);
1571 }
1572 }
1573 log.debug("getVlanIdFromDhcpRecord mac: {} vlan: {}", mac, vlan);
1574 log.debug("filteredRecords are: {}", filteredRecords);
1575 if (filteredRecords.size() == 1) {
1576 log.debug("Only one DHCP record entry. Returning back the vlan of that DHCP record: {}", filteredRecords);
1577 return filteredRecords.get(0).vlanId();
1578 }
1579 // Check in the DHCP filtered record for matching vlan
1580 for (DhcpRecord e: filteredRecords) {
1581 if (e.vlanId().equals(vlan)) {
1582 log.debug("Found a matching vlan entry in the DHCP record:{}", e);
1583 return vlan;
1584 }
1585 }
1586 // Found nothing return null
1587 log.debug("Returning null as no matching or more than one matching entry found");
1588 return null;
1589
1590 }
1591
1592
1593
1594 /**
Yi Tseng51301292017-07-28 13:02:59 -07001595 * Send the response DHCP to the requester host.
1596 *
Taras Lemkin96a0d342018-03-26 14:52:58 +00001597 * @param thePacket the packet
Yi Tseng51301292017-07-28 13:02:59 -07001598 * @param dhcpPayload the DHCP data
1599 */
Taras Lemkin96a0d342018-03-26 14:52:58 +00001600 private void sendResponseToClient(InternalPacket thePacket, DHCP dhcpPayload) {
1601 checkNotNull(thePacket, "Nothing to send");
1602 checkNotNull(thePacket.getPacket(), "Packet to send must not be empty");
1603 checkNotNull(thePacket.getDestLocation(), "Packet destination not be empty");
1604
1605 Ethernet ethPacket = thePacket.getPacket();
Yi Tsengdcef2c22017-08-05 20:34:06 -07001606 if (directlyConnected(dhcpPayload)) {
1607 ethPacket = removeRelayAgentOption(ethPacket);
1608 }
Taras Lemkin96a0d342018-03-26 14:52:58 +00001609
Yi Tsengdcef2c22017-08-05 20:34:06 -07001610 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
Taras Lemkin96a0d342018-03-26 14:52:58 +00001611 .setOutput(thePacket.getDestLocation().port())
Yi Tsengdcef2c22017-08-05 20:34:06 -07001612 .build();
1613 OutboundPacket o = new DefaultOutboundPacket(
Taras Lemkin96a0d342018-03-26 14:52:58 +00001614 thePacket.getDestLocation().deviceId(),
Yi Tsengdcef2c22017-08-05 20:34:06 -07001615 treatment,
1616 ByteBuffer.wrap(ethPacket.serialize()));
1617 if (log.isTraceEnabled()) {
Taras Lemkin96a0d342018-03-26 14:52:58 +00001618 log.trace("Relaying packet to DHCP client {} via {}",
Yi Tsengdcef2c22017-08-05 20:34:06 -07001619 ethPacket,
Taras Lemkin96a0d342018-03-26 14:52:58 +00001620 thePacket.getDestLocation());
Yi Tsengdcef2c22017-08-05 20:34:06 -07001621 }
1622 packetService.emit(o);
Yi Tseng51301292017-07-28 13:02:59 -07001623 }
Yi Tseng483ac6f2017-08-02 15:03:31 -07001624
Yi Tsengaa417a62017-09-08 17:22:51 -07001625 @Override
1626 public void triggerProbe(Host host) {
1627 // Do nothing here
1628 }
1629
1630 @Override
1631 public ProviderId id() {
Charles Chand988c282017-09-12 17:09:32 -07001632 return PROVIDER_ID;
Yi Tsengaa417a62017-09-08 17:22:51 -07001633 }
1634
Yi Tseng483ac6f2017-08-02 15:03:31 -07001635 class InternalHostListener implements HostListener {
1636 @Override
1637 public void event(HostEvent event) {
Yi Tseng919b2df2017-09-07 16:22:51 -07001638 if (!configured()) {
1639 return;
1640 }
Yi Tseng483ac6f2017-08-02 15:03:31 -07001641 switch (event.type()) {
1642 case HOST_ADDED:
1643 case HOST_UPDATED:
Charles Chan24a96ff2018-08-13 10:32:03 -07001644 case HOST_MOVED:
Jordan Halterman6328db72018-04-10 13:34:50 -04001645 log.trace("Scheduled host event {}", event);
1646 hostEventExecutor.execute(() -> hostUpdated(event.subject()));
Yi Tseng483ac6f2017-08-02 15:03:31 -07001647 break;
1648 case HOST_REMOVED:
Jordan Halterman6328db72018-04-10 13:34:50 -04001649 log.trace("Scheduled host event {}", event);
1650 hostEventExecutor.execute(() -> hostRemoved(event.subject()));
Yi Tseng483ac6f2017-08-02 15:03:31 -07001651 break;
Yi Tseng483ac6f2017-08-02 15:03:31 -07001652 default:
1653 break;
1654 }
1655 }
1656 }
1657
1658 /**
Yi Tseng483ac6f2017-08-02 15:03:31 -07001659 * Handle host updated.
1660 * If the host is DHCP server or gateway, update connect mac and vlan.
1661 *
1662 * @param host the host
1663 */
1664 private void hostUpdated(Host host) {
Yi Tseng525ff402017-10-23 19:39:39 -07001665 hostUpdated(host, defaultServerInfoList);
1666 hostUpdated(host, indirectServerInfoList);
Yi Tseng483ac6f2017-08-02 15:03:31 -07001667 }
1668
Yi Tseng525ff402017-10-23 19:39:39 -07001669 private void hostUpdated(Host host, List<DhcpServerInfo> srverInfoList) {
jjosep004c05f55ab2018-08-21 09:01:10 -04001670 srverInfoList.stream().forEach(serverInfo -> {
1671 Ip4Address targetIp = serverInfo.getDhcpGatewayIp4().orElse(null);
Yi Tseng525ff402017-10-23 19:39:39 -07001672 Ip4Address serverIp = serverInfo.getDhcpServerIp4().orElse(null);
Yi Tseng525ff402017-10-23 19:39:39 -07001673 if (targetIp == null) {
1674 targetIp = serverIp;
1675 }
Yi Tseng525ff402017-10-23 19:39:39 -07001676 if (targetIp != null) {
1677 if (host.ipAddresses().contains(targetIp)) {
1678 serverInfo.setDhcpConnectMac(host.mac());
1679 serverInfo.setDhcpConnectVlan(host.vlan());
1680 requestDhcpPacket(serverIp);
1681 }
1682 }
jjosep004c05f55ab2018-08-21 09:01:10 -04001683 });
Yi Tseng525ff402017-10-23 19:39:39 -07001684 }
1685
1686
Yi Tseng483ac6f2017-08-02 15:03:31 -07001687 /**
1688 * Handle host removed.
1689 * If the host is DHCP server or gateway, unset connect mac and vlan.
1690 *
1691 * @param host the host
1692 */
1693 private void hostRemoved(Host host) {
Yi Tseng525ff402017-10-23 19:39:39 -07001694 hostRemoved(host, defaultServerInfoList);
1695 hostRemoved(host, indirectServerInfoList);
1696 }
1697
1698 private void hostRemoved(Host host, List<DhcpServerInfo> serverInfoList) {
jjosep004c05f55ab2018-08-21 09:01:10 -04001699 serverInfoList.stream().forEach(serverInfo -> {
1700 Ip4Address targetIp = serverInfo.getDhcpGatewayIp4().orElse(null);
Yi Tseng525ff402017-10-23 19:39:39 -07001701 Ip4Address serverIp = serverInfo.getDhcpServerIp4().orElse(null);
Yi Tseng525ff402017-10-23 19:39:39 -07001702 if (targetIp == null) {
1703 targetIp = serverIp;
Yi Tseng919b2df2017-09-07 16:22:51 -07001704 }
Yi Tseng525ff402017-10-23 19:39:39 -07001705
1706 if (targetIp != null) {
1707 if (host.ipAddresses().contains(targetIp)) {
Yi Tseng919b2df2017-09-07 16:22:51 -07001708 serverInfo.setDhcpConnectVlan(null);
1709 serverInfo.setDhcpConnectMac(null);
Yi Tseng525ff402017-10-23 19:39:39 -07001710 cancelDhcpPacket(serverIp);
Yi Tseng919b2df2017-09-07 16:22:51 -07001711 }
Yi Tseng483ac6f2017-08-02 15:03:31 -07001712 }
jjosep004c05f55ab2018-08-21 09:01:10 -04001713 });
Yi Tseng525ff402017-10-23 19:39:39 -07001714 }
Yi Tseng919b2df2017-09-07 16:22:51 -07001715
Yi Tseng525ff402017-10-23 19:39:39 -07001716 private void requestDhcpPacket(Ip4Address serverIp) {
1717 requestServerDhcpPacket(serverIp);
1718 requestClientDhcpPacket(serverIp);
1719 }
Yi Tseng919b2df2017-09-07 16:22:51 -07001720
Yi Tseng525ff402017-10-23 19:39:39 -07001721 private void cancelDhcpPacket(Ip4Address serverIp) {
1722 cancelServerDhcpPacket(serverIp);
1723 cancelClientDhcpPacket(serverIp);
1724 }
1725
1726 private void cancelServerDhcpPacket(Ip4Address serverIp) {
1727 TrafficSelector serverSelector =
1728 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1729 .matchIPSrc(serverIp.toIpPrefix())
1730 .build();
1731 packetService.cancelPackets(serverSelector,
1732 PacketPriority.CONTROL,
1733 appId);
1734 }
1735
1736 private void requestServerDhcpPacket(Ip4Address serverIp) {
1737 TrafficSelector serverSelector =
1738 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1739 .matchIPSrc(serverIp.toIpPrefix())
1740 .build();
1741 packetService.requestPackets(serverSelector,
1742 PacketPriority.CONTROL,
1743 appId);
1744 }
1745
1746 private void cancelClientDhcpPacket(Ip4Address serverIp) {
1747 // Packet comes from relay
1748 TrafficSelector indirectClientSelector =
1749 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1750 .matchIPDst(serverIp.toIpPrefix())
1751 .build();
1752 packetService.cancelPackets(indirectClientSelector,
1753 PacketPriority.CONTROL,
1754 appId);
1755
1756 // Packet comes from client
1757 packetService.cancelPackets(CLIENT_SERVER_SELECTOR,
1758 PacketPriority.CONTROL,
1759 appId);
1760 }
1761
1762 private void requestClientDhcpPacket(Ip4Address serverIp) {
1763 // Packet comes from relay
1764 TrafficSelector indirectClientSelector =
1765 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1766 .matchIPDst(serverIp.toIpPrefix())
1767 .build();
1768 packetService.requestPackets(indirectClientSelector,
1769 PacketPriority.CONTROL,
1770 appId);
1771
1772 // Packet comes from client
1773 packetService.requestPackets(CLIENT_SERVER_SELECTOR,
1774 PacketPriority.CONTROL,
1775 appId);
1776 }
1777
1778 /**
1779 * Process the ignore rules.
1780 *
1781 * @param deviceId the device id
1782 * @param vlanId the vlan to be ignored
1783 * @param op the operation, ADD to install; REMOVE to uninstall rules
1784 */
1785 private void processIgnoreVlanRule(DeviceId deviceId, VlanId vlanId, Objective.Operation op) {
Yi Tseng525ff402017-10-23 19:39:39 -07001786 AtomicInteger installedCount = new AtomicInteger(DHCP_SELECTORS.size());
1787 DHCP_SELECTORS.forEach(trafficSelector -> {
1788 TrafficSelector selector = DefaultTrafficSelector.builder(trafficSelector)
1789 .matchVlanId(vlanId)
1790 .build();
1791
1792 ForwardingObjective.Builder builder = DefaultForwardingObjective.builder()
1793 .withFlag(ForwardingObjective.Flag.VERSATILE)
1794 .withSelector(selector)
1795 .withPriority(IGNORE_CONTROL_PRIORITY)
Yi Tsengdbabeed2017-11-29 10:49:20 -08001796 .withTreatment(DefaultTrafficTreatment.emptyTreatment())
Yi Tseng525ff402017-10-23 19:39:39 -07001797 .fromApp(appId);
1798
1799
1800 ObjectiveContext objectiveContext = new ObjectiveContext() {
1801 @Override
1802 public void onSuccess(Objective objective) {
1803 log.info("Ignore rule {} (Vlan id {}, device {}, selector {})",
1804 op, vlanId, deviceId, selector);
1805 int countDown = installedCount.decrementAndGet();
1806 if (countDown != 0) {
1807 return;
1808 }
1809 switch (op) {
1810 case ADD:
1811 ignoredVlans.put(deviceId, vlanId);
1812 break;
1813 case REMOVE:
1814 ignoredVlans.remove(deviceId, vlanId);
1815 break;
1816 default:
1817 log.warn("Unsupported objective operation {}", op);
1818 break;
1819 }
Yi Tseng919b2df2017-09-07 16:22:51 -07001820 }
Yi Tseng525ff402017-10-23 19:39:39 -07001821
1822 @Override
1823 public void onError(Objective objective, ObjectiveError error) {
1824 log.warn("Can't {} ignore rule (vlan id {}, selector {}, device {}) due to {}",
1825 op, vlanId, selector, deviceId, error);
Yi Tseng919b2df2017-09-07 16:22:51 -07001826 }
Yi Tseng525ff402017-10-23 19:39:39 -07001827 };
1828
1829 ForwardingObjective fwd;
1830 switch (op) {
1831 case ADD:
1832 fwd = builder.add(objectiveContext);
1833 break;
1834 case REMOVE:
1835 fwd = builder.remove(objectiveContext);
1836 break;
1837 default:
1838 log.warn("Unsupported objective operation {}", op);
1839 return;
Yi Tseng4f2a0462017-08-31 11:21:00 -07001840 }
Yi Tseng525ff402017-10-23 19:39:39 -07001841
1842 Device device = deviceService.getDevice(deviceId);
1843 if (device == null || !device.is(Pipeliner.class)) {
1844 log.warn("Device {} is not available now, wait until device is available", deviceId);
1845 return;
1846 }
1847 flowObjectiveService.apply(deviceId, fwd);
1848 });
Yi Tseng483ac6f2017-08-02 15:03:31 -07001849 }
Kalhee Kimba366062017-11-07 16:32:09 +00001850
1851 @Override
1852 public void setDhcpFpmEnabled(Boolean enabled) {
1853 // v4 does not use fpm. Do nothing.
1854 }
rsahot036620655b2018-02-26 15:01:37 -05001855 private List<DhcpServerInfo> findValidServerInfo(boolean directConnFlag) {
1856 List<DhcpServerInfo> validServerInfo;
1857
1858 if (directConnFlag || indirectServerInfoList.isEmpty()) {
1859 validServerInfo = new ArrayList<DhcpServerInfo>(defaultServerInfoList);
1860 } else {
1861 validServerInfo = new ArrayList<DhcpServerInfo>(indirectServerInfoList);
1862 }
1863 return validServerInfo;
1864 }
1865
1866
1867 private boolean checkDhcpServerConnPt(boolean directConnFlag,
1868 DhcpServerInfo serverInfo) {
1869 if (serverInfo.getDhcpServerConnectPoint() == null) {
1870 log.warn("DHCP4 server connect point for {} connPt {}",
1871 directConnFlag ? "direct" : "indirect", serverInfo.getDhcpServerConnectPoint());
1872 return false;
1873 }
1874 return true;
1875 }
1876
1877 /**
1878 * Checks if serverInfo's host info (mac and vlan) is filled in; if not, fills in.
1879 *
1880 * @param serverInfo server information
1881 * @return newServerInfo if host info can be either found or filled in.
1882 */
1883 private DhcpServerInfo getHostInfoForServerInfo(DhcpServerInfo serverInfo, List<DhcpServerInfo> sererInfoList) {
1884 DhcpServerInfo newServerInfo = null;
1885 MacAddress dhcpServerConnectMac = serverInfo.getDhcpConnectMac().orElse(null);
1886 VlanId dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
1887 ConnectPoint dhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
1888
1889 if (dhcpServerConnectMac != null && dhcpConnectVlan != null) {
1890 newServerInfo = serverInfo;
Charles Chan2de55302018-03-02 18:27:59 -08001891 log.debug("DHCP server {} host info found. ConnectPt{} Mac {} vlan {}", serverInfo.getDhcpServerIp4(),
rsahot036620655b2018-02-26 15:01:37 -05001892 dhcpServerConnectPoint, dhcpServerConnectMac, dhcpConnectVlan);
1893 } else {
1894 log.warn("DHCP server {} not resolve yet connectPt {} mac {} vlan {}", serverInfo.getDhcpServerIp4(),
1895 dhcpServerConnectPoint, dhcpServerConnectMac, dhcpConnectVlan);
1896
1897 Ip4Address ipToProbe;
1898 if (serverInfo.getDhcpGatewayIp4().isPresent()) {
1899 ipToProbe = serverInfo.getDhcpGatewayIp4().get();
1900 } else {
1901 ipToProbe = serverInfo.getDhcpServerIp4().orElse(null);
1902 }
1903 String hostToProbe = serverInfo.getDhcpGatewayIp6()
1904 .map(ip -> "gateway").orElse("server");
1905
1906 log.warn("Dynamically probing to resolve {} IP {}", hostToProbe, ipToProbe);
1907 hostService.startMonitoringIp(ipToProbe);
1908
1909 Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
1910 if (!hosts.isEmpty()) {
1911 int serverInfoIndex = sererInfoList.indexOf(serverInfo);
1912 Host host = hosts.iterator().next();
1913 serverInfo.setDhcpConnectVlan(host.vlan());
1914 serverInfo.setDhcpConnectMac(host.mac());
1915 // replace the serverInfo in the list
1916 sererInfoList.set(serverInfoIndex, serverInfo);
1917 newServerInfo = serverInfo;
1918 log.warn("Dynamically host found host {}", host);
1919 } else {
1920 log.warn("No host found host ip {} dynamically", ipToProbe);
1921 }
1922 }
1923 return newServerInfo;
1924 }
1925
1926 /**
1927 * Gets Interface facing to the server for default host.
1928 *
1929 * @param serverInfo server information
1930 * @return the Interface facing to the server; null if not found
1931 */
1932 private Interface getServerInterface(DhcpServerInfo serverInfo) {
1933 Interface serverInterface = null;
1934
1935 ConnectPoint dhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
1936 VlanId dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
1937
1938 if (dhcpServerConnectPoint != null && dhcpConnectVlan != null) {
1939 serverInterface = interfaceService.getInterfacesByPort(dhcpServerConnectPoint)
1940 .stream()
Taras Lemkin96a0d342018-03-26 14:52:58 +00001941 .filter(iface -> Dhcp4HandlerUtil.interfaceContainsVlan(iface, dhcpConnectVlan))
rsahot036620655b2018-02-26 15:01:37 -05001942 .findFirst()
1943 .orElse(null);
1944 } else {
1945 log.warn("DHCP server {} not resolve yet connectPoint {} vlan {}", serverInfo.getDhcpServerIp6(),
1946 dhcpServerConnectPoint, dhcpConnectVlan);
1947 }
1948
1949 return serverInterface;
1950 }
1951
1952 //forward the packet to ConnectPoint where the DHCP server is attached.
1953 private void forwardPacket(InternalPacket packet) {
1954 //send Packetout to dhcp server connectpoint.
Taras Lemkin96a0d342018-03-26 14:52:58 +00001955 if (packet.getDestLocation() != null) {
rsahot036620655b2018-02-26 15:01:37 -05001956 TrafficTreatment t = DefaultTrafficTreatment.builder()
Taras Lemkin96a0d342018-03-26 14:52:58 +00001957 .setOutput(packet.getDestLocation().port()).build();
rsahot036620655b2018-02-26 15:01:37 -05001958 OutboundPacket o = new DefaultOutboundPacket(
Taras Lemkin96a0d342018-03-26 14:52:58 +00001959 packet.getDestLocation().deviceId(), t, ByteBuffer.wrap(packet.getPacket().serialize()));
rsahot036620655b2018-02-26 15:01:37 -05001960 if (log.isTraceEnabled()) {
Taras Lemkin96a0d342018-03-26 14:52:58 +00001961 log.trace("Relaying packet to destination {}", packet.getDestLocation());
rsahot036620655b2018-02-26 15:01:37 -05001962 }
Taras Lemkin96a0d342018-03-26 14:52:58 +00001963 log.debug("packetService.emit(o) to port {}", packet.getDestLocation());
rsahot036620655b2018-02-26 15:01:37 -05001964 packetService.emit(o);
1965 }
1966 }
1967
1968
1969 private DhcpServerInfo findServerInfoFromServer(boolean directConnFlag, ConnectPoint inPort) {
1970 List<DhcpServerInfo> validServerInfoList = findValidServerInfo(directConnFlag);
1971 DhcpServerInfo foundServerInfo = null;
1972 for (DhcpServerInfo serverInfo : validServerInfoList) {
1973 if (inPort.equals(serverInfo.getDhcpServerConnectPoint().get())) {
1974 foundServerInfo = serverInfo;
Charles Chan2de55302018-03-02 18:27:59 -08001975 log.debug("ServerInfo found for Rcv port {} Server Connect Point {} for {}",
rsahot036620655b2018-02-26 15:01:37 -05001976 inPort, serverInfo.getDhcpServerConnectPoint(), directConnFlag ? "direct" : "indirect");
1977 break;
rsahot036620655b2018-02-26 15:01:37 -05001978 }
1979 }
1980 return foundServerInfo;
1981 }
Yi Tseng51301292017-07-28 13:02:59 -07001982}