blob: f473715ddf6109a5da5a2780340fd29a9ccc7595 [file] [log] [blame]
Yi Tseng51301292017-07-28 13:02:59 -07001/*
2 * Copyright 2017-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17
18package org.onosproject.dhcprelay;
19
Yi Tseng525ff402017-10-23 19:39:39 -070020import com.google.common.collect.HashMultimap;
21import com.google.common.collect.ImmutableSet;
Charles Chane5e0c9a2018-03-30 12:11:34 -070022import com.google.common.collect.Lists;
Yi Tseng525ff402017-10-23 19:39:39 -070023import com.google.common.collect.Multimap;
Charles Chan70fdd492018-03-07 17:36:06 -080024import com.google.common.collect.Multimaps;
Yi Tseng51301292017-07-28 13:02:59 -070025import com.google.common.collect.Sets;
Yi Tseng51301292017-07-28 13:02:59 -070026import org.onlab.packet.BasePacket;
27import org.onlab.packet.DHCP;
28import org.onlab.packet.Ethernet;
29import org.onlab.packet.IPv4;
30import org.onlab.packet.Ip4Address;
31import org.onlab.packet.IpAddress;
32import org.onlab.packet.MacAddress;
Yi Tseng525ff402017-10-23 19:39:39 -070033import org.onlab.packet.TpPort;
Yi Tseng51301292017-07-28 13:02:59 -070034import org.onlab.packet.UDP;
35import org.onlab.packet.VlanId;
36import org.onlab.packet.dhcp.CircuitId;
37import org.onlab.packet.dhcp.DhcpOption;
38import org.onlab.packet.dhcp.DhcpRelayAgentOption;
Taras Lemkin96a0d342018-03-26 14:52:58 +000039import org.onlab.util.Tools;
40import org.onosproject.cfg.ComponentConfigService;
Yi Tseng525ff402017-10-23 19:39:39 -070041import org.onosproject.core.ApplicationId;
42import org.onosproject.core.CoreService;
Yi Tseng51301292017-07-28 13:02:59 -070043import org.onosproject.dhcprelay.api.DhcpHandler;
Yi Tseng919b2df2017-09-07 16:22:51 -070044import org.onosproject.dhcprelay.api.DhcpServerInfo;
Yi Tseng483ac6f2017-08-02 15:03:31 -070045import org.onosproject.dhcprelay.config.DhcpServerConfig;
Yi Tseng525ff402017-10-23 19:39:39 -070046import org.onosproject.dhcprelay.config.IgnoreDhcpConfig;
Yi Tseng51301292017-07-28 13:02:59 -070047import org.onosproject.dhcprelay.store.DhcpRecord;
48import org.onosproject.dhcprelay.store.DhcpRelayStore;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070049import org.onosproject.net.ConnectPoint;
Yi Tseng525ff402017-10-23 19:39:39 -070050import org.onosproject.net.Device;
51import org.onosproject.net.DeviceId;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070052import org.onosproject.net.Host;
53import org.onosproject.net.HostId;
54import org.onosproject.net.HostLocation;
Yi Tseng525ff402017-10-23 19:39:39 -070055import org.onosproject.net.behaviour.Pipeliner;
56import org.onosproject.net.device.DeviceService;
57import org.onosproject.net.flow.DefaultTrafficSelector;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070058import org.onosproject.net.flow.DefaultTrafficTreatment;
Yi Tseng525ff402017-10-23 19:39:39 -070059import org.onosproject.net.flow.TrafficSelector;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070060import org.onosproject.net.flow.TrafficTreatment;
Yi Tseng525ff402017-10-23 19:39:39 -070061import org.onosproject.net.flowobjective.DefaultForwardingObjective;
62import org.onosproject.net.flowobjective.FlowObjectiveService;
63import org.onosproject.net.flowobjective.ForwardingObjective;
64import org.onosproject.net.flowobjective.Objective;
65import org.onosproject.net.flowobjective.ObjectiveContext;
66import org.onosproject.net.flowobjective.ObjectiveError;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070067import org.onosproject.net.host.DefaultHostDescription;
68import org.onosproject.net.host.HostDescription;
Yi Tseng483ac6f2017-08-02 15:03:31 -070069import org.onosproject.net.host.HostEvent;
70import org.onosproject.net.host.HostListener;
Yi Tsengaa417a62017-09-08 17:22:51 -070071import org.onosproject.net.host.HostProvider;
72import org.onosproject.net.host.HostProviderRegistry;
73import org.onosproject.net.host.HostProviderService;
Yi Tseng51301292017-07-28 13:02:59 -070074import org.onosproject.net.host.HostService;
Yi Tseng51301292017-07-28 13:02:59 -070075import org.onosproject.net.host.InterfaceIpAddress;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070076import org.onosproject.net.intf.Interface;
77import org.onosproject.net.intf.InterfaceService;
Yi Tseng51301292017-07-28 13:02:59 -070078import org.onosproject.net.packet.DefaultOutboundPacket;
79import org.onosproject.net.packet.OutboundPacket;
80import org.onosproject.net.packet.PacketContext;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070081import org.onosproject.net.packet.PacketPriority;
Yi Tseng51301292017-07-28 13:02:59 -070082import org.onosproject.net.packet.PacketService;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070083import org.onosproject.net.provider.ProviderId;
84import org.onosproject.routeservice.Route;
85import org.onosproject.routeservice.RouteStore;
Taras Lemkin96a0d342018-03-26 14:52:58 +000086import org.osgi.service.component.ComponentContext;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070087import org.osgi.service.component.annotations.Activate;
88import org.osgi.service.component.annotations.Component;
89import org.osgi.service.component.annotations.Deactivate;
90import org.osgi.service.component.annotations.Modified;
91import org.osgi.service.component.annotations.Reference;
92import org.osgi.service.component.annotations.ReferenceCardinality;
Yi Tseng51301292017-07-28 13:02:59 -070093import org.slf4j.Logger;
94import org.slf4j.LoggerFactory;
95
96import java.nio.ByteBuffer;
Taras Lemkin96a0d342018-03-26 14:52:58 +000097import java.util.ArrayList;
Yi Tsengdcef2c22017-08-05 20:34:06 -070098import java.util.Collection;
Yi Tseng51301292017-07-28 13:02:59 -070099import java.util.Collections;
Taras Lemkin96a0d342018-03-26 14:52:58 +0000100import java.util.Dictionary;
Yi Tseng51301292017-07-28 13:02:59 -0700101import java.util.List;
102import java.util.Optional;
103import java.util.Set;
Charles Chan909cff82018-03-05 13:14:02 -0800104import java.util.concurrent.CopyOnWriteArrayList;
Jordan Halterman6328db72018-04-10 13:34:50 -0400105import java.util.concurrent.Executor;
Yi Tseng525ff402017-10-23 19:39:39 -0700106import java.util.concurrent.atomic.AtomicInteger;
Yi Tseng51301292017-07-28 13:02:59 -0700107import java.util.stream.Collectors;
108
109import static com.google.common.base.Preconditions.checkNotNull;
110import static com.google.common.base.Preconditions.checkState;
Jordan Halterman6328db72018-04-10 13:34:50 -0400111import static java.util.concurrent.Executors.newSingleThreadExecutor;
Yi Tseng51301292017-07-28 13:02:59 -0700112import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_CircuitID;
113import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_END;
114import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
115import static org.onlab.packet.MacAddress.valueOf;
116import static org.onlab.packet.dhcp.DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID;
Jordan Halterman6328db72018-04-10 13:34:50 -0400117import static org.onlab.util.Tools.groupedThreads;
Ray Milkey687c00c2018-10-31 10:18:41 -0700118import static org.onosproject.dhcprelay.OsgiPropertyConstants.LEARN_ROUTE_FROM_LEASE_QUERY;
119import static org.onosproject.dhcprelay.OsgiPropertyConstants.LEARN_ROUTE_FROM_LEASE_QUERY_DEFAULT;
Yi Tseng525ff402017-10-23 19:39:39 -0700120import static org.onosproject.net.flowobjective.Objective.Operation.ADD;
121import static org.onosproject.net.flowobjective.Objective.Operation.REMOVE;
Yi Tseng51301292017-07-28 13:02:59 -0700122
Ray Milkey687c00c2018-10-31 10:18:41 -0700123@Component(
124 service = { DhcpHandler.class, HostProvider.class },
125 property = {
126 "version:Integer = 4",
127 LEARN_ROUTE_FROM_LEASE_QUERY + ":Boolean=" + LEARN_ROUTE_FROM_LEASE_QUERY_DEFAULT
128 }
129)
Yi Tsengaa417a62017-09-08 17:22:51 -0700130public class Dhcp4HandlerImpl implements DhcpHandler, HostProvider {
Charles Chand988c282017-09-12 17:09:32 -0700131 public static final String DHCP_V4_RELAY_APP = "org.onosproject.Dhcp4HandlerImpl";
132 public static final ProviderId PROVIDER_ID = new ProviderId("dhcp4", DHCP_V4_RELAY_APP);
Yi Tseng525ff402017-10-23 19:39:39 -0700133 private static final String BROADCAST_IP = "255.255.255.255";
134 private static final int IGNORE_CONTROL_PRIORITY = PacketPriority.CONTROL.priorityValue() + 1000;
135
136 private static final TrafficSelector CLIENT_SERVER_SELECTOR = DefaultTrafficSelector.builder()
137 .matchEthType(Ethernet.TYPE_IPV4)
138 .matchIPProtocol(IPv4.PROTOCOL_UDP)
139 .matchIPSrc(Ip4Address.ZERO.toIpPrefix())
140 .matchIPDst(Ip4Address.valueOf(BROADCAST_IP).toIpPrefix())
141 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
142 .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
143 .build();
144 private static final TrafficSelector SERVER_RELAY_SELECTOR = DefaultTrafficSelector.builder()
145 .matchEthType(Ethernet.TYPE_IPV4)
146 .matchIPProtocol(IPv4.PROTOCOL_UDP)
147 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
148 .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
149 .build();
150 static final Set<TrafficSelector> DHCP_SELECTORS = ImmutableSet.of(
151 CLIENT_SERVER_SELECTOR,
152 SERVER_RELAY_SELECTOR
153 );
Yi Tseng51301292017-07-28 13:02:59 -0700154 private static Logger log = LoggerFactory.getLogger(Dhcp4HandlerImpl.class);
155
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700156 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Yi Tseng51301292017-07-28 13:02:59 -0700157 protected DhcpRelayStore dhcpRelayStore;
158
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700159 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Yi Tseng51301292017-07-28 13:02:59 -0700160 protected PacketService packetService;
161
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700162 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Yi Tseng51301292017-07-28 13:02:59 -0700163 protected RouteStore routeStore;
164
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700165 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Yi Tseng51301292017-07-28 13:02:59 -0700166 protected InterfaceService interfaceService;
167
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700168 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Yi Tseng51301292017-07-28 13:02:59 -0700169 protected HostService hostService;
170
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700171 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Yi Tsengaa417a62017-09-08 17:22:51 -0700172 protected HostProviderRegistry providerRegistry;
173
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700174 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Yi Tseng525ff402017-10-23 19:39:39 -0700175 protected CoreService coreService;
176
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700177 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Yi Tseng525ff402017-10-23 19:39:39 -0700178 protected DeviceService deviceService;
179
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700180 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Yi Tseng525ff402017-10-23 19:39:39 -0700181 protected FlowObjectiveService flowObjectiveService;
182
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700183 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Taras Lemkin96a0d342018-03-26 14:52:58 +0000184 protected ComponentConfigService cfgService;
185
Yi Tsengaa417a62017-09-08 17:22:51 -0700186 protected HostProviderService providerService;
Yi Tseng525ff402017-10-23 19:39:39 -0700187 protected ApplicationId appId;
Charles Chan70fdd492018-03-07 17:36:06 -0800188 protected Multimap<DeviceId, VlanId> ignoredVlans = Multimaps.synchronizedMultimap(HashMultimap.create());
Yi Tseng483ac6f2017-08-02 15:03:31 -0700189 private InternalHostListener hostListener = new InternalHostListener();
190
Charles Chan909cff82018-03-05 13:14:02 -0800191 private List<DhcpServerInfo> defaultServerInfoList = new CopyOnWriteArrayList<>();
192 private List<DhcpServerInfo> indirectServerInfoList = new CopyOnWriteArrayList<>();
Taras Lemkin96a0d342018-03-26 14:52:58 +0000193
Ray Milkey687c00c2018-10-31 10:18:41 -0700194 /** Enable learning routing information from LQ. */
195 private Boolean learnRouteFromLeasequery = LEARN_ROUTE_FROM_LEASE_QUERY_DEFAULT;
Yi Tseng4f2a0462017-08-31 11:21:00 -0700196
Jordan Halterman6328db72018-04-10 13:34:50 -0400197 private Executor hostEventExecutor = newSingleThreadExecutor(
198 groupedThreads("dhcp4-event-host", "%d", log));
199
Yi Tseng483ac6f2017-08-02 15:03:31 -0700200 @Activate
Taras Lemkin96a0d342018-03-26 14:52:58 +0000201 protected void activate(ComponentContext context) {
202 cfgService.registerProperties(getClass());
203 modified(context);
Yi Tseng525ff402017-10-23 19:39:39 -0700204 appId = coreService.registerApplication(DHCP_V4_RELAY_APP);
Yi Tseng483ac6f2017-08-02 15:03:31 -0700205 hostService.addListener(hostListener);
Yi Tsengaa417a62017-09-08 17:22:51 -0700206 providerService = providerRegistry.register(this);
Yi Tseng483ac6f2017-08-02 15:03:31 -0700207 }
208
209 @Deactivate
210 protected void deactivate() {
Taras Lemkin96a0d342018-03-26 14:52:58 +0000211 cfgService.unregisterProperties(getClass(), false);
Yi Tsengaa417a62017-09-08 17:22:51 -0700212 providerRegistry.unregister(this);
213 hostService.removeListener(hostListener);
Yi Tseng919b2df2017-09-07 16:22:51 -0700214 defaultServerInfoList.forEach(this::stopMonitoringIps);
Charles Chanfc1c22e2018-07-19 09:52:01 -0700215 defaultServerInfoList.forEach(info -> info.getDhcpServerIp4().ifPresent(this::cancelDhcpPacket));
Yi Tseng919b2df2017-09-07 16:22:51 -0700216 defaultServerInfoList.clear();
217 indirectServerInfoList.forEach(this::stopMonitoringIps);
Charles Chanfc1c22e2018-07-19 09:52:01 -0700218 indirectServerInfoList.forEach(info -> info.getDhcpServerIp4().ifPresent(this::cancelDhcpPacket));
Yi Tseng919b2df2017-09-07 16:22:51 -0700219 indirectServerInfoList.clear();
Yi Tseng483ac6f2017-08-02 15:03:31 -0700220 }
221
Taras Lemkin96a0d342018-03-26 14:52:58 +0000222 @Modified
223 protected void modified(ComponentContext context) {
224 Dictionary<?, ?> properties = context.getProperties();
225 Boolean flag;
Ray Milkey687c00c2018-10-31 10:18:41 -0700226 flag = Tools.isPropertyEnabled(properties, LEARN_ROUTE_FROM_LEASE_QUERY);
Taras Lemkin96a0d342018-03-26 14:52:58 +0000227 if (flag != null) {
228 learnRouteFromLeasequery = flag;
229 log.info("Learning routes from DHCP leasequery is {}",
230 learnRouteFromLeasequery ? "enabled" : "disabled");
231 }
232 }
233
Yi Tseng919b2df2017-09-07 16:22:51 -0700234 private void stopMonitoringIps(DhcpServerInfo serverInfo) {
235 serverInfo.getDhcpGatewayIp4().ifPresent(gatewayIp -> {
236 hostService.stopMonitoringIp(gatewayIp);
237 });
238 serverInfo.getDhcpServerIp4().ifPresent(serverIp -> {
239 hostService.stopMonitoringIp(serverIp);
240 });
Yi Tseng51301292017-07-28 13:02:59 -0700241 }
242
243 @Override
Yi Tseng483ac6f2017-08-02 15:03:31 -0700244 public void setDefaultDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
Yi Tseng919b2df2017-09-07 16:22:51 -0700245 setDhcpServerConfigs(configs, defaultServerInfoList);
246 }
247
248 @Override
249 public void setIndirectDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
250 setDhcpServerConfigs(configs, indirectServerInfoList);
251 }
252
253 @Override
254 public List<DhcpServerInfo> getDefaultDhcpServerInfoList() {
255 return defaultServerInfoList;
256 }
257
258 @Override
259 public List<DhcpServerInfo> getIndirectDhcpServerInfoList() {
260 return indirectServerInfoList;
261 }
262
Yi Tseng525ff402017-10-23 19:39:39 -0700263 @Override
264 public void updateIgnoreVlanConfig(IgnoreDhcpConfig config) {
265 if (config == null) {
266 ignoredVlans.forEach(((deviceId, vlanId) -> {
267 processIgnoreVlanRule(deviceId, vlanId, REMOVE);
268 }));
269 return;
270 }
271 config.ignoredVlans().forEach((deviceId, vlanId) -> {
272 if (ignoredVlans.get(deviceId).contains(vlanId)) {
273 // don't need to process if it already ignored
274 return;
275 }
276 processIgnoreVlanRule(deviceId, vlanId, ADD);
277 });
278
279 ignoredVlans.forEach((deviceId, vlanId) -> {
280 if (!config.ignoredVlans().get(deviceId).contains(vlanId)) {
281 // not contains in new config, remove it
282 processIgnoreVlanRule(deviceId, vlanId, REMOVE);
283 }
284 });
285 }
286
Saurav Dasb805f1a2017-12-13 16:19:35 -0800287 @Override
288 public void removeIgnoreVlanState(IgnoreDhcpConfig config) {
289 if (config == null) {
290 ignoredVlans.clear();
291 return;
292 }
293 config.ignoredVlans().forEach((deviceId, vlanId) -> {
294 ignoredVlans.remove(deviceId, vlanId);
295 });
296 }
297
Yi Tseng919b2df2017-09-07 16:22:51 -0700298 public void setDhcpServerConfigs(Collection<DhcpServerConfig> configs, List<DhcpServerInfo> serverInfoList) {
Yi Tseng483ac6f2017-08-02 15:03:31 -0700299 if (configs.size() == 0) {
300 // no config to update
301 return;
302 }
303
rsahot036620655b2018-02-26 15:01:37 -0500304 Boolean isConfigValid = false;
305 for (DhcpServerConfig serverConfig : configs) {
306 if (serverConfig.getDhcpServerIp4().isPresent()) {
307 isConfigValid = true;
308 break;
309 }
Yi Tseng483ac6f2017-08-02 15:03:31 -0700310 }
rsahot036620655b2018-02-26 15:01:37 -0500311 if (!isConfigValid) {
312 log.warn("No IP V4 server address found.");
313 return; // No IP V6 address found
314 }
315 // if (!serverInfoList.isEmpty()) {
316 for (DhcpServerInfo oldServerInfo : serverInfoList) {
317 log.info("In for (DhcpServerInfo oldServerInfo : serverInfoList) {");
Yi Tseng919b2df2017-09-07 16:22:51 -0700318 // remove old server info
rsahot036620655b2018-02-26 15:01:37 -0500319 //DhcpServerInfo oldServerInfo = serverInfoList.remove(0);
Yi Tseng483ac6f2017-08-02 15:03:31 -0700320
Yi Tseng919b2df2017-09-07 16:22:51 -0700321 // stop monitoring gateway or server
322 oldServerInfo.getDhcpGatewayIp4().ifPresent(gatewayIp -> {
323 hostService.stopMonitoringIp(gatewayIp);
324 });
325 oldServerInfo.getDhcpServerIp4().ifPresent(serverIp -> {
326 hostService.stopMonitoringIp(serverIp);
Yi Tseng525ff402017-10-23 19:39:39 -0700327 cancelDhcpPacket(serverIp);
Yi Tseng919b2df2017-09-07 16:22:51 -0700328 });
Yi Tseng483ac6f2017-08-02 15:03:31 -0700329 }
Yi Tseng3df7f9d2017-08-17 13:08:31 -0700330
Yi Tseng919b2df2017-09-07 16:22:51 -0700331 // Create new server info according to the config
rsahot036620655b2018-02-26 15:01:37 -0500332 serverInfoList.clear();
333 for (DhcpServerConfig serverConfig : configs) {
Taras Lemkin96a0d342018-03-26 14:52:58 +0000334 log.debug("Create new server info according to the config");
rsahot036620655b2018-02-26 15:01:37 -0500335 DhcpServerInfo newServerInfo = new DhcpServerInfo(serverConfig,
336 DhcpServerInfo.Version.DHCP_V4);
337 checkState(newServerInfo.getDhcpServerConnectPoint().isPresent(),
338 "Connect point not exists");
339 checkState(newServerInfo.getDhcpServerIp4().isPresent(),
340 "IP of DHCP server not exists");
Yi Tseng4f2a0462017-08-31 11:21:00 -0700341
rsahot036620655b2018-02-26 15:01:37 -0500342 log.debug("DHCP server connect point: {}", newServerInfo.getDhcpServerConnectPoint().orElse(null));
343 log.debug("DHCP server IP: {}", newServerInfo.getDhcpServerIp4().orElse(null));
Yi Tseng919b2df2017-09-07 16:22:51 -0700344
rsahot036620655b2018-02-26 15:01:37 -0500345 Ip4Address serverIp = newServerInfo.getDhcpServerIp4().get();
346 Ip4Address ipToProbe;
347 if (newServerInfo.getDhcpGatewayIp4().isPresent()) {
348 ipToProbe = newServerInfo.getDhcpGatewayIp4().get();
349 } else {
350 ipToProbe = newServerInfo.getDhcpServerIp4().orElse(null);
351 }
352 log.info("Probe_IP {}", ipToProbe);
353 String hostToProbe = newServerInfo.getDhcpGatewayIp4()
354 .map(ip -> "gateway").orElse("server");
355
356 log.debug("Probing to resolve {} IP {}", hostToProbe, ipToProbe);
357 hostService.startMonitoringIp(ipToProbe);
358
359 Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
360 if (!hosts.isEmpty()) {
361 Host host = hosts.iterator().next();
362 newServerInfo.setDhcpConnectVlan(host.vlan());
363 newServerInfo.setDhcpConnectMac(host.mac());
364 }
365
366 // Add new server info
367 synchronized (this) {
368 //serverInfoList.clear();
369 serverInfoList.add(newServerInfo);
370 }
371
372 requestDhcpPacket(serverIp);
Yi Tseng4f2a0462017-08-31 11:21:00 -0700373 }
Yi Tseng483ac6f2017-08-02 15:03:31 -0700374 }
375
Yi Tseng3df7f9d2017-08-17 13:08:31 -0700376 @Override
Yi Tseng51301292017-07-28 13:02:59 -0700377 public void processDhcpPacket(PacketContext context, BasePacket payload) {
378 checkNotNull(payload, "DHCP payload can't be null");
379 checkState(payload instanceof DHCP, "Payload is not a DHCP");
380 DHCP dhcpPayload = (DHCP) payload;
381 if (!configured()) {
Yi Tseng919b2df2017-09-07 16:22:51 -0700382 log.warn("Missing default DHCP relay server config. Abort packet processing");
Yi Tseng51301292017-07-28 13:02:59 -0700383 return;
384 }
385
386 ConnectPoint inPort = context.inPacket().receivedFrom();
Yi Tseng51301292017-07-28 13:02:59 -0700387 checkNotNull(dhcpPayload, "Can't find DHCP payload");
388 Ethernet packet = context.inPacket().parsed();
389 DHCP.MsgType incomingPacketType = dhcpPayload.getOptions().stream()
390 .filter(dhcpOption -> dhcpOption.getCode() == OptionCode_MessageType.getValue())
391 .map(DhcpOption::getData)
392 .map(data -> DHCP.MsgType.getType(data[0]))
393 .findFirst()
394 .orElse(null);
395 checkNotNull(incomingPacketType, "Can't get message type from DHCP payload {}", dhcpPayload);
rsahot036620655b2018-02-26 15:01:37 -0500396 Set<Interface> receivingInterfaces = interfaceService.getInterfacesByPort(inPort);
397 //ignore the packets if dhcp client interface is not configured on onos.
398 if (receivingInterfaces.isEmpty()) {
399 log.warn("Virtual interface is not configured on {}", inPort);
400 return;
401 }
Yi Tseng51301292017-07-28 13:02:59 -0700402 switch (incomingPacketType) {
403 case DHCPDISCOVER:
Yi Tsengdcef2c22017-08-05 20:34:06 -0700404 // Add the gateway IP as virtual interface IP for server to understand
Yi Tseng51301292017-07-28 13:02:59 -0700405 // the lease to be assigned and forward the packet to dhcp server.
rsahot036620655b2018-02-26 15:01:37 -0500406 List<InternalPacket> ethernetClientPacket =
407 processDhcpPacketFromClient(context, packet, receivingInterfaces);
408 for (InternalPacket internalPacket : ethernetClientPacket) {
409 log.debug("DHCPDISCOVER from {} Forward to server", inPort);
Yi Tseng51301292017-07-28 13:02:59 -0700410 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
rsahot036620655b2018-02-26 15:01:37 -0500411 forwardPacket(internalPacket);
Yi Tseng51301292017-07-28 13:02:59 -0700412 }
413 break;
414 case DHCPOFFER:
415 //reply to dhcp client.
Taras Lemkin96a0d342018-03-26 14:52:58 +0000416 InternalPacket ethernetPacketOffer = processDhcpPacketFromServer(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700417 if (ethernetPacketOffer != null) {
Taras Lemkin96a0d342018-03-26 14:52:58 +0000418 writeResponseDhcpRecord(ethernetPacketOffer.getPacket(), dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700419 sendResponseToClient(ethernetPacketOffer, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700420 }
421 break;
422 case DHCPREQUEST:
423 // add the gateway ip as virtual interface ip for server to understand
424 // the lease to be assigned and forward the packet to dhcp server.
rsahot036620655b2018-02-26 15:01:37 -0500425 List<InternalPacket> ethernetPacketRequest =
426 processDhcpPacketFromClient(context, packet, receivingInterfaces);
427 for (InternalPacket internalPacket : ethernetPacketRequest) {
428 log.debug("DHCPDISCOVER from {} Forward to server", inPort);
Yi Tseng51301292017-07-28 13:02:59 -0700429 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
rsahot036620655b2018-02-26 15:01:37 -0500430 forwardPacket(internalPacket);
Yi Tseng51301292017-07-28 13:02:59 -0700431 }
432 break;
Charles Chand0dd7002017-10-08 23:53:36 -0400433 case DHCPDECLINE:
434 break;
Yi Tseng51301292017-07-28 13:02:59 -0700435 case DHCPACK:
436 // reply to dhcp client.
Taras Lemkin96a0d342018-03-26 14:52:58 +0000437 InternalPacket ethernetPacketAck = processDhcpPacketFromServer(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700438 if (ethernetPacketAck != null) {
Taras Lemkin96a0d342018-03-26 14:52:58 +0000439 writeResponseDhcpRecord(ethernetPacketAck.getPacket(), dhcpPayload);
440 handleDhcpAck(ethernetPacketAck.getPacket(), dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700441 sendResponseToClient(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700442 }
443 break;
Charles Chand0dd7002017-10-08 23:53:36 -0400444 case DHCPNAK:
445 break;
Yi Tseng51301292017-07-28 13:02:59 -0700446 case DHCPRELEASE:
447 // TODO: release the ip address from client
448 break;
Charles Chand0dd7002017-10-08 23:53:36 -0400449 case DHCPINFORM:
450 break;
451 case DHCPFORCERENEW:
452 break;
453 case DHCPLEASEQUERY:
454 handleLeaseQueryMsg(context, packet, dhcpPayload);
455 break;
456 case DHCPLEASEACTIVE:
457 handleLeaseQueryActivateMsg(packet, dhcpPayload);
458 break;
459 case DHCPLEASEUNASSIGNED:
460 case DHCPLEASEUNKNOWN:
461 handleLeaseQueryUnknown(packet, dhcpPayload);
462 break;
Yi Tseng51301292017-07-28 13:02:59 -0700463 default:
464 break;
465 }
466 }
467
468 /**
469 * Checks if this app has been configured.
470 *
471 * @return true if all information we need have been initialized
472 */
Yi Tseng4f2a0462017-08-31 11:21:00 -0700473 private boolean configured() {
Yi Tseng919b2df2017-09-07 16:22:51 -0700474 return !defaultServerInfoList.isEmpty();
Yi Tseng51301292017-07-28 13:02:59 -0700475 }
476
477 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -0700478 * Returns the first interface ip from interface.
Yi Tseng51301292017-07-28 13:02:59 -0700479 *
Yi Tsengdcef2c22017-08-05 20:34:06 -0700480 * @param iface interface of one connect point
Yi Tseng51301292017-07-28 13:02:59 -0700481 * @return the first interface IP; null if not exists an IP address in
482 * these interfaces
483 */
Yi Tseng3df7f9d2017-08-17 13:08:31 -0700484 private Ip4Address getFirstIpFromInterface(Interface iface) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700485 checkNotNull(iface, "Interface can't be null");
486 return iface.ipAddressesList().stream()
Yi Tseng51301292017-07-28 13:02:59 -0700487 .map(InterfaceIpAddress::ipAddress)
488 .filter(IpAddress::isIp4)
489 .map(IpAddress::getIp4Address)
490 .findFirst()
491 .orElse(null);
492 }
493
494 /**
Yi Tseng4f2a0462017-08-31 11:21:00 -0700495 * Gets Interface facing to the server for default host.
Yi Tsengdcef2c22017-08-05 20:34:06 -0700496 *
497 * @return the Interface facing to the server; null if not found
498 */
Yi Tseng919b2df2017-09-07 16:22:51 -0700499 private Interface getDefaultServerInterface() {
500 return getServerInterface(defaultServerInfoList);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700501 }
502
503 /**
Yi Tseng4f2a0462017-08-31 11:21:00 -0700504 * Gets Interface facing to the server for indirect hosts.
505 * Use default server Interface if indirect server not configured.
506 *
507 * @return the Interface facing to the server; null if not found
508 */
509 private Interface getIndirectServerInterface() {
Yi Tseng919b2df2017-09-07 16:22:51 -0700510 return getServerInterface(indirectServerInfoList);
511 }
512
513 private Interface getServerInterface(List<DhcpServerInfo> serverInfos) {
Yi Tseng3bd57ac2017-11-29 14:39:18 -0800514 return serverInfos.stream()
Yi Tseng4f2a0462017-08-31 11:21:00 -0700515 .findFirst()
Yi Tseng3bd57ac2017-11-29 14:39:18 -0800516 .map(serverInfo -> {
517 ConnectPoint dhcpServerConnectPoint =
518 serverInfo.getDhcpServerConnectPoint().orElse(null);
519 VlanId dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
520 if (dhcpServerConnectPoint == null || dhcpConnectVlan == null) {
521 return null;
522 }
523 return interfaceService.getInterfacesByPort(dhcpServerConnectPoint)
524 .stream()
525 .filter(iface -> interfaceContainsVlan(iface, dhcpConnectVlan))
526 .findFirst()
527 .orElse(null);
528 })
Yi Tseng4f2a0462017-08-31 11:21:00 -0700529 .orElse(null);
530 }
531
532 /**
533 * Determind if an Interface contains a vlan id.
534 *
535 * @param iface the Interface
536 * @param vlanId the vlan id
537 * @return true if the Interface contains the vlan id
538 */
539 private boolean interfaceContainsVlan(Interface iface, VlanId vlanId) {
Yi Tseng4025a102017-09-30 11:35:42 +0800540 if (vlanId.equals(VlanId.NONE)) {
541 // untagged packet, check if vlan untagged or vlan native is not NONE
542 return !iface.vlanUntagged().equals(VlanId.NONE) ||
543 !iface.vlanNative().equals(VlanId.NONE);
544 }
545 // tagged packet, check if the interface contains the vlan
546 return iface.vlanTagged().contains(vlanId);
Yi Tseng4f2a0462017-08-31 11:21:00 -0700547 }
548
Charles Chand0dd7002017-10-08 23:53:36 -0400549 private void handleLeaseQueryActivateMsg(Ethernet packet, DHCP dhcpPayload) {
550 log.debug("LQ: Got DHCPLEASEACTIVE packet!");
551
Taras Lemkin96a0d342018-03-26 14:52:58 +0000552 if (learnRouteFromLeasequery) {
553 // TODO: release the ip address from client
554 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
555 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
556 HostId hostId = HostId.hostId(clientMacAddress, vlanId);
557 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
Charles Chand0dd7002017-10-08 23:53:36 -0400558
Taras Lemkin96a0d342018-03-26 14:52:58 +0000559 if (record == null) {
560 log.warn("Can't find record for host {} when processing DHCPLEASEACTIVE", hostId);
561 return;
562 }
563
564 // need to update routes
565 log.debug("Lease Query for Client results in DHCPLEASEACTIVE - route needs to be modified");
566 // get current route
567 // find the ip of that client with the DhcpRelay store
568
569 Ip4Address clientIP = record.ip4Address().orElse(null);
570 log.debug("LQ: IP of host is " + clientIP.getIp4Address());
571
572 MacAddress nextHopMac = record.nextHop().orElse(null);
573 log.debug("LQ: MAC of resulting *OLD* NH for that host is " + nextHopMac.toString());
574
575 // find the new NH by looking at the src MAC of the dhcp request
576 // from the LQ store
577 MacAddress newNextHopMac = record.nextHopTemp().orElse(null);
578 log.debug("LQ: MAC of resulting *NEW* NH for that host is " + newNextHopMac.toString());
579
580 log.debug("LQ: updating dhcp relay record with new NH");
581 record.nextHop(newNextHopMac);
582
583 // find the next hop IP from its mac
584 HostId gwHostId = HostId.hostId(newNextHopMac, vlanId);
585 Host gwHost = hostService.getHost(gwHostId);
586
587 if (gwHost == null) {
588 log.warn("Can't find gateway for new NH host " + gwHostId);
589 return;
590 }
591
592 Ip4Address nextHopIp = gwHost.ipAddresses()
593 .stream()
594 .filter(IpAddress::isIp4)
595 .map(IpAddress::getIp4Address)
596 .findFirst()
597 .orElse(null);
598
599 if (nextHopIp == null) {
600 log.warn("Can't find IP address of gateway " + gwHost);
601 return;
602 }
603
604 log.debug("LQ: *NEW* NH IP for host is " + nextHopIp.getIp4Address());
605 Route route = new Route(Route.Source.DHCP, clientIP.toIpPrefix(), nextHopIp);
606 routeStore.updateRoute(route);
Charles Chand0dd7002017-10-08 23:53:36 -0400607 }
608
Charles Chand0dd7002017-10-08 23:53:36 -0400609 // and forward to client
Taras Lemkin96a0d342018-03-26 14:52:58 +0000610 InternalPacket ethernetPacket = processLeaseQueryFromServer(packet);
Charles Chand0dd7002017-10-08 23:53:36 -0400611 if (ethernetPacket != null) {
612 sendResponseToClient(ethernetPacket, dhcpPayload);
613 }
614 }
615
Charles Chand0dd7002017-10-08 23:53:36 -0400616 private void handleLeaseQueryMsg(PacketContext context, Ethernet packet, DHCP dhcpPayload) {
Taras Lemkin96a0d342018-03-26 14:52:58 +0000617 // If this flag is enabled we expect that DHCPLEASEQUERY-ies are sent from an access concentrator
618 // where queried client is connected to. Otherwise, DHCPLEASEQUERY source may be a separate connected agent
619 if (learnRouteFromLeasequery) {
620 log.debug("LQ: Got DHCPLEASEQUERY packet!");
621 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
622 log.debug("LQ: got DHCPLEASEQUERY with MAC " + clientMacAddress.toString());
623 // add the client mac (hostid) of this request to a store (the entry will be removed with
624 // the reply sent to the originator)
625 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
626 HostId hId = HostId.hostId(clientMacAddress, vlanId);
627 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hId).orElse(null);
628 if (record != null) {
629 //new NH is to be taken from src mac of LQ packet
630 MacAddress newNextHop = packet.getSourceMAC();
631 record.nextHopTemp(newNextHop);
632 record.ip4Status(dhcpPayload.getPacketType());
633 record.updateLastSeen();
634
635 // do a basic routing of the packet (this is unicast routing
636 // not a relay operation like for other broadcast dhcp packets
637 List<InternalPacket> ethernetPacketRequest = processLeaseQueryFromAgent(context, packet);
638 // and forward to server
639 for (InternalPacket internalPacket : ethernetPacketRequest) {
640 log.debug("LeaseQueryMsg forward to server");
641 forwardPacket(internalPacket);
642 }
643 } else {
644 log.warn("LQ: Error! - DHCP relay record for that client not found - ignoring LQ!");
645 }
646 } else {
647 log.debug("LQ: Got DHCPLEASEQUERY packet!");
648
649 int giaddr = dhcpPayload.getGatewayIPAddress();
650
651 log.debug("DHCPLEASEQUERY giaddr: {} ({}). Originators connectPoint: {}", giaddr,
652 Ip4Address.valueOf(giaddr), context.inPacket().receivedFrom());
Charles Chand0dd7002017-10-08 23:53:36 -0400653
654 // do a basic routing of the packet (this is unicast routing
655 // not a relay operation like for other broadcast dhcp packets
rsahot036620655b2018-02-26 15:01:37 -0500656 List<InternalPacket> ethernetPacketRequest = processLeaseQueryFromAgent(context, packet);
Charles Chand0dd7002017-10-08 23:53:36 -0400657 // and forward to server
rsahot036620655b2018-02-26 15:01:37 -0500658 for (InternalPacket internalPacket : ethernetPacketRequest) {
Taras Lemkin96a0d342018-03-26 14:52:58 +0000659 log.trace("LeaseQueryMsg forward to server connected to {}", internalPacket.getDestLocation());
rsahot036620655b2018-02-26 15:01:37 -0500660 forwardPacket(internalPacket);
661 }
Charles Chand0dd7002017-10-08 23:53:36 -0400662 }
663 }
664
665 private void handleLeaseQueryUnknown(Ethernet packet, DHCP dhcpPayload) {
666 log.debug("Lease Query for Client results in DHCPLEASEUNASSIGNED or " +
667 "DHCPLEASEUNKNOWN - removing route & forwarding reply to originator");
Taras Lemkin96a0d342018-03-26 14:52:58 +0000668 if (learnRouteFromLeasequery) {
669 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
670 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
671 HostId hostId = HostId.hostId(clientMacAddress, vlanId);
672 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
Charles Chand0dd7002017-10-08 23:53:36 -0400673
Taras Lemkin96a0d342018-03-26 14:52:58 +0000674 if (record == null) {
675 log.warn("Can't find record for host {} when handling LQ UNKNOWN/UNASSIGNED message", hostId);
676 return;
677 }
678
679 Ip4Address clientIP = record.ip4Address().orElse(null);
680 log.debug("LQ: IP of host is " + clientIP.getIp4Address());
681
682 // find the new NH by looking at the src MAC of the dhcp request
683 // from the LQ store
684 MacAddress nextHopMac = record.nextHop().orElse(null);
685 log.debug("LQ: MAC of resulting *Existing* NH for that route is " + nextHopMac.toString());
686
687 // find the next hop IP from its mac
688 HostId gwHostId = HostId.hostId(nextHopMac, vlanId);
689 Host gwHost = hostService.getHost(gwHostId);
690
691 if (gwHost == null) {
692 log.warn("Can't find gateway for new NH host " + gwHostId);
693 return;
694 }
695
696 Ip4Address nextHopIp = gwHost.ipAddresses()
697 .stream()
698 .filter(IpAddress::isIp4)
699 .map(IpAddress::getIp4Address)
700 .findFirst()
701 .orElse(null);
702
703 if (nextHopIp == null) {
704 log.warn("Can't find IP address of gateway {}", gwHost);
705 return;
706 }
707
708 log.debug("LQ: *Existing* NH IP for host is " + nextHopIp.getIp4Address() + " removing route for it");
709 Route route = new Route(Route.Source.DHCP, clientIP.toIpPrefix(), nextHopIp);
710 routeStore.removeRoute(route);
711
712 // remove from temp store
713 dhcpRelayStore.removeDhcpRecord(hostId);
Charles Chand0dd7002017-10-08 23:53:36 -0400714 }
Charles Chand0dd7002017-10-08 23:53:36 -0400715 // and forward to client
Taras Lemkin96a0d342018-03-26 14:52:58 +0000716 InternalPacket ethernetPacket = processLeaseQueryFromServer(packet);
Charles Chand0dd7002017-10-08 23:53:36 -0400717 if (ethernetPacket != null) {
718 sendResponseToClient(ethernetPacket, dhcpPayload);
719 }
720 }
721
Yi Tseng4f2a0462017-08-31 11:21:00 -0700722 /**
Yi Tseng51301292017-07-28 13:02:59 -0700723 * Build the DHCP discover/request packet with gateway IP(unicast packet).
724 *
725 * @param context the packet context
726 * @param ethernetPacket the ethernet payload to process
Yi Tseng51301292017-07-28 13:02:59 -0700727 * @return processed packet
728 */
rsahot036620655b2018-02-26 15:01:37 -0500729 private List<InternalPacket> processDhcpPacketFromClient(PacketContext context,
730 Ethernet ethernetPacket,
731 Set<Interface> clientInterfaces) {
Yi Tseng25bfe372017-11-03 16:27:32 -0700732 ConnectPoint receivedFrom = context.inPacket().receivedFrom();
733 DeviceId receivedFromDevice = receivedFrom.deviceId();
rsahot036620655b2018-02-26 15:01:37 -0500734 Ip4Address relayAgentIp = null;
Taras Lemkin96a0d342018-03-26 14:52:58 +0000735 relayAgentIp = Dhcp4HandlerUtil.getRelayAgentIPv4Address(clientInterfaces);
rsahot036620655b2018-02-26 15:01:37 -0500736 MacAddress relayAgentMac = clientInterfaces.iterator().next().mac();
737 if (relayAgentIp == null || relayAgentMac == null) {
738 log.warn("Missing DHCP relay agent interface Ipv4 addr config for "
739 + "packet from client on port: {}. Aborting packet processing",
740 clientInterfaces.iterator().next().connectPoint());
Charles Chane5e0c9a2018-03-30 12:11:34 -0700741 return Lists.newArrayList();
rsahot036620655b2018-02-26 15:01:37 -0500742 }
743 log.debug("Multi DHCP V4 processDhcpPacketFromClient on port {}",
744 clientInterfaces.iterator().next().connectPoint());
Yi Tseng25bfe372017-11-03 16:27:32 -0700745
Yi Tseng4f2a0462017-08-31 11:21:00 -0700746 // get dhcp header.
rsahot036620655b2018-02-26 15:01:37 -0500747 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
Yi Tseng4f2a0462017-08-31 11:21:00 -0700748 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
749 UDP udpPacket = (UDP) ipv4Packet.getPayload();
750 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
751
Yi Tseng919b2df2017-09-07 16:22:51 -0700752
Yi Tsengdcef2c22017-08-05 20:34:06 -0700753 Ip4Address clientInterfaceIp =
754 interfaceService.getInterfacesByPort(context.inPacket().receivedFrom())
755 .stream()
756 .map(Interface::ipAddressesList)
757 .flatMap(Collection::stream)
758 .map(InterfaceIpAddress::ipAddress)
759 .filter(IpAddress::isIp4)
760 .map(IpAddress::getIp4Address)
761 .findFirst()
762 .orElse(null);
763 if (clientInterfaceIp == null) {
764 log.warn("Can't find interface IP for client interface for port {}",
rsahot036620655b2018-02-26 15:01:37 -0500765 context.inPacket().receivedFrom());
Charles Chane5e0c9a2018-03-30 12:11:34 -0700766 return Lists.newArrayList();
Yi Tsengdcef2c22017-08-05 20:34:06 -0700767 }
rsahot036620655b2018-02-26 15:01:37 -0500768
Yi Tseng4f2a0462017-08-31 11:21:00 -0700769 boolean isDirectlyConnected = directlyConnected(dhcpPacket);
rsahot036620655b2018-02-26 15:01:37 -0500770 boolean directConnFlag = directlyConnected(dhcpPacket);
771
772 // Multi DHCP Start
773 ConnectPoint clientConnectionPoint = context.inPacket().receivedFrom();
774 VlanId vlanIdInUse = VlanId.vlanId(ethernetPacket.getVlanID());
775 Interface clientInterface = interfaceService.getInterfacesByPort(clientConnectionPoint)
Taras Lemkin96a0d342018-03-26 14:52:58 +0000776 .stream().filter(iface -> Dhcp4HandlerUtil.interfaceContainsVlan(iface, vlanIdInUse))
rsahot036620655b2018-02-26 15:01:37 -0500777 .findFirst()
778 .orElse(null);
779
780 List<InternalPacket> internalPackets = new ArrayList<>();
781 List<DhcpServerInfo> serverInfoList = findValidServerInfo(directConnFlag);
782 List<DhcpServerInfo> copyServerInfoList = new ArrayList<DhcpServerInfo>(serverInfoList);
783
784
785 for (DhcpServerInfo serverInfo : copyServerInfoList) {
786 etherReply = (Ethernet) ethernetPacket.clone();
Taras Lemkin96a0d342018-03-26 14:52:58 +0000787 ipv4Packet = (IPv4) etherReply.getPayload();
788 udpPacket = (UDP) ipv4Packet.getPayload();
789 dhcpPacket = (DHCP) udpPacket.getPayload();
rsahot036620655b2018-02-26 15:01:37 -0500790 if (!checkDhcpServerConnPt(directConnFlag, serverInfo)) {
791 log.warn("Can't get server connect point, ignore");
792 continue;
793 }
794 DhcpServerInfo newServerInfo = getHostInfoForServerInfo(serverInfo, serverInfoList);
795 if (newServerInfo == null) {
796 log.warn("Can't get server interface with host info resolved, ignore");
797 continue;
798 }
799
800 Interface serverInterface = getServerInterface(newServerInfo);
Yi Tseng3bd57ac2017-11-29 14:39:18 -0800801 if (serverInterface == null) {
rsahot036620655b2018-02-26 15:01:37 -0500802 log.warn("Can't get server interface, ignore");
803 continue;
Yi Tseng3bd57ac2017-11-29 14:39:18 -0800804 }
Yi Tseng4f2a0462017-08-31 11:21:00 -0700805
rsahot036620655b2018-02-26 15:01:37 -0500806 Ip4Address ipFacingServer = getFirstIpFromInterface(serverInterface);
807 MacAddress macFacingServer = serverInterface.mac();
808 log.debug("Interfacing server {} Mac : {} ", ipFacingServer, macFacingServer);
809 if (ipFacingServer == null || macFacingServer == null) {
810 log.warn("No IP address for server Interface {}", serverInterface);
rsahot036620655b2018-02-26 15:01:37 -0500811 continue;
812 }
Yi Tseng51301292017-07-28 13:02:59 -0700813
Charles Chand0d1e332017-10-10 16:53:32 -0400814
rsahot036620655b2018-02-26 15:01:37 -0500815 etherReply.setSourceMACAddress(macFacingServer);
816 // set default info and replace with indirect if available later on.
817 if (newServerInfo.getDhcpConnectMac().isPresent()) {
818 etherReply.setDestinationMACAddress(newServerInfo.getDhcpConnectMac().get());
819 }
820 if (newServerInfo.getDhcpConnectVlan().isPresent()) {
821 etherReply.setVlanID(newServerInfo.getDhcpConnectVlan().get().toShort());
822 }
823 ipv4Packet.setSourceAddress(ipFacingServer.toInt());
824 ipv4Packet.setDestinationAddress(newServerInfo.getDhcpServerIp4().get().toInt());
Charles Chan2de55302018-03-02 18:27:59 -0800825 log.debug("Directly connected {}", isDirectlyConnected);
826 log.debug("DHCP server IP: {}", newServerInfo.getDhcpServerIp4().get());
rsahot036620655b2018-02-26 15:01:37 -0500827 if (isDirectlyConnected) {
Yi Tseng51301292017-07-28 13:02:59 -0700828
Charles Chan2de55302018-03-02 18:27:59 -0800829 log.debug("Default DHCP server IP: {}", newServerInfo.getDhcpServerIp4().get());
rsahot036620655b2018-02-26 15:01:37 -0500830 if (newServerInfo.getDhcpConnectMac().isPresent()) {
831 etherReply.setDestinationMACAddress(newServerInfo.getDhcpConnectMac().get());
Charles Chand0d1e332017-10-10 16:53:32 -0400832 }
rsahot036620655b2018-02-26 15:01:37 -0500833 if (newServerInfo.getDhcpConnectVlan().isPresent()) {
834 etherReply.setVlanID(newServerInfo.getDhcpConnectVlan().get().toShort());
835 }
836
837 ipv4Packet.setDestinationAddress(newServerInfo.getDhcpServerIp4().get().toInt());
838
839
840 ConnectPoint inPort = context.inPacket().receivedFrom();
841 VlanId vlanId = VlanId.vlanId(ethernetPacket.getVlanID());
842 // add connected in port and vlan
843 CircuitId cid = new CircuitId(inPort.toString(), vlanId);
844 byte[] circuitId = cid.serialize();
845 DhcpOption circuitIdSubOpt = new DhcpOption();
846 circuitIdSubOpt
847 .setCode(CIRCUIT_ID.getValue())
848 .setLength((byte) circuitId.length)
849 .setData(circuitId);
850
851 DhcpRelayAgentOption newRelayAgentOpt = new DhcpRelayAgentOption();
852 newRelayAgentOpt.setCode(OptionCode_CircuitID.getValue());
853 newRelayAgentOpt.addSubOption(circuitIdSubOpt);
854
855 // Removes END option first
856 List<DhcpOption> options = dhcpPacket.getOptions().stream()
857 .filter(opt -> opt.getCode() != OptionCode_END.getValue())
858 .collect(Collectors.toList());
859
860 // push relay agent option
861 options.add(newRelayAgentOpt);
862
863 // make sure option 255(End) is the last option
864 DhcpOption endOption = new DhcpOption();
865 endOption.setCode(OptionCode_END.getValue());
866 options.add(endOption);
867
868 dhcpPacket.setOptions(options);
869
870 relayAgentIp = serverInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
871
872 // Sets relay agent IP
873 int effectiveRelayAgentIp = relayAgentIp != null ?
874 relayAgentIp.toInt() : clientInterfaceIp.toInt();
875 dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
Charles Chan2de55302018-03-02 18:27:59 -0800876 log.debug("In Default, Relay Agent IP {}", effectiveRelayAgentIp);
Charles Chand0d1e332017-10-10 16:53:32 -0400877 } else {
rsahot036620655b2018-02-26 15:01:37 -0500878 if (!newServerInfo.getDhcpServerIp4().isPresent()) {
879 // do nothing
880 } else if (!newServerInfo.getDhcpConnectMac().isPresent()) {
881 continue;
882 } else {
883 relayAgentIp = newServerInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
884 // Sets relay agent IP
885 int effectiveRelayAgentIp = relayAgentIp != null ?
886 relayAgentIp.toInt() : clientInterfaceIp.toInt();
Mayank Tiwari30149832018-10-19 12:12:44 -0400887 Ip4Address effectiveRealRealyAgentIP = relayAgentIp != null ?
888 relayAgentIp : clientInterfaceIp;
rsahot036620655b2018-02-26 15:01:37 -0500889 dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
Mayank Tiwari30149832018-10-19 12:12:44 -0400890 ipv4Packet.setSourceAddress(effectiveRealRealyAgentIP.toInt());
891 log.debug("Source IP address set as relay agent IP with value: {}", effectiveRealRealyAgentIP);
Charles Chand0d1e332017-10-10 16:53:32 -0400892 }
Yi Tseng4f2a0462017-08-31 11:21:00 -0700893 }
Yi Tseng3df7f9d2017-08-17 13:08:31 -0700894
rsahot036620655b2018-02-26 15:01:37 -0500895 // Remove broadcast flag
896 dhcpPacket.setFlags((short) 0);
897
898 udpPacket.setPayload(dhcpPacket);
899 // As a DHCP relay, the source port should be server port( instead
900 // of client port.
901 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
902 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
903 ipv4Packet.setPayload(udpPacket);
904 ipv4Packet.setTtl((byte) 64);
905 etherReply.setPayload(ipv4Packet);
Taras Lemkin96a0d342018-03-26 14:52:58 +0000906 InternalPacket internalPacket = InternalPacket.internalPacket(etherReply,
rsahot036620655b2018-02-26 15:01:37 -0500907 serverInfo.getDhcpServerConnectPoint().get());
Taras Lemkin96a0d342018-03-26 14:52:58 +0000908
rsahot036620655b2018-02-26 15:01:37 -0500909 internalPackets.add(internalPacket);
910 }
911 return internalPackets;
Yi Tseng51301292017-07-28 13:02:59 -0700912 }
913
Charles Chand0dd7002017-10-08 23:53:36 -0400914
915 /**
916 * Do a basic routing for a packet from client (used for LQ processing).
917 *
918 * @param context the packet context
919 * @param ethernetPacket the ethernet payload to process
920 * @return processed packet
921 */
rsahot036620655b2018-02-26 15:01:37 -0500922 private List<InternalPacket> processLeaseQueryFromAgent(PacketContext context,
923 Ethernet ethernetPacket) {
924 ConnectPoint receivedFrom = context.inPacket().receivedFrom();
925 DeviceId receivedFromDevice = receivedFrom.deviceId();
926
Charles Chand0dd7002017-10-08 23:53:36 -0400927 // get dhcp header.
928 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
929 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
930 UDP udpPacket = (UDP) ipv4Packet.getPayload();
931 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
932
Charles Chan2de55302018-03-02 18:27:59 -0800933 Ip4Address relayAgentIp;
Charles Chand0dd7002017-10-08 23:53:36 -0400934
Charles Chand0dd7002017-10-08 23:53:36 -0400935 Ip4Address clientInterfaceIp =
936 interfaceService.getInterfacesByPort(context.inPacket().receivedFrom())
937 .stream()
938 .map(Interface::ipAddressesList)
939 .flatMap(Collection::stream)
940 .map(InterfaceIpAddress::ipAddress)
941 .filter(IpAddress::isIp4)
942 .map(IpAddress::getIp4Address)
943 .findFirst()
944 .orElse(null);
945 if (clientInterfaceIp == null) {
946 log.warn("Can't find interface IP for client interface for port {}",
rsahot036620655b2018-02-26 15:01:37 -0500947 context.inPacket().receivedFrom());
Charles Chand0dd7002017-10-08 23:53:36 -0400948 return null;
949 }
rsahot036620655b2018-02-26 15:01:37 -0500950
Charles Chand0dd7002017-10-08 23:53:36 -0400951 boolean isDirectlyConnected = directlyConnected(dhcpPacket);
rsahot036620655b2018-02-26 15:01:37 -0500952 boolean directConnFlag = directlyConnected(dhcpPacket);
953
954 // Multi DHCP Start
rsahot036620655b2018-02-26 15:01:37 -0500955 List<InternalPacket> internalPackets = new ArrayList<>();
956 List<DhcpServerInfo> serverInfoList = findValidServerInfo(directConnFlag);
Charles Chan2de55302018-03-02 18:27:59 -0800957 List<DhcpServerInfo> copyServerInfoList = new ArrayList<>(serverInfoList);
rsahot036620655b2018-02-26 15:01:37 -0500958
959 for (DhcpServerInfo serverInfo : copyServerInfoList) {
960 // get dhcp header.
961 etherReply = (Ethernet) ethernetPacket.clone();
962 ipv4Packet = (IPv4) etherReply.getPayload();
963 udpPacket = (UDP) ipv4Packet.getPayload();
964 dhcpPacket = (DHCP) udpPacket.getPayload();
965
966 if (!checkDhcpServerConnPt(directConnFlag, serverInfo)) {
967 log.warn("Can't get server connect point, ignore");
968 continue;
Yi Tseng3bd57ac2017-11-29 14:39:18 -0800969 }
rsahot036620655b2018-02-26 15:01:37 -0500970 DhcpServerInfo newServerInfo = getHostInfoForServerInfo(serverInfo, serverInfoList);
971 if (newServerInfo == null) {
972 log.warn("Can't get server interface with host info resolved, ignore");
973 continue;
974 }
Charles Chand0dd7002017-10-08 23:53:36 -0400975
rsahot036620655b2018-02-26 15:01:37 -0500976 Interface serverInterface = getServerInterface(newServerInfo);
977 if (serverInterface == null) {
978 log.warn("Can't get server interface, ignore");
979 continue;
980 }
981 Ip4Address ipFacingServer = getFirstIpFromInterface(serverInterface);
982 MacAddress macFacingServer = serverInterface.mac();
983 if (ipFacingServer == null || macFacingServer == null) {
984 log.warn("No IP address for server Interface {}", serverInterface);
985 continue;
986 }
Charles Chand0dd7002017-10-08 23:53:36 -0400987
rsahot036620655b2018-02-26 15:01:37 -0500988 etherReply.setSourceMACAddress(macFacingServer);
Charles Chan2de55302018-03-02 18:27:59 -0800989 etherReply.setDestinationMACAddress(newServerInfo.getDhcpConnectMac().get());
990 etherReply.setVlanID(newServerInfo.getDhcpConnectVlan().get().toShort());
rsahot036620655b2018-02-26 15:01:37 -0500991 ipv4Packet.setSourceAddress(ipFacingServer.toInt());
Charles Chan2de55302018-03-02 18:27:59 -0800992 ipv4Packet.setDestinationAddress(newServerInfo.getDhcpServerIp4().get().toInt());
rsahot036620655b2018-02-26 15:01:37 -0500993 if (isDirectlyConnected) {
994 // set default info and replace with indirect if available later on.
995 if (newServerInfo.getDhcpConnectMac().isPresent()) {
996 etherReply.setDestinationMACAddress(newServerInfo.getDhcpConnectMac().get());
997 }
998 if (newServerInfo.getDhcpConnectVlan().isPresent()) {
999 etherReply.setVlanID(serverInfo.getDhcpConnectVlan().get().toShort());
1000 }
Taras Lemkin96a0d342018-03-26 14:52:58 +00001001 if (learnRouteFromLeasequery) {
rsahot036620655b2018-02-26 15:01:37 -05001002 relayAgentIp = newServerInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
1003 // Sets relay agent IP
1004 int effectiveRelayAgentIp = relayAgentIp != null ?
1005 relayAgentIp.toInt() : clientInterfaceIp.toInt();
1006 dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
Taras Lemkin96a0d342018-03-26 14:52:58 +00001007 }
1008 } else {
1009 if (!newServerInfo.getDhcpServerIp4().isPresent()) {
1010 //do nothing
1011 } else if (!newServerInfo.getDhcpConnectMac().isPresent()) {
1012 continue;
1013 } else if (learnRouteFromLeasequery) {
1014 relayAgentIp = newServerInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
1015 // Sets relay agent IP
1016 int effectiveRelayAgentIp = relayAgentIp != null ?
1017 relayAgentIp.toInt() : clientInterfaceIp.toInt();
rsahot036620655b2018-02-26 15:01:37 -05001018 dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
Taras Lemkin96a0d342018-03-26 14:52:58 +00001019 log.debug("Relay Agent IP {}", relayAgentIp);
rsahot036620655b2018-02-26 15:01:37 -05001020 }
1021
Taras Lemkin96a0d342018-03-26 14:52:58 +00001022 log.trace("Indirect");
rsahot036620655b2018-02-26 15:01:37 -05001023 }
1024
1025 // Remove broadcast flag
1026 dhcpPacket.setFlags((short) 0);
1027
1028 udpPacket.setPayload(dhcpPacket);
1029 // As a DHCP relay, the source port should be server port( instead
1030 // of client port.
1031 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
1032 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
1033 ipv4Packet.setPayload(udpPacket);
1034 ipv4Packet.setTtl((byte) 64);
1035 etherReply.setPayload(ipv4Packet);
Taras Lemkin96a0d342018-03-26 14:52:58 +00001036 udpPacket.resetChecksum();
rsahot036620655b2018-02-26 15:01:37 -05001037 ////return etherReply;
Taras Lemkin96a0d342018-03-26 14:52:58 +00001038 InternalPacket internalPacket = InternalPacket.internalPacket(etherReply,
rsahot036620655b2018-02-26 15:01:37 -05001039 newServerInfo.getDhcpServerConnectPoint().get());
Taras Lemkin96a0d342018-03-26 14:52:58 +00001040
rsahot036620655b2018-02-26 15:01:37 -05001041 internalPackets.add(internalPacket);
Charles Chand0dd7002017-10-08 23:53:36 -04001042 }
Taras Lemkin96a0d342018-03-26 14:52:58 +00001043 log.debug("num of processLeaseQueryFromAgent packets to send is: {}", internalPackets.size());
Charles Chand0dd7002017-10-08 23:53:36 -04001044
rsahot036620655b2018-02-26 15:01:37 -05001045 return internalPackets;
Charles Chand0dd7002017-10-08 23:53:36 -04001046 }
1047
1048
Yi Tseng51301292017-07-28 13:02:59 -07001049 /**
1050 * Writes DHCP record to the store according to the request DHCP packet (Discover, Request).
1051 *
1052 * @param location the location which DHCP packet comes from
1053 * @param ethernet the DHCP packet
1054 * @param dhcpPayload the DHCP payload
1055 */
1056 private void writeRequestDhcpRecord(ConnectPoint location,
1057 Ethernet ethernet,
1058 DHCP dhcpPayload) {
1059 VlanId vlanId = VlanId.vlanId(ethernet.getVlanID());
1060 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1061 HostId hostId = HostId.hostId(macAddress, vlanId);
1062 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1063 if (record == null) {
1064 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
1065 } else {
1066 record = record.clone();
1067 }
1068 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
1069 record.ip4Status(dhcpPayload.getPacketType());
1070 record.setDirectlyConnected(directlyConnected(dhcpPayload));
1071 if (!directlyConnected(dhcpPayload)) {
1072 // Update gateway mac address if the host is not directly connected
1073 record.nextHop(ethernet.getSourceMAC());
1074 }
1075 record.updateLastSeen();
1076 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
1077 }
1078
1079 /**
1080 * Writes DHCP record to the store according to the response DHCP packet (Offer, Ack).
1081 *
1082 * @param ethernet the DHCP packet
1083 * @param dhcpPayload the DHCP payload
1084 */
1085 private void writeResponseDhcpRecord(Ethernet ethernet,
1086 DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001087 Optional<Interface> outInterface = getClientInterface(ethernet, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -07001088 if (!outInterface.isPresent()) {
1089 log.warn("Failed to determine where to send {}", dhcpPayload.getPacketType());
1090 return;
1091 }
1092
1093 Interface outIface = outInterface.get();
1094 ConnectPoint location = outIface.connectPoint();
Yi Tseng4f2a0462017-08-31 11:21:00 -07001095 VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001096 if (vlanId == null) {
1097 vlanId = outIface.vlan();
1098 }
Yi Tseng51301292017-07-28 13:02:59 -07001099 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1100 HostId hostId = HostId.hostId(macAddress, vlanId);
1101 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1102 if (record == null) {
1103 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
1104 } else {
1105 record = record.clone();
1106 }
1107 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
1108 if (dhcpPayload.getPacketType() == DHCP.MsgType.DHCPACK) {
1109 record.ip4Address(Ip4Address.valueOf(dhcpPayload.getYourIPAddress()));
1110 }
1111 record.ip4Status(dhcpPayload.getPacketType());
1112 record.setDirectlyConnected(directlyConnected(dhcpPayload));
1113 record.updateLastSeen();
1114 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
1115 }
1116
1117 /**
1118 * Build the DHCP offer/ack with proper client port.
1119 *
1120 * @param ethernetPacket the original packet comes from server
1121 * @return new packet which will send to the client
1122 */
Taras Lemkin96a0d342018-03-26 14:52:58 +00001123 private InternalPacket processDhcpPacketFromServer(PacketContext context, Ethernet ethernetPacket) {
Yi Tseng51301292017-07-28 13:02:59 -07001124 // get dhcp header.
rsahot036620655b2018-02-26 15:01:37 -05001125 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
Yi Tseng51301292017-07-28 13:02:59 -07001126 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
1127 UDP udpPacket = (UDP) ipv4Packet.getPayload();
1128 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
1129
1130 // determine the vlanId of the client host - note that this vlan id
1131 // could be different from the vlan in the packet from the server
Yi Tsengdcef2c22017-08-05 20:34:06 -07001132 Interface clientInterface = getClientInterface(ethernetPacket, dhcpPayload).orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -07001133
Yi Tsengdcef2c22017-08-05 20:34:06 -07001134 if (clientInterface == null) {
Yi Tseng51301292017-07-28 13:02:59 -07001135 log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
1136 return null;
1137 }
Yi Tsengdcef2c22017-08-05 20:34:06 -07001138 VlanId vlanId;
rsahot036620655b2018-02-26 15:01:37 -05001139 ConnectPoint inPort = context.inPacket().receivedFrom();
1140 boolean directConnFlag = directlyConnected(dhcpPayload);
1141 DhcpServerInfo foundServerInfo = findServerInfoFromServer(directConnFlag, inPort);
1142
1143 if (foundServerInfo == null) {
jayakumarthazhathed5f05d2018-09-25 15:56:51 -04001144 log.warn("Cannot find server info for {} server, inPort {}",
1145 directConnFlag ? "direct" : "indirect", inPort);
rsahot036620655b2018-02-26 15:01:37 -05001146 return null;
1147 } else {
Taras Lemkin96a0d342018-03-26 14:52:58 +00001148 if (Dhcp4HandlerUtil.isServerIpEmpty(foundServerInfo)) {
rsahot036620655b2018-02-26 15:01:37 -05001149 log.warn("Cannot find server info's ipaddress");
1150 return null;
1151 }
1152 }
Yi Tsengdcef2c22017-08-05 20:34:06 -07001153 if (clientInterface.vlanTagged().isEmpty()) {
1154 vlanId = clientInterface.vlan();
1155 } else {
1156 // might be multiple vlan in same interface
Yi Tseng4f2a0462017-08-31 11:21:00 -07001157 vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001158 }
1159 if (vlanId == null) {
1160 vlanId = VlanId.NONE;
1161 }
1162 etherReply.setVlanID(vlanId.toShort());
1163 etherReply.setSourceMACAddress(clientInterface.mac());
Yi Tseng51301292017-07-28 13:02:59 -07001164
Yi Tsengdcef2c22017-08-05 20:34:06 -07001165 if (!directlyConnected(dhcpPayload)) {
1166 // if client is indirectly connected, try use next hop mac address
1167 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1168 HostId hostId = HostId.hostId(macAddress, vlanId);
Daniel Ginsburgb1dec912018-01-29 11:40:22 -08001169 if (((int) dhcpPayload.getFlags() & 0x8000) == 0x0000) {
1170 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1171 if (record != null) {
1172 // if next hop can be found, use mac address of next hop
1173 record.nextHop().ifPresent(etherReply::setDestinationMACAddress);
1174 } else {
1175 // otherwise, discard the packet
1176 log.warn("Can't find record for host id {}, discard packet", hostId);
1177 return null;
1178 }
Yi Tsengdcef2c22017-08-05 20:34:06 -07001179 } else {
Daniel Ginsburgb1dec912018-01-29 11:40:22 -08001180 etherReply.setDestinationMACAddress(MacAddress.BROADCAST);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001181 }
Yi Tseng1696f562017-08-17 17:43:38 -07001182 } else {
1183 etherReply.setDestinationMACAddress(dhcpPayload.getClientHardwareAddress());
Yi Tsengdcef2c22017-08-05 20:34:06 -07001184 }
1185
Yi Tseng3df7f9d2017-08-17 13:08:31 -07001186 Ip4Address ipFacingClient = getFirstIpFromInterface(clientInterface);
Mayank Tiwari84a538f2018-11-14 15:41:40 -05001187 if (directlyConnected(dhcpPayload)) {
1188 // we leave the srcMac from the original packet
1189 // figure out the relay agent IP corresponding to the original request
1190 if (ipFacingClient == null) {
1191 log.warn("Cannot determine relay agent interface Ipv4 addr for host {}/{}. "
1192 + "Aborting relay for dhcp packet from server {}",
1193 etherReply.getDestinationMAC(), clientInterface.vlan(),
1194 ethernetPacket);
1195 return null;
1196 }
1197 // SRC_IP: IP facing client
1198 ipv4Packet.setSourceAddress(ipFacingClient.toInt());
1199 } else {
1200 // Get the IP address of the relay agent
1201 Ip4Address relayAgentIp = foundServerInfo
1202 .getRelayAgentIp4(clientInterface.connectPoint().deviceId()).orElse(null);
1203 if (relayAgentIp == null) {
1204 if (ipFacingClient == null) {
1205 log.warn("Cannot determine relay agent interface Ipv4 addr for host {}/{}. "
1206 + "Aborting relay for dhcp packet from server for indirect host {}",
1207 etherReply.getDestinationMAC(), clientInterface.vlan(),
1208 ethernetPacket);
1209 return null;
1210 } else {
1211 // SRC_IP: IP facing client
1212 ipv4Packet.setSourceAddress(ipFacingClient.toInt());
1213 }
1214 } else {
1215 // SRC_IP: relay agent IP
1216 ipv4Packet.setSourceAddress(relayAgentIp.toInt());
1217 }
Yi Tseng51301292017-07-28 13:02:59 -07001218 }
Yi Tseng51301292017-07-28 13:02:59 -07001219 // DST_IP: offered IP
Daniel Ginsburgb1dec912018-01-29 11:40:22 -08001220 if (((int) dhcpPayload.getFlags() & 0x8000) == 0x0000) {
1221 ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
1222 } else {
1223 ipv4Packet.setDestinationAddress(BROADCAST_IP);
1224 }
Yi Tseng51301292017-07-28 13:02:59 -07001225 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
1226 if (directlyConnected(dhcpPayload)) {
1227 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
1228 } else {
Charles Chan15cd86f2018-12-21 12:41:20 -08001229 // TODO Implement component config to support for both L2 and L3 relay
1230 // L2 relay expects destination port to be CLIENT_PORT while L3 relay expects SERVER_PORT
1231 // Currently we only support L2 relay for DHCPv4
Yi Tseng93ba53c2017-09-14 13:24:21 -07001232 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
Yi Tseng51301292017-07-28 13:02:59 -07001233 }
1234
1235 udpPacket.setPayload(dhcpPayload);
1236 ipv4Packet.setPayload(udpPacket);
1237 etherReply.setPayload(ipv4Packet);
Taras Lemkin96a0d342018-03-26 14:52:58 +00001238 return InternalPacket.internalPacket(etherReply, clientInterface.connectPoint());
Yi Tseng51301292017-07-28 13:02:59 -07001239 }
1240
Yi Tsengdcef2c22017-08-05 20:34:06 -07001241 /**
Charles Chand0dd7002017-10-08 23:53:36 -04001242 * Build the DHCP offer/ack with proper client port.
1243 *
1244 * @param ethernetPacket the original packet comes from server
1245 * @return new packet which will send to the client
1246 */
Taras Lemkin96a0d342018-03-26 14:52:58 +00001247 private InternalPacket processLeaseQueryFromServer(Ethernet ethernetPacket) {
Charles Chand0dd7002017-10-08 23:53:36 -04001248 // get dhcp header.
1249 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
1250 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
1251 UDP udpPacket = (UDP) ipv4Packet.getPayload();
1252 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
1253
1254 // determine the vlanId of the client host - note that this vlan id
1255 // could be different from the vlan in the packet from the server
Taras Lemkin96a0d342018-03-26 14:52:58 +00001256 Interface clientInterface = null;
1257 MacAddress destinationMac = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1258
1259 if (!learnRouteFromLeasequery) {
1260 int giaddr = ipv4Packet.getDestinationAddress();
1261 IpAddress destinationAddress = Ip4Address.valueOf(giaddr);
1262 log.debug("DHCPLEASEQUERYRESP giaddr: {}({})", giaddr, destinationAddress);
1263
1264 Host destinationHost = hostService.getHostsByIp(destinationAddress).stream().findFirst().orElse(null);
1265 if (destinationHost != null) {
1266 destinationMac = destinationHost.mac();
1267 log.trace("DHCPLEASEQUERYRESP destination mac is: {}", destinationMac);
1268 ConnectPoint destinationLocation = destinationHost.location();
1269 log.trace("Lookup for client interface by destination location {}", destinationLocation);
1270 clientInterface = interfaceService.getInterfacesByPort(destinationLocation)
1271 .stream()
1272 .filter(iface -> interfaceContainsVlan(iface, VlanId.vlanId(etherReply.getVlanID())))
1273 .findFirst()
1274 .orElse(null);
1275 log.trace("Found Host {} by ip {}", destinationHost, destinationAddress);
1276 log.debug("DHCPLEASEQUERYRESP Client interface: {}",
1277 (clientInterface != null ? clientInterface : "not resolved"));
1278
1279 }
1280 } else {
1281 clientInterface = getClientInterface(ethernetPacket, dhcpPayload).orElse(null);
1282 }
Charles Chand0dd7002017-10-08 23:53:36 -04001283
1284 if (clientInterface == null) {
1285 log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
1286 return null;
1287 }
1288 VlanId vlanId;
1289 if (clientInterface.vlanTagged().isEmpty()) {
1290 vlanId = clientInterface.vlan();
1291 } else {
1292 // might be multiple vlan in same interface
1293 vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
1294 }
1295 if (vlanId == null) {
1296 vlanId = VlanId.NONE;
1297 }
1298 etherReply.setVlanID(vlanId.toShort());
1299 etherReply.setSourceMACAddress(clientInterface.mac());
1300
Taras Lemkin96a0d342018-03-26 14:52:58 +00001301 if (!directlyConnected(dhcpPayload) && learnRouteFromLeasequery) {
Charles Chand0dd7002017-10-08 23:53:36 -04001302 // if client is indirectly connected, try use next hop mac address
1303 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1304 HostId hostId = HostId.hostId(macAddress, vlanId);
1305 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1306 if (record != null) {
1307 // if next hop can be found, use mac address of next hop
Taras Lemkin96a0d342018-03-26 14:52:58 +00001308 Optional<MacAddress> nextHop = record.nextHopTemp();
1309 if (!nextHop.isPresent()) {
1310 nextHop = record.nextHop();
1311 }
1312 nextHop.ifPresent(etherReply::setDestinationMACAddress);
Charles Chand0dd7002017-10-08 23:53:36 -04001313 } else {
1314 // otherwise, discard the packet
1315 log.warn("Can't find record for host id {}, discard packet", hostId);
1316 return null;
1317 }
1318 } else {
Taras Lemkin96a0d342018-03-26 14:52:58 +00001319 etherReply.setDestinationMACAddress(destinationMac);
Charles Chand0dd7002017-10-08 23:53:36 -04001320 }
1321
Charles Chand0dd7002017-10-08 23:53:36 -04001322 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
Taras Lemkin96a0d342018-03-26 14:52:58 +00001323 if (directlyConnected(dhcpPayload)) {
1324 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
1325 } else {
1326 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
1327 }
Charles Chand0dd7002017-10-08 23:53:36 -04001328
1329 udpPacket.setPayload(dhcpPayload);
1330 ipv4Packet.setPayload(udpPacket);
1331 etherReply.setPayload(ipv4Packet);
Taras Lemkin96a0d342018-03-26 14:52:58 +00001332 udpPacket.resetChecksum();
1333
1334 return InternalPacket.internalPacket(etherReply, clientInterface.connectPoint());
Charles Chand0dd7002017-10-08 23:53:36 -04001335 }
1336 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -07001337 * Extracts VLAN ID from relay agent option.
1338 *
1339 * @param dhcpPayload the DHCP payload
1340 * @return VLAN ID from DHCP payload; null if not exists
1341 */
Yi Tseng4f2a0462017-08-31 11:21:00 -07001342 private VlanId getVlanIdFromRelayAgentOption(DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001343 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
1344 if (option == null) {
1345 return null;
1346 }
1347 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
1348 if (circuitIdSubOption == null) {
1349 return null;
1350 }
1351 try {
1352 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
1353 return circuitId.vlanId();
1354 } catch (IllegalArgumentException e) {
1355 // can't deserialize the circuit ID
1356 return null;
1357 }
1358 }
1359
1360 /**
1361 * Removes DHCP relay agent information option (option 82) from DHCP payload.
1362 * Also reset giaddr to 0
1363 *
1364 * @param ethPacket the Ethernet packet to be processed
1365 * @return Ethernet packet processed
1366 */
1367 private Ethernet removeRelayAgentOption(Ethernet ethPacket) {
rsahot036620655b2018-02-26 15:01:37 -05001368 Ethernet ethernet = (Ethernet) ethPacket.duplicate();
Yi Tsengdcef2c22017-08-05 20:34:06 -07001369 IPv4 ipv4 = (IPv4) ethernet.getPayload();
1370 UDP udp = (UDP) ipv4.getPayload();
1371 DHCP dhcpPayload = (DHCP) udp.getPayload();
1372
1373 // removes relay agent information option
1374 List<DhcpOption> options = dhcpPayload.getOptions();
1375 options = options.stream()
1376 .filter(option -> option.getCode() != OptionCode_CircuitID.getValue())
1377 .collect(Collectors.toList());
1378 dhcpPayload.setOptions(options);
1379 dhcpPayload.setGatewayIPAddress(0);
1380
1381 udp.setPayload(dhcpPayload);
1382 ipv4.setPayload(udp);
1383 ethernet.setPayload(ipv4);
1384 return ethernet;
1385 }
1386
Taras Lemkin96a0d342018-03-26 14:52:58 +00001387 private boolean isDhcpPacketLeasequery(DHCP dhcpPacket) {
1388 switch (dhcpPacket.getPacketType()) {
1389 case DHCPLEASEACTIVE:
1390 case DHCPLEASEQUERY:
1391 case DHCPLEASEUNASSIGNED:
1392 case DHCPLEASEUNKNOWN:
1393 return true;
1394 default:
1395 return false;
1396 }
1397 }
Yi Tseng51301292017-07-28 13:02:59 -07001398
1399 /**
1400 * Check if the host is directly connected to the network or not.
1401 *
1402 * @param dhcpPayload the dhcp payload
1403 * @return true if the host is directly connected to the network; false otherwise
1404 */
1405 private boolean directlyConnected(DHCP dhcpPayload) {
Taras Lemkin96a0d342018-03-26 14:52:58 +00001406 // leasequery is always indirect
1407 if (isDhcpPacketLeasequery(dhcpPayload)) {
1408 return false;
1409 }
1410
Yi Tseng440e2b72017-08-24 14:47:34 -07001411 DhcpRelayAgentOption relayAgentOption =
1412 (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
Yi Tseng51301292017-07-28 13:02:59 -07001413
1414 // Doesn't contains relay option
1415 if (relayAgentOption == null) {
1416 return true;
1417 }
1418
Yi Tseng440e2b72017-08-24 14:47:34 -07001419 // check circuit id, if circuit id is invalid, we say it is an indirect host
1420 DhcpOption circuitIdOpt = relayAgentOption.getSubOption(CIRCUIT_ID.getValue());
Yi Tseng51301292017-07-28 13:02:59 -07001421
Yi Tseng440e2b72017-08-24 14:47:34 -07001422 try {
1423 CircuitId.deserialize(circuitIdOpt.getData());
Yi Tseng51301292017-07-28 13:02:59 -07001424 return true;
Yi Tseng440e2b72017-08-24 14:47:34 -07001425 } catch (Exception e) {
1426 // invalid circuit id
1427 return false;
Yi Tseng51301292017-07-28 13:02:59 -07001428 }
Yi Tseng51301292017-07-28 13:02:59 -07001429 }
1430
1431
1432 /**
1433 * Send the DHCP ack to the requester host.
1434 * Modify Host or Route store according to the type of DHCP.
1435 *
1436 * @param ethernetPacketAck the packet
1437 * @param dhcpPayload the DHCP data
1438 */
1439 private void handleDhcpAck(Ethernet ethernetPacketAck, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001440 Optional<Interface> outInterface = getClientInterface(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -07001441 if (!outInterface.isPresent()) {
1442 log.warn("Can't find output interface for dhcp: {}", dhcpPayload);
1443 return;
1444 }
1445
1446 Interface outIface = outInterface.get();
1447 HostLocation hostLocation = new HostLocation(outIface.connectPoint(), System.currentTimeMillis());
1448 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
Yi Tseng4f2a0462017-08-31 11:21:00 -07001449 VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001450 if (vlanId == null) {
1451 vlanId = outIface.vlan();
1452 }
Yi Tseng51301292017-07-28 13:02:59 -07001453 HostId hostId = HostId.hostId(macAddress, vlanId);
1454 Ip4Address ip = Ip4Address.valueOf(dhcpPayload.getYourIPAddress());
1455
1456 if (directlyConnected(dhcpPayload)) {
1457 // Add to host store if it connect to network directly
1458 Set<IpAddress> ips = Sets.newHashSet(ip);
Yi Tsengaa417a62017-09-08 17:22:51 -07001459 Host host = hostService.getHost(hostId);
Yi Tseng51301292017-07-28 13:02:59 -07001460
Yi Tsengaa417a62017-09-08 17:22:51 -07001461 Set<HostLocation> hostLocations = Sets.newHashSet(hostLocation);
1462 if (host != null) {
1463 // Dual homing support:
1464 // if host exists, use old locations and new location
1465 hostLocations.addAll(host.locations());
1466 }
1467 HostDescription desc = new DefaultHostDescription(macAddress, vlanId,
1468 hostLocations, ips, false);
1469 // Add IP address when dhcp server give the host new ip address
1470 providerService.hostDetected(hostId, desc, false);
Yi Tseng51301292017-07-28 13:02:59 -07001471 } else {
1472 // Add to route store if it does not connect to network directly
1473 // Get gateway host IP according to host mac address
Yi Tsengdcef2c22017-08-05 20:34:06 -07001474 // TODO: remove relay store here
Yi Tseng51301292017-07-28 13:02:59 -07001475 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1476
1477 if (record == null) {
1478 log.warn("Can't find DHCP record of host {}", hostId);
1479 return;
1480 }
1481
1482 MacAddress gwMac = record.nextHop().orElse(null);
1483 if (gwMac == null) {
1484 log.warn("Can't find gateway mac address from record {}", record);
1485 return;
1486 }
1487
1488 HostId gwHostId = HostId.hostId(gwMac, record.vlanId());
1489 Host gwHost = hostService.getHost(gwHostId);
1490
1491 if (gwHost == null) {
1492 log.warn("Can't find gateway host {}", gwHostId);
1493 return;
1494 }
1495
1496 Ip4Address nextHopIp = gwHost.ipAddresses()
1497 .stream()
1498 .filter(IpAddress::isIp4)
1499 .map(IpAddress::getIp4Address)
1500 .findFirst()
1501 .orElse(null);
1502
1503 if (nextHopIp == null) {
1504 log.warn("Can't find IP address of gateway {}", gwHost);
1505 return;
1506 }
1507
Charles Chan6305b692018-04-04 11:43:54 -07001508 Route route = new Route(Route.Source.DHCP, ip.toIpPrefix(), nextHopIp);
Daniel Ginsburg83b76452018-06-09 01:43:59 +03001509 routeStore.replaceRoute(route);
Yi Tseng51301292017-07-28 13:02:59 -07001510 }
Yi Tseng51301292017-07-28 13:02:59 -07001511 }
1512
1513 /**
Yi Tseng51301292017-07-28 13:02:59 -07001514 * Gets output interface of a dhcp packet.
1515 * If option 82 exists in the dhcp packet and the option was sent by
Yi Tseng4f2a0462017-08-31 11:21:00 -07001516 * ONOS (circuit format is correct), use the connect
Yi Tseng51301292017-07-28 13:02:59 -07001517 * point and vlan id from circuit id; otherwise, find host by destination
1518 * address and use vlan id from sender (dhcp server).
1519 *
1520 * @param ethPacket the ethernet packet
1521 * @param dhcpPayload the dhcp packet
1522 * @return an interface represent the output port and vlan; empty value
1523 * if the host or circuit id not found
1524 */
Yi Tsengdcef2c22017-08-05 20:34:06 -07001525 private Optional<Interface> getClientInterface(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tseng51301292017-07-28 13:02:59 -07001526 VlanId originalPacketVlanId = VlanId.vlanId(ethPacket.getVlanID());
Yi Tseng51301292017-07-28 13:02:59 -07001527 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
1528
Yi Tseng4f2a0462017-08-31 11:21:00 -07001529 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
1530 try {
1531 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
1532 ConnectPoint connectPoint = ConnectPoint.deviceConnectPoint(circuitId.connectPoint());
1533 VlanId vlanId = circuitId.vlanId();
1534 return interfaceService.getInterfacesByPort(connectPoint)
1535 .stream()
1536 .filter(iface -> interfaceContainsVlan(iface, vlanId))
1537 .findFirst();
1538 } catch (IllegalArgumentException ex) {
1539 // invalid circuit format, didn't sent by ONOS
1540 log.debug("Invalid circuit {}, use information from dhcp payload",
1541 circuitIdSubOption.getData());
Yi Tseng51301292017-07-28 13:02:59 -07001542 }
Yi Tseng51301292017-07-28 13:02:59 -07001543 // Use Vlan Id from DHCP server if DHCP relay circuit id was not
1544 // sent by ONOS or circuit Id can't be parsed
Yi Tsengdcef2c22017-08-05 20:34:06 -07001545 // TODO: remove relay store from this method
Yi Tseng51301292017-07-28 13:02:59 -07001546 MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
jayakumarthazhathed5f05d2018-09-25 15:56:51 -04001547 VlanId filteredVlanId = getVlanIdFromDhcpRecord(dstMac, originalPacketVlanId);
1548 // Get the vlan from the dhcp record
1549 if (filteredVlanId == null) {
1550 log.debug("not find the matching DHCP record for mac: {} and vlan: {}", dstMac, originalPacketVlanId);
1551 return Optional.empty();
1552 }
jayakumarthazhathed5f05d2018-09-25 15:56:51 -04001553 Optional<DhcpRecord> dhcpRecord = dhcpRelayStore.getDhcpRecord(HostId.hostId(dstMac, filteredVlanId));
Yi Tsengdcef2c22017-08-05 20:34:06 -07001554 ConnectPoint clientConnectPoint = dhcpRecord
Yi Tseng51301292017-07-28 13:02:59 -07001555 .map(DhcpRecord::locations)
1556 .orElse(Collections.emptySet())
1557 .stream()
1558 .reduce((hl1, hl2) -> {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001559 // find latest host connect point
Yi Tseng51301292017-07-28 13:02:59 -07001560 if (hl1 == null || hl2 == null) {
1561 return hl1 == null ? hl2 : hl1;
1562 }
1563 return hl1.time() > hl2.time() ? hl1 : hl2;
1564 })
Yi Tsengdcef2c22017-08-05 20:34:06 -07001565 .orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -07001566
Yi Tsengdcef2c22017-08-05 20:34:06 -07001567 if (clientConnectPoint != null) {
1568 return interfaceService.getInterfacesByPort(clientConnectPoint)
1569 .stream()
jayakumarthazhathed5f05d2018-09-25 15:56:51 -04001570 .filter(iface -> interfaceContainsVlan(iface, filteredVlanId))
Yi Tsengdcef2c22017-08-05 20:34:06 -07001571 .findFirst();
1572 }
1573 return Optional.empty();
Yi Tseng51301292017-07-28 13:02:59 -07001574 }
1575
1576 /**
jayakumarthazhathed5f05d2018-09-25 15:56:51 -04001577 * Get the required vlanId in case the DCHP record has more than one vlanId for a given MAC.
1578 *
1579 * @param mac MAC address of the DHCP client
1580 * @param vlan Expected vlan of the DHCP client
1581 */
1582 private VlanId getVlanIdFromDhcpRecord(MacAddress mac, VlanId vlan) {
1583 // Get all the DHCP records matching with the mac address
1584 // If only one entry is present then pick the vlan of that entry
1585 // If more then one entry is present then look for an entry with matching vlan
1586 // else return null
1587 Collection<DhcpRecord> records = dhcpRelayStore.getDhcpRecords();
1588 List<DhcpRecord> filteredRecords = new ArrayList<>();
1589 for (DhcpRecord e: records) {
1590 if (e.macAddress().equals(mac)) {
1591 filteredRecords.add(e);
1592 }
1593 }
1594 log.debug("getVlanIdFromDhcpRecord mac: {} vlan: {}", mac, vlan);
1595 log.debug("filteredRecords are: {}", filteredRecords);
1596 if (filteredRecords.size() == 1) {
1597 log.debug("Only one DHCP record entry. Returning back the vlan of that DHCP record: {}", filteredRecords);
1598 return filteredRecords.get(0).vlanId();
1599 }
1600 // Check in the DHCP filtered record for matching vlan
1601 for (DhcpRecord e: filteredRecords) {
1602 if (e.vlanId().equals(vlan)) {
1603 log.debug("Found a matching vlan entry in the DHCP record:{}", e);
1604 return vlan;
1605 }
1606 }
1607 // Found nothing return null
1608 log.debug("Returning null as no matching or more than one matching entry found");
1609 return null;
1610
1611 }
1612
jayakumarthazhathed5f05d2018-09-25 15:56:51 -04001613 /**
Yi Tseng51301292017-07-28 13:02:59 -07001614 * Send the response DHCP to the requester host.
1615 *
Taras Lemkin96a0d342018-03-26 14:52:58 +00001616 * @param thePacket the packet
Yi Tseng51301292017-07-28 13:02:59 -07001617 * @param dhcpPayload the DHCP data
1618 */
Taras Lemkin96a0d342018-03-26 14:52:58 +00001619 private void sendResponseToClient(InternalPacket thePacket, DHCP dhcpPayload) {
1620 checkNotNull(thePacket, "Nothing to send");
1621 checkNotNull(thePacket.getPacket(), "Packet to send must not be empty");
1622 checkNotNull(thePacket.getDestLocation(), "Packet destination not be empty");
1623
1624 Ethernet ethPacket = thePacket.getPacket();
Yi Tsengdcef2c22017-08-05 20:34:06 -07001625 if (directlyConnected(dhcpPayload)) {
1626 ethPacket = removeRelayAgentOption(ethPacket);
1627 }
Taras Lemkin96a0d342018-03-26 14:52:58 +00001628
Yi Tsengdcef2c22017-08-05 20:34:06 -07001629 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
Taras Lemkin96a0d342018-03-26 14:52:58 +00001630 .setOutput(thePacket.getDestLocation().port())
Yi Tsengdcef2c22017-08-05 20:34:06 -07001631 .build();
1632 OutboundPacket o = new DefaultOutboundPacket(
Taras Lemkin96a0d342018-03-26 14:52:58 +00001633 thePacket.getDestLocation().deviceId(),
Yi Tsengdcef2c22017-08-05 20:34:06 -07001634 treatment,
1635 ByteBuffer.wrap(ethPacket.serialize()));
1636 if (log.isTraceEnabled()) {
Taras Lemkin96a0d342018-03-26 14:52:58 +00001637 log.trace("Relaying packet to DHCP client {} via {}",
Yi Tsengdcef2c22017-08-05 20:34:06 -07001638 ethPacket,
Taras Lemkin96a0d342018-03-26 14:52:58 +00001639 thePacket.getDestLocation());
Yi Tsengdcef2c22017-08-05 20:34:06 -07001640 }
1641 packetService.emit(o);
Yi Tseng51301292017-07-28 13:02:59 -07001642 }
Yi Tseng483ac6f2017-08-02 15:03:31 -07001643
Yi Tsengaa417a62017-09-08 17:22:51 -07001644 @Override
1645 public void triggerProbe(Host host) {
1646 // Do nothing here
1647 }
1648
1649 @Override
1650 public ProviderId id() {
Charles Chand988c282017-09-12 17:09:32 -07001651 return PROVIDER_ID;
Yi Tsengaa417a62017-09-08 17:22:51 -07001652 }
1653
Yi Tseng483ac6f2017-08-02 15:03:31 -07001654 class InternalHostListener implements HostListener {
1655 @Override
1656 public void event(HostEvent event) {
Yi Tseng919b2df2017-09-07 16:22:51 -07001657 if (!configured()) {
1658 return;
1659 }
Yi Tseng483ac6f2017-08-02 15:03:31 -07001660 switch (event.type()) {
1661 case HOST_ADDED:
1662 case HOST_UPDATED:
Charles Chan24a96ff2018-08-13 10:32:03 -07001663 case HOST_MOVED:
Jordan Halterman6328db72018-04-10 13:34:50 -04001664 log.trace("Scheduled host event {}", event);
1665 hostEventExecutor.execute(() -> hostUpdated(event.subject()));
Yi Tseng483ac6f2017-08-02 15:03:31 -07001666 break;
1667 case HOST_REMOVED:
Jordan Halterman6328db72018-04-10 13:34:50 -04001668 log.trace("Scheduled host event {}", event);
1669 hostEventExecutor.execute(() -> hostRemoved(event.subject()));
Yi Tseng483ac6f2017-08-02 15:03:31 -07001670 break;
Yi Tseng483ac6f2017-08-02 15:03:31 -07001671 default:
1672 break;
1673 }
1674 }
1675 }
1676
1677 /**
Yi Tseng483ac6f2017-08-02 15:03:31 -07001678 * Handle host updated.
1679 * If the host is DHCP server or gateway, update connect mac and vlan.
1680 *
1681 * @param host the host
1682 */
1683 private void hostUpdated(Host host) {
Yi Tseng525ff402017-10-23 19:39:39 -07001684 hostUpdated(host, defaultServerInfoList);
1685 hostUpdated(host, indirectServerInfoList);
Yi Tseng483ac6f2017-08-02 15:03:31 -07001686 }
1687
Yi Tseng525ff402017-10-23 19:39:39 -07001688 private void hostUpdated(Host host, List<DhcpServerInfo> srverInfoList) {
jjosep004c05f55ab2018-08-21 09:01:10 -04001689 srverInfoList.stream().forEach(serverInfo -> {
1690 Ip4Address targetIp = serverInfo.getDhcpGatewayIp4().orElse(null);
Yi Tseng525ff402017-10-23 19:39:39 -07001691 Ip4Address serverIp = serverInfo.getDhcpServerIp4().orElse(null);
Yi Tseng525ff402017-10-23 19:39:39 -07001692 if (targetIp == null) {
1693 targetIp = serverIp;
1694 }
Yi Tseng525ff402017-10-23 19:39:39 -07001695 if (targetIp != null) {
1696 if (host.ipAddresses().contains(targetIp)) {
1697 serverInfo.setDhcpConnectMac(host.mac());
1698 serverInfo.setDhcpConnectVlan(host.vlan());
1699 requestDhcpPacket(serverIp);
1700 }
1701 }
jjosep004c05f55ab2018-08-21 09:01:10 -04001702 });
Yi Tseng525ff402017-10-23 19:39:39 -07001703 }
1704
1705
Yi Tseng483ac6f2017-08-02 15:03:31 -07001706 /**
1707 * Handle host removed.
1708 * If the host is DHCP server or gateway, unset connect mac and vlan.
1709 *
1710 * @param host the host
1711 */
1712 private void hostRemoved(Host host) {
Yi Tseng525ff402017-10-23 19:39:39 -07001713 hostRemoved(host, defaultServerInfoList);
1714 hostRemoved(host, indirectServerInfoList);
1715 }
1716
1717 private void hostRemoved(Host host, List<DhcpServerInfo> serverInfoList) {
jjosep004c05f55ab2018-08-21 09:01:10 -04001718 serverInfoList.stream().forEach(serverInfo -> {
1719 Ip4Address targetIp = serverInfo.getDhcpGatewayIp4().orElse(null);
Yi Tseng525ff402017-10-23 19:39:39 -07001720 Ip4Address serverIp = serverInfo.getDhcpServerIp4().orElse(null);
Yi Tseng525ff402017-10-23 19:39:39 -07001721 if (targetIp == null) {
1722 targetIp = serverIp;
Yi Tseng919b2df2017-09-07 16:22:51 -07001723 }
Yi Tseng525ff402017-10-23 19:39:39 -07001724
1725 if (targetIp != null) {
1726 if (host.ipAddresses().contains(targetIp)) {
Yi Tseng919b2df2017-09-07 16:22:51 -07001727 serverInfo.setDhcpConnectVlan(null);
1728 serverInfo.setDhcpConnectMac(null);
Yi Tseng525ff402017-10-23 19:39:39 -07001729 cancelDhcpPacket(serverIp);
Yi Tseng919b2df2017-09-07 16:22:51 -07001730 }
Yi Tseng483ac6f2017-08-02 15:03:31 -07001731 }
jjosep004c05f55ab2018-08-21 09:01:10 -04001732 });
Yi Tseng525ff402017-10-23 19:39:39 -07001733 }
Yi Tseng919b2df2017-09-07 16:22:51 -07001734
Yi Tseng525ff402017-10-23 19:39:39 -07001735 private void requestDhcpPacket(Ip4Address serverIp) {
1736 requestServerDhcpPacket(serverIp);
1737 requestClientDhcpPacket(serverIp);
1738 }
Yi Tseng919b2df2017-09-07 16:22:51 -07001739
Yi Tseng525ff402017-10-23 19:39:39 -07001740 private void cancelDhcpPacket(Ip4Address serverIp) {
1741 cancelServerDhcpPacket(serverIp);
1742 cancelClientDhcpPacket(serverIp);
1743 }
1744
1745 private void cancelServerDhcpPacket(Ip4Address serverIp) {
1746 TrafficSelector serverSelector =
1747 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1748 .matchIPSrc(serverIp.toIpPrefix())
1749 .build();
1750 packetService.cancelPackets(serverSelector,
1751 PacketPriority.CONTROL,
1752 appId);
1753 }
1754
1755 private void requestServerDhcpPacket(Ip4Address serverIp) {
1756 TrafficSelector serverSelector =
1757 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1758 .matchIPSrc(serverIp.toIpPrefix())
1759 .build();
1760 packetService.requestPackets(serverSelector,
1761 PacketPriority.CONTROL,
1762 appId);
1763 }
1764
1765 private void cancelClientDhcpPacket(Ip4Address serverIp) {
1766 // Packet comes from relay
1767 TrafficSelector indirectClientSelector =
1768 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1769 .matchIPDst(serverIp.toIpPrefix())
1770 .build();
1771 packetService.cancelPackets(indirectClientSelector,
1772 PacketPriority.CONTROL,
1773 appId);
1774
1775 // Packet comes from client
1776 packetService.cancelPackets(CLIENT_SERVER_SELECTOR,
1777 PacketPriority.CONTROL,
1778 appId);
1779 }
1780
1781 private void requestClientDhcpPacket(Ip4Address serverIp) {
1782 // Packet comes from relay
1783 TrafficSelector indirectClientSelector =
1784 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1785 .matchIPDst(serverIp.toIpPrefix())
1786 .build();
1787 packetService.requestPackets(indirectClientSelector,
1788 PacketPriority.CONTROL,
1789 appId);
1790
1791 // Packet comes from client
1792 packetService.requestPackets(CLIENT_SERVER_SELECTOR,
1793 PacketPriority.CONTROL,
1794 appId);
1795 }
1796
1797 /**
1798 * Process the ignore rules.
1799 *
1800 * @param deviceId the device id
1801 * @param vlanId the vlan to be ignored
1802 * @param op the operation, ADD to install; REMOVE to uninstall rules
1803 */
1804 private void processIgnoreVlanRule(DeviceId deviceId, VlanId vlanId, Objective.Operation op) {
Yi Tseng525ff402017-10-23 19:39:39 -07001805 AtomicInteger installedCount = new AtomicInteger(DHCP_SELECTORS.size());
1806 DHCP_SELECTORS.forEach(trafficSelector -> {
1807 TrafficSelector selector = DefaultTrafficSelector.builder(trafficSelector)
1808 .matchVlanId(vlanId)
1809 .build();
1810
1811 ForwardingObjective.Builder builder = DefaultForwardingObjective.builder()
1812 .withFlag(ForwardingObjective.Flag.VERSATILE)
1813 .withSelector(selector)
1814 .withPriority(IGNORE_CONTROL_PRIORITY)
Yi Tsengdbabeed2017-11-29 10:49:20 -08001815 .withTreatment(DefaultTrafficTreatment.emptyTreatment())
Yi Tseng525ff402017-10-23 19:39:39 -07001816 .fromApp(appId);
1817
1818
1819 ObjectiveContext objectiveContext = new ObjectiveContext() {
1820 @Override
1821 public void onSuccess(Objective objective) {
1822 log.info("Ignore rule {} (Vlan id {}, device {}, selector {})",
1823 op, vlanId, deviceId, selector);
1824 int countDown = installedCount.decrementAndGet();
1825 if (countDown != 0) {
1826 return;
1827 }
1828 switch (op) {
1829 case ADD:
1830 ignoredVlans.put(deviceId, vlanId);
1831 break;
1832 case REMOVE:
1833 ignoredVlans.remove(deviceId, vlanId);
1834 break;
1835 default:
1836 log.warn("Unsupported objective operation {}", op);
1837 break;
1838 }
Yi Tseng919b2df2017-09-07 16:22:51 -07001839 }
Yi Tseng525ff402017-10-23 19:39:39 -07001840
1841 @Override
1842 public void onError(Objective objective, ObjectiveError error) {
1843 log.warn("Can't {} ignore rule (vlan id {}, selector {}, device {}) due to {}",
1844 op, vlanId, selector, deviceId, error);
Yi Tseng919b2df2017-09-07 16:22:51 -07001845 }
Yi Tseng525ff402017-10-23 19:39:39 -07001846 };
1847
1848 ForwardingObjective fwd;
1849 switch (op) {
1850 case ADD:
1851 fwd = builder.add(objectiveContext);
1852 break;
1853 case REMOVE:
1854 fwd = builder.remove(objectiveContext);
1855 break;
1856 default:
1857 log.warn("Unsupported objective operation {}", op);
1858 return;
Yi Tseng4f2a0462017-08-31 11:21:00 -07001859 }
Yi Tseng525ff402017-10-23 19:39:39 -07001860
1861 Device device = deviceService.getDevice(deviceId);
1862 if (device == null || !device.is(Pipeliner.class)) {
1863 log.warn("Device {} is not available now, wait until device is available", deviceId);
1864 return;
1865 }
1866 flowObjectiveService.apply(deviceId, fwd);
1867 });
Yi Tseng483ac6f2017-08-02 15:03:31 -07001868 }
Kalhee Kimba366062017-11-07 16:32:09 +00001869
1870 @Override
1871 public void setDhcpFpmEnabled(Boolean enabled) {
1872 // v4 does not use fpm. Do nothing.
1873 }
rsahot036620655b2018-02-26 15:01:37 -05001874 private List<DhcpServerInfo> findValidServerInfo(boolean directConnFlag) {
1875 List<DhcpServerInfo> validServerInfo;
1876
1877 if (directConnFlag || indirectServerInfoList.isEmpty()) {
1878 validServerInfo = new ArrayList<DhcpServerInfo>(defaultServerInfoList);
1879 } else {
1880 validServerInfo = new ArrayList<DhcpServerInfo>(indirectServerInfoList);
1881 }
1882 return validServerInfo;
1883 }
1884
rsahot036620655b2018-02-26 15:01:37 -05001885 private boolean checkDhcpServerConnPt(boolean directConnFlag,
1886 DhcpServerInfo serverInfo) {
1887 if (serverInfo.getDhcpServerConnectPoint() == null) {
1888 log.warn("DHCP4 server connect point for {} connPt {}",
1889 directConnFlag ? "direct" : "indirect", serverInfo.getDhcpServerConnectPoint());
1890 return false;
1891 }
1892 return true;
1893 }
1894
1895 /**
1896 * Checks if serverInfo's host info (mac and vlan) is filled in; if not, fills in.
1897 *
1898 * @param serverInfo server information
1899 * @return newServerInfo if host info can be either found or filled in.
1900 */
1901 private DhcpServerInfo getHostInfoForServerInfo(DhcpServerInfo serverInfo, List<DhcpServerInfo> sererInfoList) {
1902 DhcpServerInfo newServerInfo = null;
1903 MacAddress dhcpServerConnectMac = serverInfo.getDhcpConnectMac().orElse(null);
1904 VlanId dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
1905 ConnectPoint dhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
1906
1907 if (dhcpServerConnectMac != null && dhcpConnectVlan != null) {
1908 newServerInfo = serverInfo;
Charles Chan2de55302018-03-02 18:27:59 -08001909 log.debug("DHCP server {} host info found. ConnectPt{} Mac {} vlan {}", serverInfo.getDhcpServerIp4(),
rsahot036620655b2018-02-26 15:01:37 -05001910 dhcpServerConnectPoint, dhcpServerConnectMac, dhcpConnectVlan);
1911 } else {
1912 log.warn("DHCP server {} not resolve yet connectPt {} mac {} vlan {}", serverInfo.getDhcpServerIp4(),
1913 dhcpServerConnectPoint, dhcpServerConnectMac, dhcpConnectVlan);
1914
1915 Ip4Address ipToProbe;
1916 if (serverInfo.getDhcpGatewayIp4().isPresent()) {
1917 ipToProbe = serverInfo.getDhcpGatewayIp4().get();
1918 } else {
1919 ipToProbe = serverInfo.getDhcpServerIp4().orElse(null);
1920 }
1921 String hostToProbe = serverInfo.getDhcpGatewayIp6()
1922 .map(ip -> "gateway").orElse("server");
1923
1924 log.warn("Dynamically probing to resolve {} IP {}", hostToProbe, ipToProbe);
1925 hostService.startMonitoringIp(ipToProbe);
1926
1927 Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
1928 if (!hosts.isEmpty()) {
1929 int serverInfoIndex = sererInfoList.indexOf(serverInfo);
1930 Host host = hosts.iterator().next();
1931 serverInfo.setDhcpConnectVlan(host.vlan());
1932 serverInfo.setDhcpConnectMac(host.mac());
1933 // replace the serverInfo in the list
1934 sererInfoList.set(serverInfoIndex, serverInfo);
1935 newServerInfo = serverInfo;
1936 log.warn("Dynamically host found host {}", host);
1937 } else {
1938 log.warn("No host found host ip {} dynamically", ipToProbe);
1939 }
1940 }
1941 return newServerInfo;
1942 }
1943
1944 /**
1945 * Gets Interface facing to the server for default host.
1946 *
1947 * @param serverInfo server information
1948 * @return the Interface facing to the server; null if not found
1949 */
1950 private Interface getServerInterface(DhcpServerInfo serverInfo) {
1951 Interface serverInterface = null;
1952
1953 ConnectPoint dhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
1954 VlanId dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
1955
1956 if (dhcpServerConnectPoint != null && dhcpConnectVlan != null) {
1957 serverInterface = interfaceService.getInterfacesByPort(dhcpServerConnectPoint)
1958 .stream()
Taras Lemkin96a0d342018-03-26 14:52:58 +00001959 .filter(iface -> Dhcp4HandlerUtil.interfaceContainsVlan(iface, dhcpConnectVlan))
rsahot036620655b2018-02-26 15:01:37 -05001960 .findFirst()
1961 .orElse(null);
1962 } else {
1963 log.warn("DHCP server {} not resolve yet connectPoint {} vlan {}", serverInfo.getDhcpServerIp6(),
1964 dhcpServerConnectPoint, dhcpConnectVlan);
1965 }
1966
1967 return serverInterface;
1968 }
1969
1970 //forward the packet to ConnectPoint where the DHCP server is attached.
1971 private void forwardPacket(InternalPacket packet) {
1972 //send Packetout to dhcp server connectpoint.
Taras Lemkin96a0d342018-03-26 14:52:58 +00001973 if (packet.getDestLocation() != null) {
rsahot036620655b2018-02-26 15:01:37 -05001974 TrafficTreatment t = DefaultTrafficTreatment.builder()
Taras Lemkin96a0d342018-03-26 14:52:58 +00001975 .setOutput(packet.getDestLocation().port()).build();
rsahot036620655b2018-02-26 15:01:37 -05001976 OutboundPacket o = new DefaultOutboundPacket(
Taras Lemkin96a0d342018-03-26 14:52:58 +00001977 packet.getDestLocation().deviceId(), t, ByteBuffer.wrap(packet.getPacket().serialize()));
rsahot036620655b2018-02-26 15:01:37 -05001978 if (log.isTraceEnabled()) {
Taras Lemkin96a0d342018-03-26 14:52:58 +00001979 log.trace("Relaying packet to destination {}", packet.getDestLocation());
rsahot036620655b2018-02-26 15:01:37 -05001980 }
Taras Lemkin96a0d342018-03-26 14:52:58 +00001981 log.debug("packetService.emit(o) to port {}", packet.getDestLocation());
rsahot036620655b2018-02-26 15:01:37 -05001982 packetService.emit(o);
1983 }
1984 }
1985
rsahot036620655b2018-02-26 15:01:37 -05001986 private DhcpServerInfo findServerInfoFromServer(boolean directConnFlag, ConnectPoint inPort) {
1987 List<DhcpServerInfo> validServerInfoList = findValidServerInfo(directConnFlag);
1988 DhcpServerInfo foundServerInfo = null;
1989 for (DhcpServerInfo serverInfo : validServerInfoList) {
1990 if (inPort.equals(serverInfo.getDhcpServerConnectPoint().get())) {
1991 foundServerInfo = serverInfo;
Charles Chan2de55302018-03-02 18:27:59 -08001992 log.debug("ServerInfo found for Rcv port {} Server Connect Point {} for {}",
rsahot036620655b2018-02-26 15:01:37 -05001993 inPort, serverInfo.getDhcpServerConnectPoint(), directConnFlag ? "direct" : "indirect");
1994 break;
rsahot036620655b2018-02-26 15:01:37 -05001995 }
1996 }
1997 return foundServerInfo;
1998 }
Yi Tseng51301292017-07-28 13:02:59 -07001999}