blob: 4107d5d94891497dab65389cf8312f94dec7883f [file] [log] [blame]
/*
* (C) 2006-2009 by Pablo Neira Ayuso <pablo@netfilter.org>
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* Description: multicast socket library
*/
#include "mcast.h"
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <errno.h>
#include <limits.h>
#include <libnfnetlink/libnfnetlink.h>
struct mcast_sock *mcast_server_create(struct mcast_conf *conf)
{
int yes = 1;
union {
struct ip_mreq ipv4;
struct ipv6_mreq ipv6;
} mreq;
struct mcast_sock *m;
socklen_t socklen = sizeof(int);
m = (struct mcast_sock *) malloc(sizeof(struct mcast_sock));
if (!m)
return NULL;
memset(m, 0, sizeof(struct mcast_sock));
switch(conf->ipproto) {
case AF_INET:
mreq.ipv4.imr_multiaddr.s_addr = conf->in.inet_addr.s_addr;
mreq.ipv4.imr_interface.s_addr =conf->ifa.interface_addr.s_addr;
m->addr.ipv4.sin_family = AF_INET;
m->addr.ipv4.sin_port = htons(conf->port);
m->addr.ipv4.sin_addr.s_addr = htonl(INADDR_ANY);
m->sockaddr_len = sizeof(struct sockaddr_in);
break;
case AF_INET6:
memcpy(&mreq.ipv6.ipv6mr_multiaddr, &conf->in.inet_addr6,
sizeof(uint32_t) * 4);
mreq.ipv6.ipv6mr_interface = conf->ifa.interface_index6;
m->addr.ipv6.sin6_family = AF_INET6;
m->addr.ipv6.sin6_port = htons(conf->port);
m->addr.ipv6.sin6_addr = in6addr_any;
m->sockaddr_len = sizeof(struct sockaddr_in6);
break;
}
if ((m->fd = socket(conf->ipproto, SOCK_DGRAM, 0)) == -1) {
free(m);
return NULL;
}
if (setsockopt(m->fd, SOL_SOCKET, SO_REUSEADDR, &yes,
sizeof(int)) == -1) {
close(m->fd);
free(m);
return NULL;
}
#ifndef SO_RCVBUFFORCE
#define SO_RCVBUFFORCE 33
#endif
if (conf->rcvbuf &&
setsockopt(m->fd, SOL_SOCKET, SO_RCVBUFFORCE, &conf->rcvbuf,
sizeof(int)) == -1) {
/* not supported in linux kernel < 2.6.14 */
if (errno != ENOPROTOOPT) {
close(m->fd);
free(m);
return NULL;
}
}
getsockopt(m->fd, SOL_SOCKET, SO_RCVBUF, &conf->rcvbuf, &socklen);
if (bind(m->fd, (struct sockaddr *) &m->addr, m->sockaddr_len) == -1) {
close(m->fd);
free(m);
return NULL;
}
switch(conf->ipproto) {
case AF_INET:
if (setsockopt(m->fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
&mreq.ipv4, sizeof(mreq.ipv4)) < 0) {
close(m->fd);
free(m);
return NULL;
}
break;
case AF_INET6:
if (setsockopt(m->fd, IPPROTO_IPV6, IPV6_JOIN_GROUP,
&mreq.ipv6, sizeof(mreq.ipv6)) < 0) {
close(m->fd);
free(m);
return NULL;
}
break;
}
return m;
}
void mcast_server_destroy(struct mcast_sock *m)
{
close(m->fd);
free(m);
}
static int
__mcast_client_create_ipv4(struct mcast_sock *m, struct mcast_conf *conf)
{
int no = 0;
m->addr.ipv4.sin_family = AF_INET;
m->addr.ipv4.sin_port = htons(conf->port);
m->addr.ipv4.sin_addr = conf->in.inet_addr;
m->sockaddr_len = sizeof(struct sockaddr_in);
if (setsockopt(m->fd, IPPROTO_IP, IP_MULTICAST_LOOP, &no,
sizeof(int)) < 0) {
close(m->fd);
return -1;
}
if (setsockopt(m->fd, IPPROTO_IP, IP_MULTICAST_IF,
&conf->ifa.interface_addr,
sizeof(struct in_addr)) == -1) {
close(m->fd);
return -1;
}
return 0;
}
static int
__mcast_client_create_ipv6(struct mcast_sock *m, struct mcast_conf *conf)
{
int no = 0;
m->addr.ipv6.sin6_family = AF_INET6;
m->addr.ipv6.sin6_port = htons(conf->port);
memcpy(&m->addr.ipv6.sin6_addr,
&conf->in.inet_addr6,
sizeof(struct in6_addr));
m->sockaddr_len = sizeof(struct sockaddr_in6);
if (setsockopt(m->fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &no,
sizeof(int)) < 0) {
close(m->fd);
return -1;
}
if (setsockopt(m->fd, IPPROTO_IPV6, IPV6_MULTICAST_IF,
&conf->ifa.interface_index6,
sizeof(unsigned int)) == -1) {
close(m->fd);
return -1;
}
return 0;
}
struct mcast_sock *mcast_client_create(struct mcast_conf *conf)
{
int ret;
struct mcast_sock *m;
socklen_t socklen = sizeof(int);
m = (struct mcast_sock *) malloc(sizeof(struct mcast_sock));
if (!m)
return NULL;
memset(m, 0, sizeof(struct mcast_sock));
if ((m->fd = socket(conf->ipproto, SOCK_DGRAM, 0)) == -1) {
free(m);
return NULL;
}
if (setsockopt(m->fd, SOL_SOCKET, SO_NO_CHECK, &conf->checksum,
sizeof(int)) == -1) {
close(m->fd);
free(m);
return NULL;
}
#ifndef SO_SNDBUFFORCE
#define SO_SNDBUFFORCE 32
#endif
if (conf->sndbuf &&
setsockopt(m->fd, SOL_SOCKET, SO_SNDBUFFORCE, &conf->sndbuf,
sizeof(int)) == -1) {
/* not supported in linux kernel < 2.6.14 */
if (errno != ENOPROTOOPT) {
close(m->fd);
free(m);
return NULL;
}
}
getsockopt(m->fd, SOL_SOCKET, SO_SNDBUF, &conf->sndbuf, &socklen);
switch(conf->ipproto) {
case AF_INET:
ret = __mcast_client_create_ipv4(m, conf);
break;
case AF_INET6:
ret = __mcast_client_create_ipv6(m, conf);
break;
default:
ret = 0;
break;
}
if (ret == -1) {
close(m->fd);
free(m);
m = NULL;
}
return m;
}
void mcast_client_destroy(struct mcast_sock *m)
{
close(m->fd);
free(m);
}
ssize_t mcast_send(struct mcast_sock *m, const void *data, int size)
{
ssize_t ret;
ret = sendto(m->fd,
data,
size,
0,
(struct sockaddr *) &m->addr,
m->sockaddr_len);
if (ret == -1) {
m->stats.error++;
return ret;
}
m->stats.bytes += ret;
m->stats.messages++;
return ret;
}
ssize_t mcast_recv(struct mcast_sock *m, void *data, int size)
{
ssize_t ret;
socklen_t sin_size = sizeof(struct sockaddr_in);
ret = recvfrom(m->fd,
data,
size,
0,
(struct sockaddr *)&m->addr,
&sin_size);
if (ret == -1) {
if (errno != EAGAIN)
m->stats.error++;
return ret;
}
m->stats.bytes += ret;
m->stats.messages++;
return ret;
}
int mcast_get_fd(struct mcast_sock *m)
{
return m->fd;
}
int mcast_isset(struct mcast_sock *m, fd_set *readfds)
{
return FD_ISSET(m->fd, readfds);
}
int
mcast_snprintf_stats(char *buf, size_t buflen, char *ifname,
struct mcast_stats *s, struct mcast_stats *r)
{
size_t size;
size = snprintf(buf, buflen, "multicast traffic (active device=%s):\n"
"%20llu Bytes sent "
"%20llu Bytes recv\n"
"%20llu Pckts sent "
"%20llu Pckts recv\n"
"%20llu Error send "
"%20llu Error recv\n\n",
ifname,
(unsigned long long)s->bytes,
(unsigned long long)r->bytes,
(unsigned long long)s->messages,
(unsigned long long)r->messages,
(unsigned long long)s->error,
(unsigned long long)r->error);
return size;
}
int
mcast_snprintf_stats2(char *buf, size_t buflen, const char *ifname,
const char *status, int active,
struct mcast_stats *s, struct mcast_stats *r)
{
size_t size;
size = snprintf(buf, buflen,
"multicast traffic device=%s status=%s role=%s:\n"
"%20llu Bytes sent "
"%20llu Bytes recv\n"
"%20llu Pckts sent "
"%20llu Pckts recv\n"
"%20llu Error send "
"%20llu Error recv\n\n",
ifname, status, active ? "ACTIVE" : "BACKUP",
(unsigned long long)s->bytes,
(unsigned long long)r->bytes,
(unsigned long long)s->messages,
(unsigned long long)r->messages,
(unsigned long long)s->error,
(unsigned long long)r->error);
return size;
}