Open vSwitch 数据包接收的实现

news2024/12/27 7:03:35

一、Open vSwitch 数据包的来源

        Open vSwitch 中的数据包有许多种来源:

  • 物理网络接口:OVS 可以连接到物理网络设备,并处理从这些设备收到的数据包。这些数据包可能来自外部网络,需要被转发或进一步处理。
  • 虚拟网络接口:OVS 可以创建和管理虚拟网络接口,如 OVS 内部端口(vport)或 Linux 网桥端口。这些虚拟接口可能来自虚拟机、容器或其他网络应用程序,数据包也需要被转发或进一步处理。
  • 隧道连接:OVS 支持多种隧道协议,如 VXLAN 和 GRE 等,可以从这些隧道收到数据包。这些数据包需要被解封装并转发到正确的目的地。
  • 内部生成:OVS 自身可能会生成一些控制和管理类型的数据包,例如用于实现 OpenFlow 协议、OVSDB 协议等。这些数据包需要被特殊处理。
  • 用户空间:OVS 可以接收来自用户空间应用程序的数据包,例如通过 AF_PACKET 套接字或 netdev-based 接口。这些数据包需要被转发到适当的目的地。

        对于 Open vSwitch 数据包的接收过程而言,来自物理网络接口、虚拟网络接口和隧道连接的数据包更为常见。在代码实现中,对于不同的来源和不同类型的数据包会有不同的接收和处理策略,比如内核模块 openvswitch.ko 在网卡上注册的 netdev_frame_hook() 函数和不同隧道的 rcv 接收函数。这里我们忽略不同的前置接收方式,重点关注 Datapath 模块对数据包的接收过程。

        从图中可以看出,无论何种接收方式,最终都会汇集到 netdev_port_receive() 函数,即都需要调用这个函数。所以我们以 netdev_port_receive() 函数为起点,分析 Open vSwitch 数据包的接收过程。

二、数据包接收 netdev_port_receive()

        函数 netdev_port_receive() 主要负责处理从网络设备接收的数据包,存储在 ovs-main/datapath/vport-netdev.c 文件中:

/* Must be called with rcu_read_lock. */
void netdev_port_receive(struct sk_buff *skb, struct ip_tunnel_info *tun_info) {
	struct vport *vport;

	vport = ovs_netdev_get_vport(skb->dev);
	if (unlikely(!vport))
		goto error;

	if (unlikely(skb_warn_if_lro(skb)))
		goto error;

	/* Make our own copy of the packet. 
     * Otherwise we will mangle the packet for anyone who came before us (e.g. tcpdump via AF_PACKET). */
	skb = skb_share_check(skb, GFP_ATOMIC);
	if (unlikely(!skb))
		return;

	if (skb->dev->type == ARPHRD_ETHER) {
		skb_push(skb, ETH_HLEN);
		skb_postpush_rcsum(skb, skb->data, ETH_HLEN);
	}
	ovs_vport_receive(vport, skb, tun_info);
	return;
error:
	kfree_skb(skb);
}

        函数的第一个输入参数 struct sk_buff *skb 代表接收到的数据包,第二个输入参数 struct ip_tunnel_info *tun_info 包含了数据包相关的隧道信息。注意这里的 skb 是一个指向 sk_buff 结构体的指针,代表要被处理的网络数据包,其中 sk_buff 是 Linux 内核中用于表示网络数据包的核心结构体,参见下图:

        函数首先通过 vport = ovs_netdev_get_vport(skb->dev) 获取与数据包关联的 vport 对象(代表了一个 OVS 的虚拟端口),并使用 skb = skb_share_check(skb, GFP_ATOMIC) 创建数据包的副本,以免影响其他使用该数据包的实体(比如 tcpdump 或镜像端口)。如果数据包来自以太网设备(ARPHRD_ETHER),则添加以太网头部(ETH_HLEN)并更新校验和。最后调用 ovs_vport_receive(vport, skb, tun_info) 函数将数据包转发给 OVS 虚拟端口进行进一步处理。

