blob: 5a5eafc10e5d2b53887174575f5b65366683b130 [file] [log] [blame]
sangho0c2a3da2016-02-16 13:39:07 +09001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2016-present Open Networking Laboratory
sangho0c2a3da2016-02-16 13:39:07 +09003 *
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 */
Hyunsun Moon05400872017-02-07 17:11:25 +090016package org.onosproject.openstacknetworking.impl;
sangho0c2a3da2016-02-16 13:39:07 +090017
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070018import 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;
sangho0c2a3da2016-02-16 13:39:07 +090023import org.onlab.packet.Ethernet;
24import org.onlab.packet.IPv4;
Kyuhwi Choie2b37e32016-02-05 14:04:14 +090025import org.onlab.packet.Ip4Address;
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070026import org.onlab.packet.IpAddress;
27import org.onlab.packet.IpPrefix;
sangho0c2a3da2016-02-16 13:39:07 +090028import org.onlab.packet.MacAddress;
29import org.onlab.packet.TCP;
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070030import org.onlab.packet.TpPort;
sangho0c2a3da2016-02-16 13:39:07 +090031import org.onlab.packet.UDP;
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070032import org.onlab.util.KryoNamespace;
33import org.onlab.util.Tools;
34import org.onosproject.core.ApplicationId;
35import org.onosproject.core.CoreService;
sangho0c2a3da2016-02-16 13:39:07 +090036import org.onosproject.net.DeviceId;
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070037import org.onosproject.net.Host;
38import org.onosproject.net.device.DeviceService;
39import org.onosproject.net.flow.DefaultTrafficSelector;
sangho0c2a3da2016-02-16 13:39:07 +090040import org.onosproject.net.flow.DefaultTrafficTreatment;
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070041import org.onosproject.net.flow.TrafficSelector;
sangho0c2a3da2016-02-16 13:39:07 +090042import org.onosproject.net.flow.TrafficTreatment;
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070043import org.onosproject.net.flowobjective.DefaultForwardingObjective;
44import org.onosproject.net.flowobjective.FlowObjectiveService;
45import org.onosproject.net.flowobjective.ForwardingObjective;
46import org.onosproject.net.host.HostService;
sangho0c2a3da2016-02-16 13:39:07 +090047import org.onosproject.net.packet.DefaultOutboundPacket;
48import org.onosproject.net.packet.InboundPacket;
49import org.onosproject.net.packet.PacketContext;
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070050import org.onosproject.net.packet.PacketProcessor;
sangho0c2a3da2016-02-16 13:39:07 +090051import org.onosproject.net.packet.PacketService;
sangho93447f12016-02-24 00:33:22 +090052import org.onosproject.openstackinterface.OpenstackInterfaceService;
sangho93447f12016-02-24 00:33:22 +090053import org.onosproject.openstackinterface.OpenstackPort;
54import org.onosproject.openstackinterface.OpenstackRouter;
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070055import org.onosproject.openstacknode.OpenstackNodeService;
Kyuhwi Choi92d9ea42016-06-13 17:28:00 +090056import org.onosproject.scalablegateway.api.ScalableGatewayService;
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070057import org.onosproject.store.serializers.KryoNamespaces;
58import org.onosproject.store.service.ConsistentMap;
59import org.onosproject.store.service.Serializer;
60import org.onosproject.store.service.StorageService;
sangho0c2a3da2016-02-16 13:39:07 +090061import org.slf4j.Logger;
sangho0c2a3da2016-02-16 13:39:07 +090062
63import java.nio.ByteBuffer;
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070064import java.util.Objects;
65import java.util.Optional;
66import java.util.concurrent.ExecutorService;
sangho0c2a3da2016-02-16 13:39:07 +090067
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070068import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
69import static org.onlab.util.Tools.groupedThreads;
Hyunsun Moon05400872017-02-07 17:11:25 +090070import static org.onosproject.openstacknetworking.api.Constants.*;
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070071import static org.slf4j.LoggerFactory.getLogger;
sangho0c2a3da2016-02-16 13:39:07 +090072
73/**
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070074 * Handle NAT packet processing for managing flow rules in openstack nodes.
sangho0c2a3da2016-02-16 13:39:07 +090075 */
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070076@Component(immediate = true)
77public class OpenstackPnatHandler {
78 private final Logger log = getLogger(getClass());
sangho0c2a3da2016-02-16 13:39:07 +090079
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070080 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
81 protected CoreService coreService;
sangho0c2a3da2016-02-16 13:39:07 +090082
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070083 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
84 protected PacketService packetService;
Kyuhwi Choie2b37e32016-02-05 14:04:14 +090085
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070086 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
87 protected HostService hostService;
sangho0c2a3da2016-02-16 13:39:07 +090088
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070089 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
90 protected StorageService storageService;
91
92 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
93 protected FlowObjectiveService flowObjectiveService;
94
95 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
96 protected DeviceService deviceService;
97
98 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
99 protected OpenstackInterfaceService openstackService;
100
101 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
102 protected OpenstackNodeService nodeService;
103
104 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
105 protected ScalableGatewayService gatewayService;
106
107 private static final KryoNamespace.Builder NUMBER_SERIALIZER = KryoNamespace.newBuilder()
108 .register(KryoNamespaces.API);
109
110 private static final int PNAT_PORT_EXPIRE_TIME = 1200 * 1000;
111 private static final int TP_PORT_MINIMUM_NUM = 1024;
112 private static final int TP_PORT_MAXIMUM_NUM = 65535;
113
114 private final ExecutorService eventExecutor = newSingleThreadScheduledExecutor(
115 groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
116 private final InternalPacketProcessor packetProcessor = new InternalPacketProcessor();
117
118 private ConsistentMap<Integer, String> tpPortNumMap;
119 private ApplicationId appId;
120
121 @Activate
122 protected void activate() {
123 appId = coreService.registerApplication(ROUTING_APP_ID);
124 tpPortNumMap = storageService.<Integer, String>consistentMapBuilder()
125 .withSerializer(Serializer.using(NUMBER_SERIALIZER.build()))
126 .withName("openstackrouting-tpportnum")
127 .withApplicationId(appId)
128 .build();
129
130 packetService.addProcessor(packetProcessor, PacketProcessor.director(1));
131 log.info("Started");
sangho0c2a3da2016-02-16 13:39:07 +0900132 }
133
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700134 @Deactivate
135 protected void deactivate() {
136 packetService.removeProcessor(packetProcessor);
137 log.info("Stopped");
138 }
sangho0c2a3da2016-02-16 13:39:07 +0900139
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700140 private void processPnatPacket(PacketContext context, Ethernet ethernet) {
141 IPv4 iPacket = (IPv4) ethernet.getPayload();
142 InboundPacket inboundPacket = context.inPacket();
143
144 int srcPort = getPortNum(ethernet.getSourceMAC(), iPacket.getDestinationAddress());
145 OpenstackPort osPort = getOpenstackPort(ethernet.getSourceMAC());
146 if (osPort == null) {
147 return;
148 }
149 Ip4Address externalGatewayIp = getExternalGatewayIp(osPort);
150 if (externalGatewayIp == null) {
sangho0c2a3da2016-02-16 13:39:07 +0900151 return;
152 }
153
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700154 populatePnatFlowRules(context.inPacket(),
155 osPort,
156 TpPort.tpPort(srcPort),
157 externalGatewayIp);
sangho0c2a3da2016-02-16 13:39:07 +0900158
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700159 packetOut((Ethernet) ethernet.clone(),
160 inboundPacket.receivedFrom().deviceId(),
161 srcPort,
162 externalGatewayIp);
sangho0c2a3da2016-02-16 13:39:07 +0900163 }
164
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700165 private void packetOut(Ethernet ethernet, DeviceId deviceId, int portNum, Ip4Address externalIp) {
sangho0c2a3da2016-02-16 13:39:07 +0900166 IPv4 iPacket = (IPv4) ethernet.getPayload();
sangho0c2a3da2016-02-16 13:39:07 +0900167 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
168
169 switch (iPacket.getProtocol()) {
170 case IPv4.PROTOCOL_TCP:
171 TCP tcpPacket = (TCP) iPacket.getPayload();
172 tcpPacket.setSourcePort(portNum);
173 tcpPacket.resetChecksum();
174 tcpPacket.setParent(iPacket);
175 iPacket.setPayload(tcpPacket);
176 break;
177 case IPv4.PROTOCOL_UDP:
178 UDP udpPacket = (UDP) iPacket.getPayload();
179 udpPacket.setSourcePort(portNum);
180 udpPacket.resetChecksum();
181 udpPacket.setParent(iPacket);
182 iPacket.setPayload(udpPacket);
183 break;
184 default:
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700185 log.trace("Temporally, this method can process UDP and TCP protocol.");
Kyuhwi Choie2b37e32016-02-05 14:04:14 +0900186 return;
sangho0c2a3da2016-02-16 13:39:07 +0900187 }
188
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700189 iPacket.setSourceAddress(externalIp.toString());
sangho0c2a3da2016-02-16 13:39:07 +0900190 iPacket.resetChecksum();
Kyuhwi Choie2b37e32016-02-05 14:04:14 +0900191 iPacket.setParent(ethernet);
sangho5c8f0482016-08-04 23:50:52 +0900192 ethernet.setDestinationMACAddress(DEFAULT_EXTERNAL_ROUTER_MAC);
Kyuhwi Choiee9e3712016-02-22 22:49:36 +0900193 ethernet.setPayload(iPacket);
sangho6032f342016-07-07 14:32:03 +0900194
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700195 treatment.setOutput(gatewayService.getUplinkPort(deviceId));
196 ethernet.resetChecksum();
197 packetService.emit(new DefaultOutboundPacket(
198 deviceId,
199 treatment.build(),
200 ByteBuffer.wrap(ethernet.serialize())));
201 }
202
203 private int getPortNum(MacAddress sourceMac, int destinationAddress) {
204 int portNum = findUnusedPortNum();
205 if (portNum == 0) {
206 clearPortNumMap();
207 portNum = findUnusedPortNum();
208 }
209 tpPortNumMap.put(portNum, sourceMac.toString().concat(":").concat(String.valueOf(destinationAddress)));
210 return portNum;
211 }
212
213 private int findUnusedPortNum() {
214 for (int i = TP_PORT_MINIMUM_NUM; i < TP_PORT_MAXIMUM_NUM; i++) {
215 if (!tpPortNumMap.containsKey(i)) {
216 return i;
217 }
218 }
219 return 0;
220 }
221
222 private void clearPortNumMap() {
223 tpPortNumMap.entrySet().forEach(e -> {
224 if (System.currentTimeMillis() - e.getValue().creationTime() > PNAT_PORT_EXPIRE_TIME) {
225 tpPortNumMap.remove(e.getKey());
226 }
227 });
228 }
229
230 // TODO there can be multiple routers connected to a particular openstack port
231 // TODO cache router information
232 private Ip4Address getExternalGatewayIp(OpenstackPort osPort) {
233 Optional<OpenstackPort> routerPort = openstackService.ports().stream()
234 .filter(p -> p.deviceOwner().equals(DEVICE_OWNER_ROUTER_INTERFACE))
235 .filter(p -> checkSameSubnet(p, osPort))
236 .findAny();
237 if (!routerPort.isPresent()) {
238 log.warn("No router is connected to network {}", osPort.networkId());
239 return null;
240 }
241
242 OpenstackRouter osRouter = openstackService.router(routerPort.get().deviceId());
243 if (osRouter == null) {
244 log.warn("Failed to get OpenStack router {}",
245 routerPort.get().deviceId());
246 return null;
247 }
248
249 return osRouter.gatewayExternalInfo().externalFixedIps().values()
250 .stream().findAny().orElse(null);
251 }
252
253 private OpenstackPort getOpenstackPort(MacAddress srcMac) {
254 Optional<Host> host = hostService.getHostsByMac(srcMac).stream()
255 .filter(h -> h.annotations().value(PORT_ID) != null)
256 .findAny();
257 if (!host.isPresent()) {
258 log.warn("Failed to find a host with MAC:{}", srcMac);
259 return null;
260 }
261 return openstackService.port(host.get().annotations().value(PORT_ID));
262 }
263
264 private boolean checkSameSubnet(OpenstackPort osPortA, OpenstackPort osPortB) {
265 return osPortA.fixedIps().keySet().stream()
266 .anyMatch(subnetId -> osPortB.fixedIps().keySet().contains(subnetId));
267 }
268
269 private void populatePnatFlowRules(InboundPacket inboundPacket,
270 OpenstackPort osPort,
271 TpPort patPort,
272 Ip4Address externalIp) {
273 long vni = getVni(osPort.networkId());
274 populatePnatIncomingFlowRules(vni, externalIp, patPort, inboundPacket);
275 populatePnatOutgoingFlowRules(vni, externalIp, patPort, inboundPacket);
276 }
277
278 private long getVni(String netId) {
279 // TODO remove this and use host vxlan annotation if applicable
280 return Long.parseLong(openstackService.network(netId).segmentId());
281 }
282
283 private void populatePnatOutgoingFlowRules(long vni, Ip4Address externalIp, TpPort patPort,
284 InboundPacket inboundPacket) {
285 IPv4 iPacket = (IPv4) inboundPacket.parsed().getPayload();
286
287 TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
288 sBuilder.matchEthType(Ethernet.TYPE_IPV4)
289 .matchIPProtocol(iPacket.getProtocol())
290 .matchTunnelId(vni)
291 .matchIPSrc(IpPrefix.valueOf(iPacket.getSourceAddress(), 32))
292 .matchIPDst(IpPrefix.valueOf(iPacket.getDestinationAddress(), 32));
293
294 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
295 switch (iPacket.getProtocol()) {
296 case IPv4.PROTOCOL_TCP:
297 TCP tcpPacket = (TCP) iPacket.getPayload();
298 sBuilder.matchTcpSrc(TpPort.tpPort(tcpPacket.getSourcePort()))
299 .matchTcpDst(TpPort.tpPort(tcpPacket.getDestinationPort()));
300 tBuilder.setTcpSrc(patPort)
301 .setEthDst(DEFAULT_EXTERNAL_ROUTER_MAC);
302 break;
303 case IPv4.PROTOCOL_UDP:
304 UDP udpPacket = (UDP) iPacket.getPayload();
305 sBuilder.matchUdpSrc(TpPort.tpPort(udpPacket.getSourcePort()))
306 .matchUdpDst(TpPort.tpPort(udpPacket.getDestinationPort()));
307 tBuilder.setUdpSrc(patPort)
308 .setEthDst(DEFAULT_EXTERNAL_ROUTER_MAC);
309
310 break;
311 default:
312 log.debug("Unsupported IPv4 protocol {}");
313 break;
314 }
315
316 tBuilder.setIpSrc(externalIp);
Sho SHIMIZU8ebb04a2016-10-06 15:58:29 -0700317 gatewayService.getGatewayNodes().forEach(gateway -> {
sangho4d287732016-08-04 23:24:13 +0900318 TrafficTreatment.Builder tmpBuilder = DefaultTrafficTreatment.builder(tBuilder.build());
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700319 tmpBuilder.setOutput(gatewayService.getUplinkPort(gateway.getGatewayDeviceId()));
320 ForwardingObjective fo = DefaultForwardingObjective.builder()
321 .withSelector(sBuilder.build())
322 .withTreatment(tmpBuilder.build())
323 .withFlag(ForwardingObjective.Flag.VERSATILE)
324 .withPriority(PNAT_RULE_PRIORITY)
325 .makeTemporary(PNAT_TIMEOUT)
326 .fromApp(appId)
327 .add();
328
329 flowObjectiveService.forward(gateway.getGatewayDeviceId(), fo);
330 });
331 }
332
333 private void populatePnatIncomingFlowRules(long vni, Ip4Address externalIp, TpPort patPort,
334 InboundPacket inboundPacket) {
335 IPv4 iPacket = (IPv4) inboundPacket.parsed().getPayload();
336 IpAddress internalIp = IpAddress.valueOf(iPacket.getSourceAddress());
337
338 TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
339 sBuilder.matchEthType(Ethernet.TYPE_IPV4)
340 .matchIPProtocol(iPacket.getProtocol())
341 .matchIPDst(IpPrefix.valueOf(externalIp, 32))
342 .matchIPSrc(IpPrefix.valueOf(iPacket.getDestinationAddress(), 32));
343
344 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
345 tBuilder.setTunnelId(vni)
346 .setEthDst(inboundPacket.parsed().getSourceMAC())
347 .setIpDst(internalIp);
348
349 switch (iPacket.getProtocol()) {
350 case IPv4.PROTOCOL_TCP:
351 TCP tcpPacket = (TCP) iPacket.getPayload();
352 sBuilder.matchTcpSrc(TpPort.tpPort(tcpPacket.getDestinationPort()))
353 .matchTcpDst(patPort);
354 tBuilder.setTcpDst(TpPort.tpPort(tcpPacket.getSourcePort()));
355 break;
356 case IPv4.PROTOCOL_UDP:
357 UDP udpPacket = (UDP) iPacket.getPayload();
358 sBuilder.matchUdpSrc(TpPort.tpPort(udpPacket.getDestinationPort()))
359 .matchUdpDst(patPort);
360 tBuilder.setUdpDst(TpPort.tpPort(udpPacket.getSourcePort()));
361 break;
362 default:
363 break;
364 }
365
366 Optional<Host> srcVm = Tools.stream(hostService.getHostsByIp(internalIp))
367 .filter(host -> Objects.equals(
368 host.annotations().value(VXLAN_ID),
369 String.valueOf(vni)))
370 .findFirst();
371 if (!srcVm.isPresent()) {
372 log.warn("Failed to find source VM with IP {}", internalIp);
Kyuhwi Choi92d9ea42016-06-13 17:28:00 +0900373 return;
374 }
Kyuhwi Choi92d9ea42016-06-13 17:28:00 +0900375
Sho SHIMIZU8ebb04a2016-10-06 15:58:29 -0700376 gatewayService.getGatewayDeviceIds().forEach(deviceId -> {
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700377 DeviceId srcDeviceId = srcVm.get().location().deviceId();
sangho4d287732016-08-04 23:24:13 +0900378 TrafficTreatment.Builder tmpBuilder = DefaultTrafficTreatment.builder(tBuilder.build());
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700379 tmpBuilder.extension(RulePopulatorUtil.buildExtension(
380 deviceService,
381 deviceId,
382 nodeService.dataIp(srcDeviceId).get().getIp4Address()), deviceId)
383 .setOutput(nodeService.tunnelPort(deviceId).get());
sangho0c2a3da2016-02-16 13:39:07 +0900384
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700385 ForwardingObjective fo = DefaultForwardingObjective.builder()
386 .withSelector(sBuilder.build())
387 .withTreatment(tmpBuilder.build())
388 .withFlag(ForwardingObjective.Flag.VERSATILE)
389 .withPriority(PNAT_RULE_PRIORITY)
390 .makeTemporary(PNAT_TIMEOUT)
391 .fromApp(appId)
392 .add();
393
394 flowObjectiveService.forward(deviceId, fo);
395 });
396 }
397
398 private class InternalPacketProcessor implements PacketProcessor {
399
400 @Override
401 public void process(PacketContext context) {
402 if (context.isHandled()) {
403 return;
404 } else if (!gatewayService.getGatewayDeviceIds().contains(
405 context.inPacket().receivedFrom().deviceId())) {
406 // return if the packet is not from gateway nodes
407 return;
408 }
409
410 InboundPacket pkt = context.inPacket();
411 Ethernet ethernet = pkt.parsed();
412 if (ethernet == null || ethernet.getEtherType() == Ethernet.TYPE_ARP) {
413 return;
414 }
415
416 IPv4 iPacket = (IPv4) ethernet.getPayload();
417 switch (iPacket.getProtocol()) {
418 case IPv4.PROTOCOL_ICMP:
419 break;
420 case IPv4.PROTOCOL_UDP:
421 UDP udpPacket = (UDP) iPacket.getPayload();
422 if (udpPacket.getDestinationPort() == UDP.DHCP_SERVER_PORT &&
423 udpPacket.getSourcePort() == UDP.DHCP_CLIENT_PORT) {
424 // don't process DHCP
425 break;
426 }
427 default:
428 eventExecutor.execute(() -> processPnatPacket(context, ethernet));
429 break;
430 }
431 }
sangho0c2a3da2016-02-16 13:39:07 +0900432 }
Sho SHIMIZU8ebb04a2016-10-06 15:58:29 -0700433}