diff --git a/server.py b/server.py index 38bc4d0..86fba74 100644 --- a/server.py +++ b/server.py @@ -2,54 +2,39 @@ USERLENGTH = 50 -import ipaddress -import random -import sys -import etcd -import json -import datetime +# Important imports -from flask import Flask, abort, request, Response +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() -class etcdWrapper(object): - """ Generalises some etcd actions """ - - def __init__(self, client, base): - self.client = client - self.base = base - - def read_key_or_none(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_user_key_or_none(self, user, key): - path = "{}/user/{}/{}".format(self.base, user, key) - return self.read_key_or_none(path) - - def set_user_key(self, user, key, value): - path = "{}/user/{}/{}".format(self.base, user, key) - self.client.write(path, value) - +# Classes class Challenge(object): """ A sample challenge -- inherit this and overwrite accordingly """ @@ -76,16 +61,22 @@ class Challenge(object): """ 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.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 @@ -95,6 +86,7 @@ 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'] @@ -107,16 +99,25 @@ Submit your network with the "network" parameter. if not net.prefixlen == 64: return Response(status=400, response="{} mask is not /64 - please use a /64 network".format(net)) - # Save network - self.db.set_user_key(user, "network", network) - self.save_points(user) + 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 - return "Network {} registered, have fun with the next challenge!".format(network) +###################################### 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! @@ -131,23 +132,22 @@ and POST to this address, when it should be reachable by ping6. def describe(self): args = require_args("user") user = args['user'] - key = "network" + #key = "network" - network = self.db.get_user_key_or_none(user, key) + 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.db.get_user_key_or_none(user, key) + #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) + #self.db.set_user_key(user, key, address) else: address = address.value @@ -156,35 +156,76 @@ provide the network are: def solve(self): args = require_args("user") user = args['user'] - - return Response(status=400, response=""" -Not yet implemented""") + 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="/ungleichgame/v1"): - self.client = etcdclient + def __init__(self, name, etcdclient, etcbase="/"): self.etcbase = etcbase - - self.wrapper = etcdWrapper(etcdclient, self.etcbase) - + self.wrapper = Etcd3Wrapper() self.app = Flask(name) + #self.userbase = "{}/user".format(self.etcbase) - self.app.add_url_rule('/', 'index', self.index) - self.app.add_url_rule('/points', 'points', self.points) - self.app.add_url_rule('/register', 'register', self.register, methods=['POST']) +# Index text works! _ - # etcd paths are below here + 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) - self.userbase = "{}/user".format(self.etcbase) +# 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.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.providers = {} self.challenge_names = [] for challenge in challenges: @@ -212,45 +253,46 @@ class Game(object): challenge.dependencies_provided_by[requirement] = self.providers[requirement] +# List of challenges works _ - def list_of_challenges(self): - 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: - + 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(self): + def get_points(user): """ Returns a dict['username'] = points """ - user_points = {} + #user_points = {} - path = "{}/".format(self.userbase) - users = self.wrapper.read_key_or_none(path) + #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.children: - username = user.key.replace(path,"") - user_points[username] = 0 + ###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 - point_path = "{}/points".format(user.key) - points = self.wrapper.read_key_or_none(point_path, recursive=True) + # for challenge in points.children: + # user_points[username] += int(challenge.value) - if not points: - continue + # return user_points - for challenge in points.children: - user_points[username] += int(challenge.value) - - return user_points - - def index(self): - points = self.points() + # def index(self): + # points = self.points() return """Welcome to the game server! @@ -278,76 +320,8 @@ Point list (aka high score) {} """.format("\n".join(res)) - def register(self): - args = require_args("user") - path = "{}/{}/registered_at".format(self.userbase, args['user']) - value = str(datetime.datetime.now()) - - cur = self.read_etcd(path) - - if cur: - value = cur.value - else: - self.client.write(path, value) - - return "Registered at: {}\n".format(value) - -# 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") - +# This (below) works :D if __name__ == '__main__': - g = Game(__name__, etcd.Client(port=2379)) + g = Game(__name__, etcd3.client(port=2379)) g.app.run(host="::", port=5002)