0

This is regarding the use of tinyradius-netty & the mechanism to properly close the channel & shutdown (as part of app shutdown).

Here is the sample code on the tinyradius-netty public git repo.

If I try to test it as-is I see that the program does NOT finish & return after executing the main method. When I look up for active socket use (with netstat -anpe) I see that its still connected.

root@Ubuntu1:~/TINYRADIUS-NETTY# ps -aef | grep java
root     13701 34395  0 Aug07 pts/17   00:02:19 java -jar target/tinyradius-netty-poc-1.0-jar-with-dependencies.jar 10.20.30.40 somesecret someusername somepwd
...
...
  
root@Ubuntu1:~/TINYRADIUS-NETTY# netstat -anpe | grep java
...
...
unix  2      [ ]         STREAM     CONNECTED     7314176  13701/java          
...
...

Now I know the above sample program is a little incomplete in the sense that it doesn't properly shutdown the NioEventLoopGroup or (I suspect) properly close the NioDatagramChannel (based on this SO answer).

But even if I do the above two I still see that the program does NOT finish & return. Specifically even after I invoke NioDatagramChannel#close() & then invoke ChannelFuture#syncUninterruptibly() or ChannelFuture#awaitUninterruptibly() & they both return, I still see the same behavior (program doesn't return & UNIX domain socket actively connected).

I also tried invoking ChannelFuture#cancel(true) but it always returns false (ChannelFuture#isCancellable() returns true).

Based on this SO answer I also verified that I am not using the affected version & instead using the version 4.1.87.Final.

Can someone help throw some pointers on what I am missing here?

Below is my POM & code.

Code:

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelOutboundHandler;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.tinyradius.core.RadiusPacketException;
import org.tinyradius.core.dictionary.DefaultDictionary;
import org.tinyradius.core.dictionary.Dictionary;
import org.tinyradius.core.packet.request.AccessRequest;
import org.tinyradius.core.packet.request.AccessRequestPap;
import org.tinyradius.core.packet.request.AccountingRequest;
import org.tinyradius.core.packet.request.RadiusRequest;
import org.tinyradius.core.packet.response.RadiusResponse;
import org.tinyradius.io.RadiusEndpoint;
import org.tinyradius.io.client.RadiusClient;
import org.tinyradius.io.client.handler.BlacklistHandler;
import org.tinyradius.io.client.handler.ClientDatagramCodec;
import org.tinyradius.io.client.handler.PromiseAdapter;
import org.tinyradius.io.client.timeout.FixedTimeoutHandler;

import java.net.InetSocketAddress;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.TimeUnit;

import static org.tinyradius.core.packet.PacketType.ACCESS_REQUEST;
import static org.tinyradius.core.packet.PacketType.ACCOUNTING_REQUEST;

/**
 * @author lmk
 *
 */
public final class TestRadiusClient {

    private static final Logger logger = LogManager.getLogger();


