Inspect and Call Varlink Services with varlinkctl
Varlink services describe themselves, and varlinkctl is how you read that description before calling anything blind. This guide finds what a service offers, reads its exact interface, and calls a method with plain JSON. The examples use varlinkctl from systemd 255.4-1ubuntu8.17: the inspection commands never change a service or its configuration, only a method call can.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about fifteen minutes. You need a shell, the varlinkctl command, and access to a Varlink socket or executable. Most inspection commands are ordinary user commands. A protected socket or executable may need elevated privileges, but sudo does not make an unsafe method call safe: a method may have side effects, so read its interface before invoking it.
1. Confirm the installed command
Start by checking the binary and package version. This is read-only and needs no elevated privileges:
$ command -v varlinkctl
/usr/bin/varlinkctl
$ varlinkctl --version
systemd 255 (255.4-1ubuntu8.17)
The installed man page documents the commands used here as additions in version 255. If your version is older, check varlinkctl --help and its local man page before copying an example.
Checkpoint
If command -v finds nothing, install the systemd package provided by your distribution or use the executable from the correct package. Do not download a replacement binary into a system path just to make this guide work.
2. Choose a service address
unix:/absolute/pathrefers to a Unix socket. An abstract socket usesunix:@name.exec:/absolute/pathrefers to an executable that varlinkctl starts.- An absolute socket or executable path can be used directly. A relative path must begin with
./.
On this host, systemd-resolved exposes a Varlink socket at /run/systemd/resolve/io.systemd.Resolve. Check that the path exists before using it:
$ test -S /run/systemd/resolve/io.systemd.Resolve && echo 'socket found'
socket found
Replace that path with the socket or executable your own service supplies. Do not guess an address from a service name: a missing socket is usually a service or installation problem, not a reason to create one by hand.
3. Read the service summary
Use info to ask the service for its vendor, product, version, URL and implemented interfaces:
$ varlinkctl info /run/systemd/resolve/io.systemd.Resolve
Vendor: The systemd Project
Product: systemd (systemd-resolved)
Version: 255 (255.4-1ubuntu8.17)
URL: https://systemd.io/
Interfaces: io.systemd
io.systemd.Resolve
org.varlink.service
This describes the endpoint that answered. It does not prove every method is harmless, or that a service is suitable for an untrusted caller.
Checkpoint
Copy the interface name you intend to inspect exactly, including capitalisation and full stops. A service can implement several interfaces, and a shortened name will not identify the same one.
4. List and inspect the interface
If you only need the names, use list-interfaces:
$ varlinkctl list-interfaces /run/systemd/resolve/io.systemd.Resolve
io.systemd
io.systemd.Resolve
org.varlink.service
Then use introspect with one complete interface name. It returns the IDL, including types, methods, parameters and return values:
$ varlinkctl introspect /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve
interface io.systemd.Resolve
type ResolvedAddress(
ifindex: ?int,
...
The output above is intentionally abbreviated: read the complete output from your own service before constructing a call. In particular, check which fields are required, which are optional, and whether a method can return multiple replies.
For readable terminal output, add -j where JSON output is available. The default JSON mode is short, with minimal whitespace. -j gives pretty JSON on an interactive terminal but short JSON when output is piped, so scripts should never depend on the indentation.
5. Make one JSON method call
Use the fully qualified method name and a JSON object containing its arguments. This example asks systemd-resolved to resolve an IPv4 address:
$ varlinkctl call /run/systemd/resolve/io.systemd.Resolve \
io.systemd.Resolve.ResolveHostname \
'{"name":"systemd.io","family":2}' -j
{
"addresses" : [
{
"ifindex" : 2,
"family" : 2,
"address" : [
185,
199,
111,
153
]
}
],
"name" : "systemd.io",
"flags" : 1048577
}
Your address list, interface index and flags will differ. The useful check is that the response is a JSON object matching the method's declared return fields. If a method takes no parameters, pass {}: omitting the argument object does something else entirely, since varlinkctl then reads JSON from standard input instead.
For example, this is equivalent when the input is kept in a shell variable:
$ params='{"name":"systemd.io","family":2}'
$ printf '%s\n' "$params" | varlinkctl call \
/run/systemd/resolve/io.systemd.Resolve \
io.systemd.Resolve.ResolveHostname -j
{"addresses":[...],"name":"systemd.io","flags":1048577}
Keep JSON in single quotes when writing it directly in a POSIX shell. If values come from users or another command, construct and validate the JSON with a JSON-aware tool rather than concatenating untrusted text into a shell command.
6. Handle streaming and one-way calls deliberately
Most calls expect one reply.
--moreis for methods that explicitly support multiple replies. It sets Varlink'smoreflag and switches output to JSON-SEQ so separate reply objects can be told apart. Replace the placeholders below only after introspection: the command keeps running until the service marks the final reply, so a method that does not support this may fail or wait unexpectedly. Do not use it merely because you want verbose output.--onewaytells the service not to send a reply. varlinkctl exits immediately after sending the request. Use it only when the method contract allows one-way calls; there is no response to confirm success, so record that limitation in scripts and monitoring.
$ varlinkctl --more call SOCKET_PATH FULL.INTERFACE.Method '{}'
Safety boundary
Calling a method can change state, trigger work or expose data, and there is no general undo command in varlinkctl. For a state-changing method, identify the service owner, read the IDL and method documentation, test against a disposable service where possible, and keep the original request so you can use the service's own documented reversal procedure.
7. Validate an interface definition file
validate-idl parses a Varlink interface definition, checks its syntax and internal consistency, and prints the validated definition with syntax highlighting. Give it a file path:
$ varlinkctl validate-idl ./example.varlink
interface example.Service
...
It reads standard input when the file argument is omitted:
$ varlinkctl validate-idl < ./example.varlink
interface example.Service
...
This command does not install, load or publish the interface. A successful parse does not mean a service implements it, so verify the deployed endpoint separately with list-interfaces and introspect.
Common traps
- Wrong address. Use an existing Unix socket or executable path. An absolute path and the
unix:form are both accepted, but they must identify a real endpoint. - Incomplete method name. Use the fully qualified name shown by introspection, not just the final method component.
- Missing JSON. An omitted argument object makes varlinkctl wait for JSON on standard input; pass
{}for an empty argument set. - Unexpected output formatting. Use
--json=shortfor compact machine output or--json=prettyfor deliberate human-readable output, and never parse the pretty spacing. - Permission denied. Verify the socket's access policy and service documentation first. Use elevated privileges only when they are expected for that endpoint, and avoid running arbitrary executable services as root.
Done means
- Versions confirmed. You confirmed the installed systemd and varlinkctl versions.
- Contract identified. You used
infoandlist-interfacesto identify the endpoint's contract. - Interface read first. You used
introspectbefore constructing a method request. - Call formed correctly. Your call used a fully qualified method name and valid JSON, or you deliberately used standard input.
- Risky options respected. You treated
--more,--onewayand state-changing methods as contract-sensitive operations. - IDL validated separately. You validated any local IDL with
validate-idl, knowing that validation alone does not deploy it.