【问题标题】:create SOCK_RAW socket just for sending data without any recvform()创建 SOCK_RAW 套接字仅用于发送数据而无需任何 recvform()
【发布时间】:2012-08-17 10:21:26
【问题描述】:

如果我创建一个类型为 SOCK_RAW 的套接字只是为了发送一些数据而不接收任何数据,那么当内核继续接收网络数据包并将其数据报复制到某个缓冲区(应用程序?)时是否有任何问题。换句话说,在 somebuffer 被填满之后会发生什么?错误还是忽略?

我不知道如何防止内核将数据报副本传递给我的应用程序。

参考http://sock-raw.org/papers/sock_raw0x4 raw_input

IP 层处理之后 一个新的传入 IP 数据报,它调用 ip_local_deliver_finish() 内核函数 它负责调用已注册的传输协议处理程序 检查 IP 标头的协议字段(请记住上面的内容)。然而 在将数据报传递给处理程序之前,它每次都检查是否有 应用程序创建了一个具有 same 协议号的原始套接字。如果有 是一个或多个这样的应用程序,它会复制数据报并传递 也送给他们。

【问题讨论】:

  • “从内核复制到某个缓冲区”是什么意思?发送时则相反:数据从您的应用程序复制到套接字发送缓冲区。
  • `如果应用程序创建了具有相同协议号的原始套接字。如果有一个或多个这样的应用程序,它会制作数据报的副本并将其传递给它们。应用程序将调用 recvfrom() 来接收副本。问题是我不调用 recvform() 复制只是使用套接字发送原始数据包。那么副本存储在哪里?会被填满吗?然后……

标签: linux sockets raw-sockets


【解决方案1】:

您可以使用 shutdown(2) 来关闭套接字的接收部分。 见shutdown man page

编辑: 我发现 shutdown 仅适用于已连接(即 TCP)的套接字。 使用 Raw socket,有两种可能性:

  • 将数据接收到临时缓冲区(使用 recv)并丢弃它们(可能在其他线程中)
  • 如果我没记错的话,当套接字缓冲区已满时,传入的数据会自动丢弃(并且缓冲区中的数据不会被修改),因此您可以将套接字接收缓冲区大小设置为 0(如果需要,稍后再增加) )。

以下是如何将接收缓冲区大小设置为 0:

int opt = 0;
setsockopt(sock_fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt));

测试

/**
 * @file raw_print_pkt.c
 * @brief 
 * @author Airead Fan <fgh1987168@gmail.com>
 * @date 2012/08/22 12:35:22
 */

#include <stdio.h> 
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>

int main(int argc, char *argv[])
{
    int s;
    ssize_t rn;                 /* receive number */
    struct sockaddr_in saddr;
    char packet[4096];
    int count;

    if ((s = socket(AF_INET, SOCK_RAW, IPPROTO_TCP)) < 0) {
        perror("error:");
        exit(EXIT_FAILURE);
    }

    memset(packet, 0, sizeof(packet));
    socklen_t *len = (socklen_t *)sizeof(saddr);
    int fromlen = sizeof(saddr);
    int opt = 0;

    count = 0;
    while(1) {
        if ((rn = recvfrom(s, (char *)&packet, sizeof(packet), 0,
                           (struct sockaddr *)&saddr, &fromlen)) < 0)
            perror("packet receive error:");
        if (rn == 0) {
            printf("the peer has performed an orderly shutdown\n");
            break;
        }

        printf("[%d] rn = %lu \n", count++, rn);

        if (count == 16) {
            if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)) < 0) {
                perror("setsocketopt failed");
            } else {
                fprintf(stdout, "setsocketopt successful\n");
            }
            // int shutdown(int sockfd, int how);
            /* if (shutdown(s, SHUT_RD) < 0) {
             *     perror("shutdown failed");
             * } */
        }
    }

    return 0;
}

测试 2(同样包括):

int main(int argc, char *argv[])
{
int s;
ssize_t rn;                 /* receive number */
char packet[4096];
int count;

if ((s = socket(AF_INET, SOCK_RAW, IPPROTO_TCP)) < 0) {
    perror("error:");
    exit(EXIT_FAILURE);
}

memset(packet, 0, sizeof(packet));
int opt = 0;
count = 0;

//Set recv buffer size
if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)) < 0) {
    perror("setsocketopt failed");
} else {
    fprintf(stdout, "setsocketopt successful\n");
}

//10 seconds countdown
int i = 10;
while(i > 0)
{
    printf("\r%d              ", i);
    fflush(stdout);
    i--;
    sleep(1);
}
printf("\n");
while(1) {
    if ((rn = recv(s, (char *)&packet, sizeof(packet), 0)) <= 0)
        perror("packet receive error:");

    printf("[%d] rn = %lu \n", count++, rn);
    }
return 0;
}

下面是如何进行测试 2:

首先,将缓冲区大小设置为 4096(如果网络上有大量流量,则设置更大)。编译并启动。在开始接收数据前的 10 秒内,向套接字发送大量数据。 10 秒后,程序将收到您在倒计时期间发送的所有内容。

之后,将缓冲区大小设置为 0。如前所述继续。 10 秒后,程序将不会收到您在倒计时期间发送的数据。但是如果你在 recvfrom 中发送数据,它会正常读取它们。

【讨论】:

  • 简单有效!但是当我尝试时,关闭失败:传输端点未连接。虽然shutdown(2) 失败了,recvfrom(2) 返回0 表示对端已经有序关闭。 -_-!还有其他问题吗?
  • 我编辑了您添加测试程序的答案。设置 SO_RECV 效果不佳。
  • 我用你提供的代码做了一些测试。看起来当buffer size为0的时候,还是可以接收到数据的,但是这些数据并不是来自buffer,而是直接来自socket。所以只有在读取套接字时才会收到数据,否则会被丢弃。
  • 添加了修改后的测试,以便您可以检查我所说的
  • 这是一个非常有趣的案例!
【解决方案2】:

我真的不明白你想要什么!如果你只想注入一些数据包,很简单:

#include<netinet/tcp.h> /* TCP header */
#include<netinet/ip.h>  /* IP header */

/* Checksum compute function */
/* source : http://www.winpcap.org/pipermail/winpcap-users/2007-July/001984.html */
unsigned short checksum(unsigned short *buffer, int size)
{
    unsigned long cksum=0;
    while(size >1)
    {
        cksum+=*buffer++;
        size -=sizeof(unsigned short);
    }
    if(size)
        cksum += *(UCHAR*)buffer;

    cksum = (cksum >> 16) + (cksum & 0xffff);
    cksum += (cksum >>16);
    return (unsigned short)(~cksum);
}

int main (int argc, char **argv)
{
    char packet_buffer[BUFFER_SIZE];
    struct sockaddr_in sin;     
    struct iphdr *ip_header;    /* IP header */
    struct tcphdr *tcp_header;  /* TCP header */
    int flag = 1;

    /* Creating RAW socket */
    int raw_socket = socket (PF_INET, SOCK_RAW, IPPROTO_TCP);

    ip_header = (struct iphdr *) packet_buffer;

    tcp_header = (struct tcphdr *) (packet_buffer + sizeof (struct ip));

    sin.sin_family = AF_INET;
    sin.sin_port = htons(PORT_NUMBER);
    sin.sin_addr.s_addr = inet_addr (IP_ADDRESS);

    /* Zeroing the bbuffer */ 
    memset (packet_buffer, 0, BUFFER_SIZE);

    /* Construct your IP Header */
    ip_header->ihl = 5;
    ip_header->version = 4;
    ip_header->tos = 0;
    ip_header->tot_len = sizeof (struct ip) + sizeof (struct tcphdr);
    ip_header->id = htonl(CHOOSE_PACKET_ID);
    ip_header->frag_off = 0;
    ip_header->ttl = 255;
    ip_header->protocol = 6;    /* TCP. Change to 17 if you want UDP */
    ip_header->check = 0;
    ip_header->saddr = inet_addr (SOURCE_IP_ADDRESS_TO_SPOOF);
    ip_header->daddr = sin.sin_addr.s_addr;

    /* Construct your TCP Header */
    tcp_header->source = htons (SOURCE);
    tcp_header->dest = htons(DEST);
    tcp_header->seq = random();
    tcp_header->ack_seq = 0;
    tcp_header->doff = 0;
    tcp_header->syn = 1; 
    tcp_header->window = htonl(65535);
    tcp_header->check = 0;
    tcp_header->urg_ptr = 0;

    /* IP Checksum */
    ip_header->check = checksum((unsigned short *) packet_buffer, ip_header->tot_len >> 1);

    if (setsockopt(raw_socket, IPPROTO_IP, IP_HDRINCL, &flag, sizeof(flag)) < 0)
    {
        /* ERROR handling */
    }

    while (1)
    {
        /* Send the packet */
    if (sendto(raw_socket, packet_buffer, ip_header->tot_len, 0,  (struct sockaddr *) &sin, sizeof (sin)) < 0)
    {
        /* ERROR handling */
    }
    /* The rest of your need */
 }

 return 0;
}

【讨论】:

  • 一旦你的应用程序调用raw_socket = socket (PF_INET, SOCK_RAW, IPPROTO_TCP);', the kernel will deliver all tcp packets to raw_socket',即使应用程序没有调用recvfrom() 系统调用。例如,在应用程序休眠 100 秒后,recvfrom() 将接收内核在 100 秒内传递的所有 tcp 数据包。此外,尽管您在 1000 秒内调用了 recvfrom(),但您仍然会收到从该点(创建 IPPROTO_TCP 套接字)到现在的所有 tcp 数据包。所以 tcp 数据包必须存储在某个地方。可以用tcp数据包填充某个地方吗?
猜你喜欢
  • 1970-01-01
  • 2013-05-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-15
  • 2017-08-31
  • 2013-08-30
  • 2013-09-13
相关资源
最近更新 更多