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