Enable network direct dial by default

master
Simon 3 weeks ago
parent 2fa05ae0a3
commit 923069deee

@ -61,7 +61,7 @@ SINGLE_MODE: True
VOICE_IDENT: True VOICE_IDENT: True
DIAL_A_TG: True DIAL_A_TG: True
DYNAMIC_TG_ROUTING: True DYNAMIC_TG_ROUTING: True
NETWORK_DIRECT_DIAL_SLOT: 0 NETWORK_DIRECT_DIAL_SLOT: 1
TS1_STATIC: TS1_STATIC:
TS2_STATIC: TS2_STATIC:
DEFAULT_DIAL_TS1: 0 DEFAULT_DIAL_TS1: 0

@ -275,7 +275,7 @@ def activate_network_direct_dial(_dst_id, pkt_time):
if CONFIG['SYSTEMS'][system]['MODE'] != 'MASTER': if CONFIG['SYSTEMS'][system]['MODE'] != 'MASTER':
continue continue
slot = CONFIG['SYSTEMS'][system].get('NETWORK_DIRECT_DIAL_SLOT', 0) slot = CONFIG['SYSTEMS'][system].get('NETWORK_DIRECT_DIAL_SLOT', 1)
if slot not in (1, 2): if slot not in (1, 2):
continue continue
@ -1516,7 +1516,7 @@ def options_config():
if 'DYNAMIC_TG_ROUTING' not in _options: if 'DYNAMIC_TG_ROUTING' not in _options:
_options['DYNAMIC_TG_ROUTING'] = int(CONFIG['SYSTEMS'][_system].get('DYNAMIC_TG_ROUTING', True)) _options['DYNAMIC_TG_ROUTING'] = int(CONFIG['SYSTEMS'][_system].get('DYNAMIC_TG_ROUTING', True))
if 'NETWORK_DIRECT_DIAL_SLOT' not in _options: if 'NETWORK_DIRECT_DIAL_SLOT' not in _options:
_options['NETWORK_DIRECT_DIAL_SLOT'] = CONFIG['SYSTEMS'][_system].get('NETWORK_DIRECT_DIAL_SLOT', 0) _options['NETWORK_DIRECT_DIAL_SLOT'] = CONFIG['SYSTEMS'][_system].get('NETWORK_DIRECT_DIAL_SLOT', 1)
_default_dial_ts1 = parse_default_reflector_option(_options['DEFAULT_DIAL_TS1'], default=None) _default_dial_ts1 = parse_default_reflector_option(_options['DEFAULT_DIAL_TS1'], default=None)
if _default_dial_ts1 is None: if _default_dial_ts1 is None:
@ -1590,7 +1590,7 @@ def options_config():
_network_direct_dial_slot = parse_network_direct_dial_slot(_options['NETWORK_DIRECT_DIAL_SLOT'], default=None) _network_direct_dial_slot = parse_network_direct_dial_slot(_options['NETWORK_DIRECT_DIAL_SLOT'], default=None)
if _network_direct_dial_slot is None: if _network_direct_dial_slot is None:
logger.debug('(OPTIONS) %s - NETWORK_DIRECT_DIAL_SLOT is not 0, 1, or 2, ignoring',_system) logger.debug('(OPTIONS) %s - NETWORK_DIRECT_DIAL_SLOT is not 0, 1, or 2, ignoring',_system)
_network_direct_dial_slot = CONFIG['SYSTEMS'][_system].get('NETWORK_DIRECT_DIAL_SLOT', 0) _network_direct_dial_slot = CONFIG['SYSTEMS'][_system].get('NETWORK_DIRECT_DIAL_SLOT', 1)
if 'TS1_STATIC' not in _options or 'TS2_STATIC' not in _options or 'DEFAULT_DIAL_TS1' not in _options or 'DEFAULT_DIAL_TS2' not in _options or 'DEFAULT_UA_TIMER' not in _options: if 'TS1_STATIC' not in _options or 'TS2_STATIC' not in _options or 'DEFAULT_DIAL_TS1' not in _options or 'DEFAULT_DIAL_TS2' not in _options or 'DEFAULT_UA_TIMER' not in _options:
logger.debug('(OPTIONS) %s - Required field missing, ignoring',_system) logger.debug('(OPTIONS) %s - Required field missing, ignoring',_system)
@ -1653,7 +1653,7 @@ def options_config():
if _dynamic_tg_routing != CONFIG['SYSTEMS'][_system].get('DYNAMIC_TG_ROUTING', True): if _dynamic_tg_routing != CONFIG['SYSTEMS'][_system].get('DYNAMIC_TG_ROUTING', True):
logger.debug('(OPTIONS) %s DYNAMIC_TG_ROUTING changed to %s',_system,_dynamic_tg_routing) logger.debug('(OPTIONS) %s DYNAMIC_TG_ROUTING changed to %s',_system,_dynamic_tg_routing)
if _network_direct_dial_slot != CONFIG['SYSTEMS'][_system].get('NETWORK_DIRECT_DIAL_SLOT', 0): if _network_direct_dial_slot != CONFIG['SYSTEMS'][_system].get('NETWORK_DIRECT_DIAL_SLOT', 1):
logger.debug('(OPTIONS) %s NETWORK_DIRECT_DIAL_SLOT changed to %s',_system,_network_direct_dial_slot) logger.debug('(OPTIONS) %s NETWORK_DIRECT_DIAL_SLOT changed to %s',_system,_network_direct_dial_slot)
ts1 = [] ts1 = []

