<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>ALDO TECH blog</title>
    <link>https://aldo.tech/hu/blog/</link>
    <atom:link href="https://aldo.tech/hu/blog/feed.xml" rel="self" type="application/rss+xml"/>
    <description>Jegyzetek a terepről: tömegközlekedés, jegyértékesítés, valamint hardver és szoftver, amelynek az úton is működnie kell.</description>
    <language>hu</language>
    <item>
      <title>Az urMQTT a VPS után: SQLite felhasználói módban, összevetés a Mosquittóval, konfiguráció mint adat és böngészős építő</title>
      <link>https://aldo.tech/hu/blog/urmqtt-config-builder-plaintext-mosquitto/</link>
      <guid isPermaLink="true">https://aldo.tech/hu/blog/urmqtt-config-builder-plaintext-mosquitto/</guid>
      <pubDate>Sun, 01 Nov 2026 08:00:00 +0000</pubDate>
      <description>Mi történt az urMQTT-ről szóló második írás után: a magon kívülre szigetelt SQLite, mérések a Mosquitto ellen ugyanazon a VPS-en (azzal együtt, ami nem nekünk kedvezett), az egyetlen adatfolyam lassúságának feltárt okai, a konfiguráció mint adat webes ISO-építővel és böngészős emulátorral, az opcionális sima MQTT, és ami még nincs bizonyítva.</description>
      <content:encoded><![CDATA[<p>A második írás, <a href="https://aldo.tech/blog/urmqtt-vps-handshake-disk-hardening/">az urMQTT egy valódi VPS-en</a>, a „Mi következik" listával zárult: H6 (SQLite a magon kívül), H7 (az idő biztonsága), X11 (összevetés a Mosquittóval ugyanazon a VPS-en) és az első kiadás. Ez az írás ugyanabban a sorrendben követi ezt a listát, majd hozzáad két dolgot, amely nem szerepelt rajta, mert menet közben bukkantak fel: a konfigurációt mint adatot, webes ISO-építővel, és az opcionális sima MQTT-t TLS nélkül. Mint korábban: mit terveztünk, mit építettünk, mi lepett meg bennünket, és mi maradt nyitva. Ebben az írásban van néhány eredmény, amely nem az urMQTT javára szól; benne hagytuk őket, mert az igazság részei.</p>
<p>Az előzetes megjegyzés ugyanaz marad: a projekt pre-alfa, az első kiadás csak KVM-et használó VPS-ekre szól, a számok pedig a kódot, a teszteket és bizonyos kísérleteket mérik, nem az éles üzemi érettséget.</p>
<h2 id="hogyan-nezett-ki-ez-az-idoben">Hogyan nézett ki ez az időben</h2>
<p>Az előző írás (10.31., 23:00) óta máig (11.1., 20:41) 59 új commit került a repóba, összesen 178. Mind egyetlen naptári napon készült, ezért ugyanaz a figyelmeztetés érvényes, mint korábban: a projekt Claude Code segítségével épül (a commitok <code>Co-Authored-By: Claude</code> záróelemet viselnek), a terv, a kód, a tesztek és az ADR-ek rövid, zárt ciklusokban készülnek, az alábbi számok pedig a kód és az ellenőrzések mennyiségéről szólnak, nem arról, mennyi emberi munka van bennük.</p>
<figure><img loading="lazy" alt="Növekedés az előző írás óta: Rust-sorok 89 790-ről 112 408-ra, #[test] függvények 739-ről 926-ra, ADR-ek 104-ről 126-ra és commitok 119-ről 178-ra. Minden sornak saját skálája van." src="https://aldo.tech/blog/urmqtt-config-builder-plaintext-mosquitto/img/growth-since.svg" /><figcaption>Növekedés az előző írás óta: Rust-sorok 89 790-ről 112 408-ra, #[test] függvények 739-ről 926-ra, ADR-ek 104-ről 126-ra és commitok 119-ről 178-ra. Minden sornak saját skálája van.</figcaption></figure>
<p>A számokat ugyanazzal a módszerrel számoltuk, mint az előző írásban: minden <code>.rs</code> fájl a <code>vendor/</code> és a <code>fuzz/</code> kívül, a <code>#[test]</code> függvények, a <code>## ADR-</code> címek a <code>docs/DECISIONS.md</code> fájlban.</p>
<table>
<thead>
<tr>
<th>Pillanat</th>
<th>Commit</th>
<th>Idő</th>
<th style="text-align: right;">Rust-sorok</th>
<th style="text-align: right;">Tesztek</th>
<th style="text-align: right;">ADR-ek</th>
</tr>
</thead>
<tbody>
<tr>
<td>előző írás</td>
<td>794f607</td>
<td>10.31. 23:00</td>
<td style="text-align: right;">89 790</td>
<td style="text-align: right;">739</td>
<td style="text-align: right;">104</td>
</tr>
<tr>
<td>H6 kész</td>
<td>a0423cc</td>
<td>11.1. 03:59</td>
<td style="text-align: right;">95 078</td>
<td style="text-align: right;">754</td>
<td style="text-align: right;">107</td>
</tr>
<tr>
<td>X11</td>
<td>a29f49b</td>
<td>11.1. 07:56</td>
<td style="text-align: right;">95 579</td>
<td style="text-align: right;">754</td>
<td style="text-align: right;">108</td>
</tr>
<tr>
<td>webes építő és a kiadási tükör</td>
<td>582783f</td>
<td>11.1. 16:49</td>
<td style="text-align: right;">105 645</td>
<td style="text-align: right;">848</td>
<td style="text-align: right;">122</td>
</tr>
<tr>
<td>sima MQTT, dokumentáció</td>
<td>8f959f1</td>
<td>11.1. 19:05</td>
<td style="text-align: right;">112 408</td>
<td style="text-align: right;">926</td>
<td style="text-align: right;">125</td>
</tr>
<tr>
<td>ma</td>
<td>d21e3ec</td>
<td>11.1. 20:41</td>
<td style="text-align: right;">112 408</td>
<td style="text-align: right;">926</td>
<td style="text-align: right;">126</td>
</tr>
</tbody>
</table>
<p>Több ágon párhuzamosan dolgoztunk, ezért a commitok sorrendje nem szigorúan a munka sorrendje. Az ADR-ek számozása 232-ig megy, de a számozásban vannak hézagok (egy részüket átnevezték az ágak egyesítésekor); a címeket számoljuk, nem a legnagyobb számot. Kiadások: v0.2.0 (11.1., 18:45 UTC) és v0.2.1 (11.1., 20:22 UTC), mindkettő pre-release-ként megjelölve.</p>
<h2 id="h6-sqlite-felhasznaloi-modban">H6: SQLite felhasználói módban</h2>
<p><strong>Mit terveztünk.</strong> Az SQLite 254 268 sornyi C-kód, amely az adatbázist, a WAL-t és a naplót parseolja a lemezről, a lemezt pedig elő lehet állítani vagy módosítani. Addig a magban futott, a mag jogaival. <strong>Mit építettünk</strong> (ADR-210–ADR-212): az SQLite, a hozzá tartozó libc-részek és a heap egy külön programba kerültek, a <code>dbworker</code>-be, amely EL0-n (aarch64), illetve ring 3-ban (x86_64) fut, saját címtérben. A mag már nem linkeli az SQLite-ot. A worker egy közös memóriaablakon és tizenkét rendszerhíváson át beszél a maggal, és minden visszaadott hosszt és eltolást használat előtt ellenőrzünk. A workerben fellépő hiba (tiltott hozzáférés, nem definiált utasítás, a 30 s-os időkeret túllépése) lezárja a hívást, a worker újraindul, az adatbázis pedig a WAL-ból nyílik meg, mint induláskor; percenként 5 újraindítás után a tároló hibásnak minősül, és nem indul újra a végtelenségig.</p>
<p>Az ár, aarch64-en, HVF alatt mérve, ugyanaz a VM, a beépített SQLite-os mag a workeres maggal szemben, mindkettőből hat váltakozó futtatás:</p>
<table>
<thead>
<tr>
<th>Mérés</th>
<th>Előtte (a magban)</th>
<th>Utána (worker)</th>
<th>Különbség</th>
</tr>
</thead>
<tbody>
<tr>
<td>belépés a felhasználói módba és kilépés</td>
<td>-</td>
<td>1,1 us körönként</td>
<td></td>
</tr>
<tr>
<td><code>db-bench</code>, 5000 beszúrás 50 tranzakcióban, legjobb / medián</td>
<td>61,9k / 58,4k op/s</td>
<td>54,7k / 53,3k op/s</td>
<td>-11,6% / -8,7%</td>
</tr>
<tr>
<td>egy beszúrás tranzakciónként, legjobb / medián</td>
<td>2 319 / 2 148 op/s</td>
<td>2 307 / 2 126 op/s</td>
<td>-0,5% / -1%</td>
</tr>
<tr>
<td>teljes biztonsági mentés egyetlen hívással (23 MiB)</td>
<td>514 ms</td>
<td>635 ms</td>
<td>+23%</td>
</tr>
</tbody>
</table>
<p>A lemezírások (1572) és a flushek (319) azonosak, ahogyan kell. Ez nincs szigetelve: <strong>a mag továbbra is hisz annak, amit a worker visszaad</strong>, az alak ellenőrzése után. Egy kompromittált worker hazudhat (például üres visszavont tanúsítványlistát adhat vissza, és ezzel „visszavonja a visszavonást"), de nem olvashatja a mag memóriáját, a TLS-kulcsokat és a jegykulcsokat. KPTI nincs, ezért a Spectre/Meltdown-típusú szivárgások nincsenek megakadályozva, és semmit nem állítunk róluk; a PAC-kulcson az EL0 és az EL1 osztozik. x86_64-en az átváltás árát KVM-en nem mértük meg (TCG alatt a szám értelmetlen lenne), magának a workernek és a protokollnak a fuzzolása pedig nincs kész. A tárolási tesztcsoport (79 forgatókönyv, mindegyik az SQLite-ot forgatja a workerben) átmegy aarch64 HVF-en és x86_64 TCG-n, aarch64 TCG-n 76 megy át (egyet kihagy, mert a TCG-nek nincs MSI-X-e).</p>
<p><strong>A H7 lezárult, de nem úgy, ahogy bejelentettük.</strong> Az időalsó korlát (ADR-050) és a konszenzusos SNTP (T1) lefedi a H7 hatókörét; az NTS (RFC 8915) a hatókörön kívül marad, és megmaradó kockázatként van feljegyezve, nem működő dologként. Nem azt gondoljuk, hogy az idő biztonsága „megoldott", hanem azt, hogy a bizonyítható része kész.</p>
<p><strong>Új hálózati opció.</strong> A Contabo nem ad DHCP-t egyéni ISO-nak, ezért a VPS-hez addig más képfájl kellett, mint a laboratóriumi VM-hez. Mostantól a <code>network.mode = "auto"</code> (ADR-214) először a DHCP-t kérdezi, és ha a címbérlet nem érkezik meg a megadott időn belül (<code>dhcp_timeout_s</code>, alapértelmezés 15 s), vagy elvész, az alapértelmezett statikus címet alkalmazza. Kézzel kipróbálva QEMU-n (DHCP-vel a bérletet használja, hálózat nélkül 5 s után visszaáll a statikus címre); füstteszt-forgatókönyv ehhez még nincs.</p>
<h2 id="x11-osszevetes-a-mosquittoval-ugyanazon-a-vps-en">X11: összevetés a Mosquittóval ugyanazon a VPS-en</h2>
<p>Ugyanolyan gép, mint az előző írásban (Contabo, 4 vCPU AMD EPYC, 8 GiB), a terhelés egy Linux gazdagépről érkezik 15 ms távolságból, kölcsönös TLS 1.3 mindkét oldalon ugyanazokkal a tanúsítványokkal, Mosquitto 2.0.21 Debian 13-on, tartósság nélkül. A módszer és a döntések az ADR-213-ban vannak, a nyers adatok a <code>docs/perf-results/</code> könyvtárban. Három futtatás mediánja.</p>
<figure><img loading="lazy" alt="Teljes kézfogások másodpercenként (TLS 1.3 + kliens tanúsítvány + CONNECT, munkamenet-folytatás nélkül) 32, 64 és 128 kliensnél: urMQTT 169, 278, 413; Mosquitto 82, 140, 184. Ezek alsó korlátok: a terheléskeltő közel volt a saját korlátjához." src="https://aldo.tech/blog/urmqtt-config-builder-plaintext-mosquitto/img/mosquitto.svg" /><figcaption>Teljes kézfogások másodpercenként (TLS 1.3 + kliens tanúsítvány + CONNECT, munkamenet-folytatás nélkül) 32, 64 és 128 kliensnél: urMQTT 169, 278, 413; Mosquitto 82, 140, 184. Ezek alsó korlátok: a terheléskeltő közel volt a saját korlátjához.</figcaption></figure>
<table>
<thead>
<tr>
<th></th>
<th>urMQTT</th>
<th>Mosquitto 2.0.21</th>
</tr>
</thead>
<tbody>
<tr>
<td>kézfogás/s, 32 / 64 / 128 kliens (bench profil)</td>
<td>169 / 278 / 413</td>
<td>82 / 140 / 184</td>
</tr>
<tr>
<td>ugyanez, a szállított korlátokkal</td>
<td>185 / 262 / 333 (128-nál: 202–286 hiba, kézfogás-resetek)</td>
<td>-</td>
</tr>
<tr>
<td>memória inaktív kapcsolatonként (20 000 kapcsolat)</td>
<td>0,50 KB magheap (0,66 KB 5000-nél)</td>
<td>26,8 KB folyamatnövekedés</td>
</tr>
<tr>
<td>késleltetés p50, QoS 0 / QoS 1, 800 üzenet/s, 64 B</td>
<td>14,0 / 14,8 ms</td>
<td>21,4 / 39,5 ms</td>
</tr>
<tr>
<td>QoS 1, 4800 üzenet/s, kézbesítve/s (p50)</td>
<td>4209 (77 ms)</td>
<td>4235 (197 ms)</td>
</tr>
<tr>
<td>100 feliratkozó, 50 üzenet/s: p50</td>
<td>16,6 ms</td>
<td>99 ms</td>
</tr>
<tr>
<td>egy adatfolyam, QoS 0, 64 B, közzétéve/s</td>
<td>2962 (0–7733)</td>
<td>22 749</td>
</tr>
</tbody>
</table>
<p>Mit jelent ez, és mit nem. A kézfogások <strong>mindkét szerverre alsó korlátok</strong>: a terheléskeltő egy megosztott VM volt 60–70% ellopott processzoridővel, és 128 szálnál közel volt a saját korlátjához, az urMQTT munkamagjai pedig csak 12–13%-ban voltak foglaltak. A 128 kliensnél mért arány (körülbelül 2,2-szeres) a legértelmezhetőbb; 32-nél és 64-nél többnyire a kliensek várakozását méri. A memóriaszámok nem ugyanazt számolják (magheap szemben a folyamat RSS-ével), ezért a „körülbelül 50-szer kevesebb" nagyságrend, nem pontos arány. A 14 ms-os késleltetés egyetlen hálózati kör (körülbelül 15 ms), tehát „nincs mérhető brókeridő"; a farkakat (p99 körülbelül 100–200 ms) mindkét bróker esetében a megosztott gazdagépek uralják. A körülbelül 20% alatti különbségeket nem állítjuk.</p>
<p><strong>Ami nem nekünk kedvezett</strong>, és benne maradt a táblázatokban: egyetlen TCP-adatfolyam az urMQTT-n át 2–8-szor kevesebbet vitt, mint a Mosquittón át, a futtatások között nullától 7,7 ezer üzenet/másodpercig terjedő ingadozással, a kezelő 20 ms-os watchdogja pedig <strong>megszakította az élő kapcsolatokat</strong>, amikor a hipervizor leállította a vCPU-t (éles beállításban <code>action = "drop"</code>, futtatásonként 6–8 kapcsolat QoS 0 elárasztásnál). Mindkettőt a következő szakasz írja le, mert az okaik a mi kódunkban voltak.</p>
<h2 id="miert-volt-lassu-az-egy-adatfolyam-okok-nem-kifogasok">Miért volt lassú az egy adatfolyam: okok, nem kifogások</h2>
<p>A legkönnyebb az lett volna, hogy „megosztott VPS". Nem ezt tettük, hanem ugyanazon a VPS-en, ugyanazzal a módszerrel okokat kerestünk. Öt okot találtunk, négyet az adatúton és egyet a watchdogban (ADR-221–ADR-224):</p>
<table>
<thead>
<tr>
<th>Tünet az X11-ben</th>
<th>Ok az urMQTT-ben</th>
<th>Változtatás</th>
</tr>
</thead>
<tbody>
<tr>
<td>egy adatfolyam 1,9k üzenet/s, gyakran <strong>0 kézbesítve</strong></td>
<td><strong>Elveszett ébresztés:</strong> a kimeneti ciklus a kész kapcsolatok listáját felülírta az éppen kiírttal; az a feliratkozó, akinek a kimeneti puffere egyszer megtelt, soha többé nem kapott kiszolgálást, amíg egy PINGRESP fel nem ébresztette</td>
<td>javítva (ADR-223)</td>
</tr>
<tr>
<td>néhány százalék veszteség 48k-t 14k üzenet/s-ra ront</td>
<td><strong>minden elveszett szegmens utáni szegmens el lett dobva</strong> (csak sorrendben vétel), így a helyreállítás szegmensenként egy hálózati kör árába került</td>
<td>sorrenden kívüli szegmensek sora, SACK (ADR-222)</td>
</tr>
<tr>
<td>4,4 MB/s plafon 15 ms-nál</td>
<td>16 bites ablak, 64 KiB-os küldési sor és lassú indítás, amely az ACK-okat számolta, nem a bájtokat</td>
<td>ablakskálázás, időbélyegek, bájtszámlálás (ADR-222)</td>
</tr>
<tr>
<td>minden, a beleférő adatfolyam körei 0–10 ms-ot várnak</td>
<td><strong>a diszpécser a következő 10 ms-os ütemig aludt</strong> (a virtio-net-nek nincs megszakítása a vételhez)</td>
<td>a NIC lekérdezése 200 us-onként, amíg vannak kapcsolatok (ADR-224)</td>
</tr>
<tr>
<td>futtatásonként 1–8 megszakadt kapcsolat</td>
<td><strong>a watchdog a kezelőnek az eltelt időt számolta fel</strong>, a VPS pedig gyakran és sokáig leállítja a vCPU-kat</td>
<td>az időkeret az az idő, amelyet a mag ténylegesen futott (ADR-221)</td>
</tr>
</tbody>
</table>
<p>A leállások valódiak: ezen a VPS-en a 0–2. magoknak 5 másodpercenként medián 36–80 késett ütésük volt, az ablak legrosszabb leállása pedig medián körülbelül 70 ms, legfeljebb 0,9 s (3. mag, adatbázis és napló: legfeljebb 1,55 s), miközben a gazdagép nem jelent ellopott időt (<code>host steal 0 us</code>). Ezért a watchdognak második órára volt szüksége, nem nagyobb időkeretre.</p>
<figure><img loading="lazy" alt="Előtte és utána, ugyanazon a VPS-en ugyanazzal a módszerrel, három–hat futtatás mediánja: egy adatfolyam QoS 0, 64 B: közzétéve 1,9k, 23,7k (Mosquitto 29,3k), kézbesítve 0 (medián), 12,6k (29,2k). 16 pár, elárasztás, 64 B: közzétéve 24,7k, 43,2k (31,4k), kézbesítve 20,0k, 37,4k (30,7k)." src="https://aldo.tech/blog/urmqtt-config-builder-plaintext-mosquitto/img/before-after.svg" /><figcaption>Előtte és utána, ugyanazon a VPS-en ugyanazzal a módszerrel, három–hat futtatás mediánja: egy adatfolyam QoS 0, 64 B: közzétéve 1,9k, 23,7k (Mosquitto 29,3k), kézbesítve 0 (medián), 12,6k (29,2k). 16 pár, elárasztás, 64 B: közzétéve 24,7k, 43,2k (31,4k), kézbesítve 20,0k, 37,4k (30,7k).</figcaption></figure>
<table>
<thead>
<tr>
<th></th>
<th>X11-kód (7f9517e)</th>
<th>most (86a71ff)</th>
<th>Mosquitto</th>
</tr>
</thead>
<tbody>
<tr>
<td>egy adatfolyam, QoS 0, 64 B, közzétéve / kézbesítve s-onként</td>
<td>1,9k / 0</td>
<td>23,7k / 12,6k</td>
<td>29,3k / 29,2k</td>
</tr>
<tr>
<td>egy adatfolyam, QoS 0, 1 KiB</td>
<td>0 / 0</td>
<td>3,9k / 2,3k</td>
<td>5,4k / 5,4k</td>
</tr>
<tr>
<td>16 pár, elárasztás, QoS 0, 64 B</td>
<td>24,7k / 20,0k</td>
<td>43,2k / 37,4k</td>
<td>31,4k / 30,7k</td>
</tr>
<tr>
<td>16 pár, elárasztás, QoS 0, 1 KiB</td>
<td>3,7k / 2,9k</td>
<td>5,0k / 4,5k</td>
<td>5,9k / 5,7k</td>
</tr>
<tr>
<td>elveszett kapcsolatok futtatásonként (16 pár, 64 B)</td>
<td>13 (7–16)</td>
<td>0 (0–0)</td>
<td>0</td>
</tr>
</tbody>
</table>
<p>Watchdog: 148 megszakadt kapcsolat 30 terhelt futtatásban előtte, 0 a 24 futtatásban utána; egy helyi eszközön, amely késlelteti és veszíti a kereteket, 0,1% veszteségnél egy 64 B-os adatfolyam, amely 14,3k üzenet/s-ot adott (az X11 magja), most 41,0k-t ad, 0,5%-nál pedig 5,0k helyett 17,7k-t.</p>
<p><strong>Amit ez nem mond.</strong> Az urMQTT <strong>továbbra sem gyorsabb a Mosquittónál egy adatfolyamon</strong>: 64 B-nál a sebességének körülbelül 80%-át közli, és körülbelül 40%-át kézbesíti, mert eldobja a QoS 0-t, ha a feliratkozó adatfolyama lassabb a kiadóénál (a Mosquitto korlát nélkül sorba állítja). 1 KiB-nál a 16 páros elárasztásban a Mosquitto körülbelül 15%-kal előrébb van, ami a zajon belül van. A terheléskeltő ismét megosztott VM volt 58–77% ellopott idővel. A második kör (a heap zárolása az adatúton kívülre, SACK a küldési oldalon és más) a be nem épített <code>perf-single-flow-wip</code> ágon van; a számai még nem állítások, és itt nem közöljük őket.</p>
<h2 id="igeret-amelyet-nem-tettunk-de-teljesitettunk-a-konfiguracio-adat">Ígéret, amelyet nem tettünk, de teljesítettünk: a konfiguráció adat</h2>
<p>Eddig minden konfiguráció új build volt: a kulcsok és a korlátok bele voltak építve a képfájlba. A C1 ezt megfordítja (ADR-215–ADR-219): a magot egyszer építjük, a konfiguráció pedig adat.</p>
<ul>
<li><strong>Az <code>urmqtt-cfg</code> blob</strong> (C1a): verziózott, kanonikus írásmód (a mezők növekvő címkék szerint, SHA-256 ujjlenyomat, opcionális különálló Ed25519 aláírás), 70 mező alapértékekkel és korlátokkal, és egy dekóder, amely minden hibásat visszautasít.</li>
<li><strong>A mag Limine-modulként olvassa</strong> (C1b), bármi más előtt, aarch64-en és x86_64-en. <strong>Minden <code>ENV</code>-hez, a <code>dev</code> kivételével, egyetlen kiadott mag van</strong> (a <code>release</code>, <code>prod</code> és <code>bench</code> aarch64 ELF-jeinek azonos a SHA-256-ja; csak aarch64-en ellenőrizve). A kiadott mag visszautasítja a fejlesztői konfigurációt, konfiguráció nélkül pedig „refusing to serve" üzenetet ír, és semmit nem szolgál ki.</li>
<li><strong>A telepített lemez hordozza a konfigurációt</strong>, és a frissítés megtartja (C1c).</li>
<li><strong>Az <code>urmqtt-config</code> és az ISO-sablon</strong> (C1d): <code>pack</code>, <code>show</code>, <code>verify</code>, a <code>pack --iso</code> pedig a konfigurációs hely mindkét példányát és a 24 modulösszegzést cseréli a kész ISO-ban. Az eredmény bájtról bájtra azonos azzal az ISO-val, amelyet a <code>mkiso.sh --config</code> épít. A v0.2.0 kiadás sablonja 63 111 168 bájt.</li>
<li><strong>Bizalmi nyilatkozat</strong> (ADR-219): a konfigurált képfájl induláskor kiírja a konfiguráció ujjlenyomatát (<code>config: fingerprint ...</code>), az aláírás pedig opcionális és <strong>alapértelmezés szerint ki van kapcsolva</strong>. A módosított ISO-nak nincs hivatalos összegzése; az ujjlenyomat az eszköz arra, hogy lássuk, mi van benne.</li>
</ul>
<h2 id="az-epito-az-aldotech-en">Az építő az aldo.tech-en</h2>
<p>Ugyanazt, amit az <code>urmqtt-config pack --iso</code> csinál, a weboldal is csinálja, ugyanazzal a kóddal: az eszközkönyvtár WebAssemblyre fordítva (1,06 MB, importok nélkül és <code>unsafe</code> blokk nélkül). Az oldal a <a href="https://aldo.tech/hu/termekek/urmqtt-builder/">aldo.tech/hu/termekek/urmqtt-builder/</a> címen érhető el, és az oldal mind a 15 nyelvén létezik (például <a href="https://aldo.tech/proizvodi/urmqtt-builder/">hr</a>).</p>
<ul>
<li><strong>Semmi nem hagyja el a böngészőt.</strong> A tanúsítványok, a privát kulcsok és a kész ISO a számítógépen maradnak; az oldal csak a kiadási fájlokat tölti le erről a helyről, nincs követés, és nincs harmadik fél szkriptje. Az élő tesztben egyetlen kérés sem vitt kulcsanyagot.</li>
<li><strong>Kiadási tükör</strong> (ADR-227): óránként (systemd timer, alacsony prioritású) átmásolja a kiadásokat a GitHubról, mindegyiket közzététel előtt ellenőrzi, az oldal pedig a böngészőben az oldalba beépített kulccsal ellenőrzi a <code>SHA256SUMS</code> aláírását, és csak azután írja a konfigurációt a sablonba. A sikertelen ellenőrzés blokkolja a letöltést.</li>
<li><strong>Alapértelmezésben egyszerű, szükség esetén haladó</strong> (ADR-232): az első képernyő három lépés (hol fut, az Ön tanúsítványai, a képfájl neve). Egyetlen húzásos mező a tartalma alapján szétválogatja a tanúsítványt, a kulcsot és a CA-t. A haladó nézet megnyitja mind a 70 mezőt, keresést, valamint a TOML importját és exportját; mindkét nézet ugyanazt az állapotot szerkeszti. A sablon (63 MB) letöltődik, amíg a felhasználó tölti az űrlapot. Az élő próbában, próbatükörrel és próbakulccsal az ISO böngészőbeli felépítése 5–18 s-ig tartott.</li>
</ul>
<figure><img loading="lazy" alt="Az építő egyszerű nézete: három lépés, „Hol fog futni?&quot;, „Az Ön tanúsítványai&quot; és „A képfájl neve&quot;, egyetlen következő művelettel az oldalsávban." src="https://aldo.tech/blog/urmqtt-config-builder-plaintext-mosquitto/img/builder-simple.png" /><figcaption>Az építő egyszerű nézete: három lépés, „Hol fog futni?&quot;, „Az Ön tanúsítványai&quot; és „A képfájl neve&quot;, egyetlen következő művelettel az oldalsávban.</figcaption></figure>
<figure><img loading="lazy" alt="Haladó nézet, próbatükrön, próbakulccsal rögzítve (a kiadás dátuma és az aláírókulcs nem valódi): ellenőrzött aláírású kiadás, sablonok, a konfiguráció minden mezője." src="https://aldo.tech/blog/urmqtt-config-builder-plaintext-mosquitto/img/builder-advanced.png" /><figcaption>Haladó nézet, próbatükrön, próbakulccsal rögzítve (a kiadás dátuma és az aláírókulcs nem valódi): ellenőrzött aláírású kiadás, sablonok, a konfiguráció minden mezője.</figcaption></figure>
<p><strong>Az emulátor, opcionálisan.</strong> A „ISO letöltése" mellett ott áll az „Kipróbálás emulátorban": a WebAssemblyre fordított QEMU a frissen épített képfájlt futtatja egy böngészőlapon. A kattintás előtt semmi nem töltődik le belőle (először körülbelül 3 MB). Az emulátor csak BIOS-os, <strong>hálózat nélküli</strong> (hogy a felhasználó forgalma ne haladjon át valamilyen közvetítőn, és ne sértse a „semmi nem hagyja el a böngészőt" elvet), körülbelül 1,5 GB memóriát kér, és asztali böngészőt igényel (Chrome-mal tesztelve; Firefoxszal és Safarival nem). Benne az urMQTT körülbelül 8 s alatt eljut a <code>URMQTT READY</code> üzenetig (a visszaszámlálás átugrásával), és ugyanazt a konfigurációs ujjlenyomatot írja ki, mint az oldal, ami bizonyíték arra, hogy éppen ez a konfiguráció indult el. Az indulás körülbelül 2-szer lassabb, mint az eredeti QEMU ugyanazon a Macen (0,82 s szemben 1,9 s-mal), a kriptográfia pedig körülbelül 58-szor lassabb, mert a hardveres utasítások emuláltak. Ezért <strong>az emulátorból semmilyen sebességre vonatkozó állítást nem vonunk le</strong>, az oldal pedig ezt a futtatás előtt kimondja. A build során négy bájtot kellett foltozni a <code>.wasm</code>-ban az eredeti emulátorban a POPCNT utasítás fordításában lévő hiba miatt (még nem jelentettük be); maga az emulátor GPL alatt áll, a forráskód felkínálásával.</p>
<figure><img loading="lazy" alt="Diagnosztikai bejegyzés az emulátorban élőben az aldo.tech-en: a kernel ugyanazt a konfigurációs ujjlenyomatot írta ki, mint az oldal, a képernyő pedig a diagnosztikát mutatja (az emulátorban körülbelül 20 s alatt ér véget; a számai az emulátort írják le, nem az urMQTT-t)." src="https://aldo.tech/blog/urmqtt-config-builder-plaintext-mosquitto/img/emulator-diagnostic.png" /><figcaption>Diagnosztikai bejegyzés az emulátorban élőben az aldo.tech-en: a kernel ugyanazt a konfigurációs ujjlenyomatot írta ki, mint az oldal, a képernyő pedig a diagnosztikát mutatja (az emulátorban körülbelül 20 s alatt ér véget; a számai az emulátort írják le, nem az urMQTT-t).</figcaption></figure>
<h2 id="sima-mqtt-csak-olyan-halozatokra-amelyekben-megbizunk">Sima MQTT, csak olyan hálózatokra, amelyekben megbízunk</h2>
<p>A sima MQTT (TCP 1883, TLS nélkül) nagyon gyakori az IoT-ban: alapértelmezett a Mosquittóban, az Arduino és ESP legtöbb firmware-ében, a Tasmotában, a Zigbee2MQTT-ben és a Home Assistantben, használják szegmentált ipari hálózatokban, VPN mögött és privát LAN-okon, sőt olyan eszközökön is, amelyeknek nincs órájuk (a tanúsítványokhoz dátum kell), vagy nincs elég flashük vagy processzoruk a TLS-hez. Megbízhatatlan hálózaton nem elfogadható: aki az úton van, olvassa és módosítja a forgalmat. Ezért a P1 (ADR-229–ADR-231) kifejezett, korlátozott választás, <strong>alapértelmezés szerint kikapcsolva</strong>, a TLS port pedig megmarad; ezzel a TLS nem kapcsol ki, a sima port mellette érkezik.</p>
<ul>
<li><strong>A megengedett hálózatok listája kötelező</strong> (<code>allow_from</code>, CIDR-ek), és SYN-nél ellenőrzik: a listán kívül nincs SYN-ACK és nincs állapot. A teljes internethez (<code>0.0.0.0/0</code>) külön, kifejezett opció kell.</li>
<li><strong>Három bejelentkezési mód:</strong> jelszó (alapértelmezett, ha be van kapcsolva; PBKDF2-HMAC-SHA-256, só, 10 000 iteráció, állandó idejű összehasonlítás, zárolás cím szerint), anonim (egy beállított identitás saját ACL-lel) és cím szerint (<code>ip</code>). Az építő a jelszót a böngészőben hasheli, és a memóriában csak az összegzést tartja.</li>
<li><strong>Kevesebb jog, mint az mTLS-nél:</strong> a sima kliensnek soha nincs szolgáltatási, üzemeltetői és vezérlési joga, nincs <code>$SYS</code>-e és konzolja, saját munkamenetei vannak (<code>$plain/&lt;identitás&gt;/&lt;kliens azonosítója&gt;</code>, csak tiszták), és a saját korlátok és az engedélyezettek jegyzéke továbbra is kötelezi.</li>
<li><strong>Amit elveszítünk, a <code>SECURITY.md</code>-ben áll:</strong> a bizalmasság, az integritás, a bróker hitelessége és a rögzített bejelentkezés újrajátszása. Ezt konfigurációval nem lehet kijavítani; ilyen, ha nincs TLS.</li>
</ul>
<p>Laptopon mérve (Apple silicon, HVF, QEMU felhasználói hálózat), 16 pár, 64 B, három 10 s-os futtatás: 8000 üzenet/s mellett a TLS-es és a sima út minden üzenetet azonos késleltetéssel kézbesít (p50 0,17 ms szemben 0,13–0,18 ms-mal), új kapcsolatok másodpercenként: TLS körülbelül 2600–2750, anonim sima körülbelül 4900–7800, a <strong>jelszavas bejelentkezés pedig a drága rész</strong>: egy PBKDF2 körülbelül 2,5 ms-ba kerül, ezért az új bejelentkezések száma körülbelül 200-ra korlátozódik adatmagonként másodpercenként (két magon 308–342-t mértünk). Az elárasztás azon a gépen nem stabil műszer (20k–440k kézbesített másodpercenként a futtatások között), és <strong>belőle nem vonunk le következtetést</strong>. A sima út összevetése a Mosquittóval VPS-en még nem készült el. Tesztek: a <code>plaintext</code> füstteszt-csoport 7 a 7-ből aarch64 HVF-en és x86_64 TCG-n, új fuzz-cél a <code>plain_input</code> (2,3 millió futtatás lelet nélkül).</p>
<h2 id="hibak-amelyeket-az-elo-uzem-talalt">Hibák, amelyeket az élő üzem talált</h2>
<p>Két dolgot nem a tesztek fogtak meg, hanem az első bekapcsolás a valódi helyen:</p>
<ul>
<li>Az oldal a kiadási fájlokat <strong>egy könyvtárral feljebb</strong> kereste, ezért minden aláírás-ellenőrzés 404-et adott (javítás: 13af301). A teszt egy másik útvonalon lévő próbatükröt használt.</li>
<li><strong>Az emulátor billentyűzete soha nem jutott el a vendégig</strong>: az ok nem a billentyűk rossz fordítása volt, hanem az, hogy a párbeszédablak egyáltalán nem figyelte a billentyűket (javítás az ADR-232-ben, billentyűk nélküli indítási bejegyzéssel).</li>
</ul>
<h2 id="tanulsagok">Tanulságok</h2>
<ul>
<li><strong>Az okot keressük, ne a környezetet hibáztassuk.</strong> Az egy adatfolyam gyengeségeinek többsége és az összes megszakadt kapcsolat a miénk volt; a hipervizor valódi, de mérni kellett, nem feltételezni.</li>
<li><strong>Egy másik termékkel való összevetésnek a veszteségeket is tartalmaznia kell.</strong> Egy táblázat, amelyben csak a mi győzelmeink szerepelnek, nem lenne összevetés.</li>
<li><strong>A mérőműszernek vannak határai.</strong> Egy megosztott terheléskeltő 60–70% ellopott idővel megváltoztatja, mit jelentenek a számok; ezért írunk „alsó korlátot", és nem állítunk 20% alatti különbségeket.</li>
<li><strong>A szigetelés nem bizonyíték.</strong> Az SQLite EL0-n azt csökkenti, mit tehet egy hiba benne, nem azt, miről hazudhat.</li>
<li><strong>A valódi telepítés megtalálja azt, amit a tesztek nem kérdeznek</strong>: a rosszul összerakott útvonalat és a billentyűket, amelyeket senki nem figyel.</li>
</ul>
<h2 id="ami-meg-nincs-bizonyitva">Ami még nincs bizonyítva</h2>
<ul>
<li>A kiadás aláírókulcsa egyelőre próbakulcs; a v0.2.0 és a v0.2.1 pre-release, a hozzájuk tartozó képfájlok pedig fejlesztői képfájlok próbatanúsítványokkal (a sablon kivételével, amely konfiguráció nélküli).</li>
<li>Az építőoldal 13 fordítását (és ezét az írásét) AI írta, és tesztek meg mintavételek ellenőrizték, nem anyanyelvi beszélők.</li>
<li>A Mosquittóval való összevetés egy ülés, egy út és egy megosztott terheléskeltő, megosztott VPS-en; a 20% alatti különbségek nem állítások.</li>
<li>A sima utat nem mértük VPS-en, és nem a Mosquitto ellen.</li>
<li>A Lighthouse az új egyszerű nézeten nincs megerősítve (a régi nézet: akadálymentesség 95 vagy több).</li>
<li>Bare metal, a worker x86_64-es ára KVM-en, Firefox és Safari az emulátorhoz: nincs mérve.</li>
</ul>
<h2 id="mi-kovetkezik">Mi következik</h2>
<ul>
<li><strong>D1: nyilvános demo bróker.</strong> A böngészős emulátort nem tehetjük nyilvánossá: egyetlen látogató lapján él, 20–100-szor lassabb a KVM-nél, nincs bejövő hálózata, és a lap bezárásával eltűnik; az internet átirányítása valaki más böngészőjébe ráadásul visszaélés lenne. Lehetséges egy valódi demo bróker: az urMQTT egy VPS-en, az aldo.tech egyik neve mögött, közzétett próba-CA-val, szigorú ACL-lel és kis korlátokkal. Ez egyelőre csak ötlet, nem építés, és kifejezett hozzájárulás kell a nyilvános kitételhez.</li>
<li><strong>A második kör az egy adatfolyamhoz</strong> (<code>perf-single-flow-wip</code> ág): csak akkor, ha VPS-en megmértük és beépítettük.</li>
<li><strong>A sima út a Mosquitto ellen VPS-en</strong>, a sima MQTT-hez a fiókok bejelentkezése és visszavonása aláírt vezérlőcsatornán át.</li>
<li><strong>A kiadás után:</strong> bare metal (X12–X16), IOMMU és Secure Boot, lemeztitkosítás.</li>
</ul>
<p>A kód a <a href="https://github.com/zeljan-alduk/urMQTT">github.com/zeljan-alduk/urMQTT</a> címen van, a <code>docs/PROGRESS.md</code> pedig továbbra is az egyetlen igazságforrás arról, mi működik valóban, a <code>docs/PERFORMANCE.md</code> arról, mit mértünk. Pre-alfa, mint korábban; a hibabejelentéseket és a biztonsági bejelentéseket a <code>SECURITY.md</code> útmutatása szerint szívesen fogadjuk.</p>]]></content:encoded>
      <category>rust</category>
      <category>mqtt</category>
      <category>iot</category>
      <category>unikernel</category>
      <category>open source</category>
    </item>
    <item>
      <title>urMQTT valódi VPS-en: ISO, 0,24 ms-os kézfogás, saját lemez és megerősített kernel</title>
      <link>https://aldo.tech/hu/blog/urmqtt-vps-handshake-disk-hardening/</link>
      <guid isPermaLink="true">https://aldo.tech/hu/blog/urmqtt-vps-handshake-disk-hardening/</guid>
      <pubDate>Sat, 31 Oct 2026 08:00:00 +0000</pubDate>
      <description>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.</description>
      <content:encoded><![CDATA[<p>Az <a href="https://aldo.tech/blog/urmqtt-unikernel-mqtt-broker/">urMQTT-ről szóló első írásban</a> 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.</p>
<p>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.</p>
<h2 id="hogyan-nezett-ki-ez-az-idoben">Hogyan nézett ki ez az időben</h2>
<p>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 <code>Co-Authored-By: Claude</code> 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.</p>
<figure><img loading="lazy" alt="A projekt növekedése négy napon át az M0-tól a mai napig terjedő mérföldkövekkel: Rust-sorok ezrekben (1,6-ról 89,8-ra), a #[test] függvények száma (27-ről 739-re) és az ADR-ek száma (13-ról 104-re), valamint a napi commitokat mutató oszlopok (39, 38 és 39 október 29-én, 30-án és 31-én)." src="https://aldo.tech/blog/urmqtt-vps-handshake-disk-hardening/img/growth.svg" /><figcaption>A projekt növekedése négy napon át az M0-tól a mai napig terjedő mérföldkövekkel: Rust-sorok ezrekben (1,6-ról 89,8-ra), a #[test] függvények száma (27-ről 739-re) és az ADR-ek száma (13-ról 104-re), valamint a napi commitokat mutató oszlopok (39, 38 és 39 október 29-én, 30-án és 31-én).</figcaption></figure>
<p>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).</p>
<table>
<thead>
<tr>
<th>Mérföldkő</th>
<th>Commit</th>
<th>Időpont</th>
<th style="text-align: right;">Rust-sorok</th>
<th style="text-align: right;">Tesztek</th>
<th style="text-align: right;">ADR-ek</th>
</tr>
</thead>
<tbody>
<tr>
<td>M0</td>
<td>9412a9c</td>
<td>10.28. 23:48</td>
<td style="text-align: right;">1 592</td>
<td style="text-align: right;">27</td>
<td style="text-align: right;">13</td>
</tr>
<tr>
<td>M5</td>
<td>a61083b</td>
<td>10.29. 01:32</td>
<td style="text-align: right;">9 314</td>
<td style="text-align: right;">88</td>
<td style="text-align: right;">24</td>
</tr>
<tr>
<td>M9</td>
<td>4be8cc9</td>
<td>10.29. 02:21</td>
<td style="text-align: right;">15 729</td>
<td style="text-align: right;">149</td>
<td style="text-align: right;">28</td>
</tr>
<tr>
<td>M11</td>
<td>4800904</td>
<td>10.29. 03:43</td>
<td style="text-align: right;">22 737</td>
<td style="text-align: right;">180</td>
<td style="text-align: right;">30</td>
</tr>
<tr>
<td>M13</td>
<td>a558d5e</td>
<td>10.29. 05:22</td>
<td style="text-align: right;">26 556</td>
<td style="text-align: right;">193</td>
<td style="text-align: right;">32</td>
</tr>
<tr>
<td>M17</td>
<td>7df4f98</td>
<td>10.29. 08:48</td>
<td style="text-align: right;">33 504</td>
<td style="text-align: right;">232</td>
<td style="text-align: right;">36</td>
</tr>
<tr>
<td>S1</td>
<td>ecf72ec</td>
<td>10.30. 00:46</td>
<td style="text-align: right;">41 910</td>
<td style="text-align: right;">299</td>
<td style="text-align: right;">45</td>
</tr>
<tr>
<td>X8</td>
<td>fb1504a</td>
<td>10.30. 19:08</td>
<td style="text-align: right;">56 341</td>
<td style="text-align: right;">456</td>
<td style="text-align: right;">59</td>
</tr>
<tr>
<td>PERF</td>
<td>76f7c36</td>
<td>10.31. 14:19</td>
<td style="text-align: right;">72 511</td>
<td style="text-align: right;">618</td>
<td style="text-align: right;">76</td>
</tr>
<tr>
<td>ma</td>
<td>HEAD</td>
<td>10.31. 23:00</td>
<td style="text-align: right;">89 790</td>
<td style="text-align: right;">739</td>
<td style="text-align: right;">104</td>
</tr>
</tbody>
</table>
<p>A sorok a <code>vendor/</code> és a <code>fuzz/</code> mappán kívüli összes <code>.rs</code> fájl, a tesztek pedig a <code>#[test]</code> függvények. Hogy melyik rész mit adott hozzá, a git-előzmények dátumai szerint:</p>
<table>
<thead>
<tr>
<th>Dátum</th>
<th>Mérföldkövek</th>
<th>Mi készült el</th>
</tr>
</thead>
<tbody>
<tr>
<td>10.29.</td>
<td>M0–M17</td>
<td>kernel, hálózat, TLS, bróker, SQLite, több mag, saját TCP, megerősítés, reprodukálható kiadás</td>
</tr>
<tr>
<td>10.29. este és 10.30.</td>
<td>X0–X10, S1, H2, H3 (az X0 10.29-én 20:54-kor indul)</td>
<td>x86_64 port, univerzális ISO, egyetlen lemez és öntelepítő ISO, TLS-ellenőrzések, támadó kliensek</td>
</tr>
<tr>
<td>10.31.</td>
<td>O1–O4, T1, PERF, H4, H5, H9, S2–S6</td>
<td>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</td>
</tr>
</tbody>
</table>
<h2 id="1-igeret-egyetlen-iso-tobb-platformra">1. ígéret: egyetlen ISO több platformra</h2>
<p><strong>Mit terveztünk.</strong> Egyetlen fájl, amely elindul aarch64 UEFI-n, x86_64 UEFI-n és x86_64 BIOS-on. <strong>Mit építettünk.</strong> 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).</p>
<figure><img loading="lazy" alt="Az ISO indítómenüje a VPS-en rögzítve: az „urMQTT&quot;, az „Install urMQTT on this machine's disk&quot; és a „Diagnostic: hardware detection and benchmarks&quot; tétel, visszaszámlálással az automatikus indításig. A felvétel egy hosszabb visszaszámlálású teszt-ISO-ból származik; az alapértelmezés 10 másodperc." src="https://aldo.tech/blog/urmqtt-vps-handshake-disk-hardening/img/boot-menu.png" /><figcaption>Az ISO indítómenüje a VPS-en rögzítve: az „urMQTT&quot;, az „Install urMQTT on this machine's disk&quot; és a „Diagnostic: hardware detection and benchmarks&quot; tétel, visszaszámlálással az automatikus indításig. A felvétel egy hosszabb visszaszámlálású teszt-ISO-ból származik; az alapértelmezés 10 másodperc.</figcaption></figure>
<p>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.</p>
<p>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 <code>WIPE</code> begépelése után; az alábbi felvételen ezt kihagytuk.</p>
<figure><img loading="lazy" alt="A diagnosztikai képernyő egy Contabo VPS-en (AMD EPYC, KVM, 4 vCPU): rendszer, memória, lemez, PCI-eszközök, a RAM áteresztőképessége és késleltetése, valamint kriptográfiai mérések, köztük 244 us mTLS-kézfogásonként, X25519 48 us, ECDSA-aláírás 35,7 us és ellenőrzés 107 us." src="https://aldo.tech/blog/urmqtt-vps-handshake-disk-hardening/img/diag-vps.png" /><figcaption>A diagnosztikai képernyő egy Contabo VPS-en (AMD EPYC, KVM, 4 vCPU): rendszer, memória, lemez, PCI-eszközök, a RAM áteresztőképessége és késleltetése, valamint kriptográfiai mérések, köztük 244 us mTLS-kézfogásonként, X25519 48 us, ECDSA-aláírás 35,7 us és ellenőrzés 107 us.</figcaption></figure>
<p><strong>Mi maradt nyitva.</strong> 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.</p>
<h2 id="kezfogas-12-ms-rol-024-ms-ra">Kézfogás: 1,2 ms-ról 0,24 ms-ra</h2>
<p>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.</p>
<ul>
<li>A P-256-hoz átvettük az <code>nistz256</code> assemblert 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).</li>
<li>Az X25519-et négy 64 bites végtagra írtuk át MULX/ADX utasításokkal (ADR-131).</li>
<li>Ezután a maradékot is profiloztuk (ADR-132, ADR-200).</li>
</ul>
<figure><img loading="lazy" alt="Kézfogás a VPS-en: 1200 us előtte, 237 us az assembler és a táblák után; A/B összehasonlítás a memcpy-ra ugyanazon a futáson, 278,8 us a régivel, 229,4 us az újjal; valamint előtte és utána az X25519-re (146-ról 48 us-ra), az ECDSA-aláírásra (673-ról 34 us-ra) és az ellenőrzésre (309-ről 90 us-ra)." src="https://aldo.tech/blog/urmqtt-vps-handshake-disk-hardening/img/handshake.svg" /><figcaption>Kézfogás a VPS-en: 1200 us előtte, 237 us az assembler és a táblák után; A/B összehasonlítás a memcpy-ra ugyanazon a futáson, 278,8 us a régivel, 229,4 us az újjal; valamint előtte és utána az X25519-re (146-ról 48 us-ra), az ECDSA-aláírásra (673-ról 34 us-ra) és az ellenőrzésre (309-ről 90 us-ra).</figcaption></figure>
<table>
<thead>
<tr>
<th>Mérés a VPS-en (szerver, mikroszekundum)</th>
<th style="text-align: right;">Előtte</th>
<th style="text-align: right;">Utána</th>
</tr>
</thead>
<tbody>
<tr>
<td>mTLS-kézfogás, a teljes kriptográfia</td>
<td style="text-align: right;">1200</td>
<td style="text-align: right;">körülbelül 240 (237 a grafikon mérésében, 244 a felvételen)</td>
</tr>
<tr>
<td>X25519</td>
<td style="text-align: right;">146</td>
<td style="text-align: right;">48</td>
</tr>
<tr>
<td>ECDSA-aláírás</td>
<td style="text-align: right;">673</td>
<td style="text-align: right;">34</td>
</tr>
<tr>
<td>ECDSA-ellenőrzés</td>
<td style="text-align: right;">309</td>
<td style="text-align: right;">90 (107 a felvételen)</td>
</tr>
<tr>
<td>kézfogás, régi memcpy, ugyanaz a boot</td>
<td style="text-align: right;">278,8</td>
<td style="text-align: right;">229,4 (-17,7%)</td>
</tr>
</tbody>
</table>
<p>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.</p>
<p>Amit a memcpy-ról tanultunk. Miután a kriptográfia lecsökkent, a profil olyat mutatott, amire nem számítottunk: a <code>mem*</code> 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.</p>
<figure><img loading="lazy" alt="Balra: a szerver idejének mem* függvényekben töltött aránya az átírás előtt (kézfogás 7%, TLS-rekord 64 B 39%, 256 B 37%, 1 KiB 24%, szétosztás 17–34%). Jobbra: mennyivel gyorsabb utána az egyes út (kézfogás 17,7%, 64 B 26,4%, 256 B 19,6%, 1 KiB 16,1%, 4 KiB 6,6%, szétosztás 7–34%)." src="https://aldo.tech/blog/urmqtt-vps-handshake-disk-hardening/img/hotpaths.svg" /><figcaption>Balra: a szerver idejének mem* függvényekben töltött aránya az átírás előtt (kézfogás 7%, TLS-rekord 64 B 39%, 256 B 37%, 1 KiB 24%, szétosztás 17–34%). Jobbra: mennyivel gyorsabb utána az egyes út (kézfogás 17,7%, 64 B 26,4%, 256 B 19,6%, 1 KiB 16,1%, 4 KiB 6,6%, szétosztás 7–34%).</figcaption></figure>
<table>
<thead>
<tr>
<th>Út</th>
<th style="text-align: right;">A mem* aránya előtte</th>
<th style="text-align: right;">Gyorsabb utána</th>
</tr>
</thead>
<tbody>
<tr>
<td>kézfogás</td>
<td style="text-align: right;">7%</td>
<td style="text-align: right;">17,7%</td>
</tr>
<tr>
<td>TLS-rekord 64 B</td>
<td style="text-align: right;">39%</td>
<td style="text-align: right;">26,4%</td>
</tr>
<tr>
<td>TLS-rekord 256 B</td>
<td style="text-align: right;">37%</td>
<td style="text-align: right;">19,6%</td>
</tr>
<tr>
<td>TLS-rekord 1 KiB</td>
<td style="text-align: right;">24%</td>
<td style="text-align: right;">16,1%</td>
</tr>
<tr>
<td>TLS-rekord 4 KiB</td>
<td style="text-align: right;">13%</td>
<td style="text-align: right;">6,6%</td>
</tr>
<tr>
<td>üzenetek szétosztása</td>
<td style="text-align: right;">17–34%</td>
<td style="text-align: right;">7–34%</td>
</tr>
</tbody>
</table>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<h2 id="2-igeret-nyers-lemez-de-korultekintoen">2. ígéret: nyers lemez, de körültekintően</h2>
<p><strong>Mit terveztünk.</strong> 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. <strong>Miért nem fájlrendszer.</strong> 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.</p>
<p><strong>Mit építettünk (S2–S6).</strong></p>
<ul>
<li><strong>Elrendezés v2 (S2).</strong> 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 <code>urmqtt-disk migrate</code> offline 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.</li>
<li><strong>Növekedés (S3).</strong> 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).</li>
<li><strong>Discard és write-zeroes (S4).</strong> 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.</li>
<li><strong>Az adatbázis biztonsági mentése a lemezen (S5).</strong> 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 <code>quick_check</code> induláskor hibázik, az adatbázis a másolatból áll vissza.</li>
<li><strong>Tisztítás és korlátozott indulás (S6).</strong> 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.</li>
</ul>
<p>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.</p>
<p><strong>Mi lepett meg.</strong> 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.</p>
<p><strong>Mi maradt nyitva.</strong> 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.</p>
<h2 id="3-igeret-bizonyithatoan-biztonsagos-amennyire-csak-sikerul">3. ígéret: bizonyíthatóan biztonságos, amennyire csak sikerül</h2>
<p><strong>Megerősítés (H4).</strong> 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 (<code>-Zstack-protector=strong</code>, 1315 függvény, az SQLite pedig <code>-fstack-protector-strong</code> 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).</p>
<p><strong>Az ár megmérve, nem feltételezve.</strong> A kézfogás útján (<code>dp-bench</code>, aarch64 HVF, teljes TLS 1.3 + mTLS + CONNECT kézfogások):</p>
<table>
<thead>
<tr>
<th>Lépés</th>
<th style="text-align: right;">1 mag</th>
<th style="text-align: right;">4 mag</th>
<th>Változás</th>
</tr>
</thead>
<tbody>
<tr>
<td>megerősítés nélkül</td>
<td style="text-align: right;">1941</td>
<td style="text-align: right;">7620</td>
<td></td>
</tr>
<tr>
<td>+ allokátor</td>
<td style="text-align: right;">1898</td>
<td style="text-align: right;">7429</td>
<td>-2,2% – -2,5%</td>
</tr>
<tr>
<td>+ veremvédelem</td>
<td style="text-align: right;">1845</td>
<td style="text-align: right;">7243</td>
<td>együtt -4,9% – -5%</td>
</tr>
<tr>
<td>+ PIE és címkék</td>
<td style="text-align: right;">körülbelül 1800</td>
<td style="text-align: right;">körülbelül 6750</td>
<td>terhelt gépen, zajos</td>
</tr>
</tbody>
</table>
<p>Ö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.</p>
<p>É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.</p>
<p><strong>Hogyan ellenőriztük, és mit nem.</strong> 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 <code>record_len</code> függvényt a TLS-ben, a <code>parse</code> függvényt a TCP-ben és az időzítőkerék két aritmetikáját. <strong>Nincs bebizonyítva</strong> (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 <code>cargo-vet</code> á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 <code>VULNERABILITY-HANDLING.md</code> 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.</p>
<p>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.</p>
<h2 id="macen-aarch64-nativan-es-x86_64-emulacioval">Macen: aarch64 natívan és x86_64 emulációval</h2>
<p>Macen fejlesztünk, és ennek két módja van ugyanannak a kódnak a futtatására, amelyek nagyon különböznek.</p>
<table>
<thead>
<tr>
<th></th>
<th>aarch64 HVF alatt</th>
<th>x86_64 TCG alatt</th>
</tr>
</thead>
<tbody>
<tr>
<td>Mi ez</td>
<td>natív végrehajtás, a valódi processzorhoz közeli sebesség</td>
<td>a QEMU emulálja az utasításokat</td>
</tr>
<tr>
<td>Kézfogás a vendégben</td>
<td>körülbelül 0,58 ms (először az M13 körül mérve); a <code>dp-bench</code> ma 1941/s magonként</td>
<td>körülbelül 3,3 ms</td>
</tr>
<tr>
<td>TLS füstteszt-forgatókönyvek</td>
<td>gyorsak</td>
<td>körülbelül 15 perc forgatókönyvenként</td>
</tr>
<tr>
<td>MTE</td>
<td>nem (a HVF-en nincs)</td>
<td>az x86-nak nincs; a heap jelölése csak aarch64 vendégen működik TCG alatt</td>
</tr>
<tr>
<td>Mire való</td>
<td>valódi mérések</td>
<td>helyesség: az x86 utat ellenőrizzük, a számok nem teljesítményt jelentenek</td>
</tr>
</tbody>
</table>
<p>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.</p>
<h2 id="tanulsagok">Tanulságok</h2>
<ul>
<li><strong>Mérjen valódi gépen, mielőtt optimalizál.</strong> 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ő.</li>
<li><strong>A túlterhelés külön tulajdonság.</strong> Az a rendszer, amely terhelés alatt gyors, felette összeomolhat; az elutasításnak olcsóbbnak kell lennie az elfogadásnál.</li>
<li><strong>A profil meglep.</strong> A kis üzenetek legnagyobb egyedi nyeresége nem a kriptográfiában volt, hanem a memóriamásolásban.</li>
<li><strong>A fájlrendszer nélküli lemez több munkát kér, nem kevesebbet.</strong> 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.</li>
<li><strong>Őszinteség a számokban.</strong> 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.</li>
<li><strong>A fejlesztés sebessége nem érettség.</strong> A tesztek és az ADR-ek a kóddal együtt nőnek, de négy nap nem valódi üzem.</li>
</ul>
<h2 id="mi-kovetkezik">Mi következik</h2>
<ul>
<li><strong>H6:</strong> az SQLite EL0 szinten izolálva, hogy egy adatbázishiba ne nyissa meg a kernelt.</li>
<li><strong>H7:</strong> az időbiztonság maradéka (NTS és az ugrások kezelése); az SNTP csak az első lépés.</li>
<li><strong>X11:</strong> ö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.</li>
<li><strong>Kiadás:</strong> az első nyilvános megjelenés VPS-ekre (KVM), aláírt artefaktumokkal és útmutatókkal.</li>
<li><strong>A kiadás után:</strong> bare metal (X12–X16), IOMMU és Secure Boot, valamint lemeztitkosítás.</li>
</ul>
<p>A kód a <a href="https://github.com/zeljan-alduk/urMQTT">github.com/zeljan-alduk/urMQTT</a> címen érhető el, a <code>docs/PROGRESS.md</code> 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 <code>SECURITY.md</code> útmutatása szerint szívesen fogadjuk.</p>]]></content:encoded>
      <category>rust</category>
      <category>mqtt</category>
      <category>iot</category>
      <category>unikernel</category>
      <category>open source</category>
    </item>
    <item>
      <title>urMQTT: olyan MQTT brókert építünk, amely egyben operációs rendszer is</title>
      <link>https://aldo.tech/hu/blog/urmqtt-unikernel-mqtt-broker/</link>
      <guid isPermaLink="true">https://aldo.tech/hu/blog/urmqtt-unikernel-mqtt-broker/</guid>
      <pubDate>Thu, 29 Oct 2026 08:00:00 +0000</pubDate>
      <description>Fejlesztési jegyzetek az urMQTT-ről, egy Rustban írt unikernelről, amely önmagában MQTT bróker: egyetlen ELF saját TCP/IP-vel, TLS 1.3-mal, brókerrel és SQLite-tal, egyetlen ISO-ról indítva.</description>
      <content:encoded><![CDATA[<p>A járművekben, a parkolókban és az elektronikai szekrényekben működő eszközökben van egy közös vonás: kis üzeneteket küldenek, ritkán, de sok van belőlük, és folyamatosan csatlakoznak. Évek óta MQTT brókerekhez kapcsoljuk őket, és évek óta ugyanaz a kérdés foglalkoztat bennünket. Miért kell egy ilyen egyszerű dologhoz egy egész Linux, OpenSSL, valamilyen nyelvi futtatókörnyezet, SSH, csomagkezelő, és csak azután maga a bróker? Ezen rétegek mindegyike hozza a maga sebezhetőségeit, fogyasztja a maga memóriáját, és még egy olyan dologgal bővíti a rendszert, ami elromolhat, miközben egy eszközflotta a szervertől mindössze hármat vár el: tartson fenn sok, szinte tétlen kapcsolatot, minden eszközt megbízhatóan hitelesítsen, és az üzeneteket veszteség nélkül továbbítsa.</p>
<p>Így született meg az <a href="https://github.com/zeljan-alduk/urMQTT">urMQTT</a>: egy unikernel, amely maga <em>az</em> MQTT bróker. A mag, a TCP/IP, a TLS 1.3, az MQTT és egy beágyazott adatbázis egyetlen Rust binárisban, amelyet egyetlen ISO-ról lehet elindítani. Nincsenek folyamatok, nincsenek rendszerhívások, nincs shell. Az első platform, amelyen épül és tesztelődik, az aarch64, ugyanez a kód pedig x86_64 felé is halad, UEFI-n és klasszikus BIOS-on keresztül, valamint valódi hardver felé: a cél egyetlen ISO, amely mindezeken elindul. A projekt nyílt forráskódú, MIT vagy Apache 2.0 licenc alatt, és ebben az írásban szeretnénk rögzíteni, hol tartunk, hogyan épül fel belülről, és mit tanultunk útközben.</p>
<h2 id="mit-jelent-pontosan-az-unikernel">Mit jelent pontosan az „unikernel"</h2>
<p>Egy hagyományos bróker olyan program, amely egy operációs rendszeren fut. Az urMQTT alatt nincs operációs rendszer: maga indítja el magát az UEFI firmware-en és a Limine bootloaderen keresztül, beállítja a saját laptábláit, programozza a megszakításvezérlőt és az időzítőt, PCIe-n megkeresi a virtio eszközöket, DHCP-vel címet kap, és egyetlen porton hallgatózik. Mindaz, amit egyébként a kernel, az eszközmeghajtók, a hálózati verem és az alkalmazás végezne, itt egyetlen ELF, amely EL1 szinten fut.</p>
<p>Egy tipikus telepítéssel való összehasonlítás önmagáért beszél:</p>
<table>
<thead>
<tr>
<th></th>
<th>Tipikus bróker Linuxon</th>
<th>urMQTT</th>
</tr>
</thead>
<tbody>
<tr>
<td>Kitett szolgáltatások</td>
<td>SSH, bróker, gyakran metrika- vagy adminisztrációs port is</td>
<td><strong>csak a TCP 8883</strong> (MQTT TLS 1.3-on, mTLS-sel)</td>
</tr>
<tr>
<td>Kód a képfájlban</td>
<td>kernel, userland, libc, OpenSSL, futtatókörnyezet</td>
<td><strong>egyetlen Rust ELF</strong> (plusz a vendorolt SQLite)</td>
</tr>
<tr>
<td>Távoli felügyelet</td>
<td>shell, konfigurációs fájlok, csomagfrissítések</td>
<td><strong>semmi.</strong> Az új verzió egy új ISO</td>
</tr>
<tr>
<td>Memória tétlen kapcsolatonként</td>
<td>kilobájtok (socketpufferek, TLS-objektumok, heap)</td>
<td><strong>cél: ≤ 512 B</strong></td>
</tr>
<tr>
<td>Többmagos modell</td>
<td>szálak és zárak, vagy egyetlen eseményciklus</td>
<td><strong>szál magonként, megosztott állapot nélkül, végigvitt feldolgozás</strong></td>
</tr>
<tr>
<td>Kódintegritás</td>
<td>lehetségesek egyszerre írható és végrehajtható leképezések</td>
<td><strong>W^X, lezárt laptáblák, PAC/BTI</strong></td>
</tr>
</tbody>
</table>
<p>A prioritásaink ebben a sorrendben: biztonság, skálázhatóság, többmagos teljesítmény, kis lábnyom és megváltoztathatatlan telepítés. Ha két cél ütközik, az nyer, amelyik ezen a listán feljebb áll.</p>
<h2 id="hol-tartunk-most">Hol tartunk most</h2>
<p>A projekt pre-alfa fázisban van, de tizenhét fejlesztési mérföldkő lezárult, M0-tól M17-ig. Az urMQTT ma MQTT 3.1.1-et szolgál ki TLS 1.3-on, kötelező klienstanúsítványokkal, az állapotát pedig SQLite-ban őrzi, nyers lemezen. Röviden, ez az, amit már tud:</p>
<ul>
<li><strong>Mag:</strong> saját kivételvektorok dekódolt jelentésekkel, saját laptáblák érvényesített W^X-szel, őrlapok, PAC és BTI, ahol a processzor támogatja, GICv3 és GICv2, általános időzítő, SMP legfeljebb 64 maggal.</li>
<li><strong>Hardverfelismerés</strong> DTB-ből vagy ACPI-ból egyetlen platformleírásba, mindkét forrásból azonos eredménnyel.</li>
<li><strong>Eszközmeghajtók:</strong> PCIe ECAM, MSI-X, virtio-net, virtio-blk, virtio-scsi és virtio-rng, valamint hardveres entrópiával kezdeti értékkel ellátott CSPRNG.</li>
<li><strong>Hálózat:</strong> saját DHCPv4, ARP, ICMP és szigorú IPv4, majd saját, csak szerveroldali TCP: SYN-sütik, hierarchikus időzítőkerék, NewReno, ACK-ok összevonása, TIME_WAIT állapotok nélkül.</li>
<li><strong>TLS:</strong> csak TLS 1.3, kötelező mTLS, az eszközazonosító a CN-ből vagy a SAN URI-ból származik; a kézfogást a rustls végzi, utána saját rekordréteg következik AES-128-GCM-mel és ChaCha20-Poly1305-tel. Munkamenet-folytatás PSK-jegyekkel, visszavonási lista, már a kézfogásnál működő befogadás-vezérlés.</li>
<li><strong>MQTT:</strong> másolásmentes kodek, fuzzolva és valódi mosquitto kliensekkel tesztelve; QoS 0 és 1, helyettesítő karakterek, megőrzött üzenetek, végrendelet (LWT), tartós munkamenetek, a <code>devices/{id}/#</code> mintára szűkített ACL, eszközönkénti sebességkorlátok.</li>
<li><strong>Tárolás:</strong> SQLite a magon belül, nyers lemez feletti VFS-en keresztül, WAL módban, indításkor integritásellenőrzéssel; eszközregiszter, visszavonások, megőrzött üzenetek, tartós munkamenetek üzenetsorokkal az offline eszközök számára; magonként külön telemetriai napló rekordonkénti CRC-vel és összeomlás utáni helyreállítással.</li>
<li><strong>Üzemeltetés és felügyelet:</strong> watchdog időzítő a kezelőkhöz, állapotellenőrzés, korlátozott naplózás, syslog és statsd UDP-n, az újracsatlakozási viharokat átvészelő befogadás.</li>
<li><strong>Szállítás:</strong> CI-ben ellenőrzött, bitre azonos újraépítések, ISO és nyers GPT lemezkép, minisign aláírások, útmutatók KVM-hez, Proxmoxhoz és Hetznerhez.</li>
</ul>
<p>Minden lépést az azt elhozó mérföldkő jelöl, a repóban lévő <code>docs/PROGRESS.md</code> fájl pedig az egyetlen igazságforrás arról, hogy mi működik valójában.</p>
<h2 id="az-architektura-harom-kepben">Az architektúra három képben</h2>
<p>A teljes tervet a <code>docs/ARCHITECTURE.md</code> írja le, minden fontosabb döntés indoklása pedig ADR-ként található a <code>docs/DECISIONS.md</code> fájlban. Íme az áttekintés.</p>
<h3 id="a-retegek">A rétegek</h3>
<figure><img loading="lazy" alt="Az urMQTT rétegei: a brókertől az MQTT kodeken, a TLS-en, a TCP-n és a hálózaton át a virtio meghajtókig és az architektúrarétegig, mindez egyetlen ELF-ben, EL1 szinten." src="https://aldo.tech/blog/urmqtt/img/layers.svg" /><figcaption>Az urMQTT rétegei: a brókertől az MQTT kodeken, a TLS-en, a TCP-n és a hálózaton át a virtio meghajtókig és az architektúrarétegig, mindez egyetlen ELF-ben, EL1 szinten.</figcaption></figure>
<p>Fentről lefelé: a bróker (munkamenetek, témafa, QoS, megőrzött üzenetek, végrendeletek, ACL, korlátok), az MQTT kodek, a TLS, a TCP, a hálózati réteg, majd a meghajtók. E fő útvonal mellett áll a tárolás (SQLite és a telemetriai napló), mindez alatt pedig a memóriakezelés, a platformfelismerés és a build időben konstansokká fordított konfiguráció, a beágyazott CA-tanúsítvánnyal, a szervertanúsítvánnyal és a kulccsal együtt. Egyedül a virtio eszközökkel rendelkező hipervizor van a képen kívül.</p>
<h3 id="szal-magonkent-megosztott-allapot-nelkul">Szál magonként, megosztott állapot nélkül</h3>
<figure><img loading="lazy" alt="A végrehajtási modell: minden adatmag saját ciklussal és a bróker saját szeletével rendelkezik, a legfelső mag pedig kizárólag az SQLite-ot futtatja. A magok csak SPSC gyűrűkön keresztül kommunikálnak." src="https://aldo.tech/blog/urmqtt/img/cores.svg" /><figcaption>A végrehajtási modell: minden adatmag saját ciklussal és a bróker saját szeletével rendelkezik, a legfelső mag pedig kizárólag az SQLite-ot futtatja. A magok csak SPSC gyűrűkön keresztül kommunikálnak.</figcaption></figure>
<p>Minden adatmag saját ciklust forgat, saját TCP-veremmel, TLS-állapotokkal, a bróker egy szeletével (munkamenetek és témafa), heap-gyorsítótárral és telemetriai partícióval. A magok semmilyen módosítható állapotot nem osztanak meg, csak korlátos, zármentes gyűrűket, minden rendezett magpárhoz egyet. A legfelső mag az SQLite-ot futtatja, és semmi mást.</p>
<p>A hálózati kártyának ma egyetlen sorpárja van, amelyet a 0. mag tart kézben. Ez minden MQTT-folyamot a négyes Toeplitz-hash-e alapján az egyik munkamagra irányít, és elküldi mindazt, amit a munkamagok visszaadnak. A munkamenet a saját otthoni magján él, amelyet a kliensazonosító hash-e határoz meg, így az átvétel, a végrendeletek és a tartósság sosem lépi át a magok határát. Az a kapcsolat, amely egy másik magra érkezik, ott tartja a TCP- és TLS-állapotát, az egyszerű MQTT-t pedig kredit alapú forgalomszabályozással továbbítja az otthoni magnak. Minden irányított üzenet a bróker többi szeletére is szétterjed, amelyek mindegyike a megőrzött üzenetek egy replikáját tartja.</p>
<p>Három szabály tartja egyben ezt a modellt. <strong>Végigvitt feldolgozás:</strong> a beérkezett csomagot egyetlen mag teljes egészében feldolgozza, a TCP-től a helyben történő TLS-visszafejtésen, az MQTT-elemzésen és az útválasztáson át a titkosított válaszig, feladatsorok nélkül és az alkalmazási pufferekbe való másolás nélkül. <strong>Adaptív lekérdezés:</strong> terhelés alatt a vételi sorokat aktívan lekérdezzük, tétlenség esetén a mag visszatér a megszakításokhoz és a <code>WFI</code>-hez; a megszakításkezelők csak nyugtázzák a megszakítást és megjelölik a munkát. <strong>Watchdog időzítő:</strong> magonkénti 10 ms-os ütem elkapja azokat a kezelőket, amelyek túllépik a költségvetésüket.</p>
<h3 id="egy-publish-elete">Egy PUBLISH élete</h3>
<figure><img loading="lazy" alt="Egy QoS 1-es PUBLISH üzenet útja: a hálózati kártyán érkező TLS-rekordtól a visszafejtésen, az ACL-ellenőrzésen és az útválasztáson át a PUBACK-ig, amely egyetlen szegmensben egyesül a TCP ACK-kal." src="https://aldo.tech/blog/urmqtt/img/publish.svg" /><figcaption>Egy QoS 1-es PUBLISH üzenet útja: a hálózati kártyán érkező TLS-rekordtól a visszafejtésen, az ACL-ellenőrzésen és az útválasztáson át a PUBACK-ig, amely egyetlen szegmensben egyesül a TCP ACK-kal.</figcaption></figure>
<p>Az eszköz elküld egy TLS-rekordot. A hálózati kártya leíróval, másolás nélkül átadja a mag ciklusának. A ciklus ellenőrzi a TCP-t, helyben visszafejti a TLS-t, és egy kölcsönzött pufferből elemzi az MQTT-t, majd a memóriában ellenőrzi az ACL-t, a sebességkorlátot és az útvonalat a témafán át. Ha egy másik magot érdekel az adott téma, útválasztási üzenetet kap a tartalomra mutató hivatkozással és a témaazonosítóval; az adatbázis telemetriai rekordot vagy megőrzött üzenet frissítést kap, kötegelve. A PUBACK válasz közvetlenül a kimenő pufferbe titkosítódik, és egyesül a TCP ACK-kal, így az eszköz felé egyetlen szegmens indul. A másik magnak az üzenet előfizetőnként egy titkosításba kerül a küldés pillanatában.</p>
<h2 id="mennyibe-kerul-egy-kapcsolat">Mennyibe kerül egy kapcsolat</h2>
<p>A cél <strong>legfeljebb 512 bájt tétlen TLS-kapcsolatonként</strong>, 256 felé törekedve. Amíg a kapcsolat nyugszik, semmit nem tartunk pufferben érte: a beérkezett adatokat közvetlenül a hálózati keretből dolgozzuk fel, a küldési, a rekord- és az MQTT-pufferek pedig csak addig léteznek, amíg valami befejezetlen vagy nyugtázatlan. A mag ezt maga is méri: pontos heapszámlálók kért bájtokban és a ténylegesen lefoglalt blokkokban, tíz másodpercenként rögzítve, nyitott kapcsolatonkénti, alapszint feletti heapként.</p>
<table>
<thead>
<tr>
<th>Rész</th>
<th style="text-align: right;">Méret</th>
<th>Hogyan</th>
</tr>
</thead>
<tbody>
<tr>
<td>TCP vezérlőblokk</td>
<td style="text-align: right;">96 B</td>
<td>tömörített jelzőbitek, 16 bites RTT/RTO, rés az időzítőkerékben, tétlenségkor puffer nélkül</td>
</tr>
<tr>
<td>Rés a TCP-táblában</td>
<td style="text-align: right;">4–8 B</td>
<td>csak a kapcsolatindex, a tábla legfeljebb félig tele</td>
</tr>
<tr>
<td>Kapcsolat és TLS 1.3 rekordállapot</td>
<td style="text-align: right;">152 B</td>
<td>irányonkénti titok és sorszám (40 B); a kulcs és az IV rekordonként származtatódik</td>
</tr>
<tr>
<td>MQTT-kapcsolat</td>
<td style="text-align: right;">48 B</td>
<td>az azonosító és a végrendelet csak addig van a heapen, amíg létezik</td>
</tr>
<tr>
<td>MQTT-munkamenet, forró alak</td>
<td style="text-align: right;">64 B</td>
<td>egyetlen előfizetés sorban, internált csomópontazonosítók</td>
</tr>
<tr>
<td>Előfizetés a <code>devices/&lt;id&gt;/cmd/#</code> témára</td>
<td style="text-align: right;">0 facsomópont</td>
<td>a munkamenet-azonosítóhoz viszonyítva tárolva, minden eszközre közös</td>
</tr>
</tbody>
</table>
<p>Mérve HVF alatt, négy virtuális maggal, ebből három munkamag, ahol minden eszköz egy mTLS-kapcsolatot és egy QoS 1-es előfizetést tart, a foglaló kerekítésével együtt:</p>
<table>
<thead>
<tr>
<th style="text-align: right;">Kapcsolatok</th>
<th style="text-align: right;">Bájt kapcsolatonként</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: right;">960</td>
<td style="text-align: right;">531 B</td>
</tr>
<tr>
<td style="text-align: right;">9 922</td>
<td style="text-align: right;">494 B</td>
</tr>
<tr>
<td style="text-align: right;">10 197</td>
<td style="text-align: right;">474 B</td>
</tr>
</tbody>
</table>
<p>A hálózati keretek készlete fixen körülbelül 2 MiB, ami tízezer kapcsolatnál nagyjából 200 B kapcsolatonként, egymillió kapcsolatnál nagyjából 2 B. Ennél többet nem tudtunk mérni: a QEMU felhasználói módú hálózata macOS-en 10 197 kapcsolatnál nem fogadott el több kapcsolatot, nagyjából a QEMU-folyamat 10 240 leírós határánál. A szám csökken, ahogy a fix részek több kapcsolatra oszlanak szét, így nem nő a kapcsolatok számával.</p>
<h2 id="biztonsagi-modell">Biztonsági modell</h2>
<p>A biztonság az első prioritás, és minden ütközést megnyer a többi céllal szemben. A modell több rétegből áll:</p>
<ul>
<li><strong>Egyetlen port.</strong> TCP 8883, csak TLS 1.3, kötelező klienstanúsítványok, amelyeket a képfájlba ágyazott CA ír alá. Az MQTT-kliensazonosítónak meg kell egyeznie a tanúsítványban szereplő eszközazonosítóval.</li>
<li><strong>Nincs bejárat.</strong> Nincs shell, konzolbemenet, SSH, adminisztrációs API, dinamikus kód, hálózaton elérhető SQL, sem fájlrendszer.</li>
<li><strong>Memóriabiztonság.</strong> Rust, a protokollcsomagokban <code>forbid(unsafe_code)</code>-dal; minden <code>unsafe</code> blokkhoz <code>// SAFETY:</code> megjegyzés tartozik, amelyet egy lint ellenőriz, külső bemenetre pedig nincs pánik: a clippy az adatúton lévő csomagokban tiltja az <code>unwrap</code>, <code>expect</code>, indexelés és <code>panic!</code> használatát.</li>
<li><strong>Kihasználás elleni védelem.</strong> A W^X a leíró kódolásával és a <code>SCTLR_EL1.WXN</code> bittel érvényesül, és minden indításkor felülvizsgálatra kerül. A laptáblák indítás után csak olvashatók, írható alias nélkül. A címtér alsó fele ki van zárva. Minden veremnek van őrlapja, a kivételek külön veremen futnak. A PAC és a BTI akkor kapcsol be, ha a processzor támogatja, és ezt füsttesztek ellenőrzik.</li>
<li><strong>Visszaélésekkel szembeni ellenállás.</strong> SYN-sütik; a befogadás elutasítja az új kapcsolatokat, bármiféle kriptográfia előtt, ha kevés a heap, ha az adatbázis lemaradásban van, ha túl sok a folyamatban lévő kézfogás magonként vagy forráscímenként, vagy ha túl magas a kézfogások üteme. Eszközönkénti sebességkorlátok, korlátos sorok, a bróker ellennyomásához kötött TCP-ablak, korlátozott naplózás, watchdog időzítő és állapotellenőrzés.</li>
<li><strong>Megváltoztathatatlanság.</strong> A konfiguráció, a CA és a szerverkulcs a képfájlba van ágyazva. Bármi megváltoztatása új képfájlt jelent.</li>
</ul>
<p>A fenyegetésmodell a <code>docs/SECURITY.md</code> fájlban található.</p>
<h2 id="tarolas-fajlrendszer-nelkul">Tárolás fájlrendszer nélkül</h2>
<p>Az adatok egy különálló lemezen élnek, virtio-blk vagy virtio-scsi eszközön, amelyen nincs fájlrendszer. Az elrendezés minimális és saját:</p>
<pre><code>LBA 0      [ szuperblokk: varázsérték · elrendezés-verzió · uuid · régiótábla · CRC-32C ]
LBA 8, 16  [ fájlméret-tábla, A/B rések (a legújabb érvényes nyer) ]
LBA 64     [ SQLite főadatbázis ½ ][ SQLite WAL ⅛ ][ napló 1 MiB ][ telemetriai napló: 8 partíció … ]
utolsó 4 KiB [ terület a blokkeszköz indításkori önellenőrzéséhez ]
</code></pre>
<p>Az SQLite-ot (vendorolt 3.54.1, build közbeni ellenőrzőösszeg-vizsgálattal) clanggal fordítottuk a magba, szabványos könyvtár nélkül, és fix <code>memsys5</code> heapet használ. A VFS-e három fix fájlt képez le lemezrégiókra. WAL módban fut <code>locking_mode=EXCLUSIVE</code> beállítással, így nincs szüksége megosztott memóriás indexre, valamint <code>synchronous=FULL</code> mellett; ha a <code>quick_check</code> indításkor hibát talál, a rendszer biztonságosan leáll. Az adatbázisban találhatók az eszközregiszter, a visszavonások, a megőrzött üzenetek és a tartós munkamenetek a nem csatlakozó eszközök QoS 1-es üzenetsoraival.</p>
<p>Az adatbázis magja, a legmagasabb sorszámú, az SQLite-ot futtatja és semmi mást. Az adatsík zármentes SPSC gyűrűn keresztül küldi neki az állapotváltozásokat; ő ezeket 5 ms-onként vagy 512 műveletenként egyetlen tranzakcióba gyűjti, a nyugtákat pedig egy másik gyűrűn adja vissza. Az üres lemezt az első indításkor formázza; minden mást soha nem formáz felül.</p>
<p>A telemetria más úton halad. Minden elfogadott PUBLISH a <code>storage.telemetry_topics</code> alatt felsorolt témákon a saját magja telemetriai naplójának partíciójába kerül: szegmensek gyűrűjébe, rekordonkénti CRC-vel, a szegmens sorszámával sózva, így a gyűrű régi köreinek tartalma soha nem bukkan fel újra. A magok a rekordokat saját gyűrűjükön, másolás nélkül adják át az adatbázis magjának, amely 5 ms-onként vagy 256 KiB-onként írja ki őket. Indításkor a helyreállítás minden kiírt rekordot megtart, levágja a befejezetlen farkat, és új szegmensben folytatja. A megőrzés méret szerint történik, a gyűrűn keresztül, és kor szerint, a <code>retention_days</code> beállításon keresztül. Ezt egymillió rekorddal és írás közben kiadott <code>kill -9</code>-cel ellenőriztük.</p>
<h2 id="a-szamok">A számok</h2>
<p>A <code>tools/loadgen</code> terhelésgenerátor három dolgot végez: mTLS-es és CONNECT-es TLS 1.3 kézfogási vihart, hosszú életű, PINGREQ-et küldő kapcsolatokat, amelyek másodpercenként telemetriát publikálnak, valamint újracsatlakozási viharokat. A <code>make bench</code> Apple M4 Pro gépen, HVF alatt a következőt adja:</p>
<table>
<thead>
<tr>
<th>Munkamagok</th>
<th>kézfogás/s a vendégen belül</th>
<th>skálázás</th>
<th>QEMU felhasználói hálózaton át (kapcsolat/s)</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>862</td>
<td>1,00×</td>
<td>789</td>
</tr>
<tr>
<td>2</td>
<td>1 724</td>
<td>2,00×</td>
<td>636</td>
</tr>
<tr>
<td>4</td>
<td>3 438</td>
<td>3,99×</td>
<td>536</td>
</tr>
</tbody>
</table>
<p>A vendégen belüli szám, ahol a kliens és a szerver ugyanabban a virtuális gépben van, és minden munkamag önállóan dolgozik, azt mutatja, hogy az adatsík lineárisan skálázódik. A hálózati számot a QEMU egyszálú felhasználói hálózata korlátozza, nem a vendég; ezt is rögzítettük, ADR-032 néven.</p>
<p>Az újracsatlakozási viharokat úgy teszteljük, hogy az összes eszköz egyszerre csatlakozik, az elutasított próbálkozásokat késleltetéssel megismétli, majd mind leáll DISCONNECT nélkül. A fejlesztői módban a <code>heap_limit_mb=</code> paraméter arra készteti a befogadást, hogy a heapet a valóságosnál kisebbnek tekintse, így a heapnél nagyobb vihar is kipróbálható kevés sockettel. Egy ilyen, 3 000 eszközös körben 1 368 csatlakozott 36 másodperc alatt, 5 619 próbálkozást pedig rendben elutasítottunk, 8 MiB-os korlát mellett; a memória a vihar után visszaállt.</p>
<h2 id="hogyan-dolgozunk">Hogyan dolgozunk</h2>
<p>A projektet egy írott specifikáció, a <code>PROMPT.md</code> vezeti a mérföldkőtervvel, és minden mérföldkő ugyanazzal a rituáléval zárul: zöld <code>make check</code> és <code>make smoke</code>, egyetlen commit <code>M&lt;n&gt;</code> jelöléssel, bejegyzés a <code>docs/PROGRESS.md</code> fájlba és ADR minden olyan döntéshez, amely nem magától értetődő. A szabályok rövidek, és nem alkudozunk rajtuk: nincs pánik külső bemenetre, <code>unsafe</code> csak a hardveres és FFI-csomagokban és mindig megjegyzéssel, nincs allokáció a forró úton, megjegyzés minden atomi műveleti sorrendhez, minden korlát a konfigurációból származik, a szándékos ideiglenes részek pedig hangosan buknak el.</p>
<p>A CI többet végez, mint amennyit kézzel el tudnánk érni: fmt, clippy a hostra és az <code>aarch64-unknown-none</code> célra, typos, actionlint és shellcheck; teszteket Linuxon és macOS-en arm64-en; a mag építését fejlesztői és éles konfigurációval, az ELF méretével az összefoglalóban; indítást QEMU alatt a füsttesztek összes forgatókönyvével és a soros naplók elmentésével; cargo-deny a figyelmeztetésekre, licencekre és forrásokra; percenként fuzzolást a tizenegy cél mindegyikén, éjszaka pedig célonként tíz percet gyorsítótárazott korpusszal. Ehhez jön a CodeQL, az OpenSSF Scorecard, a Dependabot és a SLSA-eredetű kiadás. Minden idegen action a teljes commit SHA-hoz van rögzítve.</p>
<p>A reprodukálhatóságot szó szerint vesszük: a <code>make repro</code> kétszer építi meg a HEAD-et friss munkafákban, és bájtról bájtra összehasonlítja az ELF-et, az ISO-t és a nyers lemezképet. A kiadásokat minisignnel írjuk alá, az artefaktumok pedig ellenőrizhetők a <code>gh attestation verify</code> paranccsal. Az éles képfájl tartalmazza a szerver privát kulcsát, ezért maga az ISO titok; a GitHubon a kiadások mellett lévő képfájlok fejlesztői képek, ideiglenes kulcsokkal, és csak kipróbálásra valók.</p>
<h2 id="mit-tanultunk">Mit tanultunk</h2>
<p>Néhány lecke, amely megváltoztatta a tervet:</p>
<ul>
<li><strong>A saját TCP végül mégis megérte.</strong> A prototípus smoltcp-n futott, és az M13-ig jól szolgált minket. De egy szerver, amely százezres nagyságrendű kapcsolatot tart, nem kliens-TCP-t igényel, hanem SYN-sütiket, időzítőkereket és 96 bájtos állapotot. Az M14-ben a smoltcp-t saját, csak szerveroldali veremre cseréltük, szimulált veszteséges tesztekkel és szegmens-fuzzinggal.</li>
<li><strong>A memóriát belülről kell mérni.</strong> A kapcsolatonkénti becslések használhatatlanok voltak, amíg a mag maga nem kezdte számolni a heapet az allokátor blokkjaiban, nem pedig a kért bájtokban. Csak ekkor láttuk, hová megy valójában az 512 bájt, és ekkor jutottunk le 494-re.</li>
<li><strong>A tesztelőeszköz a termék része.</strong> A legnagyobb korlát nem a mag volt, hanem a QEMU felhasználói hálózata és az efemer portok száma a fejlesztői gépen. Százezer és több kapcsolathoz macOS-en <code>vmnet-shared</code>, vagy Linux KVM-mel, tap interfésszel és több klienscímmel van szükség.</li>
<li><strong>Az SQLite operációs rendszer nélkül is lehetséges</strong>, de csak szigorú feltételekkel: fix heap, kizárólagos zárolás megosztott memória nélkül, fájlokat nem ismerő VFS, és olyan befogadás, amely nem engedi lemaradni az adatbázist.</li>
<li><strong>Az újracsatlakozási viharok a normális állapot</strong>, nem a kivétel. Amikor a mobilhálózat leesik, majd visszatér, eszközök ezrei kopogtatnak egyszerre. Ezért a befogadás bármiféle kriptográfia előtt elutasítja a kapcsolatot, és minden elutasított kapcsolat szinte semmibe sem kerül.</li>
</ul>
<h2 id="mi-kovetkezik">Mi következik</h2>
<p>Az M17 utáni munkát három irányban terveztük meg, a közöttük lévő sorrend pedig a <code>docs/PLAN.md</code> fájlban van rögzítve. Ezek közül egyik sincs még megépítve.</p>
<ul>
<li><strong>Egyetlen ISO több platformra.</strong> Ugyanaz az <code>urmqtt-&lt;env&gt;-&lt;verzió&gt;.iso</code> fájl és egyetlen nyers GPT lemezkép, amely aarch64 UEFI-n, x86_64 UEFI-n és x86_64 BIOS-on is elindul, virtuális gépekben és valódi hardveren, egyetlen Intel PC-vel kezdve. Továbbra is mindent magában foglaló eszköz, reprodukálható és aláírt.</li>
<li><strong>Nyers lemez, fájlrendszer nélkül.</strong> Bájtszintű formátumspecifikáció, a magéval közös elemzőket használó <code>urmqtt-disk</code> host eszköz, 1 MiB-os régiózarás, a lemez növekedése az adatokkal, discard a vékonyan kiosztott lemezekhez, az adatbázis A/B pillanatképei, bitellenőrzés és választhatóan AES-256-XTS titkosítás hardveres AES-szel.</li>
<li><strong>Bizonyíthatóan biztonságos.</strong> Eszközök futásidejű visszavonása és engedélyezési listája aláírt vezérlőcsatornán, új képfájl nélkül; a saját TLS és TCP differenciális és konformanciatesztelése; KASLR, veremvédelem, kCFI, MTE és megerősített allokátor; EL0 szinten izolált SQLite; Secure Boot; SBOM és cargo-vet; végül külső auditra szánt csomag.</li>
</ul>
<p>Azok a platformok, amelyek csak nem virtio eszközöket kínálnak, mint az AWS ENA vagy az Azure MANA, egyelőre nem támogatottak. A Raspberry Pi 4 és 5 KVM-hostként, a Hetzner CAX és az Oracle Ampere A1 dokumentált és kóddal támogatott, de valódi szolgáltatáson még nem próbáltuk ki őket.</p>
<h2 id="probalja-ki">Próbálja ki</h2>
<p>A repó a <a href="https://github.com/zeljan-alduk/urMQTT">github.com/zeljan-alduk/urMQTT</a> címen érhető el. A rögzített nightly eszközlánccal rendelkező Rusttal és a QEMU-val a <code>make certs</code>, a <code>make run</code> és egyetlen mosquitto kliens elegendő az első PUBLISH-hoz mTLS-en keresztül a saját gépén. Az elején szereplő figyelmeztetés a végén is érvényes: ez pre-alfa, és mindent, ami működik, a <code>docs/PROGRESS.md</code> ír le. A hibajelentéseket, a tervezéssel kapcsolatos kérdéseket és a biztonsági bejelentéseket a <code>SECURITY.md</code> útmutatása szerint szívesen fogadjuk. Minden következő mérföldkőről itt fogunk beszámolni.</p>]]></content:encoded>
      <category>rust</category>
      <category>mqtt</category>
      <category>iot</category>
      <category>unikernel</category>
      <category>nyílt forráskód</category>
    </item>
    <item>
      <title>Blogot indítunk: jegyzetek a terepről</title>
      <link>https://aldo.tech/hu/blog/blogot-inditunk/</link>
      <guid isPermaLink="true">https://aldo.tech/hu/blog/blogot-inditunk/</guid>
      <pubDate>Sat, 17 Oct 2026 08:00:00 +0000</pubDate>
      <description>Miért kezd írni egy cég, amely terminálokat, alkalmazásokat és elektronikát épít a tömegközlekedésnek, és miről olvashat itt.</description>
      <content:encoded><![CDATA[<p>Annak nagy részét, amit építünk, senki sem látja. A szoftver a busz ajtajánál álló terminálban, a firmware egy eszközben, amely sosem kap kijelzőt, a szerver, amely éjszaka a napi tranzakciókból jelentéseket készít. Amikor minden működik, csendben működik. Amikor nem, azt mindenki észreveszi.</p>
<p>Ezen a blogon leírjuk, amit útközben megtanultunk. Nem marketingszövegeket, hanem konkrét jegyzeteket: mi romlott el, hogyan oldottuk meg, és mit csinálnánk ma másképp.</p>
<h2 id="mirol-fogunk-irni">Miről fogunk írni</h2>
<ul>
<li><strong>Jegyértékesítés a tömegközlekedésben.</strong> Érintés nélküli kártyák, SAM-modulok, hálózat nélküli működés és minden, amit egy terminálnak ki kell bírnia egy napi tizenkét órát rázkódó járműben.</li>
<li><strong>Mobilalkalmazások a terepen.</strong> Android és Flutter olyan eszközökön, amelyek nem hétköznapi telefonok: kártyaolvasóval, nyomtatóval és egy egész műszakot kibíró akkumulátorral.</li>
<li><strong>Backend és flottafelügyelet.</strong> Honnan tudjuk, hogy a járműben lévő eszköznek gondja van, még mielőtt a sofőr jelentené.</li>
<li><strong>Hardver.</strong> Az asztali prototípustól a sorozatgyártásba kerülő panelig, és minden hiba a kettő között.</li>
<li><strong>Helyi MI-modellek.</strong> Hol van értelme a saját hardveren futó modelleknek, és hol egyszerűen jobb választás a felhő és egy API.</li>
</ul>
<h2 id="hogyan-fogunk-irni">Hogyan fogunk írni</h2>
<p>Röviden és konkrétan, kóddal és számokkal, ahol vannak. Akkor publikálunk, amikor van mit mondanunk, nem menetrend szerint. A bejegyzések horvátul íródnak, és az oldal minden nyelvére lefordítjuk őket; <a href="https://aldo.tech/hu/blog/feed.xml">RSS</a> útján is követhet minket.</p>
<p>Ha érdekli egy téma, vagy hasonló problémán dolgozik, írjon nekünk az <a href="mailto:info@aldo.tech">info@aldo.tech</a> címre.</p>]]></content:encoded>
      <category>bevezeto</category>
    </item>
  </channel>
</rss>
