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 com.google.common.annotations.VisibleForTesting;
11 import com.google.common.base.Objects;
12 import com.google.common.base.Objects.ToStringHelper;
13 import com.google.common.base.Preconditions;
14 import com.google.common.collect.Sets;
15 import io.netty.channel.Channel;
16 import io.netty.channel.ChannelFuture;
17 import io.netty.channel.ChannelFutureListener;
18 import java.io.IOException;
19 import java.util.Date;
21 import java.util.concurrent.TimeUnit;
22 import javax.annotation.concurrent.GuardedBy;
23 import org.opendaylight.protocol.bgp.parser.AsNumberUtil;
24 import org.opendaylight.protocol.bgp.parser.BGPError;
25 import org.opendaylight.protocol.bgp.parser.BgpTableTypeImpl;
26 import org.opendaylight.protocol.bgp.rib.spi.BGPSession;
27 import org.opendaylight.protocol.bgp.rib.spi.BGPSessionListener;
28 import org.opendaylight.protocol.bgp.rib.spi.BGPTerminationReason;
29 import org.opendaylight.protocol.framework.AbstractProtocolSession;
30 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.AsNumber;
31 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv4Address;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.Keepalive;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.KeepaliveBuilder;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.Notify;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.NotifyBuilder;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.Open;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.Update;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.open.BgpParameters;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.open.bgp.parameters.CParameters;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev130919.BgpTableType;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev130919.open.bgp.parameters.c.parameters.MultiprotocolCase;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.TablesKey;
43 import org.opendaylight.yangtools.yang.binding.Notification;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
48 public class BGPSessionImpl extends AbstractProtocolSession<Notification> implements BGPSession {
50 private static final Logger LOG = LoggerFactory.getLogger(BGPSessionImpl.class);
52 private static final Notification KEEP_ALIVE = new KeepaliveBuilder().build();
55 * Internal session state.
59 * The session object is created by the negotiator in OpenConfirm state. While in this state, the session object
60 * is half-alive, e.g. the timers are running, but the session is not completely up, e.g. it has not been
61 * announced to the listener. If the session is torn down in this state, we do not inform the listener.
65 * The session has been completely established.
69 * The session has been closed. It will not be resurrected.
75 * System.nanoTime value about when was sent the last message.
78 private long lastMessageSentAt;
81 * System.nanoTime value about when was received the last message
83 private long lastMessageReceivedAt;
85 private final BGPSessionListener listener;
87 private final BGPSynchronization sync;
89 private int kaCounter = 0;
91 private final Channel channel;
94 private State state = State.OpenConfirm;
96 private final Set<BgpTableType> tableTypes;
97 private final int holdTimerValue;
98 private final int keepAlive;
99 private final AsNumber asNumber;
100 private final Ipv4Address bgpId;
102 public BGPSessionImpl(final BGPSessionListener listener, final Channel channel, final Open remoteOpen, final int localHoldTimer) {
103 this.listener = Preconditions.checkNotNull(listener);
104 this.channel = Preconditions.checkNotNull(channel);
105 this.holdTimerValue = (remoteOpen.getHoldTimer() < localHoldTimer) ? remoteOpen.getHoldTimer() : localHoldTimer;
106 LOG.info("BGP HoldTimer new value: {}", this.holdTimerValue);
107 this.keepAlive = this.holdTimerValue / 3;
108 this.asNumber = AsNumberUtil.advertizedAsNumber(remoteOpen);
110 final Set<TablesKey> tts = Sets.newHashSet();
111 final Set<BgpTableType> tats = Sets.newHashSet();
112 if (remoteOpen.getBgpParameters() != null) {
113 for (final BgpParameters param : remoteOpen.getBgpParameters()) {
114 final CParameters cp = param.getCParameters();
115 if (cp instanceof MultiprotocolCase) {
116 final TablesKey tt = new TablesKey(((MultiprotocolCase) cp).getMultiprotocolCapability().getAfi(), ((MultiprotocolCase) cp).getMultiprotocolCapability().getSafi());
117 LOG.trace("Added table type to sync {}", tt);
119 tats.add(new BgpTableTypeImpl(tt.getAfi(), tt.getSafi()));
124 this.sync = new BGPSynchronization(this, this.listener, tts);
125 this.tableTypes = tats;
127 if (this.holdTimerValue != 0) {
128 channel.eventLoop().schedule(new Runnable() {
133 }, this.holdTimerValue, TimeUnit.SECONDS);
135 channel.eventLoop().schedule(new Runnable() {
138 handleKeepaliveTimer();
140 }, this.keepAlive, TimeUnit.SECONDS);
142 this.bgpId = remoteOpen.getBgpIdentifier();
146 public synchronized void close() {
147 LOG.info("Closing session: {}", this);
148 if (this.state != State.Idle) {
149 this.sendMessage(new NotifyBuilder().setErrorCode(BGPError.CEASE.getCode()).setErrorSubcode((short)0).build());
150 this.channel.close();
151 this.state = State.Idle;
156 * Handles incoming message based on their type.
158 * @param msg incoming message
161 public synchronized void handleMessage(final Notification msg) {
162 // Update last reception time
163 this.lastMessageReceivedAt = System.nanoTime();
165 if (msg instanceof Open) {
166 // Open messages should not be present here
167 this.terminate(BGPError.FSM_ERROR);
168 } else if (msg instanceof Notify) {
169 // Notifications are handled internally
170 LOG.info("Session closed because Notification message received: {} / {}", ((Notify) msg).getErrorCode(),
171 ((Notify) msg).getErrorSubcode());
172 this.closeWithoutMessage();
173 this.listener.onSessionTerminated(this, new BGPTerminationReason(BGPError.forValue(((Notify) msg).getErrorCode(),
174 ((Notify) msg).getErrorSubcode())));
175 } else if (msg instanceof Keepalive) {
176 // Keepalives are handled internally
177 LOG.trace("Received KeepAlive messsage.");
179 if (this.kaCounter >= 2) {
180 this.sync.kaReceived();
183 // All others are passed up
184 this.listener.onMessage(this, msg);
185 this.sync.updReceived((Update) msg);
190 public synchronized void endOfInput() {
191 if (this.state == State.Up) {
192 this.listener.onSessionDown(this, new IOException("End of input detected. Close the session."));
196 synchronized void sendMessage(final Notification msg) {
198 this.channel.writeAndFlush(msg).addListener(
199 new ChannelFutureListener() {
201 public void operationComplete(final ChannelFuture f) {
202 if (!f.isSuccess()) {
203 LOG.info("Failed to send message {} to socket {}", msg, f.cause(), BGPSessionImpl.this.channel);
205 LOG.trace("Message {} sent to socket {}", msg, BGPSessionImpl.this.channel);
209 this.lastMessageSentAt = System.nanoTime();
210 } catch (final Exception e) {
211 LOG.warn("Message {} was not sent.", msg, e);
215 private synchronized void closeWithoutMessage() {
216 LOG.debug("Closing session: {}", this);
217 this.channel.close();
218 this.state = State.Idle;
222 * Closes PCEP session from the parent with given reason. A message needs to be sent, but parent doesn't have to be
223 * modified, because he initiated the closing. (To prevent concurrent modification exception).
227 private void terminate(final BGPError error) {
228 this.sendMessage(new NotifyBuilder().setErrorCode(error.getCode()).setErrorSubcode(error.getSubcode()).build());
229 this.closeWithoutMessage();
231 this.listener.onSessionTerminated(this, new BGPTerminationReason(error));
235 * If HoldTimer expires, the session ends. If a message (whichever) was received during this period, the HoldTimer
236 * will be rescheduled by HOLD_TIMER_VALUE + the time that has passed from the start of the HoldTimer to the time at
237 * which the message was received. If the session was closed by the time this method starts to execute (the session
238 * state will become IDLE), then rescheduling won't occur.
240 private synchronized void handleHoldTimer() {
241 if (this.state == State.Idle) {
245 final long ct = System.nanoTime();
246 final long nextHold = this.lastMessageReceivedAt + TimeUnit.SECONDS.toNanos(this.holdTimerValue);
248 if (ct >= nextHold) {
249 LOG.debug("HoldTimer expired. {}", new Date());
250 this.terminate(BGPError.HOLD_TIMER_EXPIRED);
252 this.channel.eventLoop().schedule(new Runnable() {
257 }, nextHold - ct, TimeUnit.NANOSECONDS);
262 * If KeepAlive Timer expires, sends KeepAlive message. If a message (whichever) was send during this period, the
263 * KeepAlive Timer will be rescheduled by KEEP_ALIVE_TIMER_VALUE + the time that has passed from the start of the
264 * KeepAlive timer to the time at which the message was sent. If the session was closed by the time this method
265 * starts to execute (the session state will become IDLE), that rescheduling won't occur.
267 private synchronized void handleKeepaliveTimer() {
268 if (this.state == State.Idle) {
272 final long ct = System.nanoTime();
273 long nextKeepalive = this.lastMessageSentAt + TimeUnit.SECONDS.toNanos(this.keepAlive);
275 if (ct >= nextKeepalive) {
276 this.sendMessage(KEEP_ALIVE);
277 nextKeepalive = this.lastMessageSentAt + TimeUnit.SECONDS.toNanos(this.keepAlive);
279 this.channel.eventLoop().schedule(new Runnable() {
282 handleKeepaliveTimer();
284 }, nextKeepalive - ct, TimeUnit.NANOSECONDS);
288 public final String toString() {
289 return addToStringAttributes(Objects.toStringHelper(this)).toString();
292 protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
293 toStringHelper.add("channel", this.channel);
294 toStringHelper.add("state", this.getState());
295 return toStringHelper;
299 public Set<BgpTableType> getAdvertisedTableTypes() {
300 return this.tableTypes;
304 protected synchronized void sessionUp() {
305 this.state = State.Up;
306 this.listener.onSessionUp(this);
309 public synchronized State getState() {
314 public final Ipv4Address getBgpId() {
319 public final AsNumber getAsNumber() {
320 return this.asNumber;
323 synchronized boolean isWritable() {
324 return this.channel != null && this.channel.isWritable();
327 synchronized void schedule(final Runnable task) {
328 Preconditions.checkState(this.channel != null);
329 this.channel.eventLoop().submit(task);
334 protected void setLastMessageSentAt(final long lastMessageSentAt) {
335 this.lastMessageSentAt = lastMessageSentAt;