#!/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)