diff --git a/chatserver/README.md b/chatserver/README.md index 47301bf..3a2caaa 100644 --- a/chatserver/README.md +++ b/chatserver/README.md @@ -1,8 +1,12 @@ # Running the chatserver -Use `python server.py` to run the server +Use `python3 server.py` to run the server ## chatserver communication -Use the client to communicate with the chatserver by running `python client.py` +Use the client to communicate with the chatserver by running `python3 client.py` ## chatserver information -Included is the server and client written in Python. \ No newline at end of file +Included is the server and client written in Python. + +## pcap information +A demonstration of the chatserver has been captured with Wireshark in the +included `demo.pcap` file. diff --git a/chatserver/client.py b/chatserver/client.py index 74ccf2b..9ad3c98 100644 --- a/chatserver/client.py +++ b/chatserver/client.py @@ -1,7 +1,6 @@ import socket import sys import select -import os BUFFER_SIZE = 1024 DEFAULT_HOST = 'localhost' @@ -23,50 +22,29 @@ def main(): client_socket.connect((SERVER_HOST, SERVER_PORT)) print(f"Connected to chat server at {SERVER_HOST}:{SERVER_PORT}") - if os.name == 'nt': # Windows-specific logic - import msvcrt - client_socket.setblocking(False) # Set the socket to non-blocking mode - - while True: - # Check for server messages - try: + while True: + # use select to wait for either server messages or user input + read_sockets, _, _ = select.select([client_socket, sys.stdin], [], []) + + for sock in read_sockets: + if sock == client_socket: + # receive message from server server_message = client_socket.recv(BUFFER_SIZE).decode('utf-8') - if server_message: - print(f"\n{server_message}") - print("> ", end="", flush=True) - except BlockingIOError: - pass - - # Check for user input - if msvcrt.kbhit(): - client_message = input("> ") + if not server_message: + print("Disconnected from server") + return + print(f"\n{server_message}") + print("> ", end="", flush=True) + else: + # prompt for user input + client_message = input() if client_message.strip() == "%!": print("Exiting chat...") - break + return + + # send message to server client_socket.send(client_message.encode('utf-8')) - - else: # Linux/Unix-specific logic - while True: - # use select to wait for either server messages or user input - read_sockets, _, _ = select.select([client_socket, sys.stdin], [], []) - - for sock in read_sockets: - if sock == client_socket: - # receive message from server - server_message = client_socket.recv(BUFFER_SIZE).decode('utf-8') - if not server_message: - print("Disconnected from server") - return - print(f"\n{server_message}") - print("> ", end="", flush=True) - else: - # prompt for user input - client_message = input() - if client_message.strip() == "%!": - print("Exiting chat...") - return - client_socket.send(client_message.encode('utf-8')) - + except Exception as e: print(f"Error: {e}") finally: diff --git a/chatserver/demo.pcap b/chatserver/demo.pcap new file mode 100644 index 0000000..ba4013e Binary files /dev/null and b/chatserver/demo.pcap differ diff --git a/chatserver/server.py b/chatserver/server.py index 40978b3..ee1d18b 100644 --- a/chatserver/server.py +++ b/chatserver/server.py @@ -1,419 +1,76 @@ import socket import select -import uuid -import pickle -import os -import threading -from flask import Flask, request, jsonify -from flask_socketio import SocketIO, emit, join_room, leave_room -from flask_cors import CORS -from datetime import datetime # Import for timestamps -import markdown # Import the markdown library - -# Flask app for REST API and WebSocket -app = Flask(__name__) -socketio = SocketIO(app, cors_allowed_origins="*") # Allow all origins - -# Enable CORS for the Flask app -CORS(app) CHATPORT = 8181 BUFFER_SIZE = 1024 MAX_CONNECT_QUEUE = 10 -DATA_FILE = 'server_data.pkl' -UUID_FILE = 'uuids.pkl' -# Global variables for chat server -socket_list = [] -client_info = {} -chat_rooms = {} -client_states = {} -uuid_dict = {} -message_history = {} # New: Store message history for each chat room -server_socket = None -quitFlag = True - -# Utility functions -def save_data(client_info, chat_rooms, client_states): - serializable_client_info = {info["user_id"]: info for sock, info in client_info.items()} - serializable_chat_rooms = {info["user_id"]: chat_rooms[sock] for sock, info in client_info.items()} - serializable_client_states = {info["user_id"]: client_states[sock] for sock, info in client_info.items()} - with open(DATA_FILE, 'wb') as f: - pickle.dump((serializable_client_info, serializable_chat_rooms, serializable_client_states), f) - -def load_data(): - if os.path.exists(DATA_FILE): - with open(DATA_FILE, 'rb') as f: - serializable_client_info, serializable_chat_rooms, serializable_client_states = pickle.load(f) - client_info = {uuid.UUID(user_id): info for user_id, info in serializable_client_info.items()} - chat_rooms = {uuid.UUID(user_id): room for user_id, room in serializable_chat_rooms.items()} - client_states = {uuid.UUID(user_id): state for user_id, state in serializable_client_states.items()} - return client_info, chat_rooms, client_states - return {}, {}, {} - -def save_uuids(uuid_dict): - with open(UUID_FILE, 'wb') as f: - pickle.dump(uuid_dict, f) - -def load_uuids(): - if os.path.exists(UUID_FILE): - with open(UUID_FILE, 'rb') as f: - return pickle.load(f) - return {} - -def get_client_uuid(client_socket, uuid_dict): - client_address = client_socket.getpeername() - client_key = f"{client_address[0]}_{client_address[1]}" - - if client_key in uuid_dict: - user_id = uuid_dict[client_key] - else: - user_id = str(uuid.uuid4()) - uuid_dict[client_key] = user_id - save_uuids(uuid_dict) - - return user_id - -def handle_message(username, chat_room, message): - """Store the message in history and broadcast it to all clients.""" - # Add a timestamp to the message - timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') - - # Convert the message to HTML using Markdown - html_message = markdown.markdown(message) - - # Store the message in the chat room's history - if chat_room not in message_history: - message_history[chat_room] = [] - message_history[chat_room].append(f"[{timestamp}] {username}: {message}") - - # Broadcast the message to WebSocket clients in the chat room - socketio.emit('message', { - 'username': username, - 'message': html_message, # Send the HTML-rendered message - 'timestamp': timestamp, - 'chat_room': chat_room - }, room=chat_room) - - # Broadcast the message to socket-based clients in the chat room - send_message = f"[{timestamp}] {username} >> {message}".encode('utf-8') - for client_sock in socket_list: - if client_sock != server_socket and chat_rooms.get(client_sock) == chat_room: - client_sock.send(send_message) - -def handle_command(username, chat_room, command, sock=None): - """Process commands sent by clients.""" - if command.strip() == "%?": - help_message = ( - "Help: (%?)\n" - "Send messages like normal. Use enter to send a message.\n" - "Available Commands:\n" - "\t%? : Show this help prompt.\n" - "\t%! : Exit from the Chat Server.\n" - "\t%* : Switch to another chatroom.\n" - "\t%$ : Change Username.\n" - "\t%@ : Send a direct message to a user.\n" - ) - if sock: - sock.send(help_message.encode('utf-8')) - else: - socketio.emit('message', {'username': 'Server', 'message': help_message}, room=chat_room) - return - - if command.strip() == "%*": - if sock: - sock.send(b"\nEnter chatroom name: \n") - client_states[sock] = "WAITING_FOR_NEW_CHATROOM" - else: - socketio.emit('message', {'username': 'Server', 'message': "Enter chatroom name:"}, room=chat_room) - return - - if command.strip() == "%$": - if sock: - sock.send(b"\nEnter new username: \n") - client_states[sock] = "WAITING_FOR_NEW_USERNAME" - else: - socketio.emit('message', {'username': 'Server', 'message': "Enter new username:"}, room=chat_room) - return - - if command.strip().startswith("%@ "): - parts = command.strip().split(' ', 2) - if len(parts) < 3: - error_message = "Invalid direct message format. Use: %@ " - if sock: - sock.send(f"\n{error_message}\n".encode('utf-8')) - else: - socketio.emit('message', {'username': 'Server', 'message': error_message}, room=chat_room) - else: - target_username = parts[1] - dm_message = parts[2] - timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') - - # Check if the target is a socket-based client - target_sock = None - for s, info in client_info.items(): - if not info.get("is_websocket") and info.get("username") == target_username: - target_sock = s - break - - # Check if the target is a WebSocket client - target_websocket_sid = None - for sid, info in client_info.items(): - if info.get("is_websocket") and info.get("username") == target_username: - target_websocket_sid = sid - break - - if target_sock: - # Send the direct message to the socket-based client - sender_username = username - target_sock.send(f"[{timestamp}] DM from {sender_username}: {dm_message}\n".encode('utf-8')) - if sock: - sock.send(f"[{timestamp}] Direct message sent.\n".encode('utf-8')) - elif target_websocket_sid: - # Send the direct message to the WebSocket client - socketio.emit('message', { - 'username': username, - 'message': dm_message, - 'timestamp': timestamp, - 'chat_room': 'Direct Message' - }, to=target_websocket_sid) - if sock: - sock.send(f"[{timestamp}] Direct message sent.\n".encode('utf-8')) - else: - # Target user not found - error_message = "User not found." - if sock: - sock.send(f"\n{error_message}\n".encode('utf-8')) - else: - socketio.emit('message', {'username': 'Server', 'message': error_message}, room=chat_room) - return - - if command.strip() == "%stop": - if client_info.get(sock, {}).get("is_admin"): - print("\nAdmin has stopped the server.") - global quitFlag - quitFlag = False - if sock: - sock.send(b"\nServer is stopping...\n") - else: - socketio.emit('message', {'username': 'Server', 'message': "Server is stopping..."}, room=chat_room) - else: - error_message = "You do not have permission to stop the server." - if sock: - sock.send(f"\n{error_message}\n".encode('utf-8')) - else: - socketio.emit('message', {'username': 'Server', 'message': error_message}, room=chat_room) - return - -# REST API Endpoints -@app.route('/send_message', methods=['POST']) -def send_message(): - """Send a message to a specific chat room.""" - data = request.json - username = data.get('username') - chat_room = data.get('chat_room', 'default') - message = data.get('message') - - if not username or not message: - return jsonify({"error": "Username and message are required"}), 400 - - if message.strip().startswith("%"): - handle_command(username, chat_room, message) - else: - handle_message(username, chat_room, message) - - return jsonify({"status": "Message sent and processed"}), 200 - -@app.route('/create_chatroom', methods=['POST']) -def create_chatroom(): - """Create a new chat room.""" - data = request.json - chat_room = data.get('chat_room') - - if not chat_room: - return jsonify({"error": "Chat room name is required"}), 400 - - # Add logic to create a chat room (if needed) - return jsonify({"status": f"Chat room '{chat_room}' created"}), 200 - -@app.route('/get_messages', methods=['GET']) -def get_messages(): - """Retrieve messages for a specific user (not implemented here).""" - # You can implement a message queue or buffer for each user to store messages - return jsonify({"error": "Not implemented"}), 501 - -@app.route('/get_history', methods=['GET']) -def get_history(): - """Retrieve message history for a specific chat room.""" - chat_room = request.args.get('chat_room', 'default') - if chat_room not in message_history: - return jsonify({"messages": []}) # Return an empty list if no history exists - - # Return the full message history with timestamps - return jsonify({"messages": message_history[chat_room]}) - -@app.route('/send_direct_message', methods=['POST']) -def send_direct_message(): - """Send a direct message to a specific user.""" - data = request.json - sender_username = data.get('sender_username') - target_username = data.get('target_username') - message = data.get('message') - - if not sender_username or not target_username or not message: - return jsonify({"error": "Sender username, target username, and message are required"}), 400 - - timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') - - # Check if the target is a socket-based client - target_sock = None - for s, info in client_info.items(): - if not info.get("is_websocket") and info.get("username") == target_username: - target_sock = s - break - - # Check if the target is a WebSocket client - target_websocket_sid = None - for sid, info in client_info.items(): - if info.get("is_websocket") and info.get("username") == target_username: - target_websocket_sid = sid - break - - if target_sock: - # Send the direct message to the socket-based client - target_sock.send(f"[{timestamp}] DM from {sender_username}: {message}\n".encode('utf-8')) - return jsonify({"status": "Direct message sent to socket-based client"}), 200 - elif target_websocket_sid: - # Send the direct message to the WebSocket client - socketio.emit('message', { - 'username': sender_username, - 'message': message, - 'timestamp': timestamp, - 'chat_room': 'Direct Message' - }, to=target_websocket_sid) - return jsonify({"status": "Direct message sent to WebSocket client"}), 200 - else: - # Target user not found - return jsonify({"error": "Target user not found"}), 404 - -# WebSocket event handlers -@socketio.on('connect') -def handle_connect(): - print("WebSocket client connected") - # Assign a unique ID to the WebSocket client - user_id = str(uuid.uuid4()) - client_info[request.sid] = {"user_id": user_id, "username": None, "is_websocket": True} - print(f"WebSocket client registered with ID: {user_id}") - -@socketio.on('disconnect') -def handle_disconnect(): - print("WebSocket client disconnected") - -@socketio.on('join') -def handle_join(data): - print(f"Received join event: {data}") - username = data.get('username') - chat_room = data.get('chat_room', 'default') - - if not username: - emit('error', {'message': 'Username is required'}) - return - - # Update client info for WebSocket clients - client_info[request.sid]["username"] = username - client_info[request.sid]["chat_room"] = chat_room - - join_room(chat_room) - emit('message', {'username': 'Server', 'message': f"{username} has joined the chat room '{chat_room}'."}, room=chat_room) - -@socketio.on('leave') -def handle_leave(data): - username = data.get('username') - chat_room = data.get('chat_room', 'default') - leave_room(chat_room) - emit('message', {'message': f"{username} has left the chat room '{chat_room}'."}, room=chat_room) - -@socketio.on('send_message') -def handle_send_message(data): - print(f"Received send_message event: {data}") - username = data.get('username') - chat_room = data.get('chat_room', 'default') - message = data.get('message') - - if not username or not message: - emit('error', {'message': 'Username and message are required'}) - return - - if message.strip().startswith("%"): - handle_command(username, chat_room, message) - else: - handle_message(username, chat_room, message) - -# Chat server logic -def chat_server(): - global quitFlag, server_socket, socket_list, client_info, chat_rooms, client_states, uuid_dict - - # Create and configure the server socket +def main(): + # set the flag variable to true for infinite loop + quitFlag = True + # create a socket object server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + # set the socket options, SO_REUSEADDR allows to reuse the same port + # SO_KEEPALIVE enables the socket to send keepalive messages server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) + # bind the socket to the address and port server_socket.bind(('', CHATPORT)) + # listen for incoming connections server_socket.listen(MAX_CONNECT_QUEUE) + # set the server socket to non-blocking server_socket.setblocking(False) - socket_list.append(server_socket) - print(f"Chat server listening on port {CHATPORT}") + # create a list to store the sockets, server and client + socket_list = [server_socket] + # since a socket is associated with a username and a chatroom, + # and each one can only be associated one way at once, use dictionaries to + # associate with each other. + usernames = {} + chat_rooms = {} + # keep track of the states of each client connected, and if they are + # technically in an 'unsafe' state like in change username or chatroom mode. + client_states = {} + # let the user know the server is listening on port CHATPORT. + print(f"Chatserver listening on port: {CHATPORT}") + # infinite loop for the program while quitFlag: + # use the select function to monitor the sockets read_sockets, _, _ = select.select(socket_list, [], []) + # loop through the sockets for sock in read_sockets: + # if the socket is the server socket, accept the connection if sock == server_socket: - # Accept new client connections client_socket, client_address = server_socket.accept() client_socket.setblocking(False) socket_list.append(client_socket) - print(f"Accepted connection from {client_address}") - client_socket.send(b"Welcome to the chat server!\n") - client_socket.send(b"Type in a username: \n") + print("\nAccepted connection from new client.") + client_socket.send(b"\nWelcome to the chat server!\n") + client_socket.send(b"\nUse '%!' to exit, or '%?' for help.\n") + client_socket.send(b"\nType in a username: ") client_states[client_socket] = "WAITING_FOR_USERNAME" - user_id = get_client_uuid(client_socket, uuid_dict) - client_info[client_socket] = {"user_id": user_id} + # otherwise the socket is a client socket. else: - # Handle client messages + # try to receive the message from the client try: message = sock.recv(BUFFER_SIZE).decode('utf-8') if message: state = client_states.get(sock, "NORMAL") if state == "WAITING_FOR_USERNAME": - username = message.strip() - if username == "admin": - sock.send(b"\nEnter admin password: \n") - client_states[sock] = "WAITING_FOR_ADMIN_PASSWORD" - client_info[sock]["username"] = username - else: - client_info[sock]["username"] = username - chat_rooms[sock] = "default" - sock.send(b"\nUsername accepted. You are now in the 'default' chatroom.\n") - client_states[sock] = "NORMAL" - elif state == "WAITING_FOR_ADMIN_PASSWORD": - password = message.strip() - if password == "adminpass": - sock.send(b"\nAdmin authenticated successfully.\n") - client_info[sock]["is_admin"] = True - chat_rooms[sock] = "default" - client_states[sock] = "NORMAL" - else: - sock.send(b"\nInvalid admin password. Please enter a username: \n") - client_states[sock] = "WAITING_FOR_USERNAME" + usernames[sock] = message.strip() + chat_rooms[sock] = "default" + sock.send(b"\nUsername accepted. You are now in the 'default' chatroom.") + client_states[sock] = "NORMAL" elif state == "WAITING_FOR_NEW_USERNAME": - client_info[sock]["username"] = message.strip() - sock.send(b"\nUsername changed successfully.\n") - print(f"\nClient switched username to {client_info[sock]['username']}.") + usernames[sock] = message.strip() + sock.send(b"\nUsername changed successfully.") + print(f"\nClient switched username to {usernames[sock]}.") client_states[sock] = "NORMAL" elif state == "WAITING_FOR_NEW_CHATROOM": old_chatroom = chat_rooms[sock] chat_rooms[sock] = message.strip() - sock.send(f"\nSwitched to chatroom '{chat_rooms[sock]}.'\n".encode('utf-8')) + sock.send(f"\nSwitched to chatroom '{chat_rooms[sock]}.'".encode('utf-8')) print(f"\nClient switched to chatroom '{chat_rooms[sock]}.'") if old_chatroom != "default" and old_chatroom not in chat_rooms.values(): print(f"\nChatroom '{old_chatroom}' is empty and removed.") @@ -422,22 +79,37 @@ def chat_server(): if message.strip() == "%!": sock.close() socket_list.remove(sock) - del client_info[sock] + del usernames[sock] del chat_rooms[sock] del client_states[sock] print("\nClient has closed connection.") + elif message.strip() == "%?": + print("\nClient requested help.") + sock.send(b"Help: (%?)\n") + sock.send(b"Send messages like normal. Use enter to send a message.\n") + sock.send(b"Available Commands:\n") + sock.send(b"\t%? : Show this help prompt.\n") + sock.send(b"\t%! : Exit from the Chat Server.\n") + sock.send(b"\t%* : Switch to another chatroom.\n") + sock.send(b"\t%$ : Change Username.\n") + elif message.strip() == "%*": + sock.send(b"\nEnter chatroom name: ") + client_states[sock] = "WAITING_FOR_NEW_CHATROOM" + elif message.strip() == "%$": + sock.send(b"\nEnter new username: ") + client_states[sock] = "WAITING_FOR_NEW_USERNAME" else: - if message.strip().startswith("%"): - handle_command(client_info[sock]["username"], chat_rooms[sock], message, sock) - else: - username = client_info[sock]["username"] - chat_room = chat_rooms[sock] - handle_message(username, chat_room, message) + username = usernames[sock] + chat_room = chat_rooms[sock] + send_message = f"{username} >> {message}".encode('utf-8') + for client_sock in socket_list: + if client_sock != server_socket and chat_rooms.get(client_sock) == chat_room: + client_sock.send(send_message) else: sock.close() socket_list.remove(sock) - if sock in client_info: - del client_info[sock] + if sock in usernames: + del usernames[sock] if sock in chat_rooms: old_chatroom = chat_rooms[sock] del chat_rooms[sock] @@ -450,8 +122,8 @@ def chat_server(): print(f"Error: {err}") sock.close() socket_list.remove(sock) - if sock in client_info: - del client_info[sock] + if sock in usernames: + del usernames[sock] if sock in chat_rooms: old_chatroom = chat_rooms[sock] del chat_rooms[sock] @@ -461,14 +133,7 @@ def chat_server(): del client_states[sock] print("\nClient has closed connection.") - # Save data before shutting down - save_data(client_info, chat_rooms, client_states) server_socket.close() if __name__ == "__main__": - # Start the chat server in a separate thread - server_thread = threading.Thread(target=chat_server, daemon=True) - server_thread.start() - - # Start the Flask REST API and WebSocket server - socketio.run(app, host='0.0.0.0', port=5000) \ No newline at end of file + main() \ No newline at end of file diff --git a/chatserver/templates/index.html b/chatserver/templates/index.html deleted file mode 100644 index 4018ba3..0000000 --- a/chatserver/templates/index.html +++ /dev/null @@ -1,376 +0,0 @@ - - - - - - Web Chat Client - - - - - - - -
- Username: | Chat Room: | Server: -
- - - - - -
-
- - -
- - -
- - -
- - - - \ No newline at end of file diff --git a/chatserver/webclient.py b/chatserver/webclient.py deleted file mode 100644 index 2a1856c..0000000 --- a/chatserver/webclient.py +++ /dev/null @@ -1,10 +0,0 @@ -from flask import Flask, render_template - -app = Flask(__name__) - -@app.route('/') -def index(): - return render_template('index.html') # Serve the HTML client - -if __name__ == "__main__": - app.run(host='0.0.0.0', port=8080) # Run the web client on port 8080 \ No newline at end of file diff --git a/echoserver/Makefile b/echoserver/Makefile new file mode 100644 index 0000000..221b29f --- /dev/null +++ b/echoserver/Makefile @@ -0,0 +1,20 @@ +CC = gcc -Wall -std=c11 -pedantic +SOURCES = echoserver.c +OBJECTS = echoserver.o + +all: echoserver + +echoserver: $(OBJECTS) + $(CC) $(OBJECTS) -o echoserver + +echoserver.o: echoserver.c + $(CC) -c echoserver.c -o echoserver.o + +clean: + rm -f $(OBJECTS) echoserver echoserver.out + +clean_objects: + rm -f $(OBJECTS) + +run: + ./echoserver diff --git a/echoserver/README.md b/echoserver/README.md new file mode 100644 index 0000000..fb81333 --- /dev/null +++ b/echoserver/README.md @@ -0,0 +1,10 @@ +# Running the echoserver +Use `make` to compile the echoserver. +Use `make run` to run the echoserver. This will run on an infinite loop. +Use `Ctrl+C` to stop the echoserver. + +## echoserver communication +Communicating with the server can happen using telnet in TCP mode. + +## echoserver information +The echoserver runs on TCP port 8888. \ No newline at end of file diff --git a/echoserver/echoserver.c b/echoserver/echoserver.c new file mode 100644 index 0000000..73e2bbc --- /dev/null +++ b/echoserver/echoserver.c @@ -0,0 +1,150 @@ +// header files +#include "echoserver.h" + +// constants +// echoserver port is 8888 +#define ECHOPORT 8888 +#define BUFFER_SIZE 1024 +#define MAX_CONNECT_QUEUE 10 + +// prototypes + +// main function +int main(int argc, char **argv) +{ + // initialize program (variables and structs) + bool quitFlag = true; + int socketEchoFD; + int clientEchoFD; + int reuseAddrOpt = 1; + int keepAliveOpt = 1; + char message[BUFFER_SIZE]; + int messageBytes; + struct sockaddr_in serverAddr; + struct sockaddr_storage clientAddr; + socklen_t clientSize; + memset(&serverAddr, 0, sizeof(serverAddr)); + memset(&clientAddr, 0, sizeof(clientAddr)); + // define a TCP socket + socketEchoFD = socket(AF_INET, SOCK_STREAM, 0); + if(socketEchoFD < 0) + { + printf("Unable to create the socket.\n"); + return -1; + } + // use the setsockopt function to make the socket accept different configurations + // allow for the reuse of the address/bind. + setsockopt(socketEchoFD, SOL_SOCKET, SO_REUSEADDR, &reuseAddrOpt, sizeof(reuseAddrOpt)); + // also need to set the socket to keepalive using setsockopt + setsockopt(socketEchoFD, SOL_SOCKET, SO_KEEPALIVE, &keepAliveOpt, sizeof(keepAliveOpt)); + // bind the TCP socket to port 8888 / ECHOPORT + serverAddr.sin_family = AF_INET; + serverAddr.sin_addr.s_addr = INADDR_ANY; + serverAddr.sin_port = htons(ECHOPORT); + int bindCheck = bind(socketEchoFD, &serverAddr, sizeof(serverAddr)); + if(bindCheck < 0) + { + printf("Unable to bind the socket to the port or address.\n Is either in use?"); + return -1; + } + // use fnctl to set the non-blocking flag for the socket + fcntl(socketEchoFD, F_SETFL, O_NONBLOCK); + // the server will now accept client connections by listening. + listen(socketEchoFD, MAX_CONNECT_QUEUE); + // create a file descriptor set, to keep track of the sockets in use using fd_set and FD_ZERO? + fd_set socketList; + FD_ZERO(&socketList); + // add the socketEchoFD to the file descriptor set using fd_set + FD_SET(socketEchoFD, &socketList); + // max file descriptor, this will be used to 'find' the socket currently connected + int maxFD = socketEchoFD; + // while loop that will continue to listen for messages from clients. + printf("Echoserver listening on port: %d\n", ECHOPORT); + while (quitFlag) + { + // set of active connections + fd_set activeSet = socketList; + // use the select() function here to check the set of sockets + int selectedFD = select(maxFD+1, &activeSet, NULL, NULL, NULL); + // check to make sure select() didn't error out. + if(selectedFD == -1) + { + // + printf("\nUnable to select socket to listen to.\n"); + return -1; + } + // for loop that loops up to the max file descriptor. + for(int fileIter = 0; fileIter <= maxFD; fileIter++) + { + // check to see if the file descriptor is in the set of sockets using FD_ISSET() + if(FD_ISSET(fileIter, &activeSet)) + { + // if the server socket is being written to/read from, + if(fileIter == socketEchoFD) + { + // create a client socket using accept + clientSize = sizeof(clientAddr); + clientEchoFD = accept(socketEchoFD, &clientAddr, &clientSize); + // check to see if the client socket is connected (-1) + if(clientEchoFD == -1) + { + printf("\nCould not connect the client to a socket.\n"); + return -1; + } + else + { + // set the non blocking flag using fnctl + fcntl(clientEchoFD, F_SETFL, O_NONBLOCK); + // add the client socket to the set of file descriptor sockets + FD_SET(clientEchoFD, &socketList); + // check to see if the new socket FD is greater than the current max (clientSocket > maxFD) + if(clientEchoFD > maxFD) + { + // if it is, set the new max. + maxFD = clientEchoFD; + } + printf("\nConnected client to socket.\n"); + } + printf("\nAccepted connection from new client.\n"); + } + // if the client socket(s) are being written to/read from, + else + { + // grab the data from the client, save to message. + messageBytes = recv(fileIter, message, BUFFER_SIZE, 0); + // make sure that the connection is still open before attempting to send. + if(messageBytes <= 0) // old: messageBytes <= 0 + { + // if it is not open, close the socket and remove the socket from the socket list using FD_CLR() + close(fileIter); + FD_CLR(fileIter, &socketList); + printf("\nClient has closed connection.\n"); + } + else + { + // if it is open, send the message back to the client over the client socket + send(fileIter, message, messageBytes, 0); + printf("\nMessage has been sent back to client.\n"); + } + } + } + } + } + // close the socket + close(socketEchoFD); + // profit??? + return 0; +} + +// function definitions + +/* +Name: ??? +Process: ??? +Function Input/Parameters: ??? +Function Output/Parameters: ??? +Function Output/Returned: ??? +Device Input/Device: ??? +Device Output/Device: ??? +Dependencies: ??? +*/ \ No newline at end of file diff --git a/echoserver/echoserver.h b/echoserver/echoserver.h new file mode 100644 index 0000000..b1ef31c --- /dev/null +++ b/echoserver/echoserver.h @@ -0,0 +1,25 @@ +#ifndef ECHOSERVER +#define ECHOSERVER + +// included libraries +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// data structures + +// function prototypes + +#endif // ECHOSERVER \ No newline at end of file diff --git a/networksim/CS460Homework4_CBvCB_graph.png b/networksim/CS460Homework4_CBvCB_graph.png new file mode 100644 index 0000000..0005c3a Binary files /dev/null and b/networksim/CS460Homework4_CBvCB_graph.png differ diff --git a/networksim/CS460Homework4_Cubic_graph.png b/networksim/CS460Homework4_Cubic_graph.png new file mode 100644 index 0000000..b98c5b3 Binary files /dev/null and b/networksim/CS460Homework4_Cubic_graph.png differ diff --git a/networksim/CS460Homework4_NRvCB_graph.png b/networksim/CS460Homework4_NRvCB_graph.png new file mode 100644 index 0000000..b325280 Binary files /dev/null and b/networksim/CS460Homework4_NRvCB_graph.png differ diff --git a/networksim/CS460Homework4_NRvNR_graph.png b/networksim/CS460Homework4_NRvNR_graph.png new file mode 100644 index 0000000..ba38290 Binary files /dev/null and b/networksim/CS460Homework4_NRvNR_graph.png differ diff --git a/networksim/CS460Homework4_NewReno_graph.png b/networksim/CS460Homework4_NewReno_graph.png new file mode 100644 index 0000000..9c251a6 Binary files /dev/null and b/networksim/CS460Homework4_NewReno_graph.png differ diff --git a/networksim/CS460Homework4_graph.png b/networksim/CS460Homework4_graph.png new file mode 100644 index 0000000..81b2168 Binary files /dev/null and b/networksim/CS460Homework4_graph.png differ diff --git a/networksim/CS460Homework4_graph_5p.png b/networksim/CS460Homework4_graph_5p.png new file mode 100644 index 0000000..8a24d71 Binary files /dev/null and b/networksim/CS460Homework4_graph_5p.png differ diff --git a/networksim/README.md b/networksim/README.md new file mode 100644 index 0000000..c81d21b --- /dev/null +++ b/networksim/README.md @@ -0,0 +1,7 @@ +# Adapted tcp-variants-comparison file +The `tcp-variants-comparison.cc` file has been adapted to allow for multiple TCP variants to be run in one simulation. +In order to change the variants ran, find line 510-524. This is where the magic of which variants to run is. +The code relies on multiple flows to also be ran, which can be found using the number of cases - 1. If you want to test the file, you'll want to replace the file with this modified version, which must then be rebuilt. You should be able to use `./ns3 build tcp-variants-comparison` (or `./ns3 build examples/tcp-variants-comparison` might work better) although this hasn't been tested, and the best method is to use ccache to cache the build before making the modification. + +## How to find the cwnd of multiple TCP variants (how to run) +The command `./ns3 run tcp-variants-comparison --command-template="%s --tracing=1 --prefix_name=CS460Homework4_TCPCmp --num_flows=3 --access_delay=50ms"` can be used to find the comparisons between TCP variants. You'll want the number of flows to equal the amount of cases in the switch statement. \ No newline at end of file diff --git a/networksim/tcp-variants-comparison.cc b/networksim/tcp-variants-comparison.cc new file mode 100644 index 0000000..09d3142 --- /dev/null +++ b/networksim/tcp-variants-comparison.cc @@ -0,0 +1,624 @@ +/* + * Copyright (c) 2013 ResiliNets, ITTC, University of Kansas + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Authors: Justin P. Rohrer, Truc Anh N. Nguyen , Siddharth Gangadhar + * + * + * James P.G. Sterbenz , director + * ResiliNets Research Group https://resilinets.org/ + * Information and Telecommunication Technology Center (ITTC) + * and Department of Electrical Engineering and Computer Science + * The University of Kansas Lawrence, KS USA. + * + * Work supported in part by NSF FIND (Future Internet Design) Program + * under grant CNS-0626918 (Postmodern Internet Architecture), + * NSF grant CNS-1050226 (Multilayer Network Resilience Analysis and Experimentation on GENI), + * US Department of Defense (DoD), and ITTC at The University of Kansas. + * + * "TCP Westwood(+) Protocol Implementation in ns-3" + * Siddharth Gangadhar, Trúc Anh Ngọc Nguyễn , Greeshma Umapathi, and James P.G. Sterbenz, + * ICST SIMUTools Workshop on ns-3 (WNS3), Cannes, France, March 2013 + */ + +#include "ns3/applications-module.h" +#include "ns3/core-module.h" +#include "ns3/enum.h" +#include "ns3/error-model.h" +#include "ns3/event-id.h" +#include "ns3/flow-monitor-helper.h" +#include "ns3/internet-module.h" +#include "ns3/ipv4-global-routing-helper.h" +#include "ns3/network-module.h" +#include "ns3/point-to-point-module.h" +#include "ns3/tcp-header.h" +#include "ns3/traffic-control-module.h" +#include "ns3/udp-header.h" + +#include +#include +#include + +using namespace ns3; + +NS_LOG_COMPONENT_DEFINE("TcpVariantsComparison"); + +static std::map firstCwnd; //!< First congestion window. +static std::map firstSshThr; //!< First SlowStart threshold. +static std::map firstRtt; //!< First RTT. +static std::map firstRto; //!< First RTO. +static std::map> cWndStream; //!< Congstion window output stream. +static std::map> + ssThreshStream; //!< SlowStart threshold output stream. +static std::map> rttStream; //!< RTT output stream. +static std::map> rtoStream; //!< RTO output stream. +static std::map> nextTxStream; //!< Next TX output stream. +static std::map> nextRxStream; //!< Next RX output stream. +static std::map> inFlightStream; //!< In flight output stream. +static std::map cWndValue; //!< congestion window value. +static std::map ssThreshValue; //!< SlowStart threshold value. + +/** + * Get the Node Id From Context. + * + * @param context The context. + * @return the node ID. + */ +static uint32_t +GetNodeIdFromContext(std::string context) +{ + const std::size_t n1 = context.find_first_of('/', 1); + const std::size_t n2 = context.find_first_of('/', n1 + 1); + return std::stoul(context.substr(n1 + 1, n2 - n1 - 1)); +} + +/** + * Congestion window tracer. + * + * @param context The context. + * @param oldval Old value. + * @param newval New value. + */ +static void +CwndTracer(std::string context, uint32_t oldval, uint32_t newval) +{ + uint32_t nodeId = GetNodeIdFromContext(context); + + if (firstCwnd[nodeId]) + { + *cWndStream[nodeId]->GetStream() << "0.0 " << oldval << std::endl; + firstCwnd[nodeId] = false; + } + *cWndStream[nodeId]->GetStream() << Simulator::Now().GetSeconds() << " " << newval << std::endl; + cWndValue[nodeId] = newval; + + if (!firstSshThr[nodeId]) + { + *ssThreshStream[nodeId]->GetStream() + << Simulator::Now().GetSeconds() << " " << ssThreshValue[nodeId] << std::endl; + } +} + +/** + * Slow start threshold tracer. + * + * @param context The context. + * @param oldval Old value. + * @param newval New value. + */ +static void +SsThreshTracer(std::string context, uint32_t oldval, uint32_t newval) +{ + uint32_t nodeId = GetNodeIdFromContext(context); + + if (firstSshThr[nodeId]) + { + *ssThreshStream[nodeId]->GetStream() << "0.0 " << oldval << std::endl; + firstSshThr[nodeId] = false; + } + *ssThreshStream[nodeId]->GetStream() + << Simulator::Now().GetSeconds() << " " << newval << std::endl; + ssThreshValue[nodeId] = newval; + + if (!firstCwnd[nodeId]) + { + *cWndStream[nodeId]->GetStream() + << Simulator::Now().GetSeconds() << " " << cWndValue[nodeId] << std::endl; + } +} + +/** + * RTT tracer. + * + * @param context The context. + * @param oldval Old value. + * @param newval New value. + */ +static void +RttTracer(std::string context, Time oldval, Time newval) +{ + uint32_t nodeId = GetNodeIdFromContext(context); + + if (firstRtt[nodeId]) + { + *rttStream[nodeId]->GetStream() << "0.0 " << oldval.GetSeconds() << std::endl; + firstRtt[nodeId] = false; + } + *rttStream[nodeId]->GetStream() + << Simulator::Now().GetSeconds() << " " << newval.GetSeconds() << std::endl; +} + +/** + * RTO tracer. + * + * @param context The context. + * @param oldval Old value. + * @param newval New value. + */ +static void +RtoTracer(std::string context, Time oldval, Time newval) +{ + uint32_t nodeId = GetNodeIdFromContext(context); + + if (firstRto[nodeId]) + { + *rtoStream[nodeId]->GetStream() << "0.0 " << oldval.GetSeconds() << std::endl; + firstRto[nodeId] = false; + } + *rtoStream[nodeId]->GetStream() + << Simulator::Now().GetSeconds() << " " << newval.GetSeconds() << std::endl; +} + +/** + * Next TX tracer. + * + * @param context The context. + * @param old Old sequence number. + * @param nextTx Next sequence number. + */ +static void +NextTxTracer(std::string context, SequenceNumber32 old [[maybe_unused]], SequenceNumber32 nextTx) +{ + uint32_t nodeId = GetNodeIdFromContext(context); + + *nextTxStream[nodeId]->GetStream() + << Simulator::Now().GetSeconds() << " " << nextTx << std::endl; +} + +/** + * In-flight tracer. + * + * @param context The context. + * @param old Old value. + * @param inFlight In flight value. + */ +static void +InFlightTracer(std::string context, uint32_t old [[maybe_unused]], uint32_t inFlight) +{ + uint32_t nodeId = GetNodeIdFromContext(context); + + *inFlightStream[nodeId]->GetStream() + << Simulator::Now().GetSeconds() << " " << inFlight << std::endl; +} + +/** + * Next RX tracer. + * + * @param context The context. + * @param old Old sequence number. + * @param nextRx Next sequence number. + */ +static void +NextRxTracer(std::string context, SequenceNumber32 old [[maybe_unused]], SequenceNumber32 nextRx) +{ + uint32_t nodeId = GetNodeIdFromContext(context); + + *nextRxStream[nodeId]->GetStream() + << Simulator::Now().GetSeconds() << " " << nextRx << std::endl; +} + +/** + * Congestion window trace connection. + * + * @param cwnd_tr_file_name Congestion window trace file name. + * @param nodeId Node ID. + */ +static void +TraceCwnd(std::string cwnd_tr_file_name, uint32_t nodeId) +{ + AsciiTraceHelper ascii; + cWndStream[nodeId] = ascii.CreateFileStream(cwnd_tr_file_name); + Config::Connect("/NodeList/" + std::to_string(nodeId) + + "/$ns3::TcpL4Protocol/SocketList/0/CongestionWindow", + MakeCallback(&CwndTracer)); +} + +/** + * Slow start threshold trace connection. + * + * @param ssthresh_tr_file_name Slow start threshold trace file name. + * @param nodeId Node ID. + */ +static void +TraceSsThresh(std::string ssthresh_tr_file_name, uint32_t nodeId) +{ + AsciiTraceHelper ascii; + ssThreshStream[nodeId] = ascii.CreateFileStream(ssthresh_tr_file_name); + Config::Connect("/NodeList/" + std::to_string(nodeId) + + "/$ns3::TcpL4Protocol/SocketList/0/SlowStartThreshold", + MakeCallback(&SsThreshTracer)); +} + +/** + * RTT trace connection. + * + * @param rtt_tr_file_name RTT trace file name. + * @param nodeId Node ID. + */ +static void +TraceRtt(std::string rtt_tr_file_name, uint32_t nodeId) +{ + AsciiTraceHelper ascii; + rttStream[nodeId] = ascii.CreateFileStream(rtt_tr_file_name); + Config::Connect("/NodeList/" + std::to_string(nodeId) + "/$ns3::TcpL4Protocol/SocketList/0/RTT", + MakeCallback(&RttTracer)); +} + +/** + * RTO trace connection. + * + * @param rto_tr_file_name RTO trace file name. + * @param nodeId Node ID. + */ +static void +TraceRto(std::string rto_tr_file_name, uint32_t nodeId) +{ + AsciiTraceHelper ascii; + rtoStream[nodeId] = ascii.CreateFileStream(rto_tr_file_name); + Config::Connect("/NodeList/" + std::to_string(nodeId) + "/$ns3::TcpL4Protocol/SocketList/0/RTO", + MakeCallback(&RtoTracer)); +} + +/** + * Next TX trace connection. + * + * @param next_tx_seq_file_name Next TX trace file name. + * @param nodeId Node ID. + */ +static void +TraceNextTx(std::string& next_tx_seq_file_name, uint32_t nodeId) +{ + AsciiTraceHelper ascii; + nextTxStream[nodeId] = ascii.CreateFileStream(next_tx_seq_file_name); + Config::Connect("/NodeList/" + std::to_string(nodeId) + + "/$ns3::TcpL4Protocol/SocketList/0/NextTxSequence", + MakeCallback(&NextTxTracer)); +} + +/** + * In flight trace connection. + * + * @param in_flight_file_name In flight trace file name. + * @param nodeId Node ID. + */ +static void +TraceInFlight(std::string& in_flight_file_name, uint32_t nodeId) +{ + AsciiTraceHelper ascii; + inFlightStream[nodeId] = ascii.CreateFileStream(in_flight_file_name); + Config::Connect("/NodeList/" + std::to_string(nodeId) + + "/$ns3::TcpL4Protocol/SocketList/0/BytesInFlight", + MakeCallback(&InFlightTracer)); +} + +/** + * Next RX trace connection. + * + * @param next_rx_seq_file_name Next RX trace file name. + * @param nodeId Node ID. + */ +static void +TraceNextRx(std::string& next_rx_seq_file_name, uint32_t nodeId) +{ + AsciiTraceHelper ascii; + nextRxStream[nodeId] = ascii.CreateFileStream(next_rx_seq_file_name); + Config::Connect("/NodeList/" + std::to_string(nodeId) + + "/$ns3::TcpL4Protocol/SocketList/1/RxBuffer/NextRxSequence", + MakeCallback(&NextRxTracer)); +} + +int +main(int argc, char* argv[]) +{ + std::string transport_prot = "TcpWestwoodPlus"; + double error_p = 0.0; + std::string bandwidth = "2Mbps"; + std::string delay = "0.01ms"; + std::string access_bandwidth = "10Mbps"; + std::string access_delay = "45ms"; + bool tracing = false; + std::string prefix_file_name = "TcpVariantsComparison"; + uint64_t data_mbytes = 0; + uint32_t mtu_bytes = 400; + uint16_t num_flows = 1; + double duration = 100.0; + uint32_t run = 0; + bool flow_monitor = false; + bool pcap = false; + bool sack = true; + std::string queue_disc_type = "ns3::PfifoFastQueueDisc"; + std::string recovery = "ns3::TcpClassicRecovery"; + + CommandLine cmd(__FILE__); + cmd.AddValue("transport_prot", + "Transport protocol to use: TcpNewReno, TcpLinuxReno, " + "TcpHybla, TcpHighSpeed, TcpHtcp, TcpVegas, TcpScalable, TcpVeno, " + "TcpBic, TcpYeah, TcpIllinois, TcpWestwoodPlus, TcpLedbat, " + "TcpLp, TcpDctcp, TcpCubic, TcpBbr", + transport_prot); + cmd.AddValue("error_p", "Packet error rate", error_p); + cmd.AddValue("bandwidth", "Bottleneck bandwidth", bandwidth); + cmd.AddValue("delay", "Bottleneck delay", delay); + cmd.AddValue("access_bandwidth", "Access link bandwidth", access_bandwidth); + cmd.AddValue("access_delay", "Access link delay", access_delay); + cmd.AddValue("tracing", "Flag to enable/disable tracing", tracing); + cmd.AddValue("prefix_name", "Prefix of output trace file", prefix_file_name); + cmd.AddValue("data", "Number of Megabytes of data to transmit", data_mbytes); + cmd.AddValue("mtu", "Size of IP packets to send in bytes", mtu_bytes); + cmd.AddValue("num_flows", "Number of flows", num_flows); + cmd.AddValue("duration", "Time to allow flows to run in seconds", duration); + cmd.AddValue("run", "Run index (for setting repeatable seeds)", run); + cmd.AddValue("flow_monitor", "Enable flow monitor", flow_monitor); + cmd.AddValue("pcap_tracing", "Enable or disable PCAP tracing", pcap); + cmd.AddValue("queue_disc_type", + "Queue disc type for gateway (e.g. ns3::CoDelQueueDisc)", + queue_disc_type); + cmd.AddValue("sack", "Enable or disable SACK option", sack); + cmd.AddValue("recovery", "Recovery algorithm type to use (e.g., ns3::TcpPrrRecovery", recovery); + cmd.Parse(argc, argv); + + transport_prot = std::string("ns3::") + transport_prot; + + SeedManager::SetSeed(1); + SeedManager::SetRun(run); + + // User may find it convenient to enable logging + // LogComponentEnable("TcpVariantsComparison", LOG_LEVEL_ALL); + // LogComponentEnable("BulkSendApplication", LOG_LEVEL_INFO); + // LogComponentEnable("PfifoFastQueueDisc", LOG_LEVEL_ALL); + + // Calculate the ADU size + Header* temp_header = new Ipv4Header(); + uint32_t ip_header = temp_header->GetSerializedSize(); + NS_LOG_LOGIC("IP Header size is: " << ip_header); + delete temp_header; + temp_header = new TcpHeader(); + uint32_t tcp_header = temp_header->GetSerializedSize(); + NS_LOG_LOGIC("TCP Header size is: " << tcp_header); + delete temp_header; + uint32_t tcp_adu_size = mtu_bytes - 20 - (ip_header + tcp_header); + NS_LOG_LOGIC("TCP ADU size is: " << tcp_adu_size); + + // Set the simulation start and stop time + double start_time = 0.1; + double stop_time = start_time + duration; + + // 2 MB of TCP buffer + Config::SetDefault("ns3::TcpSocket::RcvBufSize", UintegerValue(1 << 21)); + Config::SetDefault("ns3::TcpSocket::SndBufSize", UintegerValue(1 << 21)); + Config::SetDefault("ns3::TcpSocketBase::Sack", BooleanValue(sack)); + + Config::SetDefault("ns3::TcpL4Protocol::RecoveryType", + TypeIdValue(TypeId::LookupByName(recovery))); + // Select TCP variant + TypeId tcpTid; + NS_ABORT_MSG_UNLESS(TypeId::LookupByNameFailSafe(transport_prot, &tcpTid), + "TypeId " << transport_prot << " not found"); + Config::SetDefault("ns3::TcpL4Protocol::SocketType", + TypeIdValue(TypeId::LookupByName(transport_prot))); + + // Create gateways, sources, and sinks + NodeContainer gateways; + gateways.Create(1); + NodeContainer sources; + sources.Create(num_flows); + NodeContainer sinks; + sinks.Create(num_flows); + + // Configure the error model + // Here we use RateErrorModel with packet error rate + Ptr uv = CreateObject(); + uv->SetStream(50); + RateErrorModel error_model; + error_model.SetRandomVariable(uv); + error_model.SetUnit(RateErrorModel::ERROR_UNIT_PACKET); + error_model.SetRate(error_p); + + PointToPointHelper UnReLink; + UnReLink.SetDeviceAttribute("DataRate", StringValue(bandwidth)); + UnReLink.SetChannelAttribute("Delay", StringValue(delay)); + UnReLink.SetDeviceAttribute("ReceiveErrorModel", PointerValue(&error_model)); + + InternetStackHelper stack; + stack.InstallAll(); + + TrafficControlHelper tchPfifo; + tchPfifo.SetRootQueueDisc("ns3::PfifoFastQueueDisc"); + + TrafficControlHelper tchCoDel; + tchCoDel.SetRootQueueDisc("ns3::CoDelQueueDisc"); + + Ipv4AddressHelper address; + address.SetBase("10.0.0.0", "255.255.255.0"); + + // Configure the sources and sinks net devices + // and the channels between the sources/sinks and the gateways + PointToPointHelper LocalLink; + LocalLink.SetDeviceAttribute("DataRate", StringValue(access_bandwidth)); + LocalLink.SetChannelAttribute("Delay", StringValue(access_delay)); + + Ipv4InterfaceContainer sink_interfaces; + + DataRate access_b(access_bandwidth); + DataRate bottle_b(bandwidth); + Time access_d(access_delay); + Time bottle_d(delay); + + uint32_t size = static_cast((std::min(access_b, bottle_b).GetBitRate() / 8) * + ((access_d + bottle_d) * 2).GetSeconds()); + + Config::SetDefault("ns3::PfifoFastQueueDisc::MaxSize", + QueueSizeValue(QueueSize(QueueSizeUnit::PACKETS, size / mtu_bytes))); + Config::SetDefault("ns3::CoDelQueueDisc::MaxSize", + QueueSizeValue(QueueSize(QueueSizeUnit::BYTES, size))); + + for (uint32_t i = 0; i < num_flows; i++) + { + NetDeviceContainer devices; + devices = LocalLink.Install(sources.Get(i), gateways.Get(0)); + tchPfifo.Install(devices); + address.NewNetwork(); + Ipv4InterfaceContainer interfaces = address.Assign(devices); + + devices = UnReLink.Install(gateways.Get(0), sinks.Get(i)); + if (queue_disc_type == "ns3::PfifoFastQueueDisc") + { + tchPfifo.Install(devices); + } + else if (queue_disc_type == "ns3::CoDelQueueDisc") + { + tchCoDel.Install(devices); + } + else + { + NS_FATAL_ERROR("Queue not recognized. Allowed values are ns3::CoDelQueueDisc or " + "ns3::PfifoFastQueueDisc"); + } + address.NewNetwork(); + interfaces = address.Assign(devices); + sink_interfaces.Add(interfaces.Get(1)); + } + + NS_LOG_INFO("Initialize Global Routing."); + Ipv4GlobalRoutingHelper::PopulateRoutingTables(); + + uint16_t port = 50000; + Address sinkLocalAddress(InetSocketAddress(Ipv4Address::GetAny(), port)); + PacketSinkHelper sinkHelper("ns3::TcpSocketFactory", sinkLocalAddress); + + for (uint32_t i = 0; i < sources.GetN(); i++) + { + // Assign a different TCP variant to each flow + std::string tcpVariant; + switch (i % 2) // Example: Rotate between two TCP variants + { + case 0: + tcpVariant = "ns3::TcpNewReno"; + break; + case 1: + tcpVariant = "ns3::TcpCubic"; + break; + default: + tcpVariant = "ns3::TcpNewReno"; + } + + // Set the TCP variant for this flow + Config::Set("/NodeList/" + std::to_string(sources.Get(i)->GetId()) + + "/$ns3::TcpL4Protocol/SocketType", + StringValue(tcpVariant)); + + // Configure the BulkSend application + Address remoteAddress(InetSocketAddress(sink_interfaces.GetAddress(i, 0), port)); + BulkSendHelper ftp("ns3::TcpSocketFactory", remoteAddress); + ftp.SetAttribute("SendSize", UintegerValue(tcp_adu_size)); + ftp.SetAttribute("MaxBytes", UintegerValue(data_mbytes * 1000000)); + + ApplicationContainer sourceApp = ftp.Install(sources.Get(i)); + sourceApp.Start(Seconds(start_time * i)); + sourceApp.Stop(Seconds(stop_time - 3)); + + // Configure the PacketSink application + sinkHelper.SetAttribute("Protocol", TypeIdValue(TcpSocketFactory::GetTypeId())); + ApplicationContainer sinkApp = sinkHelper.Install(sinks.Get(i)); + sinkApp.Start(Seconds(start_time * i)); + sinkApp.Stop(Seconds(stop_time)); + } + + // Set up tracing if enabled + if (tracing) + { + std::ofstream ascii; + Ptr ascii_wrap; + ascii.open(prefix_file_name + "-ascii"); + ascii_wrap = new OutputStreamWrapper(prefix_file_name + "-ascii", std::ios::out); + stack.EnableAsciiIpv4All(ascii_wrap); + + for (uint16_t index = 0; index < num_flows; index++) + { + std::string flowString; + if (num_flows > 1) + { + flowString = "-flow" + std::to_string(index); + } + + firstCwnd[index + 1] = true; + firstSshThr[index + 1] = true; + firstRtt[index + 1] = true; + firstRto[index + 1] = true; + + Simulator::Schedule(Seconds(start_time * index + 0.00001), + &TraceCwnd, + prefix_file_name + flowString + "-cwnd.data", + index + 1); + Simulator::Schedule(Seconds(start_time * index + 0.00001), + &TraceSsThresh, + prefix_file_name + flowString + "-ssth.data", + index + 1); + Simulator::Schedule(Seconds(start_time * index + 0.00001), + &TraceRtt, + prefix_file_name + flowString + "-rtt.data", + index + 1); + Simulator::Schedule(Seconds(start_time * index + 0.00001), + &TraceRto, + prefix_file_name + flowString + "-rto.data", + index + 1); + Simulator::Schedule(Seconds(start_time * index + 0.00001), + &TraceNextTx, + prefix_file_name + flowString + "-next-tx.data", + index + 1); + Simulator::Schedule(Seconds(start_time * index + 0.00001), + &TraceInFlight, + prefix_file_name + flowString + "-inflight.data", + index + 1); + Simulator::Schedule(Seconds(start_time * index + 0.1), + &TraceNextRx, + prefix_file_name + flowString + "-next-rx.data", + num_flows + index + 1); + } + } + + if (pcap) + { + UnReLink.EnablePcapAll(prefix_file_name, true); + LocalLink.EnablePcapAll(prefix_file_name, true); + } + + // Flow monitor + FlowMonitorHelper flowHelper; + if (flow_monitor) + { + flowHelper.InstallAll(); + } + + Simulator::Stop(Seconds(stop_time)); + Simulator::Run(); + + if (flow_monitor) + { + flowHelper.SerializeToXmlFile(prefix_file_name + ".flowmonitor", true, true); + } + + Simulator::Destroy(); + return 0; +} diff --git a/timeserver/Makefile b/timeserver/Makefile new file mode 100644 index 0000000..6f7d8b7 --- /dev/null +++ b/timeserver/Makefile @@ -0,0 +1,20 @@ +CC = gcc -Wall -std=c11 -pedantic +SOURCES = timeserver.c +OBJECTS = timeserver.o + +all: timeserver + +timeserver: $(OBJECTS) + $(CC) $(OBJECTS) -o timeserver + +timeserver.o: timeserver.c + $(CC) -c timeserver.c -o timeserver.o + +clean: + rm -f $(OBJECTS) timeserver timeserver.out + +clean_objects: + rm -f $(OBJECTS) + +run: + ./timeserver diff --git a/timeserver/README.md b/timeserver/README.md new file mode 100644 index 0000000..b9d8b6a --- /dev/null +++ b/timeserver/README.md @@ -0,0 +1,16 @@ +# Running the timeserver +Use `make` to compile the timeserver. +Use `make run` to run the timeserver. This will run on an infinite loop. +Use `Ctrl+C` to stop the timeserver. + +## timeserver communication +Communicating with the server can happen using netcat in UDP mode (-u flag). + +## timeserver information +The timeserver runs on UDP port 8888. +The timeserver will wait for `time\r\n` to be sent on this port, and if received, +will send back a response of the time (in ms) since January 1st, 1970 (UNIX Epoch). + +## pcap information +A demonstration of the timeserver has been captured with Wireshark in the +included `demo.pcap` file. \ No newline at end of file diff --git a/timeserver/demo.pcap b/timeserver/demo.pcap new file mode 100644 index 0000000..6c48bde Binary files /dev/null and b/timeserver/demo.pcap differ diff --git a/timeserver/timeserver.c b/timeserver/timeserver.c new file mode 100644 index 0000000..6eef908 --- /dev/null +++ b/timeserver/timeserver.c @@ -0,0 +1,83 @@ +// header files +#include "timeserver.h" + +// constants +// timeserver port is 8888 +#define TIMEPORT 8888 +#define BUFFER_SIZE 1024 + +// prototypes + +// main function +int main(int argc, char **argv) +{ + // initialize program (variables and structs) + bool quitFlag = true; + int socketTimeFD; + char message[BUFFER_SIZE]; + struct sockaddr_in serverAddr, clientAddr; + memset(&serverAddr, 0, sizeof(serverAddr)); + memset(&clientAddr, 0, sizeof(clientAddr)); + // define a UDP socket + socketTimeFD = socket(AF_INET, SOCK_DGRAM, 0); + if(socketTimeFD < 0) + { + printf("Unable to create the socket."); + return -1; + } + // bind the UDP socket to port 8888 / TIMEPORT + serverAddr.sin_family = AF_INET; + serverAddr.sin_addr.s_addr = INADDR_ANY; + serverAddr.sin_port = htons(TIMEPORT); + int bindCheck = bind(socketTimeFD, &serverAddr, sizeof(serverAddr)); + if(bindCheck < 0) + { + printf("Unable to bind the socket to the port or address.\n Is either in use?"); + return -1; + } + // while loop to see if the "time\r\n" has been received (recv call?) + printf("Timeserver listening on port: %d\n", TIMEPORT); + while(quitFlag) + { + int clientAddrLen; + int receivedDataLen; + // grab the data from the client + receivedDataLen = recvfrom(socketTimeFD, message, BUFFER_SIZE, MSG_WAITALL, &clientAddr, &clientAddrLen); + message[receivedDataLen] = '\0'; + // if "time\r\n" is received, then send() back the linux epoch. + if(strcmp(message, "time\n") == 0 || strcmp(message, "time\r\n") == 0) + { + // setup the reply message + char reply[BUFFER_SIZE]; + // add the current time to the reply + sprintf(reply, "%d", time(NULL)); + // send the reply to the requestee + sendto(socketTimeFD, (const char *)reply, strlen(reply), 0, (const struct sockaddr *) &clientAddr, clientAddrLen); + // log the time command recv'd + printf("\nServer received time. Replied with: %d\n", time(NULL)); + } + else if (strcmp(message, "close\n") == 0 || strcmp(message, "quit\n") == 0 || strcmp(message, "q\n") == 0) + { + // if the server recv'd the close command, then close the server. + printf("\nServer received close.\n"); + quitFlag = false; + } + } + // close the socket + close(socketTimeFD); + // profit??? + return 0; +} + +// function definitions + +/* +Name: ??? +Process: ??? +Function Input/Parameters: ??? +Function Output/Parameters: ??? +Function Output/Returned: ??? +Device Input/Device: ??? +Device Output/Device: ??? +Dependencies: ??? +*/ \ No newline at end of file diff --git a/timeserver/timeserver.h b/timeserver/timeserver.h new file mode 100644 index 0000000..1a8135b --- /dev/null +++ b/timeserver/timeserver.h @@ -0,0 +1,23 @@ +#ifndef TIMESERVER +#define TIMESERVER + +// included libraries +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// data structures + +// function prototypes + +#endif // TIMESERVER \ No newline at end of file