bf859614097fc2971ec6bcdf06a9a5a7c41178f7
[bgpcep.git] / pcep / impl / src / main / java / org / opendaylight / protocol / pcep / impl / AbstractPCEPSessionNegotiator.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.protocol.pcep.impl;
9
10 import com.google.common.annotations.VisibleForTesting;
11 import com.google.common.base.Preconditions;
12 import io.netty.channel.Channel;
13 import io.netty.handler.ssl.SslHandler;
14 import io.netty.util.concurrent.Promise;
15 import java.util.concurrent.Future;
16 import java.util.concurrent.TimeUnit;
17 import java.util.concurrent.TimeoutException;
18 import javax.net.ssl.SSLContext;
19 import javax.net.ssl.SSLEngine;
20 import org.opendaylight.controller.config.yang.pcep.impl.Tls;
21 import org.opendaylight.protocol.framework.AbstractSessionNegotiator;
22 import org.opendaylight.protocol.pcep.impl.tls.SslContextFactory;
23 import org.opendaylight.protocol.pcep.spi.PCEPErrors;
24 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Keepalive;
25 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.KeepaliveBuilder;
26 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.OpenBuilder;
27 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Pcerr;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Starttls;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.StarttlsBuilder;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.Message;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.OpenMessage;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.keepalive.message.KeepaliveMessageBuilder;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.open.message.OpenMessageBuilder;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.open.object.Open;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.pcep.error.object.ErrorObject;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.pcerr.message.pcerr.message.error.type.SessionCase;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.start.tls.message.StartTlsMessageBuilder;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 /**
42  * Abstract PCEP session negotiator. Takes care of basic handshake without implementing a specific policy. Policies need
43  * to be provided by a specific subclass.
44  */
45 public abstract class AbstractPCEPSessionNegotiator extends AbstractSessionNegotiator<Message, PCEPSessionImpl> {
46     /**
47      * Unified KeepWait and OpenWait timer expiration, in seconds.
48      */
49     public static final int FAIL_TIMER_VALUE = 60;
50
51     /**
52      * PCEP session negotiation state transitions are described in RFC5440. Simplification the two timers (KeepWait and
53      * OpenWait) are merged into a FailTimer, as they are mutually exclusive, have the same timeout value and their
54      * action is to terminate negotiation. This timer is restarted between state transitions and runs in all states
55      * except Idle and Finished.
56      */
57     @VisibleForTesting
58     public enum State {
59         /**
60          * Negotiation has not begun. It will be activated once we are asked to provide our initial proposal, at which
61          * point we move into OpenWait state.
62          */
63         IDLE,
64         /**
65          * Waiting for the peer's StartTLS message
66          */
67         START_TLS_WAIT,
68         /**
69          * Waiting for the peer's OPEN message.
70          */
71         OPEN_WAIT,
72         /**
73          * Waiting for the peer's KEEPALIVE message.
74          */
75         KEEP_WAIT,
76         /**
77          * Negotiation has completed.
78          */
79         FINISHED,
80     }
81
82     private static final Logger LOG = LoggerFactory.getLogger(AbstractPCEPSessionNegotiator.class);
83     private static final Keepalive KEEPALIVE = new KeepaliveBuilder().setKeepaliveMessage(new KeepaliveMessageBuilder().build()).build();
84
85     private volatile boolean localOK, openRetry, remoteOK;
86     private volatile State state = State.IDLE;
87     private Future<?> failTimer;
88     private Open localPrefs;
89     private Open remotePrefs;
90     private Tls tlsConfiguration;
91
92     protected AbstractPCEPSessionNegotiator(final Promise<PCEPSessionImpl> promise, final Channel channel) {
93         super(promise, channel);
94     }
95
96     /**
97      * Get the initial session parameters proposal.
98      *
99      * @return Session parameters proposal.
100      */
101     protected abstract Open getInitialProposal();
102
103     /**
104      * Get the revised session parameters proposal based on the feedback the peer has provided to us.
105      *
106      * @param suggestion Peer-provided suggested session parameters
107      * @return Session parameters proposal, or null if peers session parameters preclude us from suggesting anything
108      */
109     protected abstract Open getRevisedProposal(Open suggestion);
110
111     /**
112      * Check whether a peer-provided session parameters proposal is acceptable.
113      *
114      * @param proposal peer-proposed session parameters
115      * @return true if the proposal is acceptable, false otherwise
116      */
117     protected abstract boolean isProposalAcceptable(Open proposal);
118
119     /**
120      * Given a peer-provided session parameters proposal which we found unacceptable, provide a counter-proposal. The
121      * requirement is that the isProposalAcceptable() method has to return true when presented with this proposal.
122      *
123      * @param proposal unacceptable peer proposal
124      * @return our counter-proposal, or null if there is no way to negotiate an acceptable proposal
125      */
126     protected abstract Open getCounterProposal(Open proposal);
127
128     /**
129      * Create the protocol session.
130      *
131      * @param channel Underlying channel.
132      * @param localPrefs Session preferences proposed by us and accepted by the peer.
133      * @param remotePrefs Session preferences proposed by the peer and accepted by us.
134      * @return New protocol session.
135      */
136     protected abstract PCEPSessionImpl createSession(Channel channel, Open localPrefs, Open remotePrefs);
137
138     /**
139      * Sends PCEP Error Message with one PCEPError.
140      *
141      * @param value
142      */
143     private void sendErrorMessage(final PCEPErrors value) {
144
145         this.sendMessage(Util.createErrorMessage(value, null));
146     }
147
148     private void scheduleFailTimer() {
149         this.failTimer = this.channel.eventLoop().schedule(new Runnable() {
150             @Override
151             public void run() {
152                 switch (AbstractPCEPSessionNegotiator.this.state) {
153                 case FINISHED:
154                 case IDLE:
155                     break;
156                 case START_TLS_WAIT:
157                     sendErrorMessage(PCEPErrors.STARTTLS_TIMER_EXP);
158                     negotiationFailed(new TimeoutException("StartTLSWait timer expired"));
159                     AbstractPCEPSessionNegotiator.this.state = State.FINISHED;
160                 case KEEP_WAIT:
161                     sendErrorMessage(PCEPErrors.NO_MSG_BEFORE_EXP_KEEPWAIT);
162                     negotiationFailed(new TimeoutException("KeepWait timer expired"));
163                     AbstractPCEPSessionNegotiator.this.state = State.FINISHED;
164                     break;
165                 case OPEN_WAIT:
166                     sendErrorMessage(PCEPErrors.NO_OPEN_BEFORE_EXP_OPENWAIT);
167                     negotiationFailed(new TimeoutException("OpenWait timer expired"));
168                     AbstractPCEPSessionNegotiator.this.state = State.FINISHED;
169                     break;
170                 default:
171                     break;
172                 }
173             }
174         }, FAIL_TIMER_VALUE, TimeUnit.SECONDS);
175     }
176
177     @Override
178     protected final void startNegotiation() {
179         Preconditions.checkState(this.state == State.IDLE);
180         if (this.tlsConfiguration != null) {
181             this.sendMessage(new StarttlsBuilder().setStartTlsMessage(new StartTlsMessageBuilder().build()).build());
182             this.state = State.START_TLS_WAIT;
183             scheduleFailTimer();
184             LOG.info("Started TLS connection negotiation with peer {}", this.channel);
185         } else {
186             startNegotiationWithOpen();
187         }
188     }
189
190     private void startNegotiationWithOpen() {
191         this.localPrefs = getInitialProposal();
192         final OpenMessage m = new org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.OpenBuilder().setOpenMessage(
193                 new OpenMessageBuilder().setOpen(this.localPrefs).build()).build();
194         this.sendMessage(m);
195         this.state = State.OPEN_WAIT;
196         scheduleFailTimer();
197
198         LOG.info("PCEP session with {} started, sent proposal {}", this.channel, this.localPrefs);
199     }
200
201     private boolean handleMessageKeepWait(final Message msg) {
202         if (msg instanceof Keepalive) {
203             this.localOK = true;
204             if (this.remoteOK) {
205                 LOG.info("PCEP peer {} completed negotiation", this.channel);
206                 negotiationSuccessful(createSession(this.channel, this.localPrefs, this.remotePrefs));
207                 this.state = State.FINISHED;
208             } else {
209                 scheduleFailTimer();
210                 this.state = State.OPEN_WAIT;
211                 LOG.debug("Channel {} moved to OpenWait state with localOK=1", this.channel);
212             }
213             return true;
214         } else if (msg instanceof Pcerr) {
215             final org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.pcerr.message.PcerrMessage err = ((Pcerr) msg).getPcerrMessage();
216             if (err.getErrorType() == null) {
217                 final ErrorObject obj = err.getErrors().get(0).getErrorObject();
218                 LOG.warn("Unexpected error received from PCC: type {} value {}", obj.getType(), obj.getValue());
219                 negotiationFailed(new IllegalStateException("Unexpected error received from PCC."));
220                 this.state = State.IDLE;
221                 return true;
222             }
223             this.localPrefs = getRevisedProposal(((SessionCase) err.getErrorType()).getSession().getOpen());
224             if (this.localPrefs == null) {
225                 sendErrorMessage(PCEPErrors.PCERR_NON_ACC_SESSION_CHAR);
226                 negotiationFailed(new IllegalStateException("Peer suggested unacceptable retry proposal"));
227                 this.state = State.FINISHED;
228                 return true;
229             }
230             this.sendMessage(new OpenBuilder().setOpenMessage(new OpenMessageBuilder().setOpen(this.localPrefs).build()).build());
231             if (!this.remoteOK) {
232                 this.state = State.OPEN_WAIT;
233             }
234             scheduleFailTimer();
235             return true;
236         }
237         return false;
238     }
239
240     private boolean handleMessageOpenWait(final Message msg) {
241         if (!(msg instanceof org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Open)) {
242             return false;
243         }
244         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();
245         final Open open = o.getOpen();
246         if (isProposalAcceptable(open)) {
247             this.sendMessage(KEEPALIVE);
248             this.remotePrefs = open;
249             this.remoteOK = true;
250             if (this.localOK) {
251                 negotiationSuccessful(createSession(this.channel, this.localPrefs, this.remotePrefs));
252                 LOG.info("PCEP peer {} completed negotiation", this.channel);
253                 this.state = State.FINISHED;
254             } else {
255                 scheduleFailTimer();
256                 this.state = State.KEEP_WAIT;
257                 LOG.debug("Channel {} moved to KeepWait state with remoteOK=1", this.channel);
258             }
259             return true;
260         }
261         if (this.openRetry) {
262             sendErrorMessage(PCEPErrors.SECOND_OPEN_MSG);
263             negotiationFailed(new IllegalStateException("OPEN renegotiation failed"));
264             this.state = State.FINISHED;
265             return true;
266         }
267         final Open newPrefs = getCounterProposal(open);
268         if (newPrefs == null) {
269             sendErrorMessage(PCEPErrors.NON_ACC_NON_NEG_SESSION_CHAR);
270             negotiationFailed(new IllegalStateException("Peer sent unacceptable session parameters"));
271             this.state = State.FINISHED;
272             return true;
273         }
274         this.sendMessage(Util.createErrorMessage(PCEPErrors.NON_ACC_NEG_SESSION_CHAR, newPrefs));
275         this.openRetry = true;
276         this.state = this.localOK ? State.OPEN_WAIT : State.KEEP_WAIT;
277         scheduleFailTimer();
278         return true;
279     }
280
281     private boolean handleMessageStartTlsWait(final Message msg) {
282         if (msg instanceof Starttls) {
283             final SslContextFactory sslFactory = new SslContextFactory(this.tlsConfiguration);
284             final SSLContext sslContext = sslFactory.getServerContext();
285             if (sslContext == null) {
286                 this.sendErrorMessage(PCEPErrors.NOT_POSSIBLE_WITHOUT_TLS);
287                 negotiationFailed(new IllegalStateException("Failed to establish a TLS connection."));
288                 this.state = State.FINISHED;
289                 return true;
290             }
291             final SSLEngine engine = sslContext.createSSLEngine();
292             engine.setNeedClientAuth(true);
293             engine.setUseClientMode(false);
294             this.channel.pipeline().addFirst(new SslHandler(engine));
295             LOG.info("PCEPS TLS connection with peer: {} established succesfully.", this.channel);
296             startNegotiationWithOpen();
297             return true;
298         } else if (! (msg instanceof Pcerr)) {
299             this.sendErrorMessage(PCEPErrors.NON_STARTTLS_MSG_RCVD);
300             negotiationFailed(new IllegalStateException("Unexpected message recieved."));
301             this.state = State.FINISHED;
302             return true;
303         }
304         return false;
305     }
306
307     @Override
308     protected final void handleMessage(final Message msg) {
309         this.failTimer.cancel(false);
310
311         LOG.debug("Channel {} handling message {} in state {}", this.channel, msg, this.state);
312
313         switch (this.state) {
314         case FINISHED:
315         case IDLE:
316             throw new IllegalStateException("Unexpected handleMessage in state " + this.state);
317         case START_TLS_WAIT:
318             if (handleMessageStartTlsWait(msg)) {
319                 return;
320             }
321             break;
322         case KEEP_WAIT:
323             if (handleMessageKeepWait(msg)) {
324                 return;
325             }
326             break;
327         case OPEN_WAIT:
328             if (handleMessageOpenWait(msg)) {
329                 return;
330             }
331             break;
332         default:
333             break;
334         }
335         LOG.warn("Channel {} in state {} received unexpected message {}", this.channel, this.state, msg);
336         sendErrorMessage(PCEPErrors.NON_OR_INVALID_OPEN_MSG);
337         negotiationFailed(new Exception("Illegal message encountered"));
338         this.state = State.FINISHED;
339     }
340
341     @VisibleForTesting
342     State getState() {
343         return this.state;
344     }
345
346     public void setTlsConfiguration(final Tls tlsConfiguration) {
347         this.tlsConfiguration = tlsConfiguration;
348     }
349 }