Wi-Fi, Bluetooth and DNS
Wi-Fi sign-in pages, Bluetooth audio that crackles and the fastest DNS for each network.
Bluetooth audio: why AirPods crackle
While AirPods or other Bluetooth headphones or speakers play, Kilobat checks every five seconds what on the Mac’s side can make them crackle or cut out. Bluetooth hops across the 2.4 GHz band and shares a radio with Wi-Fi, so it looks at: the Mac’s Wi-Fi on 2.4 GHz; that channel crowded by neighbours’ networks (from a Wi-Fi scan in the last half hour; Kilobat never scans while you listen, since a scan can itself make audio skip); AirDrop’s link (AWDL) busy, and whether AirDrop, Universal Control or Sidecar keeps it so; 2.5 MB/s or more moving over Wi-Fi, with the busiest app; the headphones’ Bluetooth signal at −72 dBm or weaker; and four or more other Bluetooth devices connected. It reads macOS’s sound output from Core Audio, which needs no permission, and notes when the headset’s microphone is on too: macOS then uses its lower-quality hands-free mode, which breaks up sooner. The signal and the other devices need Bluetooth permission, asked only when you press Check Bluetooth.
When something is in the way, Health shows a line with what to change, most effective first: join your router’s 5 GHz network (with a button when macOS remembers one in range, also when it has the same name as the 2.4 GHz one), set the router to the quietest of channels 1, 6 and 11, pause AirDrop (a button; macOS asks for your password, and AirDrop comes back when the sound stops for five minutes or after an hour), pause the big download, and move closer or keep the Mac out of your bag. On a call it also suggests the Mac’s own microphone, which keeps the headphones in high quality. Kilobat can’t hear the sound itself: macOS doesn’t tell other apps when Bluetooth audio drops out, so the advice comes from conditions and says so. Press It’s crackling, in that line or in Health’s Nearby panel, to tell Kilobat; it keeps what was in the way then, and when nothing was, says distance or the headphones’ own link is the likelier cause. One notification per listening session, only when two or more causes line up for 20 seconds and at most every four hours; turn it off in Settings › Monitoring. All of this is free. With Pro, a call heard through Bluetooth headphones while the Wi-Fi was on 2.4 GHz or AirDrop was scanning gets a line on its report card. In the terminal, kilobat bluetooth audio runs the same check (with the app running it adds the signal and the busiest app), and agents read it through get_bluetooth_audio.
What your Wi-Fi link negotiated
Health › The local link shows what your Mac and the router agreed on, beside the link speed: the channel, band and channel width, the Wi-Fi standard (Wi-Fi 4, 5, 6, 6E or 7, with its 802.11 name), the security, such as WPA2 Personal or WPA3 Personal, and how strongly your Mac transmits. An old standard or a 20 MHz channel on 5 GHz caps the link speed however strong the signal is.
Security shows in red, with a line saying what it means, on an open network (no password and no encryption on the Wi-Fi itself; sites and apps that use HTTPS stay encrypted), and on WEP or the first WPA, which are outdated and slow Wi-Fi down. Enhanced Open encrypts without a password. Nearby networks under Radar show the strongest security each one offers. In the terminal, kilobat wifi status prints the same, and --json adds widthMHz, standard, phyMode, security, securityLevel (strong, good, weak or open), securityNote, transmitPowerDBm and countryCode, when CoreWLAN provides them (MCP: get_wifi). kilobat scan --json and scan_wifi add each network's security and securityLevel.
Wi-Fi sign-in pages
Hotel, café, airport and coworking Wi-Fi often holds every connection until you sign in on its own page, and logs you out again after an hour or a day. Kilobat checks for that page the way macOS and Android do: a plain http request to captive.apple.com, which answers “Success”, and if that fails, Google’s connectivitycheck.gstatic.com/generate_204. A redirect or any other page means a sign-in page is in the way, and Kilobat keeps that page’s address, whether the network sends it as a redirect, a meta refresh or a script. It checks when you join a network, when the internet check fails and while a sign-in page is up, and otherwise every 5 to 15 minutes, never in a tight loop. Nothing is sent anywhere but those two addresses.
It tells three cases apart: no internet at all, a network that wants you to sign in, and a network that logged you out (you were signed in on it and the same sign-in page came back). The menu bar popover shows one row for it: “This Wi-Fi wants you to sign in” with Open sign-in page, or “Logged out by the Wi-Fi’s sign-in page” with Sign in again. If you named the place on the This place card, the row uses that name, such as “Logged out by The Long Table’s Wi-Fi”. The header still says Login needed, and the Wi-Fi menu says Logged out · sign in again. Kilobat never opens a browser on its own, and it leaves the sign-in window macOS shows when you join a network alone. It is there for when that window doesn’t appear, and for later.
While a sign-in page is in the way, Health gives that one cause, “Logged out by the Wi-Fi’s sign-in page” with Sign in again or “Network login needed” with Open login page, and stops blaming DNS, ping loss or your provider. The event log records a Wi-Fi sign-in rather than an outage, so it doesn’t count as an outage in the provider report or set off the Connection unreachable alert. A call that froze then says it froze after the Wi-Fi’s sign-in page logged you out. The stutter test to the internet says the sign-in page is holding the connection, not that nothing replied. Why can’t it connect? and kilobat why say the Wi-Fi’s sign-in page is in the way instead of reporting DNS or TLS errors.
Kilobat also learns how long a sign-in lasts on each network. It notes when a sign-in starts, when the check goes from the sign-in page to Success, and when the sign-in page comes back. The usual length is the median of the sessions it saw from start to end without a break, at least two of them. One session is enough when the sign-in page itself names exactly one length, such as “1 hour of free Wi-Fi” (English pages only). A session cut short by sleep, leaving the network or reconnecting doesn’t count, and each one ends at the last check that went through, so the estimate errs short. The history is kept per network, with the This place name, in ~/Library/Application Support/Kilobat/captive-portals.json on this Mac, and never sent anywhere.
With Kilobat Pro, the row then reads “Signed in · about 40 min left”, and about five minutes before a sign-in usually runs out, a notification says “Café Wi-Fi will log you out in 5 min” (or “The Wi-Fi at The Long Table will log you out in 5 min” for a place you named), with Open sign-in page. If it logs you out mid-session anyway, “Logged out by Café Wi-Fi” says your internet isn’t down, with Sign in again. You get one warning per sign-in and at most one of these notifications every 20 minutes, and none when you join a network, where macOS shows its own window. Turn them off in Settings › Monitoring › Warn before Wi-Fi sign-ins run out.
Spotting the sign-in page, the row, Open sign-in page and the logged-out cause in Health are free; Health’s full list of findings stays part of Pro. Sign-in lengths are recorded for everyone, and the time left and the warnings are part of Kilobat Pro. In the terminal, kilobat portal checks now and prints the state (open, portal or offline), the sign-in page’s address with query values redacted, this network’s status, its usual sign-in length and the history for each network: its name, the This place name, the sign-in page’s host, the sessions seen, their usual length and when it was last seen. kilobat portal open opens the sign-in page in your browser. Agents read the same JSON through get_captive_portal. For a sign-in page that never appears, see the hotel Wi-Fi login page.
Can I cast or print here?
A few seconds after you join a network, and when you click Check again in Health, Kilobat looks for what you can cast or print to: Chromecast and TVs with Chromecast built in (_googlecast._tcp), Apple TV, AirPlay TVs and speakers (_airplay._tcp, _raop._tcp) and printers (_ipp._tcp, _ipps._tcp, _printer._tcp), for three seconds over Bonjour, leaving out this Mac’s own AirPlay Receiver. When something answers, Health’s Casting and printing panel and the Wi-Fi menu say “Casting and printing should work: found Living Room TV, HP LaserJet”.
When nothing does, Kilobat checks whether other devices answer at all: one empty packet to each private address of the /24 around the Mac, the neighbour cache, one ping to up to six neighbours, and Continuity records. If addresses answer with the router’s hardware address (proxy ARP), it says the network may keep devices apart. Proxy ARP also supports ordinary routing, so this is a possible cause, never proof of blocked casting or printing. An empty sweep, a large subnet or a sign-in page alone cannot establish isolation. Otherwise it says “No casting devices seen”, with what answered and what remains uncertain. On hotel or café Wi-Fi the verdict also appears beside the sign-in row. --quick performs a fresh Bonjour browse and reads the cache without a peer sweep or peer pings. Bonjour and peer probes stay on the local network; when the app cannot answer, the standalone CLI also runs the usual internet-reachability and sign-in-page checks to recognise device setup Wi-Fi. macOS asks for Local Network access the first time.
When the Mac joins a device’s own Wi-Fi with no internet beyond it, such as a printer’s Wi-Fi Direct (DIRECT-xx-HP …, HP-Setup…, Brother’s …_BRxxxx, Canon’s …series), a Sony or Canon camera’s, a GoPro’s (GP and eight digits), an Insta360 or Ricoh Theta (….OSC), a DJI Tello or Spark, or a Shelly, Tasmota, Kasa, Tapo or WiZ setup network, Kilobat says “You’re on your printer’s own Wi-Fi; there’s no internet here, by design.” The popover title reads Device Wi-Fi, there’s no Connection unreachable alert, the event log notes Device Wi-Fi instead of an outage, and Back to rejoins the Wi-Fi you were on with the password macOS saved. The names come from the makers’ manuals, and a network with a matching name that does reach the internet is treated as ordinary Wi-Fi.
While the Mac AirPlays (the sound output is an AirPlay receiver, or macOS’s AirPlay sender streams on the local network) or a Chromium browser sends enough local traffic to suggest a possible cast, Health shows what on the Mac’s side can make it stutter: the Mac on 2.4 GHz, a weak signal (−70 dBm or weaker), AirDrop scanning (not when AirPlay itself uses AirDrop’s link), and another app moving 2.5 MB/s, or uploading 1.25 MB/s. Buttons join a remembered 5 GHz network, pause AirDrop until the cast ends or an hour passes, and with Kilobat Pro pause sync and download apps as call mode does. Kilobat can’t see dropped frames or the TV’s own signal, and says so. In the terminal: kilobat local check [--quick] [--json]; for agents, the MCP tool check_local_network.
Stuck on the far mesh node
A Mac leaves a Wi-Fi access point only once the signal drops to about -75 dBm and another is 12 dB stronger, so on a mesh system it often stays on the node it joined downstairs while the one in your room sits unused: the classic “Wi-Fi is slow upstairs”. Kilobat notices and says so in Health: “You’re on the Living room node at -78 dBm; the Office node is at -52 dBm.” Reconnect, beside it and in the Wi-Fi menu, moves the Mac: it joins the closer node directly with the network’s saved password, and if that doesn’t take, turns Wi-Fi off and on so macOS picks the strongest node itself. Wi-Fi drops for a few seconds, so during a call Kilobat asks first. Afterwards it says where the Mac ended up: “Now on the Office node at -52 dBm.”
It speaks up only when your signal is weaker than -65 dBm, another node of the same network is at least 15 dB stronger on a band your Mac supports, and that node promises at least one and a half times the link rate, an estimate from signal and channel width. That last rule keeps band steering quiet: a strong 2.4 GHz radio is no improvement on a decent 5 GHz link. It has to last two minutes, over at least three readings, with your own signal steady, so walking around the house or a roam in progress never counts. Kilobat reads the radio with its usual background check and scans for your network at most every two minutes, only while the signal is weak and never during a call. Radios are grouped into nodes by their addresses; in Health › Wi-Fi nodes you can name each one, such as Office, and the findings use the names. A call report names the far node when a freeze happened there (Pro). With Settings › Monitoring › Tell me when the Mac stays on a far Wi-Fi node, you get one notification after five minutes, at most every six hours. When it happens three times in two weeks, Kilobat suggests your mesh system’s steering or roaming settings, or moving the nodes so they overlap less.
macOS shows access point addresses only to apps with Location access, so this needs it; your location stays on the Mac. Joining and turning Wi-Fi off and on work without an administrator password unless Wi-Fi Settings › Advanced asks for one. In the terminal, kilobat wifi nodes lists the nodes, kilobat wifi nodes name <id> Office names one and kilobat wifi reconnect moves the Mac (CLI 0.31); agents read get_wifi_nodes and ask before calling reconnect_wifi.
The fastest DNS for each network
The DNS that’s fastest at home is often not the fastest at the café. Kilobat finds out per network, quietly, and with Kilobat Pro switches to it when you join. Health › DNS shows the result: “Fastest here: Quad9 (12 ms vs your network’s 48 ms)”, or “Your network’s DNS is fastest here”, with every resolver from the latest run.
What it compares. Your network’s own DNS, the one the router or provider hands out, against Cloudflare (1.1.1.1), Quad9 (9.9.9.9) and Google (8.8.8.8), and any resolver you add by its IP address in Settings › Network › Resolvers to try. Leave one out there and Kilobat never times or picks it. Each resolver comes with a line on what it keeps: Google, for one, keeps your IP address for up to 48 hours. A faster resolver isn’t a more private one.
Who runs it, and where. Each resolver shows its operator and the country whose law it operates under, from the operator’s own pages: Cloudflare · United States, Quad9 · Switzerland, Google · United States, and for addresses you add, AdGuard · Cyprus, OpenDNS (Cisco) · United States and DNS4EU · Czechia. Your network’s own DNS is “your provider’s”, and an address Kilobat doesn’t know is “unknown operator”: it never guesses from an address.
How each one holds up. Under the latest run, Health › DNS shows each resolver’s last 14 days on this network, one square a day: the share of its lookups that were answered, with days that failed some or answered slowly (over 200 ms) marked. It comes from the same quiet runs, so it costs nothing extra.
How it measures. Up to three times a day per network, at least four hours apart, while your Mac’s own traffic is light, Kilobat asks each resolver directly for ten popular sites twice: the first answer is what a site you haven’t opened lately waits for, the second the resolver’s own round trip. It also asks for a name that can’t exist, to catch a resolver that answers it anyway (NXDOMAIN rewriting, usually to show ads), and for three sites served from CDNs, then times a TCP connection to the address each resolver handed out against the one your network’s DNS gave. A resolver that sends you to a CDN node clearly further away is penalised three times the extra connect time, since every connection pays it on the handshake, TLS and the first request: fast lookups that make browsing slower don’t win. A run is about two dozen small queries per resolver and a few connections, a few kilobytes. It never runs on a metered network or phone hotspot, through a VPN, behind a Wi-Fi sign-in page or in Low Power Mode. Test now runs one straight away.
When it picks one. Only when a resolver was at least 20 ms faster than your network’s own at the median, with a lower 90th percentile, in each of its latest runs on at least three different days. Until then your network’s own DNS stays. Once picked, it stays while it’s still ahead at all, and gives way to the network’s own once it hasn’t been ahead on three days, or at once when its lookups fail. In at 20 ms, out at 0, so it never flips back and forth.
Switching. Turn on Use the fastest DNS on this network in Health › DNS, or for every network in Settings › Network. Kilobat sets the network service’s DNS through its helper, the same one that keeps the Mac running with the lid closed, so it needs no password each time; you allow the helper once in Login Items. It never switches while a VPN is connected, while a configuration profile or an app’s encrypted DNS decides lookups, or while a Wi-Fi sign-in page is waiting, since the sign-in page needs the network’s own DNS. DNS servers you set yourself are never touched. Kilobat only ever undoes its own change, and does: when a VPN connects, when you leave the network, when Kilobat quits, and when lookups fail twice in a row while the network itself answers (that resolver then waits a day on that network). Pick a fallback under If it fails, use in Health › DNS or Settings › Network › Fallback DNS, and a failing resolver hands over to it instead, straight away and only if it answers right then; if the fallback fails too, your network’s own DNS comes back, so the Mac is never left without working DNS. Each switch is in History and shows in the Wi-Fi menu as “DNS: Cloudflare (fastest here)” with Undo, which puts the network’s DNS back and turns switching off for that network.
Measuring and the result are free; switching on its own is part of Kilobat Pro. In the terminal, kilobat dns fastest shows the result for this network (--all for every network, --json for scripts), kilobat dns auto on|off [--network <name>] turns switching on or off, kilobat dns exclude|include <resolver> leaves one out or adds your own, and kilobat dns fallback <resolver>|off sets the fallback. The output names who runs each resolver and where, and each one’s answered lookups over 14 days. Agents read it with get_fastest_dns; set_fastest_dns and set_dns_fallback ask you first.