本文简要分析Zephyr网络部分从应用层到驱动层的纵向数据流,以作为分析了解Zephyr网络协议栈的基础。
概述
zephyr抽象出net_if提供一般性的API给ip layer使用,本文分析net_if如何基于L2将device drv和ip layer相连。
zephyr使用net_context/net_conn组织tcp/ip stack API,并提供一般性的API给app使用使用,本文分析net_context/net_conn如何和ip layer相连并传递数据。
关联
下图说明了net_if如何和其它部分进行关联
说明如下
- net_if是上下层连接层
- 编译期,通过NET_DEVICE_INIT,将device drv, l2注册入net_if(iface)
- 运行时,通过device drv init将net_if(iface)注册入device drv
- 运行时,通过net_if_ipv4_addr_add/net_if_ipv6_addr_add为net_if(iface)配置ip地址
- net_context是ip layer操作网络的句柄,net_context实例化为一个context
- 运行时,通过net_context_bind为context配置ip地址,配置的同时会将该ip地址的net_if(iface)注册入context
- 当ip layer通过context收发数据,而context会使用自己拥有的net_if(iface)来收发数据
编译期
编译期使用NET_DEVICE_INIT 完成了device的初始化并调用NET_IF_INIT完成net_if到l2&device drv的关联1
2
3
4
5
6
7#define NET_DEVICE_INIT(dev_name, drv_name, init_fn, \
data, cfg_info, prio, api, l2, \
l2_ctx_type, mtu) \
DEVICE_AND_API_INIT(dev_name, drv_name, init_fn, data, \
cfg_info, POST_KERNEL, prio, api); \
NET_L2_DATA_INIT(dev_name, 0, l2_ctx_type); \
NET_IF_INIT(dev_name, 0, l2, mtu, NET_IF_MAX_CONFIGS)
上面代码中DEVICE_AND_API_INIT是初始化一个网络设备驱动, 最后生成一个struct device结构体变量,详细参考zephyr驱动模型,这里说明一下和关联相关的部分
DEVICE_AND_API_INIT 会注册api和data
- api最开始的成员是struct net_if_api,其中init是device用于设置自己的net_if(iface)
- 通常情况下data在device drv实现时会在起内部预留字段用于保存net_if(iface), 运行时执行net_if_api的init就将net_if(iface)设置给device drv,让device drv知道自己归属于哪个net_if(iface)
1
2
3
4struct net_if_api {
void (*init)(struct net_if *iface);
int (*send)(struct net_if *iface, struct net_pkt *pkt);
};
编译期使用NET_L2_INIT定义一个net_l2结构体变量,将定义好的5个l2函数注册进去1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18struct net_l2 {
enum net_verdict (*recv)(struct net_if *iface, struct net_pkt *pkt); //接收封包
enum net_verdict (*send)(struct net_if *iface, struct net_pkt *pkt); //发送封包
u16_t (*reserve)(struct net_if *iface, void *data); //返回该L2 link layer header占用的字节大小
int (*enable)(struct net_if *iface, bool state); //enable/disable l2
enum net_l2_flags (*get_flags)(struct net_if *iface); //获取l2标记(多播,支援混杂模式等)
};
#define NET_L2_INIT(_name, _recv_fn, _send_fn, _reserve_fn, _enable_fn, \
_get_flags_fn) \
const struct net_l2 (NET_L2_GET_NAME(_name)) __used \
__attribute__((__section__(".net_l2.init"))) = { \
.recv = (_recv_fn), \
.send = (_send_fn), \
.reserve = (_reserve_fn), \
.enable = (_enable_fn), \
.get_flags = (_get_flags_fn), \
}
NET_IF_INIT主要完成一个net_if(iface)和device drv, l2的绑定. net_if可以找到device和l21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18#define NET_IF_INIT(dev_name, sfx, _l2, _mtu, _num_configs) \
//net_if_dev的建立为了保存l2的相关信息和操作API,同时也保存device drv的相关信息
static struct net_if_dev (NET_IF_DEV_GET_NAME(dev_name, sfx)) \
__used __attribute__((__section__(".net_if_dev.data"))) = { \
.dev = &(DEVICE_NAME_GET(dev_name)), \
.l2 = &(NET_L2_GET_NAME(_l2)), \
.l2_data = &(NET_L2_GET_DATA(dev_name, sfx)), \
.mtu = _mtu, \
}; \
//建立net_if,将net_if_dev的信息注册进入,并预留if_config用于绑定IP地址
static struct net_if \
(NET_IF_GET_NAME(dev_name, sfx))[_num_configs] __used \
__attribute__((__section__(".net_if.data"))) = { \
[0 ... (_num_configs - 1)] = { \
.if_dev = &(NET_IF_DEV_GET_NAME(dev_name, sfx)), \
NET_IF_CONFIG_INIT \
} \
}
运行期
运行期完成net_if(iface)到device的关联(device可以找到自己的iface)
运行期完成context到net_if(iface)的关联,二者的桥梁是ip地址
device到net_if的管理
device drv内部要知道自己所属的net_if(iface),尤其是在收到packet后,需要将packet送到对应的net_if(iface), net_if是在net初始化时被设置到device drv的1
SYS_INIT(net_init, POST_KERNEL, CONFIG_NET_INIT_PRIO);
系统初始化定义了net_init在POST_KERNEL level时被调用,参考zephyr驱动模型
net_init内按照下面的调用顺序执行到net_if(iface)被设置到device内
net_init->init_rx_queues->net_if_init1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23void net_if_init(void)
{
...
//变量所有的net_if,并进行初始化
for (iface = __net_if_start, if_count = 0; iface != __net_if_end;
iface++, if_count++) {
init_iface(iface);
}
...
}
static inline void init_iface(struct net_if *iface)
{
//取出net_if->device的api
const struct net_if_api *api = net_if_get_device(iface)->driver_api;
NET_ASSERT(api && api->init && api->send);
NET_DBG("On iface %p", iface);
//通过api的init函数将iface设置给device drv,device drv内部自行保存iface
api->init(iface);
}
context到net_if(iface)的关联
net_if和ip地址绑定
通过net_if_ipv4_addr_add/net_if_ipv6_addr_add,将ip地址保存存iface(net_if),以ipv6为例1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65struct net_if_addr *net_if_ipv6_addr_add(struct net_if *iface,
struct in6_addr *addr,
enum net_addr_type addr_type,
u32_t vlifetime)
{
struct net_if_addr *ifaddr;
struct net_if_ipv6 *ipv6;
int i;
//从ipv6_addresses数组中寻找未使用单元提供给iface,用于存储up地址
if (net_if_config_ipv6_get(iface, &ipv6) < 0) {
return NULL;
}
for (i = 0; i < NET_IF_MAX_IPV6_ADDR; i++) {
if (ipv6->unicast[i].is_used) {
continue;
}
//将ip地址存放在iface内
net_if_addr_init(&ipv6->unicast[i], addr, addr_type,
vlifetime);
net_if_ipv6_start_dad(iface, &ipv6->unicast[i]);
net_mgmt_event_notify(NET_EVENT_IPV6_ADDR_ADD, iface);
return &ipv6->unicast[i];
}
...
}
int net_if_config_ipv6_get(struct net_if *iface, struct net_if_ipv6 **ipv6)
{
int i;
if (iface->config.ip.ipv6) {
//iface已经配置了ipv6地址,返回之前用的单元
if (ipv6) {
*ipv6 = iface->config.ip.ipv6;
}
return 0;
}
for (i = 0; i < ARRAY_SIZE(ipv6_addresses); i++) {
if (ipv6_addresses[i].iface) { //寻找未使用的单元
continue;
}
//将未使用的单元提供给iface,用于保存iface的ip地址
iface->config.ip.ipv6 = &ipv6_addresses[i].ipv6;
//ip地址反向映射到iface(ip可以找到iface)
ipv6_addresses[i].iface = iface;
if (ipv6) {
*ipv6 = &ipv6_addresses[i].ipv6;
}
return 0;
}
return -ESRCH;
}
context和ip地址绑定
通过net_context_bind将ip地址和context绑定时,会根据ip地址查找对应的iface,同时将iface和context绑定1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21int net_context_bind(struct net_context *context, const struct sockaddr *addr,
socklen_t addrlen)
{
...
if (addr->sa_family == AF_INET6) {
struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)addr;
//通过ip地址查到对应的iface
ifaddr = net_if_ipv6_addr_lookup(&addr6->sin6_addr,
&iface);
//将iface和context绑定
net_context_set_iface(context, iface);
}
}
static inline void net_context_set_iface(struct net_context *context,
struct net_if *iface)
{
NET_ASSERT(iface);
//context中iface是以其在.net_if.data section的index进行保存的
context->iface = net_if_get_by_iface(iface);
}
关联总结
编译期:
定义device
定义l2
定义net_if
net_if -> device
net_if -> l2
运行期:
device->net_if(iface)
net_if->ip address
ip_address->net_if(iface)
context->ip_address
context->net_if(iface)
经过上面两个时期的关联:
上层通过context发送数据,可以找到net_if,再找到l2,经过l2的处理再找到device drv发送数据
device 接收数据可以通过device找到net_if
数据通路
网络上数据通路我们比较关心的收发,Zephyr将TCP/IP Stack进行包装成context(Network Connectivity API)和socket(BSD Sockets compatible API)两种形式, 这里基于context分析网络数据流向。
Zephyr官网上给出了数据流向图如下,可以看到最后数据的接收全部都是依赖callback
发送
数据包形成
从Network Connectivity API的应用者来看发送数据的数据包都是使用struct net_pkt net_pkt_get_tx(struct net_context context, s32_t timeout)来申请的,在申请的时候就为其指定了context。该context和对应的iface(net_if)被保存在pkt内。之后发送数据时会根据pkt内的context来选择发送数据通路
数据包发送
从Network Connectivity API的应用者来看发送数据都是基于TCP/UDP,二者最后都是走到net_send_data
UDP
net_context_send(struct net_pkt pkt,…)->sendto(pkt, …)->net_send_data(pkt)
TCP
net_tcp_send_data(struct net_pkt pkt,…)->net_tcp_send_pkt(pkt)->net_send_data(pkt)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17int net_send_data(struct net_pkt *pkt)
{
...
//从pkt内取出iface,并将数据报通过该iface发送出去
if (net_if_send_data(net_pkt_iface(pkt), pkt) == NET_DROP) {
return -EIO;
}
...
}
enum net_verdict net_if_send_data(struct net_if *iface, struct net_pkt *pkt)
{
...
//取出iface的l2并通过l2发送数据
verdict = net_if_l2(iface)->send(iface, pkt);
...
}
在不同的l2中会对pkt做不同的处理(这里不对l2函数进行分析可以参考net_bt_send & ethernet_send内实现),在l2函数中调用net_if_queue_tx进行发送, 这里的发送实际只是将pkt放入work queue1
2
3
4
5
6void net_if_queue_tx(struct net_if *iface, struct net_pkt *pkt)
{
//将pkt和pkt的处理函数放入work Queue,等待被处理
k_work_init(net_pkt_work(pkt), process_tx_packet);
net_tc_submit_to_tx_queue(tc, pkt);
}
work queue的main thread收到work queue时使用process_tx_packet接收pkt1
2
3
4
5
6
7
8
9
10
11
12
13
14
15static void process_tx_packet(struct k_work *work)
{
struct net_pkt *pkt;
pkt = CONTAINER_OF(work, struct net_pkt, work);
//从pkt中取出iface,将pkt发送到指定的iface
net_if_tx(net_pkt_iface(pkt), pkt);
}
static bool net_if_tx(struct net_if *iface, struct net_pkt *pkt)
{
//从iface取出device drv api,并发送数据,这里之后就已经进入到device drv内的发送阶段
const struct net_if_api *api = net_if_get_device(iface)->driver_api;
api->send(iface, pkt);
}
ARP/ICMP
ARP/ICMP是TC/IP内的协议,不用Network Connectivity API来处理,而是在ip内就处理了,其直接通过net_if_queue_tx/net_send_data来发送数据
接收
数据包形成
在device drv内获取的数据都是网络封包净数据,device drv通过net_pkt_get_reserve_rx申请pkt,并将净数据放入其内,之后发送到自己所拥有的iface上, drv内的代码不做分析,可参见ipsp_recv & eth_enc28j60_rx
数据包接收
device准备好接收的pkt后调用net_recv_data把数据向上送1
2
3
4
5
6
7
8
9
10
11
12
13
14
15int net_recv_data(struct net_if *iface, struct net_pkt *pkt)
{
...
//标记pkt所属的iface
net_pkt_set_iface(pkt, iface);
//将pkt加入到work q中
net_queue_rx(iface, pkt);
}
static void net_queue_rx(struct net_if *iface, struct net_pkt *pkt)
{
//将pkt和pkt的处理函数放入work Queue,等待被处理
k_work_init(net_pkt_work(pkt), process_rx_packet);
net_tc_submit_to_rx_queue(tc, pkt);
}
work queue的main thread收到work queue时使用process_rx_packet接收pkt1
2
3
4
5
6
7
8static void process_rx_packet(struct k_work *work)
{
struct net_pkt *pkt;
pkt = CONTAINER_OF(work, struct net_pkt, work);
//从pkt中取出iface,指定iface处理pkt
net_rx(net_pkt_iface(pkt), pkt);
}
之后经过几层函数包装调用到process_data
net_rx(struct net_if iface, struct net_pkt pkt) -> processing_data(pkt, …) -> process_data(net_pkt, …)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23static inline enum net_verdict process_data(struct net_pkt *pkt,
bool is_loopback)
{
...
// net_if 处理pkt,主要是让l2处理pkt
ret = net_if_recv_data(net_pkt_iface(pkt), pkt);
...
//将l2处理后的pkt送到ip层处理
switch (NET_IPV6_HDR(pkt)->vtc & 0xf0) {
#if defined(CONFIG_NET_IPV6)
case 0x60:
...
return net_ipv6_process_pkt(pkt);
#endif
#if defined(CONFIG_NET_IPV4)
case 0x40:
...
return net_ipv4_process_pkt(pkt);
#endif
}
...
}
ip层数据接收
ipv4和ipv6的数据接收主流程类似,最后都是分为icmp/udp/tcp,这里以ipv6代码示例,可以看到icmp使用process_icmpv6_pkt处理,而TCP/UDP统一使用net_conn_input处理1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16enum net_verdict net_ipv6_process_pkt(struct net_pkt *pkt)
{
//IP协议处理代码,不列出
...
switch (next) {
case IPPROTO_ICMPV6:
return process_icmpv6_pkt(pkt, hdr);
case IPPROTO_UDP:
return net_conn_input(IPPROTO_UDP, pkt);
return NET_DROP;
case IPPROTO_TCP:
return net_conn_input(IPPROTO_TCP, pkt);
}
...
}
ICMP
icmp处理可分为2部分来看, icmpv4和v6的处理流程相似,这里分析ipv6的代码
注册处理函数
net_init->l3_init->net_icmpv6_init->net_icmpv6_register_handler(&echo_request_handler)
实际icmp的处理函数是handle_echo_request1
2
3
4
5
6
7
8
9
10
11static struct net_icmpv6_handler echo_request_handler = {
.type = NET_ICMPV6_ECHO_REQUEST,
.code = 0,
.handler = handle_echo_request,
};
void net_icmpv6_register_handler(struct net_icmpv6_handler *handler)
{
将handle注册入handlers list
sys_slist_prepend(&handlers, &handler->node);
}
ICMP数据处理
1 | enum net_verdict net_icmpv6_input(struct net_pkt *pkt, |
TCP/UDP
TCP/UDP都是使用net_conn_input进行接收数据的处理, 涉及到conn和context的横向涉及这里不做分析,只说明数据的垂直流向
net_conn_input要处理数据也分为2部分
注册处理函数
TCP和UDP都是将cb保存在context中,但之后的包装方式不太相同1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48int net_context_recv(struct net_context *context,
net_context_recv_cb_t cb,
s32_t timeout,
void *user_data)
{
...
switch (net_context_get_ip_proto(context)) {
case IPPROTO_UDP:
//注册udp callback
ret = recv_udp(context, cb, timeout, user_data);
break;
case IPPROTO_TCP:
//注册tcp callback
ret = net_tcp_recv(context, cb, user_data);
break;
default:
ret = -EPROTOTYPE;
break;
}
...
}
static int recv_udp(struct net_context *context,
net_context_recv_cb_t cb,
s32_t timeout,
void *user_data)
{
...
//将处理
context->recv_cb = cb;
ret = net_conn_register(net_context_get_ip_proto(context),
context->flags & NET_CONTEXT_REMOTE_ADDR_SET ?
&context->remote : NULL,
laddr,
ntohs(net_sin(&context->remote)->sin_port),
ntohs(lport),
net_context_packet_received,
user_data,
&context->conn_handler);
}
int net_tcp_recv(struct net_context *context, net_context_recv_cb_t cb,
void *user_data)
{
...
context->recv_cb = cb;
...
}
对于UDP来说1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50static int recv_udp(struct net_context *context,
net_context_recv_cb_t cb,
s32_t timeout,
void *user_data)
{
...
//将callback放入 context
context->recv_cb = cb;
//callback被net_context_packet_received包装注册入conn
ret = net_conn_register(net_context_get_ip_proto(context),
context->flags & NET_CONTEXT_REMOTE_ADDR_SET ?
&context->remote : NULL,
laddr,
ntohs(net_sin(&context->remote)->sin_port),
ntohs(lport),
net_context_packet_received,
user_data,
&context->conn_handler);
}enum net_verdict net_context_packet_received(struct net_conn *conn,
struct net_pkt *pkt,
void *user_data)
{
//更新context的iface
net_context_set_iface(context, net_pkt_iface(pkt));
//更新pkt的context
net_pkt_set_context(pkt, context);
//调用上层注册到context的callback
context->recv_cb(context, pkt, 0, user_data);
}
int net_conn_register(enum net_ip_protocol proto,
const struct sockaddr *remote_addr,
const struct sockaddr *local_addr,
u16_t remote_port,
u16_t local_port,
net_conn_cb_t cb,
void *user_data,
struct net_conn_handle **handle)
{
//找到未使用的conns,设置入cb(net_context_packet_received)
conns[i].flags |= NET_CONN_IN_USE;
conns[i].cb = cb;
conns[i].user_data = user_data;
conns[i].rank = rank;
conns[i].proto = proto;
//返回conn,会放入到context->conn_handler
*handle = (struct net_conn_handle *)&conns[i];
}
对于TCP只将cb放入到context中不做其它注册动作1
2
3
4
5
6
7int net_tcp_recv(struct net_context *context, net_context_recv_cb_t cb,
void *user_data)
{
...
context->recv_cb = cb;
...
}
但实际的注册动作纵向看相当曲折,这里简要列出
context使用者使用net_context_connect进行tcp连接,其作用就是将tcp callback tcp_synack_received注册到conn内1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54int net_context_connect(struct net_context *context,
const struct sockaddr *addr,
socklen_t addrlen,
net_context_connect_cb_t cb,
s32_t timeout,
void *user_data)
{
...
switch (net_context_get_type(context)) {
case SOCK_STREAM:
//进行TCP连接
return net_tcp_connect(context, addr, laddr, rport, lport,
timeout, cb, user_data);
default:
return -ENOTSUP;
}
return 0;
}
int net_tcp_connect(struct net_context *context,
const struct sockaddr *addr,
struct sockaddr *laddr,
u16_t rport,
u16_t lport,
s32_t timeout,
net_context_connect_cb_t cb,
void *user_data)
{
....
//注册tcp_synack_received callback
ret = net_tcp_register(addr,
laddr,
ntohs(rport),
ntohs(lport),
tcp_synack_received,
context,
&context->conn_handler);
}
static inline int net_tcp_register(const struct sockaddr *remote_addr,
const struct sockaddr *local_addr,
u16_t remote_port,
u16_t local_port,
net_conn_cb_t cb,
void *user_data,
struct net_conn_handle **handle)
{
//将callback注册到conn
return net_conn_register(IPPROTO_TCP, remote_addr, local_addr,
remote_port, local_port, cb, user_data,
handle);
}
直到这里tcp的conn还没有和net_tcp_recv注册的cb挂钩,继续看tcp_synack_received1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26NET_CONN_CB(tcp_synack_received)
{
...
//在tcp_synack_received callback中会将tcp_established注册到conn
ret = net_tcp_register(&remote_addr,
&local_addr,
ntohs(tcp_hdr->src_port),
ntohs(tcp_hdr->dst_port),
tcp_established,
context,
&context->conn_handler);
}
NET_CONN_CB(tcp_established)
{
...
if (data_len > 0) {
//在tcp_established中实际处理数据
ret = net_context_packet_received(conn, pkt,
context->tcp->recv_user_data);
} else if (data_len == 0) {
net_pkt_unref(pkt);
}
...
}
数据处理
enum net_verdict net_conn_input(enum net_ip_protocol proto, struct net_pkt *pkt)
{
//查找最匹配的conn
for (i = 0; i < CONFIG_NET_MAX_CONN; i++) {
...
if (best_match >= 0 &&
net_sin(&conns[best_match].remote_addr)->sin_port) {
continue;
}
if (best_rank < conns[i].rank) {
best_rank = conns[i].rank;
best_match = i;
}
}
if (conns[best_match].cb(&conns[best_match], pkt,
conns[best_match].user_data) == NET_DROP) {
goto drop;
}
}
对于UDP: conns[best_match].cb 调用callback处理函数就是net_context_packet_received,然后在该函数内调用recv_udp注册到context的context->recv_cb 进行处理
对于TCP:
conns[best_match].cb 第一次调用tcp_synack_received 注册tcp_established到conns
conns[best_match].cb 第二次调用tcp_established, 在tcp_established内调用net_context_packet_received,然后在该函数内调用net_tcp_recv注册到context的context->recv_cb 进行处理
总结
UDP接收数据直接调用net_context_recv注册数据接收callback即可
TCP接收数据需要先net_context_connect建立连接,收到数据时net_context_recv注册的callback才会被调用
参考
https://docs.zephyrproject.org/latest/subsystems/networking/ip-stack-architecture.html