blob: 2c8cf92b0a42ab04b60bae0cb19a155d15c34f50 [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
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700246 public void setDhcpServerConfigs(Collection<DhcpServerConfig> configs, List<DhcpServerInfo> serverInfoList) {
Yi Tsenge72fbb52017-08-02 15:03:31 -0700247 if (configs.size() == 0) {
248 // no config to update
249 return;
250 }
251
252 // TODO: currently we pick up first DHCP server config.
253 // Will use other server configs in the future for HA.
254 DhcpServerConfig serverConfig = configs.iterator().next();
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700255
Yi Tsengaefbb002017-09-08 16:23:32 -0700256 if (!serverConfig.getDhcpServerIp4().isPresent()) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700257 // not a DHCPv4 config
Yi Tsenge72fbb52017-08-02 15:03:31 -0700258 return;
259 }
260
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700261 if (!serverInfoList.isEmpty()) {
262 // remove old server info
263 DhcpServerInfo oldServerInfo = serverInfoList.remove(0);
Yi Tsenge72fbb52017-08-02 15:03:31 -0700264
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700265 // stop monitoring gateway or server
266 oldServerInfo.getDhcpGatewayIp4().ifPresent(gatewayIp -> {
267 hostService.stopMonitoringIp(gatewayIp);
268 });
269 oldServerInfo.getDhcpServerIp4().ifPresent(serverIp -> {
270 hostService.stopMonitoringIp(serverIp);
Yi Tseng7da339e2017-10-23 19:39:39 -0700271 cancelDhcpPacket(serverIp);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700272 });
Yi Tsenge72fbb52017-08-02 15:03:31 -0700273 }
Yi Tseng4fa05832017-08-17 13:08:31 -0700274
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700275 // Create new server info according to the config
276 DhcpServerInfo newServerInfo = new DhcpServerInfo(serverConfig,
277 DhcpServerInfo.Version.DHCP_V4);
278 checkState(newServerInfo.getDhcpServerConnectPoint().isPresent(),
Yi Tseng4ec727d2017-08-31 11:21:00 -0700279 "Connect point not exists");
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700280 checkState(newServerInfo.getDhcpServerIp4().isPresent(),
Yi Tseng4ec727d2017-08-31 11:21:00 -0700281 "IP of DHCP server not exists");
Yi Tseng4ec727d2017-08-31 11:21:00 -0700282
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700283 log.debug("DHCP server connect point: {}", newServerInfo.getDhcpServerConnectPoint().orElse(null));
284 log.debug("DHCP server IP: {}", newServerInfo.getDhcpServerIp4().orElse(null));
285
Yi Tseng7da339e2017-10-23 19:39:39 -0700286 Ip4Address serverIp = newServerInfo.getDhcpServerIp4().get();
287 Ip4Address ipToProbe;
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700288 if (newServerInfo.getDhcpGatewayIp4().isPresent()) {
289 ipToProbe = newServerInfo.getDhcpGatewayIp4().get();
290 } else {
291 ipToProbe = newServerInfo.getDhcpServerIp4().orElse(null);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700292 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700293 String hostToProbe = newServerInfo.getDhcpGatewayIp4()
294 .map(ip -> "gateway").orElse("server");
Yi Tseng4ec727d2017-08-31 11:21:00 -0700295
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700296 log.debug("Probing to resolve {} IP {}", hostToProbe, ipToProbe);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700297 hostService.startMonitoringIp(ipToProbe);
298
299 Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
300 if (!hosts.isEmpty()) {
301 Host host = hosts.iterator().next();
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700302 newServerInfo.setDhcpConnectVlan(host.vlan());
303 newServerInfo.setDhcpConnectMac(host.mac());
Yi Tseng4ec727d2017-08-31 11:21:00 -0700304 }
305
Yi Tseng053682b2017-11-09 13:54:12 -0800306 // Add new server info
307 synchronized (this) {
308 serverInfoList.clear();
309 serverInfoList.add(0, newServerInfo);
310 }
311
Yi Tseng7da339e2017-10-23 19:39:39 -0700312 requestDhcpPacket(serverIp);
Yi Tsenge72fbb52017-08-02 15:03:31 -0700313 }
314
Yi Tseng4fa05832017-08-17 13:08:31 -0700315 @Override
Yi Tseng51301292017-07-28 13:02:59 -0700316 public void processDhcpPacket(PacketContext context, BasePacket payload) {
317 checkNotNull(payload, "DHCP payload can't be null");
318 checkState(payload instanceof DHCP, "Payload is not a DHCP");
319 DHCP dhcpPayload = (DHCP) payload;
320 if (!configured()) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700321 log.warn("Missing default DHCP relay server config. Abort packet processing");
Yi Tseng51301292017-07-28 13:02:59 -0700322 return;
323 }
324
325 ConnectPoint inPort = context.inPacket().receivedFrom();
Yi Tseng51301292017-07-28 13:02:59 -0700326 checkNotNull(dhcpPayload, "Can't find DHCP payload");
327 Ethernet packet = context.inPacket().parsed();
328 DHCP.MsgType incomingPacketType = dhcpPayload.getOptions().stream()
329 .filter(dhcpOption -> dhcpOption.getCode() == OptionCode_MessageType.getValue())
330 .map(DhcpOption::getData)
331 .map(data -> DHCP.MsgType.getType(data[0]))
332 .findFirst()
333 .orElse(null);
334 checkNotNull(incomingPacketType, "Can't get message type from DHCP payload {}", dhcpPayload);
335 switch (incomingPacketType) {
336 case DHCPDISCOVER:
Yi Tsengdcef2c22017-08-05 20:34:06 -0700337 // Add the gateway IP as virtual interface IP for server to understand
Yi Tseng51301292017-07-28 13:02:59 -0700338 // the lease to be assigned and forward the packet to dhcp server.
339 Ethernet ethernetPacketDiscover =
Yi Tsengdcef2c22017-08-05 20:34:06 -0700340 processDhcpPacketFromClient(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700341 if (ethernetPacketDiscover != null) {
342 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700343 handleDhcpDiscoverAndRequest(ethernetPacketDiscover, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700344 }
345 break;
346 case DHCPOFFER:
347 //reply to dhcp client.
348 Ethernet ethernetPacketOffer = processDhcpPacketFromServer(packet);
349 if (ethernetPacketOffer != null) {
350 writeResponseDhcpRecord(ethernetPacketOffer, dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700351 sendResponseToClient(ethernetPacketOffer, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700352 }
353 break;
354 case DHCPREQUEST:
355 // add the gateway ip as virtual interface ip for server to understand
356 // the lease to be assigned and forward the packet to dhcp server.
357 Ethernet ethernetPacketRequest =
Yi Tsengdcef2c22017-08-05 20:34:06 -0700358 processDhcpPacketFromClient(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700359 if (ethernetPacketRequest != null) {
360 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700361 handleDhcpDiscoverAndRequest(ethernetPacketRequest, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700362 }
363 break;
Charles Chan2f9aa2c2017-10-08 23:53:36 -0400364 case DHCPDECLINE:
365 break;
Yi Tseng51301292017-07-28 13:02:59 -0700366 case DHCPACK:
367 // reply to dhcp client.
368 Ethernet ethernetPacketAck = processDhcpPacketFromServer(packet);
369 if (ethernetPacketAck != null) {
370 writeResponseDhcpRecord(ethernetPacketAck, dhcpPayload);
371 handleDhcpAck(ethernetPacketAck, dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700372 sendResponseToClient(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700373 }
374 break;
Charles Chan2f9aa2c2017-10-08 23:53:36 -0400375 case DHCPNAK:
376 break;
Yi Tseng51301292017-07-28 13:02:59 -0700377 case DHCPRELEASE:
378 // TODO: release the ip address from client
379 break;
Charles Chan2f9aa2c2017-10-08 23:53:36 -0400380 case DHCPINFORM:
381 break;
382 case DHCPFORCERENEW:
383 break;
384 case DHCPLEASEQUERY:
385 handleLeaseQueryMsg(context, packet, dhcpPayload);
386 break;
387 case DHCPLEASEACTIVE:
388 handleLeaseQueryActivateMsg(packet, dhcpPayload);
389 break;
390 case DHCPLEASEUNASSIGNED:
391 case DHCPLEASEUNKNOWN:
392 handleLeaseQueryUnknown(packet, dhcpPayload);
393 break;
Yi Tseng51301292017-07-28 13:02:59 -0700394 default:
395 break;
396 }
397 }
398
399 /**
400 * Checks if this app has been configured.
401 *
402 * @return true if all information we need have been initialized
403 */
Yi Tseng4ec727d2017-08-31 11:21:00 -0700404 private boolean configured() {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700405 return !defaultServerInfoList.isEmpty();
Yi Tseng51301292017-07-28 13:02:59 -0700406 }
407
408 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -0700409 * Returns the first interface ip from interface.
Yi Tseng51301292017-07-28 13:02:59 -0700410 *
Yi Tsengdcef2c22017-08-05 20:34:06 -0700411 * @param iface interface of one connect point
Yi Tseng51301292017-07-28 13:02:59 -0700412 * @return the first interface IP; null if not exists an IP address in
413 * these interfaces
414 */
Yi Tseng4fa05832017-08-17 13:08:31 -0700415 private Ip4Address getFirstIpFromInterface(Interface iface) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700416 checkNotNull(iface, "Interface can't be null");
417 return iface.ipAddressesList().stream()
Yi Tseng51301292017-07-28 13:02:59 -0700418 .map(InterfaceIpAddress::ipAddress)
419 .filter(IpAddress::isIp4)
420 .map(IpAddress::getIp4Address)
421 .findFirst()
422 .orElse(null);
423 }
424
425 /**
Yi Tseng4ec727d2017-08-31 11:21:00 -0700426 * Gets Interface facing to the server for default host.
Yi Tsengdcef2c22017-08-05 20:34:06 -0700427 *
428 * @return the Interface facing to the server; null if not found
429 */
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700430 private Interface getDefaultServerInterface() {
431 return getServerInterface(defaultServerInfoList);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700432 }
433
434 /**
Yi Tseng4ec727d2017-08-31 11:21:00 -0700435 * Gets Interface facing to the server for indirect hosts.
436 * Use default server Interface if indirect server not configured.
437 *
438 * @return the Interface facing to the server; null if not found
439 */
440 private Interface getIndirectServerInterface() {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700441 return getServerInterface(indirectServerInfoList);
442 }
443
444 private Interface getServerInterface(List<DhcpServerInfo> serverInfos) {
Yi Tsengdc510082017-11-29 14:39:18 -0800445 return serverInfos.stream()
Yi Tseng4ec727d2017-08-31 11:21:00 -0700446 .findFirst()
Yi Tsengdc510082017-11-29 14:39:18 -0800447 .map(serverInfo -> {
448 ConnectPoint dhcpServerConnectPoint =
449 serverInfo.getDhcpServerConnectPoint().orElse(null);
450 VlanId dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
451 if (dhcpServerConnectPoint == null || dhcpConnectVlan == null) {
452 return null;
453 }
454 return interfaceService.getInterfacesByPort(dhcpServerConnectPoint)
455 .stream()
456 .filter(iface -> interfaceContainsVlan(iface, dhcpConnectVlan))
457 .findFirst()
458 .orElse(null);
459 })
Yi Tseng4ec727d2017-08-31 11:21:00 -0700460 .orElse(null);
461 }
462
463 /**
464 * Determind if an Interface contains a vlan id.
465 *
466 * @param iface the Interface
467 * @param vlanId the vlan id
468 * @return true if the Interface contains the vlan id
469 */
470 private boolean interfaceContainsVlan(Interface iface, VlanId vlanId) {
Yi Tseng58e74312017-09-30 11:35:42 +0800471 if (vlanId.equals(VlanId.NONE)) {
472 // untagged packet, check if vlan untagged or vlan native is not NONE
473 return !iface.vlanUntagged().equals(VlanId.NONE) ||
474 !iface.vlanNative().equals(VlanId.NONE);
475 }
476 // tagged packet, check if the interface contains the vlan
477 return iface.vlanTagged().contains(vlanId);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700478 }
479
Charles Chan2f9aa2c2017-10-08 23:53:36 -0400480 private void handleLeaseQueryActivateMsg(Ethernet packet, DHCP dhcpPayload) {
481 log.debug("LQ: Got DHCPLEASEACTIVE packet!");
482
483 // TODO: release the ip address from client
484 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
485 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
486 HostId hostId = HostId.hostId(clientMacAddress, vlanId);
487 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
488
489 if (record == null) {
490 log.warn("Can't find record for host {} when processing DHCPLEASEACTIVE", hostId);
491 return;
492 }
493
494 // need to update routes
495 log.debug("Lease Query for Client results in DHCPLEASEACTIVE - route needs to be modified");
496 // get current route
497 // find the ip of that client with the DhcpRelay store
498
499 Ip4Address clientIP = record.ip4Address().orElse(null);
500 log.debug("LQ: IP of host is " + clientIP.getIp4Address());
501
502 MacAddress nextHopMac = record.nextHop().orElse(null);
503 log.debug("LQ: MAC of resulting *OLD* NH for that host is " + nextHopMac.toString());
504
505 // find the new NH by looking at the src MAC of the dhcp request
506 // from the LQ store
507 MacAddress newNextHopMac = record.nextHopTemp().orElse(null);
508 log.debug("LQ: MAC of resulting *NEW* NH for that host is " + newNextHopMac.toString());
509
510 log.debug("LQ: updating dhcp relay record with new NH");
511 record.nextHop(newNextHopMac);
512
513 // find the next hop IP from its mac
514 HostId gwHostId = HostId.hostId(newNextHopMac, vlanId);
515 Host gwHost = hostService.getHost(gwHostId);
516
517 if (gwHost == null) {
518 log.warn("Can't find gateway for new NH host " + gwHostId);
519 return;
520 }
521
522 Ip4Address nextHopIp = gwHost.ipAddresses()
523 .stream()
524 .filter(IpAddress::isIp4)
525 .map(IpAddress::getIp4Address)
526 .findFirst()
527 .orElse(null);
528
529 if (nextHopIp == null) {
530 log.warn("Can't find IP address of gateway " + gwHost);
531 return;
532 }
533
534 log.debug("LQ: *NEW* NH IP for host is " + nextHopIp.getIp4Address());
535 Route route = new Route(Route.Source.STATIC, clientIP.toIpPrefix(), nextHopIp);
536 routeStore.updateRoute(route);
537
538 // and forward to client
539 Ethernet ethernetPacket = processLeaseQueryFromServer(packet);
540 if (ethernetPacket != null) {
541 sendResponseToClient(ethernetPacket, dhcpPayload);
542 }
543 }
544
545 /**
546 *
547 */
548 private void handleLeaseQueryMsg(PacketContext context, Ethernet packet, DHCP dhcpPayload) {
549 log.debug("LQ: Got DHCPLEASEQUERY packet!");
550 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
551 log.debug("LQ: got DHCPLEASEQUERY with MAC " + clientMacAddress.toString());
552 // add the client mac (hostid) of this request to a store (the entry will be removed with
553 // the reply sent to the originator)
554 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
555 HostId hId = HostId.hostId(clientMacAddress, vlanId);
556 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hId).orElse(null);
557 if (record != null) {
558 //new NH is to be taken from src mac of LQ packet
559 MacAddress newNextHop = packet.getSourceMAC();
560 record.nextHopTemp(newNextHop);
561 record.ip4Status(dhcpPayload.getPacketType());
562 record.updateLastSeen();
563
564 // do a basic routing of the packet (this is unicast routing
565 // not a relay operation like for other broadcast dhcp packets
566 Ethernet ethernetPacketLQ = processLeaseQueryFromAgent(context, packet);
567 // and forward to server
568 handleDhcpDiscoverAndRequest(ethernetPacketLQ, dhcpPayload);
569 } else {
570 log.warn("LQ: Error! - DHCP relay record for that client not found - ignoring LQ!");
571 }
572 }
573
574 private void handleLeaseQueryUnknown(Ethernet packet, DHCP dhcpPayload) {
575 log.debug("Lease Query for Client results in DHCPLEASEUNASSIGNED or " +
576 "DHCPLEASEUNKNOWN - removing route & forwarding reply to originator");
577 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
578 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
579 HostId hostId = HostId.hostId(clientMacAddress, vlanId);
580 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
581
582 if (record == null) {
583 log.warn("Can't find record for host {} when handling LQ UNKNOWN/UNASSIGNED message", hostId);
584 return;
585 }
586
587 Ip4Address clientIP = record.ip4Address().orElse(null);
588 log.debug("LQ: IP of host is " + clientIP.getIp4Address());
589
590 // find the new NH by looking at the src MAC of the dhcp request
591 // from the LQ store
592 MacAddress nextHopMac = record.nextHop().orElse(null);
593 log.debug("LQ: MAC of resulting *Existing* NH for that route is " + nextHopMac.toString());
594
595 // find the next hop IP from its mac
596 HostId gwHostId = HostId.hostId(nextHopMac, vlanId);
597 Host gwHost = hostService.getHost(gwHostId);
598
599 if (gwHost == null) {
600 log.warn("Can't find gateway for new NH host " + gwHostId);
601 return;
602 }
603
604 Ip4Address nextHopIp = gwHost.ipAddresses()
605 .stream()
606 .filter(IpAddress::isIp4)
607 .map(IpAddress::getIp4Address)
608 .findFirst()
609 .orElse(null);
610
611 if (nextHopIp == null) {
612 log.warn("Can't find IP address of gateway {}", gwHost);
613 return;
614 }
615
616 log.debug("LQ: *Existing* NH IP for host is " + nextHopIp.getIp4Address() + " removing route for it");
617 Route route = new Route(Route.Source.STATIC, clientIP.toIpPrefix(), nextHopIp);
618 routeStore.removeRoute(route);
619
620 // remove from temp store
621 dhcpRelayStore.removeDhcpRecord(hostId);
622
623 // and forward to client
624 Ethernet ethernetPacket = processLeaseQueryFromServer(packet);
625 if (ethernetPacket != null) {
626 sendResponseToClient(ethernetPacket, dhcpPayload);
627 }
628 }
629
Yi Tseng4ec727d2017-08-31 11:21:00 -0700630 /**
Yi Tseng51301292017-07-28 13:02:59 -0700631 * Build the DHCP discover/request packet with gateway IP(unicast packet).
632 *
633 * @param context the packet context
634 * @param ethernetPacket the ethernet payload to process
Yi Tseng51301292017-07-28 13:02:59 -0700635 * @return processed packet
636 */
637 private Ethernet processDhcpPacketFromClient(PacketContext context,
Yi Tsengdcef2c22017-08-05 20:34:06 -0700638 Ethernet ethernetPacket) {
Yi Tsengc44dc2e2017-11-03 16:27:32 -0700639 ConnectPoint receivedFrom = context.inPacket().receivedFrom();
640 DeviceId receivedFromDevice = receivedFrom.deviceId();
641
Yi Tseng4ec727d2017-08-31 11:21:00 -0700642 // get dhcp header.
643 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
644 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
645 UDP udpPacket = (UDP) ipv4Packet.getPayload();
646 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
647
Yi Tsengc44dc2e2017-11-03 16:27:32 -0700648 // TODO: refactor
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700649 VlanId dhcpConnectVlan = null;
650 MacAddress dhcpConnectMac = null;
651 Ip4Address dhcpServerIp = null;
652 Ip4Address relayAgentIp = null;
653
654 VlanId indirectDhcpConnectVlan = null;
655 MacAddress indirectDhcpConnectMac = null;
656 Ip4Address indirectDhcpServerIp = null;
657 Ip4Address indirectRelayAgentIp = null;
658
659 if (!defaultServerInfoList.isEmpty()) {
660 DhcpServerInfo serverInfo = defaultServerInfoList.get(0);
661 dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
662 dhcpConnectMac = serverInfo.getDhcpConnectMac().orElse(null);
663 dhcpServerIp = serverInfo.getDhcpServerIp4().orElse(null);
Yi Tsengc44dc2e2017-11-03 16:27:32 -0700664 relayAgentIp = serverInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700665 }
666
667 if (!indirectServerInfoList.isEmpty()) {
668 DhcpServerInfo indirectServerInfo = indirectServerInfoList.get(0);
669 indirectDhcpConnectVlan = indirectServerInfo.getDhcpConnectVlan().orElse(null);
670 indirectDhcpConnectMac = indirectServerInfo.getDhcpConnectMac().orElse(null);
671 indirectDhcpServerIp = indirectServerInfo.getDhcpServerIp4().orElse(null);
Yi Tsengc44dc2e2017-11-03 16:27:32 -0700672 indirectRelayAgentIp = indirectServerInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700673 }
674
Yi Tsengdcef2c22017-08-05 20:34:06 -0700675 Ip4Address clientInterfaceIp =
676 interfaceService.getInterfacesByPort(context.inPacket().receivedFrom())
677 .stream()
678 .map(Interface::ipAddressesList)
679 .flatMap(Collection::stream)
680 .map(InterfaceIpAddress::ipAddress)
681 .filter(IpAddress::isIp4)
682 .map(IpAddress::getIp4Address)
683 .findFirst()
684 .orElse(null);
685 if (clientInterfaceIp == null) {
686 log.warn("Can't find interface IP for client interface for port {}",
687 context.inPacket().receivedFrom());
688 return null;
689 }
Yi Tseng4ec727d2017-08-31 11:21:00 -0700690 boolean isDirectlyConnected = directlyConnected(dhcpPacket);
Yi Tsengdc510082017-11-29 14:39:18 -0800691 Interface serverInterface;
692 if (isDirectlyConnected) {
693 serverInterface = getDefaultServerInterface();
694 } else {
695 serverInterface = getIndirectServerInterface();
696 if (serverInterface == null) {
697 // Indirect server interface not found, use default server interface
698 serverInterface = getDefaultServerInterface();
699 }
700 }
Yi Tsengdcef2c22017-08-05 20:34:06 -0700701 if (serverInterface == null) {
Yi Tseng4ec727d2017-08-31 11:21:00 -0700702 log.warn("Can't get {} server interface, ignore", isDirectlyConnected ? "direct" : "indirect");
Yi Tsengdcef2c22017-08-05 20:34:06 -0700703 return null;
704 }
Yi Tseng4fa05832017-08-17 13:08:31 -0700705 Ip4Address ipFacingServer = getFirstIpFromInterface(serverInterface);
706 MacAddress macFacingServer = serverInterface.mac();
707 if (ipFacingServer == null || macFacingServer == null) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700708 log.warn("No IP address for server Interface {}", serverInterface);
Yi Tseng51301292017-07-28 13:02:59 -0700709 return null;
710 }
711 if (dhcpConnectMac == null) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700712 log.warn("DHCP Server/Gateway IP not yet resolved .. Aborting DHCP "
Yi Tseng51301292017-07-28 13:02:59 -0700713 + "packet processing from client on port: {}",
Yi Tsengdcef2c22017-08-05 20:34:06 -0700714 context.inPacket().receivedFrom());
Yi Tseng51301292017-07-28 13:02:59 -0700715 return null;
716 }
Yi Tseng4ec727d2017-08-31 11:21:00 -0700717
Yi Tseng4fa05832017-08-17 13:08:31 -0700718 etherReply.setSourceMACAddress(macFacingServer);
Yi Tseng4fa05832017-08-17 13:08:31 -0700719 ipv4Packet.setSourceAddress(ipFacingServer.toInt());
Yi Tseng51301292017-07-28 13:02:59 -0700720
Yi Tseng4ec727d2017-08-31 11:21:00 -0700721 if (isDirectlyConnected) {
Charles Chanb5d24822017-10-10 16:53:32 -0400722 etherReply.setDestinationMACAddress(dhcpConnectMac);
723 etherReply.setVlanID(dhcpConnectVlan.toShort());
724 ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
725
Yi Tseng51301292017-07-28 13:02:59 -0700726 ConnectPoint inPort = context.inPacket().receivedFrom();
727 VlanId vlanId = VlanId.vlanId(ethernetPacket.getVlanID());
728 // add connected in port and vlan
729 CircuitId cid = new CircuitId(inPort.toString(), vlanId);
730 byte[] circuitId = cid.serialize();
731 DhcpOption circuitIdSubOpt = new DhcpOption();
732 circuitIdSubOpt
733 .setCode(CIRCUIT_ID.getValue())
734 .setLength((byte) circuitId.length)
735 .setData(circuitId);
736
737 DhcpRelayAgentOption newRelayAgentOpt = new DhcpRelayAgentOption();
738 newRelayAgentOpt.setCode(OptionCode_CircuitID.getValue());
739 newRelayAgentOpt.addSubOption(circuitIdSubOpt);
740
Charles Chanb5d24822017-10-10 16:53:32 -0400741 // Removes END option first
Yi Tseng51301292017-07-28 13:02:59 -0700742 List<DhcpOption> options = dhcpPacket.getOptions().stream()
743 .filter(opt -> opt.getCode() != OptionCode_END.getValue())
744 .collect(Collectors.toList());
745
746 // push relay agent option
747 options.add(newRelayAgentOpt);
748
749 // make sure option 255(End) is the last option
750 DhcpOption endOption = new DhcpOption();
751 endOption.setCode(OptionCode_END.getValue());
752 options.add(endOption);
753
754 dhcpPacket.setOptions(options);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700755
Charles Chanb5d24822017-10-10 16:53:32 -0400756 // Sets relay agent IP
757 int effectiveRelayAgentIp = relayAgentIp != null ?
758 relayAgentIp.toInt() : clientInterfaceIp.toInt();
759 dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
760 } else {
761 if (indirectDhcpServerIp != null) {
762 // Use indirect server config for indirect packets if configured
763 etherReply.setDestinationMACAddress(indirectDhcpConnectMac);
764 etherReply.setVlanID(indirectDhcpConnectVlan.toShort());
765 ipv4Packet.setDestinationAddress(indirectDhcpServerIp.toInt());
Yi Tseng51301292017-07-28 13:02:59 -0700766
Charles Chanb5d24822017-10-10 16:53:32 -0400767 // Set giaddr if indirect relay agent IP is configured
768 if (indirectRelayAgentIp != null) {
769 dhcpPacket.setGatewayIPAddress(indirectRelayAgentIp.toInt());
770 }
771 } else {
772 // Otherwise, use default server config for indirect packets
773 etherReply.setDestinationMACAddress(dhcpConnectMac);
774 etherReply.setVlanID(dhcpConnectVlan.toShort());
775 ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
Yi Tseng4ec727d2017-08-31 11:21:00 -0700776
Charles Chanb5d24822017-10-10 16:53:32 -0400777 // Set giaddr if direct relay agent IP is configured
778 if (relayAgentIp != null) {
779 dhcpPacket.setGatewayIPAddress(relayAgentIp.toInt());
780 }
Yi Tseng4ec727d2017-08-31 11:21:00 -0700781 }
Yi Tseng4fa05832017-08-17 13:08:31 -0700782 }
783
Yi Tsengf41681e2017-10-03 09:58:19 -0700784 // Remove broadcast flag
785 dhcpPacket.setFlags((short) 0);
786
Yi Tseng51301292017-07-28 13:02:59 -0700787 udpPacket.setPayload(dhcpPacket);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700788 // As a DHCP relay, the source port should be server port( instead
789 // of client port.
Yi Tsengdcef2c22017-08-05 20:34:06 -0700790 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
Yi Tseng51301292017-07-28 13:02:59 -0700791 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
792 ipv4Packet.setPayload(udpPacket);
Charles Chan7edf7642017-10-09 11:07:25 -0400793 ipv4Packet.setTtl((byte) 64);
Yi Tseng51301292017-07-28 13:02:59 -0700794 etherReply.setPayload(ipv4Packet);
795 return etherReply;
796 }
797
Charles Chan2f9aa2c2017-10-08 23:53:36 -0400798
799 /**
800 * Do a basic routing for a packet from client (used for LQ processing).
801 *
802 * @param context the packet context
803 * @param ethernetPacket the ethernet payload to process
804 * @return processed packet
805 */
806 private Ethernet processLeaseQueryFromAgent(PacketContext context,
807 Ethernet ethernetPacket) {
808 // get dhcp header.
809 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
810 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
811 UDP udpPacket = (UDP) ipv4Packet.getPayload();
812 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
813
814 VlanId dhcpConnectVlan = null;
815 MacAddress dhcpConnectMac = null;
816 Ip4Address dhcpServerIp = null;
817
818 VlanId indirectDhcpConnectVlan = null;
819 MacAddress indirectDhcpConnectMac = null;
820 Ip4Address indirectDhcpServerIp = null;
821
822 if (!defaultServerInfoList.isEmpty()) {
823 DhcpServerInfo serverInfo = defaultServerInfoList.get(0);
824 dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
825 dhcpConnectMac = serverInfo.getDhcpConnectMac().orElse(null);
826 dhcpServerIp = serverInfo.getDhcpServerIp4().orElse(null);
827 }
828
829 if (!indirectServerInfoList.isEmpty()) {
830 DhcpServerInfo indirectServerInfo = indirectServerInfoList.get(0);
831 indirectDhcpConnectVlan = indirectServerInfo.getDhcpConnectVlan().orElse(null);
832 indirectDhcpConnectMac = indirectServerInfo.getDhcpConnectMac().orElse(null);
833 indirectDhcpServerIp = indirectServerInfo.getDhcpServerIp4().orElse(null);
834 }
835
836 Ip4Address clientInterfaceIp =
837 interfaceService.getInterfacesByPort(context.inPacket().receivedFrom())
838 .stream()
839 .map(Interface::ipAddressesList)
840 .flatMap(Collection::stream)
841 .map(InterfaceIpAddress::ipAddress)
842 .filter(IpAddress::isIp4)
843 .map(IpAddress::getIp4Address)
844 .findFirst()
845 .orElse(null);
846 if (clientInterfaceIp == null) {
847 log.warn("Can't find interface IP for client interface for port {}",
848 context.inPacket().receivedFrom());
849 return null;
850 }
851 boolean isDirectlyConnected = directlyConnected(dhcpPacket);
Yi Tsengdc510082017-11-29 14:39:18 -0800852 Interface serverInterface;
853 if (isDirectlyConnected) {
854 serverInterface = getDefaultServerInterface();
855 } else {
856 serverInterface = getIndirectServerInterface();
857 if (serverInterface == null) {
858 // Indirect server interface not found, use default server interface
859 serverInterface = getDefaultServerInterface();
860 }
861 }
Charles Chan2f9aa2c2017-10-08 23:53:36 -0400862 if (serverInterface == null) {
863 log.warn("Can't get {} server interface, ignore", isDirectlyConnected ? "direct" : "indirect");
864 return null;
865 }
866 Ip4Address ipFacingServer = getFirstIpFromInterface(serverInterface);
867 MacAddress macFacingServer = serverInterface.mac();
868 if (ipFacingServer == null || macFacingServer == null) {
869 log.warn("No IP address for server Interface {}", serverInterface);
870 return null;
871 }
872 if (dhcpConnectMac == null) {
873 log.warn("DHCP server/gateway not yet resolved .. Aborting DHCP "
874 + "packet processing from client on port: {}",
875 context.inPacket().receivedFrom());
876 return null;
877 }
878
879 etherReply.setSourceMACAddress(macFacingServer);
880 etherReply.setDestinationMACAddress(dhcpConnectMac);
881 etherReply.setVlanID(dhcpConnectVlan.toShort());
882 ipv4Packet.setSourceAddress(ipFacingServer.toInt());
883 ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
884
885 if (indirectDhcpServerIp != null) {
886 // Indirect case, replace destination to indirect dhcp server if exist
887 etherReply.setDestinationMACAddress(indirectDhcpConnectMac);
888 etherReply.setVlanID(indirectDhcpConnectVlan.toShort());
889 ipv4Packet.setDestinationAddress(indirectDhcpServerIp.toInt());
890 }
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 etherReply.setPayload(ipv4Packet);
899 return etherReply;
900 }
901
902
Yi Tseng51301292017-07-28 13:02:59 -0700903 /**
904 * Writes DHCP record to the store according to the request DHCP packet (Discover, Request).
905 *
906 * @param location the location which DHCP packet comes from
907 * @param ethernet the DHCP packet
908 * @param dhcpPayload the DHCP payload
909 */
910 private void writeRequestDhcpRecord(ConnectPoint location,
911 Ethernet ethernet,
912 DHCP dhcpPayload) {
913 VlanId vlanId = VlanId.vlanId(ethernet.getVlanID());
914 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
915 HostId hostId = HostId.hostId(macAddress, vlanId);
916 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
917 if (record == null) {
918 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
919 } else {
920 record = record.clone();
921 }
922 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
923 record.ip4Status(dhcpPayload.getPacketType());
924 record.setDirectlyConnected(directlyConnected(dhcpPayload));
925 if (!directlyConnected(dhcpPayload)) {
926 // Update gateway mac address if the host is not directly connected
927 record.nextHop(ethernet.getSourceMAC());
928 }
929 record.updateLastSeen();
930 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
931 }
932
933 /**
934 * Writes DHCP record to the store according to the response DHCP packet (Offer, Ack).
935 *
936 * @param ethernet the DHCP packet
937 * @param dhcpPayload the DHCP payload
938 */
939 private void writeResponseDhcpRecord(Ethernet ethernet,
940 DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700941 Optional<Interface> outInterface = getClientInterface(ethernet, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700942 if (!outInterface.isPresent()) {
943 log.warn("Failed to determine where to send {}", dhcpPayload.getPacketType());
944 return;
945 }
946
947 Interface outIface = outInterface.get();
948 ConnectPoint location = outIface.connectPoint();
Yi Tseng4ec727d2017-08-31 11:21:00 -0700949 VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700950 if (vlanId == null) {
951 vlanId = outIface.vlan();
952 }
Yi Tseng51301292017-07-28 13:02:59 -0700953 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
954 HostId hostId = HostId.hostId(macAddress, vlanId);
955 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
956 if (record == null) {
957 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
958 } else {
959 record = record.clone();
960 }
961 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
962 if (dhcpPayload.getPacketType() == DHCP.MsgType.DHCPACK) {
963 record.ip4Address(Ip4Address.valueOf(dhcpPayload.getYourIPAddress()));
964 }
965 record.ip4Status(dhcpPayload.getPacketType());
966 record.setDirectlyConnected(directlyConnected(dhcpPayload));
967 record.updateLastSeen();
968 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
969 }
970
971 /**
972 * Build the DHCP offer/ack with proper client port.
973 *
974 * @param ethernetPacket the original packet comes from server
975 * @return new packet which will send to the client
976 */
977 private Ethernet processDhcpPacketFromServer(Ethernet ethernetPacket) {
978 // get dhcp header.
979 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
980 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
981 UDP udpPacket = (UDP) ipv4Packet.getPayload();
982 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
983
984 // determine the vlanId of the client host - note that this vlan id
985 // could be different from the vlan in the packet from the server
Yi Tsengdcef2c22017-08-05 20:34:06 -0700986 Interface clientInterface = getClientInterface(ethernetPacket, dhcpPayload).orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -0700987
Yi Tsengdcef2c22017-08-05 20:34:06 -0700988 if (clientInterface == null) {
Yi Tseng51301292017-07-28 13:02:59 -0700989 log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
990 return null;
991 }
Yi Tsengdcef2c22017-08-05 20:34:06 -0700992 VlanId vlanId;
993 if (clientInterface.vlanTagged().isEmpty()) {
994 vlanId = clientInterface.vlan();
995 } else {
996 // might be multiple vlan in same interface
Yi Tseng4ec727d2017-08-31 11:21:00 -0700997 vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700998 }
999 if (vlanId == null) {
1000 vlanId = VlanId.NONE;
1001 }
1002 etherReply.setVlanID(vlanId.toShort());
1003 etherReply.setSourceMACAddress(clientInterface.mac());
Yi Tseng51301292017-07-28 13:02:59 -07001004
Yi Tsengdcef2c22017-08-05 20:34:06 -07001005 if (!directlyConnected(dhcpPayload)) {
1006 // if client is indirectly connected, try use next hop mac address
1007 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1008 HostId hostId = HostId.hostId(macAddress, vlanId);
1009 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1010 if (record != null) {
1011 // if next hop can be found, use mac address of next hop
1012 record.nextHop().ifPresent(etherReply::setDestinationMACAddress);
1013 } else {
1014 // otherwise, discard the packet
1015 log.warn("Can't find record for host id {}, discard packet", hostId);
1016 return null;
1017 }
Yi Tsengc03fa242017-08-17 17:43:38 -07001018 } else {
1019 etherReply.setDestinationMACAddress(dhcpPayload.getClientHardwareAddress());
Yi Tsengdcef2c22017-08-05 20:34:06 -07001020 }
1021
Yi Tseng51301292017-07-28 13:02:59 -07001022 // we leave the srcMac from the original packet
Yi Tseng51301292017-07-28 13:02:59 -07001023 // figure out the relay agent IP corresponding to the original request
Yi Tseng4fa05832017-08-17 13:08:31 -07001024 Ip4Address ipFacingClient = getFirstIpFromInterface(clientInterface);
1025 if (ipFacingClient == null) {
Yi Tseng51301292017-07-28 13:02:59 -07001026 log.warn("Cannot determine relay agent interface Ipv4 addr for host {}/{}. "
1027 + "Aborting relay for dhcp packet from server {}",
Yi Tsengdcef2c22017-08-05 20:34:06 -07001028 etherReply.getDestinationMAC(), clientInterface.vlan(),
Yi Tseng51301292017-07-28 13:02:59 -07001029 ethernetPacket);
1030 return null;
1031 }
1032 // SRC_IP: relay agent IP
1033 // DST_IP: offered IP
Yi Tseng4fa05832017-08-17 13:08:31 -07001034 ipv4Packet.setSourceAddress(ipFacingClient.toInt());
Yi Tseng51301292017-07-28 13:02:59 -07001035 ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
1036 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
1037 if (directlyConnected(dhcpPayload)) {
1038 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
1039 } else {
1040 // forward to another dhcp relay
Yi Tseng72b599a2017-09-14 13:24:21 -07001041 // FIXME: Currently we assume the DHCP comes from a L2 relay with
1042 // Option 82, this might not work if DHCP message comes from
1043 // L3 relay.
1044 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
Yi Tseng51301292017-07-28 13:02:59 -07001045 }
1046
1047 udpPacket.setPayload(dhcpPayload);
1048 ipv4Packet.setPayload(udpPacket);
1049 etherReply.setPayload(ipv4Packet);
1050 return etherReply;
1051 }
1052
Yi Tsengdcef2c22017-08-05 20:34:06 -07001053 /**
Charles Chan2f9aa2c2017-10-08 23:53:36 -04001054 * Build the DHCP offer/ack with proper client port.
1055 *
1056 * @param ethernetPacket the original packet comes from server
1057 * @return new packet which will send to the client
1058 */
1059 private Ethernet processLeaseQueryFromServer(Ethernet ethernetPacket) {
1060 // get dhcp header.
1061 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
1062 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
1063 UDP udpPacket = (UDP) ipv4Packet.getPayload();
1064 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
1065
1066 // determine the vlanId of the client host - note that this vlan id
1067 // could be different from the vlan in the packet from the server
1068 Interface clientInterface = getClientInterface(ethernetPacket, dhcpPayload).orElse(null);
1069
1070 if (clientInterface == null) {
1071 log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
1072 return null;
1073 }
1074 VlanId vlanId;
1075 if (clientInterface.vlanTagged().isEmpty()) {
1076 vlanId = clientInterface.vlan();
1077 } else {
1078 // might be multiple vlan in same interface
1079 vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
1080 }
1081 if (vlanId == null) {
1082 vlanId = VlanId.NONE;
1083 }
1084 etherReply.setVlanID(vlanId.toShort());
1085 etherReply.setSourceMACAddress(clientInterface.mac());
1086
1087 if (!directlyConnected(dhcpPayload)) {
1088 // if client is indirectly connected, try use next hop mac address
1089 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1090 HostId hostId = HostId.hostId(macAddress, vlanId);
1091 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1092 if (record != null) {
1093 // if next hop can be found, use mac address of next hop
1094 record.nextHop().ifPresent(etherReply::setDestinationMACAddress);
1095 } else {
1096 // otherwise, discard the packet
1097 log.warn("Can't find record for host id {}, discard packet", hostId);
1098 return null;
1099 }
1100 } else {
1101 etherReply.setDestinationMACAddress(dhcpPayload.getClientHardwareAddress());
1102 }
1103
1104 // default is client port
1105 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
1106 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
1107
1108 udpPacket.setPayload(dhcpPayload);
1109 ipv4Packet.setPayload(udpPacket);
1110 etherReply.setPayload(ipv4Packet);
1111 return etherReply;
1112 }
1113 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -07001114 * Extracts VLAN ID from relay agent option.
1115 *
1116 * @param dhcpPayload the DHCP payload
1117 * @return VLAN ID from DHCP payload; null if not exists
1118 */
Yi Tseng4ec727d2017-08-31 11:21:00 -07001119 private VlanId getVlanIdFromRelayAgentOption(DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001120 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
1121 if (option == null) {
1122 return null;
1123 }
1124 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
1125 if (circuitIdSubOption == null) {
1126 return null;
1127 }
1128 try {
1129 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
1130 return circuitId.vlanId();
1131 } catch (IllegalArgumentException e) {
1132 // can't deserialize the circuit ID
1133 return null;
1134 }
1135 }
1136
1137 /**
1138 * Removes DHCP relay agent information option (option 82) from DHCP payload.
1139 * Also reset giaddr to 0
1140 *
1141 * @param ethPacket the Ethernet packet to be processed
1142 * @return Ethernet packet processed
1143 */
1144 private Ethernet removeRelayAgentOption(Ethernet ethPacket) {
1145 Ethernet ethernet = (Ethernet) ethPacket.clone();
1146 IPv4 ipv4 = (IPv4) ethernet.getPayload();
1147 UDP udp = (UDP) ipv4.getPayload();
1148 DHCP dhcpPayload = (DHCP) udp.getPayload();
1149
1150 // removes relay agent information option
1151 List<DhcpOption> options = dhcpPayload.getOptions();
1152 options = options.stream()
1153 .filter(option -> option.getCode() != OptionCode_CircuitID.getValue())
1154 .collect(Collectors.toList());
1155 dhcpPayload.setOptions(options);
1156 dhcpPayload.setGatewayIPAddress(0);
1157
1158 udp.setPayload(dhcpPayload);
1159 ipv4.setPayload(udp);
1160 ethernet.setPayload(ipv4);
1161 return ethernet;
1162 }
1163
Yi Tseng51301292017-07-28 13:02:59 -07001164
1165 /**
1166 * Check if the host is directly connected to the network or not.
1167 *
1168 * @param dhcpPayload the dhcp payload
1169 * @return true if the host is directly connected to the network; false otherwise
1170 */
1171 private boolean directlyConnected(DHCP dhcpPayload) {
Yi Tseng2cf59912017-08-24 14:47:34 -07001172 DhcpRelayAgentOption relayAgentOption =
1173 (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
Yi Tseng51301292017-07-28 13:02:59 -07001174
1175 // Doesn't contains relay option
1176 if (relayAgentOption == null) {
1177 return true;
1178 }
1179
Yi Tseng2cf59912017-08-24 14:47:34 -07001180 // check circuit id, if circuit id is invalid, we say it is an indirect host
1181 DhcpOption circuitIdOpt = relayAgentOption.getSubOption(CIRCUIT_ID.getValue());
Yi Tseng51301292017-07-28 13:02:59 -07001182
Yi Tseng2cf59912017-08-24 14:47:34 -07001183 try {
1184 CircuitId.deserialize(circuitIdOpt.getData());
Yi Tseng51301292017-07-28 13:02:59 -07001185 return true;
Yi Tseng2cf59912017-08-24 14:47:34 -07001186 } catch (Exception e) {
1187 // invalid circuit id
1188 return false;
Yi Tseng51301292017-07-28 13:02:59 -07001189 }
Yi Tseng51301292017-07-28 13:02:59 -07001190 }
1191
1192
1193 /**
1194 * Send the DHCP ack to the requester host.
1195 * Modify Host or Route store according to the type of DHCP.
1196 *
1197 * @param ethernetPacketAck the packet
1198 * @param dhcpPayload the DHCP data
1199 */
1200 private void handleDhcpAck(Ethernet ethernetPacketAck, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001201 Optional<Interface> outInterface = getClientInterface(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -07001202 if (!outInterface.isPresent()) {
1203 log.warn("Can't find output interface for dhcp: {}", dhcpPayload);
1204 return;
1205 }
1206
1207 Interface outIface = outInterface.get();
1208 HostLocation hostLocation = new HostLocation(outIface.connectPoint(), System.currentTimeMillis());
1209 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
Yi Tseng4ec727d2017-08-31 11:21:00 -07001210 VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001211 if (vlanId == null) {
1212 vlanId = outIface.vlan();
1213 }
Yi Tseng51301292017-07-28 13:02:59 -07001214 HostId hostId = HostId.hostId(macAddress, vlanId);
1215 Ip4Address ip = Ip4Address.valueOf(dhcpPayload.getYourIPAddress());
1216
1217 if (directlyConnected(dhcpPayload)) {
1218 // Add to host store if it connect to network directly
1219 Set<IpAddress> ips = Sets.newHashSet(ip);
Yi Tseng4b013202017-09-08 17:22:51 -07001220 Host host = hostService.getHost(hostId);
Yi Tseng51301292017-07-28 13:02:59 -07001221
Yi Tseng4b013202017-09-08 17:22:51 -07001222 Set<HostLocation> hostLocations = Sets.newHashSet(hostLocation);
1223 if (host != null) {
1224 // Dual homing support:
1225 // if host exists, use old locations and new location
1226 hostLocations.addAll(host.locations());
1227 }
1228 HostDescription desc = new DefaultHostDescription(macAddress, vlanId,
1229 hostLocations, ips, false);
1230 // Add IP address when dhcp server give the host new ip address
1231 providerService.hostDetected(hostId, desc, false);
Yi Tseng51301292017-07-28 13:02:59 -07001232 } else {
1233 // Add to route store if it does not connect to network directly
1234 // Get gateway host IP according to host mac address
Yi Tsengdcef2c22017-08-05 20:34:06 -07001235 // TODO: remove relay store here
Yi Tseng51301292017-07-28 13:02:59 -07001236 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1237
1238 if (record == null) {
1239 log.warn("Can't find DHCP record of host {}", hostId);
1240 return;
1241 }
1242
1243 MacAddress gwMac = record.nextHop().orElse(null);
1244 if (gwMac == null) {
1245 log.warn("Can't find gateway mac address from record {}", record);
1246 return;
1247 }
1248
1249 HostId gwHostId = HostId.hostId(gwMac, record.vlanId());
1250 Host gwHost = hostService.getHost(gwHostId);
1251
1252 if (gwHost == null) {
1253 log.warn("Can't find gateway host {}", gwHostId);
1254 return;
1255 }
1256
1257 Ip4Address nextHopIp = gwHost.ipAddresses()
1258 .stream()
1259 .filter(IpAddress::isIp4)
1260 .map(IpAddress::getIp4Address)
1261 .findFirst()
1262 .orElse(null);
1263
1264 if (nextHopIp == null) {
1265 log.warn("Can't find IP address of gateway {}", gwHost);
1266 return;
1267 }
1268
1269 Route route = new Route(Route.Source.STATIC, ip.toIpPrefix(), nextHopIp);
1270 routeStore.updateRoute(route);
1271 }
Yi Tseng51301292017-07-28 13:02:59 -07001272 }
1273
1274 /**
1275 * forward the packet to ConnectPoint where the DHCP server is attached.
1276 *
1277 * @param packet the packet
1278 */
Yi Tseng4ec727d2017-08-31 11:21:00 -07001279 private void handleDhcpDiscoverAndRequest(Ethernet packet, DHCP dhcpPayload) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001280 boolean direct = directlyConnected(dhcpPayload);
1281 DhcpServerInfo serverInfo = defaultServerInfoList.get(0);
1282 if (!direct && !indirectServerInfoList.isEmpty()) {
1283 serverInfo = indirectServerInfoList.get(0);
Yi Tseng4ec727d2017-08-31 11:21:00 -07001284 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001285 ConnectPoint portToFotward = serverInfo.getDhcpServerConnectPoint().orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -07001286 // send packet to dhcp server connect point.
Yi Tseng4ec727d2017-08-31 11:21:00 -07001287 if (portToFotward != null) {
Yi Tseng51301292017-07-28 13:02:59 -07001288 TrafficTreatment t = DefaultTrafficTreatment.builder()
Yi Tseng4ec727d2017-08-31 11:21:00 -07001289 .setOutput(portToFotward.port()).build();
Yi Tseng51301292017-07-28 13:02:59 -07001290 OutboundPacket o = new DefaultOutboundPacket(
Yi Tseng4ec727d2017-08-31 11:21:00 -07001291 portToFotward.deviceId(), t, ByteBuffer.wrap(packet.serialize()));
Yi Tseng51301292017-07-28 13:02:59 -07001292 if (log.isTraceEnabled()) {
1293 log.trace("Relaying packet to dhcp server {}", packet);
1294 }
1295 packetService.emit(o);
1296 } else {
1297 log.warn("Can't find DHCP server connect point, abort.");
1298 }
1299 }
1300
1301
1302 /**
1303 * Gets output interface of a dhcp packet.
1304 * If option 82 exists in the dhcp packet and the option was sent by
Yi Tseng4ec727d2017-08-31 11:21:00 -07001305 * ONOS (circuit format is correct), use the connect
Yi Tseng51301292017-07-28 13:02:59 -07001306 * point and vlan id from circuit id; otherwise, find host by destination
1307 * address and use vlan id from sender (dhcp server).
1308 *
1309 * @param ethPacket the ethernet packet
1310 * @param dhcpPayload the dhcp packet
1311 * @return an interface represent the output port and vlan; empty value
1312 * if the host or circuit id not found
1313 */
Yi Tsengdcef2c22017-08-05 20:34:06 -07001314 private Optional<Interface> getClientInterface(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tseng51301292017-07-28 13:02:59 -07001315 VlanId originalPacketVlanId = VlanId.vlanId(ethPacket.getVlanID());
Yi Tseng51301292017-07-28 13:02:59 -07001316 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
1317
Yi Tseng4ec727d2017-08-31 11:21:00 -07001318 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
1319 try {
1320 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
1321 ConnectPoint connectPoint = ConnectPoint.deviceConnectPoint(circuitId.connectPoint());
1322 VlanId vlanId = circuitId.vlanId();
1323 return interfaceService.getInterfacesByPort(connectPoint)
1324 .stream()
1325 .filter(iface -> interfaceContainsVlan(iface, vlanId))
1326 .findFirst();
1327 } catch (IllegalArgumentException ex) {
1328 // invalid circuit format, didn't sent by ONOS
1329 log.debug("Invalid circuit {}, use information from dhcp payload",
1330 circuitIdSubOption.getData());
Yi Tseng51301292017-07-28 13:02:59 -07001331 }
1332
1333 // Use Vlan Id from DHCP server if DHCP relay circuit id was not
1334 // sent by ONOS or circuit Id can't be parsed
Yi Tsengdcef2c22017-08-05 20:34:06 -07001335 // TODO: remove relay store from this method
Yi Tseng51301292017-07-28 13:02:59 -07001336 MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
1337 Optional<DhcpRecord> dhcpRecord = dhcpRelayStore.getDhcpRecord(HostId.hostId(dstMac, originalPacketVlanId));
Yi Tsengdcef2c22017-08-05 20:34:06 -07001338 ConnectPoint clientConnectPoint = dhcpRecord
Yi Tseng51301292017-07-28 13:02:59 -07001339 .map(DhcpRecord::locations)
1340 .orElse(Collections.emptySet())
1341 .stream()
1342 .reduce((hl1, hl2) -> {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001343 // find latest host connect point
Yi Tseng51301292017-07-28 13:02:59 -07001344 if (hl1 == null || hl2 == null) {
1345 return hl1 == null ? hl2 : hl1;
1346 }
1347 return hl1.time() > hl2.time() ? hl1 : hl2;
1348 })
Yi Tsengdcef2c22017-08-05 20:34:06 -07001349 .orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -07001350
Yi Tsengdcef2c22017-08-05 20:34:06 -07001351 if (clientConnectPoint != null) {
1352 return interfaceService.getInterfacesByPort(clientConnectPoint)
1353 .stream()
Yi Tseng4ec727d2017-08-31 11:21:00 -07001354 .filter(iface -> interfaceContainsVlan(iface, originalPacketVlanId))
Yi Tsengdcef2c22017-08-05 20:34:06 -07001355 .findFirst();
1356 }
1357 return Optional.empty();
Yi Tseng51301292017-07-28 13:02:59 -07001358 }
1359
1360 /**
1361 * Send the response DHCP to the requester host.
1362 *
1363 * @param ethPacket the packet
1364 * @param dhcpPayload the DHCP data
1365 */
1366 private void sendResponseToClient(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001367 Optional<Interface> outInterface = getClientInterface(ethPacket, dhcpPayload);
1368 if (directlyConnected(dhcpPayload)) {
1369 ethPacket = removeRelayAgentOption(ethPacket);
1370 }
1371 if (!outInterface.isPresent()) {
1372 log.warn("Can't find output interface for client, ignore");
1373 return;
1374 }
1375 Interface outIface = outInterface.get();
1376 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
1377 .setOutput(outIface.connectPoint().port())
1378 .build();
1379 OutboundPacket o = new DefaultOutboundPacket(
1380 outIface.connectPoint().deviceId(),
1381 treatment,
1382 ByteBuffer.wrap(ethPacket.serialize()));
1383 if (log.isTraceEnabled()) {
1384 log.trace("Relaying packet to DHCP client {} via {}, vlan {}",
1385 ethPacket,
1386 outIface.connectPoint(),
1387 outIface.vlan());
1388 }
1389 packetService.emit(o);
Yi Tseng51301292017-07-28 13:02:59 -07001390 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001391
Yi Tseng4b013202017-09-08 17:22:51 -07001392 @Override
1393 public void triggerProbe(Host host) {
1394 // Do nothing here
1395 }
1396
1397 @Override
1398 public ProviderId id() {
Charles Chan75edab72017-09-12 17:09:32 -07001399 return PROVIDER_ID;
Yi Tseng4b013202017-09-08 17:22:51 -07001400 }
1401
Yi Tsenge72fbb52017-08-02 15:03:31 -07001402 class InternalHostListener implements HostListener {
1403 @Override
1404 public void event(HostEvent event) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001405 if (!configured()) {
1406 return;
1407 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001408 switch (event.type()) {
1409 case HOST_ADDED:
1410 case HOST_UPDATED:
1411 hostUpdated(event.subject());
1412 break;
1413 case HOST_REMOVED:
1414 hostRemoved(event.subject());
1415 break;
Yi Tsenge72fbb52017-08-02 15:03:31 -07001416 default:
1417 break;
1418 }
1419 }
1420 }
1421
1422 /**
Yi Tsenge72fbb52017-08-02 15:03:31 -07001423 * Handle host updated.
1424 * If the host is DHCP server or gateway, update connect mac and vlan.
1425 *
1426 * @param host the host
1427 */
1428 private void hostUpdated(Host host) {
Yi Tseng7da339e2017-10-23 19:39:39 -07001429 hostUpdated(host, defaultServerInfoList);
1430 hostUpdated(host, indirectServerInfoList);
Yi Tsenge72fbb52017-08-02 15:03:31 -07001431 }
1432
Yi Tseng7da339e2017-10-23 19:39:39 -07001433 private void hostUpdated(Host host, List<DhcpServerInfo> srverInfoList) {
1434 DhcpServerInfo serverInfo;
1435 Ip4Address targetIp;
1436 if (!srverInfoList.isEmpty()) {
1437 serverInfo = srverInfoList.get(0);
1438 targetIp = serverInfo.getDhcpGatewayIp4().orElse(null);
1439 Ip4Address serverIp = serverInfo.getDhcpServerIp4().orElse(null);
1440
1441 if (targetIp == null) {
1442 targetIp = serverIp;
1443 }
1444
1445 if (targetIp != null) {
1446 if (host.ipAddresses().contains(targetIp)) {
1447 serverInfo.setDhcpConnectMac(host.mac());
1448 serverInfo.setDhcpConnectVlan(host.vlan());
1449 requestDhcpPacket(serverIp);
1450 }
1451 }
1452 }
1453 }
1454
1455
Yi Tsenge72fbb52017-08-02 15:03:31 -07001456 /**
1457 * Handle host removed.
1458 * If the host is DHCP server or gateway, unset connect mac and vlan.
1459 *
1460 * @param host the host
1461 */
1462 private void hostRemoved(Host host) {
Yi Tseng7da339e2017-10-23 19:39:39 -07001463 hostRemoved(host, defaultServerInfoList);
1464 hostRemoved(host, indirectServerInfoList);
1465 }
1466
1467 private void hostRemoved(Host host, List<DhcpServerInfo> serverInfoList) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001468 DhcpServerInfo serverInfo;
Yi Tseng7da339e2017-10-23 19:39:39 -07001469 Ip4Address targetIp;
1470 if (!serverInfoList.isEmpty()) {
1471 serverInfo = serverInfoList.get(0);
1472 Ip4Address serverIp = serverInfo.getDhcpServerIp4().orElse(null);
1473 targetIp = serverInfo.getDhcpGatewayIp4().orElse(null);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001474
Yi Tseng7da339e2017-10-23 19:39:39 -07001475 if (targetIp == null) {
1476 targetIp = serverIp;
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001477 }
Yi Tseng7da339e2017-10-23 19:39:39 -07001478
1479 if (targetIp != null) {
1480 if (host.ipAddresses().contains(targetIp)) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001481 serverInfo.setDhcpConnectVlan(null);
1482 serverInfo.setDhcpConnectMac(null);
Yi Tseng7da339e2017-10-23 19:39:39 -07001483 cancelDhcpPacket(serverIp);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001484 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001485 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001486 }
Yi Tseng7da339e2017-10-23 19:39:39 -07001487 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001488
Yi Tseng7da339e2017-10-23 19:39:39 -07001489 private void requestDhcpPacket(Ip4Address serverIp) {
1490 requestServerDhcpPacket(serverIp);
1491 requestClientDhcpPacket(serverIp);
1492 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001493
Yi Tseng7da339e2017-10-23 19:39:39 -07001494 private void cancelDhcpPacket(Ip4Address serverIp) {
1495 cancelServerDhcpPacket(serverIp);
1496 cancelClientDhcpPacket(serverIp);
1497 }
1498
1499 private void cancelServerDhcpPacket(Ip4Address serverIp) {
1500 TrafficSelector serverSelector =
1501 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1502 .matchIPSrc(serverIp.toIpPrefix())
1503 .build();
1504 packetService.cancelPackets(serverSelector,
1505 PacketPriority.CONTROL,
1506 appId);
1507 }
1508
1509 private void requestServerDhcpPacket(Ip4Address serverIp) {
1510 TrafficSelector serverSelector =
1511 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1512 .matchIPSrc(serverIp.toIpPrefix())
1513 .build();
1514 packetService.requestPackets(serverSelector,
1515 PacketPriority.CONTROL,
1516 appId);
1517 }
1518
1519 private void cancelClientDhcpPacket(Ip4Address serverIp) {
1520 // Packet comes from relay
1521 TrafficSelector indirectClientSelector =
1522 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1523 .matchIPDst(serverIp.toIpPrefix())
1524 .build();
1525 packetService.cancelPackets(indirectClientSelector,
1526 PacketPriority.CONTROL,
1527 appId);
1528
1529 // Packet comes from client
1530 packetService.cancelPackets(CLIENT_SERVER_SELECTOR,
1531 PacketPriority.CONTROL,
1532 appId);
1533 }
1534
1535 private void requestClientDhcpPacket(Ip4Address serverIp) {
1536 // Packet comes from relay
1537 TrafficSelector indirectClientSelector =
1538 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1539 .matchIPDst(serverIp.toIpPrefix())
1540 .build();
1541 packetService.requestPackets(indirectClientSelector,
1542 PacketPriority.CONTROL,
1543 appId);
1544
1545 // Packet comes from client
1546 packetService.requestPackets(CLIENT_SERVER_SELECTOR,
1547 PacketPriority.CONTROL,
1548 appId);
1549 }
1550
1551 /**
1552 * Process the ignore rules.
1553 *
1554 * @param deviceId the device id
1555 * @param vlanId the vlan to be ignored
1556 * @param op the operation, ADD to install; REMOVE to uninstall rules
1557 */
1558 private void processIgnoreVlanRule(DeviceId deviceId, VlanId vlanId, Objective.Operation op) {
Yi Tseng7da339e2017-10-23 19:39:39 -07001559 AtomicInteger installedCount = new AtomicInteger(DHCP_SELECTORS.size());
1560 DHCP_SELECTORS.forEach(trafficSelector -> {
1561 TrafficSelector selector = DefaultTrafficSelector.builder(trafficSelector)
1562 .matchVlanId(vlanId)
1563 .build();
1564
1565 ForwardingObjective.Builder builder = DefaultForwardingObjective.builder()
1566 .withFlag(ForwardingObjective.Flag.VERSATILE)
1567 .withSelector(selector)
1568 .withPriority(IGNORE_CONTROL_PRIORITY)
Yi Tsengf29ffd72017-11-29 10:49:20 -08001569 .withTreatment(DefaultTrafficTreatment.emptyTreatment())
Yi Tseng7da339e2017-10-23 19:39:39 -07001570 .fromApp(appId);
1571
1572
1573 ObjectiveContext objectiveContext = new ObjectiveContext() {
1574 @Override
1575 public void onSuccess(Objective objective) {
1576 log.info("Ignore rule {} (Vlan id {}, device {}, selector {})",
1577 op, vlanId, deviceId, selector);
1578 int countDown = installedCount.decrementAndGet();
1579 if (countDown != 0) {
1580 return;
1581 }
1582 switch (op) {
1583 case ADD:
1584 ignoredVlans.put(deviceId, vlanId);
1585 break;
1586 case REMOVE:
1587 ignoredVlans.remove(deviceId, vlanId);
1588 break;
1589 default:
1590 log.warn("Unsupported objective operation {}", op);
1591 break;
1592 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001593 }
Yi Tseng7da339e2017-10-23 19:39:39 -07001594
1595 @Override
1596 public void onError(Objective objective, ObjectiveError error) {
1597 log.warn("Can't {} ignore rule (vlan id {}, selector {}, device {}) due to {}",
1598 op, vlanId, selector, deviceId, error);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001599 }
Yi Tseng7da339e2017-10-23 19:39:39 -07001600 };
1601
1602 ForwardingObjective fwd;
1603 switch (op) {
1604 case ADD:
1605 fwd = builder.add(objectiveContext);
1606 break;
1607 case REMOVE:
1608 fwd = builder.remove(objectiveContext);
1609 break;
1610 default:
1611 log.warn("Unsupported objective operation {}", op);
1612 return;
Yi Tseng4ec727d2017-08-31 11:21:00 -07001613 }
Yi Tseng7da339e2017-10-23 19:39:39 -07001614
1615 Device device = deviceService.getDevice(deviceId);
1616 if (device == null || !device.is(Pipeliner.class)) {
1617 log.warn("Device {} is not available now, wait until device is available", deviceId);
1618 return;
1619 }
1620 flowObjectiveService.apply(deviceId, fwd);
1621 });
Yi Tsenge72fbb52017-08-02 15:03:31 -07001622 }
Yi Tseng51301292017-07-28 13:02:59 -07001623}