blob: 424b628d3b4c27d9d07c4dbd5608aec4789d602c [file] [log] [blame]
Yi Tseng51301292017-07-28 13:02:59 -07001/*
2 * Copyright 2017-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17
18package org.onosproject.dhcprelay;
19
Yi Tseng525ff402017-10-23 19:39:39 -070020import com.google.common.collect.HashMultimap;
21import com.google.common.collect.ImmutableSet;
Charles Chane5e0c9a2018-03-30 12:11:34 -070022import com.google.common.collect.Lists;
Yi Tseng525ff402017-10-23 19:39:39 -070023import com.google.common.collect.Multimap;
Charles Chan70fdd492018-03-07 17:36:06 -080024import com.google.common.collect.Multimaps;
Yi Tseng51301292017-07-28 13:02:59 -070025import com.google.common.collect.Sets;
Yi Tseng483ac6f2017-08-02 15:03:31 -070026import org.apache.felix.scr.annotations.Activate;
Yi Tseng51301292017-07-28 13:02:59 -070027import org.apache.felix.scr.annotations.Component;
Yi Tseng483ac6f2017-08-02 15:03:31 -070028import org.apache.felix.scr.annotations.Deactivate;
Taras Lemkin96a0d342018-03-26 14:52:58 +000029import org.apache.felix.scr.annotations.Modified;
Yi Tseng51301292017-07-28 13:02:59 -070030import org.apache.felix.scr.annotations.Property;
31import org.apache.felix.scr.annotations.Reference;
32import org.apache.felix.scr.annotations.ReferenceCardinality;
33import org.apache.felix.scr.annotations.Service;
34import org.onlab.packet.BasePacket;
35import org.onlab.packet.DHCP;
36import org.onlab.packet.Ethernet;
37import org.onlab.packet.IPv4;
38import org.onlab.packet.Ip4Address;
39import org.onlab.packet.IpAddress;
40import org.onlab.packet.MacAddress;
Yi Tseng525ff402017-10-23 19:39:39 -070041import org.onlab.packet.TpPort;
Yi Tseng51301292017-07-28 13:02:59 -070042import org.onlab.packet.UDP;
43import org.onlab.packet.VlanId;
44import org.onlab.packet.dhcp.CircuitId;
45import org.onlab.packet.dhcp.DhcpOption;
46import org.onlab.packet.dhcp.DhcpRelayAgentOption;
Taras Lemkin96a0d342018-03-26 14:52:58 +000047import org.onlab.util.Tools;
48import org.onosproject.cfg.ComponentConfigService;
Yi Tseng525ff402017-10-23 19:39:39 -070049import org.onosproject.core.ApplicationId;
50import org.onosproject.core.CoreService;
Yi Tseng51301292017-07-28 13:02:59 -070051import org.onosproject.dhcprelay.api.DhcpHandler;
Yi Tseng919b2df2017-09-07 16:22:51 -070052import org.onosproject.dhcprelay.api.DhcpServerInfo;
Yi Tseng483ac6f2017-08-02 15:03:31 -070053import org.onosproject.dhcprelay.config.DhcpServerConfig;
Yi Tseng525ff402017-10-23 19:39:39 -070054import org.onosproject.dhcprelay.config.IgnoreDhcpConfig;
Yi Tseng51301292017-07-28 13:02:59 -070055import org.onosproject.dhcprelay.store.DhcpRecord;
56import org.onosproject.dhcprelay.store.DhcpRelayStore;
Yi Tseng525ff402017-10-23 19:39:39 -070057import org.onosproject.net.Device;
58import org.onosproject.net.DeviceId;
59import org.onosproject.net.behaviour.Pipeliner;
60import org.onosproject.net.device.DeviceService;
61import org.onosproject.net.flow.DefaultTrafficSelector;
62import org.onosproject.net.flow.TrafficSelector;
63import org.onosproject.net.flowobjective.DefaultForwardingObjective;
64import org.onosproject.net.flowobjective.FlowObjectiveService;
65import org.onosproject.net.flowobjective.ForwardingObjective;
66import org.onosproject.net.flowobjective.Objective;
67import org.onosproject.net.flowobjective.ObjectiveContext;
68import org.onosproject.net.flowobjective.ObjectiveError;
Yi Tseng483ac6f2017-08-02 15:03:31 -070069import org.onosproject.net.host.HostEvent;
70import org.onosproject.net.host.HostListener;
Yi Tsengaa417a62017-09-08 17:22:51 -070071import org.onosproject.net.host.HostProvider;
72import org.onosproject.net.host.HostProviderRegistry;
73import org.onosproject.net.host.HostProviderService;
Ray Milkeyfacf2862017-08-03 11:58:29 -070074import org.onosproject.net.intf.Interface;
75import org.onosproject.net.intf.InterfaceService;
Yi Tseng525ff402017-10-23 19:39:39 -070076import org.onosproject.net.packet.PacketPriority;
Yi Tsengaa417a62017-09-08 17:22:51 -070077import org.onosproject.net.provider.ProviderId;
Ray Milkey69ec8712017-08-08 13:00:43 -070078import org.onosproject.routeservice.Route;
79import org.onosproject.routeservice.RouteStore;
Yi Tseng51301292017-07-28 13:02:59 -070080import org.onosproject.net.ConnectPoint;
81import org.onosproject.net.Host;
82import org.onosproject.net.HostId;
83import org.onosproject.net.HostLocation;
84import org.onosproject.net.flow.DefaultTrafficTreatment;
85import org.onosproject.net.flow.TrafficTreatment;
86import org.onosproject.net.host.DefaultHostDescription;
87import org.onosproject.net.host.HostDescription;
88import org.onosproject.net.host.HostService;
Yi Tseng51301292017-07-28 13:02:59 -070089import org.onosproject.net.host.InterfaceIpAddress;
90import org.onosproject.net.packet.DefaultOutboundPacket;
91import org.onosproject.net.packet.OutboundPacket;
92import org.onosproject.net.packet.PacketContext;
93import org.onosproject.net.packet.PacketService;
Taras Lemkin96a0d342018-03-26 14:52:58 +000094import org.osgi.service.component.ComponentContext;
Yi Tseng51301292017-07-28 13:02:59 -070095import org.slf4j.Logger;
96import org.slf4j.LoggerFactory;
97
98import java.nio.ByteBuffer;
Taras Lemkin96a0d342018-03-26 14:52:58 +000099import java.util.ArrayList;
Yi Tsengdcef2c22017-08-05 20:34:06 -0700100import java.util.Collection;
Yi Tseng51301292017-07-28 13:02:59 -0700101import java.util.Collections;
Taras Lemkin96a0d342018-03-26 14:52:58 +0000102import java.util.Dictionary;
Yi Tseng51301292017-07-28 13:02:59 -0700103import java.util.List;
104import java.util.Optional;
105import java.util.Set;
Charles Chan909cff82018-03-05 13:14:02 -0800106import java.util.concurrent.CopyOnWriteArrayList;
Jordan Halterman6328db72018-04-10 13:34:50 -0400107import java.util.concurrent.Executor;
Yi Tseng525ff402017-10-23 19:39:39 -0700108import java.util.concurrent.atomic.AtomicInteger;
Yi Tseng51301292017-07-28 13:02:59 -0700109import java.util.stream.Collectors;
110
111import static com.google.common.base.Preconditions.checkNotNull;
112import static com.google.common.base.Preconditions.checkState;
Jordan Halterman6328db72018-04-10 13:34:50 -0400113import static java.util.concurrent.Executors.newSingleThreadExecutor;
Yi Tseng51301292017-07-28 13:02:59 -0700114import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_CircuitID;
115import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_END;
116import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
117import static org.onlab.packet.MacAddress.valueOf;
118import static org.onlab.packet.dhcp.DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID;
Jordan Halterman6328db72018-04-10 13:34:50 -0400119import static org.onlab.util.Tools.groupedThreads;
Yi Tseng525ff402017-10-23 19:39:39 -0700120import static org.onosproject.net.flowobjective.Objective.Operation.ADD;
121import static org.onosproject.net.flowobjective.Objective.Operation.REMOVE;
Yi Tseng51301292017-07-28 13:02:59 -0700122
rsahot036620655b2018-02-26 15:01:37 -0500123
Yi Tseng51301292017-07-28 13:02:59 -0700124@Component
125@Service
126@Property(name = "version", value = "4")
Yi Tsengaa417a62017-09-08 17:22:51 -0700127public class Dhcp4HandlerImpl implements DhcpHandler, HostProvider {
Charles Chand988c282017-09-12 17:09:32 -0700128 public static final String DHCP_V4_RELAY_APP = "org.onosproject.Dhcp4HandlerImpl";
129 public static final ProviderId PROVIDER_ID = new ProviderId("dhcp4", DHCP_V4_RELAY_APP);
Yi Tseng525ff402017-10-23 19:39:39 -0700130 private static final String BROADCAST_IP = "255.255.255.255";
131 private static final int IGNORE_CONTROL_PRIORITY = PacketPriority.CONTROL.priorityValue() + 1000;
132
Taras Lemkin96a0d342018-03-26 14:52:58 +0000133 private static final String LQ_ROUTE_PROPERTY_NAME = "learnRouteFromLeasequery";
134
Yi Tseng525ff402017-10-23 19:39:39 -0700135 private static final TrafficSelector CLIENT_SERVER_SELECTOR = DefaultTrafficSelector.builder()
136 .matchEthType(Ethernet.TYPE_IPV4)
137 .matchIPProtocol(IPv4.PROTOCOL_UDP)
138 .matchIPSrc(Ip4Address.ZERO.toIpPrefix())
139 .matchIPDst(Ip4Address.valueOf(BROADCAST_IP).toIpPrefix())
140 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
141 .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
142 .build();
143 private static final TrafficSelector SERVER_RELAY_SELECTOR = DefaultTrafficSelector.builder()
144 .matchEthType(Ethernet.TYPE_IPV4)
145 .matchIPProtocol(IPv4.PROTOCOL_UDP)
146 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
147 .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
148 .build();
149 static final Set<TrafficSelector> DHCP_SELECTORS = ImmutableSet.of(
150 CLIENT_SERVER_SELECTOR,
151 SERVER_RELAY_SELECTOR
152 );
Yi Tseng51301292017-07-28 13:02:59 -0700153 private static Logger log = LoggerFactory.getLogger(Dhcp4HandlerImpl.class);
154
155 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
156 protected DhcpRelayStore dhcpRelayStore;
157
158 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
159 protected PacketService packetService;
160
161 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Yi Tseng51301292017-07-28 13:02:59 -0700162 protected RouteStore routeStore;
163
164 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
165 protected InterfaceService interfaceService;
166
167 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
168 protected HostService hostService;
169
Yi Tsengaa417a62017-09-08 17:22:51 -0700170 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
171 protected HostProviderRegistry providerRegistry;
172
Yi Tseng525ff402017-10-23 19:39:39 -0700173 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
174 protected CoreService coreService;
175
176 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
177 protected DeviceService deviceService;
178
179 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
180 protected FlowObjectiveService flowObjectiveService;
181
Taras Lemkin96a0d342018-03-26 14:52:58 +0000182 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
183 protected ComponentConfigService cfgService;
184
Yi Tsengaa417a62017-09-08 17:22:51 -0700185 protected HostProviderService providerService;
Yi Tseng525ff402017-10-23 19:39:39 -0700186 protected ApplicationId appId;
Charles Chan70fdd492018-03-07 17:36:06 -0800187 protected Multimap<DeviceId, VlanId> ignoredVlans = Multimaps.synchronizedMultimap(HashMultimap.create());
Yi Tseng483ac6f2017-08-02 15:03:31 -0700188 private InternalHostListener hostListener = new InternalHostListener();
189
Charles Chan909cff82018-03-05 13:14:02 -0800190 private List<DhcpServerInfo> defaultServerInfoList = new CopyOnWriteArrayList<>();
191 private List<DhcpServerInfo> indirectServerInfoList = new CopyOnWriteArrayList<>();
Taras Lemkin96a0d342018-03-26 14:52:58 +0000192
193 @Property(name = Dhcp4HandlerImpl.LQ_ROUTE_PROPERTY_NAME, boolValue = false,
194 label = "Enable learning routing information from LQ")
195 private Boolean learnRouteFromLeasequery = Boolean.TRUE;
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);
215 defaultServerInfoList.clear();
216 indirectServerInfoList.forEach(this::stopMonitoringIps);
217 indirectServerInfoList.clear();
Yi Tseng483ac6f2017-08-02 15:03:31 -0700218 }
219
Taras Lemkin96a0d342018-03-26 14:52:58 +0000220 @Modified
221 protected void modified(ComponentContext context) {
222 Dictionary<?, ?> properties = context.getProperties();
223 Boolean flag;
224 flag = Tools.isPropertyEnabled(properties, Dhcp4HandlerImpl.LQ_ROUTE_PROPERTY_NAME);
225 if (flag != null) {
226 learnRouteFromLeasequery = flag;
227 log.info("Learning routes from DHCP leasequery is {}",
228 learnRouteFromLeasequery ? "enabled" : "disabled");
229 }
230 }
231
Yi Tseng919b2df2017-09-07 16:22:51 -0700232 private void stopMonitoringIps(DhcpServerInfo serverInfo) {
233 serverInfo.getDhcpGatewayIp4().ifPresent(gatewayIp -> {
234 hostService.stopMonitoringIp(gatewayIp);
235 });
236 serverInfo.getDhcpServerIp4().ifPresent(serverIp -> {
237 hostService.stopMonitoringIp(serverIp);
238 });
Yi Tseng51301292017-07-28 13:02:59 -0700239 }
240
241 @Override
Yi Tseng483ac6f2017-08-02 15:03:31 -0700242 public void setDefaultDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
Yi Tseng919b2df2017-09-07 16:22:51 -0700243 setDhcpServerConfigs(configs, defaultServerInfoList);
244 }
245
246 @Override
247 public void setIndirectDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
248 setDhcpServerConfigs(configs, indirectServerInfoList);
249 }
250
251 @Override
252 public List<DhcpServerInfo> getDefaultDhcpServerInfoList() {
253 return defaultServerInfoList;
254 }
255
256 @Override
257 public List<DhcpServerInfo> getIndirectDhcpServerInfoList() {
258 return indirectServerInfoList;
259 }
260
Yi Tseng525ff402017-10-23 19:39:39 -0700261 @Override
262 public void updateIgnoreVlanConfig(IgnoreDhcpConfig config) {
263 if (config == null) {
264 ignoredVlans.forEach(((deviceId, vlanId) -> {
265 processIgnoreVlanRule(deviceId, vlanId, REMOVE);
266 }));
267 return;
268 }
269 config.ignoredVlans().forEach((deviceId, vlanId) -> {
270 if (ignoredVlans.get(deviceId).contains(vlanId)) {
271 // don't need to process if it already ignored
272 return;
273 }
274 processIgnoreVlanRule(deviceId, vlanId, ADD);
275 });
276
277 ignoredVlans.forEach((deviceId, vlanId) -> {
278 if (!config.ignoredVlans().get(deviceId).contains(vlanId)) {
279 // not contains in new config, remove it
280 processIgnoreVlanRule(deviceId, vlanId, REMOVE);
281 }
282 });
283 }
284
Saurav Dasb805f1a2017-12-13 16:19:35 -0800285 @Override
286 public void removeIgnoreVlanState(IgnoreDhcpConfig config) {
287 if (config == null) {
288 ignoredVlans.clear();
289 return;
290 }
291 config.ignoredVlans().forEach((deviceId, vlanId) -> {
292 ignoredVlans.remove(deviceId, vlanId);
293 });
294 }
295
Yi Tseng919b2df2017-09-07 16:22:51 -0700296 public void setDhcpServerConfigs(Collection<DhcpServerConfig> configs, List<DhcpServerInfo> serverInfoList) {
Yi Tseng483ac6f2017-08-02 15:03:31 -0700297 if (configs.size() == 0) {
298 // no config to update
299 return;
300 }
301
rsahot036620655b2018-02-26 15:01:37 -0500302 Boolean isConfigValid = false;
303 for (DhcpServerConfig serverConfig : configs) {
304 if (serverConfig.getDhcpServerIp4().isPresent()) {
305 isConfigValid = true;
306 break;
307 }
Yi Tseng483ac6f2017-08-02 15:03:31 -0700308 }
rsahot036620655b2018-02-26 15:01:37 -0500309 if (!isConfigValid) {
310 log.warn("No IP V4 server address found.");
311 return; // No IP V6 address found
312 }
313 // if (!serverInfoList.isEmpty()) {
314 for (DhcpServerInfo oldServerInfo : serverInfoList) {
315 log.info("In for (DhcpServerInfo oldServerInfo : serverInfoList) {");
Yi Tseng919b2df2017-09-07 16:22:51 -0700316 // remove old server info
rsahot036620655b2018-02-26 15:01:37 -0500317 //DhcpServerInfo oldServerInfo = serverInfoList.remove(0);
Yi Tseng483ac6f2017-08-02 15:03:31 -0700318
Yi Tseng919b2df2017-09-07 16:22:51 -0700319 // stop monitoring gateway or server
320 oldServerInfo.getDhcpGatewayIp4().ifPresent(gatewayIp -> {
321 hostService.stopMonitoringIp(gatewayIp);
322 });
323 oldServerInfo.getDhcpServerIp4().ifPresent(serverIp -> {
324 hostService.stopMonitoringIp(serverIp);
Yi Tseng525ff402017-10-23 19:39:39 -0700325 cancelDhcpPacket(serverIp);
Yi Tseng919b2df2017-09-07 16:22:51 -0700326 });
Yi Tseng483ac6f2017-08-02 15:03:31 -0700327 }
Yi Tseng3df7f9d2017-08-17 13:08:31 -0700328
Yi Tseng919b2df2017-09-07 16:22:51 -0700329 // Create new server info according to the config
rsahot036620655b2018-02-26 15:01:37 -0500330 serverInfoList.clear();
331 for (DhcpServerConfig serverConfig : configs) {
Taras Lemkin96a0d342018-03-26 14:52:58 +0000332 log.debug("Create new server info according to the config");
rsahot036620655b2018-02-26 15:01:37 -0500333 DhcpServerInfo newServerInfo = new DhcpServerInfo(serverConfig,
334 DhcpServerInfo.Version.DHCP_V4);
335 checkState(newServerInfo.getDhcpServerConnectPoint().isPresent(),
336 "Connect point not exists");
337 checkState(newServerInfo.getDhcpServerIp4().isPresent(),
338 "IP of DHCP server not exists");
Yi Tseng4f2a0462017-08-31 11:21:00 -0700339
rsahot036620655b2018-02-26 15:01:37 -0500340 log.debug("DHCP server connect point: {}", newServerInfo.getDhcpServerConnectPoint().orElse(null));
341 log.debug("DHCP server IP: {}", newServerInfo.getDhcpServerIp4().orElse(null));
Yi Tseng919b2df2017-09-07 16:22:51 -0700342
rsahot036620655b2018-02-26 15:01:37 -0500343 Ip4Address serverIp = newServerInfo.getDhcpServerIp4().get();
344 Ip4Address ipToProbe;
345 if (newServerInfo.getDhcpGatewayIp4().isPresent()) {
346 ipToProbe = newServerInfo.getDhcpGatewayIp4().get();
347 } else {
348 ipToProbe = newServerInfo.getDhcpServerIp4().orElse(null);
349 }
350 log.info("Probe_IP {}", ipToProbe);
351 String hostToProbe = newServerInfo.getDhcpGatewayIp4()
352 .map(ip -> "gateway").orElse("server");
353
354 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 -> {
515 ConnectPoint dhcpServerConnectPoint =
516 serverInfo.getDhcpServerConnectPoint().orElse(null);
517 VlanId dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
518 if (dhcpServerConnectPoint == null || dhcpConnectVlan == null) {
519 return null;
520 }
521 return interfaceService.getInterfacesByPort(dhcpServerConnectPoint)
522 .stream()
523 .filter(iface -> interfaceContainsVlan(iface, dhcpConnectVlan))
524 .findFirst()
525 .orElse(null);
526 })
Yi Tseng4f2a0462017-08-31 11:21:00 -0700527 .orElse(null);
528 }
529
530 /**
531 * Determind if an Interface contains a vlan id.
532 *
533 * @param iface the Interface
534 * @param vlanId the vlan id
535 * @return true if the Interface contains the vlan id
536 */
537 private boolean interfaceContainsVlan(Interface iface, VlanId vlanId) {
Yi Tseng4025a102017-09-30 11:35:42 +0800538 if (vlanId.equals(VlanId.NONE)) {
539 // untagged packet, check if vlan untagged or vlan native is not NONE
540 return !iface.vlanUntagged().equals(VlanId.NONE) ||
541 !iface.vlanNative().equals(VlanId.NONE);
542 }
543 // tagged packet, check if the interface contains the vlan
544 return iface.vlanTagged().contains(vlanId);
Yi Tseng4f2a0462017-08-31 11:21:00 -0700545 }
546
Charles Chand0dd7002017-10-08 23:53:36 -0400547 private void handleLeaseQueryActivateMsg(Ethernet packet, DHCP dhcpPayload) {
548 log.debug("LQ: Got DHCPLEASEACTIVE packet!");
549
Taras Lemkin96a0d342018-03-26 14:52:58 +0000550 if (learnRouteFromLeasequery) {
551 // TODO: release the ip address from client
552 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
553 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
554 HostId hostId = HostId.hostId(clientMacAddress, vlanId);
555 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
Charles Chand0dd7002017-10-08 23:53:36 -0400556
Taras Lemkin96a0d342018-03-26 14:52:58 +0000557 if (record == null) {
558 log.warn("Can't find record for host {} when processing DHCPLEASEACTIVE", hostId);
559 return;
560 }
561
562 // need to update routes
563 log.debug("Lease Query for Client results in DHCPLEASEACTIVE - route needs to be modified");
564 // get current route
565 // find the ip of that client with the DhcpRelay store
566
567 Ip4Address clientIP = record.ip4Address().orElse(null);
568 log.debug("LQ: IP of host is " + clientIP.getIp4Address());
569
570 MacAddress nextHopMac = record.nextHop().orElse(null);
571 log.debug("LQ: MAC of resulting *OLD* NH for that host is " + nextHopMac.toString());
572
573 // find the new NH by looking at the src MAC of the dhcp request
574 // from the LQ store
575 MacAddress newNextHopMac = record.nextHopTemp().orElse(null);
576 log.debug("LQ: MAC of resulting *NEW* NH for that host is " + newNextHopMac.toString());
577
578 log.debug("LQ: updating dhcp relay record with new NH");
579 record.nextHop(newNextHopMac);
580
581 // find the next hop IP from its mac
582 HostId gwHostId = HostId.hostId(newNextHopMac, vlanId);
583 Host gwHost = hostService.getHost(gwHostId);
584
585 if (gwHost == null) {
586 log.warn("Can't find gateway for new NH host " + gwHostId);
587 return;
588 }
589
590 Ip4Address nextHopIp = gwHost.ipAddresses()
591 .stream()
592 .filter(IpAddress::isIp4)
593 .map(IpAddress::getIp4Address)
594 .findFirst()
595 .orElse(null);
596
597 if (nextHopIp == null) {
598 log.warn("Can't find IP address of gateway " + gwHost);
599 return;
600 }
601
602 log.debug("LQ: *NEW* NH IP for host is " + nextHopIp.getIp4Address());
603 Route route = new Route(Route.Source.DHCP, clientIP.toIpPrefix(), nextHopIp);
604 routeStore.updateRoute(route);
Charles Chand0dd7002017-10-08 23:53:36 -0400605 }
606
Charles Chand0dd7002017-10-08 23:53:36 -0400607 // and forward to client
Taras Lemkin96a0d342018-03-26 14:52:58 +0000608 InternalPacket ethernetPacket = processLeaseQueryFromServer(packet);
Charles Chand0dd7002017-10-08 23:53:36 -0400609 if (ethernetPacket != null) {
610 sendResponseToClient(ethernetPacket, dhcpPayload);
611 }
612 }
613
Charles Chand0dd7002017-10-08 23:53:36 -0400614 private void handleLeaseQueryMsg(PacketContext context, Ethernet packet, DHCP dhcpPayload) {
Taras Lemkin96a0d342018-03-26 14:52:58 +0000615 // If this flag is enabled we expect that DHCPLEASEQUERY-ies are sent from an access concentrator
616 // where queried client is connected to. Otherwise, DHCPLEASEQUERY source may be a separate connected agent
617 if (learnRouteFromLeasequery) {
618 log.debug("LQ: Got DHCPLEASEQUERY packet!");
619 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
620 log.debug("LQ: got DHCPLEASEQUERY with MAC " + clientMacAddress.toString());
621 // add the client mac (hostid) of this request to a store (the entry will be removed with
622 // the reply sent to the originator)
623 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
624 HostId hId = HostId.hostId(clientMacAddress, vlanId);
625 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hId).orElse(null);
626 if (record != null) {
627 //new NH is to be taken from src mac of LQ packet
628 MacAddress newNextHop = packet.getSourceMAC();
629 record.nextHopTemp(newNextHop);
630 record.ip4Status(dhcpPayload.getPacketType());
631 record.updateLastSeen();
632
633 // do a basic routing of the packet (this is unicast routing
634 // not a relay operation like for other broadcast dhcp packets
635 List<InternalPacket> ethernetPacketRequest = processLeaseQueryFromAgent(context, packet);
636 // and forward to server
637 for (InternalPacket internalPacket : ethernetPacketRequest) {
638 log.debug("LeaseQueryMsg forward to server");
639 forwardPacket(internalPacket);
640 }
641 } else {
642 log.warn("LQ: Error! - DHCP relay record for that client not found - ignoring LQ!");
643 }
644 } else {
645 log.debug("LQ: Got DHCPLEASEQUERY packet!");
646
647 int giaddr = dhcpPayload.getGatewayIPAddress();
648
649 log.debug("DHCPLEASEQUERY giaddr: {} ({}). Originators connectPoint: {}", giaddr,
650 Ip4Address.valueOf(giaddr), context.inPacket().receivedFrom());
Charles Chand0dd7002017-10-08 23:53:36 -0400651
652 // do a basic routing of the packet (this is unicast routing
653 // not a relay operation like for other broadcast dhcp packets
rsahot036620655b2018-02-26 15:01:37 -0500654 List<InternalPacket> ethernetPacketRequest = processLeaseQueryFromAgent(context, packet);
Charles Chand0dd7002017-10-08 23:53:36 -0400655 // and forward to server
rsahot036620655b2018-02-26 15:01:37 -0500656 for (InternalPacket internalPacket : ethernetPacketRequest) {
Taras Lemkin96a0d342018-03-26 14:52:58 +0000657 log.trace("LeaseQueryMsg forward to server connected to {}", internalPacket.getDestLocation());
rsahot036620655b2018-02-26 15:01:37 -0500658 forwardPacket(internalPacket);
659 }
Charles Chand0dd7002017-10-08 23:53:36 -0400660 }
661 }
662
663 private void handleLeaseQueryUnknown(Ethernet packet, DHCP dhcpPayload) {
664 log.debug("Lease Query for Client results in DHCPLEASEUNASSIGNED or " +
665 "DHCPLEASEUNKNOWN - removing route & forwarding reply to originator");
Taras Lemkin96a0d342018-03-26 14:52:58 +0000666 if (learnRouteFromLeasequery) {
667 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
668 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
669 HostId hostId = HostId.hostId(clientMacAddress, vlanId);
670 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
Charles Chand0dd7002017-10-08 23:53:36 -0400671
Taras Lemkin96a0d342018-03-26 14:52:58 +0000672 if (record == null) {
673 log.warn("Can't find record for host {} when handling LQ UNKNOWN/UNASSIGNED message", hostId);
674 return;
675 }
676
677 Ip4Address clientIP = record.ip4Address().orElse(null);
678 log.debug("LQ: IP of host is " + clientIP.getIp4Address());
679
680 // find the new NH by looking at the src MAC of the dhcp request
681 // from the LQ store
682 MacAddress nextHopMac = record.nextHop().orElse(null);
683 log.debug("LQ: MAC of resulting *Existing* NH for that route is " + nextHopMac.toString());
684
685 // find the next hop IP from its mac
686 HostId gwHostId = HostId.hostId(nextHopMac, vlanId);
687 Host gwHost = hostService.getHost(gwHostId);
688
689 if (gwHost == null) {
690 log.warn("Can't find gateway for new NH host " + gwHostId);
691 return;
692 }
693
694 Ip4Address nextHopIp = gwHost.ipAddresses()
695 .stream()
696 .filter(IpAddress::isIp4)
697 .map(IpAddress::getIp4Address)
698 .findFirst()
699 .orElse(null);
700
701 if (nextHopIp == null) {
702 log.warn("Can't find IP address of gateway {}", gwHost);
703 return;
704 }
705
706 log.debug("LQ: *Existing* NH IP for host is " + nextHopIp.getIp4Address() + " removing route for it");
707 Route route = new Route(Route.Source.DHCP, clientIP.toIpPrefix(), nextHopIp);
708 routeStore.removeRoute(route);
709
710 // remove from temp store
711 dhcpRelayStore.removeDhcpRecord(hostId);
Charles Chand0dd7002017-10-08 23:53:36 -0400712 }
Charles Chand0dd7002017-10-08 23:53:36 -0400713 // and forward to client
Taras Lemkin96a0d342018-03-26 14:52:58 +0000714 InternalPacket ethernetPacket = processLeaseQueryFromServer(packet);
Charles Chand0dd7002017-10-08 23:53:36 -0400715 if (ethernetPacket != null) {
716 sendResponseToClient(ethernetPacket, dhcpPayload);
717 }
718 }
719
Yi Tseng4f2a0462017-08-31 11:21:00 -0700720 /**
Yi Tseng51301292017-07-28 13:02:59 -0700721 * Build the DHCP discover/request packet with gateway IP(unicast packet).
722 *
723 * @param context the packet context
724 * @param ethernetPacket the ethernet payload to process
Yi Tseng51301292017-07-28 13:02:59 -0700725 * @return processed packet
726 */
rsahot036620655b2018-02-26 15:01:37 -0500727 private List<InternalPacket> processDhcpPacketFromClient(PacketContext context,
728 Ethernet ethernetPacket,
729 Set<Interface> clientInterfaces) {
Yi Tseng25bfe372017-11-03 16:27:32 -0700730 ConnectPoint receivedFrom = context.inPacket().receivedFrom();
731 DeviceId receivedFromDevice = receivedFrom.deviceId();
rsahot036620655b2018-02-26 15:01:37 -0500732 Ip4Address relayAgentIp = null;
Taras Lemkin96a0d342018-03-26 14:52:58 +0000733 relayAgentIp = Dhcp4HandlerUtil.getRelayAgentIPv4Address(clientInterfaces);
rsahot036620655b2018-02-26 15:01:37 -0500734 MacAddress relayAgentMac = clientInterfaces.iterator().next().mac();
735 if (relayAgentIp == null || relayAgentMac == null) {
736 log.warn("Missing DHCP relay agent interface Ipv4 addr config for "
737 + "packet from client on port: {}. Aborting packet processing",
738 clientInterfaces.iterator().next().connectPoint());
Charles Chane5e0c9a2018-03-30 12:11:34 -0700739 return Lists.newArrayList();
rsahot036620655b2018-02-26 15:01:37 -0500740 }
741 log.debug("Multi DHCP V4 processDhcpPacketFromClient on port {}",
742 clientInterfaces.iterator().next().connectPoint());
Yi Tseng25bfe372017-11-03 16:27:32 -0700743
Yi Tseng4f2a0462017-08-31 11:21:00 -0700744 // get dhcp header.
rsahot036620655b2018-02-26 15:01:37 -0500745 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
Yi Tseng4f2a0462017-08-31 11:21:00 -0700746 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
747 UDP udpPacket = (UDP) ipv4Packet.getPayload();
748 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
749
Yi Tseng919b2df2017-09-07 16:22:51 -0700750
Yi Tsengdcef2c22017-08-05 20:34:06 -0700751 Ip4Address clientInterfaceIp =
752 interfaceService.getInterfacesByPort(context.inPacket().receivedFrom())
753 .stream()
754 .map(Interface::ipAddressesList)
755 .flatMap(Collection::stream)
756 .map(InterfaceIpAddress::ipAddress)
757 .filter(IpAddress::isIp4)
758 .map(IpAddress::getIp4Address)
759 .findFirst()
760 .orElse(null);
761 if (clientInterfaceIp == null) {
762 log.warn("Can't find interface IP for client interface for port {}",
rsahot036620655b2018-02-26 15:01:37 -0500763 context.inPacket().receivedFrom());
Charles Chane5e0c9a2018-03-30 12:11:34 -0700764 return Lists.newArrayList();
Yi Tsengdcef2c22017-08-05 20:34:06 -0700765 }
rsahot036620655b2018-02-26 15:01:37 -0500766
Yi Tseng4f2a0462017-08-31 11:21:00 -0700767 boolean isDirectlyConnected = directlyConnected(dhcpPacket);
rsahot036620655b2018-02-26 15:01:37 -0500768 boolean directConnFlag = directlyConnected(dhcpPacket);
769
770 // Multi DHCP Start
771 ConnectPoint clientConnectionPoint = context.inPacket().receivedFrom();
772 VlanId vlanIdInUse = VlanId.vlanId(ethernetPacket.getVlanID());
773 Interface clientInterface = interfaceService.getInterfacesByPort(clientConnectionPoint)
Taras Lemkin96a0d342018-03-26 14:52:58 +0000774 .stream().filter(iface -> Dhcp4HandlerUtil.interfaceContainsVlan(iface, vlanIdInUse))
rsahot036620655b2018-02-26 15:01:37 -0500775 .findFirst()
776 .orElse(null);
777
778 List<InternalPacket> internalPackets = new ArrayList<>();
779 List<DhcpServerInfo> serverInfoList = findValidServerInfo(directConnFlag);
780 List<DhcpServerInfo> copyServerInfoList = new ArrayList<DhcpServerInfo>(serverInfoList);
781
782
783 for (DhcpServerInfo serverInfo : copyServerInfoList) {
784 etherReply = (Ethernet) ethernetPacket.clone();
Taras Lemkin96a0d342018-03-26 14:52:58 +0000785 ipv4Packet = (IPv4) etherReply.getPayload();
786 udpPacket = (UDP) ipv4Packet.getPayload();
787 dhcpPacket = (DHCP) udpPacket.getPayload();
rsahot036620655b2018-02-26 15:01:37 -0500788 if (!checkDhcpServerConnPt(directConnFlag, serverInfo)) {
789 log.warn("Can't get server connect point, ignore");
790 continue;
791 }
792 DhcpServerInfo newServerInfo = getHostInfoForServerInfo(serverInfo, serverInfoList);
793 if (newServerInfo == null) {
794 log.warn("Can't get server interface with host info resolved, ignore");
795 continue;
796 }
797
798 Interface serverInterface = getServerInterface(newServerInfo);
Yi Tseng3bd57ac2017-11-29 14:39:18 -0800799 if (serverInterface == null) {
rsahot036620655b2018-02-26 15:01:37 -0500800 log.warn("Can't get server interface, ignore");
801 continue;
Yi Tseng3bd57ac2017-11-29 14:39:18 -0800802 }
Yi Tseng4f2a0462017-08-31 11:21:00 -0700803
rsahot036620655b2018-02-26 15:01:37 -0500804 Ip4Address ipFacingServer = getFirstIpFromInterface(serverInterface);
805 MacAddress macFacingServer = serverInterface.mac();
806 log.debug("Interfacing server {} Mac : {} ", ipFacingServer, macFacingServer);
807 if (ipFacingServer == null || macFacingServer == null) {
808 log.warn("No IP address for server Interface {}", serverInterface);
rsahot036620655b2018-02-26 15:01:37 -0500809 continue;
810 }
Yi Tseng51301292017-07-28 13:02:59 -0700811
Charles Chand0d1e332017-10-10 16:53:32 -0400812
rsahot036620655b2018-02-26 15:01:37 -0500813 etherReply.setSourceMACAddress(macFacingServer);
814 // set default info and replace with indirect if available later on.
815 if (newServerInfo.getDhcpConnectMac().isPresent()) {
816 etherReply.setDestinationMACAddress(newServerInfo.getDhcpConnectMac().get());
817 }
818 if (newServerInfo.getDhcpConnectVlan().isPresent()) {
819 etherReply.setVlanID(newServerInfo.getDhcpConnectVlan().get().toShort());
820 }
821 ipv4Packet.setSourceAddress(ipFacingServer.toInt());
822 ipv4Packet.setDestinationAddress(newServerInfo.getDhcpServerIp4().get().toInt());
Charles Chan2de55302018-03-02 18:27:59 -0800823 log.debug("Directly connected {}", isDirectlyConnected);
824 log.debug("DHCP server IP: {}", newServerInfo.getDhcpServerIp4().get());
rsahot036620655b2018-02-26 15:01:37 -0500825 if (isDirectlyConnected) {
Yi Tseng51301292017-07-28 13:02:59 -0700826
Charles Chan2de55302018-03-02 18:27:59 -0800827 log.debug("Default DHCP server IP: {}", newServerInfo.getDhcpServerIp4().get());
rsahot036620655b2018-02-26 15:01:37 -0500828 if (newServerInfo.getDhcpConnectMac().isPresent()) {
829 etherReply.setDestinationMACAddress(newServerInfo.getDhcpConnectMac().get());
Charles Chand0d1e332017-10-10 16:53:32 -0400830 }
rsahot036620655b2018-02-26 15:01:37 -0500831 if (newServerInfo.getDhcpConnectVlan().isPresent()) {
832 etherReply.setVlanID(newServerInfo.getDhcpConnectVlan().get().toShort());
833 }
834
835 ipv4Packet.setDestinationAddress(newServerInfo.getDhcpServerIp4().get().toInt());
836
837
838 ConnectPoint inPort = context.inPacket().receivedFrom();
839 VlanId vlanId = VlanId.vlanId(ethernetPacket.getVlanID());
840 // add connected in port and vlan
841 CircuitId cid = new CircuitId(inPort.toString(), vlanId);
842 byte[] circuitId = cid.serialize();
843 DhcpOption circuitIdSubOpt = new DhcpOption();
844 circuitIdSubOpt
845 .setCode(CIRCUIT_ID.getValue())
846 .setLength((byte) circuitId.length)
847 .setData(circuitId);
848
849 DhcpRelayAgentOption newRelayAgentOpt = new DhcpRelayAgentOption();
850 newRelayAgentOpt.setCode(OptionCode_CircuitID.getValue());
851 newRelayAgentOpt.addSubOption(circuitIdSubOpt);
852
853 // Removes END option first
854 List<DhcpOption> options = dhcpPacket.getOptions().stream()
855 .filter(opt -> opt.getCode() != OptionCode_END.getValue())
856 .collect(Collectors.toList());
857
858 // push relay agent option
859 options.add(newRelayAgentOpt);
860
861 // make sure option 255(End) is the last option
862 DhcpOption endOption = new DhcpOption();
863 endOption.setCode(OptionCode_END.getValue());
864 options.add(endOption);
865
866 dhcpPacket.setOptions(options);
867
868 relayAgentIp = serverInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
869
870 // Sets relay agent IP
871 int effectiveRelayAgentIp = relayAgentIp != null ?
872 relayAgentIp.toInt() : clientInterfaceIp.toInt();
873 dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
Charles Chan2de55302018-03-02 18:27:59 -0800874 log.debug("In Default, Relay Agent IP {}", effectiveRelayAgentIp);
Charles Chand0d1e332017-10-10 16:53:32 -0400875 } else {
rsahot036620655b2018-02-26 15:01:37 -0500876 if (!newServerInfo.getDhcpServerIp4().isPresent()) {
877 // do nothing
878 } else if (!newServerInfo.getDhcpConnectMac().isPresent()) {
879 continue;
880 } else {
881 relayAgentIp = newServerInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
882 // Sets relay agent IP
883 int effectiveRelayAgentIp = relayAgentIp != null ?
884 relayAgentIp.toInt() : clientInterfaceIp.toInt();
885 dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
Charles Chand0d1e332017-10-10 16:53:32 -0400886 }
Yi Tseng4f2a0462017-08-31 11:21:00 -0700887 }
Yi Tseng3df7f9d2017-08-17 13:08:31 -0700888
rsahot036620655b2018-02-26 15:01:37 -0500889 // Remove broadcast flag
890 dhcpPacket.setFlags((short) 0);
891
892 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 }
905 return internalPackets;
Yi Tseng51301292017-07-28 13:02:59 -0700906 }
907
Charles Chand0dd7002017-10-08 23:53:36 -0400908
909 /**
910 * Do a basic routing for a packet from client (used for LQ processing).
911 *
912 * @param context the packet context
913 * @param ethernetPacket the ethernet payload to process
914 * @return processed packet
915 */
rsahot036620655b2018-02-26 15:01:37 -0500916 private List<InternalPacket> processLeaseQueryFromAgent(PacketContext context,
917 Ethernet ethernetPacket) {
918 ConnectPoint receivedFrom = context.inPacket().receivedFrom();
919 DeviceId receivedFromDevice = receivedFrom.deviceId();
920
Charles Chand0dd7002017-10-08 23:53:36 -0400921 // get dhcp header.
922 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
923 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
924 UDP udpPacket = (UDP) ipv4Packet.getPayload();
925 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
926
Charles Chan2de55302018-03-02 18:27:59 -0800927 Ip4Address relayAgentIp;
Charles Chand0dd7002017-10-08 23:53:36 -0400928
Charles Chand0dd7002017-10-08 23:53:36 -0400929 Ip4Address clientInterfaceIp =
930 interfaceService.getInterfacesByPort(context.inPacket().receivedFrom())
931 .stream()
932 .map(Interface::ipAddressesList)
933 .flatMap(Collection::stream)
934 .map(InterfaceIpAddress::ipAddress)
935 .filter(IpAddress::isIp4)
936 .map(IpAddress::getIp4Address)
937 .findFirst()
938 .orElse(null);
939 if (clientInterfaceIp == null) {
940 log.warn("Can't find interface IP for client interface for port {}",
rsahot036620655b2018-02-26 15:01:37 -0500941 context.inPacket().receivedFrom());
Charles Chand0dd7002017-10-08 23:53:36 -0400942 return null;
943 }
rsahot036620655b2018-02-26 15:01:37 -0500944
Charles Chand0dd7002017-10-08 23:53:36 -0400945 boolean isDirectlyConnected = directlyConnected(dhcpPacket);
rsahot036620655b2018-02-26 15:01:37 -0500946 boolean directConnFlag = directlyConnected(dhcpPacket);
947
948 // Multi DHCP Start
rsahot036620655b2018-02-26 15:01:37 -0500949 List<InternalPacket> internalPackets = new ArrayList<>();
950 List<DhcpServerInfo> serverInfoList = findValidServerInfo(directConnFlag);
Charles Chan2de55302018-03-02 18:27:59 -0800951 List<DhcpServerInfo> copyServerInfoList = new ArrayList<>(serverInfoList);
rsahot036620655b2018-02-26 15:01:37 -0500952
953 for (DhcpServerInfo serverInfo : copyServerInfoList) {
954 // get dhcp header.
955 etherReply = (Ethernet) ethernetPacket.clone();
956 ipv4Packet = (IPv4) etherReply.getPayload();
957 udpPacket = (UDP) ipv4Packet.getPayload();
958 dhcpPacket = (DHCP) udpPacket.getPayload();
959
960 if (!checkDhcpServerConnPt(directConnFlag, serverInfo)) {
961 log.warn("Can't get server connect point, ignore");
962 continue;
Yi Tseng3bd57ac2017-11-29 14:39:18 -0800963 }
rsahot036620655b2018-02-26 15:01:37 -0500964 DhcpServerInfo newServerInfo = getHostInfoForServerInfo(serverInfo, serverInfoList);
965 if (newServerInfo == null) {
966 log.warn("Can't get server interface with host info resolved, ignore");
967 continue;
968 }
Charles Chand0dd7002017-10-08 23:53:36 -0400969
rsahot036620655b2018-02-26 15:01:37 -0500970 Interface serverInterface = getServerInterface(newServerInfo);
971 if (serverInterface == null) {
972 log.warn("Can't get server interface, ignore");
973 continue;
974 }
975 Ip4Address ipFacingServer = getFirstIpFromInterface(serverInterface);
976 MacAddress macFacingServer = serverInterface.mac();
977 if (ipFacingServer == null || macFacingServer == null) {
978 log.warn("No IP address for server Interface {}", serverInterface);
979 continue;
980 }
Charles Chand0dd7002017-10-08 23:53:36 -0400981
rsahot036620655b2018-02-26 15:01:37 -0500982 etherReply.setSourceMACAddress(macFacingServer);
Charles Chan2de55302018-03-02 18:27:59 -0800983 etherReply.setDestinationMACAddress(newServerInfo.getDhcpConnectMac().get());
984 etherReply.setVlanID(newServerInfo.getDhcpConnectVlan().get().toShort());
rsahot036620655b2018-02-26 15:01:37 -0500985 ipv4Packet.setSourceAddress(ipFacingServer.toInt());
Charles Chan2de55302018-03-02 18:27:59 -0800986 ipv4Packet.setDestinationAddress(newServerInfo.getDhcpServerIp4().get().toInt());
rsahot036620655b2018-02-26 15:01:37 -0500987 if (isDirectlyConnected) {
988 // set default info and replace with indirect if available later on.
989 if (newServerInfo.getDhcpConnectMac().isPresent()) {
990 etherReply.setDestinationMACAddress(newServerInfo.getDhcpConnectMac().get());
991 }
992 if (newServerInfo.getDhcpConnectVlan().isPresent()) {
993 etherReply.setVlanID(serverInfo.getDhcpConnectVlan().get().toShort());
994 }
Taras Lemkin96a0d342018-03-26 14:52:58 +0000995 if (learnRouteFromLeasequery) {
rsahot036620655b2018-02-26 15:01:37 -0500996 relayAgentIp = newServerInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
997 // Sets relay agent IP
998 int effectiveRelayAgentIp = relayAgentIp != null ?
999 relayAgentIp.toInt() : clientInterfaceIp.toInt();
1000 dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
Taras Lemkin96a0d342018-03-26 14:52:58 +00001001 }
1002 } else {
1003 if (!newServerInfo.getDhcpServerIp4().isPresent()) {
1004 //do nothing
1005 } else if (!newServerInfo.getDhcpConnectMac().isPresent()) {
1006 continue;
1007 } else if (learnRouteFromLeasequery) {
1008 relayAgentIp = newServerInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
1009 // Sets relay agent IP
1010 int effectiveRelayAgentIp = relayAgentIp != null ?
1011 relayAgentIp.toInt() : clientInterfaceIp.toInt();
rsahot036620655b2018-02-26 15:01:37 -05001012 dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
Taras Lemkin96a0d342018-03-26 14:52:58 +00001013 log.debug("Relay Agent IP {}", relayAgentIp);
rsahot036620655b2018-02-26 15:01:37 -05001014 }
1015
Taras Lemkin96a0d342018-03-26 14:52:58 +00001016 log.trace("Indirect");
rsahot036620655b2018-02-26 15:01:37 -05001017 }
1018
1019 // Remove broadcast flag
1020 dhcpPacket.setFlags((short) 0);
1021
1022 udpPacket.setPayload(dhcpPacket);
1023 // As a DHCP relay, the source port should be server port( instead
1024 // of client port.
1025 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
1026 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
1027 ipv4Packet.setPayload(udpPacket);
1028 ipv4Packet.setTtl((byte) 64);
1029 etherReply.setPayload(ipv4Packet);
Taras Lemkin96a0d342018-03-26 14:52:58 +00001030 udpPacket.resetChecksum();
rsahot036620655b2018-02-26 15:01:37 -05001031 ////return etherReply;
Taras Lemkin96a0d342018-03-26 14:52:58 +00001032 InternalPacket internalPacket = InternalPacket.internalPacket(etherReply,
rsahot036620655b2018-02-26 15:01:37 -05001033 newServerInfo.getDhcpServerConnectPoint().get());
Taras Lemkin96a0d342018-03-26 14:52:58 +00001034
rsahot036620655b2018-02-26 15:01:37 -05001035 internalPackets.add(internalPacket);
Charles Chand0dd7002017-10-08 23:53:36 -04001036 }
Taras Lemkin96a0d342018-03-26 14:52:58 +00001037 log.debug("num of processLeaseQueryFromAgent packets to send is: {}", internalPackets.size());
Charles Chand0dd7002017-10-08 23:53:36 -04001038
rsahot036620655b2018-02-26 15:01:37 -05001039 return internalPackets;
Charles Chand0dd7002017-10-08 23:53:36 -04001040 }
1041
1042
Yi Tseng51301292017-07-28 13:02:59 -07001043 /**
1044 * Writes DHCP record to the store according to the request DHCP packet (Discover, Request).
1045 *
1046 * @param location the location which DHCP packet comes from
1047 * @param ethernet the DHCP packet
1048 * @param dhcpPayload the DHCP payload
1049 */
1050 private void writeRequestDhcpRecord(ConnectPoint location,
1051 Ethernet ethernet,
1052 DHCP dhcpPayload) {
1053 VlanId vlanId = VlanId.vlanId(ethernet.getVlanID());
1054 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1055 HostId hostId = HostId.hostId(macAddress, vlanId);
1056 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1057 if (record == null) {
1058 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
1059 } else {
1060 record = record.clone();
1061 }
1062 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
1063 record.ip4Status(dhcpPayload.getPacketType());
1064 record.setDirectlyConnected(directlyConnected(dhcpPayload));
1065 if (!directlyConnected(dhcpPayload)) {
1066 // Update gateway mac address if the host is not directly connected
1067 record.nextHop(ethernet.getSourceMAC());
1068 }
1069 record.updateLastSeen();
1070 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
1071 }
1072
1073 /**
1074 * Writes DHCP record to the store according to the response DHCP packet (Offer, Ack).
1075 *
1076 * @param ethernet the DHCP packet
1077 * @param dhcpPayload the DHCP payload
1078 */
1079 private void writeResponseDhcpRecord(Ethernet ethernet,
1080 DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001081 Optional<Interface> outInterface = getClientInterface(ethernet, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -07001082 if (!outInterface.isPresent()) {
1083 log.warn("Failed to determine where to send {}", dhcpPayload.getPacketType());
1084 return;
1085 }
1086
1087 Interface outIface = outInterface.get();
1088 ConnectPoint location = outIface.connectPoint();
Yi Tseng4f2a0462017-08-31 11:21:00 -07001089 VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001090 if (vlanId == null) {
1091 vlanId = outIface.vlan();
1092 }
Yi Tseng51301292017-07-28 13:02:59 -07001093 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1094 HostId hostId = HostId.hostId(macAddress, vlanId);
1095 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1096 if (record == null) {
1097 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
1098 } else {
1099 record = record.clone();
1100 }
1101 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
1102 if (dhcpPayload.getPacketType() == DHCP.MsgType.DHCPACK) {
1103 record.ip4Address(Ip4Address.valueOf(dhcpPayload.getYourIPAddress()));
1104 }
1105 record.ip4Status(dhcpPayload.getPacketType());
1106 record.setDirectlyConnected(directlyConnected(dhcpPayload));
1107 record.updateLastSeen();
1108 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
1109 }
1110
1111 /**
1112 * Build the DHCP offer/ack with proper client port.
1113 *
1114 * @param ethernetPacket the original packet comes from server
1115 * @return new packet which will send to the client
1116 */
Taras Lemkin96a0d342018-03-26 14:52:58 +00001117 private InternalPacket processDhcpPacketFromServer(PacketContext context, Ethernet ethernetPacket) {
Yi Tseng51301292017-07-28 13:02:59 -07001118 // get dhcp header.
rsahot036620655b2018-02-26 15:01:37 -05001119 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
Yi Tseng51301292017-07-28 13:02:59 -07001120 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
1121 UDP udpPacket = (UDP) ipv4Packet.getPayload();
1122 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
1123
1124 // determine the vlanId of the client host - note that this vlan id
1125 // could be different from the vlan in the packet from the server
Yi Tsengdcef2c22017-08-05 20:34:06 -07001126 Interface clientInterface = getClientInterface(ethernetPacket, dhcpPayload).orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -07001127
Yi Tsengdcef2c22017-08-05 20:34:06 -07001128 if (clientInterface == null) {
Yi Tseng51301292017-07-28 13:02:59 -07001129 log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
1130 return null;
1131 }
Yi Tsengdcef2c22017-08-05 20:34:06 -07001132 VlanId vlanId;
rsahot036620655b2018-02-26 15:01:37 -05001133 ConnectPoint inPort = context.inPacket().receivedFrom();
1134 boolean directConnFlag = directlyConnected(dhcpPayload);
1135 DhcpServerInfo foundServerInfo = findServerInfoFromServer(directConnFlag, inPort);
1136
1137 if (foundServerInfo == null) {
1138 log.warn("Cannot find server info");
1139 return null;
1140 } else {
Taras Lemkin96a0d342018-03-26 14:52:58 +00001141 if (Dhcp4HandlerUtil.isServerIpEmpty(foundServerInfo)) {
rsahot036620655b2018-02-26 15:01:37 -05001142 log.warn("Cannot find server info's ipaddress");
1143 return null;
1144 }
1145 }
Yi Tsengdcef2c22017-08-05 20:34:06 -07001146 if (clientInterface.vlanTagged().isEmpty()) {
1147 vlanId = clientInterface.vlan();
1148 } else {
1149 // might be multiple vlan in same interface
Yi Tseng4f2a0462017-08-31 11:21:00 -07001150 vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001151 }
1152 if (vlanId == null) {
1153 vlanId = VlanId.NONE;
1154 }
1155 etherReply.setVlanID(vlanId.toShort());
1156 etherReply.setSourceMACAddress(clientInterface.mac());
Yi Tseng51301292017-07-28 13:02:59 -07001157
Yi Tsengdcef2c22017-08-05 20:34:06 -07001158 if (!directlyConnected(dhcpPayload)) {
1159 // if client is indirectly connected, try use next hop mac address
1160 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1161 HostId hostId = HostId.hostId(macAddress, vlanId);
Daniel Ginsburgb1dec912018-01-29 11:40:22 -08001162 if (((int) dhcpPayload.getFlags() & 0x8000) == 0x0000) {
1163 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1164 if (record != null) {
1165 // if next hop can be found, use mac address of next hop
1166 record.nextHop().ifPresent(etherReply::setDestinationMACAddress);
1167 } else {
1168 // otherwise, discard the packet
1169 log.warn("Can't find record for host id {}, discard packet", hostId);
1170 return null;
1171 }
Yi Tsengdcef2c22017-08-05 20:34:06 -07001172 } else {
Daniel Ginsburgb1dec912018-01-29 11:40:22 -08001173 etherReply.setDestinationMACAddress(MacAddress.BROADCAST);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001174 }
Yi Tseng1696f562017-08-17 17:43:38 -07001175 } else {
1176 etherReply.setDestinationMACAddress(dhcpPayload.getClientHardwareAddress());
Yi Tsengdcef2c22017-08-05 20:34:06 -07001177 }
1178
Yi Tseng51301292017-07-28 13:02:59 -07001179 // we leave the srcMac from the original packet
Yi Tseng51301292017-07-28 13:02:59 -07001180 // figure out the relay agent IP corresponding to the original request
Yi Tseng3df7f9d2017-08-17 13:08:31 -07001181 Ip4Address ipFacingClient = getFirstIpFromInterface(clientInterface);
1182 if (ipFacingClient == null) {
Yi Tseng51301292017-07-28 13:02:59 -07001183 log.warn("Cannot determine relay agent interface Ipv4 addr for host {}/{}. "
1184 + "Aborting relay for dhcp packet from server {}",
Yi Tsengdcef2c22017-08-05 20:34:06 -07001185 etherReply.getDestinationMAC(), clientInterface.vlan(),
Yi Tseng51301292017-07-28 13:02:59 -07001186 ethernetPacket);
1187 return null;
1188 }
1189 // SRC_IP: relay agent IP
1190 // DST_IP: offered IP
Yi Tseng3df7f9d2017-08-17 13:08:31 -07001191 ipv4Packet.setSourceAddress(ipFacingClient.toInt());
Daniel Ginsburgb1dec912018-01-29 11:40:22 -08001192 if (((int) dhcpPayload.getFlags() & 0x8000) == 0x0000) {
1193 ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
1194 } else {
1195 ipv4Packet.setDestinationAddress(BROADCAST_IP);
1196 }
Yi Tseng51301292017-07-28 13:02:59 -07001197 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
1198 if (directlyConnected(dhcpPayload)) {
1199 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
1200 } else {
1201 // forward to another dhcp relay
Yi Tseng93ba53c2017-09-14 13:24:21 -07001202 // FIXME: Currently we assume the DHCP comes from a L2 relay with
1203 // Option 82, this might not work if DHCP message comes from
1204 // L3 relay.
1205 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
Yi Tseng51301292017-07-28 13:02:59 -07001206 }
1207
1208 udpPacket.setPayload(dhcpPayload);
1209 ipv4Packet.setPayload(udpPacket);
1210 etherReply.setPayload(ipv4Packet);
Taras Lemkin96a0d342018-03-26 14:52:58 +00001211 return InternalPacket.internalPacket(etherReply, clientInterface.connectPoint());
Yi Tseng51301292017-07-28 13:02:59 -07001212 }
1213
Yi Tsengdcef2c22017-08-05 20:34:06 -07001214 /**
Charles Chand0dd7002017-10-08 23:53:36 -04001215 * Build the DHCP offer/ack with proper client port.
1216 *
1217 * @param ethernetPacket the original packet comes from server
1218 * @return new packet which will send to the client
1219 */
Taras Lemkin96a0d342018-03-26 14:52:58 +00001220 private InternalPacket processLeaseQueryFromServer(Ethernet ethernetPacket) {
Charles Chand0dd7002017-10-08 23:53:36 -04001221 // get dhcp header.
1222 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
1223 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
1224 UDP udpPacket = (UDP) ipv4Packet.getPayload();
1225 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
1226
1227 // determine the vlanId of the client host - note that this vlan id
1228 // could be different from the vlan in the packet from the server
Taras Lemkin96a0d342018-03-26 14:52:58 +00001229 Interface clientInterface = null;
1230 MacAddress destinationMac = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1231
1232 if (!learnRouteFromLeasequery) {
1233 int giaddr = ipv4Packet.getDestinationAddress();
1234 IpAddress destinationAddress = Ip4Address.valueOf(giaddr);
1235 log.debug("DHCPLEASEQUERYRESP giaddr: {}({})", giaddr, destinationAddress);
1236
1237 Host destinationHost = hostService.getHostsByIp(destinationAddress).stream().findFirst().orElse(null);
1238 if (destinationHost != null) {
1239 destinationMac = destinationHost.mac();
1240 log.trace("DHCPLEASEQUERYRESP destination mac is: {}", destinationMac);
1241 ConnectPoint destinationLocation = destinationHost.location();
1242 log.trace("Lookup for client interface by destination location {}", destinationLocation);
1243 clientInterface = interfaceService.getInterfacesByPort(destinationLocation)
1244 .stream()
1245 .filter(iface -> interfaceContainsVlan(iface, VlanId.vlanId(etherReply.getVlanID())))
1246 .findFirst()
1247 .orElse(null);
1248 log.trace("Found Host {} by ip {}", destinationHost, destinationAddress);
1249 log.debug("DHCPLEASEQUERYRESP Client interface: {}",
1250 (clientInterface != null ? clientInterface : "not resolved"));
1251
1252 }
1253 } else {
1254 clientInterface = getClientInterface(ethernetPacket, dhcpPayload).orElse(null);
1255 }
Charles Chand0dd7002017-10-08 23:53:36 -04001256
1257 if (clientInterface == null) {
1258 log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
1259 return null;
1260 }
1261 VlanId vlanId;
1262 if (clientInterface.vlanTagged().isEmpty()) {
1263 vlanId = clientInterface.vlan();
1264 } else {
1265 // might be multiple vlan in same interface
1266 vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
1267 }
1268 if (vlanId == null) {
1269 vlanId = VlanId.NONE;
1270 }
1271 etherReply.setVlanID(vlanId.toShort());
1272 etherReply.setSourceMACAddress(clientInterface.mac());
1273
Taras Lemkin96a0d342018-03-26 14:52:58 +00001274 if (!directlyConnected(dhcpPayload) && learnRouteFromLeasequery) {
Charles Chand0dd7002017-10-08 23:53:36 -04001275 // if client is indirectly connected, try use next hop mac address
1276 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1277 HostId hostId = HostId.hostId(macAddress, vlanId);
1278 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1279 if (record != null) {
1280 // if next hop can be found, use mac address of next hop
Taras Lemkin96a0d342018-03-26 14:52:58 +00001281 Optional<MacAddress> nextHop = record.nextHopTemp();
1282 if (!nextHop.isPresent()) {
1283 nextHop = record.nextHop();
1284 }
1285 nextHop.ifPresent(etherReply::setDestinationMACAddress);
Charles Chand0dd7002017-10-08 23:53:36 -04001286 } else {
1287 // otherwise, discard the packet
1288 log.warn("Can't find record for host id {}, discard packet", hostId);
1289 return null;
1290 }
1291 } else {
Taras Lemkin96a0d342018-03-26 14:52:58 +00001292 etherReply.setDestinationMACAddress(destinationMac);
Charles Chand0dd7002017-10-08 23:53:36 -04001293 }
1294
Charles Chand0dd7002017-10-08 23:53:36 -04001295 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
Taras Lemkin96a0d342018-03-26 14:52:58 +00001296 if (directlyConnected(dhcpPayload)) {
1297 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
1298 } else {
1299 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
1300 }
Charles Chand0dd7002017-10-08 23:53:36 -04001301
1302 udpPacket.setPayload(dhcpPayload);
1303 ipv4Packet.setPayload(udpPacket);
1304 etherReply.setPayload(ipv4Packet);
Taras Lemkin96a0d342018-03-26 14:52:58 +00001305 udpPacket.resetChecksum();
1306
1307 return InternalPacket.internalPacket(etherReply, clientInterface.connectPoint());
Charles Chand0dd7002017-10-08 23:53:36 -04001308 }
1309 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -07001310 * Extracts VLAN ID from relay agent option.
1311 *
1312 * @param dhcpPayload the DHCP payload
1313 * @return VLAN ID from DHCP payload; null if not exists
1314 */
Yi Tseng4f2a0462017-08-31 11:21:00 -07001315 private VlanId getVlanIdFromRelayAgentOption(DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001316 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
1317 if (option == null) {
1318 return null;
1319 }
1320 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
1321 if (circuitIdSubOption == null) {
1322 return null;
1323 }
1324 try {
1325 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
1326 return circuitId.vlanId();
1327 } catch (IllegalArgumentException e) {
1328 // can't deserialize the circuit ID
1329 return null;
1330 }
1331 }
1332
1333 /**
1334 * Removes DHCP relay agent information option (option 82) from DHCP payload.
1335 * Also reset giaddr to 0
1336 *
1337 * @param ethPacket the Ethernet packet to be processed
1338 * @return Ethernet packet processed
1339 */
1340 private Ethernet removeRelayAgentOption(Ethernet ethPacket) {
rsahot036620655b2018-02-26 15:01:37 -05001341 Ethernet ethernet = (Ethernet) ethPacket.duplicate();
Yi Tsengdcef2c22017-08-05 20:34:06 -07001342 IPv4 ipv4 = (IPv4) ethernet.getPayload();
1343 UDP udp = (UDP) ipv4.getPayload();
1344 DHCP dhcpPayload = (DHCP) udp.getPayload();
1345
1346 // removes relay agent information option
1347 List<DhcpOption> options = dhcpPayload.getOptions();
1348 options = options.stream()
1349 .filter(option -> option.getCode() != OptionCode_CircuitID.getValue())
1350 .collect(Collectors.toList());
1351 dhcpPayload.setOptions(options);
1352 dhcpPayload.setGatewayIPAddress(0);
1353
1354 udp.setPayload(dhcpPayload);
1355 ipv4.setPayload(udp);
1356 ethernet.setPayload(ipv4);
1357 return ethernet;
1358 }
1359
Taras Lemkin96a0d342018-03-26 14:52:58 +00001360 private boolean isDhcpPacketLeasequery(DHCP dhcpPacket) {
1361 switch (dhcpPacket.getPacketType()) {
1362 case DHCPLEASEACTIVE:
1363 case DHCPLEASEQUERY:
1364 case DHCPLEASEUNASSIGNED:
1365 case DHCPLEASEUNKNOWN:
1366 return true;
1367 default:
1368 return false;
1369 }
1370 }
Yi Tseng51301292017-07-28 13:02:59 -07001371
1372 /**
1373 * Check if the host is directly connected to the network or not.
1374 *
1375 * @param dhcpPayload the dhcp payload
1376 * @return true if the host is directly connected to the network; false otherwise
1377 */
1378 private boolean directlyConnected(DHCP dhcpPayload) {
Taras Lemkin96a0d342018-03-26 14:52:58 +00001379 // leasequery is always indirect
1380 if (isDhcpPacketLeasequery(dhcpPayload)) {
1381 return false;
1382 }
1383
Yi Tseng440e2b72017-08-24 14:47:34 -07001384 DhcpRelayAgentOption relayAgentOption =
1385 (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
Yi Tseng51301292017-07-28 13:02:59 -07001386
1387 // Doesn't contains relay option
1388 if (relayAgentOption == null) {
1389 return true;
1390 }
1391
Yi Tseng440e2b72017-08-24 14:47:34 -07001392 // check circuit id, if circuit id is invalid, we say it is an indirect host
1393 DhcpOption circuitIdOpt = relayAgentOption.getSubOption(CIRCUIT_ID.getValue());
Yi Tseng51301292017-07-28 13:02:59 -07001394
Yi Tseng440e2b72017-08-24 14:47:34 -07001395 try {
1396 CircuitId.deserialize(circuitIdOpt.getData());
Yi Tseng51301292017-07-28 13:02:59 -07001397 return true;
Yi Tseng440e2b72017-08-24 14:47:34 -07001398 } catch (Exception e) {
1399 // invalid circuit id
1400 return false;
Yi Tseng51301292017-07-28 13:02:59 -07001401 }
Yi Tseng51301292017-07-28 13:02:59 -07001402 }
1403
1404
1405 /**
1406 * Send the DHCP ack to the requester host.
1407 * Modify Host or Route store according to the type of DHCP.
1408 *
1409 * @param ethernetPacketAck the packet
1410 * @param dhcpPayload the DHCP data
1411 */
1412 private void handleDhcpAck(Ethernet ethernetPacketAck, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001413 Optional<Interface> outInterface = getClientInterface(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -07001414 if (!outInterface.isPresent()) {
1415 log.warn("Can't find output interface for dhcp: {}", dhcpPayload);
1416 return;
1417 }
1418
1419 Interface outIface = outInterface.get();
1420 HostLocation hostLocation = new HostLocation(outIface.connectPoint(), System.currentTimeMillis());
1421 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
Yi Tseng4f2a0462017-08-31 11:21:00 -07001422 VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001423 if (vlanId == null) {
1424 vlanId = outIface.vlan();
1425 }
Yi Tseng51301292017-07-28 13:02:59 -07001426 HostId hostId = HostId.hostId(macAddress, vlanId);
1427 Ip4Address ip = Ip4Address.valueOf(dhcpPayload.getYourIPAddress());
1428
1429 if (directlyConnected(dhcpPayload)) {
1430 // Add to host store if it connect to network directly
1431 Set<IpAddress> ips = Sets.newHashSet(ip);
Yi Tsengaa417a62017-09-08 17:22:51 -07001432 Host host = hostService.getHost(hostId);
Yi Tseng51301292017-07-28 13:02:59 -07001433
Yi Tsengaa417a62017-09-08 17:22:51 -07001434 Set<HostLocation> hostLocations = Sets.newHashSet(hostLocation);
1435 if (host != null) {
1436 // Dual homing support:
1437 // if host exists, use old locations and new location
1438 hostLocations.addAll(host.locations());
1439 }
1440 HostDescription desc = new DefaultHostDescription(macAddress, vlanId,
1441 hostLocations, ips, false);
1442 // Add IP address when dhcp server give the host new ip address
1443 providerService.hostDetected(hostId, desc, false);
Yi Tseng51301292017-07-28 13:02:59 -07001444 } else {
1445 // Add to route store if it does not connect to network directly
1446 // Get gateway host IP according to host mac address
Yi Tsengdcef2c22017-08-05 20:34:06 -07001447 // TODO: remove relay store here
Yi Tseng51301292017-07-28 13:02:59 -07001448 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1449
1450 if (record == null) {
1451 log.warn("Can't find DHCP record of host {}", hostId);
1452 return;
1453 }
1454
1455 MacAddress gwMac = record.nextHop().orElse(null);
1456 if (gwMac == null) {
1457 log.warn("Can't find gateway mac address from record {}", record);
1458 return;
1459 }
1460
1461 HostId gwHostId = HostId.hostId(gwMac, record.vlanId());
1462 Host gwHost = hostService.getHost(gwHostId);
1463
1464 if (gwHost == null) {
1465 log.warn("Can't find gateway host {}", gwHostId);
1466 return;
1467 }
1468
1469 Ip4Address nextHopIp = gwHost.ipAddresses()
1470 .stream()
1471 .filter(IpAddress::isIp4)
1472 .map(IpAddress::getIp4Address)
1473 .findFirst()
1474 .orElse(null);
1475
1476 if (nextHopIp == null) {
1477 log.warn("Can't find IP address of gateway {}", gwHost);
1478 return;
1479 }
1480
Charles Chan6305b692018-04-04 11:43:54 -07001481 Route route = new Route(Route.Source.DHCP, ip.toIpPrefix(), nextHopIp);
Yi Tseng51301292017-07-28 13:02:59 -07001482 routeStore.updateRoute(route);
1483 }
Yi Tseng51301292017-07-28 13:02:59 -07001484 }
1485
1486 /**
Yi Tseng51301292017-07-28 13:02:59 -07001487 * Gets output interface of a dhcp packet.
1488 * If option 82 exists in the dhcp packet and the option was sent by
Yi Tseng4f2a0462017-08-31 11:21:00 -07001489 * ONOS (circuit format is correct), use the connect
Yi Tseng51301292017-07-28 13:02:59 -07001490 * point and vlan id from circuit id; otherwise, find host by destination
1491 * address and use vlan id from sender (dhcp server).
1492 *
1493 * @param ethPacket the ethernet packet
1494 * @param dhcpPayload the dhcp packet
1495 * @return an interface represent the output port and vlan; empty value
1496 * if the host or circuit id not found
1497 */
Yi Tsengdcef2c22017-08-05 20:34:06 -07001498 private Optional<Interface> getClientInterface(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tseng51301292017-07-28 13:02:59 -07001499 VlanId originalPacketVlanId = VlanId.vlanId(ethPacket.getVlanID());
Yi Tseng51301292017-07-28 13:02:59 -07001500 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
1501
Yi Tseng4f2a0462017-08-31 11:21:00 -07001502 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
1503 try {
1504 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
1505 ConnectPoint connectPoint = ConnectPoint.deviceConnectPoint(circuitId.connectPoint());
1506 VlanId vlanId = circuitId.vlanId();
1507 return interfaceService.getInterfacesByPort(connectPoint)
1508 .stream()
1509 .filter(iface -> interfaceContainsVlan(iface, vlanId))
1510 .findFirst();
1511 } catch (IllegalArgumentException ex) {
1512 // invalid circuit format, didn't sent by ONOS
1513 log.debug("Invalid circuit {}, use information from dhcp payload",
1514 circuitIdSubOption.getData());
Yi Tseng51301292017-07-28 13:02:59 -07001515 }
1516
1517 // Use Vlan Id from DHCP server if DHCP relay circuit id was not
1518 // sent by ONOS or circuit Id can't be parsed
Yi Tsengdcef2c22017-08-05 20:34:06 -07001519 // TODO: remove relay store from this method
Yi Tseng51301292017-07-28 13:02:59 -07001520 MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
1521 Optional<DhcpRecord> dhcpRecord = dhcpRelayStore.getDhcpRecord(HostId.hostId(dstMac, originalPacketVlanId));
Yi Tsengdcef2c22017-08-05 20:34:06 -07001522 ConnectPoint clientConnectPoint = dhcpRecord
Yi Tseng51301292017-07-28 13:02:59 -07001523 .map(DhcpRecord::locations)
1524 .orElse(Collections.emptySet())
1525 .stream()
1526 .reduce((hl1, hl2) -> {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001527 // find latest host connect point
Yi Tseng51301292017-07-28 13:02:59 -07001528 if (hl1 == null || hl2 == null) {
1529 return hl1 == null ? hl2 : hl1;
1530 }
1531 return hl1.time() > hl2.time() ? hl1 : hl2;
1532 })
Yi Tsengdcef2c22017-08-05 20:34:06 -07001533 .orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -07001534
Yi Tsengdcef2c22017-08-05 20:34:06 -07001535 if (clientConnectPoint != null) {
1536 return interfaceService.getInterfacesByPort(clientConnectPoint)
1537 .stream()
Yi Tseng4f2a0462017-08-31 11:21:00 -07001538 .filter(iface -> interfaceContainsVlan(iface, originalPacketVlanId))
Yi Tsengdcef2c22017-08-05 20:34:06 -07001539 .findFirst();
1540 }
1541 return Optional.empty();
Yi Tseng51301292017-07-28 13:02:59 -07001542 }
1543
1544 /**
1545 * Send the response DHCP to the requester host.
1546 *
Taras Lemkin96a0d342018-03-26 14:52:58 +00001547 * @param thePacket the packet
Yi Tseng51301292017-07-28 13:02:59 -07001548 * @param dhcpPayload the DHCP data
1549 */
Taras Lemkin96a0d342018-03-26 14:52:58 +00001550 private void sendResponseToClient(InternalPacket thePacket, DHCP dhcpPayload) {
1551 checkNotNull(thePacket, "Nothing to send");
1552 checkNotNull(thePacket.getPacket(), "Packet to send must not be empty");
1553 checkNotNull(thePacket.getDestLocation(), "Packet destination not be empty");
1554
1555 Ethernet ethPacket = thePacket.getPacket();
Yi Tsengdcef2c22017-08-05 20:34:06 -07001556 if (directlyConnected(dhcpPayload)) {
1557 ethPacket = removeRelayAgentOption(ethPacket);
1558 }
Taras Lemkin96a0d342018-03-26 14:52:58 +00001559
Yi Tsengdcef2c22017-08-05 20:34:06 -07001560 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
Taras Lemkin96a0d342018-03-26 14:52:58 +00001561 .setOutput(thePacket.getDestLocation().port())
Yi Tsengdcef2c22017-08-05 20:34:06 -07001562 .build();
1563 OutboundPacket o = new DefaultOutboundPacket(
Taras Lemkin96a0d342018-03-26 14:52:58 +00001564 thePacket.getDestLocation().deviceId(),
Yi Tsengdcef2c22017-08-05 20:34:06 -07001565 treatment,
1566 ByteBuffer.wrap(ethPacket.serialize()));
1567 if (log.isTraceEnabled()) {
Taras Lemkin96a0d342018-03-26 14:52:58 +00001568 log.trace("Relaying packet to DHCP client {} via {}",
Yi Tsengdcef2c22017-08-05 20:34:06 -07001569 ethPacket,
Taras Lemkin96a0d342018-03-26 14:52:58 +00001570 thePacket.getDestLocation());
Yi Tsengdcef2c22017-08-05 20:34:06 -07001571 }
1572 packetService.emit(o);
Yi Tseng51301292017-07-28 13:02:59 -07001573 }
Yi Tseng483ac6f2017-08-02 15:03:31 -07001574
Yi Tsengaa417a62017-09-08 17:22:51 -07001575 @Override
1576 public void triggerProbe(Host host) {
1577 // Do nothing here
1578 }
1579
1580 @Override
1581 public ProviderId id() {
Charles Chand988c282017-09-12 17:09:32 -07001582 return PROVIDER_ID;
Yi Tsengaa417a62017-09-08 17:22:51 -07001583 }
1584
Yi Tseng483ac6f2017-08-02 15:03:31 -07001585 class InternalHostListener implements HostListener {
1586 @Override
1587 public void event(HostEvent event) {
Yi Tseng919b2df2017-09-07 16:22:51 -07001588 if (!configured()) {
1589 return;
1590 }
Yi Tseng483ac6f2017-08-02 15:03:31 -07001591 switch (event.type()) {
1592 case HOST_ADDED:
1593 case HOST_UPDATED:
Jordan Halterman6328db72018-04-10 13:34:50 -04001594 log.trace("Scheduled host event {}", event);
1595 hostEventExecutor.execute(() -> hostUpdated(event.subject()));
Yi Tseng483ac6f2017-08-02 15:03:31 -07001596 break;
1597 case HOST_REMOVED:
Jordan Halterman6328db72018-04-10 13:34:50 -04001598 log.trace("Scheduled host event {}", event);
1599 hostEventExecutor.execute(() -> hostRemoved(event.subject()));
Yi Tseng483ac6f2017-08-02 15:03:31 -07001600 break;
Yi Tseng483ac6f2017-08-02 15:03:31 -07001601 default:
1602 break;
1603 }
1604 }
1605 }
1606
1607 /**
Yi Tseng483ac6f2017-08-02 15:03:31 -07001608 * Handle host updated.
1609 * If the host is DHCP server or gateway, update connect mac and vlan.
1610 *
1611 * @param host the host
1612 */
1613 private void hostUpdated(Host host) {
Yi Tseng525ff402017-10-23 19:39:39 -07001614 hostUpdated(host, defaultServerInfoList);
1615 hostUpdated(host, indirectServerInfoList);
Yi Tseng483ac6f2017-08-02 15:03:31 -07001616 }
1617
Yi Tseng525ff402017-10-23 19:39:39 -07001618 private void hostUpdated(Host host, List<DhcpServerInfo> srverInfoList) {
1619 DhcpServerInfo serverInfo;
1620 Ip4Address targetIp;
1621 if (!srverInfoList.isEmpty()) {
1622 serverInfo = srverInfoList.get(0);
1623 targetIp = serverInfo.getDhcpGatewayIp4().orElse(null);
1624 Ip4Address serverIp = serverInfo.getDhcpServerIp4().orElse(null);
1625
1626 if (targetIp == null) {
1627 targetIp = serverIp;
1628 }
1629
1630 if (targetIp != null) {
1631 if (host.ipAddresses().contains(targetIp)) {
1632 serverInfo.setDhcpConnectMac(host.mac());
1633 serverInfo.setDhcpConnectVlan(host.vlan());
1634 requestDhcpPacket(serverIp);
1635 }
1636 }
1637 }
1638 }
1639
1640
Yi Tseng483ac6f2017-08-02 15:03:31 -07001641 /**
1642 * Handle host removed.
1643 * If the host is DHCP server or gateway, unset connect mac and vlan.
1644 *
1645 * @param host the host
1646 */
1647 private void hostRemoved(Host host) {
Yi Tseng525ff402017-10-23 19:39:39 -07001648 hostRemoved(host, defaultServerInfoList);
1649 hostRemoved(host, indirectServerInfoList);
1650 }
1651
1652 private void hostRemoved(Host host, List<DhcpServerInfo> serverInfoList) {
Yi Tseng919b2df2017-09-07 16:22:51 -07001653 DhcpServerInfo serverInfo;
Yi Tseng525ff402017-10-23 19:39:39 -07001654 Ip4Address targetIp;
1655 if (!serverInfoList.isEmpty()) {
1656 serverInfo = serverInfoList.get(0);
1657 Ip4Address serverIp = serverInfo.getDhcpServerIp4().orElse(null);
1658 targetIp = serverInfo.getDhcpGatewayIp4().orElse(null);
Yi Tseng919b2df2017-09-07 16:22:51 -07001659
Yi Tseng525ff402017-10-23 19:39:39 -07001660 if (targetIp == null) {
1661 targetIp = serverIp;
Yi Tseng919b2df2017-09-07 16:22:51 -07001662 }
Yi Tseng525ff402017-10-23 19:39:39 -07001663
1664 if (targetIp != null) {
1665 if (host.ipAddresses().contains(targetIp)) {
Yi Tseng919b2df2017-09-07 16:22:51 -07001666 serverInfo.setDhcpConnectVlan(null);
1667 serverInfo.setDhcpConnectMac(null);
Yi Tseng525ff402017-10-23 19:39:39 -07001668 cancelDhcpPacket(serverIp);
Yi Tseng919b2df2017-09-07 16:22:51 -07001669 }
Yi Tseng483ac6f2017-08-02 15:03:31 -07001670 }
Yi Tseng483ac6f2017-08-02 15:03:31 -07001671 }
Yi Tseng525ff402017-10-23 19:39:39 -07001672 }
Yi Tseng919b2df2017-09-07 16:22:51 -07001673
Yi Tseng525ff402017-10-23 19:39:39 -07001674 private void requestDhcpPacket(Ip4Address serverIp) {
1675 requestServerDhcpPacket(serverIp);
1676 requestClientDhcpPacket(serverIp);
1677 }
Yi Tseng919b2df2017-09-07 16:22:51 -07001678
Yi Tseng525ff402017-10-23 19:39:39 -07001679 private void cancelDhcpPacket(Ip4Address serverIp) {
1680 cancelServerDhcpPacket(serverIp);
1681 cancelClientDhcpPacket(serverIp);
1682 }
1683
1684 private void cancelServerDhcpPacket(Ip4Address serverIp) {
1685 TrafficSelector serverSelector =
1686 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1687 .matchIPSrc(serverIp.toIpPrefix())
1688 .build();
1689 packetService.cancelPackets(serverSelector,
1690 PacketPriority.CONTROL,
1691 appId);
1692 }
1693
1694 private void requestServerDhcpPacket(Ip4Address serverIp) {
1695 TrafficSelector serverSelector =
1696 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1697 .matchIPSrc(serverIp.toIpPrefix())
1698 .build();
1699 packetService.requestPackets(serverSelector,
1700 PacketPriority.CONTROL,
1701 appId);
1702 }
1703
1704 private void cancelClientDhcpPacket(Ip4Address serverIp) {
1705 // Packet comes from relay
1706 TrafficSelector indirectClientSelector =
1707 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1708 .matchIPDst(serverIp.toIpPrefix())
1709 .build();
1710 packetService.cancelPackets(indirectClientSelector,
1711 PacketPriority.CONTROL,
1712 appId);
1713
1714 // Packet comes from client
1715 packetService.cancelPackets(CLIENT_SERVER_SELECTOR,
1716 PacketPriority.CONTROL,
1717 appId);
1718 }
1719
1720 private void requestClientDhcpPacket(Ip4Address serverIp) {
1721 // Packet comes from relay
1722 TrafficSelector indirectClientSelector =
1723 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1724 .matchIPDst(serverIp.toIpPrefix())
1725 .build();
1726 packetService.requestPackets(indirectClientSelector,
1727 PacketPriority.CONTROL,
1728 appId);
1729
1730 // Packet comes from client
1731 packetService.requestPackets(CLIENT_SERVER_SELECTOR,
1732 PacketPriority.CONTROL,
1733 appId);
1734 }
1735
1736 /**
1737 * Process the ignore rules.
1738 *
1739 * @param deviceId the device id
1740 * @param vlanId the vlan to be ignored
1741 * @param op the operation, ADD to install; REMOVE to uninstall rules
1742 */
1743 private void processIgnoreVlanRule(DeviceId deviceId, VlanId vlanId, Objective.Operation op) {
Yi Tseng525ff402017-10-23 19:39:39 -07001744 AtomicInteger installedCount = new AtomicInteger(DHCP_SELECTORS.size());
1745 DHCP_SELECTORS.forEach(trafficSelector -> {
1746 TrafficSelector selector = DefaultTrafficSelector.builder(trafficSelector)
1747 .matchVlanId(vlanId)
1748 .build();
1749
1750 ForwardingObjective.Builder builder = DefaultForwardingObjective.builder()
1751 .withFlag(ForwardingObjective.Flag.VERSATILE)
1752 .withSelector(selector)
1753 .withPriority(IGNORE_CONTROL_PRIORITY)
Yi Tsengdbabeed2017-11-29 10:49:20 -08001754 .withTreatment(DefaultTrafficTreatment.emptyTreatment())
Yi Tseng525ff402017-10-23 19:39:39 -07001755 .fromApp(appId);
1756
1757
1758 ObjectiveContext objectiveContext = new ObjectiveContext() {
1759 @Override
1760 public void onSuccess(Objective objective) {
1761 log.info("Ignore rule {} (Vlan id {}, device {}, selector {})",
1762 op, vlanId, deviceId, selector);
1763 int countDown = installedCount.decrementAndGet();
1764 if (countDown != 0) {
1765 return;
1766 }
1767 switch (op) {
1768 case ADD:
1769 ignoredVlans.put(deviceId, vlanId);
1770 break;
1771 case REMOVE:
1772 ignoredVlans.remove(deviceId, vlanId);
1773 break;
1774 default:
1775 log.warn("Unsupported objective operation {}", op);
1776 break;
1777 }
Yi Tseng919b2df2017-09-07 16:22:51 -07001778 }
Yi Tseng525ff402017-10-23 19:39:39 -07001779
1780 @Override
1781 public void onError(Objective objective, ObjectiveError error) {
1782 log.warn("Can't {} ignore rule (vlan id {}, selector {}, device {}) due to {}",
1783 op, vlanId, selector, deviceId, error);
Yi Tseng919b2df2017-09-07 16:22:51 -07001784 }
Yi Tseng525ff402017-10-23 19:39:39 -07001785 };
1786
1787 ForwardingObjective fwd;
1788 switch (op) {
1789 case ADD:
1790 fwd = builder.add(objectiveContext);
1791 break;
1792 case REMOVE:
1793 fwd = builder.remove(objectiveContext);
1794 break;
1795 default:
1796 log.warn("Unsupported objective operation {}", op);
1797 return;
Yi Tseng4f2a0462017-08-31 11:21:00 -07001798 }
Yi Tseng525ff402017-10-23 19:39:39 -07001799
1800 Device device = deviceService.getDevice(deviceId);
1801 if (device == null || !device.is(Pipeliner.class)) {
1802 log.warn("Device {} is not available now, wait until device is available", deviceId);
1803 return;
1804 }
1805 flowObjectiveService.apply(deviceId, fwd);
1806 });
Yi Tseng483ac6f2017-08-02 15:03:31 -07001807 }
Kalhee Kimba366062017-11-07 16:32:09 +00001808
1809 @Override
1810 public void setDhcpFpmEnabled(Boolean enabled) {
1811 // v4 does not use fpm. Do nothing.
1812 }
rsahot036620655b2018-02-26 15:01:37 -05001813 private List<DhcpServerInfo> findValidServerInfo(boolean directConnFlag) {
1814 List<DhcpServerInfo> validServerInfo;
1815
1816 if (directConnFlag || indirectServerInfoList.isEmpty()) {
1817 validServerInfo = new ArrayList<DhcpServerInfo>(defaultServerInfoList);
1818 } else {
1819 validServerInfo = new ArrayList<DhcpServerInfo>(indirectServerInfoList);
1820 }
1821 return validServerInfo;
1822 }
1823
1824
1825 private boolean checkDhcpServerConnPt(boolean directConnFlag,
1826 DhcpServerInfo serverInfo) {
1827 if (serverInfo.getDhcpServerConnectPoint() == null) {
1828 log.warn("DHCP4 server connect point for {} connPt {}",
1829 directConnFlag ? "direct" : "indirect", serverInfo.getDhcpServerConnectPoint());
1830 return false;
1831 }
1832 return true;
1833 }
1834
1835 /**
1836 * Checks if serverInfo's host info (mac and vlan) is filled in; if not, fills in.
1837 *
1838 * @param serverInfo server information
1839 * @return newServerInfo if host info can be either found or filled in.
1840 */
1841 private DhcpServerInfo getHostInfoForServerInfo(DhcpServerInfo serverInfo, List<DhcpServerInfo> sererInfoList) {
1842 DhcpServerInfo newServerInfo = null;
1843 MacAddress dhcpServerConnectMac = serverInfo.getDhcpConnectMac().orElse(null);
1844 VlanId dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
1845 ConnectPoint dhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
1846
1847 if (dhcpServerConnectMac != null && dhcpConnectVlan != null) {
1848 newServerInfo = serverInfo;
Charles Chan2de55302018-03-02 18:27:59 -08001849 log.debug("DHCP server {} host info found. ConnectPt{} Mac {} vlan {}", serverInfo.getDhcpServerIp4(),
rsahot036620655b2018-02-26 15:01:37 -05001850 dhcpServerConnectPoint, dhcpServerConnectMac, dhcpConnectVlan);
1851 } else {
1852 log.warn("DHCP server {} not resolve yet connectPt {} mac {} vlan {}", serverInfo.getDhcpServerIp4(),
1853 dhcpServerConnectPoint, dhcpServerConnectMac, dhcpConnectVlan);
1854
1855 Ip4Address ipToProbe;
1856 if (serverInfo.getDhcpGatewayIp4().isPresent()) {
1857 ipToProbe = serverInfo.getDhcpGatewayIp4().get();
1858 } else {
1859 ipToProbe = serverInfo.getDhcpServerIp4().orElse(null);
1860 }
1861 String hostToProbe = serverInfo.getDhcpGatewayIp6()
1862 .map(ip -> "gateway").orElse("server");
1863
1864 log.warn("Dynamically probing to resolve {} IP {}", hostToProbe, ipToProbe);
1865 hostService.startMonitoringIp(ipToProbe);
1866
1867 Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
1868 if (!hosts.isEmpty()) {
1869 int serverInfoIndex = sererInfoList.indexOf(serverInfo);
1870 Host host = hosts.iterator().next();
1871 serverInfo.setDhcpConnectVlan(host.vlan());
1872 serverInfo.setDhcpConnectMac(host.mac());
1873 // replace the serverInfo in the list
1874 sererInfoList.set(serverInfoIndex, serverInfo);
1875 newServerInfo = serverInfo;
1876 log.warn("Dynamically host found host {}", host);
1877 } else {
1878 log.warn("No host found host ip {} dynamically", ipToProbe);
1879 }
1880 }
1881 return newServerInfo;
1882 }
1883
1884 /**
1885 * Gets Interface facing to the server for default host.
1886 *
1887 * @param serverInfo server information
1888 * @return the Interface facing to the server; null if not found
1889 */
1890 private Interface getServerInterface(DhcpServerInfo serverInfo) {
1891 Interface serverInterface = null;
1892
1893 ConnectPoint dhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
1894 VlanId dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
1895
1896 if (dhcpServerConnectPoint != null && dhcpConnectVlan != null) {
1897 serverInterface = interfaceService.getInterfacesByPort(dhcpServerConnectPoint)
1898 .stream()
Taras Lemkin96a0d342018-03-26 14:52:58 +00001899 .filter(iface -> Dhcp4HandlerUtil.interfaceContainsVlan(iface, dhcpConnectVlan))
rsahot036620655b2018-02-26 15:01:37 -05001900 .findFirst()
1901 .orElse(null);
1902 } else {
1903 log.warn("DHCP server {} not resolve yet connectPoint {} vlan {}", serverInfo.getDhcpServerIp6(),
1904 dhcpServerConnectPoint, dhcpConnectVlan);
1905 }
1906
1907 return serverInterface;
1908 }
1909
1910 //forward the packet to ConnectPoint where the DHCP server is attached.
1911 private void forwardPacket(InternalPacket packet) {
1912 //send Packetout to dhcp server connectpoint.
Taras Lemkin96a0d342018-03-26 14:52:58 +00001913 if (packet.getDestLocation() != null) {
rsahot036620655b2018-02-26 15:01:37 -05001914 TrafficTreatment t = DefaultTrafficTreatment.builder()
Taras Lemkin96a0d342018-03-26 14:52:58 +00001915 .setOutput(packet.getDestLocation().port()).build();
rsahot036620655b2018-02-26 15:01:37 -05001916 OutboundPacket o = new DefaultOutboundPacket(
Taras Lemkin96a0d342018-03-26 14:52:58 +00001917 packet.getDestLocation().deviceId(), t, ByteBuffer.wrap(packet.getPacket().serialize()));
rsahot036620655b2018-02-26 15:01:37 -05001918 if (log.isTraceEnabled()) {
Taras Lemkin96a0d342018-03-26 14:52:58 +00001919 log.trace("Relaying packet to destination {}", packet.getDestLocation());
rsahot036620655b2018-02-26 15:01:37 -05001920 }
Taras Lemkin96a0d342018-03-26 14:52:58 +00001921 log.debug("packetService.emit(o) to port {}", packet.getDestLocation());
rsahot036620655b2018-02-26 15:01:37 -05001922 packetService.emit(o);
1923 }
1924 }
1925
1926
1927 private DhcpServerInfo findServerInfoFromServer(boolean directConnFlag, ConnectPoint inPort) {
1928 List<DhcpServerInfo> validServerInfoList = findValidServerInfo(directConnFlag);
1929 DhcpServerInfo foundServerInfo = null;
1930 for (DhcpServerInfo serverInfo : validServerInfoList) {
1931 if (inPort.equals(serverInfo.getDhcpServerConnectPoint().get())) {
1932 foundServerInfo = serverInfo;
Charles Chan2de55302018-03-02 18:27:59 -08001933 log.debug("ServerInfo found for Rcv port {} Server Connect Point {} for {}",
rsahot036620655b2018-02-26 15:01:37 -05001934 inPort, serverInfo.getDhcpServerConnectPoint(), directConnFlag ? "direct" : "indirect");
1935 break;
1936 } else {
1937 log.warn("Rcv port {} not the same as Server Connect Point {} for {}",
1938 inPort, serverInfo.getDhcpServerConnectPoint(), directConnFlag ? "direct" : "indirect");
1939 }
1940 }
1941 return foundServerInfo;
1942 }
Yi Tseng51301292017-07-28 13:02:59 -07001943}