This patch implements code to send messages via UDP multicast, and sends an empty (XML declaration only) message.
Signed-off-by: Owen Rudge orudge@codeweavers.com --- dlls/wsdapi/Makefile.in | 3 +- dlls/wsdapi/discovery.c | 5 + dlls/wsdapi/network.c | 278 ++++++++++++++++++++++++++++++++++++++++++ dlls/wsdapi/soap.c | 16 ++- dlls/wsdapi/tests/discovery.c | 4 +- dlls/wsdapi/wsdapi_internal.h | 6 + 6 files changed, 307 insertions(+), 5 deletions(-) create mode 100644 dlls/wsdapi/network.c
On Mon, Mar 12, 2018 at 10:59:46PM +0000, Owen Rudge wrote:
+#define SEND_ADDRESS_IPV4 "239.255.255.250" +#define SEND_ADDRESS_IPV6 "FF02::C" +#define SEND_PORT "3702"
It seems odd to store these as strings and then resolve them. Couldn't they just be stored direclty as addresses?
+#define UNICAST_UDP_REPEAT 1 +#define MULTICAST_UDP_REPEAT 2 +#define UDP_MIN_DELAY 50 +#define UDP_MAX_DELAY 250 +#define UDP_UPPER_DELAY 500
+static struct addrinfo *resolve_address(const char *address, const char *port, int family, int type, int protocol) +{
- struct addrinfo hints, *result = NULL;
- int ret;
- ZeroMemory(&hints, sizeof(hints));
- hints.ai_flags = AI_PASSIVE;
- hints.ai_family = family;
- hints.ai_socktype = type;
- hints.ai_protocol = protocol;
- ret = getaddrinfo(address, port, &hints, &result);
- if (ret != 0)
- {
WARN("Invalid address '%s' (getaddrinfo returned %d)\n", debugstr_a(address), ret);
return NULL;
- }
- return result;
+}
+static void send_message(SOCKET s, char *data, int length, SOCKADDR_STORAGE *dest, int max_initial_delay, int repeat) +{
- UINT delay;
- int len;
- /* Sleep for a random amount of time before sending the message */
- if (max_initial_delay > 0)
- {
BCryptGenRandom(NULL, (BYTE*) &delay, sizeof(UINT), BCRYPT_USE_SYSTEM_PREFERRED_RNG);
Sleep(delay % max_initial_delay);
- }
- len = (dest->ss_family == AF_INET6) ? sizeof(SOCKADDR_IN6) : sizeof(SOCKADDR_IN);
- if (sendto(s, data, length, 0, (SOCKADDR *) dest, len) == SOCKET_ERROR)
WARN("Unable to send data to socket: %d\n", WSAGetLastError());
- if (repeat <= 0)
return;
- repeat--;
- BCryptGenRandom(NULL, (BYTE*) &delay, sizeof(UINT), BCRYPT_USE_SYSTEM_PREFERRED_RNG);
- delay = delay % (UDP_MAX_DELAY - UDP_MIN_DELAY + 1) + UDP_MIN_DELAY;
- for (;;)
- {
Sleep(delay);
if (sendto(s, data, length, 0, (SOCKADDR *) dest, len) == SOCKET_ERROR)
WARN("Unable to send data to socket: %d\n", WSAGetLastError());
if (repeat <= 0)
break;
repeat--;
delay = min(delay * 2, UDP_UPPER_DELAY);
- }
+}
It should be possible to arrange this so there is only one sendto() call, rather than the first one followed by the loop.
Are these things really supposed to go off semi-randomly?
+typedef struct sending_thread_params +{
- char *data;
- int length;
- SOCKET sock;
- SOCKADDR_STORAGE dest;
- int max_initial_delay;
+} sending_thread_params;
+static DWORD WINAPI sending_thread(LPVOID lpParam) +{
- sending_thread_params *params = (sending_thread_params *) lpParam;
- send_message(params->sock, params->data, params->length, ¶ms->dest, params->max_initial_delay, MULTICAST_UDP_REPEAT);
- closesocket(params->sock);
- heap_free(params->data);
- heap_free(params);
- return 0;
+}
+BOOL send_udp_multicast_of_type(char *data, int length, int max_initial_delay, ULONG family) +{
- IP_ADAPTER_ADDRESSES *adapter_addresses = NULL, *adapter_addr;
- sending_thread_params *send_params;
- struct addrinfo *multi_addr;
- ULONG bufferSize = 0;
- struct in6_addr i_addr;
- LPSOCKADDR sockaddr;
- BOOL ret = FALSE;
- HANDLE thread_handle;
- char ttl = 8;
This magic number should be a #define or constant.
- ULONG retval;
- SOCKET s;
- /* Resolve the multicast address */
- if (family == AF_INET6)
multi_addr = resolve_address(SEND_ADDRESS_IPV6, SEND_PORT, AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
- else
multi_addr = resolve_address(SEND_ADDRESS_IPV4, SEND_PORT, AF_INET, SOCK_DGRAM, IPPROTO_UDP);
- if (multi_addr == NULL)
return FALSE;
- /* Get size of buffer for adapters */
- retval = GetAdaptersAddresses(family, 0, NULL, NULL, &bufferSize);
- if (retval != ERROR_BUFFER_OVERFLOW)
- {
WARN("GetAdaptorsAddresses failed with error %08x\n", retval);
goto cleanup;
- }
- adapter_addresses = (IP_ADAPTER_ADDRESSES *) heap_alloc(bufferSize);
- if (adapter_addresses == NULL)
- {
WARN("Out of memory allocating space for adapter information\n");
goto cleanup;
- }
- /* Get list of adapters */
- retval = GetAdaptersAddresses(family, 0, NULL, adapter_addresses, &bufferSize);
- if (retval != ERROR_SUCCESS)
- {
WARN("GetAdaptorsAddresses failed with error %08x\n", retval);
goto cleanup;
- }
- for (adapter_addr = adapter_addresses; adapter_addr != NULL; adapter_addr = adapter_addr->Next)
- {
if (adapter_addr->FirstUnicastAddress == NULL)
{
TRACE("No address found for adaptor '%s' (%p)\n", debugstr_a(adapter_addr->AdapterName), adapter_addr);
continue;
}
sockaddr = adapter_addr->FirstUnicastAddress->Address.lpSockaddr;
/* Create a socket and bind to the adapter address */
s = socket(family, SOCK_DGRAM, IPPROTO_UDP);
if (s == INVALID_SOCKET)
{
WARN("Unable to create socket: %d\n", WSAGetLastError());
continue;
}
if (bind(s, sockaddr, adapter_addr->FirstUnicastAddress->Address.iSockaddrLength) == SOCKET_ERROR)
{
WARN("Unable to bind to socket (adaptor '%s' (%p)): %d\n", debugstr_a(adapter_addr->AdapterName), adapter_addr, WSAGetLastError());
closesocket(s);
continue;
}
/* Zero out the interface */
ZeroMemory(&i_addr, sizeof(i_addr));
If you just want an address full of zeros, declare it as static const and call it i_addr_zero or something.
/* Set the multicast interface and TTL value */
setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, (char *) &i_addr, (family == AF_INET6) ? sizeof(struct in6_addr) : sizeof(struct in_addr));
setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));
/* Set up the thread parameters */
send_params = heap_alloc(sizeof(sending_thread_params));
send_params = heap_alloc(sizeof(*send_params));
send_params->data = heap_alloc(length);
memcpy(send_params->data, data, length);
send_params->length = length;
send_params->sock = s;
send_params->max_initial_delay = max_initial_delay;
memcpy(&send_params->dest, multi_addr->ai_addr, (family == AF_INET6) ? sizeof(SOCKADDR_IN6) : sizeof(SOCKADDR_IN));
thread_handle = CreateThread(NULL, 0, sending_thread, send_params, 0, NULL);
if (thread_handle == NULL)
{
WARN("CreateThread failed (error %d)\n", GetLastError());
closesocket(s);
heap_free(send_params->data);
heap_free(send_params);
continue;
}
CloseHandle(thread_handle);
- }
- ret = TRUE;
+cleanup:
- freeaddrinfo(multi_addr);
- if (adapter_addresses != NULL) heap_free(adapter_addresses);
- return ret;
+}
+BOOL send_udp_multicast(IWSDiscoveryPublisherImpl *impl, char *data, int length, int max_initial_delay) +{
- if ((impl->addressFamily & WSDAPI_ADDRESSFAMILY_IPV4) && (!send_udp_multicast_of_type(data, length, max_initial_delay, AF_INET))) return FALSE;
- if ((impl->addressFamily & WSDAPI_ADDRESSFAMILY_IPV6) && (!send_udp_multicast_of_type(data, length, max_initial_delay, AF_INET6))) return FALSE;
- return TRUE;
+}
+void terminate_networking(IWSDiscoveryPublisherImpl *impl) +{
- BOOL needsCleanup = impl->publisherStarted;
- impl->publisherStarted = FALSE;
- if (needsCleanup)
WSACleanup();
+}
+BOOL init_networking(IWSDiscoveryPublisherImpl *impl) +{
- WSADATA wsaData;
- int ret = WSAStartup(MAKEWORD(2, 2), &wsaData);
- if (ret != 0)
- {
WARN("WSAStartup failed with error: %d\n", ret);
return FALSE;
- }
- impl->publisherStarted = TRUE;
- /* TODO: Start listening */
- return TRUE;
+} diff --git a/dlls/wsdapi/soap.c b/dlls/wsdapi/soap.c index cb1ec0ea9d..cd3900007d 100644 --- a/dlls/wsdapi/soap.c +++ b/dlls/wsdapi/soap.c @@ -27,6 +27,8 @@ #include "wine/debug.h" #include "wine/heap.h"
+WINE_DEFAULT_DEBUG_CHANNEL(wsdapi);
#define APP_MAX_DELAY 500
static BOOL write_and_send_message(IWSDiscoveryPublisherImpl *impl, WSD_SOAP_HEADER *header, WSDXML_ELEMENT *body_element, @@ -49,7 +51,17 @@ static BOOL write_and_send_message(IWSDiscoveryPublisherImpl *impl, WSD_SOAP_HEA
memcpy(full_xml, xml_header, xml_header_len);
- /* TODO: Send the message */
if (remote_address == NULL)
{
/* Send the message via UDP multicast */
if (send_udp_multicast(impl, full_xml, xml_length + xml_header_len, max_initial_delay))
ret = TRUE;
}
else
{
/* TODO: Send the message via UDP unicast */
FIXME("TODO: Send the message via UDP unicast\n");
}
heap_free(full_xml);
@@ -61,7 +73,7 @@ HRESULT send_hello_message(IWSDiscoveryPublisherImpl *impl, LPCWSTR id, ULONGLON const WSD_URI_LIST *xaddrs_list, const WSDXML_ELEMENT *hdr_any, const WSDXML_ELEMENT *ref_param_any, const WSDXML_ELEMENT *endpoint_ref_any, const WSDXML_ELEMENT *any) {
- HRESULT ret = E_NOTIMPL;
HRESULT ret = E_OUTOFMEMORY;
/* TODO: Populate message body */
diff --git a/dlls/wsdapi/tests/discovery.c b/dlls/wsdapi/tests/discovery.c index e4a672b311..d81dcdc936 100644 --- a/dlls/wsdapi/tests/discovery.c +++ b/dlls/wsdapi/tests/discovery.c @@ -570,7 +570,7 @@ static void Publish_tests(void)
/* Publish the service */ rc = IWSDiscoveryPublisher_Publish(publisher, publisherIdW, 1, 1, 1, NULL, NULL, NULL, NULL);
- todo_wine ok(rc == S_OK, "Publish failed: %08x\n", rc);
ok(rc == S_OK, "Publish failed: %08x\n", rc);
/* Wait up to 2 seconds for messages to be received */ if (WaitForMultipleObjects(msgStorage->numThreadHandles, msgStorage->threadHandles, TRUE, 2000) == WAIT_TIMEOUT)
@@ -583,7 +583,7 @@ static void Publish_tests(void) DeleteCriticalSection(&msgStorage->criticalSection);
/* Verify we've received a message */
- todo_wine ok(msgStorage->messageCount >= 1, "No messages received\n");
ok(msgStorage->messageCount >= 1, "No messages received\n");
sprintf(endpointReferenceString, "wsa:EndpointReferencewsa:Address%s</wsa:Address></wsa:EndpointReference>", publisherId);
diff --git a/dlls/wsdapi/wsdapi_internal.h b/dlls/wsdapi/wsdapi_internal.h index 631dfb3d7e..417eed9145 100644 --- a/dlls/wsdapi/wsdapi_internal.h +++ b/dlls/wsdapi/wsdapi_internal.h @@ -49,6 +49,12 @@ typedef struct IWSDiscoveryPublisherImpl { BOOL publisherStarted; } IWSDiscoveryPublisherImpl;
+/* network.c */
+BOOL init_networking(IWSDiscoveryPublisherImpl *impl); +void terminate_networking(IWSDiscoveryPublisherImpl *impl); +BOOL send_udp_multicast(IWSDiscoveryPublisherImpl *impl, char *data, int length, int max_initial_delay);
/* soap.c */
HRESULT send_hello_message(IWSDiscoveryPublisherImpl *impl, LPCWSTR id, ULONGLONG metadata_ver, ULONGLONG instance_id,
Hi Huw,
It seems odd to store these as strings and then resolve them. Couldn't they just be stored direclty as addresses?
Yes, I guess we could populate a sockaddr_in's in_addr.S_addr with htonl(0xEFFFFFFA), and a sockaddr_in6's s6_addr with { 0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x0C };
We will need the resolve_address function in a later patch however (not submitted with this patchset) for listening for incoming messages. Basically the same code as:
https://source.winehq.org/git/wine.git/blob/HEAD:/dlls/wsdapi/tests/discover...
I can take it out of this patch and add it into the later patch if you think that's better though.
Are these things really supposed to go off semi-randomly?
Yes - the SOAP over UDP specification supplies an algorithm to be used, including random delays to try to avoid collisions:
http://specs.xmlsoap.org/ws/2004/09/soap-over-udp/soap-over-udp.pdf (section 4.4, plus Appendix I)
and the WS-Discovery specification provides for a message-specific randomised initial delay:
http://specs.xmlsoap.org/ws/2005/04/discovery/ws-discovery.pdf (section 2.4)
Thanks for your other comments, I appreciate your time spent reviewing this.
Cheers,
Owen