Commit Graph
3024 Commits
Author SHA1 Message Date
Yu Watanabe a4e4ced188 resolve: use custom hash_ops for managing Link objects 2026-09-23 11:38:15 +09:00
Yu Watanabe 7a19ecb825 resolve: several trivial cleanups for dnssd
- make several functions static,
- add serveral missing assertions,
- several coding style cleanups.
2026-09-23 11:38:15 +09:00
Yu Watanabe e677674522 resolve: use custom hash_ops for Manager.dnssd_registered_services 2026-09-23 11:38:15 +09:00
Yu Watanabe 3959ccbd32 resolve: drop unused argument t from mdns_service_update() 2026-09-23 11:38:15 +09:00
Yu Watanabe 923146d951 resolve: make several functions static 2026-09-23 11:38:15 +09:00
Yu Watanabe 02f65ebead resolve: stop timer event source and detach from browser when a dnssd service is removed
Otherwise, if the timer event source is enabled, a new query may be
invoked by the already removed service.

This also renames dns_remove_service() to dnssd_discovered_service_detach(),
and drops unused DnssdDiscoveredService.query.
2026-09-23 11:38:14 +09:00
Yu Watanabe 219abd95ae resolve: detach DnsServiceBrowser object from Manager when freed or disconnected
When a DnsServiceBrowser object is detached from the manager,
all queries, event sources, and then the reference to Manager should be
dropped. Otherwise, spurious query may be triggered for already
disconnected varlink.
2026-09-23 11:38:14 +09:00
Christian Glombek b3896afea2 test: add unit coverage for mDNS announcement packetization
Cover the size derivation and the packing decisions that the integration
test cannot reach: its veth MTU exceeds every record TEST-89 publishes,
so neither the oversized-record fallback nor the drop of a record beyond
the RFC 6762 section 17 ceiling is ever exercised there.

The cases pin the two size bounds for both families (including the
fallback for an MTU not known yet and the jumbo-frame cap), the split
at max_size, the lone oversized record in a fragmented packet, the
drop of a record that fits nowhere, the skip of a record that cannot be
encoded at all, that
answer-item flags reach the wire (a goodbye must keep TTL 0 and its
cache-flush bit), and that name compression stays within a packet --
the property the split exists for, since every DNS-SD name shares a
suffix and a compression pointer may not cross a packet boundary.
2026-09-23 00:30:03 +02:00
Christian Glombek d96c288ed4 resolved: split oversized mDNS announcements across MTU-sized packets
dns_scope_announce() put the entire zone -- for DNS-SD every PTR/SRV/TXT
record of every published service -- into a single reply packet. With a
few hundred published services that grows far beyond the interface MTU,
and beyond what RFC 6762 section 17 permits: a packet larger than the
MTU is sent using fragments, and such a packet "MUST NOT contain more
than one resource record".

Pack the announcement into as many MTU-sized packets as needed instead.
A single record that does not fit the MTU by itself is emitted alone in
an oversized packet, relying on IP fragmentation, which the same section
explicitly allows for that case -- bounded by its hard 9000-byte
ceiling, headers included, which also caps jumbo-frame links. While the
interface MTU is not known yet, packets are cut to the datagram size
every host of the family must be able to accept -- RFC 791 section
3.1's 576 octets for IPv4, whole or reassembled, and RFC 8200 section
5's 1280-byte minimum link MTU for IPv6, which no link fragments --
rather than to that ceiling, so an announcement is not packed for
deliberate fragmentation merely because the MTU was not read yet.

A record that cannot be encoded at any size -- a character string past
255 bytes, say -- is skipped like one too large for the ceiling, rather
than failing the whole announcement: dns_packet_append_rr() rolls the
packet back on every failure, so its neighbours are unaffected either
way. The packetizer returns how many it dropped and the scope warns on
it, since nothing else would distinguish an announcement that carried
everything from one that did not -- and for a withdrawal that matters:
the caller drops the records from the zone either way, so one skipped
here is never withdrawn at all and peers keep it for its full TTL.

