Thread-safe programming

Greetings!
I wrote a ping library for Linux using raw sockets to use in my application (piping ping is not an option) and it is working fine if I use it without threads.
However if I try to ping multiple hosts (each host in a separate thread) I get mixed responses in both threads, e.g. thread 1 pinging 192.168.1.100 and thread 2 pinging 192.168.1.200: sometimes the answers are: "ping to 192.168.1.100: reply from 192.168.1.200", etc. Is there any way to prevent raw sockets from capturing packets that are't sent through them ?

This is my code:
ping.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#ifndef PING_H
#define PING_H

#include <stdio.h>

#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/timex.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>

typedef struct PingReply PingReply;

struct PingReply {
  char *DAddr, *RAddr;
  unsigned short icmp_type, icmp_code;
  double msec;
  char *error;
};

class Ping
{
public:
  Ping(char *destination, unsigned int size, unsigned int timeout);
  PingReply Send();
private:
  unsigned int id, seq, size, timeout;
  char *destination;
  unsigned short ip_checksum(void* vdata,size_t length);
};

#endif // PING_H 


Ping.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#include "ping.h"

static int difference_micro(struct ntptimeval *bBefore,
struct ntptimeval *aAfter)
{
        return (signed long long) aAfter->time.tv_sec * 1000000ll +
               (signed long long) aAfter->time.tv_usec -
               (signed long long) bBefore->time.tv_sec * 1000000ll -
               (signed long long) bBefore->time.tv_usec;
}

Ping::Ping(char *destination, unsigned int size, unsigned int timeout)
{
  this->destination = destination;
  this->size = size;
  this->timeout = timeout;
  this->id = getpid();
  this->seq = 0;
}

PingReply Ping::Send()
{
  PingReply result;
  result.DAddr = destination;
  result.error = "";

  struct ntptimeval start;
  ntp_gettime(&start);
  int sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
  if (sock==-1)
    {
      result.error = strerror(errno);
      return result;
    }

  timeval tv_timeout;
  tv_timeout.tv_sec = 0;
  tv_timeout.tv_usec = timeout*1000;
  int rc = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv_timeout, sizeof(timeval));
  if (rc==-1)
    {
      result.error = strerror(errno);
      close(sock);
      return result;
    }

  seq++;
  char *send_buffer = (char*)malloc(size);
  if (!send_buffer)
    {
      result.error = "(ping.cpp:29: malloc() failed. Unable to allocate memory.";
      close(sock);
      return result;
    }

  memset(send_buffer,0,size);
  struct icmphdr IcmpHeader;
  IcmpHeader.type = ICMP_ECHO;
  IcmpHeader.code = 0;
  IcmpHeader.checksum = 0;
  IcmpHeader.un.echo.id = htons(id);
  IcmpHeader.un.echo.sequence = htons(seq);
  IcmpHeader.checksum = this->ip_checksum(&IcmpHeader, sizeof(struct icmphdr));
  memcpy(send_buffer, &IcmpHeader, sizeof(struct icmphdr));

  struct sockaddr_in sa_dest;
  sa_dest.sin_family = AF_INET;
  sa_dest.sin_addr.s_addr = inet_addr(result.DAddr);
  rc = sendto(sock, send_buffer, size, 0, (struct sockaddr*)&sa_dest, sizeof(struct sockaddr_in));
  if (rc==-1)
    {
      result.error = strerror(errno);
      free(send_buffer);
      close(sock);
      return result;
    }

  char *recv_buffer = (char*)malloc(size);
  if (!recv_buffer)
    {
      free(send_buffer);
      close(sock);
      return result;
    }

  struct sockaddr_in sa_reply;
  socklen_t sa_size = sizeof(struct sockaddr_in);
  rc = recvfrom(sock, recv_buffer, size, 0, (struct sockaddr*)&sa_reply, &sa_size);
  struct ntptimeval elapsed;
  ntp_gettime(&elapsed);
  if (rc==-1)
    {
      result.error = "Request timed out";
      free(recv_buffer);
      free(send_buffer);
      close(sock);
      return result;
    }
  result.RAddr = inet_ntoa(sa_reply.sin_addr);

  struct icmphdr *IcmpReplyHeader = (struct icmphdr*)(recv_buffer+20); // ICMP header is after the IP Header which is 20 bytes long
  if (IcmpHeader.un.echo.id != IcmpReplyHeader->un.echo.id) { result.error = "Request timed out"; } // If we capture an ICMP echo reply that's not for our PID, discard it
  if (IcmpHeader.un.echo.sequence != IcmpReplyHeader->un.echo.sequence) { result.error = "Request timed out"; } // If we capture an old ICMP echo reply, discard it
  result.icmp_type = IcmpReplyHeader->type;
  result.icmp_code = IcmpReplyHeader->code;
  result.msec = difference_micro(&start,&elapsed)*0.001;
  free(recv_buffer); free(send_buffer); close(sock);
  return result;
}

