blob: a6f0a3907fdebb91e1f2bb3026e65378bcb36c1f [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 com.google.common.base.Strings;
19import com.google.common.collect.Maps;
20import org.apache.felix.scr.annotations.Activate;
21import org.apache.felix.scr.annotations.Component;
22import org.apache.felix.scr.annotations.Deactivate;
23import org.apache.felix.scr.annotations.Reference;
24import org.apache.felix.scr.annotations.ReferenceCardinality;
25import org.onlab.packet.Ethernet;
26import org.onlab.packet.ICMP;
27import org.onlab.packet.IPv4;
28import org.onlab.packet.IpAddress;
29import org.onlab.packet.MacAddress;
30import org.onosproject.core.ApplicationId;
31import org.onosproject.core.CoreService;
32import org.onosproject.mastership.MastershipService;
33import org.onosproject.net.DeviceId;
34import org.onosproject.net.flow.DefaultTrafficSelector;
35import org.onosproject.net.flow.DefaultTrafficTreatment;
36import org.onosproject.net.flow.TrafficSelector;
37import org.onosproject.net.flow.TrafficTreatment;
38import org.onosproject.net.packet.DefaultOutboundPacket;
39import org.onosproject.net.packet.InboundPacket;
40import org.onosproject.net.packet.OutboundPacket;
41import org.onosproject.net.packet.PacketContext;
42import org.onosproject.net.packet.PacketPriority;
43import org.onosproject.net.packet.PacketProcessor;
44import org.onosproject.net.packet.PacketService;
45import org.onosproject.openstacknetworking.api.Constants;
46import org.onosproject.openstacknetworking.api.InstancePort;
47import org.onosproject.openstacknetworking.api.InstancePortService;
48import org.onosproject.openstacknetworking.api.OpenstackRouterService;
49import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
Hyunsun Moon0d457362017-06-27 17:19:41 +090050import org.onosproject.openstacknode.api.OpenstackNode;
51import org.onosproject.openstacknode.api.OpenstackNodeEvent;
52import org.onosproject.openstacknode.api.OpenstackNodeListener;
53import org.onosproject.openstacknode.api.OpenstackNodeService;
Hyunsun Moon44aac662017-02-18 02:07:01 +090054import org.openstack4j.model.network.ExternalGateway;
55import org.openstack4j.model.network.IP;
56import org.openstack4j.model.network.Port;
57import org.openstack4j.model.network.Router;
58import org.openstack4j.model.network.RouterInterface;
59import org.openstack4j.model.network.Subnet;
60import org.slf4j.Logger;
61
62import java.nio.ByteBuffer;
63import java.util.Map;
64import java.util.Objects;
65import java.util.Optional;
66import java.util.Set;
67import java.util.concurrent.ExecutorService;
68import java.util.stream.Collectors;
69
70import static java.util.concurrent.Executors.newSingleThreadExecutor;
71import static org.onlab.util.Tools.groupedThreads;
72import static org.onosproject.openstacknetworking.api.Constants.*;
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: ";
90
91 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
92 protected CoreService coreService;
93
94 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
95 protected PacketService packetService;
96
97 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
98 protected MastershipService mastershipService;
99
100 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Hyunsun Moon44aac662017-02-18 02:07:01 +0900101 protected OpenstackNodeService osNodeService;
102
103 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
104 protected InstancePortService instancePortService;
105
106 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
107 protected OpenstackNetworkService osNetworkService;
108
109 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
110 protected OpenstackRouterService osRouterService;
111
112 private final ExecutorService eventExecutor = newSingleThreadExecutor(
113 groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
Hyunsun Moon0d457362017-06-27 17:19:41 +0900114 private final PacketProcessor packetProcessor = new InternalPacketProcessor();
115 private final OpenstackNodeListener osNodeListener = new InternalNodeListener();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900116 private final Map<String, InstancePort> icmpInfoMap = Maps.newHashMap();
117
118 private ApplicationId appId;
119
120 @Activate
121 protected void activate() {
122 appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
123 packetService.addProcessor(packetProcessor, PacketProcessor.director(1));
Hyunsun Moon0d457362017-06-27 17:19:41 +0900124 osNodeService.addListener(osNodeListener);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900125 requestPacket(appId);
126
127 log.info("Started");
128 }
129
130 @Deactivate
131 protected void deactivate() {
132 packetService.removeProcessor(packetProcessor);
Hyunsun Moon0d457362017-06-27 17:19:41 +0900133 osNodeService.removeListener(osNodeListener);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900134 eventExecutor.shutdown();
135
136 log.info("Stopped");
137 }
138
139 private void requestPacket(ApplicationId appId) {
140 TrafficSelector icmpSelector = DefaultTrafficSelector.builder()
141 .matchEthType(Ethernet.TYPE_IPV4)
142 .matchIPProtocol(IPv4.PROTOCOL_ICMP)
143 .build();
144
Hyunsun Moon0d457362017-06-27 17:19:41 +0900145 osNodeService.completeNodes(GATEWAY).forEach(gNode -> {
Hyunsun Moon44aac662017-02-18 02:07:01 +0900146 packetService.requestPackets(
147 icmpSelector,
148 PacketPriority.CONTROL,
149 appId,
Hyunsun Moon0d457362017-06-27 17:19:41 +0900150 Optional.of(gNode.intgBridge()));
151 log.debug("Requested ICMP packet to {}", gNode.intgBridge());
Hyunsun Moon44aac662017-02-18 02:07:01 +0900152 });
153 }
154
155 private void processIcmpPacket(PacketContext context, Ethernet ethernet) {
156 IPv4 ipPacket = (IPv4) ethernet.getPayload();
157 ICMP icmp = (ICMP) ipPacket.getPayload();
158 log.trace("Processing ICMP packet source MAC:{}, source IP:{}," +
159 "dest MAC:{}, dest IP:{}",
160 ethernet.getSourceMAC(),
161 IpAddress.valueOf(ipPacket.getSourceAddress()),
162 ethernet.getDestinationMAC(),
163 IpAddress.valueOf(ipPacket.getDestinationAddress()));
164
165 switch (icmp.getIcmpType()) {
166 case ICMP.TYPE_ECHO_REQUEST:
167 handleEchoRequest(
168 context.inPacket().receivedFrom().deviceId(),
169 ethernet.getSourceMAC(),
170 ipPacket,
171 icmp);
172 context.block();
173 break;
174 case ICMP.TYPE_ECHO_REPLY:
175 handleEchoReply(ipPacket, icmp);
176 context.block();
177 break;
178 default:
179 break;
180 }
181 }
182
183 private void handleEchoRequest(DeviceId srcDevice, MacAddress srcMac, IPv4 ipPacket,
184 ICMP icmp) {
185 InstancePort instPort = instancePortService.instancePort(srcMac);
186 if (instPort == null) {
187 log.trace(ERR_REQ + "unknown source host(MAC:{})", srcMac);
188 return;
189 }
190
191 IpAddress srcIp = IpAddress.valueOf(ipPacket.getSourceAddress());
192 Subnet srcSubnet = getSourceSubnet(instPort, srcIp);
193 if (srcSubnet == null) {
194 log.trace(ERR_REQ + "unknown source subnet(IP:{})", srcIp);
195 return;
196 }
197 if (Strings.isNullOrEmpty(srcSubnet.getGateway())) {
198 log.trace(ERR_REQ + "source subnet(ID:{}, CIDR:{}) has no gateway",
199 srcSubnet.getId(), srcSubnet.getCidr());
200 return;
201 }
202
203 if (isForSubnetGateway(IpAddress.valueOf(ipPacket.getDestinationAddress()),
204 srcSubnet)) {
205 // this is a request for the subnet gateway
206 processRequestForGateway(ipPacket, instPort);
207 } else {
208 // this is a request for the external network
209 IpAddress externalIp = getExternalIp(srcSubnet);
210 if (externalIp == null) {
211 return;
212 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900213 sendRequestForExternal(ipPacket, srcDevice, externalIp);
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()));
217 icmpInfoMap.putIfAbsent(icmpInfoKey, instPort);
218 }
219 }
220
221 private void handleEchoReply(IPv4 ipPacket, ICMP icmp) {
222 String icmpInfoKey = String.valueOf(getIcmpId(icmp))
223 .concat(String.valueOf(ipPacket.getDestinationAddress()))
224 .concat(String.valueOf(ipPacket.getSourceAddress()));
225
226 processReplyFromExternal(ipPacket, icmpInfoMap.get(icmpInfoKey));
227 icmpInfoMap.remove(icmpInfoKey);
228 }
229
230 private Subnet getSourceSubnet(InstancePort instance, IpAddress srcIp) {
231 Port osPort = osNetworkService.port(instance.portId());
232 IP fixedIp = osPort.getFixedIps().stream()
233 .filter(ip -> IpAddress.valueOf(ip.getIpAddress()).equals(srcIp))
234 .findAny().orElse(null);
235 if (fixedIp == null) {
236 return null;
237 }
238 return osNetworkService.subnet(fixedIp.getSubnetId());
239 }
240
241 private boolean isForSubnetGateway(IpAddress dstIp, Subnet srcSubnet) {
242 RouterInterface osRouterIface = osRouterService.routerInterfaces().stream()
243 .filter(i -> Objects.equals(i.getSubnetId(), srcSubnet.getId()))
244 .findAny().orElse(null);
245 if (osRouterIface == null) {
246 log.trace(ERR_REQ + "source subnet(ID:{}, CIDR:{}) has no router",
247 srcSubnet.getId(), srcSubnet.getCidr());
248 return false;
249 }
250
251 Router osRouter = osRouterService.router(osRouterIface.getId());
252 Set<IpAddress> routableGateways = osRouterService.routerInterfaces(osRouter.getId())
253 .stream()
254 .map(iface -> osNetworkService.subnet(iface.getSubnetId()).getGateway())
255 .map(IpAddress::valueOf)
256 .collect(Collectors.toSet());
257
258 return routableGateways.contains(dstIp);
259 }
260
261 private IpAddress getExternalIp(Subnet srcSubnet) {
262 RouterInterface osRouterIface = osRouterService.routerInterfaces().stream()
263 .filter(i -> Objects.equals(i.getSubnetId(), srcSubnet.getId()))
264 .findAny().orElse(null);
265 if (osRouterIface == null) {
266 final String error = String.format(ERR_REQ +
267 "subnet(ID:%s, CIDR:%s) is not connected to any router",
268 srcSubnet.getId(), srcSubnet.getCidr());
269 throw new IllegalStateException(error);
270 }
271
272 Router osRouter = osRouterService.router(osRouterIface.getId());
273 if (osRouter.getExternalGatewayInfo() == null) {
274 final String error = String.format(ERR_REQ +
275 "router(ID:%s, name:%s) does not have external gateway",
276 osRouter.getId(), osRouter.getName());
277 throw new IllegalStateException(error);
278 }
279
280 // TODO fix openstack4j for ExternalGateway provides external fixed IP list
281 ExternalGateway exGatewayInfo = osRouter.getExternalGatewayInfo();
282 Port exGatewayPort = osNetworkService.ports(exGatewayInfo.getNetworkId())
283 .stream()
284 .filter(port -> Objects.equals(port.getDeviceId(), osRouter.getId()))
285 .findAny().orElse(null);
286 if (exGatewayPort == null) {
287 final String error = String.format(ERR_REQ +
288 "no external gateway port for router (ID:%s, name:%s)",
289 osRouter.getId(), osRouter.getName());
290 throw new IllegalStateException(error);
291 }
292
293 return IpAddress.valueOf(exGatewayPort.getFixedIps().stream()
294 .findFirst().get().getIpAddress());
295 }
296
297 private void processRequestForGateway(IPv4 ipPacket, InstancePort instPort) {
298 ICMP icmpReq = (ICMP) ipPacket.getPayload();
299 icmpReq.setChecksum((short) 0);
300 icmpReq.setIcmpType(ICMP.TYPE_ECHO_REPLY).resetChecksum();
301
302 int destinationAddress = ipPacket.getSourceAddress();
303
304 ipPacket.setSourceAddress(ipPacket.getDestinationAddress())
305 .setDestinationAddress(destinationAddress)
306 .resetChecksum();
307
308 ipPacket.setPayload(icmpReq);
309 Ethernet icmpReply = new Ethernet();
310 icmpReply.setEtherType(Ethernet.TYPE_IPV4)
311 .setSourceMACAddress(Constants.DEFAULT_GATEWAY_MAC)
312 .setDestinationMACAddress(instPort.macAddress())
313 .setPayload(ipPacket);
314
315 sendReply(icmpReply, instPort);
316 }
317
318 private void sendRequestForExternal(IPv4 ipPacket, DeviceId srcDevice, IpAddress srcNatIp) {
319 ICMP icmpReq = (ICMP) ipPacket.getPayload();
320 icmpReq.resetChecksum();
321 ipPacket.setSourceAddress(srcNatIp.getIp4Address().toInt()).resetChecksum();
322 ipPacket.setPayload(icmpReq);
323
324 Ethernet icmpRequestEth = new Ethernet();
325 icmpRequestEth.setEtherType(Ethernet.TYPE_IPV4)
326 .setSourceMACAddress(DEFAULT_GATEWAY_MAC)
327 .setDestinationMACAddress(DEFAULT_EXTERNAL_ROUTER_MAC)
328 .setPayload(ipPacket);
329
Hyunsun Moon0d457362017-06-27 17:19:41 +0900330 OpenstackNode osNode = osNodeService.node(srcDevice);
331 if (osNode == null) {
332 final String error = String.format("Cannot find openstack node for %s",
333 srcDevice);
334 throw new IllegalStateException(error);
335 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900336 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
Hyunsun Moon0d457362017-06-27 17:19:41 +0900337 .setOutput(osNode.patchPortNum())
Hyunsun Moon44aac662017-02-18 02:07:01 +0900338 .build();
339
340 OutboundPacket packet = new DefaultOutboundPacket(
341 srcDevice,
342 treatment,
343 ByteBuffer.wrap(icmpRequestEth.serialize()));
344
345 packetService.emit(packet);
346 }
347
348 private void processReplyFromExternal(IPv4 ipPacket, InstancePort instPort) {
349 ICMP icmpReply = (ICMP) ipPacket.getPayload();
350 icmpReply.resetChecksum();
351
352 ipPacket.setDestinationAddress(instPort.ipAddress().getIp4Address().toInt())
353 .resetChecksum();
354 ipPacket.setPayload(icmpReply);
355
356 Ethernet icmpResponseEth = new Ethernet();
357 icmpResponseEth.setEtherType(Ethernet.TYPE_IPV4)
358 .setSourceMACAddress(Constants.DEFAULT_GATEWAY_MAC)
359 .setDestinationMACAddress(instPort.macAddress())
360 .setPayload(ipPacket);
361
362 sendReply(icmpResponseEth, instPort);
363 }
364
365 private void sendReply(Ethernet icmpReply, InstancePort instPort) {
366 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
367 .setOutput(instPort.portNumber())
368 .build();
369
370 OutboundPacket packet = new DefaultOutboundPacket(
371 instPort.deviceId(),
372 treatment,
373 ByteBuffer.wrap(icmpReply.serialize()));
374
375 packetService.emit(packet);
376 }
377
378 private short getIcmpId(ICMP icmp) {
379 return ByteBuffer.wrap(icmp.serialize(), 4, 2).getShort();
380 }
381
382 private class InternalPacketProcessor implements PacketProcessor {
383
384 @Override
385 public void process(PacketContext context) {
Hyunsun Moon0d457362017-06-27 17:19:41 +0900386 Set<DeviceId> gateways = osNodeService.completeNodes(GATEWAY)
387 .stream().map(OpenstackNode::intgBridge)
388 .collect(Collectors.toSet());
389
Hyunsun Moon44aac662017-02-18 02:07:01 +0900390 if (context.isHandled()) {
391 return;
Hyunsun Moon0d457362017-06-27 17:19:41 +0900392 } else if (!gateways.contains(context.inPacket().receivedFrom().deviceId())) {
Hyunsun Moon44aac662017-02-18 02:07:01 +0900393 // return if the packet is not from gateway nodes
394 return;
395 }
396
397 InboundPacket pkt = context.inPacket();
398 Ethernet ethernet = pkt.parsed();
399 if (ethernet == null || ethernet.getEtherType() == Ethernet.TYPE_ARP) {
400 return;
401 }
402
403 IPv4 iPacket = (IPv4) ethernet.getPayload();
404 if (iPacket.getProtocol() == IPv4.PROTOCOL_ICMP) {
405 eventExecutor.execute(() -> processIcmpPacket(context, ethernet));
406 }
407 }
408 }
409
410 private class InternalNodeListener implements OpenstackNodeListener {
411
412 @Override
413 public boolean isRelevant(OpenstackNodeEvent event) {
414 // do not proceed without mastership
415 OpenstackNode osNode = event.subject();
Hyunsun Moon0d457362017-06-27 17:19:41 +0900416 return mastershipService.isLocalMaster(osNode.intgBridge());
Hyunsun Moon44aac662017-02-18 02:07:01 +0900417 }
418
419 @Override
420 public void event(OpenstackNodeEvent event) {
421 OpenstackNode osNode = event.subject();
422
423 switch (event.type()) {
Hyunsun Moon0d457362017-06-27 17:19:41 +0900424 case OPENSTACK_NODE_COMPLETE:
Hyunsun Moon44aac662017-02-18 02:07:01 +0900425 if (osNode.type() == GATEWAY) {
426 log.info("GATEWAY node {} detected", osNode.hostname());
427 eventExecutor.execute(() -> {
Hyunsun Moon44aac662017-02-18 02:07:01 +0900428 requestPacket(appId);
429 });
430 }
431 break;
Hyunsun Moon0d457362017-06-27 17:19:41 +0900432 case OPENSTACK_NODE_CREATED:
433 case OPENSTACK_NODE_UPDATED:
434 case OPENSTACK_NODE_REMOVED:
435 case OPENSTACK_NODE_INCOMPLETE:
Hyunsun Moon44aac662017-02-18 02:07:01 +0900436 default:
Hyunsun Moon0d457362017-06-27 17:19:41 +0900437 // do nothing
Hyunsun Moon44aac662017-02-18 02:07:01 +0900438 break;
439 }
440 }
441 }
442}