The packing itself lives in resolved-mdns.c as mdns_announcement_packetize(),
taking an answer and the two size bounds and returning finalized packets,
so it can be tested without a scope, a link or a socket.
Emission is best effort per packet -- a failed send no longer withholds
the announcement's remaining packets -- and each pass logs the packet and
record counts it actually put on the wire. Each packet takes its own
token from the scope's multicast rate limit: that limit bounds datagrams
on the wire, and an announcement that would exhaust its burst is the
very flood it exists to cap.

dns_scope_send_goodbye(), the other unsolicited-response emitter, goes
through the same packetizer: a withdrawal of many records had the same
oversized-packet problem, and a single record failing to append
abandoned the whole withdrawal. Its return value follows the emitter's
now, so a withdrawal split across packets reports an error when any one
of them fails to go out.
2026-09-23 00:30:03 +02:00
Frantisek Sumsal efa16c7f31 dns-packet: limit the number of jumps during packet decompression
So far we didn't limit the number of jumps when decompressing a DNS
packet. This can lead to some extra work with specially crafted packets
that send a staircase of thousands of descending compression pointers,
which can then be amplified by referencing such staircase from thousands
of names.

Given that legitimate DNS packets need only a handful of jumps in the
compression chains, let's limit the number of jumps per a single name to
256 (the same limit as, for example, Unbound currently has).

Reported by opensec-intelligence (OpenSec).
2026-09-22 15:55:13 +02:00
Christian Glombek 21de179519 dns-packet: roll back the compression map when the root label fails
dns_packet_append_name() appends a name's labels and then the root byte
that terminates it. A failure on that last byte returned directly,
leaving both the labels on the packet and the compression entries this
call had added -- entries naming offsets the caller then truncates away.
Take the same rollback path as every other failure in the function.

Independent of the map invariant fixed before it: this is reachable
whether or not any offset exceeded the pointer range.
2026-09-07 04:26:36 +02:00
Christian Glombek a4b109f01f test: dns-packet: pin suffix compression past the pointer range
The existing case only covered a fully-uncompressed re-occurrence. Every
DNS-SD name shares an in-range suffix, so also pin that a name first
seen beyond the pointer range still compresses from its mapped suffix --
one label plus a two-byte pointer per occurrence, map unchanged, and the
pointer addressing the suffix's prior occurrence rather than any
in-range octet.

A padding helper puts the next append at a chosen offset, for the two
sides of the boundary: at 0x3FFF the name is still mapped and re-occurs
as a pointer whose wire bytes are checked independently of the parser;
one past it, it is not mapped.
2026-09-07 04:26:36 +02:00
Christian Glombek a95358da6f dns-packet: keep unreferenceable names out of the compression map
dns_packet_append_name() added every name it appends to the packet's
compression map, including names written at offsets >= 0x4000 that an
RFC 1035 compression pointer (14 bits) can never reference. Such
entries are not just useless -- when the same name occurs again, the
lookup finds it, the pointer cannot be emitted, the code falls through
to appending the labels again, and the re-insertion into the map then
fails the whole packet build with hashmap_ensure_put() returning
-EEXIST.

This is easily triggered with mDNS: an unsolicited DNS-SD announcement
covering a zone with a few hundred services exceeds 16KiB, and
dns_scope_announce() then fails with "Failed to build reply packet:
File exists".

This lifts the failure only: dns_scope_announce() still builds the whole
zone into one packet and hands it to the socket as a single datagram,
fragmented as the link requires. Splitting announcements across
MTU-sized packets, as RFC 6762 section 17 asks, is done separately.

Only insert a name into the map if its offset is pointer-expressible,
making "mapped implies referenceable" a map invariant. Re-occurrences of
out-of-range names are emitted as labels again -- the only encoding RFC
1035 section 4.1.4 permits in that case -- still compressed from the
first suffix that is mapped in range.

