【问题标题】:Listening for a UDP unicast reply to a UDP multicast message侦听对 UDP 多播消息的 UDP 单播回复
【发布时间】:2014-12-04 06:44:21
【问题描述】:

我正在尝试在 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.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);
}

}

【问题讨论】:

  • 设备在本地主机上?如何?尝试删除bind() 步骤和IP_MULTICAST_IF 步骤。
  • 为了避免防火墙问题,我从软件 (minidlna) 中并在本地系统上运行的 DLNA 设备开始。一旦我开始工作,我将尝试在 LAN 上使用真实设备。 minidlna 从 localhost 响应其他 DLNA 控制器。感谢您提出的补救措施。不幸的是,如果我删除 IP_MULTICAST_IF 步骤,来自我的程序的流量会在 eth0 上消失。单独删除绑定,或同时删除绑定和 IP_MULTICAST_IF,似乎无法解决问题。
  • 你机器上 route -n 的输出是什么?
  • 嗨@wick。输出(对糟糕的格式表示歉意)是:Destination Gateway Genmask Flags Metric Ref Use Iface0.0.0.0 192.168.1.1 0.0.0.0 UG 0 0 0 eth0192.168.1.0 0.0.0.0 255.255.255.0 U 1 0 0 eth0
  • @Tony:据此,发送到 239.x.x.x 的数据包将通过 eth0,而不是环回,对吗?根据我的实践,90% 的多播故障排除都在路由表中......这不是答案,但我会朝那个方向看,并为 238.0.0.0/8 添加静态路由

标签: sockets udp multicast upnp


【解决方案1】:

最后对我有用的代码如下。

方法总结:

  • 创建一个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.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 未被阻塞。

【讨论】:

  • 那和你原来的代码有什么区别呢?
  • @EJP。已编辑以指示更改。
  • 消息通过 IP 路由表指示的任何接口发出。它与“无论最小编号的接口碰巧是什么”没有任何关系。
  • @EJP。我被this reference弄糊涂了。我现在看到at least one other 指的是路由表(在订阅组的上下文中)。我已将答案编辑为少说,希望能减少出错的机会。
【解决方案2】:

在 cmets 中发现,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.x.x.x 的数据包将通过 eth0,而不是环回。

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

【讨论】:

  • 静态路由会起作用吗?当我运行 ifconfig 时,不清楚 lo 是否支持多播。据我了解,使用 IF_MULTICAST_LOOP 可以更直接地工作,将数据包直接放入 lo 队列,而不是需要 lo 来发现要添加到队列中的多播数据包。如果我错过了重点,我深表歉意——超出了我的理解范围。
  • @Tony:不幸的是,我无法测试它,因为您有非常具体的配置,但是使用 route 命令添加这样的静态路由并进行检查很容易。路线的人解释了如何。
  • 我尝试使用sudo ip route add 239.0.0.0/8 via 127.0.0.1 添加静态路由。添加了路线,ip route get to 239.255.255.250 生成了multicast 239.255.255.250 dev lo src 192.168.1.xxx。但是,无论有没有 IF_MULTICAST_LOOP,多播都会停止工作。
  • 不完全,不要使用via,尝试添加: route add -net 239.0.0.0 netmask 255.0.0.0 dev lo0
  • 谢谢。我试过了,但得到了同样的结果——没有快乐。没有静态路由的 IF_MULTICAST_LOOP 可以完成这项工作。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-09
相关资源
最近更新 更多