Home > Software engineering >  Socket programming in C on Windows (server, multithreading with select() and fd_set)
Socket programming in C on Windows (server, multithreading with select() and fd_set)

Time:11-21

I try to create a simple application, where the server serves several clients simultaneously in the LAN, and the clients send data to the server. The problem is that the server does not know how to communicate with multiple clients at the same time, it still communicates only with the last connected client.

I deployed the Salt channel cryptographic protocol on the application layer, which ensures the integrity and security of the transmitted data. I program in C language, I use Mingw compiler, and I use the select() function and the fd_set structure to work with multiple sockets at once. I wrote the code of the client and the server, where the handshake was successful, and it was possible to exchange data with all clients at the same time. Subsequently, I tried to deploy the salt channel application protocol in the code. However, as I deployed the protocol, the server only serves the last logged-in client.

I created the structure of the client, I think about whether it contains everything that is needed. I can't figure out the error why the server can't serve more than one client at a time. I tried to test if I connect more than one client to the server, but the clients do not send any message, so the server holds their sockets, and when I shut down the server, all client-server connections are shut down, but once the client sends the message there is only one current client-server connection. I have a while loop communication, where I add a server socket to the fd_set structure and call the select() function, and when the socket is available for handshake, I call accept() and the return value is a specific client socket, which I then add to the fd_set structure, and the cycle continues and it looks for available sockets and when they check if it is suitable for handshake, if not, a function is called that receives the message from the client and decrypts it.

//Libraries for working with network tools in Windows
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0600
#endif
#include <winsock2.h>
#include <ws2tcpip.h>

//pragma comment nie je potrebny, lebo vyuzivam v Makefile subore flag -lws2_32
//#pragma comment(lib, "ws2_32.lib")

//Constants for working with sockets in Windows
#define ISVALIDSOCKET(s) ((s) != INVALID_SOCKET)
#define CLOSESOCKET(s) closesocket(s)
#define GETSOCKETERRNO() (WSAGetLastError())

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>

//Libraries of Salt channelv2
#include "salt.h"
#include "salt_io.h"
#include "salti_util.h"

#include <time.h>

//Function for reads encrypted message
salt_ret_t salt_read_begin_pom(salt_channel_t *p_channel, 
                               uint8_t *p_buffer, 
                               uint32_t buffer_size, 
                               salt_msg_t *p_msg, 
                               uint8_t *p_pom, 
                               uint32_t *p_size);


//Ready sk_sec key for server
static uint8_t host_sk_sec[64] = { 
    0x7a, 0x77, 0x2f, 0xa9, 0x01, 0x4b, 0x42, 0x33,
    0x00, 0x07, 0x6a, 0x2f, 0xf6, 0x46, 0x46, 0x39,
    0x52, 0xf1, 0x41, 0xe2, 0xaa, 0x8d, 0x98, 0x26,
    0x3c, 0x69, 0x0c, 0x0d, 0x72, 0xee, 0xd5, 0x2d,
    0x07, 0xe2, 0x8d, 0x4e, 0xe3, 0x2b, 0xfd, 0xc4,
    0xb0, 0x7d, 0x41, 0xc9, 0x21, 0x93, 0xc0, 0xc2,
    0x5e, 0xe6, 0xb3, 0x09, 0x4c, 0x62, 0x96, 0xf3,
    0x73, 0x41, 0x3b, 0x37, 0x3d, 0x36, 0x16, 0x8b
};

typedef struct{
    SOCKET sock_fd;
    salt_channel_t channel;
    struct sockaddr_storage client_address;
    socklen_t client_len;
} CLIENT;

void connection_and_servicing(CLIENT *p_client, SOCKET p_socket);

