How are socket timeouts enforced in linux - sockets

I'm trying to better understand how socket timeouts and keep-alive work in linux. This is the current snippet of code I'm using.
import socket
def set_keepalive_linux(sock, after_idle_sec=1, interval_sec=3, max_fails=5):
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, after_idle_sec)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, interval_sec)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, max_fails)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
set_keepalive_linux(s)
s.connect(("www.python.org" , 80))
s.recv(1024)
Now, running netstat I see the timer set for this connection which makes sense.
tcp 0 0 myhost:randomport 199.232.44.223:http ESTABLISHED 81658/python3 keepalive (0.88/0/0)
Now, if I replace this code with
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(10)
s.connect(("www.python.org" , 80))
s.recv(1024)
I'm unable to see any timer set (I understand that the timer in netstat -o is specifically for keepalive/retransmission so off is the expected result on rerunning netstat but is it maintained elsewhere?). So my question would be, are socket timeouts expected to be implemented by the application and the kernel has nothing to do with it?
[[Edit 1]]
Seems like this is as expected for python. I've hacked together a C program from various parts of stackoverflow that does roughly the same. Would like to see if there's some way I can access the timeout information somewhere for this
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main(void)
{
int socket_desc;
struct sockaddr_in server_addr;
char server_message[2000], client_message[2000];
// Clean buffers:
memset(server_message,'\0',sizeof(server_message));
memset(client_message,'\0',sizeof(client_message));
// Create socket:
socket_desc = socket(AF_INET, SOCK_STREAM, 0);
if(socket_desc < 0){
printf("Unable to create socket\n");
return -1;
}
printf("Socket created successfully\n");
// Set port and IP the same as server-side:
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(80);
server_addr.sin_addr.s_addr = inet_addr("199.232.44.223");
struct timeval timeout;
timeout.tv_sec = 10;
timeout.tv_usec = 0;
if (setsockopt (socket_desc, SOL_SOCKET, SO_RCVTIMEO, &timeout,
sizeof timeout) < 0)
error("setsockopt failed\n");
if (setsockopt (socket_desc, SOL_SOCKET, SO_SNDTIMEO, &timeout,
sizeof timeout) < 0)
error("setsockopt failed\n");
// Send connection request to server:
if(connect(socket_desc, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0){
printf("Unable to connect\n");
return -1;
}
printf("Connected with server successfully\n");
// Receive the server's response:
if(recv(socket_desc, server_message, sizeof(server_message), 0) < 0){
printf("Error while receiving server's msg\n");
return -1;
}
printf("Server's response: %s\n",server_message);
// Close the socket:
close(socket_desc);
return 0;
}

Related

C: Server/client socket program - Client error connecting

I'm new to networking and trying to create a simple client, server socket program in C, where arguments determine whether the program should run as a client or server. I did this by using simple if statements (if a flag is given, run as server, else run as client), but I'm not sure how to test this. I run my code with the argument to be a server in one terminal (on localhost and port number 3000 for example), and open another terminal and run the code with the argument to be a client (also on localhost and the same port).
The expected result is to see the client prompt the user for a message (if connected successfully), and send that message to the server, which prints out the message, however, I don't get the prompt on the client terminal to enter a message.
(I got the code for server and client behavior from one of many websites online, but they separate the client.c and server.c, whereas I want to combine both into one .c program)
Here's my code below, the error is triggered by
if (connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0)
error("ERROR connecting");
in the client section of the code.
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
void error(char *msg)
{
perror(msg);
exit(1);
}
int main(int argc, char *argv[])
{
int sockfd, newsockfd, portno, clilen;
char buffer[256];
struct sockaddr_in serv_addr, cli_addr;
int n, i, server = 0; // 1 = server, 0 = client
// check if server or client
for (i = 0; i<argc; i++) {
if (strcmp(argv[i], "-l") == 0)
server = 1;
}
// client
if (server == 0) {
struct hostent *server;
if (argc < 3) {
fprintf(stderr,"usage %s hostname port\n", argv[0]);
exit(0);
}
portno = atoi(argv[2]);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
error("ERROR opening socket");
server = gethostbyname(argv[1]);
if (server == NULL) {
fprintf(stderr,"FOUR*** ERROR, no such host\n");
exit(0);
}
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
bcopy((char *)server->h_addr,
(char *)&serv_addr.sin_addr.s_addr,
server->h_length);
serv_addr.sin_port = htons(portno);
if (connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0)
error("ERROR connecting");
printf("Please enter the message: ");
bzero(buffer,256);
fgets(buffer,255,stdin);
n = write(sockfd,buffer,strlen(buffer));
if (n < 0)
error("ERROR writing to socket");
bzero(buffer,256);
n = read(sockfd,buffer,255);
if (n < 0)
error("ERROR reading from socket");
printf("%s\n",buffer);
return 0;
}
// server
if (server == 1) {
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
error("ERROR opening socket");
bzero((char *) &serv_addr, sizeof(serv_addr));
portno = atoi(argv[1]);
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(portno);
if (bind(sockfd, (struct sockaddr *) &serv_addr,
sizeof(serv_addr)) < 0)
error("ERROR on binding");
listen(sockfd,5);
clilen = sizeof(cli_addr);
newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
if (newsockfd < 0)
error("ERROR on accept");
bzero(buffer,256);
n = read(newsockfd,buffer,255);
if (n < 0) error("ERROR reading from socket");
printf("Here is the message: %s\n",buffer);
n = write(newsockfd,"I got your message",18);
if (n < 0) error("ERROR writing to socket");
return 0;
}
}
Here's the exact error output:
In one terminal window, I run the program as a server first:
$ ./socketz -l localhost 2003
Then in another terminal window, I run the program as a client:
$ ./socketz localhost 2003
ERROR connecting: Connection refused
The reason you can't connect is because your server process is not listening on port 2003. In particular, on this line:
serv_addr.sin_port = htons(portno);
The value of portno is zero, which causes the value of serv_addr.sin_port to also be zero, which accept() interprets as meaning that it should just pick an available TCP port to bind to.
The root of the problem is here:
portno = atoi(argv[1]);
... that line assigns a value to portno based on the first argument you entered when running the program, but you entered this:
./a.out -l localhost 2003
So the first argument is "-l", which is a non-number so it will cause atoi() to return 0. I think what you intended was portno = atoi(argv[3]); instead.

Epoll events(EPOLLLT) only triggered once on udp socket

From online resource, they said that if epoll listening on file descriptors using default mode(level trigger), when the fd(file descriptor) is ready to read and the buffer data associated with fd not fully consumed, epoll will continue to trigger until all data is consumed, however, when I test with epoll(LT mode) listening on udp socket, when multiple characters comes epoll only trigger once.
the process like below:
step 1: create epoll, udp socket fd, then make epoll listening on socket for write event.
step 2: send multiple characters("abc") to the udp socket
step 3: each time epoll triggered, then read 1 character from the udp socket.
I am expecting that epoll trigger three times as udp socket receive 3 characters, but the result is epoll only trigger once.
here is my code:
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define BUFFER_SIZE 512
#define log(fmt, arg...) printf(""fmt, ##arg)
void main(){
int fd1,efd, fds, i, fd;
int ret, addr_len;
struct epoll_event g_event;
struct epoll_event *epoll_events_ptr;
char buffer[BUFFER_SIZE] = {0};
struct sockaddr_in addr1;
fd1 = socket(AF_INET, SOCK_DGRAM, 0);
if (fd1 == -1) {
log("create socket fail \r\n");
return ;
}
addr1.sin_family = AF_INET;
addr1.sin_addr.s_addr = INADDR_ANY;
addr1.sin_port = htons(3500);
addr_len = sizeof(struct sockaddr_in);
if (0 != bind(fd1, (struct sockaddr *)&addr1, sizeof(struct sockaddr_in))) {
log("bind local listening addr fail,errno : %d \r\n", errno);
goto err;
}
efd = epoll_create1(0);
if (efd == -1) {
log("create epoll fail \r\n");
goto err;
}
log("create epoll instance success \r\n");
epoll_events_ptr = (struct epoll_event *)calloc(2, sizeof(struct epoll_event));
if (epoll_events_ptr == NULL) {
log("calloc fail \r\n");
goto err;
}
g_event.data.fd = fd1;
g_event.events = EPOLLIN;
epoll_ctl(efd, EPOLL_CTL_ADD, fd1, &g_event);
while(1) {
fds = epoll_wait(efd, epoll_events_ptr, 2, -1);
for (i = 0; i<fds; i++)
{
if (epoll_events_ptr[i].events & EPOLLIN)
{
ret = recv(fd1, buffer, 1, MSG_DONTWAIT);
if(ret != -1)
log("recv msg : %s \n", buffer);
}
memset(buffer, 0, BUFFER_SIZE);
}
}
err:
close(fd1);
if(epoll_events_ptr)
free(epoll_events_ptr);
return ;
}
enter image description here
You are treating UDP as though it was a streaming protocol, i.e. TCP. It isn't. It is a datagram protocol. If you read a UDP datagram into a buffer that is too small to receive it, the remainder of the datagram is discarded. Not left in the buffer for next time.
Reading one character at a time is therefore pointless in UDP, not to mention extremely inefficient in any protocol.
NB You don't need the memset(), and this:
log("recv msg : %s \n", buffer);
is invalid. It should be:
log("recv msg : %.*s \n", ret, buffer);
You can't assume the received data is null-terminated.

How to use select() function for both TCP & UDP connection?

I have a functional server code with only TCP connection. Now I want the server to receive from UDP connection. I have used port 2000 for TCP & port 2001 for UDP. Here is a snippet of my code
struct timeval timeout; // timeout for select(), 1ms
timeout.tv_sec = 0;
timeout.tv_usec = 1000;
fd_set master; // master file descriptor list
fd_set read_fds; // temp file descriptor list for select()
int fdmax; // maximum file descriptor number
FD_ZERO(&master); // clear the master and temp sets
FD_ZERO(&read_fds);
// TCP port setup
int sockfd; // listening socket descriptor
int newsockfd; // newly accept()ed socket descriptor
struct sockaddr_storage remoteaddr; // client address
socklen_t addrlen;
char buf_tcp[256]; // buffer for client data
char buf_copy_tcp[256];
int recv_bytes;
char remoteIP[INET6_ADDRSTRLEN];
int yes=1; // for setsockopt() SO_REUSEADDR
int i, k, rv_getaddrinfo, rv_setsockopt, rv_bind, rv_listen, rv_select;
struct addrinfo hints, *servinfo, *ptr;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
rv_getaddrinfo = getaddrinfo(NULL, "2000", &hints, &servinfo);
for(ptr=servinfo; ptr!=NULL; ptr=ptr->ai_next)
{
sockfd = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
rv_setsockopt = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
rv_bind = bind(sockfd, ptr->ai_addr, ptr->ai_addrlen);
break;
}
if (ptr == NULL)
{
fprintf(stderr, "CLI Server error: failed to bind\n\r");
exit(2);
}
freeaddrinfo(servinfo); // all done with this
rv_listen = listen(sockfd, 10);
////////////////////////////////////////////////////////////////////////////////////////////////
// UDP port setup
int sockfd_udp; // listening socket descriptor
struct sockaddr_storage remoteaddr_udp; // client address
socklen_t addrlen_udp;
char buf_udp[256]; // buffer for client data
char buf_copy_udp[256];
int recv_bytes_udp;
char remoteIP_udp[INET6_ADDRSTRLEN];
int yes_udp=1; // for setsockopt() SO_REUSEADDR
int j, rv_getaddrinfo_udp, rv_setsockopt_udp, rv_bind_udp;
struct addrinfo hints_udp, *servinfo_udp, *ptr_udp;
memset(&hints_udp, 0, sizeof(hints_udp));
hints_udp.ai_family = AF_UNSPEC;
hints_udp.ai_socktype = SOCK_DGRAM;
hints_udp.ai_flags = AI_PASSIVE;
rv_getaddrinfo_udp = getaddrinfo(NULL, "2001", &hints_udp, &servinfo_udp);
for(ptr_udp=servinfo_udp; ptr_udp!=NULL; ptr_udp=ptr_udp->ai_next)
{
sockfd_udp = socket(ptr_udp->ai_family, ptr_udp->ai_socktype, ptr_udp->ai_protocol);
rv_setsockopt_udp = setsockopt(sockfd_udp, SOL_SOCKET, SO_REUSEADDR, &yes_udp, sizeof(int));
rv_bind_udp = bind(sockfd_udp, ptr_udp->ai_addr, ptr_udp->ai_addrlen);
break;
}
if (ptr_udp == NULL)
{
fprintf(stderr, "CLI UDP Server error: failed to bind\n\r");
exit(2);
}
freeaddrinfo(servinfo_udp); // all done with this
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// add the listener to the master set
FD_SET(sockfd, &master);
FD_SET(sockfd_udp, &master);
// keep track of the biggest file descriptor
if(sockfd > sockfd_udp)
fdmax = sockfd; // so far, it's this one
else
fdmax = sockfd_udp; // so far, it's this one
do
{
read_fds = master; // copy it
rv_select = select(fdmax+1, &read_fds, NULL, NULL, &timeout);
// run through the existing connections looking for data to read
for(i=0; i<=fdmax; i++)
{
if (FD_ISSET(i, &read_fds))
{ // we got one!!
if (i == sockfd)
{
// handle new connections
addrlen = sizeof(remoteaddr);
newsockfd = accept(sockfd, (struct sockaddr *)&remoteaddr, &addrlen);
FD_SET(newsockfd, &master); // add to master set
if (newsockfd > fdmax) // keep track of the max
fdmax = newsockfd;
inet_ntop(remoteaddr.ss_family, get_in_addr((struct sockaddr*)&remoteaddr), remoteIP, INET6_ADDRSTRLEN);
fprintf(stdout, "CLI Server: new connection from %s on socket %d\n\r", remoteIP, newsockfd);
}
else if (i == sockfd_udp)
{
// handle new udp connections
addrlen_udp = sizeof(remoteaddr_udp);
recv_bytes_udp = recvfrom(i, buf_udp, sizeof(buf_udp), 0, (struct sockaddr *)&remoteaddr_udp, &addrlen_udp);
inet_ntop(remoteaddr_udp.ss_family, get_in_addr((struct sockaddr*)&remoteaddr_udp), remoteIP_udp, INET6_ADDRSTRLEN);
for(j=0; j<=recv_bytes_udp; j++)
{
if( (buf_udp[k] == '\r') | (buf_udp[k] == '\n') )
buf_udp[k] = '\0';
}
fprintf(stdout, "CLI UDP Server: received %s from connection %s\n\r", buf_udp, remoteIP_udp);
}
else
{ // handle data from a client
if ((recv_bytes = recv(i, buf_tcp, sizeof(buf_tcp), 0)) <= 0)
{ // got error or connection closed by client
if (recv_bytes == 0) // connection closed
{
fprintf(stdout, "CLI Server: socket %d hung up\n\r", i);
}
else
{
perror("CLI Server error: recv");
exit(6);
}
close(i); // bye!
FD_CLR(i, &master); // remove from master set
}
else
{
for(k=0; k<=recv_bytes; k++)
{
if( (buf_tcp[k] == '\r') | (buf_tcp[k] == '\n') )
buf_tcp[k] = '\0';
}
fprintf(stdout, "CLI Server: received %s from socket %d\n\r", buf_tcp, i);
}
} // END handle data from client
} // END got new incoming connection
} // END looping through file descriptors
} while(QUIT);
I am doing error checking at each stage but didn't include it in the snippet. When I compile & run this, I can connect to port 2000 but not to 2001, my Tera term terminal closes with connection refused message. Why is the client not able to connect to port 2001 (UDP socket) but connects to port 2000 (TCP socket). The server just responds to client messages until client enters QUIT.
I have modeled this code from Beej's Guide to Network Programming selectserver.c code.
The program that I wrote above was correct but my understanding of socket programming was not. It was a fluke that I managed to write correct code but thanks to #EJP for having an extended discussion in the comments to clear up my doubt.
My mistake was using Teraterm's TCP client to connect to a UDP server. Both communications are mutually exclusive & hence can't communicate with one another. So I had to use a UDP client. Netcat provides an option of UDP client using netcat -u <ip address> <port>. Then my UDP server was able to receive messages from UDP client.
Another mistake was confusing bind() with connect() in DATAGRAM sockets. A connected DGRAM is when I use connect on both server & client.
I thought the problem was with select(), as I wrongly thought that UDP & TCP sockets can't be used simultaneously in select(). But the above code is how you write a UDP/TCP server for multiple client.
Thanks again to Beej & #EJP

systemd-activate socket activation for UDP daemons

I like using systemd-activate(8) for testing socket-activated daemons during development,
however, it seems it only listens for TCP connections:
% /usr/lib/systemd/systemd-activate -l 5700 ./prog
Listening on [::]:5700 as 3.
% netstat -nl |grep 5700
tcp6 0 0 :::5700 :::* LISTEN
I am using a program that handles datagrams (UDP). How can I make systemd-activate listen on a UDP port? Or is there a
simple way to do this using other tools, without going to the trouble of crafting and installing a systemd unit file?
This was recently added to systemd-activate: https://github.com/systemd/systemd/pull/2411, and will be part of systemd-229 when it is released.
I'm not sure that there is a way to do it with systemd-activate.
You may want to employ some .service unit file and a .socket unit file with dependencies. In a .socket unit you will describe ListenDatagram= option. See here for more details.
I ended up writing a simple C program to do this; code below (public domain).
The usage is:
./a.out <port-number> <prog> [<arg1> ...]
The program opens a UDP socket on <port-number>, sets the environment variables that systemd socket-activated daemons expect, then executes <prog> with whatever arguments follow.
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
int main(int argc, char **argv) {
if (argc < 2) {
printf("no port specified\n");
return -1;
}
if (argc < 3) {
printf("no program specified\n");
return -1;
}
uint16_t port = htons((uint16_t) strtoul(argv[1], NULL, 10));
if (port == 0 || errno) {
printf("failed to parse port: %s\n", argv[1]);
return -1;
}
/* create datagram socket */
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) {
printf("failed to open socket; errno: %d\n", errno);
return -1;
}
struct sockaddr_in sa;
sa.sin_family = AF_INET;
sa.sin_port = port;
sa.sin_addr.s_addr = INADDR_ANY;
/* bind socket to port */
int r = bind(fd, (struct sockaddr *) &sa, sizeof(struct sockaddr_in));
if (r < 0) {
printf("bind failed; errno: %d\n", errno);
return -1;
}
/* execute subprocess */
setenv("LISTEN_FDS", "1", 0);
execvp(argv[2], argv + 2);
}

