/* * Copyright 2014-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.sdnip; import com.google.common.collect.ImmutableList; 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.TpPort; import org.onlab.packet.VlanId; import org.onosproject.core.ApplicationId; import org.onosproject.incubator.net.intf.Interface; import org.onosproject.incubator.net.intf.InterfaceEvent; import org.onosproject.incubator.net.intf.InterfaceListener; import org.onosproject.incubator.net.intf.InterfaceService; import org.onosproject.net.ConnectPoint; import org.onosproject.net.EncapsulationType; import org.onosproject.net.FilteredConnectPoint; import org.onosproject.net.config.NetworkConfigEvent; import org.onosproject.net.config.NetworkConfigListener; import org.onosproject.net.config.NetworkConfigService; 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.InterfaceIpAddress; import org.onosproject.net.intent.ConnectivityIntent; import org.onosproject.net.intent.IntentUtils; import org.onosproject.net.intent.Key; import org.onosproject.net.intent.PointToPointIntent; import org.onosproject.net.intent.constraint.EncapsulationConstraint; import org.onosproject.intentsync.IntentSynchronizationService; import org.onosproject.routing.RoutingService; import org.onosproject.routing.config.BgpConfig; import org.onosproject.sdnip.config.SdnIpConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import static com.google.common.base.Preconditions.checkNotNull; import static org.onosproject.net.EncapsulationType.NONE; /** * Manages the connectivity requirements between peers. */ public class PeerConnectivityManager { private static final int PRIORITY_OFFSET = 1000; 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( PeerConnectivityManager.class); private static final short BGP_PORT = 179; private final IntentSynchronizationService intentSynchronizer; private final NetworkConfigService configService; private final InterfaceService interfaceService; private final ApplicationId appId; private final ApplicationId routerAppId; private final Map<Key, PointToPointIntent> peerIntents; private final InternalNetworkConfigListener configListener = new InternalNetworkConfigListener(); private final InternalInterfaceListener interfaceListener = new InternalInterfaceListener(); /** * Creates a new PeerConnectivityManager. * * @param appId the application ID * @param intentSynchronizer the intent synchronizer * @param configService the network config service * @param routerAppId application ID * @param interfaceService the interface service */ public PeerConnectivityManager(ApplicationId appId, IntentSynchronizationService intentSynchronizer, NetworkConfigService configService, ApplicationId routerAppId, InterfaceService interfaceService) { this.appId = appId; this.intentSynchronizer = intentSynchronizer; this.configService = configService; this.routerAppId = routerAppId; this.interfaceService = interfaceService; peerIntents = new HashMap<>(); } /** * Starts the peer connectivity manager. */ public void start() { configService.addListener(configListener); interfaceService.addListener(interfaceListener); setUpConnectivity(); } /** * Stops the peer connectivity manager. */ public void stop() { configService.removeListener(configListener); interfaceService.removeListener(interfaceListener); } /** * Sets up paths to establish connectivity between all internal * BGP speakers and external BGP peers. */ private void setUpConnectivity() { BgpConfig bgpConfig = configService.getConfig(routerAppId, RoutingService.CONFIG_CLASS); SdnIpConfig sdnIpConfig = configService.getConfig(appId, SdnIpConfig.class); Set<BgpConfig.BgpSpeakerConfig> bgpSpeakers; EncapsulationType encap; if (bgpConfig == null) { log.debug("No BGP config available"); bgpSpeakers = Collections.emptySet(); } else { bgpSpeakers = bgpConfig.bgpSpeakers(); } if (sdnIpConfig == null) { log.debug("No SDN-IP config available"); encap = EncapsulationType.NONE; } else { encap = sdnIpConfig.encap(); } Map<Key, PointToPointIntent> existingIntents = new HashMap<>(peerIntents); for (BgpConfig.BgpSpeakerConfig bgpSpeaker : bgpSpeakers) { log.debug("Start to set up BGP paths for BGP speaker: {}", bgpSpeaker); buildSpeakerIntents(bgpSpeaker, encap).forEach(i -> { PointToPointIntent intent = existingIntents.remove(i.key()); if (intent == null || !IntentUtils.intentsAreEqual(i, intent)) { peerIntents.put(i.key(), i); intentSynchronizer.submit(i); } }); } // Remove any remaining intents that we used to have that we don't need // anymore existingIntents.values().forEach(i -> { peerIntents.remove(i.key()); intentSynchronizer.withdraw(i); }); } private Collection<PointToPointIntent> buildSpeakerIntents(BgpConfig.BgpSpeakerConfig speaker, EncapsulationType encap) { List<PointToPointIntent> intents = new ArrayList<>(); // Get the BGP Speaker VLAN Id VlanId bgpSpeakerVlanId = speaker.vlan(); for (IpAddress peerAddress : speaker.peers()) { Interface peeringInterface = interfaceService.getMatchingInterface(peerAddress); if (peeringInterface == null) { log.debug("No peering interface found for peer {} on speaker {}", peerAddress, speaker); continue; } IpAddress bgpSpeakerAddress = null; for (InterfaceIpAddress address : peeringInterface.ipAddressesList()) { if (address.subnetAddress().contains(peerAddress)) { bgpSpeakerAddress = address.ipAddress(); break; } } checkNotNull(bgpSpeakerAddress); VlanId peerVlanId = peeringInterface.vlan(); intents.addAll(buildIntents(speaker.connectPoint(), bgpSpeakerVlanId, bgpSpeakerAddress, peeringInterface.connectPoint(), peerVlanId, peerAddress, encap)); } return intents; } /** * Builds the required intents between a BGP speaker and an external router. * * @param portOne the BGP speaker connect point * @param vlanOne the BGP speaker VLAN * @param ipOne the BGP speaker IP address * @param portTwo the external BGP peer connect point * @param vlanTwo the external BGP peer VLAN * @param ipTwo the external BGP peer IP address * @param encap the encapsulation type * @return the intents to install */ private Collection<PointToPointIntent> buildIntents(ConnectPoint portOne, VlanId vlanOne, IpAddress ipOne, ConnectPoint portTwo, VlanId vlanTwo, IpAddress ipTwo, EncapsulationType encap) { List<PointToPointIntent> intents = new ArrayList<>(); TrafficTreatment.Builder treatmentToPeer = DefaultTrafficTreatment.builder(); TrafficTreatment.Builder treatmentToSpeaker = DefaultTrafficTreatment.builder(); PointToPointIntent.Builder intentBuilder; 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; } // Add VLAN treatment for traffic going from BGP speaker to BGP peer treatmentToPeer = applyVlanTreatment(vlanOne, vlanTwo, treatmentToPeer); // Path from BGP speaker to BGP peer matching destination TCP port 179 selector = buildSelector(tcpProtocol, vlanOne, ipOne, ipTwo, null, BGP_PORT); key = buildKey(ipOne, ipTwo, SUFFIX_DST); intentBuilder = PointToPointIntent.builder() .appId(appId) .key(key) .filteredIngressPoint(new FilteredConnectPoint(portOne)) .filteredEgressPoint(new FilteredConnectPoint(portTwo)) .selector(selector) .treatment(treatmentToPeer.build()) .priority(PRIORITY_OFFSET); encap(intentBuilder, encap); intents.add(intentBuilder.build()); // Path from BGP speaker to BGP peer matching source TCP port 179 selector = buildSelector(tcpProtocol, vlanOne, ipOne, ipTwo, BGP_PORT, null); key = buildKey(ipOne, ipTwo, SUFFIX_SRC); intentBuilder = PointToPointIntent.builder() .appId(appId) .key(key) .filteredIngressPoint(new FilteredConnectPoint(portOne)) .filteredEgressPoint(new FilteredConnectPoint(portTwo)) .selector(selector) .treatment(treatmentToPeer.build()) .priority(PRIORITY_OFFSET); encap(intentBuilder, encap); intents.add(intentBuilder.build()); // ICMP path from BGP speaker to BGP peer selector = buildSelector(icmpProtocol, vlanOne, ipOne, ipTwo, null, null); key = buildKey(ipOne, ipTwo, SUFFIX_ICMP); intentBuilder = PointToPointIntent.builder() .appId(appId) .key(key) .filteredIngressPoint(new FilteredConnectPoint(portOne)) .filteredEgressPoint(new FilteredConnectPoint(portTwo)) .selector(selector) .treatment(treatmentToPeer.build()) .priority(PRIORITY_OFFSET); encap(intentBuilder, encap); intents.add(intentBuilder.build()); // Add VLAN treatment for traffic going from BGP peer to BGP speaker treatmentToSpeaker = applyVlanTreatment(vlanTwo, vlanOne, treatmentToSpeaker); // Path from BGP peer to BGP speaker matching destination TCP port 179 selector = buildSelector(tcpProtocol, vlanTwo, ipTwo, ipOne, null, BGP_PORT); key = buildKey(ipTwo, ipOne, SUFFIX_DST); intentBuilder = PointToPointIntent.builder() .appId(appId) .key(key) .filteredIngressPoint(new FilteredConnectPoint(portTwo)) .filteredEgressPoint(new FilteredConnectPoint(portOne)) .selector(selector) .treatment(treatmentToSpeaker.build()) .priority(PRIORITY_OFFSET); encap(intentBuilder, encap); intents.add(intentBuilder.build()); // Path from BGP peer to BGP speaker matching source TCP port 179 selector = buildSelector(tcpProtocol, vlanTwo, ipTwo, ipOne, BGP_PORT, null); key = buildKey(ipTwo, ipOne, SUFFIX_SRC); intentBuilder = PointToPointIntent.builder() .appId(appId) .key(key) .filteredIngressPoint(new FilteredConnectPoint(portTwo)) .filteredEgressPoint(new FilteredConnectPoint(portOne)) .selector(selector) .treatment(treatmentToSpeaker.build()) .priority(PRIORITY_OFFSET); encap(intentBuilder, encap); intents.add(intentBuilder.build()); // ICMP path from BGP peer to BGP speaker selector = buildSelector(icmpProtocol, vlanTwo, ipTwo, ipOne, null, null); key = buildKey(ipTwo, ipOne, SUFFIX_ICMP); intentBuilder = PointToPointIntent.builder() .appId(appId) .key(key) .filteredIngressPoint(new FilteredConnectPoint(portTwo)) .filteredEgressPoint(new FilteredConnectPoint(portOne)) .selector(selector) .treatment(treatmentToSpeaker.build()) .priority(PRIORITY_OFFSET); encap(intentBuilder, encap); intents.add(intentBuilder.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, VlanId ingressVlanId, IpAddress srcIp, IpAddress dstIp, Short srcTcpPort, Short dstTcpPort) { TrafficSelector.Builder builder = DefaultTrafficSelector.builder().matchIPProtocol(ipProto); // Match on VLAN Id if a VLAN Id configured on the ingress interface if (!ingressVlanId.equals(VlanId.NONE)) { builder.matchVlanId(ingressVlanId); } 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(); } /* * Adds the VLAN Id treatment before building the intents, depending on how * the VLAN Ids of the BGP speakers and the BGP peers are configured. */ private TrafficTreatment.Builder applyVlanTreatment(VlanId vlanOne, VlanId vlanTwo, TrafficTreatment.Builder treatment) { if (!vlanOne.equals(vlanTwo)) { // VLANs are different. Do some VLAN treatment if (vlanTwo.equals(VlanId.NONE)) { // VLAN two is none. VLAN one is set. Do a pop treatment.popVlan(); } else { // Either both VLANs are set or vlanOne is not if (vlanOne.equals(VlanId.NONE)) { // VLAN one is none. VLAN two is set. Push the VLAN header treatment.pushVlan(); } // Set the VLAN Id to the egress VLAN Id treatment.setVlanId(vlanTwo); } } return treatment; } /** * 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); } /** * Adds an encapsulation constraint to the builder given, if encap is not * equal to NONE. * * @param builder the intent builder * @param encap the encapsulation type */ private static void encap(ConnectivityIntent.Builder builder, EncapsulationType encap) { if (!encap.equals(NONE)) { builder.constraints(ImmutableList.of( new EncapsulationConstraint(encap))); } } private class InternalNetworkConfigListener implements NetworkConfigListener { @Override public void event(NetworkConfigEvent event) { switch (event.type()) { case CONFIG_REGISTERED: break; case CONFIG_UNREGISTERED: break; case CONFIG_ADDED: case CONFIG_UPDATED: case CONFIG_REMOVED: if (event.configClass() == RoutingService.CONFIG_CLASS || event.configClass() == SdnIpConfig.class) { setUpConnectivity(); } break; default: break; } } } private class InternalInterfaceListener implements InterfaceListener { @Override public void event(InterfaceEvent event) { switch (event.type()) { case INTERFACE_ADDED: case INTERFACE_UPDATED: case INTERFACE_REMOVED: setUpConnectivity(); break; default: break; } } } }