diff mbox series

[3/4] RFC: Add rtnetlink helper library

Message ID 20210426111918.4304-4-mdoucha@suse.cz
State Superseded
Headers show
Series RTNetlink and network device management library | expand

Commit Message

Martin Doucha April 26, 2021, 11:19 a.m. UTC
This library provides simple interface for creating arbitrary rtnetlink
messages with complex attributes, sending requests and receiving results.

Signed-off-by: Martin Doucha <mdoucha@suse.cz>
---
 include/tst_rtnetlink.h | 105 +++++++++++
 lib/tst_rtnetlink.c     | 399 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 504 insertions(+)
 create mode 100644 include/tst_rtnetlink.h
 create mode 100644 lib/tst_rtnetlink.c

Comments

Cyril Hrubis April 27, 2021, 1:41 p.m. UTC | #1
Hi!
> +static int tst_rtnl_grow_buffer(const char *file, const int lineno,
> +	struct tst_rtnl_context *ctx, size_t size)
> +{
> +	size_t needed, offset, curlen = NLMSG_ALIGN(ctx->datalen);
> +	char *buf;
> +
> +	if (ctx->bufsize - curlen >= size)
> +		return 1;
> +
> +	needed = size - (ctx->bufsize - curlen);
> +	size = ctx->bufsize + (ctx->bufsize > needed ? ctx->bufsize : needed);
> +	size = NLMSG_ALIGN(size);
> +	buf = safe_realloc(file, lineno, ctx->buffer, size);
> +
> +	if (!buf)
> +		return 0;

You are calling safe_realloc() here yet you check the return value. And
it's the same for safe_malloc(), safe_bind(), safe_socket() and a few
more in the code.

So either we get rid of the error checks and of the error
propagation or we avoid using safe_ variants.

Other than that the code looks sane but it's hard to review the API
without an example that would excersize it. What about adding something
simple in newlib_tests?

> +	memset(buf + ctx->bufsize, 0, size - ctx->bufsize);
> +	offset = ((char *)ctx->curmsg) - ctx->buffer;
> +	ctx->buffer = buf;
> +	ctx->curmsg = (struct nlmsghdr *)(buf + offset);
> +	ctx->bufsize = size;
> +	return 1;
> +}
> +
> +struct tst_rtnl_context *tst_rtnl_create_context(const char *file,
> +	const int lineno)
> +{
> +	struct tst_rtnl_context *ctx;
> +	struct sockaddr_nl addr = {0};
> +
> +	ctx = safe_malloc(file, lineno, NULL, sizeof(struct tst_rtnl_context));
> +
> +	if (!ctx)
> +		return NULL;
> +
> +	ctx->pid = 0;
> +	ctx->seq = 0;
> +	ctx->bufsize = 1024;
> +	ctx->datalen = 0;
> +	ctx->curmsg = NULL;
> +	ctx->socket = safe_socket(file, lineno, NULL, AF_NETLINK,
> +		SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_ROUTE);
> +	addr.nl_family = AF_NETLINK;
> +
> +	if (ctx->socket < 0) {
> +		free(ctx);
> +		return NULL;
> +	}
> +
> +	if (safe_bind(file, lineno, NULL, ctx->socket, (struct sockaddr *)&addr,
> +		sizeof(addr))) {
> +		free(ctx);
> +		return NULL;
> +	}
> +
> +	ctx->buffer = safe_malloc(file, lineno, NULL, ctx->bufsize);
> +
> +	if (!ctx->buffer) {
> +		safe_close(file, lineno, NULL, ctx->socket);
> +		free(ctx);
> +		return NULL;
> +	}
> +
> +	memset(ctx->buffer, 0, ctx->bufsize);
> +	return ctx;
> +}
> +
> +void tst_rtnl_free_message(struct tst_rtnl_message *msg)
> +{
> +	if (!msg)
> +		return;
> +
> +	// all ptr->header and ptr->info pointers point to the same buffer
> +	// msg->header is the start of the buffer
> +	free(msg->header);
> +	free(msg);
> +}
> +
> +void tst_rtnl_free_context(const char *file, const int lineno,
> +	struct tst_rtnl_context *ctx)

This should be probably called destroy_context() but that is very minor.

