<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>unfoxo</title><link href="https://unfoxo.it/it/" rel="alternate"/><link href="https://unfoxo.it/atom.xml" rel="self"/><id>https://unfoxo.it/it/</id><updated>2026-04-24T15:05:32.753803+02:00</updated><entry><title>3 milioni di richieste al giorno, zero CDN: l'ottimizzazione di CoMaps</title><link href="https://unfoxo.it/it/blog/3-milioni-di-richieste-al-giorno-zero-cdn-lottimizzazione-di-comaps.html" rel="alternate"/><published>2026-04-19T00:00:00+02:00</published><updated>2026-04-23T13:54:13.767187+02:00</updated><author><name>unfoxo</name></author><id>tag:unfoxo.it,2026-04-19:/it/blog/3-milioni-di-richieste-al-giorno-zero-cdn-lottimizzazione-di-comaps.html</id><summary type="html">&lt;p&gt;CoMaps ha deciso di non affidarsi, per questioni di privacy, a CDN commerciali. Il traffico viene gestito da volontari direttamente coinvolti nel progetto, ma per rendere il tutto fluido è necessaria qualche ottimizzazione.&lt;/p&gt;</summary><content type="html">&lt;p&gt;CoMaps è un&amp;rsquo;applicazione di navigazione Open Source che utilizza i dati di OpenStreetMap come base cartografica. Uno dei suoi punti di forza è la capacità di funzionare completamente offline, scaricando le mappe in anticipo o a mano a mano che si esplora.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Tre screenshot mostrano i diversi modi in cui l'utente può scegliere che mappe scaricare." src="/images/comaps-screenshot.png"&gt;&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;app fornisce pacchetti compressi e distillati dai dati di OpenStreetMap, che includono solo le informazioni utili a una navigazione di tutti i giorni. In questo modo con pochi GB è possibile ottenere mappe sempre disponibili e una ricerca completamente offline.&lt;/p&gt;
&lt;p&gt;Non è dato sapere quanti siano gli utenti effettivi&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; (in quanto non ci sono nè tracking nè statistiche), tuttavia fornire mappe di alta qualità su una scala così grossa e soprattutto mantenendo la privacy degli utenti è una sfida infrastrutturale interessante.&lt;/p&gt;
&lt;p&gt;Lo scopo di CoMaps è quello di abbandonare CDN commerciali e utilizzare per una serie di nodi indipendenti gestiti direttamente dai volontari del progetto. In questo modo, il traffico passerebbe esclusivamente attraverso soggetti realmente coinvolti e allineati con gli obbiettivi del progetto, senza intermediari.&lt;/p&gt;
&lt;p&gt;Da Aprile 2026 unfoxo offre a CoMaps il nodo &amp;ldquo;comaps-it1&amp;rdquo;.&lt;/p&gt;
&lt;h2 id="e-realmente-una-cdn"&gt;È realmente una CDN?&lt;/h2&gt;
&lt;p&gt;L&amp;rsquo;obbiettivo di una CDN è quello di portare i dati il più vicino possibile all&amp;rsquo;utente: i dati vengono copiati e distribuiti su diversi nodi che coprono maggior numero possibile di aree geografiche differenti. In questo modo, in qualsiasi luogo si trovi l&amp;rsquo;utente, statisticamente avrà almeno un nodo abbastanza vicino da cui scaricare i dati nel modo più veloce possibile. Nodi più vicini significano anche costi del traffico inferiore e maggiore resilienza.&lt;/p&gt;
&lt;p&gt;Lo scopo della &amp;ldquo;CDN&amp;rdquo; di CoMaps è diverso: ogni client si connette a nodi casuali e scarica solo le regioni della mappa in formato compresso. In questo modo diventa quasi impossibile capire, correlando le richieste di un utente in particolare, la sua posizione esatta.&lt;/p&gt;
&lt;p&gt;In più, essendo gli stati divisi in sezioni molto grossolane e non in &amp;ldquo;tiles&amp;rdquo; (i &amp;ldquo;quadretti&amp;rdquo; classici delle altre app di navigazione), nel caso peggiore è possibile conoscere solo una posizione molto approssimata dell&amp;rsquo;utente.&lt;/p&gt;
&lt;h2 id="larchitettura-del-nodo-comaps-it1"&gt;L&amp;rsquo;architettura del nodo comaps-it1&lt;/h2&gt;
&lt;p&gt;In una CDN il server http è ciò che si contrappone tra utente e dati: è quindi necessario che sia efficiente e ottimizzato.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Filesystem: Bcachefs su SSD SATA, con caching nvme&lt;/li&gt;
&lt;li&gt;Sistema operativo: Alpine Linux&lt;/li&gt;
&lt;li&gt;Web Server: Nginx&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Per la sincronizzazione il team di CoMaps utilizza sftp, che è stato installato su un container separato con permessi di scrittura solo nella cartella che verrà poi servita dal nodo.&lt;/p&gt;
&lt;p&gt;Bcachefs è stato scelto per la possibilità di poter creare un archivio a due &amp;ldquo;strati&amp;rdquo;: davanti, i dischi nvme eccellono in velocità e ricevono tutte le scritture e le letture dei dati più &amp;ldquo;caldi&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;I dischi SATA, al contrario, vengono utilizzati come archivio per i dati utilizzati meno, fornendo lo spazio necessario ma a un costo molto inferiore e con una longevità maggiore.&lt;/p&gt;
&lt;p&gt;Alpine Linux è stato scelto per la leggerezza e la semplicità: si tratta del sistema operativo su cui si basano moltissime immagini di Docker, ma eccelle anche utilizzato come sistema operativo base e si avvicina molto bene al metodo &amp;ldquo;old school&amp;rdquo; di gestione di server.&lt;/p&gt;
&lt;p&gt;Il server web non è un elemento critico: siccome la banda è il limite per questo progetto, qualsiasi server web probabilmente l&amp;rsquo;avrebbe saturata. In questo caso ho deciso di usare nginx per la familiarità e la possibilità di agire sia come server statico che come proxy.&lt;/p&gt;
&lt;h2 id="ottimizzazione-delle-performance"&gt;Ottimizzazione delle performance&lt;/h2&gt;
&lt;p&gt;Il server è stato avviato con le impostazioni di default per un periodo iniziale e il logging attivo, in modo da poter stabilire una baseline. Il server, sostenuto dalla pool SSD, era in ogni caso in grado di saturare la WAN attraverso il quale era collegato.&lt;/p&gt;
&lt;p&gt;Sono sorte però opportunità per ottimizzare ancora di più il sistema: un carico minore significa meno consumo di energia, una durata più lunga dei componenti ma anche la possibilità di dirigere quel carico verso altri compiti più importanti.&lt;/p&gt;
&lt;h2 id="la-legge-di-pareto"&gt;La legge di pareto&lt;/h2&gt;
&lt;p&gt;Chiunque abbia avuto a che fare con grandi numeri conoscerà sicuramente la legge di Pareto. Statisticamente, il 20% delle cause genera l&amp;lsquo;80% degli effetti.&lt;/p&gt;
&lt;p&gt;Ciò significa che, se riusciamo a individuare e ottimizzare &amp;ldquo;il 20%&amp;rdquo; responsabile del lavoro, potremo teoricamente ottenere l&amp;lsquo;80% di performance in più.&lt;/p&gt;
&lt;p&gt;Ho quindi raccolto un giorno di log. In totale sul server sono passate all&amp;rsquo;incirca 3 milioni di richieste.&lt;/p&gt;
&lt;p&gt;Utilizzando uno script bash, possiamo estrarre facilmente solo le richieste relativamente alle mappe dividerle per stato:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## sed -n 's/.*\/260405\/\([^_.]*\).*/\1/p' access.log | sort | uniq -c | sort -nr | awk '{cnt[NR]=$1; name[NR]=$2; total+=$1; lines=NR} END {for(i=1;i&amp;lt;=lines;i++) 
printf &amp;quot;%8d %6.2f%% %s\n&amp;quot;, cnt[i], (cnt[i]/total)*100, name[i]}'
  934949  32.25% France
  532499  18.37% Germany
  252621   8.71% US
  148178   5.11% Italy
   98293   3.39% Netherlands
   86948   3.00% Spain
   61763   2.13% Belgium
   57862   2.00% Austria
   53080   1.83% Switzerland
   51552   1.78% World
   50985   1.76% Canada
   49935   1.72% UK
   [... altri stati qui ...]

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dai log emerge chiaramente: l&amp;lsquo;80% del traffico è generato da soli 12 stati su 220. Nel nostro caso &lt;strong&gt;il 5% causa l&amp;lsquo;80% del traffico&lt;/strong&gt;. Possiamo quindi concentrarci nell&amp;rsquo;ottimizzare questa piccola parte per ottenere un risultato nettamente migliore.&lt;/p&gt;
&lt;h2 id="la-ram-non-usata-e-ram-sprecata"&gt;La ram non usata è ram sprecata&lt;/h2&gt;
&lt;p&gt;Sin da subito è emersa un&amp;rsquo;inefficienza del sistema: la maggior parte delle richieste leggeva direttamente dal disco. Un operazione comunque efficiente grazie all&amp;rsquo;utilizzo degli SSD ma che, in caso di altri carichi concorrenti, avrebbe sicuramente causato rallentamenti.&lt;/p&gt;
&lt;p&gt;La causa era semplice: Linux utilizza pesantemente la ram libera come cache del disco, e il container dedicato al server web aveva solo 512MB allocati. Poichè buona parte della memoria era occupata dal sistema stesso, il server era costretto a &amp;ldquo;buttare via&amp;rdquo; i dati appena letti senza poterli tenere in memoria.&lt;/p&gt;
&lt;p&gt;Dando invece a Linux la giusta quantità di RAM, possiamo fare in modo che la sfrutti al meglio riempiendola con i dati più &amp;ldquo;caldi&amp;rdquo; provenienti dal disco.&lt;/p&gt;
&lt;p&gt;Questa &amp;ldquo;pienezza&amp;rdquo; è in realtà solo apparente, ed infatti la memoria usata per il caching può essere liberata immediatamente nel caso un altro software ne abbia bisogno. Non tutti ne sono a conoscenza e addirittura &lt;a href="https://www.linuxatemyram.com/"&gt;esiste un sito web dedicato&lt;/a&gt; che spiega bene il concetto.&lt;/p&gt;
&lt;p&gt;A quanto corrisponde il 5% individuato prima? Possiamo scoprirlo sommando la dimensione di tutti gli stati che lo compongono:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## du -ch France_* Germany_* US_* Italy_* Netherlands_* Spain_* Belgium_* Austria_* Switzerland_* World* Canada_* UK_*
[...]
38.7G   total
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ho quindi allocato al server web circa 40GB di RAM, in modo da dare l&amp;rsquo;opportunità al sistema di tenere in memoria buona parte di quei file. Nel giro di qualche ora il sistema riportava di averne usati 39.3G (colonna buff/cache).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## free -h
              total        used        free      shared  buff/cache   available
