From 153c45ff3ab624dad17a8a5687e14ec84d2feacb Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Thu, 14 May 2026 23:54:40 -0400
Subject: [PATCH] api: Added broadcast packets, creation properties, datagram.c
example.
Fixes #97.
---
CMakeLists.txt | 1 +
examples/datagram.c | 148 ++++++++++++++++++++++++
examples/echo-server.c | 2 +-
examples/simple-http-get.c | 2 +-
examples/voipchat.c | 2 +-
include/SDL3_net/SDL_net.h | 90 ++++++++++++++-
src/SDL_net.c | 223 ++++++++++++++++++++++++++++++++-----
7 files changed, 436 insertions(+), 32 deletions(-)
create mode 100644 examples/datagram.c
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ec0c8fa6..7a792560 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -339,6 +339,7 @@ if(SDLNET_SAMPLES)
add_sdl_net_example_executable(resolve-hostnames examples/resolve-hostnames.c)
add_sdl_net_example_executable(get-local-addrs examples/get-local-addrs.c)
add_sdl_net_example_executable(echo-server examples/echo-server.c)
+ add_sdl_net_example_executable(datagram examples/datagram.c)
# Build at least one example in C90
set_property(TARGET get-local-addrs PROPERTY C_STANDARD 90)
diff --git a/examples/datagram.c b/examples/datagram.c
new file mode 100644
index 00000000..b8993da7
--- /dev/null
+++ b/examples/datagram.c
@@ -0,0 +1,148 @@
+#include <SDL3/SDL_main.h>
+#include <SDL3/SDL.h>
+#include <SDL3_net/SDL_net.h>
+
+static NET_DatagramSocket *sock = NULL; /* you talk over this, client or server. */
+static NET_Address *server_addr = NULL; /* address of the server you're talking to, NULL if you _are_ the server. */
+static Uint16 server_port = 9781;
+
+static void print_usage(const char *prog) {
+ SDL_Log("USAGE: %s <hostname|ip|-> [--help] [--server] [--port X] [--simulate-failure Y]", prog);
+}
+
+static void run_datagram(int argc, char **argv)
+{
+ const char *hostname = NULL;
+ bool is_server = false;
+ int simulate_failure = 0;
+ int i;
+ NET_Address *socket_address = NULL;
+ const SDL_PropertiesID props = SDL_CreateProperties();
+ SDL_SetBooleanProperty(props, NET_PROP_DATAGRAM_SOCKET_ALLOW_BROADCAST_BOOLEAN, true);
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+ if (SDL_strcmp(arg, "--help") == 0) {
+ print_usage(argv[0]);
+ return;
+ } else if (SDL_strcmp(arg, "--server") == 0) {
+ is_server = true;
+ } else if ((SDL_strcmp(arg, "--port") == 0) && (i < (argc-1))) {
+ server_port = (Uint16) SDL_atoi(argv[++i]);
+ } else if ((SDL_strcmp(arg, "--simulate-failure") == 0) && (i < (argc-1))) {
+ simulate_failure = (int) SDL_atoi(argv[++i]);
+ } else {
+ hostname = arg;
+ }
+ }
+
+ simulate_failure = SDL_clamp(simulate_failure, 0, 100);
+ if (simulate_failure) {
+ SDL_Log("Simulating failure at %d percent", simulate_failure);
+ }
+
+ if (!is_server && !hostname) {
+ print_usage(argv[0]);
+ return;
+ }
+
+ if (is_server) {
+ if (hostname) {
+ SDL_Log("SERVER: Resolving binding hostname '%s' ...", hostname);
+ socket_address = NET_ResolveHostname(hostname);
+ if (socket_address) {
+ if (NET_WaitUntilResolved(socket_address, -1) == NET_FAILURE) {
+ NET_UnrefAddress(socket_address);
+ socket_address = NULL;
+ }
+ }
+ #if 0
+ } else {
+ int num_addresses;
+ NET_Address **addresses;
+ addresses = NET_GetLocalAddresses(&num_addresses);
+ if (addresses == NULL || num_addresses <= 0) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to to get local addresses: %s", SDL_GetError());
+ } else {
+ socket_address = addresses[0];
+ NET_RefAddress(socket_address);
+ }
+ #endif
+ }
+ if (socket_address) {
+ SDL_Log("SERVER: Listening on %s:%d.", NET_GetAddressString(socket_address), server_port);
+ } else {
+ SDL_Log("SERVER: Listening on port %d", server_port);
+ }
+ } else {
+ if (SDL_strcmp(hostname, "-") == 0) {
+ SDL_Log("CLIENT: Broadcasting instead of unicasting to a specific address");
+ } else {
+ SDL_Log("CLIENT: Resolving server hostname '%s' ...", hostname);
+ server_addr = NET_ResolveHostname(hostname);
+ if (server_addr) {
+ if (NET_WaitUntilResolved(server_addr, -1) == NET_FAILURE) {
+ NET_UnrefAddress(server_addr);
+ server_addr = NULL;
+ }
+ }
+
+ if (!server_addr) {
+ SDL_Log("CLIENT: Failed! %s", SDL_GetError());
+ SDL_Log("CLIENT: Giving up.");
+ return;
+ }
+
+ SDL_Log("CLIENT: Server is at %s:%d.", NET_GetAddressString(server_addr), (int) server_port);
+ }
+ }
+
+ /* server _must_ be on the requested port. Clients can take anything available, server will respond to where it sees it come from. */
+ sock = NET_CreateDatagramSocket(socket_address, is_server ? server_port : 0, props);
+ SDL_DestroyProperties(props);
+ NET_UnrefAddress(socket_address);
+ if (!sock) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create datagram socket: %s", SDL_GetError());
+ } else {
+ if (simulate_failure) {
+ NET_SimulateDatagramPacketLoss(sock, simulate_failure);
+ }
+
+ if (!is_server) {
+ Uint8 buf[128];
+ SDL_zeroa(buf);
+ SDL_Log("CLIENT: %s %d bytes...", server_addr ? "Sending" : "Broadcasting", (int) sizeof (buf));
+ NET_SendDatagram(sock, server_addr, server_port, buf, sizeof (buf));
+ } else {
+ while (NET_WaitUntilInputAvailable((void **) &sock, 1, -1) >= 0) {
+ NET_Datagram *dgram = NULL;
+ if (NET_ReceiveDatagram(sock, &dgram) && (dgram != NULL)) {
+ SDL_Log("SERVER: got %d-byte datagram from %s:%d", (int) dgram->buflen, NET_GetAddressString(dgram->addr), (int) dgram->port);
+ NET_DestroyDatagram(dgram);
+ }
+ }
+ }
+ }
+
+ SDL_Log("Shutting down...");
+
+ NET_UnrefAddress(server_addr);
+ server_addr = NULL;
+ NET_DestroyDatagramSocket(sock);
+ sock = NULL;
+}
+
+int main(int argc, char **argv)
+{
+ if (!NET_Init()) {
+ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "NET_Init failed: %s", SDL_GetError());
+ SDL_Quit();
+ return 1;
+ }
+
+ run_datagram(argc, argv);
+
+ NET_Quit();
+ return 0;
+}
+
diff --git a/examples/echo-server.c b/examples/echo-server.c
index ec660f76..91cc9f5b 100644
--- a/examples/echo-server.c
+++ b/examples/echo-server.c
@@ -52,7 +52,7 @@ int main(int argc, char **argv)
}
}
- NET_Server *server = NET_CreateServer(server_addr, server_port);
+ NET_Server *server = NET_CreateServer(server_addr, server_port, 0);
if (!server) {
SDL_Log("Failed to create server: %s", SDL_GetError());
} else {
diff --git a/examples/simple-http-get.c b/examples/simple-http-get.c
index 40838712..5c32f444 100644
--- a/examples/simple-http-get.c
+++ b/examples/simple-http-get.c
@@ -27,7 +27,7 @@ int main(int argc, char **argv)
SDL_Log("%s is %s", argv[i], NET_GetAddressString(addr));
char *req = NULL;
SDL_asprintf(&req, "GET / HTTP/1.0\r\nHost: %s\r\n\r\n", argv[i]);
- NET_StreamSocket *sock = req ? NET_CreateClient(addr, 80) : NULL;
+ NET_StreamSocket *sock = req ? NET_CreateClient(addr, 80, 0) : NULL;
if (!req) {
SDL_Log("Out of memory!");
} else if (!sock) {
diff --git a/examples/voipchat.c b/examples/voipchat.c
index adfd9468..d3886998 100644
--- a/examples/voipchat.c
+++ b/examples/voipchat.c
@@ -347,7 +347,7 @@ static void run_voipchat(int argc, char **argv)
}
/* server _must_ be on the requested port. Clients can take anything available, server will respond to where it sees it come from. */
- sock = NET_CreateDatagramSocket(socket_address, is_server ? server_port : 0);
+ sock = NET_CreateDatagramSocket(socket_address, is_server ? server_port : 0, 0);
NET_UnrefAddress(socket_address);
if (!sock) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create datagram socket: %s", SDL_GetError());
diff --git a/include/SDL3_net/SDL_net.h b/include/SDL3_net/SDL_net.h
index 7aabb737..67ce9288 100644
--- a/include/SDL3_net/SDL_net.h
+++ b/include/SDL3_net/SDL_net.h
@@ -640,8 +640,13 @@ typedef struct NET_StreamSocket NET_StreamSocket;
* you do not have to byteswap it into "network order," as the library will
* handle that for you.
*
+ * There are currently no extra properties for creating a client, so `props`
+ * should be zero. A future revision of SDL_net may add additional (optional)
+ * properties.
+ *
* \param address the address of the remote server to connect to.
* \param port the port on the remote server to connect to.
+ * \param props properties of the new client. Specify zero for defaults.
* \returns a new NET_StreamSocket, pending connection, or NULL on error; call
* SDL_GetError() for details.
*
@@ -653,7 +658,7 @@ typedef struct NET_StreamSocket NET_StreamSocket;
* \sa NET_GetConnectionStatus
* \sa NET_DestroyStreamSocket
*/
-extern SDL_DECLSPEC NET_StreamSocket * SDLCALL NET_CreateClient(NET_Address *address, Uint16 port);
+extern SDL_DECLSPEC NET_StreamSocket * SDLCALL NET_CreateClient(NET_Address *address, Uint16 port, SDL_PropertiesID props);
/**
* Block until a stream socket has connected to a server.
@@ -750,8 +755,24 @@ typedef struct NET_Server NET_Server;
* you do not have to byteswap it into "network order," as the library will
* handle that for you.
*
+ * The caller may supply properties to customize behavior. This is optional,
+ * and a value of zero for `props` will request defaults for all properties.
+ *
+ * These are the supported properties:
+ *
+ * - `NET_PROP_SERVER_REUSEADDR_BOOLEAN`: true if the server should
+ * be created even if a previous server has recently used this address.
+ * For various reasons, networks prefer that there be some delay between
+ * apps reusing the same address, but this can be problematic when
+ * iterating quickly, for software development purposes or just restarting
+ * a crashed service. This property defaults to true (although it should be
+ * noted that, at the operating system level, this defaults to false!). If
+ * this property is false and the OS feels that not enough time has elapsed,
+ * server creation will fail and this function will report an error.
+ *
* \param addr the _local_ address to listen for connections on, or NULL.
* \param port the port on the local address to listen for connections on.
+ * \param props properties of the new server. Specify zero for defaults.
* \returns a new NET_Server, or NULL on error; call SDL_GetError() for
* details.
*
@@ -763,7 +784,10 @@ typedef struct NET_Server NET_Server;
* \sa NET_AcceptClient
* \sa NET_DestroyServer
*/
-extern SDL_DECLSPEC NET_Server * SDLCALL NET_CreateServer(NET_Address *addr, Uint16 port);
+extern SDL_DECLSPEC NET_Server * SDLCALL NET_CreateServer(NET_Address *addr, Uint16 port, SDL_PropertiesID props);
+
+#define NET_PROP_SERVER_REUSEADDR_BOOLEAN "NET.server.reuseaddr"
+
/**
* Create a stream socket for the next pending client connection.
@@ -1213,10 +1237,37 @@ typedef struct NET_Datagram
* you do not have to byteswap it into "network order," as the library will
* handle that for you.
*
+ * The caller may supply properties to customize behavior. This is optional,
+ * and a value of zero for `props` will request defaults for all properties.
+ *
+ * These are the supported properties:
+ *
+ * - `NET_PROP_DATAGRAM_SOCKET_REUSEADDR_BOOLEAN`: true if the socket should
+ * be created even if a previous socket has recently used this address.
+ * For various reasons, networks prefer that there be some delay between
+ * apps reusing the same address, but this can be problematic when
+ * iterating quickly, for software development purposes or just restarting
+ * a crashed service. This property defaults to true (although it should be
+ * noted that, at the operating system level, this defaults to false!). If
+ * this property is false and the OS feels that not enough time has elapsed,
+ * socket creation will fail and this function will report an error.
+ * - `NET_PROP_DATAGRAM_SOCKET_ALLOW_BROADCAST_BOOLEAN`: true if the socket
+ * should allow broadcasting. At the lower level, this will set
+ * `SO_BROADCAST` for IPv4 sockets, to allow sending to the subnet's
+ * broadcast address at the OS level. For IPv6, it'll join the all-nodes
+ * link-local multicast group, ff02::1, allowing sending and receiving
+ * there, more or less simulating the usual IPv4 broadcast semantics. Other
+ * protocols take similar approaches. If you do not intend to send or
+ * receive broadcast packets on this socket, set this property to false, or
+ * omit it, as it defaults to false. Note: IPv4 will still be able to
+ * receive broadcast packets without this option, but IPv6 will not. Also
+ * see notes about sending to a broadcast address in NET_SendDatagram().
+ *
* \param addr the local address to listen for connections on, or NULL to
* listen on all available local addresses.
* \param port the port on the local address to listen for connections on, or
* zero for the system to decide.
+ * \param props properties of the new socket. Specify zero for defaults.
* \returns a new NET_DatagramSocket, or NULL on error; call SDL_GetError()
* for details.
*
@@ -1227,7 +1278,11 @@ typedef struct NET_Datagram
* \sa NET_GetLocalAddresses
* \sa NET_DestroyDatagramSocket
*/
-extern SDL_DECLSPEC NET_DatagramSocket * SDLCALL NET_CreateDatagramSocket(NET_Address *addr, Uint16 port);
+extern SDL_DECLSPEC NET_DatagramSocket * SDLCALL NET_CreateDatagramSocket(NET_Address *addr, Uint16 port, SDL_PropertiesID props);
+
+#define NET_PROP_DATAGRAM_SOCKET_REUSEADDR_BOOLEAN "NET.datagram_socket.reuseaddr"
+#define NET_PROP_DATAGRAM_SOCKET_ALLOW_BROADCAST_BOOLEAN "NET.datagram_socket.allow_broadcast"
+
/**
* Send a new packet over a datagram socket to a remote system.
@@ -1264,8 +1319,35 @@ extern SDL_DECLSPEC NET_DatagramSocket * SDLCALL NET_CreateDatagramSocket(NET_Ad
* should assume it is no longer usable and should destroy it with
* SDL_DestroyDatagramSocket().
*
+ * Sending to a NULL address is treated as a request to broadcast a packet.
+ * Note that this will report failure immediately if the socket was not
+ * created with broadcast permission. Broadcast packets are (more or less)
+ * sent to every machine on the LAN, unconditionally.
+ *
+ * **WARNING**: It is possible to build a game where everyone is playing on
+ * the same LAN, and every player is simply broadcasting packets. This is
+ * absolutely the wrong thing to do, however. Broadcast packets go to every
+ * device on the LAN, whether they want them or not. The game DOOM, in its
+ * heyday, was capable of [bringing entire networks to their knees](https://doomwiki.org/wiki/Doom_in_workplaces),
+ * as many players on the same network would all be broadcasting relentlessly.
+ *
+ * In practice, broadcasting sparingly can be useful for certain
+ * functionality: a LAN-only client broadcasting a few packets to ask for
+ * available servers, and running servers replying directly to that client
+ * without broadcasting at all, is reasonable and safe. Once clients and
+ * servers have found each other, they can communicate directly without
+ * any broadcasting at all. For peer-to-peer games, once connection is
+ * established, it's better to either send unique packets to each known
+ * player, or use a multicasting (which works like broadcast, but only
+ * routes packets to devices that are explicitly listening for it).
+ *
+ * With IPv6, which doesn't support broadcasts, broadcasting is faked with
+ * multicast to the all-nodes link-local multicast group, ff02::1, either on a
+ * specific interface or letting the OS choose the default. Other protocols
+ * might fake broadcast operations in similar ways in the future.
+ *
* \param sock the datagram socket to send data through.
- * \param address the NET_Address object address.
+ * \param address the NET_Address object address. May be NULL to broadcast.
* \param port the address port.
* \param buf a pointer to the data to send as a single packet.
* \param buflen the size of the data to send, in bytes.
diff --git a/src/SDL_net.c b/src/SDL_net.c
index 35dbb68d..feb66d96 100644
--- a/src/SDL_net.c
+++ b/src/SDL_net.c
@@ -253,6 +253,10 @@ static int num_interfaces = 0;
static NetworkInterface *interfaces = NULL;
static SDL_AtomicInt interfaces_have_changed;
+// Other stuff...
+static NET_Address *ipv6_broadcast_addr = NULL;
+
+
// between lo and hi (inclusive; it can return lo or hi itself, too!).
static int RandomNumberBetween(const int lo, const int hi)
{
@@ -1026,6 +1030,13 @@ bool NET_Init(void)
}
}
+ struct sockaddr_in6 sa_in6;
+ SDL_zero(sa_in6);
+ if (inet_pton(AF_INET6, "ff02::1", &sa_in6.sin6_addr) == 1) {
+ sa_in6.sin6_family = AF_INET6;
+ ipv6_broadcast_addr = CreateSDLNetAddrFromSockAddr((const struct sockaddr *) &sa_in6, sizeof (sa_in6));
+ }
+
return true; // good to go.
failed:
@@ -1103,6 +1114,9 @@ void NET_Quit(void)
SDL_assert(!interface_rwlock);
SDL_assert(num_interfaces == 0);
+ NET_UnrefAddress(ipv6_broadcast_addr);
+ ipv6_broadcast_addr = NULL;
+
#ifdef SDL_PLATFORM_WINDOWS
WSACleanup();
#endif
@@ -1367,7 +1381,7 @@ struct NET_StreamSocket
Uint64 simulated_failure_until;
};
-NET_StreamSocket *NET_CreateClient(NET_Address *addr, Uint16 port)
+NET_StreamSocket *NET_CreateClient(NET_Address *addr, Uint16 port, SDL_PropertiesID props)
{
if (addr == NULL) {
SDL_InvalidParamError("address");
@@ -1466,7 +1480,7 @@ struct NET_Server
Socket handle_pool[4];
};
-NET_Server *NET_CreateServer(NET_Address *addr, Uint16 port)
+NET_Server *NET_CreateServer(NET_Address *addr, Uint16 port, SDL_PropertiesID props)
{
if (addr && (((NET_Status) SDL_GetAtomicInt(&addr->status)) != NET_SUCCESS)) {
SDL_SetError("Address is not resolved"); // strictly speaking, this should be a local interface, but a resolved address can fail later.
@@ -1510,6 +1524,8 @@ NET_Server *NET_CreateServer(NET_Address *addr, Uint16 port)
}
}
+ const int reuseaddr = SDL_GetBooleanProperty(props, NET_PROP_SERVER_REUSEADDR_BOOLEAN, true) ? 1 : 0;
+
// Make sockets for all desired interfaces; if addr!=NULL, this is one socket on one interface,
// but if addr==NULL, it might be multiple sockets for IPv4, IPv6, etc, bound to their INADDR_ANY equivalent.
struct addrinfo *ainfo = addrwithport;
@@ -1533,7 +1549,7 @@ NET_Server *NET_CreateServer(NET_Address *addr, Uint16 port)
setsockopt(handle, IPPROTO_IPV6, IPV6_V6ONLY, (const char *) &one, sizeof (one)); // if this fails, oh well.
}
- setsockopt(handle, SOL_SOCKET, SO_REUSEADDR, (const char *) &one, sizeof (one));
+ setsockopt(handle, SOL_SOCKET, SO_REUSEADDR, (const char *) &reuseaddr, sizeof (reuseaddr));
int rc = bind(handle, ainfo->ai_addr, (SockLen) ainfo->ai_addrlen);
if (rc == SOCKET_ERROR) {
@@ -1850,6 +1866,7 @@ typedef struct NET_DatagramSocketHandle
Socket handle;
int family;
int protocol;
+ NET_Address *broadcast;
} NET_DatagramSocketHandle;
struct NET_DatagramSocket
@@ -1867,10 +1884,64 @@ struct NET_DatagramSocket
NET_Datagram **pending_output;
int pending_output_len;
int pending_output_allocation;
+ bool allow_broadcast;
};
-NET_DatagramSocket *NET_CreateDatagramSocket(NET_Address *addr, Uint16 port)
+// if `addr` isn't from NET_GetLocalAddresses(), lookup the actual NET_Address
+// that matches it. This is so people can get an interface address specified on
+// the command line by "resolving" the command line string into a NET_Address,
+// and we'll figure out if it matches here, if necessary.
+// Caller must hold SDL_LockRWLockForReading(interface_rwlock)!
+static NetworkInterface *GetInterfaceForAddress(NET_Address *addr)
+{
+ if (!addr) {
+ SDL_InvalidParamError("interface");
+ return NULL;
+ } else if (!InterfacesReady()) {
+ return NULL;
+ }
+
+ for (int i = 0; i < num_interfaces; i++) {
+ if (NET_CompareAddresses(addr, interfaces[i].address) == 0) {
+ return &interfaces[i];
+ }
+ }
+
+ return NULL;
+}
+
+static NET_Address *FindBroadcastAddress(struct addrinfo *ainfo, Uint32 *interface_index)
+{
+ NET_Address *retval = NULL;
+
+ NET_Address *iface = CreateSDLNetAddrFromSockAddr(ainfo->ai_addr, (SockLen) ainfo->ai_addrlen);
+ SDL_LockRWLockForReading(interface_rwlock);
+ NetworkInterface *ni = GetInterfaceForAddress(iface);
+ NET_UnrefAddress(iface);
+ if (!ni) {
+ SDL_SetError("Not a network interface address");
+ } else {
+ SDL_assert(ni->address != NULL);
+ *interface_index = ni->index;
+ if (ni->broadcast != NULL) {
+ retval = NET_RefAddress(ni->broadcast); // we calculated the broadcast address when discovering this interface. It's probably IPv4. We're good to go.
+ } else if (ainfo->ai_family == AF_INET6) { // we fake this for IPv6 on the all-nodes link-local multicast group.
+ retval = NET_RefAddress(ipv6_broadcast_addr);
+ } else {
+ SDL_SetError("Can't determine broadcast address for this interface");
+ }
+ }
+ SDL_UnlockRWLock(interface_rwlock);
+
+ if (!retval && (ainfo->ai_family == AF_INET6)) { // we fake this for IPv6 on the all-nodes link-local multicast group.
+ retval = NET_RefAddress(ipv6_broadcast_addr);
+ }
+
+ return retval;
+}
+
+NET_DatagramSocket *NET_CreateDatagramSocket(NET_Address *addr, Uint16 port, SDL_PropertiesID props)
{
if (addr && (((NET_Status) SDL_GetAtomicInt(&addr->status)) != NET_SUCCESS)) {
SDL_SetError("Address is not resolved"); // strictly speaking, this should be a local interface, but a resolved address can fail later.
@@ -1892,6 +1963,11 @@ NET_DatagramSocket *NET_CreateDatagramSocket(NET_Address *addr, Uint16 port)
sock->addr = addr;
sock->port = port;
+ const int reuseaddr = SDL_GetBooleanProperty(props, NET_PROP_DATAGRAM_SOCKET_REUSEADDR_BOOLEAN, true) ? 1 : 0;
+ sock->allow_broadcast = SDL_GetBooleanProperty(props, NET_PROP_DATAGRAM_SOCKET_ALLOW_BROADCAST_BOOLEAN, false);
+
+ const int bcast = sock->allow_broadcast ? 1 : 0;
+
int num_handles = 0;
NET_DatagramSocketHandle *allocated_handles = NULL;
if (addr != NULL) {
@@ -1937,24 +2013,26 @@ NET_DatagramSocket *NET_CreateDatagramSocket(NET_Address *addr, Uint16 port)
goto failed;
}
- sock->handles[sock->num_handles].handle = handle;
- sock->handles[sock->num_handles].family = ainfo->ai_family;
- sock->handles[sock->num_handles].protocol = ainfo->ai_protocol;
- sock->num_handles++;
+ NET_DatagramSocketHandle *socket_handle = &sock->handles[sock->num_handles++];
+
+ SDL_zerop(socket_handle);
+ socket_handle->handle = handle;
+ socket_handle->family = ainfo->ai_family;
+ socket_handle->protocol = ainfo->ai_protocol;
if (MakeSocketNonblocking(handle) < 0) {
SDL_SetError("Failed to make new socket non-blocking");
goto failed;
}
- const int one = 1;
+ setsockopt(handle, SOL_SOCKET, SO_REUSEADDR, (const char *) &reuseaddr, sizeof (reuseaddr));
+ setsockopt(handle, SOL_SOCKET, SO_BROADCAST, (const char *) &bcast, sizeof (bcast));
+
if (ainfo->ai_family == AF_INET6) {
+ const int one = 1;
setsockopt(handle, IPPROTO_IPV6, IPV6_V6ONLY, (const char *) &one, sizeof (one)); // if this fails, oh well.
}
- setsockopt(handle, SOL_SOCKET, SO_REUSEADDR, (const char *) &one, sizeof (one));
- setsockopt(handle, SOL_SOCKET, SO_BROADCAST, (const char *) &one, sizeof (one));
-
const int rc = bind(handle, ainfo->ai_addr, (SockLen) ainfo->ai_addrlen);
if (rc == SOCKET_ERROR) {
const int err = LastSocketError();
@@ -1962,6 +2040,34 @@ NET_DatagramSocket *NET_CreateDatagramSocket(NET_Address *addr, Uint16 port)
SetSocketError("Failed to bind socket", err);
goto failed;
}
+
+ if (sock->allow_broadcast) {
+ Uint32 interface_index = 0; // this will stay zero for INADDR6_ANY, to pick a default interface.
+ socket_handle->broadcast = FindBroadcastAddress(ainfo, &interface_index);
+ // if failed but addr==NULL, this is IPv4 and we can't find an interface for INADDR_ANY. We'll broadcast to all interfaces when sending (because 255.255.255.255 doesn't work on Windows).
+ // alternately, we're IPv6 and ipv6_broadcast_addr failed to init for some reason. Just panic and fail here if that happens, I guess.
+ if (!socket_handle->broadcast && (addr || (ainfo->ai_family == AF_INET6))) {
+ SDL_SetError("Failed to determine broadcast address for this interface");
+ goto failed;
+ } else if (ainfo->ai_family == AF_INET6) { // fake broadcast support via multicasting to all-nodes link-local group, ff02::1.
+ SDL_assert(socket_handle->broadcast == ipv6_broadcast_addr);
+ const struct addrinfo *bai = socket_handle->broadcast->ainfo;
+ SDL_assert(bai != NULL);
+ SDL_assert(bai->ai_addr != NULL);
+ SDL_assert(bai->ai_addr->sa_family == AF_INET6);
+ struct ipv6_mreq mreq6;
+ SDL_zero(mreq6);
+ SDL_copyp(&mreq6.ipv6mr_multiaddr, &((struct sockaddr_in6 *) bai->ai_addr)->sin6_addr);
+ mreq6.ipv6mr_interface = interface_index;
+ //SDL_Log("Add membership for %s, index=%d", socket_handle->broadcast->human_readable, (int) interface_index);
+ if (setsockopt(handle, IPPROTO_IPV6, IPV6_JOIN_GROUP, (const char *) &mreq6, (SockLen) sizeof (mreq6)) < 0) {
+ SDL_SetError("Failed to join all-nodes link-local multicast group for broadcasting");
+ goto failed;
+ }
+ const int ifidx = (int) interface_index;
+ setsockopt(handle, IPPROTO_IPV6, IPV6_MULTICAST_IF, (const char *) &ifidx, (SockLen) sizeof (ifidx)); // multicast sends go through the same interface.
+ }
+ }
}
freeaddrinfo(addrwithport);
@@ -1972,6 +2078,7 @@ NET_DatagramSocket *NET_CreateDatagramSocket(NET_Address *addr, Uint16 port)
failed:
for (int i = 0; i < sock->num_handles; i++) {
CloseSocketHandle(sock->handles[i].handle);
+ NET_UnrefAddress(sock->handles[i].broadcast);
}
freeaddrinfo(addrwithport);
SDL_free(allocated_handles);
@@ -1981,29 +2088,94 @@ NET_DatagramSocket *NET_CreateDatagramSocket(NET_Address *addr, Uint16 port)
static NET_Status SendOneDatagram(NET_DatagramSocket *sock, NET_Address *addr, Uint16 port, const void *buf, int buflen)
{
- struct addrinfo *addrwithport = MakeAddrInfoWithPort(addr, SOCK_DGRAM, port);
- if (!addrwithport) {
+ if (addr) { // unicast to a specific address.
+ struct addrinfo *addrwithport = MakeAddrInfoWithPort(addr, SOCK_DGRAM, port);
+ if (!addrwithport) {
+ return NET_FAILURE;
+ }
+
+ const int family = addrwithport->ai_family;
+ const int protocol = addrwithport->ai_protocol;
+ for (int i = 0; i < sock->num_handles; i++) {
+ const NET_DatagramSocketHandle *handle = &sock->handles[i];
+ if ((handle->family == family) && (handle->protocol == protocol)) { // !!! FIXME: strictly speaking, this _probably_ just needs to check `family`, right?
+ const int rc = sendto(handle->handle, buf, (size_t) buflen, 0, addrwithport->ai_addr, (SockLen) addrwithport->ai_addrlen);
+ const int err = (rc == SOCKET_ERROR) ? LastSocketError() : 0;
+ freeaddrinfo(addrwithport);
+ if (err != 0) {
+ return WouldBlock(err) ? NET_WAITING : SetSocketError("Failed to send from socket", err);
+ }
+ SDL_assert(rc == buflen);
+ return NET_SUCCESS; // !!! FIXME: should we sent to _all_ interfaces in this family?
+ }
+ }
+ SDL_SetError("Unsupported network family in destination address");
return NET_FAILURE;
+
}
- const int family = addrwithport->ai_family;
- const int protocol = addrwithport->ai_protocol;
+ // broadcast (or fake with multicast) this packet.
+ SDL_assert(sock->allow_broadcast); // we should have checked this in NET_SendDatagram!
+
+ bool all_wouldblock = false;
+ NET_Status retval = NET_FAILURE;
for (int i = 0; i < sock->num_handles; i++) {
const NET_DatagramSocketHandle *handle = &sock->handles[i];
- if ((handle->family == family) && (handle->protocol == protocol)) { // !!! FIXME: strictly speaking, this _probably_ just needs to check `family`, right?
+
+ if (handle->broadcast) {
+ struct addrinfo *addrwithport = MakeAddrInfoWithPort(handle->broadcast, SOCK_DGRAM, port);
+ if (!addrwithport) {
+ continue; // oh well, lost UDP packet, I guess.
+ }
+
+ //SDL_Log("Broadcasting on %s ...", handle->broadcast->human_readable);
const int rc = sendto(handle->handle, buf, (size_t) buflen, 0, addrwithport->ai_addr, (SockLen) addrwithport->ai_addrlen);
const int err = (rc == SOCKET_ERROR) ? LastSocketError() : 0;
freeaddrinfo(addrwithport);
- if (err != 0) {
- return WouldBlock(err) ? NET_WAITING : SetSocketError("Failed to send from socket", err);
+ if (!err) {
+ retval = NET_SUCCESS; // it went to at least one interface's broadcast address, we'll call it success.
+ } else {
+ if (!WouldBlock(err)) {
+ all_wouldblock = false;
+ SetSocketError("Failed to send from socket", err); // so there's a clear error message, but keep going, maybe something else works out.
+ }
}
- SDL_assert(rc == buflen);
- return NET_SUCCESS;
+ } else if (!addr) { // iterate all interfaces for this broadcast.
+ SDL_LockRWLockForReading(interface_rwlock);
+ for (int i = 0; i < num_interfaces; i++) {
+ const NET_Address *bc = interfaces[i].broadcast;
+ if (!bc) { continue; }
+ const struct addrinfo *ainfo = bc->ainfo;
+ SDL_assert(ainfo != NULL);
+ if (ainfo->ai_family != handle->family) { continue; }
+
+ str
(Patch may be truncated, please check the link at the top of this post.)