BUG-4344: Expose PCEP local session characteristics
[bgpcep.git] / pcep / impl / src / main / java / org / opendaylight / protocol / pcep / impl / AbstractPCEPSessionNegotiator.java
index c9629096f1de63b4126afe912122521834d59155..eee8434de4b5511157649afd7ecf6681f0aacc9f 100644 (file)
@@ -10,16 +10,23 @@ package org.opendaylight.protocol.pcep.impl;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
 import io.netty.channel.Channel;
+import io.netty.handler.ssl.SslHandler;
 import io.netty.util.concurrent.Promise;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
-import org.opendaylight.protocol.framework.AbstractSessionNegotiator;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import org.opendaylight.controller.config.yang.pcep.impl.Tls;
+import org.opendaylight.protocol.pcep.impl.spi.Util;
+import org.opendaylight.protocol.pcep.impl.tls.SslContextFactory;
 import org.opendaylight.protocol.pcep.spi.PCEPErrors;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Keepalive;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.KeepaliveBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.OpenBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Pcerr;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Starttls;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.StarttlsBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.Message;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.OpenMessage;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.keepalive.message.KeepaliveMessageBuilder;
@@ -27,6 +34,7 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.typ
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.open.object.Open;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.pcep.error.object.ErrorObject;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.pcerr.message.pcerr.message.error.type.SessionCase;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.start.tls.message.StartTlsMessageBuilder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -34,7 +42,7 @@ import org.slf4j.LoggerFactory;
  * Abstract PCEP session negotiator. Takes care of basic handshake without implementing a specific policy. Policies need
  * to be provided by a specific subclass.
  */
-public abstract class AbstractPCEPSessionNegotiator extends AbstractSessionNegotiator<Message, PCEPSessionImpl> {
+public abstract class AbstractPCEPSessionNegotiator extends AbstractSessionNegotiator {
     /**
      * Unified KeepWait and OpenWait timer expiration, in seconds.
      */
@@ -53,6 +61,10 @@ public abstract class AbstractPCEPSessionNegotiator extends AbstractSessionNegot
          * point we move into OpenWait state.
          */
         IDLE,
+        /**
+         * Waiting for the peer's StartTLS message
+         */
+        START_TLS_WAIT,
         /**
          * Waiting for the peer's OPEN message.
          */
@@ -75,6 +87,7 @@ public abstract class AbstractPCEPSessionNegotiator extends AbstractSessionNegot
     private Future<?> failTimer;
     private Open localPrefs;
     private Open remotePrefs;
+    private Tls tlsConfiguration;
 
     protected AbstractPCEPSessionNegotiator(final Promise<PCEPSessionImpl> promise, final Channel channel) {
         super(promise, channel);
@@ -116,7 +129,6 @@ public abstract class AbstractPCEPSessionNegotiator extends AbstractSessionNegot
      * Create the protocol session.
      *
      * @param channel Underlying channel.
-     * @param sessionId Assigned session ID.
      * @param localPrefs Session preferences proposed by us and accepted by the peer.
      * @param remotePrefs Session preferences proposed by the peer and accepted by us.
      * @return New protocol session.
@@ -141,6 +153,11 @@ public abstract class AbstractPCEPSessionNegotiator extends AbstractSessionNegot
                 case FINISHED:
                 case IDLE:
                     break;
+                case START_TLS_WAIT:
+                    sendErrorMessage(PCEPErrors.STARTTLS_TIMER_EXP);
+                    negotiationFailed(new TimeoutException("StartTLSWait timer expired"));
+                    AbstractPCEPSessionNegotiator.this.state = State.FINISHED;
+                    break;
                 case KEEP_WAIT:
                     sendErrorMessage(PCEPErrors.NO_MSG_BEFORE_EXP_KEEPWAIT);
                     negotiationFailed(new TimeoutException("KeepWait timer expired"));
@@ -161,6 +178,17 @@ public abstract class AbstractPCEPSessionNegotiator extends AbstractSessionNegot
     @Override
     protected final void startNegotiation() {
         Preconditions.checkState(this.state == State.IDLE);
+        if (this.tlsConfiguration != null) {
+            this.sendMessage(new StarttlsBuilder().setStartTlsMessage(new StartTlsMessageBuilder().build()).build());
+            this.state = State.START_TLS_WAIT;
+            scheduleFailTimer();
+            LOG.info("Started TLS connection negotiation with peer {}", this.channel);
+        } else {
+            startNegotiationWithOpen();
+        }
+    }
+
+    private void startNegotiationWithOpen() {
         this.localPrefs = getInitialProposal();
         final OpenMessage m = new org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.OpenBuilder().setOpenMessage(
                 new OpenMessageBuilder().setOpen(this.localPrefs).build()).build();
@@ -173,83 +201,113 @@ public abstract class AbstractPCEPSessionNegotiator extends AbstractSessionNegot
 
     private boolean handleMessageKeepWait(final Message msg) {
         if (msg instanceof Keepalive) {
-            this.localOK = true;
-            if (this.remoteOK) {
-                LOG.info("PCEP peer {} completed negotiation", this.channel);
-                negotiationSuccessful(createSession(this.channel, this.localPrefs, this.remotePrefs));
-                this.state = State.FINISHED;
-            } else {
-                scheduleFailTimer();
-                this.state = State.OPEN_WAIT;
-                LOG.debug("Channel {} moved to OpenWait state with localOK=1", this.channel);
-            }
-            return true;
+            return handleMessageKeepAlive();
         } else if (msg instanceof Pcerr) {
-            final org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.pcerr.message.PcerrMessage err = ((Pcerr) msg).getPcerrMessage();
-            if (err.getErrorType() == null) {
-                final ErrorObject obj = err.getErrors().get(0).getErrorObject();
-                LOG.warn("Unexpected error received from PCC: type {} value {}", obj.getType(), obj.getValue());
-                negotiationFailed(new IllegalStateException("Unexpected error received from PCC."));
-                this.state = State.IDLE;
-                return true;
-            }
-            this.localPrefs = getRevisedProposal(((SessionCase) err.getErrorType()).getSession().getOpen());
-            if (this.localPrefs == null) {
-                sendErrorMessage(PCEPErrors.PCERR_NON_ACC_SESSION_CHAR);
-                negotiationFailed(new IllegalStateException("Peer suggested unacceptable retry proposal"));
-                this.state = State.FINISHED;
-                return true;
-            }
-            this.sendMessage(new OpenBuilder().setOpenMessage(new OpenMessageBuilder().setOpen(this.localPrefs).build()).build());
-            if (!this.remoteOK) {
-                this.state = State.OPEN_WAIT;
-            }
+            return handleMessagePcerr(msg);
+        }
+        return false;
+    }
+
+    private boolean handleMessageKeepAlive() {
+        this.localOK = true;
+        if (this.remoteOK) {
+            LOG.info("PCEP peer {} completed negotiation", this.channel);
+            negotiationSuccessful(createSession(this.channel, this.localPrefs, this.remotePrefs));
+            this.state = State.FINISHED;
+        } else {
             scheduleFailTimer();
+            this.state = State.OPEN_WAIT;
+            LOG.debug("Channel {} moved to OpenWait state with localOK=1", this.channel);
+        }
+        return true;
+    }
+
+    private boolean handleMessagePcerr(final Message msg) {
+        final org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.pcerr.message.PcerrMessage err = ((Pcerr) msg).getPcerrMessage();
+        if (err.getErrorType() == null) {
+            final ErrorObject obj = err.getErrors().get(0).getErrorObject();
+            LOG.warn("Unexpected error received from PCC: type {} value {}", obj.getType(), obj.getValue());
+            negotiationFailed(new IllegalStateException("Unexpected error received from PCC."));
+            this.state = State.IDLE;
             return true;
         }
-        return false;
+        this.localPrefs = getRevisedProposal(((SessionCase) err.getErrorType()).getSession().getOpen());
+        if (this.localPrefs == null) {
+            sendErrorMessage(PCEPErrors.PCERR_NON_ACC_SESSION_CHAR);
+            negotiationFailed(new IllegalStateException("Peer suggested unacceptable retry proposal"));
+            this.state = State.FINISHED;
+            return true;
+        }
+        this.sendMessage(new OpenBuilder().setOpenMessage(new OpenMessageBuilder().setOpen(this.localPrefs).build()).build());
+        if (!this.remoteOK) {
+            this.state = State.OPEN_WAIT;
+        }
+        scheduleFailTimer();
+        return true;
     }
 
     private boolean handleMessageOpenWait(final Message msg) {
-        if (msg instanceof org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Open) {
-            final org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.open.message.OpenMessage o = ((org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Open) msg).getOpenMessage();
-            final Open open = o.getOpen();
-            if (isProposalAcceptable(open)) {
-                this.sendMessage(KEEPALIVE);
-                this.remotePrefs = open;
-                this.remoteOK = true;
-                if (this.localOK) {
-                    negotiationSuccessful(createSession(this.channel, this.localPrefs, this.remotePrefs));
-                    LOG.info("PCEP peer {} completed negotiation", this.channel);
-                    this.state = State.FINISHED;
-                } else {
-                    scheduleFailTimer();
-                    this.state = State.KEEP_WAIT;
-                    LOG.debug("Channel {} moved to KeepWait state with remoteOK=1", this.channel);
-                }
-                return true;
-            }
-
-            if (this.openRetry) {
-                sendErrorMessage(PCEPErrors.SECOND_OPEN_MSG);
-                negotiationFailed(new IllegalStateException("OPEN renegotiation failed"));
+        if (!(msg instanceof org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Open)) {
+            return false;
+        }
+        final org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.open.message.OpenMessage o = ((org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Open) msg).getOpenMessage();
+        final Open open = o.getOpen();
+        if (isProposalAcceptable(open)) {
+            this.sendMessage(KEEPALIVE);
+            this.remotePrefs = open;
+            this.remoteOK = true;
+            if (this.localOK) {
+                negotiationSuccessful(createSession(this.channel, this.localPrefs, this.remotePrefs));
+                LOG.info("PCEP peer {} completed negotiation", this.channel);
                 this.state = State.FINISHED;
-                return true;
+            } else {
+                scheduleFailTimer();
+                this.state = State.KEEP_WAIT;
+                LOG.debug("Channel {} moved to KeepWait state with remoteOK=1", this.channel);
             }
+            return true;
+        }
+        if (this.openRetry) {
+            sendErrorMessage(PCEPErrors.SECOND_OPEN_MSG);
+            negotiationFailed(new IllegalStateException("OPEN renegotiation failed"));
+            this.state = State.FINISHED;
+            return true;
+        }
+        final Open newPrefs = getCounterProposal(open);
+        if (newPrefs == null) {
+            sendErrorMessage(PCEPErrors.NON_ACC_NON_NEG_SESSION_CHAR);
+            negotiationFailed(new IllegalStateException("Peer sent unacceptable session parameters"));
+            this.state = State.FINISHED;
+            return true;
+        }
+        this.sendMessage(Util.createErrorMessage(PCEPErrors.NON_ACC_NEG_SESSION_CHAR, newPrefs));
+        this.openRetry = true;
+        this.state = this.localOK ? State.OPEN_WAIT : State.KEEP_WAIT;
+        scheduleFailTimer();
+        return true;
+    }
 
-            final Open newPrefs = getCounterProposal(open);
-            if (newPrefs == null) {
-                sendErrorMessage(PCEPErrors.NON_ACC_NON_NEG_SESSION_CHAR);
-                negotiationFailed(new IllegalStateException("Peer sent unacceptable session parameters"));
+    private boolean handleMessageStartTlsWait(final Message msg) {
+        if (msg instanceof Starttls) {
+            final SslContextFactory sslFactory = new SslContextFactory(this.tlsConfiguration);
+            final SSLContext sslContext = sslFactory.getServerContext();
+            if (sslContext == null) {
+                this.sendErrorMessage(PCEPErrors.NOT_POSSIBLE_WITHOUT_TLS);
+                negotiationFailed(new IllegalStateException("Failed to establish a TLS connection."));
                 this.state = State.FINISHED;
                 return true;
             }
-
-            this.sendMessage(Util.createErrorMessage(PCEPErrors.NON_ACC_NEG_SESSION_CHAR, newPrefs));
-
-            this.openRetry = true;
-            this.state = this.localOK ? State.OPEN_WAIT : State.KEEP_WAIT;
-            scheduleFailTimer();
+            final SSLEngine engine = sslContext.createSSLEngine();
+            engine.setNeedClientAuth(true);
+            engine.setUseClientMode(false);
+            this.channel.pipeline().addFirst(new SslHandler(engine));
+            LOG.info("PCEPS TLS connection with peer: {} established succesfully.", this.channel);
+            startNegotiationWithOpen();
+            return true;
+        } else if (!(msg instanceof Pcerr)) {
+            this.sendErrorMessage(PCEPErrors.NON_STARTTLS_MSG_RCVD);
+            negotiationFailed(new IllegalStateException("Unexpected message recieved."));
+            this.state = State.FINISHED;
             return true;
         }
         return false;
@@ -265,6 +323,11 @@ public abstract class AbstractPCEPSessionNegotiator extends AbstractSessionNegot
         case FINISHED:
         case IDLE:
             throw new IllegalStateException("Unexpected handleMessage in state " + this.state);
+        case START_TLS_WAIT:
+            if (handleMessageStartTlsWait(msg)) {
+                return;
+            }
+            break;
         case KEEP_WAIT:
             if (handleMessageKeepWait(msg)) {
                 return;
@@ -288,4 +351,15 @@ public abstract class AbstractPCEPSessionNegotiator extends AbstractSessionNegot
     State getState() {
         return this.state;
     }
+
+    public void setTlsConfiguration(final Tls tlsConfiguration) {
+        this.tlsConfiguration = tlsConfiguration;
+    }
+
+    @Override
+    protected void negotiationFailed(final Throwable cause) {
+        this.LOG.debug("Negotiation on channel {} failed", this.channel, cause);
+        this.channel.close();
+        this.promise.setFailure(cause);
+    }
 }