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