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.
Related
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;
}
GOAL: write a simple packet filter using BPF. The packet filter should allow you to choose the interface.
PROBLEM: if I uncomment the third to last instruction in the code (where there is a call to recvfrom, the execution just hangs and I can't see no output (neither "buffer zeroed" which I should be able to see in the stdout).
QUESTIONS: 1) how can I fix it? 2) why the programs hangs during the execution and doesn't show the first printf output? 3) how can I receive from ANY interface?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <linux/filter.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <net/if.h>
#define DEFAULT_IF "wlan0"
/* definisco programma bpf */
/* tcpdump -i lo icmp -dd */
struct sock_filter bpfcode[] = {
{ 0x28, 0, 0, 0x0000000c }, /* (000) ldh [12] */
{ 0x15, 0, 3, 0x00000800 }, /* (001) jeq #0x800 jt 2 jf 5 */
{ 0x30, 0, 0, 0x00000017 }, /* (002) ldb [23] */
{ 0x15, 0, 1, 0x00000001 }, /* (003) jeq #0x1 jt 4 jf 5 */
{ 0x6, 0, 0, 0x00040000 }, /* (004) ret #262144 */
{ 0x6, 0, 0, 0x00000000 }, /* (005) ret #0 */
};
int main(int argc, char *argv[])
{
struct sock_fprog bpf = {
sizeof(bpfcode) / sizeof(struct sock_filter),
bpfcode
};
socklen_t saddr_len = sizeof(struct sockaddr_ll);
struct sockaddr_ll addr;
unsigned char *buffer;
char ifname[IFNAMSIZ];
int ret, sfd, rval;
buffer = calloc(1, 65536);
if (!buffer) {
perror("calloc");
return -1;
}
// prendi nome interfaccia
if (argc > 1)
strcpy(ifname, argv[1]);
else
strcpy(ifname, DEFAULT_IF);
// creazione raw socket
sfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (sfd < 0) {
perror("socket");
return -1;
}
// attacco filtro alla socket
ret = setsockopt(sfd, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf));
if (ret < 0) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
// quando si usa packet socket bisogna settare sll_protocol e
// sll_ifindex se si vuol fare il bind ad una specifica interfaccia
memset(&addr, 0, sizeof(addr));
addr.sll_family = AF_PACKET;
addr.sll_protocol = htons(ETH_P_ALL);
addr.sll_ifindex = if_nametoindex(ifname);
printf("index %d", addr.sll_ifindex);
// viene assegnato un indirizzo al socket
if (bind(sfd, (struct sockaddr *) &addr,
sizeof(struct sockaddr_ll)) == -1) {
perror("bind");
exit(EXIT_FAILURE);
}
// ricevo traffico
if (!buffer[0])
printf("buffer zeroed");
// rval = recvfrom(sfd, buffer, 65536, 0, (struct sockaddr *)&addr,
// &saddr_len);
if (buffer[0])
printf("something was written in the buffer");
return 0;
}
How do I fix it?
What do you want to fix exactly? See below.
Why the programs hangs during the execution and doesn't show the first printf output?
Both printf() do work, except you're not printing any line breaks ('\n') at the end of your messages, so the system does not flush your message to the console. Just end your messages with line breaks and you will see your messages as expected.
As for the hang, this is simply because recvfrom() waits until a packet arrives. Well, not just any packet in your case, since you are filtering on ICMP. Ping your interface from the outside, and the program should resume. Alternatively, for debugging your C program, just keep { 0x6, 0, 0, 0x00040000 } (return non-zero) in your BPF program, and any received packet should do.
How can I receive from ANY interface?
How to bind a socket to multiple interfaces
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.
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);
}
i wrote a client application in c using the posix sockets api on linux that sends information to a server, which then gets printed to the servers terminal window. If the server is on the same machine as the client and the client sends to the loopback or to its own IP address then all is good. However, if the server is running on another machine, then sendto returns an "invalid argument" error.
Here is the code for the client application:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include "utils.h" // for 'errexit()'
#define PEER_PORT 54321
#define BUFFSIZE 100
#define local_net_ip "192.168.0.10"
int main( int argc, char *argv[] )
{
int clientfd;
clientfd = socket( AF_INET, SOCK_DGRAM, 0 );
if( clientfd == -1 )
errexit( "socket()" );
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons( PEER_PORT );
if( inet_pton( AF_INET, local_net_ip, (void *) &(servaddr.sin_addr) ) == -1 )
errexit( "inet_pton()" );
char addrStr[ INET_ADDRSTRLEN ];
inet_ntop( servaddr.sin_family, &(servaddr.sin_addr), addrStr, INET_ADDRSTRLEN );
printf("Server IPv4 addr: [ %s ]\n", addrStr);
char buff[ BUFFSIZE ];
int writebytes;
for( ;; ) {
printf( "Enter text: ");
fgets( buff, BUFFSIZE, stdin );
writebytes = sendto( clientfd, buff, BUFFSIZE, 0, (struct sockaddr *) &servaddr, sizeof( struct sockaddr_in ) );
if( writebytes == -1 )
errexit( "sendto()" );
}
exit( EXIT_SUCCESS );
}
I would greatly appreciate any information as to why the application won't send to another computer! Thanks in advance!!
clientfd = socket( AF_INET, SOCK_DGRAM, 0 );
You are creating a socket with protocol 0, e.g. IP. But later you use and IP:Port as a target. You probably wanted to use an UDP socket here (proto 17).