🔗 Project repo: MiniTunnel
Summer vacation was here, and as usual, I connected my hotspot and started playing Mini Militia with my cousins. But every single time, someone was missing out — they just weren't in WiFi range. (Far out of range).
And because I'm a computer engineer (future), I always thought: somehow I should be able to modify this game so it works over the internet instead of just LAN. I'd already worked on a LAN-based project before (SecureChat), so I had decent confidence I could crack this too.
So one day on vacation, I finally sat down and started. And that's how the reverse engineering and deep networking inspection began.
Part 1: Getting Everyone on the Same Network (Over the Internet)
My first thought was to build something using a public IP and a connector server — like a TURN server, the way WebRTC does it. But then I realized something even simpler already existed.
VPN tunnels.
Turns out VPNs aren't just for privacy or accessing stuff banned in your country. A VPN can create a tunnel, and using that tunnel, you can bring multiple devices onto the same virtual LAN (vLAN) — even if they're physically on opposite sides of the country.
So I rented an AWS machine, hosted a WireGuard server on it (you could also use a free VPN provider for this), and tested connectivity with a simple ping.
It worked. Every device could see every other device on the vLAN.
What I thought: Biggest part is done. Everyone's on the same LAN now — let's fire up the game and play, right?
No. This is where the real mess began.
Part 2: The Real Mess
Just having everyone on a VPN wasn't enough. The game still couldn't find anyone.
At this point I had two options:
- Decompile the app and modify the code directly — very hard, and honestly not that ethical either.
- Build a companion app that helps Mini Militia connect over the tunnel.
But before picking a path, I needed to actually understand how players find and connect to a host in the first place.
Sniffing the Traffic
I installed PCAPdroid (basically Wireshark for Android) and started analyzing the game's packets. Here's what I found:
- Some broadcast packets going out on ports in the range
9331–9345(expected, this is how LAN discovery usually works). - One packet sent to another device's IP with a payload containing the server name.
That second packet was clearly the thing to dig into.
Note: Reverse engineering is a process where you inspect something and try to understand its mechanism — and you almost never get it on the first try. Early on I was just moving in some direction without a clear idea. It only became crystal clear later, as the project moved forward.
PCAPdroidonly shows packets sent by the phone, not packets received — a limitation that cost me a lot of confusion later. It also causes connectivity problems because of a multiple-interface conflict (more on that at the very end).
Understanding the Discovery Flow
The core piece of any LAN-discovery app is exactly this: discovery.
Some apps let you type in an IP directly if broadcast discovery fails — Mini Militia doesn't give you that option.
I found the real problem here: the broadcast packets from the game (turns out they're not actually sent by the server — more on that below) weren't passing through the VPN tunnel at all. So other devices had no way to discover the host.
So here's the actual connection flow I mapped out:
- The server listens for connection requests starting on port
9331. If that port isn't free, it increments —9332,9333, and so on — until it finds one open. - When a receiver wants to join, it sends a broadcast packet. It doesn't know the server's port yet, so it broadcasts across the whole
9331–9345range. - The server replies with a payload containing its game name. Now the receiver has the server's IP, and can connect directly.
Two ways to fix this:
- Modify the game so it also broadcasts over the VPN interface.
- Build an app that bridges/relays these broadcast packets onto the VPN's broadcast address.
Option 2 seemed workable, but there was a catch: since the broadcasting happens on the receiver's side (not the server's), this app would need to be installed on every single device.
We could build an app that listens for these broadcasts and forwards them onto the VPN's broadcast address (this is also what most AI tools suggested when I asked).
But I wanted to think outside the box, and I landed on a bolder idea.
The Bold Idea: Skip the Broadcast Entirely
At step 2, the receiver sends a broadcast, and at step 3, the server replies with its name. Up to this point, no real game connection has happened yet — it's just a handshake.
So what if I skip the broadcast part entirely, and just send the server's reply payload directly?
The plan:
I captured the server's reply payload using PCAPdroid, edited the game name inside it using ASCII, connected my laptop to the same VPN, and sent this crafted payload straight to my phone's IP address using a python script.
And it worked: The game name showed up in the joining lobby. I had successfully tricked Mini Militia into thinking a host existed.
My original plan from here was simple: run a script on my WireGuard machine that blasts this crafted packet to every IP on the vLAN, and everyone could join at once.
That didn't work either. Turns out the receiver extracts the server's IP address from the packet header, not from anything inside the payload itself — there was no IP baked into the payload to redirect. So the packet had to originate from the actual host's phone, not from some central relay machine.
So I quickly built a dummy app that sends this crafted packet from the host's phone. The name appeared in the lobby. I clicked join.
It didn't work.
I almost lost hope right there. And at that point, I had no idea why.
Digging Deeper: RakNet
Back to analysis — but PCAPdroid wasn't cutting it anymore. I needed better tools.
I looked into WiFi monitoring mode (interesting concept, honestly, but it didn't help much here). Then I remembered I had a spare router lying around, so I flashed OpenWrt onto it and ran tcpdump from there for deeper inspection.
It captured some of the initial handshake packets, but never the main game connection traffic. This was strange — both devices were connected through the same router, so everything should've passed through it, right?
Note: Apparently not always. Either the devices were finding some way to bypass the router directly, or my router just wasn't capturing that traffic. Still not 100% sure which. But the
tcpdumpcaptures were enough to reveal the actual protocol the game was using.
The protocol was RakNet — the same one Minecraft uses for local multiplayer discovery. Its handshake goes roughly like this:
Unconnected Ping— sent by the receiver, broadcast out.Unconnected Pong— the server's reply, which also carries the game name.Open Connection Request 1Open Connection Reply 1- ...and so on, into the actual game connection, which RakNet handles internally from there.
Steps 1 and 2 are all I actually cared about — that's the part that hands the receiver the server's IP. Everything after that is managed by the game engine itself, and not something I needed to touch.
More Dead Ends
I read that WireGuard and most VPNs operate at Layer 3 (IP-based tunneling), and figured maybe a Layer 2 (bridged) VPN would behave differently and fix the discovery problem. I tried one.
Disappointment again.
I also went back to the bridge-broadcasting-app idea and tried running PCAPdroid alongside it to catch anything useful.
PCAPdroidactually works by running its own local VPN to intercept packets — and having that VPN active at all the time interfered with the game connection.
So I tried a bunch of scenarios — turning PCAPdroid off first and then starting it, running PCAPdroid on both the receiver and the server, different orders, different combinations. Then I opened up the dumped packet file in Wireshark.
The Detail That Cracked the Whole Project
This is the part everything else in this article is really building toward.
I noticed something, very carefully, buried in the captures: every single time the server sent its
Unconnected Pong, it was sent from the exact same source port —9331.That was the key. The port number.
In my dummy app, I was sending my forged pong from a random port. I switched it to
9330, then9331— and it worked. For the first time, I actually connected and played.But then a new mystery: why did it only work once?
Remember the game extracts the server's IP address from the packet header, and it uses that later to connect. So it was also extracting the port from that same header, and the server was listening only on that port. Sending the packet from port
9331specifically was the whole thing.
But worked only once?? Maybe it was a fluke. Maybe it accidentally connected over the hotspot instead of the VPN. Confusing enough that I genuinely wasn't sure if it had "really" worked.
By this point it was 1 AM. I tried again and again — nothing. Eventually I gave up for the night, still turning it over in my head, and fell asleep still thinking about it.
NOTE: In my app, instead of sending a broadcast onto the VPN, I was looping through IPs and sending the packet to each one separately.
Solving It
The next day, it clicked.
Here's what was actually happening: my dummy app was the one sending the forged packet, which meant my app was the one holding port 9331. When I then opened the actual game and clicked "Host," the game tried to grab 9331 too — but it was already taken by my app. So the game fell back to 9332. Port mismatch, right when it mattered most.
If I opened the game host screen first, my app could never grab 9331 at all, and it couldn't send the forged pong.
So the real sequence had to be:
- Send the forged packet from my app (the name appears on the receiver's screen).
- Immediately let the app release port
9331. - Immediately switch to the game and tap "Host" — before the port gets taken by anything else.
- The receiver joins right away — if you wait too long, the name disappears from their lobby list.
Timing had to be almost exact. But it worked. Consistently.
Wrapping Up
That's the project — start to finish, through a mess of dead-end VPN modes, misleading capture tools. and one very small but very important detail: a single port number.
I was genuinely proud of this one. It doesn't modify the game at all, works reliably, and since the underlying trick is really just spoofing a RakNet Unconnected Pong, it isn't specific to Mini Militia at all. The same approach should work for any game or app using the RakNet protocol for local discovery.
The full project is up here: MiniTunnel
Why Plain VPN Alone Wasn't Enough
At the time of writing this article, I went back and resumed my unfinished SecureChat project (the Windows app piece was still pending) — and while working on that, I finally figured out why just running a VPN never solved the discovery problem on its own.
When you add a VPN, your device ends up with multiple network interfaces. On a laptop, for example, you typically have:
- a WiFi interface
- a LAN interface
- a virtual interface for the VPN
- etc.
The same thing happens on mobile — you end up with at least two interfaces, one for WiFi and one for the VPN. And here's the catch: the broadcast packet gets sent out on the WiFi interface, not the VPN interface. So on the VPN side, no broadcast packets are ever received at all. That's the actual root cause.
My SecureChat project was making this exact same mistake. So I'm now adding an interface selector to that app, and also thinking about adding a direct-IP connection option as a fallback for whenever discovery fails.