blob: d2c146bcd5b253cc5eb656eee3095ff287c221db [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 Tseng7da339e2017-10-23 19:39:39 -070022import com.google.common.collect.Multimap;
Charles Chan1122cae2018-03-07 17:36:06 -080023import com.google.common.collect.Multimaps;
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;
Charles Chana18afb82018-03-05 13:14:02 -080099import java.util.concurrent.CopyOnWriteArrayList;
Yi Tseng7da339e2017-10-23 19:39:39 -0700100import java.util.concurrent.atomic.AtomicInteger;
Yi Tseng51301292017-07-28 13:02:59 -0700101import java.util.stream.Collectors;
102
103import static com.google.common.base.Preconditions.checkNotNull;
104import static com.google.common.base.Preconditions.checkState;
105import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_CircuitID;
106import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_END;
107import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
108import static org.onlab.packet.MacAddress.valueOf;
109import static org.onlab.packet.dhcp.DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID;
Yi Tseng7da339e2017-10-23 19:39:39 -0700110import static org.onosproject.net.flowobjective.Objective.Operation.ADD;
111import static org.onosproject.net.flowobjective.Objective.Operation.REMOVE;
Yi Tseng51301292017-07-28 13:02:59 -0700112
113@Component
114@Service
115@Property(name = "version", value = "4")
Yi Tseng4b013202017-09-08 17:22:51 -0700116public class Dhcp4HandlerImpl implements DhcpHandler, HostProvider {
Charles Chan75edab72017-09-12 17:09:32 -0700117 public static final String DHCP_V4_RELAY_APP = "org.onosproject.Dhcp4HandlerImpl";
118 public static final ProviderId PROVIDER_ID = new ProviderId("dhcp4", DHCP_V4_RELAY_APP);
Yi Tseng7da339e2017-10-23 19:39:39 -0700119 private static final String BROADCAST_IP = "255.255.255.255";
120 private static final int IGNORE_CONTROL_PRIORITY = PacketPriority.CONTROL.priorityValue() + 1000;
121
122 private static final TrafficSelector CLIENT_SERVER_SELECTOR = DefaultTrafficSelector.builder()
123 .matchEthType(Ethernet.TYPE_IPV4)
124 .matchIPProtocol(IPv4.PROTOCOL_UDP)
125 .matchIPSrc(Ip4Address.ZERO.toIpPrefix())
126 .matchIPDst(Ip4Address.valueOf(BROADCAST_IP).toIpPrefix())
127 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
128 .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
129 .build();
130 private static final TrafficSelector SERVER_RELAY_SELECTOR = DefaultTrafficSelector.builder()
131 .matchEthType(Ethernet.TYPE_IPV4)
132 .matchIPProtocol(IPv4.PROTOCOL_UDP)
133 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
134 .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
135 .build();
136 static final Set<TrafficSelector> DHCP_SELECTORS = ImmutableSet.of(
137 CLIENT_SERVER_SELECTOR,
138 SERVER_RELAY_SELECTOR
139 );
Yi Tseng51301292017-07-28 13:02:59 -0700140 private static Logger log = LoggerFactory.getLogger(Dhcp4HandlerImpl.class);
141
142 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
143 protected DhcpRelayStore dhcpRelayStore;
144
145 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
146 protected PacketService packetService;
147
148 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Yi Tseng51301292017-07-28 13:02:59 -0700149 protected RouteStore routeStore;
150
151 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
152 protected InterfaceService interfaceService;
153
154 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
155 protected HostService hostService;
156
Yi Tseng4b013202017-09-08 17:22:51 -0700157 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
158 protected HostProviderRegistry providerRegistry;
159
Yi Tseng7da339e2017-10-23 19:39:39 -0700160 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
161 protected CoreService coreService;
162
163 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
164 protected DeviceService deviceService;
165
166 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
167 protected FlowObjectiveService flowObjectiveService;
168
Yi Tseng4b013202017-09-08 17:22:51 -0700169 protected HostProviderService providerService;
Yi Tseng7da339e2017-10-23 19:39:39 -0700170 protected ApplicationId appId;
Charles Chan1122cae2018-03-07 17:36:06 -0800171 protected Multimap<DeviceId, VlanId> ignoredVlans = Multimaps.synchronizedMultimap(HashMultimap.create());
Yi Tsenge72fbb52017-08-02 15:03:31 -0700172 private InternalHostListener hostListener = new InternalHostListener();
173
Charles Chana18afb82018-03-05 13:14:02 -0800174 private List<DhcpServerInfo> defaultServerInfoList = new CopyOnWriteArrayList<>();
175 private List<DhcpServerInfo> indirectServerInfoList = new CopyOnWriteArrayList<>();
Yi Tseng4ec727d2017-08-31 11:21:00 -0700176
Yi Tsenge72fbb52017-08-02 15:03:31 -0700177 @Activate
178 protected void activate() {
Yi Tseng7da339e2017-10-23 19:39:39 -0700179 appId = coreService.registerApplication(DHCP_V4_RELAY_APP);
Yi Tsenge72fbb52017-08-02 15:03:31 -0700180 hostService.addListener(hostListener);
Yi Tseng4b013202017-09-08 17:22:51 -0700181 providerService = providerRegistry.register(this);
Yi Tsenge72fbb52017-08-02 15:03:31 -0700182 }
183
184 @Deactivate
185 protected void deactivate() {
Yi Tseng4b013202017-09-08 17:22:51 -0700186 providerRegistry.unregister(this);
187 hostService.removeListener(hostListener);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700188 defaultServerInfoList.forEach(this::stopMonitoringIps);
189 defaultServerInfoList.clear();
190 indirectServerInfoList.forEach(this::stopMonitoringIps);
191 indirectServerInfoList.clear();
Yi Tsenge72fbb52017-08-02 15:03:31 -0700192 }
193
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700194 private void stopMonitoringIps(DhcpServerInfo serverInfo) {
195 serverInfo.getDhcpGatewayIp4().ifPresent(gatewayIp -> {
196 hostService.stopMonitoringIp(gatewayIp);
197 });
198 serverInfo.getDhcpServerIp4().ifPresent(serverIp -> {
199 hostService.stopMonitoringIp(serverIp);
200 });
Yi Tseng51301292017-07-28 13:02:59 -0700201 }
202
203 @Override
Yi Tsenge72fbb52017-08-02 15:03:31 -0700204 public void setDefaultDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700205 setDhcpServerConfigs(configs, defaultServerInfoList);
206 }
207
208 @Override
209 public void setIndirectDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
210 setDhcpServerConfigs(configs, indirectServerInfoList);
211 }
212
213 @Override
214 public List<DhcpServerInfo> getDefaultDhcpServerInfoList() {
215 return defaultServerInfoList;
216 }
217
218 @Override
219 public List<DhcpServerInfo> getIndirectDhcpServerInfoList() {
220 return indirectServerInfoList;
221 }
222
Yi Tseng7da339e2017-10-23 19:39:39 -0700223 @Override
224 public void updateIgnoreVlanConfig(IgnoreDhcpConfig config) {
225 if (config == null) {
226 ignoredVlans.forEach(((deviceId, vlanId) -> {
227 processIgnoreVlanRule(deviceId, vlanId, REMOVE);
228 }));
229 return;
230 }
231 config.ignoredVlans().forEach((deviceId, vlanId) -> {
232 if (ignoredVlans.get(deviceId).contains(vlanId)) {
233 // don't need to process if it already ignored
234 return;
235 }
236 processIgnoreVlanRule(deviceId, vlanId, ADD);
237 });
238
239 ignoredVlans.forEach((deviceId, vlanId) -> {
240 if (!config.ignoredVlans().get(deviceId).contains(vlanId)) {
241 // not contains in new config, remove it
242 processIgnoreVlanRule(deviceId, vlanId, REMOVE);
243 }
244 });
245 }
246
Saurav Dasba7eb1a2017-12-13 16:19:35 -0800247 @Override
248 public void removeIgnoreVlanState(IgnoreDhcpConfig config) {
249 if (config == null) {
250 ignoredVlans.clear();
251 return;
252 }
253 config.ignoredVlans().forEach((deviceId, vlanId) -> {
254 ignoredVlans.remove(deviceId, vlanId);
255 });
256 }
257
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700258 public void setDhcpServerConfigs(Collection<DhcpServerConfig> configs, List<DhcpServerInfo> serverInfoList) {
Yi Tsenge72fbb52017-08-02 15:03:31 -0700259 if (configs.size() == 0) {
260 // no config to update
261 return;
262 }
263
264 // TODO: currently we pick up first DHCP server config.
265 // Will use other server configs in the future for HA.
266 DhcpServerConfig serverConfig = configs.iterator().next();
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700267
Yi Tsengaefbb002017-09-08 16:23:32 -0700268 if (!serverConfig.getDhcpServerIp4().isPresent()) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700269 // not a DHCPv4 config
Yi Tsenge72fbb52017-08-02 15:03:31 -0700270 return;
271 }
272
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700273 if (!serverInfoList.isEmpty()) {
274 // remove old server info
275 DhcpServerInfo oldServerInfo = serverInfoList.remove(0);
Yi Tsenge72fbb52017-08-02 15:03:31 -0700276
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700277 // stop monitoring gateway or server
278 oldServerInfo.getDhcpGatewayIp4().ifPresent(gatewayIp -> {
279 hostService.stopMonitoringIp(gatewayIp);
280 });
281 oldServerInfo.getDhcpServerIp4().ifPresent(serverIp -> {
282 hostService.stopMonitoringIp(serverIp);
Yi Tseng7da339e2017-10-23 19:39:39 -0700283 cancelDhcpPacket(serverIp);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700284 });
Yi Tsenge72fbb52017-08-02 15:03:31 -0700285 }
Yi Tseng4fa05832017-08-17 13:08:31 -0700286
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700287 // Create new server info according to the config
288 DhcpServerInfo newServerInfo = new DhcpServerInfo(serverConfig,
289 DhcpServerInfo.Version.DHCP_V4);
290 checkState(newServerInfo.getDhcpServerConnectPoint().isPresent(),
Yi Tseng4ec727d2017-08-31 11:21:00 -0700291 "Connect point not exists");
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700292 checkState(newServerInfo.getDhcpServerIp4().isPresent(),
Yi Tseng4ec727d2017-08-31 11:21:00 -0700293 "IP of DHCP server not exists");
Yi Tseng4ec727d2017-08-31 11:21:00 -0700294
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700295 log.debug("DHCP server connect point: {}", newServerInfo.getDhcpServerConnectPoint().orElse(null));
296 log.debug("DHCP server IP: {}", newServerInfo.getDhcpServerIp4().orElse(null));
297
Yi Tseng7da339e2017-10-23 19:39:39 -0700298 Ip4Address serverIp = newServerInfo.getDhcpServerIp4().get();
299 Ip4Address ipToProbe;
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700300 if (newServerInfo.getDhcpGatewayIp4().isPresent()) {
301 ipToProbe = newServerInfo.getDhcpGatewayIp4().get();
302 } else {
303 ipToProbe = newServerInfo.getDhcpServerIp4().orElse(null);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700304 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700305 String hostToProbe = newServerInfo.getDhcpGatewayIp4()
306 .map(ip -> "gateway").orElse("server");
Yi Tseng4ec727d2017-08-31 11:21:00 -0700307
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700308 log.debug("Probing to resolve {} IP {}", hostToProbe, ipToProbe);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700309 hostService.startMonitoringIp(ipToProbe);
310
311 Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
312 if (!hosts.isEmpty()) {
313 Host host = hosts.iterator().next();
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700314 newServerInfo.setDhcpConnectVlan(host.vlan());
315 newServerInfo.setDhcpConnectMac(host.mac());
Yi Tseng4ec727d2017-08-31 11:21:00 -0700316 }
317
Yi Tseng053682b2017-11-09 13:54:12 -0800318 // Add new server info
319 synchronized (this) {
320 serverInfoList.clear();
321 serverInfoList.add(0, newServerInfo);
322 }
323
Yi Tseng7da339e2017-10-23 19:39:39 -0700324 requestDhcpPacket(serverIp);
Yi Tsenge72fbb52017-08-02 15:03:31 -0700325 }
326
Yi Tseng4fa05832017-08-17 13:08:31 -0700327 @Override
Yi Tseng51301292017-07-28 13:02:59 -0700328 public void processDhcpPacket(PacketContext context, BasePacket payload) {
329 checkNotNull(payload, "DHCP payload can't be null");
330 checkState(payload instanceof DHCP, "Payload is not a DHCP");
331 DHCP dhcpPayload = (DHCP) payload;
332 if (!configured()) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700333 log.warn("Missing default DHCP relay server config. Abort packet processing");
Yi Tseng51301292017-07-28 13:02:59 -0700334 return;
335 }
336
337 ConnectPoint inPort = context.inPacket().receivedFrom();
Yi Tseng51301292017-07-28 13:02:59 -0700338 checkNotNull(dhcpPayload, "Can't find DHCP payload");
339 Ethernet packet = context.inPacket().parsed();
340 DHCP.MsgType incomingPacketType = dhcpPayload.getOptions().stream()
341 .filter(dhcpOption -> dhcpOption.getCode() == OptionCode_MessageType.getValue())
342 .map(DhcpOption::getData)
343 .map(data -> DHCP.MsgType.getType(data[0]))
344 .findFirst()
345 .orElse(null);
346 checkNotNull(incomingPacketType, "Can't get message type from DHCP payload {}", dhcpPayload);
347 switch (incomingPacketType) {
348 case DHCPDISCOVER:
Yi Tsengdcef2c22017-08-05 20:34:06 -0700349 // Add the gateway IP as virtual interface IP for server to understand
Yi Tseng51301292017-07-28 13:02:59 -0700350 // the lease to be assigned and forward the packet to dhcp server.
351 Ethernet ethernetPacketDiscover =
Yi Tsengdcef2c22017-08-05 20:34:06 -0700352 processDhcpPacketFromClient(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700353 if (ethernetPacketDiscover != null) {
354 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700355 handleDhcpDiscoverAndRequest(ethernetPacketDiscover, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700356 }
357 break;
358 case DHCPOFFER:
359 //reply to dhcp client.
360 Ethernet ethernetPacketOffer = processDhcpPacketFromServer(packet);
361 if (ethernetPacketOffer != null) {
362 writeResponseDhcpRecord(ethernetPacketOffer, dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700363 sendResponseToClient(ethernetPacketOffer, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700364 }
365 break;
366 case DHCPREQUEST:
367 // add the gateway ip as virtual interface ip for server to understand
368 // the lease to be assigned and forward the packet to dhcp server.
369 Ethernet ethernetPacketRequest =
Yi Tsengdcef2c22017-08-05 20:34:06 -0700370 processDhcpPacketFromClient(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700371 if (ethernetPacketRequest != null) {
372 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700373 handleDhcpDiscoverAndRequest(ethernetPacketRequest, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700374 }
375 break;
Charles Chan2f9aa2c2017-10-08 23:53:36 -0400376 case DHCPDECLINE:
377 break;
Yi Tseng51301292017-07-28 13:02:59 -0700378 case DHCPACK:
379 // reply to dhcp client.
380 Ethernet ethernetPacketAck = processDhcpPacketFromServer(packet);
381 if (ethernetPacketAck != null) {
382 writeResponseDhcpRecord(ethernetPacketAck, dhcpPayload);
383 handleDhcpAck(ethernetPacketAck, dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700384 sendResponseToClient(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700385 }
386 break;
Charles Chan2f9aa2c2017-10-08 23:53:36 -0400387 case DHCPNAK:
388 break;
Yi Tseng51301292017-07-28 13:02:59 -0700389 case DHCPRELEASE:
390 // TODO: release the ip address from client
391 break;
Charles Chan2f9aa2c2017-10-08 23:53:36 -0400392 case DHCPINFORM:
393 break;
394 case DHCPFORCERENEW:
395 break;
396 case DHCPLEASEQUERY:
397 handleLeaseQueryMsg(context, packet, dhcpPayload);
398 break;
399 case DHCPLEASEACTIVE:
400 handleLeaseQueryActivateMsg(packet, dhcpPayload);
401 break;
402 case DHCPLEASEUNASSIGNED:
403 case DHCPLEASEUNKNOWN:
404 handleLeaseQueryUnknown(packet, dhcpPayload);
405 break;
Yi Tseng51301292017-07-28 13:02:59 -0700406 default:
407 break;
408 }
409 }
410
411 /**
412 * Checks if this app has been configured.
413 *
414 * @return true if all information we need have been initialized
415 */
Yi Tseng4ec727d2017-08-31 11:21:00 -0700416 private boolean configured() {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700417 return !defaultServerInfoList.isEmpty();
Yi Tseng51301292017-07-28 13:02:59 -0700418 }
419
420 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -0700421 * Returns the first interface ip from interface.
Yi Tseng51301292017-07-28 13:02:59 -0700422 *
Yi Tsengdcef2c22017-08-05 20:34:06 -0700423 * @param iface interface of one connect point
Yi Tseng51301292017-07-28 13:02:59 -0700424 * @return the first interface IP; null if not exists an IP address in
425 * these interfaces
426 */
Yi Tseng4fa05832017-08-17 13:08:31 -0700427 private Ip4Address getFirstIpFromInterface(Interface iface) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700428 checkNotNull(iface, "Interface can't be null");
429 return iface.ipAddressesList().stream()
Yi Tseng51301292017-07-28 13:02:59 -0700430 .map(InterfaceIpAddress::ipAddress)
431 .filter(IpAddress::isIp4)
432 .map(IpAddress::getIp4Address)
433 .findFirst()
434 .orElse(null);
435 }
436
437 /**
Yi Tseng4ec727d2017-08-31 11:21:00 -0700438 * Gets Interface facing to the server for default host.
Yi Tsengdcef2c22017-08-05 20:34:06 -0700439 *
440 * @return the Interface facing to the server; null if not found
441 */
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700442 private Interface getDefaultServerInterface() {
443 return getServerInterface(defaultServerInfoList);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700444 }
445
446 /**
Yi Tseng4ec727d2017-08-31 11:21:00 -0700447 * Gets Interface facing to the server for indirect hosts.
448 * Use default server Interface if indirect server not configured.
449 *
450 * @return the Interface facing to the server; null if not found
451 */
452 private Interface getIndirectServerInterface() {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700453 return getServerInterface(indirectServerInfoList);
454 }
455
456 private Interface getServerInterface(List<DhcpServerInfo> serverInfos) {
Yi Tsengdc510082017-11-29 14:39:18 -0800457 return serverInfos.stream()
Yi Tseng4ec727d2017-08-31 11:21:00 -0700458 .findFirst()
Yi Tsengdc510082017-11-29 14:39:18 -0800459 .map(serverInfo -> {
460 ConnectPoint dhcpServerConnectPoint =
461 serverInfo.getDhcpServerConnectPoint().orElse(null);
462 VlanId dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
463 if (dhcpServerConnectPoint == null || dhcpConnectVlan == null) {
464 return null;
465 }
466 return interfaceService.getInterfacesByPort(dhcpServerConnectPoint)
467 .stream()
468 .filter(iface -> interfaceContainsVlan(iface, dhcpConnectVlan))
469 .findFirst()
470 .orElse(null);
471 })
Yi Tseng4ec727d2017-08-31 11:21:00 -0700472 .orElse(null);
473 }
474
475 /**
476 * Determind if an Interface contains a vlan id.
477 *
478 * @param iface the Interface
479 * @param vlanId the vlan id
480 * @return true if the Interface contains the vlan id
481 */
482 private boolean interfaceContainsVlan(Interface iface, VlanId vlanId) {
Yi Tseng58e74312017-09-30 11:35:42 +0800483 if (vlanId.equals(VlanId.NONE)) {
484 // untagged packet, check if vlan untagged or vlan native is not NONE
485 return !iface.vlanUntagged().equals(VlanId.NONE) ||
486 !iface.vlanNative().equals(VlanId.NONE);
487 }
488 // tagged packet, check if the interface contains the vlan
489 return iface.vlanTagged().contains(vlanId);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700490 }
491
Charles Chan2f9aa2c2017-10-08 23:53:36 -0400492 private void handleLeaseQueryActivateMsg(Ethernet packet, DHCP dhcpPayload) {
493 log.debug("LQ: Got DHCPLEASEACTIVE packet!");
494
495 // TODO: release the ip address from client
496 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
497 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
498 HostId hostId = HostId.hostId(clientMacAddress, vlanId);
499 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
500
501 if (record == null) {
502 log.warn("Can't find record for host {} when processing DHCPLEASEACTIVE", hostId);
503 return;
504 }
505
506 // need to update routes
507 log.debug("Lease Query for Client results in DHCPLEASEACTIVE - route needs to be modified");
508 // get current route
509 // find the ip of that client with the DhcpRelay store
510
511 Ip4Address clientIP = record.ip4Address().orElse(null);
512 log.debug("LQ: IP of host is " + clientIP.getIp4Address());
513
514 MacAddress nextHopMac = record.nextHop().orElse(null);
515 log.debug("LQ: MAC of resulting *OLD* NH for that host is " + nextHopMac.toString());
516
517 // find the new NH by looking at the src MAC of the dhcp request
518 // from the LQ store
519 MacAddress newNextHopMac = record.nextHopTemp().orElse(null);
520 log.debug("LQ: MAC of resulting *NEW* NH for that host is " + newNextHopMac.toString());
521
522 log.debug("LQ: updating dhcp relay record with new NH");
523 record.nextHop(newNextHopMac);
524
525 // find the next hop IP from its mac
526 HostId gwHostId = HostId.hostId(newNextHopMac, vlanId);
527 Host gwHost = hostService.getHost(gwHostId);
528
529 if (gwHost == null) {
530 log.warn("Can't find gateway for new NH host " + gwHostId);
531 return;
532 }
533
534 Ip4Address nextHopIp = gwHost.ipAddresses()
535 .stream()
536 .filter(IpAddress::isIp4)
537 .map(IpAddress::getIp4Address)
538 .findFirst()
539 .orElse(null);
540
541 if (nextHopIp == null) {
542 log.warn("Can't find IP address of gateway " + gwHost);
543 return;
544 }
545
546 log.debug("LQ: *NEW* NH IP for host is " + nextHopIp.getIp4Address());
547 Route route = new Route(Route.Source.STATIC, clientIP.toIpPrefix(), nextHopIp);
548 routeStore.updateRoute(route);
549
550 // and forward to client
551 Ethernet ethernetPacket = processLeaseQueryFromServer(packet);
552 if (ethernetPacket != null) {
553 sendResponseToClient(ethernetPacket, dhcpPayload);
554 }
555 }
556
557 /**
558 *
559 */
560 private void handleLeaseQueryMsg(PacketContext context, Ethernet packet, DHCP dhcpPayload) {
561 log.debug("LQ: Got DHCPLEASEQUERY packet!");
562 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
563 log.debug("LQ: got DHCPLEASEQUERY with MAC " + clientMacAddress.toString());
564 // add the client mac (hostid) of this request to a store (the entry will be removed with
565 // the reply sent to the originator)
566 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
567 HostId hId = HostId.hostId(clientMacAddress, vlanId);
568 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hId).orElse(null);
569 if (record != null) {
570 //new NH is to be taken from src mac of LQ packet
571 MacAddress newNextHop = packet.getSourceMAC();
572 record.nextHopTemp(newNextHop);
573 record.ip4Status(dhcpPayload.getPacketType());
574 record.updateLastSeen();
575
576 // do a basic routing of the packet (this is unicast routing
577 // not a relay operation like for other broadcast dhcp packets
578 Ethernet ethernetPacketLQ = processLeaseQueryFromAgent(context, packet);
579 // and forward to server
580 handleDhcpDiscoverAndRequest(ethernetPacketLQ, dhcpPayload);
581 } else {
582 log.warn("LQ: Error! - DHCP relay record for that client not found - ignoring LQ!");
583 }
584 }
585
586 private void handleLeaseQueryUnknown(Ethernet packet, DHCP dhcpPayload) {
587 log.debug("Lease Query for Client results in DHCPLEASEUNASSIGNED or " +
588 "DHCPLEASEUNKNOWN - removing route & forwarding reply to originator");
589 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
590 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
591 HostId hostId = HostId.hostId(clientMacAddress, vlanId);
592 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
593
594 if (record == null) {
595 log.warn("Can't find record for host {} when handling LQ UNKNOWN/UNASSIGNED message", hostId);
596 return;
597 }
598
599 Ip4Address clientIP = record.ip4Address().orElse(null);
600 log.debug("LQ: IP of host is " + clientIP.getIp4Address());
601
602 // find the new NH by looking at the src MAC of the dhcp request
603 // from the LQ store
604 MacAddress nextHopMac = record.nextHop().orElse(null);
605 log.debug("LQ: MAC of resulting *Existing* NH for that route is " + nextHopMac.toString());
606
607 // find the next hop IP from its mac
608 HostId gwHostId = HostId.hostId(nextHopMac, vlanId);
609 Host gwHost = hostService.getHost(gwHostId);
610
611 if (gwHost == null) {
612 log.warn("Can't find gateway for new NH host " + gwHostId);
613 return;
614 }
615
616 Ip4Address nextHopIp = gwHost.ipAddresses()
617 .stream()
618 .filter(IpAddress::isIp4)
619 .map(IpAddress::getIp4Address)
620 .findFirst()
621 .orElse(null);
622
623 if (nextHopIp == null) {
624 log.warn("Can't find IP address of gateway {}", gwHost);
625 return;
626 }
627
628 log.debug("LQ: *Existing* NH IP for host is " + nextHopIp.getIp4Address() + " removing route for it");
629 Route route = new Route(Route.Source.STATIC, clientIP.toIpPrefix(), nextHopIp);
630 routeStore.removeRoute(route);
631
632 // remove from temp store
633 dhcpRelayStore.removeDhcpRecord(hostId);
634
635 // and forward to client
636 Ethernet ethernetPacket = processLeaseQueryFromServer(packet);
637 if (ethernetPacket != null) {
638 sendResponseToClient(ethernetPacket, dhcpPayload);
639 }
640 }
641
Yi Tseng4ec727d2017-08-31 11:21:00 -0700642 /**
Yi Tseng51301292017-07-28 13:02:59 -0700643 * Build the DHCP discover/request packet with gateway IP(unicast packet).
644 *
645 * @param context the packet context
646 * @param ethernetPacket the ethernet payload to process
Yi Tseng51301292017-07-28 13:02:59 -0700647 * @return processed packet
648 */
649 private Ethernet processDhcpPacketFromClient(PacketContext context,
Yi Tsengdcef2c22017-08-05 20:34:06 -0700650 Ethernet ethernetPacket) {
Yi Tsengc44dc2e2017-11-03 16:27:32 -0700651 ConnectPoint receivedFrom = context.inPacket().receivedFrom();
652 DeviceId receivedFromDevice = receivedFrom.deviceId();
653
Yi Tseng4ec727d2017-08-31 11:21:00 -0700654 // get dhcp header.
655 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
656 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
657 UDP udpPacket = (UDP) ipv4Packet.getPayload();
658 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
659
Yi Tsengc44dc2e2017-11-03 16:27:32 -0700660 // TODO: refactor
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700661 VlanId dhcpConnectVlan = null;
662 MacAddress dhcpConnectMac = null;
663 Ip4Address dhcpServerIp = null;
664 Ip4Address relayAgentIp = null;
665
666 VlanId indirectDhcpConnectVlan = null;
667 MacAddress indirectDhcpConnectMac = null;
668 Ip4Address indirectDhcpServerIp = null;
669 Ip4Address indirectRelayAgentIp = null;
670
671 if (!defaultServerInfoList.isEmpty()) {
672 DhcpServerInfo serverInfo = defaultServerInfoList.get(0);
673 dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
674 dhcpConnectMac = serverInfo.getDhcpConnectMac().orElse(null);
675 dhcpServerIp = serverInfo.getDhcpServerIp4().orElse(null);
Yi Tsengc44dc2e2017-11-03 16:27:32 -0700676 relayAgentIp = serverInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700677 }
678
679 if (!indirectServerInfoList.isEmpty()) {
680 DhcpServerInfo indirectServerInfo = indirectServerInfoList.get(0);
681 indirectDhcpConnectVlan = indirectServerInfo.getDhcpConnectVlan().orElse(null);
682 indirectDhcpConnectMac = indirectServerInfo.getDhcpConnectMac().orElse(null);
683 indirectDhcpServerIp = indirectServerInfo.getDhcpServerIp4().orElse(null);
Yi Tsengc44dc2e2017-11-03 16:27:32 -0700684 indirectRelayAgentIp = indirectServerInfo.getRelayAgentIp4(receivedFromDevice).orElse(null);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700685 }
686
Yi Tsengdcef2c22017-08-05 20:34:06 -0700687 Ip4Address clientInterfaceIp =
688 interfaceService.getInterfacesByPort(context.inPacket().receivedFrom())
689 .stream()
690 .map(Interface::ipAddressesList)
691 .flatMap(Collection::stream)
692 .map(InterfaceIpAddress::ipAddress)
693 .filter(IpAddress::isIp4)
694 .map(IpAddress::getIp4Address)
695 .findFirst()
696 .orElse(null);
697 if (clientInterfaceIp == null) {
698 log.warn("Can't find interface IP for client interface for port {}",
699 context.inPacket().receivedFrom());
700 return null;
701 }
Yi Tseng4ec727d2017-08-31 11:21:00 -0700702 boolean isDirectlyConnected = directlyConnected(dhcpPacket);
Yi Tsengdc510082017-11-29 14:39:18 -0800703 Interface serverInterface;
704 if (isDirectlyConnected) {
705 serverInterface = getDefaultServerInterface();
706 } else {
707 serverInterface = getIndirectServerInterface();
708 if (serverInterface == null) {
709 // Indirect server interface not found, use default server interface
710 serverInterface = getDefaultServerInterface();
711 }
712 }
Yi Tsengdcef2c22017-08-05 20:34:06 -0700713 if (serverInterface == null) {
Yi Tseng4ec727d2017-08-31 11:21:00 -0700714 log.warn("Can't get {} server interface, ignore", isDirectlyConnected ? "direct" : "indirect");
Yi Tsengdcef2c22017-08-05 20:34:06 -0700715 return null;
716 }
Yi Tseng4fa05832017-08-17 13:08:31 -0700717 Ip4Address ipFacingServer = getFirstIpFromInterface(serverInterface);
718 MacAddress macFacingServer = serverInterface.mac();
719 if (ipFacingServer == null || macFacingServer == null) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700720 log.warn("No IP address for server Interface {}", serverInterface);
Yi Tseng51301292017-07-28 13:02:59 -0700721 return null;
722 }
723 if (dhcpConnectMac == null) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700724 log.warn("DHCP Server/Gateway IP not yet resolved .. Aborting DHCP "
Yi Tseng51301292017-07-28 13:02:59 -0700725 + "packet processing from client on port: {}",
Yi Tsengdcef2c22017-08-05 20:34:06 -0700726 context.inPacket().receivedFrom());
Yi Tseng51301292017-07-28 13:02:59 -0700727 return null;
728 }
Yi Tseng4ec727d2017-08-31 11:21:00 -0700729
Yi Tseng4fa05832017-08-17 13:08:31 -0700730 etherReply.setSourceMACAddress(macFacingServer);
Yi Tseng4fa05832017-08-17 13:08:31 -0700731 ipv4Packet.setSourceAddress(ipFacingServer.toInt());
Yi Tseng51301292017-07-28 13:02:59 -0700732
Yi Tseng4ec727d2017-08-31 11:21:00 -0700733 if (isDirectlyConnected) {
Charles Chanb5d24822017-10-10 16:53:32 -0400734 etherReply.setDestinationMACAddress(dhcpConnectMac);
735 etherReply.setVlanID(dhcpConnectVlan.toShort());
736 ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
737
Yi Tseng51301292017-07-28 13:02:59 -0700738 ConnectPoint inPort = context.inPacket().receivedFrom();
739 VlanId vlanId = VlanId.vlanId(ethernetPacket.getVlanID());
740 // add connected in port and vlan
741 CircuitId cid = new CircuitId(inPort.toString(), vlanId);
742 byte[] circuitId = cid.serialize();
743 DhcpOption circuitIdSubOpt = new DhcpOption();
744 circuitIdSubOpt
745 .setCode(CIRCUIT_ID.getValue())
746 .setLength((byte) circuitId.length)
747 .setData(circuitId);
748
749 DhcpRelayAgentOption newRelayAgentOpt = new DhcpRelayAgentOption();
750 newRelayAgentOpt.setCode(OptionCode_CircuitID.getValue());
751 newRelayAgentOpt.addSubOption(circuitIdSubOpt);
752
Charles Chanb5d24822017-10-10 16:53:32 -0400753 // Removes END option first
Yi Tseng51301292017-07-28 13:02:59 -0700754 List<DhcpOption> options = dhcpPacket.getOptions().stream()
755 .filter(opt -> opt.getCode() != OptionCode_END.getValue())
756 .collect(Collectors.toList());
757
758 // push relay agent option
759 options.add(newRelayAgentOpt);
760
761 // make sure option 255(End) is the last option
762 DhcpOption endOption = new DhcpOption();
763 endOption.setCode(OptionCode_END.getValue());
764 options.add(endOption);
765
766 dhcpPacket.setOptions(options);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700767
Charles Chanb5d24822017-10-10 16:53:32 -0400768 // Sets relay agent IP
769 int effectiveRelayAgentIp = relayAgentIp != null ?
770 relayAgentIp.toInt() : clientInterfaceIp.toInt();
771 dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
772 } else {
773 if (indirectDhcpServerIp != null) {
774 // Use indirect server config for indirect packets if configured
775 etherReply.setDestinationMACAddress(indirectDhcpConnectMac);
776 etherReply.setVlanID(indirectDhcpConnectVlan.toShort());
777 ipv4Packet.setDestinationAddress(indirectDhcpServerIp.toInt());
Yi Tseng51301292017-07-28 13:02:59 -0700778
Charles Chanb5d24822017-10-10 16:53:32 -0400779 // Set giaddr if indirect relay agent IP is configured
780 if (indirectRelayAgentIp != null) {
781 dhcpPacket.setGatewayIPAddress(indirectRelayAgentIp.toInt());
782 }
783 } else {
784 // Otherwise, use default server config for indirect packets
785 etherReply.setDestinationMACAddress(dhcpConnectMac);
786 etherReply.setVlanID(dhcpConnectVlan.toShort());
787 ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
Yi Tseng4ec727d2017-08-31 11:21:00 -0700788
Charles Chanb5d24822017-10-10 16:53:32 -0400789 // Set giaddr if direct relay agent IP is configured
790 if (relayAgentIp != null) {
791 dhcpPacket.setGatewayIPAddress(relayAgentIp.toInt());
792 }
Yi Tseng4ec727d2017-08-31 11:21:00 -0700793 }
Yi Tseng4fa05832017-08-17 13:08:31 -0700794 }
795
Yi Tseng51301292017-07-28 13:02:59 -0700796 udpPacket.setPayload(dhcpPacket);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700797 // As a DHCP relay, the source port should be server port( instead
798 // of client port.
Yi Tsengdcef2c22017-08-05 20:34:06 -0700799 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
Yi Tseng51301292017-07-28 13:02:59 -0700800 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
801 ipv4Packet.setPayload(udpPacket);
Charles Chan7edf7642017-10-09 11:07:25 -0400802 ipv4Packet.setTtl((byte) 64);
Yi Tseng51301292017-07-28 13:02:59 -0700803 etherReply.setPayload(ipv4Packet);
804 return etherReply;
805 }
806
Charles Chan2f9aa2c2017-10-08 23:53:36 -0400807
808 /**
809 * Do a basic routing for a packet from client (used for LQ processing).
810 *
811 * @param context the packet context
812 * @param ethernetPacket the ethernet payload to process
813 * @return processed packet
814 */
815 private Ethernet processLeaseQueryFromAgent(PacketContext context,
816 Ethernet ethernetPacket) {
817 // get dhcp header.
818 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
819 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
820 UDP udpPacket = (UDP) ipv4Packet.getPayload();
821 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
822
823 VlanId dhcpConnectVlan = null;
824 MacAddress dhcpConnectMac = null;
825 Ip4Address dhcpServerIp = null;
826
827 VlanId indirectDhcpConnectVlan = null;
828 MacAddress indirectDhcpConnectMac = null;
829 Ip4Address indirectDhcpServerIp = null;
830
831 if (!defaultServerInfoList.isEmpty()) {
832 DhcpServerInfo serverInfo = defaultServerInfoList.get(0);
833 dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
834 dhcpConnectMac = serverInfo.getDhcpConnectMac().orElse(null);
835 dhcpServerIp = serverInfo.getDhcpServerIp4().orElse(null);
836 }
837
838 if (!indirectServerInfoList.isEmpty()) {
839 DhcpServerInfo indirectServerInfo = indirectServerInfoList.get(0);
840 indirectDhcpConnectVlan = indirectServerInfo.getDhcpConnectVlan().orElse(null);
841 indirectDhcpConnectMac = indirectServerInfo.getDhcpConnectMac().orElse(null);
842 indirectDhcpServerIp = indirectServerInfo.getDhcpServerIp4().orElse(null);
843 }
844
845 Ip4Address clientInterfaceIp =
846 interfaceService.getInterfacesByPort(context.inPacket().receivedFrom())
847 .stream()
848 .map(Interface::ipAddressesList)
849 .flatMap(Collection::stream)
850 .map(InterfaceIpAddress::ipAddress)
851 .filter(IpAddress::isIp4)
852 .map(IpAddress::getIp4Address)
853 .findFirst()
854 .orElse(null);
855 if (clientInterfaceIp == null) {
856 log.warn("Can't find interface IP for client interface for port {}",
857 context.inPacket().receivedFrom());
858 return null;
859 }
860 boolean isDirectlyConnected = directlyConnected(dhcpPacket);
Yi Tsengdc510082017-11-29 14:39:18 -0800861 Interface serverInterface;
862 if (isDirectlyConnected) {
863 serverInterface = getDefaultServerInterface();
864 } else {
865 serverInterface = getIndirectServerInterface();
866 if (serverInterface == null) {
867 // Indirect server interface not found, use default server interface
868 serverInterface = getDefaultServerInterface();
869 }
870 }
Charles Chan2f9aa2c2017-10-08 23:53:36 -0400871 if (serverInterface == null) {
872 log.warn("Can't get {} server interface, ignore", isDirectlyConnected ? "direct" : "indirect");
873 return null;
874 }
875 Ip4Address ipFacingServer = getFirstIpFromInterface(serverInterface);
876 MacAddress macFacingServer = serverInterface.mac();
877 if (ipFacingServer == null || macFacingServer == null) {
878 log.warn("No IP address for server Interface {}", serverInterface);
879 return null;
880 }
881 if (dhcpConnectMac == null) {
882 log.warn("DHCP server/gateway not yet resolved .. Aborting DHCP "
883 + "packet processing from client on port: {}",
884 context.inPacket().receivedFrom());
885 return null;
886 }
887
888 etherReply.setSourceMACAddress(macFacingServer);
889 etherReply.setDestinationMACAddress(dhcpConnectMac);
890 etherReply.setVlanID(dhcpConnectVlan.toShort());
891 ipv4Packet.setSourceAddress(ipFacingServer.toInt());
892 ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
893
894 if (indirectDhcpServerIp != null) {
895 // Indirect case, replace destination to indirect dhcp server if exist
896 etherReply.setDestinationMACAddress(indirectDhcpConnectMac);
897 etherReply.setVlanID(indirectDhcpConnectVlan.toShort());
898 ipv4Packet.setDestinationAddress(indirectDhcpServerIp.toInt());
899 }
900
901 udpPacket.setPayload(dhcpPacket);
902 // As a DHCP relay, the source port should be server port( instead
903 // of client port.
904 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
905 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
906 ipv4Packet.setPayload(udpPacket);
907 etherReply.setPayload(ipv4Packet);
908 return etherReply;
909 }
910
911
Yi Tseng51301292017-07-28 13:02:59 -0700912 /**
913 * Writes DHCP record to the store according to the request DHCP packet (Discover, Request).
914 *
915 * @param location the location which DHCP packet comes from
916 * @param ethernet the DHCP packet
917 * @param dhcpPayload the DHCP payload
918 */
919 private void writeRequestDhcpRecord(ConnectPoint location,
920 Ethernet ethernet,
921 DHCP dhcpPayload) {
922 VlanId vlanId = VlanId.vlanId(ethernet.getVlanID());
923 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
924 HostId hostId = HostId.hostId(macAddress, vlanId);
925 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
926 if (record == null) {
927 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
928 } else {
929 record = record.clone();
930 }
931 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
932 record.ip4Status(dhcpPayload.getPacketType());
933 record.setDirectlyConnected(directlyConnected(dhcpPayload));
934 if (!directlyConnected(dhcpPayload)) {
935 // Update gateway mac address if the host is not directly connected
936 record.nextHop(ethernet.getSourceMAC());
937 }
938 record.updateLastSeen();
939 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
940 }
941
942 /**
943 * Writes DHCP record to the store according to the response DHCP packet (Offer, Ack).
944 *
945 * @param ethernet the DHCP packet
946 * @param dhcpPayload the DHCP payload
947 */
948 private void writeResponseDhcpRecord(Ethernet ethernet,
949 DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700950 Optional<Interface> outInterface = getClientInterface(ethernet, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700951 if (!outInterface.isPresent()) {
952 log.warn("Failed to determine where to send {}", dhcpPayload.getPacketType());
953 return;
954 }
955
956 Interface outIface = outInterface.get();
957 ConnectPoint location = outIface.connectPoint();
Yi Tseng4ec727d2017-08-31 11:21:00 -0700958 VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700959 if (vlanId == null) {
960 vlanId = outIface.vlan();
961 }
Yi Tseng51301292017-07-28 13:02:59 -0700962 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
963 HostId hostId = HostId.hostId(macAddress, vlanId);
964 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
965 if (record == null) {
966 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
967 } else {
968 record = record.clone();
969 }
970 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
971 if (dhcpPayload.getPacketType() == DHCP.MsgType.DHCPACK) {
972 record.ip4Address(Ip4Address.valueOf(dhcpPayload.getYourIPAddress()));
973 }
974 record.ip4Status(dhcpPayload.getPacketType());
975 record.setDirectlyConnected(directlyConnected(dhcpPayload));
976 record.updateLastSeen();
977 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
978 }
979
980 /**
981 * Build the DHCP offer/ack with proper client port.
982 *
983 * @param ethernetPacket the original packet comes from server
984 * @return new packet which will send to the client
985 */
986 private Ethernet processDhcpPacketFromServer(Ethernet ethernetPacket) {
987 // get dhcp header.
988 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
989 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
990 UDP udpPacket = (UDP) ipv4Packet.getPayload();
991 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
992
993 // determine the vlanId of the client host - note that this vlan id
994 // could be different from the vlan in the packet from the server
Yi Tsengdcef2c22017-08-05 20:34:06 -0700995 Interface clientInterface = getClientInterface(ethernetPacket, dhcpPayload).orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -0700996
Yi Tsengdcef2c22017-08-05 20:34:06 -0700997 if (clientInterface == null) {
Yi Tseng51301292017-07-28 13:02:59 -0700998 log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
999 return null;
1000 }
Yi Tsengdcef2c22017-08-05 20:34:06 -07001001 VlanId vlanId;
1002 if (clientInterface.vlanTagged().isEmpty()) {
1003 vlanId = clientInterface.vlan();
1004 } else {
1005 // might be multiple vlan in same interface
Yi Tseng4ec727d2017-08-31 11:21:00 -07001006 vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001007 }
1008 if (vlanId == null) {
1009 vlanId = VlanId.NONE;
1010 }
1011 etherReply.setVlanID(vlanId.toShort());
1012 etherReply.setSourceMACAddress(clientInterface.mac());
Yi Tseng51301292017-07-28 13:02:59 -07001013
Yi Tsengdcef2c22017-08-05 20:34:06 -07001014 if (!directlyConnected(dhcpPayload)) {
1015 // if client is indirectly connected, try use next hop mac address
1016 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1017 HostId hostId = HostId.hostId(macAddress, vlanId);
Daniel Ginsburgb63dccc2018-01-29 11:40:22 -08001018 if (((int) dhcpPayload.getFlags() & 0x8000) == 0x0000) {
1019 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1020 if (record != null) {
1021 // if next hop can be found, use mac address of next hop
1022 record.nextHop().ifPresent(etherReply::setDestinationMACAddress);
1023 } else {
1024 // otherwise, discard the packet
1025 log.warn("Can't find record for host id {}, discard packet", hostId);
1026 return null;
1027 }
Yi Tsengdcef2c22017-08-05 20:34:06 -07001028 } else {
Daniel Ginsburgb63dccc2018-01-29 11:40:22 -08001029 etherReply.setDestinationMACAddress(MacAddress.BROADCAST);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001030 }
Yi Tsengc03fa242017-08-17 17:43:38 -07001031 } else {
1032 etherReply.setDestinationMACAddress(dhcpPayload.getClientHardwareAddress());
Yi Tsengdcef2c22017-08-05 20:34:06 -07001033 }
1034
Yi Tseng51301292017-07-28 13:02:59 -07001035 // we leave the srcMac from the original packet
Yi Tseng51301292017-07-28 13:02:59 -07001036 // figure out the relay agent IP corresponding to the original request
Yi Tseng4fa05832017-08-17 13:08:31 -07001037 Ip4Address ipFacingClient = getFirstIpFromInterface(clientInterface);
1038 if (ipFacingClient == null) {
Yi Tseng51301292017-07-28 13:02:59 -07001039 log.warn("Cannot determine relay agent interface Ipv4 addr for host {}/{}. "
1040 + "Aborting relay for dhcp packet from server {}",
Yi Tsengdcef2c22017-08-05 20:34:06 -07001041 etherReply.getDestinationMAC(), clientInterface.vlan(),
Yi Tseng51301292017-07-28 13:02:59 -07001042 ethernetPacket);
1043 return null;
1044 }
1045 // SRC_IP: relay agent IP
1046 // DST_IP: offered IP
Yi Tseng4fa05832017-08-17 13:08:31 -07001047 ipv4Packet.setSourceAddress(ipFacingClient.toInt());
Daniel Ginsburgb63dccc2018-01-29 11:40:22 -08001048 if (((int) dhcpPayload.getFlags() & 0x8000) == 0x0000) {
1049 ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
1050 } else {
1051 ipv4Packet.setDestinationAddress(BROADCAST_IP);
1052 }
Yi Tseng51301292017-07-28 13:02:59 -07001053 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
1054 if (directlyConnected(dhcpPayload)) {
1055 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
1056 } else {
1057 // forward to another dhcp relay
Yi Tseng72b599a2017-09-14 13:24:21 -07001058 // FIXME: Currently we assume the DHCP comes from a L2 relay with
1059 // Option 82, this might not work if DHCP message comes from
1060 // L3 relay.
1061 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
Yi Tseng51301292017-07-28 13:02:59 -07001062 }
1063
1064 udpPacket.setPayload(dhcpPayload);
1065 ipv4Packet.setPayload(udpPacket);
1066 etherReply.setPayload(ipv4Packet);
1067 return etherReply;
1068 }
1069
Yi Tsengdcef2c22017-08-05 20:34:06 -07001070 /**
Charles Chan2f9aa2c2017-10-08 23:53:36 -04001071 * Build the DHCP offer/ack with proper client port.
1072 *
1073 * @param ethernetPacket the original packet comes from server
1074 * @return new packet which will send to the client
1075 */
1076 private Ethernet processLeaseQueryFromServer(Ethernet ethernetPacket) {
1077 // get dhcp header.
1078 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
1079 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
1080 UDP udpPacket = (UDP) ipv4Packet.getPayload();
1081 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
1082
1083 // determine the vlanId of the client host - note that this vlan id
1084 // could be different from the vlan in the packet from the server
1085 Interface clientInterface = getClientInterface(ethernetPacket, dhcpPayload).orElse(null);
1086
1087 if (clientInterface == null) {
1088 log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
1089 return null;
1090 }
1091 VlanId vlanId;
1092 if (clientInterface.vlanTagged().isEmpty()) {
1093 vlanId = clientInterface.vlan();
1094 } else {
1095 // might be multiple vlan in same interface
1096 vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
1097 }
1098 if (vlanId == null) {
1099 vlanId = VlanId.NONE;
1100 }
1101 etherReply.setVlanID(vlanId.toShort());
1102 etherReply.setSourceMACAddress(clientInterface.mac());
1103
1104 if (!directlyConnected(dhcpPayload)) {
1105 // if client is indirectly connected, try use next hop mac address
1106 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1107 HostId hostId = HostId.hostId(macAddress, vlanId);
1108 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1109 if (record != null) {
1110 // if next hop can be found, use mac address of next hop
1111 record.nextHop().ifPresent(etherReply::setDestinationMACAddress);
1112 } else {
1113 // otherwise, discard the packet
1114 log.warn("Can't find record for host id {}, discard packet", hostId);
1115 return null;
1116 }
1117 } else {
1118 etherReply.setDestinationMACAddress(dhcpPayload.getClientHardwareAddress());
1119 }
1120
1121 // default is client port
1122 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
1123 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
1124
1125 udpPacket.setPayload(dhcpPayload);
1126 ipv4Packet.setPayload(udpPacket);
1127 etherReply.setPayload(ipv4Packet);
1128 return etherReply;
1129 }
1130 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -07001131 * Extracts VLAN ID from relay agent option.
1132 *
1133 * @param dhcpPayload the DHCP payload
1134 * @return VLAN ID from DHCP payload; null if not exists
1135 */
Yi Tseng4ec727d2017-08-31 11:21:00 -07001136 private VlanId getVlanIdFromRelayAgentOption(DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001137 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
1138 if (option == null) {
1139 return null;
1140 }
1141 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
1142 if (circuitIdSubOption == null) {
1143 return null;
1144 }
1145 try {
1146 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
1147 return circuitId.vlanId();
1148 } catch (IllegalArgumentException e) {
1149 // can't deserialize the circuit ID
1150 return null;
1151 }
1152 }
1153
1154 /**
1155 * Removes DHCP relay agent information option (option 82) from DHCP payload.
1156 * Also reset giaddr to 0
1157 *
1158 * @param ethPacket the Ethernet packet to be processed
1159 * @return Ethernet packet processed
1160 */
1161 private Ethernet removeRelayAgentOption(Ethernet ethPacket) {
1162 Ethernet ethernet = (Ethernet) ethPacket.clone();
1163 IPv4 ipv4 = (IPv4) ethernet.getPayload();
1164 UDP udp = (UDP) ipv4.getPayload();
1165 DHCP dhcpPayload = (DHCP) udp.getPayload();
1166
1167 // removes relay agent information option
1168 List<DhcpOption> options = dhcpPayload.getOptions();
1169 options = options.stream()
1170 .filter(option -> option.getCode() != OptionCode_CircuitID.getValue())
1171 .collect(Collectors.toList());
1172 dhcpPayload.setOptions(options);
1173 dhcpPayload.setGatewayIPAddress(0);
1174
1175 udp.setPayload(dhcpPayload);
1176 ipv4.setPayload(udp);
1177 ethernet.setPayload(ipv4);
1178 return ethernet;
1179 }
1180
Yi Tseng51301292017-07-28 13:02:59 -07001181
1182 /**
1183 * Check if the host is directly connected to the network or not.
1184 *
1185 * @param dhcpPayload the dhcp payload
1186 * @return true if the host is directly connected to the network; false otherwise
1187 */
1188 private boolean directlyConnected(DHCP dhcpPayload) {
Yi Tseng2cf59912017-08-24 14:47:34 -07001189 DhcpRelayAgentOption relayAgentOption =
1190 (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
Yi Tseng51301292017-07-28 13:02:59 -07001191
1192 // Doesn't contains relay option
1193 if (relayAgentOption == null) {
1194 return true;
1195 }
1196
Yi Tseng2cf59912017-08-24 14:47:34 -07001197 // check circuit id, if circuit id is invalid, we say it is an indirect host
1198 DhcpOption circuitIdOpt = relayAgentOption.getSubOption(CIRCUIT_ID.getValue());
Yi Tseng51301292017-07-28 13:02:59 -07001199
Yi Tseng2cf59912017-08-24 14:47:34 -07001200 try {
1201 CircuitId.deserialize(circuitIdOpt.getData());
Yi Tseng51301292017-07-28 13:02:59 -07001202 return true;
Yi Tseng2cf59912017-08-24 14:47:34 -07001203 } catch (Exception e) {
1204 // invalid circuit id
1205 return false;
Yi Tseng51301292017-07-28 13:02:59 -07001206 }
Yi Tseng51301292017-07-28 13:02:59 -07001207 }
1208
1209
1210 /**
1211 * Send the DHCP ack to the requester host.
1212 * Modify Host or Route store according to the type of DHCP.
1213 *
1214 * @param ethernetPacketAck the packet
1215 * @param dhcpPayload the DHCP data
1216 */
1217 private void handleDhcpAck(Ethernet ethernetPacketAck, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001218 Optional<Interface> outInterface = getClientInterface(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -07001219 if (!outInterface.isPresent()) {
1220 log.warn("Can't find output interface for dhcp: {}", dhcpPayload);
1221 return;
1222 }
1223
1224 Interface outIface = outInterface.get();
1225 HostLocation hostLocation = new HostLocation(outIface.connectPoint(), System.currentTimeMillis());
1226 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
Yi Tseng4ec727d2017-08-31 11:21:00 -07001227 VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001228 if (vlanId == null) {
1229 vlanId = outIface.vlan();
1230 }
Yi Tseng51301292017-07-28 13:02:59 -07001231 HostId hostId = HostId.hostId(macAddress, vlanId);
1232 Ip4Address ip = Ip4Address.valueOf(dhcpPayload.getYourIPAddress());
1233
1234 if (directlyConnected(dhcpPayload)) {
1235 // Add to host store if it connect to network directly
1236 Set<IpAddress> ips = Sets.newHashSet(ip);
Yi Tseng4b013202017-09-08 17:22:51 -07001237 Host host = hostService.getHost(hostId);
Yi Tseng51301292017-07-28 13:02:59 -07001238
Yi Tseng4b013202017-09-08 17:22:51 -07001239 Set<HostLocation> hostLocations = Sets.newHashSet(hostLocation);
1240 if (host != null) {
1241 // Dual homing support:
1242 // if host exists, use old locations and new location
1243 hostLocations.addAll(host.locations());
1244 }
1245 HostDescription desc = new DefaultHostDescription(macAddress, vlanId,
1246 hostLocations, ips, false);
1247 // Add IP address when dhcp server give the host new ip address
1248 providerService.hostDetected(hostId, desc, false);
Yi Tseng51301292017-07-28 13:02:59 -07001249 } else {
1250 // Add to route store if it does not connect to network directly
1251 // Get gateway host IP according to host mac address
Yi Tsengdcef2c22017-08-05 20:34:06 -07001252 // TODO: remove relay store here
Yi Tseng51301292017-07-28 13:02:59 -07001253 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1254
1255 if (record == null) {
1256 log.warn("Can't find DHCP record of host {}", hostId);
1257 return;
1258 }
1259
1260 MacAddress gwMac = record.nextHop().orElse(null);
1261 if (gwMac == null) {
1262 log.warn("Can't find gateway mac address from record {}", record);
1263 return;
1264 }
1265
1266 HostId gwHostId = HostId.hostId(gwMac, record.vlanId());
1267 Host gwHost = hostService.getHost(gwHostId);
1268
1269 if (gwHost == null) {
1270 log.warn("Can't find gateway host {}", gwHostId);
1271 return;
1272 }
1273
1274 Ip4Address nextHopIp = gwHost.ipAddresses()
1275 .stream()
1276 .filter(IpAddress::isIp4)
1277 .map(IpAddress::getIp4Address)
1278 .findFirst()
1279 .orElse(null);
1280
1281 if (nextHopIp == null) {
1282 log.warn("Can't find IP address of gateway {}", gwHost);
1283 return;
1284 }
1285
1286 Route route = new Route(Route.Source.STATIC, ip.toIpPrefix(), nextHopIp);
1287 routeStore.updateRoute(route);
1288 }
Yi Tseng51301292017-07-28 13:02:59 -07001289 }
1290
1291 /**
1292 * forward the packet to ConnectPoint where the DHCP server is attached.
1293 *
1294 * @param packet the packet
1295 */
Yi Tseng4ec727d2017-08-31 11:21:00 -07001296 private void handleDhcpDiscoverAndRequest(Ethernet packet, DHCP dhcpPayload) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001297 boolean direct = directlyConnected(dhcpPayload);
1298 DhcpServerInfo serverInfo = defaultServerInfoList.get(0);
1299 if (!direct && !indirectServerInfoList.isEmpty()) {
1300 serverInfo = indirectServerInfoList.get(0);
Yi Tseng4ec727d2017-08-31 11:21:00 -07001301 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001302 ConnectPoint portToFotward = serverInfo.getDhcpServerConnectPoint().orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -07001303 // send packet to dhcp server connect point.
Yi Tseng4ec727d2017-08-31 11:21:00 -07001304 if (portToFotward != null) {
Yi Tseng51301292017-07-28 13:02:59 -07001305 TrafficTreatment t = DefaultTrafficTreatment.builder()
Yi Tseng4ec727d2017-08-31 11:21:00 -07001306 .setOutput(portToFotward.port()).build();
Yi Tseng51301292017-07-28 13:02:59 -07001307 OutboundPacket o = new DefaultOutboundPacket(
Yi Tseng4ec727d2017-08-31 11:21:00 -07001308 portToFotward.deviceId(), t, ByteBuffer.wrap(packet.serialize()));
Yi Tseng51301292017-07-28 13:02:59 -07001309 if (log.isTraceEnabled()) {
1310 log.trace("Relaying packet to dhcp server {}", packet);
1311 }
1312 packetService.emit(o);
1313 } else {
1314 log.warn("Can't find DHCP server connect point, abort.");
1315 }
1316 }
1317
1318
1319 /**
1320 * Gets output interface of a dhcp packet.
1321 * If option 82 exists in the dhcp packet and the option was sent by
Yi Tseng4ec727d2017-08-31 11:21:00 -07001322 * ONOS (circuit format is correct), use the connect
Yi Tseng51301292017-07-28 13:02:59 -07001323 * point and vlan id from circuit id; otherwise, find host by destination
1324 * address and use vlan id from sender (dhcp server).
1325 *
1326 * @param ethPacket the ethernet packet
1327 * @param dhcpPayload the dhcp packet
1328 * @return an interface represent the output port and vlan; empty value
1329 * if the host or circuit id not found
1330 */
Yi Tsengdcef2c22017-08-05 20:34:06 -07001331 private Optional<Interface> getClientInterface(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tseng51301292017-07-28 13:02:59 -07001332 VlanId originalPacketVlanId = VlanId.vlanId(ethPacket.getVlanID());
Yi Tseng51301292017-07-28 13:02:59 -07001333 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
1334
Yi Tseng4ec727d2017-08-31 11:21:00 -07001335 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
1336 try {
1337 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
1338 ConnectPoint connectPoint = ConnectPoint.deviceConnectPoint(circuitId.connectPoint());
1339 VlanId vlanId = circuitId.vlanId();
1340 return interfaceService.getInterfacesByPort(connectPoint)
1341 .stream()
1342 .filter(iface -> interfaceContainsVlan(iface, vlanId))
1343 .findFirst();
1344 } catch (IllegalArgumentException ex) {
1345 // invalid circuit format, didn't sent by ONOS
1346 log.debug("Invalid circuit {}, use information from dhcp payload",
1347 circuitIdSubOption.getData());
Yi Tseng51301292017-07-28 13:02:59 -07001348 }
1349
1350 // Use Vlan Id from DHCP server if DHCP relay circuit id was not
1351 // sent by ONOS or circuit Id can't be parsed
Yi Tsengdcef2c22017-08-05 20:34:06 -07001352 // TODO: remove relay store from this method
Yi Tseng51301292017-07-28 13:02:59 -07001353 MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
1354 Optional<DhcpRecord> dhcpRecord = dhcpRelayStore.getDhcpRecord(HostId.hostId(dstMac, originalPacketVlanId));
Yi Tsengdcef2c22017-08-05 20:34:06 -07001355 ConnectPoint clientConnectPoint = dhcpRecord
Yi Tseng51301292017-07-28 13:02:59 -07001356 .map(DhcpRecord::locations)
1357 .orElse(Collections.emptySet())
1358 .stream()
1359 .reduce((hl1, hl2) -> {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001360 // find latest host connect point
Yi Tseng51301292017-07-28 13:02:59 -07001361 if (hl1 == null || hl2 == null) {
1362 return hl1 == null ? hl2 : hl1;
1363 }
1364 return hl1.time() > hl2.time() ? hl1 : hl2;
1365 })
Yi Tsengdcef2c22017-08-05 20:34:06 -07001366 .orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -07001367
Yi Tsengdcef2c22017-08-05 20:34:06 -07001368 if (clientConnectPoint != null) {
1369 return interfaceService.getInterfacesByPort(clientConnectPoint)
1370 .stream()
Yi Tseng4ec727d2017-08-31 11:21:00 -07001371 .filter(iface -> interfaceContainsVlan(iface, originalPacketVlanId))
Yi Tsengdcef2c22017-08-05 20:34:06 -07001372 .findFirst();
1373 }
1374 return Optional.empty();
Yi Tseng51301292017-07-28 13:02:59 -07001375 }
1376
1377 /**
1378 * Send the response DHCP to the requester host.
1379 *
1380 * @param ethPacket the packet
1381 * @param dhcpPayload the DHCP data
1382 */
1383 private void sendResponseToClient(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001384 Optional<Interface> outInterface = getClientInterface(ethPacket, dhcpPayload);
1385 if (directlyConnected(dhcpPayload)) {
1386 ethPacket = removeRelayAgentOption(ethPacket);
1387 }
1388 if (!outInterface.isPresent()) {
1389 log.warn("Can't find output interface for client, ignore");
1390 return;
1391 }
1392 Interface outIface = outInterface.get();
1393 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
1394 .setOutput(outIface.connectPoint().port())
1395 .build();
1396 OutboundPacket o = new DefaultOutboundPacket(
1397 outIface.connectPoint().deviceId(),
1398 treatment,
1399 ByteBuffer.wrap(ethPacket.serialize()));
1400 if (log.isTraceEnabled()) {
1401 log.trace("Relaying packet to DHCP client {} via {}, vlan {}",
1402 ethPacket,
1403 outIface.connectPoint(),
1404 outIface.vlan());
1405 }
1406 packetService.emit(o);
Yi Tseng51301292017-07-28 13:02:59 -07001407 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001408
Yi Tseng4b013202017-09-08 17:22:51 -07001409 @Override
1410 public void triggerProbe(Host host) {
1411 // Do nothing here
1412 }
1413
1414 @Override
1415 public ProviderId id() {
Charles Chan75edab72017-09-12 17:09:32 -07001416 return PROVIDER_ID;
Yi Tseng4b013202017-09-08 17:22:51 -07001417 }
1418
Yi Tsenge72fbb52017-08-02 15:03:31 -07001419 class InternalHostListener implements HostListener {
1420 @Override
1421 public void event(HostEvent event) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001422 if (!configured()) {
1423 return;
1424 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001425 switch (event.type()) {
1426 case HOST_ADDED:
1427 case HOST_UPDATED:
1428 hostUpdated(event.subject());
1429 break;
1430 case HOST_REMOVED:
1431 hostRemoved(event.subject());
1432 break;
Yi Tsenge72fbb52017-08-02 15:03:31 -07001433 default:
1434 break;
1435 }
1436 }
1437 }
1438
1439 /**
Yi Tsenge72fbb52017-08-02 15:03:31 -07001440 * Handle host updated.
1441 * If the host is DHCP server or gateway, update connect mac and vlan.
1442 *
1443 * @param host the host
1444 */
1445 private void hostUpdated(Host host) {
Yi Tseng7da339e2017-10-23 19:39:39 -07001446 hostUpdated(host, defaultServerInfoList);
1447 hostUpdated(host, indirectServerInfoList);
Yi Tsenge72fbb52017-08-02 15:03:31 -07001448 }
1449
Yi Tseng7da339e2017-10-23 19:39:39 -07001450 private void hostUpdated(Host host, List<DhcpServerInfo> srverInfoList) {
1451 DhcpServerInfo serverInfo;
1452 Ip4Address targetIp;
1453 if (!srverInfoList.isEmpty()) {
1454 serverInfo = srverInfoList.get(0);
1455 targetIp = serverInfo.getDhcpGatewayIp4().orElse(null);
1456 Ip4Address serverIp = serverInfo.getDhcpServerIp4().orElse(null);
1457
1458 if (targetIp == null) {
1459 targetIp = serverIp;
1460 }
1461
1462 if (targetIp != null) {
1463 if (host.ipAddresses().contains(targetIp)) {
1464 serverInfo.setDhcpConnectMac(host.mac());
1465 serverInfo.setDhcpConnectVlan(host.vlan());
1466 requestDhcpPacket(serverIp);
1467 }
1468 }
1469 }
1470 }
1471
1472
Yi Tsenge72fbb52017-08-02 15:03:31 -07001473 /**
1474 * Handle host removed.
1475 * If the host is DHCP server or gateway, unset connect mac and vlan.
1476 *
1477 * @param host the host
1478 */
1479 private void hostRemoved(Host host) {
Yi Tseng7da339e2017-10-23 19:39:39 -07001480 hostRemoved(host, defaultServerInfoList);
1481 hostRemoved(host, indirectServerInfoList);
1482 }
1483
1484 private void hostRemoved(Host host, List<DhcpServerInfo> serverInfoList) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001485 DhcpServerInfo serverInfo;
Yi Tseng7da339e2017-10-23 19:39:39 -07001486 Ip4Address targetIp;
1487 if (!serverInfoList.isEmpty()) {
1488 serverInfo = serverInfoList.get(0);
1489 Ip4Address serverIp = serverInfo.getDhcpServerIp4().orElse(null);
1490 targetIp = serverInfo.getDhcpGatewayIp4().orElse(null);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001491
Yi Tseng7da339e2017-10-23 19:39:39 -07001492 if (targetIp == null) {
1493 targetIp = serverIp;
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001494 }
Yi Tseng7da339e2017-10-23 19:39:39 -07001495
1496 if (targetIp != null) {
1497 if (host.ipAddresses().contains(targetIp)) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001498 serverInfo.setDhcpConnectVlan(null);
1499 serverInfo.setDhcpConnectMac(null);
Yi Tseng7da339e2017-10-23 19:39:39 -07001500 cancelDhcpPacket(serverIp);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001501 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001502 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001503 }
Yi Tseng7da339e2017-10-23 19:39:39 -07001504 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001505
Yi Tseng7da339e2017-10-23 19:39:39 -07001506 private void requestDhcpPacket(Ip4Address serverIp) {
1507 requestServerDhcpPacket(serverIp);
1508 requestClientDhcpPacket(serverIp);
1509 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001510
Yi Tseng7da339e2017-10-23 19:39:39 -07001511 private void cancelDhcpPacket(Ip4Address serverIp) {
1512 cancelServerDhcpPacket(serverIp);
1513 cancelClientDhcpPacket(serverIp);
1514 }
1515
1516 private void cancelServerDhcpPacket(Ip4Address serverIp) {
1517 TrafficSelector serverSelector =
1518 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1519 .matchIPSrc(serverIp.toIpPrefix())
1520 .build();
1521 packetService.cancelPackets(serverSelector,
1522 PacketPriority.CONTROL,
1523 appId);
1524 }
1525
1526 private void requestServerDhcpPacket(Ip4Address serverIp) {
1527 TrafficSelector serverSelector =
1528 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1529 .matchIPSrc(serverIp.toIpPrefix())
1530 .build();
1531 packetService.requestPackets(serverSelector,
1532 PacketPriority.CONTROL,
1533 appId);
1534 }
1535
1536 private void cancelClientDhcpPacket(Ip4Address serverIp) {
1537 // Packet comes from relay
1538 TrafficSelector indirectClientSelector =
1539 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1540 .matchIPDst(serverIp.toIpPrefix())
1541 .build();
1542 packetService.cancelPackets(indirectClientSelector,
1543 PacketPriority.CONTROL,
1544 appId);
1545
1546 // Packet comes from client
1547 packetService.cancelPackets(CLIENT_SERVER_SELECTOR,
1548 PacketPriority.CONTROL,
1549 appId);
1550 }
1551
1552 private void requestClientDhcpPacket(Ip4Address serverIp) {
1553 // Packet comes from relay
1554 TrafficSelector indirectClientSelector =
1555 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1556 .matchIPDst(serverIp.toIpPrefix())
1557 .build();
1558 packetService.requestPackets(indirectClientSelector,
1559 PacketPriority.CONTROL,
1560 appId);
1561
1562 // Packet comes from client
1563 packetService.requestPackets(CLIENT_SERVER_SELECTOR,
1564 PacketPriority.CONTROL,
1565 appId);
1566 }
1567
1568 /**
1569 * Process the ignore rules.
1570 *
1571 * @param deviceId the device id
1572 * @param vlanId the vlan to be ignored
1573 * @param op the operation, ADD to install; REMOVE to uninstall rules
1574 */
1575 private void processIgnoreVlanRule(DeviceId deviceId, VlanId vlanId, Objective.Operation op) {
Yi Tseng7da339e2017-10-23 19:39:39 -07001576 AtomicInteger installedCount = new AtomicInteger(DHCP_SELECTORS.size());
1577 DHCP_SELECTORS.forEach(trafficSelector -> {
1578 TrafficSelector selector = DefaultTrafficSelector.builder(trafficSelector)
1579 .matchVlanId(vlanId)
1580 .build();
1581
1582 ForwardingObjective.Builder builder = DefaultForwardingObjective.builder()
1583 .withFlag(ForwardingObjective.Flag.VERSATILE)
1584 .withSelector(selector)
1585 .withPriority(IGNORE_CONTROL_PRIORITY)
Yi Tsengf29ffd72017-11-29 10:49:20 -08001586 .withTreatment(DefaultTrafficTreatment.emptyTreatment())
Yi Tseng7da339e2017-10-23 19:39:39 -07001587 .fromApp(appId);
1588
1589
1590 ObjectiveContext objectiveContext = new ObjectiveContext() {
1591 @Override
1592 public void onSuccess(Objective objective) {
1593 log.info("Ignore rule {} (Vlan id {}, device {}, selector {})",
1594 op, vlanId, deviceId, selector);
1595 int countDown = installedCount.decrementAndGet();
1596 if (countDown != 0) {
1597 return;
1598 }
1599 switch (op) {
1600 case ADD:
1601 ignoredVlans.put(deviceId, vlanId);
1602 break;
1603 case REMOVE:
1604 ignoredVlans.remove(deviceId, vlanId);
1605 break;
1606 default:
1607 log.warn("Unsupported objective operation {}", op);
1608 break;
1609 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001610 }
Yi Tseng7da339e2017-10-23 19:39:39 -07001611
1612 @Override
1613 public void onError(Objective objective, ObjectiveError error) {
1614 log.warn("Can't {} ignore rule (vlan id {}, selector {}, device {}) due to {}",
1615 op, vlanId, selector, deviceId, error);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001616 }
Yi Tseng7da339e2017-10-23 19:39:39 -07001617 };
1618
1619 ForwardingObjective fwd;
1620 switch (op) {
1621 case ADD:
1622 fwd = builder.add(objectiveContext);
1623 break;
1624 case REMOVE:
1625 fwd = builder.remove(objectiveContext);
1626 break;
1627 default:
1628 log.warn("Unsupported objective operation {}", op);
1629 return;
Yi Tseng4ec727d2017-08-31 11:21:00 -07001630 }
Yi Tseng7da339e2017-10-23 19:39:39 -07001631
1632 Device device = deviceService.getDevice(deviceId);
1633 if (device == null || !device.is(Pipeliner.class)) {
1634 log.warn("Device {} is not available now, wait until device is available", deviceId);
1635 return;
1636 }
1637 flowObjectiveService.apply(deviceId, fwd);
1638 });
Yi Tsenge72fbb52017-08-02 15:03:31 -07001639 }
Yi Tseng51301292017-07-28 13:02:59 -07001640}