← Blog

Fire TV killing your Wi-Fi? The mDNS storm bug, and how to fix it

Every access point in the house went unusable at once — pages timing out, video stuck at 240p, 46–60% packet loss even pinging the gateway one hop away — while every wired client stayed perfectly fine. The cause turned out to be a single Fire TV Stick, a router answering one of its queries with an empty packet, and a Fire OS bug that has been open and unfixed since July 2026: no backoff on that empty reply, so the device just keeps asking, forever, at close to 300 packets a second.

Confirm it in ten seconds from any Linux/Mac box on the same network — if this fills up almost instantly (normal mDNS traffic is a few packets a minute, not thousands in ten seconds), this is your problem:

sudo tcpdump -i <iface> -nn -e -c 3000 port 5353

Fastest fix — no rooting, about two minutes

The Fire TV is only reacting to a bad answer from somewhere else on the network. Fix that, and the flood stops on its own:

  1. Turn off (or remove from mDNS duty) whatever device is answering with the empty packet. In our case that was a FRITZ!Box used only as a Wi-Fi repeater, easily replaced. This alone dropped the flood from ~300 pps to ~1 pps. The Fire TV doesn't save the hostname anywhere — it only ever learns it live from that device's own mDNS announcements, so no announcements means nothing to chase.
  2. Can't remove that device? Run a small always-on responder that answers the Fire TV's query properly instead, with a standard negative reply ("this name has no AAAA record") followed by the correct A record. The Fire TV caches that and stops asking. Verified: 12 responses silenced 881 queries. It needs to keep running continuously — the negative-cache TTL is 120 seconds, so a one-off reply isn't enough — on any always-on Linux box on the LAN (a Raspberry Pi, a router running OpenWrt, anything). Python 3, standard library only, no dependencies:
#!/usr/bin/env python3
"""Answers a Fire TV's repeated mDNS AAAA query for a name with a proper
negative response (NSEC), instead of leaving it to an empty/bad reply that
Fire OS never backs off from.

Usage:  mdns-nsec-responder.py <your-interface-ip> <name-being-queried> <that-name's-ip>
Example: mdns-nsec-responder.py 192.168.1.5 192-168-1-1.fritz.box 192.168.1.1

Find <name-being-queried> by watching tcpdump output above for the AAAA
query the Fire TV keeps repeating (it names the mDNS record it wants).
"""
import socket, struct, sys, time

IFACE_IP = sys.argv[1]
NAME = sys.argv[2].lower().rstrip(".")
NAME_IP = sys.argv[3]

TYPE_A, TYPE_AAAA, TYPE_NSEC = 1, 28, 47
CLASS_FLUSH_IN = 0x8001
MIN_INTERVAL = 0.25   # don't become the storm ourselves
TTL = 120


def encode_name(name):
    out = b""
    for label in name.split("."):
        out += bytes([len(label)]) + label.encode()
    return out + b"\x00"


def decode_name(data, off):
    parts = []
    while True:
        ln = data[off]
        if ln == 0:
            off += 1
            break
        if ln & 0xC0 == 0xC0:
            ptr = struct.unpack("!H", data[off:off + 2])[0] & 0x3FFF
            parts.append(decode_name(data, ptr)[0])
            off += 2
            break
        parts.append(data[off + 1:off + 1 + ln].decode("utf-8", "replace"))
        off += 1 + ln
    return ".".join(parts), off


def rr(name, rtype, rdata):
    return encode_name(name) + struct.pack("!HHIH", rtype, CLASS_FLUSH_IN, TTL, len(rdata)) + rdata


def build_response(name, ip):
    # NSEC bitmap: window 0, 1 byte, only the A-type bit set
    # -> this name has an A record but no AAAA
    answer = rr(name, TYPE_NSEC, encode_name(name) + bytes([0, 1, 0x40]))
    additional = rr(name, TYPE_A, socket.inet_aton(ip))
    return struct.pack("!HHHHHH", 0, 0x8400, 0, 1, 0, 1) + answer + additional


def main():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(("", 5353))
    s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255)
    s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(IFACE_IP))
    s.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP,
                 socket.inet_aton("224.0.0.251") + socket.inet_aton(IFACE_IP))
    s.settimeout(1.0)

    response = build_response(NAME, NAME_IP)
    last_sent = 0.0
    while True:
        try:
            data, _ = s.recvfrom(9000)
        except socket.timeout:
            continue
        except OSError:
            time.sleep(1)
            continue
        try:
            if struct.unpack("!H", data[4:6])[0] == 0:   # no questions: it's a reply, ignore
                continue
            qname, off = decode_name(data, 12)
            qtype = struct.unpack("!H", data[off:off + 2])[0]
        except Exception:
            continue
        if qtype != TYPE_AAAA or qname.lower().rstrip(".") != NAME:
            continue
        now = time.time()
        if now - last_sent < MIN_INTERVAL:
            continue
        last_sent = now
        try:
            s.sendto(response, ("224.0.0.251", 5353))
        except OSError:
            pass


if __name__ == "__main__":
    main()

Run it in the foreground first to confirm it's working, then keep it running (a simple systemd unit is enough on most Linux boxes):

sudo python3 mdns-nsec-responder.py <your-lan-ip> <name-from-tcpdump> <that-name-ip>
# /etc/systemd/system/mdns-nsec-responder.service
[Unit]
Description=mDNS NSEC responder for the Fire TV storm bug
After=network.target

[Service]
ExecStart=/usr/bin/python3 /opt/mdns-nsec-responder.py <your-lan-ip> <name> <name-ip>
Restart=always

[Install]
WantedBy=multi-user.target

