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