int main() { 

#if defined(_WIN32)

    //Variables
    SOCKET socket_listen;;
    CLIENT *client_info;

    uint8_t rx_buffer[UINT16_MAX * 4];
    uint8_t hndsk_buffer[SALT_HNDSHK_BUFFER_SIZE];
    uint8_t pom_buffer[SALT_HNDSHK_BUFFER_SIZE];
    salt_msg_t msg_in;
    salt_protocols_t protocols;
    salt_msg_t msg_out;
     salt_ret_t ret_msg;
     uint32_t verify = 0, decrypt_size;

    //The MAKEWORD macro allows us to request Winsock version 2.2
    WSADATA d;
    if (WSAStartup(MAKEWORD(2, 2), &d)) { //inicializacia Winscok-u
        fprintf(stderr, "Failed to initialize.\n");
        return 1;
    }
    
    printf("Configuring local address...\n");
    //Struct addrinfo hints
    struct addrinfo hints; 
    memset(&hints, 0, sizeof(hints));
    //Looking address IPv4
    hints.ai_family = AF_INET; 
    hints.ai_socktype = SOCK_STREAM; //TCP connection
    //We ask getaddrinfo () to set the address, for the availability of any network device
    hints.ai_flags = AI_PASSIVE;

    //Setting a pointer to a structure that contains return information from the getaddrinfo () function
    struct addrinfo *bind_address; 
    getaddrinfo("192.168.100.8", "8080", &hints, &bind_address); //port 8080, generate an address suitable for the bind () function

    //Creating socket
    printf("Creating socket...\n");
    socket_listen = socket(bind_address->ai_family, 
            bind_address->ai_socktype, bind_address->ai_protocol);
    if (!ISVALIDSOCKET(socket_listen)) {
        fprintf(stderr, "socket() failed. (%d)\n", GETSOCKETERRNO());
        return 1;
    }

    //Binding socket to local address
    printf("Binding socket to local address...\n");
    if (bind(socket_listen,
                bind_address->ai_addr, bind_address->ai_addrlen)) {
        fprintf(stderr, "bind() failed. (%d)\n", GETSOCKETERRNO());
        return 1;
    }

    //After we've called bind(), we use the freeaddrinfo() function to free the memory for bind_address
    puts("Bind done");
    freeaddrinfo(bind_address); 

    printf("Listening...\n");
    if (listen(socket_listen, 5) < 0) {
        fprintf(stderr, "listen() failed. (%d)\n", GETSOCKETERRNO());
        return 1;
    }

    //Define fd_set structure master that stores all of the active sockets 
    fd_set master;
    FD_ZERO(&master);
    FD_SET(socket_listen, &master);
    SOCKET max_socket = socket_listen;


    printf("Waiting for connections...\n");

    while(1) {
        fd_set reads;
        reads = master;

        //The select function determines the status of one or more sockets, waiting if necessary, to perform synchronous I/O
        if (select(max_socket 1, &reads, 0, 0, 0) < 0) {
            fprintf(stderr, "select() failed. (%d)\n", GETSOCKETERRNO());
            return 1;
        }

        SOCKET i;
        //Loop through each possible socket 
        for(i = 1; i <= max_socket;   i) {
            if (FD_ISSET(i, &reads)) {

                //If socket_listen, create TCP connection of accept() function
                 if (i == socket_listen) {
                    client_info = (CLIENT *) malloc(sizeof(CLIENT));
                    client_info->client_len = sizeof(client_info->client_address);
                    client_info->sock_fd = accept(socket_listen,
                            (struct sockaddr*) &client_info->client_address,
                            &client_info->client_len);

                    if (!ISVALIDSOCKET(client_info->sock_fd)) {
                        fprintf(stderr, "accept() failed. (%d)\n",
                                GETSOCKETERRNO());
                        return 1;
                    }

                    FD_SET(client_info->sock_fd, &master);
                    if (client_info->sock_fd > max_socket)
                        max_socket = client_info->sock_fd;
                
                    //Prints the client address using the getnameinfo() function
                    char address_buffer[100];
                    getnameinfo((struct sockaddr*)&client_info->client_address,
                            &client_info->client_len,
                            address_buffer, sizeof(address_buffer), 0, 0,
                            NI_NUMERICHOST);
                    printf("New connection %s\n", address_buffer);
                    
                    printf("\nWaiting for succeses Salt handshake...\n");

                    connection_and_servicing(client_info, socket_listen);
                    printf("handshake\n");
        
                
                
                
                } else {
                    ret_msg = SALT_ERROR;
                    memset(rx_buffer, 0, sizeof(hndsk_buffer));
                     ret_msg = salt_read_begin_pom(&client_info->channel, rx_buffer, sizeof(rx_buffer), &msg_in, pom_buffer, &decrypt_size);
                        continue;
                    }
            } //if FD_ISSET
        } //for i to max_socket
    } //while(1)
    
    printf("Closing listening socket...\n");
    free(client_info);
    CLOSESOCKET(socket_listen);

    WSACleanup();
#endif

    printf("Finished.\n");
    return 0;
}