Calling setsockopt many times

I have application which uses sockets to transfer data between two clients. It uses a single socket to communicate control and data traffic (over UDP).
Qos and tos fields of IP header can be changed using
setsockopt(sockfd, IPPROTO_IP, IP_TOS, &tos, toslen);
setsockopt(sockfd, SOL_SOCKET, SO_PRIORITY, &cos, coslen);
But how many calls to setsockopt (to the same socket) is too many?
For example, lets assume it will be called every 1ms.
To narrow question scope, I am asking about modern linux system (generic explanation is more than welcomed).
Here is an example to demonstrate it (this is the sending-only part of the application):
#include <stdlib.h>
#include <memory.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#define NPACK 10000
#define PORT 44444
#define BUFLEN 128
void diep(char *s) {
perror(s);
exit(1);
}
#define SRV_IP "12.34.56.78"
int main(void) {
struct sockaddr_in si_other, si_me;
int s, i, slen = sizeof(si_other);
int toss[2] = { 56, 160 }, coss[] = { 1, 3 };
char buf[BUFLEN];
//Create UDP socket
if ((s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
diep("socket");
//Create remote socket
memset((char *) &si_other, 0, sizeof(si_other));
si_other.sin_family = AF_INET;
si_other.sin_port = htons(PORT);
if (inet_aton(SRV_IP, &si_other.sin_addr) == 0) {
fprintf(stderr, "inet_aton() failed\n");
exit(1);
}
//Create local socket and bind to it.
memset((char *) &si_me, 0, sizeof(si_me));
si_me.sin_family = AF_INET;
si_me.sin_port = htons(PORT);
si_me.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(s, &si_me, sizeof(si_me)) == -1)
diep("bind");
//Loop on number of packets
for (i = 0; i < NPACK; i++) {
sprintf(buf, "This is packet %d, %d\n", i, toss[i % 2]);
printf("Sending packet %d. %s", i, buf);
//Change tos and cos. odd packets high priority , even packets low priority.
setsockopt(s, IPPROTO_IP, IP_TOS, &toss[i % 2], sizeof(int));
setsockopt(s, SOL_SOCKET, SO_PRIORITY, &coss[i % 2], sizeof(int));
//Send!
if (sendto(s, buf, strlen(buf), 0, &si_other, slen) == -1)
diep("sendto()");
}
close(s);
return 0;
}
NOTES:
Both control and data should share the same socket (same UDP source port).
Multiple threads will use the same socket (so some locking mechanism needed between setsockopt and sendto; but this is outside the scope of the question).
SO_PRIORITY is linux only.