手记

Openvswitch总体架构与代码结构

一、Openvswitch的总体架构

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

  • 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. if (ovsthread_once_start(&once)) {

  7. error = nl_lookup_genl_family(OVS_DATAPATH_FAMILY,

  8. &ovs_datapath_family);

  9. if (error) {

  10. VLOG_ERR("Generic Netlink family ‘%s’ does not exist. "

  11. “The Open vSwitch kernel module is probably not loaded.”,

  12. OVS_DATAPATH_FAMILY);

  13. }

  14. if (!error) {

  15. error = nl_lookup_genl_family(OVS_VPORT_FAMILY, &ovs_vport_family);

  16. }

  17. if (!error) {

  18. error = nl_lookup_genl_family(OVS_FLOW_FAMILY, &ovs_flow_family);

  19. }

  20. if (!error) {

  21. error = nl_lookup_genl_family(OVS_PACKET_FAMILY,

  22. &ovs_packet_family);

  23. }

  24. if (!error) {

  25. error = nl_lookup_genl_mcgroup(OVS_VPORT_FAMILY, OVS_VPORT_MCGROUP,

  26. &ovs_vport_mcgroup);

  27. }

  28. ovsthread_once_done(&once);

  29. }

  30. return error;

  31. }

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

0人推荐
随时随地看视频
慕课网APP