Browse Source

contrib: Improvements to hardcoded seeds scripts

- Moved all seed related scripts to contrib/seeds for consistency
- Updated `makeseeds.py` to handle IPv6 and onions, fix regular
  expression for recent Bitcoin Core versions
- Fixed a bug in `generate-seeds.py` with regard to IPv6 parsing
0.13
Wladimir J. van der Laan 10 years ago
parent
commit
ccd4369a23
  1. 2
      contrib/seeds/README.md
  2. 5
      contrib/seeds/generate-seeds.py
  3. 58
      contrib/seeds/makeseeds.py
  4. 0
      contrib/seeds/nodes_main.txt
  5. 11
      contrib/seeds/nodes_test.txt
  6. 5
      share/seeds/nodes_test.txt

2
contrib/seeds/README.md

@ -1,7 +1,7 @@
### Seeds ### ### Seeds ###
Utility to generate the seeds.txt list that is compiled into the client Utility to generate the seeds.txt list that is compiled into the client
(see [src/chainparamsseeds.h](/src/chainparamsseeds.h) and [share/seeds](/share/seeds)). (see [src/chainparamsseeds.h](/src/chainparamsseeds.h) and other utilities in [contrib/seeds](/contrib/seeds)).
The 512 seeds compiled into the 0.10 release were created from sipa's DNS seed data, like this: The 512 seeds compiled into the 0.10 release were created from sipa's DNS seed data, like this:

5
share/seeds/generate-seeds.py → contrib/seeds/generate-seeds.py

@ -77,6 +77,9 @@ def parse_spec(s, defaultport):
if match: # ipv6 if match: # ipv6
host = match.group(1) host = match.group(1)
port = match.group(2) port = match.group(2)
elif s.count(':') > 1: # ipv6, no port
host = s
port = ''
else: else:
(host,_,port) = s.partition(':') (host,_,port) = s.partition(':')
@ -118,7 +121,7 @@ def main():
g.write('#define BITCOIN_CHAINPARAMSSEEDS_H\n') g.write('#define BITCOIN_CHAINPARAMSSEEDS_H\n')
g.write('/**\n') g.write('/**\n')
g.write(' * List of fixed seed nodes for the bitcoin network\n') g.write(' * List of fixed seed nodes for the bitcoin network\n')
g.write(' * AUTOGENERATED by share/seeds/generate-seeds.py\n') g.write(' * AUTOGENERATED by contrib/seeds/generate-seeds.py\n')
g.write(' *\n') g.write(' *\n')
g.write(' * Each line contains a 16-byte IPv6 address and a port.\n') g.write(' * Each line contains a 16-byte IPv6 address and a port.\n')
g.write(' * IPv4 as well as onion addresses are wrapped inside a IPv6 address accordingly.\n') g.write(' * IPv4 as well as onion addresses are wrapped inside a IPv6 address accordingly.\n')

58
contrib/seeds/makeseeds.py

