Merge branch 'main' of github.com:lalanza808/monero.fail into levin
This commit is contained in:
commit
cd99f0c0c6
4
Makefile
4
Makefile
|
@ -1,3 +1,7 @@
|
||||||
|
setup:
|
||||||
|
python3 -m venv .venv
|
||||||
|
.venv/bin/pip install -r requirements.txt
|
||||||
|
|
||||||
up:
|
up:
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
|
|
||||||
|
|
|
@ -4,4 +4,6 @@ peewee
|
||||||
gunicorn
|
gunicorn
|
||||||
arrow
|
arrow
|
||||||
flask_wtf
|
flask_wtf
|
||||||
|
pysocks
|
||||||
git+https://github.com/cdiv1e12/py-levin
|
git+https://github.com/cdiv1e12/py-levin
|
||||||
|
geoip2
|
||||||
|
|
131
xmrnodes/app.py
131
xmrnodes/app.py
|
@ -1,18 +1,23 @@
|
||||||
import arrow
|
|
||||||
import json
|
import json
|
||||||
import requests
|
|
||||||
import re
|
import re
|
||||||
import logging
|
import logging
|
||||||
import click
|
|
||||||
from os import makedirs
|
from os import makedirs
|
||||||
from random import shuffle
|
from random import shuffle
|
||||||
|
from socket import gethostbyname_ex
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from flask import Flask, request, redirect
|
|
||||||
|
import geoip2.database
|
||||||
|
import arrow
|
||||||
|
import requests
|
||||||
|
import click
|
||||||
|
from flask import Flask, request, redirect, jsonify
|
||||||
from flask import render_template, flash, url_for
|
from flask import render_template, flash, url_for
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from xmrnodes.helpers import determine_crypto, is_onion, make_request, retrieve_peers
|
from xmrnodes.helpers import determine_crypto, is_onion, make_request, retrieve_peers
|
||||||
from xmrnodes.forms import SubmitNode
|
from xmrnodes.forms import SubmitNode
|
||||||
from xmrnodes.models import Node, HealthCheck
|
from xmrnodes.models import Node, HealthCheck, Peer
|
||||||
from xmrnodes import config
|
from xmrnodes import config
|
||||||
|
|
||||||
|
|
||||||
|
@ -54,6 +59,68 @@ def index():
|
||||||
form=form
|
form=form
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@app.route("/nodes.json")
|
||||||
|
def nodes_json():
|
||||||
|
nodes = Node.select().where(
|
||||||
|
Node.validated==True
|
||||||
|
).where(
|
||||||
|
Node.nettype=="mainnet"
|
||||||
|
)
|
||||||
|
xmr_nodes = [n for n in nodes if n.crypto == "monero"]
|
||||||
|
wow_nodes = [n for n in nodes if n.crypto == "wownero"]
|
||||||
|
return jsonify({
|
||||||
|
"monero": {
|
||||||
|
"clear": [n.url for n in xmr_nodes if n.is_tor == False],
|
||||||
|
"onion": [n.url for n in xmr_nodes if n.is_tor == True]
|
||||||
|
},
|
||||||
|
"wownero": {
|
||||||
|
"clear": [n.url for n in wow_nodes if n.is_tor == False],
|
||||||
|
"onion": [n.url for n in wow_nodes if n.is_tor == True]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
@app.route("/wow_nodes.json")
|
||||||
|
def wow_nodes_json():
|
||||||
|
nodes = Node.select().where(
|
||||||
|
Node.validated==True
|
||||||
|
).where(
|
||||||
|
Node.nettype=="mainnet"
|
||||||
|
).where(
|
||||||
|
Node.crypto=="wownero"
|
||||||
|
)
|
||||||
|
nodes = [n for n in nodes]
|
||||||
|
return jsonify({
|
||||||
|
"clear": [n.url for n in nodes if n.is_tor == False],
|
||||||
|
"onion": [n.url for n in nodes if n.is_tor == True]
|
||||||
|
})
|
||||||
|
|
||||||
|
@app.route("/map")
|
||||||
|
def map():
|
||||||
|
peers = Peer.select()
|
||||||
|
nodes = list()
|
||||||
|
_nodes = Node.select().where(
|
||||||
|
Node.is_tor == False,
|
||||||
|
Node.crypto == 'monero',
|
||||||
|
Node.validated == True,
|
||||||
|
Node.nettype == 'mainnet'
|
||||||
|
)
|
||||||
|
with geoip2.database.Reader('./data/GeoLite2-City.mmdb') as reader:
|
||||||
|
for node in _nodes:
|
||||||
|
try:
|
||||||
|
_url = urlparse(node.url)
|
||||||
|
ip = gethostbyname_ex(_url.hostname)[2][0]
|
||||||
|
response = reader.city(ip)
|
||||||
|
nodes.append((response.location.longitude, response.location.latitude, _url.hostname, node.datetime_entered))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
"map.html",
|
||||||
|
peers=peers,
|
||||||
|
nodes=nodes,
|
||||||
|
source_node=config.NODE_HOST
|
||||||
|
)
|
||||||
|
|
||||||
@app.route("/resources")
|
@app.route("/resources")
|
||||||
def resources():
|
def resources():
|
||||||
return render_template("resources.html")
|
return render_template("resources.html")
|
||||||
|
@ -123,8 +190,56 @@ def check():
|
||||||
|
|
||||||
@app.cli.command("get_peers")
|
@app.cli.command("get_peers")
|
||||||
def get_peers():
|
def get_peers():
|
||||||
r = retrieve_peers()
|
all_peers = []
|
||||||
print(r)
|
print(f'[+] Retrieving initial peers from {config.NODE_HOST}:{config.NODE_PORT}')
|
||||||
|
initial_peers = retrieve_peers(config.NODE_HOST, config.NODE_PORT)
|
||||||
|
with geoip2.database.Reader('./data/GeoLite2-City.mmdb') as reader:
|
||||||
|
for peer in initial_peers:
|
||||||
|
if peer not in all_peers:
|
||||||
|
all_peers.append(peer)
|
||||||
|
_url = urlparse(peer)
|
||||||
|
url = f"{_url.scheme}://{_url.netloc}".lower()
|
||||||
|
if not Peer.select().where(Peer.url == peer).exists():
|
||||||
|
response = reader.city(_url.hostname)
|
||||||
|
p = Peer(
|
||||||
|
url=peer,
|
||||||
|
country=response.country.name,
|
||||||
|
city=response.city.name,
|
||||||
|
postal=response.postal.code,
|
||||||
|
lat=response.location.latitude,
|
||||||
|
lon=response.location.longitude,
|
||||||
|
)
|
||||||
|
p.save()
|
||||||
|
print(f'{peer} - saving new peer')
|
||||||
|
else:
|
||||||
|
print(f'{peer} - already seen')
|
||||||
|
|
||||||
|
try:
|
||||||
|
print(f'[+] Retrieving crawled peers from {_url.netloc}')
|
||||||
|
new_peers = retrieve_peers(_url.hostname, _url.port)
|
||||||
|
for peer in new_peers:
|
||||||
|
all_peers.append(peer)
|
||||||
|
_url = urlparse(peer)
|
||||||
|
url = f"{_url.scheme}://{_url.netloc}".lower()
|
||||||
|
if not Peer.select().where(Peer.url == peer).exists():
|
||||||
|
response = reader.city(_url.hostname)
|
||||||
|
p = Peer(
|
||||||
|
url=peer,
|
||||||
|
country=response.country.name,
|
||||||
|
city=response.city.name,
|
||||||
|
postal=response.postal.code,
|
||||||
|
lat=response.location.latitude,
|
||||||
|
lon=response.location.longitude,
|
||||||
|
)
|
||||||
|
p.save()
|
||||||
|
print(f'{peer} - saving new peer')
|
||||||
|
else:
|
||||||
|
print(f'{peer} - already seen')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
print(f'{len(all_peers)} peers found from {config.NODE_HOST}:{config.NODE_PORT}')
|
||||||
|
|
||||||
|
|
||||||
@app.cli.command("validate")
|
@app.cli.command("validate")
|
||||||
def validate():
|
def validate():
|
||||||
|
@ -182,7 +297,7 @@ def export():
|
||||||
def import_():
|
def import_():
|
||||||
all_nodes = []
|
all_nodes = []
|
||||||
export_dir = f"{config.DATA_DIR}/export.txt"
|
export_dir = f"{config.DATA_DIR}/export.txt"
|
||||||
with open(export_dir, 'r') as f:
|
with open(export_dir, "r") as f:
|
||||||
for url in f.readlines():
|
for url in f.readlines():
|
||||||
try:
|
try:
|
||||||
n = url.rstrip().lower()
|
n = url.rstrip().lower()
|
||||||
|
|
|
@ -54,12 +54,14 @@ def is_onion(url: str):
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def retrieve_peers():
|
def retrieve_peers(host, port):
|
||||||
try:
|
try:
|
||||||
|
print(f'[.] Connecting to {host}:{port}')
|
||||||
sock = socket.socket()
|
sock = socket.socket()
|
||||||
sock.connect((config.NODE_HOST, int(config.NODE_PORT))
|
sock.settimeout(5)
|
||||||
|
sock.connect((host, int(port)))
|
||||||
except:
|
except:
|
||||||
sys.stderr.write("unable to connect to %s:%d\n" % (config.NODE_HOST, int(config.NODE_PORT))
|
sys.stderr.write("unable to connect to %s:%d\n" % (host, int([port])))
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
bucket = Bucket.create_handshake_request()
|
bucket = Bucket.create_handshake_request()
|
||||||
|
@ -84,9 +86,9 @@ def retrieve_peers():
|
||||||
buckets.append(bucket)
|
buckets.append(bucket)
|
||||||
|
|
||||||
if bucket.command == 1001:
|
if bucket.command == 1001:
|
||||||
peers = bucket.get_peers() or []
|
_peers = bucket.get_peers() or []
|
||||||
|
|
||||||
for peer in peers:
|
for peer in _peers:
|
||||||
try:
|
try:
|
||||||
peers.append('http://%s:%d' % (peer['ip'].ip, peer['port'].value))
|
peers.append('http://%s:%d' % (peer['ip'].ip, peer['port'].value))
|
||||||
except:
|
except:
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
from peewee import *
|
from urllib.parse import urlparse
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
from peewee import *
|
||||||
|
|
||||||
from xmrnodes import config
|
from xmrnodes import config
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,6 +25,22 @@ class Node(Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
database = db
|
database = db
|
||||||
|
|
||||||
|
class Peer(Model):
|
||||||
|
id = AutoField()
|
||||||
|
url = CharField(unique=True)
|
||||||
|
country = CharField(null=True)
|
||||||
|
city = CharField(null=True)
|
||||||
|
postal = IntegerField(null=True)
|
||||||
|
lat = FloatField(null=True)
|
||||||
|
lon = FloatField(null=True)
|
||||||
|
datetime = DateTimeField(default=datetime.utcnow)
|
||||||
|
|
||||||
|
def get_ip(self):
|
||||||
|
return urlparse(self.url).hostname
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
database = db
|
||||||
|
|
||||||
class HealthCheck(Model):
|
class HealthCheck(Model):
|
||||||
id = AutoField()
|
id = AutoField()
|
||||||
node = ForeignKeyField(Node, backref='healthchecks')
|
node = ForeignKeyField(Node, backref='healthchecks')
|
||||||
|
@ -31,4 +50,4 @@ class HealthCheck(Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
database = db
|
database = db
|
||||||
|
|
||||||
db.create_tables([Node, HealthCheck])
|
db.create_tables([Node, HealthCheck, Peer])
|
||||||
|
|
|
@ -45,6 +45,8 @@
|
||||||
<br>
|
<br>
|
||||||
<a href="https://twitter.com/lza_menace" target="_blank">Contact me</a>
|
<a href="https://twitter.com/lza_menace" target="_blank">Contact me</a>
|
||||||
-
|
-
|
||||||
|
<a href="{{ url_for('map') }}">Map</a>
|
||||||
|
-
|
||||||
<a href="https://github.com/lalanza808/monero.fail" target="_blank">Source Code</a>
|
<a href="https://github.com/lalanza808/monero.fail" target="_blank">Source Code</a>
|
||||||
-
|
-
|
||||||
<a href="{{ url_for('resources') }}">Resources</a>
|
<a href="{{ url_for('resources') }}">Resources</a>
|
||||||
|
|
|
@ -0,0 +1,190 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<meta name="HandheldFriendly" content="True">
|
||||||
|
<meta name="MobileOptimized" content="320">
|
||||||
|
<link rel="shortcut icon" href="/static/favicon.ico" type="image/x-icon" />
|
||||||
|
|
||||||
|
<meta property="fb:app_id" content="0" />
|
||||||
|
<meta property="og:image" content="https://www.getmonero.org/press-kit/symbols/monero-symbol-on-white-480.png" />
|
||||||
|
<meta property="og:description" content="xmrnodes" />
|
||||||
|
<meta property="og:url" content="http://localhost" />
|
||||||
|
<meta property="og:title" content="XMR Nodes" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
|
<meta name="theme-color" content="#ffffff">
|
||||||
|
<meta name="apple-mobile-web-app-title" content="XMR Nodes">
|
||||||
|
<meta name="application-name" content="XMR Nodes">
|
||||||
|
<meta name="msapplication-TileColor" content="#da532c">
|
||||||
|
<meta name="keywords" content="wownero, monero, xmr, bitmonero, cryptocurrency">
|
||||||
|
|
||||||
|
<link href="/static/css/normalize.css" rel="stylesheet">
|
||||||
|
<link href="/static/css/pure.css" rel="stylesheet">
|
||||||
|
<link href="/static/css/style.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="//cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/css/ol.css" type="text/css">
|
||||||
|
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" type="text/css">
|
||||||
|
<style>
|
||||||
|
.map {
|
||||||
|
height: 450px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.popover-body {
|
||||||
|
min-width: 276px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src="https://cdn.polyfill.io/v3/polyfill.min.js?features=fetch,requestAnimationFrame,Element.prototype.classList,URL,TextDecoder"></script>
|
||||||
|
<script src="//cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/build/ol.js"></script>
|
||||||
|
<script src="//code.jquery.com/jquery-3.5.1.min.js"></script>
|
||||||
|
<script src="//maxcdn.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<title>XMR Nodes</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
{% with messages = get_flashed_messages() %}
|
||||||
|
{% if messages %}
|
||||||
|
<ul class="flashes pure-u-1 center">
|
||||||
|
{% for message in messages %}
|
||||||
|
<li>{{ message }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
<div id="" class="center">
|
||||||
|
<br>
|
||||||
|
<a href="https://twitter.com/lza_menace" target="_blank">Contact me</a>
|
||||||
|
-
|
||||||
|
<a href="https://github.com/lalanza808/monero.fail" target="_blank">Source Code</a>
|
||||||
|
-
|
||||||
|
<a href="{{ url_for('resources') }}">Resources</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Map -->
|
||||||
|
<h2>View Map</h2>
|
||||||
|
<div id="map" class="map"></div>
|
||||||
|
<div id="popup" class="popup" title="Welcome to OpenLayers"></div>
|
||||||
|
<p>Found Peers (via source node, levin p2p): {{ peers | length }}</p>
|
||||||
|
<p>Added Nodes (Monero mainnet): {{ nodes | length }}</p>
|
||||||
|
<p>Source Node: {{ source_node }}</p>
|
||||||
|
<p>
|
||||||
|
This is not a full representation of the entire Monero network,
|
||||||
|
just a look into the peers being crawled from the source node ({{ source_node }})
|
||||||
|
and the nodes already added to the monero.fail database.
|
||||||
|
New peers are searched for on a recurring interval throughout the day.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<div id="footer" class="center">
|
||||||
|
<a href="/">Go home</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Marker layer
|
||||||
|
markerLayer = new ol.layer.Vector({
|
||||||
|
source: new ol.source.Vector({
|
||||||
|
features: [],
|
||||||
|
projection: 'EPSG:3857'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create the map
|
||||||
|
var map = new ol.Map({
|
||||||
|
target: 'map',
|
||||||
|
layers: [
|
||||||
|
new ol.layer.Tile({
|
||||||
|
source: new ol.source.OSM()
|
||||||
|
}),
|
||||||
|
markerLayer
|
||||||
|
],
|
||||||
|
view: new ol.View({
|
||||||
|
center: ol.proj.fromLonLat([0, 25]),
|
||||||
|
zoom: 1
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Define a circle marker
|
||||||
|
var circle = new ol.style.Style({
|
||||||
|
image: new ol.style.Circle({
|
||||||
|
radius: 4,
|
||||||
|
fill: new ol.style.Fill({
|
||||||
|
color: 'rgba(76,76,76,0.7)',
|
||||||
|
}),
|
||||||
|
stroke: new ol.style.Stroke({
|
||||||
|
color: 'rgba(53,53,53,0.7)',
|
||||||
|
width: 1
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
var circle2 = new ol.style.Style({
|
||||||
|
image: new ol.style.Circle({
|
||||||
|
radius: 8,
|
||||||
|
fill: new ol.style.Fill({
|
||||||
|
color: 'rgba(255,102,0,0.5)',
|
||||||
|
}),
|
||||||
|
stroke: new ol.style.Stroke({
|
||||||
|
color: 'rgba(204,81,0,0.5)',
|
||||||
|
width: 1
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
{% for peer in peers %}
|
||||||
|
var feature = new ol.Feature(
|
||||||
|
new ol.geom.Point(ol.proj.transform(['{{ peer.lon }}', '{{ peer.lat }}'], 'EPSG:4326', 'EPSG:3857'))
|
||||||
|
);
|
||||||
|
feature.description = [
|
||||||
|
'Peer {{ peer.get_ip() }}',
|
||||||
|
'Last seen {{ peer.datetime }}'
|
||||||
|
];
|
||||||
|
feature.setStyle(circle);
|
||||||
|
markerLayer.getSource().addFeature(feature);
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% for peer in nodes %}
|
||||||
|
var feature = new ol.Feature(
|
||||||
|
new ol.geom.Point(ol.proj.transform(['{{ peer[0] }}', '{{ peer[1] }}'], 'EPSG:4326', 'EPSG:3857'))
|
||||||
|
);
|
||||||
|
feature.description = [
|
||||||
|
'Node {{ peer[2] }}',
|
||||||
|
'Last seen {{ peer[3] }}'
|
||||||
|
];
|
||||||
|
feature.setStyle(circle2);
|
||||||
|
markerLayer.getSource().addFeature(feature);
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
// Setup popup
|
||||||
|
var popup = new ol.Overlay({
|
||||||
|
element: $('#popup')[0],
|
||||||
|
});
|
||||||
|
map.addOverlay(popup);
|
||||||
|
|
||||||
|
// Show details on each pixel
|
||||||
|
map.on("click", function(e) {
|
||||||
|
var element = popup.getElement();
|
||||||
|
$(element).popover('dispose')
|
||||||
|
map.forEachFeatureAtPixel(e.pixel, function (feature, layer) {
|
||||||
|
var coordinate = e.coordinate;
|
||||||
|
$(element).popover('dispose');
|
||||||
|
popup.setPosition(coordinate);
|
||||||
|
element.title = feature.description[0]
|
||||||
|
$(element).popover({
|
||||||
|
container: element,
|
||||||
|
placement: 'top',
|
||||||
|
animation: false,
|
||||||
|
html: true,
|
||||||
|
content: '<p>' + feature.description[1] + '</p>',
|
||||||
|
});
|
||||||
|
$(element).popover('show');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue