Network Peer Discovery and Server-Client Election System

This program automatically retrieves its own network details, such as IP address and netmask, and pings all devices within the same network range to find active peers. When a live peer is detected, it establishes a TCP connection and exchanges a predefined TOKEN with the peer. The program uses an election mechanism: the peer with more successful connections becomes the server, and the one with fewer connections flips to client mode. All successful connection IP addresses are tracked for future use. The program runs in both client and server modes, allowing it to simultaneously connect to other programs and accept incoming connections.

https://counter.news/wp-content/uploads/2024/10/multiple-devices-connected-to-each-other-via-lines.webp

MY NOTES. NOT 100% FUNCTIONAL

Key Features:

  • Peer Discovery: Identifies live hosts by pinging all IP addresses in the network range.
  • Connection and Token Exchange: Establishes a client-server connection and exchanges a TOKEN with the peer.
  • Server-Client Election: The program with more successful connections becomes the server.
#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <thread>
#include <sys/ioctl.h>
#include <net/if.h>
#include <fstream>

#define PORT 8080
#define PACKET_SIZE 64
const char* TOKEN = "TOKEN_EXCHANGE";

// Global variable to keep track of the number of successful connections
int connectionCount = 0;
bool isServer = true;  // Initially, each program will act as a server
std::vector<std::string> successfulConnections;  // Track successful connection IPs

// Function to retrieve the network IP address
std::string getIPAddress(const char* iface) {
    int fd;
    struct ifreq ifr;
    fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd == -1) {
        std::cerr << "Socket creation failed" << std::endl;
        return "";
    }

    ifr.ifr_addr.sa_family = AF_INET;
    strncpy(ifr.ifr_name, iface, IFNAMSIZ - 1);
    ioctl(fd, SIOCGIFADDR, &ifr);
    close(fd);

    return inet_ntoa(((struct sockaddr_in*)&ifr.ifr_addr)->sin_addr);
}

// Function to retrieve the network netmask
std::string getNetmask(const char* iface) {
    int fd;
    struct ifreq ifr;
    fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd == -1) {
        std::cerr << "Socket creation failed" << std::endl;
        return "";
    }

    ifr.ifr_addr.sa_family = AF_INET;
    strncpy(ifr.ifr_name, iface, IFNAMSIZ - 1);
    ioctl(fd, SIOCGIFNETMASK, &ifr);
    close(fd);

    return inet_ntoa(((struct sockaddr_in*)&ifr.ifr_netmask)->sin_addr);
}

// Function to ping an IP and check if it's alive
bool isAlive(const std::string& ip) {
    std::string command = "ping -c 1 -w 1 " + ip + " > /dev/null 2>&1";
    return (system(command.c_str()) == 0);
}

// Generate a list of IPs in the range based on IP and netmask
std::vector<std::string> generateIPRange(const std::string& ip, const std::string& netmask) {
    struct in_addr ip_addr, mask_addr;
    inet_aton(ip.c_str(), &ip_addr);
    inet_aton(netmask.c_str(), &mask_addr);

    uint32_t ip_host = ntohl(ip_addr.s_addr);
    uint32_t mask_host = ntohl(mask_addr.s_addr);
    uint32_t network_host = ip_host & mask_host;
    uint32_t broadcast_host = network_host | ~mask_host;

    std::vector<std::string> ips;
    for (uint32_t i = network_host + 1; i < broadcast_host; ++i) {
        struct in_addr addr;
        addr.s_addr = htonl(i);
        ips.push_back(inet_ntoa(addr));
    }

    return ips;
}

// Function to handle incoming connections (server-side)
void handleClient(int new_socket, std::string peerIP) {
    char buffer[1024] = {0};
    int peerConnectionCount;

    // Read incoming TOKEN and connection count
    read(new_socket, buffer, 1024);
    if (strcmp(buffer, TOKEN) == 0) {
        read(new_socket, &peerConnectionCount, sizeof(int));
        std::cout << "TOKEN received from client, peer's connection count: " << peerConnectionCount << std::endl;

        // Add peer IP to the successful connection list
        successfulConnections.push_back(peerIP);
        connectionCount++;
        std::cout << "Connection established with " << peerIP << std::endl;

        // Perform election
        if (peerConnectionCount > connectionCount) {
            std::cout << "Flipping to client mode, peer has more connections." << std::endl;
            isServer = false;
        }

        // Send back our TOKEN and connection count
        send(new_socket, TOKEN, strlen(TOKEN), 0);
        send(new_socket, &connectionCount, sizeof(int), 0);
        std::cout << "TOKEN and connection count sent to client" << std::endl;
    }
    close(new_socket);
}

// Function to run the server and listen for incoming connections
void runServer() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);

    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        std::cerr << "Socket failed" << std::endl;
        exit(EXIT_FAILURE);
    }

    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        std::cerr << "setsockopt failed" << std::endl;
        exit(EXIT_FAILURE);
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
        std::cerr << "Bind failed" << std::endl;
        exit(EXIT_FAILURE);
    }

    if (listen(server_fd, 3) < 0) {
        std::cerr << "Listen failed" << std::endl;
        exit(EXIT_FAILURE);
    }

    std::cout << "Server listening on port " << PORT << std::endl;

    while (true) {
        if ((new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen)) < 0) {
            std::cerr << "Accept failed" << std::endl;
            exit(EXIT_FAILURE);
        }

        // Convert the incoming address to a readable IP address
        char peerIP[INET_ADDRSTRLEN];
        inet_ntop(AF_INET, &(address.sin_addr), peerIP, INET_ADDRSTRLEN);
        std::thread clientThread(handleClient, new_socket, std::string(peerIP));
        clientThread.detach();
    }
}

// Function to connect to a live IP and exchange a TOKEN (client-side)
void connectToServer(const std::string& ip) {
    struct sockaddr_in serv_addr;
    char buffer[1024] = {0};
    int peerConnectionCount;

    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        std::cerr << "Socket creation error" << std::endl;
        return;
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);

    if (inet_pton(AF_INET, ip.c_str(), &serv_addr.sin_addr) <= 0) {
        std::cerr << "Invalid address or Address not supported" << std::endl;
        return;
    }

    if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
        std::cerr << "Connection failed to " << ip << std::endl;
        return;
    }

    // Send TOKEN and connection count to server
    send(sock, TOKEN, strlen(TOKEN), 0);
    send(sock, &connectionCount, sizeof(int), 0);
    std::cout << "TOKEN and connection count sent to " << ip << std::endl;

    // Receive server's TOKEN and connection count
    read(sock, buffer, 1024);
    read(sock, &peerConnectionCount, sizeof(int));
    if (strcmp(buffer, TOKEN) == 0) {
        std::cout << "TOKEN received from " << ip << ", peer's connection count: " << peerConnectionCount << std::endl;

        // Add peer IP to the successful connection list
        successfulConnections.push_back(ip);
        connectionCount++;
        std::cout << "Connection established with " << ip << std::endl;

        // Perform election
        if (peerConnectionCount > connectionCount) {
            std::cout << "Flipping to client mode, peer has more connections." << std::endl;
            isServer = false;
        } else {
            std::cout << "Remaining server, more connections." << std::endl;
        }
    }
    close(sock);
}

// Main function to run the program
int main() {
    // Get network information
    std::string ip = getIPAddress("enp0s3");
    std::string netmask = getNetmask("enp0s3");

    // Generate the list of IPs in the network range
    std::vector<std::string> ips = generateIPRange(ip, netmask);
    bool liveHostFound = false;

    // Ping all IPs and attempt to connect if alive
    std::cout << "Pinging IPs in range..." << std::endl;
    for (const auto& target_ip : ips) {
        if (isAlive(target_ip)) {
            std::cout << "Host is alive: " << target_ip << std::endl;
            connectToServer(target_ip);
            liveHostFound = true;
        }
    }

    // If no live host was found, start the server
    if (!liveHostFound) {
        std::cout << "No live hosts found, starting server mode..." << std::endl;
        std::thread serverThread(runServer);
        serverThread.join();
    } else {
        // Always be listening for incoming connections as well
        std::cout << "Starting server to listen for incoming connections..." << std::endl;
        std::thread serverThread(runServer);
        serverThread.detach();  // Keep the server running in the background
    }

    return 0;
}