blob: a65ebc7128317dead2ed6cbd6b11ecadb160d62d [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 Tseng2fe8f3f2017-09-07 16:22:51 -070020import com.google.common.collect.Lists;
Yi Tseng51301292017-07-28 13:02:59 -070021import com.google.common.collect.Sets;
Yi Tsenge72fbb52017-08-02 15:03:31 -070022import org.apache.felix.scr.annotations.Activate;
Yi Tseng51301292017-07-28 13:02:59 -070023import org.apache.felix.scr.annotations.Component;
Yi Tsenge72fbb52017-08-02 15:03:31 -070024import org.apache.felix.scr.annotations.Deactivate;
Yi Tseng51301292017-07-28 13:02:59 -070025import org.apache.felix.scr.annotations.Property;
26import org.apache.felix.scr.annotations.Reference;
27import org.apache.felix.scr.annotations.ReferenceCardinality;
28import org.apache.felix.scr.annotations.Service;
29import org.onlab.packet.BasePacket;
30import org.onlab.packet.DHCP;
31import org.onlab.packet.Ethernet;
32import org.onlab.packet.IPv4;
33import org.onlab.packet.Ip4Address;
34import org.onlab.packet.IpAddress;
35import org.onlab.packet.MacAddress;
36import org.onlab.packet.UDP;
37import org.onlab.packet.VlanId;
38import org.onlab.packet.dhcp.CircuitId;
39import org.onlab.packet.dhcp.DhcpOption;
40import org.onlab.packet.dhcp.DhcpRelayAgentOption;
41import org.onosproject.dhcprelay.api.DhcpHandler;
Yi Tseng2fe8f3f2017-09-07 16:22:51 -070042import org.onosproject.dhcprelay.api.DhcpServerInfo;
Yi Tsenge72fbb52017-08-02 15:03:31 -070043import org.onosproject.dhcprelay.config.DhcpServerConfig;
Yi Tseng51301292017-07-28 13:02:59 -070044import org.onosproject.dhcprelay.store.DhcpRecord;
45import org.onosproject.dhcprelay.store.DhcpRelayStore;
Yi Tsenge72fbb52017-08-02 15:03:31 -070046import org.onosproject.net.host.HostEvent;
47import org.onosproject.net.host.HostListener;
Yi Tseng4b013202017-09-08 17:22:51 -070048import org.onosproject.net.host.HostProvider;
49import org.onosproject.net.host.HostProviderRegistry;
50import org.onosproject.net.host.HostProviderService;
Ray Milkeyfacf2862017-08-03 11:58:29 -070051import org.onosproject.net.intf.Interface;
52import org.onosproject.net.intf.InterfaceService;
Yi Tseng4b013202017-09-08 17:22:51 -070053import org.onosproject.net.provider.ProviderId;
Ray Milkey69ec8712017-08-08 13:00:43 -070054import org.onosproject.routeservice.Route;
55import org.onosproject.routeservice.RouteStore;
Yi Tseng51301292017-07-28 13:02:59 -070056import org.onosproject.net.ConnectPoint;
57import org.onosproject.net.Host;
58import org.onosproject.net.HostId;
59import org.onosproject.net.HostLocation;
60import org.onosproject.net.flow.DefaultTrafficTreatment;
61import org.onosproject.net.flow.TrafficTreatment;
62import org.onosproject.net.host.DefaultHostDescription;
63import org.onosproject.net.host.HostDescription;
64import org.onosproject.net.host.HostService;
Yi Tseng51301292017-07-28 13:02:59 -070065import org.onosproject.net.host.InterfaceIpAddress;
66import org.onosproject.net.packet.DefaultOutboundPacket;
67import org.onosproject.net.packet.OutboundPacket;
68import org.onosproject.net.packet.PacketContext;
69import org.onosproject.net.packet.PacketService;
70import org.slf4j.Logger;
71import org.slf4j.LoggerFactory;
72
73import java.nio.ByteBuffer;
Yi Tsengdcef2c22017-08-05 20:34:06 -070074import java.util.Collection;
Yi Tseng51301292017-07-28 13:02:59 -070075import java.util.Collections;
76import java.util.List;
77import java.util.Optional;
78import java.util.Set;
79import java.util.stream.Collectors;
80
81import static com.google.common.base.Preconditions.checkNotNull;
82import static com.google.common.base.Preconditions.checkState;
83import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_CircuitID;
84import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_END;
85import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
86import static org.onlab.packet.MacAddress.valueOf;
87import static org.onlab.packet.dhcp.DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID;
88
89@Component
90@Service
91@Property(name = "version", value = "4")
Yi Tseng4b013202017-09-08 17:22:51 -070092public class Dhcp4HandlerImpl implements DhcpHandler, HostProvider {
Charles Chan75edab72017-09-12 17:09:32 -070093 public static final String DHCP_V4_RELAY_APP = "org.onosproject.Dhcp4HandlerImpl";
94 public static final ProviderId PROVIDER_ID = new ProviderId("dhcp4", DHCP_V4_RELAY_APP);
Yi Tseng51301292017-07-28 13:02:59 -070095 private static Logger log = LoggerFactory.getLogger(Dhcp4HandlerImpl.class);
96
97 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
98 protected DhcpRelayStore dhcpRelayStore;
99
100 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
101 protected PacketService packetService;
102
103 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Yi Tseng51301292017-07-28 13:02:59 -0700104 protected RouteStore routeStore;
105
106 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
107 protected InterfaceService interfaceService;
108
109 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
110 protected HostService hostService;
111
Yi Tseng4b013202017-09-08 17:22:51 -0700112 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
113 protected HostProviderRegistry providerRegistry;
114
115 protected HostProviderService providerService;
Yi Tsenge72fbb52017-08-02 15:03:31 -0700116 private InternalHostListener hostListener = new InternalHostListener();
117
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700118 private List<DhcpServerInfo> defaultServerInfoList = Lists.newArrayList();
119 private List<DhcpServerInfo> indirectServerInfoList = Lists.newArrayList();
Yi Tseng4ec727d2017-08-31 11:21:00 -0700120
Yi Tsenge72fbb52017-08-02 15:03:31 -0700121 @Activate
122 protected void activate() {
123 hostService.addListener(hostListener);
Yi Tseng4b013202017-09-08 17:22:51 -0700124 providerService = providerRegistry.register(this);
Yi Tsenge72fbb52017-08-02 15:03:31 -0700125 }
126
127 @Deactivate
128 protected void deactivate() {
Yi Tseng4b013202017-09-08 17:22:51 -0700129 providerRegistry.unregister(this);
130 hostService.removeListener(hostListener);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700131 defaultServerInfoList.forEach(this::stopMonitoringIps);
132 defaultServerInfoList.clear();
133 indirectServerInfoList.forEach(this::stopMonitoringIps);
134 indirectServerInfoList.clear();
Yi Tsenge72fbb52017-08-02 15:03:31 -0700135 }
136
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700137 private void stopMonitoringIps(DhcpServerInfo serverInfo) {
138 serverInfo.getDhcpGatewayIp4().ifPresent(gatewayIp -> {
139 hostService.stopMonitoringIp(gatewayIp);
140 });
141 serverInfo.getDhcpServerIp4().ifPresent(serverIp -> {
142 hostService.stopMonitoringIp(serverIp);
143 });
Yi Tseng51301292017-07-28 13:02:59 -0700144 }
145
146 @Override
Yi Tsenge72fbb52017-08-02 15:03:31 -0700147 public void setDefaultDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700148 setDhcpServerConfigs(configs, defaultServerInfoList);
149 }
150
151 @Override
152 public void setIndirectDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
153 setDhcpServerConfigs(configs, indirectServerInfoList);
154 }
155
156 @Override
157 public List<DhcpServerInfo> getDefaultDhcpServerInfoList() {
158 return defaultServerInfoList;
159 }
160
161 @Override
162 public List<DhcpServerInfo> getIndirectDhcpServerInfoList() {
163 return indirectServerInfoList;
164 }
165
166 public void setDhcpServerConfigs(Collection<DhcpServerConfig> configs, List<DhcpServerInfo> serverInfoList) {
Yi Tsenge72fbb52017-08-02 15:03:31 -0700167 if (configs.size() == 0) {
168 // no config to update
169 return;
170 }
171
172 // TODO: currently we pick up first DHCP server config.
173 // Will use other server configs in the future for HA.
174 DhcpServerConfig serverConfig = configs.iterator().next();
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700175
Yi Tsengaefbb002017-09-08 16:23:32 -0700176 if (!serverConfig.getDhcpServerIp4().isPresent()) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700177 // not a DHCPv4 config
Yi Tsenge72fbb52017-08-02 15:03:31 -0700178 return;
179 }
180
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700181 if (!serverInfoList.isEmpty()) {
182 // remove old server info
183 DhcpServerInfo oldServerInfo = serverInfoList.remove(0);
Yi Tsenge72fbb52017-08-02 15:03:31 -0700184
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700185 // stop monitoring gateway or server
186 oldServerInfo.getDhcpGatewayIp4().ifPresent(gatewayIp -> {
187 hostService.stopMonitoringIp(gatewayIp);
188 });
189 oldServerInfo.getDhcpServerIp4().ifPresent(serverIp -> {
190 hostService.stopMonitoringIp(serverIp);
191 });
Yi Tsenge72fbb52017-08-02 15:03:31 -0700192 }
Yi Tseng4fa05832017-08-17 13:08:31 -0700193
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700194 // Create new server info according to the config
195 DhcpServerInfo newServerInfo = new DhcpServerInfo(serverConfig,
196 DhcpServerInfo.Version.DHCP_V4);
197 checkState(newServerInfo.getDhcpServerConnectPoint().isPresent(),
Yi Tseng4ec727d2017-08-31 11:21:00 -0700198 "Connect point not exists");
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700199 checkState(newServerInfo.getDhcpServerIp4().isPresent(),
Yi Tseng4ec727d2017-08-31 11:21:00 -0700200 "IP of DHCP server not exists");
Yi Tseng4ec727d2017-08-31 11:21:00 -0700201
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700202 log.debug("DHCP server connect point: {}", newServerInfo.getDhcpServerConnectPoint().orElse(null));
203 log.debug("DHCP server IP: {}", newServerInfo.getDhcpServerIp4().orElse(null));
204
205 IpAddress ipToProbe;
206 if (newServerInfo.getDhcpGatewayIp4().isPresent()) {
207 ipToProbe = newServerInfo.getDhcpGatewayIp4().get();
208 } else {
209 ipToProbe = newServerInfo.getDhcpServerIp4().orElse(null);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700210 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700211 String hostToProbe = newServerInfo.getDhcpGatewayIp4()
212 .map(ip -> "gateway").orElse("server");
Yi Tseng4ec727d2017-08-31 11:21:00 -0700213
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700214 log.debug("Probing to resolve {} IP {}", hostToProbe, ipToProbe);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700215 hostService.startMonitoringIp(ipToProbe);
216
217 Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
218 if (!hosts.isEmpty()) {
219 Host host = hosts.iterator().next();
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700220 newServerInfo.setDhcpConnectVlan(host.vlan());
221 newServerInfo.setDhcpConnectMac(host.mac());
Yi Tseng4ec727d2017-08-31 11:21:00 -0700222 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700223 // Add new server info
224 serverInfoList.add(0, newServerInfo);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700225
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700226 // Remove duplicated server info
227 Set<DhcpServerInfo> nonDupServerInfoList = Sets.newLinkedHashSet();
228 nonDupServerInfoList.addAll(serverInfoList);
229 serverInfoList.clear();
230 serverInfoList.addAll(nonDupServerInfoList);
Yi Tsenge72fbb52017-08-02 15:03:31 -0700231 }
232
Yi Tseng4fa05832017-08-17 13:08:31 -0700233 @Override
Yi Tseng51301292017-07-28 13:02:59 -0700234 public void processDhcpPacket(PacketContext context, BasePacket payload) {
235 checkNotNull(payload, "DHCP payload can't be null");
236 checkState(payload instanceof DHCP, "Payload is not a DHCP");
237 DHCP dhcpPayload = (DHCP) payload;
238 if (!configured()) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700239 log.warn("Missing default DHCP relay server config. Abort packet processing");
Yi Tseng51301292017-07-28 13:02:59 -0700240 return;
241 }
242
243 ConnectPoint inPort = context.inPacket().receivedFrom();
Yi Tseng51301292017-07-28 13:02:59 -0700244 checkNotNull(dhcpPayload, "Can't find DHCP payload");
245 Ethernet packet = context.inPacket().parsed();
246 DHCP.MsgType incomingPacketType = dhcpPayload.getOptions().stream()
247 .filter(dhcpOption -> dhcpOption.getCode() == OptionCode_MessageType.getValue())
248 .map(DhcpOption::getData)
249 .map(data -> DHCP.MsgType.getType(data[0]))
250 .findFirst()
251 .orElse(null);
252 checkNotNull(incomingPacketType, "Can't get message type from DHCP payload {}", dhcpPayload);
253 switch (incomingPacketType) {
254 case DHCPDISCOVER:
Yi Tsengdcef2c22017-08-05 20:34:06 -0700255 // Add the gateway IP as virtual interface IP for server to understand
Yi Tseng51301292017-07-28 13:02:59 -0700256 // the lease to be assigned and forward the packet to dhcp server.
257 Ethernet ethernetPacketDiscover =
Yi Tsengdcef2c22017-08-05 20:34:06 -0700258 processDhcpPacketFromClient(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700259 if (ethernetPacketDiscover != null) {
260 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700261 handleDhcpDiscoverAndRequest(ethernetPacketDiscover, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700262 }
263 break;
264 case DHCPOFFER:
265 //reply to dhcp client.
266 Ethernet ethernetPacketOffer = processDhcpPacketFromServer(packet);
267 if (ethernetPacketOffer != null) {
268 writeResponseDhcpRecord(ethernetPacketOffer, dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700269 sendResponseToClient(ethernetPacketOffer, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700270 }
271 break;
272 case DHCPREQUEST:
273 // add the gateway ip as virtual interface ip for server to understand
274 // the lease to be assigned and forward the packet to dhcp server.
275 Ethernet ethernetPacketRequest =
Yi Tsengdcef2c22017-08-05 20:34:06 -0700276 processDhcpPacketFromClient(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700277 if (ethernetPacketRequest != null) {
278 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700279 handleDhcpDiscoverAndRequest(ethernetPacketRequest, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700280 }
281 break;
282 case DHCPACK:
283 // reply to dhcp client.
284 Ethernet ethernetPacketAck = processDhcpPacketFromServer(packet);
285 if (ethernetPacketAck != null) {
286 writeResponseDhcpRecord(ethernetPacketAck, dhcpPayload);
287 handleDhcpAck(ethernetPacketAck, dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700288 sendResponseToClient(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700289 }
290 break;
291 case DHCPRELEASE:
292 // TODO: release the ip address from client
293 break;
294 default:
295 break;
296 }
297 }
298
299 /**
300 * Checks if this app has been configured.
301 *
302 * @return true if all information we need have been initialized
303 */
Yi Tseng4ec727d2017-08-31 11:21:00 -0700304 private boolean configured() {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700305 return !defaultServerInfoList.isEmpty();
Yi Tseng51301292017-07-28 13:02:59 -0700306 }
307
308 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -0700309 * Returns the first interface ip from interface.
Yi Tseng51301292017-07-28 13:02:59 -0700310 *
Yi Tsengdcef2c22017-08-05 20:34:06 -0700311 * @param iface interface of one connect point
Yi Tseng51301292017-07-28 13:02:59 -0700312 * @return the first interface IP; null if not exists an IP address in
313 * these interfaces
314 */
Yi Tseng4fa05832017-08-17 13:08:31 -0700315 private Ip4Address getFirstIpFromInterface(Interface iface) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700316 checkNotNull(iface, "Interface can't be null");
317 return iface.ipAddressesList().stream()
Yi Tseng51301292017-07-28 13:02:59 -0700318 .map(InterfaceIpAddress::ipAddress)
319 .filter(IpAddress::isIp4)
320 .map(IpAddress::getIp4Address)
321 .findFirst()
322 .orElse(null);
323 }
324
325 /**
Yi Tseng4ec727d2017-08-31 11:21:00 -0700326 * Gets Interface facing to the server for default host.
Yi Tsengdcef2c22017-08-05 20:34:06 -0700327 *
328 * @return the Interface facing to the server; null if not found
329 */
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700330 private Interface getDefaultServerInterface() {
331 return getServerInterface(defaultServerInfoList);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700332 }
333
334 /**
Yi Tseng4ec727d2017-08-31 11:21:00 -0700335 * Gets Interface facing to the server for indirect hosts.
336 * Use default server Interface if indirect server not configured.
337 *
338 * @return the Interface facing to the server; null if not found
339 */
340 private Interface getIndirectServerInterface() {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700341 return getServerInterface(indirectServerInfoList);
342 }
343
344 private Interface getServerInterface(List<DhcpServerInfo> serverInfos) {
345 DhcpServerInfo serverInfo = serverInfos.get(0);
346 ConnectPoint dhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
347 VlanId dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
348 if (dhcpServerConnectPoint == null || dhcpConnectVlan == null) {
349 return null;
Yi Tseng4ec727d2017-08-31 11:21:00 -0700350 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700351 return interfaceService.getInterfacesByPort(dhcpServerConnectPoint)
Yi Tseng4ec727d2017-08-31 11:21:00 -0700352 .stream()
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700353 .filter(iface -> interfaceContainsVlan(iface, dhcpConnectVlan))
Yi Tseng4ec727d2017-08-31 11:21:00 -0700354 .findFirst()
355 .orElse(null);
356 }
357
358 /**
359 * Determind if an Interface contains a vlan id.
360 *
361 * @param iface the Interface
362 * @param vlanId the vlan id
363 * @return true if the Interface contains the vlan id
364 */
365 private boolean interfaceContainsVlan(Interface iface, VlanId vlanId) {
Yi Tseng58e74312017-09-30 11:35:42 +0800366 if (vlanId.equals(VlanId.NONE)) {
367 // untagged packet, check if vlan untagged or vlan native is not NONE
368 return !iface.vlanUntagged().equals(VlanId.NONE) ||
369 !iface.vlanNative().equals(VlanId.NONE);
370 }
371 // tagged packet, check if the interface contains the vlan
372 return iface.vlanTagged().contains(vlanId);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700373 }
374
375 /**
Yi Tseng51301292017-07-28 13:02:59 -0700376 * Build the DHCP discover/request packet with gateway IP(unicast packet).
377 *
378 * @param context the packet context
379 * @param ethernetPacket the ethernet payload to process
Yi Tseng51301292017-07-28 13:02:59 -0700380 * @return processed packet
381 */
382 private Ethernet processDhcpPacketFromClient(PacketContext context,
Yi Tsengdcef2c22017-08-05 20:34:06 -0700383 Ethernet ethernetPacket) {
Yi Tseng4ec727d2017-08-31 11:21:00 -0700384 // get dhcp header.
385 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
386 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
387 UDP udpPacket = (UDP) ipv4Packet.getPayload();
388 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
389
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700390
391 VlanId dhcpConnectVlan = null;
392 MacAddress dhcpConnectMac = null;
393 Ip4Address dhcpServerIp = null;
394 Ip4Address relayAgentIp = null;
395
396 VlanId indirectDhcpConnectVlan = null;
397 MacAddress indirectDhcpConnectMac = null;
398 Ip4Address indirectDhcpServerIp = null;
399 Ip4Address indirectRelayAgentIp = null;
400
401 if (!defaultServerInfoList.isEmpty()) {
402 DhcpServerInfo serverInfo = defaultServerInfoList.get(0);
403 dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
404 dhcpConnectMac = serverInfo.getDhcpConnectMac().orElse(null);
405 dhcpServerIp = serverInfo.getDhcpServerIp4().orElse(null);
406 relayAgentIp = serverInfo.getRelayAgentIp4().orElse(null);
407 }
408
409 if (!indirectServerInfoList.isEmpty()) {
410 DhcpServerInfo indirectServerInfo = indirectServerInfoList.get(0);
411 indirectDhcpConnectVlan = indirectServerInfo.getDhcpConnectVlan().orElse(null);
412 indirectDhcpConnectMac = indirectServerInfo.getDhcpConnectMac().orElse(null);
413 indirectDhcpServerIp = indirectServerInfo.getDhcpServerIp4().orElse(null);
414 indirectRelayAgentIp = indirectServerInfo.getRelayAgentIp4().orElse(null);
415 }
416
417
Yi Tsengdcef2c22017-08-05 20:34:06 -0700418 Ip4Address clientInterfaceIp =
419 interfaceService.getInterfacesByPort(context.inPacket().receivedFrom())
420 .stream()
421 .map(Interface::ipAddressesList)
422 .flatMap(Collection::stream)
423 .map(InterfaceIpAddress::ipAddress)
424 .filter(IpAddress::isIp4)
425 .map(IpAddress::getIp4Address)
426 .findFirst()
427 .orElse(null);
428 if (clientInterfaceIp == null) {
429 log.warn("Can't find interface IP for client interface for port {}",
430 context.inPacket().receivedFrom());
431 return null;
432 }
Yi Tseng4ec727d2017-08-31 11:21:00 -0700433 boolean isDirectlyConnected = directlyConnected(dhcpPacket);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700434 Interface serverInterface = isDirectlyConnected ? getDefaultServerInterface() : getIndirectServerInterface();
Yi Tsengdcef2c22017-08-05 20:34:06 -0700435 if (serverInterface == null) {
Yi Tseng4ec727d2017-08-31 11:21:00 -0700436 log.warn("Can't get {} server interface, ignore", isDirectlyConnected ? "direct" : "indirect");
Yi Tsengdcef2c22017-08-05 20:34:06 -0700437 return null;
438 }
Yi Tseng4fa05832017-08-17 13:08:31 -0700439 Ip4Address ipFacingServer = getFirstIpFromInterface(serverInterface);
440 MacAddress macFacingServer = serverInterface.mac();
441 if (ipFacingServer == null || macFacingServer == null) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700442 log.warn("No IP address for server Interface {}", serverInterface);
Yi Tseng51301292017-07-28 13:02:59 -0700443 return null;
444 }
445 if (dhcpConnectMac == null) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700446 log.warn("DHCP Server/Gateway IP not yet resolved .. Aborting DHCP "
Yi Tseng51301292017-07-28 13:02:59 -0700447 + "packet processing from client on port: {}",
Yi Tsengdcef2c22017-08-05 20:34:06 -0700448 context.inPacket().receivedFrom());
Yi Tseng51301292017-07-28 13:02:59 -0700449 return null;
450 }
Yi Tseng4ec727d2017-08-31 11:21:00 -0700451
Yi Tseng4fa05832017-08-17 13:08:31 -0700452 etherReply.setSourceMACAddress(macFacingServer);
Yi Tseng4fa05832017-08-17 13:08:31 -0700453 ipv4Packet.setSourceAddress(ipFacingServer.toInt());
Yi Tseng51301292017-07-28 13:02:59 -0700454
Yi Tseng4ec727d2017-08-31 11:21:00 -0700455 if (isDirectlyConnected) {
Charles Chanb5d24822017-10-10 16:53:32 -0400456 etherReply.setDestinationMACAddress(dhcpConnectMac);
457 etherReply.setVlanID(dhcpConnectVlan.toShort());
458 ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
459
Yi Tseng51301292017-07-28 13:02:59 -0700460 ConnectPoint inPort = context.inPacket().receivedFrom();
461 VlanId vlanId = VlanId.vlanId(ethernetPacket.getVlanID());
462 // add connected in port and vlan
463 CircuitId cid = new CircuitId(inPort.toString(), vlanId);
464 byte[] circuitId = cid.serialize();
465 DhcpOption circuitIdSubOpt = new DhcpOption();
466 circuitIdSubOpt
467 .setCode(CIRCUIT_ID.getValue())
468 .setLength((byte) circuitId.length)
469 .setData(circuitId);
470
471 DhcpRelayAgentOption newRelayAgentOpt = new DhcpRelayAgentOption();
472 newRelayAgentOpt.setCode(OptionCode_CircuitID.getValue());
473 newRelayAgentOpt.addSubOption(circuitIdSubOpt);
474
Charles Chanb5d24822017-10-10 16:53:32 -0400475 // Removes END option first
Yi Tseng51301292017-07-28 13:02:59 -0700476 List<DhcpOption> options = dhcpPacket.getOptions().stream()
477 .filter(opt -> opt.getCode() != OptionCode_END.getValue())
478 .collect(Collectors.toList());
479
480 // push relay agent option
481 options.add(newRelayAgentOpt);
482
483 // make sure option 255(End) is the last option
484 DhcpOption endOption = new DhcpOption();
485 endOption.setCode(OptionCode_END.getValue());
486 options.add(endOption);
487
488 dhcpPacket.setOptions(options);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700489
Charles Chanb5d24822017-10-10 16:53:32 -0400490 // Sets relay agent IP
491 int effectiveRelayAgentIp = relayAgentIp != null ?
492 relayAgentIp.toInt() : clientInterfaceIp.toInt();
493 dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
494 } else {
495 if (indirectDhcpServerIp != null) {
496 // Use indirect server config for indirect packets if configured
497 etherReply.setDestinationMACAddress(indirectDhcpConnectMac);
498 etherReply.setVlanID(indirectDhcpConnectVlan.toShort());
499 ipv4Packet.setDestinationAddress(indirectDhcpServerIp.toInt());
Yi Tseng51301292017-07-28 13:02:59 -0700500
Charles Chanb5d24822017-10-10 16:53:32 -0400501 // Set giaddr if indirect relay agent IP is configured
502 if (indirectRelayAgentIp != null) {
503 dhcpPacket.setGatewayIPAddress(indirectRelayAgentIp.toInt());
504 }
505 } else {
506 // Otherwise, use default server config for indirect packets
507 etherReply.setDestinationMACAddress(dhcpConnectMac);
508 etherReply.setVlanID(dhcpConnectVlan.toShort());
509 ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
Yi Tseng4ec727d2017-08-31 11:21:00 -0700510
Charles Chanb5d24822017-10-10 16:53:32 -0400511 // Set giaddr if direct relay agent IP is configured
512 if (relayAgentIp != null) {
513 dhcpPacket.setGatewayIPAddress(relayAgentIp.toInt());
514 }
Yi Tseng4ec727d2017-08-31 11:21:00 -0700515 }
Yi Tseng4fa05832017-08-17 13:08:31 -0700516 }
517
Yi Tsengf41681e2017-10-03 09:58:19 -0700518 // Remove broadcast flag
519 dhcpPacket.setFlags((short) 0);
520
Yi Tseng51301292017-07-28 13:02:59 -0700521 udpPacket.setPayload(dhcpPacket);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700522 // As a DHCP relay, the source port should be server port( instead
523 // of client port.
Yi Tsengdcef2c22017-08-05 20:34:06 -0700524 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
Yi Tseng51301292017-07-28 13:02:59 -0700525 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
526 ipv4Packet.setPayload(udpPacket);
Charles Chan7edf7642017-10-09 11:07:25 -0400527 ipv4Packet.setTtl((byte) 64);
Yi Tseng51301292017-07-28 13:02:59 -0700528 etherReply.setPayload(ipv4Packet);
529 return etherReply;
530 }
531
532 /**
533 * Writes DHCP record to the store according to the request DHCP packet (Discover, Request).
534 *
535 * @param location the location which DHCP packet comes from
536 * @param ethernet the DHCP packet
537 * @param dhcpPayload the DHCP payload
538 */
539 private void writeRequestDhcpRecord(ConnectPoint location,
540 Ethernet ethernet,
541 DHCP dhcpPayload) {
542 VlanId vlanId = VlanId.vlanId(ethernet.getVlanID());
543 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
544 HostId hostId = HostId.hostId(macAddress, vlanId);
545 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
546 if (record == null) {
547 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
548 } else {
549 record = record.clone();
550 }
551 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
552 record.ip4Status(dhcpPayload.getPacketType());
553 record.setDirectlyConnected(directlyConnected(dhcpPayload));
554 if (!directlyConnected(dhcpPayload)) {
555 // Update gateway mac address if the host is not directly connected
556 record.nextHop(ethernet.getSourceMAC());
557 }
558 record.updateLastSeen();
559 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
560 }
561
562 /**
563 * Writes DHCP record to the store according to the response DHCP packet (Offer, Ack).
564 *
565 * @param ethernet the DHCP packet
566 * @param dhcpPayload the DHCP payload
567 */
568 private void writeResponseDhcpRecord(Ethernet ethernet,
569 DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700570 Optional<Interface> outInterface = getClientInterface(ethernet, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700571 if (!outInterface.isPresent()) {
572 log.warn("Failed to determine where to send {}", dhcpPayload.getPacketType());
573 return;
574 }
575
576 Interface outIface = outInterface.get();
577 ConnectPoint location = outIface.connectPoint();
Yi Tseng4ec727d2017-08-31 11:21:00 -0700578 VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700579 if (vlanId == null) {
580 vlanId = outIface.vlan();
581 }
Yi Tseng51301292017-07-28 13:02:59 -0700582 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
583 HostId hostId = HostId.hostId(macAddress, vlanId);
584 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
585 if (record == null) {
586 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
587 } else {
588 record = record.clone();
589 }
590 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
591 if (dhcpPayload.getPacketType() == DHCP.MsgType.DHCPACK) {
592 record.ip4Address(Ip4Address.valueOf(dhcpPayload.getYourIPAddress()));
593 }
594 record.ip4Status(dhcpPayload.getPacketType());
595 record.setDirectlyConnected(directlyConnected(dhcpPayload));
596 record.updateLastSeen();
597 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
598 }
599
600 /**
601 * Build the DHCP offer/ack with proper client port.
602 *
603 * @param ethernetPacket the original packet comes from server
604 * @return new packet which will send to the client
605 */
606 private Ethernet processDhcpPacketFromServer(Ethernet ethernetPacket) {
607 // get dhcp header.
608 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
609 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
610 UDP udpPacket = (UDP) ipv4Packet.getPayload();
611 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
612
613 // determine the vlanId of the client host - note that this vlan id
614 // could be different from the vlan in the packet from the server
Yi Tsengdcef2c22017-08-05 20:34:06 -0700615 Interface clientInterface = getClientInterface(ethernetPacket, dhcpPayload).orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -0700616
Yi Tsengdcef2c22017-08-05 20:34:06 -0700617 if (clientInterface == null) {
Yi Tseng51301292017-07-28 13:02:59 -0700618 log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
619 return null;
620 }
Yi Tsengdcef2c22017-08-05 20:34:06 -0700621 VlanId vlanId;
622 if (clientInterface.vlanTagged().isEmpty()) {
623 vlanId = clientInterface.vlan();
624 } else {
625 // might be multiple vlan in same interface
Yi Tseng4ec727d2017-08-31 11:21:00 -0700626 vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700627 }
628 if (vlanId == null) {
629 vlanId = VlanId.NONE;
630 }
631 etherReply.setVlanID(vlanId.toShort());
632 etherReply.setSourceMACAddress(clientInterface.mac());
Yi Tseng51301292017-07-28 13:02:59 -0700633
Yi Tsengdcef2c22017-08-05 20:34:06 -0700634 if (!directlyConnected(dhcpPayload)) {
635 // if client is indirectly connected, try use next hop mac address
636 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
637 HostId hostId = HostId.hostId(macAddress, vlanId);
638 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
639 if (record != null) {
640 // if next hop can be found, use mac address of next hop
641 record.nextHop().ifPresent(etherReply::setDestinationMACAddress);
642 } else {
643 // otherwise, discard the packet
644 log.warn("Can't find record for host id {}, discard packet", hostId);
645 return null;
646 }
Yi Tsengc03fa242017-08-17 17:43:38 -0700647 } else {
648 etherReply.setDestinationMACAddress(dhcpPayload.getClientHardwareAddress());
Yi Tsengdcef2c22017-08-05 20:34:06 -0700649 }
650
Yi Tseng51301292017-07-28 13:02:59 -0700651 // we leave the srcMac from the original packet
Yi Tseng51301292017-07-28 13:02:59 -0700652 // figure out the relay agent IP corresponding to the original request
Yi Tseng4fa05832017-08-17 13:08:31 -0700653 Ip4Address ipFacingClient = getFirstIpFromInterface(clientInterface);
654 if (ipFacingClient == null) {
Yi Tseng51301292017-07-28 13:02:59 -0700655 log.warn("Cannot determine relay agent interface Ipv4 addr for host {}/{}. "
656 + "Aborting relay for dhcp packet from server {}",
Yi Tsengdcef2c22017-08-05 20:34:06 -0700657 etherReply.getDestinationMAC(), clientInterface.vlan(),
Yi Tseng51301292017-07-28 13:02:59 -0700658 ethernetPacket);
659 return null;
660 }
661 // SRC_IP: relay agent IP
662 // DST_IP: offered IP
Yi Tseng4fa05832017-08-17 13:08:31 -0700663 ipv4Packet.setSourceAddress(ipFacingClient.toInt());
Yi Tseng51301292017-07-28 13:02:59 -0700664 ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
665 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
666 if (directlyConnected(dhcpPayload)) {
667 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
668 } else {
669 // forward to another dhcp relay
Yi Tseng72b599a2017-09-14 13:24:21 -0700670 // FIXME: Currently we assume the DHCP comes from a L2 relay with
671 // Option 82, this might not work if DHCP message comes from
672 // L3 relay.
673 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
Yi Tseng51301292017-07-28 13:02:59 -0700674 }
675
676 udpPacket.setPayload(dhcpPayload);
677 ipv4Packet.setPayload(udpPacket);
678 etherReply.setPayload(ipv4Packet);
679 return etherReply;
680 }
681
Yi Tsengdcef2c22017-08-05 20:34:06 -0700682 /**
683 * Extracts VLAN ID from relay agent option.
684 *
685 * @param dhcpPayload the DHCP payload
686 * @return VLAN ID from DHCP payload; null if not exists
687 */
Yi Tseng4ec727d2017-08-31 11:21:00 -0700688 private VlanId getVlanIdFromRelayAgentOption(DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700689 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
690 if (option == null) {
691 return null;
692 }
693 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
694 if (circuitIdSubOption == null) {
695 return null;
696 }
697 try {
698 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
699 return circuitId.vlanId();
700 } catch (IllegalArgumentException e) {
701 // can't deserialize the circuit ID
702 return null;
703 }
704 }
705
706 /**
707 * Removes DHCP relay agent information option (option 82) from DHCP payload.
708 * Also reset giaddr to 0
709 *
710 * @param ethPacket the Ethernet packet to be processed
711 * @return Ethernet packet processed
712 */
713 private Ethernet removeRelayAgentOption(Ethernet ethPacket) {
714 Ethernet ethernet = (Ethernet) ethPacket.clone();
715 IPv4 ipv4 = (IPv4) ethernet.getPayload();
716 UDP udp = (UDP) ipv4.getPayload();
717 DHCP dhcpPayload = (DHCP) udp.getPayload();
718
719 // removes relay agent information option
720 List<DhcpOption> options = dhcpPayload.getOptions();
721 options = options.stream()
722 .filter(option -> option.getCode() != OptionCode_CircuitID.getValue())
723 .collect(Collectors.toList());
724 dhcpPayload.setOptions(options);
725 dhcpPayload.setGatewayIPAddress(0);
726
727 udp.setPayload(dhcpPayload);
728 ipv4.setPayload(udp);
729 ethernet.setPayload(ipv4);
730 return ethernet;
731 }
732
Yi Tseng51301292017-07-28 13:02:59 -0700733
734 /**
735 * Check if the host is directly connected to the network or not.
736 *
737 * @param dhcpPayload the dhcp payload
738 * @return true if the host is directly connected to the network; false otherwise
739 */
740 private boolean directlyConnected(DHCP dhcpPayload) {
Yi Tseng2cf59912017-08-24 14:47:34 -0700741 DhcpRelayAgentOption relayAgentOption =
742 (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
Yi Tseng51301292017-07-28 13:02:59 -0700743
744 // Doesn't contains relay option
745 if (relayAgentOption == null) {
746 return true;
747 }
748
Yi Tseng2cf59912017-08-24 14:47:34 -0700749 // check circuit id, if circuit id is invalid, we say it is an indirect host
750 DhcpOption circuitIdOpt = relayAgentOption.getSubOption(CIRCUIT_ID.getValue());
Yi Tseng51301292017-07-28 13:02:59 -0700751
Yi Tseng2cf59912017-08-24 14:47:34 -0700752 try {
753 CircuitId.deserialize(circuitIdOpt.getData());
Yi Tseng51301292017-07-28 13:02:59 -0700754 return true;
Yi Tseng2cf59912017-08-24 14:47:34 -0700755 } catch (Exception e) {
756 // invalid circuit id
757 return false;
Yi Tseng51301292017-07-28 13:02:59 -0700758 }
Yi Tseng51301292017-07-28 13:02:59 -0700759 }
760
761
762 /**
763 * Send the DHCP ack to the requester host.
764 * Modify Host or Route store according to the type of DHCP.
765 *
766 * @param ethernetPacketAck the packet
767 * @param dhcpPayload the DHCP data
768 */
769 private void handleDhcpAck(Ethernet ethernetPacketAck, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700770 Optional<Interface> outInterface = getClientInterface(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700771 if (!outInterface.isPresent()) {
772 log.warn("Can't find output interface for dhcp: {}", dhcpPayload);
773 return;
774 }
775
776 Interface outIface = outInterface.get();
777 HostLocation hostLocation = new HostLocation(outIface.connectPoint(), System.currentTimeMillis());
778 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
Yi Tseng4ec727d2017-08-31 11:21:00 -0700779 VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700780 if (vlanId == null) {
781 vlanId = outIface.vlan();
782 }
Yi Tseng51301292017-07-28 13:02:59 -0700783 HostId hostId = HostId.hostId(macAddress, vlanId);
784 Ip4Address ip = Ip4Address.valueOf(dhcpPayload.getYourIPAddress());
785
786 if (directlyConnected(dhcpPayload)) {
787 // Add to host store if it connect to network directly
788 Set<IpAddress> ips = Sets.newHashSet(ip);
Yi Tseng4b013202017-09-08 17:22:51 -0700789 Host host = hostService.getHost(hostId);
Yi Tseng51301292017-07-28 13:02:59 -0700790
Yi Tseng4b013202017-09-08 17:22:51 -0700791 Set<HostLocation> hostLocations = Sets.newHashSet(hostLocation);
792 if (host != null) {
793 // Dual homing support:
794 // if host exists, use old locations and new location
795 hostLocations.addAll(host.locations());
796 }
797 HostDescription desc = new DefaultHostDescription(macAddress, vlanId,
798 hostLocations, ips, false);
799 // Add IP address when dhcp server give the host new ip address
800 providerService.hostDetected(hostId, desc, false);
Yi Tseng51301292017-07-28 13:02:59 -0700801 } else {
802 // Add to route store if it does not connect to network directly
803 // Get gateway host IP according to host mac address
Yi Tsengdcef2c22017-08-05 20:34:06 -0700804 // TODO: remove relay store here
Yi Tseng51301292017-07-28 13:02:59 -0700805 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
806
807 if (record == null) {
808 log.warn("Can't find DHCP record of host {}", hostId);
809 return;
810 }
811
812 MacAddress gwMac = record.nextHop().orElse(null);
813 if (gwMac == null) {
814 log.warn("Can't find gateway mac address from record {}", record);
815 return;
816 }
817
818 HostId gwHostId = HostId.hostId(gwMac, record.vlanId());
819 Host gwHost = hostService.getHost(gwHostId);
820
821 if (gwHost == null) {
822 log.warn("Can't find gateway host {}", gwHostId);
823 return;
824 }
825
826 Ip4Address nextHopIp = gwHost.ipAddresses()
827 .stream()
828 .filter(IpAddress::isIp4)
829 .map(IpAddress::getIp4Address)
830 .findFirst()
831 .orElse(null);
832
833 if (nextHopIp == null) {
834 log.warn("Can't find IP address of gateway {}", gwHost);
835 return;
836 }
837
838 Route route = new Route(Route.Source.STATIC, ip.toIpPrefix(), nextHopIp);
839 routeStore.updateRoute(route);
840 }
Yi Tseng51301292017-07-28 13:02:59 -0700841 }
842
843 /**
844 * forward the packet to ConnectPoint where the DHCP server is attached.
845 *
846 * @param packet the packet
847 */
Yi Tseng4ec727d2017-08-31 11:21:00 -0700848 private void handleDhcpDiscoverAndRequest(Ethernet packet, DHCP dhcpPayload) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700849 boolean direct = directlyConnected(dhcpPayload);
850 DhcpServerInfo serverInfo = defaultServerInfoList.get(0);
851 if (!direct && !indirectServerInfoList.isEmpty()) {
852 serverInfo = indirectServerInfoList.get(0);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700853 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700854 ConnectPoint portToFotward = serverInfo.getDhcpServerConnectPoint().orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -0700855 // send packet to dhcp server connect point.
Yi Tseng4ec727d2017-08-31 11:21:00 -0700856 if (portToFotward != null) {
Yi Tseng51301292017-07-28 13:02:59 -0700857 TrafficTreatment t = DefaultTrafficTreatment.builder()
Yi Tseng4ec727d2017-08-31 11:21:00 -0700858 .setOutput(portToFotward.port()).build();
Yi Tseng51301292017-07-28 13:02:59 -0700859 OutboundPacket o = new DefaultOutboundPacket(
Yi Tseng4ec727d2017-08-31 11:21:00 -0700860 portToFotward.deviceId(), t, ByteBuffer.wrap(packet.serialize()));
Yi Tseng51301292017-07-28 13:02:59 -0700861 if (log.isTraceEnabled()) {
862 log.trace("Relaying packet to dhcp server {}", packet);
863 }
864 packetService.emit(o);
865 } else {
866 log.warn("Can't find DHCP server connect point, abort.");
867 }
868 }
869
870
871 /**
872 * Gets output interface of a dhcp packet.
873 * If option 82 exists in the dhcp packet and the option was sent by
Yi Tseng4ec727d2017-08-31 11:21:00 -0700874 * ONOS (circuit format is correct), use the connect
Yi Tseng51301292017-07-28 13:02:59 -0700875 * point and vlan id from circuit id; otherwise, find host by destination
876 * address and use vlan id from sender (dhcp server).
877 *
878 * @param ethPacket the ethernet packet
879 * @param dhcpPayload the dhcp packet
880 * @return an interface represent the output port and vlan; empty value
881 * if the host or circuit id not found
882 */
Yi Tsengdcef2c22017-08-05 20:34:06 -0700883 private Optional<Interface> getClientInterface(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tseng51301292017-07-28 13:02:59 -0700884 VlanId originalPacketVlanId = VlanId.vlanId(ethPacket.getVlanID());
Yi Tseng51301292017-07-28 13:02:59 -0700885 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
886
Yi Tseng4ec727d2017-08-31 11:21:00 -0700887 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
888 try {
889 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
890 ConnectPoint connectPoint = ConnectPoint.deviceConnectPoint(circuitId.connectPoint());
891 VlanId vlanId = circuitId.vlanId();
892 return interfaceService.getInterfacesByPort(connectPoint)
893 .stream()
894 .filter(iface -> interfaceContainsVlan(iface, vlanId))
895 .findFirst();
896 } catch (IllegalArgumentException ex) {
897 // invalid circuit format, didn't sent by ONOS
898 log.debug("Invalid circuit {}, use information from dhcp payload",
899 circuitIdSubOption.getData());
Yi Tseng51301292017-07-28 13:02:59 -0700900 }
901
902 // Use Vlan Id from DHCP server if DHCP relay circuit id was not
903 // sent by ONOS or circuit Id can't be parsed
Yi Tsengdcef2c22017-08-05 20:34:06 -0700904 // TODO: remove relay store from this method
Yi Tseng51301292017-07-28 13:02:59 -0700905 MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
906 Optional<DhcpRecord> dhcpRecord = dhcpRelayStore.getDhcpRecord(HostId.hostId(dstMac, originalPacketVlanId));
Yi Tsengdcef2c22017-08-05 20:34:06 -0700907 ConnectPoint clientConnectPoint = dhcpRecord
Yi Tseng51301292017-07-28 13:02:59 -0700908 .map(DhcpRecord::locations)
909 .orElse(Collections.emptySet())
910 .stream()
911 .reduce((hl1, hl2) -> {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700912 // find latest host connect point
Yi Tseng51301292017-07-28 13:02:59 -0700913 if (hl1 == null || hl2 == null) {
914 return hl1 == null ? hl2 : hl1;
915 }
916 return hl1.time() > hl2.time() ? hl1 : hl2;
917 })
Yi Tsengdcef2c22017-08-05 20:34:06 -0700918 .orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -0700919
Yi Tsengdcef2c22017-08-05 20:34:06 -0700920 if (clientConnectPoint != null) {
921 return interfaceService.getInterfacesByPort(clientConnectPoint)
922 .stream()
Yi Tseng4ec727d2017-08-31 11:21:00 -0700923 .filter(iface -> interfaceContainsVlan(iface, originalPacketVlanId))
Yi Tsengdcef2c22017-08-05 20:34:06 -0700924 .findFirst();
925 }
926 return Optional.empty();
Yi Tseng51301292017-07-28 13:02:59 -0700927 }
928
929 /**
930 * Send the response DHCP to the requester host.
931 *
932 * @param ethPacket the packet
933 * @param dhcpPayload the DHCP data
934 */
935 private void sendResponseToClient(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700936 Optional<Interface> outInterface = getClientInterface(ethPacket, dhcpPayload);
937 if (directlyConnected(dhcpPayload)) {
938 ethPacket = removeRelayAgentOption(ethPacket);
939 }
940 if (!outInterface.isPresent()) {
941 log.warn("Can't find output interface for client, ignore");
942 return;
943 }
944 Interface outIface = outInterface.get();
945 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
946 .setOutput(outIface.connectPoint().port())
947 .build();
948 OutboundPacket o = new DefaultOutboundPacket(
949 outIface.connectPoint().deviceId(),
950 treatment,
951 ByteBuffer.wrap(ethPacket.serialize()));
952 if (log.isTraceEnabled()) {
953 log.trace("Relaying packet to DHCP client {} via {}, vlan {}",
954 ethPacket,
955 outIface.connectPoint(),
956 outIface.vlan());
957 }
958 packetService.emit(o);
Yi Tseng51301292017-07-28 13:02:59 -0700959 }
Yi Tsenge72fbb52017-08-02 15:03:31 -0700960
Yi Tseng4b013202017-09-08 17:22:51 -0700961 @Override
962 public void triggerProbe(Host host) {
963 // Do nothing here
964 }
965
966 @Override
967 public ProviderId id() {
Charles Chan75edab72017-09-12 17:09:32 -0700968 return PROVIDER_ID;
Yi Tseng4b013202017-09-08 17:22:51 -0700969 }
970
Yi Tsenge72fbb52017-08-02 15:03:31 -0700971 class InternalHostListener implements HostListener {
972 @Override
973 public void event(HostEvent event) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700974 if (!configured()) {
975 return;
976 }
Yi Tsenge72fbb52017-08-02 15:03:31 -0700977 switch (event.type()) {
978 case HOST_ADDED:
979 case HOST_UPDATED:
980 hostUpdated(event.subject());
981 break;
982 case HOST_REMOVED:
983 hostRemoved(event.subject());
984 break;
985 case HOST_MOVED:
986 hostMoved(event.subject());
987 break;
988 default:
989 break;
990 }
991 }
992 }
993
994 /**
995 * Handle host move.
996 * If the host DHCP server or gateway and it moved to the location different
997 * to user configured, unsets the connect mac and vlan
998 *
999 * @param host the host
1000 */
1001 private void hostMoved(Host host) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001002 Set<ConnectPoint> hostConnectPoints = host.locations().stream()
1003 .map(hl -> new ConnectPoint(hl.elementId(), hl.port()))
1004 .collect(Collectors.toSet());
1005 DhcpServerInfo serverInfo;
1006 ConnectPoint dhcpServerConnectPoint;
1007 Ip4Address dhcpGatewayIp;
1008 Ip4Address dhcpServerIp;
1009
1010 if (!defaultServerInfoList.isEmpty()) {
1011 serverInfo = defaultServerInfoList.get(0);
1012 dhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
1013 dhcpGatewayIp = serverInfo.getDhcpGatewayIp4().orElse(null);
1014 dhcpServerIp = serverInfo.getDhcpServerIp4().orElse(null);
1015 if (dhcpGatewayIp != null) {
1016 if (host.ipAddresses().contains(dhcpGatewayIp) &&
1017 !hostConnectPoints.contains(dhcpServerConnectPoint)) {
1018 serverInfo.setDhcpConnectVlan(null);
1019 serverInfo.setDhcpConnectMac(null);
1020 }
1021 }
1022 if (dhcpServerIp != null) {
1023 if (host.ipAddresses().contains(dhcpServerIp) &&
1024 !hostConnectPoints.contains(dhcpServerConnectPoint)) {
1025 serverInfo.setDhcpConnectVlan(null);
1026 serverInfo.setDhcpConnectMac(null);
1027 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001028 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001029 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001030
1031 if (!indirectServerInfoList.isEmpty()) {
1032 // Indirect server
1033 serverInfo = indirectServerInfoList.get(0);
1034 dhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
1035 dhcpGatewayIp = serverInfo.getDhcpGatewayIp4().orElse(null);
1036 dhcpServerIp = serverInfo.getDhcpServerIp4().orElse(null);
1037 if (dhcpGatewayIp != null) {
1038 if (host.ipAddresses().contains(dhcpGatewayIp) &&
1039 !hostConnectPoints.contains(dhcpServerConnectPoint)) {
1040 serverInfo.setDhcpConnectVlan(null);
1041 serverInfo.setDhcpConnectMac(null);
1042 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001043 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001044 if (dhcpServerIp != null) {
1045 if (host.ipAddresses().contains(dhcpServerIp) &&
1046 !hostConnectPoints.contains(dhcpServerConnectPoint)) {
1047 serverInfo.setDhcpConnectVlan(null);
1048 serverInfo.setDhcpConnectMac(null);
1049 }
Yi Tseng4ec727d2017-08-31 11:21:00 -07001050 }
1051 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001052 }
1053
1054 /**
1055 * Handle host updated.
1056 * If the host is DHCP server or gateway, update connect mac and vlan.
1057 *
1058 * @param host the host
1059 */
1060 private void hostUpdated(Host host) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001061 DhcpServerInfo serverInfo;
1062 Ip4Address dhcpGatewayIp;
1063 Ip4Address dhcpServerIp;
1064
1065 if (!defaultServerInfoList.isEmpty()) {
1066 serverInfo = defaultServerInfoList.get(0);
1067 dhcpGatewayIp = serverInfo.getDhcpGatewayIp4().orElse(null);
1068 dhcpServerIp = serverInfo.getDhcpServerIp4().orElse(null);
1069 if (dhcpGatewayIp != null) {
1070 if (host.ipAddresses().contains(dhcpGatewayIp)) {
1071 serverInfo.setDhcpConnectMac(host.mac());
1072 serverInfo.setDhcpConnectVlan(host.vlan());
1073 }
1074 }
1075 if (dhcpServerIp != null) {
1076 if (host.ipAddresses().contains(dhcpServerIp)) {
1077 serverInfo.setDhcpConnectMac(host.mac());
1078 serverInfo.setDhcpConnectVlan(host.vlan());
1079 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001080 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001081 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001082
1083 if (!indirectServerInfoList.isEmpty()) {
1084 serverInfo = indirectServerInfoList.get(0);
1085 dhcpGatewayIp = serverInfo.getDhcpGatewayIp4().orElse(null);
1086 dhcpServerIp = serverInfo.getDhcpServerIp4().orElse(null);
1087 if (dhcpGatewayIp != null) {
1088 if (host.ipAddresses().contains(dhcpGatewayIp)) {
1089 serverInfo.setDhcpConnectMac(host.mac());
1090 serverInfo.setDhcpConnectVlan(host.vlan());
1091 }
1092 }
1093 if (dhcpServerIp != null) {
1094 if (host.ipAddresses().contains(dhcpServerIp)) {
1095 serverInfo.setDhcpConnectMac(host.mac());
1096 serverInfo.setDhcpConnectVlan(host.vlan());
1097 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001098 }
1099 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001100
Yi Tsenge72fbb52017-08-02 15:03:31 -07001101 }
1102
1103 /**
1104 * Handle host removed.
1105 * If the host is DHCP server or gateway, unset connect mac and vlan.
1106 *
1107 * @param host the host
1108 */
1109 private void hostRemoved(Host host) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001110 DhcpServerInfo serverInfo;
1111 Ip4Address dhcpGatewayIp;
1112 Ip4Address dhcpServerIp;
1113 if (!defaultServerInfoList.isEmpty()) {
1114 serverInfo = defaultServerInfoList.get(0);
1115 dhcpGatewayIp = serverInfo.getDhcpGatewayIp4().orElse(null);
1116 dhcpServerIp = serverInfo.getDhcpServerIp4().orElse(null);
1117
1118 if (dhcpGatewayIp != null) {
1119 if (host.ipAddresses().contains(dhcpGatewayIp)) {
1120 serverInfo.setDhcpConnectVlan(null);
1121 serverInfo.setDhcpConnectMac(null);
1122 }
1123 }
1124 if (dhcpServerIp != null) {
1125 if (host.ipAddresses().contains(dhcpServerIp)) {
1126 serverInfo.setDhcpConnectVlan(null);
1127 serverInfo.setDhcpConnectMac(null);
1128 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001129 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001130 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001131
1132 if (!indirectServerInfoList.isEmpty()) {
1133 serverInfo = indirectServerInfoList.get(0);
1134 dhcpGatewayIp = serverInfo.getDhcpGatewayIp4().orElse(null);
1135 dhcpServerIp = serverInfo.getDhcpServerIp4().orElse(null);
1136
1137 if (dhcpGatewayIp != null) {
1138 if (host.ipAddresses().contains(dhcpGatewayIp)) {
1139 serverInfo.setDhcpConnectVlan(null);
1140 serverInfo.setDhcpConnectMac(null);
1141 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001142 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001143 if (dhcpServerIp != null) {
1144 if (host.ipAddresses().contains(dhcpServerIp)) {
1145 serverInfo.setDhcpConnectVlan(null);
1146 serverInfo.setDhcpConnectMac(null);
1147 }
Yi Tseng4ec727d2017-08-31 11:21:00 -07001148 }
1149 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001150 }
Yi Tseng51301292017-07-28 13:02:59 -07001151}