关于 VLAN 上的 UDP 套接字 Unicast/Multicast 的问题
Question for UDP socket Unicast/Multicast over VLAN
我是 VLAN 的新手,正在编写一个 UDP 程序以 send/receive 通过 VLAN 进行单播和多播。
我需要从传入的包裹中取出三样东西。
- 发件人的IP和端口(以便记录发件人)
- 接收者的IP和端口(这样我就可以知道它是单播还是多播)。
- 当然,数据本身。
用我的代码(下面列出),多播看起来不错,但单播有问题。
// gcc udp.c -o udp -pthread
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <arpa/inet.h>
static int fd = 0;
static uint16_t port = 0;
static char* local = NULL;
static char* unicast = NULL;
static char* multicast = NULL;
static void* sending(void *_arg)
{
int i = 0;
struct sockaddr_in target;
while(1) {
memset((char *) &target, 0, sizeof(target));
target.sin_family = AF_INET;
if((i % 2) == 1) {
target.sin_addr.s_addr = inet_addr(unicast);
}
else {
target.sin_addr.s_addr = inet_addr(multicast);
}
target.sin_port = htons(port);
char d[1] = {i++};
sendto(fd, d, 1, 0, (struct sockaddr*)&target, sizeof(target));
sleep(1);
}
return (void*)0;
}
int main(int argc, char const *argv[])
{
port = atoi(argv[1]);
local = strdup(argv[2]);
unicast = strdup(argv[3]);
multicast = strdup(argv[4]);
fd = socket(AF_INET, SOCK_DGRAM, 0);
int on = 1, off = 0;
setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on));
struct ip_mreq group;
memset(&group, 0, sizeof(struct ip_mreq));
group.imr_multiaddr.s_addr = inet_addr(multicast);
group.imr_interface.s_addr = inet_addr(local);
setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &group, sizeof(group));
setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &off, sizeof(off));
setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group));
struct sockaddr_in localSock;
memset((char *)&localSock, 0, sizeof(localSock));
localSock.sin_family = AF_INET;
localSock.sin_port = htons(port);
localSock.sin_addr.s_addr = INADDR_ANY; /* KEY CODE 1 */
// localSock.sin_addr.s_addr = inet_addr(local); /* KEY CODE 2 */
bind(fd, (struct sockaddr*)&localSock, sizeof(localSock));
pthread_t tid;
pthread_create(&tid, NULL, sending, NULL);
const int max_length = 2048;
char data_[2048];
while(1) {
char cmbuf[BUFSIZ];
struct msghdr msg;
struct iovec iov[2];
(void)memset(&msg, 0, sizeof(msg));
(void)memset(&iov, 0, sizeof(iov));
iov[0].iov_base = data_;
iov[0].iov_len = max_length-1;
struct sockaddr_in addr;
msg.msg_name = &addr;
msg.msg_namelen = (int)(sizeof(addr));
msg.msg_iov = iov;
msg.msg_iovlen = (int)(1);
msg.msg_control = (caddr_t)cmbuf;
msg.msg_controllen = sizeof(cmbuf);
int result = recvmsg(fd, &msg, 0);
if(result > 0) {
printf("[%s:%d->", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
cmsg != NULL;
cmsg = CMSG_NXTHDR(&msg, cmsg)) {
if (cmsg->cmsg_level == IPPROTO_IP
&& cmsg->cmsg_type == IP_PKTINFO) {
struct in_pktinfo *pi =
(struct in_pktinfo *) CMSG_DATA(cmsg);
if (pi) {
printf("%15s:%d]", inet_ntoa(pi->ipi_addr),
ntohs(addr.sin_port));
}
}
}
printf("%02X\n", (uint8_t)data_[0]);
}
}
// release resources ....
return 0;
}
当我在两台PC上运行时,我发现单播的发件人地址是错误的。
PC1:
4: tap0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 42:2a:e6:xx:xx:xx brd ff:ff:ff:ff:ff:ff
inet 172.0.0.1/16 brd 172.0.255.255 scope global tap0
valid_lft forever preferred_lft forever
5: tap0.6@tap0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 42:2a:e6:xx:xx:xx brd ff:ff:ff:ff:ff:ff
inet 172.0.0.6/16 brd 172.0.255.255 scope global tap0.6
valid_lft forever preferred_lft forever
./udp 30490 172.0.0.6 172.0.0.106 224.244.224.245
[172.0.0.100:30490-> 172.0.0.6:30490]03
[172.0.0.106:30490->224.244.224.245:30490]04
[172.0.0.100:30490-> 172.0.0.6:30490]05
[172.0.0.106:30490->224.244.224.245:30490]06
PC2:
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 08:00:27:xx:xx:xx brd ff:ff:ff:ff:ff:ff
inet 172.0.0.100/16 brd 172.0.255.255 scope global enp0s8
valid_lft forever preferred_lft forever
4: enp0s8.6@enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 08:00:27:faxxxx:xx brd ff:ff:ff:ff:ff:ff
inet 172.0.0.106/16 brd 172.0.255.255 scope global enp0s8.6
valid_lft forever preferred_lft forever
./udp 30490 172.0.0.106 172.0.0.6 224.244.224.245
[172.0.0.6:30490->224.244.224.245:30490]00
[172.0.0.1:30490-> 172.0.0.106:30490]01
[172.0.0.6:30490->224.244.224.245:30490]02
[172.0.0.1:30490-> 172.0.0.106:30490]03
可以看出,单播的sender-address不是VLAN IP-address
然后,我尝试将套接字绑定到本地 VLAN IP 地址。意思是,我删除了标记为KEY CODE 1
的代码,并添加了标记为KEY CODE 2
的代码。此后,我再也收不到任何多播了。但是,此刻,我可以通过 tcpdump
.
捕获那些多播包
17:21:37.682577 IP 172-0-0-6.lightspeed.brhmal.sbcglobal.net.30490 > 224.244.224.245.30490: UDP, length 1
17:21:38.550628 IP 172-0-0-106.lightspeed.brhmal.sbcglobal.net.30490 > 224.244.224.245.30490: UDP, length 1
17:21:38.682709 IP 172-0-0-6.lightspeed.brhmal.sbcglobal.net.30490 > 172-0-0-106.lightspeed.brhmal.sbcglobal.net.30490: UDP, length 1
17:21:39.550925 IP 172-0-0-106.lightspeed.brhmal.sbcglobal.net.30490 > 172-0-0-6.lightspeed.brhmal.sbcglobal.net.30490: UDP, length 1
17:21:39.682835 IP 172-0-0-6.lightspeed.brhmal.sbcglobal.net.30490 > 224.244.224.245.30490: UDP, length 1
17:21:40.551344 IP 172-0-0-106.lightspeed.brhmal.sbcglobal.net.30490 > 224.244.224.245.30490: UDP, length 1
17:21:40.682982 IP 172-0-0-6.lightspeed.brhmal.sbcglobal.net.30490 > 172-0-0-106.lightspeed.brhmal.sbcglobal.net.30490: UDP, length 1
17:21:41.551612 IP 172-0-0-106.lightspeed.brhmal.sbcglobal.net.30490 > 172-0-0-6.lightspeed.brhmal.sbcglobal.net.30490: UDP, length 1
17:21:41.683123 IP 172-0-0-6.lightspeed.brhmal.sbcglobal.net.30490 > 224.244.224.245.30490: UDP, length 1
看起来,当我在本地 VLAN IP 地址上绑定套接字时,recvmsg
不再为多播工作。
那么,知道我在代码中遗漏了什么吗?
在两台机器上,物理接口和虚拟接口的 IP 地址都在同一个子网中,因此根据路由 table 的设置方式,单播数据包可能会被发送出去(运行 netstat -nr
查看路由 table)。根据输出,路由 table 似乎支持物理接口而不是两台机器上的虚拟接口。
当您将套接字绑定到 Linux 上的特定网络接口时,即使您已加入多播组,也无法在该套接字上接收多播数据包。您可以绑定到多播地址,但是您不会收到单播数据包。因此,如果您想在单个套接字上获取单播和多播数据包,则必须绑定到 INADDR_ANY
.
您需要修改路由 table(使用 route
或 ip route
命令)或更改您的虚拟接口以使用与物理接口。
我是 VLAN 的新手,正在编写一个 UDP 程序以 send/receive 通过 VLAN 进行单播和多播。 我需要从传入的包裹中取出三样东西。
- 发件人的IP和端口(以便记录发件人)
- 接收者的IP和端口(这样我就可以知道它是单播还是多播)。
- 当然,数据本身。
用我的代码(下面列出),多播看起来不错,但单播有问题。
// gcc udp.c -o udp -pthread
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <arpa/inet.h>
static int fd = 0;
static uint16_t port = 0;
static char* local = NULL;
static char* unicast = NULL;
static char* multicast = NULL;
static void* sending(void *_arg)
{
int i = 0;
struct sockaddr_in target;
while(1) {
memset((char *) &target, 0, sizeof(target));
target.sin_family = AF_INET;
if((i % 2) == 1) {
target.sin_addr.s_addr = inet_addr(unicast);
}
else {
target.sin_addr.s_addr = inet_addr(multicast);
}
target.sin_port = htons(port);
char d[1] = {i++};
sendto(fd, d, 1, 0, (struct sockaddr*)&target, sizeof(target));
sleep(1);
}
return (void*)0;
}
int main(int argc, char const *argv[])
{
port = atoi(argv[1]);
local = strdup(argv[2]);
unicast = strdup(argv[3]);
multicast = strdup(argv[4]);
fd = socket(AF_INET, SOCK_DGRAM, 0);
int on = 1, off = 0;
setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on));
struct ip_mreq group;
memset(&group, 0, sizeof(struct ip_mreq));
group.imr_multiaddr.s_addr = inet_addr(multicast);
group.imr_interface.s_addr = inet_addr(local);
setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &group, sizeof(group));
setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &off, sizeof(off));
setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group));
struct sockaddr_in localSock;
memset((char *)&localSock, 0, sizeof(localSock));
localSock.sin_family = AF_INET;
localSock.sin_port = htons(port);
localSock.sin_addr.s_addr = INADDR_ANY; /* KEY CODE 1 */
// localSock.sin_addr.s_addr = inet_addr(local); /* KEY CODE 2 */
bind(fd, (struct sockaddr*)&localSock, sizeof(localSock));
pthread_t tid;
pthread_create(&tid, NULL, sending, NULL);
const int max_length = 2048;
char data_[2048];
while(1) {
char cmbuf[BUFSIZ];
struct msghdr msg;
struct iovec iov[2];
(void)memset(&msg, 0, sizeof(msg));
(void)memset(&iov, 0, sizeof(iov));
iov[0].iov_base = data_;
iov[0].iov_len = max_length-1;
struct sockaddr_in addr;
msg.msg_name = &addr;
msg.msg_namelen = (int)(sizeof(addr));
msg.msg_iov = iov;
msg.msg_iovlen = (int)(1);
msg.msg_control = (caddr_t)cmbuf;
msg.msg_controllen = sizeof(cmbuf);
int result = recvmsg(fd, &msg, 0);
if(result > 0) {
printf("[%s:%d->", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
cmsg != NULL;
cmsg = CMSG_NXTHDR(&msg, cmsg)) {
if (cmsg->cmsg_level == IPPROTO_IP
&& cmsg->cmsg_type == IP_PKTINFO) {
struct in_pktinfo *pi =
(struct in_pktinfo *) CMSG_DATA(cmsg);
if (pi) {
printf("%15s:%d]", inet_ntoa(pi->ipi_addr),
ntohs(addr.sin_port));
}
}
}
printf("%02X\n", (uint8_t)data_[0]);
}
}
// release resources ....
return 0;
}
当我在两台PC上运行时,我发现单播的发件人地址是错误的。
PC1:
4: tap0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 42:2a:e6:xx:xx:xx brd ff:ff:ff:ff:ff:ff
inet 172.0.0.1/16 brd 172.0.255.255 scope global tap0
valid_lft forever preferred_lft forever
5: tap0.6@tap0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 42:2a:e6:xx:xx:xx brd ff:ff:ff:ff:ff:ff
inet 172.0.0.6/16 brd 172.0.255.255 scope global tap0.6
valid_lft forever preferred_lft forever
./udp 30490 172.0.0.6 172.0.0.106 224.244.224.245
[172.0.0.100:30490-> 172.0.0.6:30490]03
[172.0.0.106:30490->224.244.224.245:30490]04
[172.0.0.100:30490-> 172.0.0.6:30490]05
[172.0.0.106:30490->224.244.224.245:30490]06
PC2:
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 08:00:27:xx:xx:xx brd ff:ff:ff:ff:ff:ff
inet 172.0.0.100/16 brd 172.0.255.255 scope global enp0s8
valid_lft forever preferred_lft forever
4: enp0s8.6@enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 08:00:27:faxxxx:xx brd ff:ff:ff:ff:ff:ff
inet 172.0.0.106/16 brd 172.0.255.255 scope global enp0s8.6
valid_lft forever preferred_lft forever
./udp 30490 172.0.0.106 172.0.0.6 224.244.224.245
[172.0.0.6:30490->224.244.224.245:30490]00
[172.0.0.1:30490-> 172.0.0.106:30490]01
[172.0.0.6:30490->224.244.224.245:30490]02
[172.0.0.1:30490-> 172.0.0.106:30490]03
可以看出,单播的sender-address不是VLAN IP-address
然后,我尝试将套接字绑定到本地 VLAN IP 地址。意思是,我删除了标记为KEY CODE 1
的代码,并添加了标记为KEY CODE 2
的代码。此后,我再也收不到任何多播了。但是,此刻,我可以通过 tcpdump
.
17:21:37.682577 IP 172-0-0-6.lightspeed.brhmal.sbcglobal.net.30490 > 224.244.224.245.30490: UDP, length 1
17:21:38.550628 IP 172-0-0-106.lightspeed.brhmal.sbcglobal.net.30490 > 224.244.224.245.30490: UDP, length 1
17:21:38.682709 IP 172-0-0-6.lightspeed.brhmal.sbcglobal.net.30490 > 172-0-0-106.lightspeed.brhmal.sbcglobal.net.30490: UDP, length 1
17:21:39.550925 IP 172-0-0-106.lightspeed.brhmal.sbcglobal.net.30490 > 172-0-0-6.lightspeed.brhmal.sbcglobal.net.30490: UDP, length 1
17:21:39.682835 IP 172-0-0-6.lightspeed.brhmal.sbcglobal.net.30490 > 224.244.224.245.30490: UDP, length 1
17:21:40.551344 IP 172-0-0-106.lightspeed.brhmal.sbcglobal.net.30490 > 224.244.224.245.30490: UDP, length 1
17:21:40.682982 IP 172-0-0-6.lightspeed.brhmal.sbcglobal.net.30490 > 172-0-0-106.lightspeed.brhmal.sbcglobal.net.30490: UDP, length 1
17:21:41.551612 IP 172-0-0-106.lightspeed.brhmal.sbcglobal.net.30490 > 172-0-0-6.lightspeed.brhmal.sbcglobal.net.30490: UDP, length 1
17:21:41.683123 IP 172-0-0-6.lightspeed.brhmal.sbcglobal.net.30490 > 224.244.224.245.30490: UDP, length 1
看起来,当我在本地 VLAN IP 地址上绑定套接字时,recvmsg
不再为多播工作。
那么,知道我在代码中遗漏了什么吗?
在两台机器上,物理接口和虚拟接口的 IP 地址都在同一个子网中,因此根据路由 table 的设置方式,单播数据包可能会被发送出去(运行 netstat -nr
查看路由 table)。根据输出,路由 table 似乎支持物理接口而不是两台机器上的虚拟接口。
当您将套接字绑定到 Linux 上的特定网络接口时,即使您已加入多播组,也无法在该套接字上接收多播数据包。您可以绑定到多播地址,但是您不会收到单播数据包。因此,如果您想在单个套接字上获取单播和多播数据包,则必须绑定到 INADDR_ANY
.
您需要修改路由 table(使用 route
或 ip route
命令)或更改您的虚拟接口以使用与物理接口。