Skip to content

stunclient: connect() UDP socket to STUN server#67

Open
Kimapr wants to merge 1 commit intojselbie:masterfrom
Kimapr:master
Open

stunclient: connect() UDP socket to STUN server#67
Kimapr wants to merge 1 commit intojselbie:masterfrom
Kimapr:master

Conversation

@Kimapr
Copy link

@Kimapr Kimapr commented Jan 28, 2026

I have a Bash script that uses stunclient to perform Full Cone NAT traversal for server applications that are unaware of NAT: https://git.kimapr.net/kimapr/fcnatfw

The way it works, is the script first opens as many port mappings as it can using UPnP, and then pings a STUN server repeatedly forever, using stunclient, which is bound to a specific port using --localport. The server application is bound to the same port as stunclient, and this should result in external internet users being able to connect to the server application using the address printed by the script. To make both stunclient and the server application be able to use the same port, a shared library with a hacked socket(2) function that sets the non-standard SO_REUSEPORT option on all internet sockets is interposed using LD_PRELOAD (important to note that this relies on Linux specifics of SO_REUSEPORT implementation — other systems have very different semantics for it, and even Linux doesn't seem to document at all what should happen when the feature is used in the way I am using it. Might be possible to achieve similar results with SO_REUSEADDR but I am not sure (doesn't help that the docs don't explain at all what it really does), and SO_REUSEPORT is still better either way as it prevents other users from binding on the port while SO_REUSEADDR seems not to).

This approach works well for TCP, and I have been able to expose a web server to the internet from my laptop through 2 levels of home router NAT + 1 CGNAT. However, with UDP, Linux can't distinguish stunclient's unconnected socket from the server application's listening socket, and sometimes it would route STUN response messages to the server application instead of stunclient, or vice versa. By connecting stunclient's UDP socket to the STUN server during a binding request this is avoided, as the kernel now knows that stunclient is the intended recipient of the STUN server's response packets, and is not interested in packets from other peers.

The change shouldn't affect normal operation. Tested on Linux 6.17.12, basic binding and behavior and filtering tests continue working as expected. Might also be useful to add an option to set SO_REUSEPORT and such on sockets in stunclient on platforms that support this, though this is not as important to me as I have a crude and evil way to do this already.

To test the problem:

  • run fcnatfw from linked repository like this: fcnatfw 21010 udp (stunclient as well as a few other programs should be in PATH)
  • run a few instances of fcnatwrap ncat -k -l -c 'echo meow; cat > /dev/stderr' --udp 21010 at the same time as fcnatfw
  • before this PR, there's good chance (increase chance by running more ncats at once) that fcnatfw will complain about binding STUN failing, and one of the fcnatwrapped ncats will display binary STUN responses on the terminal. After this PR that never happens and things work reliably.

the purpose is so that when the socket has the Linux SO_REUSEPORT option
set, and another program listens on the same port as stunclient, the
kernel knows to route the STUN response to stunclient rather than the
other program.  stunclient currently offers no option to set
SO_REUSEPORT on the socket, but if it's somehow set externally this is
still useful, and doesn't break anything if it isn't.

since the Filtering Test involves receiving the response from a
different address, unconnected socket is used for it as usual.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant