Нетти - как пережить DDOS?

Я использую netty 4.1 в качестве сервера сокетов NIO для игры MMORPG. Он отлично работал в течение многих лет, но в последнее время мы страдаем от DDOS-атак. Я долго боролся с этим, но в настоящее время у меня больше нет идей о том, как я могу его улучшить. Ddoser рассылает спам новыми соединениями с тысяч IP-адресов со всего мира. На сетевом уровне это сложно вырезать, потому что атаки очень похожи на обычных игроков. Атаки не очень велики по сравнению с атаками на HTTP-серверы, но достаточно велики, чтобы привести к краху нашей игры.

Как я использую нетти:

public void startServer() {

    bossGroup = new NioEventLoopGroup(1);
    workerGroup = new NioEventLoopGroup();

    try {
        int timeout = (Settings.SOCKET_TIMEOUT*1000);
        bootstrap = new ServerBootstrap();

        int bufferSize = 65536;
        bootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                .childOption(ChannelOption.SO_TIMEOUT, timeout)
                .childOption(ChannelOption.SO_RCVBUF, bufferSize)
                .childOption(ChannelOption.SO_SNDBUF, bufferSize)
                .handler(new LoggingHandler(LogLevel.INFO))
                .childHandler(new CustomInitalizer(sslCtx));


        ChannelFuture bind = bootstrap.bind(DrServerAdmin.port);
        bossChannel = bind.sync();

    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        bossGroup.shutdownGracefully();
        workerGroup.shutdownGracefully();
    }
}

Инициализатор:

public class CustomInitalizer extends ChannelInitializer<SocketChannel> {

    public static  DefaultEventExecutorGroup normalGroup = new DefaultEventExecutorGroup(16);
    public static  DefaultEventExecutorGroup loginGroup = new DefaultEventExecutorGroup(8);
    public static  DefaultEventExecutorGroup commandsGroup = new DefaultEventExecutorGroup(4);

    private final SslContext sslCtx;

    public CustomInitalizer(SslContext sslCtx) {
        this.sslCtx = sslCtx;
    }

    @Override
    public void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();

        if (sslCtx != null) {
            pipeline.addLast(sslCtx.newHandler(ch.alloc()));
        }

        pipeline.addLast(new CustomFirewall()); //it is AbstractRemoteAddressFilter<InetSocketAddress>
        int limit = 32768;        
        pipeline.addLast(new DelimiterBasedFrameDecoder(limit, Delimiters.nulDelimiter()));
        pipeline.addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
        pipeline.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));

        pipeline.addLast(new CustomReadTimeoutHandler(Settings.SOCKET_TIMEOUT));

        int id = DrServerNetty.getDrServer().getIdClient();
        CustomHandler normalHandler = new CustomHandler();
        FlashClientNetty client = new FlashClientNetty(normalHandler,id);
        normalHandler.setClient(client);

        pipeline.addLast(normalGroup,"normalHandler",normalHandler);

        CustomLoginHandler loginHandler = new CustomLoginHandler(client);
        pipeline.addLast(loginGroup,"loginHandler",loginHandler);


        CustomCommandsHandler commandsHandler = new CustomCommandsHandler(loginHandler.client);
        pipeline.addLast(commandsGroup, "commandsHandler", commandsHandler);

    }
}

Я использую 5 групп:

  • bootstrap bossGroup — для новых подключений
  • bootstrap workerGroup — для доставки сообщений
  • normalGroup - для большинства сообщений
  • loginGroup - для тяжелого процесса входа в систему
  • группа команд - для тяжелой логики

Я отслеживаю количество новых подключений и сообщений, чтобы сразу узнать, идет ли атака. Во время атаки я больше не принимаю новые подключения: я возвращаю false в пользовательском брандмауэре ( AbstractRemoteAddressFilter ).

protected boolean accept(ChannelHandlerContext ctx, InetSocketAddress remoteAddress) throws Exception {
    if(ddosDetected())
       return false;
    else
        return true;
}

Но даже то, что я сразу же сбрасываю новые соединения, моя рабочая группа перегружается. PendingTasks для рабочей группы (все остальные группы в порядке) растут, что приводит к все более и более длительному общению для обычных игроков, и, наконец, их выкидывает socket_timeouts. Я не уверен, почему это происходит. При обычном использовании сервера самыми загруженными группами являются логин и обычная группа. На сетевом уровне сервер в порядке - он использует всего ~ 10% своего лимита пропускной способности. Использование ЦП и ОЗУ также не очень велико во время атаки. Но после нескольких минут такой атаки всех моих игроков выкидывает из игры и они больше не могут подключиться.

Есть ли лучший способ мгновенно сбросить все входящие соединения и защитить пользователей, которые уже подключены?


person drygu    schedule 06.03.2019    source источник
comment
Немного не связано, но, возможно, вам следует рассмотреть внешние сервисы, например. CloudFlare в зависимости от масштаба DDOS.   -  person Karol Dowbecki    schedule 06.03.2019
comment
В прошлый раз, когда я исследовал эту тему, я не смог найти никаких служб, поддерживающих защиту TCP. Но теперь я вижу, что у cloudflare есть что-то вроде этого: cloudflare.com/products/cloudflare-spectrum . Спасибо, я проверю это.   -  person drygu    schedule 06.03.2019


Ответы (1)


Я думаю, вам нужно будет «исправить это» на уровне ядра, например, через iptables. В противном случае вы можете закрыть соединение только после того, как уже примете его, что в данном случае звучит недостаточно хорошо.

person Norman Maurer    schedule 07.03.2019