4

I am writing a ping program using raw sockets but recvfrom is not returning -1 with EINTR even though a SIGALRM is being handled.This SIGALRM is produced by my alarm(1).I want recvfrom to return so that i can decide that the packet has indeed been lost.

#include "libsock"
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>

double total=0, max=0, min=10000000;
int totalpackets=0, packetslost=0;
int recieved=0;

void handler2()
{
  printf("host unreachable\n");
}

unsigned short 
csum (unsigned short *buf, int nwords)
{
  unsigned long sum;
  for (sum = 0; nwords > 0; nwords--)
    sum += *buf++;
  sum = (sum >> 16) + (sum & 0xffff);
  sum += (sum >> 16);
  return ~sum;
}

void 
handler()
{
  printf("\n");
  printf("--------------PINGING STATISTICS----------------\n");
  printf("AVG:%f MAX:%f MIN:%f TOTAL PACKETS:%d PACKETS LOST:%d SUCCESS PERCENTAGE:%f\n\n",total/(totalpackets-packetslost),max,min,totalpackets,packetslost,((double)(totalpackets-packetslost)/(double)totalpackets)*100);
  exit(0);
}

int 
main (int argc, char *argv[])
{
  if (argc != 2)
  {
    printf ("need destination for tracert\n");
    exit (0);
  }
  int sfd = socket (AF_INET, SOCK_RAW, IPPROTO_ICMP);
  char buf[4096] = { 0 };

  int one = 1;
  const int *val = &one;
  if (setsockopt (sfd, IPPROTO_IP, IP_HDRINCL, val, sizeof (one)) < 0)
    printf ("Cannot set HDRINCL!\n");

  struct sockaddr_in addr;
  struct ip* ip_hdr=(struct ip*)buf;
  addr.sin_port = htons (7);
  addr.sin_family = AF_INET;
  inet_pton (AF_INET, argv[1], &(addr.sin_addr));

  ip_hdr->ip_hl = 5;
  ip_hdr->ip_v = 4;
  ip_hdr->ip_tos = 0;
  ip_hdr->ip_len = 20 + 8 + 64;
  ip_hdr->ip_id =0;
  ip_hdr->ip_off = 64;
  ip_hdr->ip_ttl = 64;
  ip_hdr->ip_p = IPPROTO_ICMP;
  inet_pton (AF_INET, "172.30.104.59", &(ip_hdr->ip_src));
  inet_pton (AF_INET, argv[1], &(ip_hdr->ip_dst));
  ip_hdr->ip_sum = csum ((unsigned short *) buf, 4);

  struct icmphdr *icmphd = (struct icmphdr *) (buf+20);
  icmphd->type = ICMP_ECHO;
  icmphd->code = 0;
  icmphd->checksum = 0;
  icmphd->un.echo.id = 0;
  icmphd->un.echo.sequence =1;

  memset(buf+28,'a',64);
  icmphd->checksum = csum ((unsigned short *) (buf+20), 36);
  signal(SIGINT,handler);
  struct timeval tv1,tv2;  

  printf("Pinging %s with 64 bytes of data.\n\n",argv[1]);

  while(1)
  {
    recieved=0;     
    totalpackets++;
    sendto (sfd, buf,20+ 8+64, 0, SA & addr, sizeof addr);
    char buff[4096] = { 0 };
    struct sockaddr_in addr2;
    socklen_t len = sizeof (struct sockaddr_in);
    signal(SIGALRM,handler2);
    gettimeofday(&tv1,NULL);
    alarm(1);
    int x=recvfrom (sfd, buff, 20+8+64, 0, SA & addr2, &len);
    gettimeofday(&tv2,NULL);
    double   d=(double)(tv2.tv_sec-tv1.tv_sec)*1000+((double)(tv2.tv_usec-tv1.tv_usec))/1000;

    if(x>0) {
      struct icmphdr *icmphd2 = (struct icmphdr *) (buff + 20);
      printf ("Reached destination:%s\tTime elapsed:%f ms\n\n",
          inet_ntoa (addr2.sin_addr),d);
      total+=d;
      if(d>max)
        max=d;
      if(d<min)
        min=d;
    } else {
      printf("Packet lost\n");   
      packetslost++;
    }

    sleep(1);
  }
  return 0;
}

