blob: 13c9e1818a831701d1ee69468ee122586c44a946 [file] [log] [blame]
Hyunsun Moon44aac662017-02-18 02:07:01 +09001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2016-present Open Networking Foundation
Hyunsun Moon44aac662017-02-18 02:07:01 +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 */
16package org.onosproject.openstacknetworking.impl;
17
18import com.google.common.base.Strings;
Hyunsun Moon44aac662017-02-18 02:07:01 +090019import 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.Ethernet;
25import org.onlab.packet.ICMP;
26import org.onlab.packet.IPv4;
27import org.onlab.packet.IpAddress;
28import org.onlab.packet.MacAddress;
sangho247232c2017-08-24 17:22:08 +090029import org.onlab.util.KryoNamespace;
Hyunsun Moon44aac662017-02-18 02:07:01 +090030import org.onosproject.core.ApplicationId;
31import org.onosproject.core.CoreService;
Hyunsun Moon44aac662017-02-18 02:07:01 +090032import org.onosproject.net.DeviceId;
Hyunsun Moon44aac662017-02-18 02:07:01 +090033import org.onosproject.net.flow.DefaultTrafficTreatment;
Hyunsun Moon44aac662017-02-18 02:07:01 +090034import org.onosproject.net.flow.TrafficTreatment;
35import org.onosproject.net.packet.DefaultOutboundPacket;
36import org.onosproject.net.packet.InboundPacket;
37import org.onosproject.net.packet.OutboundPacket;
38import org.onosproject.net.packet.PacketContext;
Hyunsun Moon44aac662017-02-18 02:07:01 +090039import org.onosproject.net.packet.PacketProcessor;
40import org.onosproject.net.packet.PacketService;
41import org.onosproject.openstacknetworking.api.Constants;
42import org.onosproject.openstacknetworking.api.InstancePort;
43import org.onosproject.openstacknetworking.api.InstancePortService;
Hyunsun Moon44aac662017-02-18 02:07:01 +090044import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
sangho36721992017-08-03 11:13:17 +090045import org.onosproject.openstacknetworking.api.OpenstackRouterService;
Hyunsun Moon0d457362017-06-27 17:19:41 +090046import org.onosproject.openstacknode.api.OpenstackNode;
Hyunsun Moon0d457362017-06-27 17:19:41 +090047import org.onosproject.openstacknode.api.OpenstackNodeService;
sangho247232c2017-08-24 17:22:08 +090048import org.onosproject.store.serializers.KryoNamespaces;
49import org.onosproject.store.service.ConsistentMap;
50import org.onosproject.store.service.Serializer;
51import org.onosproject.store.service.StorageService;
Hyunsun Moon44aac662017-02-18 02:07:01 +090052import org.openstack4j.model.network.ExternalGateway;
53import org.openstack4j.model.network.IP;
54import org.openstack4j.model.network.Port;
55import org.openstack4j.model.network.Router;
56import org.openstack4j.model.network.RouterInterface;
57import org.openstack4j.model.network.Subnet;
sangho36721992017-08-03 11:13:17 +090058import org.openstack4j.openstack.networking.domain.NeutronIP;
Hyunsun Moon44aac662017-02-18 02:07:01 +090059import org.slf4j.Logger;
60
61import java.nio.ByteBuffer;
Hyunsun Moon44aac662017-02-18 02:07:01 +090062import java.util.Objects;
sangho36721992017-08-03 11:13:17 +090063import java.util.Optional;
Hyunsun Moon44aac662017-02-18 02:07:01 +090064import java.util.Set;
65import java.util.concurrent.ExecutorService;
66import java.util.stream.Collectors;
67
sangho247232c2017-08-24 17:22:08 +090068import static com.google.common.base.Preconditions.checkArgument;
Hyunsun Moon44aac662017-02-18 02:07:01 +090069import static java.util.concurrent.Executors.newSingleThreadExecutor;
70import static org.onlab.util.Tools.groupedThreads;
sangho36721992017-08-03 11:13:17 +090071import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_GATEWAY_MAC;
72import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
Hyunsun Moon0d457362017-06-27 17:19:41 +090073import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.GATEWAY;
Hyunsun Moon44aac662017-02-18 02:07:01 +090074import static org.slf4j.LoggerFactory.getLogger;
75
76
77/**
78 * Handles ICMP packet received from a gateway node.
79 * For a request for virtual network subnet gateway, it generates fake ICMP reply.
80 * For a request for the external network, it does source NAT with the public IP and
81 * forward the request to the external only if the requested virtual subnet has
82 * external connectivity.
83 */
84@Component(immediate = true)
85public class OpenstackRoutingIcmpHandler {
86
87 protected final Logger log = getLogger(getClass());
88
89 private static final String ERR_REQ = "Failed to handle ICMP request: ";
sangho247232c2017-08-24 17:22:08 +090090 private static final String ERR_DUPLICATE = " already exists";
Hyunsun Moon44aac662017-02-18 02:07:01 +090091
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)
sangho247232c2017-08-24 17:22:08 +090099 protected StorageService storageService;
100
101 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Hyunsun Moon44aac662017-02-18 02:07:01 +0900102 protected OpenstackNodeService osNodeService;
103
104 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
105 protected InstancePortService instancePortService;
106
107 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
108 protected OpenstackNetworkService osNetworkService;
109
110 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
111 protected OpenstackRouterService osRouterService;
112
113 private final ExecutorService eventExecutor = newSingleThreadExecutor(
114 groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
sangho072c4dd2017-05-17 10:45:21 +0900115 private final InternalPacketProcessor packetProcessor = new InternalPacketProcessor();
sangho247232c2017-08-24 17:22:08 +0900116 private ConsistentMap<String, InstancePort> icmpInfoMap;
117
118 private static final KryoNamespace SERIALIZER_ICMP_MAP = KryoNamespace.newBuilder()
119 .register(KryoNamespaces.API)
120 .register(InstancePort.class)
121 .register(HostBasedInstancePort.class)
122 .build();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900123
124 private ApplicationId appId;
125
126 @Activate
127 protected void activate() {
128 appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
129 packetService.addProcessor(packetProcessor, PacketProcessor.director(1));
Hyunsun Moon44aac662017-02-18 02:07:01 +0900130
sangho247232c2017-08-24 17:22:08 +0900131 icmpInfoMap = storageService.<String, InstancePort>consistentMapBuilder()
132 .withSerializer(Serializer.using(SERIALIZER_ICMP_MAP))
133 .withName("openstack-icmpmap")
134 .withApplicationId(appId)
135 .build();
136
Hyunsun Moon44aac662017-02-18 02:07:01 +0900137 log.info("Started");
138 }
139
140 @Deactivate
141 protected void deactivate() {
142 packetService.removeProcessor(packetProcessor);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900143 eventExecutor.shutdown();
144
145 log.info("Stopped");
146 }
147
Hyunsun Moon44aac662017-02-18 02:07:01 +0900148 private void processIcmpPacket(PacketContext context, Ethernet ethernet) {
149 IPv4 ipPacket = (IPv4) ethernet.getPayload();
150 ICMP icmp = (ICMP) ipPacket.getPayload();
151 log.trace("Processing ICMP packet source MAC:{}, source IP:{}," +
152 "dest MAC:{}, dest IP:{}",
153 ethernet.getSourceMAC(),
154 IpAddress.valueOf(ipPacket.getSourceAddress()),
155 ethernet.getDestinationMAC(),
156 IpAddress.valueOf(ipPacket.getDestinationAddress()));
157
158 switch (icmp.getIcmpType()) {
159 case ICMP.TYPE_ECHO_REQUEST:
160 handleEchoRequest(
161 context.inPacket().receivedFrom().deviceId(),
162 ethernet.getSourceMAC(),
163 ipPacket,
164 icmp);
165 context.block();
166 break;
167 case ICMP.TYPE_ECHO_REPLY:
168 handleEchoReply(ipPacket, icmp);
169 context.block();
170 break;
171 default:
172 break;
173 }
174 }
175
176 private void handleEchoRequest(DeviceId srcDevice, MacAddress srcMac, IPv4 ipPacket,
177 ICMP icmp) {
178 InstancePort instPort = instancePortService.instancePort(srcMac);
179 if (instPort == null) {
180 log.trace(ERR_REQ + "unknown source host(MAC:{})", srcMac);
181 return;
182 }
183
184 IpAddress srcIp = IpAddress.valueOf(ipPacket.getSourceAddress());
185 Subnet srcSubnet = getSourceSubnet(instPort, srcIp);
186 if (srcSubnet == null) {
187 log.trace(ERR_REQ + "unknown source subnet(IP:{})", srcIp);
188 return;
189 }
190 if (Strings.isNullOrEmpty(srcSubnet.getGateway())) {
191 log.trace(ERR_REQ + "source subnet(ID:{}, CIDR:{}) has no gateway",
192 srcSubnet.getId(), srcSubnet.getCidr());
193 return;
194 }
195
daniel parkeeb8e042018-02-21 14:06:58 +0900196 MacAddress externalPeerRouterMac = externalPeerRouterMac(srcSubnet);
197 if (externalPeerRouterMac == null) {
198 log.trace(ERR_REQ + "failed to get external peer router mac");
199 return;
200 }
201
Hyunsun Moon44aac662017-02-18 02:07:01 +0900202 if (isForSubnetGateway(IpAddress.valueOf(ipPacket.getDestinationAddress()),
203 srcSubnet)) {
204 // this is a request for the subnet gateway
205 processRequestForGateway(ipPacket, instPort);
206 } else {
207 // this is a request for the external network
208 IpAddress externalIp = getExternalIp(srcSubnet);
209 if (externalIp == null) {
210 return;
211 }
daniel parkeeb8e042018-02-21 14:06:58 +0900212
213 sendRequestForExternal(ipPacket, srcDevice, externalIp, externalPeerRouterMac(srcSubnet));
Hyunsun Moon44aac662017-02-18 02:07:01 +0900214 String icmpInfoKey = String.valueOf(getIcmpId(icmp))
215 .concat(String.valueOf(externalIp.getIp4Address().toInt()))
216 .concat(String.valueOf(ipPacket.getDestinationAddress()));
daniel parkeeb8e042018-02-21 14:06:58 +0900217 try {
218 icmpInfoMap.compute(icmpInfoKey, (id, existing) -> {
219 checkArgument(existing == null, ERR_DUPLICATE);
220 return instPort;
221 });
222 } catch (IllegalArgumentException e) {
223 log.warn("Exception occurred because of {}", e.toString());
224 }
225
Hyunsun Moon44aac662017-02-18 02:07:01 +0900226 }
227 }
228
daniel parkeeb8e042018-02-21 14:06:58 +0900229 private MacAddress externalPeerRouterMac(Subnet subnet) {
230 RouterInterface osRouterIface = osRouterService.routerInterfaces().stream()
231 .filter(i -> Objects.equals(i.getSubnetId(), subnet.getId()))
232 .findAny().orElse(null);
233 if (osRouterIface == null) {
234 return null;
235 }
236
237 Router osRouter = osRouterService.router(osRouterIface.getId());
238 if (osRouter == null) {
239 return null;
240 }
241 if (osRouter.getExternalGatewayInfo() == null) {
242 return null;
243 }
244
245 ExternalGateway exGatewayInfo = osRouter.getExternalGatewayInfo();
246
247 return osNetworkService.externalPeerRouterMac(exGatewayInfo);
248 }
249
Hyunsun Moon44aac662017-02-18 02:07:01 +0900250 private void handleEchoReply(IPv4 ipPacket, ICMP icmp) {
251 String icmpInfoKey = String.valueOf(getIcmpId(icmp))
252 .concat(String.valueOf(ipPacket.getDestinationAddress()))
253 .concat(String.valueOf(ipPacket.getSourceAddress()));
254
sangho247232c2017-08-24 17:22:08 +0900255 if (icmpInfoMap.get(icmpInfoKey) != null) {
256 processReplyFromExternal(ipPacket, icmpInfoMap.get(icmpInfoKey).value());
257 icmpInfoMap.remove(icmpInfoKey);
258 } else {
259 log.warn("No ICMP Info for ICMP packet");
260 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900261 }
262
263 private Subnet getSourceSubnet(InstancePort instance, IpAddress srcIp) {
264 Port osPort = osNetworkService.port(instance.portId());
265 IP fixedIp = osPort.getFixedIps().stream()
266 .filter(ip -> IpAddress.valueOf(ip.getIpAddress()).equals(srcIp))
267 .findAny().orElse(null);
268 if (fixedIp == null) {
269 return null;
270 }
271 return osNetworkService.subnet(fixedIp.getSubnetId());
272 }
273
274 private boolean isForSubnetGateway(IpAddress dstIp, Subnet srcSubnet) {
275 RouterInterface osRouterIface = osRouterService.routerInterfaces().stream()
276 .filter(i -> Objects.equals(i.getSubnetId(), srcSubnet.getId()))
277 .findAny().orElse(null);
278 if (osRouterIface == null) {
279 log.trace(ERR_REQ + "source subnet(ID:{}, CIDR:{}) has no router",
280 srcSubnet.getId(), srcSubnet.getCidr());
281 return false;
282 }
283
284 Router osRouter = osRouterService.router(osRouterIface.getId());
285 Set<IpAddress> routableGateways = osRouterService.routerInterfaces(osRouter.getId())
286 .stream()
287 .map(iface -> osNetworkService.subnet(iface.getSubnetId()).getGateway())
288 .map(IpAddress::valueOf)
289 .collect(Collectors.toSet());
290
291 return routableGateways.contains(dstIp);
292 }
293
294 private IpAddress getExternalIp(Subnet srcSubnet) {
295 RouterInterface osRouterIface = osRouterService.routerInterfaces().stream()
296 .filter(i -> Objects.equals(i.getSubnetId(), srcSubnet.getId()))
297 .findAny().orElse(null);
298 if (osRouterIface == null) {
299 final String error = String.format(ERR_REQ +
300 "subnet(ID:%s, CIDR:%s) is not connected to any router",
301 srcSubnet.getId(), srcSubnet.getCidr());
302 throw new IllegalStateException(error);
303 }
304
305 Router osRouter = osRouterService.router(osRouterIface.getId());
306 if (osRouter.getExternalGatewayInfo() == null) {
307 final String error = String.format(ERR_REQ +
308 "router(ID:%s, name:%s) does not have external gateway",
309 osRouter.getId(), osRouter.getName());
310 throw new IllegalStateException(error);
311 }
312
313 // TODO fix openstack4j for ExternalGateway provides external fixed IP list
314 ExternalGateway exGatewayInfo = osRouter.getExternalGatewayInfo();
315 Port exGatewayPort = osNetworkService.ports(exGatewayInfo.getNetworkId())
316 .stream()
317 .filter(port -> Objects.equals(port.getDeviceId(), osRouter.getId()))
318 .findAny().orElse(null);
319 if (exGatewayPort == null) {
320 final String error = String.format(ERR_REQ +
321 "no external gateway port for router (ID:%s, name:%s)",
322 osRouter.getId(), osRouter.getName());
323 throw new IllegalStateException(error);
324 }
sangho36721992017-08-03 11:13:17 +0900325 Optional<NeutronIP> externalIpAddress = (Optional<NeutronIP>) exGatewayPort.getFixedIps().stream().findFirst();
326 if (!externalIpAddress.isPresent() || externalIpAddress.get().getIpAddress() == null) {
327 final String error = String.format(ERR_REQ +
328 "no external gateway IP address for router (ID:%s, name:%s)",
329 osRouter.getId(), osRouter.getName());
330 throw new IllegalStateException(error);
331 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900332
sangho36721992017-08-03 11:13:17 +0900333 return IpAddress.valueOf(externalIpAddress.get().getIpAddress());
Hyunsun Moon44aac662017-02-18 02:07:01 +0900334 }
335
336 private void processRequestForGateway(IPv4 ipPacket, InstancePort instPort) {
337 ICMP icmpReq = (ICMP) ipPacket.getPayload();
338 icmpReq.setChecksum((short) 0);
339 icmpReq.setIcmpType(ICMP.TYPE_ECHO_REPLY).resetChecksum();
340
341 int destinationAddress = ipPacket.getSourceAddress();
342
343 ipPacket.setSourceAddress(ipPacket.getDestinationAddress())
344 .setDestinationAddress(destinationAddress)
345 .resetChecksum();
346
347 ipPacket.setPayload(icmpReq);
348 Ethernet icmpReply = new Ethernet();
349 icmpReply.setEtherType(Ethernet.TYPE_IPV4)
350 .setSourceMACAddress(Constants.DEFAULT_GATEWAY_MAC)
351 .setDestinationMACAddress(instPort.macAddress())
352 .setPayload(ipPacket);
353
354 sendReply(icmpReply, instPort);
355 }
356
daniel parkeeb8e042018-02-21 14:06:58 +0900357 private void sendRequestForExternal(IPv4 ipPacket, DeviceId srcDevice,
358 IpAddress srcNatIp, MacAddress externalRouterMac) {
Hyunsun Moon44aac662017-02-18 02:07:01 +0900359 ICMP icmpReq = (ICMP) ipPacket.getPayload();
360 icmpReq.resetChecksum();
361 ipPacket.setSourceAddress(srcNatIp.getIp4Address().toInt()).resetChecksum();
362 ipPacket.setPayload(icmpReq);
363
364 Ethernet icmpRequestEth = new Ethernet();
365 icmpRequestEth.setEtherType(Ethernet.TYPE_IPV4)
366 .setSourceMACAddress(DEFAULT_GATEWAY_MAC)
daniel parkeeb8e042018-02-21 14:06:58 +0900367 .setDestinationMACAddress(externalRouterMac)
Hyunsun Moon44aac662017-02-18 02:07:01 +0900368 .setPayload(ipPacket);
369
Hyunsun Moon0d457362017-06-27 17:19:41 +0900370 OpenstackNode osNode = osNodeService.node(srcDevice);
371 if (osNode == null) {
372 final String error = String.format("Cannot find openstack node for %s",
373 srcDevice);
374 throw new IllegalStateException(error);
375 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900376 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
daniel parkeeb8e042018-02-21 14:06:58 +0900377 .setOutput(osNode.uplinkPortNum())
Hyunsun Moon44aac662017-02-18 02:07:01 +0900378 .build();
379
380 OutboundPacket packet = new DefaultOutboundPacket(
381 srcDevice,
382 treatment,
383 ByteBuffer.wrap(icmpRequestEth.serialize()));
384
385 packetService.emit(packet);
386 }
387
388 private void processReplyFromExternal(IPv4 ipPacket, InstancePort instPort) {
389 ICMP icmpReply = (ICMP) ipPacket.getPayload();
390 icmpReply.resetChecksum();
391
392 ipPacket.setDestinationAddress(instPort.ipAddress().getIp4Address().toInt())
393 .resetChecksum();
394 ipPacket.setPayload(icmpReply);
395
396 Ethernet icmpResponseEth = new Ethernet();
397 icmpResponseEth.setEtherType(Ethernet.TYPE_IPV4)
398 .setSourceMACAddress(Constants.DEFAULT_GATEWAY_MAC)
399 .setDestinationMACAddress(instPort.macAddress())
400 .setPayload(ipPacket);
401
402 sendReply(icmpResponseEth, instPort);
403 }
404
405 private void sendReply(Ethernet icmpReply, InstancePort instPort) {
406 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
407 .setOutput(instPort.portNumber())
408 .build();
409
410 OutboundPacket packet = new DefaultOutboundPacket(
411 instPort.deviceId(),
412 treatment,
413 ByteBuffer.wrap(icmpReply.serialize()));
414
415 packetService.emit(packet);
416 }
417
418 private short getIcmpId(ICMP icmp) {
419 return ByteBuffer.wrap(icmp.serialize(), 4, 2).getShort();
420 }
421
422 private class InternalPacketProcessor implements PacketProcessor {
423
424 @Override
425 public void process(PacketContext context) {
Hyunsun Moon0d457362017-06-27 17:19:41 +0900426 Set<DeviceId> gateways = osNodeService.completeNodes(GATEWAY)
427 .stream().map(OpenstackNode::intgBridge)
428 .collect(Collectors.toSet());
429
Hyunsun Moon44aac662017-02-18 02:07:01 +0900430 if (context.isHandled()) {
431 return;
Hyunsun Moon0d457362017-06-27 17:19:41 +0900432 } else if (!gateways.contains(context.inPacket().receivedFrom().deviceId())) {
Hyunsun Moon44aac662017-02-18 02:07:01 +0900433 // return if the packet is not from gateway nodes
434 return;
435 }
436
437 InboundPacket pkt = context.inPacket();
438 Ethernet ethernet = pkt.parsed();
439 if (ethernet == null || ethernet.getEtherType() == Ethernet.TYPE_ARP) {
440 return;
441 }
442
443 IPv4 iPacket = (IPv4) ethernet.getPayload();
444 if (iPacket.getProtocol() == IPv4.PROTOCOL_ICMP) {
445 eventExecutor.execute(() -> processIcmpPacket(context, ethernet));
446 }
447 }
448 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900449}