Related
I am trying to build a chat application between the server and the client. My doubt is for sending information from the client or from the server I was able to handle the partial send with the help of the loop, but I am unable to find out the length of the send data bytes from the client to the server or from the server to the client, thereby having problem in creating the memory for the received bytes and printing.
My chat function code for the client:
int chat_function(int sockfd)
{
char ch;
char *buf;
char *newp;
int ret_send = 0;
int ret_recv = 0;
int buf_size = 0;
while(1) {
printf("From client, enter the message : ");
buf = (char *)malloc(sizeof(char));
if (buf == NULL)
return -1;
while ((ch = getchar()) != '\n') {
buf[buf_size++] = ch;
newp = (char *)realloc(buf, (buf_size + 1) * sizeof(char));
if ( newp == NULL) {
free(buf);
return -1;
}
buf = newp;
}
buf[buf_size] = '\0';
ret_send = send_all(sockfd, buf, buf_size);
if (ret_send == -1)
error(1, errno, "error in send() function call\n");
memset(buf, 0, buf_size);
ret_recv = recv_all(sockfd, buf, buf_size);
if (ret_recv == -1) {
error(1, errno, "error in recv() function call\n");
} else if (ret_recv == -2) {
printf("Oops the server has closed the connection\n");
free(buf);
break;
}
printf("From Server : %s", buf);
if ((strncmp(buf, "exit", 4)) == 0) {
printf("Client Exit...\n");
free(buf);
break;
}
free(buf);
}
}
For handling partial send:
int send_all(int sockfd, char *buf, int buf_size)
{
int bytes_left = 0;
size_t send_bytes = 0;
bytes_left = buf_size
while (1) {
send_bytes = send(fd, buf, bytes_left, 0);
if (send_bytes == -1)
return -1;
buf = buf + send_bytes;
bytes_left = bytes_left - send_bytes;
if (bytes_left == 0)
break;
}
return 0;
}
TCP is a stream protocol, meaning there are no message boundaries: it is just a full-duplex (meaning data flows in both directions at the same time, as if there were two separate lanes) more or less continuous stream of data.
UDP is a datagram protocol, and does have message boundaries. There is an ioctl (FIONREAD/SIOCINQ) that provides the length of the next datagram, but because it involves a syscall, doing that for every message you receive is going to be slow and inefficient. Instead, you normally use a buffer large enough to hold the largest acceptable message, and copy it if/when necessary. However, UDP also has no reliability guarantees, and often UDP datagrams are completely lost without any trace or discernible reason; that's just what happens.
For a chat client-server connection, you'll want to use TCP.
Since the underlying connection is just a stream of data, you need to design a protocol for the communications, so that the stream can be split into messages, with each message processed separately.
The simplest case would be to use the nul character, \0, as a message separator.
The "send" function would then look something like this:
/* Returns 0 if message successfully sent,
nonzero errno code otherwise. */
int send_message(int descriptor, const char *message)
{
/* If message is NULL, we cannot use strlen(); use zero for that. */
const size_t message_len = (message) ? strlen(message) : 0;
/* Temporary variables for the sending part. */
const char *ptr = message;
const char *const end = message + message_len + 1; /* Include '\0' at end */
ssize_t bytes;
/* Check valid descriptor and message length. */
if (descriptor == -1 || message_len < 1)
return errno = EINVAL;
/* Write loop for sending the entire message. */
while (ptr < end) {
bytes = write(descriptor, ptr, (size_t)(end - ptr));
if (bytes > 0) {
ptr += bytes;
} else
if (bytes != -1) {
/* This should never happen. */
return errno = EIO;
} else
if (errno != EINTR) {
/* We do not consider EINTR an actual error; others we do. */
return errno;
}
}
return 0;
}
The above send_message() function writes the specified string, including the string terminating nul character \0, to the specified descriptor.
On the read end, we need a buffer large enough to hold at least one full message. Instead of always waiting for incoming data, we need to check if the buffer already contains a full message, and if it does, return that. Also, you do not necessarily want to always wait for an incoming message, because that would mean you cannot send two messages in a row.
So, here's my suggestion:
static int incoming_desc = -1;
static char *incoming_data = NULL;
static size_t incoming_size = 0;
static char *incoming_next = NULL; /* First received but not handled */
static char *incoming_ends = NULL; /* Last received but not handled */
#define INCOMING_CHUNK 4096
/* Receive a new message into dynamically allocated buffer,
and return the length. Returns 0 when no message, with errno set.
Waits at most ms milliseconds for a new message to arrive.
errno == EAGAIN: no message, timeout elapsed.
errno == ECONNABORTED: other end closed the connection.
*/
size_t get_message(char **message, size_t *size, long ms)
{
struct timeval timeout;
/* Make sure the parameters are sane. */
if (!message || !size || ms < 0) {
errno = EINVAL;
return 0;
}
/* For this function to work like getline() and getdelim() do,
we need to treat *message as NULL if *size == 0. */
if (!*size)
*message = NULL;
timeout.tv_sec = ms / 1000;
timeout.tv_usec = (ms % 1000) * 1000;
/* Timeout loop. */
while (1) {
fd_set readfds;
ssize_t bytes;
size_t used;
int result;
/* Is there a pending complete message in the buffer? */
if (incoming_ends > incoming_next) {
char *endmark = memchr(incoming_next, '\0', (size_t)(incoming_ends - incoming_next));
if (endmark) {
const size_t len = (size_t)(endmark - incoming_next) + 1;
/* Reallocate the message buffer, if necessary. */
if (len > *size) {
char *temp = realloc(*message, len);
if (!temp) {
errno = ENOMEM;
return 0;
}
*message = temp;
*size = len;
}
/* Copy message, */
memcpy(*message, incoming_next, len);
/* and remove it from the buffer. */
incoming_next += len;
/* In case the other end sent just the separator, clear errno. */
errno = 0;
/* We return the length sans the separator. */
return len - 1;
}
}
/* Do we have time left to check for input? */
if (timeout.tv_sec <= 0 && timeout.tv_usec <= 0)
break; /* Nope. */
/* Is incoming_desc one we can select() for? */
if (incoming_desc < 0 || incoming_desc >= FD_SETSIZE)
break; /* Nope. */
FD_ZERO(&readfds);
FD_SET(incoming_desc, &readfds);
result = select(incoming_desc + 1, &readfds, NULL, NULL, &timeout);
if (result < 1)
break; /* Nothing interesting happened (we ignore error here). */
if (!FD_ISSET(incoming_fd, &readfds))
break;
/* Number of bytes used in the buffer right now. */
used = (size_t)(incoming_ends - incoming_data);
/* Do we have at least INCOMING_CHUNK bytes available? */
if (used + INCOMING_CHUNK >= incoming_size) {
/* Nope. Repack the incoming buffer first. */
if (incoming_next > incoming_data) {
const size_t len = (size_t)(incoming_ends - incoming_next);
if (len > 0)
memmove(incoming_data, incoming_next, len);
incoming_next = incoming_data;
incoming_ends = incoming_data + len;
}
/* Recalculate the number of bytes we have free now. Enough? */
used = (size_t)(incoming_ends - incoming_data);
if (used + INCOMING_CHUNK > incoming_size) {
/* Grow incoming buffer. */
const size_t newsize = used + INCOMING_CHUNK;
char *temp = realloc(incoming_data, newsize);
if (!temp) {
errno = ENOMEM;
return 0;
}
incoming_next = temp + (size_t)(incoming_next - incoming_data);
incoming_ends = temp + used;
incoming_data = temp;
incoming_size = newsize;
}
}
/* Read more data into the buffer; up to a full buffer. */
bytes = read(incoming_fd, incoming_ends, incoming_size - used);
if (bytes > 0) {
incoming_ends += bytes;
} else
if (bytes == 0) {
/* Other end closed the connection. We may have a partial message
in the buffer, and should handle that too, but for now, we
just error out. */
errno = ECONNABORTED;
return 0;
} else
if (bytes != -1) {
/* Should never happen. */
errno = EIO;
return 0;
} else
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) {
/* No data yet, interrupted by signal delivery, etc. */
continue;
} else {
/* errno is set to indicate which error happened. */
return 0;
}
}
/* Timeout. */
errno = EAGAIN;
return 0;
}
Note that get_message() works like getline(): you do e.g.
char *msg = NULL;
size_t size = 0;
size_t len;
len = get_message(&msg, &size, 100); /* 100 ms = 0.1 seconds */
if (len) {
/* msg contains a full message of len characters */
} else
if (errno == ECONNABORTED) {
/* Other end closed the connection */
} else
if (errno != EAGAIN) {
fprintf(stderr, "Error receiving data: %s.\n", strerror(errno));
}
Then, you can reuse the same dynamically allocated buffer by just calling e.g.
len = get_message(&msg, &size, 100); /* 100 ms = 0.1 seconds */
again.
There is no such mechanism built into TCP or UDP. You need to implement your own protocol on top of it. One of the possible solutions is:
If the content delivered is static.
If the sending end knows the size of the data that is being delivered prior, your client and server can agree on specific terms. For example, the first four bytes sent by the server is the size of the remaining message represented in network byte order.
Server code
uint32_t n_size = htonl(size); // Convert the data size into network byte order.
write(sockfd, &n_size, sizeof(n_size)); // Send to the client.
Client code
uint32_t n_size;
int n_read = 0;
for ( ; ; ) {
int rd_status = read(sockfd, (void*) &n_size + n_read, sizeof(n_size) - n_read);
if (rd_status <= 0)
goto handle_this_case;
n_read = n_read + rd_status;
if (n_read == sizeof(n_size))
break;
}
uint32_t size = ntohl(n_size);
If the content delivered is generated on the fly.
In this case, even the server is not aware of the size of the message. You need to build your functions for handling this case. Below I have shown a bare minimal implementation:
Client-Side:
struct data_unit
{
void* data;
int size;
};
struct data_storage
{
struct data_unit unit;
struct data_storage* next;
};
void append_data(struct data_storage* storage, struct data_unit* unit);
struct data_unit* dump_data(struct data_storage* storage);
int main()
{
struct data_storage storage;
struct data_unit unit;
unit.data = malloc(MAX_SIZE);
for ( ; ; ) {
int rd_status = read(sockfd, unit.data, MAX_SIZE);
if (rd_status < 0)
goto handle_this_case;
else if (rd_status == 0)
break;
unit.size = rd_status;
append_data(&storage, &unit);
}
struct data_unit* t_data = dump_data(&storage);
}
My single threaded HTTP Server works just fine, but I'm having trouble multithreading it. I know I am supposed to use pthreads, locks, and condition variables, but I can't get the logic set up properly. The trouble starts after listening to the server. Currently I have a struct that contains a client socket variable, a lock variable, a condition variable, and some variables necessary for parsing and storing headers. I create a struct array sized with the amount of threads, then create a pthread array sized with the amount of threads. I go into a while(1) loop which goes into a for loop and iterates through all the threads accepting each connection, calling pthread_create and passing them to my handle connections function, then closing the client socket. My handle connections then does the request handling that my single threaded http server did (reading, parsing, processing, constructing), then returns NULL. No request gets read when I run this using pthread_create, but if I run handle connections without the pthreads, it works just fine. And below I'll attach my code. Any help is appreciated
Thank you for commenting so well ...
Okay, I coded up, but not tested the changes.
Your loop is inherently single threaded, so a bit of refactoring is in order
You have to scan for an unused thread control slot after doing accept.
You have to pthread_join completed/done threads [from any prior invocations].
The thread function has to close the per-client socket [not main thread]
You need a global (file scope) mutex.
I've coded it up, but not tested it. I put #if 0 around most of what I clipped out and #if 1 around new code.
Note that number of simultaneous connections [second arg to listen], herein 5 has to be less than or equal to threadNum. Although I didn't do it, I'd just do listen(...,threadNum) instead of hardwiring it.
Here's the short code with just the relevant changes:
#if 1
pthread_mutex_t global_mutex = PTHREAD_MUTEX_INITIALIZER;
#endif
struct threadObject {
char method[5]; // PUT, HEAD, GET. HEAD==4 letters+null terminator
char filename[28]; // what is the file we are worried about. Max 27 ASCII characters (NULL terminated on 28)
char httpversion[9]; // HTTP/1.1
ssize_t content_length; // example: 13
uint16_t status_code; // status code for the request
char buffer[BUFFER_SIZE]; // buffer to transfer data
char rest_of_PUT[BUFFER_SIZE]; // incase client send part of PUT message in header
int client_sockd;
pthread_mutex_t *dispatch_lock;
const pthread_cond_t *job_pool_empty;
// pthread_mutex_t* log_lock;
// const pthread_cond_t* log_pool_empty;
pthread_mutex_t *read_write_lock;
pthread_cond_t *file_list_update;
// JobQueue* job_pool;
// LogQueue log_pool;
// bool is_logging;
#if 1
pthread_t tsk_threadid;
int tsk_inuse;
int tsk_done;
#endif
};
void *
handle_connections(void *ptr_thread)
{
// create a mutual exclusion to lock out any other threads from the function
// pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// pthread_mutex_lock(&mutex);
// operations go here
struct threadObject *thread = (struct threadObject *) ptr_thread;
// reset message after each loop
memset(thread->buffer, '\0', BUFFER_SIZE);
memset(thread->method, '\0', 5);
memset(thread->filename, '\0', 28);
memset(thread->httpversion, '\0', 9);
thread->content_length = 0;
thread->status_code = 0;
memset(thread->rest_of_PUT, '\0', BUFFER_SIZE);
// read message
if (read_http_response(thread) == true) {
// process message
process_request(thread);
}
// construct a response
construct_http_response(thread);
// unlock the function
// pthread_mutex_unlock(&mutex);
#if 1
close(thread->client_sockd);
pthread_mutex_lock(&global_mutex);
thread->tsk_done = 1;
pthread_mutex_unlock(&global_mutex);
#endif
return NULL;
}
int
main(int argc, char **argv)
{
// Create sockaddr_in with server information
if (argc < 2) {
perror("No arguments passed\n");
return -1;
}
// make sure port number is above 1024 and set the port # to it
if (atoi(argv[1]) < 1024) {
return 1;
}
char *port = argv[1];
// parse the command line args for options -l and -N. -l specifies it will use a log and the following parameter is the filename. -N specifies the number of threads it will use and the following parameter will be a number
int opt;
uint8_t threadNum = 1;
char *logName = NULL;
while ((opt = getopt(argc - 1, argv + 1, "N:l:")) != -1) {
if (opt == 'N') {
threadNum = atoi(optarg);
}
else if (opt == 'l') {
logName = optarg;
}
}
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(port));
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
socklen_t addrlen = sizeof(server_addr);
// Create server socket
int server_sockd = socket(AF_INET, SOCK_STREAM, 0);
// Need to check if server_sockd < 0, meaning an error
if (server_sockd < 0) {
perror("socket");
return 1;
}
// Configure server socket
int enable = 1;
// This allows you to avoid: 'Bind: Address Already in Use' error
int ret = setsockopt(server_sockd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
if (ret < 0) {
return EXIT_FAILURE;
}
// Bind server address to socket that is open
ret = bind(server_sockd, (struct sockaddr *) &server_addr, addrlen);
if (ret < 0) {
return EXIT_FAILURE;
}
// Listen for incoming connections
ret = listen(server_sockd, 5); // 5 should be enough, if not use SOMAXCONN
if (ret < 0) {
return EXIT_FAILURE;
}
struct threadObject thread[threadNum];
// Connecting with a client
struct sockaddr client_addr;
socklen_t client_addrlen = sizeof(client_addr);
// create a pthread array of size (number of threads). specify this will be using the handle connections function. join the threads together
#if 0
pthread_t thread_id[threadNum];
#endif
#if 1
struct threadObject *tsk = NULL;
int tskidx;
// clear out the thread structs
for (tskidx = 0; tskidx < threadNum; tskidx++) {
tsk = &thread[tskidx];
memset(tsk,0,sizeof(struct threadObject));
}
while (true) {
// accept connection
int client_sockd = accept(server_sockd, &client_addr, &client_addrlen);
pthread_mutex_lock(&global_mutex);
// join any previously completed threads
for (tskidx = 0; tskidx < threadNum; tskidx++) {
tsk = &thread[tskidx];
if (tsk->tsk_done) {
pthread_join(tsk->tsk_threadid,NULL);
tsk->tsk_inuse = 0;
tsk->tsk_done = 0;
}
}
// find unused task slot
for (tskidx = 0; tskidx < threadNum; tskidx++) {
tsk = &thread[tskidx];
if (! tsk->tsk_inuse)
break;
}
memset(tsk,0,sizeof(struct threadObject));
tsk->client_sockd = client_sockd;
tsk->tsk_inuse = 1;
pthread_mutex_unlock(&global_mutex);
// fire in the hole ...
pthread_create(&tsk->tsk_threadid, NULL, handle_connections, tsk);
}
#endif
#if 0
for (int i = 0; i < threadNum; i++) {
printf("\n[+] server is waiting...\n");
thread[i].client_sockd = accept(server_sockd, &client_addr, &client_addrlen);
handle_connections(&thread[i]);
// pthread_create(&thread_id[i], NULL, handle_connections, &thread[i]);
printf("Response Sent\n");
// close the current client socket
close(thread[i].client_sockd);
}
}
#endif
return EXIT_SUCCESS;
}
Here's the complete code [just in case I clipped out too much]:
#include <sys/socket.h>
#include <sys/stat.h>
#include <stdio.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <fcntl.h>
#include <unistd.h> // write
#include <string.h> // memset
#include <stdlib.h> // atoi
#include <stdbool.h> // true, false
#include <errno.h>
#include <sys/types.h>
#include <ctype.h>
#include <pthread.h>
#define BUFFER_SIZE 4096
#if 1
pthread_mutex_t global_mutex = PTHREAD_MUTEX_INITIALIZER;
#endif
struct threadObject {
char method[5]; // PUT, HEAD, GET. HEAD==4 letters+null terminator
char filename[28]; // what is the file we are worried about. Max 27 ASCII characters (NULL terminated on 28)
char httpversion[9]; // HTTP/1.1
ssize_t content_length; // example: 13
uint16_t status_code; // status code for the request
char buffer[BUFFER_SIZE]; // buffer to transfer data
char rest_of_PUT[BUFFER_SIZE]; // incase client send part of PUT message in header
int client_sockd;
pthread_mutex_t *dispatch_lock;
const pthread_cond_t *job_pool_empty;
// pthread_mutex_t* log_lock;
// const pthread_cond_t* log_pool_empty;
pthread_mutex_t *read_write_lock;
pthread_cond_t *file_list_update;
// JobQueue* job_pool;
// LogQueue log_pool;
// bool is_logging;
#if 1
pthread_t tsk_threadid;
int tsk_inuse;
int tsk_done;
#endif
};
//read in the header and store it in the appropriate places
bool
read_http_response(struct threadObject *thread)
{
printf("\nThis function will take care of reading message\n");
// how many bytes we're receiving from the header. also puts the message into the buffer
ssize_t bytes = recv(thread->client_sockd, thread->buffer, BUFFER_SIZE, 0);
// if nothing or too much gets sent in the header, return
if (bytes <= 0 || bytes >= BUFFER_SIZE) {
thread->status_code = 400;
printf("Too long or nothing in here\n");
return false;
}
// NULL terminate the last spot on the buffer
thread->buffer[bytes] = '\0';
// how many bytes we received
printf("[+] received %ld bytes from client\n[+] response: \n", bytes);
printf("those bytes are: %s\n", thread->buffer);
// make a char pointer pointer to the buffer to easily traverse it and parse it into the right spots
char *traverse = thread->buffer;
// first stop. sgnals the beginning of the filename
char *file = strstr(traverse, "/");
// 2nd stop. signls the beginning of the HTTP version. only 1.1 is accepted
char *http = strstr(traverse, "HTTP/1.1");
// 3rd stop. Signals the beginning of the content length
char *contlength1 = strstr(traverse, "Content-Length");
char *chunked = strstr(traverse, "chunked");
if (chunked != NULL) {
printf("MESSAGE NOT A FILE PUT\n");
thread->status_code = 403;
return false;
}
// store the method
sscanf(traverse, "%s", thread->method);
printf("method:%s\n", thread->method);
// if its not 1 of the 3 valid requests, throw 400 error
if (strcmp(thread->method, "GET") != 0 &&
strcmp(thread->method, "PUT") != 0 &&
strcmp(thread->method, "HEAD") != 0) {
thread->status_code = 400;
printf("Invalid Method:%s\n", thread->method);
return false;
}
// if the filename doesnt start with /, its invalid throw 400 error
if (*file != '/') {
thread->status_code = 400;
printf("bad filename\n");
return false;
}
// only store the filename portion after the required /
traverse = file + 1;
// to make sure the filename isnt too long
uint8_t size_check = 0;
// traverse filename until first whitespace
while (*traverse != ' ') {
// if any character in the filename isnt 1 of these, its invalid. throw 400 error
if (!isalnum(*traverse) && *traverse != '_' && *traverse != '-') {
// if theres no filename at all, throw a 404 error
if (size_check == 0) {
thread->status_code = 404;
printf("No file specified\n");
return thread->status_code;
}
thread->status_code = 400;
printf("Invalid filename character:%c\n", *traverse);
return false;
}
sscanf(traverse++, "%c", thread->filename + size_check++);
// if the filename breaks the 27 character limit, return a 400 error
if (size_check > 27) {
thread->status_code = 400;
printf("filename too long\n");
return false;
}
}
printf("filename:%s\n", thread->filename);
// if HTTP/1.1 isnt given, throw a 400 error
if (http == NULL) {
printf("HTTP/1.1 400 Bad Request\r\n\r\n");
thread->status_code = 400;
return false;
}
traverse = http;
// read in the http version until the first \r\n. this signals the end of the given version name
sscanf(traverse, "%[^\r\n]s", thread->httpversion);
printf("HTTP:%s\n", thread->httpversion);
// if its not a put request, this is the end of the header. return
if (strcmp(thread->method, "PUT") != 0) {
return true;
}
// for put requests only. traverse until the beginning of the content length
traverse = contlength1;
// last stop. signals the end of a normal PUT header. if a client wants to put some of the message in the header, it gets stored after this
char *end = strstr(traverse, "\r\n\r\n");
// if theres no \r\n\r\n, the header is bad. return 400
if (end == NULL) {
printf("bad header\n");
thread->status_code = 400;
return false;
}
// traverse to the next digit
while (!isdigit(*traverse)) {
// if theres no next digit after "content length", the header is bad. return 400
if (traverse == end) {
printf("bad header\n");
thread->status_code = 400;
return false;
}
traverse++;
}
// set to traverse to be sure fit the entire content length. use size_check to traverse through
char *temp = traverse;
size_check = 0;
// while its taking in digits, put them into the char array.
while (isdigit(*traverse)) {
sscanf(traverse++, "%c", temp + size_check++);
}
// convert the new string into numbers
thread->content_length = atoi(temp);
// if the content length is < 0 throw a 400 error
if (thread->content_length < 0) {
thread->status_code = 400;
printf("bad content length:%ld\n", thread->content_length);
return false;
}
// printf("Content Length:%ld\n", thread->content_length);
// move +4 spots to get to the end of this. if its a normal PUT, this will be the last spot. If the client puts part of the message in the header, it goes after this
traverse = end + 4;
// put the rest of the header into a char array to append later. if theres nothing, itll do nothing
strcpy(thread->rest_of_PUT, traverse);
// printf("Rest of PUT:%s\n", thread->rest_of_PUT);
// will only get here if status code is 0
return true;
}
//process the message we just recieved
void
process_request(struct threadObject *thread)
{
printf("\nProcessing Request\n");
// server side file descriptor
int fd;
// if the method is PUT
if (strcmp(thread->method, "PUT") == 0) {
// open the file for read only to check if its already there or not to set proper status code
fd = open(thread->filename, O_WRONLY);
// if it doesnt exist, set 201 status code
struct stat checkExist;
if (stat(thread->filename, &checkExist) != 0) {
thread->status_code = 201;
}
// if it exists, set 200 and overwrite
else {
struct stat fileStat;
fstat(fd, &fileStat);
// check write permission
if ((S_IWUSR & fileStat.st_mode) == 0) {
printf("MESSAGE NOT WRITEABLE PUT\n");
thread->status_code = 403;
return;
}
thread->status_code = 200;
}
// close it
close(fd);
// reopen it. this time for writing to or overwriting. if its there, overwrite it. if not, create it. cant use for status codes since it will always create a new file
fd = open(thread->filename, O_WRONLY | O_CREAT | O_TRUNC);
// printf("fd in process is:%d\n", fd);
// if theres a bad fd, throw a 403
if (fd < 0) {
printf("ERROR\n\n");
thread->status_code = 403;
return;
}
// to check that the amount of bytes sent = the amount received
ssize_t bytes_recv,
bytes_send;
// if theres no body, put an empty file on the server
if (thread->content_length == 0) {
bytes_send = write(fd, '\0', 0);
}
// if there is a body, put it onto the new file created on the server and make sure the received bytes = the sent ones
else {
ssize_t total = 0,
len_track = thread->content_length;
while (thread->content_length != 0) {
bytes_recv = recv(thread->client_sockd, thread->buffer, BUFFER_SIZE, 0);
bytes_send = write(fd, thread->buffer, bytes_recv);
total += bytes_send;
// if the received bytes != the sent byes, send a 500 error
if (bytes_recv != bytes_send) {
thread->status_code = 500;
printf("Recieved != sent for put request\n");
return;
}
thread->content_length -= bytes_recv;
// printf("Bytes read:%ld\nBytes sent:%ld\nMessage content length:%ld\n", bytes_recv, bytes_send, message->content_length);
}
// if the content length != bytes sent, throw a 403 error
if (len_track != total) {
thread->status_code = 403;
printf("Content length != sent for put request\n");
return;
}
}
printf("Message status code:%d\n", thread->status_code);
// close the fd
close(fd);
return;
}
// if the method is GET or HEAD
else if (strcmp(thread->method, "GET") == 0 || strcmp(thread->method, "HEAD") == 0) {
// open the file for reading only
fd = open(thread->filename, O_RDONLY);
// if bad fd, throw a 404
struct stat fileStat;
fstat(fd, &fileStat);
// check read permission and if it exists
if (((S_IRUSR & fileStat.st_mode) == 0) || stat(thread->filename, &fileStat) != 0) {
printf("BAD GET\n");
thread->status_code = 404;
return;
}
else {
thread->status_code = 200;
thread->content_length = lseek(fd, 0, SEEK_END);
}
// close the fd
close(fd);
return;
}
}
void
construct_http_response(struct threadObject *thread)
{
printf("Constructing Response\n");
// size 22 since the largest code is 21 characters + NULL
char response[22];
// 200=OK, 201=CREATED, 400=BAD REQUEST, 403=FORBIDDEN, 404=NOT FOUND, 500=INTERNAL SERVER ERROR
if (thread->status_code == 200) {
strcpy(response, "OK");
}
else if (thread->status_code == 201) {
strcpy(response, "CREATED");
}
else if (thread->status_code == 400) {
strcpy(response, "BAD REQUEST");
}
else if (thread->status_code == 403) {
strcpy(response, "FORBIDDEN");
}
else if (thread->status_code == 404) {
strcpy(response, "NOT FOUND");
}
else if (thread->status_code == 500) {
strcpy(response, "INTERNAL SERVER ERROR");
}
else {
printf("Bad response...\n");
return;
}
dprintf(thread->client_sockd, "%s %d %s\r\nContent-Length: %ld\r\n\r\n", thread->httpversion, thread->status_code, response, thread->content_length);
if (strcmp(thread->method, "GET") == 0 && thread->status_code == 200) {
int fd = open(thread->filename, O_RDONLY);
ssize_t total = 0,
len_track = thread->content_length,
bytes_recv,
bytes_send;
while (thread->content_length != 0) {
bytes_recv = read(fd, thread->buffer, BUFFER_SIZE);
bytes_send = send(thread->client_sockd, thread->buffer, bytes_recv, 0);
if (bytes_recv != bytes_send) {
thread->status_code = 500;
close(fd);
printf("Recieved != sent for GET request\nReceived:%ld\nSent:%ld\n", bytes_recv, bytes_send);
dprintf(thread->client_sockd, "%s %d %s\r\nContent-Length: %ld\r\n\r\n", thread->httpversion, thread->status_code, response, thread->content_length);
close(fd);
return;
}
total += bytes_send;
thread->content_length -= bytes_recv;
}
if (total != len_track) {
thread->status_code = 403;
printf("Content length != recvd for GET request\n");
dprintf(thread->client_sockd, "%s %d %s\r\nContent-Length: %ld\r\n\r\n", thread->httpversion, thread->status_code, response, thread->content_length);
close(fd);
return;
}
close(fd);
}
}
void *
handle_connections(void *ptr_thread)
{
// create a mutual exclusion to lock out any other threads from the function
// pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// pthread_mutex_lock(&mutex);
// operations go here
struct threadObject *thread = (struct threadObject *) ptr_thread;
// reset message after each loop
memset(thread->buffer, '\0', BUFFER_SIZE);
memset(thread->method, '\0', 5);
memset(thread->filename, '\0', 28);
memset(thread->httpversion, '\0', 9);
thread->content_length = 0;
thread->status_code = 0;
memset(thread->rest_of_PUT, '\0', BUFFER_SIZE);
// read message
if (read_http_response(thread) == true) {
// process message
process_request(thread);
}
// construct a response
construct_http_response(thread);
// unlock the function
// pthread_mutex_unlock(&mutex);
#if 1
close(thread->client_sockd);
pthread_mutex_lock(&global_mutex);
thread->tsk_done = 1;
pthread_mutex_unlock(&global_mutex);
#endif
return NULL;
}
int
main(int argc, char **argv)
{
// Create sockaddr_in with server information
if (argc < 2) {
perror("No arguments passed\n");
return -1;
}
// make sure port number is above 1024 and set the port # to it
if (atoi(argv[1]) < 1024) {
return 1;
}
char *port = argv[1];
// parse the command line args for options -l and -N. -l specifies it will use a log and the following parameter is the filename. -N specifies the number of threads it will use and the following parameter will be a number
int opt;
uint8_t threadNum = 1;
char *logName = NULL;
while ((opt = getopt(argc - 1, argv + 1, "N:l:")) != -1) {
if (opt == 'N') {
threadNum = atoi(optarg);
}
else if (opt == 'l') {
logName = optarg;
}
}
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(port));
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
socklen_t addrlen = sizeof(server_addr);
// Create server socket
int server_sockd = socket(AF_INET, SOCK_STREAM, 0);
// Need to check if server_sockd < 0, meaning an error
if (server_sockd < 0) {
perror("socket");
return 1;
}
// Configure server socket
int enable = 1;
// This allows you to avoid: 'Bind: Address Already in Use' error
int ret = setsockopt(server_sockd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
if (ret < 0) {
return EXIT_FAILURE;
}
// Bind server address to socket that is open
ret = bind(server_sockd, (struct sockaddr *) &server_addr, addrlen);
if (ret < 0) {
return EXIT_FAILURE;
}
// Listen for incoming connections
ret = listen(server_sockd, 5); // 5 should be enough, if not use SOMAXCONN
if (ret < 0) {
return EXIT_FAILURE;
}
struct threadObject thread[threadNum];
// Connecting with a client
struct sockaddr client_addr;
socklen_t client_addrlen = sizeof(client_addr);
// create a pthread array of size (number of threads). specify this will be using the handle connections function. join the threads together
#if 0
pthread_t thread_id[threadNum];
#endif
#if 1
struct threadObject *tsk = NULL;
int tskidx;
// clear out the thread structs
for (tskidx = 0; tskidx < threadNum; tskidx++) {
tsk = &thread[tskidx];
memset(tsk,0,sizeof(struct threadObject));
}
while (true) {
// accept connection
int client_sockd = accept(server_sockd, &client_addr, &client_addrlen);
pthread_mutex_lock(&global_mutex);
// join any previously completed threads
for (tskidx = 0; tskidx < threadNum; tskidx++) {
tsk = &thread[tskidx];
if (tsk->tsk_done) {
pthread_join(tsk->tsk_threadid,NULL);
tsk->tsk_inuse = 0;
tsk->tsk_done = 0;
}
}
// find unused task slot
for (tskidx = 0; tskidx < threadNum; tskidx++) {
tsk = &thread[tskidx];
if (! tsk->tsk_inuse)
break;
}
memset(tsk,0,sizeof(struct threadObject));
tsk->client_sockd = client_sockd;
tsk->tsk_inuse = 1;
pthread_mutex_unlock(&global_mutex);
// fire in the hole ...
pthread_create(&tsk->tsk_threadid, NULL, handle_connections, tsk);
}
#endif
#if 0
for (int i = 0; i < threadNum; i++) {
printf("\n[+] server is waiting...\n");
thread[i].client_sockd = accept(server_sockd, &client_addr, &client_addrlen);
handle_connections(&thread[i]);
// pthread_create(&thread_id[i], NULL, handle_connections, &thread[i]);
printf("Response Sent\n");
// close the current client socket
close(thread[i].client_sockd);
}
}
#endif
return EXIT_SUCCESS;
}
I am writing a small http proxy server(in C) on a linux machine, Ubuntu 18.04.1 to be specific, and I've been trying to find a way to get the pid of the process that is connecting to it.
It might be of use to mention that the proxy is intended to proxy connections only for processes running on the same machine, so I guess this should make this task possible.
The server uses AF_INET family sockets along with read/write operations in order to do it's job; I am mentioning this because after some research I did encounter threads about "ancillary data",for example: Is there a way to get the uid of the other end of a unix socket connection
Ancillary data contain credentials of the connecting socket(such as PID), but only work on AF_UNIX sockets, used for local IPC, and requires us to explicitly send/receive it on both sides(client/server). In my case, although, as I mentioned, the server will only proxy traffic on the same machine as the server, I need to use AF_INET sockets, so everyone(e.g. web browser) is able to connect to it.
Performance is not so critical; so any suggestions(including workarounds using system calls etc.) are very welcome.
We can use netstat -nptW output to see which local processes' TCP connections. As the output may be security sensitive, superuser privileges are required to see processes belonging to all users.
Since there is no reason to run a proxy service with elevated privileges (expect perhaps CAP_NET_BIND_SERVICE), a privileged helper program is needed.
I pondered a suitable security model for a bit, and came to the conclusion that a helper which examines the connected socket given to it (as say standard input), and outputs just the peer PID(s), would be safest: it would be extremely hard to misuse it, and even if possible, only the peer process ID is revealed.
Here is the example helper, tcp-peer-pids.c:
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#define EXITCODE_OK 0
#define EXITCODE_STDIN_INVALID 1
#define EXITCODE_UNKNOWN_ADDRESS 2
#define EXITCODE_NETSTAT 3
#define EXITCODE_NETSTAT_OUTPUT 4
#define EXITCODE_WRITE_ERROR 5
#define EXITCODE_PRIVILEGES 6
static pid_t *pids = NULL;
static size_t num_pids = 0;
static size_t max_pids = 0;
static int add_pid(const pid_t p)
{
size_t i;
/* Check if already listed. */
for (i = 0; i < num_pids; i++)
if (pids[i] == p)
return 0;
/* Ensure enough room in pids array. */
if (num_pids >= max_pids) {
const size_t max_temp = (num_pids | 1023) + 1025 - 8;
pid_t *temp;
temp = realloc(pids, max_temp * sizeof pids[0]);
if (!temp)
return ENOMEM;
pids = temp;
max_pids = max_temp;
}
pids[num_pids++] = p;
return 0;
}
int main(void)
{
struct sockaddr_storage sock_addr;
socklen_t sock_addrlen = sizeof sock_addr;
char sock_match[128], sock_host[64], sock_port[32];
struct sockaddr_storage peer_addr;
socklen_t peer_addrlen = sizeof peer_addr;
char peer_match[128], peer_host[64], peer_port[32];
FILE *cmd;
char *line = NULL;
size_t size = 0;
ssize_t len;
int status;
/* Socket address is *remote*, and peer address is *local*.
This is because the variables are named after their matching netstat lines. */
if (getsockname(STDIN_FILENO, (struct sockaddr *)&sock_addr, &sock_addrlen) == -1) {
fprintf(stderr, "Standard input is not a valid socket.\n");
exit(EXITCODE_STDIN_INVALID);
}
if (getpeername(STDIN_FILENO, (struct sockaddr *)&peer_addr, &peer_addrlen) == -1) {
fprintf(stderr, "Standard input is not a connected socket.\n");
exit(EXITCODE_STDIN_INVALID);
}
if ((sock_addr.ss_family != AF_INET && sock_addr.ss_family != AF_INET6) ||
(peer_addr.ss_family != AF_INET && peer_addr.ss_family != AF_INET6)) {
fprintf(stderr, "Standard input is not an IP socket.\n");
exit(EXITCODE_STDIN_INVALID);
}
/* For security, we close the standard input descriptor, */
close(STDIN_FILENO);
/* and redirect it from /dev/null, if possible. */
{
int fd = open("/dev/null", O_RDONLY);
if (fd != -1 && fd != STDIN_FILENO) {
dup2(fd, STDIN_FILENO);
close(fd);
}
}
/* Convert sockets to numerical host and port strings. */
if (getnameinfo((const struct sockaddr *)&sock_addr, sock_addrlen,
sock_host, sizeof sock_host, sock_port, sizeof sock_port,
NI_NUMERICHOST | NI_NUMERICSERV)) {
fprintf(stderr, "Unknown socket address.\n");
exit(EXITCODE_UNKNOWN_ADDRESS);
}
if (getnameinfo((const struct sockaddr *)&peer_addr, peer_addrlen,
peer_host, sizeof peer_host, peer_port, sizeof peer_port,
NI_NUMERICHOST | NI_NUMERICSERV)) {
fprintf(stderr, "Unknown peer address.\n");
exit(EXITCODE_UNKNOWN_ADDRESS);
}
/* Combine to the host:port format netstat uses. */
snprintf(sock_match, sizeof sock_match, "%s:%s", sock_host, sock_port);
snprintf(peer_match, sizeof peer_match, "%s:%s", peer_host, peer_port);
/* Switch to privileged user, if installed as setuid. */
{
uid_t real_uid = getuid();
gid_t real_gid = getgid();
uid_t effective_uid = geteuid();
gid_t effective_gid = getegid();
if (real_gid != effective_gid || real_uid != effective_uid) {
/* SetUID or SetGID in effect. Switch privileges. */
if (setresgid(effective_gid, effective_gid, effective_gid) == -1 ||
setresuid(effective_uid, effective_uid, effective_uid) == -1) {
fprintf(stderr, "Error in privileges: %s.\n", strerror(errno));
exit(EXITCODE_PRIVILEGES);
}
}
}
/* Run netstat to obtain the data; redirect standard error to standard output. */
cmd = popen("LANG=C LC_ALL=C /bin/netstat -nptW 2>&1", "r");
if (!cmd) {
fprintf(stderr, "Cannot run netstat.\n");
exit(EXITCODE_NETSTAT);
}
/* Input line loop. */
while (1) {
char *field[8], *ends;
long val;
pid_t p;
len = getline(&line, &size, cmd);
if (len < 1)
break;
/* Split each line into fields. */
field[0] = strtok(line, "\t\n\v\f\r "); /* Protocol */
/* We are only interested in tcp ("tcp" and "tcp6" protocols). */
if (strcmp(field[0], "tcp") && strcmp(field[0], "tcp6"))
continue;
field[1] = strtok(NULL, "\t\n\v\f\r "); /* Recv-Q */
field[2] = strtok(NULL, "\t\n\v\f\r "); /* Send-Q */
field[3] = strtok(NULL, "\t\n\v\f\r "); /* Local address (peer) */
field[4] = strtok(NULL, "\t\n\v\f\r "); /* Remote address (sock) */
field[5] = strtok(NULL, "\t\n\v\f\r "); /* State */
field[6] = strtok(NULL, "\t\n\v\f\r /"); /* PID */
field[7] = strtok(NULL, "\t\n\v\f\r "); /* Process name */
/* Local address must match peer_match, and foreign/remote sock_match. */
if (strcmp(field[3], peer_match) || strcmp(field[4], sock_match))
continue;
/* This line corresponds to the process we are looking for. */
/* Missing PID field is an error at this point. */
if (!field[6])
break;
/* Parse the PID. Parsing errors are fatal. */
ends = field[6];
errno = 0;
val = strtol(field[6], &ends, 10);
if (errno || ends == field[6] || *ends != '\0' || val < 1)
break;
p = (pid_t)val;
if ((long)p != val)
break;
/* Add the pid to the known pids list. */
if (add_pid(p))
break;
}
/* The line buffer is no longer needed. */
free(line);
/* I/O error? */
if (!feof(cmd) || ferror(cmd)) {
fprintf(stderr, "Error reading netstat output.\n");
exit(EXITCODE_NETSTAT_OUTPUT);
}
/* Reap the netstat process. */
status = pclose(cmd);
if (status == -1) {
fprintf(stderr, "Error reading netstat output: %s.\n", strerror(errno));
exit(EXITCODE_NETSTAT_OUTPUT);
}
if (!WIFEXITED(status)) {
fprintf(stderr, "Netstat died unexpectedly.\n");
exit(EXITCODE_NETSTAT_OUTPUT);
}
if (WEXITSTATUS(status)) {
fprintf(stderr, "Netstat failed with exit status %d.\n", WEXITSTATUS(status));
exit(EXITCODE_NETSTAT_OUTPUT);
}
/* Output the array of pids as binary data. */
if (num_pids > 0) {
const char *head = (const char *)pids;
const char *const ends = (const char *)(pids + num_pids);
ssize_t n;
while (head < ends) {
n = write(STDOUT_FILENO, head, (size_t)(ends - head));
if (n > 0)
head += n;
else
if (n != -1)
exit(EXITCODE_WRITE_ERROR);
else
if (errno != EINTR)
exit(EXITCODE_WRITE_ERROR);
}
}
/* Discard the pids array. */
free(pids);
exit(EXITCODE_OK);
}
It can be run using ordinary user privileges (in which case it'll only know about processes owned by that user), root privileges, or as setuid root.
If used with sudo, ensure you use rule proxyuser ALL = NOPASSWD: /path/to/helper, because sudo has no way of asking a password there. I would probably just install the helper as setuid root at /usr/lib/yourproxy/tcp-peer-pid, owner root, group your proxy service group, and no access to other users (root:proxygroup -r-sr-x---).
The helper is tightly coupled to netstat -nptW output format, but does explicitly set the C locale to avoid getting localized output.
The comparison address:port strings to match to "Local Address" and "Foreign Address" in netstat output are constructed from the addresses returned by getpeername() and getsockname(), respectively, using [getnameinfo()(http://man7.org/linux/man-pages/man3/getnameinfo.3.html) in numerical form (using NI_NUMERICHOST | NI_NUMERICSERV flags).
The helper provides the PIDs in binary form to the server, because the server code would have been too long to fit in a single post here otherwise.
Here is an example TCP service, server.c, which uses the above helper to find out the PID of the peer end of the socket on the local computer. (To avoid denial-of-service attacks, you should set an IP filter that rejects accesses to your proxy service port from outside the computer.)
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <netdb.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#ifndef HELPER_PATH
#define HELPER_PATH "./tcp-peer-pids"
#endif
#ifndef HELPER_NAME
#define HELPER_NAME "tcp-peer-pids"
#endif
#ifndef SUDO_PATH
#define SUDO_PATH "/usr/bin/sudo"
#endif
#ifndef SUDO_NAME
#define SUDO_NAME "sudo"
#endif
/*
* Signal handler, to detect INT (Ctrl+C), HUP, and TERM signals.
*/
static volatile sig_atomic_t done = 0;
static void handle_done(int signum)
{
/* In Linux, all signals have signum > 0. */
__atomic_store_n(&done, (sig_atomic_t)signum, __ATOMIC_SEQ_CST);
}
static int install_done(int signum)
{
struct sigaction act;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
act.sa_flags = SA_RESTART; /* Do not interrupt slow syscalls. */
act.sa_handler = handle_done;
if (sigaction(signum, &act, NULL) == -1)
return -1; /* errno set by getpeername() */
return 0;
}
/* Helper function: Move descriptors away from STDIN/STDOUT/STDERR.
Returns 0 if successful, -1 with errno set if an error occurs. */
static inline int normalfds(int fd[], const size_t n)
{
unsigned int closemask = 0;
int err = 0;
size_t i;
int newfd;
for (i = 0; i < n; i++)
while (fd[i] == STDIN_FILENO || fd[i] == STDOUT_FILENO || fd[i] == STDERR_FILENO) {
newfd = dup(fd[i]);
if (newfd == -1) {
err = errno;
break;
}
closemask |= 1u << fd[i];
fd[i] = newfd;
}
/* Close temporary descriptors. */
if (closemask & (1u << STDIN_FILENO)) close(STDIN_FILENO);
if (closemask & (1u << STDOUT_FILENO)) close(STDOUT_FILENO);
if (closemask & (1u << STDERR_FILENO)) close(STDERR_FILENO);
/* Success? */
if (!err)
return 0;
/* Report error. */
errno = err;
return -1;
}
/* Return the number of peer processes.
If an error occurs, returns zero; examine errno. */
size_t peer_pids(const int connfd, pid_t *const pids, size_t maxpids)
{
char *in_data = NULL;
size_t in_size = 0;
size_t in_used = 0;
size_t n;
int binpipe[2], status;
pid_t child, p;
/* Sanity check. */
if (connfd == -1) {
errno = EBADF;
return 0;
}
/* Create a pipe to transfer the PIDs (in binary). */
if (pipe(binpipe) == -1)
return 0; /* errno set by pipe(). */
/* Make sure the binary pipe descriptors do not conflict with standard descriptors. */
if (normalfds(binpipe, 2) == -1) {
const int saved_errno = errno;
close(binpipe[0]);
close(binpipe[1]);
errno = saved_errno;
return 0;
}
/* Fork a child process. */
child = fork();
if (child == -1) {
const int saved_errno = errno;
close(binpipe[0]);
close(binpipe[1]);
errno = saved_errno;
return 0;
}
if (!child) {
/* This is the child process. */
#ifdef USE_SUDO
const char *cmd_path = SUDO_PATH;
char *const cmd_args[3] = { SUDO_NAME, HELPER_PATH, NULL };
#else
const char *cmd_path = HELPER_PATH;
char *const cmd_args[2] = { HELPER_NAME, NULL };
#endif
/* The child runs in its own process group, for easier management. */
setsid();
/* Close read end of pipe. */
close(binpipe[0]);
/* Move established connection to standard input. */
if (connfd != STDIN_FILENO) {
if (dup2(connfd, STDIN_FILENO) != STDIN_FILENO)
_Exit(99);
close(connfd);
}
/* Move write end of pipe to standard output. */
if (dup2(binpipe[1], STDOUT_FILENO) != STDOUT_FILENO)
_Exit(99);
else
close(binpipe[1]);
/* Execute helper. */
execv(cmd_path, cmd_args);
/* Failed to execute helper. */
_Exit(98);
}
/* Parent process. */
/* Close write end of pipe, so we detect when child exits. */
close(binpipe[1]);
/* Read all output from child. */
status = 0;
while (1) {
ssize_t bytes;
if (in_used >= in_size) {
const size_t size = (in_used | 1023) + 1025 - 8;
char *temp;
temp = realloc(in_data, in_size);
if (!temp) {
status = ENOMEM;
break;
}
in_data = temp;
in_size = size;
}
bytes = read(binpipe[0], in_data + in_used, in_size - in_used);
if (bytes > 0) {
in_used += bytes;
} else
if (bytes == 0) {
/* End of input condition. */
break;
} else
if (bytes != -1) {
status = EIO;
break;
} else
if (errno != EINTR) {
status = errno;
break;
}
}
/* Close the pipe. */
close(binpipe[0]);
/* Abort, if an error occurred. */
if (status) {
free(in_data);
kill(-child, SIGKILL);
do {
p = waitpid(child, NULL, 0);
} while (p == -1 && errno == EINTR);
errno = status;
return 0;
}
/* Reap the child process. */
do {
status = 0;
p = waitpid(child, &status, 0);
} while (p == -1 && errno == EINTR);
if (p == -1) {
const int saved_errno = errno;
free(in_data);
errno = saved_errno;
return 0;
}
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
free(in_data);
errno = ESRCH; /* The helper command failed, really. */
return 0;
}
/* We expect an integer number of pid_t's. Check. */
n = in_used / sizeof (pid_t);
if ((in_used % sizeof (pid_t)) != 0) {
free(in_data);
errno = EIO;
return 0;
}
/* None found? */
if (!n) {
free(in_data);
errno = ENOENT; /* Not found, really. */
return 0;
}
/* Be paranoid, and verify the pids look sane. */
{
const pid_t *const pid = (const pid_t *const)in_data;
size_t i;
for (i = 0; i < n; i++)
if (pid[i] < 2) {
free(in_data);
errno = ESRCH; /* Helper failed */
return 0;
}
}
/* Copy to user buffer, if specified. */
if (maxpids > n)
memcpy(pids, in_data, n * sizeof (pid_t));
else
if (maxpids > 0)
memcpy(pids, in_data, maxpids * sizeof (pid_t));
/* The pid buffer is no longer needed. */
free(in_data);
/* Return the number of pids we actually received. */
return n;
}
int main(int argc, char *argv[])
{
struct addrinfo hints, *list, *curr;
const char *node, *serv;
int service_fd, err;
struct sockaddr_storage client_addr;
socklen_t client_addrlen;
int client_fd;
if (argc != 3) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s HOST PORT\n", argv[0]);
fprintf(stderr, "\n");
return EXIT_FAILURE;
}
/* Install signal handers for Ctrl+C, HUP, and TERM. */
if (install_done(SIGINT) ||
install_done(SIGHUP) ||
install_done(SIGTERM)) {
fprintf(stderr, "Cannot install signal handlers: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
/* Empty or - or * is a wildcard host. */
if (argv[1][0] == '\0' || !strcmp(argv[1], "-") || !strcmp(argv[1], "*"))
node = NULL;
else
node = argv[1];
serv = argv[2];
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; /* IPv4 or IPv6 */
hints.ai_socktype = SOCK_STREAM; /* TCP */
hints.ai_flags = AI_PASSIVE;
hints.ai_protocol = 0;
hints.ai_canonname = NULL;
hints.ai_addr = NULL;
hints.ai_next = NULL;
list = NULL;
err = getaddrinfo(node, serv, &hints, &list);
if (err) {
fprintf(stderr, "Invalid host and/or port: %s.\n", gai_strerror(err));
return EXIT_FAILURE;
}
service_fd = -1;
err = 0;
for (curr = list; curr != NULL; curr = curr->ai_next) {
service_fd = socket(curr->ai_family, curr->ai_socktype, curr->ai_protocol);
if (service_fd == -1)
continue;
errno = 0;
if (bind(service_fd, curr->ai_addr, curr->ai_addrlen) == -1) {
if (!err)
if (errno == EADDRINUSE || errno == EADDRNOTAVAIL || errno == EACCES)
err = errno;
close(service_fd);
service_fd = -1;
continue;
}
if (listen(service_fd, 5) == -1) {
if (!err)
if (errno == EADDRINUSE)
err = errno;
close(service_fd);
service_fd = -1;
continue;
}
/* This socket works. */
break;
}
freeaddrinfo(list);
list = curr = NULL;
if (service_fd == -1) {
if (err)
fprintf(stderr, "Cannot listen for incoming connections on the specified host and port: %s.\n", strerror(err));
else
fprintf(stderr, "Cannot listen for incoming connections on the specified host and port.\n");
return EXIT_FAILURE;
}
/* Do not leak the listening socket to child processes. */
fcntl(service_fd, F_SETFD, FD_CLOEXEC);
/* We also want the listening socket to be nonblocking. */
fcntl(service_fd, F_SETFL, O_NONBLOCK);
fprintf(stderr, "Process %ld is waiting for incoming TCP connections.\n", (long)getpid());
/* Incoming connection loop. */
while (!done) {
struct timeval t;
char client_host[64]; /* 64 for numeric, 1024 for non-numeric */
char client_port[32];
pid_t client_pid;
fd_set fds;
t.tv_sec = 0;
t.tv_usec = 100000; /* Max. 0.1s delay to react to done signal. */
FD_ZERO(&fds);
FD_SET(service_fd, &fds);
if (select(service_fd + 1, &fds, NULL, NULL, &t) < 1)
continue;
client_addrlen = sizeof client_addr;
client_fd = accept(service_fd, (struct sockaddr *)&client_addr, &client_addrlen);
if (client_fd == -1) {
if (errno == EINTR || errno == ECONNABORTED)
continue;
fprintf(stderr, "Error accepting an incoming connection: %s.\n", strerror(errno));
continue;
}
if (getnameinfo((const struct sockaddr *)&client_addr, client_addrlen,
client_host, sizeof client_host, client_port, sizeof client_port,
NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
fprintf(stderr, "Cannot resolve peer address for incoming connection, so dropping it.\n");
close(client_fd);
continue;
}
printf("Incoming connection from %s:%s", client_host, client_port);
fflush(stdout);
if (peer_pids(client_fd, &client_pid, 1) != 1) {
printf(", but cannot determine process ID. Dropped.\n");
close(client_fd);
continue;
}
printf(" from local process %ld.\n", (long)client_pid);
fflush(stdout);
/*
* Handle connection.
*/
printf("Closing connection.\n");
fflush(stdout);
close(client_fd);
}
/* Close service socket. */
close(service_fd);
switch (__atomic_load_n(&done, __ATOMIC_SEQ_CST)) {
case SIGINT:
fprintf(stderr, "Received INT signal.\n");
break;
case SIGHUP:
fprintf(stderr, "Received HUP signal.\n");
break;
case SIGTERM:
fprintf(stderr, "Received TERM signal.\n");
break;
}
return EXIT_SUCCESS;
}
The peer_pids() function communicates with the helper process. It is very straightforward, albeit careful to not return unreliable data: instead of ignoring errors or trying to recover from them, it reports failure. This allows the main program do if (peer_pids(client_fd, &pid, 1) != 1) /* Don't know! */ and drop any connection the server is unsure of -- an approach I consider the sane one here.
The normalfds() helper function is often ignored. It helps avoid issues if any of the standard streams are/get closed. It simply moves the set of descriptors away from the three standard streams, using at most three extra descriptors.
You can define USE_SUDO at compile time to have it use sudo when executing the helper. Define HELPER_PATH and HELPER_NAME to the absolute path to the helper and its file name, respectively. (As it is now, they default to ./tcp-peer-pid and tcp-peer-pid, for easier testing.)
The server does install a signal handler for INT (Ctrl+C), HUP (sent when the user closes the terminal), or TERM signals, which all cause it to stop accepting new connections and exit in a controlled manner. (Because the signal handler is installed using SA_RESTART flag, its delivery will not interrupt slow syscalls or cause errno == EINTR. This also means that accept() should not block, or the signal delivery will not be noticed. So, blocking in select() for 0.1s, and checking if a signal was delivered in between, is a good compromise, at least in an example server.)
On my machine, I compiled and tested the service in one terminal window using
gcc -Wall -O2 tcp-peer-pids.c -o tcp-peer-pids
gcc -Wall -O2 "-DHELPER_PATH=\"$PWD/tcp-peer-pids\"" server.c -o server
./server - 2400
That will report Process # is waiting for incoming TCP connections. In another window, using Bash or POSIX shell, I run one or more test netcat commands:
nc localhost 2400 & wait
It might look silly to run a command in the background, and immediately wait for it, but that way you can see the PID of the nc process.
On my system, all loopback (127.x.y.z), TCP/IPv4, and TCP/IPv6 (the addresses of my ethernet and WiFi interfaces) worked fine, and reliably reported the correct PID of the process connecting to the example server.
There are a number of cases where the number of PIDs reported might vary: For example, if the program has executed a child process, but left the connected descriptor open in the child as well. (This should be considered a bug.) Another typical case is the program having exited before the netstat command executes.
If you find any typos or errors or strange behaviour, let me know in a comment so I can verify and fix. I wrote both programs in one sitting, so they are quite likely to contain bugs. As I mentioned, I would not trust either in production before having a colleague (or myself a few times, later on, with fresh eyes) going through it with a critical/paranoid eye.
I would personally only use this approach for logging and statistics, not access control per se. By access control, I mean that you should configure an IP filter (the firewall built in to the Linux kernel) to limit access to only trusted hosts; and specifically allow no incoming proxy connections to the proxy service if only local applications are to be proxied, rather than rely on this detecting all remote connections.
For application-specific logging/limiting, use readlink() on the /proc/PID/exe pseudosymlink. This cannot be faked, but the call may fail if the executable is not accessible, or is too deep in the directory tree. (In those cases I'd reject the proxy connection altogether.)
Note that it is usually trivial for an user to copy an executable to any directory they own, and execute it from there. This means that for application-specific limiting to work at all, you should have tight limits for all applications by default, and relax the limits for specific executables.
I have a little doubt with an excercise that i have found to train my ability with sockets.
The exercise says:
Write pseudocode of a tcp server based on fork()
Constraints:
- Max 20000 simultaneusly active connections; after this limit new connection are dropped
- At most 1000 request per client(ip) per hour
I've sketched a solution and i want to know if's it's a good way to go:
struct client{
int ip;
timestamp to;
int n_req;
client* next;
}
void serve(int c_fd, int ip, client* list){
client* c = find_in_list(list, ip);
timestamp now = gettimeofday();
if(now.tv_sec - c->to.tv_sec > (60*60)){
// ig one hour is passed is possible to reset counter
c->n_req = 0;
c->to = now;
}
if(c->n_req > 1000){
/*do_nothing */
} else {
n_req++;
/*
do stuff
*/
}
exit();
}
int main(){
client* list = NULL;
a_fd = socket(AF_INET);
bind(a_fd);
listen(a_fd);
while(1){
/*inizilize poll*/
n_ready = poll();
if(n_ready > 0){
for(/*each ready file descriptor*/){
c_fd = accept(a_fd, this_sockaddr);
if(/* if the ip in this_sockaddr is new*/){
client* new = /*create neew client */;
add_list(list, new);
}
if(served <= 20000){
served++;
pid = fork();
if(pid == 0 ){ //CHILD
serve(c_fd, ip, list);
close(c_fd)
} else { //FATHER
close(c_fd);
do{
pid = blocking_wait();
served--;
} while(pid != 0)
}
} else {
close(c_fd);
}
}
}
}
}
Thanks for any advice.
If you want to see a simple, yet fully effective example of a server using fork, I would recommend you review this simple little project:
ftp://ftp.cs.umass.edu/pub/net/pub/kurose/ftpserver.c
if (listen(sockid,5) < 0)
{ printf("server: listen error :%d\n",errno);exit(0);}
while(1==1) {
/* ACCEPT A CONNECTION AND THEN CREATE A CHILD TO DO THE WORK */
/* LOOP BACK AND WAIT FOR ANOTHER CONNECTION */
printf("server: starting accept\n");
if ((newsd = accept(sockid ,(struct sockaddr *) &client_addr,
&clilen)) < 0)
{printf("server: accept error :%d\n", errno); exit(0); }
printf("server: return from accept, socket for this ftp: %d\n",
newsd);
if ( (pid=fork()) == 0) {
/* CHILD PROC STARTS HERE. IT WILL DO ACTUAL FILE TRANSFER */
close(sockid); /* child shouldn't do an accept */
doftp(newsd);
close (newsd);
exit(0); /* child all done with work */
}
/* PARENT CONTINUES BELOW HERE */
close(newsd); /* parent all done with client, only child */
} /* will communicate with that client from now on */
Child fork looks like this:
/* CHILD PROCEDURE, WHICH ACTUALLY DOES THE FILE TRANSFER */
doftp(int newsd)
{
int i,fsize,fd,msg_ok,fail,fail1,req,c,ack;
int no_read ,num_blks , num_blks1,num_last_blk,num_last_blk1,tmp;
char fname[MAXLINE];
char out_buf[MAXSIZE];
FILE *fp;
no_read = 0;
num_blks = 0;
num_last_blk = 0;
/* START SERVICING THE CLIENT */
/* get command code from client.*/
/* only one supported command: 100 - get a file */
req = 0;
if((readn(newsd,(char *)&req,sizeof(req))) < 0)
{printf("server: read error %d\n",errno);exit(0);}
req = ntohs(req);
...
I have two nodes communicating with a socket. Each node has a read thread and a write thread to communicate with the other. Given below is the code for the read thread. The communication works fine between the two nodes with that code. But I am trying to add a select function in this thread and that is giving me problems (the code for select is in the comments. I just uncomment it to add the functionality). The problem is one node does not receive messages and only does the timeout. The other node gets the messages from the other node but never timesout. That problem is not there (both nodes send and receive messages) without the select (keeping the comments /* */).
Can anyone point out what the problem might be? Thanks.
void *Read_Thread(void *arg_passed)
{
int numbytes;
unsigned char *buf;
buf = (unsigned char *)malloc(MAXDATASIZE);
/*
fd_set master;
int fdmax;
FD_ZERO(&master);
*/
struct RWThread_args_template *my_args = (struct RWThread_args_template *)arg_passed;
/*
FD_SET(my_args->new_fd, &master);
struct timeval tv;
tv.tv_sec = 2;
tv.tv_usec = 0;
int s_rv = 0;
fdmax = my_args->new_fd;
*/
while(1)
{
/*
s_rv = -1;
if((s_rv = select(fdmax+1, &master, NULL, NULL, &tv)) == -1)
{
perror("select");
exit(1);
}
if(s_rv == 0)
{
printf("Read: Timed out\n");
continue;
}
else
{
printf("Read: Received msg\n");
}
*/
if( (numbytes = recv(my_args->new_fd, buf, MAXDATASIZE-1, 0)) == -1 )
{
perror("recv");
exit(1);
}
buf[numbytes] = '\0';
printf("Read: received '%s'\n", buf);
}
pthread_exit(NULL);
}
You must set up master and tv before each call to select(), within the loop. They are both modified by the select() call.
In particular, if select() returned 0, then master will now be empty.