blob: 4c91f7ac1081b443976836115d7a4e3a54f32bf4 [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 Tseng7da339e2017-10-23 19:39:39 -070020import com.google.common.collect.HashMultimap;
21import com.google.common.collect.ImmutableSet;
Yi Tseng2fe8f3f2017-09-07 16:22:51 -070022import com.google.common.collect.Lists;
Yi Tseng7da339e2017-10-23 19:39:39 -070023import com.google.common.collect.Multimap;
Yi Tseng51301292017-07-28 13:02:59 -070024import com.google.common.collect.Sets;
Yi Tsenge72fbb52017-08-02 15:03:31 -070025import org.apache.felix.scr.annotations.Activate;
Yi Tseng51301292017-07-28 13:02:59 -070026import org.apache.felix.scr.annotations.Component;
Yi Tsenge72fbb52017-08-02 15:03:31 -070027import org.apache.felix.scr.annotations.Deactivate;
Yi Tseng51301292017-07-28 13:02:59 -070028import org.apache.felix.scr.annotations.Property;
29import org.apache.felix.scr.annotations.Reference;
30import org.apache.felix.scr.annotations.ReferenceCardinality;
31import org.apache.felix.scr.annotations.Service;
32import org.onlab.packet.BasePacket;
33import org.onlab.packet.DHCP;
34import org.onlab.packet.Ethernet;
35import org.onlab.packet.IPv4;
36import org.onlab.packet.Ip4Address;
37import org.onlab.packet.IpAddress;
38import org.onlab.packet.MacAddress;
Yi Tseng7da339e2017-10-23 19:39:39 -070039import org.onlab.packet.TpPort;
Yi Tseng51301292017-07-28 13:02:59 -070040import org.onlab.packet.UDP;
41import org.onlab.packet.VlanId;
42import org.onlab.packet.dhcp.CircuitId;
43import org.onlab.packet.dhcp.DhcpOption;
44import org.onlab.packet.dhcp.DhcpRelayAgentOption;
Yi Tseng7da339e2017-10-23 19:39:39 -070045import org.onosproject.core.ApplicationId;
46import org.onosproject.core.CoreService;
Yi Tseng51301292017-07-28 13:02:59 -070047import org.onosproject.dhcprelay.api.DhcpHandler;
Yi Tseng2fe8f3f2017-09-07 16:22:51 -070048import org.onosproject.dhcprelay.api.DhcpServerInfo;
Yi Tsenge72fbb52017-08-02 15:03:31 -070049import org.onosproject.dhcprelay.config.DhcpServerConfig;
Yi Tseng7da339e2017-10-23 19:39:39 -070050import org.onosproject.dhcprelay.config.IgnoreDhcpConfig;
Yi Tseng51301292017-07-28 13:02:59 -070051import org.onosproject.dhcprelay.store.DhcpRecord;
52import org.onosproject.dhcprelay.store.DhcpRelayStore;
Yi Tseng7da339e2017-10-23 19:39:39 -070053import org.onosproject.net.Device;
54import org.onosproject.net.DeviceId;
55import org.onosproject.net.behaviour.Pipeliner;
56import org.onosproject.net.device.DeviceService;
57import org.onosproject.net.flow.DefaultTrafficSelector;
58import org.onosproject.net.flow.TrafficSelector;
59import org.onosproject.net.flowobjective.DefaultForwardingObjective;
60import org.onosproject.net.flowobjective.FlowObjectiveService;
61import org.onosproject.net.flowobjective.ForwardingObjective;
62import org.onosproject.net.flowobjective.Objective;
63import org.onosproject.net.flowobjective.ObjectiveContext;
64import org.onosproject.net.flowobjective.ObjectiveError;
Yi Tsenge72fbb52017-08-02 15:03:31 -070065import org.onosproject.net.host.HostEvent;
66import org.onosproject.net.host.HostListener;
Yi Tseng4b013202017-09-08 17:22:51 -070067import org.onosproject.net.host.HostProvider;
68import org.onosproject.net.host.HostProviderRegistry;
69import org.onosproject.net.host.HostProviderService;
Ray Milkeyfacf2862017-08-03 11:58:29 -070070import org.onosproject.net.intf.Interface;
71import org.onosproject.net.intf.InterfaceService;
Yi Tseng7da339e2017-10-23 19:39:39 -070072import org.onosproject.net.packet.PacketPriority;
Yi Tseng4b013202017-09-08 17:22:51 -070073import org.onosproject.net.provider.ProviderId;
Ray Milkey69ec8712017-08-08 13:00:43 -070074import org.onosproject.routeservice.Route;
75import org.onosproject.routeservice.RouteStore;
Yi Tseng51301292017-07-28 13:02:59 -070076import org.onosproject.net.ConnectPoint;
77import org.onosproject.net.Host;
78import org.onosproject.net.HostId;
79import org.onosproject.net.HostLocation;
80import org.onosproject.net.flow.DefaultTrafficTreatment;
81import org.onosproject.net.flow.TrafficTreatment;
82import org.onosproject.net.host.DefaultHostDescription;
83import org.onosproject.net.host.HostDescription;
84import org.onosproject.net.host.HostService;
Yi Tseng51301292017-07-28 13:02:59 -070085import org.onosproject.net.host.InterfaceIpAddress;
86import org.onosproject.net.packet.DefaultOutboundPacket;
87import org.onosproject.net.packet.OutboundPacket;
88import org.onosproject.net.packet.PacketContext;
89import org.onosproject.net.packet.PacketService;
90import org.slf4j.Logger;
91import org.slf4j.LoggerFactory;
92
93import java.nio.ByteBuffer;
Yi Tsengdcef2c22017-08-05 20:34:06 -070094import java.util.Collection;
Yi Tseng51301292017-07-28 13:02:59 -070095import java.util.Collections;
96import java.util.List;
97import java.util.Optional;
98import java.util.Set;
Yi Tseng7da339e2017-10-23 19:39:39 -070099import java.util.concurrent.atomic.AtomicInteger;
Yi Tseng51301292017-07-28 13:02:59 -0700100import java.util.stream.Collectors;
101
102import static com.google.common.base.Preconditions.checkNotNull;
103import static com.google.common.base.Preconditions.checkState;
104import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_CircuitID;
105import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_END;
106import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
107import static org.onlab.packet.MacAddress.valueOf;
108import static org.onlab.packet.dhcp.DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID;
Yi Tseng7da339e2017-10-23 19:39:39 -0700109import static org.onosproject.net.flowobjective.Objective.Operation.ADD;
110import static org.onosproject.net.flowobjective.Objective.Operation.REMOVE;
Yi Tseng51301292017-07-28 13:02:59 -0700111
112@Component
113@Service
114@Property(name = "version", value = "4")
Yi Tseng4b013202017-09-08 17:22:51 -0700115public class Dhcp4HandlerImpl implements DhcpHandler, HostProvider {
Charles Chan75edab72017-09-12 17:09:32 -0700116 public static final String DHCP_V4_RELAY_APP = "org.onosproject.Dhcp4HandlerImpl";
117 public static final ProviderId PROVIDER_ID = new ProviderId("dhcp4", DHCP_V4_RELAY_APP);
Yi Tseng7da339e2017-10-23 19:39:39 -0700118 private static final String BROADCAST_IP = "255.255.255.255";
119 private static final int IGNORE_CONTROL_PRIORITY = PacketPriority.CONTROL.priorityValue() + 1000;
120
121 private static final TrafficSelector CLIENT_SERVER_SELECTOR = DefaultTrafficSelector.builder()
122 .matchEthType(Ethernet.TYPE_IPV4)
123 .matchIPProtocol(IPv4.PROTOCOL_UDP)
124 .matchIPSrc(Ip4Address.ZERO.toIpPrefix())
125 .matchIPDst(Ip4Address.valueOf(BROADCAST_IP).toIpPrefix())
126 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
127 .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
128 .build();
129 private static final TrafficSelector SERVER_RELAY_SELECTOR = DefaultTrafficSelector.builder()
130 .matchEthType(Ethernet.TYPE_IPV4)
131 .matchIPProtocol(IPv4.PROTOCOL_UDP)
132 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
133 .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
134 .build();
135 static final Set<TrafficSelector> DHCP_SELECTORS = ImmutableSet.of(
136 CLIENT_SERVER_SELECTOR,
137 SERVER_RELAY_SELECTOR
138 );
Yi Tseng51301292017-07-28 13:02:59 -0700139 private static Logger log = LoggerFactory.getLogger(Dhcp4HandlerImpl.class);
140
141 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
142 protected DhcpRelayStore dhcpRelayStore;
143
144 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
145 protected PacketService packetService;
146
147 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Yi Tseng51301292017-07-28 13:02:59 -0700148 protected RouteStore routeStore;
149
150 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
151 protected InterfaceService interfaceService;
152
153 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
154 protected HostService hostService;
155
Yi Tseng4b013202017-09-08 17:22:51 -0700156 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
157 protected HostProviderRegistry providerRegistry;
158
Yi Tseng7da339e2017-10-23 19:39:39 -0700159 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
160 protected CoreService coreService;
161
162 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
163 protected DeviceService deviceService;
164
165 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
166 protected FlowObjectiveService flowObjectiveService;
167
Yi Tseng4b013202017-09-08 17:22:51 -0700168 protected HostProviderService providerService;
Yi Tseng7da339e2017-10-23 19:39:39 -0700169 protected ApplicationId appId;
170 protected Multimap<DeviceId, VlanId> ignoredVlans = HashMultimap.create();
Yi Tsenge72fbb52017-08-02 15:03:31 -0700171 private InternalHostListener hostListener = new InternalHostListener();
172
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700173 private List<DhcpServerInfo> defaultServerInfoList = Lists.newArrayList();
174 private List<DhcpServerInfo> indirectServerInfoList = Lists.newArrayList();
Yi Tseng4ec727d2017-08-31 11:21:00 -0700175
Yi Tsenge72fbb52017-08-02 15:03:31 -0700176 @Activate
177 protected void activate() {
Yi Tseng7da339e2017-10-23 19:39:39 -0700178 appId = coreService.registerApplication(DHCP_V4_RELAY_APP);
Yi Tsenge72fbb52017-08-02 15:03:31 -0700179 hostService.addListener(hostListener);
Yi Tseng4b013202017-09-08 17:22:51 -0700180 providerService = providerRegistry.register(this);
Yi Tsenge72fbb52017-08-02 15:03:31 -0700181 }
182
183 @Deactivate
184 protected void deactivate() {
Yi Tseng4b013202017-09-08 17:22:51 -0700185 providerRegistry.unregister(this);
186 hostService.removeListener(hostListener);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700187 defaultServerInfoList.forEach(this::stopMonitoringIps);
188 defaultServerInfoList.clear();
189 indirectServerInfoList.forEach(this::stopMonitoringIps);
190 indirectServerInfoList.clear();
Yi Tsenge72fbb52017-08-02 15:03:31 -0700191 }
192
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700193 private void stopMonitoringIps(DhcpServerInfo serverInfo) {
194 serverInfo.getDhcpGatewayIp4().ifPresent(gatewayIp -> {
195 hostService.stopMonitoringIp(gatewayIp);
196 });
197 serverInfo.getDhcpServerIp4().ifPresent(serverIp -> {
198 hostService.stopMonitoringIp(serverIp);
199 });
Yi Tseng51301292017-07-28 13:02:59 -0700200 }
201
202 @Override
Yi Tsenge72fbb52017-08-02 15:03:31 -0700203 public void setDefaultDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700204 setDhcpServerConfigs(configs, defaultServerInfoList);
205 }
206
207 @Override
208 public void setIndirectDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
209 setDhcpServerConfigs(configs, indirectServerInfoList);
210 }
211
212 @Override
213 public List<DhcpServerInfo> getDefaultDhcpServerInfoList() {
214 return defaultServerInfoList;
215 }
216
217 @Override
218 public List<DhcpServerInfo> getIndirectDhcpServerInfoList() {
219 return indirectServerInfoList;
220 }
221
Yi Tseng7da339e2017-10-23 19:39:39 -0700222 @Override
223 public void updateIgnoreVlanConfig(IgnoreDhcpConfig config) {
224 if (config == null) {
225 ignoredVlans.forEach(((deviceId, vlanId) -> {
226 processIgnoreVlanRule(deviceId, vlanId, REMOVE);
227 }));
228 return;
229 }
230 config.ignoredVlans().forEach((deviceId, vlanId) -> {
231 if (ignoredVlans.get(deviceId).contains(vlanId)) {
232 // don't need to process if it already ignored
233 return;
234 }
235 processIgnoreVlanRule(deviceId, vlanId, ADD);
236 });
237
238 ignoredVlans.forEach((deviceId, vlanId) -> {
239 if (!config.ignoredVlans().get(deviceId).contains(vlanId)) {
240 // not contains in new config, remove it
241 processIgnoreVlanRule(deviceId, vlanId, REMOVE);
242 }
243 });
244 }
245
Saurav Dasba7eb1a2017-12-13 16:19:35 -0800246 @Override
247 public void removeIgnoreVlanState(IgnoreDhcpConfig config) {
248 if (config == null) {
249 ignoredVlans.clear();
250 return;
251 }
252 config.ignoredVlans().forEach((deviceId, vlanId) -> {
253 ignoredVlans.remove(deviceId, vlanId);
254 });
255 }
256
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700257 public void setDhcpServerConfigs(Collection<DhcpServerConfig> configs, List<DhcpServerInfo> serverInfoList) {
Yi Tsenge72fbb52017-08-02 15:03:31 -0700258 if (configs.size() == 0) {
259 // no config to update
260 return;
261 }
262
263 // TODO: currently we pick up first DHCP server config.
264 // Will use other server configs in the future for HA.
265 DhcpServerConfig serverConfig = configs.iterator().next();
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700266
Yi Tsengaefbb002017-09-08 16:23:32 -0700267 if (!serverConfig.getDhcpServerIp4().isPresent()) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700268 // not a DHCPv4 config
Yi Tsenge72fbb52017-08-02 15:03:31 -0700269 return;
270 }
271
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700272 if (!serverInfoList.isEmpty()) {
273 // remove old server info
274 DhcpServerInfo oldServerInfo = serverInfoList.remove(0);
Yi Tsenge72fbb52017-08-02 15:03:31 -0700275
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700276 // stop monitoring gateway or server
277 oldServerInfo.getDhcpGatewayIp4().ifPresent(gatewayIp -> {
278 hostService.stopMonitoringIp(gatewayIp);
279 });
280 oldServerInfo.getDhcpServerIp4().ifPresent(serverIp -> {
281 hostService.stopMonitoringIp(serverIp);
Yi Tseng7da339e2017-10-23 19:39:39 -0700282 cancelDhcpPacket(serverIp);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700283 });
Yi Tsenge72fbb52017-08-02 15:03:31 -0700284 }
Yi Tseng4fa05832017-08-17 13:08:31 -0700285
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700286 // Create new server info according to the config
287 DhcpServerInfo newServerInfo = new DhcpServerInfo(serverConfig,
288 DhcpServerInfo.Version.DHCP_V4);
289 checkState(newServerInfo.getDhcpServerConnectPoint().isPresent(),
Yi Tseng4ec727d2017-08-31 11:21:00 -0700290 "Connect point not exists");
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700291 checkState(newServerInfo.getDhcpServerIp4().isPresent(),
Yi Tseng4ec727d2017-08-31 11:21:00 -0700292 "IP of DHCP server not exists");
Yi Tseng4ec727d2017-08-31 11:21:00 -0700293
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700294 log.debug("DHCP server connect point: {}", newServerInfo.getDhcpServerConnectPoint().orElse(null));
295 log.debug("DHCP server IP: {}", newServerInfo.getDhcpServerIp4().orElse(null));
296
Yi Tseng7da339e2017-10-23 19:39:39 -0700297 Ip4Address serverIp = newServerInfo.getDhcpServerIp4().get();
298 Ip4Address ipToProbe;
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700299 if (newServerInfo.getDhcpGatewayIp4().isPresent()) {
300 ipToProbe = newServerInfo.getDhcpGatewayIp4().get();
301 } else {
302 ipToProbe = newServerInfo.getDhcpServerIp4().orElse(null);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700303 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700304 String hostToProbe = newServerInfo.getDhcpGatewayIp4()
305 .map(ip -> "gateway").orElse("server");
Yi Tseng4ec727d2017-08-31 11:21:00 -0700306
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700307 log.debug("Probing to resolve {} IP {}", hostToProbe, ipToProbe);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700308 hostService.startMonitoringIp(ipToProbe);
309
310 Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
311 if (!hosts.isEmpty()) {
312 Host host = hosts.iterator().next();
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700313 newServerInfo.setDhcpConnectVlan(host.vlan());
314 newServerInfo.setDhcpConnectMac(host.mac());
Yi Tseng4ec727d2017-08-31 11:21:00 -0700315 }
316
Yi Tseng053682b2017-11-09 13:54:12 -0800317 // Add new server info
318 synchronized (this) {
319 serverInfoList.clear();
320 serverInfoList.add(0, newServerInfo);
321 }
322
Yi Tseng7da339e2017-10-23 19:39:39 -0700323 requestDhcpPacket(serverIp);
Yi Tsenge72fbb52017-08-02 15:03:31 -0700324 }
325
Yi Tseng4fa05832017-08-17 13:08:31 -0700326 @Override
Yi Tseng51301292017-07-28 13:02:59 -0700327 public void processDhcpPacket(PacketContext context, BasePacket payload) {
328 checkNotNull(payload, "DHCP payload can't be null");
329 checkState(payload instanceof DHCP, "Payload is not a DHCP");
330 DHCP dhcpPayload = (DHCP) payload;
331 if (!configured()) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700332 log.warn("Missing default DHCP relay server config. Abort packet processing");
Yi Tseng51301292017-07-28 13:02:59 -0700333 return;
334 }
335
336 ConnectPoint inPort = context.inPacket().receivedFrom();
Yi Tseng51301292017-07-28 13:02:59 -0700337 checkNotNull(dhcpPayload, "Can't find DHCP payload");
338 Ethernet packet = context.inPacket().parsed();
339 DHCP.MsgType incomingPacketType = dhcpPayload.getOptions().stream()
340 .filter(dhcpOption -> dhcpOption.getCode() == OptionCode_MessageType.getValue())
341 .map(DhcpOption::getData)
342 .map(data -> DHCP.MsgType.getType(data[0]))
343 .findFirst()
344 .orElse(null);
345 checkNotNull(incomingPacketType, "Can't get message type from DHCP payload {}", dhcpPayload);
346 switch (incomingPacketType) {
347 case DHCPDISCOVER:
Yi Tsengdcef2c22017-08-05 20:34:06 -0700348 // Add the gateway IP as virtual interface IP for server to understand
Yi Tseng51301292017-07-28 13:02:59 -0700349 // the lease to be assigned and forward the packet to dhcp server.
350 Ethernet ethernetPacketDiscover =
Yi Tsengdcef2c22017-08-05 20:34:06 -0700351 processDhcpPacketFromClient(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700352 if (ethernetPacketDiscover != null) {
353 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700354 handleDhcpDiscoverAndRequest(ethernetPacketDiscover, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700355 }
356 break;
357 case DHCPOFFER:
358 //reply to dhcp client.
359 Ethernet ethernetPacketOffer = processDhcpPacketFromServer(packet);
360 if (ethernetPacketOffer != null) {
361 writeResponseDhcpRecord(ethernetPacketOffer, dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700362 sendResponseToClient(ethernetPacketOffer, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700363 }
364 break;
365 case DHCPREQUEST:
366 // add the gateway ip as virtual interface ip for server to understand
367 // the lease to be assigned and forward the packet to dhcp server.
368 Ethernet ethernetPacketRequest =
Yi Tsengdcef2c22017-08-05 20:34:06 -0700369 processDhcpPacketFromClient(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700370 if (ethernetPacketRequest != null) {
371 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700372 handleDhcpDiscoverAndRequest(ethernetPacketRequest, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700373 }
374 break;
Charles Chan2f9aa2c2017-10-08 23:53:36 -0400375 case DHCPDECLINE:
376 break;
Yi Tseng51301292017-07-28 13:02:59 -0700377 case DHCPACK:
378 // reply to dhcp client.
379 Ethernet ethernetPacketAck = processDhcpPacketFromServer(packet);
380 if (ethernetPacketAck != null) {
381 writeResponseDhcpRecord(ethernetPacketAck, dhcpPayload);
382 handleDhcpAck(ethernetPacketAck, dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700383 sendResponseToClient(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700384 }
385 break;
Charles Chan2f9aa2c2017-10-08 23:53:36 -0400386 case DHCPNAK:
387 break;
Yi Tseng51301292017-07-28 13:02:59 -0700388 case DHCPRELEASE:
389 // TODO: release the ip address from client
390 break;
Charles Chan2f9aa2c2017-10-08 23:53:36 -0400391 case DHCPINFORM:
392 break;
393 case DHCPFORCERENEW:
394 break;
395 case DHCPLEASEQUERY:
396 handleLeaseQueryMsg(context, packet, dhcpPayload);
397 break;
398 case DHCPLEASEACTIVE:
399 handleLeaseQueryActivateMsg(packet, dhcpPayload);
400 break;
401 case DHCPLEASEUNASSIGNED:
402 case DHCPLEASEUNKNOWN:
403 handleLeaseQueryUnknown(packet, dhcpPayload);
404 break;
Yi Tseng51301292017-07-28 13:02:59 -0700405 default:
406 break;
407 }
408 }
409
410 /**
411 * Checks if this app has been configured.
412 *
413 * @return true if all information we need have been initialized
414 */
Yi Tseng4ec727d2017-08-31 11:21:00 -0700415 private boolean configured() {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700416 return !defaultServerInfoList.isEmpty();
Yi Tseng51301292017-07-28 13:02:59 -0700417 }
418
419 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -0700420 * Returns the first interface ip from interface.
Yi Tseng51301292017-07-28 13:02:59 -0700421 *
Yi Tsengdcef2c22017-08-05 20:34:06 -0700422 * @param iface interface of one connect point
Yi Tseng51301292017-07-28 13:02:59 -0700423 * @return the first interface IP; null if not exists an IP address in
424 * these interfaces
425 */
Yi Tseng4fa05832017-08-17 13:08:31 -0700426 private Ip4Address getFirstIpFromInterface(Interface iface) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700427 checkNotNull(iface, "Interface can't be null");
428 return iface.ipAddressesList().stream()
Yi Tseng51301292017-07-28 13:02:59 -0700429 .map(InterfaceIpAddress::ipAddress)
430 .filter(IpAddress::isIp4)
431 .map(IpAddress::getIp4Address)
432 .findFirst()
433 .orElse(null);
434 }
435
436 /**
Yi Tseng4ec727d2017-08-31 11:21:00 -0700437 * Gets Interface facing to the server for default host.
Yi Tsengdcef2c22017-08-05 20:34:06 -0700438 *
439 * @return the Interface facing to the server; null if not found
440 */
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700441 private Interface getDefaultServerInterface() {
442 return getServerInterface(defaultServerInfoList);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700443 }
444
445 /**
Yi Tseng4ec727d2017-08-31 11:21:00 -0700446 * Gets Interface facing to the server for indirect hosts.
447 * Use default server Interface if indirect server not configured.
448 *
449 * @return the Interface facing to the server; null if not found
450 */
451 private Interface getIndirectServerInterface() {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700452 return getServerInterface(indirectServerInfoList);
453 }
454
455 private Interface getServerInterface(List<DhcpServerInfo> serverInfos) {
Yi Tsengdc510082017-11-29 14:39:18 -0800456 return serverInfos.stream()
Yi Tseng4ec727d2017-08-31 11:21:00 -0700457 .findFirst()
Yi Tsengdc510082017-11-29 14:39:18 -0800458 .map(serverInfo -> {
459 ConnectPoint dhcpServerConnectPoint =
460 serverInfo.getDhcpServerConnectPoint().orElse(null);
461 VlanId dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
462 if (dhcpServerConnectPoint == null || dhcpConnectVlan == null) {
463 return null;
464 }
465 return interfaceService.getInterfacesByPort(dhcpServerConnectPoint)
466 .stream()
467 .filter(iface -> interfaceContainsVlan(iface, dhcpConnectVlan))
468 .findFirst()
469 .orElse(null);
470 })
Yi Tseng4ec727d2017-08-31 11:21:00 -0700471 .orElse(null);
472 }
473
474 /**
475 * Determind if an Interface contains a vlan id.
476 *
477 * @param iface the Interface
478 * @param vlanId the vlan id
479 * @return true if the Interface contains the vlan id
480 */
481 private boolean interfaceContainsVlan(Interface iface, VlanId vlanId) {
Yi Tseng58e74312017-09-30 11:35:42 +0800482 if (vlanId.equals(VlanId.NONE)) {
483 // untagged packet, check if vlan untagged or vlan native is not NONE
484 return !iface.vlanUntagged().equals(VlanId.NONE) ||
485 !iface.vlanNative().equals(VlanId.NONE);
486 }
487 // tagged packet, check if the interface contains the vlan
488 return iface.vlanTagged().contains(vlanId);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700489 }
490
Charles Chan2f9aa2c2017-10-08 23:53:36 -0400491 private void handleLeaseQueryActivateMsg(Ethernet packet, DHCP dhcpPayload) {
492 log.debug("LQ: Got DHCPLEASEACTIVE packet!");
493
494 // TODO: release the ip address from client
495 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
496 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
497 HostId hostId = HostId.hostId(clientMacAddress, vlanId);
498 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
499
500 if (record == null) {
501 log.warn("Can't find record for host {} when processing DHCPLEASEACTIVE", hostId);
502 return;
503 }
504
505 // need to update routes
506 log.debug("Lease Query for Client results in DHCPLEASEACTIVE - route needs to be modified");
507 // get current route
508 // find the ip of that client with the DhcpRelay store
509
510 Ip4Address clientIP = record.ip4Address().orElse(null);
511 log.debug("LQ: IP of host is " + clientIP.getIp4Address());
512
513 MacAddress nextHopMac = record.nextHop().orElse(null);
514 log.debug("LQ: MAC of resulting *OLD* NH for that host is " + nextHopMac.toString());
515
516 // find the new NH by looking at the src MAC of the dhcp request
517 // from the LQ store
518 MacAddress newNextHopMac = record.nextHopTemp().orElse(null);
519 log.debug("LQ: MAC of resulting *NEW* NH for that host is " + newNextHopMac.toString());
520
521 log.debug("LQ: updating dhcp relay record with new NH");
522 record.nextHop(newNextHopMac);
523
524 // find the next hop IP from its mac
525 HostId gwHostId = HostId.hostId(newNextHopMac, vlanId);
526 Host gwHost = hostService.getHost(gwHostId);
527
528 if (gwHost == null) {
529 log.warn("Can't find gateway for new NH host " + gwHostId);
530 return;
531 }
532
533 Ip4Address nextHopIp = gwHost.ipAddresses()
534 .stream()
535 .filter(IpAddress::isIp4)
536 .map(IpAddress::getIp4Address)
537 .findFirst()
538 .orElse(null);
539
540 if (nextHopIp == null) {
541 log.warn("Can't find IP address of gateway " + gwHost);
542 return;
543 }
544
545 log.debug("LQ: *NEW* NH IP for host is " + nextHopIp.getIp4Address());
546 Route route = new Route(Route.Source.STATIC, clientIP.toIpPrefix(), nextHopIp);
547 routeStore.updateRoute(route);
548
549 // and forward to client
550 Ethernet ethernetPacket = processLeaseQueryFromServer(packet);
551 if (ethernetPacket != null) {
552 sendResponseToClient(ethernetPacket, dhcpPayload);
553 }
554 }
555
556 /**
557 *
558 */
559 private void handleLeaseQueryMsg(PacketContext context, Ethernet packet, DHCP dhcpPayload) {
560 log.debug("LQ: Got DHCPLEASEQUERY packet!");
561 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
562 log.debug("LQ: got DHCPLEASEQUERY with MAC " + clientMacAddress.toString());
563 // add the client mac (hostid) of this request to a store (the entry will be removed with
564 // the reply sent to the originator)
565 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
566 HostId hId = HostId.hostId(clientMacAddress, vlanId);
567 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hId).orElse(null);
568 if (record != null) {
569 //new NH is to be taken from src mac of LQ packet
570 MacAddress newNextHop = packet.getSourceMAC();
571 record.nextHopTemp(newNextHop);
572 record.ip4Status(dhcpPayload.getPacketType());
573 record.updateLastSeen();
574
575 // do a basic routing of the packet (this is unicast routing
576 // not a relay operation like for other broadcast dhcp packets
577 Ethernet ethernetPacketLQ = processLeaseQueryFromAgent(context, packet);
578 // and forward to server
579 handleDhcpDiscoverAndRequest(ethernetPacketLQ, dhcpPayload);
580 } else {
581 log.warn("LQ: Error! - DHCP relay record for that client not found - ignoring LQ!");
582 }
583 }
584
585 private void handleLeaseQueryUnknown(Ethernet packet, DHCP dhcpPayload) {
586 log.debug("Lease Query for Client results in DHCPLEASEUNASSIGNED or " +
587 "DHCPLEASEUNKNOWN - removing route & forwarding reply to originator");
588 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
589 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
590 HostId hostId = HostId.hostId(clientMacAddress, vlanId);
591 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
592
593 if (record == null) {
594 log.warn("Can't find record for host {} when handling LQ UNKNOWN/UNASSIGNED message", hostId);
595 return;
596 }
597
598 Ip4Address clientIP = record.ip4Address().orElse(null);
599 log.debug("LQ: IP of host is " + clientIP.getIp4Address());
600
601 // find the new NH by looking at the src MAC of the dhcp request
602 // from the LQ store
603 MacAddress nextHopMac = record.nextHop().orElse(null);
604 log.debug("LQ: MAC of resulting *Existing* NH for that route is " + nextHopMac.toString());
605
606 // find the next hop IP from its mac
607 HostId gwHostId = HostId.hostId(nextHopMac, vlanId);
608 Host gwHost = hostService.getHost(gwHostId);
609
610 if (gwHost == null) {
611 log.warn("Can't find gateway for new NH host " + gwHostId);
612 return;
613 }
614
615 Ip4Address nextHopIp = gwHost.ipAddresses()
616 .stream()
617 .filter(IpAddress::isIp4)
618 .map(IpAddress::getIp4Address)
619 .findFirst()
620 .orElse(null);
621
622 if (nextHopIp == null) {
623 log.warn("Can't find IP address of gateway {}", gwHost);
624 return;
625 }
626
627 log.debug("LQ: *Existing* NH IP for host is " + nextHopIp.getIp4Address() + " removing route for it");
628 Route route = new Route(Route.Source.STATIC, clientIP.toIpPrefix(), nextHopIp);
629 routeStore.removeRoute(route);
630
631 // remove from temp store
632 dhcpRelayStore.removeDhcpRecord(hostId);
633
634 // and forward to client
635 Ethernet ethernetPacket = processLeaseQueryFromServer(packet);
636 if (ethernetPacket != null) {
637 sendResponseToClient(ethernetPacket, dhcpPayload);
638 }
639 }
640
Yi Tseng4ec727d2017-08-31 11:21:00 -0700641 /**
Yi Tseng51301292017-07-28 13:02:59 -0700642 * Build the DHCP discover/request packet with gateway IP(unicast packet).
643 *
644 * @param context the packet context
645 * @param ethernetPacket the ethernet payload to process
Yi Tseng51301292017-07-28 13:02:59 -0700646 * @return processed packet
647 */
648 private Ethernet processDhcpPacketFromClient(PacketContext context,
Yi Tsengdcef2c22017-08-05 20:34:06 -0700649 Ethernet ethernetPacket) {
Yi Tsengc44dc2e2017-11-03 16:27:32 -0700650 ConnectPoint receivedFrom = context.inPacket().receivedFrom();
651 DeviceId receivedFromDevice = receivedFrom.deviceId();
652
Yi Tseng4ec727d2017-08-31 11:21:00 -0700653 // get dhcp header.
654 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
655 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
656 UDP udpPacket = (UDP) ipv4Packet.getPayload();
657 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
658
Yi Tsengc44dc2e2017-11-03 16:27:32 -0700659 // TODO: refactor
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700660 VlanId dhcpConnectVlan = null;
661 MacAddress dhcpConnectMac = null;
662 Ip4Address dhcpServerIp = null;
663 Ip4Address relayAgentIp = null;
664
665 VlanId indirectDhcpConnectVlan = null;
666 MacAddress indirectDhcpConnectMac = null;
667 Ip4Address indirectDhcpServerIp = null;
668 Ip4Address indirectRelayAgentIp = null;
669
670 if (!defaultServerInfoList.isEmpty()) {
671 DhcpServerInfo serverInfo = defaultServerInfoList.get(0);
672 dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
673 dhcpConnectMac = serverInfo.getDhcpConnectMac().orElse(null);
674 dhcpServerIp = serverInfo.getDhcpServerIp4().orElse(null);
Yi Tsengc44dc2e2017-11-03 16:27:32 -0700675 relayAgentIp = serverInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700676 }
677
678 if (!indirectServerInfoList.isEmpty()) {
679 DhcpServerInfo indirectServerInfo = indirectServerInfoList.get(0);
680 indirectDhcpConnectVlan = indirectServerInfo.getDhcpConnectVlan().orElse(null);
681 indirectDhcpConnectMac = indirectServerInfo.getDhcpConnectMac().orElse(null);
682 indirectDhcpServerIp = indirectServerInfo.getDhcpServerIp4().orElse(null);
Yi Tsengc44dc2e2017-11-03 16:27:32 -0700683 indirectRelayAgentIp = indirectServerInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700684 }
685
Yi Tsengdcef2c22017-08-05 20:34:06 -0700686 Ip4Address clientInterfaceIp =
687 interfaceService.getInterfacesByPort(context.inPacket().receivedFrom())
688 .stream()
689 .map(Interface::ipAddressesList)
690 .flatMap(Collection::stream)
691 .map(InterfaceIpAddress::ipAddress)
692 .filter(IpAddress::isIp4)
693 .map(IpAddress::getIp4Address)
694 .findFirst()
695 .orElse(null);
696 if (clientInterfaceIp == null) {
697 log.warn("Can't find interface IP for client interface for port {}",
698 context.inPacket().receivedFrom());
699 return null;
700 }
Yi Tseng4ec727d2017-08-31 11:21:00 -0700701 boolean isDirectlyConnected = directlyConnected(dhcpPacket);
Yi Tsengdc510082017-11-29 14:39:18 -0800702 Interface serverInterface;
703 if (isDirectlyConnected) {
704 serverInterface = getDefaultServerInterface();
705 } else {
706 serverInterface = getIndirectServerInterface();
707 if (serverInterface == null) {
708 // Indirect server interface not found, use default server interface
709 serverInterface = getDefaultServerInterface();
710 }
711 }
Yi Tsengdcef2c22017-08-05 20:34:06 -0700712 if (serverInterface == null) {
Yi Tseng4ec727d2017-08-31 11:21:00 -0700713 log.warn("Can't get {} server interface, ignore", isDirectlyConnected ? "direct" : "indirect");
Yi Tsengdcef2c22017-08-05 20:34:06 -0700714 return null;
715 }
Yi Tseng4fa05832017-08-17 13:08:31 -0700716 Ip4Address ipFacingServer = getFirstIpFromInterface(serverInterface);
717 MacAddress macFacingServer = serverInterface.mac();
718 if (ipFacingServer == null || macFacingServer == null) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700719 log.warn("No IP address for server Interface {}", serverInterface);
Yi Tseng51301292017-07-28 13:02:59 -0700720 return null;
721 }
722 if (dhcpConnectMac == null) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700723 log.warn("DHCP Server/Gateway IP not yet resolved .. Aborting DHCP "
Yi Tseng51301292017-07-28 13:02:59 -0700724 + "packet processing from client on port: {}",
Yi Tsengdcef2c22017-08-05 20:34:06 -0700725 context.inPacket().receivedFrom());
Yi Tseng51301292017-07-28 13:02:59 -0700726 return null;
727 }
Yi Tseng4ec727d2017-08-31 11:21:00 -0700728
Yi Tseng4fa05832017-08-17 13:08:31 -0700729 etherReply.setSourceMACAddress(macFacingServer);
Yi Tseng4fa05832017-08-17 13:08:31 -0700730 ipv4Packet.setSourceAddress(ipFacingServer.toInt());
Yi Tseng51301292017-07-28 13:02:59 -0700731
Yi Tseng4ec727d2017-08-31 11:21:00 -0700732 if (isDirectlyConnected) {
Charles Chanb5d24822017-10-10 16:53:32 -0400733 etherReply.setDestinationMACAddress(dhcpConnectMac);
734 etherReply.setVlanID(dhcpConnectVlan.toShort());
735 ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
736
Yi Tseng51301292017-07-28 13:02:59 -0700737 ConnectPoint inPort = context.inPacket().receivedFrom();
738 VlanId vlanId = VlanId.vlanId(ethernetPacket.getVlanID());
739 // add connected in port and vlan
740 CircuitId cid = new CircuitId(inPort.toString(), vlanId);
741 byte[] circuitId = cid.serialize();
742 DhcpOption circuitIdSubOpt = new DhcpOption();
743 circuitIdSubOpt
744 .setCode(CIRCUIT_ID.getValue())
745 .setLength((byte) circuitId.length)
746 .setData(circuitId);
747
748 DhcpRelayAgentOption newRelayAgentOpt = new DhcpRelayAgentOption();
749 newRelayAgentOpt.setCode(OptionCode_CircuitID.getValue());
750 newRelayAgentOpt.addSubOption(circuitIdSubOpt);
751
Charles Chanb5d24822017-10-10 16:53:32 -0400752 // Removes END option first
Yi Tseng51301292017-07-28 13:02:59 -0700753 List<DhcpOption> options = dhcpPacket.getOptions().stream()
754 .filter(opt -> opt.getCode() != OptionCode_END.getValue())
755 .collect(Collectors.toList());
756
757 // push relay agent option
758 options.add(newRelayAgentOpt);
759
760 // make sure option 255(End) is the last option
761 DhcpOption endOption = new DhcpOption();
762 endOption.setCode(OptionCode_END.getValue());
763 options.add(endOption);
764
765 dhcpPacket.setOptions(options);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700766
Charles Chanb5d24822017-10-10 16:53:32 -0400767 // Sets relay agent IP
768 int effectiveRelayAgentIp = relayAgentIp != null ?
769 relayAgentIp.toInt() : clientInterfaceIp.toInt();
770 dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
771 } else {
772 if (indirectDhcpServerIp != null) {
773 // Use indirect server config for indirect packets if configured
774 etherReply.setDestinationMACAddress(indirectDhcpConnectMac);
775 etherReply.setVlanID(indirectDhcpConnectVlan.toShort());
776 ipv4Packet.setDestinationAddress(indirectDhcpServerIp.toInt());
Yi Tseng51301292017-07-28 13:02:59 -0700777
Charles Chanb5d24822017-10-10 16:53:32 -0400778 // Set giaddr if indirect relay agent IP is configured
779 if (indirectRelayAgentIp != null) {
780 dhcpPacket.setGatewayIPAddress(indirectRelayAgentIp.toInt());
781 }
782 } else {
783 // Otherwise, use default server config for indirect packets
784 etherReply.setDestinationMACAddress(dhcpConnectMac);
785 etherReply.setVlanID(dhcpConnectVlan.toShort());
786 ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
Yi Tseng4ec727d2017-08-31 11:21:00 -0700787
Charles Chanb5d24822017-10-10 16:53:32 -0400788 // Set giaddr if direct relay agent IP is configured
789 if (relayAgentIp != null) {
790 dhcpPacket.setGatewayIPAddress(relayAgentIp.toInt());
791 }
Yi Tseng4ec727d2017-08-31 11:21:00 -0700792 }
Yi Tseng4fa05832017-08-17 13:08:31 -0700793 }
794
Yi Tseng51301292017-07-28 13:02:59 -0700795 udpPacket.setPayload(dhcpPacket);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700796 // As a DHCP relay, the source port should be server port( instead
797 // of client port.
Yi Tsengdcef2c22017-08-05 20:34:06 -0700798 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
Yi Tseng51301292017-07-28 13:02:59 -0700799 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
800 ipv4Packet.setPayload(udpPacket);
Charles Chan7edf7642017-10-09 11:07:25 -0400801 ipv4Packet.setTtl((byte) 64);
Yi Tseng51301292017-07-28 13:02:59 -0700802 etherReply.setPayload(ipv4Packet);
803 return etherReply;
804 }
805
Charles Chan2f9aa2c2017-10-08 23:53:36 -0400806
807 /**
808 * Do a basic routing for a packet from client (used for LQ processing).
809 *
810 * @param context the packet context
811 * @param ethernetPacket the ethernet payload to process
812 * @return processed packet
813 */
814 private Ethernet processLeaseQueryFromAgent(PacketContext context,
815 Ethernet ethernetPacket) {
816 // get dhcp header.
817 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
818 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
819 UDP udpPacket = (UDP) ipv4Packet.getPayload();
820 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
821
822 VlanId dhcpConnectVlan = null;
823 MacAddress dhcpConnectMac = null;
824 Ip4Address dhcpServerIp = null;
825
826 VlanId indirectDhcpConnectVlan = null;
827 MacAddress indirectDhcpConnectMac = null;
828 Ip4Address indirectDhcpServerIp = null;
829
830 if (!defaultServerInfoList.isEmpty()) {
831 DhcpServerInfo serverInfo = defaultServerInfoList.get(0);
832 dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
833 dhcpConnectMac = serverInfo.getDhcpConnectMac().orElse(null);
834 dhcpServerIp = serverInfo.getDhcpServerIp4().orElse(null);
835 }
836
837 if (!indirectServerInfoList.isEmpty()) {
838 DhcpServerInfo indirectServerInfo = indirectServerInfoList.get(0);
839 indirectDhcpConnectVlan = indirectServerInfo.getDhcpConnectVlan().orElse(null);
840 indirectDhcpConnectMac = indirectServerInfo.getDhcpConnectMac().orElse(null);
841 indirectDhcpServerIp = indirectServerInfo.getDhcpServerIp4().orElse(null);
842 }
843
844 Ip4Address clientInterfaceIp =
845 interfaceService.getInterfacesByPort(context.inPacket().receivedFrom())
846 .stream()
847 .map(Interface::ipAddressesList)
848 .flatMap(Collection::stream)
849 .map(InterfaceIpAddress::ipAddress)
850 .filter(IpAddress::isIp4)
851 .map(IpAddress::getIp4Address)
852 .findFirst()
853 .orElse(null);
854 if (clientInterfaceIp == null) {
855 log.warn("Can't find interface IP for client interface for port {}",
856 context.inPacket().receivedFrom());
857 return null;
858 }
859 boolean isDirectlyConnected = directlyConnected(dhcpPacket);
Yi Tsengdc510082017-11-29 14:39:18 -0800860 Interface serverInterface;
861 if (isDirectlyConnected) {
862 serverInterface = getDefaultServerInterface();
863 } else {
864 serverInterface = getIndirectServerInterface();
865 if (serverInterface == null) {
866 // Indirect server interface not found, use default server interface
867 serverInterface = getDefaultServerInterface();
868 }
869 }
Charles Chan2f9aa2c2017-10-08 23:53:36 -0400870 if (serverInterface == null) {
871 log.warn("Can't get {} server interface, ignore", isDirectlyConnected ? "direct" : "indirect");
872 return null;
873 }
874 Ip4Address ipFacingServer = getFirstIpFromInterface(serverInterface);
875 MacAddress macFacingServer = serverInterface.mac();
876 if (ipFacingServer == null || macFacingServer == null) {
877 log.warn("No IP address for server Interface {}", serverInterface);
878 return null;
879 }
880 if (dhcpConnectMac == null) {
881 log.warn("DHCP server/gateway not yet resolved .. Aborting DHCP "
882 + "packet processing from client on port: {}",
883 context.inPacket().receivedFrom());
884 return null;
885 }
886
887 etherReply.setSourceMACAddress(macFacingServer);
888 etherReply.setDestinationMACAddress(dhcpConnectMac);
889 etherReply.setVlanID(dhcpConnectVlan.toShort());
890 ipv4Packet.setSourceAddress(ipFacingServer.toInt());
891 ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
892
893 if (indirectDhcpServerIp != null) {
894 // Indirect case, replace destination to indirect dhcp server if exist
895 etherReply.setDestinationMACAddress(indirectDhcpConnectMac);
896 etherReply.setVlanID(indirectDhcpConnectVlan.toShort());
897 ipv4Packet.setDestinationAddress(indirectDhcpServerIp.toInt());
898 }
899
900 udpPacket.setPayload(dhcpPacket);
901 // As a DHCP relay, the source port should be server port( instead
902 // of client port.
903 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
904 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
905 ipv4Packet.setPayload(udpPacket);
906 etherReply.setPayload(ipv4Packet);
907 return etherReply;
908 }
909
910
Yi Tseng51301292017-07-28 13:02:59 -0700911 /**
912 * Writes DHCP record to the store according to the request DHCP packet (Discover, Request).
913 *
914 * @param location the location which DHCP packet comes from
915 * @param ethernet the DHCP packet
916 * @param dhcpPayload the DHCP payload
917 */
918 private void writeRequestDhcpRecord(ConnectPoint location,
919 Ethernet ethernet,
920 DHCP dhcpPayload) {
921 VlanId vlanId = VlanId.vlanId(ethernet.getVlanID());
922 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
923 HostId hostId = HostId.hostId(macAddress, vlanId);
924 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
925 if (record == null) {
926 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
927 } else {
928 record = record.clone();
929 }
930 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
931 record.ip4Status(dhcpPayload.getPacketType());
932 record.setDirectlyConnected(directlyConnected(dhcpPayload));
933 if (!directlyConnected(dhcpPayload)) {
934 // Update gateway mac address if the host is not directly connected
935 record.nextHop(ethernet.getSourceMAC());
936 }
937 record.updateLastSeen();
938 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
939 }
940
941 /**
942 * Writes DHCP record to the store according to the response DHCP packet (Offer, Ack).
943 *
944 * @param ethernet the DHCP packet
945 * @param dhcpPayload the DHCP payload
946 */
947 private void writeResponseDhcpRecord(Ethernet ethernet,
948 DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700949 Optional<Interface> outInterface = getClientInterface(ethernet, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700950 if (!outInterface.isPresent()) {
951 log.warn("Failed to determine where to send {}", dhcpPayload.getPacketType());
952 return;
953 }
954
955 Interface outIface = outInterface.get();
956 ConnectPoint location = outIface.connectPoint();
Yi Tseng4ec727d2017-08-31 11:21:00 -0700957 VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700958 if (vlanId == null) {
959 vlanId = outIface.vlan();
960 }
Yi Tseng51301292017-07-28 13:02:59 -0700961 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
962 HostId hostId = HostId.hostId(macAddress, vlanId);
963 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
964 if (record == null) {
965 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
966 } else {
967 record = record.clone();
968 }
969 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
970 if (dhcpPayload.getPacketType() == DHCP.MsgType.DHCPACK) {
971 record.ip4Address(Ip4Address.valueOf(dhcpPayload.getYourIPAddress()));
972 }
973 record.ip4Status(dhcpPayload.getPacketType());
974 record.setDirectlyConnected(directlyConnected(dhcpPayload));
975 record.updateLastSeen();
976 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
977 }
978
979 /**
980 * Build the DHCP offer/ack with proper client port.
981 *
982 * @param ethernetPacket the original packet comes from server
983 * @return new packet which will send to the client
984 */
985 private Ethernet processDhcpPacketFromServer(Ethernet ethernetPacket) {
986 // get dhcp header.
987 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
988 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
989 UDP udpPacket = (UDP) ipv4Packet.getPayload();
990 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
991
992 // determine the vlanId of the client host - note that this vlan id
993 // could be different from the vlan in the packet from the server
Yi Tsengdcef2c22017-08-05 20:34:06 -0700994 Interface clientInterface = getClientInterface(ethernetPacket, dhcpPayload).orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -0700995
Yi Tsengdcef2c22017-08-05 20:34:06 -0700996 if (clientInterface == null) {
Yi Tseng51301292017-07-28 13:02:59 -0700997 log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
998 return null;
999 }
Yi Tsengdcef2c22017-08-05 20:34:06 -07001000 VlanId vlanId;
1001 if (clientInterface.vlanTagged().isEmpty()) {
1002 vlanId = clientInterface.vlan();
1003 } else {
1004 // might be multiple vlan in same interface
Yi Tseng4ec727d2017-08-31 11:21:00 -07001005 vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001006 }
1007 if (vlanId == null) {
1008 vlanId = VlanId.NONE;
1009 }
1010 etherReply.setVlanID(vlanId.toShort());
1011 etherReply.setSourceMACAddress(clientInterface.mac());
Yi Tseng51301292017-07-28 13:02:59 -07001012
Yi Tsengdcef2c22017-08-05 20:34:06 -07001013 if (!directlyConnected(dhcpPayload)) {
1014 // if client is indirectly connected, try use next hop mac address
1015 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1016 HostId hostId = HostId.hostId(macAddress, vlanId);
Daniel Ginsburgb63dccc2018-01-29 11:40:22 -08001017 if (((int) dhcpPayload.getFlags() & 0x8000) == 0x0000) {
1018 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1019 if (record != null) {
1020 // if next hop can be found, use mac address of next hop
1021 record.nextHop().ifPresent(etherReply::setDestinationMACAddress);
1022 } else {
1023 // otherwise, discard the packet
1024 log.warn("Can't find record for host id {}, discard packet", hostId);
1025 return null;
1026 }
Yi Tsengdcef2c22017-08-05 20:34:06 -07001027 } else {
Daniel Ginsburgb63dccc2018-01-29 11:40:22 -08001028 etherReply.setDestinationMACAddress(MacAddress.BROADCAST);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001029 }
Yi Tsengc03fa242017-08-17 17:43:38 -07001030 } else {
1031 etherReply.setDestinationMACAddress(dhcpPayload.getClientHardwareAddress());
Yi Tsengdcef2c22017-08-05 20:34:06 -07001032 }
1033
Yi Tseng51301292017-07-28 13:02:59 -07001034 // we leave the srcMac from the original packet
Yi Tseng51301292017-07-28 13:02:59 -07001035 // figure out the relay agent IP corresponding to the original request
Yi Tseng4fa05832017-08-17 13:08:31 -07001036 Ip4Address ipFacingClient = getFirstIpFromInterface(clientInterface);
1037 if (ipFacingClient == null) {
Yi Tseng51301292017-07-28 13:02:59 -07001038 log.warn("Cannot determine relay agent interface Ipv4 addr for host {}/{}. "
1039 + "Aborting relay for dhcp packet from server {}",
Yi Tsengdcef2c22017-08-05 20:34:06 -07001040 etherReply.getDestinationMAC(), clientInterface.vlan(),
Yi Tseng51301292017-07-28 13:02:59 -07001041 ethernetPacket);
1042 return null;
1043 }
1044 // SRC_IP: relay agent IP
1045 // DST_IP: offered IP
Yi Tseng4fa05832017-08-17 13:08:31 -07001046 ipv4Packet.setSourceAddress(ipFacingClient.toInt());
Daniel Ginsburgb63dccc2018-01-29 11:40:22 -08001047 if (((int) dhcpPayload.getFlags() & 0x8000) == 0x0000) {
1048 ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
1049 } else {
1050 ipv4Packet.setDestinationAddress(BROADCAST_IP);
1051 }
Yi Tseng51301292017-07-28 13:02:59 -07001052 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
1053 if (directlyConnected(dhcpPayload)) {
1054 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
1055 } else {
1056 // forward to another dhcp relay
Yi Tseng72b599a2017-09-14 13:24:21 -07001057 // FIXME: Currently we assume the DHCP comes from a L2 relay with
1058 // Option 82, this might not work if DHCP message comes from
1059 // L3 relay.
1060 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
Yi Tseng51301292017-07-28 13:02:59 -07001061 }
1062
1063 udpPacket.setPayload(dhcpPayload);
1064 ipv4Packet.setPayload(udpPacket);
1065 etherReply.setPayload(ipv4Packet);
1066 return etherReply;
1067 }
1068
Yi Tsengdcef2c22017-08-05 20:34:06 -07001069 /**
Charles Chan2f9aa2c2017-10-08 23:53:36 -04001070 * Build the DHCP offer/ack with proper client port.
1071 *
1072 * @param ethernetPacket the original packet comes from server
1073 * @return new packet which will send to the client
1074 */
1075 private Ethernet processLeaseQueryFromServer(Ethernet ethernetPacket) {
1076 // get dhcp header.
1077 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
1078 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
1079 UDP udpPacket = (UDP) ipv4Packet.getPayload();
1080 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
1081
1082 // determine the vlanId of the client host - note that this vlan id
1083 // could be different from the vlan in the packet from the server
1084 Interface clientInterface = getClientInterface(ethernetPacket, dhcpPayload).orElse(null);
1085
1086 if (clientInterface == null) {
1087 log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
1088 return null;
1089 }
1090 VlanId vlanId;
1091 if (clientInterface.vlanTagged().isEmpty()) {
1092 vlanId = clientInterface.vlan();
1093 } else {
1094 // might be multiple vlan in same interface
1095 vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
1096 }
1097 if (vlanId == null) {
1098 vlanId = VlanId.NONE;
1099 }
1100 etherReply.setVlanID(vlanId.toShort());
1101 etherReply.setSourceMACAddress(clientInterface.mac());
1102
1103 if (!directlyConnected(dhcpPayload)) {
1104 // if client is indirectly connected, try use next hop mac address
1105 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1106 HostId hostId = HostId.hostId(macAddress, vlanId);
1107 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1108 if (record != null) {
1109 // if next hop can be found, use mac address of next hop
1110 record.nextHop().ifPresent(etherReply::setDestinationMACAddress);
1111 } else {
1112 // otherwise, discard the packet
1113 log.warn("Can't find record for host id {}, discard packet", hostId);
1114 return null;
1115 }
1116 } else {
1117 etherReply.setDestinationMACAddress(dhcpPayload.getClientHardwareAddress());
1118 }
1119
1120 // default is client port
1121 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
1122 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
1123
1124 udpPacket.setPayload(dhcpPayload);
1125 ipv4Packet.setPayload(udpPacket);
1126 etherReply.setPayload(ipv4Packet);
1127 return etherReply;
1128 }
1129 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -07001130 * Extracts VLAN ID from relay agent option.
1131 *
1132 * @param dhcpPayload the DHCP payload
1133 * @return VLAN ID from DHCP payload; null if not exists
1134 */
Yi Tseng4ec727d2017-08-31 11:21:00 -07001135 private VlanId getVlanIdFromRelayAgentOption(DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001136 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
1137 if (option == null) {
1138 return null;
1139 }
1140 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
1141 if (circuitIdSubOption == null) {
1142 return null;
1143 }
1144 try {
1145 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
1146 return circuitId.vlanId();
1147 } catch (IllegalArgumentException e) {
1148 // can't deserialize the circuit ID
1149 return null;
1150 }
1151 }
1152
1153 /**
1154 * Removes DHCP relay agent information option (option 82) from DHCP payload.
1155 * Also reset giaddr to 0
1156 *
1157 * @param ethPacket the Ethernet packet to be processed
1158 * @return Ethernet packet processed
1159 */
1160 private Ethernet removeRelayAgentOption(Ethernet ethPacket) {
1161 Ethernet ethernet = (Ethernet) ethPacket.clone();
1162 IPv4 ipv4 = (IPv4) ethernet.getPayload();
1163 UDP udp = (UDP) ipv4.getPayload();
1164 DHCP dhcpPayload = (DHCP) udp.getPayload();
1165
1166 // removes relay agent information option
1167 List<DhcpOption> options = dhcpPayload.getOptions();
1168 options = options.stream()
1169 .filter(option -> option.getCode() != OptionCode_CircuitID.getValue())
1170 .collect(Collectors.toList());
1171 dhcpPayload.setOptions(options);
1172 dhcpPayload.setGatewayIPAddress(0);
1173
1174 udp.setPayload(dhcpPayload);
1175 ipv4.setPayload(udp);
1176 ethernet.setPayload(ipv4);
1177 return ethernet;
1178 }
1179
Yi Tseng51301292017-07-28 13:02:59 -07001180
1181 /**
1182 * Check if the host is directly connected to the network or not.
1183 *
1184 * @param dhcpPayload the dhcp payload
1185 * @return true if the host is directly connected to the network; false otherwise
1186 */
1187 private boolean directlyConnected(DHCP dhcpPayload) {
Yi Tseng2cf59912017-08-24 14:47:34 -07001188 DhcpRelayAgentOption relayAgentOption =
1189 (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
Yi Tseng51301292017-07-28 13:02:59 -07001190
1191 // Doesn't contains relay option
1192 if (relayAgentOption == null) {
1193 return true;
1194 }
1195
Yi Tseng2cf59912017-08-24 14:47:34 -07001196 // check circuit id, if circuit id is invalid, we say it is an indirect host
1197 DhcpOption circuitIdOpt = relayAgentOption.getSubOption(CIRCUIT_ID.getValue());
Yi Tseng51301292017-07-28 13:02:59 -07001198
Yi Tseng2cf59912017-08-24 14:47:34 -07001199 try {
1200 CircuitId.deserialize(circuitIdOpt.getData());
Yi Tseng51301292017-07-28 13:02:59 -07001201 return true;
Yi Tseng2cf59912017-08-24 14:47:34 -07001202 } catch (Exception e) {
1203 // invalid circuit id
1204 return false;
Yi Tseng51301292017-07-28 13:02:59 -07001205 }
Yi Tseng51301292017-07-28 13:02:59 -07001206 }
1207
1208
1209 /**
1210 * Send the DHCP ack to the requester host.
1211 * Modify Host or Route store according to the type of DHCP.
1212 *
1213 * @param ethernetPacketAck the packet
1214 * @param dhcpPayload the DHCP data
1215 */
1216 private void handleDhcpAck(Ethernet ethernetPacketAck, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001217 Optional<Interface> outInterface = getClientInterface(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -07001218 if (!outInterface.isPresent()) {
1219 log.warn("Can't find output interface for dhcp: {}", dhcpPayload);
1220 return;
1221 }
1222
1223 Interface outIface = outInterface.get();
1224 HostLocation hostLocation = new HostLocation(outIface.connectPoint(), System.currentTimeMillis());
1225 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
Yi Tseng4ec727d2017-08-31 11:21:00 -07001226 VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001227 if (vlanId == null) {
1228 vlanId = outIface.vlan();
1229 }
Yi Tseng51301292017-07-28 13:02:59 -07001230 HostId hostId = HostId.hostId(macAddress, vlanId);
1231 Ip4Address ip = Ip4Address.valueOf(dhcpPayload.getYourIPAddress());
1232
1233 if (directlyConnected(dhcpPayload)) {
1234 // Add to host store if it connect to network directly
1235 Set<IpAddress> ips = Sets.newHashSet(ip);
Yi Tseng4b013202017-09-08 17:22:51 -07001236 Host host = hostService.getHost(hostId);
Yi Tseng51301292017-07-28 13:02:59 -07001237
Yi Tseng4b013202017-09-08 17:22:51 -07001238 Set<HostLocation> hostLocations = Sets.newHashSet(hostLocation);
1239 if (host != null) {
1240 // Dual homing support:
1241 // if host exists, use old locations and new location
1242 hostLocations.addAll(host.locations());
1243 }
1244 HostDescription desc = new DefaultHostDescription(macAddress, vlanId,
1245 hostLocations, ips, false);
1246 // Add IP address when dhcp server give the host new ip address
1247 providerService.hostDetected(hostId, desc, false);
Yi Tseng51301292017-07-28 13:02:59 -07001248 } else {
1249 // Add to route store if it does not connect to network directly
1250 // Get gateway host IP according to host mac address
Yi Tsengdcef2c22017-08-05 20:34:06 -07001251 // TODO: remove relay store here
Yi Tseng51301292017-07-28 13:02:59 -07001252 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1253
1254 if (record == null) {
1255 log.warn("Can't find DHCP record of host {}", hostId);
1256 return;
1257 }
1258
1259 MacAddress gwMac = record.nextHop().orElse(null);
1260 if (gwMac == null) {
1261 log.warn("Can't find gateway mac address from record {}", record);
1262 return;
1263 }
1264
1265 HostId gwHostId = HostId.hostId(gwMac, record.vlanId());
1266 Host gwHost = hostService.getHost(gwHostId);
1267
1268 if (gwHost == null) {
1269 log.warn("Can't find gateway host {}", gwHostId);
1270 return;
1271 }
1272
1273 Ip4Address nextHopIp = gwHost.ipAddresses()
1274 .stream()
1275 .filter(IpAddress::isIp4)
1276 .map(IpAddress::getIp4Address)
1277 .findFirst()
1278 .orElse(null);
1279
1280 if (nextHopIp == null) {
1281 log.warn("Can't find IP address of gateway {}", gwHost);
1282 return;
1283 }
1284
1285 Route route = new Route(Route.Source.STATIC, ip.toIpPrefix(), nextHopIp);
1286 routeStore.updateRoute(route);
1287 }
Yi Tseng51301292017-07-28 13:02:59 -07001288 }
1289
1290 /**
1291 * forward the packet to ConnectPoint where the DHCP server is attached.
1292 *
1293 * @param packet the packet
1294 */
Yi Tseng4ec727d2017-08-31 11:21:00 -07001295 private void handleDhcpDiscoverAndRequest(Ethernet packet, DHCP dhcpPayload) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001296 boolean direct = directlyConnected(dhcpPayload);
1297 DhcpServerInfo serverInfo = defaultServerInfoList.get(0);
1298 if (!direct && !indirectServerInfoList.isEmpty()) {
1299 serverInfo = indirectServerInfoList.get(0);
Yi Tseng4ec727d2017-08-31 11:21:00 -07001300 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001301 ConnectPoint portToFotward = serverInfo.getDhcpServerConnectPoint().orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -07001302 // send packet to dhcp server connect point.
Yi Tseng4ec727d2017-08-31 11:21:00 -07001303 if (portToFotward != null) {
Yi Tseng51301292017-07-28 13:02:59 -07001304 TrafficTreatment t = DefaultTrafficTreatment.builder()
Yi Tseng4ec727d2017-08-31 11:21:00 -07001305 .setOutput(portToFotward.port()).build();
Yi Tseng51301292017-07-28 13:02:59 -07001306 OutboundPacket o = new DefaultOutboundPacket(
Yi Tseng4ec727d2017-08-31 11:21:00 -07001307 portToFotward.deviceId(), t, ByteBuffer.wrap(packet.serialize()));
Yi Tseng51301292017-07-28 13:02:59 -07001308 if (log.isTraceEnabled()) {
1309 log.trace("Relaying packet to dhcp server {}", packet);
1310 }
1311 packetService.emit(o);
1312 } else {
1313 log.warn("Can't find DHCP server connect point, abort.");
1314 }
1315 }
1316
1317
1318 /**
1319 * Gets output interface of a dhcp packet.
1320 * If option 82 exists in the dhcp packet and the option was sent by
Yi Tseng4ec727d2017-08-31 11:21:00 -07001321 * ONOS (circuit format is correct), use the connect
Yi Tseng51301292017-07-28 13:02:59 -07001322 * point and vlan id from circuit id; otherwise, find host by destination
1323 * address and use vlan id from sender (dhcp server).
1324 *
1325 * @param ethPacket the ethernet packet
1326 * @param dhcpPayload the dhcp packet
1327 * @return an interface represent the output port and vlan; empty value
1328 * if the host or circuit id not found
1329 */
Yi Tsengdcef2c22017-08-05 20:34:06 -07001330 private Optional<Interface> getClientInterface(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tseng51301292017-07-28 13:02:59 -07001331 VlanId originalPacketVlanId = VlanId.vlanId(ethPacket.getVlanID());
Yi Tseng51301292017-07-28 13:02:59 -07001332 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
1333
Yi Tseng4ec727d2017-08-31 11:21:00 -07001334 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
1335 try {
1336 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
1337 ConnectPoint connectPoint = ConnectPoint.deviceConnectPoint(circuitId.connectPoint());
1338 VlanId vlanId = circuitId.vlanId();
1339 return interfaceService.getInterfacesByPort(connectPoint)
1340 .stream()
1341 .filter(iface -> interfaceContainsVlan(iface, vlanId))
1342 .findFirst();
1343 } catch (IllegalArgumentException ex) {
1344 // invalid circuit format, didn't sent by ONOS
1345 log.debug("Invalid circuit {}, use information from dhcp payload",
1346 circuitIdSubOption.getData());
Yi Tseng51301292017-07-28 13:02:59 -07001347 }
1348
1349 // Use Vlan Id from DHCP server if DHCP relay circuit id was not
1350 // sent by ONOS or circuit Id can't be parsed
Yi Tsengdcef2c22017-08-05 20:34:06 -07001351 // TODO: remove relay store from this method
Yi Tseng51301292017-07-28 13:02:59 -07001352 MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
1353 Optional<DhcpRecord> dhcpRecord = dhcpRelayStore.getDhcpRecord(HostId.hostId(dstMac, originalPacketVlanId));
Yi Tsengdcef2c22017-08-05 20:34:06 -07001354 ConnectPoint clientConnectPoint = dhcpRecord
Yi Tseng51301292017-07-28 13:02:59 -07001355 .map(DhcpRecord::locations)
1356 .orElse(Collections.emptySet())
1357 .stream()
1358 .reduce((hl1, hl2) -> {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001359 // find latest host connect point
Yi Tseng51301292017-07-28 13:02:59 -07001360 if (hl1 == null || hl2 == null) {
1361 return hl1 == null ? hl2 : hl1;
1362 }
1363 return hl1.time() > hl2.time() ? hl1 : hl2;
1364 })
Yi Tsengdcef2c22017-08-05 20:34:06 -07001365 .orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -07001366
Yi Tsengdcef2c22017-08-05 20:34:06 -07001367 if (clientConnectPoint != null) {
1368 return interfaceService.getInterfacesByPort(clientConnectPoint)
1369 .stream()
Yi Tseng4ec727d2017-08-31 11:21:00 -07001370 .filter(iface -> interfaceContainsVlan(iface, originalPacketVlanId))
Yi Tsengdcef2c22017-08-05 20:34:06 -07001371 .findFirst();
1372 }
1373 return Optional.empty();
Yi Tseng51301292017-07-28 13:02:59 -07001374 }
1375
1376 /**
1377 * Send the response DHCP to the requester host.
1378 *
1379 * @param ethPacket the packet
1380 * @param dhcpPayload the DHCP data
1381 */
1382 private void sendResponseToClient(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001383 Optional<Interface> outInterface = getClientInterface(ethPacket, dhcpPayload);
1384 if (directlyConnected(dhcpPayload)) {
1385 ethPacket = removeRelayAgentOption(ethPacket);
1386 }
1387 if (!outInterface.isPresent()) {
1388 log.warn("Can't find output interface for client, ignore");
1389 return;
1390 }
1391 Interface outIface = outInterface.get();
1392 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
1393 .setOutput(outIface.connectPoint().port())
1394 .build();
1395 OutboundPacket o = new DefaultOutboundPacket(
1396 outIface.connectPoint().deviceId(),
1397 treatment,
1398 ByteBuffer.wrap(ethPacket.serialize()));
1399 if (log.isTraceEnabled()) {
1400 log.trace("Relaying packet to DHCP client {} via {}, vlan {}",
1401 ethPacket,
1402 outIface.connectPoint(),
1403 outIface.vlan());
1404 }
1405 packetService.emit(o);
Yi Tseng51301292017-07-28 13:02:59 -07001406 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001407
Yi Tseng4b013202017-09-08 17:22:51 -07001408 @Override
1409 public void triggerProbe(Host host) {
1410 // Do nothing here
1411 }
1412
1413 @Override
1414 public ProviderId id() {
Charles Chan75edab72017-09-12 17:09:32 -07001415 return PROVIDER_ID;
Yi Tseng4b013202017-09-08 17:22:51 -07001416 }
1417
Yi Tsenge72fbb52017-08-02 15:03:31 -07001418 class InternalHostListener implements HostListener {
1419 @Override
1420 public void event(HostEvent event) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001421 if (!configured()) {
1422 return;
1423 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001424 switch (event.type()) {
1425 case HOST_ADDED:
1426 case HOST_UPDATED:
1427 hostUpdated(event.subject());
1428 break;
1429 case HOST_REMOVED:
1430 hostRemoved(event.subject());
1431 break;
Yi Tsenge72fbb52017-08-02 15:03:31 -07001432 default:
1433 break;
1434 }
1435 }
1436 }
1437
1438 /**
Yi Tsenge72fbb52017-08-02 15:03:31 -07001439 * Handle host updated.
1440 * If the host is DHCP server or gateway, update connect mac and vlan.
1441 *
1442 * @param host the host
1443 */
1444 private void hostUpdated(Host host) {
Yi Tseng7da339e2017-10-23 19:39:39 -07001445 hostUpdated(host, defaultServerInfoList);
1446 hostUpdated(host, indirectServerInfoList);
Yi Tsenge72fbb52017-08-02 15:03:31 -07001447 }
1448
Yi Tseng7da339e2017-10-23 19:39:39 -07001449 private void hostUpdated(Host host, List<DhcpServerInfo> srverInfoList) {
1450 DhcpServerInfo serverInfo;
1451 Ip4Address targetIp;
1452 if (!srverInfoList.isEmpty()) {
1453 serverInfo = srverInfoList.get(0);
1454 targetIp = serverInfo.getDhcpGatewayIp4().orElse(null);
1455 Ip4Address serverIp = serverInfo.getDhcpServerIp4().orElse(null);
1456
1457 if (targetIp == null) {
1458 targetIp = serverIp;
1459 }
1460
1461 if (targetIp != null) {
1462 if (host.ipAddresses().contains(targetIp)) {
1463 serverInfo.setDhcpConnectMac(host.mac());
1464 serverInfo.setDhcpConnectVlan(host.vlan());
1465 requestDhcpPacket(serverIp);
1466 }
1467 }
1468 }
1469 }
1470
1471
Yi Tsenge72fbb52017-08-02 15:03:31 -07001472 /**
1473 * Handle host removed.
1474 * If the host is DHCP server or gateway, unset connect mac and vlan.
1475 *
1476 * @param host the host
1477 */
1478 private void hostRemoved(Host host) {
Yi Tseng7da339e2017-10-23 19:39:39 -07001479 hostRemoved(host, defaultServerInfoList);
1480 hostRemoved(host, indirectServerInfoList);
1481 }
1482
1483 private void hostRemoved(Host host, List<DhcpServerInfo> serverInfoList) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001484 DhcpServerInfo serverInfo;
Yi Tseng7da339e2017-10-23 19:39:39 -07001485 Ip4Address targetIp;
1486 if (!serverInfoList.isEmpty()) {
1487 serverInfo = serverInfoList.get(0);
1488 Ip4Address serverIp = serverInfo.getDhcpServerIp4().orElse(null);
1489 targetIp = serverInfo.getDhcpGatewayIp4().orElse(null);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001490
Yi Tseng7da339e2017-10-23 19:39:39 -07001491 if (targetIp == null) {
1492 targetIp = serverIp;
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001493 }
Yi Tseng7da339e2017-10-23 19:39:39 -07001494
1495 if (targetIp != null) {
1496 if (host.ipAddresses().contains(targetIp)) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001497 serverInfo.setDhcpConnectVlan(null);
1498 serverInfo.setDhcpConnectMac(null);
Yi Tseng7da339e2017-10-23 19:39:39 -07001499 cancelDhcpPacket(serverIp);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001500 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001501 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001502 }
Yi Tseng7da339e2017-10-23 19:39:39 -07001503 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001504
Yi Tseng7da339e2017-10-23 19:39:39 -07001505 private void requestDhcpPacket(Ip4Address serverIp) {
1506 requestServerDhcpPacket(serverIp);
1507 requestClientDhcpPacket(serverIp);
1508 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001509
Yi Tseng7da339e2017-10-23 19:39:39 -07001510 private void cancelDhcpPacket(Ip4Address serverIp) {
1511 cancelServerDhcpPacket(serverIp);
1512 cancelClientDhcpPacket(serverIp);
1513 }
1514
1515 private void cancelServerDhcpPacket(Ip4Address serverIp) {
1516 TrafficSelector serverSelector =
1517 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1518 .matchIPSrc(serverIp.toIpPrefix())
1519 .build();
1520 packetService.cancelPackets(serverSelector,
1521 PacketPriority.CONTROL,
1522 appId);
1523 }
1524
1525 private void requestServerDhcpPacket(Ip4Address serverIp) {
1526 TrafficSelector serverSelector =
1527 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1528 .matchIPSrc(serverIp.toIpPrefix())
1529 .build();
1530 packetService.requestPackets(serverSelector,
1531 PacketPriority.CONTROL,
1532 appId);
1533 }
1534
1535 private void cancelClientDhcpPacket(Ip4Address serverIp) {
1536 // Packet comes from relay
1537 TrafficSelector indirectClientSelector =
1538 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1539 .matchIPDst(serverIp.toIpPrefix())
1540 .build();
1541 packetService.cancelPackets(indirectClientSelector,
1542 PacketPriority.CONTROL,
1543 appId);
1544
1545 // Packet comes from client
1546 packetService.cancelPackets(CLIENT_SERVER_SELECTOR,
1547 PacketPriority.CONTROL,
1548 appId);
1549 }
1550
1551 private void requestClientDhcpPacket(Ip4Address serverIp) {
1552 // Packet comes from relay
1553 TrafficSelector indirectClientSelector =
1554 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1555 .matchIPDst(serverIp.toIpPrefix())
1556 .build();
1557 packetService.requestPackets(indirectClientSelector,
1558 PacketPriority.CONTROL,
1559 appId);
1560
1561 // Packet comes from client
1562 packetService.requestPackets(CLIENT_SERVER_SELECTOR,
1563 PacketPriority.CONTROL,
1564 appId);
1565 }
1566
1567 /**
1568 * Process the ignore rules.
1569 *
1570 * @param deviceId the device id
1571 * @param vlanId the vlan to be ignored
1572 * @param op the operation, ADD to install; REMOVE to uninstall rules
1573 */
1574 private void processIgnoreVlanRule(DeviceId deviceId, VlanId vlanId, Objective.Operation op) {
Yi Tseng7da339e2017-10-23 19:39:39 -07001575 AtomicInteger installedCount = new AtomicInteger(DHCP_SELECTORS.size());
1576 DHCP_SELECTORS.forEach(trafficSelector -> {
1577 TrafficSelector selector = DefaultTrafficSelector.builder(trafficSelector)
1578 .matchVlanId(vlanId)
1579 .build();
1580
1581 ForwardingObjective.Builder builder = DefaultForwardingObjective.builder()
1582 .withFlag(ForwardingObjective.Flag.VERSATILE)
1583 .withSelector(selector)
1584 .withPriority(IGNORE_CONTROL_PRIORITY)
Yi Tsengf29ffd72017-11-29 10:49:20 -08001585 .withTreatment(DefaultTrafficTreatment.emptyTreatment())
Yi Tseng7da339e2017-10-23 19:39:39 -07001586 .fromApp(appId);
1587
1588
1589 ObjectiveContext objectiveContext = new ObjectiveContext() {
1590 @Override
1591 public void onSuccess(Objective objective) {
1592 log.info("Ignore rule {} (Vlan id {}, device {}, selector {})",
1593 op, vlanId, deviceId, selector);
1594 int countDown = installedCount.decrementAndGet();
1595 if (countDown != 0) {
1596 return;
1597 }
1598 switch (op) {
1599 case ADD:
1600 ignoredVlans.put(deviceId, vlanId);
1601 break;
1602 case REMOVE:
1603 ignoredVlans.remove(deviceId, vlanId);
1604 break;
1605 default:
1606 log.warn("Unsupported objective operation {}", op);
1607 break;
1608 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001609 }
Yi Tseng7da339e2017-10-23 19:39:39 -07001610
1611 @Override
1612 public void onError(Objective objective, ObjectiveError error) {
1613 log.warn("Can't {} ignore rule (vlan id {}, selector {}, device {}) due to {}",
1614 op, vlanId, selector, deviceId, error);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001615 }
Yi Tseng7da339e2017-10-23 19:39:39 -07001616 };
1617
1618 ForwardingObjective fwd;
1619 switch (op) {
1620 case ADD:
1621 fwd = builder.add(objectiveContext);
1622 break;
1623 case REMOVE:
1624 fwd = builder.remove(objectiveContext);
1625 break;
1626 default:
1627 log.warn("Unsupported objective operation {}", op);
1628 return;
Yi Tseng4ec727d2017-08-31 11:21:00 -07001629 }
Yi Tseng7da339e2017-10-23 19:39:39 -07001630
1631 Device device = deviceService.getDevice(deviceId);
1632 if (device == null || !device.is(Pipeliner.class)) {
1633 log.warn("Device {} is not available now, wait until device is available", deviceId);
1634 return;
1635 }
1636 flowObjectiveService.apply(deviceId, fwd);
1637 });
Yi Tsenge72fbb52017-08-02 15:03:31 -07001638 }
Yi Tseng51301292017-07-28 13:02:59 -07001639}