/* * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.protocol.bgp.rib.impl; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.util.Timeout; import io.netty.util.Timer; import io.netty.util.TimerTask; import io.netty.util.concurrent.Promise; import java.util.List; import java.util.concurrent.TimeUnit; import javax.annotation.concurrent.GuardedBy; import org.opendaylight.protocol.bgp.parser.BGPDocumentedException; import org.opendaylight.protocol.bgp.parser.BGPError; import org.opendaylight.protocol.bgp.parser.BGPSessionListener; 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.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 { // 4 minutes recommended in http://tools.ietf.org/html/rfc4271#section-8.2.2 protected static final int INITIAL_HOLDTIMER = 4; @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 BGPSessionListener listener; private final Timer timer; private final BGPSessionPreferences localPref; @GuardedBy("this") private Open remotePref; @GuardedBy("this") private State state = State.Idle; @GuardedBy("this") private BGPSessionImpl session; public BGPSessionNegotiator(final Timer timer, final Promise 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); } private void writeMessage(final Object o) { this.channel.writeAndFlush(o).addListener(new ChannelFutureListener() { @Override public void operationComplete(final ChannelFuture f) throws Exception { if (f.isSuccess()) { LOG.debug("Message {} sent to socket", o); } else { LOG.info("Failed to send message {}", o, f.cause()); } } }); } @Override protected void startNegotiation() { Preconditions.checkState(this.state == State.Idle); this.writeMessage(new OpenBuilder().setMyAsNumber(this.localPref.getMyAs()).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) throws Exception { synchronized (lock) { if (BGPSessionNegotiator.this.state != State.Finished) { BGPSessionNegotiator.this.writeMessage(new NotifyBuilder().setErrorCode(BGPError.HOLD_TIMER_EXPIRED.getCode()).setErrorSubcode( BGPError.HOLD_TIMER_EXPIRED.getSubcode()).build()); 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: final Notify fsmError = new NotifyBuilder().setErrorCode(BGPError.FSM_ERROR.getCode()).setErrorSubcode( BGPError.FSM_ERROR.getSubcode()).build(); this.writeMessage(fsmError); case OpenConfirm: if (msg instanceof Keepalive) { negotiationSuccessful(this.session); } 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.writeMessage(new NotifyBuilder().setErrorCode(BGPError.FSM_ERROR.getCode()).setErrorSubcode(BGPError.FSM_ERROR.getSubcode()).build()); negotiationFailed(new BGPDocumentedException("Unexpected message", BGPError.FSM_ERROR)); this.state = State.Finished; } private void handleOpen(final Open openObj) { final List prefs = openObj.getBgpParameters(); if (prefs != null && !prefs.isEmpty() && prefs.containsAll(this.localPref.getParams())) { this.remotePref = openObj; this.writeMessage(new KeepaliveBuilder().build()); this.session = new BGPSessionImpl(this.timer, this.listener, this.channel, this.remotePref); this.state = State.OpenConfirm; LOG.debug("Channel {} moved to OpenConfirm state with remote proposal {}", this.channel, this.remotePref); return; } final Notify ntf = new NotifyBuilder().setErrorCode(BGPError.UNSPECIFIC_OPEN_ERROR.getCode()).setErrorSubcode( BGPError.UNSPECIFIC_OPEN_ERROR.getSubcode()).build(); this.writeMessage(ntf); negotiationFailed(new BGPDocumentedException("Open message unacceptable. Check the configuration of BGP speaker.", BGPError.forValue( ntf.getErrorCode(), ntf.getErrorSubcode()))); this.state = State.Finished; } public synchronized State getState() { return this.state; } }