who-plays-ipv6-games/server.py
2019-12-16 23:48:48 +01:00

321 lines
7.3 KiB
Python

from flask import Flask, request, redirect, url_for
import os
import sys
import ipaddress
import random
import subprocess
app = Flask(__name__)
# where player are stored
DB_DIR= "./db"
PLAYER_DIR = os.path.join(DB_DIR, "players")
NET_DIR = os.path.join(DB_DIR, "networks")
REGULAR_PAGE = """
<html>
<head>
<title>Who plays IPv6 games?</title>
</head>
<body>
<h1>Who plays IPv6 games?</h1>
So who of us is able to use IPv6 <b>and</b> is interested in playing
IPv6 games?
If you want to register, simply submit your name below.
Obviously, your name and IPv6 address will be publicly recorded.
<p>You are player: {player}
<h2>Register</h2>
<form action="/" method=post>
Your name: <input type="text" name="name">
<input type="submit" value="Submit">
</form>
<p>Afterwards <a href="/net">register your network</a>
and then <a href="/check_addr">make your random ip reachable</a>.
<h2>Interested in playing IPv6 games are... </h2>
<ul>
{player_list}
</ul>
<h2>Open Source</h2>
As usual this project is open source and
you can find the source code on
<a href="https://code.ungleich.ch/nico/who-plays-ipv6-games">code.ungleich.ch</a>.
</body>
</html>
"""
NET_PAGE = """
<html>
<head>
<title>Register your network</title>
</head>
<body>
<h1>Register your network</h1>
For playing IPv6 games, you should have a free /64 network to play around with.
Use the format <b>2001:db8::/64</b>.
<h2>Register</h2>
<form action="/net" method=post>
Your network: <input type="text" name="network">
<input type="submit" value="Submit">
</form>
"""
ADDR_CHECK_PAGE = """
<html>
<head>
<title>Check your network</title>
</head>
<body>
<h1>Check your network</h1>
To be able to prove that you control a network, we selected a random IP in it.
Only if you can make that IPv6 address <b>ping6-able</b>, then we believe that
you really control it.
<p>You are: {player}
<p>
Your registered random IPv6 address in your network is {random_ip}.
The result of the check is: {returncode} (0 mean s reachable, everything else is an error)
and the output of the ping command is:
<pre>
{output}
</pre>
(we should really convert to jinja2 now)
"""
NO_NETWORK = """Sorry, this is not an IPv6 network"""
NO_SLASH_64 = """Sorry, this is not a /64 IPv6 network"""
NO_USER = """Sorry, you are not a user, you cannot check your IPv6 address. Register first"""
def player_exists_at_address(address):
fname = os.path.join(PLAYER_DIR, address)
exists = False
if os.path.exists(fname):
exists = True
return exists
def addresses():
address_list = []
if os.path.isdir(PLAYER_DIR):
address_list = os.listdir(PLAYER_DIR)
return address_list
def networks():
network_list = []
if os.path.isdir(NET_DIR):
network_list = os.listdir(NET_DIR)
return network_list
def get_player_from_network(network):
fname = os.path.join(NET_DIR, network, "address")
with open(fname,"r") as fd:
address = fd.readlines()[0].strip()
return address
def get_player_network(address):
network = None
for net in networks():
fname = os.path.join(NET_DIR, net, "address")
with open(fname,"r") as fd:
net_address = fd.readlines()[0].strip()
if net_address == address:
network = net
return network
def get_player_ip(address):
random_ip = None
network = get_player_network(address)
if not network:
return None
fname = os.path.join(NET_DIR, network, "random_ip")
if os.path.exists(fname):
with open(fname,"r") as fd:
random_ip = fd.readlines()[0].strip()
return random_ip
def get_player_name(address):
fname = os.path.join(PLAYER_DIR, address)
with open(fname, "r") as fd:
name = fd.readlines()
return name[0].strip()
def save_player(name, address):
fname = os.path.join(PLAYER_DIR, address)
with open(fname, "w+") as fd:
fd.write("{}\n".format(name))
def is_v6_network(network):
is_network = True
try:
net = ipaddress.IPv6Network(network)
except:
is_network = False
return is_network
def is_v6_slash_64(network):
is_64 = True
try:
net = ipaddress.IPv6Network(network)
print("{} {}".format(net, network))
if not net.prefixlen == 64:
is_64 = False
except:
is_64 = False
return is_64
def get_random_ip(network):
net = ipaddress.IPv6Network(network)
addr_offset = random.randrange(2**64)
addr = net[0] + addr_offset
return addr
def save_network(address, network):
if not is_v6_network(network):
return NO_NETWORK
if not is_v6_slash_64(network):
return NO_SLASH_64
if not os.path.isdir(NET_DIR):
os.mkdir(NET_DIR)
# get filesystem friendly name
net_name = str(ipaddress.IPv6Network(network)[0])
dirname = os.path.join(NET_DIR, net_name)
if not os.path.isdir(dirname):
os.mkdir(dirname)
fname = os.path.join(dirname, "address")
with open(fname, "w+") as fd:
fd.write("{}\n".format(address))
fname = os.path.join(dirname, "network")
with open(fname, "w+") as fd:
fd.write("{}\n".format(network))
random_ip = get_random_ip(network)
print("ip={}".format(random_ip))
fname = os.path.join(dirname, "random_ip")
with open(fname, "w+") as fd:
fd.write("{}\n".format(random_ip))
return redirect(url_for('net'))
def identify_user(address):
""" Find user by connecting IPv6 address """
player = None
# found the player
if player_exists_at_address(address):
return address
# check whether player is connecting from a registered network
v6_addr = ipaddress.IPv6Address(address)
for net in networks():
v6_net = ipaddress.IPv6Network("{}/64".format(net))
if v6_addr in v6_net:
player = get_player_from_network(net)
break
return player
@app.route('/check_addr', methods=['GET'])
def check_addr():
address = request.remote_addr
player = identify_user(address)
if not player:
return NO_USER
random_ip = get_player_ip(player)
s = subprocess.run(["ping", "-c1", random_ip], capture_output=True)
return ADDR_CHECK_PAGE.format(player=player,
random_ip=random_ip,
returncode=s.returncode,
output=s.stdout.decode("utf-8"))
@app.route('/net', methods=['GET', 'POST' ])
def net():
if request.method == 'GET':
return NET_PAGE
else:
network = request.form['network']
address = request.remote_addr
return save_network(address, network)
@app.route('/', methods=['GET', 'POST' ])
def main():
address = request.remote_addr
if request.method == 'GET':
html = []
player = identify_user(address)
for address in addresses():
name = get_player_name(address)
network = get_player_network(address)
random_ip = get_player_ip(address)
html.append("<li> {}@{}: net: {} random_ip: {}".format(name,address, network, random_ip))
return REGULAR_PAGE.format(player_list="\n".join(html), player=player)
else:
name = request.form['name']
save_player(name, address)
return redirect(url_for('main'))
if __name__ == '__main__':
if not os.path.isdir(DB_DIR):
os.mkdir(DB_DIR)
app.run(host="::", debug=True)