1#!/bin/bash 2# SPDX-License-Identifier: GPL-2.0 3# Copyright (C) 2020-2025 OpenVPN, Inc. 4# 5# Author: Antonio Quartulli <antonio@openvpn.net> 6 7OVPN_COMMON_DIR=$(dirname "$(readlink -f "${BASH_SOURCE[0]}")") 8source "$OVPN_COMMON_DIR/../../kselftest/ktap_helpers.sh" 9 10OVPN_UDP_PEERS_FILE=${OVPN_UDP_PEERS_FILE:-udp_peers.txt} 11OVPN_TCP_PEERS_FILE=${OVPN_TCP_PEERS_FILE:-tcp_peers.txt} 12OVPN_CLI=${OVPN_CLI:-${OVPN_COMMON_DIR}/ovpn-cli} 13OVPN_YNL=${OVPN_YNL:-${OVPN_COMMON_DIR}/../../../../net/ynl/pyynl/cli.py} 14OVPN_ALG=${OVPN_ALG:-aes} 15OVPN_PROTO=${OVPN_PROTO:-UDP} 16OVPN_FLOAT=${OVPN_FLOAT:-0} 17OVPN_SYMMETRIC_ID=${OVPN_SYMMETRIC_ID:-0} 18OVPN_VERBOSE=${OVPN_VERBOSE:-0} 19 20export OVPN_ID_OFFSET=$(( 9 * (OVPN_SYMMETRIC_ID == 0) )) 21 22OVPN_JQ_FILTER='map(if type == "array" then .[] else . end) | 23 map(select(.msg.peer | has("remote-ipv6") | not)) | 24 map(del(.msg.ifindex)) | sort_by(.msg.peer.id)[]' 25OVPN_LAN_IP="11.11.11.11" 26 27declare -A OVPN_TMP_JSONS=() 28declare -A OVPN_LISTENER_PIDS=() 29OVPN_CURRENT_STAGE="" 30 31ovpn_is_verbose() { 32 [[ "${OVPN_VERBOSE}" == "1" ]] 33} 34 35ovpn_log() { 36 ovpn_is_verbose || return 0 37 printf '%s\n' "$*" 38} 39 40ovpn_print_cmd_output() { 41 local output_file="$1" 42 local line 43 44 [[ -s "${output_file}" ]] || return 0 45 46 while IFS= read -r line; do 47 ovpn_log "${line}" 48 done < "${output_file}" 49} 50 51ovpn_cmd_run() { 52 local mode="$1" 53 local label="$2" 54 local output_file 55 local rc 56 local ret=0 57 58 shift 2 59 60 output_file=$(mktemp) 61 if "$@" >"${output_file}" 2>&1; then 62 rc=0 63 else 64 rc=$? 65 fi 66 67 case "${mode}" in 68 ok) 69 if [[ "${rc}" -ne 0 ]]; then 70 cat "${output_file}" 71 printf '%s\n' \ 72 "${label}: command failed with rc=${rc}: $*" 73 ret="${rc}" 74 fi 75 ;; 76 mayfail) 77 ;; 78 fail) 79 [[ "${rc}" -eq 0 ]] && ret=1 80 ;; 81 esac 82 83 if ovpn_is_verbose && [[ "${rc}" -eq 0 || "${mode}" != "ok" ]]; then 84 ovpn_print_cmd_output "${output_file}" 85 fi 86 87 rm -f "${output_file}" 88 return "${ret}" 89} 90 91ovpn_cmd_ok() { 92 ovpn_cmd_run ok "$@" 93} 94 95ovpn_cmd_mayfail() { 96 ovpn_cmd_run mayfail "$@" 97} 98 99ovpn_cmd_fail() { 100 ovpn_cmd_run fail "$@" 101} 102 103ovpn_run_bg() { 104 local pid_var="$1" 105 106 shift 107 if ovpn_is_verbose; then 108 "$@" & 109 else 110 "$@" >/dev/null 2>&1 & 111 fi 112 113 printf -v "${pid_var}" '%s' "$!" 114} 115 116ovpn_run_stage() { 117 local label="$1" 118 119 shift 120 OVPN_CURRENT_STAGE="${label}" 121 "$@" 122 OVPN_CURRENT_STAGE="" 123 ktap_test_pass "${label}" 124} 125 126ovpn_stage_err() { 127 # ERR trap is global under set -eE: only report failures that happen 128 # while ovpn_run_stage() is actively executing a stage body. 129 if [[ -n "${OVPN_CURRENT_STAGE}" ]]; then 130 ktap_test_fail "${OVPN_CURRENT_STAGE}" 131 OVPN_CURRENT_STAGE="" 132 fi 133} 134 135ovpn_create_ns() { 136 ip netns add "ovpn_peer${1}" 137} 138 139ovpn_peer_vpn_addr() { 140 local peer="$1" 141 local file 142 143 if [ "${OVPN_PROTO}" == "UDP" ]; then 144 file="${OVPN_UDP_PEERS_FILE}" 145 else 146 file="${OVPN_TCP_PEERS_FILE}" 147 fi 148 149 awk -v peer="${peer}" '$1 == peer {print $NF; exit}' "${file}" 150} 151 152ovpn_setup_ns() { 153 local peer="ovpn_peer${1}" 154 local server_ns="ovpn_peer0" 155 local peer_ns 156 MODE="P2P" 157 158 if [ ${1} -eq 0 ]; then 159 MODE="MP" 160 for p in $(seq 1 ${OVPN_NUM_PEERS}); do 161 peer_ns="ovpn_peer${p}" 162 ip link add veth${p} netns "${server_ns}" type veth \ 163 peer name veth${p} netns "${peer_ns}" 164 165 ip -n "${server_ns}" addr add 10.10.${p}.1/24 dev \ 166 veth${p} 167 ip -n "${server_ns}" addr add fd00:0:0:${p}::1/64 dev \ 168 veth${p} 169 ip -n "${server_ns}" link set veth${p} up 170 171 ip -n "${peer_ns}" addr add 10.10.${p}.2/24 dev veth${p} 172 ip -n "${peer_ns}" addr add fd00:0:0:${p}::2/64 dev \ 173 veth${p} 174 ip -n "${peer_ns}" link set veth${p} up 175 done 176 fi 177 178 ip netns exec "${peer}" ${OVPN_CLI} new_iface tun${1} $MODE 179 ip -n "${peer}" addr add ${2} dev tun${1} 180 # add a secondary IP to peer 1, to test a LAN behind a client 181 if [ ${1} -eq 1 -a -n "${OVPN_LAN_IP}" ]; then 182 ip -n "${peer}" addr add ${OVPN_LAN_IP} dev tun${1} 183 ip -n "${server_ns}" route add ${OVPN_LAN_IP} via \ 184 $(echo ${2} |sed -e s'!/.*!!') dev tun0 185 fi 186 if [ -n "${3}" ]; then 187 ip -n "${peer}" link set mtu ${3} dev tun${1} 188 fi 189 ip -n "${peer}" link set tun${1} up 190} 191 192ovpn_build_capture_filter() { 193 # match the first four bytes of the openvpn data payload 194 if [ "${OVPN_PROTO}" == "UDP" ]; then 195 # For UDP, libpcap transport indexing only works for IPv4, so 196 # use an explicit IPv4 or IPv6 expression based on the peer 197 # address. The IPv6 branch assumes there are no extension 198 # headers in the outer packet. 199 if [[ "${2}" == *:* ]]; then 200 printf "ip6 and ip6[6] = 17 and ip6[48:4] = %s" "${1}" 201 else 202 printf "ip and udp[8:4] = %s" "${1}" 203 fi 204 else 205 # openvpn over TCP prepends a 2-byte packet length ahead of the 206 # DATA_V2 opcode, so skip it before matching the payload header 207 printf "ip and tcp[(((tcp[12] & 0xf0) >> 2) + 2):4] = %s" "${1}" 208 fi 209} 210 211ovpn_setup_listener() { 212 local peer="$1" 213 local file 214 local peer_ns="ovpn_peer${peer}" 215 216 file=$(mktemp) 217 PYTHONUNBUFFERED=1 ip netns exec "${peer_ns}" "${OVPN_YNL}" --family \ 218 ovpn --subscribe peers --output-json > "${file}" \ 219 2>/dev/null & 220 OVPN_LISTENER_PIDS["${peer}"]=$! 221 OVPN_TMP_JSONS["${peer}"]="${file}" 222} 223 224ovpn_add_peer() { 225 labels=("ASYMM" "SYMM") 226 local peer_ns 227 local server_ns="ovpn_peer0" 228 M_ID=${labels[OVPN_SYMMETRIC_ID]} 229 230 if [ "${OVPN_PROTO}" == "UDP" ]; then 231 if [ ${1} -eq 0 ]; then 232 ip netns exec "${server_ns}" ${OVPN_CLI} \ 233 new_multi_peer tun0 1 ${M_ID} \ 234 ${OVPN_UDP_PEERS_FILE} 235 236 for p in $(seq 1 ${OVPN_NUM_PEERS}); do 237 ip netns exec "${server_ns}" ${OVPN_CLI} \ 238 new_key tun0 ${p} 1 0 ${OVPN_ALG} 0 \ 239 data64.key 240 done 241 else 242 peer_ns="ovpn_peer${1}" 243 if [ "${OVPN_SYMMETRIC_ID}" -eq 1 ]; then 244 PEER_ID=${1} 245 TX_ID="none" 246 else 247 PEER_ID=$(awk "NR == ${1} {print \$2}" \ 248 ${OVPN_UDP_PEERS_FILE}) 249 TX_ID=${1} 250 fi 251 RADDR=$(awk "NR == ${1} {print \$3}" \ 252 ${OVPN_UDP_PEERS_FILE}) 253 RPORT=$(awk "NR == ${1} {print \$4}" \ 254 ${OVPN_UDP_PEERS_FILE}) 255 LPORT=$(awk "NR == ${1} {print \$6}" \ 256 ${OVPN_UDP_PEERS_FILE}) 257 ip netns exec "${peer_ns}" ${OVPN_CLI} new_peer \ 258 tun${1} ${PEER_ID} ${TX_ID} ${LPORT} ${RADDR} \ 259 ${RPORT} 260 ip netns exec "${peer_ns}" ${OVPN_CLI} new_key tun${1} \ 261 ${PEER_ID} 1 0 ${OVPN_ALG} 1 data64.key 262 fi 263 else 264 if [ ${1} -eq 0 ]; then 265 (ip netns exec "${server_ns}" ${OVPN_CLI} listen tun0 \ 266 1 ${M_ID} ${OVPN_TCP_PEERS_FILE} && { 267 for p in $(seq 1 ${OVPN_NUM_PEERS}); do 268 ip netns exec "${server_ns}" \ 269 ${OVPN_CLI} new_key tun0 ${p} \ 270 1 0 ${OVPN_ALG} 0 data64.key 271 done 272 }) & 273 sleep 5 274 else 275 peer_ns="ovpn_peer${1}" 276 if [ "${OVPN_SYMMETRIC_ID}" -eq 1 ]; then 277 PEER_ID=${1} 278 TX_ID="none" 279 else 280 PEER_ID=$(awk "NR == ${1} {print \$2}" \ 281 ${OVPN_TCP_PEERS_FILE}) 282 TX_ID=${1} 283 fi 284 ip netns exec "${peer_ns}" ${OVPN_CLI} connect tun${1} \ 285 ${PEER_ID} ${TX_ID} 10.10.${1}.1 1 data64.key 286 fi 287 fi 288} 289 290ovpn_compare_ntfs() { 291 local diff_rc=0 292 local diff_file 293 294 if [ ${#OVPN_TMP_JSONS[@]} -gt 0 ]; then 295 suffix="" 296 [ "${OVPN_SYMMETRIC_ID}" -eq 1 ] && suffix="${suffix}-symm" 297 [ "$OVPN_FLOAT" == 1 ] && suffix="${suffix}-float" 298 expected="json/peer${1}${suffix}.json" 299 received="${OVPN_TMP_JSONS[$1]}" 300 diff_file=$(mktemp) 301 302 ovpn_stop_listener "${1}" 1 303 printf "Checking notifications for peer ${1}... " 304 if diff <(jq -s "${OVPN_JQ_FILTER}" ${expected}) \ 305 <(jq -s "${OVPN_JQ_FILTER}" ${received}) \ 306 >"${diff_file}" 2>&1; then 307 echo "OK" 308 else 309 diff_rc=$? 310 echo "failed" 311 cat "${diff_file}" 312 fi 313 314 rm -f "${diff_file}" || true 315 rm -f "${received}" || true 316 unset "OVPN_TMP_JSONS[$1]" 317 fi 318 319 return "${diff_rc}" 320} 321 322ovpn_stop_listener() { 323 local peer="$1" 324 local keep_json="${2:-0}" 325 local pid="${OVPN_LISTENER_PIDS[$peer]:-}" 326 local json="${OVPN_TMP_JSONS[$peer]:-}" 327 328 if [[ -n "${pid}" ]]; then 329 kill -TERM "${pid}" 2>/dev/null || true 330 wait "${pid}" 2>/dev/null || true 331 unset "OVPN_LISTENER_PIDS[$peer]" 332 fi 333 334 if [[ -n "${json}" && "${keep_json}" -eq 0 ]]; then 335 rm -f "${json}" || true 336 unset "OVPN_TMP_JSONS[$peer]" 337 fi 338} 339 340ovpn_cleanup_peer_ns() { 341 local peer="$1" 342 local peer_id="${peer#ovpn_peer}" 343 344 ip -n "${peer}" link set tun${peer_id} down 2>/dev/null || true 345 ip netns exec "${peer}" ${OVPN_CLI} del_iface tun${peer_id} \ 346 1>/dev/null 2>&1 || true 347 ip netns del "${peer}" 2>/dev/null || true 348} 349 350ovpn_cleanup() { 351 local peer 352 353 # some ovpn-cli processes sleep in background so they need manual poking 354 killall "$(basename "${OVPN_CLI}")" 2>/dev/null || true 355 356 for peer in "${!OVPN_LISTENER_PIDS[@]}"; do 357 ovpn_stop_listener "${peer}" 2>/dev/null 358 done 359 360 for p in $(seq 1 10); do 361 ip -n ovpn_peer0 link del veth${p} 2>/dev/null || true 362 done 363 364 # remove from ovpn's netns pool 365 while IFS= read -r peer; do 366 [[ -n "${peer}" ]] || continue 367 ovpn_cleanup_peer_ns "${peer}" 2>/dev/null 368 done < <(ip netns list 2>/dev/null | awk '/^ovpn_/ {print $1}') 369} 370 371if [ "${OVPN_PROTO}" == "UDP" ]; then 372 OVPN_NUM_PEERS=${OVPN_NUM_PEERS:-$(wc -l ${OVPN_UDP_PEERS_FILE} | \ 373 awk '{print $1}')} 374else 375 OVPN_NUM_PEERS=${OVPN_NUM_PEERS:-$(wc -l ${OVPN_TCP_PEERS_FILE} | \ 376 awk '{print $1}')} 377fi 378