为什么在使用 PACKET_TX_RING 复制到数据缓冲区时出现段错误?

Why do I get a seg fault when copying to data buffer with PACKET_TX_RING?

以下代码在第 33 次迭代时生成分段错误。有问题的代码行是 memcpy(data, datagram, 4096);。一个简单的 memcpy 怎么会产生分段错误?

我的程序在gdb中的输出如下:

(gdb) r
Starting program: /home/yaakov/mmap/minimal 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Iteration 0
Iteration 1
Iteration 2
Iteration 3
Iteration 4
Iteration 5
Iteration 6
Iteration 7
Iteration 8
Iteration 9
Iteration 10
Iteration 11
Iteration 12
Iteration 13
Iteration 14
Iteration 15
Iteration 16
Iteration 17
Iteration 18
Iteration 19
Iteration 20
Iteration 21
Iteration 22
Iteration 23
Iteration 24
Iteration 25
Iteration 26
Iteration 27
Iteration 28
Iteration 29
Iteration 30
Iteration 31
Iteration 32
Iteration 33

Program received signal SIGSEGV, Segmentation fault.
0x00005555555556e0 in main (argc=1, argv=0x7fffffffe618) at mmap/minimal.c:151
151         memcpy(data, datagram, 4096);
(gdb) bt
#0  0x00005555555556e0 in main (argc=1, argv=0x7fffffffe618) at mmap/minimal.c:151

产生这个段错误的最小复制器如下:

#include <string.h>
#include <sys/mman.h>
#include <stdint.h>
#include <sys/socket.h>
#include <features.h>    /* for the glibc version number */
#if __GLIBC__ >= 2 && __GLIBC_MINOR >= 1
#include <netpacket/packet.h>
#include <net/ethernet.h>     /* the L2 protocols */
#else
#include <asm/types.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>   /* The L2 protocols */
#endif
#include <arpa/inet.h> 
#include <stdio.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <net/if.h>

volatile struct tpacket_hdr * ps_header_start;
volatile int fd_socket;
volatile int data_offset = 0;
volatile struct sockaddr_ll *ps_sockaddr = NULL;
static int mode_loss     = 0;
static char * str_devname= NULL;
static int c_buffer_sz   = 1024*8;
static int c_buffer_nb   = 1024;
static int c_sndbuf_sz   = 0;
struct tpacket_req s_packet_req;

