- Yusuf Elsharawy, Period 5
Online coding competitions such as Halite and similarly (but not exactly) CodinGame take user-submitted code and run them to compete in a game against each other repeatedly, usually with a ranking system that pits similarly-ranked players together.
My project recreates this system in an extensible way, allowing anyone to host a coding competition server for others to submit code to. It should be simple for the host to create their own games to support any (feasible) number of players.
- Each player's code is run in an isolated container (using Linux namespaces), only with access to the bare necessities and revoked access to security-breaking functions (i.e.
chroot
). Stack & memory usage is also limited, so there should be no way to crash the host.- Child processes that execute arbitrary code are not able to access the parent process's files.
- The only things a bad actor could do, as far as I know, is fill up the filesystem with tons of files, and idle forever to stall the server. A fork bomb may also be an issue, but I'm too scared to test it.
- Since the server cannot install submissions on its own, these cases can be caught by the operator with minimal damage.
- If the container's "creator" process encounters an error, the container's root process and all of its children will be killed, to prevent orphan processes.
- The same will happen if the container's creator's parent encounters an error, while also ensuring to clean up.
- All containers exist temporarily in the
/tmp
directory and are removed when no longer needed. They're generated with random names ensured to not collide viamkdtemp
. - There should not be any cases of orphaned processes.
- Probably valgrind clean (i.e. no memory leaks). I couldn't figure out how to test it because
valgrind
doesn't like theclone3
system call or something.
- (Note: in this context, "AI" stands for "artificial intelligence", not "machine learning" or "neural network". A deterministic, hard-coded AI is still an AI.)
- Language
- Any game and any AI for any game can be written in any language, so long as you can create a makefile for it.
- Game Creation
-
It's really easy to make a game! The game is not responsible for handling containers or reranking players, allowing the game to follow a very simple protocol to communicate with its parent. Through
stdin
andstdout
, it only has to:- Get the paths to the AIs' input/output pipes from the parent.
- Report back to the parent on the outcome of the game, all through
stdin
andstdout
.
You can also use
stderr
to print things to the terminal (for debugging/logging). In case something goes wrong, justexit
, and the parent will handle it! -
Although the game has to (to some extent) follow my made-up protocol, you can design whatever protocol you like for communication between your game and the players. Data is sent directly between the game and players with no process in between.
-
Games aren't limited to just 2 players! I made su0re everywhere to provide support for games with more players, even using a generalized version of the Elo rating system for ranking players.
-
- AI Creation
- Similarly, it's really easy to make an AI! Again,
stdin
andstdout
are redirected (andstderr
isn't), so as long as your language has input and output, you can write an AI.
- Similarly, it's really easy to make an AI! Again,
- At the time of completing this project, I was very proud of the functions and macros I made behind the scenes, such as
clone_fork
andcreate_container
incontainers.c
, as well as my error-checking macros andexeclp_block
.
- When creating this project, I let my workspace be a bit messy. I had cleaned up some of this mess by putting it into the
misc
folder, such as thecreate_container.sh
script, andreadwrite
, which I used for testing containers and protocols respectively.
Nothing, aside from POSIX C on GCC, as my project makes use of many GNU extensions. In hindsight, it might've sped up development to find something to do the container work for me.
Clone this repo, and run make
. You should get a program called ccs
(which stands for Coding Competition Server, a misnomer now that it's not really a server).
Run ./css
for a list of commands and their usages. An example consisting of a series of commands will also show up, which you should be able to directly run exactly as is. Remember that my project is only the server backend (the operator is expected to provide their own games & AIs), but you can use the example tic-tac-toe.tar
game and random_player.tar
submission to test it.
As far as I know, no errors should come up during proper usage. Any errors that do come up should have descriptive error messages pointing to the specific line where it occured (segmentation faults eradicated).
The rest of the README is my old vision of the project, with edits made for significant changes.
The entire interface, for both the server and client, will be through the command line. The client will naturally require more user interaction, while the server will require little to none. The client can also connect to a server on the same machine, in which case it's an "admin" client and can run priviledged commands (this feature may be left out, mostly because I can't think of a very practical purpose for an admin client, except maybe for throttling the number of matchmaking children for each game).
I have not yet decided whether either program will only take command line arguments, or display a sort of "shell" of their own for live interaction.
(Crossed out sections are unimplemented due to time constraints)
The included topics are:
- Allocating memory
- This is rather straightforward -- the server will definitely need to allocate memory for all sorts of things (strings, data structures, etc.)
- Working with files
Source code files will be sent to/from the server/client, so both sides will have to work with files.- User information and leaderboard stats are stored in files.
- Redirection of stdin and stdout will be necessary for communication between players and the game.
- The
flock
function is also used to ensure only one process creates a container around a folder at a time.
- Finding information about files
- To test for the existence of a directory and ensure that it is a directory,
stat
is used. - Going through the subfolders of the
users
directory is also used to find a specific user by their username.
- To test for the existence of a directory and ensure that it is a directory,
- Processes
- The server will operate with multiple subservers & other children, for the purposes of:
Matching users &competing user submissions against each other(1-many children per game)- Children of these children will
exec
the user-submitted programs - One child of these children will run the actual game to communicate with the user-submitted programs
- Children of these children will
- Re-ranking users after each match (1 child per game)
Interacting with each client (many children, fluctuating with respect to # of active users)Interacting with an "admin" client (1 child)
- Other command line utilities such as
make
,rm
andtar
are used viafork
&exec
.
- The server will operate with multiple subservers & other children, for the purposes of:
- Signals
- When building & running games & user submissions, signals are used to terminate the process if the parent dies or requests for it.
- A signal handler is also used to make sure all cleaning up is done properly upon receival of
SIGTERM
.
SemaphoresAccessing the leaderboard of a game should be locked behind a semaphore to prevent interference with the re-ranking process(design changed)
- Pipes
The "admin" client will communicate with the server via a pipe.- The matchmaking children will send information on wins/losses to the re-ranking child through a single unnamed pipe.
- The running game will also need to communicate through the user-submitted programs'
stdin
andstdout
, which will have named pipesdup2
'd into them.
SocketsAll other clients will communicate with the server through sockets.
There is only me. I will do all of the work.
- A doubly-linked list
(likely in shared memory)will be used to store all of the users, in order of their ranking, for each gamestruct player_ranking { int user_id; int score; struct player_ranking *prev; struct player_ranking *next; }
A game will be stored as (roughly):(game info is not stored in memory)struct game { struct player_ranking *leaderboard; // `game->leadboard->next` is first player char[NAME_MAX+1] name; // `NAME_MAX` comes from `<limits.h>` unsigned char min_players; unsigned char max_players; // anything else i may add };
- The file system will be used by the server for storing a lot of data, including:
- Registered users' info & submitted programs
- Each user gets their own directory, in which subdirectories contain their submissions for each game
server_data/ users/ 8005882300/ // randomly generated ID user_info // username, key, etc. submissions/ tic-tac-toe/ submission.c // user-submitted prog // executable connect-four/ Submission.java // user-submitted Submission.class prog // executable shim ...
- Each user gets their own directory, in which subdirectories contain their submissions for each game
- All games on this server and their leaderboards (it's still undecided whether
game_info
is really necessary, as I could require each game to provide info when executed with certain command line arguments)server_data/ games/ tic-tac-toe/ game_info // game's accepted # of players, etc. prog // executable leaderboard // list of players & their scores
- Registered users' info & submitted programs
I will focus on one section at a time, in mostly, but not strictly, this order:
- Running 2 player-programs and a game-program in communication with each other (by 01/14)
- An example game I may create is tic-tac-toe, checkers, or connect four
- I decide the "protocol" by which a game has to follow (e.g. game prints to stdout how many players it wants, receives in stdin the names of each player's stdin/stdout pipes)
- The game creator decides by what format input & output is sent in between the game program and the player.
- Sending outcomes of a match through a pipe to a "re-ranking" process that will, accordingly, re-rank the players (by 01/17)
- The re-ranking process and matchmaking processes will be "siblings", using shared memory created by their parent
- Ranking will use the Elo rating system or something similar. Each match will change the player's scores and have their player be sorted
- Loading games from the file system to start running & matchmaking on (by 01/19)
- Support for multiple games per server may be cut out due to time constraints
- Server-client interaction (by 01/24)
- Registering a new user
- Designate a directory in which the user's info will be stored
- Some sort of public/private key system for future login attempts?
- Sending & receiving files over a network, and replacing potentially existing ones
- The leadboard score for a resubmission will be reset as well
- Compiling the user-submitted program
- Handling other user requests, such as viewing their position on the leaderboard
- Registering a new user
- Putting all of the server components together as separate children in one program (by 01/26)
The remaining features are auxiliary, and may not be implemented due to time constraints:
- Additional user requests (by 01/31)
- Users may request to view the whole leaderboard
- Users may request to see games recently played by their bot
- Users may request to receive logs on specific previous games (consisting of the info sent to/from their program, and the opponent's program)
- Users may request to play a match against any other user, without getting scored for the result
- Security features (by 01/31)
- Running user-submitted code can be a huge security risk, so, if I have time, I will try to run the user-submitted programs in a sandbox.
- I can also double check to ensure there are no memory leaks or possible file table mishaps (accidentally leaving a pipe open before
exec
-ing, so a user-submitted program could write to it)