Compare commits
8 commits
Author | SHA1 | Date | |
---|---|---|---|
c7e5cd437b | |||
25388b1baf | |||
3053ba3d31 | |||
89037034cf | |||
3fdb385ad9 | |||
1e19efc15f | |||
e440b54ed0 | |||
cb10b41d40 |
4 changed files with 574 additions and 167 deletions
32
README.md
32
README.md
|
@ -15,36 +15,43 @@ the project name might change later.
|
||||||
* Register
|
* Register
|
||||||
* List challenges - have fun!
|
* List challenges - have fun!
|
||||||
|
|
||||||
### How to play (for instance on Nico's notebook)
|
### How to play (for instance on sxiii's laptop)
|
||||||
|
* Note: here HTTPie is used; can be replaced by curl or any other http tool
|
||||||
|
* Note2: example with localhost [::] is ipv6 localhost representation
|
||||||
|
* Note3: you can ran sample commands as-is in most cases, as they utilize your user name automatically
|
||||||
|
|
||||||
|
0. Try to access the game server without any argument
|
||||||
|
```
|
||||||
|
http [::]:5002
|
||||||
|
```
|
||||||
|
|
||||||
1. Register: send a POST request with your username
|
1. Register: send a POST request with your username
|
||||||
|
|
||||||
```
|
```
|
||||||
curl -d user=nico http://nico.ungleich.cloud:5002/register
|
http POST [::]:5002/register user=$USER
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Get challenges
|
2. Get challenges
|
||||||
|
|
||||||
```
|
```
|
||||||
curl http://nico.ungleich.cloud:5002/challenge
|
http [::]:5002/challenge
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Get a challenge description
|
3. Get a challenge description
|
||||||
|
|
||||||
```
|
```
|
||||||
curl http://nico.ungleich.cloud:5002/challenge/registernet
|
http [::]:5002/challenge/RegisterNet
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Solve a challenge
|
4. Solve a challenge
|
||||||
|
|
||||||
```
|
```
|
||||||
curl -d user=nico -d 2a0a:e5c0:101::/64 http://nico.ungleich.cloud:5002/challenge/RegisterNet
|
http POST [::]:5002/challenge/RegisterNet 'user=$USER' 'network=2a0a:e5c0:101::/64'
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Get high score
|
5. Get high score
|
||||||
|
|
||||||
```
|
```
|
||||||
curl http://nico.ungleich.cloud:5002/highscore
|
http POST [::]:5002/points 'user=$USER'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -76,6 +83,8 @@ Run
|
||||||
python server.py
|
python server.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Testing game
|
||||||
|
If you want to automatically test the game server, run the ./test_game.sh script included in this directory.
|
||||||
|
|
||||||
## Overview - Security
|
## Overview - Security
|
||||||
|
|
||||||
|
@ -90,6 +99,15 @@ The base for building games is:
|
||||||
* Flask - web frontend
|
* Flask - web frontend
|
||||||
* etcd - storing data, games, etcd.
|
* etcd - storing data, games, etcd.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
Tested packages @ Ubuntu 18.04.3
|
||||||
|
|
||||||
|
* etcd3 (important, version 3)
|
||||||
|
* python3-etcd
|
||||||
|
* python3-flask
|
||||||
|
* python3-flask-restful
|
||||||
|
|
||||||
## Things to solve
|
## Things to solve
|
||||||
|
|
||||||
* Enhance the Challenge class - maybe make it easier for challenges to abort
|
* Enhance the Challenge class - maybe make it easier for challenges to abort
|
||||||
|
|
353
archive/oldserver.py
Normal file
353
archive/oldserver.py
Normal file
|
@ -0,0 +1,353 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
USERLENGTH = 50
|
||||||
|
|
||||||
|
import ipaddress
|
||||||
|
import random
|
||||||
|
import sys
|
||||||
|
import etcd
|
||||||
|
import json
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from flask import Flask, abort, request, Response
|
||||||
|
from flask_restful import reqparse
|
||||||
|
|
||||||
|
def get_random_ip(network):
|
||||||
|
net = ipaddress.IPv6Network(network)
|
||||||
|
addr_offset = random.randrange(2**64)
|
||||||
|
addr = net[0] + addr_offset
|
||||||
|
|
||||||
|
return addr
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
def solve(self):
|
||||||
|
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))
|
||||||
|
|
||||||
|
# Save network
|
||||||
|
self.db.set_user_key(user, "network", network)
|
||||||
|
self.save_points(user)
|
||||||
|
|
||||||
|
return "Network {} registered, have fun with the next challenge!".format(network)
|
||||||
|
|
||||||
|
class IPv6Address(Challenge):
|
||||||
|
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.db.get_user_key_or_none(user, key)
|
||||||
|
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)
|
||||||
|
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']
|
||||||
|
|
||||||
|
return Response(status=400, response="""
|
||||||
|
Not yet implemented""")
|
||||||
|
|
||||||
|
|
||||||
|
class Game(object):
|
||||||
|
def __init__(self, name, etcdclient, etcbase="/ungleichgame/v1"):
|
||||||
|
self.client = etcdclient
|
||||||
|
self.etcbase = etcbase
|
||||||
|
|
||||||
|
self.wrapper = etcdWrapper(etcdclient, self.etcbase)
|
||||||
|
|
||||||
|
self.app = Flask(name)
|
||||||
|
|
||||||
|
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'])
|
||||||
|
|
||||||
|
# etcd paths are below here
|
||||||
|
|
||||||
|
self.userbase = "{}/user".format(self.etcbase)
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
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]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
{}
|
||||||
|
|
||||||
|
""".format("\n".join(challenges))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def get_points(self):
|
||||||
|
""" Returns a dict['username'] = points """
|
||||||
|
|
||||||
|
user_points = {}
|
||||||
|
|
||||||
|
path = "{}/".format(self.userbase)
|
||||||
|
users = self.wrapper.read_key_or_none(path)
|
||||||
|
|
||||||
|
if users:
|
||||||
|
for user in users.children:
|
||||||
|
username = user.key.replace(path,"")
|
||||||
|
user_points[username] = 0
|
||||||
|
|
||||||
|
point_path = "{}/points".format(user.key)
|
||||||
|
points = self.wrapper.read_key_or_none(point_path, recursive=True)
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
g = Game(__name__, etcd.Client(port=2379))
|
||||||
|
g.app.run(host="::", port=5002)
|
288
server.py
288
server.py
|
@ -2,54 +2,39 @@
|
||||||
|
|
||||||
USERLENGTH = 50
|
USERLENGTH = 50
|
||||||
|
|
||||||
import ipaddress
|
# Important imports
|
||||||
import random
|
|
||||||
import sys
|
|
||||||
import etcd
|
|
||||||
import json
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
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 flask_restful import reqparse
|
||||||
|
from etcd3_wrapper import Etcd3Wrapper
|
||||||
|
|
||||||
|
# Generate random IPv6 address
|
||||||
|
|
||||||
def get_random_ip(network):
|
def get_random_ip(network):
|
||||||
net = ipaddress.IPv6Network(network)
|
net = ipaddress.IPv6Network(network)
|
||||||
addr_offset = random.randrange(2**64)
|
addr_offset = random.randrange(2**64)
|
||||||
addr = net[0] + addr_offset
|
addr = net[0] + addr_offset
|
||||||
|
|
||||||
return addr
|
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):
|
def require_args(*args):
|
||||||
parser = reqparse.RequestParser()
|
parser = reqparse.RequestParser()
|
||||||
for arg in args:
|
for arg in args:
|
||||||
parser.add_argument(arg, required=True)
|
parser.add_argument(arg, required=True)
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
class etcdWrapper(object):
|
# Classes
|
||||||
""" 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)
|
|
||||||
|
|
||||||
|
|
||||||
class Challenge(object):
|
class Challenge(object):
|
||||||
""" A sample challenge -- inherit this and overwrite accordingly """
|
""" A sample challenge -- inherit this and overwrite accordingly """
|
||||||
|
@ -76,16 +61,22 @@ class Challenge(object):
|
||||||
""" should be called when the challenge was solved successfully"""
|
""" should be called when the challenge was solved successfully"""
|
||||||
|
|
||||||
key = "points/{}".format(user, type(self).__name__)
|
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):
|
def solve(self):
|
||||||
""" Needs to be implemented per challenge """
|
""" Needs to be implemented per challenge """
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Challenge description works
|
||||||
|
|
||||||
class RegisterNet(Challenge):
|
class RegisterNet(Challenge):
|
||||||
|
#self.etcbase = etcbase
|
||||||
|
#self.wrapper = Etcd3Wrapper()
|
||||||
|
#self.app = Flask(name)
|
||||||
|
wrapper = Etcd3Wrapper()
|
||||||
points = 10
|
points = 10
|
||||||
provides = [ "network" ]
|
provides = [ "network" ]
|
||||||
|
|
||||||
description = """
|
description = """
|
||||||
Register a /64 IPv6 network that you fully control.
|
Register a /64 IPv6 network that you fully control.
|
||||||
Many other challenges depend on this. You will need to
|
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.
|
Submit your network with the "network" parameter.
|
||||||
"""
|
"""
|
||||||
def solve(self):
|
def solve(self):
|
||||||
|
#self.wrapper = Etcd3Wrapper()
|
||||||
args = require_args("user", "network")
|
args = require_args("user", "network")
|
||||||
network = args['network']
|
network = args['network']
|
||||||
user = args['user']
|
user = args['user']
|
||||||
|
@ -107,16 +99,25 @@ Submit your network with the "network" parameter.
|
||||||
if not net.prefixlen == 64:
|
if not net.prefixlen == 64:
|
||||||
return Response(status=400, response="{} mask is not /64 - please use a /64 network".format(net))
|
return Response(status=400, response="{} mask is not /64 - please use a /64 network".format(net))
|
||||||
|
|
||||||
# Save network
|
path = "{}/{}/points".format("/user", user)
|
||||||
self.db.set_user_key(user, "network", network)
|
cur = self.wrapper.get(path)
|
||||||
self.save_points(user)
|
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):
|
class IPv6Address(Challenge):
|
||||||
|
wrapper = Etcd3Wrapper()
|
||||||
points = 20
|
points = 20
|
||||||
requires = [ "network" ]
|
requires = [ "network" ]
|
||||||
|
|
||||||
description = """
|
description = """
|
||||||
You have setup your network, great!
|
You have setup your network, great!
|
||||||
Now it is time to show that you are really controlling your network!
|
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):
|
def describe(self):
|
||||||
args = require_args("user")
|
args = require_args("user")
|
||||||
user = 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:
|
if not network:
|
||||||
return Response(status=400, response="""
|
return Response(status=400, response="""
|
||||||
Register a network before trying to be reachable. Possible challenges that
|
Register a network before trying to be reachable. Possible challenges that
|
||||||
provide the network are:
|
provide the network are:
|
||||||
|
|
||||||
{}
|
{}
|
||||||
""".format("\n".join(self.dependencies_provided_by['network'])))
|
""".format("\n".join(self.dependencies_provided_by['network'])))
|
||||||
|
|
||||||
|
|
||||||
key = "address"
|
#key = "address"
|
||||||
address = self.db.get_user_key_or_none(user, key)
|
address = self.wrapper.get('/user/' + user + '/ip')
|
||||||
if not address:
|
if not address:
|
||||||
address = get_random_ip(network.value)
|
address = get_random_ip(network.value)
|
||||||
self.db.set_user_key(user, key, address)
|
#self.db.set_user_key(user, key, address)
|
||||||
else:
|
else:
|
||||||
address = address.value
|
address = address.value
|
||||||
|
|
||||||
|
@ -156,35 +156,76 @@ provide the network are:
|
||||||
def solve(self):
|
def solve(self):
|
||||||
args = require_args("user")
|
args = require_args("user")
|
||||||
user = args['user']
|
user = args['user']
|
||||||
|
address = self.wrapper.get('/user/' + user + '/ip')
|
||||||
return Response(status=400, response="""
|
response = os.system('ping -c 1 -t 3 ' + address.value)
|
||||||
Not yet implemented""")
|
if response == 0:
|
||||||
|
x = address.value + ' is up!'
|
||||||
|
else:
|
||||||
|
x = address.value + ' is down!'
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
class Game(object):
|
class Game(object):
|
||||||
def __init__(self, name, etcdclient, etcbase="/ungleichgame/v1"):
|
def __init__(self, name, etcdclient, etcbase="/"):
|
||||||
self.client = etcdclient
|
|
||||||
self.etcbase = etcbase
|
self.etcbase = etcbase
|
||||||
|
self.wrapper = Etcd3Wrapper()
|
||||||
self.wrapper = etcdWrapper(etcdclient, self.etcbase)
|
|
||||||
|
|
||||||
self.app = Flask(name)
|
self.app = Flask(name)
|
||||||
|
#self.userbase = "{}/user".format(self.etcbase)
|
||||||
|
|
||||||
self.app.add_url_rule('/', 'index', self.index)
|
# Index text works! _
|
||||||
self.app.add_url_rule('/points', 'points', self.points)
|
|
||||||
self.app.add_url_rule('/register', 'register', self.register, methods=['POST'])
|
|
||||||
|
|
||||||
# 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
|
# Automate this
|
||||||
challenges = [ RegisterNet, IPv6Address ]
|
challenges = [ RegisterNet, IPv6Address ]
|
||||||
challenge_instances = []
|
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.providers = {}
|
||||||
self.challenge_names = []
|
self.challenge_names = []
|
||||||
for challenge in challenges:
|
for challenge in challenges:
|
||||||
|
@ -212,45 +253,46 @@ class Game(object):
|
||||||
challenge.dependencies_provided_by[requirement] = self.providers[requirement]
|
challenge.dependencies_provided_by[requirement] = self.providers[requirement]
|
||||||
|
|
||||||
|
|
||||||
|
# List of challenges works _
|
||||||
|
|
||||||
def list_of_challenges(self):
|
def list_of_challenges():
|
||||||
base = request.base_url
|
base = request.base_url
|
||||||
challenges = [ "{} ({})".format(name, "{}/{}".format(base, name)) for name in self.challenge_names ]
|
challenges = [ "{} ({})".format(name, "{}/{}".format(base, name)) for name in self.challenge_names ]
|
||||||
|
|
||||||
return """The following challenges are available on this server:
|
return """The following challenges are available on this server:
|
||||||
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
""".format("\n".join(challenges))
|
""".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):
|
||||||
def get_points(self):
|
|
||||||
""" Returns a dict['username'] = points """
|
""" Returns a dict['username'] = points """
|
||||||
|
|
||||||
user_points = {}
|
#user_points = {}
|
||||||
|
|
||||||
path = "{}/".format(self.userbase)
|
#path = "{}/".format(self.userbase)
|
||||||
users = self.wrapper.read_key_or_none(path)
|
#users = self.wrapper.read_key_or_none(path)
|
||||||
|
points = self.wrapper.get('/user/' + user + '/points').value
|
||||||
|
|
||||||
if users:
|
###if users:
|
||||||
for user in users.children:
|
### for user in users:
|
||||||
username = user.key.replace(path,"")
|
# username = user.key.replace(path,"")
|
||||||
user_points[username] = 0
|
# 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)
|
# for challenge in points.children:
|
||||||
points = self.wrapper.read_key_or_none(point_path, recursive=True)
|
# user_points[username] += int(challenge.value)
|
||||||
|
|
||||||
if not points:
|
# return user_points
|
||||||
continue
|
|
||||||
|
|
||||||
for challenge in points.children:
|
# def index(self):
|
||||||
user_points[username] += int(challenge.value)
|
# points = self.points()
|
||||||
|
|
||||||
return user_points
|
|
||||||
|
|
||||||
def index(self):
|
|
||||||
points = self.points()
|
|
||||||
|
|
||||||
return """Welcome to the game server!
|
return """Welcome to the game server!
|
||||||
|
|
||||||
|
@ -278,76 +320,8 @@ Point list (aka high score)
|
||||||
{}
|
{}
|
||||||
""".format("\n".join(res))
|
""".format("\n".join(res))
|
||||||
|
|
||||||
def register(self):
|
# This (below) works :D
|
||||||
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")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
g = Game(__name__, etcd.Client(port=2379))
|
g = Game(__name__, etcd3.client(port=2379))
|
||||||
g.app.run(host="::", port='5002')
|
g.app.run(host="::", port=5002)
|
||||||
|
|
62
test_game.sh
Normal file
62
test_game.sh
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
#!/bin/bash
|
||||||
|
###################################################################
|
||||||
|
# Ungleich-game testing script
|
||||||
|
# NB: For this script to work you need to start server first:
|
||||||
|
# python3 ./server.py
|
||||||
|
############################# written by @sxiii on 30.11.2019 #####
|
||||||
|
|
||||||
|
sleepdelay="10" # Delay between commands
|
||||||
|
gameaddr="[::]:5002" # Game server address
|
||||||
|
user="sxiii-$(date +%HH%MM)" # User to register
|
||||||
|
network="2a0a:e5c0:101::/64" # Network to register
|
||||||
|
|
||||||
|
cmd="http $gameaddr"
|
||||||
|
echo -e "Getting information about the game:\n $ $cmd"
|
||||||
|
sleep $sleepdelay && eval "$cmd"
|
||||||
|
|
||||||
|
cmd="http POST $gameaddr/register user=$user"
|
||||||
|
echo -e "Registering a new user called $user:\n $ $cmd"
|
||||||
|
sleep $sleepdelay && eval "$cmd"
|
||||||
|
|
||||||
|
cmd="http POST $gameaddr/register user=$user"
|
||||||
|
echo -e "Trying to register with the same user again:\n $ $cmd"
|
||||||
|
sleep $sleepdelay && eval "$cmd"
|
||||||
|
|
||||||
|
cmd="http $gameaddr/challenge"
|
||||||
|
echo -e "Looking which challenges are available:\n $ $cmd"
|
||||||
|
sleep $sleepdelay && eval "$cmd"
|
||||||
|
|
||||||
|
cmd="http $gameaddr/challenge/RegisterNet"
|
||||||
|
echo -e "Reading challenge #1 description:\n $ $cmd"
|
||||||
|
sleep $sleepdelay && eval "$cmd"
|
||||||
|
|
||||||
|
cmd="http POST $gameaddr/challenge/RegisterNet user=$user network=$network"
|
||||||
|
echo -e "Registering network (challenge 1):\n $ $cmd"
|
||||||
|
sleep $sleepdelay && eval "$cmd"
|
||||||
|
|
||||||
|
cmd="http POST $gameaddr/challenge/RegisterNet user=$user network=$network"
|
||||||
|
echo -e "Trying to register network again:\n $ $cmd"
|
||||||
|
sleep $sleepdelay && eval "$cmd"
|
||||||
|
|
||||||
|
cmd="http POST $gameaddr/points user=$user"
|
||||||
|
echo -e "Getting points for our player:\n $ $cmd"
|
||||||
|
sleep $sleepdelay && eval "$cmd"
|
||||||
|
|
||||||
|
cmd="http $gameaddr/challenge/IPv6Address"
|
||||||
|
echo -e "Reading challenge #2 description:\n $ $cmd"
|
||||||
|
sleep $sleepdelay && eval "$cmd"
|
||||||
|
|
||||||
|
cmd="http POST $gameaddr/challenge/IPv6Address user=sxiii"
|
||||||
|
echo -e "Checking if IP is pingable (challenge 2):\n $ $cmd"
|
||||||
|
sleep $sleepdelay && eval "$cmd"
|
||||||
|
sleep $sleepdelay
|
||||||
|
toilet --rainbow -t "Done! Thank you :)"
|
||||||
|
|
||||||
|
#1) Information about the game: ("curl ip/index")
|
||||||
|
#2) Registration of the new user with his IP and date: ("curl -X POST -d user=sxiii ip/register")
|
||||||
|
#3) Checking if user registered, it returns user IP (re-run previous command again)
|
||||||
|
#4) Challenges listing ("curl ip/challenge")
|
||||||
|
#5) RegisterNet challenge ("curl -X POST -d user=sxiii -d 'network=2a0a:e5c0:101::/64' ip/challenge/RegisterNet")
|
||||||
|
#6) Checking if challenge already done; it returns error (re-run prev. command)
|
||||||
|
#7) Getting points for user: ``` curl -X POST -d user=sxiii ip/points ```
|
||||||
|
#8) Challenge IPv6Address (checking if IP is pingable): ``` curl -X POST -d user=sxiii ip/challenge/IPv6Address ```
|
Loading…
Reference in a new issue