from flask import Flask, render_template, jsonify, request, redirect, url_for
from peeringdb import resource
from peeringdb.client import Client
from numpy import setdiff1d
from requests import session
from re import findall as re_findall, search as re_search, MULTILINE as re_multiline
import sqlite3, requests, geoip2.database
import psycopg2
import psycopg2.extras, json
import ipaddress
import maxminddb
from werkzeug.utils import secure_filename
from scapy.all import *
import csv

app = Flask(__name__)

app.config['UPLOAD_FOLDER'] = '/var/www/html/gsl.tools/public'

pdb = Client()

def subnet_crush(description, input_list):
    subnets6 = []
    subnets4 = []
    to_return = ""

    # parse subnets
    for net in input_list:
            try:
                    n = ipaddress.ip_network(net.strip())
                    if n.version == 4:
                            subnets4.append(n)
                    else:
                            subnets6.append(n)
            except ValueError:
                    pass

    ipv4s = [ipaddress.IPv4Network(_, False) for _ in subnets4]
    ipv6s = [ipaddress.IPv6Network(_, False) for _ in subnets6]

    description = description.strip()

    for ipv4 in ipaddress.collapse_addresses(ipv4s):
        to_return = to_return + f"\"{ipv4.with_prefixlen.strip()}\",\"{description}\",\n"

    for ipv6 in ipaddress.collapse_addresses(ipv6s):
        to_return = to_return + f"\"{ipv6.with_prefixlen.strip()}\",\"{description}\",\n"

    return to_return

def pretty_speed(value):
    if not value:
        return ""
    try:
        value = int(value)
        if value >= 1000000:
            return "%d T" % (value / 10 ** 6)
        elif value >= 1000:
            return "%d G" % (value / 10 ** 3)
        else:
            return "%d M" % value
    except ValueError:
        return value

def get_primary_ixlan(ix):
    ixlan = ix.ixlan_set.filter(status=ix.status, id=ix.id).first()

    if not ixlan:
        ixlan = ix.ixlan_set.filter(status=ix.status).order_by("id").first()
    if not ixlan:
        ixlan = ix.ixlan_set.all().order_by("id").first()

    return ixlan

def get_members(id, ix=True):
    if ix:
        field = pdb.get(resource.InternetExchange, id)
        members = get_primary_ixlan(field).netixlan_set.all()
    else:
        field = pdb.get(resource.Network, id)
        members = field.netixlan_set.all()

    networks = {}
    for member in members:
        if ix:
            cmp_field = member.net_id
        else:
            cmp_field = member.ixlan.id
        if cmp_field in networks:
            networks[cmp_field]['speed'] += member.speed
        else:
            networks[cmp_field] = {'speed': member.speed}

    return networks, list(networks.keys()), field.name

def member_on(member_id, ix):
    if member_id in ix:
        return pretty_speed(ix[member_id]['speed'])
    else:
        return 0

def id_to_name(id, ix=True):
    if ix:
        return pdb.get(resource.Network, id).name
    else:
        return pdb.get(resource.InternetExchange, id).name

@app.route('/pcap-up', methods = ['POST'])
def pcap_up():
    failure = "Unknown failure has happened"
    # If file not existing
    if 'file' not in request.files:
        failure = "No file part"
        
    # Get the file
    file = request.files['file']

    # If browser submits filename without filename - Empty file.
    if file.filename == '':
        failure = "No selected file"

    if file:
        src_file = os.path.join(app.config['UPLOAD_FOLDER'], "upload.csv")
        tmp_file = os.path.join(app.config['UPLOAD_FOLDER'], "filtered.pcap")
        limit = int(request.form['limit'])

        # Put it into the upload folder
        file.save(src_file)

        # Modified code from Lorenzo
        packet = None

        # Turns out we've got a valid file...
        try:
            with open(src_file, newline='') as csvfile:
                parsed_counter = 0
                failed_counter = 0
                pdu_reader = csv.DictReader(csvfile)
                for row in pdu_reader:
                    packet = None
                            
                    try:
                        packet = Ether(bytes.fromhex(row['pdu']))
                        parsed_counter += 1
                    except:
                        failed_counter += 1
                        continue

                    wrpcap(tmp_file, packet, append=True)
                    if limit != 0 and parsed_counter > limit: break

            # Render stuff
            return render_template('output.html', title='Last PDU', subtitle="Bringing you the latest in PDUs", dump=packet.show(dump=True))
        except Exception as e:
            failure = "Your honour, my client pleads oopsie daisy - " + str(e)

    return render_template('output.html', title='Last PDU', subtitle="Bringing you the latest in PDUs", dump=failure)