unsigned short Ping::ip_checksum(void* vdata,size_t length) {
    // Cast the data pointer to one that can be indexed.
    char* data=(char*)vdata;

    // Initialise the accumulator.
    uint32_t acc=0xffff;

    // Handle complete 16-bit blocks.
    size_t i;
    for (i=0;i+1<length;i+=2) {
        uint16_t word;
        memcpy(&word,data+i,2);
        acc+=ntohs(word);
        if (acc>0xffff) {
            acc-=0xffff;
        }
    }

    // Handle any partial block at the end of the data.
    if (length&1) {
        uint16_t word=0;
        memcpy(&word,data+length-1,1);
        acc+=ntohs(word);
        if (acc>0xffff) {
            acc-=0xffff;
        }
    }

    // Return the checksum in network byte order.
    return htons(~acc);
}


thread.h
1
2
3
4
5
6
7
8
9
10
11
12
#include <QThread>
#include "ping.h"

class Thread : public QThread
{
        public:
        void run();
        void setParams(char *ip, int size, int timeout);
        private:
        Ping *p;
        PingReply pr;
};


thread.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "threads.h"

void Thread::run()
{
        printf("Spawning thread...\n");
        for (int i=0;i<7;i++) {
        pr = p->Send();
        if (strlen(pr.error)) { printf("%s\n",pr.error); }
        else {
        printf("Ping %s: Reply from %s;Response type: %d;Response code: %d;Delay: %.2f\n",
                        pr.DAddr, pr.RAddr, pr.icmp_type, pr.icmp_code, pr.msec);
        }
        }
        printf("Thread finished...\n");
        exec();
}

void Thread::setParams(char *ip, int size, int timeout)
{
        p = new Ping(ip,size,timeout);
}


and
main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "threads.h"

int main(int argc, char **argv)
{
        if (argc < 3) { printf("Usage: %s <size> <timeout> <IP1> [IP2]...\n",argv[0]); }
        for (int i=3;i<argc;i++) {
        Thread *t = new Thread;
        printf("Thread at %p\n", t);
        t->setParams(argv[i],atoi(argv[1]),atoi(argv[2]));
        t->start();
        }
        sleep(5);
        return 0;
}

Is there any way to prevent raw sockets from capturing packets that are't sent through them ?

I haven't used Linux's raw sockets, but the man page says

A raw socket can be bound to a specific local address using the bind(2) call. If it isn't bound, all packets with the specified IP protocol are received.


Sounds like something to look into, after fixing all the memory allocation errors (since you use linux, make sure to run your program through valgrind)
Thank you for the reply. I ended up connect()-ing to the destination host and using send/recv. It works like it should at least for now, tested with 100 different hosts and every thread got the corresponding request/reply pair. Also fixed some nasty memleaks, thanks for the tip with valgrind :)
Cheers.
Topic archived. No new replies allowed.