int main( int argc, char ** argv )
{
    uint32_t size;
    struct sockaddr_ll my_addr;
    struct ifreq s_ifr;
    int i_index = 0;
    int ec;
    int i_ifindex;
    int tmp;
    
    fd_socket = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
    if(fd_socket == -1)
    {
        perror("socket");
        return 1;
    }
    
    /* clear structure */
    memset(&my_addr, 0, sizeof(struct sockaddr_ll));
    my_addr.sll_family = PF_PACKET;
    my_addr.sll_protocol = htons(ETH_P_ALL);
    
    str_devname = "enp3s0";
    //strcpy (str_devname, ifname);
        
    /* initialize interface struct */
    strncpy (s_ifr.ifr_name, str_devname, sizeof(s_ifr.ifr_name));
    
    /* Get the broad cast address */
    ec = ioctl(fd_socket, SIOCGIFINDEX, &s_ifr);
    if(ec == -1)
    {
        perror("iotcl");
        return 1;
    }
    
    /* update with interface index */
    i_ifindex = s_ifr.ifr_ifindex;
    
    s_ifr.ifr_mtu = 7200;
    /* update the mtu through ioctl */
    ec = ioctl(fd_socket, SIOCSIFMTU, &s_ifr);
    if(ec == -1)
    {
        perror("iotcl");
        return 1;
    }
    
    /* set sockaddr info */
    memset(&my_addr, 0, sizeof(struct sockaddr_ll));
    my_addr.sll_family = AF_PACKET;
    my_addr.sll_protocol = ETH_P_ALL;
    my_addr.sll_ifindex = i_ifindex;
    
    /* bind port */
    if (bind(fd_socket, (struct sockaddr *)&my_addr, sizeof(struct sockaddr_ll)) == -1)
    {
        perror("bind");
        return 1;
    }
    
    /* prepare Tx ring request */
    s_packet_req.tp_block_size = c_buffer_sz;
    s_packet_req.tp_frame_size = c_buffer_sz;
    s_packet_req.tp_block_nr = c_buffer_nb;
    s_packet_req.tp_frame_nr = c_buffer_nb;
    
    /* calculate memory to mmap in the kernel */
    size = s_packet_req.tp_block_size * s_packet_req.tp_block_nr;
    
    /* set packet loss option */
    tmp = mode_loss;
    if (setsockopt(fd_socket, SOL_PACKET, PACKET_LOSS, (char *)&tmp, sizeof(tmp))<0)
    {
        perror("setsockopt: PACKET_LOSS");
        return 1;
    }
        
    /* send TX ring request */
    if (setsockopt(fd_socket, SOL_PACKET, PACKET_TX_RING, (char *)&s_packet_req, sizeof(s_packet_req))<0)
    {
        perror("setsockopt: PACKET_TX_RING");
        return 1;
    }
    
    /* change send buffer size */
    if(c_sndbuf_sz) {
        printf("send buff size = %d\n", c_sndbuf_sz);
        if (setsockopt(fd_socket, SOL_SOCKET, SO_SNDBUF, &c_sndbuf_sz, sizeof(c_sndbuf_sz))< 0)
        {
            perror("getsockopt: SO_SNDBUF");
            return 1;
        }
    }
    
    /* get data offset */
    data_offset = TPACKET_HDRLEN - sizeof(struct sockaddr_ll);
    
    /* mmap Tx ring buffers memory */
    ps_header_start = mmap(0, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd_socket, 0);
    if (ps_header_start == (void*)-1)
    {
        perror("mmap");
        return 1;
    }

    while (1)
    {
        char * data;
        char datagram[4096];
        struct tpacket_hdr * ps_header;
        int ec_send = 0;
        
        printf("Iteration %d\n", i_index);
        
        ps_header = ((struct tpacket_hdr *)((void *)ps_header_start) + (c_buffer_sz*i_index));
        data = ((void*) ps_header) + data_offset;
        
        memset (datagram, 0, 4096);
        
        memcpy(data, datagram, 4096);
        
        /* update packet len */
        ps_header->tp_len = c_buffer_sz;
        /* set header flag to USER (trigs xmit)*/
        ps_header->tp_status = TP_STATUS_SEND_REQUEST;
        
        //int ec_send;
        static int total=0;
        
        /* send all buffers with TP_STATUS_SEND_REQUEST */
        /* Wait end of transfer */
        //ec_send = sendto(fd_socket,NULL,0,MSG_DONTWAIT,(struct sockaddr *) ps_sockaddr,sizeof(struct sockaddr_ll));
        ec_send = sendto(fd_socket,NULL,0,0,(struct sockaddr *) ps_sockaddr,sizeof(struct sockaddr_ll));
        
        i_index++;
        if (i_index >= c_buffer_nb) {
            i_index = 0;
        }
    }
}

请注意,接口名称已硬编码为 enp3s0。您可能必须将其更改为系统上的 运行 代码。

编辑:

这里是请求的调试信息:

(gdb) p data
 = 0x7ffff7dbf020 <__GI_tolower> "37", <incomplete sequence 2570>
(gdb) p ps_header_start
 = (volatile struct tpacket_hdr *) 0x7ffff757f000
(gdb) p size
 = 8388608
(gdb) 

将十进制 8388608 转换为十六进制得到 800000。将 800000 添加到 7ffff757f000 得到 7FFFF7D7F000data 指针确实越界了。

问题在这里:

ps_header = ((struct tpacket_hdr *)((void *)ps_header_start) + (c_buffer_sz*i_index));

您正在使用 ps_header_start,将其转换为 struct tpacket_hdr *,然后添加缓冲区大小。但是当你在 C 中添加一个指针时,添加的值乘以指向类型的大小。换句话说,您实际上是在向起始指针添加 c_buffer_size * i_index,而不是向其添加 c_buffer_sz * i_index * sizeof(struct tpacket_hdr)

计算下一帧槽地址的正确方法是:

ps_header = (struct tpacket_hdr *)((char *)ps_header_start + (c_buffer_sz * i_index));

将起始指针转换为char *(因为char的大小为1),然后将帧大小和索引的乘积添加到其中,然后将其转换回struct tpacket_hdr *.