blob: 79a25305c182fd65ff62c534b934436f6604f9e4 [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;
24import org.onlab.util.Tools;
25import org.onosproject.cfg.ComponentConfigService;
26import org.onosproject.cfg.ConfigProperty;
27import org.onosproject.cluster.ClusterService;
28import org.onosproject.cluster.LeadershipService;
29import org.onosproject.cluster.NodeId;
30import org.onosproject.core.ApplicationId;
31import org.onosproject.core.CoreService;
32import org.onosproject.k8snetworking.api.K8sFlowRuleService;
33import org.onosproject.k8snetworking.api.K8sNetworkService;
34import org.onosproject.k8snetworking.api.K8sPort;
Jian Li7d111d72019-04-12 13:58:44 +090035import org.onosproject.k8snetworking.api.K8sServiceService;
Jian Li4aa17642019-01-30 00:01:11 +090036import org.onosproject.k8snode.api.K8sNode;
37import org.onosproject.k8snode.api.K8sNodeEvent;
38import org.onosproject.k8snode.api.K8sNodeListener;
39import org.onosproject.k8snode.api.K8sNodeService;
40import org.onosproject.mastership.MastershipService;
41import org.onosproject.net.PortNumber;
42import org.onosproject.net.device.DeviceService;
43import org.onosproject.net.flow.DefaultTrafficSelector;
44import org.onosproject.net.flow.DefaultTrafficTreatment;
45import org.onosproject.net.flow.TrafficSelector;
46import org.onosproject.net.flow.TrafficTreatment;
47import org.onosproject.net.packet.DefaultOutboundPacket;
48import org.onosproject.net.packet.PacketContext;
49import org.onosproject.net.packet.PacketProcessor;
50import org.onosproject.net.packet.PacketService;
51import org.osgi.service.component.ComponentContext;
52import org.osgi.service.component.annotations.Activate;
53import org.osgi.service.component.annotations.Component;
54import org.osgi.service.component.annotations.Deactivate;
55import org.osgi.service.component.annotations.Modified;
56import org.osgi.service.component.annotations.Reference;
57import org.osgi.service.component.annotations.ReferenceCardinality;
58import org.slf4j.Logger;
59import org.slf4j.LoggerFactory;
60
61import java.nio.ByteBuffer;
62import java.util.Dictionary;
63import java.util.Objects;
64import java.util.Set;
65import java.util.concurrent.ExecutorService;
Jian Li7d111d72019-04-12 13:58:44 +090066import java.util.stream.Collectors;
Jian Li4aa17642019-01-30 00:01:11 +090067
68import static java.util.concurrent.Executors.newSingleThreadExecutor;
69import static org.onlab.util.Tools.groupedThreads;
70import static org.onosproject.k8snetworking.api.Constants.ARP_BROADCAST_MODE;
71import static org.onosproject.k8snetworking.api.Constants.ARP_PROXY_MODE;
72import static org.onosproject.k8snetworking.api.Constants.ARP_TABLE;
73import static org.onosproject.k8snetworking.api.Constants.K8S_NETWORKING_APP_ID;
74import static org.onosproject.k8snetworking.api.Constants.PRIORITY_ARP_CONTROL_RULE;
Jian Li7d111d72019-04-12 13:58:44 +090075import static org.onosproject.k8snetworking.api.Constants.SERVICE_FAKE_MAC_STR;
Jian Li4aa17642019-01-30 00:01:11 +090076import static org.onosproject.k8snetworking.impl.OsgiPropertyConstants.ARP_MODE;
77import static org.onosproject.k8snetworking.impl.OsgiPropertyConstants.ARP_MODE_DEFAULT;
78import static org.onosproject.k8snetworking.impl.OsgiPropertyConstants.GATEWAY_MAC;
79import static org.onosproject.k8snetworking.impl.OsgiPropertyConstants.GATEWAY_MAC_DEFAULT;
80import static org.onosproject.k8snetworking.util.K8sNetworkingUtil.getPropertyValue;
Jian Li004526d2019-02-25 16:26:27 +090081import static org.onosproject.k8snetworking.util.K8sNetworkingUtil.unshiftIpDomain;
Jian Li4aa17642019-01-30 00:01:11 +090082
83/**
84 * Handles ARP packet from containers.
85 */
86@Component(
87 immediate = true,
88 property = {
89 GATEWAY_MAC + "=" + GATEWAY_MAC_DEFAULT,
90 ARP_MODE + "=" + ARP_MODE_DEFAULT
91 }
92)
93public class K8sSwitchingArpHandler {
94
95 private final Logger log = LoggerFactory.getLogger(getClass());
96
Jian Lid89db462019-02-08 18:21:57 +090097 private static final String API_SERVER_CLUSTER_IP = "10.96.0.1";
98
Jian Li7d111d72019-04-12 13:58:44 +090099 private static final String GATEWAY_MAC = "gatewayMac";
100 private static final String ARP_MODE = "arpMode";
101
Jian Li4aa17642019-01-30 00:01:11 +0900102 @Reference(cardinality = ReferenceCardinality.MANDATORY)
103 protected CoreService coreService;
104
105 @Reference(cardinality = ReferenceCardinality.MANDATORY)
106 protected PacketService packetService;
107
108 @Reference(cardinality = ReferenceCardinality.MANDATORY)
109 protected ComponentConfigService configService;
110
111 @Reference(cardinality = ReferenceCardinality.MANDATORY)
112 protected ClusterService clusterService;
113
114 @Reference(cardinality = ReferenceCardinality.MANDATORY)
115 protected LeadershipService leadershipService;
116
117 @Reference(cardinality = ReferenceCardinality.MANDATORY)
118 protected DeviceService deviceService;
119
120 @Reference(cardinality = ReferenceCardinality.MANDATORY)
121 protected MastershipService mastershipService;
122
123 @Reference(cardinality = ReferenceCardinality.MANDATORY)
124 protected K8sNodeService k8sNodeService;
125
126 @Reference(cardinality = ReferenceCardinality.MANDATORY)
127 protected K8sNetworkService k8sNetworkService;
128
129 @Reference(cardinality = ReferenceCardinality.MANDATORY)
130 protected K8sFlowRuleService k8sFlowRuleService;
131
Jian Li7d111d72019-04-12 13:58:44 +0900132 @Reference(cardinality = ReferenceCardinality.MANDATORY)
133 protected K8sServiceService k8sServiceService;
134
Jian Li4aa17642019-01-30 00:01:11 +0900135 /** Fake MAC address for virtual network subnet gateway. */
136 private String gatewayMac = GATEWAY_MAC_DEFAULT;
137
138 /** ARP processing mode, broadcast | proxy (default). */
139 protected String arpMode = ARP_MODE_DEFAULT;
140
141 private MacAddress gwMacAddress;
142
143 private final ExecutorService eventExecutor = newSingleThreadExecutor(
144 groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
145
146 private final InternalPacketProcessor packetProcessor = new InternalPacketProcessor();
147 private final InternalNodeEventListener k8sNodeListener = new InternalNodeEventListener();
148
149 private ApplicationId appId;
150 private NodeId localNodeId;
151
152 @Activate
153 void activate() {
154 appId = coreService.registerApplication(K8S_NETWORKING_APP_ID);
155 configService.registerProperties(getClass());
156 localNodeId = clusterService.getLocalNode().id();
157 leadershipService.runForLeadership(appId.name());
158 k8sNodeService.addListener(k8sNodeListener);
159 packetService.addProcessor(packetProcessor, PacketProcessor.director(0));
160
161 log.info("Started");
162 }
163
164 @Deactivate
165 void deactivate() {
166 packetService.removeProcessor(packetProcessor);
167 k8sNodeService.removeListener(k8sNodeListener);
168 leadershipService.withdraw(appId.name());
169 configService.unregisterProperties(getClass(), false);
170 eventExecutor.shutdown();
171
172 log.info("Stopped");
173 }
174
175 @Modified
176 void modified(ComponentContext context) {
177 readComponentConfiguration(context);
178
179 log.info("Modified");
180 }
181
182 /**
183 * Processes ARP request packets.
184 *
185 * @param context packet context
186 * @param ethPacket ethernet packet
187 */
188 private void processPacketIn(PacketContext context, Ethernet ethPacket) {
189 // if the ARP mode is configured as broadcast mode, we simply ignore ARP packet_in
190 if (ARP_BROADCAST_MODE.equals(getArpMode())) {
191 return;
192 }
193
194 ARP arpPacket = (ARP) ethPacket.getPayload();
195 if (arpPacket.getOpCode() != ARP.OP_REQUEST) {
196 return;
197 }
198
199 K8sPort srcPort = k8sNetworkService.ports().stream()
200 .filter(p -> p.macAddress().equals(ethPacket.getSourceMAC()))
201 .findAny().orElse(null);
202
203 if (srcPort == null && !context.inPacket().receivedFrom().port()
204 .equals(PortNumber.LOCAL)) {
205 log.warn("Failed to find source port(MAC:{})", ethPacket.getSourceMAC());
206 return;
207 }
208
209 // FIXME: this is a workaround for storing host GW MAC address,
210 // need to find a way to store the MAC address in persistent way
211 if (context.inPacket().receivedFrom().port().equals(PortNumber.LOCAL)) {
212 gwMacAddress = ethPacket.getSourceMAC();
213 }
214
215 IpAddress targetIp = Ip4Address.valueOf(arpPacket.getTargetProtocolAddress());
216
217 MacAddress replyMac = k8sNetworkService.ports().stream()
218 // .filter(p -> p.networkId().equals(srcPort.networkId()))
219 .filter(p -> p.ipAddress().equals(targetIp))
220 .map(K8sPort::macAddress)
221 .findAny().orElse(null);
222
223 long gwIpCnt = k8sNetworkService.networks().stream()
224 .filter(n -> n.gatewayIp().equals(targetIp))
225 .count();
226
Jian Li7d111d72019-04-12 13:58:44 +0900227 if (gwIpCnt > 0) {
Jian Li4aa17642019-01-30 00:01:11 +0900228 replyMac = gwMacAddress;
229 }
230
231 if (replyMac == null) {
Jian Li004526d2019-02-25 16:26:27 +0900232 Set<String> unshiftedIps = unshiftIpDomain(targetIp.toString(), k8sNetworkService);
233 for (String ip : unshiftedIps) {
234 replyMac = k8sNetworkService.ports().stream()
235 .filter(p -> p.ipAddress().equals(IpAddress.valueOf(ip)))
236 .map(K8sPort::macAddress)
237 .findAny().orElse(null);
238
239 if (replyMac != null) {
240 break;
241 }
242 }
243 }
244
245 if (replyMac == null) {
Jian Li7d111d72019-04-12 13:58:44 +0900246 Set<String> serviceIps = k8sServiceService.services().stream()
247 .map(s -> s.getSpec().getClusterIP())
248 .collect(Collectors.toSet());
249 if (serviceIps.contains(targetIp.toString())) {
250 replyMac = MacAddress.valueOf(SERVICE_FAKE_MAC_STR);
251 }
252 }
253
254 if (replyMac == null) {
Jian Lieb488ea2019-04-16 01:50:02 +0900255 replyMac = MacAddress.valueOf(gatewayMac);
256 }
257
258 if (replyMac == null) {
Jian Li4aa17642019-01-30 00:01:11 +0900259 log.debug("Failed to find MAC address for {}", targetIp);
260 return;
261 }
262
263 Ethernet ethReply = ARP.buildArpReply(
264 targetIp.getIp4Address(),
265 replyMac,
266 ethPacket);
267
268 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
269 .setOutput(context.inPacket().receivedFrom().port())
270 .build();
271
272 packetService.emit(new DefaultOutboundPacket(
273 context.inPacket().receivedFrom().deviceId(),
274 treatment,
275 ByteBuffer.wrap(ethReply.serialize())));
276 }
277
278 private String getArpMode() {
279 Set<ConfigProperty> properties = configService.getProperties(this.getClass().getName());
280 return getPropertyValue(properties, ARP_MODE);
281 }
282
283 /**
284 * Extracts properties from the component configuration context.
285 *
286 * @param context the component context
287 */
288 private void readComponentConfiguration(ComponentContext context) {
289 Dictionary<?, ?> properties = context.getProperties();
290
291 String updatedMac = Tools.get(properties, GATEWAY_MAC);
292 gatewayMac = updatedMac != null ? updatedMac : GATEWAY_MAC_DEFAULT;
293 log.info("Configured. Gateway MAC is {}", gatewayMac);
294 }
295
296 /**
297 * An internal packet processor which processes ARP request, and results in
298 * packet-out ARP reply.
299 */
300 private class InternalPacketProcessor implements PacketProcessor {
301
302 @Override
303 public void process(PacketContext context) {
304 if (context.isHandled()) {
305 return;
306 }
307
308 Ethernet ethPacket = context.inPacket().parsed();
309 if (ethPacket == null || ethPacket.getEtherType() != Ethernet.TYPE_ARP) {
310 return;
311 }
312
313 eventExecutor.execute(() -> processPacketIn(context, ethPacket));
314 }
315 }
316
317 /**
318 * An internal kubernetes node listener which is used for listening kubernetes
319 * node activity. As long as a node is in complete state, we will install
320 * default ARP rule to handle ARP request.
321 */
322 private class InternalNodeEventListener implements K8sNodeListener {
323
324 private boolean isRelevantHelper() {
325 return Objects.equals(localNodeId, leadershipService.getLeader(appId.name()));
326 }
327
328 @Override
329 public void event(K8sNodeEvent event) {
330 K8sNode k8sNode = event.subject();
331 switch (event.type()) {
332 case K8S_NODE_COMPLETE:
333 eventExecutor.execute(() -> processNodeCompletion(k8sNode));
334 break;
335 case K8S_NODE_INCOMPLETE:
336 eventExecutor.execute(() -> processNodeIncompletion(k8sNode));
337 break;
338 default:
339 break;
340 }
341 }
342
343 private void processNodeCompletion(K8sNode node) {
344 if (!isRelevantHelper()) {
345 return;
346 }
347
348 setDefaultArpRule(node, true);
349 }
350
351 private void processNodeIncompletion(K8sNode node) {
352 if (!isRelevantHelper()) {
353 return;
354 }
355
356 setDefaultArpRule(node, false);
357 }
358
359 private void setDefaultArpRule(K8sNode node, boolean install) {
360
361 if (getArpMode() == null) {
362 return;
363 }
364
365 switch (getArpMode()) {
366 case ARP_PROXY_MODE:
367 setDefaultArpRuleForProxyMode(node, install);
368 break;
369 case ARP_BROADCAST_MODE:
370 // TODO: need to implement broadcast mode
371 log.warn("Not implemented yet.");
372 break;
373 default:
374 log.warn("Invalid ARP mode {}. Please use either " +
375 "broadcast or proxy mode.", getArpMode());
376 break;
377 }
378 }
379
380 private void setDefaultArpRuleForProxyMode(K8sNode node, boolean install) {
381 TrafficSelector selector = DefaultTrafficSelector.builder()
382 .matchEthType(EthType.EtherType.ARP.ethType().toShort())
383 .build();
384
385 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
386 .punt()
387 .build();
388
389 k8sFlowRuleService.setRule(
390 appId,
391 node.intgBridge(),
392 selector,
393 treatment,
394 PRIORITY_ARP_CONTROL_RULE,
395 ARP_TABLE,
396 install
397 );
398 }
399 }
400}