I am trying to write a small controller binary for a sonos speaker. To do that I want to perform a SSDP discovery.
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";
However when using wireshark or any other network monitor I do see any packets being send. I ran strace on the binary and got this output:
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)
What sticks out here for me is the syscall recv() failing and also getpeername(). To verify its not a network permission issue or something similar I wrote a python script:
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)
This script works. Running strace on that:
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}])
What strikes me is that the python code does not seem to do all of the setsockopt
syscalls.
Can anyone help me figuring out whats the issue with using the QUdpSocket? I know that if I need to that I can implement using raw sockets but I'd rather use the built in stuff, if I can get it working
m_socket->waitForReadyRead()
, but a proper solution would be to attach a handler to thereadyRead
signal and drop back into the event loop. – Botje