blob: 3192cb4ef2c2ce1c810d195c1ac23c24cd30c0c0 [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;
Yi Tsengc7403c22017-06-19 16:23:22 -070028import org.onlab.packet.dhcp.DhcpOption;
Hyunsun Moon44aac662017-02-18 02:07:01 +090029import org.onlab.packet.Ethernet;
30import org.onlab.packet.IPv4;
31import org.onlab.packet.Ip4Address;
32import org.onlab.packet.IpPrefix;
33import org.onlab.packet.MacAddress;
34import org.onlab.packet.TpPort;
35import org.onlab.packet.UDP;
36import org.onlab.util.Tools;
37import org.onosproject.cfg.ComponentConfigService;
38import org.onosproject.core.ApplicationId;
39import org.onosproject.core.CoreService;
40import org.onosproject.net.ConnectPoint;
41import org.onosproject.net.flow.DefaultTrafficSelector;
42import org.onosproject.net.flow.DefaultTrafficTreatment;
43import org.onosproject.net.flow.TrafficSelector;
44import org.onosproject.net.flow.TrafficTreatment;
45import org.onosproject.net.packet.DefaultOutboundPacket;
46import org.onosproject.net.packet.PacketContext;
47import org.onosproject.net.packet.PacketPriority;
48import 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;
53import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
54import org.openstack4j.model.network.IP;
55import org.openstack4j.model.network.Port;
56import org.openstack4j.model.network.Subnet;
57import org.osgi.service.component.ComponentContext;
58import org.slf4j.Logger;
59
60import java.nio.ByteBuffer;
61import java.util.Dictionary;
62import java.util.List;
63
64import static org.onlab.packet.DHCP.DHCPOptionCode.*;
Yi Tsengc7403c22017-06-19 16:23:22 -070065import static org.onlab.packet.DHCP.MsgType.DHCPACK;
66import static org.onlab.packet.DHCP.MsgType.DHCPOFFER;
Hyunsun Moon44aac662017-02-18 02:07:01 +090067import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_GATEWAY_MAC_STR;
68import static org.slf4j.LoggerFactory.getLogger;
69
70/**
71 * Handles DHCP requests for the virtual instances.
72 */
73@Component(immediate = true)
74public class OpenstackSwitchingDhcpHandler {
75 protected final Logger log = getLogger(getClass());
76
77 private static final String DHCP_SERVER_MAC = "dhcpServerMac";
Jian Lifb005492018-03-02 10:50:15 +090078 private static final String DHCP_DATA_MTU = "dhcpDataMtu";
Hyunsun Moon44aac662017-02-18 02:07:01 +090079 private static final Ip4Address DEFAULT_DNS = Ip4Address.valueOf("8.8.8.8");
80 private static final byte PACKET_TTL = (byte) 127;
81 // TODO add MTU, static route option codes to ONOS DHCP and remove here
82 private static final byte DHCP_OPTION_MTU = (byte) 26;
83 private static final byte[] DHCP_DATA_LEASE_INFINITE =
84 ByteBuffer.allocate(4).putInt(-1).array();
Jian Lifb005492018-03-02 10:50:15 +090085 // we are using 1450 as a default DHCP MTU value
86 private static final int DHCP_DATA_MTU_DEFAULT = 1450;
Hyunsun Moon44aac662017-02-18 02:07:01 +090087
88 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
89 protected CoreService coreService;
90
91 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
92 protected ComponentConfigService configService;
93
94 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
95 protected PacketService packetService;
96
97 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
98 protected InstancePortService instancePortService;
99
100 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
101 protected OpenstackNetworkService osNetworkService;
102
103 @Property(name = DHCP_SERVER_MAC, value = DEFAULT_GATEWAY_MAC_STR,
104 label = "Fake MAC address for virtual network subnet gateway")
105 private String dhcpServerMac = DEFAULT_GATEWAY_MAC_STR;
106
Jian Lifb005492018-03-02 10:50:15 +0900107 @Property(name = DHCP_DATA_MTU, intValue = DHCP_DATA_MTU_DEFAULT,
108 label = "DHCP data Maximum Transmission Unit")
109 private int dhcpDataMtu = DHCP_DATA_MTU_DEFAULT;
110
Hyunsun Moon44aac662017-02-18 02:07:01 +0900111 private final PacketProcessor packetProcessor = new InternalPacketProcessor();
112
113 private ApplicationId appId;
114
115 @Activate
116 protected void activate() {
117 appId = coreService.registerApplication(Constants.OPENSTACK_NETWORKING_APP_ID);
118 configService.registerProperties(getClass());
119 packetService.addProcessor(packetProcessor, PacketProcessor.director(0));
120 requestPackets();
121
122 log.info("Started");
123 }
124
125 @Deactivate
126 protected void deactivate() {
127 cancelPackets();
128 packetService.removeProcessor(packetProcessor);
129 configService.unregisterProperties(getClass(), false);
130
131 log.info("Stopped");
132 }
133
134 @Modified
135 protected void modified(ComponentContext context) {
136 Dictionary<?, ?> properties = context.getProperties();
137 String updatedMac;
Jian Lifb005492018-03-02 10:50:15 +0900138 Integer updateMtu;
Hyunsun Moon44aac662017-02-18 02:07:01 +0900139
140 updatedMac = Tools.get(properties, DHCP_SERVER_MAC);
Jian Lifb005492018-03-02 10:50:15 +0900141 updateMtu = Tools.getIntegerProperty(properties, DHCP_DATA_MTU);
142
Hyunsun Moon44aac662017-02-18 02:07:01 +0900143 if (!Strings.isNullOrEmpty(updatedMac) && !updatedMac.equals(dhcpServerMac)) {
144 dhcpServerMac = updatedMac;
145 }
146
Jian Lifb005492018-03-02 10:50:15 +0900147 if (updateMtu != null && updateMtu != dhcpDataMtu) {
148 dhcpDataMtu = updateMtu;
149 }
150
Hyunsun Moon44aac662017-02-18 02:07:01 +0900151 log.info("Modified");
152 }
153
154 private void requestPackets() {
155 TrafficSelector selector = DefaultTrafficSelector.builder()
156 .matchEthType(Ethernet.TYPE_IPV4)
157 .matchIPProtocol(IPv4.PROTOCOL_UDP)
158 .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
159 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
160 .build();
161 packetService.requestPackets(selector, PacketPriority.CONTROL, appId);
162 }
163
164 private void cancelPackets() {
165 TrafficSelector selector = DefaultTrafficSelector.builder()
166 .matchEthType(Ethernet.TYPE_IPV4)
167 .matchIPProtocol(IPv4.PROTOCOL_UDP)
168 .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
169 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
170 .build();
171 packetService.cancelPackets(selector, PacketPriority.CONTROL, appId);
172 }
173
174 private class InternalPacketProcessor implements PacketProcessor {
175
176 @Override
177 public void process(PacketContext context) {
178 if (context.isHandled()) {
179 return;
180 }
181
182 Ethernet ethPacket = context.inPacket().parsed();
183 if (ethPacket == null || ethPacket.getEtherType() != Ethernet.TYPE_IPV4) {
184 return;
185 }
186 IPv4 ipv4Packet = (IPv4) ethPacket.getPayload();
187 if (ipv4Packet.getProtocol() != IPv4.PROTOCOL_UDP) {
188 return;
189 }
190 UDP udpPacket = (UDP) ipv4Packet.getPayload();
191 if (udpPacket.getDestinationPort() != UDP.DHCP_SERVER_PORT ||
192 udpPacket.getSourcePort() != UDP.DHCP_CLIENT_PORT) {
193 return;
194 }
195
196 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
197 processDhcp(context, dhcpPacket);
198 }
199
200 private void processDhcp(PacketContext context, DHCP dhcpPacket) {
201 if (dhcpPacket == null) {
202 log.trace("DHCP packet without payload received, do nothing");
203 return;
204 }
205
Yi Tsengc7403c22017-06-19 16:23:22 -0700206 DHCP.MsgType inPacketType = getPacketType(dhcpPacket);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900207 if (inPacketType == null || dhcpPacket.getClientHardwareAddress() == null) {
208 log.trace("Malformed DHCP packet received, ignore it");
209 return;
210 }
211
212 MacAddress clientMac = MacAddress.valueOf(dhcpPacket.getClientHardwareAddress());
213 InstancePort reqInstPort = instancePortService.instancePort(clientMac);
214 if (reqInstPort == null) {
215 log.trace("Failed to find host(MAC:{})", clientMac);
216 return;
217 }
218 Ethernet ethPacket = context.inPacket().parsed();
219 switch (inPacketType) {
220 case DHCPDISCOVER:
221 log.trace("DHCP DISCOVER received from {}", clientMac);
222 Ethernet discoverReply = buildReply(
223 ethPacket,
224 (byte) DHCPOFFER.getValue(),
225 reqInstPort);
226 sendReply(context, discoverReply);
227 log.trace("DHCP OFFER({}) is sent for {}",
228 reqInstPort.ipAddress(), clientMac);
229 break;
230 case DHCPREQUEST:
231 log.trace("DHCP REQUEST received from {}", clientMac);
232 Ethernet requestReply = buildReply(
233 ethPacket,
234 (byte) DHCPACK.getValue(),
235 reqInstPort);
236 sendReply(context, requestReply);
237 log.trace("DHCP ACK({}) is sent for {}",
238 reqInstPort.ipAddress(), clientMac);
239 break;
240 case DHCPRELEASE:
241 log.trace("DHCP RELEASE received from {}", clientMac);
242 // do nothing
243 break;
244 default:
245 break;
246 }
247 }
248
Yi Tsengc7403c22017-06-19 16:23:22 -0700249 private DHCP.MsgType getPacketType(DHCP dhcpPacket) {
250 DhcpOption optType = dhcpPacket.getOption(OptionCode_MessageType);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900251 if (optType == null) {
252 log.trace("DHCP packet with no message type, ignore it");
253 return null;
254 }
255
Yi Tsengc7403c22017-06-19 16:23:22 -0700256 DHCP.MsgType inPacketType = DHCP.MsgType.getType(optType.getData()[0]);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900257 if (inPacketType == null) {
258 log.trace("DHCP packet with no packet type, ignore it");
259 }
260 return inPacketType;
261 }
262
263 private Ethernet buildReply(Ethernet ethRequest, byte packetType,
264 InstancePort reqInstPort) {
265 Port osPort = osNetworkService.port(reqInstPort.portId());
266 // pick one IP address to make a reply
267 IP fixedIp = osPort.getFixedIps().stream().findFirst().get();
268 Subnet osSubnet = osNetworkService.subnet(fixedIp.getSubnetId());
269
270 Ethernet ethReply = new Ethernet();
271 ethReply.setSourceMACAddress(dhcpServerMac);
272 ethReply.setDestinationMACAddress(ethRequest.getSourceMAC());
273 ethReply.setEtherType(Ethernet.TYPE_IPV4);
274
275 IPv4 ipv4Request = (IPv4) ethRequest.getPayload();
276 IPv4 ipv4Reply = new IPv4();
277 ipv4Reply.setSourceAddress(Ip4Address.valueOf(osSubnet.getGateway()).toInt());
278 ipv4Reply.setDestinationAddress(reqInstPort.ipAddress().getIp4Address().toInt());
279 ipv4Reply.setTtl(PACKET_TTL);
280
281 UDP udpRequest = (UDP) ipv4Request.getPayload();
282 UDP udpReply = new UDP();
283 udpReply.setSourcePort((byte) UDP.DHCP_SERVER_PORT);
284 udpReply.setDestinationPort((byte) UDP.DHCP_CLIENT_PORT);
285
286 DHCP dhcpRequest = (DHCP) udpRequest.getPayload();
287 DHCP dhcpReply = buildDhcpReply(
288 dhcpRequest,
289 packetType,
290 reqInstPort.ipAddress().getIp4Address(),
291 osSubnet);
292
293 udpReply.setPayload(dhcpReply);
294 ipv4Reply.setPayload(udpReply);
295 ethReply.setPayload(ipv4Reply);
296
297 return ethReply;
298 }
299
300 private void sendReply(PacketContext context, Ethernet ethReply) {
301 if (ethReply == null) {
302 return;
303 }
304 ConnectPoint srcPoint = context.inPacket().receivedFrom();
305 TrafficTreatment treatment = DefaultTrafficTreatment
306 .builder()
307 .setOutput(srcPoint.port())
308 .build();
309
310 packetService.emit(new DefaultOutboundPacket(
311 srcPoint.deviceId(),
312 treatment,
313 ByteBuffer.wrap(ethReply.serialize())));
314 context.block();
315 }
316
317 private DHCP buildDhcpReply(DHCP request, byte msgType, Ip4Address yourIp,
318 Subnet osSubnet) {
319 Ip4Address gatewayIp = Ip4Address.valueOf(osSubnet.getGateway());
320 int subnetPrefixLen = IpPrefix.valueOf(osSubnet.getCidr()).prefixLength();
321
322 DHCP dhcpReply = new DHCP();
323 dhcpReply.setOpCode(DHCP.OPCODE_REPLY);
324 dhcpReply.setHardwareType(DHCP.HWTYPE_ETHERNET);
325 dhcpReply.setHardwareAddressLength((byte) 6);
326 dhcpReply.setTransactionId(request.getTransactionId());
327 dhcpReply.setFlags(request.getFlags());
328 dhcpReply.setYourIPAddress(yourIp.toInt());
329 dhcpReply.setServerIPAddress(gatewayIp.toInt());
330 dhcpReply.setClientHardwareAddress(request.getClientHardwareAddress());
331
Yi Tsengc7403c22017-06-19 16:23:22 -0700332 List<DhcpOption> options = Lists.newArrayList();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900333 // message type
Yi Tsengc7403c22017-06-19 16:23:22 -0700334 DhcpOption option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900335 option.setCode(OptionCode_MessageType.getValue());
336 option.setLength((byte) 1);
337 byte[] optionData = {msgType};
338 option.setData(optionData);
339 options.add(option);
340
341 // server identifier
Yi Tsengc7403c22017-06-19 16:23:22 -0700342 option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900343 option.setCode(OptionCode_DHCPServerIp.getValue());
344 option.setLength((byte) 4);
345 option.setData(gatewayIp.toOctets());
346 options.add(option);
347
348 // lease time
Yi Tsengc7403c22017-06-19 16:23:22 -0700349 option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900350 option.setCode(OptionCode_LeaseTime.getValue());
351 option.setLength((byte) 4);
352 option.setData(DHCP_DATA_LEASE_INFINITE);
353 options.add(option);
354
355 // subnet mask
356 Ip4Address subnetMask = Ip4Address.makeMaskPrefix(subnetPrefixLen);
Yi Tsengc7403c22017-06-19 16:23:22 -0700357 option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900358 option.setCode(OptionCode_SubnetMask.getValue());
359 option.setLength((byte) 4);
360 option.setData(subnetMask.toOctets());
361 options.add(option);
362
363 // broadcast address
364 Ip4Address broadcast = Ip4Address.makeMaskedAddress(yourIp, subnetPrefixLen);
Yi Tsengc7403c22017-06-19 16:23:22 -0700365 option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900366 option.setCode(OptionCode_BroadcastAddress.getValue());
367 option.setLength((byte) 4);
368 option.setData(broadcast.toOctets());
369 options.add(option);
370
371 // domain server
Yi Tsengc7403c22017-06-19 16:23:22 -0700372 option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900373 option.setCode(OptionCode_DomainServer.getValue());
374 option.setLength((byte) 4);
375 option.setData(DEFAULT_DNS.toOctets());
376 options.add(option);
377
Yi Tsengc7403c22017-06-19 16:23:22 -0700378 option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900379 option.setCode(DHCP_OPTION_MTU);
380 option.setLength((byte) 2);
Jian Lifb005492018-03-02 10:50:15 +0900381 option.setData(ByteBuffer.allocate(2).putShort((short) dhcpDataMtu).array());
Hyunsun Moon44aac662017-02-18 02:07:01 +0900382 options.add(option);
383
384 // router address
Yi Tsengc7403c22017-06-19 16:23:22 -0700385 option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900386 option.setCode(OptionCode_RouterAddress.getValue());
387 option.setLength((byte) 4);
388 option.setData(gatewayIp.toOctets());
389 options.add(option);
390
391 // end option
Yi Tsengc7403c22017-06-19 16:23:22 -0700392 option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900393 option.setCode(OptionCode_END.getValue());
394 option.setLength((byte) 1);
395 options.add(option);
396
397 dhcpReply.setOptions(options);
398 return dhcpReply;
399 }
400 }
401}