blob: 27c54f148dd8ebac444e720792a1fff59403beae [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 *
Daniel Park81a61a12016-02-26 08:24:44 +09008 * http://www.apache.org/licenses/LICENSE-2.0
sangho0c2a3da2016-02-16 13:39:07 +09009 *
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.routing;
17
Daniel Park81a61a12016-02-26 08:24:44 +090018import com.google.common.collect.Maps;
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070019import 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;
Daniel Park81a61a12016-02-26 08:24:44 +090024import org.onlab.packet.Ethernet;
25import org.onlab.packet.ICMP;
26import org.onlab.packet.IPv4;
27import org.onlab.packet.Ip4Address;
Daniel Park81a61a12016-02-26 08:24:44 +090028import org.onosproject.core.ApplicationId;
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070029import org.onosproject.core.CoreService;
Daniel Park81a61a12016-02-26 08:24:44 +090030import org.onosproject.net.DeviceId;
sangho6032f342016-07-07 14:32:03 +090031import org.onosproject.net.Host;
Daniel Park81a61a12016-02-26 08:24:44 +090032import org.onosproject.net.flow.DefaultTrafficSelector;
33import org.onosproject.net.flow.DefaultTrafficTreatment;
34import org.onosproject.net.flow.TrafficSelector;
35import org.onosproject.net.flow.TrafficTreatment;
sangho6032f342016-07-07 14:32:03 +090036import org.onosproject.net.host.HostService;
Daniel Park81a61a12016-02-26 08:24:44 +090037import org.onosproject.net.packet.DefaultOutboundPacket;
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070038import org.onosproject.net.packet.InboundPacket;
Daniel Park81a61a12016-02-26 08:24:44 +090039import org.onosproject.net.packet.OutboundPacket;
sangho0c2a3da2016-02-16 13:39:07 +090040import org.onosproject.net.packet.PacketContext;
Daniel Park81a61a12016-02-26 08:24:44 +090041import org.onosproject.net.packet.PacketPriority;
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070042import org.onosproject.net.packet.PacketProcessor;
Daniel Park81a61a12016-02-26 08:24:44 +090043import org.onosproject.net.packet.PacketService;
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070044import org.onosproject.openstackinterface.OpenstackRouter;
sangho6032f342016-07-07 14:32:03 +090045import org.onosproject.openstacknetworking.Constants;
Daniel Park81a61a12016-02-26 08:24:44 +090046import org.onosproject.openstackinterface.OpenstackInterfaceService;
47import org.onosproject.openstackinterface.OpenstackPort;
sangho6032f342016-07-07 14:32:03 +090048import org.onosproject.openstacknode.OpenstackNode;
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070049import org.onosproject.openstacknode.OpenstackNodeEvent;
50import org.onosproject.openstacknode.OpenstackNodeListener;
sangho6032f342016-07-07 14:32:03 +090051import org.onosproject.openstacknode.OpenstackNodeService;
Hyunsun Moon5aa480b2016-08-03 12:23:14 -070052import org.onosproject.scalablegateway.api.GatewayNode;
Kyuhwi Choi92d9ea42016-06-13 17:28:00 +090053import org.onosproject.scalablegateway.api.ScalableGatewayService;
Daniel Park81a61a12016-02-26 08:24:44 +090054import org.slf4j.Logger;
55
56import java.nio.ByteBuffer;
57import java.util.Map;
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070058import java.util.Objects;
Daniel Park81a61a12016-02-26 08:24:44 +090059import java.util.Optional;
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070060import java.util.concurrent.ExecutorService;
Daniel Park81a61a12016-02-26 08:24:44 +090061
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070062import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
63import static org.onlab.util.Tools.groupedThreads;
64import static org.onosproject.openstacknetworking.Constants.*;
65import static org.onosproject.openstacknode.OpenstackNodeService.NodeType.GATEWAY;
Daniel Park81a61a12016-02-26 08:24:44 +090066import static org.slf4j.LoggerFactory.getLogger;
67
sangho0c2a3da2016-02-16 13:39:07 +090068
69/**
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070070 * Handle ICMP packet sent from OpenStack Gateway nodes.
71 * For a request to any private network gateway IPs, it generates fake reply.
72 * For a request to the external network, it does source NAT with a public IP and
73 * forward the request to the external only if the request instance has external
74 * connection setups.
sangho0c2a3da2016-02-16 13:39:07 +090075 */
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070076@Component(immediate = true)
Daniel Park81a61a12016-02-26 08:24:44 +090077public class OpenstackIcmpHandler {
78 protected 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;
sangho6032f342016-07-07 14:32:03 +090082
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070083 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
84 protected PacketService packetService;
sangho6032f342016-07-07 14:32:03 +090085
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -070086 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
87 protected HostService hostService;
88
89 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
90 protected OpenstackInterfaceService openstackService;
91
92 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
93 protected ScalableGatewayService gatewayService;
94
95 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
96 protected OpenstackNodeService nodeService;
97
98 private final ExecutorService eventExecutor = newSingleThreadScheduledExecutor(
99 groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
100 private final InternalPacketProcessor packetProcessor = new InternalPacketProcessor();
101 private final InternalNodeListener nodeListener = new InternalNodeListener();
sangho6032f342016-07-07 14:32:03 +0900102 private final Map<String, Host> icmpInfoMap = Maps.newHashMap();
sangho6032f342016-07-07 14:32:03 +0900103
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700104 ApplicationId appId;
105
106 @Activate
107 protected void activate() {
108 appId = coreService.registerApplication(ROUTING_APP_ID);
109 packetService.addProcessor(packetProcessor, PacketProcessor.director(1));
110 nodeService.addListener(nodeListener);
111 requestPacket(appId);
112
113 log.info("Started");
sangho0c2a3da2016-02-16 13:39:07 +0900114 }
115
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700116 @Deactivate
117 protected void deactivate() {
118 packetService.removeProcessor(packetProcessor);
119 log.info("Stopped");
120 }
121
122 private void requestPacket(ApplicationId appId) {
Daniel Park81a61a12016-02-26 08:24:44 +0900123 TrafficSelector icmpSelector = DefaultTrafficSelector.builder()
124 .matchEthType(Ethernet.TYPE_IPV4)
125 .matchIPProtocol(IPv4.PROTOCOL_ICMP)
126 .build();
127
Sho SHIMIZU8ebb04a2016-10-06 15:58:29 -0700128 gatewayService.getGatewayDeviceIds().forEach(gateway -> {
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700129 packetService.requestPackets(icmpSelector,
130 PacketPriority.CONTROL,
131 appId,
132 Optional.of(gateway));
133 log.debug("Requested ICMP packet on {}", gateway);
134 });
sangho0c2a3da2016-02-16 13:39:07 +0900135 }
Daniel Park81a61a12016-02-26 08:24:44 +0900136
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700137 private void processIcmpPacket(PacketContext context, Ethernet ethernet) {
Daniel Park81a61a12016-02-26 08:24:44 +0900138 IPv4 ipPacket = (IPv4) ethernet.getPayload();
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700139 log.trace("Processing ICMP packet from ip {}, mac {}",
140 Ip4Address.valueOf(ipPacket.getSourceAddress()),
141 ethernet.getSourceMAC());
Daniel Park81a61a12016-02-26 08:24:44 +0900142
143 ICMP icmp = (ICMP) ipPacket.getPayload();
144 short icmpId = getIcmpId(icmp);
145
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700146 DeviceId srcDevice = context.inPacket().receivedFrom().deviceId();
147 switch (icmp.getIcmpType()) {
148 case ICMP.TYPE_ECHO_REQUEST:
149 Optional<Host> reqHost = hostService.getHostsByMac(ethernet.getSourceMAC())
150 .stream().findFirst();
151 if (!reqHost.isPresent()) {
152 log.warn("No host found for MAC {}", ethernet.getSourceMAC());
153 return;
154 }
sangho6032f342016-07-07 14:32:03 +0900155
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700156 // TODO Considers icmp between internal subnets belong to the same router.
157 // TODO do we have to support ICMP reply for non-existing gateway?
158 Ip4Address gatewayIp = Ip4Address.valueOf(
159 reqHost.get().annotations().value(Constants.GATEWAY_IP));
160 if (Objects.equals(ipPacket.getDestinationAddress(), gatewayIp.toInt())) {
161 processRequestToGateway(ipPacket, reqHost.get());
162 } else {
163 Optional<Ip4Address> srcNatIp = getSrcNatIp(reqHost.get());
164 if (!srcNatIp.isPresent()) {
165 log.trace("VM {} has no external connection", reqHost.get());
166 return;
167 }
Daniel Park81a61a12016-02-26 08:24:44 +0900168
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700169 sendRequestToExternal(ipPacket, srcDevice, srcNatIp.get());
170 String icmpInfoKey = String.valueOf(icmpId)
171 .concat(String.valueOf(srcNatIp.get().toInt()))
172 .concat(String.valueOf(ipPacket.getDestinationAddress()));
173 icmpInfoMap.putIfAbsent(icmpInfoKey, reqHost.get());
174 }
175 break;
176 case ICMP.TYPE_ECHO_REPLY:
Daniel Park81a61a12016-02-26 08:24:44 +0900177 String icmpInfoKey = String.valueOf(icmpId)
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700178 .concat(String.valueOf(ipPacket.getDestinationAddress()))
179 .concat(String.valueOf(ipPacket.getSourceAddress()));
Daniel Park81a61a12016-02-26 08:24:44 +0900180
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700181 processReplyFromExternal(ipPacket, icmpInfoMap.get(icmpInfoKey));
182 icmpInfoMap.remove(icmpInfoKey);
183 break;
184 default:
185 break;
Daniel Park81a61a12016-02-26 08:24:44 +0900186 }
187 }
188
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700189 // TODO do we have to handle the request to the fake gateway?
190 private void processRequestToGateway(IPv4 ipPacket, Host reqHost) {
191 ICMP icmpReq = (ICMP) ipPacket.getPayload();
192 icmpReq.setChecksum((short) 0);
193 icmpReq.setIcmpType(ICMP.TYPE_ECHO_REPLY).resetChecksum();
194
Daniel Park1063c232016-08-02 18:53:47 +0900195 int destinationAddress = ipPacket.getSourceAddress();
196
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700197 ipPacket.setSourceAddress(ipPacket.getDestinationAddress())
Daniel Park1063c232016-08-02 18:53:47 +0900198 .setDestinationAddress(destinationAddress)
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700199 .resetChecksum();
200
201 ipPacket.setPayload(icmpReq);
202 Ethernet icmpReply = new Ethernet();
203 icmpReply.setEtherType(Ethernet.TYPE_IPV4)
204 .setSourceMACAddress(Constants.DEFAULT_GATEWAY_MAC)
205 .setDestinationMACAddress(reqHost.mac())
Daniel Park1063c232016-08-02 18:53:47 +0900206 .setPayload(ipPacket);
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700207
208 sendReply(icmpReply, reqHost);
hyungseo Ryu38b5f182016-06-14 16:42:27 +0900209 }
210
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700211 private void sendRequestToExternal(IPv4 ipPacket, DeviceId srcDevice, Ip4Address srcNatIp) {
212 ICMP icmpReq = (ICMP) ipPacket.getPayload();
213 icmpReq.resetChecksum();
214 ipPacket.setSourceAddress(srcNatIp.toInt()).resetChecksum();
215 ipPacket.setPayload(icmpReq);
Daniel Park81a61a12016-02-26 08:24:44 +0900216
217 Ethernet icmpRequestEth = new Ethernet();
Daniel Park81a61a12016-02-26 08:24:44 +0900218 icmpRequestEth.setEtherType(Ethernet.TYPE_IPV4)
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700219 .setSourceMACAddress(DEFAULT_GATEWAY_MAC)
220 .setDestinationMACAddress(DEFAULT_EXTERNAL_ROUTER_MAC)
221 .setPayload(ipPacket);
Kyuhwi Choi92d9ea42016-06-13 17:28:00 +0900222
sangho6032f342016-07-07 14:32:03 +0900223 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700224 .setOutput(gatewayService.getUplinkPort(srcDevice))
sangho6032f342016-07-07 14:32:03 +0900225 .build();
Daniel Park81a61a12016-02-26 08:24:44 +0900226
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700227 OutboundPacket packet = new DefaultOutboundPacket(
228 srcDevice,
229 treatment,
230 ByteBuffer.wrap(icmpRequestEth.serialize()));
Daniel Park81a61a12016-02-26 08:24:44 +0900231
232 packetService.emit(packet);
233 }
234
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700235 private void processReplyFromExternal(IPv4 ipPacket, Host dstHost) {
236 ICMP icmpReply = (ICMP) ipPacket.getPayload();
237 icmpReply.resetChecksum();
Daniel Park81a61a12016-02-26 08:24:44 +0900238
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700239 Ip4Address ipAddress = dstHost.ipAddresses().stream().findFirst().get().getIp4Address();
240 ipPacket.setDestinationAddress(ipAddress.toInt())
Daniel Park81a61a12016-02-26 08:24:44 +0900241 .resetChecksum();
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700242 ipPacket.setPayload(icmpReply);
Daniel Park81a61a12016-02-26 08:24:44 +0900243
244 Ethernet icmpResponseEth = new Ethernet();
Daniel Park81a61a12016-02-26 08:24:44 +0900245 icmpResponseEth.setEtherType(Ethernet.TYPE_IPV4)
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700246 .setSourceMACAddress(Constants.DEFAULT_GATEWAY_MAC)
247 .setDestinationMACAddress(dstHost.mac())
248 .setPayload(ipPacket);
Daniel Park81a61a12016-02-26 08:24:44 +0900249
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700250 sendReply(icmpResponseEth, dstHost);
Daniel Park81a61a12016-02-26 08:24:44 +0900251 }
252
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700253 private void sendReply(Ethernet icmpReply, Host dstHost) {
Daniel Park81a61a12016-02-26 08:24:44 +0900254 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700255 .setOutput(dstHost.location().port())
Daniel Park81a61a12016-02-26 08:24:44 +0900256 .build();
257
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700258 OutboundPacket packet = new DefaultOutboundPacket(
259 dstHost.location().deviceId(),
260 treatment,
261 ByteBuffer.wrap(icmpReply.serialize()));
Daniel Park81a61a12016-02-26 08:24:44 +0900262
263 packetService.emit(packet);
264 }
265
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700266 private Optional<Ip4Address> getSrcNatIp(Host host) {
267 // TODO cache external gateway IP for each network because
268 // asking Neutron for every ICMP request is a bad idea
269 Optional<OpenstackPort> osPort = openstackService.ports().stream()
270 .filter(port -> port.deviceOwner().equals(DEVICE_OWNER_ROUTER_INTERFACE) &&
271 Objects.equals(host.annotations().value(NETWORK_ID),
272 port.networkId()))
273 .findAny();
274 if (!osPort.isPresent()) {
275 return Optional.empty();
276 }
277
278 OpenstackRouter osRouter = openstackService.router(osPort.get().deviceId());
279 if (osRouter == null) {
280 return Optional.empty();
281 }
282
283 return osRouter.gatewayExternalInfo().externalFixedIps()
284 .values().stream().findAny();
285 }
286
Daniel Park81a61a12016-02-26 08:24:44 +0900287 private short getIcmpId(ICMP icmp) {
288 return ByteBuffer.wrap(icmp.serialize(), 4, 2).getShort();
289 }
290
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700291 private class InternalPacketProcessor implements PacketProcessor {
Daniel Park81a61a12016-02-26 08:24:44 +0900292
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700293 @Override
294 public void process(PacketContext context) {
295 if (context.isHandled()) {
296 return;
297 } else if (!gatewayService.getGatewayDeviceIds().contains(
298 context.inPacket().receivedFrom().deviceId())) {
299 // return if the packet is not from gateway nodes
300 return;
301 }
Daniel Park81a61a12016-02-26 08:24:44 +0900302
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700303 InboundPacket pkt = context.inPacket();
304 Ethernet ethernet = pkt.parsed();
305 if (ethernet == null || ethernet.getEtherType() == Ethernet.TYPE_ARP) {
306 return;
307 }
Daniel Park81a61a12016-02-26 08:24:44 +0900308
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700309 IPv4 iPacket = (IPv4) ethernet.getPayload();
310 if (iPacket.getProtocol() == IPv4.PROTOCOL_ICMP) {
311 eventExecutor.execute(() -> processIcmpPacket(context, ethernet));
312 }
hyungseo Ryu38b5f182016-06-14 16:42:27 +0900313 }
hyungseo Ryu38b5f182016-06-14 16:42:27 +0900314 }
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700315
316 private class InternalNodeListener implements OpenstackNodeListener {
317
318 @Override
319 public void event(OpenstackNodeEvent event) {
320 OpenstackNode node = event.node();
321
322 switch (event.type()) {
323 case COMPLETE:
324 if (node.type() == GATEWAY) {
325 log.info("GATEWAY node {} detected", node.hostname());
Hyunsun Moon5aa480b2016-08-03 12:23:14 -0700326 eventExecutor.execute(() -> {
327 GatewayNode gnode = GatewayNode.builder()
328 .gatewayDeviceId(node.intBridge())
329 .dataIpAddress(node.dataIp().getIp4Address())
330 .uplinkIntf(node.externalPortName().get())
331 .build();
332 gatewayService.addGatewayNode(gnode);
333 requestPacket(appId);
334 });
Hyunsun Moonb3eb84d2016-07-27 19:10:52 -0700335 }
336 break;
337 case INIT:
338 case DEVICE_CREATED:
339 case INCOMPLETE:
340 default:
341 break;
342 }
343 }
Kyuhwi Choi92d9ea42016-06-13 17:28:00 +0900344 }
345}