blob: f6c1d8b6bc776e1e2db6f547711dad7a8a746992 [file] [log] [blame]
Jian Li4aa17642019-01-30 00:01:11 +09001/*
2 * Copyright 2019-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.k8snetworking.impl;
17
18import org.onlab.packet.ARP;
19import org.onlab.packet.EthType;
20import org.onlab.packet.Ethernet;
21import org.onlab.packet.Ip4Address;
22import org.onlab.packet.IpAddress;
23import org.onlab.packet.MacAddress;
Jian Li44c2b122019-05-03 14:46:34 +090024import org.onlab.packet.VlanId;
25import org.onlab.util.KryoNamespace;
Jian Li4aa17642019-01-30 00:01:11 +090026import org.onlab.util.Tools;
27import org.onosproject.cfg.ComponentConfigService;
28import org.onosproject.cfg.ConfigProperty;
29import org.onosproject.cluster.ClusterService;
30import org.onosproject.cluster.LeadershipService;
31import org.onosproject.cluster.NodeId;
32import org.onosproject.core.ApplicationId;
33import org.onosproject.core.CoreService;
34import org.onosproject.k8snetworking.api.K8sFlowRuleService;
Jian Li140d8a22019-04-24 23:41:44 +090035import org.onosproject.k8snetworking.api.K8sNetwork;
Jian Li4aa17642019-01-30 00:01:11 +090036import org.onosproject.k8snetworking.api.K8sNetworkService;
37import org.onosproject.k8snetworking.api.K8sPort;
Jian Li7d111d72019-04-12 13:58:44 +090038import org.onosproject.k8snetworking.api.K8sServiceService;
Jian Lieab51352020-09-11 03:29:16 +090039import org.onosproject.k8snode.api.K8sHostService;
Jian Li4aa17642019-01-30 00:01:11 +090040import org.onosproject.k8snode.api.K8sNode;
41import org.onosproject.k8snode.api.K8sNodeEvent;
42import org.onosproject.k8snode.api.K8sNodeListener;
43import org.onosproject.k8snode.api.K8sNodeService;
44import org.onosproject.mastership.MastershipService;
Jian Li44c2b122019-05-03 14:46:34 +090045import org.onosproject.net.ConnectPoint;
Jian Lieab51352020-09-11 03:29:16 +090046import org.onosproject.net.DeviceId;
Jian Li4aa17642019-01-30 00:01:11 +090047import org.onosproject.net.PortNumber;
48import org.onosproject.net.device.DeviceService;
49import org.onosproject.net.flow.DefaultTrafficSelector;
50import org.onosproject.net.flow.DefaultTrafficTreatment;
51import org.onosproject.net.flow.TrafficSelector;
52import org.onosproject.net.flow.TrafficTreatment;
53import org.onosproject.net.packet.DefaultOutboundPacket;
54import org.onosproject.net.packet.PacketContext;
55import org.onosproject.net.packet.PacketProcessor;
56import org.onosproject.net.packet.PacketService;
Jian Li44c2b122019-05-03 14:46:34 +090057import org.onosproject.store.serializers.KryoNamespaces;
58import org.onosproject.store.service.ConsistentMap;
59import org.onosproject.store.service.Serializer;
60import org.onosproject.store.service.StorageService;
Jian Li4aa17642019-01-30 00:01:11 +090061import org.osgi.service.component.ComponentContext;
62import 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.Modified;
66import org.osgi.service.component.annotations.Reference;
67import org.osgi.service.component.annotations.ReferenceCardinality;
68import org.slf4j.Logger;
69import org.slf4j.LoggerFactory;
70
71import java.nio.ByteBuffer;
72import java.util.Dictionary;
73import java.util.Objects;
74import java.util.Set;
75import java.util.concurrent.ExecutorService;
Jian Li7d111d72019-04-12 13:58:44 +090076import java.util.stream.Collectors;
Jian Li4aa17642019-01-30 00:01:11 +090077
78import static java.util.concurrent.Executors.newSingleThreadExecutor;
79import static org.onlab.util.Tools.groupedThreads;
80import static org.onosproject.k8snetworking.api.Constants.ARP_BROADCAST_MODE;
81import static org.onosproject.k8snetworking.api.Constants.ARP_PROXY_MODE;
82import static org.onosproject.k8snetworking.api.Constants.ARP_TABLE;
83import static org.onosproject.k8snetworking.api.Constants.K8S_NETWORKING_APP_ID;
Jian Li140d8a22019-04-24 23:41:44 +090084import static org.onosproject.k8snetworking.api.Constants.NODE_IP_PREFIX;
Jian Li4aa17642019-01-30 00:01:11 +090085import static org.onosproject.k8snetworking.api.Constants.PRIORITY_ARP_CONTROL_RULE;
Jian Li7d111d72019-04-12 13:58:44 +090086import static org.onosproject.k8snetworking.api.Constants.SERVICE_FAKE_MAC_STR;
Jian Lieab51352020-09-11 03:29:16 +090087import static org.onosproject.k8snetworking.api.Constants.SHIFTED_IP_PREFIX;
Jian Li4aa17642019-01-30 00:01:11 +090088import static org.onosproject.k8snetworking.impl.OsgiPropertyConstants.ARP_MODE;
89import static org.onosproject.k8snetworking.impl.OsgiPropertyConstants.ARP_MODE_DEFAULT;
90import static org.onosproject.k8snetworking.impl.OsgiPropertyConstants.GATEWAY_MAC;
91import static org.onosproject.k8snetworking.impl.OsgiPropertyConstants.GATEWAY_MAC_DEFAULT;
Jian Lieab51352020-09-11 03:29:16 +090092import static org.onosproject.k8snetworking.util.K8sNetworkingUtil.allK8sDevices;
Jian Li4aa17642019-01-30 00:01:11 +090093import static org.onosproject.k8snetworking.util.K8sNetworkingUtil.getPropertyValue;
Jian Li004526d2019-02-25 16:26:27 +090094import static org.onosproject.k8snetworking.util.K8sNetworkingUtil.unshiftIpDomain;
Jian Li4aa17642019-01-30 00:01:11 +090095
96/**
97 * Handles ARP packet from containers.
98 */
99@Component(
100 immediate = true,
101 property = {
102 GATEWAY_MAC + "=" + GATEWAY_MAC_DEFAULT,
103 ARP_MODE + "=" + ARP_MODE_DEFAULT
104 }
105)
106public class K8sSwitchingArpHandler {
107
108 private final Logger log = LoggerFactory.getLogger(getClass());
109
Jian Li7d111d72019-04-12 13:58:44 +0900110 private static final String GATEWAY_MAC = "gatewayMac";
111 private static final String ARP_MODE = "arpMode";
112
Jian Li44c2b122019-05-03 14:46:34 +0900113 private static final KryoNamespace SERIALIZER_HOST_MAC = KryoNamespace.newBuilder()
114 .register(KryoNamespaces.API)
115 .build();
116
Jian Li4aa17642019-01-30 00:01:11 +0900117 @Reference(cardinality = ReferenceCardinality.MANDATORY)
118 protected CoreService coreService;
119
120 @Reference(cardinality = ReferenceCardinality.MANDATORY)
121 protected PacketService packetService;
122
123 @Reference(cardinality = ReferenceCardinality.MANDATORY)
124 protected ComponentConfigService configService;
125
126 @Reference(cardinality = ReferenceCardinality.MANDATORY)
127 protected ClusterService clusterService;
128
129 @Reference(cardinality = ReferenceCardinality.MANDATORY)
130 protected LeadershipService leadershipService;
131
132 @Reference(cardinality = ReferenceCardinality.MANDATORY)
133 protected DeviceService deviceService;
134
135 @Reference(cardinality = ReferenceCardinality.MANDATORY)
136 protected MastershipService mastershipService;
137
138 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li44c2b122019-05-03 14:46:34 +0900139 protected StorageService storageService;
140
141 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li4aa17642019-01-30 00:01:11 +0900142 protected K8sNodeService k8sNodeService;
143
144 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Lieab51352020-09-11 03:29:16 +0900145 protected K8sHostService k8sHostService;
146
147 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li4aa17642019-01-30 00:01:11 +0900148 protected K8sNetworkService k8sNetworkService;
149
150 @Reference(cardinality = ReferenceCardinality.MANDATORY)
151 protected K8sFlowRuleService k8sFlowRuleService;
152
Jian Li7d111d72019-04-12 13:58:44 +0900153 @Reference(cardinality = ReferenceCardinality.MANDATORY)
154 protected K8sServiceService k8sServiceService;
155
Jian Li4aa17642019-01-30 00:01:11 +0900156 /** Fake MAC address for virtual network subnet gateway. */
157 private String gatewayMac = GATEWAY_MAC_DEFAULT;
158
159 /** ARP processing mode, broadcast | proxy (default). */
160 protected String arpMode = ARP_MODE_DEFAULT;
161
162 private MacAddress gwMacAddress;
163
Jian Li44c2b122019-05-03 14:46:34 +0900164 private ConsistentMap<IpAddress, MacAddress> extHostMacStore;
165
Jian Li4aa17642019-01-30 00:01:11 +0900166 private final ExecutorService eventExecutor = newSingleThreadExecutor(
167 groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
168
169 private final InternalPacketProcessor packetProcessor = new InternalPacketProcessor();
170 private final InternalNodeEventListener k8sNodeListener = new InternalNodeEventListener();
171
172 private ApplicationId appId;
173 private NodeId localNodeId;
174
175 @Activate
176 void activate() {
177 appId = coreService.registerApplication(K8S_NETWORKING_APP_ID);
178 configService.registerProperties(getClass());
179 localNodeId = clusterService.getLocalNode().id();
180 leadershipService.runForLeadership(appId.name());
181 k8sNodeService.addListener(k8sNodeListener);
182 packetService.addProcessor(packetProcessor, PacketProcessor.director(0));
183
Jian Li44c2b122019-05-03 14:46:34 +0900184 extHostMacStore = storageService.<IpAddress, MacAddress>consistentMapBuilder()
185 .withSerializer(Serializer.using(SERIALIZER_HOST_MAC))
186 .withName("k8s-host-mac-store")
187 .withApplicationId(appId)
188 .build();
189
Jian Li4aa17642019-01-30 00:01:11 +0900190 log.info("Started");
191 }
192
193 @Deactivate
194 void deactivate() {
195 packetService.removeProcessor(packetProcessor);
196 k8sNodeService.removeListener(k8sNodeListener);
197 leadershipService.withdraw(appId.name());
198 configService.unregisterProperties(getClass(), false);
199 eventExecutor.shutdown();
200
201 log.info("Stopped");
202 }
203
204 @Modified
205 void modified(ComponentContext context) {
206 readComponentConfiguration(context);
207
208 log.info("Modified");
209 }
210
211 /**
212 * Processes ARP request packets.
213 *
214 * @param context packet context
215 * @param ethPacket ethernet packet
216 */
217 private void processPacketIn(PacketContext context, Ethernet ethPacket) {
218 // if the ARP mode is configured as broadcast mode, we simply ignore ARP packet_in
219 if (ARP_BROADCAST_MODE.equals(getArpMode())) {
220 return;
221 }
222
Jian Lieab51352020-09-11 03:29:16 +0900223 DeviceId deviceId = context.inPacket().receivedFrom().deviceId();
224
225 if (!allK8sDevices(k8sNodeService, k8sHostService).contains(deviceId)) {
226 return;
227 }
228
Jian Li4aa17642019-01-30 00:01:11 +0900229 ARP arpPacket = (ARP) ethPacket.getPayload();
Jian Li44c2b122019-05-03 14:46:34 +0900230 if (arpPacket.getOpCode() == ARP.OP_REQUEST) {
231 processArpRequest(context, ethPacket);
232 } else if (arpPacket.getOpCode() == ARP.OP_REPLY) {
233 processArpReply(context, ethPacket);
Jian Li4aa17642019-01-30 00:01:11 +0900234 }
Jian Li44c2b122019-05-03 14:46:34 +0900235 }
Jian Li4aa17642019-01-30 00:01:11 +0900236
Jian Li44c2b122019-05-03 14:46:34 +0900237 private void processArpRequest(PacketContext context, Ethernet ethPacket) {
238 ARP arpPacket = (ARP) ethPacket.getPayload();
Jian Li4aa17642019-01-30 00:01:11 +0900239 K8sPort srcPort = k8sNetworkService.ports().stream()
240 .filter(p -> p.macAddress().equals(ethPacket.getSourceMAC()))
241 .findAny().orElse(null);
242
243 if (srcPort == null && !context.inPacket().receivedFrom().port()
244 .equals(PortNumber.LOCAL)) {
245 log.warn("Failed to find source port(MAC:{})", ethPacket.getSourceMAC());
246 return;
247 }
248
249 // FIXME: this is a workaround for storing host GW MAC address,
250 // need to find a way to store the MAC address in persistent way
251 if (context.inPacket().receivedFrom().port().equals(PortNumber.LOCAL)) {
252 gwMacAddress = ethPacket.getSourceMAC();
253 }
254
255 IpAddress targetIp = Ip4Address.valueOf(arpPacket.getTargetProtocolAddress());
256
257 MacAddress replyMac = k8sNetworkService.ports().stream()
Jian Li44c2b122019-05-03 14:46:34 +0900258 // .filter(p -> p.networkId().equals(srcPort.networkId()))
Jian Li4aa17642019-01-30 00:01:11 +0900259 .filter(p -> p.ipAddress().equals(targetIp))
260 .map(K8sPort::macAddress)
261 .findAny().orElse(null);
262
263 long gwIpCnt = k8sNetworkService.networks().stream()
264 .filter(n -> n.gatewayIp().equals(targetIp))
265 .count();
266
Jian Li7d111d72019-04-12 13:58:44 +0900267 if (gwIpCnt > 0) {
Jian Li4aa17642019-01-30 00:01:11 +0900268 replyMac = gwMacAddress;
269 }
270
271 if (replyMac == null) {
Jian Li140d8a22019-04-24 23:41:44 +0900272 String cidr = k8sNetworkService.networks().stream()
273 .map(K8sNetwork::cidr).findAny().orElse(null);
274
275 if (cidr != null) {
276 String unshiftedIp = unshiftIpDomain(targetIp.toString(),
277 SHIFTED_IP_PREFIX, cidr);
278
Jian Li004526d2019-02-25 16:26:27 +0900279 replyMac = k8sNetworkService.ports().stream()
Jian Li140d8a22019-04-24 23:41:44 +0900280 .filter(p -> p.ipAddress().equals(IpAddress.valueOf(unshiftedIp)))
Jian Li004526d2019-02-25 16:26:27 +0900281 .map(K8sPort::macAddress)
282 .findAny().orElse(null);
Jian Li004526d2019-02-25 16:26:27 +0900283 }
284 }
285
286 if (replyMac == null) {
Jian Li7d111d72019-04-12 13:58:44 +0900287 Set<String> serviceIps = k8sServiceService.services().stream()
288 .map(s -> s.getSpec().getClusterIP())
289 .collect(Collectors.toSet());
290 if (serviceIps.contains(targetIp.toString())) {
291 replyMac = MacAddress.valueOf(SERVICE_FAKE_MAC_STR);
292 }
293 }
294
295 if (replyMac == null) {
Jian Li140d8a22019-04-24 23:41:44 +0900296
Jian Li44c2b122019-05-03 14:46:34 +0900297 if (targetIp.toString().startsWith(NODE_IP_PREFIX)) {
298 String targetIpPrefix = targetIp.toString().split("\\.")[1];
299 String nodePrefix = NODE_IP_PREFIX + "." + targetIpPrefix;
Jian Li140d8a22019-04-24 23:41:44 +0900300
Jian Li44c2b122019-05-03 14:46:34 +0900301 String exBridgeCidr = k8sNodeService.completeNodes().stream()
302 .map(n -> n.extBridgeIp().toString()).findAny().orElse(null);
Jian Li140d8a22019-04-24 23:41:44 +0900303
Jian Li44c2b122019-05-03 14:46:34 +0900304 if (exBridgeCidr != null) {
305 String extBridgeIp = unshiftIpDomain(targetIp.toString(),
306 nodePrefix, exBridgeCidr);
307
308 replyMac = k8sNodeService.completeNodes().stream()
309 .filter(n -> extBridgeIp.equals(n.extBridgeIp().toString()))
310 .map(K8sNode::extBridgeMac).findAny().orElse(null);
311
312 if (replyMac == null) {
313 replyMac = extHostMacStore.asJavaMap().get(
314 IpAddress.valueOf(extBridgeIp));
315 }
316
317 // if the source hosts are not in k8s cluster range,
318 // we need to manually learn their MAC addresses
319 if (replyMac == null) {
320 ConnectPoint cp = context.inPacket().receivedFrom();
321 K8sNode k8sNode = k8sNodeService.node(cp.deviceId());
322
323 if (k8sNode != null) {
324 setArpRequest(k8sNode.extBridgeMac().toBytes(),
325 k8sNode.extBridgeIp().toOctets(),
326 IpAddress.valueOf(extBridgeIp).toOctets(),
327 k8sNode);
328 context.block();
329 return;
330 }
331 }
332 }
Jian Li140d8a22019-04-24 23:41:44 +0900333 }
334 }
335
336 if (replyMac == null) {
Jian Lieb488ea2019-04-16 01:50:02 +0900337 replyMac = MacAddress.valueOf(gatewayMac);
338 }
339
340 if (replyMac == null) {
Jian Li4aa17642019-01-30 00:01:11 +0900341 log.debug("Failed to find MAC address for {}", targetIp);
342 return;
343 }
344
345 Ethernet ethReply = ARP.buildArpReply(
346 targetIp.getIp4Address(),
347 replyMac,
348 ethPacket);
349
350 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
351 .setOutput(context.inPacket().receivedFrom().port())
352 .build();
353
354 packetService.emit(new DefaultOutboundPacket(
355 context.inPacket().receivedFrom().deviceId(),
356 treatment,
357 ByteBuffer.wrap(ethReply.serialize())));
Jian Li1b08d652019-05-02 17:28:09 +0900358
359 context.block();
Jian Li4aa17642019-01-30 00:01:11 +0900360 }
361
Jian Li44c2b122019-05-03 14:46:34 +0900362 private void processArpReply(PacketContext context, Ethernet ethPacket) {
363 ARP arpPacket = (ARP) ethPacket.getPayload();
364 ConnectPoint cp = context.inPacket().receivedFrom();
365 K8sNode k8sNode = k8sNodeService.node(cp.deviceId());
366
367 if (k8sNode != null &&
368 ethPacket.getDestinationMAC().equals(k8sNode.extBridgeMac())) {
369 IpAddress srcIp = IpAddress.valueOf(IpAddress.Version.INET,
370 arpPacket.getSenderProtocolAddress());
371 MacAddress srcMac = MacAddress.valueOf(arpPacket.getSenderHardwareAddress());
372
373 // we only add the host IP - MAC map store once,
374 // mutable MAP scenario is not considered for now
375 if (!extHostMacStore.containsKey(srcIp)) {
376 extHostMacStore.put(srcIp, srcMac);
377 }
378 }
379 }
380
381 private void setArpRequest(byte[] senderMac, byte[] senderIp,
382 byte[] targetIp, K8sNode k8sNode) {
383 Ethernet ethRequest = ARP.buildArpRequest(senderMac,
384 senderIp, targetIp, VlanId.NO_VID);
385
386 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
387 .setOutput(k8sNode.intgToExtPatchPortNum())
388 .build();
389
390 packetService.emit(new DefaultOutboundPacket(
391 k8sNode.intgBridge(),
392 treatment,
393 ByteBuffer.wrap(ethRequest.serialize())));
394 }
395
Jian Li4aa17642019-01-30 00:01:11 +0900396 private String getArpMode() {
397 Set<ConfigProperty> properties = configService.getProperties(this.getClass().getName());
398 return getPropertyValue(properties, ARP_MODE);
399 }
400
401 /**
402 * Extracts properties from the component configuration context.
403 *
404 * @param context the component context
405 */
406 private void readComponentConfiguration(ComponentContext context) {
407 Dictionary<?, ?> properties = context.getProperties();
408
409 String updatedMac = Tools.get(properties, GATEWAY_MAC);
410 gatewayMac = updatedMac != null ? updatedMac : GATEWAY_MAC_DEFAULT;
411 log.info("Configured. Gateway MAC is {}", gatewayMac);
412 }
413
414 /**
415 * An internal packet processor which processes ARP request, and results in
416 * packet-out ARP reply.
417 */
418 private class InternalPacketProcessor implements PacketProcessor {
419
420 @Override
421 public void process(PacketContext context) {
422 if (context.isHandled()) {
423 return;
424 }
425
426 Ethernet ethPacket = context.inPacket().parsed();
427 if (ethPacket == null || ethPacket.getEtherType() != Ethernet.TYPE_ARP) {
428 return;
429 }
430
431 eventExecutor.execute(() -> processPacketIn(context, ethPacket));
432 }
433 }
434
435 /**
436 * An internal kubernetes node listener which is used for listening kubernetes
437 * node activity. As long as a node is in complete state, we will install
438 * default ARP rule to handle ARP request.
439 */
440 private class InternalNodeEventListener implements K8sNodeListener {
441
442 private boolean isRelevantHelper() {
443 return Objects.equals(localNodeId, leadershipService.getLeader(appId.name()));
444 }
445
446 @Override
447 public void event(K8sNodeEvent event) {
448 K8sNode k8sNode = event.subject();
449 switch (event.type()) {
450 case K8S_NODE_COMPLETE:
451 eventExecutor.execute(() -> processNodeCompletion(k8sNode));
452 break;
453 case K8S_NODE_INCOMPLETE:
454 eventExecutor.execute(() -> processNodeIncompletion(k8sNode));
455 break;
456 default:
457 break;
458 }
459 }
460
461 private void processNodeCompletion(K8sNode node) {
462 if (!isRelevantHelper()) {
463 return;
464 }
465
466 setDefaultArpRule(node, true);
467 }
468
469 private void processNodeIncompletion(K8sNode node) {
470 if (!isRelevantHelper()) {
471 return;
472 }
473
474 setDefaultArpRule(node, false);
475 }
476
477 private void setDefaultArpRule(K8sNode node, boolean install) {
478
479 if (getArpMode() == null) {
480 return;
481 }
482
483 switch (getArpMode()) {
484 case ARP_PROXY_MODE:
485 setDefaultArpRuleForProxyMode(node, install);
486 break;
487 case ARP_BROADCAST_MODE:
488 // TODO: need to implement broadcast mode
489 log.warn("Not implemented yet.");
490 break;
491 default:
492 log.warn("Invalid ARP mode {}. Please use either " +
493 "broadcast or proxy mode.", getArpMode());
494 break;
495 }
496 }
497
498 private void setDefaultArpRuleForProxyMode(K8sNode node, boolean install) {
499 TrafficSelector selector = DefaultTrafficSelector.builder()
500 .matchEthType(EthType.EtherType.ARP.ethType().toShort())
501 .build();
502
503 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
504 .punt()
505 .build();
506
507 k8sFlowRuleService.setRule(
508 appId,
509 node.intgBridge(),
510 selector,
511 treatment,
512 PRIORITY_ARP_CONTROL_RULE,
513 ARP_TABLE,
514 install
515 );
516 }
517 }
518}