diff options
| author | Mark Brown <broonie@kernel.org> | 2020-12-11 17:47:55 +0000 | 
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2020-12-11 17:47:55 +0000 | 
| commit | 031616c434db05ce766f76c62865f55698e0924f (patch) | |
| tree | 7f29aa1ff3e7b51a8058cd570fb785c6e769b245 /tools/testing/selftests/net/ipsec.c | |
| parent | 064841ccfc49b2315dc0b797239862d3a343aa07 (diff) | |
| parent | 85a7555575a0e48f9b73db310d0d762a08a46d63 (diff) | |
Merge remote-tracking branch 'asoc/for-5.10' into asoc-linus
Diffstat (limited to 'tools/testing/selftests/net/ipsec.c')
| -rw-r--r-- | tools/testing/selftests/net/ipsec.c | 2195 | 
1 files changed, 2195 insertions, 0 deletions
diff --git a/tools/testing/selftests/net/ipsec.c b/tools/testing/selftests/net/ipsec.c new file mode 100644 index 000000000000..17ced7d6ce25 --- /dev/null +++ b/tools/testing/selftests/net/ipsec.c @@ -0,0 +1,2195 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ipsec.c - Check xfrm on veth inside a net-ns. + * Copyright (c) 2018 Dmitry Safonov + */ + +#define _GNU_SOURCE + +#include <arpa/inet.h> +#include <asm/types.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <linux/limits.h> +#include <linux/netlink.h> +#include <linux/random.h> +#include <linux/rtnetlink.h> +#include <linux/veth.h> +#include <linux/xfrm.h> +#include <netinet/in.h> +#include <net/if.h> +#include <sched.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <time.h> +#include <unistd.h> + +#include "../kselftest.h" + +#define printk(fmt, ...)						\ +	ksft_print_msg("%d[%u] " fmt "\n", getpid(), __LINE__, ##__VA_ARGS__) + +#define pr_err(fmt, ...)	printk(fmt ": %m", ##__VA_ARGS__) + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) +#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)])) + +#define IPV4_STR_SZ	16	/* xxx.xxx.xxx.xxx is longest + \0 */ +#define MAX_PAYLOAD	2048 +#define XFRM_ALGO_KEY_BUF_SIZE	512 +#define MAX_PROCESSES	(1 << 14) /* /16 mask divided by /30 subnets */ +#define INADDR_A	((in_addr_t) 0x0a000000) /* 10.0.0.0 */ +#define INADDR_B	((in_addr_t) 0xc0a80000) /* 192.168.0.0 */ + +/* /30 mask for one veth connection */ +#define PREFIX_LEN	30 +#define child_ip(nr)	(4*nr + 1) +#define grchild_ip(nr)	(4*nr + 2) + +#define VETH_FMT	"ktst-%d" +#define VETH_LEN	12 + +static int nsfd_parent	= -1; +static int nsfd_childa	= -1; +static int nsfd_childb	= -1; +static long page_size; + +/* + * ksft_cnt is static in kselftest, so isn't shared with children. + * We have to send a test result back to parent and count there. + * results_fd is a pipe with test feedback from children. + */ +static int results_fd[2]; + +const unsigned int ping_delay_nsec	= 50 * 1000 * 1000; +const unsigned int ping_timeout		= 300; +const unsigned int ping_count		= 100; +const unsigned int ping_success		= 80; + +static void randomize_buffer(void *buf, size_t buflen) +{ +	int *p = (int *)buf; +	size_t words = buflen / sizeof(int); +	size_t leftover = buflen % sizeof(int); + +	if (!buflen) +		return; + +	while (words--) +		*p++ = rand(); + +	if (leftover) { +		int tmp = rand(); + +		memcpy(buf + buflen - leftover, &tmp, leftover); +	} + +	return; +} + +static int unshare_open(void) +{ +	const char *netns_path = "/proc/self/ns/net"; +	int fd; + +	if (unshare(CLONE_NEWNET) != 0) { +		pr_err("unshare()"); +		return -1; +	} + +	fd = open(netns_path, O_RDONLY); +	if (fd <= 0) { +		pr_err("open(%s)", netns_path); +		return -1; +	} + +	return fd; +} + +static int switch_ns(int fd) +{ +	if (setns(fd, CLONE_NEWNET)) { +		pr_err("setns()"); +		return -1; +	} +	return 0; +} + +/* + * Running the test inside a new parent net namespace to bother less + * about cleanup on error-path. + */ +static int init_namespaces(void) +{ +	nsfd_parent = unshare_open(); +	if (nsfd_parent <= 0) +		return -1; + +	nsfd_childa = unshare_open(); +	if (nsfd_childa <= 0) +		return -1; + +	if (switch_ns(nsfd_parent)) +		return -1; + +	nsfd_childb = unshare_open(); +	if (nsfd_childb <= 0) +		return -1; + +	if (switch_ns(nsfd_parent)) +		return -1; +	return 0; +} + +static int netlink_sock(int *sock, uint32_t *seq_nr, int proto) +{ +	if (*sock > 0) { +		seq_nr++; +		return 0; +	} + +	*sock = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, proto); +	if (*sock <= 0) { +		pr_err("socket(AF_NETLINK)"); +		return -1; +	} + +	randomize_buffer(seq_nr, sizeof(*seq_nr)); + +	return 0; +} + +static inline struct rtattr *rtattr_hdr(struct nlmsghdr *nh) +{ +	return (struct rtattr *)((char *)(nh) + RTA_ALIGN((nh)->nlmsg_len)); +} + +static int rtattr_pack(struct nlmsghdr *nh, size_t req_sz, +		unsigned short rta_type, const void *payload, size_t size) +{ +	/* NLMSG_ALIGNTO == RTA_ALIGNTO, nlmsg_len already aligned */ +	struct rtattr *attr = rtattr_hdr(nh); +	size_t nl_size = RTA_ALIGN(nh->nlmsg_len) + RTA_LENGTH(size); + +	if (req_sz < nl_size) { +		printk("req buf is too small: %zu < %zu", req_sz, nl_size); +		return -1; +	} +	nh->nlmsg_len = nl_size; + +	attr->rta_len = RTA_LENGTH(size); +	attr->rta_type = rta_type; +	memcpy(RTA_DATA(attr), payload, size); + +	return 0; +} + +static struct rtattr *_rtattr_begin(struct nlmsghdr *nh, size_t req_sz, +		unsigned short rta_type, const void *payload, size_t size) +{ +	struct rtattr *ret = rtattr_hdr(nh); + +	if (rtattr_pack(nh, req_sz, rta_type, payload, size)) +		return 0; + +	return ret; +} + +static inline struct rtattr *rtattr_begin(struct nlmsghdr *nh, size_t req_sz, +		unsigned short rta_type) +{ +	return _rtattr_begin(nh, req_sz, rta_type, 0, 0); +} + +static inline void rtattr_end(struct nlmsghdr *nh, struct rtattr *attr) +{ +	char *nlmsg_end = (char *)nh + nh->nlmsg_len; + +	attr->rta_len = nlmsg_end - (char *)attr; +} + +static int veth_pack_peerb(struct nlmsghdr *nh, size_t req_sz, +		const char *peer, int ns) +{ +	struct ifinfomsg pi; +	struct rtattr *peer_attr; + +	memset(&pi, 0, sizeof(pi)); +	pi.ifi_family	= AF_UNSPEC; +	pi.ifi_change	= 0xFFFFFFFF; + +	peer_attr = _rtattr_begin(nh, req_sz, VETH_INFO_PEER, &pi, sizeof(pi)); +	if (!peer_attr) +		return -1; + +	if (rtattr_pack(nh, req_sz, IFLA_IFNAME, peer, strlen(peer))) +		return -1; + +	if (rtattr_pack(nh, req_sz, IFLA_NET_NS_FD, &ns, sizeof(ns))) +		return -1; + +	rtattr_end(nh, peer_attr); + +	return 0; +} + +static int netlink_check_answer(int sock) +{ +	struct nlmsgerror { +		struct nlmsghdr hdr; +		int error; +		struct nlmsghdr orig_msg; +	} answer; + +	if (recv(sock, &answer, sizeof(answer), 0) < 0) { +		pr_err("recv()"); +		return -1; +	} else if (answer.hdr.nlmsg_type != NLMSG_ERROR) { +		printk("expected NLMSG_ERROR, got %d", (int)answer.hdr.nlmsg_type); +		return -1; +	} else if (answer.error) { +		printk("NLMSG_ERROR: %d: %s", +			answer.error, strerror(-answer.error)); +		return answer.error; +	} + +	return 0; +} + +static int veth_add(int sock, uint32_t seq, const char *peera, int ns_a, +		const char *peerb, int ns_b) +{ +	uint16_t flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE; +	struct { +		struct nlmsghdr		nh; +		struct ifinfomsg	info; +		char			attrbuf[MAX_PAYLOAD]; +	} req; +	const char veth_type[] = "veth"; +	struct rtattr *link_info, *info_data; + +	memset(&req, 0, sizeof(req)); +	req.nh.nlmsg_len	= NLMSG_LENGTH(sizeof(req.info)); +	req.nh.nlmsg_type	= RTM_NEWLINK; +	req.nh.nlmsg_flags	= flags; +	req.nh.nlmsg_seq	= seq; +	req.info.ifi_family	= AF_UNSPEC; +	req.info.ifi_change	= 0xFFFFFFFF; + +	if (rtattr_pack(&req.nh, sizeof(req), IFLA_IFNAME, peera, strlen(peera))) +		return -1; + +	if (rtattr_pack(&req.nh, sizeof(req), IFLA_NET_NS_FD, &ns_a, sizeof(ns_a))) +		return -1; + +	link_info = rtattr_begin(&req.nh, sizeof(req), IFLA_LINKINFO); +	if (!link_info) +		return -1; + +	if (rtattr_pack(&req.nh, sizeof(req), IFLA_INFO_KIND, veth_type, sizeof(veth_type))) +		return -1; + +	info_data = rtattr_begin(&req.nh, sizeof(req), IFLA_INFO_DATA); +	if (!info_data) +		return -1; + +	if (veth_pack_peerb(&req.nh, sizeof(req), peerb, ns_b)) +		return -1; + +	rtattr_end(&req.nh, info_data); +	rtattr_end(&req.nh, link_info); + +	if (send(sock, &req, req.nh.nlmsg_len, 0) < 0) { +		pr_err("send()"); +		return -1; +	} +	return netlink_check_answer(sock); +} + +static int ip4_addr_set(int sock, uint32_t seq, const char *intf, +		struct in_addr addr, uint8_t prefix) +{ +	uint16_t flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE; +	struct { +		struct nlmsghdr		nh; +		struct ifaddrmsg	info; +		char			attrbuf[MAX_PAYLOAD]; +	} req; + +	memset(&req, 0, sizeof(req)); +	req.nh.nlmsg_len	= NLMSG_LENGTH(sizeof(req.info)); +	req.nh.nlmsg_type	= RTM_NEWADDR; +	req.nh.nlmsg_flags	= flags; +	req.nh.nlmsg_seq	= seq; +	req.info.ifa_family	= AF_INET; +	req.info.ifa_prefixlen	= prefix; +	req.info.ifa_index	= if_nametoindex(intf); + +#ifdef DEBUG +	{ +		char addr_str[IPV4_STR_SZ] = {}; + +		strncpy(addr_str, inet_ntoa(addr), IPV4_STR_SZ - 1); + +		printk("ip addr set %s", addr_str); +	} +#endif + +	if (rtattr_pack(&req.nh, sizeof(req), IFA_LOCAL, &addr, sizeof(addr))) +		return -1; + +	if (rtattr_pack(&req.nh, sizeof(req), IFA_ADDRESS, &addr, sizeof(addr))) +		return -1; + +	if (send(sock, &req, req.nh.nlmsg_len, 0) < 0) { +		pr_err("send()"); +		return -1; +	} +	return netlink_check_answer(sock); +} + +static int link_set_up(int sock, uint32_t seq, const char *intf) +{ +	struct { +		struct nlmsghdr		nh; +		struct ifinfomsg	info; +		char			attrbuf[MAX_PAYLOAD]; +	} req; + +	memset(&req, 0, sizeof(req)); +	req.nh.nlmsg_len	= NLMSG_LENGTH(sizeof(req.info)); +	req.nh.nlmsg_type	= RTM_NEWLINK; +	req.nh.nlmsg_flags	= NLM_F_REQUEST | NLM_F_ACK; +	req.nh.nlmsg_seq	= seq; +	req.info.ifi_family	= AF_UNSPEC; +	req.info.ifi_change	= 0xFFFFFFFF; +	req.info.ifi_index	= if_nametoindex(intf); +	req.info.ifi_flags	= IFF_UP; +	req.info.ifi_change	= IFF_UP; + +	if (send(sock, &req, req.nh.nlmsg_len, 0) < 0) { +		pr_err("send()"); +		return -1; +	} +	return netlink_check_answer(sock); +} + +static int ip4_route_set(int sock, uint32_t seq, const char *intf, +		struct in_addr src, struct in_addr dst) +{ +	struct { +		struct nlmsghdr	nh; +		struct rtmsg	rt; +		char		attrbuf[MAX_PAYLOAD]; +	} req; +	unsigned int index = if_nametoindex(intf); + +	memset(&req, 0, sizeof(req)); +	req.nh.nlmsg_len	= NLMSG_LENGTH(sizeof(req.rt)); +	req.nh.nlmsg_type	= RTM_NEWROUTE; +	req.nh.nlmsg_flags	= NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE; +	req.nh.nlmsg_seq	= seq; +	req.rt.rtm_family	= AF_INET; +	req.rt.rtm_dst_len	= 32; +	req.rt.rtm_table	= RT_TABLE_MAIN; +	req.rt.rtm_protocol	= RTPROT_BOOT; +	req.rt.rtm_scope	= RT_SCOPE_LINK; +	req.rt.rtm_type		= RTN_UNICAST; + +	if (rtattr_pack(&req.nh, sizeof(req), RTA_DST, &dst, sizeof(dst))) +		return -1; + +	if (rtattr_pack(&req.nh, sizeof(req), RTA_PREFSRC, &src, sizeof(src))) +		return -1; + +	if (rtattr_pack(&req.nh, sizeof(req), RTA_OIF, &index, sizeof(index))) +		return -1; + +	if (send(sock, &req, req.nh.nlmsg_len, 0) < 0) { +		pr_err("send()"); +		return -1; +	} + +	return netlink_check_answer(sock); +} + +static int tunnel_set_route(int route_sock, uint32_t *route_seq, char *veth, +		struct in_addr tunsrc, struct in_addr tundst) +{ +	if (ip4_addr_set(route_sock, (*route_seq)++, "lo", +			tunsrc, PREFIX_LEN)) { +		printk("Failed to set ipv4 addr"); +		return -1; +	} + +	if (ip4_route_set(route_sock, (*route_seq)++, veth, tunsrc, tundst)) { +		printk("Failed to set ipv4 route"); +		return -1; +	} + +	return 0; +} + +static int init_child(int nsfd, char *veth, unsigned int src, unsigned int dst) +{ +	struct in_addr intsrc = inet_makeaddr(INADDR_B, src); +	struct in_addr tunsrc = inet_makeaddr(INADDR_A, src); +	struct in_addr tundst = inet_makeaddr(INADDR_A, dst); +	int route_sock = -1, ret = -1; +	uint32_t route_seq; + +	if (switch_ns(nsfd)) +		return -1; + +	if (netlink_sock(&route_sock, &route_seq, NETLINK_ROUTE)) { +		printk("Failed to open netlink route socket in child"); +		return -1; +	} + +	if (ip4_addr_set(route_sock, route_seq++, veth, intsrc, PREFIX_LEN)) { +		printk("Failed to set ipv4 addr"); +		goto err; +	} + +	if (link_set_up(route_sock, route_seq++, veth)) { +		printk("Failed to bring up %s", veth); +		goto err; +	} + +	if (tunnel_set_route(route_sock, &route_seq, veth, tunsrc, tundst)) { +		printk("Failed to add tunnel route on %s", veth); +		goto err; +	} +	ret = 0; + +err: +	close(route_sock); +	return ret; +} + +#define ALGO_LEN	64 +enum desc_type { +	CREATE_TUNNEL	= 0, +	ALLOCATE_SPI, +	MONITOR_ACQUIRE, +	EXPIRE_STATE, +	EXPIRE_POLICY, +}; +const char *desc_name[] = { +	"create tunnel", +	"alloc spi", +	"monitor acquire", +	"expire state", +	"expire policy" +}; +struct xfrm_desc { +	enum desc_type	type; +	uint8_t		proto; +	char		a_algo[ALGO_LEN]; +	char		e_algo[ALGO_LEN]; +	char		c_algo[ALGO_LEN]; +	char		ae_algo[ALGO_LEN]; +	unsigned int	icv_len; +	/* unsigned key_len; */ +}; + +enum msg_type { +	MSG_ACK		= 0, +	MSG_EXIT, +	MSG_PING, +	MSG_XFRM_PREPARE, +	MSG_XFRM_ADD, +	MSG_XFRM_DEL, +	MSG_XFRM_CLEANUP, +}; + +struct test_desc { +	enum msg_type type; +	union { +		struct { +			in_addr_t reply_ip; +			unsigned int port; +		} ping; +		struct xfrm_desc xfrm_desc; +	} body; +}; + +struct test_result { +	struct xfrm_desc desc; +	unsigned int res; +}; + +static void write_test_result(unsigned int res, struct xfrm_desc *d) +{ +	struct test_result tr = {}; +	ssize_t ret; + +	tr.desc = *d; +	tr.res = res; + +	ret = write(results_fd[1], &tr, sizeof(tr)); +	if (ret != sizeof(tr)) +		pr_err("Failed to write the result in pipe %zd", ret); +} + +static void write_msg(int fd, struct test_desc *msg, bool exit_of_fail) +{ +	ssize_t bytes = write(fd, msg, sizeof(*msg)); + +	/* Make sure that write/read is atomic to a pipe */ +	BUILD_BUG_ON(sizeof(struct test_desc) > PIPE_BUF); + +	if (bytes < 0) { +		pr_err("write()"); +		if (exit_of_fail) +			exit(KSFT_FAIL); +	} +	if (bytes != sizeof(*msg)) { +		pr_err("sent part of the message %zd/%zu", bytes, sizeof(*msg)); +		if (exit_of_fail) +			exit(KSFT_FAIL); +	} +} + +static void read_msg(int fd, struct test_desc *msg, bool exit_of_fail) +{ +	ssize_t bytes = read(fd, msg, sizeof(*msg)); + +	if (bytes < 0) { +		pr_err("read()"); +		if (exit_of_fail) +			exit(KSFT_FAIL); +	} +	if (bytes != sizeof(*msg)) { +		pr_err("got incomplete message %zd/%zu", bytes, sizeof(*msg)); +		if (exit_of_fail) +			exit(KSFT_FAIL); +	} +} + +static int udp_ping_init(struct in_addr listen_ip, unsigned int u_timeout, +		unsigned int *server_port, int sock[2]) +{ +	struct sockaddr_in server; +	struct timeval t = { .tv_sec = 0, .tv_usec = u_timeout }; +	socklen_t s_len = sizeof(server); + +	sock[0] = socket(AF_INET, SOCK_DGRAM, 0); +	if (sock[0] < 0) { +		pr_err("socket()"); +		return -1; +	} + +	server.sin_family	= AF_INET; +	server.sin_port		= 0; +	memcpy(&server.sin_addr.s_addr, &listen_ip, sizeof(struct in_addr)); + +	if (bind(sock[0], (struct sockaddr *)&server, s_len)) { +		pr_err("bind()"); +		goto err_close_server; +	} + +	if (getsockname(sock[0], (struct sockaddr *)&server, &s_len)) { +		pr_err("getsockname()"); +		goto err_close_server; +	} + +	*server_port = ntohs(server.sin_port); + +	if (setsockopt(sock[0], SOL_SOCKET, SO_RCVTIMEO, (const char *)&t, sizeof t)) { +		pr_err("setsockopt()"); +		goto err_close_server; +	} + +	sock[1] = socket(AF_INET, SOCK_DGRAM, 0); +	if (sock[1] < 0) { +		pr_err("socket()"); +		goto err_close_server; +	} + +	return 0; + +err_close_server: +	close(sock[0]); +	return -1; +} + +static int udp_ping_send(int sock[2], in_addr_t dest_ip, unsigned int port, +		char *buf, size_t buf_len) +{ +	struct sockaddr_in server; +	const struct sockaddr *dest_addr = (struct sockaddr *)&server; +	char *sock_buf[buf_len]; +	ssize_t r_bytes, s_bytes; + +	server.sin_family	= AF_INET; +	server.sin_port		= htons(port); +	server.sin_addr.s_addr	= dest_ip; + +	s_bytes = sendto(sock[1], buf, buf_len, 0, dest_addr, sizeof(server)); +	if (s_bytes < 0) { +		pr_err("sendto()"); +		return -1; +	} else if (s_bytes != buf_len) { +		printk("send part of the message: %zd/%zu", s_bytes, sizeof(server)); +		return -1; +	} + +	r_bytes = recv(sock[0], sock_buf, buf_len, 0); +	if (r_bytes < 0) { +		if (errno != EAGAIN) +			pr_err("recv()"); +		return -1; +	} else if (r_bytes == 0) { /* EOF */ +		printk("EOF on reply to ping"); +		return -1; +	} else if (r_bytes != buf_len || memcmp(buf, sock_buf, buf_len)) { +		printk("ping reply packet is corrupted %zd/%zu", r_bytes, buf_len); +		return -1; +	} + +	return 0; +} + +static int udp_ping_reply(int sock[2], in_addr_t dest_ip, unsigned int port, +		char *buf, size_t buf_len) +{ +	struct sockaddr_in server; +	const struct sockaddr *dest_addr = (struct sockaddr *)&server; +	char *sock_buf[buf_len]; +	ssize_t r_bytes, s_bytes; + +	server.sin_family	= AF_INET; +	server.sin_port		= htons(port); +	server.sin_addr.s_addr	= dest_ip; + +	r_bytes = recv(sock[0], sock_buf, buf_len, 0); +	if (r_bytes < 0) { +		if (errno != EAGAIN) +			pr_err("recv()"); +		return -1; +	} +	if (r_bytes == 0) { /* EOF */ +		printk("EOF on reply to ping"); +		return -1; +	} +	if (r_bytes != buf_len || memcmp(buf, sock_buf, buf_len)) { +		printk("ping reply packet is corrupted %zd/%zu", r_bytes, buf_len); +		return -1; +	} + +	s_bytes = sendto(sock[1], buf, buf_len, 0, dest_addr, sizeof(server)); +	if (s_bytes < 0) { +		pr_err("sendto()"); +		return -1; +	} else if (s_bytes != buf_len) { +		printk("send part of the message: %zd/%zu", s_bytes, sizeof(server)); +		return -1; +	} + +	return 0; +} + +typedef int (*ping_f)(int sock[2], in_addr_t dest_ip, unsigned int port, +		char *buf, size_t buf_len); +static int do_ping(int cmd_fd, char *buf, size_t buf_len, struct in_addr from, +		bool init_side, int d_port, in_addr_t to, ping_f func) +{ +	struct test_desc msg; +	unsigned int s_port, i, ping_succeeded = 0; +	int ping_sock[2]; +	char to_str[IPV4_STR_SZ] = {}, from_str[IPV4_STR_SZ] = {}; + +	if (udp_ping_init(from, ping_timeout, &s_port, ping_sock)) { +		printk("Failed to init ping"); +		return -1; +	} + +	memset(&msg, 0, sizeof(msg)); +	msg.type		= MSG_PING; +	msg.body.ping.port	= s_port; +	memcpy(&msg.body.ping.reply_ip, &from, sizeof(from)); + +	write_msg(cmd_fd, &msg, 0); +	if (init_side) { +		/* The other end sends ip to ping */ +		read_msg(cmd_fd, &msg, 0); +		if (msg.type != MSG_PING) +			return -1; +		to = msg.body.ping.reply_ip; +		d_port = msg.body.ping.port; +	} + +	for (i = 0; i < ping_count ; i++) { +		struct timespec sleep_time = { +			.tv_sec = 0, +			.tv_nsec = ping_delay_nsec, +		}; + +		ping_succeeded += !func(ping_sock, to, d_port, buf, page_size); +		nanosleep(&sleep_time, 0); +	} + +	close(ping_sock[0]); +	close(ping_sock[1]); + +	strncpy(to_str, inet_ntoa(*(struct in_addr *)&to), IPV4_STR_SZ - 1); +	strncpy(from_str, inet_ntoa(from), IPV4_STR_SZ - 1); + +	if (ping_succeeded < ping_success) { +		printk("ping (%s) %s->%s failed %u/%u times", +			init_side ? "send" : "reply", from_str, to_str, +			ping_count - ping_succeeded, ping_count); +		return -1; +	} + +#ifdef DEBUG +	printk("ping (%s) %s->%s succeeded %u/%u times", +		init_side ? "send" : "reply", from_str, to_str, +		ping_succeeded, ping_count); +#endif + +	return 0; +} + +static int xfrm_fill_key(char *name, char *buf, +		size_t buf_len, unsigned int *key_len) +{ +	/* TODO: use set/map instead */ +	if (strncmp(name, "digest_null", ALGO_LEN) == 0) +		*key_len = 0; +	else if (strncmp(name, "ecb(cipher_null)", ALGO_LEN) == 0) +		*key_len = 0; +	else if (strncmp(name, "cbc(des)", ALGO_LEN) == 0) +		*key_len = 64; +	else if (strncmp(name, "hmac(md5)", ALGO_LEN) == 0) +		*key_len = 128; +	else if (strncmp(name, "cmac(aes)", ALGO_LEN) == 0) +		*key_len = 128; +	else if (strncmp(name, "xcbc(aes)", ALGO_LEN) == 0) +		*key_len = 128; +	else if (strncmp(name, "cbc(cast5)", ALGO_LEN) == 0) +		*key_len = 128; +	else if (strncmp(name, "cbc(serpent)", ALGO_LEN) == 0) +		*key_len = 128; +	else if (strncmp(name, "hmac(sha1)", ALGO_LEN) == 0) +		*key_len = 160; +	else if (strncmp(name, "hmac(rmd160)", ALGO_LEN) == 0) +		*key_len = 160; +	else if (strncmp(name, "cbc(des3_ede)", ALGO_LEN) == 0) +		*key_len = 192; +	else if (strncmp(name, "hmac(sha256)", ALGO_LEN) == 0) +		*key_len = 256; +	else if (strncmp(name, "cbc(aes)", ALGO_LEN) == 0) +		*key_len = 256; +	else if (strncmp(name, "cbc(camellia)", ALGO_LEN) == 0) +		*key_len = 256; +	else if (strncmp(name, "cbc(twofish)", ALGO_LEN) == 0) +		*key_len = 256; +	else if (strncmp(name, "rfc3686(ctr(aes))", ALGO_LEN) == 0) +		*key_len = 288; +	else if (strncmp(name, "hmac(sha384)", ALGO_LEN) == 0) +		*key_len = 384; +	else if (strncmp(name, "cbc(blowfish)", ALGO_LEN) == 0) +		*key_len = 448; +	else if (strncmp(name, "hmac(sha512)", ALGO_LEN) == 0) +		*key_len = 512; +	else if (strncmp(name, "rfc4106(gcm(aes))-128", ALGO_LEN) == 0) +		*key_len = 160; +	else if (strncmp(name, "rfc4543(gcm(aes))-128", ALGO_LEN) == 0) +		*key_len = 160; +	else if (strncmp(name, "rfc4309(ccm(aes))-128", ALGO_LEN) == 0) +		*key_len = 152; +	else if (strncmp(name, "rfc4106(gcm(aes))-192", ALGO_LEN) == 0) +		*key_len = 224; +	else if (strncmp(name, "rfc4543(gcm(aes))-192", ALGO_LEN) == 0) +		*key_len = 224; +	else if (strncmp(name, "rfc4309(ccm(aes))-192", ALGO_LEN) == 0) +		*key_len = 216; +	else if (strncmp(name, "rfc4106(gcm(aes))-256", ALGO_LEN) == 0) +		*key_len = 288; +	else if (strncmp(name, "rfc4543(gcm(aes))-256", ALGO_LEN) == 0) +		*key_len = 288; +	else if (strncmp(name, "rfc4309(ccm(aes))-256", ALGO_LEN) == 0) +		*key_len = 280; +	else if (strncmp(name, "rfc7539(chacha20,poly1305)-128", ALGO_LEN) == 0) +		*key_len = 0; + +	if (*key_len > buf_len) { +		printk("Can't pack a key - too big for buffer"); +		return -1; +	} + +	randomize_buffer(buf, *key_len); + +	return 0; +} + +static int xfrm_state_pack_algo(struct nlmsghdr *nh, size_t req_sz, +		struct xfrm_desc *desc) +{ +	struct { +		union { +			struct xfrm_algo	alg; +			struct xfrm_algo_aead	aead; +			struct xfrm_algo_auth	auth; +		} u; +		char buf[XFRM_ALGO_KEY_BUF_SIZE]; +	} alg = {}; +	size_t alen, elen, clen, aelen; +	unsigned short type; + +	alen = strlen(desc->a_algo); +	elen = strlen(desc->e_algo); +	clen = strlen(desc->c_algo); +	aelen = strlen(desc->ae_algo); + +	/* Verify desc */ +	switch (desc->proto) { +	case IPPROTO_AH: +		if (!alen || elen || clen || aelen) { +			printk("BUG: buggy ah desc"); +			return -1; +		} +		strncpy(alg.u.alg.alg_name, desc->a_algo, ALGO_LEN - 1); +		if (xfrm_fill_key(desc->a_algo, alg.u.alg.alg_key, +				sizeof(alg.buf), &alg.u.alg.alg_key_len)) +			return -1; +		type = XFRMA_ALG_AUTH; +		break; +	case IPPROTO_COMP: +		if (!clen || elen || alen || aelen) { +			printk("BUG: buggy comp desc"); +			return -1; +		} +		strncpy(alg.u.alg.alg_name, desc->c_algo, ALGO_LEN - 1); +		if (xfrm_fill_key(desc->c_algo, alg.u.alg.alg_key, +				sizeof(alg.buf), &alg.u.alg.alg_key_len)) +			return -1; +		type = XFRMA_ALG_COMP; +		break; +	case IPPROTO_ESP: +		if (!((alen && elen) ^ aelen) || clen) { +			printk("BUG: buggy esp desc"); +			return -1; +		} +		if (aelen) { +			alg.u.aead.alg_icv_len = desc->icv_len; +			strncpy(alg.u.aead.alg_name, desc->ae_algo, ALGO_LEN - 1); +			if (xfrm_fill_key(desc->ae_algo, alg.u.aead.alg_key, +						sizeof(alg.buf), &alg.u.aead.alg_key_len)) +				return -1; +			type = XFRMA_ALG_AEAD; +		} else { + +			strncpy(alg.u.alg.alg_name, desc->e_algo, ALGO_LEN - 1); +			type = XFRMA_ALG_CRYPT; +			if (xfrm_fill_key(desc->e_algo, alg.u.alg.alg_key, +						sizeof(alg.buf), &alg.u.alg.alg_key_len)) +				return -1; +			if (rtattr_pack(nh, req_sz, type, &alg, sizeof(alg))) +				return -1; + +			strncpy(alg.u.alg.alg_name, desc->a_algo, ALGO_LEN); +			type = XFRMA_ALG_AUTH; +			if (xfrm_fill_key(desc->a_algo, alg.u.alg.alg_key, +						sizeof(alg.buf), &alg.u.alg.alg_key_len)) +				return -1; +		} +		break; +	default: +		printk("BUG: unknown proto in desc"); +		return -1; +	} + +	if (rtattr_pack(nh, req_sz, type, &alg, sizeof(alg))) +		return -1; + +	return 0; +} + +static inline uint32_t gen_spi(struct in_addr src) +{ +	return htonl(inet_lnaof(src)); +} + +static int xfrm_state_add(int xfrm_sock, uint32_t seq, uint32_t spi, +		struct in_addr src, struct in_addr dst, +		struct xfrm_desc *desc) +{ +	struct { +		struct nlmsghdr		nh; +		struct xfrm_usersa_info	info; +		char			attrbuf[MAX_PAYLOAD]; +	} req; + +	memset(&req, 0, sizeof(req)); +	req.nh.nlmsg_len	= NLMSG_LENGTH(sizeof(req.info)); +	req.nh.nlmsg_type	= XFRM_MSG_NEWSA; +	req.nh.nlmsg_flags	= NLM_F_REQUEST | NLM_F_ACK; +	req.nh.nlmsg_seq	= seq; + +	/* Fill selector. */ +	memcpy(&req.info.sel.daddr, &dst, sizeof(dst)); +	memcpy(&req.info.sel.saddr, &src, sizeof(src)); +	req.info.sel.family		= AF_INET; +	req.info.sel.prefixlen_d	= PREFIX_LEN; +	req.info.sel.prefixlen_s	= PREFIX_LEN; + +	/* Fill id */ +	memcpy(&req.info.id.daddr, &dst, sizeof(dst)); +	/* Note: zero-spi cannot be deleted */ +	req.info.id.spi = spi; +	req.info.id.proto	= desc->proto; + +	memcpy(&req.info.saddr, &src, sizeof(src)); + +	/* Fill lifteme_cfg */ +	req.info.lft.soft_byte_limit	= XFRM_INF; +	req.info.lft.hard_byte_limit	= XFRM_INF; +	req.info.lft.soft_packet_limit	= XFRM_INF; +	req.info.lft.hard_packet_limit	= XFRM_INF; + +	req.info.family		= AF_INET; +	req.info.mode		= XFRM_MODE_TUNNEL; + +	if (xfrm_state_pack_algo(&req.nh, sizeof(req), desc)) +		return -1; + +	if (send(xfrm_sock, &req, req.nh.nlmsg_len, 0) < 0) { +		pr_err("send()"); +		return -1; +	} + +	return netlink_check_answer(xfrm_sock); +} + +static bool xfrm_usersa_found(struct xfrm_usersa_info *info, uint32_t spi, +		struct in_addr src, struct in_addr dst, +		struct xfrm_desc *desc) +{ +	if (memcmp(&info->sel.daddr, &dst, sizeof(dst))) +		return false; + +	if (memcmp(&info->sel.saddr, &src, sizeof(src))) +		return false; + +	if (info->sel.family != AF_INET					|| +			info->sel.prefixlen_d != PREFIX_LEN		|| +			info->sel.prefixlen_s != PREFIX_LEN) +		return false; + +	if (info->id.spi != spi || info->id.proto != desc->proto) +		return false; + +	if (memcmp(&info->id.daddr, &dst, sizeof(dst))) +		return false; + +	if (memcmp(&info->saddr, &src, sizeof(src))) +		return false; + +	if (info->lft.soft_byte_limit != XFRM_INF			|| +			info->lft.hard_byte_limit != XFRM_INF		|| +			info->lft.soft_packet_limit != XFRM_INF		|| +			info->lft.hard_packet_limit != XFRM_INF) +		return false; + +	if (info->family != AF_INET || info->mode != XFRM_MODE_TUNNEL) +		return false; + +	/* XXX: check xfrm algo, see xfrm_state_pack_algo(). */ + +	return true; +} + +static int xfrm_state_check(int xfrm_sock, uint32_t seq, uint32_t spi, +		struct in_addr src, struct in_addr dst, +		struct xfrm_desc *desc) +{ +	struct { +		struct nlmsghdr		nh; +		char			attrbuf[MAX_PAYLOAD]; +	} req; +	struct { +		struct nlmsghdr		nh; +		union { +			struct xfrm_usersa_info	info; +			int error; +		}; +		char			attrbuf[MAX_PAYLOAD]; +	} answer; +	struct xfrm_address_filter filter = {}; +	bool found = false; + + +	memset(&req, 0, sizeof(req)); +	req.nh.nlmsg_len	= NLMSG_LENGTH(0); +	req.nh.nlmsg_type	= XFRM_MSG_GETSA; +	req.nh.nlmsg_flags	= NLM_F_REQUEST | NLM_F_DUMP; +	req.nh.nlmsg_seq	= seq; + +	/* +	 * Add dump filter by source address as there may be other tunnels +	 * in this netns (if tests run in parallel). +	 */ +	filter.family = AF_INET; +	filter.splen = 0x1f;	/* 0xffffffff mask see addr_match() */ +	memcpy(&filter.saddr, &src, sizeof(src)); +	if (rtattr_pack(&req.nh, sizeof(req), XFRMA_ADDRESS_FILTER, +				&filter, sizeof(filter))) +		return -1; + +	if (send(xfrm_sock, &req, req.nh.nlmsg_len, 0) < 0) { +		pr_err("send()"); +		return -1; +	} + +	while (1) { +		if (recv(xfrm_sock, &answer, sizeof(answer), 0) < 0) { +			pr_err("recv()"); +			return -1; +		} +		if (answer.nh.nlmsg_type == NLMSG_ERROR) { +			printk("NLMSG_ERROR: %d: %s", +				answer.error, strerror(-answer.error)); +			return -1; +		} else if (answer.nh.nlmsg_type == NLMSG_DONE) { +			if (found) +				return 0; +			printk("didn't find allocated xfrm state in dump"); +			return -1; +		} else if (answer.nh.nlmsg_type == XFRM_MSG_NEWSA) { +			if (xfrm_usersa_found(&answer.info, spi, src, dst, desc)) +				found = true; +		} +	} +} + +static int xfrm_set(int xfrm_sock, uint32_t *seq, +		struct in_addr src, struct in_addr dst, +		struct in_addr tunsrc, struct in_addr tundst, +		struct xfrm_desc *desc) +{ +	int err; + +	err = xfrm_state_add(xfrm_sock, (*seq)++, gen_spi(src), src, dst, desc); +	if (err) { +		printk("Failed to add xfrm state"); +		return -1; +	} + +	err = xfrm_state_add(xfrm_sock, (*seq)++, gen_spi(src), dst, src, desc); +	if (err) { +		printk("Failed to add xfrm state"); +		return -1; +	} + +	/* Check dumps for XFRM_MSG_GETSA */ +	err = xfrm_state_check(xfrm_sock, (*seq)++, gen_spi(src), src, dst, desc); +	err |= xfrm_state_check(xfrm_sock, (*seq)++, gen_spi(src), dst, src, desc); +	if (err) { +		printk("Failed to check xfrm state"); +		return -1; +	} + +	return 0; +} + +static int xfrm_policy_add(int xfrm_sock, uint32_t seq, uint32_t spi, +		struct in_addr src, struct in_addr dst, uint8_t dir, +		struct in_addr tunsrc, struct in_addr tundst, uint8_t proto) +{ +	struct { +		struct nlmsghdr			nh; +		struct xfrm_userpolicy_info	info; +		char				attrbuf[MAX_PAYLOAD]; +	} req; +	struct xfrm_user_tmpl tmpl; + +	memset(&req, 0, sizeof(req)); +	memset(&tmpl, 0, sizeof(tmpl)); +	req.nh.nlmsg_len	= NLMSG_LENGTH(sizeof(req.info)); +	req.nh.nlmsg_type	= XFRM_MSG_NEWPOLICY; +	req.nh.nlmsg_flags	= NLM_F_REQUEST | NLM_F_ACK; +	req.nh.nlmsg_seq	= seq; + +	/* Fill selector. */ +	memcpy(&req.info.sel.daddr, &dst, sizeof(tundst)); +	memcpy(&req.info.sel.saddr, &src, sizeof(tunsrc)); +	req.info.sel.family		= AF_INET; +	req.info.sel.prefixlen_d	= PREFIX_LEN; +	req.info.sel.prefixlen_s	= PREFIX_LEN; + +	/* Fill lifteme_cfg */ +	req.info.lft.soft_byte_limit	= XFRM_INF; +	req.info.lft.hard_byte_limit	= XFRM_INF; +	req.info.lft.soft_packet_limit	= XFRM_INF; +	req.info.lft.hard_packet_limit	= XFRM_INF; + +	req.info.dir = dir; + +	/* Fill tmpl */ +	memcpy(&tmpl.id.daddr, &dst, sizeof(dst)); +	/* Note: zero-spi cannot be deleted */ +	tmpl.id.spi = spi; +	tmpl.id.proto	= proto; +	tmpl.family	= AF_INET; +	memcpy(&tmpl.saddr, &src, sizeof(src)); +	tmpl.mode	= XFRM_MODE_TUNNEL; +	tmpl.aalgos = (~(uint32_t)0); +	tmpl.ealgos = (~(uint32_t)0); +	tmpl.calgos = (~(uint32_t)0); + +	if (rtattr_pack(&req.nh, sizeof(req), XFRMA_TMPL, &tmpl, sizeof(tmpl))) +		return -1; + +	if (send(xfrm_sock, &req, req.nh.nlmsg_len, 0) < 0) { +		pr_err("send()"); +		return -1; +	} + +	return netlink_check_answer(xfrm_sock); +} + +static int xfrm_prepare(int xfrm_sock, uint32_t *seq, +		struct in_addr src, struct in_addr dst, +		struct in_addr tunsrc, struct in_addr tundst, uint8_t proto) +{ +	if (xfrm_policy_add(xfrm_sock, (*seq)++, gen_spi(src), src, dst, +				XFRM_POLICY_OUT, tunsrc, tundst, proto)) { +		printk("Failed to add xfrm policy"); +		return -1; +	} + +	if (xfrm_policy_add(xfrm_sock, (*seq)++, gen_spi(src), dst, src, +				XFRM_POLICY_IN, tunsrc, tundst, proto)) { +		printk("Failed to add xfrm policy"); +		return -1; +	} + +	return 0; +} + +static int xfrm_policy_del(int xfrm_sock, uint32_t seq, +		struct in_addr src, struct in_addr dst, uint8_t dir, +		struct in_addr tunsrc, struct in_addr tundst) +{ +	struct { +		struct nlmsghdr			nh; +		struct xfrm_userpolicy_id	id; +		char				attrbuf[MAX_PAYLOAD]; +	} req; + +	memset(&req, 0, sizeof(req)); +	req.nh.nlmsg_len	= NLMSG_LENGTH(sizeof(req.id)); +	req.nh.nlmsg_type	= XFRM_MSG_DELPOLICY; +	req.nh.nlmsg_flags	= NLM_F_REQUEST | NLM_F_ACK; +	req.nh.nlmsg_seq	= seq; + +	/* Fill id */ +	memcpy(&req.id.sel.daddr, &dst, sizeof(tundst)); +	memcpy(&req.id.sel.saddr, &src, sizeof(tunsrc)); +	req.id.sel.family		= AF_INET; +	req.id.sel.prefixlen_d		= PREFIX_LEN; +	req.id.sel.prefixlen_s		= PREFIX_LEN; +	req.id.dir = dir; + +	if (send(xfrm_sock, &req, req.nh.nlmsg_len, 0) < 0) { +		pr_err("send()"); +		return -1; +	} + +	return netlink_check_answer(xfrm_sock); +} + +static int xfrm_cleanup(int xfrm_sock, uint32_t *seq, +		struct in_addr src, struct in_addr dst, +		struct in_addr tunsrc, struct in_addr tundst) +{ +	if (xfrm_policy_del(xfrm_sock, (*seq)++, src, dst, +				XFRM_POLICY_OUT, tunsrc, tundst)) { +		printk("Failed to add xfrm policy"); +		return -1; +	} + +	if (xfrm_policy_del(xfrm_sock, (*seq)++, dst, src, +				XFRM_POLICY_IN, tunsrc, tundst)) { +		printk("Failed to add xfrm policy"); +		return -1; +	} + +	return 0; +} + +static int xfrm_state_del(int xfrm_sock, uint32_t seq, uint32_t spi, +		struct in_addr src, struct in_addr dst, uint8_t proto) +{ +	struct { +		struct nlmsghdr		nh; +		struct xfrm_usersa_id	id; +		char			attrbuf[MAX_PAYLOAD]; +	} req; +	xfrm_address_t saddr = {}; + +	memset(&req, 0, sizeof(req)); +	req.nh.nlmsg_len	= NLMSG_LENGTH(sizeof(req.id)); +	req.nh.nlmsg_type	= XFRM_MSG_DELSA; +	req.nh.nlmsg_flags	= NLM_F_REQUEST | NLM_F_ACK; +	req.nh.nlmsg_seq	= seq; + +	memcpy(&req.id.daddr, &dst, sizeof(dst)); +	req.id.family		= AF_INET; +	req.id.proto		= proto; +	/* Note: zero-spi cannot be deleted */ +	req.id.spi = spi; + +	memcpy(&saddr, &src, sizeof(src)); +	if (rtattr_pack(&req.nh, sizeof(req), XFRMA_SRCADDR, &saddr, sizeof(saddr))) +		return -1; + +	if (send(xfrm_sock, &req, req.nh.nlmsg_len, 0) < 0) { +		pr_err("send()"); +		return -1; +	} + +	return netlink_check_answer(xfrm_sock); +} + +static int xfrm_delete(int xfrm_sock, uint32_t *seq, +		struct in_addr src, struct in_addr dst, +		struct in_addr tunsrc, struct in_addr tundst, uint8_t proto) +{ +	if (xfrm_state_del(xfrm_sock, (*seq)++, gen_spi(src), src, dst, proto)) { +		printk("Failed to remove xfrm state"); +		return -1; +	} + +	if (xfrm_state_del(xfrm_sock, (*seq)++, gen_spi(src), dst, src, proto)) { +		printk("Failed to remove xfrm state"); +		return -1; +	} + +	return 0; +} + +static int xfrm_state_allocspi(int xfrm_sock, uint32_t *seq, +		uint32_t spi, uint8_t proto) +{ +	struct { +		struct nlmsghdr			nh; +		struct xfrm_userspi_info	spi; +	} req; +	struct { +		struct nlmsghdr			nh; +		union { +			struct xfrm_usersa_info	info; +			int error; +		}; +	} answer; + +	memset(&req, 0, sizeof(req)); +	req.nh.nlmsg_len	= NLMSG_LENGTH(sizeof(req.spi)); +	req.nh.nlmsg_type	= XFRM_MSG_ALLOCSPI; +	req.nh.nlmsg_flags	= NLM_F_REQUEST; +	req.nh.nlmsg_seq	= (*seq)++; + +	req.spi.info.family	= AF_INET; +	req.spi.min		= spi; +	req.spi.max		= spi; +	req.spi.info.id.proto	= proto; + +	if (send(xfrm_sock, &req, req.nh.nlmsg_len, 0) < 0) { +		pr_err("send()"); +		return KSFT_FAIL; +	} + +	if (recv(xfrm_sock, &answer, sizeof(answer), 0) < 0) { +		pr_err("recv()"); +		return KSFT_FAIL; +	} else if (answer.nh.nlmsg_type == XFRM_MSG_NEWSA) { +		uint32_t new_spi = htonl(answer.info.id.spi); + +		if (new_spi != spi) { +			printk("allocated spi is different from requested: %#x != %#x", +					new_spi, spi); +			return KSFT_FAIL; +		} +		return KSFT_PASS; +	} else if (answer.nh.nlmsg_type != NLMSG_ERROR) { +		printk("expected NLMSG_ERROR, got %d", (int)answer.nh.nlmsg_type); +		return KSFT_FAIL; +	} + +	printk("NLMSG_ERROR: %d: %s", answer.error, strerror(-answer.error)); +	return (answer.error) ? KSFT_FAIL : KSFT_PASS; +} + +static int netlink_sock_bind(int *sock, uint32_t *seq, int proto, uint32_t groups) +{ +	struct sockaddr_nl snl = {}; +	socklen_t addr_len; +	int ret = -1; + +	snl.nl_family = AF_NETLINK; +	snl.nl_groups = groups; + +	if (netlink_sock(sock, seq, proto)) { +		printk("Failed to open xfrm netlink socket"); +		return -1; +	} + +	if (bind(*sock, (struct sockaddr *)&snl, sizeof(snl)) < 0) { +		pr_err("bind()"); +		goto out_close; +	} + +	addr_len = sizeof(snl); +	if (getsockname(*sock, (struct sockaddr *)&snl, &addr_len) < 0) { +		pr_err("getsockname()"); +		goto out_close; +	} +	if (addr_len != sizeof(snl)) { +		printk("Wrong address length %d", addr_len); +		goto out_close; +	} +	if (snl.nl_family != AF_NETLINK) { +		printk("Wrong address family %d", snl.nl_family); +		goto out_close; +	} +	return 0; + +out_close: +	close(*sock); +	return ret; +} + +static int xfrm_monitor_acquire(int xfrm_sock, uint32_t *seq, unsigned int nr) +{ +	struct { +		struct nlmsghdr nh; +		union { +			struct xfrm_user_acquire acq; +			int error; +		}; +		char attrbuf[MAX_PAYLOAD]; +	} req; +	struct xfrm_user_tmpl xfrm_tmpl = {}; +	int xfrm_listen = -1, ret = KSFT_FAIL; +	uint32_t seq_listen; + +	if (netlink_sock_bind(&xfrm_listen, &seq_listen, NETLINK_XFRM, XFRMNLGRP_ACQUIRE)) +		return KSFT_FAIL; + +	memset(&req, 0, sizeof(req)); +	req.nh.nlmsg_len	= NLMSG_LENGTH(sizeof(req.acq)); +	req.nh.nlmsg_type	= XFRM_MSG_ACQUIRE; +	req.nh.nlmsg_flags	= NLM_F_REQUEST | NLM_F_ACK; +	req.nh.nlmsg_seq	= (*seq)++; + +	req.acq.policy.sel.family	= AF_INET; +	req.acq.aalgos	= 0xfeed; +	req.acq.ealgos	= 0xbaad; +	req.acq.calgos	= 0xbabe; + +	xfrm_tmpl.family = AF_INET; +	xfrm_tmpl.id.proto = IPPROTO_ESP; +	if (rtattr_pack(&req.nh, sizeof(req), XFRMA_TMPL, &xfrm_tmpl, sizeof(xfrm_tmpl))) +		goto out_close; + +	if (send(xfrm_sock, &req, req.nh.nlmsg_len, 0) < 0) { +		pr_err("send()"); +		goto out_close; +	} + +	if (recv(xfrm_sock, &req, sizeof(req), 0) < 0) { +		pr_err("recv()"); +		goto out_close; +	} else if (req.nh.nlmsg_type != NLMSG_ERROR) { +		printk("expected NLMSG_ERROR, got %d", (int)req.nh.nlmsg_type); +		goto out_close; +	} + +	if (req.error) { +		printk("NLMSG_ERROR: %d: %s", req.error, strerror(-req.error)); +		ret = req.error; +		goto out_close; +	} + +	if (recv(xfrm_listen, &req, sizeof(req), 0) < 0) { +		pr_err("recv()"); +		goto out_close; +	} + +	if (req.acq.aalgos != 0xfeed || req.acq.ealgos != 0xbaad +			|| req.acq.calgos != 0xbabe) { +		printk("xfrm_user_acquire has changed  %x %x %x", +				req.acq.aalgos, req.acq.ealgos, req.acq.calgos); +		goto out_close; +	} + +	ret = KSFT_PASS; +out_close: +	close(xfrm_listen); +	return ret; +} + +static int xfrm_expire_state(int xfrm_sock, uint32_t *seq, +		unsigned int nr, struct xfrm_desc *desc) +{ +	struct { +		struct nlmsghdr nh; +		union { +			struct xfrm_user_expire expire; +			int error; +		}; +	} req; +	struct in_addr src, dst; +	int xfrm_listen = -1, ret = KSFT_FAIL; +	uint32_t seq_listen; + +	src = inet_makeaddr(INADDR_B, child_ip(nr)); +	dst = inet_makeaddr(INADDR_B, grchild_ip(nr)); + +	if (xfrm_state_add(xfrm_sock, (*seq)++, gen_spi(src), src, dst, desc)) { +		printk("Failed to add xfrm state"); +		return KSFT_FAIL; +	} + +	if (netlink_sock_bind(&xfrm_listen, &seq_listen, NETLINK_XFRM, XFRMNLGRP_EXPIRE)) +		return KSFT_FAIL; + +	memset(&req, 0, sizeof(req)); +	req.nh.nlmsg_len	= NLMSG_LENGTH(sizeof(req.expire)); +	req.nh.nlmsg_type	= XFRM_MSG_EXPIRE; +	req.nh.nlmsg_flags	= NLM_F_REQUEST | NLM_F_ACK; +	req.nh.nlmsg_seq	= (*seq)++; + +	memcpy(&req.expire.state.id.daddr, &dst, sizeof(dst)); +	req.expire.state.id.spi		= gen_spi(src); +	req.expire.state.id.proto	= desc->proto; +	req.expire.state.family		= AF_INET; +	req.expire.hard			= 0xff; + +	if (send(xfrm_sock, &req, req.nh.nlmsg_len, 0) < 0) { +		pr_err("send()"); +		goto out_close; +	} + +	if (recv(xfrm_sock, &req, sizeof(req), 0) < 0) { +		pr_err("recv()"); +		goto out_close; +	} else if (req.nh.nlmsg_type != NLMSG_ERROR) { +		printk("expected NLMSG_ERROR, got %d", (int)req.nh.nlmsg_type); +		goto out_close; +	} + +	if (req.error) { +		printk("NLMSG_ERROR: %d: %s", req.error, strerror(-req.error)); +		ret = req.error; +		goto out_close; +	} + +	if (recv(xfrm_listen, &req, sizeof(req), 0) < 0) { +		pr_err("recv()"); +		goto out_close; +	} + +	if (req.expire.hard != 0x1) { +		printk("expire.hard is not set: %x", req.expire.hard); +		goto out_close; +	} + +	ret = KSFT_PASS; +out_close: +	close(xfrm_listen); +	return ret; +} + +static int xfrm_expire_policy(int xfrm_sock, uint32_t *seq, +		unsigned int nr, struct xfrm_desc *desc) +{ +	struct { +		struct nlmsghdr nh; +		union { +			struct xfrm_user_polexpire expire; +			int error; +		}; +	} req; +	struct in_addr src, dst, tunsrc, tundst; +	int xfrm_listen = -1, ret = KSFT_FAIL; +	uint32_t seq_listen; + +	src = inet_makeaddr(INADDR_B, child_ip(nr)); +	dst = inet_makeaddr(INADDR_B, grchild_ip(nr)); +	tunsrc = inet_makeaddr(INADDR_A, child_ip(nr)); +	tundst = inet_makeaddr(INADDR_A, grchild_ip(nr)); + +	if (xfrm_policy_add(xfrm_sock, (*seq)++, gen_spi(src), src, dst, +				XFRM_POLICY_OUT, tunsrc, tundst, desc->proto)) { +		printk("Failed to add xfrm policy"); +		return KSFT_FAIL; +	} + +	if (netlink_sock_bind(&xfrm_listen, &seq_listen, NETLINK_XFRM, XFRMNLGRP_EXPIRE)) +		return KSFT_FAIL; + +	memset(&req, 0, sizeof(req)); +	req.nh.nlmsg_len	= NLMSG_LENGTH(sizeof(req.expire)); +	req.nh.nlmsg_type	= XFRM_MSG_POLEXPIRE; +	req.nh.nlmsg_flags	= NLM_F_REQUEST | NLM_F_ACK; +	req.nh.nlmsg_seq	= (*seq)++; + +	/* Fill selector. */ +	memcpy(&req.expire.pol.sel.daddr, &dst, sizeof(tundst)); +	memcpy(&req.expire.pol.sel.saddr, &src, sizeof(tunsrc)); +	req.expire.pol.sel.family	= AF_INET; +	req.expire.pol.sel.prefixlen_d	= PREFIX_LEN; +	req.expire.pol.sel.prefixlen_s	= PREFIX_LEN; +	req.expire.pol.dir		= XFRM_POLICY_OUT; +	req.expire.hard			= 0xff; + +	if (send(xfrm_sock, &req, req.nh.nlmsg_len, 0) < 0) { +		pr_err("send()"); +		goto out_close; +	} + +	if (recv(xfrm_sock, &req, sizeof(req), 0) < 0) { +		pr_err("recv()"); +		goto out_close; +	} else if (req.nh.nlmsg_type != NLMSG_ERROR) { +		printk("expected NLMSG_ERROR, got %d", (int)req.nh.nlmsg_type); +		goto out_close; +	} + +	if (req.error) { +		printk("NLMSG_ERROR: %d: %s", req.error, strerror(-req.error)); +		ret = req.error; +		goto out_close; +	} + +	if (recv(xfrm_listen, &req, sizeof(req), 0) < 0) { +		pr_err("recv()"); +		goto out_close; +	} + +	if (req.expire.hard != 0x1) { +		printk("expire.hard is not set: %x", req.expire.hard); +		goto out_close; +	} + +	ret = KSFT_PASS; +out_close: +	close(xfrm_listen); +	return ret; +} + +static int child_serv(int xfrm_sock, uint32_t *seq, +		unsigned int nr, int cmd_fd, void *buf, struct xfrm_desc *desc) +{ +	struct in_addr src, dst, tunsrc, tundst; +	struct test_desc msg; +	int ret = KSFT_FAIL; + +	src = inet_makeaddr(INADDR_B, child_ip(nr)); +	dst = inet_makeaddr(INADDR_B, grchild_ip(nr)); +	tunsrc = inet_makeaddr(INADDR_A, child_ip(nr)); +	tundst = inet_makeaddr(INADDR_A, grchild_ip(nr)); + +	/* UDP pinging without xfrm */ +	if (do_ping(cmd_fd, buf, page_size, src, true, 0, 0, udp_ping_send)) { +		printk("ping failed before setting xfrm"); +		return KSFT_FAIL; +	} + +	memset(&msg, 0, sizeof(msg)); +	msg.type = MSG_XFRM_PREPARE; +	memcpy(&msg.body.xfrm_desc, desc, sizeof(*desc)); +	write_msg(cmd_fd, &msg, 1); + +	if (xfrm_prepare(xfrm_sock, seq, src, dst, tunsrc, tundst, desc->proto)) { +		printk("failed to prepare xfrm"); +		goto cleanup; +	} + +	memset(&msg, 0, sizeof(msg)); +	msg.type = MSG_XFRM_ADD; +	memcpy(&msg.body.xfrm_desc, desc, sizeof(*desc)); +	write_msg(cmd_fd, &msg, 1); +	if (xfrm_set(xfrm_sock, seq, src, dst, tunsrc, tundst, desc)) { +		printk("failed to set xfrm"); +		goto delete; +	} + +	/* UDP pinging with xfrm tunnel */ +	if (do_ping(cmd_fd, buf, page_size, tunsrc, +				true, 0, 0, udp_ping_send)) { +		printk("ping failed for xfrm"); +		goto delete; +	} + +	ret = KSFT_PASS; +delete: +	/* xfrm delete */ +	memset(&msg, 0, sizeof(msg)); +	msg.type = MSG_XFRM_DEL; +	memcpy(&msg.body.xfrm_desc, desc, sizeof(*desc)); +	write_msg(cmd_fd, &msg, 1); + +	if (xfrm_delete(xfrm_sock, seq, src, dst, tunsrc, tundst, desc->proto)) { +		printk("failed ping to remove xfrm"); +		ret = KSFT_FAIL; +	} + +cleanup: +	memset(&msg, 0, sizeof(msg)); +	msg.type = MSG_XFRM_CLEANUP; +	memcpy(&msg.body.xfrm_desc, desc, sizeof(*desc)); +	write_msg(cmd_fd, &msg, 1); +	if (xfrm_cleanup(xfrm_sock, seq, src, dst, tunsrc, tundst)) { +		printk("failed ping to cleanup xfrm"); +		ret = KSFT_FAIL; +	} +	return ret; +} + +static int child_f(unsigned int nr, int test_desc_fd, int cmd_fd, void *buf) +{ +	struct xfrm_desc desc; +	struct test_desc msg; +	int xfrm_sock = -1; +	uint32_t seq; + +	if (switch_ns(nsfd_childa)) +		exit(KSFT_FAIL); + +	if (netlink_sock(&xfrm_sock, &seq, NETLINK_XFRM)) { +		printk("Failed to open xfrm netlink socket"); +		exit(KSFT_FAIL); +	} + +	/* Check that seq sock is ready, just for sure. */ +	memset(&msg, 0, sizeof(msg)); +	msg.type = MSG_ACK; +	write_msg(cmd_fd, &msg, 1); +	read_msg(cmd_fd, &msg, 1); +	if (msg.type != MSG_ACK) { +		printk("Ack failed"); +		exit(KSFT_FAIL); +	} + +	for (;;) { +		ssize_t received = read(test_desc_fd, &desc, sizeof(desc)); +		int ret; + +		if (received == 0) /* EOF */ +			break; + +		if (received != sizeof(desc)) { +			pr_err("read() returned %zd", received); +			exit(KSFT_FAIL); +		} + +		switch (desc.type) { +		case CREATE_TUNNEL: +			ret = child_serv(xfrm_sock, &seq, nr, +					 cmd_fd, buf, &desc); +			break; +		case ALLOCATE_SPI: +			ret = xfrm_state_allocspi(xfrm_sock, &seq, +						  -1, desc.proto); +			break; +		case MONITOR_ACQUIRE: +			ret = xfrm_monitor_acquire(xfrm_sock, &seq, nr); +			break; +		case EXPIRE_STATE: +			ret = xfrm_expire_state(xfrm_sock, &seq, nr, &desc); +			break; +		case EXPIRE_POLICY: +			ret = xfrm_expire_policy(xfrm_sock, &seq, nr, &desc); +			break; +		default: +			printk("Unknown desc type %d", desc.type); +			exit(KSFT_FAIL); +		} +		write_test_result(ret, &desc); +	} + +	close(xfrm_sock); + +	msg.type = MSG_EXIT; +	write_msg(cmd_fd, &msg, 1); +	exit(KSFT_PASS); +} + +static void grand_child_serv(unsigned int nr, int cmd_fd, void *buf, +		struct test_desc *msg, int xfrm_sock, uint32_t *seq) +{ +	struct in_addr src, dst, tunsrc, tundst; +	bool tun_reply; +	struct xfrm_desc *desc = &msg->body.xfrm_desc; + +	src = inet_makeaddr(INADDR_B, grchild_ip(nr)); +	dst = inet_makeaddr(INADDR_B, child_ip(nr)); +	tunsrc = inet_makeaddr(INADDR_A, grchild_ip(nr)); +	tundst = inet_makeaddr(INADDR_A, child_ip(nr)); + +	switch (msg->type) { +	case MSG_EXIT: +		exit(KSFT_PASS); +	case MSG_ACK: +		write_msg(cmd_fd, msg, 1); +		break; +	case MSG_PING: +		tun_reply = memcmp(&dst, &msg->body.ping.reply_ip, sizeof(in_addr_t)); +		/* UDP pinging without xfrm */ +		if (do_ping(cmd_fd, buf, page_size, tun_reply ? tunsrc : src, +				false, msg->body.ping.port, +				msg->body.ping.reply_ip, udp_ping_reply)) { +			printk("ping failed before setting xfrm"); +		} +		break; +	case MSG_XFRM_PREPARE: +		if (xfrm_prepare(xfrm_sock, seq, src, dst, tunsrc, tundst, +					desc->proto)) { +			xfrm_cleanup(xfrm_sock, seq, src, dst, tunsrc, tundst); +			printk("failed to prepare xfrm"); +		} +		break; +	case MSG_XFRM_ADD: +		if (xfrm_set(xfrm_sock, seq, src, dst, tunsrc, tundst, desc)) { +			xfrm_cleanup(xfrm_sock, seq, src, dst, tunsrc, tundst); +			printk("failed to set xfrm"); +		} +		break; +	case MSG_XFRM_DEL: +		if (xfrm_delete(xfrm_sock, seq, src, dst, tunsrc, tundst, +					desc->proto)) { +			xfrm_cleanup(xfrm_sock, seq, src, dst, tunsrc, tundst); +			printk("failed to remove xfrm"); +		} +		break; +	case MSG_XFRM_CLEANUP: +		if (xfrm_cleanup(xfrm_sock, seq, src, dst, tunsrc, tundst)) { +			printk("failed to cleanup xfrm"); +		} +		break; +	default: +		printk("got unknown msg type %d", msg->type); +	}; +} + +static int grand_child_f(unsigned int nr, int cmd_fd, void *buf) +{ +	struct test_desc msg; +	int xfrm_sock = -1; +	uint32_t seq; + +	if (switch_ns(nsfd_childb)) +		exit(KSFT_FAIL); + +	if (netlink_sock(&xfrm_sock, &seq, NETLINK_XFRM)) { +		printk("Failed to open xfrm netlink socket"); +		exit(KSFT_FAIL); +	} + +	do { +		read_msg(cmd_fd, &msg, 1); +		grand_child_serv(nr, cmd_fd, buf, &msg, xfrm_sock, &seq); +	} while (1); + +	close(xfrm_sock); +	exit(KSFT_FAIL); +} + +static int start_child(unsigned int nr, char *veth, int test_desc_fd[2]) +{ +	int cmd_sock[2]; +	void *data_map; +	pid_t child; + +	if (init_child(nsfd_childa, veth, child_ip(nr), grchild_ip(nr))) +		return -1; + +	if (init_child(nsfd_childb, veth, grchild_ip(nr), child_ip(nr))) +		return -1; + +	child = fork(); +	if (child < 0) { +		pr_err("fork()"); +		return -1; +	} else if (child) { +		/* in parent - selftest */ +		return switch_ns(nsfd_parent); +	} + +	if (close(test_desc_fd[1])) { +		pr_err("close()"); +		return -1; +	} + +	/* child */ +	data_map = mmap(0, page_size, PROT_READ | PROT_WRITE, +			MAP_SHARED | MAP_ANONYMOUS, -1, 0); +	if (data_map == MAP_FAILED) { +		pr_err("mmap()"); +		return -1; +	} + +	randomize_buffer(data_map, page_size); + +	if (socketpair(PF_LOCAL, SOCK_SEQPACKET, 0, cmd_sock)) { +		pr_err("socketpair()"); +		return -1; +	} + +	child = fork(); +	if (child < 0) { +		pr_err("fork()"); +		return -1; +	} else if (child) { +		if (close(cmd_sock[0])) { +			pr_err("close()"); +			return -1; +		} +		return child_f(nr, test_desc_fd[0], cmd_sock[1], data_map); +	} +	if (close(cmd_sock[1])) { +		pr_err("close()"); +		return -1; +	} +	return grand_child_f(nr, cmd_sock[0], data_map); +} + +static void exit_usage(char **argv) +{ +	printk("Usage: %s [nr_process]", argv[0]); +	exit(KSFT_FAIL); +} + +static int __write_desc(int test_desc_fd, struct xfrm_desc *desc) +{ +	ssize_t ret; + +	ret = write(test_desc_fd, desc, sizeof(*desc)); + +	if (ret == sizeof(*desc)) +		return 0; + +	pr_err("Writing test's desc failed %ld", ret); + +	return -1; +} + +static int write_desc(int proto, int test_desc_fd, +		char *a, char *e, char *c, char *ae) +{ +	struct xfrm_desc desc = {}; + +	desc.type = CREATE_TUNNEL; +	desc.proto = proto; + +	if (a) +		strncpy(desc.a_algo, a, ALGO_LEN - 1); +	if (e) +		strncpy(desc.e_algo, e, ALGO_LEN - 1); +	if (c) +		strncpy(desc.c_algo, c, ALGO_LEN - 1); +	if (ae) +		strncpy(desc.ae_algo, ae, ALGO_LEN - 1); + +	return __write_desc(test_desc_fd, &desc); +} + +int proto_list[] = { IPPROTO_AH, IPPROTO_COMP, IPPROTO_ESP }; +char *ah_list[] = { +	"digest_null", "hmac(md5)", "hmac(sha1)", "hmac(sha256)", +	"hmac(sha384)", "hmac(sha512)", "hmac(rmd160)", +	"xcbc(aes)", "cmac(aes)" +}; +char *comp_list[] = { +	"deflate", +#if 0 +	/* No compression backend realization */ +	"lzs", "lzjh" +#endif +}; +char *e_list[] = { +	"ecb(cipher_null)", "cbc(des)", "cbc(des3_ede)", "cbc(cast5)", +	"cbc(blowfish)", "cbc(aes)", "cbc(serpent)", "cbc(camellia)", +	"cbc(twofish)", "rfc3686(ctr(aes))" +}; +char *ae_list[] = { +#if 0 +	/* not implemented */ +	"rfc4106(gcm(aes))", "rfc4309(ccm(aes))", "rfc4543(gcm(aes))", +	"rfc7539esp(chacha20,poly1305)" +#endif +}; + +const unsigned int proto_plan = ARRAY_SIZE(ah_list) + ARRAY_SIZE(comp_list) \ +				+ (ARRAY_SIZE(ah_list) * ARRAY_SIZE(e_list)) \ +				+ ARRAY_SIZE(ae_list); + +static int write_proto_plan(int fd, int proto) +{ +	unsigned int i; + +	switch (proto) { +	case IPPROTO_AH: +		for (i = 0; i < ARRAY_SIZE(ah_list); i++) { +			if (write_desc(proto, fd, ah_list[i], 0, 0, 0)) +				return -1; +		} +		break; +	case IPPROTO_COMP: +		for (i = 0; i < ARRAY_SIZE(comp_list); i++) { +			if (write_desc(proto, fd, 0, 0, comp_list[i], 0)) +				return -1; +		} +		break; +	case IPPROTO_ESP: +		for (i = 0; i < ARRAY_SIZE(ah_list); i++) { +			int j; + +			for (j = 0; j < ARRAY_SIZE(e_list); j++) { +				if (write_desc(proto, fd, ah_list[i], +							e_list[j], 0, 0)) +					return -1; +			} +		} +		for (i = 0; i < ARRAY_SIZE(ae_list); i++) { +			if (write_desc(proto, fd, 0, 0, 0, ae_list[i])) +				return -1; +		} +		break; +	default: +		printk("BUG: Specified unknown proto %d", proto); +		return -1; +	} + +	return 0; +} + +/* + * Some structures in xfrm uapi header differ in size between + * 64-bit and 32-bit ABI: + * + *             32-bit UABI               |            64-bit UABI + *  -------------------------------------|------------------------------------- + *   sizeof(xfrm_usersa_info)     = 220  |  sizeof(xfrm_usersa_info)     = 224 + *   sizeof(xfrm_userpolicy_info) = 164  |  sizeof(xfrm_userpolicy_info) = 168 + *   sizeof(xfrm_userspi_info)    = 228  |  sizeof(xfrm_userspi_info)    = 232 + *   sizeof(xfrm_user_acquire)    = 276  |  sizeof(xfrm_user_acquire)    = 280 + *   sizeof(xfrm_user_expire)     = 224  |  sizeof(xfrm_user_expire)     = 232 + *   sizeof(xfrm_user_polexpire)  = 168  |  sizeof(xfrm_user_polexpire)  = 176 + * + * Check the affected by the UABI difference structures. + */ +const unsigned int compat_plan = 4; +static int write_compat_struct_tests(int test_desc_fd) +{ +	struct xfrm_desc desc = {}; + +	desc.type = ALLOCATE_SPI; +	desc.proto = IPPROTO_AH; +	strncpy(desc.a_algo, ah_list[0], ALGO_LEN - 1); + +	if (__write_desc(test_desc_fd, &desc)) +		return -1; + +	desc.type = MONITOR_ACQUIRE; +	if (__write_desc(test_desc_fd, &desc)) +		return -1; + +	desc.type = EXPIRE_STATE; +	if (__write_desc(test_desc_fd, &desc)) +		return -1; + +	desc.type = EXPIRE_POLICY; +	if (__write_desc(test_desc_fd, &desc)) +		return -1; + +	return 0; +} + +static int write_test_plan(int test_desc_fd) +{ +	unsigned int i; +	pid_t child; + +	child = fork(); +	if (child < 0) { +		pr_err("fork()"); +		return -1; +	} +	if (child) { +		if (close(test_desc_fd)) +			printk("close(): %m"); +		return 0; +	} + +	if (write_compat_struct_tests(test_desc_fd)) +		exit(KSFT_FAIL); + +	for (i = 0; i < ARRAY_SIZE(proto_list); i++) { +		if (write_proto_plan(test_desc_fd, proto_list[i])) +			exit(KSFT_FAIL); +	} + +	exit(KSFT_PASS); +} + +static int children_cleanup(void) +{ +	unsigned ret = KSFT_PASS; + +	while (1) { +		int status; +		pid_t p = wait(&status); + +		if ((p < 0) && errno == ECHILD) +			break; + +		if (p < 0) { +			pr_err("wait()"); +			return KSFT_FAIL; +		} + +		if (!WIFEXITED(status)) { +			ret = KSFT_FAIL; +			continue; +		} + +		if (WEXITSTATUS(status) == KSFT_FAIL) +			ret = KSFT_FAIL; +	} + +	return ret; +} + +typedef void (*print_res)(const char *, ...); + +static int check_results(void) +{ +	struct test_result tr = {}; +	struct xfrm_desc *d = &tr.desc; +	int ret = KSFT_PASS; + +	while (1) { +		ssize_t received = read(results_fd[0], &tr, sizeof(tr)); +		print_res result; + +		if (received == 0) /* EOF */ +			break; + +		if (received != sizeof(tr)) { +			pr_err("read() returned %zd", received); +			return KSFT_FAIL; +		} + +		switch (tr.res) { +		case KSFT_PASS: +			result = ksft_test_result_pass; +			break; +		case KSFT_FAIL: +		default: +			result = ksft_test_result_fail; +			ret = KSFT_FAIL; +		} + +		result(" %s: [%u, '%s', '%s', '%s', '%s', %u]\n", +		       desc_name[d->type], (unsigned int)d->proto, d->a_algo, +		       d->e_algo, d->c_algo, d->ae_algo, d->icv_len); +	} + +	return ret; +} + +int main(int argc, char **argv) +{ +	unsigned int nr_process = 1; +	int route_sock = -1, ret = KSFT_SKIP; +	int test_desc_fd[2]; +	uint32_t route_seq; +	unsigned int i; + +	if (argc > 2) +		exit_usage(argv); + +	if (argc > 1) { +		char *endptr; + +		errno = 0; +		nr_process = strtol(argv[1], &endptr, 10); +		if ((errno == ERANGE && (nr_process == LONG_MAX || nr_process == LONG_MIN)) +				|| (errno != 0 && nr_process == 0) +				|| (endptr == argv[1]) || (*endptr != '\0')) { +			printk("Failed to parse [nr_process]"); +			exit_usage(argv); +		} + +		if (nr_process > MAX_PROCESSES || !nr_process) { +			printk("nr_process should be between [1; %u]", +					MAX_PROCESSES); +			exit_usage(argv); +		} +	} + +	srand(time(NULL)); +	page_size = sysconf(_SC_PAGESIZE); +	if (page_size < 1) +		ksft_exit_skip("sysconf(): %m\n"); + +	if (pipe2(test_desc_fd, O_DIRECT) < 0) +		ksft_exit_skip("pipe(): %m\n"); + +	if (pipe2(results_fd, O_DIRECT) < 0) +		ksft_exit_skip("pipe(): %m\n"); + +	if (init_namespaces()) +		ksft_exit_skip("Failed to create namespaces\n"); + +	if (netlink_sock(&route_sock, &route_seq, NETLINK_ROUTE)) +		ksft_exit_skip("Failed to open netlink route socket\n"); + +	for (i = 0; i < nr_process; i++) { +		char veth[VETH_LEN]; + +		snprintf(veth, VETH_LEN, VETH_FMT, i); + +		if (veth_add(route_sock, route_seq++, veth, nsfd_childa, veth, nsfd_childb)) { +			close(route_sock); +			ksft_exit_fail_msg("Failed to create veth device"); +		} + +		if (start_child(i, veth, test_desc_fd)) { +			close(route_sock); +			ksft_exit_fail_msg("Child %u failed to start", i); +		} +	} + +	if (close(route_sock) || close(test_desc_fd[0]) || close(results_fd[1])) +		ksft_exit_fail_msg("close(): %m"); + +	ksft_set_plan(proto_plan + compat_plan); + +	if (write_test_plan(test_desc_fd[1])) +		ksft_exit_fail_msg("Failed to write test plan to pipe"); + +	ret = check_results(); + +	if (children_cleanup() == KSFT_FAIL) +		exit(KSFT_FAIL); + +	exit(ret); +}  | 
