Re: [PATCH net-next v9 23/23] testing/selftest: add test tool and scripts for ovpn module

Shuah Khan
2024-10-16T21:14:23Z

On 10/15/24 19:03, Antonio Quartulli wrote:
> The ovpn-cli tool can be compiled and used as selftest for the ovpn
> kernel module.
>
> It implements the netlink API and can thus be integrated in any
> script for more automated testing.
>
> Along with the tool, 2 scripts are added that perform basic
> functionality tests by means of network namespaces.
>
> The scripts can be performed in sequence by running run.sh
>
> Cc: shuah@kernel.org
> Cc: linux-kselftest@vger.kernel.org
> Signed-off-by: Antonio Quartulli <antonio@openvpn.net>

I almost gave my Reviewed-by when I saw the very long argument parsing
in the main() - please see comment below under main().

Let's simply the logic using getopt() - it is way too long and
complex.

> ---
> MAINTAINERS | 1 +
> tools/testing/selftests/Makefile | 1 +
> tools/testing/selftests/net/ovpn/.gitignore | 2 +
> tools/testing/selftests/net/ovpn/Makefile | 16 +
> tools/testing/selftests/net/ovpn/config | 10 +
> tools/testing/selftests/net/ovpn/data-test-tcp.sh | 9 +
> tools/testing/selftests/net/ovpn/data-test.sh | 157 ++
> tools/testing/selftests/net/ovpn/data64.key | 5 +
> tools/testing/selftests/net/ovpn/float-test.sh | 122 ++
> tools/testing/selftests/net/ovpn/ovpn-cli.c | 2136 +++++++++++++++++++++
> tools/testing/selftests/net/ovpn/tcp_peers.txt | 5 +
> tools/testing/selftests/net/ovpn/udp_peers.txt | 5 +
> 12 files changed, 2469 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 8edccdabd96ab4a4e8e9ed24d18ecbcd6d33ecec..ee94f245a18557974ff35b82d9d6d883357e6a01 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -17476,6 +17476,7 @@ T: git https://github.com/OpenVPN/linux-kernel-ovpn.git
> F: Documentation/netlink/specs/ovpn.yaml
> F: drivers/net/ovpn/
> F: include/uapi/linux/ovpn.h
> +F: tools/testing/selftests/net/ovpn/
>
> P54 WIRELESS DRIVER
> M: Christian Lamparter <chunkeey@googlemail.com>
> diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
> index b38199965f99014f3e2636fe8d705972f2c0d148..3ae2dd6492ca70d5e317c6e5b4e2560b060e3214 100644
> --- a/tools/testing/selftests/Makefile
> +++ b/tools/testing/selftests/Makefile
> @@ -68,6 +68,7 @@ TARGETS += net/hsr
> TARGETS += net/mptcp
> TARGETS += net/netfilter
> TARGETS += net/openvswitch
> +TARGETS += net/ovpn
> TARGETS += net/packetdrill
> TARGETS += net/rds
> TARGETS += net/tcp_ao
> diff --git a/tools/testing/selftests/net/ovpn/.gitignore b/tools/testing/selftests/net/ovpn/.gitignore
> new file mode 100644
> index 0000000000000000000000000000000000000000..ee44c081ca7c089933659689303c303a9fa9713b
> --- /dev/null
> +++ b/tools/testing/selftests/net/ovpn/.gitignore
> @@ -0,0 +1,2 @@
> +# SPDX-License-Identifier: GPL-2.0+
> +ovpn-cli
> diff --git a/tools/testing/selftests/net/ovpn/Makefile b/tools/testing/selftests/net/ovpn/Makefile
> new file mode 100644
> index 0000000000000000000000000000000000000000..9510c9b171390809bcce9f8c81a3a0abce885ac0
> --- /dev/null
> +++ b/tools/testing/selftests/net/ovpn/Makefile
> @@ -0,0 +1,16 @@
> +# SPDX-License-Identifier: GPL-2.0
> +# Copyright (C) 2020-2024 OpenVPN, Inc.
> +#
> +CFLAGS = -Wall -Wl,--no-as-needed -g -O2 $(KHDR_INCLUDES)
> +CFLAGS += $(shell pkg-config --cflags libnl-3.0 libnl-genl-3.0)
> +
> +LDFLAGS = -lmbedtls -lmbedcrypto
> +LDFLAGS += $(shell pkg-config --libs libnl-3.0 libnl-genl-3.0)
> +
> +TEST_PROGS = data-test.sh \
> + data-test-tcp.sh \
> + float-test.sh
> +
> +TEST_GEN_FILES = ovpn-cli
> +
> +include ../../lib.mk
> diff --git a/tools/testing/selftests/net/ovpn/config b/tools/testing/selftests/net/ovpn/config
> new file mode 100644
> index 0000000000000000000000000000000000000000..71946ba9fa175c191725e369eb9b973503d9d9c4
> --- /dev/null
> +++ b/tools/testing/selftests/net/ovpn/config
> @@ -0,0 +1,10 @@
> +CONFIG_NET=y
> +CONFIG_INET=y
> +CONFIG_STREAM_PARSER=y
> +CONFIG_NET_UDP_TUNNEL=y
> +CONFIG_DST_CACHE=y
> +CONFIG_CRYPTO=y
> +CONFIG_CRYPTO_AES=y
> +CONFIG_CRYPTO_GCM=y
> +CONFIG_CRYPTO_CHACHA20POLY1305=y
> +CONFIG_OVPN=m
> diff --git a/tools/testing/selftests/net/ovpn/data-test-tcp.sh b/tools/testing/selftests/net/ovpn/data-test-tcp.sh
> new file mode 100755
> index 0000000000000000000000000000000000000000..65f05659b5fd8f05d216f2c22898e7f89a6ea4de
> --- /dev/null
> +++ b/tools/testing/selftests/net/ovpn/data-test-tcp.sh
> @@ -0,0 +1,9 @@
> +#!/bin/bash
> +# SPDX-License-Identifier: GPL-2.0
> +# Copyright (C) 2024 OpenVPN, Inc.
> +#
> +# Author: Antonio Quartulli <antonio@openvpn.net>
> +
> +PROTO="TCP"
> +
> +source data-test.sh
> diff --git a/tools/testing/selftests/net/ovpn/data-test.sh b/tools/testing/selftests/net/ovpn/data-test.sh
> new file mode 100755
> index 0000000000000000000000000000000000000000..9c649c680b57bbebaf58761193950def7d3dad74
> --- /dev/null
> +++ b/tools/testing/selftests/net/ovpn/data-test.sh
> @@ -0,0 +1,157 @@
> +#!/bin/bash
> +# SPDX-License-Identifier: GPL-2.0
> +# Copyright (C) 2020-2024 OpenVPN, Inc.
> +#
> +# Author: Antonio Quartulli <antonio@openvpn.net>
> +
> +#set -x
> +set -e
> +
> +UDP_PEERS_FILE=${UDP_PEERS_FILE:-udp_peers.txt}
> +TCP_PEERS_FILE=${TCP_PEERS_FILE:-tcp_peers.txt}
> +OVPN_CLI=${OVPN_CLI:-./ovpn-cli}
> +ALG=${ALG:-aes}
> +PROTO=${PROTO:-UDP}
> +
> +create_ns() {
> + ip netns add peer${1}
> +}
> +
> +setup_ns() {
> + MODE="P2P"
> +
> + if [ ${1} -eq 0 ]; then
> + MODE="MP"
> + for p in $(seq 1 ${NUM_PEERS}); do
> + ip link add veth${p} netns peer0 type veth peer name veth${p} netns peer${p}
> +
> + ip -n peer0 addr add 10.10.${p}.1/24 dev veth${p}
> + ip -n peer0 link set veth${p} up
> +
> + ip -n peer${p} addr add 10.10.${p}.2/24 dev veth${p}
> + ip -n peer${p} link set veth${p} up
> + done
> + fi
> +
> + ip netns exec peer${1} ${OVPN_CLI} new_iface tun${1} $MODE
> + ip -n peer${1} addr add ${2} dev tun${1}
> + ip -n peer${1} link set tun${1} up
> +}
> +
> +add_peer() {
> + if [ "${PROTO}" == "UDP" ]; then
> + if [ ${1} -eq 0 ]; then
> + ip netns exec peer0 ${OVPN_CLI} new_multi_peer tun0 1 ${UDP_PEERS_FILE}
> +
> + for p in $(seq 1 ${NUM_PEERS}); do
> + ip netns exec peer0 ${OVPN_CLI} new_key tun0 ${p} 1 0 ${ALG} 0 \
> + data64.key
> + done
> + else
> + ip netns exec peer${1} ${OVPN_CLI} new_peer tun${1} 1 ${1} 10.10.${1}.1 1
> + ip netns exec peer${1} ${OVPN_CLI} new_key tun${1} ${1} 1 0 ${ALG} 1 \
> + data64.key
> + fi
> + else
> + if [ ${1} -eq 0 ]; then
> + (ip netns exec peer0 ${OVPN_CLI} listen tun0 1 ${TCP_PEERS_FILE} && {
> + for p in $(seq 1 ${NUM_PEERS}); do
> + ip netns exec peer0 ${OVPN_CLI} new_key tun0 ${p} 1 0 \
> + ${ALG} 0 data64.key
> + done
> + }) &
> + sleep 5
> + else
> + ip netns exec peer${1} ${OVPN_CLI} connect tun${1} ${1} 10.10.${1}.1 1 \
> + data64.key
> + fi
> + fi
> +}
> +
> +cleanup() {
> + for p in $(seq 1 10); do
> + ip -n peer0 link del veth${p} 2>/dev/null || true
> + done
> + for p in $(seq 0 10); do
> + ip netns exec peer${p} ${OVPN_CLI} del_iface tun${p} 2>/dev/null || true
> + ip netns del peer${p} 2>/dev/null || true
> + done
> +}
> +
> +if [ "${PROTO}" == "UDP" ]; then
> + NUM_PEERS=${NUM_PEERS:-$(wc -l ${UDP_PEERS_FILE} | awk '{print $1}')}
> +else
> + NUM_PEERS=${NUM_PEERS:-$(wc -l ${TCP_PEERS_FILE} | awk '{print $1}')}
> +fi
> +
> +cleanup
> +
> +modprobe -q ovpn || true
> +
> +for p in $(seq 0 ${NUM_PEERS}); do
> + create_ns ${p}
> +done
> +
> +for p in $(seq 0 ${NUM_PEERS}); do
> + setup_ns ${p} 5.5.5.$((${p} + 1))/24
> +done
> +
> +for p in $(seq 0 ${NUM_PEERS}); do
> + add_peer ${p}
> +done
> +
> +for p in $(seq 1 ${NUM_PEERS}); do
> + ip netns exec peer0 ${OVPN_CLI} set_peer tun0 ${p} 60 120
> + ip netns exec peer${p} ${OVPN_CLI} set_peer tun${p} ${p} 60 120
> +done
> +
> +for p in $(seq 1 ${NUM_PEERS}); do
> + ip netns exec peer0 ping -qfc 1000 -w 5 5.5.5.$((${p} + 1))
> +done
> +
> +ip netns exec peer0 iperf3 -1 -s &
> +sleep 1
> +ip netns exec peer1 iperf3 -Z -t 3 -c 5.5.5.1
> +
> +for p in $(seq 1 ${NUM_PEERS}); do
> + ip netns exec peer0 ${OVPN_CLI} new_key tun0 ${p} 2 1 ${ALG} 0 data64.key
> + ip netns exec peer${p} ${OVPN_CLI} new_key tun${p} ${p} 2 1 ${ALG} 1 data64.key
> + ip netns exec peer${p} ${OVPN_CLI} swap_keys tun${p} ${p}
> +done
> +
> +sleep 1
> +echo "Querying all peers:"
> +ip netns exec peer0 ${OVPN_CLI} get_peer tun0
> +ip netns exec peer1 ${OVPN_CLI} get_peer tun1
> +
> +echo "Querying peer 1:"
> +ip netns exec peer0 ${OVPN_CLI} get_peer tun0 1
> +
> +echo "Querying non-existent peer 10:"
> +ip netns exec peer0 ${OVPN_CLI} get_peer tun0 10 || true
> +
> +ip netns exec peer0 ${OVPN_CLI} del_peer tun0 1
> +ip netns exec peer1 ${OVPN_CLI} del_peer tun1 1
> +
> +echo "Setting timeout to 10s MP:"
> +# bring ifaces down to prevent traffic being sent
> +for p in $(seq 0 ${NUM_PEERS}); do
> + ip -n peer${p} link set tun${p} down
> +done
> +# set short timeout
> +for p in $(seq 2 ${NUM_PEERS}); do
> + ip netns exec peer0 ${OVPN_CLI} set_peer tun0 ${p} 10 10 || true
> + ip netns exec peer${p} ${OVPN_CLI} set_peer tun${p} ${p} 0 0
> +done
> +# wait for peers to timeout
> +sleep 15
> +
> +echo "Setting timeout to 10s P2P:"
> +for p in $(seq 2 ${NUM_PEERS}); do
> + ip netns exec peer${p} ${OVPN_CLI} set_peer tun${p} ${p} 10 10
> +done
> +sleep 15
> +
> +cleanup
> +
> +modprobe -r ovpn || true
> diff --git a/tools/testing/selftests/net/ovpn/data64.key b/tools/testing/selftests/net/ovpn/data64.key
> new file mode 100644
> index 0000000000000000000000000000000000000000..a99e88c4e290f58b12f399b857b873f308d9ba09
> --- /dev/null
> +++ b/tools/testing/selftests/net/ovpn/data64.key
> @@ -0,0 +1,5 @@
> +jRqMACN7d7/aFQNT8S7jkrBD8uwrgHbG5OQZP2eu4R1Y7tfpS2bf5RHv06Vi163CGoaIiTX99R3B
> +ia9ycAH8Wz1+9PWv51dnBLur9jbShlgZ2QHLtUc4a/gfT7zZwULXuuxdLnvR21DDeMBaTbkgbai9
> +uvAa7ne1liIgGFzbv+Bas4HDVrygxIxuAnP5Qgc3648IJkZ0QEXPF+O9f0n5+QIvGCxkAUVx+5K6
> +KIs+SoeWXnAopELmoGSjUpFtJbagXK82HfdqpuUxT2Tnuef0/14SzVE/vNleBNu2ZbyrSAaah8tE
> +BofkPJUBFY+YQcfZNM5Dgrw3i+Bpmpq/gpdg5w==
> diff --git a/tools/testing/selftests/net/ovpn/float-test.sh b/tools/testing/selftests/net/ovpn/float-test.sh
> new file mode 100755
> index 0000000000000000000000000000000000000000..704ecf72ad1fe4b8c49a93d33e6a88d0a315b42a
> --- /dev/null
> +++ b/tools/testing/selftests/net/ovpn/float-test.sh
> @@ -0,0 +1,122 @@
> +#!/bin/bash
> +# SPDX-License-Identifier: GPL-2.0
> +# Copyright (C) 2020-2024 OpenVPN, Inc.
> +#
> +# Author: Antonio Quartulli <antonio@openvpn.net>
> +
> +#set -x
> +set -e
> +
> +UDP_PEERS_FILE=${UDP_PEERS_FILE:-udp_peers.txt}
> +TCP_PEERS_FILE=${TCP_PEERS_FILE:-tcp_peers.txt}
> +OVPN_CLI=${OVPN_CLI:-./ovpn-cli}
> +ALG=${ALG:-aes}
> +PROTO=${PROTO:-UDP}
> +
> +create_ns() {
> + ip netns add peer${1}
> +}
> +
> +setup_ns() {
> + MODE="P2P"
> +
> + if [ ${1} -eq 0 ]; then
> + MODE="MP"
> + for p in $(seq 1 ${NUM_PEERS}); do
> + ip link add veth${p} netns peer0 type veth peer name veth${p} netns peer${p}
> +
> + ip -n peer0 addr add 10.10.${p}.1/24 dev veth${p}
> + ip -n peer0 link set veth${p} up
> +
> + ip -n peer${p} addr add 10.10.${p}.2/24 dev veth${p}
> + ip -n peer${p} link set veth${p} up
> + done
> + fi
> +
> + ip netns exec peer${1} ${OVPN_CLI} new_iface tun${1} $MODE
> + ip -n peer${1} addr add ${2} dev tun${1}
> + ip -n peer${1} link set tun${1} up
> +}
> +
> +add_peer() {
> + if [ "${PROTO}" == "UDP" ]; then
> + if [ ${1} -eq 0 ]; then
> + ip netns exec peer0 ${OVPN_CLI} new_multi_peer tun0 1 ${UDP_PEERS_FILE}
> +
> + for p in $(seq 1 ${NUM_PEERS}); do
> + ip netns exec peer0 ${OVPN_CLI} new_key tun0 ${p} 1 0 ${ALG} 0 \
> + data64.key
> + done
> + else
> + ip netns exec peer${1} ${OVPN_CLI} new_peer tun${1} 1 ${1} 10.10.${1}.1 1
> + ip netns exec peer${1} ${OVPN_CLI} new_key tun${1} ${1} 1 0 ${ALG} 1 \
> + data64.key
> + fi
> + else
> + if [ ${1} -eq 0 ]; then
> + (ip netns exec peer0 ${OVPN_CLI} listen tun0 1 ${TCP_PEERS_FILE} && {
> + for p in $(seq 1 ${NUM_PEERS}); do
> + ip netns exec peer0 ${OVPN_CLI} new_key tun0 ${p} 1 0 \
> + ${ALG} 0 data64.key
> + done
> + }) &
> + sleep 5
> + else
> + ip netns exec peer${1} ${OVPN_CLI} connect tun${1} ${1} 10.10.${1}.1 1 \
> + 5.5.5.1 data64.key
> + fi
> + fi
> +}
> +
> +cleanup() {
> + for p in $(seq 1 10); do
> + ip -n peer0 link del veth${p} 2>/dev/null || true
> + done
> + for p in $(seq 0 10); do
> + ip netns exec peer${p} ${OVPN_CLI} del_iface tun${p} 2>/dev/null || true
> + ip netns del peer${p} 2>/dev/null || true
> + done
> +}
> +
> +if [ "${PROTO}" == "UDP" ]; then
> + NUM_PEERS=${NUM_PEERS:-$(wc -l ${UDP_PEERS_FILE} | awk '{print $1}')}
> +else
> + NUM_PEERS=${NUM_PEERS:-$(wc -l ${TCP_PEERS_FILE} | awk '{print $1}')}
> +fi
> +
> +cleanup
> +
> +modprobe -q ovpn || true
> +
> +for p in $(seq 0 ${NUM_PEERS}); do
> + create_ns ${p}
> +done
> +
> +for p in $(seq 0 ${NUM_PEERS}); do
> + setup_ns ${p} 5.5.5.$((${p} + 1))/24
> +done
> +
> +for p in $(seq 0 ${NUM_PEERS}); do
> + add_peer ${p}
> +done
> +
> +for p in $(seq 1 ${NUM_PEERS}); do
> + ip netns exec peer0 ${OVPN_CLI} set_peer tun0 ${p} 60 120
> + ip netns exec peer${p} ${OVPN_CLI} set_peer tun${p} ${p} 60 120
> +done
> +
> +for p in $(seq 1 ${NUM_PEERS}); do
> + ip netns exec peer0 ping -qfc 1000 -w 5 5.5.5.$((${p} + 1))
> +done
> +# make clients float..
> +for p in $(seq 1 ${NUM_PEERS}); do
> + ip -n peer${p} addr del 10.10.${p}.2/24 dev veth${p}
> + ip -n peer${p} addr add 10.10.${p}.3/24 dev veth${p}
> +done
> +for p in $(seq 1 ${NUM_PEERS}); do
> + ip netns exec peer${p} ping -qfc 1000 -w 5 5.5.5.1
> +done
> +
> +cleanup
> +
> +modprobe -r ovpn || true
> diff --git a/tools/testing/selftests/net/ovpn/ovpn-cli.c b/tools/testing/selftests/net/ovpn/ovpn-cli.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..546c7e345ce9fb9921ef4532f0756d9f8938232c
> --- /dev/null
> +++ b/tools/testing/selftests/net/ovpn/ovpn-cli.c
> @@ -0,0 +1,2136 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* OpenVPN data channel accelerator
> + *
> + * Copyright (C) 2020-2024 OpenVPN, Inc.
> + *
> + * Author: Antonio Quartulli <antonio@openvpn.net>
> + */
> +
> +#include <stdio.h>
> +#include <inttypes.h>
> +#include <stdbool.h>
> +#include <string.h>
> +#include <errno.h>
> +#include <unistd.h>
> +#include <arpa/inet.h>
> +#include <net/if.h>
> +#include <netinet/in.h>
> +#include <time.h>
> +
> +#include <linux/ovpn.h>
> +#include <linux/types.h>
> +#include <linux/netlink.h>
> +
> +#include <netlink/socket.h>
> +#include <netlink/netlink.h>
> +#include <netlink/genl/genl.h>
> +#include <netlink/genl/family.h>
> +#include <netlink/genl/ctrl.h>
> +
> +#include <mbedtls/base64.h>
> +#include <mbedtls/error.h>
> +
> +#include <sys/socket.h>
> +
> +/* defines to make checkpatch happy */
> +#define strscpy strncpy
> +#define __always_unused __attribute__((__unused__))
> +
> +/* libnl < 3.5.0 does not set the NLA_F_NESTED on its own, therefore we
> + * have to explicitly do it to prevent the kernel from failing upon
> + * parsing of the message
> + */
> +#define nla_nest_start(_msg, _type) \
> + nla_nest_start(_msg, (_type) | NLA_F_NESTED)
> +
> +uint64_t nla_get_uint(struct nlattr *attr)
> +{
> + if (nla_len(attr) == sizeof(uint32_t))
> + return nla_get_u32(attr);
> + else
> + return nla_get_u64(attr);
> +}
> +
> +typedef int (*ovpn_nl_cb)(struct nl_msg *msg, void *arg);
> +
> +enum ovpn_key_direction {
> + KEY_DIR_IN = 0,
> + KEY_DIR_OUT,
> +};
> +
> +#define KEY_LEN (256 / 8)
> +#define NONCE_LEN 8
> +
> +#define PEER_ID_UNDEF 0x00FFFFFF
> +
> +struct nl_ctx {
> + struct nl_sock *nl_sock;
> + struct nl_msg *nl_msg;
> + struct nl_cb *nl_cb;
> +
> + int ovpn_dco_id;
> +};
> +
> +struct ovpn_ctx {
> + __u8 key_enc[KEY_LEN];
> + __u8 key_dec[KEY_LEN];
> + __u8 nonce[NONCE_LEN];
> +
> + enum ovpn_cipher_alg cipher;
> +
> + sa_family_t sa_family;
> +
> + __u32 peer_id;
> + __u16 lport;
> +
> + union {
> + struct sockaddr_in in4;
> + struct sockaddr_in6 in6;
> + } remote;
> +
> + union {
> + struct sockaddr_in in4;
> + struct sockaddr_in6 in6;
> + } peer_ip;
> +
> + bool peer_ip_set;
> +
> + unsigned int ifindex;
> + char ifname[IFNAMSIZ];
> + enum ovpn_mode mode;
> + bool mode_set;
> +
> + int socket;
> + int cli_socket;
> +
> + __u32 keepalive_interval;
> + __u32 keepalive_timeout;
> +
> + enum ovpn_key_direction key_dir;
> + enum ovpn_key_slot key_slot;
> + int key_id;
> +};
> +
> +static int ovpn_nl_recvmsgs(struct nl_ctx *ctx)
> +{
> + int ret;
> +
> + ret = nl_recvmsgs(ctx->nl_sock, ctx->nl_cb);
> +
> + switch (ret) {
> + case -NLE_INTR:
> + fprintf(stderr,
> + "netlink received interrupt due to signal - ignoring\n");
> + break;
> + case -NLE_NOMEM:
> + fprintf(stderr, "netlink out of memory error\n");
> + break;
> + case -NLE_AGAIN:
> + fprintf(stderr,
> + "netlink reports blocking read - aborting wait\n");
> + break;
> + default:
> + if (ret)
> + fprintf(stderr, "netlink reports error (%d): %s\n",
> + ret, nl_geterror(-ret));
> + break;
> + }
> +
> + return ret;
> +}
> +
> +static struct nl_ctx *nl_ctx_alloc_flags(struct ovpn_ctx *ovpn, int cmd,
> + int flags)
> +{
> + struct nl_ctx *ctx;
> + int err, ret;
> +
> + ctx = calloc(1, sizeof(*ctx));
> + if (!ctx)
> + return NULL;
> +
> + ctx->nl_sock = nl_socket_alloc();
> + if (!ctx->nl_sock) {
> + fprintf(stderr, "cannot allocate netlink socket\n");
> + goto err_free;
> + }
> +
> + nl_socket_set_buffer_size(ctx->nl_sock, 8192, 8192);
> +
> + ret = genl_connect(ctx->nl_sock);
> + if (ret) {
> + fprintf(stderr, "cannot connect to generic netlink: %s\n",
> + nl_geterror(ret));
> + goto err_sock;
> + }
> +
> + /* enable Extended ACK for detailed error reporting */
> + err = 1;
> + setsockopt(nl_socket_get_fd(ctx->nl_sock), SOL_NETLINK, NETLINK_EXT_ACK,
> + &err, sizeof(err));
> +
> + ctx->ovpn_dco_id = genl_ctrl_resolve(ctx->nl_sock, OVPN_FAMILY_NAME);
> + if (ctx->ovpn_dco_id < 0) {
> + fprintf(stderr, "cannot find ovpn_dco netlink component: %d\n",
> + ctx->ovpn_dco_id);
> + goto err_free;
> + }
> +
> + ctx->nl_msg = nlmsg_alloc();
> + if (!ctx->nl_msg) {
> + fprintf(stderr, "cannot allocate netlink message\n");
> + goto err_sock;
> + }
> +
> + ctx->nl_cb = nl_cb_alloc(NL_CB_DEFAULT);
> + if (!ctx->nl_cb) {
> + fprintf(stderr, "failed to allocate netlink callback\n");
> + goto err_msg;
> + }
> +
> + nl_socket_set_cb(ctx->nl_sock, ctx->nl_cb);
> +
> + genlmsg_put(ctx->nl_msg, 0, 0, ctx->ovpn_dco_id, 0, flags, cmd, 0);
> +
> + if (ovpn->ifindex > 0)
> + NLA_PUT_U32(ctx->nl_msg, OVPN_A_IFINDEX, ovpn->ifindex);
> +
> + return ctx;
> +nla_put_failure:
> +err_msg:
> + nlmsg_free(ctx->nl_msg);
> +err_sock:
> + nl_socket_free(ctx->nl_sock);
> +err_free:
> + free(ctx);
> + return NULL;
> +}
> +
> +static struct nl_ctx *nl_ctx_alloc(struct ovpn_ctx *ovpn, int cmd)
> +{
> + return nl_ctx_alloc_flags(ovpn, cmd, 0);
> +}
> +
> +static void nl_ctx_free(struct nl_ctx *ctx)
> +{
> + if (!ctx)
> + return;
> +
> + nl_socket_free(ctx->nl_sock);
> + nlmsg_free(ctx->nl_msg);
> + nl_cb_put(ctx->nl_cb);
> + free(ctx);
> +}
> +
> +static int ovpn_nl_cb_error(struct sockaddr_nl (*nla)__always_unused,
> + struct nlmsgerr *err, void *arg)
> +{
> + struct nlmsghdr *nlh = (struct nlmsghdr *)err - 1;
> + struct nlattr *tb_msg[NLMSGERR_ATTR_MAX + 1];
> + int len = nlh->nlmsg_len;
> + struct nlattr *attrs;
> + int *ret = arg;
> + int ack_len = sizeof(*nlh) + sizeof(int) + sizeof(*nlh);
> +
> + *ret = err->error;
> +
> + if (!(nlh->nlmsg_flags & NLM_F_ACK_TLVS))
> + return NL_STOP;
> +
> + if (!(nlh->nlmsg_flags & NLM_F_CAPPED))
> + ack_len += err->msg.nlmsg_len - sizeof(*nlh);
> +
> + if (len <= ack_len)
> + return NL_STOP;
> +
> + attrs = (void *)((unsigned char *)nlh + ack_len);
> + len -= ack_len;
> +
> + nla_parse(tb_msg, NLMSGERR_ATTR_MAX, attrs, len, NULL);
> + if (tb_msg[NLMSGERR_ATTR_MSG]) {
> + len = strnlen((char *)nla_data(tb_msg[NLMSGERR_ATTR_MSG]),
> + nla_len(tb_msg[NLMSGERR_ATTR_MSG]));
> + fprintf(stderr, "kernel error: %*s\n", len,
> + (char *)nla_data(tb_msg[NLMSGERR_ATTR_MSG]));
> + }
> +
> + if (tb_msg[NLMSGERR_ATTR_MISS_NEST]) {
> + fprintf(stderr, "missing required nesting type %u\n",
> + nla_get_u32(tb_msg[NLMSGERR_ATTR_MISS_NEST]));
> + }
> +
> + if (tb_msg[NLMSGERR_ATTR_MISS_TYPE]) {
> + fprintf(stderr, "missing required attribute type %u\n",
> + nla_get_u32(tb_msg[NLMSGERR_ATTR_MISS_TYPE]));
> + }
> +
> + return NL_STOP;
> +}
> +
> +static int ovpn_nl_cb_finish(struct nl_msg (*msg)__always_unused,
> + void *arg)
> +{
> + int *status = arg;
> +
> + *status = 0;
> + return NL_SKIP;
> +}
> +
> +static int ovpn_nl_cb_ack(struct nl_msg (*msg)__always_unused,
> + void *arg)
> +{
> + int *status = arg;
> +
> + *status = 0;
> + return NL_STOP;
> +}
> +
> +static int ovpn_nl_msg_send(struct nl_ctx *ctx, ovpn_nl_cb cb)
> +{
> + int status = 1;
> +
> + nl_cb_err(ctx->nl_cb, NL_CB_CUSTOM, ovpn_nl_cb_error, &status);
> + nl_cb_set(ctx->nl_cb, NL_CB_FINISH, NL_CB_CUSTOM, ovpn_nl_cb_finish,
> + &status);
> + nl_cb_set(ctx->nl_cb, NL_CB_ACK, NL_CB_CUSTOM, ovpn_nl_cb_ack, &status);
> +
> + if (cb)
> + nl_cb_set(ctx->nl_cb, NL_CB_VALID, NL_CB_CUSTOM, cb, ctx);
> +
> + nl_send_auto_complete(ctx->nl_sock, ctx->nl_msg);
> +
> + while (status == 1)
> + ovpn_nl_recvmsgs(ctx);
> +
> + if (status < 0)
> + fprintf(stderr, "failed to send netlink message: %s (%d)\n",
> + strerror(-status), status);
> +
> + return status;
> +}
> +
> +static int ovpn_read_key(const char *file, struct ovpn_ctx *ctx)
> +{
> + int idx_enc, idx_dec, ret = -1;
> + unsigned char *ckey = NULL;
> + __u8 *bkey = NULL;
> + size_t olen = 0;
> + long ckey_len;
> + FILE *fp;
> +
> + fp = fopen(file, "r");
> + if (!fp) {
> + fprintf(stderr, "cannot open: %s\n", file);
> + return -1;
> + }
> +
> + /* get file size */
> + fseek(fp, 0L, SEEK_END);
> + ckey_len = ftell(fp);
> + rewind(fp);
> +
> + /* if the file is longer, let's just read a portion */
> + if (ckey_len > 256)
> + ckey_len = 256;
> +
> + ckey = malloc(ckey_len);
> + if (!ckey)
> + goto err;
> +
> + ret = fread(ckey, 1, ckey_len, fp);
> + if (ret != ckey_len) {
> + fprintf(stderr,
> + "couldn't read enough data from key file: %dbytes read\n",
> + ret);
> + goto err;
> + }
> +
> + olen = 0;
> + ret = mbedtls_base64_decode(NULL, 0, &olen, ckey, ckey_len);
> + if (ret != MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) {
> + char buf[256];
> +
> + mbedtls_strerror(ret, buf, sizeof(buf));
> + fprintf(stderr, "unexpected base64 error1: %s (%d)\n", buf,
> + ret);
> +
> + goto err;
> + }
> +
> + bkey = malloc(olen);
> + if (!bkey) {
> + fprintf(stderr, "cannot allocate binary key buffer\n");
> + goto err;
> + }
> +
> + ret = mbedtls_base64_decode(bkey, olen, &olen, ckey, ckey_len);
> + if (ret) {
> + char buf[256];
> +
> + mbedtls_strerror(ret, buf, sizeof(buf));
> + fprintf(stderr, "unexpected base64 error2: %s (%d)\n", buf,
> + ret);
> +
> + goto err;
> + }
> +
> + if (olen < 2 * KEY_LEN + NONCE_LEN) {
> + fprintf(stderr,
> + "not enough data in key file, found %zdB but needs %dB\n",
> + olen, 2 * KEY_LEN + NONCE_LEN);
> + goto err;
> + }
> +
> + switch (ctx->key_dir) {
> + case KEY_DIR_IN:
> + idx_enc = 0;
> + idx_dec = 1;
> + break;
> + case KEY_DIR_OUT:
> + idx_enc = 1;
> + idx_dec = 0;
> + break;
> + default:
> + goto err;
> + }
> +
> + memcpy(ctx->key_enc, bkey + KEY_LEN * idx_enc, KEY_LEN);
> + memcpy(ctx->key_dec, bkey + KEY_LEN * idx_dec, KEY_LEN);
> + memcpy(ctx->nonce, bkey + 2 * KEY_LEN, NONCE_LEN);
> +
> + ret = 0;
> +
> +err:
> + fclose(fp);
> + free(bkey);
> + free(ckey);
> +
> + return ret;
> +}
> +
> +static int ovpn_read_cipher(const char *cipher, struct ovpn_ctx *ctx)
> +{
> + if (strcmp(cipher, "aes") == 0)
> + ctx->cipher = OVPN_CIPHER_ALG_AES_GCM;
> + else if (strcmp(cipher, "chachapoly") == 0)
> + ctx->cipher = OVPN_CIPHER_ALG_CHACHA20_POLY1305;
> + else if (strcmp(cipher, "none") == 0)
> + ctx->cipher = OVPN_CIPHER_ALG_NONE;
> + else
> + return -ENOTSUP;
> +
> + return 0;
> +}
> +
> +static int ovpn_read_key_direction(const char *dir, struct ovpn_ctx *ctx)
> +{
> + int in_dir;
> +
> + in_dir = strtoll(dir, NULL, 10);
> + switch (in_dir) {
> + case KEY_DIR_IN:
> + case KEY_DIR_OUT:
> + ctx->key_dir = in_dir;
> + break;
> + default:
> + fprintf(stderr,
> + "invalid key direction provided. Can be 0 or 1 only\n");
> + return -1;
> + }
> +
> + return 0;
> +}
> +
> +static int ovpn_socket(struct ovpn_ctx *ctx, sa_family_t family, int proto)
> +{
> + struct sockaddr_storage local_sock;
> + struct sockaddr_in6 *in6;
> + struct sockaddr_in *in;
> + int ret, s, sock_type;
> + size_t sock_len;
> +
> + if (proto == IPPROTO_UDP)
> + sock_type = SOCK_DGRAM;
> + else if (proto == IPPROTO_TCP)
> + sock_type = SOCK_STREAM;
> + else
> + return -EINVAL;
> +
> + s = socket(family, sock_type, 0);
> + if (s < 0) {
> + perror("cannot create socket");
> + return -1;
> + }
> +
> + memset((char *)&local_sock, 0, sizeof(local_sock));
> +
> + switch (family) {
> + case AF_INET:
> + in = (struct sockaddr_in *)&local_sock;
> + in->sin_family = family;
> + in->sin_port = htons(ctx->lport);
> + in->sin_addr.s_addr = htonl(INADDR_ANY);
> + sock_len = sizeof(*in);
> + break;
> + case AF_INET6:
> + in6 = (struct sockaddr_in6 *)&local_sock;
> + in6->sin6_family = family;
> + in6->sin6_port = htons(ctx->lport);
> + in6->sin6_addr = in6addr_any;
> + sock_len = sizeof(*in6);
> + break;
> + default:
> + return -1;
> + }
> +
> + int opt = 1;
> +
> + ret = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
> +
> + if (ret < 0) {
> + perror("setsockopt for SO_REUSEADDR");
> + return ret;
> + }
> +
> + ret = setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
> + if (ret < 0) {
> + perror("setsockopt for SO_REUSEPORT");
> + return ret;
> + }
> +
> + if (family == AF_INET6) {
> + opt = 0;
> + if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &opt,
> + sizeof(opt))) {
> + perror("failed to set IPV6_V6ONLY");
> + return -1;
> + }
> + }
> +
> + ret = bind(s, (struct sockaddr *)&local_sock, sock_len);
> + if (ret < 0) {
> + perror("cannot bind socket");
> + goto err_socket;
> + }
> +
> + ctx->socket = s;
> + ctx->sa_family = family;
> + return 0;
> +
> +err_socket:
> + close(s);
> + return -1;
> +}
> +
> +static int ovpn_udp_socket(struct ovpn_ctx *ctx, sa_family_t family)
> +{
> + return ovpn_socket(ctx, family, IPPROTO_UDP);
> +}
> +
> +static int ovpn_listen(struct ovpn_ctx *ctx, sa_family_t family)
> +{
> + int ret;
> +
> + ret = ovpn_socket(ctx, family, IPPROTO_TCP);
> + if (ret < 0)
> + return ret;
> +
> + ret = listen(ctx->socket, 10);
> + if (ret < 0) {
> + perror("listen");
> + close(ctx->socket);
> + return -1;
> + }
> +
> + return 0;
> +}
> +
> +static int ovpn_accept(struct ovpn_ctx *ctx)
> +{
> + socklen_t socklen;
> + int ret;
> +
> + socklen = sizeof(ctx->remote);
> + ret = accept(ctx->socket, (struct sockaddr *)&ctx->remote, &socklen);
> + if (ret < 0) {
> + perror("accept");
> + goto err;
> + }
> +
> + fprintf(stderr, "Connection received!\n");
> +
> + switch (socklen) {
> + case sizeof(struct sockaddr_in):
> + case sizeof(struct sockaddr_in6):
> + break;
> + default:
> + fprintf(stderr, "error: expecting IPv4 or IPv6 connection\n");
> + close(ret);
> + ret = -EINVAL;
> + goto err;
> + }
> +
> + return ret;
> +err:
> + close(ctx->socket);
> + return ret;
> +}
> +
> +static int ovpn_connect(struct ovpn_ctx *ovpn)
> +{
> + socklen_t socklen;
> + int s, ret;
> +
> + s = socket(ovpn->remote.in4.sin_family, SOCK_STREAM, 0);
> + if (s < 0) {
> + perror("cannot create socket");
> + return -1;
> + }
> +
> + switch (ovpn->remote.in4.sin_family) {
> + case AF_INET:
> + socklen = sizeof(struct sockaddr_in);
> + break;
> + case AF_INET6:
> + socklen = sizeof(struct sockaddr_in6);
> + break;
> + default:
> + return -EOPNOTSUPP;
> + }
> +
> + ret = connect(s, (struct sockaddr *)&ovpn->remote, socklen);
> + if (ret < 0) {
> + perror("connect");
> + goto err;
> + }
> +
> + fprintf(stderr, "connected\n");
> +
> + ovpn->socket = s;
> +
> + return 0;
> +err:
> + close(s);
> + return ret;
> +}
> +
> +static int ovpn_new_peer(struct ovpn_ctx *ovpn, bool is_tcp)
> +{
> + struct nlattr *attr;
> + struct nl_ctx *ctx;
> + int ret = -1;
> +
> + ctx = nl_ctx_alloc(ovpn, OVPN_CMD_PEER_NEW);
> + if (!ctx)
> + return -ENOMEM;
> +
> + attr = nla_nest_start(ctx->nl_msg, OVPN_A_PEER);
> + NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_ID, ovpn->peer_id);
> + NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_SOCKET, ovpn->socket);
> +
> + if (!is_tcp) {
> + switch (ovpn->remote.in4.sin_family) {
> + case AF_INET:
> + NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_REMOTE_IPV4,
> + ovpn->remote.in4.sin_addr.s_addr);
> + NLA_PUT_U16(ctx->nl_msg, OVPN_A_PEER_REMOTE_PORT,
> + ovpn->remote.in4.sin_port);
> + break;
> + case AF_INET6:
> + NLA_PUT(ctx->nl_msg, OVPN_A_PEER_REMOTE_IPV6,
> + sizeof(ovpn->remote.in6.sin6_addr),
> + &ovpn->remote.in6.sin6_addr);
> + NLA_PUT_U32(ctx->nl_msg,
> + OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID,
> + ovpn->remote.in6.sin6_scope_id);
> + NLA_PUT_U16(ctx->nl_msg, OVPN_A_PEER_REMOTE_PORT,
> + ovpn->remote.in6.sin6_port);
> + break;
> + default:
> + fprintf(stderr,
> + "Invalid family for remote socket address\n");
> + goto nla_put_failure;
> + }
> + }
> +
> + if (ovpn->peer_ip_set) {
> + switch (ovpn->peer_ip.in4.sin_family) {
> + case AF_INET:
> + NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_VPN_IPV4,
> + ovpn->peer_ip.in4.sin_addr.s_addr);
> + break;
> + case AF_INET6:
> + NLA_PUT(ctx->nl_msg, OVPN_A_PEER_VPN_IPV6,
> + sizeof(struct in6_addr),
> + &ovpn->peer_ip.in6.sin6_addr);
> + break;
> + default:
> + fprintf(stderr, "Invalid family for peer address\n");
> + goto nla_put_failure;
> + }
> + }
> +
> + nla_nest_end(ctx->nl_msg, attr);
> +
> + ret = ovpn_nl_msg_send(ctx, NULL);
> +nla_put_failure:
> + nl_ctx_free(ctx);
> + return ret;
> +}
> +
> +static int ovpn_set_peer(struct ovpn_ctx *ovpn)
> +{
> + struct nlattr *attr;
> + struct nl_ctx *ctx;
> + int ret = -1;
> +
> + ctx = nl_ctx_alloc(ovpn, OVPN_CMD_PEER_SET);
> + if (!ctx)
> + return -ENOMEM;
> +
> + attr = nla_nest_start(ctx->nl_msg, OVPN_A_PEER);
> + NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_ID, ovpn->peer_id);
> + NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_KEEPALIVE_INTERVAL,
> + ovpn->keepalive_interval);
> + NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_KEEPALIVE_TIMEOUT,
> + ovpn->keepalive_timeout);
> + nla_nest_end(ctx->nl_msg, attr);
> +
> + ret = ovpn_nl_msg_send(ctx, NULL);
> +nla_put_failure:
> + nl_ctx_free(ctx);
> + return ret;
> +}
> +
> +static int ovpn_del_peer(struct ovpn_ctx *ovpn)
> +{
> + struct nlattr *attr;
> + struct nl_ctx *ctx;
> + int ret = -1;
> +
> + ctx = nl_ctx_alloc(ovpn, OVPN_CMD_PEER_DEL);
> + if (!ctx)
> + return -ENOMEM;
> +
> + attr = nla_nest_start(ctx->nl_msg, OVPN_A_PEER);
> + NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_ID, ovpn->peer_id);
> + nla_nest_end(ctx->nl_msg, attr);
> +
> + ret = ovpn_nl_msg_send(ctx, NULL);
> +nla_put_failure:
> + nl_ctx_free(ctx);
> + return ret;
> +}
> +
> +static int ovpn_handle_peer(struct nl_msg *msg, void *arg)
> +{
> + struct nlattr *pattrs[OVPN_A_PEER_MAX + 1];
> + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
> + struct nlattr *attrs[OVPN_A_MAX + 1];
> + __u16 rport = 0, lport = 0;
> +
> + nla_parse(attrs, OVPN_A_MAX, genlmsg_attrdata(gnlh, 0),
> + genlmsg_attrlen(gnlh, 0), NULL);
> +
> + if (!attrs[OVPN_A_PEER]) {
> + fprintf(stderr, "no packet content in netlink message\n");
> + return NL_SKIP;
> + }
> +
> + nla_parse(pattrs, OVPN_A_PEER_MAX, nla_data(attrs[OVPN_A_PEER]),
> + nla_len(attrs[OVPN_A_PEER]), NULL);
> +
> + if (pattrs[OVPN_A_PEER_ID])
> + fprintf(stderr, "* Peer %u\n",
> + nla_get_u32(pattrs[OVPN_A_PEER_ID]));
> +
> + if (pattrs[OVPN_A_PEER_VPN_IPV4]) {
> + char buf[INET_ADDRSTRLEN];
> +
> + inet_ntop(AF_INET, nla_data(pattrs[OVPN_A_PEER_VPN_IPV4]),
> + buf, sizeof(buf));
> + fprintf(stderr, "\tVPN IPv4: %s\n", buf);
> + }
> +
> + if (pattrs[OVPN_A_PEER_VPN_IPV6]) {
> + char buf[INET6_ADDRSTRLEN];
> +
> + inet_ntop(AF_INET6, nla_data(pattrs[OVPN_A_PEER_VPN_IPV6]),
> + buf, sizeof(buf));
> + fprintf(stderr, "\tVPN IPv6: %s\n", buf);
> + }
> +
> + if (pattrs[OVPN_A_PEER_LOCAL_PORT])
> + lport = ntohs(nla_get_u16(pattrs[OVPN_A_PEER_LOCAL_PORT]));
> +
> + if (pattrs[OVPN_A_PEER_REMOTE_PORT])
> + rport = ntohs(nla_get_u16(pattrs[OVPN_A_PEER_REMOTE_PORT]));
> +
> + if (pattrs[OVPN_A_PEER_REMOTE_IPV6]) {
> + void *ip = pattrs[OVPN_A_PEER_REMOTE_IPV6];
> + char buf[INET6_ADDRSTRLEN];
> + int scope_id = -1;
> +
> + if (pattrs[OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID]) {
> + void *p = pattrs[OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID];
> +
> + scope_id = nla_get_u32(p);
> + }
> +
> + inet_ntop(AF_INET6, nla_data(ip), buf, sizeof(buf));
> + fprintf(stderr, "\tRemote: %s:%hu (scope-id: %u)\n", buf, rport,
> + scope_id);
> +
> + if (pattrs[OVPN_A_PEER_LOCAL_IPV6]) {
> + void *ip = pattrs[OVPN_A_PEER_LOCAL_IPV6];
> +
> + inet_ntop(AF_INET6, nla_data(ip), buf, sizeof(buf));
> + fprintf(stderr, "\tLocal: %s:%hu\n", buf, lport);
> + }
> + }
> +
> + if (pattrs[OVPN_A_PEER_REMOTE_IPV4]) {
> + void *ip = pattrs[OVPN_A_PEER_REMOTE_IPV4];
> + char buf[INET_ADDRSTRLEN];
> +
> + inet_ntop(AF_INET, nla_data(ip), buf, sizeof(buf));
> + fprintf(stderr, "\tRemote: %s:%hu\n", buf, rport);
> +
> + if (pattrs[OVPN_A_PEER_LOCAL_IPV4]) {
> + void *p = pattrs[OVPN_A_PEER_LOCAL_IPV4];
> +
> + inet_ntop(AF_INET, nla_data(p), buf, sizeof(buf));
> + fprintf(stderr, "\tLocal: %s:%hu\n", buf, lport);
> + }
> + }
> +
> + if (pattrs[OVPN_A_PEER_KEEPALIVE_INTERVAL]) {
> + void *p = pattrs[OVPN_A_PEER_KEEPALIVE_INTERVAL];
> +
> + fprintf(stderr, "\tKeepalive interval: %u sec\n",
> + nla_get_u32(p));
> + }
> +
> + if (pattrs[OVPN_A_PEER_KEEPALIVE_TIMEOUT])
> + fprintf(stderr, "\tKeepalive timeout: %u sec\n",
> + nla_get_u32(pattrs[OVPN_A_PEER_KEEPALIVE_TIMEOUT]));
> +
> + if (pattrs[OVPN_A_PEER_VPN_RX_BYTES])
> + fprintf(stderr, "\tVPN RX bytes: %" PRIu64 "\n",
> + nla_get_uint(pattrs[OVPN_A_PEER_VPN_RX_BYTES]));
> +
> + if (pattrs[OVPN_A_PEER_VPN_TX_BYTES])
> + fprintf(stderr, "\tVPN TX bytes: %" PRIu64 "\n",
> + nla_get_uint(pattrs[OVPN_A_PEER_VPN_TX_BYTES]));
> +
> + if (pattrs[OVPN_A_PEER_VPN_RX_PACKETS])
> + fprintf(stderr, "\tVPN RX packets: %" PRIu64 "\n",
> + nla_get_uint(pattrs[OVPN_A_PEER_VPN_RX_PACKETS]));
> +
> + if (pattrs[OVPN_A_PEER_VPN_TX_PACKETS])
> + fprintf(stderr, "\tVPN TX packets: %" PRIu64 "\n",
> + nla_get_uint(pattrs[OVPN_A_PEER_VPN_TX_PACKETS]));
> +
> + if (pattrs[OVPN_A_PEER_LINK_RX_BYTES])
> + fprintf(stderr, "\tLINK RX bytes: %" PRIu64 "\n",
> + nla_get_uint(pattrs[OVPN_A_PEER_LINK_RX_BYTES]));
> +
> + if (pattrs[OVPN_A_PEER_LINK_TX_BYTES])
> + fprintf(stderr, "\tLINK TX bytes: %" PRIu64 "\n",
> + nla_get_uint(pattrs[OVPN_A_PEER_LINK_TX_BYTES]));
> +
> + if (pattrs[OVPN_A_PEER_LINK_RX_PACKETS])
> + fprintf(stderr, "\tLINK RX packets: %" PRIu64 "\n",
> + nla_get_uint(pattrs[OVPN_A_PEER_LINK_RX_PACKETS]));
> +
> + if (pattrs[OVPN_A_PEER_LINK_TX_PACKETS])
> + fprintf(stderr, "\tLINK TX packets: %" PRIu64 "\n",
> + nla_get_uint(pattrs[OVPN_A_PEER_LINK_TX_PACKETS]));
> +
> + return NL_SKIP;
> +}
> +
> +static int ovpn_get_peer(struct ovpn_ctx *ovpn)
> +{
> + int flags = 0, ret = -1;
> + struct nlattr *attr;
> + struct nl_ctx *ctx;
> +
> + if (ovpn->peer_id == PEER_ID_UNDEF)
> + flags = NLM_F_DUMP;
> +
> + ctx = nl_ctx_alloc_flags(ovpn, OVPN_CMD_PEER_GET, flags);
> + if (!ctx)
> + return -ENOMEM;
> +
> + if (ovpn->peer_id != PEER_ID_UNDEF) {
> + attr = nla_nest_start(ctx->nl_msg, OVPN_A_PEER);
> + NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_ID, ovpn->peer_id);
> + nla_nest_end(ctx->nl_msg, attr);
> + }
> +
> + ret = ovpn_nl_msg_send(ctx, ovpn_handle_peer);
> +nla_put_failure:
> + nl_ctx_free(ctx);
> + return ret;
> +}
> +
> +static int ovpn_new_key(struct ovpn_ctx *ovpn)
> +{
> + struct nlattr *keyconf, *key_dir;
> + struct nl_ctx *ctx;
> + int ret = -1;
> +
> + ctx = nl_ctx_alloc(ovpn, OVPN_CMD_KEY_NEW);
> + if (!ctx)
> + return -ENOMEM;
> +
> + keyconf = nla_nest_start(ctx->nl_msg, OVPN_A_KEYCONF);
> + NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_PEER_ID, ovpn->peer_id);
> + NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_SLOT, ovpn->key_slot);
> + NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_KEY_ID, ovpn->key_id);
> + NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_CIPHER_ALG, ovpn->cipher);
> +
> + key_dir = nla_nest_start(ctx->nl_msg, OVPN_A_KEYCONF_ENCRYPT_DIR);
> + NLA_PUT(ctx->nl_msg, OVPN_A_KEYDIR_CIPHER_KEY, KEY_LEN, ovpn->key_enc);
> + NLA_PUT(ctx->nl_msg, OVPN_A_KEYDIR_NONCE_TAIL, NONCE_LEN, ovpn->nonce);
> + nla_nest_end(ctx->nl_msg, key_dir);
> +
> + key_dir = nla_nest_start(ctx->nl_msg, OVPN_A_KEYCONF_DECRYPT_DIR);
> + NLA_PUT(ctx->nl_msg, OVPN_A_KEYDIR_CIPHER_KEY, KEY_LEN, ovpn->key_dec);
> + NLA_PUT(ctx->nl_msg, OVPN_A_KEYDIR_NONCE_TAIL, NONCE_LEN, ovpn->nonce);
> + nla_nest_end(ctx->nl_msg, key_dir);
> +
> + nla_nest_end(ctx->nl_msg, keyconf);
> +
> + ret = ovpn_nl_msg_send(ctx, NULL);
> +nla_put_failure:
> + nl_ctx_free(ctx);
> + return ret;
> +}
> +
> +static int ovpn_del_key(struct ovpn_ctx *ovpn)
> +{
> + struct nlattr *keyconf;
> + struct nl_ctx *ctx;
> + int ret = -1;
> +
> + ctx = nl_ctx_alloc(ovpn, OVPN_CMD_KEY_DEL);
> + if (!ctx)
> + return -ENOMEM;
> +
> + keyconf = nla_nest_start(ctx->nl_msg, OVPN_A_KEYCONF);
> + NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_PEER_ID, ovpn->peer_id);
> + NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_SLOT, OVPN_KEY_SLOT_PRIMARY);
> + nla_nest_end(ctx->nl_msg, keyconf);
> +
> + ret = ovpn_nl_msg_send(ctx, NULL);
> +nla_put_failure:
> + nl_ctx_free(ctx);
> + return ret;
> +}
> +
> +static int ovpn_swap_keys(struct ovpn_ctx *ovpn)
> +{
> + struct nl_ctx *ctx;
> + struct nlattr *kc;
> + int ret = -1;
> +
> + ctx = nl_ctx_alloc(ovpn, OVPN_CMD_KEY_SWAP);
> + if (!ctx)
> + return -ENOMEM;
> +
> + kc = nla_nest_start(ctx->nl_msg, OVPN_A_KEYCONF);
> + NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_PEER_ID, ovpn->peer_id);
> + nla_nest_end(ctx->nl_msg, kc);
> +
> + ret = ovpn_nl_msg_send(ctx, NULL);
> +nla_put_failure:
> + nl_ctx_free(ctx);
> + return ret;
> +}
> +
> +#define OVPN_ADDATTR(_msg, _max_size, _attr, _data, _size) { \
> + if (ovpn_addattr(_msg, _max_size, _attr, _data, _size) < 0) \
> + goto err; \
> +}
> +
> +#define NLMSG_TAIL(nmsg) \
> + ((struct rtattr *)(((uint8_t *)(nmsg)) + \
> + NLMSG_ALIGN((nmsg)->nlmsg_len)))
> +
> +#define OVPN_NEST(_msg, _max_size, _attr) ({ \
> + struct rtattr *_nest = NLMSG_TAIL(_msg); \
> + OVPN_ADDATTR(_msg, _max_size, _attr, NULL, 0); \
> + _nest; \
> +})
> +
> +#define OVPN_NEST_END(_msg, _nest) { \
> + _nest->rta_len = (void *)NLMSG_TAIL(_msg) - (void *)_nest; \
> +}
> +
> +/**
> + * Helper function used to easily add attributes to a rtnl message
> + */
> +static int ovpn_addattr(struct nlmsghdr *n, int maxlen, int type,
> + const void *data, int alen)
> +{
> + int len = RTA_LENGTH(alen);
> + struct rtattr *rta;
> +
> + if ((int)(NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len)) > maxlen) {
> + fprintf(stderr, "%s: rtnl: message exceeded bound of %d",
> + __func__, maxlen);
> + return -EMSGSIZE;
> + }
> +
> + rta = NLMSG_TAIL(n);
> + rta->rta_type = type;
> + rta->rta_len = len;
> +
> + if (!data)
> + memset(RTA_DATA(rta), 0, alen);
> + else
> + memcpy(RTA_DATA(rta), data, alen);
> +
> + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len);
> +
> + return 0;
> +}
> +
> +#define RT_SNDBUF_SIZE (1024 * 2)
> +#define RT_RCVBUF_SIZE (1024 * 4)
> +
> +/**
> + * Open RTNL socket
> + */
> +static int ovpn_rt_socket(void)
> +{
> + int sndbuf = RT_SNDBUF_SIZE, rcvbuf = RT_RCVBUF_SIZE, fd;
> +
> + fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
> + if (fd < 0) {
> + fprintf(stderr, "%s: cannot open netlink socket", __func__);
> + return fd;
> + }
> +
> + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf,
> + sizeof(sndbuf)) < 0) {
> + fprintf(stderr, "%s: SO_SNDBUF", __func__);
> + close(fd);
> + return -1;
> + }
> +
> + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf,
> + sizeof(rcvbuf)) < 0) {
> + fprintf(stderr, "%s: SO_RCVBUF", __func__);
> + close(fd);
> + return -1;
> + }
> +
> + return fd;
> +}
> +
> +/**
> + * Bind socket to Netlink subsystem
> + */
> +static int ovpn_rt_bind(int fd, uint32_t groups)
> +{
> + struct sockaddr_nl local = { };
> + socklen_t addr_len;
> +
> + local.nl_family = AF_NETLINK;
> + local.nl_groups = groups;
> +
> + if (bind(fd, (struct sockaddr *)&local, sizeof(local)) < 0) {
> + fprintf(stderr, "%s: cannot bind netlink socket: %d", __func__,
> + errno);
> + return -errno;
> + }
> +
> + addr_len = sizeof(local);
> + if (getsockname(fd, (struct sockaddr *)&local, &addr_len) < 0) {
> + fprintf(stderr, "%s: cannot getsockname: %d", __func__, errno);
> + return -errno;
> + }
> +
> + if (addr_len != sizeof(local)) {
> + fprintf(stderr, "%s: wrong address length %d", __func__,
> + addr_len);
> + return -EINVAL;
> + }
> +
> + if (local.nl_family != AF_NETLINK) {
> + fprintf(stderr, "%s: wrong address family %d", __func__,
> + local.nl_family);
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +typedef int (*ovpn_parse_reply_cb)(struct nlmsghdr *msg, void *arg);
> +
> +/**
> + * Send Netlink message and run callback on reply (if specified)
> + */
> +static int ovpn_rt_send(struct nlmsghdr *payload, pid_t peer,
> + unsigned int groups, ovpn_parse_reply_cb cb,
> + void *arg_cb)
> +{
> + int len, rem_len, fd, ret, rcv_len;
> + struct sockaddr_nl nladdr = { };
> + struct nlmsgerr *err;
> + struct nlmsghdr *h;
> + char buf[1024 * 16];
> + struct iovec iov = {
> + .iov_base = payload,
> + .iov_len = payload->nlmsg_len,
> + };
> + struct msghdr nlmsg = {
> + .msg_name = &nladdr,
> + .msg_namelen = sizeof(nladdr),
> + .msg_iov = &iov,
> + .msg_iovlen = 1,
> + };
> +
> + nladdr.nl_family = AF_NETLINK;
> + nladdr.nl_pid = peer;
> + nladdr.nl_groups = groups;
> +
> + payload->nlmsg_seq = time(NULL);
> +
> + /* no need to send reply */
> + if (!cb)
> + payload->nlmsg_flags |= NLM_F_ACK;
> +
> + fd = ovpn_rt_socket();
> + if (fd < 0) {
> + fprintf(stderr, "%s: can't open rtnl socket", __func__);
> + return -errno;
> + }
> +
> + ret = ovpn_rt_bind(fd, 0);
> + if (ret < 0) {
> + fprintf(stderr, "%s: can't bind rtnl socket", __func__);
> + ret = -errno;
> + goto out;
> + }
> +
> + ret = sendmsg(fd, &nlmsg, 0);
> + if (ret < 0) {
> + fprintf(stderr, "%s: rtnl: error on sendmsg()", __func__);
> + ret = -errno;
> + goto out;
> + }
> +
> + /* prepare buffer to store RTNL replies */
> + memset(buf, 0, sizeof(buf));
> + iov.iov_base = buf;
> +
> + while (1) {
> + /*
> + * iov_len is modified by recvmsg(), therefore has to be initialized before
> + * using it again
> + */
> + iov.iov_len = sizeof(buf);
> + rcv_len = recvmsg(fd, &nlmsg, 0);
> + if (rcv_len < 0) {
> + if (errno == EINTR || errno == EAGAIN) {
> + fprintf(stderr, "%s: interrupted call",
> + __func__);
> + continue;
> + }
> + fprintf(stderr, "%s: rtnl: error on recvmsg()",
> + __func__);
> + ret = -errno;
> + goto out;
> + }
> +
> + if (rcv_len == 0) {
> + fprintf(stderr,
> + "%s: rtnl: socket reached unexpected EOF",
> + __func__);
> + ret = -EIO;
> + goto out;
> + }
> +
> + if (nlmsg.msg_namelen != sizeof(nladdr)) {
> + fprintf(stderr,
> + "%s: sender address length: %u (expected %zu)",
> + __func__, nlmsg.msg_namelen, sizeof(nladdr));
> + ret = -EIO;
> + goto out;
> + }
> +
> + h = (struct nlmsghdr *)buf;
> + while (rcv_len >= (int)sizeof(*h)) {
> + len = h->nlmsg_len;
> + rem_len = len - sizeof(*h);
> +
> + if (rem_len < 0 || len > rcv_len) {
> + if (nlmsg.msg_flags & MSG_TRUNC) {
> + fprintf(stderr, "%s: truncated message",
> + __func__);
> + ret = -EIO;
> + goto out;
> + }
> + fprintf(stderr, "%s: malformed message: len=%d",
> + __func__, len);
> + ret = -EIO;
> + goto out;
> + }
> +
> + if (h->nlmsg_type == NLMSG_DONE) {
> + ret = 0;
> + goto out;
> + }
> +
> + if (h->nlmsg_type == NLMSG_ERROR) {
> + err = (struct nlmsgerr *)NLMSG_DATA(h);
> + if (rem_len < (int)sizeof(struct nlmsgerr)) {
> + fprintf(stderr, "%s: ERROR truncated",
> + __func__);
> + ret = -EIO;
> + goto out;
> + }
> + if (!err->error) {
> + ret = 0;
> + if (cb) {
> + int r = cb(h, arg_cb);
> + if (r <= 0)
> + ret = r;
> + }
> + } else {
> + fprintf(stderr,
> + "%s: rtnl: generic error (%d): %s",
> + __func__, err->error,
> + strerror(-err->error));
> + ret = err->error;
> + }
> + goto out;
> + }
> +
> + if (cb) {
> + int r = cb(h, arg_cb);
> +
> + if (r <= 0) {
> + ret = r;
> + goto out;
> + }
> + } else {
> + fprintf(stderr, "%s: RTNL: unexpected reply",
> + __func__);
> + }
> +
> + rcv_len -= NLMSG_ALIGN(len);
> + h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len));
> + }
> +
> + if (nlmsg.msg_flags & MSG_TRUNC) {
> + fprintf(stderr, "%s: message truncated", __func__);
> + continue;
> + }
> +
> + if (rcv_len) {
> + fprintf(stderr, "%s: rtnl: %d not parsed bytes",
> + __func__, rcv_len);
> + ret = -1;
> + goto out;
> + }
> + }
> +out:
> + close(fd);
> +
> + return ret;
> +}
> +
> +struct ovpn_link_req {
> + struct nlmsghdr n;
> + struct ifinfomsg i;
> + char buf[256];
> +};
> +
> +static int ovpn_new_iface(struct ovpn_ctx *ovpn)
> +{
> + struct rtattr *linkinfo, *data;
> + struct ovpn_link_req req = { };
> + int ret = -1;
> +
> + fprintf(stdout, "Creating interface %s with mode %u\n", ovpn->ifname,
> + ovpn->mode);
> +
> + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));
> + req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL;
> + req.n.nlmsg_type = RTM_NEWLINK;
> +
> + OVPN_ADDATTR(&req.n, sizeof(req), IFLA_IFNAME, ovpn->ifname,
> + strlen(ovpn->ifname) + 1);
> +
> + linkinfo = OVPN_NEST(&req.n, sizeof(req), IFLA_LINKINFO);
> + OVPN_ADDATTR(&req.n, sizeof(req), IFLA_INFO_KIND, OVPN_FAMILY_NAME,
> + strlen(OVPN_FAMILY_NAME) + 1);
> +
> + if (ovpn->mode_set) {
> + data = OVPN_NEST(&req.n, sizeof(req), IFLA_INFO_DATA);
> + OVPN_ADDATTR(&req.n, sizeof(req), IFLA_OVPN_MODE, &ovpn->mode,
> + sizeof(uint8_t));
> + OVPN_NEST_END(&req.n, data);
> + }
> +
> + OVPN_NEST_END(&req.n, linkinfo);
> +
> + req.i.ifi_family = AF_PACKET;
> +
> + ret = ovpn_rt_send(&req.n, 0, 0, NULL, NULL);
> +err:
> + return ret;
> +}
> +
> +static int ovpn_del_iface(struct ovpn_ctx *ovpn)
> +{
> + struct ovpn_link_req req = { };
> +
> + fprintf(stdout, "Deleting interface %s ifindex %u\n", ovpn->ifname,
> + ovpn->ifindex);
> +
> + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));
> + req.n.nlmsg_flags = NLM_F_REQUEST;
> + req.n.nlmsg_type = RTM_DELLINK;
> +
> + req.i.ifi_family = AF_PACKET;
> + req.i.ifi_index = ovpn->ifindex;
> +
> + return ovpn_rt_send(&req.n, 0, 0, NULL, NULL);
> +}
> +
> +static int nl_seq_check(struct nl_msg *msg, void *arg)
> +{
> + return NL_OK;
> +}
> +
> +struct mcast_handler_args {
> + const char *group;
> + int id;
> +};
> +
> +static int mcast_family_handler(struct nl_msg *msg, void *arg)
> +{
> + struct mcast_handler_args *grp = arg;
> + struct nlattr *tb[CTRL_ATTR_MAX + 1];
> + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
> + struct nlattr *mcgrp;
> + int rem_mcgrp;
> +
> + nla_parse(tb, CTRL_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
> + genlmsg_attrlen(gnlh, 0), NULL);
> +
> + if (!tb[CTRL_ATTR_MCAST_GROUPS])
> + return NL_SKIP;
> +
> + nla_for_each_nested(mcgrp, tb[CTRL_ATTR_MCAST_GROUPS], rem_mcgrp) {
> + struct nlattr *tb_mcgrp[CTRL_ATTR_MCAST_GRP_MAX + 1];
> +
> + nla_parse(tb_mcgrp, CTRL_ATTR_MCAST_GRP_MAX,
> + nla_data(mcgrp), nla_len(mcgrp), NULL);
> +
> + if (!tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME] ||
> + !tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID])
> + continue;
> + if (strncmp(nla_data(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]),
> + grp->group, nla_len(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME])))
> + continue;
> + grp->id = nla_get_u32(tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]);
> + break;
> + }
> +
> + return NL_SKIP;
> +}
> +
> +static int mcast_error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err,
> + void *arg)
> +{
> + int *ret = arg;
> +
> + *ret = err->error;
> + return NL_STOP;
> +}
> +
> +static int mcast_ack_handler(struct nl_msg *msg, void *arg)
> +{
> + int *ret = arg;
> +
> + *ret = 0;
> + return NL_STOP;
> +}
> +
> +static int ovpn_handle_msg(struct nl_msg *msg, void *arg)
> +{
> + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
> + struct nlattr *attrs[OVPN_A_MAX + 1];
> + struct nlmsghdr *nlh = nlmsg_hdr(msg);
> + //enum ovpn_del_peer_reason reason;
> + char ifname[IF_NAMESIZE];
> + int *ret = arg;
> + __u32 ifindex;
> +
> + fprintf(stderr, "received message from ovpn-dco\n");
> +
> + *ret = -1;
> +
> + if (!genlmsg_valid_hdr(nlh, 0)) {
> + fprintf(stderr, "invalid header\n");
> + return NL_STOP;
> + }
> +
> + if (nla_parse(attrs, OVPN_A_MAX, genlmsg_attrdata(gnlh, 0),
> + genlmsg_attrlen(gnlh, 0), NULL)) {
> + fprintf(stderr, "received bogus data from ovpn-dco\n");
> + return NL_STOP;
> + }
> +
> + if (!attrs[OVPN_A_IFINDEX]) {
> + fprintf(stderr, "no ifindex in this message\n");
> + return NL_STOP;
> + }
> +
> + ifindex = nla_get_u32(attrs[OVPN_A_IFINDEX]);
> + if (!if_indextoname(ifindex, ifname)) {
> + fprintf(stderr, "cannot resolve ifname for ifindex: %u\n",
> + ifindex);
> + return NL_STOP;
> + }
> +
> + switch (gnlh->cmd) {
> + case OVPN_CMD_PEER_DEL_NTF:
> + /*if (!attrs[OVPN_A_DEL_PEER_REASON]) {
> + * fprintf(stderr, "no reason in DEL_PEER message\n");
> + * return NL_STOP;
> + *}
> + *
> + *reason = nla_get_u8(attrs[OVPN_A_DEL_PEER_REASON]);
> + *fprintf(stderr,
> + * "received CMD_DEL_PEER, ifname: %s reason: %d\n",
> + * ifname, reason);
> + */
> + fprintf(stdout, "received CMD_PEER_DEL_NTF\n");
> + break;
> + case OVPN_CMD_KEY_SWAP_NTF:
> + fprintf(stdout, "received CMD_KEY_SWAP_NTF\n");
> + break;
> + default:
> + fprintf(stderr, "received unknown command: %d\n", gnlh->cmd);
> + return NL_STOP;
> + }
> +
> + *ret = 0;
> + return NL_OK;
> +}
> +
> +static int ovpn_get_mcast_id(struct nl_sock *sock, const char *family,
> + const char *group)
> +{
> + struct nl_msg *msg;
> + struct nl_cb *cb;
> + int ret, ctrlid;
> + struct mcast_handler_args grp = {
> + .group = group,
> + .id = -ENOENT,
> + };
> +
> + msg = nlmsg_alloc();
> + if (!msg)
> + return -ENOMEM;
> +
> + cb = nl_cb_alloc(NL_CB_DEFAULT);
> + if (!cb) {
> + ret = -ENOMEM;
> + goto out_fail_cb;
> + }
> +
> + ctrlid = genl_ctrl_resolve(sock, "nlctrl");
> +
> + genlmsg_put(msg, 0, 0, ctrlid, 0, 0, CTRL_CMD_GETFAMILY, 0);
> +
> + ret = -ENOBUFS;
> + NLA_PUT_STRING(msg, CTRL_ATTR_FAMILY_NAME, family);
> +
> + ret = nl_send_auto_complete(sock, msg);
> + if (ret < 0)
> + goto nla_put_failure;
> +
> + ret = 1;
> +
> + nl_cb_err(cb, NL_CB_CUSTOM, mcast_error_handler, &ret);
> + nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, mcast_ack_handler, &ret);
> + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, mcast_family_handler, &grp);
> +
> + while (ret > 0)
> + nl_recvmsgs(sock, cb);
> +
> + if (ret == 0)
> + ret = grp.id;
> + nla_put_failure:
> + nl_cb_put(cb);
> + out_fail_cb:
> + nlmsg_free(msg);
> + return ret;
> +}
> +
> +static int ovpn_listen_mcast(void)
> +{
> + struct nl_sock *sock;
> + struct nl_cb *cb;
> + int mcid, ret;
> +
> + sock = nl_socket_alloc();
> + if (!sock) {
> + fprintf(stderr, "cannot allocate netlink socket\n");
> + goto err_free;
> + }
> +
> + nl_socket_set_buffer_size(sock, 8192, 8192);
> +
> + ret = genl_connect(sock);
> + if (ret < 0) {
> + fprintf(stderr, "cannot connect to generic netlink: %s\n",
> + nl_geterror(ret));
> + goto err_free;
> + }
> +
> + mcid = ovpn_get_mcast_id(sock, OVPN_FAMILY_NAME, OVPN_MCGRP_PEERS);
> + if (mcid < 0) {
> + fprintf(stderr, "cannot get mcast group: %s\n",
> + nl_geterror(mcid));
> + goto err_free;
> + }
> +
> + ret = nl_socket_add_membership(sock, mcid);
> + if (ret) {
> + fprintf(stderr, "failed to join mcast group: %d\n", ret);
> + goto err_free;
> + }
> +
> + ret = 1;
> + cb = nl_cb_alloc(NL_CB_DEFAULT);
> + nl_cb_set(cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, nl_seq_check, NULL);
> + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, ovpn_handle_msg, &ret);
> + nl_cb_err(cb, NL_CB_CUSTOM, ovpn_nl_cb_error, &ret);
> +
> + while (ret == 1) {
> + int err = nl_recvmsgs(sock, cb);
> +
> + if (err < 0) {
> + fprintf(stderr,
> + "cannot receive netlink message: (%d) %s\n",
> + err, nl_geterror(-err));
> + ret = -1;
> + break;
> + }
> + }
> +
> + nl_cb_put(cb);
> +err_free:
> + nl_socket_free(sock);
> + return ret;
> +}
> +
> +static void usage(const char *cmd)
> +{
> + fprintf(stderr, "Error: invalid arguments.\n\n");
> + fprintf(stderr,
> + "Usage %s <iface> <connect|listen|new_peer|new_multi_peer|set_peer|del_peer|new_key|del_key|recv|send|listen_mcast> [arguments..]\n",
> + cmd);
> + fprintf(stderr, "\tiface: tun interface name\n\n");
> +
> + fprintf(stderr,
> + "* connect <peer_id> <raddr> <rport> <key file>: start connecting peer of TCP-based VPN session\n");
> + fprintf(stderr, "\tpeer-id: peer ID of the connecting peer\n");
> + fprintf(stderr, "\tremote-addr: peer IP address\n");
> + fprintf(stderr, "\tremote-port: peer TCP port\n");
> +
> + fprintf(stderr,
> + "* listen <lport> <peers_file>: listen for incoming peer TCP connections\n");
> + fprintf(stderr, "\tlport: src TCP port\n");
> + fprintf(stderr,
> + "\tpeers_file: file containing one peer per line: Line format:\n");
> + fprintf(stderr, "\t\t<peer_id> <vpnaddr>\n\n");
> +
> + fprintf(stderr,
> + "* new_peer <lport> <peer-id> <raddr> <rport> [vpnaddr]: add new peer\n");
> + fprintf(stderr,
> + "\tpeer-id: peer ID to be used in data packets to/from this peer\n");
> + fprintf(stderr, "\tlocal-port: local UDP port\n");
> + fprintf(stderr, "\tremote-addr: peer IP address\n");
> + fprintf(stderr, "\tremote-port: peer UDP port\n");
> + fprintf(stderr, "\tvpnaddr: peer VPN IP\n\n");
> +
> + fprintf(stderr,
> + "* new_multi_peer <lport> <file>: add multiple peers as listed in the file\n");
> + fprintf(stderr, "\tlport: local UDP port to bind to\n");
> + fprintf(stderr,
> + "\tfile: text file containing one peer per line. Line format:\n");
> + fprintf(stderr, "\t\t<peer-id> <raddr> <rport> <vpnaddr>\n\n");
> +
> + fprintf(stderr,
> + "* set_peer <peer-id> <keepalive_interval> <keepalive_timeout>: set peer attributes\n");
> + fprintf(stderr, "\tpeer-id: peer ID of the peer to modify\n");
> + fprintf(stderr,
> + "\tkeepalive_interval: interval for sending ping messages\n");
> + fprintf(stderr,
> + "\tkeepalive_timeout: time after which a peer is timed out\n\n");
> +
> + fprintf(stderr, "* del_peer <peer-id>: delete peer\n");
> + fprintf(stderr, "\tpeer-id: peer ID of the peer to delete\n\n");
> +
> + fprintf(stderr,
> + "* new_key <peer-id> <slot> <key_id> <cipher> <key_dir> <key_file>: set data channel key\n");
> + fprintf(stderr,
> + "\tpeer-id: peer ID of the peer to configure the key for\n");
> + fprintf(stderr,
> + "\tcipher: cipher to use, supported: aes (AES-GCM), chachapoly (CHACHA20POLY1305), none\n");
> + fprintf(stderr,
> + "\tkey_dir: key direction, must 0 on one host and 1 on the other\n");
> + fprintf(stderr, "\tkey_file: file containing the pre-shared key\n\n");
> +
> + fprintf(stderr,
> + "* del_key <peer-id>: erase existing data channel key\n");
> + fprintf(stderr, "\tpeer-id: peer ID of the peer to modify\n\n");
> +
> + fprintf(stderr,
> + "* swap_keys <peer-id>: swap primary and secondary key slots\n");
> + fprintf(stderr, "\tpeer-id: peer ID of the peer to modify\n\n");
> +
> + fprintf(stderr,
> + "* listen_mcast: listen to ovpn-dco netlink multicast messages\n");
> +}
> +
> +static int ovpn_parse_remote(struct ovpn_ctx *ovpn, const char *host,
> + const char *service, const char *vpnip)
> +{
> + int ret;
> + struct addrinfo *result;
> + struct addrinfo hints = {
> + .ai_family = ovpn->sa_family,
> + .ai_socktype = SOCK_DGRAM,
> + .ai_protocol = IPPROTO_UDP
> + };
> +
> + if (host) {
> + ret = getaddrinfo(host, service, &hints, &result);
> + if (ret == EAI_NONAME || ret == EAI_FAIL)
> + return -1;
> +
> + if (!(result->ai_family == AF_INET &&
> + result->ai_addrlen == sizeof(struct sockaddr_in)) &&
> + !(result->ai_family == AF_INET6 &&
> + result->ai_addrlen == sizeof(struct sockaddr_in6))) {
> + ret = -EINVAL;
> + goto out;
> + }
> +
> + memcpy(&ovpn->remote, result->ai_addr, result->ai_addrlen);
> + }
> +
> + if (vpnip) {
> + ret = getaddrinfo(vpnip, NULL, &hints, &result);
> + if (ret == EAI_NONAME || ret == EAI_FAIL)
> + return -1;
> +
> + if (!(result->ai_family == AF_INET &&
> + result->ai_addrlen == sizeof(struct sockaddr_in)) &&
> + !(result->ai_family == AF_INET6 &&
> + result->ai_addrlen == sizeof(struct sockaddr_in6))) {
> + ret = -EINVAL;
> + goto out;
> + }
> +
> + memcpy(&ovpn->peer_ip, result->ai_addr, result->ai_addrlen);
> + ovpn->sa_family = result->ai_family;
> +
> + ovpn->peer_ip_set = true;
> + }
> +
> + ret = 0;
> +out:
> + freeaddrinfo(result);
> + return ret;
> +}
> +
> +static int ovpn_parse_new_peer(struct ovpn_ctx *ovpn, const char *peer_id,
> + const char *raddr, const char *rport,
> + const char *vpnip)
> +{
> + ovpn->peer_id = strtoul(peer_id, NULL, 10);
> + if (errno == ERANGE) {
> + fprintf(stderr, "peer ID value out of range\n");
> + return -1;
> + }
> +
> + return ovpn_parse_remote(ovpn, raddr, rport, vpnip);
> +}
> +
> +static void ovpn_send_tcp_data(int socket)
> +{
> + uint16_t len = htons(1000);
> + uint8_t buf[1002];
> + int ret;
> +
> + memcpy(buf, &len, sizeof(len));
> + memset(buf + sizeof(len), 0x86, sizeof(buf) - sizeof(len));
> +
> + ret = send(socket, buf, sizeof(buf), 0);
> +
> + fprintf(stdout, "Sent %u bytes over TCP socket\n", ret);
> +}
> +
> +static void ovpn_recv_tcp_data(int socket)
> +{
> + uint8_t buf[1002];
> + uint16_t len;
> + int ret;
> +
> + ret = recv(socket, buf, sizeof(buf), 0);
> +
> + if (ret < 2) {
> + fprintf(stderr, ">>>> Error while reading TCP data: %d\n", ret);
> + return;
> + }
> +
> + memcpy(&len, buf, sizeof(len));
> + len = ntohs(len);
> +
> + fprintf(stdout, ">>>> Received %u bytes over TCP socket, header: %u\n",
> + ret, len);
> +
> +/* int i;
> + * for (i = 2; i < ret; i++) {
> + * fprintf(stdout, "0x%.2x ", buf[i]);
> + * if (i && !((i - 2) % 16))
> + * fprintf(stdout, "\n");
> + * }
> + * fprintf(stdout, "\n");
> + */
> +}
> +
> +static int ovpn_parse_set_peer(struct ovpn_ctx *ovpn, int argc, char *argv[])
> +{
> + if (argc < 5) {
> + usage(argv[0]);
> + return -1;
> + }
> +
> + ovpn->keepalive_interval = strtoul(argv[3], NULL, 10);
> + if (errno == ERANGE) {
> + fprintf(stderr, "keepalive interval value out of range\n");
> + return -1;
> + }
> +
> + ovpn->keepalive_timeout = strtoul(argv[4], NULL, 10);
> + if (errno == ERANGE) {
> + fprintf(stderr, "keepalive interval value out of range\n");
> + return -1;
> + }
> +
> + return 0;
> +}
> +
> +int main(int argc, char *argv[])
> +{
> + struct ovpn_ctx ovpn;
> + int ret;
> +
> + if (argc < 2) {
> + usage(argv[0]);
> + return -1;
> + }
> +
> + memset(&ovpn, 0, sizeof(ovpn));
> + ovpn.sa_family = AF_INET;
> + ovpn.cli_socket = -1;
> +
> + if (argc > 2) {
> + strscpy(ovpn.ifname, argv[2], IFNAMSIZ - 1);
> + ovpn.ifname[IFNAMSIZ - 1] = '\0';
> + }
> +
> + /* all commands except new_iface expect a valid ifindex */
> + if (strcmp(argv[1], "new_iface")) {
> + /* in this case a ifname MUST be defined */
> + if (argc < 3) {
> + usage(argv[0]);
> + return -1;
> + }
> +
> + ovpn.ifindex = if_nametoindex(ovpn.ifname);
> + if (!ovpn.ifindex) {
> + fprintf(stderr, "cannot find interface: %s\n",
> + strerror(errno));
> + return -1;
> + }
> + }
> +
> + if (!strcmp(argv[1], "new_iface")) {
> + if (argc > 3) {
> + if (!strcmp(argv[3], "P2P")) {
> + ovpn.mode = OVPN_MODE_P2P;
> + } else if (!strcmp(argv[3], "MP")) {
> + ovpn.mode = OVPN_MODE_MP;
> + } else {
> + fprintf(stderr, "Cannot parse iface mode: %s\n",
> + argv[3]);
> + return -1;
> + }
> + ovpn.mode_set = true;
> + }
> +
> + ret = ovpn_new_iface(&ovpn);
> + if (ret < 0) {
> + fprintf(stderr, "Cannot create interface %s: %d\n",
> + ovpn.ifname, ret);
> + return -1;
> + }
> + } else if (!strcmp(argv[1], "del_iface")) {
> + ret = ovpn_del_iface(&ovpn);
> + if (ret < 0) {
> + fprintf(stderr, "Cannot delete interface %s: %d\n",
> + ovpn.ifname, ret);
> + return -1;
> + }
> + } else if (!strcmp(argv[1], "listen")) {
> + char peer_id[10], vpnip[100];
> + int n;
> + FILE *fp;
> +
> + if (argc < 4) {
> + usage(argv[0]);
> + return -1;
> + }
> +
> + ovpn.lport = strtoul(argv[3], NULL, 10);
> + if (errno == ERANGE || ovpn.lport > 65535) {
> + fprintf(stderr, "lport value out of range\n");
> + return -1;
> + }
> +
> + if (argc > 4 && !strcmp(argv[4], "ipv6"))
> + ovpn.sa_family = AF_INET6;
> +
> + ret = ovpn_listen(&ovpn, ovpn.sa_family);
> + if (ret < 0) {
> + fprintf(stderr, "cannot listen on TCP socket\n");
> + return ret;
> + }
> +
> + fp = fopen(argv[4], "r");
> + if (!fp) {
> + fprintf(stderr, "cannot open file: %s\n", argv[4]);
> + return -1;
> + }
> +
> + while ((n = fscanf(fp, "%s %s\n", peer_id, vpnip)) == 2) {
> + struct ovpn_ctx peer_ctx = { 0 };
> +
> + peer_ctx.ifindex = ovpn.ifindex;
> + peer_ctx.sa_family = ovpn.sa_family;
> +
> + peer_ctx.socket = ovpn_accept(&ovpn);
> + if (peer_ctx.socket < 0) {
> + fprintf(stderr, "cannot accept connection!\n");
> + return -1;
> + }
> +
> + /* store the socket of the first peer to test TCP I/O */
> + if (ovpn.cli_socket < 0)
> + ovpn.cli_socket = peer_ctx.socket;
> +
> + ret = ovpn_parse_new_peer(&peer_ctx, peer_id, NULL,
> + NULL, vpnip);
> + if (ret < 0) {
> + fprintf(stderr, "error while parsing line\n");
> + return -1;
> + }
> +
> + ret = ovpn_new_peer(&peer_ctx, true);
> + if (ret < 0) {
> + fprintf(stderr,
> + "cannot add peer to VPN: %s %s\n",
> + peer_id, vpnip);
> + return ret;
> + }
> + }
> +
> + if (ovpn.cli_socket >= 0)
> + ovpn_recv_tcp_data(ovpn.cli_socket);
> + } else if (!strcmp(argv[1], "connect")) {
> + if (argc < 5) {
> + usage(argv[0]);
> + return -1;
> + }
> +
> + ovpn.sa_family = AF_INET;
> +
> + ret = ovpn_parse_new_peer(&ovpn, argv[3], argv[4], argv[5],
> + NULL);
> + if (ret < 0) {
> + fprintf(stderr, "Cannot parse remote peer data\n");
> + return ret;
> + }
> +
> + ret = ovpn_connect(&ovpn);
> + if (ret < 0) {
> + fprintf(stderr, "cannot connect TCP socket\n");
> + return ret;
> + }
> +
> + ret = ovpn_new_peer(&ovpn, true);
> + if (ret < 0) {
> + fprintf(stderr, "cannot add peer to VPN\n");
> + close(ovpn.socket);
> + return ret;
> + }
> +
> + if (argc > 6) {
> + ovpn.key_slot = OVPN_KEY_SLOT_PRIMARY;
> + ovpn.key_id = 0;
> + ovpn.cipher = OVPN_CIPHER_ALG_AES_GCM;
> + ovpn.key_dir = KEY_DIR_OUT;
> +
> + ret = ovpn_read_key(argv[6], &ovpn);
> + if (ret)
> + return ret;
> +
> + ret = ovpn_new_key(&ovpn);
> + if (ret < 0) {
> + fprintf(stderr, "cannot set key\n");
> + return ret;
> + }
> +
> + ovpn_send_tcp_data(ovpn.socket);
> + }
> + } else if (!strcmp(argv[1], "new_peer")) {
> + if (argc < 7) {
> + usage(argv[0]);
> + return -1;
> + }
> +
> + ovpn.lport = strtoul(argv[3], NULL, 10);
> + if (errno == ERANGE || ovpn.lport > 65535) {
> + fprintf(stderr, "lport value out of range\n");
> + return -1;
> + }
> +
> + const char *vpnip = (argc > 7) ? argv[7] : NULL;
> +
> + ret = ovpn_parse_new_peer(&ovpn, argv[4], argv[5], argv[6],
> + vpnip);
> + if (ret < 0)
> + return ret;
> +
> + ret = ovpn_udp_socket(&ovpn, AF_INET6); //ovpn.sa_family ?
> + if (ret < 0)
> + return ret;
> +
> + ret = ovpn_new_peer(&ovpn, false);
> + if (ret < 0) {
> + fprintf(stderr, "cannot add peer to VPN\n");
> + return ret;
> + }
> + } else if (!strcmp(argv[1], "new_multi_peer")) {
> + char peer_id[10], raddr[128], rport[10], vpnip[100];
> + FILE *fp;
> + int n;
> +
> + if (argc < 5) {
> + usage(argv[0]);
> + return -1;
> + }
> +
> + ovpn.lport = strtoul(argv[3], NULL, 10);
> + if (errno == ERANGE || ovpn.lport > 65535) {
> + fprintf(stderr, "lport value out of range\n");
> + return -1;
> + }
> +
> + fp = fopen(argv[4], "r");
> + if (!fp) {
> + fprintf(stderr, "cannot open file: %s\n", argv[4]);
> + return -1;
> + }
> +
> + ret = ovpn_udp_socket(&ovpn, AF_INET6);
> + if (ret < 0)
> + return ret;
> +
> + while ((n = fscanf(fp, "%s %s %s %s\n", peer_id, raddr, rport,
> + vpnip)) == 4) {
> + struct ovpn_ctx peer_ctx = { 0 };
> +
> + peer_ctx.ifindex = ovpn.ifindex;
> + peer_ctx.socket = ovpn.socket;
> + peer_ctx.sa_family = AF_UNSPEC;
> +
> + ret = ovpn_parse_new_peer(&peer_ctx, peer_id, raddr,
> + rport, vpnip);
> + if (ret < 0) {
> + fprintf(stderr, "error while parsing line\n");
> + return -1;
> + }
> +
> + ret = ovpn_new_peer(&peer_ctx, false);
> + if (ret < 0) {
> + fprintf(stderr,
> + "cannot add peer to VPN: %s %s %s %s\n",
> + peer_id, raddr, rport, vpnip);
> + return ret;
> + }
> + }
> + } else if (!strcmp(argv[1], "set_peer")) {
> + ovpn.peer_id = strtoul(argv[3], NULL, 10);
> + if (errno == ERANGE) {
> + fprintf(stderr, "peer ID value out of range\n");
> + return -1;
> + }
> +
> + argv++;
> + argc--;
> +
> + ret = ovpn_parse_set_peer(&ovpn, argc, argv);
> + if (ret < 0)
> + return ret;
> +
> + ret = ovpn_set_peer(&ovpn);
> + if (ret < 0) {
> + fprintf(stderr, "cannot set peer to VPN\n");
> + return ret;
> + }
> + } else if (!strcmp(argv[1], "del_peer")) {
> + if (argc < 4) {
> + usage(argv[0]);
> + return -1;
> + }
> +
> + ovpn.peer_id = strtoul(argv[3], NULL, 10);
> + if (errno == ERANGE) {
> + fprintf(stderr, "peer ID value out of range\n");
> + return -1;
> + }
> +
> + ret = ovpn_del_peer(&ovpn);
> + if (ret < 0) {
> + fprintf(stderr, "cannot delete peer to VPN\n");
> + return ret;
> + }
> + } else if (!strcmp(argv[1], "get_peer")) {
> + ovpn.peer_id = PEER_ID_UNDEF;
> + if (argc > 3)
> + ovpn.peer_id = strtoul(argv[3], NULL, 10);
> +
> + fprintf(stderr, "List of peers connected to: %s\n",
> + ovpn.ifname);
> +
> + ret = ovpn_get_peer(&ovpn);
> + if (ret < 0) {
> + fprintf(stderr, "cannot get peer(s): %d\n", ret);
> + return ret;
> + }
> + } else if (!strcmp(argv[1], "new_key")) {
> + if (argc < 8) {
> + usage(argv[0]);
> + return -1;
> + }
> +
> + ovpn.peer_id = strtoul(argv[3], NULL, 10);
> + if (errno == ERANGE) {
> + fprintf(stderr, "peer ID value out of range\n");
> + return -1;
> + }
> +
> + int slot = strtoul(argv[4], NULL, 10);
> +
> + if (errno == ERANGE || slot < 1 || slot > 2) {
> + fprintf(stderr, "key slot out of range\n");
> + return -1;
> + }
> +
> + switch (slot) {
> + case 1:
> + ovpn.key_slot = OVPN_KEY_SLOT_PRIMARY;
> + break;
> + case 2:
> + ovpn.key_slot = OVPN_KEY_SLOT_SECONDARY;
> + break;
> + }
> +
> + ovpn.key_id = strtoul(argv[5], NULL, 10);
> + if (errno == ERANGE || ovpn.key_id > 2) {
> + fprintf(stderr, "key ID out of range\n");
> + return -1;
> + }
> +
> + ret = ovpn_read_cipher(argv[6], &ovpn);
> + if (ret < 0)
> + return ret;
> +
> + ret = ovpn_read_key_direction(argv[7], &ovpn);
> + if (ret < 0)
> + return ret;
> +
> + ret = ovpn_read_key(argv[8], &ovpn);
> + if (ret)
> + return ret;
> +
> + ret = ovpn_new_key(&ovpn);
> + if (ret < 0) {
> + fprintf(stderr, "cannot set key\n");
> + return ret;
> + }
> + } else if (!strcmp(argv[1], "del_key")) {
> + if (argc < 3) {
> + usage(argv[0]);
> + return -1;
> + }
> +
> + ovpn.peer_id = strtoul(argv[3], NULL, 10);
> + if (errno == ERANGE) {
> + fprintf(stderr, "peer ID value out of range\n");
> + return -1;
> + }
> +
> + argv++;
> + argc--;
> +
> + ret = ovpn_del_key(&ovpn);
> + if (ret < 0) {
> + fprintf(stderr, "cannot delete key\n");
> + return ret;
> + }
> + } else if (!strcmp(argv[1], "swap_keys")) {
> + if (argc < 3) {
> + usage(argv[0]);
> + return -1;
> + }
> +
> + ovpn.peer_id = strtoul(argv[3], NULL, 10);
> + if (errno == ERANGE) {
> + fprintf(stderr, "peer ID value out of range\n");
> + return -1;
> + }
> +
> + argv++;
> + argc--;
> +
> + ret = ovpn_swap_keys(&ovpn);
> + if (ret < 0) {
> + fprintf(stderr, "cannot swap keys\n");
> + return ret;
> + }
> + } else if (!strcmp(argv[1], "listen_mcast")) {
> + ret = ovpn_listen_mcast();
> + } else {
> + usage(argv[0]);
> + return -1;

This is loooong arguments parsing. What's the reason to not use getopt()
Doesn't it simplify all ofthie logic?

I would like to see it simplified for maintainability.

> + }
> +
> + return ret;
> +}
> diff --git a/tools/testing/selftests/net/ovpn/tcp_peers.txt b/tools/testing/selftests/net/ovpn/tcp_peers.txt
> new file mode 100644
> index 0000000000000000000000000000000000000000..d753eebe8716ed3588334ad766981e883ed2469a
> --- /dev/null
> +++ b/tools/testing/selftests/net/ovpn/tcp_peers.txt
> @@ -0,0 +1,5 @@
> +1 5.5.5.2
> +2 5.5.5.3
> +3 5.5.5.4
> +4 5.5.5.5
> +5 5.5.5.6
> diff --git a/tools/testing/selftests/net/ovpn/udp_peers.txt b/tools/testing/selftests/net/ovpn/udp_peers.txt
> new file mode 100644
> index 0000000000000000000000000000000000000000..32f14bd9347a63e58438311b6d880b9fef768aa2
> --- /dev/null
> +++ b/tools/testing/selftests/net/ovpn/udp_peers.txt
> @@ -0,0 +1,5 @@
> +1 10.10.1.2 1 5.5.5.2
> +2 10.10.2.2 1 5.5.5.3
> +3 10.10.3.2 1 5.5.5.4
> +4 10.10.4.2 1 5.5.5.5
> +5 10.10.5.2 1 5.5.5.6
>

thanks,
-- Shuah