blob: 4f8e6d91d83819159c4bbb71d59f55d110f50932 [file] [log] [blame]
Pingping Linffa27d32015-04-30 14:41:03 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
Pingping Linffa27d32015-04-30 14:41:03 -07003 *
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.virtualbng;
17
Pingping Lin53ae34f2015-06-09 10:07:08 -070018import com.fasterxml.jackson.databind.JsonNode;
19import com.fasterxml.jackson.databind.node.ArrayNode;
20import com.fasterxml.jackson.databind.node.ObjectNode;
Pingping Linffa27d32015-04-30 14:41:03 -070021import org.apache.felix.scr.annotations.Activate;
22import org.apache.felix.scr.annotations.Component;
23import org.apache.felix.scr.annotations.Deactivate;
24import org.apache.felix.scr.annotations.Reference;
25import org.apache.felix.scr.annotations.ReferenceCardinality;
26import org.apache.felix.scr.annotations.Service;
27import org.onlab.packet.Ethernet;
28import org.onlab.packet.IpAddress;
29import org.onlab.packet.IpPrefix;
30import org.onlab.packet.MacAddress;
31import org.onosproject.core.ApplicationId;
32import org.onosproject.core.CoreService;
33import org.onosproject.net.ConnectPoint;
Pingping Lindead2052015-06-08 16:07:23 -070034import org.onosproject.net.DeviceId;
Pingping Linffa27d32015-04-30 14:41:03 -070035import org.onosproject.net.Host;
36import org.onosproject.net.flow.DefaultTrafficSelector;
37import org.onosproject.net.flow.DefaultTrafficTreatment;
38import org.onosproject.net.flow.TrafficSelector;
39import org.onosproject.net.flow.TrafficTreatment;
Pingping Lin4e0c73d2015-05-06 15:41:10 -070040import org.onosproject.net.host.HostEvent;
41import org.onosproject.net.host.HostListener;
Pingping Linffa27d32015-04-30 14:41:03 -070042import org.onosproject.net.host.HostService;
43import org.onosproject.net.intent.IntentService;
44import org.onosproject.net.intent.Key;
45import org.onosproject.net.intent.PointToPointIntent;
46import org.slf4j.Logger;
47import org.slf4j.LoggerFactory;
48
Jonathan Hartbbc352f2015-10-27 19:24:36 -070049import java.util.Iterator;
50import java.util.Map;
51import java.util.Map.Entry;
52import java.util.concurrent.ConcurrentHashMap;
53
54import static com.google.common.base.Preconditions.checkNotNull;
55
Pingping Linffa27d32015-04-30 14:41:03 -070056/**
57 * This is a virtual Broadband Network Gateway (BNG) application. It mainly
58 * has 3 functions:
59 * (1) assigns and replies a public IP address to a REST request with a private
60 * IP address
61 * (2) maintains the mapping from the private IP address to the public IP address
62 * (3) installs point to point intents for the host configured with private IP
63 * address to access Internet
64 */
65@Component(immediate = true)
66@Service
67public class VbngManager implements VbngService {
68
69 private static final String APP_NAME = "org.onosproject.virtualbng";
Pingping Lin53ae34f2015-06-09 10:07:08 -070070 private static final String VBNG_MAP_NAME = "vbng_mapping";
Pingping Linffa27d32015-04-30 14:41:03 -070071
72 private final Logger log = LoggerFactory.getLogger(getClass());
73
74 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
75 protected CoreService coreService;
76
77 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
78 protected HostService hostService;
79
80 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
81 protected IntentService intentService;
82
83 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
84 protected VbngConfigurationService vbngConfigurationService;
85
86 private ApplicationId appId;
87 private Map<IpAddress, PointToPointIntent> p2pIntentsFromHost;
88 private Map<IpAddress, PointToPointIntent> p2pIntentsToHost;
89
Pingping Lindead2052015-06-08 16:07:23 -070090 // This map stores the mapping from the private IP addresses to VcpeHost.
91 // The IP addresses in this map are all the private IP addresses we failed
92 // to create vBNGs due to the next hop host was not in ONOS.
93 private Map<IpAddress, VcpeHost> privateIpAddressMap;
94
95 // Store the mapping from hostname to connect point
96 private Map<String, ConnectPoint> nodeToPort;
Pingping Lin4e0c73d2015-05-06 15:41:10 -070097
98 private HostListener hostListener;
99 private IpAddress nextHopIpAddress;
100
Pingping Lindead2052015-06-08 16:07:23 -0700101 private static final DeviceId FABRIC_DEVICE_ID =
102 DeviceId.deviceId("of:8f0e486e73000187");
103
Pingping Linffa27d32015-04-30 14:41:03 -0700104 @Activate
105 public void activate() {
106 appId = coreService.registerApplication(APP_NAME);
107 p2pIntentsFromHost = new ConcurrentHashMap<>();
108 p2pIntentsToHost = new ConcurrentHashMap<>();
Pingping Lindead2052015-06-08 16:07:23 -0700109 privateIpAddressMap = new ConcurrentHashMap<>();
110
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700111 nextHopIpAddress = vbngConfigurationService.getNextHopIpAddress();
Jonathan Hartbbc352f2015-10-27 19:24:36 -0700112 nodeToPort = vbngConfigurationService.getNodeToPort();
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700113 hostListener = new InternalHostListener();
114 hostService.addListener(hostListener);
115
Pingping Linffa27d32015-04-30 14:41:03 -0700116 log.info("vBNG Started");
Pingping Lin53ae34f2015-06-09 10:07:08 -0700117
118 // Recover the status before vBNG restarts
119 statusRecovery();
Pingping Linffa27d32015-04-30 14:41:03 -0700120 }
121
122 @Deactivate
123 public void deactivate() {
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700124 hostService.removeListener(hostListener);
Pingping Linffa27d32015-04-30 14:41:03 -0700125 log.info("vBNG Stopped");
126 }
127
Pingping Lindead2052015-06-08 16:07:23 -0700128 /**
Pingping Lin53ae34f2015-06-09 10:07:08 -0700129 * Recovers from XOS record. Re-sets up the mapping between private IP
130 * address and public IP address, re-calculates intents and re-installs
131 * those intents.
132 */
133 private void statusRecovery() {
134 log.info("vBNG starts to recover from XOS record......");
Jonathan Hartbbc352f2015-10-27 19:24:36 -0700135 ObjectNode map;
136 try {
137 RestClient restClient =
138 new RestClient(vbngConfigurationService.getXosIpAddress(),
139 vbngConfigurationService.getXosRestPort());
140 map = restClient.getRest();
141 } catch (Exception e) {
142 log.error("Could not contact XOS", e);
143 return;
144 }
Pingping Lin53ae34f2015-06-09 10:07:08 -0700145 if (map == null) {
146 log.info("Stop to recover vBNG status due to the vBNG map "
147 + "is null!");
148 return;
149 }
150
151 log.info("Get record from XOS: {}", map);
152
153 ArrayNode array = (ArrayNode) map.get(VBNG_MAP_NAME);
154 Iterator<JsonNode> entries = array.elements();
155 while (entries.hasNext()) {
156 ObjectNode entry = (ObjectNode) entries.next();
157
158 IpAddress hostIpAdddress =
159 IpAddress.valueOf(entry.get("private_ip").asText());
160 IpAddress publicIpAddress =
161 IpAddress.valueOf(entry.get("routeable_subnet").asText());
162 MacAddress macAddress =
163 MacAddress.valueOf(entry.get("mac").asText());
164 String hostName = entry.get("hostname").asText();
165
166 // Create vBNG
167 createVbng(hostIpAdddress, publicIpAddress, macAddress, hostName);
168
169 }
170 }
171
172 /**
Pingping Lin53ae34f2015-06-09 10:07:08 -0700173 * Creates a new vBNG.
174 *
175 * @param privateIpAddress a private IP address
176 * @param publicIpAddress the public IP address for the private IP address
177 * @param hostMacAddress the MAC address for the private IP address
178 * @param hostName the host name for the private IP address
179 */
180 private void createVbng(IpAddress privateIpAddress,
181 IpAddress publicIpAddress,
182 MacAddress hostMacAddress,
183 String hostName) {
184 boolean result = vbngConfigurationService
185 .assignSpecifiedPublicIp(publicIpAddress, privateIpAddress);
186 if (!result) {
187 log.info("Assign public IP address {} for private IP address {} "
188 + "failed!", publicIpAddress, privateIpAddress);
189 log.info("Failed to create vBNG for private IP address {}",
190 privateIpAddress);
191 return;
192 }
193 log.info("[ADD] Private IP to Public IP mapping: {} --> {}",
194 privateIpAddress, publicIpAddress);
195
196 // Setup paths between the host configured with private IP and
197 // next hop
198 if (!setupForwardingPaths(privateIpAddress, publicIpAddress,
199 hostMacAddress, hostName)) {
200 privateIpAddressMap.put(privateIpAddress,
201 new VcpeHost(hostMacAddress, hostName));
202 }
203 }
204
Pingping Linffa27d32015-04-30 14:41:03 -0700205 @Override
Pingping Lindead2052015-06-08 16:07:23 -0700206 public IpAddress createVbng(IpAddress privateIpAddress,
207 MacAddress hostMacAddress,
208 String hostName) {
Pingping Linffa27d32015-04-30 14:41:03 -0700209
Pingping Linffa27d32015-04-30 14:41:03 -0700210 IpAddress publicIpAddress =
211 vbngConfigurationService.getAvailablePublicIpAddress(
212 privateIpAddress);
213 if (publicIpAddress == null) {
214 log.info("Did not find an available public IP address to use.");
215 return null;
216 }
Pingping Linde77ee52015-06-03 17:16:07 -0700217 log.info("[ADD] Private IP to Public IP mapping: {} --> {}",
Pingping Linffa27d32015-04-30 14:41:03 -0700218 privateIpAddress, publicIpAddress);
219
220 // Setup paths between the host configured with private IP and
221 // next hop
Pingping Lindead2052015-06-08 16:07:23 -0700222 if (!setupForwardingPaths(privateIpAddress, publicIpAddress,
223 hostMacAddress, hostName)) {
224 privateIpAddressMap.put(privateIpAddress,
225 new VcpeHost(hostMacAddress, hostName));
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700226 }
Pingping Linffa27d32015-04-30 14:41:03 -0700227 return publicIpAddress;
228 }
229
Pingping Lind2afaf22015-06-02 10:46:29 -0700230 @Override
231 public IpAddress deleteVbng(IpAddress privateIpAddress) {
232 // Recycle the public IP address assigned to this private IP address.
233 // Recycling will also delete the mapping entry from the private IP
234 // address to public IP address.
235 IpAddress assignedPublicIpAddress = vbngConfigurationService
236 .recycleAssignedPublicIpAddress(privateIpAddress);
237 if (assignedPublicIpAddress == null) {
238 return null;
239 }
240
Pingping Lindead2052015-06-08 16:07:23 -0700241 // Remove the private IP address from privateIpAddressMap
242 privateIpAddressMap.remove(privateIpAddress);
Pingping Lind2afaf22015-06-02 10:46:29 -0700243
244 // Remove intents
245 removeForwardingPaths(privateIpAddress);
246
247 return assignedPublicIpAddress;
248 }
249
250 /**
251 * Removes the forwarding paths in both two directions between host
252 * configured with private IP and next hop.
253 *
254 * @param privateIp the private IP address of a local host
255 */
256 private void removeForwardingPaths(IpAddress privateIp) {
257 PointToPointIntent toNextHopIntent =
258 p2pIntentsFromHost.remove(privateIp);
259 if (toNextHopIntent != null) {
260 intentService.withdraw(toNextHopIntent);
261 //intentService.purge(toNextHopIntent);
262 }
263 PointToPointIntent toLocalHostIntent =
264 p2pIntentsToHost.remove(privateIp);
265 if (toLocalHostIntent != null) {
266 intentService.withdraw(toLocalHostIntent);
267 //intentService.purge(toLocalHostIntent);
268 }
269 }
270
Pingping Linffa27d32015-04-30 14:41:03 -0700271 /**
272 * Sets up forwarding paths in both two directions between host configured
273 * with private IP and next hop.
274 *
275 * @param privateIp the private IP address of a local host
276 * @param publicIp the public IP address assigned for the private IP address
Pingping Lindead2052015-06-08 16:07:23 -0700277 * @param hostMacAddress the MAC address for the IP address
278 * @param hostName the host name for the IP address
Pingping Linffa27d32015-04-30 14:41:03 -0700279 */
Pingping Lindead2052015-06-08 16:07:23 -0700280 private boolean setupForwardingPaths(IpAddress privateIp,
281 IpAddress publicIp,
282 MacAddress hostMacAddress,
283 String hostName) {
Pingping Linffa27d32015-04-30 14:41:03 -0700284 checkNotNull(privateIp);
285 checkNotNull(publicIp);
Pingping Lindead2052015-06-08 16:07:23 -0700286 checkNotNull(hostMacAddress);
287 checkNotNull(hostName);
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700288
289 if (nextHopIpAddress == null) {
290 log.warn("Did not find next hop IP address");
291 return false;
292 }
Pingping Linffa27d32015-04-30 14:41:03 -0700293
294 // If there are already intents for private IP address in the system,
295 // we will do nothing and directly return.
296 if (p2pIntentsFromHost.containsKey(privateIp)
297 && p2pIntentsToHost.containsKey(privateIp)) {
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700298 return true;
Pingping Linffa27d32015-04-30 14:41:03 -0700299 }
300
Pingping Linffa27d32015-04-30 14:41:03 -0700301 Host nextHopHost = null;
302 if (!hostService.getHostsByIp(nextHopIpAddress).isEmpty()) {
303 nextHopHost = hostService.getHostsByIp(nextHopIpAddress)
304 .iterator().next();
305 } else {
Pingping Linffa27d32015-04-30 14:41:03 -0700306 hostService.startMonitoringIp(nextHopIpAddress);
307 if (hostService.getHostsByIp(privateIp).isEmpty()) {
308 hostService.startMonitoringIp(privateIp);
309 }
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700310 return false;
Pingping Linffa27d32015-04-30 14:41:03 -0700311 }
312
Pingping Linffa27d32015-04-30 14:41:03 -0700313 ConnectPoint nextHopConnectPoint =
314 new ConnectPoint(nextHopHost.location().elementId(),
315 nextHopHost.location().port());
Pingping Lindead2052015-06-08 16:07:23 -0700316 ConnectPoint localHostConnectPoint = nodeToPort.get(hostName);
Pingping Linffa27d32015-04-30 14:41:03 -0700317
318 // Generate and install intent for traffic from host configured with
319 // private IP
320 if (!p2pIntentsFromHost.containsKey(privateIp)) {
321 PointToPointIntent toNextHopIntent
322 = srcMatchIntentGenerator(privateIp,
323 publicIp,
324 nextHopHost.mac(),
325 nextHopConnectPoint,
326 localHostConnectPoint
327 );
328 p2pIntentsFromHost.put(privateIp, toNextHopIntent);
329 intentService.submit(toNextHopIntent);
330 }
331
332 // Generate and install intent for traffic to host configured with
333 // private IP
334 if (!p2pIntentsToHost.containsKey(privateIp)) {
335 PointToPointIntent toLocalHostIntent
336 = dstMatchIntentGenerator(publicIp,
337 privateIp,
Pingping Lindead2052015-06-08 16:07:23 -0700338 hostMacAddress,
Pingping Linffa27d32015-04-30 14:41:03 -0700339 localHostConnectPoint,
340 nextHopConnectPoint);
Pingping Lind2afaf22015-06-02 10:46:29 -0700341 p2pIntentsToHost.put(privateIp, toLocalHostIntent);
Pingping Linffa27d32015-04-30 14:41:03 -0700342 intentService.submit(toLocalHostIntent);
343 }
344
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700345 return true;
346 }
347
348 /**
349 * Listener for host events.
350 */
351 private class InternalHostListener implements HostListener {
352 @Override
353 public void event(HostEvent event) {
354 log.debug("Received HostEvent {}", event);
355
356 Host host = event.subject();
357 if (event.type() != HostEvent.Type.HOST_ADDED) {
358 return;
359 }
360
361 for (IpAddress ipAddress: host.ipAddresses()) {
Pingping Lindead2052015-06-08 16:07:23 -0700362 // The POST method from XOS gives us MAC and host name, so we
363 // do not need to do anything after receive a vCPE host event
364 // for now.
365 /*if (privateIpAddressSet.contains(ipAddress)) {
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700366 createVbngAgain(ipAddress);
Pingping Lindead2052015-06-08 16:07:23 -0700367 }*/
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700368
369 if (nextHopIpAddress != null &&
370 ipAddress.equals(nextHopIpAddress)) {
Pingping Lindead2052015-06-08 16:07:23 -0700371
372 for (Entry<IpAddress, VcpeHost> entry:
373 privateIpAddressMap.entrySet()) {
374 createVbngAgain(entry.getKey());
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700375 }
Pingping Lindead2052015-06-08 16:07:23 -0700376
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700377 }
378 }
379 }
380 }
381
382 /**
383 * Tries to create vBNG again after receiving a host event if the IP
Pingping Lindead2052015-06-08 16:07:23 -0700384 * address of the host is the next hop IP address.
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700385 *
386 * @param privateIpAddress the private IP address
387 */
388 private void createVbngAgain(IpAddress privateIpAddress) {
389 IpAddress publicIpAddress = vbngConfigurationService
390 .getAssignedPublicIpAddress(privateIpAddress);
391 if (publicIpAddress == null) {
392 // We only need to handle the private IP addresses for which we
393 // already returned the REST replies with assigned public IP
394 // addresses. If a private IP addresses does not have an assigned
395 // public IP address, we should not get it an available public IP
396 // address here, and we should delete it in the unhandled private
Pingping Lindead2052015-06-08 16:07:23 -0700397 // IP address map.
398 privateIpAddressMap.remove(privateIpAddress);
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700399 return;
400 }
Pingping Lindead2052015-06-08 16:07:23 -0700401 VcpeHost vcpeHost = privateIpAddressMap.get(privateIpAddress);
402 if (setupForwardingPaths(privateIpAddress, publicIpAddress,
403 vcpeHost.macAddress, vcpeHost.hostName)) {
404 privateIpAddressMap.remove(privateIpAddress);
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700405 }
Pingping Linffa27d32015-04-30 14:41:03 -0700406 }
407
408 /**
409 * PointToPointIntent Generator.
410 * <p>
411 * The intent will match the source IP address in packet, rewrite the
412 * source IP address, and rewrite the destination MAC address.
413 * </p>
414 *
415 * @param srcIpAddress the source IP address in packet to match
416 * @param newSrcIpAddress the new source IP address to set
417 * @param dstMacAddress the destination MAC address to set
418 * @param dstConnectPoint the egress point
419 * @param srcConnectPoint the ingress point
420 * @return a PointToPointIntent
421 */
422 private PointToPointIntent srcMatchIntentGenerator(
423 IpAddress srcIpAddress,
424 IpAddress newSrcIpAddress,
425 MacAddress dstMacAddress,
426 ConnectPoint dstConnectPoint,
427 ConnectPoint srcConnectPoint) {
428 checkNotNull(srcIpAddress);
429 checkNotNull(newSrcIpAddress);
430 checkNotNull(dstMacAddress);
431 checkNotNull(dstConnectPoint);
432 checkNotNull(srcConnectPoint);
433
434 TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
435 selector.matchEthType(Ethernet.TYPE_IPV4);
436 selector.matchIPSrc(IpPrefix.valueOf(srcIpAddress,
437 IpPrefix.MAX_INET_MASK_LENGTH));
438
439 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
440 treatment.setEthDst(dstMacAddress);
441 treatment.setIpSrc(newSrcIpAddress);
442
443 Key key = Key.of(srcIpAddress.toString() + "MatchSrc", appId);
444 PointToPointIntent intent = PointToPointIntent.builder()
445 .appId(appId)
446 .key(key)
447 .selector(selector.build())
448 .treatment(treatment.build())
449 .egressPoint(dstConnectPoint)
450 .ingressPoint(srcConnectPoint)
451 .build();
452
453 log.info("Generated a PointToPointIntent for traffic from local host "
454 + ": {}", intent);
455 return intent;
456 }
457
458 /**
459 * PointToPointIntent Generator.
460 * <p>
461 * The intent will match the destination IP address in packet, rewrite the
462 * destination IP address, and rewrite the destination MAC address.
463 * </p>
464 *
465 * @param dstIpAddress the destination IP address in packet to match
466 * @param newDstIpAddress the new destination IP address to set
467 * @param dstMacAddress the destination MAC address to set
468 * @param dstConnectPoint the egress point
469 * @param srcConnectPoint the ingress point
470 * @return a PointToPointIntent
471 */
472 private PointToPointIntent dstMatchIntentGenerator(
473 IpAddress dstIpAddress,
474 IpAddress newDstIpAddress,
475 MacAddress dstMacAddress,
476 ConnectPoint dstConnectPoint,
477 ConnectPoint srcConnectPoint) {
478 checkNotNull(dstIpAddress);
479 checkNotNull(newDstIpAddress);
480 checkNotNull(dstMacAddress);
481 checkNotNull(dstConnectPoint);
482 checkNotNull(srcConnectPoint);
483
484 TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
485 selector.matchEthType(Ethernet.TYPE_IPV4);
486 selector.matchIPDst(IpPrefix.valueOf(dstIpAddress,
487 IpPrefix.MAX_INET_MASK_LENGTH));
488
489 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
490 treatment.setEthDst(dstMacAddress);
491 treatment.setIpDst(newDstIpAddress);
492
493 Key key = Key.of(newDstIpAddress.toString() + "MatchDst", appId);
494 PointToPointIntent intent = PointToPointIntent.builder()
495 .appId(appId)
496 .key(key)
497 .selector(selector.build())
498 .treatment(treatment.build())
499 .egressPoint(dstConnectPoint)
500 .ingressPoint(srcConnectPoint)
501 .build();
502 log.info("Generated a PointToPointIntent for traffic to local host "
503 + ": {}", intent);
504
505 return intent;
506 }
Pingping Lindead2052015-06-08 16:07:23 -0700507
508 /**
509 * Constructor to store the a vCPE host info.
510 */
511 private class VcpeHost {
512 MacAddress macAddress;
513 String hostName;
514 public VcpeHost(MacAddress macAddress, String hostName) {
515 this.macAddress = macAddress;
516 this.hostName = hostName;
517 }
518 }
Pingping Linffa27d32015-04-30 14:41:03 -0700519}