Socket handle transfer between independent processes - c

I am doing some experiments in socket programming(in unix environment). What I am trying is
Client sends request to Server.
Server should send the clients socket to Worker(An Independent process)
Worker should reply back to Client.
Is this possible?
This scenario works if Worker is a child of Server.
If Server and Worker are independent processes does this work?
If yes can somebody give me some ideas about this ?
Is there any samples available for this type of scenario ?

The Linux Programming Interface book has examples for both sending and receiving file descriptors between unrelated processes, using an Unix domain socket.
For fun, I wrote my own examples from scratch. server.c:
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netdb.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
/* How many concurrent pending connections are allowed */
#define LISTEN_BACKLOG 32
/* Unix domain socket path length (including NUL byte) */
#ifndef UNIX_PATH_LEN
#define UNIX_PATH_LEN 108
#endif
/* Flag to indicate we have received a shutdown request. */
volatile sig_atomic_t done = 0;
/* Shutdown request signal handler, of the basic type. */
void handle_done_signal(int signum)
{
if (!done)
done = signum;
return;
}
/* Install shutdown request signal handler on signal signum. */
int set_done_signal(const int signum)
{
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_handler = handle_done_signal;
act.sa_flags = 0;
if (sigaction(signum, &act, NULL) == -1)
return errno;
else
return 0;
}
/* Return empty, -, and * as NULL, so users can use that
* to bind the server to the wildcard address.
*/
char *wildcard(char *address)
{
/* NULL? */
if (!address)
return NULL;
/* Empty? */
if (!address[0])
return NULL;
/* - or ? or * or : */
if (address[0] == '-' || address[0] == '?' ||
address[0] == '*' || address[0] == ':')
return NULL;
return address;
}
int main(int argc, char *argv[])
{
struct addrinfo hints;
struct addrinfo *list, *curr;
int listenfd, failure;
struct sockaddr_un worker;
int workerfd, workerpathlen;
struct sockaddr_in6 conn;
socklen_t connlen;
struct msghdr connhdr;
struct iovec conniov;
struct cmsghdr *connmsg;
char conndata[1];
char connbuf[CMSG_SPACE(sizeof (int))];
int connfd;
int result;
ssize_t written;
if (argc != 4) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s ADDRESS PORT WORKER\n", argv[0]);
fprintf(stderr, "This creates a server that binds to ADDRESS and PORT,\n");
fprintf(stderr, "and passes each connection to a separate unrelated\n");
fprintf(stderr, "process using an Unix domain socket at WORKER.\n");
fprintf(stderr, "\n");
return (argc == 1) ? 0 : 1;
}
/* Handle HUP, INT, PIPE, and TERM signals,
* so when the user presses Ctrl-C, the worker process cannot be contacted,
* or the user sends a HUP or TERM signal, this server closes down cleanly. */
if (set_done_signal(SIGINT) ||
set_done_signal(SIGHUP) ||
set_done_signal(SIGPIPE) ||
set_done_signal(SIGTERM)) {
fprintf(stderr, "Error: Cannot install signal handlers.\n");
return 1;
}
/* Unix domain socket to the worker */
memset(&worker, 0, sizeof worker);
worker.sun_family = AF_UNIX;
workerpathlen = strlen(argv[3]);
if (workerpathlen < 1) {
fprintf(stderr, "Worker Unix domain socket path cannot be empty.\n");
return 1;
} else
if (workerpathlen >= UNIX_PATH_LEN) {
fprintf(stderr, "%s: Worker Unix domain socket path is too long.\n", argv[3]);
return 1;
}
memcpy(&worker.sun_path, argv[3], workerpathlen);
/* Note: the terminating NUL byte was set by memset(&worker, 0, sizeof worker) above. */
workerfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (workerfd == -1) {
fprintf(stderr, "Cannot create an Unix domain socket: %s.\n", strerror(errno));
return 1;
}
if (connect(workerfd, (const struct sockaddr *)(&worker), (socklen_t)sizeof worker) == -1) {
fprintf(stderr, "Cannot connect to %s: %s.\n", argv[3], strerror(errno));
close(workerfd);
return 1;
}
/* Initialize the address info hints */
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; /* IPv4 or IPv6 */
hints.ai_socktype = SOCK_STREAM; /* Stream socket */
hints.ai_flags = AI_PASSIVE /* Wildcard ADDRESS */
| AI_ADDRCONFIG /* Only return IPv4/IPv6 if available locally */
| AI_NUMERICSERV /* Port must be a number */
;
hints.ai_protocol = 0; /* Any protocol */
/* Obtain the chain of possible addresses and ports to bind to */
result = getaddrinfo(wildcard(argv[1]), argv[2], &hints, &list);
if (result) {
fprintf(stderr, "%s %s: %s.\n", argv[1], argv[2], gai_strerror(result));
close(workerfd);
return 1;
}
/* Bind to the first working entry in the chain */
listenfd = -1;
failure = EINVAL;
for (curr = list; curr != NULL; curr = curr->ai_next) {
listenfd = socket(curr->ai_family, curr->ai_socktype, curr->ai_protocol);
if (listenfd == -1)
continue;
if (bind(listenfd, curr->ai_addr, curr->ai_addrlen) == -1) {
if (!failure)
failure = errno;
close(listenfd);
listenfd = -1;
continue;
}
/* Bind successfully */
break;
}
/* Discard the chain, as we don't need it anymore.
* Note: curr is no longer valid after this. */
freeaddrinfo(list);
/* Failed to bind? */
if (listenfd == -1) {
fprintf(stderr, "Cannot bind to %s port %s: %s.\n", argv[1], argv[2], strerror(failure));
close(workerfd);
return 1;
}
if (listen(listenfd, LISTEN_BACKLOG) == -1) {
fprintf(stderr, "Cannot listen for incoming connections to %s port %s: %s.\n", argv[1], argv[2], strerror(errno));
close(listenfd);
close(workerfd);
return 1;
}
printf("Now waiting for incoming connections to %s port %s\n", argv[1], argv[2]);
fflush(stdout);
while (!done) {
memset(&conn, 0, sizeof conn);
connlen = sizeof conn;
connfd = accept(listenfd, (struct sockaddr *)&conn, &connlen);
if (connfd == -1) {
/* Did we just receive a signal? */
if (errno == EINTR)
continue;
/* Report a connection failure. */
printf("Failed to accept a connection: %s\n", strerror(errno));
fflush(stdout);
continue;
}
/* Construct the message to the worker process. */
memset(&connhdr, 0, sizeof connhdr);
memset(&conniov, 0, sizeof conniov);
memset(&connbuf, 0, sizeof connbuf);
conniov.iov_base = conndata; /* Data payload to send */
conniov.iov_len = 1; /* We send just one (dummy) byte, */
conndata[0] = 0; /* a zero. */
/* Construct the message (header) */
connhdr.msg_name = NULL; /* No optional address */
connhdr.msg_namelen = 0; /* No optional address */
connhdr.msg_iov = &conniov; /* Normal payload - at least one byte */
connhdr.msg_iovlen = 1; /* Only one vector in conniov */
connhdr.msg_control = connbuf; /* Ancillary data */
connhdr.msg_controllen = sizeof connbuf;
/* Construct the ancillary data needed to pass one descriptor. */
connmsg = CMSG_FIRSTHDR(&connhdr);
connmsg->cmsg_level = SOL_SOCKET;
connmsg->cmsg_type = SCM_RIGHTS;
connmsg->cmsg_len = CMSG_LEN(sizeof (int));
/* Copy the descriptor to the ancillary data. */
memcpy(CMSG_DATA(connmsg), &connfd, sizeof (int));
/* Update the message to reflect the ancillary data length */
connhdr.msg_controllen = connmsg->cmsg_len;
do {
written = sendmsg(workerfd, &connhdr, MSG_NOSIGNAL);
} while (written == (ssize_t)-1 && errno == EINTR);
if (written == (ssize_t)-1) {
const char *const errmsg = strerror(errno);
/* Lost connection to the other end? */
if (!done) {
if (errno == EPIPE)
done = SIGPIPE;
else
done = -1;
}
printf("Cannot pass connection to worker: %s.\n", errmsg);
fflush(stdout);
close(connfd);
/* Break main loop. */
break;
}
/* Since the descriptor has been transferred to the other process,
* we can close our end. */
do {
result = close(connfd);
} while (result == -1 && errno == EINTR);
if (result == -1)
printf("Error closing leftover connection descriptor: %s.\n", strerror(errno));
printf("Connection transferred to the worker process.\n");
fflush(stdout);
}
/* Shutdown. */
close(listenfd);
close(workerfd);
switch (done) {
case SIGTERM:
printf("Terminated.\n");
break;
case SIGPIPE:
printf("Lost connection.\n");
break;
case SIGHUP:
printf("Hanging up.\n");
break;
case SIGINT:
printf("Interrupted; exiting.\n");
break;
default:
printf("Exiting.\n");
}
return 0;
}
and worker.c:
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netdb.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
/* How many concurrent pending connections are allowed */
#define LISTEN_BACKLOG 32
/* Unix domain socket path length (including NUL byte) */
#ifndef UNIX_PATH_LEN
#define UNIX_PATH_LEN 108
#endif
/* Flag to indicate we have received a shutdown request. */
volatile sig_atomic_t done = 0;
/* Shutdown request signal handler, of the basic type. */
void handle_done_signal(int signum)
{
if (!done)
done = signum;
return;
}
/* Install shutdown request signal handler on signal signum. */
int set_done_signal(const int signum)
{
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_handler = handle_done_signal;
act.sa_flags = 0;
if (sigaction(signum, &act, NULL) == -1)
return errno;
else
return 0;
}
/* Helper function to duplicate file descriptors.
* Returns 0 if success, errno error code otherwise.
*/
static int copy_fd(const int fromfd, const int tofd)
{
int result;
if (fromfd == tofd)
return 0;
if (fromfd == -1 || tofd == -1)
return errno = EINVAL;
do {
result = dup2(fromfd, tofd);
} while (result == -1 && errno == EINTR);
if (result == -1)
return errno;
return 0;
}
int main(int argc, char *argv[])
{
struct sockaddr_un worker;
int workerfd, workerpathlen;
int serverfd, clientfd;
pid_t child;
struct msghdr msghdr;
struct iovec msgiov;
struct cmsghdr *cmsg;
char data[1];
char ancillary[CMSG_SPACE(sizeof (int))];
ssize_t received;
if (argc < 3) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s WORKER COMMAND [ ARGS .. ]\n", argv[0]);
fprintf(stderr, "This creates a worker that receives connections\n");
fprintf(stderr, "from Unix domain socket WORKER.\n");
fprintf(stderr, "Each connection is served by COMMAND, with the\n");
fprintf(stderr, "connection connected to its standard input and output.\n");
fprintf(stderr, "\n");
return (argc == 1) ? 0 : 1;
}
/* Handle HUP, INT, PIPE, and TERM signals,
* so when the user presses Ctrl-C, the worker process cannot be contacted,
* or the user sends a HUP or TERM signal, this server closes down cleanly. */
if (set_done_signal(SIGINT) ||
set_done_signal(SIGHUP) ||
set_done_signal(SIGPIPE) ||
set_done_signal(SIGTERM)) {
fprintf(stderr, "Error: Cannot install signal handlers.\n");
return 1;
}
/* Unix domain socket */
memset(&worker, 0, sizeof worker);
worker.sun_family = AF_UNIX;
workerpathlen = strlen(argv[1]);
if (workerpathlen < 1) {
fprintf(stderr, "Worker Unix domain socket path cannot be empty.\n");
return 1;
} else
if (workerpathlen >= UNIX_PATH_LEN) {
fprintf(stderr, "%s: Worker Unix domain socket path is too long.\n", argv[1]);
return 1;
}
memcpy(&worker.sun_path, argv[1], workerpathlen);
/* Note: the terminating NUL byte was set by memset(&worker, 0, sizeof worker) above. */
workerfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (workerfd == -1) {
fprintf(stderr, "Cannot create an Unix domain socket: %s.\n", strerror(errno));
return 1;
}
if (bind(workerfd, (const struct sockaddr *)(&worker), (socklen_t)sizeof worker) == -1) {
fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
close(workerfd);
return 1;
}
if (listen(workerfd, LISTEN_BACKLOG) == -1) {
fprintf(stderr, "%s: Cannot listen for messages: %s.\n", argv[1], strerror(errno));
close(workerfd);
return 1;
}
printf("Listening for descriptors on %s.\n", argv[1]);
fflush(stdout);
while (!done) {
serverfd = accept(workerfd, NULL, NULL);
if (serverfd == -1) {
if (errno == EINTR)
continue;
printf("Failed to accept a connection from the server: %s.\n", strerror(errno));
fflush(stdout);
continue;
}
printf("Connection from the server.\n");
fflush(stdout);
while (!done && serverfd != -1) {
memset(&msghdr, 0, sizeof msghdr);
memset(&msgiov, 0, sizeof msgiov);
msghdr.msg_name = NULL;
msghdr.msg_namelen = 0;
msghdr.msg_control = &ancillary;
msghdr.msg_controllen = sizeof ancillary;
cmsg = CMSG_FIRSTHDR(&msghdr);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof (int));
msghdr.msg_iov = &msgiov;
msghdr.msg_iovlen = 1;
msgiov.iov_base = &data;
msgiov.iov_len = 1; /* Just one byte */
received = recvmsg(serverfd, &msghdr, 0);
if (received == (ssize_t)-1) {
if (errno == EINTR)
continue;
printf("Error receiving a message from server: %s.\n", strerror(errno));
fflush(stdout);
break;
}
cmsg = CMSG_FIRSTHDR(&msghdr);
if (!cmsg || cmsg->cmsg_len != CMSG_LEN(sizeof (int))) {
printf("Received a bad message from server.\n");
fflush(stdout);
break;
}
memcpy(&clientfd, CMSG_DATA(cmsg), sizeof (int));
printf("Executing command with descriptor %d: ", clientfd);
fflush(stdout);
child = fork();
if (child == (pid_t)-1) {
printf("Fork failed: %s.\n", strerror(errno));
fflush(stdout);
close(clientfd);
break;
}
if (!child) {
/* This is the child process. */
close(workerfd);
close(serverfd);
if (copy_fd(clientfd, STDIN_FILENO) ||
copy_fd(clientfd, STDOUT_FILENO) ||
copy_fd(clientfd, STDERR_FILENO))
return 126; /* Exits the client */
if (clientfd != STDIN_FILENO &&
clientfd != STDOUT_FILENO &&
clientfd != STDERR_FILENO)
close(clientfd);
execvp(argv[2], argv + 2);
return 127; /* Exits the client */
}
printf("Done.\n");
fflush(stdout);
close(clientfd);
}
close(serverfd);
printf("Closed connection to server.\n");
fflush(stdout);
}
/* Shutdown. */
close(workerfd);
switch (done) {
case SIGTERM:
printf("Terminated.\n");
break;
case SIGPIPE:
printf("Lost connection.\n");
break;
case SIGHUP:
printf("Hanging up.\n");
break;
case SIGINT:
printf("Interrupted; exiting.\n");
break;
default:
printf("Exiting.\n");
}
return 0;
}
You can compile them using
gcc -W -Wall -O3 worker.c -o worker
gcc -W -Wall -O3 server.c -o server
and run using e.g.
rm -f connection
./worker connection /bin/date &
./server 127.0.0.1 8000 connection &
As you can see, the ./worker and ./server processes are completely separate. I recommend starting them from different windows (leaving out the & at the end of the command lines, which otherwise runs the commands at the background). The connection is the path or name of the Unix domain socket used to transfer the network connection file descriptor. The /bin/date is a command (not a shell command, an executable) that will be executed for each connection, with standard input, output and error connected directly to the network client -- very much like inetd or xinetd does, just bare bones.
You can test the connection via e.g.
nc 127.0.0.1 8000
or
telnet 127.0.0.1 8000
The above /bin/date command will just output the current date to standard output, but if you use a bit cleverer worker command, say
rm -f connection
./worker connection printf 'HTTP/1.0 200 Ok\r\nConnection: close\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Length: 67\r\n\r\n<html><head><title>Works</title></head><body>Works!</body></html>\r\n'
you can use your browser (http://127.0.0.1:8000/) to test.
The design is such that worker.c listens to an Unix domain socket (connection in current working directory in all above example commands). It first accepts a connection (from a single server), then expects each incoming byte to be associated with SCM_RIGHTS ancillary data containing the file descriptor referring to the client connection. If there is a problem, or the connection is dropped, it goes back to waiting for a new connection from a server. If it receives a client descriptor, it forks a child process, redirects its standard input, output and error to the client descriptor, and executes the command specified on the ./worker command line. The parent process closes its copy of the client descriptor, and goes back to waiting for a new one.
server.c listens for incoming connections to the IPv4 or IPv6 address and port specified on its command line. When it gets a connection, it transfers the connected file descriptor to above worker.c process via the Unix domain socket specified on the command line (connection), closes its own copy, and goes back to waiting for a new connection. Note that if the server loses the connection to the worker, it aborts; you'll want to start ./worker always before the ./server.
Both server.c and worker.c install simple signal handlers so that you can tell them to exit by sending them a HUP or INT signal (Ctrl-C, if you run the commands in the foreground in separate terminals or shells). They also have reasonable error checking, so when they exit, they tell you exactly why. To be honest, I did it because that way you WILL receive EINTR errors occasionally, and unless you treat them correctly (retrying the relevant syscalls unless asked to exit), your processes will be fragile, and crash from the slightest changes in conditions. Be robust; it's not that hard, and the results are much more user/sysadmin-friendly.
I hope you find the code interesting. I'd be happy to elaborate, if you have any questions on the details. Just remember that I wrote it from scratch in very little time, and it is only intended as a simple example. There is a lot of room for improvement.

UNIX socket is used to pass file descriptors between processes.

According to this post it should be possible. You need some way (pipes or sockets come to mind) to let your worker process know the sockets handle.
Unfortunately, I am not experienced with unix programming, so I cannot give you more concrete info.

Related

How do I perform port forwarding in C using libssh on Windows

Below is a section of code I wrote with the help of this post here: C: can I forward port for external application with libssh?.
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; /* initialize the mutex */
struct arg_struct
{
int sockfd; /* client socket */
ssh_session session; /* ssh session */
char *rhost; /* remote where we perform forwarding */
int rport; /* ssh port */
int lport; /* local port to listen on */
};
/* forward_handler: handle the port forwarding for every thread */
void *forward_handler(void *arg)
{
char data[4060];
int rc, recvl, nwritten, nread;
ssh_channel forwarding_channel;
struct arg_struct *args = (struct arg_struct*)arg;
pthread_mutex_lock(&lock); /* lock mutex until task complete */
/* requests on the fd can return immediately with a failure status if no input, vs blocking */
if ((fcntl(args->sockfd, F_SETFL, O_NONBLOCK)) != 0)
goto exit;
/* get pointer to a newly allocated channel, NULL on error */
if ((forwarding_channel = ssh_channel_new(args->session)) == NULL) {
perror("Error");
goto exit;
}
/* open a TCP/IP forwarding channel, SSH_OK if successfull */
rc = ssh_channel_open_forward(forwarding_channel,
args->rhost, args->rport,
"127.0.0.1", args->lport);
if (rc != SSH_OK) {
perror("Error");
goto exit;
}
printf("Connected! Tunnel open (127.0.0.1:%d) -> (ssh://%s) -> (\"%s:%d\")\n",
args->lport, args->rhost, args->rhost, args->rport
);
/* while we have an open channel and channel has NOT sent an EOF response */
while(ssh_channel_is_open(forwarding_channel) && !ssh_channel_is_eof(forwarding_channel))
{
if ((recvl = recv(args->sockfd, data, sizeof(data), MSG_DONTWAIT)) < 0) /* recieve data & enable nonblocking */
if ((nread = ssh_channel_read_nonblocking(forwarding_channel, data, sizeof(data), 0)) > 0) /* do a nonblocking read on the channel */
if ((write(args->sockfd, data, nread)) < 0) /* write to socket */
{
perror("Error");
goto exit;
}
else if (!recvl) {
printf("Local client disconnected, exiting\n");
goto exit;
}
nwritten = ssh_channel_write(forwarding_channel, data, recvl); /* send to the remote host */
if (recvl != nwritten) {
perror("Error");
goto exit;
}
}
exit:
ssh_channel_free(forwarding_channel); /* free the forwarding channel */
pthread_mutex_unlock(&lock); /* unlock mutex */
close(args->sockfd); /* close socket file descriptor */
pthread_exit(NULL); /* exit thread */
}
This code has not been optimized and heavily tested and tbh wrote it fairly quickly and need to review it, but it works on Linux as my POC for now. When I try to do the same on Windows I mainly run into an issue on the following lines:
if ((fcntl(args->sockfd, F_SETFL, O_NONBLOCK)) != 0)
goto exit;
---snip---
if ((recvl = recv(args->sockfd, data, sizeof(data), MSG_DONTWAIT)) < 0) /* recieve data & enable nonblocking */
if ((nread = ssh_channel_read_nonblocking(forwarding_channel, data, sizeof(data), 0)) > 0) /* do a nonblocking read on the channel */
if ((write(args->sockfd, data, nread)) < 0)
Below are my attempts to fix this:
/* forward_handler: handle the port forwarding for every thread */
void *forward_handler(void *arg)
{
char data[4096];
unsigned long int iMode = 1;
int rc, recvl, nwritten, nread;
ssh_channel forwarding_channel;
struct arg_struct *args = (struct arg_struct*)arg;
pthread_mutex_lock(&lock); /* lock mutex until task complete */
/* requests on the fd can return immediately with a failure status if no input, vs blocking */
if ((ioctlsocket(args->sockfd, FIONBIO, &iMode)) != NO_ERROR)
goto exit;
/* get pointer to a newly allocated channel, NULL on error */
if ((forwarding_channel = ssh_channel_new(args->session)) == NULL) {
fprintf(stderr, "forward_handler: Failed to create new channel with error: %d\n", forwarding_channel);
exit(0);
}
/* open a TCP/IP forwarding channel, SSH_OK if successfull */
rc = ssh_channel_open_forward(forwarding_channel,
args->rhost, args->rport,
"127.0.0.1", args->lport);
if (rc != SSH_OK) {
fprintf(stderr, "forward_handler: Failed to open forwarding channel with error: %d\n", rc);
goto exit_and_free;
}
printf("Connected! Tunnel open (127.0.0.1:%d) -> (ssh://%s) -> (\"%s:%d\")\n",
args->lport, args->rhost, args->rhost, args->rport
);
/* while we have an open channel and channel has NOT sent an EOF response */
while(ssh_channel_is_open(forwarding_channel) && !ssh_channel_is_eof(forwarding_channel))
{
if ((recvl = recv(args->sockfd, data, sizeof(data), 0)) < 0) {
if ((nread = ssh_channel_read_nonblocking(forwarding_channel, data, sizeof(data), 0)) > 0) /* do a nonblocking read on the channel */
if ((WriteFile((HANDLE)args->sockfd, data, nread, NULL, NULL)) < 0) /* write to socket */
{
fprintf(stderr, "forward_handler: Error writing to file descriptor");
goto exit_and_free;
}
}
else if (!recvl) {
printf("Local client disconnected, exiting\n");
goto exit_and_free;
}
nwritten = ssh_channel_write(forwarding_channel, data, recvl); /* send to the remote host */
if (recvl != nwritten) {
fprintf(stderr, "forward_handler: Error writing to SSH channel\n");
goto exit_and_free;
}
}
exit_and_free:
ssh_channel_free(forwarding_channel); /* free the forwarding channel */
exit:
pthread_mutex_unlock(&lock); /* unlock mutex */
close(args->sockfd); /* close socket file descriptor */
pthread_exit(NULL); /* exit thread */
}
Aight hit me with the roasts on bad code :) because I'm stuck.

