
org.apache.cassandra.net.InboundConnectionInitiator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cassandra-all Show documentation
Show all versions of cassandra-all Show documentation
The Apache Cassandra Project develops a highly scalable second-generation distributed database, bringing together Dynamo's fully distributed design and Bigtable's ColumnFamily-based data model.
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.net;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.List;
import java.util.concurrent.Future;
import java.util.function.Consumer;
import com.google.common.annotations.VisibleForTesting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslHandler;
import org.apache.cassandra.config.EncryptionOptions;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.net.OutboundConnectionSettings.Framing;
import org.apache.cassandra.security.SSLFactory;
import org.apache.cassandra.streaming.async.StreamingInboundHandler;
import org.apache.cassandra.utils.memory.BufferPools;
import static java.lang.Math.*;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.apache.cassandra.net.MessagingService.*;
import static org.apache.cassandra.net.MessagingService.VERSION_40;
import static org.apache.cassandra.net.MessagingService.current_version;
import static org.apache.cassandra.net.MessagingService.minimum_version;
import static org.apache.cassandra.net.SocketFactory.WIRETRACE;
import static org.apache.cassandra.net.SocketFactory.newSslHandler;
public class InboundConnectionInitiator
{
private static final Logger logger = LoggerFactory.getLogger(InboundConnectionInitiator.class);
private static class Initializer extends ChannelInitializer
{
private final InboundConnectionSettings settings;
private final ChannelGroup channelGroup;
private final Consumer pipelineInjector;
Initializer(InboundConnectionSettings settings, ChannelGroup channelGroup,
Consumer pipelineInjector)
{
this.settings = settings;
this.channelGroup = channelGroup;
this.pipelineInjector = pipelineInjector;
}
@Override
public void initChannel(SocketChannel channel) throws Exception
{
channelGroup.add(channel);
channel.config().setOption(ChannelOption.ALLOCATOR, GlobalBufferPoolAllocator.instance);
channel.config().setOption(ChannelOption.SO_KEEPALIVE, true);
channel.config().setOption(ChannelOption.SO_REUSEADDR, true);
channel.config().setOption(ChannelOption.TCP_NODELAY, true); // we only send handshake messages; no point ever delaying
ChannelPipeline pipeline = channel.pipeline();
pipelineInjector.accept(pipeline);
// order of handlers: ssl -> logger -> handshakeHandler
// For either unencrypted or transitional modes, allow Ssl optionally.
switch(settings.encryption.tlsEncryptionPolicy())
{
case UNENCRYPTED:
// Handler checks for SSL connection attempts and cleanly rejects them if encryption is disabled
pipeline.addFirst("rejectssl", new RejectSslHandler());
break;
case OPTIONAL:
pipeline.addFirst("ssl", new OptionalSslHandler(settings.encryption));
break;
case ENCRYPTED:
SslHandler sslHandler = getSslHandler("creating", channel, settings.encryption);
pipeline.addFirst("ssl", sslHandler);
break;
}
if (WIRETRACE)
pipeline.addLast("logger", new LoggingHandler(LogLevel.INFO));
channel.pipeline().addLast("handshake", new Handler(settings));
}
}
/**
* Create a {@link Channel} that listens on the {@code localAddr}. This method will block while trying to bind to the address,
* but it does not make a remote call.
*/
private static ChannelFuture bind(Initializer initializer) throws ConfigurationException
{
logger.info("Listening on {}", initializer.settings);
ServerBootstrap bootstrap = initializer.settings.socketFactory
.newServerBootstrap()
.option(ChannelOption.SO_BACKLOG, 1 << 9)
.option(ChannelOption.ALLOCATOR, GlobalBufferPoolAllocator.instance)
.option(ChannelOption.SO_REUSEADDR, true)
.childHandler(initializer);
int socketReceiveBufferSizeInBytes = initializer.settings.socketReceiveBufferSizeInBytes;
if (socketReceiveBufferSizeInBytes > 0)
bootstrap.childOption(ChannelOption.SO_RCVBUF, socketReceiveBufferSizeInBytes);
InetAddressAndPort bind = initializer.settings.bindAddress;
ChannelFuture channelFuture = bootstrap.bind(new InetSocketAddress(bind.address, bind.port));
if (!channelFuture.awaitUninterruptibly().isSuccess())
{
if (channelFuture.channel().isOpen())
channelFuture.channel().close();
Throwable failedChannelCause = channelFuture.cause();
String causeString = "";
if (failedChannelCause != null && failedChannelCause.getMessage() != null)
causeString = failedChannelCause.getMessage();
if (causeString.contains("in use"))
{
throw new ConfigurationException(bind + " is in use by another process. Change listen_address:storage_port " +
"in cassandra.yaml to values that do not conflict with other services");
}
// looking at the jdk source, solaris/windows bind failue messages both use the phrase "cannot assign requested address".
// windows message uses "Cannot" (with a capital 'C'), and solaris (a/k/a *nux) doe not. hence we search for "annot"
else if (causeString.contains("annot assign requested address"))
{
throw new ConfigurationException("Unable to bind to address " + bind
+ ". Set listen_address in cassandra.yaml to an interface you can bind to, e.g., your private IP address on EC2");
}
else
{
throw new ConfigurationException("failed to bind to: " + bind, failedChannelCause);
}
}
return channelFuture;
}
public static ChannelFuture bind(InboundConnectionSettings settings, ChannelGroup channelGroup,
Consumer pipelineInjector)
{
return bind(new Initializer(settings, channelGroup, pipelineInjector));
}
/**
* 'Server-side' component that negotiates the internode handshake when establishing a new connection.
* This handler will be the first in the netty channel for each incoming connection (secure socket (TLS) notwithstanding),
* and once the handshake is successful, it will configure the proper handlers ({@link InboundMessageHandler}
* or {@link StreamingInboundHandler}) and remove itself from the working pipeline.
*/
static class Handler extends ByteToMessageDecoder
{
private final InboundConnectionSettings settings;
private HandshakeProtocol.Initiate initiate;
private HandshakeProtocol.ConfirmOutboundPre40 confirmOutboundPre40;
/**
* A future the essentially places a timeout on how long we'll wait for the peer
* to complete the next step of the handshake.
*/
private Future> handshakeTimeout;
Handler(InboundConnectionSettings settings)
{
this.settings = settings;
}
/**
* On registration, immediately schedule a timeout to kill this connection if it does not handshake promptly,
* and authenticate the remote address.
*/
public void handlerAdded(ChannelHandlerContext ctx) throws Exception
{
handshakeTimeout = ctx.executor().schedule(() -> {
logger.error("Timeout handshaking with {} (on {})", SocketFactory.addressId(initiate.from, (InetSocketAddress) ctx.channel().remoteAddress()), settings.bindAddress);
failHandshake(ctx);
}, HandshakeProtocol.TIMEOUT_MILLIS, MILLISECONDS);
authenticate(ctx.channel().remoteAddress());
}
private void authenticate(SocketAddress socketAddress) throws IOException
{
if (socketAddress.getClass().getSimpleName().equals("EmbeddedSocketAddress"))
return;
if (!(socketAddress instanceof InetSocketAddress))
throw new IOException(String.format("Unexpected SocketAddress type: %s, %s", socketAddress.getClass(), socketAddress));
InetSocketAddress addr = (InetSocketAddress)socketAddress;
if (!settings.authenticate(addr.getAddress(), addr.getPort()))
throw new IOException("Authentication failure for inbound connection from peer " + addr);
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List
© 2015 - 2024 Weber Informatics LLC | Privacy Policy