blob: 56fe815b320baa442641e5a270ea3191b4e3d4af [file] [log] [blame]
Jian Lieb488ea2019-04-16 01:50:02 +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.Ethernet;
20import org.onlab.packet.Ip4Address;
21import org.onlab.packet.IpPrefix;
Jian Lic2242bd2020-09-03 13:12:14 +090022import org.onlab.packet.MacAddress;
Jian Lieb488ea2019-04-16 01:50:02 +090023import org.onlab.packet.TpPort;
24import org.onosproject.cluster.ClusterService;
25import org.onosproject.cluster.LeadershipService;
26import org.onosproject.cluster.NodeId;
27import org.onosproject.core.ApplicationId;
28import org.onosproject.core.CoreService;
29import org.onosproject.k8snetworking.api.K8sFlowRuleService;
30import org.onosproject.k8snetworking.api.K8sNetwork;
31import org.onosproject.k8snetworking.api.K8sNetworkEvent;
32import org.onosproject.k8snetworking.api.K8sNetworkListener;
33import org.onosproject.k8snetworking.api.K8sNetworkService;
34import org.onosproject.k8snetworking.api.K8sPort;
35import org.onosproject.k8snetworking.util.RulePopulatorUtil;
Jian Li019ce6a2020-09-09 10:23:21 +090036import org.onosproject.k8snode.api.K8sHost;
37import org.onosproject.k8snode.api.K8sHostEvent;
38import org.onosproject.k8snode.api.K8sHostListener;
39import org.onosproject.k8snode.api.K8sHostService;
Jian Lieb488ea2019-04-16 01:50:02 +090040import org.onosproject.k8snode.api.K8sNode;
41import org.onosproject.k8snode.api.K8sNodeEvent;
42import org.onosproject.k8snode.api.K8sNodeListener;
43import org.onosproject.k8snode.api.K8sNodeService;
Jian Li019ce6a2020-09-09 10:23:21 +090044import org.onosproject.k8snode.api.K8sRouterBridge;
Jian Lieb488ea2019-04-16 01:50:02 +090045import org.onosproject.mastership.MastershipService;
46import org.onosproject.net.Device;
47import org.onosproject.net.DeviceId;
48import org.onosproject.net.PortNumber;
49import org.onosproject.net.device.DeviceService;
50import org.onosproject.net.driver.DriverService;
51import org.onosproject.net.flow.DefaultTrafficSelector;
52import org.onosproject.net.flow.DefaultTrafficTreatment;
53import org.onosproject.net.flow.TrafficSelector;
54import org.onosproject.net.flow.TrafficTreatment;
55import org.onosproject.net.flow.instructions.ExtensionTreatment;
56import org.osgi.service.component.annotations.Activate;
57import org.osgi.service.component.annotations.Component;
58import org.osgi.service.component.annotations.Deactivate;
59import org.osgi.service.component.annotations.Reference;
60import org.osgi.service.component.annotations.ReferenceCardinality;
61import org.slf4j.Logger;
62
63import java.util.Objects;
64import java.util.concurrent.ExecutorService;
65
66import static java.util.concurrent.Executors.newSingleThreadExecutor;
67import static org.onlab.util.Tools.groupedThreads;
68import static org.onosproject.k8snetworking.api.Constants.DEFAULT_GATEWAY_MAC;
Jian Li140d8a22019-04-24 23:41:44 +090069import static org.onosproject.k8snetworking.api.Constants.EXT_ENTRY_TABLE;
Jian Lieb488ea2019-04-16 01:50:02 +090070import static org.onosproject.k8snetworking.api.Constants.K8S_NETWORKING_APP_ID;
Jian Li140d8a22019-04-24 23:41:44 +090071import static org.onosproject.k8snetworking.api.Constants.POD_RESOLUTION_TABLE;
Jian Li019ce6a2020-09-09 10:23:21 +090072import static org.onosproject.k8snetworking.api.Constants.PRIORITY_DEFAULT_RULE;
Jian Lieb488ea2019-04-16 01:50:02 +090073import static org.onosproject.k8snetworking.api.Constants.PRIORITY_EXTERNAL_ROUTING_RULE;
74import static org.onosproject.k8snetworking.api.Constants.PRIORITY_STATEFUL_SNAT_RULE;
Jian Li019ce6a2020-09-09 10:23:21 +090075import static org.onosproject.k8snetworking.api.Constants.ROUTER_EXTRY_TABLE;
Jian Lieb488ea2019-04-16 01:50:02 +090076import static org.onosproject.k8snetworking.api.Constants.ROUTING_TABLE;
77import static org.onosproject.k8snetworking.util.RulePopulatorUtil.CT_NAT_SRC_FLAG;
78import static org.onosproject.k8snetworking.util.RulePopulatorUtil.buildMoveArpShaToThaExtension;
79import static org.onosproject.k8snetworking.util.RulePopulatorUtil.buildMoveArpSpaToTpaExtension;
80import static org.onosproject.k8snetworking.util.RulePopulatorUtil.buildMoveEthSrcToDstExtension;
Jian Lic2242bd2020-09-03 13:12:14 +090081import static org.onosproject.k8snode.api.Constants.DEFAULT_EXTERNAL_GATEWAY_MAC;
Jian Li019ce6a2020-09-09 10:23:21 +090082import static org.onosproject.k8snode.api.K8sApiConfig.Mode.PASSTHROUGH;
Jian Lieb488ea2019-04-16 01:50:02 +090083import static org.slf4j.LoggerFactory.getLogger;
84
85/**
86 * Provides POD's internal to external connectivity using source NAT (SNAT).
87 */
88@Component(immediate = true)
89public class K8sRoutingSnatHandler {
90
91 private final Logger log = getLogger(getClass());
92
Jian Li140d8a22019-04-24 23:41:44 +090093 private static final int HOST_PREFIX = 32;
Jian Lieb488ea2019-04-16 01:50:02 +090094
Jian Li140d8a22019-04-24 23:41:44 +090095 // we try to avoid port number overlapping with node port (30000 ~ 32767)
96 // in case the user has customized node port range, the following static
97 // value should be changed accordingly
98 private static final int TP_PORT_MINIMUM_NUM = 32768;
Jian Lieb488ea2019-04-16 01:50:02 +090099 private static final int TP_PORT_MAXIMUM_NUM = 65535;
100
101 @Reference(cardinality = ReferenceCardinality.MANDATORY)
102 protected CoreService coreService;
103
104 @Reference(cardinality = ReferenceCardinality.MANDATORY)
105 protected DeviceService deviceService;
106
107 @Reference(cardinality = ReferenceCardinality.MANDATORY)
108 protected DriverService driverService;
109
110 @Reference(cardinality = ReferenceCardinality.MANDATORY)
111 protected LeadershipService leadershipService;
112
113 @Reference(cardinality = ReferenceCardinality.MANDATORY)
114 protected MastershipService mastershipService;
115
116 @Reference(cardinality = ReferenceCardinality.MANDATORY)
117 protected ClusterService clusterService;
118
119 @Reference(cardinality = ReferenceCardinality.MANDATORY)
120 protected K8sNetworkService k8sNetworkService;
121
122 @Reference(cardinality = ReferenceCardinality.MANDATORY)
123 protected K8sNodeService k8sNodeService;
124
125 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li019ce6a2020-09-09 10:23:21 +0900126 protected K8sHostService k8sHostService;
127
128 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Lieb488ea2019-04-16 01:50:02 +0900129 protected K8sFlowRuleService k8sFlowRuleService;
130
131 private final InternalK8sNetworkListener k8sNetworkListener =
132 new InternalK8sNetworkListener();
133 private final InternalK8sNodeListener k8sNodeListener =
134 new InternalK8sNodeListener();
Jian Li019ce6a2020-09-09 10:23:21 +0900135 private final InternalK8sHostListener k8sHostListener =
136 new InternalK8sHostListener();
Jian Lieb488ea2019-04-16 01:50:02 +0900137 private final ExecutorService eventExecutor = newSingleThreadExecutor(
138 groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
139
140 private ApplicationId appId;
141 private NodeId localNodeId;
142
143 @Activate
144 protected void activate() {
145 appId = coreService.registerApplication(K8S_NETWORKING_APP_ID);
146
147 localNodeId = clusterService.getLocalNode().id();
148 leadershipService.runForLeadership(appId.name());
149 k8sNetworkService.addListener(k8sNetworkListener);
150 k8sNodeService.addListener(k8sNodeListener);
Jian Li019ce6a2020-09-09 10:23:21 +0900151 k8sHostService.addListener(k8sHostListener);
Jian Lieb488ea2019-04-16 01:50:02 +0900152
153 log.info("Started");
154 }
155
156 @Deactivate
157 protected void deactivate() {
Jian Li019ce6a2020-09-09 10:23:21 +0900158 k8sHostService.removeListener(k8sHostListener);
Jian Lieb488ea2019-04-16 01:50:02 +0900159 k8sNodeService.removeListener(k8sNodeListener);
160 k8sNetworkService.removeListener(k8sNetworkListener);
161 leadershipService.withdraw(appId.name());
162 eventExecutor.shutdown();
163
164 log.info("Stopped");
165 }
166
167 private void setContainerToExtRule(K8sNode k8sNode, boolean install) {
168
169 K8sNetwork net = k8sNetworkService.network(k8sNode.hostname());
170
171 if (net == null) {
172 return;
173 }
174
175 TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder()
176 .matchEthType(Ethernet.TYPE_IPV4)
177 .matchTunnelId(Long.valueOf(net.segmentId()))
178 .matchEthDst(DEFAULT_GATEWAY_MAC);
179
180 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder()
181 .setOutput(k8sNode.intgToExtPatchPortNum());
182
183 k8sFlowRuleService.setRule(
184 appId,
185 k8sNode.intgBridge(),
186 sBuilder.build(),
187 tBuilder.build(),
188 PRIORITY_EXTERNAL_ROUTING_RULE,
189 ROUTING_TABLE,
190 install);
191 }
192
193 private void setExtToContainerRule(K8sNode k8sNode,
194 K8sPort k8sPort, boolean install) {
195
196 K8sNetwork net = k8sNetworkService.network(k8sPort.networkId());
197
198 if (net == null) {
199 return;
200 }
201
202 TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder()
203 .matchEthType(Ethernet.TYPE_IPV4)
Jian Li140d8a22019-04-24 23:41:44 +0900204 .matchIPDst(IpPrefix.valueOf(k8sPort.ipAddress(), HOST_PREFIX));
Jian Lieb488ea2019-04-16 01:50:02 +0900205
206 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder()
207 .setOutput(k8sNode.extToIntgPatchPortNum());
208
209 k8sFlowRuleService.setRule(
210 appId,
211 k8sNode.extBridge(),
212 sBuilder.build(),
213 tBuilder.build(),
214 PRIORITY_STATEFUL_SNAT_RULE,
Jian Li140d8a22019-04-24 23:41:44 +0900215 POD_RESOLUTION_TABLE,
Jian Lieb488ea2019-04-16 01:50:02 +0900216 install);
217 }
218
Jian Li019ce6a2020-09-09 10:23:21 +0900219 private void setExtSnatDownstreamRule(K8sNode k8sNode,
220 boolean install) {
Jian Lieb488ea2019-04-16 01:50:02 +0900221 DeviceId deviceId = k8sNode.extBridge();
222
Jian Li140d8a22019-04-24 23:41:44 +0900223 TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder()
224 .matchEthType(Ethernet.TYPE_IPV4)
225 .matchIPDst(IpPrefix.valueOf(k8sNode.extBridgeIp(), HOST_PREFIX));
Jian Lieb488ea2019-04-16 01:50:02 +0900226
227 ExtensionTreatment natTreatment = RulePopulatorUtil
228 .niciraConnTrackTreatmentBuilder(driverService, deviceId)
229 .commit(false)
230 .natAction(true)
Jian Li140d8a22019-04-24 23:41:44 +0900231 .table((short) POD_RESOLUTION_TABLE)
Jian Lieb488ea2019-04-16 01:50:02 +0900232 .build();
233
234 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
235 .setEthSrc(DEFAULT_GATEWAY_MAC)
236 .extension(natTreatment, deviceId)
237 .build();
238
239 k8sFlowRuleService.setRule(
240 appId,
241 deviceId,
242 sBuilder.build(),
243 treatment,
244 PRIORITY_STATEFUL_SNAT_RULE,
Jian Li140d8a22019-04-24 23:41:44 +0900245 EXT_ENTRY_TABLE,
Jian Lieb488ea2019-04-16 01:50:02 +0900246 install);
247 }
248
Jian Li019ce6a2020-09-09 10:23:21 +0900249 private void setExtSnatUpstreamRule(K8sNode k8sNode,
250 boolean install) {
Jian Lieb488ea2019-04-16 01:50:02 +0900251
252 K8sNetwork net = k8sNetworkService.network(k8sNode.hostname());
253
254 if (net == null) {
255 return;
256 }
257
258 TrafficSelector selector = DefaultTrafficSelector.builder()
259 .matchEthType(Ethernet.TYPE_IPV4)
Jian Li140d8a22019-04-24 23:41:44 +0900260 .matchInPort(k8sNode.extToIntgPatchPortNum())
Jian Lieb488ea2019-04-16 01:50:02 +0900261 .matchEthDst(DEFAULT_GATEWAY_MAC)
262 .build();
263
264 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
265
266 if (install) {
267 ExtensionTreatment natTreatment = RulePopulatorUtil
268 .niciraConnTrackTreatmentBuilder(driverService, k8sNode.extBridge())
269 .commit(true)
270 .natFlag(CT_NAT_SRC_FLAG)
271 .natAction(true)
272 .natIp(k8sNode.extBridgeIp())
273 .natPortMin(TpPort.tpPort(TP_PORT_MINIMUM_NUM))
274 .natPortMax(TpPort.tpPort(TP_PORT_MAXIMUM_NUM))
275 .build();
276
277 tBuilder.extension(natTreatment, k8sNode.extBridge())
278 .setEthSrc(k8sNode.extBridgeMac())
Jian Lic2242bd2020-09-03 13:12:14 +0900279 .setEthDst(k8sNode.extGatewayMac());
280
Jian Li019ce6a2020-09-09 10:23:21 +0900281 if (k8sNode.mode() == PASSTHROUGH) {
282 tBuilder.setOutput(k8sNode.extToRouterPortNum());
Jian Lic2242bd2020-09-03 13:12:14 +0900283 } else {
Jian Li019ce6a2020-09-09 10:23:21 +0900284 if (MacAddress.valueOf(DEFAULT_EXTERNAL_GATEWAY_MAC).equals(
285 k8sNode.extGatewayMac())) {
286 tBuilder.setOutput(k8sNode.extIntfPortNum());
287 } else {
288 tBuilder.setOutput(k8sNode.extBridgePortNum());
289 }
Jian Lic2242bd2020-09-03 13:12:14 +0900290 }
Jian Lieb488ea2019-04-16 01:50:02 +0900291 }
292
293 k8sFlowRuleService.setRule(
294 appId,
295 k8sNode.extBridge(),
296 selector,
297 tBuilder.build(),
298 PRIORITY_STATEFUL_SNAT_RULE,
Jian Li140d8a22019-04-24 23:41:44 +0900299 EXT_ENTRY_TABLE,
Jian Lieb488ea2019-04-16 01:50:02 +0900300 install);
301 }
302
303 private void setExtIntfArpRule(K8sNode k8sNode, boolean install) {
Jian Li140d8a22019-04-24 23:41:44 +0900304 k8sNodeService.completeNodes().forEach(n -> {
305 Device device = deviceService.getDevice(n.extBridge());
306 TrafficSelector selector = DefaultTrafficSelector.builder()
307 .matchEthType(Ethernet.TYPE_ARP)
308 .matchArpOp(ARP.OP_REQUEST)
309 .matchArpTpa(Ip4Address.valueOf(k8sNode.extBridgeIp().toString()))
310 .build();
Jian Lieb488ea2019-04-16 01:50:02 +0900311
Jian Li140d8a22019-04-24 23:41:44 +0900312 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
313 .setArpOp(ARP.OP_REPLY)
314 .extension(buildMoveEthSrcToDstExtension(device), device.id())
315 .extension(buildMoveArpShaToThaExtension(device), device.id())
316 .extension(buildMoveArpSpaToTpaExtension(device), device.id())
Jian Li1efcb982020-02-04 00:32:21 +0900317 .setEthSrc(k8sNode.extBridgeMac())
Jian Li140d8a22019-04-24 23:41:44 +0900318 .setArpSha(k8sNode.extBridgeMac())
Jian Li1efcb982020-02-04 00:32:21 +0900319 .setArpSpa(Ip4Address.valueOf(k8sNode.extBridgeIp().toString()))
Jian Li140d8a22019-04-24 23:41:44 +0900320 .setOutput(PortNumber.IN_PORT)
321 .build();
Jian Lieb488ea2019-04-16 01:50:02 +0900322
Jian Li140d8a22019-04-24 23:41:44 +0900323 k8sFlowRuleService.setRule(
324 appId,
325 n.extBridge(),
326 selector,
327 treatment,
328 PRIORITY_STATEFUL_SNAT_RULE,
329 EXT_ENTRY_TABLE,
330 install);
331 });
Jian Lieb488ea2019-04-16 01:50:02 +0900332 }
333
Jian Li019ce6a2020-09-09 10:23:21 +0900334 private void setRouterSnatUpstreamRule(K8sNode k8sNode,
335 K8sRouterBridge bridge,
336 boolean install) {
337
338 TrafficSelector selector = DefaultTrafficSelector.builder()
339 .matchEthType(Ethernet.TYPE_IPV4)
340 .matchInPort(k8sNode.routerToExtPortNum())
341 .build();
342
343 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
344 .setOutput(k8sNode.routerPortNum())
345 .build();
346
347 k8sFlowRuleService.setRule(
348 appId,
349 bridge.deviceId(),
350 selector,
351 treatment,
352 PRIORITY_DEFAULT_RULE,
353 ROUTER_EXTRY_TABLE,
354 install);
355 }
356
357 private void setRouterSnatDownstreamRule(K8sNode k8sNode,
358 K8sRouterBridge bridge,
359 boolean install) {
360 TrafficSelector selector = DefaultTrafficSelector.builder()
361 .matchEthType(Ethernet.TYPE_IPV4)
362 .matchInPort(k8sNode.routerPortNum())
363 .matchIPDst(IpPrefix.valueOf(k8sNode.extBridgeIp(), 32))
364 .build();
365
366 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
367 .setOutput(k8sNode.routerToExtPortNum())
368 .build();
369
370 k8sFlowRuleService.setRule(
371 appId,
372 bridge.deviceId(),
373 selector,
374 treatment,
375 PRIORITY_DEFAULT_RULE,
376 ROUTER_EXTRY_TABLE,
377 install);
378 }
379
Jian Lieb488ea2019-04-16 01:50:02 +0900380 private class InternalK8sNodeListener implements K8sNodeListener {
381
382 private boolean isRelevantHelper() {
383 return Objects.equals(localNodeId, leadershipService.getLeader(appId.name()));
384 }
385
386 @Override
387 public void event(K8sNodeEvent event) {
388 switch (event.type()) {
389 case K8S_NODE_COMPLETE:
390 eventExecutor.execute(() -> processNodeCompletion(event.subject()));
391 break;
Jian Li1b08d652019-05-02 17:28:09 +0900392 case K8S_NODE_UPDATED:
393 eventExecutor.execute(() -> processNodeUpdate(event.subject()));
394 break;
Jian Lieb488ea2019-04-16 01:50:02 +0900395 case K8S_NODE_INCOMPLETE:
396 default:
397 break;
398 }
399 }
400
401 private void processNodeCompletion(K8sNode k8sNode) {
402 if (!isRelevantHelper()) {
403 return;
404 }
405
406 setExtIntfArpRule(k8sNode, true);
Jian Li019ce6a2020-09-09 10:23:21 +0900407 setExtSnatDownstreamRule(k8sNode, true);
Jian Lieb488ea2019-04-16 01:50:02 +0900408 setContainerToExtRule(k8sNode, true);
409 }
Jian Li1b08d652019-05-02 17:28:09 +0900410
411 private void processNodeUpdate(K8sNode k8sNode) {
412 if (k8sNode.extGatewayMac() != null) {
Jian Li019ce6a2020-09-09 10:23:21 +0900413 setExtSnatUpstreamRule(k8sNode, true);
414 }
415 }
416 }
417
418 private class InternalK8sHostListener implements K8sHostListener {
419
420 private boolean isRelevantHelper() {
421 return Objects.equals(localNodeId, leadershipService.getLeader(appId.name()));
422 }
423
424 @Override
425 public void event(K8sHostEvent event) {
426 switch (event.type()) {
427 case K8S_HOST_COMPLETE:
428 eventExecutor.execute(() -> processNodeCompletion(event.subject()));
429 break;
430 case K8S_HOST_INCOMPLETE:
431 default:
432 break;
433 }
434 }
435
436 private void processNodeCompletion(K8sHost k8sHost) {
437 if (!isRelevantHelper()) {
438 return;
439 }
440
441 for (String name : k8sHost.nodeNames()) {
442 K8sNode node = k8sNodeService.node(name);
443 if (node == null) {
444 return;
445 }
446 K8sRouterBridge bridge = k8sHost.routerBridges().stream()
447 .filter(b -> b.segmentId() == node.segmentId())
448 .findAny().orElse(null);
449 if (bridge == null) {
450 return;
451 }
452
453 setRouterSnatUpstreamRule(node, bridge, true);
454 setRouterSnatDownstreamRule(node, bridge, true);
Jian Li1b08d652019-05-02 17:28:09 +0900455 }
456 }
Jian Lieb488ea2019-04-16 01:50:02 +0900457 }
458
459 private class InternalK8sNetworkListener implements K8sNetworkListener {
460
461 private boolean isRelevantHelper() {
462 return Objects.equals(localNodeId, leadershipService.getLeader(appId.name()));
463 }
464
465 @Override
466 public void event(K8sNetworkEvent event) {
467 switch (event.type()) {
468 case K8S_PORT_ACTIVATED:
469 eventExecutor.execute(() -> processPortActivation(event.port()));
470 break;
471 case K8S_PORT_REMOVED:
472 eventExecutor.execute(() -> processPortRemoval(event.port()));
473 break;
474 default:
475 break;
476 }
477 }
478
479 private void processPortActivation(K8sPort port) {
480 if (!isRelevantHelper()) {
481 return;
482 }
483
484 k8sNodeService.completeNodes().forEach(n ->
485 setExtToContainerRule(n, port, true));
486 }
487
488 private void processPortRemoval(K8sPort port) {
489 if (!isRelevantHelper()) {
490 return;
491 }
492
493 k8sNodeService.completeNodes().forEach(n ->
494 setExtToContainerRule(n, port, false));
495 }
496 }
497}