/* * 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.ObjectMapper; 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.Service; import org.onlab.packet.IpAddress; import org.onlab.packet.IpPrefix; import org.onlab.packet.MacAddress; import org.onosproject.net.ConnectPoint; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Collections; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; /** * Implementation of ConfigurationService which reads virtual BNG * configuration from a file. */ @Component(immediate = true) @Service public class VbngConfigurationManager implements VbngConfigurationService { private final Logger log = LoggerFactory.getLogger(getClass()); private static final String CONFIG_DIR = "../config"; private static final String DEFAULT_CONFIG_FILE = "virtualbng.json"; private String configFileName = DEFAULT_CONFIG_FILE; // If all the IP addresses of one IP prefix are assigned, then we // mark the value of this IP prefix as false, otherwise as true. private Map<IpPrefix, Boolean> localPublicIpPrefixes = new ConcurrentHashMap<>(); // Map from private IP address to public IP address private Map<IpAddress, IpAddress> ipAddressMap = new ConcurrentHashMap<>(); private IpAddress nextHopIpAddress; private MacAddress macOfPublicIpAddresses; private IpAddress xosIpAddress; private int xosRestPort; private Map<String, ConnectPoint> nodeToPort; @Activate public void activate() { readConfiguration(); log.info("vBNG configuration service started"); } @Deactivate public void deactivate() { log.info("vBNG configuration service stopped"); } /** * Instructs the configuration reader to read the configuration from the * file. */ public void readConfiguration() { readConfiguration(configFileName); } /** * Reads virtual BNG information contained in configuration file. * * @param configFilename the name of the configuration file for the virtual * BNG application */ private void readConfiguration(String configFilename) { File configFile = new File(CONFIG_DIR, configFilename); ObjectMapper mapper = new ObjectMapper(); try { log.info("Loading config: {}", configFile.getAbsolutePath()); VbngConfiguration config = mapper.readValue(configFile, VbngConfiguration.class); for (IpPrefix prefix : config.getLocalPublicIpPrefixes()) { localPublicIpPrefixes.put(prefix, true); } nextHopIpAddress = config.getNextHopIpAddress(); macOfPublicIpAddresses = config.getPublicFacingMac(); xosIpAddress = config.getXosIpAddress(); xosRestPort = config.getXosRestPort(); nodeToPort = config.getHosts(); } catch (FileNotFoundException e) { log.warn("Configuration file not found: {}", configFileName); } catch (IOException e) { log.error("Error loading configuration", e); } } @Override public IpAddress getNextHopIpAddress() { return nextHopIpAddress; } @Override public MacAddress getPublicFacingMac() { return macOfPublicIpAddresses; } @Override public IpAddress getXosIpAddress() { return xosIpAddress; } @Override public int getXosRestPort() { return xosRestPort; } @Override public Map<String, ConnectPoint> getNodeToPort() { return nodeToPort; } // TODO handle the case: the number of public IP addresses is not enough // for 1:1 mapping from public IP to private IP. @Override public synchronized IpAddress getAvailablePublicIpAddress(IpAddress privateIpAddress) { // If there is already a mapping entry for the private IP address, // then fetch the public IP address in the mapping entry and return it. IpAddress publicIpAddress = ipAddressMap.get(privateIpAddress); if (publicIpAddress != null) { return publicIpAddress; } // There is no mapping for the private IP address. Iterator<Entry<IpPrefix, Boolean>> prefixes = localPublicIpPrefixes.entrySet().iterator(); while (prefixes.hasNext()) { Entry<IpPrefix, Boolean> prefix = prefixes.next(); if (!prefix.getValue()) { continue; } if (prefix.getKey().prefixLength() == 32) { updateIpPrefixStatus(prefix.getKey(), false); publicIpAddress = prefix.getKey().address(); ipAddressMap.put(privateIpAddress, publicIpAddress); return publicIpAddress; } int prefixLen = prefix.getKey().prefixLength(); int availableIpNum = (int) Math.pow(2, IpPrefix.MAX_INET_MASK_LENGTH - prefixLen) - 1; for (int i = 1; i <= availableIpNum; i++) { publicIpAddress = increaseIpAddress(prefix.getKey().address(), i); if (publicIpAddress == null) { return null; } if (ipAddressMap.values().contains(publicIpAddress)) { continue; } else if (i == availableIpNum) { // All the IP addresses are assigned out // Update this IP prefix status to false // Note: in this version we do not consider the // IP recycling issue. updateIpPrefixStatus(prefix.getKey(), false); ipAddressMap.put(privateIpAddress, publicIpAddress); return publicIpAddress; } else { ipAddressMap.put(privateIpAddress, publicIpAddress); return publicIpAddress; } } } return null; } @Override public IpAddress getAssignedPublicIpAddress(IpAddress privateIpAddress) { return ipAddressMap.get(privateIpAddress); } @Override public boolean isAssignedPublicIpAddress(IpAddress ipAddress) { return ipAddressMap.containsValue(ipAddress); } @Override public synchronized IpAddress recycleAssignedPublicIpAddress(IpAddress privateIpAddress) { IpAddress publicIpAddress = ipAddressMap.remove(privateIpAddress); if (publicIpAddress == null) { return null; } Iterator<Entry<IpPrefix, Boolean>> prefixes = localPublicIpPrefixes.entrySet().iterator(); while (prefixes.hasNext()) { Entry<IpPrefix, Boolean> prefixEntry = prefixes.next(); if (prefixEntry.getKey().contains(publicIpAddress) && !prefixEntry.getValue()) { updateIpPrefixStatus(prefixEntry.getKey(), true); } } log.info("[DELETE] Private IP to Public IP mapping: {} --> {}", privateIpAddress, publicIpAddress); return publicIpAddress; } @Override public Map<IpAddress, IpAddress> getIpAddressMappings() { return Collections.unmodifiableMap(ipAddressMap); } @Override public synchronized boolean assignSpecifiedPublicIp(IpAddress publicIpAddress, IpAddress privateIpAddress) { // Judge whether this public IP address is in our public IP // prefix/address list. boolean isPublicIpExist = false; for (Entry<IpPrefix, Boolean> prefix: localPublicIpPrefixes.entrySet()) { if (prefix.getKey().contains(publicIpAddress)) { isPublicIpExist = true; // Judge whether this public IP address is already assigned if (!prefix.getValue() || isAssignedPublicIpAddress(publicIpAddress)) { log.info("The public IP address {} is already assigned, " + "and not available.", publicIpAddress); return false; } // The public IP address is still available // Store the mapping from private IP address to public IP address ipAddressMap.put(privateIpAddress, publicIpAddress); // Update the prefix status if (prefix.getKey().prefixLength() == 32) { updateIpPrefixStatus(prefix.getKey(), false); return true; } // Judge whether the prefix of this public IP address is used // up, if so, update the IP prefix status. int prefixLen = prefix.getKey().prefixLength(); int availableIpNum = (int) Math.pow(2, IpPrefix.MAX_INET_MASK_LENGTH - prefixLen) - 1; int usedIpNum = 0; for (Entry<IpAddress, IpAddress> ipAddressMapEntry: ipAddressMap.entrySet()) { if (prefix.getKey().contains(ipAddressMapEntry.getValue())) { usedIpNum = usedIpNum + 1; } } if (usedIpNum == availableIpNum) { updateIpPrefixStatus(prefix.getKey(), false); } return true; } } if (!isPublicIpExist) { log.info("The public IP address {} retrieved from XOS mapping does " + "not exist", publicIpAddress); } return false; } /** * Generates a new IP address base on a given IP address plus a number to * increase. * * @param ipAddress the IP address to increase * @param num the number for ipAddress to add * @return the new IP address after increase */ private IpAddress increaseIpAddress(IpAddress ipAddress, int num) { if (ipAddress.isIp6()) { log.info("vBNG currently does not handle IPv6"); return null; } return IpAddress.valueOf(ipAddress.getIp4Address().toInt() + num); } /** * Updates the IP prefix status in the local public IP prefix table. * * @param ipPprefix the IP prefix to update * @param b the new value for the IP prefix */ private void updateIpPrefixStatus(IpPrefix ipPprefix, boolean b) { localPublicIpPrefixes.replace(ipPprefix, b); } }