    /**
     * Radius command line client.
     *  
     * @param args [host, sharedSecret, username, password] 
     */
    public static void main(String... args) {
        
        if (args.length != 4) {
            System.out.println("Usage: TestClient [hostName] [sharedSecret] [userName] [password]");
            return;
        }
        
        final NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup(4);
        RadiusClient rc = null;
        // Below is just a temporary hack to get a reference to the channel to see if i can close it to verify..
        final Map.Entry<String, DatagramChannel> channelHolder = new AbstractMap.SimpleEntry<>(NioDatagramChannel.class.getName(), null);
        
        try {
            final String host = args[0];
            final String shared = args[1];
            final String user = args[2];
            final String pass = args[3];

            final Dictionary dictionary = DefaultDictionary.INSTANCE;
            final Timer timer = new HashedWheelTimer();

            final Bootstrap bootstrap = new Bootstrap().group(eventLoopGroup).channel(NioDatagramChannel.class);
             class MyOutboundHandler extends ChannelOutboundHandlerAdapter {
                  @Override
                 public void close(ChannelHandlerContext ctx, ChannelPromise promise) {
                     System.out.println("Closing from MyOutboundHandler..");
                     ctx.close(promise);
                 }
             }
            
            rc = new RadiusClient(
                    bootstrap, new InetSocketAddress(0), new FixedTimeoutHandler(timer), new ChannelInitializer<DatagramChannel>() {
                        @Override
                        protected void initChannel(DatagramChannel ch) {
                            channelHolder.setValue(ch);
                            ch.pipeline().addLast(
                                    new ClientDatagramCodec(dictionary),
                                    new PromiseAdapter(),
                                    new BlacklistHandler(60_000, 3));
                            ch.pipeline().addLast(new MyOutboundHandler());
                        }
                        
                        @Override
                        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
                            System.out.println("Exception caught! Cause is : " + cause);
                            ctx.channel().close();
                        }
                        
                        @Override
                        public void channelUnregistered(ChannelHandlerContext ctx) {
                            System.out.println("IS CHANNEL OPEN? Y/N" + ctx.channel().isOpen());
                            ctx.channel().close();
                        }
                        
                    });

            final RadiusEndpoint authEndpoint = new RadiusEndpoint(new InetSocketAddress(host, 1812), shared);
            final RadiusEndpoint acctEndpoint = new RadiusEndpoint(new InetSocketAddress(host, 1813), shared);

            // 1. Send Access-Request
            final AccessRequestPap ar = (AccessRequestPap)
                    ((AccessRequest) RadiusRequest.create(dictionary, ACCESS_REQUEST, (byte) 1, null, Collections.emptyList()))
                    .withPapPassword(pass)
                    .addAttribute("User-Name", user)
                    .addAttribute("NAS-Identifier", "this.is.my.nas-identifier")
                    .addAttribute("NAS-IP-Address", "10.20.30.40")
                    .addAttribute("Service-Type", "Login-User");

            System.out.println("Packet before it is sent\n" + ar + "\n");
            RadiusResponse response = rc.communicate(ar, authEndpoint).syncUninterruptibly().getNow();
            System.out.println("Packet after it was sent\n" + ar + "\n");
            System.out.println("Response\n" + response + "\n");

            // 2. Send Accounting-Request
            final AccountingRequest acc = (AccountingRequest) RadiusRequest.create(dictionary, ACCOUNTING_REQUEST, (byte) 2, null, new ArrayList<>())
                    .addAttribute("User-Name", "username")
                    .addAttribute("Acct-Status-Type", "1")
                    .addAttribute("Acct-Session-Id", "1234567890")
                    .addAttribute("NAS-Identifier", "this.is.my.nas-identifier")
                    .addAttribute("NAS-Port", "0");

            System.out.println(acc + "\n");
            response = rc.communicate(acc, acctEndpoint).syncUninterruptibly().getNow();
            System.out.println("Response: " + response);
            
        } catch(Exception e) {
            System.out.println(String.format("Failed executing RadiusClient. Cause : %s", e));
        } finally {
            try {
                rc.close();
                channelHolder.getValue().disconnect();
                System.out.println("Disconnected!");
                ChannelFuture cf = channelHolder.getValue().close();
                cf.syncUninterruptibly();
                //cf.awaitUninterruptibly();
                System.out.println("Is cancelable y/n " + cf.isCancellable());
                System.out.println("Is cancelled t/f " + cf.cancel(true));
                eventLoopGroup.shutdownGracefully().sync();
                System.out.println("Is eventLoopGroup terminated? t/f : " + eventLoopGroup.awaitTermination(1, TimeUnit.SECONDS));
            } catch (Exception e) {
                System.out.println("Graceful shutdown failed!" + e);
            }
        }
    }
}

POM:

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

    <groupId>dcm.poc</groupId>
    <artifactId>tinyradius-netty-poc</artifactId>
    <version>${revision}</version>
    <packaging>jar</packaging>
    <name>dcm-poc-tinyradius-netty-poc</name>
    <description>TinyRadius-Netty POC</description>

    <properties>
        <java.version>11</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.version>3.11.0</maven.compiler.version>
        <maven.assembly.version>3.6.0</maven.assembly.version>
        <tinyradius.netty.version>1.5.12</tinyradius.netty.version>
        <netty.codec.version>4.1.87.Final</netty.codec.version>
        <jakarta.xml.bind-api.version>4.0.0</jakarta.xml.bind-api.version>
        <log4j2.version>2.19.0</log4j2.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.globalreachtech</groupId>
            <artifactId>tinyradius-netty</artifactId>
            <version>${tinyradius.netty.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>com.google.protobuf</groupId>
                    <artifactId>protobuf-java</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>${netty.codec.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>com.google.protobuf</groupId>
                    <artifactId>protobuf-java</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>jakarta.xml.bind</groupId>
            <artifactId>jakarta.xml.bind-api</artifactId>
            <version>${jakarta.xml.bind-api.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>${log4j2.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>${log4j2.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven.compiler.version}</version>
                <configuration>
                    <release>${java.version}</release>
                    <encoding>${project.build.sourceEncoding}</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>${maven.assembly.version}</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                        <configuration>
                            <archive>
                                <manifest>
                                    <mainClass>
                                        com.dcm.tinyradiusnetty.poc.client.TestRadiusClient
                                    </mainClass>
                                </manifest>
                                <manifestEntries>
                                    <Multi-Release>true</Multi-Release>
                                </manifestEntries>
                            </archive>
                            <descriptorRefs>
                                <descriptorRef>jar-with-dependencies</descriptorRef>
                            </descriptorRefs>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

Thanks

lmk
  • 654
  • 5
  • 21
  • OK I just saw this [one](https://github.com/globalreachtech/tinyradius-netty/issues/62). Seems like this is a planned item.. – lmk Aug 10 '23 at 19:03

0 Answers0