a2bf6a5d5b224c8b0b3096a222535cb5896584b7
[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 io.netty.channel.ChannelHandlerContext;
11
12 import java.io.IOException;
13 import java.util.Date;
14 import java.util.Set;
15 import java.util.Timer;
16 import java.util.TimerTask;
17
18 import org.opendaylight.protocol.bgp.concepts.BGPTableType;
19 import org.opendaylight.protocol.bgp.parser.BGPDocumentedException;
20 import org.opendaylight.protocol.bgp.parser.BGPError;
21 import org.opendaylight.protocol.bgp.parser.BGPMessage;
22 import org.opendaylight.protocol.bgp.parser.BGPParameter;
23 import org.opendaylight.protocol.bgp.parser.BGPSession;
24 import org.opendaylight.protocol.bgp.parser.BGPSessionListener;
25 import org.opendaylight.protocol.bgp.parser.message.BGPKeepAliveMessage;
26 import org.opendaylight.protocol.bgp.parser.message.BGPNotificationMessage;
27 import org.opendaylight.protocol.bgp.parser.message.BGPOpenMessage;
28 import org.opendaylight.protocol.bgp.parser.parameter.MultiprotocolCapability;
29 import org.opendaylight.protocol.bgp.rib.impl.spi.BGPConnection;
30 import org.opendaylight.protocol.bgp.rib.impl.spi.BGPSessionPreferences;
31 import org.opendaylight.protocol.bgp.rib.impl.spi.BGPSessionProposalChecker;
32 import org.opendaylight.protocol.framework.DeserializerException;
33 import org.opendaylight.protocol.framework.DocumentedException;
34 import org.opendaylight.protocol.framework.ProtocolMessage;
35 import org.opendaylight.protocol.framework.ProtocolMessageFactory;
36 import org.opendaylight.protocol.framework.ProtocolSession;
37 import org.opendaylight.protocol.framework.ProtocolSessionOutboundHandler;
38 import org.opendaylight.protocol.framework.SessionParent;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 import com.google.common.collect.Sets;
43
44 class BGPSessionImpl implements BGPSession, ProtocolSession {
45
46         private static final Logger logger = LoggerFactory.getLogger(BGPSessionImpl.class);
47
48         /**
49          * KeepAlive Timer is to be scheduled periodically, each time it starts, it sends KeepAlive Message.
50          */
51         private class KeepAliveTimer extends TimerTask {
52                 private final BGPSessionImpl parent;
53
54                 public KeepAliveTimer(final BGPSessionImpl parent) {
55                         this.parent = parent;
56                 }
57
58                 @Override
59                 public void run() {
60                         this.parent.handleKeepaliveTimer();
61                 }
62         }
63
64         /**
65          * HoldTimer is to be scheduled periodically, when it expires, it closes BGP session.
66          */
67         private class HoldTimer extends TimerTask {
68                 private final BGPSessionImpl parent;
69
70                 public HoldTimer(final BGPSessionImpl parent) {
71                         this.parent = parent;
72                 }
73
74                 @Override
75                 public void run() {
76                         this.parent.handleHoldTimer();
77                 }
78         }
79
80         private static final int DEFAULT_HOLD_TIMER_VALUE = 15;
81
82         public static int HOLD_TIMER_VALUE = DEFAULT_HOLD_TIMER_VALUE; // 240
83
84         public int KEEP_ALIVE_TIMER_VALUE;
85
86         /**
87          * Possible states for Finite State Machine
88          */
89         private enum State {
90                 IDLE, OPEN_SENT, OPEN_CONFIRM, ESTABLISHED
91         }
92
93         /**
94          * Actual state of the FSM.
95          */
96         private State state;
97
98         /**
99          * System.nanoTime value about when was sent the last message Protected to be updated also in tests.
100          */
101         protected long lastMessageSentAt;
102
103         /**
104          * System.nanoTime value about when was received the last message
105          */
106         private long lastMessageReceivedAt;
107
108         private final int sessionId;
109
110         private final BGPSessionListener listener;
111
112         /**
113          * Open message with session characteristics that were accepted by another BGP (sent from this session).
114          */
115         private BGPSessionPreferences localOpen = null;
116
117         /**
118          * Open Object with session characteristics for this session (sent from another BGP speaker).
119          */
120         private BGPSessionPreferences remoteOpen = null;
121
122         /**
123          * Timer object grouping FSM Timers
124          */
125         private final Timer stateTimer;
126
127         private final SessionParent parent;
128
129         private final ProtocolMessageFactory parser;
130
131         private final BGPSessionProposalChecker checker;
132
133         private final BGPSynchronization sync;
134
135         private final ProtocolSessionOutboundHandler handler;
136
137         private int kaCounter = 0;
138
139         private final ChannelHandlerContext ctx;
140
141         BGPSessionImpl(final SessionParent parent, final Timer timer, final BGPConnection connection, final int sessionId,
142                         final ProtocolMessageFactory parser, final ChannelHandlerContext ctx) {
143                 this.state = State.IDLE;
144                 this.listener = connection.getListener();
145                 this.sessionId = sessionId;
146                 this.localOpen = connection.getProposal();
147                 this.stateTimer = timer;
148                 this.parent = parent;
149                 this.parser = parser;
150                 this.ctx = ctx;
151                 this.checker = connection.getProposalChecker();
152                 this.sync = new BGPSynchronization(this.listener);
153                 this.handler = new ProtocolSessionOutboundHandler();
154         }
155
156         @Override
157         public void close() {
158                 logger.debug("Closing session: " + this);
159                 if (this.state == State.ESTABLISHED) {
160                         this.sendMessage(new BGPNotificationMessage(BGPError.CEASE));
161                 }
162                 this.changeState(State.IDLE);
163                 this.parent.onSessionClosed(this);
164         }
165
166         @Override
167         public void startSession() {
168                 logger.debug("Session started.");
169                 this.sendMessage(new BGPOpenMessage(this.localOpen.getMyAs(), (short) this.localOpen.getHoldTime(), this.localOpen.getBgpId(), this.localOpen.getParams()));
170                 this.stateTimer.schedule(new HoldTimer(this), DEFAULT_HOLD_TIMER_VALUE * 1000);
171                 this.changeState(State.OPEN_SENT);
172         }
173
174         /**
175          * Handles incoming message based on their type.
176          * 
177          * @param msg incoming message
178          */
179         @Override
180         public void handleMessage(final ProtocolMessage msg) {
181                 final BGPMessage bgpMsg = (BGPMessage) msg;
182                 // Update last reception time
183                 this.lastMessageReceivedAt = System.nanoTime();
184
185                 // Open messages are handled internally, but are parsed also in bgp-parser, so notify bgp listener
186                 if (bgpMsg instanceof BGPOpenMessage) {
187                         this.handleOpenMessage((BGPOpenMessage) bgpMsg);
188                 }
189                 // Keepalives are handled internally
190                 else if (bgpMsg instanceof BGPKeepAliveMessage) {
191                         this.handleKeepAliveMessage();
192                 }
193                 // Notifications are handled internally
194                 else if (bgpMsg instanceof BGPNotificationMessage) {
195                         logger.info("Session closed because Notification message received: {}" + ((BGPNotificationMessage) bgpMsg).getError());
196                         this.closeWithoutMessage();
197                         this.listener.onSessionTerminated(((BGPNotificationMessage) bgpMsg).getError());
198                 } else {
199                         this.listener.onMessage(bgpMsg);
200                 }
201         }
202
203         @Override
204         public void handleMalformedMessage(final DeserializerException e) {
205                 logger.warn("Received malformed message: {}", e.getMessage(), e);
206                 this.terminate(BGPError.FSM_ERROR);
207         }
208
209         @Override
210         public void handleMalformedMessage(final DocumentedException e) {
211                 logger.warn("Received malformed message: {}", e.getMessage(), e);
212                 this.terminate(((BGPDocumentedException) e).getError());
213         }
214
215         @Override
216         public void endOfInput() {
217                 if (this.state != State.IDLE) {
218                         this.listener.onSessionDown(this, new IOException("End of input detected. Close the session."));
219                 }
220         }
221
222         @Override
223         public ProtocolMessageFactory getMessageFactory() {
224                 return this.parser;
225         }
226
227         @Override
228         public void onConnectionFailed(final IOException e) {
229                 logger.info("Connection failed before finishing: {}", e.getMessage(), e);
230                 this.listener.onSessionDown(this, e);
231         }
232
233         @Override
234         public int maximumMessageSize() {
235                 return 4096;
236         }
237
238         void sendMessage(final BGPMessage msg) {
239                 try {
240                         this.handler.writeDown(this.ctx, msg);
241                         this.lastMessageSentAt = System.nanoTime();
242                         logger.debug("Sent message: " + msg);
243                 } catch (final Exception e) {
244                         logger.warn("Message {} was not sent.", msg, e);
245                 }
246         }
247
248         private void closeWithoutMessage() {
249                 logger.debug("Closing session: " + this);
250                 HOLD_TIMER_VALUE = DEFAULT_HOLD_TIMER_VALUE;
251                 this.changeState(State.IDLE);
252                 this.parent.onSessionClosed(this);
253         }
254
255         /**
256          * Closes PCEP session from the parent with given reason. A message needs to be sent, but parent doesn't have to be
257          * modified, because he initiated the closing. (To prevent concurrent modification exception).
258          * 
259          * @param closeObject
260          */
261         private void terminate(final BGPError error) {
262                 this.sendMessage(new BGPNotificationMessage(error));
263                 this.closeWithoutMessage();
264                 this.listener.onSessionTerminated(error);
265         }
266
267         /**
268          * If HoldTimer expires, the session ends. If a message (whichever) was received during this period, the HoldTimer
269          * will be rescheduled by HOLD_TIMER_VALUE + the time that has passed from the start of the HoldTimer to the time at
270          * which the message was received. If the session was closed by the time this method starts to execute (the session
271          * state will become IDLE), then rescheduling won't occur.
272          */
273         private synchronized void handleHoldTimer() {
274                 final long ct = System.nanoTime();
275
276                 final long nextHold = (long) (this.lastMessageReceivedAt + (HOLD_TIMER_VALUE * 1E9));
277
278                 if (this.state != State.IDLE) {
279                         if (ct >= nextHold) {
280                                 logger.debug("HoldTimer expired. " + new Date());
281                                 this.terminate(BGPError.HOLD_TIMER_EXPIRED);
282                                 return;
283                         }
284                         this.stateTimer.schedule(new HoldTimer(this), (long) ((nextHold - ct) / 1E6));
285                 }
286         }
287
288         /**
289          * If KeepAlive Timer expires, sends KeepAlive message. If a message (whichever) was send during this period, the
290          * KeepAlive Timer will be rescheduled by KEEP_ALIVE_TIMER_VALUE + the time that has passed from the start of the
291          * KeepAlive timer to the time at which the message was sent. If the session was closed by the time this method
292          * starts to execute (the session state will become IDLE), that rescheduling won't occur.
293          */
294         private synchronized void handleKeepaliveTimer() {
295                 final long ct = System.nanoTime();
296
297                 long nextKeepalive = (long) (this.lastMessageSentAt + (this.KEEP_ALIVE_TIMER_VALUE * 1E9));
298
299                 if (this.state == State.ESTABLISHED) {
300                         if (ct >= nextKeepalive) {
301                                 this.sendMessage(new BGPKeepAliveMessage());
302                                 nextKeepalive = (long) (this.lastMessageSentAt + (this.KEEP_ALIVE_TIMER_VALUE * 1E9));
303                         }
304                         this.stateTimer.schedule(new KeepAliveTimer(this), (long) ((nextKeepalive - ct) / 1E6));
305                 }
306         }
307
308         private void changeState(final State finalState) {
309                 final String desc = "Changed to state: ";
310                 switch (finalState) {
311                 case IDLE:
312                         logger.debug(desc + State.IDLE);
313                         this.state = State.IDLE;
314                         return;
315                 case OPEN_SENT:
316                         logger.debug(desc + State.OPEN_SENT);
317                         if (this.state != State.IDLE) {
318                                 throw new IllegalArgumentException("Cannot change state from " + this.state + " to " + State.OPEN_SENT);
319                         }
320                         this.state = State.OPEN_SENT;
321                         return;
322                 case OPEN_CONFIRM:
323                         logger.debug(desc + State.OPEN_CONFIRM);
324                         if (this.state == State.ESTABLISHED) {
325                                 throw new IllegalArgumentException("Cannot change state from " + this.state + " to " + State.OPEN_CONFIRM);
326                         }
327                         this.state = State.OPEN_CONFIRM;
328                         return;
329                 case ESTABLISHED:
330                         logger.debug(desc + State.ESTABLISHED);
331                         if (this.state != State.OPEN_CONFIRM) {
332                                 throw new IllegalArgumentException("Cannot change state from " + this.state + " to " + State.ESTABLISHED);
333                         }
334                         this.state = State.ESTABLISHED;
335                         return;
336                 }
337         }
338
339         /**
340          * Open message should be handled only if the FSM is in OPEN_SENT or IDLE state. When in IDLE state, the Open
341          * message was received _before_ local Open message was sent. When in OPEN_SENT state, the message was received
342          * _after_ local Open message was sent.
343          * 
344          * @param msg received Open Message.
345          */
346         private void handleOpenMessage(final BGPOpenMessage msg) {
347                 this.remoteOpen = new BGPSessionPreferences(msg.getMyAS(), msg.getHoldTime(), msg.getBgpId(), msg.getOptParams());
348                 logger.debug("Received message: {}", msg.toString());
349                 if (this.state != State.IDLE && this.state != State.OPEN_SENT) {
350                         this.terminate(BGPError.FSM_ERROR);
351                         return;
352                 }
353                 // if the session characteristics were unacceptable, the session is terminated
354                 // with given BGP error
355                 try {
356                         this.checker.checkSessionCharacteristics(this.remoteOpen);
357                 } catch (final BGPDocumentedException e) {
358                         this.terminate(e.getError());
359                 }
360                 // the session characteristics were acceptable
361                 HOLD_TIMER_VALUE = this.remoteOpen.getHoldTime();
362                 logger.debug("Session chars are acceptable. Overwriting: holdtimer: {}", HOLD_TIMER_VALUE);
363                 // when in IDLE state, we haven't send Open Message yet, do it now
364                 if (this.state == State.IDLE) {
365                         this.sendMessage(new BGPOpenMessage(this.localOpen.getMyAs(), (short) this.localOpen.getHoldTime(), this.localOpen.getBgpId(), this.localOpen.getParams()));
366                 }
367                 this.sendMessage(new BGPKeepAliveMessage());
368                 // if the timer is not disabled
369                 if (HOLD_TIMER_VALUE != 0) {
370                         this.KEEP_ALIVE_TIMER_VALUE = HOLD_TIMER_VALUE / 3;
371                         this.stateTimer.schedule(new KeepAliveTimer(this), this.KEEP_ALIVE_TIMER_VALUE * 1000);
372                         this.stateTimer.schedule(new HoldTimer(this), HOLD_TIMER_VALUE * 1000);
373                 }
374                 this.changeState(State.OPEN_CONFIRM);
375         }
376
377         /**
378          * KeepAlive message should be explicitly parsed in FSM when its state is OPEN_CONFIRM. Otherwise is handled by the
379          * KeepAliveTimer or it's invalid.
380          */
381         private void handleKeepAliveMessage() {
382                 logger.debug("Received KeepAlive messsage.");
383                 if (this.state == State.OPEN_CONFIRM) {
384                         if (HOLD_TIMER_VALUE != 0) {
385                                 this.stateTimer.schedule(new HoldTimer(this), HOLD_TIMER_VALUE * 1000);
386                                 this.stateTimer.schedule(new KeepAliveTimer(this), this.KEEP_ALIVE_TIMER_VALUE * 1000);
387                         }
388                         this.changeState(State.ESTABLISHED);
389                         final Set<BGPTableType> tts = Sets.newHashSet();
390                         if (this.remoteOpen.getParams() != null) {
391                                 for (final BGPParameter param : this.remoteOpen.getParams()) {
392                                         if (param instanceof MultiprotocolCapability) {
393                                                 tts.add(((MultiprotocolCapability) param).getTableType());
394                                         }
395                                 }
396                         }
397                         this.sync.addTableTypes(tts);
398                         this.listener.onSessionUp(tts);
399                         // check if the KA is EOR for some AFI/SAFI
400                 } else if (this.state == State.ESTABLISHED) {
401                         this.kaCounter++;
402                         if (this.kaCounter >= 2) {
403                                 this.sync.kaReceived();
404                         }
405                 }
406         }
407
408         @Override
409         public String toString() {
410                 final StringBuilder builder = new StringBuilder();
411                 builder.append("BGPSessionImpl [state=");
412                 builder.append(this.state);
413                 builder.append(", sessionId=");
414                 builder.append(this.sessionId);
415                 builder.append(", localOpen=");
416                 builder.append(this.localOpen);
417                 builder.append(", remoteOpen=");
418                 builder.append(this.remoteOpen);
419                 builder.append("]");
420                 return builder.toString();
421         }
422 }