Skip to content

A real world example of producer-consumer problem, solved in C.

Notifications You must be signed in to change notification settings

raresgoidescu/parallel-firewall

Repository files navigation

Parallel Firewall

A high-performance, multi-threaded firewall simulation built in C using the POSIX threads (pthreads) API. This project transforms a serial packet-filtering program into a highly concurrent one, demonstrating advanced parallel programming concepts and synchronization techniques.

The core of the project is a producer-consumer architecture where a single producer thread simulates the arrival of network packets, and a pool of consumer threads processes them in parallel.

Key Features

  • Concurrent Packet Processing: Leverages multi-threading to significantly speed up packet filtering compared to a serial approach.
  • Thread-Safe Ring Buffer: Implements a custom circular buffer for efficient, non-blocking communication between the producer and consumer threads.
  • Advanced Synchronization: Utilizes pthread synchronization primitives, including mutexes and condition variables, to prevent race conditions and ensure data integrity without resorting to inefficient busy-waiting.
  • Ordered Logging: A key challenge solved in this project is the generation of a perfectly ordered log file. Despite packets being processed in a non-deterministic order by multiple threads, the final log is written sequentially based on the packet's original timestamp. This is achieved through careful synchronization, ensuring the output reflects the order of packet arrival.

How It Works

  1. Packet Production: The main thread acts as the producer. In producer.c, the publish_data function reads simulated packet data from an input file. It then calls ring_buffer_enqueue() to place each packet into the shared ring buffer. Once all packets are published, it calls ring_buffer_stop() to signal consumers that no more data will be produced.
  2. Thread-Safe Ring Buffer: The so_ring_buffer_t struct in ring_buffer.c is the central data structure. Its thread-safety is achieved using a combination of a pthread_mutex_t and two POSIX semaphores (sem_t):
    • fill_cnt: Counts the number of items in the buffer. Consumers wait on this semaphore, blocking with sem_wait() until it's greater than zero.
    • empty_cnt: Counts the number of empty slots. The producer waits on this, blocking if the buffer is full.
    • A pthread_mutex_t protects the critical sections where the buffer's internal state (read_pos, write_pos, len) is modified.
  3. Parallel Consumption: In consumer.c, create_consumers spawns a pool of consumer threads. Each consumer_thread runs an infinite loop calling ring_buffer_dequeue(), which blocks until a packet is available. The dequeue function returns a unique, sequential ID for each packet. When the producer signals a stop and the buffer is empty, ring_buffer_dequeue() returns -1, causing the consumer threads to exit their loops and terminate cleanly.
  4. Filtering Logic: After dequeuing a packet, each consumer thread calls process_packet() to determine the firewall action (PASS or DROP) and packet_hash() to compute its hash, preparing it for logging.
  5. Synchronized Logging: To ensure logs are written in the correct order despite non-deterministic processing, a sophisticated synchronization mechanism is used in consumer.c:
    • A global atomic_ulong g_next_seq_to_write tracks the sequence number of the next packet that should be logged.
    • A shared pthread_mutex_t and pthread_cond_t are used to coordinate the threads.
    • After processing a packet, a consumer acquires the lock and enters a while loop, waiting on the condition variable (pthread_cond_wait) until its packet's sequence number matches g_next_seq_to_write.
    • Once it's its turn, the thread writes to the log file, atomically increments g_next_seq_to_write using atomic_fetch_add, and calls pthread_cond_broadcast() to wake up other waiting threads so they can re-check the condition. This ensures strict, ordered logging without sacrificing parallel processing.

Building the Project

To build the serial and parallel firewall binaries, navigate to the src/ directory and run make.

cd src/
make

Running the Firewall

You can run the parallel firewall with a specified input file, output file, and the number of consumer threads.

# Example: Run with 4 consumer threads
./firewall <input_file> <output_file> 4

This project was originally developed as a university assignment focused on operating systems and concurrent programming. The repository includes the original tests and grading scripts for validation.

About

A real world example of producer-consumer problem, solved in C.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 3

  •  
  •  
  •