@app.route('/ip-lookup', methods = ['POST'])
def ip_lookup_html():
    value = request.form['input']
    reader = maxminddb.open_database("ipnetdb_prefix_latest.mmdb")

    rows = []

    rank = 0
    for ip in re_findall(r"(?:(?:25[0-5]|(?:2[0-4]|1\d|[1-9]|)\d)\.?\b){4}", value, re_multiline):
        ip_data = reader.get(ip)
        data = {}
        if ip_data is None:
            data['AS Entity'] = "UNKNOWN"
            data['AS'] = "UNKNOWN"
            data['Origin'] = "UNKNOWN"
            data['Prefix'] = "UNKNOWN"
        else:
            data['AS Entity'] = ip_data['as_entity']
            data['AS'] = ip_data['as']
            data['Origin'] = ip_data['prefix_origins']
            data['Prefix'] = ip_data['prefix']

        rank += 1
        
        data['IP'] = ip
        data['Rank'] = rank

        rows.append(data)

    return render_template('generic.html', title='IP Lookup', subtitle="Powered by Leakage", keys=['Prefix', 'IP', 'Rank', 'AS Entity', 'AS', 'Origin'], rows=rows, canvas_count_1="AS Entity", canvas_count_2="AS")

@app.route('/eyeball')
def eyeball_html():
    rows = []
    keys = []
    au_rankings_json = {}
    us_rankings_json = {}
    asn_lookup_json = {}

    with open('au-rankings.json', 'r') as JSON:
        au_rankings_json = json.load(JSON)

    with open('us-rankings.json', 'r') as JSON:
        us_rankings_json = json.load(JSON)

    with open('asn-lookup.json', 'r') as JSON:
        asn_lookup_json = json.load(JSON)

    keys = list(au_rankings_json.keys()) + list(us_rankings_json.keys() - set(au_rankings_json.keys()))

    for key in keys:
        data = {}

        us = us_rankings_json.get(str(key))
        au = au_rankings_json.get(str(key))
        try:
            data['Name'] = asn_lookup_json[key]
            data['ASN'] = key

            if au is None:
                data['AU Eyeball Samples'] = '-'
                data['AU Peer Count'] = '-' 
                data['AU CONE Size'] = '-'
                data['AU Domains Hosted'] = '-'
            else:
                data['AU Eyeball Samples'] = au.get('es') 
                data['AU Peer Count'] = au.get('pc') 
                data['AU CONE Size'] = au.get('cs')
                data['AU Domains Hosted'] = au.get('dh')

            if us is None:
                data['US Eyeball Samples'] = '-'
                data['US Peer Count'] = '-'
                data['US CONE Size'] = '-'
                data['US Domains Hosted'] = '-'
            else:
                data['US Eyeball Samples'] = us.get('es') 
                data['US Peer Count'] = us.get('pc') 
                data['US CONE Size'] = us.get('cs')
                data['US Domains Hosted'] = us.get('dh')
        except:
            data['Name'] = 'ERROR OCCURRED'
            data['ASN'] = '-'
            data['AU Eyeball Samples'] = '-'
            data['AU Peer Count'] = '-' 
            data['AU CONE Size'] = '-'
            data['AU Domains Hosted'] = '-'
        rows.append(data)

    return render_template('generic.html', title='Ranking Data', subtitle="Powered by Memes", keys=['Name', 'ASN', 'AU Eyeball Samples', 'AU Peer Count', 'AU CONE Size', 'AU Domains Hosted', 'US Eyeball Samples', 'US Peer Count', 'US CONE Size', 'US Domains Hosted'], rows=rows)


@app.route('/subnet-crush', methods = ['POST'])
def subnet_crush_display():
    description = request.form['description']
    addresses = request.form['addresses'].splitlines()

    crushed = subnet_crush(description, addresses)
    
    return render_template('subnet-crush.html', title='Subnet Crusher', crushed=crushed, rows=len(addresses))

