From feeb0a2e40083ce9176c1f1ebda4b4eb4745b3f4 Mon Sep 17 00:00:00 2001 From: Rosalie Wanders Date: Thu, 18 Jun 2026 14:01:04 +0200 Subject: [PATCH] 3rdParty: update SDL_net --- Source/3rdParty/SDL_net/.gitrepo | 6 +- Source/3rdParty/SDL_net/Android.mk | 2 +- Source/3rdParty/SDL_net/CMakeLists.txt | 55 +- Source/3rdParty/SDL_net/INSTALL.md | 47 ++ Source/3rdParty/SDL_net/README.md | 21 +- .../SDL_net/cmake/PrivateSdlFunctions.cmake | 2 +- .../SDL_net/include/SDL3_net/SDL_net.h | 152 ++++- Source/3rdParty/SDL_net/src/SDL_net.c | 568 +++++++++++++++--- Source/3rdParty/SDL_net/src/SDL_net.exports | 1 + Source/3rdParty/SDL_net/src/SDL_net.sym | 1 + Source/3rdParty/SDL_net/src/SDL_net_haiku.cpp | 82 +++ .../3rdParty/SDL_net/src/SDL_net_stub_only.c | 59 ++ Source/3rdParty/SDL_net/src/version.rc | 8 +- 13 files changed, 874 insertions(+), 130 deletions(-) create mode 100644 Source/3rdParty/SDL_net/INSTALL.md create mode 100644 Source/3rdParty/SDL_net/src/SDL_net_haiku.cpp create mode 100644 Source/3rdParty/SDL_net/src/SDL_net_stub_only.c diff --git a/Source/3rdParty/SDL_net/.gitrepo b/Source/3rdParty/SDL_net/.gitrepo index bed454e9..eddbfd69 100644 --- a/Source/3rdParty/SDL_net/.gitrepo +++ b/Source/3rdParty/SDL_net/.gitrepo @@ -6,7 +6,7 @@ [subrepo] remote = git@github.com:libsdl-org/SDL_net.git branch = main - commit = 4dd85af45d8597f93796815fba0defa5d499be21 - parent = 02eef161e7f4ac7a4deebd0115574b7a27c5f58f + commit = 4dd9d8418e9781d10481fd6177e3a1064ca201fe + parent = b0c09d17c97848d0386a11b3b6fdd572a2b2228a method = merge - cmdver = 0.4.9 + cmdver = 0.4.6 diff --git a/Source/3rdParty/SDL_net/Android.mk b/Source/3rdParty/SDL_net/Android.mk index 31db3b0c..af98a51e 100644 --- a/Source/3rdParty/SDL_net/Android.mk +++ b/Source/3rdParty/SDL_net/Android.mk @@ -18,7 +18,7 @@ LOCAL_SRC_FILES := \ $(subst $(LOCAL_PATH)/,,) \ $(wildcard $(LOCAL_PATH)/src/*.c) \ -LOCAL_CFLAGS = +LOCAL_CFLAGS = -DHAVE_GETIFADDRS # Warnings we haven't fixed (yet) LOCAL_CFLAGS += -Wno-unused-parameter -Wno-sign-compare diff --git a/Source/3rdParty/SDL_net/CMakeLists.txt b/Source/3rdParty/SDL_net/CMakeLists.txt index e5430513..5b89ac98 100644 --- a/Source/3rdParty/SDL_net/CMakeLists.txt +++ b/Source/3rdParty/SDL_net/CMakeLists.txt @@ -8,7 +8,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") # See docs/release_checklist.md set(MAJOR_VERSION 3) -set(MINOR_VERSION 0) +set(MINOR_VERSION 3) set(MICRO_VERSION 0) set(SDL_REQUIRED_VERSION 3.0.0) @@ -67,7 +67,7 @@ include(CMakePackageConfigHelpers) include(GNUInstallDirs) set(PLATFORM_SUPPORTS_SHARED ON) -if(EMSCRIPTEN OR VITA OR PSP OR PS2 OR N3DS OR RISCOS) +if(EMSCRIPTEN OR VITA OR PSP OR PS2 OR N3DS OR RISCOS OR DOS) set(PLATFORM_SUPPORTS_SHARED OFF) endif() @@ -104,9 +104,46 @@ endif() set(PC_LIBS) set(PC_REQUIRES) -add_library(${sdl3_net_target_name} src/SDL_net.c) +if(HAIKU) + set(HAVE_GETIFADDRS True) # Haiku always has it (but you have to -lnetwork, so check_c_source_compiles won't work as-is). + set(EXTRA_SOURCES "src/SDL_net_haiku.cpp") + enable_language(CXX) +endif() + +set(PLATFORM_UNSUPPORTED False) +if (DOS OR EMSCRIPTEN) # These either don't have networking at all, or have needs we don't currently meet. + set(PLATFORM_UNSUPPORTED True) +endif() + +option(SDLNET_STUB_ONLY "Build a stub library that does nothing" ${PLATFORM_UNSUPPORTED}) +if(SDLNET_STUB_ONLY) + message(WARNING "Only building a stub library. The functions will be there but all of them will fail!") + add_library(${sdl3_net_target_name} src/SDL_net_stub_only.c) +else() + add_library(${sdl3_net_target_name} src/SDL_net.c ${EXTRA_SOURCES}) +endif() + +if(NOT (HAIKU OR WIN32 OR SDLNET_STUB_ONLY)) + check_c_source_compiles(" + #include + int main(void) { + struct ifaddrs* ifap; + getifaddrs(&ifap); + return 0; + } + " HAVE_GETIFADDRS) +endif() + +if(HAVE_GETIFADDRS) + target_compile_definitions(${sdl3_net_target_name} PRIVATE HAVE_GETIFADDRS) +endif() + add_library(SDL3_net::${sdl3_net_target_name} ALIAS ${sdl3_net_target_name}) -set_property(TARGET ${sdl3_net_target_name} PROPERTY C_STANDARD 99) +if("c_std_99" IN_LIST CMAKE_C_COMPILE_FEATURES) + target_compile_features(${sdl3_net_target_name} PRIVATE c_std_99) +else() + message(WARNING "target_compile_features does not know c_std_99 for C compiler") +endif() if(NOT TARGET SDL3_net::SDL3_net) add_library(SDL3_net::SDL3_net ALIAS ${sdl3_net_target_name}) endif() @@ -140,8 +177,8 @@ if(WIN32) target_link_libraries(${sdl3_net_target_name} PRIVATE iphlpapi ws2_32) list(APPEND PC_LIBS -liphlpapi -lws2_32) endif() -if(CMAKE_SYSTEM_NAME MATCHES "Haiku.*") - target_link_libraries(${sdl3_net_target_name} PRIVATE network) +if(HAIKU) + target_link_libraries(${sdl3_net_target_name} PRIVATE network be bnetapi) endif() set_target_properties(${sdl3_net_target_name} PROPERTIES OUTPUT_NAME "SDL3_net" @@ -306,7 +343,10 @@ if(SDLNET_SAMPLES) sdl_target_link_options_no_undefined(${TARGET}) target_link_libraries(${TARGET} PRIVATE SDL3_net::${sdl3_net_target_name}) target_link_libraries(${TARGET} PRIVATE ${sdl3_target_name}) - set_property(TARGET ${TARGET} PROPERTY C_STANDARD 99) + + if("c_std_99" IN_LIST CMAKE_C_COMPILE_FEATURES) + target_compile_features(${TARGET} PRIVATE c_std_99) + endif() set_property(TARGET ${TARGET} PROPERTY C_EXTENSIONS FALSE) if(SDLNET_SAMPLES_INSTALL) @@ -320,6 +360,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/Source/3rdParty/SDL_net/INSTALL.md b/Source/3rdParty/SDL_net/INSTALL.md new file mode 100644 index 00000000..c6e3f0be --- /dev/null +++ b/Source/3rdParty/SDL_net/INSTALL.md @@ -0,0 +1,47 @@ +# To build and use SDL_net: + +SDL_net supports a number of development environments: +- [CMake](docs/INTRO-cmake.md) +- [Visual Studio on Windows](docs/INTRO-visualstudio.md) +- [Xcode on Apple platforms](docs/INTRO-xcode.md) + +SDL_net is also usable in other environments. The basic steps are to use CMake to build the library and then use the headers and library that you built in your project. You can search online to see if anyone has specific steps for your setup. + +# Documentation + +An API reference and additional documentation is available at: + +https://wiki.libsdl.org/SDL3_net + +# Example code + +There are simple example programs in the examples directory. + +If you're using CMake, you can build them adding `-DSDLNET_SAMPLES=ON` to the CMake command line when building SDL_net. + +If you're using Visual Studio there are separate projects in the VisualC directory. + +If you're using Xcode there are separate projects in the Xcode directory. + +# Discussions + +## Discord + +You can join the official Discord server at: + +https://discord.com/invite/BwpFGBWsv8 + +## Forums/mailing lists + +You can join SDL development discussions at: + +https://discourse.libsdl.org/ + +Once you sign up, you can use the forum through the website or as a mailing list from your email client. + +## Announcement list + +You can sign up for the low traffic announcement list at: + +https://www.libsdl.org/mailing-list.php + diff --git a/Source/3rdParty/SDL_net/README.md b/Source/3rdParty/SDL_net/README.md index 4ce286ad..68c72157 100644 --- a/Source/3rdParty/SDL_net/README.md +++ b/Source/3rdParty/SDL_net/README.md @@ -1,19 +1,18 @@ # SDL_net 3.0 -The latest version of this library is available from GitHub: - -https://github.com/libsdl-org/SDL_net/releases - This is a portable network library for use with SDL. It's goal is to -simplify the use of the usual socket interfaces and use SDL infrastructure -to handle some portability things (such as threading and reporting -errors). +simplify the use of the usual socket interfaces and use SDL to handle +common portable functionality such as threading and reporting errors. -It is available under the zlib license, found in the file LICENSE.txt. -The API can be found in the file SDL_net.h and online at https://wiki.libsdl.org/SDL3_net This library supports most platforms that offer both SDL3 and networking. -This is a work in progress! +The latest version of this library is available from GitHub: +https://github.com/libsdl-org/SDL_net/releases + +Installation instructions and a quick introduction is available in +[INSTALL.md](INSTALL.md) + +This library is distributed under the terms of the zlib license, +available in [LICENSE.txt](LICENSE.txt). Enjoy! - diff --git a/Source/3rdParty/SDL_net/cmake/PrivateSdlFunctions.cmake b/Source/3rdParty/SDL_net/cmake/PrivateSdlFunctions.cmake index 0f3eb406..afabcd73 100644 --- a/Source/3rdParty/SDL_net/cmake/PrivateSdlFunctions.cmake +++ b/Source/3rdParty/SDL_net/cmake/PrivateSdlFunctions.cmake @@ -199,7 +199,7 @@ function(target_get_dynamic_library DEST TARGET) endif() if(result) string(TOLOWER "${result}" result_lower) - if(WIN32 OR OS2) + if(WIN32 OR OS2 OR CYGWIN) if(NOT result_lower MATCHES ".*dll") message(FATAL_ERROR "\"${result}\" is not a .dll library") endif() diff --git a/Source/3rdParty/SDL_net/include/SDL3_net/SDL_net.h b/Source/3rdParty/SDL_net/include/SDL3_net/SDL_net.h index a7a2ca87..2d700ecc 100644 --- a/Source/3rdParty/SDL_net/include/SDL3_net/SDL_net.h +++ b/Source/3rdParty/SDL_net/include/SDL3_net/SDL_net.h @@ -27,7 +27,7 @@ * SDL_net is a simple library to help with networking. * * In current times, it's a relatively thin layer over system-level APIs like - * BSD Sockets or WinSock. It's primary strength is in making those interfaces + * BSD Sockets or WinSock. Its primary strength is in making those interfaces * less complicated to use, and handling several unexpected corner cases, so * the app doesn't have to. * @@ -136,7 +136,7 @@ extern "C" { * * \since This macro is available since SDL_net 3.0.0. */ -#define SDL_NET_MINOR_VERSION 0 +#define SDL_NET_MINOR_VERSION 3 /** * The current micro (or patchlevel) version of the SDL_net headers. @@ -415,6 +415,50 @@ extern SDL_DECLSPEC NET_Status SDLCALL NET_GetAddressStatus(NET_Address *address */ extern SDL_DECLSPEC const char * SDLCALL NET_GetAddressString(NET_Address *address); +/** + * Get the protocol-level bytes of a network address from a resolved address. + * + * This data is not human-readable, is protocol-specific, and might not even + * be in a specific byte order. + * + * This is only useful for possibly hashing, to map a address to a specific + * player in a game, or possibly for handing to a system-level networking API + * (which is _not_ recommended; an app does this at their own risk). + * + * Do not store these bytes for future runs of the program; there is no + * promise the format won't change. + * + * On return `*num_bytes` will hold the number of bytes provided with the + * address. Since the data is not NULL-terminated, this is the only way to + * determine its size; as such, this parameter must not be NULL. + * + * Do not free or modify the returned data; it belongs to the NET_Address that + * was queried, and is valid as long as the object lives. Either make sure the + * address has a reference as long as you need this or make a copy of the + * bytes. + * + * This will return NULL if resolution is still in progress, or if resolution + * failed. You can use NET_GetAddressStatus() or NET_WaitUntilResolved() to + * make sure resolution has successfully completed before calling this. + * + * A human-readable version is available in NET_GetAddressString() and isn't + * any less efficient to query than the raw bytes. + * + * \param address The NET_Address to query. + * \param num_bytes on return, will be set to the number of bytes returned. + * \returns a pointer to bytes, or NULL on error; call SDL_GetError() for + * details. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL_net 3.0.0. + * + * \sa NET_GetAddressString + * \sa NET_GetAddressStatus + * \sa NET_WaitUntilResolved + */ +extern SDL_DECLSPEC const void * SDLCALL NET_GetAddressBytes(NET_Address *address, int *num_bytes); + /** * Add a reference to an NET_Address. * @@ -640,8 +684,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 +702,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 +799,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 +828,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. @@ -1029,7 +1097,7 @@ extern SDL_DECLSPEC int SDLCALL NET_WaitUntilStreamSocketDrained(NET_StreamSocke * on what is available at the time, and also the app isn't required to read * all available data at once. * - * This call never blocks; if no new data isn't available at the time of the + * This call never blocks; if no new data is available at the time of the * call, it returns 0 immediately. The caller can try again later. * * If the connection has failed (remote side dropped us, or one of a million @@ -1163,8 +1231,8 @@ typedef struct NET_DatagramSocket NET_DatagramSocket; */ typedef struct NET_Datagram { - NET_Address *addr; /**< this is unref'd by NET_DestroyDatagram. You only need to ref it if you want to keep it. */ - Uint16 port; /**< these do not have to come from the same port the receiver is bound to. These are in host byte order, don't byteswap them! */ + NET_Address *addr; /**< Sender's address. This is unref'd by NET_DestroyDatagram. You only need to ref it if you want to keep it. */ + Uint16 port; /**< Sender's port. These do not have to come from the same port the receiver is bound to. These are in host byte order, don't byteswap them! */ Uint8 *buf; /**< the payload of this datagram. */ int buflen; /**< the number of bytes available at `buf`. */ } NET_Datagram; @@ -1213,10 +1281,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 +1322,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. @@ -1239,7 +1338,9 @@ extern SDL_DECLSPEC NET_DatagramSocket * SDLCALL NET_CreateDatagramSocket(NET_Ad * * Datagram packets might arrive in a different order than you sent them, or * they may just be lost while travelling across the network. You have to plan - * for this. + * for this. As an added confusion, since SDL_net might send the same packet + * on multiple interfaces, you might get duplicate packets, possibly from + * different network addresses. You have to plan for this, too. * * You can send to any address and port on the network, but there has to be a * datagram socket waiting for the data on the other side for the packet not @@ -1262,8 +1363,37 @@ 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. @@ -1287,7 +1417,7 @@ extern SDL_DECLSPEC bool SDLCALL NET_SendDatagram(NET_DatagramSocket *sock, NET_ * Datagram sockets send packets of data. They either arrive as complete * packets or they don't arrive at all, so you'll never receive half a packet. * - * This call never blocks; if no new data isn't available at the time of the + * This call never blocks; if no new data is available at the time of the * call, it returns true immediately. The caller can try again later. * * On a successful call to this function, it returns true, even if no new diff --git a/Source/3rdParty/SDL_net/src/SDL_net.c b/Source/3rdParty/SDL_net/src/SDL_net.c index 46aae63b..799cda8d 100644 --- a/Source/3rdParty/SDL_net/src/SDL_net.c +++ b/Source/3rdParty/SDL_net/src/SDL_net.c @@ -171,7 +171,9 @@ static int WindowsPoll(struct pollfd *fds, unsigned int nfds, int timeout) #include #include #include +#ifndef SDL_PLATFORM_VITA #include +#endif #include #include #include @@ -182,15 +184,18 @@ static int WindowsPoll(struct pollfd *fds, unsigned int nfds, int timeout) #if defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_ANDROID) #define USE_NETLINK 1 -#endif - -#ifdef USE_NETLINK #include #include -#else +#endif + +#ifdef HAVE_GETIFADDRS #include #endif +#ifdef SDL_PLATFORM_VITA +#include +#endif + #define INVALID_SOCKET -1 #define SOCKET_ERROR -1 typedef int Socket; @@ -211,13 +216,7 @@ int NET_Version(void) return SDL_NET_VERSION; } -typedef enum NET_AddressType -{ - NET_ADDRTYPE_UNKNOWN=-1, - NET_ADDRTYPE_UNICAST, - NET_ADDRTYPE_MULTICAST, - NET_ADDRTYPE_BROADCAST -} NET_AddressType; +typedef struct NetworkInterface NetworkInterface; struct NET_Address { @@ -226,18 +225,17 @@ struct NET_Address char *errstr; SDL_AtomicInt refcount; SDL_AtomicInt status; // This is actually a NET_Status. - NET_AddressType type; struct addrinfo *ainfo; NET_Address *resolver_next; // a linked list for the resolution job queue. }; -typedef struct NetworkInterfaces +struct NetworkInterface { char *name; - int index; + Uint32 index; NET_Address *address; NET_Address *broadcast; -} NetworkInterfaces; +}; #define MIN_RESOLVER_THREADS 2 @@ -257,9 +255,13 @@ static SDL_AtomicInt resolver_percent_loss; static SDL_InitState interface_init; static SDL_RWLock *interface_rwlock = NULL; static int num_interfaces = 0; -static NetworkInterfaces *interfaces = NULL; +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) { @@ -381,7 +383,7 @@ static NET_Address *CreateSDLNetAddrFromSockAddr(const struct sockaddr *saddr, S /* Network interface enumeration... */ -static void FreeNetworkInterfaces(NetworkInterfaces *ifaces, int num_ifaces) +static void FreeNetworkInterfaces(NetworkInterface *ifaces, int num_ifaces) { for (int i = 0; i < num_ifaces; i++) { NET_UnrefAddress(ifaces[i].address); @@ -447,7 +449,7 @@ static void RefreshInterfaces(void) // WINDOWS VERSION } } - NetworkInterfaces *new_interfaces = (NetworkInterfaces *) SDL_calloc(new_num_interfaces + 1, sizeof (*new_interfaces)); + NetworkInterface *new_interfaces = (NetworkInterface *) SDL_calloc(new_num_interfaces + 1, sizeof (*new_interfaces)); if (!new_interfaces) { SDL_free(addrs); return; @@ -455,30 +457,32 @@ static void RefreshInterfaces(void) // WINDOWS VERSION int count = 0; for (IP_ADAPTER_ADDRESSES *i = addrs; i != NULL; i = i->Next) { + char *friendlyname = SDL_iconv_string("UTF-8", "UTF-16LE", (const char*)(i->FriendlyName), (SDL_wcslen(i->FriendlyName) + 1) * sizeof(WCHAR)); for (IP_ADAPTER_UNICAST_ADDRESS *j = i->FirstUnicastAddress; j != NULL; j = j->Next) { NET_Address *addr = CreateSDLNetAddrFromSockAddr(j->Address.lpSockaddr, j->Address.iSockaddrLength); if (addr) { + new_interfaces[count].name = friendlyname ? SDL_strdup(friendlyname) : NULL; new_interfaces[count].address = addr; if (j->Address.lpSockaddr->sa_family == AF_INET6) { - new_interfaces[count].index = i->Ipv6IfIndex; + new_interfaces[count].index = (Uint32) i->Ipv6IfIndex; } else if (j->Address.lpSockaddr->sa_family == AF_INET) { // if this is IPv4, calculate the broadcast address. SOCKADDR_IN bcast; SDL_assert(j->Address.iSockaddrLength == sizeof (bcast)); SDL_memcpy(&bcast, (const SOCKADDR_IN *) j->Address.lpSockaddr, sizeof (SOCKADDR_IN)); - bcast.sin_addr.S_un.S_addr |= htonl(~((1 << (32 - j->OnLinkPrefixLength)) - 1)); - new_interfaces[count].broadcast = CreateSDLNetAddrFromSockAddr((const struct sockaddr *) &bcast, sizeof (bcast)); - new_interfaces[count].broadcast->type = NET_ADDRTYPE_BROADCAST; - new_interfaces[count].index = i->IfIndex; + bcast.sin_addr.S_un.S_addr |= htonl((1 << (32 - j->OnLinkPrefixLength)) - 1); + new_interfaces[count].broadcast = CreateSDLNetAddrFromSockAddr((const struct sockaddr*)&bcast, sizeof(bcast)); + new_interfaces[count].index = (Uint32) i->IfIndex; } count++; } } + SDL_free(friendlyname); } new_num_interfaces = count; // in case we dropped one somewhere. SDL_LockRWLockForWriting(interface_rwlock); - NetworkInterfaces *old_interfaces = interfaces; + NetworkInterface *old_interfaces = interfaces; const int old_num_interfaces = num_interfaces; interfaces = new_interfaces; num_interfaces = new_num_interfaces; @@ -488,11 +492,21 @@ static void RefreshInterfaces(void) // WINDOWS VERSION SDL_free(addrs); } -#elif defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_APPLE) || defined(SDL_PLATFORM_FREEBSD) || defined(SDL_PLATFORM_OPENBSD) || defined(SDL_PLATFORM_NETBSD) +#elif defined(USE_NETLINK) || defined(HAVE_GETIFADDRS) // AF_NETLINK covers Linux (and by extension Android). PF_ROUTE covers the BSDs (and by extension Apple platforms). // This doesn't cover all Unix platforms that ever existed, but this hits just about everything that is still being maintained seriously. +#if defined(SDL_PLATFORM_HAIKU) // haiku has its own thing for monitoring, but needs getifaddrs. +extern bool NET_HAIKU_InitInterfaceChangeNotifications(SDL_AtomicInt *changed); +extern void NET_HAIKU_QuitInterfaceChangeNotifications(void); +#elif defined(USE_NETLINK) || defined(PF_ROUTE) +#define USE_NETWORK_MONITOR 1 +#else +#warning unknown network monitoring system for this platform - will not report network changes +#endif + +#if defined(USE_NETWORK_MONITOR) static SDL_Thread *interface_change_notifications_thread = NULL; static SDL_AtomicInt interface_change_notifications_flag; // !!! FIXME @@ -514,7 +528,7 @@ static int SDLCALL LinuxInterfaceChangeNotificationThread(void *data) struct sockaddr_nl addr; SDL_zero(addr); addr.nl_family = AF_NETLINK; - addr.nl_pid = getpid(); + addr.nl_pid = 0; // zero==let the kernel choose a unique number. THIS IS THE SOCKET PORT ID, NOT THE PROCESS ID! addr.nl_groups = RTMGRP_LINK|RTMGRP_IPV4_IFADDR|RTMGRP_IPV6_IFADDR; if (bind(fd, (struct sockaddr *) &addr, sizeof (addr)) == -1) { close(fd); @@ -552,7 +566,7 @@ static int SDLCALL LinuxInterfaceChangeNotificationThread(void *data) // we don't currently bother parsing for specifics, we just use this as a signal to reenumerate interfaces. //for (struct nlmsghdr *i = buf; NLMSG_OK(i, br); i = NLMSG_NEXT(i, br)) {} - SDL_Log("Network interfaces changed!"); + //SDL_Log("Network interfaces changed!"); SDL_SetAtomicInt(&interfaces_have_changed, 1); } @@ -560,21 +574,32 @@ static int SDLCALL LinuxInterfaceChangeNotificationThread(void *data) return 0; } +#endif static bool InitInterfaceChangeNotifications(void) // LINUX/BSD VERSION { +#ifdef USE_NETWORK_MONITOR interface_change_notifications_thread = SDL_CreateThread(LinuxInterfaceChangeNotificationThread, "SDLNetIfaceEnum", NULL); return (interface_change_notifications_thread != NULL); +#elif defined(SDL_PLATFORM_HAIKU) + return NET_HAIKU_InitInterfaceChangeNotifications(&interfaces_have_changed); +#else + return true; +#endif } static void QuitInterfaceChangeNotifications(void) // LINUX/BSD VERSION { +#ifdef USE_NETWORK_MONITOR if (interface_change_notifications_thread) { SDL_SetAtomicInt(&interface_change_notifications_flag, 1); SDL_WaitThread(interface_change_notifications_thread, NULL); SDL_SetAtomicInt(&interface_change_notifications_flag, 0); } interface_change_notifications_thread = NULL; +#elif defined(SDL_PLATFORM_HAIKU) + NET_HAIKU_QuitInterfaceChangeNotifications(); +#endif } static void RefreshInterfaces(void) // LINUX/BSD VERSION @@ -602,13 +627,13 @@ static void RefreshInterfaces(void) // LINUX/BSD VERSION req.header.nlmsg_len = NLMSG_LENGTH(sizeof (req.msg)); req.msg.ifa_family = AF_UNSPEC; - if (send(sock, &req, req.header.nlmsg_len, 0) != req.header.nlmsg_len) { + if (send(sock, &req, req.header.nlmsg_len, 0) != (Sint32)req.header.nlmsg_len) { close(sock); return; // oh well. } int new_num_interfaces = 0; - NetworkInterfaces *new_interfaces = NULL; + NetworkInterface *new_interfaces = NULL; Uint8 buffer[64 * 1024]; ssize_t br; @@ -624,8 +649,8 @@ static void RefreshInterfaces(void) // LINUX/BSD VERSION if (!ptr) { goto failed; } - new_interfaces = (NetworkInterfaces *) ptr; - NetworkInterfaces *iface = &new_interfaces[new_num_interfaces]; + new_interfaces = (NetworkInterface *) ptr; + NetworkInterface *iface = &new_interfaces[new_num_interfaces]; SDL_zerop(iface); const struct ifaddrmsg *msg = (const struct ifaddrmsg *) (NLMSG_DATA(header)); @@ -657,10 +682,7 @@ static void RefreshInterfaces(void) // LINUX/BSD VERSION } if (iface->address) { - iface->index = msg->ifa_index; - if (iface->broadcast) { - iface->broadcast->type = NET_ADDRTYPE_BROADCAST; - } + iface->index = (Uint32) msg->ifa_index; char interface_name[IF_NAMESIZE]; if (if_indextoname((unsigned int) iface->index, interface_name) == NULL) { // miraculously, this function is in Android 21; nothing else is, though! We could have done this with netlink too, but this is the easy button. @@ -683,7 +705,7 @@ static void RefreshInterfaces(void) // LINUX/BSD VERSION succeeded: close(sock); SDL_LockRWLockForWriting(interface_rwlock); - NetworkInterfaces *old_interfaces = interfaces; + NetworkInterface *old_interfaces = interfaces; const int old_num_interfaces = num_interfaces; interfaces = new_interfaces; num_interfaces = new_num_interfaces; @@ -701,10 +723,10 @@ failed: return; // oh well. } - NetworkInterfaces *new_interfaces = NULL; + NetworkInterface *new_interfaces = NULL; int new_num_interfaces = 0; for (struct ifaddrs *i = ifaddr; i != NULL; i = i->ifa_next) { - if (i->ifa_name != NULL) { + if (i->ifa_name && i->ifa_addr && (i->ifa_flags & IFF_UP)) { new_num_interfaces++; } } @@ -718,10 +740,10 @@ failed: goto failed; } - NetworkInterfaces *iface = &new_interfaces[0]; - for (struct ifaddrs *i = ifaddr; i != NULL; i = i->ifa_next, iface++) { - if (i->ifa_name == NULL) { - continue; // i guess. + NetworkInterface *iface = &new_interfaces[0]; + for (struct ifaddrs *i = ifaddr; i != NULL; i = i->ifa_next) { + if (!i->ifa_name || !i->ifa_addr || ((i->ifa_flags & IFF_UP) == 0)) { + continue; } // !!! FIXME: getifaddrs doesn't return the sockaddr length, so we have to go with known protocols. :/ @@ -734,16 +756,11 @@ failed: iface->address = CreateSDLNetAddrFromSockAddr(i->ifa_addr, sizeof (struct sockaddr_in6)); } else { new_num_interfaces--; - iface--; continue; } if (iface->address) { - if (iface->broadcast) { - iface->broadcast->type = NET_ADDRTYPE_BROADCAST; - } - - iface->index = (int) if_nametoindex(i->ifa_name); + iface->index = (Uint32) if_nametoindex(i->ifa_name); iface->name = SDL_strdup(i->ifa_name); if (!iface->name) { goto failed; @@ -757,6 +774,8 @@ failed: NET_UnrefAddress(iface->broadcast); // just in case. goto failed; } + + iface++; } succeeded: @@ -765,7 +784,7 @@ succeeded: } SDL_LockRWLockForWriting(interface_rwlock); - NetworkInterfaces *old_interfaces = interfaces; + NetworkInterface *old_interfaces = interfaces; const int old_num_interfaces = num_interfaces; interfaces = new_interfaces; num_interfaces = new_num_interfaces; @@ -782,18 +801,182 @@ failed: #endif } +#elif defined(SDL_PLATFORM_VITA) + +static int interface_change_notifications_handle; +static SDL_Thread *interface_change_notifications_thread = NULL; +static SDL_AtomicInt interface_change_notifications_flag; + +static int SDLCALL VitaInterfaceChangeNotificationThread(void *data) +{ + (void)data; + + while(SDL_GetAtomicInt(&interface_change_notifications_flag) == 0) + { + sceNetCtlCheckCallback(); + SDL_Delay(100); + } + return 0; +} + +static void NetworkInterfaceChangedCallback(int event_type, void* arg) +{ + SDL_SetAtomicInt(&interfaces_have_changed, 1); +} + +static bool InitInterfaceChangeNotifications(void) +{ + if (sceNetCtlInetRegisterCallback(NetworkInterfaceChangedCallback, NULL, &interface_change_notifications_handle) != 0) + return false; + + interface_change_notifications_thread = SDL_CreateThread(VitaInterfaceChangeNotificationThread, "SDLNetIfaceEnum", NULL); + return (interface_change_notifications_thread != NULL); +} + +static void QuitInterfaceChangeNotifications(void) +{ + sceNetCtlInetUnregisterCallback(interface_change_notifications_handle); + if (interface_change_notifications_thread) { + SDL_SetAtomicInt(&interface_change_notifications_flag, 1); + SDL_WaitThread(interface_change_notifications_thread, NULL); + SDL_SetAtomicInt(&interface_change_notifications_flag, 0); + } + interface_change_notifications_thread = NULL; +} + +static NET_Address *CreateSDLNetAddrFromIp(char* hostbuf) +{ + NET_Address *addr = (NET_Address *) SDL_calloc(1, sizeof (NET_Address)); + if (!addr) { + return NULL; + } + SDL_SetAtomicInt(&addr->status, (int) NET_SUCCESS); + + struct addrinfo hints; + SDL_zero(hints); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = 0; + hints.ai_flags = AI_NUMERICHOST; + + int gairc = getaddrinfo(hostbuf, NULL, &hints, &addr->ainfo); + if (gairc != 0) { + SDL_free(addr); + SetGetAddrInfoError("Failed to determine address", gairc); + return NULL; + } + + addr->human_readable = SDL_strdup(hostbuf); + if (!addr->human_readable) { + freeaddrinfo(addr->ainfo); + SDL_free(addr); + return NULL; + } + + return NET_RefAddress(addr); +} + + +static void RefreshInterfaces(void) +{ + NetworkInterface *new_interfaces = NULL; + int new_num_interfaces = 0; + SceNetCtlInfo info; + + int state; + if (sceNetCtlInetGetState(&state) < 0 ) { + goto failed; + } + + if (state != SCE_NETCTL_STATE_CONNECTED) { + goto succeeded; + } + + if (state == SCE_NETCTL_STATE_CONNECTED) { + new_num_interfaces = 1; + + new_interfaces = SDL_calloc(new_num_interfaces, sizeof (*new_interfaces)); + if (!new_interfaces) { + goto failed; + } + + NetworkInterface *iface = &new_interfaces[0]; + + if (sceNetCtlInetGetInfo(SCE_NETCTL_INFO_GET_IP_ADDRESS, &info) < 0) { + goto failed; + } + + + iface->address = CreateSDLNetAddrFromIp(info.ip_address); + + if (iface->address) { + iface->index = 0; + + if (sceNetCtlInetGetInfo(SCE_NETCTL_INFO_GET_DEVICE, &info) < 0) { + goto failed; + } + + switch (info.device) { + case 0: + iface->name = SDL_strdup("wlan0"); + break; + case 1: + iface->name = SDL_strdup("eth0"); + break; + case 2: + iface->name = SDL_strdup("phone0"); + break; + } + + if (!iface->name) { + goto failed; + } + + char *ptr = SDL_strchr(iface->address->human_readable, '%'); // chop off interface name. + if (ptr) { + *ptr = '\0'; + } + } else { + NET_UnrefAddress(iface->broadcast); // just in case. + goto failed; + } + } + +succeeded: + SDL_LockRWLockForWriting(interface_rwlock); + NetworkInterface *old_interfaces = interfaces; + const int old_num_interfaces = num_interfaces; + interfaces = new_interfaces; + num_interfaces = new_num_interfaces; + SDL_UnlockRWLock(interface_rwlock); + FreeNetworkInterfaces(old_interfaces, old_num_interfaces); + return; + +failed: + FreeNetworkInterfaces(new_interfaces, new_num_interfaces); +} + #else -#error implement me for your platform. +#warning implement me for your platform. +static bool InitInterfaceChangeNotifications(void) +{ + return true; +} + +static void QuitInterfaceChangeNotifications(void) +{ +} + +static void RefreshInterfaces(void) +{ +} #endif static bool InterfacesReady(void) { if (SDL_ShouldInit(&interface_init)) { - if (!interface_rwlock) { - interface_rwlock = SDL_CreateRWLock(); - } - if (!interface_rwlock || !InitInterfaceChangeNotifications()) { + if (!InitInterfaceChangeNotifications()) { SDL_SetInitialized(&interface_init, false); return false; } @@ -806,14 +989,16 @@ static bool InterfacesReady(void) RefreshInterfaces(); #if 0 SDL_Log("NETWORK INTERFACE REFRESH"); + SDL_LockRWLockForReading(interface_rwlock); for (int i = 0; i < num_interfaces; i++) { - const NetworkInterfaces *iface = &interfaces[i]; - SDL_Log("Interface %d ('%s')", iface->index, iface->name); + const NetworkInterface *iface = &interfaces[i]; + SDL_Log("Interface %u ('%s')", (unsigned int) iface->index, iface->name); SDL_Log(" - address %s", iface->address->human_readable); if (iface->broadcast) { SDL_Log(" - broadcast %s", iface->broadcast->human_readable); } } + SDL_UnlockRWLock(interface_rwlock); #endif } @@ -849,7 +1034,6 @@ static NET_Status ResolveAddress(NET_Address *addr) addr->human_readable = SDL_strdup(buf); addr->ainfo = ainfo; - addr->type = NET_ADDRTYPE_UNICAST; return NET_SUCCESS; // success (zero means "still in progress"). } @@ -976,8 +1160,6 @@ static NET_Address *CreateSDLNetAddrFromSockAddr(const struct sockaddr *saddr, S return NULL; } - addr->type = NET_ADDRTYPE_UNICAST; // until told otherwise. - addr->human_readable = SDL_strdup(hostbuf); if (!addr->human_readable) { freeaddrinfo(addr->ainfo); @@ -1003,6 +1185,9 @@ bool NET_Init(void) if (WSAStartup(MAKEWORD(1, 1), &data) != 0) { return SetSocketErrorBool("WSAStartup() failed", LastSocketError()); } + #elif defined(SDL_PLATFORM_VITA) + // trigger vita network stack startup + gethostname(NULL,0); #else signal(SIGPIPE, SIG_IGN); #endif @@ -1030,6 +1215,18 @@ 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)); + } + + interface_rwlock = SDL_CreateRWLock(); + if (!interface_rwlock) { + goto failed; + } + return true; // good to go. failed: @@ -1093,20 +1290,23 @@ void NET_Quit(void) if (interfaces) { FreeNetworkInterfaces(interfaces, num_interfaces); } - if (interface_rwlock) { - SDL_DestroyRWLock(interface_rwlock); - } interfaces = NULL; num_interfaces = 0; - interface_rwlock = NULL; SDL_SetInitialized(&interface_init, false); } // asserts to catch that these shouldn't have ever been set if we never initialized interface_init... SDL_assert(!interfaces); - SDL_assert(!interface_rwlock); SDL_assert(num_interfaces == 0); + if (interface_rwlock) { + SDL_DestroyRWLock(interface_rwlock); + interface_rwlock = NULL; + } + + NET_UnrefAddress(ipv6_broadcast_addr); + ipv6_broadcast_addr = NULL; + #ifdef SDL_PLATFORM_WINDOWS WSACleanup(); #endif @@ -1238,6 +1438,34 @@ const char *NET_GetAddressString(NET_Address *addr) return retval; } +const void * NET_GetAddressBytes(NET_Address *addr, int *num_bytes) +{ + if (!num_bytes) { + SDL_InvalidParamError("num_bytes"); + return NULL; + } + + *num_bytes = 0; + + if (!addr) { + SDL_InvalidParamError("address"); + return NULL; + } + + const void *retval = NULL; + const NET_Status rc = NET_GetAddressStatus(addr); + if (rc == NET_SUCCESS) { + const struct addrinfo *ainfo = addr->ainfo; + SDL_assert(ainfo != NULL); + retval = (const void *) ainfo->ai_addr; + *num_bytes = (int) ainfo->ai_addrlen; + } else if (rc != NET_FAILURE) { // if NET_FAILURE, it'll set the error message. + SDL_SetError("Address not yet resolved"); + } + + return retval; +} + int NET_CompareAddresses(const NET_Address *sdlneta, const NET_Address *sdlnetb) { if (sdlneta == sdlnetb) { // same pointer? @@ -1289,7 +1517,6 @@ void NET_SimulateAddressResolutionLoss(int percent_loss) SDL_SetAtomicInt(&resolver_percent_loss, SDL_clamp(percent_loss, 0, 100)); } - NET_Address **NET_GetLocalAddresses(int *num_addresses) { int dummy_addresses; @@ -1372,7 +1599,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"); @@ -1471,7 +1698,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. @@ -1515,6 +1742,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; @@ -1538,7 +1767,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) { @@ -1855,6 +2084,7 @@ typedef struct NET_DatagramSocketHandle Socket handle; int family; int protocol; + NET_Address *broadcast; } NET_DatagramSocketHandle; struct NET_DatagramSocket @@ -1872,10 +2102,57 @@ struct NET_DatagramSocket NET_Datagram **pending_output; int pending_output_len; int pending_output_allocation; + bool allow_broadcast; }; +static NET_Address *FindBroadcastAddress(struct addrinfo *ainfo, Uint32 *interface_index) +{ + NET_Address *retval = NULL; -NET_DatagramSocket *NET_CreateDatagramSocket(NET_Address *addr, Uint16 port) + NET_Address *iface = CreateSDLNetAddrFromSockAddr(ainfo->ai_addr, (SockLen) ainfo->ai_addrlen); + if (!iface) { + return NULL; + } + + NetworkInterface *ni = NULL; + if (!InterfacesReady()) { + NET_UnrefAddress(iface); + return NULL; + } + + SDL_LockRWLockForReading(interface_rwlock); + + for (int i = 0; i < num_interfaces; i++) { + if (NET_CompareAddresses(iface, interfaces[i].address) == 0) { + ni = &interfaces[i]; + break; + } + } + + 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. @@ -1897,6 +2174,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) { @@ -1942,24 +2224,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(); @@ -1967,6 +2251,39 @@ 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. +#ifdef SDL_PLATFORM_VITA + SDL_SetError("No IPv6 support"); + goto failed; +#else + 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. +#endif + } + } } freeaddrinfo(addrwithport); @@ -1977,6 +2294,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); @@ -1986,29 +2304,94 @@ failed: 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 = (int) 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? - const int rc = sendto(handle->handle, buf, (size_t) buflen, 0, addrwithport->ai_addr, (SockLen) addrwithport->ai_addrlen); + + 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 = (int) 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 && InterfacesReady()) { // 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; } + + struct addrinfo *addrwithport = MakeAddrInfoWithPort(bc, SOCK_DGRAM, port); + if (!addrwithport) { + continue; // oh well, lost UDP packet, I guess. + } + + //SDL_Log("Broadcasting on %s ...", bc->human_readable); + const int rc = (int) 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) { + 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_UnlockRWLock(interface_rwlock); } } - SDL_SetError("Unsupported network family in destination address"); - return NET_FAILURE; + if ((retval == NET_FAILURE) && all_wouldblock) { + retval = NET_WAITING; + } + + return retval; } // see if any pending data can finally be sent, etc @@ -2043,8 +2426,8 @@ bool NET_SendDatagram(NET_DatagramSocket *sock, NET_Address *addr, Uint16 port, { if (!PumpDatagramSocket(sock)) { // try to flush any queued data to the socket now, before we handle more. return false; - } else if (addr == NULL) { - return SDL_InvalidParamError("address"); + } else if (!addr && !sock->allow_broadcast) { + return SDL_SetError("Datagram socket was not created with broadcast support"); } else if (buf == NULL) { return SDL_InvalidParamError("buf"); } else if (buflen < 0) { @@ -2119,7 +2502,7 @@ bool NET_ReceiveDatagram(NET_DatagramSocket *sock, NET_Datagram **dgram) AddressStorage from; SockLen fromlen = sizeof (from); // WinSock's recvfrom wants a `char *` buffer instead of `void *`. The cast here is harmless on BSD Sockets. - const int br = recvfrom(sock->handles[i].handle, (char *) sock->recv_buffer, sizeof (sock->recv_buffer), 0, (struct sockaddr *) &from, &fromlen); + const int br = (int) recvfrom(sock->handles[i].handle, (char *) sock->recv_buffer, sizeof (sock->recv_buffer), 0, (struct sockaddr *) &from, &fromlen); if (br == SOCKET_ERROR) { const int err = LastSocketError(); if (WouldBlock(err)) { @@ -2224,6 +2607,7 @@ void NET_DestroyDatagramSocket(NET_DatagramSocket *sock) for (int i = 0; i < sock->num_handles; i++) { CloseSocketHandle(sock->handles[i].handle); // !!! FIXME: what does this do with non-blocking sockets? Release the descriptor but the kernel continues sending queued buffers behind the scenes? + NET_UnrefAddress(sock->handles[i].broadcast); } for (int i = 0; i < ((int) SDL_arraysize(sock->latest_recv_addrs)); i++) { NET_UnrefAddress(sock->latest_recv_addrs[i]); diff --git a/Source/3rdParty/SDL_net/src/SDL_net.exports b/Source/3rdParty/SDL_net/src/SDL_net.exports index ef3bf338..d1c52d9b 100644 --- a/Source/3rdParty/SDL_net/src/SDL_net.exports +++ b/Source/3rdParty/SDL_net/src/SDL_net.exports @@ -32,4 +32,5 @@ _NET_WaitUntilInputAvailable _NET_WaitUntilResolved _NET_WaitUntilStreamSocketDrained _NET_WriteToStreamSocket +_NET_GetAddressBytes # extra symbols go here (don't modify this line) diff --git a/Source/3rdParty/SDL_net/src/SDL_net.sym b/Source/3rdParty/SDL_net/src/SDL_net.sym index 53c9c9c1..07108897 100644 --- a/Source/3rdParty/SDL_net/src/SDL_net.sym +++ b/Source/3rdParty/SDL_net/src/SDL_net.sym @@ -33,6 +33,7 @@ SDL3_net_0.0.0 { NET_WaitUntilResolved; NET_WaitUntilStreamSocketDrained; NET_WriteToStreamSocket; + NET_GetAddressBytes; # extra symbols go here (don't modify this line) local: *; }; diff --git a/Source/3rdParty/SDL_net/src/SDL_net_haiku.cpp b/Source/3rdParty/SDL_net/src/SDL_net_haiku.cpp new file mode 100644 index 00000000..dcb3f600 --- /dev/null +++ b/Source/3rdParty/SDL_net/src/SDL_net_haiku.cpp @@ -0,0 +1,82 @@ +/* + SDL_net: A simple networking library for use with SDL + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +// we use a little bit of C++ for Haiku support, since the system frameworks +// require it. Specifically, we need to get network interface change +// notifications through BNetworkRoster. + +#include +#include + +#include + +extern "C" { + extern bool NET_HAIKU_InitInterfaceChangeNotifications(SDL_AtomicInt *changed); + extern void NET_HAIKU_QuitInterfaceChangeNotifications(void); +} + +class NET_InterfaceLooper : public BLooper +{ +public: + NET_InterfaceLooper(SDL_AtomicInt *_changed); + virtual void MessageReceived(BMessage *message); + virtual ~NET_InterfaceLooper() {} + +private: + SDL_AtomicInt *changed; +}; + + +NET_InterfaceLooper::NET_InterfaceLooper(SDL_AtomicInt *_changed) + : BLooper("NET_InterfaceLooper") + , changed(_changed) +{ +} + +void NET_InterfaceLooper::MessageReceived(BMessage *message) +{ + //SDL_Log("NET_InterfaceLooper::MessageReceived %d", (int) message->what); + SDL_SetAtomicInt(changed, 1); + BLooper::MessageReceived(message); +} + + +static NET_InterfaceLooper *looper = NULL; + +bool NET_HAIKU_InitInterfaceChangeNotifications(SDL_AtomicInt *changed) +{ + SDL_assert(!looper); + looper = new NET_InterfaceLooper(changed); + looper->Run(); + return start_watching_network( + B_NETWORK_INTERFACE_ADDED | B_NETWORK_INTERFACE_REMOVED | + B_NETWORK_INTERFACE_CHANGED | B_NETWORK_DEVICE_LINK_CHANGED, looper + ) == B_OK; +} + +void NET_HAIKU_QuitInterfaceChangeNotifications(void) +{ + if (looper) { + looper->PostMessage(B_QUIT_REQUESTED); + looper = NULL; + } +} + diff --git a/Source/3rdParty/SDL_net/src/SDL_net_stub_only.c b/Source/3rdParty/SDL_net/src/SDL_net_stub_only.c new file mode 100644 index 00000000..c2313264 --- /dev/null +++ b/Source/3rdParty/SDL_net/src/SDL_net_stub_only.c @@ -0,0 +1,59 @@ +/* + SDL_net: A simple networking library for use with SDL + Copyright (C) 1997-2026 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL3_net/SDL_net.h" + +// don't do anything. Sorry! +int NET_Version(void) { return SDL_NET_VERSION; } +bool NET_Init(void) { return SDL_Unsupported(); } +void NET_Quit(void) {} +NET_Address * NET_ResolveHostname(const char *host) { SDL_Unsupported(); return NULL; } +NET_Status NET_WaitUntilResolved(NET_Address *address, Sint32 timeout) { SDL_Unsupported(); return NET_FAILURE; } +NET_Status NET_GetAddressStatus(NET_Address *address) { SDL_Unsupported(); return NET_FAILURE; } +const char * NET_GetAddressString(NET_Address *address) { SDL_Unsupported(); return NULL; } +const void * NET_GetAddressBytes(NET_Address *address, int *num_bytes) { if (num_bytes) { *num_bytes = 0; } SDL_Unsupported(); return NULL; } +NET_Address *NET_RefAddress(NET_Address *address) { SDL_Unsupported(); return NULL; } +void NET_UnrefAddress(NET_Address *address) {} +void NET_SimulateAddressResolutionLoss(int percent_loss) {} +int NET_CompareAddresses(const NET_Address *a, const NET_Address *b) { return 0; } +NET_Address **NET_GetLocalAddresses(int *num_addresses) { SDL_Unsupported(); return NULL; } +void NET_FreeLocalAddresses(NET_Address **addresses) {} +NET_StreamSocket * NET_CreateClient(NET_Address *address, Uint16 port, SDL_PropertiesID props) { SDL_Unsupported(); return NULL; } +NET_Status NET_WaitUntilConnected(NET_StreamSocket *sock, Sint32 timeout) { SDL_Unsupported(); return NET_FAILURE; } +NET_Server * NET_CreateServer(NET_Address *addr, Uint16 port, SDL_PropertiesID props) { SDL_Unsupported(); return NULL; } +bool NET_AcceptClient(NET_Server *server, NET_StreamSocket **client_stream) { SDL_Unsupported(); return false; } +void NET_DestroyServer(NET_Server *server) {} +NET_Address * NET_GetStreamSocketAddress(NET_StreamSocket *sock) { SDL_Unsupported(); return NULL; } +NET_Status NET_GetConnectionStatus(NET_StreamSocket *sock) { SDL_Unsupported(); return NET_FAILURE; } +bool NET_WriteToStreamSocket(NET_StreamSocket *sock, const void *buf, int buflen) { SDL_Unsupported(); return false; } +int NET_GetStreamSocketPendingWrites(NET_StreamSocket *sock) { SDL_Unsupported(); return -1; } +int NET_WaitUntilStreamSocketDrained(NET_StreamSocket *sock, Sint32 timeout) { SDL_Unsupported(); return -1; } +int NET_ReadFromStreamSocket(NET_StreamSocket *sock, void *buf, int buflen) { SDL_Unsupported(); return -1; } +void NET_SimulateStreamPacketLoss(NET_StreamSocket *sock, int percent_loss) {} +void NET_DestroyStreamSocket(NET_StreamSocket *sock) {} +NET_DatagramSocket * NET_CreateDatagramSocket(NET_Address *addr, Uint16 port, SDL_PropertiesID props) { SDL_Unsupported(); return NULL; } +bool NET_SendDatagram(NET_DatagramSocket *sock, NET_Address *address, Uint16 port, const void *buf, int buflen) { SDL_Unsupported(); return false; } +bool NET_ReceiveDatagram(NET_DatagramSocket *sock, NET_Datagram **dgram) { SDL_Unsupported(); return false; } +void NET_DestroyDatagram(NET_Datagram *dgram) {} +void NET_SimulateDatagramPacketLoss(NET_DatagramSocket *sock, int percent_loss) {} +void NET_DestroyDatagramSocket(NET_DatagramSocket *sock) {} +int NET_WaitUntilInputAvailable(void **vsockets, int numsockets, Sint32 timeout) { SDL_Unsupported(); return -1; } + diff --git a/Source/3rdParty/SDL_net/src/version.rc b/Source/3rdParty/SDL_net/src/version.rc index 40c66d31..dc6a11ca 100644 --- a/Source/3rdParty/SDL_net/src/version.rc +++ b/Source/3rdParty/SDL_net/src/version.rc @@ -9,8 +9,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 3,0,0,0 - PRODUCTVERSION 3,0,0,0 + FILEVERSION 3,3,0,0 + PRODUCTVERSION 3,3,0,0 FILEFLAGSMASK 0x3fL FILEFLAGS 0x0L FILEOS 0x40004L @@ -23,12 +23,12 @@ BEGIN BEGIN VALUE "CompanyName", "\0" VALUE "FileDescription", "SDL_net\0" - VALUE "FileVersion", "3, 0, 0, 0\0" + VALUE "FileVersion", "3, 3, 0, 0\0" VALUE "InternalName", "SDL_net\0" VALUE "LegalCopyright", "Copyright (C) 2026 Sam Lantinga\0" VALUE "OriginalFilename", "SDL3_net.dll\0" VALUE "ProductName", "Simple DirectMedia Layer\0" - VALUE "ProductVersion", "3, 0, 0, 0\0" + VALUE "ProductVersion", "3, 3, 0, 0\0" END END BLOCK "VarFileInfo"