Exemplo n.º 1
0
    def cache_read(path):
        """
        Reads nodes from the nodes cache file.
        :return: List of RpcNode objects
        """
        log_msg('Reading \'%s\'' % path)

        try:
            f = open(path, 'r')
            blob = json.loads(f.read())
            f.close()
        except Exception as ex:
            log_err('Reading \'%s\' failed' % path)
            return RpcNodeList()

        if not isinstance(blob, list):
            return RpcNodeList()

        nodes = RpcNodeList()
        for node in blob:
            dt = dateutil_parse(node.pop('dt'))
            if (datetime.now() - dt).total_seconds(
            ) < CONFIG['scan_interval'] and 'address' in node:
                nodes.append(RpcNode(**node))

        log_msg('Loaded %d nodes from \'%s\'' % (len(nodes), path))
        return nodes
    def __init__(self, dns_provider: DnsProvider, md_address: str = '127.0.0.1', md_port: int = 18081,
                 md_auth: str = 'not:used', md_path: str = 'monerod.exe',
                 md_height_discovery_method: str = 'xmrchain', ban_list_path: str = ''):
        self.dns_provider = dns_provider

        self.md_path = md_path
        self.md_daemon_addr = md_address
        self.md_daemon_port = md_port
        self.md_daemon_auth = md_auth
        if md_height_discovery_method not in ['xmrchain', 'monerod', 'compare', 'moneroblocks']:
            log_err('bad height_discovery_method option', fatal=True)
        self.md_height_discovery_method = md_height_discovery_method

        # default Monero RPC port
        self._m_rpc_port = 18089
        self._blockchain_height = None

        self.last_mass_scan_time = 0

        if not os.path.isfile(PATH_CACHE):
            log_msg("Auto creating \'%s\'" % PATH_CACHE)
            f = open(PATH_CACHE, 'a')
            f.write('[]')
            f.close()
        
        if ban_list_path != '':
            ban_list = parse_ban_list(ban_list_path)
            log_msg('Load %d nodes from %s'%(len(ban_list), ban_list_path))
            self.ban_list = ban_list
        else:
            self.ban_list = []

        self.monerod_check()
Exemplo n.º 3
0
    def cache_read(path):
        """
        Reads nodes from the nodes cache file.
        :return: List of RpcNode objects
        """
        log_msg('Reading \'%s\'' % path)

        try:
            f = open(path, 'r')
            blob = json.loads(f.read())
            f.close()
        except Exception as ex:
            log_err('Reading \'%s\' failed' % path)
            return RpcNodeList()

        if not isinstance(blob, list):
            return RpcNodeList()

        nodes = RpcNodeList()
        for node in blob:
            dt = dateutil_parse(node['dt'])
            if 'address' in node:
                nodes.append(RpcNode(**node))

        log_msg('Loaded %d nodes from \'%s\'' % (len(nodes), path))
        return nodes
Exemplo n.º 4
0
    def __init__(self, **kwargs):
        super(Cloudflare, self).__init__(**kwargs)

        self.headers = {
            'Content-Type': 'application/json',
            'X-Auth-Email': kwargs['api_email'],
            'X-Auth-Key': kwargs['api_key'],
            'User-Agent': random_user_agent()
        }
        self.api_base = 'https://api.cloudflare.com/client/v4/zones'
        self.zone_id = None

        # zone_id is required and will be detected via Cloudflare API
        if not self.zone_id:
            log_msg('Determining zone_id; looking for \'%s\'' %
                    self.domain_name)
            result = make_json_request(url=self.api_base, headers=self.headers)
            try:
                zones = result.get('result')
                self.zone_id = next(
                    zone.get('id') for zone in zones
                    if zone.get('name') == self.domain_name)
            except StopIteration:
                log_err(
                    'could not determine zone_id. Is your Cloudflare domain correct?',
                    fatal=True)
            log_msg('Cloudflare zone_id \'%s\' matched to \'%s\'' %
                    (self.zone_id, self.domain_name))