@ -49,14 +49,14 @@ logger = logging.getLogger(__name__)
def config_network_direct_dial_slot(config, section): def config_network_direct_dial_slot(config, section):
try: try:
slot = config.getint(section, 'NETWORK_DIRECT_DIAL_SLOT', fallback=0) slot = config.getint(section, 'NETWORK_DIRECT_DIAL_SLOT', fallback=1)
except ValueError: except ValueError:
logger.warning('(%s) NETWORK_DIRECT_DIAL_SLOT is not an integer, defaulting to 0', section) logger.warning('(%s) NETWORK_DIRECT_DIAL_SLOT is not an integer, defaulting to 1', section)
return 0 return 1
if slot not in (0, 1, 2): if slot not in (0, 1, 2):
logger.warning('(%s) NETWORK_DIRECT_DIAL_SLOT is not 0, 1, or 2, defaulting to 0', section) logger.warning('(%s) NETWORK_DIRECT_DIAL_SLOT is not 0, 1, or 2, defaulting to 1', section)
return 0 return 1
return slot return slot
def master_timer_config(config, section): def master_timer_config(config, section):

@ -61,7 +61,7 @@ SINGLE_MODE: True
VOICE_IDENT: True VOICE_IDENT: True
DIAL_A_TG: True DIAL_A_TG: True
DYNAMIC_TG_ROUTING: True DYNAMIC_TG_ROUTING: True
NETWORK_DIRECT_DIAL_SLOT: 0 NETWORK_DIRECT_DIAL_SLOT: 1
TS1_STATIC: TS1_STATIC:
TS2_STATIC: TS2_STATIC:
DEFAULT_DIAL_TS1: 0 DEFAULT_DIAL_TS1: 0

@ -16,12 +16,12 @@ Findings:
routing policy knob for dynamic/dial behavior. routing policy knob for dynamic/dial behavior.
Assumptions: Assumptions:
- `NETWORK_DIRECT_DIAL_SLOT=0` means disabled; `1` presents TG9 TS1; `2` - `NETWORK_DIRECT_DIAL_SLOT=1` is the default and presents TG9 TS1; `0` means
presents TG9 TS2 for DMO/simplex clients. disabled; `2` presents TG9 TS2 for DMO/simplex clients.
- Network direct dial is client/session-overridable through `OPTIONS`; the - Network direct dial is client/session-overridable through `OPTIONS`; the
compact alias is `NDD_SLOT`. compact alias is `NDD_SLOT`.
- RF-originated traffic must not activate network direct dial. - RF-originated traffic must not activate network direct dial.
- Invalid `NETWORK_DIRECT_DIAL_SLOT` values in config should fall back to `0` - Invalid `NETWORK_DIRECT_DIAL_SLOT` values in config should fall back to `1`
and log a warning; invalid OPTIONS values should leave the current session and log a warning; invalid OPTIONS values should leave the current session
value unchanged. value unchanged.

