blob: d833c41b2c83c204b62067c446e729589195faa0 [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_EXTERNAL_ROUTER_MAC;
72import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_GATEWAY_MAC;
73import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
Hyunsun Moon0d457362017-06-27 17:19:41 +090074import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.GATEWAY;
Hyunsun Moon44aac662017-02-18 02:07:01 +090075import static org.slf4j.LoggerFactory.getLogger;
76
77
78/**
79 * Handles ICMP packet received from a gateway node.
80 * For a request for virtual network subnet gateway, it generates fake ICMP reply.
81 * For a request for the external network, it does source NAT with the public IP and
82 * forward the request to the external only if the requested virtual subnet has
83 * external connectivity.
84 */
85@Component(immediate = true)
86public class OpenstackRoutingIcmpHandler {
87
88 protected final Logger log = getLogger(getClass());
89
90 private static final String ERR_REQ = "Failed to handle ICMP request: ";
sangho247232c2017-08-24 17:22:08 +090091 private static final String ERR_DUPLICATE = " already exists";
Hyunsun Moon44aac662017-02-18 02:07:01 +090092
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)
sangho247232c2017-08-24 17:22:08 +0900100 protected StorageService storageService;
101
102 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Hyunsun Moon44aac662017-02-18 02:07:01 +0900103 protected OpenstackNodeService osNodeService;
104
105 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
106 protected InstancePortService instancePortService;
107
108 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
109 protected OpenstackNetworkService osNetworkService;
110
111 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
112 protected OpenstackRouterService osRouterService;
113
114 private final ExecutorService eventExecutor = newSingleThreadExecutor(
115 groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
sangho072c4dd2017-05-17 10:45:21 +0900116 private final InternalPacketProcessor packetProcessor = new InternalPacketProcessor();
sangho247232c2017-08-24 17:22:08 +0900117 private ConsistentMap<String, InstancePort> icmpInfoMap;
118
119 private static final KryoNamespace SERIALIZER_ICMP_MAP = KryoNamespace.newBuilder()
120 .register(KryoNamespaces.API)
121 .register(InstancePort.class)
122 .register(HostBasedInstancePort.class)
123 .build();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900124
125 private ApplicationId appId;
126
127 @Activate
128 protected void activate() {
129 appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
130 packetService.addProcessor(packetProcessor, PacketProcessor.director(1));
Hyunsun Moon44aac662017-02-18 02:07:01 +0900131
sangho247232c2017-08-24 17:22:08 +0900132 icmpInfoMap = storageService.<String, InstancePort>consistentMapBuilder()
133 .withSerializer(Serializer.using(SERIALIZER_ICMP_MAP))
134 .withName("openstack-icmpmap")
135 .withApplicationId(appId)
136 .build();
137
Hyunsun Moon44aac662017-02-18 02:07:01 +0900138 log.info("Started");
139 }
140
141 @Deactivate
142 protected void deactivate() {
143 packetService.removeProcessor(packetProcessor);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900144 eventExecutor.shutdown();
145
146 log.info("Stopped");
147 }
148
Hyunsun Moon44aac662017-02-18 02:07:01 +0900149 private void processIcmpPacket(PacketContext context, Ethernet ethernet) {
150 IPv4 ipPacket = (IPv4) ethernet.getPayload();
151 ICMP icmp = (ICMP) ipPacket.getPayload();
152 log.trace("Processing ICMP packet source MAC:{}, source IP:{}," +
153 "dest MAC:{}, dest IP:{}",
154 ethernet.getSourceMAC(),
155 IpAddress.valueOf(ipPacket.getSourceAddress()),
156 ethernet.getDestinationMAC(),
157 IpAddress.valueOf(ipPacket.getDestinationAddress()));
158
159 switch (icmp.getIcmpType()) {
160 case ICMP.TYPE_ECHO_REQUEST:
161 handleEchoRequest(
162 context.inPacket().receivedFrom().deviceId(),
163 ethernet.getSourceMAC(),
164 ipPacket,
165 icmp);
166 context.block();
167 break;
168 case ICMP.TYPE_ECHO_REPLY:
169 handleEchoReply(ipPacket, icmp);
170 context.block();
171 break;
172 default:
173 break;
174 }
175 }
176
177 private void handleEchoRequest(DeviceId srcDevice, MacAddress srcMac, IPv4 ipPacket,
178 ICMP icmp) {
179 InstancePort instPort = instancePortService.instancePort(srcMac);
180 if (instPort == null) {
181 log.trace(ERR_REQ + "unknown source host(MAC:{})", srcMac);
182 return;
183 }
184
185 IpAddress srcIp = IpAddress.valueOf(ipPacket.getSourceAddress());
186 Subnet srcSubnet = getSourceSubnet(instPort, srcIp);
187 if (srcSubnet == null) {
188 log.trace(ERR_REQ + "unknown source subnet(IP:{})", srcIp);
189 return;
190 }
191 if (Strings.isNullOrEmpty(srcSubnet.getGateway())) {
192 log.trace(ERR_REQ + "source subnet(ID:{}, CIDR:{}) has no gateway",
193 srcSubnet.getId(), srcSubnet.getCidr());
194 return;
195 }
196
197 if (isForSubnetGateway(IpAddress.valueOf(ipPacket.getDestinationAddress()),
198 srcSubnet)) {
199 // this is a request for the subnet gateway
200 processRequestForGateway(ipPacket, instPort);
201 } else {
202 // this is a request for the external network
203 IpAddress externalIp = getExternalIp(srcSubnet);
204 if (externalIp == null) {
205 return;
206 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900207 sendRequestForExternal(ipPacket, srcDevice, externalIp);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900208 String icmpInfoKey = String.valueOf(getIcmpId(icmp))
209 .concat(String.valueOf(externalIp.getIp4Address().toInt()))
210 .concat(String.valueOf(ipPacket.getDestinationAddress()));
sangho247232c2017-08-24 17:22:08 +0900211 icmpInfoMap.compute(icmpInfoKey, (id, existing) -> {
212 checkArgument(existing == null, ERR_DUPLICATE);
213 return instPort;
214 });
Hyunsun Moon44aac662017-02-18 02:07:01 +0900215 }
216 }
217
218 private void handleEchoReply(IPv4 ipPacket, ICMP icmp) {
219 String icmpInfoKey = String.valueOf(getIcmpId(icmp))
220 .concat(String.valueOf(ipPacket.getDestinationAddress()))
221 .concat(String.valueOf(ipPacket.getSourceAddress()));
222
sangho247232c2017-08-24 17:22:08 +0900223 if (icmpInfoMap.get(icmpInfoKey) != null) {
224 processReplyFromExternal(ipPacket, icmpInfoMap.get(icmpInfoKey).value());
225 icmpInfoMap.remove(icmpInfoKey);
226 } else {
227 log.warn("No ICMP Info for ICMP packet");
228 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900229 }
230
231 private Subnet getSourceSubnet(InstancePort instance, IpAddress srcIp) {
232 Port osPort = osNetworkService.port(instance.portId());
233 IP fixedIp = osPort.getFixedIps().stream()
234 .filter(ip -> IpAddress.valueOf(ip.getIpAddress()).equals(srcIp))
235 .findAny().orElse(null);
236 if (fixedIp == null) {
237 return null;
238 }
239 return osNetworkService.subnet(fixedIp.getSubnetId());
240 }
241
242 private boolean isForSubnetGateway(IpAddress dstIp, Subnet srcSubnet) {
243 RouterInterface osRouterIface = osRouterService.routerInterfaces().stream()
244 .filter(i -> Objects.equals(i.getSubnetId(), srcSubnet.getId()))
245 .findAny().orElse(null);
246 if (osRouterIface == null) {
247 log.trace(ERR_REQ + "source subnet(ID:{}, CIDR:{}) has no router",
248 srcSubnet.getId(), srcSubnet.getCidr());
249 return false;
250 }
251
252 Router osRouter = osRouterService.router(osRouterIface.getId());
253 Set<IpAddress> routableGateways = osRouterService.routerInterfaces(osRouter.getId())
254 .stream()
255 .map(iface -> osNetworkService.subnet(iface.getSubnetId()).getGateway())
256 .map(IpAddress::valueOf)
257 .collect(Collectors.toSet());
258
259 return routableGateways.contains(dstIp);
260 }
261
262 private IpAddress getExternalIp(Subnet srcSubnet) {
263 RouterInterface osRouterIface = osRouterService.routerInterfaces().stream()
264 .filter(i -> Objects.equals(i.getSubnetId(), srcSubnet.getId()))
265 .findAny().orElse(null);
266 if (osRouterIface == null) {
267 final String error = String.format(ERR_REQ +
268 "subnet(ID:%s, CIDR:%s) is not connected to any router",
269 srcSubnet.getId(), srcSubnet.getCidr());
270 throw new IllegalStateException(error);
271 }
272
273 Router osRouter = osRouterService.router(osRouterIface.getId());
274 if (osRouter.getExternalGatewayInfo() == null) {
275 final String error = String.format(ERR_REQ +
276 "router(ID:%s, name:%s) does not have external gateway",
277 osRouter.getId(), osRouter.getName());
278 throw new IllegalStateException(error);
279 }
280
281 // TODO fix openstack4j for ExternalGateway provides external fixed IP list
282 ExternalGateway exGatewayInfo = osRouter.getExternalGatewayInfo();
283 Port exGatewayPort = osNetworkService.ports(exGatewayInfo.getNetworkId())
284 .stream()
285 .filter(port -> Objects.equals(port.getDeviceId(), osRouter.getId()))
286 .findAny().orElse(null);
287 if (exGatewayPort == null) {
288 final String error = String.format(ERR_REQ +
289 "no external gateway port for router (ID:%s, name:%s)",
290 osRouter.getId(), osRouter.getName());
291 throw new IllegalStateException(error);
292 }
sangho36721992017-08-03 11:13:17 +0900293 Optional<NeutronIP> externalIpAddress = (Optional<NeutronIP>) exGatewayPort.getFixedIps().stream().findFirst();
294 if (!externalIpAddress.isPresent() || externalIpAddress.get().getIpAddress() == null) {
295 final String error = String.format(ERR_REQ +
296 "no external gateway IP address for router (ID:%s, name:%s)",
297 osRouter.getId(), osRouter.getName());
298 throw new IllegalStateException(error);
299 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900300
sangho36721992017-08-03 11:13:17 +0900301 return IpAddress.valueOf(externalIpAddress.get().getIpAddress());
Hyunsun Moon44aac662017-02-18 02:07:01 +0900302 }
303
304 private void processRequestForGateway(IPv4 ipPacket, InstancePort instPort) {
305 ICMP icmpReq = (ICMP) ipPacket.getPayload();
306 icmpReq.setChecksum((short) 0);
307 icmpReq.setIcmpType(ICMP.TYPE_ECHO_REPLY).resetChecksum();
308
309 int destinationAddress = ipPacket.getSourceAddress();
310
311 ipPacket.setSourceAddress(ipPacket.getDestinationAddress())
312 .setDestinationAddress(destinationAddress)
313 .resetChecksum();
314
315 ipPacket.setPayload(icmpReq);
316 Ethernet icmpReply = new Ethernet();
317 icmpReply.setEtherType(Ethernet.TYPE_IPV4)
318 .setSourceMACAddress(Constants.DEFAULT_GATEWAY_MAC)
319 .setDestinationMACAddress(instPort.macAddress())
320 .setPayload(ipPacket);
321
322 sendReply(icmpReply, instPort);
323 }
324
325 private void sendRequestForExternal(IPv4 ipPacket, DeviceId srcDevice, IpAddress srcNatIp) {
326 ICMP icmpReq = (ICMP) ipPacket.getPayload();
327 icmpReq.resetChecksum();
328 ipPacket.setSourceAddress(srcNatIp.getIp4Address().toInt()).resetChecksum();
329 ipPacket.setPayload(icmpReq);
330
331 Ethernet icmpRequestEth = new Ethernet();
332 icmpRequestEth.setEtherType(Ethernet.TYPE_IPV4)
333 .setSourceMACAddress(DEFAULT_GATEWAY_MAC)
334 .setDestinationMACAddress(DEFAULT_EXTERNAL_ROUTER_MAC)
335 .setPayload(ipPacket);
336
Hyunsun Moon0d457362017-06-27 17:19:41 +0900337 OpenstackNode osNode = osNodeService.node(srcDevice);
338 if (osNode == null) {
339 final String error = String.format("Cannot find openstack node for %s",
340 srcDevice);
341 throw new IllegalStateException(error);
342 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900343 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
Hyunsun Moon0d457362017-06-27 17:19:41 +0900344 .setOutput(osNode.patchPortNum())
Hyunsun Moon44aac662017-02-18 02:07:01 +0900345 .build();
346
347 OutboundPacket packet = new DefaultOutboundPacket(
348 srcDevice,
349 treatment,
350 ByteBuffer.wrap(icmpRequestEth.serialize()));
351
352 packetService.emit(packet);
353 }
354
355 private void processReplyFromExternal(IPv4 ipPacket, InstancePort instPort) {
356 ICMP icmpReply = (ICMP) ipPacket.getPayload();
357 icmpReply.resetChecksum();
358
359 ipPacket.setDestinationAddress(instPort.ipAddress().getIp4Address().toInt())
360 .resetChecksum();
361 ipPacket.setPayload(icmpReply);
362
363 Ethernet icmpResponseEth = new Ethernet();
364 icmpResponseEth.setEtherType(Ethernet.TYPE_IPV4)
365 .setSourceMACAddress(Constants.DEFAULT_GATEWAY_MAC)
366 .setDestinationMACAddress(instPort.macAddress())
367 .setPayload(ipPacket);
368
369 sendReply(icmpResponseEth, instPort);
370 }
371
372 private void sendReply(Ethernet icmpReply, InstancePort instPort) {
373 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
374 .setOutput(instPort.portNumber())
375 .build();
376
377 OutboundPacket packet = new DefaultOutboundPacket(
378 instPort.deviceId(),
379 treatment,
380 ByteBuffer.wrap(icmpReply.serialize()));
381
382 packetService.emit(packet);
383 }
384
385 private short getIcmpId(ICMP icmp) {
386 return ByteBuffer.wrap(icmp.serialize(), 4, 2).getShort();
387 }
388
389 private class InternalPacketProcessor implements PacketProcessor {
390
391 @Override
392 public void process(PacketContext context) {
Hyunsun Moon0d457362017-06-27 17:19:41 +0900393 Set<DeviceId> gateways = osNodeService.completeNodes(GATEWAY)
394 .stream().map(OpenstackNode::intgBridge)
395 .collect(Collectors.toSet());
396
Hyunsun Moon44aac662017-02-18 02:07:01 +0900397 if (context.isHandled()) {
398 return;
Hyunsun Moon0d457362017-06-27 17:19:41 +0900399 } else if (!gateways.contains(context.inPacket().receivedFrom().deviceId())) {
Hyunsun Moon44aac662017-02-18 02:07:01 +0900400 // return if the packet is not from gateway nodes
401 return;
402 }
403
404 InboundPacket pkt = context.inPacket();
405 Ethernet ethernet = pkt.parsed();
406 if (ethernet == null || ethernet.getEtherType() == Ethernet.TYPE_ARP) {
407 return;
408 }
409
410 IPv4 iPacket = (IPv4) ethernet.getPayload();
411 if (iPacket.getProtocol() == IPv4.PROTOCOL_ICMP) {
412 eventExecutor.execute(() -> processIcmpPacket(context, ethernet));
413 }
414 }
415 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900416}