blob: 673ce87167bc544ed4538c4deccd9e64cfdaff1b [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;
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;
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;
Hyunsun Moon0d457362017-06-27 17:19:41 +090045import org.onosproject.openstacknode.api.OpenstackNode;
Hyunsun Moon0d457362017-06-27 17:19:41 +090046import org.onosproject.openstacknode.api.OpenstackNodeService;
sangho072c4dd2017-05-17 10:45:21 +090047import org.onosproject.openstacknetworking.api.OpenstackRouterService;
Hyunsun Moon44aac662017-02-18 02:07:01 +090048import org.openstack4j.model.network.ExternalGateway;
49import org.openstack4j.model.network.IP;
50import org.openstack4j.model.network.Port;
51import org.openstack4j.model.network.Router;
52import org.openstack4j.model.network.RouterInterface;
53import org.openstack4j.model.network.Subnet;
54import org.slf4j.Logger;
55
56import java.nio.ByteBuffer;
57import java.util.Map;
58import java.util.Objects;
Hyunsun Moon44aac662017-02-18 02:07:01 +090059import java.util.Set;
60import java.util.concurrent.ExecutorService;
61import java.util.stream.Collectors;
62
63import static java.util.concurrent.Executors.newSingleThreadExecutor;
64import static org.onlab.util.Tools.groupedThreads;
65import static org.onosproject.openstacknetworking.api.Constants.*;
Hyunsun Moon0d457362017-06-27 17:19:41 +090066import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.GATEWAY;
Hyunsun Moon44aac662017-02-18 02:07:01 +090067import static org.slf4j.LoggerFactory.getLogger;
68
69
70/**
71 * Handles ICMP packet received from a gateway node.
72 * For a request for virtual network subnet gateway, it generates fake ICMP reply.
73 * For a request for the external network, it does source NAT with the public IP and
74 * forward the request to the external only if the requested virtual subnet has
75 * external connectivity.
76 */
77@Component(immediate = true)
78public class OpenstackRoutingIcmpHandler {
79
80 protected final Logger log = getLogger(getClass());
81
82 private static final String ERR_REQ = "Failed to handle ICMP request: ";
83
84 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
85 protected CoreService coreService;
86
87 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
88 protected PacketService packetService;
89
90 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Hyunsun Moon44aac662017-02-18 02:07:01 +090091 protected OpenstackNodeService osNodeService;
92
93 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
94 protected InstancePortService instancePortService;
95
96 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
97 protected OpenstackNetworkService osNetworkService;
98
99 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
100 protected OpenstackRouterService osRouterService;
101
102 private final ExecutorService eventExecutor = newSingleThreadExecutor(
103 groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
sangho072c4dd2017-05-17 10:45:21 +0900104 private final InternalPacketProcessor packetProcessor = new InternalPacketProcessor();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900105 private final Map<String, InstancePort> icmpInfoMap = Maps.newHashMap();
106
107 private ApplicationId appId;
108
109 @Activate
110 protected void activate() {
111 appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
112 packetService.addProcessor(packetProcessor, PacketProcessor.director(1));
Hyunsun Moon44aac662017-02-18 02:07:01 +0900113
114 log.info("Started");
115 }
116
117 @Deactivate
118 protected void deactivate() {
119 packetService.removeProcessor(packetProcessor);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900120 eventExecutor.shutdown();
121
122 log.info("Stopped");
123 }
124
Hyunsun Moon44aac662017-02-18 02:07:01 +0900125 private void processIcmpPacket(PacketContext context, Ethernet ethernet) {
126 IPv4 ipPacket = (IPv4) ethernet.getPayload();
127 ICMP icmp = (ICMP) ipPacket.getPayload();
128 log.trace("Processing ICMP packet source MAC:{}, source IP:{}," +
129 "dest MAC:{}, dest IP:{}",
130 ethernet.getSourceMAC(),
131 IpAddress.valueOf(ipPacket.getSourceAddress()),
132 ethernet.getDestinationMAC(),
133 IpAddress.valueOf(ipPacket.getDestinationAddress()));
134
135 switch (icmp.getIcmpType()) {
136 case ICMP.TYPE_ECHO_REQUEST:
137 handleEchoRequest(
138 context.inPacket().receivedFrom().deviceId(),
139 ethernet.getSourceMAC(),
140 ipPacket,
141 icmp);
142 context.block();
143 break;
144 case ICMP.TYPE_ECHO_REPLY:
145 handleEchoReply(ipPacket, icmp);
146 context.block();
147 break;
148 default:
149 break;
150 }
151 }
152
153 private void handleEchoRequest(DeviceId srcDevice, MacAddress srcMac, IPv4 ipPacket,
154 ICMP icmp) {
155 InstancePort instPort = instancePortService.instancePort(srcMac);
156 if (instPort == null) {
157 log.trace(ERR_REQ + "unknown source host(MAC:{})", srcMac);
158 return;
159 }
160
161 IpAddress srcIp = IpAddress.valueOf(ipPacket.getSourceAddress());
162 Subnet srcSubnet = getSourceSubnet(instPort, srcIp);
163 if (srcSubnet == null) {
164 log.trace(ERR_REQ + "unknown source subnet(IP:{})", srcIp);
165 return;
166 }
167 if (Strings.isNullOrEmpty(srcSubnet.getGateway())) {
168 log.trace(ERR_REQ + "source subnet(ID:{}, CIDR:{}) has no gateway",
169 srcSubnet.getId(), srcSubnet.getCidr());
170 return;
171 }
172
173 if (isForSubnetGateway(IpAddress.valueOf(ipPacket.getDestinationAddress()),
174 srcSubnet)) {
175 // this is a request for the subnet gateway
176 processRequestForGateway(ipPacket, instPort);
177 } else {
178 // this is a request for the external network
179 IpAddress externalIp = getExternalIp(srcSubnet);
180 if (externalIp == null) {
181 return;
182 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900183 sendRequestForExternal(ipPacket, srcDevice, externalIp);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900184 String icmpInfoKey = String.valueOf(getIcmpId(icmp))
185 .concat(String.valueOf(externalIp.getIp4Address().toInt()))
186 .concat(String.valueOf(ipPacket.getDestinationAddress()));
187 icmpInfoMap.putIfAbsent(icmpInfoKey, instPort);
188 }
189 }
190
191 private void handleEchoReply(IPv4 ipPacket, ICMP icmp) {
192 String icmpInfoKey = String.valueOf(getIcmpId(icmp))
193 .concat(String.valueOf(ipPacket.getDestinationAddress()))
194 .concat(String.valueOf(ipPacket.getSourceAddress()));
195
196 processReplyFromExternal(ipPacket, icmpInfoMap.get(icmpInfoKey));
197 icmpInfoMap.remove(icmpInfoKey);
198 }
199
200 private Subnet getSourceSubnet(InstancePort instance, IpAddress srcIp) {
201 Port osPort = osNetworkService.port(instance.portId());
202 IP fixedIp = osPort.getFixedIps().stream()
203 .filter(ip -> IpAddress.valueOf(ip.getIpAddress()).equals(srcIp))
204 .findAny().orElse(null);
205 if (fixedIp == null) {
206 return null;
207 }
208 return osNetworkService.subnet(fixedIp.getSubnetId());
209 }
210
211 private boolean isForSubnetGateway(IpAddress dstIp, Subnet srcSubnet) {
212 RouterInterface osRouterIface = osRouterService.routerInterfaces().stream()
213 .filter(i -> Objects.equals(i.getSubnetId(), srcSubnet.getId()))
214 .findAny().orElse(null);
215 if (osRouterIface == null) {
216 log.trace(ERR_REQ + "source subnet(ID:{}, CIDR:{}) has no router",
217 srcSubnet.getId(), srcSubnet.getCidr());
218 return false;
219 }
220
221 Router osRouter = osRouterService.router(osRouterIface.getId());
222 Set<IpAddress> routableGateways = osRouterService.routerInterfaces(osRouter.getId())
223 .stream()
224 .map(iface -> osNetworkService.subnet(iface.getSubnetId()).getGateway())
225 .map(IpAddress::valueOf)
226 .collect(Collectors.toSet());
227
228 return routableGateways.contains(dstIp);
229 }
230
231 private IpAddress getExternalIp(Subnet srcSubnet) {
232 RouterInterface osRouterIface = osRouterService.routerInterfaces().stream()
233 .filter(i -> Objects.equals(i.getSubnetId(), srcSubnet.getId()))
234 .findAny().orElse(null);
235 if (osRouterIface == null) {
236 final String error = String.format(ERR_REQ +
237 "subnet(ID:%s, CIDR:%s) is not connected to any router",
238 srcSubnet.getId(), srcSubnet.getCidr());
239 throw new IllegalStateException(error);
240 }
241
242 Router osRouter = osRouterService.router(osRouterIface.getId());
243 if (osRouter.getExternalGatewayInfo() == null) {
244 final String error = String.format(ERR_REQ +
245 "router(ID:%s, name:%s) does not have external gateway",
246 osRouter.getId(), osRouter.getName());
247 throw new IllegalStateException(error);
248 }
249
250 // TODO fix openstack4j for ExternalGateway provides external fixed IP list
251 ExternalGateway exGatewayInfo = osRouter.getExternalGatewayInfo();
252 Port exGatewayPort = osNetworkService.ports(exGatewayInfo.getNetworkId())
253 .stream()
254 .filter(port -> Objects.equals(port.getDeviceId(), osRouter.getId()))
255 .findAny().orElse(null);
256 if (exGatewayPort == null) {
257 final String error = String.format(ERR_REQ +
258 "no external gateway port for router (ID:%s, name:%s)",
259 osRouter.getId(), osRouter.getName());
260 throw new IllegalStateException(error);
261 }
262
263 return IpAddress.valueOf(exGatewayPort.getFixedIps().stream()
264 .findFirst().get().getIpAddress());
265 }
266
267 private void processRequestForGateway(IPv4 ipPacket, InstancePort instPort) {
268 ICMP icmpReq = (ICMP) ipPacket.getPayload();
269 icmpReq.setChecksum((short) 0);
270 icmpReq.setIcmpType(ICMP.TYPE_ECHO_REPLY).resetChecksum();
271
272 int destinationAddress = ipPacket.getSourceAddress();
273
274 ipPacket.setSourceAddress(ipPacket.getDestinationAddress())
275 .setDestinationAddress(destinationAddress)
276 .resetChecksum();
277
278 ipPacket.setPayload(icmpReq);
279 Ethernet icmpReply = new Ethernet();
280 icmpReply.setEtherType(Ethernet.TYPE_IPV4)
281 .setSourceMACAddress(Constants.DEFAULT_GATEWAY_MAC)
282 .setDestinationMACAddress(instPort.macAddress())
283 .setPayload(ipPacket);
284
285 sendReply(icmpReply, instPort);
286 }
287
288 private void sendRequestForExternal(IPv4 ipPacket, DeviceId srcDevice, IpAddress srcNatIp) {
289 ICMP icmpReq = (ICMP) ipPacket.getPayload();
290 icmpReq.resetChecksum();
291 ipPacket.setSourceAddress(srcNatIp.getIp4Address().toInt()).resetChecksum();
292 ipPacket.setPayload(icmpReq);
293
294 Ethernet icmpRequestEth = new Ethernet();
295 icmpRequestEth.setEtherType(Ethernet.TYPE_IPV4)
296 .setSourceMACAddress(DEFAULT_GATEWAY_MAC)
297 .setDestinationMACAddress(DEFAULT_EXTERNAL_ROUTER_MAC)
298 .setPayload(ipPacket);
299
Hyunsun Moon0d457362017-06-27 17:19:41 +0900300 OpenstackNode osNode = osNodeService.node(srcDevice);
301 if (osNode == null) {
302 final String error = String.format("Cannot find openstack node for %s",
303 srcDevice);
304 throw new IllegalStateException(error);
305 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900306 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
Hyunsun Moon0d457362017-06-27 17:19:41 +0900307 .setOutput(osNode.patchPortNum())
Hyunsun Moon44aac662017-02-18 02:07:01 +0900308 .build();
309
310 OutboundPacket packet = new DefaultOutboundPacket(
311 srcDevice,
312 treatment,
313 ByteBuffer.wrap(icmpRequestEth.serialize()));
314
315 packetService.emit(packet);
316 }
317
318 private void processReplyFromExternal(IPv4 ipPacket, InstancePort instPort) {
319 ICMP icmpReply = (ICMP) ipPacket.getPayload();
320 icmpReply.resetChecksum();
321
322 ipPacket.setDestinationAddress(instPort.ipAddress().getIp4Address().toInt())
323 .resetChecksum();
324 ipPacket.setPayload(icmpReply);
325
326 Ethernet icmpResponseEth = new Ethernet();
327 icmpResponseEth.setEtherType(Ethernet.TYPE_IPV4)
328 .setSourceMACAddress(Constants.DEFAULT_GATEWAY_MAC)
329 .setDestinationMACAddress(instPort.macAddress())
330 .setPayload(ipPacket);
331
332 sendReply(icmpResponseEth, instPort);
333 }
334
335 private void sendReply(Ethernet icmpReply, InstancePort instPort) {
336 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
337 .setOutput(instPort.portNumber())
338 .build();
339
340 OutboundPacket packet = new DefaultOutboundPacket(
341 instPort.deviceId(),
342 treatment,
343 ByteBuffer.wrap(icmpReply.serialize()));
344
345 packetService.emit(packet);
346 }
347
348 private short getIcmpId(ICMP icmp) {
349 return ByteBuffer.wrap(icmp.serialize(), 4, 2).getShort();
350 }
351
352 private class InternalPacketProcessor implements PacketProcessor {
353
354 @Override
355 public void process(PacketContext context) {
Hyunsun Moon0d457362017-06-27 17:19:41 +0900356 Set<DeviceId> gateways = osNodeService.completeNodes(GATEWAY)
357 .stream().map(OpenstackNode::intgBridge)
358 .collect(Collectors.toSet());
359
Hyunsun Moon44aac662017-02-18 02:07:01 +0900360 if (context.isHandled()) {
361 return;
Hyunsun Moon0d457362017-06-27 17:19:41 +0900362 } else if (!gateways.contains(context.inPacket().receivedFrom().deviceId())) {
Hyunsun Moon44aac662017-02-18 02:07:01 +0900363 // return if the packet is not from gateway nodes
364 return;
365 }
366
367 InboundPacket pkt = context.inPacket();
368 Ethernet ethernet = pkt.parsed();
369 if (ethernet == null || ethernet.getEtherType() == Ethernet.TYPE_ARP) {
370 return;
371 }
372
373 IPv4 iPacket = (IPv4) ethernet.getPayload();
374 if (iPacket.getProtocol() == IPv4.PROTOCOL_ICMP) {
375 eventExecutor.execute(() -> processIcmpPacket(context, ethernet));
376 }
377 }
378 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900379}