/* * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.protocol.pcep.pcc.mock.protocol; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.util.concurrent.Future; import java.math.BigInteger; import java.net.InetSocketAddress; import java.util.concurrent.ExecutionException; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.opendaylight.protocol.pcep.PCEPSession; import org.opendaylight.protocol.pcep.PCEPSessionListenerFactory; import org.opendaylight.protocol.pcep.PCEPSessionNegotiatorFactory; import org.opendaylight.protocol.pcep.impl.PCEPHandlerFactory; import org.opendaylight.protocol.pcep.pcc.mock.api.PCCDispatcher; import org.opendaylight.protocol.pcep.spi.MessageRegistry; import org.opendaylight.tcpmd5.api.DummyKeyAccessFactory; import org.opendaylight.tcpmd5.api.KeyAccessFactory; import org.opendaylight.tcpmd5.api.KeyMapping; import org.opendaylight.tcpmd5.jni.NativeKeyAccessFactory; import org.opendaylight.tcpmd5.jni.NativeSupportUnavailableException; import org.opendaylight.tcpmd5.netty.MD5ChannelFactory; import org.opendaylight.tcpmd5.netty.MD5ChannelOption; import org.opendaylight.tcpmd5.netty.MD5NioSocketChannelFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public final class PCCDispatcherImpl implements PCCDispatcher, AutoCloseable { private static final Logger LOG = LoggerFactory.getLogger(PCCDispatcherImpl.class); private static final int CONNECT_TIMEOUT = 2000; private final PCEPHandlerFactory factory; private final MD5ChannelFactory cf; private final NioEventLoopGroup workerGroup; public PCCDispatcherImpl(@Nonnull final MessageRegistry registry) { this.workerGroup = new NioEventLoopGroup(); this.factory = new PCEPHandlerFactory(registry); this.cf = new MD5NioSocketChannelFactory(DeafultKeyAccessFactory.getKeyAccessFactory()); } private static final class DeafultKeyAccessFactory { private static final Logger LOG = LoggerFactory.getLogger(DeafultKeyAccessFactory.class); private static final KeyAccessFactory FACTORY; static { KeyAccessFactory factory; try { factory = NativeKeyAccessFactory.getInstance(); } catch (final NativeSupportUnavailableException e) { LOG.debug("Native key access not available, using no-op fallback", e); factory = DummyKeyAccessFactory.getInstance(); } FACTORY = factory; } private DeafultKeyAccessFactory() { throw new UnsupportedOperationException("Utility class should never be instantiated"); } public static KeyAccessFactory getKeyAccessFactory() { return FACTORY; } } @Override public Future createClient(@Nonnull final InetSocketAddress remoteAddress, @Nonnull final long reconnectTime, @Nonnull final PCEPSessionListenerFactory listenerFactory, @Nonnull final PCEPSessionNegotiatorFactory negotiatorFactory, @Nonnull final KeyMapping keys, @Nullable final InetSocketAddress localAddress) { return createClient(remoteAddress, reconnectTime, listenerFactory, negotiatorFactory, keys, localAddress, BigInteger.ONE); } @Override public Future createClient(@Nonnull final InetSocketAddress remoteAddress, @Nonnull final long reconnectTime, @Nonnull final PCEPSessionListenerFactory listenerFactory, @Nonnull final PCEPSessionNegotiatorFactory negotiatorFactory, @Nonnull final KeyMapping keys, @Nullable final InetSocketAddress localAddress, @Nonnull final BigInteger dbVersion) { final Bootstrap b = new Bootstrap(); b.group(this.workerGroup); b.localAddress(localAddress); setChannelFactory(b, keys); b.option(ChannelOption.SO_KEEPALIVE, true); b.option(ChannelOption.MAX_MESSAGES_PER_READ, 1); final long retryTimer = reconnectTime == -1 ? 0 : reconnectTime; final PCCReconnectPromise promise = new PCCReconnectPromise(remoteAddress, (int) retryTimer, CONNECT_TIMEOUT, b); final ChannelInitializer channelInitializer = new ChannelInitializer() { @Override protected void initChannel(final SocketChannel ch) throws Exception { ch.pipeline().addLast(PCCDispatcherImpl.this.factory.getDecoders()); ch.pipeline().addLast("negotiator", negotiatorFactory.getSessionNegotiator(listenerFactory, ch, promise, new PCCPeerProposal(dbVersion))); ch.pipeline().addLast(PCCDispatcherImpl.this.factory.getEncoders()); ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { @Override public void channelInactive(final ChannelHandlerContext ctx) throws Exception { if (promise.isCancelled()) { return; } if (!promise.isInitialConnectFinished()) { LOG.debug("Connection to {} was dropped during negotiation, reattempting", remoteAddress); return; } LOG.debug("Reconnecting after connection to {} was dropped", remoteAddress); PCCDispatcherImpl.this.createClient(remoteAddress, reconnectTime, listenerFactory, negotiatorFactory, keys, localAddress, dbVersion); } }); } }; b.handler(channelInitializer); promise.connect(); return promise; } private void setChannelFactory(final Bootstrap bootstrap, final KeyMapping keys) { if (keys != null && !keys.isEmpty()) { if (this.cf == null) { throw new UnsupportedOperationException("No key access instance available, cannot use key mapping"); } LOG.debug("Adding MD5 keys {} to boostrap {}", keys, bootstrap); bootstrap.channelFactory(this.cf); bootstrap.option(MD5ChannelOption.TCP_MD5SIG, keys); } else { bootstrap.channel(NioSocketChannel.class); } } @Override public void close() { try { this.workerGroup.shutdownGracefully().get(); } catch (final InterruptedException | ExecutionException e) { LOG.warn("Failed to properly close dispatcher.", e); } } }