void connection_and_servicing(CLIENT *p_client, SOCKET p_socket)
{

    //CLIENT *p_client = (context *);
    //SOCKET sock = p_client->sock_fd;

    uint8_t hndsk_buffer[SALT_HNDSHK_BUFFER_SIZE];
    uint8_t rx_buffer[UINT16_MAX * 4];
    uint8_t pom_buffer[SALT_HNDSHK_BUFFER_SIZE];
    uint8_t tx_buffer[UINT16_MAX * 4];
    uint8_t protocol_buffer[128];
    uint32_t verify = 0, decrypt_size;

    salt_msg_t msg_out;
    salt_ret_t ret;
    salt_ret_t ret_msg;
    salt_msg_t msg_in;
    salt_protocols_t protocols;

    clock_t start_t, end_t;

    ret = salt_create(&p_client->channel, SALT_SERVER, my_write, my_read, &my_time);
    assert(ret == SALT_SUCCESS);

    //Initiates to add information about supported protocols to host
    ret = salt_protocols_init(&p_client->channel, &protocols, protocol_buffer, sizeof(protocol_buffer));
    assert(ret == SALT_SUCCESS);

    //Add a protocol to supported protocols
    ret = salt_protocols_append(&protocols, "ECHO", 4);
    assert(ret == SALT_SUCCESS);

    //Sets the signature used for the salt channel
    ret = salt_set_signature(&p_client->channel, host_sk_sec);
    assert(ret == SALT_SUCCESS);

    //New ephemeral key pair is generated and the read and write nonce  is reseted
    ret = salt_init_session(&p_client->channel, hndsk_buffer, sizeof(hndsk_buffer));
    assert(ret == SALT_SUCCESS);

    //Sets the context passed to the user injected read implementation
    ret = salt_set_context(&p_client->channel, &p_client->sock_fd, &p_client->sock_fd);
    assert(ret == SALT_SUCCESS);

    //Set threshold for delay protection
    salt_set_delay_threshold(&p_client->channel, 20000);

    start_t = clock();
    //Salt handshake 
    ret = salt_handshake(&p_client->channel, NULL);
    end_t = clock();

    printf("\n");
    printf("\t\n***** SERVER:Salt channelv2 handshake lasted: %6.6f sec. *****\n", ((double) (end_t -
            start_t) / (CLOCKS_PER_SEC))); 
    printf("\n");

    //Testing success for Salt handshake
    while (ret != SALT_SUCCESS) {

        if (ret == SALT_ERROR) {
            printf("Error during handshake:\r\n");
            printf("Salt error: 0xx\r\n", p_client->channel.err_code);
            printf("Salt error read: 0xx\r\n", p_client->channel.read_channel.err_code);
            printf("Salt error write: 0xx\r\n", p_client->channel.write_channel.err_code);
            printf("Connection closed.\r\n");
            CLOSESOCKET(p_client->sock_fd);
            free(p_client);
            break;
        }

        ret = salt_handshake(&p_client->channel, NULL);
    }

    if (ret == SALT_SUCCESS) {
    printf("\nSalt handshake successful\r\n");
    printf("\n");
    verify = 1;
    }

This is the server code, I have created a CLIENT structure that contains a socket (which represents the value of the accept() function at handshake, the salt_channel_T structure needed to create a handshake in the connection and service() function).

The salt_read_begin_pom() function receives an encrypted message from the client, verifies it, decrypts it, and prints it to the screen.

CodePudding user response:

The problem is with the channel that you are passing to salt_read_begin_pom().

You are not storing your allocated CLIENT instances anywhere useful. You have only a single client_info variable, which is updated each time a new client is accept()'ed. So every i socket in your read loop that is not the server socket ends up calling salt_read_begin_pom() using the same CLIENT instance for the last accept()'ed client.

You need to store your CLIENT instances somewhere, such as in an array or lookup table, and then for each non-server i socket in the read loop that has data waiting to read, find the corresponding CLIENT whose sock_fd matches i, and then use that found CLIENT to call salt_read_begin_pom().

  • Related