2021-09-24 12:42:24 +02:00
#!/usr/bin/env python3
2022-04-01 17:18:18 +02:00
# MIT License
#
# Copyright (c) 2016-2022 Mark Qvist / unsigned.io
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
2021-09-24 12:42:24 +02:00
import RNS
import sys
import time
import argparse
from RNS . _version import __version__
2022-05-14 22:14:38 +02:00
def program_setup ( configdir , table , rates , drop , destination_hexhash , verbosity , timeout , drop_queues ) :
2022-04-20 10:40:51 +02:00
if table :
2022-05-14 22:14:38 +02:00
destination_hash = None
if destination_hexhash != None :
try :
dest_len = ( RNS . Reticulum . TRUNCATED_HASHLENGTH / / 8 ) * 2
if len ( destination_hexhash ) != dest_len :
raise ValueError ( " Destination length is invalid, must be {hex} hexadecimal characters ( {byte} bytes). " . format ( hex = dest_len , byte = dest_len / / 2 ) )
try :
destination_hash = bytes . fromhex ( destination_hexhash )
except Exception as e :
raise ValueError ( " Invalid destination entered. Check your input. " )
except Exception as e :
print ( str ( e ) )
sys . exit ( 1 )
2022-04-20 10:40:51 +02:00
reticulum = RNS . Reticulum ( configdir = configdir , loglevel = 3 + verbosity )
table = sorted ( reticulum . get_path_table ( ) , key = lambda e : ( e [ " interface " ] , e [ " hops " ] ) )
2021-09-24 12:42:24 +02:00
2022-05-14 22:14:38 +02:00
displayed = 0
2022-04-20 10:40:51 +02:00
for path in table :
2022-05-14 22:14:38 +02:00
if destination_hash == None or destination_hash == path [ " hash " ] :
displayed + = 1
exp_str = RNS . timestamp_str ( path [ " expires " ] )
if path [ " hops " ] == 1 :
m_str = " "
else :
m_str = " s "
print ( RNS . prettyhexrep ( path [ " hash " ] ) + " is " + str ( path [ " hops " ] ) + " hop " + m_str + " away via " + RNS . prettyhexrep ( path [ " via " ] ) + " on " + path [ " interface " ] + " expires " + RNS . timestamp_str ( path [ " expires " ] ) )
if destination_hash != None and displayed == 0 :
print ( " No path known " )
sys . exit ( 1 )
elif rates :
destination_hash = None
if destination_hexhash != None :
try :
dest_len = ( RNS . Reticulum . TRUNCATED_HASHLENGTH / / 8 ) * 2
if len ( destination_hexhash ) != dest_len :
raise ValueError ( " Destination length is invalid, must be {hex} hexadecimal characters ( {byte} bytes). " . format ( hex = dest_len , byte = dest_len / / 2 ) )
try :
destination_hash = bytes . fromhex ( destination_hexhash )
except Exception as e :
raise ValueError ( " Invalid destination entered. Check your input. " )
except Exception as e :
print ( str ( e ) )
sys . exit ( 1 )
reticulum = RNS . Reticulum ( configdir = configdir , loglevel = 3 + verbosity )
table = sorted ( reticulum . get_rate_table ( ) , key = lambda e : e [ " last " ] )
if len ( table ) == 0 :
print ( " No information available " )
else :
displayed = 0
for entry in table :
if destination_hash == None or destination_hash == entry [ " hash " ] :
displayed + = 1
try :
last_str = pretty_date ( int ( entry [ " last " ] ) )
start_ts = entry [ " timestamps " ] [ 0 ]
span = max ( time . time ( ) - start_ts , 3600.0 )
span_hours = span / 3600.0
span_str = pretty_date ( int ( entry [ " timestamps " ] [ 0 ] ) )
hour_rate = round ( len ( entry [ " timestamps " ] ) / span_hours , 3 )
if hour_rate - int ( hour_rate ) == 0 :
hour_rate = int ( hour_rate )
if entry [ " rate_violations " ] > 0 :
if entry [ " rate_violations " ] == 1 :
s_str = " "
else :
s_str = " s "
rv_str = " , " + str ( entry [ " rate_violations " ] ) + " active rate violation " + s_str
else :
rv_str = " "
if entry [ " blocked_until " ] > time . time ( ) :
bli = time . time ( ) - ( int ( entry [ " blocked_until " ] ) - time . time ( ) )
bl_str = " , new announces allowed in " + pretty_date ( int ( bli ) )
else :
bl_str = " "
print ( RNS . prettyhexrep ( entry [ " hash " ] ) + " last heard " + last_str + " ago, " + str ( hour_rate ) + " announces/hour in the last " + span_str + rv_str + bl_str )
except Exception as e :
print ( " Error while processing entry for " + RNS . prettyhexrep ( entry [ " hash " ] ) )
print ( str ( e ) )
if destination_hash != None and displayed == 0 :
print ( " No information available " )
sys . exit ( 1 )
2021-09-24 14:16:25 +02:00
2022-05-13 16:18:13 +02:00
elif drop_queues :
reticulum = RNS . Reticulum ( configdir = configdir , loglevel = 3 + verbosity )
RNS . log ( " Dropping announce queues on all interfaces... " )
reticulum . drop_announce_queues ( )
2022-04-20 11:12:21 +02:00
elif drop :
try :
dest_len = ( RNS . Reticulum . TRUNCATED_HASHLENGTH / / 8 ) * 2
if len ( destination_hexhash ) != dest_len :
raise ValueError ( " Destination length is invalid, must be {hex} hexadecimal characters ( {byte} bytes). " . format ( hex = dest_len , byte = dest_len / / 2 ) )
try :
destination_hash = bytes . fromhex ( destination_hexhash )
except Exception as e :
raise ValueError ( " Invalid destination entered. Check your input. " )
except Exception as e :
print ( str ( e ) )
2022-05-14 22:14:38 +02:00
sys . exit ( 1 )
2022-04-20 11:12:21 +02:00
reticulum = RNS . Reticulum ( configdir = configdir , loglevel = 3 + verbosity )
if reticulum . drop_path ( destination_hash ) :
print ( " Dropped path to " + RNS . prettyhexrep ( destination_hash ) )
else :
print ( " Unable to drop path to " + RNS . prettyhexrep ( destination_hash ) + " . Does it exist? " )
2022-05-14 22:14:38 +02:00
sys . exit ( 1 )
2022-04-20 11:12:21 +02:00
2021-09-24 15:34:03 +02:00
else :
2022-04-20 10:40:51 +02:00
try :
dest_len = ( RNS . Reticulum . TRUNCATED_HASHLENGTH / / 8 ) * 2
if len ( destination_hexhash ) != dest_len :
raise ValueError ( " Destination length is invalid, must be {hex} hexadecimal characters ( {byte} bytes). " . format ( hex = dest_len , byte = dest_len / / 2 ) )
try :
destination_hash = bytes . fromhex ( destination_hexhash )
except Exception as e :
raise ValueError ( " Invalid destination entered. Check your input. " )
except Exception as e :
print ( str ( e ) )
2022-05-14 22:14:38 +02:00
sys . exit ( 1 )
2022-04-20 10:40:51 +02:00
reticulum = RNS . Reticulum ( configdir = configdir , loglevel = 3 + verbosity )
if not RNS . Transport . has_path ( destination_hash ) :
RNS . Transport . request_path ( destination_hash )
print ( " Path to " + RNS . prettyhexrep ( destination_hash ) + " requested " , end = " " )
sys . stdout . flush ( )
i = 0
syms = " ⢄⢂⢁⡁⡈⡐⡠ "
2022-04-20 13:40:07 +02:00
limit = time . time ( ) + timeout
while not RNS . Transport . has_path ( destination_hash ) and time . time ( ) < limit :
2022-04-20 10:40:51 +02:00
time . sleep ( 0.1 )
print ( ( " \b \b " + syms [ i ] + " " ) , end = " " )
sys . stdout . flush ( )
i = ( i + 1 ) % len ( syms )
2022-04-20 13:40:07 +02:00
if RNS . Transport . has_path ( destination_hash ) :
hops = RNS . Transport . hops_to ( destination_hash )
next_hop = RNS . prettyhexrep ( reticulum . get_next_hop ( destination_hash ) )
next_hop_interface = reticulum . get_next_hop_if_name ( destination_hash )
2022-04-20 10:40:51 +02:00
2022-04-20 13:40:07 +02:00
if hops != 1 :
ms = " s "
else :
ms = " "
2021-09-24 15:34:03 +02:00
2022-04-20 13:40:07 +02:00
print ( " \r Path found, destination " + RNS . prettyhexrep ( destination_hash ) + " is " + str ( hops ) + " hop " + ms + " away via " + next_hop + " on " + next_hop_interface )
else :
print ( " \r \r Path not found " )
2022-05-14 22:14:38 +02:00
sys . exit ( 1 )
2021-09-24 12:42:24 +02:00
def main ( ) :
try :
parser = argparse . ArgumentParser ( description = " Reticulum Path Discovery Utility " )
parser . add_argument ( " --config " ,
action = " store " ,
default = None ,
help = " path to alternative Reticulum config directory " ,
type = str
)
parser . add_argument (
" --version " ,
action = " version " ,
version = " rnpath {version} " . format ( version = __version__ )
)
2022-04-20 10:40:51 +02:00
parser . add_argument (
" -t " ,
" --table " ,
action = " store_true " ,
help = " show all known paths " ,
default = False
)
2022-05-14 22:14:38 +02:00
parser . add_argument (
" -r " ,
" --rates " ,
action = " store_true " ,
help = " show announce rate info " ,
default = False
)
2022-04-20 11:12:21 +02:00
parser . add_argument (
" -d " ,
" --drop " ,
action = " store_true " ,
help = " remove the path to a destination " ,
default = False
)
2022-05-13 16:18:13 +02:00
parser . add_argument (
" -D " ,
" --drop-announces " ,
action = " store_true " ,
help = " drop all queued announces " ,
default = False
)
2022-04-20 13:40:07 +02:00
parser . add_argument (
" -w " ,
action = " store " ,
metavar = " seconds " ,
type = float ,
help = " timeout before giving up " ,
default = 15
)
2021-09-24 12:42:24 +02:00
parser . add_argument (
" destination " ,
nargs = " ? " ,
default = None ,
help = " hexadecimal hash of the destination " ,
type = str
)
2021-09-24 15:34:03 +02:00
parser . add_argument ( ' -v ' , ' --verbose ' , action = ' count ' , default = 0 )
2021-09-24 12:42:24 +02:00
args = parser . parse_args ( )
if args . config :
configarg = args . config
else :
configarg = None
2022-05-14 22:14:38 +02:00
if not args . drop_announces and not args . table and not args . rates and not args . destination :
2021-09-24 12:42:24 +02:00
print ( " " )
parser . print_help ( )
print ( " " )
else :
2022-04-20 11:12:21 +02:00
program_setup (
configdir = configarg ,
table = args . table ,
2022-05-14 22:14:38 +02:00
rates = args . rates ,
2022-04-20 11:12:21 +02:00
drop = args . drop ,
destination_hexhash = args . destination ,
2022-04-20 13:40:07 +02:00
verbosity = args . verbose ,
timeout = args . w ,
2022-05-13 16:18:13 +02:00
drop_queues = args . drop_announces ,
2022-04-20 11:12:21 +02:00
)
2022-05-14 22:14:38 +02:00
sys . exit ( 0 )
2021-09-24 12:42:24 +02:00
except KeyboardInterrupt :
print ( " " )
exit ( )
2022-05-14 22:14:38 +02:00
def pretty_date ( time = False ) :
from datetime import datetime
now = datetime . now ( )
if type ( time ) is int :
diff = now - datetime . fromtimestamp ( time )
elif isinstance ( time , datetime ) :
diff = now - time
elif not time :
diff = now - now
second_diff = diff . seconds
day_diff = diff . days
if day_diff < 0 :
return ' '
if day_diff == 0 :
if second_diff < 10 :
return str ( second_diff ) + " seconds "
if second_diff < 60 :
return str ( second_diff ) + " seconds "
if second_diff < 120 :
return " 1 minute "
if second_diff < 3600 :
return str ( int ( second_diff / 60 ) ) + " minutes "
if second_diff < 7200 :
2022-05-18 00:47:29 +02:00
return " an hour "
2022-05-14 22:14:38 +02:00
if second_diff < 86400 :
return str ( int ( second_diff / 3600 ) ) + " hours "
if day_diff == 1 :
return " 1 day "
if day_diff < 7 :
return str ( day_diff ) + " days "
if day_diff < 31 :
return str ( int ( day_diff / 7 ) ) + " weeks "
if day_diff < 365 :
return str ( int ( day_diff / 30 ) ) + " months "
return str ( int ( day_diff / 365 ) ) + " years "
2021-09-24 12:42:24 +02:00
if __name__ == " __main__ " :
main ( )