blob: 82716441a96b7cbd64d48b417d14221571cda95c [file] [log] [blame]
Hyunsun Moon44aac662017-02-18 02:07:01 +09001/*
2 * Copyright 2017-present Open Networking Laboratory
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
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";
78 private static final Ip4Address DEFAULT_DNS = Ip4Address.valueOf("8.8.8.8");
79 private static final byte PACKET_TTL = (byte) 127;
80 // TODO add MTU, static route option codes to ONOS DHCP and remove here
81 private static final byte DHCP_OPTION_MTU = (byte) 26;
82 private static final byte[] DHCP_DATA_LEASE_INFINITE =
83 ByteBuffer.allocate(4).putInt(-1).array();
84 private static final byte[] DHCP_DATA_MTU_DEFAULT =
85 ByteBuffer.allocate(2).putShort((short) 1450).array();
86
87 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
88 protected CoreService coreService;
89
90 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
91 protected ComponentConfigService configService;
92
93 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
94 protected PacketService packetService;
95
96 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
97 protected InstancePortService instancePortService;
98
99 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
100 protected OpenstackNetworkService osNetworkService;
101
102 @Property(name = DHCP_SERVER_MAC, value = DEFAULT_GATEWAY_MAC_STR,
103 label = "Fake MAC address for virtual network subnet gateway")
104 private String dhcpServerMac = DEFAULT_GATEWAY_MAC_STR;
105
106 private final PacketProcessor packetProcessor = new InternalPacketProcessor();
107
108 private ApplicationId appId;
109
110 @Activate
111 protected void activate() {
112 appId = coreService.registerApplication(Constants.OPENSTACK_NETWORKING_APP_ID);
113 configService.registerProperties(getClass());
114 packetService.addProcessor(packetProcessor, PacketProcessor.director(0));
115 requestPackets();
116
117 log.info("Started");
118 }
119
120 @Deactivate
121 protected void deactivate() {
122 cancelPackets();
123 packetService.removeProcessor(packetProcessor);
124 configService.unregisterProperties(getClass(), false);
125
126 log.info("Stopped");
127 }
128
129 @Modified
130 protected void modified(ComponentContext context) {
131 Dictionary<?, ?> properties = context.getProperties();
132 String updatedMac;
133
134 updatedMac = Tools.get(properties, DHCP_SERVER_MAC);
135 if (!Strings.isNullOrEmpty(updatedMac) && !updatedMac.equals(dhcpServerMac)) {
136 dhcpServerMac = updatedMac;
137 }
138
139 log.info("Modified");
140 }
141
142 private void requestPackets() {
143 TrafficSelector selector = DefaultTrafficSelector.builder()
144 .matchEthType(Ethernet.TYPE_IPV4)
145 .matchIPProtocol(IPv4.PROTOCOL_UDP)
146 .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
147 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
148 .build();
149 packetService.requestPackets(selector, PacketPriority.CONTROL, appId);
150 }
151
152 private void cancelPackets() {
153 TrafficSelector selector = DefaultTrafficSelector.builder()
154 .matchEthType(Ethernet.TYPE_IPV4)
155 .matchIPProtocol(IPv4.PROTOCOL_UDP)
156 .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
157 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
158 .build();
159 packetService.cancelPackets(selector, PacketPriority.CONTROL, appId);
160 }
161
162 private class InternalPacketProcessor implements PacketProcessor {
163
164 @Override
165 public void process(PacketContext context) {
166 if (context.isHandled()) {
167 return;
168 }
169
170 Ethernet ethPacket = context.inPacket().parsed();
171 if (ethPacket == null || ethPacket.getEtherType() != Ethernet.TYPE_IPV4) {
172 return;
173 }
174 IPv4 ipv4Packet = (IPv4) ethPacket.getPayload();
175 if (ipv4Packet.getProtocol() != IPv4.PROTOCOL_UDP) {
176 return;
177 }
178 UDP udpPacket = (UDP) ipv4Packet.getPayload();
179 if (udpPacket.getDestinationPort() != UDP.DHCP_SERVER_PORT ||
180 udpPacket.getSourcePort() != UDP.DHCP_CLIENT_PORT) {
181 return;
182 }
183
184 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
185 processDhcp(context, dhcpPacket);
186 }
187
188 private void processDhcp(PacketContext context, DHCP dhcpPacket) {
189 if (dhcpPacket == null) {
190 log.trace("DHCP packet without payload received, do nothing");
191 return;
192 }
193
Yi Tsengc7403c22017-06-19 16:23:22 -0700194 DHCP.MsgType inPacketType = getPacketType(dhcpPacket);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900195 if (inPacketType == null || dhcpPacket.getClientHardwareAddress() == null) {
196 log.trace("Malformed DHCP packet received, ignore it");
197 return;
198 }
199
200 MacAddress clientMac = MacAddress.valueOf(dhcpPacket.getClientHardwareAddress());
201 InstancePort reqInstPort = instancePortService.instancePort(clientMac);
202 if (reqInstPort == null) {
203 log.trace("Failed to find host(MAC:{})", clientMac);
204 return;
205 }
206 Ethernet ethPacket = context.inPacket().parsed();
207 switch (inPacketType) {
208 case DHCPDISCOVER:
209 log.trace("DHCP DISCOVER received from {}", clientMac);
210 Ethernet discoverReply = buildReply(
211 ethPacket,
212 (byte) DHCPOFFER.getValue(),
213 reqInstPort);
214 sendReply(context, discoverReply);
215 log.trace("DHCP OFFER({}) is sent for {}",
216 reqInstPort.ipAddress(), clientMac);
217 break;
218 case DHCPREQUEST:
219 log.trace("DHCP REQUEST received from {}", clientMac);
220 Ethernet requestReply = buildReply(
221 ethPacket,
222 (byte) DHCPACK.getValue(),
223 reqInstPort);
224 sendReply(context, requestReply);
225 log.trace("DHCP ACK({}) is sent for {}",
226 reqInstPort.ipAddress(), clientMac);
227 break;
228 case DHCPRELEASE:
229 log.trace("DHCP RELEASE received from {}", clientMac);
230 // do nothing
231 break;
232 default:
233 break;
234 }
235 }
236
Yi Tsengc7403c22017-06-19 16:23:22 -0700237 private DHCP.MsgType getPacketType(DHCP dhcpPacket) {
238 DhcpOption optType = dhcpPacket.getOption(OptionCode_MessageType);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900239 if (optType == null) {
240 log.trace("DHCP packet with no message type, ignore it");
241 return null;
242 }
243
Yi Tsengc7403c22017-06-19 16:23:22 -0700244 DHCP.MsgType inPacketType = DHCP.MsgType.getType(optType.getData()[0]);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900245 if (inPacketType == null) {
246 log.trace("DHCP packet with no packet type, ignore it");
247 }
248 return inPacketType;
249 }
250
251 private Ethernet buildReply(Ethernet ethRequest, byte packetType,
252 InstancePort reqInstPort) {
253 Port osPort = osNetworkService.port(reqInstPort.portId());
254 // pick one IP address to make a reply
255 IP fixedIp = osPort.getFixedIps().stream().findFirst().get();
256 Subnet osSubnet = osNetworkService.subnet(fixedIp.getSubnetId());
257
258 Ethernet ethReply = new Ethernet();
259 ethReply.setSourceMACAddress(dhcpServerMac);
260 ethReply.setDestinationMACAddress(ethRequest.getSourceMAC());
261 ethReply.setEtherType(Ethernet.TYPE_IPV4);
262
263 IPv4 ipv4Request = (IPv4) ethRequest.getPayload();
264 IPv4 ipv4Reply = new IPv4();
265 ipv4Reply.setSourceAddress(Ip4Address.valueOf(osSubnet.getGateway()).toInt());
266 ipv4Reply.setDestinationAddress(reqInstPort.ipAddress().getIp4Address().toInt());
267 ipv4Reply.setTtl(PACKET_TTL);
268
269 UDP udpRequest = (UDP) ipv4Request.getPayload();
270 UDP udpReply = new UDP();
271 udpReply.setSourcePort((byte) UDP.DHCP_SERVER_PORT);
272 udpReply.setDestinationPort((byte) UDP.DHCP_CLIENT_PORT);
273
274 DHCP dhcpRequest = (DHCP) udpRequest.getPayload();
275 DHCP dhcpReply = buildDhcpReply(
276 dhcpRequest,
277 packetType,
278 reqInstPort.ipAddress().getIp4Address(),
279 osSubnet);
280
281 udpReply.setPayload(dhcpReply);
282 ipv4Reply.setPayload(udpReply);
283 ethReply.setPayload(ipv4Reply);
284
285 return ethReply;
286 }
287
288 private void sendReply(PacketContext context, Ethernet ethReply) {
289 if (ethReply == null) {
290 return;
291 }
292 ConnectPoint srcPoint = context.inPacket().receivedFrom();
293 TrafficTreatment treatment = DefaultTrafficTreatment
294 .builder()
295 .setOutput(srcPoint.port())
296 .build();
297
298 packetService.emit(new DefaultOutboundPacket(
299 srcPoint.deviceId(),
300 treatment,
301 ByteBuffer.wrap(ethReply.serialize())));
302 context.block();
303 }
304
305 private DHCP buildDhcpReply(DHCP request, byte msgType, Ip4Address yourIp,
306 Subnet osSubnet) {
307 Ip4Address gatewayIp = Ip4Address.valueOf(osSubnet.getGateway());
308 int subnetPrefixLen = IpPrefix.valueOf(osSubnet.getCidr()).prefixLength();
309
310 DHCP dhcpReply = new DHCP();
311 dhcpReply.setOpCode(DHCP.OPCODE_REPLY);
312 dhcpReply.setHardwareType(DHCP.HWTYPE_ETHERNET);
313 dhcpReply.setHardwareAddressLength((byte) 6);
314 dhcpReply.setTransactionId(request.getTransactionId());
315 dhcpReply.setFlags(request.getFlags());
316 dhcpReply.setYourIPAddress(yourIp.toInt());
317 dhcpReply.setServerIPAddress(gatewayIp.toInt());
318 dhcpReply.setClientHardwareAddress(request.getClientHardwareAddress());
319
Yi Tsengc7403c22017-06-19 16:23:22 -0700320 List<DhcpOption> options = Lists.newArrayList();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900321 // message type
Yi Tsengc7403c22017-06-19 16:23:22 -0700322 DhcpOption option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900323 option.setCode(OptionCode_MessageType.getValue());
324 option.setLength((byte) 1);
325 byte[] optionData = {msgType};
326 option.setData(optionData);
327 options.add(option);
328
329 // server identifier
Yi Tsengc7403c22017-06-19 16:23:22 -0700330 option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900331 option.setCode(OptionCode_DHCPServerIp.getValue());
332 option.setLength((byte) 4);
333 option.setData(gatewayIp.toOctets());
334 options.add(option);
335
336 // lease time
Yi Tsengc7403c22017-06-19 16:23:22 -0700337 option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900338 option.setCode(OptionCode_LeaseTime.getValue());
339 option.setLength((byte) 4);
340 option.setData(DHCP_DATA_LEASE_INFINITE);
341 options.add(option);
342
343 // subnet mask
344 Ip4Address subnetMask = Ip4Address.makeMaskPrefix(subnetPrefixLen);
Yi Tsengc7403c22017-06-19 16:23:22 -0700345 option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900346 option.setCode(OptionCode_SubnetMask.getValue());
347 option.setLength((byte) 4);
348 option.setData(subnetMask.toOctets());
349 options.add(option);
350
351 // broadcast address
352 Ip4Address broadcast = Ip4Address.makeMaskedAddress(yourIp, subnetPrefixLen);
Yi Tsengc7403c22017-06-19 16:23:22 -0700353 option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900354 option.setCode(OptionCode_BroadcastAddress.getValue());
355 option.setLength((byte) 4);
356 option.setData(broadcast.toOctets());
357 options.add(option);
358
359 // domain server
Yi Tsengc7403c22017-06-19 16:23:22 -0700360 option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900361 option.setCode(OptionCode_DomainServer.getValue());
362 option.setLength((byte) 4);
363 option.setData(DEFAULT_DNS.toOctets());
364 options.add(option);
365
366 // TODO fix MTU value to be configurable
Yi Tsengc7403c22017-06-19 16:23:22 -0700367 option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900368 option.setCode(DHCP_OPTION_MTU);
369 option.setLength((byte) 2);
370 option.setData(DHCP_DATA_MTU_DEFAULT);
371 options.add(option);
372
373 // router address
Yi Tsengc7403c22017-06-19 16:23:22 -0700374 option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900375 option.setCode(OptionCode_RouterAddress.getValue());
376 option.setLength((byte) 4);
377 option.setData(gatewayIp.toOctets());
378 options.add(option);
379
380 // end option
Yi Tsengc7403c22017-06-19 16:23:22 -0700381 option = new DhcpOption();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900382 option.setCode(OptionCode_END.getValue());
383 option.setLength((byte) 1);
384 options.add(option);
385
386 dhcpReply.setOptions(options);
387 return dhcpReply;
388 }
389 }
390}