【问题标题】:Can't receive packets to raw socket无法接收到原始套接字的数据包
【发布时间】:2017-04-09 07:27:25
【问题描述】:

我正在编写原始套接字客户端(成功发送 UDP 数据包)和服务器套接字,问题出在服务器部分。

我通过以下方式创建套接字:

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

我也尝试过使用 IPPROTO_RAW,但得到相同的结果并且我正在绑定它:

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

当尝试使用套接字接收一些数据包时,我收到的唯一有效负载是“E”(我认为这意味着“错误”),或者套接字继续侦听但阻塞并且没有任何反应。 如何使用原始套接字接收 UDP 数据包? 我的代码:

#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();
}

【问题讨论】:

  • 如果你在 linux 上并且 ufw(unix 防火墙)正在运行 UDP 数据包默认被丢弃
  • 也许你想告诉我们你写了什么
  • 什么是“E”?你怎么得到“E”?
  • 您应该发布minimal reproducible example 并告诉我们程序在哪个系统/权限上运行
  • DrPrItay,我已经编辑了帖子并添加了代码

标签: c sockets udp raw-sockets


【解决方案1】:

正如原始套接字手册所说user@host:~$ man 7 raw

IPPROTO_RAW 协议意味着启用了 IP_HDRINCL,并且能够发送任何在传递的标头中指定的 IP 协议。 使用原始套接字无法通过 IPPROTO_RAW 接收所有 IP 协议。

从手册中提取的另一个重要说明是:

只有有效用户 ID 为 0 或具有 CAP_NET_RAW 能力的进程才能打开原始套接字。

手册也说:

从 Linux 2.2 开始,所有 IP 标头字段和选项都可以使用 IP 套接字选项进行设置。这意味着通常只有新协议或没有用户界面的协议(如 ICMP)才需要原始套接字。

好的,假设您需要准备好 IP/UPD 标头,让我们开始工作:-)

首先,我们需要明确几点:

  • 在您上面的代码中,缺少一些 #include ... 标头。
  • IPPROTO_RAW(如手册所述)不能用于接收所有协议。
  • 为什么要定义 socklen?例如,您可以在 bind() 中使用它。
  • char *msg[SIZE] ???这是一个字符指针数组!您只需要一个字符数组,如下所示:char msg[SIZE]
  • 请记住,您使用的是 RAW 套接字,并且从这些套接字接收的数据包带有 标头。要打印您的消息,您需要在 msg 中进行偏移,该偏移对应于 ip 标头加上 upd 标头。 (在下面的代码中,请注意我添加了#include &lt;linux/ip.h&gt;#include &lt;linux/udp.h&gt; 来获取标题的大小)。
  • 最后,进行清理:在这种情况下,只有 close() 套接字 :-)

代码...


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

好的,现在编译程序:
user@host:~$ gcc -o main main.c

以root身份执行:
root@host:~# ./main

并在另一个终端使用 nc 发送消息:
-u 将 UPD 指定为 nc
user@host:~$ nc -u 127.0.0.1 9090

就是这样!

【讨论】:

  • 感谢您的详细解释。很有帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-28
  • 2015-11-26
  • 1970-01-01
相关资源
最近更新 更多