The lookup restates that range as control flow rather than asserting it.
Under -Db_ndebug=true, which CI builds, assert() is
__builtin_unreachable(), so an out-of-range entry would put 0xC000 | n
truncated to 14 bits on the wire -- a pointer to an arbitrary earlier
offset -- where falling through emits the labels instead. This layer is
shared with unicast DNS, DoT and LLMNR.

The pointer's tag bits and its offset bound are now named once, in
dns-def.h next to the other wire-format constants rather than in
dns-packet.h, since the format is RFC 1035's and not this packet
abstraction's -- dhcp-message.c open-codes the same two bits to parse
compressed names out of DHCP options. Compile-time checks pin that the
two partition the 16-bit field and that the tag is its top two bits,
which is what lets the reader take it from the first octet alone.

The offset's other bound becomes control flow along with it: it was an
assert(), which under -Db_ndebug=true is no check at all, for the bound
whose violation puts a pointer to the wrong bytes on the wire. It is
also tightened to the start of the name being appended rather than the
end of the packet, which is the "prior occurrence" RFC 1035 section
4.1.4 allows a pointer to address and what dns_packet_read_name()
enforces on the way in.

Add regression tests, including one through dns_packet_append_rr() --
the path the reported failure came from, where the name append is nested
inside the rdlength backpatch and two further rollbacks -- and one for
the reader's label-type dispatch, which the renamed constants touch.
2026-09-07 04:26:33 +02:00
Armaan Sandhu 4a25049a9e resolved: don't disable the listening socket when a datagram is dropped
manager_recv() sizes its buffer with next_datagram_size_fd(), which peeks at
the datagram with MSG_PEEK|MSG_TRUNC. That peek makes the kernel verify the UDP
checksum - which is the whole point of using it over FIONREAD - and hence also
drop the datagram when the checksum is bad. The datagram that woke the event
source can thus be gone by the time we peek at it, and the peek returns EAGAIN.

manager_recv() returned that error, while the recvmsg_safe() below, which can
fail the same way, already treats it as "no packet". The callers on the mDNS,
LLMNR and stub listening sockets pass a negative return on to the event loop,
which disables the event source, so a single datagram with a bad checksum -
something anyone on the link can send - stops resolved from receiving on that
socket for good. on_dns_packet() completes the transaction with -EAGAIN instead
of retrying it.

So return 0 on a transient error here too, like the other users of the helper
on an event source do. Disconnect errors are left as they are: they show up
here from ICMP errors on the connected UDP socket, and on_dns_packet() handles
them.
2026-09-04 13:53:04 +09:00
Armaan Sandhu 2451b1a9c4 resolve: fix use-after-free of the service browser in mDNS maintenance
mdns_maintenance_query_complete() takes a reference on
query->dnsservice_request and never drops it, so every successful mDNS
maintenance query leaks one reference to the discovered service.

The leak alone would be benign, but the service owns the one-shot event
source that drives its maintenance schedule, and it only borrows a plain
pointer to its service browser. Once the extra reference is outstanding,
dns_service_browser_free() -> dns_remove_service() only drops the list's
reference, so dns_service_free() never runs, the maintenance timer is
never disabled, and the service outlives the browser it points at. The
next time the timer fires, mdns_maintenance_query() dereferences the
freed DnsServiceBrowser to reach the manager's event loop and to issue
the query.

Reaching it only takes a browse subscription whose record lives long
enough for one maintenance query to be answered, followed by the
subscription going away.