@app.route('/ix/nbn/<loc_id>')
def nbn_display_html(loc_id):
    if not re_search("^LOC\d{12}$", loc_id):
        return '', 400

    nbn_session = session()
    nbn_session.headers.update({
        "User-Agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:81.0) Gecko/20100101 Firefox/81.0',
        "Referer": 'https://www.nbnco.com.au/learn/rollout-map'
    })

    loc_response = nbn_session.get(f"https://places.nbnco.net.au/places/v2/details/{loc_id}?source=website_rollout_map")
    if not loc_response.ok:
        return '', 400

    loc_data = loc_response.json()
    
    return render_template('nbn.html', title='NBN', loc_data={**loc_data['servingArea'], **loc_data['addressDetail']})

TYPES = ['net', 'ix']

@app.route('/ix/cmp4/<int:ix_id_a>/<int:ix_id_b>/<int:ix_id_c>/<int:ix_id_d>/<int:mode>')
def factors4_display_raw_html(ix_id_a, ix_id_b, ix_id_c, ix_id_d, mode):
    if mode == 1:
        ix_id_a = pdb.all(resource.Network).filter(asn=ix_id_a).first().id
        ix_id_b = pdb.all(resource.Network).filter(asn=ix_id_b).first().id
        ix_id_c = pdb.all(resource.Network).filter(asn=ix_id_c).first().id
        ix_id_d = pdb.all(resource.Network).filter(asn=ix_id_d).first().id

    ix_a = get_members(ix_id_a, (mode == 0))
    ix_b = get_members(ix_id_b, (mode == 0))
    ix_c = get_members(ix_id_c, (mode == 0))
    ix_d = get_members(ix_id_d, (mode == 0))

    ix_all = set(ix_a[1]+ix_b[1]+ix_c[1]+ix_d[1])
    ix_on_all = set(ix_a[1]) & set(ix_b[1]) & set(ix_c[1]) & set(ix_d[1])

    out_all = []
    for id in set(ix_all):
        out_all.append({'id': id, 'name': id_to_name(id, (mode == 0)), 'all': (id in ix_on_all), 'a': member_on(id, ix_a[0]), 'b': member_on(id, ix_b[0]), 'c': member_on(id, ix_c[0]), 'd': member_on(id, ix_d[0])})

    return render_template('ix4.html', title='Home', out_names=[ix_a[2], ix_b[2], ix_c[2], ix_d[2]], out_all=out_all, type=TYPES[mode])

@app.route('/ix/cmp3/<int:ix_id_a>/<int:ix_id_b>/<int:ix_id_c>/<int:mode>')
def factors3_display_raw_html(ix_id_a, ix_id_b, ix_id_c, mode):
    if mode == 1:
        ix_id_a = pdb.all(resource.Network).filter(asn=ix_id_a).first().id
        ix_id_b = pdb.all(resource.Network).filter(asn=ix_id_b).first().id
        ix_id_c = pdb.all(resource.Network).filter(asn=ix_id_c).first().id

    ix_a = get_members(ix_id_a, (mode == 0))
    ix_b = get_members(ix_id_b, (mode == 0))
    ix_c = get_members(ix_id_c, (mode == 0))

    ix_all = set(ix_a[1]+ix_b[1]+ix_c[1])
    ix_on_all = set(ix_a[1]) & set(ix_b[1]) & set(ix_c[1])

    out_all = []
    for id in set(ix_all):
        out_all.append({'id': id, 'name': id_to_name(id, (mode == 0)), 'all': (id in ix_on_all), 'a': member_on(id, ix_a[0]), 'b': member_on(id, ix_b[0]), 'c': member_on(id, ix_c[0])})

    return render_template('ix3.html', title='Home', out_names=[ix_a[2], ix_b[2], ix_c[2]], out_all=out_all, type=TYPES[mode])

