Openvswitch原理与代码分析(1):总体架构

一、Opevswitch总体架构

Openvswitch的架构网上有如下的图表示:

每个模块都有不同的功能

ovs-vswitchd 为主要模块,实现交换机的守护进程daemon

在Openvswitch所在的服务器进行ps aux可以看到以下的进程


root 1008 0.1 0.8 242948 31712 ? S<Ll Aug06 32:17 ovs-vswitchd unix:/var/run/openvswitch/db.sock -vconsole:emer -vsyslog:err -vfile:info --mlockall
--no-chdir --log-file=/var/log/openvswitch/ovs-vswitchd.log --pidfile=/var/run/openvswitch/ovs-vswitchd.pid --detach --monitor

注意这里ovs-vswitchd监听了一个本机的db.sock文件

openvswitch.ko为Linux内核模块,支持数据流在内核的交换

我们使用lsmod列举加载到内核的模块:


~# lsmod | grep openvswitch

openvswitch 66901 0

gre 13808 1 openvswitch

vxlan 37619 1 openvswitch

libcrc32c 12644 2 btrfs,openvswitch

既有Openvswitch.ko,也有

ovsdb-server 轻量级数据库服务器,保存配置信息,ovs-vswitchd通过这个数据库获取配置信息

通过ps aux可以看到如下进程


root 985 0.0 0.0 21172 2120 ? S< Aug06 1:20 ovsdb-server /etc/openvswitch/conf.db -vconsole:emer -vsyslog:err -vfile:info --remote=punix:/var/run/openvswitch/db.sock --private-key=db:Open_vSwitch,SSL,private_key
--certificate=db:Open_vSwitch,SSL,certificate --bootstrap-ca-cert=db:Open_vSwitch,SSL,ca_cert --no-chdir --log-file=/var/log/openvswitch/ovsdb-server.log --pidfile=/var/run/openvswitch/ovsdb-server.pid --detach –monitor

可以看出,ovsdb-server将配置信息保存在conf.db中,并通过db.sock提供服务,ovs-vswitchd通过这个db.sock从这个进程读取配置信息。

/etc/openvswitch/conf.db是json格式的,可以通过命令ovsdb-client dump将数据库结构打印出来。

数据库结构包含如下的表格。

数据库结构如下:

通过ovs-vsctl创建的所有的网桥,网卡,都保存在数据库里面,ovs-vswitchd会根据数据库里面的配置创建真正的网桥,网卡。

ovs-dpctl 用来配置switch内核模块。

ovs-vsctl 查询和更新ovs-vswitchd的配置。

ovs-appctl 发送命令消息,运行相关daemon。

ovs-ofctl 查询和控制OpenFlow交换机和控制器。

二、Openvswitch的代码结构

Openvwitch进行数据流交换的主要逻辑都是在ovs-vswitchd和openvswitch.ko里面实现的。

ovs-vswitchd会从ovsdb-server读取配置,然后调用ofproto层进行虚拟网卡的创建或者流表的操作。

Ofproto是一个库,实现了软件的交换机和对流表的操作。

Netdev层抽象了连接到虚拟交换机上的网络设备。

Dpif层实现了对于流表的操作。

对于OVS来讲,有以下几种网卡类型

1). netdev: 通用网卡设备 eth0 veth

接收: 一个nedev在L2收到报文后回直接通过ovs接收函数处理,不会再走传统内核协议栈.

发送: ovs中的一条流指定从该netdev发出的时候就通过该网卡设备发送

2). internal: 一种虚拟网卡设备

接收: 当从系统发出的报文路由查找通过该设备发送的时候,就进入ovs接收处理函数

发送: ovs中的一条流制定从该internal设备发出的时候,该报文被重新注入内核协议栈

3). gre device: gre设备. 不管用户态创建多少个gre tunnel, 在内核态有且只有一个gre设备

接收: 当系统收到gre报文后,传递给L4层解析gre header, 然后传递给ovs接收处理函数

发送: ovs中的一条流制定从该gre设备发送, 报文会根据流表规则加上gre头以及外层包裹ip,查找路由发送

在如上的代码结构中,vswitchd中就是ovs-vswitchd的入口代码,ovsdb就是ovsdb-server的代码,ofproto即上述的中间抽象层,lib下面有netdev,dpif的实现,datapath里面就是内核模块openvswitch.ko的代码。

