blob: 922d1b5f1f92607d39b291d1a44de8c099db69bd [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;
48import org.onosproject.openstacknetworking.api.InstancePort;
49import org.onosproject.openstacknetworking.api.InstancePortService;
daniel park796c2eb2018-03-22 17:01:51 +090050import org.onosproject.openstacknetworking.api.OpenstackFlowRuleService;
Hyunsun Moon44aac662017-02-18 02:07:01 +090051import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
daniel park15506e82018-04-04 18:52:16 +090052import org.onosproject.openstacknode.api.OpenstackNode;
53import org.onosproject.openstacknode.api.OpenstackNodeEvent;
54import org.onosproject.openstacknode.api.OpenstackNodeListener;
daniel park796c2eb2018-03-22 17:01:51 +090055import org.onosproject.openstacknode.api.OpenstackNodeService;
Daniel Parkd9d4c292018-06-26 20:33:58 +090056import org.openstack4j.model.network.HostRoute;
Hyunsun Moon44aac662017-02-18 02:07:01 +090057import org.openstack4j.model.network.IP;
Daniel Park468e7852018-07-28 00:38:45 +090058import org.openstack4j.model.network.Network;
Hyunsun Moon44aac662017-02-18 02:07:01 +090059import org.openstack4j.model.network.Port;
60import org.openstack4j.model.network.Subnet;
61import org.osgi.service.component.ComponentContext;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070062import org.osgi.service.component.annotations.Activate;
63import org.osgi.service.component.annotations.Component;
64import org.osgi.service.component.annotations.Deactivate;
65import org.osgi.service.component.annotations.Modified;
66import org.osgi.service.component.annotations.Reference;
67import org.osgi.service.component.annotations.ReferenceCardinality;
Hyunsun Moon44aac662017-02-18 02:07:01 +090068import org.slf4j.Logger;
69
70import java.nio.ByteBuffer;
71import java.util.Dictionary;
72import java.util.List;
daniel park15506e82018-04-04 18:52:16 +090073import java.util.Objects;
Jian Li32b03622018-11-06 17:54:24 +090074import java.util.concurrent.ExecutorService;
Hyunsun Moon44aac662017-02-18 02:07:01 +090075
Daniel Parkf8e422d2018-07-30 14:14:37 +090076import static com.google.common.base.Preconditions.checkNotNull;
Jian Li32b03622018-11-06 17:54:24 +090077import static java.util.concurrent.Executors.newSingleThreadExecutor;
daniel park796c2eb2018-03-22 17:01:51 +090078import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_BroadcastAddress;
Daniel Parkd9d4c292018-06-26 20:33:58 +090079import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_Classless_Static_Route;
daniel park796c2eb2018-03-22 17:01:51 +090080import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_DHCPServerIp;
81import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_DomainServer;
82import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_END;
83import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_LeaseTime;
84import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
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 Li5c09e212018-10-24 18:23:58 +090090import static org.onosproject.openstacknetworking.api.Constants.DHCP_TABLE;
daniel park796c2eb2018-03-22 17:01:51 +090091import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_DHCP_RULE;
Ray Milkey8e406512018-10-24 15:56:50 -070092import static org.onosproject.openstacknetworking.impl.OsgiPropertyConstants.DHCP_SERVER_MAC;
93import static org.onosproject.openstacknetworking.impl.OsgiPropertyConstants.DHCP_SERVER_MAC_DEFAULT;
Jian Lif654dd12020-01-30 17:41:26 +090094import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.getBroadcastAddr;
Jian Lifb64d882018-11-27 10:57:40 +090095import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.COMPUTE;
Hyunsun Moon44aac662017-02-18 02:07:01 +090096import static org.slf4j.LoggerFactory.getLogger;
97
98/**
99 * Handles DHCP requests for the virtual instances.
100 */
Ray Milkey8e406512018-10-24 15:56:50 -0700101@Component(
102 immediate = true,
103 property = {
104 DHCP_SERVER_MAC + "=" + DHCP_SERVER_MAC_DEFAULT
105 }
106)
Hyunsun Moon44aac662017-02-18 02:07:01 +0900107public class OpenstackSwitchingDhcpHandler {
108 protected final Logger log = getLogger(getClass());
109
Daniel Park4d421002018-07-27 23:36:57 +0900110 private static final Ip4Address DEFAULT_PRIMARY_DNS = Ip4Address.valueOf("8.8.8.8");
111 private static final Ip4Address DEFAULT_SECONDARY_DNS = Ip4Address.valueOf("8.8.4.4");
Hyunsun Moon44aac662017-02-18 02:07:01 +0900112 private static final byte PACKET_TTL = (byte) 127;
113 // TODO add MTU, static route option codes to ONOS DHCP and remove here
114 private static final byte DHCP_OPTION_MTU = (byte) 26;
115 private static final byte[] DHCP_DATA_LEASE_INFINITE =
116 ByteBuffer.allocate(4).putInt(-1).array();
Jian Li6a47fd02018-11-27 21:51:03 +0900117
Daniel Parkd9d4c292018-06-26 20:33:58 +0900118 private static final int OCTET_BIT_LENGTH = 8;
119 private static final int V4_BYTE_SIZE = 4;
Daniel Parkf8e422d2018-07-30 14:14:37 +0900120 private static final int V4_CIDR_LOWER_BOUND = -1;
Daniel Parkd9d4c292018-06-26 20:33:58 +0900121 private static final int V4_CIDR_UPPER_BOUND = 33;
122 private static final int PADDING_SIZE = 4;
Hyunsun Moon44aac662017-02-18 02:07:01 +0900123
Jian Li411bf2e2018-11-29 00:08:54 +0900124 private static final byte HARDWARE_ADDR_LENGTH = (byte) 6;
125 private static final byte DHCP_OPTION_DATA_LENGTH = (byte) 4;
126 private static final int DHCP_OPTION_DNS_LENGTH = 8;
127 private static final int DHCP_OPTION_MTU_LENGTH = 2;
128
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700129 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Hyunsun Moon44aac662017-02-18 02:07:01 +0900130 protected CoreService coreService;
131
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700132 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Hyunsun Moon44aac662017-02-18 02:07:01 +0900133 protected ComponentConfigService configService;
134
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700135 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Hyunsun Moon44aac662017-02-18 02:07:01 +0900136 protected PacketService packetService;
137
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700138 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Hyunsun Moon44aac662017-02-18 02:07:01 +0900139 protected InstancePortService instancePortService;
140
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700141 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Hyunsun Moon44aac662017-02-18 02:07:01 +0900142 protected OpenstackNetworkService osNetworkService;
143
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700144 @Reference(cardinality = ReferenceCardinality.MANDATORY)
daniel park796c2eb2018-03-22 17:01:51 +0900145 protected OpenstackNodeService osNodeService;
146
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700147 @Reference(cardinality = ReferenceCardinality.MANDATORY)
daniel park796c2eb2018-03-22 17:01:51 +0900148 protected OpenstackFlowRuleService osFlowRuleService;
149
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700150 @Reference(cardinality = ReferenceCardinality.MANDATORY)
daniel park15506e82018-04-04 18:52:16 +0900151 protected ClusterService clusterService;
152
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700153 @Reference(cardinality = ReferenceCardinality.MANDATORY)
daniel park15506e82018-04-04 18:52:16 +0900154 protected LeadershipService leadershipService;
155
Ray Milkey8e406512018-10-24 15:56:50 -0700156 /** Fake MAC address for virtual network subnet gateway. */
157 private String dhcpServerMac = DHCP_SERVER_MAC_DEFAULT;
Hyunsun Moon44aac662017-02-18 02:07:01 +0900158
159 private final PacketProcessor packetProcessor = new InternalPacketProcessor();
daniel park15506e82018-04-04 18:52:16 +0900160 private final OpenstackNodeListener osNodeListener = new InternalNodeEventListener();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900161
Jian Li32b03622018-11-06 17:54:24 +0900162 private final ExecutorService eventExecutor = newSingleThreadExecutor(
163 groupedThreads(this.getClass().getSimpleName(), "event-handler"));
164
Hyunsun Moon44aac662017-02-18 02:07:01 +0900165 private ApplicationId appId;
daniel park15506e82018-04-04 18:52:16 +0900166 private NodeId localNodeId;
Hyunsun Moon44aac662017-02-18 02:07:01 +0900167
168 @Activate
169 protected void activate() {
170 appId = coreService.registerApplication(Constants.OPENSTACK_NETWORKING_APP_ID);
daniel park15506e82018-04-04 18:52:16 +0900171 localNodeId = clusterService.getLocalNode().id();
172 osNodeService.addListener(osNodeListener);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900173 configService.registerProperties(getClass());
174 packetService.addProcessor(packetProcessor, PacketProcessor.director(0));
daniel park15506e82018-04-04 18:52:16 +0900175 leadershipService.runForLeadership(appId.name());
Hyunsun Moon44aac662017-02-18 02:07:01 +0900176
177 log.info("Started");
178 }
179
180 @Deactivate
181 protected void deactivate() {
Hyunsun Moon44aac662017-02-18 02:07:01 +0900182 packetService.removeProcessor(packetProcessor);
daniel park15506e82018-04-04 18:52:16 +0900183 osNodeService.removeListener(osNodeListener);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900184 configService.unregisterProperties(getClass(), false);
daniel park15506e82018-04-04 18:52:16 +0900185 leadershipService.withdraw(appId.name());
Jian Li32b03622018-11-06 17:54:24 +0900186 eventExecutor.shutdown();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900187
188 log.info("Stopped");
189 }
190
191 @Modified
192 protected void modified(ComponentContext context) {
193 Dictionary<?, ?> properties = context.getProperties();
194 String updatedMac;
195
196 updatedMac = Tools.get(properties, DHCP_SERVER_MAC);
Jian Lifb005492018-03-02 10:50:15 +0900197
Hyunsun Moon44aac662017-02-18 02:07:01 +0900198 if (!Strings.isNullOrEmpty(updatedMac) && !updatedMac.equals(dhcpServerMac)) {
199 dhcpServerMac = updatedMac;
200 }
201
202 log.info("Modified");
203 }
204
Hyunsun Moon44aac662017-02-18 02:07:01 +0900205 private class InternalPacketProcessor implements PacketProcessor {
206
207 @Override
208 public void process(PacketContext context) {
209 if (context.isHandled()) {
210 return;
211 }
212
213 Ethernet ethPacket = context.inPacket().parsed();
214 if (ethPacket == null || ethPacket.getEtherType() != Ethernet.TYPE_IPV4) {
215 return;
216 }
217 IPv4 ipv4Packet = (IPv4) ethPacket.getPayload();
218 if (ipv4Packet.getProtocol() != IPv4.PROTOCOL_UDP) {
219 return;
220 }
221 UDP udpPacket = (UDP) ipv4Packet.getPayload();
222 if (udpPacket.getDestinationPort() != UDP.DHCP_SERVER_PORT ||
223 udpPacket.getSourcePort() != UDP.DHCP_CLIENT_PORT) {
224 return;
225 }
226
227 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
Jian Li32b03622018-11-06 17:54:24 +0900228
229 eventExecutor.execute(() -> processDhcp(context, dhcpPacket));
Hyunsun Moon44aac662017-02-18 02:07:01 +0900230 }
231
232 private void processDhcp(PacketContext context, DHCP dhcpPacket) {
233 if (dhcpPacket == null) {
234 log.trace("DHCP packet without payload received, do nothing");
235 return;
236 }
237
Yi Tsengc7403c22017-06-19 16:23:22 -0700238 DHCP.MsgType inPacketType = getPacketType(dhcpPacket);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900239 if (inPacketType == null || dhcpPacket.getClientHardwareAddress() == null) {
240 log.trace("Malformed DHCP packet received, ignore it");
241 return;
242 }
243
244 MacAddress clientMac = MacAddress.valueOf(dhcpPacket.getClientHardwareAddress());
245 InstancePort reqInstPort = instancePortService.instancePort(clientMac);
246 if (reqInstPort == null) {
247 log.trace("Failed to find host(MAC:{})", clientMac);
248 return;
249 }
250 Ethernet ethPacket = context.inPacket().parsed();
251 switch (inPacketType) {
252 case DHCPDISCOVER:
Jian Li6a47fd02018-11-27 21:51:03 +0900253 processDhcpDiscover(context, clientMac, reqInstPort, ethPacket);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900254 break;
255 case DHCPREQUEST:
Jian Li6a47fd02018-11-27 21:51:03 +0900256 processDhcpRequest(context, clientMac, reqInstPort, ethPacket);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900257 break;
258 case DHCPRELEASE:
259 log.trace("DHCP RELEASE received from {}", clientMac);
260 // do nothing
261 break;
262 default:
263 break;
264 }
265 }
266
Jian Li6a47fd02018-11-27 21:51:03 +0900267 private void processDhcpDiscover(PacketContext context, MacAddress clientMac,
268 InstancePort instPort, Ethernet ethPacket) {
269 log.trace("DHCP DISCOVER received from {}", clientMac);
270 Ethernet discoverReply = buildReply(ethPacket,
271 (byte) DHCPOFFER.getValue(),
272 instPort);
273 sendReply(context, discoverReply);
274 log.trace("DHCP OFFER({}) is sent for {}", instPort.ipAddress(), clientMac);
275 }
276
277 private void processDhcpRequest(PacketContext context, MacAddress clientMac,
278 InstancePort instPort, Ethernet ethPacket) {
279 log.trace("DHCP REQUEST received from {}", clientMac);
280 Ethernet requestReply = buildReply(ethPacket,
281 (byte) DHCPACK.getValue(),
282 instPort);
283 sendReply(context, requestReply);
284 log.trace("DHCP ACK({}) is sent for {}", instPort.ipAddress(), clientMac);
285 }
286
Yi Tsengc7403c22017-06-19 16:23:22 -0700287 private DHCP.MsgType getPacketType(DHCP dhcpPacket) {
288 DhcpOption optType = dhcpPacket.getOption(OptionCode_MessageType);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900289 if (optType == null) {
290 log.trace("DHCP packet with no message type, ignore it");
291 return null;
292 }
293
Yi Tsengc7403c22017-06-19 16:23:22 -0700294 DHCP.MsgType inPacketType = DHCP.MsgType.getType(optType.getData()[0]);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900295 if (inPacketType == null) {
296 log.trace("DHCP packet with no packet type, ignore it");
297 }
298 return inPacketType;
299 }
300
301 private Ethernet buildReply(Ethernet ethRequest, byte packetType,
302 InstancePort reqInstPort) {
Daniel Parke0945c12018-08-28 17:22:25 +0900303 log.trace("Build for DHCP reply msg for instance port {}", reqInstPort.toString());
Hyunsun Moon44aac662017-02-18 02:07:01 +0900304 Port osPort = osNetworkService.port(reqInstPort.portId());
Daniel Parke0945c12018-08-28 17:22:25 +0900305 if (osPort == null) {
306 log.error("Failed to retrieve openstack port information for instance port {}",
307 reqInstPort.toString());
Ray Milkey92815932018-08-28 10:32:17 -0700308 return null;
Daniel Parke0945c12018-08-28 17:22:25 +0900309 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900310 // pick one IP address to make a reply
311 IP fixedIp = osPort.getFixedIps().stream().findFirst().get();
312 Subnet osSubnet = osNetworkService.subnet(fixedIp.getSubnetId());
313
314 Ethernet ethReply = new Ethernet();
315 ethReply.setSourceMACAddress(dhcpServerMac);
316 ethReply.setDestinationMACAddress(ethRequest.getSourceMAC());
317 ethReply.setEtherType(Ethernet.TYPE_IPV4);
318
319 IPv4 ipv4Request = (IPv4) ethRequest.getPayload();
320 IPv4 ipv4Reply = new IPv4();
daniel park15506e82018-04-04 18:52:16 +0900321
Jian Li5ecfd1a2018-12-10 11:41:03 +0900322 ipv4Reply.setSourceAddress(
323 clusterService.getLocalNode().ip().getIp4Address().toString());
Hyunsun Moon44aac662017-02-18 02:07:01 +0900324 ipv4Reply.setDestinationAddress(reqInstPort.ipAddress().getIp4Address().toInt());
325 ipv4Reply.setTtl(PACKET_TTL);
326
327 UDP udpRequest = (UDP) ipv4Request.getPayload();
328 UDP udpReply = new UDP();
329 udpReply.setSourcePort((byte) UDP.DHCP_SERVER_PORT);
330 udpReply.setDestinationPort((byte) UDP.DHCP_CLIENT_PORT);
331
332 DHCP dhcpRequest = (DHCP) udpRequest.getPayload();
333 DHCP dhcpReply = buildDhcpReply(
334 dhcpRequest,
335 packetType,
336 reqInstPort.ipAddress().getIp4Address(),
337 osSubnet);
338
339 udpReply.setPayload(dhcpReply);
340 ipv4Reply.setPayload(udpReply);
341 ethReply.setPayload(ipv4Reply);
342
343 return ethReply;
344 }
345
346 private void sendReply(PacketContext context, Ethernet ethReply) {
347 if (ethReply == null) {
348 return;
349 }
350 ConnectPoint srcPoint = context.inPacket().receivedFrom();
351 TrafficTreatment treatment = DefaultTrafficTreatment
352 .builder()
353 .setOutput(srcPoint.port())
354 .build();
355
356 packetService.emit(new DefaultOutboundPacket(
357 srcPoint.deviceId(),
358 treatment,
359 ByteBuffer.wrap(ethReply.serialize())));
360 context.block();
361 }
362
363 private DHCP buildDhcpReply(DHCP request, byte msgType, Ip4Address yourIp,
364 Subnet osSubnet) {
daniel park15506e82018-04-04 18:52:16 +0900365 Ip4Address gatewayIp = clusterService.getLocalNode().ip().getIp4Address();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900366 int subnetPrefixLen = IpPrefix.valueOf(osSubnet.getCidr()).prefixLength();
367
368 DHCP dhcpReply = new DHCP();
369 dhcpReply.setOpCode(DHCP.OPCODE_REPLY);
370 dhcpReply.setHardwareType(DHCP.HWTYPE_ETHERNET);
Jian Li411bf2e2018-11-29 00:08:54 +0900371 dhcpReply.setHardwareAddressLength(HARDWARE_ADDR_LENGTH);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900372 dhcpReply.setTransactionId(request.getTransactionId());
373 dhcpReply.setFlags(request.getFlags());
374 dhcpReply.setYourIPAddress(yourIp.toInt());
375 dhcpReply.setServerIPAddress(gatewayIp.toInt());
376 dhcpReply.setClientHardwareAddress(request.getClientHardwareAddress());
377
Yi Tsengc7403c22017-06-19 16:23:22 -0700378 List<DhcpOption> options = Lists.newArrayList();
Jian Li6a47fd02018-11-27 21:51:03 +0900379
Hyunsun Moon44aac662017-02-18 02:07:01 +0900380 // message type
Jian Li6a47fd02018-11-27 21:51:03 +0900381 options.add(doMsgType(msgType));
382
383 // server identifier
384 options.add(doServerId(gatewayIp));
385
386 // lease time
387 options.add(doLeaseTime());
388
389 // subnet mask
390 options.add(doSubnetMask(subnetPrefixLen));
391
392 // broadcast address
Jian Lif654dd12020-01-30 17:41:26 +0900393 options.add(doBroadcastAddr(yourIp, subnetPrefixLen));
Jian Li6a47fd02018-11-27 21:51:03 +0900394
395 // domain server
396 options.add(doDomainServer(osSubnet));
397
398 // mtu
399 options.add(doMtu(osSubnet));
400
401 // classless static route
402 if (!osSubnet.getHostRoutes().isEmpty()) {
403 options.add(doClasslessSr(osSubnet));
404 }
405
406 // Sets the default router address up.
407 // Performs only if the gateway is set in subnet.
408 if (!Strings.isNullOrEmpty(osSubnet.getGateway())) {
409 options.add(doRouterAddr(osSubnet));
410 }
411
412 // end option
413 options.add(doEnd());
414
415 dhcpReply.setOptions(options);
416 return dhcpReply;
417 }
418
419
420 private DhcpOption doMsgType(byte msgType) {
Yi Tsengc7403c22017-06-19 16:23:22 -0700421 DhcpOption option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900422 option.setCode(OptionCode_MessageType.getValue());
423 option.setLength((byte) 1);
424 byte[] optionData = {msgType};
425 option.setData(optionData);
Jian Li6a47fd02018-11-27 21:51:03 +0900426 return option;
427 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900428
Jian Li6a47fd02018-11-27 21:51:03 +0900429 private DhcpOption doServerId(IpAddress gatewayIp) {
430 DhcpOption option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900431 option.setCode(OptionCode_DHCPServerIp.getValue());
Jian Li411bf2e2018-11-29 00:08:54 +0900432 option.setLength(DHCP_OPTION_DATA_LENGTH);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900433 option.setData(gatewayIp.toOctets());
Jian Li6a47fd02018-11-27 21:51:03 +0900434 return option;
435 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900436
Jian Li6a47fd02018-11-27 21:51:03 +0900437 private DhcpOption doLeaseTime() {
438 DhcpOption option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900439 option.setCode(OptionCode_LeaseTime.getValue());
Jian Li411bf2e2018-11-29 00:08:54 +0900440 option.setLength(DHCP_OPTION_DATA_LENGTH);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900441 option.setData(DHCP_DATA_LEASE_INFINITE);
Jian Li6a47fd02018-11-27 21:51:03 +0900442 return option;
443 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900444
Jian Li6a47fd02018-11-27 21:51:03 +0900445 private DhcpOption doSubnetMask(int subnetPrefixLen) {
Hyunsun Moon44aac662017-02-18 02:07:01 +0900446 Ip4Address subnetMask = Ip4Address.makeMaskPrefix(subnetPrefixLen);
Jian Li6a47fd02018-11-27 21:51:03 +0900447 DhcpOption option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900448 option.setCode(OptionCode_SubnetMask.getValue());
Jian Li411bf2e2018-11-29 00:08:54 +0900449 option.setLength(DHCP_OPTION_DATA_LENGTH);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900450 option.setData(subnetMask.toOctets());
Jian Li6a47fd02018-11-27 21:51:03 +0900451 return option;
452 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900453
Jian Li6a47fd02018-11-27 21:51:03 +0900454 private DhcpOption doBroadcastAddr(Ip4Address yourIp, int subnetPrefixLen) {
Jian Lif654dd12020-01-30 17:41:26 +0900455 String broadcast = getBroadcastAddr(yourIp.toString(), subnetPrefixLen);
456
Jian Li6a47fd02018-11-27 21:51:03 +0900457 DhcpOption option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900458 option.setCode(OptionCode_BroadcastAddress.getValue());
Jian Li411bf2e2018-11-29 00:08:54 +0900459 option.setLength(DHCP_OPTION_DATA_LENGTH);
Jian Lif654dd12020-01-30 17:41:26 +0900460 option.setData(IpAddress.valueOf(broadcast).toOctets());
461
Jian Li6a47fd02018-11-27 21:51:03 +0900462 return option;
463 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900464
Jian Li6a47fd02018-11-27 21:51:03 +0900465 private DhcpOption doDomainServer(Subnet osSubnet) {
466 DhcpOption option = new DhcpOption();
Daniel Park4d421002018-07-27 23:36:57 +0900467
468 List<String> dnsServers = osSubnet.getDnsNames();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900469 option.setCode(OptionCode_DomainServer.getValue());
Daniel Park4d421002018-07-27 23:36:57 +0900470
471 if (dnsServers.isEmpty()) {
Jian Li411bf2e2018-11-29 00:08:54 +0900472 option.setLength((byte) DHCP_OPTION_DNS_LENGTH);
473 ByteBuffer dnsByteBuf = ByteBuffer.allocate(DHCP_OPTION_DNS_LENGTH);
Daniel Park4d421002018-07-27 23:36:57 +0900474 dnsByteBuf.put(DEFAULT_PRIMARY_DNS.toOctets());
475 dnsByteBuf.put(DEFAULT_SECONDARY_DNS.toOctets());
476
477 option.setData(dnsByteBuf.array());
478 } else {
479 int dnsLength = 4 * dnsServers.size();
480
481 option.setLength((byte) dnsLength);
482
Jian Li411bf2e2018-11-29 00:08:54 +0900483 ByteBuffer dnsByteBuf = ByteBuffer.allocate(DHCP_OPTION_DNS_LENGTH);
Daniel Park4d421002018-07-27 23:36:57 +0900484
Jian Li6a47fd02018-11-27 21:51:03 +0900485 for (String dnsServer : dnsServers) {
486 dnsByteBuf.put(IpAddress.valueOf(dnsServer).toOctets());
Daniel Park4d421002018-07-27 23:36:57 +0900487 }
488 option.setData(dnsByteBuf.array());
489 }
490
Jian Li6a47fd02018-11-27 21:51:03 +0900491 return option;
492 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900493
Jian Li6a47fd02018-11-27 21:51:03 +0900494 private DhcpOption doMtu(Subnet osSubnet) {
495 DhcpOption option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900496 option.setCode(DHCP_OPTION_MTU);
Jian Li411bf2e2018-11-29 00:08:54 +0900497 option.setLength((byte) DHCP_OPTION_MTU_LENGTH);
Daniel Park468e7852018-07-28 00:38:45 +0900498 Network osNetwork = osNetworkService.network(osSubnet.getNetworkId());
499 checkNotNull(osNetwork);
500 checkNotNull(osNetwork.getMTU());
501
Jian Li411bf2e2018-11-29 00:08:54 +0900502 option.setData(ByteBuffer.allocate(DHCP_OPTION_MTU_LENGTH)
503 .putShort(osNetwork.getMTU().shortValue()).array());
Hyunsun Moon44aac662017-02-18 02:07:01 +0900504
Jian Li6a47fd02018-11-27 21:51:03 +0900505 return option;
506 }
Daniel Parkd9d4c292018-06-26 20:33:58 +0900507
Jian Li6a47fd02018-11-27 21:51:03 +0900508 private DhcpOption doClasslessSr(Subnet osSubnet) {
509 DhcpOption option = new DhcpOption();
510 option.setCode(OptionCode_Classless_Static_Route.getValue());
Daniel Parkd9d4c292018-06-26 20:33:58 +0900511
Jian Li6a47fd02018-11-27 21:51:03 +0900512 int hostRoutesSize = hostRoutesSize(ImmutableList.copyOf(osSubnet.getHostRoutes()));
513 if (hostRoutesSize == 0) {
514 throw new IllegalArgumentException("Illegal CIDR hostRoutesSize value!");
Daniel Parkd9d4c292018-06-26 20:33:58 +0900515 }
516
Jian Li6a47fd02018-11-27 21:51:03 +0900517 log.trace("hostRouteSize: {}", hostRoutesSize);
Daniel Park48f10332018-08-02 10:57:27 +0900518
Jian Li6a47fd02018-11-27 21:51:03 +0900519 option.setLength((byte) hostRoutesSize);
520 ByteBuffer hostRouteByteBuf = ByteBuffer.allocate(hostRoutesSize);
521
522 osSubnet.getHostRoutes().forEach(h -> {
523 log.debug("processing host route information: {}", h.toString());
524
525 IpPrefix ipPrefix = IpPrefix.valueOf(h.getDestination());
526
527 hostRouteByteBuf.put(Objects.requireNonNull(bytesDestinationDescriptor(ipPrefix)));
528
529 hostRouteByteBuf.put(Ip4Address.valueOf(h.getNexthop()).toOctets());
530 });
531
532 option.setData(hostRouteByteBuf.array());
533 return option;
534 }
535
536 private DhcpOption doRouterAddr(Subnet osSubnet) {
537 DhcpOption option = new DhcpOption();
538 option.setCode(OptionCode_RouterAddress.getValue());
Jian Li411bf2e2018-11-29 00:08:54 +0900539 option.setLength(DHCP_OPTION_DATA_LENGTH);
Jian Li6a47fd02018-11-27 21:51:03 +0900540 option.setData(Ip4Address.valueOf(osSubnet.getGateway()).toOctets());
541 return option;
542 }
543
544 private DhcpOption doEnd() {
545 DhcpOption option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900546 option.setCode(OptionCode_END.getValue());
547 option.setLength((byte) 1);
Jian Li6a47fd02018-11-27 21:51:03 +0900548 return option;
Hyunsun Moon44aac662017-02-18 02:07:01 +0900549 }
Daniel Parkd9d4c292018-06-26 20:33:58 +0900550
551 private int hostRoutesSize(List<HostRoute> hostRoutes) {
552 int size = 0;
553 int preFixLen;
554
555 for (HostRoute h : hostRoutes) {
556 preFixLen = IpPrefix.valueOf(h.getDestination()).prefixLength();
557 if (Math.max(V4_CIDR_LOWER_BOUND, preFixLen) == V4_CIDR_LOWER_BOUND ||
558 Math.min(preFixLen, V4_CIDR_UPPER_BOUND) == V4_CIDR_UPPER_BOUND) {
559 throw new IllegalArgumentException("Illegal CIDR length value!");
560 }
561
Daniel Parkf8e422d2018-07-30 14:14:37 +0900562 for (int i = 0; i <= V4_BYTE_SIZE; i++) {
Daniel Parkd9d4c292018-06-26 20:33:58 +0900563 if (preFixLen == Math.min(preFixLen, i * OCTET_BIT_LENGTH)) {
564 size = size + i + 1 + PADDING_SIZE;
565 break;
566 }
567 }
568 }
569 return size;
570 }
571
572 private byte[] bytesDestinationDescriptor(IpPrefix ipPrefix) {
573 ByteBuffer byteBuffer;
574 int prefixLen = ipPrefix.prefixLength();
575
576 // retrieve ipPrefix to the destination descriptor format
577 // ex) 10.1.1.0/24 -> [10,1,1,0]
578 String[] ipPrefixString = ipPrefix.getIp4Prefix().toString()
579 .split("/")[0]
580 .split("\\.");
581
Jian Li5ecfd1a2018-12-10 11:41:03 +0900582 // retrieve destination descriptor and put this to byte buffer
583 // according to RFC 3442
Daniel Parkf8e422d2018-07-30 14:14:37 +0900584 // ex) 0.0.0.0/0 -> 0
Daniel Parkd9d4c292018-06-26 20:33:58 +0900585 // ex) 10.0.0.0/8 -> 8.10
586 // ex) 10.17.0.0/16 -> 16.10.17
587 // ex) 10.27.129.0/24 -> 24.10.27.129
588 // ex) 10.229.0.128/25 -> 25.10.229.0.128
Daniel Parkf8e422d2018-07-30 14:14:37 +0900589 for (int i = 0; i <= V4_BYTE_SIZE; i++) {
Daniel Parkd9d4c292018-06-26 20:33:58 +0900590 if (prefixLen == Math.min(prefixLen, i * OCTET_BIT_LENGTH)) {
591 byteBuffer = ByteBuffer.allocate(i + 1);
592 byteBuffer.put((byte) prefixLen);
593
594 for (int j = 0; j < i; j++) {
595 byteBuffer.put((byte) Integer.parseInt(ipPrefixString[j]));
596 }
597 return byteBuffer.array();
598 }
599 }
600
601 return null;
602 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900603 }
daniel park15506e82018-04-04 18:52:16 +0900604
605 private class InternalNodeEventListener implements OpenstackNodeListener {
Jian Lifb64d882018-11-27 10:57:40 +0900606 @Override
607 public boolean isRelevant(OpenstackNodeEvent event) {
608 return event.subject().type() == COMPUTE;
609 }
610
Jian Li34220ea2018-11-14 01:30:24 +0900611 private boolean isRelevantHelper() {
612 return Objects.equals(localNodeId, leadershipService.getLeader(appId.name()));
daniel park15506e82018-04-04 18:52:16 +0900613 }
614
615 @Override
616 public void event(OpenstackNodeEvent event) {
617 OpenstackNode osNode = event.subject();
618 switch (event.type()) {
619 case OPENSTACK_NODE_COMPLETE:
Jian Li6a47fd02018-11-27 21:51:03 +0900620 eventExecutor.execute(() -> processNodeCompletion(osNode));
daniel park15506e82018-04-04 18:52:16 +0900621 break;
622 case OPENSTACK_NODE_INCOMPLETE:
daniel park15506e82018-04-04 18:52:16 +0900623 case OPENSTACK_NODE_CREATED:
624 case OPENSTACK_NODE_UPDATED:
625 case OPENSTACK_NODE_REMOVED:
626 default:
627 break;
628 }
629 }
630
Jian Li6a47fd02018-11-27 21:51:03 +0900631 private void processNodeCompletion(OpenstackNode osNode) {
632 if (!isRelevantHelper()) {
633 return;
634 }
635 setDhcpRule(osNode, true);
636 }
637
638 private void processNodeIncompletion(OpenstackNode osNode) {
639 if (!isRelevantHelper()) {
640 return;
641 }
642 setDhcpRule(osNode, false);
643 }
644
daniel park15506e82018-04-04 18:52:16 +0900645 private void setDhcpRule(OpenstackNode openstackNode, boolean install) {
daniel park15506e82018-04-04 18:52:16 +0900646 TrafficSelector selector = DefaultTrafficSelector.builder()
647 .matchEthType(Ethernet.TYPE_IPV4)
648 .matchIPProtocol(IPv4.PROTOCOL_UDP)
649 .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
650 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
651 .build();
652
653 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
654 .punt()
655 .build();
656
657 osFlowRuleService.setRule(
658 appId,
659 openstackNode.intgBridge(),
660 selector,
661 treatment,
662 PRIORITY_DHCP_RULE,
Jian Li5c09e212018-10-24 18:23:58 +0900663 DHCP_TABLE,
daniel park15506e82018-04-04 18:52:16 +0900664 install);
665 }
666 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900667}