2 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.protocol.pcep.impl;
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;
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.
46 public abstract class AbstractPCEPSessionNegotiator extends AbstractSessionNegotiator {
48 * Unified KeepWait and OpenWait timer expiration, in seconds.
50 public static final int FAIL_TIMER_VALUE = 60;
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.
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.
66 * Waiting for the peer's StartTLS message
70 * Waiting for the peer's OPEN message.
74 * Waiting for the peer's KEEPALIVE message.
78 * Negotiation has completed.
83 private static final Logger LOG = LoggerFactory.getLogger(AbstractPCEPSessionNegotiator.class);
84 private static final Keepalive KEEPALIVE = new KeepaliveBuilder().setKeepaliveMessage(new KeepaliveMessageBuilder().build()).build();
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;
95 protected AbstractPCEPSessionNegotiator(final Promise<PCEPSessionImpl> promise, final Channel channel) {
96 super(promise, channel);
100 * Get the initial session parameters proposal.
102 * @return Session parameters proposal.
104 protected abstract Open getInitialProposal();
107 * Get the revised session parameters proposal based on the feedback the peer has provided to us.
109 * @param suggestion Peer-provided suggested session parameters
110 * @return Session parameters proposal, or null if peers session parameters preclude us from suggesting anything
112 protected abstract Open getRevisedProposal(Open suggestion);
115 * Check whether a peer-provided session parameters proposal is acceptable.
117 * @param proposal peer-proposed session parameters
118 * @return true if the proposal is acceptable, false otherwise
120 protected abstract boolean isProposalAcceptable(Open proposal);
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.
126 * @param proposal unacceptable peer proposal
127 * @return our counter-proposal, or null if there is no way to negotiate an acceptable proposal
129 protected abstract Open getCounterProposal(Open proposal);
132 * Create the protocol session.
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.
139 protected abstract PCEPSessionImpl createSession(Channel channel, Open localPrefs, Open remotePrefs);
142 * Sends PCEP Error Message with one PCEPError.
146 private void sendErrorMessage(final PCEPErrors value) {
148 this.sendMessage(Util.createErrorMessage(value, null));
151 private void scheduleFailTimer() {
152 this.failTimer = this.channel.eventLoop().schedule(() -> {
153 switch (AbstractPCEPSessionNegotiator.this.state) {
158 sendErrorMessage(PCEPErrors.STARTTLS_TIMER_EXP);
159 negotiationFailed(new TimeoutException("StartTLSWait timer expired"));
160 AbstractPCEPSessionNegotiator.this.state = State.FINISHED;
163 sendErrorMessage(PCEPErrors.NO_MSG_BEFORE_EXP_KEEPWAIT);
164 negotiationFailed(new TimeoutException("KeepWait timer expired"));
165 AbstractPCEPSessionNegotiator.this.state = State.FINISHED;
168 sendErrorMessage(PCEPErrors.NO_OPEN_BEFORE_EXP_OPENWAIT);
169 negotiationFailed(new TimeoutException("OpenWait timer expired"));
170 AbstractPCEPSessionNegotiator.this.state = State.FINISHED;
175 }, FAIL_TIMER_VALUE, TimeUnit.SECONDS);
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;
185 LOG.info("Started TLS connection negotiation with peer {}", this.channel);
187 startNegotiationWithOpen();
189 this.channel.closeFuture().addListener((ChannelFutureListener) f -> cancelTimers());
192 private void cancelTimers() {
193 this.failTimer.cancel(false);
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();
201 this.state = State.OPEN_WAIT;
204 LOG.info("PCEP session with {} started, sent proposal {}", this.channel, this.localPrefs);
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);
216 private boolean handleMessageKeepAlive() {
219 LOG.info("PCEP peer {} completed negotiation", this.channel);
220 negotiationSuccessful(createSession(this.channel, this.localPrefs, this.remotePrefs));
221 this.state = State.FINISHED;
224 this.state = State.OPEN_WAIT;
225 LOG.debug("Channel {} moved to OpenWait state with localOK=1", this.channel);
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;
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;
246 this.sendMessage(new OpenBuilder().setOpenMessage(new OpenMessageBuilder().setOpen(this.localPrefs).build()).build());
247 if (!this.remoteOK) {
248 this.state = State.OPEN_WAIT;
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)) {
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;
265 negotiationSuccessful(createSession(this.channel, this.localPrefs, this.remotePrefs));
266 LOG.info("PCEP peer {} completed negotiation", this.channel);
267 this.state = State.FINISHED;
270 this.state = State.KEEP_WAIT;
271 LOG.debug("Channel {} moved to KeepWait state with remoteOK=1", this.channel);
275 if (this.openRetry) {
276 sendErrorMessage(PCEPErrors.SECOND_OPEN_MSG);
277 negotiationFailed(new IllegalStateException("OPEN renegotiation failed"));
278 this.state = State.FINISHED;
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;
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;
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;
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();
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;
322 protected final void handleMessage(final Message msg) {
325 LOG.debug("Channel {} handling message {} in state {}", this.channel, msg, this.state);
327 switch (this.state) {
330 throw new IllegalStateException("Unexpected handleMessage in state " + this.state);
332 if (handleMessageStartTlsWait(msg)) {
337 if (handleMessageKeepWait(msg)) {
342 if (handleMessageOpenWait(msg)) {
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;
360 public void setTlsConfiguration(final Tls tlsConfiguration) {
361 this.tlsConfiguration = tlsConfiguration;
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);