三、ovs-vswitchd和openvswitch.ko的交互方式netlink

datapath 运行在内核态,ovs-vswitchd 运行在用户态,两者通过netlink 通信。

netlink 是一种灵活和强大的进程间通信机制(socket),甚至可以沟通用户态和内核态。

netlink 是全双工的。作为socket,netlink 的地址族是AF_NETLINK(TCP/IP socket 的地址族是AF_INET)

目前有大量的通信场景应用了netlink,这些特定扩展和设计的netlink 通信bus,被定义为family。比如NETLINK_ROUTE、NETLINK_FIREWALL、NETLINK_ARPD 等。

因为大量的专用family 会占用了family id,而family id 数量自身有限(kernel 允许32个);同时为了方便用户扩展使用,一个通用的netlink family 被定义出来,这就是generic netlink family。

要使用generic netlink,需要熟悉的数据结构包括genl_family、genl_ops 等。

下面写一个generic netlink的简单实例

定义family如下

  1. /* attributes */
  2. enum {
  3. DOC_EXMPL_A_UNSPEC,
  4. DOC_EXMPL_A_MSG,
  5. __DOC_EXMPL_A_MAX,
  6. };
  7. #define DOC_EXMPL_A_MAX (__DOC_EXMPL_A_MAX - 1)
  8. /* attribute policy */
  9. static struct nla_policy
    doc_exmpl_genl_policy[DOC_EXMPL_A_MAX + 1] = {
  10. [DOC_EXMPL_A_MSG] = { .type = NLA_NUL_STRING },
  11. };
  12. /* family definition */
  13. static struct genl_family
    doc_exmpl_gnl_family = {
  14. .id = GENL_ID_GENERATE,
  15. .hdrsize = 0,
  16. .name = "DOC_EXMPL",
  17. .version = 1,
  18. .maxattr = DOC_EXMPL_A_MAX,
  19. };

定义op如下

  1. /* handler */
  2. int doc_exmpl_echo(struct sk_buff
    *skb, struct genl_info *info)
  3. {
  4. /* message handling code goes here; return 0 on success, negative values on failure */
  5. }
  6. /* commands */
  7. enum {
  8. DOC_EXMPL_C_UNSPEC,
  9. DOC_EXMPL_C_ECHO,
  10. __DOC_EXMPL_C_MAX,
  11. };
  12. #define DOC_EXMPL_C_MAX (__DOC_EXMPL_C_MAX - 1)
  13. /* operation definition */
  14. struct genl_ops doc_exmpl_gnl_ops_echo = {
  15. .cmd = DOC_EXMPL_C_ECHO,
  16. .flags = 0,
  17. .policy = doc_exmpl_genl_policy,
  18. .doit = doc_exmpl_echo,
  19. .dumpit = NULL,
  20. };

注册family 到generic netlink 机制

  1. int rc;
  2. rc = genl_register_family(&doc_exmpl_gnl_family);
  3. if (rc != 0)
  4. goto failure;

将操作注册到family

  1. int rc;
  2. rc = genl_register_ops(&doc_exmpl_gnl_family, &doc_exmpl_gnl_ops_echo);
  3. if (rc != 0)
  4.     goto failure;

Datapath是如何使用netlink的呢?

在dp_init()函数(datapath.c)中,调用dp_register_genl()完成对四种类型的family 以及相应操作的注册,包括datapath、vport、flow 和packet。

前三种family,都对应四种操作都包括NEW、DEL、GET、SET,而packet 的操作仅为EXECUTE。

对于flow这个family的定义如下:

  1. static const struct nla_policy
    flow_policy[OVS_FLOW_ATTR_MAX + 1] = {
  2.     [OVS_FLOW_ATTR_KEY] = { .type = NLA_NESTED },
  3.     [OVS_FLOW_ATTR_ACTIONS] = { .type = NLA_NESTED },
  4.     [OVS_FLOW_ATTR_CLEAR] = { .type = NLA_FLAG },
  5. };
  6. static struct genl_family
    dp_flow_genl_family = {
  7.     .id = GENL_ID_GENERATE,
  8.     .hdrsize = sizeof(struct ovs_header),
  9.     .name = OVS_FLOW_FAMILY,
  10.     .version = OVS_FLOW_VERSION,
  11.     .maxattr = OVS_FLOW_ATTR_MAX,
  12.     SET_NETNSOK
  13. };

