加入收藏 | 设为首页 | 会员中心 | 我要投稿 常州站长网 (https://www.0519zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 服务器 > 搭建环境 > Linux > 正文

Linux原始套接字实现分析---转

发布时间:2021-01-25 14:29:42 所属栏目:Linux 来源:网络整理
导读:副标题#e# 本文从IPV4协议栈原始套接字的分类入手,详细介绍了链路层和网络层原始套接字的特点及其内核实现细节。并结合原始套接字的实际应用,说明各类型原始套接字的适应范围,以及在实际使用时需要注意的问题。 一、原始套接字概述 链路层原始套接字可以
<tr>
<td>

?????????

……?
  • res?=?dev->hard_header(skb,?dev,?ntohs(proto),?addr,?NULL,?len);?//构造MAC首部?
  • if?(sock->type?!=?SOCK_DGRAM)?{?
  • ??????? skb->tail?=?skb->data;?//SOCK_RAW类型?
  • ??????? skb->len?=?0;?
  • }?
  • ……
  • err?=?memcpy_fromiovec(skb_put(skb,len),?msg->msg_iov,?len);?//拷贝报文数据
  • ……?
  • err?=?dev_queue_xmit(skb);?//发送报文?
  • ……
  • 2.2.4??其它

    a)?????????套接字的绑定

    链路层原始套接字可调用bind()函数进行绑定,让packet_type结构dev字段指向相应的net_device结构,即将套接字绑定到相应的网口上。如2.2.2节报文接收的描述,在接收时如果套接口有绑定就需要进一步确认当前skb->dev是否与绑定网口相匹配,只有匹配的才会将报文上送到相应的套接字。

    sys_bind()->packet_bind()->packet_do_bind()

    b)????????套接字选项

    以下是比较常用的套接字选项

    PACKET_RX_RING:用于PACKET_MMAP收包方式设置接收环形队列

    :用于读取收包统计信息

    c)???????信息查看

    (编辑:常州站长网)

    【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    ??? 当socket()函数的第三个参数protocol为非零的情况下,会调用dev_add_pack()将链路层套接字packet_sock的packet_type结构链到ptype_all链表或ptype_base链表中。????

    void dev_add_pack(struct packet_type?*pt)?
  • {?
  • ??????? ……?
  • ????????if?(pt->type?==?htons(ETH_P_ALL))?{?
  • ??????????????? netdev_nit++;?
  • ??????????????? list_add_rcu(&pt->list,?&ptype_all);?
  • ????????}?else?{?
  • ??????????????? hash?=?ntohs(pt->type)?&?15;?
  • ??????????????? list_add_rcu(&pt->list,?&ptype_base[hash]);?
  • ????????}?
  • ??????? ……?
  • }
  • ????当protocol为ETH_P_ALL时,会将套接字加入到ptype_all链表中。如图2所示,这里创建了两个链路层原始套接字。

    当protocol为其它非0值时,会将套接字加入到ptype_base链表中。如图3所示,协议栈本身也需要注册packet_type结构,图中浅色的两个packet_type结构分别是IP协议和ARP协议注册的,其处理函数分别为ip_rcv()和arp_rcv()。图中另外3个深色的packet_type结构则是链路层原始套接字注册的,分别用于接收类型为ETH_P_IP、ETH_P_ARP和0x0810类型的报文。

    2.2.2??报文接收

    网卡驱动程序接收到报文后,在软中断上下文由netif_receive_skb()处理。首先会逐个遍历ptype_all链表中的packet_type结构,若满足条件“(!ptype->dev || ptype->dev == skb->dev)”,即套接字未绑定或者套接字绑定网口与skb所在网口匹配,就增加报文引用计数并交给packet_rcv()函数处理(若使用PACKET_MMAP收包方式则由tpacket_rcv()函数处理)。

    网卡驱动->netif_receive_skb()->deliver_skb()->packet_rcv()/tpacket_rcv()

    ??? 以非PACKET_MMAP收包方式为例进行说明,packet_rcv()函数中比较重要的代码片段如下。当报文skb到达packet_rcv()函数时,其skb->data所指的数据是不包含MAC首部的,所以对于type为非SOCK_DGRAM(即SOCK_RAW)类型,需要将skb->data指针前移,以便数据部分可以包含MAC首部。最后将skb放到套接字的接收队列sk->sk_receive_queue中,并唤醒用户态进程来读取套接字中的数据。

    ……?
  • if?(sk->sk_type?!=?SOCK_DGRAM)?//即SOCK_RAW类型?
  • ??????? skb_push(skb,?skb->data?-?skb->mac.raw);?
  • ……?
  • __skb_queue_tail(&sk->sk_receive_queue,?skb);?
  • sk->sk_data_ready(sk,?skb->len);?//唤醒进程读取数据?
  • ……
  • PACKET_MMAP收包方式的实现有所不同,tpacket_rcv()函数将skb->data拷贝到与用户态mmap映射的共享内存中,最后唤醒用户态进程来读取数据。由于报文的内容已存放在内核空间和用户空间共享的缓冲区中,用户态可以直接读取以减少数据的拷贝,所以这种方式效率比较高。

    ??? 上面介绍了报文接收在软中断的处理流程。下面以非PACKET_MMAP收包方式为例,介绍用户态读取报文数据的流程。用户态recvmsg()最终调用skb_recv_datagram(),如果套接字接收队列sk->sk_receive_queue中有报文就取skb并返回。否则调用wait_for_packet()等待,直到内核软中断收到报文并唤醒用户态进程。

    sys_recvmsg()->sock_recvmsg()->…->packet_recvmsg()->skb_recv_datagram()

    2.2.3??报文发送

    用户态调用sendto()或sendmsg(),由套接字层最终会调用到packet_sendmsg()。

    sys_sendto()->sock_sendmsg()->__sock_sendmsg()->packet_sendmsg()->dev_queue_xmit()

    ??? 该函数比较重要的函数片段如下。首先进行参数检查及skb分配,再调用驱动程序的hard_header函数(对于以太网驱动是eth_header()函数)来构造报文的MAC头部,此时的skb->data是指向MAC首部的,且skb->len为MAC首部长度(即14)。对于创建时指定type为SOCK_RAW类型套接字,由于在发送时需要自行构造MAC头部,所以将skb->tail指针恢复到MAC首部开始的位置,并将skb->len设置为0(即不使用内核构造的MAC首部)。接着再调用memcpy_fromiovec()从skb->tail的位置开始拷贝报文数据,最终调用网卡驱动的发送函数将报文发送出去。

    注:如果创建套接字时指定type为SOCK_DGRAM,则使用内核构造的MAC首部,用户态发送的数据中不含MAC头部数据。

    <table style="width: 100%;" cellspacing="0" cellpadding="0">

    热点阅读