blob: 13e95797b71ca0953c3bea644ebc02aa84fb3245 [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;
19import 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;
24import org.apache.felix.scr.annotations.Service;
25import org.onlab.packet.DeserializationException;
26import org.onlab.packet.Ethernet;
27import org.onlab.packet.ICMP;
28import org.onlab.packet.ICMPEcho;
29import org.onlab.packet.IPv4;
30import org.onlab.packet.IpAddress;
31import org.onlab.packet.IpPrefix;
Jian Lic38e9032018-08-09 17:08:38 +090032import org.onlab.packet.MacAddress;
Jian Li0b93b002018-07-31 13:41:08 +090033import org.onlab.util.KryoNamespace;
34import org.onosproject.cluster.ClusterService;
35import org.onosproject.cluster.LeadershipService;
36import org.onosproject.cluster.NodeId;
37import org.onosproject.core.ApplicationId;
38import org.onosproject.core.CoreService;
39import org.onosproject.mastership.MastershipService;
Jian Lic38e9032018-08-09 17:08:38 +090040import org.onosproject.net.DeviceId;
Jian Li0b93b002018-07-31 13:41:08 +090041import org.onosproject.net.flow.DefaultTrafficSelector;
42import org.onosproject.net.flow.DefaultTrafficTreatment;
43import org.onosproject.net.flow.FlowEntry;
44import org.onosproject.net.flow.FlowRuleService;
45import org.onosproject.net.flow.TrafficSelector;
46import org.onosproject.net.flow.TrafficTreatment;
47import org.onosproject.net.flow.criteria.IPCriterion;
48import org.onosproject.net.packet.DefaultOutboundPacket;
49import org.onosproject.net.packet.InboundPacket;
50import org.onosproject.net.packet.OutboundPacket;
51import org.onosproject.net.packet.PacketContext;
52import org.onosproject.net.packet.PacketProcessor;
53import org.onosproject.net.packet.PacketService;
54import org.onosproject.openstacknetworking.api.InstancePort;
55import org.onosproject.openstacknetworking.api.InstancePortService;
56import org.onosproject.openstacknetworking.api.OpenstackFlowRuleService;
57import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
Jian Lic38e9032018-08-09 17:08:38 +090058import org.onosproject.openstacknode.api.OpenstackNode;
Jian Li0b93b002018-07-31 13:41:08 +090059import org.onosproject.openstacknode.api.OpenstackNodeService;
Jian Lia1186772018-07-27 18:06:41 +090060import org.onosproject.openstacktroubleshoot.api.OpenstackTroubleshootService;
Jian Li0b93b002018-07-31 13:41:08 +090061import org.onosproject.openstacktroubleshoot.api.Reachability;
62import org.onosproject.store.serializers.KryoNamespaces;
63import org.onosproject.store.service.AtomicCounter;
64import org.onosproject.store.service.ConsistentMap;
65import org.onosproject.store.service.Serializer;
66import org.onosproject.store.service.StorageService;
67import 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 */
Jian Li0b93b002018-07-31 13:41:08 +0900102@Component(immediate = true)
103@Service
Jian Lia1186772018-07-27 18:06:41 +0900104public class OpenstackTroubleshootManager implements OpenstackTroubleshootService {
Jian Li0b93b002018-07-31 13:41:08 +0900105
106 private final Logger log = LoggerFactory.getLogger(getClass());
107
108 private static final int VID_TAG_RULE_INSTALL_TIMEOUT_MS = 1000;
109 private static final int ICMP_RULE_INSTALL_TIMEOUT_MS = 1000;
110 private static final int ICMP_REPLY_TIMEOUT_MS = 3000;
111 private static final String SERIALIZER_NAME = "openstack-troubleshoot";
112 private static final byte TTL = 64;
113 private static final short INITIAL_SEQ = 1;
114 private static final short MAX_ICMP_GEN = 3;
115 private static final int PREFIX_LENGTH = 32;
116 private static final int ICMP_PROCESSOR_PRIORITY = 99;
117
Jian Lic38e9032018-08-09 17:08:38 +0900118 private static final MacAddress LOCAL_MAC = MacAddress.valueOf("11:22:33:44:55:66");
119
Jian Li0b93b002018-07-31 13:41:08 +0900120 private static final String ICMP_COUNTER_NAME = "icmp-id-counter";
121
122 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
123 protected CoreService coreService;
124
125 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
126 protected PacketService packetService;
127
128 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
129 protected FlowRuleService flowRuleService;
130
131 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
132 protected StorageService storageService;
133
134 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
135 protected LeadershipService leadershipService;
136
137 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
138 protected MastershipService mastershipService;
139
140 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
141 protected ClusterService clusterService;
142
143 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
144 protected OpenstackNodeService osNodeService;
145
146 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
147 protected OpenstackNetworkService osNetworkService;
148
149 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
150 protected OpenstackFlowRuleService osFlowRuleService;
151
152 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
153 protected InstancePortService instancePortService;
154
155 private final ExecutorService eventExecutor = newSingleThreadScheduledExecutor(
156 groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
157 private final InternalPacketProcessor packetProcessor = new InternalPacketProcessor();
158 private ConsistentMap<String, Reachability> icmpReachabilityMap;
159 private AtomicCounter icmpIdCounter;
160
161 private static final KryoNamespace SERIALIZER_DEFAULT_MAP = KryoNamespace.newBuilder()
162 .register(KryoNamespaces.API)
163 .register(Reachability.class)
164 .register(DefaultReachability.class)
165 .build();
166
167 private Set<String> icmpIds = Sets.newConcurrentHashSet();
168
169 private ApplicationId appId;
170 private NodeId localNodeId;
171
172 @Activate
173 protected void activate() {
174
175 appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
176 packetService.addProcessor(packetProcessor,
177 PacketProcessor.director(ICMP_PROCESSOR_PRIORITY));
178
179 localNodeId = clusterService.getLocalNode().id();
180 leadershipService.runForLeadership(appId.name());
181
182 icmpReachabilityMap = storageService.<String, Reachability>consistentMapBuilder()
183 .withSerializer(Serializer.using(SERIALIZER_DEFAULT_MAP))
184 .withName(SERIALIZER_NAME)
185 .withApplicationId(appId)
186 .build();
187
188 icmpIdCounter = storageService.getAtomicCounter(ICMP_COUNTER_NAME);
189
190 log.info("Started");
191 }
192
193 @Deactivate
194 protected void deactivate() {
195
196 packetService.removeProcessor(packetProcessor);
197 leadershipService.withdraw(appId.name());
198 eventExecutor.shutdown();
199
200 log.info("Stopped");
201 }
202
Jian Lia1186772018-07-27 18:06:41 +0900203 @Override
Jian Lie189c1c2018-08-08 15:55:08 +0900204 public Reachability probeEastWest(InstancePort srcPort, InstancePort dstPort) {
Jian Li0b93b002018-07-31 13:41:08 +0900205
206 Reachability.Builder rBuilder = DefaultReachability.builder()
Jian Lie189c1c2018-08-08 15:55:08 +0900207 .srcIp(srcPort.ipAddress())
208 .dstIp(dstPort.ipAddress());
Jian Li0b93b002018-07-31 13:41:08 +0900209
Jian Lie189c1c2018-08-08 15:55:08 +0900210 if (srcPort.equals(dstPort)) {
Jian Li0b93b002018-07-31 13:41:08 +0900211 // self probing should always return true
212 rBuilder.isReachable(true);
213 return rBuilder.build();
214 } else {
Jian Li0b93b002018-07-31 13:41:08 +0900215 if (srcPort.state() == ACTIVE && dstPort.state() == ACTIVE) {
216
Jian Lic38e9032018-08-09 17:08:38 +0900217 // if the two ports are located in different types of networks,
218 // we immediately return unreachable state
219 if (!osNetworkService.networkType(srcPort.networkId())
220 .equals(osNetworkService.networkType(dstPort.networkId()))) {
221 rBuilder.isReachable(false);
222 return rBuilder.build();
223 }
224
Jian Li0b93b002018-07-31 13:41:08 +0900225 // install flow rules to enforce ICMP_REQUEST to be tagged and direct to ACL table
226 eventExecutor.execute(() -> setVidTagRule(srcPort, true));
227
228 // install flow rules to enforce forwarding ICMP_REPLY to controller
Jian Lic38e9032018-08-09 17:08:38 +0900229 eventExecutor.execute(() -> setEastWestIcmpReplyRule(srcPort, true));
Jian Li0b93b002018-07-31 13:41:08 +0900230
231 timeoutPredicate(1, VID_TAG_RULE_INSTALL_TIMEOUT_MS,
232 this::checkVidTagRule, srcPort.ipAddress().toString());
233
234 timeoutPredicate(1, ICMP_RULE_INSTALL_TIMEOUT_MS,
Jian Lic38e9032018-08-09 17:08:38 +0900235 this::checkEastWestIcmpReplyRule, srcPort.ipAddress().toString());
Jian Li0b93b002018-07-31 13:41:08 +0900236
237 // send out ICMP ECHO request
Jian Lic38e9032018-08-09 17:08:38 +0900238 sendIcmpEchoRequest(srcPort, dstPort, null, Direction.EAST_WEST);
Jian Li0b93b002018-07-31 13:41:08 +0900239
240 BooleanSupplier checkReachability = () -> icmpReachabilityMap.asJavaMap()
241 .values().stream().allMatch(Reachability::isReachable);
242
243 timeoutSupplier(1, ICMP_REPLY_TIMEOUT_MS, checkReachability);
244
245 // uninstall ICMP_REQUEST VID tagging rules
246 eventExecutor.execute(() -> setVidTagRule(srcPort, false));
247
248 // uninstall ICMP_REPLY enforcing rules
Jian Lic38e9032018-08-09 17:08:38 +0900249 eventExecutor.execute(() -> setEastWestIcmpReplyRule(srcPort, false));
Jian Li0b93b002018-07-31 13:41:08 +0900250
251 return icmpReachabilityMap.asJavaMap()
252 .get(String.valueOf(icmpIdCounter.get()));
253
254 } else {
255 rBuilder.isReachable(false);
256 return rBuilder.build();
257 }
258 }
259 }
260
261 @Override
Jian Lic38e9032018-08-09 17:08:38 +0900262 public Reachability probeNorthSouth(InstancePort port) {
263 Optional<OpenstackNode> gw = osNodeService.completeNodes(GATEWAY).stream().findFirst();
Jian Li0b93b002018-07-31 13:41:08 +0900264
Jian Lic38e9032018-08-09 17:08:38 +0900265 if (!gw.isPresent()) {
266 log.warn("Gateway is not available to troubleshoot north-south traffic.");
267 return null;
Jian Li0b93b002018-07-31 13:41:08 +0900268 }
269
Jian Lic38e9032018-08-09 17:08:38 +0900270 // install flow rules to enforce forwarding ICMP_REPLY to controller
271 eventExecutor.execute(() -> setNorthSouthIcmpReplyRule(port, gw.get(), true));
272
273 timeoutPredicate(1, ICMP_RULE_INSTALL_TIMEOUT_MS,
274 this::checkNorthSouthIcmpReplyRule, port.ipAddress().toString());
275
276 // send out ICMP ECHO request
277 sendIcmpEchoRequest(null, port, gw.get(), Direction.NORTH_SOUTH);
278
279 BooleanSupplier checkReachability = () -> icmpReachabilityMap.asJavaMap()
280 .values().stream().allMatch(Reachability::isReachable);
281
282 timeoutSupplier(1, ICMP_REPLY_TIMEOUT_MS, checkReachability);
283
284 // uninstall ICMP_REPLY enforcing rules
285 eventExecutor.execute(() -> setNorthSouthIcmpReplyRule(port, gw.get(), false));
286
287 return icmpReachabilityMap.asJavaMap().get(String.valueOf(icmpIdCounter.get()));
Jian Li0b93b002018-07-31 13:41:08 +0900288 }
289
290 /**
Jian Lic38e9032018-08-09 17:08:38 +0900291 * Checks whether east-west ICMP reply rule is added or not.
Jian Li0b93b002018-07-31 13:41:08 +0900292 *
Jian Lic38e9032018-08-09 17:08:38 +0900293 * @param ip IP address
Jian Li0b93b002018-07-31 13:41:08 +0900294 * @return true if ICMP reply rule is added, false otherwise
295 */
Jian Lic38e9032018-08-09 17:08:38 +0900296 private boolean checkEastWestIcmpReplyRule(String ip) {
Jian Li0b93b002018-07-31 13:41:08 +0900297 for (FlowEntry entry : flowRuleService.getFlowEntriesById(appId)) {
298 TrafficSelector selector = entry.selector();
299
Jian Lic38e9032018-08-09 17:08:38 +0900300 IPCriterion ipCriterion = (IPCriterion) selector.getCriterion(IPV4_DST);
Jian Li0b93b002018-07-31 13:41:08 +0900301
Jian Lic38e9032018-08-09 17:08:38 +0900302 if (ipCriterion != null &&
303 ip.equals(ipCriterion.ip().address().toString()) &&
Jian Li0b93b002018-07-31 13:41:08 +0900304 entry.state() == ADDED) {
305 return true;
306 }
307 }
308
309 return false;
310 }
311
312 /**
Jian Lic38e9032018-08-09 17:08:38 +0900313 * Checks whether north-south ICMP reply rule is added or not.
Jian Li0b93b002018-07-31 13:41:08 +0900314 *
Jian Lic38e9032018-08-09 17:08:38 +0900315 * @param ip IP address
316 * @return true if ICMP reply rule is added, false otherwise
Jian Li0b93b002018-07-31 13:41:08 +0900317 */
Jian Lic38e9032018-08-09 17:08:38 +0900318 private boolean checkNorthSouthIcmpReplyRule(String ip) {
319 for (FlowEntry entry : flowRuleService.getFlowEntriesById(appId)) {
320 TrafficSelector selector = entry.selector();
Jian Li0b93b002018-07-31 13:41:08 +0900321
Jian Lic38e9032018-08-09 17:08:38 +0900322 IPCriterion ipCriterion = (IPCriterion) selector.getCriterion(IPV4_SRC);
323
324 if (ipCriterion != null &&
325 ip.equals(ipCriterion.ip().address().toString()) &&
326 entry.state() == ADDED) {
327 return true;
Jian Li0b93b002018-07-31 13:41:08 +0900328 }
329 }
330
Jian Lic38e9032018-08-09 17:08:38 +0900331 return false;
Jian Li0b93b002018-07-31 13:41:08 +0900332 }
333
334 /**
335 * Checks whether ICMP request VID tagging rule is added or not.
336 *
337 * @param srcIp source IP address
338 * @return true if the rule is added, false otherwise
339 */
340 private boolean checkVidTagRule(String srcIp) {
341 for (FlowEntry entry : flowRuleService.getFlowEntriesById(appId)) {
342 TrafficSelector selector = entry.selector();
343
344 IPCriterion srcIpCriterion = (IPCriterion) selector.getCriterion(IPV4_SRC);
345
346 if (srcIpCriterion != null &&
347 srcIp.equals(srcIpCriterion.ip().address().toString()) &&
348 entry.state() == ADDED) {
349 return true;
350 }
351 }
352
353 return false;
354 }
355
356 /**
Jian Li0b93b002018-07-31 13:41:08 +0900357 * Installs/uninstalls a flow rule to match ingress fake ICMP request packets,
358 * and tags VNI/VID, direct the tagged packet to ACL table.
359 *
360 * @param port instance port
361 * @param install installation flag
362 */
363 private void setVidTagRule(InstancePort port, boolean install) {
364 TrafficSelector selector = DefaultTrafficSelector.builder()
365 .matchEthType(Ethernet.TYPE_IPV4)
366 .matchIPSrc(IpPrefix.valueOf(port.ipAddress(), PREFIX_LENGTH))
367 .build();
368
369 TrafficTreatment.Builder tb = DefaultTrafficTreatment.builder()
370 .setTunnelId(getSegId(osNetworkService, port))
371 .transition(ACL_TABLE);
372
373 osFlowRuleService.setRule(
374 appId,
375 port.deviceId(),
376 selector,
377 tb.build(),
378 PRIORITY_ICMP_PROBE_RULE,
379 VTAG_TABLE,
380 install);
381 }
382
383 /**
Jian Lic38e9032018-08-09 17:08:38 +0900384 * Installs/uninstalls a flow rule to match north-south ICMP reply packets,
385 * direct all ICMP reply packets to the controller.
Jian Li0b93b002018-07-31 13:41:08 +0900386 *
Jian Lic38e9032018-08-09 17:08:38 +0900387 * @param port instance port
388 * @param gw gateway node
Jian Li0b93b002018-07-31 13:41:08 +0900389 * @param install installation flag
390 */
Jian Lic38e9032018-08-09 17:08:38 +0900391 private void setNorthSouthIcmpReplyRule(InstancePort port, OpenstackNode gw,
392 boolean install) {
393 TrafficSelector selector = DefaultTrafficSelector.builder()
394 .matchEthType(Ethernet.TYPE_IPV4)
395 .matchIPSrc(IpPrefix.valueOf(port.ipAddress(), PREFIX_LENGTH))
396 .matchIPProtocol(IPv4.PROTOCOL_ICMP)
397 .matchIcmpType(ICMP.TYPE_ECHO_REPLY)
398 .matchTunnelId(getSegId(osNetworkService, port))
399 .build();
400
401 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
402 .setIpSrc(instancePortService.floatingIp(port.portId()))
403 .setEthSrc(port.macAddress())
404 .setEthDst(DEFAULT_EXTERNAL_ROUTER_MAC)
405 .punt()
406 .build();
407
408 osFlowRuleService.setRule(
409 appId,
410 gw.intgBridge(),
411 selector,
412 treatment,
413 PRIORITY_ICMP_PROBE_RULE,
414 GW_COMMON_TABLE,
415 install);
Jian Li0b93b002018-07-31 13:41:08 +0900416 }
417
418 /**
Jian Lic38e9032018-08-09 17:08:38 +0900419 * Installs/uninstalls a flow rule to match east-west ICMP reply packets,
420 * direct all ICMP reply packets to the controller.
Jian Li0b93b002018-07-31 13:41:08 +0900421 *
Jian Lic38e9032018-08-09 17:08:38 +0900422 * @param port instance port
Jian Li0b93b002018-07-31 13:41:08 +0900423 * @param install installation flag
424 */
Jian Lic38e9032018-08-09 17:08:38 +0900425 private void setEastWestIcmpReplyRule(InstancePort port, boolean install) {
Jian Li0b93b002018-07-31 13:41:08 +0900426 TrafficSelector selector = DefaultTrafficSelector.builder()
427 .matchEthType(Ethernet.TYPE_IPV4)
428 .matchIPDst(IpPrefix.valueOf(port.ipAddress(), PREFIX_LENGTH))
429 .matchIPProtocol(IPv4.PROTOCOL_ICMP)
430 .matchIcmpType(ICMP.TYPE_ECHO_REPLY)
431 .build();
432
433 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
434 .punt()
435 .build();
436
437 osFlowRuleService.setRule(
438 appId,
439 port.deviceId(),
440 selector,
441 treatment,
442 PRIORITY_ICMP_PROBE_RULE,
443 FORWARDING_TABLE,
444 install);
445 }
446
447 /**
448 * Sends out ICMP ECHO REQUEST to destined VM.
449 *
450 * @param srcPort source instance port
451 * @param dstPort destination instance port
452 */
Jian Lic38e9032018-08-09 17:08:38 +0900453 private void sendIcmpEchoRequest(InstancePort srcPort, InstancePort dstPort,
454 OpenstackNode gateway, Direction direction) {
Jian Li0b93b002018-07-31 13:41:08 +0900455
456 short icmpSeq = INITIAL_SEQ;
457
458 short icmpId = (short) icmpIdCounter.incrementAndGet();
459
460 for (int i = 0; i < MAX_ICMP_GEN; i++) {
Jian Lic38e9032018-08-09 17:08:38 +0900461 packetService.emit(buildIcmpOutputPacket(srcPort, dstPort, gateway,
462 icmpId, icmpSeq, direction));
Jian Li0b93b002018-07-31 13:41:08 +0900463 icmpSeq++;
464 }
465 }
466
467 /**
468 * Builds ICMP Outbound packet.
469 *
470 * @param srcPort source instance port
471 * @param dstPort destination instance port
472 * @param icmpId ICMP identifier
473 * @param icmpSeq ICMP sequence number
474 */
475 private OutboundPacket buildIcmpOutputPacket(InstancePort srcPort,
476 InstancePort dstPort,
Jian Lic38e9032018-08-09 17:08:38 +0900477 OpenstackNode gateway,
Jian Li0b93b002018-07-31 13:41:08 +0900478 short icmpId,
Jian Lic38e9032018-08-09 17:08:38 +0900479 short icmpSeq,
480 Direction direction) {
Jian Li0b93b002018-07-31 13:41:08 +0900481
Jian Lic38e9032018-08-09 17:08:38 +0900482 Ethernet ethFrame;
483 IpAddress srcIp;
484 IpAddress dstIp;
485 DeviceId deviceId;
486
487 if (direction == Direction.EAST_WEST) {
488 ethFrame = constructEastWestIcmpPacket(srcPort, dstPort, icmpId, icmpSeq);
489 srcIp = srcPort.ipAddress();
490 dstIp = dstPort.ipAddress();
491 deviceId = srcPort.deviceId();
492 } else if (direction == Direction.NORTH_SOUTH) {
493 ethFrame = constructNorthSouthIcmpPacket(dstPort, icmpId, icmpSeq);
494 srcIp = clusterService.getLocalNode().ip();
495 dstIp = instancePortService.floatingIp(dstPort.portId());
496 deviceId = gateway.intgBridge();
497 } else {
498 log.warn("Invalid traffic direction {}", direction);
499 return null;
500 }
Jian Li0b93b002018-07-31 13:41:08 +0900501
502 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
503
504 // we send out the packet to ingress table (index is 0) of source OVS
505 // to enforce the Outbound packet to go through the ingress and egress
506 // pipeline
507 tBuilder.setOutput(TABLE);
508
509 Reachability reachability = DefaultReachability.builder()
Jian Lic38e9032018-08-09 17:08:38 +0900510 .srcIp(srcIp)
511 .dstIp(dstIp)
Jian Li0b93b002018-07-31 13:41:08 +0900512 .isReachable(false)
513 .build();
514
515 icmpReachabilityMap.put(String.valueOf(icmpId), reachability);
516 icmpIds.add(String.valueOf(icmpId));
517
518 return new DefaultOutboundPacket(
Jian Lic38e9032018-08-09 17:08:38 +0900519 deviceId,
Jian Li0b93b002018-07-31 13:41:08 +0900520 tBuilder.build(),
521 ByteBuffer.wrap(ethFrame.serialize()));
522 }
523
524 /**
525 * Constructs an ICMP packet with given source and destination IP/MAC.
526 *
Jian Lic38e9032018-08-09 17:08:38 +0900527 * @param srcIp source IP address
528 * @param dstIp destination IP address
529 * @param srcMac source MAC address
530 * @param dstMac destination MAC address
Jian Li0b93b002018-07-31 13:41:08 +0900531 * @param icmpId ICMP identifier
532 * @param icmpSeq ICMP sequence number
533 * @return an ethernet frame which contains ICMP payload
534 */
Jian Lic38e9032018-08-09 17:08:38 +0900535 private Ethernet constructIcmpPacket(IpAddress srcIp, IpAddress dstIp,
536 MacAddress srcMac, MacAddress dstMac,
Jian Li0b93b002018-07-31 13:41:08 +0900537 short icmpId, short icmpSeq) {
Jian Lic38e9032018-08-09 17:08:38 +0900538
Jian Li0b93b002018-07-31 13:41:08 +0900539 // Ethernet frame
540 Ethernet ethFrame = new Ethernet();
541
542 ethFrame.setEtherType(TYPE_IPV4);
Jian Lic38e9032018-08-09 17:08:38 +0900543 ethFrame.setSourceMACAddress(srcMac);
544 ethFrame.setDestinationMACAddress(dstMac);
Jian Li0b93b002018-07-31 13:41:08 +0900545
546 // IP packet
547 IPv4 iPacket = new IPv4();
Jian Lic38e9032018-08-09 17:08:38 +0900548 iPacket.setDestinationAddress(dstIp.toString());
549 iPacket.setSourceAddress(srcIp.toString());
Jian Li0b93b002018-07-31 13:41:08 +0900550 iPacket.setTtl(TTL);
551 iPacket.setProtocol(IPv4.PROTOCOL_ICMP);
552
553 // ICMP packet
554 ICMP icmp = new ICMP();
555 icmp.setIcmpType(TYPE_ECHO_REQUEST)
556 .setIcmpCode(TYPE_ECHO_REQUEST)
557 .resetChecksum();
558
559 // ICMP ECHO packet
560 ICMPEcho icmpEcho = new ICMPEcho();
561 icmpEcho.setIdentifier(icmpId)
562 .setSequenceNum(icmpSeq);
563
564 ByteBuffer byteBufferIcmpEcho = ByteBuffer.wrap(icmpEcho.serialize());
565
566 try {
567 icmp.setPayload(ICMPEcho.deserializer().deserialize(byteBufferIcmpEcho.array(),
568 0, ICMPEcho.ICMP_ECHO_HEADER_LENGTH));
569 } catch (DeserializationException e) {
570 log.warn("Failed to deserialize ICMP ECHO REQUEST packet");
571 }
572
573 ByteBuffer byteBufferIcmp = ByteBuffer.wrap(icmp.serialize());
574
575 try {
576 iPacket.setPayload(ICMP.deserializer().deserialize(byteBufferIcmp.array(),
577 0,
578 byteBufferIcmp.array().length));
579 } catch (DeserializationException e) {
580 log.warn("Failed to deserialize ICMP packet");
581 }
582
583 ethFrame.setPayload(iPacket);
584
585 return ethFrame;
586 }
587
588 /**
Jian Lic38e9032018-08-09 17:08:38 +0900589 * Constructs an east-west ICMP packet with given source and destination IP/MAC.
590 *
591 * @param srcPort source instance port
592 * @param dstPort destination instance port
593 * @param icmpId ICMP identifier
594 * @param icmpSeq ICMP sequence number
595 * @return an ethernet frame which contains ICMP payload
596 */
597 private Ethernet constructEastWestIcmpPacket(InstancePort srcPort,
598 InstancePort dstPort,
599 short icmpId, short icmpSeq) {
600 boolean isRemote = true;
601
602 if (srcPort.deviceId().equals(dstPort.deviceId()) &&
603 osNetworkService.gatewayIp(srcPort.portId())
604 .equals(osNetworkService.gatewayIp(dstPort.portId()))) {
605 isRemote = false;
606 }
607
608 // if the source and destination VMs are located in different OVS,
609 // we will assign fake gateway MAC as the destination MAC
610 MacAddress dstMac = isRemote ? DEFAULT_GATEWAY_MAC : dstPort.macAddress();
611
612 return constructIcmpPacket(srcPort.ipAddress(), dstPort.ipAddress(),
613 srcPort.macAddress(), dstMac, icmpId, icmpSeq);
614 }
615
616 /**
617 * Constructs a north-south ICMP packet with the given destination IP/MAC.
618 *
619 * @param dstPort destination instance port
620 * @param icmpId ICMP identifier
621 * @param icmpSeq ICMP sequence number
622 * @return an ethernet frame which contains ICMP payload
623 */
624 private Ethernet constructNorthSouthIcmpPacket(InstancePort dstPort,
625 short icmpId, short icmpSeq) {
626
627 IpAddress localIp = clusterService.getLocalNode().ip();
628 IpAddress fip = instancePortService.floatingIp(dstPort.portId());
629
630 if (fip != null) {
631 return constructIcmpPacket(localIp, fip, LOCAL_MAC, dstPort.macAddress(),
632 icmpId, icmpSeq);
633 } else {
634 return null;
635 }
636 }
637
638 /**
Jian Li0b93b002018-07-31 13:41:08 +0900639 * Handles ICMP ECHO REPLY packets.
640 *
641 * @param ipPacket IP packet
642 * @param icmp ICMP packet
643 */
644 private void handleIcmpEchoReply(IPv4 ipPacket, ICMP icmp) {
645
646 String icmpKey = icmpId(icmp);
647
648 String srcIp = IPv4.fromIPv4Address(ipPacket.getDestinationAddress());
649 String dstIp = IPv4.fromIPv4Address(ipPacket.getSourceAddress());
650
651 Reachability reachability = DefaultReachability.builder()
652 .srcIp(IpAddress.valueOf(srcIp))
653 .dstIp(IpAddress.valueOf(dstIp))
654 .isReachable(false)
655 .build();
656
657 icmpReachabilityMap.computeIfPresent(icmpKey, (key, value) -> {
658 if (value.equals(reachability)) {
659
660 log.debug("src: {}, dst: {} is reachable!", value.dstIp(), value.srcIp());
661
662 return DefaultReachability.builder()
663 .srcIp(IpAddress.valueOf(srcIp))
664 .dstIp(IpAddress.valueOf(dstIp))
665 .isReachable(true)
666 .build();
667 }
668 return reachability;
669 });
670 }
671
672 /**
673 * Obtains an unique ICMP key.
674 *
675 * @param icmp ICMP packet
676 * @return ICMP key
677 */
678 private String icmpId(ICMP icmp) {
679 ICMPEcho echo = (ICMPEcho) icmp.getPayload();
680 checkNotNull(echo);
681
682 short icmpId = echo.getIdentifier();
683
684 return String.valueOf(icmpId);
685 }
686
687 /**
688 * Holds the current thread unit the timeout expires, during the hold the
689 * thread periodically execute the given method.
690 *
691 * @param count count of unit
692 * @param unit unit
693 * @param predicate predicate
694 * @param predicateArg predicate argument
695 */
696 private void timeoutPredicate(long count, int unit,
697 Predicate<String> predicate, String predicateArg) {
698 long timeoutExpiredMs = System.currentTimeMillis() + unit * count;
699
700 while (true) {
701
702 long waitMs = timeoutExpiredMs - System.currentTimeMillis();
703
704 if (predicate.test(predicateArg)) {
705 break;
706 }
707
708 if (waitMs <= 0) {
709 break;
710 }
711 }
712 }
713
714 /**
715 * Holds the current thread unit the timeout expires, during the hold the
716 * thread periodically execute the given method.
717 *
718 * @param count count of unit
719 * @param unit unit
720 * @param supplier boolean supplier
721 */
722 private void timeoutSupplier(long count, int unit, BooleanSupplier supplier) {
723 long timeoutExpiredMs = System.currentTimeMillis() + unit * count;
724
725 while (true) {
726
727 long waitMs = timeoutExpiredMs - System.currentTimeMillis();
728
729 if (supplier.getAsBoolean()) {
730 break;
731 }
732
733 if (waitMs <= 0) {
734 break;
735 }
736 }
737 }
738
739 private class InternalPacketProcessor implements PacketProcessor {
740
741 @Override
742 public void process(PacketContext context) {
743 if (context.isHandled()) {
744 return;
745 }
746
747 InboundPacket pkt = context.inPacket();
748 Ethernet ethernet = pkt.parsed();
749 if (ethernet == null || ethernet.getEtherType() == Ethernet.TYPE_ARP) {
750 return;
751 }
752
753 IPv4 iPacket = (IPv4) ethernet.getPayload();
754 if (iPacket.getProtocol() == IPv4.PROTOCOL_ICMP) {
755 eventExecutor.execute(() -> processIcmpPacket(context, ethernet));
756 }
757 }
758
759 /**
760 * Processes the received ICMP packet.
761 *
762 * @param context packet context
763 * @param ethernet ethernet
764 */
765 private void processIcmpPacket(PacketContext context, Ethernet ethernet) {
766 IPv4 ipPacket = (IPv4) ethernet.getPayload();
767 ICMP icmp = (ICMP) ipPacket.getPayload();
768 log.trace("Processing ICMP packet source MAC:{}, source IP:{}," +
769 "dest MAC:{}, dest IP:{}",
770 ethernet.getSourceMAC(),
771 IpAddress.valueOf(ipPacket.getSourceAddress()),
772 ethernet.getDestinationMAC(),
773 IpAddress.valueOf(ipPacket.getDestinationAddress()));
774
775 String icmpId = icmpId(icmp);
776
777 // if the ICMP ID is not contained in ICMP ID set, we do not handle it
778 if (!icmpIds.contains(icmpId)) {
779 return;
780 }
781
782 switch (icmp.getIcmpType()) {
783 case TYPE_ECHO_REPLY:
784 handleIcmpEchoReply(ipPacket, icmp);
785 context.block();
786 icmpIds.remove(icmpId);
787 break;
788 default:
789 break;
790 }
791 }
Jian Lia1186772018-07-27 18:06:41 +0900792 }
793}