urMQTT valódi VPS-en: ISO, 0,24 ms-os kézfogás, saját lemez és megerősített kernel
Mi lett az urMQTT-ről szóló első írás három ígéretéből (egyetlen ISO, nyers lemez fájlrendszer nélkül, bizonyíthatóan biztonságos) a fejlesztés első négy napja után, mérésekkel, táblázatokkal és a még hiányzó dolgok listájával.

Az urMQTT-ről szóló első írásban bemutattuk az unikernelt, amely maga az MQTT bróker, egészen az M17 mérföldkőig, és három ígérettel zártuk a „Mi következik" cím alatt: egyetlen ISO, amely több platformon elindul, nyers lemez fájlrendszer nélkül, és olyan biztonság, amelyet bizonyítani lehet, nem csak állítani. Akkor ezek közül még semmi sem volt megépítve. Ez az írás azt követi végig, mi történt az egyes ígéretekkel, ugyanabban a sorrendben, mint korábban: mit terveztünk, mit építettünk, mi lepett meg bennünket, és mi maradt nyitva. Közben azt is megmutatjuk, mennyi ideig tartott mindez, mert ez is a történet része.
Egy megjegyzés előre: a projekt továbbra is pre-alfa. Az írásban szereplő számok a kódot és a teszteket mérik, nem az éles üzemi érettséget, az első kiadás pedig szándékosan csak a KVM-et használó VPS-ekre korlátozódik. A bare metal későbbre marad.
Hogyan nézett ki ez az időben
Az első commit 2026. október 28-án 23:48-kor született (M0). Október 31-ig 23:00-ig a repóban 119 commit van, naptári napok szerint 3, 39, 38 és 39 eloszlásban. Rögtön el is mondjuk, hogyan lehetséges ez: a projekt Claude Code segítségével épül, a commitok pedig Co-Authored-By: Claude záróelemet viselnek. A négy naptári nap története nem azt jelenti, hogy egy ember négy nap alatt 90 000 sort írt meg; azt jelenti, hogy a tervezés, a megvalósítás, a tesztek és az ADR-ek rövid, zárt ciklusokban készülnek, és minden lépés ugyanazzal az ellenőrzési rituáléval zárul, mint korábban. Az alábbi számok a kód és a tesztek mennyiségét mutatják, nem azt, mennyi tényleges munka állt mögöttük.
A grafikon három görbét mutat, amelyek szinte együtt emelkednek, és ez a szándék: a tesztek és a leírt döntések követik a kódot, nem maradnak le mögötte. A meredekség október 29-én a legnagyobb, amikor elkészül az egész út a kerneltől a brókerig. Később a sorok lassabban szaporodnak, a tesztek viszont gyorsabban nőnek, mint a kód (27 teszt 1,6 ezer sorra az elején, 739 teszt 89,8 ezer sorra ma).
| Mérföldkő | Commit | Időpont | Rust-sorok | Tesztek | ADR-ek |
|---|---|---|---|---|---|
| M0 | 9412a9c | 10.28. 23:48 | 1 592 | 27 | 13 |
| M5 | a61083b | 10.29. 01:32 | 9 314 | 88 | 24 |
| M9 | 4be8cc9 | 10.29. 02:21 | 15 729 | 149 | 28 |
| M11 | 4800904 | 10.29. 03:43 | 22 737 | 180 | 30 |
| M13 | a558d5e | 10.29. 05:22 | 26 556 | 193 | 32 |
| M17 | 7df4f98 | 10.29. 08:48 | 33 504 | 232 | 36 |
| S1 | ecf72ec | 10.30. 00:46 | 41 910 | 299 | 45 |
| X8 | fb1504a | 10.30. 19:08 | 56 341 | 456 | 59 |
| PERF | 76f7c36 | 10.31. 14:19 | 72 511 | 618 | 76 |
| ma | HEAD | 10.31. 23:00 | 89 790 | 739 | 104 |
A sorok a vendor/ és a fuzz/ mappán kívüli összes .rs fájl, a tesztek pedig a #[test] függvények. Hogy melyik rész mit adott hozzá, a git-előzmények dátumai szerint:
| Dátum | Mérföldkövek | Mi készült el |
|---|---|---|
| 10.29. | M0–M17 | kernel, hálózat, TLS, bróker, SQLite, több mag, saját TCP, megerősítés, reprodukálható kiadás |
| 10.29. este és 10.30. | X0–X10, S1, H2, H3 (az X0 10.29-én 20:54-kor indul) | x86_64 port, univerzális ISO, egyetlen lemez és öntelepítő ISO, TLS-ellenőrzések, támadó kliensek |
| 10.31. | O1–O4, T1, PERF, H4, H5, H9, S2–S6 | betekintés és konzol MQTT-n keresztül, élő mód és diagnosztika, SNTP, gyorsítások, megerősítés, verifikáció, ellátási lánc, tárolás |
1. ígéret: egyetlen ISO több platformra
Mit terveztünk. Egyetlen fájl, amely elindul aarch64 UEFI-n, x86_64 UEFI-n és x86_64 BIOS-on. Mit építettünk. Az x86_64 portot (X0–X10) és a Limine bootloaderes univerzális ISO-t: egyetlen hibrid ISO, amelyről mindezek a kombinációk elindulnak; a füsttesztek négy virtuális gépen indítják el. A menünek három tétele van: urMQTT (élő mód, a RAM-ból fut, semmit nem ír a lemezre), telepítés annak a gépnek a lemezére, amelyen elindították (a telepítő GPT-t és adatokat ír, majd leállítja a gépet), és Diagnosztika (hardverfelismerés és mérések).

