Question

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.

Was it helpful?

Solution

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);

OTHER TIPS

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.

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;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top