From 41e8560ea7c09533d03f523380c1cb5c62d87261 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Fri, 13 Mar 2009 14:00:59 +0100 Subject: sync-mode: add unicast UDP support to propagate state-changes This patch adds support for unicast UDP to the channel infrastructure. With this patch, you can select UDP unicast to propagate state-changes instead of multicast. Signed-off-by: Pablo Neira Ayuso --- src/Makefile.am | 4 +- src/channel.c | 2 + src/channel_udp.c | 123 ++++++++++++++++++++++++ src/read_config_lex.l | 10 +- src/read_config_yy.y | 170 +++++++++++++++++++++++++++++--- src/udp.c | 261 ++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 551 insertions(+), 19 deletions(-) create mode 100644 src/channel_udp.c create mode 100644 src/udp.c (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index 54cfda4..667040c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -11,7 +11,7 @@ conntrack_LDADD = ../extensions/libct_proto_tcp.la ../extensions/libct_proto_udp conntrack_LDFLAGS = $(all_libraries) @LIBNETFILTER_CONNTRACK_LIBS@ conntrackd_SOURCES = alarm.c main.c run.c hash.c queue.c rbtree.c \ - local.c log.c mcast.c netlink.c vector.c \ + local.c log.c mcast.c udp.c netlink.c vector.c \ filter.c fds.c event.c \ cache.c cache_iterators.c \ cache_timer.c cache_wt.c \ @@ -19,7 +19,7 @@ conntrackd_SOURCES = alarm.c main.c run.c hash.c queue.c rbtree.c \ traffic_stats.c stats-mode.c \ network.c cidr.c \ build.c parse.c \ - channel.c multichannel.c channel_mcast.c \ + channel.c multichannel.c channel_mcast.c channel_udp.c \ read_config_yy.y read_config_lex.l # yacc and lex generate dirty code diff --git a/src/channel.c b/src/channel.c index 733fd03..255026a 100644 --- a/src/channel.c +++ b/src/channel.c @@ -19,10 +19,12 @@ static struct channel_ops *ops[CHANNEL_MAX]; extern struct channel_ops channel_mcast; +extern struct channel_ops channel_udp; void channel_init(void) { ops[CHANNEL_MCAST] = &channel_mcast; + ops[CHANNEL_UDP] = &channel_udp; } #define HEADERSIZ 28 /* IP header (20 bytes) + UDP header 8 (bytes) */ diff --git a/src/channel_udp.c b/src/channel_udp.c new file mode 100644 index 0000000..1c15b47 --- /dev/null +++ b/src/channel_udp.c @@ -0,0 +1,123 @@ +/* + * (C) 2009 by Pablo Neira Ayuso + * + * 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. + */ + +#include +#include + +#include "channel.h" +#include "udp.h" + +static void +*channel_udp_open(void *conf) +{ + struct udp_channel *m; + struct udp_conf *c = conf; + + m = calloc(sizeof(struct udp_channel), 1); + if (m == NULL) + return NULL; + + m->client = udp_client_create(c); + if (m->client == NULL) { + free(m); + return NULL; + } + + m->server = udp_server_create(c); + if (m->server == NULL) { + udp_client_destroy(m->client); + free(m); + return NULL; + } + return m; +} + +static int +channel_udp_send(void *channel, const void *data, int len) +{ + struct udp_channel *m = channel; + return udp_send(m->client, data, len); +} + +static int +channel_udp_recv(void *channel, char *buf, int size) +{ + struct udp_channel *m = channel; + return udp_recv(m->server, buf, size); +} + +static void +channel_udp_close(void *channel) +{ + struct udp_channel *m = channel; + udp_client_destroy(m->client); + udp_server_destroy(m->server); + free(m); +} + +static int +channel_udp_get_fd(void *channel) +{ + struct udp_channel *m = channel; + return udp_get_fd(m->server); +} + +static void +channel_udp_stats(struct channel *c, int fd) +{ + struct udp_channel *m = c->data; + char ifname[IFNAMSIZ], buf[512]; + int size; + + if_indextoname(c->channel_ifindex, ifname); + size = udp_snprintf_stats(buf, sizeof(buf), ifname, + &m->client->stats, &m->server->stats); + send(fd, buf, size, 0); +} + +static void +channel_udp_stats_extended(struct channel *c, int active, + struct nlif_handle *h, int fd) +{ + struct udp_channel *m = c->data; + char ifname[IFNAMSIZ], buf[512]; + const char *status; + unsigned int flags; + int size; + + if_indextoname(c->channel_ifindex, ifname); + nlif_get_ifflags(h, c->channel_ifindex, &flags); + /* + * IFF_UP shows administrative status + * IFF_RUNNING shows carrier status + */ + if (flags & IFF_UP) { + if (!(flags & IFF_RUNNING)) + status = "NO-CARRIER"; + else + status = "RUNNING"; + } else { + status = "DOWN"; + } + size = udp_snprintf_stats2(buf, sizeof(buf), + ifname, status, active, + &m->client->stats, + &m->server->stats); + send(fd, buf, size, 0); +} + +struct channel_ops channel_udp = { + .open = channel_udp_open, + .close = channel_udp_close, + .send = channel_udp_send, + .recv = channel_udp_recv, + .get_fd = channel_udp_get_fd, + .stats = channel_udp_stats, + .stats_extended = channel_udp_stats_extended, +}; diff --git a/src/read_config_lex.l b/src/read_config_lex.l index d75e299..44ccf0b 100644 --- a/src/read_config_lex.l +++ b/src/read_config_lex.l @@ -58,11 +58,14 @@ notrack [N|n][O|o][T|t][R|r][A|a][C|c][K|k] "UNIX" { return T_UNIX; } "IPv4_address" { return T_IPV4_ADDR; } "IPv6_address" { return T_IPV6_ADDR; } +"IPv4_Destination_Address" { return T_IPV4_DEST_ADDR; } +"IPv6_Destination_Address" { return T_IPV6_DEST_ADDR; } "IPv4_interface" { return T_IPV4_IFACE; } "IPv6_interface" { return T_IPV6_IFACE; } "Interface" { return T_IFACE; } "Port" { return T_PORT; } "Multicast" { return T_MULTICAST; } +"UDP" { return T_UDP; } "HashSize" { return T_HASHSIZE; } "RefreshTime" { return T_REFRESH; } "CacheTimeout" { return T_EXPIRE; } @@ -75,6 +78,7 @@ notrack [N|n][O|o][T|t][R|r][A|a][C|c][K|k] "StripNAT" { return T_STRIP_NAT; } "Backlog" { return T_BACKLOG; } "Group" { return T_GROUP; } +"Port" { return T_PORT; } "LogFile" { return T_LOG; } "Syslog" { return T_SYSLOG; } "LockFile" { return T_LOCK; } @@ -109,8 +113,10 @@ notrack [N|n][O|o][T|t][R|r][A|a][C|c][K|k] "LISTEN" { return T_LISTEN; } "LogFileBufferSize" { return T_STAT_BUFFER_SIZE; } "DestroyTimeout" { return T_DESTROY_TIMEOUT; } -"McastSndSocketBuffer" { return T_MCAST_SNDBUFF; } -"McastRcvSocketBuffer" { return T_MCAST_RCVBUFF; } +"McastSndSocketBuffer" { return T_SNDBUFF; /* deprecated */ } +"McastRcvSocketBuffer" { return T_RCVBUFF; /* deprecated */ } +"SndSocketBuffer" { return T_SNDBUFF; } +"RcvSocketBuffer" { return T_RCVBUFF; } "Filter" { return T_FILTER; } "Protocol" { return T_PROTOCOL; } "Address" { return T_ADDRESS; } diff --git a/src/read_config_yy.y b/src/read_config_yy.y index b3a2640..cfcd574 100644 --- a/src/read_config_yy.y +++ b/src/read_config_yy.y @@ -38,7 +38,7 @@ struct ct_conf conf; static void __kernel_filter_start(void); static void __kernel_filter_add_state(int value); -static void __max_mcast_dedicated_links_reached(void); +static void __max_dedicated_links_reached(void); %} %union { @@ -58,10 +58,10 @@ static void __max_mcast_dedicated_links_reached(void); %token T_ESTABLISHED T_SYN_SENT T_SYN_RECV T_FIN_WAIT %token T_CLOSE_WAIT T_LAST_ACK T_TIME_WAIT T_CLOSE T_LISTEN %token T_SYSLOG T_WRITE_THROUGH T_STAT_BUFFER_SIZE T_DESTROY_TIMEOUT -%token T_MCAST_RCVBUFF T_MCAST_SNDBUFF T_NOTRACK T_POLL_SECS +%token T_RCVBUFF T_SNDBUFF T_NOTRACK T_POLL_SECS %token T_FILTER T_ADDRESS T_PROTOCOL T_STATE T_ACCEPT T_IGNORE %token T_FROM T_USERSPACE T_KERNELSPACE T_EVENT_ITER_LIMIT T_DEFAULT -%token T_NETLINK_OVERRUN_RESYNC T_NICE +%token T_NETLINK_OVERRUN_RESYNC T_NICE T_IPV4_DEST_ADDR T_IPV6_DEST_ADDR %token T_IP T_PATH_VAL %token T_NUMBER @@ -256,6 +256,13 @@ ignore_traffic_option : T_IPV6_ADDR T_IP multicast_line : T_MULTICAST '{' multicast_options '}' { + if (conf.channel_type_global != CHANNEL_NONE && + conf.channel_type_global != CHANNEL_MCAST) { + fprintf(stderr, "ERROR: Cannot use `Multicast' with other " + "dedicated link protocols!\n"); + exit(EXIT_FAILURE); + } + conf.channel_type_global = CHANNEL_MCAST; conf.channel[conf.channel_num].channel_type = CHANNEL_MCAST; conf.channel[conf.channel_num].channel_flags = CHANNEL_F_BUFFERED; conf.channel_num++; @@ -263,6 +270,13 @@ multicast_line : T_MULTICAST '{' multicast_options '}' multicast_line : T_MULTICAST T_DEFAULT '{' multicast_options '}' { + if (conf.channel_type_global != CHANNEL_NONE && + conf.channel_type_global != CHANNEL_MCAST) { + fprintf(stderr, "ERROR: Cannot use `Multicast' with other " + "dedicated link protocols!\n"); + exit(EXIT_FAILURE); + } + conf.channel_type_global = CHANNEL_MCAST; conf.channel[conf.channel_num].channel_type = CHANNEL_MCAST; conf.channel[conf.channel_num].channel_flags = CHANNEL_F_DEFAULT | CHANNEL_F_BUFFERED; @@ -275,7 +289,7 @@ multicast_options : multicast_option : T_IPV4_ADDR T_IP { - __max_mcast_dedicated_links_reached(); + __max_dedicated_links_reached(); if (!inet_aton($2, &conf.channel[conf.channel_num].u.mcast.in)) { fprintf(stderr, "%s is not a valid IPv4 address\n", $2); @@ -294,7 +308,7 @@ multicast_option : T_IPV4_ADDR T_IP multicast_option : T_IPV6_ADDR T_IP { - __max_mcast_dedicated_links_reached(); + __max_dedicated_links_reached(); #ifdef HAVE_INET_PTON_IPV6 if (inet_pton(AF_INET6, $2, @@ -333,7 +347,7 @@ multicast_option : T_IPV6_ADDR T_IP multicast_option : T_IPV4_IFACE T_IP { - __max_mcast_dedicated_links_reached(); + __max_dedicated_links_reached(); if (!inet_aton($2, &conf.channel[conf.channel_num].u.mcast.ifa)) { fprintf(stderr, "%s is not a valid IPv4 address\n", $2); @@ -359,7 +373,7 @@ multicast_option : T_IFACE T_STRING { unsigned int idx; - __max_mcast_dedicated_links_reached(); + __max_dedicated_links_reached(); strncpy(conf.channel[conf.channel_num].channel_ifname, $2, IFNAMSIZ); strncpy(conf.channel[conf.channel_num].u.mcast.iface, $2, IFNAMSIZ); @@ -385,34 +399,159 @@ multicast_option : T_BACKLOG T_NUMBER multicast_option : T_GROUP T_NUMBER { - __max_mcast_dedicated_links_reached(); + __max_dedicated_links_reached(); conf.channel[conf.channel_num].u.mcast.port = $2; }; -multicast_option: T_MCAST_SNDBUFF T_NUMBER +multicast_option: T_SNDBUFF T_NUMBER { - __max_mcast_dedicated_links_reached(); + __max_dedicated_links_reached(); conf.channel[conf.channel_num].u.mcast.sndbuf = $2; }; -multicast_option: T_MCAST_RCVBUFF T_NUMBER +multicast_option: T_RCVBUFF T_NUMBER { - __max_mcast_dedicated_links_reached(); + __max_dedicated_links_reached(); conf.channel[conf.channel_num].u.mcast.rcvbuf = $2; }; multicast_option: T_CHECKSUM T_ON { - __max_mcast_dedicated_links_reached(); + __max_dedicated_links_reached(); conf.channel[conf.channel_num].u.mcast.checksum = 0; }; multicast_option: T_CHECKSUM T_OFF { - __max_mcast_dedicated_links_reached(); + __max_dedicated_links_reached(); conf.channel[conf.channel_num].u.mcast.checksum = 1; }; +udp_line : T_UDP '{' udp_options '}' +{ + if (conf.channel_type_global != CHANNEL_NONE && + conf.channel_type_global != CHANNEL_UDP) { + fprintf(stderr, "ERROR: Cannot use `UDP' with other " + "dedicated link protocols!\n"); + exit(EXIT_FAILURE); + } + conf.channel_type_global = CHANNEL_UDP; + conf.channel[conf.channel_num].channel_type = CHANNEL_UDP; + conf.channel[conf.channel_num].channel_flags = CHANNEL_F_BUFFERED; + conf.channel_num++; +}; + +udp_line : T_UDP T_DEFAULT '{' udp_options '}' +{ + if (conf.channel_type_global != CHANNEL_NONE && + conf.channel_type_global != CHANNEL_UDP) { + fprintf(stderr, "ERROR: Cannot use `UDP' with other " + "dedicated link protocols!\n"); + exit(EXIT_FAILURE); + } + conf.channel_type_global = CHANNEL_UDP; + conf.channel[conf.channel_num].channel_type = CHANNEL_UDP; + conf.channel[conf.channel_num].channel_flags = CHANNEL_F_DEFAULT | + CHANNEL_F_BUFFERED; + conf.channel_default = conf.channel_num; + conf.channel_num++; +}; + +udp_options : + | udp_options udp_option; + +udp_option : T_IPV4_ADDR T_IP +{ + __max_dedicated_links_reached(); + + if (!inet_aton($2, &conf.channel[conf.channel_num].u.udp.server)) { + fprintf(stderr, "%s is not a valid IPv4 address\n", $2); + break; + } + conf.channel[conf.channel_num].u.udp.ipproto = AF_INET; +}; + +udp_option : T_IPV6_ADDR T_IP +{ + __max_dedicated_links_reached(); + +#ifdef HAVE_INET_PTON_IPV6 + if (inet_pton(AF_INET6, $2, + &conf.channel[conf.channel_num].u.udp.server) <= 0) { + fprintf(stderr, "%s is not a valid IPv6 address\n", $2); + break; + } +#else + fprintf(stderr, "Cannot find inet_pton(), IPv6 unsupported!"); + break; +#endif + conf.channel[conf.channel_num].u.udp.ipproto = AF_INET6; +}; + +udp_option : T_IPV4_DEST_ADDR T_IP +{ + __max_dedicated_links_reached(); + + if (!inet_aton($2, &conf.channel[conf.channel_num].u.udp.client)) { + fprintf(stderr, "%s is not a valid IPv4 address\n", $2); + break; + } + conf.channel[conf.channel_num].u.udp.ipproto = AF_INET; +}; + +udp_option : T_IPV6_DEST_ADDR T_IP +{ + __max_dedicated_links_reached(); + +#ifdef HAVE_INET_PTON_IPV6 + if (inet_pton(AF_INET6, $2, + &conf.channel[conf.channel_num].u.udp.client) <= 0) { + fprintf(stderr, "%s is not a valid IPv6 address\n", $2); + break; + } +#else + fprintf(stderr, "Cannot find inet_pton(), IPv6 unsupported!"); + break; +#endif + conf.channel[conf.channel_num].u.udp.ipproto = AF_INET6; +}; + +udp_option : T_IFACE T_STRING +{ + __max_dedicated_links_reached(); + strncpy(conf.channel[conf.channel_num].channel_ifname, $2, IFNAMSIZ); +}; + +udp_option : T_PORT T_NUMBER +{ + __max_dedicated_links_reached(); + conf.channel[conf.channel_num].u.udp.port = $2; +}; + +udp_option: T_SNDBUFF T_NUMBER +{ + __max_dedicated_links_reached(); + conf.channel[conf.channel_num].u.udp.sndbuf = $2; +}; + +udp_option: T_RCVBUFF T_NUMBER +{ + __max_dedicated_links_reached(); + conf.channel[conf.channel_num].u.udp.rcvbuf = $2; +}; + +udp_option: T_CHECKSUM T_ON +{ + __max_dedicated_links_reached(); + conf.channel[conf.channel_num].u.udp.checksum = 0; +}; + +udp_option: T_CHECKSUM T_OFF +{ + __max_dedicated_links_reached(); + conf.channel[conf.channel_num].u.udp.checksum = 1; +}; + hashsize : T_HASHSIZE T_NUMBER { conf.hashsize = $2; @@ -493,6 +632,7 @@ sync_line: refreshtime | purge | checksum | multicast_line + | udp_line | relax_transitions | delay_destroy_msgs | sync_mode_alarm @@ -1133,7 +1273,7 @@ static void __kernel_filter_add_state(int value) &filter_proto); } -static void __max_mcast_dedicated_links_reached(void) +static void __max_dedicated_links_reached(void) { if (conf.channel_num >= MULTICHANNEL_MAX) { fprintf(stderr, "ERROR: too many dedicated links in " diff --git a/src/udp.c b/src/udp.c new file mode 100644 index 0000000..bad8db8 --- /dev/null +++ b/src/udp.c @@ -0,0 +1,261 @@ +/* + * (C) 2009 by Pablo Neira Ayuso + * + * 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. + */ + +#include "udp.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct udp_sock *udp_server_create(struct udp_conf *conf) +{ + int yes = 1; + struct udp_sock *m; + socklen_t socklen = sizeof(int); + + m = calloc(sizeof(struct udp_sock), 1); + if (m == NULL) + return NULL; + + switch(conf->ipproto) { + case AF_INET: + m->addr.ipv4.sin_family = AF_INET; + m->addr.ipv4.sin_port = htons(conf->port); + m->addr.ipv4.sin_addr.s_addr = conf->server.inet_addr.s_addr; + m->sockaddr_len = sizeof(struct sockaddr_in); + break; + + case AF_INET6: + m->addr.ipv6.sin6_family = AF_INET6; + m->addr.ipv6.sin6_port = htons(conf->port); + m->addr.ipv6.sin6_addr = conf->server.inet_addr6; + m->sockaddr_len = sizeof(struct sockaddr_in6); + break; + } + + m->fd = socket(conf->ipproto, SOCK_DGRAM, 0); + if (m->fd == -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; + } + + return m; +} + +void udp_server_destroy(struct udp_sock *m) +{ + close(m->fd); + free(m); +} + +struct udp_sock *udp_client_create(struct udp_conf *conf) +{ + int ret = 0; + struct udp_sock *m; + socklen_t socklen = sizeof(int); + + m = calloc(sizeof(struct udp_sock), 1); + if (m == NULL) + return NULL; + + m->fd = socket(conf->ipproto, SOCK_DGRAM, 0); + if (m->fd == -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: + m->addr.ipv4.sin_family = AF_INET; + m->addr.ipv4.sin_port = htons(conf->port); + m->addr.ipv4.sin_addr = conf->client.inet_addr; + m->sockaddr_len = sizeof(struct sockaddr_in); + break; + case AF_INET6: + m->addr.ipv6.sin6_family = AF_INET6; + m->addr.ipv6.sin6_port = htons(conf->port); + memcpy(&m->addr.ipv6.sin6_addr, &conf->client.inet_addr6, + sizeof(struct in6_addr)); + m->sockaddr_len = sizeof(struct sockaddr_in6); + break; + default: + ret = -1; + break; + } + + if (ret == -1) { + close(m->fd); + free(m); + m = NULL; + } + + return m; +} + +void udp_client_destroy(struct udp_sock *m) +{ + close(m->fd); + free(m); +} + +ssize_t udp_send(struct udp_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 udp_recv(struct udp_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) { + m->stats.error++; + return ret; + } + + m->stats.bytes += ret; + m->stats.messages++; + + return ret; +} + +int udp_get_fd(struct udp_sock *m) +{ + return m->fd; +} + +int +udp_snprintf_stats(char *buf, size_t buflen, char *ifname, + struct udp_stats *s, struct udp_stats *r) +{ + size_t size; + + size = snprintf(buf, buflen, "UDP 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 +udp_snprintf_stats2(char *buf, size_t buflen, const char *ifname, + const char *status, int active, + struct udp_stats *s, struct udp_stats *r) +{ + size_t size; + + size = snprintf(buf, buflen, + "UDP 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; +} -- cgit v1.2.3