Skip navigation

Netty 4.0.28.Final released

I'm happy to announce the release of Netty 4.0.28.Final,

This release contains 60+ changes, including bug-fixes, improvements and two new exciting features.

If you use Netty with AutoRead disabled we highly recommend upgrading as previous versions of Netty contained a regression which could lead to stales, when using any decoder that extends either ByteToMessageDecoder or ReplayingDecoder.

The most important are:

  • ChannelFuture is not done after shutdown (#3567)
  • Fix handling of non-auto read for ByteToMessageDecoder and SslHandler (#3658), (#3529), (#3587)
  • Correctly handle EPOLLRDHUP (#3611), (#3539)
  • Add ChannelPool abstraction and implementations (#3607), (#3218)
  • Change PoolThreadCache to use LIFO for better cache performance (#3553)
  • Add support for ALPN when using openssl + NPN client mode and support for CipherSuiteFilter (#3481) (#3481)
  • Add support for splice(...) (#3665)
  • Allow rejection of remote initiated renegotiation (#3750)
  • Not defer close of Channel when in flush (#3739)
  • Add support for mutual auth when using OpenSslEngine (#3732)
  • Fix possible IllegalStateException caused by closeNotifyTimeout when using SslHandler (#3727)
  • Validate cookie name and value characters (#3748)

For the details and all changes, please browse our issue tracker.

Special notes

Validate cookie name and value characters

This problem was discovered by the LinkedIn Security Team and could lead to security problems due the improper parsing of Cookies (without strict validating).

ChannelPool

Many of our users needed to support connection pooling at some point when they used Netty to writing a client. This resulted in a lot of duplicated code. With the release of 4.0.28.Final we include a pool API that users can use to implement there own ChannelPool or reuse either the SimpleChannelPool or the FixedChannelPool that are provided by Netty itself.

EventLoopGroup group = new NioEventLoopGroup();
final Bootstrap cb = new Bootstrap();
InetSocketAddress addr1 = new InetSocketAddress("10.0.0.10", 8888);
InetSocketAddress addr2 = new InetSocketAddress("10.0.0.11", 8888);

cb.group(group).channel(NioSocketChannel.class);

ChannelPoolMap<InetSocketAddress, SimpleChannelPool> poolMap = new AbstractChannelPoolMap<InetSocketAddress, SimpleChannelPool>() {
    @Override
    protected SimpleChannelPool newPool(InetSocketAddress key) {
        return new SimpleChannelPool(cb.remoteAddress(key), new TestChannelPoolHandler());
    }
};

// depending on when you use addr1 or addr2 you will get different pools.
final SimpleChannelPool pool = poolMap.get(addr1);
Future<Channel> f = pool.acquire();
f.addListener(new FutureListener<Channel>() {
    @Override
    public void operationComplete(Future<Channel> f) {
        if (f.isSuccess()) {
            Channel ch = f.getNow();
            // Do somethings
            // ...
            // ...

            // Release back to pool
            pool.release(ch);
        }
    }
});

The important thing to note is that your key could be everything. This makes it very flexible... For example you could have different pools for different EventLoops etc.

Please refer to the api documentation in the io.netty.transport.pool package.

Splice

This release includes support for splice when using the native epoll transport. Splicing allows to directly transfer bytes from one filedescriptor to another without copy data from kernel to user-space. This makes splice a perfect fit i.e when you want to write a TCP proxy.

The following code snipped shows a full TCP proxy that uses splicing:

EventLoopGroup group = new EpollEventLoopGroup(1);
ServerBootstrap bs2 = new ServerBootstrap();
bs2.channel(EpollServerSocketChannel.class);
bs2.childOption(EpollChannelOption.EPOLL_MODE, EpollMode.LEVEL_TRIGGERED);
bs2.group(group).childHandler(new ChannelInboundHandlerAdapter() {
    @Override
    public void channelActive(final ChannelHandlerContext ctx) throws Exception {
        ctx.channel().config().setAutoRead(false);
        Bootstrap bs = new Bootstrap();
        bs.option(EpollChannelOption.EPOLL_MODE, EpollMode.LEVEL_TRIGGERED);

        bs.channel(EpollSocketChannel.class);
        bs.group(ctx.channel().eventLoop()).handler(new ChannelInboundHandlerAdapter() {
            @Override
            public void channelActive(ChannelHandlerContext context) throws Exception {
                final EpollSocketChannel ch = (EpollSocketChannel) ctx.channel();
                final EpollSocketChannel ch2 = (EpollSocketChannel) context.channel();
                // We are splicing two channels together, at this point we have a tcp proxy which handles all
                // the data transfer only in kernel space!

                // Integer.MAX_VALUE will splice infinitly.
                ch.spliceTo(ch2, Integer.MAX_VALUE).addListener(new ChannelFutureListener() {
                    @Override
                    public void operationComplete(ChannelFuture future) throws Exception {
                        if (!future.isSuccess()) {
                            future.channel().close();
                        }
                    }
                });
                // Trigger multiple splices to see if partial splicing works as well.
                ch2.spliceTo(ch, SPLICE_LEN).addListener(new ChannelFutureListener() {
                    @Override
                    public void operationComplete(ChannelFuture future) throws Exception {
                        if (!future.isSuccess()) {
                            future.channel().close();
                        } else {
                            ch2.spliceTo(ch, SPLICE_LEN).addListener(this);
                        }
                    }
                });
                ctx.channel().config().setAutoRead(true);
            }

            @Override
            public void channelInactive(ChannelHandlerContext context) throws Exception {
                context.close();
            }
        });
        bs.connect(new InetSocketAddress("10.0.0.10", 8888)).addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                if (!future.isSuccess()) {
                    ctx.close();
                } else {
                    future.channel().closeFuture().addListener(new ChannelFutureListener() {
                        @Override
                        public void operationComplete(ChannelFuture future) throws Exception {
                            ctx.close();
                        }
                    });
                }
            }
        });
    }
});
Channel pc = bs2.bind(new InetSocketAddress(0)).syncUninterruptibly().channel();

Beside this we also support splicing directly to a file. For more informations please refer to the AbstractEpollStreamChannel javadocs.

As always, please let us know if you find any issues. We love feedback!

Thank You

Every idea and bug-report counts and so we thought it is worth mentioning those who helped in this area. Please report an unintended omission.

A special thank you to the LinkedIn Security Team for reporting the possible security bug.