Skip to content
Snippets Groups Projects
Commit 9762728c authored by clohr's avatar clohr
Browse files

Reflecteur multicast-unicast

git-svn-id: https://redmine.imt-atlantique.fr/svn/xaal/code/C/branches/version-0.7@2324 b32b6428-25c9-4566-ad07-03861ab6144f
parent a9acfaef
No related branches found
No related tags found
No related merge requests found
reflector.o
reflector
xaal-reflector.service
PROG = reflector
CFLAGS = -Wall -I. -g -O0
all: $(PROG)
clean:
-rm -f *.o *~
proper: clean uninstall
-rm -f $(PROG) xaal-$(PROG).service
test: all
./$(PROG) -a 224.0.29.200 -p 1234 -r 1235
.PHONY: all clean proper test install uninstall svnignore
.SUFFIXES: .service .service.in
.svnignore:
echo $(PROG).o $(PROG) xaal-$(PROG).service | tr ' ' '\012' > $@
svnignore: .svnignore
svn propset svn:ignore -F $< .
install: xaal-$(PROG).service $(PROG)
sudo systemctl --system --now enable $(PWD)/$<
xaal-%.service: %.service.in
envsubst < $< > $@
uninstall:
-if systemctl --system --quiet is-enabled xaal-$(PROG) &>/dev/null ; then \
sudo systemctl --system --now disable xaal-$(PROG) ; \
fi
status:
-sudo systemctl --system --no-pager -l status xaal-$(PROG)
# Unicast-Multicast Reflector
This is not an xAAL component.
This is a workaround for defective network infrastructures that does not
properly handle multicast traffic. E.g. poor quality WiFi AP or PowerLine
Adapters.
See: https://tools.ietf.org/id/draft-ietf-mboned-ieee802-mcast-problems-09.txt
This daemon forwards message from a multicast channel (e.g. the xAAL bus)
towards connected UDP unicast clients, and vice-versa.
This is the same approach as solutions developed in the middle of 90's for the
MBone project.
https://www.researchgate.net/publication/2855653_Multicast-Unicast_Reflector
Note however that our reflector does not intent to manage RTP RTSP RTCP
protocols. It works at the UDP layer.
## Usage
Do not use it! ...if possible...
Well, if you are facing a poor quality WiFi AP which lost many multicast
packets, you can use it as follow.
First, put as many as possible xAAL devices on the wired side of your AP.
On plain old Ethernet wires, multicast works well.
Put also the reflector on the wired side. All those component can
communicate via the xAAL multicast bus, as usual.
Then, for xAAL devices that have to go on the wireless side of your AP,
select the unicast IP of the reflector instead of the multicast IP of the
xAAL bus. This increase delays and bandwidth consumption, but this works.
## Conclusion
This workaround works, but should be avoided as much as possible.
As an alternative, select a better quality WiFi AP, or use Ethernet.
/* Unicast-Multicast Reflector
* (c) 2019 Christophe Lohr <christophe.lohr@imt-atlantique.fr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>
#include <sys/types.h>
#include <netinet/ip.h>
#include <netdb.h>
#include <errno.h>
#include <sys/queue.h>
#include <time.h>
#include <linux/errqueue.h>
#include <linux/icmpv6.h>
#define BUFSIZE 65527
#define MAXAGE 3600
void print_addr(struct sockaddr_storage *addr, socklen_t len) {
char host[NI_MAXHOST], service[NI_MAXSERV];
int s = getnameinfo((struct sockaddr *)addr, len, host, NI_MAXHOST, service, NI_MAXSERV, NI_NUMERICHOST|NI_NUMERICSERV);
if (s == 0)
printf("[%s]:%s", host, service);
else
fprintf(stderr, "getnameinfo: %s\n", gai_strerror(s));
}
int join_multicast(const char *addr, const char *port, int hops, struct sockaddr_storage *m_addr, socklen_t *m_len) {
int sfd, s;
int one = 1;
int mcast_loop = 1;
struct ip_mreqn mreqn;
struct ipv6_mreq mreq6;
struct addrinfo hints;
struct addrinfo *result, *rp;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
s = getaddrinfo(addr, port, &hints, &result);
if (s != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
return -1;
}
for (rp = result; rp != NULL; rp = rp->ai_next) {
sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (sfd == -1)
continue;
if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) == -1) {
fprintf(stderr, "SO_REUSEADDR: %s\n", strerror(errno));
close(sfd);
continue;
}
if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0)
break;
fprintf(stderr, "Trying port %s: %s\n", port, strerror(errno));
close(sfd);
}
if (rp == NULL) {
fprintf(stderr, "Could not bind\n");
return -1;
}
switch (rp->ai_family) {
case AF_INET:
memcpy(&mreqn.imr_multiaddr.s_addr, &(((struct sockaddr_in*)(rp->ai_addr))->sin_addr), sizeof(struct in_addr));
mreqn.imr_address.s_addr = INADDR_ANY;
mreqn.imr_ifindex = 0;
if ( setsockopt(sfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreqn, sizeof(mreqn)) == -1) {
fprintf(stderr, "Could not join the multicast group: %s\n", strerror(errno));
return -1;
}
if ( setsockopt(sfd, IPPROTO_IP, IP_MULTICAST_TTL, &hops, sizeof(hops)) == -1) {
fprintf(stderr, "Could not set TTL to %d: %s\n", hops, strerror(errno));
return -1;
}
if ( setsockopt(sfd, IPPROTO_IP, IP_MULTICAST_LOOP, &mcast_loop, sizeof(mcast_loop)) == -1) {
fprintf(stderr, "Could not %s multicast loop: %s\n", mcast_loop?"enable":"disable", strerror(errno));
return -1;
}
break;
case AF_INET6:
memcpy(&mreq6.ipv6mr_multiaddr, &(((struct sockaddr_in6*)(rp->ai_addr))->sin6_addr), sizeof(struct in6_addr));
mreq6.ipv6mr_interface = 0;
if ( setsockopt(sfd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq6, sizeof(mreq6)) == -1) {
fprintf(stderr, "Could not join the multicast group: %s\n", strerror(errno));
return -1;
}
if ( setsockopt(sfd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hops, sizeof(hops)) == -1) {
fprintf(stderr, "Could not set Hops to %d: %s\n", hops, strerror(errno));
return -1;
}
if ( setsockopt(sfd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &mcast_loop, sizeof(mcast_loop)) == -1) {
fprintf(stderr, "Could not %s multicast loop: %s\n", mcast_loop?"enable":"disable", strerror(errno));
return -1;
}
break;
default:
fprintf(stderr, "Unknown protocol %d\n", rp->ai_family);
return -1;
}
memcpy(m_addr, rp->ai_addr, rp->ai_addrlen);
*m_len = rp->ai_addrlen;
freeaddrinfo(result);
return sfd;
}
int udp_listener(const char *port) {
int sfd, s;
struct addrinfo hints;
struct addrinfo *result, *rp;
int one = 1;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET6;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_flags = AI_PASSIVE;
hints.ai_flags |= AI_V4MAPPED|AI_ALL;
s = getaddrinfo(NULL, port, &hints, &result);
if (s != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
return -1;
}
for (rp = result; rp != NULL; rp = rp->ai_next) {
sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (sfd == -1)
continue;
if (setsockopt(sfd, IPPROTO_IPV6, IPV6_RECVERR, &one, sizeof(one)) == -1) {
fprintf(stderr, "IPV6_RECVERR: %s\n", strerror(errno));
close(sfd);
continue;
}
if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0)
break;
fprintf(stderr, "Trying port %s: %s\n", port, strerror(errno));
close(sfd);
}
if (rp == NULL) {
fprintf(stderr, "Could not bind\n");
return -1;
}
freeaddrinfo(result);
return sfd;
}
typedef LIST_HEAD(clienthead, cliententry) clients_t;
typedef struct cliententry {
struct sockaddr_storage addr;
socklen_t len;
time_t last;
LIST_ENTRY(cliententry) entries;
} client_t;
client_t *select_client(clients_t *clients, struct sockaddr_storage *addr, socklen_t len) {
client_t *client;
LIST_FOREACH(client, clients, entries)
if ( (client->len == len) && (memcmp(&client->addr,addr,len)==0) )
return client;
return NULL;
}
void add_client(clients_t *clients, struct sockaddr_storage *addr, socklen_t len) {
printf("New client "); print_addr(addr, len); printf("\n");
client_t *client = (client_t *) malloc( sizeof(client_t) );
memcpy(&(client->addr), addr, len);
client->len = len;
client->last = time(NULL);
LIST_INSERT_HEAD(clients, client, entries);
}
void del_client(clients_t *clients, struct sockaddr_storage *addr, socklen_t len) {
printf("Del client "); print_addr(addr, len); printf("\n");
client_t *client = select_client(clients, addr, len);
if (client) {
LIST_REMOVE(client, entries);
free(client);
}
}
void old_clients(clients_t *clients) {
client_t *client;
time_t now = time(NULL);
LIST_FOREACH(client, clients, entries)
if (now-client->last > MAXAGE) {
LIST_REMOVE(client, entries);
free(client);
}
}
void check_reflector(int reflector, clients_t *clients) {
char buf[1024];
char ctr[1024];
struct iovec iov[1];
iov[0].iov_base=buf;
iov[0].iov_len=sizeof(buf);
struct sockaddr_storage addr;
struct msghdr message;
message.msg_name=&addr;
message.msg_namelen=sizeof(addr);
message.msg_iov=iov;
message.msg_iovlen=1;
message.msg_control=ctr;
message.msg_controllen=sizeof(ctr);
ssize_t s;
s = recvmsg(reflector, &message, MSG_ERRQUEUE);
if (s == -1) {
perror("recvmsg(MSG_ERRQUEUE)");
return;
}
if (message.msg_flags != MSG_ERRQUEUE)
return;
struct cmsghdr *cmsg;
struct sock_extended_err *sock_err;
for (cmsg = CMSG_FIRSTHDR(&message);cmsg; cmsg = CMSG_NXTHDR(&message, cmsg)) {
if ( (cmsg->cmsg_level == IPPROTO_IPV6) && (cmsg->cmsg_type == IPV6_RECVERR) ) {
sock_err = (struct sock_extended_err*)CMSG_DATA(cmsg);
if (sock_err && (sock_err->ee_origin == SO_EE_ORIGIN_ICMP6) && (sock_err->ee_type == ICMPV6_DEST_UNREACH) )
del_client(clients, &addr, message.msg_iovlen);
}
}
}
void multicast2reflector(int multicast, int reflector, clients_t *clients) {
char buf[BUFSIZE];
ssize_t r, s;
client_t *client;
r = recvfrom(multicast, buf, BUFSIZE, 0, NULL, NULL);
if (r == -1) {
perror("recvfrom(multicast)");
return;
}
LIST_FOREACH(client, clients, entries) {
s = sendto(reflector, buf, r, 0, (struct sockaddr *) &(client->addr), client->len);
if (s == -1)
perror("sendto()");
if (s != r)
fprintf(stderr, "sendto(reflector): send %lu bytes over %lu\n", s, r);
}
}
void reflector2multicast(int reflector, int multicast, struct sockaddr_storage *m_addr, socklen_t m_len, clients_t *clients) {
char buf[BUFSIZE];
ssize_t r, s;
struct sockaddr_storage addr;
socklen_t len;
client_t *client, *sender;
len = sizeof(addr);
r = recvfrom(reflector, buf, BUFSIZE, 0, (struct sockaddr *)&addr, &len);
if (r == -1) {
perror("recvfrom(unicast)");
return;
}
s = sendto(multicast, buf, r, 0, (struct sockaddr *)m_addr, m_len);
if (s == -1)
perror("sendto(multicast)");
if (s != r)
fprintf(stderr, "sendto(multicast): send %lu bytes over %lu\n", s, r);
sender = select_client(clients, &addr, len);
LIST_FOREACH(client, clients, entries)
if (client != sender) {
s = sendto(reflector, buf, r, 0, (struct sockaddr *) &(client->addr), client->len);
if (s == -1)
perror("sendto()");
if (s != r)
fprintf(stderr, "sendto(client): send %lu bytes over %lu\n", s, r);
}
if (!sender)
add_client(clients, &addr, len);
else
sender->last = time(NULL);
}
int main(int argc, char *argv[]) {
int opt, multicast, reflector;
char *addr=NULL, *port=NULL, *refl=NULL;
int hops = -1;
struct sockaddr_storage m_addr;
socklen_t m_len;
bool arg_error = false;
fd_set rfds, rfds_, efds, efds_;
int fd_max;
clients_t clients;
while ((opt = getopt(argc, argv, "a:p:h:r:")) != -1) {
switch (opt) {
case 'a':
addr = optarg;
break;
case 'p':
port = optarg;
break;
case 'h':
hops = atoi(optarg);
break;
case 'r':
refl = optarg;
break;
default: /* '?' */
arg_error = true;
}
}
if (optind < argc) {
fprintf(stderr, "Unknown argument %s\n", argv[optind]);
arg_error = true;
}
if (!addr || !port || arg_error || !refl) {
fprintf(stderr, "Usage: %s -a <addr> -p <port> [-h <hops>] -r <port>\n"
" -a <addr> Multicast IPv4/IPv6 address to join\n"
" -p <port> UDP port for the multicast bus\n"
" -h <hops> IP packets hops count of the multicast bus\n"
" -r <port> UDP port for receiving unicast\n", argv[0]);
exit(EXIT_FAILURE);
}
multicast = join_multicast(addr, port, hops, &m_addr, &m_len);
if (multicast <= 0)
exit(EXIT_FAILURE);
reflector = udp_listener(refl);
if (reflector <= 0)
exit(EXIT_FAILURE);
LIST_INIT(&clients);
FD_ZERO(&rfds);
FD_SET(multicast, &rfds);
fd_max = multicast;
FD_SET(reflector, &rfds);
fd_max = (fd_max > reflector)? fd_max : reflector;
fd_max++;
FD_ZERO(&efds);
FD_SET(reflector, &efds);
for (;;) {
rfds_ = rfds;
efds_ = efds;
if ( (select(fd_max, &rfds_, NULL, &efds_, NULL) == -1) && (errno != EINTR) )
perror("select()");
if (FD_ISSET(multicast, &rfds_))
multicast2reflector(multicast, reflector, &clients);
if (FD_ISSET(reflector, &rfds_))
reflector2multicast(reflector, multicast, &m_addr, m_len, &clients);
if (FD_ISSET(reflector, &efds_))
check_reflector(reflector, &clients);
old_clients(&clients);
}
exit(EXIT_SUCCESS);
}
[Unit]
Description=Unicast-Multicast Reflector
[Service]
WorkingDirectory=$PWD
User=$USER
ExecStart=$PWD/reflector -a 224.0.29.200 -p 1234 -r 1235
[Install]
WantedBy=multi-user.target
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment