ReticulumTelemetryHub 0.1.0__py3-none-any.whl → 0.143.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- reticulum_telemetry_hub/api/__init__.py +23 -0
- reticulum_telemetry_hub/api/models.py +323 -0
- reticulum_telemetry_hub/api/service.py +836 -0
- reticulum_telemetry_hub/api/storage.py +528 -0
- reticulum_telemetry_hub/api/storage_base.py +156 -0
- reticulum_telemetry_hub/api/storage_models.py +118 -0
- reticulum_telemetry_hub/atak_cot/__init__.py +49 -0
- reticulum_telemetry_hub/atak_cot/base.py +277 -0
- reticulum_telemetry_hub/atak_cot/chat.py +506 -0
- reticulum_telemetry_hub/atak_cot/detail.py +235 -0
- reticulum_telemetry_hub/atak_cot/event.py +181 -0
- reticulum_telemetry_hub/atak_cot/pytak_client.py +569 -0
- reticulum_telemetry_hub/atak_cot/tak_connector.py +848 -0
- reticulum_telemetry_hub/config/__init__.py +25 -0
- reticulum_telemetry_hub/config/constants.py +7 -0
- reticulum_telemetry_hub/config/manager.py +515 -0
- reticulum_telemetry_hub/config/models.py +215 -0
- reticulum_telemetry_hub/embedded_lxmd/__init__.py +5 -0
- reticulum_telemetry_hub/embedded_lxmd/embedded.py +418 -0
- reticulum_telemetry_hub/internal_api/__init__.py +21 -0
- reticulum_telemetry_hub/internal_api/bus.py +344 -0
- reticulum_telemetry_hub/internal_api/core.py +690 -0
- reticulum_telemetry_hub/internal_api/v1/__init__.py +74 -0
- reticulum_telemetry_hub/internal_api/v1/enums.py +109 -0
- reticulum_telemetry_hub/internal_api/v1/manifest.json +8 -0
- reticulum_telemetry_hub/internal_api/v1/schemas.py +478 -0
- reticulum_telemetry_hub/internal_api/versioning.py +63 -0
- reticulum_telemetry_hub/lxmf_daemon/Handlers.py +122 -0
- reticulum_telemetry_hub/lxmf_daemon/LXMF.py +252 -0
- reticulum_telemetry_hub/lxmf_daemon/LXMPeer.py +898 -0
- reticulum_telemetry_hub/lxmf_daemon/LXMRouter.py +4227 -0
- reticulum_telemetry_hub/lxmf_daemon/LXMessage.py +1006 -0
- reticulum_telemetry_hub/lxmf_daemon/LXStamper.py +490 -0
- reticulum_telemetry_hub/lxmf_daemon/__init__.py +10 -0
- reticulum_telemetry_hub/lxmf_daemon/_version.py +1 -0
- reticulum_telemetry_hub/lxmf_daemon/lxmd.py +1655 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/fields/field_telemetry_stream.py +6 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/__init__.py +3 -0
- {lxmf_telemetry → reticulum_telemetry_hub/lxmf_telemetry}/model/persistance/appearance.py +19 -19
- {lxmf_telemetry → reticulum_telemetry_hub/lxmf_telemetry}/model/persistance/peer.py +17 -13
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/__init__.py +65 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/acceleration.py +68 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/ambient_light.py +37 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/angular_velocity.py +68 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/battery.py +68 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/connection_map.py +258 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/generic.py +841 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/gravity.py +68 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/humidity.py +37 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/information.py +42 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/location.py +110 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/lxmf_propagation.py +429 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/magnetic_field.py +68 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/physical_link.py +53 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/pressure.py +37 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/proximity.py +37 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/received.py +75 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/rns_transport.py +209 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/sensor.py +65 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/sensor_enum.py +27 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/sensor_mapping.py +58 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/temperature.py +37 -0
- {lxmf_telemetry → reticulum_telemetry_hub/lxmf_telemetry}/model/persistance/sensors/time.py +36 -32
- {lxmf_telemetry → reticulum_telemetry_hub/lxmf_telemetry}/model/persistance/telemeter.py +26 -23
- reticulum_telemetry_hub/lxmf_telemetry/sampler.py +229 -0
- reticulum_telemetry_hub/lxmf_telemetry/telemeter_manager.py +409 -0
- reticulum_telemetry_hub/lxmf_telemetry/telemetry_controller.py +804 -0
- reticulum_telemetry_hub/northbound/__init__.py +5 -0
- reticulum_telemetry_hub/northbound/app.py +195 -0
- reticulum_telemetry_hub/northbound/auth.py +119 -0
- reticulum_telemetry_hub/northbound/gateway.py +310 -0
- reticulum_telemetry_hub/northbound/internal_adapter.py +302 -0
- reticulum_telemetry_hub/northbound/models.py +213 -0
- reticulum_telemetry_hub/northbound/routes_chat.py +123 -0
- reticulum_telemetry_hub/northbound/routes_files.py +119 -0
- reticulum_telemetry_hub/northbound/routes_rest.py +345 -0
- reticulum_telemetry_hub/northbound/routes_subscribers.py +150 -0
- reticulum_telemetry_hub/northbound/routes_topics.py +178 -0
- reticulum_telemetry_hub/northbound/routes_ws.py +107 -0
- reticulum_telemetry_hub/northbound/serializers.py +72 -0
- reticulum_telemetry_hub/northbound/services.py +373 -0
- reticulum_telemetry_hub/northbound/websocket.py +855 -0
- reticulum_telemetry_hub/reticulum_server/__main__.py +2237 -0
- reticulum_telemetry_hub/reticulum_server/command_manager.py +1268 -0
- reticulum_telemetry_hub/reticulum_server/command_text.py +399 -0
- reticulum_telemetry_hub/reticulum_server/constants.py +1 -0
- reticulum_telemetry_hub/reticulum_server/event_log.py +357 -0
- reticulum_telemetry_hub/reticulum_server/internal_adapter.py +358 -0
- reticulum_telemetry_hub/reticulum_server/outbound_queue.py +312 -0
- reticulum_telemetry_hub/reticulum_server/services.py +422 -0
- reticulumtelemetryhub-0.143.0.dist-info/METADATA +181 -0
- reticulumtelemetryhub-0.143.0.dist-info/RECORD +97 -0
- {reticulumtelemetryhub-0.1.0.dist-info → reticulumtelemetryhub-0.143.0.dist-info}/WHEEL +1 -1
- reticulumtelemetryhub-0.143.0.dist-info/licenses/LICENSE +277 -0
- lxmf_telemetry/model/fields/field_telemetry_stream.py +0 -7
- lxmf_telemetry/model/persistance/__init__.py +0 -3
- lxmf_telemetry/model/persistance/sensors/location.py +0 -69
- lxmf_telemetry/model/persistance/sensors/magnetic_field.py +0 -36
- lxmf_telemetry/model/persistance/sensors/sensor.py +0 -44
- lxmf_telemetry/model/persistance/sensors/sensor_enum.py +0 -24
- lxmf_telemetry/model/persistance/sensors/sensor_mapping.py +0 -9
- lxmf_telemetry/telemetry_controller.py +0 -124
- reticulum_server/main.py +0 -182
- reticulumtelemetryhub-0.1.0.dist-info/METADATA +0 -15
- reticulumtelemetryhub-0.1.0.dist-info/RECORD +0 -19
- {lxmf_telemetry → reticulum_telemetry_hub}/__init__.py +0 -0
- {lxmf_telemetry/model/persistance/sensors → reticulum_telemetry_hub/lxmf_telemetry}/__init__.py +0 -0
- {reticulum_server → reticulum_telemetry_hub/reticulum_server}/__init__.py +0 -0
|
@@ -0,0 +1,1655 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# Reticulum License
|
|
4
|
+
#
|
|
5
|
+
# Copyright (c) 2020-2025 Mark Qvist
|
|
6
|
+
#
|
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
# furnished to do so, subject to the following conditions:
|
|
13
|
+
#
|
|
14
|
+
# - The Software shall not be used in any kind of system which includes amongst
|
|
15
|
+
# its functions the ability to purposefully do harm to human beings.
|
|
16
|
+
#
|
|
17
|
+
# - The Software shall not be used, directly or indirectly, in the creation of
|
|
18
|
+
# an artificial intelligence, machine learning or language model training
|
|
19
|
+
# dataset, including but not limited to any use that contributes to the
|
|
20
|
+
# training or development of such a model or algorithm.
|
|
21
|
+
#
|
|
22
|
+
# - The above copyright notice and this permission notice shall be included in
|
|
23
|
+
# all copies or substantial portions of the Software.
|
|
24
|
+
#
|
|
25
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
26
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
27
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
28
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
29
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
30
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
31
|
+
# SOFTWARE.
|
|
32
|
+
|
|
33
|
+
import argparse
|
|
34
|
+
import os
|
|
35
|
+
import shlex
|
|
36
|
+
import subprocess
|
|
37
|
+
import threading
|
|
38
|
+
import time
|
|
39
|
+
|
|
40
|
+
import RNS
|
|
41
|
+
from RNS.vendor.configobj import ConfigObj
|
|
42
|
+
|
|
43
|
+
from reticulum_telemetry_hub import lxmf_daemon as LXMF
|
|
44
|
+
from reticulum_telemetry_hub.config.manager import HubConfigurationManager
|
|
45
|
+
from reticulum_telemetry_hub.lxmf_daemon.LXMF import APP_NAME
|
|
46
|
+
from reticulum_telemetry_hub.lxmf_daemon._version import __version__
|
|
47
|
+
|
|
48
|
+
DEFFERED_JOBS_DELAY = 10
|
|
49
|
+
JOBS_INTERVAL = 5
|
|
50
|
+
|
|
51
|
+
configpath = None
|
|
52
|
+
ignoredpath = None
|
|
53
|
+
identitypath = None
|
|
54
|
+
storagedir = None
|
|
55
|
+
lxmdir = None
|
|
56
|
+
targetloglevel = None
|
|
57
|
+
|
|
58
|
+
identity = None
|
|
59
|
+
lxmd_config = None
|
|
60
|
+
message_router = None
|
|
61
|
+
lxmf_destination = None
|
|
62
|
+
active_configuration = {}
|
|
63
|
+
|
|
64
|
+
last_peer_announce = None
|
|
65
|
+
last_node_announce = None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _default_config_directory() -> str:
|
|
69
|
+
"""Return the lxmd configuration directory within the hub storage path."""
|
|
70
|
+
|
|
71
|
+
storage_path = HubConfigurationManager().storage_path
|
|
72
|
+
return str(storage_path / "lxmd")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def create_default_config(configpath):
|
|
76
|
+
lxmd_config = ConfigObj(__default_lxmd_config__.splitlines())
|
|
77
|
+
lxmd_config.filename = configpath
|
|
78
|
+
lxmd_config.write()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def apply_config():
|
|
82
|
+
global active_configuration, targetloglevel
|
|
83
|
+
try:
|
|
84
|
+
# Load peer settings
|
|
85
|
+
if "lxmf" in lxmd_config and "display_name" in lxmd_config["lxmf"]:
|
|
86
|
+
active_configuration["display_name"] = lxmd_config["lxmf"]["display_name"]
|
|
87
|
+
else:
|
|
88
|
+
active_configuration["display_name"] = "Anonymous Peer"
|
|
89
|
+
|
|
90
|
+
if "lxmf" in lxmd_config and "announce_at_start" in lxmd_config["lxmf"]:
|
|
91
|
+
active_configuration["peer_announce_at_start"] = lxmd_config[
|
|
92
|
+
"lxmf"
|
|
93
|
+
].as_bool("announce_at_start")
|
|
94
|
+
else:
|
|
95
|
+
active_configuration["peer_announce_at_start"] = False
|
|
96
|
+
|
|
97
|
+
if "lxmf" in lxmd_config and "announce_interval" in lxmd_config["lxmf"]:
|
|
98
|
+
active_configuration["peer_announce_interval"] = (
|
|
99
|
+
lxmd_config["lxmf"].as_int("announce_interval") * 60
|
|
100
|
+
)
|
|
101
|
+
else:
|
|
102
|
+
active_configuration["peer_announce_interval"] = None
|
|
103
|
+
|
|
104
|
+
if (
|
|
105
|
+
"lxmf" in lxmd_config
|
|
106
|
+
and "delivery_transfer_max_accepted_size" in lxmd_config["lxmf"]
|
|
107
|
+
):
|
|
108
|
+
active_configuration["delivery_transfer_max_accepted_size"] = lxmd_config[
|
|
109
|
+
"lxmf"
|
|
110
|
+
].as_float("delivery_transfer_max_accepted_size")
|
|
111
|
+
if active_configuration["delivery_transfer_max_accepted_size"] < 0.38:
|
|
112
|
+
active_configuration["delivery_transfer_max_accepted_size"] = 0.38
|
|
113
|
+
else:
|
|
114
|
+
active_configuration["delivery_transfer_max_accepted_size"] = 1000
|
|
115
|
+
|
|
116
|
+
if "lxmf" in lxmd_config and "on_inbound" in lxmd_config["lxmf"]:
|
|
117
|
+
active_configuration["on_inbound"] = lxmd_config["lxmf"]["on_inbound"]
|
|
118
|
+
else:
|
|
119
|
+
active_configuration["on_inbound"] = None
|
|
120
|
+
|
|
121
|
+
# Load propagation node settings
|
|
122
|
+
if "propagation" in lxmd_config and "enable_node" in lxmd_config["propagation"]:
|
|
123
|
+
active_configuration["enable_propagation_node"] = lxmd_config[
|
|
124
|
+
"propagation"
|
|
125
|
+
].as_bool("enable_node")
|
|
126
|
+
else:
|
|
127
|
+
active_configuration["enable_propagation_node"] = False
|
|
128
|
+
|
|
129
|
+
if "propagation" in lxmd_config and "node_name" in lxmd_config["propagation"]:
|
|
130
|
+
active_configuration["node_name"] = lxmd_config["propagation"].get(
|
|
131
|
+
"node_name"
|
|
132
|
+
)
|
|
133
|
+
else:
|
|
134
|
+
active_configuration["node_name"] = None
|
|
135
|
+
|
|
136
|
+
if (
|
|
137
|
+
"propagation" in lxmd_config
|
|
138
|
+
and "auth_required" in lxmd_config["propagation"]
|
|
139
|
+
):
|
|
140
|
+
active_configuration["auth_required"] = lxmd_config["propagation"].as_bool(
|
|
141
|
+
"auth_required"
|
|
142
|
+
)
|
|
143
|
+
else:
|
|
144
|
+
active_configuration["auth_required"] = False
|
|
145
|
+
|
|
146
|
+
if (
|
|
147
|
+
"propagation" in lxmd_config
|
|
148
|
+
and "announce_at_start" in lxmd_config["propagation"]
|
|
149
|
+
):
|
|
150
|
+
active_configuration["node_announce_at_start"] = lxmd_config[
|
|
151
|
+
"propagation"
|
|
152
|
+
].as_bool("announce_at_start")
|
|
153
|
+
else:
|
|
154
|
+
active_configuration["node_announce_at_start"] = False
|
|
155
|
+
|
|
156
|
+
if "propagation" in lxmd_config and "autopeer" in lxmd_config["propagation"]:
|
|
157
|
+
active_configuration["autopeer"] = lxmd_config["propagation"].as_bool(
|
|
158
|
+
"autopeer"
|
|
159
|
+
)
|
|
160
|
+
else:
|
|
161
|
+
active_configuration["autopeer"] = True
|
|
162
|
+
|
|
163
|
+
if (
|
|
164
|
+
"propagation" in lxmd_config
|
|
165
|
+
and "autopeer_maxdepth" in lxmd_config["propagation"]
|
|
166
|
+
):
|
|
167
|
+
active_configuration["autopeer_maxdepth"] = lxmd_config[
|
|
168
|
+
"propagation"
|
|
169
|
+
].as_int("autopeer_maxdepth")
|
|
170
|
+
else:
|
|
171
|
+
active_configuration["autopeer_maxdepth"] = None
|
|
172
|
+
|
|
173
|
+
if (
|
|
174
|
+
"propagation" in lxmd_config
|
|
175
|
+
and "announce_interval" in lxmd_config["propagation"]
|
|
176
|
+
):
|
|
177
|
+
active_configuration["node_announce_interval"] = (
|
|
178
|
+
lxmd_config["propagation"].as_int("announce_interval") * 60
|
|
179
|
+
)
|
|
180
|
+
else:
|
|
181
|
+
active_configuration["node_announce_interval"] = None
|
|
182
|
+
|
|
183
|
+
if (
|
|
184
|
+
"propagation" in lxmd_config
|
|
185
|
+
and "message_storage_limit" in lxmd_config["propagation"]
|
|
186
|
+
):
|
|
187
|
+
active_configuration["message_storage_limit"] = lxmd_config[
|
|
188
|
+
"propagation"
|
|
189
|
+
].as_float("message_storage_limit")
|
|
190
|
+
if active_configuration["message_storage_limit"] < 0.005:
|
|
191
|
+
active_configuration["message_storage_limit"] = 0.005
|
|
192
|
+
else:
|
|
193
|
+
active_configuration["message_storage_limit"] = 500
|
|
194
|
+
|
|
195
|
+
if (
|
|
196
|
+
"propagation" in lxmd_config
|
|
197
|
+
and "propagation_transfer_max_accepted_size" in lxmd_config["propagation"]
|
|
198
|
+
):
|
|
199
|
+
active_configuration["propagation_transfer_max_accepted_size"] = (
|
|
200
|
+
lxmd_config["propagation"].as_float(
|
|
201
|
+
"propagation_transfer_max_accepted_size"
|
|
202
|
+
)
|
|
203
|
+
)
|
|
204
|
+
if active_configuration["propagation_transfer_max_accepted_size"] < 0.38:
|
|
205
|
+
active_configuration["propagation_transfer_max_accepted_size"] = 0.38
|
|
206
|
+
else:
|
|
207
|
+
active_configuration["propagation_transfer_max_accepted_size"] = 256
|
|
208
|
+
|
|
209
|
+
if (
|
|
210
|
+
"propagation" in lxmd_config
|
|
211
|
+
and "propagation_message_max_accepted_size" in lxmd_config["propagation"]
|
|
212
|
+
):
|
|
213
|
+
active_configuration["propagation_transfer_max_accepted_size"] = (
|
|
214
|
+
lxmd_config["propagation"].as_float(
|
|
215
|
+
"propagation_message_max_accepted_size"
|
|
216
|
+
)
|
|
217
|
+
)
|
|
218
|
+
if active_configuration["propagation_transfer_max_accepted_size"] < 0.38:
|
|
219
|
+
active_configuration["propagation_transfer_max_accepted_size"] = 0.38
|
|
220
|
+
else:
|
|
221
|
+
active_configuration["propagation_transfer_max_accepted_size"] = 256
|
|
222
|
+
|
|
223
|
+
if (
|
|
224
|
+
"propagation" in lxmd_config
|
|
225
|
+
and "propagation_sync_max_accepted_size" in lxmd_config["propagation"]
|
|
226
|
+
):
|
|
227
|
+
active_configuration["propagation_sync_max_accepted_size"] = lxmd_config[
|
|
228
|
+
"propagation"
|
|
229
|
+
].as_float("propagation_sync_max_accepted_size")
|
|
230
|
+
if active_configuration["propagation_sync_max_accepted_size"] < 0.38:
|
|
231
|
+
active_configuration["propagation_sync_max_accepted_size"] = 0.38
|
|
232
|
+
else:
|
|
233
|
+
active_configuration["propagation_sync_max_accepted_size"] = 256 * 40
|
|
234
|
+
|
|
235
|
+
if (
|
|
236
|
+
"propagation" in lxmd_config
|
|
237
|
+
and "propagation_stamp_cost_target" in lxmd_config["propagation"]
|
|
238
|
+
):
|
|
239
|
+
active_configuration["propagation_stamp_cost_target"] = lxmd_config[
|
|
240
|
+
"propagation"
|
|
241
|
+
].as_int("propagation_stamp_cost_target")
|
|
242
|
+
if (
|
|
243
|
+
active_configuration["propagation_stamp_cost_target"]
|
|
244
|
+
< LXMF.LXMRouter.PROPAGATION_COST_MIN
|
|
245
|
+
):
|
|
246
|
+
active_configuration["propagation_stamp_cost_target"] = (
|
|
247
|
+
LXMF.LXMRouter.PROPAGATION_COST_MIN
|
|
248
|
+
)
|
|
249
|
+
else:
|
|
250
|
+
active_configuration["propagation_stamp_cost_target"] = (
|
|
251
|
+
LXMF.LXMRouter.PROPAGATION_COST
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
if (
|
|
255
|
+
"propagation" in lxmd_config
|
|
256
|
+
and "propagation_stamp_cost_flexibility" in lxmd_config["propagation"]
|
|
257
|
+
):
|
|
258
|
+
active_configuration["propagation_stamp_cost_flexibility"] = lxmd_config[
|
|
259
|
+
"propagation"
|
|
260
|
+
].as_int("propagation_stamp_cost_flexibility")
|
|
261
|
+
if active_configuration["propagation_stamp_cost_flexibility"] < 0:
|
|
262
|
+
active_configuration["propagation_stamp_cost_flexibility"] = 0
|
|
263
|
+
else:
|
|
264
|
+
active_configuration["propagation_stamp_cost_flexibility"] = (
|
|
265
|
+
LXMF.LXMRouter.PROPAGATION_COST_FLEX
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
if (
|
|
269
|
+
"propagation" in lxmd_config
|
|
270
|
+
and "peering_cost" in lxmd_config["propagation"]
|
|
271
|
+
):
|
|
272
|
+
active_configuration["peering_cost"] = lxmd_config["propagation"].as_int(
|
|
273
|
+
"peering_cost"
|
|
274
|
+
)
|
|
275
|
+
if active_configuration["peering_cost"] < 0:
|
|
276
|
+
active_configuration["peering_cost"] = 0
|
|
277
|
+
else:
|
|
278
|
+
active_configuration["peering_cost"] = LXMF.LXMRouter.PEERING_COST
|
|
279
|
+
|
|
280
|
+
if (
|
|
281
|
+
"propagation" in lxmd_config
|
|
282
|
+
and "remote_peering_cost_max" in lxmd_config["propagation"]
|
|
283
|
+
):
|
|
284
|
+
active_configuration["remote_peering_cost_max"] = lxmd_config[
|
|
285
|
+
"propagation"
|
|
286
|
+
].as_int("remote_peering_cost_max")
|
|
287
|
+
if active_configuration["remote_peering_cost_max"] < 0:
|
|
288
|
+
active_configuration["remote_peering_cost_max"] = 0
|
|
289
|
+
else:
|
|
290
|
+
active_configuration["remote_peering_cost_max"] = (
|
|
291
|
+
LXMF.LXMRouter.MAX_PEERING_COST
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
if (
|
|
295
|
+
"propagation" in lxmd_config
|
|
296
|
+
and "prioritise_destinations" in lxmd_config["propagation"]
|
|
297
|
+
):
|
|
298
|
+
active_configuration["prioritised_lxmf_destinations"] = lxmd_config[
|
|
299
|
+
"propagation"
|
|
300
|
+
].as_list("prioritise_destinations")
|
|
301
|
+
else:
|
|
302
|
+
active_configuration["prioritised_lxmf_destinations"] = []
|
|
303
|
+
|
|
304
|
+
if (
|
|
305
|
+
"propagation" in lxmd_config
|
|
306
|
+
and "control_allowed" in lxmd_config["propagation"]
|
|
307
|
+
):
|
|
308
|
+
active_configuration["control_allowed_identities"] = lxmd_config[
|
|
309
|
+
"propagation"
|
|
310
|
+
].as_list("control_allowed")
|
|
311
|
+
else:
|
|
312
|
+
active_configuration["control_allowed_identities"] = []
|
|
313
|
+
|
|
314
|
+
if (
|
|
315
|
+
"propagation" in lxmd_config
|
|
316
|
+
and "static_peers" in lxmd_config["propagation"]
|
|
317
|
+
):
|
|
318
|
+
static_peers = lxmd_config["propagation"].as_list("static_peers")
|
|
319
|
+
active_configuration["static_peers"] = []
|
|
320
|
+
for static_peer in static_peers:
|
|
321
|
+
active_configuration["static_peers"].append(bytes.fromhex(static_peer))
|
|
322
|
+
else:
|
|
323
|
+
active_configuration["static_peers"] = []
|
|
324
|
+
|
|
325
|
+
if "propagation" in lxmd_config and "max_peers" in lxmd_config["propagation"]:
|
|
326
|
+
active_configuration["max_peers"] = lxmd_config["propagation"].as_int(
|
|
327
|
+
"max_peers"
|
|
328
|
+
)
|
|
329
|
+
else:
|
|
330
|
+
active_configuration["max_peers"] = None
|
|
331
|
+
|
|
332
|
+
if (
|
|
333
|
+
"propagation" in lxmd_config
|
|
334
|
+
and "from_static_only" in lxmd_config["propagation"]
|
|
335
|
+
):
|
|
336
|
+
active_configuration["from_static_only"] = lxmd_config[
|
|
337
|
+
"propagation"
|
|
338
|
+
].as_bool("from_static_only")
|
|
339
|
+
else:
|
|
340
|
+
active_configuration["from_static_only"] = False
|
|
341
|
+
|
|
342
|
+
# Load various settings
|
|
343
|
+
if "logging" in lxmd_config and "loglevel" in lxmd_config["logging"]:
|
|
344
|
+
targetloglevel = lxmd_config["logging"].as_int("loglevel")
|
|
345
|
+
|
|
346
|
+
active_configuration["ignored_lxmf_destinations"] = []
|
|
347
|
+
if os.path.isfile(ignoredpath):
|
|
348
|
+
try:
|
|
349
|
+
fh = open(ignoredpath, "rb")
|
|
350
|
+
ignored_input = fh.read()
|
|
351
|
+
fh.close()
|
|
352
|
+
|
|
353
|
+
ignored_hash_strs = ignored_input.splitlines()
|
|
354
|
+
|
|
355
|
+
for hash_str in ignored_hash_strs:
|
|
356
|
+
if len(hash_str) == RNS.Identity.TRUNCATED_HASHLENGTH // 8 * 2:
|
|
357
|
+
try:
|
|
358
|
+
ignored_hash = bytes.fromhex(hash_str.decode("utf-8"))
|
|
359
|
+
active_configuration["ignored_lxmf_destinations"].append(
|
|
360
|
+
ignored_hash
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
except Exception as e:
|
|
364
|
+
RNS.log(
|
|
365
|
+
"Could not decode hash from: " + str(hash_str),
|
|
366
|
+
RNS.LOG_DEBUG,
|
|
367
|
+
)
|
|
368
|
+
RNS.log(
|
|
369
|
+
"The contained exception was: " + str(e), RNS.LOG_DEBUG
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
except Exception as e:
|
|
373
|
+
RNS.log(
|
|
374
|
+
"Error while loading list of ignored destinations: " + str(e),
|
|
375
|
+
RNS.LOG_ERROR,
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
active_configuration["allowed_identities"] = []
|
|
379
|
+
if os.path.isfile(allowedpath):
|
|
380
|
+
try:
|
|
381
|
+
fh = open(allowedpath, "rb")
|
|
382
|
+
allowed_input = fh.read()
|
|
383
|
+
fh.close()
|
|
384
|
+
|
|
385
|
+
allowed_hash_strs = allowed_input.splitlines()
|
|
386
|
+
|
|
387
|
+
for hash_str in allowed_hash_strs:
|
|
388
|
+
if len(hash_str) == RNS.Identity.TRUNCATED_HASHLENGTH // 8 * 2:
|
|
389
|
+
try:
|
|
390
|
+
allowed_hash = bytes.fromhex(hash_str.decode("utf-8"))
|
|
391
|
+
active_configuration["allowed_identities"].append(
|
|
392
|
+
allowed_hash
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
except Exception as e:
|
|
396
|
+
RNS.log(
|
|
397
|
+
"Could not decode hash from: " + str(hash_str),
|
|
398
|
+
RNS.LOG_DEBUG,
|
|
399
|
+
)
|
|
400
|
+
RNS.log(
|
|
401
|
+
"The contained exception was: " + str(e), RNS.LOG_DEBUG
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
except Exception as e:
|
|
405
|
+
RNS.log(
|
|
406
|
+
"Error while loading list of allowed identities: " + str(e),
|
|
407
|
+
RNS.LOG_ERROR,
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
except Exception as e:
|
|
411
|
+
RNS.log(
|
|
412
|
+
"Could not apply LXM Daemon configuration. The contained exception was: "
|
|
413
|
+
+ str(e),
|
|
414
|
+
RNS.LOG_ERROR,
|
|
415
|
+
)
|
|
416
|
+
raise e
|
|
417
|
+
exit(3)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def lxmf_delivery(lxm):
|
|
421
|
+
global active_configuration, lxmdir
|
|
422
|
+
|
|
423
|
+
try:
|
|
424
|
+
written_path = lxm.write_to_directory(lxmdir)
|
|
425
|
+
RNS.log(
|
|
426
|
+
"Received " + str(lxm) + " written to " + str(written_path), RNS.LOG_DEBUG
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
if active_configuration["on_inbound"]:
|
|
430
|
+
RNS.log("Calling external program to handle message", RNS.LOG_DEBUG)
|
|
431
|
+
command = active_configuration["on_inbound"]
|
|
432
|
+
processing_command = command + ' "' + written_path + '"'
|
|
433
|
+
return_code = subprocess.call(
|
|
434
|
+
shlex.split(processing_command),
|
|
435
|
+
stdout=subprocess.DEVNULL,
|
|
436
|
+
stderr=subprocess.DEVNULL,
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
else:
|
|
440
|
+
RNS.log("No action defined for inbound messages, ignoring", RNS.LOG_DEBUG)
|
|
441
|
+
|
|
442
|
+
except Exception as e:
|
|
443
|
+
RNS.log(
|
|
444
|
+
"Error occurred while processing received message "
|
|
445
|
+
+ str(lxm)
|
|
446
|
+
+ ". The contained exception was: "
|
|
447
|
+
+ str(e),
|
|
448
|
+
RNS.LOG_ERROR,
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def program_setup(
|
|
453
|
+
configdir=None,
|
|
454
|
+
rnsconfigdir=None,
|
|
455
|
+
run_pn=False,
|
|
456
|
+
on_inbound=None,
|
|
457
|
+
verbosity=0,
|
|
458
|
+
quietness=0,
|
|
459
|
+
service=False,
|
|
460
|
+
):
|
|
461
|
+
global configpath, ignoredpath, identitypath, allowedpath, storagedir, lxmdir
|
|
462
|
+
global lxmd_config, active_configuration, targetloglevel
|
|
463
|
+
global message_router, lxmf_destination
|
|
464
|
+
|
|
465
|
+
if service:
|
|
466
|
+
targetlogdest = RNS.LOG_FILE
|
|
467
|
+
targetloglevel = None
|
|
468
|
+
else:
|
|
469
|
+
targetlogdest = RNS.LOG_STDOUT
|
|
470
|
+
|
|
471
|
+
# Get configuration
|
|
472
|
+
if configdir is None:
|
|
473
|
+
configdir = _default_config_directory()
|
|
474
|
+
|
|
475
|
+
configdir = str(configdir)
|
|
476
|
+
|
|
477
|
+
configpath = configdir + "/config"
|
|
478
|
+
ignoredpath = configdir + "/ignored"
|
|
479
|
+
allowedpath = configdir + "/allowed"
|
|
480
|
+
identitypath = configdir + "/identity"
|
|
481
|
+
storagedir = configdir + "/storage"
|
|
482
|
+
lxmdir = storagedir + "/messages"
|
|
483
|
+
|
|
484
|
+
if not os.path.isdir(storagedir):
|
|
485
|
+
os.makedirs(storagedir)
|
|
486
|
+
|
|
487
|
+
if not os.path.isdir(lxmdir):
|
|
488
|
+
os.makedirs(lxmdir)
|
|
489
|
+
|
|
490
|
+
if not os.path.isfile(configpath):
|
|
491
|
+
RNS.log("Could not load config file, creating default configuration file...")
|
|
492
|
+
create_default_config(configpath)
|
|
493
|
+
RNS.log(
|
|
494
|
+
"Default config file created. Make any necessary changes in "
|
|
495
|
+
+ configpath
|
|
496
|
+
+ " and restart lxmd if needed."
|
|
497
|
+
)
|
|
498
|
+
time.sleep(1.5)
|
|
499
|
+
|
|
500
|
+
if os.path.isfile(configpath):
|
|
501
|
+
try:
|
|
502
|
+
lxmd_config = ConfigObj(configpath)
|
|
503
|
+
except Exception as e:
|
|
504
|
+
RNS.log("Could not parse the configuration at " + configpath, RNS.LOG_ERROR)
|
|
505
|
+
RNS.log("Check your configuration file for errors!", RNS.LOG_ERROR)
|
|
506
|
+
RNS.panic()
|
|
507
|
+
|
|
508
|
+
apply_config()
|
|
509
|
+
RNS.log("Configuration loaded from " + configpath, RNS.LOG_VERBOSE)
|
|
510
|
+
|
|
511
|
+
if targetloglevel is None:
|
|
512
|
+
targetloglevel = 3
|
|
513
|
+
|
|
514
|
+
if verbosity != 0 or quietness != 0:
|
|
515
|
+
targetloglevel = targetloglevel + verbosity - quietness
|
|
516
|
+
|
|
517
|
+
# Start Reticulum
|
|
518
|
+
RNS.log("Substantiating Reticulum...")
|
|
519
|
+
reticulum = RNS.Reticulum(
|
|
520
|
+
configdir=rnsconfigdir, loglevel=targetloglevel, logdest=targetlogdest
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
# Generate or load primary identity
|
|
524
|
+
if os.path.isfile(identitypath):
|
|
525
|
+
try:
|
|
526
|
+
identity = RNS.Identity.from_file(identitypath)
|
|
527
|
+
if identity is not None:
|
|
528
|
+
RNS.log("Loaded Primary Identity %s" % (str(identity)))
|
|
529
|
+
else:
|
|
530
|
+
RNS.log(
|
|
531
|
+
"Could not load the Primary Identity from " + identitypath,
|
|
532
|
+
RNS.LOG_ERROR,
|
|
533
|
+
)
|
|
534
|
+
exit(4)
|
|
535
|
+
except Exception as e:
|
|
536
|
+
RNS.log(
|
|
537
|
+
"Could not load the Primary Identity from " + identitypath,
|
|
538
|
+
RNS.LOG_ERROR,
|
|
539
|
+
)
|
|
540
|
+
RNS.log("The contained exception was: %s" % (str(e)), RNS.LOG_ERROR)
|
|
541
|
+
exit(1)
|
|
542
|
+
else:
|
|
543
|
+
try:
|
|
544
|
+
RNS.log("No Primary Identity file found, creating new...")
|
|
545
|
+
identity = RNS.Identity()
|
|
546
|
+
identity.to_file(identitypath)
|
|
547
|
+
RNS.log("Created new Primary Identity %s" % (str(identity)))
|
|
548
|
+
except Exception as e:
|
|
549
|
+
RNS.log("Could not create and save a new Primary Identity", RNS.LOG_ERROR)
|
|
550
|
+
RNS.log("The contained exception was: %s" % (str(e)), RNS.LOG_ERROR)
|
|
551
|
+
exit(2)
|
|
552
|
+
|
|
553
|
+
# Start LXMF
|
|
554
|
+
message_router = LXMF.LXMRouter(
|
|
555
|
+
identity=identity,
|
|
556
|
+
storagepath=storagedir,
|
|
557
|
+
autopeer=active_configuration["autopeer"],
|
|
558
|
+
autopeer_maxdepth=active_configuration["autopeer_maxdepth"],
|
|
559
|
+
propagation_limit=active_configuration[
|
|
560
|
+
"propagation_transfer_max_accepted_size"
|
|
561
|
+
],
|
|
562
|
+
propagation_cost=active_configuration["propagation_stamp_cost_target"],
|
|
563
|
+
propagation_cost_flexibility=active_configuration[
|
|
564
|
+
"propagation_stamp_cost_flexibility"
|
|
565
|
+
],
|
|
566
|
+
peering_cost=active_configuration["peering_cost"],
|
|
567
|
+
max_peering_cost=active_configuration["remote_peering_cost_max"],
|
|
568
|
+
sync_limit=active_configuration["propagation_sync_max_accepted_size"],
|
|
569
|
+
delivery_limit=active_configuration["delivery_transfer_max_accepted_size"],
|
|
570
|
+
max_peers=active_configuration["max_peers"],
|
|
571
|
+
static_peers=active_configuration["static_peers"],
|
|
572
|
+
from_static_only=active_configuration["from_static_only"],
|
|
573
|
+
name=active_configuration["node_name"],
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
message_router.register_delivery_callback(lxmf_delivery)
|
|
577
|
+
|
|
578
|
+
for destination_hash in active_configuration["ignored_lxmf_destinations"]:
|
|
579
|
+
message_router.ignore_destination(destination_hash)
|
|
580
|
+
|
|
581
|
+
lxmf_destination = message_router.register_delivery_identity(
|
|
582
|
+
identity, display_name=active_configuration["display_name"]
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
RNS.Identity.remember(
|
|
586
|
+
packet_hash=None,
|
|
587
|
+
destination_hash=lxmf_destination.hash,
|
|
588
|
+
public_key=identity.get_public_key(),
|
|
589
|
+
app_data=None,
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
# Set up authentication
|
|
593
|
+
if active_configuration["auth_required"]:
|
|
594
|
+
message_router.set_authentication(required=True)
|
|
595
|
+
|
|
596
|
+
if len(active_configuration["allowed_identities"]) == 0:
|
|
597
|
+
RNS.log(
|
|
598
|
+
"Clint authentication was enabled, but no identity hashes could be loaded from "
|
|
599
|
+
+ str(allowedpath)
|
|
600
|
+
+ ". Nobody will be able to sync messages from this propagation node.",
|
|
601
|
+
RNS.LOG_WARNING,
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
for identity_hash in active_configuration["allowed_identities"]:
|
|
605
|
+
message_router.allow(identity_hash)
|
|
606
|
+
|
|
607
|
+
RNS.log(
|
|
608
|
+
"LXMF Router ready to receive on " + RNS.prettyhexrep(lxmf_destination.hash)
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
if run_pn or active_configuration["enable_propagation_node"]:
|
|
612
|
+
message_router.set_message_storage_limit(
|
|
613
|
+
megabytes=active_configuration["message_storage_limit"]
|
|
614
|
+
)
|
|
615
|
+
for dest_str in active_configuration["prioritised_lxmf_destinations"]:
|
|
616
|
+
try:
|
|
617
|
+
dest_hash = bytes.fromhex(dest_str)
|
|
618
|
+
if len(dest_hash) == RNS.Reticulum.TRUNCATED_HASHLENGTH // 8:
|
|
619
|
+
message_router.prioritise(dest_hash)
|
|
620
|
+
except Exception as e:
|
|
621
|
+
RNS.log(
|
|
622
|
+
"Cannot prioritise "
|
|
623
|
+
+ str(dest_str)
|
|
624
|
+
+ ", it is not a valid destination hash",
|
|
625
|
+
RNS.LOG_ERROR,
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
message_router.enable_propagation()
|
|
629
|
+
|
|
630
|
+
for ident_str in active_configuration["control_allowed_identities"]:
|
|
631
|
+
try:
|
|
632
|
+
identity_hash = bytes.fromhex(ident_str)
|
|
633
|
+
if len(identity_hash) == RNS.Reticulum.TRUNCATED_HASHLENGTH // 8:
|
|
634
|
+
message_router.allow_control(identity_hash)
|
|
635
|
+
except Exception as e:
|
|
636
|
+
RNS.log(
|
|
637
|
+
f"Cannot allow control from {ident_str}, it is not a valid identity hash",
|
|
638
|
+
RNS.LOG_ERROR,
|
|
639
|
+
)
|
|
640
|
+
|
|
641
|
+
RNS.log(
|
|
642
|
+
"LXMF Propagation Node started on "
|
|
643
|
+
+ RNS.prettyhexrep(message_router.propagation_destination.hash)
|
|
644
|
+
)
|
|
645
|
+
|
|
646
|
+
RNS.log(
|
|
647
|
+
"Started lxmd version {version}".format(version=__version__), RNS.LOG_NOTICE
|
|
648
|
+
)
|
|
649
|
+
|
|
650
|
+
threading.Thread(target=deferred_start_jobs, daemon=True).start()
|
|
651
|
+
|
|
652
|
+
while True:
|
|
653
|
+
time.sleep(1)
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
def jobs():
|
|
657
|
+
global active_configuration, last_peer_announce, last_node_announce
|
|
658
|
+
global message_router, lxmf_destination
|
|
659
|
+
|
|
660
|
+
while True:
|
|
661
|
+
try:
|
|
662
|
+
if (
|
|
663
|
+
"peer_announce_interval" in active_configuration
|
|
664
|
+
and active_configuration["peer_announce_interval"] is not None
|
|
665
|
+
):
|
|
666
|
+
if (
|
|
667
|
+
time.time()
|
|
668
|
+
> last_peer_announce
|
|
669
|
+
+ active_configuration["peer_announce_interval"]
|
|
670
|
+
):
|
|
671
|
+
RNS.log(
|
|
672
|
+
"Sending announce for LXMF delivery destination",
|
|
673
|
+
RNS.LOG_VERBOSE,
|
|
674
|
+
)
|
|
675
|
+
message_router.announce(lxmf_destination.hash)
|
|
676
|
+
last_peer_announce = time.time()
|
|
677
|
+
|
|
678
|
+
if (
|
|
679
|
+
"node_announce_interval" in active_configuration
|
|
680
|
+
and active_configuration["node_announce_interval"] is not None
|
|
681
|
+
):
|
|
682
|
+
if (
|
|
683
|
+
time.time()
|
|
684
|
+
> last_node_announce
|
|
685
|
+
+ active_configuration["node_announce_interval"]
|
|
686
|
+
):
|
|
687
|
+
RNS.log(
|
|
688
|
+
"Sending announce for LXMF Propagation Node", RNS.LOG_VERBOSE
|
|
689
|
+
)
|
|
690
|
+
message_router.announce_propagation_node()
|
|
691
|
+
last_node_announce = time.time()
|
|
692
|
+
|
|
693
|
+
except Exception as e:
|
|
694
|
+
RNS.log(
|
|
695
|
+
"An error occurred while running periodic jobs. The contained exception was: "
|
|
696
|
+
+ str(e),
|
|
697
|
+
RNS.LOG_ERROR,
|
|
698
|
+
)
|
|
699
|
+
|
|
700
|
+
time.sleep(JOBS_INTERVAL)
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
def deferred_start_jobs():
|
|
704
|
+
global active_configuration, last_peer_announce, last_node_announce
|
|
705
|
+
global message_router, lxmf_destination
|
|
706
|
+
time.sleep(DEFFERED_JOBS_DELAY)
|
|
707
|
+
RNS.log("Running deferred start jobs", RNS.LOG_DEBUG)
|
|
708
|
+
if active_configuration["peer_announce_at_start"]:
|
|
709
|
+
RNS.log("Sending announce for LXMF delivery destination", RNS.LOG_EXTREME)
|
|
710
|
+
message_router.announce(lxmf_destination.hash)
|
|
711
|
+
|
|
712
|
+
if active_configuration["node_announce_at_start"]:
|
|
713
|
+
RNS.log("Sending announce for LXMF Propagation Node", RNS.LOG_EXTREME)
|
|
714
|
+
message_router.announce_propagation_node()
|
|
715
|
+
|
|
716
|
+
last_peer_announce = time.time()
|
|
717
|
+
last_node_announce = time.time()
|
|
718
|
+
threading.Thread(target=jobs, daemon=True).start()
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
def _request_sync(
|
|
722
|
+
identity, destination_hash, remote_identity, timeout=15, exit_on_fail=False
|
|
723
|
+
):
|
|
724
|
+
control_destination = RNS.Destination(
|
|
725
|
+
remote_identity,
|
|
726
|
+
RNS.Destination.OUT,
|
|
727
|
+
RNS.Destination.SINGLE,
|
|
728
|
+
APP_NAME,
|
|
729
|
+
"propagation",
|
|
730
|
+
"control",
|
|
731
|
+
)
|
|
732
|
+
|
|
733
|
+
timeout = time.time() + timeout
|
|
734
|
+
|
|
735
|
+
def check_timeout():
|
|
736
|
+
if time.time() > timeout:
|
|
737
|
+
if exit_on_fail:
|
|
738
|
+
print("Requesting lxmd peer sync timed out, exiting now")
|
|
739
|
+
exit(200)
|
|
740
|
+
else:
|
|
741
|
+
return LXMF.LXMPeer.LXMPeer.ERROR_TIMEOUT
|
|
742
|
+
else:
|
|
743
|
+
time.sleep(0.1)
|
|
744
|
+
|
|
745
|
+
if not RNS.Transport.has_path(control_destination.hash):
|
|
746
|
+
RNS.Transport.request_path(control_destination.hash)
|
|
747
|
+
while not RNS.Transport.has_path(control_destination.hash):
|
|
748
|
+
tc = check_timeout()
|
|
749
|
+
if tc:
|
|
750
|
+
return tc
|
|
751
|
+
|
|
752
|
+
link = RNS.Link(control_destination)
|
|
753
|
+
while not link.status == RNS.Link.ACTIVE:
|
|
754
|
+
tc = check_timeout()
|
|
755
|
+
if tc:
|
|
756
|
+
return tc
|
|
757
|
+
|
|
758
|
+
link.identify(identity)
|
|
759
|
+
request_receipt = link.request(
|
|
760
|
+
LXMF.LXMRouter.SYNC_REQUEST_PATH,
|
|
761
|
+
data=destination_hash,
|
|
762
|
+
response_callback=None,
|
|
763
|
+
failed_callback=None,
|
|
764
|
+
)
|
|
765
|
+
while not request_receipt.get_status() == RNS.RequestReceipt.READY:
|
|
766
|
+
tc = check_timeout()
|
|
767
|
+
if tc:
|
|
768
|
+
return tc
|
|
769
|
+
|
|
770
|
+
link.teardown()
|
|
771
|
+
return request_receipt.get_response()
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
def request_sync(
|
|
775
|
+
target,
|
|
776
|
+
remote=None,
|
|
777
|
+
configdir=None,
|
|
778
|
+
rnsconfigdir=None,
|
|
779
|
+
verbosity=0,
|
|
780
|
+
quietness=0,
|
|
781
|
+
timeout=15,
|
|
782
|
+
identity_path=None,
|
|
783
|
+
):
|
|
784
|
+
global configpath, identitypath, storagedir, lxmdir
|
|
785
|
+
global lxmd_config, active_configuration, targetloglevel
|
|
786
|
+
|
|
787
|
+
try:
|
|
788
|
+
peer_destination_hash = bytes.fromhex(target)
|
|
789
|
+
if len(peer_destination_hash) != RNS.Identity.TRUNCATED_HASHLENGTH // 8:
|
|
790
|
+
raise ValueError(
|
|
791
|
+
f"Destination hash length must be {RNS.Identity.TRUNCATED_HASHLENGTH//8*2} characters"
|
|
792
|
+
)
|
|
793
|
+
except Exception as e:
|
|
794
|
+
print(f"Invalid peer destination hash: {e}")
|
|
795
|
+
exit(203)
|
|
796
|
+
remote
|
|
797
|
+
_remote_init(configdir, rnsconfigdir, verbosity, quietness, identity_path)
|
|
798
|
+
response = _request_sync(
|
|
799
|
+
identity,
|
|
800
|
+
peer_destination_hash,
|
|
801
|
+
remote_identity=_get_target_identity(remote),
|
|
802
|
+
timeout=timeout,
|
|
803
|
+
exit_on_fail=True,
|
|
804
|
+
)
|
|
805
|
+
|
|
806
|
+
if response == LXMF.LXMPeer.LXMPeer.ERROR_NO_IDENTITY:
|
|
807
|
+
print("Remote received no identity")
|
|
808
|
+
exit(203)
|
|
809
|
+
elif response == LXMF.LXMPeer.LXMPeer.ERROR_NO_ACCESS:
|
|
810
|
+
print("Access denied")
|
|
811
|
+
exit(204)
|
|
812
|
+
elif response == LXMF.LXMPeer.LXMPeer.ERROR_INVALID_DATA:
|
|
813
|
+
print("Invalid data received by remote")
|
|
814
|
+
exit(205)
|
|
815
|
+
elif response == LXMF.LXMPeer.LXMPeer.ERROR_NOT_FOUND:
|
|
816
|
+
print("The requested peer was not found")
|
|
817
|
+
exit(206)
|
|
818
|
+
elif response is None:
|
|
819
|
+
print("Empty response received")
|
|
820
|
+
exit(207)
|
|
821
|
+
else:
|
|
822
|
+
print(f"Sync requested for peer {RNS.prettyhexrep(peer_destination_hash)}")
|
|
823
|
+
exit(0)
|
|
824
|
+
|
|
825
|
+
|
|
826
|
+
def _request_unpeer(
|
|
827
|
+
identity, destination_hash, remote_identity, timeout=15, exit_on_fail=False
|
|
828
|
+
):
|
|
829
|
+
control_destination = RNS.Destination(
|
|
830
|
+
remote_identity,
|
|
831
|
+
RNS.Destination.OUT,
|
|
832
|
+
RNS.Destination.SINGLE,
|
|
833
|
+
APP_NAME,
|
|
834
|
+
"propagation",
|
|
835
|
+
"control",
|
|
836
|
+
)
|
|
837
|
+
|
|
838
|
+
timeout = time.time() + timeout
|
|
839
|
+
|
|
840
|
+
def check_timeout():
|
|
841
|
+
if time.time() > timeout:
|
|
842
|
+
if exit_on_fail:
|
|
843
|
+
print("Requesting lxmd peering break timed out, exiting now")
|
|
844
|
+
exit(200)
|
|
845
|
+
else:
|
|
846
|
+
return LXMF.LXMPeer.LXMPeer.ERROR_TIMEOUT
|
|
847
|
+
else:
|
|
848
|
+
time.sleep(0.1)
|
|
849
|
+
|
|
850
|
+
if not RNS.Transport.has_path(control_destination.hash):
|
|
851
|
+
RNS.Transport.request_path(control_destination.hash)
|
|
852
|
+
while not RNS.Transport.has_path(control_destination.hash):
|
|
853
|
+
tc = check_timeout()
|
|
854
|
+
if tc:
|
|
855
|
+
return tc
|
|
856
|
+
|
|
857
|
+
link = RNS.Link(control_destination)
|
|
858
|
+
while not link.status == RNS.Link.ACTIVE:
|
|
859
|
+
tc = check_timeout()
|
|
860
|
+
if tc:
|
|
861
|
+
return tc
|
|
862
|
+
|
|
863
|
+
link.identify(identity)
|
|
864
|
+
request_receipt = link.request(
|
|
865
|
+
LXMF.LXMRouter.UNPEER_REQUEST_PATH,
|
|
866
|
+
data=destination_hash,
|
|
867
|
+
response_callback=None,
|
|
868
|
+
failed_callback=None,
|
|
869
|
+
)
|
|
870
|
+
while not request_receipt.get_status() == RNS.RequestReceipt.READY:
|
|
871
|
+
tc = check_timeout()
|
|
872
|
+
if tc:
|
|
873
|
+
return tc
|
|
874
|
+
|
|
875
|
+
link.teardown()
|
|
876
|
+
return request_receipt.get_response()
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
def request_unpeer(
|
|
880
|
+
target,
|
|
881
|
+
remote=None,
|
|
882
|
+
configdir=None,
|
|
883
|
+
rnsconfigdir=None,
|
|
884
|
+
verbosity=0,
|
|
885
|
+
quietness=0,
|
|
886
|
+
timeout=15,
|
|
887
|
+
identity_path=None,
|
|
888
|
+
):
|
|
889
|
+
global configpath, identitypath, storagedir, lxmdir
|
|
890
|
+
global lxmd_config, active_configuration, targetloglevel
|
|
891
|
+
|
|
892
|
+
try:
|
|
893
|
+
peer_destination_hash = bytes.fromhex(target)
|
|
894
|
+
if len(peer_destination_hash) != RNS.Identity.TRUNCATED_HASHLENGTH // 8:
|
|
895
|
+
raise ValueError(
|
|
896
|
+
f"Destination hash length must be {RNS.Identity.TRUNCATED_HASHLENGTH//8*2} characters"
|
|
897
|
+
)
|
|
898
|
+
except Exception as e:
|
|
899
|
+
print(f"Invalid peer destination hash: {e}")
|
|
900
|
+
exit(203)
|
|
901
|
+
remote
|
|
902
|
+
_remote_init(configdir, rnsconfigdir, verbosity, quietness, identity_path)
|
|
903
|
+
response = _request_unpeer(
|
|
904
|
+
identity,
|
|
905
|
+
peer_destination_hash,
|
|
906
|
+
remote_identity=_get_target_identity(remote),
|
|
907
|
+
timeout=timeout,
|
|
908
|
+
exit_on_fail=True,
|
|
909
|
+
)
|
|
910
|
+
|
|
911
|
+
if response == LXMF.LXMPeer.LXMPeer.ERROR_NO_IDENTITY:
|
|
912
|
+
print("Remote received no identity")
|
|
913
|
+
exit(203)
|
|
914
|
+
elif response == LXMF.LXMPeer.LXMPeer.ERROR_NO_ACCESS:
|
|
915
|
+
print("Access denied")
|
|
916
|
+
exit(204)
|
|
917
|
+
elif response == LXMF.LXMPeer.LXMPeer.ERROR_INVALID_DATA:
|
|
918
|
+
print("Invalid data received by remote")
|
|
919
|
+
exit(205)
|
|
920
|
+
elif response == LXMF.LXMPeer.LXMPeer.ERROR_NOT_FOUND:
|
|
921
|
+
print("The requested peer was not found")
|
|
922
|
+
exit(206)
|
|
923
|
+
elif response is None:
|
|
924
|
+
print("Empty response received")
|
|
925
|
+
exit(207)
|
|
926
|
+
else:
|
|
927
|
+
print(f"Broke peering with {RNS.prettyhexrep(peer_destination_hash)}")
|
|
928
|
+
exit(0)
|
|
929
|
+
|
|
930
|
+
|
|
931
|
+
def query_status(identity, remote_identity=None, timeout=5, exit_on_fail=False):
|
|
932
|
+
if remote_identity is None:
|
|
933
|
+
remote_identity = identity
|
|
934
|
+
control_destination = RNS.Destination(
|
|
935
|
+
remote_identity,
|
|
936
|
+
RNS.Destination.OUT,
|
|
937
|
+
RNS.Destination.SINGLE,
|
|
938
|
+
APP_NAME,
|
|
939
|
+
"propagation",
|
|
940
|
+
"control",
|
|
941
|
+
)
|
|
942
|
+
|
|
943
|
+
timeout = time.time() + timeout
|
|
944
|
+
|
|
945
|
+
def check_timeout():
|
|
946
|
+
if time.time() > timeout:
|
|
947
|
+
if exit_on_fail:
|
|
948
|
+
print("Getting lxmd statistics timed out, exiting now")
|
|
949
|
+
exit(200)
|
|
950
|
+
else:
|
|
951
|
+
return LXMF.LXMPeer.LXMPeer.ERROR_TIMEOUT
|
|
952
|
+
else:
|
|
953
|
+
time.sleep(0.1)
|
|
954
|
+
|
|
955
|
+
if not RNS.Transport.has_path(control_destination.hash):
|
|
956
|
+
RNS.Transport.request_path(control_destination.hash)
|
|
957
|
+
while not RNS.Transport.has_path(control_destination.hash):
|
|
958
|
+
tc = check_timeout()
|
|
959
|
+
if tc:
|
|
960
|
+
return tc
|
|
961
|
+
|
|
962
|
+
link = RNS.Link(control_destination)
|
|
963
|
+
while not link.status == RNS.Link.ACTIVE:
|
|
964
|
+
tc = check_timeout()
|
|
965
|
+
if tc:
|
|
966
|
+
return tc
|
|
967
|
+
|
|
968
|
+
link.identify(identity)
|
|
969
|
+
request_receipt = link.request(
|
|
970
|
+
LXMF.LXMRouter.STATS_GET_PATH,
|
|
971
|
+
data=None,
|
|
972
|
+
response_callback=None,
|
|
973
|
+
failed_callback=None,
|
|
974
|
+
)
|
|
975
|
+
while not request_receipt.get_status() == RNS.RequestReceipt.READY:
|
|
976
|
+
tc = check_timeout()
|
|
977
|
+
if tc:
|
|
978
|
+
return tc
|
|
979
|
+
|
|
980
|
+
link.teardown()
|
|
981
|
+
return request_receipt.get_response()
|
|
982
|
+
|
|
983
|
+
|
|
984
|
+
def get_status(
|
|
985
|
+
remote=None,
|
|
986
|
+
configdir=None,
|
|
987
|
+
rnsconfigdir=None,
|
|
988
|
+
verbosity=0,
|
|
989
|
+
quietness=0,
|
|
990
|
+
timeout=5,
|
|
991
|
+
show_status=False,
|
|
992
|
+
show_peers=False,
|
|
993
|
+
identity_path=None,
|
|
994
|
+
):
|
|
995
|
+
|
|
996
|
+
global identity
|
|
997
|
+
_remote_init(configdir, rnsconfigdir, verbosity, quietness, identity_path)
|
|
998
|
+
response = query_status(
|
|
999
|
+
identity,
|
|
1000
|
+
remote_identity=_get_target_identity(remote),
|
|
1001
|
+
timeout=timeout,
|
|
1002
|
+
exit_on_fail=True,
|
|
1003
|
+
)
|
|
1004
|
+
|
|
1005
|
+
if response == LXMF.LXMPeer.LXMPeer.ERROR_NO_IDENTITY:
|
|
1006
|
+
print("Remote received no identity")
|
|
1007
|
+
exit(203)
|
|
1008
|
+
if response == LXMF.LXMPeer.LXMPeer.ERROR_NO_ACCESS:
|
|
1009
|
+
print("Access denied")
|
|
1010
|
+
exit(204)
|
|
1011
|
+
elif response is None:
|
|
1012
|
+
print("Empty response received")
|
|
1013
|
+
exit(207)
|
|
1014
|
+
else:
|
|
1015
|
+
s = response
|
|
1016
|
+
mutil = round(
|
|
1017
|
+
(s["messagestore"]["bytes"] / s["messagestore"]["limit"]) * 100, 2
|
|
1018
|
+
)
|
|
1019
|
+
ms_util = f"{mutil}%"
|
|
1020
|
+
if s["from_static_only"]:
|
|
1021
|
+
who_str = "static peers only"
|
|
1022
|
+
else:
|
|
1023
|
+
who_str = "all nodes"
|
|
1024
|
+
|
|
1025
|
+
available_peers = 0
|
|
1026
|
+
unreachable_peers = 0
|
|
1027
|
+
peered_incoming = 0
|
|
1028
|
+
peered_outgoing = 0
|
|
1029
|
+
peered_rx_bytes = 0
|
|
1030
|
+
peered_tx_bytes = 0
|
|
1031
|
+
for peer_id in s["peers"]:
|
|
1032
|
+
p = s["peers"][peer_id]
|
|
1033
|
+
pm = p["messages"]
|
|
1034
|
+
peered_incoming += pm["incoming"]
|
|
1035
|
+
peered_outgoing += pm["outgoing"]
|
|
1036
|
+
peered_rx_bytes += p["rx_bytes"]
|
|
1037
|
+
peered_tx_bytes += p["tx_bytes"]
|
|
1038
|
+
|
|
1039
|
+
if p["alive"]:
|
|
1040
|
+
available_peers += 1
|
|
1041
|
+
else:
|
|
1042
|
+
unreachable_peers += 1
|
|
1043
|
+
|
|
1044
|
+
total_incoming = (
|
|
1045
|
+
peered_incoming
|
|
1046
|
+
+ s["unpeered_propagation_incoming"]
|
|
1047
|
+
+ s["clients"]["client_propagation_messages_received"]
|
|
1048
|
+
)
|
|
1049
|
+
total_rx_bytes = peered_rx_bytes + s["unpeered_propagation_rx_bytes"]
|
|
1050
|
+
if total_incoming != 0:
|
|
1051
|
+
df = round(peered_outgoing / total_incoming, 2)
|
|
1052
|
+
else:
|
|
1053
|
+
df = 0
|
|
1054
|
+
|
|
1055
|
+
dhs = RNS.prettyhexrep(s["destination_hash"])
|
|
1056
|
+
uts = RNS.prettytime(s["uptime"])
|
|
1057
|
+
print(f"\nLXMF Propagation Node running on {dhs}, uptime is {uts}")
|
|
1058
|
+
|
|
1059
|
+
if show_status:
|
|
1060
|
+
msb = RNS.prettysize(s["messagestore"]["bytes"])
|
|
1061
|
+
msl = RNS.prettysize(s["messagestore"]["limit"])
|
|
1062
|
+
ptl = RNS.prettysize(s["propagation_limit"] * 1000)
|
|
1063
|
+
psl = RNS.prettysize(s["sync_limit"] * 1000)
|
|
1064
|
+
uprx = RNS.prettysize(s["unpeered_propagation_rx_bytes"])
|
|
1065
|
+
mscnt = s["messagestore"]["count"]
|
|
1066
|
+
stp = s["total_peers"]
|
|
1067
|
+
smp = s["max_peers"]
|
|
1068
|
+
sdp = s["discovered_peers"]
|
|
1069
|
+
ssp = s["static_peers"]
|
|
1070
|
+
cprr = s["clients"]["client_propagation_messages_received"]
|
|
1071
|
+
cprs = s["clients"]["client_propagation_messages_served"]
|
|
1072
|
+
upi = s["unpeered_propagation_incoming"]
|
|
1073
|
+
psc = s["target_stamp_cost"]
|
|
1074
|
+
scf = s["stamp_cost_flexibility"]
|
|
1075
|
+
pc = s["peering_cost"]
|
|
1076
|
+
pcm = s["max_peering_cost"]
|
|
1077
|
+
print(
|
|
1078
|
+
f"Messagestore contains {mscnt} messages, {msb} ({ms_util} utilised of {msl})"
|
|
1079
|
+
)
|
|
1080
|
+
print(f"Required propagation stamp cost is {psc}, flexibility is {scf}")
|
|
1081
|
+
print(f"Peering cost is {pc}, max remote peering cost is {pcm}")
|
|
1082
|
+
print(f"Accepting propagated messages from {who_str}")
|
|
1083
|
+
print(f"{ptl} message limit, {psl} sync limit")
|
|
1084
|
+
print(f"")
|
|
1085
|
+
print(f"Peers : {stp} total (peer limit is {smp})")
|
|
1086
|
+
print(f" {sdp} discovered, {ssp} static")
|
|
1087
|
+
print(
|
|
1088
|
+
f" {available_peers} available, {unreachable_peers} unreachable"
|
|
1089
|
+
)
|
|
1090
|
+
print(f"")
|
|
1091
|
+
print(
|
|
1092
|
+
f"Traffic : {total_incoming} messages received in total ({RNS.prettysize(total_rx_bytes)})"
|
|
1093
|
+
)
|
|
1094
|
+
print(
|
|
1095
|
+
f" {peered_incoming} messages received from peered nodes ({RNS.prettysize(peered_rx_bytes)})"
|
|
1096
|
+
)
|
|
1097
|
+
print(f" {upi} messages received from unpeered nodes ({uprx})")
|
|
1098
|
+
print(
|
|
1099
|
+
f" {peered_outgoing} messages transferred to peered nodes ({RNS.prettysize(peered_tx_bytes)})"
|
|
1100
|
+
)
|
|
1101
|
+
print(
|
|
1102
|
+
f" {cprr} propagation messages received directly from clients"
|
|
1103
|
+
)
|
|
1104
|
+
print(f" {cprs} propagation messages served to clients")
|
|
1105
|
+
print(f" Distribution factor is {df}")
|
|
1106
|
+
print(f"")
|
|
1107
|
+
|
|
1108
|
+
if show_peers:
|
|
1109
|
+
if not show_status:
|
|
1110
|
+
print("")
|
|
1111
|
+
|
|
1112
|
+
for peer_id in s["peers"]:
|
|
1113
|
+
ind = " "
|
|
1114
|
+
p = s["peers"][peer_id]
|
|
1115
|
+
if p["type"] == "static":
|
|
1116
|
+
t = "Static peer "
|
|
1117
|
+
elif p["type"] == "discovered":
|
|
1118
|
+
t = "Discovered peer "
|
|
1119
|
+
else:
|
|
1120
|
+
t = "Unknown peer "
|
|
1121
|
+
a = "Available" if p["alive"] == True else "Unreachable"
|
|
1122
|
+
h = max(time.time() - p["last_heard"], 0)
|
|
1123
|
+
hops = p["network_distance"]
|
|
1124
|
+
hs = (
|
|
1125
|
+
"hops unknown"
|
|
1126
|
+
if hops == RNS.Transport.PATHFINDER_M
|
|
1127
|
+
else f"{hops} hop away" if hops == 1 else f"{hops} hops away"
|
|
1128
|
+
)
|
|
1129
|
+
pm = p["messages"]
|
|
1130
|
+
pk = p["peering_key"]
|
|
1131
|
+
pc = p["peering_cost"]
|
|
1132
|
+
psc = p["target_stamp_cost"]
|
|
1133
|
+
psf = p["stamp_cost_flexibility"]
|
|
1134
|
+
if pc is None:
|
|
1135
|
+
pc = "unknown"
|
|
1136
|
+
if psc is None:
|
|
1137
|
+
psc = "unknown"
|
|
1138
|
+
if psf is None:
|
|
1139
|
+
psf = "unknown"
|
|
1140
|
+
if pk is None:
|
|
1141
|
+
pk = "Not generated"
|
|
1142
|
+
else:
|
|
1143
|
+
pk = f"Generated, value is {pk}"
|
|
1144
|
+
if p["last_sync_attempt"] != 0:
|
|
1145
|
+
lsa = p["last_sync_attempt"]
|
|
1146
|
+
ls = f"last synced {RNS.prettytime(max(time.time()-lsa, 0))} ago"
|
|
1147
|
+
else:
|
|
1148
|
+
ls = "never synced"
|
|
1149
|
+
|
|
1150
|
+
sstr = RNS.prettyspeed(p["str"])
|
|
1151
|
+
sler = RNS.prettyspeed(p["ler"])
|
|
1152
|
+
stl = (
|
|
1153
|
+
RNS.prettysize(p["transfer_limit"] * 1000)
|
|
1154
|
+
if p["transfer_limit"]
|
|
1155
|
+
else "Unknown"
|
|
1156
|
+
)
|
|
1157
|
+
ssl = (
|
|
1158
|
+
RNS.prettysize(p["sync_limit"] * 1000)
|
|
1159
|
+
if p["sync_limit"]
|
|
1160
|
+
else "unknown"
|
|
1161
|
+
)
|
|
1162
|
+
srxb = RNS.prettysize(p["rx_bytes"])
|
|
1163
|
+
stxb = RNS.prettysize(p["tx_bytes"])
|
|
1164
|
+
pmo = pm["offered"]
|
|
1165
|
+
pmout = pm["outgoing"]
|
|
1166
|
+
pmi = pm["incoming"]
|
|
1167
|
+
pmuh = pm["unhandled"]
|
|
1168
|
+
ar = round(p["acceptance_rate"] * 100, 2)
|
|
1169
|
+
if p["name"] is None:
|
|
1170
|
+
nn = ""
|
|
1171
|
+
else:
|
|
1172
|
+
nn = p["name"].strip().replace("\n", "").replace("\r", "")
|
|
1173
|
+
if len(nn) > 45:
|
|
1174
|
+
nn = f"{nn[:45]}..."
|
|
1175
|
+
print(f"{ind}{t}{RNS.prettyhexrep(peer_id)}")
|
|
1176
|
+
if len(nn):
|
|
1177
|
+
print(f"{ind*2}Name : {nn}")
|
|
1178
|
+
print(
|
|
1179
|
+
f"{ind*2}Status : {a}, {hs}, last heard {RNS.prettytime(h)} ago"
|
|
1180
|
+
)
|
|
1181
|
+
print(
|
|
1182
|
+
f"{ind*2}Costs : Propagation {psc} (flex {psf}), peering {pc}"
|
|
1183
|
+
)
|
|
1184
|
+
print(f"{ind*2}Sync key : {pk}")
|
|
1185
|
+
print(f"{ind*2}Speeds : {sstr} STR, {sler} LER")
|
|
1186
|
+
print(f"{ind*2}Limits : {stl} message limit, {ssl} sync limit")
|
|
1187
|
+
print(
|
|
1188
|
+
f"{ind*2}Messages : {pmo} offered, {pmout} outgoing, {pmi} incoming, {ar}% acceptance rate"
|
|
1189
|
+
)
|
|
1190
|
+
print(f"{ind*2}Traffic : {srxb} received, {stxb} sent")
|
|
1191
|
+
ms = "" if pm["unhandled"] == 1 else "s"
|
|
1192
|
+
print(f"{ind*2}Sync state : {pmuh} unhandled message{ms}, {ls}")
|
|
1193
|
+
print("")
|
|
1194
|
+
|
|
1195
|
+
|
|
1196
|
+
def _get_target_identity(remote=None, timeout=5):
|
|
1197
|
+
global identity
|
|
1198
|
+
timeout = time.time() + timeout
|
|
1199
|
+
|
|
1200
|
+
def check_timeout():
|
|
1201
|
+
if time.time() > timeout:
|
|
1202
|
+
print("Resolving remote identity timed out, exiting now")
|
|
1203
|
+
exit(200)
|
|
1204
|
+
else:
|
|
1205
|
+
time.sleep(0.1)
|
|
1206
|
+
|
|
1207
|
+
if remote is None:
|
|
1208
|
+
return identity
|
|
1209
|
+
else:
|
|
1210
|
+
try:
|
|
1211
|
+
destination_hash = bytes.fromhex(remote)
|
|
1212
|
+
if len(destination_hash) != RNS.Identity.TRUNCATED_HASHLENGTH // 8:
|
|
1213
|
+
raise ValueError(
|
|
1214
|
+
f"Destination hash length must be {RNS.Identity.TRUNCATED_HASHLENGTH//8*2} characters"
|
|
1215
|
+
)
|
|
1216
|
+
except Exception as e:
|
|
1217
|
+
print(f"Invalid remote destination hash: {e}")
|
|
1218
|
+
exit(203)
|
|
1219
|
+
|
|
1220
|
+
remote_identity = RNS.Identity.recall(destination_hash)
|
|
1221
|
+
if remote_identity:
|
|
1222
|
+
return remote_identity
|
|
1223
|
+
else:
|
|
1224
|
+
if not RNS.Transport.has_path(destination_hash):
|
|
1225
|
+
RNS.Transport.request_path(destination_hash)
|
|
1226
|
+
while not RNS.Transport.has_path(destination_hash):
|
|
1227
|
+
check_timeout()
|
|
1228
|
+
|
|
1229
|
+
return RNS.Identity.recall(destination_hash)
|
|
1230
|
+
|
|
1231
|
+
|
|
1232
|
+
def _remote_init(
|
|
1233
|
+
configdir=None, rnsconfigdir=None, verbosity=0, quietness=0, identity_path=None
|
|
1234
|
+
):
|
|
1235
|
+
global configpath, identitypath, storagedir, lxmdir, identity
|
|
1236
|
+
global lxmd_config, active_configuration, targetloglevel
|
|
1237
|
+
targetlogdest = RNS.LOG_STDOUT
|
|
1238
|
+
|
|
1239
|
+
if identity_path is None:
|
|
1240
|
+
if configdir is None:
|
|
1241
|
+
configdir = _default_config_directory()
|
|
1242
|
+
|
|
1243
|
+
configdir = str(configdir)
|
|
1244
|
+
|
|
1245
|
+
configpath = configdir + "/config"
|
|
1246
|
+
identitypath = configdir + "/identity"
|
|
1247
|
+
identity = None
|
|
1248
|
+
|
|
1249
|
+
if not os.path.isdir(configdir):
|
|
1250
|
+
RNS.log(
|
|
1251
|
+
"Specified configuration directory does not exist, exiting now",
|
|
1252
|
+
RNS.LOG_ERROR,
|
|
1253
|
+
)
|
|
1254
|
+
exit(201)
|
|
1255
|
+
if not os.path.isfile(identitypath):
|
|
1256
|
+
RNS.log(
|
|
1257
|
+
"Identity file not found in specified configuration directory, exiting now",
|
|
1258
|
+
RNS.LOG_ERROR,
|
|
1259
|
+
)
|
|
1260
|
+
exit(202)
|
|
1261
|
+
else:
|
|
1262
|
+
identity = RNS.Identity.from_file(identitypath)
|
|
1263
|
+
if identity is None:
|
|
1264
|
+
RNS.log(
|
|
1265
|
+
"Could not load the Primary Identity from " + identitypath,
|
|
1266
|
+
RNS.LOG_ERROR,
|
|
1267
|
+
)
|
|
1268
|
+
exit(4)
|
|
1269
|
+
|
|
1270
|
+
else:
|
|
1271
|
+
if not os.path.isfile(identity_path):
|
|
1272
|
+
RNS.log(
|
|
1273
|
+
"Identity file not found in specified configuration directory, exiting now",
|
|
1274
|
+
RNS.LOG_ERROR,
|
|
1275
|
+
)
|
|
1276
|
+
exit(202)
|
|
1277
|
+
else:
|
|
1278
|
+
identity = RNS.Identity.from_file(identity_path)
|
|
1279
|
+
if identity is None:
|
|
1280
|
+
RNS.log(
|
|
1281
|
+
"Could not load the Primary Identity from " + identity_path,
|
|
1282
|
+
RNS.LOG_ERROR,
|
|
1283
|
+
)
|
|
1284
|
+
exit(4)
|
|
1285
|
+
|
|
1286
|
+
if targetloglevel is None:
|
|
1287
|
+
targetloglevel = 3
|
|
1288
|
+
if verbosity != 0 or quietness != 0:
|
|
1289
|
+
targetloglevel = targetloglevel + verbosity - quietness
|
|
1290
|
+
|
|
1291
|
+
reticulum = RNS.Reticulum(
|
|
1292
|
+
configdir=rnsconfigdir, loglevel=targetloglevel, logdest=targetlogdest
|
|
1293
|
+
)
|
|
1294
|
+
|
|
1295
|
+
|
|
1296
|
+
def main():
|
|
1297
|
+
try:
|
|
1298
|
+
parser = argparse.ArgumentParser(
|
|
1299
|
+
description="Lightweight Extensible Messaging Daemon"
|
|
1300
|
+
)
|
|
1301
|
+
parser.add_argument(
|
|
1302
|
+
"--config",
|
|
1303
|
+
action="store",
|
|
1304
|
+
default=None,
|
|
1305
|
+
help="path to alternative lxmd config directory",
|
|
1306
|
+
type=str,
|
|
1307
|
+
)
|
|
1308
|
+
parser.add_argument(
|
|
1309
|
+
"--rnsconfig",
|
|
1310
|
+
action="store",
|
|
1311
|
+
default=None,
|
|
1312
|
+
help="path to alternative Reticulum config directory",
|
|
1313
|
+
type=str,
|
|
1314
|
+
)
|
|
1315
|
+
parser.add_argument(
|
|
1316
|
+
"-p",
|
|
1317
|
+
"--propagation-node",
|
|
1318
|
+
action="store_true",
|
|
1319
|
+
default=False,
|
|
1320
|
+
help="run an LXMF Propagation Node",
|
|
1321
|
+
)
|
|
1322
|
+
parser.add_argument(
|
|
1323
|
+
"-i",
|
|
1324
|
+
"--on-inbound",
|
|
1325
|
+
action="store",
|
|
1326
|
+
metavar="PATH",
|
|
1327
|
+
default=None,
|
|
1328
|
+
help="executable to run when a message is received",
|
|
1329
|
+
type=str,
|
|
1330
|
+
)
|
|
1331
|
+
parser.add_argument("-v", "--verbose", action="count", default=0)
|
|
1332
|
+
parser.add_argument("-q", "--quiet", action="count", default=0)
|
|
1333
|
+
parser.add_argument(
|
|
1334
|
+
"-s",
|
|
1335
|
+
"--service",
|
|
1336
|
+
action="store_true",
|
|
1337
|
+
default=False,
|
|
1338
|
+
help="lxmd is running as a service and should log to file",
|
|
1339
|
+
)
|
|
1340
|
+
parser.add_argument(
|
|
1341
|
+
"--status", action="store_true", default=False, help="display node status"
|
|
1342
|
+
)
|
|
1343
|
+
parser.add_argument(
|
|
1344
|
+
"--peers", action="store_true", default=False, help="display peered nodes"
|
|
1345
|
+
)
|
|
1346
|
+
parser.add_argument(
|
|
1347
|
+
"--sync",
|
|
1348
|
+
action="store",
|
|
1349
|
+
default=None,
|
|
1350
|
+
help="request a sync with the specified peer",
|
|
1351
|
+
type=str,
|
|
1352
|
+
)
|
|
1353
|
+
parser.add_argument(
|
|
1354
|
+
"-b",
|
|
1355
|
+
"--break",
|
|
1356
|
+
dest="unpeer",
|
|
1357
|
+
action="store",
|
|
1358
|
+
default=None,
|
|
1359
|
+
help="break peering with the specified peer",
|
|
1360
|
+
type=str,
|
|
1361
|
+
)
|
|
1362
|
+
parser.add_argument(
|
|
1363
|
+
"--timeout",
|
|
1364
|
+
action="store",
|
|
1365
|
+
default=None,
|
|
1366
|
+
help="timeout in seconds for query operations",
|
|
1367
|
+
type=float,
|
|
1368
|
+
)
|
|
1369
|
+
parser.add_argument(
|
|
1370
|
+
"-r",
|
|
1371
|
+
"--remote",
|
|
1372
|
+
action="store",
|
|
1373
|
+
default=None,
|
|
1374
|
+
help="remote propagation node destination hash",
|
|
1375
|
+
type=str,
|
|
1376
|
+
)
|
|
1377
|
+
parser.add_argument(
|
|
1378
|
+
"--identity",
|
|
1379
|
+
action="store",
|
|
1380
|
+
default=None,
|
|
1381
|
+
help="path to identity used for remote requests",
|
|
1382
|
+
type=str,
|
|
1383
|
+
)
|
|
1384
|
+
parser.add_argument(
|
|
1385
|
+
"--exampleconfig",
|
|
1386
|
+
action="store_true",
|
|
1387
|
+
default=False,
|
|
1388
|
+
help="print verbose configuration example to stdout and exit",
|
|
1389
|
+
)
|
|
1390
|
+
parser.add_argument(
|
|
1391
|
+
"--version",
|
|
1392
|
+
action="version",
|
|
1393
|
+
version="lxmd {version}".format(version=__version__),
|
|
1394
|
+
)
|
|
1395
|
+
|
|
1396
|
+
args = parser.parse_args()
|
|
1397
|
+
|
|
1398
|
+
if args.exampleconfig:
|
|
1399
|
+
print(__default_lxmd_config__)
|
|
1400
|
+
exit()
|
|
1401
|
+
|
|
1402
|
+
if args.status or args.peers:
|
|
1403
|
+
if not args.timeout:
|
|
1404
|
+
args.timeout = 5
|
|
1405
|
+
get_status(
|
|
1406
|
+
configdir=args.config,
|
|
1407
|
+
rnsconfigdir=args.rnsconfig,
|
|
1408
|
+
verbosity=args.verbose,
|
|
1409
|
+
quietness=args.quiet,
|
|
1410
|
+
timeout=args.timeout,
|
|
1411
|
+
show_status=args.status,
|
|
1412
|
+
show_peers=args.peers,
|
|
1413
|
+
identity_path=args.identity,
|
|
1414
|
+
remote=args.remote,
|
|
1415
|
+
)
|
|
1416
|
+
exit()
|
|
1417
|
+
|
|
1418
|
+
if args.sync:
|
|
1419
|
+
if not args.timeout:
|
|
1420
|
+
args.timeout = 10
|
|
1421
|
+
request_sync(
|
|
1422
|
+
target=args.sync,
|
|
1423
|
+
configdir=args.config,
|
|
1424
|
+
rnsconfigdir=args.rnsconfig,
|
|
1425
|
+
verbosity=args.verbose,
|
|
1426
|
+
quietness=args.quiet,
|
|
1427
|
+
timeout=args.timeout,
|
|
1428
|
+
identity_path=args.identity,
|
|
1429
|
+
remote=args.remote,
|
|
1430
|
+
)
|
|
1431
|
+
exit()
|
|
1432
|
+
|
|
1433
|
+
if args.unpeer:
|
|
1434
|
+
if not args.timeout:
|
|
1435
|
+
args.timeout = 10
|
|
1436
|
+
request_unpeer(
|
|
1437
|
+
target=args.unpeer,
|
|
1438
|
+
configdir=args.config,
|
|
1439
|
+
rnsconfigdir=args.rnsconfig,
|
|
1440
|
+
verbosity=args.verbose,
|
|
1441
|
+
quietness=args.quiet,
|
|
1442
|
+
timeout=args.timeout,
|
|
1443
|
+
identity_path=args.identity,
|
|
1444
|
+
remote=args.remote,
|
|
1445
|
+
)
|
|
1446
|
+
exit()
|
|
1447
|
+
|
|
1448
|
+
program_setup(
|
|
1449
|
+
configdir=args.config,
|
|
1450
|
+
rnsconfigdir=args.rnsconfig,
|
|
1451
|
+
run_pn=args.propagation_node,
|
|
1452
|
+
on_inbound=args.on_inbound,
|
|
1453
|
+
verbosity=args.verbose,
|
|
1454
|
+
quietness=args.quiet,
|
|
1455
|
+
service=args.service,
|
|
1456
|
+
)
|
|
1457
|
+
|
|
1458
|
+
except KeyboardInterrupt:
|
|
1459
|
+
print("")
|
|
1460
|
+
exit()
|
|
1461
|
+
|
|
1462
|
+
|
|
1463
|
+
__default_lxmd_config__ = """# This is an example LXM Daemon config file.
|
|
1464
|
+
# You should probably edit it to suit your
|
|
1465
|
+
# intended usage.
|
|
1466
|
+
|
|
1467
|
+
[propagation]
|
|
1468
|
+
|
|
1469
|
+
# Whether to enable propagation node
|
|
1470
|
+
|
|
1471
|
+
enable_node = no
|
|
1472
|
+
|
|
1473
|
+
# You can specify identity hashes for remotes
|
|
1474
|
+
# that are allowed to control and query status
|
|
1475
|
+
# for this propagation node.
|
|
1476
|
+
|
|
1477
|
+
# control_allowed = 7d7e542829b40f32364499b27438dba8, 437229f8e29598b2282b88bad5e44698
|
|
1478
|
+
|
|
1479
|
+
# An optional name for this node, included
|
|
1480
|
+
# in announces.
|
|
1481
|
+
|
|
1482
|
+
# node_name = Anonymous Propagation Node
|
|
1483
|
+
|
|
1484
|
+
# Automatic announce interval in minutes.
|
|
1485
|
+
# 6 hours by default.
|
|
1486
|
+
|
|
1487
|
+
announce_interval = 360
|
|
1488
|
+
|
|
1489
|
+
# Whether to announce when the node starts.
|
|
1490
|
+
|
|
1491
|
+
announce_at_start = yes
|
|
1492
|
+
|
|
1493
|
+
# Wheter to automatically peer with other
|
|
1494
|
+
# propagation nodes on the network.
|
|
1495
|
+
|
|
1496
|
+
autopeer = yes
|
|
1497
|
+
|
|
1498
|
+
# The maximum peering depth (in hops) for
|
|
1499
|
+
# automatically peered nodes.
|
|
1500
|
+
|
|
1501
|
+
autopeer_maxdepth = 4
|
|
1502
|
+
|
|
1503
|
+
# The maximum amount of storage to use for
|
|
1504
|
+
# the LXMF Propagation Node message store,
|
|
1505
|
+
# specified in megabytes. When this limit
|
|
1506
|
+
# is reached, LXMF will periodically remove
|
|
1507
|
+
# messages in its message store. By default,
|
|
1508
|
+
# LXMF prioritises keeping messages that are
|
|
1509
|
+
# new and small. Large and old messages will
|
|
1510
|
+
# be removed first. This setting is optional
|
|
1511
|
+
# and defaults to 500 megabytes.
|
|
1512
|
+
|
|
1513
|
+
# message_storage_limit = 500
|
|
1514
|
+
|
|
1515
|
+
# The maximum accepted transfer size per in-
|
|
1516
|
+
# coming propagation message, in kilobytes.
|
|
1517
|
+
# This sets the upper limit for the size of
|
|
1518
|
+
# single messages accepted onto this node.
|
|
1519
|
+
|
|
1520
|
+
# propagation_message_max_accepted_size = 256
|
|
1521
|
+
|
|
1522
|
+
# The maximum accepted transfer size per in-
|
|
1523
|
+
# coming propagation node sync.
|
|
1524
|
+
#
|
|
1525
|
+
# If a node wants to propagate a larger number
|
|
1526
|
+
# of messages to this node, than what can fit
|
|
1527
|
+
# within this limit, it will prioritise sending
|
|
1528
|
+
# the smallest messages first, and try again
|
|
1529
|
+
# with any remaining messages at a later point.
|
|
1530
|
+
|
|
1531
|
+
# propagation_sync_max_accepted_size = 10240
|
|
1532
|
+
|
|
1533
|
+
# You can configure the target stamp cost
|
|
1534
|
+
# required to deliver messages via this node.
|
|
1535
|
+
|
|
1536
|
+
# propagation_stamp_cost_target = 16
|
|
1537
|
+
|
|
1538
|
+
# If set higher than 0, the stamp cost flexi-
|
|
1539
|
+
# bility option will make this node accept
|
|
1540
|
+
# messages with a lower stamp cost than the
|
|
1541
|
+
# target from other propagation nodes (but
|
|
1542
|
+
# not from peers directly). This allows the
|
|
1543
|
+
# network to gradually adjust stamp cost.
|
|
1544
|
+
|
|
1545
|
+
# propagation_stamp_cost_flexibility = 3
|
|
1546
|
+
|
|
1547
|
+
# The peering_cost option configures the target
|
|
1548
|
+
# value required for a remote node to peer with
|
|
1549
|
+
# and deliver messages to this node.
|
|
1550
|
+
|
|
1551
|
+
# peering_cost = 18
|
|
1552
|
+
|
|
1553
|
+
# You can configure the maximum peering cost
|
|
1554
|
+
# of remote nodes that this node will peer with.
|
|
1555
|
+
# Setting this to a higher number will allow
|
|
1556
|
+
# this node to peer with other nodes requiring
|
|
1557
|
+
# a higher peering key value, but will require
|
|
1558
|
+
# more computation time during initial peering
|
|
1559
|
+
# when generating the peering key.
|
|
1560
|
+
|
|
1561
|
+
# remote_peering_cost_max = 26
|
|
1562
|
+
|
|
1563
|
+
# You can tell the LXMF message router to
|
|
1564
|
+
# prioritise storage for one or more
|
|
1565
|
+
# destinations. If the message store reaches
|
|
1566
|
+
# the specified limit, LXMF will prioritise
|
|
1567
|
+
# keeping messages for destinations specified
|
|
1568
|
+
# with this option. This setting is optional,
|
|
1569
|
+
# and generally you do not need to use it.
|
|
1570
|
+
|
|
1571
|
+
# prioritise_destinations = 41d20c727598a3fbbdf9106133a3a0ed, d924b81822ca24e68e2effea99bcb8cf
|
|
1572
|
+
|
|
1573
|
+
# You can configure the maximum number of other
|
|
1574
|
+
# propagation nodes that this node will peer
|
|
1575
|
+
# with automatically. The default is 20.
|
|
1576
|
+
|
|
1577
|
+
# max_peers = 20
|
|
1578
|
+
|
|
1579
|
+
# You can configure a list of static propagation
|
|
1580
|
+
# node peers, that this node will always be
|
|
1581
|
+
# peered with, by specifying a list of
|
|
1582
|
+
# destination hashes.
|
|
1583
|
+
|
|
1584
|
+
# static_peers = e17f833c4ddf8890dd3a79a6fea8161d, 5a2d0029b6e5ec87020abaea0d746da4
|
|
1585
|
+
|
|
1586
|
+
# You can configure the propagation node to
|
|
1587
|
+
# only accept incoming propagation messages
|
|
1588
|
+
# from configured static peers.
|
|
1589
|
+
|
|
1590
|
+
# from_static_only = True
|
|
1591
|
+
|
|
1592
|
+
# By default, any destination is allowed to
|
|
1593
|
+
# connect and download messages, but you can
|
|
1594
|
+
# optionally restrict this. If you enable
|
|
1595
|
+
# authentication, you must provide a list of
|
|
1596
|
+
# allowed identity hashes in the a file named
|
|
1597
|
+
# "allowed" in the lxmd config directory.
|
|
1598
|
+
|
|
1599
|
+
auth_required = no
|
|
1600
|
+
|
|
1601
|
+
|
|
1602
|
+
[lxmf]
|
|
1603
|
+
|
|
1604
|
+
# The LXM Daemon will create an LXMF destination
|
|
1605
|
+
# that it can receive messages on. This option sets
|
|
1606
|
+
# the announced display name for this destination.
|
|
1607
|
+
|
|
1608
|
+
display_name = Anonymous Peer
|
|
1609
|
+
|
|
1610
|
+
# It is possible to announce the internal LXMF
|
|
1611
|
+
# destination when the LXM Daemon starts up.
|
|
1612
|
+
|
|
1613
|
+
announce_at_start = no
|
|
1614
|
+
|
|
1615
|
+
# You can also announce the delivery destination
|
|
1616
|
+
# at a specified interval. This is not enabled by
|
|
1617
|
+
# default.
|
|
1618
|
+
|
|
1619
|
+
# announce_interval = 360
|
|
1620
|
+
|
|
1621
|
+
# The maximum accepted unpacked size for mes-
|
|
1622
|
+
# sages received directly from other peers,
|
|
1623
|
+
# specified in kilobytes. Messages larger than
|
|
1624
|
+
# this will be rejected before the transfer
|
|
1625
|
+
# begins.
|
|
1626
|
+
|
|
1627
|
+
delivery_transfer_max_accepted_size = 1000
|
|
1628
|
+
|
|
1629
|
+
# You can configure an external program to be run
|
|
1630
|
+
# every time a message is received. The program
|
|
1631
|
+
# will receive as an argument the full path to the
|
|
1632
|
+
# message saved as a file. The example below will
|
|
1633
|
+
# simply result in the message getting deleted as
|
|
1634
|
+
# soon as it has been received.
|
|
1635
|
+
|
|
1636
|
+
# on_inbound = rm
|
|
1637
|
+
|
|
1638
|
+
|
|
1639
|
+
[logging]
|
|
1640
|
+
# Valid log levels are 0 through 7:
|
|
1641
|
+
# 0: Log only critical information
|
|
1642
|
+
# 1: Log errors and lower log levels
|
|
1643
|
+
# 2: Log warnings and lower log levels
|
|
1644
|
+
# 3: Log notices and lower log levels
|
|
1645
|
+
# 4: Log info and lower (this is the default)
|
|
1646
|
+
# 5: Verbose logging
|
|
1647
|
+
# 6: Debug logging
|
|
1648
|
+
# 7: Extreme logging
|
|
1649
|
+
|
|
1650
|
+
loglevel = 4
|
|
1651
|
+
|
|
1652
|
+
"""
|
|
1653
|
+
|
|
1654
|
+
if __name__ == "__main__":
|
|
1655
|
+
main()
|