Exemplo n.º 5
0
    def get_records(self):
        max_retries = 5
        nodes = RpcNodeList()
        log_msg('Fetching existing record(s) (%s.%s)' %
                (self.subdomain_name, self.domain_name))

        retries = 0
        while (True):
            try:
                result = make_json_request(
                    '%s/%s/dns_records/?type=A&name=%s.%s' %
                    (self.api_base, self.zone_id, self.subdomain_name,
                     self.domain_name),
                    headers=self.headers)
                records = result.get('result')

                # filter on A records / subdomain
                for record in records:
                    if record.get('type') != 'A' or record.get(
                            'name') != self.fulldomain_name:
                        continue

                    node = RpcNode(address=record.get('content'),
                                   uid=record.get('id'))
                    nodes.append(node)
                    log_msg('> A %s %s' %
                            (record.get('name'), record.get('content')))
                return nodes

            except Exception as ex:
                log_err("Cloudflare record fetching failed: %s" % (str(ex)))
                retries += 1
                time.sleep(1)
                if retries > max_retries:
                    return None
    def main(self):
        # get & set the current blockheight
        height = self.monerod_get_height(
            method=self.md_height_discovery_method)
        if not height or not isinstance(height, int):
            log_err("Unable to fetch the current blockchain height")
            return
        self._blockchain_height = height

        nodes = RpcNodeList()
        nodes += RpcNodeList.cache_read(PATH_CACHE)  # from `cached_nodes.json`
        if nodes:
            nodes = self.scan(nodes, remove_invalid=True)

        if len(nodes.nodes) <= 2:
            peers = self.monerod_get_peers()  # from monerod
            nodes += self.scan(peers, remove_invalid=True)

        if nodes.nodes:
            nodes.cache_write()

        nodes.shuffle()
        inserts = nodes.nodes[:self.dns_provider.max_records]
        dns_nodes = self.dns_provider.get_records()

        # insert new records
        for node in inserts:
            if node.address not in dns_nodes:
                self.dns_provider.add_record(node)

        # remove old records
        for i, node in enumerate(dns_nodes):
            if node.address not in inserts:
                self.dns_provider.delete_record(node)
    def __init__(self,
                 dns_provider: DnsProvider,
                 md_address: str = '127.0.0.1',
                 md_port: int = 18081,
                 md_auth: str = 'not:used',
                 md_path: str = 'monerod.exe',
                 md_height_discovery_method: str = 'xmrchain'):
        self.dns_provider = dns_provider

        self.md_path = md_path
        self.md_daemon_addr = md_address
        self.md_daemon_port = md_port
        self.md_daemon_auth = md_auth
        if md_height_discovery_method not in [
                'xmrchain', 'monerod', 'compare', 'moneroblocks'
        ]:
            log_err('bad height_discovery_method option', fatal=True)
        self.md_height_discovery_method = md_height_discovery_method

        # default Monero RPC port
        self._m_rpc_port = 18089
        self._blockchain_height = None

        if not os.path.isfile(PATH_CACHE):
            log_msg("Auto creating \'%s\'" % PATH_CACHE)
            f = open(PATH_CACHE, 'a')
            f.write('[]')
            f.close()

        self.monerod_check()
    def monerod_get_height(self, method='compare'):
        """
        Gets the current top block on the chain
        :param method: 'monerod' will use only monerod to fetch the height.
        'xmrchain' will only use xmrchain. 'both' will query both and compare.
        :return:
        """
        data = {}
        xmrchain_height = 0
        max_retries = 5

        if method == ['compare', 'monerod']:
            output = self._daemon_command(cmd="print_height")
            if isinstance(output, str) and output.startswith('Error') or not output:
                log_err("monerod output: %s" % output)
            elif isinstance(output, str):
                data['md_height'] = int(re.sub('[^0-9]', '', output.splitlines()[1]))
                log_msg('monerod height is %d' % data['md_height'])
                if method == 'monerod':
                    return data['md_height']

        if method in ['compare', 'moneroblocks']:
            retries = 0
            while True:
                if retries > max_retries:
                    break
                try:
                    blob = make_json_request('https://moneroblocks.info/api/get_stats/', timeout=5, verify=True)
                    data['moneroblocks'] = blob.get('height')
                    break
                except Exception as ex:
                    log_msg('Fetching moneroblocks JSON has failed. Retrying.')
                    retries += 1
                    time.sleep(1)

        if method in ['compare', 'xmrchain']:
            retries = 0
            while True:
                if retries > max_retries:
                    break
                try:
                    blob = make_json_request('https://xmrchain.net/api/networkinfo', timeout=5, verify=True)
                    assert blob.get('status') == 'success'
                    data['xmrchain_height'] = blob.get('data', {}).get('height')
                    assert isinstance(data['xmrchain_height'], int)
                    log_msg('xmrchain height is %d' % data['xmrchain_height'])
                    if method == 'xmrchain':
                        return data['xmrchain_height']
                    break
                except Exception as ex:
                    log_msg('Fetching xmrchain JSON has failed. Retrying.')
                    retries += 1
                    time.sleep(1)
                    continue

        if data:
            return max(data.values())
        log_err('Unable to obtain blockheight.')
    def monerod_check(self):
        url = 'http://%s:%d' % (self.md_daemon_addr, self.md_daemon_port)

        try:
            resp = requests.get(url, timeout=2)
            assert resp.status_code in [401, 403, 404]
            assert resp.headers.get('Server', '').startswith('Epee')
            return True
        except Exception as ex:
            log_err("monerod not reachable: %s" % url, fatal=True)
    def delete_record(self, node: RpcNode):
        # Delete DNS Record
        log_msg('Cloudflare record deletion: %s' % node.address)

        try:
            url = '%s/%s/dns_records/%s' % (self.api_base, self.zone_id, node.uid)
            data = make_json_request(url=url, method='DELETE', headers=self.headers)
            assert data.get('success') is True
            return data.get('result')
        except Exception as ex:
            log_err("Record (%s) deletion failed: %s" % (node.address, str(ex)))
    def add_record(self, node: RpcNode):
        log_msg('Record insertion: %s' % node.address)

        try:
            url = '%s/%s/dns_records' % (self.api_base, self.zone_id)
            make_json_request(url=url, method='POST', headers=self.headers, json={
                'name': self.subdomain_name,
                'content': node.address,
                'type': 'A',
                'ttl': 120
            })
        except Exception as ex:
            log_err("Cloudflare record (%s) insertion failed: %s" % (node.address, str(ex)))
