Optimize getAs4BytesCapability()
[bgpcep.git] / bgp / rib-impl / src / main / java / org / opendaylight / protocol / bgp / rib / impl / StrictBGPPeerRegistry.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.protocol.bgp.rib.impl;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.base.MoreObjects;
13 import com.google.common.base.Preconditions;
14 import com.google.common.net.InetAddresses;
15 import com.google.common.primitives.UnsignedInts;
16 import io.netty.buffer.ByteBuf;
17 import io.netty.buffer.Unpooled;
18 import java.net.Inet4Address;
19 import java.net.Inet6Address;
20 import java.net.InetAddress;
21 import java.net.InetSocketAddress;
22 import java.net.SocketAddress;
23 import java.util.HashMap;
24 import java.util.HashSet;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Map.Entry;
28 import java.util.Set;
29 import org.checkerframework.checker.lock.qual.GuardedBy;
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.opendaylight.protocol.bgp.parser.AsNumberUtil;
32 import org.opendaylight.protocol.bgp.parser.BGPDocumentedException;
33 import org.opendaylight.protocol.bgp.parser.BGPError;
34 import org.opendaylight.protocol.bgp.parser.impl.message.open.As4CapabilityHandler;
35 import org.opendaylight.protocol.bgp.rib.impl.spi.BGPPeerRegistry;
36 import org.opendaylight.protocol.bgp.rib.impl.spi.BGPSessionPreferences;
37 import org.opendaylight.protocol.bgp.rib.impl.spi.PeerRegistryListener;
38 import org.opendaylight.protocol.bgp.rib.impl.spi.PeerRegistrySessionListener;
39 import org.opendaylight.protocol.bgp.rib.spi.BGPSessionListener;
40 import org.opendaylight.protocol.util.Ipv6Util;
41 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.AsNumber;
42 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IetfInetUtil;
43 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
44 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Address;
45 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv6Address;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.Open;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.open.message.BgpParameters;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.open.message.bgp.parameters.OptionalCapabilities;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.open.message.bgp.parameters.optional.capabilities.CParameters;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.open.message.bgp.parameters.optional.capabilities.CParametersBuilder;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.open.message.bgp.parameters.optional.capabilities.c.parameters.As4BytesCapability;
52 import org.opendaylight.yangtools.concepts.AbstractRegistration;
53 import org.opendaylight.yangtools.concepts.Registration;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
56
57 /**
58  * BGP peer registry that allows only 1 session per BGP peer. If a second session with peer is established, one of
59  * the sessions will be dropped. The session with lower source BGP id will be dropped. This class is thread-safe.
60  */
61 public final class StrictBGPPeerRegistry implements BGPPeerRegistry {
62     private static final Logger LOG = LoggerFactory.getLogger(StrictBGPPeerRegistry.class);
63
64     @GuardedBy("this")
65     private final Map<IpAddress, BGPSessionListener> peers = new HashMap<>();
66     @GuardedBy("this")
67     private final Map<IpAddress, BGPSessionId> sessionIds = new HashMap<>();
68     @GuardedBy("this")
69     private final Map<IpAddress, BGPSessionPreferences> peerPreferences = new HashMap<>();
70     @GuardedBy("this")
71     private final Set<PeerRegistryListener> listeners = new HashSet<>();
72     @GuardedBy("this")
73     private final Set<PeerRegistrySessionListener> sessionListeners = new HashSet<>();
74
75     public static BGPPeerRegistry instance() {
76         return new StrictBGPPeerRegistry();
77     }
78
79     @Override
80     public synchronized void addPeer(final IpAddress oldIp, final BGPSessionListener peer,
81             final BGPSessionPreferences preferences) {
82         IpAddress fullIp = getFullIp(oldIp);
83         Preconditions.checkArgument(!this.peers.containsKey(fullIp),
84                 "Peer for %s already present", fullIp);
85         this.peers.put(fullIp, requireNonNull(peer));
86         requireNonNull(preferences.getMyAs());
87         requireNonNull(preferences.getParams());
88         requireNonNull(preferences.getBgpId());
89         this.peerPreferences.put(fullIp, preferences);
90         for (final PeerRegistryListener peerRegistryListener : this.listeners) {
91             peerRegistryListener.onPeerAdded(fullIp, preferences);
92         }
93     }
94
95     private static IpAddress getFullIp(final IpAddress ip) {
96         final Ipv6Address addr = ip.getIpv6Address();
97         return addr == null ? ip : new IpAddress(Ipv6Util.getFullForm(addr));
98     }
99
100     @Override
101     public synchronized void removePeer(final IpAddress oldIp) {
102         IpAddress fullIp = getFullIp(oldIp);
103         this.peers.remove(fullIp);
104         for (final PeerRegistryListener peerRegistryListener : this.listeners) {
105             peerRegistryListener.onPeerRemoved(fullIp);
106         }
107     }
108
109     @Override
110     public synchronized void removePeerSession(final IpAddress oldIp) {
111         IpAddress fullIp = getFullIp(oldIp);
112         this.sessionIds.remove(fullIp);
113         for (final PeerRegistrySessionListener peerRegistrySessionListener : this.sessionListeners) {
114             peerRegistrySessionListener.onSessionRemoved(fullIp);
115         }
116     }
117
118     @Override
119     public boolean isPeerConfigured(final IpAddress oldIp) {
120         IpAddress fullIp = getFullIp(oldIp);
121         return this.peers.containsKey(fullIp);
122     }
123
124     private void checkPeerConfigured(final IpAddress ip) {
125         Preconditions.checkState(isPeerConfigured(ip),
126                 "BGP peer with ip: %s not configured, configured peers are: %s",
127                 ip, this.peers.keySet());
128     }
129
130     @Override
131     public synchronized BGPSessionListener getPeer(final IpAddress ip, final Ipv4Address sourceId,
132         final Ipv4Address remoteId, final Open openObj) throws BGPDocumentedException {
133         requireNonNull(ip);
134         requireNonNull(sourceId);
135         requireNonNull(remoteId);
136         final AsNumber remoteAsNumber = AsNumberUtil.advertizedAsNumber(openObj);
137         requireNonNull(remoteAsNumber);
138
139         final BGPSessionPreferences prefs = getPeerPreferences(ip);
140
141         checkPeerConfigured(ip);
142
143         final BGPSessionId currentConnection = new BGPSessionId(sourceId, remoteId, remoteAsNumber);
144         final BGPSessionListener p = this.peers.get(ip);
145
146         final BGPSessionId previousConnection = this.sessionIds.get(ip);
147
148         if (previousConnection != null) {
149
150             LOG.warn("Duplicate BGP session established with {}", ip);
151
152             // Session reestablished with different ids
153             if (!previousConnection.equals(currentConnection)) {
154                 LOG.warn("BGP session with {} {} has to be dropped. Same session already present {}", ip,
155                         currentConnection, previousConnection);
156                 throw new BGPDocumentedException(
157                         String.format("BGP session with %s %s has to be dropped. Same session already present %s",
158                                 ip, currentConnection, previousConnection),
159                         BGPError.CEASE);
160
161                 // Session reestablished with lower source bgp id, dropping current
162             } else if (previousConnection.isHigherDirection(currentConnection)
163                     || previousConnection.hasHigherAsNumber(currentConnection)) {
164                 LOG.warn("BGP session with {} {} has to be dropped. Opposite session already present",
165                         ip, currentConnection);
166                 throw new BGPDocumentedException(
167                         String.format("BGP session with %s initiated %s has to be dropped. "
168                                 + "Opposite session already present", ip, currentConnection), BGPError.CEASE);
169
170                 // Session reestablished with higher source bgp id, dropping previous
171             } else if (currentConnection.isHigherDirection(previousConnection)
172                     || currentConnection.hasHigherAsNumber(previousConnection)) {
173                 LOG.warn("BGP session with {} {} released. Replaced by opposite session", ip, previousConnection);
174                 this.peers.get(ip).releaseConnection();
175                 return this.peers.get(ip);
176                 // Session reestablished with same source bgp id, dropping current as duplicate
177             } else {
178                 LOG.warn("BGP session with {} initiated from {} to {} has to be dropped. Same session already present",
179                         ip, sourceId, remoteId);
180                 throw new BGPDocumentedException(
181                         String.format("BGP session with %s initiated %s has to be dropped. "
182                                         + "Same session already present", ip, currentConnection), BGPError.CEASE);
183             }
184         }
185         validateAs(remoteAsNumber, openObj, prefs);
186
187         // Map session id to peer IP address
188         this.sessionIds.put(ip, currentConnection);
189         for (final PeerRegistrySessionListener peerRegistrySessionListener : this.sessionListeners) {
190             peerRegistrySessionListener.onSessionCreated(ip);
191         }
192         return p;
193     }
194
195     private static void validateAs(final AsNumber remoteAs, final Open openObj, final BGPSessionPreferences localPref)
196             throws BGPDocumentedException {
197         if (!remoteAs.equals(localPref.getExpectedRemoteAs())) {
198             LOG.warn("Unexpected remote AS number. Expecting {}, got {}", localPref.getExpectedRemoteAs(), remoteAs);
199             throw new BGPDocumentedException("Peer AS number mismatch", BGPError.BAD_PEER_AS);
200         }
201
202         // https://tools.ietf.org/html/rfc6286#section-2.2
203         if (openObj.getBgpIdentifier() != null
204                 && openObj.getBgpIdentifier().getValue().equals(localPref.getBgpId().getValue())) {
205             LOG.warn("Remote and local BGP Identifiers are the same: {}", openObj.getBgpIdentifier());
206             throw new BGPDocumentedException("Remote and local BGP Identifiers are the same.", BGPError.BAD_BGP_ID);
207         }
208         final List<BgpParameters> prefs = openObj.getBgpParameters();
209         if (prefs != null) {
210             final As4BytesCapability localCap = getAs4BytesCapability(localPref.getParams());
211             if (localCap != null && getAs4BytesCapability(prefs) == null) {
212                 throw new BGPDocumentedException("The peer must advertise AS4Bytes capability.",
213                         BGPError.UNSUPPORTED_CAPABILITY, serializeAs4BytesCapability(localCap));
214             }
215             if (!prefs.containsAll(localPref.getParams())) {
216                 LOG.info("BGP Open message session parameters differ, session still accepted.");
217             }
218         } else {
219             throw new BGPDocumentedException("Open message unacceptable. Check the configuration of BGP speaker.",
220                     BGPError.UNSPECIFIC_OPEN_ERROR);
221         }
222     }
223
224     private static @Nullable As4BytesCapability getAs4BytesCapability(final List<BgpParameters> prefs) {
225         for (final BgpParameters param : prefs) {
226             for (final OptionalCapabilities capa : param.getOptionalCapabilities()) {
227                 final CParameters cParam = capa.getCParameters();
228                 final As4BytesCapability asCapa = cParam.getAs4BytesCapability();
229                 if (asCapa != null) {
230                     return asCapa;
231                 }
232             }
233         }
234         return null;
235     }
236
237     private static byte[] serializeAs4BytesCapability(final As4BytesCapability as4Capability) {
238         final ByteBuf buffer = Unpooled.buffer(1 /*CODE*/ + 1 /*LENGTH*/
239                 + Integer.SIZE / Byte.SIZE /*4 byte value*/);
240         final As4CapabilityHandler serializer = new As4CapabilityHandler();
241         serializer.serializeCapability(new CParametersBuilder().setAs4BytesCapability(as4Capability).build(), buffer);
242         return buffer.array();
243     }
244
245     @Override
246     public BGPSessionPreferences getPeerPreferences(final IpAddress ip) {
247         requireNonNull(ip);
248         checkPeerConfigured(ip);
249         return this.peerPreferences.get(ip);
250     }
251
252     /**
253      * Creates IpAddress from SocketAddress. Only InetSocketAddress
254      * is accepted with inner address: Inet4Address and Inet6Address.
255      *
256      * @param socketAddress socket address to transform
257      * @return IpAddress equivalent to given socket address
258      * @throws IllegalArgumentException if submitted socket address is not InetSocketAddress[ipv4 | ipv6]
259      */
260     public static IpAddress getIpAddress(final SocketAddress socketAddress) {
261         requireNonNull(socketAddress);
262         Preconditions.checkArgument(socketAddress instanceof InetSocketAddress,
263                 "Expecting InetSocketAddress but was %s", socketAddress.getClass());
264         final InetAddress inetAddress = ((InetSocketAddress) socketAddress).getAddress();
265
266         Preconditions.checkArgument(inetAddress instanceof Inet4Address
267                 || inetAddress instanceof Inet6Address, "Expecting %s or %s but was %s",
268                 Inet4Address.class, Inet6Address.class, inetAddress.getClass());
269         return IetfInetUtil.INSTANCE.ipAddressFor(inetAddress);
270     }
271
272     @Override
273     public synchronized void close() {
274         this.peers.clear();
275         this.sessionIds.clear();
276     }
277
278     @Override
279     public String toString() {
280         return MoreObjects.toStringHelper(this)
281             .add("peers", this.peers.keySet())
282             .toString();
283     }
284
285     /**
286      * Session identifier that contains (source Bgp Id) -> (destination Bgp Id) AsNumber is the remoteAs coming from
287      * remote Open message.
288      */
289     private static final class BGPSessionId {
290
291         private final Ipv4Address from;
292         private final Ipv4Address to;
293         private final AsNumber asNumber;
294
295         BGPSessionId(final Ipv4Address from, final Ipv4Address to, final AsNumber asNumber) {
296             this.from = requireNonNull(from);
297             this.to = requireNonNull(to);
298             this.asNumber = requireNonNull(asNumber);
299         }
300
301         /**
302          * Equals does not take direction of connection into account id1 -> id2 and id2 -> id1 are equal.
303          */
304         @Override
305         public boolean equals(final Object obj) {
306             if (this == obj) {
307                 return true;
308             }
309             if (obj == null || getClass() != obj.getClass()) {
310                 return false;
311             }
312
313             final BGPSessionId bGPSessionId = (BGPSessionId) obj;
314
315             if (!this.from.equals(bGPSessionId.from) && !this.from.equals(bGPSessionId.to)) {
316                 return false;
317             }
318             if (!this.to.equals(bGPSessionId.to) && !this.to.equals(bGPSessionId.from)) {
319                 return false;
320             }
321
322             return true;
323         }
324
325         @Override
326         public int hashCode() {
327             final int prime = 31;
328             int result = this.from.hashCode() + this.to.hashCode();
329             result = prime * result;
330             return result;
331         }
332
333         /**
334          * Check if this connection is equal to other and if it contains higher source bgp id.
335          */
336         boolean isHigherDirection(final BGPSessionId other) {
337             return toLong(this.from) > toLong(other.from);
338         }
339
340         boolean hasHigherAsNumber(final BGPSessionId other) {
341             return this.asNumber.getValue() > other.asNumber.getValue();
342         }
343
344         private static long toLong(final Ipv4Address from) {
345             final int i = InetAddresses.coerceToInteger(InetAddresses.forString(from.getValue()));
346             return UnsignedInts.toLong(i);
347         }
348
349         @Override
350         public String toString() {
351             return MoreObjects.toStringHelper(this)
352                 .add("from", this.from)
353                 .add("to", this.to)
354                 .toString();
355         }
356     }
357
358     @Override
359     public synchronized Registration registerPeerRegisterListener(final PeerRegistryListener listener) {
360         this.listeners.add(listener);
361         for (final Entry<IpAddress, BGPSessionPreferences> entry : this.peerPreferences.entrySet()) {
362             listener.onPeerAdded(entry.getKey(), entry.getValue());
363         }
364         return new AbstractRegistration() {
365             @Override
366             protected void removeRegistration() {
367                 synchronized (StrictBGPPeerRegistry.this) {
368                     StrictBGPPeerRegistry.this.listeners.remove(listener);
369                 }
370             }
371         };
372     }
373
374     @Override
375     public synchronized Registration registerPeerSessionListener(final PeerRegistrySessionListener listener) {
376         this.sessionListeners.add(listener);
377         for (final IpAddress ipAddress : this.sessionIds.keySet()) {
378             listener.onSessionCreated(ipAddress);
379         }
380         return new AbstractRegistration() {
381             @Override
382             protected void removeRegistration() {
383                 synchronized (StrictBGPPeerRegistry.this) {
384                     StrictBGPPeerRegistry.this.sessionListeners.remove(listener);
385                 }
386             }
387         };
388     }
389
390     @Override
391     public void updatePeerPreferences(final IpAddress address, final BGPSessionPreferences preferences) {
392         if (this.peerPreferences.containsKey(address)) {
393             this.peerPreferences.put(address, preferences);
394         }
395     }
396 }