Linux 网络:交换芯片 EDSA 以太网帧简介
1. 前言
限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。
2. EDSA 协议以太网帧的发送和接收
2.1 什么是 EDSA ?
在说明 EDSA
前,先说一说什么是 DSA
。DSA
,是 Distributed Switch Architecture
的缩写,是 Linux
下针对交换类芯片的子系统框架。那什么是 EDSA
?DSA 内核文档 有如下相关的一段解释:
Note that in certain cases, it might be the case that the tagging format used by a leaf switch (not connected
directly to the CPU) to not be the same as what the network stack sees. This can be seen with Marvell switch
trees, where the CPU port can be configured to use either the DSA or the Ethertype DSA (EDSA) format, but
the DSA links are configured to use the shorter (without Ethertype) DSA frame header, in order to reduce
the autonomous packet forwarding overhead. It still remains the case that, if the DSA switch tree is
configured for the EDSA tagging protocol, the operating system sees EDSA-tagged packets from the leaf
switches that tagged them with the shorter DSA header. This can be done because the Marvell switch
connected directly to the CPU is configured to perform tag translation between DSA and EDSA (which is
simply the operation of adding or removing the ETH_P_EDSA EtherType and some padding octets).
其核心含义是:
EDSA 是交换芯片上的传输数据一种帧格式,格式相关的数据为 8 字节长,对应 Linux 内核代码中定义的 ETH_P_EDSA 以太网帧类型。
/* include/uapi/linux/if_ether.h */
#define ETH_P_EDSA0xDADA/* Ethertype DSA [ NOT AN OFFICIALLY REGISTERED ID ] */
从 ETH_P_EDSA
定义的注释 [ NOT AN OFFICIALLY REGISTERED ID ]
了解到,这并不是一个有官方标准定义的格式
。
2.2 EDSA 以太网帧的发送
先给定一个的场景,本文将基于下图的场景进行讨论。场景的网络拓扑结构如图 1.
所示:
本文以直接从 L2
层发送数据为例来进行分析,其它从 L4
等其它层的发送过程类似。发送端用户空间
核心逻辑如下:
int fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
...
sendmsg(fd, ...);
发送数据在内核空间
经过交换芯片
时,重点关注在图 1.
所示的拓扑中,EDSA
协议(即 ETH_P_EDSA
)字段的加入过程。首先看一下 DSA
框架下,通过 dsa_register_switch()
接口注册交换芯片驱动的过程:
static const struct net_device_ops dsa_slave_netdev_ops = {
...
.ndo_start_xmit= dsa_slave_xmit, // 交换芯片驱动通用发送接口
...
};
dsa_register_switch() /* net/dsa/dsa2.c */
...
dsa_slave_create() /* net/dsa/slave.c */
...
struct net_device *slave_dev;
...
slave_dev = alloc_netdev_mqs(sizeof(struct dsa_slave_priv), name,
NET_NAME_UNKNOWN, ether_setup,
ds->num_tx_queues, 1);
...
slave_dev->netdev_ops = &dsa_slave_netdev_ops;
...
从上面的代码分析可以看到,在 DSA
驱动框架下的交换芯片通过接口 dsa_slave_xmit()
往外发送数据,看一下细节:
sys_sendmsg()
...
packet_sendmsg() // socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)) 类型包发送接口
dev_queue_xmit()
...
dsa_slave_xmit()
edsa_xmit() // 1. 添加 EDSA 协议头
dev_queue_xmit()
...
fec_enet_start_xmit() // 2. 然后经 MAC (eth0) 的往外发送: MAC (eth0) -> 交换芯片 PHY
/* net/dsa/slave.c */
static netdev_tx_t dsa_slave_xmit(struct sk_buff *skb, struct net_device *dev)
{
...
struct sk_buff *nskb;
...
// 发送 EDSA 包前,加入 EDSA 协议头
nskb = p->xmit(skb, dev); // edsa_xmit()
...
// 往外经 图 1. 中的 eth0 驱动接口发送
dev_queue_xmit(nskb);
...
}
/* include/uapi/linux/if_ether.h */
#define ETH_P_EDSA0xDADA/* Ethertype DSA [ NOT AN OFFICIALLY REGISTERED ID ] */
/* net/dsa/tag_edsa.c */
static struct sk_buff *edsa_xmit(struct sk_buff *skb, struct net_device *dev)
{
...
if (skb->protocol == htons(ETH_P_8021Q)) {
...
} else { // EDSA 协议包
if (skb_cow_head(skb, EDSA_HLEN) < 0)
return NULL;
skb_push(skb, EDSA_HLEN);
memmove(skb->data, skb->data + EDSA_HLEN, 2 * ETH_ALEN);
/*
* Construct untagged FROM_CPU DSA tag.
*/
edsa_header = skb->data + 2 * ETH_ALEN; // EDSA 协议头,紧随以太网帧 源 MAC、目的 MAC 之后
edsa_header[0] = (ETH_P_EDSA >> 8) & 0xff;
edsa_header[1] = ETH_P_EDSA & 0xff;
edsa_header[2] = 0x00;
edsa_header[3] = 0x00;
edsa_header[4] = 0x40 | p->dp->ds->index; // 交换芯片编号索引
edsa_header[5] = p->dp->index << 3; // 交换芯片上的端口号索引
edsa_header[6] = 0x00;
edsa_header[7] = 0x00;
}
...
}
总结一下发送过程
:
以太网帧的发送过程中,首先经使用 `EDSA` 协议的`交换芯片驱动`(如 `Marvell 88E6320`)的发送接口
添加 `EDSA` 协议头,然后经过 `MAC (eth0) 驱动`的发送接口,提交给`交换芯片 PHY 端口`发送出去。
2.3 EDSA 以太网帧的接收
来看一下接收过程中的代码细节:
gic_handle_irq() // 收到包产生中断
...
__do_softirq()
net_rx_action()
fec_enet_rx_napi() // eth0 的 NAPI 收包接口
napi_gro_receive()
netif_receive_skb_internal()
__netif_receive_skb()
__netif_receive_skb_core()
dsa_switch_rcv()
edsa_rcv() // 1. EDSA 协议包处理:去掉 EDSA 头部
netif_receive_skb() // 2. 继续往后传递【去掉了 EDSA 协议头】的数据包
...
__netif_receive_skb_core()
// socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)) 类型包接收接口,
// 类似于 tcpdump。
// 这里放置 skb 到 socket 的缓冲,用户空间通过 recvmsg() 来提取。
packet_rcv()
/* net/dsa/tag_edsa.c */
static struct sk_buff *edsa_rcv(struct sk_buff *skb, struct net_device *dev,
struct packet_type *pt)
{
...
if (edsa_header[0] & 0x20) {
...
} else {
/*
* Remove DSA tag and update checksum.
*/
// 移除 8 字节的 EDSA 协议头
skb_pull_rcsum(skb, EDSA_HLEN);
memmove(skb->data - ETH_HLEN,
skb->data - ETH_HLEN - EDSA_HLEN,
2 * ETH_ALEN);
}
}
总结一下接收过程
:
以太网帧的接收过程中,数据从`交换芯片 PHY 端口`到达`MAC (eth0) 驱动`的接收接口,传递给使用
`EDSA` 协议的`交换芯片驱动`(如 `Marvell 88E6320`)的接收接口, 然后首先去掉`EDSA` 协议头,
然后继续往后传递。
3. 验证
从前面的章节分析了解到,数据接收过程中,数据到达 MAC (eth0)
时,仍然带有 EDSA
头部。我们以 IEEE 1588 PTP
协议 L2
层的收发包过程为例来进行演示。首先,在机器1
上执行命令:
ptp4l -i eth0 -2 -m
然后在机器2
上,用 tcpdump
抓包 eth0
:
tcpdump -i eth0 -w eth0.pcap
用 Wireshark
解析抓包数据 eth0.pcap
(下图仅保留了来自机器1
作为源 MAC
的包):
从上面的抓包数据可以看到,edsa_xmit()
中填充的 EDSA
协议头部(以 0xdada
打头的 8
个字节),Wireshark
不能识别该协议。
再在虚拟网口 lan3
上进行抓包:
tcpdump -i eth0 -w lan3.pcap
同样用 Wireshark
解析抓包数据 lan3.pcap
(下图仅保留了来自机器1
作为源 MAC
的包):
可见,接收数据经过 edsa_rcv()
去掉 EDSA
协议头后,再到达的虚拟网口 lan3
。此时,Wireshark
已经能够将数据包正确解析成 IEEE 1588 PTP
协议包。
最后,这里有一个相关的示例:cBPF 示例:过滤 EDSA 协议下的 PTP 以太网帧 。
原文地址:https://blog.csdn.net/JiMoKuangXiangQu/article/details/145208709
免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!