Inspect and Call Varlink Services with varlinkctl
You will finish with a small workflow for finding what a Varlink service provides, reading the exact interface definition, and calling a method with JSON. The examples use varlinkctl from systemd 255.4-1ubuntu8.17 on this machine. The commands inspect services or make a single request; none of the introductory steps changes a service or its configuration.
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. A method call may have side effects, so read its interface before invoking it. Most inspection commands are ordinary user commands. Access to a protected socket or executable may require privileges, but adding sudo does not make an unsafe method call safe.
1. Confirm the installed command
Start by checking the binary and package version. This is read-only and does not need 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
Varlinkctl accepts an explicit address, which makes the connection type clear:
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 supplied by your service. Do not guess an address from a service name. A missing socket is usually a service or installation problem, not a reason to create a socket 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 output describes the endpoint that answered. It does not prove that 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. The service can implement several interfaces, and a shortened name will not identify the same thing.
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 means pretty JSON on an interactive terminal, but short JSON when output is piped, so scripts should not depend on 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 verification 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 different: varlinkctl reads JSON from standard input.
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. The --more option is for methods that explicitly support multiple replies. It sets Varlink's more flag and switches output to JSON-SEQ so separate reply objects can be distinguished:
$ varlinkctl --more call SOCKET_PATH FULL.INTERFACE.Method '{}'
Replace the placeholders only after introspection. The command remains running until the service marks the final reply, so a method that does not support this mechanism may fail or wait unexpectedly. Do not use --more merely because you want verbose output.
The --oneway option tells the service not to send a reply and makes varlinkctl exit immediately after sending the request. This is appropriate only when the method contract allows one-way calls. There is no response to confirm success, so record that limitation in scripts and monitoring.
Safety boundary
Calling a method can change state, trigger work or expose data. 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 make a service implement the interface, so verify the deployed endpoint separately with list-interfaces and introspect.
Common failure points
- Wrong address: use an existing Unix socket or executable path. An absolute path and
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. Do not parse 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
- You confirmed the installed systemd and varlinkctl versions.
- You used
infoandlist-interfacesto identify the endpoint's contract. - You used
introspectbefore constructing a method request. - Your call used a fully qualified method name and valid JSON, or you deliberately used standard input.
- You treated
--more,--onewayand state-changing methods as contract-sensitive operations. - You validated any local IDL with
validate-idland know that validation alone does not deploy it.