三、数据包传递 ovs_vport_receive()

         函数 ovs_vport_receive() 用于将接收到的数据包传递到数据平面进行处理,存储在 ovs-main/datapath/vport.c 文件中:

/* Must be called with rcu_read_lock.  
 * The packet cannot be shared and skb->data should point to the Ethernet header. */
int ovs_vport_receive(struct vport *vport, struct sk_buff *skb, const struct ip_tunnel_info *tun_info) {
	struct sw_flow_key key;
	int error;

	OVS_CB(skb)->input_vport = vport;
	OVS_CB(skb)->mru = 0;
	OVS_CB(skb)->cutlen = 0;
	if (unlikely(dev_net(skb->dev) != ovs_dp_get_net(vport->dp))) {
		u32 mark;

		mark = skb->mark;
		skb_scrub_packet(skb, true);
		skb->mark = mark;
		tun_info = NULL;
	}

	ovs_skb_init_inner_protocol(skb);
	skb_clear_ovs_gso_cb(skb);
	/* Extract flow from 'skb' into 'key'. */
	error = ovs_flow_key_extract(tun_info, skb, &key);
	if (unlikely(error)) {
		kfree_skb(skb);
		return error;
	}
	ovs_dp_process_packet(skb, &key);
	return 0;
}

        函数的第一个输入参数 struct vport *vport 是在 netdev_port_receive() 函数中关联的 vport 对象,其他参数和 netdev_port_receive() 函数相同。

        函数首先设置 OVS_CB(skb) 结构体中的一些字段如 input_vport、mru 和 cutlen,其中 OVS_CB(skb) 结构体主要用于在 sk_buff 结构体的私有数据区域 (skb->cb) 存储 OVS 的相关的信息,定义在 ovs-main/datapath/datapath.h 头文件中:

/* struct ovs_skb_cb - OVS data in skb CB */
struct ovs_skb_cb {
	struct vport		*input_vport;
	u16			mru;
	u16			acts_origlen;
	u32			cutlen;
};
#define OVS_CB(skb) ((struct ovs_skb_cb *)(skb)->cb)

        然后调用 ovs_skb_init_inner_protocol(skb) 函数初始化数据包的内部协议信息,并调用 skb_clear_ovs_gso_cb(skb) 函数清除 skb 中与 OVS 分组服务相关的回调函数。接下来使用 error = ovs_flow_key_extract(tun_info, skb, &key) 从 skb 中提取流的 key 值信息到 key 结构体中,如果提取失败还需要返回错误码。最后,调用 ovs_dp_process_packet(skb, &key) 函数将数据包及其 key 值信息传递到数据平面进行处理。

四、数据包处理 ovs_dp_process_packet()

        函数 ovs_dp_process_packet() 主要实现 OVS 数据平面的数据包处理逻辑,存储在 ovs-main/datapath/datapath.c 头文件中:

