{"id":52064884,"url":"https://github.com/jedbillyb/airdrop-mt7921","last_synced_at":"2026-08-03T01:01:58.643Z","repository":{"id":373962639,"uuid":"1317772344","full_name":"jedbillyb/airdrop-mt7921","owner":"jedbillyb","description":"AirDrop on Linux using the built-in MediaTek MT7921 Wi-Fi chip, no USB adapter. Receiving from an iPhone works end to end.","archived":false,"fork":false,"pushed_at":"2026-07-31T02:05:31.000Z","size":119,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-31T04:05:18.671Z","etag":null,"topics":["airdrop","awdl","linux","mediatek","mt7921","reverse-engineering","wifi"],"latest_commit_sha":null,"homepage":null,"language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jedbillyb.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null,"disclosure":null}},"created_at":"2026-07-31T00:00:54.000Z","updated_at":"2026-07-31T02:05:35.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jedbillyb/airdrop-mt7921","commit_stats":null,"previous_names":["jedbillyb/airdrop-mt7921"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/jedbillyb/airdrop-mt7921","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jedbillyb%2Fairdrop-mt7921","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jedbillyb%2Fairdrop-mt7921/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jedbillyb%2Fairdrop-mt7921/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jedbillyb%2Fairdrop-mt7921/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jedbillyb","download_url":"https://codeload.github.com/jedbillyb/airdrop-mt7921/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jedbillyb%2Fairdrop-mt7921/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":36213603,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-08-02T02:00:06.915Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["airdrop","awdl","linux","mediatek","mt7921","reverse-engineering","wifi"],"created_at":"2026-08-03T01:01:58.241Z","updated_at":"2026-08-03T01:01:58.634Z","avatar_url":"https://github.com/jedbillyb.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# airdrop-mt7921\n\nAirDrop on Linux using the **built-in MediaTek MT7921** Wi-Fi chip, with no USB\nadapter.\n\nSending to **and** receiving from an iPhone on iOS 26 both work, end to end,\nwith no Apple ID and no signed identity. This repo is the script that sets it up,\nthe OpenDrop patches that make iOS 26 transfers parse and send, and the research\nthat got there.\n\n\u003e **Unsupported personal fork / personal project.** This is my own work, done on\n\u003e my own laptop, published in case it is useful. It is not affiliated with the\n\u003e Open Wireless Link project, OpenDrop, or Apple. There is no support, no\n\u003e warranty, and no promise that it works on your hardware. Issues and PRs may\n\u003e sit unread.\n\n## Documentation\n- [docs/FINDINGS.md](docs/FINDINGS.md) - numbered session-by-session findings log\n- [docs/NOTES.md](docs/NOTES.md) - project notes\n- [patches/README.md](patches/README.md) - what each opendrop patch does and why\n- [tools/README.md](tools/README.md) - diagnostic tool scripts\n\n## Why this exists\n\nEvery guide for AirDrop on Linux tells you the same thing: you need a card with\nworking **active monitor mode**, and in practice that means buying an Atheros\nAR9271 or AR9280 USB adapter. Upstream OWL's README says it outright.\n\nThe MT7921 was believed not to qualify. An active monitor vif on this chip is\npinned to 5180 MHz no matter what you ask for, and appears to destroy reception\nwhile it is up.\n\nBoth halves of that turn out to be wrong, and the way around it is a\nconfiguration, not a patch:\n\n**Create a plain monitor vif first and tune it. Then add the active vif\nalongside it.** They share one channel context. The active vif comes up on the\nplain vif's channel with no reception penalty, and retuning either one moves\nboth - so you keep hardware ACKs *and* AWDL channel hopping.\n\nThat is the whole trick. Details, including the two false conclusions I reached\nbefore finding it, are in [docs/FINDINGS.md](docs/FINDINGS.md) §13-§14.\n\n## Status\n\n| | |\n|---|---|\n| Receiving from an iPhone | **works** (iOS 26, proven end to end) |\n| Sending to an iPhone | **works** (iOS 26, `POST /Upload -\u003e 200`, file delivered - see [FINDINGS §37](docs/FINDINGS.md)) |\n| Receive throughput | ~40-45 kB/s with `-S verbatim` - ~22 kB per availability window, ~1.8 windows/s ([§18](docs/FINDINGS.md)) |\n| Send throughput | not yet measured - the proving run sent a 68-byte file; needs a real file + `tools/bursts.py` |\n| Hardware tested | MT7921 (Filogic 330), Void Linux, kernel 6.18.33 |\n\nThe auth wall that everyone warns about was never reached, in **either**\ndirection. In **Everyone** mode an iPhone both accepts an unsigned receiver and\naccepts an upload from an unsigned sender - no Apple ID, no push token, no signed\nvalidation record. Contacts-only would need an Apple-key-signed validation record\nand is genuinely not forgeable.\n\n**What made sending work** ([§37](docs/FINDINGS.md)): the whole block was one\nmissing field. Our `/Ask` never declared a `TransferID`, but the `/Upload` header\nasserted a fresh one, so the phone had no accepted transfer to bind the upload to\nand refused it on the headers before reading the body. A real iOS 26 sender\nannounces `TransferID={'id': UUID}` in its `/Ask` body and repeats the same id on\n`/Upload`. Do the same and the phone takes the file. It was never an auth wall.\n\n## Requirements\n\n- An MT7921 (or likely any `mt76`) card. Others may work; nothing here is\n  MT7921-specific except the two driver workarounds below.\n- `iw`, `tcpdump`, `libpcap`, `libev`, `libnl`\n- A patched OWL build - see below\n- OpenDrop, patched with `patches/opendrop-ios26-airdrop.patch`\n- Root, and a willingness to lose networking for the duration of a run\n\n## Setup\n\n**1. Build the patched OWL.** Upstream OWL will sync but will not give you good\nthroughput; my fork adds the two mt7921 workarounds and the channel-sequence\nfix:\n\n```sh\ngit clone https://github.com/jedbillyb/owl.git ~/owl\ncd ~/owl \u0026\u0026 cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \u0026\u0026 cmake --build build\n```\n\n**2. Install and patch OpenDrop.** Stock OpenDrop cannot complete an iOS 26\ntransfer in either direction. The patches are applied in order, in place, inside\nthe venv - see [patches/README.md](patches/README.md) for what each one does and\nwhy:\n\n```sh\npython -m venv ~/owl/.venv-opendrop\n~/owl/.venv-opendrop/bin/pip install opendrop\ncd ~/owl/.venv-opendrop/lib/python*/site-packages\nfor p in ios26-airdrop recv-window py314-send mdns-repeat find-report \\\n         tls-keylog upload-arms; do\n  git apply /path/to/airdrop-mt7921/patches/opendrop-$p.patch\ndone\n```\n\nThe first two make **receiving** work; the rest make **sending** work\n(`py314-send` unbreaks the send path on modern Python, `mdns-repeat` gets the\nphone to answer, `find-report` hands the receiver to `send`, `tls-keylog` makes\nfailures decryptable, and `upload-arms` carries the `TransferID` fix that\ndelivers the file). Void has no `patch(1)`; `git apply` is what the patches are\nverified against.\n\n**3. Run it.**\n\n```sh\n./airdrop.sh receive          # advertise this machine as an AirDrop target\n./airdrop.sh                  # discover nearby devices only\n./airdrop.sh send \u003cfile\u003e      # send to a phone\n```\n\nOn the phone, always: **Settings → General → AirDrop → Everyone for 10 Minutes**\n(this expires - re-arm it). The rest depends on direction, because the phone\nplays opposite roles:\n\n- **Receiving** (`./airdrop.sh receive`): the phone is the *sender*. **Open a\n  share sheet and leave it open** - that wakes its AWDL and lets it query for you.\n- **Sending** (`./airdrop.sh send \u003cfile\u003e`): the phone must be the *receiver*, so\n  **do not open a share sheet** (that puts it in sender mode, where it queries but\n  never advertises). Just keep the phone unlocked and awake. The script wakes the\n  phone's AWDL over Bluetooth LE itself (see below), then discovers it.\n\n**The Bluetooth LE bootstrap.** Apple bootstraps AirDrop discovery over BLE: a\nsender broadcasts a Continuity advertisement, and that is what wakes a nearby\nreceiver's AWDL interface. For *receiving*, opening the share sheet is how a user\nforces the phone's AWDL up. For *sending*, `tools/blewake.sh` emits that\nadvertisement for us - and `airdrop.sh send` now starts it automatically, because\nthe MT7921 is a combined Wi-Fi/BT chip and reconfiguring Wi-Fi resets the BT\ncontroller, so the advert has to come up *after* the radio is set\n([§36](docs/FINDINGS.md)).\n\nMeasured on this hardware, sweeping all five AWDL social channels:\n\n| phone state | AWDL frames heard |\n|---|---|\n| share sheet open | sync established, peer found |\n| Control Centre only | **zero, on every channel** |\n\nSo Control Centre is not enough, and a phone sitting locked on a desk is\ninvisible no matter what AirDrop is set to.\n\n### Which phone am I sending to?\n\n`AirDrop → Everyone` makes *every* Apple device in range a candidate, and the\ndefault `-r 0` picks whichever answered mDNS first. Check the discovered list the\nscript prints, then select by name rather than position:\n\n```sh\nRECEIVER=\"Jed's iPhone\" ./airdrop.sh send photo.jpg\n```\n\nFiles land in `~/Downloads`. Per-run logs and captures go to `./runs/`.\n\n### Configuration\n\nAll optional, all environment variables:\n\n| variable | default | meaning |\n|---|---|---|\n| `IFACE` | autodetected mt7921 interface | Wi-Fi interface |\n| `REG` | `NZ` | regulatory domain - **set this to your country** |\n| `CHAN` | `36` | starting channel (6, 36, 44, 149) |\n| `OWL_DIR` | `~/owl` | your patched OWL checkout |\n| `RECV_DIR` | `~/Downloads` | where received files are extracted |\n| `RECV_TIME` | `300` | seconds to stay advertising |\n| `OUT_DIR` | `./runs` | where logs and captures go |\n| `STRATEGY` | `verbatim` | how OWL derives its channel sequence: `verbatim`, `widen`, `rotate`, `pin`. `verbatim` is the default because `pin` breaks TX to iOS 26 ([§25](docs/FINDINGS.md)) |\n\n## Open questions\n\nTwo throughput questions remain; both need a human and a phone in the room.\n\n**1. How fast is sending?** Sending is proven to *work* ([§37](docs/FINDINGS.md))\nbut the proving run sent a 68-byte file, which measures nothing. Send a real one\nand read the rate:\n\n```sh\n./airdrop.sh send ~/some-photo.jpg\ntools/bursts.py runs/\u003csend-run\u003e/send.pcap\n```\n\nThe `send` TX path is hardcoded to 12 Mbit/s legacy OFDM (`src/tx.c`, under\nupstream's own TODO), so send may well be slower than the ~45 kB/s receive\nceiling. Unknown until measured.\n\n**2. Does widening the channel sequence beat `verbatim` on receive?**\n[§21](docs/FINDINGS.md) argues the ~45 kB/s ceiling is the 2-of-16-slot sequence\nwe copy, while the phone offers up to 11 of 16. `-S pin` (all 16 slots) was the\nfirst attempt and **breaks TX to iOS 26** ([§25](docs/FINDINGS.md)) - it is\ndisqualified. `-S widen -W n` keeps the peer's own sequence and fills only its\nempty slots, which iOS 26 *does* accept ([§26](docs/FINDINGS.md)). To settle it:\n\n```sh\nSTRATEGY=verbatim ACTIVE=1 ./airdrop.sh receive     # reproduce the baseline\nSTRATEGY=widen    ACTIVE=1 ./airdrop.sh receive     # the change under test\n\ntools/bursts.py runs/\u003cwiden-run\u003e/receive.pcap \\\n   --baseline runs/\u003cverbatim-run\u003e/receive.pcap\n```\n\nRead **bursts per second**, not throughput. §18 established that bytes per\navailability window is fixed at ~22-25 kB and cannot be moved from this side, so\nwindow count is the only real lever. **Check first whether the run was winnable:**\nif `tools/slotmap.py --log runs/\u003crun\u003e/owl.log` shows the peer never offered more\nthan 2 of 16 slots, there was nothing to gain and the run proves nothing.\n\nThe predictions and their falsification conditions are written down in §21 in\nadvance, because the earlier confident diagnoses in this project were more than\nonce contradicted by their own logs.\n\n## Safety\n\nBy default `airdrop.sh` takes the Wi-Fi card exclusively - **you have no internet\nwhile it runs**. It restores NetworkManager on exit via a bash trap *and* a\n`setsid`-detached watchdog, so networking comes back even if the script is\n`kill -9`ed or hangs. Init system is detected (runit or systemd).\n\n### Keeping your internet: `KEEP_WIFI=1`\n\n```sh\nKEEP_WIFI=1 ACTIVE=1 ./airdrop.sh receive\n```\n\nThe monitor vifs coexist with an associated managed vif perfectly well - that\nwas measured, with a concurrent ping at 0% loss across a full run (FINDINGS\n§38). The exclusive-card rule was never about the interfaces; it was about the\nchannel.\n\nThe catch is that the monitor vif then gets **no channel of its own** and is\nlocked to whatever channel your AP is on (`iw dev mon0 set freq` returns EBUSY).\nSo this mode only works when **your AP happens to be parked on a channel the\nphone's AWDL sequence uses** - 36, 44, 149 or 6. It refuses up front if your AP\nis somewhere else, rather than failing later in a way that looks like a dozen\nother problems.\n\nThat is less alarming than it sounds: OWL's channel hopping was already fiction\nin every working transfer (§24), so the radio has always been effectively pinned\nto one channel. `KEEP_WIFI` only changes who chooses it.\n\n**Not yet proven to complete a transfer** - only to coexist. If you want Wi-Fi\nand AirDrop simultaneously with no conditions attached, give AWDL its own radio\n(an AR9271 on USB): two phys, no shared channel context.\n\n## Two mt7921 driver bugs you will hit\n\nBoth are worked around by `airdrop.sh`; both cost me a day each, so they are\nworth stating plainly:\n\n1. **Runtime power management silently kills monitor RX.** With `runtime-pm=1`\n   the chip dozes and you capture nothing, with no error anywhere. Set\n   `runtime-pm` and `deep-sleep` to 0 in\n   `/sys/kernel/debug/ieee80211/\u003cphy\u003e/mt76`.\n2. **An in-place interface type switch never retunes the radio.** `iw dev X set\n   type monitor` leaves the radio where it was. You must create a *dedicated*\n   monitor vif.\n\nAnd a methodology note that cost me more than either: **on this chip `iw` lies\nabout the channel.** It reports what you asked for, not where the radio is. The\nonly trustworthy source is the radiotap frequency on captured frames. Two of the\nretracted conclusions in `docs/FINDINGS.md` come from trusting `iw`.\n\n## Repo layout\n\n```\nairdrop.sh          the tool\npatches/            OpenDrop fixes for iOS 26\ndocs/FINDINGS.md    the full investigation, including what I got wrong\ndocs/NOTES.md       dated lab notebook\ntools/              diagnostic harnesses, each answering one question\n```\n\n`tools/` is research, not a test suite. Each script isolates one question and\nrestores your networking afterwards. `activelate2.sh` and `activelate3.sh` are\nthe two that establish the pair configuration this whole project rests on.\n\n## Credit\n\n- [seemoo-lab/owl](https://github.com/seemoo-lab/owl) - the AWDL implementation\n- [seemoo-lab/opendrop](https://github.com/seemoo-lab/opendrop) - the AirDrop layer\n- The [Open Wireless Link](https://owlink.org) project's reverse engineering\n\n## Licence\n\nGPLv3, matching OWL.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjedbillyb%2Fairdrop-mt7921","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjedbillyb%2Fairdrop-mt7921","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjedbillyb%2Fairdrop-mt7921/lists"}