varlinkctl: add protocol upgrade support
The varlink spec supports protocol upgrades and they are very useful to e.g. transfer binary data directly via varlink. So far varlinkctl/sd-varlink was not supporting this. This commit adds support for it in varlinkctl by using the new code in sd-varlink and the generalized socket-forward code.
This commit is contained in:
@@ -255,3 +255,102 @@ systemd-run --wait --pipe --user --machine testuser@ \
|
||||
# test io.systemd.Unit in user manager
|
||||
systemd-run --wait --pipe --user --machine testuser@ \
|
||||
varlinkctl --more call "/run/user/$testuser_uid/systemd/io.systemd.Manager" io.systemd.Unit.List '{}'
|
||||
|
||||
# test --upgrade (protocol upgrade)
|
||||
UPGRADE_SOCKET="$(mktemp -d)/upgrade.sock"
|
||||
UPGRADE_SERVER="$(mktemp)"
|
||||
cat >"$UPGRADE_SERVER" <<'PYEOF'
|
||||
#!/usr/bin/env python3
|
||||
"""Varlink upgrade test server. With a socket path argument, listens on a unix socket.
|
||||
Without arguments, speaks over stdin/stdout (for ssh-exec: transport testing)."""
|
||||
import json, os, socket, sys
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
sock.bind(sys.argv[1])
|
||||
sock.listen(1)
|
||||
print("READY", flush=True)
|
||||
conn, _ = sock.accept()
|
||||
inp = conn.makefile("rb")
|
||||
out = conn.makefile("wb")
|
||||
else:
|
||||
inp = sys.stdin.buffer
|
||||
out = sys.stdout.buffer
|
||||
conn = sock = None
|
||||
|
||||
# Read the varlink request (NUL-terminated JSON)
|
||||
data = b""
|
||||
while True:
|
||||
chunk = inp.read(1)
|
||||
assert chunk, "Connection closed before receiving full varlink request"
|
||||
data += chunk
|
||||
if b"\0" in data:
|
||||
break
|
||||
|
||||
msg = json.loads(data.split(b"\0")[0])
|
||||
received_parameters = msg.get("parameters", {})
|
||||
out.write(b'{"parameters": {}}\0')
|
||||
out.flush()
|
||||
|
||||
# Upgraded protocol: send a non-JSON banner first to prove we're truly in raw mode,
|
||||
# then echo the received parameters, then reverse lines from the client
|
||||
out.write(b"<<< UPGRADED >>>\n")
|
||||
out.write((json.dumps(received_parameters) + "\n").encode())
|
||||
out.flush()
|
||||
data = inp.read().decode().rstrip("\n")
|
||||
out.write((data[::-1] + "\n").encode())
|
||||
out.flush()
|
||||
|
||||
if conn:
|
||||
conn.close()
|
||||
if sock:
|
||||
sock.close()
|
||||
PYEOF
|
||||
chmod +x "$UPGRADE_SERVER"
|
||||
|
||||
# Start the server in the background
|
||||
python3 "$UPGRADE_SERVER" "$UPGRADE_SOCKET" &
|
||||
SERVER_PID=$!
|
||||
|
||||
# Wait for server readiness
|
||||
timeout 5 bash -c "while [ ! -S '$UPGRADE_SOCKET' ]; do sleep 0.1; done"
|
||||
|
||||
# Test proxy mode: pipe data through --upgrade, passing parameters and validate
|
||||
result="$(echo "hello world" | varlinkctl call --upgrade "unix:$UPGRADE_SOCKET" io.systemd.test.Reverse '{"foo":"bar"}')"
|
||||
echo "$result" | grep "<<< UPGRADED >>>" >/dev/null
|
||||
echo "$result" | grep '"foo": "bar"' >/dev/null
|
||||
echo "$result" | grep "dlrow olleh" >/dev/null
|
||||
|
||||
wait "$SERVER_PID" || :
|
||||
|
||||
# Test --upgrade with stdin redirected from a regular file (epoll can't poll regular files,
|
||||
# so this exercises the fork+pipe fallback path)
|
||||
UPGRADE_SOCKET2="$(mktemp -d)/upgrade.sock"
|
||||
python3 "$UPGRADE_SERVER" "$UPGRADE_SOCKET2" &
|
||||
SERVER_PID=$!
|
||||
timeout 5 bash -c "while [ ! -S '$UPGRADE_SOCKET2' ]; do sleep 0.1; done"
|
||||
|
||||
echo "file input test" > /tmp/test-upgrade-input
|
||||
result="$(varlinkctl call --upgrade "unix:$UPGRADE_SOCKET2" io.systemd.test.Reverse '{"foo":"file"}' < /tmp/test-upgrade-input)"
|
||||
echo "$result" | grep "<<< UPGRADED >>>" >/dev/null
|
||||
echo "$result" | grep '"foo": "file"' >/dev/null
|
||||
echo "$result" | grep "tset tupni elif" >/dev/null
|
||||
|
||||
wait "$SERVER_PID" || :
|
||||
|
||||
# Test --upgrade over ssh-exec: transport (pipe pair, not a bidirectional socket).
|
||||
# This exercises the input_fd != output_fd path in sd_varlink_call_and_upgrade().
|
||||
# Reuse the same server script without a socket argument - it speaks over stdin/stdout.
|
||||
cat > "$SSHBINDIR"/ssh <<EOF
|
||||
#!/usr/bin/env bash
|
||||
exec python3 "$UPGRADE_SERVER"
|
||||
EOF
|
||||
chmod +x "$SSHBINDIR"/ssh
|
||||
|
||||
result="$(echo "ssh pipe test" | SYSTEMD_SSH="$SSHBINDIR/ssh" varlinkctl call --upgrade ssh-exec:foobar:test-upgrade io.systemd.test.Reverse '{"foo":"ssh"}')"
|
||||
echo "$result" | grep "<<< UPGRADED >>>" >/dev/null
|
||||
echo "$result" | grep '"foo": "ssh"' >/dev/null
|
||||
echo "$result" | grep "tset epip hss" >/dev/null
|
||||
|
||||
rm -f "$UPGRADE_SOCKET" "$UPGRADE_SOCKET2" "$UPGRADE_SERVER" /tmp/test-upgrade-input
|
||||
rm -rf "$(dirname "$UPGRADE_SOCKET")" "$(dirname "$UPGRADE_SOCKET2")"
|
||||
|
||||
Reference in New Issue
Block a user