Unix system programming: get a network identifier to be passed to getaddrinfo

I am following on a book the code in C to build a server using system calls.
The main function is the following:
int main(int argc, char* argv[])
{
printf("entered main\n");
struct addrinfo *ailist, *aip, hint;
int sockfd, err, n;
char *host;
if (argc != 1)
{
printf("usage: ruptimed\n");
exit(1);
}
if ((n=sysconf(_SC_HOST_NAME_MAX))<0)
{
n = HOST_NAME_MAX;
}
if((host = malloc(n)) == NULL)
{
printf("malloc error\n");
exit(1);
}
if (gethostname(host, n)<0)
{
printf("gethostname error\n");
exit(1);
}
printf("host: %s\n", host);
printf("Daemonizing\n");
int res = daemonize("ruptimed");
printf("%d\n", res);
printf("Daemonized\n");
memset(&hint, 0, sizeof(hint)); //set to 0 all bytes
printf("hint initialized\n");
hint.ai_flags = AI_CANONNAME;
hint.ai_socktype = SOCK_STREAM;
hint.ai_canonname = NULL;
hint.ai_addr = NULL;
hint.ai_next = NULL;
printf("getting addresses\n");
if((err = getaddrinfo(host, "ruptime", &hint, &ailist))!=0)
{
printf("error %s\n", gai_strerror(err));
syslog(LOG_ERR, "ruptimed: getaddrinfo error %s", gai_strerror(err));
exit(1);
}
printf("Got addresses\n");
for (aip = ailist; aip!=NULL; aip = aip->ai_next)
{
if ((sockfd = initserver(SOCK_STREAM, aip->ai_addr, aip->ai_addrlen, QLEN))>=0)
{
printf("starting to serve\n");
serve(sockfd);
exit(0);
}
}
exit(1);
}
My problem is when getting the host name with gethostname and then using it with getaddrinfo.
Running the code on OSX I get a name such as pippo's-MacBook-pro.local memorized in the host char pointer variable. Passing this to getaddrinfo results in the error: nodename nor servname provided, or not known.
I was expecting the gethostname to return a local IP or a local network identifier (even localhost would be good for learning). I doubt such name can be used to identify a (local) server without proper settings on the machine (plus I cannot remember the book saying anything about setting the host name).
How can I get a network identifier (such as the local IP) to be passed to getaddrinfo?
If I would like to use gethostname what changes or settings should be performed?
CODE
server.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h> //_SC_HOST_NAME_MAX
#include<string.h>
#include<netdb.h> //Here are defined AF_INET and the others of the family
#include<syslog.h> //LOG_ERR
#include<errno.h> //errno
#include <sys/types.h>
#include"utilities.h"
#include "error.h"
#define BUFLEN 128
#define QLEN 10
#ifndef HOST_NAME_MAX
#define HOST_NAME_MAX 156
#endif
int initserver(int type, const struct sockaddr *addr, socklen_t alen, int qlen);
void serve(int sockfd);
int main(int argc, char* argv[])
{
printf("entered main\n");
struct addrinfo *ailist, *aip, hint;
int sockfd, err, n;
char *host;
if (argc != 1)
{
printf("usage: ruptimed\n");
exit(1);
}
if ((n=sysconf(_SC_HOST_NAME_MAX))<0)
{
n = HOST_NAME_MAX;
}
if((host = malloc(n)) == NULL)
{
printf("malloc error\n");
exit(1);
}
if (gethostname(host, n)<0)
{
printf("gethostname error\n");
exit(1);
}
printf("host: %s\n", host);
printf("Daemonizing\n");
int res = daemonize("ruptimed");
printf("%d\n", res);
printf("Daemonized\n");
memset(&hint, 0, sizeof(hint)); //set to 0 all bytes
printf("hint initialized\n");
hint.ai_flags = AI_CANONNAME;
hint.ai_socktype = SOCK_STREAM;
hint.ai_canonname = NULL;
hint.ai_addr = NULL;
hint.ai_next = NULL;
printf("getting addresses\n");
if((err = getaddrinfo(host, "ruptime", &hint, &ailist))!=0)
{
printf("error %s\n", gai_strerror(err));
syslog(LOG_ERR, "ruptimed: getaddrinfo error %s", gai_strerror(err));
exit(1);
}
printf("Got addresses\n");
for (aip = ailist; aip!=NULL; aip = aip->ai_next)
{
if ((sockfd = initserver(SOCK_STREAM, aip->ai_addr, aip->ai_addrlen, QLEN))>=0)
{
printf("starting to serve\n");
serve(sockfd);
exit(0);
}
}
exit(1);
}
void serve(int sockfd)
{
int clfd;
FILE *fp;
char buf[BUFLEN];
set_cloexec(sockfd);
for(;;)
{
/*After listen, the socket can receive connect requests. accept
retrieves a connect request and converts it into a connection.
The file returned by accept is a socket descriptor connected to the client that
called connect, haing the same coket type and family type. The original
soket remains available to receive otherconneion requests. If we don't care
about client's identity we can set the second (struct sockaddr *addr)
and third parameter (socklen_t *len) to NULL*/
if((clfd = accept(sockfd, NULL, NULL))<0)
{
/*This generates a log mesage.
syslog(int priority, const char *fformat,...)
priority is a combination of facility and level. Levels are ordered from highest to lowest:
LOG_EMERG: emergency system unusable
LOG_ALERT: condiotin that must be fied immediately
LOG_CRIT: critical condition
LOG_ERR: error condition
LOG_WARNING
LOG_NOTICE
LOG_INFO
LOG_DEBUG
format and other arguments are passed to vsprintf function forf formatting.*/
syslog(LOG_ERR, "ruptimed: accept error: %s", strerror(errno));
exit(1);
}
/* set the FD_CLOEXEC file descriptor flag */
/*it causes the file descriptor to be automatically and atomically closed
when any of the exec family function is called*/
set_cloexec(clfd);
/**pg. 542 Since a common operation is to create a pipe to another process
to either read its output or write its input Stdio has provided popen and
pclose: popen creates pipe, close the unused ends of the pipe,
forks a child and call exec to execute cmdstr and
returns a file pointer (connected to std output if "r", to stdin if "w").
pclose closes the stream, waits for the command to terminate*/
if ((fp = popen("/usr/bin/uptime", "r")) == NULL)
{
/*sprintf copy the string passed as second parameter inside buf*/
sprintf(buf, "error: %s\n", strerror(errno));
/*pag 610. send is similar to write. send(int sockfd, const void *buf, size_t nbytes, it flags)*/
send(clfd, buf, strlen(buf),0);
}
else
{
/*get data from the pipe that reads created to exec /usr/bin/uptime */
while(fgets(buf, BUFLEN, fp)!=NULL)
{
/* clfd is returned by accept and it is a socket descriptor
connected to the client that called connect*/
send(clfd, buf, strlen(buf), 0);
}
/*see popen pag. 542*/
pclose(fp);
}
close(clfd);
}
}
int initserver(int type, const struct sockaddr *addr, socklen_t alen, int qlen)
{
int fd, err;
int reuse = 1;
if ((fd = socket(addr->sa_family, type, 0))<0)
{
return (-1);
}
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int))<0)
{
goto errout;
}
if(bind(fd, addr, alen)<0)
{
goto errout;
}
if (type == SOCK_STREAM || type == SOCK_SEQPACKET)
{
if(listen(fd, qlen)<0)
{
goto errout;
}
}
return fd;
errout:
err = errno;
close (fd);
errno = err;
return(-1);
}
utilities.c: containing the demonize and setcloexec functions. In daemonize function I did not close file descriptors for debugging.
#include "utilities.h"
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <syslog.h>
#include <sys/time.h>//getrlimit
#include <sys/resource.h>//getrlimit
#include <signal.h> //sigempyset , asigcation (umask?)
#include <sys/resource.h>
#include <fcntl.h> //O_RDWR
#include <stdarg.h>
#include "error.h"
int daemonize(const char *cmd)
{
int fd0, fd1, fd2;
unsigned int i;
pid_t pid;
struct rlimit rl;
struct sigaction sa;
/* *Clear file creation mask.*/
umask(0);
/* *Get maximum number of file descriptors. */
if (getrlimit(RLIMIT_NOFILE, &rl) < 0)
{
err_quit("%s: can’t get file limit", cmd);
}
/* *Become a session leader to lose controlling TTY. */
if ((pid = fork()) < 0)
{
err_quit("%s: can’t fork", cmd);
}
else if (pid != 0) /* parent */
{
exit(0); //the parent will exit
}
setsid();
/* *Ensure future opens won’t allocate controlling TTYs. */
sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGHUP, &sa, NULL) < 0)
{
err_quit("%s: can’t ignore SIGHUP", cmd);
}
if ((pid = fork()) < 0)
{
err_quit("%s: can’t fork", cmd);
}
else if (pid != 0) /* parent */
{
exit(0);
}
/*
*Change the current working directory to the root so
* we won’t prevent file systems from being unmounted.
*/
if (chdir("/") < 0)
{
err_quit("%s: can’t change directory to /", cmd);
}
/* Close all open file descriptors. */
if (rl.rlim_max == RLIM_INFINITY)
{
rl.rlim_max = 1024;
}
printf("closing file descriptors\n");
/*for (i = 0; i < rl.rlim_max; i++)
{
close(i);
}*/
/* *Attach file descriptors 0, 1, and 2 to /dev/null.*/
//printf not working
/*printf("closed all file descriptors for daemonizing\n");*/
/*fd0 = open("/dev/null", O_RDWR);
fd1 = dup(0);
fd2 = dup(0);*/
/* *Initialize the log file. Daemons do not have a controlling terminal so
they can't write to stderror. We don't want them to write to the console device
because on many workstations the control device runs a windowing system. They can't
write on separate files either. A central daemon error-logging facility is required.
This is the BSD. 3 ways to generate log messages:
1) kernel routines call the log function. These messages can be read from /dev/klog
2) Most user processes (daemons) call syslog to generate log messages. This causes
messages to be sent to the UNIX domain datagram socket /dev/log
3) A user process on this host or on other host connected to this with TCP/ID
can send log messages to UDP port 514. Explicit network programmin is required
(it is not managed by syslog.
The syslogd daemon reads al three of log messages.
openlog is optional since if not called, syslog calls it. Also closelog is optional
openlog(const char *ident, int option, int facility)
It lets us specify ident that is added to each logmessage. option is a bitmask:
LOG_CONS tells that if the log message can't be sent to syslogd via UNIX
domain datagram, the message is written to the console instead.
facility lets the configuration file specify that messages from different
facilities are to be handled differently. It can be specified also in the 'priority'
argument of syslog. LOG_DAEMON is for system deamons
*/
/*
openlog(cmd, LOG_CONS, LOG_DAEMON);
if (fd0 != 0 || fd1 != 1 || fd2 != 2)
{*/
/*This generates a log mesage.
syslog(int priority, const char *fformat,...)
priority is a combination of facility and level. Levels are ordered from highest to lowest:
LOG_EMERG: emergency system unusable
LOG_ALERT: condiotin that must be fied immediately
LOG_CRIT: critical condition
LOG_ERR: error condition
LOG_WARNING
LOG_NOTICE
LOG_INFO
LOG_DEBUG
format and other arguments are passed to vsprintf function forf formatting.*/
/*syslog(LOG_ERR, "unexpected file descriptors %d %d %d", fd0, fd1, fd2);
exit(1);
}*/
return 0;
}
/*The function set the FD_CLOEXEC flag of the file descriptor already open that
is passed to as parameter. FD_CLOEXEC causes the file descriptor to be
automatically and atomically closed when any of the exec family function is
called*/
int set_cloexec(int fd)
{
int val;
/* retrieve the flags of the file descriptor */
if((val = fcntl(fd, F_GETFD, 0))<0)
{
return -1;
}
/* set the FD_CLOEXEC file descriptor flag */
/*it causes the file descriptor to be automatically and atomically closed
when any of the exec family function is called*/
val |= FD_CLOEXEC;
return (fcntl(fd, F_SETFD, val));
}
error functions I used
/* Fatal error unrelated to a system call.
* Print a message and terminate*/
void err_quit (const char *fmt, ...)
{
va_list ap;
va_start (ap, fmt);
err_doit (0, 0, fmt, ap);
va_end (ap);
exit(1);
}
/*Print a message and return to caller.
*Caller specifies "errnoflag"*/
static void err_doit(int errnoflag, int error, const char *fmt, va_list ap)
{
char buf [MAXLINE];
vsnprintf (buf, MAXLINE-1, fmt, ap);
if (errnoflag)
{
snprintf (buf+strlen(buf), MAXLINE-strlen(buf)-1, ": %s",
strerror (error));
}
strcat(buf, "\n");
fflush(stdout); /*in case stdout and stderr are the same*/
fputs (buf, stderr);
fflush(NULL); /* flushes all stdio output streams*/
}
There is a relatively simple typo in the getaddrinfo call
if((err = getaddrinfo("host", "ruptime", &hint, &ailist))!=0)
/* ^^^^^^ */
/* Should be the variable host */
{
printf("error %s\n", gai_strerror(err));
syslog(LOG_ERR, "ruptimed: getaddrinfo error %s", gai_strerror(err));
exit(1);
}
The code is looking for the address of "host". I would also replace "ruptime" with NULL since ruptime isn't an entry in /etc/services. See getaddrinfo(3) for more detail.