Make the reference a _cleanup_ one, the way the sibling
mdns_browse_service_query_complete() below already handles the reference
it takes on the service browser.
2026-09-03 06:37:53 +09:00
Stefan Agner 11e4e35f4c resolved: actually unregister DNS-SD services when the bus client vanishes
Since the D-Bus DNS-SD API was introduced in c3036641f0 ("resolved:
implement D-Bus API for DNS-SD", v236), RegisterService() has set up an
sd_bus_track object so that a registered service is destroyed when the
client that registered it disconnects from the bus — there's even a
handler logging "Client of active request vanished, destroying DNS-SD
service."

However, the track object was never stored anywhere: it is declared as
a _cleanup_(sd_bus_track_unrefp) local in bus_method_register_service()
and hence destroyed again when the method handler returns, taking the
tracking down with it. The vanish handler was thus dead code, and
services registered via D-Bus silently persisted after their client
disconnected — unowned, impossible to re-register (the identifier
stays taken, failing with DNSSD_SERVICE_EXISTS), and advertised until
systemd-resolved itself exited.

Store the track object in DnssdRegisteredService, pinning it for the
lifetime of the service, so that client tracking works as originally
intended. Route the vanish path through the full unregistration logic
(goodbye packets + removal from the per-link mDNS zones), since freeing
the service alone would leave its RRs published in the zones.

Note for fire-and-forget clients (e.g. one-shot busctl/gdbus calls):
these were never reliable — the registration silently vanished on any
resolved restart — and now behave as documented for the corresponding
Avahi API as well: the bus connection must be kept open for as long as
the service is to remain registered. Document that in the man page and
NEWS, and point to *.dnssd files for registrations independent of a
client's lifetime.
2026-09-02 10:40:34 +02:00
Stefan Agner a3a8e82f25 resolved: only send goodbye for the RRs of the service being unregistered
dns_scope_announce(scope, /* goodbye= */ true) is not scoped to a
specific service: it walks the whole per-link mDNS zone and flags every
established RR as goodbye (TTL 0). Unregistering a single DNS-SD
service via the UnregisterService() D-Bus call hence withdrew the
host's own A/AAAA records and every other published service on the
link from remote caches, with nothing re-announcing them afterwards.

Introduce dns_scope_send_goodbye(), which sends an unsolicited response
for a caller-provided RR set, and let the DNS-SD unregistration path
send goodbye only for the RRs of the service actually being removed,
mirroring the flag logic of dns_scope_announce().

The goodbye answer is built per scope, and only includes records
actually established in the respective zone (or under re-verification,
in which case they are still served from it): a per-service goodbye
must take care not to withdraw records that are still being probed for
uniqueness, were withdrawn again after a conflict (in which case the
name belongs to the neighbor), or were never published in the scope in
the first place (e.g. on MulticastDNS=resolve links, which have an mDNS
scope but do not publish DNS-SD services).

This becomes more important with the next commit, which promotes
unregistration from an explicit method call to an automatic consequence
of the registering client disconnecting from the bus.
2026-09-02 10:40:34 +02:00
Stefan Agner 27ea9761ac resolved: split out dnssd_registered_service_unregister() helper
Move the logic that sends goodbye messages, removes a registered DNS-SD
service's RRs from all mDNS zones and frees the service out of
bus_dnssd_method_unregister() into a new helper. While at it, add the
nowadays customary parameter comments and ", ignoring" log message
suffix to the moved code. No functional change.

This is preparation for unregistering services when the D-Bus client
that registered them disconnects: that path needs the full teardown as
well, not just dnssd_registered_service_free(), which would leave the
service's RRs published in the per-link mDNS zones.
2026-09-01 14:29:04 +02:00
Zbigniew Jędrzejewski-Szmek efd63ddfbf shared/verbs: turn verb argument specification into nulstr
Like the command-level CommandDescription.argspec, a verb's argspec is now
a nulstr, so that a verb may declare multiple alternative usage lines. The
verbs help table renders one synopsis line per nulstr entry in the
synopsis cell.

All verb argument specifications must gain an explicit NUL suffix. This
is a lot of churn, but I think this needs to be done. For now, there'll
be only one user of the new functionality (i.e. a verb with more than
one argspec), but I think we'll eventually want to use this in other
places too.
2026-08-28 09:29:04 +02:00
felix h a11144d0a9 tree-wide: format man page references as foo(1) 2026-08-28 06:34:45 +09:00
Christian Glombek 9bb06d5cd6 resolved: only clamp cache TTLs when actually serving stale data
dns_cache_lookup() computes each answer item's expiry ('until', also the
basis of the SD_RESOLVED_CLAMP_TTL TTL patch) as "30s from now" whenever
the caller did not pass SD_RESOLVED_NO_STALE — keyed on the lookup mode,
not on whether the entry being returned is actually stale. That misfires
in both directions:

 * A fresh entry hit by a serve-stale retry (second transaction attempt
   with StaleRetentionSec= set) has its TTL reported as 30s instead of
   its real remaining TTL.

 * A lookup that passes neither SD_RESOLVED_NO_STALE nor
   SD_RESOLVED_CLAMP_TTL — as the mDNS browse path does for clients
   that set no flags — leaves 'current' at 0, so every answer item's
   expiry becomes the constant absolute time 30s after boot,
   permanently in the past. The RFC 6762 section 5.2 cache maintenance
   schedule that resolved-dns-browse-services.c derives from
   item->until then degenerates to its already-expired fallback.