On OpenWrt, wrap it as a procd service instead — same script, started with procd_set_param respawn so it survives crashes and reboots.

Neither of these two fixes touches the Fire TV itself. That's covered next, for anyone who wants the flood gone permanently rather than routed around.

Why this happens

The Fire TV Stick was repeatedly querying the AAAA record for a *.fritz.box hostname — the self-assigned name a FRITZ!Box router announces over mDNS. The FRITZ!Box was answering with empty mDNS packets:

192.168.1.1.5353 > 224.0.0.251.5353: 0* [0q] 0/0/0 (12)

Zero questions, zero records — not a valid answer, and not silence either. Fire OS doesn't treat that as a real response and applies no backoff: it retransmits at roughly 180 queries/second. Multicast frames are sent at the lowest common wireless rate to every client on the segment, so ~300 pps of multicast is enough to saturate 2.4GHz airtime for the whole network, while wired gigabit clients never notice.

This is a known, Amazon-acknowledged Fire OS bug, reported July 28, 2026, still unfixed as of this write-up.

A prediction that turned out wrong: Amazon's own report says the flood starts "when no response is available," which we initially read as meaning removing the FRITZ!Box would make things worse. It didn't — that line describes the loop after it starts, not how it's triggered. Without the FRITZ!Box announcing that hostname at all, the Fire TV never has anything to chase in the first place. Measuring beats guessing.

What we tried that didn't work

AttemptResult
Uninstalling apps from the Fire TVNo effect — it's a system component
am force-stop / pm clear on com.amazon.whisperlink.core.androidRefused — protected package
Disabling IPv6 on the FRITZ!BoxResponses went from useless fe80:: to empty — same 300 pps
Rebooting the FRITZ!BoxStorm resumed immediately
Publishing the name via avahiavahi doesn't answer for names outside .local
Filtering at the routerTraffic never crosses it — stays link-local between the Fire TV and the FRITZ!Box
NSEC responder without an A recordNot enough on its own — the trailing A record is required

The two quick fixes, measured

mDNS rateWi-Fi packet loss
Baseline (storm active)~300 pps46–60%
Offending device powered off~1 pps0%
NSEC responder running continuously~0.1 pps0%

Permanent fix — remove it from the Fire TV itself (advanced, requires temporary root)

Both mitigations above treat the symptom. The actual source is on the Fire TV: a component of Amazon's WhisperLink/WhisperPlay stack does the querying, and it's a protected system package — normal ADB can't touch it.

Device identification matters

There are two different "Fire TV Stick 4K Max" devices with very similar names: karat (2nd gen, 2023, Fire OS 8.x) and kara (1st gen, 2021, Fire OS 7.x, MediaTek SoC). Public root tooling for one does not apply to the other — confirm the exact codename and build number with getprop before assuming a guide applies.

Temporary root, then targeted removal

We used a public, build-specific fork of the GhostLock kernel exploit (CVE-2026-43499), pinned to our exact firmware (PS7713.5443 / build 0035334210436), to obtain temporary root — it disappears on the next reboot, no bootloader unlock, no partition writes. That temporary root was then used, directly, to remove just the three packages responsible:

pm uninstall -k --user 0 com.amazon.whisperlink.core.android
pm uninstall -k --user 0 com.amazon.whisperplay.service.install
pm uninstall -k --user 0 com.amazon.whisperjoin.middleware.np

com.amazon.whisperplay.contracts was left in place deliberately, for compatibility. Package removal is Android package-manager state, not a root artifact — it survives reboots even though the root session that performed it doesn't.

What tripped us up along the way

Verifying the fix

sudo tcpdump -i <iface> -nn -c 3000 port 5353
mDNS traffic (15s window)
Before~4,500 packets, dominated by the Fire TV
After3 packets total, none from the Fire TV

Neither the FRITZ!Box nor the NSEC responder are needed anymore. The query simply never gets generated in the first place.

Known caveat, not yet tested

Removing the WhisperLink stack may break device discovery for phone-to-TV casting/screen-mirroring, and Alexa multi-room audio routing to this specific Stick. We kept whisperplay.contracts in place for baseline compatibility, but haven't yet exercised those specific features against the change.

For Amazon

This isn't a proper upstream fix — it's package removal on a temporarily-rooted device, not a patch to the closed-source WhisperLink binary, and it needs redoing after a factory reset. We don't have the WhisperLink source, so we can't send an actual diff. What we can send is the exact behavior that's wrong and the minimal fix for it:

on mDNS response received:
    if response.questions == 0 and response.answers == 0:
        # this is an empty reply, not "no reply" —
        # today's code treats it as neither and just retries
        treat_as(NXDOMAIN)
        apply_negative_cache(ttl=120s)
        stop_retransmitting()
    else:
        normal_backoff_logic()

The bug is specifically that an empty (zero questions, zero answers) mDNS packet doesn't get classified as a negative response, so the retry loop never triggers its own backoff. Backing off on empty replies the same way it already must for NSEC replies should be a small, self-contained change. We're linking this write-up from the existing Amazon Developer forum report in the hope it helps someone triage it.

How this was built, honestly

As with the other write-ups on this site, a significant part of the investigation notes and this text were put together with LLM assistance (Claude, through Claude Code). The packet captures, the physical access to the router and the Fire TV, and the decisions about what to actually run on the device were mine, under my authorisation for every root/write action taken against the hardware.


References
Amazon Developer forum report (2026-07-28)
Delitants/firetv-kara-standalone — kara-specific root tooling
R0rt1z2/GhostLock — CVE-2026-43499 kernel exploit (karat/sunstone)