MVPN RFC6514 Extendend communities
[bgpcep.git] / bgp / rib-impl / src / main / java / org / opendaylight / protocol / bgp / rib / impl / StrictBGPPeerRegistry.java
index 6eb975b120906671e5f431f6b6f4b187e0e26e14..92056a9ddb836057bcda03509a34955111584551 100644 (file)
@@ -8,28 +8,49 @@
 
 package org.opendaylight.protocol.bgp.rib.impl;
 
-import com.google.common.base.Objects;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Maps;
 import com.google.common.net.InetAddresses;
 import com.google.common.primitives.UnsignedInts;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
+import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
 import javax.annotation.concurrent.GuardedBy;
 import javax.annotation.concurrent.ThreadSafe;
+import org.opendaylight.protocol.bgp.parser.AsNumberUtil;
 import org.opendaylight.protocol.bgp.parser.BGPDocumentedException;
 import org.opendaylight.protocol.bgp.parser.BGPError;
+import org.opendaylight.protocol.bgp.parser.impl.message.open.As4CapabilityHandler;
 import org.opendaylight.protocol.bgp.rib.impl.spi.BGPPeerRegistry;
 import org.opendaylight.protocol.bgp.rib.impl.spi.BGPSessionPreferences;
-import org.opendaylight.protocol.bgp.rib.impl.spi.ReusableBGPPeer;
+import org.opendaylight.protocol.bgp.rib.impl.spi.PeerRegistryListener;
+import org.opendaylight.protocol.bgp.rib.impl.spi.PeerRegistrySessionListener;
 import org.opendaylight.protocol.bgp.rib.spi.BGPSessionListener;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.IpAddress;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv4Address;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv6Address;
