examples/ex_bind_exclusive.c
Regression: a listening bind is EXCLUSIVE on every platform. More...
Functions
| Name | |
|---|---|
| int | main(void ) |
| int | check_exclusive(int udp) Bind a listener, then try to bind the same port again; the second must fail. |
Detailed Description
Regression: a listening bind is EXCLUSIVE on every platform.
Author: Castagnier Mickael
netw_make_listening and netw_bind_udp set SO_REUSEADDR so a restarted listener can re-take the port it just released. That option means the opposite thing on Windows: it lets an UNRELATED process bind over a socket already holding the port, the second bind succeeds, and which socket receives a given connection is undefined. A program that reports "listening" while another process owns the port is worse than one that refuses to start, so the option is POSIX-only and this example holds that contract in place.
The port is taken from the OS rather than hardcoded, so the check cannot fail because some unrelated service happened to own a fixed number.
Functions Documentation
function main
int main(
void
)
function check_exclusive
static int check_exclusive(
int udp
)
Bind a listener, then try to bind the same port again; the second must fail.
Parameters:
- udp non-zero to test netw_bind_udp, zero for netw_make_listening.
Return: 0 when the contract holds, 1 otherwise.
Source code
#include "nilorea/n_log.h"
#include "nilorea/n_network.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int check_exclusive(int udp) {
NETWORK* first = NULL;
NETWORK* second = NULL;
char port[16] = "";
const char* what = udp ? "netw_bind_udp" : "netw_make_listening";
int p, ok = 0;
/* Take a free port by trying a range, rather than asking for 0: the NETWORK keeps the
* port string it was GIVEN, so an ephemeral bind would not report back which one it got
* and the second bind would have nothing to aim at. */
for (p = 34100; p < 34100 + 200; p++) {
snprintf(port, sizeof(port), "%d", p);
ok = udp ? (netw_bind_udp(&first, "127.0.0.1", port, NETWORK_IPV4) == TRUE)
: (netw_make_listening(&first, "127.0.0.1", port, 8, NETWORK_IPV4) == TRUE);
if (ok && first)
break;
first = NULL;
}
if (!ok || !first) {
n_log(LOG_ERR, "%s: could not take any port in the test range", what);
return 1;
}
second = NULL;
ok = udp ? (netw_bind_udp(&second, "127.0.0.1", port, NETWORK_IPV4) == TRUE)
: (netw_make_listening(&second, "127.0.0.1", port, 8, NETWORK_IPV4) == TRUE);
if (second)
netw_close(&second);
netw_close(&first);
if (!udp) {
/* TCP: exclusive on every platform. This is the contract that matters for a
* listener a user was told is running. */
if (ok) {
n_log(LOG_ERR,
"%s: the SECOND bind to port %s SUCCEEDED. The port is being shared, so a "
"listener can be started over one another process already owns.",
what, port);
return 1;
}
n_log(LOG_INFO, "%s: a second bind to port %s was refused, as it must be", what, port);
return 0;
}
/* UDP is not the same question, and asserting it were would be asserting something
* POSIX does not offer. SO_REUSEADDR on a datagram socket deliberately PERMITS several
* sockets on one port (that is how multicast and broadcast receivers work), so a second
* bind succeeding on POSIX is correct behaviour rather than a defect. What is worth
* holding is the Windows side, where the option is not set and the second bind is
* refused, because that is where it would otherwise mean a process silently binding
* over one already holding the port. */
#ifdef __windows__
if (ok) {
n_log(LOG_ERR,
"%s: the second UDP bind to port %s SUCCEEDED on Windows, where the port "
"should be exclusive",
what, port);
return 1;
}
n_log(LOG_INFO, "%s: a second UDP bind to port %s was refused, as it must be on Windows", what, port);
#else
n_log(LOG_INFO,
"%s: a second UDP bind to port %s %s, which is what POSIX SO_REUSEADDR means "
"for datagram sockets (shared receivers); the exclusivity that matters is "
"asserted for TCP above and for UDP on Windows",
what, port, ok ? "succeeded" : "was refused");
#endif
return 0;
}
int main(void) {
int rc = 0;
set_log_level(LOG_INFO);
rc |= check_exclusive(0);
rc |= check_exclusive(1);
if (rc == 0)
printf(
"ex_bind_exclusive: a TCP listener's port is exclusive; UDP is exclusive on Windows "
"and shareable on POSIX, which is what SO_REUSEADDR means there\n");
else
printf("ex_bind_exclusive: FAILED\n");
return rc;
}
Modules
- AABB3D
- ACCEPT_POOL
- ALLEGRO4
- ALLEGRO5
- ANIM_LIB
- ASTAR
- ATLAS_LIB
- AVRO
- CLOCK_SYNC
- COMMONS
- CONFIGFILE
- CYPHER_BASE64
- CYPHER_VIGENERE
- DEAD_RECKONING
- ENUMS
- EXCEPTIONS
- GAME_ENV
- GIT
- GUI
- HASH_TABLE
- ISOMETRIC_ENGINE
- LIST
- LOG
- LOGNODUP
- LZ4
- NETWORKING
- NETWORK_MSG
- N_COOKIES
- N_DIFF
- N_DIGEST
- N_DISK_CACHE
- N_DNS
- N_ECC
- N_ENTROPY
- N_FILES
- N_FLUIDS
- N_GUI_COLOR
- N_GUI_COLORWHEEL
- N_GUI_CURVE
- N_GUI_GRID
- N_GUI_POPUP
- N_HEX
- N_HTTP3
- N_HTTP_COOKIE
- N_HTTP_FORM
- N_HTTP_MIME
- N_HTTP_REQ
- N_HTTP_RESP
- N_HTTP_ROUTER
- N_HTTP_SERVER
- N_ICC
- N_JSON
- N_KAFKA
- N_LINALG
- N_MODULE
- N_OPENSSL_COMPAT
- N_PRETTY
- N_PTH
- N_QUERY
- N_RANDOM
- N_SHA256
- N_STR
- N_TIFF
- N_TIME
- N_USER
- N_X509
- N_ZIP
- PARTICLES
- PCRE
- PHYSICS
- SIGNALS
- STACK
- THREADS
- TRAJECTORY
- TREE
- ZLIB