Mem:         125.7G       39.4G        3.0G        1.9G       39.3G       40.3G
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ciò ha causato un immediata discesa dell&amp;rsquo;utilizzo del disco, che da un utilizzo medio di 15MB/s con picchi di 60MB/s si è stabilizzato a circa 5MB/s con picchi di 10MB/s (praticamente un terzo), con una curva sempre più bassa a mano a mano che l&amp;rsquo;algoritmo ottimizzava i file più letti.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Il grafico dell'IO mostra una netta riduzione del traffico dopo aver dato più RAM al container." src="/images/io-graph.png"&gt;&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;effetto benefico della RAM aggiuntiva appare ancora più evidente dal grafico della pressione IO, che indica la percentuale di tempo &amp;ldquo;persa&amp;rdquo; ad aspettare che i dischi siano disponibili.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Lo stall è diventato praticamente zero!" src="/images/stall-graph.png"&gt;&lt;/p&gt;
&lt;h2 id="chi-ha-bisogno-di-atime"&gt;Chi ha bisogno di atime?&lt;/h2&gt;
&lt;p&gt;Risolto il nodo delle letture frequenti, rimane un altro fatto di cui tener conto: le scritture frequenti.&lt;/p&gt;
&lt;p&gt;Anche se leggendo un file questo non viene effettivamente &amp;ldquo;modificato&amp;rdquo;, tra i propri metadati contiene l&amp;rsquo;ultima volta che ha subito un accesso.&lt;/p&gt;
&lt;p&gt;Questo dato viene aggiornato dal sistema operativo ed è visualizzabile con il comando stat:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## stat World.mwm 
  File: World.mwm
  Size: 53104836        Blocks: 103728     IO Block: 4096   regular file