Flow相关的ops的定义如下:

  1. static struct genl_ops dp_flow_genl_ops[]
    = {
  2.     {
  3.         .cmd = OVS_FLOW_CMD_NEW,
  4.         .flags = GENL_ADMIN_PERM, /* Requires CAP_NET_ADMIN privilege. */
  5.         .policy = flow_policy,
  6.         .doit = ovs_flow_cmd_new_or_set
  7.     },
  8.     {
  9.         .cmd = OVS_FLOW_CMD_DEL,
  10.         .flags = GENL_ADMIN_PERM, /* Requires CAP_NET_ADMIN privilege. */
  11.         .policy = flow_policy,
  12.         .doit = ovs_flow_cmd_del
  13.     },
  14.     {
  15.         .cmd = OVS_FLOW_CMD_GET,
  16.         .flags = 0, /* OK for unprivileged users. */
  17.         .policy = flow_policy,
  18.         .doit = ovs_flow_cmd_get,
  19.         .dumpit = ovs_flow_cmd_dump
  20.     },
  21.     {
  22.         .cmd = OVS_FLOW_CMD_SET,
  23.         .flags = GENL_ADMIN_PERM, /* Requires CAP_NET_ADMIN privilege. */
  24.         .policy = flow_policy,
  25.         .doit = ovs_flow_cmd_new_or_set,
  26.     },
  27. };

Ovs-vswitchd作为客户端如何使用netlink

Ovs-vswitchd 对于netlink 的实现,主要在lib/netlink-socket.c 文件中。

