blob: d87326d805b4e1bf4e32a741ee1145596d0546ba [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;
28import org.onlab.packet.DHCPOption;
29import org.onlab.packet.DHCPPacketType;
30import org.onlab.packet.Ethernet;
31import org.onlab.packet.IPv4;
32import org.onlab.packet.Ip4Address;
33import org.onlab.packet.IpPrefix;
34import org.onlab.packet.MacAddress;
35import org.onlab.packet.TpPort;
36import org.onlab.packet.UDP;
37import org.onlab.util.Tools;
38import org.onosproject.cfg.ComponentConfigService;
39import org.onosproject.core.ApplicationId;
40import org.onosproject.core.CoreService;
41import org.onosproject.net.ConnectPoint;
42import 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;
48import org.onosproject.net.packet.PacketPriority;
49import org.onosproject.net.packet.PacketProcessor;
50import org.onosproject.net.packet.PacketService;
51import org.onosproject.openstacknetworking.api.Constants;
52import org.onosproject.openstacknetworking.api.InstancePort;
53import org.onosproject.openstacknetworking.api.InstancePortService;
54import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
55import org.openstack4j.model.network.IP;
56import org.openstack4j.model.network.Port;
57import org.openstack4j.model.network.Subnet;
58import org.osgi.service.component.ComponentContext;
59import org.slf4j.Logger;
60
61import java.nio.ByteBuffer;
62import java.util.Dictionary;
63import java.util.List;
64
65import static org.onlab.packet.DHCP.DHCPOptionCode.*;
66import static org.onlab.packet.DHCPPacketType.DHCPACK;
67import static org.onlab.packet.DHCPPacketType.DHCPOFFER;
68import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_GATEWAY_MAC_STR;
69import static org.slf4j.LoggerFactory.getLogger;
70
71/**
72 * Handles DHCP requests for the virtual instances.
73 */
74@Component(immediate = true)
75public class OpenstackSwitchingDhcpHandler {
76 protected final Logger log = getLogger(getClass());
77
78 private static final String DHCP_SERVER_MAC = "dhcpServerMac";
79 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();
85 private static final byte[] DHCP_DATA_MTU_DEFAULT =
86 ByteBuffer.allocate(2).putShort((short) 1450).array();
87
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
107 private final PacketProcessor packetProcessor = new InternalPacketProcessor();
108
109 private ApplicationId appId;
110
111 @Activate
112 protected void activate() {
113 appId = coreService.registerApplication(Constants.OPENSTACK_NETWORKING_APP_ID);
114 configService.registerProperties(getClass());
115 packetService.addProcessor(packetProcessor, PacketProcessor.director(0));
116 requestPackets();
117
118 log.info("Started");
119 }
120
121 @Deactivate
122 protected void deactivate() {
123 cancelPackets();
124 packetService.removeProcessor(packetProcessor);
125 configService.unregisterProperties(getClass(), false);
126
127 log.info("Stopped");
128 }
129
130 @Modified
131 protected void modified(ComponentContext context) {
132 Dictionary<?, ?> properties = context.getProperties();
133 String updatedMac;
134
135 updatedMac = Tools.get(properties, DHCP_SERVER_MAC);
136 if (!Strings.isNullOrEmpty(updatedMac) && !updatedMac.equals(dhcpServerMac)) {
137 dhcpServerMac = updatedMac;
138 }
139
140 log.info("Modified");
141 }
142
143 private void requestPackets() {
144 TrafficSelector selector = DefaultTrafficSelector.builder()
145 .matchEthType(Ethernet.TYPE_IPV4)
146 .matchIPProtocol(IPv4.PROTOCOL_UDP)
147 .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
148 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
149 .build();
150 packetService.requestPackets(selector, PacketPriority.CONTROL, appId);
151 }
152
153 private void cancelPackets() {
154 TrafficSelector selector = DefaultTrafficSelector.builder()
155 .matchEthType(Ethernet.TYPE_IPV4)
156 .matchIPProtocol(IPv4.PROTOCOL_UDP)
157 .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
158 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
159 .build();
160 packetService.cancelPackets(selector, PacketPriority.CONTROL, appId);
161 }
162
163 private class InternalPacketProcessor implements PacketProcessor {
164
165 @Override
166 public void process(PacketContext context) {
167 if (context.isHandled()) {
168 return;
169 }
170
171 Ethernet ethPacket = context.inPacket().parsed();
172 if (ethPacket == null || ethPacket.getEtherType() != Ethernet.TYPE_IPV4) {
173 return;
174 }
175 IPv4 ipv4Packet = (IPv4) ethPacket.getPayload();
176 if (ipv4Packet.getProtocol() != IPv4.PROTOCOL_UDP) {
177 return;
178 }
179 UDP udpPacket = (UDP) ipv4Packet.getPayload();
180 if (udpPacket.getDestinationPort() != UDP.DHCP_SERVER_PORT ||
181 udpPacket.getSourcePort() != UDP.DHCP_CLIENT_PORT) {
182 return;
183 }
184
185 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
186 processDhcp(context, dhcpPacket);
187 }
188
189 private void processDhcp(PacketContext context, DHCP dhcpPacket) {
190 if (dhcpPacket == null) {
191 log.trace("DHCP packet without payload received, do nothing");
192 return;
193 }
194
195 DHCPPacketType inPacketType = getPacketType(dhcpPacket);
196 if (inPacketType == null || dhcpPacket.getClientHardwareAddress() == null) {
197 log.trace("Malformed DHCP packet received, ignore it");
198 return;
199 }
200
201 MacAddress clientMac = MacAddress.valueOf(dhcpPacket.getClientHardwareAddress());
202 InstancePort reqInstPort = instancePortService.instancePort(clientMac);
203 if (reqInstPort == null) {
204 log.trace("Failed to find host(MAC:{})", clientMac);
205 return;
206 }
207 Ethernet ethPacket = context.inPacket().parsed();
208 switch (inPacketType) {
209 case DHCPDISCOVER:
210 log.trace("DHCP DISCOVER received from {}", clientMac);
211 Ethernet discoverReply = buildReply(
212 ethPacket,
213 (byte) DHCPOFFER.getValue(),
214 reqInstPort);
215 sendReply(context, discoverReply);
216 log.trace("DHCP OFFER({}) is sent for {}",
217 reqInstPort.ipAddress(), clientMac);
218 break;
219 case DHCPREQUEST:
220 log.trace("DHCP REQUEST received from {}", clientMac);
221 Ethernet requestReply = buildReply(
222 ethPacket,
223 (byte) DHCPACK.getValue(),
224 reqInstPort);
225 sendReply(context, requestReply);
226 log.trace("DHCP ACK({}) is sent for {}",
227 reqInstPort.ipAddress(), clientMac);
228 break;
229 case DHCPRELEASE:
230 log.trace("DHCP RELEASE received from {}", clientMac);
231 // do nothing
232 break;
233 default:
234 break;
235 }
236 }
237
238 private DHCPPacketType getPacketType(DHCP dhcpPacket) {
239 DHCPOption optType = dhcpPacket.getOption(OptionCode_MessageType);
240 if (optType == null) {
241 log.trace("DHCP packet with no message type, ignore it");
242 return null;
243 }
244
245 DHCPPacketType inPacketType = DHCPPacketType.getType(optType.getData()[0]);
246 if (inPacketType == null) {
247 log.trace("DHCP packet with no packet type, ignore it");
248 }
249 return inPacketType;
250 }
251
252 private Ethernet buildReply(Ethernet ethRequest, byte packetType,
253 InstancePort reqInstPort) {
254 Port osPort = osNetworkService.port(reqInstPort.portId());
255 // pick one IP address to make a reply
256 IP fixedIp = osPort.getFixedIps().stream().findFirst().get();
257 Subnet osSubnet = osNetworkService.subnet(fixedIp.getSubnetId());
258
259 Ethernet ethReply = new Ethernet();
260 ethReply.setSourceMACAddress(dhcpServerMac);
261 ethReply.setDestinationMACAddress(ethRequest.getSourceMAC());
262 ethReply.setEtherType(Ethernet.TYPE_IPV4);
263
264 IPv4 ipv4Request = (IPv4) ethRequest.getPayload();
265 IPv4 ipv4Reply = new IPv4();
266 ipv4Reply.setSourceAddress(Ip4Address.valueOf(osSubnet.getGateway()).toInt());
267 ipv4Reply.setDestinationAddress(reqInstPort.ipAddress().getIp4Address().toInt());
268 ipv4Reply.setTtl(PACKET_TTL);
269
270 UDP udpRequest = (UDP) ipv4Request.getPayload();
271 UDP udpReply = new UDP();
272 udpReply.setSourcePort((byte) UDP.DHCP_SERVER_PORT);
273 udpReply.setDestinationPort((byte) UDP.DHCP_CLIENT_PORT);
274
275 DHCP dhcpRequest = (DHCP) udpRequest.getPayload();
276 DHCP dhcpReply = buildDhcpReply(
277 dhcpRequest,
278 packetType,
279 reqInstPort.ipAddress().getIp4Address(),
280 osSubnet);
281
282 udpReply.setPayload(dhcpReply);
283 ipv4Reply.setPayload(udpReply);
284 ethReply.setPayload(ipv4Reply);
285
286 return ethReply;
287 }
288
289 private void sendReply(PacketContext context, Ethernet ethReply) {
290 if (ethReply == null) {
291 return;
292 }
293 ConnectPoint srcPoint = context.inPacket().receivedFrom();
294 TrafficTreatment treatment = DefaultTrafficTreatment
295 .builder()
296 .setOutput(srcPoint.port())
297 .build();
298
299 packetService.emit(new DefaultOutboundPacket(
300 srcPoint.deviceId(),
301 treatment,
302 ByteBuffer.wrap(ethReply.serialize())));
303 context.block();
304 }
305
306 private DHCP buildDhcpReply(DHCP request, byte msgType, Ip4Address yourIp,
307 Subnet osSubnet) {
308 Ip4Address gatewayIp = Ip4Address.valueOf(osSubnet.getGateway());
309 int subnetPrefixLen = IpPrefix.valueOf(osSubnet.getCidr()).prefixLength();
310
311 DHCP dhcpReply = new DHCP();
312 dhcpReply.setOpCode(DHCP.OPCODE_REPLY);
313 dhcpReply.setHardwareType(DHCP.HWTYPE_ETHERNET);
314 dhcpReply.setHardwareAddressLength((byte) 6);
315 dhcpReply.setTransactionId(request.getTransactionId());
316 dhcpReply.setFlags(request.getFlags());
317 dhcpReply.setYourIPAddress(yourIp.toInt());
318 dhcpReply.setServerIPAddress(gatewayIp.toInt());
319 dhcpReply.setClientHardwareAddress(request.getClientHardwareAddress());
320
321 List<DHCPOption> options = Lists.newArrayList();
322 // message type
323 DHCPOption option = new DHCPOption();
324 option.setCode(OptionCode_MessageType.getValue());
325 option.setLength((byte) 1);
326 byte[] optionData = {msgType};
327 option.setData(optionData);
328 options.add(option);
329
330 // server identifier
331 option = new DHCPOption();
332 option.setCode(OptionCode_DHCPServerIp.getValue());
333 option.setLength((byte) 4);
334 option.setData(gatewayIp.toOctets());
335 options.add(option);
336
337 // lease time
338 option = new DHCPOption();
339 option.setCode(OptionCode_LeaseTime.getValue());
340 option.setLength((byte) 4);
341 option.setData(DHCP_DATA_LEASE_INFINITE);
342 options.add(option);
343
344 // subnet mask
345 Ip4Address subnetMask = Ip4Address.makeMaskPrefix(subnetPrefixLen);
346 option = new DHCPOption();
347 option.setCode(OptionCode_SubnetMask.getValue());
348 option.setLength((byte) 4);
349 option.setData(subnetMask.toOctets());
350 options.add(option);
351
352 // broadcast address
353 Ip4Address broadcast = Ip4Address.makeMaskedAddress(yourIp, subnetPrefixLen);
354 option = new DHCPOption();
355 option.setCode(OptionCode_BroadcastAddress.getValue());
356 option.setLength((byte) 4);
357 option.setData(broadcast.toOctets());
358 options.add(option);
359
360 // domain server
361 option = new DHCPOption();
362 option.setCode(OptionCode_DomainServer.getValue());
363 option.setLength((byte) 4);
364 option.setData(DEFAULT_DNS.toOctets());
365 options.add(option);
366
367 // TODO fix MTU value to be configurable
368 option = new DHCPOption();
369 option.setCode(DHCP_OPTION_MTU);
370 option.setLength((byte) 2);
371 option.setData(DHCP_DATA_MTU_DEFAULT);
372 options.add(option);
373
374 // router address
375 option = new DHCPOption();
376 option.setCode(OptionCode_RouterAddress.getValue());
377 option.setLength((byte) 4);
378 option.setData(gatewayIp.toOctets());
379 options.add(option);
380
381 // end option
382 option = new DHCPOption();
383 option.setCode(OptionCode_END.getValue());
384 option.setLength((byte) 1);
385 options.add(option);
386
387 dhcpReply.setOptions(options);
388 return dhcpReply;
389 }
390 }
391}