blob: 7325010137113d321327cb00703073f83ca669fa [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 = {
Ray Milkey5504bd22019-03-22 16:24:38 -0700126 "_version:Integer = 4",
Ray Milkey687c00c2018-10-31 10:18:41 -0700127 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);
Harshada Chaundkarf0780fe2019-05-06 20:16:13 +0000353 String hostToProbe = newServerInfo.getDhcpGatewayIp4().map(ip -> "gateway").orElse("server");
rsahot036620655b2018-02-26 15:01:37 -0500354 log.debug("Probing to resolve {} IP {}", hostToProbe, ipToProbe);
355 hostService.startMonitoringIp(ipToProbe);
356
357 Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
358 if (!hosts.isEmpty()) {
359 Host host = hosts.iterator().next();
360 newServerInfo.setDhcpConnectVlan(host.vlan());
361 newServerInfo.setDhcpConnectMac(host.mac());
362 }
363
364 // Add new server info
365 synchronized (this) {
366 //serverInfoList.clear();
367 serverInfoList.add(newServerInfo);
368 }
369
370 requestDhcpPacket(serverIp);
Yi Tseng4f2a0462017-08-31 11:21:00 -0700371 }
Yi Tseng483ac6f2017-08-02 15:03:31 -0700372 }
373
Yi Tseng3df7f9d2017-08-17 13:08:31 -0700374 @Override
Yi Tseng51301292017-07-28 13:02:59 -0700375 public void processDhcpPacket(PacketContext context, BasePacket payload) {
376 checkNotNull(payload, "DHCP payload can't be null");
377 checkState(payload instanceof DHCP, "Payload is not a DHCP");
378 DHCP dhcpPayload = (DHCP) payload;
379 if (!configured()) {
Yi Tseng919b2df2017-09-07 16:22:51 -0700380 log.warn("Missing default DHCP relay server config. Abort packet processing");
Yi Tseng51301292017-07-28 13:02:59 -0700381 return;
382 }
383
384 ConnectPoint inPort = context.inPacket().receivedFrom();
Yi Tseng51301292017-07-28 13:02:59 -0700385 checkNotNull(dhcpPayload, "Can't find DHCP payload");
386 Ethernet packet = context.inPacket().parsed();
387 DHCP.MsgType incomingPacketType = dhcpPayload.getOptions().stream()
388 .filter(dhcpOption -> dhcpOption.getCode() == OptionCode_MessageType.getValue())
389 .map(DhcpOption::getData)
390 .map(data -> DHCP.MsgType.getType(data[0]))
391 .findFirst()
392 .orElse(null);
393 checkNotNull(incomingPacketType, "Can't get message type from DHCP payload {}", dhcpPayload);
rsahot036620655b2018-02-26 15:01:37 -0500394 Set<Interface> receivingInterfaces = interfaceService.getInterfacesByPort(inPort);
395 //ignore the packets if dhcp client interface is not configured on onos.
396 if (receivingInterfaces.isEmpty()) {
397 log.warn("Virtual interface is not configured on {}", inPort);
398 return;
399 }
Yi Tseng51301292017-07-28 13:02:59 -0700400 switch (incomingPacketType) {
401 case DHCPDISCOVER:
Yi Tsengdcef2c22017-08-05 20:34:06 -0700402 // Add the gateway IP as virtual interface IP for server to understand
Yi Tseng51301292017-07-28 13:02:59 -0700403 // the lease to be assigned and forward the packet to dhcp server.
rsahot036620655b2018-02-26 15:01:37 -0500404 List<InternalPacket> ethernetClientPacket =
405 processDhcpPacketFromClient(context, packet, receivingInterfaces);
406 for (InternalPacket internalPacket : ethernetClientPacket) {
407 log.debug("DHCPDISCOVER from {} Forward to server", inPort);
Yi Tseng51301292017-07-28 13:02:59 -0700408 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
rsahot036620655b2018-02-26 15:01:37 -0500409 forwardPacket(internalPacket);
Yi Tseng51301292017-07-28 13:02:59 -0700410 }
411 break;
412 case DHCPOFFER:
413 //reply to dhcp client.
Taras Lemkin96a0d342018-03-26 14:52:58 +0000414 InternalPacket ethernetPacketOffer = processDhcpPacketFromServer(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700415 if (ethernetPacketOffer != null) {
Taras Lemkin96a0d342018-03-26 14:52:58 +0000416 writeResponseDhcpRecord(ethernetPacketOffer.getPacket(), dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700417 sendResponseToClient(ethernetPacketOffer, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700418 }
419 break;
420 case DHCPREQUEST:
421 // add the gateway ip as virtual interface ip for server to understand
422 // the lease to be assigned and forward the packet to dhcp server.
rsahot036620655b2018-02-26 15:01:37 -0500423 List<InternalPacket> ethernetPacketRequest =
424 processDhcpPacketFromClient(context, packet, receivingInterfaces);
425 for (InternalPacket internalPacket : ethernetPacketRequest) {
426 log.debug("DHCPDISCOVER from {} Forward to server", inPort);
Yi Tseng51301292017-07-28 13:02:59 -0700427 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
rsahot036620655b2018-02-26 15:01:37 -0500428 forwardPacket(internalPacket);
Yi Tseng51301292017-07-28 13:02:59 -0700429 }
430 break;
Charles Chand0dd7002017-10-08 23:53:36 -0400431 case DHCPDECLINE:
432 break;
Yi Tseng51301292017-07-28 13:02:59 -0700433 case DHCPACK:
434 // reply to dhcp client.
Taras Lemkin96a0d342018-03-26 14:52:58 +0000435 InternalPacket ethernetPacketAck = processDhcpPacketFromServer(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700436 if (ethernetPacketAck != null) {
Taras Lemkin96a0d342018-03-26 14:52:58 +0000437 writeResponseDhcpRecord(ethernetPacketAck.getPacket(), dhcpPayload);
438 handleDhcpAck(ethernetPacketAck.getPacket(), dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700439 sendResponseToClient(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700440 }
441 break;
Charles Chand0dd7002017-10-08 23:53:36 -0400442 case DHCPNAK:
443 break;
Yi Tseng51301292017-07-28 13:02:59 -0700444 case DHCPRELEASE:
445 // TODO: release the ip address from client
446 break;
Charles Chand0dd7002017-10-08 23:53:36 -0400447 case DHCPINFORM:
448 break;
449 case DHCPFORCERENEW:
450 break;
451 case DHCPLEASEQUERY:
452 handleLeaseQueryMsg(context, packet, dhcpPayload);
453 break;
454 case DHCPLEASEACTIVE:
455 handleLeaseQueryActivateMsg(packet, dhcpPayload);
456 break;
457 case DHCPLEASEUNASSIGNED:
458 case DHCPLEASEUNKNOWN:
459 handleLeaseQueryUnknown(packet, dhcpPayload);
460 break;
Yi Tseng51301292017-07-28 13:02:59 -0700461 default:
462 break;
463 }
464 }
465
466 /**
467 * Checks if this app has been configured.
468 *
469 * @return true if all information we need have been initialized
470 */
Yi Tseng4f2a0462017-08-31 11:21:00 -0700471 private boolean configured() {
Yi Tseng919b2df2017-09-07 16:22:51 -0700472 return !defaultServerInfoList.isEmpty();
Yi Tseng51301292017-07-28 13:02:59 -0700473 }
474
475 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -0700476 * Returns the first interface ip from interface.
Yi Tseng51301292017-07-28 13:02:59 -0700477 *
Yi Tsengdcef2c22017-08-05 20:34:06 -0700478 * @param iface interface of one connect point
Yi Tseng51301292017-07-28 13:02:59 -0700479 * @return the first interface IP; null if not exists an IP address in
480 * these interfaces
481 */
Yi Tseng3df7f9d2017-08-17 13:08:31 -0700482 private Ip4Address getFirstIpFromInterface(Interface iface) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700483 checkNotNull(iface, "Interface can't be null");
484 return iface.ipAddressesList().stream()
Yi Tseng51301292017-07-28 13:02:59 -0700485 .map(InterfaceIpAddress::ipAddress)
486 .filter(IpAddress::isIp4)
487 .map(IpAddress::getIp4Address)
488 .findFirst()
489 .orElse(null);
490 }
491
492 /**
Yi Tseng4f2a0462017-08-31 11:21:00 -0700493 * Gets Interface facing to the server for default host.
Yi Tsengdcef2c22017-08-05 20:34:06 -0700494 *
495 * @return the Interface facing to the server; null if not found
496 */
Yi Tseng919b2df2017-09-07 16:22:51 -0700497 private Interface getDefaultServerInterface() {
498 return getServerInterface(defaultServerInfoList);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700499 }
500
501 /**
Yi Tseng4f2a0462017-08-31 11:21:00 -0700502 * Gets Interface facing to the server for indirect hosts.
503 * Use default server Interface if indirect server not configured.
504 *
505 * @return the Interface facing to the server; null if not found
506 */
507 private Interface getIndirectServerInterface() {
Yi Tseng919b2df2017-09-07 16:22:51 -0700508 return getServerInterface(indirectServerInfoList);
509 }
510
511 private Interface getServerInterface(List<DhcpServerInfo> serverInfos) {
Yi Tseng3bd57ac2017-11-29 14:39:18 -0800512 return serverInfos.stream()
Yi Tseng4f2a0462017-08-31 11:21:00 -0700513 .findFirst()
Yi Tseng3bd57ac2017-11-29 14:39:18 -0800514 .map(serverInfo -> {
Harshada Chaundkarf0780fe2019-05-06 20:16:13 +0000515 ConnectPoint dhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
Yi Tseng3bd57ac2017-11-29 14:39:18 -0800516 VlanId dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
517 if (dhcpServerConnectPoint == null || dhcpConnectVlan == null) {
518 return null;
519 }
520 return interfaceService.getInterfacesByPort(dhcpServerConnectPoint)
521 .stream()
522 .filter(iface -> interfaceContainsVlan(iface, dhcpConnectVlan))
523 .findFirst()
524 .orElse(null);
525 })
Yi Tseng4f2a0462017-08-31 11:21:00 -0700526 .orElse(null);
527 }
528
529 /**
530 * Determind if an Interface contains a vlan id.
531 *
532 * @param iface the Interface
533 * @param vlanId the vlan id
534 * @return true if the Interface contains the vlan id
535 */
536 private boolean interfaceContainsVlan(Interface iface, VlanId vlanId) {
Yi Tseng4025a102017-09-30 11:35:42 +0800537 if (vlanId.equals(VlanId.NONE)) {
538 // untagged packet, check if vlan untagged or vlan native is not NONE
Harshada Chaundkarf0780fe2019-05-06 20:16:13 +0000539 return !iface.vlanUntagged().equals(VlanId.NONE) || !iface.vlanNative().equals(VlanId.NONE);
Yi Tseng4025a102017-09-30 11:35:42 +0800540 }
541 // tagged packet, check if the interface contains the vlan
542 return iface.vlanTagged().contains(vlanId);
Yi Tseng4f2a0462017-08-31 11:21:00 -0700543 }
544
Charles Chand0dd7002017-10-08 23:53:36 -0400545 private void handleLeaseQueryActivateMsg(Ethernet packet, DHCP dhcpPayload) {
546 log.debug("LQ: Got DHCPLEASEACTIVE packet!");
547
Taras Lemkin96a0d342018-03-26 14:52:58 +0000548 if (learnRouteFromLeasequery) {
549 // TODO: release the ip address from client
550 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
551 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
552 HostId hostId = HostId.hostId(clientMacAddress, vlanId);
553 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
Charles Chand0dd7002017-10-08 23:53:36 -0400554
Taras Lemkin96a0d342018-03-26 14:52:58 +0000555 if (record == null) {
556 log.warn("Can't find record for host {} when processing DHCPLEASEACTIVE", hostId);
557 return;
558 }
559
560 // need to update routes
561 log.debug("Lease Query for Client results in DHCPLEASEACTIVE - route needs to be modified");
562 // get current route
563 // find the ip of that client with the DhcpRelay store
564
565 Ip4Address clientIP = record.ip4Address().orElse(null);
566 log.debug("LQ: IP of host is " + clientIP.getIp4Address());
567
568 MacAddress nextHopMac = record.nextHop().orElse(null);
569 log.debug("LQ: MAC of resulting *OLD* NH for that host is " + nextHopMac.toString());
570
571 // find the new NH by looking at the src MAC of the dhcp request
572 // from the LQ store
573 MacAddress newNextHopMac = record.nextHopTemp().orElse(null);
574 log.debug("LQ: MAC of resulting *NEW* NH for that host is " + newNextHopMac.toString());
575
576 log.debug("LQ: updating dhcp relay record with new NH");
577 record.nextHop(newNextHopMac);
578
579 // find the next hop IP from its mac
580 HostId gwHostId = HostId.hostId(newNextHopMac, vlanId);
581 Host gwHost = hostService.getHost(gwHostId);
582
583 if (gwHost == null) {
584 log.warn("Can't find gateway for new NH host " + gwHostId);
585 return;
586 }
587
588 Ip4Address nextHopIp = gwHost.ipAddresses()
589 .stream()
590 .filter(IpAddress::isIp4)
591 .map(IpAddress::getIp4Address)
592 .findFirst()
593 .orElse(null);
594
595 if (nextHopIp == null) {
596 log.warn("Can't find IP address of gateway " + gwHost);
597 return;
598 }
599
600 log.debug("LQ: *NEW* NH IP for host is " + nextHopIp.getIp4Address());
601 Route route = new Route(Route.Source.DHCP, clientIP.toIpPrefix(), nextHopIp);
602 routeStore.updateRoute(route);
Charles Chand0dd7002017-10-08 23:53:36 -0400603 }
604
Charles Chand0dd7002017-10-08 23:53:36 -0400605 // and forward to client
Taras Lemkin96a0d342018-03-26 14:52:58 +0000606 InternalPacket ethernetPacket = processLeaseQueryFromServer(packet);
Charles Chand0dd7002017-10-08 23:53:36 -0400607 if (ethernetPacket != null) {
608 sendResponseToClient(ethernetPacket, dhcpPayload);
609 }
610 }
611
Charles Chand0dd7002017-10-08 23:53:36 -0400612 private void handleLeaseQueryMsg(PacketContext context, Ethernet packet, DHCP dhcpPayload) {
Taras Lemkin96a0d342018-03-26 14:52:58 +0000613 // If this flag is enabled we expect that DHCPLEASEQUERY-ies are sent from an access concentrator
614 // where queried client is connected to. Otherwise, DHCPLEASEQUERY source may be a separate connected agent
615 if (learnRouteFromLeasequery) {
616 log.debug("LQ: Got DHCPLEASEQUERY packet!");
617 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
618 log.debug("LQ: got DHCPLEASEQUERY with MAC " + clientMacAddress.toString());
619 // add the client mac (hostid) of this request to a store (the entry will be removed with
620 // the reply sent to the originator)
621 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
622 HostId hId = HostId.hostId(clientMacAddress, vlanId);
623 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hId).orElse(null);
624 if (record != null) {
625 //new NH is to be taken from src mac of LQ packet
626 MacAddress newNextHop = packet.getSourceMAC();
627 record.nextHopTemp(newNextHop);
628 record.ip4Status(dhcpPayload.getPacketType());
629 record.updateLastSeen();
630
631 // do a basic routing of the packet (this is unicast routing
632 // not a relay operation like for other broadcast dhcp packets
633 List<InternalPacket> ethernetPacketRequest = processLeaseQueryFromAgent(context, packet);
634 // and forward to server
635 for (InternalPacket internalPacket : ethernetPacketRequest) {
636 log.debug("LeaseQueryMsg forward to server");
637 forwardPacket(internalPacket);
638 }
639 } else {
640 log.warn("LQ: Error! - DHCP relay record for that client not found - ignoring LQ!");
641 }
642 } else {
643 log.debug("LQ: Got DHCPLEASEQUERY packet!");
644
645 int giaddr = dhcpPayload.getGatewayIPAddress();
646
647 log.debug("DHCPLEASEQUERY giaddr: {} ({}). Originators connectPoint: {}", giaddr,
648 Ip4Address.valueOf(giaddr), context.inPacket().receivedFrom());
Charles Chand0dd7002017-10-08 23:53:36 -0400649
650 // do a basic routing of the packet (this is unicast routing
651 // not a relay operation like for other broadcast dhcp packets
rsahot036620655b2018-02-26 15:01:37 -0500652 List<InternalPacket> ethernetPacketRequest = processLeaseQueryFromAgent(context, packet);
Charles Chand0dd7002017-10-08 23:53:36 -0400653 // and forward to server
rsahot036620655b2018-02-26 15:01:37 -0500654 for (InternalPacket internalPacket : ethernetPacketRequest) {
Taras Lemkin96a0d342018-03-26 14:52:58 +0000655 log.trace("LeaseQueryMsg forward to server connected to {}", internalPacket.getDestLocation());
rsahot036620655b2018-02-26 15:01:37 -0500656 forwardPacket(internalPacket);
657 }
Charles Chand0dd7002017-10-08 23:53:36 -0400658 }
659 }
660
661 private void handleLeaseQueryUnknown(Ethernet packet, DHCP dhcpPayload) {
662 log.debug("Lease Query for Client results in DHCPLEASEUNASSIGNED or " +
663 "DHCPLEASEUNKNOWN - removing route & forwarding reply to originator");
Taras Lemkin96a0d342018-03-26 14:52:58 +0000664 if (learnRouteFromLeasequery) {
665 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
666 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
667 HostId hostId = HostId.hostId(clientMacAddress, vlanId);
668 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
Charles Chand0dd7002017-10-08 23:53:36 -0400669
Taras Lemkin96a0d342018-03-26 14:52:58 +0000670 if (record == null) {
671 log.warn("Can't find record for host {} when handling LQ UNKNOWN/UNASSIGNED message", hostId);
672 return;
673 }
674
675 Ip4Address clientIP = record.ip4Address().orElse(null);
676 log.debug("LQ: IP of host is " + clientIP.getIp4Address());
677
678 // find the new NH by looking at the src MAC of the dhcp request
679 // from the LQ store
680 MacAddress nextHopMac = record.nextHop().orElse(null);
681 log.debug("LQ: MAC of resulting *Existing* NH for that route is " + nextHopMac.toString());
682
683 // find the next hop IP from its mac
684 HostId gwHostId = HostId.hostId(nextHopMac, vlanId);
685 Host gwHost = hostService.getHost(gwHostId);
686
687 if (gwHost == null) {
688 log.warn("Can't find gateway for new NH host " + gwHostId);
689 return;
690 }
691
692 Ip4Address nextHopIp = gwHost.ipAddresses()
693 .stream()
694 .filter(IpAddress::isIp4)
695 .map(IpAddress::getIp4Address)
696 .findFirst()
697 .orElse(null);
698
699 if (nextHopIp == null) {
700 log.warn("Can't find IP address of gateway {}", gwHost);
701 return;
702 }
703
704 log.debug("LQ: *Existing* NH IP for host is " + nextHopIp.getIp4Address() + " removing route for it");
705 Route route = new Route(Route.Source.DHCP, clientIP.toIpPrefix(), nextHopIp);
706 routeStore.removeRoute(route);
707
708 // remove from temp store
709 dhcpRelayStore.removeDhcpRecord(hostId);
Charles Chand0dd7002017-10-08 23:53:36 -0400710 }
Charles Chand0dd7002017-10-08 23:53:36 -0400711 // and forward to client
Taras Lemkin96a0d342018-03-26 14:52:58 +0000712 InternalPacket ethernetPacket = processLeaseQueryFromServer(packet);
Charles Chand0dd7002017-10-08 23:53:36 -0400713 if (ethernetPacket != null) {
714 sendResponseToClient(ethernetPacket, dhcpPayload);
715 }
716 }
717
Yi Tseng4f2a0462017-08-31 11:21:00 -0700718 /**
Yi Tseng51301292017-07-28 13:02:59 -0700719 * Build the DHCP discover/request packet with gateway IP(unicast packet).
720 *
721 * @param context the packet context
722 * @param ethernetPacket the ethernet payload to process
Yi Tseng51301292017-07-28 13:02:59 -0700723 * @return processed packet
724 */
rsahot036620655b2018-02-26 15:01:37 -0500725 private List<InternalPacket> processDhcpPacketFromClient(PacketContext context,
726 Ethernet ethernetPacket,
727 Set<Interface> clientInterfaces) {
Yi Tseng25bfe372017-11-03 16:27:32 -0700728 ConnectPoint receivedFrom = context.inPacket().receivedFrom();
729 DeviceId receivedFromDevice = receivedFrom.deviceId();
rsahot036620655b2018-02-26 15:01:37 -0500730 Ip4Address relayAgentIp = null;
Taras Lemkin96a0d342018-03-26 14:52:58 +0000731 relayAgentIp = Dhcp4HandlerUtil.getRelayAgentIPv4Address(clientInterfaces);
rsahot036620655b2018-02-26 15:01:37 -0500732 MacAddress relayAgentMac = clientInterfaces.iterator().next().mac();
733 if (relayAgentIp == null || relayAgentMac == null) {
734 log.warn("Missing DHCP relay agent interface Ipv4 addr config for "
735 + "packet from client on port: {}. Aborting packet processing",
736 clientInterfaces.iterator().next().connectPoint());
Charles Chane5e0c9a2018-03-30 12:11:34 -0700737 return Lists.newArrayList();
rsahot036620655b2018-02-26 15:01:37 -0500738 }
739 log.debug("Multi DHCP V4 processDhcpPacketFromClient on port {}",
740 clientInterfaces.iterator().next().connectPoint());
Yi Tseng25bfe372017-11-03 16:27:32 -0700741
Yi Tseng4f2a0462017-08-31 11:21:00 -0700742 // get dhcp header.
rsahot036620655b2018-02-26 15:01:37 -0500743 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
Yi Tseng4f2a0462017-08-31 11:21:00 -0700744 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
745 UDP udpPacket = (UDP) ipv4Packet.getPayload();
746 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
Yi Tsengdcef2c22017-08-05 20:34:06 -0700747 Ip4Address clientInterfaceIp =
748 interfaceService.getInterfacesByPort(context.inPacket().receivedFrom())
749 .stream()
750 .map(Interface::ipAddressesList)
751 .flatMap(Collection::stream)
752 .map(InterfaceIpAddress::ipAddress)
753 .filter(IpAddress::isIp4)
754 .map(IpAddress::getIp4Address)
755 .findFirst()
756 .orElse(null);
757 if (clientInterfaceIp == null) {
758 log.warn("Can't find interface IP for client interface for port {}",
rsahot036620655b2018-02-26 15:01:37 -0500759 context.inPacket().receivedFrom());
Charles Chane5e0c9a2018-03-30 12:11:34 -0700760 return Lists.newArrayList();
Yi Tsengdcef2c22017-08-05 20:34:06 -0700761 }
rsahot036620655b2018-02-26 15:01:37 -0500762
Yi Tseng4f2a0462017-08-31 11:21:00 -0700763 boolean isDirectlyConnected = directlyConnected(dhcpPacket);
rsahot036620655b2018-02-26 15:01:37 -0500764 boolean directConnFlag = directlyConnected(dhcpPacket);
765
766 // Multi DHCP Start
767 ConnectPoint clientConnectionPoint = context.inPacket().receivedFrom();
768 VlanId vlanIdInUse = VlanId.vlanId(ethernetPacket.getVlanID());
769 Interface clientInterface = interfaceService.getInterfacesByPort(clientConnectionPoint)
Taras Lemkin96a0d342018-03-26 14:52:58 +0000770 .stream().filter(iface -> Dhcp4HandlerUtil.interfaceContainsVlan(iface, vlanIdInUse))
rsahot036620655b2018-02-26 15:01:37 -0500771 .findFirst()
772 .orElse(null);
773
774 List<InternalPacket> internalPackets = new ArrayList<>();
775 List<DhcpServerInfo> serverInfoList = findValidServerInfo(directConnFlag);
776 List<DhcpServerInfo> copyServerInfoList = new ArrayList<DhcpServerInfo>(serverInfoList);
Harshada Chaundkarf0780fe2019-05-06 20:16:13 +0000777 boolean serverFound = false;
rsahot036620655b2018-02-26 15:01:37 -0500778
779 for (DhcpServerInfo serverInfo : copyServerInfoList) {
Harshada Chaundkarf0780fe2019-05-06 20:16:13 +0000780
rsahot036620655b2018-02-26 15:01:37 -0500781 etherReply = (Ethernet) ethernetPacket.clone();
Taras Lemkin96a0d342018-03-26 14:52:58 +0000782 ipv4Packet = (IPv4) etherReply.getPayload();
783 udpPacket = (UDP) ipv4Packet.getPayload();
784 dhcpPacket = (DHCP) udpPacket.getPayload();
rsahot036620655b2018-02-26 15:01:37 -0500785 if (!checkDhcpServerConnPt(directConnFlag, serverInfo)) {
786 log.warn("Can't get server connect point, ignore");
787 continue;
788 }
789 DhcpServerInfo newServerInfo = getHostInfoForServerInfo(serverInfo, serverInfoList);
790 if (newServerInfo == null) {
Harshada Chaundkarf0780fe2019-05-06 20:16:13 +0000791 log.debug("Can't get server interface with host info resolved, ignore serverInfo {} serverInfoList {}",
792 serverInfo, serverInfoList);
rsahot036620655b2018-02-26 15:01:37 -0500793 continue;
794 }
rsahot036620655b2018-02-26 15:01:37 -0500795 Interface serverInterface = getServerInterface(newServerInfo);
Yi Tseng3bd57ac2017-11-29 14:39:18 -0800796 if (serverInterface == null) {
Harshada Chaundkarf0780fe2019-05-06 20:16:13 +0000797 log.debug("Can't get server interface, ignore for serverInfo {}, serverInfoList {}",
798 serverInfo, serverInfoList);
rsahot036620655b2018-02-26 15:01:37 -0500799 continue;
Yi Tseng3bd57ac2017-11-29 14:39:18 -0800800 }
Yi Tseng4f2a0462017-08-31 11:21:00 -0700801
rsahot036620655b2018-02-26 15:01:37 -0500802 Ip4Address ipFacingServer = getFirstIpFromInterface(serverInterface);
803 MacAddress macFacingServer = serverInterface.mac();
804 log.debug("Interfacing server {} Mac : {} ", ipFacingServer, macFacingServer);
805 if (ipFacingServer == null || macFacingServer == null) {
Harshada Chaundkarf0780fe2019-05-06 20:16:13 +0000806 log.debug("No IP address for server Interface {}", serverInterface);
rsahot036620655b2018-02-26 15:01:37 -0500807 continue;
808 }
Yi Tseng51301292017-07-28 13:02:59 -0700809
Harshada Chaundkarf0780fe2019-05-06 20:16:13 +0000810 serverFound = true;
811 log.debug("Server Info Found {}", serverInfo.getDhcpConnectMac());
rsahot036620655b2018-02-26 15:01:37 -0500812 etherReply.setSourceMACAddress(macFacingServer);
813 // set default info and replace with indirect if available later on.
814 if (newServerInfo.getDhcpConnectMac().isPresent()) {
815 etherReply.setDestinationMACAddress(newServerInfo.getDhcpConnectMac().get());
816 }
817 if (newServerInfo.getDhcpConnectVlan().isPresent()) {
818 etherReply.setVlanID(newServerInfo.getDhcpConnectVlan().get().toShort());
819 }
820 ipv4Packet.setSourceAddress(ipFacingServer.toInt());
821 ipv4Packet.setDestinationAddress(newServerInfo.getDhcpServerIp4().get().toInt());
Charles Chan2de55302018-03-02 18:27:59 -0800822 log.debug("Directly connected {}", isDirectlyConnected);
823 log.debug("DHCP server IP: {}", newServerInfo.getDhcpServerIp4().get());
rsahot036620655b2018-02-26 15:01:37 -0500824 if (isDirectlyConnected) {
Yi Tseng51301292017-07-28 13:02:59 -0700825
Charles Chan2de55302018-03-02 18:27:59 -0800826 log.debug("Default DHCP server IP: {}", newServerInfo.getDhcpServerIp4().get());
rsahot036620655b2018-02-26 15:01:37 -0500827 if (newServerInfo.getDhcpConnectMac().isPresent()) {
828 etherReply.setDestinationMACAddress(newServerInfo.getDhcpConnectMac().get());
Charles Chand0d1e332017-10-10 16:53:32 -0400829 }
rsahot036620655b2018-02-26 15:01:37 -0500830 if (newServerInfo.getDhcpConnectVlan().isPresent()) {
831 etherReply.setVlanID(newServerInfo.getDhcpConnectVlan().get().toShort());
832 }
833
834 ipv4Packet.setDestinationAddress(newServerInfo.getDhcpServerIp4().get().toInt());
835
rsahot036620655b2018-02-26 15:01:37 -0500836 ConnectPoint inPort = context.inPacket().receivedFrom();
837 VlanId vlanId = VlanId.vlanId(ethernetPacket.getVlanID());
838 // add connected in port and vlan
839 CircuitId cid = new CircuitId(inPort.toString(), vlanId);
840 byte[] circuitId = cid.serialize();
841 DhcpOption circuitIdSubOpt = new DhcpOption();
Harshada Chaundkarf0780fe2019-05-06 20:16:13 +0000842 circuitIdSubOpt.setCode(CIRCUIT_ID.getValue())
rsahot036620655b2018-02-26 15:01:37 -0500843 .setLength((byte) circuitId.length)
844 .setData(circuitId);
845
846 DhcpRelayAgentOption newRelayAgentOpt = new DhcpRelayAgentOption();
847 newRelayAgentOpt.setCode(OptionCode_CircuitID.getValue());
848 newRelayAgentOpt.addSubOption(circuitIdSubOpt);
849
850 // Removes END option first
851 List<DhcpOption> options = dhcpPacket.getOptions().stream()
852 .filter(opt -> opt.getCode() != OptionCode_END.getValue())
853 .collect(Collectors.toList());
854
855 // push relay agent option
856 options.add(newRelayAgentOpt);
857
858 // make sure option 255(End) is the last option
859 DhcpOption endOption = new DhcpOption();
860 endOption.setCode(OptionCode_END.getValue());
861 options.add(endOption);
862
863 dhcpPacket.setOptions(options);
864
865 relayAgentIp = serverInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
866
867 // Sets relay agent IP
868 int effectiveRelayAgentIp = relayAgentIp != null ?
869 relayAgentIp.toInt() : clientInterfaceIp.toInt();
870 dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
Charles Chan2de55302018-03-02 18:27:59 -0800871 log.debug("In Default, Relay Agent IP {}", effectiveRelayAgentIp);
Charles Chand0d1e332017-10-10 16:53:32 -0400872 } else {
rsahot036620655b2018-02-26 15:01:37 -0500873 if (!newServerInfo.getDhcpServerIp4().isPresent()) {
874 // do nothing
875 } else if (!newServerInfo.getDhcpConnectMac().isPresent()) {
876 continue;
877 } else {
878 relayAgentIp = newServerInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
879 // Sets relay agent IP
880 int effectiveRelayAgentIp = relayAgentIp != null ?
881 relayAgentIp.toInt() : clientInterfaceIp.toInt();
Mayank Tiwari30149832018-10-19 12:12:44 -0400882 Ip4Address effectiveRealRealyAgentIP = relayAgentIp != null ?
883 relayAgentIp : clientInterfaceIp;
rsahot036620655b2018-02-26 15:01:37 -0500884 dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
Mayank Tiwari30149832018-10-19 12:12:44 -0400885 ipv4Packet.setSourceAddress(effectiveRealRealyAgentIP.toInt());
886 log.debug("Source IP address set as relay agent IP with value: {}", effectiveRealRealyAgentIP);
Charles Chand0d1e332017-10-10 16:53:32 -0400887 }
Yi Tseng4f2a0462017-08-31 11:21:00 -0700888 }
Yi Tseng3df7f9d2017-08-17 13:08:31 -0700889
rsahot036620655b2018-02-26 15:01:37 -0500890 // Remove broadcast flag
891 dhcpPacket.setFlags((short) 0);
rsahot036620655b2018-02-26 15:01:37 -0500892 udpPacket.setPayload(dhcpPacket);
893 // As a DHCP relay, the source port should be server port( instead
894 // of client port.
895 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
896 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
897 ipv4Packet.setPayload(udpPacket);
898 ipv4Packet.setTtl((byte) 64);
899 etherReply.setPayload(ipv4Packet);
Taras Lemkin96a0d342018-03-26 14:52:58 +0000900 InternalPacket internalPacket = InternalPacket.internalPacket(etherReply,
rsahot036620655b2018-02-26 15:01:37 -0500901 serverInfo.getDhcpServerConnectPoint().get());
Taras Lemkin96a0d342018-03-26 14:52:58 +0000902
rsahot036620655b2018-02-26 15:01:37 -0500903 internalPackets.add(internalPacket);
904 }
Harshada Chaundkarf0780fe2019-05-06 20:16:13 +0000905 if (!serverFound) {
906 log.warn("ProcessDhcp4PacketFromClient No Server Found");
907 }
rsahot036620655b2018-02-26 15:01:37 -0500908 return internalPackets;
Yi Tseng51301292017-07-28 13:02:59 -0700909 }
910
Charles Chand0dd7002017-10-08 23:53:36 -0400911
912 /**
913 * Do a basic routing for a packet from client (used for LQ processing).
914 *
915 * @param context the packet context
916 * @param ethernetPacket the ethernet payload to process
917 * @return processed packet
918 */
rsahot036620655b2018-02-26 15:01:37 -0500919 private List<InternalPacket> processLeaseQueryFromAgent(PacketContext context,
920 Ethernet ethernetPacket) {
921 ConnectPoint receivedFrom = context.inPacket().receivedFrom();
922 DeviceId receivedFromDevice = receivedFrom.deviceId();
923
Charles Chand0dd7002017-10-08 23:53:36 -0400924 // get dhcp header.
925 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
926 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
927 UDP udpPacket = (UDP) ipv4Packet.getPayload();
928 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
929
Charles Chan2de55302018-03-02 18:27:59 -0800930 Ip4Address relayAgentIp;
Charles Chand0dd7002017-10-08 23:53:36 -0400931
Charles Chand0dd7002017-10-08 23:53:36 -0400932 Ip4Address clientInterfaceIp =
933 interfaceService.getInterfacesByPort(context.inPacket().receivedFrom())
934 .stream()
935 .map(Interface::ipAddressesList)
936 .flatMap(Collection::stream)
937 .map(InterfaceIpAddress::ipAddress)
938 .filter(IpAddress::isIp4)
939 .map(IpAddress::getIp4Address)
940 .findFirst()
941 .orElse(null);
942 if (clientInterfaceIp == null) {
943 log.warn("Can't find interface IP for client interface for port {}",
rsahot036620655b2018-02-26 15:01:37 -0500944 context.inPacket().receivedFrom());
Charles Chand0dd7002017-10-08 23:53:36 -0400945 return null;
946 }
rsahot036620655b2018-02-26 15:01:37 -0500947
Charles Chand0dd7002017-10-08 23:53:36 -0400948 boolean isDirectlyConnected = directlyConnected(dhcpPacket);
rsahot036620655b2018-02-26 15:01:37 -0500949 boolean directConnFlag = directlyConnected(dhcpPacket);
950
951 // Multi DHCP Start
rsahot036620655b2018-02-26 15:01:37 -0500952 List<InternalPacket> internalPackets = new ArrayList<>();
953 List<DhcpServerInfo> serverInfoList = findValidServerInfo(directConnFlag);
Charles Chan2de55302018-03-02 18:27:59 -0800954 List<DhcpServerInfo> copyServerInfoList = new ArrayList<>(serverInfoList);
rsahot036620655b2018-02-26 15:01:37 -0500955
956 for (DhcpServerInfo serverInfo : copyServerInfoList) {
957 // get dhcp header.
958 etherReply = (Ethernet) ethernetPacket.clone();
959 ipv4Packet = (IPv4) etherReply.getPayload();
960 udpPacket = (UDP) ipv4Packet.getPayload();
961 dhcpPacket = (DHCP) udpPacket.getPayload();
962
963 if (!checkDhcpServerConnPt(directConnFlag, serverInfo)) {
964 log.warn("Can't get server connect point, ignore");
965 continue;
Yi Tseng3bd57ac2017-11-29 14:39:18 -0800966 }
rsahot036620655b2018-02-26 15:01:37 -0500967 DhcpServerInfo newServerInfo = getHostInfoForServerInfo(serverInfo, serverInfoList);
968 if (newServerInfo == null) {
969 log.warn("Can't get server interface with host info resolved, ignore");
970 continue;
971 }
Charles Chand0dd7002017-10-08 23:53:36 -0400972
rsahot036620655b2018-02-26 15:01:37 -0500973 Interface serverInterface = getServerInterface(newServerInfo);
974 if (serverInterface == null) {
975 log.warn("Can't get server interface, ignore");
976 continue;
977 }
978 Ip4Address ipFacingServer = getFirstIpFromInterface(serverInterface);
979 MacAddress macFacingServer = serverInterface.mac();
980 if (ipFacingServer == null || macFacingServer == null) {
981 log.warn("No IP address for server Interface {}", serverInterface);
982 continue;
983 }
Charles Chand0dd7002017-10-08 23:53:36 -0400984
rsahot036620655b2018-02-26 15:01:37 -0500985 etherReply.setSourceMACAddress(macFacingServer);
Charles Chan2de55302018-03-02 18:27:59 -0800986 etherReply.setDestinationMACAddress(newServerInfo.getDhcpConnectMac().get());
987 etherReply.setVlanID(newServerInfo.getDhcpConnectVlan().get().toShort());
rsahot036620655b2018-02-26 15:01:37 -0500988 ipv4Packet.setSourceAddress(ipFacingServer.toInt());
Charles Chan2de55302018-03-02 18:27:59 -0800989 ipv4Packet.setDestinationAddress(newServerInfo.getDhcpServerIp4().get().toInt());
rsahot036620655b2018-02-26 15:01:37 -0500990 if (isDirectlyConnected) {
991 // set default info and replace with indirect if available later on.
992 if (newServerInfo.getDhcpConnectMac().isPresent()) {
993 etherReply.setDestinationMACAddress(newServerInfo.getDhcpConnectMac().get());
994 }
995 if (newServerInfo.getDhcpConnectVlan().isPresent()) {
996 etherReply.setVlanID(serverInfo.getDhcpConnectVlan().get().toShort());
997 }
Taras Lemkin96a0d342018-03-26 14:52:58 +0000998 if (learnRouteFromLeasequery) {
rsahot036620655b2018-02-26 15:01:37 -0500999 relayAgentIp = newServerInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
1000 // Sets relay agent IP
1001 int effectiveRelayAgentIp = relayAgentIp != null ?
1002 relayAgentIp.toInt() : clientInterfaceIp.toInt();
1003 dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
Taras Lemkin96a0d342018-03-26 14:52:58 +00001004 }
1005 } else {
1006 if (!newServerInfo.getDhcpServerIp4().isPresent()) {
1007 //do nothing
1008 } else if (!newServerInfo.getDhcpConnectMac().isPresent()) {
1009 continue;
1010 } else if (learnRouteFromLeasequery) {
1011 relayAgentIp = newServerInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
1012 // Sets relay agent IP
1013 int effectiveRelayAgentIp = relayAgentIp != null ?
1014 relayAgentIp.toInt() : clientInterfaceIp.toInt();
rsahot036620655b2018-02-26 15:01:37 -05001015 dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
Taras Lemkin96a0d342018-03-26 14:52:58 +00001016 log.debug("Relay Agent IP {}", relayAgentIp);
rsahot036620655b2018-02-26 15:01:37 -05001017 }
1018
Taras Lemkin96a0d342018-03-26 14:52:58 +00001019 log.trace("Indirect");
rsahot036620655b2018-02-26 15:01:37 -05001020 }
1021
1022 // Remove broadcast flag
1023 dhcpPacket.setFlags((short) 0);
1024
1025 udpPacket.setPayload(dhcpPacket);
1026 // As a DHCP relay, the source port should be server port( instead
1027 // of client port.
1028 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
1029 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
1030 ipv4Packet.setPayload(udpPacket);
1031 ipv4Packet.setTtl((byte) 64);
1032 etherReply.setPayload(ipv4Packet);
Taras Lemkin96a0d342018-03-26 14:52:58 +00001033 udpPacket.resetChecksum();
rsahot036620655b2018-02-26 15:01:37 -05001034 ////return etherReply;
Taras Lemkin96a0d342018-03-26 14:52:58 +00001035 InternalPacket internalPacket = InternalPacket.internalPacket(etherReply,
rsahot036620655b2018-02-26 15:01:37 -05001036 newServerInfo.getDhcpServerConnectPoint().get());
Taras Lemkin96a0d342018-03-26 14:52:58 +00001037
rsahot036620655b2018-02-26 15:01:37 -05001038 internalPackets.add(internalPacket);
Charles Chand0dd7002017-10-08 23:53:36 -04001039 }
Taras Lemkin96a0d342018-03-26 14:52:58 +00001040 log.debug("num of processLeaseQueryFromAgent packets to send is: {}", internalPackets.size());
Charles Chand0dd7002017-10-08 23:53:36 -04001041
rsahot036620655b2018-02-26 15:01:37 -05001042 return internalPackets;
Charles Chand0dd7002017-10-08 23:53:36 -04001043 }
1044
1045
Yi Tseng51301292017-07-28 13:02:59 -07001046 /**
1047 * Writes DHCP record to the store according to the request DHCP packet (Discover, Request).
1048 *
1049 * @param location the location which DHCP packet comes from
1050 * @param ethernet the DHCP packet
1051 * @param dhcpPayload the DHCP payload
1052 */
1053 private void writeRequestDhcpRecord(ConnectPoint location,
1054 Ethernet ethernet,
1055 DHCP dhcpPayload) {
1056 VlanId vlanId = VlanId.vlanId(ethernet.getVlanID());
1057 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1058 HostId hostId = HostId.hostId(macAddress, vlanId);
1059 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1060 if (record == null) {
1061 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
1062 } else {
1063 record = record.clone();
1064 }
1065 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
1066 record.ip4Status(dhcpPayload.getPacketType());
1067 record.setDirectlyConnected(directlyConnected(dhcpPayload));
1068 if (!directlyConnected(dhcpPayload)) {
1069 // Update gateway mac address if the host is not directly connected
1070 record.nextHop(ethernet.getSourceMAC());
1071 }
1072 record.updateLastSeen();
1073 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
1074 }
1075
1076 /**
1077 * Writes DHCP record to the store according to the response DHCP packet (Offer, Ack).
1078 *
1079 * @param ethernet the DHCP packet
1080 * @param dhcpPayload the DHCP payload
1081 */
1082 private void writeResponseDhcpRecord(Ethernet ethernet,
1083 DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001084 Optional<Interface> outInterface = getClientInterface(ethernet, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -07001085 if (!outInterface.isPresent()) {
1086 log.warn("Failed to determine where to send {}", dhcpPayload.getPacketType());
1087 return;
1088 }
1089
1090 Interface outIface = outInterface.get();
1091 ConnectPoint location = outIface.connectPoint();
Yi Tseng4f2a0462017-08-31 11:21:00 -07001092 VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001093 if (vlanId == null) {
1094 vlanId = outIface.vlan();
1095 }
Yi Tseng51301292017-07-28 13:02:59 -07001096 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1097 HostId hostId = HostId.hostId(macAddress, vlanId);
1098 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1099 if (record == null) {
1100 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
1101 } else {
1102 record = record.clone();
1103 }
1104 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
1105 if (dhcpPayload.getPacketType() == DHCP.MsgType.DHCPACK) {
1106 record.ip4Address(Ip4Address.valueOf(dhcpPayload.getYourIPAddress()));
1107 }
1108 record.ip4Status(dhcpPayload.getPacketType());
1109 record.setDirectlyConnected(directlyConnected(dhcpPayload));
1110 record.updateLastSeen();
1111 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
1112 }
1113
1114 /**
1115 * Build the DHCP offer/ack with proper client port.
1116 *
1117 * @param ethernetPacket the original packet comes from server
1118 * @return new packet which will send to the client
1119 */
Taras Lemkin96a0d342018-03-26 14:52:58 +00001120 private InternalPacket processDhcpPacketFromServer(PacketContext context, Ethernet ethernetPacket) {
Yi Tseng51301292017-07-28 13:02:59 -07001121 // get dhcp header.
rsahot036620655b2018-02-26 15:01:37 -05001122 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
Yi Tseng51301292017-07-28 13:02:59 -07001123 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
1124 UDP udpPacket = (UDP) ipv4Packet.getPayload();
1125 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
1126
1127 // determine the vlanId of the client host - note that this vlan id
1128 // could be different from the vlan in the packet from the server
Yi Tsengdcef2c22017-08-05 20:34:06 -07001129 Interface clientInterface = getClientInterface(ethernetPacket, dhcpPayload).orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -07001130
Yi Tsengdcef2c22017-08-05 20:34:06 -07001131 if (clientInterface == null) {
Yi Tseng51301292017-07-28 13:02:59 -07001132 log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
1133 return null;
1134 }
Yi Tsengdcef2c22017-08-05 20:34:06 -07001135 VlanId vlanId;
rsahot036620655b2018-02-26 15:01:37 -05001136 ConnectPoint inPort = context.inPacket().receivedFrom();
1137 boolean directConnFlag = directlyConnected(dhcpPayload);
1138 DhcpServerInfo foundServerInfo = findServerInfoFromServer(directConnFlag, inPort);
1139
1140 if (foundServerInfo == null) {
jayakumarthazhathed5f05d2018-09-25 15:56:51 -04001141 log.warn("Cannot find server info for {} server, inPort {}",
1142 directConnFlag ? "direct" : "indirect", inPort);
rsahot036620655b2018-02-26 15:01:37 -05001143 return null;
1144 } else {
Taras Lemkin96a0d342018-03-26 14:52:58 +00001145 if (Dhcp4HandlerUtil.isServerIpEmpty(foundServerInfo)) {
rsahot036620655b2018-02-26 15:01:37 -05001146 log.warn("Cannot find server info's ipaddress");
1147 return null;
1148 }
1149 }
Yi Tsengdcef2c22017-08-05 20:34:06 -07001150 if (clientInterface.vlanTagged().isEmpty()) {
1151 vlanId = clientInterface.vlan();
1152 } else {
1153 // might be multiple vlan in same interface
Yi Tseng4f2a0462017-08-31 11:21:00 -07001154 vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001155 }
1156 if (vlanId == null) {
1157 vlanId = VlanId.NONE;
1158 }
1159 etherReply.setVlanID(vlanId.toShort());
1160 etherReply.setSourceMACAddress(clientInterface.mac());
Yi Tseng51301292017-07-28 13:02:59 -07001161
Yi Tsengdcef2c22017-08-05 20:34:06 -07001162 if (!directlyConnected(dhcpPayload)) {
1163 // if client is indirectly connected, try use next hop mac address
1164 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1165 HostId hostId = HostId.hostId(macAddress, vlanId);
Daniel Ginsburgb1dec912018-01-29 11:40:22 -08001166 if (((int) dhcpPayload.getFlags() & 0x8000) == 0x0000) {
1167 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1168 if (record != null) {
1169 // if next hop can be found, use mac address of next hop
1170 record.nextHop().ifPresent(etherReply::setDestinationMACAddress);
1171 } else {
1172 // otherwise, discard the packet
1173 log.warn("Can't find record for host id {}, discard packet", hostId);
1174 return null;
1175 }
Yi Tsengdcef2c22017-08-05 20:34:06 -07001176 } else {
Daniel Ginsburgb1dec912018-01-29 11:40:22 -08001177 etherReply.setDestinationMACAddress(MacAddress.BROADCAST);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001178 }
Yi Tseng1696f562017-08-17 17:43:38 -07001179 } else {
1180 etherReply.setDestinationMACAddress(dhcpPayload.getClientHardwareAddress());
Yi Tsengdcef2c22017-08-05 20:34:06 -07001181 }
1182
Yi Tseng3df7f9d2017-08-17 13:08:31 -07001183 Ip4Address ipFacingClient = getFirstIpFromInterface(clientInterface);
Mayank Tiwari84a538f2018-11-14 15:41:40 -05001184 if (directlyConnected(dhcpPayload)) {
1185 // we leave the srcMac from the original packet
1186 // figure out the relay agent IP corresponding to the original request
1187 if (ipFacingClient == null) {
1188 log.warn("Cannot determine relay agent interface Ipv4 addr for host {}/{}. "
1189 + "Aborting relay for dhcp packet from server {}",
1190 etherReply.getDestinationMAC(), clientInterface.vlan(),
1191 ethernetPacket);
1192 return null;
1193 }
1194 // SRC_IP: IP facing client
1195 ipv4Packet.setSourceAddress(ipFacingClient.toInt());
1196 } else {
1197 // Get the IP address of the relay agent
1198 Ip4Address relayAgentIp = foundServerInfo
1199 .getRelayAgentIp4(clientInterface.connectPoint().deviceId()).orElse(null);
1200 if (relayAgentIp == null) {
1201 if (ipFacingClient == null) {
1202 log.warn("Cannot determine relay agent interface Ipv4 addr for host {}/{}. "
1203 + "Aborting relay for dhcp packet from server for indirect host {}",
1204 etherReply.getDestinationMAC(), clientInterface.vlan(),
1205 ethernetPacket);
1206 return null;
1207 } else {
1208 // SRC_IP: IP facing client
1209 ipv4Packet.setSourceAddress(ipFacingClient.toInt());
1210 }
1211 } else {
1212 // SRC_IP: relay agent IP
1213 ipv4Packet.setSourceAddress(relayAgentIp.toInt());
1214 }
Yi Tseng51301292017-07-28 13:02:59 -07001215 }
Yi Tseng51301292017-07-28 13:02:59 -07001216 // DST_IP: offered IP
Daniel Ginsburgb1dec912018-01-29 11:40:22 -08001217 if (((int) dhcpPayload.getFlags() & 0x8000) == 0x0000) {
1218 ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
1219 } else {
1220 ipv4Packet.setDestinationAddress(BROADCAST_IP);
1221 }
Yi Tseng51301292017-07-28 13:02:59 -07001222 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
1223 if (directlyConnected(dhcpPayload)) {
1224 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
1225 } else {
Charles Chan15cd86f2018-12-21 12:41:20 -08001226 // TODO Implement component config to support for both L2 and L3 relay
1227 // L2 relay expects destination port to be CLIENT_PORT while L3 relay expects SERVER_PORT
1228 // Currently we only support L2 relay for DHCPv4
Yi Tseng93ba53c2017-09-14 13:24:21 -07001229 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
Yi Tseng51301292017-07-28 13:02:59 -07001230 }
1231
1232 udpPacket.setPayload(dhcpPayload);
1233 ipv4Packet.setPayload(udpPacket);
1234 etherReply.setPayload(ipv4Packet);
Taras Lemkin96a0d342018-03-26 14:52:58 +00001235 return InternalPacket.internalPacket(etherReply, clientInterface.connectPoint());
Yi Tseng51301292017-07-28 13:02:59 -07001236 }
1237
Yi Tsengdcef2c22017-08-05 20:34:06 -07001238 /**
Charles Chand0dd7002017-10-08 23:53:36 -04001239 * Build the DHCP offer/ack with proper client port.
1240 *
1241 * @param ethernetPacket the original packet comes from server
1242 * @return new packet which will send to the client
1243 */
Taras Lemkin96a0d342018-03-26 14:52:58 +00001244 private InternalPacket processLeaseQueryFromServer(Ethernet ethernetPacket) {
Charles Chand0dd7002017-10-08 23:53:36 -04001245 // get dhcp header.
1246 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
1247 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
1248 UDP udpPacket = (UDP) ipv4Packet.getPayload();
1249 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
1250
1251 // determine the vlanId of the client host - note that this vlan id
1252 // could be different from the vlan in the packet from the server
Taras Lemkin96a0d342018-03-26 14:52:58 +00001253 Interface clientInterface = null;
1254 MacAddress destinationMac = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1255
1256 if (!learnRouteFromLeasequery) {
1257 int giaddr = ipv4Packet.getDestinationAddress();
1258 IpAddress destinationAddress = Ip4Address.valueOf(giaddr);
1259 log.debug("DHCPLEASEQUERYRESP giaddr: {}({})", giaddr, destinationAddress);
1260
1261 Host destinationHost = hostService.getHostsByIp(destinationAddress).stream().findFirst().orElse(null);
1262 if (destinationHost != null) {
1263 destinationMac = destinationHost.mac();
1264 log.trace("DHCPLEASEQUERYRESP destination mac is: {}", destinationMac);
1265 ConnectPoint destinationLocation = destinationHost.location();
1266 log.trace("Lookup for client interface by destination location {}", destinationLocation);
1267 clientInterface = interfaceService.getInterfacesByPort(destinationLocation)
1268 .stream()
1269 .filter(iface -> interfaceContainsVlan(iface, VlanId.vlanId(etherReply.getVlanID())))
1270 .findFirst()
1271 .orElse(null);
1272 log.trace("Found Host {} by ip {}", destinationHost, destinationAddress);
1273 log.debug("DHCPLEASEQUERYRESP Client interface: {}",
1274 (clientInterface != null ? clientInterface : "not resolved"));
1275
1276 }
1277 } else {
1278 clientInterface = getClientInterface(ethernetPacket, dhcpPayload).orElse(null);
1279 }
Charles Chand0dd7002017-10-08 23:53:36 -04001280
1281 if (clientInterface == null) {
1282 log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
1283 return null;
1284 }
1285 VlanId vlanId;
1286 if (clientInterface.vlanTagged().isEmpty()) {
1287 vlanId = clientInterface.vlan();
1288 } else {
1289 // might be multiple vlan in same interface
1290 vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
1291 }
1292 if (vlanId == null) {
1293 vlanId = VlanId.NONE;
1294 }
1295 etherReply.setVlanID(vlanId.toShort());
1296 etherReply.setSourceMACAddress(clientInterface.mac());
1297
Taras Lemkin96a0d342018-03-26 14:52:58 +00001298 if (!directlyConnected(dhcpPayload) && learnRouteFromLeasequery) {
Charles Chand0dd7002017-10-08 23:53:36 -04001299 // if client is indirectly connected, try use next hop mac address
1300 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1301 HostId hostId = HostId.hostId(macAddress, vlanId);
1302 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1303 if (record != null) {
1304 // if next hop can be found, use mac address of next hop
Taras Lemkin96a0d342018-03-26 14:52:58 +00001305 Optional<MacAddress> nextHop = record.nextHopTemp();
1306 if (!nextHop.isPresent()) {
1307 nextHop = record.nextHop();
1308 }
1309 nextHop.ifPresent(etherReply::setDestinationMACAddress);
Charles Chand0dd7002017-10-08 23:53:36 -04001310 } else {
1311 // otherwise, discard the packet
1312 log.warn("Can't find record for host id {}, discard packet", hostId);
1313 return null;
1314 }
1315 } else {
Taras Lemkin96a0d342018-03-26 14:52:58 +00001316 etherReply.setDestinationMACAddress(destinationMac);
Charles Chand0dd7002017-10-08 23:53:36 -04001317 }
1318
Charles Chand0dd7002017-10-08 23:53:36 -04001319 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
Taras Lemkin96a0d342018-03-26 14:52:58 +00001320 if (directlyConnected(dhcpPayload)) {
1321 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
1322 } else {
1323 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
1324 }
Charles Chand0dd7002017-10-08 23:53:36 -04001325
1326 udpPacket.setPayload(dhcpPayload);
1327 ipv4Packet.setPayload(udpPacket);
1328 etherReply.setPayload(ipv4Packet);
Taras Lemkin96a0d342018-03-26 14:52:58 +00001329 udpPacket.resetChecksum();
1330
1331 return InternalPacket.internalPacket(etherReply, clientInterface.connectPoint());
Charles Chand0dd7002017-10-08 23:53:36 -04001332 }
1333 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -07001334 * Extracts VLAN ID from relay agent option.
1335 *
1336 * @param dhcpPayload the DHCP payload
1337 * @return VLAN ID from DHCP payload; null if not exists
1338 */
Yi Tseng4f2a0462017-08-31 11:21:00 -07001339 private VlanId getVlanIdFromRelayAgentOption(DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001340 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
1341 if (option == null) {
1342 return null;
1343 }
1344 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
1345 if (circuitIdSubOption == null) {
1346 return null;
1347 }
1348 try {
1349 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
1350 return circuitId.vlanId();
1351 } catch (IllegalArgumentException e) {
1352 // can't deserialize the circuit ID
1353 return null;
1354 }
1355 }
1356
1357 /**
1358 * Removes DHCP relay agent information option (option 82) from DHCP payload.
1359 * Also reset giaddr to 0
1360 *
1361 * @param ethPacket the Ethernet packet to be processed
1362 * @return Ethernet packet processed
1363 */
1364 private Ethernet removeRelayAgentOption(Ethernet ethPacket) {
rsahot036620655b2018-02-26 15:01:37 -05001365 Ethernet ethernet = (Ethernet) ethPacket.duplicate();
Yi Tsengdcef2c22017-08-05 20:34:06 -07001366 IPv4 ipv4 = (IPv4) ethernet.getPayload();
1367 UDP udp = (UDP) ipv4.getPayload();
1368 DHCP dhcpPayload = (DHCP) udp.getPayload();
1369
1370 // removes relay agent information option
1371 List<DhcpOption> options = dhcpPayload.getOptions();
1372 options = options.stream()
1373 .filter(option -> option.getCode() != OptionCode_CircuitID.getValue())
1374 .collect(Collectors.toList());
1375 dhcpPayload.setOptions(options);
1376 dhcpPayload.setGatewayIPAddress(0);
1377
1378 udp.setPayload(dhcpPayload);
1379 ipv4.setPayload(udp);
1380 ethernet.setPayload(ipv4);
1381 return ethernet;
1382 }
1383
Taras Lemkin96a0d342018-03-26 14:52:58 +00001384 private boolean isDhcpPacketLeasequery(DHCP dhcpPacket) {
1385 switch (dhcpPacket.getPacketType()) {
1386 case DHCPLEASEACTIVE:
1387 case DHCPLEASEQUERY:
1388 case DHCPLEASEUNASSIGNED:
1389 case DHCPLEASEUNKNOWN:
1390 return true;
1391 default:
1392 return false;
1393 }
1394 }
Yi Tseng51301292017-07-28 13:02:59 -07001395
1396 /**
1397 * Check if the host is directly connected to the network or not.
1398 *
1399 * @param dhcpPayload the dhcp payload
1400 * @return true if the host is directly connected to the network; false otherwise
1401 */
1402 private boolean directlyConnected(DHCP dhcpPayload) {
Taras Lemkin96a0d342018-03-26 14:52:58 +00001403 // leasequery is always indirect
1404 if (isDhcpPacketLeasequery(dhcpPayload)) {
1405 return false;
1406 }
1407
Yi Tseng440e2b72017-08-24 14:47:34 -07001408 DhcpRelayAgentOption relayAgentOption =
1409 (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
Yi Tseng51301292017-07-28 13:02:59 -07001410
1411 // Doesn't contains relay option
1412 if (relayAgentOption == null) {
1413 return true;
1414 }
1415
Yi Tseng440e2b72017-08-24 14:47:34 -07001416 // check circuit id, if circuit id is invalid, we say it is an indirect host
1417 DhcpOption circuitIdOpt = relayAgentOption.getSubOption(CIRCUIT_ID.getValue());
Yi Tseng51301292017-07-28 13:02:59 -07001418
Yi Tseng440e2b72017-08-24 14:47:34 -07001419 try {
1420 CircuitId.deserialize(circuitIdOpt.getData());
Yi Tseng51301292017-07-28 13:02:59 -07001421 return true;
Yi Tseng440e2b72017-08-24 14:47:34 -07001422 } catch (Exception e) {
1423 // invalid circuit id
1424 return false;
Yi Tseng51301292017-07-28 13:02:59 -07001425 }
Yi Tseng51301292017-07-28 13:02:59 -07001426 }
1427
1428
1429 /**
1430 * Send the DHCP ack to the requester host.
1431 * Modify Host or Route store according to the type of DHCP.
1432 *
1433 * @param ethernetPacketAck the packet
1434 * @param dhcpPayload the DHCP data
1435 */
1436 private void handleDhcpAck(Ethernet ethernetPacketAck, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001437 Optional<Interface> outInterface = getClientInterface(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -07001438 if (!outInterface.isPresent()) {
1439 log.warn("Can't find output interface for dhcp: {}", dhcpPayload);
1440 return;
1441 }
1442
1443 Interface outIface = outInterface.get();
1444 HostLocation hostLocation = new HostLocation(outIface.connectPoint(), System.currentTimeMillis());
1445 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
Yi Tseng4f2a0462017-08-31 11:21:00 -07001446 VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001447 if (vlanId == null) {
1448 vlanId = outIface.vlan();
1449 }
Yi Tseng51301292017-07-28 13:02:59 -07001450 HostId hostId = HostId.hostId(macAddress, vlanId);
1451 Ip4Address ip = Ip4Address.valueOf(dhcpPayload.getYourIPAddress());
1452
1453 if (directlyConnected(dhcpPayload)) {
1454 // Add to host store if it connect to network directly
1455 Set<IpAddress> ips = Sets.newHashSet(ip);
Yi Tsengaa417a62017-09-08 17:22:51 -07001456 Host host = hostService.getHost(hostId);
Yi Tseng51301292017-07-28 13:02:59 -07001457
Yi Tsengaa417a62017-09-08 17:22:51 -07001458 Set<HostLocation> hostLocations = Sets.newHashSet(hostLocation);
1459 if (host != null) {
1460 // Dual homing support:
1461 // if host exists, use old locations and new location
1462 hostLocations.addAll(host.locations());
1463 }
1464 HostDescription desc = new DefaultHostDescription(macAddress, vlanId,
1465 hostLocations, ips, false);
1466 // Add IP address when dhcp server give the host new ip address
1467 providerService.hostDetected(hostId, desc, false);
Yi Tseng51301292017-07-28 13:02:59 -07001468 } else {
1469 // Add to route store if it does not connect to network directly
1470 // Get gateway host IP according to host mac address
Yi Tsengdcef2c22017-08-05 20:34:06 -07001471 // TODO: remove relay store here
Yi Tseng51301292017-07-28 13:02:59 -07001472 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1473
1474 if (record == null) {
1475 log.warn("Can't find DHCP record of host {}", hostId);
1476 return;
1477 }
1478
1479 MacAddress gwMac = record.nextHop().orElse(null);
1480 if (gwMac == null) {
1481 log.warn("Can't find gateway mac address from record {}", record);
1482 return;
1483 }
1484
1485 HostId gwHostId = HostId.hostId(gwMac, record.vlanId());
1486 Host gwHost = hostService.getHost(gwHostId);
1487
1488 if (gwHost == null) {
1489 log.warn("Can't find gateway host {}", gwHostId);
1490 return;
1491 }
1492
1493 Ip4Address nextHopIp = gwHost.ipAddresses()
1494 .stream()
1495 .filter(IpAddress::isIp4)
1496 .map(IpAddress::getIp4Address)
1497 .findFirst()
1498 .orElse(null);
1499
1500 if (nextHopIp == null) {
1501 log.warn("Can't find IP address of gateway {}", gwHost);
1502 return;
1503 }
1504
Charles Chan6305b692018-04-04 11:43:54 -07001505 Route route = new Route(Route.Source.DHCP, ip.toIpPrefix(), nextHopIp);
Daniel Ginsburg83b76452018-06-09 01:43:59 +03001506 routeStore.replaceRoute(route);
Yi Tseng51301292017-07-28 13:02:59 -07001507 }
Yi Tseng51301292017-07-28 13:02:59 -07001508 }
1509
1510 /**
Yi Tseng51301292017-07-28 13:02:59 -07001511 * Gets output interface of a dhcp packet.
1512 * If option 82 exists in the dhcp packet and the option was sent by
Yi Tseng4f2a0462017-08-31 11:21:00 -07001513 * ONOS (circuit format is correct), use the connect
Yi Tseng51301292017-07-28 13:02:59 -07001514 * point and vlan id from circuit id; otherwise, find host by destination
1515 * address and use vlan id from sender (dhcp server).
1516 *
1517 * @param ethPacket the ethernet packet
1518 * @param dhcpPayload the dhcp packet
1519 * @return an interface represent the output port and vlan; empty value
1520 * if the host or circuit id not found
1521 */
Yi Tsengdcef2c22017-08-05 20:34:06 -07001522 private Optional<Interface> getClientInterface(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tseng51301292017-07-28 13:02:59 -07001523 VlanId originalPacketVlanId = VlanId.vlanId(ethPacket.getVlanID());
Yi Tseng51301292017-07-28 13:02:59 -07001524 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
1525
Yi Tseng4f2a0462017-08-31 11:21:00 -07001526 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
1527 try {
1528 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
1529 ConnectPoint connectPoint = ConnectPoint.deviceConnectPoint(circuitId.connectPoint());
1530 VlanId vlanId = circuitId.vlanId();
1531 return interfaceService.getInterfacesByPort(connectPoint)
1532 .stream()
1533 .filter(iface -> interfaceContainsVlan(iface, vlanId))
1534 .findFirst();
1535 } catch (IllegalArgumentException ex) {
1536 // invalid circuit format, didn't sent by ONOS
1537 log.debug("Invalid circuit {}, use information from dhcp payload",
1538 circuitIdSubOption.getData());
Yi Tseng51301292017-07-28 13:02:59 -07001539 }
Yi Tseng51301292017-07-28 13:02:59 -07001540 // Use Vlan Id from DHCP server if DHCP relay circuit id was not
1541 // sent by ONOS or circuit Id can't be parsed
Yi Tsengdcef2c22017-08-05 20:34:06 -07001542 // TODO: remove relay store from this method
Yi Tseng51301292017-07-28 13:02:59 -07001543 MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
jayakumarthazhathed5f05d2018-09-25 15:56:51 -04001544 VlanId filteredVlanId = getVlanIdFromDhcpRecord(dstMac, originalPacketVlanId);
1545 // Get the vlan from the dhcp record
1546 if (filteredVlanId == null) {
1547 log.debug("not find the matching DHCP record for mac: {} and vlan: {}", dstMac, originalPacketVlanId);
1548 return Optional.empty();
1549 }
jayakumarthazhathed5f05d2018-09-25 15:56:51 -04001550 Optional<DhcpRecord> dhcpRecord = dhcpRelayStore.getDhcpRecord(HostId.hostId(dstMac, filteredVlanId));
Yi Tsengdcef2c22017-08-05 20:34:06 -07001551 ConnectPoint clientConnectPoint = dhcpRecord
Yi Tseng51301292017-07-28 13:02:59 -07001552 .map(DhcpRecord::locations)
1553 .orElse(Collections.emptySet())
1554 .stream()
1555 .reduce((hl1, hl2) -> {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001556 // find latest host connect point
Yi Tseng51301292017-07-28 13:02:59 -07001557 if (hl1 == null || hl2 == null) {
1558 return hl1 == null ? hl2 : hl1;
1559 }
1560 return hl1.time() > hl2.time() ? hl1 : hl2;
1561 })
Yi Tsengdcef2c22017-08-05 20:34:06 -07001562 .orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -07001563
Yi Tsengdcef2c22017-08-05 20:34:06 -07001564 if (clientConnectPoint != null) {
1565 return interfaceService.getInterfacesByPort(clientConnectPoint)
1566 .stream()
jayakumarthazhathed5f05d2018-09-25 15:56:51 -04001567 .filter(iface -> interfaceContainsVlan(iface, filteredVlanId))
Yi Tsengdcef2c22017-08-05 20:34:06 -07001568 .findFirst();
1569 }
1570 return Optional.empty();
Yi Tseng51301292017-07-28 13:02:59 -07001571 }
1572
1573 /**
jayakumarthazhathed5f05d2018-09-25 15:56:51 -04001574 * Get the required vlanId in case the DCHP record has more than one vlanId for a given MAC.
1575 *
1576 * @param mac MAC address of the DHCP client
1577 * @param vlan Expected vlan of the DHCP client
1578 */
1579 private VlanId getVlanIdFromDhcpRecord(MacAddress mac, VlanId vlan) {
1580 // Get all the DHCP records matching with the mac address
1581 // If only one entry is present then pick the vlan of that entry
1582 // If more then one entry is present then look for an entry with matching vlan
1583 // else return null
1584 Collection<DhcpRecord> records = dhcpRelayStore.getDhcpRecords();
1585 List<DhcpRecord> filteredRecords = new ArrayList<>();
1586 for (DhcpRecord e: records) {
1587 if (e.macAddress().equals(mac)) {
1588 filteredRecords.add(e);
1589 }
1590 }
1591 log.debug("getVlanIdFromDhcpRecord mac: {} vlan: {}", mac, vlan);
1592 log.debug("filteredRecords are: {}", filteredRecords);
1593 if (filteredRecords.size() == 1) {
1594 log.debug("Only one DHCP record entry. Returning back the vlan of that DHCP record: {}", filteredRecords);
1595 return filteredRecords.get(0).vlanId();
1596 }
1597 // Check in the DHCP filtered record for matching vlan
1598 for (DhcpRecord e: filteredRecords) {
1599 if (e.vlanId().equals(vlan)) {
1600 log.debug("Found a matching vlan entry in the DHCP record:{}", e);
1601 return vlan;
1602 }
1603 }
1604 // Found nothing return null
1605 log.debug("Returning null as no matching or more than one matching entry found");
1606 return null;
1607
1608 }
1609
jayakumarthazhathed5f05d2018-09-25 15:56:51 -04001610 /**
Yi Tseng51301292017-07-28 13:02:59 -07001611 * Send the response DHCP to the requester host.
1612 *
Taras Lemkin96a0d342018-03-26 14:52:58 +00001613 * @param thePacket the packet
Yi Tseng51301292017-07-28 13:02:59 -07001614 * @param dhcpPayload the DHCP data
1615 */
Taras Lemkin96a0d342018-03-26 14:52:58 +00001616 private void sendResponseToClient(InternalPacket thePacket, DHCP dhcpPayload) {
1617 checkNotNull(thePacket, "Nothing to send");
1618 checkNotNull(thePacket.getPacket(), "Packet to send must not be empty");
1619 checkNotNull(thePacket.getDestLocation(), "Packet destination not be empty");
1620
1621 Ethernet ethPacket = thePacket.getPacket();
Yi Tsengdcef2c22017-08-05 20:34:06 -07001622 if (directlyConnected(dhcpPayload)) {
1623 ethPacket = removeRelayAgentOption(ethPacket);
1624 }
Taras Lemkin96a0d342018-03-26 14:52:58 +00001625
Yi Tsengdcef2c22017-08-05 20:34:06 -07001626 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
Taras Lemkin96a0d342018-03-26 14:52:58 +00001627 .setOutput(thePacket.getDestLocation().port())
Yi Tsengdcef2c22017-08-05 20:34:06 -07001628 .build();
1629 OutboundPacket o = new DefaultOutboundPacket(
Taras Lemkin96a0d342018-03-26 14:52:58 +00001630 thePacket.getDestLocation().deviceId(),
Yi Tsengdcef2c22017-08-05 20:34:06 -07001631 treatment,
1632 ByteBuffer.wrap(ethPacket.serialize()));
1633 if (log.isTraceEnabled()) {
Taras Lemkin96a0d342018-03-26 14:52:58 +00001634 log.trace("Relaying packet to DHCP client {} via {}",
Yi Tsengdcef2c22017-08-05 20:34:06 -07001635 ethPacket,
Taras Lemkin96a0d342018-03-26 14:52:58 +00001636 thePacket.getDestLocation());
Yi Tsengdcef2c22017-08-05 20:34:06 -07001637 }
1638 packetService.emit(o);
Yi Tseng51301292017-07-28 13:02:59 -07001639 }
Yi Tseng483ac6f2017-08-02 15:03:31 -07001640
Yi Tsengaa417a62017-09-08 17:22:51 -07001641 @Override
1642 public void triggerProbe(Host host) {
1643 // Do nothing here
1644 }
1645
1646 @Override
1647 public ProviderId id() {
Charles Chand988c282017-09-12 17:09:32 -07001648 return PROVIDER_ID;
Yi Tsengaa417a62017-09-08 17:22:51 -07001649 }
1650
Yi Tseng483ac6f2017-08-02 15:03:31 -07001651 class InternalHostListener implements HostListener {
1652 @Override
1653 public void event(HostEvent event) {
Yi Tseng919b2df2017-09-07 16:22:51 -07001654 if (!configured()) {
1655 return;
1656 }
Yi Tseng483ac6f2017-08-02 15:03:31 -07001657 switch (event.type()) {
1658 case HOST_ADDED:
1659 case HOST_UPDATED:
Charles Chan24a96ff2018-08-13 10:32:03 -07001660 case HOST_MOVED:
Jordan Halterman6328db72018-04-10 13:34:50 -04001661 log.trace("Scheduled host event {}", event);
1662 hostEventExecutor.execute(() -> hostUpdated(event.subject()));
Yi Tseng483ac6f2017-08-02 15:03:31 -07001663 break;
1664 case HOST_REMOVED:
Jordan Halterman6328db72018-04-10 13:34:50 -04001665 log.trace("Scheduled host event {}", event);
1666 hostEventExecutor.execute(() -> hostRemoved(event.subject()));
Yi Tseng483ac6f2017-08-02 15:03:31 -07001667 break;
Yi Tseng483ac6f2017-08-02 15:03:31 -07001668 default:
1669 break;
1670 }
1671 }
1672 }
1673
1674 /**
Yi Tseng483ac6f2017-08-02 15:03:31 -07001675 * Handle host updated.
1676 * If the host is DHCP server or gateway, update connect mac and vlan.
1677 *
1678 * @param host the host
1679 */
1680 private void hostUpdated(Host host) {
Yi Tseng525ff402017-10-23 19:39:39 -07001681 hostUpdated(host, defaultServerInfoList);
1682 hostUpdated(host, indirectServerInfoList);
Yi Tseng483ac6f2017-08-02 15:03:31 -07001683 }
1684
Yi Tseng525ff402017-10-23 19:39:39 -07001685 private void hostUpdated(Host host, List<DhcpServerInfo> srverInfoList) {
jjosep004c05f55ab2018-08-21 09:01:10 -04001686 srverInfoList.stream().forEach(serverInfo -> {
1687 Ip4Address targetIp = serverInfo.getDhcpGatewayIp4().orElse(null);
Yi Tseng525ff402017-10-23 19:39:39 -07001688 Ip4Address serverIp = serverInfo.getDhcpServerIp4().orElse(null);
Yi Tseng525ff402017-10-23 19:39:39 -07001689 if (targetIp == null) {
1690 targetIp = serverIp;
1691 }
Yi Tseng525ff402017-10-23 19:39:39 -07001692 if (targetIp != null) {
1693 if (host.ipAddresses().contains(targetIp)) {
1694 serverInfo.setDhcpConnectMac(host.mac());
1695 serverInfo.setDhcpConnectVlan(host.vlan());
1696 requestDhcpPacket(serverIp);
1697 }
1698 }
jjosep004c05f55ab2018-08-21 09:01:10 -04001699 });
Yi Tseng525ff402017-10-23 19:39:39 -07001700 }
1701
1702
Yi Tseng483ac6f2017-08-02 15:03:31 -07001703 /**
1704 * Handle host removed.
1705 * If the host is DHCP server or gateway, unset connect mac and vlan.
1706 *
1707 * @param host the host
1708 */
1709 private void hostRemoved(Host host) {
Yi Tseng525ff402017-10-23 19:39:39 -07001710 hostRemoved(host, defaultServerInfoList);
1711 hostRemoved(host, indirectServerInfoList);
1712 }
1713
1714 private void hostRemoved(Host host, List<DhcpServerInfo> serverInfoList) {
jjosep004c05f55ab2018-08-21 09:01:10 -04001715 serverInfoList.stream().forEach(serverInfo -> {
1716 Ip4Address targetIp = serverInfo.getDhcpGatewayIp4().orElse(null);
Yi Tseng525ff402017-10-23 19:39:39 -07001717 Ip4Address serverIp = serverInfo.getDhcpServerIp4().orElse(null);
Yi Tseng525ff402017-10-23 19:39:39 -07001718 if (targetIp == null) {
1719 targetIp = serverIp;
Yi Tseng919b2df2017-09-07 16:22:51 -07001720 }
Yi Tseng525ff402017-10-23 19:39:39 -07001721
1722 if (targetIp != null) {
1723 if (host.ipAddresses().contains(targetIp)) {
Yi Tseng919b2df2017-09-07 16:22:51 -07001724 serverInfo.setDhcpConnectVlan(null);
1725 serverInfo.setDhcpConnectMac(null);
Yi Tseng525ff402017-10-23 19:39:39 -07001726 cancelDhcpPacket(serverIp);
Yi Tseng919b2df2017-09-07 16:22:51 -07001727 }
Yi Tseng483ac6f2017-08-02 15:03:31 -07001728 }
jjosep004c05f55ab2018-08-21 09:01:10 -04001729 });
Yi Tseng525ff402017-10-23 19:39:39 -07001730 }
Yi Tseng919b2df2017-09-07 16:22:51 -07001731
Yi Tseng525ff402017-10-23 19:39:39 -07001732 private void requestDhcpPacket(Ip4Address serverIp) {
1733 requestServerDhcpPacket(serverIp);
1734 requestClientDhcpPacket(serverIp);
1735 }
Yi Tseng919b2df2017-09-07 16:22:51 -07001736
Yi Tseng525ff402017-10-23 19:39:39 -07001737 private void cancelDhcpPacket(Ip4Address serverIp) {
1738 cancelServerDhcpPacket(serverIp);
1739 cancelClientDhcpPacket(serverIp);
1740 }
1741
1742 private void cancelServerDhcpPacket(Ip4Address serverIp) {
1743 TrafficSelector serverSelector =
1744 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1745 .matchIPSrc(serverIp.toIpPrefix())
1746 .build();
1747 packetService.cancelPackets(serverSelector,
1748 PacketPriority.CONTROL,
1749 appId);
1750 }
1751
1752 private void requestServerDhcpPacket(Ip4Address serverIp) {
1753 TrafficSelector serverSelector =
1754 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1755 .matchIPSrc(serverIp.toIpPrefix())
1756 .build();
1757 packetService.requestPackets(serverSelector,
1758 PacketPriority.CONTROL,
1759 appId);
1760 }
1761
1762 private void cancelClientDhcpPacket(Ip4Address serverIp) {
1763 // Packet comes from relay
1764 TrafficSelector indirectClientSelector =
1765 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1766 .matchIPDst(serverIp.toIpPrefix())
1767 .build();
1768 packetService.cancelPackets(indirectClientSelector,
1769 PacketPriority.CONTROL,
1770 appId);
1771
1772 // Packet comes from client
1773 packetService.cancelPackets(CLIENT_SERVER_SELECTOR,
1774 PacketPriority.CONTROL,
1775 appId);
1776 }
1777
1778 private void requestClientDhcpPacket(Ip4Address serverIp) {
1779 // Packet comes from relay
1780 TrafficSelector indirectClientSelector =
1781 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1782 .matchIPDst(serverIp.toIpPrefix())
1783 .build();
1784 packetService.requestPackets(indirectClientSelector,
1785 PacketPriority.CONTROL,
1786 appId);
1787
1788 // Packet comes from client
1789 packetService.requestPackets(CLIENT_SERVER_SELECTOR,
1790 PacketPriority.CONTROL,
1791 appId);
1792 }
1793
1794 /**
1795 * Process the ignore rules.
1796 *
1797 * @param deviceId the device id
1798 * @param vlanId the vlan to be ignored
1799 * @param op the operation, ADD to install; REMOVE to uninstall rules
1800 */
1801 private void processIgnoreVlanRule(DeviceId deviceId, VlanId vlanId, Objective.Operation op) {
Yi Tseng525ff402017-10-23 19:39:39 -07001802 AtomicInteger installedCount = new AtomicInteger(DHCP_SELECTORS.size());
1803 DHCP_SELECTORS.forEach(trafficSelector -> {
1804 TrafficSelector selector = DefaultTrafficSelector.builder(trafficSelector)
1805 .matchVlanId(vlanId)
1806 .build();
1807
1808 ForwardingObjective.Builder builder = DefaultForwardingObjective.builder()
1809 .withFlag(ForwardingObjective.Flag.VERSATILE)
1810 .withSelector(selector)
1811 .withPriority(IGNORE_CONTROL_PRIORITY)
Yi Tsengdbabeed2017-11-29 10:49:20 -08001812 .withTreatment(DefaultTrafficTreatment.emptyTreatment())
Yi Tseng525ff402017-10-23 19:39:39 -07001813 .fromApp(appId);
1814
1815
1816 ObjectiveContext objectiveContext = new ObjectiveContext() {
1817 @Override
1818 public void onSuccess(Objective objective) {
1819 log.info("Ignore rule {} (Vlan id {}, device {}, selector {})",
1820 op, vlanId, deviceId, selector);
1821 int countDown = installedCount.decrementAndGet();
1822 if (countDown != 0) {
1823 return;
1824 }
1825 switch (op) {
1826 case ADD:
1827 ignoredVlans.put(deviceId, vlanId);
1828 break;
1829 case REMOVE:
1830 ignoredVlans.remove(deviceId, vlanId);
1831 break;
1832 default:
1833 log.warn("Unsupported objective operation {}", op);
1834 break;
1835 }
Yi Tseng919b2df2017-09-07 16:22:51 -07001836 }
Yi Tseng525ff402017-10-23 19:39:39 -07001837
1838 @Override
1839 public void onError(Objective objective, ObjectiveError error) {
1840 log.warn("Can't {} ignore rule (vlan id {}, selector {}, device {}) due to {}",
1841 op, vlanId, selector, deviceId, error);
Yi Tseng919b2df2017-09-07 16:22:51 -07001842 }
Yi Tseng525ff402017-10-23 19:39:39 -07001843 };
1844
1845 ForwardingObjective fwd;
1846 switch (op) {
1847 case ADD:
1848 fwd = builder.add(objectiveContext);
1849 break;
1850 case REMOVE:
1851 fwd = builder.remove(objectiveContext);
1852 break;
1853 default:
1854 log.warn("Unsupported objective operation {}", op);
1855 return;
Yi Tseng4f2a0462017-08-31 11:21:00 -07001856 }
Yi Tseng525ff402017-10-23 19:39:39 -07001857
1858 Device device = deviceService.getDevice(deviceId);
1859 if (device == null || !device.is(Pipeliner.class)) {
1860 log.warn("Device {} is not available now, wait until device is available", deviceId);
1861 return;
1862 }
1863 flowObjectiveService.apply(deviceId, fwd);
1864 });
Yi Tseng483ac6f2017-08-02 15:03:31 -07001865 }
Kalhee Kimba366062017-11-07 16:32:09 +00001866
1867 @Override
1868 public void setDhcpFpmEnabled(Boolean enabled) {
1869 // v4 does not use fpm. Do nothing.
1870 }
rsahot036620655b2018-02-26 15:01:37 -05001871 private List<DhcpServerInfo> findValidServerInfo(boolean directConnFlag) {
1872 List<DhcpServerInfo> validServerInfo;
1873
1874 if (directConnFlag || indirectServerInfoList.isEmpty()) {
1875 validServerInfo = new ArrayList<DhcpServerInfo>(defaultServerInfoList);
1876 } else {
1877 validServerInfo = new ArrayList<DhcpServerInfo>(indirectServerInfoList);
1878 }
1879 return validServerInfo;
1880 }
1881
rsahot036620655b2018-02-26 15:01:37 -05001882 private boolean checkDhcpServerConnPt(boolean directConnFlag,
1883 DhcpServerInfo serverInfo) {
1884 if (serverInfo.getDhcpServerConnectPoint() == null) {
1885 log.warn("DHCP4 server connect point for {} connPt {}",
1886 directConnFlag ? "direct" : "indirect", serverInfo.getDhcpServerConnectPoint());
1887 return false;
1888 }
1889 return true;
1890 }
1891
1892 /**
1893 * Checks if serverInfo's host info (mac and vlan) is filled in; if not, fills in.
1894 *
1895 * @param serverInfo server information
1896 * @return newServerInfo if host info can be either found or filled in.
1897 */
1898 private DhcpServerInfo getHostInfoForServerInfo(DhcpServerInfo serverInfo, List<DhcpServerInfo> sererInfoList) {
1899 DhcpServerInfo newServerInfo = null;
1900 MacAddress dhcpServerConnectMac = serverInfo.getDhcpConnectMac().orElse(null);
1901 VlanId dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
1902 ConnectPoint dhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
1903
1904 if (dhcpServerConnectMac != null && dhcpConnectVlan != null) {
1905 newServerInfo = serverInfo;
Charles Chan2de55302018-03-02 18:27:59 -08001906 log.debug("DHCP server {} host info found. ConnectPt{} Mac {} vlan {}", serverInfo.getDhcpServerIp4(),
rsahot036620655b2018-02-26 15:01:37 -05001907 dhcpServerConnectPoint, dhcpServerConnectMac, dhcpConnectVlan);
1908 } else {
Harshada Chaundkarf0780fe2019-05-06 20:16:13 +00001909 log.debug("DHCP server {} not resolve yet connectPt {} mac {} vlan {}", serverInfo.getDhcpServerIp4(),
rsahot036620655b2018-02-26 15:01:37 -05001910 dhcpServerConnectPoint, dhcpServerConnectMac, dhcpConnectVlan);
1911
1912 Ip4Address ipToProbe;
1913 if (serverInfo.getDhcpGatewayIp4().isPresent()) {
1914 ipToProbe = serverInfo.getDhcpGatewayIp4().get();
1915 } else {
1916 ipToProbe = serverInfo.getDhcpServerIp4().orElse(null);
1917 }
1918 String hostToProbe = serverInfo.getDhcpGatewayIp6()
1919 .map(ip -> "gateway").orElse("server");
1920
Harshada Chaundkarf0780fe2019-05-06 20:16:13 +00001921 log.debug("Dynamically probing to resolve {} IP {}", hostToProbe, ipToProbe);
rsahot036620655b2018-02-26 15:01:37 -05001922 hostService.startMonitoringIp(ipToProbe);
1923
1924 Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
1925 if (!hosts.isEmpty()) {
1926 int serverInfoIndex = sererInfoList.indexOf(serverInfo);
1927 Host host = hosts.iterator().next();
1928 serverInfo.setDhcpConnectVlan(host.vlan());
1929 serverInfo.setDhcpConnectMac(host.mac());
1930 // replace the serverInfo in the list
1931 sererInfoList.set(serverInfoIndex, serverInfo);
1932 newServerInfo = serverInfo;
1933 log.warn("Dynamically host found host {}", host);
1934 } else {
Harshada Chaundkarf0780fe2019-05-06 20:16:13 +00001935 log.debug("No host found host ip {} dynamically", ipToProbe);
rsahot036620655b2018-02-26 15:01:37 -05001936 }
1937 }
1938 return newServerInfo;
1939 }
1940
1941 /**
1942 * Gets Interface facing to the server for default host.
1943 *
1944 * @param serverInfo server information
1945 * @return the Interface facing to the server; null if not found
1946 */
1947 private Interface getServerInterface(DhcpServerInfo serverInfo) {
1948 Interface serverInterface = null;
1949
1950 ConnectPoint dhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
1951 VlanId dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
1952
1953 if (dhcpServerConnectPoint != null && dhcpConnectVlan != null) {
1954 serverInterface = interfaceService.getInterfacesByPort(dhcpServerConnectPoint)
1955 .stream()
Taras Lemkin96a0d342018-03-26 14:52:58 +00001956 .filter(iface -> Dhcp4HandlerUtil.interfaceContainsVlan(iface, dhcpConnectVlan))
rsahot036620655b2018-02-26 15:01:37 -05001957 .findFirst()
1958 .orElse(null);
1959 } else {
1960 log.warn("DHCP server {} not resolve yet connectPoint {} vlan {}", serverInfo.getDhcpServerIp6(),
1961 dhcpServerConnectPoint, dhcpConnectVlan);
1962 }
1963
1964 return serverInterface;
1965 }
1966
1967 //forward the packet to ConnectPoint where the DHCP server is attached.
1968 private void forwardPacket(InternalPacket packet) {
1969 //send Packetout to dhcp server connectpoint.
Taras Lemkin96a0d342018-03-26 14:52:58 +00001970 if (packet.getDestLocation() != null) {
rsahot036620655b2018-02-26 15:01:37 -05001971 TrafficTreatment t = DefaultTrafficTreatment.builder()
Taras Lemkin96a0d342018-03-26 14:52:58 +00001972 .setOutput(packet.getDestLocation().port()).build();
rsahot036620655b2018-02-26 15:01:37 -05001973 OutboundPacket o = new DefaultOutboundPacket(
Taras Lemkin96a0d342018-03-26 14:52:58 +00001974 packet.getDestLocation().deviceId(), t, ByteBuffer.wrap(packet.getPacket().serialize()));
rsahot036620655b2018-02-26 15:01:37 -05001975 if (log.isTraceEnabled()) {
Taras Lemkin96a0d342018-03-26 14:52:58 +00001976 log.trace("Relaying packet to destination {}", packet.getDestLocation());
rsahot036620655b2018-02-26 15:01:37 -05001977 }
Taras Lemkin96a0d342018-03-26 14:52:58 +00001978 log.debug("packetService.emit(o) to port {}", packet.getDestLocation());
rsahot036620655b2018-02-26 15:01:37 -05001979 packetService.emit(o);
1980 }
1981 }
1982
rsahot036620655b2018-02-26 15:01:37 -05001983 private DhcpServerInfo findServerInfoFromServer(boolean directConnFlag, ConnectPoint inPort) {
1984 List<DhcpServerInfo> validServerInfoList = findValidServerInfo(directConnFlag);
1985 DhcpServerInfo foundServerInfo = null;
1986 for (DhcpServerInfo serverInfo : validServerInfoList) {
1987 if (inPort.equals(serverInfo.getDhcpServerConnectPoint().get())) {
1988 foundServerInfo = serverInfo;
Charles Chan2de55302018-03-02 18:27:59 -08001989 log.debug("ServerInfo found for Rcv port {} Server Connect Point {} for {}",
rsahot036620655b2018-02-26 15:01:37 -05001990 inPort, serverInfo.getDhcpServerConnectPoint(), directConnFlag ? "direct" : "indirect");
1991 break;
rsahot036620655b2018-02-26 15:01:37 -05001992 }
1993 }
1994 return foundServerInfo;
1995 }
Yi Tseng51301292017-07-28 13:02:59 -07001996}