blob: 5125ce36a6f64094c9585d70f5a0d7a4f6abbfd4 [file] [log] [blame]
/*
* Copyright 2016-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.castor;
import org.onlab.packet.Ethernet;
import org.onlab.packet.IPv4;
import org.onlab.packet.IPv6;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
import org.onlab.packet.TpPort;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.intentsync.IntentSynchronizationService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.FilteredConnectPoint;
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.intent.Key;
import org.onosproject.net.intent.MultiPointToSinglePointIntent;
import org.onosproject.net.intent.PointToPointIntent;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Manages the connectivity requirements between peers.
*/
@Component(immediate = true, service = ConnectivityManagerService.class)
public class ConnectivityManager implements ConnectivityManagerService {
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected IntentSynchronizationService intentSynchronizer;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected CastorStore castorStore;
private static final int PRIORITY_OFFSET = 1000;
private static final int FLOW_PRIORITY = 500;
private static final String SUFFIX_DST = "dst";
private static final String SUFFIX_SRC = "src";
private static final String SUFFIX_ICMP = "icmp";
private static final Logger log = LoggerFactory.getLogger(ConnectivityManager.class);
private static final short BGP_PORT = 179;
private ApplicationId appId;
@Activate
public void activate() {
appId = coreService.getAppId(Castor.CASTOR_APP);
}
@Deactivate
public void deactivate() {
}
/**
* Inputs the Route Servers.
*/
@Override
public void start(Peer server) {
//routeServers.add(server);
//allPeers.add(server);
castorStore.storePeer(server);
castorStore.storeServer(server);
}
/**
* Stops the peer connectivity manager.
*/
public void stop() {};
/**
* Sets up paths to establish connectivity between all internal.
* BGP speakers and external BGP peers.
*/
@Override
public void setUpConnectivity(Peer peer) {
if (!castorStore.getCustomers().contains(peer)) {
castorStore.storePeer(peer);
castorStore.storeCustomer(peer);
}
for (Peer routeServer : castorStore.getServers()) {
log.debug("Start to set up BGP paths for BGP peer and Route Server"
+ peer + "&" + routeServer);
buildSpeakerIntents(routeServer, peer).forEach(i -> {
castorStore.storePeerIntent(i.key(), i);
intentSynchronizer.submit(i);
});
}
}
private Collection<PointToPointIntent> buildSpeakerIntents(Peer speaker, Peer peer) {
List<PointToPointIntent> intents = new ArrayList<>();
IpAddress peerAddress = IpAddress.valueOf(peer.getIpAddress());
IpAddress speakerAddress = IpAddress.valueOf(speaker.getIpAddress());
checkNotNull(peerAddress);
intents.addAll(buildIntents(ConnectPoint.deviceConnectPoint(speaker.getPort()), speakerAddress,
ConnectPoint.deviceConnectPoint(peer.getPort()), peerAddress));
return intents;
}
/**
* Builds the required intents between the two pairs of connect points and
* IP addresses.
*
* @param portOne the first connect point
* @param ipOne the first IP address
* @param portTwo the second connect point
* @param ipTwo the second IP address
* @return the intents to install
*/
private Collection<PointToPointIntent> buildIntents(ConnectPoint portOne,
IpAddress ipOne,
ConnectPoint portTwo,
IpAddress ipTwo) {
List<PointToPointIntent> intents = new ArrayList<>();
TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment();
TrafficSelector selector;
Key key;
byte tcpProtocol;
byte icmpProtocol;
if (ipOne.isIp4()) {
tcpProtocol = IPv4.PROTOCOL_TCP;
icmpProtocol = IPv4.PROTOCOL_ICMP;
} else {
tcpProtocol = IPv6.PROTOCOL_TCP;
icmpProtocol = IPv6.PROTOCOL_ICMP6;
}
// Path from BGP speaker to BGP peer matching source TCP port 179
selector = buildSelector(tcpProtocol,
ipOne,
ipTwo,
BGP_PORT,
null);
key = buildKey(ipOne, ipTwo, SUFFIX_SRC);
intents.add(PointToPointIntent.builder()
.appId(appId)
.key(key)
.selector(selector)
.treatment(treatment)
.filteredIngressPoint(new FilteredConnectPoint(portOne))
.filteredEgressPoint(new FilteredConnectPoint(portTwo))
.priority(PRIORITY_OFFSET)
.build());
// Path from BGP peer to BGP speaker matching destination TCP port 179
selector = buildSelector(tcpProtocol,
ipTwo,
ipOne,
null,
BGP_PORT);
key = buildKey(ipTwo, ipOne, SUFFIX_DST);
intents.add(PointToPointIntent.builder()
.appId(appId)
.key(key)
.selector(selector)
.treatment(treatment)
.filteredIngressPoint(new FilteredConnectPoint(portTwo))
.filteredEgressPoint(new FilteredConnectPoint(portOne))
.priority(PRIORITY_OFFSET)
.build());
// ICMP path from BGP speaker to BGP peer
selector = buildSelector(icmpProtocol,
ipOne,
ipTwo,
null,
null);
key = buildKey(ipOne, ipTwo, SUFFIX_ICMP);
intents.add(PointToPointIntent.builder()
.appId(appId)
.key(key)
.selector(selector)
.treatment(treatment)
.filteredIngressPoint(new FilteredConnectPoint(portOne))
.filteredEgressPoint(new FilteredConnectPoint(portTwo))
.priority(PRIORITY_OFFSET)
.build());
// ICMP path from BGP peer to BGP speaker
selector = buildSelector(icmpProtocol,
ipTwo,
ipOne,
null,
null);
key = buildKey(ipTwo, ipOne, SUFFIX_ICMP);
intents.add(PointToPointIntent.builder()
.appId(appId)
.key(key)
.selector(selector)
.treatment(treatment)
.filteredIngressPoint(new FilteredConnectPoint(portTwo))
.filteredEgressPoint(new FilteredConnectPoint(portOne))
.priority(PRIORITY_OFFSET)
.build());
return intents;
}
/**
* Builds a traffic selector based on the set of input parameters.
*
* @param ipProto IP protocol
* @param srcIp source IP address
* @param dstIp destination IP address
* @param srcTcpPort source TCP port, or null if shouldn't be set
* @param dstTcpPort destination TCP port, or null if shouldn't be set
* @return the new traffic selector
*/
private TrafficSelector buildSelector(byte ipProto, IpAddress srcIp,
IpAddress dstIp, Short srcTcpPort,
Short dstTcpPort) {
TrafficSelector.Builder builder = DefaultTrafficSelector.builder().matchIPProtocol(ipProto);
if (dstIp.isIp4()) {
builder.matchEthType(Ethernet.TYPE_IPV4)
.matchIPSrc(IpPrefix.valueOf(srcIp, IpPrefix.MAX_INET_MASK_LENGTH))
.matchIPDst(IpPrefix.valueOf(dstIp, IpPrefix.MAX_INET_MASK_LENGTH));
} else {
builder.matchEthType(Ethernet.TYPE_IPV6)
.matchIPv6Src(IpPrefix.valueOf(srcIp, IpPrefix.MAX_INET6_MASK_LENGTH))
.matchIPv6Dst(IpPrefix.valueOf(dstIp, IpPrefix.MAX_INET6_MASK_LENGTH));
}
if (srcTcpPort != null) {
builder.matchTcpSrc(TpPort.tpPort(srcTcpPort));
}
if (dstTcpPort != null) {
builder.matchTcpDst(TpPort.tpPort(dstTcpPort));
}
return builder.build();
}
/**
* Builds an intent Key for a point-to-point intent based off the source
* and destination IP address, as well as a suffix String to distinguish
* between different types of intents between the same source and
* destination.
*
* @param srcIp source IP address
* @param dstIp destination IP address
* @param suffix suffix string
* @return intent key
*/
private Key buildKey(IpAddress srcIp, IpAddress dstIp, String suffix) {
String keyString = new StringBuilder()
.append(srcIp.toString())
.append("-")
.append(dstIp.toString())
.append("-")
.append(suffix)
.toString();
return Key.of(keyString, appId);
}
@Override
public void setUpL2(Peer peer) {
// First update all the previous existing intents. Update ingress points.
if (!castorStore.getLayer2Intents().isEmpty()) {
updateExistingL2Intents(peer);
}
Set<FilteredConnectPoint> ingressPorts = new HashSet<>();
ConnectPoint egressPort = ConnectPoint.deviceConnectPoint(peer.getPort());
for (Peer inPeer : castorStore.getAllPeers()) {
if (!inPeer.getName().equals(peer.getName())) {
ingressPorts.add(new FilteredConnectPoint(ConnectPoint.deviceConnectPoint(inPeer.getPort())));
}
}
TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
MacAddress macAddress = castorStore.getAddressMap().get(IpAddress.valueOf(peer.getIpAddress()));
selector.matchEthDst(macAddress);
TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment();
Key key = Key.of(peer.getIpAddress(), appId);
MultiPointToSinglePointIntent intent = MultiPointToSinglePointIntent.builder()
.appId(appId)
.key(key)
.selector(selector.build())
.treatment(treatment)
.filteredIngressPoints(ingressPorts)
.filteredEgressPoint(new FilteredConnectPoint(egressPort))
.priority(FLOW_PRIORITY)
.build();
intentSynchronizer.submit(intent);
castorStore.storeLayer2Intent(peer.getIpAddress(), intent);
castorStore.removeCustomer(peer);
peer.setL2(true);
castorStore.storeCustomer(peer);
}
/**
* Updates the existing layer 2 flows. Whenever a new Peer is added, it is also
* added as the ingress point to the existing layer two flows.
*
* @param peer The Peer being added.
*/
private void updateExistingL2Intents(Peer peer) {
Collection<MultiPointToSinglePointIntent> oldIntents = castorStore.getLayer2Intents().values();
for (MultiPointToSinglePointIntent oldIntent : oldIntents) {
Set<FilteredConnectPoint> ingressPoints = oldIntent.filteredIngressPoints();
ConnectPoint egressPoint = oldIntent.egressPoint();
if (ingressPoints.add(new FilteredConnectPoint(ConnectPoint.deviceConnectPoint(peer.getPort())))) {
MultiPointToSinglePointIntent updatedMp2pIntent =
MultiPointToSinglePointIntent.builder()
.appId(appId)
.key(oldIntent.key())
.selector(oldIntent.selector())
.treatment(oldIntent.treatment())
.filteredIngressPoints(ingressPoints)
.filteredEgressPoint(new FilteredConnectPoint(egressPoint))
.priority(oldIntent.priority())
.build();
//layer2Intents.put(peer.getIpAddress(), updatedMp2pIntent);
castorStore.storeLayer2Intent(peer.getIpAddress(), updatedMp2pIntent);
intentSynchronizer.submit(updatedMp2pIntent);
}
}
}
@Override
public void deletePeer(Peer peer) {
if (castorStore.getCustomers().contains(peer)) {
deletel3(peer);
for (Peer customer : castorStore.getCustomers()) {
if (customer.getIpAddress().equals(peer.getIpAddress()) && customer.getl2Status()) {
deleteL2(customer);
updateL2AfterDeletion(customer);
}
}
castorStore.removeCustomer(peer);
}
}
/**
* Delete all the flows between the Peer being deleted and the Route Servers
* to kill the BGP sessions. It uses the keys to retrive the previous intents
* and withdraw them.
*
* @param peer The Peer being deleted.
*/
private void deletel3(Peer peer) {
List<Key> keys = new LinkedList<>();
IpAddress ipTwo = IpAddress.valueOf(peer.getIpAddress());
for (Peer server : castorStore.getServers()) {
IpAddress ipOne = IpAddress.valueOf(server.getIpAddress());
keys.add(buildKey(ipOne, ipTwo, SUFFIX_SRC));
keys.add(buildKey(ipTwo, ipOne, SUFFIX_DST));
keys.add(buildKey(ipOne, ipTwo, SUFFIX_ICMP));
keys.add(buildKey(ipTwo, ipOne, SUFFIX_ICMP));
}
for (Key keyDel : keys) {
PointToPointIntent intent = castorStore.getPeerIntents().get(keyDel);
intentSynchronizer.withdraw(intent);
castorStore.removePeerIntent(keyDel);
}
}
/**
* Deletes the layer two flows for the peer being deleted.
*
* @param peer The Peer being deleted.
*/
private void deleteL2(Peer peer) {
intentSynchronizer.withdraw(castorStore.getLayer2Intents().get(peer.getIpAddress()));
castorStore.removeLayer2Intent(peer.getIpAddress());
}
/**
* Updates all the layer 2 flows after successful deletion of a Peer.
* The Peer being deleted is removed from the ingress points of all
* other flows.
*
* @param peer The Peer being deleted.
*/
private void updateL2AfterDeletion(Peer peer) {
Collection<MultiPointToSinglePointIntent> oldIntents = castorStore.getLayer2Intents().values();
Map<String, MultiPointToSinglePointIntent> intents = new HashMap<>();
for (MultiPointToSinglePointIntent oldIntent : oldIntents) {
Set<FilteredConnectPoint> ingressPoints = oldIntent.filteredIngressPoints();
FilteredConnectPoint egressPoint = oldIntent.filteredEgressPoint();
if (ingressPoints.remove(new FilteredConnectPoint(ConnectPoint.deviceConnectPoint(peer.getPort())))) {
MultiPointToSinglePointIntent updatedMp2pIntent =
MultiPointToSinglePointIntent.builder()
.appId(appId)
.key(oldIntent.key())
.selector(oldIntent.selector())
.treatment(oldIntent.treatment())
.filteredIngressPoints(ingressPoints)
.filteredEgressPoint(egressPoint)
.priority(oldIntent.priority())
.build();
intents.put(peer.getIpAddress(), updatedMp2pIntent);
intentSynchronizer.submit(updatedMp2pIntent);
}
}
for (String key : intents.keySet()) {
castorStore.storeLayer2Intent(key, intents.get(key));
}
}
}