Device Binding
Anti-theft for stolen MAC addresses — pins each device to its first-seen country and a capped set of IPs
Device Binding stops stolen MAC addresses from being used. It sits on top of MAC Protection and targets a different problem: not bots scanning for valid MACs, but a single valid MAC that has leaked and is now being used by someone who shouldn't have it.
MAC rate limiting can't catch this — a thief streaming from one stolen MAC at a normal rate looks exactly like a paying customer. Device Binding catches it by learning where each device normally lives and flagging it when that changes.
How it works
The first time a device successfully authenticates — the panel
returns an authorized profile for its MAC (a 2xx whose body is a real
get_profile/create_link/account_info, not a rejection) — the
gateway records a binding for its MAC: the country it connected from
and the IP it used. A MAC that only loads the static portal page (/c),
or that the panel rejects, is never pinned — so scanners spraying
MAC addresses don't create phantom devices in your Devices list. On
every later request for that MAC, the gateway enforces two rules:
- Country lock — the request must come from the same country the MAC first pinned to. A MAC pinned in Germany that suddenly appears from the Netherlands is blocked. Stolen MAC lists get sold and used worldwide; a real set-top box does not change countries.
- IP cap — a MAC may use at most a set number of distinct IPs inside a rolling window (default 5 IPs / 24h). This catches sharing and re-streaming even within the same country. IPs already inside the window keep working — only a cap-breaking new IP is blocked.
A blocked request gets a 403 Forbidden. The real customer in the
correct country keeps streaming the entire time — only the requests
from the wrong country or beyond the IP cap are rejected.
[!info] Device Binding only acts on paths that MAC Protection already guards (e.g.
/cand the portal API/portal.php), and only when MAC Protection is enabled. It reuses the same MAC extraction and the same country lookup the firewall already does per request — so it adds no extra latency and makes no call to your panel backend.
Start in Monitor mode
When you first turn Device Binding on, every active device pins itself on its next successful login — and at that exact moment, whoever connects first wins the pin. If a stolen MAC's thief happens to connect before the real customer, the thief's country gets pinned. This self-corrects over time, but to avoid a wave of support tickets on day one:
[!warning] Enable Monitor only first and leave it on for a few days. In Monitor mode the gateway records every violation in the Devices tab but blocks nothing. You get the full picture of who would be blocked — travelers, VPN users, border-region customers — before any real traffic is rejected. When the flagged list looks right, turn Monitor off to start enforcing.
Configuration
Sidebar → Firewall → MAC Protection → Device Binding:
| Field | Default | Notes |
|---|---|---|
| Bind devices to country & IPs | off | Master toggle for the feature |
| Monitor only | off | Record violations without blocking — run this first |
| Country lock | on | Block requests from a country other than the MAC's pinned one |
| Max IPs / MAC | 5 | Distinct IPs one device may use inside the window |
| IP window (hours) | 24 | Rolling window for the IP cap |
| Expiry (days) | 30 | Forget bindings idle this long, so the device re-pins fresh |
In config JSON this lives under firewall.mac_protection.binding:
{
"binding": {
"enabled": true,
"log_only": false,
"country_lock": true,
"max_ips_per_mac": 5,
"ip_window_hours": 24,
"expiry_days": 30
}
}Why these numbers?
5 IPs / 24h — A real household churns through a few IPs a day (router reboots, mobile/CGNAT address changes), but not many. Five gives comfortable headroom for one home while still catching a MAC being shared across several locations. Raise it for customers who genuinely roam (mobile-heavy users); lower it to tighten sharing detection.
30-day expiry — A device that goes quiet for a month (returned box, resold hardware, cancelled line) is forgotten, so the next person to use that MAC re-pins cleanly instead of inheriting a stale country. Active devices are never expired — the clock is based on last-seen.
The Devices tab
Sidebar → Firewall → Devices lists every known device and is where you manage bindings:
- MAC and Country (with flag) — the pinned identity of each device
- First seen / Last seen — when it pinned and when it was last active
- IPs (window) — how many distinct IPs it's used inside the cap window
- Status —
OK, or a red flagged badge showing the violation count and the most recent reason (country mismatch or too many IPs), including the offending country/IP
To find devices for one country, type the country code (e.g. DE) in
the search box — it matches both MAC and country. Press Flagged to
show only devices that have had a violation. The Devices tab itself
shows a red count badge when anything is flagged.
Resetting a device
A device's binding is not permanent — click Reset on its row to forget it. The device re-pins on its very next request, adopting whatever country it connects from.
Reset is the fix for the legitimate exceptions:
- A customer who moved or is travelling — reset once, and their own next login pins the new country.
- A MAC that pinned the thief's country first — reset it while the real customer is connected, and it re-pins correctly.
Resets apply instantly (no save/restart needed), the same as IP bans.
Where the data is stored
Device Binding needs no database and no Redis. Bindings live in memory while the gateway runs and are snapshotted to a plain JSON file:
shared-config/mac_bindings.json- Written every 30 seconds and on graceful shutdown.
- Reloaded on startup, so country pins survive restarts and upgrades — a restart does not "un-steal" a MAC.
- Idle bindings are pruned after the expiry window to keep the file bounded.
[!info] This file is separate from
config.jsonon purpose — it's live state, not configuration, so it stays out of config snapshots and the raw config editor. A hard crash (kill -9, power loss) can lose at most the last ~30 seconds of new pins, which simply re-pin on the next request. Always stop the service gracefully (SIGTERM, which the installer's service manager sends) so the final snapshot flushes.
Logging
Binding activity is logged to Sidebar → Logs:
| Log prefix | Meaning |
|---|---|
MAC_BIND | A new device pinned (records mac, country, ip) |
MAC_BIND_BLOCK | Blocked — country mismatch or over the IP cap |
MAC_BIND_FLAG | Violation recorded but not blocked (Monitor mode) |
MAC_BIND_RESET | A binding was reset from the dashboard |
Violating requests are recorded against the device but never change its stored pin or IP list — a thief can't overwrite the real customer's country by hammering the endpoint.
Notes & limitations
- Requires MAC Protection. Binding only runs on protected paths with MAC Protection enabled. A request with no MAC, or a malformed MAC, is left to the MAC Protection rules — binding ignores it.
- Country comes from GeoIP. Accuracy is whatever the bundled MaxMind databases provide. If GeoIP has no answer for the first request, the device pins with an empty country and adopts the first real country it sees afterwards — it won't lock to "unknown".
- VPN users. A customer who connects through a VPN in another country will trip the country lock. Reset them, ask them to disable the VPN, or use Monitor mode if a lot of your users do this. You can also block VPNs outright with VPN/Proxy detection.
Monitoring
- Dashboard — the Devices tab header shows total devices and flagged count.
- API —
GET /internal/firewall/mac-bindingsreturns the binding list with search, flagged filter, and pagination;DELETE /internal/firewall/mac-bindings/{mac}resets one device.