> +{
> +	safe_close(file, lineno, NULL, ctx->socket);
> +	free(ctx->buffer);
> +	free(ctx);
> +}
> +
> +int tst_rtnl_send(const char *file, const int lineno,
> +	struct tst_rtnl_context *ctx)
> +{
> +	struct sockaddr_nl addr = {0};
> +	struct iovec iov;
> +	struct msghdr msg = {0};
> +	int ret;
> +
> +	if (!ctx->curmsg) {
> +		tst_brk_(file, lineno, TBROK, "%s(): No message to send",
> +			__func__);
> +		return 0;
> +	}
> +
> +	if (ctx->curmsg->nlmsg_flags & NLM_F_MULTI) {
> +		size_t size = NLMSG_ALIGN(ctx->curmsg->nlmsg_len);
> +
> +		if (!tst_rtnl_grow_buffer(file, lineno, ctx, NLMSG_SPACE(0)))
> +			return 0;
> +
> +		ctx->curmsg = NLMSG_NEXT(ctx->curmsg, size);
> +		memset(ctx->curmsg, 0, sizeof(struct nlmsghdr));
> +		ctx->curmsg->nlmsg_len = NLMSG_LENGTH(0);
> +		ctx->curmsg->nlmsg_type = NLMSG_DONE;
> +		ctx->curmsg->nlmsg_flags = 0;
> +		ctx->curmsg->nlmsg_seq = ctx->seq++;
> +		ctx->curmsg->nlmsg_pid = ctx->pid;
> +		ctx->datalen = NLMSG_ALIGN(ctx->datalen) + NLMSG_LENGTH(0);
> +	}
> +
> +	addr.nl_family = AF_NETLINK;
> +	iov.iov_base = ctx->buffer;
> +	iov.iov_len = ctx->datalen;
> +	msg.msg_name = &addr;
> +	msg.msg_namelen = sizeof(addr);
> +	msg.msg_iov = &iov;
> +	msg.msg_iovlen = 1;
> +
> +	ret = safe_sendmsg(file, lineno, ctx->datalen, ctx->socket, &msg, 0);
> +
> +	if (ret > 0)
> +		ctx->curmsg = NULL;
> +
> +	return ret;
> +}
> +
> +int tst_rtnl_wait(struct tst_rtnl_context *ctx)
> +{
> +	fd_set fdlist;
> +	struct timeval timeout = {0};
> +
> +	FD_ZERO(&fdlist);
> +	FD_SET(ctx->socket, &fdlist);
> +	timeout.tv_sec = 1;
> +
> +	return select(ctx->socket + 1, &fdlist, NULL, NULL, &timeout);

I find the poll() syscall to have a bit saner API than this.

> +}
> +
> +struct tst_rtnl_message *tst_rtnl_recv(const char *file, const int lineno,
> +	struct tst_rtnl_context *ctx)
> +{
> +	char *buffer, tmp;
> +	struct tst_rtnl_message *ret;
> +	struct nlmsghdr *ptr;
> +	ssize_t size;
> +	int i, size_left, msgcount;
> +
> +	errno = 0;
> +	size = recv(ctx->socket, &tmp, 1, MSG_DONTWAIT | MSG_PEEK | MSG_TRUNC);
> +
> +	if (size <= 0) {
> +		if (errno != EAGAIN)
> +			tst_brk_(file, lineno, TBROK | TERRNO, "recv() failed");
> +		return NULL;
> +	}
> +
> +	buffer = safe_malloc(file, lineno, NULL, size);
> +
> +	if (!buffer)
> +		return NULL;
> +
> +	size = safe_recv(file, lineno, size, ctx->socket, buffer, size, 0);
> +
> +	if (size <= 0) {
> +		free(buffer);
> +		return NULL;
> +	}
> +
> +	ptr = (struct nlmsghdr *)buffer;
> +	size_left = size;
> +	msgcount = 0;
> +
> +	for (; size_left > 0 && NLMSG_OK(ptr, size_left); msgcount++)
> +		ptr = NLMSG_NEXT(ptr, size_left);
> +
> +	ret = safe_malloc(file, lineno, NULL,
> +		(msgcount + 1) * sizeof(struct tst_rtnl_message));
> +
> +	if (!ret) {
> +		free(buffer);
> +		return NULL;
> +	}
> +
> +	memset(ret, 0, (msgcount + 1) * sizeof(struct tst_rtnl_message));
> +	ptr = (struct nlmsghdr *)buffer;
> +	size_left = size;
> +
> +	for (i = 0; i < msgcount; i++, ptr = NLMSG_NEXT(ptr, size_left)) {
> +		ret[i].header = ptr;
> +		ret[i].payload = NLMSG_DATA(ptr);
> +		ret[i].payload_size = NLMSG_PAYLOAD(ptr, 0);
> +
> +		if (ptr->nlmsg_type == NLMSG_ERROR)
> +			ret[i].err = NLMSG_DATA(ptr);
> +	}
> +
> +	return ret;
> +}
> +
> +int tst_rtnl_add_message(const char *file, const int lineno,
> +	struct tst_rtnl_context *ctx, const struct nlmsghdr *header,
> +	const void *payload, size_t payload_size)
> +{
> +	size_t size;
> +	unsigned int extra_flags = 0;
> +
> +	if (!tst_rtnl_grow_buffer(file, lineno, ctx, NLMSG_SPACE(payload_size)))
> +		return 0;
> +
> +	if (!ctx->curmsg) {
> +		/*
> +		 * datalen may hold the size of last sent message for ACK
> +		 * checking, reset it back to 0 here
> +		 */
> +		ctx->datalen = 0;
> +		ctx->curmsg = (struct nlmsghdr *)ctx->buffer;
> +	} else {
> +		size = NLMSG_ALIGN(ctx->curmsg->nlmsg_len);
> +
> +		extra_flags = NLM_F_MULTI;
> +		ctx->curmsg->nlmsg_flags |= extra_flags;
> +		ctx->curmsg = NLMSG_NEXT(ctx->curmsg, size);
> +		ctx->datalen = NLMSG_ALIGN(ctx->datalen);
> +	}
> +
> +	*ctx->curmsg = *header;
> +	ctx->curmsg->nlmsg_len = NLMSG_LENGTH(payload_size);
> +	ctx->curmsg->nlmsg_flags |= extra_flags;
> +	ctx->curmsg->nlmsg_seq = ctx->seq++;
> +	ctx->curmsg->nlmsg_pid = ctx->pid;
> +	memcpy(NLMSG_DATA(ctx->curmsg), payload, payload_size);
> +	ctx->datalen += ctx->curmsg->nlmsg_len;
> +	return 1;
> +}
> +
> +int tst_rtnl_add_attr(const char *file, const int lineno,
> +	struct tst_rtnl_context *ctx, unsigned short type,
> +	const void *data, unsigned short len)
> +{
> +	size_t size;
> +	struct rtattr *attr;
> +
> +	if (!ctx->curmsg) {
> +		tst_brk_(file, lineno, TBROK,
> +			"%s(): No message to add attributes to", __func__);
> +		return 0;
> +	}
> +
> +	if (!tst_rtnl_grow_buffer(file, lineno, ctx, RTA_SPACE(len)))
> +		return 0;
> +
> +	size = NLMSG_ALIGN(ctx->curmsg->nlmsg_len);
> +	attr = (struct rtattr *)(((char *)ctx->curmsg) + size);
> +	attr->rta_type = type;
> +	attr->rta_len = RTA_LENGTH(len);
> +	memcpy(RTA_DATA(attr), data, len);
> +	ctx->curmsg->nlmsg_len = size + attr->rta_len;
> +	ctx->datalen = NLMSG_ALIGN(ctx->datalen) + attr->rta_len;
> +	return 1;
> +}
> +
> +int tst_rtnl_add_attr_string(const char *file, const int lineno,
> +	struct tst_rtnl_context *ctx, unsigned short type,
> +	const char *data)
> +{
> +	return tst_rtnl_add_attr(file, lineno, ctx, type, data,
> +		strlen(data) + 1);
> +}
> +
> +int tst_rtnl_add_attr_list(const char *file, const int lineno,
> +	struct tst_rtnl_context *ctx,
> +	const struct tst_rtnl_attr_list *list)
> +{
> +	int i, ret;
> +	size_t offset;
> +
> +	for (i = 0; list[i].len >= 0; i++) {
> +		if (list[i].len > USHRT_MAX) {
> +			tst_brk_(file, lineno, TBROK,
> +				"%s(): Attribute value too long", __func__);
> +			return -1;
> +		}
> +
> +		offset = NLMSG_ALIGN(ctx->datalen);
> +		ret = tst_rtnl_add_attr(file, lineno, ctx, list[i].type,
> +			list[i].data, list[i].len);
> +
> +		if (!ret)
> +			return -1;
> +
> +		if (list[i].sublist) {
> +			struct rtattr *attr;
> +
> +			ret = tst_rtnl_add_attr_list(file, lineno, ctx,
> +				list[i].sublist);
> +
> +			if (ret < 0)
> +				return ret;
> +
> +			attr = (struct rtattr *)(ctx->buffer + offset);
> +
> +			if (ctx->datalen - offset > USHRT_MAX) {
> +				tst_brk_(file, lineno, TBROK,
> +					"%s(): Sublist too long", __func__);
> +				return -1;
> +			}
> +
> +			attr->rta_len = ctx->datalen - offset;
> +		}
> +	}
> +
> +	return i;
> +}
> +
> +int tst_rtnl_check_acks(const char *file, const int lineno,
> +	struct tst_rtnl_context *ctx, struct tst_rtnl_message *res)
> +{
> +	struct nlmsghdr *msg = (struct nlmsghdr *)ctx->buffer;
> +	int size_left = ctx->datalen;
> +
> +	for (; size_left > 0 && NLMSG_OK(msg, size_left);
> +		msg = NLMSG_NEXT(msg, size_left)) {
> +
> +		if (!(msg->nlmsg_flags & NLM_F_ACK))
> +			continue;
> +
> +		while (res->header && res->header->nlmsg_seq != msg->nlmsg_seq)
> +			res++;
> +
> +		if (!res->err || res->header->nlmsg_seq != msg->nlmsg_seq) {
> +			tst_brk_(file, lineno, TBROK,
> +				"No ACK found for Netlink message %u",
> +				msg->nlmsg_seq);
> +			return 0;
> +		}
> +
> +		if (res->err->error) {
> +			TST_ERR = -res->err->error;
> +			return 0;
> +		}
> +	}
> +
> +	return 1;
> +}
> +
> +int tst_rtnl_send_validate(const char *file, const int lineno,
> +	struct tst_rtnl_context *ctx)
> +{
> +	struct tst_rtnl_message *response;
> +	int ret;
> +
> +	TST_ERR = 0;
> +
> +	if (tst_rtnl_send(file, lineno, ctx) <= 0)
> +		return 0;
> +
> +	tst_rtnl_wait(ctx);
> +	response = tst_rtnl_recv(file, lineno, ctx);
> +
> +	if (!response)
> +		return 0;
> +
> +	ret = tst_rtnl_check_acks(file, lineno, ctx, response);
> +	tst_rtnl_free_message(response);
> +	return ret;
> +}
> -- 
> 2.31.1
> 
> 
> -- 
> Mailing list info: https://lists.linux.it/listinfo/ltp
Martin Doucha April 27, 2021, 2:14 p.m. UTC | #2
On 27. 04. 21 15:41, Cyril Hrubis wrote:
> Hi!
>> +static int tst_rtnl_grow_buffer(const char *file, const int lineno,
>> +	struct tst_rtnl_context *ctx, size_t size)
>> +{
>> +	size_t needed, offset, curlen = NLMSG_ALIGN(ctx->datalen);
>> +	char *buf;
>> +
>> +	if (ctx->bufsize - curlen >= size)
>> +		return 1;
>> +
>> +	needed = size - (ctx->bufsize - curlen);
>> +	size = ctx->bufsize + (ctx->bufsize > needed ? ctx->bufsize : needed);
>> +	size = NLMSG_ALIGN(size);
>> +	buf = safe_realloc(file, lineno, ctx->buffer, size);
>> +
>> +	if (!buf)
>> +		return 0;
> 
> You are calling safe_realloc() here yet you check the return value. And
> it's the same for safe_malloc(), safe_bind(), safe_socket() and a few
> more in the code.
> 
> So either we get rid of the error checks and of the error
> propagation or we avoid using safe_ variants.

Both rtnetlink and netdevice management functions will be called in
cleanup() so I have to assume that the safe functions will only print a
warning instead of terminating the program. But I still want to use the
error reporting code in the safe functions.

> Other than that the code looks sane but it's hard to review the API
> without an example that would excersize it. What about adding something
> simple in newlib_tests?

There are short examples for both rtnetlink and netdevice management in
the cover letter. The netdevice library itself is also a detailed
example for the rtnetlink API. The final patchset will include the
SADDNS CVE test which will use most of the netdevice management
functions in setup().

>> +void tst_rtnl_free_context(const char *file, const int lineno,
>> +	struct tst_rtnl_context *ctx)
> 
> This should be probably called destroy_context() but that is very minor.

OK, why not.

>> +int tst_rtnl_wait(struct tst_rtnl_context *ctx)
>> +{
>> +	fd_set fdlist;
>> +	struct timeval timeout = {0};
>> +
>> +	FD_ZERO(&fdlist);
>> +	FD_SET(ctx->socket, &fdlist);
>> +	timeout.tv_sec = 1;
>> +
>> +	return select(ctx->socket + 1, &fdlist, NULL, NULL, &timeout);
> 
> I find the poll() syscall to have a bit saner API than this.

I'll have a look at it.
Cyril Hrubis April 27, 2021, 2:35 p.m. UTC | #3
Hi!
> > You are calling safe_realloc() here yet you check the return value. And
> > it's the same for safe_malloc(), safe_bind(), safe_socket() and a few
> > more in the code.
> > 
> > So either we get rid of the error checks and of the error
> > propagation or we avoid using safe_ variants.
> 
> Both rtnetlink and netdevice management functions will be called in
> cleanup() so I have to assume that the safe functions will only print a
> warning instead of terminating the program. But I still want to use the
> error reporting code in the safe functions.

Right, I've realized that too, we have to return to the caller in that
case to carry on with the cleanup.

> > Other than that the code looks sane but it's hard to review the API
> > without an example that would excersize it. What about adding something
> > simple in newlib_tests?
> 
> There are short examples for both rtnetlink and netdevice management in
> the cover letter. The netdevice library itself is also a detailed
> example for the rtnetlink API. The final patchset will include the
> SADDNS CVE test which will use most of the netdevice management
> functions in setup().

Ah I've missed that. We should add it somewhere to the doc/ directory
since the cover letter would end up burried in the mailing list history.
Cyril Hrubis April 27, 2021, 3:44 p.m. UTC | #4
Hi!
> This library provides simple interface for creating arbitrary rtnetlink
> messages with complex attributes, sending requests and receiving results.
> 
> Signed-off-by: Martin Doucha <mdoucha@suse.cz>
> ---
>  include/tst_rtnetlink.h | 105 +++++++++++
>  lib/tst_rtnetlink.c     | 399 ++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 504 insertions(+)
>  create mode 100644 include/tst_rtnetlink.h
>  create mode 100644 lib/tst_rtnetlink.c
> 
> diff --git a/include/tst_rtnetlink.h b/include/tst_rtnetlink.h
> new file mode 100644
> index 000000000..3b307fbec
> --- /dev/null
> +++ b/include/tst_rtnetlink.h
> @@ -0,0 +1,105 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later
> + * Copyright (c) 2021 Linux Test Project
> + */
> +
> +#ifndef TST_RTNETLINK_H
> +#define TST_RTNETLINK_H
> +
> +struct tst_rtnl_context;
> +
> +struct tst_rtnl_attr_list {
> +	unsigned short type;
> +	const void *data;
> +	ssize_t len;
> +	const struct tst_rtnl_attr_list *sublist;
> +};
> +
> +struct tst_rtnl_message {
> +	struct nlmsghdr *header;
> +	struct nlmsgerr *err;
> +	void *payload;
> +	size_t payload_size;
> +};
> +
> +/* Open a netlink socket */
> +struct tst_rtnl_context *tst_rtnl_create_context(const char *file,
> +	const int lineno);
> +#define RTNL_CREATE_CONTEXT() tst_rtnl_create_context(__FILE__, __LINE__)
> +
> +/* Free a tst_rtnl_message array returned by tst_rtnl_recv() */
> +void tst_rtnl_free_message(struct tst_rtnl_message *msg);
> +#define RTNL_FREE_MESSAGE tst_rtnl_free_message
> +
> +/* Close netlink socket */
> +void tst_rtnl_free_context(const char *file, const int lineno,
> +	struct tst_rtnl_context *ctx);
> +#define RTNL_FREE_CONTEXT(ctx) tst_rtnl_free_context(__FILE__, __LINE__, (ctx))
> +
> +/* Send all messages in given buffer */
> +int tst_rtnl_send(const char *file, const int lineno,
> +	struct tst_rtnl_context *ctx);
> +#define RTNL_SEND(ctx) tst_rtnl_send(__FILE__, __LINE__, (ctx))
> +
> +/* Send all messages in given buffer and validate kernel response */
> +int tst_rtnl_send_validate(const char *file, const int lineno,
> +	struct tst_rtnl_context *ctx);
> +#define RTNL_SEND_VALIDATE(ctx) \
> +	tst_rtnl_send_validate(__FILE__, __LINE__, (ctx))
> +
> +/* Wait until data is available for reading from the netlink socket */
> +int tst_rtnl_wait(struct tst_rtnl_context *ctx);
> +#define RTNL_WAIT tst_rtnl_wait
> +
> +/*
> + * Read from netlink socket and return an array of partially parsed messages.
> + * header == NULL indicates end of array.
> + */
> +struct tst_rtnl_message *tst_rtnl_recv(const char *file, const int lineno,
> +	struct tst_rtnl_context *ctx);
> +#define RTNL_RECV(ctx) tst_rtnl_recv(__FILE__, __LINE__, (ctx))
> +
> +/* Add new message to buffer */
> +int tst_rtnl_add_message(const char *file, const int lineno,
> +	struct tst_rtnl_context *ctx, const struct nlmsghdr *header,
> +	const void *payload, size_t payload_size);
> +#define RTNL_ADD_MESSAGE(ctx, header, payload, psize) \
> +	tst_rtnl_add_message(__FILE__, __LINE__, (ctx), (header), (payload), \
> +		(psize))
> +
> +/* Add arbitrary attribute to last message */
> +int tst_rtnl_add_attr(const char *file, const int lineno,
> +	struct tst_rtnl_context *ctx, unsigned short type, const void *data,
> +	unsigned short len);
> +#define RTNL_ADD_ATTR(ctx, type, data, len) \
> +	tst_rtnl_add_attr(__FILE__, __LINE__, (ctx), (type), (data), (len))
> +
> +/* Add string attribute to last message */
> +int tst_rtnl_add_attr_string(const char *file, const int lineno,
> +	struct tst_rtnl_context *ctx, unsigned short type, const char *data);
> +#define RTNL_ADD_ATTR_STRING(ctx, type, data) \
> +	tst_rtnl_add_attr_string(__FILE__, __LINE__, (ctx), (type), (data))
> +
> +/*
> + * Add list of arbitrary attributes to last message. The list is terminated
> + * by attribute with negative length. Nested sublists are supported.
> + */
> +int tst_rtnl_add_attr_list(const char *file, const int lineno,
> +	struct tst_rtnl_context *ctx, const struct tst_rtnl_attr_list *list);
> +#define RTNL_ADD_ATTR_LIST(ctx, list) \
> +	tst_rtnl_add_attr_list(__FILE__, __LINE__, (ctx), (list))
> +
> +/* Check that all sent messages with NLM_F_ACK flag have been acked without
> + * error. Usage:
> + *
> + * tst_rtnl_send(ctx);
> + * tst_rtnl_wait(ctx);
> + * response = tst_rtnl_recv(ctx);
> + * if (!tst_rtnl_check_acks(ctx, response)) { ... }
> + * tst_rtnl_free_message(response);
> + */
> +int tst_rtnl_check_acks(const char *file, const int lineno,
> +	struct tst_rtnl_context *ctx, struct tst_rtnl_message *response);
> +#define RTNL_CHECK_ACKS(ctx, response) \
> +	tst_rtnl_context(__FILE__, __LINE__, (ctx), (response))
> +
> +#endif /* TST_RTNETLINK_H */
> diff --git a/lib/tst_rtnetlink.c b/lib/tst_rtnetlink.c
> new file mode 100644
> index 000000000..bb6116d57
> --- /dev/null
> +++ b/lib/tst_rtnetlink.c
> @@ -0,0 +1,399 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (c) 2021 Linux Test Project
> + */
> +
> +#include <stdlib.h>
> +#include <limits.h>
> +#include <asm/types.h>
> +#include <linux/netlink.h>
> +#include <linux/rtnetlink.h>
> +#include <sys/types.h>
> +#include <sys/socket.h>
> +#include <sys/select.h>
> +#define TST_NO_DEFAULT_MAIN
> +#include "tst_test.h"
> +#include "tst_rtnetlink.h"
> +
> +struct tst_rtnl_context {
> +	int socket;
> +	pid_t pid;
> +	uint32_t seq;
> +	size_t bufsize, datalen;
> +	char *buffer;
> +	struct nlmsghdr *curmsg;
> +};
> +
> +static int tst_rtnl_grow_buffer(const char *file, const int lineno,
> +	struct tst_rtnl_context *ctx, size_t size)
> +{
> +	size_t needed, offset, curlen = NLMSG_ALIGN(ctx->datalen);
> +	char *buf;
> +
> +	if (ctx->bufsize - curlen >= size)
> +		return 1;
> +
> +	needed = size - (ctx->bufsize - curlen);
> +	size = ctx->bufsize + (ctx->bufsize > needed ? ctx->bufsize : needed);
> +	size = NLMSG_ALIGN(size);
> +	buf = safe_realloc(file, lineno, ctx->buffer, size);
> +
> +	if (!buf)
> +		return 0;
> +
> +	memset(buf + ctx->bufsize, 0, size - ctx->bufsize);
> +	offset = ((char *)ctx->curmsg) - ctx->buffer;
> +	ctx->buffer = buf;
> +	ctx->curmsg = (struct nlmsghdr *)(buf + offset);
> +	ctx->bufsize = size;
> +	return 1;
> +}
> +
> +struct tst_rtnl_context *tst_rtnl_create_context(const char *file,
> +	const int lineno)
> +{
> +	struct tst_rtnl_context *ctx;
> +	struct sockaddr_nl addr = {0};
> +
> +	ctx = safe_malloc(file, lineno, NULL, sizeof(struct tst_rtnl_context));
> +
> +	if (!ctx)
> +		return NULL;
> +
> +	ctx->pid = 0;
> +	ctx->seq = 0;
> +	ctx->bufsize = 1024;
> +	ctx->datalen = 0;
> +	ctx->curmsg = NULL;
> +	ctx->socket = safe_socket(file, lineno, NULL, AF_NETLINK,
> +		SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_ROUTE);
> +	addr.nl_family = AF_NETLINK;
> +
> +	if (ctx->socket < 0) {
> +		free(ctx);
> +		return NULL;
> +	}
> +
> +	if (safe_bind(file, lineno, NULL, ctx->socket, (struct sockaddr *)&addr,
> +		sizeof(addr))) {

safe_close() is missing here.

Also why don't we use the kernel style goto error handling here, that
would be probably easier to write. I.e.

	if (ctx->socket < 0)
		goto err0;

	if (safe_bind(...))
		goto err1;

	...

	return ctx;
err1:
	safe_close(file, lineno, NULL, ctx->socket);
err0:
	free(ctx);
	return NULL;

> +		free(ctx);
> +		return NULL;
> +	}
> +
> +	ctx->buffer = safe_malloc(file, lineno, NULL, ctx->bufsize);
> +
> +	if (!ctx->buffer) {
> +		safe_close(file, lineno, NULL, ctx->socket);
> +		free(ctx);
> +		return NULL;
> +	}
> +
> +	memset(ctx->buffer, 0, ctx->bufsize);
> +	return ctx;
> +}
> +
> +void tst_rtnl_free_message(struct tst_rtnl_message *msg)
> +{
> +	if (!msg)
> +		return;
> +
> +	// all ptr->header and ptr->info pointers point to the same buffer
> +	// msg->header is the start of the buffer
> +	free(msg->header);
> +	free(msg);
> +}
> +
> +void tst_rtnl_free_context(const char *file, const int lineno,
> +	struct tst_rtnl_context *ctx)
> +{
> +	safe_close(file, lineno, NULL, ctx->socket);
> +	free(ctx->buffer);
> +	free(ctx);
> +}
> +
> +int tst_rtnl_send(const char *file, const int lineno,
> +	struct tst_rtnl_context *ctx)
> +{
> +	struct sockaddr_nl addr = {0};
> +	struct iovec iov;
> +	struct msghdr msg = {0};
> +	int ret;
> +
> +	if (!ctx->curmsg) {
> +		tst_brk_(file, lineno, TBROK, "%s(): No message to send",
> +			__func__);
> +		return 0;
> +	}
> +
> +	if (ctx->curmsg->nlmsg_flags & NLM_F_MULTI) {
> +		size_t size = NLMSG_ALIGN(ctx->curmsg->nlmsg_len);
> +
> +		if (!tst_rtnl_grow_buffer(file, lineno, ctx, NLMSG_SPACE(0)))
> +			return 0;
> +
> +		ctx->curmsg = NLMSG_NEXT(ctx->curmsg, size);
> +		memset(ctx->curmsg, 0, sizeof(struct nlmsghdr));
> +		ctx->curmsg->nlmsg_len = NLMSG_LENGTH(0);
> +		ctx->curmsg->nlmsg_type = NLMSG_DONE;
> +		ctx->curmsg->nlmsg_flags = 0;
> +		ctx->curmsg->nlmsg_seq = ctx->seq++;
> +		ctx->curmsg->nlmsg_pid = ctx->pid;
> +		ctx->datalen = NLMSG_ALIGN(ctx->datalen) + NLMSG_LENGTH(0);

If we add one if (lenth) before the memcpy() we can just call
tst_rtnl_add_message(file, lineno, ctx, &header, NULL, 0) here instead.

> +	}
> +	addr.nl_family = AF_NETLINK;
> +	iov.iov_base = ctx->buffer;
> +	iov.iov_len = ctx->datalen;
> +	msg.msg_name = &addr;
> +	msg.msg_namelen = sizeof(addr);
> +	msg.msg_iov = &iov;
> +	msg.msg_iovlen = 1;
> +
> +	ret = safe_sendmsg(file, lineno, ctx->datalen, ctx->socket, &msg, 0);
> +
> +	if (ret > 0)
> +		ctx->curmsg = NULL;
> +
> +	return ret;
> +}
> +
> +int tst_rtnl_wait(struct tst_rtnl_context *ctx)
> +{
> +	fd_set fdlist;
> +	struct timeval timeout = {0};
> +
> +	FD_ZERO(&fdlist);
> +	FD_SET(ctx->socket, &fdlist);
> +	timeout.tv_sec = 1;
> +
> +	return select(ctx->socket + 1, &fdlist, NULL, NULL, &timeout);
> +}
> +
> +struct tst_rtnl_message *tst_rtnl_recv(const char *file, const int lineno,
> +	struct tst_rtnl_context *ctx)
> +{
> +	char *buffer, tmp;
> +	struct tst_rtnl_message *ret;
> +	struct nlmsghdr *ptr;
> +	ssize_t size;
> +	int i, size_left, msgcount;
> +
> +	errno = 0;
> +	size = recv(ctx->socket, &tmp, 1, MSG_DONTWAIT | MSG_PEEK | MSG_TRUNC);
> +
> +	if (size <= 0) {
> +		if (errno != EAGAIN)
> +			tst_brk_(file, lineno, TBROK | TERRNO, "recv() failed");
> +		return NULL;
> +	}
> +
> +	buffer = safe_malloc(file, lineno, NULL, size);
> +
> +	if (!buffer)
> +		return NULL;
> +
> +	size = safe_recv(file, lineno, size, ctx->socket, buffer, size, 0);
> +
> +	if (size <= 0) {
> +		free(buffer);
> +		return NULL;
> +	}
> +
> +	ptr = (struct nlmsghdr *)buffer;
> +	size_left = size;
> +	msgcount = 0;
> +
> +	for (; size_left > 0 && NLMSG_OK(ptr, size_left); msgcount++)
> +		ptr = NLMSG_NEXT(ptr, size_left);
> +
> +	ret = safe_malloc(file, lineno, NULL,
> +		(msgcount + 1) * sizeof(struct tst_rtnl_message));
                     ^
		     Maybe store this size to a variable so that we
		     don't have to repeat it for the memset()?
> +
> +	if (!ret) {
> +		free(buffer);
> +		return NULL;
> +	}
> +
> +	memset(ret, 0, (msgcount + 1) * sizeof(struct tst_rtnl_message));
> +	ptr = (struct nlmsghdr *)buffer;
> +	size_left = size;
> +
> +	for (i = 0; i < msgcount; i++, ptr = NLMSG_NEXT(ptr, size_left)) {
> +		ret[i].header = ptr;
> +		ret[i].payload = NLMSG_DATA(ptr);
> +		ret[i].payload_size = NLMSG_PAYLOAD(ptr, 0);
> +
> +		if (ptr->nlmsg_type == NLMSG_ERROR)
> +			ret[i].err = NLMSG_DATA(ptr);
> +	}
> +
> +	return ret;
> +}
> +
> +int tst_rtnl_add_message(const char *file, const int lineno,
> +	struct tst_rtnl_context *ctx, const struct nlmsghdr *header,
> +	const void *payload, size_t payload_size)
> +{
> +	size_t size;
> +	unsigned int extra_flags = 0;
> +
> +	if (!tst_rtnl_grow_buffer(file, lineno, ctx, NLMSG_SPACE(payload_size)))
> +		return 0;
> +
> +	if (!ctx->curmsg) {
> +		/*
> +		 * datalen may hold the size of last sent message for ACK
> +		 * checking, reset it back to 0 here
> +		 */
> +		ctx->datalen = 0;
> +		ctx->curmsg = (struct nlmsghdr *)ctx->buffer;
> +	} else {
> +		size = NLMSG_ALIGN(ctx->curmsg->nlmsg_len);
> +
> +		extra_flags = NLM_F_MULTI;
> +		ctx->curmsg->nlmsg_flags |= extra_flags;
> +		ctx->curmsg = NLMSG_NEXT(ctx->curmsg, size);
> +		ctx->datalen = NLMSG_ALIGN(ctx->datalen);
> +	}
> +
> +	*ctx->curmsg = *header;
> +	ctx->curmsg->nlmsg_len = NLMSG_LENGTH(payload_size);
> +	ctx->curmsg->nlmsg_flags |= extra_flags;
> +	ctx->curmsg->nlmsg_seq = ctx->seq++;
> +	ctx->curmsg->nlmsg_pid = ctx->pid;
> +	memcpy(NLMSG_DATA(ctx->curmsg), payload, payload_size);
> +	ctx->datalen += ctx->curmsg->nlmsg_len;
> +	return 1;
> +}
> +
> +int tst_rtnl_add_attr(const char *file, const int lineno,
> +	struct tst_rtnl_context *ctx, unsigned short type,
> +	const void *data, unsigned short len)
> +{
> +	size_t size;
> +	struct rtattr *attr;
> +
> +	if (!ctx->curmsg) {
> +		tst_brk_(file, lineno, TBROK,
> +			"%s(): No message to add attributes to", __func__);
> +		return 0;
> +	}
> +
> +	if (!tst_rtnl_grow_buffer(file, lineno, ctx, RTA_SPACE(len)))
> +		return 0;
> +
> +	size = NLMSG_ALIGN(ctx->curmsg->nlmsg_len);
> +	attr = (struct rtattr *)(((char *)ctx->curmsg) + size);
> +	attr->rta_type = type;
> +	attr->rta_len = RTA_LENGTH(len);
> +	memcpy(RTA_DATA(attr), data, len);
> +	ctx->curmsg->nlmsg_len = size + attr->rta_len;
> +	ctx->datalen = NLMSG_ALIGN(ctx->datalen) + attr->rta_len;
> +	return 1;
> +}
> +
> +int tst_rtnl_add_attr_string(const char *file, const int lineno,
> +	struct tst_rtnl_context *ctx, unsigned short type,
> +	const char *data)
> +{
> +	return tst_rtnl_add_attr(file, lineno, ctx, type, data,
> +		strlen(data) + 1);
> +}
> +
> +int tst_rtnl_add_attr_list(const char *file, const int lineno,
> +	struct tst_rtnl_context *ctx,
> +	const struct tst_rtnl_attr_list *list)
> +{
> +	int i, ret;
> +	size_t offset;
> +
> +	for (i = 0; list[i].len >= 0; i++) {
> +		if (list[i].len > USHRT_MAX) {
> +			tst_brk_(file, lineno, TBROK,
> +				"%s(): Attribute value too long", __func__);
> +			return -1;
> +		}
> +
> +		offset = NLMSG_ALIGN(ctx->datalen);
> +		ret = tst_rtnl_add_attr(file, lineno, ctx, list[i].type,
> +			list[i].data, list[i].len);
> +
> +		if (!ret)
> +			return -1;
> +
> +		if (list[i].sublist) {
> +			struct rtattr *attr;
> +
> +			ret = tst_rtnl_add_attr_list(file, lineno, ctx,
> +				list[i].sublist);
> +
> +			if (ret < 0)
> +				return ret;
> +
> +			attr = (struct rtattr *)(ctx->buffer + offset);
> +
> +			if (ctx->datalen - offset > USHRT_MAX) {
> +				tst_brk_(file, lineno, TBROK,
> +					"%s(): Sublist too long", __func__);
> +				return -1;
> +			}
> +
> +			attr->rta_len = ctx->datalen - offset;
> +		}
> +	}
> +
> +	return i;
> +}
> +
> +int tst_rtnl_check_acks(const char *file, const int lineno,
> +	struct tst_rtnl_context *ctx, struct tst_rtnl_message *res)
> +{
> +	struct nlmsghdr *msg = (struct nlmsghdr *)ctx->buffer;
> +	int size_left = ctx->datalen;
> +
> +	for (; size_left > 0 && NLMSG_OK(msg, size_left);
> +		msg = NLMSG_NEXT(msg, size_left)) {
> +
> +		if (!(msg->nlmsg_flags & NLM_F_ACK))
> +			continue;
> +
> +		while (res->header && res->header->nlmsg_seq != msg->nlmsg_seq)
> +			res++;
> +
> +		if (!res->err || res->header->nlmsg_seq != msg->nlmsg_seq) {
> +			tst_brk_(file, lineno, TBROK,
> +				"No ACK found for Netlink message %u",
> +				msg->nlmsg_seq);
> +			return 0;
> +		}
> +
> +		if (res->err->error) {
> +			TST_ERR = -res->err->error;
> +			return 0;
> +		}
> +	}
> +
> +	return 1;
> +}
> +
> +int tst_rtnl_send_validate(const char *file, const int lineno,
> +	struct tst_rtnl_context *ctx)
> +{
> +	struct tst_rtnl_message *response;
> +	int ret;
> +
> +	TST_ERR = 0;
> +
> +	if (tst_rtnl_send(file, lineno, ctx) <= 0)
> +		return 0;
> +
> +	tst_rtnl_wait(ctx);
> +	response = tst_rtnl_recv(file, lineno, ctx);
> +
> +	if (!response)
> +		return 0;
> +
> +	ret = tst_rtnl_check_acks(file, lineno, ctx, response);
> +	tst_rtnl_free_message(response);
> +	return ret;
> +}

I had a closer look at the library and minus a few minor things it looks
good to me.
diff mbox series

Patch

diff --git a/include/tst_rtnetlink.h b/include/tst_rtnetlink.h
new file mode 100644
index 000000000..3b307fbec
--- /dev/null
+++ b/include/tst_rtnetlink.h
@@ -0,0 +1,105 @@ 
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright (c) 2021 Linux Test Project
+ */
+
+#ifndef TST_RTNETLINK_H
+#define TST_RTNETLINK_H
+
+struct tst_rtnl_context;
+
+struct tst_rtnl_attr_list {
+	unsigned short type;
+	const void *data;
+	ssize_t len;
+	const struct tst_rtnl_attr_list *sublist;
+};
+
+struct tst_rtnl_message {
+	struct nlmsghdr *header;
+	struct nlmsgerr *err;
+	void *payload;
+	size_t payload_size;
+};
+
+/* Open a netlink socket */
+struct tst_rtnl_context *tst_rtnl_create_context(const char *file,
+	const int lineno);
+#define RTNL_CREATE_CONTEXT() tst_rtnl_create_context(__FILE__, __LINE__)
+
+/* Free a tst_rtnl_message array returned by tst_rtnl_recv() */
+void tst_rtnl_free_message(struct tst_rtnl_message *msg);
+#define RTNL_FREE_MESSAGE tst_rtnl_free_message
+
+/* Close netlink socket */
+void tst_rtnl_free_context(const char *file, const int lineno,
+	struct tst_rtnl_context *ctx);
+#define RTNL_FREE_CONTEXT(ctx) tst_rtnl_free_context(__FILE__, __LINE__, (ctx))
+
+/* Send all messages in given buffer */
+int tst_rtnl_send(const char *file, const int lineno,
+	struct tst_rtnl_context *ctx);
+#define RTNL_SEND(ctx) tst_rtnl_send(__FILE__, __LINE__, (ctx))
+
+/* Send all messages in given buffer and validate kernel response */
+int tst_rtnl_send_validate(const char *file, const int lineno,
+	struct tst_rtnl_context *ctx);
+#define RTNL_SEND_VALIDATE(ctx) \
+	tst_rtnl_send_validate(__FILE__, __LINE__, (ctx))
+
+/* Wait until data is available for reading from the netlink socket */
+int tst_rtnl_wait(struct tst_rtnl_context *ctx);
+#define RTNL_WAIT tst_rtnl_wait
+
+/*
+ * Read from netlink socket and return an array of partially parsed messages.
+ * header == NULL indicates end of array.
+ */
+struct tst_rtnl_message *tst_rtnl_recv(const char *file, const int lineno,
+	struct tst_rtnl_context *ctx);
+#define RTNL_RECV(ctx) tst_rtnl_recv(__FILE__, __LINE__, (ctx))
+
+/* Add new message to buffer */
+int tst_rtnl_add_message(const char *file, const int lineno,
+	struct tst_rtnl_context *ctx, const struct nlmsghdr *header,
+	const void *payload, size_t payload_size);
+#define RTNL_ADD_MESSAGE(ctx, header, payload, psize) \
+	tst_rtnl_add_message(__FILE__, __LINE__, (ctx), (header), (payload), \
+		(psize))
+
+/* Add arbitrary attribute to last message */
+int tst_rtnl_add_attr(const char *file, const int lineno,
+	struct tst_rtnl_context *ctx, unsigned short type, const void *data,
+	unsigned short len);
+#define RTNL_ADD_ATTR(ctx, type, data, len) \
+	tst_rtnl_add_attr(__FILE__, __LINE__, (ctx), (type), (data), (len))
+
+/* Add string attribute to last message */
+int tst_rtnl_add_attr_string(const char *file, const int lineno,
+	struct tst_rtnl_context *ctx, unsigned short type, const char *data);
+#define RTNL_ADD_ATTR_STRING(ctx, type, data) \
+	tst_rtnl_add_attr_string(__FILE__, __LINE__, (ctx), (type), (data))
+
+/*
+ * Add list of arbitrary attributes to last message. The list is terminated
+ * by attribute with negative length. Nested sublists are supported.
+ */
+int tst_rtnl_add_attr_list(const char *file, const int lineno,
+	struct tst_rtnl_context *ctx, const struct tst_rtnl_attr_list *list);
+#define RTNL_ADD_ATTR_LIST(ctx, list) \
+	tst_rtnl_add_attr_list(__FILE__, __LINE__, (ctx), (list))
+
+/* Check that all sent messages with NLM_F_ACK flag have been acked without
+ * error. Usage:
+ *
+ * tst_rtnl_send(ctx);
+ * tst_rtnl_wait(ctx);
+ * response = tst_rtnl_recv(ctx);
+ * if (!tst_rtnl_check_acks(ctx, response)) { ... }
+ * tst_rtnl_free_message(response);
+ */
+int tst_rtnl_check_acks(const char *file, const int lineno,
+	struct tst_rtnl_context *ctx, struct tst_rtnl_message *response);
+#define RTNL_CHECK_ACKS(ctx, response) \
+	tst_rtnl_context(__FILE__, __LINE__, (ctx), (response))
+
+#endif /* TST_RTNETLINK_H */
diff --git a/lib/tst_rtnetlink.c b/lib/tst_rtnetlink.c
new file mode 100644
index 000000000..bb6116d57
--- /dev/null
+++ b/lib/tst_rtnetlink.c
@@ -0,0 +1,399 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2021 Linux Test Project
+ */
+
+#include <stdlib.h>
+#include <limits.h>
+#include <asm/types.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#define TST_NO_DEFAULT_MAIN
+#include "tst_test.h"
+#include "tst_rtnetlink.h"
+
+struct tst_rtnl_context {
+	int socket;
+	pid_t pid;
+	uint32_t seq;
+	size_t bufsize, datalen;
+	char *buffer;
+	struct nlmsghdr *curmsg;
+};
+
+static int tst_rtnl_grow_buffer(const char *file, const int lineno,
+	struct tst_rtnl_context *ctx, size_t size)
+{
+	size_t needed, offset, curlen = NLMSG_ALIGN(ctx->datalen);
+	char *buf;
+
+	if (ctx->bufsize - curlen >= size)
+		return 1;
+
+	needed = size - (ctx->bufsize - curlen);
+	size = ctx->bufsize + (ctx->bufsize > needed ? ctx->bufsize : needed);
+	size = NLMSG_ALIGN(size);
+	buf = safe_realloc(file, lineno, ctx->buffer, size);
+
+	if (!buf)
+		return 0;
+
+	memset(buf + ctx->bufsize, 0, size - ctx->bufsize);
+	offset = ((char *)ctx->curmsg) - ctx->buffer;
+	ctx->buffer = buf;
+	ctx->curmsg = (struct nlmsghdr *)(buf + offset);
+	ctx->bufsize = size;
+	return 1;
+}
+
+struct tst_rtnl_context *tst_rtnl_create_context(const char *file,
+	const int lineno)
+{
+	struct tst_rtnl_context *ctx;
+	struct sockaddr_nl addr = {0};
+
+	ctx = safe_malloc(file, lineno, NULL, sizeof(struct tst_rtnl_context));
+
+	if (!ctx)
+		return NULL;
+
+	ctx->pid = 0;
+	ctx->seq = 0;
+	ctx->bufsize = 1024;
+	ctx->datalen = 0;
+	ctx->curmsg = NULL;
+	ctx->socket = safe_socket(file, lineno, NULL, AF_NETLINK,
+		SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_ROUTE);
+	addr.nl_family = AF_NETLINK;
+
+	if (ctx->socket < 0) {
+		free(ctx);
+		return NULL;
+	}
+
+	if (safe_bind(file, lineno, NULL, ctx->socket, (struct sockaddr *)&addr,
+		sizeof(addr))) {
+		free(ctx);
+		return NULL;
+	}
+
+	ctx->buffer = safe_malloc(file, lineno, NULL, ctx->bufsize);
+
+	if (!ctx->buffer) {
+		safe_close(file, lineno, NULL, ctx->socket);
+		free(ctx);
+		return NULL;
+	}
+
+	memset(ctx->buffer, 0, ctx->bufsize);
+	return ctx;
+}
+
+void tst_rtnl_free_message(struct tst_rtnl_message *msg)
+{
+	if (!msg)
+		return;
+
+	// all ptr->header and ptr->info pointers point to the same buffer
+	// msg->header is the start of the buffer
+	free(msg->header);
+	free(msg);
+}
+
+void tst_rtnl_free_context(const char *file, const int lineno,
+	struct tst_rtnl_context *ctx)
+{
+	safe_close(file, lineno, NULL, ctx->socket);
+	free(ctx->buffer);
+	free(ctx);
+}
+
+int tst_rtnl_send(const char *file, const int lineno,
+	struct tst_rtnl_context *ctx)
+{
+	struct sockaddr_nl addr = {0};
+	struct iovec iov;
+	struct msghdr msg = {0};
+	int ret;
+
+	if (!ctx->curmsg) {
+		tst_brk_(file, lineno, TBROK, "%s(): No message to send",
+			__func__);
+		return 0;
+	}
+
+	if (ctx->curmsg->nlmsg_flags & NLM_F_MULTI) {
+		size_t size = NLMSG_ALIGN(ctx->curmsg->nlmsg_len);
+
+		if (!tst_rtnl_grow_buffer(file, lineno, ctx, NLMSG_SPACE(0)))
+			return 0;
+
+		ctx->curmsg = NLMSG_NEXT(ctx->curmsg, size);
+		memset(ctx->curmsg, 0, sizeof(struct nlmsghdr));
+		ctx->curmsg->nlmsg_len = NLMSG_LENGTH(0);
+		ctx->curmsg->nlmsg_type = NLMSG_DONE;
+		ctx->curmsg->nlmsg_flags = 0;
+		ctx->curmsg->nlmsg_seq = ctx->seq++;
+		ctx->curmsg->nlmsg_pid = ctx->pid;
+		ctx->datalen = NLMSG_ALIGN(ctx->datalen) + NLMSG_LENGTH(0);
+	}
+
+	addr.nl_family = AF_NETLINK;
+	iov.iov_base = ctx->buffer;
+	iov.iov_len = ctx->datalen;
+	msg.msg_name = &addr;
+	msg.msg_namelen = sizeof(addr);
+	msg.msg_iov = &iov;
+	msg.msg_iovlen = 1;
+
+	ret = safe_sendmsg(file, lineno, ctx->datalen, ctx->socket, &msg, 0);
+
+	if (ret > 0)
+		ctx->curmsg = NULL;
+
+	return ret;
+}
+
+int tst_rtnl_wait(struct tst_rtnl_context *ctx)
+{
+	fd_set fdlist;
+	struct timeval timeout = {0};
+
+	FD_ZERO(&fdlist);
+	FD_SET(ctx->socket, &fdlist);
+	timeout.tv_sec = 1;
+
+	return select(ctx->socket + 1, &fdlist, NULL, NULL, &timeout);
+}
+
+struct tst_rtnl_message *tst_rtnl_recv(const char *file, const int lineno,
+	struct tst_rtnl_context *ctx)
+{
+	char *buffer, tmp;
+	struct tst_rtnl_message *ret;
+	struct nlmsghdr *ptr;
+	ssize_t size;
+	int i, size_left, msgcount;
+
+	errno = 0;
+	size = recv(ctx->socket, &tmp, 1, MSG_DONTWAIT | MSG_PEEK | MSG_TRUNC);
+
+	if (size <= 0) {
+		if (errno != EAGAIN)
+			tst_brk_(file, lineno, TBROK | TERRNO, "recv() failed");
+		return NULL;
+	}
+
+	buffer = safe_malloc(file, lineno, NULL, size);
+
+	if (!buffer)
+		return NULL;
+
+	size = safe_recv(file, lineno, size, ctx->socket, buffer, size, 0);
+
+	if (size <= 0) {
+		free(buffer);
+		return NULL;
+	}
+
+	ptr = (struct nlmsghdr *)buffer;
+	size_left = size;
+	msgcount = 0;
+
+	for (; size_left > 0 && NLMSG_OK(ptr, size_left); msgcount++)
+		ptr = NLMSG_NEXT(ptr, size_left);
+
+	ret = safe_malloc(file, lineno, NULL,
+		(msgcount + 1) * sizeof(struct tst_rtnl_message));
+
+	if (!ret) {
+		free(buffer);
+		return NULL;
+	}
+
+	memset(ret, 0, (msgcount + 1) * sizeof(struct tst_rtnl_message));
+	ptr = (struct nlmsghdr *)buffer;
+	size_left = size;
+
+	for (i = 0; i < msgcount; i++, ptr = NLMSG_NEXT(ptr, size_left)) {
+		ret[i].header = ptr;
+		ret[i].payload = NLMSG_DATA(ptr);
+		ret[i].payload_size = NLMSG_PAYLOAD(ptr, 0);
+
+		if (ptr->nlmsg_type == NLMSG_ERROR)
+			ret[i].err = NLMSG_DATA(ptr);
+	}
+
+	return ret;
+}
+
+int tst_rtnl_add_message(const char *file, const int lineno,
+	struct tst_rtnl_context *ctx, const struct nlmsghdr *header,
+	const void *payload, size_t payload_size)
+{
+	size_t size;
+	unsigned int extra_flags = 0;
+
+	if (!tst_rtnl_grow_buffer(file, lineno, ctx, NLMSG_SPACE(payload_size)))
+		return 0;
+
+	if (!ctx->curmsg) {
+		/*
+		 * datalen may hold the size of last sent message for ACK
+		 * checking, reset it back to 0 here
+		 */
+		ctx->datalen = 0;
+		ctx->curmsg = (struct nlmsghdr *)ctx->buffer;
+	} else {
+		size = NLMSG_ALIGN(ctx->curmsg->nlmsg_len);
+
+		extra_flags = NLM_F_MULTI;
+		ctx->curmsg->nlmsg_flags |= extra_flags;
+		ctx->curmsg = NLMSG_NEXT(ctx->curmsg, size);
+		ctx->datalen = NLMSG_ALIGN(ctx->datalen);
+	}
+
+	*ctx->curmsg = *header;
+	ctx->curmsg->nlmsg_len = NLMSG_LENGTH(payload_size);
+	ctx->curmsg->nlmsg_flags |= extra_flags;
+	ctx->curmsg->nlmsg_seq = ctx->seq++;
+	ctx->curmsg->nlmsg_pid = ctx->pid;
+	memcpy(NLMSG_DATA(ctx->curmsg), payload, payload_size);
+	ctx->datalen += ctx->curmsg->nlmsg_len;
+	return 1;
+}
+
+int tst_rtnl_add_attr(const char *file, const int lineno,
+	struct tst_rtnl_context *ctx, unsigned short type,
+	const void *data, unsigned short len)
+{
+	size_t size;
+	struct rtattr *attr;
+
+	if (!ctx->curmsg) {
+		tst_brk_(file, lineno, TBROK,
+			"%s(): No message to add attributes to", __func__);
+		return 0;
+	}
+
+	if (!tst_rtnl_grow_buffer(file, lineno, ctx, RTA_SPACE(len)))
+		return 0;
+
+	size = NLMSG_ALIGN(ctx->curmsg->nlmsg_len);
+	attr = (struct rtattr *)(((char *)ctx->curmsg) + size);
+	attr->rta_type = type;
+	attr->rta_len = RTA_LENGTH(len);
+	memcpy(RTA_DATA(attr), data, len);
+	ctx->curmsg->nlmsg_len = size + attr->rta_len;
+	ctx->datalen = NLMSG_ALIGN(ctx->datalen) + attr->rta_len;
+	return 1;
+}
+
+int tst_rtnl_add_attr_string(const char *file, const int lineno,
+	struct tst_rtnl_context *ctx, unsigned short type,
+	const char *data)
+{
+	return tst_rtnl_add_attr(file, lineno, ctx, type, data,
+		strlen(data) + 1);
+}
+
+int tst_rtnl_add_attr_list(const char *file, const int lineno,
+	struct tst_rtnl_context *ctx,
+	const struct tst_rtnl_attr_list *list)
+{
+	int i, ret;
+	size_t offset;
+
+	for (i = 0; list[i].len >= 0; i++) {
+		if (list[i].len > USHRT_MAX) {
+			tst_brk_(file, lineno, TBROK,
+				"%s(): Attribute value too long", __func__);
+			return -1;
+		}
+
+		offset = NLMSG_ALIGN(ctx->datalen);
+		ret = tst_rtnl_add_attr(file, lineno, ctx, list[i].type,
+			list[i].data, list[i].len);
+
+		if (!ret)
+			return -1;
+
+		if (list[i].sublist) {
+			struct rtattr *attr;
+
+			ret = tst_rtnl_add_attr_list(file, lineno, ctx,
+				list[i].sublist);
+
+			if (ret < 0)
+				return ret;
+
+			attr = (struct rtattr *)(ctx->buffer + offset);
+
+			if (ctx->datalen - offset > USHRT_MAX) {
+				tst_brk_(file, lineno, TBROK,
+					"%s(): Sublist too long", __func__);
+				return -1;
+			}
+
+			attr->rta_len = ctx->datalen - offset;
+		}
+	}
+
+	return i;
+}
+
+int tst_rtnl_check_acks(const char *file, const int lineno,
+	struct tst_rtnl_context *ctx, struct tst_rtnl_message *res)
+{
+	struct nlmsghdr *msg = (struct nlmsghdr *)ctx->buffer;
+	int size_left = ctx->datalen;
+
+	for (; size_left > 0 && NLMSG_OK(msg, size_left);
+		msg = NLMSG_NEXT(msg, size_left)) {
+
+		if (!(msg->nlmsg_flags & NLM_F_ACK))
+			continue;
+
+		while (res->header && res->header->nlmsg_seq != msg->nlmsg_seq)
+			res++;
+
+		if (!res->err || res->header->nlmsg_seq != msg->nlmsg_seq) {
+			tst_brk_(file, lineno, TBROK,
+				"No ACK found for Netlink message %u",
+				msg->nlmsg_seq);
+			return 0;
+		}
+
+		if (res->err->error) {
+			TST_ERR = -res->err->error;
+			return 0;
+		}
+	}
+
+	return 1;
+}
+
+int tst_rtnl_send_validate(const char *file, const int lineno,
+	struct tst_rtnl_context *ctx)
+{
+	struct tst_rtnl_message *response;
+	int ret;
+
+	TST_ERR = 0;
+
+	if (tst_rtnl_send(file, lineno, ctx) <= 0)
+		return 0;
+
+	tst_rtnl_wait(ctx);
+	response = tst_rtnl_recv(file, lineno, ctx);
+
+	if (!response)
+		return 0;
+
+	ret = tst_rtnl_check_acks(file, lineno, ctx, response);
+	tst_rtnl_free_message(response);
+	return ret;
+}