implement warn on dnssd name collisions
This commit is contained in:
1 parent
3910ab974c
commit
f15ea07f44
3 files changed
+159
-16
No files matched your search
@@ -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
@@ -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
|
||||
|
||||
Reference in new issue
Block a user