@ -24,24 +24,42 @@ import sys
import dns.resolver import dns.resolver
PATTERN_IPV4 = re.compile(r"^((\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})):8333$") PATTERN_IPV4 = re.compile(r"^((\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})):8333$")
PATTERN_AGENT = re.compile(r"^(\/Satoshi:0.8.6\/|\/Satoshi:0.9.(2|3)\/|\/Satoshi:0.10.\d{1,2}\/)$") PATTERN_IPV6 = re.compile(r"^\[([0-9a-z:]+)\]:8333$")
PATTERN_ONION = re.compile(r"^([abcdefghijklmnopqrstuvwxyz234567]{16}\.onion):8333$")
PATTERN_AGENT = re.compile(r"^(\/Satoshi:0\.8\.6\/|\/Satoshi:0\.9\.(2|3|4|5)\/|\/Satoshi:0\.10\.\d{1,2}\/|\/Satoshi:0\.11\.\d{1,2}\/)$")
def parseline(line): def parseline(line):
sline = line.split() sline = line.split()
if len(sline) < 11: if len(sline) < 11:
return None return None
# Match only IPv4
m = PATTERN_IPV4.match(sline[0]) m = PATTERN_IPV4.match(sline[0])
sortkey = None
ip = None
if m is None: if m is None:
return None m = PATTERN_IPV6.match(sline[0])
# Do IPv4 sanity check if m is None:
ip = 0 m = PATTERN_ONION.match(sline[0])
for i in range(0,4): if m is None:
if int(m.group(i+2)) < 0 or int(m.group(i+2)) > 255: return None
else:
net = 'onion'
sortkey = m.group(1)
else:
net = 'ipv6'
if m.group(1) in ['::']: # Not interested in localhost
return None
sortkey = m.group(1) # XXX parse IPv6 into number, could use name_to_ipv6 from generate-seeds
else:
# Do IPv4 sanity check
ip = 0
for i in range(0,4):
if int(m.group(i+2)) < 0 or int(m.group(i+2)) > 255:
return None
ip = ip + (int(m.group(i+2)) << (8*(3-i)))
if ip == 0:
return None return None
ip = ip + (int(m.group(i+2)) << (8*(3-i))) net = 'ipv4'
if ip == 0: sortkey = ip
return None
# Skip bad results. # Skip bad results.
if sline[1] == 0: if sline[1] == 0:
return None return None
@ -59,6 +77,7 @@ def parseline(line):
blocks = int(sline[8]) blocks = int(sline[8])
# Construct result. # Construct result.
return { return {
'net': net,
'ip': m.group(1), 'ip': m.group(1),
'ipnum': ip, 'ipnum': ip,
'uptime': uptime30, 'uptime': uptime30,
@ -67,13 +86,20 @@ def parseline(line):
'agent': agent, 'agent': agent,
'service': service, 'service': service,
'blocks': blocks, 'blocks': blocks,
'sortkey': sortkey,
} }
# Based on Greg Maxwell's seed_filter.py # Based on Greg Maxwell's seed_filter.py
def filterbyasn(ips, max_per_asn, max_total): def filterbyasn(ips, max_per_asn, max_total):
# Sift out ips by type
ips_ipv4 = [ip for ip in ips if ip['net'] == 'ipv4']
ips_ipv6 = [ip for ip in ips if ip['net'] == 'ipv6']
ips_onion = [ip for ip in ips if ip['net'] == 'onion']
# Filter IPv4 by ASN
result = [] result = []
asn_count = {} asn_count = {}
for ip in ips: for ip in ips_ipv4:
if len(result) == max_total: if len(result) == max_total:
break break
try: try:
@ -86,13 +112,19 @@ def filterbyasn(ips, max_per_asn, max_total):
result.append(ip) result.append(ip)
except: except:
sys.stderr.write('ERR: Could not resolve ASN for "' + ip['ip'] + '"\n') sys.stderr.write('ERR: Could not resolve ASN for "' + ip['ip'] + '"\n')
# TODO: filter IPv6 by ASN
# Add back non-IPv4
result.extend(ips_ipv6)
result.extend(ips_onion)
return result return result
def main(): def main():
lines = sys.stdin.readlines() lines = sys.stdin.readlines()
ips = [parseline(line) for line in lines] ips = [parseline(line) for line in lines]
# Skip entries with valid IPv4 address. # Skip entries with valid address.
ips = [ip for ip in ips if ip is not None] ips = [ip for ip in ips if ip is not None]
# Skip entries from suspicious hosts. # Skip entries from suspicious hosts.
ips = [ip for ip in ips if ip['ip'] not in SUSPICIOUS_HOSTS] ips = [ip for ip in ips if ip['ip'] not in SUSPICIOUS_HOSTS]
@ -109,7 +141,7 @@ def main():
# Look up ASNs and limit results, both per ASN and globally. # Look up ASNs and limit results, both per ASN and globally.
ips = filterbyasn(ips, MAX_SEEDS_PER_ASN, NSEEDS) ips = filterbyasn(ips, MAX_SEEDS_PER_ASN, NSEEDS)
# Sort the results by IP address (for deterministic output). # Sort the results by IP address (for deterministic output).
ips.sort(key=lambda x: (x['ipnum'])) ips.sort(key=lambda x: (x['net'], x['sortkey']))
for ip in ips: for ip in ips:
print ip['ip'] print ip['ip']

0
share/seeds/nodes_main.txt → contrib/seeds/nodes_main.txt

11
contrib/seeds/nodes_test.txt

@ -0,0 +1,11 @@
# List of fixed seed nodes for testnet
# Onion nodes
thfsmmn2jbitcoin.onion
it2pj4f7657g3rhi.onion
nkf5e6b7pl4jfd4a.onion
4zhkir2ofl7orfom.onion
t6xj6wilh4ytvcs7.onion
i6y6ivorwakd7nw3.onion
ubqj4rsu3nqtxmtp.onion

5
share/seeds/nodes_test.txt

@ -1,5 +0,0 @@
# List of fixed seed nodes for testnet
# Onion nodes
thfsmmn2jbitcoin.onion
it2pj4f7657g3rhi.onion
Loading…
Cancel
Save