ungleich-game/server.py

328 lines
10 KiB
Python
Raw Normal View History

2019-05-26 19:19:58 +00:00
#!/usr/bin/env python3
2019-04-14 16:57:39 +00:00
2019-05-26 19:19:58 +00:00
USERLENGTH = 50
2019-04-14 16:57:39 +00:00
2019-12-01 04:45:34 +00:00
# Important imports
import ipaddress, random, sys, etcd3, json, datetime, os
from flask import Flask, abort, request, Response, jsonify
2019-05-26 19:19:58 +00:00
from flask_restful import reqparse
2019-12-01 04:45:34 +00:00
from etcd3_wrapper import Etcd3Wrapper
# Generate random IPv6 address
2019-04-14 16:57:39 +00:00
2019-05-26 19:19:58 +00:00
def get_random_ip(network):
net = ipaddress.IPv6Network(network)
addr_offset = random.randrange(2**64)
addr = net[0] + addr_offset
return addr
2019-05-11 22:18:03 +00:00
2019-12-01 04:45:34 +00:00
# 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
2019-05-26 20:33:28 +00:00
def require_args(*args):
parser = reqparse.RequestParser()
for arg in args:
parser.add_argument(arg, required=True)
return parser.parse_args()
2019-12-01 04:45:34 +00:00
# Classes
2019-05-26 21:30:58 +00:00
2019-05-26 19:19:58 +00:00
class Challenge(object):
""" A sample challenge -- inherit this and overwrite accordingly """
2019-05-11 22:18:03 +00:00
2019-05-26 19:19:58 +00:00
points = 0
provides = []
requires = []
2019-05-26 20:04:59 +00:00
description = None
2019-05-26 21:30:58 +00:00
dependencies_provided_by = {}
2019-05-11 22:18:03 +00:00
2019-05-26 21:30:58 +00:00
def __init__(self, wrapper):
self.db = wrapper
2019-05-11 22:18:03 +00:00
2019-05-26 20:04:59 +00:00
def game(self):
if request.method == 'GET':
return self.describe()
if request.method == 'POST':
return self.solve()
2019-05-26 19:19:58 +00:00
def describe(self):
return self.description
2019-05-11 22:18:03 +00:00
2019-05-26 19:19:58 +00:00
def save_points(self, user):
""" should be called when the challenge was solved successfully"""
2019-05-11 22:18:03 +00:00
2019-05-26 20:04:59 +00:00
key = "points/{}".format(user, type(self).__name__)
2019-12-01 04:45:34 +00:00
#self.db.set_user_key(user, key, self.points)
self.wrapper.put('/users' + user + '/points', self.points)
2019-05-26 19:19:58 +00:00
def solve(self):
""" Needs to be implemented per challenge """
pass
2019-12-01 04:45:34 +00:00
# Challenge description works
2019-05-26 19:19:58 +00:00
class RegisterNet(Challenge):
2019-12-01 04:45:34 +00:00
#self.etcbase = etcbase
#self.wrapper = Etcd3Wrapper()
#self.app = Flask(name)
wrapper = Etcd3Wrapper()
2019-05-26 19:19:58 +00:00
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.
2019-05-11 22:18:03 +00:00
"""
2019-05-26 19:19:58 +00:00
def solve(self):
2019-12-01 04:45:34 +00:00
#self.wrapper = Etcd3Wrapper()
2019-05-26 20:33:28 +00:00
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))
2019-12-01 04:45:34 +00:00
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
2019-05-26 20:33:28 +00:00
2019-12-01 04:45:34 +00:00
######################################
2019-05-26 20:33:28 +00:00
class IPv6Address(Challenge):
2019-12-01 04:45:34 +00:00
wrapper = Etcd3Wrapper()
2019-05-26 20:33:28 +00:00
points = 20
requires = [ "network" ]
description = """
You have setup your network, great!
Now it is time to show that you are really controlling your network!
2019-05-26 21:30:58 +00:00
Setup the IPv6 address
{}
and POST to this address, when it should be reachable by ping6.
2019-05-26 20:33:28 +00:00
"""
def describe(self):
2019-05-26 21:30:58 +00:00
args = require_args("user")
2019-05-26 20:04:59 +00:00
user = args['user']
2019-12-01 04:45:34 +00:00
#key = "network"
2019-05-26 19:19:58 +00:00
2019-12-01 04:45:34 +00:00
network = self.wrapper.get('/user/' + user + '/network')
2019-05-26 21:30:58 +00:00
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'])))
2019-05-26 19:19:58 +00:00
2019-12-01 04:45:34 +00:00
#key = "address"
address = self.wrapper.get('/user/' + user + '/ip')
2019-05-26 21:30:58 +00:00
if not address:
address = get_random_ip(network.value)
2019-12-01 04:45:34 +00:00
#self.db.set_user_key(user, key, address)
2019-05-26 21:30:58 +00:00
else:
address = address.value
return self.description.format(address)
def solve(self):
args = require_args("user")
user = args['user']
2019-12-01 04:45:34 +00:00
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
2019-05-26 19:19:58 +00:00
class Game(object):
2019-12-01 04:45:34 +00:00
def __init__(self, name, etcdclient, etcbase="/"):
2019-05-26 21:30:58 +00:00
self.etcbase = etcbase
2019-12-01 04:45:34 +00:00
self.wrapper = Etcd3Wrapper()
2019-05-26 19:19:58 +00:00
self.app = Flask(name)
2019-12-01 04:45:34 +00:00
#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 ^
2019-05-26 19:19:58 +00:00
# Automate this
2019-05-26 21:30:58 +00:00
challenges = [ RegisterNet, IPv6Address ]
challenge_instances = []
2019-05-26 19:19:58 +00:00
2019-05-26 21:30:58 +00:00
self.providers = {}
2019-05-26 20:04:59 +00:00
self.challenge_names = []
2019-05-26 19:19:58 +00:00
for challenge in challenges:
2019-05-26 21:30:58 +00:00
c = challenge(self.wrapper)
challenge_instances.append(c)
2019-05-26 20:04:59 +00:00
name = type(c).__name__
self.challenge_names.append(name)
2019-05-26 19:19:58 +00:00
path = "/challenge/{}".format(name)
2019-05-26 20:04:59 +00:00
self.app.add_url_rule(path, name, c.game, methods=['GET', 'POST'])
2019-05-26 21:30:58 +00:00
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]
2019-12-01 04:45:34 +00:00
# List of challenges works _
2019-05-26 20:04:59 +00:00
2019-12-01 04:45:34 +00:00
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:
2019-05-26 20:04:59 +00:00
{}
2019-05-26 20:49:26 +00:00
""".format("\n".join(challenges))
2019-12-01 04:45:34 +00:00
self.app.add_url_rule('/challenge', 'list_of_challenges', list_of_challenges)
self.app.add_url_rule('/challenge/', 'list_of_challenges', list_of_challenges)
2019-05-11 22:18:03 +00:00
2019-12-01 04:45:34 +00:00
# End of list of challenges ^
2019-05-11 22:18:03 +00:00
2019-12-01 04:45:34 +00:00
def get_points(user):
2019-05-26 19:19:58 +00:00
""" Returns a dict['username'] = points """
2019-12-01 04:45:34 +00:00
#user_points = {}
2019-05-26 20:04:59 +00:00
2019-12-01 04:45:34 +00:00
#path = "{}/".format(self.userbase)
#users = self.wrapper.read_key_or_none(path)
points = self.wrapper.get('/user/' + user + '/points').value
2019-05-26 20:04:59 +00:00
2019-12-01 04:45:34 +00:00
###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
2019-05-26 20:04:59 +00:00
2019-12-01 04:45:34 +00:00
# for challenge in points.children:
# user_points[username] += int(challenge.value)
2019-05-26 20:37:58 +00:00
2019-12-01 04:45:34 +00:00
# return user_points
2019-05-26 20:04:59 +00:00
2019-12-01 04:45:34 +00:00
# def index(self):
# points = self.points()
2019-05-26 20:04:59 +00:00
return """Welcome to the game server!
Current point list is:
2019-05-26 20:05:47 +00:00
2019-05-26 20:04:59 +00:00
{}
For more information visit
https://code.ungleich.ch/nico/ungleich-game
""".format(points)
def points(self):
point_list = self.get_points()
2019-05-26 19:19:58 +00:00
res = []
if not point_list:
return Response("No winners yet!")
for k, v in point_list.items():
2019-05-26 20:04:59 +00:00
res.append("{} has {} points".format(k, v))
2019-05-26 19:19:58 +00:00
2019-05-26 20:37:58 +00:00
return """
Point list (aka high score)
---------------------------
{}
""".format("\n".join(res))
2019-05-26 19:19:58 +00:00
2019-12-01 04:45:34 +00:00
# This (below) works :D
2019-04-14 16:57:39 +00:00
if __name__ == '__main__':
2019-12-01 04:45:34 +00:00
g = Game(__name__, etcd3.client(port=2379))
2019-11-10 13:45:28 +00:00
g.app.run(host="::", port=5002)