Search code examples
clinuxsocketsupnpssdp

Unable to show all UPnP devices within the local network C++


I am new to UPnP development and trying to discover all UPnP device within the local network, and I followed an example from the online resource, but my code will only keep looping at the first response. How could I get another response other than the first one, could I get some hints for this?

Example :

First Response from 192.168.xxx.123, and it will keeps printing the following result:

HTTP/1.1 200 OK
CACHE-CONTROL: max-age=1790
DATE: Thu, 01 Jan 2015 10:43:15 GMT
ST: uuid:4d696xxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
USN: uuid:4d696xxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
EXT:
SERVER: Linux 2.6 DLNADOC/1.50 UPnP/1.0 ReadyDLNA/1.0.26
LOCATION: http://192.168.xxx.123:xxxx/rootDesc.xml
Content-Length: 0

I checked in Wireshark, and I can see the other device [IP: 192.168.xxx.99] has given me a response, but I am not able to receive it in my code.

I also read a question on SO and used select in my code, but still cannot get it working. Receiving response(s) from N number of clients in reply to a broadcast request over UDP

The code:

#include <QCoreApplication>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>

#define RESPONSE_BUFFER_LEN 1024
#define SSDP_MULTICAST "239.255.255.250"
#define SSDP_PORT 1900

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    int sock;
    size_t ret;
    unsigned int socklen;
    struct sockaddr_in sockname;
    struct sockaddr clientsock;
    struct hostent *hostname;
    char data[] =
        "M-SEARCH * HTTP/1.1\r\n"
        "HOST: 239.255.255.250:1900\r\n"
        "MAN: \"ssdp:discover\"\r\n"
        "ST: ssdp:all\r\n"
        "MX: 120\r\n"
        "\r\n";
    char buffer[RESPONSE_BUFFER_LEN];
    unsigned int len = RESPONSE_BUFFER_LEN;
    fd_set fds;
    struct timeval timeout;

    hostname = gethostbyname(SSDP_MULTICAST);
    hostname->h_addrtype = AF_INET;

    if((sock = socket(PF_INET, SOCK_DGRAM, 0)) == -1)
    {
        printf("err: socket() failed");
        return -1;
    }

    memset((char *)&sockname, 0, sizeof(struct sockaddr_in));
    sockname.sin_family = AF_INET;
    sockname.sin_port = htons(SSDP_PORT);
    sockname.sin_addr.s_addr = *((unsigned long *)(hostname->h_addr_list[0]));

    ret = sendto(sock, data, strlen(data), 0, (struct sockaddr *)&sockname,
                 sizeof(struct sockaddr_in));
    if(ret != strlen(data))
    {
        printf("err:sendto");
        return -1;
    }

    /* Get response */
    FD_ZERO(&fds);
    FD_SET(sock, &fds);
    timeout.tv_sec = 5;
    timeout.tv_usec = 5;
    while(select(sock + 1, &fds, NULL, NULL, &timeout) > 0)
    {
        if(FD_ISSET(sock, &fds))
        {
            socklen = sizeof(clientsock);
            if((len = recvfrom(sock, buffer, len, MSG_PEEK, &clientsock, &socklen)) == (size_t)-1)
            {
                printf("err: recvfrom");
                return -1;
            }

            buffer[len] = '\0';

            /* Check the HTTP response code */
            if(strncmp(buffer, "HTTP/1.1 200 OK", 12) != 0)
            {
                printf("err: ssdp parsing ");
                return -1;
            }
            printf(buffer);
        }
        else
        {
            printf("err: no ssdp answer");
        }
    }
    //close(sock);
    return a.exec();
}

Solution

  • You are using MSG_PEEK, which means to read the first message in the socket's receive buffer, but not remove it from the buffer.

    Therefore every time you call recvfrom you get the first received message.

    Change MSG_PEEK to 0 and then each call will read the first message that hasn't been read yet. (So the second call will read the second message, and so on)