Exemplo n.º 12
0
    def cache_write(self):
        """Writes a cache file of valid nodes"""
        now = datetime.now()
        data = []

        for node in self.nodes:
            if node.valid:
                data.append({
                    'address': node.address,
                    'port': node.port,
                    'dt': now.strftime('%Y-%m-%d %H:%M:%S')
                })
        try:
            f = open(PATH_CACHE, 'w')
            f.write(json.dumps(data, indent=4))
            f.close()
        except Exception as ex:
            log_err('Writing \'%s\' failed' % PATH_CACHE)
            raise
        log_msg('Written \'%s\' with %d nodes' % (PATH_CACHE, len(data)))
Exemplo n.º 13
0
    def _daemon_command(self, cmd: str):
        if not os.path.exists(self.md_path):
            log_err("monerod not found in path \'%s\'" % self.md_path)
            return

        log_msg("Spawning daemon; executing command \'%s\'" % cmd)

        # build proc args
        args = [
            '--rpc-bind-ip', self.md_daemon_addr,
            '--rpc-bind-port', str(self.md_daemon_port),
        ]
        if self.md_daemon_auth:
            args.extend(['--rpc-login', self.md_daemon_auth])
        args.append(cmd)

        try:
            process = Popen([self.md_path, *args],
                            stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
                            universal_newlines=True, bufsize=1)
            output, err = process.communicate(timeout=10)
            if not output:
                log_err("No output from monerod")
            return output
        except Exception as ex:
            log_err('Could not spawn \'%s %s\': %s' % (
                self.md_path, ' '.join(args), str(ex)
            ))
        finally:
            # cleanup
            process.kill()
