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