diff options
Diffstat (limited to 'framework/src/onos/apps/virtualbng/src/main/java/org/onosproject/virtualbng/VbngManager.java')
-rw-r--r-- | framework/src/onos/apps/virtualbng/src/main/java/org/onosproject/virtualbng/VbngManager.java | 532 |
1 files changed, 532 insertions, 0 deletions
diff --git a/framework/src/onos/apps/virtualbng/src/main/java/org/onosproject/virtualbng/VbngManager.java b/framework/src/onos/apps/virtualbng/src/main/java/org/onosproject/virtualbng/VbngManager.java new file mode 100644 index 00000000..5e82b7e8 --- /dev/null +++ b/framework/src/onos/apps/virtualbng/src/main/java/org/onosproject/virtualbng/VbngManager.java @@ -0,0 +1,532 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.virtualbng; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.Maps; + +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.onlab.packet.Ethernet; +import org.onlab.packet.IpAddress; +import org.onlab.packet.IpPrefix; +import org.onlab.packet.MacAddress; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Host; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.host.HostEvent; +import org.onosproject.net.host.HostListener; +import org.onosproject.net.host.HostService; +import org.onosproject.net.intent.IntentService; +import org.onosproject.net.intent.Key; +import org.onosproject.net.intent.PointToPointIntent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This is a virtual Broadband Network Gateway (BNG) application. It mainly + * has 3 functions: + * (1) assigns and replies a public IP address to a REST request with a private + * IP address + * (2) maintains the mapping from the private IP address to the public IP address + * (3) installs point to point intents for the host configured with private IP + * address to access Internet + */ +@Component(immediate = true) +@Service +public class VbngManager implements VbngService { + + private static final String APP_NAME = "org.onosproject.virtualbng"; + private static final String VBNG_MAP_NAME = "vbng_mapping"; + + private final Logger log = LoggerFactory.getLogger(getClass()); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected CoreService coreService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected HostService hostService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected IntentService intentService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected VbngConfigurationService vbngConfigurationService; + + private ApplicationId appId; + private Map<IpAddress, PointToPointIntent> p2pIntentsFromHost; + private Map<IpAddress, PointToPointIntent> p2pIntentsToHost; + + // This map stores the mapping from the private IP addresses to VcpeHost. + // The IP addresses in this map are all the private IP addresses we failed + // to create vBNGs due to the next hop host was not in ONOS. + private Map<IpAddress, VcpeHost> privateIpAddressMap; + + // Store the mapping from hostname to connect point + private Map<String, ConnectPoint> nodeToPort; + + private HostListener hostListener; + private IpAddress nextHopIpAddress; + + private static final DeviceId FABRIC_DEVICE_ID = + DeviceId.deviceId("of:8f0e486e73000187"); + + @Activate + public void activate() { + appId = coreService.registerApplication(APP_NAME); + p2pIntentsFromHost = new ConcurrentHashMap<>(); + p2pIntentsToHost = new ConcurrentHashMap<>(); + privateIpAddressMap = new ConcurrentHashMap<>(); + + setupMap(); + + nextHopIpAddress = vbngConfigurationService.getNextHopIpAddress(); + hostListener = new InternalHostListener(); + hostService.addListener(hostListener); + + log.info("vBNG Started"); + + // Recover the status before vBNG restarts + statusRecovery(); + } + + @Deactivate + public void deactivate() { + hostService.removeListener(hostListener); + log.info("vBNG Stopped"); + } + + /** + * Recovers from XOS record. Re-sets up the mapping between private IP + * address and public IP address, re-calculates intents and re-installs + * those intents. + */ + private void statusRecovery() { + log.info("vBNG starts to recover from XOS record......"); + RestClient restClient = + new RestClient(vbngConfigurationService.getXosIpAddress(), + vbngConfigurationService.getXosRestPort()); + ObjectNode map = restClient.getRest(); + if (map == null) { + log.info("Stop to recover vBNG status due to the vBNG map " + + "is null!"); + return; + } + + log.info("Get record from XOS: {}", map); + + ArrayNode array = (ArrayNode) map.get(VBNG_MAP_NAME); + Iterator<JsonNode> entries = array.elements(); + while (entries.hasNext()) { + ObjectNode entry = (ObjectNode) entries.next(); + + IpAddress hostIpAdddress = + IpAddress.valueOf(entry.get("private_ip").asText()); + IpAddress publicIpAddress = + IpAddress.valueOf(entry.get("routeable_subnet").asText()); + MacAddress macAddress = + MacAddress.valueOf(entry.get("mac").asText()); + String hostName = entry.get("hostname").asText(); + + // Create vBNG + createVbng(hostIpAdddress, publicIpAddress, macAddress, hostName); + + } + } + + /** + * Sets up mapping from hostname to connect point. + */ + private void setupMap() { + nodeToPort = Maps.newHashMap(); + + nodeToPort.put("cordcompute01.onlab.us", + new ConnectPoint(FABRIC_DEVICE_ID, + PortNumber.portNumber(48))); + + nodeToPort.put("cordcompute02.onlab.us", + new ConnectPoint(FABRIC_DEVICE_ID, + PortNumber.portNumber(47))); + } + + /** + * Creates a new vBNG. + * + * @param privateIpAddress a private IP address + * @param publicIpAddress the public IP address for the private IP address + * @param hostMacAddress the MAC address for the private IP address + * @param hostName the host name for the private IP address + */ + private void createVbng(IpAddress privateIpAddress, + IpAddress publicIpAddress, + MacAddress hostMacAddress, + String hostName) { + boolean result = vbngConfigurationService + .assignSpecifiedPublicIp(publicIpAddress, privateIpAddress); + if (!result) { + log.info("Assign public IP address {} for private IP address {} " + + "failed!", publicIpAddress, privateIpAddress); + log.info("Failed to create vBNG for private IP address {}", + privateIpAddress); + return; + } + log.info("[ADD] Private IP to Public IP mapping: {} --> {}", + privateIpAddress, publicIpAddress); + + // Setup paths between the host configured with private IP and + // next hop + if (!setupForwardingPaths(privateIpAddress, publicIpAddress, + hostMacAddress, hostName)) { + privateIpAddressMap.put(privateIpAddress, + new VcpeHost(hostMacAddress, hostName)); + } + } + + @Override + public IpAddress createVbng(IpAddress privateIpAddress, + MacAddress hostMacAddress, + String hostName) { + + IpAddress publicIpAddress = + vbngConfigurationService.getAvailablePublicIpAddress( + privateIpAddress); + if (publicIpAddress == null) { + log.info("Did not find an available public IP address to use."); + return null; + } + log.info("[ADD] Private IP to Public IP mapping: {} --> {}", + privateIpAddress, publicIpAddress); + + // Setup paths between the host configured with private IP and + // next hop + if (!setupForwardingPaths(privateIpAddress, publicIpAddress, + hostMacAddress, hostName)) { + privateIpAddressMap.put(privateIpAddress, + new VcpeHost(hostMacAddress, hostName)); + } + return publicIpAddress; + } + + @Override + public IpAddress deleteVbng(IpAddress privateIpAddress) { + // Recycle the public IP address assigned to this private IP address. + // Recycling will also delete the mapping entry from the private IP + // address to public IP address. + IpAddress assignedPublicIpAddress = vbngConfigurationService + .recycleAssignedPublicIpAddress(privateIpAddress); + if (assignedPublicIpAddress == null) { + return null; + } + + // Remove the private IP address from privateIpAddressMap + privateIpAddressMap.remove(privateIpAddress); + + // Remove intents + removeForwardingPaths(privateIpAddress); + + return assignedPublicIpAddress; + } + + /** + * Removes the forwarding paths in both two directions between host + * configured with private IP and next hop. + * + * @param privateIp the private IP address of a local host + */ + private void removeForwardingPaths(IpAddress privateIp) { + PointToPointIntent toNextHopIntent = + p2pIntentsFromHost.remove(privateIp); + if (toNextHopIntent != null) { + intentService.withdraw(toNextHopIntent); + //intentService.purge(toNextHopIntent); + } + PointToPointIntent toLocalHostIntent = + p2pIntentsToHost.remove(privateIp); + if (toLocalHostIntent != null) { + intentService.withdraw(toLocalHostIntent); + //intentService.purge(toLocalHostIntent); + } + } + + /** + * Sets up forwarding paths in both two directions between host configured + * with private IP and next hop. + * + * @param privateIp the private IP address of a local host + * @param publicIp the public IP address assigned for the private IP address + * @param hostMacAddress the MAC address for the IP address + * @param hostName the host name for the IP address + */ + private boolean setupForwardingPaths(IpAddress privateIp, + IpAddress publicIp, + MacAddress hostMacAddress, + String hostName) { + checkNotNull(privateIp); + checkNotNull(publicIp); + checkNotNull(hostMacAddress); + checkNotNull(hostName); + + if (nextHopIpAddress == null) { + log.warn("Did not find next hop IP address"); + return false; + } + + // If there are already intents for private IP address in the system, + // we will do nothing and directly return. + if (p2pIntentsFromHost.containsKey(privateIp) + && p2pIntentsToHost.containsKey(privateIp)) { + return true; + } + + Host nextHopHost = null; + if (!hostService.getHostsByIp(nextHopIpAddress).isEmpty()) { + nextHopHost = hostService.getHostsByIp(nextHopIpAddress) + .iterator().next(); + } else { + hostService.startMonitoringIp(nextHopIpAddress); + if (hostService.getHostsByIp(privateIp).isEmpty()) { + hostService.startMonitoringIp(privateIp); + } + return false; + } + + ConnectPoint nextHopConnectPoint = + new ConnectPoint(nextHopHost.location().elementId(), + nextHopHost.location().port()); + ConnectPoint localHostConnectPoint = nodeToPort.get(hostName); + + // Generate and install intent for traffic from host configured with + // private IP + if (!p2pIntentsFromHost.containsKey(privateIp)) { + PointToPointIntent toNextHopIntent + = srcMatchIntentGenerator(privateIp, + publicIp, + nextHopHost.mac(), + nextHopConnectPoint, + localHostConnectPoint + ); + p2pIntentsFromHost.put(privateIp, toNextHopIntent); + intentService.submit(toNextHopIntent); + } + + // Generate and install intent for traffic to host configured with + // private IP + if (!p2pIntentsToHost.containsKey(privateIp)) { + PointToPointIntent toLocalHostIntent + = dstMatchIntentGenerator(publicIp, + privateIp, + hostMacAddress, + localHostConnectPoint, + nextHopConnectPoint); + p2pIntentsToHost.put(privateIp, toLocalHostIntent); + intentService.submit(toLocalHostIntent); + } + + return true; + } + + /** + * Listener for host events. + */ + private class InternalHostListener implements HostListener { + @Override + public void event(HostEvent event) { + log.debug("Received HostEvent {}", event); + + Host host = event.subject(); + if (event.type() != HostEvent.Type.HOST_ADDED) { + return; + } + + for (IpAddress ipAddress: host.ipAddresses()) { + // The POST method from XOS gives us MAC and host name, so we + // do not need to do anything after receive a vCPE host event + // for now. + /*if (privateIpAddressSet.contains(ipAddress)) { + createVbngAgain(ipAddress); + }*/ + + if (nextHopIpAddress != null && + ipAddress.equals(nextHopIpAddress)) { + + for (Entry<IpAddress, VcpeHost> entry: + privateIpAddressMap.entrySet()) { + createVbngAgain(entry.getKey()); + } + + } + } + } + } + + /** + * Tries to create vBNG again after receiving a host event if the IP + * address of the host is the next hop IP address. + * + * @param privateIpAddress the private IP address + */ + private void createVbngAgain(IpAddress privateIpAddress) { + IpAddress publicIpAddress = vbngConfigurationService + .getAssignedPublicIpAddress(privateIpAddress); + if (publicIpAddress == null) { + // We only need to handle the private IP addresses for which we + // already returned the REST replies with assigned public IP + // addresses. If a private IP addresses does not have an assigned + // public IP address, we should not get it an available public IP + // address here, and we should delete it in the unhandled private + // IP address map. + privateIpAddressMap.remove(privateIpAddress); + return; + } + VcpeHost vcpeHost = privateIpAddressMap.get(privateIpAddress); + if (setupForwardingPaths(privateIpAddress, publicIpAddress, + vcpeHost.macAddress, vcpeHost.hostName)) { + privateIpAddressMap.remove(privateIpAddress); + } + } + + /** + * PointToPointIntent Generator. + * <p> + * The intent will match the source IP address in packet, rewrite the + * source IP address, and rewrite the destination MAC address. + * </p> + * + * @param srcIpAddress the source IP address in packet to match + * @param newSrcIpAddress the new source IP address to set + * @param dstMacAddress the destination MAC address to set + * @param dstConnectPoint the egress point + * @param srcConnectPoint the ingress point + * @return a PointToPointIntent + */ + private PointToPointIntent srcMatchIntentGenerator( + IpAddress srcIpAddress, + IpAddress newSrcIpAddress, + MacAddress dstMacAddress, + ConnectPoint dstConnectPoint, + ConnectPoint srcConnectPoint) { + checkNotNull(srcIpAddress); + checkNotNull(newSrcIpAddress); + checkNotNull(dstMacAddress); + checkNotNull(dstConnectPoint); + checkNotNull(srcConnectPoint); + + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + selector.matchEthType(Ethernet.TYPE_IPV4); + selector.matchIPSrc(IpPrefix.valueOf(srcIpAddress, + IpPrefix.MAX_INET_MASK_LENGTH)); + + TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder(); + treatment.setEthDst(dstMacAddress); + treatment.setIpSrc(newSrcIpAddress); + + Key key = Key.of(srcIpAddress.toString() + "MatchSrc", appId); + PointToPointIntent intent = PointToPointIntent.builder() + .appId(appId) + .key(key) + .selector(selector.build()) + .treatment(treatment.build()) + .egressPoint(dstConnectPoint) + .ingressPoint(srcConnectPoint) + .build(); + + log.info("Generated a PointToPointIntent for traffic from local host " + + ": {}", intent); + return intent; + } + + /** + * PointToPointIntent Generator. + * <p> + * The intent will match the destination IP address in packet, rewrite the + * destination IP address, and rewrite the destination MAC address. + * </p> + * + * @param dstIpAddress the destination IP address in packet to match + * @param newDstIpAddress the new destination IP address to set + * @param dstMacAddress the destination MAC address to set + * @param dstConnectPoint the egress point + * @param srcConnectPoint the ingress point + * @return a PointToPointIntent + */ + private PointToPointIntent dstMatchIntentGenerator( + IpAddress dstIpAddress, + IpAddress newDstIpAddress, + MacAddress dstMacAddress, + ConnectPoint dstConnectPoint, + ConnectPoint srcConnectPoint) { + checkNotNull(dstIpAddress); + checkNotNull(newDstIpAddress); + checkNotNull(dstMacAddress); + checkNotNull(dstConnectPoint); + checkNotNull(srcConnectPoint); + + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + selector.matchEthType(Ethernet.TYPE_IPV4); + selector.matchIPDst(IpPrefix.valueOf(dstIpAddress, + IpPrefix.MAX_INET_MASK_LENGTH)); + + TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder(); + treatment.setEthDst(dstMacAddress); + treatment.setIpDst(newDstIpAddress); + + Key key = Key.of(newDstIpAddress.toString() + "MatchDst", appId); + PointToPointIntent intent = PointToPointIntent.builder() + .appId(appId) + .key(key) + .selector(selector.build()) + .treatment(treatment.build()) + .egressPoint(dstConnectPoint) + .ingressPoint(srcConnectPoint) + .build(); + log.info("Generated a PointToPointIntent for traffic to local host " + + ": {}", intent); + + return intent; + } + + /** + * Constructor to store the a vCPE host info. + */ + private class VcpeHost { + MacAddress macAddress; + String hostName; + public VcpeHost(MacAddress macAddress, String hostName) { + this.macAddress = macAddress; + this.hostName = hostName; + } + } +} |