/* Must be called with rcu_read_lock. */
void ovs_dp_process_packet(struct sk_buff *skb, struct sw_flow_key *key) {
	const struct vport *p = OVS_CB(skb)->input_vport;
	struct datapath *dp = p->dp;
	struct sw_flow *flow;
	struct sw_flow_actions *sf_acts;
	struct dp_stats_percpu *stats;
	u64 *stats_counter;
	u32 n_mask_hit;
	int error;

	stats = this_cpu_ptr(dp->stats_percpu);

	/* Look up flow. */
	flow = ovs_flow_tbl_lookup_stats(&dp->table, key, skb_get_hash(skb), &n_mask_hit);
	if (unlikely(!flow)) {
		struct dp_upcall_info upcall;

		memset(&upcall, 0, sizeof(upcall));
		upcall.cmd = OVS_PACKET_CMD_MISS;
		upcall.portid = ovs_vport_find_upcall_portid(p, skb);
		upcall.mru = OVS_CB(skb)->mru;
		error = ovs_dp_upcall(dp, skb, key, &upcall, 0);
		if (unlikely(error))
			kfree_skb(skb);
		else
			consume_skb(skb);
		stats_counter = &stats->n_missed;
		goto out;
	}

	ovs_flow_stats_update(flow, key->tp.flags, skb);
	sf_acts = rcu_dereference(flow->sf_acts);
	error = ovs_execute_actions(dp, skb, sf_acts, key);
	if (unlikely(error))
		net_dbg_ratelimited("ovs: action execution error on datapath %s: %d\n", ovs_dp_name(dp), error);

	stats_counter = &stats->n_hit;

out:
	/* Update datapath statistics. */
	u64_stats_update_begin(&stats->syncp);
	(*stats_counter)++;
	stats->n_mask_hit += n_mask_hit;
	u64_stats_update_end(&stats->syncp);
}

        函数的第一个输入参数 struct sk_buff *skb 代表接收到的数据包,第二个参数 struct sw_flow_key *key 是在 ovs_vport_receive() 函数中调用 ovs_flow_key_extract() 函数获取的 key 值信息。

        函数首先使用 flow = ovs_flow_tbl_lookup_stats(&dp->table, key, skb_get_hash(skb), &n_mask_hit),以根据数据包的 hash 值和 key 结构体在数据平面的流表中查找匹配的流表项 flow。此时根据是否成功匹配流表分别采用的不同的数据包处理策略。

        新流处理(数据包首次处理):如果找不到匹配的流表项,则会创建一个 upcall 结构体,包含 OVS_PACKET_CMD_MISS 命令和其他必要信息。然后调用 ovs_dp_upcall(dp, skb, key, &upcall, 0) 函数,将数据包发送到 vswitchd 守护进程进行处理。这里对应 Open vSwitch 数据包处理流程中的慢速路径

        已知流处理(数据包非首次处理):如果存在匹配的流表项,则更新流表项的统计信息(如包计数和标志位),并获取与流表项关联的动作集 sf_acts。接下来调用 ovs_execute_actions(dp, skb, sf_acts, key) 函数,执行与该流关联的数据包处理动作。这里对应 Open vSwitch 数据包处理流程中的快速路径

        最后,更新数据平面的统计信息。这里使用 u64_stats_update_begin() 和 u64_stats_update_end() 原子更新的方式,避免出现数据的不一致。

五、同步机制

        细心的你可能发现了,在数据包接收的整个流程中,每个函数最开头的注释都提到了:

/* Must be called with rcu_read_lock. */

        这里的 rcu_read_lock() 是 Linux 内核中实现读-写锁的一种机制,即 RCU(Read-Copy-Update),具有以下特点:

  • rcu_read_lock() 通过 rcu_read_lock() 和 rcu_read_unlock() 来标记和保护临界区资源,并且在受保护的临界区内,读取线程可以安全地访问数据结构,不会观察到中间状态。
  • RCU 允许读取线程并发地访问共享数据,而不会被写入线程的修改所干扰(这种无锁的并发读写机制主要是通过延迟释放旧版本的数据结构来实现的)
  • 相比传统的读写锁,RCU 在读取密集型场景下能提供更好的性能,因为读取线程不需要获取锁,减少了竞争和上下文切换

        总的来说,Open vSwitch 通过 RCU 机制确保了在处理数据包时,不会观察到数据结构的中间状态,从而保证数据包处理的正确性

Tips:细心的你可能又发现了,在数据包接收的整个流程中,出现过很多关于 error 的判断,并且对于所有 error 的处理方式都差不多,即丢弃数据包并释放空间。也就是说 Open vSwitch 在数据包接收的过程中是没有反馈机制的,出现问题直接丢掉。其实这是非常合理的,因为对于数据包的传输而言,网络层主要提供的是不可靠的服务,可靠的服务会在链路层和传输层实现。

总结:

        本文介绍了 Open vSwitch 数据包接收过程的源码实现,其中函数 ovs_dp_process_packet() 是 Datapath 模块进行数据包处理的核心函数,非常重要


        数据包的接收过程的调用关系如上图所示,并以此函数作为终点,但是对于 upcall 调用和数据包匹配和发送等内容而言,则将以此函数作为起点来讨论。

        由于本人水平有限,以上内容如有不足之处欢迎大家指正(评论区/私信均可)。

