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