Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions docs/cli_commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,40 @@ This document provides an overview of CLI commands that can be sent to MeshCore
**Parameters:**
- `name`: Region name

**Note:** The home region is used by the auto-tag feature (see `region.autotag` below) to stamp a transport code onto untagged flood packets this repeater receives, converting them from `ROUTE_TYPE_FLOOD` to `ROUTE_TYPE_TRANSPORT_FLOOD`. When selecting a home region, choose the **most specific region in the local region hierarchy that includes all nodes this repeater can possibly hear** (including via hops you intend to cover — see `region.autotag.max.hops`). Choosing a home region that is too narrow will cause unscoped packets originating from neighbouring regions to be tagged incorrectly; choosing one that is too broad defeats the purpose of scoping.
Comment thread
dale-ruane marked this conversation as resolved.

**When no home region is set:** the repeater has no scope to apply, so auto-tagging is effectively disabled regardless of the `region.autotag` setting — untagged flood packets are forwarded based on the wildcard (`*`) region's flood permission only. To participate in auto-tagging, both a home region must be configured here **and** `region.autotag` must be `on`.

---

#### View or change whether this repeater auto-tags untagged flood packets
**Usage:**
- `get region.autotag`
- `set region.autotag <state>`

**Parameters:**
- `state`: `on` (enable) or `off` (disable)

**Default:** `off`

**Note:** When enabled, the repeater stamps its home region's transport code onto untagged flood packets (`ROUTE_TYPE_FLOOD`) it receives, converting them to `ROUTE_TYPE_TRANSPORT_FLOOD` before re-broadcast. This scopes legacy / un-scoped traffic into the configured home region, but requires a home region to be configured (see `region home`). Because mis-tagging is possible when the repeater can hear traffic originating outside its home region, this feature is opt-in. See also `region.autotag.max.hops` to limit how far a packet may have travelled before becoming eligible for auto-tagging. The reserved transport code `0xFFFF` (TRANSPORT_CODE_ALL) is always forwarded regardless of local region configuration, allowing explicit mesh-wide flooding when a sender requests it.

---

#### View or change the max hop count for auto-tagging
**Usage:**
- `get region.autotag.max.hops`
- `set region.autotag.max.hops <value>`

**Parameters:**
- `value`: Maximum path hash count. `0` means only auto-tag packets without scope received directly (zero-hop); higher values also auto-tag packets without scope that already traversed that many repeaters.

**Range:** `0` to `8` (inclusive). Values outside this range are rejected by `set` and clamped to this range on load. The upper bound of `8` is intentionally well below the default `flood.max` of `64`, because auto-tagging packets from far across the mesh almost always produces incorrect region assignments — the limit exists to keep admins honest about the geographic scope they can actually account for.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, nice.


**Default:** `1`

**Note:** Only applies when `region.autotag` is `on`. Keep this small (0-2) unless you are certain no untagged / older-firmware repeaters exist within that many hops, otherwise distant-origin traffic forwarded through them may be tagged with the wrong region.

---

