Inspiration
We were frustrated by how much modern communication depends on the internet. In a classroom, a campus event, a hostel, a disaster zone, or a crowded venue, Wi-Fi may exist while the internet doesn't, or the network is overloaded. Yet everyone's phones are sitting a few meters apart, fully capable of talking to each other directly. We wanted to build a messenger that works with no internet, no server, and no account: a chat and calling app that treats the local Wi-Fi or a phone hotspot as the entire network. LANMESH grew out of the idea that people nearby should be able to reach each other privately and reliably, even when the rest of the world is unreachable.
What it does
LANMESH is a fully decentralized, offline-first communication app for Android that runs over a local Wi-Fi network or mobile hotspot.
- Automatic peer discovery: Devices find each other via mDNS with no setup, no internet, and no central server. When discovery is blocked (for example by guest Wi-Fi or client isolation), users can connect by scanning a QR code or entering a multiaddress manually.
- Rich messaging: Real-time text chat, voice messages, images, and file attachments up to 8 MB, plus reactions, message editing and deletion, GPS location sharing, and typing indicators. History is stored locally.
- Groups: Topic channels such as
#generalor#dev, with messages and call signaling isolated per group. - Voice and video calls: One-to-one voice and video calls over direct WebRTC connections, plus open-join group rooms for up to 6 participants with live presence banners.
- Multi-hop forwarding: Messages hop through intermediate devices (up to 6 hops) to reach people outside direct Wi-Fi range, with duplicate and loop suppression.
- History sync and admin tools: A designated admin node backfills missing chat history across partitions and keeps an append-only traffic log that can be viewed or exported as JSON.
- Network visibility and recovery: A Mesh & Peers panel shows connected peers and health stats, and a one-tap "clear state" resets the device when something goes wrong.
How we built it
- Networking core in Go: We built the peer-to-peer engine on libp2p. It handles mDNS discovery, direct TCP connections secured with TLS 1.3, and Ed25519 peer identities. Chat flows over
/p2p-chat/1.0.0, history sync over/p2p-chat-sync/1.0.0, and call signaling over a dedicated/lanmesh-call/1.0.0protocol, so signaling stays fast and separate from the chat relay. - Storage: Messages are persisted in an embedded bbolt database with one bucket per group, and a two-generation
seenCachehandles deduplication. - Frontend: A single-page web UI served locally from
127.0.0.1:9000, talking to the Go backend over a local WebSocket and HTTP endpoints. Voice messages useMediaRecorder, and files and images are sent as Base64 data URLs. - Calling: WebRTC with local host ICE candidates, so no STUN or TURN servers are needed. Group calls use a full-mesh topology, where the peer with the lexicographically smaller ID initiates the offer to avoid signaling collisions. Presence heartbeats every 4 seconds and a 15-second expiry timer keep room banners accurate.
- Android packaging: The Go node is compiled into an AAR via Gomobile and runs inside a foreground service (
LanMeshService) holdingWifiLockandMulticastLock. A WebView hosts the UI, and a Kotlin JavaScript bridge handles QR generation and scanning (ZXing), permissions, downloads, and data clearing.
Challenges we ran into
- Multicast on Android: The OS aggressively drops multicast packets and throttles background networking. We had to hold Wi-Fi and multicast locks inside a foreground service to keep discovery alive when the screen is off.
- Dialing wrong addresses: Nodes advertised wildcard
0.0.0.0addresses and stale cached ports. We wrote address filtering and expansion logic to substitute real LAN IPs and clear stale peerstore entries, along with timeouts and retry backoff. - Broadcast storms: Naive flooding in a mesh creates loops and message amplification. A hop limit combined with a bounded deduplication cache solved this.
- WebRTC glare: Both sides of a group call could send offers at the same time. A deterministic rule based on peer ID ordering fixed this.
- Ghost calls: Devices that crashed or dropped off the network left stale call banners behind. Explicit end packets plus heartbeat-based expiry keep room state accurate.
- Hardware lifecycle: Camera and microphone had to be released cleanly on call end, and WebView permission requests had to be mapped to Android runtime permissions.
- Sync without congestion: Letting every peer sync with every other peer would flood the network, so we made synchronization admin-mediated, using a three-step manifest exchange.
- Network restrictions: Some routers and guest networks block device-to-device traffic entirely, which is why we added QR and manual connection as a fallback.
Accomplishments that we're proud of
- A messenger and calling system that works with zero internet, zero servers, and zero accounts.
- A working multi-hop mesh with loop-free forwarding on ordinary phones.
- Direct WebRTC voice, video, and multi-party group calls without any STUN or TURN infrastructure.
- A full feature set (reactions, edits, deletes, file sharing, location, voice notes, groups) comparable to mainstream messengers, running entirely locally.
- Reliable background operation on Android, with automatic reconnection when the app resumes.
- A clean separation of concerns: a Go networking core, a lightweight web UI, and a thin native Android shell.
- Honest documentation of our limitations, including no end-to-end encryption yet.
What we learned
- How libp2p works in practice: peer identities, protocol multiplexing, stream handling, and peerstore behavior.
- That mobile operating systems are hostile to background networking, and that small details like multicast locks decide whether a P2P app works at all.
- Flooding-based mesh design: hop limits, deduplication, and why signaling should bypass the relay path.
- How WebRTC behaves on a LAN, and how to manage signaling, ICE, and media lifecycles correctly.
- That decentralized systems need explicit answers for presence, expiry, and conflict resolution, because no server is there to arbitrate.
- Security trade-offs: transport encryption protects each hop but is not end-to-end, and self-assigned display names can be spoofed, so only peer IDs can be trusted.
- The value of fallback paths such as QR and manual connection when the ideal path (mDNS) is blocked.
What's next for LanMeshApp
- End-to-end encryption for chat payloads, files, and images, so intermediate relay nodes can't read content.
- Chunked file transfer with resume, removing the 8 MB limit and avoiding large single payloads.
- Admin-free peer-to-peer sync, so devices can reconcile history directly without a designated admin.
- Authenticated identities, such as verified peer keys or QR-based trust, to prevent display name spoofing.
- Wider platform support, including iOS and desktop clients.
- Bluetooth and Wi-Fi Direct transports, so the mesh works even without a shared Wi-Fi network.
- Battery and performance tuning and larger group call capacity through smarter topologies than full mesh.
- Field testing in classrooms, events, and disaster-response scenarios to harden the mesh in real conditions.
Built With
- android
- css
- foreground
- go
- gomobile
- html
- ibp2p
- javascript
- kotlin
- mdns
- mediarecorder
- openstreetmap
- service
- studio
- tcp
- tls
- webrtc
- websocket
- webview
- zxing
Log in or sign up for Devpost to join the conversation.