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