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-05-26 19:19:58 +00:00
|
|
|
import ipaddress
|
|
|
|
import random
|
|
|
|
import sys
|
|
|
|
import etcd
|
|
|
|
import json
|
|
|
|
import datetime
|
2019-04-14 16:57:39 +00:00
|
|
|
|
2019-05-26 19:19:58 +00:00
|
|
|
from flask import Flask, abort, request, Response
|
|
|
|
from flask_restful import reqparse
|
2019-04-14 16:57:39 +00:00
|
|
|
|
2019-05-26 19:19:58 +00:00
|
|
|
# app = Flask(__name__)
|
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
|
2019-04-14 16:57:39 +00:00
|
|
|
|
2019-05-26 19:19:58 +00:00
|
|
|
return addr
|
2019-05-11 22:18:03 +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-11 22:18:03 +00:00
|
|
|
|
2019-05-26 19:19:58 +00:00
|
|
|
def __init__(self, etcdclient):
|
|
|
|
self.client = etcdclient
|
2019-05-11 22:18:03 +00:00
|
|
|
|
2019-05-26 19:19:58 +00:00
|
|
|
def require_args(self, *args):
|
|
|
|
parser = reqparse.RequestParser()
|
|
|
|
for arg in args:
|
|
|
|
parser.add_argument(arg, required=True)
|
|
|
|
return parser.parse_args()
|
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__)
|
|
|
|
self.set_user_key(user, key, self.points)
|
|
|
|
|
|
|
|
def set_user_key(self, user, key, value):
|
|
|
|
path = "/ungleichgame/v1/user/{}/{}".format(user, key)
|
|
|
|
self.client.write(path, value)
|
2019-05-26 19:19:58 +00:00
|
|
|
|
|
|
|
def solve(self):
|
|
|
|
""" Needs to be implemented per challenge """
|
|
|
|
pass
|
|
|
|
|
|
|
|
class RegisterNet(Challenge):
|
|
|
|
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-05-26 20:04:59 +00:00
|
|
|
args = self.require_args("user", "network")
|
|
|
|
network = args['network']
|
|
|
|
user = args['user']
|
2019-05-26 19:19:58 +00:00
|
|
|
|
|
|
|
try:
|
2019-05-26 20:04:59 +00:00
|
|
|
net = ipaddress.IPv6Network(network)
|
2019-05-26 19:19:58 +00:00
|
|
|
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))
|
|
|
|
|
|
|
|
# Save network
|
2019-05-26 20:04:59 +00:00
|
|
|
self.set_user_key(user, "network", network)
|
|
|
|
self.save_points(user)
|
2019-05-26 19:19:58 +00:00
|
|
|
|
2019-05-26 20:04:59 +00:00
|
|
|
return "Network {} registered, have fun with the next challenge!".format(network)
|
2019-05-26 19:19:58 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Game(object):
|
|
|
|
def __init__(self, name, etcdclient, etcbase="/ungleichgame/v1"):
|
|
|
|
self.client = etcdclient
|
|
|
|
self.app = Flask(name)
|
|
|
|
|
2019-05-26 20:04:59 +00:00
|
|
|
self.app.add_url_rule('/', 'index', self.index)
|
|
|
|
self.app.add_url_rule('/points', 'points', self.points)
|
2019-05-26 19:19:58 +00:00
|
|
|
|
|
|
|
# etcd paths are below here
|
|
|
|
self.etcbase = etcbase
|
|
|
|
self.userbase = "{}/user".format(self.etcbase)
|
|
|
|
|
|
|
|
# Automate this
|
|
|
|
challenges = [ RegisterNet ]
|
|
|
|
|
2019-05-26 20:04:59 +00:00
|
|
|
self.app.add_url_rule('/challenge', 'list_of_challenges', self.list_of_challenges)
|
|
|
|
self.app.add_url_rule('/challenge/', 'list_of_challenges', self.list_of_challenges)
|
|
|
|
|
|
|
|
self.challenge_names = []
|
2019-05-26 19:19:58 +00:00
|
|
|
for challenge in challenges:
|
|
|
|
c = challenge(self.client)
|
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'])
|
|
|
|
|
|
|
|
|
|
|
|
def list_of_challenges(self):
|
|
|
|
return """The following challenges are available on this server:
|
2019-05-26 20:15:13 +00:00
|
|
|
|
2019-05-26 20:04:59 +00:00
|
|
|
{}
|
2019-05-26 20:15:13 +00:00
|
|
|
|
2019-05-26 20:04:59 +00:00
|
|
|
""".format("\n".join(self.challenge_names))
|
2019-05-11 22:18:03 +00:00
|
|
|
|
|
|
|
|
2019-05-26 19:19:58 +00:00
|
|
|
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
|
2019-05-11 22:18:03 +00:00
|
|
|
|
2019-05-26 20:04:59 +00:00
|
|
|
def get_points(self):
|
2019-05-26 19:19:58 +00:00
|
|
|
""" Returns a dict['username'] = points """
|
|
|
|
|
2019-05-26 20:04:59 +00:00
|
|
|
user_points = {}
|
|
|
|
|
|
|
|
path = "{}/".format(self.userbase)
|
|
|
|
users = self.client.get(path)
|
|
|
|
|
|
|
|
if users:
|
|
|
|
print(users)
|
|
|
|
for user in users.children:
|
|
|
|
username= user.key # needs to be FIXED
|
|
|
|
user_points[username] = 0
|
|
|
|
|
|
|
|
point_path = "{}/points".format(user.key)
|
|
|
|
points = self.read_etcd(point_path, recursive=True)
|
|
|
|
|
|
|
|
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:
|
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:04:59 +00:00
|
|
|
return "\n".join(res)
|
2019-05-26 19:19:58 +00:00
|
|
|
|
|
|
|
|
|
|
|
# def get_ip_address():
|
|
|
|
# args = self.require_args("network", "user")
|
|
|
|
|
|
|
|
# # 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")
|
2019-04-14 16:57:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2019-05-26 19:19:58 +00:00
|
|
|
# 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(host="::", port='5002')
|