Zephyr网络数据流

Creative Commons
本作品采用知识共享署名

本文简要分析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如何和其它部分进行关联
iface

说明如下

  • 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
    4
    struct 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
18
struct 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和l2

1
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_init

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void 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
65
struct 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
21
int 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
flow

发送

数据包形成

从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
17
int 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 queue

1
2
3
4
5
6
void 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接收pkt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static 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
15
int 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接收pkt

1
2
3
4
5
6
7
8
static 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
23
static 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
16
enum 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_request

1
2
3
4
5
6
7
8
9
10
11
static 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
2
3
4
5
6
7
8
9
enum net_verdict net_icmpv6_input(struct net_pkt *pkt,
u8_t type, u8_t code)
{
//寻找handle并处理
SYS_SLIST_FOR_EACH_CONTAINER(&handlers, cb, node) {
if (cb->type == type && (cb->code == code || cb->code == 0)) {
return cb->handler(pkt); //这里调用的就是handle_echo_request
}
}

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
48
int 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
50
static 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
7
int 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
54
int 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_received

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
NET_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