blob: 5e2947f93786783d38352cb7a8d9ff512b2de48b [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;
19import com.google.common.collect.Lists;
20import org.apache.felix.scr.annotations.Activate;
21import org.apache.felix.scr.annotations.Component;
22import org.apache.felix.scr.annotations.Deactivate;
23import org.apache.felix.scr.annotations.Modified;
24import org.apache.felix.scr.annotations.Property;
25import org.apache.felix.scr.annotations.Reference;
26import org.apache.felix.scr.annotations.ReferenceCardinality;
27import org.onlab.packet.DHCP;
Hyunsun Moon44aac662017-02-18 02:07:01 +090028import org.onlab.packet.Ethernet;
29import org.onlab.packet.IPv4;
30import org.onlab.packet.Ip4Address;
31import org.onlab.packet.IpPrefix;
32import org.onlab.packet.MacAddress;
33import org.onlab.packet.TpPort;
34import org.onlab.packet.UDP;
daniel park796c2eb2018-03-22 17:01:51 +090035import org.onlab.packet.dhcp.DhcpOption;
Hyunsun Moon44aac662017-02-18 02:07:01 +090036import org.onlab.util.Tools;
37import org.onosproject.cfg.ComponentConfigService;
38import org.onosproject.core.ApplicationId;
39import org.onosproject.core.CoreService;
40import org.onosproject.net.ConnectPoint;
daniel park796c2eb2018-03-22 17:01:51 +090041import org.onosproject.net.PortNumber;
Hyunsun Moon44aac662017-02-18 02:07:01 +090042import org.onosproject.net.flow.DefaultTrafficSelector;
43import org.onosproject.net.flow.DefaultTrafficTreatment;
44import org.onosproject.net.flow.TrafficSelector;
45import org.onosproject.net.flow.TrafficTreatment;
46import org.onosproject.net.packet.DefaultOutboundPacket;
47import org.onosproject.net.packet.PacketContext;
Hyunsun Moon44aac662017-02-18 02:07:01 +090048import org.onosproject.net.packet.PacketProcessor;
49import org.onosproject.net.packet.PacketService;
50import org.onosproject.openstacknetworking.api.Constants;
51import org.onosproject.openstacknetworking.api.InstancePort;
52import org.onosproject.openstacknetworking.api.InstancePortService;
daniel park796c2eb2018-03-22 17:01:51 +090053import org.onosproject.openstacknetworking.api.OpenstackFlowRuleService;
Hyunsun Moon44aac662017-02-18 02:07:01 +090054import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
daniel park796c2eb2018-03-22 17:01:51 +090055import org.onosproject.openstacknode.api.OpenstackNodeService;
Hyunsun Moon44aac662017-02-18 02:07:01 +090056import org.openstack4j.model.network.IP;
57import org.openstack4j.model.network.Port;
58import org.openstack4j.model.network.Subnet;
59import org.osgi.service.component.ComponentContext;
60import org.slf4j.Logger;
61
62import java.nio.ByteBuffer;
63import java.util.Dictionary;
64import java.util.List;
65
daniel park796c2eb2018-03-22 17:01:51 +090066import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_BroadcastAddress;
67import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_DHCPServerIp;
68import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_DomainServer;
69import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_END;
70import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_LeaseTime;
71import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
72import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_RouterAddress;
73import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_SubnetMask;
Yi Tsengc7403c22017-06-19 16:23:22 -070074import static org.onlab.packet.DHCP.MsgType.DHCPACK;
75import static org.onlab.packet.DHCP.MsgType.DHCPOFFER;
Hyunsun Moon44aac662017-02-18 02:07:01 +090076import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_GATEWAY_MAC_STR;
daniel park796c2eb2018-03-22 17:01:51 +090077import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_DHCP_RULE;
78import static org.onosproject.openstacknetworking.api.Constants.SRC_VNI_TABLE;
79import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.COMPUTE;
Hyunsun Moon44aac662017-02-18 02:07:01 +090080import static org.slf4j.LoggerFactory.getLogger;
81
82/**
83 * Handles DHCP requests for the virtual instances.
84 */
85@Component(immediate = true)
86public class OpenstackSwitchingDhcpHandler {
87 protected final Logger log = getLogger(getClass());
88
89 private static final String DHCP_SERVER_MAC = "dhcpServerMac";
Jian Lifb005492018-03-02 10:50:15 +090090 private static final String DHCP_DATA_MTU = "dhcpDataMtu";
Hyunsun Moon44aac662017-02-18 02:07:01 +090091 private static final Ip4Address DEFAULT_DNS = Ip4Address.valueOf("8.8.8.8");
92 private static final byte PACKET_TTL = (byte) 127;
93 // TODO add MTU, static route option codes to ONOS DHCP and remove here
94 private static final byte DHCP_OPTION_MTU = (byte) 26;
95 private static final byte[] DHCP_DATA_LEASE_INFINITE =
96 ByteBuffer.allocate(4).putInt(-1).array();
Jian Lifb005492018-03-02 10:50:15 +090097 // we are using 1450 as a default DHCP MTU value
98 private static final int DHCP_DATA_MTU_DEFAULT = 1450;
Hyunsun Moon44aac662017-02-18 02:07:01 +090099
100 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
101 protected CoreService coreService;
102
103 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
104 protected ComponentConfigService configService;
105
106 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
107 protected PacketService packetService;
108
109 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
110 protected InstancePortService instancePortService;
111
112 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
113 protected OpenstackNetworkService osNetworkService;
114
daniel park796c2eb2018-03-22 17:01:51 +0900115 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
116 protected OpenstackNodeService osNodeService;
117
118 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
119 protected OpenstackFlowRuleService osFlowRuleService;
120
Hyunsun Moon44aac662017-02-18 02:07:01 +0900121 @Property(name = DHCP_SERVER_MAC, value = DEFAULT_GATEWAY_MAC_STR,
122 label = "Fake MAC address for virtual network subnet gateway")
123 private String dhcpServerMac = DEFAULT_GATEWAY_MAC_STR;
124
Jian Lifb005492018-03-02 10:50:15 +0900125 @Property(name = DHCP_DATA_MTU, intValue = DHCP_DATA_MTU_DEFAULT,
126 label = "DHCP data Maximum Transmission Unit")
127 private int dhcpDataMtu = DHCP_DATA_MTU_DEFAULT;
128
Hyunsun Moon44aac662017-02-18 02:07:01 +0900129 private final PacketProcessor packetProcessor = new InternalPacketProcessor();
130
131 private ApplicationId appId;
132
133 @Activate
134 protected void activate() {
135 appId = coreService.registerApplication(Constants.OPENSTACK_NETWORKING_APP_ID);
136 configService.registerProperties(getClass());
137 packetService.addProcessor(packetProcessor, PacketProcessor.director(0));
daniel park796c2eb2018-03-22 17:01:51 +0900138 setDhcpRule(true);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900139
140 log.info("Started");
141 }
142
143 @Deactivate
144 protected void deactivate() {
daniel park796c2eb2018-03-22 17:01:51 +0900145 setDhcpRule(false);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900146 packetService.removeProcessor(packetProcessor);
147 configService.unregisterProperties(getClass(), false);
148
149 log.info("Stopped");
150 }
151
152 @Modified
153 protected void modified(ComponentContext context) {
154 Dictionary<?, ?> properties = context.getProperties();
155 String updatedMac;
Jian Lifb005492018-03-02 10:50:15 +0900156 Integer updateMtu;
Hyunsun Moon44aac662017-02-18 02:07:01 +0900157
158 updatedMac = Tools.get(properties, DHCP_SERVER_MAC);
Jian Lifb005492018-03-02 10:50:15 +0900159 updateMtu = Tools.getIntegerProperty(properties, DHCP_DATA_MTU);
160
Hyunsun Moon44aac662017-02-18 02:07:01 +0900161 if (!Strings.isNullOrEmpty(updatedMac) && !updatedMac.equals(dhcpServerMac)) {
162 dhcpServerMac = updatedMac;
163 }
164
Jian Lifb005492018-03-02 10:50:15 +0900165 if (updateMtu != null && updateMtu != dhcpDataMtu) {
166 dhcpDataMtu = updateMtu;
167 }
168
Hyunsun Moon44aac662017-02-18 02:07:01 +0900169 log.info("Modified");
170 }
171
daniel park796c2eb2018-03-22 17:01:51 +0900172 private void setDhcpRule(boolean install) {
Hyunsun Moon44aac662017-02-18 02:07:01 +0900173 TrafficSelector selector = DefaultTrafficSelector.builder()
174 .matchEthType(Ethernet.TYPE_IPV4)
175 .matchIPProtocol(IPv4.PROTOCOL_UDP)
176 .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
177 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
178 .build();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900179
daniel park796c2eb2018-03-22 17:01:51 +0900180 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
181 .setOutput(PortNumber.CONTROLLER)
Hyunsun Moon44aac662017-02-18 02:07:01 +0900182 .build();
daniel park796c2eb2018-03-22 17:01:51 +0900183
184 osNodeService.completeNodes(COMPUTE).forEach(node -> {
185 osFlowRuleService.setRule(
186 appId,
187 node.intgBridge(),
188 selector,
189 treatment,
190 PRIORITY_DHCP_RULE,
191 SRC_VNI_TABLE,
192 install);
193 });
Hyunsun Moon44aac662017-02-18 02:07:01 +0900194 }
195
196 private class InternalPacketProcessor implements PacketProcessor {
197
198 @Override
199 public void process(PacketContext context) {
200 if (context.isHandled()) {
201 return;
202 }
203
204 Ethernet ethPacket = context.inPacket().parsed();
205 if (ethPacket == null || ethPacket.getEtherType() != Ethernet.TYPE_IPV4) {
206 return;
207 }
208 IPv4 ipv4Packet = (IPv4) ethPacket.getPayload();
209 if (ipv4Packet.getProtocol() != IPv4.PROTOCOL_UDP) {
210 return;
211 }
212 UDP udpPacket = (UDP) ipv4Packet.getPayload();
213 if (udpPacket.getDestinationPort() != UDP.DHCP_SERVER_PORT ||
214 udpPacket.getSourcePort() != UDP.DHCP_CLIENT_PORT) {
215 return;
216 }
217
218 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
219 processDhcp(context, dhcpPacket);
220 }
221
222 private void processDhcp(PacketContext context, DHCP dhcpPacket) {
223 if (dhcpPacket == null) {
224 log.trace("DHCP packet without payload received, do nothing");
225 return;
226 }
227
Yi Tsengc7403c22017-06-19 16:23:22 -0700228 DHCP.MsgType inPacketType = getPacketType(dhcpPacket);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900229 if (inPacketType == null || dhcpPacket.getClientHardwareAddress() == null) {
230 log.trace("Malformed DHCP packet received, ignore it");
231 return;
232 }
233
234 MacAddress clientMac = MacAddress.valueOf(dhcpPacket.getClientHardwareAddress());
235 InstancePort reqInstPort = instancePortService.instancePort(clientMac);
236 if (reqInstPort == null) {
237 log.trace("Failed to find host(MAC:{})", clientMac);
238 return;
239 }
240 Ethernet ethPacket = context.inPacket().parsed();
241 switch (inPacketType) {
242 case DHCPDISCOVER:
243 log.trace("DHCP DISCOVER received from {}", clientMac);
244 Ethernet discoverReply = buildReply(
245 ethPacket,
246 (byte) DHCPOFFER.getValue(),
247 reqInstPort);
248 sendReply(context, discoverReply);
249 log.trace("DHCP OFFER({}) is sent for {}",
250 reqInstPort.ipAddress(), clientMac);
251 break;
252 case DHCPREQUEST:
253 log.trace("DHCP REQUEST received from {}", clientMac);
254 Ethernet requestReply = buildReply(
255 ethPacket,
256 (byte) DHCPACK.getValue(),
257 reqInstPort);
258 sendReply(context, requestReply);
259 log.trace("DHCP ACK({}) is sent for {}",
260 reqInstPort.ipAddress(), clientMac);
261 break;
262 case DHCPRELEASE:
263 log.trace("DHCP RELEASE received from {}", clientMac);
264 // do nothing
265 break;
266 default:
267 break;
268 }
269 }
270
Yi Tsengc7403c22017-06-19 16:23:22 -0700271 private DHCP.MsgType getPacketType(DHCP dhcpPacket) {
272 DhcpOption optType = dhcpPacket.getOption(OptionCode_MessageType);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900273 if (optType == null) {
274 log.trace("DHCP packet with no message type, ignore it");
275 return null;
276 }
277
Yi Tsengc7403c22017-06-19 16:23:22 -0700278 DHCP.MsgType inPacketType = DHCP.MsgType.getType(optType.getData()[0]);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900279 if (inPacketType == null) {
280 log.trace("DHCP packet with no packet type, ignore it");
281 }
282 return inPacketType;
283 }
284
285 private Ethernet buildReply(Ethernet ethRequest, byte packetType,
286 InstancePort reqInstPort) {
287 Port osPort = osNetworkService.port(reqInstPort.portId());
288 // pick one IP address to make a reply
289 IP fixedIp = osPort.getFixedIps().stream().findFirst().get();
290 Subnet osSubnet = osNetworkService.subnet(fixedIp.getSubnetId());
291
292 Ethernet ethReply = new Ethernet();
293 ethReply.setSourceMACAddress(dhcpServerMac);
294 ethReply.setDestinationMACAddress(ethRequest.getSourceMAC());
295 ethReply.setEtherType(Ethernet.TYPE_IPV4);
296
297 IPv4 ipv4Request = (IPv4) ethRequest.getPayload();
298 IPv4 ipv4Reply = new IPv4();
299 ipv4Reply.setSourceAddress(Ip4Address.valueOf(osSubnet.getGateway()).toInt());
300 ipv4Reply.setDestinationAddress(reqInstPort.ipAddress().getIp4Address().toInt());
301 ipv4Reply.setTtl(PACKET_TTL);
302
303 UDP udpRequest = (UDP) ipv4Request.getPayload();
304 UDP udpReply = new UDP();
305 udpReply.setSourcePort((byte) UDP.DHCP_SERVER_PORT);
306 udpReply.setDestinationPort((byte) UDP.DHCP_CLIENT_PORT);
307
308 DHCP dhcpRequest = (DHCP) udpRequest.getPayload();
309 DHCP dhcpReply = buildDhcpReply(
310 dhcpRequest,
311 packetType,
312 reqInstPort.ipAddress().getIp4Address(),
313 osSubnet);
314
315 udpReply.setPayload(dhcpReply);
316 ipv4Reply.setPayload(udpReply);
317 ethReply.setPayload(ipv4Reply);
318
319 return ethReply;
320 }
321
322 private void sendReply(PacketContext context, Ethernet ethReply) {
323 if (ethReply == null) {
324 return;
325 }
326 ConnectPoint srcPoint = context.inPacket().receivedFrom();
327 TrafficTreatment treatment = DefaultTrafficTreatment
328 .builder()
329 .setOutput(srcPoint.port())
330 .build();
331
332 packetService.emit(new DefaultOutboundPacket(
333 srcPoint.deviceId(),
334 treatment,
335 ByteBuffer.wrap(ethReply.serialize())));
336 context.block();
337 }
338
339 private DHCP buildDhcpReply(DHCP request, byte msgType, Ip4Address yourIp,
340 Subnet osSubnet) {
341 Ip4Address gatewayIp = Ip4Address.valueOf(osSubnet.getGateway());
342 int subnetPrefixLen = IpPrefix.valueOf(osSubnet.getCidr()).prefixLength();
343
344 DHCP dhcpReply = new DHCP();
345 dhcpReply.setOpCode(DHCP.OPCODE_REPLY);
346 dhcpReply.setHardwareType(DHCP.HWTYPE_ETHERNET);
347 dhcpReply.setHardwareAddressLength((byte) 6);
348 dhcpReply.setTransactionId(request.getTransactionId());
349 dhcpReply.setFlags(request.getFlags());
350 dhcpReply.setYourIPAddress(yourIp.toInt());
351 dhcpReply.setServerIPAddress(gatewayIp.toInt());
352 dhcpReply.setClientHardwareAddress(request.getClientHardwareAddress());
353
Yi Tsengc7403c22017-06-19 16:23:22 -0700354 List<DhcpOption> options = Lists.newArrayList();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900355 // message type
Yi Tsengc7403c22017-06-19 16:23:22 -0700356 DhcpOption option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900357 option.setCode(OptionCode_MessageType.getValue());
358 option.setLength((byte) 1);
359 byte[] optionData = {msgType};
360 option.setData(optionData);
361 options.add(option);
362
363 // server identifier
Yi Tsengc7403c22017-06-19 16:23:22 -0700364 option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900365 option.setCode(OptionCode_DHCPServerIp.getValue());
366 option.setLength((byte) 4);
367 option.setData(gatewayIp.toOctets());
368 options.add(option);
369
370 // lease time
Yi Tsengc7403c22017-06-19 16:23:22 -0700371 option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900372 option.setCode(OptionCode_LeaseTime.getValue());
373 option.setLength((byte) 4);
374 option.setData(DHCP_DATA_LEASE_INFINITE);
375 options.add(option);
376
377 // subnet mask
378 Ip4Address subnetMask = Ip4Address.makeMaskPrefix(subnetPrefixLen);
Yi Tsengc7403c22017-06-19 16:23:22 -0700379 option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900380 option.setCode(OptionCode_SubnetMask.getValue());
381 option.setLength((byte) 4);
382 option.setData(subnetMask.toOctets());
383 options.add(option);
384
385 // broadcast address
386 Ip4Address broadcast = Ip4Address.makeMaskedAddress(yourIp, subnetPrefixLen);
Yi Tsengc7403c22017-06-19 16:23:22 -0700387 option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900388 option.setCode(OptionCode_BroadcastAddress.getValue());
389 option.setLength((byte) 4);
390 option.setData(broadcast.toOctets());
391 options.add(option);
392
393 // domain server
Yi Tsengc7403c22017-06-19 16:23:22 -0700394 option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900395 option.setCode(OptionCode_DomainServer.getValue());
396 option.setLength((byte) 4);
397 option.setData(DEFAULT_DNS.toOctets());
398 options.add(option);
399
Yi Tsengc7403c22017-06-19 16:23:22 -0700400 option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900401 option.setCode(DHCP_OPTION_MTU);
402 option.setLength((byte) 2);
Jian Lifb005492018-03-02 10:50:15 +0900403 option.setData(ByteBuffer.allocate(2).putShort((short) dhcpDataMtu).array());
Hyunsun Moon44aac662017-02-18 02:07:01 +0900404 options.add(option);
405
406 // router address
Yi Tsengc7403c22017-06-19 16:23:22 -0700407 option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900408 option.setCode(OptionCode_RouterAddress.getValue());
409 option.setLength((byte) 4);
410 option.setData(gatewayIp.toOctets());
411 options.add(option);
412
413 // end option
Yi Tsengc7403c22017-06-19 16:23:22 -0700414 option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900415 option.setCode(OptionCode_END.getValue());
416 option.setLength((byte) 1);
417 options.add(option);
418
419 dhcpReply.setOptions(options);
420 return dhcpReply;
421 }
422 }
423}