<?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>Blog ALDO TECH</title>
    <link>https://aldo.tech/sl/blog/</link>
    <atom:link href="https://aldo.tech/sl/blog/feed.xml" rel="self" type="application/rss+xml"/>
    <description>Zapiski s terena: javni prevoz, plačevanje vozovnic, strojna in programska oprema, ki mora delovati na poti.</description>
    <language>sl</language>
    <item>
      <title>urMQTT po VPS-ju: SQLite v uporabniškem načinu, primerjava z Mosquittom, konfiguracija kot podatek in graditelj v brskalniku</title>
      <link>https://aldo.tech/sl/blog/urmqtt-config-builder-plaintext-mosquitto/</link>
      <guid isPermaLink="true">https://aldo.tech/sl/blog/urmqtt-config-builder-plaintext-mosquitto/</guid>
      <pubDate>Sun, 01 Nov 2026 08:00:00 +0000</pubDate>
      <description>Kaj se je zgodilo po drugem besedilu o urMQTT: SQLite izoliran zunaj jedra, meritve proti Mosquittu na istem VPS-ju (in tisto, kar ni šlo v našo korist), najdeni vzroki za počasen posamezen tok, konfiguracija kot podatek z spletnim graditeljem ISO in emulatorjem v brskalniku, neobvezen navaden MQTT in kaj še ni dokazano.</description>
      <content:encoded><![CDATA[<p>Drugo besedilo, <a href="https://aldo.tech/blog/urmqtt-vps-handshake-disk-hardening/">urMQTT na pravem VPS-ju</a>, se je končalo s seznamom »Kaj sledi«: H6 (SQLite zunaj jedra), H7 (varnost časa), X11 (primerjava z Mosquittom na istem VPS-ju) in prva izdaja. To besedilo sledi temu seznamu v istem vrstnem redu in nato doda dve stvari, ki ju na seznamu ni bilo, ker sta se pojavili vmes: konfiguracijo kot podatek s spletnim graditeljem ISO ter neobvezen navaden MQTT brez TLS. Kot prej: kaj smo načrtovali, kaj smo zgradili, kaj nas je presenetilo in kaj je odprto. V tem besedilu je tudi nekaj rezultatov, ki ne govorijo v prid urMQTT; pustili smo jih, ker so del resnice.</p>
<p>Opomba vnaprej ostaja enaka: projekt je v predalfa fazi, prva izdaja je samo za VPS-je s KVM-jem, številke pa merijo kodo, teste in določene poskuse, ne zrelosti v produkciji.</p>
<h2 id="kako-je-to-izgledalo-skozi-cas">Kako je to izgledalo skozi čas</h2>
<p>Od prejšnjega besedila (31.10. ob 23:00) do danes (1.11. ob 20:41) je v repozitoriju 59 novih commitov, skupaj 178. Vsi so nastali v enem koledarskem dnevu, zato velja enako opozorilo kot prej: projekt gradimo s Claude Code (commiti nosijo <code>Co-Authored-By: Claude</code>), načrt, koda, testi in ADR-ji nastajajo v kratkih, zaprtih zankah, spodnje številke pa govorijo o količini kode in preverjanj, ne o tem, koliko človeškega dela je v njih.</p>
<figure><img loading="lazy" alt="Rast od prejšnjega besedila: vrstic Rusta od 89 790 do 112 408, funkcij #[test] od 739 do 926, ADR-jev od 104 do 126 in commitov od 119 do 178. Vsaka vrstica ima svojo lestvico." src="https://aldo.tech/blog/urmqtt-config-builder-plaintext-mosquitto/img/growth-since.svg" /><figcaption>Rast od prejšnjega besedila: vrstic Rusta od 89 790 do 112 408, funkcij #[test] od 739 do 926, ADR-jev od 104 do 126 in commitov od 119 do 178. Vsaka vrstica ima svojo lestvico.</figcaption></figure>
<p>Številke so izračunane z isto metodo kot v prejšnjem besedilu: vse datoteke <code>.rs</code> zunaj <code>vendor/</code> in <code>fuzz/</code>, funkcije <code>#[test]</code>, naslovi <code>## ADR-</code> v <code>docs/DECISIONS.md</code>.</p>
<table>
<thead>
<tr>
<th>Trenutek</th>
<th>Commit</th>
<th>Čas</th>
<th style="text-align: right;">Vrstic Rusta</th>
<th style="text-align: right;">Testov</th>
<th style="text-align: right;">ADR-jev</th>
</tr>
</thead>
<tbody>
<tr>
<td>prejšnje besedilo</td>
<td>794f607</td>
<td>31.10. 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 končan</td>
<td>a0423cc</td>
<td>1.11. 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>1.11. 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>spletni graditelj in zrcalo izdaj</td>
<td>582783f</td>
<td>1.11. 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>navaden MQTT, dokumentacija</td>
<td>8f959f1</td>
<td>1.11. 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>danes</td>
<td>d21e3ec</td>
<td>1.11. 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>Delo je potekalo vzporedno na več vejah, zato vrstni red commitov ni strogo vrstni red dela. ADR-ji so oštevilčeni do 232, vendar ima oštevilčenje vrzeli (del jih je bil ob združevanju vej preimenovan); štejemo naslove, ne najvišje številke. Izdaji: v0.2.0 (1.11., 18:45 UTC) in v0.2.1 (1.11., 20:22 UTC), obe označeni kot pre-release.</p>
<h2 id="h6-sqlite-v-uporabniskem-nacinu">H6: SQLite v uporabniškem načinu</h2>
<p><strong>Kaj smo načrtovali.</strong> SQLite je 254 268 vrstic C-ja, ki razčlenjuje bazo, WAL in dnevnik z diska, disk pa je lahko izdelan ali spremenjen. Dotlej je deloval v jedru, s pravicami jedra. <strong>Kaj smo zgradili</strong> (ADR-210 do ADR-212): SQLite, njegovi deli libc in kopica so bili premaknjeni v ločen program, <code>dbworker</code>, ki teče na EL0 (aarch64) oziroma v ring 3 (x86_64), v lastnem naslovnem prostoru. Jedro SQLite-a ne povezuje več vase. Delavec z jedrom komunicira prek skupnega pomnilniškega okna in dvanajstih sistemskih klicev, vsaka vrnjena dolžina in odmik pa se pred uporabo preverita. Napaka v delavcu (nedovoljen dostop, nedefiniran ukaz, preseženi časovni proračun 30 s) konča klic, delavec se znova zažene in baza se odpre iz WAL-a kot ob zagonu; po 5 ponovnih zagonih na minuto se shramba razglasi za neustrezno in se ne zaganja v nedogled.</p>
<p>Cena, izmerjena na aarch64 pod HVF-jem, ista VM, jedro z vgrajenim SQLite-om proti jedru z delavcem, šest izmeničnih zagonov vsakega:</p>
<table>
<thead>
<tr>
<th>Meritev</th>
<th>Prej (v jedru)</th>
<th>Potem (delavec)</th>
<th>Razlika</th>
</tr>
</thead>
<tbody>
<tr>
<td>vstop in izstop iz uporabniškega načina</td>
<td>-</td>
<td>1,1 us na krog</td>
<td></td>
</tr>
<tr>
<td><code>db-bench</code>, 5 000 vstavljanj v 50 transakcijah, najboljši / mediana</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>eno vstavljanje na transakcijo, najboljši / mediana</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>celotna varnostna kopija z enim klicem (23 MiB)</td>
<td>514 ms</td>
<td>635 ms</td>
<td>+23 %</td>
</tr>
</tbody>
</table>
<p>Zapisi na disk (1 572) in flushi (319) so enaki, kot mora biti. Tole ni izolirano: <strong>jedro še vedno zaupa temu, kar delavec vrne</strong>, po preverjanju oblike. Ogrožen delavec lahko laže (na primer vrne prazen seznam preklicanih potrdil in jih tako »prekliče preklic«), ne more pa brati pomnilnika jedra, ključev TLS ali ključev vstopnic. KPTI-ja ni, zato uhajanja tipa Spectre/Meltdown niso preprečena in o njih ne trdimo ničesar; ključ PAC si delita EL0 in EL1. Na x86_64 cene preklopa na KVM-ju nismo izmerili (pod TCG-jem bi bila številka brez pomena), fuzziranje delavca in protokola pa še ni končano. Nabor testov za shrambo (79 scenarijev, vsak vrti SQLite v delavcu) uspešno teče na aarch64 HVF in x86_64 TCG, na aarch64 TCG jih teče 76 (eden se preskoči, ker TCG nima MSI-X).</p>
<p><strong>H7 je zaprt, vendar ne tako, kot smo napovedali.</strong> Spodnja meja časa (ADR-050) in SNTP s konsenzom (T1) pokrivata obseg H7; NTS (RFC 8915) ostaja zunaj obsega in je zapisan kot preostalo tveganje, ne kot nekaj, kar deluje. Ne menimo, da je varnost časa »rešena«, ampak da je njen dokazljivi del končan.</p>
<p><strong>Nova omrežna možnost.</strong> Contabo ne daje DHCP-ja prilagojenemu ISO-ju, zato je VPS doslej potreboval drugo sliko kot laboratorijska VM. Zdaj <code>network.mode = "auto"</code> (ADR-214) najprej vpraša DHCP, če zakupa v danem roku (<code>dhcp_timeout_s</code>, privzeto 15 s) ni ali ga izgubi, pa uporabi privzeti statični naslov. Ročno preizkušeno na QEMU-ju (z DHCP-jem uporabi zakup, brez omrežja se po 5 s vrne na statični naslov); smoke scenarija za to še ni.</p>
<h2 id="x11-primerjava-z-mosquittom-na-istem-vps-ju">X11: primerjava z Mosquittom na istem VPS-ju</h2>
<p>Ista vrsta stroja kot v prejšnjem besedilu (Contabo, 4 vCPU AMD EPYC, 8 GiB), obremenitev z gostitelja Linux, oddaljenega 15 ms, vzajemni TLS 1.3 z istimi potrdili na obeh straneh, Mosquitto 2.0.21 na Debianu 13 brez trajnosti. Metoda in odločitve so v ADR-213, surovi podatki v <code>docs/perf-results/</code>. Mediana treh zagonov.</p>
<figure><img loading="lazy" alt="Popolna rokovanja na sekundo (TLS 1.3 + odjemalsko potrdilo + CONNECT, brez nadaljevanja seje) pri 32, 64 in 128 odjemalcih: urMQTT 169, 278, 413; Mosquitto 82, 140, 184. To so spodnje meje: generator obremenitve je bil blizu lastne omejitve." src="https://aldo.tech/blog/urmqtt-config-builder-plaintext-mosquitto/img/mosquitto.svg" /><figcaption>Popolna rokovanja na sekundo (TLS 1.3 + odjemalsko potrdilo + CONNECT, brez nadaljevanja seje) pri 32, 64 in 128 odjemalcih: urMQTT 169, 278, 413; Mosquitto 82, 140, 184. To so spodnje meje: generator obremenitve je bil blizu lastne omejitve.</figcaption></figure>
<table>
<thead>
<tr>
<th></th>
<th>urMQTT</th>
<th>Mosquitto 2.0.21</th>
</tr>
</thead>
<tbody>
<tr>
<td>rokovanj/s, 32 / 64 / 128 odjemalcev (profil bench)</td>
<td>169 / 278 / 413</td>
<td>82 / 140 / 184</td>
</tr>
<tr>
<td>isto, z omejitvami, kakršne se dobavljajo</td>
<td>185 / 262 / 333 (pri 128: 202 do 286 napak, ponastavitve rokovanja)</td>
<td>-</td>
</tr>
<tr>
<td>pomnilnik na nedejavno povezavo (20 000 povezav)</td>
<td>0,50 KB kopice jedra (0,66 KB pri 5 000)</td>
<td>26,8 KB rasti procesa</td>
</tr>
<tr>
<td>zakasnitev p50, QoS 0 / QoS 1, 800 sporočil/s, 64 B</td>
<td>14,0 / 14,8 ms</td>
<td>21,4 / 39,5 ms</td>
</tr>
<tr>
<td>QoS 1, 4 800 sporočil/s, dostavljeno/s (p50)</td>
<td>4 209 (77 ms)</td>
<td>4 235 (197 ms)</td>
</tr>
<tr>
<td>100 naročnikov, 50 sporočil/s: p50</td>
<td>16,6 ms</td>
<td>99 ms</td>
</tr>
<tr>
<td>en tok, QoS 0, 64 B, objavljeno/s</td>
<td>2 962 (0 do 7 733)</td>
<td>22 749</td>
</tr>
</tbody>
</table>
<p>Kaj to pomeni in česa ne. Rokovanja so <strong>spodnje meje za oba strežnika</strong>: generator obremenitve je deljena VM z 60 do 70 % ukradenega procesorskega časa in je bil pri 128 nitih blizu lastne omejitve, delovna jedra urMQTT pa so bila zasedena le 12 do 13 %. Razmerje pri 128 odjemalcih (okoli 2,2-krat) je najsmiselnejše; pri 32 in 64 večinoma meri čakanje odjemalcev. Številke pomnilnika ne štejejo enakega (kopica jedra proti RSS procesa), zato je »okoli 50-krat manj« velikostni red, ne natančno razmerje. Zakasnitev 14 ms je en krog omrežja (okoli 15 ms), torej »ni merljivega časa posrednika«; repi (p99 okoli 100 do 200 ms) so pri obeh posrednikih pod prevlado deljenih gostiteljev. Razlik pod okoli 20 % ne zatrjujemo.</p>
<p><strong>Kaj ni šlo v našo korist</strong> in je ostalo v tabelah: en tok TCP prek urMQTT je prenesel 2 do 8-krat manj kot prek Mosquitta, z nihanji od nič do 7,7 tisoč sporočil na sekundo med zagoni, 20 ms watchdog upravljalnika pa je <strong>prekinjal žive povezave</strong>, ko je hipervizor ustavil vCPU (v produkcijski nastavitvi <code>action = "drop"</code>, 6 do 8 povezav na zagon pri preplavljanju QoS 0). Obe stvari sta opisani v naslednjem razdelku, ker sta imeli vzroka v naši kodi.</p>
<h2 id="zakaj-je-bil-en-tok-pocasen-vzroki-ne-izgovori">Zakaj je bil en tok počasen: vzroki, ne izgovori</h2>
<p>Najlažje bi bilo reči »deljeni VPS«. Nismo, ampak smo pri istem VPS-ju in isti metodi iskali vzroke. Najdenih je bilo pet vzrokov, štirje v poti podatkov in eden v watchdogu (ADR-221 do ADR-224):</p>
<table>
<thead>
<tr>
<th>Simptom v X11</th>
<th>Vzrok v urMQTT</th>
<th>Sprememba</th>
</tr>
</thead>
<tbody>
<tr>
<td>en tok 1,9k sporočil/s, pogosto <strong>0 dostavljenih</strong></td>
<td><strong>Izgubljeno prebujanje:</strong> zanka izhoda je vrnila seznam pripravljenih povezav čez tisto, kar je bilo pravkar izpisano; naročnik, katerega izhodni medpomnilnik se je enkrat napolnil, ni bil nikoli več postrežen, dokler ga ni prebudil PINGRESP</td>
<td>popravljeno (ADR-223)</td>
</tr>
<tr>
<td>nekaj odstotkov izgube spremeni 48k v 14k sporočil/s</td>
<td><strong>vsak segment za izgubljenim je bil zavržen</strong> (sprejem samo po vrsti), zato je okrevanje stalo krog omrežja na segment</td>
<td>vrsta segmentov zunaj zaporedja, SACK (ADR-222)</td>
</tr>
<tr>
<td>zgornja meja 4,4 MB/s pri 15 ms</td>
<td>16-bitno okno, čakalna vrsta pošiljanja 64 KiB in počasen začetek, ki je štel ACK-je, ne bajtov</td>
<td>skaliranje okna, časovni žigi, štetje bajtov (ADR-222)</td>
</tr>
<tr>
<td>vsak krog toka, ki ustreza, čaka 0 do 10 ms</td>
<td><strong>razpošiljalnik je spal do naslednjega utripa po 10 ms</strong> (virtio-net nima prekinitve za sprejem)</td>
<td>povpraševanje po NIC vsakih 200 us, dokler povezave obstajajo (ADR-224)</td>
</tr>
<tr>
<td>1 do 8 prekinjenih povezav na zagon</td>
<td><strong>watchdog je upravljalniku zaračunaval pretekli čas</strong>, VPS pa vCPU-je ustavlja pogosto in dolgo</td>
<td>proračun je čas, ki ga je jedro dejansko delalo (ADR-221)</td>
</tr>
</tbody>
</table>
<p>Ustavitve so resnične: na tem VPS-ju so imela jedra 0 do 2 mediano 36 do 80 zamujenih utripov na 5 sekund, najhujša ustavitev v oknu pa je bila po mediani okoli 70 ms in do 0,9 s (jedro 3, baza in dnevnik: do 1,55 s), medtem ko gostitelj ne sporoča ukradenega časa (<code>host steal 0 us</code>). Zato je watchdog potreboval drugo uro, ne večjega proračuna.</p>
<figure><img loading="lazy" alt="Prej in potem, na istem VPS-ju z isto metodo, mediana treh do šestih zagonov: en tok QoS 0, 64 B: objavljeno 1,9k, 23,7k (Mosquitto 29,3k), dostavljeno 0 (mediana), 12,6k (29,2k). 16 parov, preplavljanje, 64 B: objavljeno 24,7k, 43,2k (31,4k), dostavljeno 20,0k, 37,4k (30,7k)." src="https://aldo.tech/blog/urmqtt-config-builder-plaintext-mosquitto/img/before-after.svg" /><figcaption>Prej in potem, na istem VPS-ju z isto metodo, mediana treh do šestih zagonov: en tok QoS 0, 64 B: objavljeno 1,9k, 23,7k (Mosquitto 29,3k), dostavljeno 0 (mediana), 12,6k (29,2k). 16 parov, preplavljanje, 64 B: objavljeno 24,7k, 43,2k (31,4k), dostavljeno 20,0k, 37,4k (30,7k).</figcaption></figure>
<table>
<thead>
<tr>
<th></th>
<th>koda X11 (7f9517e)</th>
<th>zdaj (86a71ff)</th>
<th>Mosquitto</th>
</tr>
</thead>
<tbody>
<tr>
<td>en tok, QoS 0, 64 B, objavljeno / dostavljeno na s</td>
<td>1,9k / 0</td>
<td>23,7k / 12,6k</td>
<td>29,3k / 29,2k</td>
</tr>
<tr>
<td>en tok, 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 parov, preplavljanje, 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 parov, preplavljanje, 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>izgubljene povezave na zagon (16 parov, 64 B)</td>
<td>13 (7 do 16)</td>
<td>0 (0 do 0)</td>
<td>0</td>
</tr>
</tbody>
</table>
<p>Watchdog: 148 prekinjenih povezav v 30 obremenjenih zagonih prej, 0 v 24 zagonih potem; na lokalni napravi, ki zakasnjuje in izgublja okvire, ima pri izgubi 0,1 % en tok 64 B s 14,3k sporočil/s (jedro X11) zdaj 41,0k, pri 0,5 % pa s 5,0k na 17,7k.</p>
<p><strong>Kaj to ne pove.</strong> urMQTT <strong>na enem toku še vedno ni hitrejši od Mosquitta</strong>: pri 64 B objavlja okoli 80 % njegove hitrosti, dostavlja pa okoli 40 %, ker zavrže QoS 0, ko je tok naročnika počasnejši od izdajateljevega (Mosquitto brez omejitve postavlja v vrsto). Pri 1 KiB je pri preplavljanju s 16 pari Mosquitto spredaj za okoli 15 %, kar je znotraj šuma. Generator je bil spet deljena VM z 58 do 77 % ukradenega časa. Drugi krog (zaklepanje kopice zunaj poti podatkov, SACK na strani pošiljanja in drugo) je na neintegrirani veji <code>perf-single-flow-wip</code>; njegove številke še niso trditve in jih tu ne navajamo.</p>
<h2 id="obljuba-ki-je-nismo-dali-a-smo-jo-izpolnili-konfiguracija-je-podatek">Obljuba, ki je nismo dali, a smo jo izpolnili: konfiguracija je podatek</h2>
<p>Doslej je bila vsaka konfiguracija nova gradnja: ključi in omejitve so bili vgrajeni v sliko. C1 to obrne (ADR-215 do ADR-219): jedro se zgradi enkrat, konfiguracija pa je podatek.</p>
<ul>
<li><strong>Blob <code>urmqtt-cfg</code></strong> (C1a): verzioniran, kanoničen zapis (polja po naraščajočih oznakah, prstni odtis SHA-256, neobvezen ločen podpis Ed25519), 70 polj s privzetimi vrednostmi in mejami, dekoder, ki zavrne vse neustrezno.</li>
<li><strong>Jedro ga prebere kot modul Limine</strong> (C1b), pred vsem drugim, na aarch64 in x86_64. Obstaja <strong>eno izdano jedro za vsak <code>ENV</code> razen <code>dev</code></strong> (ELF-i aarch64 za <code>release</code>, <code>prod</code> in <code>bench</code> imajo isti SHA-256; preverjeno samo na aarch64). Izdano jedro zavrne razvojno konfiguracijo, brez konfiguracije pa izpiše »refusing to serve« in ne streže ničesar.</li>
<li><strong>Nameščeni disk nosi konfiguracijo</strong> in posodobitev jo ohrani (C1c).</li>
<li><strong><code>urmqtt-config</code> in predloga ISO</strong> (C1d): <code>pack</code>, <code>show</code>, <code>verify</code>, <code>pack --iso</code> pa zamenja oba izvoda mesta za konfiguracijo in 24 povzetkov modulov v končnem ISO-ju. Rezultat je bajt za bajtom enak ISO-ju, ki ga zgradi <code>mkiso.sh --config</code>. Predloga izdaje v0.2.0 ima 63 111 168 bajtov.</li>
<li><strong>Izjava o zaupanju</strong> (ADR-219): konfigurirana slika ob zagonu izpiše prstni odtis konfiguracije (<code>config: fingerprint ...</code>), podpis pa je neobvezen in <strong>privzeto izključen</strong>. Spremenjen ISO nima uradnega povzetka; odtis je način, da se vidi, kaj je v njem.</li>
</ul>
<h2 id="graditelj-na-aldotech">Graditelj na aldo.tech</h2>
<p>Isto, kar dela <code>urmqtt-config pack --iso</code>, dela tudi spletna stran, z isto kodo: knjižnica orodja, prevedena v WebAssembly (1,06 MB, brez uvozov in brez bloka <code>unsafe</code>). Stran je na <a href="https://aldo.tech/sl/izdelki/urmqtt-builder/">aldo.tech/sl/izdelki/urmqtt-builder/</a> in obstaja v vseh 15 jezikih strani (na primer <a href="https://aldo.tech/proizvodi/urmqtt-builder/">hr</a>).</p>
<ul>
<li><strong>Nič ne zapusti brskalnika.</strong> Potrdila, zasebni ključi in končni ISO ostanejo na računalniku; stran prenese samo datoteke izdaje s tega mesta, brez sledenja in skript tretjih strani. V testu v živo nobena zahteva ni nosila ključnega gradiva.</li>
<li><strong>Zrcalo izdaj</strong> (ADR-227): ura za uro (časovnik systemd, nizka prioriteta) kopira izdaje z GitHuba, vsako preveri pred objavo, stran v brskalniku pa preveri podpis <code>SHA256SUMS</code> s ključem, vgrajenim v stran, in šele nato vpiše konfiguracijo v predlogo. Neuspešno preverjanje blokira prenos.</li>
<li><strong>Enostavno privzeto, napredno po potrebi</strong> (ADR-232): prvi zaslon so trije koraki (kje deluje, vaša potrdila, ime slike). Eno polje za povlečenje datotek razvrsti potrdilo, ključ in CA po vsebini. Napredni pogled odpre vsako od 70 polj, iskanje, uvoz in izvoz TOML; oba pogleda urejata isto stanje. Predloga (63 MB) se prenaša, medtem ko uporabnik izpolnjuje obrazec. V preizkusu v živo, s preizkusnim zrcalom in preizkusnim ključem, je gradnja ISO-ja v brskalniku trajala 5 do 18 s.</li>
</ul>
<figure><img loading="lazy" alt="Enostavni pogled graditelja: trije koraki, »Kje bo deloval?«, »Vaša potrdila« in »Poimenujte svojo sliko«, z enim naslednjim dejanjem v stranski vrstici." src="https://aldo.tech/blog/urmqtt-config-builder-plaintext-mosquitto/img/builder-simple.png" /><figcaption>Enostavni pogled graditelja: trije koraki, »Kje bo deloval?«, »Vaša potrdila« in »Poimenujte svojo sliko«, z enim naslednjim dejanjem v stranski vrstici.</figcaption></figure>
<figure><img loading="lazy" alt="Napredni pogled, posnet na preizkusnem zrcalu s preizkusnim ključem (datum izdaje in podpisni ključ nista pravi): izdaja s preverjenim podpisom, predloge, vsako polje konfiguracije." src="https://aldo.tech/blog/urmqtt-config-builder-plaintext-mosquitto/img/builder-advanced.png" /><figcaption>Napredni pogled, posnet na preizkusnem zrcalu s preizkusnim ključem (datum izdaje in podpisni ključ nista pravi): izdaja s preverjenim podpisom, predloge, vsako polje konfiguracije.</figcaption></figure>
<p><strong>Emulator, neobvezno.</strong> Ob »Prenesi ISO« stoji »Preizkusi v emulatorju«: QEMU, preveden v WebAssembly, zažene ravnokar zgrajeno sliko v zavihku brskalnika. Nič od njega se ne prenese pred klikom (okoli 3 MB prvič). Emulator je samo BIOS, <strong>brez omrežja</strong> (da promet uporabnika ne bi šel prek kakšnega posrednika in omajal »nič ne zapusti brskalnika«), potrebuje okoli 1,5 GB pomnilnika in namizni brskalnik (preizkušen Chrome; Firefox in Safari nista). V njem urMQTT doseže <code>URMQTT READY</code> v okoli 8 s (s preskočenim odštevanjem) in izpiše isti prstni odtis konfiguracije kot stran, kar je dokaz, da je bila zagnana prav ta konfiguracija. Zagon je okoli 2-krat počasnejši od izvirnega QEMU-ja na istem Macu (0,82 s proti 1,9 s), kriptografija pa okoli 58-krat počasnejša, ker so strojni ukazi emulirani. Zato <strong>iz emulatorja ne izpeljujemo nobenih trditev o hitrosti</strong>, stran pa to pove pred zagonom. Pri gradnji je bilo treba v <code>.wasm</code> zakrpati štiri bajte zaradi napake pri prevodu ukaza POPCNT v izvirnem emulatorju (še je nismo prijavili); sam emulator je pod GPL, s ponudbo izvorne kode.</p>
<figure><img loading="lazy" alt="Diagnostični vnos v emulatorju v živo na aldo.tech: jedro je izpisalo isti prstni odtis konfiguracije kot stran, zaslon pa prikazuje diagnostiko (v emulatorju se konča v okoli 20 s; njene številke opisujejo emulator, ne urMQTT)." src="https://aldo.tech/blog/urmqtt-config-builder-plaintext-mosquitto/img/emulator-diagnostic.png" /><figcaption>Diagnostični vnos v emulatorju v živo na aldo.tech: jedro je izpisalo isti prstni odtis konfiguracije kot stran, zaslon pa prikazuje diagnostiko (v emulatorju se konča v okoli 20 s; njene številke opisujejo emulator, ne urMQTT).</figcaption></figure>
<h2 id="navaden-mqtt-samo-za-omrezja-ki-jim-zaupate">Navaden MQTT, samo za omrežja, ki jim zaupate</h2>
<p>Navaden MQTT (TCP 1883, brez TLS) je v IoT zelo pogost: je privzet v Mosquittu, v večini strojne programske opreme za Arduino in ESP, v Tasmoti, Zigbee2MQTT in Home Assistantu, uporablja pa se tudi znotraj segmentiranih industrijskih omrežij, za VPN-ji in v zasebnih omrežjih LAN, pa tudi na napravah brez ure (potrdila zahtevajo datum), brez dovolj flasha ali procesorja za TLS. Prek nezanesljivega omrežja ni sprejemljiv: kdor je na poti, bere in spreminja promet. Zato je P1 (ADR-229 do ADR-231) izrecna, omejena izbira, <strong>privzeto izključena</strong>, vrata TLS pa ostanejo; TLS se s tem ne izklopi, navadna vrata pridejo zraven.</p>
<ul>
<li><strong>Seznam dovoljenih omrežij je obvezen</strong> (<code>allow_from</code>, CIDR-ji) in se preveri ob SYN: zunaj seznama ni SYN-ACK-a ne stanja. Cel internet (<code>0.0.0.0/0</code>) zahteva ločeno izrecno možnost.</li>
<li><strong>Trije načini prijave:</strong> geslo (privzeto, ko je vklopljeno; PBKDF2-HMAC-SHA-256, sol, 10 000 iteracij, primerjava v stalnem času, zaklepanje po naslovu), anonimno (ena konfigurirana identiteta z lastnim ACL) in po naslovu (<code>ip</code>). Graditelj geslo razprši v brskalniku, v pomnilniku pa hrani samo povzetek.</li>
<li><strong>Manj pravic kot mTLS:</strong> navaden odjemalec nikoli nima storitvenih, operaterskih ali upravljalskih pravic, nima <code>$SYS</code> ne konzole, ima lastne seje (<code>$plain/&lt;identiteta&gt;/&lt;id odjemalca&gt;</code>, samo čiste), lastne omejitve in register dovoljenih ga še vedno zavezuje.</li>
<li><strong>Kaj se izgubi, piše v <code>SECURITY.md</code>:</strong> zaupnost, celovitost, pristnost posrednika in ponavljanje posnete prijave. To se s konfiguracijo ne da popraviti; tako je, kadar ni TLS.</li>
</ul>
<p>Izmerjeno na prenosniku (Apple silicon, HVF, uporabniško omrežje QEMU), 16 parov, 64 B, trije zagoni po 10 s: pri 8 000 sporočilih na sekundo TLS in navadna pot dostavita vsa sporočila z isto zakasnitvijo (p50 0,17 ms proti 0,13 do 0,18 ms), nove povezave na sekundo: TLS okoli 2 600 do 2 750, anonimni navaden okoli 4 900 do 7 800, <strong>prijava z geslom pa je drag del</strong>: en PBKDF2 stane okoli 2,5 ms, zato je število novih prijav omejeno na okoli 200 na podatkovno jedro na sekundo (izmerjeno 308 do 342 na dveh jedrih). Preplavljanje na tem stroju ni stabilen instrument (20k do 440k dostavljenih na sekundo med zagoni) in <strong>iz njega ne izpeljujemo zaključka</strong>. Primerjava navadne poti z Mosquittom na VPS-ju še ni narejena. Testi: smoke skupina <code>plaintext</code> 7 od 7 na aarch64 HVF in na x86_64 TCG, nov fuzz cilj <code>plain_input</code> (2,3 milijona zagonov brez najdb).</p>
<h2 id="napake-ki-jih-je-naslo-delo-v-zivo">Napake, ki jih je našlo delo v živo</h2>
<p>Dveh stvari niso ujeli testi, ampak prva vključitev na pravem mestu:</p>
<ul>
<li>Stran je datoteke izdaje iskala <strong>en imenik previsoko</strong>, zato je vsako preverjanje podpisa odgovorilo 404 (popravek 13af301). Test je uporabljal preizkusno zrcalo na drugi poti.</li>
<li><strong>Tipkovnica emulatorja nikoli ni prišla do gosta</strong>: vzrok ni bil slab prevod tipk, ampak to, da pogovorno okno sploh ni poslušalo tipk (popravek v ADR-232, z vnosom za zagon brez tipk).</li>
</ul>
<h2 id="lekcije">Lekcije</h2>
<ul>
<li><strong>Iščite vzrok, ne obtožujte okolja.</strong> Večina slabosti enega toka in vse prekinjene povezave so bile naše; hipervizor je resničen, vendar ga je bilo treba meriti, ne predpostavljati.</li>
<li><strong>Primerjava z drugim izdelkom mora vključevati tudi izgube.</strong> Tabela, v kateri so samo naše zmage, ne bi bila primerjava.</li>
<li><strong>Merilni instrument ima meje.</strong> Deljen generator z 60 do 70 % ukradenega časa spremeni, kaj številke pomenijo; zato pišemo »spodnja meja« in ne zatrjujemo razlik pod 20 %.</li>
<li><strong>Izolacija ni dokaz.</strong> SQLite v EL0 zmanjša, kaj lahko stori napaka v njem, ne tega, o čemer lahko laže.</li>
<li><strong>Resnična namestitev najde tisto, česar testi ne iščejo</strong>: pot, ki je bila narobe sestavljena, in tipke, ki jih nihče ne posluša.</li>
</ul>
<h2 id="kaj-se-ni-dokazano">Kaj še ni dokazano</h2>
<ul>
<li>Podpisni ključ izdaje je za zdaj preizkusni ključ; v0.2.0 in v0.2.1 sta pre-release, slike zraven njiju pa razvojne s preizkusnimi potrdili (razen predloge, ki je brez konfiguracije).</li>
<li>13 prevodov strani graditelja (in tega besedila) je napisala umetna inteligenca, preverili pa so jih testi in vzorci, ne materni govorci.</li>
<li>Primerjava z Mosquittom je ena seja, ena pot in en deljen generator, na deljenem VPS-ju; razlike pod 20 % niso trditve.</li>
<li>Navadna pot ni bila merjena na VPS-ju ne proti Mosquittu.</li>
<li>Lighthouse na novem enostavnem pogledu ni potrjen (stari pogled: dostopnost 95 ali več).</li>
<li>Bare metal, cena delavca x86_64 na KVM-ju, Firefox in Safari za emulator: ni merjeno.</li>
</ul>
<h2 id="kaj-sledi">Kaj sledi</h2>
<ul>
<li><strong>D1: javni demo posrednik.</strong> Emulatorja iz brskalnika ne moremo narediti javnega: živi v zavihku enega obiskovalca, je 20 do 100-krat počasnejši od KVM-ja, nima dohodnega omrežja in izgine z zaprtjem zavihka; preusmerjanje interneta v tuj brskalnik bi bilo tudi zloraba. Mogoč je pravi demo posrednik: urMQTT na VPS-ju za imenom na aldo.tech, z objavljenim preizkusnim CA, strogim ACL in majhnimi omejitvami. To je zaenkrat samo zamisel, ne gradnja, in potrebuje izrecno soglasje za javno izpostavitev.</li>
<li><strong>Drugi krog za en tok</strong> (veja <code>perf-single-flow-wip</code>): šele ko je izmerjen na VPS-ju in združen.</li>
<li><strong>Navadna pot proti Mosquittu na VPS-ju</strong>, prijava in preklic računa za navaden MQTT prek podpisanega upravljalskega kanala.</li>
<li><strong>Po izdaji:</strong> bare metal (X12 do X16), IOMMU in Secure Boot, šifriranje diska.</li>
</ul>
<p>Koda je na <a href="https://github.com/zeljan-alduk/urMQTT">github.com/zeljan-alduk/urMQTT</a>, <code>docs/PROGRESS.md</code> pa je še vedno edini vir resnice o tem, kaj dejansko deluje, <code>docs/PERFORMANCE.md</code> o tem, kaj je izmerjeno. Predalfa, kot prej; prijave napak in varnostne prijave po navodilih v <code>SECURITY.md</code> so dobrodošle.</p>]]></content:encoded>
      <category>rust</category>
      <category>mqtt</category>
      <category>iot</category>
      <category>unikernel</category>
      <category>open source</category>
    </item>
    <item>
      <title>urMQTT na pravem VPS-ju: ISO, rokovanje v 0,24 ms, lasten disk in utrjeno jedro</title>
      <link>https://aldo.tech/sl/blog/urmqtt-vps-handshake-disk-hardening/</link>
      <guid isPermaLink="true">https://aldo.tech/sl/blog/urmqtt-vps-handshake-disk-hardening/</guid>
      <pubDate>Sat, 31 Oct 2026 08:00:00 +0000</pubDate>
      <description>Kaj se je zgodilo s tremi obljubami iz prvega besedila o urMQTT (en ISO, surovi disk brez datotečnega sistema, dokazljivo varno) po prvih štirih dneh razvoja, z meritvami, tabelami in seznamom tega, kar še ni končano.</description>
      <content:encoded><![CDATA[<p>V <a href="https://aldo.tech/blog/urmqtt-unikernel-mqtt-broker/">prvem besedilu o urMQTT</a> smo opisali unikernel, ki <em>je</em> MQTT posrednik, do mejnika M17, in končali s tremi obljubami pod naslovom »Kaj sledi«: en ISO, ki se zažene na več platformah, surovi disk brez datotečnega sistema in varnost, ki jo je mogoče dokazati, ne le zatrjevati. Nič od tega takrat še ni bilo zgrajeno. To besedilo spremlja, kaj se je zgodilo s posamezno obljubo, v enakem vrstnem redu kot prej: kaj smo načrtovali, kaj smo zgradili, kaj nas je presenetilo in kaj je še odprto. Ob tem bomo pokazali tudi, koliko časa je to trajalo, saj je tudi to del zgodbe.</p>
<p>Ena opomba vnaprej: projekt je še vedno v predalfa fazi. Številke v besedilu merijo kodo in teste, ne zrelosti v produkciji, prva objava pa je namenoma omejena na VPS-je s KVM-jem. Bare metal ostaja za pozneje.</p>
<h2 id="kako-je-to-izgledalo-skozi-cas">Kako je to izgledalo skozi čas</h2>
<p>Prvi commit je nastal 28. oktobra 2026 ob 23:48 (M0). Do 31. oktobra ob 23:00 je v repozitoriju 119 commitov, razporejenih kot 3, 39, 38 in 39 po koledarskih dneh. Takoj povejmo tudi, kako je to mogoče: projekt gradimo s Claude Code, commiti pa nosijo trailerje <code>Co-Authored-By: Claude</code>. Štirje koledarski dnevi zgodovine ne pomenijo, da je ena oseba v štirih dneh napisala 90 000 vrstic; pomenijo, da se načrt, implementacija, testi in ADR-ji pišejo v kratkih, zaprtih zankah, vsak korak pa se konča z enakim obredom preverjanja kot prej. Spodnje številke govorijo o količini kode in testov, ne o tem, koliko dejanskega dela je bilo vloženega.</p>
<figure><img loading="lazy" alt="Rast projekta v štirih dneh z mejniki od M0 do danes: tisoči vrstic Rusta (od 1,6 do 89,8), število funkcij #[test] (od 27 do 739) in število ADR-jev (od 13 do 104), skupaj s stolpci commitov po dnevih (39, 38 in 39 za 29., 30. in 31. oktober)." src="https://aldo.tech/blog/urmqtt-vps-handshake-disk-hardening/img/growth.svg" /><figcaption>Rast projekta v štirih dneh z mejniki od M0 do danes: tisoči vrstic Rusta (od 1,6 do 89,8), število funkcij #[test] (od 27 do 739) in število ADR-jev (od 13 do 104), skupaj s stolpci commitov po dnevih (39, 38 in 39 za 29., 30. in 31. oktober).</figcaption></figure>
<p>Graf kaže tri krivulje, ki rastejo skoraj skupaj, kar je tudi namen: testi in zapisane odločitve sledijo kodi, ne zaostajajo za njo. Naklon je najstrmejši 29. oktobra, ko nastane celotna pot od jedra do posrednika. Pozneje se vrstice dodajajo počasneje, testi pa rastejo hitreje od kode (27 testov na 1,6 tisoč vrstic na začetku, 739 na 89,8 tisoč danes).</p>
<table>
<thead>
<tr>
<th>Mejnik</th>
<th>Commit</th>
<th>Čas</th>
<th style="text-align: right;">Vrstic Rusta</th>
<th style="text-align: right;">Testov</th>
<th style="text-align: right;">ADR-jev</th>
</tr>
</thead>
<tbody>
<tr>
<td>M0</td>
<td>9412a9c</td>
<td>28.10. 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>29.10. 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>29.10. 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>29.10. 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>29.10. 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>29.10. 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>30.10. 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>30.10. 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>31.10. 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>danes</td>
<td>HEAD</td>
<td>31.10. 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>Vrstice so vse datoteke <code>.rs</code> zunaj <code>vendor/</code> in <code>fuzz/</code>, testi so funkcije <code>#[test]</code>. Kaj je dodal kateri del, po datumu iz zgodovine gita:</p>
<table>
<thead>
<tr>
<th>Datum</th>
<th>Mejniki</th>
<th>Kaj je bilo dodano</th>
</tr>
</thead>
<tbody>
<tr>
<td>29.10.</td>
<td>M0 do M17</td>
<td>jedro, omrežje, TLS, posrednik, SQLite, več jeder, lasten TCP, utrjevanje, ponovljiva izdaja</td>
</tr>
<tr>
<td>29.10. zvečer in 30.10.</td>
<td>X0 do X10, S1, H2, H3 (X0 se začne 29.10. ob 20:54)</td>
<td>port na x86_64, univerzalni ISO, en disk in samonamestitveni ISO, preverjanja TLS, napadalski odjemalci</td>
</tr>
<tr>
<td>31.10.</td>
<td>O1 do O4, T1, PERF, H4, H5, H9, S2 do S6</td>
<td>vpogled in konzola prek MQTT, živi način in diagnostika, SNTP, pospešitve, utrjevanje, verifikacija, dobavna veriga, shranjevanje</td>
</tr>
</tbody>
</table>
<h2 id="obljuba-1-en-iso-za-vec-platform">Obljuba 1: en ISO za več platform</h2>
<p><strong>Kaj smo načrtovali.</strong> Ena datoteka, ki se zažene na aarch64 UEFI, x86_64 UEFI in x86_64 BIOS. <strong>Kaj smo zgradili.</strong> Port na x86_64 (X0 do X10) in univerzalni ISO z zagonskim nalagalnikom Limine, en hibridni ISO, s katerega se zaženejo vse te kombinacije; dimni testi ga zaženejo na štirih virtualnih strojih. Meni ima tri vnose: urMQTT (živi način, teče iz RAM-a, nič ne piše na disk), namestitev na disk računalnika, na katerem je zagnan (namestitev zapiše GPT in podatke, nato ugasne stroj), in Diagnostika (zaznavanje strojne opreme in meritve).</p>
<figure><img loading="lazy" alt="Zagonski meni ISO-ja, posnet na VPS-ju: vnosi »urMQTT«, »Install urMQTT on this machine's disk« in »Diagnostic: hardware detection and benchmarks« z odštevanjem do samodejnega zagona. Posnetek je iz testnega ISO-ja z daljšim odštevanjem; privzeto je 10 sekund." src="https://aldo.tech/blog/urmqtt-vps-handshake-disk-hardening/img/boot-menu.png" /><figcaption>Zagonski meni ISO-ja, posnet na VPS-ju: vnosi »urMQTT«, »Install urMQTT on this machine's disk« in »Diagnostic: hardware detection and benchmarks« z odštevanjem do samodejnega zagona. Posnetek je iz testnega ISO-ja z daljšim odštevanjem; privzeto je 10 sekund.</figcaption></figure>
<p>Prva prava tarča je bil VPS, ne fizični računalnik: odločitev je, da prva izdaja podpira samo VPS (KVM), bare metal pa pride za njo. Testirali smo na VPS-ju Contabo s procesorjem AMD EPYC (družina 23, Zen 1), 4 vCPU-ji in 8 GiB RAM-a, z omrežjem virtio-net in diskom virtio-scsi. Prvi poskus nas je presenetil: ISO, priključen kot virtualni CD, je zagnal privzeti vnos, produkcijska slika pa je zavrnila streženje z medija samo za branje in se ponastavila, tako da smo dobili neskončno zanko meni, jedro, ponastavitev brez česar koli berljivega. Danes usodna napaka v produkcijski sliki pusti sporočilo na konzoli 60 sekund pred ponastavitvijo, z optičnega medija pa izpiše, da je bil zagon z medija samo za branje, in se ustavi. Živi način v RAM-u (O3) je pozneje to težavo za običajni primer zaobšel.</p>
<p>Diagnostični vnos je nastal iz te izkušnje: stroj, ki ga ne vidimo, moramo znati vprašati, kaj ima. Izpiše CPU, RAM, omrežje, diske in naprave PCI, izmeri prepustnost pomnilnika, disk in kriptografijo, enake meritve pa izpiše tudi kot JSON na serijska vrata. Pisanje na disk je dovoljeno samo v nerazporejenem prostoru in šele, ko vtipkate <code>WIPE</code>; na spodnjem posnetku je bilo to preskočeno.</p>
<figure><img loading="lazy" alt="Diagnostični zaslon na VPS-ju Contabo (AMD EPYC, KVM, 4 vCPU): sistem, pomnilnik, disk, naprave PCI, prepustnost in zakasnitev RAM-a ter kripto meritve, vključno z 244 us na rokovanje mTLS, X25519 48 us, podpisom ECDSA 35,7 us in preverjanjem 107 us." src="https://aldo.tech/blog/urmqtt-vps-handshake-disk-hardening/img/diag-vps.png" /><figcaption>Diagnostični zaslon na VPS-ju Contabo (AMD EPYC, KVM, 4 vCPU): sistem, pomnilnik, disk, naprave PCI, prepustnost in zakasnitev RAM-a ter kripto meritve, vključno z 244 us na rokovanje mTLS, X25519 48 us, podpisom ECDSA 35,7 us in preverjanjem 107 us.</figcaption></figure>
<p><strong>Kaj je odprto.</strong> Prava strojna oprema. Po prvi izdaji sledi bare metal (zagon z USB-ja, NVMe in AHCI, omrežna kartica pravega računalnika), skupaj z IOMMU-jem in Secure Bootom, ker brez IOMMU-ja lahko zlonamerna ali okvarjena naprava piše po celotnem pomnilniku. V tej različici o tem ne trdimo ničesar.</p>
<h2 id="rokovanje-od-12-ms-do-024-ms">Rokovanje: od 1,2 ms do 0,24 ms</h2>
<p>Prva meritev diagnostike na VPS-ju je dala neprijetno številko: rokovanje mTLS, samo kriptografija na strani strežnika, je trajalo 1,2 ms. Na aarch64 smo že uporabljali strojne ukaze AES in SHA, na x86_64 pa nismo imeli nič podobnega za javni ključ: RustCrypto je prenosljiv in constant-time, zato pa tudi počasen. Spustile so ga tri spremembe.</p>
<ul>
<li>Za P-256 smo prevzeli zbirnik <code>nistz256</code> iz OpenSSL-a in ringa (ADR-130), s tabelami s fiksno bazo za podpisovanje in preverjanje podpisov CA (ADR-110, ADR-111).</li>
<li>X25519 je prepisan na štiri 64-bitne ude z ukazi MULX/ADX (ADR-131).</li>
<li>Nato smo profilirali še preostanek (ADR-132, ADR-200).</li>
</ul>
<figure><img loading="lazy" alt="Rokovanje na VPS-ju: 1200 us prej, 237 us po zbirniku in tabelah; primerjava A/B za memcpy v istem zagonu, 278,8 us s starim, 229,4 us z novim; ter prej in potem za X25519 (146 na 48 us), podpis ECDSA (673 na 34 us) in preverjanje (309 na 90 us)." src="https://aldo.tech/blog/urmqtt-vps-handshake-disk-hardening/img/handshake.svg" /><figcaption>Rokovanje na VPS-ju: 1200 us prej, 237 us po zbirniku in tabelah; primerjava A/B za memcpy v istem zagonu, 278,8 us s starim, 229,4 us z novim; ter prej in potem za X25519 (146 na 48 us), podpis ECDSA (673 na 34 us) in preverjanje (309 na 90 us).</figcaption></figure>
<table>
<thead>
<tr>
<th>Meritev na VPS-ju (strežnik, mikrosekunde)</th>
<th style="text-align: right;">Prej</th>
<th style="text-align: right;">Potem</th>
</tr>
</thead>
<tbody>
<tr>
<td>rokovanje mTLS, vsa kriptografija</td>
<td style="text-align: right;">1200</td>
<td style="text-align: right;">okoli 240 (237 v meritvi z grafa, 244 na posnetku)</td>
</tr>
<tr>
<td>X25519</td>
<td style="text-align: right;">146</td>
<td style="text-align: right;">48</td>
</tr>
<tr>
<td>podpis ECDSA</td>
<td style="text-align: right;">673</td>
<td style="text-align: right;">34</td>
</tr>
<tr>
<td>preverjanje ECDSA</td>
<td style="text-align: right;">309</td>
<td style="text-align: right;">90 (107 na posnetku)</td>
</tr>
<tr>
<td>rokovanje, stari memcpy, isti zagon</td>
<td style="text-align: right;">278,8</td>
<td style="text-align: right;">229,4 (-17,7%)</td>
</tr>
</tbody>
</table>
<p>Številke se med zagoni razlikujejo za okoli 10%, zato smo zapisali razpon namesto ene vrednosti. Skupaj je to približno petkrat manj procesorskega časa za rokovanje.</p>
<p>Kaj smo se naučili o memcpy-ju. Ko je kriptografija padla, je profil pokazal nekaj, česar nismo pričakovali: funkcije <code>mem*</code> (memcpy, memmove, memset) so porabile 7% rokovanja, 13 do 39% poti zapisa TLS (39% za 64 B, 13% za 4 KiB) in 17 do 34% razdeljevanja PUBLISH-a. Zamenjali smo jih z zbirnikom SSE2 za x86_64 (ADR-200), z diferencialnimi testi proti referenčni implementaciji za vse velikosti, poravnave in prekrivanja. Na klic je to 55 do 75 ciklov proti 7 do 12 za 1 do 64 bajtov.</p>
<figure><img loading="lazy" alt="Levo: delež časa strežnika v funkcijah mem* pred prepisom (rokovanje 7%, zapis TLS 64 B 39%, 256 B 37%, 1 KiB 24%, razdeljevanje 17 do 34%). Desno: za koliko je vsaka pot hitrejša potem (rokovanje 17,7%, 64 B 26,4%, 256 B 19,6%, 1 KiB 16,1%, 4 KiB 6,6%, razdeljevanje 7 do 34%)." src="https://aldo.tech/blog/urmqtt-vps-handshake-disk-hardening/img/hotpaths.svg" /><figcaption>Levo: delež časa strežnika v funkcijah mem* pred prepisom (rokovanje 7%, zapis TLS 64 B 39%, 256 B 37%, 1 KiB 24%, razdeljevanje 17 do 34%). Desno: za koliko je vsaka pot hitrejša potem (rokovanje 17,7%, 64 B 26,4%, 256 B 19,6%, 1 KiB 16,1%, 4 KiB 6,6%, razdeljevanje 7 do 34%).</figcaption></figure>
<table>
<thead>
<tr>
<th>Pot</th>
<th style="text-align: right;">Delež mem* prej</th>
<th style="text-align: right;">Hitreje potem</th>
</tr>
</thead>
<tbody>
<tr>
<td>rokovanje</td>
<td style="text-align: right;">7%</td>
<td style="text-align: right;">17,7%</td>
</tr>
<tr>
<td>zapis TLS 64 B</td>
<td style="text-align: right;">39%</td>
<td style="text-align: right;">26,4%</td>
</tr>
<tr>
<td>zapis TLS 256 B</td>
<td style="text-align: right;">37%</td>
<td style="text-align: right;">19,6%</td>
</tr>
<tr>
<td>zapis TLS 1 KiB</td>
<td style="text-align: right;">24%</td>
<td style="text-align: right;">16,1%</td>
</tr>
<tr>
<td>zapis TLS 4 KiB</td>
<td style="text-align: right;">13%</td>
<td style="text-align: right;">6,6%</td>
</tr>
<tr>
<td>razdeljevanje sporočil</td>
<td style="text-align: right;">17 do 34%</td>
<td style="text-align: right;">7 do 34%</td>
</tr>
</tbody>
</table>
<p>Branje grafa: manjši ko je zapis, večji del časa odide za kopiranje, zato je tudi dobitek večji. Za majhna sporočila, ki so večina prometa MQTT, je to tisto, kar šteje.</p>
<p>Kaj to ne pomeni. Na izvornem x86_64 smo OpenSSL 3.5.5 merili samo kot referenco za zbirnik (ADR-130), ne kot primerjavo izdelkov. Primerjave Linux plus Mosquitto na istem VPS-ju (naloga X11) še nismo naredili. Zato ne trdimo, da je urMQTT hitrejši od Linuxa: ni izmerjeno. Zgornje številke primerjajo urMQTT sam s seboj.</p>
<p>Preobremenitev je bil drugi rezultat. Na VPS-ju, z enega Maca (RTT 35 ms), je 128 odjemalcev dalo 618 rokovanj na sekundo, 256 odjemalcev 393, 512 odjemalcev pa 67, z na tisoče zavrnjenih povezav. To ni bil plato, ampak sesutje. Vzroke smo našli v kodi (dva sta potrjena v QEMU-ju/HVF-ju, na VPS-ju se ju ni dalo ponoviti), več hkrati: zavrnjena povezava je odjemalca vrnila prek naslednjega segmenta, ki nosi veljaven piškotek SYN, zavrnitev z RST je odjemalcu sporočala, naj takoj poskusi znova, sprejem pa je bil O(vrsta). Popravek (ADR-080 do ADR-082) izpusti SYN, ki ne bi bil sprejet, si zapomni zavrnjene tokove, drži sprejem v O(1) in šteje rokovanja po jedru. Možno je (hipoteza, ni preverjeno), da je bil del vzroka tudi na odjemalcu: orodje za obremenitev je zapiralo prvo, zato je en Mac s 16 384 efemernimi vrati in TIME_WAIT-om 30 sekund omejen na okoli 550 povezav na sekundo. Zato pravega stropa strežnika še nismo izmerili.</p>
<h2 id="obljuba-2-surovi-disk-a-previden">Obljuba 2: surovi disk, a previden</h2>
<p><strong>Kaj smo načrtovali.</strong> Obdržati lasten razpored brez datotečnega sistema in dodati vse tisto, kar datotečni sistem sicer daje: poravnavo, rast, discard, varnostne kopije in preverjanja. <strong>Zakaj ne datotečni sistem.</strong> O tem smo razpravljali in odločitev je potrjena: urMQTT piše eno bazo, en telemetrijski dnevnik in lastne metapodatke, ne potrebuje imenikov, dovoljenj ali imen datotek, vsaka dodatna plast pa bi bila še več kode v zaupanja vrednem delu in še en način, da zapis tiho izgine. Disk je GPT s particijo ESP in eno particijo podatkov urMQTT, na kateri je lasten razpored.</p>
<p><strong>Kaj smo zgradili (S2 do S6).</strong></p>
<ul>
<li><strong>Razpored v2 (S2).</strong> Topologija diska, poravnava na 1 MiB ali optimalno velikost V/I, celi fizični bloki, par superblokov A/B z generacijo (ADR-140). Razpored v1 kernel zavrne z jasnim sporočilom, preseli pa ga izvenmrežno orodje <code>urmqtt-disk migrate</code> (ADR-141): privzeto je poskusni prehod, kopira se in šele nato preklopi, z vrnitvijo in vbrizgavanjem napak pri vsakem zapisu.</li>
<li><strong>Rast (S3).</strong> Ob zagonu lahko disk zraste (nikoli se ne zmanjša), na virtio-blk pa tudi med delovanjem, prek prekinitve spremembe konfiguracije (ADR-142, ADR-143).</li>
<li><strong>Discard in write-zeroes (S4).</strong> Tanko dodeljeni diski vračajo prostor gostitelju. V testu je datoteka qcow2 na gostitelju padla s 62 na 16 MiB, ko so potekli telemetrijski segmenti, obnovitev pa nikoli ne zaupa vsebini, ki je bila zavržena.</li>
<li><strong>Varnostna kopija baze na disku (S5).</strong> Kopija A/B s SQLite online backup API-jem v korakih po 16 strani: najdaljši korak 4,6 ms proti 445 ms, kadar se kopira z enim klicem. Commiti med kopiranjem so imeli maksimum 2,4 ms proti 2,2 ms brez njega. Če <code>quick_check</code> ob zagonu ne uspe, se baza obnovi iz kopije.</li>
<li><strong>Čiščenje in omejen zagon (S6).</strong> Scrubber bere disk z omejenim V/I proračunom in poroča o poškodovanih zapisih, zagon pa prebere samo najnovejši segment vsake particije, tako da čas zagona ne raste z velikostjo telemetrijske regije. Tako smo zaprli težavo, ki smo jo izračunali vnaprej: na velikem disku bi popolna obnovitev trajala od minut do več deset minut.</li>
</ul>
<p>Celotna skupina dimnih testov za shranjevanje prehaja na obeh arhitekturah: 79 uspešnih, 0 neuspešnih, na aarch64 pod HVF-jem in na x86_64 pod TCG-jem. Izmerili smo tudi, da S4 ni upočasnil baze: 53,3 tisoč vstavljanj na sekundo v paketih prej in potem, brez merljive razlike.</p>
<p><strong>Kaj je presenetilo.</strong> Koliko »preprostega« razporeda odpade na robne primere: virtio-scsi in virtio-blk poročata topologijo na različne načine, nepopoln zapis sredi migracije pa mora vedno pustiti vsaj eno veljavno različico. Zato je vsak zapis na poti migracije in rasti šel skozi vbrizgavanje napak.</p>
<p><strong>Kaj je odprto.</strong> Strani SQLite nimajo kontrolne vsote, zato spremenjenega bita znotraj vrednosti v vrstici baza ne bo zaznala; telemetrijski zapisi in vsebina kopij imajo CRC in se zaznajo. Šifriranje diska (S7) ni končano.</p>
<h2 id="obljuba-3-dokazljivo-varno-kolikor-nam-uspe">Obljuba 3: dokazljivo varno, kolikor nam uspe</h2>
<p><strong>Utrjevanje (H4).</strong> Alokator briše sproščeni pomnilnik in ga preverja ob ponovni uporabi, zazna dvojno sprostitev, kazalce proste liste veže z XOR-om in ob sprostitvi ničli ključe (ADR-170). Zaščita sklada (<code>-Zstack-protector=strong</code>, 1315 funkcij, SQLite pa s <code>-fstack-protector-strong</code>, ADR-171). Poročila o napakah v produkciji ne vsebujejo registrov ali naslovov (ADR-172). KASLR na obeh arhitekturah: kernel je PIE in ga Limine premakne za okoli 19 bitov, naključno pa se postavijo tudi kopica, skladi in okno MMIO (ADR-173). Na aarch64 MTE označuje kopico (ADR-174), vendar samo pod TCG-jem: HVF in x86 ga nimata. kCFI smo ovrednotili in ga nismo sprejeli, ker prevajalnik na pripetem nightlyju pade z notranjo napako; IOMMU je odložen skupaj z bare metalom (ADR-175, ADR-176).</p>
<p><strong>Ceno smo izmerili, ne predpostavili.</strong> Na poti rokovanja (<code>dp-bench</code>, aarch64 HVF, polna rokovanja TLS 1.3 + mTLS + CONNECT):</p>
<table>
<thead>
<tr>
<th>Korak</th>
<th style="text-align: right;">1 jedro</th>
<th style="text-align: right;">4 jedra</th>
<th>Sprememba</th>
</tr>
</thead>
<tbody>
<tr>
<td>brez utrjevanja</td>
<td style="text-align: right;">1941</td>
<td style="text-align: right;">7620</td>
<td></td>
</tr>
<tr>
<td>+ alokator</td>
<td style="text-align: right;">1898</td>
<td style="text-align: right;">7429</td>
<td>-2,2% do -2,5%</td>
</tr>
<tr>
<td>+ zaščita sklada</td>
<td style="text-align: right;">1845</td>
<td style="text-align: right;">7243</td>
<td>skupaj -4,9% do -5%</td>
</tr>
<tr>
<td>+ PIE in oznake</td>
<td style="text-align: right;">okoli 1800</td>
<td style="text-align: right;">okoli 6750</td>
<td>na obremenjenem računalniku, šumno</td>
</tr>
</tbody>
</table>
<p>Skupaj je torej cena na poti rokovanja približno 5% za dele, izmerjene čisto, s PIE-jem in oznakami pa pride okoli 7% na enem in okoli 11% na štirih jedrih, a je ta meritev šumna. Menimo, da je sprejemljiva, vendar je nismo ponovili na VPS-ju x86_64.</p>
<p>Vredno je primerjati s številko iz prvega besedila (862 rokovanj na sekundo na enem jedru). Isto orodje danes, s strojnim šifriranjem na aarch64 (X0b) in nizom pospešitev, ki so prišle od takrat, da 1941 pred utrjevanjem. Prispevka vsake spremembe nismo izolirali, zato o vzroku ne trdimo ničesar.</p>
<p><strong>Kako je preverjeno in kaj ni.</strong> H5 je dodal Miri na podmnožico gostiteljskih cratov, dokaze Kani, sanitizerje, model-fuzzer za ACL in poročila o pokritosti. Kani je dokazal <code>record_len</code> v TLS-u, <code>parse</code> v TCP-ju in dve aritmetiki kolesa časovnikov. <strong>Ni dokazano</strong> (potekel čas): oba dekodirnika MQTT, oba razčlenjevanja konfiguracije in obnašanje kolesa časovnikov; delo CI za Kani zato še ne blokira. Miri ne more izvajati zbirnika. Dobavna veriga (H9): CycloneDX SBOM s 215 komponentami, <code>cargo-vet</code> prehaja s 39 v celoti pregledanimi in 140 izjemami, cargo-deny pa je nastavljen tako, da varnostna obvestila obravnava kot padec CI-ja (ta dela CI so bila doslej preverjena samo lokalno, ne na GitHubu, negativni test pa ni bil zagnan), skupaj z <code>VULNERABILITY-HANDLING.md</code>. Iskreno stanje: od 67 različic tretjih cratov, povezanih v kernel, jih je 53 izjem, ne pregledov. Vet dokazuje, da se množica odvisnosti ne more spremeniti brez vidne razlike, ne da je bil TCB pregledan. Lastnega pregleda nobenega cratea nismo napisali.</p>
<p>SNTP (T1) je v tem smislu majhna zgodba o varnosti: je neobvezen, samo proti zasebnim strežnikom IPv4, z največjim skokom in pogojem, da se viri ujemajo (mediana, večina znotraj ene sekunde). Ura, ki jo lahko premakne kdor koli na poti, ni ura, ki ji smemo zaupati pri veljavnosti certifikatov.</p>
<h2 id="na-macu-aarch64-izvorno-in-x86_64-z-emulacijo">Na Macu: aarch64 izvorno in x86_64 z emulacijo</h2>
<p>Razvijamo na Macu, ta pa ima dva načina za zagon iste kode in sta zelo različna.</p>
<table>
<thead>
<tr>
<th></th>
<th>aarch64 pod HVF-jem</th>
<th>x86_64 pod TCG-jem</th>
</tr>
</thead>
<tbody>
<tr>
<td>Kaj je to</td>
<td>izvorno izvajanje, hitrost blizu pravega procesorja</td>
<td>QEMU emulira ukaze</td>
</tr>
<tr>
<td>Rokovanje v gostu</td>
<td>okoli 0,58 ms (prvič izmerjeno okoli M13); <code>dp-bench</code> danes 1941/s na jedro</td>
<td>okoli 3,3 ms</td>
</tr>
<tr>
<td>Scenariji dimnega testa TLS</td>
<td>hitri</td>
<td>okoli 15 minut na scenarij</td>
</tr>
<tr>
<td>MTE</td>
<td>ne (na HVF-ju ga ni)</td>
<td>x86 ga nima; označevanje kopice deluje samo za gosta aarch64 pod TCG-jem</td>
</tr>
<tr>
<td>Čemu služi</td>
<td>prave meritve</td>
<td>pravilnost: pot x86 se preverja, številke niso zmogljivost</td>
</tr>
</tbody>
</table>
<p>Pravilo je preprosto: številke pod TCG-jem so emulacija in jih ne navajamo kot zmogljivost. Zmogljivost x86 merimo samo na pravem VPS-ju. Zato kripto številke zgoraj izhajajo z VPS-ja Contabo, ne z Maca.</p>
<h2 id="lekcije">Lekcije</h2>
<ul>
<li><strong>Merite na pravem stroju, preden optimirate.</strong> Prvi pristop je bil optimirati tisto, za kar smo mislili, da je počasno; šele diagnostika na VPS-ju je pokazala 1,2 ms in kje natančno odhaja čas.</li>
<li><strong>Preobremenitev je ločena lastnost.</strong> Sistem, ki je hiter pod obremenitvijo, se lahko sesuje nad njo; zavrnitev mora biti cenejša od sprejema.</li>
<li><strong>Profil preseneča.</strong> Največji posamični dobitek za majhna sporočila ni bil v kriptografiji, ampak v kopiranju pomnilnika.</li>
<li><strong>Disk brez datotečnega sistema zahteva več dela, ne manj.</strong> Tisto, kar datotečni sistem sicer daje (poravnavo, rast, discard, kopije, preverjanja), moramo napisati in preveriti sami, vsak korak z vbrizgavanjem napak.</li>
<li><strong>Poštenost v številkah.</strong> Meritve pomnilnika na deljenem VPS-ju se med zagoni razlikujejo do 10-krat in o kodi ne povedo ničesar, zato jih ne navajamo. Kripto številke so stabilne do okoli 10%.</li>
<li><strong>Hitrost razvoja ni zrelost.</strong> Testi in ADR-ji rastejo skupaj s kodo, a štirje dnevi niso pravo obratovanje.</li>
</ul>
<h2 id="kaj-sledi">Kaj sledi</h2>
<ul>
<li><strong>H6:</strong> SQLite, izoliran na ravni EL0, tako da napaka v bazi ne odpre jedra.</li>
<li><strong>H7:</strong> preostanek varnosti časa (NTS in obdelava skokov); SNTP je šele prvi korak.</li>
<li><strong>X11:</strong> primerjava z Linuxom in Mosquittom na istem VPS-ju, šele takrat je smiselno govoriti o relativni hitrosti.</li>
<li><strong>Izdaja:</strong> prva javna objava za VPS (KVM), s podpisanimi artefakti in navodili.</li>
<li><strong>Po izdaji:</strong> bare metal (X12 do X16), IOMMU in Secure Boot ter šifriranje diska.</li>
</ul>
<p>Koda je na <a href="https://github.com/zeljan-alduk/urMQTT">github.com/zeljan-alduk/urMQTT</a>, <code>docs/PROGRESS.md</code> pa je še naprej edini vir resnice o tem, kaj res deluje. Predalfa, kot prej; prijave napak in varnostne prijave po navodilih v <code>SECURITY.md</code> so dobrodošle.</p>]]></content:encoded>
      <category>rust</category>
      <category>mqtt</category>
      <category>iot</category>
      <category>unikernel</category>
      <category>open source</category>
    </item>
    <item>
      <title>urMQTT: gradimo MQTT posrednik, ki je hkrati operacijski sistem</title>
      <link>https://aldo.tech/sl/blog/urmqtt-unikernel-mqtt-posrednik/</link>
      <guid isPermaLink="true">https://aldo.tech/sl/blog/urmqtt-unikernel-mqtt-posrednik/</guid>
      <pubDate>Thu, 29 Oct 2026 08:00:00 +0000</pubDate>
      <description>Razvojni zapiski o urMQTT, unikernelu, napisanem v Rustu, ki je sam po sebi MQTT posrednik: en ELF z lastnim TCP/IP, TLS 1.3, posrednikom in SQLite, zagnan z enega ISO-a.</description>
      <content:encoded><![CDATA[<p>Naprave v vozilih, na parkiriščih in v elektronskih omarah imajo eno skupno lastnost: pošiljajo majhna sporočila, redko, a jih je veliko in so nenehno povezane. Že leta jih povezujemo z MQTT posredniki in že leta nas muči isto vprašanje. Zakaj za nekaj tako preprostega potrebujemo cel Linux, OpenSSL, izvajalno okolje nekega jezika, SSH, upravitelj paketov in šele nato posrednika? Vsaka od teh plasti prinaša svoje ranljivosti, porablja svoj pomnilnik in dodaja še eno stvar, ki se lahko pokvari, flota naprav pa od strežnika potrebuje le troje: da drži veliko skoraj nedejavnih povezav, da vsako napravo zanesljivo overi in da sporočila prenese brez izgub.</p>
<p>Tako je nastal <a href="https://github.com/zeljan-alduk/urMQTT">urMQTT</a>: unikernel, ki <em>je</em> MQTT posrednik. Jedro, TCP/IP, TLS 1.3, MQTT in vgrajena podatkovna baza v enem Rustovem binarnem programu, ki se zažene z enega ISO-a. Ni procesov, ni sistemskih klicev, ni lupine. Prva platforma, na kateri se gradi in preizkuša, je aarch64, isto kodo pa pripravljamo za x86_64, prek UEFI in klasičnega BIOS-a, ter za pravo strojno opremo: cilj je en ISO, ki se zažene na vseh. Projekt je odprtokoden pod licenco MIT ali Apache 2.0, v tem besedilu pa želimo zapisati, kje smo, kako je zgrajen znotraj in česa smo se med delom naučili.</p>
<h2 id="kaj-natancno-pomeni-unikernel">Kaj natančno pomeni »unikernel«</h2>
<p>Običajen posrednik je program, ki teče na operacijskem sistemu. urMQTT pod sabo nima operacijskega sistema: sam se zažene prek UEFI firmwara in zagonskega nalagalnika Limine, vzpostavi lastne tabele strani, programira krmilnik prekinitev in časovnik, prek PCIe poišče naprave virtio, naslov pridobi z DHCP in posluša na enih vratih. Vse, kar bi sicer počeli jedro, gonilniki, omrežni sklad in aplikacija, je tu en ELF, ki teče na ravni EL1.</p>
<p>Primerjava s tipično namestitvijo govori sama zase:</p>
<table>
<thead>
<tr>
<th></th>
<th>Tipičen posrednik na Linuxu</th>
<th>urMQTT</th>
</tr>
</thead>
<tbody>
<tr>
<td>Izpostavljene storitve</td>
<td>SSH, posrednik, pogosto tudi vrata za metrike ali administracijo</td>
<td><strong>samo TCP 8883</strong> (MQTT prek TLS 1.3 z mTLS)</td>
</tr>
<tr>
<td>Koda v sliki</td>
<td>jedro, uporabniški prostor, libc, OpenSSL, izvajalno okolje</td>
<td><strong>en Rustov ELF</strong> (plus vendoriran SQLite)</td>
</tr>
<tr>
<td>Oddaljeno upravljanje</td>
<td>lupina, konfiguracijske datoteke, posodobitve paketov</td>
<td><strong>nič.</strong> Nova različica je nov ISO</td>
</tr>
<tr>
<td>Pomnilnik na nedejavno povezavo</td>
<td>kilobajti (medpomnilniki vtičnic, objekti TLS, kopica)</td>
<td><strong>cilj ≤ 512 B</strong></td>
</tr>
<tr>
<td>Večjedrni model</td>
<td>niti in ključavnice ali ena zanka dogodkov</td>
<td><strong>nit na jedro, brez skupnega stanja, obdelava do konca</strong></td>
</tr>
<tr>
<td>Celovitost kode</td>
<td>možne so preslikave, ki so hkrati zapisljive in izvršljive</td>
<td><strong>W^X, zaklenjene tabele strani, PAC/BTI</strong></td>
</tr>
</tbody>
</table>
<p>Prednostne naloge so v tem vrstnem redu: varnost, razširljivost, večjedrna zmogljivost, majhen odtis in nespremenljiva namestitev. Kadar sta dva cilja v navzkrižju, zmaga tisti, ki je na tem seznamu višje.</p>
<h2 id="kje-smo-zdaj">Kje smo zdaj</h2>
<p>Projekt je v predalfa fazi, vendar je zaprtih sedemnajst razvojnih mejnikov, od M0 do M17. Danes urMQTT streže MQTT 3.1.1 prek TLS 1.3 z obveznimi odjemalskimi certifikati, stanje pa hrani v SQLite na surovem disku. Na kratko, kaj že deluje:</p>
<ul>
<li><strong>Jedro:</strong> lastni vektorji izjem z dekodiranimi poročili, lastne tabele strani z uveljavljenim W^X, zaščitne strani, PAC in BTI, kjer ju procesor ima, GICv3 in GICv2, generični časovnik, SMP do 64 jeder.</li>
<li><strong>Odkrivanje strojne opreme</strong> iz DTB ali ACPI v en opis platforme, z enakim rezultatom iz obeh virov.</li>
<li><strong>Gonilniki:</strong> PCIe ECAM, MSI-X, virtio-net, virtio-blk, virtio-scsi in virtio-rng ter CSPRNG, zasejan s strojno entropijo.</li>
<li><strong>Omrežje:</strong> lasten DHCPv4, ARP, ICMP in strogi IPv4, nato lasten TCP samo za strežniško stran: piškotki SYN, hierarhično kolo časovnikov, NewReno, združevanje ACK-jev, brez stanja TIME_WAIT.</li>
<li><strong>TLS:</strong> samo TLS 1.3, obvezni mTLS, identiteta naprave iz CN ali SAN URI; rokovanje opravi rustls, nato pa lasten sloj zapisov z AES-128-GCM in ChaCha20-Poly1305. Nadaljevanje seje s PSK-vstopnicami, seznam preklicev, nadzor sprejema že pri rokovanju.</li>
<li><strong>MQTT:</strong> kodek brez kopiranja, fuzziran in testiran s pravimi odjemalci mosquitto; QoS 0 in 1, nadomestni znaki, obdržana sporočila, oporoka (LWT), trajne seje, ACL z omejitvijo na <code>devices/{id}/#</code>, omejitve hitrosti po napravi.</li>
<li><strong>Shranjevanje:</strong> SQLite znotraj jedra prek VFS nad surovim diskom, v načinu WAL, s preverjanjem celovitosti ob zagonu; register naprav, preklici, obdržana sporočila, trajne seje z vrstami sporočil za naprave, ki niso povezane; za vsako jedro ločen telemetrijski dnevnik s CRC na zapis in obnovitvijo po sesutju.</li>
<li><strong>Delovanje in nadzor:</strong> nadzorni časovnik za obdelovalce, preverjanje zdravja, omejeno beleženje, syslog in statsd prek UDP, sprejem, ki preživi nevihte ponovnih povezav.</li>
<li><strong>Dostava:</strong> bitno enake ponovne izgradnje, preverjene v CI, ISO in surova GPT slika diska, podpisi minisign, navodila za KVM, Proxmox in Hetzner.</li>
</ul>
<p>Vsak korak je označen z mejnikom, ki ga je prinesel, datoteka <code>docs/PROGRESS.md</code> v repozitoriju pa je edini vir resnice o tem, kaj res deluje.</p>
<h2 id="arhitektura-v-treh-slikah">Arhitektura v treh slikah</h2>
<p>Celoten načrt je opisan v <code>docs/ARCHITECTURE.md</code>, razlogi za vsako pomembnejšo odločitev pa so zapisani kot ADR-ji v <code>docs/DECISIONS.md</code>. Tu je pregled.</p>
<h3 id="plasti">Plasti</h3>
<figure><img loading="lazy" alt="Plasti urMQTT: od posrednika prek kodeka MQTT, TLS, TCP in omrežja do gonilnikov virtio in arhitekturne plasti, vse v enem ELF-u na ravni EL1." src="https://aldo.tech/blog/urmqtt/img/layers.svg" /><figcaption>Plasti urMQTT: od posrednika prek kodeka MQTT, TLS, TCP in omrežja do gonilnikov virtio in arhitekturne plasti, vse v enem ELF-u na ravni EL1.</figcaption></figure>
<p>Od vrha navzdol: posrednik (seje, drevo tem, QoS, obdržana sporočila, oporoke, ACL, omejitve), kodek MQTT, TLS, TCP, omrežna plast in nato gonilniki. Ob tej glavni poti stoji shranjevanje (SQLite in telemetrijski dnevnik), pod vsem pa upravljanje pomnilnika, odkrivanje platforme in konfiguracija, ki je ob izgradnji prevedena v konstante, skupaj z vgrajenim certifikatom CA, strežniškim certifikatom in ključem. Hipervizor z napravami virtio je edino, kar je zunaj slike.</p>
<h3 id="nit-na-jedro-brez-skupnega-stanja">Nit na jedro, brez skupnega stanja</h3>
<figure><img loading="lazy" alt="Model izvajanja: vsako podatkovno jedro ima svojo zanko in svoj del posrednika, najvišje jedro pa poganja samo SQLite. Jedra komunicirajo izključno z obroči SPSC." src="https://aldo.tech/blog/urmqtt/img/cores.svg" /><figcaption>Model izvajanja: vsako podatkovno jedro ima svojo zanko in svoj del posrednika, najvišje jedro pa poganja samo SQLite. Jedra komunicirajo izključno z obroči SPSC.</figcaption></figure>
<p>Vsako podatkovno jedro poganja lastno zanko z lastnim skladom TCP, stanji TLS, delom posrednika (seje in drevo tem), predpomnilnikom kopice in particijo telemetrije. Jedra ne delijo nobenega spremenljivega stanja, le omejene obroče brez ključavnic, po enega za vsak urejen par jeder. Najvišje jedro poganja SQLite in nič drugega.</p>
<p>Omrežna kartica ima danes en par vrst, ki ga drži jedro 0. To vsak tok MQTT glede na Toeplitzev hash njegove četverice usmeri na eno od delovnih jeder in pošlje tisto, kar delovna jedra vrnejo. Seja živi na svojem domačem jedru, določenem s hashem identifikatorja odjemalca, zato prevzem, oporoke in trajnost nikoli ne prečkajo jeder. Povezava, ki pristane na drugem jedru, tam obdrži svoj TCP in TLS, navaden MQTT pa posreduje domačemu jedru z nadzorom pretoka, ki temelji na kreditih. Vsako usmerjeno sporočilo se razdeli tudi na preostale dele posrednika, od katerih vsak drži repliko obdržanih sporočil.</p>
<p>Ta model drže skupaj tri pravila. <strong>Obdelava do konca:</strong> prejeti paket se v celoti obdela na enem jedru, od TCP prek dešifriranja TLS na mestu, razčlenjevanja MQTT in usmerjanja do šifriranega odgovora, brez vrst opravil in brez kopiranja v aplikacijske medpomnilnike. <strong>Prilagodljivo poizvedovanje:</strong> pod obremenitvijo se vrste za sprejem aktivno poizvedujejo, v mirovanju pa se jedro vrne k prekinitvam in <code>WFI</code>; obdelovalci prekinitev prekinitev le potrdijo in označijo delo. <strong>Nadzorni časovnik:</strong> utrip na 10 ms na jedro ujame obdelovalce, ki presežejo svoj proračun.</p>
<h3 id="zivljenje-enega-publish-a">Življenje enega PUBLISH-a</h3>
<figure><img loading="lazy" alt="Pot enega sporočila PUBLISH s QoS 1: od zapisa TLS na omrežni kartici prek dešifriranja, preverjanja ACL in usmerjanja do PUBACK-a, združenega s TCP ACK v enem segmentu." src="https://aldo.tech/blog/urmqtt/img/publish.svg" /><figcaption>Pot enega sporočila PUBLISH s QoS 1: od zapisa TLS na omrežni kartici prek dešifriranja, preverjanja ACL in usmerjanja do PUBACK-a, združenega s TCP ACK v enem segmentu.</figcaption></figure>
<p>Naprava pošlje zapis TLS. Omrežna kartica ga preda zanki jedra z deskriptorjem, brez kopiranja. Zanka preveri TCP, dešifrira TLS na mestu in razčleni MQTT iz izposojenega medpomnilnika, nato v pomnilniku preveri ACL, omejitev hitrosti in pot skozi drevo tem. Če drugo jedro ta tema zanima, prejme sporočilo za usmerjanje s sklicem na vsebino in identifikatorjem teme; baza prejme telemetrijski zapis ali posodobitev obdržanega sporočila, v paketu. Odgovor PUBACK se šifrira neposredno v izhodni medpomnilnik in združi s TCP ACK, tako da napravi odide en segment. Drugo jedro sporočilo stane eno šifriranje na naročnika v trenutku pošiljanja.</p>
<h2 id="koliko-stane-ena-povezava">Koliko stane ena povezava</h2>
<p>Cilj je <strong>največ 512 bajtov na nedejavno povezavo TLS</strong>, z željo po 256. Medtem ko povezava miruje, zanjo ni v medpomnilniku ničesar: prejeti podatki se obdelajo neposredno iz omrežnega okvirja, medpomnilniki za pošiljanje, zapise in MQTT pa obstajajo samo, dokler je kaj nedokončanega ali nepotrjenega. Jedro to meri samo: natančni števci kopice v zahtevanih bajtih in v blokih, ki jih zasedajo, zapisani vsakih deset sekund kot kopica nad osnovno ravnjo na odprto povezavo.</p>
<table>
<thead>
<tr>
<th>Del</th>
<th style="text-align: right;">Velikost</th>
<th>Kako</th>
</tr>
</thead>
<tbody>
<tr>
<td>Kontrolni blok TCP</td>
<td style="text-align: right;">96 B</td>
<td>zapakirane zastavice, 16-bitni RTT/RTO, reža v kolesu časovnikov, brez medpomnilnikov v mirovanju</td>
</tr>
<tr>
<td>Reža v tabeli TCP</td>
<td style="text-align: right;">4–8 B</td>
<td>samo indeks povezave, tabela največ do polovice polna</td>
</tr>
<tr>
<td>Povezava in stanje zapisov TLS 1.3</td>
<td style="text-align: right;">152 B</td>
<td>skrivnost in zaporedje po smeri (40 B); ključ in IV se izpeljeta za vsak zapis</td>
</tr>
<tr>
<td>Povezava MQTT</td>
<td style="text-align: right;">48 B</td>
<td>identiteta in oporoka na kopici samo, dokler obstajata</td>
</tr>
<tr>
<td>Seja MQTT, vroča oblika</td>
<td style="text-align: right;">64 B</td>
<td>ena naročnina v vrstici, internirani identifikatorji vozlišč</td>
</tr>
<tr>
<td>Naročnina <code>devices/&lt;id&gt;/cmd/#</code></td>
<td style="text-align: right;">0 vozlišč drevesa</td>
<td>shranjena relativno glede na identifikator seje, skupna vsem napravam</td>
</tr>
</tbody>
</table>
<p>Izmerjeno pod HVF s štirimi virtualnimi jedri, od tega tremi delovnimi, pri čemer vsaka naprava drži povezavo mTLS in eno naročnino s QoS 1, vključno z zaokroževanjem alokatorja:</p>
<table>
<thead>
<tr>
<th style="text-align: right;">Povezave</th>
<th style="text-align: right;">Bajtov na povezavo</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>Bazen omrežnih okvirjev je fiksnih približno 2 MiB, kar je okoli 200 B na povezavo pri desettisoč povezavah in okoli 2 B pri milijonu. Več od tega nismo mogli izmeriti: uporabniško omrežje QEMU na macOS je prenehalo sprejemati povezave pri 10 197, približno na meji 10 240 deskriptorjev procesa QEMU. Število pada, ko se fiksni deli porazdelijo na več povezav, zato ne raste z njihovim številom.</p>
<h2 id="varnostni-model">Varnostni model</h2>
<p>Varnost je prva prednostna naloga in dobi vse spore z ostalimi cilji. Model ima več plasti:</p>
<ul>
<li><strong>Ena vrata.</strong> TCP 8883, samo TLS 1.3, obvezni odjemalski certifikati, podpisani s CA, vgrajenim v sliko. Identifikator odjemalca MQTT mora biti enak identifikatorju naprave iz certifikata.</li>
<li><strong>Brez vhodov.</strong> Ni lupine, konzolnega vnosa, SSH, administracijskega API-ja, dinamične kode, SQL, dostopnega prek omrežja, niti datotečnega sistema.</li>
<li><strong>Pomnilniška varnost.</strong> Rust, s <code>forbid(unsafe_code)</code> v protokolnih paketih; vsak blok <code>unsafe</code> ima komentar <code>// SAFETY:</code>, ki ga preverja lint, na zunanji vnos pa ni panike: clippy v paketih na podatkovni poti prepoveduje <code>unwrap</code>, <code>expect</code>, indeksiranje in <code>panic!</code>.</li>
<li><strong>Ublažitev izkoriščanja.</strong> W^X se uveljavlja s kodiranjem deskriptorjev in bitom <code>SCTLR_EL1.WXN</code> ter se revidira ob vsakem zagonu. Tabele strani so po zagonu samo za branje, brez zapisljivega aliasa. Spodnja polovica naslovnega prostora je izključena. Vsak sklad ima zaščitne strani, izjeme pa tečejo na ločenem skladu. PAC in BTI se vklopita, ko ju procesor podpira, to pa preverjajo dimni testi.</li>
<li><strong>Odpornost na zlorabe.</strong> Piškotki SYN; sprejem zavrne nove povezave, pred kakršno koli kriptografijo, ko je kopica nizka, ko baza zaostaja, ko je v teku preveč rokovanj na jedro ali na izvorni naslov ali ko je hitrost rokovanj previsoka. Omejitve hitrosti po napravi, omejene vrste, okno TCP, vezano na protipritisk posrednika, omejeno beleženje, nadzorni časovnik in preverjanje zdravja.</li>
<li><strong>Nespremenljivost.</strong> Konfiguracija, CA in strežniški ključ so vgrajeni v sliko. Sprememba česar koli pomeni novo sliko.</li>
</ul>
<p>Model groženj je zapisan v <code>docs/SECURITY.md</code>.</p>
<h2 id="shranjevanje-brez-datotecnega-sistema">Shranjevanje brez datotečnega sistema</h2>
<p>Podatki živijo na ločenem disku, virtio-blk ali virtio-scsi, na katerem ni datotečnega sistema. Razpored je minimalen in lasten:</p>
<pre><code>LBA 0      [ superblok: magija · različica razporeda · uuid · tabela regij · CRC-32C ]
LBA 8, 16  [ tabela velikosti datotek, reži A/B (zmaga najnovejša veljavna) ]
LBA 64     [ SQLite glavna baza ½ ][ SQLite WAL ⅛ ][ dnevnik 1 MiB ][ telemetrijski dnevnik: 8 particij … ]
zadnjih 4 KiB [ prostor za samopreverjanje blokovne naprave ob zagonu ]
</code></pre>
<p>SQLite (vendorirana 3.54.1, s preverjanjem izvlečka ob izgradnji) je v jedro preveden s clangom, brez standardne knjižnice, in uporablja fiksno kopico <code>memsys5</code>. Njegov VFS preslika tri fiksne datoteke na regije diska. Deluje v načinu WAL z <code>locking_mode=EXCLUSIVE</code>, zato ne potrebuje indeksa v deljenem pomnilniku, in s <code>synchronous=FULL</code>; če <code>quick_check</code> ob zagonu najde napako, se sistem varno ustavi. V bazi so register naprav, preklici, obdržana sporočila in trajne seje z vrstami sporočil QoS 1 za naprave, ki niso povezane.</p>
<p>Jedro za bazo, tisto z najvišjo številko, poganja SQLite in nič drugega. Podatkovna ravnina mu pošilja spremembe stanja prek obroča SPSC brez ključavnic; te zbira v eno transakcijo vsakih 5 ms ali vsakih 512 operacij, potrditve pa vrača z drugim obročem. Prazen disk se formatira ob prvem zagonu; vsega drugega se nikoli ne formatira čez.</p>
<p>Telemetrija gre po drugi poti. Vsak sprejeti PUBLISH na temah iz <code>storage.telemetry_topics</code> odide v particijo telemetrijskega dnevnika svojega jedra: obroč segmentov s CRC na zapis, osoljenim z zaporedjem segmenta, tako da se stari prehodi obroča nikoli ne pojavijo znova. Jedra zapise predajo bazi z lastnim obročem, brez kopiranja, ta pa jih izpiše vsakih 5 ms ali vsakih 256 KiB. Ob zagonu obnovitev ohrani vsak izpisani zapis, odreže nedokončan rep in nadaljuje v novem segmentu. Hramba je po velikosti, prek obroča, in po starosti, prek <code>retention_days</code>. To smo preverili z milijonom zapisov in z <code>kill -9</code> sredi pisanja.</p>
<h2 id="stevilke">Številke</h2>
<p>Orodje za obremenitev v <code>tools/loadgen</code> počne tri stvari: nevihto rokovanj TLS 1.3 z mTLS in CONNECT, dolgotrajne povezave s PINGREQ, ki vsako sekundo objavljajo telemetrijo, in nevihte ponovnih povezav. <code>make bench</code> na Apple M4 Pro pod HVF daje:</p>
<table>
<thead>
<tr>
<th>Delovna jedra</th>
<th>rokovanj/s v gostu</th>
<th>skaliranje</th>
<th>prek uporabniškega omrežja QEMU (povezav/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>Število v gostu, kjer sta odjemalec in strežnik znotraj istega virtualnega stroja in vsako delovno jedro dela samo zase, kaže, da podatkovna ravnina skalira linearno. Omrežno število omejuje enonitno uporabniško omrežje QEMU, ne gost; tudi to je zapisano kot ADR-032.</p>
<p>Nevihte ponovnih povezav testiramo tako, da se vse naprave povežejo naenkrat, zavrnjene poskuse ponavljajo z zakasnitvijo in nato vse padejo brez DISCONNECT. Parameter <code>heap_limit_mb=</code> v razvojnem načinu prisili sprejem, da kopico šteje za manjšo, kot je, tako da je nevihto, večjo od kopice, mogoče preizkusiti z malo vtičnicami. V enem takem krogu s 3 000 napravami se jih je v 36 sekundah povezalo 1 368, 5 619 poskusov pa je bilo urejeno zavrnjenih, z omejitvijo 8 MiB; pomnilnik se je po nevihti vrnil.</p>
<h2 id="kako-delamo">Kako delamo</h2>
<p>Projekt vodi pisna specifikacija, <code>PROMPT.md</code>, z načrtom mejnikov, in vsak mejnik se konča z istim obredom: <code>make check</code> in <code>make smoke</code> zelena, en commit z oznako <code>M&lt;n&gt;</code>, zapis v <code>docs/PROGRESS.md</code> in ADR za vsako odločitev, ki ni očitna. Pravila so kratka in o njih se ne pogajamo: brez panike na zunanji vnos, <code>unsafe</code> samo v strojnih paketih in paketih FFI ter vedno s komentarjem, brez alokacij na vroči poti, komentar ob vsakem vrstnem redu atomskih operacij, vsaka omejitev prihaja iz konfiguracije, namerni začasni deli pa padejo glasno.</p>
<p>CI dela več, kot bi jih mi ročno zmogli: fmt, clippy za gostitelja in za <code>aarch64-unknown-none</code>, typos, actionlint in shellcheck; teste na Linuxu in macOS na arm64; izgradnjo jedra za razvojno in produkcijsko konfiguracijo z velikostjo ELF-a v povzetku; zagon pod QEMU z vsemi scenariji dimnega testa in shranjevanjem serijskih zapisov; cargo-deny za svetovalna obvestila, licence in vire; minuto fuzzinga na vsako od enajstih tarč, ponoči pa deset minut na tarčo s predpomnjenim korpusom. Poleg tega CodeQL, OpenSSF Scorecard, Dependabot in objava s poreklom SLSA. Vsaka tuja akcija je pripeta na polni commit SHA.</p>
<p>Ponovljivost razumemo dobesedno: <code>make repro</code> zgradi HEAD dvakrat v svežih delovnih drevesih in primerja ELF, ISO in surovo sliko bajt za bajtom. Objave so podpisane z minisign, artefakte pa je mogoče preveriti z <code>gh attestation verify</code>. Produkcijska slika vsebuje zasebni ključ strežnika, zato je sam ISO skrivnost; slike ob objavah na GitHubu so razvojne, z začasnimi ključi, in služijo samo za preizkušanje.</p>
<h2 id="kaj-smo-se-naucili">Kaj smo se naučili</h2>
<p>Nekaj lekcij, ki so spremenile načrt:</p>
<ul>
<li><strong>Lasten TCP se je vendarle izplačal.</strong> Prototip je tekel na smoltcp in nam je dobro služil do M13. A strežnik, ki drži stotisoče povezav, ne potrebuje odjemalskega TCP, potrebuje piškotke SYN, kolo časovnikov in stanje s 96 bajti. V M14 smo smoltcp zamenjali z lastnim skladom samo za strežniško stran, s testi simulirane izgube in fuzzingom segmentov.</li>
<li><strong>Pomnilnik merite od znotraj.</strong> Ocene na povezavo so bile neuporabne, dokler jedro ni samo začelo šteti kopice v blokih alokatorja in ne v zahtevanih bajtih. Šele takrat smo videli, kam dejansko odhaja 512 bajtov, in se spustili na 494.</li>
<li><strong>Orodje za testiranje je del izdelka.</strong> Največja omejitev ni bilo jedro, temveč uporabniško omrežje QEMU in število efemernih vrat na razvojnem računalniku. Za sto tisoč in več povezav potrebujete <code>vmnet-shared</code> na macOS ali Linux s KVM, vmesnikom tap in več odjemalskimi naslovi.</li>
<li><strong>SQLite brez operacijskega sistema je mogoč</strong>, a le ob strogih pogojih: fiksna kopica, izključno zaklepanje brez deljenega pomnilnika, VFS, ki ne ve za datoteke, in sprejem, ki bazi ne pusti zaostati.</li>
<li><strong>Nevihte ponovnih povezav so običajno stanje</strong>, ne izjema. Ko mobilno omrežje pade in se vrne, tisoči naprav trkajo hkrati. Zato sprejem povezavo zavrne pred kakršno koli kriptografijo, vsaka zavrnjena povezava pa stane skoraj nič.</li>
</ul>
<h2 id="kaj-sledi">Kaj sledi</h2>
<p>Delo po M17 je načrtovano v treh smereh, vrstni red med njimi pa je zapisan v <code>docs/PLAN.md</code>. Nič od tega še ni zgrajeno.</p>
<ul>
<li><strong>En ISO za več platform.</strong> Ista datoteka <code>urmqtt-&lt;env&gt;-&lt;različica&gt;.iso</code> in ena surova slika GPT, ki se zaganjata na aarch64 UEFI, x86_64 UEFI in x86_64 BIOS, v virtualnih strojih in na pravi strojni opremi, začenši z enim računalnikom Intel. Še vedno naprava z vsem znotraj, ponovljiva in podpisana.</li>
<li><strong>Surovi disk, brez datotečnega sistema.</strong> Specifikacija formata na ravni bajta, gostiteljsko orodje <code>urmqtt-disk</code>, ki si s jedrom deli razčlenjevalnike, poravnava regij na 1 MiB, rast diska s podatki, discard za tanko dodeljene diske, posnetki baze A/B, preverjanje bitov in, po izbiri, šifriranje AES-256-XTS s strojnim AES.</li>
<li><strong>Dokazljivo varno.</strong> Preklic in seznam dovoljenih naprav med delovanjem prek podpisanega kontrolnega kanala, brez nove slike; diferencialno in skladnostno testiranje lastnega TLS in TCP; KASLR, zaščita sklada, kCFI, MTE in utrjen alokator; SQLite, izoliran na ravni EL0; Secure Boot; SBOM in cargo-vet; in na koncu paket za zunanjo revizijo.</li>
</ul>
<p>Platforme, ki ponujajo samo naprave, ki niso virtio, kot sta AWS ENA ali Azure MANA, zaenkrat niso podprte. Raspberry Pi 4 in 5 kot gostitelj KVM, Hetzner CAX in Oracle Ampere A1 so dokumentirani in podprti s kodo, vendar še niso preizkušeni na pravi storitvi.</p>
<h2 id="preizkusite">Preizkusite</h2>
<p>Repozitorij je na <a href="https://github.com/zeljan-alduk/urMQTT">github.com/zeljan-alduk/urMQTT</a>. Z Rustom s pripeto nightly verigo orodij in QEMU so <code>make certs</code>, <code>make run</code> in en odjemalec mosquitto dovolj za prvi PUBLISH prek mTLS na vašem računalniku. Opozorilo z začetka velja tudi na koncu: to je predalfa in vse, kar deluje, piše v <code>docs/PROGRESS.md</code>. Prijave napak, vprašanja o zasnovi in varnostne prijave po navodilih v <code>SECURITY.md</code> so dobrodošle. O vsakem naslednjem mejniku se bomo oglasili tukaj.</p>]]></content:encoded>
      <category>rust</category>
      <category>mqtt</category>
      <category>iot</category>
      <category>unikernel</category>
      <category>odprta koda</category>
    </item>
    <item>
      <title>Zaganjamo blog: zapiski s terena</title>
      <link>https://aldo.tech/sl/blog/zaganjamo-blog/</link>
      <guid isPermaLink="true">https://aldo.tech/sl/blog/zaganjamo-blog/</guid>
      <pubDate>Sat, 17 Oct 2026 08:00:00 +0000</pubDate>
      <description>Zakaj podjetje, ki izdeluje terminale, aplikacije in elektroniko za javni prevoz, začenja pisati, in o čem boste tukaj brali.</description>
      <content:encoded><![CDATA[<p>Večine tega, kar delamo, nihče ne vidi. Programska oprema v terminalu pri vratih avtobusa, vdelana programska oprema v napravi, ki nikoli ne dobi zaslona, strežnik, ki ponoči dnevne transakcije spremeni v poročila. Ko vse deluje, deluje tiho. Ko ne deluje, opazijo vsi.</p>
<p>Na tem blogu zapisujemo, kar smo se na tej poti naučili. Ne marketinških besedil, temveč konkretne zapiske: kaj je šlo narobe, kako smo to rešili in kaj bi danes naredili drugače.</p>
<h2 id="o-cem-bomo-pisali">O čem bomo pisali</h2>
<ul>
<li><strong>Plačevanje vozovnic v javnem prevozu.</strong> Brezstične kartice, moduli SAM, delovanje brez omrežja in vse, kar mora terminal prenesti v vozilu, ki se trese dvanajst ur na dan.</li>
<li><strong>Mobilne aplikacije na terenu.</strong> Android in Flutter na napravah, ki niso navadni telefoni: s čitalniki kartic, tiskalniki in baterijami, ki morajo zdržati celo izmeno.</li>
<li><strong>Backend in nadzor voznega parka.</strong> Kako vedeti, da ima naprava v vozilu težavo, preden jo prijavi voznik.</li>
<li><strong>Strojna oprema.</strong> Od prototipa na mizi do tiskanega vezja, ki gre v serijsko proizvodnjo, in vse napake vmes.</li>
<li><strong>Lokalni modeli umetne inteligence.</strong> Kje imajo modeli na lastni strojni opremi smisel in kje sta oblak in API preprosto boljša izbira.</li>
</ul>
<h2 id="kako-bomo-pisali">Kako bomo pisali</h2>
<p>Kratko in konkretno, s kodo in številkami, kjer jih imamo. Objavljali bomo, ko bomo imeli kaj povedati, ne po urniku. Objave pišemo v hrvaščini in jih prevajamo v vse jezike te strani; spremljate nas lahko tudi prek <a href="https://aldo.tech/sl/blog/feed.xml">RSS</a>.</p>
<p>Če vas zanima kakšna tema ali delate na podobnem problemu, nam pišite na <a href="mailto:info@aldo.tech">info@aldo.tech</a>.</p>]]></content:encoded>
      <category>uvod</category>
    </item>
  </channel>
</rss>