How to programmatically get PID of process connecting to my proxy via AF_INET sockets, on the same machine?

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.

Bind() on AF_PACKET socket fails at first ... then fixes itself?

I can't figure this out.
When I run my code ... I see data from all Ethernet types and from all interfaces even though I bind successfuly.
After a couple minutes running ... it fixes itself.
Then I see only from a particular interface and only if the Ether type matches.
The objective is to cycle through all interfaces looking for a particular MAC address.
When the correct response is returned ... we drop out the for loop with all things configured as necessary.
// Copyright (c) 2017 Keith M. Bradley
//
//
// History:
// 13 May 2017 Keith M. Bradley Creation
// all rights reserved.
//
/* ----------------------- Standard includes --------------------------------*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <pthread.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <net/if.h>
#include <net/ethernet.h>
#include <netpacket/packet.h>
#include <netdb.h>
#define SIGNAL_THREAD_KILL 0xFF
#define SIGNAL_THREAD_RESET 0xFE
// Ethernet II protocol to use (0x88b5 ... experimental #1).
#define eType 0x88b5
#define msg_Hello "MikiePLC"
#define msg_Reply "IOM_1.0"
#define msg_Ack "ackMikiePLC"
void* PLCThread(void* arg)
{
// get our pointer to the PLC struct
PLC *myPLC = arg;
// get and save our thread ID
myPLC->tid = pthread_self();
// thread index number?
//------------------------------------------------------------------------------------------------------------------
// locals
uint8_t i; // used as an index or loop counts.
uint8_t j; // used as 2nd index or loop counts.
int rtn; // temp store or function return values.
//------------------------------------------------------------------------------------------------------------------
// create Ethernet buffers and variables.
char* outBuff = NULL; // character buffer for sending out on Ethernet.
size_t outBuffSz = 1540;
char* inBuff = NULL; // character buffer for receiving in on Ethernet.
size_t inBuffSz = 1540;
int fd; // file descriptor for socket.
int flags; // socket flags used bt fcntl().
struct
ifreq ifr; // used to get and set interface parameters.
struct
sockaddr_ll IOM_sa_flt; // socket address struct, used to filter received Ethernet frames from the remote IO module ... used by bind().
struct
sockaddr_ll IOM_sa_rcv; // socket address struct, used to store addr details of received frame ... used by recvfrom().
socklen_t IOM_sa_len; // IOM_sa_rcv length.
fd_set myfds; // used by select().
struct
timeval rcv_tm_out; // time out for select() to declare communications failed.
//------------------------------------------------------------------------------------------------------------------
// initialize Ethernet buffers and variables.
// allocate memory for the Ethernet sending message buffer.
outBuff = malloc(outBuffSz);
if (outBuff == NULL)
printf("\nNATIVE-PLCThread: Could not allocate outBuff memory.");
memset(outBuff, '\0', outBuffSz);
// allocate memory for the Ethernet recevied message buffer.
inBuff = malloc(inBuffSz);
if (inBuff == NULL)
printf("\nNATIVE-PLCThread: Could not allocate inBuff memory.");
// clear the sockaddr_ll structs.
// (send was already cleared ... it is inside the PLC typdef).
memset(&IOM_sa_rcv, 0, sizeof(IOM_sa_rcv));
memset(&IOM_sa_flt, 0, sizeof(IOM_sa_flt));
// set receiving sockaddr_ll struct size.
IOM_sa_len = sizeof(IOM_sa_rcv);
// setup the sending, receiving, and filtering sockaddr_ll's.
myPLC->IOM_sa_snd.sll_family = AF_PACKET;
myPLC->IOM_sa_snd.sll_protocol = htons(eType);
IOM_sa_rcv.sll_family = AF_PACKET;
IOM_sa_rcv.sll_protocol = htons(eType);
IOM_sa_flt.sll_family = AF_PACKET;
IOM_sa_flt.sll_protocol = htons(eType);
//------------------------------------------------------------------------------------------------------------------
// open our socket in dgram mode and setup the socket's features.
fd = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_ALL));
if (fd == -1)
{
fprintf(stderr, "%s\n", strerror(errno));
printf("\nNATIVE-PLCThread: socket() failed !! - ");
}
// get the socket file descriptor flags.
flags = fcntl(fd, F_GETFL, 0);
// if succesful, set to non-blocking.
if (flags != -1)
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
if (fd != -1) // valid socket file descriptor means ok to proceed with IOM_Addr_search.
{
// IOM_MAC_search
// if MAC_Addr is configured,
// loop to find which interface has the IOM (I/O Module).
//
// begin for loop ----------------------------------------------------------------------------------------------
for (i = 1; 1; i++)
{
// we need to test for thread kill signal.
if((myPLC->ThreadCtrl == SIGNAL_THREAD_KILL) || (myPLC->ThreadCtrl == SIGNAL_THREAD_RESET)) break;
// if the user cleared the MAC addr while we were searching ... give up and run the engine.
if (myPLC->MAC_is_Valid != 0xa5) break;
// clear the ifreq struct.
memset(&ifr, 0, sizeof(ifr));
// i is our 'for' loop counter and our current interface index.
ifr.ifr_ifindex = i;
// does the interface exist?
if (ioctl(fd, SIOCGIFNAME, &ifr) == -1)
{
// if not, we ran past top of network interfaces.
printf("\nNATIVE-PLCThread: IOM_MAC_search MAC address not found after searching all interfaces !!!\n");
printf("\n_________________________________________________________________________________________\n");
sleep(10);
i = 0;
continue;
}
// don't mess with loopback interface.
if (strcmp(ifr.ifr_name,"lo") == 0) continue;
// store the ifname using the pointer.
strncpy (myPLC->ifName, ifr.ifr_name, sizeof(ifr.ifr_name) - 1);
myPLC->ifName[IFNAMSIZ - 1] = '\0';
// update the interface index in all sockaddr structs.
myPLC->IOM_sa_snd.sll_ifindex = i;
IOM_sa_rcv.sll_ifindex = i;
IOM_sa_flt.sll_ifindex = i;
// is the interface up?
ioctl(fd, SIOCGIFFLAGS, &ifr);
if ((ifr.ifr_flags & IFF_UP) == 0)
{
printf("\nNATIVE-PLCThread: IOM_Addr_search interface %s (index %d) is down.\n", myPLC->ifName, i);
continue;
}
// bind it.
if (bind(fd, (struct sockaddr*)&IOM_sa_flt, sizeof(IOM_sa_flt)) == -1)
{
fprintf(stderr, "%s\n", strerror(errno));
printf("\nNATIVE-PLCThread: IOM_Addr_search bind() failed !!!\n");
continue;
}
// pause and flush? (didn't help at all)
sleep(2);
recvfrom(fd, inBuff, inBuffSz, 0, (struct sockaddr *)&IOM_sa_rcv, &IOM_sa_len);
// fill outBuff with the hello message.
strcpy(outBuff, msg_Hello);
// send hello msg to the IOM with configured IOM_MAC_address.
if (sendto(fd, outBuff, sizeof(msg_Hello), 0, (struct sockaddr *)&(myPLC->IOM_sa_snd), sizeof (myPLC->IOM_sa_snd)) == -1)
{
fprintf(stderr, "%s\n", strerror(errno));
printf("\nNATIVE-PLCThread: IOM_Addr_search sendto() failed on interface %s (index %d) !!!\n", myPLC->ifName, i);
continue;
}
// setup for the select() time out loop.
rcv_tm_out.tv_sec = 0;
rcv_tm_out.tv_usec = 50000;
// begin while loop ------------------------------------------------------------------------------------------
//
// select() time out loop.
// wait for valid response from IOM_MAC_address (discard any ETHERNET 2 messages from other MAC's).
//
while ((rcv_tm_out.tv_sec != 0) || (rcv_tm_out.tv_usec != 0))
{
// create the file descriptor set for use by select().
FD_ZERO(&myfds);
FD_SET(fd, &myfds);
// select() to sleep until received frame is ready, or the maximum length of time it would taked to get a response is exceeded.
rtn = select(fd + 1, &myfds, NULL, NULL, &rcv_tm_out);
if (rtn < 0)
{
fprintf(stderr, "%s\n", strerror(errno));
printf("\nNATIVE-PLCThread: IOM_Addr_search select() returned <0 on interface %s (index %d).\n", myPLC->ifName, i);
break;
}
// did we time out? ... then goto the next interface to search.
else if (rtn == 0)
{
printf("\nNATIVE-PLCThread: IOM_Addr_search select() timed out (returned 0) on interface %s (index %d).\n", myPLC->ifName, i);
break;
}
else // select() returned > 0.
{
if (FD_ISSET(fd, &myfds))
{
// our socket is ready for reading ... 1st clear the buffer and the sock addr.
memset(inBuff, '\0', inBuffSz);
for (j = 0; j < 6; j++)
IOM_sa_rcv.sll_addr[j] = 0;
rtn = recvfrom(fd, inBuff, inBuffSz, 0, (struct sockaddr *)&IOM_sa_rcv, &IOM_sa_len);
if(rtn < 0)
{
if (errno == EAGAIN)
printf("\nNATIVE-PLCThread: IOM_Addr_search recvfrom() returned EAGAIN.\n");
else if (errno == EWOULDBLOCK)
printf("\nNATIVE-PLCThread: IOM_Addr_search recvfrom() returned EWOULDBLOCK.\n");
else
{
fprintf(stderr, "%s\n", strerror(errno));
printf("\nNATIVE-PLCThread: IOM_Addr_search recvfrom() returned unrecoverable error.\n");
}
break;
}
else if (rtn == 0)
printf("\nNATIVE-PLCThread: IOM_Addr_search a_file_descriptor_is_set yet recvfrom() returned zero.\n");
else // recvfrom() returned > 0.
{
printf("\nNATIVE-PLCThread: IOM_Addr_search recvfrom() returned %d bytes on %s (index %d) MAC %02x:%02x:%02x:%02x:%02x:%02x rcv_tm_out.tv_sec = %d.%d\n",
rtn,
myPLC->ifName,
i,
IOM_sa_rcv.sll_addr[0],
IOM_sa_rcv.sll_addr[1],
IOM_sa_rcv.sll_addr[2],
IOM_sa_rcv.sll_addr[3],
IOM_sa_rcv.sll_addr[4],
IOM_sa_rcv.sll_addr[5],
(int)rcv_tm_out.tv_sec,
(int)rcv_tm_out.tv_usec);
// check the IOM_sa_rcv.MAC_Addr ... is it who we want to talk to? ... if not discard.
for (j = 0; j < 6; ++j)
if ((myPLC->IOM_sa_snd.sll_addr[j]) == (IOM_sa_rcv.sll_addr[j])) continue;
// MAC addr matches?
if (j > 50) // set to 50 to debug ... should be 5.
{
printf("\nMAC Addr from our IOM.\n");
// parse the received response to our hello msg.
if (strcmp(inBuff, msg_Reply) == 0)
{
// fill outBuff with the Ack message.
strcpy(outBuff, msg_Ack);
// send ack message to the IOM with configured IOM_MAC_address.
if (sendto(fd, outBuff, sizeof("ackMikiePLC"), 0, (struct sockaddr *)&(myPLC->IOM_sa_snd), sizeof (myPLC->IOM_sa_snd)) == -1)
{
fprintf(stderr, "%s\n", strerror(errno));
printf("\nNATIVE-PLCThread: IOM_Addr_search sendto() failed on interface %s (index %d) !!!\n", myPLC->ifName, i);
continue;
}
else
{
// declare ComStatus ok.
myPLC->ComStatus = 0xa5;
break; // we have a winner !!!
}
}
else
{
// declare ComStatus still NOT ok.
myPLC->ComStatus = 0x5a;
continue;
}
}
else
{
printf("\nMAC Addr from a stranger (discarded)!!!\n");
break;
}
}// END recvfrom() returned > 0.
}// END if (FD_ISSET(fd, &myfds))
else printf("\nNATIVE-PLCThread: IOM_Addr_search select() returned > 0 yet our only file descriptor was not set !!!\n");
}// END select() returned > 0.
}// END while loop -------------------------------------------------------------------------------------------
if (myPLC->ComStatus == 0xa5) break; // search is done ... break out of for loop.
}// END for loop -----------------------------------------------------------------------------------------------
}// END "valid socket fd means ok to proceed" ----------------------------------------------------------------------
else printf("\nNATIVE-PLCThread: IOM_Addr_search socket() previously failed ... search cannot proceed.\n");
// MAIN ENGINE LOOP !!!---------------------------------------------------------------------------------------------
//
// Loop for the life of this Sedona PLC object (unless Enable is false).
//
while((myPLC->ThreadCtrl != SIGNAL_THREAD_KILL) && (myPLC->ThreadCtrl != SIGNAL_THREAD_RESET))
{
}
CleanExit: //--------------------------------------------------------------------------------------------------------
close(fd);
free(outBuff);
free(inBuff);
free(myPLC);
pthread_exit(NULL);
}
Here is a print example when it starts:
NATIVE-PLCThread: IOM_Addr_search recvfrom() returned 104 bytes on eth0 (index 2) MAC 00:1e:c9:7d:c4:36 rcv_tm_out.tv_sec = 0.49997
MAC Addr from a stranger !!!
NATIVE-PLCThread: IOM_Addr_search recvfrom() returned 152 bytes on enp1s0 (index 3) MAC 00:1e:c9:7d:c4:36 rcv_tm_out.tv_sec = 0.49998
MAC Addr from a stranger !!!
NATIVE-PLCThread: IOM_MAC_search MAC address not found after searching all interfaces !!!
I should see "select() timed out" on eth0 since there is nothing responding with Ether type 0x88b5.
I think I see the problem.
I created the socket with ETH_P_ALL.
I assumed I could be more specific in the bind as the docs say we can.
Preliminary test so far has not reproduced the issue.
I have seen many sources that say one can do what I originally did ... so this may be a bug in Linux or the driver?

ChatRoom Service between Clients in C - TCP

Well to get started, what i'm trying to do is a multi client chat service.
I have read thousands of posts related to it but most of them are implemented with threads and nothing helps me, and i need to do it using FORKS.
I have my server that supports connections of multiple clients. Every time that a client request connection the server does the following:
Sets the shared variables that are needed.
Get the proper socket to handle the connection,
When a client connects, saves data of the client in a array of clients implemented with a struct,
Forks a process to handle this connection,
Goes back and blocks in the accept() function waiting another client.
The fork does the following:
Waits commands that the user sends,
Completes the request,
Waits for another command.
The fork works with a shared array of clients protected by a semaphore. This was done (by the father process) with shm_open, mmap and shm_open, to get the array shared among the child processes.
For now, the only 3 options are:
clientlist : to see the list of connected clients,
sendchat : to send a message to a desired client,
quit_ : to quit the programm and disconnects from the server.
Saying that, the problem is that i can't in any way to notice a client that a message is ready for him. The flow of the execution is:
Client C1 connects, Client C2 connects.
C1 whants to send a message to C2, C1 tells his process that he wants to talk to C2.
The process handling the connection of C1, search in the shared array the name of C2, and then writes the message sent by C1 in the buffer of C2.
And here is where i'm get stuck..i don't know how to make that C2 notice that is a new message for im.
I know this is long for anyone to care, but if you can, I'll be glad to get some help, please.
Below are the client, and server scripts.
Note: server.c compile with -lptrhead and -lrt for binding the shared memory library.
Note: the server gets correctly the socket from the function get_tcp_listen as you will see, no need to worry about this.
How should i approach this problem? Thanks !.
client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include "socketlib.h" // FOR BINDINGS, GET SOCKET, AND STUFF
#define SERVER_PORT "50000"
#define CLIENT_PORT "50001"
#define MAXDATASIZE 256
#define MAXTIMESIZE 30
#define NICKSIZE 25
#define MAXCLIENT 10
#define MAXMSG 1024
typedef struct{
char nick[NICKSIZE]; // NICK
char ip[NI_MAXHOST]; // IP
char port[NI_MAXSERV]; // PORT
char connTime[MAXTIMESIZE]; // TIMESTAMP
int connected; // STATE
pid_t pidConn; // PROCESS PID
char *msg; // MSG BUFFER
}connData;
int main (int argc, char **argv) {
// GET SOCKET
int sockfd;
if ((sockfd = get_tcp_connect(argv[1], SERVER_PORT, CLIENT_PORT)) == -1) {
fprintf(stderr, "Error client: client_connect\n");
exit(1);}
///////////////////////////////////////////////////////////////////////
time_t ltime;
ltime = time(NULL);
char timeStamp[MAXTIMESIZE];
strcpy(timeStamp,ctime(&ltime));
printf("\n%s\n", timeStamp);
// GET MY IP : PORT
char ip_num[NI_MAXHOST];
char port_num[NI_MAXSERV];
get_socket_addr(sockfd, ip_num, port_num);
get_peer_addr(sockfd, ip_num, port_num);
///////////////////////////////////////////////////////////////////////
// WELLCOME MSG FROM SERVER
char *well = (char*) malloc(MAXDATASIZE); int numbytes;
if ((numbytes = recv(sockfd, well, MAXDATASIZE-1, 0)) == -1) {
fprintf(stderr, "Error client: recv WELLCOME\n");
exit(1);
}
well[numbytes] = '\0'; printf("%s\n", well); free(well);
//////////////////////////////////////////////////////////////////////
// SEND NICK TO SERVER
char nick[NICKSIZE];
printf("\nEnter your NickName (25 chars): "); scanf("%s",nick);
if(send(sockfd, nick, NICKSIZE, 0) == -1){
fprintf(stderr,"Error client: send NICK\n");
exit(1);
}
///////////////////////////////////////////////////////////////////////
// GET CONNECTED USERS LIST FROM SERVER
int cantClients = 0; // FIRST: QUANTITY OF USERS
if (recv(sockfd, &cantClients, sizeof(int), 0) == -1) {
fprintf(stderr, "Error client: recv CANT CLIENTs\n");
exit(1);
}
connData *tmpCl = (connData *) malloc(sizeof(connData)*MAXCLIENT);
if (recv(sockfd, tmpCl, sizeof(connData)*MAXCLIENT, 0) == -1) {
fprintf(stderr, "Error client: recv ARRAY CLIENTS\n");
exit(1);
}
printf("\n****\tConnected Users\t****\n");
int i;
for(i = 0; i < cantClients; i++){
if(tmpCl[i].connected == 1){
printf("\nNick: %s\n", tmpCl[i].nick);
printf("IP: %s\n", tmpCl[i].ip);
printf("PORT: %s\n", tmpCl[i].port);
printf("Time: %s", tmpCl[i].connTime);
printf("Connected: %d\n", tmpCl[i].connected);
printf("PID: %d\n", tmpCl[i].pidConn);
printf("**********************************\n");
}
} free(tmpCl);
///////////////////////////////////////////////////////////////////////
// THE CLIENT PROCESS WAITS UNTIL THE USER TYPES A COMMAND
char *comm = (char*)malloc(MAXDATASIZE);
printf("\nEnter one option: ");
printf("\n\t-> clientlist TO SEE THE LIST OF CONNECTED CLIENTS\n");
printf("\t-> sendchat TO SEND A MESSAGE\n");
printf("\t-> quit_ TO QUIT CHAT\n>> ");
scanf("%s",comm);
int exitvar = 0;
while(exitvar == 0){
// PARA TRAER DATOS DEL SERVIDOR, ENVIO EL COMANDO, Y ME QUEDO ESPERANDO
if(send(sockfd, comm, MAXDATASIZE-1, 0) == -1){
fprintf(stderr,"Error client: send\n");
exit(1);
}
if(strcmp(comm,"clientlist") == 0){
// GET CONNECTED USERS LIST FROM SERVER
connData *tmpCl = (connData *) malloc(sizeof(connData)*MAXCLIENT);
if (recv(sockfd, tmpCl, sizeof(connData)*MAXCLIENT, 0) == -1) {
fprintf(stderr, "Error client: recv ARRAY CLIENT\n");
exit(1);
}
printf("\n****\tConnected Users\t****\n"); int i;
cantClients = (unsigned) sizeof(*tmpCl) / (unsigned) sizeof(connData);
for(i = 0; i < MAXCLIENT; i++){
if(tmpCl[i].connected == 1){
printf("\nNick: %s\n", tmpCl[i].nick);
printf("IP: %s\n", tmpCl[i].ip);
printf("PORT: %s\n", tmpCl[i].port);
printf("Time: %s", tmpCl[i].connTime);
printf("Connected: %d\n", tmpCl[i].connected);
printf("PID: %d\n", tmpCl[i].pidConn);
printf("**********************************\n");
}
} free(tmpCl);
}else if(strcmp(comm,"sendchat") == 0){
printf("To whom you want to talk?... ");
char *chatNick = (char *) malloc(NICKSIZE);
fgets(chatNick, NICKSIZE, stdin);
fgets(chatNick, NICKSIZE, stdin);
if((strlen(chatNick)>0) && (chatNick[strlen(chatNick)-1] == '\n') ){
chatNick[strlen(chatNick)-1] = '\0';
}
if(send(sockfd, chatNick, NICKSIZE, 0) == -1){
fprintf(stderr, "Error client: send CHAT NICK\n");
}
printf("Type your message...\n");
char *chat_msg = (char *) malloc(MAXMSG);
fgets(chat_msg,MAXMSG,stdin) ;
if((strlen(chat_msg)>0) && (chat_msg[strlen(chat_msg)-1] == '\n') ){
chat_msg[strlen(chat_msg)-1] = '\0';
}
if(send(sockfd, chat_msg, MAXMSG, 0) == -1){
fprintf(stderr, "Error client: send CHAT\n");
}
free(chatNick);
free(chat_msg);
}else{
char *buf = (char*) malloc(MAXDATASIZE); int numbytes;
if ((numbytes = recv(sockfd, buf, MAXDATASIZE, 0)) == -1) {
fprintf(stderr, "Error client: recv\n");
exit(1);
}
buf[numbytes] = '\0'; printf("-> %s\n", buf);
free(buf);
}
if(strcmp(comm, "quit_") != 0){
free(comm); comm = (char*)malloc(MAXDATASIZE);
printf("\nWhats next?... "); scanf("%s",comm);
}else{
close(sockfd);
exitvar = 1;
}
}
return 0;
}
server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <time.h>
#include "socketlib.h"
#include <semaphore.h>
#define SERVER_PORT "50000"
#define BACKLOG 10
#define MAXDATASIZE 256
#define NICKSIZE 25
#define MAXCLIENT 10
#define MAXTIMESIZE 30
#define MAXMSG 1024
// ESTRUCTURA QUE MANEJARA LA LISTA DE CLIENTES
typedef struct{
char nick[NICKSIZE]; // NICK
char ip[NI_MAXHOST]; // IP
char port[NI_MAXSERV]; // PORT
char connTime[MAXTIMESIZE]; // TIMESTAMP
int connected; // STATE
pid_t pidConn; // PROCESS PID
char *msg; // MSG BUFFER
}connData;
// NOT ZOMBIE PROCESSES
void sigchld_handler(int s) {
while (waitpid(-1, NULL, WNOHANG) > 0);
}
connData *client;
int *id;
int main (int argc, char **argv) {
// THE ARRAY OF CLIENTS IS SHARED BETWEEN THE PROCESSES
int smid = shm_open("shm1", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
ftruncate(smid, sizeof(connData)*MAXCLIENT);
// JUST FOR MAXCLIENT 10 CLIENTS AT THE MOMMENT
client = mmap(NULL, sizeof(connData)*MAXCLIENT, PROT_READ | PROT_WRITE, MAP_SHARED, smid, 0);
sem_t *sem; sem = sem_open("sem1", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR, 1);
// THE ARRAY INDEX IS ALSO SHARED
int smid2 = shm_open("shm2", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
ftruncate(smid2, sizeof(int));
id = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, smid2, 0);
sem_t *sem2; sem2 = sem_open("sem2", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR, 1);
sem_wait(sem2);
*id = 0;
sem_post(sem2);
// CONN CONFIG
struct sigaction sa;
sa.sa_handler = sigchld_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
if (sigaction(SIGCHLD, &sa, NULL) == -1) {
fprintf(stderr, "Error server: sigaction\n");
exit(1);
}
int sockfd; // LISTENER
if ((sockfd = get_tcp_listen(SERVER_PORT, BACKLOG)) == -1) {
fprintf(stderr, "Error get_tcp_listen\n");
exit(1);
}
printf("server: waiting for connections...\n");
char ip_num[NI_MAXHOST];
char port_num[NI_MAXSERV];
get_socket_addr(sockfd, ip_num, port_num);
//////////////////////////////////////////////////////////////////
while (1) {
// BLOCKS UNTIL SOMEONE REQUEST CONN
int new_fd;
if ((new_fd = accept(sockfd, NULL, NULL)) == -1) {
fprintf(stderr, "Error server: accept\n");
continue;}
////////////////////////////////////////////////////////
// IP:PORT OF JUST CONNECTED USER
get_socket_addr(new_fd, ip_num, port_num);
get_peer_addr(new_fd, ip_num, port_num);
printf("server: got connection from: %s, %s\n", ip_num, port_num);
////////////////////////////////////////////////////////
// TIMESTAMP OF USER CONN
time_t ltime; ltime = time(NULL);
char timeStamp[MAXTIMESIZE]; strcpy(timeStamp,ctime(&ltime));
////////////////////////////////////////////////////////////////////////
// WELLCOME MESSAGE SENT TO THE CLIENT
char *well = (char*) malloc(MAXDATASIZE);
if (send(new_fd, "Wellcome to the Chat Service!!\n", MAXDATASIZE-1, 0) == -1) {
fprintf(stderr, "Error sending WELLCOME\n");
} free(well);
///////////////////////////////////////////////////////////////
// SAVES IN THE ARRAY OF CLIENTS, THE DATA OF THE CLIENT THAT JUST CONNECTED
int idTmp1;
sem_wait(sem2);
idTmp1 = *id;
sem_post(sem2);
if(sem_wait(sem) == 0){
strcpy(client[idTmp1].ip, ip_num); // IP
strcpy(client[idTmp1].port, port_num); // PORT
strcpy(client[idTmp1].connTime, timeStamp); // TIMESTAMP
client[idTmp1].connected = 1;
}else{
fprintf(stderr, "Error SEM_WAIT\n");
}
sem_post(sem);
sem_wait(sem2); (*id)++; sem_post(sem2);
//////////////////////////////////////////////////////////////
// FORKS A PROCESS TO DEAL WITH THE JUST CONNECTED USER
if (fork() == 0) {
close(sockfd); // CLOSES THE FATHERS SOCKET
int numbytes = 0;
// SAVES THE NICK IN THE ARRAY
char userNick[NICKSIZE];
if(( numbytes = recv(new_fd, userNick, NICKSIZE, 0)) == -1){
fprintf(stderr,"Error rcv\n");
} userNick[numbytes-1] = '\0';
int idTmp2;
sem_wait(sem2);
pid_t pidAct = getpid(); // PID OF THE NEW CREATED FORK
idTmp2 = *id; // ID OF THE USER
idTmp2--;
strcpy(client[idTmp2].nick,userNick);
client[idTmp2].pidConn = pidAct;
idTmp2 = *id;
sem_post(sem2);
//////////////////////////////////////////////////////////////
// SENDS THE LIST OF CONNECTED CLIENTES
if (send(new_fd, id, sizeof(int), 0) == -1) {
fprintf(stderr, "Error send ID\n");
}
if (send(new_fd, client, sizeof(connData)*MAXCLIENT, 0) == -1) { // SEND THE WHOLE LIST
fprintf(stderr, "Error send LIST\n");
}
//////////////////////////////////////////////////////////////
// THE FORK WAITS SOME COMMAND OF THE USER
char *comm = (char*)malloc(MAXDATASIZE);
if( (numbytes = recv(new_fd, comm, MAXDATASIZE-1, 0)) == -1){
fprintf(stderr,"Error rcv COMMAND\n");
}
comm[numbytes] = '\0';
// THE FORK ENTERS IN A LOOP WAITING COMMANDS
int wait = 0;
while(wait == 0){
if(strcmp(comm,"clientlist") == 0){
if (send(new_fd, client, sizeof(connData)*MAXCLIENT, 0) == -1) {
fprintf(stderr, "Error send CLIENT LIST\n");
}
}else if(strcmp(comm,"sendchat") == 0){
char *chatNick = (char *) malloc(NICKSIZE); // WAIT FOR THE CLIENT TO TALK TO
if( (numbytes = recv(new_fd,chatNick, NICKSIZE, 0)) == -1){
fprintf(stderr,"Error server rcv CHAT NICK\n");
} chatNick[numbytes-1] = '\0';
char *chatmsg = (char *)malloc(MAXMSG); // WAIT FOR MSG
if((numbytes = recv(new_fd, chatmsg, MAXMSG, 0)) == -1){
fprintf(stderr,"Error server rcv CHAT\n");
} chatmsg[numbytes-1] = '\0';
int client_id;
sem_wait(sem2);
for(client_id = 0; client_id < *id; client_id++){
if(strcmp(client[client_id].nick, chatNick) == 0){
if(client[client_id].msg != NULL){
free(client[client_id].msg);
}
client[client_id].msg = (char * )malloc(MAXMSG); // COPY THE MESSAGE TO THE DESIRED USER
strcpy(client[client_id].msg, chatmsg);
printf("\nTHE MESSAGE TO: %s IS %s\n", client[client_id].nick, client[client_id].msg);
}
}
sem_post(sem2);
/*
HERE I HAVE THE NICK, SAY, 'client1' OF THE CLIENT TO WHICH I WANT TO TALK.
THE MSG NOW ITS IN HIS MSG BUFFER LIKE ABOVE.
HOW CAN I NOTICE THE FORKED PROCESS HANDLING THE CONNECTION of 'client1'
TO READ THE MESSAGE ?
*/
free(chatmsg);
free(chatNick);
}else if(strcmp(comm,"quit_") == 0){
if (send(new_fd, "Byee!!", MAXDATASIZE-1, 0) == -1) {
fprintf(stderr, "Error send EXIT\n");
}
wait = 1; // FOR EXIT AND CLOSE THE SOCKET
}else{
if (send(new_fd, "Invalid option!", MAXDATASIZE-1, 0) == -1) {
fprintf(stderr, "Error send INVALID\n");
}
}
if(wait == 0){
// WHEN THE FORKED PROCESS HAS FULFILL THE USERS REQUEST, IT JUST WAITS FOR OTHER REQUEST
free(comm); comm = (char*)malloc(MAXDATASIZE);
if((numbytes = recv(new_fd, comm, MAXDATASIZE-1, 0)) == -1){
fprintf(stderr,"Error rcv REQUEST\n");
} comm[numbytes] = '\0';
}
}
if(munmap(client,sizeof(connData)*MAXCLIENT) != 0){ printf("ERROR FREEING MEM\n");}
sem_unlink("sem1"); shm_unlink("shm1");
printf("Connection ended with %d \n", new_fd);
close(new_fd); exit(0);
}
printf("Keep waiting connections.....\n");
close(new_fd); // SOCKET DEL ACCEPT, DEL CLIENTE QUE SE HABIA CONECTADO
//////////////////////////////////////////////////////////////////////////////
}
if(munmap(client,sizeof(connData)*MAXCLIENT) != 0){ printf("ERROR FREEING MEM\n");}
sem_unlink("sem1");
shm_unlink("shm1");
return 0;
}
I'll start by noting that fork() and shared memory are not the best or easiest approach to creating a chat server; if you have the option, I'd recommend doing it a different way (e.g. via multiple threads in a single process, or even a single thread and select(), instead).
Assuming that you are required to use this approach (e.g. because it's stipulated in a class assignment, or something), however... what you're missing is an IPC notification mechanism, i.e. (as you say) a way for process C2 to notice that process C1 has some data for C2 to handle.
There are a couple of ways to go about implementing notification... the quick-and-dirty way (which might be good enough for getting a class assignment done) is simply to have each process poll the shared memory area; e.g. have each process check its portion of the shared memory area every 100 milliseconds and see if anything has changed since last time. In a scenario like that, C1 might write some new data to the shared memory area, and then increment an integer value in the shared memory area when it's done writing. The next time C2 wakes up to check the integer, it can notice that the integer's value is different from what it was on the previous check, and take that as its cue that there is fresh data in the shared memory area for it to handle. (side note: when using shared memory you should be serializing access to the shared memory regions somehow, otherwise you risk encountering race conditions where e.g. C2 starts reading memory at the same time C1 is still writing it. The symptom of that would be a system that works correctly 99.999% of the time but occasionally does something weird/wrong when the timing is "just right")
If you want a less hackish method of notification, however (i.e. one that doesn't eat CPU cycles 24/7 and doesn't cause an unnecessary 100mS of latency on every trip through the server), then you'll need to choose a notification mechanism. One mechanism would be to use the (arguably misnamed) kill() system call to send a UNIX signal to C2's process; C2 will then need to have a signal handler installed that causes it to do the right thing when it receives a signal of that type.
If you want to avoid Unix signals, however (and I try to avoid them because they are rather antiquated and ugly), you'll want a gentler mechanism by which the C2 process can be awoken when either (an IPC notification is received) or (I/O data is received), whichever condition happens first... a naive blocking-I/O-call mechanism is insufficient since it only allows your process to wait for one thing or the other, but not both. A good way to get both is to use select() or poll() to monitor two sockets simultaneously: one socket is the TCP connection to the client, and the other socket is a UDP socket that the process has set up specifically to receive IPC notifications. Each forked process sets up a UDP socket listening on a particular port, and when C1 wants to wake up C2, C1 does so by send()-ing a UDP packet to C2's UDP port. (The contents of the UDP packet don't matter, since its only purpose is to cause C2 to return from select() and, since the UDP socket has selected as ready-for-read, C2 then knows to read the packet from the UDP socket, throw the packet away, and then checked the shared memory region for fresh data.
(Of course, once you've done all that, you might find it easier for C1 to simply include C2's data in the UDP packet itself so C2 doesn't have to muck about with the potentially-racy shared memory region, but that's up to you)
You are almost finish #Emiliano, that's the point I also got stuck once ;).
Well, you have some options to tell to other process about message.
1. You always look for your own message buffer (this is bad, consume much CPU and also a bad idea)
Process C1 looks always in shared memory for C1, its own, buffer and check if there is new message, and send to client.
2. You use signal ( better than 1)
a. Client C2 send a message for C1.
b. Process C2 store message in C1 buffer on shared memory.(If i correctly understand your shared memory structure)
c. Process C2 send a signal to C1 to notify that I have placed a message for you in your buffer.(In this case, you need to know which pid handles which client)
d. Upon getting signal from a process, C1 check its buffer and send to its client.
EDIT:
It seems you are having trouble in signal.
Here is a simple snippet which show signal sending/catching.
recvsig.c
static void handler(int sig, siginfo_t *si, void *data)
{
printf("%s = %d\n", "Got a signal, signal number is ", sig);
//you can also code here what you want, after getting a signal
}
void init_signal()
{
struct sigaction act;
act.sa_sigaction = handler;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO;
sigaction(SIGRTMIN + 1, &act, NULL);
}
void main(int argc, char **argv)
{
printf("%s %d\n", "PID", getpid());
init_signal();
while(1)
{
pause();
{
printf("%s\n", "Received a signal");
//code here anything after you got signal
}
}
return;
}
sendsig.c
void main(int argc, char **argv)
{
int pid = atoi(argv[1]);
while(1)
{
sleep(5);
{
kill(pid, SIGRTMIN+1);
printf("%s %d\n", "Sent a signal to ", pid);
}
}
return;
}
In your program , call init_signal() after each forked - in child process.
Be sure you manage pid list of all forked process + connected clients.
And use kill() to signal the correct pid.

Resources