Device: 800h/2048d      Inode: 5518608     Links: 1
Access: (0644/-rw-r--r--)  Uid: ( 1000/ UNKNOWN)   Gid: ( 1000/ UNKNOWN)
Access: 2026-04-18 10:28:45.836942564 +0000
Modify: 2026-04-06 07:03:48.000000000 +0000
Change: 2026-04-07 07:01:31.892520553 +0000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ciò significa che ad ogni file scaricato, anche nel caso il file sia presente in RAM, corrisponde una piccola scrittura che aggiorna quel valore. È utile in caso di audit o per tenere un elenco di file recenti, ma nel nostro caso siccome ogni file ha accessi continui, perde completamente di significato.&lt;/p&gt;
&lt;p&gt;Non si tratta di molto traffico, ma è comunque possibile disattivarlo aggiungendo le opzioni di mount &lt;code&gt;noatime&lt;/code&gt; o &lt;code&gt;lazyatime&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Le due opzioni devono essere messe nel file &lt;code&gt;/etc/fstab&lt;/code&gt; e causano i seguenti effetti:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;noatime&lt;/code&gt;: la data di accesso non viene mai aggiornata&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lazyatime&lt;/code&gt;: la data di accesso viene aggiornata solo in concomitanza con una modifica (che avrebbe comunque causato una scrittura) o passate 24 ore dall&amp;rsquo;ultimo aggiornamento&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nel mio caso ho abilitato globalmente &lt;code&gt;lazyatime&lt;/code&gt; sull&amp;rsquo;intero filesystem: controllare il tempo di accesso può essere comodo per capire se alcuni file sono utilizzati o no, e i log di accesso permettono comunque di tirare fuori i timestamp esatti nel caso sia necessario.&lt;/p&gt;
&lt;h2 id="quelle-continue-scritture"&gt;Quelle continue scritture&lt;/h2&gt;
&lt;p&gt;Non ci avevo mai fatto caso, ma la totalità di traffico in scrittura del server web era causata dal file &lt;code&gt;access.log&lt;/code&gt;, aggiornato ad ogni visita. Ogni richiesta generava una riga, e ogni riga generava una scrittura sul file.&lt;/p&gt;
&lt;p&gt;Nginx fornisce tre opzioni aggiuntive da aggiungere alle impostazioni di logging:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;access_log /var/log/nginx/access.log privacyfmt gzip buffer=32k flush=10m;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ecco l&amp;rsquo;effetto che ha ognuno di queste:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;gzip&lt;/code&gt;: L&amp;rsquo;intero log viene compresso al volo con gzip (livello 1). Nel mio caso, &lt;code&gt;758MB&lt;/code&gt; di log diventano &lt;code&gt;34.4MB&lt;/code&gt; (22 volte meno)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;buffer=32k&lt;/code&gt;: Verrà scritto su disco solo al raggiungimento di 32kb di log in sospeso&amp;hellip;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flush=10m&lt;/code&gt;: &amp;hellip; o finchè non sono passati 10 minuti dall&amp;rsquo;ultima scrittura&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;L&amp;rsquo;unico svantaggio di questo approccio è la possibilità di perdere fino a 10 minuti di log in caso di crash del server. Non ho interesse nel tenere log così granulari, di conseguenza ho applicato queste direttive globalmente.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nota&lt;/strong&gt;: al termine ho comunque disattivato i log per policy CoMaps. Il logging iniziale è stato effettuato con un formato che non includeva indirizzi IP e User-Agent (&lt;code&gt;privacyfmt&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id="conclusioni"&gt;Conclusioni&lt;/h2&gt;
&lt;p&gt;Rendere disponibile il nodo per CoMaps non è solo un modo per contribuire a un progetto Open Source, ma anche l&amp;rsquo;opportunità di imparare a conoscere le sfide causate da un traffico particolare come quello causato da questa app.&lt;/p&gt;
&lt;p&gt;Nonostante il traffico significativo, la presenza di altri nodi contribuisce a distribuire efficacemente il traffico ben sotto la soglia di saturazione. I client sono progettati per scaricare da quanti più nodi possibile in contemporanea e ritentare in caso di errore.&lt;/p&gt;
&lt;p&gt;Anche se ci fosse, in futuro, la necessità di limitare il traffico, i client si adatterebbero di conseguenza scaricando da altri nodi più veloci.&lt;/p&gt;
&lt;p&gt;Sono felice di fornire questo nodo e vi invito a contribuire anche solo scaricando l&amp;rsquo;app per provarla e lasciare un&amp;rsquo;opinione. CoMaps è un progetto relativamente nuovo e serve molto aiuto in tutti i fronti!&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Google dice &amp;ldquo;100K+&amp;rdquo; download, ma probabilmente ci sono molti più download da F-Droid e altri store alternativi.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="articles"/><category term="Hosting"/><category term="Open Source"/><category term="Maps"/></entry><entry><title>Resuscitare un macchinario industriale afflitto da bit rot</title><link href="https://unfoxo.it/it/blog/resuscitare-un-macchinario-industriale-afflitto-da-bit-rot.html" rel="alternate"/><published>2026-04-10T00:00:00+02:00</published><updated>2026-04-23T12:35:43.303011+02:00</updated><author><name>unfoxo</name></author><id>tag:unfoxo.it,2026-04-10:/it/blog/resuscitare-un-macchinario-industriale-afflitto-da-bit-rot.html</id><summary type="html">&lt;p&gt;A causa di una SD degradata, un macchinario industriale da decine di migliaia di euro smette di funzionare. Servirà un mix di pentesting, recupero dati e reverse engineering per riportarlo in vita.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Il bit rot è un fenomeno che affligge, prima o poi, qualsiasi supporto di memorizzazione digitale. È la tendenza che hanno i supporti a degradarsi nel tempo, causando il capovolgimento di alcuni bit, che da zero diventano uno e viceversa.&lt;/p&gt;
&lt;p&gt;In base alla posizione e al significato dei bit che degradano possono accadere varie conseguenze: nella migliore delle ipotesi, il difetto riguarda una parte di memoria non utilizzata o che contiene dati non critici. Nella peggiore delle ipotesi, il bitrot colpisce una parte essenziale del sistema che lo manda completamente in tilt.&lt;/p&gt;
&lt;h2 id="laddon-di-rete"&gt;L&amp;rsquo;addon di rete&lt;/h2&gt;
&lt;p&gt;Questo progetto è nato quando l&amp;rsquo;interfaccia web di un macchinario industriale ha smesso di funzionare, impedendo al proprietario di raccogliere dati e di monitorarne lo stato. Sebbene il display integrato (collegato a un PLC separato) funzionasse ancora, con la parte web ko diventava sensibilmente più difficile da usare.&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;interfaccia web è gestita da un modulo esterno che si collega alla porta seriale del macchinario e agisce da &amp;ldquo;tramite&amp;rdquo;. Al suo interno è presente un piccolo computer con Linux che legge e scrive i dati su una scheda SD installata al suo interno.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Una piccola scatola metallica, dotata di un'interfaccia RS485 sul lato anteriore" src="/images/chamber_box.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Prima di mettere le mani sul dispositivo, è importante capire le condizioni correnti. Si è avviato? È bloccato? Cosa sta facendo?&lt;/p&gt;
&lt;p&gt;Per capire cosa aspettarci da un modulo funzionante al 100%, possiamo leggere il manuale. Ecco alcune informazioni utili:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;il DHCP non è supportato: l&amp;rsquo;unico modo per utilizzare il dispositivo è tramite un IP statico&lt;/li&gt;
&lt;li&gt;le credenziali di default sono &amp;ldquo;guest / guest&amp;rdquo;&lt;/li&gt;
&lt;li&gt;è presente un&amp;rsquo;interfaccia web in http sulla porta 80&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="ottenere-laccesso"&gt;Ottenere l&amp;rsquo;accesso&lt;/h2&gt;
&lt;p&gt;Il dispositivo è stato collegato a un router ed è stato analizzato il traffico di rete. Il mio metodo preferito per ottenere l&amp;rsquo;accesso a un dispositivo è tramite l&amp;rsquo;utilizzo dell&amp;rsquo;indirizzo IPv6 link-local, ma sembra che IPv6 sia stato disabilitato, probabilmente a causa dell&amp;rsquo;età avanzata del dispositivo o di una configurazione intenzionale.&lt;/p&gt;
&lt;p&gt;Non conoscendo l&amp;rsquo;indirizzo IP statico configurato, dobbiamo connetterci direttamente al dispositivo e tentare ogni singolo IP finchè non riceviamo una risposta, con il programma &lt;code&gt;arp-scan&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ arp-scan -I enp4s0f4u2 192.168.0.0/24
Interface: enp4s0f4u2, type: EN10MB, MAC: 1c:bf:ce:fb:e3:63, IPv4: 192.168.88.52
Starting arp-scan 1.10.0 with 256 hosts (https://github.com/royhills/arp-scan)
192.168.0.102   00:11:0c:0f:7f:bb       Atmark Techno, Inc.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ora che abbiamo scoperto l&amp;rsquo;indirizzo IP possiamo riconfigurare la scheda di rete e procedere a una scansione per verificare quali servizi sono ancora attivi.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ nmap 192.168.0.102
[...]
Host is up (0.00088s latency).
PORT   STATE SERVICE
21/tcp open  ftp
23/tcp open  telnet
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Il fatto che FTP e Telnet siano presenti indica che il sistema operativo si è avviato, ma la mancanza di http fa presupporre un problema sul server web. Il server telnet, probabilmente lasciato dagli sviluppatori per comodità, è utile in quanto ci permette di ottenere un accesso diretto alla console del dispositivo.&lt;/p&gt;
&lt;p&gt;Prima di tentare un login possiamo connetterci senza credenziali per ottenere ulteriori informazioni sulla dispositivo, leggendo il banner di sistema, che solitamente contiene il nome host e altre informazioni.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ telnet 192.168.0.102
Trying 192.168.0.102...
Connected to 192.168.0.102.
Escape character is '^]'.

atmark-dist v1.26.1 (AtmarkTechno/Armadillo-440)
Linux 2.6.26-at15 [armv5tejl arch]

WEB-MGR login:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href="https://www.atmark-techno.com/"&gt;AtmarkTechno&lt;/a&gt; è un produttore giapponese di schede embedded, e Armadillo-440 è il nome in codice di una scheda i.MX257 ormai fuori produzione. Fortunatamente, sul loro sito web sono ancora disponibili documentazione e tutto ciò che serve per ripristinare il sistema da zero nel caso fosse necessario.&lt;/p&gt;
&lt;p&gt;Il kernel 2.6.26-at15 è stato rilasciato nel 2008, ma consultando l&amp;rsquo;archivio di Atmark sembra questa versione sia stata utilizzata fino al 2013.&lt;/p&gt;
&lt;p&gt;Proviamo ad entrare con le credenziali &amp;ldquo;guest&amp;rdquo; presenti sul manuale: nonostante siano relative all&amp;rsquo;interfaccia web spesso gli sviluppatori, per semplicità, riciclano le password.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;WEB-MGR login: guest
Password: *****
[guest@WEB-MGR (ttyp0) ~]$
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Successo! Sfortunatamente l&amp;rsquo;utente guest ha accesso molto limitato alla macchina. Per ottenere un accesso totale è necessario passare all&amp;rsquo;utente &lt;code&gt;root&lt;/code&gt;. Se guest si trova nell&amp;rsquo;elenco &amp;ldquo;sudoers&amp;rdquo;, è possibile farlo con un semplice comando:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[guest@WEB-MGR (ttyp0) ~]$ sudo su -
[guest@WEB-MGR (ttyp0) ~]$
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Il comando &lt;code&gt;sudo su -&lt;/code&gt; ci riporta immediatamente alla console originale. Accesso negato.&lt;/p&gt;
&lt;p&gt;Esistono altri utenti da tentare? Elencando le cartelle presenti all&amp;rsquo;interno di &lt;code&gt;/home/&lt;/code&gt; possiamo intuire chi ha accesso al sistema.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[guest@WEB-MGR (ttyp0) ~]$ ls /home/
ftp/      guest/    hide/     ho/       mw/       wm/       www-data/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tra le varie cartelle, &lt;code&gt;hide&lt;/code&gt; attira subito l&amp;rsquo;attenzione: il nome fa pensare a un&amp;rsquo;utenza di servizio &amp;ldquo;nascosta&amp;rdquo; creata dagli sviluppatori. Proviamo ad autenticarci riutilizzando la stessa password di guest.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;WEB-MGR login: hide
Password: *****
[hide@WEB-MGR (ttyp0) ~]$ sudo su -
[root@WEB-MGR (ttyp0) ~]#
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Root ottenuto!&lt;/strong&gt; Come in ogni film di hacking che si rispetti, siamo entrati &lt;em&gt;dalla backdoor&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="dove-il-server-web"&gt;Dov&amp;rsquo;è il server web?&lt;/h2&gt;
&lt;p&gt;Ora che possediamo accesso al sistema con &lt;code&gt;root&lt;/code&gt;, possiamo leggere e modificare qualsiasi cosa. Per prima cosa, controlliamo se il server è in esecuzione. Usando il comando &lt;code&gt;ps aux&lt;/code&gt; otteniamo un elenco dei processi, tra cui però non appare alcun server http.&lt;/p&gt;
&lt;p&gt;Prima di continuare dobbiamo capire &lt;em&gt;quale&lt;/em&gt; server stiamo cercando. Potrebbe essere &lt;code&gt;nginx&lt;/code&gt;, &lt;code&gt;apache2&lt;/code&gt;, &lt;code&gt;lighttpd&lt;/code&gt;, o magari qualche server embedded base come &lt;code&gt;uhttpd&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Fortunatamente nella cartella &lt;code&gt;/etc/&lt;/code&gt; è presente un file &lt;code&gt;lighttpd.conf&lt;/code&gt;, che ci guida direttamente verso il server scelto dagli sviluppatori.&lt;/p&gt;
&lt;p&gt;Provando a eseguire lighttpd, veniamo immediatamente fermati da un errore:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[hide@WEB-MGR (ttyp0) ~]$ lighttpd
lighttpd: error while loading shared libraries: libz.so.1: cannot open shared object file: No such file or directory
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;libz&lt;/code&gt; è una libreria esterna, solitamente inclusa nel sistema operativo base, che si occupa di comprimere e decomprimere file. Buona parte dei server web moderni ne ha bisogno per poter implementare la &lt;a href="https://en.wikipedia.org/wiki/HTTP_compression"&gt;compressione HTTP&lt;/a&gt;, essendo le pagine HTML molto propense alla compressione.&lt;/p&gt;
&lt;p&gt;All&amp;rsquo;avvio di &lt;code&gt;lighttpd&lt;/code&gt;, uno speciale programma chiamato &amp;ldquo;linker dinamico&amp;rdquo; cerca e carica in memoria tutte le librerie di cui il software avrà bisogno. Le librerie di sistema, tra cui &lt;code&gt;libz.so.1&lt;/code&gt;, si trovano solitamente all&amp;rsquo;interno della cartella &lt;code&gt;/lib/&lt;/code&gt;, ma il linker effettua una ricerca anche in altre cartelle nel caso sia necessario.&lt;/p&gt;
&lt;p&gt;Il fatto che &lt;code&gt;lighttpd&lt;/code&gt; ci dica &amp;ldquo;No such file or directory&amp;rdquo; indica che la libreria potrebbe essere stata cancellata o corrotta a tal punto da diventare illeggibile.&lt;/p&gt;
&lt;h2 id="save-slot-1"&gt;Save slot 1&lt;/h2&gt;
&lt;p&gt;Fino ad ora ci siamo limitati a &lt;em&gt;guardare ma non toccare&lt;/em&gt;, evitando di causare errori aggiuntivi su una memoria che sappiamo già essere danneggiata.&lt;/p&gt;
&lt;p&gt;Per poter lavorare liberamente senza il rischio di distruggere l&amp;rsquo;unica copia dei dati rimasta, è utile creare una copia di backup del sistema operativo d&amp;rsquo;origine.&lt;/p&gt;
&lt;p&gt;Fortunatamente, avendo già l&amp;rsquo;accesso root possiamo usare una combinazione di &lt;code&gt;dd&lt;/code&gt; e &lt;code&gt;netcat&lt;/code&gt; per inviare l&amp;rsquo;intero contenuto della scheda SD ad un computer a nostra scelta&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;. Spostando l&amp;rsquo;immagine su un computer esterno otteniamo anche la possibilità di utilizzare software molto più moderni e potenti, velocizzando il lavoro.&lt;/p&gt;
&lt;h2 id="dove-finita-la-libreria"&gt;Dov&amp;rsquo;è finita la libreria?&lt;/h2&gt;
&lt;p&gt;Ora che siamo al sicuro, possiamo indagare su che fine abbia fatto libz.so.1.&lt;/p&gt;
&lt;p&gt;Un controllo veloce rivela che il file &amp;ldquo;libz.so.1&amp;rdquo; esiste, ed è un collegamento al file &amp;ldquo;libz.so.1.2.3.3&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Aggiungendo &lt;code&gt;LD_DEBUG=libs&lt;/code&gt; prima di un comando, possiamo chiedere al linker di descrivere ogni operazione che compie.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
[root@WEB-MGR (ttyp0) ~]## LD_DEBUG=libs lighttpd
   [...]
      2999:     find library=libz.so.1 [0]; searching
      2999:      search cache=/etc/ld.so.cache
      2999:      search path=/lib:/usr/lib/tls/v5l/fast-mult:/usr/lib/tls/v5l:/usr/lib/tls/fast-mult:/usr/lib/tls:/usr/lib/v5l/fast-mult:/usr/lib/v5l:/usr/lib/fast-mult:/usr/lib:/lib/arm-linux-gnueabi/tls/v5l/fast-mult:/lib/arm-linux-gnueabi/tls/v5l:/lib/arm-linux-gnueabi/tls/fast-mult:/lib/arm-linux-gnueabi/tls:/lib/arm-linux-gnueabi/v5l/fast-mult:/lib/arm-linux-gnueabi/v5l:/lib/arm-linux-gnueabi/fast-mult:/lib/arm-linux-gnueabi:/usr/lib/arm-linux-gnueabi/tls/v5l/fast-mult:/usr/lib/arm-linux-gnueabi/tls/v5l:/usr/lib/arm-linux-gnueabi/tls/fast-mult:/usr/lib/arm-linux-gnueabi/tls:/usr/lib/arm-linux-gnueabi/v5l/fast-mult:/usr/lib/arm-linux-gnueabi/v5l:/usr/lib/arm-linux-gnueabi/fast-mult:/usr/lib/arm-linux-gnueabi               (system search path)
      2999:       trying file=/lib/libz.so.1
      2999:       trying file=/usr/lib/tls/v5l/fast-mult/libz.so.1
      2999:       trying file=/usr/lib/tls/v5l/libz.so.1
      2999:       trying file=/usr/lib/tls/fast-mult/libz.so.1
      2999:       trying file=/usr/lib/tls/libz.so.1
      2999:       trying file=/usr/lib/v5l/fast-mult/libz.so.1
      2999:       trying file=/usr/lib/v5l/libz.so.1
      2999:       trying file=/usr/lib/fast-mult/libz.so.1
      [... a bunch of other tries here ...]

lighttpd: error while loading shared libraries: libz.so.1: cannot open shared object file: No such file or directory

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Il linker cerca in &lt;code&gt;/lib/&lt;/code&gt;, ma poi tenta tutte le altre cartelle che conosce. &lt;code&gt;libz.so.1&lt;/code&gt; esiste ed è in &lt;code&gt;/lib/&lt;/code&gt;. Come mai sta venendo ignorato?&lt;/p&gt;
&lt;p&gt;È possibile che qualcosa abbia sostituito &lt;code&gt;libz.so.1&lt;/code&gt; con un file completamente diverso? Per curiosità, ho provato a usare lo strumento &lt;code&gt;file&lt;/code&gt;, che analizza i file e ne descrive il contenuto:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/lib$ file libz.so.1.2.3.3
libz.so.1.2.3.3: ELF 32-bit LSB shared object, *unknown arch 0x20* version 1 (SYSV)
   can't read elf program headers at 1073741876, missing section headers at 1130896
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ignorando &amp;ldquo;unknown arch&amp;rdquo;, l&amp;rsquo;errore &amp;ldquo;can&amp;rsquo;t read elf program headers&amp;rdquo; rivela il mistero: il file è completamente corrotto.&lt;/p&gt;
&lt;h2 id="in-cerca-di-un-libzso1233"&gt;In cerca di un libz.so.1.2.3.3&lt;/h2&gt;
&lt;p&gt;Nonostante libz sia inclusa in qualsiasi computer con Linux (e probabilmente anche Windows), non possiamo prendere un file libz qualsiasi e sostituirlo nella cartella di sistema. Man mano che le librerie evolvono, infatti, cambiano il loro funzionamento e le versioni successive diventano incompatibili con quelle precedenti.&lt;/p&gt;
&lt;p&gt;In più, questo non è un computer normale, ma è basato su una CPU &amp;ldquo;ARM&amp;rdquo;: anche se la libreria fosse corretta, la CPU non saprebbe come interpretarla.&lt;/p&gt;
&lt;p&gt;Fortunatamente libz è così diffusa che possiamo dare per scontato che sia inclusa nell&amp;rsquo;immagine di sistema creata dal produttore. Per ottenere quell&amp;rsquo;esatta versione, dobbiamo capire quando questa libreria è stata creata e trovare un&amp;rsquo;immagine di quell&amp;rsquo;epoca.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## ls -lah libz.so.1*
-rw-r--r--  1 root root  81K 17 nov  2010 libz.so.1.2.3.3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;La libreria è stata creata il 17 novembre 2010, esattamente il giorno precedente al rilascio di questa immagine ISO:
&lt;a href="https://download.atmark-techno.com/armadillo-440/iso/a400_20101118_free.iso"&gt;https://download.atmark-techno.com/armadillo-440/iso/a400_20101118_free.iso&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Scaricando l&amp;rsquo;ISO ed estraendola, otteniamo l&amp;rsquo;intero contenuto originale della cartella &lt;code&gt;/lib/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Per precauzione ho confrontato tutte le librerie originali con quelle presenti nella scheda: corrispondevano tutte, tranne &lt;code&gt;libz&lt;/code&gt;, che ho sostituito immediatamente.&lt;/p&gt;
&lt;h2 id="secondo-tentativo"&gt;Secondo tentativo&lt;/h2&gt;
&lt;p&gt;Ora che la libreria è stata sostituita, possiamo riprovare ad eseguire &lt;code&gt;lighttpd&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[root@WEB-MGR (ttyp0) /bin]## lighttpd
2026-04-08 17:44:12: (../../src/server.c.521) No configuration available. Try using -f option.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;L&amp;rsquo;errore è cambiato: ciò significa che libz è stata finalmente caricata! &lt;code&gt;lighttpd&lt;/code&gt; ci sta semplicemente dicendo che non trova un file di configurazione. Proviamo ad fornirgli il file &lt;code&gt;lighttpd.conf&lt;/code&gt; trovato prima (e l&amp;rsquo;opzione &lt;code&gt;-D&lt;/code&gt;, in modo che lighttpd non vada in background):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[root@WEB-MGR (ttyp0) ~]## lighttpd -D -f /etc/lighttpd.conf
../../src/configfile.c.792: 0, (null)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Per quanto questo errore possa sembrare criptico, una veloce ricerca indica che si tratta di un problema di sintassi del file di configurazione. Vediamo cosa c&amp;rsquo;è all&amp;rsquo;interno:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## lighttpd configuration file
#
## use it as a base for lighttpd 1.0.0 and above
#
## $Id: lighttpd.conf,v 1.7 2004/11/03 22:26:05 weigon Exp $

############ Options you really have to take care of ####################

## [���Ѥ���module������] modules to load

[... normal config file ...]

                              fastcgi.server = (
                   0&amp;quot;.app&amp;quot; =&amp;gt; (
      !                 &amp;quot;localhost&amp;quot; =&amp;gt; (
                &amp;quot;      0    &amp;quot;socket&amp;quot; =&amp;gt;
                               &amp;quot;/tmp/app-Socket.socket&amp;quot;(
                                &amp;quot;max-procs&amp;quot; =&amp;gt; 1,
                                &amp;quot;bin-path&amp;quot; =&amp;gt; 2/mspec/srg/�pp.rb&amp;quot;,
##              !        (      0 &amp;quot;bi�/environment&amp;quot; 5&amp;gt; (&amp;quot;TZ* =. &amp;quot;JST-9&amp;quot;)
                       $)               `   �),
 (        � $       &amp;quot;.html&amp;quot; =&amp;gt; (
                        &amp;quot;localhost  =&amp;gt; (�             �  �           &amp;quot;socket&amp;quot; =&amp;gt;
 0 $                            &amp;quot;/tmp/app/socket2.socket&amp;quot;,
  `                             &amp;quot;max-procs&amp;quot; =&amp;gt; 1,
                               !&amp;quot;bin-path&amp;quot; =&amp;gt; &amp;quot;/chamb/src/route.rb&amp;quot;,
!`       &amp;quot;              )
         �          )
##               $    &amp;quot;.rb&amp;quot; =&amp;gt; (
##                       &amp;quot;localhost&amp;quot; =&amp;gt; (##       (    &amp;quot;                  &amp;quot;/tmp/ruby-socket.socket&amp;quot;,
##                               &amp;quot;max-procs&amp;quot; =&amp;gt;&amp;quot;1,
##  $                            &amp;quot;Bin-path&amp;quot; =&amp;gt; &amp;quot;/usr/bin/ruby&amp;quot;
##    &amp;quot;     (     (      )
##                    )
                )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ecco l&amp;rsquo;esempio perfetto di bitrot! All&amp;rsquo;interno del file di configurazione, alcuni bit hanno cambiato stato e in base alla loro posizione hanno causato:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;caratteri inesistenti o non validi, ora mostrati come &amp;ldquo;�&amp;rdquo;&lt;/li&gt;
&lt;li&gt;sostituzioni di caratteri con altri: il carattere &lt;code&gt;\n&lt;/code&gt; (a capo) prima di &lt;code&gt;"localhost"&lt;/code&gt; è diventato uno spazio, unendo due righe&lt;/li&gt;
&lt;li&gt;cambi tra maiuscole e minuscole: &lt;code&gt;bin-path&lt;/code&gt; è diventato &lt;code&gt;Bin-path&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ed è questo il motivo per cui il bit-rot è così pericoloso: mentre la perdita totale di un file rende il problema immediatamente evidente, il bitrot altera in modo subdolo e graduale i file, fino a quando il sistema diventa troppo degradato per continuare a funzionare. Il pericolo sta nell&amp;rsquo;ambiguità di non sapere quali file sono intatti, e quali hanno subito variazioni.&lt;/p&gt;
&lt;p&gt;In questo caso il bitrot aveva colpito l&amp;rsquo;inizio e la fine del file, mentre la parte centrale era completamente intatta.&lt;/p&gt;
&lt;h2 id="riscrivere-sulle-spalle-di-chi-e-stato-addestrato-a-riscrivere"&gt;Riscrivere sulle spalle di chi è stato addestrato a riscrivere&lt;/h2&gt;
&lt;p&gt;La mia conoscenza di lighttpd in combinazione con ruby non è abbastanza approfondita da poter riscrivere il file da capo: di conseguenza ho preso spunto da un metodo utilizzato per addestrare i primi LLM.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/BERT_(language_model)"&gt;BERT&lt;/a&gt; (creato da Google nel 2018) è un modello del linguaggio addestrato utilizzato il metodo &amp;ldquo;MLM&amp;rdquo; (Masked Language Modeling). Il concetto è semplice: si offre al modello un testo e si sostituiscono alcune parole con il token &lt;code&gt;[mask]&lt;/code&gt;. L&amp;rsquo;obbiettivo del modello è quello di sostituire la maschera con le parole mancanti e ottenere il testo originale.&lt;/p&gt;
&lt;p&gt;I modelli neurali moderni, con miliardi di parametri, sono in grado di eseguire questo compito molto bene. Ho sostituito tutti i caratteri corrotti, o che a occhio sembravano invalidi, con il carattere &lt;code&gt;?&lt;/code&gt;, e poi ho utilizzato &lt;code&gt;ollama&lt;/code&gt; con un modello locale e il seguente prompt per ricostruire il file.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;This attachment is a config file of an old lighttpd server recovered from a corrupted sd card.
The bit rot caused bit flips that modified characters through the entire file.
Non-printable characters were replaced with question marks (?): those are supposed to be corrected.
Be aware of bit flips that cause subtle syntax errors such as missing semicolons, case folding and letter replacements.
Provide a corrected file. Do not rewrite it, reorder options or optimize. Only edit the corrupted parts.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nonostante il prompt, purtroppo i modelli moderni tendono troppo voler &amp;ldquo;aiutare&amp;rdquo; e spesso riscrivono da capo, ottimizzano o rielaborano l&amp;rsquo;input piuttosto che modificarlo e basta.&lt;/p&gt;
&lt;p&gt;Quindi, invece di fidarmi ciecamente dell&amp;rsquo;output del modello, ho verificato e applicato manualmente le differenze riga per riga, integrando solo le correzioni effettive e ignorando i commenti extra aggiunti dall&amp;rsquo;LLM.&lt;/p&gt;
&lt;p&gt;Nonostante il file di configurazione ora valido e il server in esecuzione, l&amp;rsquo;interfaccia web era raggiungibile ma mostrava una pagina bianca. Controllando il file di log, è emerso un problema in un file &lt;code&gt;ruby&lt;/code&gt; responsabile dell&amp;rsquo;interfaccia.&lt;/p&gt;
&lt;p&gt;Siccome il disco era ormai confermato corrotto e non sapevo con certezza se ci fossero altri file danneggiati, ho deciso di controllarli tutti.&lt;/p&gt;
&lt;p&gt;Un file ruby afflitto da bitrot, statisticamente, avrà almeno un carattere non ASCII (�). Quando il comando &lt;code&gt;file&lt;/code&gt; analizza un file con caratteri non ASCII lo classifica come &amp;ldquo;binary data&amp;rdquo;. Possiamo quindi cercare tutti i file ruby classificati in questo modo per ottenere una lista di quelli corrotti.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## Feed all the *.rb files to the &amp;quot;file&amp;quot; command, then only include lines that match binary data
$ find . -name '*.rb' -exec file {} \; | grep 'binary data'
./RqstHndl.rb: a /usr/bin/ruby -I/chamb/src -Ku script executable (binary data)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Solo &lt;code&gt;RqstHndl.rb&lt;/code&gt; è risultato corrotto, ed è stato riparato utilizzando lo stesso metodo adottato per la configurazione sopra.&lt;/p&gt;
&lt;h2 id="successo"&gt;Successo!&lt;/h2&gt;
&lt;p&gt;Aprendo l&amp;rsquo;interfaccia da un browser, è apparsa finalmente l&amp;rsquo;interfaccia descritta nel manuale:&lt;/p&gt;
&lt;p&gt;&lt;img alt="L'interfaccia web della camera termica, che mostra le icone per avviare, modificare e controllare i parametri." src="/images/webint.png"&gt;&lt;/p&gt;
&lt;h2 id="sostituzione-della-sd"&gt;Sostituzione della SD&lt;/h2&gt;
&lt;p&gt;L&amp;rsquo;interfaccia è visibile e funzionante. La scheda SD su cui stava girando, però, aveva già mostrato segni di instabilità. È necessario quindi sostituirla con una nuova e, possibilmente, più longeva.&lt;/p&gt;
&lt;p&gt;Aprire l&amp;rsquo;involucro del modulo è facile: all&amp;rsquo;interno si trova solo un SBC con una scheda aggiuntiva che mantiene data e ora (RTC) e un&amp;rsquo;interfaccia RS485 supplementare utilizzata per la connessione al PLC del macchinario.&lt;/p&gt;
&lt;p&gt;&lt;img alt="L'immagine mostra una scatola metallica aperta, al cui interno si trovano un SBC e dei cavi. La struttura è semplice ed è composta principalmente da componenti standard collegati tra loro." src="/images/chamber_box_inside.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Il filesystem interno è di soli 2 GB, ma l&amp;rsquo;uso di una scheda classificata come &amp;ldquo;High Endurance&amp;rdquo;, unito alla scelta di una capienza superiore (16GB) per distribuire le scritture su un area più ampia, renderà ancora più improbabile che il difetto accada in futuro (o almeno, entro la dismissione del macchinario).&lt;/p&gt;
&lt;p&gt;È stato tentato un nuovo avvio per verificare la compatibilità con la nuova scheda, questa volta osservando tramite porta seriale la presenza di errori aggiuntivi. Non tutto è andato per il meglio:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[... bootloader and kernel boot above ...]
Running local start scripts.
Starting udevd: udevd[164]: add_to_rules: unknown key 'KERN�L' in /etc/udev/rules.d/z99_usb_image_update.rules:1
udevd[164]: add_to_rules: unknown key 'B�S' in /etc/udev/rules.d/z99_usb_image_update.rules:1
udevd[164]: add_to_rules: unknown key 'KERNML' in /etc/udev/rules.d/z99_usb_image_update.rules:2
udevd[164]: add_to_rules: invalid rule '/etc/udev/rules.d/z99_usb_image_update.rules:2'
                                                                done
Loading /etc/config:                                            done
Changing file permissions: udevd-event[227]: run_program: exec of program '/bi�/sh' failed
udevd-event[229]: run_program: exec of program '/bi�/sh' failed

[... infinite loop of run_program ...]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Questi errori ci hanno permesso di individuare altri file da correggere in &lt;code&gt;/etc/profile&lt;/code&gt; e &lt;code&gt;/etc/udev/rules.d/&lt;/code&gt;. Inoltre, gli errori generavano un loop infinito di scritture nel log di sistema, sommergendo la SD di scritture.&lt;/p&gt;
&lt;p&gt;È possibile che questo piccolo errore nella regola udev abbia causato tutti gli altri problemi.&lt;/p&gt;
&lt;h2 id="controllo-del-filesystem"&gt;Controllo del filesystem&lt;/h2&gt;
&lt;p&gt;Prima di dichiarare il sistema sicuro è necessario utilizzare &lt;code&gt;fsck.ext3&lt;/code&gt; sulla partizione per trovare e correggere gli errori rimanenti. Solitamente, &lt;code&gt;fsck&lt;/code&gt; corregge gli errori del filesystem e sposta i file orfani o irrecuperabili nella cartella &lt;code&gt;lost+found/&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo fsck.ext3 -v -y /dev/sdb1
e2fsck 1.47.3 (8-Jul-2025)
/dev/sdb1 contains a file system with errors, check forced.
Pass 1: Checking inodes, blocks, and sizes
Pass 2: Checking directory structure
Directory entry for '.' in ... (64209) is big.
Split? yes

Directory inode 64209, block #0, offset 12: directory corrupted
Salvage? yes

Pass 3: Checking directory connectivity
'..' in /etc/config.bak (64209) is &amp;lt;The NULL inode&amp;gt; (0), should be /etc (64001).
Fix? yes

Couldn't fix parent of inode 64209: Couldn't find parent directory entry

Pass 4: Checking reference counts
Pass 5: Checking group summary information

/dev/sdb1: ***** FILE SYSTEM WAS MODIFIED *****

/dev/sdb1: ********** WARNING: Filesystem still has errors **********


       4583 inodes used (3.82%, out of 120000)
         39 non-contiguous files (0.9%)
          1 non-contiguous directory (0.0%)
            ## of inodes with ind/dind/tind blocks: 146/1/0
      37720 blocks used (7.86%, out of 479990)
          0 bad blocks
          1 large file

       3687 regular files
        268 directories
        132 character device files
        133 block device files
          0 fifos
 4294967295 links
        353 symbolic links (353 fast symbolic links)
          1 socket
------------
       4573 files
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;C&amp;rsquo;è ancora un problema. Ironicamente, è proprio il backup dei file di configurazione a impedire a &lt;code&gt;fsck&lt;/code&gt; di riparare il filesystem.&lt;/p&gt;
&lt;p&gt;Per capire cosa sta succedendo, dobbiamo fare un passo indietro e spiegare cos&amp;rsquo;è un &lt;strong&gt;inode&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Nei filesystem come ext3, il nome associato al file è solo un etichetta comoda che viene data dall&amp;rsquo;utente per individuarlo.&lt;/p&gt;
&lt;p&gt;I dati reali e i metadati del file (permessi, dimensione, dove si trovano i blocchi fisici sul disco) sono memorizzati in una struttura dati chiamata &lt;em&gt;inode&lt;/em&gt;, a cui sono assegnati numeri univoci.&lt;/p&gt;
&lt;p&gt;Quando il sistema cerca &lt;code&gt;/etc/config.bak&lt;/code&gt;, in realtà va a cercare l&amp;rsquo;inode associato a quel nome, &lt;code&gt;64209&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Guardando il report finale di &lt;code&gt;fsck&lt;/code&gt;, un valore bizzarro salta all&amp;rsquo;occhio: il filesystem riporta &lt;em&gt;4.294.967.295 collegamenti&lt;/em&gt; (hard link). Siccome ogni collegamento è, a sua volta, associato a un inode, è tecnicamente impossibile avere più collegamenti che inodes.&lt;/p&gt;
&lt;p&gt;In più, il numero corrisponde esattamente a &lt;code&gt;0xFFFFFFFF&lt;/code&gt;, ovvero &lt;a href="https://en.wikipedia.org/wiki/4%2C294%2C967%2C295"&gt;il più grande numero intero rappresentabile a 32 bit&lt;/a&gt; -  una coincidenza troppo strana per essere casuale.&lt;/p&gt;
&lt;p&gt;Questo valore fuori scala è causato dall&amp;rsquo;inode &lt;code&gt;64209&lt;/code&gt;, che il sistema associa proprio alla cartella &lt;code&gt;/etc/config.bak&lt;/code&gt;, la stessa che sta bloccando &lt;code&gt;fsck&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Purtroppo, tentare di eliminare quella cartella usando il normale comando &lt;code&gt;rmdir&lt;/code&gt; causa un crash immediato del sistema. Per risolvere la situazione serve agire a basso livello: dovremo usare &lt;code&gt;debugfs&lt;/code&gt;, uno strumento che consente di modificare &amp;ldquo;chirurgicamente&amp;rdquo; il filesystem, agendo direttamente sugli inode.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## debugfs -w /dev/sdb1
debugfs 1.47.3 (8-Jul-2025)
debugfs:  stat &amp;lt;64209&amp;gt;
Inode: 64209   Type: directory    Mode:  0775   Flags: 0x0
Generation: 3813769555    Version: 0x00000000:00000000
User:     0   Group:     0   Project:     0   Size: 4096
File ACL: 0
Links: 2   Blockcount: 8
Fragment:  Address: 0    Number: 0    Size: 0
 ctime: 0x4e264393:00000000 -- Wed Jul 20 04:55:15 2011
 atime: 0x69d67a22:60bfcb28 -- Wed Apr  8 17:54:10 2026
 mtime: 0x4e264393:00000000 -- Wed Jul 20 04:55:15 2011
crtime: 0x00000000:00000000 -- Thu Jan  1 01:00:00 1970
Size of extra inode fields: 32
BLOCKS:
(0):268290
TOTAL: 1

debugfs:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lo strumento mette a disposizione una shell interattiva. Scrivendo &lt;code&gt;ncheck 64209&lt;/code&gt; verifichiamo a quale percorso corrisponde l&amp;rsquo;inode corrotto:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;debugfs:  ncheck 64209
Inode   Pathname
64209   /etc/config.bak
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Come sospettato, è il backup. Siccome si tratta di una cartella vuota, possiamo rimuoverla direttamente agendo sul filesystem:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;debugfs:  rmdir /etc/config.bak
debugfs:  quit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Siamo pronti per eseguire nuovamente il controllo. Nel caso la rimozione della cartella abbia causato altre inconsistenze nel filesystem,  &lt;code&gt;fsck&lt;/code&gt; potrà occuparsene e terminare la riparazione, ottenendo un filesystem pulito al 100%.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## fsck.ext2 /dev/sdb1
e2fsck 1.47.3 (8-Jul-2025)
/dev/sdb1 contains a file system with errors, check forced.
Pass 1: Checking inodes, blocks, and sizes
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information
/dev/sdb1: 4582/120000 files (0.9% non-contiguous), 37719/479990 blocks
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Il sistema è stato completamente ripristinato. Per sicurezza, è stata creata un&amp;rsquo;immagine disco del risultato finale per poter ripristinare il tutto in caso di problemi futuri.&lt;/p&gt;
&lt;h2 id="conclusioni"&gt;Conclusioni&lt;/h2&gt;
&lt;p&gt;Quando si progetta un dispositivo embedded, specialmente se dovrà funzionare senza supervisione per decenni, una corretta attenzione all&amp;rsquo;integrità del filesystem è cruciale. I bit flip accadranno prima o poi e il sistema deve essere in grado di ripararli o, perlomeno, rilevarli.&lt;/p&gt;
&lt;p&gt;Ci sono alcuni accorgimenti da seguire:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mantenere il sistema in sola lettura: separare i dati utente e di sistema in partizioni diverse e permettere la scrittura solo sulla partizione utente.&lt;/li&gt;
&lt;li&gt;Considerare la possibilità di corruzione totale: avere una partizione extra per un’immagine &amp;ldquo;factory&amp;rdquo; o &amp;ldquo;recovery&amp;rdquo; che possa riportare il dispositivo allo stato di fabbrica.&lt;/li&gt;
&lt;li&gt;Se possibile, eseguire tutto dalla memoria: anche se la RAM embedded raramente è ECC, qualsiasi problema potrà essere risolto con un riavvio.&lt;/li&gt;
&lt;li&gt;Scrivere in modo atomico e validare le scritture (CoW): invece di sovrascrivere direttamente i file di configurazione, scriverli altrove e poi spostarli nella destinazione.&lt;/li&gt;
&lt;li&gt;Loggare solo l&amp;rsquo;essenziale e in blocchi: non è necessario scrivere continuamente su SD. Quando si porta il dispositivo in produzione, ridurre la frequenza dei commit così che le scritture siano deframmentate oppure disattivare completamente i log.&lt;/li&gt;
&lt;li&gt;Utilizzare filesystem moderni con funzioni di &amp;ldquo;checksum&amp;rdquo; e &amp;ldquo;scrub&amp;rdquo;, che controllano e riscrivono i dati corrotti prima che vengano persi del tutto.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tutto questo può essere facilmente implementato con progetti Open Source come &lt;a href="https://www.yoctoproject.org/"&gt;Yocto Linux&lt;/a&gt;, &lt;a href="https://www.alpinelinux.org/"&gt;Alpine Linux&lt;/a&gt; o progetti della community come &lt;a href="https://dietpi.com/"&gt;DietPi&lt;/a&gt;.&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Idealmente si dovrebbe creare un immagine disco a sistema spento, dato che farlo su un sistema in esecuzione produce al 100% un immagine inconsistente. Non è un grosso problema qui poiché il filesystem è comunque corrotto e i file di cui abbiamo bisogno non vengono mai modificati.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="Riparazioni"/><category term="pentesting"/><category term="repair"/><category term="embedded"/></entry><entry><title>Ho installato una webcam pubblica sul tetto di casa mia</title><link href="https://unfoxo.it/it/blog/ho-installato-una-webcam-pubblica-sul-tetto-di-casa-mia.html" rel="alternate"/><published>2023-12-10T00:00:00+01:00</published><updated>2026-04-24T15:05:32.753803+02:00</updated><author><name>unfoxo</name></author><id>tag:unfoxo.it,2023-12-10:/it/blog/ho-installato-una-webcam-pubblica-sul-tetto-di-casa-mia.html</id><summary type="html">&lt;p&gt;I dettagli tecnici alla base della trasmissione di una webcam in diretta a centinaia di spettatori contemporaneamente senza saturare un uplink da 30 Mbps, utilizzando ffmpeg, HLS e un proxy Nginx con un semplice flag&lt;/p&gt;</summary><content type="html">&lt;p&gt;Nel 2008, quando l&amp;rsquo;Italia ha spento il segnale televisivo analogico, il governo aveva promesso a tutti che sarebbe bastato comprare un nuovo televisore per continuare a guardare la TV, tenendo la vecchia antenna. In realtà, il trasmettitore della mia zona non ha mai funzionato bene, costringendo tutti a passare al satellite e presto i pali sui tetti sono stati abbandonati, sostituiti da parabole sui balconi.&lt;/p&gt;
&lt;p&gt;Mio padre fece lo stesso, e scoprendo il palo appena liberato, me ne appropriai subito per fare ogni genere di esperimenti, soprattutto con antenne e radio fatte in casa. Sedici anni dopo ho montato una telecamera in cima al palo e l&amp;rsquo;ho resa disponibile online affinché tutti potessero vederla.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Un collage della vista della telecamera, con il palo in primo piano" src="/images/cipressa-webcam.jpg"&gt;&lt;/p&gt;
&lt;h2 id="il-panorama-corrente"&gt;Il panorama corrente&lt;/h2&gt;
&lt;p&gt;La maggior parte delle telecamere online funziona in modo semplice: un singolo file JPEG pubblicato su una pagina web, con un timestamp, proveniente direttamente dalla cartella &lt;code&gt;cgi-bin&lt;/code&gt; di una vecchia telecamera. Altre utilizzano segmenti video di quindici secondi in loop, aggiornati ogni pochi minuti, per dare l&amp;rsquo;impressione di un video in tempo reale.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Alcune fotocamere a caso che mostrano diversi tipi di testi." src="/images/example-webcams.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Nonostante questo approccio funzioni e sia sufficiente per semplicemente &amp;ldquo;vedere&amp;rdquo; un panorama, volevo qualcosa che desse una sensazione di diretta. Ciò significava trovare una videocamera che non solo fosse di ottima qualità, ma che fornisse anche un flusso pulito con cui poter lavorare.&lt;/p&gt;
&lt;h2 id="videocamere-di-videosorveglianza"&gt;Videocamere di &amp;ldquo;videosorveglianza&amp;rdquo;&lt;/h2&gt;
&lt;p&gt;Purtroppo, la maggior parte delle fotocamere consumer attualmente in commercio è vincolata all&amp;rsquo;ecosistema del proprio produttore e non dispone di un&amp;rsquo;interfaccia web, oppure ne ha una molto limitata. Hanno bisogno di un server, funzionano solo se si possiede un account e, prima o poi, saranno accessibili solo tramite abbonamento una volta che il produttore deciderà di averne avuto abbastanza.&lt;/p&gt;
&lt;p&gt;Il mio obiettivo non è gestire un sistema di sicurezza, quindi non mi servono funzioni come registrazione, rilevamento dei movimenti, archiviazione o altre funzionalità &amp;ldquo;AI&amp;rdquo;. Ho scelto una telecamera del genere perché sono molto diffuse e abbastanza robuste da resistere per anni su un palo esposto alle intemperie, e la stessa risoluzione necessaria per riprendere il volto di un ladro farà in modo che ogni dettaglio del paesaggio sia ben visibile.&lt;/p&gt;
&lt;p&gt;Fortunatamente, nel 2008, Axis, Bosch e Sony hanno creato ONVIF, &amp;ldquo;Open Network Video Interface Forum&amp;rdquo;. L&amp;rsquo;idea è semplice: qualsiasi telecamera ONVIF espone il proprio flusso in modo standard e supporta il rilevamento e la configurazione da parte di altri dispositivi compatibili.&lt;/p&gt;
&lt;p&gt;Quindi, ho cercato una telecamera ONVIF.&lt;/p&gt;
&lt;h3 id="sony-e-ancora-il-re"&gt;Sony è ancora il re&lt;/h3&gt;
&lt;p&gt;Sebbene la maggior parte dei sensori sia in grado di produrre immagini di buona qualità durante il giorno, uno degli obiettivi di questa telecamera è quello di riprendere il tramonto e fornire immagini nitide del porto vicino durante la notte. Ciò richiede una buona gamma dinamica (altrimenti il sole oscurerebbe tutto) e algoritmi di regolazione automatica efficienti, in modo da garantire immagini di buona qualità in qualsiasi condizione di luce.&lt;/p&gt;
&lt;p&gt;Sony è ancora il re nel settore dei sensori e produce una linea specializzata chiamata &amp;ldquo;Sony Starvis&amp;rdquo;. Questi sensori offrono una gamma dinamica straordinaria e una visione notturna in grado di mostrare dettagli e colori anche in condizioni di oscurità quasi totale.&lt;/p&gt;
&lt;p&gt;&lt;img alt="La fotocamera offre una buona qualità dell'immagine sia di giorno che al crepuscolo e di notte." src="/images/camera-day-night.jpg"&gt;&lt;/p&gt;
&lt;p&gt;In realtà, però, non è necessario acquistare una fotocamera Sony. Questo sensore viene fornito da Sony ai produttori di videocamere, quindi è possibile scegliere tra tantissime marche diverse: solitamente il modello del sensore è indicato nella scheda del prodotto, o basta chiedere al produttore.&lt;/p&gt;
&lt;h3 id="poe"&gt;PoE&lt;/h3&gt;
&lt;p&gt;L&amp;rsquo;uso di un iniettore PoE comporta il passaggio di un unico cavo che fornisce sia l&amp;rsquo;alimentazione che dati. Ciò semplifica l&amp;rsquo;installazione e garantisce che tutto sia IP67. Inoltre, sebbene le telecamere siano progettate per rimanere sempre accese, &lt;a href="https://unfoxo.it/it/blog/resuscitare-un-macchinario-industriale-afflitto-da-bit-rot.html"&gt;il bit rot è sempre in agguato&lt;/a&gt; e molti switch PoE dispongono di un sistema di riavvio automatico nel caso la videocamera non trasmetta dati per un certo periodo di tempo.&lt;/p&gt;
&lt;p&gt;In questo modo, i blocchi del software (e ce ne sono stati alcuni in questi tre anni) si riducono ad un reboot e a un paio di minuti di downtime.&lt;/p&gt;
&lt;h2 id="la-scelta"&gt;La scelta&lt;/h2&gt;
&lt;p&gt;Con un budget piuttosto limitato e la voglia di installarla il prima possibile, ho acquistato una telecamera PTZ (pan, tilt, zoom, orientabile a distanza) prodotta da Reolink: la RLC-830A (ora fuori produzione). Rispetto ad altre marche, offre un&amp;rsquo;ottima qualità a un prezzo ragionevole e consente comunque il controllo locale.&lt;/p&gt;
&lt;p&gt;Questa scelta ha comportato ulteriori vantaggi: avevo già lavorato con loro in passato (utilizzandoli in modo improprio come telecamere per machine vision), sapevo che il sensore era esattamente quello di cui avevo bisogno e, sebbene ONVIF fosse già sufficiente, era disponibile anche un&amp;rsquo;API aggiuntiva ben documentata.&lt;/p&gt;
&lt;p&gt;&lt;img alt="La telecamera Reolink, appena montata sul palo." src="/images/reolink-camera.jpg"&gt;&lt;/p&gt;
&lt;h2 id="proteggere-e-servire-il-video"&gt;Proteggere e servire il video&lt;/h2&gt;
&lt;p&gt;Sebbene la telecamera consenta l&amp;rsquo;accesso a più utenti o ospiti tramite il pannello integrato, rendere visibile l&amp;rsquo;intera interfaccia della telecamera solo per mostrare l&amp;rsquo;immagine è estremamente insicuro e poco pratico da gestire. Grazie a ONVIF, tuttavia, otteniamo uno streaming di buona qualità trasmesso tramite RTSP (“Real Time Streaming Protocol”) che non richiede alcun accesso.&lt;/p&gt;
&lt;p&gt;Molti player supportano RTSP e, in teoria, sarebbe possibile trasmetterlo direttamente esponendo la videocamera. Tuttavia, ci sono alcuni problemi:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I browser non supportano RTSP in modo nativo. Lo streaming RTSP significherebbe dover utilizzare un app esterna o un relay per convertirlo in WebSocket&lt;/li&gt;
&lt;li&gt;Le telecamere non sono progettate per fornire molti flussi simultanei. Non ho testato quanti ne possa gestire questa telecamera, ma presumo non molti.&lt;/li&gt;
&lt;li&gt;Ogni flusso RTSP si somma alla larghezza di banda in upload&lt;/li&gt;
&lt;li&gt;Botnet! Esistono sciami di dispositivi IoT con connessioni Internet prese in ostaggio da malintenzionati che le usano per lanciare attacchi DDoS.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Inoltre, a causa della sua distanza dalla fibra ad alta velocità, questa telecamera è collegata a una connessione Internet che fornisce solo 30 Mbps in uplink. Anche se la larghezza di banda necessaria per servire un panorama quasi statico non è enorme, basterebbero solo un paio di client connessi a saturare l&amp;rsquo;uplink della connessione &amp;ndash; che, tra l&amp;rsquo;altro, è condivisa anche con l&amp;rsquo;utilizzo normale di internet a casa.&lt;/p&gt;
&lt;p&gt;Per questo motivo, ho preso ispirazione dai giganti dello streaming e ho utilizzato la tecnologia che alimenta Youtube, Netflix e qualsiasi IPTV al mondo: HLS (“HTTP Live Streaming”).&lt;/p&gt;
&lt;h2 id="hls-verso-il-resto"&gt;HLS verso il resto&lt;/h2&gt;
&lt;p&gt;Lo streaming tradizionale funziona creando un buffer circolare: immaginate uno spezzone di nastro ad anello su cui la telecamera registra continuamente. Ogni utente che desidera vedere il video si connette al server, e il server inizia a inviare i dati a partire dalla posizione dell&amp;rsquo;utente all&amp;rsquo;interno del ciclo.&lt;/p&gt;
&lt;p&gt;Il server deve tenere traccia della posizione di ogni connessione e inviare i pacchetti in base alle esigenze del client. La maggior parte del lavoro è svolta dal server.&lt;/p&gt;
&lt;p&gt;HLS, invece, sposta il lavoro sul client: lo streaming viene suddiviso in spezzoni da 5-15 secondi con un ID sempre crescente, a cui viene associato un file (manifest) che indica dove si trovano gli spezzoni e come riprodurli.&lt;/p&gt;
&lt;p&gt;Questo trasforma un singolo flusso continuo in tanti piccoli file statici che rendono la distribuzione e il caching su larga scala molto efficaci, poiché i server non devono nemmeno supportare lo streaming video né sapere in quale posizione si trovano gli utenti.&lt;/p&gt;
&lt;p&gt;Il &lt;em&gt;manifest&lt;/em&gt;, nel caso di uno streaming live, contiene i segmenti più recenti che sono stati prodotti. Riprodurre lo streaming è molto semplice:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Scarica il manifesto&lt;/li&gt;
&lt;li&gt;Controlla l&amp;rsquo;ID dell&amp;rsquo;ultimo segmento generato&lt;/li&gt;
&lt;li&gt;Scarica e riproduci il segmento&lt;/li&gt;
&lt;li&gt;Torna al punto 1&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Tutto avviene tramite HTTPS, quindi non solo è compatibile con la maggior parte, se non tutti, i browser moderni, ma viene anche raramente bloccato dai firewall aziendali.&lt;/p&gt;
&lt;p&gt;Utilizzando &lt;code&gt;ffmpeg&lt;/code&gt; è facile trasformare uno streaming RTSP in HLS. Basta fornirgli lo streaming di origine e una cartella di destinazione, e verrà fatto tutto al volo, senza ricodifica:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ffmpeg -an -i rtsp://[...] -c:v copy -f hls \
-start_number 0 -hls_time 5 -hls_list_size 5 -hls_flags delete_segments \
/tmp/videodata/index.m3u8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ecco cosa fa il comando, pezzo per pezzo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-an&lt;/code&gt;: rimuovi l&amp;rsquo;audio&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-i rtsp://[...]&lt;/code&gt;: utilizza questo stream rtsp come input&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-c:v copy&lt;/code&gt;: copia il video (senza ricodificarlo) &amp;ndash; in questo modo azzeriamo l&amp;rsquo;uso di CPU e RAM&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-f hls&lt;/code&gt;: usiamo il formato HLS&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-start_number 0&lt;/code&gt;: numerando i segmenti dal numero 0 in poi&amp;hellip;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-hls_time 5&lt;/code&gt;: &amp;hellip;e rendendoli lunghi 5 secondi&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-hls_list_size 5&lt;/code&gt;: scrivi gli ultimi 5 segmenti nel manifest&amp;hellip;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-hls_flags delete_segments&lt;/code&gt;: &amp;hellip;e cancella i più vecchi&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/tmp/videodata/index.m3u8&lt;/code&gt;: mettendo il manifest qui&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Questo comando viene eseguito in modo continuo e crea la struttura necessaria nella directory &lt;code&gt;/tmp/videodata/&lt;/code&gt;: il manifest (&lt;code&gt;index.m3u8&lt;/code&gt;) e una serie di segmenti chiamati &lt;code&gt;indexXXX.ts&lt;/code&gt;, dove XXX rappresenta l&amp;rsquo;ID.&lt;/p&gt;
&lt;p&gt;Ad esempio, ecco come appare la cartella dopo circa mezzo minuto:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ls /tmp/videodata/
index1.ts  index2.ts  index3.ts  index4.ts  index5.ts  index6.ts  index.m3u8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Il contenuto del manifesto è chiaro:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:2
#EXTINF:5.999978,
index2.ts
#EXTINF:3.999978,
index3.ts
#EXTINF:5.999989,
index4.ts
#EXTINF:3.999978,
index5.ts
#EXTINF:5.999978,
index6.ts

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tralasciando l&amp;rsquo;inizio del file che indica la versione del manifest, è presente &lt;code&gt;TARGETDURATION&lt;/code&gt; (che comunica al lettore la durata prevista dei segmenti, in modo che possa visualizzare la barra in basso), &lt;code&gt;MEDIA-SEQUENCE&lt;/code&gt; (ID del segmento iniziale) e poi una lista di cinque segmenti, tutti con il loro nome e la durata.&lt;/p&gt;
&lt;p&gt;Il &lt;code&gt;MEDIA-SEQUENCE&lt;/code&gt; è necessario perché il browser deve conoscere l&amp;rsquo;ID di ogni segmento in modo da non saltare o riprodurre i segmenti due volte. Notate come, nonostante non sia più incluso nel manifesto, &amp;ldquo;index1.ts&amp;rdquo; esista ancora sul disco. Questo serve ad assicurarsi che ogni client abbia finito di riprodurre quel pezzo prima che venga cancellato.&lt;/p&gt;
&lt;p&gt;Noterete anche che non tutti i file hanno la lunghezza esatta che abbiamo specificato nel comando, e questo è normale: il video può essere suddiviso solo in corrispondenza dei &lt;em&gt;keyframes&lt;/em&gt;, che vengono determinati dalla telecamera in base alla quantità di movimento e altre impostazioni di codifica.&lt;/p&gt;
&lt;p&gt;Dividere lo stream in punti diversi senza reencodarlo lo corromperebbe, creando un risultato identico al &lt;a href="https://en.wikipedia.org/wiki/Compression_artifact#Artistic_use"&gt;datamoshing&lt;/a&gt;. Poiché abbiamo specificato &lt;code&gt;-c:v copy&lt;/code&gt;, ffmpeg rifiuta di dividere in punti non validi, ottenendo lunghezze dei segmenti leggermente variabili.&lt;/p&gt;
&lt;p&gt;Questa cartella può essere servita con qualsiasi server web. Un player recupererà automaticamente i segmenti e li riassemblerà lato client.&lt;/p&gt;
&lt;h2 id="distribuzione-dello-streaming"&gt;Distribuzione dello streaming&lt;/h2&gt;
&lt;p&gt;Mentre ffmpeg è in esecuzione su un piccolo server situato nella stessa posizione della telecamera, il sito web vero e proprio è ospitato su un VPS con un uplink gigabit. In questo contesto, quando parlo di &amp;ldquo;frontend&amp;rdquo; mi riferisco al proxy presente sul VPS (il server Nginx) che si trova tra gli utenti e la telecamera. &lt;/p&gt;
&lt;p&gt;Tutto, tranne la generazione effettiva del video, viene gestito da questo frontend. Quando gli utenti richiedono segmenti video, il frontend li recupera dal backend, li memorizza nella cache e li distribuisce. &lt;/p&gt;
&lt;p&gt;Tuttavia, configurare un semplice server di caching non è sufficiente. Lo streaming live presenta un problema di tempistica unico: appena creato un nuovo segmento, tutti gli spettatori lo scaricheranno esattamente nello stesso momento. Finito di riprodurlo, il segmento sarà immediatamente obsoleto e nessun&amp;rsquo;altro lo richiederà più.&lt;/p&gt;
&lt;p&gt;Ecco perché dobbiamo ottimizzare il proxy correttamente, altrimenti c&amp;rsquo;è il rischio che i segmenti vengano memorizzati nella cache solo dopo che tutti hanno smesso di richiederli.&lt;/p&gt;
&lt;h2 id="thundering-herd-as-a-service"&gt;Thundering herd as a service&lt;/h2&gt;
&lt;p&gt;L&amp;rsquo;obiettivo di un server web con cache è quello di fornire le risorse ai visitatori il più rapidamente possibile. Per questo motivo, se più utenti richiedono contemporaneamente una risorsa che non è ancora presente nella cache, un server standard potrebbe aprire più connessioni dirette al backend per fungere da proxy, salvando il file (e fornendolo dalla cache) solo una volta che la prima connessione ha completato il download. &lt;/p&gt;
&lt;p&gt;Ciò è altamente dannoso per uno streaming HLS. Se 50 persone richiedono un segmento aggiornato nello stesso momento, il server aprirà 50 connessioni simultanee al backend.&lt;/p&gt;
&lt;p&gt;Su un uplink da 30 Mbps, lo streaming di quel segmento di 5 secondi verso quelle connessioni richiederà molto più di 5 secondi. Di conseguenza, il segmento sarà già obsoleto nel momento in cui verrà effettivamente memorizzato nella cache, sprecando larghezza di banda e causando un buffering infinito per tutti.&lt;/p&gt;
&lt;p&gt;Vogliamo invece che il server recuperi il segmento dalla videocamera una sola volta, lo salvi e poi fornisca quella copia memorizzata nella cache a tutti gli altri utenti. Questo è esattamente ciò che fa la direttiva &lt;code&gt;proxy_cache_lock&lt;/code&gt; di Nginx.&lt;/p&gt;
&lt;p&gt;Quando è abilitata, se più client richiedono lo stesso file non memorizzato nella cache, il server inoltrerà solo la prima richiesta al backend e metterà in attesa le altre. Una volta che la prima richiesta avrà terminato il download e il file sarà nella cache, il server sbloccherà il resto delle richieste, che verranno completate attingendo dalla cache.&lt;/p&gt;
&lt;p&gt;In questo modo, il backend trasmette ogni segmento solo una volta e il server front-end può gestire comodamente centinaia di spettatori simultanei senza mai saturare l&amp;rsquo;uplink. Questa configurazione trasforma essenzialmente una connessione economica residenziale da 30 Mbps in uno streaming in grado di gestire centinaia di spettatori, risparmiando una linea dedicata o costosi aggiornamenti.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Un grafico che mostra l'aumento della velocità per connessione dopo l'attivazione della cache" src="/images/camera-speed-graph.svg"&gt;&lt;/p&gt;
&lt;p&gt;La differenza è notevole. Monitorando una singola connessione e attivando &lt;code&gt;proxy_cache_lock&lt;/code&gt;, la velocità è immediatamente passata da 50 kbit/s (in fase di buffering) a 300 kbit/s, ovvero esattamente il bitrate necessario per uno streaming in tempo reale.&lt;/p&gt;
&lt;h2 id="conclusioni"&gt;Conclusioni&lt;/h2&gt;
&lt;p&gt;Con la webcam online e funzionante, non restava che condividerla. I primi utenti sono arrivati grazie al passaparola: amici, parenti e loro conoscenti. Il traffico era prevalentemente locale e immagino che la webcam venisse utilizzata per controllare il tempo o per guardare l&amp;rsquo;alba e il tramonto. Ricevevo screenshot quotidiani e bastava anche un breve periodo di inattività perché il LUG (&amp;ldquo;Linux User Group&amp;rdquo;) locale di utenti Linux si attivasse in pochi minuti, segnalando il problema. &lt;/p&gt;
&lt;p&gt;Volevo renderla realmente pubblica. Ho quindi cercato “webcam meteo online” e ho trovato &lt;a href="https://www.windy.com/?p:cams"&gt;Windy&lt;/a&gt;, una sito di meteo che mostra webcam e accetta anche segnalazioni. Dopo averla aggiunta lì, la webcam si è diffusa ed è stata inserita in modo organico in vari portali e app meteo. A volte era elencata sotto la città corretta, altre volte sotto il nome di una città vicina.&lt;/p&gt;
&lt;p&gt;Mentre alcune piattaforme (purtroppo!) mostrano solo l&amp;rsquo;immagine &lt;code&gt;preview.jpg&lt;/code&gt;, altre rimandano effettivamente al sito web. Il traffico che arriva è&amp;hellip; curioso.&lt;/p&gt;
&lt;p&gt;Il picco di traffico si verifica principalmente all&amp;rsquo;alba e al tramonto, con visualizzazioni provenienti sia da utenti locali che da luoghi come Germania, Russia o Francia (in particolare, la Costa Azzurra).&lt;/p&gt;
&lt;p&gt;&lt;img alt="Visualizzazioni giornaliere, per paese, in Europa." src="/images/camera-popularity.png"&gt;&lt;/p&gt;
&lt;p&gt;Eventi nelle vicinanze, come il Festival di Sanremo e la gara Milano-Sanremo generano picchi evidenti di visualizzazioni, così come eventi meteorologici quali forti tempeste o cicli lunari insoliti (curiosamente, la luna riesce a saturare il video meglio del sole).&lt;/p&gt;
&lt;p&gt;A un certo punto, ho notato che qualcuno dalla Germania guardava lo streaming per ore ogni giorno, iniziando esattamente alle 9:00 e finendo alle 17:00. Controllando l&amp;rsquo;User-Agent, sembrava una Smart TV. Immagino che usasse la webcam come quadro vivente, o forse aveva semplicemente dimenticato di spegnere la TV.&lt;/p&gt;
&lt;p&gt;Se volete dare un&amp;rsquo;occhiata alla telecamera, &lt;a href="https://cipressa.404.blue"&gt;la trovate qui&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Grazie della lettura.   &lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;&amp;hellip; controllando che il prossimo segmento sia effettivamente sucessivo a quello precedente, per evitare salti e ripetizioni di segmenti&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="articles"/><category term="Hosting"/><category term="Streaming"/></entry></feed>