libsock contains headers and SA=(struct sockaddr*)

Is SIGALRM different from other signals, I have not set SA_RESTART..

Thanks.

Anthon
  • 69,918
  • 32
  • 186
  • 246
Sr1n4th
  • 251
  • 2
  • 8
  • Have you considered just setting a socket timeout? – nneonneo Mar 17 '13 at 16:35
  • @nneonneo thanks for that,didnt know about sock timeout before,my problem is resolved but i am still wondering why recvfrom is not working like i think it should. – Sr1n4th Mar 17 '13 at 16:44
  • 3
    1) a signal handler should have the signature `void handler(int signum)` 2) Don't use printf() in a signal handler, it is non-reentrant. – wildplasser Mar 17 '13 at 16:55
  • why couldn't you use `printf` in a signal handler? non-reentrant means that the signal is masked during the call. – Edouard Thiel Mar 17 '13 at 17:01
  • @EdouardThiel: see `man 7 signal` – alk Mar 17 '13 at 17:06
  • 1
    The signal could arrive while printf is inside malloc, to (re-)allocate its buffer. Or just updating its pointers inside this buffer. – wildplasser Mar 17 '13 at 17:07
  • ok `printf` is not a safe function in the POSIX sense, but it is not forbidden either to call it. Any example or FAQ on it, I'm interested to know more. – Edouard Thiel Mar 17 '13 at 17:19
  • 1
    @EdouardThiel: it's tough to make it fail: you have to trigger the signal right while the "outer" `printf` is in the middle of some sensitive operation. You then wind up in the same re-entrancy situation as with any other thread-based or interrupt-driven multiprocessing system. The author of the `printf` inner workings can try to minimize the window for corruption (I did in mine) but there are some things it just doesn't seem worth protecting against. – torek Mar 17 '13 at 17:53
  • possible duplicate of [recvfrom() timeout with alarm()](http://stackoverflow.com/questions/5929309/recvfrom-timeout-with-alarm) – alk Mar 17 '13 at 18:01

3 Answers3

3

signal's exact behaviour is system-dependent. From man 2 signal:

The BSD semantics are equivalent to calling sigaction(2) with the following flags:

sa.sa_flags = SA_RESTART;

The situation on Linux is as follows:

...

By default, in glibc 2 and later, the signal() wrapper function does not invoke the kernel system call. Instead, it calls sigaction(2) using flags that supply BSD semantics.

(emphasis mine)

Thus, by default, on Linux and BSD systems, signal will set SA_RESTART, which will automatically restart your system call so that it never reports EINTR.

You need to use sigaction to get precise control over the flags:

struct sigaction sact = {
    .sa_handler = handle_sigalrm,
    .sa_flags = 0,
};
sigaction(SIGALRM, &sact, NULL);
nneonneo
  • 171,345
  • 36
  • 312
  • 383
2

The behavior of using "plain" signal is to a large extent up to the OS. Assuming POSIX conformance, you should use sigaction instead. See What is the difference between sigaction and signal? for more.

Community
  • 1
  • 1
torek
  • 448,244
  • 59
  • 642
  • 775
1

You should not use signal because it is non portable. Instead, use sigaction.

With the option SA_RESTART, the blocking call will be silently resumed after the signal is caught. Without SA_RESTART, the blocking call stops, returns -1 and set errno to EINTR.

Here is a function mysignal, which you can use to replace your signal call with options set to 0:

int mysignal (int sig, void (*func)(int), int options) {
  int r; 
  struct sigaction act; 

  act.sa_handler = func; 
  act.sa_flags = options; 
  sigemptyset (&act.sa_mask); 

  r = sigaction (sig, &act, NULL); 
  if (r < 0) perror (__func__); 
  return r;
}
Edouard Thiel
  • 5,878
  • 25
  • 33