peerfinder.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. #!/usr/bin/env python3
  2. from flask import Flask
  3. from flask import request, render_template
  4. from flask.ext.sqlalchemy import SQLAlchemy
  5. #from flask import session, request, url_for, redirect, render_template
  6. from netaddr import IPAddress
  7. # Hack for python3
  8. from netaddr.strategy.ipv4 import packed_to_int as unpack_v4
  9. from netaddr.strategy.ipv6 import packed_to_int as unpack_v6
  10. from datetime import datetime
  11. from uuid import uuid4
  12. app = Flask(__name__)
  13. app.config.from_pyfile('config.py')
  14. db = SQLAlchemy(app)
  15. def unpack(ip):
  16. if len(ip) == 4:
  17. return unpack_v4(ip)
  18. elif len(ip) == 16:
  19. return unpack_v6(ip)
  20. class Target(db.Model):
  21. """Target IP to ping"""
  22. id = db.Column(db.Integer, primary_key=True)
  23. # IP addresses are encoded as their binary representation
  24. ip = db.Column(db.BINARY(length=16))
  25. # Date at which a user asked for measurements to this target
  26. submitted = db.Column(db.DateTime)
  27. def __init__(self, ip):
  28. self.ip = IPAddress(ip).packed
  29. self.submitted = datetime.now()
  30. def get_ip(self):
  31. return IPAddress(unpack(self.ip))
  32. def is_v4(self):
  33. return self.get_ip().version == 4
  34. def is_v6(self):
  35. return self.get_ip().version == 6
  36. def __repr__(self):
  37. return '%r' % self.get_ip()
  38. def __str__(self):
  39. return str(self.get_ip())
  40. class Participant(db.Model):
  41. """Participant in the ping network"""
  42. id = db.Column(db.Integer, primary_key=True)
  43. # Used both as identification and password
  44. uuid = db.Column(db.String, unique=True)
  45. # Name of the machine
  46. name = db.Column(db.String)
  47. # Mostly free-form (nick, mail address, ...)
  48. contact = db.Column(db.String)
  49. # Whether we accept this participant or not
  50. active = db.Column(db.Boolean)
  51. def __init__(self, name, contact):
  52. self.uuid = str(uuid4())
  53. self.name = name
  54. self.contact = contact
  55. self.active = False
  56. def __str__(self):
  57. return "{} ({})".format(self.name, self.contact)
  58. class Result(db.Model):
  59. """Result of a ping measurement"""
  60. id = db.Column(db.Integer, primary_key=True)
  61. target_id = db.Column(db.Integer, db.ForeignKey('target.id'))
  62. target = db.relationship('Target',
  63. backref=db.backref('results', lazy='dynamic'))
  64. # Participant
  65. source_id = db.Column(db.Integer, db.ForeignKey('participant.id'))
  66. source = db.relationship('Participant',
  67. backref=db.backref('results', lazy='dynamic'))
  68. # In milliseconds
  69. rtt = db.Column(db.Float)
  70. def __init__(self, target_id, source_uuid, rtt):
  71. target = Target.query.get_or_404(int(target_id))
  72. source = Participant.query.filter_by(uuid=source_uuid,
  73. active=True).first_or_404()
  74. self.target = target
  75. self.source = source
  76. self.rtt = float(rtt)
  77. def init_db():
  78. db.create_all()
  79. @app.route('/')
  80. def homepage():
  81. return render_template('home.html')
  82. @app.route('/submit', methods=['POST'])
  83. def submit_job():
  84. if 'target' in request.form:
  85. target = Target(request.form['target'])
  86. db.session.add(target)
  87. db.session.commit()
  88. return "Launching jobs towards {}".format(target)
  89. else:
  90. return "Invalid arguments"
  91. @app.route('/create/participant', methods=['POST'])
  92. def create_participant():
  93. if {'name', 'contact'}.issubset(request.form) and request.form['name']:
  94. participant = Participant(request.form['name'], request.form['contact'])
  95. db.session.add(participant)
  96. db.session.commit()
  97. return "OK\nPlease wait for manual validation\n"
  98. else:
  99. return "Invalid arguments"
  100. @app.route('/targets/all')
  101. def get_jobs():
  102. """"List of targets to ping"""
  103. targets = Target.query.order_by('-id').all()
  104. return "\n".join("{} {}".format(t.id, t) for t in targets)
  105. @app.route('/targets/ipv4')
  106. def get_v4jobs():
  107. """"List of IPv4 targets to ping"""
  108. targets = Target.query.order_by('-id').all()
  109. return "\n".join("{} {}".format(t.id, t) for t in targets if t.is_v4())
  110. @app.route('/targets/ipv6')
  111. def get_v6jobs():
  112. """"List of IPv6 targets to ping"""
  113. targets = Target.query.order_by('-id').all()
  114. return "\n".join("{} {}".format(t.id, t) for t in targets if t.is_v6())
  115. @app.route('/result/report', methods=['POST'])
  116. def report_result():
  117. if {'rtt', 'target', 'source'}.issubset(request.form):
  118. target = request.form['target']
  119. rtt = request.form['rtt']
  120. source_uuid = request.form['source']
  121. result = Result(target, source_uuid, rtt)
  122. db.session.add(result)
  123. db.session.commit()
  124. return "OK\n"
  125. else:
  126. return "Invalid arguments\n"
  127. @app.route('/result/show/<int:target_id>')
  128. def show_results(target_id):
  129. target = Target.query.get_or_404(target_id)
  130. results = target.results.order_by('rtt').all()
  131. return render_template('results.html', target=target, results=results)
  132. if __name__ == '__main__':
  133. init_db()
  134. app.run(host='0.0.0.0', port=8888)