blob: d49b9a994b87ad66392d32995c29aa965a16ed28 [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;
Charles Chan2f9aa2c2017-10-08 23:53:36 -0400365 case DHCPDECLINE:
366 break;
Yi Tseng51301292017-07-28 13:02:59 -0700367 case DHCPACK:
368 // reply to dhcp client.
369 Ethernet ethernetPacketAck = processDhcpPacketFromServer(packet);
370 if (ethernetPacketAck != null) {
371 writeResponseDhcpRecord(ethernetPacketAck, dhcpPayload);
372 handleDhcpAck(ethernetPacketAck, dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700373 sendResponseToClient(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700374 }
375 break;
Charles Chan2f9aa2c2017-10-08 23:53:36 -0400376 case DHCPNAK:
377 break;
Yi Tseng51301292017-07-28 13:02:59 -0700378 case DHCPRELEASE:
379 // TODO: release the ip address from client
380 break;
Charles Chan2f9aa2c2017-10-08 23:53:36 -0400381 case DHCPINFORM:
382 break;
383 case DHCPFORCERENEW:
384 break;
385 case DHCPLEASEQUERY:
386 handleLeaseQueryMsg(context, packet, dhcpPayload);
387 break;
388 case DHCPLEASEACTIVE:
389 handleLeaseQueryActivateMsg(packet, dhcpPayload);
390 break;
391 case DHCPLEASEUNASSIGNED:
392 case DHCPLEASEUNKNOWN:
393 handleLeaseQueryUnknown(packet, dhcpPayload);
394 break;
Yi Tseng51301292017-07-28 13:02:59 -0700395 default:
396 break;
397 }
398 }
399
400 /**
401 * Checks if this app has been configured.
402 *
403 * @return true if all information we need have been initialized
404 */
Yi Tseng4ec727d2017-08-31 11:21:00 -0700405 private boolean configured() {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700406 return !defaultServerInfoList.isEmpty();
Yi Tseng51301292017-07-28 13:02:59 -0700407 }
408
409 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -0700410 * Returns the first interface ip from interface.
Yi Tseng51301292017-07-28 13:02:59 -0700411 *
Yi Tsengdcef2c22017-08-05 20:34:06 -0700412 * @param iface interface of one connect point
Yi Tseng51301292017-07-28 13:02:59 -0700413 * @return the first interface IP; null if not exists an IP address in
414 * these interfaces
415 */
Yi Tseng4fa05832017-08-17 13:08:31 -0700416 private Ip4Address getFirstIpFromInterface(Interface iface) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700417 checkNotNull(iface, "Interface can't be null");
418 return iface.ipAddressesList().stream()
Yi Tseng51301292017-07-28 13:02:59 -0700419 .map(InterfaceIpAddress::ipAddress)
420 .filter(IpAddress::isIp4)
421 .map(IpAddress::getIp4Address)
422 .findFirst()
423 .orElse(null);
424 }
425
426 /**
Yi Tseng4ec727d2017-08-31 11:21:00 -0700427 * Gets Interface facing to the server for default host.
Yi Tsengdcef2c22017-08-05 20:34:06 -0700428 *
429 * @return the Interface facing to the server; null if not found
430 */
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700431 private Interface getDefaultServerInterface() {
432 return getServerInterface(defaultServerInfoList);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700433 }
434
435 /**
Yi Tseng4ec727d2017-08-31 11:21:00 -0700436 * Gets Interface facing to the server for indirect hosts.
437 * Use default server Interface if indirect server not configured.
438 *
439 * @return the Interface facing to the server; null if not found
440 */
441 private Interface getIndirectServerInterface() {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700442 return getServerInterface(indirectServerInfoList);
443 }
444
445 private Interface getServerInterface(List<DhcpServerInfo> serverInfos) {
446 DhcpServerInfo serverInfo = serverInfos.get(0);
447 ConnectPoint dhcpServerConnectPoint = serverInfo.getDhcpServerConnectPoint().orElse(null);
448 VlanId dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
449 if (dhcpServerConnectPoint == null || dhcpConnectVlan == null) {
450 return null;
Yi Tseng4ec727d2017-08-31 11:21:00 -0700451 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700452 return interfaceService.getInterfacesByPort(dhcpServerConnectPoint)
Yi Tseng4ec727d2017-08-31 11:21:00 -0700453 .stream()
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700454 .filter(iface -> interfaceContainsVlan(iface, dhcpConnectVlan))
Yi Tseng4ec727d2017-08-31 11:21:00 -0700455 .findFirst()
456 .orElse(null);
457 }
458
459 /**
460 * Determind if an Interface contains a vlan id.
461 *
462 * @param iface the Interface
463 * @param vlanId the vlan id
464 * @return true if the Interface contains the vlan id
465 */
466 private boolean interfaceContainsVlan(Interface iface, VlanId vlanId) {
Yi Tseng58e74312017-09-30 11:35:42 +0800467 if (vlanId.equals(VlanId.NONE)) {
468 // untagged packet, check if vlan untagged or vlan native is not NONE
469 return !iface.vlanUntagged().equals(VlanId.NONE) ||
470 !iface.vlanNative().equals(VlanId.NONE);
471 }
472 // tagged packet, check if the interface contains the vlan
473 return iface.vlanTagged().contains(vlanId);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700474 }
475
Charles Chan2f9aa2c2017-10-08 23:53:36 -0400476 private void handleLeaseQueryActivateMsg(Ethernet packet, DHCP dhcpPayload) {
477 log.debug("LQ: Got DHCPLEASEACTIVE packet!");
478
479 // TODO: release the ip address from client
480 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
481 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
482 HostId hostId = HostId.hostId(clientMacAddress, vlanId);
483 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
484
485 if (record == null) {
486 log.warn("Can't find record for host {} when processing DHCPLEASEACTIVE", hostId);
487 return;
488 }
489
490 // need to update routes
491 log.debug("Lease Query for Client results in DHCPLEASEACTIVE - route needs to be modified");
492 // get current route
493 // find the ip of that client with the DhcpRelay store
494
495 Ip4Address clientIP = record.ip4Address().orElse(null);
496 log.debug("LQ: IP of host is " + clientIP.getIp4Address());
497
498 MacAddress nextHopMac = record.nextHop().orElse(null);
499 log.debug("LQ: MAC of resulting *OLD* NH for that host is " + nextHopMac.toString());
500
501 // find the new NH by looking at the src MAC of the dhcp request
502 // from the LQ store
503 MacAddress newNextHopMac = record.nextHopTemp().orElse(null);
504 log.debug("LQ: MAC of resulting *NEW* NH for that host is " + newNextHopMac.toString());
505
506 log.debug("LQ: updating dhcp relay record with new NH");
507 record.nextHop(newNextHopMac);
508
509 // find the next hop IP from its mac
510 HostId gwHostId = HostId.hostId(newNextHopMac, vlanId);
511 Host gwHost = hostService.getHost(gwHostId);
512
513 if (gwHost == null) {
514 log.warn("Can't find gateway for new NH host " + gwHostId);
515 return;
516 }
517
518 Ip4Address nextHopIp = gwHost.ipAddresses()
519 .stream()
520 .filter(IpAddress::isIp4)
521 .map(IpAddress::getIp4Address)
522 .findFirst()
523 .orElse(null);
524
525 if (nextHopIp == null) {
526 log.warn("Can't find IP address of gateway " + gwHost);
527 return;
528 }
529
530 log.debug("LQ: *NEW* NH IP for host is " + nextHopIp.getIp4Address());
531 Route route = new Route(Route.Source.STATIC, clientIP.toIpPrefix(), nextHopIp);
532 routeStore.updateRoute(route);
533
534 // and forward to client
535 Ethernet ethernetPacket = processLeaseQueryFromServer(packet);
536 if (ethernetPacket != null) {
537 sendResponseToClient(ethernetPacket, dhcpPayload);
538 }
539 }
540
541 /**
542 *
543 */
544 private void handleLeaseQueryMsg(PacketContext context, Ethernet packet, DHCP dhcpPayload) {
545 log.debug("LQ: Got DHCPLEASEQUERY packet!");
546 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
547 log.debug("LQ: got DHCPLEASEQUERY with MAC " + clientMacAddress.toString());
548 // add the client mac (hostid) of this request to a store (the entry will be removed with
549 // the reply sent to the originator)
550 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
551 HostId hId = HostId.hostId(clientMacAddress, vlanId);
552 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hId).orElse(null);
553 if (record != null) {
554 //new NH is to be taken from src mac of LQ packet
555 MacAddress newNextHop = packet.getSourceMAC();
556 record.nextHopTemp(newNextHop);
557 record.ip4Status(dhcpPayload.getPacketType());
558 record.updateLastSeen();
559
560 // do a basic routing of the packet (this is unicast routing
561 // not a relay operation like for other broadcast dhcp packets
562 Ethernet ethernetPacketLQ = processLeaseQueryFromAgent(context, packet);
563 // and forward to server
564 handleDhcpDiscoverAndRequest(ethernetPacketLQ, dhcpPayload);
565 } else {
566 log.warn("LQ: Error! - DHCP relay record for that client not found - ignoring LQ!");
567 }
568 }
569
570 private void handleLeaseQueryUnknown(Ethernet packet, DHCP dhcpPayload) {
571 log.debug("Lease Query for Client results in DHCPLEASEUNASSIGNED or " +
572 "DHCPLEASEUNKNOWN - removing route & forwarding reply to originator");
573 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
574 VlanId vlanId = VlanId.vlanId(packet.getVlanID());
575 HostId hostId = HostId.hostId(clientMacAddress, vlanId);
576 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
577
578 if (record == null) {
579 log.warn("Can't find record for host {} when handling LQ UNKNOWN/UNASSIGNED message", hostId);
580 return;
581 }
582
583 Ip4Address clientIP = record.ip4Address().orElse(null);
584 log.debug("LQ: IP of host is " + clientIP.getIp4Address());
585
586 // find the new NH by looking at the src MAC of the dhcp request
587 // from the LQ store
588 MacAddress nextHopMac = record.nextHop().orElse(null);
589 log.debug("LQ: MAC of resulting *Existing* NH for that route is " + nextHopMac.toString());
590
591 // find the next hop IP from its mac
592 HostId gwHostId = HostId.hostId(nextHopMac, vlanId);
593 Host gwHost = hostService.getHost(gwHostId);
594
595 if (gwHost == null) {
596 log.warn("Can't find gateway for new NH host " + gwHostId);
597 return;
598 }
599
600 Ip4Address nextHopIp = gwHost.ipAddresses()
601 .stream()
602 .filter(IpAddress::isIp4)
603 .map(IpAddress::getIp4Address)
604 .findFirst()
605 .orElse(null);
606
607 if (nextHopIp == null) {
608 log.warn("Can't find IP address of gateway {}", gwHost);
609 return;
610 }
611
612 log.debug("LQ: *Existing* NH IP for host is " + nextHopIp.getIp4Address() + " removing route for it");
613 Route route = new Route(Route.Source.STATIC, clientIP.toIpPrefix(), nextHopIp);
614 routeStore.removeRoute(route);
615
616 // remove from temp store
617 dhcpRelayStore.removeDhcpRecord(hostId);
618
619 // and forward to client
620 Ethernet ethernetPacket = processLeaseQueryFromServer(packet);
621 if (ethernetPacket != null) {
622 sendResponseToClient(ethernetPacket, dhcpPayload);
623 }
624 }
625
Yi Tseng4ec727d2017-08-31 11:21:00 -0700626 /**
Yi Tseng51301292017-07-28 13:02:59 -0700627 * Build the DHCP discover/request packet with gateway IP(unicast packet).
628 *
629 * @param context the packet context
630 * @param ethernetPacket the ethernet payload to process
Yi Tseng51301292017-07-28 13:02:59 -0700631 * @return processed packet
632 */
633 private Ethernet processDhcpPacketFromClient(PacketContext context,
Yi Tsengdcef2c22017-08-05 20:34:06 -0700634 Ethernet ethernetPacket) {
Yi Tseng4ec727d2017-08-31 11:21:00 -0700635 // get dhcp header.
636 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
637 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
638 UDP udpPacket = (UDP) ipv4Packet.getPayload();
639 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
640
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700641 VlanId dhcpConnectVlan = null;
642 MacAddress dhcpConnectMac = null;
643 Ip4Address dhcpServerIp = null;
644 Ip4Address relayAgentIp = null;
645
646 VlanId indirectDhcpConnectVlan = null;
647 MacAddress indirectDhcpConnectMac = null;
648 Ip4Address indirectDhcpServerIp = null;
649 Ip4Address indirectRelayAgentIp = null;
650
651 if (!defaultServerInfoList.isEmpty()) {
652 DhcpServerInfo serverInfo = defaultServerInfoList.get(0);
653 dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
654 dhcpConnectMac = serverInfo.getDhcpConnectMac().orElse(null);
655 dhcpServerIp = serverInfo.getDhcpServerIp4().orElse(null);
656 relayAgentIp = serverInfo.getRelayAgentIp4().orElse(null);
657 }
658
659 if (!indirectServerInfoList.isEmpty()) {
660 DhcpServerInfo indirectServerInfo = indirectServerInfoList.get(0);
661 indirectDhcpConnectVlan = indirectServerInfo.getDhcpConnectVlan().orElse(null);
662 indirectDhcpConnectMac = indirectServerInfo.getDhcpConnectMac().orElse(null);
663 indirectDhcpServerIp = indirectServerInfo.getDhcpServerIp4().orElse(null);
664 indirectRelayAgentIp = indirectServerInfo.getRelayAgentIp4().orElse(null);
665 }
666
Yi Tsengdcef2c22017-08-05 20:34:06 -0700667 Ip4Address clientInterfaceIp =
668 interfaceService.getInterfacesByPort(context.inPacket().receivedFrom())
669 .stream()
670 .map(Interface::ipAddressesList)
671 .flatMap(Collection::stream)
672 .map(InterfaceIpAddress::ipAddress)
673 .filter(IpAddress::isIp4)
674 .map(IpAddress::getIp4Address)
675 .findFirst()
676 .orElse(null);
677 if (clientInterfaceIp == null) {
678 log.warn("Can't find interface IP for client interface for port {}",
679 context.inPacket().receivedFrom());
680 return null;
681 }
Yi Tseng4ec727d2017-08-31 11:21:00 -0700682 boolean isDirectlyConnected = directlyConnected(dhcpPacket);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700683 Interface serverInterface = isDirectlyConnected ? getDefaultServerInterface() : getIndirectServerInterface();
Yi Tsengdcef2c22017-08-05 20:34:06 -0700684 if (serverInterface == null) {
Yi Tseng4ec727d2017-08-31 11:21:00 -0700685 log.warn("Can't get {} server interface, ignore", isDirectlyConnected ? "direct" : "indirect");
Yi Tsengdcef2c22017-08-05 20:34:06 -0700686 return null;
687 }
Yi Tseng4fa05832017-08-17 13:08:31 -0700688 Ip4Address ipFacingServer = getFirstIpFromInterface(serverInterface);
689 MacAddress macFacingServer = serverInterface.mac();
690 if (ipFacingServer == null || macFacingServer == null) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700691 log.warn("No IP address for server Interface {}", serverInterface);
Yi Tseng51301292017-07-28 13:02:59 -0700692 return null;
693 }
694 if (dhcpConnectMac == null) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -0700695 log.warn("DHCP Server/Gateway IP not yet resolved .. Aborting DHCP "
Yi Tseng51301292017-07-28 13:02:59 -0700696 + "packet processing from client on port: {}",
Yi Tsengdcef2c22017-08-05 20:34:06 -0700697 context.inPacket().receivedFrom());
Yi Tseng51301292017-07-28 13:02:59 -0700698 return null;
699 }
Yi Tseng4ec727d2017-08-31 11:21:00 -0700700
Yi Tseng4fa05832017-08-17 13:08:31 -0700701 etherReply.setSourceMACAddress(macFacingServer);
Yi Tseng4fa05832017-08-17 13:08:31 -0700702 ipv4Packet.setSourceAddress(ipFacingServer.toInt());
Yi Tseng51301292017-07-28 13:02:59 -0700703
Yi Tseng4ec727d2017-08-31 11:21:00 -0700704 if (isDirectlyConnected) {
Charles Chanb5d24822017-10-10 16:53:32 -0400705 etherReply.setDestinationMACAddress(dhcpConnectMac);
706 etherReply.setVlanID(dhcpConnectVlan.toShort());
707 ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
708
Yi Tseng51301292017-07-28 13:02:59 -0700709 ConnectPoint inPort = context.inPacket().receivedFrom();
710 VlanId vlanId = VlanId.vlanId(ethernetPacket.getVlanID());
711 // add connected in port and vlan
712 CircuitId cid = new CircuitId(inPort.toString(), vlanId);
713 byte[] circuitId = cid.serialize();
714 DhcpOption circuitIdSubOpt = new DhcpOption();
715 circuitIdSubOpt
716 .setCode(CIRCUIT_ID.getValue())
717 .setLength((byte) circuitId.length)
718 .setData(circuitId);
719
720 DhcpRelayAgentOption newRelayAgentOpt = new DhcpRelayAgentOption();
721 newRelayAgentOpt.setCode(OptionCode_CircuitID.getValue());
722 newRelayAgentOpt.addSubOption(circuitIdSubOpt);
723
Charles Chanb5d24822017-10-10 16:53:32 -0400724 // Removes END option first
Yi Tseng51301292017-07-28 13:02:59 -0700725 List<DhcpOption> options = dhcpPacket.getOptions().stream()
726 .filter(opt -> opt.getCode() != OptionCode_END.getValue())
727 .collect(Collectors.toList());
728
729 // push relay agent option
730 options.add(newRelayAgentOpt);
731
732 // make sure option 255(End) is the last option
733 DhcpOption endOption = new DhcpOption();
734 endOption.setCode(OptionCode_END.getValue());
735 options.add(endOption);
736
737 dhcpPacket.setOptions(options);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700738
Charles Chanb5d24822017-10-10 16:53:32 -0400739 // Sets relay agent IP
740 int effectiveRelayAgentIp = relayAgentIp != null ?
741 relayAgentIp.toInt() : clientInterfaceIp.toInt();
742 dhcpPacket.setGatewayIPAddress(effectiveRelayAgentIp);
743 } else {
744 if (indirectDhcpServerIp != null) {
745 // Use indirect server config for indirect packets if configured
746 etherReply.setDestinationMACAddress(indirectDhcpConnectMac);
747 etherReply.setVlanID(indirectDhcpConnectVlan.toShort());
748 ipv4Packet.setDestinationAddress(indirectDhcpServerIp.toInt());
Yi Tseng51301292017-07-28 13:02:59 -0700749
Charles Chanb5d24822017-10-10 16:53:32 -0400750 // Set giaddr if indirect relay agent IP is configured
751 if (indirectRelayAgentIp != null) {
752 dhcpPacket.setGatewayIPAddress(indirectRelayAgentIp.toInt());
753 }
754 } else {
755 // Otherwise, use default server config for indirect packets
756 etherReply.setDestinationMACAddress(dhcpConnectMac);
757 etherReply.setVlanID(dhcpConnectVlan.toShort());
758 ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
Yi Tseng4ec727d2017-08-31 11:21:00 -0700759
Charles Chanb5d24822017-10-10 16:53:32 -0400760 // Set giaddr if direct relay agent IP is configured
761 if (relayAgentIp != null) {
762 dhcpPacket.setGatewayIPAddress(relayAgentIp.toInt());
763 }
Yi Tseng4ec727d2017-08-31 11:21:00 -0700764 }
Yi Tseng4fa05832017-08-17 13:08:31 -0700765 }
766
Yi Tsengf41681e2017-10-03 09:58:19 -0700767 // Remove broadcast flag
768 dhcpPacket.setFlags((short) 0);
769
Yi Tseng51301292017-07-28 13:02:59 -0700770 udpPacket.setPayload(dhcpPacket);
Yi Tseng4ec727d2017-08-31 11:21:00 -0700771 // As a DHCP relay, the source port should be server port( instead
772 // of client port.
Yi Tsengdcef2c22017-08-05 20:34:06 -0700773 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
Yi Tseng51301292017-07-28 13:02:59 -0700774 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
775 ipv4Packet.setPayload(udpPacket);
Charles Chan7edf7642017-10-09 11:07:25 -0400776 ipv4Packet.setTtl((byte) 64);
Yi Tseng51301292017-07-28 13:02:59 -0700777 etherReply.setPayload(ipv4Packet);
778 return etherReply;
779 }
780
Charles Chan2f9aa2c2017-10-08 23:53:36 -0400781
782 /**
783 * Do a basic routing for a packet from client (used for LQ processing).
784 *
785 * @param context the packet context
786 * @param ethernetPacket the ethernet payload to process
787 * @return processed packet
788 */
789 private Ethernet processLeaseQueryFromAgent(PacketContext context,
790 Ethernet ethernetPacket) {
791 // get dhcp header.
792 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
793 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
794 UDP udpPacket = (UDP) ipv4Packet.getPayload();
795 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
796
797 VlanId dhcpConnectVlan = null;
798 MacAddress dhcpConnectMac = null;
799 Ip4Address dhcpServerIp = null;
800
801 VlanId indirectDhcpConnectVlan = null;
802 MacAddress indirectDhcpConnectMac = null;
803 Ip4Address indirectDhcpServerIp = null;
804
805 if (!defaultServerInfoList.isEmpty()) {
806 DhcpServerInfo serverInfo = defaultServerInfoList.get(0);
807 dhcpConnectVlan = serverInfo.getDhcpConnectVlan().orElse(null);
808 dhcpConnectMac = serverInfo.getDhcpConnectMac().orElse(null);
809 dhcpServerIp = serverInfo.getDhcpServerIp4().orElse(null);
810 }
811
812 if (!indirectServerInfoList.isEmpty()) {
813 DhcpServerInfo indirectServerInfo = indirectServerInfoList.get(0);
814 indirectDhcpConnectVlan = indirectServerInfo.getDhcpConnectVlan().orElse(null);
815 indirectDhcpConnectMac = indirectServerInfo.getDhcpConnectMac().orElse(null);
816 indirectDhcpServerIp = indirectServerInfo.getDhcpServerIp4().orElse(null);
817 }
818
819 Ip4Address clientInterfaceIp =
820 interfaceService.getInterfacesByPort(context.inPacket().receivedFrom())
821 .stream()
822 .map(Interface::ipAddressesList)
823 .flatMap(Collection::stream)
824 .map(InterfaceIpAddress::ipAddress)
825 .filter(IpAddress::isIp4)
826 .map(IpAddress::getIp4Address)
827 .findFirst()
828 .orElse(null);
829 if (clientInterfaceIp == null) {
830 log.warn("Can't find interface IP for client interface for port {}",
831 context.inPacket().receivedFrom());
832 return null;
833 }
834 boolean isDirectlyConnected = directlyConnected(dhcpPacket);
835 Interface serverInterface = isDirectlyConnected ? getDefaultServerInterface() : getIndirectServerInterface();
836 if (serverInterface == null) {
837 log.warn("Can't get {} server interface, ignore", isDirectlyConnected ? "direct" : "indirect");
838 return null;
839 }
840 Ip4Address ipFacingServer = getFirstIpFromInterface(serverInterface);
841 MacAddress macFacingServer = serverInterface.mac();
842 if (ipFacingServer == null || macFacingServer == null) {
843 log.warn("No IP address for server Interface {}", serverInterface);
844 return null;
845 }
846 if (dhcpConnectMac == null) {
847 log.warn("DHCP server/gateway not yet resolved .. Aborting DHCP "
848 + "packet processing from client on port: {}",
849 context.inPacket().receivedFrom());
850 return null;
851 }
852
853 etherReply.setSourceMACAddress(macFacingServer);
854 etherReply.setDestinationMACAddress(dhcpConnectMac);
855 etherReply.setVlanID(dhcpConnectVlan.toShort());
856 ipv4Packet.setSourceAddress(ipFacingServer.toInt());
857 ipv4Packet.setDestinationAddress(dhcpServerIp.toInt());
858
859 if (indirectDhcpServerIp != null) {
860 // Indirect case, replace destination to indirect dhcp server if exist
861 etherReply.setDestinationMACAddress(indirectDhcpConnectMac);
862 etherReply.setVlanID(indirectDhcpConnectVlan.toShort());
863 ipv4Packet.setDestinationAddress(indirectDhcpServerIp.toInt());
864 }
865
866 udpPacket.setPayload(dhcpPacket);
867 // As a DHCP relay, the source port should be server port( instead
868 // of client port.
869 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
870 udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
871 ipv4Packet.setPayload(udpPacket);
872 etherReply.setPayload(ipv4Packet);
873 return etherReply;
874 }
875
876
Yi Tseng51301292017-07-28 13:02:59 -0700877 /**
878 * Writes DHCP record to the store according to the request DHCP packet (Discover, Request).
879 *
880 * @param location the location which DHCP packet comes from
881 * @param ethernet the DHCP packet
882 * @param dhcpPayload the DHCP payload
883 */
884 private void writeRequestDhcpRecord(ConnectPoint location,
885 Ethernet ethernet,
886 DHCP dhcpPayload) {
887 VlanId vlanId = VlanId.vlanId(ethernet.getVlanID());
888 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
889 HostId hostId = HostId.hostId(macAddress, vlanId);
890 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
891 if (record == null) {
892 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
893 } else {
894 record = record.clone();
895 }
896 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
897 record.ip4Status(dhcpPayload.getPacketType());
898 record.setDirectlyConnected(directlyConnected(dhcpPayload));
899 if (!directlyConnected(dhcpPayload)) {
900 // Update gateway mac address if the host is not directly connected
901 record.nextHop(ethernet.getSourceMAC());
902 }
903 record.updateLastSeen();
904 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
905 }
906
907 /**
908 * Writes DHCP record to the store according to the response DHCP packet (Offer, Ack).
909 *
910 * @param ethernet the DHCP packet
911 * @param dhcpPayload the DHCP payload
912 */
913 private void writeResponseDhcpRecord(Ethernet ethernet,
914 DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -0700915 Optional<Interface> outInterface = getClientInterface(ethernet, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -0700916 if (!outInterface.isPresent()) {
917 log.warn("Failed to determine where to send {}", dhcpPayload.getPacketType());
918 return;
919 }
920
921 Interface outIface = outInterface.get();
922 ConnectPoint location = outIface.connectPoint();
Yi Tseng4ec727d2017-08-31 11:21:00 -0700923 VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700924 if (vlanId == null) {
925 vlanId = outIface.vlan();
926 }
Yi Tseng51301292017-07-28 13:02:59 -0700927 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
928 HostId hostId = HostId.hostId(macAddress, vlanId);
929 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
930 if (record == null) {
931 record = new DhcpRecord(HostId.hostId(macAddress, vlanId));
932 } else {
933 record = record.clone();
934 }
935 record.addLocation(new HostLocation(location, System.currentTimeMillis()));
936 if (dhcpPayload.getPacketType() == DHCP.MsgType.DHCPACK) {
937 record.ip4Address(Ip4Address.valueOf(dhcpPayload.getYourIPAddress()));
938 }
939 record.ip4Status(dhcpPayload.getPacketType());
940 record.setDirectlyConnected(directlyConnected(dhcpPayload));
941 record.updateLastSeen();
942 dhcpRelayStore.updateDhcpRecord(HostId.hostId(macAddress, vlanId), record);
943 }
944
945 /**
946 * Build the DHCP offer/ack with proper client port.
947 *
948 * @param ethernetPacket the original packet comes from server
949 * @return new packet which will send to the client
950 */
951 private Ethernet processDhcpPacketFromServer(Ethernet ethernetPacket) {
952 // get dhcp header.
953 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
954 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
955 UDP udpPacket = (UDP) ipv4Packet.getPayload();
956 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
957
958 // determine the vlanId of the client host - note that this vlan id
959 // could be different from the vlan in the packet from the server
Yi Tsengdcef2c22017-08-05 20:34:06 -0700960 Interface clientInterface = getClientInterface(ethernetPacket, dhcpPayload).orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -0700961
Yi Tsengdcef2c22017-08-05 20:34:06 -0700962 if (clientInterface == null) {
Yi Tseng51301292017-07-28 13:02:59 -0700963 log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
964 return null;
965 }
Yi Tsengdcef2c22017-08-05 20:34:06 -0700966 VlanId vlanId;
967 if (clientInterface.vlanTagged().isEmpty()) {
968 vlanId = clientInterface.vlan();
969 } else {
970 // might be multiple vlan in same interface
Yi Tseng4ec727d2017-08-31 11:21:00 -0700971 vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -0700972 }
973 if (vlanId == null) {
974 vlanId = VlanId.NONE;
975 }
976 etherReply.setVlanID(vlanId.toShort());
977 etherReply.setSourceMACAddress(clientInterface.mac());
Yi Tseng51301292017-07-28 13:02:59 -0700978
Yi Tsengdcef2c22017-08-05 20:34:06 -0700979 if (!directlyConnected(dhcpPayload)) {
980 // if client is indirectly connected, try use next hop mac address
981 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
982 HostId hostId = HostId.hostId(macAddress, vlanId);
983 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
984 if (record != null) {
985 // if next hop can be found, use mac address of next hop
986 record.nextHop().ifPresent(etherReply::setDestinationMACAddress);
987 } else {
988 // otherwise, discard the packet
989 log.warn("Can't find record for host id {}, discard packet", hostId);
990 return null;
991 }
Yi Tsengc03fa242017-08-17 17:43:38 -0700992 } else {
993 etherReply.setDestinationMACAddress(dhcpPayload.getClientHardwareAddress());
Yi Tsengdcef2c22017-08-05 20:34:06 -0700994 }
995
Yi Tseng51301292017-07-28 13:02:59 -0700996 // we leave the srcMac from the original packet
Yi Tseng51301292017-07-28 13:02:59 -0700997 // figure out the relay agent IP corresponding to the original request
Yi Tseng4fa05832017-08-17 13:08:31 -0700998 Ip4Address ipFacingClient = getFirstIpFromInterface(clientInterface);
999 if (ipFacingClient == null) {
Yi Tseng51301292017-07-28 13:02:59 -07001000 log.warn("Cannot determine relay agent interface Ipv4 addr for host {}/{}. "
1001 + "Aborting relay for dhcp packet from server {}",
Yi Tsengdcef2c22017-08-05 20:34:06 -07001002 etherReply.getDestinationMAC(), clientInterface.vlan(),
Yi Tseng51301292017-07-28 13:02:59 -07001003 ethernetPacket);
1004 return null;
1005 }
1006 // SRC_IP: relay agent IP
1007 // DST_IP: offered IP
Yi Tseng4fa05832017-08-17 13:08:31 -07001008 ipv4Packet.setSourceAddress(ipFacingClient.toInt());
Yi Tseng51301292017-07-28 13:02:59 -07001009 ipv4Packet.setDestinationAddress(dhcpPayload.getYourIPAddress());
1010 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
1011 if (directlyConnected(dhcpPayload)) {
1012 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
1013 } else {
1014 // forward to another dhcp relay
Yi Tseng72b599a2017-09-14 13:24:21 -07001015 // FIXME: Currently we assume the DHCP comes from a L2 relay with
1016 // Option 82, this might not work if DHCP message comes from
1017 // L3 relay.
1018 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
Yi Tseng51301292017-07-28 13:02:59 -07001019 }
1020
1021 udpPacket.setPayload(dhcpPayload);
1022 ipv4Packet.setPayload(udpPacket);
1023 etherReply.setPayload(ipv4Packet);
1024 return etherReply;
1025 }
1026
Yi Tsengdcef2c22017-08-05 20:34:06 -07001027 /**
Charles Chan2f9aa2c2017-10-08 23:53:36 -04001028 * Build the DHCP offer/ack with proper client port.
1029 *
1030 * @param ethernetPacket the original packet comes from server
1031 * @return new packet which will send to the client
1032 */
1033 private Ethernet processLeaseQueryFromServer(Ethernet ethernetPacket) {
1034 // get dhcp header.
1035 Ethernet etherReply = (Ethernet) ethernetPacket.clone();
1036 IPv4 ipv4Packet = (IPv4) etherReply.getPayload();
1037 UDP udpPacket = (UDP) ipv4Packet.getPayload();
1038 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
1039
1040 // determine the vlanId of the client host - note that this vlan id
1041 // could be different from the vlan in the packet from the server
1042 Interface clientInterface = getClientInterface(ethernetPacket, dhcpPayload).orElse(null);
1043
1044 if (clientInterface == null) {
1045 log.warn("Cannot find the interface for the DHCP {}", dhcpPayload);
1046 return null;
1047 }
1048 VlanId vlanId;
1049 if (clientInterface.vlanTagged().isEmpty()) {
1050 vlanId = clientInterface.vlan();
1051 } else {
1052 // might be multiple vlan in same interface
1053 vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
1054 }
1055 if (vlanId == null) {
1056 vlanId = VlanId.NONE;
1057 }
1058 etherReply.setVlanID(vlanId.toShort());
1059 etherReply.setSourceMACAddress(clientInterface.mac());
1060
1061 if (!directlyConnected(dhcpPayload)) {
1062 // if client is indirectly connected, try use next hop mac address
1063 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
1064 HostId hostId = HostId.hostId(macAddress, vlanId);
1065 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1066 if (record != null) {
1067 // if next hop can be found, use mac address of next hop
1068 record.nextHop().ifPresent(etherReply::setDestinationMACAddress);
1069 } else {
1070 // otherwise, discard the packet
1071 log.warn("Can't find record for host id {}, discard packet", hostId);
1072 return null;
1073 }
1074 } else {
1075 etherReply.setDestinationMACAddress(dhcpPayload.getClientHardwareAddress());
1076 }
1077
1078 // default is client port
1079 udpPacket.setSourcePort(UDP.DHCP_SERVER_PORT);
1080 udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
1081
1082 udpPacket.setPayload(dhcpPayload);
1083 ipv4Packet.setPayload(udpPacket);
1084 etherReply.setPayload(ipv4Packet);
1085 return etherReply;
1086 }
1087 /**
Yi Tsengdcef2c22017-08-05 20:34:06 -07001088 * Extracts VLAN ID from relay agent option.
1089 *
1090 * @param dhcpPayload the DHCP payload
1091 * @return VLAN ID from DHCP payload; null if not exists
1092 */
Yi Tseng4ec727d2017-08-31 11:21:00 -07001093 private VlanId getVlanIdFromRelayAgentOption(DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001094 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
1095 if (option == null) {
1096 return null;
1097 }
1098 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
1099 if (circuitIdSubOption == null) {
1100 return null;
1101 }
1102 try {
1103 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
1104 return circuitId.vlanId();
1105 } catch (IllegalArgumentException e) {
1106 // can't deserialize the circuit ID
1107 return null;
1108 }
1109 }
1110
1111 /**
1112 * Removes DHCP relay agent information option (option 82) from DHCP payload.
1113 * Also reset giaddr to 0
1114 *
1115 * @param ethPacket the Ethernet packet to be processed
1116 * @return Ethernet packet processed
1117 */
1118 private Ethernet removeRelayAgentOption(Ethernet ethPacket) {
1119 Ethernet ethernet = (Ethernet) ethPacket.clone();
1120 IPv4 ipv4 = (IPv4) ethernet.getPayload();
1121 UDP udp = (UDP) ipv4.getPayload();
1122 DHCP dhcpPayload = (DHCP) udp.getPayload();
1123
1124 // removes relay agent information option
1125 List<DhcpOption> options = dhcpPayload.getOptions();
1126 options = options.stream()
1127 .filter(option -> option.getCode() != OptionCode_CircuitID.getValue())
1128 .collect(Collectors.toList());
1129 dhcpPayload.setOptions(options);
1130 dhcpPayload.setGatewayIPAddress(0);
1131
1132 udp.setPayload(dhcpPayload);
1133 ipv4.setPayload(udp);
1134 ethernet.setPayload(ipv4);
1135 return ethernet;
1136 }
1137
Yi Tseng51301292017-07-28 13:02:59 -07001138
1139 /**
1140 * Check if the host is directly connected to the network or not.
1141 *
1142 * @param dhcpPayload the dhcp payload
1143 * @return true if the host is directly connected to the network; false otherwise
1144 */
1145 private boolean directlyConnected(DHCP dhcpPayload) {
Yi Tseng2cf59912017-08-24 14:47:34 -07001146 DhcpRelayAgentOption relayAgentOption =
1147 (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
Yi Tseng51301292017-07-28 13:02:59 -07001148
1149 // Doesn't contains relay option
1150 if (relayAgentOption == null) {
1151 return true;
1152 }
1153
Yi Tseng2cf59912017-08-24 14:47:34 -07001154 // check circuit id, if circuit id is invalid, we say it is an indirect host
1155 DhcpOption circuitIdOpt = relayAgentOption.getSubOption(CIRCUIT_ID.getValue());
Yi Tseng51301292017-07-28 13:02:59 -07001156
Yi Tseng2cf59912017-08-24 14:47:34 -07001157 try {
1158 CircuitId.deserialize(circuitIdOpt.getData());
Yi Tseng51301292017-07-28 13:02:59 -07001159 return true;
Yi Tseng2cf59912017-08-24 14:47:34 -07001160 } catch (Exception e) {
1161 // invalid circuit id
1162 return false;
Yi Tseng51301292017-07-28 13:02:59 -07001163 }
Yi Tseng51301292017-07-28 13:02:59 -07001164 }
1165
1166
1167 /**
1168 * Send the DHCP ack to the requester host.
1169 * Modify Host or Route store according to the type of DHCP.
1170 *
1171 * @param ethernetPacketAck the packet
1172 * @param dhcpPayload the DHCP data
1173 */
1174 private void handleDhcpAck(Ethernet ethernetPacketAck, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001175 Optional<Interface> outInterface = getClientInterface(ethernetPacketAck, dhcpPayload);
Yi Tseng51301292017-07-28 13:02:59 -07001176 if (!outInterface.isPresent()) {
1177 log.warn("Can't find output interface for dhcp: {}", dhcpPayload);
1178 return;
1179 }
1180
1181 Interface outIface = outInterface.get();
1182 HostLocation hostLocation = new HostLocation(outIface.connectPoint(), System.currentTimeMillis());
1183 MacAddress macAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
Yi Tseng4ec727d2017-08-31 11:21:00 -07001184 VlanId vlanId = getVlanIdFromRelayAgentOption(dhcpPayload);
Yi Tsengdcef2c22017-08-05 20:34:06 -07001185 if (vlanId == null) {
1186 vlanId = outIface.vlan();
1187 }
Yi Tseng51301292017-07-28 13:02:59 -07001188 HostId hostId = HostId.hostId(macAddress, vlanId);
1189 Ip4Address ip = Ip4Address.valueOf(dhcpPayload.getYourIPAddress());
1190
1191 if (directlyConnected(dhcpPayload)) {
1192 // Add to host store if it connect to network directly
1193 Set<IpAddress> ips = Sets.newHashSet(ip);
Yi Tseng4b013202017-09-08 17:22:51 -07001194 Host host = hostService.getHost(hostId);
Yi Tseng51301292017-07-28 13:02:59 -07001195
Yi Tseng4b013202017-09-08 17:22:51 -07001196 Set<HostLocation> hostLocations = Sets.newHashSet(hostLocation);
1197 if (host != null) {
1198 // Dual homing support:
1199 // if host exists, use old locations and new location
1200 hostLocations.addAll(host.locations());
1201 }
1202 HostDescription desc = new DefaultHostDescription(macAddress, vlanId,
1203 hostLocations, ips, false);
1204 // Add IP address when dhcp server give the host new ip address
1205 providerService.hostDetected(hostId, desc, false);
Yi Tseng51301292017-07-28 13:02:59 -07001206 } else {
1207 // Add to route store if it does not connect to network directly
1208 // Get gateway host IP according to host mac address
Yi Tsengdcef2c22017-08-05 20:34:06 -07001209 // TODO: remove relay store here
Yi Tseng51301292017-07-28 13:02:59 -07001210 DhcpRecord record = dhcpRelayStore.getDhcpRecord(hostId).orElse(null);
1211
1212 if (record == null) {
1213 log.warn("Can't find DHCP record of host {}", hostId);
1214 return;
1215 }
1216
1217 MacAddress gwMac = record.nextHop().orElse(null);
1218 if (gwMac == null) {
1219 log.warn("Can't find gateway mac address from record {}", record);
1220 return;
1221 }
1222
1223 HostId gwHostId = HostId.hostId(gwMac, record.vlanId());
1224 Host gwHost = hostService.getHost(gwHostId);
1225
1226 if (gwHost == null) {
1227 log.warn("Can't find gateway host {}", gwHostId);
1228 return;
1229 }
1230
1231 Ip4Address nextHopIp = gwHost.ipAddresses()
1232 .stream()
1233 .filter(IpAddress::isIp4)
1234 .map(IpAddress::getIp4Address)
1235 .findFirst()
1236 .orElse(null);
1237
1238 if (nextHopIp == null) {
1239 log.warn("Can't find IP address of gateway {}", gwHost);
1240 return;
1241 }
1242
1243 Route route = new Route(Route.Source.STATIC, ip.toIpPrefix(), nextHopIp);
1244 routeStore.updateRoute(route);
1245 }
Yi Tseng51301292017-07-28 13:02:59 -07001246 }
1247
1248 /**
1249 * forward the packet to ConnectPoint where the DHCP server is attached.
1250 *
1251 * @param packet the packet
1252 */
Yi Tseng4ec727d2017-08-31 11:21:00 -07001253 private void handleDhcpDiscoverAndRequest(Ethernet packet, DHCP dhcpPayload) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001254 boolean direct = directlyConnected(dhcpPayload);
1255 DhcpServerInfo serverInfo = defaultServerInfoList.get(0);
1256 if (!direct && !indirectServerInfoList.isEmpty()) {
1257 serverInfo = indirectServerInfoList.get(0);
Yi Tseng4ec727d2017-08-31 11:21:00 -07001258 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001259 ConnectPoint portToFotward = serverInfo.getDhcpServerConnectPoint().orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -07001260 // send packet to dhcp server connect point.
Yi Tseng4ec727d2017-08-31 11:21:00 -07001261 if (portToFotward != null) {
Yi Tseng51301292017-07-28 13:02:59 -07001262 TrafficTreatment t = DefaultTrafficTreatment.builder()
Yi Tseng4ec727d2017-08-31 11:21:00 -07001263 .setOutput(portToFotward.port()).build();
Yi Tseng51301292017-07-28 13:02:59 -07001264 OutboundPacket o = new DefaultOutboundPacket(
Yi Tseng4ec727d2017-08-31 11:21:00 -07001265 portToFotward.deviceId(), t, ByteBuffer.wrap(packet.serialize()));
Yi Tseng51301292017-07-28 13:02:59 -07001266 if (log.isTraceEnabled()) {
1267 log.trace("Relaying packet to dhcp server {}", packet);
1268 }
1269 packetService.emit(o);
1270 } else {
1271 log.warn("Can't find DHCP server connect point, abort.");
1272 }
1273 }
1274
1275
1276 /**
1277 * Gets output interface of a dhcp packet.
1278 * If option 82 exists in the dhcp packet and the option was sent by
Yi Tseng4ec727d2017-08-31 11:21:00 -07001279 * ONOS (circuit format is correct), use the connect
Yi Tseng51301292017-07-28 13:02:59 -07001280 * point and vlan id from circuit id; otherwise, find host by destination
1281 * address and use vlan id from sender (dhcp server).
1282 *
1283 * @param ethPacket the ethernet packet
1284 * @param dhcpPayload the dhcp packet
1285 * @return an interface represent the output port and vlan; empty value
1286 * if the host or circuit id not found
1287 */
Yi Tsengdcef2c22017-08-05 20:34:06 -07001288 private Optional<Interface> getClientInterface(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tseng51301292017-07-28 13:02:59 -07001289 VlanId originalPacketVlanId = VlanId.vlanId(ethPacket.getVlanID());
Yi Tseng51301292017-07-28 13:02:59 -07001290 DhcpRelayAgentOption option = (DhcpRelayAgentOption) dhcpPayload.getOption(OptionCode_CircuitID);
1291
Yi Tseng4ec727d2017-08-31 11:21:00 -07001292 DhcpOption circuitIdSubOption = option.getSubOption(CIRCUIT_ID.getValue());
1293 try {
1294 CircuitId circuitId = CircuitId.deserialize(circuitIdSubOption.getData());
1295 ConnectPoint connectPoint = ConnectPoint.deviceConnectPoint(circuitId.connectPoint());
1296 VlanId vlanId = circuitId.vlanId();
1297 return interfaceService.getInterfacesByPort(connectPoint)
1298 .stream()
1299 .filter(iface -> interfaceContainsVlan(iface, vlanId))
1300 .findFirst();
1301 } catch (IllegalArgumentException ex) {
1302 // invalid circuit format, didn't sent by ONOS
1303 log.debug("Invalid circuit {}, use information from dhcp payload",
1304 circuitIdSubOption.getData());
Yi Tseng51301292017-07-28 13:02:59 -07001305 }
1306
1307 // Use Vlan Id from DHCP server if DHCP relay circuit id was not
1308 // sent by ONOS or circuit Id can't be parsed
Yi Tsengdcef2c22017-08-05 20:34:06 -07001309 // TODO: remove relay store from this method
Yi Tseng51301292017-07-28 13:02:59 -07001310 MacAddress dstMac = valueOf(dhcpPayload.getClientHardwareAddress());
1311 Optional<DhcpRecord> dhcpRecord = dhcpRelayStore.getDhcpRecord(HostId.hostId(dstMac, originalPacketVlanId));
Yi Tsengdcef2c22017-08-05 20:34:06 -07001312 ConnectPoint clientConnectPoint = dhcpRecord
Yi Tseng51301292017-07-28 13:02:59 -07001313 .map(DhcpRecord::locations)
1314 .orElse(Collections.emptySet())
1315 .stream()
1316 .reduce((hl1, hl2) -> {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001317 // find latest host connect point
Yi Tseng51301292017-07-28 13:02:59 -07001318 if (hl1 == null || hl2 == null) {
1319 return hl1 == null ? hl2 : hl1;
1320 }
1321 return hl1.time() > hl2.time() ? hl1 : hl2;
1322 })
Yi Tsengdcef2c22017-08-05 20:34:06 -07001323 .orElse(null);
Yi Tseng51301292017-07-28 13:02:59 -07001324
Yi Tsengdcef2c22017-08-05 20:34:06 -07001325 if (clientConnectPoint != null) {
1326 return interfaceService.getInterfacesByPort(clientConnectPoint)
1327 .stream()
Yi Tseng4ec727d2017-08-31 11:21:00 -07001328 .filter(iface -> interfaceContainsVlan(iface, originalPacketVlanId))
Yi Tsengdcef2c22017-08-05 20:34:06 -07001329 .findFirst();
1330 }
1331 return Optional.empty();
Yi Tseng51301292017-07-28 13:02:59 -07001332 }
1333
1334 /**
1335 * Send the response DHCP to the requester host.
1336 *
1337 * @param ethPacket the packet
1338 * @param dhcpPayload the DHCP data
1339 */
1340 private void sendResponseToClient(Ethernet ethPacket, DHCP dhcpPayload) {
Yi Tsengdcef2c22017-08-05 20:34:06 -07001341 Optional<Interface> outInterface = getClientInterface(ethPacket, dhcpPayload);
1342 if (directlyConnected(dhcpPayload)) {
1343 ethPacket = removeRelayAgentOption(ethPacket);
1344 }
1345 if (!outInterface.isPresent()) {
1346 log.warn("Can't find output interface for client, ignore");
1347 return;
1348 }
1349 Interface outIface = outInterface.get();
1350 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
1351 .setOutput(outIface.connectPoint().port())
1352 .build();
1353 OutboundPacket o = new DefaultOutboundPacket(
1354 outIface.connectPoint().deviceId(),
1355 treatment,
1356 ByteBuffer.wrap(ethPacket.serialize()));
1357 if (log.isTraceEnabled()) {
1358 log.trace("Relaying packet to DHCP client {} via {}, vlan {}",
1359 ethPacket,
1360 outIface.connectPoint(),
1361 outIface.vlan());
1362 }
1363 packetService.emit(o);
Yi Tseng51301292017-07-28 13:02:59 -07001364 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001365
Yi Tseng4b013202017-09-08 17:22:51 -07001366 @Override
1367 public void triggerProbe(Host host) {
1368 // Do nothing here
1369 }
1370
1371 @Override
1372 public ProviderId id() {
Charles Chan75edab72017-09-12 17:09:32 -07001373 return PROVIDER_ID;
Yi Tseng4b013202017-09-08 17:22:51 -07001374 }
1375
Yi Tsenge72fbb52017-08-02 15:03:31 -07001376 class InternalHostListener implements HostListener {
1377 @Override
1378 public void event(HostEvent event) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001379 if (!configured()) {
1380 return;
1381 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001382 switch (event.type()) {
1383 case HOST_ADDED:
1384 case HOST_UPDATED:
1385 hostUpdated(event.subject());
1386 break;
1387 case HOST_REMOVED:
1388 hostRemoved(event.subject());
1389 break;
Yi Tsenge72fbb52017-08-02 15:03:31 -07001390 default:
1391 break;
1392 }
1393 }
1394 }
1395
1396 /**
Yi Tsenge72fbb52017-08-02 15:03:31 -07001397 * Handle host updated.
1398 * If the host is DHCP server or gateway, update connect mac and vlan.
1399 *
1400 * @param host the host
1401 */
1402 private void hostUpdated(Host host) {
Yi Tseng7da339e2017-10-23 19:39:39 -07001403 hostUpdated(host, defaultServerInfoList);
1404 hostUpdated(host, indirectServerInfoList);
Yi Tsenge72fbb52017-08-02 15:03:31 -07001405 }
1406
Yi Tseng7da339e2017-10-23 19:39:39 -07001407 private void hostUpdated(Host host, List<DhcpServerInfo> srverInfoList) {
1408 DhcpServerInfo serverInfo;
1409 Ip4Address targetIp;
1410 if (!srverInfoList.isEmpty()) {
1411 serverInfo = srverInfoList.get(0);
1412 targetIp = serverInfo.getDhcpGatewayIp4().orElse(null);
1413 Ip4Address serverIp = serverInfo.getDhcpServerIp4().orElse(null);
1414
1415 if (targetIp == null) {
1416 targetIp = serverIp;
1417 }
1418
1419 if (targetIp != null) {
1420 if (host.ipAddresses().contains(targetIp)) {
1421 serverInfo.setDhcpConnectMac(host.mac());
1422 serverInfo.setDhcpConnectVlan(host.vlan());
1423 requestDhcpPacket(serverIp);
1424 }
1425 }
1426 }
1427 }
1428
1429
Yi Tsenge72fbb52017-08-02 15:03:31 -07001430 /**
1431 * Handle host removed.
1432 * If the host is DHCP server or gateway, unset connect mac and vlan.
1433 *
1434 * @param host the host
1435 */
1436 private void hostRemoved(Host host) {
Yi Tseng7da339e2017-10-23 19:39:39 -07001437 hostRemoved(host, defaultServerInfoList);
1438 hostRemoved(host, indirectServerInfoList);
1439 }
1440
1441 private void hostRemoved(Host host, List<DhcpServerInfo> serverInfoList) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001442 DhcpServerInfo serverInfo;
Yi Tseng7da339e2017-10-23 19:39:39 -07001443 Ip4Address targetIp;
1444 if (!serverInfoList.isEmpty()) {
1445 serverInfo = serverInfoList.get(0);
1446 Ip4Address serverIp = serverInfo.getDhcpServerIp4().orElse(null);
1447 targetIp = serverInfo.getDhcpGatewayIp4().orElse(null);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001448
Yi Tseng7da339e2017-10-23 19:39:39 -07001449 if (targetIp == null) {
1450 targetIp = serverIp;
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001451 }
Yi Tseng7da339e2017-10-23 19:39:39 -07001452
1453 if (targetIp != null) {
1454 if (host.ipAddresses().contains(targetIp)) {
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001455 serverInfo.setDhcpConnectVlan(null);
1456 serverInfo.setDhcpConnectMac(null);
Yi Tseng7da339e2017-10-23 19:39:39 -07001457 cancelDhcpPacket(serverIp);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001458 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001459 }
Yi Tsenge72fbb52017-08-02 15:03:31 -07001460 }
Yi Tseng7da339e2017-10-23 19:39:39 -07001461 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001462
Yi Tseng7da339e2017-10-23 19:39:39 -07001463 private void requestDhcpPacket(Ip4Address serverIp) {
1464 requestServerDhcpPacket(serverIp);
1465 requestClientDhcpPacket(serverIp);
1466 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001467
Yi Tseng7da339e2017-10-23 19:39:39 -07001468 private void cancelDhcpPacket(Ip4Address serverIp) {
1469 cancelServerDhcpPacket(serverIp);
1470 cancelClientDhcpPacket(serverIp);
1471 }
1472
1473 private void cancelServerDhcpPacket(Ip4Address serverIp) {
1474 TrafficSelector serverSelector =
1475 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1476 .matchIPSrc(serverIp.toIpPrefix())
1477 .build();
1478 packetService.cancelPackets(serverSelector,
1479 PacketPriority.CONTROL,
1480 appId);
1481 }
1482
1483 private void requestServerDhcpPacket(Ip4Address serverIp) {
1484 TrafficSelector serverSelector =
1485 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1486 .matchIPSrc(serverIp.toIpPrefix())
1487 .build();
1488 packetService.requestPackets(serverSelector,
1489 PacketPriority.CONTROL,
1490 appId);
1491 }
1492
1493 private void cancelClientDhcpPacket(Ip4Address serverIp) {
1494 // Packet comes from relay
1495 TrafficSelector indirectClientSelector =
1496 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1497 .matchIPDst(serverIp.toIpPrefix())
1498 .build();
1499 packetService.cancelPackets(indirectClientSelector,
1500 PacketPriority.CONTROL,
1501 appId);
1502
1503 // Packet comes from client
1504 packetService.cancelPackets(CLIENT_SERVER_SELECTOR,
1505 PacketPriority.CONTROL,
1506 appId);
1507 }
1508
1509 private void requestClientDhcpPacket(Ip4Address serverIp) {
1510 // Packet comes from relay
1511 TrafficSelector indirectClientSelector =
1512 DefaultTrafficSelector.builder(SERVER_RELAY_SELECTOR)
1513 .matchIPDst(serverIp.toIpPrefix())
1514 .build();
1515 packetService.requestPackets(indirectClientSelector,
1516 PacketPriority.CONTROL,
1517 appId);
1518
1519 // Packet comes from client
1520 packetService.requestPackets(CLIENT_SERVER_SELECTOR,
1521 PacketPriority.CONTROL,
1522 appId);
1523 }
1524
1525 /**
1526 * Process the ignore rules.
1527 *
1528 * @param deviceId the device id
1529 * @param vlanId the vlan to be ignored
1530 * @param op the operation, ADD to install; REMOVE to uninstall rules
1531 */
1532 private void processIgnoreVlanRule(DeviceId deviceId, VlanId vlanId, Objective.Operation op) {
1533 TrafficTreatment dropTreatment = DefaultTrafficTreatment.builder().wipeDeferred().build();
1534 AtomicInteger installedCount = new AtomicInteger(DHCP_SELECTORS.size());
1535 DHCP_SELECTORS.forEach(trafficSelector -> {
1536 TrafficSelector selector = DefaultTrafficSelector.builder(trafficSelector)
1537 .matchVlanId(vlanId)
1538 .build();
1539
1540 ForwardingObjective.Builder builder = DefaultForwardingObjective.builder()
1541 .withFlag(ForwardingObjective.Flag.VERSATILE)
1542 .withSelector(selector)
1543 .withPriority(IGNORE_CONTROL_PRIORITY)
1544 .withTreatment(dropTreatment)
1545 .fromApp(appId);
1546
1547
1548 ObjectiveContext objectiveContext = new ObjectiveContext() {
1549 @Override
1550 public void onSuccess(Objective objective) {
1551 log.info("Ignore rule {} (Vlan id {}, device {}, selector {})",
1552 op, vlanId, deviceId, selector);
1553 int countDown = installedCount.decrementAndGet();
1554 if (countDown != 0) {
1555 return;
1556 }
1557 switch (op) {
1558 case ADD:
1559 ignoredVlans.put(deviceId, vlanId);
1560 break;
1561 case REMOVE:
1562 ignoredVlans.remove(deviceId, vlanId);
1563 break;
1564 default:
1565 log.warn("Unsupported objective operation {}", op);
1566 break;
1567 }
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001568 }
Yi Tseng7da339e2017-10-23 19:39:39 -07001569
1570 @Override
1571 public void onError(Objective objective, ObjectiveError error) {
1572 log.warn("Can't {} ignore rule (vlan id {}, selector {}, device {}) due to {}",
1573 op, vlanId, selector, deviceId, error);
Yi Tseng2fe8f3f2017-09-07 16:22:51 -07001574 }
Yi Tseng7da339e2017-10-23 19:39:39 -07001575 };
1576
1577 ForwardingObjective fwd;
1578 switch (op) {
1579 case ADD:
1580 fwd = builder.add(objectiveContext);
1581 break;
1582 case REMOVE:
1583 fwd = builder.remove(objectiveContext);
1584 break;
1585 default:
1586 log.warn("Unsupported objective operation {}", op);
1587 return;
Yi Tseng4ec727d2017-08-31 11:21:00 -07001588 }
Yi Tseng7da339e2017-10-23 19:39:39 -07001589
1590 Device device = deviceService.getDevice(deviceId);
1591 if (device == null || !device.is(Pipeliner.class)) {
1592 log.warn("Device {} is not available now, wait until device is available", deviceId);
1593 return;
1594 }
1595 flowObjectiveService.apply(deviceId, fwd);
1596 });
Yi Tsenge72fbb52017-08-02 15:03:31 -07001597 }
Yi Tseng51301292017-07-28 13:02:59 -07001598}