MarfGamer MarfGamer - 5 months ago 44
Java Question

How to find out which address caused exception in Netty 5 UDP?

Introduction



I am the creator of JRakNet, a networking library which implements the basics of the latest protocol RakNet for Java. I made this library so I could make a multiplayer server for Minecraft: Pocket Edition which uses RakNet.

The problem



However one problem I've been having with the server and client is exception handling for the handler. Whenever an invalid packet is sent then the data is read incorrectly and exceptions are thrown like normal. However, I need to find a way to figure out the external address which caused the exception.

Why I can't fix the problem



Normally, on a TCP server which uses ServerBootstrap I am able to group a boss and worker group so when an exception is caught I know that where it comes from because the client has it's own handler dedicated to it. But RakNet is UDP, meaning it is connection-less and packets can come from anywhere so I only have one handler. So whenever an exception is thrown I have no idea who necessarily caused it. I've tried having an ignored exception list but I consider that extremely inefficient. I've tried having a variable called
lastSender
in the code so whenever an exception is thrown it looks to that for who caused error. Is this a good way to figure out who caused an exception? Or is there another, better way to figure out who caused an error on a UDP Netty server?

The source files at the time of posting



The RakNetClient class at the time of posting

The RakNetClientHandler classa at the time of posting

Here is the whole project code at the time of posting if needed

TL;DR



I need to find a good way to figure out which address caused an exception on a raw Bootstrap Netty UDP server/client. I've tried to use a ServerBootstrap with UDP but it seems that it will still only use one channel as stated here

Answer

Netty has a nice thread model since 4.0.

Especially

Netty will never call a ChannelHandler's methods concurrently, unless the ChannelHandler is annotated with @Sharable

there has been a slight change in 5.0 but

From a developer's point of view, the only thing that changes is that it's no longer guaranteed that a ChannelHandler will always be executed by the same thread. It is, however, guaranteed that it will never be executed by two or more threads at the same time. Furthermore, Netty will also take care of any memory visibility issues that might occur. So there's no need to worry about thread-safety and volatile variables within a ChannelHandler.

You can use this to your advantage and solve the problem with nothing more than

static class UdpHandler extends SimpleChannelInboundHandler<DatagramPacket> {
    private InetSocketAddress lastSender;

    @Override
    protected void messageReceived(ChannelHandlerContext ctx, DatagramPacket packet) throws Exception {
        lastSender = packet.sender();
        throw new RuntimeException();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("Evil sender was: " + lastSender);
    }
}

under the hood the exceptionCaught method is called directly after messageReceived like

public static void invokeChannelReadNow(final ChannelHandlerContext ctx, final Object msg) {
    try {
        ((AbstractChannelHandlerContext) ctx).invokedThisChannelRead = true;
        ctx.handler().channelRead(ctx, msg); // -> calls messageReceived
    } catch (Throwable t) {
        notifyHandlerException(ctx, t);      // -> calls exceptionCaught
    }
}

(source)

And the value of a local variable will still contain whatever you set it to in messageReceived.