Yomi Takanashi Yomi Takanashi - 10 days ago 5
C Question

Can't receive packets to raw socket

I'm writing raw socket client (which successfully sends UDP packets) and a server socket, The problem is in the server part.

Im creating a socket in the following way:

int raw_socket = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);


also I tried it with IPPROTO_RAW, but get the same result and I'm binding it:

bind(raw_socket, (struct sockaddr*)&sockstr, sizeof(sockstr))


when trying to receive some packets using the socket, the only payload I receive is "E" (I think that means "Error"), or the socket continues listening but blocks and nothing happens.
How do I receive a UDP packet using a raw socket?
My code:

#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <netinet/in.h>
#include <string.h>
int server(){
int raw_socket = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
if (raw_socket== -1){
perror("Socket_creation_error\n");
return 1;
}

struct sockaddr_in sockstr;
sockstr.sin_addr.s_addr = inet_addr("127.0.0.1");
sockstr.sin_family = AF_INET;
sockstr.sin_port = htons(9090);
socklen_t s = (socklen_t)sizeof(sockstr);

if (bind(raw_socket, (struct sockaddr*)&sockstr, sizeof(sockstr))< 0){
perror("binding_err\n");
return 0;
}
char* msg[256];
memset(msg, 0, 256);

recv(raw_socket, msg, sizeof(msg), 0);
printf(msg);
return 0;
}

void main(){
server();
}

Answer

As raw sockets manual says user@host:~$ man 7 raw:

A protocol of IPPROTO_RAW implies enabled IP_HDRINCL and is able to send any IP protocol that is specified in the passed header. Receiving of all IP protocols via IPPROTO_RAW is not possible using raw sockets.

Another important note also extracted from manual is:

Only processes with an effective user ID of 0 or the CAP_NET_RAW capability are allowed to open raw sockets.

And manual says too:

Starting with Linux 2.2, all IP header fields and options can be set using IP socket options. This means raw sockets are usually needed only for new protocols or protocols with no user interface (like ICMP).

Ok, assuming you need to have IP/UPD headers in hand ...
let's go to work :-)

First of all, we need to let some points clear:

  • In your code above some #include ... headers are missing.
  • IPPROTO_RAW (as manual says) cannot be used to receive all protocols.
  • Why did you define socklen? You could use it ... for example, in bind().
  • char *msg[SIZE] ???    It is a a variable that contains an address -- and in this address begins an array of chars! You need only an array of chars, like this: char msg[SIZE].
  • Remember ... you are using RAW sockets and packets received from these sockets comes with headers. To print your message you need to do an offset in msg which corresponds to ip header plus upd header. (In code below, note that I've added #include <linux/ip.h> and #include <linux/udp.h> to obtain the headers's size).
  • Finally, do a cleanup: in this case only close() the socket :-)

The code ...


main.c

#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>

#include <linux/ip.h> /* for ipv4 header */
#include <linux/udp.h> /* for upd header */

#define ADDR_TO_BIND "127.0.0.1"
#define PORT_TO_BIND 9090

#define MSG_SIZE 256
#define HEADER_SIZE (sizeof(struct iphdr) + sizeof(struct udphdr))

int main(void) {
    int raw_socket;
    struct sockaddr_in sockstr;
    socklen_t socklen;

    int retval = 0; /* the return value (give a look when an error happens)
                     */

    /* no pointer to array!
     * >> It was like "a variable that contains an address -- and in this
     *    address begins an array of chars"! */
    /* now it is simple an array of chars :-)  */
    char msg[MSG_SIZE];
    ssize_t msglen; /* return value from recv() */

    /* do not use IPPROTO_RAW to receive packets */
    if ((raw_socket = socket(AF_INET, SOCK_RAW, IPPROTO_UDP)) == -1) {
        perror("socket");
        return 1; /* here there is no clean up -- retval was not used */
    }

    sockstr.sin_family = AF_INET;
    sockstr.sin_port = htons(PORT_TO_BIND);
    sockstr.sin_addr.s_addr = inet_addr(ADDR_TO_BIND);
    socklen = (socklen_t) sizeof(sockstr);

    /* use socklen instead sizeof()  Why had you defined socklen? :-)  */
    if (bind(raw_socket, (struct sockaddr*) &sockstr, socklen) == -1) {
        perror("bind");
        retval = 1; /* '1' means "Error" */
        goto _go_close_socket;
    }

    memset(msg, 0, MSG_SIZE);

    if ((msglen = recv(raw_socket, msg, MSG_SIZE, 0)) == -1) {
        perror("recv");
        retval = 1;
        goto _go_close_socket;
    }

    if (msglen <= HEADER_SIZE) /* msg  can't be lesser than header! */
        printf("No msg!\n");
    else {
        msg[msglen - 1] = '\0'; /* we need a null character at the end*/
        printf("Your msg _plus_ headers's size is: %s\n",
               msg + HEADER_SIZE);
    }

_go_close_socket:
    close(raw_socket);

    return retval;
}

Ok, now compile the program with:
user@host:~$ gcc -o main main.c

Execute it as root:
root@host:~# ./main

And in another terminal send a message with nc:
    -u specifies UPD to nc
user@host:~$ nc -u 127.0.0.1 9090

That is it!