The goal of this homework assignment is to allow you to practice using system calls involving processes, signals, and sockets in C by implementing two utilities:
timeit
: The first utility allows users to compute the elapsed time of an
application while also enforcing a timeout (or cut off time), similar to
timeout.
curlit
: The second utility implements a simple HTTP client similar to
curl.
Be careful with the fork system call:
If you still manage to create a fork bomb, do not simply go to another machine and run the same program. Notify the csehelp@nd.edu and explain what happened.
For this assignment, record your source code and any responses to the
following activities in the homework09
folder of your assignments
GitHub repository and push your work by noon Saturday, April 26.
Before starting this homework assignment, you should first perform a git
pull
to retrieve any changes in your remote GitHub repository:
$ cd path/to/repository # Go to assignments repository
$ git switch master # Make sure we are in master branch
$ git pull --rebase # Get any remote changes not present locally
Next, create a new branch for this assignment:
$ git checkout -b homework09 # Create homework09 branch and check it out
To help you get started, the instructor has provided you with the following skeleton code:
# Go to homework09 folder
$ cd homework09
# Download Makefile
$ curl -LO https://www3.nd.edu/~pbui/teaching/cse.20289.sp25/static/txt/homework09/Makefile
# Download C skeleton code
$ curl -LO https://www3.nd.edu/~pbui/teaching/cse.20289.sp25/static/txt/homework09/curlit.c
$ curl -LO https://www3.nd.edu/~pbui/teaching/cse.20289.sp25/static/txt/homework09/socket.c
$ curl -LO https://www3.nd.edu/~pbui/teaching/cse.20289.sp25/static/txt/homework09/socket.h
$ curl -LO https://www3.nd.edu/~pbui/teaching/cse.20289.sp25/static/txt/homework09/socket.unit.c
$ curl -LO https://www3.nd.edu/~pbui/teaching/cse.20289.sp25/static/txt/homework09/timeit.c
Once downloaded, you should see the following files in your homework09
directory:
homework09
\_ Makefile # This is the Makefile for building all the project artifacts
\_ curlit.c # This is the curlit utility C source file
\_ socket.c # This is the timeit library C source file
\_ socket.h # This is the socket library C header file
\_ socket.unit.c # This is the socket library C unit test source file
\_ timeit.c # This is the timeit utility C source file
Now that the files are downloaded into the homework09
folder, you can
commit them to your git repository:
$ git add Makefile # Mark changes for commit
$ git add *.c *.h
$ git commit -m "Homework 09: Initial Import" # Record changes
After downloading these files, you can run make test
to run the tests.
# Run all tests (will trigger automatic download)
$ make test
You will notice that the Makefile downloads these additional test data and scripts:
homework09
\_ curlit.test.sh # This is the curlit utility test shell script
\_ socket.unit.sh # This is the socket library unit test shell script
\_ timeit.test.sh # This is the timeit utility test shell script
You will be using these unit tests and functional tests to verify the correctness and behavior of your code.
The test
scripts are automatically downloaded by the Makefile
, so any
modifications you do to them will be lost when you run make
again. Likewise,
because they are automatically downloaded, you do not need to add
or commit
them to your git repository.
The Makefile
contains all the rules or recipes for building the
project artifacts (e.g. timeit
, curlit
):
CC= gcc
CFLAGS= -Wall -g -std=gnu99
LD= gcc
LDFLAGS= -L.
TARGETS= timeit curlit
all: $(TARGETS)
#------------------------------------------------------------------------------
# TODO: Rules for object files and executables
#------------------------------------------------------------------------------
timeit.o:
socket.o:
curlit.o:
timeit:
curlit:
#------------------------------------------------------------------------------
# DO NOT MODIFY BELOW
#------------------------------------------------------------------------------
...
For this task, you will need to add rules for building the intermediate
object files, and the timeit
and curlit
executables with the appropriate
dependencies as shown in the DAG below:
You must use the CC
, CFLAGS
variables when appropriate in your
rules. You should also consider using automatic variables such as
$@
and $<
as well.
Once you have a working Makefile
, you should be able to run the following commands:
# Build all TARGETS
$ make
gcc -Wall -g -std=gnu99 -c -o timeit.o timeit.c
gcc -L. -o timeit timeit.o
gcc -Wall -g -std=gnu99 -c -o curlit.o curlit.c
gcc -Wall -g -std=gnu99 -c -o socket.o socket.c
gcc -L. -o curlit curlit.o socket.o
# Run all tests
$ make test
Testing timeit...
...
Testing socket...
...
Testing curlit...
...
# Remove generated artifacts
$ make clean
Note: The tests will fail if you haven't implemented all the necessary functions appropriately.
You must include the -Wall
flag in your CFLAGS
when you compile. This
also means that your code must compile without any warnings, otherwise
points will be deducted.
timeit
(2.5 Points)¶For the first activity, write a program, timeit
, that executes the given
command until a timeout is reached or the program terminates as show below:
If the verbose
mode is enabled, your timeit
should display the following
sort of debugging messages:
$ ./timeit -v
timeit.c:68:parse_options: Timeout = 10
timeit.c:69:parse_options: Verbose = 1
Usage: timeit [options] command...
Options:
-t SECONDS Timeout duration before killing command (default is 10)
-v Display verbose debugging output
$ ./timeit -t 5 -v sleep 1
timeit.c:68:parse_options: Timeout = 5
timeit.c:69:parse_options: Verbose = 1
timeit.c:85:parse_options: Command = sleep 1
timeit.c:116:main: Registering handlers...
timeit.c:119:main: Grabbing start time...
timeit.c:137:main: Sleeping for 5 seconds...
timeit.c:139:main: Waiting for child 160815...
timeit.c:131:main: Executing child...
timeit.c:145:main: Child exit status: 0
timeit.c:148:main: Grabbing end time...
Time Elapsed: 1.0
$ ./timeit -t 1 -v sleep 2
timeit.c:68:parse_options: Timeout = 1
timeit.c:69:parse_options: Verbose = 1
timeit.c:85:parse_options: Command = sleep 2
timeit.c:116:main: Registering handlers...
timeit.c:119:main: Grabbing start time...
timeit.c:137:main: Sleeping for 1 seconds...
timeit.c:139:main: Waiting for child 160841...
timeit.c:131:main: Executing child...
timeit.c:101:handle_signal: Killing child 160841...
timeit.c:145:main: Child exit status: 9
timeit.c:148:main: Grabbing end time...
Time Elapsed: 1.0
Note: If the time limit is exceeded, the parent should kill the child and wait for it. Moreover, the parent should always return the child's exit status as its own exit status.
timeit.c
¶To implement the timeit
utility, you are to complete the provided
timeit.c
source file which contains the following macros and
functions:
#define debug(M, ...) \
if (Verbose) { \
fprintf(stderr, "%s:%d:%s: " M, __FILE__, __LINE__, __func__, ##__VA_ARGS__); \
}
This
debug
macro displays the specified formatted message only if theVerbose
global variable istrue
. The message includes the name of the file, the line number, and the function at which the macro is called.
/**
* Display usage message and exit.
* @param status Exit status.
**/
void usage(int status);
This provided
usage
function displays the help message and terminates the process with the specifiedstatus
code.
/**
* Parse command line options.
* @param argc Number of command line arguments.
* @param argv Array of command line argument strings.
* @return Array of strings representing command to execute.
**/
char ** parse_options(int argc, char **argv);
This
parse_options
function processes the command line arguments by setting theTimeout
andVerbose
global variables and by constructing an array of strings to represent the specifiedcommand
.
/**
* Handle signal.
* @param signum Signal number.
**/
void handle_signal(int signum);
This provided
handle_signal
function kills the child process whenever aSIGALRM
is invoked.
The main logic for timeit
will be in the main
function. While there are
multiple approaches on how to accomplish the task of running a program with a
time limit, the recommended method is shown below:
In this approach, the main
function first registers the signal_handle
function for SIGALRM
using signal. Then the parent process performs a
fork and the child process executes the specified command
using one of
the exec variants.
After the fork, the parent process sets an alarm based on the specified
Timeout
. It then simply calls wait. If the alarm triggers, then the
handler should kill the child process. Otherwise, if the child terminates
before the Timeout
then the parent cancels the alarm and completes its
wait to retrieve the child's exit status.
To measure the elapsed time, you must use the clock_gettime function with
the CLOCK_MONOTONIC
clock identifier.
Once you have implemented timeit
, you can test it by running the
test-timeit
target:
$ make test-timeit
Testing timeit...
system calls ... Success
usage (-h) ... Success
usage (no arguments) ... Success
usage (-v, no command) ... Success
usage (-t 5 -v, no command) ... Success
sleep ... Success
sleep 1 ... Success
-v sleep 1 ... Success
-t 5 -v sleep 1 ... Success
sleep 5 ... Success
-v sleep 5 ... Success
-t 1 sleep 5 ... Success
-t 1 -v sleep 5 ... Success
-v find /etc -type f ... Success
-t 5 -v /tmp/timeit.1000/TROLL ... Success
Score 2.50 / 2.50
Status Success
socket
(1 Point)¶The curlit
utility will require using sockets in C to communicate with
a remote host. To simplify the process of establishing a connection, you
are to make consolidate all the code for connecting to a remote host into
a single socket_dial
function.
socket.c
¶To implement the socket
library, you are to complete the provided
socket.c
source file which contains the following function:
/**
* Create socket connection to specified host and port.
* @param host Host string to connect to.
* @param port Port string to connect to.
* @return Socket file stream of connection if successful, otherwise NULL.
**/
FILE *socket_dial(const char *host, const char *port)
This function uses socket to connect to the given
host
andport
using TCP and returns a read/writableFILE
stream corresponding to the socket connection.
Hints:
You must check if any of the system calls fail.
You should use getaddrinfo, socket, connect, freeaddrinfo, and fdopen.
As you implement the functions in socket.c
, you should use the
socket.unit
executable with the socket.unit.sh
script to test each of
your functions:
# Build test artifacts and run test scripts
$ make test-socket
Testing socket ...
socket_dial_success ... Success
socket_dial_failure ... Success
socket_dial_mode ... Success
Score 1.00 / 1.00
Status Success
You can also run the testing script manually:
# Run shell unit test script manually
$ ./socket.unit.sh
...
To debug your socket functions, you can use gdb on the socket.unit executable:
# Start gdb on socket.unit
$ gdb ./socket.unit
(gdb) run 0 # Run socket.unit with the "0" command line argument (ie. socket_dial_success)
...
You can also use valgrind to check for memory errors:
# Check for memory errors on first test case
# $ valgrind --leak-check=full ./socket.unit 0
curlit
(4.5 Points)¶Once you have the socket_dial
function implemented, you can complete the
curlit
utility, which performs a HTTP request at the specified URL.
Here are some examples of curlit
in action:
# Display usage message
$ ./curlit -h
Usage: curlit [-h ] URL
# Make HTTP request to http://example.com
$ ./curlit http://example.com
<!doctype html>
<html>
<head>
<title>Example Domain</title>
<meta charset="utf-8" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
...
</head>
<body>
<div>
<h1>Example Domain</h1>
<p>This domain is for use in illustrative examples in documents. You may use this
domain in literature without prior coordination or asking for permission.</p>
<p><a href="https://www.iana.org/domains/example">More information...</a></p>
</div>
</body>
</html>
Time Elapsed: 0.03 s
Bandwidth: 0.05 MB/s
# Return success if status was 200 OK
$ echo $?
0
# Make HTTP request to google.com
$ ./curlit google.com
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="http://www.google.com/">here</A>.
</BODY></HTML>
Time Elapsed: 0.07 s
Bandwidth: 0.00 MB/s
# Return failure if status was not 200 OK
$ echo $?
1
Note: Not all URLs will specify every component explicitly. Some may be missing the protocol or the port or even the path (the host must always be given).
For instance, here are some valid URLs you will need to support:
example.com
http://example.com
http://example.com:8888
http://example.com/data
example.com:9999/data
If the port is not specified, then you are to assume the default HTTP
port: 80
. If a path is not specified, then you can assume an empty
path: ""
.
curlit.c
¶To implement the curlit
utility, you are to complete the provided
curlit.c
source file which contains the following functions:
/**
* Display usage message and exit.
* @param status Exit status
**/
void usage(int status);
This provided
usage
function displays the help message and terminates the process with the specifiedstatus
code.
/**
* Parse URL string into URL structure.
* @param s URL string
* @param url Pointer to URL structure
**/
void parse_url(const char *s, URL *url);
This function parses the
s
URL string to extract thehost
,port
, andpath
components and copies them into the corresponding attributes of theURL
structure.
Hints:
Copy the URL s
string to a local buffer that you can manipulate.
Use strstr and strchr to search the local buffer for the appropriate
delimiters (e.g. HOST_DELIMITER
, PATH_DELIMITER
, and PORT_DELIMITER
) and
split the string into different components.
Use strcpy to copy the found components to the URL
structure.
/**
* Fetch contents of URL and print to standard out.
*
* Print elapsed time and bandwidth to standard error.
* @param s URL string
* @param url Pointer to URL structure
* @return true if client is able to read all of the content (or if the
* content length is unset), otherwise false
**/
bool fetch_url(URL *url);
This function performs a HTTP request with the given
URL
by usingsocket_dial
to form a TCP connection. It writes the response body or contents to standard out, and the elapsed time and bandwidth metrics to standard error. It returnsfalse
if any error was experienced during the HTTP transaction or if the server response was not200 OK
.
Hints:
You must check if any of the system calls fail.
You will want to follow the HTTP transaction steps described above and outlined below:
stream
.Return number of bytes written to stream
.
Remember that HTTP uses DOS line endings and terminates each line
with \r\n
.
Use strstr to check the response status.
Use sscanf to parse the response headers for the value of
Content-Length
.
Use fgets to read the response status and headers.
Use fread and fwrite to read the response body since it may contain binary data.
If the server does not return a HTTP status of 200 OK
or it provides a
Content-Length
and the number of bytes written to stream
does not match,
then the function should return false
to indicate an error. Otherwise,
if the HTTP status is 200 OK
and there is no Content-Length
or the
number of bytes written matches, then it should return true
.
Only a HTTP status of 200 OK
is considered successful.
Once you have implemented curlit
, you can test it by running the
test-curlit
target:
$ make test-curlit
Testing curlit utility...
curlit ... Success
curlit -h ... Success
curlit -? ... Success
curlit http://fake.host ... Success
curlit http://example.com ... Success
curlit http://nd.edu ... Success
curlit h4x0r.space ... Success
curlit h4x0r.space:9898/mind.txt ... Success
curlit h4x0r.space:9898/txt/walden.txt ... Success
curlit h4x0r.space:9898/txt/gatsby.txt ... Success
curlit http://h4x0r.space:9898/txt/warandpeace.txt ... Success
curlit http://h4x0r.space:9898/img/appa.png ... Success
Score 4.50 / 4.50
Status Success
Once you have completed all the activities above, you are to complete the following reflection quiz:
As with Reading 01, you will need to store your answers in a
homework09/answers.json
file. You can use the form above to generate the
contents of this file, or you can write the JSON by hand.
To test your quiz, you can use the check.py
script:
$ ../.scripts/check.py
Checking homework09 quiz ...
Q01 0.10
Q02 0.10
Q03 0.10
Q04 0.10
Q05 0.80
Q06 0.40
Q07 0.40
Score 2.00 / 2.00
Status Success
For one point of extra credit, you may perform a code review with either an AI agent (of your choice) or with a TA by Wednesday, April 30:
AI Code Review: Once the whole assignment is completed, pick one or two of the most challenging functions and ask the AI agent to review your original code by suggesting improvements, optimizations, or alternative implementations.
Test and verify these suggestions and then decide whether or not you wish
to keep them. Discuss in the README.md
what you think of the AI
code review and explain your decision.
To receive credit for the AI Code Review, you must:
Link a transcript or include sufficient screenshots of the code review in the Pull Request of your conversation with the AI agent.
This conversation must show your original code and the suggested improvements by the AI agent.
Discuss the code review and explain your decision in the
README.md
.
Update your Pull Request based on the feedback.
TA Code Review: Once the whole assignment is completed, meet with one of the teaching assistants from the course in-person and have them review your whole assignment.
To document this process, make the teaching assistant who is reviewing your code a reviewer on your Pull Request and have them record their suggestions as they discuss them with you in-person.
After the code review, you can decide whether or not you wish to incorporate this feedback into your implementation and update your Pull Request.
Note: The TA performing the code review does not need to be the same TA responsible for grading your assignment.
To receive credit for the TA Code Review, you must:
Meet with a teaching assistant in-person.
Make this teaching assistant a reviewer in your Pull Request.
Have the teaching assistant record their suggestions on the Pull Request.
Update your Pull Request based on the feedback.
For this week, there are two Guru Point opportunities.
For extra credit, you are to use Python and sockets to implement your own
version of bobbit, an IRC chat bot. Your bot should connect to the
chat.ndlug.org server and join the #bots
channel. It should be able to
respond to at least one type of command or message. The particular operation
is up to you.
To help you get started, here are some resources:
To connect to the NDLUG server yourself, you can use the following anonymous web client:
Alternatively, if you wish to have access to more chat features such as history, you can register via regserv.ndlug.org, which will setup a Lounge and IRC account for you.
The Lounge is a web-based IRC client that you can use from any web
browser, but you are free to connect to the IRC server from any IRC
client such as Weechat, Hexchat, or Textual using chat.ndlug.org
as
the hostname and 6697
as the port.
A basic IRC client session looks like this:
USER ircle-pbui 0 * :pbui's bot
NICK ircle-pbui
JOIN #bots
PRIVMSG #bots :I've fallen and I can't get up!
The USER
command sets the users real name and registers the user.
The NICK
command sets the users nickname.
The JOIN
command allows the user to join in a channel (in this case #bots
).
The PRIVMSG
command allows the user to send a message (in this case to
the channel #bots
).
Here is a basic skeleton, ircle.py, that you can start with:
import os
import socket
import ssl
# Constants
HOST = 'chat.ndlug.org'
PORT = 6697
NICK = f'ircle-{os.environ["USER"]}'
# Functions
def ircle():
# Connect to IRC server
ssl_context = ssl.create_default_context()
tcp_socket = socket.create_connection((HOST, PORT))
ssl_socket = ssl_context.wrap_socket(tcp_socket, server_hostname=HOST)
ssl_stream = ssl_socket.makefile('rw')
# Identify ourselves
ssl_stream.write(f'USER {NICK} 0 * :{NICK}\r\n')
ssl_stream.write(f'NICK {NICK}\r\n')
ssl_stream.flush()
# Join #bots channel
ssl_stream.write(f'JOIN #bots\r\n')
ssl_stream.flush()
# Write message to channel
ssl_stream.write(f"PRIVMSG #bots :I've fallen and I can't get up!\r\n")
ssl_stream.flush()
# Read and display
while True:
message = ssl_stream.readline().strip()
print(message)
# Main Execution
def main():
ircle()
if __name__ == '__main__':
main()
Alternatively, if you are interested in using the new asyncio features of Python 3 to perform event-driven and concurrent programming, you can use the following skeleton ircle-async.py:
import asyncio
import os
import sys
# Constants
HOST = 'chat.ndlug.org'
PORT = 6697
NICK = f'ircle-{os.environ["USER"]}'
# Functions
async def ircle():
# Connect to IRC server
reader, writer = await asyncio.open_connection(HOST, PORT, ssl=True)
# Identify ourselves
writer.write(f'USER {NICK} 0 * :{NICK}\r\n'.encode())
writer.write(f'NICK {NICK}\r\n'.encode())
await writer.drain()
# Join #bots channel
writer.write(f'JOIN #bots\r\n'.encode())
await writer.drain()
# Write message to channel
writer.write(f"PRIVMSG #bots :I've fallen and I can't get up!\r\n".encode())
await writer.drain()
# Read and display
while True:
message = (await reader.readline()).decode().strip()
print(message)
# Main execution
def main():
asyncio.run(ircle())
if __name__ == '__main__':
main()
To receive credit for the IRC Bot, you must:
You must have your IRC bot join the #bots
channel on chat.ndlug.org server.
Demonstrate your bot's functionality by logging into the server with your
own account and showing the instructor (pnutzh4x0r
).
You have until Wednesday, April 30.
For extra credit, you are to make your own personal website such as:
The idea behind this website is to publish a portfolio or profile that you can share with others (including potential employers). As such, you should avoid platforms such as SquareSpace, WiX, or Wordpress, and instead opt to building the website yourself.
To do so, you can use a platform such as GitHub Pages or use resources from the GitHub Student Developer Pack (which includes cloud and domain name credits).
To receive credit for the Personal Website, you must:
Post a link to your personal website in the class Slack channel.
Update your README.md
to include a link to your personal website.
You have until Wednesday, April 30.
To submit your assignment, please commit your work to the homework09
folder
of your homework09
branch in your assignments GitHub repository.
Your homework09
folder should only contain the following files:
Makefile
answers.json
curlit.c
socket.c
socket.h
socket.unit.c
timeit.c
Note: You do not need to commit the test scripts because the Makefile
automatically downloads them.
#-----------------------------------------------------------------------
# Make sure you have already completed Activity 0: Preparation
#-----------------------------------------------------------------------
...
$ git add Makefile # Mark changes for commit
$ git commit -m "Homework 09: Makefile" # Record changes
...
$ git add timeit.c # Mark changes for commit
$ git commit -m "Homework 09: timeit" # Record changes
...
$ git add socket.c # Mark changes for commit
$ git commit -m "Homework 09: socket" # Record changes
...
$ git add curlit.c # Mark changes for commit
$ git commit -m "Homework 09: curlit" # Record changes
...
$ git add answers.json # Mark changes for commit
$ git commit -m "Homework 09: quiz" # Record changes
...
$ git push -u origin homework09 # Push branch to GitHub
If you collaborated with any other students, or received help from TAs or AI
tools on this assignment, please record this support in the README.md
in
the homework09
folder and include it with your Pull Request.
Remember to create a Pull Request and assign the appropriate TA from the Reading 13 TA List.
DO NOT MERGE your own Pull Request. The TAs use open Pull Requests to keep track of which assignments to grade. Closing them yourself will cause a delay in grading and confuse the TAs.