Files
Routing-Table-Manager/app/server.c
2024-05-08 00:10:32 +05:30

619 lines
13 KiB
C

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/shm.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/select.h>
#include "dll.h"
#include "mac-list.h"
#include "routing-table.h"
#include "sync.h"
#define MAX_CLIENTS 32
#define OP_LEN 128
#define MAX_MASK 32
extern int store_IP(const char* mac, const char* ip);
int synchronized; // indicates if server has finished sending current updates
int connection_socket; // socket to establish connections with new clients
int loop = 1; // indicates if server is still running to clients
/*
* Server's copies of network data structures
*/
dll_t* routing_table;
dll_t* mac_list;
/*
* An array of File descriptors which the server process is maintaining in order
* to talk with connected clients. Master skt FD is also a member of this array.
*/
int monitored_fd_set[MAX_CLIENTS];
pid_t client_pid_set[MAX_CLIENTS]; // array of client process id's.
/*
* Remove all the FDs and client pid's, if any, from the array.
*/
void initialize_monitor_fd_and_client_pid_set() {
int i = 0;
for (; i < MAX_CLIENTS; i++) {
monitored_fd_set[i] = -1;
client_pid_set[i] = -1;
}
}
/*
* Add a new FD to the monitored_fd_set.
*/
void add_to_monitored_fd_set(int skt_fd) {
int i = 0;
for (; i < MAX_CLIENTS; i++) {
if (monitored_fd_set[i] != -1)
continue;
monitored_fd_set[i] = skt_fd;
break;
}
}
/*
* Add a new pid to the client_pid_set array
*/
void add_to_client_pid_set(pid_t pid) {
int i = 0;
for (; i < MAX_CLIENTS; i++) {
if (client_pid_set[i] != -1)
continue;
client_pid_set[i] = pid;
break;
}
}
/*Remove the FD from monitored_fd_set array*/
void remove_from_monitored_fd_set(int skt_fd) {
int i = 0;
for (; i < MAX_CLIENTS; i++) {
if (monitored_fd_set[i] != skt_fd)
continue;
monitored_fd_set[i] = -1;
break;
}
}
/*Remove the pid from client_pid_set array*/
void remove_from_client_pid_set(pid_t pid) {
int i = 0;
for (; i < MAX_CLIENTS; i++) {
if (monitored_fd_set[i] != pid)
continue;
client_pid_set[i] = -1;
break;
}
}
/*
* Clone all the fds in monitored_fd_set array into fd_set
* Data structure.
*/
void refresh_fd_set(fd_set* fd_set_ptr) {
FD_ZERO(fd_set_ptr);
int i = 0;
for (; i < MAX_CLIENTS; i++) {
if (monitored_fd_set[i] != -1) {
FD_SET(monitored_fd_set[i], fd_set_ptr);
}
}
}
/*
* Inform clients to flush their routing tables and mac lists.
*/
void flush_clients() {
int i;
for (i = 0; i < MAX_CLIENTS; i++) {
int pid = client_pid_set[i];
if (pid != -1) {
kill(pid, SIGUSR1);
}
}
}
/*
* Helper function for isValidMask.
*/
int digits_only(const char* s) {
while (*s) {
if (isdigit(*s++) == 0)return 0;
}
return 1;
}
/*
* Checks if IP address is Valid.
*/
int isValidIP(const char* addr) {
struct sockaddr_in sa;
return addr && inet_pton(AF_INET, addr, &(sa.sin_addr)) != 0;
}
/*
* Mask is valid if it is a string that can be
* converted to an integer within [0,32].
*/
int isValidMask(const char* mask) {
return digits_only(mask) && atoi(mask) <= 32;
}
/*
* Check if a given string represents a valid mac address
* in the format XX:XX:XX:XX:XX:XX, where each X represents
* a hexadecimal digit 0-9 or a-f.
*/
int isValidMAC(const char* addr) {
if (!addr)
return 0;
int i;
for (i = 0; i < MAC_ADDR_LEN - 1; i++) {
if (i % 3 != 2 && !isdigit(addr[i])) {
return 0;
}
if (i % 3 == 2 && addr[i] != ':')
return 0;
}
return addr[i] == '\0';
}
/*
* Get the numerical max value among all fds which server
* is monitoring.
*/
int get_max_fd() {
int i = 0;
int max = -1;
for (; i < MAX_CLIENTS; i++) {
if (monitored_fd_set[i] > max)
max = monitored_fd_set[i];
}
return max;
}
/*
* Parses a string command, in the format
* <Opcode, Dest, Mask, GW, OIF> or <Opcode, MAC>
* with each field seperated by a space, to create
* a sync message for clients, instructing them on
* how to update their copies of the routing table.
* The silent parameter indicates whether the server
* is actively inputting a command for MAC list via
* stdin or a client is replicating a command sent by
* the server. Returns 0 on success and -1 on any
* failure.
*/
int create_sync_message(char* operation, sync_msg_t* sync_msg, int silent) {
char* token = strtok(operation, " ");
if (token) {
switch (token[0]) {
case 'C':
sync_msg->op_code = CREATE;
break;
case 'U':
sync_msg->op_code = UPDATE;
break;
case 'D':
sync_msg->op_code = DELETE;
break;
case 'S':
sync_msg->op_code = NONE;
display_routing_table(routing_table);
display_mac_list(mac_list);
return 0;
case 'F':
sync_msg->op_code = NONE;
flush_clients();
deinit_dll(routing_table);
deinit_dll(mac_list);
routing_table = init_dll();
mac_list = init_dll();
return 0;
default:
fprintf(stderr, "Invalid operation: unknown op code\n");
return -1;
}
}
else {
fprintf(stderr, "Invalid operation: missing op code\n");
return -1;
}
token = strtok(NULL, " ");
if (isValidIP(token)) {
sync_msg->l_code = L3;
memcpy(sync_msg->msg_body.routing_table_entry.dest, token, strlen(token));
}
else if (isValidMAC(token)) {
sync_msg->l_code = L2;
memcpy(sync_msg->msg_body.mac_list_entry.mac, token, strlen(token));
if (!silent && sync_msg->op_code == CREATE) {
printf("Enter an IP address:\n");
char ip[IP_ADDR_LEN];
int ret = read(0, ip, IP_ADDR_LEN);
ip[strcspn(ip, "\r\n")] = 0;
if (ret < 0 || store_IP(sync_msg->msg_body.mac_list_entry.mac, ip) == -1) {
fprintf(stderr, "Failed to store ip address\n");
return -1;
}
}
return 0;
}
else {
fprintf(stderr, "Invalid operation: invalid or missing destination IP/MAC address\n");
return -1;
}
token = strtok(NULL, " ");
if (isValidMask(token)) {
sync_msg->msg_body.routing_table_entry.mask = atoi(token);
}
else {
fprintf(stderr, "Invalid operation: invalid or missing subnet mask/IP address\n");
return -1;
}
/*
* Only CREATE and UPDATE require a gw and oif.
*/
if (sync_msg->op_code == CREATE || sync_msg->op_code == UPDATE) {
token = strtok(NULL, " ");
if (isValidIP(token)) {
memcpy(sync_msg->msg_body.routing_table_entry.gw, token, strlen(token));
}
else {
fprintf(stderr, "Invalid operation: invalid or missing gateway IP\n");
return -1;
}
token = strtok(NULL, " ");
if (token) {
memcpy(sync_msg->msg_body.routing_table_entry.oif, token, strlen(token));
}
else {
fprintf(stderr, "Invalid operation: missing OIF\n");
return -1;
}
}
return 0;
}
/*
* Break out of main infinite loop and inform clients of
* shutdown to exit clearly.
*/
void signal_handler(int signal_num) {
if (signal_num == SIGINT) {
int i, synchronized = WAIT, loop = 0;
sync_msg_t sync_msg;
sync_msg.op_code = NONE;
for (i = 2; i < MAX_CLIENTS; i++) {
int comm_socket_fd = monitored_fd_set[i];
if (comm_socket_fd != -1) {
write(comm_socket_fd, &sync_msg, sizeof(sync_msg_t));
write(comm_socket_fd, &synchronized, sizeof(int));
write(comm_socket_fd, &loop, sizeof(int));
}
}
/*
* Clean up resources.
*/
int shm_fd;
mac_list_entry_t* entry;
ITERATE_DLL(mac_list, entry, mac_list_entry_t)
shm_fd = shm_open(entry->mac,
O_CREAT | O_RDONLY,
0660);
shm_unlink(entry->mac);
close(shm_fd);
ITERATE_DLL_END()
deinit_dll(routing_table);
deinit_dll(mac_list);
close(connection_socket);
remove_from_monitored_fd_set(connection_socket);
unlink(SOCKET_NAME);
exit(0);
}
}
/*
* Send newly client all necessary CREATE commands to replicate
* the server's copies of the current routing table or MAC list.
*/
void update_new_client(int data_socket, LCODE l_code, char* op, sync_msg_t* sync_msg) {
dll_node_t* head = l_code == L3 ? routing_table->head : mac_list->head;
dll_node_t* curr = head->next;
while (curr != head) {
routing_table_entry_t rt_entry = *((routing_table_entry_t*)curr->data);
mac_list_entry_t ml_entry = *((mac_list_entry_t*)curr->data);
sync_msg->op_code = CREATE;
if (l_code == L3) {
sprintf(op, "C %s %u %s %s",
rt_entry.dest,
rt_entry.mask,
rt_entry.gw,
rt_entry.oif);
}
else {
sprintf(op, "C %s", ml_entry.mac);
}
create_sync_message(op, sync_msg, 1);
write(data_socket, sync_msg, sizeof(sync_msg_t));
write(data_socket, &synchronized, sizeof(int));
write(data_socket, &loop, sizeof(int));
curr = curr->next;
}
/*
* Send dummy sync message to inform client that all
* current updates have been sent.
*/
sync_msg->op_code = NONE;
write(data_socket, sync_msg, sizeof(sync_msg_t));
synchronized = l_code == L3 ? RT : ML;
write(data_socket, &synchronized, sizeof(int));
write(data_socket, &loop, sizeof(int));
}
int main() {
struct sockaddr_un name;
int ret;
int data_socket;
fd_set readfds;
routing_table = init_dll();
mac_list = init_dll();
initialize_monitor_fd_and_client_pid_set();
add_to_monitored_fd_set(0);
unlink(SOCKET_NAME); //In case the program exited inadvertently on the last run, remove the socket.
/*
* Master socket for accepting connections
* from client
*/
connection_socket = socket(AF_UNIX, SOCK_STREAM, 0);
if (connection_socket == -1) {
perror("socket");
exit(1);
}
/*
* Initialize
*/
memset(&name, 0, sizeof(struct sockaddr_un));
/*
* Specify the socket Credentials
*/
name.sun_family = AF_UNIX;
strncpy(name.sun_path, SOCKET_NAME, sizeof(name.sun_path) - 1);
/*
* Bind socket to socket name
*/
ret = bind(connection_socket, (const struct sockaddr*)&name,
sizeof(struct sockaddr_un));
if (ret == -1) {
perror("bind");
exit(1);
}
/*
* Prepare for accepting connections.
*/
ret = listen(connection_socket, 20);
if (ret == -1) {
perror("listen");
exit(1);
}
add_to_monitored_fd_set(connection_socket);
signal(SIGINT, signal_handler); //register signal handler
/*
* The server continuously checks for new clients connections, monitors
* existing connections (i.e. incoming messages and inactive connections,
* modifies the routing table or MAC list if need be, and broadcasts to
* clients.
*/
while (1) {
char op[OP_LEN];
sync_msg_t* sync_msg = calloc(1, sizeof(sync_msg_t));
synchronized = 0;
refresh_fd_set(&readfds); /*Copy the entire monitored FDs to readfds*/
printf("Please select from the following options:\n");
printf("1.CREATE <Destination IP> <Mask (0-32)> <Gateway IP> <OIF>\n");
printf("2.UPDATE <Destination IP> <Mask (0-32)> <New Gateway IP> <New OIF>\n");
printf("3.DELETE <Destination IP> <Mask (0-32)>\n");
printf("4.CREATE <MAC>\n");
printf("5.DELETE <MAC>\n");
printf("6.SHOW\n");
printf("7.FLUSH\n");
select(get_max_fd() + 1, &readfds, NULL, NULL, NULL); /* Wait for incoming connections*/
/* New Connection : send entire routing table and mac list states to newly connected clients*/
if (FD_ISSET(connection_socket, &readfds)) {
data_socket = accept(connection_socket, NULL, NULL);
if (data_socket == -1) {
perror("accept");
exit(1);
}
pid_t pid;
if (read(data_socket, &pid, sizeof(pid_t)) == -1) {
perror("read");
exit(1);
}
add_to_monitored_fd_set(data_socket);
add_to_client_pid_set(pid);
update_new_client(data_socket, L3, op, sync_msg);
update_new_client(data_socket, L2, op, sync_msg);
}
else if (FD_ISSET(0, &readfds)) { // server stdin
ret = read(0, op, OP_LEN - 1);
op[strcspn(op, "\r\n")] = 0; //flush new line
if (ret == -1) {
perror("read");
return 1;
}
op[ret] = 0;
if (!create_sync_message(op, sync_msg, 0)) {
//Update server's tables
if (sync_msg->l_code == L3) {
process_sync_mesg(routing_table, sync_msg);
synchronized = RT;
}
else {
process_sync_mesg(mac_list, sync_msg);
synchronized = ML;
}
// Notify existing clients of change.
int i, comm_socket_fd;
for (i = 2; i < MAX_CLIENTS; i++) {
//Start at 2 since 0 and 1 are for server's stdin and stdout
comm_socket_fd = monitored_fd_set[i];
if (comm_socket_fd != -1) {
write(comm_socket_fd, sync_msg, sizeof(sync_msg_t));
write(comm_socket_fd, &synchronized, sizeof(int));
write(comm_socket_fd, &loop, sizeof(int));
}
}
}
}
else {
/*
* Check active status of clients.
*/
int i;
for (i = 2; i < MAX_CLIENTS; i++) {
if (FD_ISSET(monitored_fd_set[i], &readfds)) {
int done;
int comm_socket_fd = monitored_fd_set[i];
ret = read(comm_socket_fd, &done, sizeof(int));
if (done == 1) {
//this client is disconnecting
close(comm_socket_fd);
remove_from_monitored_fd_set(comm_socket_fd);
}
else if (ret == -1) {
perror("read");
exit(1);
}
else {
printf("%i\n", done);
}
}
}
}
}
exit(0);
}