wsdapi: Add support for listening for UDP multicast broadcasts.
Signed-off-by: Owen Rudge <orudge@codeweavers.com> Signed-off-by: Huw Davies <huw@codeweavers.com> Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
parent
f74e8a2967
commit
e69f09746e
|
@ -231,11 +231,287 @@ BOOL send_udp_multicast(IWSDiscoveryPublisherImpl *impl, char *data, int length,
|
|||
return TRUE;
|
||||
}
|
||||
|
||||
static int join_multicast_group(SOCKET s, SOCKADDR_STORAGE *group, SOCKADDR_STORAGE *iface)
|
||||
{
|
||||
int level, optname, optlen;
|
||||
struct ipv6_mreq mreqv6;
|
||||
struct ip_mreq mreqv4;
|
||||
char *optval;
|
||||
|
||||
if (iface->ss_family == AF_INET6)
|
||||
{
|
||||
level = IPPROTO_IPV6;
|
||||
optname = IPV6_ADD_MEMBERSHIP;
|
||||
optval = (char *)&mreqv6;
|
||||
optlen = sizeof(mreqv6);
|
||||
|
||||
mreqv6.ipv6mr_multiaddr = ((SOCKADDR_IN6 *)group)->sin6_addr;
|
||||
mreqv6.ipv6mr_interface = ((SOCKADDR_IN6 *)iface)->sin6_scope_id;
|
||||
}
|
||||
else
|
||||
{
|
||||
level = IPPROTO_IP;
|
||||
optname = IP_ADD_MEMBERSHIP;
|
||||
optval = (char *)&mreqv4;
|
||||
optlen = sizeof(mreqv4);
|
||||
|
||||
mreqv4.imr_multiaddr.s_addr = ((SOCKADDR_IN *)group)->sin_addr.s_addr;
|
||||
mreqv4.imr_interface.s_addr = ((SOCKADDR_IN *)iface)->sin_addr.s_addr;
|
||||
}
|
||||
|
||||
return setsockopt(s, level, optname, optval, optlen);
|
||||
}
|
||||
|
||||
static int set_send_interface(SOCKET s, SOCKADDR_STORAGE *iface)
|
||||
{
|
||||
int level, optname, optlen;
|
||||
char *optval = NULL;
|
||||
|
||||
if (iface->ss_family == AF_INET6)
|
||||
{
|
||||
level = IPPROTO_IPV6;
|
||||
optname = IPV6_MULTICAST_IF;
|
||||
optval = (char *) &((SOCKADDR_IN6 *)iface)->sin6_scope_id;
|
||||
optlen = sizeof(((SOCKADDR_IN6 *)iface)->sin6_scope_id);
|
||||
}
|
||||
else
|
||||
{
|
||||
level = IPPROTO_IP;
|
||||
optname = IP_MULTICAST_IF;
|
||||
optval = (char *) &((SOCKADDR_IN *)iface)->sin_addr.s_addr;
|
||||
optlen = sizeof(((SOCKADDR_IN *)iface)->sin_addr.s_addr);
|
||||
}
|
||||
|
||||
return setsockopt(s, level, optname, optval, optlen);
|
||||
}
|
||||
|
||||
typedef struct listener_thread_params
|
||||
{
|
||||
IWSDiscoveryPublisherImpl *impl;
|
||||
SOCKET listening_socket;
|
||||
BOOL ipv6;
|
||||
} listener_thread_params;
|
||||
|
||||
#define RECEIVE_BUFFER_SIZE 65536
|
||||
|
||||
static DWORD WINAPI listening_thread(LPVOID params)
|
||||
{
|
||||
listener_thread_params *parameter = (listener_thread_params *)params;
|
||||
int bytes_received, address_len, err;
|
||||
SOCKADDR_STORAGE source_addr;
|
||||
char *buffer;
|
||||
|
||||
buffer = heap_alloc(RECEIVE_BUFFER_SIZE);
|
||||
address_len = parameter->ipv6 ? sizeof(SOCKADDR_IN6) : sizeof(SOCKADDR_IN);
|
||||
|
||||
while (parameter->impl->publisherStarted)
|
||||
{
|
||||
bytes_received = recvfrom(parameter->listening_socket, buffer, RECEIVE_BUFFER_SIZE, 0,
|
||||
(LPSOCKADDR) &source_addr, &address_len);
|
||||
|
||||
if (bytes_received == SOCKET_ERROR)
|
||||
{
|
||||
err = WSAGetLastError();
|
||||
|
||||
if (err != WSAETIMEDOUT)
|
||||
{
|
||||
WARN("Received error when trying to read from socket: %d. Stopping listener.\n", err);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* TODO: Process received message */
|
||||
}
|
||||
}
|
||||
|
||||
/* The publisher has been stopped */
|
||||
closesocket(parameter->listening_socket);
|
||||
|
||||
heap_free(buffer);
|
||||
heap_free(parameter);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int start_listening(IWSDiscoveryPublisherImpl *impl, SOCKADDR_STORAGE *bind_address)
|
||||
{
|
||||
SOCKADDR_STORAGE multicast_addr, bind_addr, interface_addr;
|
||||
listener_thread_params *parameter = NULL;
|
||||
const DWORD receive_timeout = 5000;
|
||||
const UINT reuse_addr = 1;
|
||||
HANDLE thread_handle;
|
||||
int address_length;
|
||||
SOCKET s = 0;
|
||||
|
||||
TRACE("(%p, %p) family %d\n", impl, bind_address, bind_address->ss_family);
|
||||
|
||||
/* Populate the multicast address */
|
||||
ZeroMemory(&multicast_addr, sizeof(SOCKADDR_STORAGE));
|
||||
|
||||
if (bind_address->ss_family == AF_INET)
|
||||
{
|
||||
SOCKADDR_IN *sockaddr4 = (SOCKADDR_IN *)&multicast_addr;
|
||||
|
||||
sockaddr4->sin_port = htons(SEND_PORT);
|
||||
sockaddr4->sin_addr.S_un.S_addr = htonl(SEND_ADDRESS_IPV4);
|
||||
address_length = sizeof(SOCKADDR_IN);
|
||||
}
|
||||
else
|
||||
{
|
||||
SOCKADDR_IN6 *sockaddr6 = (SOCKADDR_IN6 *)&multicast_addr;
|
||||
|
||||
sockaddr6->sin6_port = htons(SEND_PORT);
|
||||
memcpy(&sockaddr6->sin6_addr, &send_address_ipv6, sizeof(send_address_ipv6));
|
||||
address_length = sizeof(SOCKADDR_IN6);
|
||||
}
|
||||
|
||||
/* Update the port for the binding address */
|
||||
memcpy(&bind_addr, bind_address, address_length);
|
||||
((SOCKADDR_IN *)&bind_addr)->sin_port = htons(SEND_PORT);
|
||||
|
||||
/* Update the port for the interface address */
|
||||
memcpy(&interface_addr, bind_address, address_length);
|
||||
((SOCKADDR_IN *)&interface_addr)->sin_port = htons(0);
|
||||
|
||||
/* Create the socket */
|
||||
s = socket(bind_address->ss_family, SOCK_DGRAM, IPPROTO_UDP);
|
||||
|
||||
if (s == INVALID_SOCKET)
|
||||
{
|
||||
WARN("socket() failed (error %d)\n", WSAGetLastError());
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Ensure the socket can be reused */
|
||||
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuse_addr, sizeof(reuse_addr)) == SOCKET_ERROR)
|
||||
{
|
||||
WARN("setsockopt(SO_REUSEADDR) failed (error %d)\n", WSAGetLastError());
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Bind the socket to the local interface so we can receive data */
|
||||
if (bind(s, (struct sockaddr *)&bind_addr, address_length) == SOCKET_ERROR)
|
||||
{
|
||||
WARN("bind() failed (error %d)\n", WSAGetLastError());
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Join the multicast group */
|
||||
if (join_multicast_group(s, &multicast_addr, &interface_addr) == SOCKET_ERROR)
|
||||
{
|
||||
WARN("Unable to join multicast group (error %d)\n", WSAGetLastError());
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Set the outgoing interface */
|
||||
if (set_send_interface(s, &interface_addr) == SOCKET_ERROR)
|
||||
{
|
||||
WARN("Unable to set outgoing interface (error %d)\n", WSAGetLastError());
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Set a 5-second receive timeout */
|
||||
if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (const char *)&receive_timeout, sizeof(receive_timeout)) == SOCKET_ERROR)
|
||||
{
|
||||
WARN("setsockopt(SO_RCVTIME0) failed (error %d)\n", WSAGetLastError());
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Allocate memory for thread parameters */
|
||||
parameter = heap_alloc(sizeof(listener_thread_params));
|
||||
|
||||
parameter->impl = impl;
|
||||
parameter->listening_socket = s;
|
||||
parameter->ipv6 = (bind_address->ss_family == AF_INET6);
|
||||
|
||||
thread_handle = CreateThread(NULL, 0, listening_thread, parameter, 0, NULL);
|
||||
|
||||
if (thread_handle == NULL)
|
||||
{
|
||||
WARN("CreateThread failed (error %d)\n", GetLastError());
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
impl->thread_handles[impl->num_thread_handles] = thread_handle;
|
||||
impl->num_thread_handles++;
|
||||
|
||||
return 1;
|
||||
|
||||
cleanup:
|
||||
closesocket(s);
|
||||
heap_free(parameter);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static BOOL start_listening_on_all_addresses(IWSDiscoveryPublisherImpl *impl, ULONG family)
|
||||
{
|
||||
IP_ADAPTER_ADDRESSES *adapter_addresses = NULL, *adapter_address;
|
||||
int valid_listeners = 0;
|
||||
ULONG bufferSize = 0;
|
||||
ULONG ret;
|
||||
|
||||
ret = GetAdaptersAddresses(family, 0, NULL, NULL, &bufferSize); /* family should be AF_INET or AF_INET6 */
|
||||
|
||||
if (ret != ERROR_BUFFER_OVERFLOW)
|
||||
{
|
||||
WARN("GetAdaptorsAddresses failed with error %08x\n", ret);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Get size of buffer for adapters */
|
||||
adapter_addresses = (IP_ADAPTER_ADDRESSES *)heap_alloc(bufferSize);
|
||||
|
||||
if (adapter_addresses == NULL)
|
||||
{
|
||||
WARN("Out of memory allocating space for adapter information\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Get list of adapters */
|
||||
ret = GetAdaptersAddresses(family, 0, NULL, adapter_addresses, &bufferSize);
|
||||
|
||||
if (ret != ERROR_SUCCESS)
|
||||
{
|
||||
WARN("GetAdaptorsAddresses failed with error %08x\n", ret);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
for (adapter_address = adapter_addresses; adapter_address != NULL; adapter_address = adapter_address->Next)
|
||||
{
|
||||
if (impl->num_thread_handles >= MAX_WSD_THREADS)
|
||||
{
|
||||
WARN("Exceeded maximum number of supported listener threads; too many network interfaces.");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (adapter_address->FirstUnicastAddress == NULL)
|
||||
{
|
||||
TRACE("No address found for adaptor '%s' (%p)\n", adapter_address->AdapterName, adapter_address);
|
||||
continue;
|
||||
}
|
||||
|
||||
valid_listeners += start_listening(impl, (SOCKADDR_STORAGE *)adapter_address->FirstUnicastAddress->Address.lpSockaddr);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
heap_free(adapter_addresses);
|
||||
return (ret == ERROR_SUCCESS) && (valid_listeners > 0);
|
||||
}
|
||||
|
||||
void terminate_networking(IWSDiscoveryPublisherImpl *impl)
|
||||
{
|
||||
BOOL needsCleanup = impl->publisherStarted;
|
||||
int i;
|
||||
|
||||
impl->publisherStarted = FALSE;
|
||||
WaitForMultipleObjects(impl->num_thread_handles, impl->thread_handles, TRUE, INFINITE);
|
||||
|
||||
for (i = 0; i < impl->num_thread_handles; i++)
|
||||
{
|
||||
CloseHandle(impl->thread_handles[i]);
|
||||
}
|
||||
|
||||
if (needsCleanup)
|
||||
WSACleanup();
|
||||
|
@ -254,6 +530,15 @@ BOOL init_networking(IWSDiscoveryPublisherImpl *impl)
|
|||
|
||||
impl->publisherStarted = TRUE;
|
||||
|
||||
/* TODO: Start listening */
|
||||
if ((impl->addressFamily & WSDAPI_ADDRESSFAMILY_IPV4) && (!start_listening_on_all_addresses(impl, AF_INET)))
|
||||
goto cleanup;
|
||||
|
||||
if ((impl->addressFamily & WSDAPI_ADDRESSFAMILY_IPV6) && (!start_listening_on_all_addresses(impl, AF_INET6)))
|
||||
goto cleanup;
|
||||
|
||||
return TRUE;
|
||||
|
||||
cleanup:
|
||||
terminate_networking(impl);
|
||||
return FALSE;
|
||||
}
|
||||
|
|
|
@ -40,6 +40,8 @@ struct notificationSink
|
|||
IWSDiscoveryPublisherNotify *notificationSink;
|
||||
};
|
||||
|
||||
#define MAX_WSD_THREADS 20
|
||||
|
||||
typedef struct IWSDiscoveryPublisherImpl {
|
||||
IWSDiscoveryPublisher IWSDiscoveryPublisher_iface;
|
||||
LONG ref;
|
||||
|
@ -47,6 +49,8 @@ typedef struct IWSDiscoveryPublisherImpl {
|
|||
DWORD addressFamily;
|
||||
struct list notificationSinks;
|
||||
BOOL publisherStarted;
|
||||
HANDLE thread_handles[MAX_WSD_THREADS];
|
||||
int num_thread_handles;
|
||||
} IWSDiscoveryPublisherImpl;
|
||||
|
||||
/* network.c */
|
||||
|
|
Loading…
Reference in New Issue