blob: adc6bad5b5e1fc2ace310cf6859e968c1a65dacf [file] [log] [blame]
Hyunsun Moon44aac662017-02-18 02:07:01 +09001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2017-present Open Networking Foundation
Hyunsun Moon44aac662017-02-18 02:07:01 +09003 *
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 */
16package org.onosproject.openstacknetworking.impl;
17
18import com.google.common.base.Strings;
Daniel Parkd9d4c292018-06-26 20:33:58 +090019import com.google.common.collect.ImmutableList;
Hyunsun Moon44aac662017-02-18 02:07:01 +090020import com.google.common.collect.Lists;
Hyunsun Moon44aac662017-02-18 02:07:01 +090021import org.onlab.packet.DHCP;
Hyunsun Moon44aac662017-02-18 02:07:01 +090022import org.onlab.packet.Ethernet;
23import org.onlab.packet.IPv4;
24import org.onlab.packet.Ip4Address;
Daniel Park4d421002018-07-27 23:36:57 +090025import org.onlab.packet.IpAddress;
Hyunsun Moon44aac662017-02-18 02:07:01 +090026import org.onlab.packet.IpPrefix;
27import org.onlab.packet.MacAddress;
28import org.onlab.packet.TpPort;
29import org.onlab.packet.UDP;
daniel park796c2eb2018-03-22 17:01:51 +090030import org.onlab.packet.dhcp.DhcpOption;
Hyunsun Moon44aac662017-02-18 02:07:01 +090031import org.onlab.util.Tools;
32import org.onosproject.cfg.ComponentConfigService;
daniel park15506e82018-04-04 18:52:16 +090033import org.onosproject.cluster.ClusterService;
34import org.onosproject.cluster.LeadershipService;
35import org.onosproject.cluster.NodeId;
Hyunsun Moon44aac662017-02-18 02:07:01 +090036import org.onosproject.core.ApplicationId;
37import org.onosproject.core.CoreService;
38import org.onosproject.net.ConnectPoint;
39import org.onosproject.net.flow.DefaultTrafficSelector;
40import org.onosproject.net.flow.DefaultTrafficTreatment;
41import org.onosproject.net.flow.TrafficSelector;
42import org.onosproject.net.flow.TrafficTreatment;
43import org.onosproject.net.packet.DefaultOutboundPacket;
44import org.onosproject.net.packet.PacketContext;
Hyunsun Moon44aac662017-02-18 02:07:01 +090045import org.onosproject.net.packet.PacketProcessor;
46import org.onosproject.net.packet.PacketService;
47import org.onosproject.openstacknetworking.api.Constants;
daniel park796c2eb2018-03-22 17:01:51 +090048import org.onosproject.openstacknetworking.api.OpenstackFlowRuleService;
Hyunsun Moon44aac662017-02-18 02:07:01 +090049import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
daniel park15506e82018-04-04 18:52:16 +090050import org.onosproject.openstacknode.api.OpenstackNode;
51import org.onosproject.openstacknode.api.OpenstackNodeEvent;
52import org.onosproject.openstacknode.api.OpenstackNodeListener;
daniel park796c2eb2018-03-22 17:01:51 +090053import org.onosproject.openstacknode.api.OpenstackNodeService;
Daniel Parkd9d4c292018-06-26 20:33:58 +090054import org.openstack4j.model.network.HostRoute;
Hyunsun Moon44aac662017-02-18 02:07:01 +090055import org.openstack4j.model.network.IP;
Daniel Park468e7852018-07-28 00:38:45 +090056import org.openstack4j.model.network.Network;
Hyunsun Moon44aac662017-02-18 02:07:01 +090057import org.openstack4j.model.network.Port;
58import org.openstack4j.model.network.Subnet;
Jian Lic55a74f2020-05-11 13:05:47 +090059import org.openstack4j.openstack.networking.domain.NeutronPort;
Hyunsun Moon44aac662017-02-18 02:07:01 +090060import org.osgi.service.component.ComponentContext;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070061import org.osgi.service.component.annotations.Activate;
62import org.osgi.service.component.annotations.Component;
63import org.osgi.service.component.annotations.Deactivate;
64import org.osgi.service.component.annotations.Modified;
65import org.osgi.service.component.annotations.Reference;
66import org.osgi.service.component.annotations.ReferenceCardinality;
Hyunsun Moon44aac662017-02-18 02:07:01 +090067import org.slf4j.Logger;
68
69import java.nio.ByteBuffer;
70import java.util.Dictionary;
71import java.util.List;
daniel park15506e82018-04-04 18:52:16 +090072import java.util.Objects;
Jian Li32b03622018-11-06 17:54:24 +090073import java.util.concurrent.ExecutorService;
Hyunsun Moon44aac662017-02-18 02:07:01 +090074
Daniel Parkf8e422d2018-07-30 14:14:37 +090075import static com.google.common.base.Preconditions.checkNotNull;
Jian Li32b03622018-11-06 17:54:24 +090076import static java.util.concurrent.Executors.newSingleThreadExecutor;
daniel park796c2eb2018-03-22 17:01:51 +090077import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_BroadcastAddress;
Daniel Parkd9d4c292018-06-26 20:33:58 +090078import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_Classless_Static_Route;
daniel park796c2eb2018-03-22 17:01:51 +090079import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_DHCPServerIp;
80import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_DomainServer;
81import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_END;
82import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_LeaseTime;
83import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
Jian Lic55a74f2020-05-11 13:05:47 +090084import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_RequestedParameters;
Daniel Park48f10332018-08-02 10:57:27 +090085import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_RouterAddress;
daniel park796c2eb2018-03-22 17:01:51 +090086import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_SubnetMask;
Yi Tsengc7403c22017-06-19 16:23:22 -070087import static org.onlab.packet.DHCP.MsgType.DHCPACK;
88import static org.onlab.packet.DHCP.MsgType.DHCPOFFER;
Jian Li32b03622018-11-06 17:54:24 +090089import static org.onlab.util.Tools.groupedThreads;
Jian Lic55a74f2020-05-11 13:05:47 +090090import static org.onosproject.openstacknetworking.api.Constants.BAREMETAL;
Jian Li5c09e212018-10-24 18:23:58 +090091import static org.onosproject.openstacknetworking.api.Constants.DHCP_TABLE;
daniel park796c2eb2018-03-22 17:01:51 +090092import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_DHCP_RULE;
Ray Milkey8e406512018-10-24 15:56:50 -070093import static org.onosproject.openstacknetworking.impl.OsgiPropertyConstants.DHCP_SERVER_MAC;
94import static org.onosproject.openstacknetworking.impl.OsgiPropertyConstants.DHCP_SERVER_MAC_DEFAULT;
Jian Lif654dd12020-01-30 17:41:26 +090095import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.getBroadcastAddr;
Jian Lic55a74f2020-05-11 13:05:47 +090096import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.getDhcpFullBootFileName;
97import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.getDhcpServerName;
98import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.getDhcpStaticBootFileName;
Jian Lifb64d882018-11-27 10:57:40 +090099import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.COMPUTE;
Hyunsun Moon44aac662017-02-18 02:07:01 +0900100import static org.slf4j.LoggerFactory.getLogger;
101
102/**
103 * Handles DHCP requests for the virtual instances.
104 */
Ray Milkey8e406512018-10-24 15:56:50 -0700105@Component(
106 immediate = true,
107 property = {
108 DHCP_SERVER_MAC + "=" + DHCP_SERVER_MAC_DEFAULT
109 }
110)
Hyunsun Moon44aac662017-02-18 02:07:01 +0900111public class OpenstackSwitchingDhcpHandler {
112 protected final Logger log = getLogger(getClass());
113
Daniel Park4d421002018-07-27 23:36:57 +0900114 private static final Ip4Address DEFAULT_PRIMARY_DNS = Ip4Address.valueOf("8.8.8.8");
115 private static final Ip4Address DEFAULT_SECONDARY_DNS = Ip4Address.valueOf("8.8.4.4");
Hyunsun Moon44aac662017-02-18 02:07:01 +0900116 private static final byte PACKET_TTL = (byte) 127;
117 // TODO add MTU, static route option codes to ONOS DHCP and remove here
118 private static final byte DHCP_OPTION_MTU = (byte) 26;
119 private static final byte[] DHCP_DATA_LEASE_INFINITE =
120 ByteBuffer.allocate(4).putInt(-1).array();
Jian Li6a47fd02018-11-27 21:51:03 +0900121
Daniel Parkd9d4c292018-06-26 20:33:58 +0900122 private static final int OCTET_BIT_LENGTH = 8;
123 private static final int V4_BYTE_SIZE = 4;
Daniel Parkf8e422d2018-07-30 14:14:37 +0900124 private static final int V4_CIDR_LOWER_BOUND = -1;
Daniel Parkd9d4c292018-06-26 20:33:58 +0900125 private static final int V4_CIDR_UPPER_BOUND = 33;
126 private static final int PADDING_SIZE = 4;
Hyunsun Moon44aac662017-02-18 02:07:01 +0900127
Jian Li411bf2e2018-11-29 00:08:54 +0900128 private static final byte HARDWARE_ADDR_LENGTH = (byte) 6;
129 private static final byte DHCP_OPTION_DATA_LENGTH = (byte) 4;
130 private static final int DHCP_OPTION_DNS_LENGTH = 8;
131 private static final int DHCP_OPTION_MTU_LENGTH = 2;
132
Jian Lic55a74f2020-05-11 13:05:47 +0900133 private static final byte DHCP_OPTION_SERVER_NAME_CODE = (byte) 66;
134 private static final byte DHCP_OPTION_BOOT_FILE_NAME_CODE = (byte) 67;
135
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700136 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Hyunsun Moon44aac662017-02-18 02:07:01 +0900137 protected CoreService coreService;
138
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700139 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Hyunsun Moon44aac662017-02-18 02:07:01 +0900140 protected ComponentConfigService configService;
141
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700142 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Hyunsun Moon44aac662017-02-18 02:07:01 +0900143 protected PacketService packetService;
144
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700145 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Hyunsun Moon44aac662017-02-18 02:07:01 +0900146 protected OpenstackNetworkService osNetworkService;
147
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700148 @Reference(cardinality = ReferenceCardinality.MANDATORY)
daniel park796c2eb2018-03-22 17:01:51 +0900149 protected OpenstackNodeService osNodeService;
150
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700151 @Reference(cardinality = ReferenceCardinality.MANDATORY)
daniel park796c2eb2018-03-22 17:01:51 +0900152 protected OpenstackFlowRuleService osFlowRuleService;
153
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700154 @Reference(cardinality = ReferenceCardinality.MANDATORY)
daniel park15506e82018-04-04 18:52:16 +0900155 protected ClusterService clusterService;
156
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700157 @Reference(cardinality = ReferenceCardinality.MANDATORY)
daniel park15506e82018-04-04 18:52:16 +0900158 protected LeadershipService leadershipService;
159
Ray Milkey8e406512018-10-24 15:56:50 -0700160 /** Fake MAC address for virtual network subnet gateway. */
161 private String dhcpServerMac = DHCP_SERVER_MAC_DEFAULT;
Hyunsun Moon44aac662017-02-18 02:07:01 +0900162
163 private final PacketProcessor packetProcessor = new InternalPacketProcessor();
daniel park15506e82018-04-04 18:52:16 +0900164 private final OpenstackNodeListener osNodeListener = new InternalNodeEventListener();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900165
Jian Li32b03622018-11-06 17:54:24 +0900166 private final ExecutorService eventExecutor = newSingleThreadExecutor(
167 groupedThreads(this.getClass().getSimpleName(), "event-handler"));
168
Hyunsun Moon44aac662017-02-18 02:07:01 +0900169 private ApplicationId appId;
daniel park15506e82018-04-04 18:52:16 +0900170 private NodeId localNodeId;
Hyunsun Moon44aac662017-02-18 02:07:01 +0900171
172 @Activate
173 protected void activate() {
174 appId = coreService.registerApplication(Constants.OPENSTACK_NETWORKING_APP_ID);
daniel park15506e82018-04-04 18:52:16 +0900175 localNodeId = clusterService.getLocalNode().id();
176 osNodeService.addListener(osNodeListener);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900177 configService.registerProperties(getClass());
178 packetService.addProcessor(packetProcessor, PacketProcessor.director(0));
daniel park15506e82018-04-04 18:52:16 +0900179 leadershipService.runForLeadership(appId.name());
Hyunsun Moon44aac662017-02-18 02:07:01 +0900180
181 log.info("Started");
182 }
183
184 @Deactivate
185 protected void deactivate() {
Hyunsun Moon44aac662017-02-18 02:07:01 +0900186 packetService.removeProcessor(packetProcessor);
daniel park15506e82018-04-04 18:52:16 +0900187 osNodeService.removeListener(osNodeListener);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900188 configService.unregisterProperties(getClass(), false);
daniel park15506e82018-04-04 18:52:16 +0900189 leadershipService.withdraw(appId.name());
Jian Li32b03622018-11-06 17:54:24 +0900190 eventExecutor.shutdown();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900191
192 log.info("Stopped");
193 }
194
195 @Modified
196 protected void modified(ComponentContext context) {
197 Dictionary<?, ?> properties = context.getProperties();
198 String updatedMac;
199
200 updatedMac = Tools.get(properties, DHCP_SERVER_MAC);
Jian Lifb005492018-03-02 10:50:15 +0900201
Hyunsun Moon44aac662017-02-18 02:07:01 +0900202 if (!Strings.isNullOrEmpty(updatedMac) && !updatedMac.equals(dhcpServerMac)) {
203 dhcpServerMac = updatedMac;
204 }
205
206 log.info("Modified");
207 }
208
Hyunsun Moon44aac662017-02-18 02:07:01 +0900209 private class InternalPacketProcessor implements PacketProcessor {
210
211 @Override
212 public void process(PacketContext context) {
213 if (context.isHandled()) {
214 return;
215 }
216
217 Ethernet ethPacket = context.inPacket().parsed();
218 if (ethPacket == null || ethPacket.getEtherType() != Ethernet.TYPE_IPV4) {
219 return;
220 }
221 IPv4 ipv4Packet = (IPv4) ethPacket.getPayload();
222 if (ipv4Packet.getProtocol() != IPv4.PROTOCOL_UDP) {
223 return;
224 }
225 UDP udpPacket = (UDP) ipv4Packet.getPayload();
226 if (udpPacket.getDestinationPort() != UDP.DHCP_SERVER_PORT ||
227 udpPacket.getSourcePort() != UDP.DHCP_CLIENT_PORT) {
228 return;
229 }
230
231 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
Jian Li32b03622018-11-06 17:54:24 +0900232
233 eventExecutor.execute(() -> processDhcp(context, dhcpPacket));
Hyunsun Moon44aac662017-02-18 02:07:01 +0900234 }
235
236 private void processDhcp(PacketContext context, DHCP dhcpPacket) {
237 if (dhcpPacket == null) {
238 log.trace("DHCP packet without payload received, do nothing");
239 return;
240 }
241
Yi Tsengc7403c22017-06-19 16:23:22 -0700242 DHCP.MsgType inPacketType = getPacketType(dhcpPacket);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900243 if (inPacketType == null || dhcpPacket.getClientHardwareAddress() == null) {
244 log.trace("Malformed DHCP packet received, ignore it");
245 return;
246 }
247
248 MacAddress clientMac = MacAddress.valueOf(dhcpPacket.getClientHardwareAddress());
Jian Li9275f9c2020-05-07 12:01:46 +0900249
250 Port port = osNetworkService.ports().stream()
251 .filter(p -> MacAddress.valueOf(p.getMacAddress()).equals(clientMac))
252 .findAny().orElse(null);
253
254 if (port == null) {
255 log.warn("Failed to retrieve openstack port information for MAC {}", clientMac);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900256 return;
257 }
Jian Li9275f9c2020-05-07 12:01:46 +0900258
259 IP fixedIp = port.getFixedIps().stream().findFirst().orElse(null);
260
261 if (fixedIp == null) {
262 log.warn("There is no IP addresses are assigned with the port {}", port.getId());
263 return;
264 }
265
Hyunsun Moon44aac662017-02-18 02:07:01 +0900266 Ethernet ethPacket = context.inPacket().parsed();
267 switch (inPacketType) {
268 case DHCPDISCOVER:
Jian Li9275f9c2020-05-07 12:01:46 +0900269 processDhcpDiscover(context, clientMac, port, ethPacket);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900270 break;
271 case DHCPREQUEST:
Jian Li9275f9c2020-05-07 12:01:46 +0900272 processDhcpRequest(context, clientMac, port, ethPacket);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900273 break;
274 case DHCPRELEASE:
275 log.trace("DHCP RELEASE received from {}", clientMac);
276 // do nothing
277 break;
278 default:
279 break;
280 }
281 }
282
Jian Li6a47fd02018-11-27 21:51:03 +0900283 private void processDhcpDiscover(PacketContext context, MacAddress clientMac,
Jian Li9275f9c2020-05-07 12:01:46 +0900284 Port port, Ethernet ethPacket) {
Jian Li6a47fd02018-11-27 21:51:03 +0900285 log.trace("DHCP DISCOVER received from {}", clientMac);
286 Ethernet discoverReply = buildReply(ethPacket,
287 (byte) DHCPOFFER.getValue(),
Jian Li9275f9c2020-05-07 12:01:46 +0900288 port);
Jian Li6a47fd02018-11-27 21:51:03 +0900289 sendReply(context, discoverReply);
Jian Li9275f9c2020-05-07 12:01:46 +0900290 log.trace("DHCP OFFER({}) is sent for {}",
291 port.getFixedIps().stream().findFirst(), clientMac);
Jian Li6a47fd02018-11-27 21:51:03 +0900292 }
293
294 private void processDhcpRequest(PacketContext context, MacAddress clientMac,
Jian Li9275f9c2020-05-07 12:01:46 +0900295 Port port, Ethernet ethPacket) {
Jian Li6a47fd02018-11-27 21:51:03 +0900296 log.trace("DHCP REQUEST received from {}", clientMac);
297 Ethernet requestReply = buildReply(ethPacket,
298 (byte) DHCPACK.getValue(),
Jian Li9275f9c2020-05-07 12:01:46 +0900299 port);
Jian Li6a47fd02018-11-27 21:51:03 +0900300 sendReply(context, requestReply);
Jian Li9275f9c2020-05-07 12:01:46 +0900301 log.trace("DHCP ACK({}) is sent for {}",
302 port.getFixedIps().stream().findFirst(), clientMac);
Jian Li6a47fd02018-11-27 21:51:03 +0900303 }
304
Yi Tsengc7403c22017-06-19 16:23:22 -0700305 private DHCP.MsgType getPacketType(DHCP dhcpPacket) {
306 DhcpOption optType = dhcpPacket.getOption(OptionCode_MessageType);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900307 if (optType == null) {
308 log.trace("DHCP packet with no message type, ignore it");
309 return null;
310 }
311
Yi Tsengc7403c22017-06-19 16:23:22 -0700312 DHCP.MsgType inPacketType = DHCP.MsgType.getType(optType.getData()[0]);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900313 if (inPacketType == null) {
314 log.trace("DHCP packet with no packet type, ignore it");
315 }
316 return inPacketType;
317 }
318
319 private Ethernet buildReply(Ethernet ethRequest, byte packetType,
Jian Li9275f9c2020-05-07 12:01:46 +0900320 Port port) {
321 log.trace("Build for DHCP reply msg for openstack port {}", port.toString());
322
Hyunsun Moon44aac662017-02-18 02:07:01 +0900323 // pick one IP address to make a reply
Jian Li9275f9c2020-05-07 12:01:46 +0900324 // since we check the validity of fixed IP address at parent method,
325 // so no need to double check the fixed IP existence here
326 IP fixedIp = port.getFixedIps().stream().findFirst().get();
327
Hyunsun Moon44aac662017-02-18 02:07:01 +0900328 Subnet osSubnet = osNetworkService.subnet(fixedIp.getSubnetId());
329
330 Ethernet ethReply = new Ethernet();
331 ethReply.setSourceMACAddress(dhcpServerMac);
332 ethReply.setDestinationMACAddress(ethRequest.getSourceMAC());
333 ethReply.setEtherType(Ethernet.TYPE_IPV4);
334
335 IPv4 ipv4Request = (IPv4) ethRequest.getPayload();
336 IPv4 ipv4Reply = new IPv4();
daniel park15506e82018-04-04 18:52:16 +0900337
Jian Li5ecfd1a2018-12-10 11:41:03 +0900338 ipv4Reply.setSourceAddress(
339 clusterService.getLocalNode().ip().getIp4Address().toString());
Jian Li9275f9c2020-05-07 12:01:46 +0900340 ipv4Reply.setDestinationAddress(fixedIp.getIpAddress());
Hyunsun Moon44aac662017-02-18 02:07:01 +0900341 ipv4Reply.setTtl(PACKET_TTL);
342
343 UDP udpRequest = (UDP) ipv4Request.getPayload();
344 UDP udpReply = new UDP();
345 udpReply.setSourcePort((byte) UDP.DHCP_SERVER_PORT);
346 udpReply.setDestinationPort((byte) UDP.DHCP_CLIENT_PORT);
347
348 DHCP dhcpRequest = (DHCP) udpRequest.getPayload();
349 DHCP dhcpReply = buildDhcpReply(
350 dhcpRequest,
351 packetType,
Jian Li9275f9c2020-05-07 12:01:46 +0900352 Ip4Address.valueOf(fixedIp.getIpAddress()),
Jian Lic55a74f2020-05-11 13:05:47 +0900353 (NeutronPort) port, osSubnet);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900354
355 udpReply.setPayload(dhcpReply);
356 ipv4Reply.setPayload(udpReply);
357 ethReply.setPayload(ipv4Reply);
358
359 return ethReply;
360 }
361
362 private void sendReply(PacketContext context, Ethernet ethReply) {
363 if (ethReply == null) {
364 return;
365 }
366 ConnectPoint srcPoint = context.inPacket().receivedFrom();
367 TrafficTreatment treatment = DefaultTrafficTreatment
368 .builder()
369 .setOutput(srcPoint.port())
370 .build();
371
372 packetService.emit(new DefaultOutboundPacket(
373 srcPoint.deviceId(),
374 treatment,
375 ByteBuffer.wrap(ethReply.serialize())));
376 context.block();
377 }
378
379 private DHCP buildDhcpReply(DHCP request, byte msgType, Ip4Address yourIp,
Jian Lic55a74f2020-05-11 13:05:47 +0900380 NeutronPort port, Subnet osSubnet) {
daniel park15506e82018-04-04 18:52:16 +0900381 Ip4Address gatewayIp = clusterService.getLocalNode().ip().getIp4Address();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900382 int subnetPrefixLen = IpPrefix.valueOf(osSubnet.getCidr()).prefixLength();
383
384 DHCP dhcpReply = new DHCP();
385 dhcpReply.setOpCode(DHCP.OPCODE_REPLY);
386 dhcpReply.setHardwareType(DHCP.HWTYPE_ETHERNET);
Jian Li411bf2e2018-11-29 00:08:54 +0900387 dhcpReply.setHardwareAddressLength(HARDWARE_ADDR_LENGTH);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900388 dhcpReply.setTransactionId(request.getTransactionId());
389 dhcpReply.setFlags(request.getFlags());
390 dhcpReply.setYourIPAddress(yourIp.toInt());
391 dhcpReply.setServerIPAddress(gatewayIp.toInt());
392 dhcpReply.setClientHardwareAddress(request.getClientHardwareAddress());
393
Yi Tsengc7403c22017-06-19 16:23:22 -0700394 List<DhcpOption> options = Lists.newArrayList();
Jian Li6a47fd02018-11-27 21:51:03 +0900395
Hyunsun Moon44aac662017-02-18 02:07:01 +0900396 // message type
Jian Li6a47fd02018-11-27 21:51:03 +0900397 options.add(doMsgType(msgType));
398
399 // server identifier
400 options.add(doServerId(gatewayIp));
401
402 // lease time
403 options.add(doLeaseTime());
404
405 // subnet mask
406 options.add(doSubnetMask(subnetPrefixLen));
407
408 // broadcast address
Jian Lif654dd12020-01-30 17:41:26 +0900409 options.add(doBroadcastAddr(yourIp, subnetPrefixLen));
Jian Li6a47fd02018-11-27 21:51:03 +0900410
411 // domain server
412 options.add(doDomainServer(osSubnet));
413
414 // mtu
415 options.add(doMtu(osSubnet));
416
417 // classless static route
418 if (!osSubnet.getHostRoutes().isEmpty()) {
419 options.add(doClasslessSr(osSubnet));
420 }
421
422 // Sets the default router address up.
423 // Performs only if the gateway is set in subnet.
424 if (!Strings.isNullOrEmpty(osSubnet.getGateway())) {
425 options.add(doRouterAddr(osSubnet));
426 }
427
Jian Lic55a74f2020-05-11 13:05:47 +0900428 // sets TFTP and bootfilename for PXE boot
429 if (BAREMETAL.equalsIgnoreCase(port.getvNicType())) {
430 options.add(doTftp(port));
431 options.add(doBootfileName(request, port));
432 }
433
Jian Li6a47fd02018-11-27 21:51:03 +0900434 // end option
435 options.add(doEnd());
436
437 dhcpReply.setOptions(options);
438 return dhcpReply;
439 }
440
441
442 private DhcpOption doMsgType(byte msgType) {
Yi Tsengc7403c22017-06-19 16:23:22 -0700443 DhcpOption option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900444 option.setCode(OptionCode_MessageType.getValue());
445 option.setLength((byte) 1);
446 byte[] optionData = {msgType};
447 option.setData(optionData);
Jian Li6a47fd02018-11-27 21:51:03 +0900448 return option;
449 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900450
Jian Li6a47fd02018-11-27 21:51:03 +0900451 private DhcpOption doServerId(IpAddress gatewayIp) {
452 DhcpOption option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900453 option.setCode(OptionCode_DHCPServerIp.getValue());
Jian Li411bf2e2018-11-29 00:08:54 +0900454 option.setLength(DHCP_OPTION_DATA_LENGTH);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900455 option.setData(gatewayIp.toOctets());
Jian Li6a47fd02018-11-27 21:51:03 +0900456 return option;
457 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900458
Jian Li6a47fd02018-11-27 21:51:03 +0900459 private DhcpOption doLeaseTime() {
460 DhcpOption option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900461 option.setCode(OptionCode_LeaseTime.getValue());
Jian Li411bf2e2018-11-29 00:08:54 +0900462 option.setLength(DHCP_OPTION_DATA_LENGTH);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900463 option.setData(DHCP_DATA_LEASE_INFINITE);
Jian Li6a47fd02018-11-27 21:51:03 +0900464 return option;
465 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900466
Jian Li6a47fd02018-11-27 21:51:03 +0900467 private DhcpOption doSubnetMask(int subnetPrefixLen) {
Hyunsun Moon44aac662017-02-18 02:07:01 +0900468 Ip4Address subnetMask = Ip4Address.makeMaskPrefix(subnetPrefixLen);
Jian Li6a47fd02018-11-27 21:51:03 +0900469 DhcpOption option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900470 option.setCode(OptionCode_SubnetMask.getValue());
Jian Li411bf2e2018-11-29 00:08:54 +0900471 option.setLength(DHCP_OPTION_DATA_LENGTH);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900472 option.setData(subnetMask.toOctets());
Jian Li6a47fd02018-11-27 21:51:03 +0900473 return option;
474 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900475
Jian Li6a47fd02018-11-27 21:51:03 +0900476 private DhcpOption doBroadcastAddr(Ip4Address yourIp, int subnetPrefixLen) {
Jian Lif654dd12020-01-30 17:41:26 +0900477 String broadcast = getBroadcastAddr(yourIp.toString(), subnetPrefixLen);
478
Jian Li6a47fd02018-11-27 21:51:03 +0900479 DhcpOption option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900480 option.setCode(OptionCode_BroadcastAddress.getValue());
Jian Li411bf2e2018-11-29 00:08:54 +0900481 option.setLength(DHCP_OPTION_DATA_LENGTH);
Jian Lif654dd12020-01-30 17:41:26 +0900482 option.setData(IpAddress.valueOf(broadcast).toOctets());
483
Jian Li6a47fd02018-11-27 21:51:03 +0900484 return option;
485 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900486
Jian Li6a47fd02018-11-27 21:51:03 +0900487 private DhcpOption doDomainServer(Subnet osSubnet) {
488 DhcpOption option = new DhcpOption();
Daniel Park4d421002018-07-27 23:36:57 +0900489
490 List<String> dnsServers = osSubnet.getDnsNames();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900491 option.setCode(OptionCode_DomainServer.getValue());
Daniel Park4d421002018-07-27 23:36:57 +0900492
493 if (dnsServers.isEmpty()) {
Jian Li411bf2e2018-11-29 00:08:54 +0900494 option.setLength((byte) DHCP_OPTION_DNS_LENGTH);
495 ByteBuffer dnsByteBuf = ByteBuffer.allocate(DHCP_OPTION_DNS_LENGTH);
Daniel Park4d421002018-07-27 23:36:57 +0900496 dnsByteBuf.put(DEFAULT_PRIMARY_DNS.toOctets());
497 dnsByteBuf.put(DEFAULT_SECONDARY_DNS.toOctets());
498
499 option.setData(dnsByteBuf.array());
500 } else {
501 int dnsLength = 4 * dnsServers.size();
502
503 option.setLength((byte) dnsLength);
504
Jian Li411bf2e2018-11-29 00:08:54 +0900505 ByteBuffer dnsByteBuf = ByteBuffer.allocate(DHCP_OPTION_DNS_LENGTH);
Daniel Park4d421002018-07-27 23:36:57 +0900506
Jian Li6a47fd02018-11-27 21:51:03 +0900507 for (String dnsServer : dnsServers) {
508 dnsByteBuf.put(IpAddress.valueOf(dnsServer).toOctets());
Daniel Park4d421002018-07-27 23:36:57 +0900509 }
510 option.setData(dnsByteBuf.array());
511 }
512
Jian Li6a47fd02018-11-27 21:51:03 +0900513 return option;
514 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900515
Jian Li6a47fd02018-11-27 21:51:03 +0900516 private DhcpOption doMtu(Subnet osSubnet) {
517 DhcpOption option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900518 option.setCode(DHCP_OPTION_MTU);
Jian Li411bf2e2018-11-29 00:08:54 +0900519 option.setLength((byte) DHCP_OPTION_MTU_LENGTH);
Daniel Park468e7852018-07-28 00:38:45 +0900520 Network osNetwork = osNetworkService.network(osSubnet.getNetworkId());
521 checkNotNull(osNetwork);
522 checkNotNull(osNetwork.getMTU());
523
Jian Li411bf2e2018-11-29 00:08:54 +0900524 option.setData(ByteBuffer.allocate(DHCP_OPTION_MTU_LENGTH)
525 .putShort(osNetwork.getMTU().shortValue()).array());
Hyunsun Moon44aac662017-02-18 02:07:01 +0900526
Jian Li6a47fd02018-11-27 21:51:03 +0900527 return option;
528 }
Daniel Parkd9d4c292018-06-26 20:33:58 +0900529
Jian Lic55a74f2020-05-11 13:05:47 +0900530 private DhcpOption doTftp(NeutronPort port) {
531 String serverName = getDhcpServerName(port);
532 log.info("DHCP server name : {}", serverName);
533
534 DhcpOption option = new DhcpOption();
535 option.setCode(DHCP_OPTION_SERVER_NAME_CODE);
536 option.setLength((byte) serverName.length());
537 option.setData(serverName.getBytes());
538
539 return option;
540 }
541
542 private DhcpOption doBootfileName(DHCP request, NeutronPort port) {
543 String bootStaticFileName = getDhcpStaticBootFileName(port);
544 String bootFullFileName = getDhcpFullBootFileName(port);
545
546 DhcpOption option = new DhcpOption();
547 option.setCode(DHCP_OPTION_BOOT_FILE_NAME_CODE);
548
549 DhcpOption requestOption = request.getOption(OptionCode_RequestedParameters);
550 if (requestOption.getLength() > 30) {
551 log.info("DHCP static boot file name {}", bootStaticFileName);
552 option.setLength((byte) bootStaticFileName.length());
553 option.setData(bootStaticFileName.getBytes());
554 } else {
555 log.info("DHCP full boot file path {}", bootFullFileName);
556 option.setLength((byte) bootFullFileName.length());
557 option.setData(bootFullFileName.getBytes());
558 }
559
560 return option;
561 }
562
Jian Li6a47fd02018-11-27 21:51:03 +0900563 private DhcpOption doClasslessSr(Subnet osSubnet) {
564 DhcpOption option = new DhcpOption();
565 option.setCode(OptionCode_Classless_Static_Route.getValue());
Daniel Parkd9d4c292018-06-26 20:33:58 +0900566
Jian Li6a47fd02018-11-27 21:51:03 +0900567 int hostRoutesSize = hostRoutesSize(ImmutableList.copyOf(osSubnet.getHostRoutes()));
568 if (hostRoutesSize == 0) {
569 throw new IllegalArgumentException("Illegal CIDR hostRoutesSize value!");
Daniel Parkd9d4c292018-06-26 20:33:58 +0900570 }
571
Jian Li6a47fd02018-11-27 21:51:03 +0900572 log.trace("hostRouteSize: {}", hostRoutesSize);
Daniel Park48f10332018-08-02 10:57:27 +0900573
Jian Li6a47fd02018-11-27 21:51:03 +0900574 option.setLength((byte) hostRoutesSize);
575 ByteBuffer hostRouteByteBuf = ByteBuffer.allocate(hostRoutesSize);
576
577 osSubnet.getHostRoutes().forEach(h -> {
578 log.debug("processing host route information: {}", h.toString());
579
580 IpPrefix ipPrefix = IpPrefix.valueOf(h.getDestination());
581
582 hostRouteByteBuf.put(Objects.requireNonNull(bytesDestinationDescriptor(ipPrefix)));
583
584 hostRouteByteBuf.put(Ip4Address.valueOf(h.getNexthop()).toOctets());
585 });
586
587 option.setData(hostRouteByteBuf.array());
588 return option;
589 }
590
591 private DhcpOption doRouterAddr(Subnet osSubnet) {
592 DhcpOption option = new DhcpOption();
593 option.setCode(OptionCode_RouterAddress.getValue());
Jian Li411bf2e2018-11-29 00:08:54 +0900594 option.setLength(DHCP_OPTION_DATA_LENGTH);
Jian Li6a47fd02018-11-27 21:51:03 +0900595 option.setData(Ip4Address.valueOf(osSubnet.getGateway()).toOctets());
596 return option;
597 }
598
599 private DhcpOption doEnd() {
600 DhcpOption option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900601 option.setCode(OptionCode_END.getValue());
602 option.setLength((byte) 1);
Jian Li6a47fd02018-11-27 21:51:03 +0900603 return option;
Hyunsun Moon44aac662017-02-18 02:07:01 +0900604 }
Daniel Parkd9d4c292018-06-26 20:33:58 +0900605
606 private int hostRoutesSize(List<HostRoute> hostRoutes) {
607 int size = 0;
608 int preFixLen;
609
610 for (HostRoute h : hostRoutes) {
611 preFixLen = IpPrefix.valueOf(h.getDestination()).prefixLength();
612 if (Math.max(V4_CIDR_LOWER_BOUND, preFixLen) == V4_CIDR_LOWER_BOUND ||
613 Math.min(preFixLen, V4_CIDR_UPPER_BOUND) == V4_CIDR_UPPER_BOUND) {
614 throw new IllegalArgumentException("Illegal CIDR length value!");
615 }
616
Daniel Parkf8e422d2018-07-30 14:14:37 +0900617 for (int i = 0; i <= V4_BYTE_SIZE; i++) {
Daniel Parkd9d4c292018-06-26 20:33:58 +0900618 if (preFixLen == Math.min(preFixLen, i * OCTET_BIT_LENGTH)) {
619 size = size + i + 1 + PADDING_SIZE;
620 break;
621 }
622 }
623 }
624 return size;
625 }
626
627 private byte[] bytesDestinationDescriptor(IpPrefix ipPrefix) {
628 ByteBuffer byteBuffer;
629 int prefixLen = ipPrefix.prefixLength();
630
631 // retrieve ipPrefix to the destination descriptor format
632 // ex) 10.1.1.0/24 -> [10,1,1,0]
633 String[] ipPrefixString = ipPrefix.getIp4Prefix().toString()
634 .split("/")[0]
635 .split("\\.");
636
Jian Li5ecfd1a2018-12-10 11:41:03 +0900637 // retrieve destination descriptor and put this to byte buffer
638 // according to RFC 3442
Daniel Parkf8e422d2018-07-30 14:14:37 +0900639 // ex) 0.0.0.0/0 -> 0
Daniel Parkd9d4c292018-06-26 20:33:58 +0900640 // ex) 10.0.0.0/8 -> 8.10
641 // ex) 10.17.0.0/16 -> 16.10.17
642 // ex) 10.27.129.0/24 -> 24.10.27.129
643 // ex) 10.229.0.128/25 -> 25.10.229.0.128
Daniel Parkf8e422d2018-07-30 14:14:37 +0900644 for (int i = 0; i <= V4_BYTE_SIZE; i++) {
Daniel Parkd9d4c292018-06-26 20:33:58 +0900645 if (prefixLen == Math.min(prefixLen, i * OCTET_BIT_LENGTH)) {
646 byteBuffer = ByteBuffer.allocate(i + 1);
647 byteBuffer.put((byte) prefixLen);
648
649 for (int j = 0; j < i; j++) {
650 byteBuffer.put((byte) Integer.parseInt(ipPrefixString[j]));
651 }
652 return byteBuffer.array();
653 }
654 }
655
656 return null;
657 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900658 }
daniel park15506e82018-04-04 18:52:16 +0900659
660 private class InternalNodeEventListener implements OpenstackNodeListener {
Jian Lifb64d882018-11-27 10:57:40 +0900661 @Override
662 public boolean isRelevant(OpenstackNodeEvent event) {
663 return event.subject().type() == COMPUTE;
664 }
665
Jian Li34220ea2018-11-14 01:30:24 +0900666 private boolean isRelevantHelper() {
667 return Objects.equals(localNodeId, leadershipService.getLeader(appId.name()));
daniel park15506e82018-04-04 18:52:16 +0900668 }
669
670 @Override
671 public void event(OpenstackNodeEvent event) {
672 OpenstackNode osNode = event.subject();
673 switch (event.type()) {
674 case OPENSTACK_NODE_COMPLETE:
Jian Li6a47fd02018-11-27 21:51:03 +0900675 eventExecutor.execute(() -> processNodeCompletion(osNode));
daniel park15506e82018-04-04 18:52:16 +0900676 break;
677 case OPENSTACK_NODE_INCOMPLETE:
daniel park15506e82018-04-04 18:52:16 +0900678 case OPENSTACK_NODE_CREATED:
679 case OPENSTACK_NODE_UPDATED:
680 case OPENSTACK_NODE_REMOVED:
681 default:
682 break;
683 }
684 }
685
Jian Li6a47fd02018-11-27 21:51:03 +0900686 private void processNodeCompletion(OpenstackNode osNode) {
687 if (!isRelevantHelper()) {
688 return;
689 }
690 setDhcpRule(osNode, true);
691 }
692
693 private void processNodeIncompletion(OpenstackNode osNode) {
694 if (!isRelevantHelper()) {
695 return;
696 }
697 setDhcpRule(osNode, false);
698 }
699
daniel park15506e82018-04-04 18:52:16 +0900700 private void setDhcpRule(OpenstackNode openstackNode, boolean install) {
daniel park15506e82018-04-04 18:52:16 +0900701 TrafficSelector selector = DefaultTrafficSelector.builder()
702 .matchEthType(Ethernet.TYPE_IPV4)
703 .matchIPProtocol(IPv4.PROTOCOL_UDP)
704 .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
705 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
706 .build();
707
708 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
709 .punt()
710 .build();
711
712 osFlowRuleService.setRule(
713 appId,
714 openstackNode.intgBridge(),
715 selector,
716 treatment,
717 PRIORITY_DHCP_RULE,
Jian Li5c09e212018-10-24 18:23:58 +0900718 DHCP_TABLE,
daniel park15506e82018-04-04 18:52:16 +0900719 install);
720 }
721 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900722}