Implemented closing operations for PCEP Netty.
[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 int maximumMessageSize() {
229                 return 4096;
230         }
231
232         void sendMessage(final BGPMessage msg) {
233                 try {
234                         this.handler.writeDown(this.ctx, msg);
235                         this.lastMessageSentAt = System.nanoTime();
236                         logger.debug("Sent message: " + msg);
237                 } catch (final Exception e) {
238                         logger.warn("Message {} was not sent.", msg, e);
239                 }
240         }
241
242         private void closeWithoutMessage() {
243                 logger.debug("Closing session: " + this);
244                 HOLD_TIMER_VALUE = DEFAULT_HOLD_TIMER_VALUE;
245                 this.changeState(State.IDLE);
246                 this.parent.onSessionClosed(this);
247         }
248
249         /**
250          * Closes PCEP session from the parent with given reason. A message needs to be sent, but parent doesn't have to be
251          * modified, because he initiated the closing. (To prevent concurrent modification exception).
252          * 
253          * @param closeObject
254          */
255         private void terminate(final BGPError error) {
256                 this.sendMessage(new BGPNotificationMessage(error));
257                 this.closeWithoutMessage();
258                 this.listener.onSessionTerminated(error);
259         }
260
261         /**
262          * If HoldTimer expires, the session ends. If a message (whichever) was received during this period, the HoldTimer
263          * will be rescheduled by HOLD_TIMER_VALUE + the time that has passed from the start of the HoldTimer to the time at
264          * which the message was received. If the session was closed by the time this method starts to execute (the session
265          * state will become IDLE), then rescheduling won't occur.
266          */
267         private synchronized void handleHoldTimer() {
268                 final long ct = System.nanoTime();
269
270                 final long nextHold = (long) (this.lastMessageReceivedAt + (HOLD_TIMER_VALUE * 1E9));
271
272                 if (this.state != State.IDLE) {
273                         if (ct >= nextHold) {
274                                 logger.debug("HoldTimer expired. " + new Date());
275                                 this.terminate(BGPError.HOLD_TIMER_EXPIRED);
276                                 return;
277                         }
278                         this.stateTimer.schedule(new HoldTimer(this), (long) ((nextHold - ct) / 1E6));
279                 }
280         }
281
282         /**
283          * If KeepAlive Timer expires, sends KeepAlive message. If a message (whichever) was send during this period, the
284          * KeepAlive Timer will be rescheduled by KEEP_ALIVE_TIMER_VALUE + the time that has passed from the start of the
285          * KeepAlive timer to the time at which the message was sent. If the session was closed by the time this method
286          * starts to execute (the session state will become IDLE), that rescheduling won't occur.
287          */
288         private synchronized void handleKeepaliveTimer() {
289                 final long ct = System.nanoTime();
290
291                 long nextKeepalive = (long) (this.lastMessageSentAt + (this.KEEP_ALIVE_TIMER_VALUE * 1E9));
292
293                 if (this.state == State.ESTABLISHED) {
294                         if (ct >= nextKeepalive) {
295                                 this.sendMessage(new BGPKeepAliveMessage());
296                                 nextKeepalive = (long) (this.lastMessageSentAt + (this.KEEP_ALIVE_TIMER_VALUE * 1E9));
297                         }
298                         this.stateTimer.schedule(new KeepAliveTimer(this), (long) ((nextKeepalive - ct) / 1E6));
299                 }
300         }
301
302         private void changeState(final State finalState) {
303                 final String desc = "Changed to state: ";
304                 switch (finalState) {
305                 case IDLE:
306                         logger.debug(desc + State.IDLE);
307                         this.state = State.IDLE;
308                         return;
309                 case OPEN_SENT:
310                         logger.debug(desc + State.OPEN_SENT);
311                         if (this.state != State.IDLE) {
312                                 throw new IllegalArgumentException("Cannot change state from " + this.state + " to " + State.OPEN_SENT);
313                         }
314                         this.state = State.OPEN_SENT;
315                         return;
316                 case OPEN_CONFIRM:
317                         logger.debug(desc + State.OPEN_CONFIRM);
318                         if (this.state == State.ESTABLISHED) {
319                                 throw new IllegalArgumentException("Cannot change state from " + this.state + " to " + State.OPEN_CONFIRM);
320                         }
321                         this.state = State.OPEN_CONFIRM;
322                         return;
323                 case ESTABLISHED:
324                         logger.debug(desc + State.ESTABLISHED);
325                         if (this.state != State.OPEN_CONFIRM) {
326                                 throw new IllegalArgumentException("Cannot change state from " + this.state + " to " + State.ESTABLISHED);
327                         }
328                         this.state = State.ESTABLISHED;
329                         return;
330                 }
331         }
332
333         /**
334          * Open message should be handled only if the FSM is in OPEN_SENT or IDLE state. When in IDLE state, the Open
335          * message was received _before_ local Open message was sent. When in OPEN_SENT state, the message was received
336          * _after_ local Open message was sent.
337          * 
338          * @param msg received Open Message.
339          */
340         private void handleOpenMessage(final BGPOpenMessage msg) {
341                 this.remoteOpen = new BGPSessionPreferences(msg.getMyAS(), msg.getHoldTime(), msg.getBgpId(), msg.getOptParams());
342                 logger.debug("Received message: {}", msg.toString());
343                 if (this.state != State.IDLE && this.state != State.OPEN_SENT) {
344                         this.terminate(BGPError.FSM_ERROR);
345                         return;
346                 }
347                 // if the session characteristics were unacceptable, the session is terminated
348                 // with given BGP error
349                 try {
350                         this.checker.checkSessionCharacteristics(this.remoteOpen);
351                 } catch (final BGPDocumentedException e) {
352                         this.terminate(e.getError());
353                 }
354                 // the session characteristics were acceptable
355                 HOLD_TIMER_VALUE = this.remoteOpen.getHoldTime();
356                 logger.debug("Session chars are acceptable. Overwriting: holdtimer: {}", HOLD_TIMER_VALUE);
357                 // when in IDLE state, we haven't send Open Message yet, do it now
358                 if (this.state == State.IDLE) {
359                         this.sendMessage(new BGPOpenMessage(this.localOpen.getMyAs(), (short) this.localOpen.getHoldTime(), this.localOpen.getBgpId(), this.localOpen.getParams()));
360                 }
361                 this.sendMessage(new BGPKeepAliveMessage());
362                 // if the timer is not disabled
363                 if (HOLD_TIMER_VALUE != 0) {
364                         this.KEEP_ALIVE_TIMER_VALUE = HOLD_TIMER_VALUE / 3;
365                         this.stateTimer.schedule(new KeepAliveTimer(this), this.KEEP_ALIVE_TIMER_VALUE * 1000);
366                         this.stateTimer.schedule(new HoldTimer(this), HOLD_TIMER_VALUE * 1000);
367                 }
368                 this.changeState(State.OPEN_CONFIRM);
369         }
370
371         /**
372          * KeepAlive message should be explicitly parsed in FSM when its state is OPEN_CONFIRM. Otherwise is handled by the
373          * KeepAliveTimer or it's invalid.
374          */
375         private void handleKeepAliveMessage() {
376                 logger.debug("Received KeepAlive messsage.");
377                 if (this.state == State.OPEN_CONFIRM) {
378                         if (HOLD_TIMER_VALUE != 0) {
379                                 this.stateTimer.schedule(new HoldTimer(this), HOLD_TIMER_VALUE * 1000);
380                                 this.stateTimer.schedule(new KeepAliveTimer(this), this.KEEP_ALIVE_TIMER_VALUE * 1000);
381                         }
382                         this.changeState(State.ESTABLISHED);
383                         final Set<BGPTableType> tts = Sets.newHashSet();
384                         if (this.remoteOpen.getParams() != null) {
385                                 for (final BGPParameter param : this.remoteOpen.getParams()) {
386                                         if (param instanceof MultiprotocolCapability) {
387                                                 tts.add(((MultiprotocolCapability) param).getTableType());
388                                         }
389                                 }
390                         }
391                         this.sync.addTableTypes(tts);
392                         this.listener.onSessionUp(tts);
393                         // check if the KA is EOR for some AFI/SAFI
394                 } else if (this.state == State.ESTABLISHED) {
395                         this.kaCounter++;
396                         if (this.kaCounter >= 2) {
397                                 this.sync.kaReceived();
398                         }
399                 }
400         }
401
402         @Override
403         public String toString() {
404                 final StringBuilder builder = new StringBuilder();
405                 builder.append("BGPSessionImpl [state=");
406                 builder.append(this.state);
407                 builder.append(", sessionId=");
408                 builder.append(this.sessionId);
409                 builder.append(", localOpen=");
410                 builder.append(this.localOpen);
411                 builder.append(", remoteOpen=");
412                 builder.append(this.remoteOpen);
413                 builder.append("]");
414                 return builder.toString();
415         }
416 }