/* * Copyright 2015-present 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 com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; 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.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; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import static com.google.common.base.Preconditions.checkNotNull; /** * 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<>(); nextHopIpAddress = vbngConfigurationService.getNextHopIpAddress(); nodeToPort = vbngConfigurationService.getNodeToPort(); 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......"); ObjectNode map; try { RestClient restClient = new RestClient(vbngConfigurationService.getXosIpAddress(), vbngConfigurationService.getXosRestPort()); map = restClient.getRest(); } catch (Exception e) { log.error("Could not contact XOS", e); return; } 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); } } /** * 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; } } }