feat: publisher-daemon (singleton + re-announce), sync-hosts, status/stop y tests
Bloque de robustez operativa + integracion de sistema + calidad:
Publisher como servicio (lib/mdns_tools/service.py):
- `mdns publish --daemon`: daemon singleton con lock PID en
~/.cache/mdns/publisher.pid. Un segundo intento es rechazado -> evita el
problema de "Termux SMB" duplicado (varios publishers compitiendo por el
mismo nombre, que zeroconf renombra a "-2").
- Re-anuncio automatico al cambiar la IP local (WiFi<->datos, roaming):
unregister + register con la nueva IP para que el registro no quede stale.
- Apagado limpio: maneja SIGTERM ademas de SIGINT (loop en ticks de 1s) y
envia goodbye packets para que los clientes borren la entrada al instante.
- `mdns status` (running/host/ip/servicios/desde) y `mdns stop`.
Integracion de sistema (lib/mdns_tools/hosts.py):
- `mdns sync-hosts [--dry-run]`: escribe un bloque gestionado en
$PREFIX/etc/hosts (o /etc/hosts) para que ping/curl/kubectl nativos
resuelvan .local sin el wrapper `resolve`. Solo toca su propio bloque.
Refactor publish.py: expone helpers reutilizables (local_ipv4, load_config,
build_infos, register_all, unregister_all) que consume el daemon.
Tests offline (tests/, pytest, 20 casos, sin red):
- construccion/parseo de queries A, seleccion de IP ante multi-homing
(rechazo de rangos virtuales), cache con TTL y familias, PTR reverse,
render/strip del bloque de hosts.
- pyproject.toml con metadata, deps, extras [test] y config de pytest.
README: seccion de daemon, sync-hosts y tests; tabla RFC ampliada.
bashrc.snippet: aliases mdns-up/status/down/hosts (consolidados).
install.sh: fix de backticks; symlinks ya cubren mdns + lib via resolve().
Nota: publish.py (109) y service.py (110) exceden levemente las 100 lineas;
se dejan como modulos cohesivos de responsabilidad unica, igual que el
dispatcher CLI, para no fragmentar.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-20 17:59:10 +00:00
|
|
|
"""Publish on the LAN: services (JSON config) or a bare hostname->IP.
|
|
|
|
|
Public helpers reused by service.py: local_ipv4, load_config, build_infos,
|
|
|
|
|
register_all, unregister_all."""
|
feat: monitor en vivo (--watch) y publish --address; interop validada
Añade las dos funciones que Avahi/Bonjour tenían y faltaban:
- `mdns browse --watch`: monitor DNS-SD en vivo (eventos +/=/-), equivalente
a `avahi-browse -a`. Sin --type descubre tipos dinámicamente via el
meta-servicio _services._dns-sd._udp.local. y adjunta un browser por tipo.
Nuevo modulo lib/mdns_tools/watch.py.
- `mdns publish --address <nombre> <ip>`: publica un A record puro
hostname->IP, equivalente a `avahi-publish -a`.
Robustez del publisher:
- Maneja NonUniqueNameException (probing RFC 6762): si el nombre ya esta
tomado, deja que zeroconf renombre en vez de crashear.
- `mdns publish` sin --config auto-carga ~/.config/mdns/services.json.
Interoperabilidad validada en LAN real (documentada en README):
- Termux -> Avahi: con `mdns publish` activo, un Linux con nss-mdns resolvio
`movil.local` y le hizo ping sin configuracion extra (getent + ping OK).
- Avahi -> Termux: `mdns browse` ve los servicios que publican equipos
Avahi/Bonjour (SMB, SSH, _workstation, Chromecast, AirPlay...).
Al usar zeroconf (RFC 6762/6763), Mac/Linux/Windows descubren y resuelven
lo publicado y viceversa, sin cliente especial.
README: seccion de interoperabilidad, tabla RFC ampliada, ejemplos --watch
y --address. bashrc.snippet: alias mdns-watch. publish.py recortado a <100.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-20 13:00:07 +00:00
|
|
|
import json
|
feat: CLI unificado mdns con browse/reverse/IPv6/cache-TTL
Cierra el gap hacia una implementacion mDNS/DNS-SD completa (RFC 6762/6763).
CLI nuevo `bin/mdns` con subcomandos:
- resolve : hostname -> IP (IPv4 stdlib rapido, IPv6 via zeroconf), cache TTL.
- reverse : IP -> hostname (PTR multicast, IPv4/IPv6).
- browse : descubrimiento DNS-SD (--list-types o --type _ssh._tcp.local.).
- publish : anuncia hostname + servicios (SSH/SMB/HTTP) con probing y goodbye.
- cache : list/clear del cache con TTL.
Biblioteca lib/mdns_tools/ (cada modulo < 100 lineas):
- resolve.py: stdlib-first para IPv4, zeroconf fallback para IPv6/miss.
- _stdlib_resolve.py: resolver A puro stdlib, acumula todas las A del host.
- _pick.py: elige la IP mas alcanzable ante multi-homing. Descarta rangos
virtuales (libvirt 192.168.122/24, docker 172.17/16, link-local) devolviendo
None para que el caller caiga a Tailscale en vez de a una IP muerta.
- browse.py / publish.py: DNS-SD sobre zeroconf.
- reverse.py: PTR stdlib. cache.py: TTL-aware JSON en ~/.cache/mdns.
Mejoras de calidad:
- IP_ADD_MEMBERSHIP en todos los sockets multicast.
- Retries con backoff (timeout dividido).
- Imports lazy en el CLI (resolve/cache no cargan zeroconf -> ~1s vs ~4s).
Wrappers:
- resolve (bash): orden mdns -> mdns-resolve stdlib -> Tailscale -> cache plano.
- mdns-publish.py: shim compat sobre `mdns publish`, lee ~/.config/mdns/services.json.
- install.sh: symlinks bin/ (updates via git pull), siembra services.json.
- ssh_config.example: HostKeyAlias documentado (alias comparte known_hosts con
el nombre canonico; evita fallos de verificacion tras reinstalar un host).
- bashrc.snippet: aliases mdns-types/ssh/smb/http, mdns-cache, whor.
Validado E2E en LAN: browse detecta 13 tipos; dell/movil resuelven a IP LAN
real; lenovo (solo publica virbr0 192.168.122.1) cae correctamente a Tailscale;
IPv6 de dell via zeroconf; reverse 192.168.1.17 -> dell-latitude3400.local;
publish visible desde otro browser. Modulos < 100 lineas salvo el dispatcher.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-20 12:06:53 +00:00
|
|
|
import socket
|
|
|
|
|
import time
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
feat: monitor en vivo (--watch) y publish --address; interop validada
Añade las dos funciones que Avahi/Bonjour tenían y faltaban:
- `mdns browse --watch`: monitor DNS-SD en vivo (eventos +/=/-), equivalente
a `avahi-browse -a`. Sin --type descubre tipos dinámicamente via el
meta-servicio _services._dns-sd._udp.local. y adjunta un browser por tipo.
Nuevo modulo lib/mdns_tools/watch.py.
- `mdns publish --address <nombre> <ip>`: publica un A record puro
hostname->IP, equivalente a `avahi-publish -a`.
Robustez del publisher:
- Maneja NonUniqueNameException (probing RFC 6762): si el nombre ya esta
tomado, deja que zeroconf renombre en vez de crashear.
- `mdns publish` sin --config auto-carga ~/.config/mdns/services.json.
Interoperabilidad validada en LAN real (documentada en README):
- Termux -> Avahi: con `mdns publish` activo, un Linux con nss-mdns resolvio
`movil.local` y le hizo ping sin configuracion extra (getent + ping OK).
- Avahi -> Termux: `mdns browse` ve los servicios que publican equipos
Avahi/Bonjour (SMB, SSH, _workstation, Chromecast, AirPlay...).
Al usar zeroconf (RFC 6762/6763), Mac/Linux/Windows descubren y resuelven
lo publicado y viceversa, sin cliente especial.
README: seccion de interoperabilidad, tabla RFC ampliada, ejemplos --watch
y --address. bashrc.snippet: alias mdns-watch. publish.py recortado a <100.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-20 13:00:07 +00:00
|
|
|
from zeroconf import Zeroconf, ServiceInfo, NonUniqueNameException
|
feat: CLI unificado mdns con browse/reverse/IPv6/cache-TTL
Cierra el gap hacia una implementacion mDNS/DNS-SD completa (RFC 6762/6763).
CLI nuevo `bin/mdns` con subcomandos:
- resolve : hostname -> IP (IPv4 stdlib rapido, IPv6 via zeroconf), cache TTL.
- reverse : IP -> hostname (PTR multicast, IPv4/IPv6).
- browse : descubrimiento DNS-SD (--list-types o --type _ssh._tcp.local.).
- publish : anuncia hostname + servicios (SSH/SMB/HTTP) con probing y goodbye.
- cache : list/clear del cache con TTL.
Biblioteca lib/mdns_tools/ (cada modulo < 100 lineas):
- resolve.py: stdlib-first para IPv4, zeroconf fallback para IPv6/miss.
- _stdlib_resolve.py: resolver A puro stdlib, acumula todas las A del host.
- _pick.py: elige la IP mas alcanzable ante multi-homing. Descarta rangos
virtuales (libvirt 192.168.122/24, docker 172.17/16, link-local) devolviendo
None para que el caller caiga a Tailscale en vez de a una IP muerta.
- browse.py / publish.py: DNS-SD sobre zeroconf.
- reverse.py: PTR stdlib. cache.py: TTL-aware JSON en ~/.cache/mdns.
Mejoras de calidad:
- IP_ADD_MEMBERSHIP en todos los sockets multicast.
- Retries con backoff (timeout dividido).
- Imports lazy en el CLI (resolve/cache no cargan zeroconf -> ~1s vs ~4s).
Wrappers:
- resolve (bash): orden mdns -> mdns-resolve stdlib -> Tailscale -> cache plano.
- mdns-publish.py: shim compat sobre `mdns publish`, lee ~/.config/mdns/services.json.
- install.sh: symlinks bin/ (updates via git pull), siembra services.json.
- ssh_config.example: HostKeyAlias documentado (alias comparte known_hosts con
el nombre canonico; evita fallos de verificacion tras reinstalar un host).
- bashrc.snippet: aliases mdns-types/ssh/smb/http, mdns-cache, whor.
Validado E2E en LAN: browse detecta 13 tipos; dell/movil resuelven a IP LAN
real; lenovo (solo publica virbr0 192.168.122.1) cae correctamente a Tailscale;
IPv6 de dell via zeroconf; reverse 192.168.1.17 -> dell-latitude3400.local;
publish visible desde otro browser. Modulos < 100 lineas salvo el dispatcher.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-20 12:06:53 +00:00
|
|
|
|
feat: publisher-daemon (singleton + re-announce), sync-hosts, status/stop y tests
Bloque de robustez operativa + integracion de sistema + calidad:
Publisher como servicio (lib/mdns_tools/service.py):
- `mdns publish --daemon`: daemon singleton con lock PID en
~/.cache/mdns/publisher.pid. Un segundo intento es rechazado -> evita el
problema de "Termux SMB" duplicado (varios publishers compitiendo por el
mismo nombre, que zeroconf renombra a "-2").
- Re-anuncio automatico al cambiar la IP local (WiFi<->datos, roaming):
unregister + register con la nueva IP para que el registro no quede stale.
- Apagado limpio: maneja SIGTERM ademas de SIGINT (loop en ticks de 1s) y
envia goodbye packets para que los clientes borren la entrada al instante.
- `mdns status` (running/host/ip/servicios/desde) y `mdns stop`.
Integracion de sistema (lib/mdns_tools/hosts.py):
- `mdns sync-hosts [--dry-run]`: escribe un bloque gestionado en
$PREFIX/etc/hosts (o /etc/hosts) para que ping/curl/kubectl nativos
resuelvan .local sin el wrapper `resolve`. Solo toca su propio bloque.
Refactor publish.py: expone helpers reutilizables (local_ipv4, load_config,
build_infos, register_all, unregister_all) que consume el daemon.
Tests offline (tests/, pytest, 20 casos, sin red):
- construccion/parseo de queries A, seleccion de IP ante multi-homing
(rechazo de rangos virtuales), cache con TTL y familias, PTR reverse,
render/strip del bloque de hosts.
- pyproject.toml con metadata, deps, extras [test] y config de pytest.
README: seccion de daemon, sync-hosts y tests; tabla RFC ampliada.
bashrc.snippet: aliases mdns-up/status/down/hosts (consolidados).
install.sh: fix de backticks; symlinks ya cubren mdns + lib via resolve().
Nota: publish.py (109) y service.py (110) exceden levemente las 100 lineas;
se dejan como modulos cohesivos de responsabilidad unica, igual que el
dispatcher CLI, para no fragmentar.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-20 17:59:10 +00:00
|
|
|
DEFAULT_CONFIG = Path.home() / ".config" / "mdns" / "services.json"
|
feat: CLI unificado mdns con browse/reverse/IPv6/cache-TTL
Cierra el gap hacia una implementacion mDNS/DNS-SD completa (RFC 6762/6763).
CLI nuevo `bin/mdns` con subcomandos:
- resolve : hostname -> IP (IPv4 stdlib rapido, IPv6 via zeroconf), cache TTL.
- reverse : IP -> hostname (PTR multicast, IPv4/IPv6).
- browse : descubrimiento DNS-SD (--list-types o --type _ssh._tcp.local.).
- publish : anuncia hostname + servicios (SSH/SMB/HTTP) con probing y goodbye.
- cache : list/clear del cache con TTL.
Biblioteca lib/mdns_tools/ (cada modulo < 100 lineas):
- resolve.py: stdlib-first para IPv4, zeroconf fallback para IPv6/miss.
- _stdlib_resolve.py: resolver A puro stdlib, acumula todas las A del host.
- _pick.py: elige la IP mas alcanzable ante multi-homing. Descarta rangos
virtuales (libvirt 192.168.122/24, docker 172.17/16, link-local) devolviendo
None para que el caller caiga a Tailscale en vez de a una IP muerta.
- browse.py / publish.py: DNS-SD sobre zeroconf.
- reverse.py: PTR stdlib. cache.py: TTL-aware JSON en ~/.cache/mdns.
Mejoras de calidad:
- IP_ADD_MEMBERSHIP en todos los sockets multicast.
- Retries con backoff (timeout dividido).
- Imports lazy en el CLI (resolve/cache no cargan zeroconf -> ~1s vs ~4s).
Wrappers:
- resolve (bash): orden mdns -> mdns-resolve stdlib -> Tailscale -> cache plano.
- mdns-publish.py: shim compat sobre `mdns publish`, lee ~/.config/mdns/services.json.
- install.sh: symlinks bin/ (updates via git pull), siembra services.json.
- ssh_config.example: HostKeyAlias documentado (alias comparte known_hosts con
el nombre canonico; evita fallos de verificacion tras reinstalar un host).
- bashrc.snippet: aliases mdns-types/ssh/smb/http, mdns-cache, whor.
Validado E2E en LAN: browse detecta 13 tipos; dell/movil resuelven a IP LAN
real; lenovo (solo publica virbr0 192.168.122.1) cae correctamente a Tailscale;
IPv6 de dell via zeroconf; reverse 192.168.1.17 -> dell-latitude3400.local;
publish visible desde otro browser. Modulos < 100 lineas salvo el dispatcher.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-20 12:06:53 +00:00
|
|
|
|
feat: publisher-daemon (singleton + re-announce), sync-hosts, status/stop y tests
Bloque de robustez operativa + integracion de sistema + calidad:
Publisher como servicio (lib/mdns_tools/service.py):
- `mdns publish --daemon`: daemon singleton con lock PID en
~/.cache/mdns/publisher.pid. Un segundo intento es rechazado -> evita el
problema de "Termux SMB" duplicado (varios publishers compitiendo por el
mismo nombre, que zeroconf renombra a "-2").
- Re-anuncio automatico al cambiar la IP local (WiFi<->datos, roaming):
unregister + register con la nueva IP para que el registro no quede stale.
- Apagado limpio: maneja SIGTERM ademas de SIGINT (loop en ticks de 1s) y
envia goodbye packets para que los clientes borren la entrada al instante.
- `mdns status` (running/host/ip/servicios/desde) y `mdns stop`.
Integracion de sistema (lib/mdns_tools/hosts.py):
- `mdns sync-hosts [--dry-run]`: escribe un bloque gestionado en
$PREFIX/etc/hosts (o /etc/hosts) para que ping/curl/kubectl nativos
resuelvan .local sin el wrapper `resolve`. Solo toca su propio bloque.
Refactor publish.py: expone helpers reutilizables (local_ipv4, load_config,
build_infos, register_all, unregister_all) que consume el daemon.
Tests offline (tests/, pytest, 20 casos, sin red):
- construccion/parseo de queries A, seleccion de IP ante multi-homing
(rechazo de rangos virtuales), cache con TTL y familias, PTR reverse,
render/strip del bloque de hosts.
- pyproject.toml con metadata, deps, extras [test] y config de pytest.
README: seccion de daemon, sync-hosts y tests; tabla RFC ampliada.
bashrc.snippet: aliases mdns-up/status/down/hosts (consolidados).
install.sh: fix de backticks; symlinks ya cubren mdns + lib via resolve().
Nota: publish.py (109) y service.py (110) exceden levemente las 100 lineas;
se dejan como modulos cohesivos de responsabilidad unica, igual que el
dispatcher CLI, para no fragmentar.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-20 17:59:10 +00:00
|
|
|
|
|
|
|
|
def local_ipv4() -> str:
|
feat: CLI unificado mdns con browse/reverse/IPv6/cache-TTL
Cierra el gap hacia una implementacion mDNS/DNS-SD completa (RFC 6762/6763).
CLI nuevo `bin/mdns` con subcomandos:
- resolve : hostname -> IP (IPv4 stdlib rapido, IPv6 via zeroconf), cache TTL.
- reverse : IP -> hostname (PTR multicast, IPv4/IPv6).
- browse : descubrimiento DNS-SD (--list-types o --type _ssh._tcp.local.).
- publish : anuncia hostname + servicios (SSH/SMB/HTTP) con probing y goodbye.
- cache : list/clear del cache con TTL.
Biblioteca lib/mdns_tools/ (cada modulo < 100 lineas):
- resolve.py: stdlib-first para IPv4, zeroconf fallback para IPv6/miss.
- _stdlib_resolve.py: resolver A puro stdlib, acumula todas las A del host.
- _pick.py: elige la IP mas alcanzable ante multi-homing. Descarta rangos
virtuales (libvirt 192.168.122/24, docker 172.17/16, link-local) devolviendo
None para que el caller caiga a Tailscale en vez de a una IP muerta.
- browse.py / publish.py: DNS-SD sobre zeroconf.
- reverse.py: PTR stdlib. cache.py: TTL-aware JSON en ~/.cache/mdns.
Mejoras de calidad:
- IP_ADD_MEMBERSHIP en todos los sockets multicast.
- Retries con backoff (timeout dividido).
- Imports lazy en el CLI (resolve/cache no cargan zeroconf -> ~1s vs ~4s).
Wrappers:
- resolve (bash): orden mdns -> mdns-resolve stdlib -> Tailscale -> cache plano.
- mdns-publish.py: shim compat sobre `mdns publish`, lee ~/.config/mdns/services.json.
- install.sh: symlinks bin/ (updates via git pull), siembra services.json.
- ssh_config.example: HostKeyAlias documentado (alias comparte known_hosts con
el nombre canonico; evita fallos de verificacion tras reinstalar un host).
- bashrc.snippet: aliases mdns-types/ssh/smb/http, mdns-cache, whor.
Validado E2E en LAN: browse detecta 13 tipos; dell/movil resuelven a IP LAN
real; lenovo (solo publica virbr0 192.168.122.1) cae correctamente a Tailscale;
IPv6 de dell via zeroconf; reverse 192.168.1.17 -> dell-latitude3400.local;
publish visible desde otro browser. Modulos < 100 lineas salvo el dispatcher.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-20 12:06:53 +00:00
|
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
|
|
|
try:
|
|
|
|
|
s.connect(("8.8.8.8", 80))
|
|
|
|
|
return s.getsockname()[0]
|
|
|
|
|
except OSError:
|
|
|
|
|
return "127.0.0.1"
|
|
|
|
|
finally:
|
|
|
|
|
s.close()
|
|
|
|
|
|
|
|
|
|
|
feat: monitor en vivo (--watch) y publish --address; interop validada
Añade las dos funciones que Avahi/Bonjour tenían y faltaban:
- `mdns browse --watch`: monitor DNS-SD en vivo (eventos +/=/-), equivalente
a `avahi-browse -a`. Sin --type descubre tipos dinámicamente via el
meta-servicio _services._dns-sd._udp.local. y adjunta un browser por tipo.
Nuevo modulo lib/mdns_tools/watch.py.
- `mdns publish --address <nombre> <ip>`: publica un A record puro
hostname->IP, equivalente a `avahi-publish -a`.
Robustez del publisher:
- Maneja NonUniqueNameException (probing RFC 6762): si el nombre ya esta
tomado, deja que zeroconf renombre en vez de crashear.
- `mdns publish` sin --config auto-carga ~/.config/mdns/services.json.
Interoperabilidad validada en LAN real (documentada en README):
- Termux -> Avahi: con `mdns publish` activo, un Linux con nss-mdns resolvio
`movil.local` y le hizo ping sin configuracion extra (getent + ping OK).
- Avahi -> Termux: `mdns browse` ve los servicios que publican equipos
Avahi/Bonjour (SMB, SSH, _workstation, Chromecast, AirPlay...).
Al usar zeroconf (RFC 6762/6763), Mac/Linux/Windows descubren y resuelven
lo publicado y viceversa, sin cliente especial.
README: seccion de interoperabilidad, tabla RFC ampliada, ejemplos --watch
y --address. bashrc.snippet: alias mdns-watch. publish.py recortado a <100.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-20 13:00:07 +00:00
|
|
|
def _addr_bytes(ip: str) -> bytes:
|
|
|
|
|
if ":" in ip:
|
|
|
|
|
return socket.inet_pton(socket.AF_INET6, ip)
|
|
|
|
|
return socket.inet_aton(ip)
|
|
|
|
|
|
|
|
|
|
|
feat: publisher-daemon (singleton + re-announce), sync-hosts, status/stop y tests
Bloque de robustez operativa + integracion de sistema + calidad:
Publisher como servicio (lib/mdns_tools/service.py):
- `mdns publish --daemon`: daemon singleton con lock PID en
~/.cache/mdns/publisher.pid. Un segundo intento es rechazado -> evita el
problema de "Termux SMB" duplicado (varios publishers compitiendo por el
mismo nombre, que zeroconf renombra a "-2").
- Re-anuncio automatico al cambiar la IP local (WiFi<->datos, roaming):
unregister + register con la nueva IP para que el registro no quede stale.
- Apagado limpio: maneja SIGTERM ademas de SIGINT (loop en ticks de 1s) y
envia goodbye packets para que los clientes borren la entrada al instante.
- `mdns status` (running/host/ip/servicios/desde) y `mdns stop`.
Integracion de sistema (lib/mdns_tools/hosts.py):
- `mdns sync-hosts [--dry-run]`: escribe un bloque gestionado en
$PREFIX/etc/hosts (o /etc/hosts) para que ping/curl/kubectl nativos
resuelvan .local sin el wrapper `resolve`. Solo toca su propio bloque.
Refactor publish.py: expone helpers reutilizables (local_ipv4, load_config,
build_infos, register_all, unregister_all) que consume el daemon.
Tests offline (tests/, pytest, 20 casos, sin red):
- construccion/parseo de queries A, seleccion de IP ante multi-homing
(rechazo de rangos virtuales), cache con TTL y familias, PTR reverse,
render/strip del bloque de hosts.
- pyproject.toml con metadata, deps, extras [test] y config de pytest.
README: seccion de daemon, sync-hosts y tests; tabla RFC ampliada.
bashrc.snippet: aliases mdns-up/status/down/hosts (consolidados).
install.sh: fix de backticks; symlinks ya cubren mdns + lib via resolve().
Nota: publish.py (109) y service.py (110) exceden levemente las 100 lineas;
se dejan como modulos cohesivos de responsabilidad unica, igual que el
dispatcher CLI, para no fragmentar.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-20 17:59:10 +00:00
|
|
|
def load_config(path: str | None) -> dict:
|
|
|
|
|
p = Path(path).expanduser() if path else DEFAULT_CONFIG
|
feat: CLI unificado mdns con browse/reverse/IPv6/cache-TTL
Cierra el gap hacia una implementacion mDNS/DNS-SD completa (RFC 6762/6763).
CLI nuevo `bin/mdns` con subcomandos:
- resolve : hostname -> IP (IPv4 stdlib rapido, IPv6 via zeroconf), cache TTL.
- reverse : IP -> hostname (PTR multicast, IPv4/IPv6).
- browse : descubrimiento DNS-SD (--list-types o --type _ssh._tcp.local.).
- publish : anuncia hostname + servicios (SSH/SMB/HTTP) con probing y goodbye.
- cache : list/clear del cache con TTL.
Biblioteca lib/mdns_tools/ (cada modulo < 100 lineas):
- resolve.py: stdlib-first para IPv4, zeroconf fallback para IPv6/miss.
- _stdlib_resolve.py: resolver A puro stdlib, acumula todas las A del host.
- _pick.py: elige la IP mas alcanzable ante multi-homing. Descarta rangos
virtuales (libvirt 192.168.122/24, docker 172.17/16, link-local) devolviendo
None para que el caller caiga a Tailscale en vez de a una IP muerta.
- browse.py / publish.py: DNS-SD sobre zeroconf.
- reverse.py: PTR stdlib. cache.py: TTL-aware JSON en ~/.cache/mdns.
Mejoras de calidad:
- IP_ADD_MEMBERSHIP en todos los sockets multicast.
- Retries con backoff (timeout dividido).
- Imports lazy en el CLI (resolve/cache no cargan zeroconf -> ~1s vs ~4s).
Wrappers:
- resolve (bash): orden mdns -> mdns-resolve stdlib -> Tailscale -> cache plano.
- mdns-publish.py: shim compat sobre `mdns publish`, lee ~/.config/mdns/services.json.
- install.sh: symlinks bin/ (updates via git pull), siembra services.json.
- ssh_config.example: HostKeyAlias documentado (alias comparte known_hosts con
el nombre canonico; evita fallos de verificacion tras reinstalar un host).
- bashrc.snippet: aliases mdns-types/ssh/smb/http, mdns-cache, whor.
Validado E2E en LAN: browse detecta 13 tipos; dell/movil resuelven a IP LAN
real; lenovo (solo publica virbr0 192.168.122.1) cae correctamente a Tailscale;
IPv6 de dell via zeroconf; reverse 192.168.1.17 -> dell-latitude3400.local;
publish visible desde otro browser. Modulos < 100 lineas salvo el dispatcher.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-20 12:06:53 +00:00
|
|
|
if not p.exists():
|
|
|
|
|
return {"hostname": "movil", "services": []}
|
|
|
|
|
try:
|
feat: monitor en vivo (--watch) y publish --address; interop validada
Añade las dos funciones que Avahi/Bonjour tenían y faltaban:
- `mdns browse --watch`: monitor DNS-SD en vivo (eventos +/=/-), equivalente
a `avahi-browse -a`. Sin --type descubre tipos dinámicamente via el
meta-servicio _services._dns-sd._udp.local. y adjunta un browser por tipo.
Nuevo modulo lib/mdns_tools/watch.py.
- `mdns publish --address <nombre> <ip>`: publica un A record puro
hostname->IP, equivalente a `avahi-publish -a`.
Robustez del publisher:
- Maneja NonUniqueNameException (probing RFC 6762): si el nombre ya esta
tomado, deja que zeroconf renombre en vez de crashear.
- `mdns publish` sin --config auto-carga ~/.config/mdns/services.json.
Interoperabilidad validada en LAN real (documentada en README):
- Termux -> Avahi: con `mdns publish` activo, un Linux con nss-mdns resolvio
`movil.local` y le hizo ping sin configuracion extra (getent + ping OK).
- Avahi -> Termux: `mdns browse` ve los servicios que publican equipos
Avahi/Bonjour (SMB, SSH, _workstation, Chromecast, AirPlay...).
Al usar zeroconf (RFC 6762/6763), Mac/Linux/Windows descubren y resuelven
lo publicado y viceversa, sin cliente especial.
README: seccion de interoperabilidad, tabla RFC ampliada, ejemplos --watch
y --address. bashrc.snippet: alias mdns-watch. publish.py recortado a <100.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-20 13:00:07 +00:00
|
|
|
return json.loads(p.read_text())
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"warning: bad config {p}: {e}")
|
feat: CLI unificado mdns con browse/reverse/IPv6/cache-TTL
Cierra el gap hacia una implementacion mDNS/DNS-SD completa (RFC 6762/6763).
CLI nuevo `bin/mdns` con subcomandos:
- resolve : hostname -> IP (IPv4 stdlib rapido, IPv6 via zeroconf), cache TTL.
- reverse : IP -> hostname (PTR multicast, IPv4/IPv6).
- browse : descubrimiento DNS-SD (--list-types o --type _ssh._tcp.local.).
- publish : anuncia hostname + servicios (SSH/SMB/HTTP) con probing y goodbye.
- cache : list/clear del cache con TTL.
Biblioteca lib/mdns_tools/ (cada modulo < 100 lineas):
- resolve.py: stdlib-first para IPv4, zeroconf fallback para IPv6/miss.
- _stdlib_resolve.py: resolver A puro stdlib, acumula todas las A del host.
- _pick.py: elige la IP mas alcanzable ante multi-homing. Descarta rangos
virtuales (libvirt 192.168.122/24, docker 172.17/16, link-local) devolviendo
None para que el caller caiga a Tailscale en vez de a una IP muerta.
- browse.py / publish.py: DNS-SD sobre zeroconf.
- reverse.py: PTR stdlib. cache.py: TTL-aware JSON en ~/.cache/mdns.
Mejoras de calidad:
- IP_ADD_MEMBERSHIP en todos los sockets multicast.
- Retries con backoff (timeout dividido).
- Imports lazy en el CLI (resolve/cache no cargan zeroconf -> ~1s vs ~4s).
Wrappers:
- resolve (bash): orden mdns -> mdns-resolve stdlib -> Tailscale -> cache plano.
- mdns-publish.py: shim compat sobre `mdns publish`, lee ~/.config/mdns/services.json.
- install.sh: symlinks bin/ (updates via git pull), siembra services.json.
- ssh_config.example: HostKeyAlias documentado (alias comparte known_hosts con
el nombre canonico; evita fallos de verificacion tras reinstalar un host).
- bashrc.snippet: aliases mdns-types/ssh/smb/http, mdns-cache, whor.
Validado E2E en LAN: browse detecta 13 tipos; dell/movil resuelven a IP LAN
real; lenovo (solo publica virbr0 192.168.122.1) cae correctamente a Tailscale;
IPv6 de dell via zeroconf; reverse 192.168.1.17 -> dell-latitude3400.local;
publish visible desde otro browser. Modulos < 100 lineas salvo el dispatcher.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-20 12:06:53 +00:00
|
|
|
return {"hostname": "movil", "services": []}
|
|
|
|
|
|
|
|
|
|
|
feat: publisher-daemon (singleton + re-announce), sync-hosts, status/stop y tests
Bloque de robustez operativa + integracion de sistema + calidad:
Publisher como servicio (lib/mdns_tools/service.py):
- `mdns publish --daemon`: daemon singleton con lock PID en
~/.cache/mdns/publisher.pid. Un segundo intento es rechazado -> evita el
problema de "Termux SMB" duplicado (varios publishers compitiendo por el
mismo nombre, que zeroconf renombra a "-2").
- Re-anuncio automatico al cambiar la IP local (WiFi<->datos, roaming):
unregister + register con la nueva IP para que el registro no quede stale.
- Apagado limpio: maneja SIGTERM ademas de SIGINT (loop en ticks de 1s) y
envia goodbye packets para que los clientes borren la entrada al instante.
- `mdns status` (running/host/ip/servicios/desde) y `mdns stop`.
Integracion de sistema (lib/mdns_tools/hosts.py):
- `mdns sync-hosts [--dry-run]`: escribe un bloque gestionado en
$PREFIX/etc/hosts (o /etc/hosts) para que ping/curl/kubectl nativos
resuelvan .local sin el wrapper `resolve`. Solo toca su propio bloque.
Refactor publish.py: expone helpers reutilizables (local_ipv4, load_config,
build_infos, register_all, unregister_all) que consume el daemon.
Tests offline (tests/, pytest, 20 casos, sin red):
- construccion/parseo de queries A, seleccion de IP ante multi-homing
(rechazo de rangos virtuales), cache con TTL y familias, PTR reverse,
render/strip del bloque de hosts.
- pyproject.toml con metadata, deps, extras [test] y config de pytest.
README: seccion de daemon, sync-hosts y tests; tabla RFC ampliada.
bashrc.snippet: aliases mdns-up/status/down/hosts (consolidados).
install.sh: fix de backticks; symlinks ya cubren mdns + lib via resolve().
Nota: publish.py (109) y service.py (110) exceden levemente las 100 lineas;
se dejan como modulos cohesivos de responsabilidad unica, igual que el
dispatcher CLI, para no fragmentar.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-20 17:59:10 +00:00
|
|
|
def _workstation(host: str, ip: str) -> ServiceInfo:
|
|
|
|
|
return ServiceInfo(
|
|
|
|
|
type_="_workstation._tcp.local.",
|
|
|
|
|
name=f"{host} [{ip.replace('.', '-')}]._workstation._tcp.local.",
|
|
|
|
|
addresses=[_addr_bytes(ip)], port=9, server=f"{host}.local.",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def build_infos(cfg: dict, host: str, ip: str) -> list[ServiceInfo]:
|
|
|
|
|
infos = [_workstation(host, ip)]
|
|
|
|
|
for svc in (cfg.get("services") or []):
|
|
|
|
|
try:
|
|
|
|
|
t = svc["type"].rstrip(".") + ".local."
|
|
|
|
|
infos.append(ServiceInfo(
|
|
|
|
|
type_=t, name=f'{svc.get("name", host)}.{t}',
|
|
|
|
|
addresses=[_addr_bytes(ip)], port=int(svc["port"]),
|
|
|
|
|
server=f"{host}.local.", properties=svc.get("txt", {}),
|
|
|
|
|
))
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"warning: could not register {svc}: {e}")
|
|
|
|
|
return infos
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def register_all(zc: Zeroconf, infos: list[ServiceInfo]) -> None:
|
feat: monitor en vivo (--watch) y publish --address; interop validada
Añade las dos funciones que Avahi/Bonjour tenían y faltaban:
- `mdns browse --watch`: monitor DNS-SD en vivo (eventos +/=/-), equivalente
a `avahi-browse -a`. Sin --type descubre tipos dinámicamente via el
meta-servicio _services._dns-sd._udp.local. y adjunta un browser por tipo.
Nuevo modulo lib/mdns_tools/watch.py.
- `mdns publish --address <nombre> <ip>`: publica un A record puro
hostname->IP, equivalente a `avahi-publish -a`.
Robustez del publisher:
- Maneja NonUniqueNameException (probing RFC 6762): si el nombre ya esta
tomado, deja que zeroconf renombre en vez de crashear.
- `mdns publish` sin --config auto-carga ~/.config/mdns/services.json.
Interoperabilidad validada en LAN real (documentada en README):
- Termux -> Avahi: con `mdns publish` activo, un Linux con nss-mdns resolvio
`movil.local` y le hizo ping sin configuracion extra (getent + ping OK).
- Avahi -> Termux: `mdns browse` ve los servicios que publican equipos
Avahi/Bonjour (SMB, SSH, _workstation, Chromecast, AirPlay...).
Al usar zeroconf (RFC 6762/6763), Mac/Linux/Windows descubren y resuelven
lo publicado y viceversa, sin cliente especial.
README: seccion de interoperabilidad, tabla RFC ampliada, ejemplos --watch
y --address. bashrc.snippet: alias mdns-watch. publish.py recortado a <100.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-20 13:00:07 +00:00
|
|
|
for i in infos:
|
|
|
|
|
try: # tolerate name conflicts per RFC 6762 (probe + rename)
|
|
|
|
|
zc.register_service(i)
|
|
|
|
|
except NonUniqueNameException:
|
|
|
|
|
zc.register_service(i, allow_name_change=True)
|
feat: publisher-daemon (singleton + re-announce), sync-hosts, status/stop y tests
Bloque de robustez operativa + integracion de sistema + calidad:
Publisher como servicio (lib/mdns_tools/service.py):
- `mdns publish --daemon`: daemon singleton con lock PID en
~/.cache/mdns/publisher.pid. Un segundo intento es rechazado -> evita el
problema de "Termux SMB" duplicado (varios publishers compitiendo por el
mismo nombre, que zeroconf renombra a "-2").
- Re-anuncio automatico al cambiar la IP local (WiFi<->datos, roaming):
unregister + register con la nueva IP para que el registro no quede stale.
- Apagado limpio: maneja SIGTERM ademas de SIGINT (loop en ticks de 1s) y
envia goodbye packets para que los clientes borren la entrada al instante.
- `mdns status` (running/host/ip/servicios/desde) y `mdns stop`.
Integracion de sistema (lib/mdns_tools/hosts.py):
- `mdns sync-hosts [--dry-run]`: escribe un bloque gestionado en
$PREFIX/etc/hosts (o /etc/hosts) para que ping/curl/kubectl nativos
resuelvan .local sin el wrapper `resolve`. Solo toca su propio bloque.
Refactor publish.py: expone helpers reutilizables (local_ipv4, load_config,
build_infos, register_all, unregister_all) que consume el daemon.
Tests offline (tests/, pytest, 20 casos, sin red):
- construccion/parseo de queries A, seleccion de IP ante multi-homing
(rechazo de rangos virtuales), cache con TTL y familias, PTR reverse,
render/strip del bloque de hosts.
- pyproject.toml con metadata, deps, extras [test] y config de pytest.
README: seccion de daemon, sync-hosts y tests; tabla RFC ampliada.
bashrc.snippet: aliases mdns-up/status/down/hosts (consolidados).
install.sh: fix de backticks; symlinks ya cubren mdns + lib via resolve().
Nota: publish.py (109) y service.py (110) exceden levemente las 100 lineas;
se dejan como modulos cohesivos de responsabilidad unica, igual que el
dispatcher CLI, para no fragmentar.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-20 17:59:10 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def unregister_all(zc: Zeroconf, infos: list[ServiceInfo]) -> None:
|
|
|
|
|
for i in infos:
|
|
|
|
|
try:
|
|
|
|
|
zc.unregister_service(i)
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _serve(infos: list[ServiceInfo], verbose: bool, label: str) -> None:
|
|
|
|
|
zc = Zeroconf()
|
|
|
|
|
register_all(zc, infos)
|
feat: CLI unificado mdns con browse/reverse/IPv6/cache-TTL
Cierra el gap hacia una implementacion mDNS/DNS-SD completa (RFC 6762/6763).
CLI nuevo `bin/mdns` con subcomandos:
- resolve : hostname -> IP (IPv4 stdlib rapido, IPv6 via zeroconf), cache TTL.
- reverse : IP -> hostname (PTR multicast, IPv4/IPv6).
- browse : descubrimiento DNS-SD (--list-types o --type _ssh._tcp.local.).
- publish : anuncia hostname + servicios (SSH/SMB/HTTP) con probing y goodbye.
- cache : list/clear del cache con TTL.
Biblioteca lib/mdns_tools/ (cada modulo < 100 lineas):
- resolve.py: stdlib-first para IPv4, zeroconf fallback para IPv6/miss.
- _stdlib_resolve.py: resolver A puro stdlib, acumula todas las A del host.
- _pick.py: elige la IP mas alcanzable ante multi-homing. Descarta rangos
virtuales (libvirt 192.168.122/24, docker 172.17/16, link-local) devolviendo
None para que el caller caiga a Tailscale en vez de a una IP muerta.
- browse.py / publish.py: DNS-SD sobre zeroconf.
- reverse.py: PTR stdlib. cache.py: TTL-aware JSON en ~/.cache/mdns.
Mejoras de calidad:
- IP_ADD_MEMBERSHIP en todos los sockets multicast.
- Retries con backoff (timeout dividido).
- Imports lazy en el CLI (resolve/cache no cargan zeroconf -> ~1s vs ~4s).
Wrappers:
- resolve (bash): orden mdns -> mdns-resolve stdlib -> Tailscale -> cache plano.
- mdns-publish.py: shim compat sobre `mdns publish`, lee ~/.config/mdns/services.json.
- install.sh: symlinks bin/ (updates via git pull), siembra services.json.
- ssh_config.example: HostKeyAlias documentado (alias comparte known_hosts con
el nombre canonico; evita fallos de verificacion tras reinstalar un host).
- bashrc.snippet: aliases mdns-types/ssh/smb/http, mdns-cache, whor.
Validado E2E en LAN: browse detecta 13 tipos; dell/movil resuelven a IP LAN
real; lenovo (solo publica virbr0 192.168.122.1) cae correctamente a Tailscale;
IPv6 de dell via zeroconf; reverse 192.168.1.17 -> dell-latitude3400.local;
publish visible desde otro browser. Modulos < 100 lineas salvo el dispatcher.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-20 12:06:53 +00:00
|
|
|
if verbose:
|
feat: monitor en vivo (--watch) y publish --address; interop validada
Añade las dos funciones que Avahi/Bonjour tenían y faltaban:
- `mdns browse --watch`: monitor DNS-SD en vivo (eventos +/=/-), equivalente
a `avahi-browse -a`. Sin --type descubre tipos dinámicamente via el
meta-servicio _services._dns-sd._udp.local. y adjunta un browser por tipo.
Nuevo modulo lib/mdns_tools/watch.py.
- `mdns publish --address <nombre> <ip>`: publica un A record puro
hostname->IP, equivalente a `avahi-publish -a`.
Robustez del publisher:
- Maneja NonUniqueNameException (probing RFC 6762): si el nombre ya esta
tomado, deja que zeroconf renombre en vez de crashear.
- `mdns publish` sin --config auto-carga ~/.config/mdns/services.json.
Interoperabilidad validada en LAN real (documentada en README):
- Termux -> Avahi: con `mdns publish` activo, un Linux con nss-mdns resolvio
`movil.local` y le hizo ping sin configuracion extra (getent + ping OK).
- Avahi -> Termux: `mdns browse` ve los servicios que publican equipos
Avahi/Bonjour (SMB, SSH, _workstation, Chromecast, AirPlay...).
Al usar zeroconf (RFC 6762/6763), Mac/Linux/Windows descubren y resuelven
lo publicado y viceversa, sin cliente especial.
README: seccion de interoperabilidad, tabla RFC ampliada, ejemplos --watch
y --address. bashrc.snippet: alias mdns-watch. publish.py recortado a <100.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-20 13:00:07 +00:00
|
|
|
print(f"[mdns] {label} ({len(infos)} record(s)). Ctrl+C to stop.")
|
feat: CLI unificado mdns con browse/reverse/IPv6/cache-TTL
Cierra el gap hacia una implementacion mDNS/DNS-SD completa (RFC 6762/6763).
CLI nuevo `bin/mdns` con subcomandos:
- resolve : hostname -> IP (IPv4 stdlib rapido, IPv6 via zeroconf), cache TTL.
- reverse : IP -> hostname (PTR multicast, IPv4/IPv6).
- browse : descubrimiento DNS-SD (--list-types o --type _ssh._tcp.local.).
- publish : anuncia hostname + servicios (SSH/SMB/HTTP) con probing y goodbye.
- cache : list/clear del cache con TTL.
Biblioteca lib/mdns_tools/ (cada modulo < 100 lineas):
- resolve.py: stdlib-first para IPv4, zeroconf fallback para IPv6/miss.
- _stdlib_resolve.py: resolver A puro stdlib, acumula todas las A del host.
- _pick.py: elige la IP mas alcanzable ante multi-homing. Descarta rangos
virtuales (libvirt 192.168.122/24, docker 172.17/16, link-local) devolviendo
None para que el caller caiga a Tailscale en vez de a una IP muerta.
- browse.py / publish.py: DNS-SD sobre zeroconf.
- reverse.py: PTR stdlib. cache.py: TTL-aware JSON en ~/.cache/mdns.
Mejoras de calidad:
- IP_ADD_MEMBERSHIP en todos los sockets multicast.
- Retries con backoff (timeout dividido).
- Imports lazy en el CLI (resolve/cache no cargan zeroconf -> ~1s vs ~4s).
Wrappers:
- resolve (bash): orden mdns -> mdns-resolve stdlib -> Tailscale -> cache plano.
- mdns-publish.py: shim compat sobre `mdns publish`, lee ~/.config/mdns/services.json.
- install.sh: symlinks bin/ (updates via git pull), siembra services.json.
- ssh_config.example: HostKeyAlias documentado (alias comparte known_hosts con
el nombre canonico; evita fallos de verificacion tras reinstalar un host).
- bashrc.snippet: aliases mdns-types/ssh/smb/http, mdns-cache, whor.
Validado E2E en LAN: browse detecta 13 tipos; dell/movil resuelven a IP LAN
real; lenovo (solo publica virbr0 192.168.122.1) cae correctamente a Tailscale;
IPv6 de dell via zeroconf; reverse 192.168.1.17 -> dell-latitude3400.local;
publish visible desde otro browser. Modulos < 100 lineas salvo el dispatcher.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-20 12:06:53 +00:00
|
|
|
try:
|
|
|
|
|
while True:
|
|
|
|
|
time.sleep(60)
|
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
|
pass
|
|
|
|
|
finally:
|
feat: publisher-daemon (singleton + re-announce), sync-hosts, status/stop y tests
Bloque de robustez operativa + integracion de sistema + calidad:
Publisher como servicio (lib/mdns_tools/service.py):
- `mdns publish --daemon`: daemon singleton con lock PID en
~/.cache/mdns/publisher.pid. Un segundo intento es rechazado -> evita el
problema de "Termux SMB" duplicado (varios publishers compitiendo por el
mismo nombre, que zeroconf renombra a "-2").
- Re-anuncio automatico al cambiar la IP local (WiFi<->datos, roaming):
unregister + register con la nueva IP para que el registro no quede stale.
- Apagado limpio: maneja SIGTERM ademas de SIGINT (loop en ticks de 1s) y
envia goodbye packets para que los clientes borren la entrada al instante.
- `mdns status` (running/host/ip/servicios/desde) y `mdns stop`.
Integracion de sistema (lib/mdns_tools/hosts.py):
- `mdns sync-hosts [--dry-run]`: escribe un bloque gestionado en
$PREFIX/etc/hosts (o /etc/hosts) para que ping/curl/kubectl nativos
resuelvan .local sin el wrapper `resolve`. Solo toca su propio bloque.
Refactor publish.py: expone helpers reutilizables (local_ipv4, load_config,
build_infos, register_all, unregister_all) que consume el daemon.
Tests offline (tests/, pytest, 20 casos, sin red):
- construccion/parseo de queries A, seleccion de IP ante multi-homing
(rechazo de rangos virtuales), cache con TTL y familias, PTR reverse,
render/strip del bloque de hosts.
- pyproject.toml con metadata, deps, extras [test] y config de pytest.
README: seccion de daemon, sync-hosts y tests; tabla RFC ampliada.
bashrc.snippet: aliases mdns-up/status/down/hosts (consolidados).
install.sh: fix de backticks; symlinks ya cubren mdns + lib via resolve().
Nota: publish.py (109) y service.py (110) exceden levemente las 100 lineas;
se dejan como modulos cohesivos de responsabilidad unica, igual que el
dispatcher CLI, para no fragmentar.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-20 17:59:10 +00:00
|
|
|
unregister_all(zc, infos)
|
feat: CLI unificado mdns con browse/reverse/IPv6/cache-TTL
Cierra el gap hacia una implementacion mDNS/DNS-SD completa (RFC 6762/6763).
CLI nuevo `bin/mdns` con subcomandos:
- resolve : hostname -> IP (IPv4 stdlib rapido, IPv6 via zeroconf), cache TTL.
- reverse : IP -> hostname (PTR multicast, IPv4/IPv6).
- browse : descubrimiento DNS-SD (--list-types o --type _ssh._tcp.local.).
- publish : anuncia hostname + servicios (SSH/SMB/HTTP) con probing y goodbye.
- cache : list/clear del cache con TTL.
Biblioteca lib/mdns_tools/ (cada modulo < 100 lineas):
- resolve.py: stdlib-first para IPv4, zeroconf fallback para IPv6/miss.
- _stdlib_resolve.py: resolver A puro stdlib, acumula todas las A del host.
- _pick.py: elige la IP mas alcanzable ante multi-homing. Descarta rangos
virtuales (libvirt 192.168.122/24, docker 172.17/16, link-local) devolviendo
None para que el caller caiga a Tailscale en vez de a una IP muerta.
- browse.py / publish.py: DNS-SD sobre zeroconf.
- reverse.py: PTR stdlib. cache.py: TTL-aware JSON en ~/.cache/mdns.
Mejoras de calidad:
- IP_ADD_MEMBERSHIP en todos los sockets multicast.
- Retries con backoff (timeout dividido).
- Imports lazy en el CLI (resolve/cache no cargan zeroconf -> ~1s vs ~4s).
Wrappers:
- resolve (bash): orden mdns -> mdns-resolve stdlib -> Tailscale -> cache plano.
- mdns-publish.py: shim compat sobre `mdns publish`, lee ~/.config/mdns/services.json.
- install.sh: symlinks bin/ (updates via git pull), siembra services.json.
- ssh_config.example: HostKeyAlias documentado (alias comparte known_hosts con
el nombre canonico; evita fallos de verificacion tras reinstalar un host).
- bashrc.snippet: aliases mdns-types/ssh/smb/http, mdns-cache, whor.
Validado E2E en LAN: browse detecta 13 tipos; dell/movil resuelven a IP LAN
real; lenovo (solo publica virbr0 192.168.122.1) cae correctamente a Tailscale;
IPv6 de dell via zeroconf; reverse 192.168.1.17 -> dell-latitude3400.local;
publish visible desde otro browser. Modulos < 100 lineas salvo el dispatcher.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-20 12:06:53 +00:00
|
|
|
zc.close()
|
|
|
|
|
if verbose:
|
|
|
|
|
print("[mdns] stopped, goodbye packets sent.")
|
feat: monitor en vivo (--watch) y publish --address; interop validada
Añade las dos funciones que Avahi/Bonjour tenían y faltaban:
- `mdns browse --watch`: monitor DNS-SD en vivo (eventos +/=/-), equivalente
a `avahi-browse -a`. Sin --type descubre tipos dinámicamente via el
meta-servicio _services._dns-sd._udp.local. y adjunta un browser por tipo.
Nuevo modulo lib/mdns_tools/watch.py.
- `mdns publish --address <nombre> <ip>`: publica un A record puro
hostname->IP, equivalente a `avahi-publish -a`.
Robustez del publisher:
- Maneja NonUniqueNameException (probing RFC 6762): si el nombre ya esta
tomado, deja que zeroconf renombre en vez de crashear.
- `mdns publish` sin --config auto-carga ~/.config/mdns/services.json.
Interoperabilidad validada en LAN real (documentada en README):
- Termux -> Avahi: con `mdns publish` activo, un Linux con nss-mdns resolvio
`movil.local` y le hizo ping sin configuracion extra (getent + ping OK).
- Avahi -> Termux: `mdns browse` ve los servicios que publican equipos
Avahi/Bonjour (SMB, SSH, _workstation, Chromecast, AirPlay...).
Al usar zeroconf (RFC 6762/6763), Mac/Linux/Windows descubren y resuelven
lo publicado y viceversa, sin cliente especial.
README: seccion de interoperabilidad, tabla RFC ampliada, ejemplos --watch
y --address. bashrc.snippet: alias mdns-watch. publish.py recortado a <100.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-20 13:00:07 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def run(config_path: str | None = None, hostname: str | None = None,
|
|
|
|
|
verbose: bool = True) -> None:
|
feat: publisher-daemon (singleton + re-announce), sync-hosts, status/stop y tests
Bloque de robustez operativa + integracion de sistema + calidad:
Publisher como servicio (lib/mdns_tools/service.py):
- `mdns publish --daemon`: daemon singleton con lock PID en
~/.cache/mdns/publisher.pid. Un segundo intento es rechazado -> evita el
problema de "Termux SMB" duplicado (varios publishers compitiendo por el
mismo nombre, que zeroconf renombra a "-2").
- Re-anuncio automatico al cambiar la IP local (WiFi<->datos, roaming):
unregister + register con la nueva IP para que el registro no quede stale.
- Apagado limpio: maneja SIGTERM ademas de SIGINT (loop en ticks de 1s) y
envia goodbye packets para que los clientes borren la entrada al instante.
- `mdns status` (running/host/ip/servicios/desde) y `mdns stop`.
Integracion de sistema (lib/mdns_tools/hosts.py):
- `mdns sync-hosts [--dry-run]`: escribe un bloque gestionado en
$PREFIX/etc/hosts (o /etc/hosts) para que ping/curl/kubectl nativos
resuelvan .local sin el wrapper `resolve`. Solo toca su propio bloque.
Refactor publish.py: expone helpers reutilizables (local_ipv4, load_config,
build_infos, register_all, unregister_all) que consume el daemon.
Tests offline (tests/, pytest, 20 casos, sin red):
- construccion/parseo de queries A, seleccion de IP ante multi-homing
(rechazo de rangos virtuales), cache con TTL y familias, PTR reverse,
render/strip del bloque de hosts.
- pyproject.toml con metadata, deps, extras [test] y config de pytest.
README: seccion de daemon, sync-hosts y tests; tabla RFC ampliada.
bashrc.snippet: aliases mdns-up/status/down/hosts (consolidados).
install.sh: fix de backticks; symlinks ya cubren mdns + lib via resolve().
Nota: publish.py (109) y service.py (110) exceden levemente las 100 lineas;
se dejan como modulos cohesivos de responsabilidad unica, igual que el
dispatcher CLI, para no fragmentar.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-20 17:59:10 +00:00
|
|
|
cfg = load_config(config_path)
|
feat: monitor en vivo (--watch) y publish --address; interop validada
Añade las dos funciones que Avahi/Bonjour tenían y faltaban:
- `mdns browse --watch`: monitor DNS-SD en vivo (eventos +/=/-), equivalente
a `avahi-browse -a`. Sin --type descubre tipos dinámicamente via el
meta-servicio _services._dns-sd._udp.local. y adjunta un browser por tipo.
Nuevo modulo lib/mdns_tools/watch.py.
- `mdns publish --address <nombre> <ip>`: publica un A record puro
hostname->IP, equivalente a `avahi-publish -a`.
Robustez del publisher:
- Maneja NonUniqueNameException (probing RFC 6762): si el nombre ya esta
tomado, deja que zeroconf renombre en vez de crashear.
- `mdns publish` sin --config auto-carga ~/.config/mdns/services.json.
Interoperabilidad validada en LAN real (documentada en README):
- Termux -> Avahi: con `mdns publish` activo, un Linux con nss-mdns resolvio
`movil.local` y le hizo ping sin configuracion extra (getent + ping OK).
- Avahi -> Termux: `mdns browse` ve los servicios que publican equipos
Avahi/Bonjour (SMB, SSH, _workstation, Chromecast, AirPlay...).
Al usar zeroconf (RFC 6762/6763), Mac/Linux/Windows descubren y resuelven
lo publicado y viceversa, sin cliente especial.
README: seccion de interoperabilidad, tabla RFC ampliada, ejemplos --watch
y --address. bashrc.snippet: alias mdns-watch. publish.py recortado a <100.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-20 13:00:07 +00:00
|
|
|
host = hostname or cfg.get("hostname", "movil")
|
feat: publisher-daemon (singleton + re-announce), sync-hosts, status/stop y tests
Bloque de robustez operativa + integracion de sistema + calidad:
Publisher como servicio (lib/mdns_tools/service.py):
- `mdns publish --daemon`: daemon singleton con lock PID en
~/.cache/mdns/publisher.pid. Un segundo intento es rechazado -> evita el
problema de "Termux SMB" duplicado (varios publishers compitiendo por el
mismo nombre, que zeroconf renombra a "-2").
- Re-anuncio automatico al cambiar la IP local (WiFi<->datos, roaming):
unregister + register con la nueva IP para que el registro no quede stale.
- Apagado limpio: maneja SIGTERM ademas de SIGINT (loop en ticks de 1s) y
envia goodbye packets para que los clientes borren la entrada al instante.
- `mdns status` (running/host/ip/servicios/desde) y `mdns stop`.
Integracion de sistema (lib/mdns_tools/hosts.py):
- `mdns sync-hosts [--dry-run]`: escribe un bloque gestionado en
$PREFIX/etc/hosts (o /etc/hosts) para que ping/curl/kubectl nativos
resuelvan .local sin el wrapper `resolve`. Solo toca su propio bloque.
Refactor publish.py: expone helpers reutilizables (local_ipv4, load_config,
build_infos, register_all, unregister_all) que consume el daemon.
Tests offline (tests/, pytest, 20 casos, sin red):
- construccion/parseo de queries A, seleccion de IP ante multi-homing
(rechazo de rangos virtuales), cache con TTL y familias, PTR reverse,
render/strip del bloque de hosts.
- pyproject.toml con metadata, deps, extras [test] y config de pytest.
README: seccion de daemon, sync-hosts y tests; tabla RFC ampliada.
bashrc.snippet: aliases mdns-up/status/down/hosts (consolidados).
install.sh: fix de backticks; symlinks ya cubren mdns + lib via resolve().
Nota: publish.py (109) y service.py (110) exceden levemente las 100 lineas;
se dejan como modulos cohesivos de responsabilidad unica, igual que el
dispatcher CLI, para no fragmentar.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-20 17:59:10 +00:00
|
|
|
ip = local_ipv4()
|
|
|
|
|
_serve(build_infos(cfg, host, ip), verbose, f"published {host}.local ({ip})")
|
feat: monitor en vivo (--watch) y publish --address; interop validada
Añade las dos funciones que Avahi/Bonjour tenían y faltaban:
- `mdns browse --watch`: monitor DNS-SD en vivo (eventos +/=/-), equivalente
a `avahi-browse -a`. Sin --type descubre tipos dinámicamente via el
meta-servicio _services._dns-sd._udp.local. y adjunta un browser por tipo.
Nuevo modulo lib/mdns_tools/watch.py.
- `mdns publish --address <nombre> <ip>`: publica un A record puro
hostname->IP, equivalente a `avahi-publish -a`.
Robustez del publisher:
- Maneja NonUniqueNameException (probing RFC 6762): si el nombre ya esta
tomado, deja que zeroconf renombre en vez de crashear.
- `mdns publish` sin --config auto-carga ~/.config/mdns/services.json.
Interoperabilidad validada en LAN real (documentada en README):
- Termux -> Avahi: con `mdns publish` activo, un Linux con nss-mdns resolvio
`movil.local` y le hizo ping sin configuracion extra (getent + ping OK).
- Avahi -> Termux: `mdns browse` ve los servicios que publican equipos
Avahi/Bonjour (SMB, SSH, _workstation, Chromecast, AirPlay...).
Al usar zeroconf (RFC 6762/6763), Mac/Linux/Windows descubren y resuelven
lo publicado y viceversa, sin cliente especial.
README: seccion de interoperabilidad, tabla RFC ampliada, ejemplos --watch
y --address. bashrc.snippet: alias mdns-watch. publish.py recortado a <100.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-20 13:00:07 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def publish_address(name: str, ip: str, verbose: bool = True) -> None:
|
feat: publisher-daemon (singleton + re-announce), sync-hosts, status/stop y tests
Bloque de robustez operativa + integracion de sistema + calidad:
Publisher como servicio (lib/mdns_tools/service.py):
- `mdns publish --daemon`: daemon singleton con lock PID en
~/.cache/mdns/publisher.pid. Un segundo intento es rechazado -> evita el
problema de "Termux SMB" duplicado (varios publishers compitiendo por el
mismo nombre, que zeroconf renombra a "-2").
- Re-anuncio automatico al cambiar la IP local (WiFi<->datos, roaming):
unregister + register con la nueva IP para que el registro no quede stale.
- Apagado limpio: maneja SIGTERM ademas de SIGINT (loop en ticks de 1s) y
envia goodbye packets para que los clientes borren la entrada al instante.
- `mdns status` (running/host/ip/servicios/desde) y `mdns stop`.
Integracion de sistema (lib/mdns_tools/hosts.py):
- `mdns sync-hosts [--dry-run]`: escribe un bloque gestionado en
$PREFIX/etc/hosts (o /etc/hosts) para que ping/curl/kubectl nativos
resuelvan .local sin el wrapper `resolve`. Solo toca su propio bloque.
Refactor publish.py: expone helpers reutilizables (local_ipv4, load_config,
build_infos, register_all, unregister_all) que consume el daemon.
Tests offline (tests/, pytest, 20 casos, sin red):
- construccion/parseo de queries A, seleccion de IP ante multi-homing
(rechazo de rangos virtuales), cache con TTL y familias, PTR reverse,
render/strip del bloque de hosts.
- pyproject.toml con metadata, deps, extras [test] y config de pytest.
README: seccion de daemon, sync-hosts y tests; tabla RFC ampliada.
bashrc.snippet: aliases mdns-up/status/down/hosts (consolidados).
install.sh: fix de backticks; symlinks ya cubren mdns + lib via resolve().
Nota: publish.py (109) y service.py (110) exceden levemente las 100 lineas;
se dejan como modulos cohesivos de responsabilidad unica, igual que el
dispatcher CLI, para no fragmentar.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-20 17:59:10 +00:00
|
|
|
"""Publish a bare hostname -> IP (like `avahi-publish -a`)."""
|
feat: monitor en vivo (--watch) y publish --address; interop validada
Añade las dos funciones que Avahi/Bonjour tenían y faltaban:
- `mdns browse --watch`: monitor DNS-SD en vivo (eventos +/=/-), equivalente
a `avahi-browse -a`. Sin --type descubre tipos dinámicamente via el
meta-servicio _services._dns-sd._udp.local. y adjunta un browser por tipo.
Nuevo modulo lib/mdns_tools/watch.py.
- `mdns publish --address <nombre> <ip>`: publica un A record puro
hostname->IP, equivalente a `avahi-publish -a`.
Robustez del publisher:
- Maneja NonUniqueNameException (probing RFC 6762): si el nombre ya esta
tomado, deja que zeroconf renombre en vez de crashear.
- `mdns publish` sin --config auto-carga ~/.config/mdns/services.json.
Interoperabilidad validada en LAN real (documentada en README):
- Termux -> Avahi: con `mdns publish` activo, un Linux con nss-mdns resolvio
`movil.local` y le hizo ping sin configuracion extra (getent + ping OK).
- Avahi -> Termux: `mdns browse` ve los servicios que publican equipos
Avahi/Bonjour (SMB, SSH, _workstation, Chromecast, AirPlay...).
Al usar zeroconf (RFC 6762/6763), Mac/Linux/Windows descubren y resuelven
lo publicado y viceversa, sin cliente especial.
README: seccion de interoperabilidad, tabla RFC ampliada, ejemplos --watch
y --address. bashrc.snippet: alias mdns-watch. publish.py recortado a <100.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-20 13:00:07 +00:00
|
|
|
host = name.rstrip(".").removesuffix(".local")
|
feat: publisher-daemon (singleton + re-announce), sync-hosts, status/stop y tests
Bloque de robustez operativa + integracion de sistema + calidad:
Publisher como servicio (lib/mdns_tools/service.py):
- `mdns publish --daemon`: daemon singleton con lock PID en
~/.cache/mdns/publisher.pid. Un segundo intento es rechazado -> evita el
problema de "Termux SMB" duplicado (varios publishers compitiendo por el
mismo nombre, que zeroconf renombra a "-2").
- Re-anuncio automatico al cambiar la IP local (WiFi<->datos, roaming):
unregister + register con la nueva IP para que el registro no quede stale.
- Apagado limpio: maneja SIGTERM ademas de SIGINT (loop en ticks de 1s) y
envia goodbye packets para que los clientes borren la entrada al instante.
- `mdns status` (running/host/ip/servicios/desde) y `mdns stop`.
Integracion de sistema (lib/mdns_tools/hosts.py):
- `mdns sync-hosts [--dry-run]`: escribe un bloque gestionado en
$PREFIX/etc/hosts (o /etc/hosts) para que ping/curl/kubectl nativos
resuelvan .local sin el wrapper `resolve`. Solo toca su propio bloque.
Refactor publish.py: expone helpers reutilizables (local_ipv4, load_config,
build_infos, register_all, unregister_all) que consume el daemon.
Tests offline (tests/, pytest, 20 casos, sin red):
- construccion/parseo de queries A, seleccion de IP ante multi-homing
(rechazo de rangos virtuales), cache con TTL y familias, PTR reverse,
render/strip del bloque de hosts.
- pyproject.toml con metadata, deps, extras [test] y config de pytest.
README: seccion de daemon, sync-hosts y tests; tabla RFC ampliada.
bashrc.snippet: aliases mdns-up/status/down/hosts (consolidados).
install.sh: fix de backticks; symlinks ya cubren mdns + lib via resolve().
Nota: publish.py (109) y service.py (110) exceden levemente las 100 lineas;
se dejan como modulos cohesivos de responsabilidad unica, igual que el
dispatcher CLI, para no fragmentar.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-20 17:59:10 +00:00
|
|
|
_serve([_workstation(host, ip)], verbose, f"published {host}.local -> {ip}")
|