創(chuàng)建套接字的函數(shù)原型如下
int socket(int domain, int type, int protocol);
對于鏈路層原始套接字來說,第一個參數(shù)指定協(xié)議族類型為PF_PACKET络拌,第二個參數(shù)type可以設(shè)置為SOCK_RAW或SOCK_DGRAM俭驮,第三個參數(shù)是協(xié)議類型(該參數(shù)只對報(bào)文接收有意義)。
第三個參數(shù)protocol的用法春贸,如下表格從參考連接截圖
image.png
表1中protocol的取值中有兩個值是比較特殊的混萝。當(dāng)protocol為ETH_P_ALL時(shí),表示能夠接收本機(jī)收到的所有二層報(bào)文(包括IP, ARP, 自定義二層報(bào)文等)萍恕,同時(shí)這種類型套接字還能夠?qū)⑼獍l(fā)的報(bào)文再收回來逸嘀。當(dāng)protocol為0時(shí),表示該套接字不能用于接收報(bào)文允粤,只能用于發(fā)送崭倘。
本文重點(diǎn)從代碼角度解釋上面的兩個特殊值
當(dāng)protocol為0時(shí),表示該套接字不能用于接收報(bào)文维哈,只能用于發(fā)送.
當(dāng)protocol為ETH_P_ALL時(shí)绳姨,表示能夠接收本機(jī)收到的所有二層報(bào)文
(包括IP, ARP, 自定義二層報(bào)文等),同時(shí)這種類型套接字還能夠?qū)⑼獍l(fā)的報(bào)文再收回來(這句話是不對的阔挠,至少在我看的內(nèi)核代碼上不能將外發(fā)的報(bào)文再收回來)飘庄。
協(xié)議棧收發(fā)包入口
協(xié)議棧收包入口
static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc)
...
//遍歷鏈表ptype_all將報(bào)文復(fù)制一份調(diào)用每個鏈表節(jié)點(diǎn)的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加入同一個鏈
//表购撼。這里根據(jù)type遍歷鏈表跪削,并調(diào)用ptype->func函數(shù)
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;
}
}
協(xié)議棧發(fā)包入口
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將報(bào)文復(fù)制一份調(diào)用每個鏈表節(jié)點(diǎn)的func
list_for_each_entry_rcu(ptype, &ptype_all, list) {
//注意這里的注釋,從不會將報(bào)文發(fā)回給原始socket迂求,即
//socket收不到自己發(fā)出去的報(bào)文
/* 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;
}
}
...
從上面收發(fā)包入口函數(shù)可看到用到了如下兩個全局變量碾盐,用來保存注冊的ptype,注冊函數(shù)為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];
}
創(chuàng)建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才會注冊收包揩局,即只能發(fā)送報(bào)文
if (proto) {
po->prot_hook.type = proto;
register_prot_hook(sk);
dev_add_pack(&po->prot_hook);
}
解釋
當(dāng)protocol為0時(shí)毫玖,不會調(diào)用dev_add_pack注冊ptype,所以也就不能接收報(bào)文了凌盯。
當(dāng)protocol為ETH_P_ALL時(shí)付枫,會將ptype注冊到ptype_all鏈表,協(xié)議
棧收包/發(fā)包入口都會遍歷ptype_all鏈表執(zhí)行ptype->func驰怎,但是發(fā)包時(shí)是收不回來本socket發(fā)送的報(bào)文的阐滩。