Files
fips/Cargo.toml
T
Johnathan Corgan 53ad528f7d fipstop: add "Listening on fips0" panel to Node tab
Surfaces local services reachable from the mesh, paired with their
current `inet fips` baseline filter classification. Lands to the
right of the existing TUN section in the Traffic block.

A new daemon control query `show_listening_sockets` returns IPv6
listeners bound to either `::` (wildcard) or the node's fd00::/8
address, each classified as Accept / Drop / Unknown / NoFirewall
against the running inbound chain. fipstop renders the result as a
table beside the Traffic counters: Accept rows in default White,
Drop / Unknown in DarkGray, a yellow banner above the table when
`fips-firewall.service` is inactive, and a trailing `*` on
wildcard binds to remind the operator the bind is not
fips0-specific.

Daemon side:

- `src/control/listening.rs` walks `/proc/net/tcp6` and
  `/proc/net/udp6` via the procfs crate (LISTEN state for TCP,
  wildcard remote for UDP), filters to fips0-reachable binds, and
  resolves inodes to PID / comm via `/proc/<pid>/fd`.

- `src/control/firewall_state.rs` shells out to
  `nft -j list table inet fips` and walks the inbound chain.
  Recognises canonical accepts (`tcp/udp dport N accept`,
  `dport { ... } accept`, `dport A-B accept`), the iifname-scoping
  line, conntrack and icmpv6 lines (skipped). Any rule with
  unrecognised matchers (saddr filters, jumps, daddr filters) or
  non-terminal verdicts forces Unknown classification for the
  ports it references. Eleven unit tests cover the classification
  logic; the listening enumerator carries a /proc-parsing test of
  its own.

- `show_listening_sockets` emits
  `{fips0_addr, firewall_active, sockets[]}` with per-row
  `{proto, local_addr, port, pid, process, filter, wildcard_bind}`.

fipstop side:

- `src/bin/fipstop/ui/dashboard.rs` splits the Traffic block into
  a 50/50 horizontal layout; the existing TUN + Forwarded panel
  occupies the left half.

- `src/bin/fipstop/ui/listening.rs` renders the right half.

- `main.rs` fetches the new query each tick when the Node tab is
  active. Errors are non-fatal: an old daemon without the query
  leaves the payload at None and the panel renders "loading...".

`Cargo.toml` gains `procfs = "0.18"` on the Linux target. IPv4
listeners are not enumerated — fips0 is IPv6-only.

Folded in: revert the default-socket lookup from writability-probe
back to existence-based selection. The previous tempfile-probe on
`/run/fips` silently steered fipstop / fipsctl onto an XDG path
the daemon never bound for any user in the `fips` group whose
shell session had not yet picked up the supplementary group (no
re-login after `usermod -aG`). `XDG_RUNTIME_DIR` is set on every
modern systemd-managed user session, so this hit the common case.
The kernel checks actual group membership at `connect(2)`, so a
user who genuinely cannot connect now gets a clear `EACCES`
rather than a silent path mismatch. Drops the now-unused
`is_writable_dir` helper. `XDG_RUNTIME_DIR` existence validation
is preserved.

Documentation:

- `docs/reference/cli-fipstop.md` — Node-tab row updated, new
  "Listening on fips0 panel" section.
- `docs/reference/control-socket.md` — `show_listening_sockets`
  added to the read-only queries table.
- `docs/how-to/enable-mesh-firewall.md` — new "Verify with
  fipstop" section.
- `docs/tutorials/host-a-service.md` — fipstop callouts at
  Steps 3, 5, 6 + Troubleshooting bullet + wildcard-bind reminder
  under "What you've learned".
- `CHANGELOG.md` — new bullet under `Added / Operator Tooling`,
  resolver `Fixed` entry rewritten to describe the
  existence-based final shape.
2026-05-08 21:36:42 +00:00

105 lines
3.7 KiB
TOML

