记录数据从网卡(无线)驱动到用户层的函数调用过程。
前置一些数据结构和操作函数:
struct sk_buff 结构 和dev_alloc_skb 函数 ,网上有人对他进行了详细描述,一下为拷贝网上的描述:
sk_buff结构的成员skb->head指向一个已分配的空间的头部,即申请到的整个缓冲区的头,skb->end指向该空间的尾部,这两个成员指针从空间创建之后,就不能被修改。skb->data指向分配空间中数据的头部,skb->tail指向数据的尾部,这两个值随着网络数据在各层之间的传递、修改,会被不断改动。刚开始接触skb_buf的时候会产生一种错误的认识,就是以为协议头都会是放在skb->head和skb->data这两个指针之间,但实际上skb_buf的操作函数都无法直接对这一段内存进行操作,所有的操作函数所做的就仅仅是修改skb->data和skb->tail这两个指针而已,向套接字缓冲区拷贝数据也是由其它函数来完成的,所以不管是从网卡接受的数据还是上层发下来的数据,协议头都是被放在了skb->data到skb->tail之间,通过skb_push前移skb->data加入协议头,通过skb_pull后移skb->data剥离协议头。
原文:http://blog.csdn.net/qq405180763/article/details/8797236
封包类型结构
结构 packet_type {
__be16 type; 封包协议类型
struct net_device*dev 针对设备,如果该值不为空,只处理该设备的数据
int (*func) (struct sk_buff *,
struct net_device *,
struct packet_type *,
struct net_device *);该类型协议的处理函数
bool (*id_match)(struct packet_type *ptype,
struct sock *sk);
void *af_packet_priv;
struct list_headlist;
}
ptype_all封包类型链表:协议类型会向该链表注册,注册函数是dev_add_pack,只有类型为htons(ETH_P_ALL)的协议才会注册到该链表
ptype_base封包类型链表数组,每个类型的协议一个链表,注册函数是dev_add_pack
结构 ieee80211_local {
struct ieee80211_hw hw;
const struct ieee80211_ops *ops;
..........................
}
结构 wiphy {
u8 perm_addr[ETH_ALEN];
u8 addr_mask[ETH_ALEN];
struct mac_address *addresses;
const struct ieee80211_txrx_stypes *mgmt_stypes;
............................
char priv[0] __aligned(NETDEV_ALIGN); ------------> ieee80211_local
}
}
结构 ieee80211_ops {
.....................................
}
结构 ieee80211_hw {
struct ieee80211_conf conf;
struct wiphy *wiphy;
void *priv;
...........................................
const struct ieee80211_cipher_scheme *cipher_schemes;
}
ieee80211_register_hw 函数,向80211注册硬件设备
1. 每个无线网卡驱动模块会有自己的 全局 ieee80211_ops 结构对象,把自己的函数和指针传递给他。
2. 驱动调用 ieee80211_alloc_hw(sizeof(struct ath_softc), &ath9k_ops),ath9k_ops为 ieee80211_ops 结构对象 返回 ieee80211_hw结构指针,调用 ieee80211_rx时,该对象会作为参数传递过来。
3. 驱动初始化时会调用 ieee80211_register_hw
数据从无线网卡驱动程序获取到整包数据开始:
___________________________netif_rx(网络核心层函数)___________________________
来自网卡驱动层的网络封包首先调用该函数,参数为 sk_buff对象
数据对象包含了14字节的网卡地址头
可能添加时标到sk_buff对象
把包放到cpu的backlog队列中去(不考虑RPS 模式)
________________________________________________________________________
|
|
___________________________process_backlog(网络核心层函数)___________________________
从cpu的backlog队列获取 sk_buff对象
转入__netif_receive_skb 函数 再到__netif_receive_skb_core 函数
对sk_buff对象头做了调整(具体没细看)
如果是虚拟局域网(VLAN网络)报文(skb->protocol,网卡驱动设置),直接调用 skb_vlan_untag 后退出
执行ptype_all 队列的处理函数
执行设备指定协议处理对象(skb->dev->ptype_all)处理函数
执行设备关联处理函数(skb->dev->rx_handler)
执行在ptype_base 注册的处理函数
核心层执行完毕
————————————————————————————————————————
现在开始跟踪,80211 帧处理的前后,主要考虑驱动,内核,80211适配器之间的关系。
首先是80211 的入口问题,肯定是无线网卡驱动直接到80211 模块,但具体怎么走的我没有深入研究,所以我把ieee80211_rx作为80211的第一入口来看待应该不会有太大关系。
1.ieee80211_rx 函数,从函数的描述上可知(Use this function to hand received frames to mac80211. The receive
* buffer in @skb must start with an IEEE 802.11 header.)它就是个80211帧,之后调用ieee80211_rx_napi 函数(该函数被导出 -_-)
2. 针对 ieee80211_rx_napi做简单分析:
a.调用 ieee80211_rx_monitor 函数,这个函数在网上有不少描述:(拷贝帧传递给所有监听接口),其实就是去掉radiotap头和FCS,然后各种调用netif_receive_skb,且skb的dev被赋值(代表只发送到指定设备)(此时监听接口应该已经收到数据量)
b.调用__ieee80211_rx_handle_packet
3. 此时,数据帧 已经被处理过一次了,然后各种ieee80211_prepare_and_rx_handle,再然后ieee80211_invoke_rx_handlers,再然后ieee80211_rx_handlers,再再然后各种轮奸,看得实在是累啊。挑选ieee80211_rx_h_data 跟踪下去吧。
4. 调用__ieee80211_data_to_8023 ,直接把80211转成802.3,然后 ieee80211_deliver_skb
5. 此时,数据已经是以太网帧格式了(802.3),最后调用 netif_receive_skb,去网络核心层了。
以上看不出 使用 socket(PF_PACKET, SOCK_RAW,htons( ETH_P_ALL ) ) 绑定网卡后能监听所有数据的情况,下面一步一步跟踪套接口,确保能与上面的数据流程衔接起来。
首先是套接口的创建(这方面网络上资源比较多,总结 列举下即可):
SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
- -sock_create(family, type, protocol, &sock)->__sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0) current->nsproxy:当前进程所属命名空间
- - - -sock = sock_alloc()分配套接口结果空间
- - - - pf->create(net, sock, protocol, kern) 调用套网络协议家族对象创建套接口,对应我们的例子也即是 packet_family_ops 结构的packet_create函数
- - - - - - - -sk = sk_alloc(net, PF_PACKET, GFP_KERNEL, &packet_proto, kern); 创建 struct sock 结构
- - - - - - - -sock->ops = &packet_ops 赋值操作对象,后面会被调用的
- - - - - - - -register_prot_hook(sk) 向ptype_all队列注册,实际执行的回调函数是packet_rcv或packet_rcv_spkt
- - - - - - - -sk_add_node_rcu(sk, &net->packet.sklist) 把truct sock 结构加到队列上去
- -sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK)) 挂到文件系统上去
- - - -fd = get_unused_fd_flags(flags);
- - - -newfile = sock_alloc_file(sock, flags, NULL);
- - - -fd_install(fd, newfile);
然后跟踪其读文件过程
- -SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
- - - -struct fd f = fdget_pos(fd);
- - - -vfs_read(f.file, buf, count, &pos)
- - - - - - new_sync_read(file, buf, count, pos)
- - - - - - - -new_sync_read(file, buf, count, pos) 同步读
- - - - - - - - - -filp->f_op->read_iter(&kiocb, &iter) 从这里可以看出,实际读的是操作对象的read_iter函数,对应本例实际的函数应该是socket_file_ops对象的sock_read_iter函数
对函数sock_read_iter 进行分析
sock_recvmsg(sock, &msg, msg.msg_flags) 其中 struct socket *sock = file->private_data
- -sock->ops->recvmsg(sock, msg, msg_data_left(msg), flags) 按本例,实际调用的函数应该是: packet_recvmsg
- - - -skb = skb_recv_datagram(sk, flags, flags & MSG_DONTWAIT, &err) 此时已经获取到了数据,实际上数据就保存在 sk->sk_receive_queue队列中
至此,所有数据流程就已经通顺了,下面就以图形的方式把整个流程表达出来(以socket(PF_PACKET, SOCK_RAW,htons( ETH_P_ALL ) ) 截取网卡报文为场景)