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.Channel;
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.SessionParent;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
41 import com.google.common.collect.Sets;
43 class BGPSessionImpl implements BGPSession, ProtocolSession {
45 private static final Logger logger = LoggerFactory.getLogger(BGPSessionImpl.class);
48 * KeepAlive Timer is to be scheduled periodically, each time it starts, it sends KeepAlive Message.
50 private class KeepAliveTimer extends TimerTask {
51 private final BGPSessionImpl parent;
53 public KeepAliveTimer(final BGPSessionImpl parent) {
59 this.parent.handleKeepaliveTimer();
64 * HoldTimer is to be scheduled periodically, when it expires, it closes BGP session.
66 private class HoldTimer extends TimerTask {
67 private final BGPSessionImpl parent;
69 public HoldTimer(final BGPSessionImpl parent) {
75 this.parent.handleHoldTimer();
79 private static final int DEFAULT_HOLD_TIMER_VALUE = 15;
81 public static int HOLD_TIMER_VALUE = DEFAULT_HOLD_TIMER_VALUE; // 240
83 public int KEEP_ALIVE_TIMER_VALUE;
86 * Possible states for Finite State Machine
89 IDLE, OPEN_SENT, OPEN_CONFIRM, ESTABLISHED
93 * Actual state of the FSM.
98 * System.nanoTime value about when was sent the last message Protected to be updated also in tests.
100 protected long lastMessageSentAt;
103 * System.nanoTime value about when was received the last message
105 private long lastMessageReceivedAt;
107 private final int sessionId;
109 private final BGPSessionListener listener;
112 * Open message with session characteristics that were accepted by another BGP (sent from this session).
114 private BGPSessionPreferences localOpen = null;
117 * Open Object with session characteristics for this session (sent from another BGP speaker).
119 private BGPSessionPreferences remoteOpen = null;
122 * Timer object grouping FSM Timers
124 private final Timer stateTimer;
126 private final SessionParent parent;
128 private final ProtocolMessageFactory parser;
130 private final BGPSessionProposalChecker checker;
132 private final BGPSynchronization sync;
134 private int kaCounter = 0;
136 private final Channel channel;
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);
153 public void close() {
154 logger.debug("Closing session: " + this);
155 if (this.state == State.ESTABLISHED) {
156 this.sendMessage(new BGPNotificationMessage(BGPError.CEASE));
158 this.changeState(State.IDLE);
159 this.parent.onSessionClosed(this);
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);
171 * Handles incoming message based on their type.
173 * @param msg incoming message
176 public void handleMessage(final ProtocolMessage msg) {
177 final BGPMessage bgpMsg = (BGPMessage) msg;
178 // Update last reception time
179 this.lastMessageReceivedAt = System.nanoTime();
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);
185 // Keepalives are handled internally
186 else if (bgpMsg instanceof BGPKeepAliveMessage) {
187 this.handleKeepAliveMessage();
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());
195 this.listener.onMessage(bgpMsg);
200 public void handleMalformedMessage(final DeserializerException e) {
201 logger.warn("Received malformed message: {}", e.getMessage(), e);
202 this.terminate(BGPError.FSM_ERROR);
206 public void handleMalformedMessage(final DocumentedException e) {
207 logger.warn("Received malformed message: {}", e.getMessage(), e);
208 this.terminate(((BGPDocumentedException) e).getError());
212 public void endOfInput() {
213 if (this.state != State.IDLE) {
214 this.listener.onSessionDown(this, new IOException("End of input detected. Close the session."));
219 public ProtocolMessageFactory getMessageFactory() {
224 public int maximumMessageSize() {
228 void sendMessage(final BGPMessage msg) {
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);
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);
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).
251 private void terminate(final BGPError error) {
252 this.sendMessage(new BGPNotificationMessage(error));
253 this.closeWithoutMessage();
254 this.listener.onSessionTerminated(error);
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.
263 private synchronized void handleHoldTimer() {
264 final long ct = System.nanoTime();
266 final long nextHold = (long) (this.lastMessageReceivedAt + HOLD_TIMER_VALUE * 1E9);
268 if (this.state != State.IDLE) {
269 if (ct >= nextHold) {
270 logger.debug("HoldTimer expired. " + new Date());
271 this.terminate(BGPError.HOLD_TIMER_EXPIRED);
274 this.stateTimer.schedule(new HoldTimer(this), (long) ((nextHold - ct) / 1E6));
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.
284 private synchronized void handleKeepaliveTimer() {
285 final long ct = System.nanoTime();
287 long nextKeepalive = (long) (this.lastMessageSentAt + this.KEEP_ALIVE_TIMER_VALUE * 1E9);
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);
294 this.stateTimer.schedule(new KeepAliveTimer(this), (long) ((nextKeepalive - ct) / 1E6));
298 private void changeState(final State finalState) {
299 final String desc = "Changed to state: ";
300 switch (finalState) {
302 logger.debug(desc + State.IDLE);
303 this.state = State.IDLE;
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);
310 this.state = State.OPEN_SENT;
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);
317 this.state = State.OPEN_CONFIRM;
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);
324 this.state = State.ESTABLISHED;
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.
334 * @param msg received Open Message.
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);
343 // if the session characteristics were unacceptable, the session is terminated
344 // with given BGP error
346 this.checker.checkSessionCharacteristics(this.remoteOpen);
347 } catch (final BGPDocumentedException e) {
348 this.terminate(e.getError());
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()));
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);
364 this.changeState(State.OPEN_CONFIRM);
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.
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);
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());
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) {
392 if (this.kaCounter >= 2) {
393 this.sync.kaReceived();
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);
410 return builder.toString();