blob: 4f8e6d91d83819159c4bbb71d59f55d110f50932 [file] [log] [blame]
/*
* Copyright 2015-present Open Networking Foundation
*
* 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;
}
}
}