链路层原始套接字

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

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 ([email protected])
         */
        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博客

你可能感兴趣的:(链路层原始套接字)