2014-10-09 12 views
2

Я пытаюсь реализовать очень простой контроллер upnp на linux, чтобы я мог управлять устройством, которое в противном случае требует проприетарного программного обеспечения.Прослушивание одноадресного ответа UDP на сообщение многоадресной рассылки UDP

Документы говорят, что мне нужно отправить запрос многоадресной рассылки UDP определенной формы (см. Строку «М-ПОИСК» в приведенном ниже коде) на конкретный адрес и порт, и что устройства будут отвечать UDP unicast по адресу и порту, который я отправил.

Я не могу выполнить эту работу. tcpdump показывает, что запрос многоадресной рассылки UDP подходит к правильному адресу и порту, и формат отображается правильно, но я не вижу ответа.

Отправляю и слушаю интерфейс loopback (устройство находится на одной машине).

Другой контроллер upnp (т. Е. Не мой) работает правильно на интерфейсе петлевой петли.

Может кто-нибудь предложить, что я делаю неправильно?

Вот код:

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <errno.h> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <netinet/in.h> 
#include <netinet/udp.h> 
#include <unistd.h> 
#include <fcntl.h> 

#define MAXBUFSIZE 65536 

int main(int argc, char ** argv) { 

unsigned char loop; 
loop = 0; 
unsigned char ttl; 
ttl = 4; 
int bcast; 
bcast = 1; 

int sock; 

sock = socket(AF_INET, SOCK_DGRAM, 0); 
if (sock < 0) { 
    perror("socket"); 
    exit(EXIT_FAILURE); 
} 

struct sockaddr_in destadd; 
memset(&destadd, 0, sizeof(destadd)); 
destadd.sin_family = AF_INET; 
destadd.sin_port = htons((uint16_t)1900); 
if (inet_pton(AF_INET, "239.255.255.250", &destadd.sin_addr) < 1) { 
    perror("inet_pton dest"); 
    exit(EXIT_FAILURE); 
} 

struct sockaddr_in interface_addr; 
memset(&interface_addr, 0, sizeof(interface_addr)); 
interface_addr.sin_family = AF_INET; 
interface_addr.sin_port = htons(0); 
if (inet_pton(AF_INET, "127.0.0.1", &interface_addr.sin_addr) < 1) { 
    perror("inet_pton interface"); 
    exit(EXIT_FAILURE); 
} 

if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)) < 0){ 
    perror("setsockopt loop"); 
    exit(EXIT_FAILURE); 
} 

if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0){ 
    perror("setsockopt ttl"); 
    exit(EXIT_FAILURE); 
} 

if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, 
       (struct in_addr *)&interface_addr.sin_addr, 
       sizeof(interface_addr.sin_addr)) < 0) { 
    perror("setsockopt if"); 
    exit(EXIT_FAILURE); 
} 

if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &bcast, sizeof(bcast)) < 0) { 
    perror("setsockopt bcast"); 
    exit(EXIT_FAILURE); 
} 

struct ip_mreqn imr; 
memset(&imr, 0, sizeof(imr)); 
if (inet_pton(AF_INET, "239.255.255.250", &imr.imr_multiaddr.s_addr) < 1) { 
    perror("inet_pton"); 
    exit(EXIT_FAILURE); 
} 
inet_pton(AF_INET, "127.0.0.1", (struct in_addr *)&imr.imr_address); 
imr.imr_ifindex = 0; 
if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, 
       (void *)&imr, sizeof(imr)) < 0) { 
    perror("setsockopt addmem"); 
    exit(EXIT_FAILURE); 
} 

if (bind(sock, (struct sockaddr *)&interface_addr, 
    sizeof(struct sockaddr_in)) < 0) { 
    perror("bind"); 
    exit(EXIT_FAILURE); 
} 

char buffer[1024]; 

strcpy(buffer, "M-SEARCH * HTTP/1.1\r\n" 
        "Host: 239.255.255.250:1900\r\n" 
        "Man: \"ssdp:discover\"\r\n" 
        "ST: upnp:rootdevice\r\n" 
        "MX: 3\r\n" 
        "User-Agent: Test/1.0\r\n" 
        "\r\n"); 

if (sendto(sock, buffer, strlen(buffer), 0, (struct sockaddr*)&destadd, 
     sizeof(destadd)) < 0) { 
    perror("sendto"); 
    exit(EXIT_FAILURE); 
} 

if (recvfrom(sock, &buffer, sizeof(buffer)-1, 0, NULL, NULL) < 0) { 
    perror("recvfrom"); 
    exit(EXIT_FAILURE); 
} 


if (close(sock) < 0) { 
    perror("close"); 
    exit(EXIT_FAILURE); 
} 

} 
+1

Устройство находится на локальном хосте? Как? Попробуйте удалить шаг 'bind()' и шаг 'IP_MULTICAST_IF'. – EJP

+0

Чтобы избежать проблем с брандмауэром, я начинаю с устройства DLNA, которое находится в программном обеспечении (minidlna) и работает в локальной системе. Как только я получу эту работу, я попытаюсь использовать реальное устройство в локальной сети. minidlna отвечает от localhost на другие контроллеры DLNA. Спасибо за предложенные средства. К сожалению, если я удалю шаг IP_MULTICAST_IF, трафик из моей программы выходит на eth0.Удаление связывания в одиночку или как bind, так и IP_MULTICAST_IF, похоже, не решает проблему. – Tony