[package]
name = "fips"
version = "0.3.0-dev"
edition = "2024"
description = "A distributed, decentralized network routing protocol for mesh nodes connecting over arbitrary transports"
license = "MIT"
authors = ["Johnathan Corgan <jcorgan@corganlabs.com>"]
repository = "https://github.com/jmcorgan/fips"
readme = "README.md"
[dependencies]
ratatui = "0.30"
secp256k1 = { version = "0.30", features = ["rand", "global-context"] }
sha2 = "0.10"
hkdf = "0.12"
chacha20poly1305 = "0.10"
rand = "0.10.1"
thiserror = "2.0"
bech32 = "0.11"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_yaml = "0.9"
dirs = "6.0"
hex = "0.4"
clap = { version = "4.6", features = ["derive"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tokio = { version = "1", features = ["rt", "macros", "signal", "sync", "net", "time", "process", "io-util"] }
futures = "0.3"
simple-dns = "0.11.2"
socket2 = { version = "0.6.2", features = ["all"] }
tokio-socks = "0.5"
portable-atomic = { version = "1", features = ["std"] }
nostr = { version = "0.44", features = ["std", "nip59"] }
nostr-sdk = "0.44"
[target.'cfg(unix)'.dependencies]
tun = { version = "0.8.7", features = ["async"] }
libc = "0.2"
[target.'cfg(target_os = "linux")'.dependencies]
rtnetlink = "0.21.0"
rustables = "0.8.7"
procfs = { version = "0.18", default-features = false }
# bluer/BlueZ needs glibc — see build.rs `bluer_available` cfg gate.
[target.'cfg(all(target_os = "linux", not(target_env = "musl")))'.dependencies]
bluer = { version = "0.17", features = ["bluetoothd", "l2cap"] }
[target.'cfg(windows)'.dependencies]
wintun = "0.5"
windows-service = "0.8.1"
[package.metadata.deb]
maintainer = "Johnathan Corgan <jcorgan@corganlabs.com>"
copyright = "2026 Johnathan Corgan"
license-file = ["LICENSE", "0"]
section = "net"
priority = "optional"
depends = "libc6, systemd, libdbus-1-3"
recommends = "bluez"
extended-description = """\
FIPS is a distributed, decentralized network routing protocol for mesh \
nodes connecting over arbitrary transports including UDP, TCP, Ethernet, \
Tor, and Bluetooth (BLE). It provides encrypted peer-to-peer connectivity \
with automatic key management, TUN-based virtual networking, and .fips DNS \
resolution."""
maintainer-scripts = "packaging/debian/"
assets = [
["target/release/fips", "/usr/bin/", "755"],
["target/release/fipsctl", "/usr/bin/", "755"],
["target/release/fipstop", "/usr/bin/", "755"],
["packaging/common/fips.yaml", "/etc/fips/fips.yaml", "600"],
["packaging/common/hosts", "/etc/fips/hosts", "644"],
["packaging/common/fips.nft", "/etc/fips/fips.nft", "644"],
["packaging/debian/fips.service", "/lib/systemd/system/fips.service", "644"],
["packaging/debian/fips-dns.service", "/lib/systemd/system/fips-dns.service", "644"],
["packaging/debian/fips-firewall.service", "/lib/systemd/system/fips-firewall.service", "644"],
["packaging/common/fips-dns-setup", "/usr/lib/fips/fips-dns-setup", "755"],
["packaging/common/fips-dns-teardown", "/usr/lib/fips/fips-dns-teardown", "755"],
["packaging/debian/fips.tmpfiles", "/usr/lib/tmpfiles.d/fips.conf", "644"],
["target/release/fips-gateway", "/usr/bin/", "755"],
["packaging/debian/fips-gateway.service", "/lib/systemd/system/fips-gateway.service", "644"],
["docs/design/fips-security.md", "/usr/share/doc/fips/fips-security.md", "644"],
]
conf-files = ["/etc/fips/fips.yaml", "/etc/fips/hosts", "/etc/fips/fips.nft"]
[dev-dependencies]
tempfile = "3.15"
criterion = { version = "0.8.2", features = ["html_reports"] }
tokio = { version = "1", features = ["test-util"] }
[[bin]]
name = "fipsctl"
path = "src/bin/fipsctl.rs"
[[bin]]
name = "fips-gateway"
path = "src/bin/fips-gateway.rs"
[[bin]]
name = "fipstop"
path = "src/bin/fipstop/main.rs"