Initial Awawawa
This commit is contained in:
18
docker/Dockerfile
Normal file
18
docker/Dockerfile
Normal file
@@ -0,0 +1,18 @@
|
||||
# PatchBay test image; the same image runs as target or client (see entrypoint.sh).
|
||||
FROM debian:trixie-slim
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
build-essential pkg-config libssh2-1-dev libsqlite3-dev \
|
||||
openssh-server openssh-client python3 python3-flask python3-cryptography \
|
||||
sqlite3 netcat-openbsd iproute2 procps socat curl ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /src
|
||||
COPY . /src
|
||||
RUN make clean && make all test-c && make install \
|
||||
&& useradd --system --home-dir /nonexistent --shell /bin/sh patchbay \
|
||||
&& usermod -p '*' patchbay \
|
||||
&& mkdir -p /etc/patchbay /run/sshd
|
||||
|
||||
COPY docker/entrypoint.sh /entrypoint.sh
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
45
docker/compose.yml
Normal file
45
docker/compose.yml
Normal file
@@ -0,0 +1,45 @@
|
||||
# Integration test topology: one target (exit gateway) and two clients.
|
||||
# Run via docker/run-tests.sh (or "make docker-test").
|
||||
name: patchbay-test
|
||||
|
||||
x-common: &common
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: docker/Dockerfile
|
||||
image: patchbay-test
|
||||
volumes:
|
||||
- shared:/shared
|
||||
networks: [pbnet]
|
||||
init: true
|
||||
# Tunnel tests create veth pairs and network namespaces.
|
||||
privileged: true
|
||||
|
||||
services:
|
||||
target:
|
||||
<<: *common
|
||||
hostname: target
|
||||
environment:
|
||||
PB_ROLE: target
|
||||
PB_MAILHOST: log
|
||||
ports:
|
||||
- "127.0.0.1:18443:8443" # web UI for manual checks
|
||||
|
||||
client1:
|
||||
<<: *common
|
||||
hostname: client1
|
||||
environment:
|
||||
PB_ROLE: client
|
||||
depends_on: [target]
|
||||
|
||||
client2:
|
||||
<<: *common
|
||||
hostname: client2
|
||||
environment:
|
||||
PB_ROLE: client
|
||||
depends_on: [target]
|
||||
|
||||
volumes:
|
||||
shared:
|
||||
|
||||
networks:
|
||||
pbnet:
|
||||
65
docker/entrypoint.sh
Executable file
65
docker/entrypoint.sh
Executable file
@@ -0,0 +1,65 @@
|
||||
#!/bin/bash
|
||||
# Test container entrypoint. PB_ROLE=target|client selects the role.
|
||||
set -eu
|
||||
|
||||
ROLE=${PB_ROLE:-client}
|
||||
CONF=/etc/patchbay/patchbay.conf
|
||||
mkdir -p /etc/patchbay /shared
|
||||
|
||||
if [ "$ROLE" = target ]; then
|
||||
if [ ! -f "$CONF" ]; then
|
||||
cat > "$CONF" <<EOF
|
||||
Role = target
|
||||
TargetPort = 2222
|
||||
HubPort = 7701
|
||||
WebPort = 8443
|
||||
SysopUser = admin
|
||||
SysopEmail = ${PB_SYSOP_EMAIL:-admin@example.org}
|
||||
SysopPassword = ${PB_SYSOP_PASSWORD:-patchbay-test-pw}
|
||||
MailHost = ${PB_MAILHOST:-log}
|
||||
EOF
|
||||
chmod 600 "$CONF"
|
||||
# Optional real SMTP account, mounted at runtime (INI [smtp] section).
|
||||
if [ -f /run/secrets/smtp.conf ]; then
|
||||
python3 - "$CONF" <<'PYEOF'
|
||||
import configparser, sys
|
||||
sys.path.insert(0, "/usr/local/lib/patchbay/web")
|
||||
from patchbay_web.conf import Config
|
||||
c = configparser.RawConfigParser()
|
||||
c.read("/run/secrets/smtp.conf")
|
||||
s = c["smtp"]
|
||||
Config(sys.argv[1]).update({
|
||||
"MailHost": s["host"], "MailPort": s["port"],
|
||||
"MailSecurity": "ssl" if s.getboolean("ssl", fallback=False) else "starttls",
|
||||
"MailUser": s["user"], "MailPassword": s["password"], "MailFrom": s.get("from", s["user"]),
|
||||
})
|
||||
PYEOF
|
||||
fi
|
||||
fi
|
||||
patchbayd -c "$CONF" &
|
||||
sleep 0.5
|
||||
/usr/local/lib/patchbay/patchbay-sshd "$CONF" &
|
||||
cd /usr/local/lib/patchbay/web
|
||||
python3 -m patchbay_web -c "$CONF" serve &
|
||||
else
|
||||
if [ ! -f "$CONF" ]; then
|
||||
cat > "$CONF" <<EOF
|
||||
Role = client
|
||||
TargetHost = ${PB_TARGET:-target}
|
||||
TargetPort = 2222
|
||||
IdentityFile = /etc/patchbay/id_ed25519
|
||||
EOF
|
||||
fi
|
||||
[ -f /etc/patchbay/id_ed25519 ] || ssh-keygen -q -t ed25519 -N '' -C "$(hostname)" -f /etc/patchbay/id_ed25519
|
||||
cp /etc/patchbay/id_ed25519.pub "/shared/$(hostname).pub"
|
||||
|
||||
# Test services: TCP and UDP echo.
|
||||
socat TCP-LISTEN:9000,fork,reuseaddr EXEC:cat &
|
||||
socat UDP-RECVFROM:9001,fork EXEC:cat &
|
||||
# Discard sink for throughput tests.
|
||||
socat -u TCP-LISTEN:9002,fork,reuseaddr OPEN:/dev/null &
|
||||
patchbayd -c "$CONF" &
|
||||
fi
|
||||
|
||||
# Exit when any service dies so the test notices.
|
||||
wait -n
|
||||
164
docker/run-tests.sh
Executable file
164
docker/run-tests.sh
Executable file
@@ -0,0 +1,164 @@
|
||||
#!/bin/bash
|
||||
# End-to-end test: builds the image, starts target + 2 clients, patches
|
||||
# services through PatchBay and checks TCP, UDP, client-to-client, integrity,
|
||||
# throughput, Services reporting and the web login.
|
||||
# Usage: docker/run-tests.sh [--keep] (--keep leaves the containers running)
|
||||
set -u
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
DC="docker compose -f compose.yml"
|
||||
KEEP=0
|
||||
[ "${1:-}" = "--keep" ] && KEEP=1
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
ok() { echo "PASS $*"; PASS=$((PASS + 1)); }
|
||||
bad() { echo "FAIL $*"; FAIL=$((FAIL + 1)); }
|
||||
on() { local c=$1; shift; $DC exec -T "$c" bash -c "$*"; }
|
||||
|
||||
cleanup() {
|
||||
if [ $KEEP = 0 ]; then
|
||||
$DC down -v >/dev/null 2>&1
|
||||
else
|
||||
echo "containers kept; web UI at https://127.0.0.1:18443 (admin / patchbay-test-pw, code in 'docker compose -f docker/compose.yml logs target')"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
$DC down -v >/dev/null 2>&1
|
||||
$DC up -d --build || exit 1
|
||||
|
||||
echo "waiting for client keys..."
|
||||
for i in $(seq 60); do
|
||||
on target 'ls /shared/client1.pub /shared/client2.pub' >/dev/null 2>&1 && break
|
||||
sleep 1
|
||||
done
|
||||
|
||||
on target 'cd /usr/local/lib/patchbay/web &&
|
||||
python3 -m patchbay_web add-client client1 < /shared/client1.pub &&
|
||||
python3 -m patchbay_web add-client client2 < /shared/client2.pub' >/dev/null
|
||||
|
||||
# Patch: echo service on client1 exposed publicly (via splitter) and on
|
||||
# client2's loopback, a UDP echo, and a discard sink for throughput.
|
||||
on target "sqlite3 /etc/patchbay/patchbay.db \"
|
||||
INSERT INTO nodes (id, type, client_id, host, port, proto) VALUES
|
||||
(1, 'client_source', 1, '127.0.0.1', 9000, 'tcp'),
|
||||
(2, 'public_sink', NULL, '0.0.0.0', 2200, 'tcp'),
|
||||
(3, 'client_source', 1, '127.0.0.1', 9001, 'udp'),
|
||||
(4, 'public_sink', NULL, '0.0.0.0', 2201, 'udp'),
|
||||
(5, 'splitter', NULL, '', 0, 'tcp'),
|
||||
(6, 'client_sink', 2, '127.0.0.1', 7000, 'tcp'),
|
||||
(7, 'client_source', 1, '127.0.0.1', 9002, 'tcp'),
|
||||
(8, 'public_sink', NULL, '0.0.0.0', 2202, 'tcp');
|
||||
INSERT INTO links (from_node, to_node) VALUES (1, 5), (5, 2), (5, 6), (3, 4), (7, 8);\""
|
||||
on target 'echo RELOAD | socat - UNIX-CONNECT:/run/patchbay/api.sock' >/dev/null
|
||||
|
||||
echo "waiting for clients to connect..."
|
||||
for i in $(seq 60); do
|
||||
st=$(on target 'echo STATUS | socat - UNIX-CONNECT:/run/patchbay/api.sock')
|
||||
echo "$st" | grep -q '"1":{"since"' && echo "$st" | grep -q '"2":{"since"' && break
|
||||
sleep 1
|
||||
done
|
||||
echo "$st" | grep -q '"2":{"since"' && ok "both clients connected" || { bad "clients did not connect: $st"; $DC logs --tail 40; exit 1; }
|
||||
sleep 2
|
||||
|
||||
r=$(on client2 'echo hello-public | socat -t3 - TCP:target:2200')
|
||||
[ "$r" = hello-public ] && ok "TCP via public sink" || bad "TCP via public sink: got '$r'"
|
||||
|
||||
r=$(on client2 'echo hello-client | socat -t3 - TCP:127.0.0.1:7000')
|
||||
[ "$r" = hello-client ] && ok "TCP client sink (client2 -> client1)" || bad "client sink: got '$r'"
|
||||
|
||||
r=$(on client2 'echo hello-udp | socat -T3 - UDP:target:2201')
|
||||
[ "$r" = hello-udp ] && ok "UDP via public sink" || bad "UDP via public sink: got '$r'"
|
||||
|
||||
r=$(on client2 'head -c 8000000 /dev/urandom > /tmp/blob && socat -t10 - TCP:target:2200 < /tmp/blob | sha256sum | cut -c1-64; sha256sum < /tmp/blob | cut -c1-64')
|
||||
[ "$(echo "$r" | sed -n 1p)" = "$(echo "$r" | sed -n 2p)" ] && ok "8 MB echo integrity" || bad "integrity: $r"
|
||||
|
||||
r=$(on client2 'for i in $(seq 20); do (echo "par$i" | socat -t5 - TCP:target:2200) & done; wait' | sort | uniq | wc -l)
|
||||
[ "$r" = 20 ] && ok "20 parallel connections" || bad "parallel connections: $r distinct answers"
|
||||
|
||||
r=$(on client2 'start=$(date +%s.%N); head -c 500000000 /dev/zero | socat -u - TCP:target:2202; end=$(date +%s.%N); echo "$start $end" | awk "{printf \"%.0f\", 500/(\$2-\$1)}"')
|
||||
[ -n "$r" ] && [ "$r" -gt 0 ] 2>/dev/null && ok "throughput 500 MB: ${r} MB/s" || bad "throughput: $r"
|
||||
|
||||
sleep 1
|
||||
r=$(on target 'echo LIVE | socat - UNIX-CONNECT:/run/patchbay/api.sock')
|
||||
echo "$r" | grep -Eq '"8":\{[^}]*"total_in":[0-9]{9}' && ok "stats count bytes" || bad "stats: $r"
|
||||
|
||||
r=$(on target "sqlite3 /etc/patchbay/patchbay.db \"SELECT COUNT(*) FROM services WHERE client_id = 1 AND port = 9000\"")
|
||||
[ "$r" = 1 ] && ok "Services report from client1" || bad "Services: $r rows"
|
||||
|
||||
# Tunnel nodes: a veth pair named tunN stands in for a VPN interface, its peer
|
||||
# end lives in network namespace "peer" with echo services on 10.77.0.2.
|
||||
mktun() {
|
||||
on "$1" "ip netns add peer && ip link add $2 type veth peer name p0 && ip link set p0 netns peer &&
|
||||
ip addr add 10.77.0.1/24 dev $2 && ip link set $2 up &&
|
||||
ip netns exec peer ip addr add 10.77.0.2/24 dev p0 && ip netns exec peer ip link set p0 up &&
|
||||
ip netns exec peer ip link set lo up &&
|
||||
(ip netns exec peer socat TCP-LISTEN:9100,fork,reuseaddr EXEC:cat >/dev/null 2>&1 &) &&
|
||||
(ip netns exec peer socat UDP-RECVFROM:9101,fork EXEC:cat >/dev/null 2>&1 &)"
|
||||
}
|
||||
mktun target tun7
|
||||
mktun client1 tun8
|
||||
mktun client2 tun9
|
||||
on target "sqlite3 /etc/patchbay/patchbay.db \"
|
||||
INSERT INTO nodes (id, type, client_id, host, port, proto, iface) VALUES
|
||||
(20, 'tunnel_source', NULL, '10.77.0.2', 9100, 'tcp', 'tun7'),
|
||||
(21, 'public_sink', NULL, '0.0.0.0', 2210, 'tcp', ''),
|
||||
(22, 'tunnel_source', NULL, '10.77.0.2', 9101, 'udp', 'tun7'),
|
||||
(23, 'public_sink', NULL, '0.0.0.0', 2211, 'udp', ''),
|
||||
(24, 'tunnel_sink', NULL, '', 9200, 'tcp', 'tun7'),
|
||||
(25, 'client_source', 1, '127.0.0.1', 9000, 'tcp', ''),
|
||||
(26, 'tunnel_source', 1, '10.77.0.2', 9100, 'tcp', 'tun8'),
|
||||
(27, 'public_sink', NULL, '0.0.0.0', 2212, 'tcp', ''),
|
||||
(28, 'tunnel_sink', 2, '', 9300, 'tcp', 'tun9'),
|
||||
(29, 'client_source', 1, '127.0.0.1', 9000, 'tcp', ''),
|
||||
(30, 'tunnel_sink', 2, '', 9301, 'udp', 'tun9'),
|
||||
(31, 'tunnel_source', NULL, '10.77.0.2', 9101, 'udp', 'tun7');
|
||||
INSERT INTO links (from_node, to_node) VALUES (20, 21), (22, 23), (25, 24), (26, 27), (29, 28), (31, 30);\""
|
||||
on target 'echo RELOAD | socat - UNIX-CONNECT:/run/patchbay/api.sock' >/dev/null
|
||||
sleep 3
|
||||
|
||||
r=$(on client2 'echo t-src-target | socat -t3 - TCP:target:2210')
|
||||
[ "$r" = t-src-target ] && ok "tunnel source on target (TCP)" || bad "tunnel source on target: got '$r'"
|
||||
|
||||
r=$(on client2 'echo t-src-udp | socat -T3 - UDP:target:2211')
|
||||
[ "$r" = t-src-udp ] && ok "tunnel source on target (UDP)" || bad "tunnel source UDP: got '$r'"
|
||||
|
||||
r=$(on target 'echo t-sink-target | ip netns exec peer socat -t3 - TCP:10.77.0.1:9200')
|
||||
[ "$r" = t-sink-target ] && ok "tunnel sink on target" || bad "tunnel sink on target: got '$r'"
|
||||
|
||||
r=$(on target 'echo not-via-tun | socat -t2 - TCP:127.0.0.1:9200 2>&1')
|
||||
[ "$r" != not-via-tun ] && ok "tunnel sink only accepts traffic from its interface" || bad "tunnel sink reachable via lo"
|
||||
|
||||
r=$(on client2 'echo t-src-client | socat -t3 - TCP:target:2212')
|
||||
[ "$r" = t-src-client ] && ok "tunnel source on client1" || bad "tunnel source on client: got '$r'"
|
||||
|
||||
r=$(on client2 'echo t-sink-client | ip netns exec peer socat -t3 - TCP:10.77.0.1:9300')
|
||||
[ "$r" = t-sink-client ] && ok "tunnel sink on client2 (TCP)" || bad "tunnel sink on client: got '$r'"
|
||||
|
||||
r=$(on client2 'echo t-sink-udp | ip netns exec peer socat -T3 - UDP:10.77.0.1:9301')
|
||||
[ "$r" = t-sink-udp ] && ok "tunnel sink on client2 -> target tunnel source (UDP)" || bad "client tunnel sink UDP: got '$r'"
|
||||
|
||||
on target 'echo SERVICES | socat - UNIX-CONNECT:/run/patchbay/api.sock' >/dev/null
|
||||
sleep 2
|
||||
r=$(on target "sqlite3 /etc/patchbay/patchbay.db \"SELECT COUNT(*) FROM interfaces WHERE (client_id = 0 AND name = 'tun7') OR (client_id = 1 AND name = 'tun8')\"")
|
||||
[ "$r" = 2 ] && ok "tun interfaces reported" || bad "interfaces: $r rows"
|
||||
|
||||
# Web login over HTTPS with the emailed (logged) code.
|
||||
r=$(on target 'set -e
|
||||
J=/tmp/jar; rm -f $J; U=https://127.0.0.1:8443
|
||||
tok=$(curl -sk -c $J -b $J $U/login | sed -n "s/.*csrf-token\" content=\"\([^\"]*\)\".*/\1/p")
|
||||
curl -sk -c $J -b $J -o /dev/null --data-urlencode "csrf_token=$tok" -d username=admin -d password=patchbay-test-pw $U/login
|
||||
echo $tok' 2>&1)
|
||||
sleep 1
|
||||
code=$($DC logs target 2>&1 | sed -n 's/.*login code is: \([0-9]\{6\}\).*/\1/p' | tail -1)
|
||||
r=$(on target "set -e
|
||||
J=/tmp/jar; U=https://127.0.0.1:8443
|
||||
tok=\$(curl -sk -c \$J -b \$J \$U/verify | sed -n 's/.*csrf-token\" content=\"\([^\"]*\)\".*/\1/p')
|
||||
curl -sk -c \$J -b \$J -o /dev/null -d csrf_token=\$tok -d code=$code \$U/verify
|
||||
curl -sk -c \$J -b \$J \$U/api/graph")
|
||||
echo "$r" | grep -q '"client_source"' && ok "web login with email code + API" || bad "web login: code='$code' $r"
|
||||
|
||||
echo
|
||||
echo "$PASS passed, $FAIL failed"
|
||||
[ $FAIL = 0 ]
|
||||
Reference in New Issue
Block a user