美文网首页
链路层原始套接字

链路层原始套接字

作者: 分享放大价值 | 来源:发表于2022-01-23 14:40 被阅读0次

    创建套接字的函数原型如下

    int socket(int domain, int type, int protocol);
    

    对于链路层原始套接字来说,第一个参数指定协议族类型为PF_PACKET,第二个参数type可以设置为SOCK_RAW或SOCK_DGRAM,第三个参数是协议类型(该参数只对报文接收有意义)。

    第三个参数protocol的用法,如下表格从参考连接截图


    image.png

    表1中protocol的取值中有两个值是比较特殊的。当protocol为ETH_P_ALL时,表示能够接收本机收到的所有二层报文(包括IP, ARP, 自定义二层报文等),同时这种类型套接字还能够将外发的报文再收回来。当protocol为0时,表示该套接字不能用于接收报文,只能用于发送。

    本文重点从代码角度解释上面的两个特殊值

    当protocol为0时,表示该套接字不能用于接收报文,只能用于发送.
    当protocol为ETH_P_ALL时,表示能够接收本机收到的所有二层报文
    (包括IP, ARP, 自定义二层报文等),同时这种类型套接字还能够将外发的报文再收回来(这句话是不对的,至少在我看的内核代码上不能将外发的报文再收回来)。
    

    协议栈收发包入口

    协议栈收包入口

    static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc)
        ...
        //遍历链表ptype_all将报文复制一份调用每个链表节点的func
        list_for_each_entry_rcu(ptype, &ptype_all, list) {
            if (!ptype->dev || ptype->dev == skb->dev) {
                if (pt_prev)
                    ret = deliver_skb(skb, pt_prev, orig_dev);
                pt_prev = ptype;
            }
        }
        ...
        //ptype_base为hash链表,其将相同type的ptype加入同一个链
        //表。这里根据type遍历链表,并调用ptype->func函数
        type = skb->protocol;
        list_for_each_entry_rcu(ptype,
                &ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {
            if (ptype->type == type &&
                (ptype->dev == null_or_dev || ptype->dev == skb->dev ||
                 ptype->dev == orig_dev)) {
                if (pt_prev)
                    ret = deliver_skb(skb, pt_prev, orig_dev);
                pt_prev = ptype;
            }
        }
    

    协议栈发包入口

    int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev, struct netdev_queue *txq)
        ...
            if (!list_empty(&ptype_all))
                dev_queue_xmit_nit(skb, dev);
        ...
    
    static void dev_queue_xmit_nit(struct sk_buff *skb, struct net_device *dev)
        ...
        //遍历链表ptype_all将报文复制一份调用每个链表节点的func
        list_for_each_entry_rcu(ptype, &ptype_all, list) {
            //注意这里的注释,从不会将报文发回给原始socket,即
            //socket收不到自己发出去的报文
            /* Never send packets back to the socket
             * they originated from - MvS (miquels@drinkel.ow.org)
             */
            if ((ptype->dev == dev || !ptype->dev) &&
                (!skb_loop_sk(ptype, skb))) {
                if (pt_prev) {
                    deliver_skb(skb2, pt_prev, skb->dev);
                    pt_prev = ptype;
                    continue;
                }
        }
        ...
    

    从上面收发包入口函数可看到用到了如下两个全局变量,用来保存注册的ptype,注册函数为dev_add_pack

    struct list_head ptype_base[PTYPE_HASH_SIZE] __read_mostly;
    struct list_head ptype_all __read_mostly;   /* Taps */
    
    void dev_add_pack(struct packet_type *pt)
    {
        struct list_head *head = ptype_head(pt);
    
        spin_lock(&ptype_lock);
        list_add_rcu(&pt->list, head);
        spin_unlock(&ptype_lock);
    }
    
    static inline struct list_head *ptype_head(const struct packet_type *pt)
    {
        if (pt->type == htons(ETH_P_ALL))
            return &ptype_all;
        else
            return &ptype_base[ntohs(pt->type) & PTYPE_HASH_MASK];
    }
    

    创建packet_create socket

    static int packet_create(struct net *net, struct socket *sock, int protocol, int kern)
        po = pkt_sk(sk);
        sk->sk_family = PF_PACKET;
        po->num = proto;
        po->xmit = dev_queue_xmit;
    
        po->prot_hook.af_packet_priv = sk;
    
        //proto 非0才会注册收包,即只能发送报文
        if (proto) {
            po->prot_hook.type = proto;
            register_prot_hook(sk);
                dev_add_pack(&po->prot_hook);
        }
    

    解释

    当protocol为0时,不会调用dev_add_pack注册ptype,所以也就不能接收报文了。
    当protocol为ETH_P_ALL时,会将ptype注册到ptype_all链表,协议
    栈收包/发包入口都会遍历ptype_all链表执行ptype->func,但是发包时是收不回来本socket发送的报文的。
    

    参考

    Linux原始套接字实现分析-albin_yang-ChinaUnix博客

    相关文章

      网友评论

          本文标题:链路层原始套接字

          本文链接:https://www.haomeiwen.com/subject/bqdmhrtx.html