@ -100,7 +100,7 @@ NDD_SLOT=2
| `TS2_TG_TIMER` | Slot 2 conventional TG timeout in minutes. | | `TS2_TG_TIMER` | Slot 2 conventional TG timeout in minutes. |
| `TS1_DIAL_TIMER` | Slot 1 dial-a-TG timeout in minutes. | | `TS1_DIAL_TIMER` | Slot 1 dial-a-TG timeout in minutes. |
| `TS2_DIAL_TIMER` | Slot 2 dial-a-TG timeout in minutes. | | `TS2_DIAL_TIMER` | Slot 2 dial-a-TG timeout in minutes. |
| `NETWORK_DIRECT_DIAL_SLOT` | Network direct dial slot. `0` disables it, `1` presents on TG9 TS1, `2` presents on TG9 TS2. | | `NETWORK_DIRECT_DIAL_SLOT` | Network direct dial slot. Default is `1`. `0` disables it, `1` presents on TG9 TS1, `2` presents on TG9 TS2. |
| `DIALTG` | Enable/disable dial-a-TG private-call control. Use `1` or `0`. | | `DIALTG` | Enable/disable dial-a-TG private-call control. Use `1` or `0`. |
| `DYNAMIC` | Enable/disable automatic dynamic conventional TG routing. Use `1` or `0`. | | `DYNAMIC` | Enable/disable automatic dynamic conventional TG routing. Use `1` or `0`. |
| `VOICE` | Enable/disable voice ident. Use `1` or `0`. | | `VOICE` | Enable/disable voice ident. Use `1` or `0`. |
@ -160,7 +160,7 @@ KEY=secret;TS1=91,92;TS2=235;DIAL1=4400;DIAL2=2350;TGTO1=10;TGTO2=10;DIALTO1=30;
configured slot. It does not enable general private voice routing. configured slot. It does not enable general private voice routing.
- Network direct dial does not override an active or hangtime user-selected - Network direct dial does not override an active or hangtime user-selected
dial-a-TG route. dial-a-TG route.
- Invalid `NETWORK_DIRECT_DIAL_SLOT` values in server config fall back to `0` - Invalid `NETWORK_DIRECT_DIAL_SLOT` values in server config fall back to `1`
and are logged. Invalid `NDD_SLOT`/`NETWORK_DIRECT_DIAL_SLOT` values in and are logged. Invalid `NDD_SLOT`/`NETWORK_DIRECT_DIAL_SLOT` values in
client `OPTIONS` are ignored and the current session value remains in use. client `OPTIONS` are ignored and the current session value remains in use.
- `DEFAULT_REFLECTOR`, `DIAL`, and `StartRef` only affect the slot-2 default. - `DEFAULT_REFLECTOR`, `DIAL`, and `StartRef` only affect the slot-2 default.

@ -146,9 +146,10 @@ for rule timeout checks without sleeping.
- Network direct dial behaviour: when `NETWORK_DIRECT_DIAL_SLOT`/`NDD_SLOT` is - Network direct dial behaviour: when `NETWORK_DIRECT_DIAL_SLOT`/`NDD_SLOT` is
set to `1` or `2`, network-originated group traffic to the connected set to `1` or `2`, network-originated group traffic to the connected
repeater/hotspot ID may activate local TG9 on that slot. Tests must verify it repeater/hotspot ID may activate local TG9 on that slot. Tests must verify it
is disabled by default, does not affect RF-originated traffic, does not defaults to TS1, can be explicitly disabled with `0`, does not affect
override active/hangtime user-selected dial-a-TG state, and uses the existing RF-originated traffic, does not override active/hangtime user-selected
TG9 rewrite path rather than a new packet mutation path. dial-a-TG state, and uses the existing TG9 rewrite path rather than a new
packet mutation path.
- Static TG configuration bugs: startup and live options reload should reject - Static TG configuration bugs: startup and live options reload should reject
prohibited local/control TGs consistently on both TS1 and TS2 after parsing the prohibited local/control TGs consistently on both TS1 and TS2 after parsing the
configured TG strings to integers. Invalid IDs at or above `16777215` should configured TG strings to integers. Invalid IDs at or above `16777215` should

