#!/usr/bin/env python3 from flask import Flask from flask import request, render_template from flask.ext.sqlalchemy import SQLAlchemy #from flask import session, request, url_for, redirect, render_template from netaddr import IPAddress # Hack for python3 from netaddr.strategy.ipv4 import packed_to_int as unpack_v4 from netaddr.strategy.ipv6 import packed_to_int as unpack_v6 from datetime import datetime from uuid import uuid4 app = Flask(__name__) app.config.from_pyfile('config.py') db = SQLAlchemy(app) def unpack(ip): if len(ip) == 4: return unpack_v4(ip) elif len(ip) == 16: return unpack_v6(ip) class Target(db.Model): """Target IP to ping""" id = db.Column(db.Integer, primary_key=True) # IP addresses are encoded as their binary representation ip = db.Column(db.BINARY(length=16)) # Date at which a user asked for measurements to this target submitted = db.Column(db.DateTime) def __init__(self, ip): self.ip = IPAddress(ip).packed self.submitted = datetime.now() def get_ip(self): return IPAddress(unpack(self.ip)) def is_v4(self): return self.get_ip().version == 4 def is_v6(self): return self.get_ip().version == 6 def __repr__(self): return '%r' % self.get_ip() def __str__(self): return str(self.get_ip()) class Participant(db.Model): """Participant in the ping network""" id = db.Column(db.Integer, primary_key=True) # Used both as identification and password uuid = db.Column(db.String, unique=True) # Name of the machine name = db.Column(db.String) # Mostly free-form (nick, mail address, ...) contact = db.Column(db.String) # Whether we accept this participant or not active = db.Column(db.Boolean) def __init__(self, name, contact): self.uuid = str(uuid4()) self.name = name self.contact = contact self.active = False def __str__(self): return "{} ({})".format(self.name, self.contact) class Result(db.Model): """Result of a ping measurement""" id = db.Column(db.Integer, primary_key=True) target_id = db.Column(db.Integer, db.ForeignKey('target.id')) target = db.relationship('Target', backref=db.backref('results', lazy='dynamic')) # Participant source_id = db.Column(db.Integer, db.ForeignKey('participant.id')) source = db.relationship('Participant', backref=db.backref('results', lazy='dynamic')) # In milliseconds rtt = db.Column(db.Float) def __init__(self, target_id, source_uuid, rtt): target = Target.query.get_or_404(int(target_id)) source = Participant.query.filter_by(uuid=source_uuid, active=True).first_or_404() self.target = target self.source = source self.rtt = float(rtt) def init_db(): db.create_all() @app.route('/') def homepage(): return render_template('home.html') @app.route('/submit', methods=['POST']) def submit_job(): if 'target' in request.form: target = Target(request.form['target']) db.session.add(target) db.session.commit() return "Launching jobs towards {}".format(target) else: return "Invalid arguments" @app.route('/create/participant', methods=['POST']) def create_participant(): if {'name', 'contact'}.issubset(request.form) and request.form['name']: participant = Participant(request.form['name'], request.form['contact']) db.session.add(participant) db.session.commit() return "OK\nPlease wait for manual validation\n" else: return "Invalid arguments" @app.route('/targets/all') def get_jobs(): """"List of targets to ping""" targets = Target.query.order_by('-id').all() return "\n".join("{} {}".format(t.id, t) for t in targets) @app.route('/targets/ipv4') def get_v4jobs(): """"List of IPv4 targets to ping""" targets = Target.query.order_by('-id').all() return "\n".join("{} {}".format(t.id, t) for t in targets if t.is_v4()) @app.route('/targets/ipv6') def get_v6jobs(): """"List of IPv6 targets to ping""" targets = Target.query.order_by('-id').all() return "\n".join("{} {}".format(t.id, t) for t in targets if t.is_v6()) @app.route('/result/report', methods=['POST']) def report_result(): if {'rtt', 'target', 'source'}.issubset(request.form): target = request.form['target'] rtt = request.form['rtt'] source_uuid = request.form['source'] result = Result(target, source_uuid, rtt) db.session.add(result) db.session.commit() return "OK\n" else: return "Invalid arguments\n" @app.route('/result/show/') def show_results(target_id): target = Target.query.get_or_404(target_id) results = target.results.order_by('rtt').all() return render_template('results.html', target=target, results=results) if __name__ == '__main__': init_db() app.run(host='0.0.0.0', port=8888)