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