Обнаружение SSDP в Qt/C++

Я пытаюсь написать небольшой двоичный файл контроллера для динамика Sonos. Для этого я хочу выполнить обнаружение SSDP.

   QHostAddress groupAddress = QHostAddress("239.255.255.250");
    m_socket = new QUdpSocket(this);
    auto ok = m_socket->bind(QHostAddress::AnyIPv4, 56123, QUdpSocket::ShareAddress);

    if (!ok)
    {
        return;
    }


    ok = m_socket->joinMulticastGroup(groupAddress);
    if (!ok)
    {
        return;
    }
    QByteArray message("M-SEARCH * HTTP/1.1\r\n"        \
                       "HOST: 239.255.255.250:1900\r\n" \
                       "MAN: \"ssdp:discover\"\r\n" \
                       "MX: 5\r\n" \
                       "ST: urn:smartspeaker-audio:service:SpeakerGroup:1\r\n" \
                       "linux/4.13 UPnP/1.1 myos/0.11.1\r\n" \
                       "\r\n");

    for(int i=0; i<4; i++){
        auto writeOk = m_socket->writeDatagram(message.data(), groupAddress, 1900);

        if (writeOk == -1)
        {
            qDebug() << "Writing Datagram failed";
        }

    }

    while (m_socket->hasPendingDatagrams())
    {
        QByteArray reply;
        reply.resize(m_socket->pendingDatagramSize());
        m_socket->readDatagram(reply.data(), reply.size());
        qDebug() << reply.data();
    }
    qDebug() << "No more pending datagrams";

Однако при использовании wireshark или любого другого сетевого монитора я вижу какие-либо отправляемые пакеты. Я запустил strace в бинарнике и получил такой вывод:

socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 5
setsockopt(5, SOL_SOCKET, SO_BROADCAST, [1], 4) = 0
setsockopt(5, SOL_IP, IP_PKTINFO, [1], 4) = 0
setsockopt(5, SOL_IP, IP_RECVTTL, [1], 4) = 0
setsockopt(5, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
bind(5, {sa_family=AF_INET, sin_port=htons(56123), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
getsockname(5, {sa_family=AF_INET, sin_port=htons(56123), sin_addr=inet_addr("0.0.0.0")}, [28->16]) = 0
getpeername(5, 0x7ed3aae8, [16])        = -1 ENOTCONN (Transport endpoint is not connected)
getsockopt(5, SOL_SOCKET, SO_TYPE, [2], [4]) = 0
setsockopt(5, SOL_IP, IP_ADD_MEMBERSHIP, {imr_multiaddr=inet_addr("239.255.255.250"), imr_interface=inet_addr("0.0.0.0")}, 8) = 0
sendmsg(5, {msg_name={sa_family=AF_INET, sin_port=htons(1900), sin_addr=inet_addr("239.255.255.250")}, msg_namelen=16, msg_iov=[{iov_base="M-SEARCH * HTTP/1.1\r\nHOST: 239.2"..., iov_len=167}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, MSG_NOSIGNAL) = 167
sendmsg(5, {msg_name={sa_family=AF_INET, sin_port=htons(1900), sin_addr=inet_addr("239.255.255.250")}, msg_namelen=16, msg_iov=[{iov_base="M-SEARCH * HTTP/1.1\r\nHOST: 239.2"..., iov_len=167}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, MSG_NOSIGNAL) = 167
sendmsg(5, {msg_name={sa_family=AF_INET, sin_port=htons(1900), sin_addr=inet_addr("239.255.255.250")}, msg_namelen=16, msg_iov=[{iov_base="M-SEARCH * HTTP/1.1\r\nHOST: 239.2"..., iov_len=167}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, MSG_NOSIGNAL) = 167
sendmsg(5, {msg_name={sa_family=AF_INET, sin_port=htons(1900), sin_addr=inet_addr("239.255.255.250")}, msg_namelen=16, msg_iov=[{iov_base="M-SEARCH * HTTP/1.1\r\nHOST: 239.2"..., iov_len=167}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, MSG_NOSIGNAL) = 167
recv(5, 0x7ed3ab2b, 1, MSG_PEEK)        = -1 EAGAIN (Resource temporarily unavailable)

Что бросается в глаза здесь для меня, так это сбой системного вызова recv(), а также getpeername(). Чтобы убедиться, что это не проблема с сетевыми разрешениями или что-то подобное, я написал скрипт python:

class SSDPClient:

    def __init__(self):
        self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    def discover(self, timeout):
        request_content = ("M-SEARCH * HTTP/1.1\n"
                          "HOST: 239.255.255.250:1900\n"
                          "MAN: \"ssdp:discover\"\n"
                          "MX: {duration}\n"
                          "ST: urn:smartspeaker-audio:service:SpeakerGroup:1\n"
                          "USER-AGENT: {osstring} UPnP/1.1 {productstring}\n").format(duration=timeout, osstring="linux/4.13", productstring="myos/0.11.1").encode('utf-8')
        [self._socket.sendto(request_content, ("239.255.255.250", 1900)) for i in range(0,3)]
        self._socket.settimeout(timeout)
        response, address = self._socket.recvfrom(1024)
        print(response)

Этот скрипт работает. Беговая дорожка на этом:

socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC, IPPROTO_IP) = 3
sendto(3, "M-SEARCH * HTTP/1.1\nHOST: 239.25"..., 171, 0, {sa_family=AF_INET, sin_port=htons(1900), sin_addr=inet_addr("239.255.255.250")}, 16) = 171
sendto(3, "M-SEARCH * HTTP/1.1\nHOST: 239.25"..., 171, 0, {sa_family=AF_INET, sin_port=htons(1900), sin_addr=inet_addr("239.255.255.250")}, 16) = 171
sendto(3, "M-SEARCH * HTTP/1.1\nHOST: 239.25"..., 171, 0, {sa_family=AF_INET, sin_port=htons(1900), sin_addr=inet_addr("239.255.255.250")}, 16) = 171
ioctl(3, FIONBIO, [1])                  = 0
clock_gettime(CLOCK_MONOTONIC, {tv_sec=10531, tv_nsec=209135513}) = 0
poll([{fd=3, events=POLLIN}], 1, 3000)  = 1 ([{fd=3, revents=POLLIN}])

Что меня поражает, так это то, что код Python, похоже, не выполняет все системные вызовы setsockopt. Может ли кто-нибудь помочь мне выяснить, в чем проблема с использованием QUdpSocket? Я знаю, что если мне это нужно, я могу реализовать с использованием необработанных сокетов, но я бы предпочел использовать встроенный материал, если я смогу заставить его работать.


person Curunir    schedule 05.10.2020    source источник
comment
Вы отправляете три UDP-сообщения и ожидаете ответа в ту же миллисекунду. Это не будет работать по сети. Более того, UDP-сокеты Qt по умолчанию не блокируются. В качестве быстрого хака вы можете вызвать m_socket->waitForReadyRead(), но правильным решением было бы присоединить обработчик к сигналу readyRead и вернуться в цикл обработки событий.   -  person Botje    schedule 05.10.2020
comment
Спасибо, это была проблема, сокет был закрыт сразу после отправки полезной нагрузки. Глупая ошибка <.<   -  person Curunir    schedule 05.10.2020


Ответы (1)


Сокеты Qt UDP по умолчанию не блокируются. Метод hasPendingDatagrams предполагает, что цикл событий успел сначала получить и обработать входящие сообщения.

В качестве быстрого взлома вы можете вызвать m_socket->waitForReadyRead(). Это запускает цикл событий в течение ограниченного времени (30 секунд) и, надеюсь, получает некоторые пакеты и устанавливает флаг. Правильным решением было бы присоединить обработчик к сигналу readyRead и вернуться в цикл обработки событий.

person Botje    schedule 05.10.2020
comment
Я решил это с помощью waitForReadyRead(), причина в том, что я вынужден выполнять блокировку обнаружения. - person Curunir; 05.10.2020