@ -91,12 +91,13 @@ Invalid specific timer fields are logged and ignored so valid broader timer
fields still apply. Voice ident override coverage verifies valid fields still apply. Voice ident override coverage verifies valid
override TGs are used, empty/false overrides use all-call, and malformed, override TGs are used, empty/false overrides use all-call, and malformed,
control, or all-call override values are logged and fall back to all-call. control, or all-call override values are logged and fall back to all-call.
Network direct dial coverage verifies the feature is disabled by default, Network direct dial coverage verifies the feature defaults to TS1, can be
routes network-originated calls to a connected repeater/hotspot ID as RF-visible explicitly disabled, routes network-originated calls to a connected
TG9 on the configured slot, supports TS2 for DMO/simplex clients, does not repeater/hotspot ID as RF-visible TG9 on the configured slot, supports TS2 for
override a user-selected dial-a-TG route, can replace an idle configured default DMO/simplex clients, does not override a user-selected dial-a-TG route, can
dial route, and is not activated by RF-originated traffic. OPTIONS coverage also replace an idle configured default dial route, and is not activated by
verifies compact aliases and overlength `OPTIONS` rejection. RF-originated traffic. OPTIONS coverage also verifies compact aliases and
overlength `OPTIONS` rejection.
Generated voice prompt helper coverage verifies prompt stream state is attached Generated voice prompt helper coverage verifies prompt stream state is attached
to the router instance sending the prompt, even if a stale module-level to the router instance sending the prompt, even if a stale module-level
`system` variable names another master. This protects dial-a-TG prompts, idents, `system` variable names another master. This protects dial-a-TG prompts, idents,

@ -30,7 +30,7 @@
acknowledged but ignored so clients do not reconnect-loop and oversized acknowledged but ignored so clients do not reconnect-loop and oversized
strings do not enter session state. strings do not enter session state.
- Added startup validation for `NETWORK_DIRECT_DIAL_SLOT`; invalid values fall - Added startup validation for `NETWORK_DIRECT_DIAL_SLOT`; invalid values fall
back to disabled (`0`) and are logged. back to the default TS1 setting (`1`) and are logged.
## Dial-a-TG ## Dial-a-TG
@ -47,10 +47,13 @@
- Preserved the current FreeDMR dial-a-TG policy cap of `999999`. - Preserved the current FreeDMR dial-a-TG policy cap of `999999`.
- Ensured FBP route targets created for dial-a-TG remain active across local - Ensured FBP route targets created for dial-a-TG remain active across local
retunes/disconnects, in line with the mesh "everything everywhere" model. retunes/disconnects, in line with the mesh "everything everywhere" model.
- Added opt-in network direct dial: a network-originated group call to a - Added network direct dial: a network-originated group call to a
connected repeater/hotspot's own ID can be presented locally as TG9 on the connected repeater/hotspot's own ID can be presented locally as TG9 on the
configured slot. This is configured with `NETWORK_DIRECT_DIAL_SLOT` or configured slot. This is configured with `NETWORK_DIRECT_DIAL_SLOT` or
`NDD_SLOT` (`0` disabled, `1` TG9 TS1, `2` TG9 TS2). `NDD_SLOT` (`0` disabled, `1` TG9 TS1, `2` TG9 TS2).
- Changed network direct dial to default to enabled on TG9 TS1. Sysops can set
`NETWORK_DIRECT_DIAL_SLOT: 0`, or clients can send `NDD_SLOT=0`, to disable it
for a session.
- Network direct dial is a deliberate, narrow exception where network traffic - Network direct dial is a deliberate, narrow exception where network traffic
may activate the local RF TG9 presentation path. It does not override an may activate the local RF TG9 presentation path. It does not override an
active or hangtime user-selected dial-a-TG route and does not apply to active or hangtime user-selected dial-a-TG route and does not apply to

