侦听对UDP多播消息的UDP单播答复



我正试图在linux上实现一个非常简单的upnp控制器,这样我就可以控制一个需要专有软件的设备。

文档说,我需要向特定地址和端口发送特定形式的UDP多播请求(请参阅下面代码中的"M-SEARCH"字符串),设备将通过UDP单播对我发送的地址和端口做出响应。

我做不到。tcpdump显示UDP多播请求指向正确的地址和端口,格式看起来正确,但我看不到回复。

我从环回接口发送并在环回接口上侦听(设备在同一台机器上)。

另一个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.1rn"
                   "Host: 239.255.255.250:1900rn"
                   "Man: "ssdp:discover"rn"
                   "ST: upnp:rootdevicern"
                   "MX: 3rn"
                   "User-Agent: Test/1.0rn"
                   "rn");
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);
}
}

最后为我工作的代码如下。

方法总结:

  • 创建UDP套接字
  • 启用IF_MULTICAST_LOOP(允许与同一主机上的客户端进行通信)
  • 设置IF_MULTICAST_TTL
  • 绑定到INADDR_ANY和端口8201(端口号任意选择)
  • 发送多播消息至239.255.255.250:1900
  • 使用同一套接字接收回复

我们开始了:

#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.1rn"
               "Host: 239.255.255.250:1900rn"
               "Man: "ssdp:discover"rn"
               "ST: upnp:rootdevicern"
               "MX: 3rn"
               "User-Agent: Test/1.0rn"
               "rn");
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("%sn", buffer);
if (close(sock) < 0) {
    perror("close");
    exit(EXIT_FAILURE);
}
}

要从外部客户端获取答复,请确保端口8201未被阻止。

如注释所示,route -n:的输出

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

表明发送到239.x.x.x的数据包将通过eth0,而不是环回。

因此,添加一个静态路由以强制环回接口上的传出数据包是有意义的,或者确保您可以通过IF_MULTICAST_LOOP标志通过环回进行接收。

最新更新