blob: eb08cc311c2202e232449476efc7cedfffdf7d47 [file] [log] [blame]
Hyunsun Moon44aac662017-02-18 02:07:01 +09001/*
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.onosproject.openstacknetworking.impl;
17
18import org.apache.felix.scr.annotations.Activate;
19import org.apache.felix.scr.annotations.Component;
20import org.apache.felix.scr.annotations.Deactivate;
21import org.apache.felix.scr.annotations.Reference;
22import org.apache.felix.scr.annotations.ReferenceCardinality;
23import org.onlab.packet.Ethernet;
24import org.onlab.packet.IPv4;
25import org.onlab.packet.IpAddress;
26import org.onlab.packet.IpPrefix;
27import org.onlab.packet.MacAddress;
28import org.onlab.packet.TCP;
29import org.onlab.packet.TpPort;
30import org.onlab.packet.UDP;
31import org.onlab.util.KryoNamespace;
32import org.onosproject.core.ApplicationId;
33import org.onosproject.core.CoreService;
34import org.onosproject.net.DeviceId;
35import org.onosproject.net.device.DeviceService;
36import org.onosproject.net.flow.DefaultTrafficSelector;
37import org.onosproject.net.flow.DefaultTrafficTreatment;
38import org.onosproject.net.flow.TrafficSelector;
39import org.onosproject.net.flow.TrafficTreatment;
40import org.onosproject.net.flowobjective.DefaultForwardingObjective;
41import org.onosproject.net.flowobjective.FlowObjectiveService;
42import org.onosproject.net.flowobjective.ForwardingObjective;
43import org.onosproject.net.packet.DefaultOutboundPacket;
44import org.onosproject.net.packet.InboundPacket;
45import org.onosproject.net.packet.PacketContext;
46import org.onosproject.net.packet.PacketProcessor;
47import org.onosproject.net.packet.PacketService;
48import org.onosproject.openstacknetworking.api.InstancePort;
49import org.onosproject.openstacknetworking.api.InstancePortService;
50import org.onosproject.openstacknetworking.api.OpenstackRouterService;
51import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
52import org.onosproject.openstacknode.OpenstackNodeService;
Hyunsun Moon44aac662017-02-18 02:07:01 +090053import org.onosproject.store.serializers.KryoNamespaces;
54import org.onosproject.store.service.ConsistentMap;
daniel park0bc7fdb2017-03-13 14:20:08 +090055import org.onosproject.store.service.DistributedSet;
Hyunsun Moon44aac662017-02-18 02:07:01 +090056import org.onosproject.store.service.Serializer;
57import org.onosproject.store.service.StorageService;
58import org.openstack4j.model.network.ExternalGateway;
59import org.openstack4j.model.network.IP;
60import org.openstack4j.model.network.Network;
61import org.openstack4j.model.network.Port;
62import org.openstack4j.model.network.Router;
63import org.openstack4j.model.network.RouterInterface;
64import org.openstack4j.model.network.Subnet;
65import org.slf4j.Logger;
66
67import java.nio.ByteBuffer;
68import java.util.Objects;
69import java.util.concurrent.ExecutorService;
70
71import static java.util.concurrent.Executors.newSingleThreadExecutor;
72import static org.onlab.util.Tools.groupedThreads;
73import static org.onosproject.openstacknetworking.api.Constants.*;
74import static org.slf4j.LoggerFactory.getLogger;
75
76/**
77 * Handle packets needs SNAT.
78 */
79@Component(immediate = true)
80public class OpenstackRoutingSnatHandler {
81
82 private final Logger log = getLogger(getClass());
83
84 private static final String ERR_PACKETIN = "Failed to handle packet in: ";
85 private static final int TIME_OUT_SNAT_RULE = 120;
daniel park0bc7fdb2017-03-13 14:20:08 +090086 private static final long TIME_OUT_SNAT_PORT_MS = 120 * 1000;
Hyunsun Moon44aac662017-02-18 02:07:01 +090087 private static final int TP_PORT_MINIMUM_NUM = 1024;
88 private static final int TP_PORT_MAXIMUM_NUM = 65535;
89
90 private static final KryoNamespace.Builder NUMBER_SERIALIZER = KryoNamespace.newBuilder()
91 .register(KryoNamespaces.API);
92
93 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
94 protected CoreService coreService;
95
96 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
97 protected PacketService packetService;
98
99 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
100 protected StorageService storageService;
101
102 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
103 protected FlowObjectiveService flowObjectiveService;
104
105 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
106 protected DeviceService deviceService;
107
108 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
109 protected InstancePortService instancePortService;
110
111 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
112 protected OpenstackNodeService osNodeService;
113
114 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
115 protected OpenstackNetworkService osNetworkService;
116
117 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
118 protected OpenstackRouterService osRouterService;
119
Hyunsun Moon44aac662017-02-18 02:07:01 +0900120 private final ExecutorService eventExecutor = newSingleThreadExecutor(
121 groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
122 private final InternalPacketProcessor packetProcessor = new InternalPacketProcessor();
123
daniel park0bc7fdb2017-03-13 14:20:08 +0900124 private ConsistentMap<Integer, Long> allocatedPortNumMap;
125 private DistributedSet<Integer> unUsedPortNumSet;
Hyunsun Moon44aac662017-02-18 02:07:01 +0900126 private ApplicationId appId;
127
128 @Activate
129 protected void activate() {
130 appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
daniel park0bc7fdb2017-03-13 14:20:08 +0900131
132 allocatedPortNumMap = storageService.<Integer, Long>consistentMapBuilder()
Hyunsun Moon44aac662017-02-18 02:07:01 +0900133 .withSerializer(Serializer.using(NUMBER_SERIALIZER.build()))
daniel park0bc7fdb2017-03-13 14:20:08 +0900134 .withName("openstackrouting-allocatedportnummap")
Hyunsun Moon44aac662017-02-18 02:07:01 +0900135 .withApplicationId(appId)
136 .build();
137
daniel park0bc7fdb2017-03-13 14:20:08 +0900138 unUsedPortNumSet = storageService.<Integer>setBuilder()
139 .withName("openstackrouting-unusedportnumset")
140 .withSerializer(Serializer.using(KryoNamespaces.API))
141 .build()
142 .asDistributedSet();
143
144 initializeUnusedPortNumSet();
145
Hyunsun Moon44aac662017-02-18 02:07:01 +0900146 packetService.addProcessor(packetProcessor, PacketProcessor.director(1));
147 log.info("Started");
148 }
149
daniel park0bc7fdb2017-03-13 14:20:08 +0900150 private void initializeUnusedPortNumSet() {
151 for (int i = TP_PORT_MINIMUM_NUM; i < TP_PORT_MAXIMUM_NUM; i++) {
152 if (!allocatedPortNumMap.containsKey(Integer.valueOf(i))) {
153 unUsedPortNumSet.add(Integer.valueOf(i));
154 }
155 }
156
157 clearPortNumMap();
158 }
159
Hyunsun Moon44aac662017-02-18 02:07:01 +0900160 @Deactivate
161 protected void deactivate() {
162 packetService.removeProcessor(packetProcessor);
163 eventExecutor.shutdown();
164 log.info("Stopped");
165 }
166
167 private void processSnatPacket(PacketContext context, Ethernet eth) {
168 IPv4 iPacket = (IPv4) eth.getPayload();
169 InboundPacket packetIn = context.inPacket();
170
171 int patPort = getPortNum(eth.getSourceMAC(),
172 iPacket.getDestinationAddress());
173
174 InstancePort srcInstPort = instancePortService.instancePort(eth.getSourceMAC());
175 if (srcInstPort == null) {
176 log.trace(ERR_PACKETIN + "source host(MAC:{}) does not exist",
177 eth.getSourceMAC());
178 return;
179 }
180 IpAddress srcIp = IpAddress.valueOf(iPacket.getSourceAddress());
181 Subnet srcSubnet = getSourceSubnet(srcInstPort, srcIp);
182 IpAddress externalGatewayIp = getExternalIp(srcSubnet);
183 if (externalGatewayIp == null) {
184 return;
185 }
daniel park0bc7fdb2017-03-13 14:20:08 +0900186 if (patPort == 0) {
187 log.error("There's no unused port for external ip {}... Drop this packet",
188 getExternalIp(srcSubnet));
189 return;
190 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900191
192 populateSnatFlowRules(context.inPacket(),
193 srcInstPort,
194 TpPort.tpPort(patPort),
195 externalGatewayIp);
196
197 packetOut((Ethernet) eth.clone(),
198 packetIn.receivedFrom().deviceId(),
199 patPort,
200 externalGatewayIp);
201 }
202
203 private Subnet getSourceSubnet(InstancePort instance, IpAddress srcIp) {
204 Port osPort = osNetworkService.port(instance.portId());
205 IP fixedIp = osPort.getFixedIps().stream()
206 .filter(ip -> IpAddress.valueOf(ip.getIpAddress()).equals(srcIp))
207 .findAny().orElse(null);
208 if (fixedIp == null) {
209 return null;
210 }
211 return osNetworkService.subnet(fixedIp.getSubnetId());
212 }
213
214 private IpAddress getExternalIp(Subnet srcSubnet) {
215 RouterInterface osRouterIface = osRouterService.routerInterfaces().stream()
216 .filter(i -> Objects.equals(i.getSubnetId(), srcSubnet.getId()))
217 .findAny().orElse(null);
218 if (osRouterIface == null) {
219 // this subnet is not connected to the router
220 log.trace(ERR_PACKETIN + "source subnet(ID:{}, CIDR:{}) has no router",
221 srcSubnet.getId(), srcSubnet.getCidr());
222 return null;
223 }
224
225 Router osRouter = osRouterService.router(osRouterIface.getId());
226 if (osRouter.getExternalGatewayInfo() == null) {
227 // this router does not have external connectivity
228 log.trace(ERR_PACKETIN + "router({}) has no external gateway",
229 osRouter.getName());
230 return null;
231 }
232
233 ExternalGateway exGatewayInfo = osRouter.getExternalGatewayInfo();
234 if (!exGatewayInfo.isEnableSnat()) {
235 // SNAT is disabled in this router
236 log.trace(ERR_PACKETIN + "router({}) SNAT is disabled", osRouter.getName());
237 return null;
238 }
239
240 // TODO fix openstack4j for ExternalGateway provides external fixed IP list
241 Port exGatewayPort = osNetworkService.ports(exGatewayInfo.getNetworkId())
242 .stream()
243 .filter(port -> Objects.equals(port.getDeviceId(), osRouter.getId()))
244 .findAny().orElse(null);
245 if (exGatewayPort == null) {
246 log.trace(ERR_PACKETIN + "no external gateway port for router({})",
247 osRouter.getName());
248 return null;
249 }
250
251 return IpAddress.valueOf(exGatewayPort.getFixedIps().stream()
252 .findFirst().get().getIpAddress());
253 }
254
255 private void populateSnatFlowRules(InboundPacket packetIn, InstancePort srcInstPort,
256 TpPort patPort, IpAddress externalIp) {
257 Network osNet = osNetworkService.network(srcInstPort.networkId());
258 if (osNet == null) {
259 final String error = String.format(ERR_PACKETIN + "network %s not found",
260 srcInstPort.networkId());
261 throw new IllegalStateException(error);
262 }
263
264 setDownstreamRules(srcInstPort,
265 Long.parseLong(osNet.getProviderSegID()),
266 externalIp,
267 patPort,
268 packetIn);
269
270 setUpstreamRules(Long.parseLong(osNet.getProviderSegID()),
271 externalIp,
272 patPort,
273 packetIn);
274 }
275
276 private void setDownstreamRules(InstancePort srcInstPort, Long srcVni,
277 IpAddress externalIp, TpPort patPort,
278 InboundPacket packetIn) {
279 IPv4 iPacket = (IPv4) packetIn.parsed().getPayload();
280 IpAddress internalIp = IpAddress.valueOf(iPacket.getSourceAddress());
281
282 TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder()
283 .matchEthType(Ethernet.TYPE_IPV4)
284 .matchIPProtocol(iPacket.getProtocol())
285 .matchIPDst(IpPrefix.valueOf(externalIp, 32))
286 .matchIPSrc(IpPrefix.valueOf(iPacket.getDestinationAddress(), 32));
287
288 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder()
289 .setTunnelId(srcVni)
290 .setEthDst(packetIn.parsed().getSourceMAC())
291 .setIpDst(internalIp);
292
293 switch (iPacket.getProtocol()) {
294 case IPv4.PROTOCOL_TCP:
295 TCP tcpPacket = (TCP) iPacket.getPayload();
296 sBuilder.matchTcpSrc(TpPort.tpPort(tcpPacket.getDestinationPort()))
297 .matchTcpDst(patPort);
298 tBuilder.setTcpDst(TpPort.tpPort(tcpPacket.getSourcePort()));
299 break;
300 case IPv4.PROTOCOL_UDP:
301 UDP udpPacket = (UDP) iPacket.getPayload();
302 sBuilder.matchUdpSrc(TpPort.tpPort(udpPacket.getDestinationPort()))
303 .matchUdpDst(patPort);
304 tBuilder.setUdpDst(TpPort.tpPort(udpPacket.getSourcePort()));
305 break;
306 default:
307 break;
308 }
309
daniel parke49eb382017-04-05 16:48:28 +0900310 osNodeService.gatewayDeviceIds().forEach(deviceId -> {
Hyunsun Moon44aac662017-02-18 02:07:01 +0900311 DeviceId srcDeviceId = srcInstPort.deviceId();
312 TrafficTreatment.Builder tmpBuilder =
313 DefaultTrafficTreatment.builder(tBuilder.build());
314 tmpBuilder.extension(RulePopulatorUtil.buildExtension(
315 deviceService,
316 deviceId,
317 osNodeService.dataIp(srcDeviceId).get().getIp4Address()), deviceId)
318 .setOutput(osNodeService.tunnelPort(deviceId).get());
319
320 ForwardingObjective fo = DefaultForwardingObjective.builder()
321 .withSelector(sBuilder.build())
322 .withTreatment(tmpBuilder.build())
323 .withFlag(ForwardingObjective.Flag.VERSATILE)
324 .withPriority(PRIORITY_SNAT_RULE)
325 .makeTemporary(TIME_OUT_SNAT_RULE)
326 .fromApp(appId)
327 .add();
328
329 flowObjectiveService.forward(deviceId, fo);
330 });
331 }
332
333 private void setUpstreamRules(Long srcVni, IpAddress externalIp, TpPort patPort,
334 InboundPacket packetIn) {
335 IPv4 iPacket = (IPv4) packetIn.parsed().getPayload();
336 TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder()
337 .matchEthType(Ethernet.TYPE_IPV4)
338 .matchIPProtocol(iPacket.getProtocol())
339 .matchTunnelId(srcVni)
340 .matchIPSrc(IpPrefix.valueOf(iPacket.getSourceAddress(), 32))
341 .matchIPDst(IpPrefix.valueOf(iPacket.getDestinationAddress(), 32));
342
343 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
344 switch (iPacket.getProtocol()) {
345 case IPv4.PROTOCOL_TCP:
346 TCP tcpPacket = (TCP) iPacket.getPayload();
347 sBuilder.matchTcpSrc(TpPort.tpPort(tcpPacket.getSourcePort()))
348 .matchTcpDst(TpPort.tpPort(tcpPacket.getDestinationPort()));
349 tBuilder.setTcpSrc(patPort)
350 .setEthDst(DEFAULT_EXTERNAL_ROUTER_MAC);
351 break;
352 case IPv4.PROTOCOL_UDP:
353 UDP udpPacket = (UDP) iPacket.getPayload();
354 sBuilder.matchUdpSrc(TpPort.tpPort(udpPacket.getSourcePort()))
355 .matchUdpDst(TpPort.tpPort(udpPacket.getDestinationPort()));
356 tBuilder.setUdpSrc(patPort)
357 .setEthDst(DEFAULT_EXTERNAL_ROUTER_MAC);
358
359 break;
360 default:
361 log.debug("Unsupported IPv4 protocol {}");
362 break;
363 }
364
365 tBuilder.setIpSrc(externalIp);
daniel parke49eb382017-04-05 16:48:28 +0900366 osNodeService.gatewayDeviceIds().forEach(deviceId -> {
Hyunsun Moon44aac662017-02-18 02:07:01 +0900367 TrafficTreatment.Builder tmpBuilder =
368 DefaultTrafficTreatment.builder(tBuilder.build());
daniel parke49eb382017-04-05 16:48:28 +0900369 tmpBuilder.setOutput(osNodeService.externalPort(deviceId).get());
Hyunsun Moon44aac662017-02-18 02:07:01 +0900370 ForwardingObjective fo = DefaultForwardingObjective.builder()
371 .withSelector(sBuilder.build())
372 .withTreatment(tmpBuilder.build())
373 .withFlag(ForwardingObjective.Flag.VERSATILE)
374 .withPriority(PRIORITY_SNAT_RULE)
375 .makeTemporary(TIME_OUT_SNAT_RULE)
376 .fromApp(appId)
377 .add();
378
379 flowObjectiveService.forward(deviceId, fo);
380 });
381 }
382
383 private void packetOut(Ethernet ethPacketIn, DeviceId srcDevice, int patPort,
384 IpAddress externalIp) {
385 IPv4 iPacket = (IPv4) ethPacketIn.getPayload();
386
387 switch (iPacket.getProtocol()) {
388 case IPv4.PROTOCOL_TCP:
389 TCP tcpPacket = (TCP) iPacket.getPayload();
390 tcpPacket.setSourcePort(patPort);
391 tcpPacket.resetChecksum();
392 tcpPacket.setParent(iPacket);
393 iPacket.setPayload(tcpPacket);
394 break;
395 case IPv4.PROTOCOL_UDP:
396 UDP udpPacket = (UDP) iPacket.getPayload();
397 udpPacket.setSourcePort(patPort);
398 udpPacket.resetChecksum();
399 udpPacket.setParent(iPacket);
400 iPacket.setPayload(udpPacket);
401 break;
402 default:
403 log.trace("Temporally, this method can process UDP and TCP protocol.");
404 return;
405 }
406
407 iPacket.setSourceAddress(externalIp.toString());
408 iPacket.resetChecksum();
409 iPacket.setParent(ethPacketIn);
410 ethPacketIn.setDestinationMACAddress(DEFAULT_EXTERNAL_ROUTER_MAC);
411 ethPacketIn.setPayload(iPacket);
412
413 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
daniel parke49eb382017-04-05 16:48:28 +0900414 .setOutput(osNodeService.externalPort(srcDevice).get())
Hyunsun Moon44aac662017-02-18 02:07:01 +0900415 .build();
416 ethPacketIn.resetChecksum();
417 packetService.emit(new DefaultOutboundPacket(
418 srcDevice,
419 treatment,
420 ByteBuffer.wrap(ethPacketIn.serialize())));
421 }
422
423 private int getPortNum(MacAddress sourceMac, int destinationAddress) {
daniel park0bc7fdb2017-03-13 14:20:08 +0900424 if (unUsedPortNumSet.isEmpty()) {
Hyunsun Moon44aac662017-02-18 02:07:01 +0900425 clearPortNumMap();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900426 }
daniel park0bc7fdb2017-03-13 14:20:08 +0900427
428 int portNum = findUnusedPortNum();
429
430 if (portNum != 0) {
431 unUsedPortNumSet.remove(Integer.valueOf(portNum));
432 allocatedPortNumMap
433 .put(Integer.valueOf(portNum), Long.valueOf(System.currentTimeMillis()));
434 }
435
Hyunsun Moon44aac662017-02-18 02:07:01 +0900436 return portNum;
437 }
438
439 private int findUnusedPortNum() {
daniel park0bc7fdb2017-03-13 14:20:08 +0900440 return unUsedPortNumSet.stream().findAny().orElse(Integer.valueOf(0)).intValue();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900441 }
442
443 private void clearPortNumMap() {
daniel park0bc7fdb2017-03-13 14:20:08 +0900444 allocatedPortNumMap.entrySet().forEach(e -> {
445 if (System.currentTimeMillis() - e.getValue().value().longValue() > TIME_OUT_SNAT_PORT_MS) {
446 allocatedPortNumMap.remove(e.getKey());
447 unUsedPortNumSet.add(e.getKey());
Hyunsun Moon44aac662017-02-18 02:07:01 +0900448 }
449 });
450 }
451
452 private class InternalPacketProcessor implements PacketProcessor {
453
454 @Override
455 public void process(PacketContext context) {
456 if (context.isHandled()) {
457 return;
daniel parke49eb382017-04-05 16:48:28 +0900458 } else if (!osNodeService.gatewayDeviceIds().contains(
Hyunsun Moon44aac662017-02-18 02:07:01 +0900459 context.inPacket().receivedFrom().deviceId())) {
460 // return if the packet is not from gateway nodes
461 return;
462 }
463
464 InboundPacket pkt = context.inPacket();
465 Ethernet eth = pkt.parsed();
466 if (eth == null || eth.getEtherType() == Ethernet.TYPE_ARP) {
467 return;
468 }
469
470 IPv4 iPacket = (IPv4) eth.getPayload();
471 switch (iPacket.getProtocol()) {
472 case IPv4.PROTOCOL_ICMP:
473 break;
474 case IPv4.PROTOCOL_UDP:
475 UDP udpPacket = (UDP) iPacket.getPayload();
476 if (udpPacket.getDestinationPort() == UDP.DHCP_SERVER_PORT &&
477 udpPacket.getSourcePort() == UDP.DHCP_CLIENT_PORT) {
478 // don't process DHCP
479 break;
480 }
481 default:
482 eventExecutor.execute(() -> processSnatPacket(context, eth));
483 break;
484 }
485 }
486 }
487}