mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2195 lines
52 KiB
2195 lines
52 KiB
// 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); |
|
}
|
|
|