Regio's en Scopes¶
TRANSPORT CODES · SCOPE · REPEATERFILTERING
MeshCore begrenst flood-verkeer met regio's. Een repeater kent een of meer regio's, en elk bericht krijgt een scope: de regio waarbinnen de afzender het wil laten rondgaan. Herkent de repeater die scope niet als een van zijn eigen regio's, dan gaat het bericht niet verder.
Denk daarbij niet aan een postzegel of een label, want dat is precies de denkfout waar dit hoofdstuk mee moet afrekenen. Denk aan een lakzegel. De repeater léést de zegel niet om te zien van wie hij is. Hij pakt zijn eigen stempel, drukt die op hetzelfde document, en kijkt of de afdruk gelijk is. Alleen wie de stempel bezit kan de afdruk maken, en de afdruk is voor elk document anders. Wat er in het pakket staat is dus geen naam en geen nummer van een regio, maar een handtekening die met de regiosleutel over dit ene pakket is gezet.
Dit hoofdstuk beschrijft de protocolkant daarvan: waar die scope in het pakket zit, hoe hij wordt berekend, en waarop een repeater zijn beslissing baseert. Voor het instellen van regio's op je eigen node, zie Aan de Slag. De naamgevingsafspraken binnen NoodNet Overijssel gaan over hoe knooppunten en regio's heten, dit hoofdstuk over wat er technisch met regio's gebeurt; voor die afspraken en voor de vraag wat ze doen met de eigenschappen van dit mechanisme, zie Regio's: bedoeling en praktijk.
[!NOTE] Bron. Geverifieerd tegen
MeshCorev1.16.0, commita3a1aa5, 19 juli 2026 —src/helpers/RegionMap.cpp,src/helpers/TransportKeyStore.cpp,src/helpers/CommonCLI.cpp,examples/simple_repeater/MyMesh.cpp,examples/companion_radio/MyMesh.cppendocs/cli_commands.md. De pakketopbouw waarin deze codes staan is beschreven in MeshCore Pakketstructuur.
Waar zit de transport code?¶
[!NOTE] Twee dingen die allebei "regiocode" heten. In de UN/LOCODE-naamgeving is een regiocode een naam:
nl-ov-zwo. Die staat op je node en gaat nooit de lucht in. Wat wél de lucht in gaat is een 16-bits transport code, en dat is iets heel anders. Dit hoofdstuk gebruikt daarom consequent "transport code" voor de bytes in het pakket, en laat "regiocode" over aan de naamgeving.
Dit is de vraag waar het om draait, en het antwoord is
specifiek: de transport code zit in transport_codes[0], de eerste twee
bytes van het optionele transport-codes-blok, direct achter de header.
┌────────┬──────────────────┬──────────────────┬─────────────┬──────┬─────────┐
│ header │ transport_code_1 │ transport_code_2 │ path_length │ path │ payload │
│ 1 byte │ 2 bytes (scope) │ 2 bytes (res.) │ 1 byte │ 0-64 │ 0-184 │
└────────┴──────────────────┴──────────────────┴─────────────┴──────┴─────────┘
└ transport code ┘
[!CAUTION] Lees dit voordat je verder leest: dit veld is geen identificatie.
De verleiding is groot om
transport_code_1te lezen als "het nummer van de regio", zoals een VLAN-tag of een netwerk-ID. Dat is het niet, en bijna elke misvatting over regio's komt daaruit voort. Het is een HMAC over de complete payload, met de regiosleutel als sleutel. Gevolg: dezelfde regio levert een andere code op bij elk ander pakket. Dit zijn drie berichten op hetzelfde kanaal#zwolle, allemaal met scopenl-ov-zwo:
Bericht transport_code_1"Op Woensdag a.s. Blauwvingerdagen" 0x7381Zelfde tekst, één seconde later 0xAEDB"Tot morgen bij de Peperbus" 0x6F56Eén regio, één sleutel, drie codes. Een repeater kan hier onmogelijk een opzoektabel op bouwen. Hoe hij het dan wél doet staat in Hoe een repeater beslist: hij herberekent de code met elke sleutel die hij heeft, en kijkt of er één uitkomt op de code in het pakket.
| Code | Bytes | Inhoud |
|---|---|---|
transport_code_1 |
2 | De scope: een handtekening over deze payload, gezet met de sleutel van de regio waarin de afzender het pakket wil laten rondgaan. Geen regio-identificatie — zie de waarschuwing hierboven |
transport_code_2 |
2 | Gereserveerd. De firmware schrijft er nu 0x0000 in; in de code staat als voornemen de home-regio van de afzender, voor antwoordverkeer |
Beide velden zijn uint16_t en gaan little-endian over de lucht. Voor een
compleet uitgewerkt record met echte bytes, zie
het kanaalbericht verderop.
Hoe de code wordt berekend¶
Een regio heeft een naam (nl, #overijssel, $besloten). Uit die naam komt
een 16-byte transport key:
| Naamvorm | Sleutel |
|---|---|
#naam of naam |
SHA-256 over de naam inclusief #, afgekapt op 16 bytes |
$naam |
Sleutel uit de keystore van het apparaat, niet afleidbaar uit de naam |
Die sleutel wordt vervolgens níet zelf verstuurd. Per pakket berekent de zender:
code = HMAC-SHA256( key = transport key, data = payload_type ‖ payload )
afgekapt op de eerste 2 bytes
De waarden 0x0000 en 0xFFFF zijn gereserveerd en worden met één opgehoogd
respectievelijk verlaagd.
[!WARNING] De naam gaat niet de lucht in, de regio is daarmee niet geheim. Wat er verstuurd wordt is geen naam maar een 16-bits HMAC over de payload, en die is voor élk pakket anders. Dat is geen privacymaatregel. Voor een
#-regio is de sleutelSHA-256(naam), dus iedereen die de naam kent of gokt rekent de code na over een payload die hij toch al ziet — één HMAC per kandidaatnaam. Regio's zijn er om airtime te besparen, niet om verkeer te verbergen.[!NOTE]
$-regio's werken nog niet. Bij een naam die met$begint haalt de firmware de sleutel uitTransportKeyStore, en die is in v1.16.0 nog een stub:saveKeysFor()geeftfalseterug enloadKeysFor()heeft alleen een RAM-cache met// TODO: retrieve from difficult-to-copy keystoreerachter. Een$-regio die je via de CLI aanmaakt levert dus nul sleutels op, matcht nooit infindMatch(), en als default scope levert hij een nulsleutel — waarna de node ongescoopt verzendt. Een app die de 16 ruwe bytes zelf aanlevert (zie hieronder) omzeilt die store en zet wél een scope; de beperking zit dan aan de repeaterkant, die de sleutel nog niet kan bewaren.
De scope komt uit de app, en kan per kanaal verschillen¶
De regionaam bestaat alleen op het apparaat dat hem instelt. Over de BLE-verbinding naar de Companion App gaat de sleutel, niet de naam:
| Commando | Werking |
|---|---|
CMD_SET_DEFAULT_FLOOD_SCOPE (63) |
Zet naam + 16-byte sleutel als vaste default scope van de node, opgeslagen in de prefs |
CMD_GET_DEFAULT_FLOOD_SCOPE (64) |
Vraagt die default op |
CMD_SET_FLOOD_SCOPE_KEY (54), byte[1]=0 |
Zet een override-sleutel voor het verzenden; blijft staan tot je hem wijzigt, wordt bij herstart gewist |
CMD_SET_FLOOD_SCOPE_KEY (54), byte[1]=1 |
Forceert ongescoopt verzenden |
Bij verzenden kiest de firmware simpelweg
send_scope.isNull() ? default_scope : send_scope. Daarmee is een scope per
kanaal gewoon het ontwerp: de app houdt bij welk kanaal bij welke scope hoort
en zet de override vóór elke verzending. De firmware bewaart die koppeling zelf
niet — in sendFloodScoped() staat daarover // TODO: have per-channel
send_scope — maar dat gaat over waar de administratie ligt, niet over of het
kan.
[!NOTE] Deze vier commando's staan niet in
docs/companion_protocol.md. Wie een eigen app of tool bouwt, moet ze uitexamples/companion_radio/MyMesh.cpphalen. Dat geldt niet alleen voor deze vier: van de 58 companion-commando's staan er zeven in de officiële spec. Zie De commandogroepen.
Kanaal-hash en transport code zijn niet hetzelfde¶
| Kanaal-hash | Transport code | |
|---|---|---|
| Waar in het record | Byte 8, binnen de payload | Bytes 1-2, vóór het pad |
| Afgeleid van | De kanaal-PSK | De regionaam |
| Grootte | 1 byte | 2 bytes |
| Verandert per bericht | Nee, blijft gelijk | Ja, het is een HMAC over de payload |
| Aard | Een opzoeksleutel: hij identificeert iets en blijft gelijk | Een handtekening: hij bewijst iets en geldt voor één pakket |
| Hoe je hem gebruikt | Vergelijken met een lijst kanaalslots | Herberekenen met je eigen sleutels en vergelijken |
| Waarvoor | Ontvanger zoekt het juiste kanaalslot voordat hij gaat ontsleutelen | Repeater bepaalt of hij mag doorsturen |
| Wie kan het gebruiken | Alleen wie de PSK heeft | Elke repeater, ook zonder de PSK |
Die twee middelste rijen zijn het makkelijkst te verwarren en het belangrijkst uit elkaar te houden. Een kanaal-hash is een naamplaatje: je leest hem af en zoekt hem op. Een transport code is dat juist niet — hem "opzoeken" is betekenisloos, want hij staat in geen enkele tabel.
De laatste rij is het hele punt van de scheiding: een repeater kan regiofiltering toepassen zónder ooit een kanaalsleutel te bezitten. Let wel op wat dat precies betekent. Om de code te kunnen herberekenen moet de repeater de volledige payload door zijn HMAC halen — hij leest dus wel degelijk alle bytes. Wat hij niet kan, is ze ontsleutelen: zonder de PSK blijft de inhoud cijfertekst. Filteren op regio levert hem geen enkel inzicht in het bericht op, maar het is nadrukkelijk geen kwestie van "even twee bytes bekijken".
[!NOTE] Eén kanaal, meerdere scopes. Omdat de transport code buiten de versleutelde payload zit en bij verzenden wordt toegevoegd vanuit de default scope van de zendende node, kan hetzelfde kanaal door de ene node landelijk en door de andere provinciaal verstuurd worden. De ontvangers zien in beide gevallen hetzelfde bericht; alleen de verspreiding verschilt.
Vier varianten: #zwolle en zwolle, met en zonder transport code¶
Twee kanalen in dezelfde gemeente, hetzelfde bericht, allebei één keer mét en
één keer zónder scope nl-ov-zwo. Dat zijn vier frames, en het verschil zit
telkens op een andere plek.
#zwolle |
zwolle |
|
|---|---|---|
| Type | Hashtag-kanaal | Privé-kanaal |
| PSK | Door de app afgeleid uit de naam | Willekeurig gegenereerd, buiten het mesh om gedeeld |
| Wie kan meelezen | Iedereen die de naam kent | Alleen wie de PSK kreeg |
| Kanaal-hash | C3 |
DB |
Transport code bij scope nl-ov-zwo |
0x7381 |
0x35A1 |
De waarden zijn berekend met de algoritmen uit de firmware, voor het bericht
"Op Woensdag a.s. Blauwvingerdagen" van afzender PE1HVH op tijdstempel
0x6A6B3CC0. Ze zijn dus na te rekenen.
Gemeenschappelijk voor alle vier
regio nl-ov-zwo (kale naam → impliciete hashtag-regio)
sleutel SHA-256("#nl-ov-zwo")[0:16] = 90B03C2AA8E72470B3899C6033E413FF
plaintext, 46 bytes:
C0 3C 6B 6A timestamp (little-endian)
00 txt_type = plain
50 45 31 48 56 48 3A 20 4F 70 20 57 6F 65 6E 73 64 61 67 20 61 2E 73 2E 20
42 6C 61 75 77 76 69 6E 67 65 72 64 61 67 65 6E
└── "PE1HVH: Op Woensdag a.s. Blauwvingerdagen" (41 tekens)
1 — #zwolle mét transport code¶
PSK 1l+r7vMjpLnsGPpbdhzrpA==
14 81 73 00 00 02 A3 7F C3 34 30 | 97 5A 1E 28 F2 D4 9A AF … F3 27 E8
│ └─┬─┘ └─┬─┘ │ └─┬─┘ │ └─┬─┘ └──────── cijfertekst, 48 bytes ────┘
│ │ │ │ │ │ └ cipher MAC
│ │ │ │ │ └ channel hash van #zwolle
│ │ │ │ └ path: twee repeaters
│ │ │ └ path_length: 2 hops
│ │ └ transport_code_2, gereserveerd
│ └ transport_code_1 = TRANSPORT CODE 0x7381
└ header 0x14: GRP_TXT, TRANSPORT_FLOOD
59 bytes
2 — #zwolle zónder transport code¶
15 02 A3 7F C3 34 30 | 97 5A 1E 28 F2 D4 9A AF … F3 27 E8
│ │ └─┬─┘ │ └─┬─┘ └───── cijfertekst, ongewijzigd ───┘
│ │ │ │ └ cipher MAC, ongewijzigd
│ │ │ └ channel hash, ongewijzigd
│ │ └ path
│ └ path_length
└ header 0x15: GRP_TXT, FLOOD
55 bytes — de vier bytes transport codes ontbreken volledig
3 — zwolle mét transport code¶
PSK P4walNILZ+WqQccFPp2LYg==, dezelfde regio, dezelfde tekst
14 A1 35 00 00 02 A3 7F DB B4 EA | F8 52 03 83 05 E1 31 39 … 8E C5 21
│ └─┬─┘ └─┬─┘ │ └─┬─┘ │ └─┬─┘ └──────── cijfertekst, 48 bytes ────┘
│ │ │ │ │ │ └ andere MAC: andere PSK
│ │ │ │ │ └ channel hash van zwolle: DB in plaats van C3
│ │ │ │ └ path
│ │ │ └ path_length
│ │ └ transport_code_2, gereserveerd
│ └ ANDERE CODE 0x35A1 — zelfde regio, zelfde sleutel, andere payload
└ header 0x14: GRP_TXT, TRANSPORT_FLOOD
59 bytes
4 — zwolle zónder transport code¶
15 02 A3 7F DB B4 EA | F8 52 03 83 05 E1 31 39 … 8E C5 21
│ │ └─┬─┘ │ └─┬─┘ └───── cijfertekst, ongewijzigd ───┘
│ │ │ │ └ cipher MAC, ongewijzigd
│ │ │ └ channel hash, ongewijzigd
│ │ └ path
│ └ path_length
└ header 0x15: GRP_TXT, FLOOD
55 bytes
Wat de vier frames laten zien¶
Van 1 naar 2, en van 3 naar 4 — wel of geen scope:
| Mét transport code | Zónder transport code | |
|---|---|---|
| Header | 0x14 (route 0x00) |
0x15 (route 0x01) |
| Transport codes | 4 bytes aanwezig | Veld ontbreekt volledig |
| Frame bij 2 hops | 59 bytes | 55 bytes |
| Kanaal-hash, MAC, cijfertekst | Identiek | Identiek |
| Doorgestuurd door | Repeaters met regio nl-ov-zwo |
Repeaters die de wildcard * toestaan |
| Geweigerd door | Repeaters zonder die regio | Repeaters met region denyf * |
De payload is byte voor byte hetzelfde in beide gevallen. Encryptie en scope raken elkaar niet: het weglaten van de scope maakt een bericht geen haar minder vertrouwelijk, en het toevoegen ervan geen haar meer.
Van 1 naar 3, en van 2 naar 4 — ander kanaal:
#zwolle |
zwolle |
|
|---|---|---|
| Kanaal-hash | C3 |
DB |
| Cipher MAC | 34 30 |
B4 EA |
| Cijfertekst | 97 5A 1E 28 … |
F8 52 03 83 … |
| Transport code | 81 73 |
A1 35 |
[!IMPORTANT] Die laatste rij is de kern van dit hele hoofdstuk. De regio is in alle vier de frames
nl-ov-zwo, met in alle vier dezelfde sleutel90B03C2A…, en tóch staat er een andere code in het pakket. De payload verschilt namelijk, omdat de PSK verschilt — en de code is een HMAC over die payload.Daarmee valt het voor de hand liggende model om: er bestaat geen vaste code die bij
nl-ov-zwohoort. Als die wél bestond, zou je met één afgeluisterd pakket voor altijd weten hoe "Zwolle" eruitziet op de radio. Dat is precies wat hier níet zo is.De vraag wordt dan: hoe weet een repeater dan welke code hij moet doorlaten? Antwoord: dat weet hij niet, en dat hoeft ook niet. Hij bezit de sleutels van zijn eigen regio's. Bij elk binnenkomend pakket zet hij met elk van die sleutels zelf een handtekening over de payload die hij zojuist ontving. Komt er één uit op de twee bytes in het pakket, dan is dit pakket door iemand met diezelfde sleutel ondertekend, en dus voor die regio bedoeld. Geen tabel, geen lijst, geen 1-op-1 afspraak — een rekensom per pakket, per regio.
Heeft een privé-kanaal een scope nodig?¶
Technisch niet. Praktisch wel, om drie redenen:
- Doorstuurgarantie. Zonder scope hang je af van de wildcard-instelling van elke repeater onderweg.
- Airtime. Een besloten groepje in Zwolle hoeft niet door heel Nederland geflood te worden. Dat is het hele doel van regio's.
- Hop-limieten.
flood.max.unscopedstaat meestal lager danflood.max, dus ongescoopt verkeer komt sowieso minder ver.
Wat een scope níet doet: hij maakt een kanaal niet vertrouwelijker. Die rol speelt de PSK, en die alleen.
Hoe een repeater beslist¶
De kern: findMatch() rekent, hij zoekt niet op¶
Alles in dit hoofdstuk komt samen in één lus in RegionMap.cpp. De moeite waard
om letterlijk te lezen, want hij weerlegt het opzoekmodel in acht regels:
RegionEntry* RegionMap::findMatch(mesh::Packet* packet, uint8_t mask) {
for (int i = 0; i < num_regions; i++) { // ← elke regio die ik ken
auto region = ®ions[i];
if ((region->flags & mask) == 0) { // ← en die flood toestaat
TransportKey keys[4];
int num = getTransportKeysFor(*region, keys, 4);
for (int j = 0; j < num; j++) { // ← elke sleutel van die regio
uint16_t code = keys[j].calcTransportCode(packet); // ← ZELF UITREKENEN
if (packet->transport_codes[0] == code) { // ← en pas dan vergelijken
return region; // ← eerste match wint
}
}
}
}
return NULL; // geen enkele van mijn sleutels past → niet doorsturen
}
Merk op wat hier niet staat. Er wordt niet gezocht op de waarde uit het pakket. Die waarde wordt pas in de vergelijking op de één-na-laatste regel voor het eerst gebruikt. Alles daarvóór is de repeater die met zijn eigen sleutels uitrekent hoe het pakket eruit zou hebben gezien als het van die regio kwam.
De richting van de logica is dus omgekeerd aan wat je zou verwachten:
| Het opzoekmodel (onjuist) | Wat er echt gebeurt |
|---|---|
| Lees code uit pakket | Neem regio 1 uit mijn lijst |
| Zoek die code op in mijn regiolijst | Bereken met die sleutel de code over déze payload |
| Gevonden? → doorsturen | Gelijk aan wat in het pakket staat? → doorsturen |
| Niet gevonden? → droppen | Nee → volgende regio, en zo tot de lijst op is |
Een repeater met tien regio's doet dus tot tien HMAC-berekeningen per pakket, en stopt bij de eerste die past.
De beslissing in het kort¶
Bij binnenkomst van een flood-pakket bepaalt de repeater eerst de regio
(filterRecvFloodPacket), en beslist daarna pas over doorsturen
(allowPacketForward):
| Situatie | Uitkomst |
|---|---|
ROUTE_TYPE_TRANSPORT_FLOOD |
Voor elke bekende regio die flood toestaat wordt de code herberekend en vergeleken met transport_codes[0]. Eerste match wint |
ROUTE_TYPE_FLOOD (geen codes) |
Valt onder de wildcard-regio *. Staat daar denyf op, dan is er geen match |
| Geen match | allowPacketForward geeft false — het pakket wordt niet doorgestuurd |
| Directe routes | Worden niet op regio gefilterd; het meegegeven pad bepaalt de route. Waarom dat zo is, staat in Direct Messages |
Codes {0x0000, 0x0000} |
Betekent "stuur nergens heen"; wordt onder meer gebruikt bij het delen van een contact, zodat zo'n advert niet als buur wordt geteld |
Antwoordt de repeater zelf, dan gaat het antwoord terug met dezelfde scope als
de binnenkomende vraag (sendFloodReply). Eigen verkeer, zoals de periodieke
advert, gaat met de ingestelde default scope.
Naast de harde ja/nee-filter staat er een tweede rem op unscoped verkeer:
| Instelling | Werking |
|---|---|
set flood.max <n> |
Maximaal aantal hops voor elk flood-pakket |
set flood.max.unscoped <n> |
Idem, maar alleen voor pakketten zónder scope |
set flood.max.advert <n> |
Idem, alleen voor adverts |
Een zachtere variant van region denyf * is dus set flood.max.unscoped 3:
lokaal ongescoopt verkeer blijft werken, maar het komt niet meer het hele land
door.
[!NOTE] Dezelfde hiërarchie wordt in de fork Dutch-MeshCore hergebruikt voor congestiebeheer: stijgt de eigen duty cycle boven een drempel, dan sluit de repeater regio's tijdelijk van buiten naar binnen af, zonder dat naar de regioconfiguratie te schrijven. Zie Forks & varianten.
Stap voor stap¶
Neem een repeater die regio nl-ov-zwo kent en het kanaal #zwolle níet. Het
gescoopte pakket van hierboven komt binnen:
- De radiochip controleert de CRC en levert 59 bytes af.
tryParsePacket()leest header0x14. Payload versie is 0, dus verwerkbaar.- Routetype is
0x00, dus er volgen vier bytes transport codes:81 73 00 00. filterRecvFloodPacket()roeptfindMatch()aan. Dit is de stap waar het misverstand zit, dus in detail. De repeater kijkt hier níet naar81 73om te zien welke regio dat is. Hij loopt zijn eigen regiolijst af en rekent per regio zelf een code uit over de 51 payload-bytes die hij zojuist ontving:
regio nl sleutel SHA-256("#nl")[:16] → F2 2A ≠ 81 73
regio nl-ov sleutel SHA-256("#nl-ov")[:16] → B7 EE ≠ 81 73
regio nl-ge sleutel SHA-256("#nl-ge")[:16] → A1 C9 ≠ 81 73
regio nl-ov-zwo sleutel SHA-256("#nl-ov-zwo")[:16] → 81 73 = 81 73 ✔ match
Pas bij de derde poging valt het samen. Daarmee weet de repeater niet dat de
afzender "nl-ov-zwo" bedoelde omdat dat ergens staat, maar omdat hij het
resultaat heeft kunnen reproduceren — wat alleen lukt met dezelfde sleutel.
Was dit pakket één seconde eerder verzonden, dan had er DB AE gestaan en was
nl-ov-zwo opnieuw de enige regio geweest die dát had uitgerekend.
5. Payload type is GRP_TXT, dus de repeater kijkt naar channel hash 0xC3 —
en vindt niets, want hij kent dat kanaal niet. Hij heeft de payload in stap 4
dus wel volledig door SHA-256 gehaald, maar er geen letter van kunnen lezen.
Geen ontsleuteling, geen probleem.
6. hasSeen() bepaalt of dit pakket al eerder langskwam via een andere route.
De vingerafdruk is een SHA-256 over payload type en payload, dus onafhankelijk
van het afgelegde pad.
7. allowPacketForward() toetst de hop-limieten en, cruciaal,
recv_pkt_region == NULL. Die is hier gevuld, dus doorgaan.
8. De repeater hangt zijn eigen hash achter het pad, path_length wordt 03,
en het pakket gaat na een willekeurige vertraging opnieuw de lucht in.
Bij het ongescoopte pakket verandert alleen stap 3 en 4: er zijn geen codes, dus
de repeater valt terug op de wildcard *. Staat daar denyf op, dan is
recv_pkt_region leeg en stopt het in stap 7.
[!NOTE] De repeater ontsleutelt nooit iets — maar hij leest wel alles. Twee uitspraken die vaak door elkaar lopen. Om
findMatch()te kunnen draaien moet hij elke payload-byte door de HMAC halen; hij verwerkt het pakket dus in zijn geheel. Wat hij mist is de PSK van#zwolle, en daarmee blijft die payload voor hem betekenisloze cijfertekst. Dát is de scheiding tussen scope en encryptie: filteren vereist de regiosleutel en levert geen inhoud op, meelezen vereist de kanaalsleutel en levert geen filterrecht op.
Welke gevolgen dit heeft, en wat het niet garandeert¶
Het herberekenmodel heeft twee consequenties die de rest van dit hoofdstuk niet noemt, en die je moet kennen voordat je een grote regioboom uitrolt.
Regiofiltering is statistisch, niet absoluut. De code is 2 bytes. De kans dat
een pakket uit een wildvreemde regio toevallig samenvalt met een van jouw
sleutels is ongeveer 1 op 65536 per sleutel, en findMatch() probeert ze
allemaal:
| Regio's op de node | Pogingen per pakket | Kans op onterecht doorsturen |
|---|---|---|
| 5 | 5 | 0,008 % — 1 op 13.000 pakketten |
| 10 | 10 | 0,015 % — 1 op 6.500 |
32 (MAX_REGION_ENTRIES) |
32 | 0,049 % — 1 op 2.000 |
| 32, elk met 4 sleutels | 128 | 0,195 % — 1 op 500 |
Voor het doel — airtime besparen — is dat ruim voldoende. Als filter waar iets van afhangt is het dat niet, en het onderstreept nog eens dat een scope geen beveiligingsmechanisme is.
Elk flood-pakket kost rekenwerk. Tot 32 regio's × 4 sleutels = 128
HMAC-SHA256-berekeningen over 50–190 bytes, op een nRF52 of ESP32, vóórdat er
ook maar iets besloten is. Bij een dicht mesh met veel flood-verkeer verbruikt een
uitgebreide regioboom dus merkbaar rekentijd. region list allowed kort houden
scheelt direct.
De regio-CLI¶
| Commando | Werking |
|---|---|
region |
Toont de complete regioboom met flood-rechten |
region put <naam> [ouder] |
Maakt een regio aan, standaard flood toegestaan |
region def <token> [<token>…] |
Bouwt een hele boom in één regel; naam\|sprong springt terug naar een bestaande regio |
region default <naam> of region default <null> |
Zet de scope waarmee deze node zelf verstuurt |
region home [<naam>] |
Toont of zet de thuisregio |
region allowf <naam> / region denyf <naam> |
Flood toestaan of weigeren; met * geldt dat voor pakketten zónder codes |
region get <naam> |
Toont ouder en flood-vlag van één regio |
region list allowed / region list denied |
Lijst met namen (firmware 1.12+) |
region remove <naam> |
Verwijdert een regio; kind-regio's moeten eerst weg |
region load |
Bulk-inladen; interactief, werkt niet op afstand |
region save |
Schrijft de wijzigingen weg — zonder dit gaat alles bij herstart verloren |
Maximaal 32 regio's per node (MAX_REGION_ENTRIES), namen tot 30 tekens,
hiërarchie tot 8 niveaus diep. Een repeaterregel is 160 tekens; grotere bomen
splits je over meerdere region def-commando's.
Antwoorden en voorbeelden per commando staan in Regio's in de CLI-referentie.
Voor de praktische kant — welke regio's je in Nederland instelt en met welke hulpmiddelen — zie Aan de Slag.