*/
package org.opendaylight.protocol.bgp.rib.impl;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+
import io.netty.channel.Channel;
import io.netty.util.Timeout;
import io.netty.util.Timer;
import javax.annotation.concurrent.GuardedBy;
+import org.opendaylight.protocol.bgp.parser.AsNumberUtil;
import org.opendaylight.protocol.bgp.parser.BGPDocumentedException;
import org.opendaylight.protocol.bgp.parser.BGPError;
-import org.opendaylight.protocol.bgp.parser.BGPMessage;
-import org.opendaylight.protocol.bgp.parser.BGPParameter;
import org.opendaylight.protocol.bgp.parser.BGPSessionListener;
-import org.opendaylight.protocol.bgp.parser.message.BGPKeepAliveMessage;
-import org.opendaylight.protocol.bgp.parser.message.BGPNotificationMessage;
-import org.opendaylight.protocol.bgp.parser.message.BGPOpenMessage;
-import org.opendaylight.protocol.bgp.parser.parameter.CapabilityParameter;
-import org.opendaylight.protocol.bgp.parser.parameter.MultiprotocolCapability;
import org.opendaylight.protocol.bgp.rib.impl.spi.BGPSessionPreferences;
import org.opendaylight.protocol.framework.AbstractSessionNegotiator;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev130919.BgpAddressFamily;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev130919.BgpSubsequentAddressFamily;
+import org.opendaylight.protocol.util.Values;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.AsNumber;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.Keepalive;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.KeepaliveBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.Notify;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.NotifyBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.Open;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.OpenBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.open.BgpParameters;
+import org.opendaylight.yangtools.yang.binding.Notification;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Preconditions;
-
-public final class BGPSessionNegotiator extends AbstractSessionNegotiator<BGPMessage, BGPSessionImpl> {
- // 4 minutes recommended in http://tools.ietf.org/html/rfc4271#section-8.2.2
- // FIXME to actual value
- protected static final int INITIAL_HOLDTIMER = 1;
-
- @VisibleForTesting
- public enum State {
- /**
- * Negotiation has not started yet.
- */
- Idle,
- /**
- * We have sent our Open message, and are waiting for the peer's Open message.
- */
- OpenSent,
- /**
- * We have received the peer's Open message, which is acceptable, and we're waiting the acknowledgement of our
- * Open message.
- */
- OpenConfirm,
- /**
- * The negotiation finished.
- */
- Finished,
- }
-
- private static final Logger logger = LoggerFactory.getLogger(BGPSessionNegotiator.class);
- private final BGPSessionListener listener;
- private final Timer timer;
- private final BGPSessionPreferences localPref;
-
- @GuardedBy("this")
- private BGPOpenMessage remotePref;
-
- @GuardedBy("this")
- private State state = State.Idle;
-
- @GuardedBy("this")
- private BGPSessionImpl session;
-
- public BGPSessionNegotiator(final Timer timer, final Promise<BGPSessionImpl> promise, final Channel channel,
- final BGPSessionPreferences initialPrefs, final BGPSessionListener listener) {
- super(promise, channel);
- this.listener = Preconditions.checkNotNull(listener);
- this.localPref = Preconditions.checkNotNull(initialPrefs);
- this.timer = Preconditions.checkNotNull(timer);
- }
-
- @Override
- protected void startNegotiation() {
- Preconditions.checkState(this.state == State.Idle);
- this.channel.writeAndFlush(new BGPOpenMessage(this.localPref.getMyAs(), (short) this.localPref.getHoldTime(), this.localPref.getBgpId(), this.localPref.getParams()));
- this.state = State.OpenSent;
-
- final Object lock = this;
- this.timer.newTimeout(new TimerTask() {
- @Override
- public void run(final Timeout timeout) throws Exception {
- synchronized (lock) {
- if (BGPSessionNegotiator.this.state != State.Finished) {
- negotiationFailed(new BGPDocumentedException("HoldTimer expired", BGPError.FSM_ERROR));
- BGPSessionNegotiator.this.channel.writeAndFlush(new BGPNotificationMessage(BGPError.HOLD_TIMER_EXPIRED));
- BGPSessionNegotiator.this.state = State.Finished;
- }
- }
- }
- }, INITIAL_HOLDTIMER, TimeUnit.MINUTES);
- }
-
- @Override
- protected synchronized void handleMessage(final BGPMessage msg) {
- logger.debug("Channel {} handling message in state {}", this.channel, this.state);
-
- switch (this.state) {
- case Finished:
- case Idle:
- throw new IllegalStateException("Unexpected state " + this.state);
- case OpenConfirm:
- if (msg instanceof BGPKeepAliveMessage) {
- negotiationSuccessful(this.session);
- } else if (msg instanceof BGPNotificationMessage) {
- final BGPNotificationMessage ntf = (BGPNotificationMessage) msg;
- negotiationFailed(new BGPDocumentedException("Peer refusal", ntf.getError()));
- }
- this.state = State.Finished;
- return;
- case OpenSent:
- if (msg instanceof BGPOpenMessage) {
- final BGPOpenMessage openObj = (BGPOpenMessage) msg;
-
- final List<BGPParameter> prefs = openObj.getOptParams();
- if (prefs != null && !prefs.isEmpty()) {
- for (final BGPParameter param : openObj.getOptParams()) {
- if (param instanceof CapabilityParameter) {
- if (((CapabilityParameter) param).getCode() == MultiprotocolCapability.CODE) {
- final MultiprotocolCapability cap = (MultiprotocolCapability) param;
- if (cap.getAfi() == BgpAddressFamily.Linkstate && cap.getSafi() == BgpSubsequentAddressFamily.Linkstate) {
- this.remotePref = openObj;
- this.channel.writeAndFlush(new BGPKeepAliveMessage());
- this.session = new BGPSessionImpl(this.timer, this.listener, this.channel, this.remotePref);
- this.state = State.OpenConfirm;
- logger.debug("Channel {} moved to OpenConfirm state with remote proposal {}", this.channel,
- this.remotePref);
- return;
- }
- }
- }
- }
- }
- final BGPNotificationMessage ntf = new BGPNotificationMessage(BGPError.UNSPECIFIC_OPEN_ERROR);
- this.channel.writeAndFlush(ntf);
- negotiationFailed(new BGPDocumentedException("Linkstate capability not advertised.", ntf.getError()));
- this.state = State.Finished;
- return;
- }
- break;
- }
-
- // Catch-all for unexpected message
- logger.warn("Channel {} state {} unexpected message {}", this.channel, this.state, msg);
- this.channel.writeAndFlush(new BGPNotificationMessage(BGPError.FSM_ERROR));
- negotiationFailed(new BGPDocumentedException("Unexpected message", BGPError.FSM_ERROR));
- this.state = State.Finished;
- }
-
- public synchronized State getState() {
- return this.state;
- }
+public final class BGPSessionNegotiator extends AbstractSessionNegotiator<Notification, BGPSessionImpl> {
+ // 4 minutes recommended in http://tools.ietf.org/html/rfc4271#section-8.2.2
+ protected static final int INITIAL_HOLDTIMER = 4;
+
+ /**
+ * @see <a href="http://tools.ietf.org/html/rfc6793">BGP Support for 4-Octet AS Number Space</a>
+ */
+ private static final int AS_TRANS = 23456;
+
+ @VisibleForTesting
+ public enum State {
+ /**
+ * Negotiation has not started yet.
+ */
+ Idle,
+ /**
+ * We have sent our Open message, and are waiting for the peer's Open message.
+ */
+ OpenSent,
+ /**
+ * We have received the peer's Open message, which is acceptable, and we're waiting the acknowledgement of our
+ * Open message.
+ */
+ OpenConfirm,
+ /**
+ * The negotiation finished.
+ */
+ Finished,
+ }
+
+ private static final Logger LOG = LoggerFactory.getLogger(BGPSessionNegotiator.class);
+ private final BGPSessionPreferences localPref;
+ private final BGPSessionListener listener;
+ private final AsNumber remoteAs;
+ private final Timer timer;
+
+ @GuardedBy("this")
+ private State state = State.Idle;
+
+ @GuardedBy("this")
+ private BGPSessionImpl session;
+
+ public BGPSessionNegotiator(final Timer timer, final Promise<BGPSessionImpl> promise, final Channel channel,
+ final BGPSessionPreferences initialPrefs, final AsNumber remoteAs, final BGPSessionListener listener) {
+ super(promise, channel);
+ this.listener = Preconditions.checkNotNull(listener);
+ this.localPref = Preconditions.checkNotNull(initialPrefs);
+ this.remoteAs = Preconditions.checkNotNull(remoteAs);
+ this.timer = Preconditions.checkNotNull(timer);
+ }
+
+ @Override
+ protected void startNegotiation() {
+ Preconditions.checkState(this.state == State.Idle);
+ int as = this.localPref.getMyAs().getValue().intValue();
+ // Set as AS_TRANS if the value is bigger than 2B
+ if (as > Values.UNSIGNED_SHORT_MAX_VALUE) {
+ as = AS_TRANS;
+ }
+ this.sendMessage(new OpenBuilder().setMyAsNumber(as).setHoldTimer(this.localPref.getHoldTime()).setBgpIdentifier(
+ this.localPref.getBgpId()).setBgpParameters(this.localPref.getParams()).build());
+ this.state = State.OpenSent;
+
+ final Object lock = this;
+ this.timer.newTimeout(new TimerTask() {
+ @Override
+ public void run(final Timeout timeout) {
+ synchronized (lock) {
+ if (BGPSessionNegotiator.this.state != State.Finished) {
+ BGPSessionNegotiator.this.sendMessage(buildErrorNotify(BGPError.HOLD_TIMER_EXPIRED));
+ negotiationFailed(new BGPDocumentedException("HoldTimer expired", BGPError.FSM_ERROR));
+ BGPSessionNegotiator.this.state = State.Finished;
+ }
+ }
+ }
+ }, INITIAL_HOLDTIMER, TimeUnit.MINUTES);
+ }
+
+ @Override
+ protected synchronized void handleMessage(final Notification msg) {
+ LOG.debug("Channel {} handling message in state {}", this.channel, this.state);
+
+ switch (this.state) {
+ case Finished:
+ case Idle:
+ this.sendMessage(buildErrorNotify(BGPError.FSM_ERROR));
+ return;
+ case OpenConfirm:
+ if (msg instanceof Keepalive) {
+ negotiationSuccessful(this.session);
+ LOG.info("BGP Session with peer {} established successfully.", this.channel);
+ } else if (msg instanceof Notify) {
+ final Notify ntf = (Notify) msg;
+ negotiationFailed(new BGPDocumentedException("Peer refusal", BGPError.forValue(ntf.getErrorCode(), ntf.getErrorSubcode())));
+ }
+ this.state = State.Finished;
+ return;
+ case OpenSent:
+ if (msg instanceof Open) {
+ final Open openObj = (Open) msg;
+ handleOpen(openObj);
+ return;
+ }
+ break;
+ }
+
+ // Catch-all for unexpected message
+ LOG.warn("Channel {} state {} unexpected message {}", this.channel, this.state, msg);
+ this.sendMessage(buildErrorNotify(BGPError.FSM_ERROR));
+ negotiationFailed(new BGPDocumentedException("Unexpected message", BGPError.FSM_ERROR));
+ this.state = State.Finished;
+ }
+
+ private static Notify buildErrorNotify(final BGPError err) {
+ return new NotifyBuilder().setErrorCode(err.getCode()).setErrorSubcode(err.getSubcode()).build();
+ }
+
+ private void handleOpen(final Open openObj) {
+ final AsNumber as = AsNumberUtil.advertizedAsNumber(openObj);
+ if (!this.remoteAs.equals(as)) {
+ LOG.warn("Unexpected remote AS number. Expecting {}, got {}", this.remoteAs, as);
+ this.sendMessage(buildErrorNotify(BGPError.BAD_PEER_AS));
+ negotiationFailed(new BGPDocumentedException("Peer AS number mismatch", BGPError.BAD_PEER_AS));
+ this.state = State.Finished;
+ return;
+ }
+
+ final List<BgpParameters> prefs = openObj.getBgpParameters();
+ if (prefs != null && !prefs.isEmpty()) {
+ if (!prefs.containsAll(this.localPref.getParams())) {
+ LOG.info("BGP Open message session parameters differ, session still accepted.");
+ }
+ this.sendMessage(new KeepaliveBuilder().build());
+ this.session = new BGPSessionImpl(this.timer, this.listener, this.channel, openObj, this.localPref.getHoldTime());
+ this.state = State.OpenConfirm;
+ LOG.debug("Channel {} moved to OpenConfirm state with remote proposal {}", this.channel, openObj);
+ return;
+ }
+
+ this.sendMessage(buildErrorNotify(BGPError.UNSPECIFIC_OPEN_ERROR));
+ negotiationFailed(new BGPDocumentedException("Open message unacceptable. Check the configuration of BGP speaker.", BGPError.UNSPECIFIC_OPEN_ERROR));
+ this.state = State.Finished;
+ }
+
+ public synchronized State getState() {
+ return this.state;
+ }
}