/* * Copyright 2016-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.castor; 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.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.net.ConnectPoint; 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.onosproject.intentsync.IntentSynchronizationService; 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, enabled = true) @Service public class ConnectivityManager implements ConnectivityManagerService { @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected IntentSynchronizationService intentSynchronizer; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected CoreService coreService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) 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) .ingressPoint(portOne) .egressPoint(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) .ingressPoint(portTwo) .egressPoint(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) .ingressPoint(portOne) .egressPoint(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) .ingressPoint(portTwo) .egressPoint(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<ConnectPoint> ingressPorts = new HashSet<>(); ConnectPoint egressPort = ConnectPoint.deviceConnectPoint(peer.getPort()); for (Peer inPeer : castorStore.getAllPeers()) { if (!inPeer.getName().equals(peer.getName())) { ingressPorts.add(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) .ingressPoints(ingressPorts) .egressPoint(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<ConnectPoint> ingressPoints = oldIntent.ingressPoints(); ConnectPoint egressPoint = oldIntent.egressPoint(); if (ingressPoints.add(ConnectPoint.deviceConnectPoint(peer.getPort()))) { MultiPointToSinglePointIntent updatedMp2pIntent = MultiPointToSinglePointIntent.builder() .appId(appId) .key(oldIntent.key()) .selector(oldIntent.selector()) .treatment(oldIntent.treatment()) .ingressPoints(ingressPoints) .egressPoint(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<ConnectPoint> ingressPoints = oldIntent.ingressPoints(); ConnectPoint egressPoint = oldIntent.egressPoint(); if (ingressPoints.remove(ConnectPoint.deviceConnectPoint(peer.getPort()))) { MultiPointToSinglePointIntent updatedMp2pIntent = MultiPointToSinglePointIntent.builder() .appId(appId) .key(oldIntent.key()) .selector(oldIntent.selector()) .treatment(oldIntent.treatment()) .ingressPoints(ingressPoints) .egressPoint(egressPoint) .priority(oldIntent.priority()) .build(); intents.put(peer.getIpAddress(), updatedMp2pIntent); intentSynchronizer.submit(updatedMp2pIntent); } } for (String key : intents.keySet()) { castorStore.storeLayer2Intent(key, intents.get(key)); } } }