+1

Каков результат маршрута -n на вашем компьютере? – wick

ответ

1

Код, который работал для меня в конце концов, следует.

Краткое описание метода:

  • Создание UDP-сокет
  • Enable IF_MULTICAST_LOOP (для обеспечения связи с клиентами на одном хосте)
  • Set IF_MULTICAST_TTL
  • Привязать к INADDR_ANY и порт 8201 (порт нет произвольно выбранных)
  • Отправить многоадресное сообщение до 239.255.255.250:1900
  • Получить ответ используя s AME гнездо

Здесь мы идем:

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <errno.h> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <netinet/in.h> 
#include <netinet/udp.h> 
#include <unistd.h> 
#include <fcntl.h> 

int main(int argc, char ** argv) { 

unsigned char loop; 
loop = 1; // Needs to be on to get replies from clients on the same host 
unsigned char ttl; 
ttl = 4; 
int bcast; 
bcast = 1; 

int sock; 
sock = socket(AF_INET, SOCK_DGRAM, 0); 
if (sock < 0) { 
    perror("socket"); 
    exit(EXIT_FAILURE); 
} 

// Multicast message will be sent to 239.255.255.250:1900 
struct sockaddr_in destadd; 
memset(&destadd, 0, sizeof(destadd)); 
destadd.sin_family = AF_INET; 
destadd.sin_port = htons((uint16_t)1900); 
if (inet_pton(AF_INET, "239.255.255.250", &destadd.sin_addr) < 1) { 
    perror("inet_pton dest"); 
    exit(EXIT_FAILURE); 
} 

// Listen on all interfaces on port 8201 
struct sockaddr_in interface_addr; 
memset(&interface_addr, 0, sizeof(interface_addr)); 
interface_addr.sin_family = AF_INET; 
interface_addr.sin_port = htons(8201); 
interface_addr.sin_addr.s_addr = htonl(INADDR_ANY); 

// Got to have this to get replies from clients on same machine 
if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)) < 0){ 
    perror("setsockopt loop"); 
    exit(EXIT_FAILURE); 
} 

if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0){ 
    perror("setsockopt ttl"); 
    exit(EXIT_FAILURE); 
} 

// Bind to port 8201 on all interfaces 
if (bind(sock, (struct sockaddr *)&interface_addr, 
    sizeof(struct sockaddr_in)) < 0) { 
    perror("bind"); 
    exit(EXIT_FAILURE); 
} 

char buffer[1024]; 

strcpy(buffer, "M-SEARCH * HTTP/1.1\r\n" 
       "Host: 239.255.255.250:1900\r\n" 
       "Man: \"ssdp:discover\"\r\n" 
       "ST: upnp:rootdevice\r\n" 
       "MX: 3\r\n" 
       "User-Agent: Test/1.0\r\n" 
       "\r\n"); 

if (sendto(sock, buffer, strlen(buffer), 0, (struct sockaddr*)&destadd, 
     sizeof(destadd)) < 0) { 
    perror("sendto"); 
    exit(EXIT_FAILURE); 
} 

if (recvfrom(sock, &buffer, sizeof(buffer)-1, 0, NULL, NULL) < 0) { 
    perror("recvfrom"); 
    exit(EXIT_FAILURE); 
} 


printf("%s\n", buffer); 

if (close(sock) < 0) { 
    perror("close"); 
    exit(EXIT_FAILURE); 
} 
} 

Чтобы получить ответы от внешних клиентов, обеспечить порт 8201 не заблокирован.

+0

Итак, какая же разница между этим и вашим исходным кодом? – EJP

+0

@ EJP. Отредактировано, чтобы указать изменения. – Tony

+0

Сообщения выходят через любой интерфейс, указанный таблицами маршрутизации IP. Он не имеет ничего общего с «каким бы ни был наименьший пронумерованный интерфейс». – EJP

0

Как найти в комментарии, выход route -n:

0.0.0.0 192.168.1.1 0.0.0.0 UG 0 0 0 eth0 192.168.1.0 0.0.0.0 255.255.255.0 U 1 0 0 eth0

предполагает, что пакет, отправленный в 239.xxx, будет проходить через eth0, а не loopback.

Таким образом, было бы целесообразно либо добавить статический маршрут для принудительной передачи исходящих пакетов по интерфейсу loopback, либо убедиться, что вы можете получать через loopback с помощью флага IF_MULTICAST_LOOP.

+0

Будет ли работать статический маршрут? Когда я запускаю ifconfig, неясно, что lo является многоадресной. Использование IF_MULTICAST_LOOP работает более непосредственно, как я понимаю, путем помещения пакета непосредственно в очередь lo, вместо того, чтобы определить, что пакет многоадресной рассылки должен быть добавлен в очередь. Извиняюсь, если я упускаю из виду свою глубину. – Tony

+0

@Tony: к сожалению, я не могу проверить его, поскольку у вас есть довольно определенная конфигурация, но легко добавить статический маршрут, подобный этому, с помощью команды route и проверки. объясните, как. – wick

+0

Я попытался добавить статический маршрут, используя 'sudo ip route add 239.0.0.0/8 через 127.0.0.1'. Маршрут был добавлен, и 'ip route до 239.255.255.250' произвел' multicast 239.255.255.250 dev lo src 192.168.1.xxx'. Однако многоадресная передача перестала работать с IF_MULTICAST_LOOP или без него. – Tony