Az első valódi cél egy VPS volt, nem fizikai számítógép: a döntés az, hogy az első kiadás csak VPS-t (KVM) támogat, a bare metal pedig utána jön. Contabo VPS-en teszteltünk, AMD EPYC processzorral (23-as család, Zen 1), 4 vCPU-val és 8 GiB RAM-mal, virtio-net hálózattal és virtio-scsi lemezzel. Az első kísérlet meglepett bennünket: a virtuális CD-ként csatlakoztatott ISO az alapértelmezett tételt indította el, az éles kép pedig nem volt hajlandó írásvédett adathordozóról működni, és újraindult, így végtelen ciklust kaptunk menü, kernel, újraindítás között, bármiféle olvasható üzenet nélkül. Ma egy végzetes hiba az éles képben 60 másodpercig kint hagyja az üzenetet a konzolon az újraindítás előtt, optikai adathordozóról pedig kiírja, hogy írásvédett adathordozóról indult el, és megáll. Az élő mód a RAM-ban (O3) később a szokásos esetben megkerülte ezt a problémát.
A diagnosztikai tétel ebből a tapasztalatból született: egy olyan gépet, amelyet nem látunk, meg kell tudnunk kérdezni, mije van. Kiírja a CPU-t, a RAM-ot, a hálózatot, a lemezeket és a PCI-eszközöket, megméri a memória áteresztőképességét, a lemezt és a kriptográfiát, ugyanezeket a méréseket pedig JSON-ként a soros portra is kiírja. Lemezre írni csak a le nem foglalt területen szabad, és csak a WIPE begépelése után; az alábbi felvételen ezt kihagytuk.

