创建套接字的函数原型如下
int socket(int domain, int type, int protocol);
对于链路层原始套接字来说,第一个参数指定协议族类型为PF_PACKET,第二个参数type可以设置为SOCK_RAW或SOCK_DGRAM,第三个参数是协议类型(该参数只对报文接收有意义)。
第三个参数protocol的用法,如下表格从参考连接截图
表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博客