参考资料:

Open vSwitch 官网

Open vSwitch 源代码 GitHub

关键数据结构 sk_buff_skb 的 freeback 字段-CSDN博客

Open vSwitch v2.17.10 LTS 源代码

如何提高 Linux RCU 实时性-望获OS

Linux 网络协议栈 ovs 收发包 - 简书

OVS - 数据包处理流程 ovs 的工作流程-CSDN博客

Open vSwitch 2.3.90 源码阅读笔记(上) | SDNLAB

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1809828.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL 常见客户端程序

本篇主要介绍MySQL常见的客户端程序 目录 一、mysqlcheck 二、mysqldump 三、mysqladmin 四、mysqldumpslow 五、mysqlbinlog 六、mysqlshow 显示列的具体信息​编辑 七、mysqlslap 一、mysqlcheck mysqlcheck是MySQL的表维护程序,其功能主要包含以下四个方…

遗传算法笔记:基本工作流程

1 介绍 遗传算法有5个主要任务,直到找到最终的解决方案 2 举例 2.1 问题描述 比如我们有 5 个变量和约束,其中 X1、X2、X3、X4 和 X5 是非负整数且小于 10(0、1、2、4、5、6、7、8、9)我们希望找到 X1、X2、X3、X4 和 X5 的最…

01 Linux网络设置

目录 1.1 查看及测试网络 1.1.1 查看网络配置 1. 查看网络接口地址 1. 查看活动的网络接口设备 2. 查看指定的网络接口信息 2. 查看主机名称 3. 查看路由表条目 4. 查看网络连接情况 1.1.2 测试网络连接 1. 测试网络连通性 2. 跟踪数据包的路由途径 3. 测试DNS域名解析 1.2 设…

Apache ShardingSphere实战与核心源码剖析

Apache ShardingSphere实战与核心源码剖析 1.数据库架构演变与分库分表介绍 1.1 海量数据存储问题及解决方案 如今随着互联网的发展,数据的量级也是成指数的增长,从GB到TB到PB。对数据的各种操作也是愈加的困难,传统的关系性数据库已经无法满足快速查询与插入数据的需求。…

HTML LocalStorage

一篇关于HTML本地存储的文章 Window.localStorage 只读的localStorage 属性允许你访问一个Document 源(origin)的对象 Storage;存储的数据将保存在浏览器会话中。 localStorage 类似 sessionStorage,但其区别在于:存储…

AXI_GPIO

REVIEW 关于PS端已经学习过: zynq PS端 GPIO-CSDN博客 zynq PS点灯-CSDN博客 C基础与SDK调试方法-CSDN博客 Zynq上GPIO无论是MIO还是EMIO,都是属于PS侧的资源,相当于是硬核。 而作为一个PS与PL相互协作的平台,当PS侧的GPIO硬核不…

使用opencv在图像上画带刻度线的对角线,以图像中心点为0点

