All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.graylog2.plugin.inputs.transports.NettyTransport Maven / Gradle / Ivy

There is a newer version: 6.0.2
Show newest version
/**
 * This file is part of Graylog.
 *
 * Graylog is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Graylog is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Graylog.  If not, see .
 */
package org.graylog2.plugin.inputs.transports;

import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.MetricSet;
import com.codahale.metrics.Timer;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.Callables;
import org.graylog2.plugin.LocalMetricRegistry;
import org.graylog2.plugin.MetricSets;
import org.graylog2.plugin.configuration.Configuration;
import org.graylog2.plugin.configuration.ConfigurationRequest;
import org.graylog2.plugin.inputs.MessageInput;
import org.graylog2.plugin.inputs.MisfireException;
import org.graylog2.plugin.inputs.codecs.CodecAggregator;
import org.graylog2.plugin.inputs.util.PacketInformationDumper;
import org.graylog2.plugin.inputs.util.ThroughputCounter;
import org.graylog2.plugin.journal.RawMessage;
import org.jboss.netty.bootstrap.Bootstrap;
import org.jboss.netty.bootstrap.ConnectionlessBootstrap;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandler;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
import org.jboss.netty.channel.socket.DatagramChannel;
import org.jboss.netty.channel.socket.DefaultDatagramChannelConfig;
import org.jboss.netty.channel.socket.ServerSocketChannelConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.Callable;

import static org.jboss.netty.channel.Channels.fireMessageReceived;

public abstract class NettyTransport implements Transport {
    public static final String CK_BIND_ADDRESS = "bind_address";
    public static final String CK_PORT = "port";
    public static final String CK_RECV_BUFFER_SIZE = "recv_buffer_size";

    private static final Logger log = LoggerFactory.getLogger(NettyTransport.class);

    protected final MetricRegistry localRegistry;

    private final InetSocketAddress socketAddress;
    protected final ThroughputCounter throughputCounter;
    private final long recvBufferSize;

    @Nullable
    private CodecAggregator aggregator;

    private Bootstrap bootstrap;
    private Channel acceptChannel;

    public NettyTransport(Configuration configuration,
                          ThroughputCounter throughputCounter,
                          LocalMetricRegistry localRegistry) {
        this.throughputCounter = throughputCounter;

        if (configuration.stringIsSet(CK_BIND_ADDRESS) && configuration.intIsSet(CK_PORT)) {
            this.socketAddress = new InetSocketAddress(
                    configuration.getString(CK_BIND_ADDRESS),
                    configuration.getInt(CK_PORT)
            );
        } else {
            this.socketAddress = null;
        }
        this.recvBufferSize = configuration.intIsSet(CK_RECV_BUFFER_SIZE)
                ? configuration.getInt(CK_RECV_BUFFER_SIZE)
                : MessageInput.getDefaultRecvBufferSize();

        this.localRegistry = localRegistry;
        localRegistry.registerAll(MetricSets.of(throughputCounter.gauges()));
    }

    private ChannelPipelineFactory getPipelineFactory(final LinkedHashMap> handlerList) {
        return new ChannelPipelineFactory() {
            @Override
            public ChannelPipeline getPipeline() throws Exception {
                final ChannelPipeline p = Channels.pipeline();
                for (final Map.Entry> entry : handlerList.entrySet()) {
                    p.addLast(entry.getKey(), entry.getValue().call());
                }
                return p;
            }
        };
    }

    @Override
    public void setMessageAggregator(@Nullable CodecAggregator aggregator) {
        this.aggregator = aggregator;
    }

    @Override
    public void launch(final MessageInput input) throws MisfireException {
        final LinkedHashMap> handlerList = getBaseChannelHandlers(input);
        final LinkedHashMap> finalHandlers = getFinalChannelHandlers(input);

        handlerList.putAll(finalHandlers);

        try {
            bootstrap = getBootstrap();
            bootstrap.setPipelineFactory(getPipelineFactory(handlerList));

            // sigh, bindable bootstraps do not share a common interface
            int receiveBufferSize;
            if (bootstrap instanceof ConnectionlessBootstrap) {
                acceptChannel = ((ConnectionlessBootstrap) bootstrap).bind(socketAddress);

                final DefaultDatagramChannelConfig channelConfig = (DefaultDatagramChannelConfig) acceptChannel.getConfig();
                receiveBufferSize = channelConfig.getReceiveBufferSize();
            } else if (bootstrap instanceof ServerBootstrap) {
                acceptChannel = ((ServerBootstrap) bootstrap).bind(socketAddress);

                final ServerSocketChannelConfig channelConfig = (ServerSocketChannelConfig) acceptChannel.getConfig();
                receiveBufferSize = channelConfig.getReceiveBufferSize();
            } else {
                log.error("Unknown Netty bootstrap class returned: {}. Cannot safely bind.", bootstrap);
                throw new IllegalStateException("Unknown netty bootstrap class returned: " + bootstrap + ". Cannot safely bind.");
            }

            if (receiveBufferSize != getRecvBufferSize()) {
                log.warn("receiveBufferSize (SO_RCVBUF) for input {} should be {} but is {}.",
                        input, getRecvBufferSize(), receiveBufferSize);
            }
        } catch (Exception e) {
            throw new MisfireException(e);
        }
    }

    @Override
    public void stop() {
        if (acceptChannel != null && acceptChannel.isOpen()) {
            acceptChannel.close();
        }
        if (bootstrap != null) {
            bootstrap.shutdown();
        }
    }

    /**
     * Construct a {@link org.jboss.netty.bootstrap.ServerBootstrap} to use with this transport.
     * 

* Set all the options on it you need to have, but do not set a {@link org.jboss.netty.channel.ChannelPipelineFactory}, it will be replaced with the * augmented list of handlers returned by {@link #getBaseChannelHandlers(org.graylog2.plugin.inputs.MessageInput)} * * @return a configured ServerBootstrap for this transport */ protected abstract Bootstrap getBootstrap(); /** * Subclasses can override this to add additional ChannelHandlers to the pipeline to support additional features. *

* Some common use cases are to add SSL/TLS, connection counters or throttling traffic shapers. * * @param input * @return the list of initial channelhandlers to add to the {@link org.jboss.netty.channel.ChannelPipelineFactory} */ protected LinkedHashMap> getBaseChannelHandlers(final MessageInput input) { LinkedHashMap> handlerList = Maps.newLinkedHashMap(); handlerList.put("exception-logger", new Callable() { @Override public ChannelHandler call() throws Exception { return new SimpleChannelUpstreamHandler() { @SuppressWarnings("ThrowableResultOfMethodCallIgnored") @Override public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { if ("Connection reset by peer".equals(e.getCause().getMessage())) { log.trace("{} in Input [{}/{}] (channel {})", e.getCause().getMessage(), input.getName(), input.getId(), e.getChannel()); } else { log.error("Error in Input [{}/{}] (channel {})", input.getName(), input.getId(), e.getChannel(), e.getCause()); } super.exceptionCaught(ctx, e); } }; } }); handlerList.put("packet-meta-dumper", new Callable() { @Override public ChannelHandler call() throws Exception { return new PacketInformationDumper(input); } }); handlerList.put("traffic-counter", Callables.returning(throughputCounter)); return handlerList; } /** * Subclasses can override this to modify the {@link org.jboss.netty.channel.ChannelHandler channel handlers} at the end of the pipeline. *

* The default handlers in this group are the aggregation handler (e.g. for chunked GELF via UDP), which can be missing, and the {@link NettyTransport.RawMessageHandler}. *

* Usually this should not be necessary, only modify them if you have a codec that cannot create a {@link org.graylog2.plugin.journal.RawMessage} for * incoming messages at the end of the pipeline. *

* One valid use case would be to insert debug handlers in the middle of the list, though. * * @param input * @return the list of channel handlers at the end of the pipeline */ protected LinkedHashMap> getFinalChannelHandlers(final MessageInput input) { LinkedHashMap> handlerList = Maps.newLinkedHashMap(); if (aggregator != null) { log.debug("Adding codec aggregator {} to channel pipeline", aggregator); handlerList.put("codec-aggregator", new Callable() { @Override public ChannelHandler call() throws Exception { return new MessageAggregationHandler(aggregator); } }); } handlerList.put("rawmessage-handler", new Callable() { @Override public ChannelHandler call() throws Exception { return new RawMessageHandler(input); } }); return handlerList; } protected long getRecvBufferSize() { return recvBufferSize; } /** * Get the local socket address this transport is listening on after being launched. * * @return the listening address of this transport or {@code null} if the transport hasn't been launched yet. */ public SocketAddress getLocalAddress() { if (acceptChannel == null || !acceptChannel.isBound()) { return null; } return acceptChannel.getLocalAddress(); } @Override public MetricSet getMetricSet() { return localRegistry; } private class MessageAggregationHandler extends SimpleChannelHandler { private final CodecAggregator aggregator; private final Timer aggregationTimer; private final Meter invalidChunksMeter; public MessageAggregationHandler(CodecAggregator aggregator) { this.aggregator = aggregator; aggregationTimer = localRegistry.timer("aggregationTime"); invalidChunksMeter = localRegistry.meter("invalidMessages"); } @Override public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { final Object message = e.getMessage(); if (message instanceof ChannelBuffer) { final ChannelBuffer buf = (ChannelBuffer) message; final CodecAggregator.Result result; try (Timer.Context ignored = aggregationTimer.time()) { result = aggregator.addChunk(buf); } final ChannelBuffer completeMessage = result.getMessage(); if (completeMessage != null) { log.debug("Message aggregation completion, forwarding {}", completeMessage); fireMessageReceived(ctx, completeMessage); } else if (result.isValid()) { log.debug("More chunks necessary to complete this message"); } else { invalidChunksMeter.mark(); log.debug("Message chunk was not valid and discarded."); } } else { log.debug("Could not handle netty message {}, sending further upstream.", e); fireMessageReceived(ctx, message); } } } private class RawMessageHandler extends SimpleChannelHandler { private final MessageInput input; public RawMessageHandler(MessageInput input) { this.input = input; } @Override public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { final Object msg = e.getMessage(); if (!(msg instanceof ChannelBuffer)) { log.error( "Invalid message type received from transport pipeline. Should be ChannelBuffer but was {}. Discarding message.", msg.getClass()); return; } final ChannelBuffer buffer = (ChannelBuffer) msg; final byte[] payload = new byte[buffer.readableBytes()]; buffer.toByteBuffer().get(payload, buffer.readerIndex(), buffer.readableBytes()); final RawMessage raw = new RawMessage(payload, (InetSocketAddress) e.getRemoteAddress()); input.processRawMessage(raw); } @Override public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { log.debug("Could not handle message, closing connection: {}", e); if (ctx.getChannel() != null && !(ctx.getChannel() instanceof DatagramChannel)) { ctx.getChannel().close(); } } } public static class Config implements Transport.Config { @Override public ConfigurationRequest getRequestedConfiguration() { final ConfigurationRequest r = new ConfigurationRequest(); r.addField(ConfigurationRequest.Templates.bindAddress(CK_BIND_ADDRESS)); r.addField(ConfigurationRequest.Templates.portNumber(CK_PORT, 5555)); r.addField(ConfigurationRequest.Templates.recvBufferSize(CK_RECV_BUFFER_SIZE, 1024 * 1024)); return r; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy