The question I get asked most about LanLink is some variation of "wait, how does it actually work without Wi-Fi?" — and the answer is usually a surprise: it does use Wi-Fi, just not the internet. Your router (or a phone acting as a hotspot) creates a local network, and that network is all two devices need to find each other and stream files at full LAN speed. No cloud, no account, no size cap. Here's exactly how the plumbing works, from the discovery layer all the way up to the file bytes.
The difference between Wi-Fi and internet
This trips people up because we use "Wi-Fi" and "internet" interchangeably, but they're two separate things. Wi-Fi is just the radio link between your device and a nearby router or hotspot. The internet is the global network your router might be connected to on the other side. When your phone shows "Connected, no internet" — that's still a perfectly functional local network. Devices on it can see each other, ping each other, and send data at whatever speed the radio supports (often 100–800 Mbps on modern hardware).
The whole trick behind offline file transfer is ignoring the internet side entirely and working with just the local link. Two devices, one network, zero round-trips to any server out in the world.
Step 1 — Finding the other device (mDNS / DNS-SD)
The first problem is discovery. If you plug a phone and a laptop into the same Wi-Fi, neither knows the other exists. You could type in an IP address manually, but that's awful UX and the IPs change every time. The elegant solution is mDNS (multicast DNS), the same protocol that powers Apple's Bonjour and lets your Mac find printers, Chromecasts, and AirPlay speakers automatically.
Here's the idea: when LanLink starts, it broadcasts a small announcement on the local
network — something like _lanlink._tcp.local — with its device name and
the port it's listening on. Every other device running LanLink hears that broadcast and
now knows "there's a peer at 192.168.1.47:53317, and it calls itself
'Tapiwa's Pixel'." No central server, no internet lookup — just a multicast packet that
stays inside the LAN.
Technically, mDNS works by sending DNS queries and responses to the multicast address
224.0.0.251 on port 5353. DNS-SD (Service Discovery) sits on
top: it defines a naming convention so devices can advertise specific services
(file sharing, printing, media streaming) rather than just their existence. When LanLink
registers _lanlink._tcp.local, any device that asks "who offers
_lanlink._tcp?" gets back a pointer record with the host, port, and a
TXT record carrying metadata like the device name, OS, and protocol version.
Step 2 — Opening a direct connection
Once both devices know each other's local IP and port, the next step depends on whether you're building a native app or a browser-based tool.
Native (what LanLink does): The sender opens a plain TLS-over-TCP socket to the receiver's advertised address. The protocol is simple — a JSON header with the file name, size, and MIME type, followed by the raw file bytes. TLS gives you encryption without needing certificates from a public CA; both sides generate self-signed certs at first launch and pin them after the first pairing. LanLink uses the LocalSend protocol, so it's cross-compatible with LocalSend, PairDrop, and anything else that speaks the same wire format.
Browser-based (WebRTC DataChannels): Browsers can't open raw TCP sockets, so browser tools like LocalDrop and PairDrop use WebRTC DataChannels instead. A DataChannel is an encrypted, peer-to-peer pipe that runs over SCTP-on-DTLS-on-UDP — sounds like a lot of layers, but the browser handles all of it. On a LAN, WebRTC only needs host ICE candidates (your local IP), so no STUN or TURN server is required. The one catch is signaling.
The signaling problem (and how to solve it without the internet)
WebRTC is peer-to-peer once the connection is up, but establishing it requires an exchange of "offers" and "answers" — small blobs of session metadata. Normally a cloud WebSocket server relays these. Offline, you need a local substitute.
The cleanest approach: run a tiny WebSocket server on the local network. It can be one of the peers themselves (the device that creates the "room" also runs a lightweight WS server on a known port), or a shared machine like a Raspberry Pi. The signaling server never sees file data — it only passes the SDP offer/answer and a handful of ICE candidates, then gets out of the way. Total signaling traffic is a few kilobytes.
Alternatively, some tools skip WebRTC entirely on native and just use HTTP. The sender POSTs file metadata to the receiver's local HTTP endpoint, the receiver accepts, and the sender streams the bytes over a second HTTP request. It's less elegant than DataChannels, but it works on every platform and avoids the signaling dance altogether.
Chunking, progress, and large files
Sending a 4 GB video isn't the same as sending a 200 KB photo. For large files you need:
- Chunking — split the file into fixed-size chunks (16–64 KB is common for DataChannels; native TCP can use larger buffers). Each chunk gets a sequence number so the receiver can reassemble in order.
- Backpressure — WebRTC's
bufferedAmountproperty tells you how many bytes are queued but not yet sent. If it exceeds a threshold, pause reading from the file until the buffer drains. Without this, you'll either exhaust memory or lose chunks silently on slow receivers. - Progress — track bytes sent against total size. On the native side this is straightforward (TCP ACKs); with DataChannels you increment a counter each time a chunk is queued and the receiver sends a lightweight ACK message back on a separate ordered channel.
- Resume — if the connection drops mid-transfer, knowing the last acknowledged chunk lets you restart from where you left off rather than re-sending the entire file.
One gotcha in browsers: Safari and Firefox buffer received DataChannel data in RAM. A 2 GB file will crash the tab on an iPhone. Chrome and Edge on desktop support the File System Access API, which lets you stream chunks directly to disk. On platforms without it, the practical browser limit is around 500 MB — above that, you're better off with a native app.
Security without a CA
Neither approach sends data in the clear. WebRTC mandates DTLS encryption on every DataChannel — it's not optional, and the keys are negotiated per-session. Native TLS works the same way, just over TCP. The remaining risk is a man-in-the-middle on the local network substituting their own certificate during the handshake.
The mitigation is fingerprint verification: both devices display a short hash of their TLS certificate (or the DTLS fingerprint from the SDP). If the hashes match, you know there's no proxy in between. In practice, most local transfers happen on a home or office network where MITM is unlikely, but the option to verify is there for anyone who wants it.
Why I built LanLink as a native app
I started with a browser prototype — it worked great on two Chrome tabs on the same laptop, which is the least useful place to share files. The moment I tested cross-device (Android Chrome to a Windows laptop), three problems appeared:
- Discovery. Browsers can't do mDNS. You need a signaling service anyway, and without a public server that means one device has to host it — which isn't possible from a mobile browser.
- Background transfers. Switch tabs on Android and the browser suspends your DataChannel. A 1 GB transfer fails unless the user stares at the page the whole time.
- File size. The RAM ceiling on Safari/iOS killed any transfer over ~500 MB.
Going native (Flutter, with platform channels for mDNS and TCP) solved all three. The app registers a real mDNS service, runs transfers in a foreground service on Android, and streams bytes to disk without buffering the whole file. And by speaking the LocalSend protocol, LanLink works with the existing ecosystem — anyone running LocalSend on any platform can send to or receive from LanLink out of the box.
The minimal viable setup
If you just want to try offline file transfer right now, you don't need to build anything:
- Install LanLink or LocalSend on both devices.
- Connect them to the same Wi-Fi — or have one phone create a hotspot and the other join it (this works with zero router infrastructure, even in a field).
- Both apps discover each other automatically via mDNS. Tap a device, pick a file, send. That's it.
Transfer speed depends on your Wi-Fi hardware, not your ISP. On a decent 5 GHz link I consistently see 40–80 MB/s — which means a 1 GB file moves in about 15 seconds. Over a phone hotspot it's slower (15–25 MB/s typical), but still vastly faster than uploading to a cloud service and downloading on the other side.
When to use a browser tool instead
Despite the limitations, browser-based tools have a genuine advantage: zero install. If you're at someone's house and need to grab a file from their PC, asking them to install an app is friction. Telling them to open a URL is not. PairDrop (the Snapdrop successor) and LocalDrop both work this way — open a page, see nearby devices, send. As long as your files are under a few hundred megabytes and the browser stays in the foreground, they work reliably.