lib\dpif-provider.h定义了struct dpif_class {,包含一系列函数指针,例如open,close等。

真正的dpif_class有两种

一个是dpif-netdev.c中定义的const struct dpif_class dpif_netdev_class = {

  1. const struct dpif_class
    dpif_netdev_class = {
  2.     "netdev",
  3.     dpif_netdev_init,
  4.     dpif_netdev_enumerate,
  5.     dpif_netdev_port_open_type,
  6.     dpif_netdev_open,
  7.     dpif_netdev_close,
  8.     dpif_netdev_destroy,
  9.     dpif_netdev_run,
  10.     dpif_netdev_wait,
  11.     dpif_netdev_get_stats,
  12.     dpif_netdev_port_add,
  13.     dpif_netdev_port_del,
  14.     dpif_netdev_port_query_by_number,
  15.     dpif_netdev_port_query_by_name,
  16.     NULL, /* port_get_pid */
  17.     dpif_netdev_port_dump_start,
  18.     dpif_netdev_port_dump_next,
  19.     dpif_netdev_port_dump_done,
  20.     dpif_netdev_port_poll,
  21.     dpif_netdev_port_poll_wait,
  22.     dpif_netdev_flow_flush,
  23.     dpif_netdev_flow_dump_create,
  24.     dpif_netdev_flow_dump_destroy,
  25.     dpif_netdev_flow_dump_thread_create,
  26.     dpif_netdev_flow_dump_thread_destroy,
  27.     dpif_netdev_flow_dump_next,
  28.     dpif_netdev_operate,
  29.     NULL, /* recv_set */
  30.     NULL, /* handlers_set */
  31.     dpif_netdev_pmd_set,
  32.     dpif_netdev_queue_to_priority,
  33.     NULL, /* recv */
  34.     NULL, /* recv_wait */
  35.     NULL, /* recv_purge */
  36.     dpif_netdev_register_dp_purge_cb,
  37.     dpif_netdev_register_upcall_cb,
  38.     dpif_netdev_enable_upcall,
  39.     dpif_netdev_disable_upcall,
  40.     dpif_netdev_get_datapath_version,
  41.     NULL, /* ct_dump_start */
  42.     NULL, /* ct_dump_next */
  43.     NULL, /* ct_dump_done */
  44.     NULL, /* ct_flush */
  45. };

一种是在dpif-netlink.c中,定义了const struct dpif_class dpif_netlink_class = {

  1. const struct dpif_class
    dpif_netlink_class = {
  2.     "system",
  3.     NULL, /* init */
  4.     dpif_netlink_enumerate,
  5.     NULL,
  6.     dpif_netlink_open,
  7.     dpif_netlink_close,
  8.     dpif_netlink_destroy,
  9.     dpif_netlink_run,
  10.     NULL, /* wait */
  11.     dpif_netlink_get_stats,
  12.     dpif_netlink_port_add,
  13.     dpif_netlink_port_del,
  14.     dpif_netlink_port_query_by_number,
  15.     dpif_netlink_port_query_by_name,
  16.     dpif_netlink_port_get_pid,
  17.     dpif_netlink_port_dump_start,
  18.     dpif_netlink_port_dump_next,
  19.     dpif_netlink_port_dump_done,
  20.     dpif_netlink_port_poll,
  21.     dpif_netlink_port_poll_wait,
  22.     dpif_netlink_flow_flush,
  23.     dpif_netlink_flow_dump_create,
  24.     dpif_netlink_flow_dump_destroy,
  25.     dpif_netlink_flow_dump_thread_create,
  26.     dpif_netlink_flow_dump_thread_destroy,
  27.     dpif_netlink_flow_dump_next,
  28.     dpif_netlink_operate,
  29.     dpif_netlink_recv_set,
  30.     dpif_netlink_handlers_set,
  31.     NULL, /* poll_thread_set */
  32.     dpif_netlink_queue_to_priority,
  33.     dpif_netlink_recv,
  34.     dpif_netlink_recv_wait,
  35.     dpif_netlink_recv_purge,
  36.     NULL, /* register_dp_purge_cb */
  37.     NULL, /* register_upcall_cb */
  38.     NULL, /* enable_upcall */
  39.     NULL, /* disable_upcall */
  40.     dpif_netlink_get_datapath_version, /* get_datapath_version */
  41. #ifdef __linux__
  42.     dpif_netlink_ct_dump_start,
  43.     dpif_netlink_ct_dump_next,
  44.     dpif_netlink_ct_dump_done,
  45.     dpif_netlink_ct_flush,
  46. #else
  47.     NULL, /* ct_dump_start */
  48.     NULL, /* ct_dump_next */
  49.     NULL, /* ct_dump_done */
  50.     NULL, /* ct_flush */
  51. #endif
  52. };

datapath 中对netlink family 类型进行了注册,ovs-vswitchd 在使用这些netlink family 之前需要获取它们的信息,这一过程主要在lib/dpif-netlink.c 文件(以dpif_netlink_class 为例),dpif_netlink_init ()函数。

  1. static int
  2. dpif_netlink_init(void)
  3. {
  4.     static struct ovsthread_once
    once = OVSTHREAD_ONCE_INITIALIZER;
  5.     static int error;
  6.  
  7.     if (ovsthread_once_start(&once)) {
  8.         error = nl_lookup_genl_family(OVS_DATAPATH_FAMILY,
  9.                                       &ovs_datapath_family);
  10.         if (error) {
  11.             VLOG_ERR("Generic Netlink family ‘%s‘ does not exist. "
  12.                      "The Open vSwitch kernel module is probably not loaded.",
  13.                      OVS_DATAPATH_FAMILY);
  14.         }
  15.         if (!error) {
  16.             error = nl_lookup_genl_family(OVS_VPORT_FAMILY, &ovs_vport_family);
  17.         }
  18.         if (!error) {
  19.             error = nl_lookup_genl_family(OVS_FLOW_FAMILY, &ovs_flow_family);
  20.         }
  21.         if (!error) {
  22.             error = nl_lookup_genl_family(OVS_PACKET_FAMILY,
  23.                                           &ovs_packet_family);
  24.         }
  25.         if (!error) {
  26.             error = nl_lookup_genl_mcgroup(OVS_VPORT_FAMILY, OVS_VPORT_MCGROUP,
  27.                                            &ovs_vport_mcgroup);
  28.         }
  29.  
  30.         ovsthread_once_done(&once);
  31.     }
  32.  
  33.     return error;
  34. }

完成这些查找后,ovs-vswitchd 即可利用dpif 中的api,通过发出这些netlink 消息给datapath,实现对datapath 的操作。

时间: 2024-10-13 01:26:02

Openvswitch原理与代码分析(1):总体架构的相关文章

Openvswitch原理与代码分析(6):用户态流表flow table的操作

当内核无法查找到流表项的时候,则会通过upcall来调用用户态ovs-vswtichd中的flow table. 会调用ofproto-dpif-upcall.c中的udpif_upcall_handler函数. static void * udpif_upcall_handler(void *arg) { ????struct handler *handler = arg; ????struct udpif *udpif = handler->udpif; ? ????while (!latc

Openvswitch原理与代码分析(5): 内核中的流表flow table操作

? 当一个数据包到达网卡的时候,首先要经过内核Openvswitch.ko,流表Flow Table在内核中有一份,通过key查找内核中的flow table,即可以得到action,然后执行action之后,直接发送这个包,只有在内核无法查找到流表项的时候,才会到用户态查找用户态的流表.仅仅查找内核中flow table的情况被称为fast path. ? ? 第一步:从数据包中提取出key ? 实现函数为int ovs_flow_key_extract(const struct ip_tun

Openvswitch原理与代码分析(4):网络包的处理过程

? 在上一节提到,Openvswitch的内核模块openvswitch.ko会在网卡上注册一个函数netdev_frame_hook,每当有网络包到达网卡的时候,这个函数就会被调用. ? static struct sk_buff *netdev_frame_hook(struct sk_buff *skb) { ???if (unlikely(skb->pkt_type == PACKET_LOOPBACK)) ??????return skb; ? ???port_receive(skb)

Openvswitch原理与代码分析(2): ovs-vswitchd的启动

ovs-vswitchd.c的main函数最终会进入一个while循环,在这个无限循环中,里面最重要的两个函数是bridge_run()和netdev_run(). ? ? Openvswitch主要管理两种类型的设备,一个是创建的虚拟网桥,一个是连接到虚拟网桥上的设备. ? 其中bridge_run就是初始化数据库中已经创建的虚拟网桥. ? 一.虚拟网桥的初始化bridge_run ? bridge_run会调用bridge_run__,bridge_run__中最重要的是对于所有的网桥,都调

Openvswitch原理与代码分析(3): openvswitch内核模块的加载

在datapath/datapath.c中会调用module_init(dp_init);来初始化内核模块. static int __init dp_init(void){   int err;    BUILD_BUG_ON(sizeof(struct ovs_skb_cb) > FIELD_SIZEOF(struct sk_buff, cb));    pr_info("Open vSwitch switching datapath %s\n", VERSION);    

Openvswitch原理与代码分析(7): 添加一条流表flow

添加一个flow,调用的命令为 ovs-ofctl add-flow hello "hard_timeout=0 idle_timeout=0 priority=1 table=21 pkt_mark=0x55 tun_id=0x55 actions=mod_nw_dst:192.168.56.101,output:2" 这里调用的是调用ovs/utilities/ovs-ofctl.c的命令行工具 这个命令行工具支持的所有的命令及处理函数定义如下: static const stru

Openvswitch原理与代码分析(8): 修改Openvswitch代码添加自定义action

有时候我们需要自定义一些自己的action,根据包头里面的信息,做一些自己的操作. ? 例如添加一个action名为handle_example ? 第一.修改ofp-actions.c文件 ? 首先在ofp-actions.c里面添加Openflow各个版本的这个action static const struct ofpact_map * get_ofpact_map(enum ofp_version version) { ????/* OpenFlow 1.0 actions. */ ??

免费的Lucene 原理与代码分析完整版下载

Lucene是一个基于Java的高效的全文检索库.那么什么是全文检索,为什么需要全文检索?目前人们生活中出现的数据总的来说分为两类:结构化数据和非结构化数据.很容易理解,结构化数据是有固定格式和结构的或者有限长度的数据,比如数据库,元数据等.非结构化数据则是不定长或者没有固定格式的数据,如图片,邮件,文档等.还有一种较少的分类为半结构化数据,如XML,HTML等,在一定程度上我们可以将其按照结构化数据来处理,也可以抽取纯文本按照非结构化数据来处理.非结构化数据又称为全文数据.,对其搜索主要有两种

大型网站技术架构--核心原理和案例分析 大型网站架构演化(一)

如果把上世纪90年代CERN正式发布web标准和第一个WEB服务的出现当作互联网的开始,那么互联网站的发展之经历了短短20多年的时间.在20多年的时间里,互联网的世界发生了变化,今天,全球有近一半的人口使用互联网,人们的生活因为互联网而产生了巨大的变化.从信息检索到即使通信,从电子购物到文化娱乐,互联网渗透到生活的每一个 角落,而且这种趋势还在蔓延.因为互联网,我们的世界正变得越来越小. 同时我们也看到,在互联网跨越式发展进程中,在电子商务火热的市场背后却是不堪重负的网站架构.某些B2C网站逢促