zebra: netlink cleanup

* Split zebra's netlink code into smaller functions to be more
   generic.

Signed-off-by: Jakub Urbańczyk <xthaid@gmail.com>
This commit is contained in:
Jakub Urbańczyk 2020-06-24 20:34:38 +02:00
parent 5b76e76515
commit ae6138bfd8

View File

@ -697,6 +697,204 @@ static void netlink_parse_extended_ack(struct nlmsghdr *h)
}
}
/*
* netlink_send_msg - send a netlink message of a certain size.
*
* Returns -1 on error. Otherwise, it returns the number of bytes sent.
*/
static int netlink_send_msg(const struct nlsock *nl, void *buf, size_t buflen)
{
struct sockaddr_nl snl;
struct iovec iov;
struct msghdr msg;
int status, save_errno = 0;
memset(&snl, 0, sizeof(snl));
memset(&iov, 0, sizeof(iov));
memset(&msg, 0, sizeof(msg));
iov.iov_base = buf;
iov.iov_len = buflen;
msg.msg_name = (void *)&snl;
msg.msg_namelen = sizeof(snl);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
snl.nl_family = AF_NETLINK;
/* Send message to netlink interface. */
frr_with_privs(&zserv_privs) {
status = sendmsg(nl->sock, &msg, 0);
save_errno = errno;
}
if (IS_ZEBRA_DEBUG_KERNEL_MSGDUMP_SEND) {
zlog_debug("%s: >> netlink message dump [sent]", __func__);
zlog_hexdump(buf, buflen);
}
if (status < 0) {
flog_err_sys(EC_LIB_SOCKET, "%s error: %s", __func__,
safe_strerror(save_errno));
return -1;
}
return status;
}
/*
* netlink_recv_msg - receive a netlink message.
*
* Returns -1 on error, 0 if read would block or the number of bytes received.
*/
static int netlink_recv_msg(const struct nlsock *nl, struct msghdr msg,
void *buf, size_t buflen)
{
struct iovec iov;
int status;
iov.iov_base = buf;
iov.iov_len = buflen;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
do {
#if defined(HANDLE_NETLINK_FUZZING)
/* Check if reading and filename is set */
if (netlink_read && '\0' != netlink_fuzz_file[0]) {
zlog_debug("Reading netlink fuzz file");
status = netlink_read_file(buf, netlink_fuzz_file);
((struct sockaddr_nl *)msg.msg_name)->nl_pid = 0;
} else {
status = recvmsg(nl->sock, &msg, 0);
}
#else
status = recvmsg(nl->sock, &msg, 0);
#endif /* HANDLE_NETLINK_FUZZING */
} while (status < 0 && errno == EINTR);
if (status < 0) {
if (errno == EWOULDBLOCK || errno == EAGAIN)
return 0;
flog_err(EC_ZEBRA_RECVMSG_OVERRUN, "%s recvmsg overrun: %s",
nl->name, safe_strerror(errno));
/*
* In this case we are screwed. There is no good way to recover
* zebra at this point.
*/
exit(-1);
}
if (status == 0) {
flog_err_sys(EC_LIB_SOCKET, "%s EOF", nl->name);
return -1;
}
if (msg.msg_namelen != sizeof(struct sockaddr_nl)) {
flog_err(EC_ZEBRA_NETLINK_LENGTH_ERROR,
"%s sender address length error: length %d", nl->name,
msg.msg_namelen);
return -1;
}
if (IS_ZEBRA_DEBUG_KERNEL_MSGDUMP_RECV) {
zlog_debug("%s: << netlink message dump [recv]", __func__);
zlog_hexdump(buf, status);
}
#if defined(HANDLE_NETLINK_FUZZING)
if (!netlink_read) {
zlog_debug("Writing incoming netlink message");
netlink_write_incoming(buf, status, netlink_file_counter++);
}
#endif /* HANDLE_NETLINK_FUZZING */
return status;
}
/*
* netlink_parse_error - parse a netlink error message
*
* Returns 1 if this message is acknowledgement, 0 if this error should be
* ignored, -1 otherwise.
*/
static int netlink_parse_error(const struct nlsock *nl, struct nlmsghdr *h,
const struct zebra_dplane_info *zns,
bool startup)
{
struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(h);
int errnum = err->error;
int msg_type = err->msg.nlmsg_type;
if (h->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr))) {
flog_err(EC_ZEBRA_NETLINK_LENGTH_ERROR,
"%s error: message truncated", nl->name);
return -1;
}
/*
* Parse the extended information before we actually handle it. At this
* point in time we do not do anything other than report the issue.
*/
if (h->nlmsg_flags & NLM_F_ACK_TLVS)
netlink_parse_extended_ack(h);
/* If the error field is zero, then this is an ACK. */
if (err->error == 0) {
if (IS_ZEBRA_DEBUG_KERNEL) {
zlog_debug("%s: %s ACK: type=%s(%u), seq=%u, pid=%u",
__func__, nl->name,
nl_msg_type_to_str(err->msg.nlmsg_type),
err->msg.nlmsg_type, err->msg.nlmsg_seq,
err->msg.nlmsg_pid);
}
return 1;
}
/* Deal with errors that occur because of races in link handling. */
if (zns->is_cmd
&& ((msg_type == RTM_DELROUTE
&& (-errnum == ENODEV || -errnum == ESRCH))
|| (msg_type == RTM_NEWROUTE
&& (-errnum == ENETDOWN || -errnum == EEXIST)))) {
if (IS_ZEBRA_DEBUG_KERNEL)
zlog_debug("%s: error: %s type=%s(%u), seq=%u, pid=%u",
nl->name, safe_strerror(-errnum),
nl_msg_type_to_str(msg_type), msg_type,
err->msg.nlmsg_seq, err->msg.nlmsg_pid);
return 0;
}
/*
* We see RTM_DELNEIGH when shutting down an interface with an IPv4
* link-local. The kernel should have already deleted the neighbor so
* do not log these as an error.
*/
if (msg_type == RTM_DELNEIGH
|| (zns->is_cmd && msg_type == RTM_NEWROUTE
&& (-errnum == ESRCH || -errnum == ENETUNREACH))) {
/*
* This is known to happen in some situations, don't log as
* error.
*/
if (IS_ZEBRA_DEBUG_KERNEL)
zlog_debug("%s error: %s, type=%s(%u), seq=%u, pid=%u",
nl->name, safe_strerror(-errnum),
nl_msg_type_to_str(msg_type), msg_type,
err->msg.nlmsg_seq, err->msg.nlmsg_pid);
} else {
if ((msg_type != RTM_GETNEXTHOP) || !startup)
flog_err(EC_ZEBRA_UNEXPECTED_MESSAGE,
"%s error: %s, type=%s(%u), seq=%u, pid=%u",
nl->name, safe_strerror(-errnum),
nl_msg_type_to_str(msg_type), msg_type,
err->msg.nlmsg_seq, err->msg.nlmsg_pid);
}
return -1;
}
/*
* netlink_parse_info
*
@ -722,71 +920,19 @@ int netlink_parse_info(int (*filter)(struct nlmsghdr *, ns_id_t, int),
while (1) {
char buf[NL_RCV_PKT_BUF_SIZE];
struct iovec iov = {.iov_base = buf, .iov_len = sizeof(buf)};
struct sockaddr_nl snl;
struct msghdr msg = {.msg_name = (void *)&snl,
.msg_namelen = sizeof(snl),
.msg_iov = &iov,
.msg_iovlen = 1};
.msg_namelen = sizeof(snl)};
struct nlmsghdr *h;
if (count && read_in >= count)
return 0;
#if defined(HANDLE_NETLINK_FUZZING)
/* Check if reading and filename is set */
if (netlink_read && '\0' != netlink_fuzz_file[0]) {
zlog_debug("Reading netlink fuzz file");
status = netlink_read_file(buf, netlink_fuzz_file);
snl.nl_pid = 0;
} else {
status = recvmsg(nl->sock, &msg, 0);
}
#else
status = recvmsg(nl->sock, &msg, 0);
#endif /* HANDLE_NETLINK_FUZZING */
if (status < 0) {
if (errno == EINTR)
continue;
if (errno == EWOULDBLOCK || errno == EAGAIN)
break;
flog_err(EC_ZEBRA_RECVMSG_OVERRUN,
"%s recvmsg overrun: %s", nl->name,
safe_strerror(errno));
/*
* In this case we are screwed.
* There is no good way to
* recover zebra at this point.
*/
exit(-1);
continue;
}
if (status == 0) {
flog_err_sys(EC_LIB_SOCKET, "%s EOF", nl->name);
status = netlink_recv_msg(nl, msg, buf, sizeof(buf));
if (status == -1)
return -1;
}
if (msg.msg_namelen != sizeof(snl)) {
flog_err(EC_ZEBRA_NETLINK_LENGTH_ERROR,
"%s sender address length error: length %d",
nl->name, msg.msg_namelen);
return -1;
}
if (IS_ZEBRA_DEBUG_KERNEL_MSGDUMP_RECV) {
zlog_debug("%s: << netlink message dump [recv]",
__func__);
zlog_hexdump(buf, status);
}
#if defined(HANDLE_NETLINK_FUZZING)
if (!netlink_read) {
zlog_debug("Writing incoming netlink message");
netlink_write_incoming(buf, status,
netlink_file_counter++);
}
#endif /* HANDLE_NETLINK_FUZZING */
else if (status == 0)
break;
read_in++;
for (h = (struct nlmsghdr *)buf;
@ -798,112 +944,14 @@ int netlink_parse_info(int (*filter)(struct nlmsghdr *, ns_id_t, int),
/* Error handling. */
if (h->nlmsg_type == NLMSG_ERROR) {
struct nlmsgerr *err =
(struct nlmsgerr *)NLMSG_DATA(h);
int errnum = err->error;
int msg_type = err->msg.nlmsg_type;
if (h->nlmsg_len
< NLMSG_LENGTH(sizeof(struct nlmsgerr))) {
flog_err(EC_ZEBRA_NETLINK_LENGTH_ERROR,
"%s error: message truncated",
nl->name);
return -1;
}
/*
* Parse the extended information before
* we actually handle it.
* At this point in time we do not
* do anything other than report the
* issue.
*/
if (h->nlmsg_flags & NLM_F_ACK_TLVS)
netlink_parse_extended_ack(h);
/* If the error field is zero, then this is an
* ACK */
if (err->error == 0) {
if (IS_ZEBRA_DEBUG_KERNEL) {
zlog_debug(
"%s: %s ACK: type=%s(%u), seq=%u, pid=%u",
__func__, nl->name,
nl_msg_type_to_str(
err->msg.nlmsg_type),
err->msg.nlmsg_type,
err->msg.nlmsg_seq,
err->msg.nlmsg_pid);
}
/* return if not a multipart message,
* otherwise continue */
int err = netlink_parse_error(nl, h, zns,
startup);
if (err == 1) {
if (!(h->nlmsg_flags & NLM_F_MULTI))
return 0;
continue;
}
/* Deal with errors that occur because of races
* in link handling */
if (zns->is_cmd
&& ((msg_type == RTM_DELROUTE
&& (-errnum == ENODEV
|| -errnum == ESRCH))
|| (msg_type == RTM_NEWROUTE
&& (-errnum == ENETDOWN
|| -errnum == EEXIST)))) {
if (IS_ZEBRA_DEBUG_KERNEL)
zlog_debug(
"%s: error: %s type=%s(%u), seq=%u, pid=%u",
nl->name,
safe_strerror(-errnum),
nl_msg_type_to_str(
msg_type),
msg_type,
err->msg.nlmsg_seq,
err->msg.nlmsg_pid);
return 0;
}
/* We see RTM_DELNEIGH when shutting down an
* interface with an IPv4
* link-local. The kernel should have already
* deleted the neighbor
* so do not log these as an error.
*/
if (msg_type == RTM_DELNEIGH
|| (zns->is_cmd && msg_type == RTM_NEWROUTE
&& (-errnum == ESRCH
|| -errnum == ENETUNREACH))) {
/* This is known to happen in some
* situations, don't log
* as error.
*/
if (IS_ZEBRA_DEBUG_KERNEL)
zlog_debug(
"%s error: %s, type=%s(%u), seq=%u, pid=%u",
nl->name,
safe_strerror(-errnum),
nl_msg_type_to_str(
msg_type),
msg_type,
err->msg.nlmsg_seq,
err->msg.nlmsg_pid);
} else {
if ((msg_type != RTM_GETNEXTHOP)
|| !startup)
flog_err(
EC_ZEBRA_UNEXPECTED_MESSAGE,
"%s error: %s, type=%s(%u), seq=%u, pid=%u",
nl->name,
safe_strerror(-errnum),
nl_msg_type_to_str(
msg_type),
msg_type,
err->msg.nlmsg_seq,
err->msg.nlmsg_pid);
}
return -1;
} else
return err;
}
/* OK we got netlink message. */
@ -966,26 +1014,8 @@ int netlink_talk_info(int (*filter)(struct nlmsghdr *, ns_id_t, int startup),
struct nlmsghdr *n,
const struct zebra_dplane_info *dp_info, int startup)
{
int status = 0;
struct sockaddr_nl snl;
struct iovec iov;
struct msghdr msg;
int save_errno = 0;
const struct nlsock *nl;
memset(&snl, 0, sizeof(snl));
memset(&iov, 0, sizeof(iov));
memset(&msg, 0, sizeof(msg));
iov.iov_base = n;
iov.iov_len = n->nlmsg_len;
msg.msg_name = (void *)&snl;
msg.msg_namelen = sizeof(snl);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
snl.nl_family = AF_NETLINK;
nl = &(dp_info->nls);
n->nlmsg_seq = nl->seq;
n->nlmsg_pid = nl->snl.nl_pid;
@ -997,22 +1027,8 @@ int netlink_talk_info(int (*filter)(struct nlmsghdr *, ns_id_t, int startup),
n->nlmsg_type, n->nlmsg_len, n->nlmsg_seq,
n->nlmsg_flags);
/* Send message to netlink interface. */
frr_with_privs(&zserv_privs) {
status = sendmsg(nl->sock, &msg, 0);
save_errno = errno;
}
if (IS_ZEBRA_DEBUG_KERNEL_MSGDUMP_SEND) {
zlog_debug("%s: >> netlink message dump [sent]", __func__);
zlog_hexdump(n, n->nlmsg_len);
}
if (status < 0) {
flog_err_sys(EC_LIB_SOCKET, "netlink_talk sendmsg() error: %s",
safe_strerror(save_errno));
if (netlink_send_msg(nl, n, n->nlmsg_len) < 0)
return -1;
}
/*
* Get reply from netlink socket.
@ -1047,8 +1063,6 @@ int netlink_talk(int (*filter)(struct nlmsghdr *, ns_id_t, int startup),
*/
int netlink_request(struct nlsock *nl, void *req)
{
int ret;
struct sockaddr_nl snl;
struct nlmsghdr *n = (struct nlmsghdr *)req;
/* Check netlink socket. */
@ -1062,20 +1076,8 @@ int netlink_request(struct nlsock *nl, void *req)
n->nlmsg_pid = nl->snl.nl_pid;
n->nlmsg_seq = ++nl->seq;
memset(&snl, 0, sizeof(snl));
snl.nl_family = AF_NETLINK;
/* Raise capabilities and send message, then lower capabilities. */
frr_with_privs(&zserv_privs) {
ret = sendto(nl->sock, req, n->nlmsg_len, 0,
(struct sockaddr *)&snl, sizeof(snl));
}
if (ret < 0) {
zlog_err("%s sendto failed: %s", nl->name,
safe_strerror(errno));
if (netlink_send_msg(nl, req, n->nlmsg_len) < 0)
return -1;
}
return 0;
}