blob: a8432431fbbf64bf08aa1380c4c344f5ac0cad6d [file] [log] [blame]
/*
* libipvs: Library for manipulating IPVS through [gs]etsockopt
*
* Version: $Id: libipvs.c,v 1.7 2003/06/08 09:31:39 wensong Exp $
*
* Authors: Wensong Zhang <wensong@linuxvirtualserver.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.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include "libipvs.h"
typedef struct ipvs_servicedest_s {
struct ip_vs_service_kern svc;
struct ip_vs_dest_kern dest;
} ipvs_servicedest_t;
static int sockfd = -1;
static void* ipvs_func = NULL;
struct ip_vs_getinfo ipvs_info;
#ifdef LIBIPVS_USE_NL
#ifdef FALLBACK_LIBNL1
#define nl_sock nl_handle
#define nl_socket_alloc nl_handle_alloc
#define nl_socket_free nl_handle_destroy
#endif
static struct nl_sock *sock = NULL;
static int family, try_nl = 1;
#endif
#define CHECK_IPV4(s, ret) if (s->af && s->af != AF_INET) \
{ errno = EAFNOSUPPORT; goto out_err; } \
s->__addr_v4 = s->addr.ip; \
#define CHECK_PE(s, ret) if (s->pe_name[0]) \
{ errno = EAFNOSUPPORT; goto out_err; }
#define CHECK_COMPAT_DEST(s, ret) CHECK_IPV4(s, ret)
#define CHECK_COMPAT_SVC(s, ret) \
CHECK_IPV4(s, ret); \
CHECK_PE(s, ret);
#ifdef LIBIPVS_USE_NL
struct nl_msg *ipvs_nl_message(int cmd, int flags)
{
struct nl_msg *msg;
msg = nlmsg_alloc();
if (!msg)
return NULL;
genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, family, 0, flags,
cmd, IPVS_GENL_VERSION);
return msg;
}
static int ipvs_nl_noop_cb(struct nl_msg *msg, void *arg)
{
return NL_OK;
}
struct cb_err_data {
int err;
};
static int ipvs_nl_err_cb(struct sockaddr_nl *nla, struct nlmsgerr *nlerr,
void *arg)
{
struct cb_err_data *data = arg;
data->err = nlerr->error;
return -nl_syserr2nlerr(nlerr->error);
}
int ipvs_nl_send_message(struct nl_msg *msg, nl_recvmsg_msg_cb_t func, void *arg)
{
int err = EINVAL;
struct cb_err_data err_data = { .err = 0 };
sock = nl_socket_alloc();
if (!sock) {
nlmsg_free(msg);
return -1;
}
if (genl_connect(sock) < 0)
goto fail_genl;
family = genl_ctrl_resolve(sock, IPVS_GENL_NAME);
if (family < 0)
goto fail_genl;
/* To test connections and set the family */
if (msg == NULL) {
nl_socket_free(sock);
sock = NULL;
return 0;
}
if (nl_socket_modify_cb(sock, NL_CB_VALID, NL_CB_CUSTOM, func, arg) != 0)
goto fail_genl;
if (nl_socket_modify_err_cb(sock, NL_CB_CUSTOM, ipvs_nl_err_cb,
&err_data) != 0)
goto fail_genl;
if (nl_send_auto_complete(sock, msg) < 0)
goto fail_genl;
if (nl_recvmsgs_default(sock) < 0) {
if (err_data.err)
err = -err_data.err;
goto fail_genl;
}
nlmsg_free(msg);
nl_socket_free(sock);
return 0;
fail_genl:
nl_socket_free(sock);
sock = NULL;
nlmsg_free(msg);
errno = err;
return -1;
}
#endif
int ipvs_init(void)
{
socklen_t len;
ipvs_func = ipvs_init;
#ifdef LIBIPVS_USE_NL
try_nl = 1;
if (ipvs_nl_send_message(NULL, NULL, NULL) == 0) {
try_nl = 1;
return ipvs_getinfo();
}
try_nl = 0;
#endif
len = sizeof(ipvs_info);
if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) == -1)
return -1;
if (getsockopt(sockfd, IPPROTO_IP, IP_VS_SO_GET_INFO,
(char *)&ipvs_info, &len))
return -1;
return 0;
}
static void copy_stats_from_kern(struct ip_vs_stats64 *d,
struct ip_vs_stats_user *s)
{
d->conns = s->conns;
d->inpkts = s->inpkts;
d->outpkts = s->outpkts;
d->inbytes = s->inbytes;
d->outbytes = s->outbytes;
d->cps = s->cps;
d->inpps = s->inpps;
d->outpps = s->outpps;
d->inbps = s->inbps;
d->outbps = s->outbps;
}
#ifdef LIBIPVS_USE_NL
static int ipvs_getinfo_parse_cb(struct nl_msg *msg, void *arg)
{
struct nlmsghdr *nlh = nlmsg_hdr(msg);
struct nlattr *attrs[IPVS_INFO_ATTR_MAX + 1];
if (genlmsg_parse(nlh, 0, attrs, IPVS_INFO_ATTR_MAX, ipvs_info_policy) != 0)
return -1;
if (!(attrs[IPVS_INFO_ATTR_VERSION] &&
attrs[IPVS_INFO_ATTR_CONN_TAB_SIZE]))
return -1;
ipvs_info.version = nla_get_u32(attrs[IPVS_INFO_ATTR_VERSION]);
ipvs_info.size = nla_get_u32(attrs[IPVS_INFO_ATTR_CONN_TAB_SIZE]);
return NL_OK;
}
#endif
int ipvs_getinfo(void)
{
socklen_t len;
#ifdef LIBIPVS_USE_NL
if (try_nl) {
struct nl_msg *msg;
msg = ipvs_nl_message(IPVS_CMD_GET_INFO, 0);
if (msg)
return ipvs_nl_send_message(msg, ipvs_getinfo_parse_cb,
NULL);
return -1;
}
#endif
ipvs_func = ipvs_getinfo;
len = sizeof(ipvs_info);
return getsockopt(sockfd, IPPROTO_IP, IP_VS_SO_GET_INFO,
(char *)&ipvs_info, &len);
}
unsigned int ipvs_version(void)
{
return ipvs_info.version;
}
int ipvs_flush(void)
{
#ifdef LIBIPVS_USE_NL
if (try_nl) {
struct nl_msg *msg = ipvs_nl_message(IPVS_CMD_FLUSH, 0);
if (msg && (ipvs_nl_send_message(msg, ipvs_nl_noop_cb, NULL) == 0))
return 0;
return -1;
}
#endif
return setsockopt(sockfd, IPPROTO_IP, IP_VS_SO_SET_FLUSH, NULL, 0);
}
#ifdef LIBIPVS_USE_NL
static int ipvs_nl_fill_service_attr(struct nl_msg *msg, ipvs_service_t *svc)
{
struct nlattr *nl_service;
struct ip_vs_flags flags = { .flags = svc->flags,
.mask = ~0 };
nl_service = nla_nest_start(msg, IPVS_CMD_ATTR_SERVICE);
if (!nl_service)
return -1;
NLA_PUT_U16(msg, IPVS_SVC_ATTR_AF, svc->af);
if (svc->fwmark) {
NLA_PUT_U32(msg, IPVS_SVC_ATTR_FWMARK, svc->fwmark);
} else {
NLA_PUT_U16(msg, IPVS_SVC_ATTR_PROTOCOL, svc->protocol);
NLA_PUT(msg, IPVS_SVC_ATTR_ADDR, sizeof(svc->addr), &(svc->addr));
NLA_PUT_U16(msg, IPVS_SVC_ATTR_PORT, svc->port);
}
NLA_PUT_STRING(msg, IPVS_SVC_ATTR_SCHED_NAME, svc->sched_name);
if (svc->pe_name[0])
NLA_PUT_STRING(msg, IPVS_SVC_ATTR_PE_NAME, svc->pe_name);
NLA_PUT(msg, IPVS_SVC_ATTR_FLAGS, sizeof(flags), &flags);
NLA_PUT_U32(msg, IPVS_SVC_ATTR_TIMEOUT, svc->timeout);
NLA_PUT_U32(msg, IPVS_SVC_ATTR_NETMASK, svc->netmask);
nla_nest_end(msg, nl_service);
return 0;
nla_put_failure:
return -1;
}
#endif
int ipvs_add_service(ipvs_service_t *svc)
{
ipvs_func = ipvs_add_service;
#ifdef LIBIPVS_USE_NL
if (try_nl) {
struct nl_msg *msg = ipvs_nl_message(IPVS_CMD_NEW_SERVICE, 0);
if (!msg) return -1;
if (ipvs_nl_fill_service_attr(msg, svc)) {
nlmsg_free(msg);
return -1;
}
return ipvs_nl_send_message(msg, ipvs_nl_noop_cb, NULL);
}
#endif
CHECK_COMPAT_SVC(svc, -1);
return setsockopt(sockfd, IPPROTO_IP, IP_VS_SO_SET_ADD, (char *)svc,
sizeof(struct ip_vs_service_kern));
out_err:
return -1;
}
int ipvs_update_service(ipvs_service_t *svc)
{
ipvs_func = ipvs_update_service;
#ifdef LIBIPVS_USE_NL
if (try_nl) {
struct nl_msg *msg = ipvs_nl_message(IPVS_CMD_SET_SERVICE, 0);
if (!msg) return -1;
if (ipvs_nl_fill_service_attr(msg, svc)) {
nlmsg_free(msg);
return -1;
}
return ipvs_nl_send_message(msg, ipvs_nl_noop_cb, NULL);
}
#endif
CHECK_COMPAT_SVC(svc, -1);
return setsockopt(sockfd, IPPROTO_IP, IP_VS_SO_SET_EDIT, (char *)svc,
sizeof(struct ip_vs_service_kern));
out_err:
return -1;
}
int ipvs_del_service(ipvs_service_t *svc)
{
ipvs_func = ipvs_del_service;
#ifdef LIBIPVS_USE_NL
if (try_nl) {
struct nl_msg *msg = ipvs_nl_message(IPVS_CMD_DEL_SERVICE, 0);
if (!msg) return -1;
if (ipvs_nl_fill_service_attr(msg, svc)) {
nlmsg_free(msg);
return -1;
}
return ipvs_nl_send_message(msg, ipvs_nl_noop_cb, NULL);
}
#endif
CHECK_COMPAT_SVC(svc, -1);
return setsockopt(sockfd, IPPROTO_IP, IP_VS_SO_SET_DEL, (char *)svc,
sizeof(struct ip_vs_service_kern));
out_err:
return -1;
}
int ipvs_zero_service(ipvs_service_t *svc)
{
ipvs_func = ipvs_zero_service;
#ifdef LIBIPVS_USE_NL
if (try_nl) {
struct nl_msg *msg = ipvs_nl_message(IPVS_CMD_ZERO, 0);
if (!msg) return -1;
if (svc->fwmark
|| memcmp(&in6addr_any, &svc->addr.in6, sizeof(struct in6_addr))
|| svc->port) {
if (ipvs_nl_fill_service_attr(msg, svc)) {
nlmsg_free(msg);
return -1;
}
}
return ipvs_nl_send_message(msg, ipvs_nl_noop_cb, NULL);
}
#endif
CHECK_COMPAT_SVC(svc, -1);
return setsockopt(sockfd, IPPROTO_IP, IP_VS_SO_SET_ZERO, (char *)svc,
sizeof(struct ip_vs_service_kern));
out_err:
return -1;
}
#ifdef LIBIPVS_USE_NL
static int ipvs_nl_fill_dest_attr(struct nl_msg *msg, ipvs_dest_t *dst)
{
struct nlattr *nl_dest;
nl_dest = nla_nest_start(msg, IPVS_CMD_ATTR_DEST);
if (!nl_dest)
return -1;
NLA_PUT_U16(msg, IPVS_DEST_ATTR_ADDR_FAMILY, dst->af);
NLA_PUT(msg, IPVS_DEST_ATTR_ADDR, sizeof(dst->addr), &(dst->addr));
NLA_PUT_U16(msg, IPVS_DEST_ATTR_PORT, dst->port);
NLA_PUT_U32(msg, IPVS_DEST_ATTR_FWD_METHOD, dst->conn_flags & IP_VS_CONN_F_FWD_MASK);
NLA_PUT_U32(msg, IPVS_DEST_ATTR_WEIGHT, dst->weight);
NLA_PUT_U32(msg, IPVS_DEST_ATTR_U_THRESH, dst->u_threshold);
NLA_PUT_U32(msg, IPVS_DEST_ATTR_L_THRESH, dst->l_threshold);
nla_nest_end(msg, nl_dest);
return 0;
nla_put_failure:
return -1;
}
#endif
int ipvs_add_dest(ipvs_service_t *svc, ipvs_dest_t *dest)
{
ipvs_servicedest_t svcdest;
#ifdef LIBIPVS_USE_NL
ipvs_func = ipvs_add_dest;
if (try_nl) {
struct nl_msg *msg = ipvs_nl_message(IPVS_CMD_NEW_DEST, 0);
if (!msg) return -1;
if (ipvs_nl_fill_service_attr(msg, svc))
goto nla_put_failure;
if (ipvs_nl_fill_dest_attr(msg, dest))
goto nla_put_failure;
return ipvs_nl_send_message(msg, ipvs_nl_noop_cb, NULL);
nla_put_failure:
nlmsg_free(msg);
return -1;
}
#endif
CHECK_COMPAT_SVC(svc, -1);
CHECK_COMPAT_DEST(dest, -1);
memcpy(&svcdest.svc, svc, sizeof(svcdest.svc));
memcpy(&svcdest.dest, dest, sizeof(svcdest.dest));
return setsockopt(sockfd, IPPROTO_IP, IP_VS_SO_SET_ADDDEST,
(char *)&svcdest, sizeof(svcdest));
out_err:
return -1;
}
int ipvs_update_dest(ipvs_service_t *svc, ipvs_dest_t *dest)
{
ipvs_servicedest_t svcdest;
ipvs_func = ipvs_update_dest;
#ifdef LIBIPVS_USE_NL
if (try_nl) {
struct nl_msg *msg = ipvs_nl_message(IPVS_CMD_SET_DEST, 0);
if (!msg) return -1;
if (ipvs_nl_fill_service_attr(msg, svc))
goto nla_put_failure;
if (ipvs_nl_fill_dest_attr(msg, dest))
goto nla_put_failure;
return ipvs_nl_send_message(msg, ipvs_nl_noop_cb, NULL);
nla_put_failure:
nlmsg_free(msg);
return -1;
}
#endif
CHECK_COMPAT_SVC(svc, -1);
CHECK_COMPAT_DEST(dest, -1);
memcpy(&svcdest.svc, svc, sizeof(svcdest.svc));
memcpy(&svcdest.dest, dest, sizeof(svcdest.dest));
return setsockopt(sockfd, IPPROTO_IP, IP_VS_SO_SET_EDITDEST,
(char *)&svcdest, sizeof(svcdest));
out_err:
return -1;
}
int ipvs_del_dest(ipvs_service_t *svc, ipvs_dest_t *dest)
{
ipvs_servicedest_t svcdest;
ipvs_func = ipvs_del_dest;
#ifdef LIBIPVS_USE_NL
if (try_nl) {
struct nl_msg *msg = ipvs_nl_message(IPVS_CMD_DEL_DEST, 0);
if (!msg) return -1;
if (ipvs_nl_fill_service_attr(msg, svc))
goto nla_put_failure;
if (ipvs_nl_fill_dest_attr(msg, dest))
goto nla_put_failure;
return ipvs_nl_send_message(msg, ipvs_nl_noop_cb, NULL);
nla_put_failure:
nlmsg_free(msg);
return -1;
}
#endif
CHECK_COMPAT_SVC(svc, -1);
CHECK_COMPAT_DEST(dest, -1);
memcpy(&svcdest.svc, svc, sizeof(svcdest.svc));
memcpy(&svcdest.dest, dest, sizeof(svcdest.dest));
return setsockopt(sockfd, IPPROTO_IP, IP_VS_SO_SET_DELDEST,
(char *)&svcdest, sizeof(svcdest));
out_err:
return -1;
}
int ipvs_set_timeout(ipvs_timeout_t *to)
{
ipvs_func = ipvs_set_timeout;
#ifdef LIBIPVS_USE_NL
if (try_nl) {
struct nl_msg *msg = ipvs_nl_message(IPVS_CMD_SET_TIMEOUT, 0);
if (!msg) return -1;
NLA_PUT_U32(msg, IPVS_CMD_ATTR_TIMEOUT_TCP, to->tcp_timeout);
NLA_PUT_U32(msg, IPVS_CMD_ATTR_TIMEOUT_TCP_FIN, to->tcp_fin_timeout);
NLA_PUT_U32(msg, IPVS_CMD_ATTR_TIMEOUT_UDP, to->udp_timeout);
return ipvs_nl_send_message(msg, ipvs_nl_noop_cb, NULL);
nla_put_failure:
nlmsg_free(msg);
return -1;
}
#endif
return setsockopt(sockfd, IPPROTO_IP, IP_VS_SO_SET_TIMEOUT, (char *)to,
sizeof(*to));
}
int ipvs_start_daemon(ipvs_daemon_t *dm)
{
struct ip_vs_daemon_kern dmk;
ipvs_func = ipvs_start_daemon;
#ifdef LIBIPVS_USE_NL
if (try_nl) {
struct nlattr *nl_daemon;
struct nl_msg *msg = ipvs_nl_message(IPVS_CMD_NEW_DAEMON, 0);
if (!msg) return -1;
nl_daemon = nla_nest_start(msg, IPVS_CMD_ATTR_DAEMON);
if (!nl_daemon)
goto nla_put_failure;
NLA_PUT_U32(msg, IPVS_DAEMON_ATTR_STATE, dm->state);
NLA_PUT_STRING(msg, IPVS_DAEMON_ATTR_MCAST_IFN, dm->mcast_ifn);
NLA_PUT_U32(msg, IPVS_DAEMON_ATTR_SYNC_ID, dm->syncid);
if (dm->sync_maxlen)
NLA_PUT_U16(msg, IPVS_DAEMON_ATTR_SYNC_MAXLEN,
dm->sync_maxlen);
if (dm->mcast_port)
NLA_PUT_U16(msg, IPVS_DAEMON_ATTR_MCAST_PORT,
dm->mcast_port);
if (dm->mcast_ttl)
NLA_PUT_U8(msg, IPVS_DAEMON_ATTR_MCAST_TTL,
dm->mcast_ttl);
if (AF_INET6 == dm->mcast_af)
NLA_PUT(msg, IPVS_DAEMON_ATTR_MCAST_GROUP6,
sizeof(dm->mcast_group.in6),
&dm->mcast_group.in6);
else if (AF_INET == dm->mcast_af)
NLA_PUT_U32(msg, IPVS_DAEMON_ATTR_MCAST_GROUP,
dm->mcast_group.ip);
nla_nest_end(msg, nl_daemon);
return ipvs_nl_send_message(msg, ipvs_nl_noop_cb, NULL);
nla_put_failure:
nlmsg_free(msg);
return -1;
}
#endif
memset(&dmk, 0, sizeof(dmk));
dmk.state = dm->state;
strcpy(dmk.mcast_ifn, dm->mcast_ifn);
dmk.syncid = dm->syncid;
return setsockopt(sockfd, IPPROTO_IP, IP_VS_SO_SET_STARTDAEMON,
(char *)&dmk, sizeof(dmk));
}
int ipvs_stop_daemon(ipvs_daemon_t *dm)
{
struct ip_vs_daemon_kern dmk;
ipvs_func = ipvs_stop_daemon;
#ifdef LIBIPVS_USE_NL
if (try_nl) {
struct nlattr *nl_daemon;
struct nl_msg *msg = ipvs_nl_message(IPVS_CMD_DEL_DAEMON, 0);
if (!msg) return -1;
nl_daemon = nla_nest_start(msg, IPVS_CMD_ATTR_DAEMON);
if (!nl_daemon)
goto nla_put_failure;
NLA_PUT_U32(msg, IPVS_DAEMON_ATTR_STATE, dm->state);
NLA_PUT_STRING(msg, IPVS_DAEMON_ATTR_MCAST_IFN, dm->mcast_ifn);
NLA_PUT_U32(msg, IPVS_DAEMON_ATTR_SYNC_ID, dm->syncid);
nla_nest_end(msg, nl_daemon);
return ipvs_nl_send_message(msg, ipvs_nl_noop_cb, NULL);
nla_put_failure:
nlmsg_free(msg);
return -1;
}
#endif
memset(&dmk, 0, sizeof(dmk));
dmk.state = dm->state;
return setsockopt(sockfd, IPPROTO_IP, IP_VS_SO_SET_STOPDAEMON,
(char *)&dmk, sizeof(dmk));
}
#ifdef LIBIPVS_USE_NL
static int ipvs_parse_stats(struct ip_vs_stats64 *stats, struct nlattr *nla)
{
struct nlattr *attrs[IPVS_STATS_ATTR_MAX + 1];
if (nla_parse_nested(attrs, IPVS_STATS_ATTR_MAX, nla, ipvs_stats_policy))
return -1;
if (!(attrs[IPVS_STATS_ATTR_CONNS] &&
attrs[IPVS_STATS_ATTR_INPKTS] &&
attrs[IPVS_STATS_ATTR_OUTPKTS] &&
attrs[IPVS_STATS_ATTR_INBYTES] &&
attrs[IPVS_STATS_ATTR_OUTBYTES] &&
attrs[IPVS_STATS_ATTR_CPS] &&
attrs[IPVS_STATS_ATTR_INPPS] &&
attrs[IPVS_STATS_ATTR_OUTPPS] &&
attrs[IPVS_STATS_ATTR_INBPS] &&
attrs[IPVS_STATS_ATTR_OUTBPS]))
return -1;
stats->conns = nla_get_u32(attrs[IPVS_STATS_ATTR_CONNS]);
stats->inpkts = nla_get_u32(attrs[IPVS_STATS_ATTR_INPKTS]);
stats->outpkts = nla_get_u32(attrs[IPVS_STATS_ATTR_OUTPKTS]);
stats->inbytes = nla_get_u64(attrs[IPVS_STATS_ATTR_INBYTES]);
stats->outbytes = nla_get_u64(attrs[IPVS_STATS_ATTR_OUTBYTES]);
stats->cps = nla_get_u32(attrs[IPVS_STATS_ATTR_CPS]);
stats->inpps = nla_get_u32(attrs[IPVS_STATS_ATTR_INPPS]);
stats->outpps = nla_get_u32(attrs[IPVS_STATS_ATTR_OUTPPS]);
stats->inbps = nla_get_u32(attrs[IPVS_STATS_ATTR_INBPS]);
stats->outbps = nla_get_u32(attrs[IPVS_STATS_ATTR_OUTBPS]);
return 0;
}
static int ipvs_parse_stats64(struct ip_vs_stats64 *stats, struct nlattr *nla)
{
struct nlattr *attrs[IPVS_STATS_ATTR_MAX + 1];
if (nla_parse_nested(attrs, IPVS_STATS_ATTR_MAX, nla,
ipvs_stats_policy))
return -1;
if (!(attrs[IPVS_STATS_ATTR_CONNS] &&
attrs[IPVS_STATS_ATTR_INPKTS] &&
attrs[IPVS_STATS_ATTR_OUTPKTS] &&
attrs[IPVS_STATS_ATTR_INBYTES] &&
attrs[IPVS_STATS_ATTR_OUTBYTES] &&
attrs[IPVS_STATS_ATTR_CPS] &&
attrs[IPVS_STATS_ATTR_INPPS] &&
attrs[IPVS_STATS_ATTR_OUTPPS] &&
attrs[IPVS_STATS_ATTR_INBPS] &&
attrs[IPVS_STATS_ATTR_OUTBPS]))
return -1;
stats->conns = nla_get_u64(attrs[IPVS_STATS_ATTR_CONNS]);
stats->inpkts = nla_get_u64(attrs[IPVS_STATS_ATTR_INPKTS]);
stats->outpkts = nla_get_u64(attrs[IPVS_STATS_ATTR_OUTPKTS]);
stats->inbytes = nla_get_u64(attrs[IPVS_STATS_ATTR_INBYTES]);
stats->outbytes = nla_get_u64(attrs[IPVS_STATS_ATTR_OUTBYTES]);
stats->cps = nla_get_u64(attrs[IPVS_STATS_ATTR_CPS]);
stats->inpps = nla_get_u64(attrs[IPVS_STATS_ATTR_INPPS]);
stats->outpps = nla_get_u64(attrs[IPVS_STATS_ATTR_OUTPPS]);
stats->inbps = nla_get_u64(attrs[IPVS_STATS_ATTR_INBPS]);
stats->outbps = nla_get_u64(attrs[IPVS_STATS_ATTR_OUTBPS]);
return 0;
}
static int ipvs_services_parse_cb(struct nl_msg *msg, void *arg)
{
struct nlmsghdr *nlh = nlmsg_hdr(msg);
struct nlattr *attrs[IPVS_CMD_ATTR_MAX + 1];
struct nlattr *svc_attrs[IPVS_SVC_ATTR_MAX + 1];
struct ip_vs_get_services **getp = (struct ip_vs_get_services **)arg;
struct ip_vs_get_services *get = (struct ip_vs_get_services *)*getp;
struct ip_vs_flags flags;
int i = get->num_services;
if (genlmsg_parse(nlh, 0, attrs, IPVS_CMD_ATTR_MAX, ipvs_cmd_policy) != 0)
return -1;
if (!attrs[IPVS_CMD_ATTR_SERVICE])
return -1;
if (nla_parse_nested(svc_attrs, IPVS_SVC_ATTR_MAX, attrs[IPVS_CMD_ATTR_SERVICE], ipvs_service_policy))
return -1;
memset(&(get->entrytable[i]), 0, sizeof(get->entrytable[i]));
if (!(svc_attrs[IPVS_SVC_ATTR_AF] &&
(svc_attrs[IPVS_SVC_ATTR_FWMARK] ||
(svc_attrs[IPVS_SVC_ATTR_PROTOCOL] &&
svc_attrs[IPVS_SVC_ATTR_ADDR] &&
svc_attrs[IPVS_SVC_ATTR_PORT])) &&
svc_attrs[IPVS_SVC_ATTR_SCHED_NAME] &&
svc_attrs[IPVS_SVC_ATTR_NETMASK] &&
svc_attrs[IPVS_SVC_ATTR_TIMEOUT] &&
svc_attrs[IPVS_SVC_ATTR_FLAGS]))
return -1;
get->entrytable[i].af = nla_get_u16(svc_attrs[IPVS_SVC_ATTR_AF]);
if (svc_attrs[IPVS_SVC_ATTR_FWMARK])
get->entrytable[i].fwmark = nla_get_u32(svc_attrs[IPVS_SVC_ATTR_FWMARK]);
else {
get->entrytable[i].protocol = nla_get_u16(svc_attrs[IPVS_SVC_ATTR_PROTOCOL]);
memcpy(&(get->entrytable[i].addr), nla_data(svc_attrs[IPVS_SVC_ATTR_ADDR]),
sizeof(get->entrytable[i].addr));
get->entrytable[i].port = nla_get_u16(svc_attrs[IPVS_SVC_ATTR_PORT]);
}
strncpy(get->entrytable[i].sched_name,
nla_get_string(svc_attrs[IPVS_SVC_ATTR_SCHED_NAME]),
IP_VS_SCHEDNAME_MAXLEN);
if (svc_attrs[IPVS_SVC_ATTR_PE_NAME])
strncpy(get->entrytable[i].pe_name,
nla_get_string(svc_attrs[IPVS_SVC_ATTR_PE_NAME]),
IP_VS_PENAME_MAXLEN);
get->entrytable[i].netmask = nla_get_u32(svc_attrs[IPVS_SVC_ATTR_NETMASK]);
get->entrytable[i].timeout = nla_get_u32(svc_attrs[IPVS_SVC_ATTR_TIMEOUT]);
nla_memcpy(&flags, svc_attrs[IPVS_SVC_ATTR_FLAGS], sizeof(flags));
get->entrytable[i].flags = flags.flags & flags.mask;
if (svc_attrs[IPVS_SVC_ATTR_STATS64]) {
if (ipvs_parse_stats64(&get->entrytable[i].stats64,
svc_attrs[IPVS_SVC_ATTR_STATS64]) != 0)
return -1;
} else if (svc_attrs[IPVS_SVC_ATTR_STATS]) {
if (ipvs_parse_stats(&get->entrytable[i].stats64,
svc_attrs[IPVS_SVC_ATTR_STATS]) != 0)
return -1;
}
get->entrytable[i].num_dests = 0;
i++;
get->num_services = i;
get = realloc(get, sizeof(*get)
+ sizeof(ipvs_service_entry_t) * (get->num_services + 1));
*getp = get;
return 0;
}
#endif
struct ip_vs_get_services *ipvs_get_services(void)
{
struct ip_vs_get_services *get;
struct ip_vs_get_services_kern *getk;
socklen_t len;
int i;
#ifdef LIBIPVS_USE_NL
if (try_nl) {
struct nl_msg *msg;
len = sizeof(*get) +
sizeof(ipvs_service_entry_t);
if (!(get = malloc(len)))
return NULL;
get->num_services = 0;
msg = ipvs_nl_message(IPVS_CMD_GET_SERVICE, NLM_F_DUMP);
if (msg && (ipvs_nl_send_message(msg, ipvs_services_parse_cb, &get) == 0))
return get;
free(get);
return NULL;
}
#endif
len = sizeof(*get) +
sizeof(ipvs_service_entry_t) * ipvs_info.num_services;
if (!(get = malloc(len)))
return NULL;
len = sizeof(*getk) +
sizeof(struct ip_vs_service_entry_kern) * ipvs_info.num_services;
if (!(getk = malloc(len))) {
free(get);
return NULL;
}
ipvs_func = ipvs_get_services;
getk->num_services = ipvs_info.num_services;
if (getsockopt(sockfd, IPPROTO_IP,
IP_VS_SO_GET_SERVICES, getk, &len) < 0) {
free(get);
free(getk);
return NULL;
}
memcpy(get, getk, sizeof(struct ip_vs_get_services));
for (i = 0; i < getk->num_services; i++) {
memcpy(&get->entrytable[i], &getk->entrytable[i],
sizeof(struct ip_vs_service_entry_kern));
get->entrytable[i].af = AF_INET;
get->entrytable[i].addr.ip = get->entrytable[i].__addr_v4;
copy_stats_from_kern(&get->entrytable[i].stats64,
&get->entrytable[i].stats);
}
free(getk);
return get;
}
typedef int (*qsort_cmp_t)(const void *, const void *);
int
ipvs_cmp_services(ipvs_service_entry_t *s1, ipvs_service_entry_t *s2)
{
int r, i;
r = s1->fwmark - s2->fwmark;
if (r != 0)
return r;
r = s1->af - s2->af;
if (r != 0)
return r;
r = s1->protocol - s2->protocol;
if (r != 0)
return r;
if (s1->af == AF_INET6)
for (i = 0; !r && (i < 4); i++)
r = ntohl(s1->addr.in6.s6_addr32[i]) - ntohl(s2->addr.in6.s6_addr32[i]);
else
r = ntohl(s1->addr.ip) - ntohl(s2->addr.ip);
if (r != 0)
return r;
return ntohs(s1->port) - ntohs(s2->port);
}
void
ipvs_sort_services(struct ip_vs_get_services *s, ipvs_service_cmp_t f)
{
qsort(s->entrytable, s->num_services,
sizeof(ipvs_service_entry_t), (qsort_cmp_t)f);
}
#ifdef LIBIPVS_USE_NL
static int ipvs_dests_parse_cb(struct nl_msg *msg, void *arg)
{
struct nlmsghdr *nlh = nlmsg_hdr(msg);
struct nlattr *attrs[IPVS_CMD_ATTR_MAX + 1];
struct nlattr *dest_attrs[IPVS_DEST_ATTR_MAX + 1];
struct nlattr *attr_addr_family = NULL;
struct ip_vs_get_dests **dp = (struct ip_vs_get_dests **)arg;
struct ip_vs_get_dests *d = (struct ip_vs_get_dests *)*dp;
int i = d->num_dests;
if (genlmsg_parse(nlh, 0, attrs, IPVS_CMD_ATTR_MAX, ipvs_cmd_policy) != 0)
return -1;
if (!attrs[IPVS_CMD_ATTR_DEST])
return -1;
if (nla_parse_nested(dest_attrs, IPVS_DEST_ATTR_MAX, attrs[IPVS_CMD_ATTR_DEST], ipvs_dest_policy))
return -1;
memset(&(d->entrytable[i]), 0, sizeof(d->entrytable[i]));
if (!(dest_attrs[IPVS_DEST_ATTR_ADDR] &&
dest_attrs[IPVS_DEST_ATTR_PORT] &&
dest_attrs[IPVS_DEST_ATTR_FWD_METHOD] &&
dest_attrs[IPVS_DEST_ATTR_WEIGHT] &&
dest_attrs[IPVS_DEST_ATTR_U_THRESH] &&
dest_attrs[IPVS_DEST_ATTR_L_THRESH] &&
dest_attrs[IPVS_DEST_ATTR_ACTIVE_CONNS] &&
dest_attrs[IPVS_DEST_ATTR_INACT_CONNS] &&
dest_attrs[IPVS_DEST_ATTR_PERSIST_CONNS]))
return -1;
memcpy(&(d->entrytable[i].addr),
nla_data(dest_attrs[IPVS_DEST_ATTR_ADDR]),
sizeof(d->entrytable[i].addr));
d->entrytable[i].port = nla_get_u16(dest_attrs[IPVS_DEST_ATTR_PORT]);
d->entrytable[i].conn_flags = nla_get_u32(dest_attrs[IPVS_DEST_ATTR_FWD_METHOD]);
d->entrytable[i].weight = nla_get_u32(dest_attrs[IPVS_DEST_ATTR_WEIGHT]);
d->entrytable[i].u_threshold = nla_get_u32(dest_attrs[IPVS_DEST_ATTR_U_THRESH]);
d->entrytable[i].l_threshold = nla_get_u32(dest_attrs[IPVS_DEST_ATTR_L_THRESH]);
d->entrytable[i].activeconns = nla_get_u32(dest_attrs[IPVS_DEST_ATTR_ACTIVE_CONNS]);
d->entrytable[i].inactconns = nla_get_u32(dest_attrs[IPVS_DEST_ATTR_INACT_CONNS]);
d->entrytable[i].persistconns = nla_get_u32(dest_attrs[IPVS_DEST_ATTR_PERSIST_CONNS]);
attr_addr_family = dest_attrs[IPVS_DEST_ATTR_ADDR_FAMILY];
if (attr_addr_family)
d->entrytable[i].af = nla_get_u16(attr_addr_family);
else
d->entrytable[i].af = d->af;
if (dest_attrs[IPVS_DEST_ATTR_STATS64]) {
if (ipvs_parse_stats(&d->entrytable[i].stats64,
dest_attrs[IPVS_DEST_ATTR_STATS64]) != 0)
return -1;
} else if (dest_attrs[IPVS_DEST_ATTR_STATS]) {
if (ipvs_parse_stats(&d->entrytable[i].stats64,
dest_attrs[IPVS_DEST_ATTR_STATS]) != 0)
return -1;
}
i++;
d->num_dests = i;
d = realloc(d, sizeof(*d) + sizeof(ipvs_dest_entry_t) * (d->num_dests + 1));
*dp = d;
return 0;
}
#endif
struct ip_vs_get_dests *ipvs_get_dests(ipvs_service_entry_t *svc)
{
struct ip_vs_get_dests *d;
struct ip_vs_get_dests_kern *dk;
socklen_t len;
int i;
len = sizeof(*d) + sizeof(ipvs_dest_entry_t) * svc->num_dests;
if (!(d = malloc(len)))
return NULL;
ipvs_func = ipvs_get_dests;
#ifdef LIBIPVS_USE_NL
if (try_nl) {
struct nl_msg *msg;
struct nlattr *nl_service;
if (svc->num_dests == 0)
d = realloc(d,sizeof(*d) + sizeof(ipvs_dest_entry_t));
d->fwmark = svc->fwmark;
d->protocol = svc->protocol;
d->addr = svc->addr;
d->port = svc->port;
d->num_dests = svc->num_dests;
d->af = svc->af;
msg = ipvs_nl_message(IPVS_CMD_GET_DEST, NLM_F_DUMP);
if (!msg)
goto ipvs_nl_dest_failure;
nl_service = nla_nest_start(msg, IPVS_CMD_ATTR_SERVICE);
if (!nl_service)
goto nla_put_failure;
NLA_PUT_U16(msg, IPVS_SVC_ATTR_AF, svc->af);
if (svc->fwmark) {
NLA_PUT_U32(msg, IPVS_SVC_ATTR_FWMARK, svc->fwmark);
} else {
NLA_PUT_U16(msg, IPVS_SVC_ATTR_PROTOCOL, svc->protocol);
NLA_PUT(msg, IPVS_SVC_ATTR_ADDR, sizeof(svc->addr),
&svc->addr);
NLA_PUT_U16(msg, IPVS_SVC_ATTR_PORT, svc->port);
}
nla_nest_end(msg, nl_service);
if (ipvs_nl_send_message(msg, ipvs_dests_parse_cb, &d))
goto ipvs_nl_dest_failure;
return d;
nla_put_failure:
nlmsg_free(msg);
ipvs_nl_dest_failure:
free(d);
return NULL;
}
#endif
if (svc->af != AF_INET) {
errno = EAFNOSUPPORT;
free(d);
return NULL;
}
len = sizeof(*dk) + sizeof(struct ip_vs_dest_entry_kern) * svc->num_dests;
if (!(dk = malloc(len))) {
free(d);
return NULL;
}
dk->fwmark = svc->fwmark;
dk->protocol = svc->protocol;
dk->addr = svc->addr.ip;
dk->port = svc->port;
dk->num_dests = svc->num_dests;
if (getsockopt(sockfd, IPPROTO_IP,
IP_VS_SO_GET_DESTS, dk, &len) < 0) {
free(d);
free(dk);
return NULL;
}
memcpy(d, dk, sizeof(struct ip_vs_get_dests_kern));
d->af = AF_INET;
d->addr.ip = d->__addr_v4;
for (i = 0; i < dk->num_dests; i++) {
memcpy(&d->entrytable[i], &dk->entrytable[i],
sizeof(struct ip_vs_dest_entry_kern));
d->entrytable[i].af = AF_INET;
d->entrytable[i].addr.ip = d->entrytable[i].__addr_v4;
copy_stats_from_kern(&d->entrytable[i].stats64,
&d->entrytable[i].stats);
}
free(dk);
return d;
}
int ipvs_cmp_dests(ipvs_dest_entry_t *d1, ipvs_dest_entry_t *d2)
{
int r = 0, i;
if (d1->af == AF_INET6)
for (i = 0; !r && (i < 4); i++)
r = ntohl(d1->addr.in6.s6_addr32[i]) -
ntohl(d2->addr.in6.s6_addr32[i]);
else
r = ntohl(d1->addr.ip) - ntohl(d2->addr.ip);
if (r != 0)
return r;
return ntohs(d1->port) - ntohs(d2->port);
}
void ipvs_sort_dests(struct ip_vs_get_dests *d, ipvs_dest_cmp_t f)
{
qsort(d->entrytable, d->num_dests,
sizeof(ipvs_dest_entry_t), (qsort_cmp_t)f);
}
ipvs_service_entry_t *
ipvs_get_service(__u32 fwmark, __u16 af, __u16 protocol, union nf_inet_addr addr, __u16 port)
{
ipvs_service_entry_t *svc;
socklen_t len;
ipvs_func = ipvs_get_service;
#ifdef LIBIPVS_USE_NL
if (try_nl) {
struct ip_vs_get_services *get;
struct nl_msg *msg;
ipvs_service_t tsvc;
svc = malloc(sizeof(*svc));
if (!svc)
return NULL;
memset(&tsvc, 0, sizeof(tsvc));
tsvc.fwmark = fwmark;
tsvc.af = af;
tsvc.protocol= protocol;
tsvc.addr = addr;
tsvc.port = port;
if (!(get = malloc(sizeof(*get) + sizeof(ipvs_service_entry_t))))
goto ipvs_get_service_err2;
get->num_services = 0;
msg = ipvs_nl_message(IPVS_CMD_GET_SERVICE, 0);
if (!msg) goto ipvs_get_service_err;
if (ipvs_nl_fill_service_attr(msg, &tsvc))
goto nla_put_failure;
if (ipvs_nl_send_message(msg, ipvs_services_parse_cb, &get))
goto ipvs_get_service_err;
memcpy(svc, &(get->entrytable[0]), sizeof(*svc));
free(get);
return svc;
nla_put_failure:
nlmsg_free(msg);
ipvs_get_service_err:
free(get);
ipvs_get_service_err2:
free(svc);
return NULL;
}
#endif
len = sizeof(*svc);
svc = calloc(1, len);
if (!svc)
return NULL;
svc->fwmark = fwmark;
svc->af = af;
svc->protocol = protocol;
svc->addr = addr;
svc->port = port;
CHECK_COMPAT_SVC(svc, NULL);
if (getsockopt(sockfd, IPPROTO_IP, IP_VS_SO_GET_SERVICE,
(char *)svc, &len)) {
free(svc);
return NULL;
}
svc->af = AF_INET;
svc->addr.ip = svc->__addr_v4;
svc->pe_name[0] = '\0';
copy_stats_from_kern(&svc->stats64, &svc->stats);
return svc;
out_err:
free(svc);
return NULL;
}
#ifdef LIBIPVS_USE_NL
static int ipvs_timeout_parse_cb(struct nl_msg *msg, void *arg)
{
struct nlmsghdr *nlh = nlmsg_hdr(msg);
struct nlattr *attrs[IPVS_CMD_ATTR_MAX + 1];
ipvs_timeout_t *u = (ipvs_timeout_t *)arg;
if (genlmsg_parse(nlh, 0, attrs, IPVS_CMD_ATTR_MAX, ipvs_cmd_policy) != 0)
return -1;
if (attrs[IPVS_CMD_ATTR_TIMEOUT_TCP])
u->tcp_timeout = nla_get_u32(attrs[IPVS_CMD_ATTR_TIMEOUT_TCP]);
if (attrs[IPVS_CMD_ATTR_TIMEOUT_TCP_FIN])
u->tcp_fin_timeout = nla_get_u32(attrs[IPVS_CMD_ATTR_TIMEOUT_TCP_FIN]);
if (attrs[IPVS_CMD_ATTR_TIMEOUT_UDP])
u->udp_timeout = nla_get_u32(attrs[IPVS_CMD_ATTR_TIMEOUT_UDP]);
return NL_OK;
}
#endif
ipvs_timeout_t *ipvs_get_timeout(void)
{
ipvs_timeout_t *u;
socklen_t len;
len = sizeof(*u);
if (!(u = malloc(len)))
return NULL;
ipvs_func = ipvs_get_timeout;
#ifdef LIBIPVS_USE_NL
if (try_nl) {
struct nl_msg *msg;
memset(u, 0, sizeof(*u));
msg = ipvs_nl_message(IPVS_CMD_GET_TIMEOUT, 0);
if (msg && (ipvs_nl_send_message(msg, ipvs_timeout_parse_cb, u) == 0))
return u;
free(u);
return NULL;
}
#endif
if (getsockopt(sockfd, IPPROTO_IP, IP_VS_SO_GET_TIMEOUT,
(char *)u, &len)) {
free(u);
return NULL;
}
return u;
}
#ifdef LIBIPVS_USE_NL
static int ipvs_daemon_parse_cb(struct nl_msg *msg, void *arg)
{
struct nlmsghdr *nlh = nlmsg_hdr(msg);
struct nlattr *attrs[IPVS_CMD_ATTR_MAX + 1];
struct nlattr *daemon_attrs[IPVS_DAEMON_ATTR_MAX + 1];
ipvs_daemon_t *u = (ipvs_daemon_t *)arg;
struct nlattr *a;
int i = 0;
/* We may get two daemons. If we've already got one, this is the second */
if (u[0].state)
i = 1;
if (genlmsg_parse(nlh, 0, attrs, IPVS_CMD_ATTR_MAX, ipvs_cmd_policy) != 0)
return -1;
if (nla_parse_nested(daemon_attrs, IPVS_DAEMON_ATTR_MAX,
attrs[IPVS_CMD_ATTR_DAEMON], ipvs_daemon_policy))
return -1;
if (!(daemon_attrs[IPVS_DAEMON_ATTR_STATE] &&
daemon_attrs[IPVS_DAEMON_ATTR_MCAST_IFN] &&
daemon_attrs[IPVS_DAEMON_ATTR_SYNC_ID]))
return -1;
u[i].state = nla_get_u32(daemon_attrs[IPVS_DAEMON_ATTR_STATE]);
strncpy(u[i].mcast_ifn,
nla_get_string(daemon_attrs[IPVS_DAEMON_ATTR_MCAST_IFN]),
IP_VS_IFNAME_MAXLEN);
u[i].syncid = nla_get_u32(daemon_attrs[IPVS_DAEMON_ATTR_SYNC_ID]);
a = daemon_attrs[IPVS_DAEMON_ATTR_SYNC_MAXLEN];
if (a)
u[i].sync_maxlen = nla_get_u16(a);
a = daemon_attrs[IPVS_DAEMON_ATTR_MCAST_PORT];
if (a)
u[i].mcast_port = nla_get_u16(a);
a = daemon_attrs[IPVS_DAEMON_ATTR_MCAST_TTL];
if (a)
u[i].mcast_ttl = nla_get_u8(a);
a = daemon_attrs[IPVS_DAEMON_ATTR_MCAST_GROUP];
if (a) {
u[i].mcast_af = AF_INET;
u[i].mcast_group.ip = nla_get_u32(a);
} else {
a = daemon_attrs[IPVS_DAEMON_ATTR_MCAST_GROUP6];
if (a) {
u[i].mcast_af = AF_INET6;
memcpy(&u[i].mcast_group.in6, nla_data(a),
sizeof(u[i].mcast_group.in6));
}
}
return NL_OK;
}
#endif
ipvs_daemon_t *ipvs_get_daemon(void)
{
struct ip_vs_daemon_kern dmk[2];
ipvs_daemon_t *u;
socklen_t len;
int i;
/* note that we need to get the info about two possible
daemons, master and backup. */
len = sizeof(*u) * 2;
u = calloc(len, 1);
if (!u)
return NULL;
ipvs_func = ipvs_get_daemon;
#ifdef LIBIPVS_USE_NL
if (try_nl) {
struct nl_msg *msg;
msg = ipvs_nl_message(IPVS_CMD_GET_DAEMON, NLM_F_DUMP);
if (msg && (ipvs_nl_send_message(msg, ipvs_daemon_parse_cb, u) == 0))
return u;
free(u);
return NULL;
}
#endif
if (getsockopt(sockfd, IPPROTO_IP, IP_VS_SO_GET_DAEMON, (char *)dmk,
&len)) {
free(u);
return NULL;
}
for (i = 0; i < 2; i++) {
u[i].state = dmk[i].state;
strncpy(u[i].mcast_ifn, dmk[i].mcast_ifn, IP_VS_IFNAME_MAXLEN);
u[i].syncid = dmk[i].syncid;
}
return u;
}
void ipvs_close(void)
{
#ifdef LIBIPVS_USE_NL
if (try_nl) {
return;
}
#endif
close(sockfd);
}
const char *ipvs_strerror(int err)
{
unsigned int i;
struct table_struct {
void *func;
int err;
const char *message;
} table [] = {
{ ipvs_add_service, EEXIST, "Service already exists" },
{ ipvs_add_service, ENOENT, "Scheduler or persistence engine not found" },
{ ipvs_update_service, ESRCH, "No such service" },
{ ipvs_update_service, ENOENT, "Scheduler or persistence engine not found" },
{ ipvs_del_service, ESRCH, "No such service" },
{ ipvs_zero_service, ESRCH, "No such service" },
{ ipvs_add_dest, ESRCH, "Service not defined" },
{ ipvs_add_dest, EEXIST, "Destination already exists" },
{ ipvs_update_dest, ESRCH, "Service not defined" },
{ ipvs_update_dest, ENOENT, "No such destination" },
{ ipvs_del_dest, ESRCH, "Service not defined" },
{ ipvs_del_dest, ENOENT, "No such destination" },
{ ipvs_start_daemon, EEXIST, "Daemon has already run" },
{ ipvs_stop_daemon, ESRCH, "No daemon is running" },
{ ipvs_get_services, ESRCH, "No such service" },
{ ipvs_get_dests, ESRCH, "No such service" },
{ ipvs_get_service, ESRCH, "No such service" },
{ 0, EPERM, "Permission denied (you must be root)" },
{ 0, EINVAL, "Invalid operation. Possibly wrong module version, address not unicast, ..." },
{ 0, ENOPROTOOPT, "Protocol not available" },
{ 0, ENOMEM, "Memory allocation problem" },
{ 0, EOPNOTSUPP, "Operation not supported with IPv6" },
{ 0, EAFNOSUPPORT, "Operation not supported with specified address family" },
{ 0, EMSGSIZE, "Module is wrong version" },
};
for (i = 0; i < sizeof(table)/sizeof(struct table_struct); i++) {
if ((!table[i].func || table[i].func == ipvs_func)
&& table[i].err == err)
return table[i].message;
}
return strerror(err);
}