blob: ba5a4afe4354b5eef4462b09561229ce1103239e [file] [log] [blame]
Hyunsun Moon28b358a2016-11-28 13:23:05 -08001/*
2 * Copyright 2016-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.opencord.cordvtn.impl;
17
18import com.google.common.collect.Lists;
19import org.apache.felix.scr.annotations.Activate;
20import org.apache.felix.scr.annotations.Component;
21import org.apache.felix.scr.annotations.Deactivate;
22import org.apache.felix.scr.annotations.Reference;
23import org.apache.felix.scr.annotations.ReferenceCardinality;
24import org.onlab.packet.DHCP;
25import org.onlab.packet.DHCPOption;
26import org.onlab.packet.DHCPPacketType;
27import org.onlab.packet.Ethernet;
28import org.onlab.packet.IPv4;
29import org.onlab.packet.Ip4Address;
30import org.onlab.packet.MacAddress;
31import org.onlab.packet.TpPort;
32import org.onlab.packet.UDP;
33import org.onosproject.core.ApplicationId;
34import org.onosproject.core.CoreService;
35import org.onosproject.net.ConnectPoint;
36import org.onosproject.net.Host;
37import org.onosproject.net.HostId;
38import org.onosproject.net.config.NetworkConfigEvent;
39import org.onosproject.net.config.NetworkConfigListener;
40import org.onosproject.net.config.NetworkConfigRegistry;
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.host.HostService;
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.opencord.cordvtn.api.Constants;
52import org.opencord.cordvtn.api.config.CordVtnConfig;
53import org.opencord.cordvtn.api.core.CordVtnService;
54import org.opencord.cordvtn.api.instance.Instance;
55import org.opencord.cordvtn.api.net.VtnNetwork;
56import org.slf4j.Logger;
57
58import java.nio.ByteBuffer;
59import java.util.List;
60
61import static com.google.common.base.Preconditions.checkArgument;
62import static org.onlab.packet.DHCP.DHCPOptionCode.*;
63import static org.onlab.packet.DHCPPacketType.DHCPACK;
64import static org.onlab.packet.DHCPPacketType.DHCPOFFER;
65import static org.opencord.cordvtn.api.net.ServiceNetwork.ServiceNetworkType.MANAGEMENT_HOST;
66import static org.opencord.cordvtn.api.net.ServiceNetwork.ServiceNetworkType.MANAGEMENT_LOCAL;
67import static org.slf4j.LoggerFactory.getLogger;
68
69/**
70 * Handles DHCP requests for the virtual instances.
71 */
72@Component(immediate = true)
73public class CordVtnDhcpProxy {
74
75 protected final Logger log = getLogger(getClass());
76
77 private static final Ip4Address DEFAULT_DNS = Ip4Address.valueOf("8.8.8.8");
78 private static final byte PACKET_TTL = (byte) 127;
79 // TODO add MTU option code to ONOS DHCP implementation and remove this
80 private static final byte DHCP_OPTION_MTU = (byte) 26;
81 private static final byte[] DHCP_DATA_LEASE_INFINITE =
82 ByteBuffer.allocate(4).putInt(-1).array();
83 private static final byte[] DHCP_DATA_MTU_DEFAULT =
84 ByteBuffer.allocate(2).putShort((short) 1450).array();
85
86 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
87 protected CoreService coreService;
88
89 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
90 protected NetworkConfigRegistry configRegistry;
91
92 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
93 protected PacketService packetService;
94
95 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
96 protected HostService hostService;
97
98 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
99 protected CordVtnService cordVtnService;
100
101 private final PacketProcessor packetProcessor = new InternalPacketProcessor();
102 private final NetworkConfigListener configListener = new InternalConfigListener();
103
104 private ApplicationId appId;
105 private MacAddress dhcpServerMac = MacAddress.NONE;
106
107 @Activate
108 protected void activate() {
109 appId = coreService.registerApplication(Constants.CORDVTN_APP_ID);
110 configRegistry.addListener(configListener);
111 readConfiguration();
112
113 packetService.addProcessor(packetProcessor, PacketProcessor.director(0));
114 requestPackets();
115 log.info("Started");
116 }
117
118 @Deactivate
119 protected void deactivate() {
120 packetService.removeProcessor(packetProcessor);
121 cancelPackets();
122 log.info("Stopped");
123 }
124
125 // TODO implement public method forceRenew
126
127 private void requestPackets() {
128 TrafficSelector selector = DefaultTrafficSelector.builder()
129 .matchEthType(Ethernet.TYPE_IPV4)
130 .matchIPProtocol(IPv4.PROTOCOL_UDP)
131 .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
132 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
133 .build();
134 packetService.requestPackets(selector, PacketPriority.CONTROL, appId);
135 }
136
137 private void cancelPackets() {
138 TrafficSelector selector = DefaultTrafficSelector.builder()
139 .matchEthType(Ethernet.TYPE_IPV4)
140 .matchIPProtocol(IPv4.PROTOCOL_UDP)
141 .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
142 .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
143 .build();
144 packetService.cancelPackets(selector, PacketPriority.CONTROL, appId);
145 }
146
147 private class InternalPacketProcessor implements PacketProcessor {
148
149 @Override
150 public void process(PacketContext context) {
151 if (context.isHandled()) {
152 return;
153 }
154
155 Ethernet ethPacket = context.inPacket().parsed();
156 if (ethPacket == null || ethPacket.getEtherType() != Ethernet.TYPE_IPV4) {
157 return;
158 }
159 IPv4 ipv4Packet = (IPv4) ethPacket.getPayload();
160 if (ipv4Packet.getProtocol() != IPv4.PROTOCOL_UDP) {
161 return;
162 }
163 UDP udpPacket = (UDP) ipv4Packet.getPayload();
164 if (udpPacket.getDestinationPort() != UDP.DHCP_SERVER_PORT ||
165 udpPacket.getSourcePort() != UDP.DHCP_CLIENT_PORT) {
166 return;
167 }
168
169 DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
170 processDhcp(context, dhcpPacket);
171 }
172
173 private void processDhcp(PacketContext context, DHCP dhcpPacket) {
174 if (dhcpPacket == null) {
175 log.trace("DHCP packet without payload received, do nothing");
176 return;
177 }
178
179 DHCPPacketType inPacketType = getPacketType(dhcpPacket);
180 if (inPacketType == null || dhcpPacket.getClientHardwareAddress() == null) {
181 log.trace("Malformed DHCP packet received, ignore it");
182 return;
183 }
184
185 MacAddress clientMac = MacAddress.valueOf(dhcpPacket.getClientHardwareAddress());
186 Host reqHost = hostService.getHost(HostId.hostId(clientMac));
187 if (reqHost == null) {
188 log.debug("DHCP packet from unknown host, ignore it");
189 return;
190 }
191
192 Instance reqInstance = Instance.of(reqHost);
193 Ethernet ethPacket = context.inPacket().parsed();
194 switch (inPacketType) {
195 case DHCPDISCOVER:
196 log.trace("DHCP DISCOVER received from {}", reqHost.id());
197 Ethernet discoverReply = buildReply(
198 ethPacket,
199 (byte) DHCPOFFER.getValue(),
200 reqInstance);
201 sendReply(context, discoverReply);
202 log.trace("DHCP OFFER({}) is sent to {}",
203 reqInstance.ipAddress(), reqHost.id());
204 break;
205 case DHCPREQUEST:
206 log.trace("DHCP REQUEST received from {}", reqHost.id());
207 Ethernet requestReply = buildReply(
208 ethPacket,
209 (byte) DHCPACK.getValue(),
210 reqInstance);
211 sendReply(context, requestReply);
212 log.trace("DHCP ACK({}) is sent to {}",
213 reqInstance.ipAddress(), reqHost.id());
214 break;
215 case DHCPRELEASE:
216 log.trace("DHCP RELEASE received from {}", reqHost.id());
217 // do nothing
218 break;
219 default:
220 break;
221 }
222 }
223
224 private DHCPPacketType getPacketType(DHCP dhcpPacket) {
225 DHCPOption optType = dhcpPacket.getOption(OptionCode_MessageType);
226 if (optType == null) {
227 log.trace("DHCP packet with no message type, ignore it");
228 return null;
229 }
230
231 DHCPPacketType inPacketType = DHCPPacketType.getType(optType.getData()[0]);
232 if (inPacketType == null) {
233 log.trace("DHCP packet with no packet type, ignore it");
234 }
235 return inPacketType;
236 }
237
238 private Ethernet buildReply(Ethernet ethRequest, byte packetType,
239 Instance reqInstance) {
240 checkArgument(!dhcpServerMac.equals(MacAddress.NONE),
241 "DHCP server MAC is not set");
242
243 VtnNetwork vtnNet = cordVtnService.vtnNetwork(reqInstance.netId());
244 Ip4Address serverIp = vtnNet.serviceIp().getIp4Address();
245
246 Ethernet ethReply = new Ethernet();
247 ethReply.setSourceMACAddress(dhcpServerMac);
248 ethReply.setDestinationMACAddress(ethRequest.getSourceMAC());
249 ethReply.setEtherType(Ethernet.TYPE_IPV4);
250
251 IPv4 ipv4Request = (IPv4) ethRequest.getPayload();
252 IPv4 ipv4Reply = new IPv4();
253 ipv4Reply.setSourceAddress(serverIp.toInt());
254 ipv4Reply.setDestinationAddress(reqInstance.ipAddress().toInt());
255 ipv4Reply.setTtl(PACKET_TTL);
256
257 UDP udpRequest = (UDP) ipv4Request.getPayload();
258 UDP udpReply = new UDP();
259 udpReply.setSourcePort((byte) UDP.DHCP_SERVER_PORT);
260 udpReply.setDestinationPort((byte) UDP.DHCP_CLIENT_PORT);
261
262 DHCP dhcpRequest = (DHCP) udpRequest.getPayload();
263 DHCP dhcpReply = buildDhcpReply(
264 dhcpRequest, packetType, reqInstance.ipAddress(), vtnNet);
265
266 udpReply.setPayload(dhcpReply);
267 ipv4Reply.setPayload(udpReply);
268 ethReply.setPayload(ipv4Reply);
269
270 return ethReply;
271 }
272
273 private void sendReply(PacketContext context, Ethernet ethReply) {
274 if (ethReply == null) {
275 return;
276 }
277 ConnectPoint srcPoint = context.inPacket().receivedFrom();
278 TrafficTreatment treatment = DefaultTrafficTreatment
279 .builder()
280 .setOutput(srcPoint.port())
281 .build();
282
283 packetService.emit(new DefaultOutboundPacket(
284 srcPoint.deviceId(),
285 treatment,
286 ByteBuffer.wrap(ethReply.serialize())));
287 context.block();
288 }
289
290 private DHCP buildDhcpReply(DHCP request, byte msgType, Ip4Address yourIp,
291 VtnNetwork vtnNet) {
292 Ip4Address serverIp = vtnNet.serviceIp().getIp4Address();
293 int subnetPrefixLen = vtnNet.subnet().prefixLength();
294
295 DHCP dhcpReply = new DHCP();
296 dhcpReply.setOpCode(DHCP.OPCODE_REPLY);
297 dhcpReply.setHardwareType(DHCP.HWTYPE_ETHERNET);
298 dhcpReply.setHardwareAddressLength((byte) 6);
299 dhcpReply.setTransactionId(request.getTransactionId());
300 dhcpReply.setFlags(request.getFlags());
301 dhcpReply.setYourIPAddress(yourIp.toInt());
302 dhcpReply.setServerIPAddress(serverIp.toInt());
303 dhcpReply.setClientHardwareAddress(request.getClientHardwareAddress());
304
305 List<DHCPOption> options = Lists.newArrayList();
306 // message type
307 DHCPOption option = new DHCPOption();
308 option.setCode(OptionCode_MessageType.getValue());
309 option.setLength((byte) 1);
310 byte[] optionData = {msgType};
311 option.setData(optionData);
312 options.add(option);
313
314 // server identifier
315 option = new DHCPOption();
316 option.setCode(OptionCode_DHCPServerIp.getValue());
317 option.setLength((byte) 4);
318 option.setData(serverIp.toOctets());
319 options.add(option);
320
321 // lease time
322 option = new DHCPOption();
323 option.setCode(OptionCode_LeaseTime.getValue());
324 option.setLength((byte) 4);
325 option.setData(DHCP_DATA_LEASE_INFINITE);
326 options.add(option);
327
328 // subnet mask
329 Ip4Address subnetMask = Ip4Address.makeMaskPrefix(subnetPrefixLen);
330 option = new DHCPOption();
331 option.setCode(OptionCode_SubnetMask.getValue());
332 option.setLength((byte) 4);
333 option.setData(subnetMask.toOctets());
334 options.add(option);
335
336 // broadcast address
337 Ip4Address broadcast = Ip4Address.makeMaskedAddress(yourIp, subnetPrefixLen);
338 option = new DHCPOption();
339 option.setCode(OptionCode_BroadcastAddress.getValue());
340 option.setLength((byte) 4);
341 option.setData(broadcast.toOctets());
342 options.add(option);
343
344 option = new DHCPOption();
345 option.setCode(OptionCode_DomainServer.getValue());
346 option.setLength((byte) 4);
347 option.setData(DEFAULT_DNS.toOctets());
348 options.add(option);
349
350 // TODO fix MTU value to be configurable
351 option = new DHCPOption();
352 option.setCode(DHCP_OPTION_MTU);
353 option.setLength((byte) 2);
354 option.setData(DHCP_DATA_MTU_DEFAULT);
355 options.add(option);
356
357 // router address
358 if (vtnNet.type() != MANAGEMENT_LOCAL && vtnNet.type() != MANAGEMENT_HOST) {
359 option = new DHCPOption();
360 option.setCode(OptionCode_RouterAddress.getValue());
361 option.setLength((byte) 4);
362 option.setData(serverIp.toOctets());
363 options.add(option);
364 }
365
366 // TODO add host route option if network has service dependency
367
368 // end option
369 option = new DHCPOption();
370 option.setCode(OptionCode_END.getValue());
371 option.setLength((byte) 1);
372 options.add(option);
373
374 dhcpReply.setOptions(options);
375 return dhcpReply;
376 }
377 }
378
379 private void readConfiguration() {
380 CordVtnConfig config = configRegistry.getConfig(appId, CordVtnConfig.class);
381 if (config == null) {
382 log.debug("No configuration found");
383 return;
384 }
385 dhcpServerMac = config.privateGatewayMac();
386 log.debug("Added DHCP server MAC {}", dhcpServerMac);
387 }
388
389 private class InternalConfigListener implements NetworkConfigListener {
390
391 @Override
392 public void event(NetworkConfigEvent event) {
393 if (!event.configClass().equals(CordVtnConfig.class)) {
394 return;
395 }
396
397 switch (event.type()) {
398 case CONFIG_ADDED:
399 case CONFIG_UPDATED:
400 readConfiguration();
401 break;
402 default:
403 break;
404 }
405 }
406 }
407}