@app.route('/ix/cmp2/<int:ix_id_a>/<int:ix_id_b>/<int:mode>')
def factors2_display_raw_html(ix_id_a, ix_id_b, mode):
    if mode == 1:
        ix_id_a = pdb.all(resource.Network).filter(asn=ix_id_a).first().id
        ix_id_b = pdb.all(resource.Network).filter(asn=ix_id_b).first().id

    ix_a = get_members(ix_id_a, (mode == 0))
    ix_b = get_members(ix_id_b, (mode == 0))

    ix_all = set(ix_a[1]+ix_b[1])
    ix_on_all = set(ix_a[1]) & set(ix_b[1])

    out_all = []
    for id in set(ix_all):
        out_all.append({'id': id, 'name': id_to_name(id, (mode == 0)), 'all': (id in ix_on_all), 'a': member_on(id, ix_a[0]), 'b': member_on(id, ix_b[0])})

    return render_template('ix2.html', title='Home', out_names=[ix_a[2], ix_b[2]], out_all=out_all, type=TYPES[mode])

def get_atlas_times(result_id):
    result = {}
    #probe_location = {}
    with requests.get(f"https://atlas.ripe.net/api/v2/measurements/{result_id}/results/?format=json") as req:
        for probe in req.json():
            try:
                ping = (probe['result'][-1]['result'][0]['rtt'] + probe['result'][-1]['result'][1]['rtt'] + probe['result'][-1]['result'][2]['rtt']) / 3
            except:
                ping = 0
            xxx = int(probe['prb_id'])

            result[xxx] = round(ping,3)
            #with geoip2.database.Reader('/home/obiwan/GeoLite2-ASN.mmdb') as reader:
            #    probe_location[xxx] = reader.asn(probe['from']).autonomous_system_number

    return result#, probe_location

def get_probe_details(probe_id):
    return 1, 2
    with requests.get(f"https://atlas.ripe.net/api/v2/probes/{probe_id}/?format=json") as req:
        detail = req.json()
        return detail['asn_v4'], detail['country_code']

@app.route('/ix/atlas/cmp3/<int:ix_id_a>/<int:ix_id_b>/<int:ix_id_c>')
def atlas_display_raw_html(ix_id_a, ix_id_b, ix_id_c):
    ix_a = get_atlas_times(ix_id_a)
    ix_b = get_atlas_times(ix_id_b)
    ix_c = get_atlas_times(ix_id_c)

    out_all = []
    for id in sorted(ix_a.keys()):
        probe_detail = get_probe_details(id)

        try:
            out_all.append({'id': id, 'country': probe_detail[1], 'asn': probe_detail[0], 'name': id, 'a': ix_a[id], 'b': ix_b[id], 'c': ix_c[id]})
        except:
            pass

    return render_template('atlas3.html', title='Home', out_names=[ix_id_a, ix_id_b, ix_id_c], out_all=out_all)

@app.route('/ix/map')
def map_display():
    connection = psycopg2.connect(user="latancy", password="xx", host="10.100.17.89", database="latancy")
    cursor = connection.cursor(cursor_factory=psycopg2.extras.DictCursor)

    out = []
    cursor.execute("select distinct host from ping order by host asc")
    for x in cursor.fetchall():
        out.append(x['host'])

    return render_template('map.html', title='Home', collectors=out)

@app.route('/ix/map/data/<collector>')
def map_data(collector):
    connection = psycopg2.connect(user="latancy", password="xx", host="10.100.17.89", database="latancy")
    cursor = connection.cursor(cursor_factory=psycopg2.extras.DictCursor)

    cursor.execute("select date_trunc('minute', time) as t_time from ping where host = %s order by t_time DESC LIMIT 1", (collector,))
    fetch_time = cursor.fetchone()[0]

    cursor.execute("select ping.url as metric,CONCAT('AS', network) as asn,latitude,longitude,minimum_response_ms AS average_response_ms from ping left join ping_points ON (ping.url = ping_points.url) where host = %s and minimum_response_ms is not null and latitude is not null and date_trunc('minute', time) = %s", (collector, fetch_time))

    out = []

    for x in cursor.fetchall():
        out.append({'url': x['metric'], 'asn': x['asn'], 'latitude': x['latitude'], 'longitude': x['longitude'], 'ms': x['average_response_ms']})

    return jsonify({'time': fetch_time, 'points': out})

@app.route('/')
def index_page():
    return render_template('index.html')

if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=True)