@ -61,7 +61,7 @@ SINGLE_MODE: True
VOICE_IDENT: True VOICE_IDENT: True
DIAL_A_TG: True DIAL_A_TG: True
DYNAMIC_TG_ROUTING: True DYNAMIC_TG_ROUTING: True
NETWORK_DIRECT_DIAL_SLOT: 0 NETWORK_DIRECT_DIAL_SLOT: 1
TS1_STATIC: TS1_STATIC:
TS2_STATIC: TS2_STATIC:
DEFAULT_DIAL_TS1: 0 DEFAULT_DIAL_TS1: 0

@ -315,7 +315,7 @@ def minimal_config(system_names: tuple[str, ...] = ("MASTER-A", "MASTER-B")) ->
"VOICE_IDENT": False, "VOICE_IDENT": False,
"DIAL_A_TG": True, "DIAL_A_TG": True,
"DYNAMIC_TG_ROUTING": True, "DYNAMIC_TG_ROUTING": True,
"NETWORK_DIRECT_DIAL_SLOT": 0, "NETWORK_DIRECT_DIAL_SLOT": 1,
"TS1_STATIC": "", "TS1_STATIC": "",
"TS2_STATIC": "", "TS2_STATIC": "",
"DEFAULT_DIAL_TS1": 0, "DEFAULT_DIAL_TS1": 0,

@ -469,7 +469,7 @@ def write_bridge_master_config(
ts2_static: str = "91", ts2_static: str = "91",
dial_a_tg: bool = True, dial_a_tg: bool = True,
dynamic_tg_routing: bool = True, dynamic_tg_routing: bool = True,
network_direct_dial_slot: int = 0, network_direct_dial_slot: int = 1,
master_extra_config: str = "", master_extra_config: str = "",
) -> None: ) -> None:
global_use_acl_text = "True" if global_use_acl else "False" global_use_acl_text = "True" if global_use_acl else "False"
@ -976,7 +976,7 @@ class UdpBlackBoxScenario:
ts2_static: str = "91", ts2_static: str = "91",
dial_a_tg: bool = True, dial_a_tg: bool = True,
dynamic_tg_routing: bool = True, dynamic_tg_routing: bool = True,
network_direct_dial_slot: int = 0, network_direct_dial_slot: int = 1,
master_extra_config: str = "", master_extra_config: str = "",
fbp_systems: dict[str, int] | None = None, fbp_systems: dict[str, int] | None = None,
fbp_proto_versions: dict[str, int] | None = None, fbp_proto_versions: dict[str, int] | None = None,

@ -84,7 +84,7 @@ DEFAULT_REFLECTOR: 0
self.assertEqual(parsed["ALIASES"]["STALE_TIME"], 86400) self.assertEqual(parsed["ALIASES"]["STALE_TIME"], 86400)
self.assertTrue(parsed["SYSTEMS"]["MASTER-A"]["DIAL_A_TG"]) self.assertTrue(parsed["SYSTEMS"]["MASTER-A"]["DIAL_A_TG"])
self.assertTrue(parsed["SYSTEMS"]["MASTER-A"]["DYNAMIC_TG_ROUTING"]) self.assertTrue(parsed["SYSTEMS"]["MASTER-A"]["DYNAMIC_TG_ROUTING"])
self.assertEqual(parsed["SYSTEMS"]["MASTER-A"]["NETWORK_DIRECT_DIAL_SLOT"], 0) self.assertEqual(parsed["SYSTEMS"]["MASTER-A"]["NETWORK_DIRECT_DIAL_SLOT"], 1)
self.assertEqual(parsed["SYSTEMS"]["MASTER-A"]["DEFAULT_DIAL_TS1"], 0) self.assertEqual(parsed["SYSTEMS"]["MASTER-A"]["DEFAULT_DIAL_TS1"], 0)
self.assertEqual(parsed["SYSTEMS"]["MASTER-A"]["DEFAULT_DIAL_TS2"], 0) self.assertEqual(parsed["SYSTEMS"]["MASTER-A"]["DEFAULT_DIAL_TS2"], 0)
@ -142,7 +142,7 @@ DEFAULT_DIAL_TS2: 92
self.assertEqual(parsed["SYSTEMS"]["MASTER-A"]["DEFAULT_DIAL_TS2"], 92) self.assertEqual(parsed["SYSTEMS"]["MASTER-A"]["DEFAULT_DIAL_TS2"], 92)
self.assertEqual(parsed["SYSTEMS"]["MASTER-A"]["DEFAULT_REFLECTOR"], 92) self.assertEqual(parsed["SYSTEMS"]["MASTER-A"]["DEFAULT_REFLECTOR"], 92)
def test_config_invalid_network_direct_dial_slot_defaults_to_disabled(self): def test_config_invalid_network_direct_dial_slot_defaults_to_ts1(self):
for value in ("3", "A"): for value in ("3", "A"):
with self.subTest(value=value): with self.subTest(value=value):
config_text = f""" config_text = f"""
@ -168,7 +168,7 @@ NETWORK_DIRECT_DIAL_SLOT: {value}
finally: finally:
os.unlink(path) os.unlink(path)
self.assertEqual(parsed["SYSTEMS"]["MASTER-A"]["NETWORK_DIRECT_DIAL_SLOT"], 0) self.assertEqual(parsed["SYSTEMS"]["MASTER-A"]["NETWORK_DIRECT_DIAL_SLOT"], 1)
self.assertIn("NETWORK_DIRECT_DIAL_SLOT", "\n".join(logs.output)) self.assertIn("NETWORK_DIRECT_DIAL_SLOT", "\n".join(logs.output))
def test_set_alias_updates_bridge_master_globals_and_shared_hblink_config(self): def test_set_alias_updates_bridge_master_globals_and_shared_hblink_config(self):
@ -1342,8 +1342,9 @@ NETWORK_DIRECT_DIAL_SLOT: {value}
self.assertEqual(fbp_entry["TO_TYPE"], "NONE") self.assertEqual(fbp_entry["TO_TYPE"], "NONE")
self.assertEqual(scenario.capture.packets, []) self.assertEqual(scenario.capture.packets, [])
def test_network_direct_dial_is_disabled_by_default(self): def test_network_direct_dial_can_be_explicitly_disabled(self):
config = minimal_config(("MASTER-A",)) config = minimal_config(("MASTER-A",))
config["SYSTEMS"]["MASTER-A"]["NETWORK_DIRECT_DIAL_SLOT"] = 0
add_openbridge_system(config, "OBP-1", network_id=3001) add_openbridge_system(config, "OBP-1", network_id=3001)
with DeterministicScenario(config=config) as scenario: with DeterministicScenario(config=config) as scenario:
@ -1355,9 +1356,8 @@ NETWORK_DIRECT_DIAL_SLOT: {value}
self.assertNotIn("#1001", scenario.bridge_state) self.assertNotIn("#1001", scenario.bridge_state)
self.assertEqual(scenario.capture.for_system("MASTER-A"), []) self.assertEqual(scenario.capture.for_system("MASTER-A"), [])
def test_network_direct_dial_routes_repeater_id_to_tg9_on_configured_slot(self): def test_network_direct_dial_routes_repeater_id_to_tg9_on_default_ts1(self):
config = minimal_config(("MASTER-A",)) config = minimal_config(("MASTER-A",))
config["SYSTEMS"]["MASTER-A"]["NETWORK_DIRECT_DIAL_SLOT"] = 1
add_openbridge_system(config, "OBP-1", network_id=3001) add_openbridge_system(config, "OBP-1", network_id=3001)
with DeterministicScenario(config=config) as scenario: with DeterministicScenario(config=config) as scenario:

Loading…
Cancel
Save

Powered by TurnKey Linux.