#!/usr/bin/env bash # logwisp IPv6 test: a relay on ::1 takes both chain transports and serves # viewers over TLS with an IPv6 SAN, and listeners keep to their address # family. Skips (exit 77) on a host without an IPv6 loopback. # Requires: bash 5+, openssl, curl, an IPv6 loopback. Linux dev host only. set -u . "$(dirname -- "${BASH_SOURCE[0]}")/lib.sh" RUN=$E2E_DIR/run-ipv6 PKI=$RUN/pki AUTH=$RUN/auth OUT=$RUN/out USERS=$AUTH/users.toml PORT_TCP_CHAIN=15851 PORT_HTTP_CHAIN=15852 PORT_TCP_SINK=15853 PORT_HTTP_SINK=15854 PORT_FAMILY=15855 PORTS="$PORT_TCP_CHAIN $PORT_HTTP_CHAIN $PORT_TCP_SINK $PORT_HTTP_SINK $PORT_FAMILY" e2e_init "$@" section "Setup" # /proc/net/if_inet6 lists ::1 as 31 zeros and a 1; it is absent without IPv6 grep -qs '^0\{31\}1 ' /proc/net/if_inet6 || skip_all "this host has no IPv6 loopback (::1)" need openssl curl ports_free $PORTS rm -rf "$RUN" mkdir -p "$CONF" "$LOG" "$PKI" "$AUTH" "$OUT" # The relay certificate names only ::1: every dialer verifies an IPv6 SAN pki_ca pki_leaf relay relay.internal serverAuth "IP:::1" info "test PKI in $(short "$PKI")/: ca, relay (SAN IP ::1)" add_users "$USERS" edge-01 viewer-01 info "users edge-01, viewer-01 in $(short "$USERS")" tls_server() { printf '[%s.tls]\nenabled = true\ncert_file = "%s"\nkey_file = "%s"\n' "$1" "$PKI/relay.crt" "$PKI/relay.key"; } scram_server() { printf '[%s.auth]\ntype = "scram"\ncredentials_file = "%s"\n' "$1" "$USERS"; } relay_pipeline() { # name source_type source_port sink_type sink_port cat <"$CONF/relay.toml" edge_conf() { # name sink_type port cat >"$CONF/$1.toml" <"$CONF/$1.toml" < . $(short "$RUN")/env Read the tcp sink as viewer-01: > \$LW auth stream -addr [::1]:$PORT_TCP_SINK -user viewer-01 -password-file \$PW -ca-file \$CA Read the http sink (bearer keeps the token off argv; -g leaves the brackets to the URL): > token=\$(\$LW auth token -url https://[::1]:$PORT_HTTP_SINK -user viewer-01 -password-file \$PW -ca-file \$CA) > curl -gN --cacert \$CA -H @<(bearer) https://[::1]:$PORT_HTTP_SINK/stream Which listener answers on $PORT_FAMILY: > curl -s http://127.0.0.1:$PORT_FAMILY/status; curl -gs http://[::1]:$PORT_FAMILY/status Ingested entries: $(short "$OUT")/ Passwords: $(short "$AUTH")/ Logs: $(short "$LOG")/ EOF manual_hold relay_log="$LOG/relay.out" curl_args=(-g -s --max-time 5 --noproxy '*') answered_by() { # url -> the instance whose /status answered, empty if refused curl "${curl_args[@]}" "$1/status" 2>/dev/null | grep -o '"instance_id":"[^"]*"' | cut -d'"' -f4 } section "Scenario 1: chain links and viewers over [::1]" # 1. Both chain transports deliver; the http_chain edge logged in at # https://[::1]:PORT/auth first wait_until 20 has_entry tcp_chain 'edge-01/' n=$(ingested tcp_chain | grep -c 'edge-01/') check "tcp_chain: entries from the edge dialing [::1] reached the relay ($n lines)" $((n >= 1)) wait_until 20 has_entry http_chain 'edge-01/' n=$(ingested http_chain | grep -c 'edge-01/') check "http_chain: entries from the edge dialing [::1] reached the relay ($n lines)" $((n >= 1)) # The logger quotes a value holding brackets n=$(grep -cF "addr \"[::1]:$PORT_TCP_CHAIN\"" "$relay_log") check "relay logged its listener as [::1]:$PORT_TCP_CHAIN ($n)" $((n >= 1)) # 2. HTTP sink: lw auth token at an IPv6 URL, the token carried by curl -g token="$("$BIN" auth token -url "https://[::1]:$PORT_HTTP_SINK" -user viewer-01 \ -password-file "$AUTH/viewer-01.pass" -ca-file "$PKI/ca.crt" 2>>"$LOG/auth-cli.out")" check "http sink: lw auth token -url https://[::1]:$PORT_HTTP_SINK issued a token" $(is_set "$token") code="$(curl "${curl_args[@]}" -o /dev/null -w '%{http_code}' --cacert "$PKI/ca.crt" \ -H @<(printf 'Authorization: Bearer %s\n' "$token") "https://[::1]:$PORT_HTTP_SINK/status" 2>/dev/null || true)" check "http sink: curl -g https://[::1]:$PORT_HTTP_SINK/status with the token (HTTP $code)" $(is "$code" 200) sse="$(timeout 4 curl -gsN --noproxy '*' --cacert "$PKI/ca.crt" \ -H @<(printf 'Authorization: Bearer %s\n' "$token") \ "https://[::1]:$PORT_HTTP_SINK/stream" 2>/dev/null || true)" n=$(grep -c '^data:.*edge-01/' <<<"$sse") check "http sink: /stream delivered SSE events over [::1] ($n events)" $((n >= 1)) # 3. TCP sink: lw auth stream at an IPv6 address timeout 4 "$BIN" auth stream -addr "[::1]:$PORT_TCP_SINK" -user viewer-01 \ -password-file "$AUTH/viewer-01.pass" -ca-file "$PKI/ca.crt" \ >"$RUN/stream.out" 2>"$LOG/stream.err" n=$(grep -c 'edge-01/' "$RUN/stream.out") check "tcp sink: lw auth stream -addr [::1]:$PORT_TCP_SINK streamed entries ($n lines)" $((n >= 1)) section "Scenario 2: each listener keeps to its address family" # 4. :: is IPv6-only: it takes [::1] and leaves IPv4, and its port, alone who="$(answered_by "http://[::1]:$PORT_FAMILY")" check ":: listener accepts [::1] (answered by ${who:-nobody})" $(is "$who" any6) who="$(answered_by "http://127.0.0.1:$PORT_FAMILY")" check ":: listener refuses 127.0.0.1 (answered by ${who:-nobody})" $(is "$who" "") start_daemon any4 any4.toml wait_port $PORT_FAMILY 127.0.0.1 who="$(answered_by "http://127.0.0.1:$PORT_FAMILY")" check "0.0.0.0 binds the same port beside :: and takes 127.0.0.1 (answered by ${who:-nobody})" $(is "$who" any4) # 5. 0.0.0.0 is IPv4-only: with :: gone, [::1] finds nobody kill -TERM "$ANY6_PID" wait "$ANY6_PID" 2>/dev/null who="$(answered_by "http://[::1]:$PORT_FAMILY")" check "0.0.0.0 listener refuses [::1] (answered by ${who:-nobody})" $(is "$who" "") summary