I am a newbie for VLAN, and coding an UDP program to send/receive unicast and multicast over VLAN. I need to get three thing from the incoming package.
With my codes(listed below), the multicast looks good, but the unicast has problem.
// 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;
}
When I run it at two PC, I find the sender-address of unicast is wrong.
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
We can see, the sender-address of unicast is not the VLAN IP-address.
Then, I tried to bind the socket to the local VLAN IP-address. Means, I deleted the codes where marked as KEY CODE 1
, and add the codes where marked as KEY CODE 2
. After this, I could not receive any multicast any more. But, at this moment, I could catch those multicast-packages by 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
Looks like, when I bind the socket on the local VLAN IP-address, the recvmsg
not working for multicast any more.
So, any idea what I am missing form the codes?
On both machines the physical interface and virtual interface have IPs in the same subnet, so a unicast packet may be sent out either one depending on how your routing table is set up (run netstat -nr
to see the routing table). Based on the output, it seems like the routing table favors the physical interface instead of the virtual interface on both machines.
When you bind a socket to a specific network interface on Linux, you won't be able to receive multicast packets on that socket even if you've joined the multicast group. You could bind to the multicast address, but then you won't receive unicast packets. So if you want to get unicast and multicast packets on a single socket you have to bind to INADDR_ANY
.
You'll need to either modify the routing table (using either the route
or ip route
command) or change your virtual interfaces to use IPs on a different subnet than the physical interfaces.