blob: 6b2e94309f3b3b5ceb5f22e7ac767b97fb853ac0 [file] [log] [blame]
Pingping Linffa27d32015-04-30 14:41:03 -07001/*
2 * Copyright 2015 Open Networking Laboratory
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.virtualbng;
17
18import static com.google.common.base.Preconditions.checkNotNull;
19
Pingping Lin4e0c73d2015-05-06 15:41:10 -070020import com.google.common.collect.Sets;
21
22import java.util.Iterator;
Pingping Linffa27d32015-04-30 14:41:03 -070023import java.util.Map;
Pingping Lin4e0c73d2015-05-06 15:41:10 -070024import java.util.Set;
Pingping Linffa27d32015-04-30 14:41:03 -070025import java.util.concurrent.ConcurrentHashMap;
26
27import org.apache.felix.scr.annotations.Activate;
28import org.apache.felix.scr.annotations.Component;
29import org.apache.felix.scr.annotations.Deactivate;
30import org.apache.felix.scr.annotations.Reference;
31import org.apache.felix.scr.annotations.ReferenceCardinality;
32import org.apache.felix.scr.annotations.Service;
33import org.onlab.packet.Ethernet;
34import org.onlab.packet.IpAddress;
35import org.onlab.packet.IpPrefix;
36import org.onlab.packet.MacAddress;
37import org.onosproject.core.ApplicationId;
38import org.onosproject.core.CoreService;
39import org.onosproject.net.ConnectPoint;
40import org.onosproject.net.Host;
41import org.onosproject.net.flow.DefaultTrafficSelector;
42import org.onosproject.net.flow.DefaultTrafficTreatment;
43import org.onosproject.net.flow.TrafficSelector;
44import org.onosproject.net.flow.TrafficTreatment;
Pingping Lin4e0c73d2015-05-06 15:41:10 -070045import org.onosproject.net.host.HostEvent;
46import org.onosproject.net.host.HostListener;
Pingping Linffa27d32015-04-30 14:41:03 -070047import org.onosproject.net.host.HostService;
48import org.onosproject.net.intent.IntentService;
49import org.onosproject.net.intent.Key;
50import org.onosproject.net.intent.PointToPointIntent;
51import org.slf4j.Logger;
52import org.slf4j.LoggerFactory;
53
54/**
55 * This is a virtual Broadband Network Gateway (BNG) application. It mainly
56 * has 3 functions:
57 * (1) assigns and replies a public IP address to a REST request with a private
58 * IP address
59 * (2) maintains the mapping from the private IP address to the public IP address
60 * (3) installs point to point intents for the host configured with private IP
61 * address to access Internet
62 */
63@Component(immediate = true)
64@Service
65public class VbngManager implements VbngService {
66
67 private static final String APP_NAME = "org.onosproject.virtualbng";
68
69 private final Logger log = LoggerFactory.getLogger(getClass());
70
71 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
72 protected CoreService coreService;
73
74 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
75 protected HostService hostService;
76
77 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
78 protected IntentService intentService;
79
80 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
81 protected VbngConfigurationService vbngConfigurationService;
82
83 private ApplicationId appId;
84 private Map<IpAddress, PointToPointIntent> p2pIntentsFromHost;
85 private Map<IpAddress, PointToPointIntent> p2pIntentsToHost;
86
Pingping Lin4e0c73d2015-05-06 15:41:10 -070087 // This set stores all the private IP addresses we failed to create vBNGs
88 // for the first time.
89 private Set<IpAddress> privateIpAddressSet;
90
91 private HostListener hostListener;
92 private IpAddress nextHopIpAddress;
93
Pingping Linffa27d32015-04-30 14:41:03 -070094 @Activate
95 public void activate() {
96 appId = coreService.registerApplication(APP_NAME);
97 p2pIntentsFromHost = new ConcurrentHashMap<>();
98 p2pIntentsToHost = new ConcurrentHashMap<>();
Pingping Lin4e0c73d2015-05-06 15:41:10 -070099 privateIpAddressSet = Sets.newConcurrentHashSet();
100
101 nextHopIpAddress = vbngConfigurationService.getNextHopIpAddress();
102 hostListener = new InternalHostListener();
103 hostService.addListener(hostListener);
104
Pingping Linffa27d32015-04-30 14:41:03 -0700105 log.info("vBNG Started");
106 }
107
108 @Deactivate
109 public void deactivate() {
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700110 hostService.removeListener(hostListener);
Pingping Linffa27d32015-04-30 14:41:03 -0700111 log.info("vBNG Stopped");
112 }
113
114 @Override
115 public IpAddress createVbng(IpAddress privateIpAddress) {
116
Pingping Linffa27d32015-04-30 14:41:03 -0700117 IpAddress publicIpAddress =
118 vbngConfigurationService.getAvailablePublicIpAddress(
119 privateIpAddress);
120 if (publicIpAddress == null) {
121 log.info("Did not find an available public IP address to use.");
122 return null;
123 }
124 log.info("Private IP to Public IP mapping: {} --> {}",
125 privateIpAddress, publicIpAddress);
126
127 // Setup paths between the host configured with private IP and
128 // next hop
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700129 if (!setupForwardingPaths(privateIpAddress, publicIpAddress)) {
130 privateIpAddressSet.add(privateIpAddress);
131 }
Pingping Linffa27d32015-04-30 14:41:03 -0700132 return publicIpAddress;
133 }
134
Pingping Lind2afaf22015-06-02 10:46:29 -0700135 @Override
136 public IpAddress deleteVbng(IpAddress privateIpAddress) {
137 // Recycle the public IP address assigned to this private IP address.
138 // Recycling will also delete the mapping entry from the private IP
139 // address to public IP address.
140 IpAddress assignedPublicIpAddress = vbngConfigurationService
141 .recycleAssignedPublicIpAddress(privateIpAddress);
142 if (assignedPublicIpAddress == null) {
143 return null;
144 }
145
146 // Remove the private IP address from privateIpAddressSet
147 privateIpAddressSet.remove(privateIpAddress);
148
149 // Remove intents
150 removeForwardingPaths(privateIpAddress);
151
152 return assignedPublicIpAddress;
153 }
154
155 /**
156 * Removes the forwarding paths in both two directions between host
157 * configured with private IP and next hop.
158 *
159 * @param privateIp the private IP address of a local host
160 */
161 private void removeForwardingPaths(IpAddress privateIp) {
162 PointToPointIntent toNextHopIntent =
163 p2pIntentsFromHost.remove(privateIp);
164 if (toNextHopIntent != null) {
165 intentService.withdraw(toNextHopIntent);
166 //intentService.purge(toNextHopIntent);
167 }
168 PointToPointIntent toLocalHostIntent =
169 p2pIntentsToHost.remove(privateIp);
170 if (toLocalHostIntent != null) {
171 intentService.withdraw(toLocalHostIntent);
172 //intentService.purge(toLocalHostIntent);
173 }
174 }
175
Pingping Linffa27d32015-04-30 14:41:03 -0700176 /**
177 * Sets up forwarding paths in both two directions between host configured
178 * with private IP and next hop.
179 *
180 * @param privateIp the private IP address of a local host
181 * @param publicIp the public IP address assigned for the private IP address
Pingping Linffa27d32015-04-30 14:41:03 -0700182 */
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700183 private boolean setupForwardingPaths(IpAddress privateIp, IpAddress publicIp) {
Pingping Linffa27d32015-04-30 14:41:03 -0700184 checkNotNull(privateIp);
185 checkNotNull(publicIp);
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700186
187 if (nextHopIpAddress == null) {
188 log.warn("Did not find next hop IP address");
189 return false;
190 }
Pingping Linffa27d32015-04-30 14:41:03 -0700191
192 // If there are already intents for private IP address in the system,
193 // we will do nothing and directly return.
194 if (p2pIntentsFromHost.containsKey(privateIp)
195 && p2pIntentsToHost.containsKey(privateIp)) {
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700196 return true;
Pingping Linffa27d32015-04-30 14:41:03 -0700197 }
198
199 Host localHost = null;
200 Host nextHopHost = null;
201 if (!hostService.getHostsByIp(nextHopIpAddress).isEmpty()) {
202 nextHopHost = hostService.getHostsByIp(nextHopIpAddress)
203 .iterator().next();
204 } else {
Pingping Linffa27d32015-04-30 14:41:03 -0700205 hostService.startMonitoringIp(nextHopIpAddress);
206 if (hostService.getHostsByIp(privateIp).isEmpty()) {
207 hostService.startMonitoringIp(privateIp);
208 }
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700209 return false;
Pingping Linffa27d32015-04-30 14:41:03 -0700210 }
211
212 if (!hostService.getHostsByIp(privateIp).isEmpty()) {
213 localHost =
214 hostService.getHostsByIp(privateIp).iterator().next();
215 } else {
Pingping Linffa27d32015-04-30 14:41:03 -0700216 hostService.startMonitoringIp(privateIp);
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700217 return false;
Pingping Linffa27d32015-04-30 14:41:03 -0700218 }
219
220 ConnectPoint nextHopConnectPoint =
221 new ConnectPoint(nextHopHost.location().elementId(),
222 nextHopHost.location().port());
223 ConnectPoint localHostConnectPoint =
224 new ConnectPoint(localHost.location().elementId(),
225 localHost.location().port());
226
227 // Generate and install intent for traffic from host configured with
228 // private IP
229 if (!p2pIntentsFromHost.containsKey(privateIp)) {
230 PointToPointIntent toNextHopIntent
231 = srcMatchIntentGenerator(privateIp,
232 publicIp,
233 nextHopHost.mac(),
234 nextHopConnectPoint,
235 localHostConnectPoint
236 );
237 p2pIntentsFromHost.put(privateIp, toNextHopIntent);
238 intentService.submit(toNextHopIntent);
239 }
240
241 // Generate and install intent for traffic to host configured with
242 // private IP
243 if (!p2pIntentsToHost.containsKey(privateIp)) {
244 PointToPointIntent toLocalHostIntent
245 = dstMatchIntentGenerator(publicIp,
246 privateIp,
247 localHost.mac(),
248 localHostConnectPoint,
249 nextHopConnectPoint);
Pingping Lind2afaf22015-06-02 10:46:29 -0700250 p2pIntentsToHost.put(privateIp, toLocalHostIntent);
Pingping Linffa27d32015-04-30 14:41:03 -0700251 intentService.submit(toLocalHostIntent);
252 }
253
Pingping Lin4e0c73d2015-05-06 15:41:10 -0700254 return true;
255 }
256
257 /**
258 * Listener for host events.
259 */
260 private class InternalHostListener implements HostListener {
261 @Override
262 public void event(HostEvent event) {
263 log.debug("Received HostEvent {}", event);
264
265 Host host = event.subject();
266 if (event.type() != HostEvent.Type.HOST_ADDED) {
267 return;
268 }
269
270 for (IpAddress ipAddress: host.ipAddresses()) {
271 if (privateIpAddressSet.contains(ipAddress)) {
272 createVbngAgain(ipAddress);
273 }
274
275 if (nextHopIpAddress != null &&
276 ipAddress.equals(nextHopIpAddress)) {
277 Iterator<IpAddress> ipAddresses =
278 privateIpAddressSet.iterator();
279 while (ipAddresses.hasNext()) {
280 IpAddress privateIpAddress = ipAddresses.next();
281 createVbngAgain(privateIpAddress);
282 }
283 }
284 }
285 }
286 }
287
288 /**
289 * Tries to create vBNG again after receiving a host event if the IP
290 * address of the host is a private IP address or the next hop IP
291 * address.
292 *
293 * @param privateIpAddress the private IP address
294 */
295 private void createVbngAgain(IpAddress privateIpAddress) {
296 IpAddress publicIpAddress = vbngConfigurationService
297 .getAssignedPublicIpAddress(privateIpAddress);
298 if (publicIpAddress == null) {
299 // We only need to handle the private IP addresses for which we
300 // already returned the REST replies with assigned public IP
301 // addresses. If a private IP addresses does not have an assigned
302 // public IP address, we should not get it an available public IP
303 // address here, and we should delete it in the unhandled private
304 // IP address set.
305 privateIpAddressSet.remove(privateIpAddress);
306 return;
307 }
308 if (setupForwardingPaths(privateIpAddress, publicIpAddress)) {
309 // At this moment it is still possible to fail to create a vBNG,
310 // because creating a vBNG needs two hosts, one is the local host
311 // configured with private IP address, the other is the next hop
312 // host.
313 privateIpAddressSet.remove(privateIpAddress);
314 }
Pingping Linffa27d32015-04-30 14:41:03 -0700315 }
316
317 /**
318 * PointToPointIntent Generator.
319 * <p>
320 * The intent will match the source IP address in packet, rewrite the
321 * source IP address, and rewrite the destination MAC address.
322 * </p>
323 *
324 * @param srcIpAddress the source IP address in packet to match
325 * @param newSrcIpAddress the new source IP address to set
326 * @param dstMacAddress the destination MAC address to set
327 * @param dstConnectPoint the egress point
328 * @param srcConnectPoint the ingress point
329 * @return a PointToPointIntent
330 */
331 private PointToPointIntent srcMatchIntentGenerator(
332 IpAddress srcIpAddress,
333 IpAddress newSrcIpAddress,
334 MacAddress dstMacAddress,
335 ConnectPoint dstConnectPoint,
336 ConnectPoint srcConnectPoint) {
337 checkNotNull(srcIpAddress);
338 checkNotNull(newSrcIpAddress);
339 checkNotNull(dstMacAddress);
340 checkNotNull(dstConnectPoint);
341 checkNotNull(srcConnectPoint);
342
343 TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
344 selector.matchEthType(Ethernet.TYPE_IPV4);
345 selector.matchIPSrc(IpPrefix.valueOf(srcIpAddress,
346 IpPrefix.MAX_INET_MASK_LENGTH));
347
348 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
349 treatment.setEthDst(dstMacAddress);
350 treatment.setIpSrc(newSrcIpAddress);
351
352 Key key = Key.of(srcIpAddress.toString() + "MatchSrc", appId);
353 PointToPointIntent intent = PointToPointIntent.builder()
354 .appId(appId)
355 .key(key)
356 .selector(selector.build())
357 .treatment(treatment.build())
358 .egressPoint(dstConnectPoint)
359 .ingressPoint(srcConnectPoint)
360 .build();
361
362 log.info("Generated a PointToPointIntent for traffic from local host "
363 + ": {}", intent);
364 return intent;
365 }
366
367 /**
368 * PointToPointIntent Generator.
369 * <p>
370 * The intent will match the destination IP address in packet, rewrite the
371 * destination IP address, and rewrite the destination MAC address.
372 * </p>
373 *
374 * @param dstIpAddress the destination IP address in packet to match
375 * @param newDstIpAddress the new destination IP address to set
376 * @param dstMacAddress the destination MAC address to set
377 * @param dstConnectPoint the egress point
378 * @param srcConnectPoint the ingress point
379 * @return a PointToPointIntent
380 */
381 private PointToPointIntent dstMatchIntentGenerator(
382 IpAddress dstIpAddress,
383 IpAddress newDstIpAddress,
384 MacAddress dstMacAddress,
385 ConnectPoint dstConnectPoint,
386 ConnectPoint srcConnectPoint) {
387 checkNotNull(dstIpAddress);
388 checkNotNull(newDstIpAddress);
389 checkNotNull(dstMacAddress);
390 checkNotNull(dstConnectPoint);
391 checkNotNull(srcConnectPoint);
392
393 TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
394 selector.matchEthType(Ethernet.TYPE_IPV4);
395 selector.matchIPDst(IpPrefix.valueOf(dstIpAddress,
396 IpPrefix.MAX_INET_MASK_LENGTH));
397
398 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
399 treatment.setEthDst(dstMacAddress);
400 treatment.setIpDst(newDstIpAddress);
401
402 Key key = Key.of(newDstIpAddress.toString() + "MatchDst", appId);
403 PointToPointIntent intent = PointToPointIntent.builder()
404 .appId(appId)
405 .key(key)
406 .selector(selector.build())
407 .treatment(treatment.build())
408 .egressPoint(dstConnectPoint)
409 .ingressPoint(srcConnectPoint)
410 .build();
411 log.info("Generated a PointToPointIntent for traffic to local host "
412 + ": {}", intent);
413
414 return intent;
415 }
Pingping Linffa27d32015-04-30 14:41:03 -0700416}