#### Create a new region
Expand Down
41 changes: 37 additions & 4 deletions examples/simple_repeater/MyMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,9 @@ bool MyMesh::isLooped(const mesh::Packet* packet, const uint8_t max_counters[])
bool MyMesh::allowPacketForward(const mesh::Packet *packet) {
if (_prefs.disable_fwd) return false;
if (packet->isRouteFlood() && packet->getPathHashCount() >= _prefs.flood_max) return false;
if (packet->isRouteFlood() && recv_pkt_region == NULL) {
if (packet->isRouteFlood() && packet->hasTransportCodes() && packet->transport_codes[0] == TRANSPORT_CODE_ALL) {
// ALL region: always forward regardless of region config
} else if (packet->isRouteFlood() && recv_pkt_region == NULL) {
MESH_DEBUG_PRINTLN("allowPacketForward: unknown transport code, or wildcard not allowed for FLOOD packet");
return false;
}
Expand Down Expand Up @@ -535,12 +537,41 @@ uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) {
bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) {
// just try to determine region for packet (apply later in allowPacketForward())
if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD) {
recv_pkt_region = region_map.findMatch(pkt, REGION_DENY_FLOOD);
if (pkt->transport_codes[0] == TRANSPORT_CODE_ALL) {
recv_pkt_region = &region_map.getWildcard(); // ALL: always allow
} else {
recv_pkt_region = region_map.findMatch(pkt, REGION_DENY_FLOOD);
}
} else if (pkt->getRouteType() == ROUTE_TYPE_FLOOD) {
if (region_map.getWildcard().flags & REGION_DENY_FLOOD) {
// untagged packet: tag with home region if auto-tagging is enabled and a home region is configured
RegionEntry* home = region_map.getHomeRegion();
if (_prefs.region_autotag && home && home->id != 0
&& pkt->getPathHashCount() <= _prefs.region_autotag_max_hops) {
// calculate transport code for home region and stamp onto packet
TransportKey key;
if (home->name[0] == '$') {
// private region: load key from store
if (key_store.loadKeysFor(home->id, &key, 1) < 1) {
recv_pkt_region = NULL;
return false;
}
} else if (home->name[0] == '#') {
key_store.getAutoKeyFor(home->id, home->name, key);
} else {
char tmp[sizeof(home->name) + 1];
tmp[0] = '#';
strcpy(&tmp[1], home->name);
key_store.getAutoKeyFor(home->id, tmp, key);
}
pkt->transport_codes[0] = key.calcTransportCode(pkt);
pkt->transport_codes[1] = 0;
pkt->header = (pkt->header & ~PH_ROUTE_MASK) | ROUTE_TYPE_TRANSPORT_FLOOD;

recv_pkt_region = home;
} else if (region_map.getWildcard().flags & REGION_DENY_FLOOD) {
recv_pkt_region = NULL;
} else {
recv_pkt_region = &region_map.getWildcard();
recv_pkt_region = &region_map.getWildcard();
}
} else {
recv_pkt_region = NULL;
Expand Down Expand Up @@ -871,6 +902,8 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
_prefs.advert_interval = 1; // default to 2 minutes for NEW installs
_prefs.flood_advert_interval = 12; // 12 hours
_prefs.flood_max = 64;
_prefs.region_autotag = 0; // opt-in, default off
_prefs.region_autotag_max_hops = 1; // only tag zero-hop / 1-hop packets by default
_prefs.interference_threshold = 0; // disabled

// bridge defaults
Expand Down
2 changes: 2 additions & 0 deletions src/Packet.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ namespace mesh {
#define ROUTE_TYPE_DIRECT 0x02 // direct route, 'path' is supplied
#define ROUTE_TYPE_TRANSPORT_DIRECT 0x03 // direct route + transport codes

#define TRANSPORT_CODE_ALL 0xFFFF // special transport code: forward to all regions
Copy link
Copy Markdown

@awhite2000 awhite2000 Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Client apps will need to support sending packets with this code, which is different from sending without a scope. @liamcottle

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I had hoped that maybe this could be implemented in the client apps. Perhaps a one-time toggle for a message which then reverts? Just to gently encourage people not to just scope every message to all

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm against '0xFFFF'; that can way to easily be abused to DDOS the whole network. I'd say a large region can decide on their own whether to agree on a region wide flood code for emergencies or not.


#define PAYLOAD_TYPE_REQ 0x00 // request (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob)
#define PAYLOAD_TYPE_RESPONSE 0x01 // response to REQ or ANON_REQ (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob)
#define PAYLOAD_TYPE_TXT_MSG 0x02 // a plain text message (prefixed with dest/src hashes, MAC) (enc data: timestamp, text)
Expand Down
25 changes: 25 additions & 0 deletions src/helpers/CommonCLI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
file.read((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
file.read((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
file.read((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170
file.read((uint8_t *)&_prefs->region_autotag, sizeof(_prefs->region_autotag)); // 290
file.read((uint8_t *)&_prefs->region_autotag_max_hops, sizeof(_prefs->region_autotag_max_hops)); // 291
// next: 292
file.read((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); // 290
// next: 291

Expand Down Expand Up @@ -118,6 +121,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {

// sanitise settings
_prefs->rx_boosted_gain = constrain(_prefs->rx_boosted_gain, 0, 1); // boolean
_prefs->region_autotag = constrain(_prefs->region_autotag, 0, 1); // boolean
_prefs->region_autotag_max_hops = constrain(_prefs->region_autotag_max_hops, 0, REGION_AUTOTAG_MAX_HOPS_LIMIT);

file.close();
}
Expand Down Expand Up @@ -178,6 +183,9 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
file.write((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
file.write((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
file.write((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170
file.write((uint8_t *)&_prefs->region_autotag, sizeof(_prefs->region_autotag)); // 290
file.write((uint8_t *)&_prefs->region_autotag_max_hops, sizeof(_prefs->region_autotag_max_hops)); // 291
// next: 292
file.write((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); // 290
// next: 291

Expand Down Expand Up @@ -356,6 +364,10 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
*reply = 0; // set null terminator
} else if (memcmp(config, "path.hash.mode", 14) == 0) {
sprintf(reply, "> %d", (uint32_t)_prefs->path_hash_mode);
} else if (memcmp(config, "region.autotag.max.hops", 23) == 0) {
sprintf(reply, "> %d", (uint32_t)_prefs->region_autotag_max_hops);
} else if (memcmp(config, "region.autotag", 14) == 0) {
sprintf(reply, "> %s", _prefs->region_autotag ? "on" : "off");
} else if (memcmp(config, "loop.detect", 11) == 0) {
if (_prefs->loop_detect == LOOP_DETECT_OFF) {
strcpy(reply, "> off");
Expand Down Expand Up @@ -616,6 +628,19 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
*dp = 0;
savePrefs();
strcpy(reply, "OK");
} else if (memcmp(config, "region.autotag.max.hops ", 24) == 0) {
int h = atoi(&config[24]);
if (h >= 0 && h <= REGION_AUTOTAG_MAX_HOPS_LIMIT) {
_prefs->region_autotag_max_hops = (uint8_t)h;
savePrefs();
strcpy(reply, "OK");
} else {
sprintf(reply, "Error, range is 0-%d", REGION_AUTOTAG_MAX_HOPS_LIMIT);
}
} else if (memcmp(config, "region.autotag ", 15) == 0) {
_prefs->region_autotag = memcmp(&config[15], "on", 2) == 0;
savePrefs();
strcpy(reply, "OK");
} else if (memcmp(config, "path.hash.mode ", 15) == 0) {
config += 15;
uint8_t mode = atoi(config);
Expand Down
4 changes: 4 additions & 0 deletions src/helpers/CommonCLI.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
#define ADVERT_LOC_SHARE 1
#define ADVERT_LOC_PREFS 2

#define REGION_AUTOTAG_MAX_HOPS_LIMIT 8 // upper bound for region.autotag.max.hops pref

#define LOOP_DETECT_OFF 0
#define LOOP_DETECT_MINIMAL 1
#define LOOP_DETECT_MODERATE 2
Expand Down Expand Up @@ -60,6 +62,8 @@ struct NodePrefs { // persisted to file
uint8_t rx_boosted_gain; // power settings
uint8_t path_hash_mode; // which path mode to use when sending
uint8_t loop_detect;
uint8_t region_autotag; // boolean: auto-tag untagged flood packets with home region's transport code
uint8_t region_autotag_max_hops; // only auto-tag packets received with pathHashCount <= this value (0 = zero-hop only)
};

class CommonCLICallbacks {
Expand Down