TCP RST on Connection Close - Request for Graceful Shutdown API in DefaultTcpTransportMapping

Hello Frank / SNMP4j Community,
We are using SNMP4J library with our Java application
SNMP4J Version: 3.8.2 (also applicable to 3.9.x)
Component: org.snmp4j.transport.DefaultTcpTransportMapping

Problem:
When using DefaultTcpTransportMapping as a trap receiver, closing the SNMP session via snmp.close() immediately terminates all TCP sockets by sending TCP RST (reset) packets instead of performing a graceful shutdown with TCP FIN (finish) packets.

This behavior causes in-flight SNMP traps to be lost when the receiver application restarts while trap senders maintain persistent TCP connections with queuing enabled.

[Trap Forwarder] ──TCP persistent──> [SNMP4J Trap Receiver]
(with queue) (Java Application)
β”‚ β”‚
β”‚ Restart required
β”‚ β”‚
β”œβ”€β”€β”€ Sends trap during restart ──────────X (TCP RST)
β”‚ β”‚
└─── Trap lost ──────────────── Socket closed abruptly

TIME SOURCE DEST TCP FLAGS STATUS
10:00:00 Sender:55123 Receiver:162 [PSH, ACK] Trap sent
10:00:00 Receiver:162 Sender:55123 [RST] Abrupt termination
^^^ Trap lost!

Current Implementation Behavior:

When snmp.close() is called, the underlying implementation directly closes TCP sockets:
// Simplified representation of current behavior
public void close() throws IOException {
for (SocketEntry entry : sockets.values()) {
Socket socket = entry.getSocket();
socket.close(); // Direct close β†’ TCP RST
}
}

Expected Graceful Shutdown:

According to RFC 793 (TCP Specification, Section 3.5), graceful connection termination should:

Send TCP FIN to peer (half-close output)
Wait for peer’s FIN acknowledgment
Allow peer to finish sending buffered data
Close socket after handshake completes

TIME SOURCE DEST TCP FLAGS STATUS

10:00:00 Sender:55123 Receiver:162 [PSH, ACK] Trap sent

10:00:00 Receiver:162 Sender:55123 [FIN, ACK] Graceful close initiated

10:00:00 Sender:55123 Receiver:162 [ACK] Data received

10:00:01 Sender:55123 Receiver:162 [FIN, ACK] Peer closes

10:00:01 Receiver:162 Sender:55123 [ACK] Close complete

Possible code changes:
// Ideal implementation
socket.shutdownOutput(); // Send TCP FIN to peer
socket.setSoTimeout(5000); // Wait up to 5s for peer’s FIN
// Drain remaining data from peer
try {
while (socket.getInputStream().read() != -1) {
// Allow peer to send its FIN
}
} catch (SocketTimeoutException e) {
// Timeout acceptable
}
socket.close(); // Final close after handshake

I will implement it for v3.9.8’s DefaultTcpTransportMapping in a similar way like it had been already done for TLSTM using the AbstractConnectionOrientedTransportMapping.close() calling the SocketEntry.closeSession() which is not implemented for DefaultTcpTransportMapping.SocketEntry yet.

1 Like

Thank you @AGENTPP for considering this request. Will look forward to v3.9.8 fixes and I will test it. Hopefully that will resolve the TCP RST broken connection issue.

Hi Frank @AGENTPP ,

Any approximate timeline for v3.9.8 release ?

It will be release 3.10.0. There are changes necessary to the internal interfaces - especially WorkerTask that require a minor version upgrade. It will take one or two weeks to finish and test the changes. A snapshot release with an initial preview will be available this weekend.

Yes, `snmp.close()` is a harsh transport teardown rather than a gentle drain, which is typical **SNMP4J DefaultTcpTransportMapping** behavior.

`Socket.close()` in Java often returns FIN. However, the stack may emit RST if there is unread data or if the selector/worker threads are still running. I think that’s what you’re seeing. In essence, it ends running TCP sessions in midair.

The actual problem is that SNMP4J lacks a two-phase shutdown. No, it just kills the sockets. β€œStop accepting β†’ drain β†’ then close.”

Although your `shutdownOutput()` + wait-for-FIN concept is theoretically sound, it should be provided as `gracefulClose(timeout)` on DefaultTcpTransportMapping rather than altering the semantics of `snmp.close()`.

In the event that this is prod-critical:
* Before executing `snmp.close()`, create a drain window, or * To prevent JVM restarts from RSTing active trap senders, front it with a TCP proxy.

The behavior is now consistent with an abort rather than a graceful TCP shutdown.

Hi @Ruth1

Yes your understanding is correct on the current TCP β€œhalf open” session issue even tough I didn’t find any FIN in pcap analysis when the socket.close() is getting called. I also understand that for a robust graceful shutdown of the TCP connection, I need to make changes in application logic also like handling the signals to give sufficient time for draining of session. But making any application changes is not fruitful if the SNMP4J just do socket.close().