blob: c2f4b2268629b87bf0b55bdf7d8bffd1aa2ad341 [file] [log] [blame]
Jian Lia1186772018-07-27 18:06:41 +09001/*
2 * Copyright 2018-present Open Networking Foundation
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.openstacktroubleshoot.impl;
17
Jian Li0b93b002018-07-31 13:41:08 +090018import com.google.common.collect.Sets;
Jian Li0b93b002018-07-31 13:41:08 +090019import org.onlab.packet.DeserializationException;
20import org.onlab.packet.Ethernet;
21import org.onlab.packet.ICMP;
22import org.onlab.packet.ICMPEcho;
23import org.onlab.packet.IPv4;
24import org.onlab.packet.IpAddress;
25import org.onlab.packet.IpPrefix;
Jian Lic38e9032018-08-09 17:08:38 +090026import org.onlab.packet.MacAddress;
Jian Li0b93b002018-07-31 13:41:08 +090027import org.onlab.util.KryoNamespace;
28import org.onosproject.cluster.ClusterService;
29import org.onosproject.cluster.LeadershipService;
30import org.onosproject.cluster.NodeId;
31import org.onosproject.core.ApplicationId;
32import org.onosproject.core.CoreService;
33import org.onosproject.mastership.MastershipService;
Jian Lic38e9032018-08-09 17:08:38 +090034import org.onosproject.net.DeviceId;
Jian Li0b93b002018-07-31 13:41:08 +090035import org.onosproject.net.flow.DefaultTrafficSelector;
36import org.onosproject.net.flow.DefaultTrafficTreatment;
37import org.onosproject.net.flow.FlowEntry;
38import org.onosproject.net.flow.FlowRuleService;
39import org.onosproject.net.flow.TrafficSelector;
40import org.onosproject.net.flow.TrafficTreatment;
Jian Lib7873422018-08-18 22:34:39 +090041import org.onosproject.net.flow.criteria.Criterion;
Jian Li0b93b002018-07-31 13:41:08 +090042import org.onosproject.net.flow.criteria.IPCriterion;
43import org.onosproject.net.packet.DefaultOutboundPacket;
44import org.onosproject.net.packet.InboundPacket;
45import org.onosproject.net.packet.OutboundPacket;
46import org.onosproject.net.packet.PacketContext;
47import org.onosproject.net.packet.PacketProcessor;
48import org.onosproject.net.packet.PacketService;
49import org.onosproject.openstacknetworking.api.InstancePort;
50import org.onosproject.openstacknetworking.api.InstancePortService;
51import org.onosproject.openstacknetworking.api.OpenstackFlowRuleService;
52import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
Jian Lic38e9032018-08-09 17:08:38 +090053import org.onosproject.openstacknode.api.OpenstackNode;
Jian Li0b93b002018-07-31 13:41:08 +090054import org.onosproject.openstacknode.api.OpenstackNodeService;
Jian Lia1186772018-07-27 18:06:41 +090055import org.onosproject.openstacktroubleshoot.api.OpenstackTroubleshootService;
Jian Li0b93b002018-07-31 13:41:08 +090056import org.onosproject.openstacktroubleshoot.api.Reachability;
57import org.onosproject.store.serializers.KryoNamespaces;
58import org.onosproject.store.service.AtomicCounter;
59import org.onosproject.store.service.ConsistentMap;
60import org.onosproject.store.service.Serializer;
61import org.onosproject.store.service.StorageService;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070062import org.osgi.service.component.annotations.Activate;
63import org.osgi.service.component.annotations.Component;
64import org.osgi.service.component.annotations.Deactivate;
65import org.osgi.service.component.annotations.Reference;
66import org.osgi.service.component.annotations.ReferenceCardinality;
Jian Li0b93b002018-07-31 13:41:08 +090067import org.slf4j.Logger;
68import org.slf4j.LoggerFactory;
69
70import java.nio.ByteBuffer;
Jian Lic38e9032018-08-09 17:08:38 +090071import java.util.Optional;
Jian Li0b93b002018-07-31 13:41:08 +090072import java.util.Set;
73import java.util.concurrent.ExecutorService;
74import java.util.function.BooleanSupplier;
75import java.util.function.Predicate;
Jian Li0b93b002018-07-31 13:41:08 +090076
77import static com.google.common.base.Preconditions.checkNotNull;
78import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
79import static org.onlab.packet.Ethernet.TYPE_IPV4;
80import static org.onlab.packet.ICMP.TYPE_ECHO_REPLY;
81import static org.onlab.packet.ICMP.TYPE_ECHO_REQUEST;
82import static org.onlab.util.Tools.groupedThreads;
83import static org.onosproject.net.PortNumber.TABLE;
84import static org.onosproject.net.flow.FlowEntry.FlowEntryState.ADDED;
85import static org.onosproject.net.flow.criteria.Criterion.Type.IPV4_DST;
86import static org.onosproject.net.flow.criteria.Criterion.Type.IPV4_SRC;
87import static org.onosproject.openstacknetworking.api.Constants.ACL_TABLE;
Jian Lic38e9032018-08-09 17:08:38 +090088import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_EXTERNAL_ROUTER_MAC;
Jian Li0b93b002018-07-31 13:41:08 +090089import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_GATEWAY_MAC;
90import static org.onosproject.openstacknetworking.api.Constants.FORWARDING_TABLE;
Jian Lic38e9032018-08-09 17:08:38 +090091import static org.onosproject.openstacknetworking.api.Constants.GW_COMMON_TABLE;
Jian Li0b93b002018-07-31 13:41:08 +090092import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
93import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_ICMP_PROBE_RULE;
94import static org.onosproject.openstacknetworking.api.Constants.VTAG_TABLE;
95import static org.onosproject.openstacknetworking.api.InstancePort.State.ACTIVE;
Jian Lic38e9032018-08-09 17:08:38 +090096import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.GATEWAY;
Jian Li0b93b002018-07-31 13:41:08 +090097import static org.onosproject.openstacktroubleshoot.util.OpenstackTroubleshootUtil.getSegId;
Jian Lia1186772018-07-27 18:06:41 +090098
99/**
100 * Implementation of openstack troubleshoot app.
101 */
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700102@Component(immediate = true, service = OpenstackTroubleshootService.class)
Jian Lia1186772018-07-27 18:06:41 +0900103public class OpenstackTroubleshootManager implements OpenstackTroubleshootService {
Jian Li0b93b002018-07-31 13:41:08 +0900104
105 private final Logger log = LoggerFactory.getLogger(getClass());
106
107 private static final int VID_TAG_RULE_INSTALL_TIMEOUT_MS = 1000;
108 private static final int ICMP_RULE_INSTALL_TIMEOUT_MS = 1000;
109 private static final int ICMP_REPLY_TIMEOUT_MS = 3000;
110 private static final String SERIALIZER_NAME = "openstack-troubleshoot";
111 private static final byte TTL = 64;
112 private static final short INITIAL_SEQ = 1;
113 private static final short MAX_ICMP_GEN = 3;
114 private static final int PREFIX_LENGTH = 32;
115 private static final int ICMP_PROCESSOR_PRIORITY = 99;
116
Jian Lic38e9032018-08-09 17:08:38 +0900117 private static final MacAddress LOCAL_MAC = MacAddress.valueOf("11:22:33:44:55:66");
118
Jian Li0b93b002018-07-31 13:41:08 +0900119 private static final String ICMP_COUNTER_NAME = "icmp-id-counter";
120
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700121 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li0b93b002018-07-31 13:41:08 +0900122 protected CoreService coreService;
123
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700124 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li0b93b002018-07-31 13:41:08 +0900125 protected PacketService packetService;
126
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700127 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li0b93b002018-07-31 13:41:08 +0900128 protected FlowRuleService flowRuleService;
129
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700130 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li0b93b002018-07-31 13:41:08 +0900131 protected StorageService storageService;
132
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700133 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li0b93b002018-07-31 13:41:08 +0900134 protected LeadershipService leadershipService;
135
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700136 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li0b93b002018-07-31 13:41:08 +0900137 protected MastershipService mastershipService;
138
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700139 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li0b93b002018-07-31 13:41:08 +0900140 protected ClusterService clusterService;
141
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700142 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li0b93b002018-07-31 13:41:08 +0900143 protected OpenstackNodeService osNodeService;
144
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700145 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li0b93b002018-07-31 13:41:08 +0900146 protected OpenstackNetworkService osNetworkService;
147
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700148 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li0b93b002018-07-31 13:41:08 +0900149 protected OpenstackFlowRuleService osFlowRuleService;
150
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700151 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li0b93b002018-07-31 13:41:08 +0900152 protected InstancePortService instancePortService;
153
154 private final ExecutorService eventExecutor = newSingleThreadScheduledExecutor(
155 groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
156 private final InternalPacketProcessor packetProcessor = new InternalPacketProcessor();
157 private ConsistentMap<String, Reachability> icmpReachabilityMap;
158 private AtomicCounter icmpIdCounter;
159
160 private static final KryoNamespace SERIALIZER_DEFAULT_MAP = KryoNamespace.newBuilder()
161 .register(KryoNamespaces.API)
162 .register(Reachability.class)
163 .register(DefaultReachability.class)
164 .build();
165
166 private Set<String> icmpIds = Sets.newConcurrentHashSet();
167
168 private ApplicationId appId;
169 private NodeId localNodeId;
170
171 @Activate
172 protected void activate() {
173
174 appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
175 packetService.addProcessor(packetProcessor,
176 PacketProcessor.director(ICMP_PROCESSOR_PRIORITY));
177
178 localNodeId = clusterService.getLocalNode().id();
179 leadershipService.runForLeadership(appId.name());
180
181 icmpReachabilityMap = storageService.<String, Reachability>consistentMapBuilder()
182 .withSerializer(Serializer.using(SERIALIZER_DEFAULT_MAP))
183 .withName(SERIALIZER_NAME)
184 .withApplicationId(appId)
185 .build();
186
187 icmpIdCounter = storageService.getAtomicCounter(ICMP_COUNTER_NAME);
188
189 log.info("Started");
190 }
191
192 @Deactivate
193 protected void deactivate() {
194
195 packetService.removeProcessor(packetProcessor);
196 leadershipService.withdraw(appId.name());
197 eventExecutor.shutdown();
198
199 log.info("Stopped");
200 }
201
Jian Lia1186772018-07-27 18:06:41 +0900202 @Override
Jian Lie189c1c2018-08-08 15:55:08 +0900203 public Reachability probeEastWest(InstancePort srcPort, InstancePort dstPort) {
Jian Li0b93b002018-07-31 13:41:08 +0900204
205 Reachability.Builder rBuilder = DefaultReachability.builder()
Jian Lie189c1c2018-08-08 15:55:08 +0900206 .srcIp(srcPort.ipAddress())
207 .dstIp(dstPort.ipAddress());
Jian Li0b93b002018-07-31 13:41:08 +0900208
Jian Lie189c1c2018-08-08 15:55:08 +0900209 if (srcPort.equals(dstPort)) {
Jian Li0b93b002018-07-31 13:41:08 +0900210 // self probing should always return true
211 rBuilder.isReachable(true);
212 return rBuilder.build();
213 } else {
Jian Li0b93b002018-07-31 13:41:08 +0900214 if (srcPort.state() == ACTIVE && dstPort.state() == ACTIVE) {
215
Jian Lic38e9032018-08-09 17:08:38 +0900216 // if the two ports are located in different types of networks,
217 // we immediately return unreachable state
218 if (!osNetworkService.networkType(srcPort.networkId())
219 .equals(osNetworkService.networkType(dstPort.networkId()))) {
220 rBuilder.isReachable(false);
221 return rBuilder.build();
222 }
223
Jian Li0b93b002018-07-31 13:41:08 +0900224 // install flow rules to enforce ICMP_REQUEST to be tagged and direct to ACL table
225 eventExecutor.execute(() -> setVidTagRule(srcPort, true));
226
227 // install flow rules to enforce forwarding ICMP_REPLY to controller
Jian Lic38e9032018-08-09 17:08:38 +0900228 eventExecutor.execute(() -> setEastWestIcmpReplyRule(srcPort, true));
Jian Li0b93b002018-07-31 13:41:08 +0900229
230 timeoutPredicate(1, VID_TAG_RULE_INSTALL_TIMEOUT_MS,
231 this::checkVidTagRule, srcPort.ipAddress().toString());
232
233 timeoutPredicate(1, ICMP_RULE_INSTALL_TIMEOUT_MS,
Jian Lic38e9032018-08-09 17:08:38 +0900234 this::checkEastWestIcmpReplyRule, srcPort.ipAddress().toString());
Jian Li0b93b002018-07-31 13:41:08 +0900235
236 // send out ICMP ECHO request
Jian Lic38e9032018-08-09 17:08:38 +0900237 sendIcmpEchoRequest(srcPort, dstPort, null, Direction.EAST_WEST);
Jian Li0b93b002018-07-31 13:41:08 +0900238
239 BooleanSupplier checkReachability = () -> icmpReachabilityMap.asJavaMap()
240 .values().stream().allMatch(Reachability::isReachable);
241
242 timeoutSupplier(1, ICMP_REPLY_TIMEOUT_MS, checkReachability);
243
244 // uninstall ICMP_REQUEST VID tagging rules
245 eventExecutor.execute(() -> setVidTagRule(srcPort, false));
246
247 // uninstall ICMP_REPLY enforcing rules
Jian Lic38e9032018-08-09 17:08:38 +0900248 eventExecutor.execute(() -> setEastWestIcmpReplyRule(srcPort, false));
Jian Li0b93b002018-07-31 13:41:08 +0900249
250 return icmpReachabilityMap.asJavaMap()
251 .get(String.valueOf(icmpIdCounter.get()));
252
253 } else {
254 rBuilder.isReachable(false);
255 return rBuilder.build();
256 }
257 }
258 }
259
260 @Override
Jian Lic38e9032018-08-09 17:08:38 +0900261 public Reachability probeNorthSouth(InstancePort port) {
262 Optional<OpenstackNode> gw = osNodeService.completeNodes(GATEWAY).stream().findFirst();
Jian Li0b93b002018-07-31 13:41:08 +0900263
Jian Lic38e9032018-08-09 17:08:38 +0900264 if (!gw.isPresent()) {
265 log.warn("Gateway is not available to troubleshoot north-south traffic.");
266 return null;
Jian Li0b93b002018-07-31 13:41:08 +0900267 }
268
Jian Lic38e9032018-08-09 17:08:38 +0900269 // install flow rules to enforce forwarding ICMP_REPLY to controller
270 eventExecutor.execute(() -> setNorthSouthIcmpReplyRule(port, gw.get(), true));
271
272 timeoutPredicate(1, ICMP_RULE_INSTALL_TIMEOUT_MS,
273 this::checkNorthSouthIcmpReplyRule, port.ipAddress().toString());
274
275 // send out ICMP ECHO request
276 sendIcmpEchoRequest(null, port, gw.get(), Direction.NORTH_SOUTH);
277
278 BooleanSupplier checkReachability = () -> icmpReachabilityMap.asJavaMap()
279 .values().stream().allMatch(Reachability::isReachable);
280
281 timeoutSupplier(1, ICMP_REPLY_TIMEOUT_MS, checkReachability);
282
283 // uninstall ICMP_REPLY enforcing rules
284 eventExecutor.execute(() -> setNorthSouthIcmpReplyRule(port, gw.get(), false));
285
286 return icmpReachabilityMap.asJavaMap().get(String.valueOf(icmpIdCounter.get()));
Jian Li0b93b002018-07-31 13:41:08 +0900287 }
288
289 /**
Jian Lic38e9032018-08-09 17:08:38 +0900290 * Checks whether east-west ICMP reply rule is added or not.
Jian Li0b93b002018-07-31 13:41:08 +0900291 *
Jian Lib7873422018-08-18 22:34:39 +0900292 * @param dstIp IP address
Jian Li0b93b002018-07-31 13:41:08 +0900293 * @return true if ICMP reply rule is added, false otherwise
294 */
Jian Lib7873422018-08-18 22:34:39 +0900295 private boolean checkEastWestIcmpReplyRule(String dstIp) {
296 return checkFlowRule(dstIp, IPV4_DST);
Jian Li0b93b002018-07-31 13:41:08 +0900297 }
298
299 /**
Jian Lic38e9032018-08-09 17:08:38 +0900300 * Checks whether north-south ICMP reply rule is added or not.
Jian Li0b93b002018-07-31 13:41:08 +0900301 *
Jian Lib7873422018-08-18 22:34:39 +0900302 * @param srcIp IP address
Jian Lic38e9032018-08-09 17:08:38 +0900303 * @return true if ICMP reply rule is added, false otherwise
Jian Li0b93b002018-07-31 13:41:08 +0900304 */
Jian Lib7873422018-08-18 22:34:39 +0900305 private boolean checkNorthSouthIcmpReplyRule(String srcIp) {
306 return checkFlowRule(srcIp, IPV4_SRC);
Jian Li0b93b002018-07-31 13:41:08 +0900307 }
308
309 /**
310 * Checks whether ICMP request VID tagging rule is added or not.
311 *
312 * @param srcIp source IP address
313 * @return true if the rule is added, false otherwise
314 */
315 private boolean checkVidTagRule(String srcIp) {
Jian Lib7873422018-08-18 22:34:39 +0900316 return checkFlowRule(srcIp, IPV4_SRC);
317 }
318
319 /**
320 * Checks whether flow rules with the given IP and criterion are added or not.
321 *
322 * @param ip IP address
323 * @param ipType IP criterion type (IPV4_DST or IPV4_SRC)
324 * @return true if the flow rule is added, false otherwise
325 */
326 private boolean checkFlowRule(String ip, Criterion.Type ipType) {
Jian Li0b93b002018-07-31 13:41:08 +0900327 for (FlowEntry entry : flowRuleService.getFlowEntriesById(appId)) {
328 TrafficSelector selector = entry.selector();
329
Jian Lib7873422018-08-18 22:34:39 +0900330 IPCriterion ipCriterion = (IPCriterion) selector.getCriterion(ipType);
Jian Li0b93b002018-07-31 13:41:08 +0900331
Jian Lib7873422018-08-18 22:34:39 +0900332 if (ipCriterion != null &&
333 ip.equals(ipCriterion.ip().address().toString()) &&
Jian Li0b93b002018-07-31 13:41:08 +0900334 entry.state() == ADDED) {
335 return true;
336 }
337 }
Jian Li0b93b002018-07-31 13:41:08 +0900338 return false;
339 }
340
341 /**
Jian Li0b93b002018-07-31 13:41:08 +0900342 * Installs/uninstalls a flow rule to match ingress fake ICMP request packets,
343 * and tags VNI/VID, direct the tagged packet to ACL table.
344 *
345 * @param port instance port
346 * @param install installation flag
347 */
348 private void setVidTagRule(InstancePort port, boolean install) {
349 TrafficSelector selector = DefaultTrafficSelector.builder()
350 .matchEthType(Ethernet.TYPE_IPV4)
351 .matchIPSrc(IpPrefix.valueOf(port.ipAddress(), PREFIX_LENGTH))
352 .build();
353
354 TrafficTreatment.Builder tb = DefaultTrafficTreatment.builder()
355 .setTunnelId(getSegId(osNetworkService, port))
356 .transition(ACL_TABLE);
357
358 osFlowRuleService.setRule(
359 appId,
360 port.deviceId(),
361 selector,
362 tb.build(),
363 PRIORITY_ICMP_PROBE_RULE,
364 VTAG_TABLE,
365 install);
366 }
367
368 /**
Jian Lic38e9032018-08-09 17:08:38 +0900369 * Installs/uninstalls a flow rule to match north-south ICMP reply packets,
370 * direct all ICMP reply packets to the controller.
Jian Li0b93b002018-07-31 13:41:08 +0900371 *
Jian Lic38e9032018-08-09 17:08:38 +0900372 * @param port instance port
373 * @param gw gateway node
Jian Li0b93b002018-07-31 13:41:08 +0900374 * @param install installation flag
375 */
Jian Lic38e9032018-08-09 17:08:38 +0900376 private void setNorthSouthIcmpReplyRule(InstancePort port, OpenstackNode gw,
377 boolean install) {
378 TrafficSelector selector = DefaultTrafficSelector.builder()
379 .matchEthType(Ethernet.TYPE_IPV4)
380 .matchIPSrc(IpPrefix.valueOf(port.ipAddress(), PREFIX_LENGTH))
381 .matchIPProtocol(IPv4.PROTOCOL_ICMP)
382 .matchIcmpType(ICMP.TYPE_ECHO_REPLY)
383 .matchTunnelId(getSegId(osNetworkService, port))
384 .build();
385
386 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
387 .setIpSrc(instancePortService.floatingIp(port.portId()))
388 .setEthSrc(port.macAddress())
389 .setEthDst(DEFAULT_EXTERNAL_ROUTER_MAC)
390 .punt()
391 .build();
392
393 osFlowRuleService.setRule(
394 appId,
395 gw.intgBridge(),
396 selector,
397 treatment,
398 PRIORITY_ICMP_PROBE_RULE,
399 GW_COMMON_TABLE,
400 install);
Jian Li0b93b002018-07-31 13:41:08 +0900401 }
402
403 /**
Jian Lic38e9032018-08-09 17:08:38 +0900404 * Installs/uninstalls a flow rule to match east-west ICMP reply packets,
405 * direct all ICMP reply packets to the controller.
Jian Li0b93b002018-07-31 13:41:08 +0900406 *
Jian Lic38e9032018-08-09 17:08:38 +0900407 * @param port instance port
Jian Li0b93b002018-07-31 13:41:08 +0900408 * @param install installation flag
409 */
Jian Lic38e9032018-08-09 17:08:38 +0900410 private void setEastWestIcmpReplyRule(InstancePort port, boolean install) {
Jian Li0b93b002018-07-31 13:41:08 +0900411 TrafficSelector selector = DefaultTrafficSelector.builder()
412 .matchEthType(Ethernet.TYPE_IPV4)
413 .matchIPDst(IpPrefix.valueOf(port.ipAddress(), PREFIX_LENGTH))
414 .matchIPProtocol(IPv4.PROTOCOL_ICMP)
415 .matchIcmpType(ICMP.TYPE_ECHO_REPLY)
416 .build();
417
418 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
419 .punt()
420 .build();
421
422 osFlowRuleService.setRule(
423 appId,
424 port.deviceId(),
425 selector,
426 treatment,
427 PRIORITY_ICMP_PROBE_RULE,
428 FORWARDING_TABLE,
429 install);
430 }
431
432 /**
433 * Sends out ICMP ECHO REQUEST to destined VM.
434 *
435 * @param srcPort source instance port
436 * @param dstPort destination instance port
437 */
Jian Lic38e9032018-08-09 17:08:38 +0900438 private void sendIcmpEchoRequest(InstancePort srcPort, InstancePort dstPort,
439 OpenstackNode gateway, Direction direction) {
Jian Li0b93b002018-07-31 13:41:08 +0900440
441 short icmpSeq = INITIAL_SEQ;
442
443 short icmpId = (short) icmpIdCounter.incrementAndGet();
444
445 for (int i = 0; i < MAX_ICMP_GEN; i++) {
Jian Lic38e9032018-08-09 17:08:38 +0900446 packetService.emit(buildIcmpOutputPacket(srcPort, dstPort, gateway,
447 icmpId, icmpSeq, direction));
Jian Li0b93b002018-07-31 13:41:08 +0900448 icmpSeq++;
449 }
450 }
451
452 /**
453 * Builds ICMP Outbound packet.
454 *
455 * @param srcPort source instance port
456 * @param dstPort destination instance port
457 * @param icmpId ICMP identifier
458 * @param icmpSeq ICMP sequence number
459 */
460 private OutboundPacket buildIcmpOutputPacket(InstancePort srcPort,
461 InstancePort dstPort,
Jian Lic38e9032018-08-09 17:08:38 +0900462 OpenstackNode gateway,
Jian Li0b93b002018-07-31 13:41:08 +0900463 short icmpId,
Jian Lic38e9032018-08-09 17:08:38 +0900464 short icmpSeq,
465 Direction direction) {
Jian Li0b93b002018-07-31 13:41:08 +0900466
Jian Lic38e9032018-08-09 17:08:38 +0900467 Ethernet ethFrame;
468 IpAddress srcIp;
469 IpAddress dstIp;
470 DeviceId deviceId;
471
472 if (direction == Direction.EAST_WEST) {
473 ethFrame = constructEastWestIcmpPacket(srcPort, dstPort, icmpId, icmpSeq);
474 srcIp = srcPort.ipAddress();
475 dstIp = dstPort.ipAddress();
476 deviceId = srcPort.deviceId();
477 } else if (direction == Direction.NORTH_SOUTH) {
478 ethFrame = constructNorthSouthIcmpPacket(dstPort, icmpId, icmpSeq);
479 srcIp = clusterService.getLocalNode().ip();
480 dstIp = instancePortService.floatingIp(dstPort.portId());
481 deviceId = gateway.intgBridge();
482 } else {
483 log.warn("Invalid traffic direction {}", direction);
484 return null;
485 }
Jian Li0b93b002018-07-31 13:41:08 +0900486
487 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
488
489 // we send out the packet to ingress table (index is 0) of source OVS
490 // to enforce the Outbound packet to go through the ingress and egress
491 // pipeline
492 tBuilder.setOutput(TABLE);
493
494 Reachability reachability = DefaultReachability.builder()
Jian Lic38e9032018-08-09 17:08:38 +0900495 .srcIp(srcIp)
496 .dstIp(dstIp)
Jian Li0b93b002018-07-31 13:41:08 +0900497 .isReachable(false)
498 .build();
499
500 icmpReachabilityMap.put(String.valueOf(icmpId), reachability);
501 icmpIds.add(String.valueOf(icmpId));
502
503 return new DefaultOutboundPacket(
Jian Lic38e9032018-08-09 17:08:38 +0900504 deviceId,
Jian Li0b93b002018-07-31 13:41:08 +0900505 tBuilder.build(),
506 ByteBuffer.wrap(ethFrame.serialize()));
507 }
508
509 /**
510 * Constructs an ICMP packet with given source and destination IP/MAC.
511 *
Jian Lic38e9032018-08-09 17:08:38 +0900512 * @param srcIp source IP address
513 * @param dstIp destination IP address
514 * @param srcMac source MAC address
515 * @param dstMac destination MAC address
Jian Li0b93b002018-07-31 13:41:08 +0900516 * @param icmpId ICMP identifier
517 * @param icmpSeq ICMP sequence number
518 * @return an ethernet frame which contains ICMP payload
519 */
Jian Lic38e9032018-08-09 17:08:38 +0900520 private Ethernet constructIcmpPacket(IpAddress srcIp, IpAddress dstIp,
521 MacAddress srcMac, MacAddress dstMac,
Jian Li0b93b002018-07-31 13:41:08 +0900522 short icmpId, short icmpSeq) {
Jian Lic38e9032018-08-09 17:08:38 +0900523
Jian Li0b93b002018-07-31 13:41:08 +0900524 // Ethernet frame
525 Ethernet ethFrame = new Ethernet();
526
527 ethFrame.setEtherType(TYPE_IPV4);
Jian Lic38e9032018-08-09 17:08:38 +0900528 ethFrame.setSourceMACAddress(srcMac);
529 ethFrame.setDestinationMACAddress(dstMac);
Jian Li0b93b002018-07-31 13:41:08 +0900530
531 // IP packet
532 IPv4 iPacket = new IPv4();
Jian Lic38e9032018-08-09 17:08:38 +0900533 iPacket.setDestinationAddress(dstIp.toString());
534 iPacket.setSourceAddress(srcIp.toString());
Jian Li0b93b002018-07-31 13:41:08 +0900535 iPacket.setTtl(TTL);
536 iPacket.setProtocol(IPv4.PROTOCOL_ICMP);
537
538 // ICMP packet
539 ICMP icmp = new ICMP();
540 icmp.setIcmpType(TYPE_ECHO_REQUEST)
541 .setIcmpCode(TYPE_ECHO_REQUEST)
542 .resetChecksum();
543
544 // ICMP ECHO packet
545 ICMPEcho icmpEcho = new ICMPEcho();
546 icmpEcho.setIdentifier(icmpId)
547 .setSequenceNum(icmpSeq);
548
549 ByteBuffer byteBufferIcmpEcho = ByteBuffer.wrap(icmpEcho.serialize());
550
551 try {
552 icmp.setPayload(ICMPEcho.deserializer().deserialize(byteBufferIcmpEcho.array(),
553 0, ICMPEcho.ICMP_ECHO_HEADER_LENGTH));
554 } catch (DeserializationException e) {
555 log.warn("Failed to deserialize ICMP ECHO REQUEST packet");
556 }
557
558 ByteBuffer byteBufferIcmp = ByteBuffer.wrap(icmp.serialize());
559
560 try {
561 iPacket.setPayload(ICMP.deserializer().deserialize(byteBufferIcmp.array(),
562 0,
563 byteBufferIcmp.array().length));
564 } catch (DeserializationException e) {
565 log.warn("Failed to deserialize ICMP packet");
566 }
567
568 ethFrame.setPayload(iPacket);
569
570 return ethFrame;
571 }
572
573 /**
Jian Lic38e9032018-08-09 17:08:38 +0900574 * Constructs an east-west ICMP packet with given source and destination IP/MAC.
575 *
576 * @param srcPort source instance port
577 * @param dstPort destination instance port
578 * @param icmpId ICMP identifier
579 * @param icmpSeq ICMP sequence number
580 * @return an ethernet frame which contains ICMP payload
581 */
582 private Ethernet constructEastWestIcmpPacket(InstancePort srcPort,
583 InstancePort dstPort,
584 short icmpId, short icmpSeq) {
585 boolean isRemote = true;
586
587 if (srcPort.deviceId().equals(dstPort.deviceId()) &&
588 osNetworkService.gatewayIp(srcPort.portId())
589 .equals(osNetworkService.gatewayIp(dstPort.portId()))) {
590 isRemote = false;
591 }
592
593 // if the source and destination VMs are located in different OVS,
594 // we will assign fake gateway MAC as the destination MAC
595 MacAddress dstMac = isRemote ? DEFAULT_GATEWAY_MAC : dstPort.macAddress();
596
597 return constructIcmpPacket(srcPort.ipAddress(), dstPort.ipAddress(),
598 srcPort.macAddress(), dstMac, icmpId, icmpSeq);
599 }
600
601 /**
602 * Constructs a north-south ICMP packet with the given destination IP/MAC.
603 *
604 * @param dstPort destination instance port
605 * @param icmpId ICMP identifier
606 * @param icmpSeq ICMP sequence number
607 * @return an ethernet frame which contains ICMP payload
608 */
609 private Ethernet constructNorthSouthIcmpPacket(InstancePort dstPort,
610 short icmpId, short icmpSeq) {
611
612 IpAddress localIp = clusterService.getLocalNode().ip();
613 IpAddress fip = instancePortService.floatingIp(dstPort.portId());
614
615 if (fip != null) {
616 return constructIcmpPacket(localIp, fip, LOCAL_MAC, dstPort.macAddress(),
617 icmpId, icmpSeq);
618 } else {
619 return null;
620 }
621 }
622
623 /**
Jian Li0b93b002018-07-31 13:41:08 +0900624 * Handles ICMP ECHO REPLY packets.
625 *
626 * @param ipPacket IP packet
627 * @param icmp ICMP packet
628 */
629 private void handleIcmpEchoReply(IPv4 ipPacket, ICMP icmp) {
630
631 String icmpKey = icmpId(icmp);
632
633 String srcIp = IPv4.fromIPv4Address(ipPacket.getDestinationAddress());
634 String dstIp = IPv4.fromIPv4Address(ipPacket.getSourceAddress());
635
636 Reachability reachability = DefaultReachability.builder()
637 .srcIp(IpAddress.valueOf(srcIp))
638 .dstIp(IpAddress.valueOf(dstIp))
639 .isReachable(false)
640 .build();
641
642 icmpReachabilityMap.computeIfPresent(icmpKey, (key, value) -> {
643 if (value.equals(reachability)) {
644
645 log.debug("src: {}, dst: {} is reachable!", value.dstIp(), value.srcIp());
646
647 return DefaultReachability.builder()
648 .srcIp(IpAddress.valueOf(srcIp))
649 .dstIp(IpAddress.valueOf(dstIp))
650 .isReachable(true)
651 .build();
652 }
653 return reachability;
654 });
655 }
656
657 /**
658 * Obtains an unique ICMP key.
659 *
660 * @param icmp ICMP packet
661 * @return ICMP key
662 */
663 private String icmpId(ICMP icmp) {
664 ICMPEcho echo = (ICMPEcho) icmp.getPayload();
665 checkNotNull(echo);
666
667 short icmpId = echo.getIdentifier();
668
669 return String.valueOf(icmpId);
670 }
671
672 /**
673 * Holds the current thread unit the timeout expires, during the hold the
674 * thread periodically execute the given method.
675 *
676 * @param count count of unit
677 * @param unit unit
678 * @param predicate predicate
679 * @param predicateArg predicate argument
680 */
681 private void timeoutPredicate(long count, int unit,
682 Predicate<String> predicate, String predicateArg) {
683 long timeoutExpiredMs = System.currentTimeMillis() + unit * count;
684
685 while (true) {
686
687 long waitMs = timeoutExpiredMs - System.currentTimeMillis();
688
689 if (predicate.test(predicateArg)) {
690 break;
691 }
692
693 if (waitMs <= 0) {
694 break;
695 }
696 }
697 }
698
699 /**
700 * Holds the current thread unit the timeout expires, during the hold the
701 * thread periodically execute the given method.
702 *
703 * @param count count of unit
704 * @param unit unit
705 * @param supplier boolean supplier
706 */
707 private void timeoutSupplier(long count, int unit, BooleanSupplier supplier) {
708 long timeoutExpiredMs = System.currentTimeMillis() + unit * count;
709
710 while (true) {
711
712 long waitMs = timeoutExpiredMs - System.currentTimeMillis();
713
714 if (supplier.getAsBoolean()) {
715 break;
716 }
717
718 if (waitMs <= 0) {
719 break;
720 }
721 }
722 }
723
724 private class InternalPacketProcessor implements PacketProcessor {
725
726 @Override
727 public void process(PacketContext context) {
728 if (context.isHandled()) {
729 return;
730 }
731
732 InboundPacket pkt = context.inPacket();
733 Ethernet ethernet = pkt.parsed();
734 if (ethernet == null || ethernet.getEtherType() == Ethernet.TYPE_ARP) {
735 return;
736 }
737
738 IPv4 iPacket = (IPv4) ethernet.getPayload();
739 if (iPacket.getProtocol() == IPv4.PROTOCOL_ICMP) {
740 eventExecutor.execute(() -> processIcmpPacket(context, ethernet));
741 }
742 }
743
744 /**
745 * Processes the received ICMP packet.
746 *
747 * @param context packet context
748 * @param ethernet ethernet
749 */
750 private void processIcmpPacket(PacketContext context, Ethernet ethernet) {
751 IPv4 ipPacket = (IPv4) ethernet.getPayload();
752 ICMP icmp = (ICMP) ipPacket.getPayload();
753 log.trace("Processing ICMP packet source MAC:{}, source IP:{}," +
754 "dest MAC:{}, dest IP:{}",
755 ethernet.getSourceMAC(),
756 IpAddress.valueOf(ipPacket.getSourceAddress()),
757 ethernet.getDestinationMAC(),
758 IpAddress.valueOf(ipPacket.getDestinationAddress()));
759
760 String icmpId = icmpId(icmp);
761
762 // if the ICMP ID is not contained in ICMP ID set, we do not handle it
763 if (!icmpIds.contains(icmpId)) {
764 return;
765 }
766
767 switch (icmp.getIcmpType()) {
768 case TYPE_ECHO_REPLY:
769 handleIcmpEchoReply(ipPacket, icmp);
770 context.block();
771 icmpIds.remove(icmpId);
772 break;
773 default:
774 break;
775 }
776 }
Jian Lia1186772018-07-27 18:06:41 +0900777 }
778}