Mi maradt nyitva. A valódi hardver. Az első kiadás után jön a bare metal (USB-ről indítás, NVMe és AHCI, egy valódi számítógép hálózati kártyája), az IOMMU-val és a Secure Boottal együtt, mert IOMMU nélkül egy rosszindulatú vagy hibás eszköz az egész memóriába írhat. Ebben a verzióban erről semmit nem állítunk.
Kézfogás: 1,2 ms-ról 0,24 ms-ra
A diagnosztika első mérése a VPS-en kellemetlen számot adott: az mTLS-kézfogás, csak a szerveroldali kriptográfia, 1,2 ms-ig tartott. Aarch64-en már használtunk hardveres AES- és SHA-utasításokat, de x86_64-en nem volt semmi hasonló a nyilvános kulcsokhoz: a RustCrypto hordozható és constant-time, ezért lassú is. Három változtatás vitte le.
- A P-256-hoz átvettük az
nistz256assemblert az OpenSSL-ből és a ringből (ADR-130), rögzített bázisú táblákkal az aláíráshoz és a CA aláírásainak ellenőrzéséhez (ADR-110, ADR-111). - Az X25519-et négy 64 bites végtagra írtuk át MULX/ADX utasításokkal (ADR-131).
- Ezután a maradékot is profiloztuk (ADR-132, ADR-200).
| Mérés a VPS-en (szerver, mikroszekundum) | Előtte | Utána |
|---|---|---|
| mTLS-kézfogás, a teljes kriptográfia | 1200 | körülbelül 240 (237 a grafikon mérésében, 244 a felvételen) |
| X25519 | 146 | 48 |
| ECDSA-aláírás | 673 | 34 |
| ECDSA-ellenőrzés | 309 | 90 (107 a felvételen) |
| kézfogás, régi memcpy, ugyanaz a boot | 278,8 | 229,4 (-17,7%) |
A számok futásonként nagyjából 10%-kal különböznek, ezért egyetlen érték helyett tartományt rögzítettünk. Összességében ez nagyjából ötször kevesebb CPU-idő egy kézfogásra.
Amit a memcpy-ról tanultunk. Miután a kriptográfia lecsökkent, a profil olyat mutatott, amire nem számítottunk: a mem* függvények (memcpy, memmove, memset) a kézfogás 7%-át, a TLS-rekordút 13–39%-át (39% 64 B-nál, 13% 4 KiB-nál) és a PUBLISH szétosztásának 17–34%-át emésztették fel. Lecseréltük őket SSE2 assemblerre x86_64-en (ADR-200), a referencia-implementációval szembeni differenciális tesztekkel minden méretre, igazításra és átfedésre. Hívásonként ez 55–75 ciklus a 7–12 helyett 1–64 bájtnál.
| Út | A mem* aránya előtte | Gyorsabb utána |
|---|---|---|
| kézfogás | 7% | 17,7% |
| TLS-rekord 64 B | 39% | 26,4% |
| TLS-rekord 256 B | 37% | 19,6% |
| TLS-rekord 1 KiB | 24% | 16,1% |
| TLS-rekord 4 KiB | 13% | 6,6% |
| üzenetek szétosztása | 17–34% | 7–34% |
A grafikon olvasata: minél kisebb a rekord, annál nagyobb része megy a másolásra, így annál nagyobb a nyereség is. A kis üzeneteknél, amelyek az MQTT-forgalom nagy részét teszik ki, ez a lényeg.
Amit ez nem jelent. Natív x86_64-en az OpenSSL 3.5.5-öt csak az assembler referenciájaként mértük (ADR-130), nem termékösszehasonlításként. A Linux plusz Mosquitto összehasonlítást ugyanazon a VPS-en (X11 feladat) még nem végeztük el. Ezért nem állítjuk, hogy az urMQTT gyorsabb a Linuxnál: nincs megmérve. A fenti számok az urMQTT-t önmagával hasonlítják össze.
A túlterhelés volt a második eredmény. A VPS-en, egyetlen Macről (RTT 35 ms) 128 kliens 618 kézfogást adott másodpercenként, 256 kliens 393-at, 512 kliens pedig 67-et, ezernyi elutasított kapcsolat mellett. Ez nem plató volt, hanem összeomlás. Az okokat a kódban találtuk meg (kettőt QEMU-ban/HVF-en megerősítettünk, a VPS-en nem sikerült reprodukálni), egyszerre többet is: egy elutasított kapcsolat visszaengedte a klienst a következő olyan szegmensen át, amely érvényes SYN-sütit hordoz, az elutasítás RST-vel azt mondta a kliensnek, hogy azonnal próbálkozzon újra, a vétel pedig O(sor) volt. A javítás (ADR-080–ADR-082) eldobja azt a SYN-t, amelyet nem fogadnának el, megjegyzi az elutasított folyamokat, a vételt O(1)-ben tartja, és magonként számolja a kézfogásokat. Lehetséges (hipotézis, nincs ellenőrizve), hogy az ok egy része a kliensoldalon volt: a terhelésgenerátor zárta le elsőként a kapcsolatot, így egyetlen Mac 16 384 efemer porttal és 30 másodperces TIME_WAIT mellett nagyjából 550 kapcsolat/másodpercre korlátozódott. Ezért a szerver valódi határát még nem mértük meg.
2. ígéret: nyers lemez, de körültekintően
Mit terveztünk. Megtartani a saját, fájlrendszer nélküli elrendezést, és hozzáadni mindazt, amit egy fájlrendszer egyébként ad: igazítást, növekedést, discardot, biztonsági másolatokat és ellenőrzéseket. Miért nem fájlrendszer. Ezt megvitattuk, és a döntést megerősítettük: az urMQTT egy adatbázist, egy telemetriai naplót és saját metaadatokat ír, nincs szüksége könyvtárakra, jogosultságokra és fájlnevekre, minden további réteg pedig még több kód lenne a megbízható részben, és még egy mód arra, hogy egy írás csendben eltűnjön. A lemez GPT egy ESP partícióval és egyetlen urMQTT-adatpartícióval, amelyen a saját elrendezés van.
Mit építettünk (S2–S6).
- Elrendezés v2 (S2). Lemeztopológia, igazítás 1 MiB-ra vagy az optimális I/O-méretre, egész fizikai blokkok, A/B szuperblokkpár generációval (ADR-140). A v1 elrendezést a kernel érthető üzenettel elutasítja, a migrálást pedig az
urmqtt-disk migrateoffline eszköz végzi (ADR-141): alapértelmezés szerint próbafutás, először másol, csak utána vált át, visszaállítással és hibainjektálással minden írásnál. - Növekedés (S3). Induláskor a lemez megnőhet (soha nem csökken), virtio-blk-n pedig üzem közben is, a konfigurációváltozási megszakításon keresztül (ADR-142, ADR-143).
- Discard és write-zeroes (S4). A vékonyan kiosztott lemezek visszaadják a helyet a gazdagépnek. Egy tesztben a gazdagépen lévő qcow2 fájl 62-ről 16 MiB-ra esett vissza, miután a telemetriai szegmensek lejártak, a helyreállítás pedig soha nem bízik az eldobott tartalomban.
- Az adatbázis biztonsági mentése a lemezen (S5). A/B másolat az SQLite online backup API-jával 16 oldalas lépésekben: a leghosszabb lépés 4,6 ms, szemben a 445 ms-mal, ha egy hívásban másolunk. A másolás alatti commitok maximuma 2,4 ms volt, a nélküle mért 2,2 ms ellenében. Ha a
quick_checkinduláskor hibázik, az adatbázis a másolatból áll vissza. - Tisztítás és korlátozott indulás (S6). A scrubber korlátozott I/O-költségvetéssel olvassa a lemezt, és jelzi a sérült rekordokat, az indulás pedig minden partíció csak a legújabb szegmensét olvassa, így az indulási idő nem nő a telemetriai régió méretével. Ezzel lezártuk a problémát, amelyet előre kiszámoltunk: nagy lemezen a teljes helyreállítás percektől több tíz percig tartott volna.
A tárolás füsttesztjeinek teljes csoportja mindkét architektúrán átmegy: 79 sikeres, 0 sikertelen, aarch64-en HVF alatt és x86_64-en TCG alatt. Azt is megmértük, hogy az S4 nem lassította az adatbázist: 53,3 ezer beszúrás másodpercenként kötegekben előtte és utána is, mérhető különbség nélkül.
Mi lepett meg. Mennyi múlik a „egyszerű" elrendezésben a szélső eseteken: a virtio-scsi és a virtio-blk különböző módon jelenti a topológiát, a migráció közepén megszakadt írásnak pedig mindig hagynia kell legalább egy érvényes változatot. Ezért a migráció és a növekedés útján minden írás átment hibainjektáláson.
Mi maradt nyitva. Az SQLite-oldalaknak nincs ellenőrző összegük, így egy sor értékén belül megváltozott bitet az adatbázis nem vesz észre; a telemetriai rekordoknak és a másolatok tartalmának van CRC-je, ezért ezeket észleljük. A lemeztitkosítás (S7) nincs kész.
3. ígéret: bizonyíthatóan biztonságos, amennyire csak sikerül
Megerősítés (H4). Az allokátor törli a felszabadított memóriát és újrahasználatkor ellenőrzi, felismeri a dupla felszabadítást, a szabadlista mutatóit XOR-ral köti, a kulcsokat pedig felszabadításkor nullázza (ADR-170). Veremvédelem (-Zstack-protector=strong, 1315 függvény, az SQLite pedig -fstack-protector-strong mellett, ADR-171). Az éles hibajelentések nem tartalmaznak regisztereket és címeket (ADR-172). KASLR mindkét architektúrán: a kernel PIE, és a Limine nagyjából 19 bittel tolja el, a heap, a vermek és az MMIO-ablak pedig véletlenszerűen helyezkedik el (ADR-173). Aarch64-en az MTE jelöli a heapet (ADR-174), de csak TCG alatt: a HVF és az x86 nem rendelkezik vele. A kCFI-t megvizsgáltuk, de nem fogadtuk el, mert a fordító a rögzített nightlyn belső hibával összeomlik; az IOMMU-t a bare metallal együtt elhalasztottuk (ADR-175, ADR-176).
Az ár megmérve, nem feltételezve. A kézfogás útján (dp-bench, aarch64 HVF, teljes TLS 1.3 + mTLS + CONNECT kézfogások):
| Lépés | 1 mag | 4 mag | Változás |
|---|---|---|---|
| megerősítés nélkül | 1941 | 7620 | |
| + allokátor | 1898 | 7429 | -2,2% – -2,5% |
| + veremvédelem | 1845 | 7243 | együtt -4,9% – -5% |
| + PIE és címkék | körülbelül 1800 | körülbelül 6750 | terhelt gépen, zajos |
Összességében tehát az ár a kézfogás útján nagyjából 5% a tisztán mért részekre, PIE-vel és címkékkel pedig körülbelül 7% egy és körülbelül 11% négy magon, de ez a mérés zajos. Elfogadhatónak tartjuk, de x86_64 VPS-en nem ismételtük meg.
Érdemes összevetni az első írás számával (862 kézfogás másodpercenként egy magon). Ugyanez az eszköz ma, az aarch64 hardveres titkosításának meghajtójával (X0b) és az azóta érkezett gyorsítások sorával, 1941-et ad a megerősítés előtt. Az egyes változtatások hozzájárulását nem különítettük el, ezért az okról semmit nem állítunk.
Hogyan ellenőriztük, és mit nem. A H5 hozzáadta a Mirit a host crate-ek egy részhalmazára, a Kani-bizonyításokat, a sanitizereket, az ACL modell-fuzzerét és a lefedettségi jelentéseket. A Kani bebizonyította a record_len függvényt a TLS-ben, a parse függvényt a TCP-ben és az időzítőkerék két aritmetikáját. Nincs bebizonyítva (időtúllépés): mindkét MQTT-dekóder, mindkét konfigurációelemzés és az időzítőkerék viselkedése; a Kani CI-feladata ezért még nem blokkol. A Miri nem tud assemblert futtatni. Ellátási lánc (H9): CycloneDX SBOM 215 összetevővel, a cargo-vet átmegy 39 teljesen átvizsgált és 140 kivétel mellett, a cargo-deny pedig úgy van beállítva, hogy a sérülékenységi tanácsokat CI-hibaként kezelje (ezeket a CI-feladatokat eddig csak helyben ellenőriztük, GitHubon nem, a negatív tesztet pedig nem futtattuk le), a VULNERABILITY-HANDLING.md mellett. Az őszinte állapot: a 67 kernelbe linkelt harmadik fél crate-verzióból 53 kivétel, nem átvizsgálás. A vet azt bizonyítja, hogy a függőségek halmaza nem változhat látható különbség nélkül, nem azt, hogy a TCB át van vizsgálva. Egyetlen crate-ről sem írtunk saját átvizsgálást.
Az SNTP (T1) ebben az értelemben egy kis biztonsági történet: opcionális, csak privát IPv4-szerverek felé, maximális ugrással és azzal a feltétellel, hogy a források egyetértenek (medián, többség egy másodpercen belül). Az az óra, amelyet bárki elmozdíthat az úton, nem olyan óra, amelyben megbízhatnánk a tanúsítványok érvényességének ellenőrzésénél.
Macen: aarch64 natívan és x86_64 emulációval
Macen fejlesztünk, és ennek két módja van ugyanannak a kódnak a futtatására, amelyek nagyon különböznek.
| aarch64 HVF alatt | x86_64 TCG alatt | |
|---|---|---|
| Mi ez | natív végrehajtás, a valódi processzorhoz közeli sebesség | a QEMU emulálja az utasításokat |
| Kézfogás a vendégben | körülbelül 0,58 ms (először az M13 körül mérve); a dp-bench ma 1941/s magonként |
körülbelül 3,3 ms |
| TLS füstteszt-forgatókönyvek | gyorsak | körülbelül 15 perc forgatókönyvenként |
| MTE | nem (a HVF-en nincs) | az x86-nak nincs; a heap jelölése csak aarch64 vendégen működik TCG alatt |
| Mire való | valódi mérések | helyesség: az x86 utat ellenőrizzük, a számok nem teljesítményt jelentenek |
A szabály egyszerű: a TCG alatti számok emulációt mérnek, és nem idézzük őket teljesítményként. Az x86 teljesítményét csak valódi VPS-en mérjük. Ezért a fenti kriptográfiai számok a Contabo VPS-ről származnak, nem a Macről.
Tanulságok
- Mérjen valódi gépen, mielőtt optimalizál. Az első megközelítés az volt, hogy azt optimalizáltuk, amiről azt hittük, hogy lassú; csak a VPS-en futó diagnosztika mutatta meg az 1,2 ms-ot és azt, hogy pontosan hová megy az idő.
- A túlterhelés külön tulajdonság. Az a rendszer, amely terhelés alatt gyors, felette összeomolhat; az elutasításnak olcsóbbnak kell lennie az elfogadásnál.
- A profil meglep. A kis üzenetek legnagyobb egyedi nyeresége nem a kriptográfiában volt, hanem a memóriamásolásban.
- A fájlrendszer nélküli lemez több munkát kér, nem kevesebbet. Amit a fájlrendszer egyébként ad (igazítás, növekedés, discard, másolatok, ellenőrzések), azt magunknak kell megírnunk és ellenőriznünk, minden lépést hibainjektálással.
- Őszinteség a számokban. A megosztott VPS-en a memóriamérések futások között akár 10-szeresen is ingadoznak, és semmit nem mondanak a kódról, ezért nem közöljük őket. A kriptográfiai számok nagyjából 10%-on belül stabilak.
- A fejlesztés sebessége nem érettség. A tesztek és az ADR-ek a kóddal együtt nőnek, de négy nap nem valódi üzem.
Mi következik
- H6: az SQLite EL0 szinten izolálva, hogy egy adatbázishiba ne nyissa meg a kernelt.
- H7: az időbiztonság maradéka (NTS és az ugrások kezelése); az SNTP csak az első lépés.
- X11: összehasonlítás a Linuxszal és a Mosquittóval ugyanazon a VPS-en, csak ezután van értelme relatív sebességről beszélni.
- Kiadás: az első nyilvános megjelenés VPS-ekre (KVM), aláírt artefaktumokkal és útmutatókkal.
- A kiadás után: bare metal (X12–X16), IOMMU és Secure Boot, valamint lemeztitkosítás.
A kód a github.com/zeljan-alduk/urMQTT címen érhető el, a docs/PROGRESS.md pedig továbbra is az egyetlen igazságforrás arról, hogy mi működik valójában. Pre-alfa, mint korábban; a hibajelentéseket és a biztonsági bejelentéseket a SECURITY.md útmutatása szerint szívesen fogadjuk.
Hozzászólások
Hozzászólási szabályok