c0a8fe80dbe556c114866916b30427bcc6d56ea2
[bgpcep.git] / bgp / rib-impl / src / main / java / org / opendaylight / protocol / bgp / rib / impl / BGPSessionImpl.java
1 /*
2  * Copyright (c) 2013 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 com.google.common.annotations.VisibleForTesting;
11 import com.google.common.base.Objects;
12 import com.google.common.base.Objects.ToStringHelper;
13 import com.google.common.base.Preconditions;
14 import com.google.common.collect.Sets;
15
16 import io.netty.channel.Channel;
17 import io.netty.util.Timeout;
18 import io.netty.util.Timer;
19 import io.netty.util.TimerTask;
20
21 import java.io.IOException;
22 import java.util.Date;
23 import java.util.Set;
24 import java.util.concurrent.TimeUnit;
25
26 import javax.annotation.concurrent.GuardedBy;
27
28 import org.opendaylight.protocol.bgp.parser.AsNumberUtil;
29 import org.opendaylight.protocol.bgp.parser.BGPError;
30 import org.opendaylight.protocol.bgp.parser.BGPSession;
31 import org.opendaylight.protocol.bgp.parser.BGPSessionListener;
32 import org.opendaylight.protocol.bgp.parser.BGPTerminationReason;
33 import org.opendaylight.protocol.bgp.parser.BgpTableTypeImpl;
34 import org.opendaylight.protocol.framework.AbstractProtocolSession;
35 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.AsNumber;
36 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv4Address;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.Keepalive;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.KeepaliveBuilder;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.Notify;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.NotifyBuilder;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.Open;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.Update;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.open.BgpParameters;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.open.bgp.parameters.CParameters;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev130919.BgpTableType;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev130919.open.bgp.parameters.c.parameters.MultiprotocolCase;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.TablesKey;
48 import org.opendaylight.yangtools.yang.binding.Notification;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 @VisibleForTesting
53 public class BGPSessionImpl extends AbstractProtocolSession<Notification> implements BGPSession {
54
55     private static final Logger LOG = LoggerFactory.getLogger(BGPSessionImpl.class);
56
57     private static final Notification KEEP_ALIVE = new KeepaliveBuilder().build();
58
59     /**
60      * Internal session state.
61      */
62     public enum State {
63         /**
64          * The session object is created by the negotiator in OpenConfirm state. While in this state, the session object
65          * is half-alive, e.g. the timers are running, but the session is not completely up, e.g. it has not been
66          * announced to the listener. If the session is torn down in this state, we do not inform the listener.
67          */
68         OpenConfirm,
69         /**
70          * The session has been completely established.
71          */
72         Up,
73         /**
74          * The session has been closed. It will not be resurrected.
75          */
76         Idle,
77     }
78
79     /**
80      * System.nanoTime value about when was sent the last message Protected to be updated also in tests.
81      */
82     @VisibleForTesting
83     protected long lastMessageSentAt;
84
85     /**
86      * System.nanoTime value about when was received the last message
87      */
88     private long lastMessageReceivedAt;
89
90     private final BGPSessionListener listener;
91
92     /**
93      * Timer object grouping FSM Timers
94      */
95     private final Timer stateTimer;
96
97     private final BGPSynchronization sync;
98
99     private int kaCounter = 0;
100
101     private final Channel channel;
102
103     @GuardedBy("this")
104     private State state = State.OpenConfirm;
105
106     private final Set<BgpTableType> tableTypes;
107     private final int holdTimerValue;
108     private final int keepAlive;
109     private final AsNumber asNumber;
110     private final Ipv4Address bgpId;
111
112     public BGPSessionImpl(final Timer timer, final BGPSessionListener listener, final Channel channel, final Open remoteOpen,
113                           final int localHoldTimer) {
114         this.listener = Preconditions.checkNotNull(listener);
115         this.stateTimer = Preconditions.checkNotNull(timer);
116         this.channel = Preconditions.checkNotNull(channel);
117         this.holdTimerValue = (remoteOpen.getHoldTimer() < localHoldTimer) ? remoteOpen.getHoldTimer() : localHoldTimer;
118         LOG.info("BGP HoldTimer new value: {}", this.holdTimerValue);
119         this.keepAlive = this.holdTimerValue / 3;
120         this.asNumber = AsNumberUtil.advertizedAsNumber(remoteOpen);
121
122         final Set<TablesKey> tts = Sets.newHashSet();
123         final Set<BgpTableType> tats = Sets.newHashSet();
124         if (remoteOpen.getBgpParameters() != null) {
125             for (final BgpParameters param : remoteOpen.getBgpParameters()) {
126                 final CParameters cp = param.getCParameters();
127                 if (cp instanceof MultiprotocolCase) {
128                     final TablesKey tt = new TablesKey(((MultiprotocolCase) cp).getMultiprotocolCapability().getAfi(), ((MultiprotocolCase) cp).getMultiprotocolCapability().getSafi());
129                     LOG.trace("Added table type to sync {}", tt);
130                     tts.add(tt);
131                     tats.add(new BgpTableTypeImpl(tt.getAfi(), tt.getSafi()));
132                 }
133             }
134         }
135
136         this.sync = new BGPSynchronization(this, this.listener, tts);
137         this.tableTypes = tats;
138
139         if (this.holdTimerValue != 0) {
140             this.stateTimer.newTimeout(new TimerTask() {
141
142                 @Override
143                 public void run(final Timeout timeout) {
144                     handleHoldTimer();
145                 }
146             }, this.holdTimerValue, TimeUnit.SECONDS);
147
148             this.stateTimer.newTimeout(new TimerTask() {
149                 @Override
150                 public void run(final Timeout timeout) {
151                     handleKeepaliveTimer();
152                 }
153             }, this.keepAlive, TimeUnit.SECONDS);
154         }
155         this.bgpId = remoteOpen.getBgpIdentifier();
156     }
157
158     @Override
159     public synchronized void close() {
160         LOG.info("Closing session: {}", this);
161         if (this.state != State.Idle) {
162             this.sendMessage(new NotifyBuilder().setErrorCode(BGPError.CEASE.getCode()).build());
163             this.channel.close();
164             this.state = State.Idle;
165         }
166     }
167
168     /**
169      * Handles incoming message based on their type.
170      *
171      * @param msg incoming message
172      */
173     @Override
174     public void handleMessage(final Notification msg) {
175         // Update last reception time
176         this.lastMessageReceivedAt = System.nanoTime();
177
178         if (msg instanceof Open) {
179             // Open messages should not be present here
180             this.terminate(BGPError.FSM_ERROR);
181         } else if (msg instanceof Notify) {
182             // Notifications are handled internally
183             LOG.info("Session closed because Notification message received: {} / {}", ((Notify) msg).getErrorCode(),
184                     ((Notify) msg).getErrorSubcode());
185             this.closeWithoutMessage();
186             this.listener.onSessionTerminated(this, new BGPTerminationReason(BGPError.forValue(((Notify) msg).getErrorCode(),
187                     ((Notify) msg).getErrorSubcode())));
188         } else if (msg instanceof Keepalive) {
189             // Keepalives are handled internally
190             LOG.trace("Received KeepAlive messsage.");
191             this.kaCounter++;
192             if (this.kaCounter >= 2) {
193                 this.sync.kaReceived();
194             }
195         } else {
196             // All others are passed up
197             this.listener.onMessage(this, msg);
198             this.sync.updReceived((Update) msg);
199         }
200     }
201
202     @Override
203     public synchronized void endOfInput() {
204         if (this.state == State.Up) {
205             this.listener.onSessionDown(this, new IOException("End of input detected. Close the session."));
206         }
207     }
208
209     void sendMessage(final Notification msg) {
210         try {
211             this.channel.writeAndFlush(msg);
212             this.lastMessageSentAt = System.nanoTime();
213             LOG.debug("Sent message: {}", msg);
214         } catch (final Exception e) {
215             LOG.warn("Message {} was not sent.", msg, e);
216         }
217     }
218
219     private synchronized void closeWithoutMessage() {
220         LOG.debug("Closing session: {}", this);
221         this.channel.close();
222         this.state = State.Idle;
223     }
224
225     /**
226      * Closes PCEP session from the parent with given reason. A message needs to be sent, but parent doesn't have to be
227      * modified, because he initiated the closing. (To prevent concurrent modification exception).
228      *
229      * @param closeObject
230      */
231     private void terminate(final BGPError error) {
232         this.sendMessage(new NotifyBuilder().setErrorCode(error.getCode()).setErrorSubcode(error.getSubcode()).build());
233         this.closeWithoutMessage();
234
235         this.listener.onSessionTerminated(this, new BGPTerminationReason(error));
236     }
237
238     /**
239      * If HoldTimer expires, the session ends. If a message (whichever) was received during this period, the HoldTimer
240      * will be rescheduled by HOLD_TIMER_VALUE + the time that has passed from the start of the HoldTimer to the time at
241      * which the message was received. If the session was closed by the time this method starts to execute (the session
242      * state will become IDLE), then rescheduling won't occur.
243      */
244     private synchronized void handleHoldTimer() {
245         if (this.state == State.Idle) {
246             return;
247         }
248
249         final long ct = System.nanoTime();
250         final long nextHold = this.lastMessageReceivedAt + TimeUnit.SECONDS.toNanos(this.holdTimerValue);
251
252         if (ct >= nextHold) {
253             LOG.debug("HoldTimer expired. {}", new Date());
254             this.terminate(BGPError.HOLD_TIMER_EXPIRED);
255         } else {
256             this.stateTimer.newTimeout(new TimerTask() {
257                 @Override
258                 public void run(final Timeout timeout) {
259                     handleHoldTimer();
260                 }
261             }, nextHold - ct, TimeUnit.NANOSECONDS);
262         }
263     }
264
265     /**
266      * If KeepAlive Timer expires, sends KeepAlive message. If a message (whichever) was send during this period, the
267      * KeepAlive Timer will be rescheduled by KEEP_ALIVE_TIMER_VALUE + the time that has passed from the start of the
268      * KeepAlive timer to the time at which the message was sent. If the session was closed by the time this method
269      * starts to execute (the session state will become IDLE), that rescheduling won't occur.
270      */
271     private synchronized void handleKeepaliveTimer() {
272         if (this.state == State.Idle) {
273             return;
274         }
275
276         final long ct = System.nanoTime();
277         long nextKeepalive = this.lastMessageSentAt + TimeUnit.SECONDS.toNanos(this.keepAlive);
278
279         if (ct >= nextKeepalive) {
280             this.sendMessage(KEEP_ALIVE);
281             nextKeepalive = this.lastMessageSentAt + TimeUnit.SECONDS.toNanos(this.keepAlive);
282         }
283         this.stateTimer.newTimeout(new TimerTask() {
284             @Override
285             public void run(final Timeout timeout) {
286                 handleKeepaliveTimer();
287             }
288         }, nextKeepalive - ct, TimeUnit.NANOSECONDS);
289     }
290
291     @Override
292     public final String toString() {
293         return addToStringAttributes(Objects.toStringHelper(this)).toString();
294     }
295
296     protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
297         toStringHelper.add("channel", this.channel);
298         toStringHelper.add("state", this.state);
299         return toStringHelper;
300     }
301
302     @Override
303     public Set<BgpTableType> getAdvertisedTableTypes() {
304         return this.tableTypes;
305     }
306
307     @Override
308     protected synchronized void sessionUp() {
309         this.state = State.Up;
310         this.listener.onSessionUp(this);
311     }
312
313     public synchronized State getState() {
314         return this.state;
315     }
316
317     @Override
318     public final Ipv4Address getBgpId() {
319         return this.bgpId;
320     }
321
322     @Override
323     public final AsNumber getAsNumber() {
324         return this.asNumber;
325     }
326 }