+import org.opendaylight.protocol.util.Ipv6Util;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.AsNumber;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IetfInetUtil;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Address;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.Open;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.open.message.BgpParameters;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.open.message.bgp.parameters.OptionalCapabilities;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.open.message.bgp.parameters.optional.capabilities.CParameters;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.open.message.bgp.parameters.optional.capabilities.CParametersBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.open.message.bgp.parameters.optional.capabilities.c.parameters.As4BytesCapability;
+import org.opendaylight.yangtools.concepts.AbstractRegistration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -43,57 +64,89 @@ public final class StrictBGPPeerRegistry implements BGPPeerRegistry {
 
     private static final Logger LOG = LoggerFactory.getLogger(StrictBGPPeerRegistry.class);
 
-    // TODO remove backwards compatibility
-    public static final StrictBGPPeerRegistry GLOBAL = new StrictBGPPeerRegistry();
-
     @GuardedBy("this")
-    private final Map<IpAddress, ReusableBGPPeer> peers = Maps.newHashMap();
+    private final Map<IpAddress, BGPSessionListener> peers = Maps.newHashMap();
     @GuardedBy("this")
     private final Map<IpAddress, BGPSessionId> sessionIds = Maps.newHashMap();
     @GuardedBy("this")
     private final Map<IpAddress, BGPSessionPreferences> peerPreferences = Maps.newHashMap();
+    @GuardedBy("this")
+    private final Set<PeerRegistryListener> listeners = new HashSet<>();
+    @GuardedBy("this")
+    private final Set<PeerRegistrySessionListener> sessionListeners = new HashSet<>();
+
+    public static BGPPeerRegistry instance() {
+        return new StrictBGPPeerRegistry();
+    }
 
     @Override
-    public synchronized void addPeer(final IpAddress ip, final ReusableBGPPeer peer, final BGPSessionPreferences preferences) {
-        Preconditions.checkNotNull(ip);
-        Preconditions.checkArgument(!this.peers.containsKey(ip), "Peer for %s already present", ip);
-        this.peers.put(ip, Preconditions.checkNotNull(peer));
-        this.peerPreferences.put(ip, Preconditions.checkNotNull(preferences));
+    public synchronized void addPeer(final IpAddress oldIp, final BGPSessionListener peer,
+            final BGPSessionPreferences preferences) {
+        IpAddress fullIp = getFullIp(oldIp);
+        Preconditions.checkArgument(!this.peers.containsKey(fullIp),
+                "Peer for %s already present", fullIp);
+        this.peers.put(fullIp, requireNonNull(peer));
+        requireNonNull(preferences.getMyAs());
+        requireNonNull(preferences.getParams());
+        requireNonNull(preferences.getBgpId());
+        this.peerPreferences.put(fullIp, preferences);
+        for (final PeerRegistryListener peerRegistryListener : this.listeners) {
+            peerRegistryListener.onPeerAdded(fullIp, preferences);
+        }
+    }
+
+    private IpAddress getFullIp(final IpAddress ip) {
+        requireNonNull(ip);
+        if (ip.getIpv6Address() != null) {
+            return new IpAddress(Ipv6Util.getFullForm(ip.getIpv6Address()));
+        }
+        return new IpAddress(new Ipv4Address(ip.getIpv4Address()));
     }
 
     @Override
-    public synchronized void removePeer(final IpAddress ip) {
-        Preconditions.checkNotNull(ip);
-        this.peers.remove(ip);
+    public synchronized void removePeer(final IpAddress oldIp) {
+        IpAddress fullIp = getFullIp(oldIp);
+        this.peers.remove(fullIp);
+        for (final PeerRegistryListener peerRegistryListener : this.listeners) {
+            peerRegistryListener.onPeerRemoved(fullIp);
+        }
     }
 
     @Override
-    public synchronized void removePeerSession(final IpAddress ip) {
-        Preconditions.checkNotNull(ip);
-        this.sessionIds.remove(ip);
+    public synchronized void removePeerSession(final IpAddress oldIp) {
+        IpAddress fullIp = getFullIp(oldIp);
+        this.sessionIds.remove(fullIp);
+        for (final PeerRegistrySessionListener peerRegistrySessionListener : this.sessionListeners) {
+            peerRegistrySessionListener.onSessionRemoved(fullIp);
+        }
     }
 
     @Override
-    public boolean isPeerConfigured(final IpAddress ip) {
-        Preconditions.checkNotNull(ip);
-        return this.peers.containsKey(ip);
+    public boolean isPeerConfigured(final IpAddress oldIp) {
+        IpAddress fullIp = getFullIp(oldIp);
+        return this.peers.containsKey(fullIp);
     }
 
     private void checkPeerConfigured(final IpAddress ip) {
-        Preconditions.checkState(isPeerConfigured(ip), "BGP peer with ip: %s not configured, configured peers are: %s", ip, this.peers.keySet());
+        Preconditions.checkState(isPeerConfigured(ip),
+                "BGP peer with ip: %s not configured, configured peers are: %s",
+                ip, this.peers.keySet());
     }
 
     @Override
-    public synchronized BGPSessionListener getPeer(final IpAddress ip,
-        final Ipv4Address sourceId, final Ipv4Address remoteId)
-            throws BGPDocumentedException {
-        Preconditions.checkNotNull(ip);
-        Preconditions.checkNotNull(sourceId);
-        Preconditions.checkNotNull(remoteId);
+    public synchronized BGPSessionListener getPeer(final IpAddress ip, final Ipv4Address sourceId,
+        final Ipv4Address remoteId, final Open openObj) throws BGPDocumentedException {
+        requireNonNull(ip);
+        requireNonNull(sourceId);
+        requireNonNull(remoteId);
+        final AsNumber remoteAsNumber = AsNumberUtil.advertizedAsNumber(openObj);
+        requireNonNull(remoteAsNumber);
+
+        final BGPSessionPreferences prefs = getPeerPreferences(ip);
 
         checkPeerConfigured(ip);
 
-        final BGPSessionId currentConnection = new BGPSessionId(sourceId, remoteId);
+        final BGPSessionId currentConnection = new BGPSessionId(sourceId, remoteId, remoteAsNumber);
         final BGPSessionListener p = this.peers.get(ip);
 
         final BGPSessionId previousConnection = this.sessionIds.get(ip);
@@ -104,66 +157,121 @@ public final class StrictBGPPeerRegistry implements BGPPeerRegistry {
 
             // Session reestablished with different ids
             if (!previousConnection.equals(currentConnection)) {
-                LOG.warn("BGP session with {} {} has to be dropped. Same session already present {}", ip, currentConnection, previousConnection);
+                LOG.warn("BGP session with {} {} has to be dropped. Same session already present {}", ip,
+                        currentConnection, previousConnection);
                 throw new BGPDocumentedException(
-                    String.format("BGP session with %s %s has to be dropped. Same session already present %s",
-                        ip, currentConnection, previousConnection),
+                        String.format("BGP session with %s %s has to be dropped. Same session already present %s",
+                                ip, currentConnection, previousConnection),
                         BGPError.CEASE);
 
                 // Session reestablished with lower source bgp id, dropping current
-            } else if (previousConnection.isHigherDirection(currentConnection)) {
-                LOG.warn("BGP session with {} {} has to be dropped. Opposite session already present", ip, currentConnection);
+            } else if (previousConnection.isHigherDirection(currentConnection) ||
+                    previousConnection.hasHigherAsNumber(currentConnection)) {
+                LOG.warn("BGP session with {} {} has to be dropped. Opposite session already present",
+                        ip, currentConnection);
                 throw new BGPDocumentedException(
-                    String.format("BGP session with %s initiated %s has to be dropped. Opposite session already present",
-                        ip, currentConnection),
-                        BGPError.CEASE);
+                        String.format("BGP session with %s initiated %s has to be dropped. "
+                                + "Opposite session already present", ip, currentConnection), BGPError.CEASE);
 
                 // Session reestablished with higher source bgp id, dropping previous
-            } else if (currentConnection.isHigherDirection(previousConnection)) {
+            } else if (currentConnection.isHigherDirection(previousConnection) ||
+                    currentConnection.hasHigherAsNumber(previousConnection)) {
                 LOG.warn("BGP session with {} {} released. Replaced by opposite session", ip, previousConnection);
                 this.peers.get(ip).releaseConnection();
                 return this.peers.get(ip);
-
                 // Session reestablished with same source bgp id, dropping current as duplicate
             } else {
-                LOG.warn("BGP session with %s initiated from %s to %s has to be dropped. Same session already present", ip, sourceId, remoteId);
+                LOG.warn("BGP session with %s initiated from %s to %s has to be dropped. Same session already present",
+                        ip, sourceId, remoteId);
                 throw new BGPDocumentedException(
-                    String.format("BGP session with %s initiated %s has to be dropped. Same session already present",
-                        ip, currentConnection),
-                        BGPError.CEASE);
+                        String.format("BGP session with %s initiated %s has to be dropped. "
+                                        + "Same session already present", ip, currentConnection), BGPError.CEASE);
             }
         }
+        validateAs(remoteAsNumber, openObj, prefs);
 
         // Map session id to peer IP address
         this.sessionIds.put(ip, currentConnection);
+        for (final PeerRegistrySessionListener peerRegistrySessionListener : this.sessionListeners) {
+            peerRegistrySessionListener.onSessionCreated(ip);
+        }
         return p;
     }
 
+    private static void validateAs(final AsNumber remoteAs, final Open openObj, final BGPSessionPreferences localPref)
+            throws BGPDocumentedException {
+        if (!remoteAs.equals(localPref.getExpectedRemoteAs())) {
+            LOG.warn("Unexpected remote AS number. Expecting {}, got {}", localPref.getExpectedRemoteAs(), remoteAs);
+            throw new BGPDocumentedException("Peer AS number mismatch", BGPError.BAD_PEER_AS);
+        }
+
+        // https://tools.ietf.org/html/rfc6286#section-2.2
+        if (openObj.getBgpIdentifier() != null
+                && openObj.getBgpIdentifier().getValue().equals(localPref.getBgpId().getValue())) {
+            LOG.warn("Remote and local BGP Identifiers are the same: {}", openObj.getBgpIdentifier());
+            throw new BGPDocumentedException("Remote and local BGP Identifiers are the same.", BGPError.BAD_BGP_ID);
+        }
+        final List<BgpParameters> prefs = openObj.getBgpParameters();
+        if (prefs != null) {
+            if (getAs4BytesCapability(localPref.getParams()).isPresent() && !getAs4BytesCapability(prefs).isPresent()) {
+                throw new BGPDocumentedException("The peer must advertise AS4Bytes capability.",
+                        BGPError.UNSUPPORTED_CAPABILITY,
+                        serializeAs4BytesCapability(getAs4BytesCapability(localPref.getParams()).get()));
+            }
+            if (!prefs.containsAll(localPref.getParams())) {
+                LOG.info("BGP Open message session parameters differ, session still accepted.");
+            }
+        } else {
+            throw new BGPDocumentedException("Open message unacceptable. Check the configuration of BGP speaker.",
+                    BGPError.UNSPECIFIC_OPEN_ERROR);
+        }
+    }
+
+    private static Optional<As4BytesCapability> getAs4BytesCapability(final List<BgpParameters> prefs) {
+        for (final BgpParameters param : prefs) {
+            for (final OptionalCapabilities capa : param.getOptionalCapabilities()) {
+                final CParameters cParam = capa.getCParameters();
+                if (cParam.getAs4BytesCapability() != null) {
+                    return Optional.of(cParam.getAs4BytesCapability());
+                }
+            }
+        }
+        return Optional.absent();
+    }
+
+    private static byte[] serializeAs4BytesCapability(final As4BytesCapability as4Capability) {
+        final ByteBuf buffer = Unpooled.buffer(1 /*CODE*/ + 1 /*LENGTH*/
+                + Integer.SIZE / Byte.SIZE /*4 byte value*/);
+        final As4CapabilityHandler serializer = new As4CapabilityHandler();
+        serializer.serializeCapability(new CParametersBuilder().setAs4BytesCapability(as4Capability).build(), buffer);
+        return buffer.array();
+    }
+
     @Override
     public BGPSessionPreferences getPeerPreferences(final IpAddress ip) {
-        Preconditions.checkNotNull(ip);
+        requireNonNull(ip);
         checkPeerConfigured(ip);
         return this.peerPreferences.get(ip);
     }
 
     /**
-     * Create IpAddress from SocketAddress. Only InetSocketAddress is accepted with inner address: Inet4Address and Inet6Address.
+     * Creates IpAddress from SocketAddress. Only InetSocketAddress
+     * is accepted with inner address: Inet4Address and Inet6Address.
      *
-     * @throws IllegalArgumentException if submitted socket address is not InetSocketAddress[ipv4 | ipv6]
      * @param socketAddress socket address to transform
+     * @return IpAddress equivalent to given socket address
+     * @throws IllegalArgumentException if submitted socket address is not InetSocketAddress[ipv4 | ipv6]
      */
     public static IpAddress getIpAddress(final SocketAddress socketAddress) {
-        Preconditions.checkNotNull(socketAddress);
-        Preconditions.checkArgument(socketAddress instanceof InetSocketAddress, "Expecting InetSocketAddress but was %s", socketAddress.getClass());
+        requireNonNull(socketAddress);
+        Preconditions.checkArgument(socketAddress instanceof InetSocketAddress,
+                "Expecting InetSocketAddress but was %s", socketAddress.getClass());
         final InetAddress inetAddress = ((InetSocketAddress) socketAddress).getAddress();
 
-        if(inetAddress instanceof Inet4Address) {
-            return new IpAddress(new Ipv4Address(inetAddress.getHostAddress()));
-        } else if(inetAddress instanceof Inet6Address) {
-            return new IpAddress(new Ipv6Address(inetAddress.getHostAddress()));
-        }
-
-        throw new IllegalArgumentException("Expecting " + Inet4Address.class + " or " + Inet6Address.class + " but was " + inetAddress.getClass());
+        Preconditions.checkArgument(inetAddress instanceof Inet4Address
+                || inetAddress instanceof Inet6Address, "Expecting %s or %s but was %s",
+                Inet4Address.class, Inet6Address.class, inetAddress.getClass());
+        return IetfInetUtil.INSTANCE.ipAddressFor(inetAddress);
     }
 
     @Override
@@ -174,21 +282,24 @@ public final class StrictBGPPeerRegistry implements BGPPeerRegistry {
 
     @Override
     public String toString() {
-        return Objects.toStringHelper(this)
+        return MoreObjects.toStringHelper(this)
             .add("peers", this.peers.keySet())
             .toString();
     }
 
     /**
-     * Session identifier that contains (source Bgp Id) -> (destination Bgp Id)
+     * Session identifier that contains (source Bgp Id) -> (destination Bgp Id) AsNumber is the remoteAs coming from
+     * remote Open message
      */
     private static final class BGPSessionId {
 
         private final Ipv4Address from, to;
+        private final AsNumber asNumber;
 
-        BGPSessionId(final Ipv4Address from, final Ipv4Address to) {
-            this.from = Preconditions.checkNotNull(from);
-            this.to = Preconditions.checkNotNull(to);
+        BGPSessionId(final Ipv4Address from, final Ipv4Address to, final AsNumber asNumber) {
+            this.from = requireNonNull(from);
+            this.to = requireNonNull(to);
+            this.asNumber = requireNonNull(asNumber);
         }
 
         /**
@@ -230,17 +341,53 @@ public final class StrictBGPPeerRegistry implements BGPPeerRegistry {
             return toLong(this.from) > toLong(other.from);
         }
 
-        private long toLong(final Ipv4Address from) {
+        boolean hasHigherAsNumber(final BGPSessionId other) {
+            return this.asNumber.getValue() > other.asNumber.getValue();
+        }
+
+        private static long toLong(final Ipv4Address from) {
             final int i = InetAddresses.coerceToInteger(InetAddresses.forString(from.getValue()));
             return UnsignedInts.toLong(i);
         }
 
         @Override
         public String toString() {
-            return Objects.toStringHelper(this)
+            return MoreObjects.toStringHelper(this)
                 .add("from", this.from)
                 .add("to", this.to)
                 .toString();
         }
     }
+
+    @Override
+    public synchronized AutoCloseable registerPeerRegisterListener(final PeerRegistryListener listener) {
+        this.listeners.add(listener);
+        for (final Entry<IpAddress, BGPSessionPreferences> entry : this.peerPreferences.entrySet()) {
+            listener.onPeerAdded(entry.getKey(), entry.getValue());
+        }
+        return new AbstractRegistration() {
+            @Override
+            protected void removeRegistration() {
+                synchronized (StrictBGPPeerRegistry.this) {
+                    StrictBGPPeerRegistry.this.listeners.remove(listener);
+                }
+            }
+        };
+    }
+
+    @Override
+    public synchronized AutoCloseable registerPeerSessionListener(final PeerRegistrySessionListener listener) {
+        this.sessionListeners.add(listener);
+        for (final IpAddress ipAddress : this.sessionIds.keySet()) {
+            listener.onSessionCreated(ipAddress);
+        }
+        return new AbstractRegistration() {
+            @Override
+            protected void removeRegistration() {
+                synchronized (StrictBGPPeerRegistry.this) {
+                    StrictBGPPeerRegistry.this.sessionListeners.remove(listener);
+                }
+            }
+        };
+    }
 }