2 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.protocol.bgp.rib.impl;
10 import io.netty.channel.ChannelHandlerContext;
12 import java.io.IOException;
13 import java.util.Date;
15 import java.util.Timer;
16 import java.util.TimerTask;
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;
42 import com.google.common.collect.Sets;
44 class BGPSessionImpl implements BGPSession, ProtocolSession {
46 private static final Logger logger = LoggerFactory.getLogger(BGPSessionImpl.class);
49 * KeepAlive Timer is to be scheduled periodically, each time it starts, it sends KeepAlive Message.
51 private class KeepAliveTimer extends TimerTask {
52 private final BGPSessionImpl parent;
54 public KeepAliveTimer(final BGPSessionImpl parent) {
60 this.parent.handleKeepaliveTimer();
65 * HoldTimer is to be scheduled periodically, when it expires, it closes BGP session.
67 private class HoldTimer extends TimerTask {
68 private final BGPSessionImpl parent;
70 public HoldTimer(final BGPSessionImpl parent) {
76 this.parent.handleHoldTimer();
80 private static final int DEFAULT_HOLD_TIMER_VALUE = 15;
82 public static int HOLD_TIMER_VALUE = DEFAULT_HOLD_TIMER_VALUE; // 240
84 public int KEEP_ALIVE_TIMER_VALUE;
87 * Possible states for Finite State Machine
90 IDLE, OPEN_SENT, OPEN_CONFIRM, ESTABLISHED
94 * Actual state of the FSM.
99 * System.nanoTime value about when was sent the last message Protected to be updated also in tests.
101 protected long lastMessageSentAt;
104 * System.nanoTime value about when was received the last message
106 private long lastMessageReceivedAt;
108 private final int sessionId;
110 private final BGPSessionListener listener;
113 * Open message with session characteristics that were accepted by another BGP (sent from this session).
115 private BGPSessionPreferences localOpen = null;
118 * Open Object with session characteristics for this session (sent from another BGP speaker).
120 private BGPSessionPreferences remoteOpen = null;
123 * Timer object grouping FSM Timers
125 private final Timer stateTimer;
127 private final SessionParent parent;
129 private final ProtocolMessageFactory parser;
131 private final BGPSessionProposalChecker checker;
133 private final BGPSynchronization sync;
135 private final ProtocolSessionOutboundHandler handler;
137 private int kaCounter = 0;
139 private final ChannelHandlerContext ctx;
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;
151 this.checker = connection.getProposalChecker();
152 this.sync = new BGPSynchronization(this.listener);
153 this.handler = new ProtocolSessionOutboundHandler();
157 public void close() {
158 logger.debug("Closing session: " + this);
159 if (this.state == State.ESTABLISHED) {
160 this.sendMessage(new BGPNotificationMessage(BGPError.CEASE));
162 this.changeState(State.IDLE);
163 this.parent.onSessionClosed(this);
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);
175 * Handles incoming message based on their type.
177 * @param msg incoming message
180 public void handleMessage(final ProtocolMessage msg) {
181 final BGPMessage bgpMsg = (BGPMessage) msg;
182 // Update last reception time
183 this.lastMessageReceivedAt = System.nanoTime();
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);
189 // Keepalives are handled internally
190 else if (bgpMsg instanceof BGPKeepAliveMessage) {
191 this.handleKeepAliveMessage();
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());
199 this.listener.onMessage(bgpMsg);
204 public void handleMalformedMessage(final DeserializerException e) {
205 logger.warn("Received malformed message: {}", e.getMessage(), e);
206 this.terminate(BGPError.FSM_ERROR);
210 public void handleMalformedMessage(final DocumentedException e) {
211 logger.warn("Received malformed message: {}", e.getMessage(), e);
212 this.terminate(((BGPDocumentedException) e).getError());
216 public void endOfInput() {
217 if (this.state != State.IDLE) {
218 this.listener.onSessionDown(this, new IOException("End of input detected. Close the session."));
223 public ProtocolMessageFactory getMessageFactory() {
228 public int maximumMessageSize() {
232 void sendMessage(final BGPMessage msg) {
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);
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);
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).
255 private void terminate(final BGPError error) {
256 this.sendMessage(new BGPNotificationMessage(error));
257 this.closeWithoutMessage();
258 this.listener.onSessionTerminated(error);
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.
267 private synchronized void handleHoldTimer() {
268 final long ct = System.nanoTime();
270 final long nextHold = (long) (this.lastMessageReceivedAt + (HOLD_TIMER_VALUE * 1E9));
272 if (this.state != State.IDLE) {
273 if (ct >= nextHold) {
274 logger.debug("HoldTimer expired. " + new Date());
275 this.terminate(BGPError.HOLD_TIMER_EXPIRED);
278 this.stateTimer.schedule(new HoldTimer(this), (long) ((nextHold - ct) / 1E6));
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.
288 private synchronized void handleKeepaliveTimer() {
289 final long ct = System.nanoTime();
291 long nextKeepalive = (long) (this.lastMessageSentAt + (this.KEEP_ALIVE_TIMER_VALUE * 1E9));
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));
298 this.stateTimer.schedule(new KeepAliveTimer(this), (long) ((nextKeepalive - ct) / 1E6));
302 private void changeState(final State finalState) {
303 final String desc = "Changed to state: ";
304 switch (finalState) {
306 logger.debug(desc + State.IDLE);
307 this.state = State.IDLE;
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);
314 this.state = State.OPEN_SENT;
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);
321 this.state = State.OPEN_CONFIRM;
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);
328 this.state = State.ESTABLISHED;
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.
338 * @param msg received Open Message.
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);
347 // if the session characteristics were unacceptable, the session is terminated
348 // with given BGP error
350 this.checker.checkSessionCharacteristics(this.remoteOpen);
351 } catch (final BGPDocumentedException e) {
352 this.terminate(e.getError());
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()));
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);
368 this.changeState(State.OPEN_CONFIRM);
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.
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);
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());
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) {
396 if (this.kaCounter >= 2) {
397 this.sync.kaReceived();
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);
414 return builder.toString();