Key the rewrite on the entry's actual staleness instead: an entry whose
TTL has lapsed keeps reporting "30s from now", so clients don't hold on
to stale data for long, and a fresh entry reports its real expiry. For
lookups with SD_RESOLVED_NO_STALE set nothing changes: a stale entry
already makes the whole chain miss, so every item returned in that mode
is fresh.
2026-08-26 18:43:26 +01:00
Zbigniew Jędrzejewski-Szmek 34d4d50d08 Another batch of conversions to COMMAND and additions of --introspect-cli (#43512) 2026-08-26 15:22:50 +02:00
Zbigniew Jędrzejewski-Szmek 65510d4cba services: convert to VERB_COMMAND and add --introspect-cli
This converts systemd-homed, -hostnamed, -importd, -localed, -logind,
-machined, -networkd, -portebled, -resolved, -sysupdated, -timedated,
and -timesyncd.

The service name and description move from service_parse_argv() arguments
into a COMMAND() description that each daemon now defines. The
service_parse_argv() macro captures the calling binary's verb section,
where that COMMAND is found, while the options are still defined and
parsed in libshared, in the new "service" option namespace that each
COMMAND references. (The named namespace makes sure that options defined
by other translation units in libshared can never leak into the
services' parsers.)

The supported features are declared via option group filtering: each
COMMAND lists "Options" plus "Bus introspection" and/or "Runtime scope".
Unsupported options are now rejected by the parser as unknown options
(and are not shown in --help), replacing the runtime "This service does
not support …" errors.

The help output changes slightly: the "Bus introspection" and "Runtime
scope" option groups now get their own headings, and some descriptions
were reworded to the imperative form used elsewhere.
2026-08-26 12:15:25 +02:00
Zbigniew Jędrzejewski-Szmek 3694241e3f shared/verbs: add helper to call command_print_help_full with name only
I didn't do this previously because I thought that there wouldn't
be too many callers. But it turns out that there're quite a few.
2026-08-26 12:14:50 +02:00
Zbigniew Jędrzejewski-Szmek 68e1f67b36 Another batch of conversions to COMMAND structures and additions of --introspect-cli (#43503)
This time, various commands with non-trivial requirements are handled.
So the series is shorter, but has a pattern of feature additions and
then those new features being used. The way that --help output is
formatted is changed a bit in various places. Those changes are
described in individual commits.

22 'static int help()' functions left after this.
2026-08-25 11:04:22 +02:00
Nick Rosbrook cf31d92de3 resolvectl: implement log-level with varlink methods 2026-08-24 13:32:42 -04:00
Nick Rosbrook 0420097525 varlink: introduce io.systemd.service.GetLogLevel
We already have io.systemd.service.SetLogLevel. Add a getter so that
CLI tools with `log-level` subcommands can be more easily implemented
with varlink.

Bind this method to all services that already bind io.systemd.service.SetLogLevel.
2026-08-24 11:54:44 -04:00
Christian Glombek f9185464bb resolved: never serve stale cache data on link-local scopes
Serving stale cache data on a retry is a resilience feature for unicast
DNS: when the configured server does not respond, an expired answer beats
no answer. The gate in dns_transaction_go() that switches the cache lookup
into serve-stale mode from the second attempt on only checks
StaleRetentionSec=, not the scope's protocol, so it applies to mDNS and
LLMNR scopes as well. There it is wrong twice over:

 * With StaleRetentionSec= set, a query for a peer that has left the
   network misses the cache on the first attempt (the record's TTL has
   expired), gets no reply, and is then answered from the expired but
   retained record on the retry — for as long as the retention window
   lasts. On the link-local protocols a retry means that no peer
   answered, not that some server is unreachable, and RFC 6762 treats
   TTL expiry as a presence signal: a host whose records lapse is gone.

 * Independently of whether anything stale is cached, a cache hit made
   in serve-stale mode is reported with its TTL clamped to 30s (see
   dns_cache_lookup()) and counted in the served-stale statistics. mDNS
   transactions are retried at 1s, 2s, 4s, ... whenever nobody answers,
   so a record announced or learnt from another transaction in between
   is handed to the client with a 30s TTL instead of its real remaining
   TTL, and logged as a "Serve Stale response" although it never was
   stale.

Hence only enter serve-stale mode on unicast DNS scopes. This is the
lookup-side counterpart of the cache-side change that stops link-local
records from being retained past their TTL at all.
2026-08-24 23:06:26 +09:00
Zbigniew Jędrzejewski-Szmek 4cf46d47a2 shared/verbs: allow commands to specify footer highlighting
In practice I think this will ever only be used with ansi_highlight_red(),
but it's cleaner to add this generic extension rather than e.g. a boolean
highlight_footer_red.

The options is passed through via command_print_help_full(), not through
the COMMAND structure, which means that commands with 'help' verb are not
covered by this. The red footer highligting is used for legacy commands,
which don't have verbs, so I think this is OK. Making it generic and
covering 'help' verbs would require more refactoring, and I think it
doesn't make sense to add it until it's actually needed.
2026-08-24 13:46:21 +02:00
Zbigniew Jędrzejewski-Szmek 1cf40c9c72 shared/verbs: stop requiring an explicit program name
Almost all programs are simple and have only a single command. When
printing options, let's just use the first COMMAND block found by default.
This means we can drop the explicit program-name argument and reduce
copy&paste boilerplate.
2026-08-24 13:45:07 +02:00
Christian Glombek 15458a89f1 resolved: only apply stale retention to unicast DNS cache entries
StaleRetentionSec= is a resilience feature for unicast DNS: when the
configured servers are unreachable, answering from an expired cache
entry beats answering nothing at all. That rationale does not carry
over to the link-local protocols, where there is no configured server
that could be unreachable -- peers simply come and go, and a record's
TTL is the only thing bounding how long a departed peer is still
answered for.

For mDNS the mismatch goes further. RFC 6762 treats record expiry as a
presence signal -- a host or service whose records lapse is to be
considered gone (section 10.1 goodbye packets even force this early by
publishing TTL=0), and the section 5.2 cache maintenance scheme exists
precisely so that expiry happens on time. Retaining expired mDNS
records for StaleRetentionSec= contradicts that: vanished peers linger
in the cache for the whole retention window, are served to clients as
if still present, and DNS-SD browsers do not see their services
disappear when the records' TTL lapses.

Beyond lingering entries, retention also swallows goodbyes outright, as
noted in review of the goodbye re-arm fix [1]: dns_cache_prune() and
dns_cache_expiry_in_one_second() key on the retention-extended "until",
so with StaleRetentionSec= set a goodbye's TTL=1 rewrite lands a full
retention window out -- the one-second prune of RFC 6762 section 10.1
then drops nothing, nothing re-arms, and the withdrawn service stays
listed for the whole window.

LLMNR has no goodbye mechanism, so only the lingering half applies to
it, but it does apply: dns_transaction_cache_answer() passes the
retention through for LLMNR replies just like for unicast DNS, and the
stale-serving gate in the transaction code does not look at the
protocol, so a departed LLMNR peer keeps being answered from cache for
the whole window as well.

Zero the retention centrally in dns_cache_put() for everything but
unicast DNS, which covers both put paths: transaction replies and the
mDNS packets cached directly by on_mdns_packet(). Add tests pinning
both directions, including the goodbye rewrite of an existing mDNS
entry.

[1] https://github.com/systemd/systemd/pull/43259#discussion_r3809488076
2026-08-23 17:45:51 +01:00
Nick Rosbrook 3a09ba33e2 resolvectl: use io.systemd.Resolve.Monitor.ResetServerFeatures
Use the new varlink method instead of dbus.
2026-08-21 11:54:32 -04:00
Nick Rosbrook 6c248546df resolve: introduce io.systemd.Resolve.Monitor.ResetServerFeatures
Add varlink version of ResetServerFeatures.

Add this to io.systemd.Resolve.Monitor, since that interface already
handles many manager related things.
2026-08-21 11:54:32 -04:00
Nick Rosbrook 85dd7a2170 resolvectl: use io.systemd.Resolve.Monitor.FlushCaches
Use the new varlink method instead of dbus.
2026-08-21 11:54:32 -04:00
Nick Rosbrook 881ab94dd0 resolve: introduce io.systemd.Resolve.Monitor.FlushCaches
Add varlink version of FlushCaches.

Add this to io.systemd.Resolve.Monitor, since that interface already
handles many cache and manager related things.
2026-08-21 10:27:03 -04:00
Yu Watanabe 515121f7b3 resolve: refuse multiple requests
Otherwise, that triggers memory leak and double free.
2026-08-19 03:45:11 +09:00
dongshengyuan 3e55065ccf resolvectl: merge JSON error fields
Follow-up for: 32b3aabfe7
2026-08-19 02:31:39 +09:00
Yu Watanabe 90481c0b1b tree-wide: add missing assertions
Found by coccinelle.
2026-08-18 06:30:10 +09:00
Luca Boccassi b98d348c37 resolved: drop dead assignment
The 'changed' variable is either unused on error or unconditionally
overwritten on success, so coverity warns about it. Drop the unused
assignment.

CID#1685266

Follow-up for a572dd2f60
2026-08-16 22:53:23 +09:00
dongshengyuan 9711c84c2d resolved: match browsed mDNS services by ifindex
BrowseServices(ifindex=0) may observe the same service on multiple
links. Treat the link index as part of the discovered service
identity so add and remove events remain link-scoped.

Use the per-service ifindex for RFC 6762 maintenance queries.
Share the service identity predicate between add and remove handling,
and propagate RR comparison errors instead of reporting removals.

Extend coverage for scoped-browser ifindex fallback, goodbye TTL
handling, and monotonic expiry updates.
2026-08-16 03:07:32 +09:00
Zbigniew Jędrzejewski-Szmek 8975c5c1cf Add a "declarative program header" and introspection of options and verbs (#43152) 2026-08-14 11:52:42 +02:00
Yu Watanabe 8f4cd7de43 resolvectl: fix query option handling and resolvconf input errors (#43370) 2026-08-14 00:35:58 +09:00
Zbigniew Jędrzejewski-Szmek 94af9a8b40 verbs: wrap abstract too
So far there's only one multi-line abstract. But as pointed out in the
review, it's silly to handle footer but not the abstract.

help_abstract() is not reused because it only works with one-line
abstracts. (It always prints an empty line to separate paragraphs.)
So the formatting is done anew in print_wrapped(). I think this is OK,
since it's all very simple. After everything has been converted to
COMMAND() and its helpers, help_abstract() and the sister functions will
become unused and can be dropped then.
2026-08-13 13:52:35 +02:00
Zbigniew Jędrzejewski-Szmek 7ea090f41b verbs: wrap the help footer at print time
The footer is now wrapped to min($COLUMNS, 80) when it is printed, so
CommandDescription footers do not need to be hard-wrapped in the source,
and make use of the available terminal width. Newlines embedded in the
text are preserved and each line is wrapped independently, so explicit
line breaks can still be used where needed, e.g. before the example
command line in varlinkctl.

The text is wrapped at max of 80 columns to avoid very wrong lines on
wide terminals. Such lines are hard to read.

A new helper is added for this. We already have format_strv_width in
format-table, but it requires a strv, so we'd need to conver the data,
and returns a formatted string, which we'd need to print ourselves. So
overall, it seems better to add a dedicated helper.
2026-08-13 13:48:24 +02:00
Zbigniew Jędrzejewski-Szmek 7670c20c6e verbs: add PagerFlags pointer to all CommandDescription's
Before this series, many help() functions opened the pager. This got lost
in the translation. Starting the pager requires access to arg_pager_flags.
While we could pipe this through in case when command_print_help() is
called directly, but this hard in the case when verb_help_auto() or
VERB_COMMON_HELP_AUTO/VERB_COMMON_HELP_AUTO_HIDDEN are used. Let's instead
extend the (const static) CommandDescription structure with a pointer to
the arg_pager_flags variable. This restores the pager functionality in a
uniform fashion with a one-line change in the individual binaries.
2026-08-13 13:46:08 +02:00
Zbigniew Jędrzejewski-Szmek 8a14181f55 resolvectl: separate systemd-resolve in introspection data
Previously, the systemd-resolve compat alias was described in the same
CommandDescription structure and resolvectl. But they have different
option sets, so this doesn't really work. The alias is deprecated, so
describe it as such in the abstract, but give it a separate entry.

$ build/resolvectl --introspect-cli
{
  "mediaType": "application/vnd.io.systemd.cli-introspection-0",
  "commands": [
    {
      "names": [ "resolvconf" ],
      "abstract": [
        "Register DNS server and domain configuration with systemd-resolved."
      ],
      ...
    },
    {
      "names": [ "resolvectl" ],
      "abstract": [
        "Send control commands to the network name resolution manager, or\nresolve domain names, IPv4 and IPv6 addresses, DNS records, and services."
      ],
      ...
    },
    {
      "names": [ "systemd-resolve" ],
      "abstract": [
        "Send control commands to the network name resolution manager, or\nresolve domain names, IPv4 and IPv6 addresses, DNS records, and services.\n\nThis command is deprecated. Use resolvectl.1 instead."
      ],
      ...
    }
  ]
}
2026-08-13 13:23:05 +02:00
Zbigniew Jędrzejewski-Szmek 1a54ce154c various: add --introspect-cli 2026-08-13 13:23:05 +02:00
Zbigniew Jędrzejewski-Szmek df03655bc5 resolvectl: convert to VERB_COMMAND 2026-08-13 13:23:05 +02:00
Zbigniew Jędrzejewski-Szmek 580489eeec resolvectl: drop --help for systemd-resolve
The rename was done in v239. We generally don't document obsolete
interfaces, so let's just drop the (very nice) --help that we have here.
2026-08-13 13:23:05 +02:00
dongshengyuan 253b34d8ac resolvconf: reject malformed stdin without changing link state
The resolvconf compatibility parser ignored errors from
nameserver, domain, and search lines. If one of those lines was
malformed, resolvectl could still send the partially parsed
configuration to resolved and overwrite the previous link state.

Reproducer:

  IF=enp1s0
  TMP="$(mktemp -d)"

  ln -s "$(command -v resolvectl)" "$TMP/resolvconf"

  sudo resolvectl dns "$IF" 1.1.1.1
  sudo resolvectl domain "$IF" keep.example
  resolvectl domain "$IF"

  printf 'nameserver 1.1.1.1\nsearch "unterminated\n' |
    sudo "$TMP/resolvconf" -a "$IF" || true

  resolvectl domain "$IF"

Before:

  The malformed search line was ignored and the previous domain
  was cleared:

    Link 2 (enp1s0): keep.example
    Link 2 (enp1s0):

After:

  The malformed input fails before updating resolved, so
  keep.example remains configured.

Follow-up for: 097f130731
2026-08-13 17:40:50 +08:00