ungleich-game/game-etcd.py

208 lines
6.1 KiB
Python

#!/usr/bin/env python3
USERLENGTH = 50
import ipaddress
import random
import sys
import etcd
import ungleichapi
import json
import datetime
from flask import Flask, abort, request, Response
from flask_restful import Resource, Api
from flask_restful import reqparse
app = Flask(__name__)
api = Api(app)
def get_random_ip(network):
net = ipaddress.IPv6Network(network)
addr_offset = random.randrange(2**64)
addr = net[0] + addr_offset
return addr
class Level(Resource):
points = 0
def test(self):
pass
class Ping6(Level):
points = 10
def test(self):
"""
ping6 -c3
"""
class Game(object):
def __init__(self, name, etcdclient, etcbase="/ungleichgame/v1"):
self.client = etcdclient
self.app = Flask(name)
self.app.add_url_rule('/', 'highscore', self.highscore)
self.app.add_url_rule('/highscore', 'highscore', self.highscore)
self.app.add_url_rule("/register", 'register', self.register, methods=['POST'])
# etcd paths are below here
self.etcbase = etcbase
self.userbase = "{}/user".format(self.etcbase)
def read_etcd(self, path, recursive=False):
try:
data = self.client.read(path, recursive=recursive)
except etcd.EtcdKeyNotFound:
return None
except Exception:
abort(Response(status=400, response="Error connecting to etcd"))
return data
def get_highscore(self, username=None):
""" Returns a dict['username'] = points """
all_users = {}
highscore = {}
print("getting high")
if username:
path = "{}/{}".format(self.userbase, username)
user = self.read_etcd(path)
if user:
all_users[username] = user
else:
path = "{}/".format(self.userbase)
users = self.read_etcd(path, recursive=True)
print("reading from {}".format(path))
if users:
for child in users.children:
print("adding user {} {} = {}".format(child, child.key, child.value))
all_users[child.key] = child.value
for k, v in all_users.items():
# Ignore all kind of errors - just add the ones that work
try:
highscore[k] = json.loads(v)['points']
print("f?")
except Exception as e:
print(e)
return highscore
def highscore(self):
point_list = self.get_highscore()
res = []
if not point_list:
return Response("No winners yet!")
for k, v in point_list.items():
res.append("<p>{} has {} points</p>".format(k, v))
return Response("\n".join(res))
def require_args(self, *args):
parser = reqparse.RequestParser()
for arg in args:
parser.add_argument(arg, required=True)
return parser.parse_args()
def register(self):
args = self.require_args("network", "user")
# Needs to be fixed with ungleich-otp
username=args['user']
try:
net = ipaddress.IPv6Network(args['network'])
network = args['network']
except Exception as e:
return Response(status=400, response="Cannot register network {}: {}".format(network, e))
if not net.prefixlen == 64:
return Response(status=400, response="{} mask is not /64 - please use a /64 network".format(net))
self.client.write("/ungleichgame/v1/{}/network".format(username), network)
data = self.client.read("/ungleichgame/v1/{}/network".format(username))
return json.dumps("All good, go to /level/1 to start with level 1! - {}".format(data.value))
@app.route("/level/1", methods=['GET', 'POST']) # post for username
def get_ip_address():
parser = reqparse.RequestParser()
parser.add_argument('user', required=True)
args = parser.parse_args()
# Needs to be fixed with ungleich-otp
username=args['user']
if request.method == 'GET':
return Response("""
This is an easy level - just register any /64 network
that you fully control. After submission the game server will generate
a random IPv6 address in this network.
""")
client = etcd.Client(port=2379)
try:
data = client.read("/ungleichgame/v1/{}/network".format(username))
# FIXME: differentiate keynotfound and other errors
except Exception as e:
return Response(status=400, response="Cannot read your network, try registering first (error: {})".format(e))
return Response("data={}".format(data.value))
address = get_random_ip(data.value)
# FIXME: catch errors
client.write("/ungleichgame/v1/{}/address".format(username), address)
return Response("Your IPv6 address for this game is {}. Make it pingable and post to /level/1/result".format(address))
@app.route("/level/2", methods=['GET', 'POST']) # post for username
def pingme():
parser = reqparse.RequestParser()
parser.add_argument('user', required=True)
args = parser.parse_args()
# Needs to be fixed with ungleich-otp
username=args['user']
if request.method == 'GET':
return Response("""
Proof that you can really control the network that you submitted:
- Setup the IPv6 address to be ping6 able globally
- POST to this address when it is configured
""")
if request.method == 'POST':
try:
data = client.read("/ungleichgame/v1/{}/address".format(username), address)
except Exception as e:
return Response(status=400,
response="""
You need to register a network before trying to be reachable.
Please go back to Level 1 for registering your network.
""")
return Response("something good")
if __name__ == '__main__':
net_base = "2a0a:e5c1:{:x}::/64"
net_offset = random.randrange(0xffff)
net = ipaddress.IPv6Network(net_base.format(net_offset))
username = 'nico{}'.format(net_offset)
print("{} has {}".format(username, net))
g = Game(__name__, etcd.Client(port=2379))
g.app.run(port='5002')
# app.run(port='5002')