ovs patch port 和 veth pair 功能上很像(类似 跳线)
OVS 跳线端口就像一根从一个 (OVS) 交换机端口插入到另一个 (OVS) 交换机端口的物理电缆。它与 Linux veth pair 非常相似。
事实上,在某些情况下,这两者可以替换使用。
例如,在 OpenStack 计算节点中,通常有两个 ovs 桥:br-int
和 br-eth1
的在早期的 OpenStack 版本(在 Kilo 之前)中,两者通过 linux veth 对连接。但是,在较新的发行版(例如 Liberty 之后的发行版)中,默认连接方式已更改为 OVS 补丁端口。
根据一些资料,从 linux veth 对切换到 OVS 补丁端口的原因是为了性能考虑。除此之外,至少对于 OpenStack,补丁端口带来了另一个巨大的好处:在 OVS 中子代理重启期间,实例 (VM) 的流量不会中断 - 这就是 **OVS 代理正常重启] ** 在较新的 OpenStack 版本中实现的。
1. OVS netdev
网络设备(例如物理网卡)有两端(部分):
- 一端在内核中,负责发送/接收,
- 一端在用户空间中,用于管理内核部分,例如更改设备 MTU 大小、禁用/启用队列等。
内核和用户空间空间之间的通信通常通过 netlink 或 ioctl(已弃用)进行。
对于 TUN/TAP 等虚拟网络设备,其工作过程类似,需要确保 TAP 设备接收的数据包不是来自外部,而是来自用户空间;
TAP 设备发送的数据包不会发送到外部,而是进入用户空间。
在 OVS 中, 一个 struct netdev
实例代表 OVS 用户空间中的一个网络设备,它用于控制这个设备的内核端,它可以是物理网卡、TAP 设备或其他类型。
c
/* A network device (e.g. an Ethernet device) */
struct netdev {
char *name; /* Name of network device. */
const struct netdev_class *netdev_class; /* Functions to control
this device. */
...
int n_txq;
int n_rxq;
int ref_cnt; /* Times this devices was opened. */
};
netdev_class
是所有网络设备的一般抽象,定义在 lib/netdev-provider.h
中。
c
struct netdev_class {
const char *type; /* Type of netdevs in this class, e.g. "system", "tap", "gre", etc. */
bool is_pmd; /* If 'true' then this netdev should be polled by PMD threads. */
/* ## Top-Level Functions ## */
int (*init)(void);
void (*run)(const struct netdev_class *netdev_class);
void (*wait)(const struct netdev_class *netdev_class);
/* ## netdev Functions ## */
int (*construct)(struct netdev *);
void (*destruct)(struct netdev *);
...
int (*rxq_recv)(struct netdev_rxq *rx, struct dp_packet_batch *batch);
void (*rxq_wait)(struct netdev_rxq *rx);
};
任何设备类型都必须在使用 (成为 netdev provider ) 之前实现 netdev_class
中的方法,因此在不同平台上有不同类型的实现:用于 Linux 平台、用于 BSD 平台、用于 Windows 等。
2. Linux netdev
system
-netdev_linux_class
system
设备的 send()
方法会通过 AF_PACKET
套接字向内核发送数据包
internal
-netdev_internal_class
internal
设备的 send()
方法将通过 AF_PACKET
套接字向内核发送数据包
tap
-netdev_tap_class
TAP
设备的 send()
方法将通过 TAP 设备的用户空间部分的 write
系统调用将数据包发送到内核: write(netdev->tap_fd, data, size)
下面是 3 种 linux netdev 的声明,其中 NETDEV_LINUX_CLASS
是一个用于初始化所有回调的宏:
kotlin
const struct netdev_class netdev_linux_class =
NETDEV_LINUX_CLASS(
"system",
netdev_linux_construct,
netdev_linux_get_stats,
netdev_linux_get_features,
netdev_linux_get_status);
const struct netdev_class netdev_tap_class =
NETDEV_LINUX_CLASS(
"tap",
netdev_linux_construct_tap,
netdev_tap_get_stats,
netdev_linux_get_features,
netdev_linux_get_status);
const struct netdev_class netdev_internal_class =
NETDEV_LINUX_CLASS(
"internal",
netdev_linux_construct,
netdev_internal_get_stats,
NULL, /* get_features */
netdev_internal_get_status);
3. vport netdev
vport 是 OVS 数据路径中的 OVS 抽象虚拟端口。vport netdevs 是 OVS vports 的用户空间部分。它分为隧道型 和补丁型两大类。
-
tunnel class 隧道类 用于隧道网络
geneve
日内瓦
gre
vxlan
lisp
stt
短期试验
-
patch
-patch_class
用于在不同 OVS 网桥之间转发数据包
隧道 vport 的注册位于 netdev_vport_tunnel_register()
中,
patch 端口在 netdev_vport_patch_register()
中。
然后,它们都将调用 netdev_register_provider()
arduino
void
netdev_vport_tunnel_register(void)
{
static const struct vport_class vport_classes[] = {
TUNNEL_CLASS("geneve", "genev_sys", netdev_geneve_build_header,
netdev_tnl_push_udp_header, netdev_geneve_pop_header),
TUNNEL_CLASS("gre", "gre_sys", netdev_gre_build_header,
netdev_gre_push_header, netdev_gre_pop_header),
TUNNEL_CLASS("vxlan", "vxlan_sys", netdev_vxlan_build_header,
netdev_tnl_push_udp_header, netdev_vxlan_pop_header),
TUNNEL_CLASS("lisp", "lisp_sys", NULL, NULL, NULL),
TUNNEL_CLASS("stt", "stt_sys", NULL, NULL, NULL),
};
for (i = 0; i < ARRAY_SIZE(vport_classes); i++) {
netdev_register_provider(&vport_classes[i].netdev_class);
}
}
void
netdev_vport_patch_register(void)
{
static const struct vport_class patch_class =
{ NULL,
{ "patch", false,
VPORT_FUNCTIONS(get_patch_config, set_patch_config,
NULL, NULL, NULL, NULL, NULL) }};
netdev_register_provider(&patch_class.netdev_class);
}
以下是简化的 init 宏:
php
#define VPORT_FUNCTIONS(GET_CONFIG, SET_CONFIG, \
GET_TUNNEL_CONFIG, GET_STATUS, \
BUILD_HEADER, \
PUSH_HEADER, POP_HEADER) \
netdev_vport_alloc, \
netdev_vport_construct, \
BUILD_HEADER, \
PUSH_HEADER, \
POP_HEADER, \
\
NULL, /* send */ \
NULL, /* send_wait */ \
...
NULL, /* rx_recv */ \
NULL, /* rx_drain */
#define TUNNEL_CLASS(NAME, DPIF_PORT, BUILD_HEADER, PUSH_HEADER, POP_HEADER) \
{ DPIF_PORT, \
{ NAME, false, \
VPORT_FUNCTIONS(get_tunnel_config, \
set_tunnel_config, \
get_netdev_tunnel_config, \
tunnel_get_status, \
BUILD_HEADER, PUSH_HEADER, POP_HEADER) }}
注意 ,所有 vport 类型的 netdev 的 send
和 rx_recv
回调都是 NULL
。
这意味着:数据包无法通过 vport netdevs 从用户空间发送到内核,并且 vport 不会从物理网卡接收数据包
实际上,vport
用于在 DataPath 内部转发数据包 ,或者通过调用内核dev_queue_xmit()
方法将数据包发送出去 。
patch port
没有实现 netdev_class
的 send 和 receive 方法,因此无法通过 patch port
从用户空间向内核 vport 发送数据包,并且 patch port
不会接收来自物理设备的数据包。
实际上,一个 patch 端口只接收来自 ovs bridge (ofproto
) 另一端(端口)的数据包。通过这种方式,它连接了两侧(通常是两个 OVS 桥)。
参考: