BootstrapperPlugin.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. import time
  2. from util import helper
  3. from Plugin import PluginManager
  4. from .BootstrapperDb import BootstrapperDb
  5. from Crypt import CryptRsa
  6. from Config import config
  7. if "db" not in locals().keys(): # Share during reloads
  8. db = BootstrapperDb()
  9. @PluginManager.registerTo("FileRequest")
  10. class FileRequestPlugin(object):
  11. def checkOnionSigns(self, onions, onion_signs, onion_sign_this):
  12. if not onion_signs or len(onion_signs) != len(set(onions)):
  13. return False
  14. if time.time() - float(onion_sign_this) > 3 * 60:
  15. return False # Signed out of allowed 3 minutes
  16. onions_signed = []
  17. # Check onion signs
  18. for onion_publickey, onion_sign in onion_signs.items():
  19. if CryptRsa.verify(onion_sign_this.encode(), onion_publickey, onion_sign):
  20. onions_signed.append(CryptRsa.publickeyToOnion(onion_publickey))
  21. else:
  22. break
  23. # Check if the same onion addresses signed as the announced onces
  24. if sorted(onions_signed) == sorted(set(onions)):
  25. return True
  26. else:
  27. return False
  28. def actionAnnounce(self, params):
  29. time_started = time.time()
  30. s = time.time()
  31. # Backward compatibility
  32. if "ip4" in params["add"]:
  33. params["add"].append("ipv4")
  34. if "ip4" in params["need_types"]:
  35. params["need_types"].append("ipv4")
  36. hashes = params["hashes"]
  37. all_onions_signed = self.checkOnionSigns(params.get("onions", []), params.get("onion_signs"), params.get("onion_sign_this"))
  38. time_onion_check = time.time() - s
  39. ip_type = helper.getIpType(self.connection.ip)
  40. if ip_type == "onion" or self.connection.ip in config.ip_local:
  41. is_port_open = False
  42. elif ip_type in params["add"]:
  43. is_port_open = True
  44. else:
  45. is_port_open = False
  46. s = time.time()
  47. # Separatley add onions to sites or at once if no onions present
  48. i = 0
  49. onion_to_hash = {}
  50. for onion in params.get("onions", []):
  51. if onion not in onion_to_hash:
  52. onion_to_hash[onion] = []
  53. onion_to_hash[onion].append(hashes[i])
  54. i += 1
  55. hashes_changed = 0
  56. for onion, onion_hashes in onion_to_hash.items():
  57. hashes_changed += db.peerAnnounce(
  58. ip_type="onion",
  59. address=onion,
  60. port=params["port"],
  61. hashes=onion_hashes,
  62. onion_signed=all_onions_signed
  63. )
  64. time_db_onion = time.time() - s
  65. s = time.time()
  66. if is_port_open:
  67. hashes_changed += db.peerAnnounce(
  68. ip_type=ip_type,
  69. address=self.connection.ip,
  70. port=params["port"],
  71. hashes=hashes,
  72. delete_missing_hashes=params.get("delete")
  73. )
  74. time_db_ip = time.time() - s
  75. s = time.time()
  76. # Query sites
  77. back = {}
  78. peers = []
  79. if params.get("onions") and not all_onions_signed and hashes_changed:
  80. back["onion_sign_this"] = "%.0f" % time.time() # Send back nonce for signing
  81. if len(hashes) > 500 or not hashes_changed:
  82. limit = 5
  83. order = False
  84. else:
  85. limit = 30
  86. order = True
  87. for hash in hashes:
  88. if time.time() - time_started > 1: # 1 sec limit on request
  89. self.connection.log("Announce time limit exceeded after %s/%s sites" % (len(peers), len(hashes)))
  90. break
  91. hash_peers = db.peerList(
  92. hash,
  93. address=self.connection.ip, onions=list(onion_to_hash.keys()), port=params["port"],
  94. limit=min(limit, params["need_num"]), need_types=params["need_types"], order=order
  95. )
  96. if "ip4" in params["need_types"]: # Backward compatibility
  97. hash_peers["ip4"] = hash_peers["ipv4"]
  98. del(hash_peers["ipv4"])
  99. peers.append(hash_peers)
  100. time_peerlist = time.time() - s
  101. back["peers"] = peers
  102. self.connection.log(
  103. "Announce %s sites (onions: %s, onion_check: %.3fs, db_onion: %.3fs, db_ip: %.3fs, peerlist: %.3fs, limit: %s)" %
  104. (len(hashes), len(onion_to_hash), time_onion_check, time_db_onion, time_db_ip, time_peerlist, limit)
  105. )
  106. self.response(back)
  107. @PluginManager.registerTo("UiRequest")
  108. class UiRequestPlugin(object):
  109. @helper.encodeResponse
  110. def actionStatsBootstrapper(self):
  111. self.sendHeader()
  112. # Style
  113. yield """
  114. <style>
  115. * { font-family: monospace; white-space: pre }
  116. table td, table th { text-align: right; padding: 0px 10px }
  117. </style>
  118. """
  119. hash_rows = db.execute("SELECT * FROM hash").fetchall()
  120. for hash_row in hash_rows:
  121. peer_rows = db.execute(
  122. "SELECT * FROM peer LEFT JOIN peer_to_hash USING (peer_id) WHERE hash_id = :hash_id",
  123. {"hash_id": hash_row["hash_id"]}
  124. ).fetchall()
  125. yield "<br>%s (added: %s, peers: %s)<br>" % (
  126. str(hash_row["hash"]).encode().hex(), hash_row["date_added"], len(peer_rows)
  127. )
  128. for peer_row in peer_rows:
  129. yield " - {type} {address}:{port} added: {date_added}, announced: {date_announced}<br>".format(**dict(peer_row))