ungleich-game/server.py
2019-12-01 05:45:34 +01:00

327 lines
10 KiB
Python

#!/usr/bin/env python3
USERLENGTH = 50
# Important imports
import ipaddress, random, sys, etcd3, json, datetime, os
from flask import Flask, abort, request, Response, jsonify
from flask_restful import reqparse
from etcd3_wrapper import Etcd3Wrapper
# Generate random IPv6 address
def get_random_ip(network):
net = ipaddress.IPv6Network(network)
addr_offset = random.randrange(2**64)
addr = net[0] + addr_offset
return addr
# Check our own IPv6 address by using ipv6-test API
# This might be used to check internet connectivity
# def check_internet():
# import urllib.request, urllib.error, urllib.parse
# url = 'http://v6.ipv6-test.com/api/myip.php'
# response = urllib.request.urlopen(url)
# webContent = response.read()
# return webContent
# RESTful flask arguments parsing
def require_args(*args):
parser = reqparse.RequestParser()
for arg in args:
parser.add_argument(arg, required=True)
return parser.parse_args()
# Classes
class Challenge(object):
""" A sample challenge -- inherit this and overwrite accordingly """
points = 0
provides = []
requires = []
description = None
dependencies_provided_by = {}
def __init__(self, wrapper):
self.db = wrapper
def game(self):
if request.method == 'GET':
return self.describe()
if request.method == 'POST':
return self.solve()
def describe(self):
return self.description
def save_points(self, user):
""" should be called when the challenge was solved successfully"""
key = "points/{}".format(user, type(self).__name__)
#self.db.set_user_key(user, key, self.points)
self.wrapper.put('/users' + user + '/points', self.points)
def solve(self):
""" Needs to be implemented per challenge """
pass
# Challenge description works
class RegisterNet(Challenge):
#self.etcbase = etcbase
#self.wrapper = Etcd3Wrapper()
#self.app = Flask(name)
wrapper = Etcd3Wrapper()
points = 10
provides = [ "network" ]
description = """
Register a /64 IPv6 network that you fully control.
Many other challenges depend on this. You will need to
be able to configure IPv6 addresses in this networks
and to setup services listening on these IPv6 addresses.
Submit your network with the "network" parameter.
"""
def solve(self):
#self.wrapper = Etcd3Wrapper()
args = require_args("user", "network")
network = args['network']
user = args['user']
try:
net = ipaddress.IPv6Network(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))
path = "{}/{}/points".format("/user", user)
cur = self.wrapper.get(path)
if cur == None:
self.wrapper.put('/user/' + user + '/network', network)
points = self.wrapper.get(path)
self.wrapper.put('/user/' + user + '/points', '20') # Adjust points for first challenge here
x = "Network {} registered, have fun with the next challenge!\n".format(network)
else:
x = "Network {} is already registered.\n".format(network)
#self.db.set_user_key(user, "network", network)
#save_points(user)
return x
######################################
class IPv6Address(Challenge):
wrapper = Etcd3Wrapper()
points = 20
requires = [ "network" ]
description = """
You have setup your network, great!
Now it is time to show that you are really controlling your network!
Setup the IPv6 address
{}
and POST to this address, when it should be reachable by ping6.
"""
def describe(self):
args = require_args("user")
user = args['user']
#key = "network"
network = self.wrapper.get('/user/' + user + '/network')
if not network:
return Response(status=400, response="""
Register a network before trying to be reachable. Possible challenges that
provide the network are:
{}
""".format("\n".join(self.dependencies_provided_by['network'])))
#key = "address"
address = self.wrapper.get('/user/' + user + '/ip')
if not address:
address = get_random_ip(network.value)
#self.db.set_user_key(user, key, address)
else:
address = address.value
return self.description.format(address)
def solve(self):
args = require_args("user")
user = args['user']
address = self.wrapper.get('/user/' + user + '/ip')
response = os.system('ping -c 1 -t 3 ' + address.value)
if response == 0:
x = address.value + ' is up!'
else:
x = address.value + ' is down!'
return x
class Game(object):
def __init__(self, name, etcdclient, etcbase="/"):
self.etcbase = etcbase
self.wrapper = Etcd3Wrapper()
self.app = Flask(name)
#self.userbase = "{}/user".format(self.etcbase)
# Index text works! _
def index():
try:
from_etcd = self.wrapper.get("/index")
response = from_etcd.key, ' : ', from_etcd.value
except:
response = 'The key is empty.'
return Response(response, 200)
self.app.add_url_rule('/', 'index', index)
# End of index ^
# The registration works! _
def register():
args = require_args("user")
user = args['user']
value = str(datetime.datetime.now())
netaddr = request.remote_addr
cur = self.wrapper.get('/user/' + user)
if cur == None:
self.wrapper.put('/user/' + user, 'user exist')
self.wrapper.put('/user/' + user + '/registered_at', value)
self.wrapper.put('/user/' + user + '/ip', netaddr)
x = "User @" + user + " successfully registered on " + value + " with IP: " + netaddr + '\n'
else:
time = self.wrapper.get('/user/' + user + '/registered_at')
ip = self.wrapper.get('/user/' + user + '/ip')
x = "User @" + user + " is already registered on " + time.value + ". His IP is: " + ip.value + '\n'
return Response(x,200)
self.app.add_url_rule('/register', 'register', register, methods=['POST'])
# End of registration ^
# Getting point for one user works! _
def points():
args = require_args("user")
user = args['user']
try:
from_etcd = self.wrapper.get("/user/" + user + "/points")
response = from_etcd.key, ' : ', from_etcd.value
except:
response = 'The key is empty.'
return Response(response, 200)
self.app.add_url_rule('/points', 'points', points, methods=['POST'])
# End of getting points ^
# Automate this
challenges = [ RegisterNet, IPv6Address ]
challenge_instances = []
self.providers = {}
self.challenge_names = []
for challenge in challenges:
c = challenge(self.wrapper)
challenge_instances.append(c)
name = type(c).__name__
self.challenge_names.append(name)
path = "/challenge/{}".format(name)
self.app.add_url_rule(path, name, c.game, methods=['GET', 'POST'])
for provider in challenge.provides:
if not provider in self.providers:
self.providers[provider] = []
self.providers[provider].append(name)
# Update challenges with provider information
for challenge in challenge_instances:
for requirement in challenge.requires:
if not requirement in self.providers:
raise Exception("Unplayable server/game: {}".format(type(challenge).__name__))
challenge.dependencies_provided_by[requirement] = self.providers[requirement]
# List of challenges works _
def list_of_challenges():
base = request.base_url
challenges = [ "{} ({})".format(name, "{}/{}".format(base, name)) for name in self.challenge_names ]
return """The following challenges are available on this server:
{}
""".format("\n".join(challenges))
self.app.add_url_rule('/challenge', 'list_of_challenges', list_of_challenges)
self.app.add_url_rule('/challenge/', 'list_of_challenges', list_of_challenges)
# End of list of challenges ^
def get_points(user):
""" Returns a dict['username'] = points """
#user_points = {}
#path = "{}/".format(self.userbase)
#users = self.wrapper.read_key_or_none(path)
points = self.wrapper.get('/user/' + user + '/points').value
###if users:
### for user in users:
# username = user.key.replace(path,"")
# user_points[username] = 0
### print("inside users: user " + user)
#point_path = "{}/points".format(user.key)
### points = self.wrapper.get("{}/points".format(user))
### print("Points: " + points)
# if not points:
# continue
# for challenge in points.children:
# user_points[username] += int(challenge.value)
# return user_points
# def index(self):
# points = self.points()
return """Welcome to the game server!
Current point list is:
{}
For more information visit
https://code.ungleich.ch/nico/ungleich-game
""".format(points)
def points(self):
point_list = self.get_points()
res = []
if not point_list:
return Response("No winners yet!")
for k, v in point_list.items():
res.append("{} has {} points".format(k, v))
return """
Point list (aka high score)
---------------------------
{}
""".format("\n".join(res))
# This (below) works :D
if __name__ == '__main__':
g = Game(__name__, etcd3.client(port=2379))
g.app.run(host="::", port=5002)