implement warn on dnssd name collisions

This commit is contained in:
juergen committed 2026-10-20 11:11:45 +02:00
1 parent 3910ab974c
commit f15ea07f44
3 files changed
+159 -16

No files matched your search

+67 -5
View File
@@ -82,7 +82,7 @@ podman run -d --name printserver \
| `PRINTER_IP` | *(empty)* | IP address / host name of the network printer. A device URI is derived from it (see `PRINTER_PROTOCOL`). |
| `PRINTER_URI` | *(empty)* | Full device URI. Overrides `PRINTER_IP` when set. |
| `PRINTER_PROTOCOL` | `socket` | Protocol used with `PRINTER_IP`: `socket`, `lpd` or `ipp`. |
| `PRINTER_NAME` | `HL4150CDN` | CUPS queue name. Also used as the name advertised via AirPrint/DNS-SD. |
| `PRINTER_NAME` | `HL4150CDN` | CUPS queue name. Also the base of the advertised AirPrint/DNS-SD name (`<name> @ <host>`) — while no Description is set; `PRINTER_DESCRIPTION` overrides the advertised name. |
| `PRINTER_DESCRIPTION` | *(empty)* | CUPS printer description (`lpadmin -D`), shown in the CUPS web UI. |
| `PPD_FILE` | `/usr/share/cups/model/BR4150_2.PPD` | PPD used for the queue. |
| `PRINTER_OPTIONS` | *(empty)* | Space-separated PPD option assignments (`key=value`) applied as queue defaults on every start, e.g. `BRColorMode=Vivid TonerSaveMode=False`. Discover the names with `lpoptions -p <queue> -l`. |
@@ -130,14 +130,21 @@ Or specify the URI explicitly for full control:
### Advertised name
`PRINTER_NAME` is used as the CUPS queue name and as the name clients see in
AirPrint discovery. To show a descriptive label in the CUPS web interface,
set `PRINTER_DESCRIPTION` (this does not change the advertised name):
`PRINTER_NAME` is the CUPS queue name. It is also the base of the name clients
see in AirPrint discovery — CUPS advertises `"<name> @ <host>"` — but only as
long as no Description is set: `PRINTER_DESCRIPTION` (the `lpadmin -D`
Description) has precedence and becomes the advertised name instead:
```sh
-e PRINTER_NAME=HL4150CDN -e PRINTER_DESCRIPTION="Brother HL-4150CDN (Office)"
```
advertises the DNS-SD instance `Brother HL-4150CDN (Office) @ <host>`, not
`HL4150CDN @ <host>`. That instance name is what clients pin into their
`dnssd://` device URI, so every queue needs a **unique** Description: two
queues deriving the same name collide, and only one of them is ever advertised
(see *macOS shows the printer offline after some time* under Troubleshooting).
### TLS certificate for `_ipps`
CUPS advertises the queue twice over mDNS: `_ipp._tcp` (plain) and `_ipps._tcp`
@@ -433,7 +440,7 @@ podman cp printserver:/etc/cups/ssl/server.crt . &&
```
A way around either cause is to add the queue from the command line, driverless
over the same secure service (`ipptool -tv ipp://<host>:631/printers/<queue>`
over the same secure service (`ipptool -tv ipp://<host>:631/printers/<queue> <file-to-print>`
prints the UUID; drop the `urn:uuid:` prefix):
```sh
@@ -482,6 +489,61 @@ greyscale. Forcing Ghostscript with `-o pdftops-renderer=gs` would, but that
path is not recommended for Brother printers (bugs in their PostScript
interpreters).
### macOS shows the printer offline after some time
Symptom: the printer was added and printed fine, then macOS reports it
*offline* ("printer is not responding") some hours later — typically after the
container was restarted or after queues were edited on the server. Restarting
the container does not help; removing the printer on the Mac and adding it
again fixes it — until it happens again.
Cause: when macOS adds a Bonjour printer it pins the exact DNS-SD **instance
name** into the queue's device URI:
```text
dnssd://Laserdrucker%20HL-4150CDN%20%40%20podp._ipps._tcp.local./?uuid=…
└── "<Description> @ <host>" — must still resolve hours later
```
CUPS derives that name from the **Description**, not from the CUPS queue name
(the queue name is only the base while no Description is set). Two queues with
the same Description therefore derive the same instance name. Avahi can
register only one service per name, and CUPS 2.4 does not rename on collision
(upstream `scheduler/dnssd.c` still carries a `TODO` for it), so the losing
queue stays *Shared* but is never advertised. The Mac keeps resolving the name
that no longer exists and flips the queue to offline. The lost race is
deterministic — the same queue wins after every restart — which is why
restarting the container never helps, while remove/re-add re-pins the Mac to
the instance that is actually advertised.
The entrypoint logs a warning at startup when it finds such a collision. To
check by hand:
```sh
podman logs printserver 2>&1 | grep 'DNS-SD name' # entrypoint warning, if any
lpstat -v | grep dnssd # the instance name the Mac pinned
podman exec printserver lpstat -l -p # duplicate Descriptions?
dns-sd -B _ipp._tcp # one instance per name?
podman exec printserver avahi-browse -rt _ipps._tcp # what is really advertised
ipptool -tv ipp://<host>:631/printers/<queue> \
/usr/share/cups/ipptool/get-printer-attributes.test # server side itself
```
Two queues showing the same `Description:` while `dns-sd -B` lists only one
instance is the collision. Fix it on the server: give each queue a unique
Description, or remove the redundant queue:
```sh
lpadmin -h <host>:631 -p laserdrucker-im-buero -D "Laserdrucker Buero"
# or drop the duplicate entirely:
lpadmin -h <host>:631 -x laserdrucker-im-buero
```
Then remove the printer on the Mac and add it again — macOS re-pins the device
URI to the instance that is advertised now. The same remove/re-add is needed
after **every** Description change: the pinned name changes with it, and a
container restart alone cannot bring the old name back.
## CUPS 3 and the future of PPDs
Adding the queue with a PPD makes `lpadmin` print:
+83 -2
View File
@@ -15,8 +15,11 @@
# PRINTER_IP IP address / host name of the network printer.
# PRINTER_URI Full device URI; overrides PRINTER_IP when set.
# PRINTER_PROTOCOL socket (default), lpd or ipp - used with PRINTER_IP.
# PRINTER_NAME CUPS queue name and advertised (DNS-SD) name.
# PRINTER_DESCRIPTION CUPS Description (lpadmin -D) shown in the web UI.
# PRINTER_NAME CUPS queue name; base of the advertised (DNS-SD)
# name "<name> @ <host>" while no Description is set.
# PRINTER_DESCRIPTION CUPS Description (lpadmin -D), shown in the web UI;
# takes precedence over PRINTER_NAME as the advertised
# (DNS-SD) name.
# PPD_FILE PPD used for the queue.
# PRINTER_OPTIONS Space-separated PPD option assignments (key=value)
# applied as queue defaults on every start, e.g.
@@ -400,6 +403,84 @@ if ! curl -fsS --noproxy '*' --max-time 2 -o /dev/null http://127.0.0.1:631/ 2>/
exit 1
fi
# --- DNS-SD name collision check --------------------------------------------
# CUPS advertises every shared queue as the DNS-SD instance
# "<Description> @ <host>" - the queue name when no Description is set.
# Avahi can register only one instance per name, and CUPS 2.4 does not rename
# on collision (scheduler/dnssd.c: "TODO: Handle collisions with
# avahi_alternate_service_name"), so with duplicate derived names one queue is
# silently never advertised. Clients that pinned the missing name - macOS
# stores the exact instance name in the queue's dnssd:// device URI - then
# report the printer offline. Detection only; a collision is never fatal
# (see README, "macOS shows the printer offline after some time").
check_dnssd_name_collisions() {
# Queue names and Descriptions straight from cupsd, forced to the C locale
# so the "Description:" detail line parses regardless of the container
# locale. An empty or failed result skips the check silently.
lpstat_out="$(LC_ALL=C lpstat -l -p 2>/dev/null || true)"
[ -n "${lpstat_out}" ] || return 0
# One "base<TAB>queue1, queue2" line per colliding name. The Shared flags
# come from printers.conf (both <Printer> and <DefaultPrinter> blocks,
# "Shared Yes/No"); a queue that cannot be read there (file not flushed
# after this run's lpadmin, no Shared line yet) counts as shared: a false
# warning is actionable, a missed one reproduces the incident. Every parse
# failure degrades to "no collision found" so that set -eu never trips
# inside this diagnostic.
collisions="$(printf '%s\n' "${lpstat_out}" | awk -v conf=/etc/cups/printers.conf '
BEGIN {
while ((getline line < conf) > 0) {
if (line ~ /^<(Default)?Printer /) {
cur = line
sub(/^<(Default)?Printer /, "", cur)
sub(/>$/, "", cur)
} else if (line ~ /^<\/(Default)?Printer>$/) {
cur = ""
} else if (cur != "" && line ~ /^Shared /) {
flag[cur] = tolower(substr(line, 8))
}
}
close(conf)
}
/^[ \t]*printer / {
curq = $2
order[++n] = curq
next
}
/^[ \t]*Description: / {
sub(/^[ \t]*Description: /, "")
if (curq != "") desc[curq] = $0
}
END {
for (i = 1; i <= n; i++) {
q = order[i]
if ((q in flag) && flag[q] == "no") continue
base = ((q in desc) && desc[q] != "") ? desc[q] : q
count[base]++
list[base] = list[base] (list[base] == "" ? "" : ", ") q
}
for (b in count)
if (count[b] > 1) print b "\t" list[b]
}' || true)"
[ -n "${collisions}" ] || return 0
dnssd_host=" @ $(hostname -s 2>/dev/null || hostname 2>/dev/null || printf host)"
printf "%s\n" "${collisions}" \
| while IFS="$(printf '\t')" read -r base queues; do
log "warning: queues ${queues} would all be advertised as the DNS-SD name"
log " '${base}${dnssd_host}' - Avahi keeps only one of them, so every"
log " other queue is silently NOT advertised, and clients pinned to the"
log " missing name (macOS dnssd:// device URI) show the printer offline."
log " fix: give each queue a unique Description (lpadmin -p <queue> -D \"...\")"
log " or remove the redundant queue (lpadmin -x <queue>); then remove"
log " and re-add the printer on the client - see README, \"macOS shows"
log " the printer offline after some time\"."
done
return 0
}
check_dnssd_name_collisions
log "ready: CUPS on 631/tcp, mDNS on 5353/udp"
# Keep the container running by waiting on cupsd.
@@ -2,7 +2,7 @@
## 1. entrypoint: Erkennung von DNS-SD-Namenskollisionen
- [ ] 1.1 `entrypoint.sh`: Funktion `check_dnssd_name_collisions` ergänzen,
- [x] 1.1 `entrypoint.sh`: Funktion `check_dnssd_name_collisions` ergänzen,
aufgerufen nach dem TCP-631-Check und vor
`log "ready: CUPS on 631/tcp, mDNS on 5353/udp"`. Queue-Namen und
Descriptions via `LC_ALL=C lpstat -l -p` lesen (Locale-fest, live aus
@@ -13,7 +13,7 @@
Verifikation: `sh -n entrypoint.sh` läuft fehlerfrei,
`grep -n 'check_dnssd_name_collisions' entrypoint.sh` zeigt Definition
und Aufruf an der vorgesehenen Stelle.
- [ ] 1.2 Bei gleichen abgeleiteten Namen pro Kollisionsgruppe über den
- [x] 1.2 Bei gleichen abgeleiteten Namen pro Kollisionsgruppe über den
bestehenden `log()`-Stil warnen (Mehrzeiler, analog zum TCP-631-Block):
kollidierender Anzeigename `<basis> @ <host>`, betroffene Queue-Namen,
Abhilfe (einzigartige Description je Queue per `lpadmin -D` oder
@@ -23,7 +23,7 @@
Verifikation: `grep -n 'warning:' entrypoint.sh` findet die neuen
Warnzeilen inkl. `lpadmin -D`-Remedies; das Verhalten selbst prüfen die
Aufgaben 3.2 (Kollision) und 3.3 (keine Kollision).
- [ ] 1.3 Alle Parseschritte abfangen, ohne dass `set -eu` den Start abbricht
- [x] 1.3 Alle Parseschritte abfangen, ohne dass `set -eu` den Start abbricht
(`|| true` / geblockte Substitutionen): fehlendes oder unlesbares
`printers.conf`,
fehlerhaftes `lpstat` → Prüfung still überspringen oder als geteilt
@@ -33,7 +33,7 @@
## 2. README: Troubleshooting und PRINTER_NAME-Korrektur
- [ ] 2.1 `README.md`: neuen Troubleshooting-Abschnitt
- [x] 2.1 `README.md`: neuen Troubleshooting-Abschnitt
„macOS shows the printer offline after some time" (englisch, Stil der
bestehenden `###`-Abschnitte) einfügen: Symptom (Queue existiert,
Status offline, erst nach Stunden/Wechseln sichtbar), Ursache
@@ -46,7 +46,7 @@
Hinweis, warum Restart allein nicht hilft: Kollision ist deterministisch).
Verifikation: jeder im Abschnitt stehende Befehl läuft unverändert gegen
den laufenden Container (lokal aus 3.1 bzw. gegen podp).
- [ ] 2.2 `PRINTER_NAME`-Wording korrigieren: README-Tabelle („Also used as
- [x] 2.2 `PRINTER_NAME`-Wording korrigieren: README-Tabelle („Also used as
the name advertised via AirPrint/DNS-SD") und Fließtext unter
*Environment variables* sowie die Kopfkommentarzeile in
`entrypoint.sh` („advertised (DNS-SD) name") präzisieren: Der
@@ -57,23 +57,23 @@
## 3. Integrationstests (podman, lokal)
- [ ] 3.1 Image bauen und ohne Kollision starten.
- [x] 3.1 Image bauen und ohne Kollision starten.
Verifikation: `podman build -t printserver:latest .` endet ohne Fehler;
`podman run …` erreicht „ready: CUPS on 631/tcp, mDNS on 5353/udp";
`podman logs` enthält KEINE Kollisions-Warnung (falls 631 nicht bindet:
`podman machine ssh -- sudo sysctl -w net.ipv4.ip_unprivileged_port_start=631`
wie bei der Vorgängeränderung).
- [ ] 3.2 Kollision erzeugen und Warnung nachweisen.
- [x] 3.2 Kollision erzeugen und Warnung nachweisen.
Verifikation: zweite Queue mit der Description des ersten Queues anlegen
(`podman exec printserver lpadmin -p dup -E -v socket://192.0.2.1:9100
-D "<identische Description>"`), danach `podman restart printserver`:
`podman logs` enthält die Warnung mit Anzeigename, beiden Queue-Namen und
Remedie, und der Start endet trotzdem mit „ready" (Warnung nicht fatal).
- [ ] 3.3 Kollision beheben → Warnung verschwindet.
- [x] 3.3 Kollision beheben → Warnung verschwindet.
Verifikation: `podman exec printserver lpadmin -p dup -D "Dup Queue"`
(eindeutige Description), `podman restart printserver`: das Log enthält
keine Kollisions-Warnung mehr und endet mit „ready".
- [ ] 3.4 Fehlendes `printers.conf` darf den Start nicht brechen.
- [x] 3.4 Fehlendes `printers.conf` darf den Start nicht brechen.
Verifikation: `podman exec printserver sh -c 'cp /etc/cups/printers.conf /tmp/pc.bak && rm /etc/cups/printers.conf'`
danach `podman restart printserver`: der Container erreicht „ready";
anschließend Status wiederherstellen