blob: d15a34a63ab336930d48b634b347f1ecb3f8e920 [file] [log] [blame]
Yi Tseng51301292017-07-28 13:02:59 -07001/*
2 * Copyright 2017-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17
18package org.onosproject.dhcprelay;
19
Yi Tseng7da339e2017-10-23 19:39:39 -070020import com.google.common.collect.HashMultimap;
21import com.google.common.collect.ImmutableSet;
Yi Tseng2fe8f3f2017-09-07 16:22:51 -070022import com.google.common.collect.Lists;
Yi Tseng7da339e2017-10-23 19:39:39 -070023import com.google.common.collect.Multimap;
Yi Tseng51301292017-07-28 13:02:59 -070024import com.google.common.collect.Sets;
Yi Tsenge72fbb52017-08-02 15:03:31 -070025import org.apache.felix.scr.annotations.Activate;
Yi Tseng51301292017-07-28 13:02:59 -070026import org.apache.felix.scr.annotations.Component;
Yi Tsenge72fbb52017-08-02 15:03:31 -070027import org.apache.felix.scr.annotations.Deactivate;
Yi Tseng51301292017-07-28 13:02:59 -070028import org.apache.felix.scr.annotations.Property;
29import org.apache.felix.scr.annotations.Reference;
30import org.apache.felix.scr.annotations.ReferenceCardinality;
31import org.apache.felix.scr.annotations.Service;
32import org.onlab.packet.BasePacket;
33import org.onlab.packet.DHCP;
34import org.onlab.packet.Ethernet;
35import org.onlab.packet.IPv4;
36import org.onlab.packet.Ip4Address;
37import org.onlab.packet.IpAddress;
38import org.onlab.packet.MacAddress;
Yi Tseng7da339e2017-10-23 19:39:39 -070039import org.onlab.packet.TpPort;
Yi Tseng51301292017-07-28 13:02:59 -070040import org.onlab.packet.UDP;
41import org.onlab.packet.VlanId;
42import org.onlab.packet.dhcp.CircuitId;
43import org.onlab.packet.dhcp.DhcpOption;
44import org.onlab.packet.dhcp.DhcpRelayAgentOption;
Yi Tseng7da339e2017-10-23 19:39:39 -070045import org.onosproject.core.ApplicationId;
46import org.onosproject.core.CoreService;
Yi Tseng51301292017-07-28 13:02:59 -070047import org.onosproject.dhcprelay.api.DhcpHandler;
Yi Tseng2fe8f3f2017-09-07 16:22:51 -070048import org.onosproject.dhcprelay.api.DhcpServerInfo;
Yi Tsenge72fbb52017-08-02 15:03:31 -070049import org.onosproject.dhcprelay.config.DhcpServerConfig;
Yi Tseng7da339e2017-10-23 19:39:39 -070050import org.onosproject.dhcprelay.config.IgnoreDhcpConfig;
Yi Tseng51301292017-07-28 13:02:59 -070051import org.onosproject.dhcprelay.store.DhcpRecord;
52import org.onosproject.dhcprelay.store.DhcpRelayStore;
Yi Tseng7da339e2017-10-23 19:39:39 -070053import org.onosproject.net.Device;
54import org.onosproject.net.DeviceId;
55import org.onosproject.net.behaviour.Pipeliner;
56import org.onosproject.net.device.DeviceService;
57import org.onosproject.net.flow.DefaultTrafficSelector;
58import org.onosproject.net.flow.TrafficSelector;
59import org.onosproject.net.flowobjective.DefaultForwardingObjective;
60import org.onosproject.net.flowobjective.FlowObjectiveService;
61import org.onosproject.net.flowobjective.ForwardingObjective;
62import org.onosproject.net.flowobjective.Objective;
63import org.onosproject.net.flowobjective.ObjectiveContext;
64import org.onosproject.net.flowobjective.ObjectiveError;
Yi Tsenge72fbb52017-08-02 15:03:31 -070065import org.onosproject.net.host.HostEvent;
66import org.onosproject.net.host.HostListener;
Yi Tseng4b013202017-09-08 17:22:51 -070067import org.onosproject.net.host.HostProvider;
68import org.onosproject.net.host.HostProviderRegistry;
69import org.onosproject.net.host.HostProviderService;
Ray Milkeyfacf2862017-08-03 11:58:29 -070070import org.onosproject.net.intf.Interface;
71import org.onosproject.net.intf.InterfaceService;
Yi Tseng7da339e2017-10-23 19:39:39 -070072import org.onosproject.net.packet.PacketPriority;
Yi Tseng4b013202017-09-08 17:22:51 -070073import org.onosproject.net.provider.ProviderId;
Ray Milkey69ec8712017-08-08 13:00:43 -070074import org.onosproject.routeservice.Route;
75import org.onosproject.routeservice.RouteStore;
Yi Tseng51301292017-07-28 13:02:59 -070076import org.onosproject.net.ConnectPoint;
77import org.onosproject.net.Host;
78import org.onosproject.net.HostId;
79import org.onosproject.net.HostLocation;
80import org.onosproject.net.flow.DefaultTrafficTreatment;
81import org.onosproject.net.flow.TrafficTreatment;
82import org.onosproject.net.host.DefaultHostDescription;
83import org.onosproject.net.host.HostDescription;
84import org.onosproject.net.host.HostService;
Yi Tseng51301292017-07-28 13:02:59 -070085import org.onosproject.net.host.InterfaceIpAddress;
86import org.onosproject.net.packet.DefaultOutboundPacket;
87import org.onosproject.net.packet.OutboundPacket;
88import org.onosproject.net.packet.PacketContext;
89import org.onosproject.net.packet.PacketService;
90import org.slf4j.Logger;
91import org.slf4j.LoggerFactory;
92
93import java.nio.ByteBuffer;
Yi Tsengdcef2c22017-08-05 20:34:06 -070094import java.util.Collection;
Yi Tseng51301292017-07-28 13:02:59 -070095import java.util.Collections;
96import java.util.List;
97import java.util.Optional;
98import java.util.Set;
Yi Tseng7da339e2017-10-23 19:39:39 -070099import java.util.concurrent.atomic.AtomicInteger;
Yi Tseng51301292017-07-28 13:02:59 -0700100import java.util.stream.Collectors;
101
102import static com.google.common.base.Preconditions.checkNotNull;
103import static com.google.common.base.Preconditions.checkState;
104import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_CircuitID;
105import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_END;
106import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
107import static org.onlab.packet.MacAddress.valueOf;
108import static org.onlab.packet.dhcp.DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID;
Yi Tseng7da339e2017-10-23 19:39:39 -0700109import static org.onosproject.net.flowobjective.Objective.Operation.ADD;
110import static org.onosproject.net.flowobjective.Objective.Operation.REMOVE;
Yi Tseng51301292017-07-28 13:02:59 -0700111
112@Component
113@Service
114@Property(name = "version", value = "4")
Yi Tseng4b013202017-09-08 17:22:51 -0700115public class Dhcp4HandlerImpl implements DhcpHandler, HostProvider {
Charles Chan75edab72017-09-12 17:09:32 -0700116 public static final String DHCP_V4_RELAY_APP = "org.onosproject.Dhcp4HandlerImpl";
117 public static final ProviderId PROVIDER_ID = new ProviderId("dhcp4", DHCP_V4_RELAY_APP);
Yi Tseng7da339e2017-10-23 19:39:39 -0700118 private static final String BROADCAST_IP = "255.255.255.255";
119 private static final int IGNORE_CONTROL_PRIORITY = PacketPriority.CONTROL.priorityValue() + 1000;
120
121 private static final TrafficSelector CLIENT_SERVER_SELECTOR = DefaultTrafficSelector.builder()
122 .matchEthType(Ethernet.TYPE_IPV4)
123 .matchIPProtocol(IPv4.PROTOCOL_UDP)
124 .matchIPSrc(Ip4Address.ZERO.toIpPrefix())
125 .matchIPDst(Ip4Address.valueOf(BROADCAST_IP).toIpPrefix())
126 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
127 .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
128 .build();
129 private static final TrafficSelector SERVER_RELAY_SELECTOR = DefaultTrafficSelector.builder()
130 .matchEthType(Ethernet.TYPE_IPV4)
131 .matchIPProtocol(IPv4.PROTOCOL_UDP)
132 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
133 .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
134 .build();
135 static final Set<TrafficSelector> DHCP_SELECTORS = ImmutableSet.of(
136 CLIENT_SERVER_SELECTOR,
137 SERVER_RELAY_SELECTOR
138 );
Yi Tseng51301292017-07-28 13:02:59 -0700139 private static Logger log = LoggerFactory.getLogger(Dhcp4HandlerImpl.class);
140
141 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
142 protected DhcpRelayStore dhcpRelayStore;
143
144 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
145 protected PacketService packetService;
146
147 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Yi Tseng51301292017-07-28 13:02:59 -0700148 protected RouteStore routeStore;
149
150 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
151 protected InterfaceService interfaceService;
152
153 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
154 protected HostService hostService;
155
Yi Tseng4b013202017-09-08 17:22:51 -0700156 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
157 protected HostProviderRegistry providerRegistry;
158
Yi Tseng7da339e2017-10-23 19:39:39 -0700159 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
160 protected CoreService coreService;
161
162 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
163 protected DeviceService deviceService;
164
165 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
166 protected FlowObjectiveService flowObjectiveService;
167
Yi Tseng4b013202017-09-08 17:22:51 -0700168 protected HostProviderService providerService;
Yi Tseng7da339e2017-10-23 19:39:39 -0700169 protected ApplicationId appId;
170 protected Multimap<DeviceId, VlanId> ignoredVlans = HashMultimap.create();
Yi Tsenge72fbb52017-08-02 15:03:31 -0700171 private InternalHostListener hostListener = new InternalHostListener();
172
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700173 private List<DhcpServerInfo> defaultServerInfoList = Lists.newArrayList();
174 private List<DhcpServerInfo> indirectServerInfoList = Lists.newArrayList();
Yi Tseng4ec727d2017-08-31 11:21:00 -0700175
Yi Tsenge72fbb52017-08-02 15:03:31 -0700176 @Activate
177 protected void activate() {
Yi Tseng7da339e2017-10-23 19:39:39 -0700178 appId = coreService.registerApplication(DHCP_V4_RELAY_APP);
Yi Tsenge72fbb52017-08-02 15:03:31 -0700179 hostService.addListener(hostListener);
Yi Tseng4b013202017-09-08 17:22:51 -0700180 providerService = providerRegistry.register(this);
Yi Tsenge72fbb52017-08-02 15:03:31 -0700181 }
182
183 @Deactivate
184 protected void deactivate() {
Yi Tseng4b013202017-09-08 17:22:51 -0700185 providerRegistry.unregister(this);
186 hostService.removeListener(hostListener);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700187 defaultServerInfoList.forEach(this::stopMonitoringIps);
188 defaultServerInfoList.clear();
189 indirectServerInfoList.forEach(this::stopMonitoringIps);
190 indirectServerInfoList.clear();
Yi Tsenge72fbb52017-08-02 15:03:31 -0700191 }
192
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700193 private void stopMonitoringIps(DhcpServerInfo serverInfo) {
194 serverInfo.getDhcpGatewayIp4().ifPresent(gatewayIp -> {
195 hostService.stopMonitoringIp(gatewayIp);
196 });
197 serverInfo.getDhcpServerIp4().ifPresent(serverIp -> {
198 hostService.stopMonitoringIp(serverIp);
199 });
Yi Tseng51301292017-07-28 13:02:59 -0700200 }
201
202 @Override
Yi Tsenge72fbb52017-08-02 15:03:31 -0700203 public void setDefaultDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700204 setDhcpServerConfigs(configs, defaultServerInfoList);
205 }
206
207 @Override
208 public void setIndirectDhcpServerConfigs(Collection<DhcpServerConfig> configs) {
209 setDhcpServerConfigs(configs, indirectServerInfoList);
210 }
211
212 @Override
213 public List<DhcpServerInfo> getDefaultDhcpServerInfoList() {
214 return defaultServerInfoList;
215 }
216
217 @Override
218 public List<DhcpServerInfo> getIndirectDhcpServerInfoList() {
219 return indirectServerInfoList;
220 }
221
Yi Tseng7da339e2017-10-23 19:39:39 -0700222 @Override
223 public void updateIgnoreVlanConfig(IgnoreDhcpConfig config) {
224 if (config == null) {
225 ignoredVlans.forEach(((deviceId, vlanId) -> {
226 processIgnoreVlanRule(deviceId, vlanId, REMOVE);
227 }));
228 return;
229 }
230 config.ignoredVlans().forEach((deviceId, vlanId) -> {
231 if (ignoredVlans.get(deviceId).contains(vlanId)) {
232 // don't need to process if it already ignored
233 return;
234 }
235 processIgnoreVlanRule(deviceId, vlanId, ADD);
236 });
237
238 ignoredVlans.forEach((deviceId, vlanId) -> {
239 if (!config.ignoredVlans().get(deviceId).contains(vlanId)) {
240 // not contains in new config, remove it
241 processIgnoreVlanRule(deviceId, vlanId, REMOVE);
242 }
243 });
244 }
245
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700246 public void setDhcpServerConfigs(Collection<DhcpServerConfig> configs, List<DhcpServerInfo> serverInfoList) {
Yi Tsenge72fbb52017-08-02 15:03:31 -0700247 if (configs.size() == 0) {
248 // no config to update
249 return;
250 }
251
252 // TODO: currently we pick up first DHCP server config.
253 // Will use other server configs in the future for HA.
254 DhcpServerConfig serverConfig = configs.iterator().next();
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700255
Yi Tsengaefbb002017-09-08 16:23:32 -0700256 if (!serverConfig.getDhcpServerIp4().isPresent()) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700257 // not a DHCPv4 config
Yi Tsenge72fbb52017-08-02 15:03:31 -0700258 return;
259 }
260
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700261 if (!serverInfoList.isEmpty()) {
262 // remove old server info
263 DhcpServerInfo oldServerInfo = serverInfoList.remove(0);
Yi Tsenge72fbb52017-08-02 15:03:31 -0700264
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700265 // stop monitoring gateway or server
266 oldServerInfo.getDhcpGatewayIp4().ifPresent(gatewayIp -> {
267 hostService.stopMonitoringIp(gatewayIp);
268 });
269 oldServerInfo.getDhcpServerIp4().ifPresent(serverIp -> {
270 hostService.stopMonitoringIp(serverIp);
Yi Tseng7da339e2017-10-23 19:39:39 -0700271 cancelDhcpPacket(serverIp);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700272 });
Yi Tsenge72fbb52017-08-02 15:03:31 -0700273 }
Yi Tseng4fa05832017-08-17 13:08:31 -0700274
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700275 // Create new server info according to the config
276 DhcpServerInfo newServerInfo = new DhcpServerInfo(serverConfig,
277 DhcpServerInfo.Version.DHCP_V4);
278 checkState(newServerInfo.getDhcpServerConnectPoint().isPresent(),
Yi Tseng4ec727d2017-08-31 11:21:00 -0700279 "Connect point not exists");
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700280 checkState(newServerInfo.getDhcpServerIp4().isPresent(),
Yi Tseng4ec727d2017-08-31 11:21:00 -0700281 "IP of DHCP server not exists");
Yi Tseng4ec727d2017-08-31 11:21:00 -0700282
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700283 log.debug("DHCP server connect point: {}", newServerInfo.getDhcpServerConnectPoint().orElse(null));
284 log.debug("DHCP server IP: {}", newServerInfo.getDhcpServerIp4().orElse(null));
285
Yi Tseng7da339e2017-10-23 19:39:39 -0700286 Ip4Address serverIp = newServerInfo.getDhcpServerIp4().get();
287 Ip4Address ipToProbe;
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700288 if (newServerInfo.getDhcpGatewayIp4().isPresent()) {
289 ipToProbe = newServerInfo.getDhcpGatewayIp4().get();
290 } else {
291 ipToProbe = newServerInfo.getDhcpServerIp4().orElse(null);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700292 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700293 String hostToProbe = newServerInfo.getDhcpGatewayIp4()
294 .map(ip -> "gateway").orElse("server");
Yi Tseng4ec727d2017-08-31 11:21:00 -0700295
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700296 log.debug("Probing to resolve {} IP {}", hostToProbe, ipToProbe);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700297 hostService.startMonitoringIp(ipToProbe);
298
299 Set<Host> hosts = hostService.getHostsByIp(ipToProbe);
300 if (!hosts.isEmpty()) {
301 Host host = hosts.iterator().next();
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700302 newServerInfo.setDhcpConnectVlan(host.vlan());
303 newServerInfo.setDhcpConnectMac(host.mac());
Yi Tseng4ec727d2017-08-31 11:21:00 -0700304 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700305 // Add new server info
306 serverInfoList.add(0, newServerInfo);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700307
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700308 // Remove duplicated server info
309 Set<DhcpServerInfo> nonDupServerInfoList = Sets.newLinkedHashSet();
310 nonDupServerInfoList.addAll(serverInfoList);
311 serverInfoList.clear();
312 serverInfoList.addAll(nonDupServerInfoList);
Yi Tseng7da339e2017-10-23 19:39:39 -0700313 requestDhcpPacket(serverIp);
Yi Tsenge72fbb52017-08-02 15:03:31 -0700314 }
315
Yi Tseng4fa05832017-08-17 13:08:31 -0700316 @Override
Yi Tseng51301292017-07-28 13:02:59 -0700317 public void processDhcpPacket(PacketContext context, BasePacket payload) {
318 checkNotNull(payload, "DHCP payload can't be null");
319 checkState(payload instanceof DHCP, "Payload is not a DHCP");
320 DHCP dhcpPayload = (DHCP) payload;
321 if (!configured()) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700322 log.warn("Missing default DHCP relay server config. Abort packet processing");
Yi Tseng51301292017-07-28 13:02:59 -0700323 return;
324 }
325
326 ConnectPoint inPort = context.inPacket().receivedFrom();
Yi Tseng51301292017-07-28 13:02:59 -0700327 checkNotNull(dhcpPayload, "Can't find DHCP payload");
328 Ethernet packet = context.inPacket().parsed();
329 DHCP.MsgType incomingPacketType = dhcpPayload.getOptions().stream()
330 .filter(dhcpOption -> dhcpOption.getCode() == OptionCode_MessageType.getValue())
331 .map(DhcpOption::getData)
332 .map(data -> DHCP.MsgType.getType(data[0]))
333 .findFirst()
334 .orElse(null);
335 checkNotNull(incomingPacketType, "Can't get message type from DHCP payload {}", dhcpPayload);
336 switch (incomingPacketType) {
337 case DHCPDISCOVER:
Yi Tsengdcef2c22017-08-05 20:34:06 -0700338 // Add the gateway IP as virtual interface IP for server to understand
Yi Tseng51301292017-07-28 13:02:59 -0700339 // the lease to be assigned and forward the packet to dhcp server.
340 Ethernet ethernetPacketDiscover =
Yi Tsengdcef2c22017-08-05 20:34:06 -0700341 processDhcpPacketFromClient(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700342 if (ethernetPacketDiscover != null) {
343 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700344 handleDhcpDiscoverAndRequest(ethernetPacketDiscover, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700345 }
346 break;
347 case DHCPOFFER:
348 //reply to dhcp client.
349 Ethernet ethernetPacketOffer = processDhcpPacketFromServer(packet);
350 if (ethernetPacketOffer != null) {
351 writeResponseDhcpRecord(ethernetPacketOffer, dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700352 sendResponseToClient(ethernetPacketOffer, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700353 }
354 break;
355 case DHCPREQUEST:
356 // add the gateway ip as virtual interface ip for server to understand
357 // the lease to be assigned and forward the packet to dhcp server.
358 Ethernet ethernetPacketRequest =
Yi Tsengdcef2c22017-08-05 20:34:06 -0700359 processDhcpPacketFromClient(context, packet);
Yi Tseng51301292017-07-28 13:02:59 -0700360 if (ethernetPacketRequest != null) {
361 writeRequestDhcpRecord(inPort, packet, dhcpPayload);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700362 handleDhcpDiscoverAndRequest(ethernetPacketRequest, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700363 }
364 break;
365 case DHCPACK:
366 // reply to dhcp client.
367 Ethernet ethernetPacketAck = processDhcpPacketFromServer(packet);
368 if (ethernetPacketAck != null) {
369 writeResponseDhcpRecord(ethernetPacketAck, dhcpPayload);
370 handleDhcpAck(ethernetPacketAck, dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700371 sendResponseToClient(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700372 }
373 break;
374 case DHCPRELEASE:
375 // TODO: release the ip address from client
376 break;
377 default:
378 break;
379 }
380 }
381
382 /**
383 * Checks if this app has been configured.
384 *
385 * @return true if all information we need have been initialized
386 */
Yi Tseng4ec727d2017-08-31 11:21:00 -0700387 private boolean configured() {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700388 return !defaultServerInfoList.isEmpty();
Yi Tseng51301292017-07-28 13:02:59 -0700389 }
390
391 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -0700392 * Returns the first interface ip from interface.
Yi Tseng51301292017-07-28 13:02:59 -0700393 *
Yi Tsengdcef2c22017-08-05 20:34:06 -0700394 * @param iface interface of one connect point
Yi Tseng51301292017-07-28 13:02:59 -0700395 * @return the first interface IP; null if not exists an IP address in
396 * these interfaces
397 */
Yi Tseng4fa05832017-08-17 13:08:31 -0700398 private Ip4Address getFirstIpFromInterface(Interface iface) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700399 checkNotNull(iface, "Interface can't be null");
400 return iface.ipAddressesList().stream()
Yi Tseng51301292017-07-28 13:02:59 -0700401 .map(InterfaceIpAddress::ipAddress)
402 .filter(IpAddress::isIp4)
403 .map(IpAddress::getIp4Address)
404 .findFirst()
405 .orElse(null);
406 }
407
408 /**
Yi Tseng4ec727d2017-08-31 11:21:00 -0700409 * Gets Interface facing to the server for default host.
Yi Tsengdcef2c22017-08-05 20:34:06 -0700410 *
411 * @return the Interface facing to the server; null if not found
412 */
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700413 private Interface getDefaultServerInterface() {
414 return getServerInterface(defaultServerInfoList);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700415 }
416
417 /**
Yi Tseng4ec727d2017-08-31 11:21:00 -0700418 * Gets Interface facing to the server for indirect hosts.
419 * Use default server Interface if indirect server not configured.
420 *
421 * @return the Interface facing to the server; null if not found
422 */
423 private Interface getIndirectServerInterface() {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700424 return getServerInterface(indirectServerInfoList);
425 }
426
427 private Interface getServerInterface(List<DhcpServerInfo> serverInfos) {
428 DhcpServerInfo serverInfo = serverInfos.get(0);
429 ConnectPoint dhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
430 VlanId dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
431 if (dhcpServerConnectPoint == null || dhcpConnectVlan == null) {
432 return null;
Yi Tseng4ec727d2017-08-31 11:21:00 -0700433 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700434 return interfaceService.getInterfacesByPort(dhcpServerConnectPoint)
Yi Tseng4ec727d2017-08-31 11:21:00 -0700435 .stream()
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700436 .filter(iface -> interfaceContainsVlan(iface, dhcpConnectVlan))
Yi Tseng4ec727d2017-08-31 11:21:00 -0700437 .findFirst()
438 .orElse(null);
439 }
440
441 /**
442 * Determind if an Interface contains a vlan id.
443 *
444 * @param iface the Interface
445 * @param vlanId the vlan id
446 * @return true if the Interface contains the vlan id
447 */
448 private boolean interfaceContainsVlan(Interface iface, VlanId vlanId) {
Yi Tseng58e74312017-09-30 11:35:42 +0800449 if (vlanId.equals(VlanId.NONE)) {
450 // untagged packet, check if vlan untagged or vlan native is not NONE
451 return !iface.vlanUntagged().equals(VlanId.NONE) ||
452 !iface.vlanNative().equals(VlanId.NONE);
453 }
454 // tagged packet, check if the interface contains the vlan
455 return iface.vlanTagged().contains(vlanId);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700456 }
457
458 /**
Yi Tseng51301292017-07-28 13:02:59 -0700459 * Build the DHCP discover/request packet with gateway IP(unicast packet).
460 *
461 * @param context the packet context
462 * @param ethernetPacket the ethernet payload to process
Yi Tseng51301292017-07-28 13:02:59 -0700463 * @return processed packet
464 */
465 private Ethernet processDhcpPacketFromClient(PacketContext context,
Yi Tsengdcef2c22017-08-05 20:34:06 -0700466 Ethernet ethernetPacket) {
Yi Tseng4ec727d2017-08-31 11:21:00 -0700467 // get dhcp header.
468 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
469 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
470 UDP udpPacket = (UDP) ipv4Packet.getPayload();
471 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
472
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700473
474 VlanId dhcpConnectVlan = null;
475 MacAddress dhcpConnectMac = null;
476 Ip4Address dhcpServerIp = null;
477 Ip4Address relayAgentIp = null;
478
479 VlanId indirectDhcpConnectVlan = null;
480 MacAddress indirectDhcpConnectMac = null;
481 Ip4Address indirectDhcpServerIp = null;
482 Ip4Address indirectRelayAgentIp = null;
483
484 if (!defaultServerInfoList.isEmpty()) {
485 DhcpServerInfo serverInfo = defaultServerInfoList.get(0);
486 dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
487 dhcpConnectMac = serverInfo.getDhcpConnectMac().orElse(null);
488 dhcpServerIp = serverInfo.getDhcpServerIp4().orElse(null);
489 relayAgentIp = serverInfo.getRelayAgentIp4().orElse(null);
490 }
491
492 if (!indirectServerInfoList.isEmpty()) {
493 DhcpServerInfo indirectServerInfo = indirectServerInfoList.get(0);
494 indirectDhcpConnectVlan = indirectServerInfo.getDhcpConnectVlan().orElse(null);
495 indirectDhcpConnectMac = indirectServerInfo.getDhcpConnectMac().orElse(null);
496 indirectDhcpServerIp = indirectServerInfo.getDhcpServerIp4().orElse(null);
497 indirectRelayAgentIp = indirectServerInfo.getRelayAgentIp4().orElse(null);
498 }
499
Yi Tsengdcef2c22017-08-05 20:34:06 -0700500 Ip4Address clientInterfaceIp =
501 interfaceService.getInterfacesByPort(context.inPacket().receivedFrom())
502 .stream()
503 .map(Interface::ipAddressesList)
504 .flatMap(Collection::stream)
505 .map(InterfaceIpAddress::ipAddress)
506 .filter(IpAddress::isIp4)
507 .map(IpAddress::getIp4Address)
508 .findFirst()
509 .orElse(null);
510 if (clientInterfaceIp == null) {
511 log.warn("Can't find interface IP for client interface for port {}",
512 context.inPacket().receivedFrom());
513 return null;
514 }
Yi Tseng4ec727d2017-08-31 11:21:00 -0700515 boolean isDirectlyConnected = directlyConnected(dhcpPacket);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700516 Interface serverInterface = isDirectlyConnected ? getDefaultServerInterface() : getIndirectServerInterface();
Yi Tsengdcef2c22017-08-05 20:34:06 -0700517 if (serverInterface == null) {
Yi Tseng4ec727d2017-08-31 11:21:00 -0700518 log.warn("Can't get {} server interface, ignore", isDirectlyConnected ? "direct" : "indirect");
Yi Tsengdcef2c22017-08-05 20:34:06 -0700519 return null;
520 }
Yi Tseng4fa05832017-08-17 13:08:31 -0700521 Ip4Address ipFacingServer = getFirstIpFromInterface(serverInterface);
522 MacAddress macFacingServer = serverInterface.mac();
523 if (ipFacingServer == null || macFacingServer == null) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700524 log.warn("No IP address for server Interface {}", serverInterface);
Yi Tseng51301292017-07-28 13:02:59 -0700525 return null;
526 }
527 if (dhcpConnectMac == null) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700528 log.warn("DHCP Server/Gateway IP not yet resolved .. Aborting DHCP "
Yi Tseng51301292017-07-28 13:02:59 -0700529 + "packet processing from client on port: {}",
Yi Tsengdcef2c22017-08-05 20:34:06 -0700530 context.inPacket().receivedFrom());
Yi Tseng51301292017-07-28 13:02:59 -0700531 return null;
532 }
Yi Tseng4ec727d2017-08-31 11:21:00 -0700533
Yi Tseng4fa05832017-08-17 13:08:31 -0700534 etherReply.setSourceMACAddress(macFacingServer);
Yi Tseng4fa05832017-08-17 13:08:31 -0700535 ipv4Packet.setSourceAddress(ipFacingServer.toInt());
Yi Tseng51301292017-07-28 13:02:59 -0700536
Yi Tseng4ec727d2017-08-31 11:21:00 -0700537 if (isDirectlyConnected) {
Charles Chanb5d24822017-10-10 16:53:32 -0400538 etherReply.setDestinationMACAddress(dhcpConnectMac);
539 etherReply.setVlanID(dhcpConnectVlan.toShort());
540 ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
541
Yi Tseng51301292017-07-28 13:02:59 -0700542 ConnectPoint inPort = context.inPacket().receivedFrom();
543 VlanId vlanId = VlanId.vlanId(ethernetPacket.getVlanID());
544 // add connected in port and vlan
545 CircuitId cid = new CircuitId(inPort.toString(), vlanId);
546 byte[] circuitId = cid.serialize();
547 DhcpOption circuitIdSubOpt = new DhcpOption();
548 circuitIdSubOpt
549 .setCode(CIRCUIT_ID.getValue())
550 .setLength((byte) circuitId.length)
551 .setData(circuitId);
552
553 DhcpRelayAgentOption newRelayAgentOpt = new DhcpRelayAgentOption();
554 newRelayAgentOpt.setCode(OptionCode_CircuitID.getValue());
555 newRelayAgentOpt.addSubOption(circuitIdSubOpt);
556
Charles Chanb5d24822017-10-10 16:53:32 -0400557 // Removes END option first
Yi Tseng51301292017-07-28 13:02:59 -0700558 List<DhcpOption> options = dhcpPacket.getOptions().stream()
559 .filter(opt -> opt.getCode() != OptionCode_END.getValue())
560 .collect(Collectors.toList());
561
562 // push relay agent option
563 options.add(newRelayAgentOpt);
564
565 // make sure option 255(End) is the last option
566 DhcpOption endOption = new DhcpOption();
567 endOption.setCode(OptionCode_END.getValue());
568 options.add(endOption);
569
570 dhcpPacket.setOptions(options);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700571
Charles Chanb5d24822017-10-10 16:53:32 -0400572 // Sets relay agent IP
573 int effectiveRelayAgentIp = relayAgentIp != null ?
574 relayAgentIp.toInt() : clientInterfaceIp.toInt();
575 dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
576 } else {
577 if (indirectDhcpServerIp != null) {
578 // Use indirect server config for indirect packets if configured
579 etherReply.setDestinationMACAddress(indirectDhcpConnectMac);
580 etherReply.setVlanID(indirectDhcpConnectVlan.toShort());
581 ipv4Packet.setDestinationAddress(indirectDhcpServerIp.toInt());
Yi Tseng51301292017-07-28 13:02:59 -0700582
Charles Chanb5d24822017-10-10 16:53:32 -0400583 // Set giaddr if indirect relay agent IP is configured
584 if (indirectRelayAgentIp != null) {
585 dhcpPacket.setGatewayIPAddress(indirectRelayAgentIp.toInt());
586 }
587 } else {
588 // Otherwise, use default server config for indirect packets
589 etherReply.setDestinationMACAddress(dhcpConnectMac);
590 etherReply.setVlanID(dhcpConnectVlan.toShort());
591 ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
Yi Tseng4ec727d2017-08-31 11:21:00 -0700592
Charles Chanb5d24822017-10-10 16:53:32 -0400593 // Set giaddr if direct relay agent IP is configured
594 if (relayAgentIp != null) {
595 dhcpPacket.setGatewayIPAddress(relayAgentIp.toInt());
596 }
Yi Tseng4ec727d2017-08-31 11:21:00 -0700597 }
Yi Tseng4fa05832017-08-17 13:08:31 -0700598 }
599
Yi Tsengf41681e2017-10-03 09:58:19 -0700600 // Remove broadcast flag
601 dhcpPacket.setFlags((short) 0);
602
Yi Tseng51301292017-07-28 13:02:59 -0700603 udpPacket.setPayload(dhcpPacket);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700604 // As a DHCP relay, the source port should be server port( instead
605 // of client port.
Yi Tsengdcef2c22017-08-05 20:34:06 -0700606 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
Yi Tseng51301292017-07-28 13:02:59 -0700607 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
608 ipv4Packet.setPayload(udpPacket);
Charles Chan7edf7642017-10-09 11:07:25 -0400609 ipv4Packet.setTtl((byte) 64);
Yi Tseng51301292017-07-28 13:02:59 -0700610 etherReply.setPayload(ipv4Packet);
611 return etherReply;
612 }
613
614 /**
615 * Writes DHCP record to the store according to the request DHCP packet (Discover, Request).
616 *
617 * @param location the location which DHCP packet comes from
618 * @param ethernet the DHCP packet
619 * @param dhcpPayload the DHCP payload
620 */
621 private void writeRequestDhcpRecord(ConnectPoint location,
622 Ethernet ethernet,
623 DHCP dhcpPayload) {
624 VlanId vlanId = VlanId.vlanId(ethernet.getVlanID());
625 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
626 HostId hostId = HostId.hostId(macAddress, vlanId);
627 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
628 if (record == null) {
629 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
630 } else {
631 record = record.clone();
632 }
633 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
634 record.ip4Status(dhcpPayload.getPacketType());
635 record.setDirectlyConnected(directlyConnected(dhcpPayload));
636 if (!directlyConnected(dhcpPayload)) {
637 // Update gateway mac address if the host is not directly connected
638 record.nextHop(ethernet.getSourceMAC());
639 }
640 record.updateLastSeen();
641 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
642 }
643
644 /**
645 * Writes DHCP record to the store according to the response DHCP packet (Offer, Ack).
646 *
647 * @param ethernet the DHCP packet
648 * @param dhcpPayload the DHCP payload
649 */
650 private void writeResponseDhcpRecord(Ethernet ethernet,
651 DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700652 Optional<Interface> outInterface = getClientInterface(ethernet, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700653 if (!outInterface.isPresent()) {
654 log.warn("Failed to determine where to send {}", dhcpPayload.getPacketType());
655 return;
656 }
657
658 Interface outIface = outInterface.get();
659 ConnectPoint location = outIface.connectPoint();
Yi Tseng4ec727d2017-08-31 11:21:00 -0700660 VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700661 if (vlanId == null) {
662 vlanId = outIface.vlan();
663 }
Yi Tseng51301292017-07-28 13:02:59 -0700664 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
665 HostId hostId = HostId.hostId(macAddress, vlanId);
666 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
667 if (record == null) {
668 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
669 } else {
670 record = record.clone();
671 }
672 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
673 if (dhcpPayload.getPacketType() == DHCP.MsgType.DHCPACK) {
674 record.ip4Address(Ip4Address.valueOf(dhcpPayload.getYourIPAddress()));
675 }
676 record.ip4Status(dhcpPayload.getPacketType());
677 record.setDirectlyConnected(directlyConnected(dhcpPayload));
678 record.updateLastSeen();
679 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
680 }
681
682 /**
683 * Build the DHCP offer/ack with proper client port.
684 *
685 * @param ethernetPacket the original packet comes from server
686 * @return new packet which will send to the client
687 */
688 private Ethernet processDhcpPacketFromServer(Ethernet ethernetPacket) {
689 // get dhcp header.
690 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
691 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
692 UDP udpPacket = (UDP) ipv4Packet.getPayload();
693 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
694
695 // determine the vlanId of the client host - note that this vlan id
696 // could be different from the vlan in the packet from the server
Yi Tsengdcef2c22017-08-05 20:34:06 -0700697 Interface clientInterface = getClientInterface(ethernetPacket, dhcpPayload).orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -0700698
Yi Tsengdcef2c22017-08-05 20:34:06 -0700699 if (clientInterface == null) {
Yi Tseng51301292017-07-28 13:02:59 -0700700 log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
701 return null;
702 }
Yi Tsengdcef2c22017-08-05 20:34:06 -0700703 VlanId vlanId;
704 if (clientInterface.vlanTagged().isEmpty()) {
705 vlanId = clientInterface.vlan();
706 } else {
707 // might be multiple vlan in same interface
Yi Tseng4ec727d2017-08-31 11:21:00 -0700708 vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700709 }
710 if (vlanId == null) {
711 vlanId = VlanId.NONE;
712 }
713 etherReply.setVlanID(vlanId.toShort());
714 etherReply.setSourceMACAddress(clientInterface.mac());
Yi Tseng51301292017-07-28 13:02:59 -0700715
Yi Tsengdcef2c22017-08-05 20:34:06 -0700716 if (!directlyConnected(dhcpPayload)) {
717 // if client is indirectly connected, try use next hop mac address
718 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
719 HostId hostId = HostId.hostId(macAddress, vlanId);
720 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
721 if (record != null) {
722 // if next hop can be found, use mac address of next hop
723 record.nextHop().ifPresent(etherReply::setDestinationMACAddress);
724 } else {
725 // otherwise, discard the packet
726 log.warn("Can't find record for host id {}, discard packet", hostId);
727 return null;
728 }
Yi Tsengc03fa242017-08-17 17:43:38 -0700729 } else {
730 etherReply.setDestinationMACAddress(dhcpPayload.getClientHardwareAddress());
Yi Tsengdcef2c22017-08-05 20:34:06 -0700731 }
732
Yi Tseng51301292017-07-28 13:02:59 -0700733 // we leave the srcMac from the original packet
Yi Tseng51301292017-07-28 13:02:59 -0700734 // figure out the relay agent IP corresponding to the original request
Yi Tseng4fa05832017-08-17 13:08:31 -0700735 Ip4Address ipFacingClient = getFirstIpFromInterface(clientInterface);
736 if (ipFacingClient == null) {
Yi Tseng51301292017-07-28 13:02:59 -0700737 log.warn("Cannot determine relay agent interface Ipv4 addr for host {}/{}. "
738 + "Aborting relay for dhcp packet from server {}",
Yi Tsengdcef2c22017-08-05 20:34:06 -0700739 etherReply.getDestinationMAC(), clientInterface.vlan(),
Yi Tseng51301292017-07-28 13:02:59 -0700740 ethernetPacket);
741 return null;
742 }
743 // SRC_IP: relay agent IP
744 // DST_IP: offered IP
Yi Tseng4fa05832017-08-17 13:08:31 -0700745 ipv4Packet.setSourceAddress(ipFacingClient.toInt());
Yi Tseng51301292017-07-28 13:02:59 -0700746 ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
747 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
748 if (directlyConnected(dhcpPayload)) {
749 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
750 } else {
751 // forward to another dhcp relay
Yi Tseng72b599a2017-09-14 13:24:21 -0700752 // FIXME: Currently we assume the DHCP comes from a L2 relay with
753 // Option 82, this might not work if DHCP message comes from
754 // L3 relay.
755 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
Yi Tseng51301292017-07-28 13:02:59 -0700756 }
757
758 udpPacket.setPayload(dhcpPayload);
759 ipv4Packet.setPayload(udpPacket);
760 etherReply.setPayload(ipv4Packet);
761 return etherReply;
762 }
763
Yi Tsengdcef2c22017-08-05 20:34:06 -0700764 /**
765 * Extracts VLAN ID from relay agent option.
766 *
767 * @param dhcpPayload the DHCP payload
768 * @return VLAN ID from DHCP payload; null if not exists
769 */
Yi Tseng4ec727d2017-08-31 11:21:00 -0700770 private VlanId getVlanIdFromRelayAgentOption(DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700771 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
772 if (option == null) {
773 return null;
774 }
775 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
776 if (circuitIdSubOption == null) {
777 return null;
778 }
779 try {
780 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
781 return circuitId.vlanId();
782 } catch (IllegalArgumentException e) {
783 // can't deserialize the circuit ID
784 return null;
785 }
786 }
787
788 /**
789 * Removes DHCP relay agent information option (option 82) from DHCP payload.
790 * Also reset giaddr to 0
791 *
792 * @param ethPacket the Ethernet packet to be processed
793 * @return Ethernet packet processed
794 */
795 private Ethernet removeRelayAgentOption(Ethernet ethPacket) {
796 Ethernet ethernet = (Ethernet) ethPacket.clone();
797 IPv4 ipv4 = (IPv4) ethernet.getPayload();
798 UDP udp = (UDP) ipv4.getPayload();
799 DHCP dhcpPayload = (DHCP) udp.getPayload();
800
801 // removes relay agent information option
802 List<DhcpOption> options = dhcpPayload.getOptions();
803 options = options.stream()
804 .filter(option -> option.getCode() != OptionCode_CircuitID.getValue())
805 .collect(Collectors.toList());
806 dhcpPayload.setOptions(options);
807 dhcpPayload.setGatewayIPAddress(0);
808
809 udp.setPayload(dhcpPayload);
810 ipv4.setPayload(udp);
811 ethernet.setPayload(ipv4);
812 return ethernet;
813 }
814
Yi Tseng51301292017-07-28 13:02:59 -0700815
816 /**
817 * Check if the host is directly connected to the network or not.
818 *
819 * @param dhcpPayload the dhcp payload
820 * @return true if the host is directly connected to the network; false otherwise
821 */
822 private boolean directlyConnected(DHCP dhcpPayload) {
Yi Tseng2cf59912017-08-24 14:47:34 -0700823 DhcpRelayAgentOption relayAgentOption =
824 (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
Yi Tseng51301292017-07-28 13:02:59 -0700825
826 // Doesn't contains relay option
827 if (relayAgentOption == null) {
828 return true;
829 }
830
Yi Tseng2cf59912017-08-24 14:47:34 -0700831 // check circuit id, if circuit id is invalid, we say it is an indirect host
832 DhcpOption circuitIdOpt = relayAgentOption.getSubOption(CIRCUIT_ID.getValue());
Yi Tseng51301292017-07-28 13:02:59 -0700833
Yi Tseng2cf59912017-08-24 14:47:34 -0700834 try {
835 CircuitId.deserialize(circuitIdOpt.getData());
Yi Tseng51301292017-07-28 13:02:59 -0700836 return true;
Yi Tseng2cf59912017-08-24 14:47:34 -0700837 } catch (Exception e) {
838 // invalid circuit id
839 return false;
Yi Tseng51301292017-07-28 13:02:59 -0700840 }
Yi Tseng51301292017-07-28 13:02:59 -0700841 }
842
843
844 /**
845 * Send the DHCP ack to the requester host.
846 * Modify Host or Route store according to the type of DHCP.
847 *
848 * @param ethernetPacketAck the packet
849 * @param dhcpPayload the DHCP data
850 */
851 private void handleDhcpAck(Ethernet ethernetPacketAck, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700852 Optional<Interface> outInterface = getClientInterface(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700853 if (!outInterface.isPresent()) {
854 log.warn("Can't find output interface for dhcp: {}", dhcpPayload);
855 return;
856 }
857
858 Interface outIface = outInterface.get();
859 HostLocation hostLocation = new HostLocation(outIface.connectPoint(), System.currentTimeMillis());
860 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
Yi Tseng4ec727d2017-08-31 11:21:00 -0700861 VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700862 if (vlanId == null) {
863 vlanId = outIface.vlan();
864 }
Yi Tseng51301292017-07-28 13:02:59 -0700865 HostId hostId = HostId.hostId(macAddress, vlanId);
866 Ip4Address ip = Ip4Address.valueOf(dhcpPayload.getYourIPAddress());
867
868 if (directlyConnected(dhcpPayload)) {
869 // Add to host store if it connect to network directly
870 Set<IpAddress> ips = Sets.newHashSet(ip);
Yi Tseng4b013202017-09-08 17:22:51 -0700871 Host host = hostService.getHost(hostId);
Yi Tseng51301292017-07-28 13:02:59 -0700872
Yi Tseng4b013202017-09-08 17:22:51 -0700873 Set<HostLocation> hostLocations = Sets.newHashSet(hostLocation);
874 if (host != null) {
875 // Dual homing support:
876 // if host exists, use old locations and new location
877 hostLocations.addAll(host.locations());
878 }
879 HostDescription desc = new DefaultHostDescription(macAddress, vlanId,
880 hostLocations, ips, false);
881 // Add IP address when dhcp server give the host new ip address
882 providerService.hostDetected(hostId, desc, false);
Yi Tseng51301292017-07-28 13:02:59 -0700883 } else {
884 // Add to route store if it does not connect to network directly
885 // Get gateway host IP according to host mac address
Yi Tsengdcef2c22017-08-05 20:34:06 -0700886 // TODO: remove relay store here
Yi Tseng51301292017-07-28 13:02:59 -0700887 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
888
889 if (record == null) {
890 log.warn("Can't find DHCP record of host {}", hostId);
891 return;
892 }
893
894 MacAddress gwMac = record.nextHop().orElse(null);
895 if (gwMac == null) {
896 log.warn("Can't find gateway mac address from record {}", record);
897 return;
898 }
899
900 HostId gwHostId = HostId.hostId(gwMac, record.vlanId());
901 Host gwHost = hostService.getHost(gwHostId);
902
903 if (gwHost == null) {
904 log.warn("Can't find gateway host {}", gwHostId);
905 return;
906 }
907
908 Ip4Address nextHopIp = gwHost.ipAddresses()
909 .stream()
910 .filter(IpAddress::isIp4)
911 .map(IpAddress::getIp4Address)
912 .findFirst()
913 .orElse(null);
914
915 if (nextHopIp == null) {
916 log.warn("Can't find IP address of gateway {}", gwHost);
917 return;
918 }
919
920 Route route = new Route(Route.Source.STATIC, ip.toIpPrefix(), nextHopIp);
921 routeStore.updateRoute(route);
922 }
Yi Tseng51301292017-07-28 13:02:59 -0700923 }
924
925 /**
926 * forward the packet to ConnectPoint where the DHCP server is attached.
927 *
928 * @param packet the packet
929 */
Yi Tseng4ec727d2017-08-31 11:21:00 -0700930 private void handleDhcpDiscoverAndRequest(Ethernet packet, DHCP dhcpPayload) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700931 boolean direct = directlyConnected(dhcpPayload);
932 DhcpServerInfo serverInfo = defaultServerInfoList.get(0);
933 if (!direct && !indirectServerInfoList.isEmpty()) {
934 serverInfo = indirectServerInfoList.get(0);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700935 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700936 ConnectPoint portToFotward = serverInfo.getDhcpServerConnectPoint().orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -0700937 // send packet to dhcp server connect point.
Yi Tseng4ec727d2017-08-31 11:21:00 -0700938 if (portToFotward != null) {
Yi Tseng51301292017-07-28 13:02:59 -0700939 TrafficTreatment t = DefaultTrafficTreatment.builder()
Yi Tseng4ec727d2017-08-31 11:21:00 -0700940 .setOutput(portToFotward.port()).build();
Yi Tseng51301292017-07-28 13:02:59 -0700941 OutboundPacket o = new DefaultOutboundPacket(
Yi Tseng4ec727d2017-08-31 11:21:00 -0700942 portToFotward.deviceId(), t, ByteBuffer.wrap(packet.serialize()));
Yi Tseng51301292017-07-28 13:02:59 -0700943 if (log.isTraceEnabled()) {
944 log.trace("Relaying packet to dhcp server {}", packet);
945 }
946 packetService.emit(o);
947 } else {
948 log.warn("Can't find DHCP server connect point, abort.");
949 }
950 }
951
952
953 /**
954 * Gets output interface of a dhcp packet.
955 * If option 82 exists in the dhcp packet and the option was sent by
Yi Tseng4ec727d2017-08-31 11:21:00 -0700956 * ONOS (circuit format is correct), use the connect
Yi Tseng51301292017-07-28 13:02:59 -0700957 * point and vlan id from circuit id; otherwise, find host by destination
958 * address and use vlan id from sender (dhcp server).
959 *
960 * @param ethPacket the ethernet packet
961 * @param dhcpPayload the dhcp packet
962 * @return an interface represent the output port and vlan; empty value
963 * if the host or circuit id not found
964 */
Yi Tsengdcef2c22017-08-05 20:34:06 -0700965 private Optional<Interface> getClientInterface(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tseng51301292017-07-28 13:02:59 -0700966 VlanId originalPacketVlanId = VlanId.vlanId(ethPacket.getVlanID());
Yi Tseng51301292017-07-28 13:02:59 -0700967 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
968
Yi Tseng4ec727d2017-08-31 11:21:00 -0700969 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
970 try {
971 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
972 ConnectPoint connectPoint = ConnectPoint.deviceConnectPoint(circuitId.connectPoint());
973 VlanId vlanId = circuitId.vlanId();
974 return interfaceService.getInterfacesByPort(connectPoint)
975 .stream()
976 .filter(iface -> interfaceContainsVlan(iface, vlanId))
977 .findFirst();
978 } catch (IllegalArgumentException ex) {
979 // invalid circuit format, didn't sent by ONOS
980 log.debug("Invalid circuit {}, use information from dhcp payload",
981 circuitIdSubOption.getData());
Yi Tseng51301292017-07-28 13:02:59 -0700982 }
983
984 // Use Vlan Id from DHCP server if DHCP relay circuit id was not
985 // sent by ONOS or circuit Id can't be parsed
Yi Tsengdcef2c22017-08-05 20:34:06 -0700986 // TODO: remove relay store from this method
Yi Tseng51301292017-07-28 13:02:59 -0700987 MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
988 Optional<DhcpRecord> dhcpRecord = dhcpRelayStore.getDhcpRecord(HostId.hostId(dstMac, originalPacketVlanId));
Yi Tsengdcef2c22017-08-05 20:34:06 -0700989 ConnectPoint clientConnectPoint = dhcpRecord
Yi Tseng51301292017-07-28 13:02:59 -0700990 .map(DhcpRecord::locations)
991 .orElse(Collections.emptySet())
992 .stream()
993 .reduce((hl1, hl2) -> {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700994 // find latest host connect point
Yi Tseng51301292017-07-28 13:02:59 -0700995 if (hl1 == null || hl2 == null) {
996 return hl1 == null ? hl2 : hl1;
997 }
998 return hl1.time() > hl2.time() ? hl1 : hl2;
999 })
Yi Tsengdcef2c22017-08-05 20:34:06 -07001000 .orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -07001001
Yi Tsengdcef2c22017-08-05 20:34:06 -07001002 if (clientConnectPoint != null) {
1003 return interfaceService.getInterfacesByPort(clientConnectPoint)
1004 .stream()
Yi Tseng4ec727d2017-08-31 11:21:00 -07001005 .filter(iface -> interfaceContainsVlan(iface, originalPacketVlanId))
Yi Tsengdcef2c22017-08-05 20:34:06 -07001006 .findFirst();
1007 }
1008 return Optional.empty();
Yi Tseng51301292017-07-28 13:02:59 -07001009 }
1010
1011 /**
1012 * Send the response DHCP to the requester host.
1013 *
1014 * @param ethPacket the packet
1015 * @param dhcpPayload the DHCP data
1016 */
1017 private void sendResponseToClient(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001018 Optional<Interface> outInterface = getClientInterface(ethPacket, dhcpPayload);
1019 if (directlyConnected(dhcpPayload)) {
1020 ethPacket = removeRelayAgentOption(ethPacket);
1021 }
1022 if (!outInterface.isPresent()) {
1023 log.warn("Can't find output interface for client, ignore");
1024 return;
1025 }
1026 Interface outIface = outInterface.get();
1027 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
1028 .setOutput(outIface.connectPoint().port())
1029 .build();
1030 OutboundPacket o = new DefaultOutboundPacket(
1031 outIface.connectPoint().deviceId(),
1032 treatment,
1033 ByteBuffer.wrap(ethPacket.serialize()));
1034 if (log.isTraceEnabled()) {
1035 log.trace("Relaying packet to DHCP client {} via {}, vlan {}",
1036 ethPacket,
1037 outIface.connectPoint(),
1038 outIface.vlan());
1039 }
1040 packetService.emit(o);
Yi Tseng51301292017-07-28 13:02:59 -07001041 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001042
Yi Tseng4b013202017-09-08 17:22:51 -07001043 @Override
1044 public void triggerProbe(Host host) {
1045 // Do nothing here
1046 }
1047
1048 @Override
1049 public ProviderId id() {
Charles Chan75edab72017-09-12 17:09:32 -07001050 return PROVIDER_ID;
Yi Tseng4b013202017-09-08 17:22:51 -07001051 }
1052
Yi Tsenge72fbb52017-08-02 15:03:31 -07001053 class InternalHostListener implements HostListener {
1054 @Override
1055 public void event(HostEvent event) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001056 if (!configured()) {
1057 return;
1058 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001059 switch (event.type()) {
1060 case HOST_ADDED:
1061 case HOST_UPDATED:
1062 hostUpdated(event.subject());
1063 break;
1064 case HOST_REMOVED:
1065 hostRemoved(event.subject());
1066 break;
Yi Tsenge72fbb52017-08-02 15:03:31 -07001067 default:
1068 break;
1069 }
1070 }
1071 }
1072
1073 /**
Yi Tsenge72fbb52017-08-02 15:03:31 -07001074 * Handle host updated.
1075 * If the host is DHCP server or gateway, update connect mac and vlan.
1076 *
1077 * @param host the host
1078 */
1079 private void hostUpdated(Host host) {
Yi Tseng7da339e2017-10-23 19:39:39 -07001080 hostUpdated(host, defaultServerInfoList);
1081 hostUpdated(host, indirectServerInfoList);
Yi Tsenge72fbb52017-08-02 15:03:31 -07001082 }
1083
Yi Tseng7da339e2017-10-23 19:39:39 -07001084 private void hostUpdated(Host host, List<DhcpServerInfo> srverInfoList) {
1085 DhcpServerInfo serverInfo;
1086 Ip4Address targetIp;
1087 if (!srverInfoList.isEmpty()) {
1088 serverInfo = srverInfoList.get(0);
1089 targetIp = serverInfo.getDhcpGatewayIp4().orElse(null);
1090 Ip4Address serverIp = serverInfo.getDhcpServerIp4().orElse(null);
1091
1092 if (targetIp == null) {
1093 targetIp = serverIp;
1094 }
1095
1096 if (targetIp != null) {
1097 if (host.ipAddresses().contains(targetIp)) {
1098 serverInfo.setDhcpConnectMac(host.mac());
1099 serverInfo.setDhcpConnectVlan(host.vlan());
1100 requestDhcpPacket(serverIp);
1101 }
1102 }
1103 }
1104 }
1105
1106
Yi Tsenge72fbb52017-08-02 15:03:31 -07001107 /**
1108 * Handle host removed.
1109 * If the host is DHCP server or gateway, unset connect mac and vlan.
1110 *
1111 * @param host the host
1112 */
1113 private void hostRemoved(Host host) {
Yi Tseng7da339e2017-10-23 19:39:39 -07001114 hostRemoved(host, defaultServerInfoList);
1115 hostRemoved(host, indirectServerInfoList);
1116 }
1117
1118 private void hostRemoved(Host host, List<DhcpServerInfo> serverInfoList) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001119 DhcpServerInfo serverInfo;
Yi Tseng7da339e2017-10-23 19:39:39 -07001120 Ip4Address targetIp;
1121 if (!serverInfoList.isEmpty()) {
1122 serverInfo = serverInfoList.get(0);
1123 Ip4Address serverIp = serverInfo.getDhcpServerIp4().orElse(null);
1124 targetIp = serverInfo.getDhcpGatewayIp4().orElse(null);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001125
Yi Tseng7da339e2017-10-23 19:39:39 -07001126 if (targetIp == null) {
1127 targetIp = serverIp;
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001128 }
Yi Tseng7da339e2017-10-23 19:39:39 -07001129
1130 if (targetIp != null) {
1131 if (host.ipAddresses().contains(targetIp)) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001132 serverInfo.setDhcpConnectVlan(null);
1133 serverInfo.setDhcpConnectMac(null);
Yi Tseng7da339e2017-10-23 19:39:39 -07001134 cancelDhcpPacket(serverIp);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001135 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001136 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001137 }
Yi Tseng7da339e2017-10-23 19:39:39 -07001138 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001139
Yi Tseng7da339e2017-10-23 19:39:39 -07001140 private void requestDhcpPacket(Ip4Address serverIp) {
1141 requestServerDhcpPacket(serverIp);
1142 requestClientDhcpPacket(serverIp);
1143 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001144
Yi Tseng7da339e2017-10-23 19:39:39 -07001145 private void cancelDhcpPacket(Ip4Address serverIp) {
1146 cancelServerDhcpPacket(serverIp);
1147 cancelClientDhcpPacket(serverIp);
1148 }
1149
1150 private void cancelServerDhcpPacket(Ip4Address serverIp) {
1151 TrafficSelector serverSelector =
1152 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1153 .matchIPSrc(serverIp.toIpPrefix())
1154 .build();
1155 packetService.cancelPackets(serverSelector,
1156 PacketPriority.CONTROL,
1157 appId);
1158 }
1159
1160 private void requestServerDhcpPacket(Ip4Address serverIp) {
1161 TrafficSelector serverSelector =
1162 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1163 .matchIPSrc(serverIp.toIpPrefix())
1164 .build();
1165 packetService.requestPackets(serverSelector,
1166 PacketPriority.CONTROL,
1167 appId);
1168 }
1169
1170 private void cancelClientDhcpPacket(Ip4Address serverIp) {
1171 // Packet comes from relay
1172 TrafficSelector indirectClientSelector =
1173 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1174 .matchIPDst(serverIp.toIpPrefix())
1175 .build();
1176 packetService.cancelPackets(indirectClientSelector,
1177 PacketPriority.CONTROL,
1178 appId);
1179
1180 // Packet comes from client
1181 packetService.cancelPackets(CLIENT_SERVER_SELECTOR,
1182 PacketPriority.CONTROL,
1183 appId);
1184 }
1185
1186 private void requestClientDhcpPacket(Ip4Address serverIp) {
1187 // Packet comes from relay
1188 TrafficSelector indirectClientSelector =
1189 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1190 .matchIPDst(serverIp.toIpPrefix())
1191 .build();
1192 packetService.requestPackets(indirectClientSelector,
1193 PacketPriority.CONTROL,
1194 appId);
1195
1196 // Packet comes from client
1197 packetService.requestPackets(CLIENT_SERVER_SELECTOR,
1198 PacketPriority.CONTROL,
1199 appId);
1200 }
1201
1202 /**
1203 * Process the ignore rules.
1204 *
1205 * @param deviceId the device id
1206 * @param vlanId the vlan to be ignored
1207 * @param op the operation, ADD to install; REMOVE to uninstall rules
1208 */
1209 private void processIgnoreVlanRule(DeviceId deviceId, VlanId vlanId, Objective.Operation op) {
1210 TrafficTreatment dropTreatment = DefaultTrafficTreatment.builder().wipeDeferred().build();
1211 AtomicInteger installedCount = new AtomicInteger(DHCP_SELECTORS.size());
1212 DHCP_SELECTORS.forEach(trafficSelector -> {
1213 TrafficSelector selector = DefaultTrafficSelector.builder(trafficSelector)
1214 .matchVlanId(vlanId)
1215 .build();
1216
1217 ForwardingObjective.Builder builder = DefaultForwardingObjective.builder()
1218 .withFlag(ForwardingObjective.Flag.VERSATILE)
1219 .withSelector(selector)
1220 .withPriority(IGNORE_CONTROL_PRIORITY)
1221 .withTreatment(dropTreatment)
1222 .fromApp(appId);
1223
1224
1225 ObjectiveContext objectiveContext = new ObjectiveContext() {
1226 @Override
1227 public void onSuccess(Objective objective) {
1228 log.info("Ignore rule {} (Vlan id {}, device {}, selector {})",
1229 op, vlanId, deviceId, selector);
1230 int countDown = installedCount.decrementAndGet();
1231 if (countDown != 0) {
1232 return;
1233 }
1234 switch (op) {
1235 case ADD:
1236 ignoredVlans.put(deviceId, vlanId);
1237 break;
1238 case REMOVE:
1239 ignoredVlans.remove(deviceId, vlanId);
1240 break;
1241 default:
1242 log.warn("Unsupported objective operation {}", op);
1243 break;
1244 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001245 }
Yi Tseng7da339e2017-10-23 19:39:39 -07001246
1247 @Override
1248 public void onError(Objective objective, ObjectiveError error) {
1249 log.warn("Can't {} ignore rule (vlan id {}, selector {}, device {}) due to {}",
1250 op, vlanId, selector, deviceId, error);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001251 }
Yi Tseng7da339e2017-10-23 19:39:39 -07001252 };
1253
1254 ForwardingObjective fwd;
1255 switch (op) {
1256 case ADD:
1257 fwd = builder.add(objectiveContext);
1258 break;
1259 case REMOVE:
1260 fwd = builder.remove(objectiveContext);
1261 break;
1262 default:
1263 log.warn("Unsupported objective operation {}", op);
1264 return;
Yi Tseng4ec727d2017-08-31 11:21:00 -07001265 }
Yi Tseng7da339e2017-10-23 19:39:39 -07001266
1267 Device device = deviceService.getDevice(deviceId);
1268 if (device == null || !device.is(Pipeliner.class)) {
1269 log.warn("Device {} is not available now, wait until device is available", deviceId);
1270 return;
1271 }
1272 flowObjectiveService.apply(deviceId, fwd);
1273 });
Yi Tsenge72fbb52017-08-02 15:03:31 -07001274 }
Yi Tseng51301292017-07-28 13:02:59 -07001275}