使用OpenCV在图像上绘制带刻度线的对角线,可以通过以下步骤实现。我们将首先找到图像的中心点,然后绘制对角线线,并在这些线的适当位置绘制刻度线。以下是详细的C代码示例: void Draw_diagonal(cv::Mat& mat, double dFactor…

【Python教程】4-字符串、列表、字典、元组与集合操作

在整理自己的笔记的时候发现了当年学习python时候整理的笔记,稍微整理一下,分享出来,方便记录和查看吧。个人觉得如果想简单了解一名语言或者技术,最简单的方式就是通过菜鸟教程去学习一下。今后会从python开始重新更新&#xff0…

shell编程(四)—— 运算符

和其他编程语言一样,bash也有多种类型的运算符,本篇对bash的相关运算符做简单介绍。 一、运算符 1.1 算术运算符 常见的算术运算符,如加()、减(-)、乘(*)、除&#xf…

Qt安装时出现无法下载存档,环境配置,main中自定义类编译不过问题

1. Qt安装时出现无法下载存档 进入Qt安装程序exe所在的文件目录,一般在下载文件夹,右键打开cmd。cmd输入:对应的exe镜像提速。 .\qt-online-installer-windows-x64-4.8.0.exe --mirror https://mirrors.cloud.tencent.com/qt/ 2. 环境配置 …

统计信号处理基础 习题解答10-11

题目 我们希望根据一个人的身高来估计他的体重。为了判断其可行性,对N100个人取数据,产生有序的数据对(h,w),其中h代表身高,w代表体重。得到的数据如图10.9(a)所示的。解释你如何利用MMSE估计量根据一个人的身高来猜测他的体重。对于这些数据的建模有些什么样的假设…

6、后端项目初始化

打开idea后, New Project ,用Maven构建 Spring Boot 项目 点击Next后:先勾选两个基本的依赖,后面再手动添加其它需要的依赖 Spring Web: 表示是一个web应用程序 Lombok:写实体类的时候添加Data注解后就会自动加上g…

npm install 的原理

1. 执行命令发生了什么 ? 执行命令后,会将安装相关的依赖,依赖会存放在根目录的node_modules下,默认采用扁平化的方式安装,排序规则为:bin文件夹为第一个,然后是开头系列的文件夹,后…

关于头条项目经验面试题的总结

文章目录 前言一、论坛项目经典话术二、请你介绍一下你最近的项目吧2.1 话术1 三、你的公司的开发环境是怎么搭建的?四、登录你们是怎么做的?4.1 账号密码登录4.2 手机验证码发送4.2.1 手机验证码发送4.2.2 手机验证码登录 五、用户行为限流是怎么做的&a…

Java面向对象-方法的重写、super

Java面向对象-方法的重写、super 一、方法的重写二、super关键字1、super可以省略2、super不可以省略3、super修饰构造器4、继承条件下构造方法的执行过程 一、方法的重写 1、发生在子类和父类中,当子类对父类提供的方法不满意的时候,要对父类的方法进行…

1000道互联网大厂面试题:ZooKeeper+Dubbo+Spring+MySQL等(含答案)

然后存储回内存,这个过程可能会出现多个线程交差。 24、a a b 与 a b 的区别 25、我能在不进行强制转换的情况下将一个 double 值赋值给 long类型的变量吗? 26、3*0.1 0.3 将会返回什么?true 还是 false? 27、int 和 Inte…

C语言学生管理系统

整理U盘发现一个C语言写的学生管理系统&#xff0c;全部代码放放我上传的资源里了 #include<stdio.h> #include<stdlib.h> #include<string.h>//需要用到strcmp函数#define LEN 15//姓名和学号的最大字符数 #define N 50//最大学生人数int n 0, t 1;//n代表…

企业微信应用 应用号内消息链接无法在企微内置浏览器打开 windows PC客户端 问题解决

问题 需求是每周在企微的应用号上发送周报&#xff0c;周报中会带着进入系统的链接&#xff0c;点击进入可以查看详情。系统需要登录鉴权&#xff0c;因此需要在内置浏览器打开便于自动认证。 但是&#xff0c;在企微应用号发送的链接&#xff0c;手机上可以正常使用企微内置…

【Vue】请求动态渲染数据

目标 请求获取数据存入 vuex, 映射渲染 安装 axios yarn add axios准备actions 和 mutations App.vue页面中调用 action, 获取数据 验证数据是否存储成功 动态渲染 cart-item.vue

现实转虚拟:Video2Game引领3D互动体验

在当今数字化时代&#xff0c;虚拟环境的创建对于游戏开发、虚拟现实应用和自动驾驶模拟器等多个领域至关重要。然而&#xff0c;传统的虚拟环境创建过程不仅复杂而且成本高昂&#xff0c;通常需要专业人员和专业软件开发工具的参与。例如&#xff0c;著名的《侠盗猎车手V》以其…