mdns-termux/README.md

185 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# mdns-termux
**mDNS + Tailscale hostname resolution for Termux (Android).**
Termux runs on Android's Bionic libc, which does **not** resolve `.local` names
and lacks `getent`. This repo ships small, dependency-light helpers that fix
that — without root, and without a custom system resolver.
Works on any network: LAN via multicast, off-LAN via Tailscale MagicDNS.
---
## What's in here
**Unified CLI:** `mdns` with five subcommands (`resolve`, `browse`, `publish`,
`reverse`, `cache`). Backed by `zeroconf` for RFC-complete behavior — TTL
cache, IPv4 + IPv6, service discovery, probing on publish, goodbye packets
on exit.
| Script | Role |
|--------|------|
| `mdns` | Unified CLI (zeroconf-backed). Preferred entry point. |
| `mdns-resolve` | Standalone stdlib fallback: works with **zero** dependencies. |
| `mdns-publish.py` | Compat shim over `mdns publish` for existing aliases. |
| `ssh-mdns-proxy` | SSH `ProxyCommand`: mDNS → Tailscale DNS → cached IP. |
| `ssh-fallback` | SSH `ProxyCommand`: Tailscale → LAN, for flaky Tailscale. |
| `resolve` | Bash wrapper: **`mdns` → stdlib → Tailscale → cache**, usable in any command. |
Python library under `lib/mdns_tools/` (each module < 100 lines):
`resolve.py`, `browse.py`, `publish.py`, `reverse.py`, `cache.py`,
`_stdlib_resolve.py` (dep-free A-record resolver).
## Install
```bash
git clone https://devops.ingeniumcodex.com/andresgarcia0313/mdns-termux.git
cd mdns-termux
bash install.sh # or --dry-run first
```
Then follow the printed reminders (append `bashrc.snippet`, set your
`TAILSCALE_DOMAIN`, optionally merge `ssh_config.example`).
## Usage
### `mdns` CLI
```bash
mdns resolve <host> [--family v4|v6] [--timeout 1.5] [--stale] [--no-cache]
mdns reverse <ip> # PTR: IP -> hostname
mdns browse [--type _svc._tcp.local.] [--json] [--list-types]
mdns publish [--config ~/.config/mdns/services.json] [--hostname NAME]
mdns cache list | clear [host]
```
Examples:
```bash
mdns browse --list-types # every service type on the LAN
mdns browse --type _ssh._tcp.local. # every SSH host on the LAN
mdns resolve lenovo-ideapad # -> 192.168.1.106 (LAN, fast stdlib path)
mdns resolve lenovo-ideapad --family v6 # -> fe80::... (via zeroconf)
mdns reverse 192.168.1.17 # -> dell-latitude3400.local
mdns publish # advertise services from ~/.config/mdns/services.json
mdns cache list # inspect TTL-aware cache
```
### `resolve` (bash) — for any command
```bash
resolve lenovo # -> 100.69.236.16
ping $(resolve hp62a)
curl "http://$(resolve dell):8080"
kubectl --server="https://$(resolve master):6443" get nodes
```
Environment knobs:
- `TAILSCALE_DOMAIN` defaults to `tailb0bb74.ts.net`. Set to your own tailnet.
- `MDNS_TIMEOUT` seconds waiting for multicast (default `2`).
- `MDNS_CACHE_PATH` override cache location (default `~/.cache/mdns/hosts.json`).
### Shell helpers (from `config/bashrc.snippet`)
```bash
alias r='resolve'
pingr <host> # ping -c 3 to resolved IP
curlr <host> [flags] # curl http://<resolved-ip>
sshr <host> [cmd...] # ssh via resolved IP with correct HostKeyAlias
whor <ip> # PTR reverse: IP -> hostname
mdns-types # list all service types advertised on LAN
mdns-ssh / mdns-smb / mdns-http # browse specific service types
mdns-cache / mdns-forget # inspect / clear the resolver cache
```
### SSH by hostname (via `ProxyCommand`)
Drop `config/ssh_config.example` into `~/.ssh/config`. Then:
```bash
ssh lenovo # resolves via mDNS then Tailscale, connects
ssh lenovo.local # forces mDNS-first path
```
### Publishing this device on the LAN
Edit `~/.config/mdns/services.json` (the installer seeds a template), then:
```bash
mdns publish # foreground
nohup mdns publish >>~/.cache/mdns.log 2>&1 & disown # background
```
The publisher registers `<hostname>.local` (`_workstation._tcp`) plus every
service listed in the config. On Ctrl+C it sends proper mDNS goodbye packets.
Example `services.json`:
```json
{
"hostname": "movil",
"services": [
{"type": "_ssh._tcp", "port": 8022, "name": "Termux SSH"},
{"type": "_smb._tcp", "port": 4450, "name": "Termux SMB"}
]
}
```
## Behavior by network
| Scenario | Path | Typical latency |
|--------------------|------------------------------------------------------|-----------------|
| On home LAN | stdlib multicast zeroconf (IPv6 or miss) | < 1 s (IPv4) |
| Mobile data / away | multicast times out Tailscale MagicDNS | < 2 s |
| No Tailscale | all fail TTL-aware cache legacy flat cache | instant |
Cache honors real mDNS TTL (default 30 3600 s) and stores IPv4 + IPv6
separately. `mdns resolve --stale <host>` accepts expired entries as a
last-resort fallback.
## RFC coverage (what's implemented)
| Feature | Status | Notes |
|----------------------------------|:------:|------------------------------------------|
| A / AAAA hostname resolution | | stdlib for A, zeroconf for AAAA |
| PTR reverse lookup | | stdlib multicast, IPv4 + IPv6 |
| DNS-SD service browsing | | `mdns browse`, all types or one |
| Publisher (hostname + services) | | via `zeroconf`, with probing + goodbye |
| Multicast group membership | | `IP_ADD_MEMBERSHIP` on all sockets |
| Retries with backoff | | 2 attempts, split timeout |
| TTL-honoring cache | | JSON at `~/.cache/mdns/hosts.json` |
| Legacy unicast queries | | Not implemented (rarely needed) |
| Known-answer suppression | | zeroconf handles internally |
## Gotchas found the hard way
- **`getent` doesn't exist on Termux.** Scripts that shell out to it silently
fail. This repo uses `python -c "import socket; socket.gethostbyname(...)"`
as the portable fallback on Bionic.
- **Shebang matters.** `#!/data/data/com.termux/files/usr/bin/bash` is required
for `declare -A`. A stray backslash (`#\!`) makes the kernel fall through to
`sh`, which rejects bash-only syntax with `Syntax error: "(" unexpected`.
- **mDNS off-LAN is a no-op.** Multicast (224.0.0.251) does not cross networks.
Over mobile data, only Tailscale resolves.
- **`zeroconf` is not `pkg`-managed.** After restoring Termux from a `tar`
backup, re-run `pip install zeroconf` or `mdns-publish.py` will silently die
with `ModuleNotFoundError`.
- **Silent failures are the enemy.** The `.bashrc` snippet deliberately logs
to `~/.cache/mdns.log` instead of `/dev/null`. Keep it that way.
## Tailscale on Termux
Tailscale runs as the **Android app**, not as a Termux CLI `tailscale up`
inside a shell won't work. The app provides MagicDNS for
`*.<your-tailnet>.ts.net`, which Bionic-libc resolves fine (it's just a
regular DNS lookup).
## Related
- [claude-code-termux](https://devops.ingeniumcodex.com/andresgarcia0313/claude-code-termux)
Claude Code CLI setup for Termux. Uses this repo for SSH/host resolution.
## License
MIT see `LICENSE`.