Exemplo n.º 14
0
    def main(self):
        # get & set the current blockheight
        height = self.monerod_get_height(
            method=self.md_height_discovery_method)
        if not height or not isinstance(height, int):
            log_err("Unable to fetch the current blockchain height")
            return
        self._blockchain_height = height

        nodes = RpcNodeList()
        nodes += RpcNodeList.cache_read(PATH_CACHE)  # from `cached_nodes.json`
        if nodes:
            nodes = self.scan(nodes, remove_invalid=True)

        now = time.time()
        this_round_uptime = now - self.last_mass_scan_time

        if len(
                nodes.nodes
        ) <= self.dns_provider.max_records or this_round_uptime > CONFIG[
                'scan_interval']:
            peers = self.monerod_get_peers()  # from monerod
            nodes += self.scan(peers, remove_invalid=True)
            self.last_mass_scan_time = now

        if len(nodes.nodes) > 0:
            nodes.cache_write()

            nodes.shuffle()

            inserts = nodes.nodes[:self.dns_provider.max_records]
            insert_ips = []
            for node in inserts:
                insert_ips.append(node.address)

            dns_nodes = self.dns_provider.get_records()

            if dns_nodes != None:
                # insert new records
                for node in inserts:
                    if node.address not in dns_nodes:
                        self.dns_provider.add_record(node)

                # remove old records
                for node in dns_nodes:
                    if node.address not in insert_ips:
                        self.dns_provider.delete_record(node)
            else:
                log_err('Could not fetch DNS records, skipping this update.')

        else:
            log_err('Could not get any valid node, skipping this update.')
Exemplo n.º 15
0
import os
import subprocess
import time
from functools import partial
from multiprocessing import Pool
from subprocess import Popen
from datetime import datetime

from moneriote import PATH_CACHE, CONFIG
from moneriote.dns import DnsProvider
from moneriote.rpc import RpcNode, RpcNodeList
from moneriote.utils import log_msg, log_err, make_json_request, banner, parse_ban_list


if sys.version_info[0] != 3 or sys.version_info[1] < 3.5:
    log_err("please run with python >= 3.5", fatal=True)

try:
    import requests
except ImportError:
    log_err("please install requests: pip install requests", fatal=True)


class Moneriote:
    def __init__(self, dns_provider: DnsProvider, md_address: str = '127.0.0.1', md_port: int = 18081,
                 md_auth: str = 'not:used', md_path: str = 'monerod.exe',
                 md_height_discovery_method: str = 'xmrchain', ban_list_path: str = ''):
        self.dns_provider = dns_provider

        self.md_path = md_path
        self.md_daemon_addr = md_address
Exemplo n.º 16
0
def cli(monerod_path, monerod_address, monerod_port, monerod_auth,
        blockheight_discovery, dns_provider, domain, subdomain, api_key,
        api_email, max_records, loop_interval, concurrent_scans, scan_interval,
        ban_list, from_config):
    from moneriote import CONFIG
    from moneriote.moneriote import Moneriote
    from moneriote.utils import log_err, log_msg, banner, parse_ini

    banner()

    if from_config:
        md, dns, ban = parse_ini(from_config)
        monerod_path = md['path']
        monerod_address = md['address']
        monerod_auth = md['auth']
        monerod_port = md['port']
        api_email = dns['api_email']
        api_key = dns['api_key']
        domain = dns['domain_name']
        subdomain = dns['subdomain_name']
        max_records = int(dns['max_records'])
        dns_provider = dns['provider']
        ban_list = ban['ban_list_path']

    if not api_email:
        log_err('Parameter api_email is required', fatal=True)

    if not api_key:
        log_err('Parameter api_key is required', fatal=True)

    if not domain:
        log_err('Parametre domain is required', fatal=True)

    CONFIG['concurrent_scans'] = concurrent_scans
    CONFIG['scan_interval'] = scan_interval

    if dns_provider == 'cloudflare':
        from moneriote.dns.cloudflare import Cloudflare
        dns_provider = Cloudflare(domain_name=domain,
                                  subdomain_name=subdomain,
                                  api_key=api_key,
                                  api_email=api_email,
                                  max_records=max_records)
    elif dns_provider == 'transip':
        from moneriote.dns.transip import TransIP
        dns_provider = TransIP(api_email=api_email,
                               api_key=api_key,
                               subdomain_name=subdomain,
                               domain_name=domain,
                               max_records=max_records)
    else:
        log_err("Unknown DNS provider \'%s\'" % dns_provider, fatal=True)

    mon = Moneriote(dns_provider=dns_provider,
                    md_path=monerod_path,
                    md_address=monerod_address,
                    md_port=monerod_port,
                    md_auth=monerod_auth,
                    md_height_discovery_method=blockheight_discovery,
                    ban_list_path=ban_list)

    while True:
        mon.main()
        log_msg('Sleeping for %d seconds' % loop_interval)
        sleep(loop_interval)