- OVS 核心代碼
- OVS 架構(gòu)
- OVS 主要的數(shù)據(jù)結(jié)構(gòu)
- datapath 模塊
- vswitchd 模塊
- 動(dòng)態(tài)過程分析
- 相關(guān)內(nèi)容
- 參考內(nèi)容
OVS 核心代碼
- datapath 目錄
- ovs-switchd
- OVS數(shù)據(jù)庫管理
- ofproto
OVS 架構(gòu)
OVS 主要的數(shù)據(jù)結(jié)構(gòu)
數(shù)據(jù)結(jié)構(gòu)關(guān)系圖
主要的數(shù)據(jù)結(jié)構(gòu)和數(shù)據(jù)結(jié)構(gòu)的參數(shù)
數(shù)據(jù)結(jié)構(gòu)代碼
vvport
/**
- struct vport - one port within a datapath
- @rcu: RCU callback head for deferred destruction.
- @dp: Datapath to which this port belongs.
- @upcall_portids: RCU protected 'struct vport_portids'.
- @port_no: Index into @dp's @ports array.
- @hash_node: Element in @dev_table hash table in vport.c.
- @dp_hash_node: Element in @datapath->ports hash table in datapath.c.
- @ops: Class structure.
- @percpu_stats: Points to per-CPU statistics used and maintained by vport
- @err_stats: Points to error statistics used and maintained by vport
*/
struct vport {
struct rcu_head rcu; // 一種鎖機(jī)制
struct datapath *dp; // 網(wǎng)橋結(jié)構(gòu)體指針又碌,表示該端口是屬于哪個(gè)網(wǎng)橋的
u32 upcall_portid; // Netlink端口收到的數(shù)據(jù)包時(shí)使用的端口id
u16 port_no; // 端口號(hào)吊圾,唯一標(biāo)識(shí)該端口
// 因?yàn)橐粋€(gè)網(wǎng)橋上有多個(gè)端口链烈,而這些端口都是用哈希鏈表來存儲(chǔ)的滩字,
// 所以這是鏈表元素(里面沒有數(shù)據(jù)爷光,只有next和prev前驅(qū)后繼指針柱徙,數(shù)據(jù)部分就是vport結(jié)構(gòu)體中的其他成員)
struct hlist_node hash_node;
struct hlist_node dp_hash_node; // 這是網(wǎng)橋的哈希鏈表元素
const struct vport_ops *ops; // 這是端口結(jié)構(gòu)體的操作函數(shù)指針結(jié)構(gòu)體,結(jié)構(gòu)體里面存放了很多操作函數(shù)的函數(shù)指針
struct pcpu_tstats __percpu *percpu_stats;// vport指向每個(gè)cpu的統(tǒng)計(jì)數(shù)據(jù)使用和維護(hù)
spinlock_t stats_lock; // 自旋鎖畜普,防止異步操作期丰,保護(hù)下面的兩個(gè)成員
struct vport_err_stats err_stats; // 錯(cuò)誤狀態(tài)(錯(cuò)誤標(biāo)識(shí))指出錯(cuò)誤vport使用和維護(hù)的統(tǒng)計(jì)數(shù)字
struct ovs_vport_stats offset_stats; // 添加到實(shí)際統(tǒng)計(jì)數(shù)據(jù),部分原因是為了兼容
};
vport_parms
/**
- struct vport_parms - parameters for creating a new vport
*
- @name: New vport's name.
- @type: New vport's type.
- @options: %OVS_VPORT_ATTR_OPTIONS attribute from Netlink message, %NULL if
- none was supplied.
- @dp: New vport's datapath.
- @port_no: New vport's port number.
*/
struct vport_parms {
const char *name; // 新端口的名字
enum ovs_vport_type type; // 新端口的類型(端口不僅僅只有一種類型,后面會(huì)分析到)
struct nlattr *options; // 這個(gè)沒怎么用到過吃挑,好像是從Netlink消息中得到的OVS_VPORT_ATTR_OPTIONS屬性
/* For ovs_vport_alloc(). */
struct datapath *dp; // 新的端口屬于哪個(gè)網(wǎng)橋的
u16 port_no; // 新端口的端口號(hào)
u32 upcall_portid; // 和Netlink通信時(shí)使用的端口id
};
vport_ops
/**
- struct vport_ops - definition of a type of virtual port
*
- @type: %OVS_VPORT_TYPE_* value for this type of virtual port.
- @create: Create a new vport configured as specified. On success returns
- a new vport allocated with ovs_vport_alloc(), otherwise an ERR_PTR() value.
- @destroy: Destroys a vport. Must call vport_free() on the vport but not
- before an RCU grace period has elapsed.
- @set_options: Modify the configuration of an existing vport. May be %NULL
- if modification is not supported.
- @get_options: Appends vport-specific attributes for the configuration of an
- existing vport to a &struct sk_buff. May be %NULL for a vport that does not
- have any configuration.
- @get_name: Get the device's name.
- @send: Send a packet on the device. Returns the length of the packet sent,
- zero for dropped packets or negative for error.
- @get_egress_tun_info: Get the egress tunnel 5-tuple and other info for
- a packet.
*/
struct vport_ops {
enum ovs_vport_type type; // 端口的類型
// 新vport端口的創(chuàng)建函數(shù)和銷毀端口的函數(shù)
struct vport *(*create)(const struct vport_parms *); // 根據(jù)指定的參數(shù)配置創(chuàng)建個(gè)新的vport咐汞,成功返回新端口指針
void (*destroy)(struct vport *); // 銷毀端口函數(shù)
// 得到和設(shè)置option成員函數(shù)
int (*set_options)(struct vport *, struct nlattr *);
int (*get_options)(const struct vport *, struct sk_buff *);
// 得到端口名稱和配置以及發(fā)送數(shù)據(jù)包函數(shù)
const char *(*get_name)(const struct vport *); //
int (*send)(struct vport *, struct sk_buff *); // 發(fā)送數(shù)據(jù)包到設(shè)備上
};
vport_ops_list
/* List of statically compiled vport implementations. Don't forget to also
- add yours to the list at the bottom of vport.h.
*/
static const struct vport_ops *vport_ops_list[] = {
&ovs_netdev_vport_ops,
&ovs_internal_vport_ops,
&ovs_geneve_vport_ops,
#if IS_ENABLED(CONFIG_NET_IPGRE_DEMUX)
&ovs_gre_vport_ops,
&ovs_gre64_vport_ops,
#endif
&ovs_vxlan_vport_ops,
&ovs_lisp_vport_ops,
};
datapath
struct datapath {
struct rcu_head rcu; // RCU調(diào)延遲破壞。
struct list_head list_node; // 網(wǎng)橋哈希鏈表元素儒鹿,里面只有next和prev前驅(qū)后繼指針化撕,數(shù)據(jù)時(shí)該結(jié)構(gòu)體其他成員
/* Flow table. */
struct flow_table __rcu *table;// 這是哈希流表,里面包含了哈希桶的地址指針约炎。該哈希表受_rcu機(jī)制保護(hù)
/* Switch ports. */
struct hlist_head *ports;// 一個(gè)網(wǎng)橋有多個(gè)端口植阴,這些端口都是用哈希鏈表來鏈接的
/* Stats. */
struct dp_stats_percpu __percpu *stats_percpu;
#ifdef CONFIG_NET_NS
/* Network namespace ref. */
struct net *net;
#endif
};
sw_flow_key
struct sw_flow_key {
// 這是隧道相關(guān)的變量
struct ovs_key_ipv4_tunnel tun_key; /* Encapsulating tunnel key. */
struct {
// 包的優(yōu)先級(jí)
u32 priority; // 包的優(yōu)先級(jí)
u32 skb_mark; // 包的mark值
u16 in_port; // 包進(jìn)入的端口號(hào)
} phy; // 這是包的物理層信息結(jié)構(gòu)體提取到的
struct {
u8 src[ETH_ALEN]; // 源mac地址
u8 dst[ETH_ALEN]; // 目的mac地址
__be16 tci; // 這好像是局域網(wǎng)組號(hào)
__be16 type; // 包的類型,即:是IP包還是ARP包
} eth; // 這是包的二層幀頭信息結(jié)構(gòu)體提取到的
struct {
u8 proto; // 協(xié)議類型 TCP:6圾浅;UDP:17掠手;ARP類型用低8位表示
u8 tos; // 服務(wù)類型
u8 ttl; // 生存時(shí)間,經(jīng)過多少跳路由
u8 frag; // 一種OVS中特有的OVS_FRAG_TYPE_*.
} ip; // 這是包的三層IP頭信息結(jié)構(gòu)體提取到的
// 下面是共用體狸捕,有IPV4和IPV6兩個(gè)結(jié)構(gòu)喷鸽,為了后期使用IPV6適應(yīng)
union {
struct {
struct {
__be32 src; // 源IP地址
__be32 dst; // 目標(biāo)IP地址
} addr; // IP中地址信息
// 這又是個(gè)共用體,有ARP包和TCP包(包含UDP)兩種
union {
struct {
__be16 src; // 源端口灸拍,應(yīng)用層發(fā)送數(shù)據(jù)的端口
__be16 dst; // 目的端口做祝,也是指應(yīng)用層傳輸數(shù)據(jù)端口
} tp; // TCP(包含UDP)地址提取
struct {
u8 sha[ETH_ALEN]; // ARP頭中源Mac地址
u8 tha[ETH_ALEN]; // ARP頭中目的Mac地址
} arp;ARP頭結(jié)構(gòu)地址提取
};
} ipv4;
// 下面是IPV6的相關(guān)信息砾省,基本和IPV4類似,這里不講
struct {
struct {
struct in6_addr src; /* IPv6 source address. */
struct in6_addr dst; /* IPv6 destination address. */
} addr;
__be32 label; /* IPv6 flow label. */
struct {
__be16 src; /* TCP/UDP source port. */
__be16 dst; /* TCP/UDP destination port. */
} tp;
struct {
struct in6_addr target; /* ND target address. */
u8 sll[ETH_ALEN]; /* ND source link layer address. */
u8 tll[ETH_ALEN]; /* ND target link layer address. */
} nd;
} ipv6;
};
};
flow_table
struct flow_table {
struct flex_array *buckets; //哈希桶地址指針
unsigned int count, n_buckets; // 哈希桶個(gè)數(shù)
struct rcu_head rcu; // rcu包含機(jī)制
struct list_head *mask_list; // struct sw_flow_mask鏈表頭指針
int node_ver;
u32 hash_seed; //哈希算法需要的種子混槐,后期匹配時(shí)要用到
bool keep_flows; //是否保留流表項(xiàng)
};
};
sw_flow
struct sw_flow {
struct rcu_head rcu; // rcu保護(hù)機(jī)制
struct hlist_node hash_node[2]; // 兩個(gè)節(jié)點(diǎn)指針编兄,用來鏈接作用,前驅(qū)后繼指針
u32 hash; // hash值
struct sw_flow_key key; // 流表中的key值
struct sw_flow_key unmasked_key; // 也是流表中的key
struct sw_flow_mask *mask; // 要匹配的mask結(jié)構(gòu)體
struct sw_flow_actions __rcu *sf_acts; // 相應(yīng)的action動(dòng)作
spinlock_t lock; // 保護(hù)機(jī)制自旋鎖
unsigned long used; // 最后使用的時(shí)間
u64 packet_count; // 匹配過的數(shù)據(jù)包數(shù)量
u64 byte_count; // 匹配字節(jié)長度
u8 tcp_flags; // TCP標(biāo)識(shí)
};
sw_flow_mask
struct sw_flow_mask {
int ref_count;
struct rcu_head rcu;
struct list_head list;// mask鏈表元素声登,因?yàn)閙ask結(jié)構(gòu)是個(gè)雙鏈表結(jié)構(gòu)體
struct sw_flow_key_range range;// 操作范圍結(jié)構(gòu)體狠鸳,因?yàn)閗ey值中有些數(shù)據(jù)時(shí)不要用來匹配的
struct sw_flow_key key;// 要和數(shù)據(jù)包操作的key,將要被用來匹配的key值
};
datapath 模塊
datapath 簡介
datapath為 ovs內(nèi)核模塊悯嗓,負(fù)責(zé)執(zhí)行數(shù)據(jù)交換件舵,也就是把從接收端口收到的數(shù)據(jù)包在流表中進(jìn)行匹配,并執(zhí)行匹配到的動(dòng)作脯厨。
一個(gè)datapath可以對(duì)應(yīng)多個(gè)vport铅祸,一個(gè)vport類似物理交換機(jī)的端口概念。一個(gè)datapth關(guān)聯(lián)一個(gè)flow table俄认,一個(gè)flow table包含多個(gè)條目,每個(gè)條目包括兩個(gè)內(nèi)容:一個(gè)match/key和一個(gè)action
datapath 代碼
- dp_init()
- ovs_dp_process_received_packet()
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, built "__DATE__" "__TIME__"\n",
VERSION);
err = ovs_flow_init();//申請(qǐng) flow_cache和 flow_stats_cache
if (err)
goto error;
err = ovs_vport_init();//vport 數(shù)據(jù)結(jié)構(gòu)初始化洪乍,申請(qǐng) dev_table
if (err)
goto error_flow_exit;
err = register_pernet_device(&ovs_net_ops);//注冊(cè)網(wǎng)絡(luò)名字空間設(shè)備
if (err)
goto error_vport_exit;
err = register_netdevice_notifier(&ovs_dp_device_notifier);//注冊(cè)設(shè)備通知事件
if (err)
goto error_netns_exit;
err = dp_register_genl();//dp_register_genl 初始化 dp 相關(guān)的 netlink 的 family和ops
if (err < 0)
goto error_unreg_notifier;
return 0;
error_unreg_notifier:
unregister_netdevice_notifier(&ovs_dp_device_notifier);
error_netns_exit:
unregister_pernet_device(&ovs_net_ops);
error_vport_exit:
ovs_vport_exit();
error_flow_exit:
ovs_flow_exit();
error:
return err;
}
vswitchd 模塊
vswitchd 代碼
set_program_name(argv)
設(shè)置程序名稱眯杏、版本、編譯日期等信息
proctitle_int(argh,argv)
復(fù)制出輸入的參數(shù)列表到新的存儲(chǔ)中壳澳,讓argv指向這塊內(nèi)存【主要是為了后面的proctitle_set()函數(shù)準(zhǔn)備】
service_start(&argc,&argv)
注冊(cè)回調(diào)和服務(wù)管理器出現(xiàn)故障錯(cuò)誤時(shí)操作的配置
remote=parse_options(argh,argv,&unixctl_path)
解析參數(shù)岂贩,其中unixctl_path存儲(chǔ)unixctrl域的sock名,作為接受外部控制命令的渠道巷波;而remote存儲(chǔ)連接到ovsdb的信息萎津,即連接到配置數(shù)據(jù)庫的sock名
ovsrec_init()
數(shù)據(jù)表結(jié)構(gòu)初始化,包括13張數(shù)據(jù)表
daemonize_start()
如果系統(tǒng)守護(hù)進(jìn)程被配置了抹镊,啟動(dòng)系統(tǒng)守護(hù)進(jìn)程锉屈,通過派生和在返回的子進(jìn)程。父進(jìn)程徘徊垮耳,直到
讓子進(jìn)程知道它完成啟動(dòng)成功(通過調(diào)用daemon_complete())颈渊,或者它沒有啟動(dòng)(用非零退出
退出代碼。
unixctl_server_create(unixctl_path,&unixctl)
創(chuàng)建一個(gè)unixctl_server(存放在unixctl)终佛,并監(jiān)聽在unixctl_path指定的punix路徑俊嗽,該路徑作為ovs-appctl發(fā)送命令給ovsd的通道
unixctl_command_register
注冊(cè)u(píng)nixctl命令
bridge_init
從remote數(shù)據(jù)庫獲取配置信息,并初始化bridge
主循環(huán)
memory_run()
運(yùn)行內(nèi)存監(jiān)視器铃彰,客戶端調(diào)用memory_should_report()绍豁。此函數(shù)以及該模塊的接口的剩余部分乌逐,僅被一個(gè)線程調(diào)用疆虚。
bridge_run
主要對(duì)網(wǎng)包進(jìn)行完整處理過程幕屹。包括完成必要的配置更新【在配置更新中會(huì)從數(shù)據(jù)庫中讀取配置信息痕鳍,生成必要的bridge和dp等數(shù)據(jù)結(jié)構(gòu)】
ovsdb_idl_run(idl);
處理了一批從'IDL'數(shù)據(jù)庫服務(wù)器的消息。這可能會(huì)導(dǎo)致IDL的內(nèi)容發(fā)生變化鬼佣∈还埃客戶端可以檢查與ovsdb_idl_get_seqno()。
system_stats_enable(false);
因?yàn)槲覀儾贿\(yùn)行system_stats的run()在這個(gè)進(jìn)程中有多個(gè)OVS-vswitchd守護(hù)進(jìn)程的現(xiàn)狀晶衷,關(guān)閉系統(tǒng)自動(dòng)統(tǒng)計(jì)信息收集蓝纲。
bridge_init_ofproto(cfg)
初始化ofproto庫。這僅需要執(zhí)行一次晌纫,但配置設(shè)置之后它必須要做的税迷。如果已經(jīng)出現(xiàn)了初始化,bridge_init_ofproto()立即返回锹漱。
bridge_run__(void)
#######ofproto_run中的p->ofproto_class->run(p)上的run函數(shù)依次調(diào)用函數(shù)
- 必選調(diào)用dpif_run()處理所有注冊(cè)的netlink notifier的匯報(bào)事件
- 必選調(diào)用run_fast()處理常見的周期事件箭养,包括對(duì)upcalls的處理等
- 可選調(diào)用netflow_run()和sflow_run(),進(jìn)行對(duì)netflow和sflow的支持
可選調(diào)用較多哥牍,自行查看
#######connmr_run函數(shù)處理與控制器的周期性交互
- 首先檢查是否存在in_band的控制器
- 調(diào)用ofconn_run()處理對(duì)ofproto的協(xié)議解析和行動(dòng)
- rconn_run(ofconn->rconn)負(fù)責(zé)連接到controller
- rconn_recv(ofconn->rconn)負(fù)責(zé)從controller收取消息
- handle_openflow()最終調(diào)用handle_openflow__()(ofproto/ofproto.c)來完成對(duì)各個(gè)Of消息的處理
以 PACKET_OUT消息為例毕泌,調(diào)用的是handle_packout 函數(shù)
首先調(diào)用ofputil_decode_packet_out()對(duì)of消息進(jìn)行解析
調(diào)用ofconn_pktbuf_retrieve()獲取payload信息
利用ofproto_class->packet_out()將網(wǎng)包發(fā)出
packet_out()
{
ofproto_dpif_execute_actions()
{
dpif_flow_stats_extract() 流狀態(tài)提取
xlate_actions()將ofpacts轉(zhuǎn)化為dp的行動(dòng)格式odp_actions
調(diào)用dpif_execute()函數(shù)讓dpif執(zhí)行給定的action構(gòu)建OVS_PACKET_CMD_EXECUTE netlink消息并發(fā)給datapath
datapath中將對(duì)應(yīng)調(diào)用ovs_packet_cmd_execute函數(shù)處理收到的nlmsg
ovs_packet_cmd_execute的調(diào)用過程
ovs_packet_cmd_execute()->ovs_execute_actions()->do_execute_actions()
}
}
重新配置SSL
通過主循環(huán)每一次遍歷,而不是只當(dāng)數(shù)據(jù)庫的變化嗅辣,因?yàn)槊荑€和證書文件的內(nèi)容可以更改在數(shù)據(jù)庫不更改中撼泛。我們完成這些在bridge_reconfigure()之前,因?yàn)樵摴δ芸赡軙?huì)啟動(dòng)SSL連接之前做到這一點(diǎn)澡谭,因此需要SSL進(jìn)行配置愿题。
對(duì)all_bidge上的每個(gè)bridge的ofproto執(zhí)行ofproto_run()
netdev_run()
如果打開了一些netted,則執(zhí)行對(duì)應(yīng)在netdev_classes上定義的每個(gè)netdev_class實(shí)體蛙奖,調(diào)用它們的run()包括處理網(wǎng)卡注冊(cè)的各個(gè)通知事件潘酗,獲取網(wǎng)卡的最新的信息等
unixctl_server_run(unixctl)
從unixctl指定的server中獲取來自ovs-appctl發(fā)出的命令數(shù)據(jù),并執(zhí)行對(duì)應(yīng)的命令
循環(huán)等待事件處理
包括memory雁仲、bridge仔夺、unixctl_server、netted等事件攒砖,被poll_fd_wait()注冊(cè)的最短時(shí)間
poll_block(void)
阻塞知道之前被poll_fd_wait()注冊(cè)過的事件發(fā)生囚灼,或者等待時(shí)間超過poll_timer_wait()注冊(cè)的最短時(shí)間
清理工作
退出bridge,關(guān)閉unixctl連接
動(dòng)態(tài)過程分析
數(shù)據(jù)流流向
一般的數(shù)據(jù)包在 Linux網(wǎng)絡(luò)協(xié)議中的流向?yàn)楹谏^流向:網(wǎng)卡收到數(shù)據(jù)包后層層網(wǎng)上分析祭衩,最后離開內(nèi)核態(tài)灶体,把數(shù)據(jù)傳送到用戶態(tài)。
有 OVS時(shí):數(shù)據(jù)流流向不同
(1)創(chuàng)網(wǎng)橋(ovs-vsctl add-br br0)
(2)綁網(wǎng)卡(ovs-vsctl add-port bro eth0 默認(rèn)為 eth0)
數(shù)據(jù)流:
從網(wǎng)卡 eth0到 ovs 的 vport 進(jìn)入OVS掐暮,根據(jù) key值流表匹配
成功——>執(zhí)行流表 action
失敗——>upcall處理
添加網(wǎng)橋
1. 鍵入命令ovs-vsctl add-br testBR
2. 內(nèi)核中的 openvswitch.ko 收到一個(gè)添加網(wǎng)橋的命令時(shí)候——即收到 OVS_DATAPATH_FAMILY通道的 OVS_DP_CMD_NEW命令蝎抽。該命令綁定的回調(diào)函數(shù)為 ovs_dp_cmd_new
3. ovs_dp_cmd_new 函數(shù)除了初始化 dp 結(jié)構(gòu)外,調(diào)用 new_vport 函數(shù)來生成新的 vport
4. new_vport 函數(shù)調(diào)用 ovs_vport_add()來嘗試生成一個(gè)新的 vport
5. ovs_vport_add()函數(shù)會(huì)檢查 vport 類型(通過 vport_ops_list[]數(shù)組),并調(diào)用相關(guān)的 create()函數(shù)來生成 vport 結(jié)構(gòu)
6. 當(dāng)dp是網(wǎng)絡(luò)設(shè)備時(shí)(vport_netdev.c)樟结,最終由 ovs_vport_add()函數(shù)調(diào)用的是 netdev_create()【在 vport_ops_list的ovs_netdev_ops 中】
7. netdev_create()函數(shù)最關(guān)鍵的一步是注冊(cè)了收到網(wǎng)包時(shí)的回調(diào)函數(shù)
8. err=netdev_rx_handler_register(netdev_vport->dev,netdev_frame_hook,vport);
9. 操作是將 netdev_vport->dev 收到網(wǎng)包時(shí)的相關(guān)數(shù)據(jù)由 netdev_frame_hook()函數(shù)來處理养交,都是些輔助處理,依次調(diào)用各處理函數(shù)瓢宦,在 netdev_port_receive()【這里會(huì)進(jìn)行數(shù)據(jù)包的拷貝碎连,避免損壞】進(jìn)入 ovs_vport_receive()回到 vport.c,從 ovs_dp_process_receive_packet()回到 datapath.c驮履,進(jìn)行統(tǒng)一處理
10. 流程:netdev_frame_hook()->netdev_port_receive->ovs_vport_receive->ovs_dp_process_received_packet()
11. net_port_receive()首先檢測是否 skb 被共享鱼辙,若是則得到 packet 的拷貝。
12. net_port_receive()其調(diào)用ovs_vport_receive()玫镐,檢查包的校驗(yàn)和倒戏,然后交付給我們的vport通用層來處理。
netdev_rx_handler_register()
linux 內(nèi)核實(shí)現(xiàn)的一個(gè)函數(shù)恐似,為網(wǎng)絡(luò)設(shè)備 dev 注冊(cè)一個(gè)handler_frame_hook,rx_handle_data 指向的是handler_frame_hook 內(nèi)存的區(qū)域杜跷,這個(gè) handler 以后會(huì)被__netif_receive_skb()呼叫,就是說netdev_rx_handler_register(netdev_vport->dev,netdev_frame_hook,vport);在收到packet 后會(huì)調(diào)用 netdev_frame_hook 函數(shù)處理
收包處理
1.ovs_vport_receive_packets()調(diào)用ovs_flow_extract基于skb生成key值矫夷,并檢查是否有錯(cuò),然后調(diào)用ovs_dp_process_packet葛闷。交付給datapath處理
2.ovs_flow_tbl_lookup_stats∷海基于前面生成的key值進(jìn)行流表查找淑趾,返回匹配的流表項(xiàng),結(jié)構(gòu)為sw_flow蔓彩。
3.若不存在匹配治笨,則調(diào)用ovs_dp_upcall上傳至userspace進(jìn)行匹配驳概。 (包括包和key都要上傳)
若存在匹配赤嚼,則直接調(diào)用ovs_execute_actions執(zhí)行對(duì)應(yīng)的action,比如添加vlan頭顺又,轉(zhuǎn)發(fā)到某個(gè)port等更卒。
流表匹配
1. flow_lookup()查找對(duì)應(yīng)的流表項(xiàng)
2. for 循環(huán)調(diào)用 rcu_dereference_ovs 對(duì)流表結(jié)構(gòu)體中的 mask_list 成員遍歷,找到對(duì)應(yīng)的的 成員
3. flow=masked_flow_lookup()遍歷進(jìn)行下一級(jí) hmap查找稚照,找到為止
4. 進(jìn)入 包含函數(shù) ovs_flow_mask_key(&masked_key,unmasked,mask)蹂空,將最開始提取的 Key 值和 mask 的 key 值進(jìn)行“與”操作,結(jié)果存放在 masked_key 中果录,用來得到后面的 Hash 值
5. hash=flow_hash(&masked_key,key_start,key_end)key 值的匹配字段只有部分
6. ovs_vport_add()函數(shù)會(huì)檢查 vport 類型(通過 vport_ops_list[]數(shù)組)上枕,并調(diào)用相關(guān)的 create()函數(shù)來生成 vport 結(jié)構(gòu)
7. 可見,當(dāng) dp 時(shí)網(wǎng)絡(luò)設(shè)備時(shí)(vport_netdev.c)弱恒,最終由 ovs_vport_add()函數(shù)調(diào)用的是 netdev_create()【在 vport_ops_list的ovs_netdev_ops 中】
8. netdev_vport->dev 收到網(wǎng)包時(shí)的相關(guān)數(shù)據(jù)由 netdev_frame_hook()函數(shù)來處理辨萍,都是些輔助處理,依次調(diào)用各處理函數(shù)返弹,在 netdev_port_receive()【這里會(huì)進(jìn)行數(shù)據(jù)包的拷貝锈玉,避免損壞】進(jìn)入 ovs_vport_receive()回到 vport.c爪飘,從 ovs_dp_process_receive_packet()回到 datapath.c,進(jìn)行統(tǒng)一處理
upcall 消息處理
1. ovs_dp_upcall()首先調(diào)用 err=queue_userspace_packet()將信息排隊(duì)發(fā)到用戶空間去
2. dp_ifindex=get_dpifindex(dp)獲取網(wǎng)卡設(shè)備索引號(hào)
3. 調(diào)整 VLAN的 MAC 地址頭指針
4. 網(wǎng)絡(luò)鏈路屬性拉背,如果不需要填充則調(diào)用此函數(shù)
5. len=upcall_msg_size()师崎,獲得 upcall 發(fā)送消息的大小
6. user_skb=genlmsg_new_unicast,創(chuàng)建一個(gè)新的 netlink 消息
7. upcall=genlmsg_put()增加一個(gè)新的 netlink 消息到 skb
8. err=genlmsg_unicast(),發(fā)送消息到用戶空間去處理
相關(guān)內(nèi)容
Linux RCU鎖機(jī)制分析
RCU是linux的新型鎖機(jī)制(RCU是在linux 2.6內(nèi)核版本中開始正式使用)
傳統(tǒng)讀寫鎖rwlock運(yùn)行機(jī)制
讀鎖(共享鎖):若請(qǐng)求是讀數(shù)據(jù)時(shí),上讀鎖椅棺,多個(gè)讀鎖不排斥(即訪問數(shù)據(jù)的讀者上限未達(dá)到時(shí)犁罩,可以對(duì)數(shù)據(jù)區(qū)再上讀鎖),若請(qǐng)求是寫數(shù)據(jù)時(shí),不能馬上上寫鎖,得等數(shù)據(jù)區(qū)的所有鎖(包括讀鎖和寫鎖)都釋放才能上寫鎖
寫鎖(獨(dú)占鎖):要操作的數(shù)據(jù)區(qū)上了寫鎖,不管什么請(qǐng)求都要等到數(shù)據(jù)區(qū)的寫鎖釋放掉后才能上鎖訪問
RCU 鎖機(jī)制——RCU(read\copy\update)對(duì)數(shù)據(jù)的讀土陪、復(fù)制昼汗、修改的保護(hù)鎖機(jī)制
寫數(shù)據(jù):(1)不需讀寫鎖那樣等待所有鎖釋放【拷貝一份數(shù)據(jù)區(qū)的副本,在副本中修改鬼雀,修改完后顷窒,用副本替代原來的數(shù)據(jù)區(qū)】(2)替換的時(shí)候需要讀寫鎖上寫鎖那樣,等到數(shù)據(jù)區(qū)上所有訪問者退出后源哩,才進(jìn)行數(shù)據(jù)的替換
(3)RCU鎖可以有多個(gè)寫者鞋吉,拷貝多份數(shù)據(jù)區(qū)數(shù)據(jù),修改后励烦,各個(gè)數(shù)據(jù)區(qū)陸續(xù)替換掉原數(shù)據(jù)區(qū)內(nèi)容
讀數(shù)據(jù):不用上任何鎖谓着,幾乎不需要等待(讀寫鎖需要等寫鎖釋放)就可以直接訪問數(shù)據(jù)
,“幾乎”,因?yàn)閷憯?shù)據(jù)中替換原數(shù)據(jù)坛掠,只需修改個(gè)指針赊锚,消耗的時(shí)間幾乎不算
RCU 鎖機(jī)制特性
? 允許多個(gè)讀者和多個(gè)寫者同時(shí)訪問共享數(shù)據(jù)區(qū)內(nèi)容
? 對(duì)多讀少寫的數(shù)據(jù)來說非常高效,可以減少 CPU 開銷
? 寫數(shù)據(jù)操作多了屉栓,就不如讀寫鎖那么好了舷蒲,因?yàn)镽CU 對(duì)寫數(shù)據(jù)開銷大,需要拷貝數(shù)據(jù)友多,修改牲平,等待替換
RCU 機(jī)制 API
rcu_read_lock();
? 這不是和上讀寫鎖的那種上鎖域滥,這僅僅只是標(biāo)識(shí)了臨界區(qū)的開始位置纵柿。表明在臨界區(qū)內(nèi)不能阻塞和休眠,也不能讓寫者進(jìn)行數(shù)據(jù)的替換(其實(shí)這功能遠(yuǎn)不止這些)启绰。rcu _read_unlock()則是和上面rcu_read_lock()對(duì)應(yīng)的昂儒,用來界定一個(gè)臨界區(qū)(就是要用鎖保護(hù)起來的數(shù)據(jù)區(qū))。
synchronize_rcu()委可;
? 當(dāng)該函數(shù)被一個(gè)CPU調(diào)用時(shí)(一般是有寫者替換數(shù)據(jù)時(shí)調(diào)用)渊跋,而其他的CPU都在RCU保護(hù)的臨界區(qū)讀數(shù)據(jù),那么synchronize_rcu()將會(huì)保證阻塞寫者,直到所有其它讀數(shù)據(jù)的CPU都退出臨界區(qū)時(shí)刹枉,才中止阻塞叽唱,讓寫著開始替換數(shù)據(jù)。該函數(shù)作用就是保證在替換數(shù)據(jù)前微宝,所有讀數(shù)據(jù)的CPU能夠安全的退出臨界區(qū)棺亭。同樣,還有個(gè)call_rcu()函數(shù)功能也是類似的蟋软。如果call_rcu()被一個(gè)CPU調(diào)用镶摘,而其他的CPU都在RCU保護(hù)的臨界區(qū)內(nèi)讀數(shù)據(jù),相應(yīng)的RCU回調(diào)的調(diào)用將被推遲到其他讀臨界區(qū)數(shù)據(jù)的CPU全部安全退出后才執(zhí)行(可以看linux內(nèi)核源文件的注釋岳守,在Rcupdate.h文件中rcu_read_look()函數(shù)前面的注釋)凄敢。
rcu_dereference();
? 獲取在一個(gè)RCU保護(hù)的指針湿痢,指向RCU讀端臨界區(qū)涝缝。他的指針以后可能會(huì)被安全地解除引用。說到底就是一個(gè)RCU保護(hù)指針譬重。
list_add_rcu()拒逮;
? 往RCU保護(hù)的數(shù)據(jù)結(jié)構(gòu)中添加一個(gè)數(shù)據(jù)節(jié)點(diǎn)進(jìn)去。這個(gè)和一般的往鏈表中增加一個(gè)節(jié)點(diǎn)操作是類似的臀规,唯一不同的是多了這條代碼:rcu_assign_pointer(prev->next, new); 代碼大概含義:分配指向一個(gè)新初始化的結(jié)構(gòu)指針滩援,將由RCU讀端臨界區(qū)被解除引用,返回指定的值塔嬉。
list_for_each_entry_rcu()玩徊;
? 這是個(gè)遍歷RCU鏈表的操作,和一般的鏈表遍歷差不多谨究。不同點(diǎn)就是必須要進(jìn)入RCU保護(hù)的CPU(即:調(diào)用了rcu_read_lock()函數(shù)的CPU)才能調(diào)用這個(gè)操作恩袱,可以和其他CPU共同遍歷這個(gè)RCU鏈表。
Generic Netlink 通信機(jī)制
+---------------------+ +---------------------+
| (3) application "A" | | (3) application "B" |
+------+--------------+ +--------------+------+
| |
\ /
\ /
| |
+-------+--------------------------------+-------+
| : : | user-space
=====+ : (5) Kernel socket API : +================
| : : | kernel-space
+--------+-------------------------------+-------+
| |
+-----+-------------------------------+----+
| (1) Netlink subsystem |
+---------------------+--------------------+
|
+---------------------+--------------------+
| (2) Generic Netlink bus |
+--+--------------------------+-------+----+
| | |
+-------+---------+ | |
| (4) Controller | / \
+-----------------+ / \
| |
+------------------+--+ +--+------------------+
| (3) kernel user "X" | | (3) kernel user "Y" |
+---------------------+ +---------------------+
(5)API向用戶空間和內(nèi)核空間分別提供接口记盒。
Netlink子系統(tǒng)(1)是所有g(shù)enl通信的基礎(chǔ)憎蛤。Netlink子系統(tǒng)中收到的所有Generic類型的netlink數(shù)據(jù)都被送到genl總線(2)上外傅;從內(nèi)核發(fā)出的數(shù)據(jù)也經(jīng)由genl總線送至netlink子系統(tǒng)纪吮,再打包送至用戶空間。
Generic Netlink控制器(4)作為內(nèi)核的一部分萎胰,負(fù)責(zé)動(dòng)態(tài)地分配genl通道(即genl family id)碾盟,并管理genl任務(wù)。genl控制器是一個(gè)特殊的genl內(nèi)核用戶技竟,它負(fù)責(zé)監(jiān)聽genl bus上的通信通道冰肴。genl通信建立在一系列的通信通道的基礎(chǔ)上,每個(gè)genl family對(duì)應(yīng)多個(gè)通道,這些通道由genl控制器動(dòng)態(tài)分配熙尉。
Generic Netlink相關(guān)結(jié)構(gòu)體
genl family
Generic Netlink是基于客戶端-服務(wù)端模型的通信機(jī)制联逻。服務(wù)端注冊(cè)family(family是對(duì)genl服務(wù)的各項(xiàng)定義的集合)〖焯担控制器和客戶端都通過已注冊(cè)的信息與服務(wù)端通信包归。
genl family的結(jié)構(gòu)體如下:
struct genl_family
{
unsigned int id;
unsigned int hdrsize;
char name[GENL_NAMSIZ];
unsigned int version;
unsigned int maxattr;
struct nlattr ** attrbuf;
struct list_head ops_list;
struct list_head family_list;
};
- id: family id。當(dāng)新注冊(cè)一個(gè)family的時(shí)候铅歼,應(yīng)該用GENL_ID_GENERATE宏(0x0)公壤,表示請(qǐng)控制器自動(dòng)為family分配的一個(gè)id。0x10保留供genl控制器使用椎椰。
- hdrsize: 用戶自定議頭部長度厦幅。即圖2中User Msg的長度。如果沒有用戶自定義頭部慨飘,這個(gè)值被賦為0确憨。
- version: 版本號(hào),一般填1即可瓤的。
- name: family名缚态,要求不同的family使用不同的名字。以便控制器進(jìn)行正確的查找堤瘤。
- maxattr:genl使用netlink標(biāo)準(zhǔn)的attr來傳輸數(shù)據(jù)玫芦。此字段定義了最大attr類型數(shù)。(注意:不是一次傳輸多少個(gè)attr本辐,而是一共有多少種attr桥帆,因此,這個(gè)值可以被設(shè)為0慎皱,為0代表不區(qū)分所收到的數(shù)據(jù)的attr type)老虫。在接收數(shù)據(jù)時(shí),可以根據(jù)attr type茫多,獲得指定的attr type的數(shù)據(jù)在整體數(shù)據(jù)中的位置祈匙。
- struct nlattr **attrbuf
- struct list_head ops_list
- struct list_head family_list
以上的三個(gè)字段為私有字段,由系統(tǒng)自動(dòng)配置天揖,開發(fā)者不需要做配置夺欲。
genl 報(bào)文格式
genl_ops 結(jié)構(gòu)體
struct genl_ops
{
u8 cmd;
unsigned int flags;
struct nla_policy *policy;
int (*doit)(struct sk_buff *skb,
struct genl_info *info);
int (*dumpit)(struct sk_buff *skb,
struct netlink_callback *cb);
struct list_head ops_list;
};
cmd: 命令名。用于識(shí)別各genl_ops
flag: 各種設(shè)置屬性今膊,以“或”連接些阅。在需要admin特權(quán)級(jí)別時(shí),使用GENL_ADMIN_PERM
policy:定義了attr規(guī)則斑唬。如果此指針非空市埋,genl在觸發(fā)事件處理程序之前黎泣,會(huì)使用這個(gè)字段來對(duì)幀中的attr做校驗(yàn)(見nlmsg_parse函數(shù))。該字段可以為空缤谎,表示在觸發(fā)事件處理程序之前抒倚,不做校驗(yàn)。
doit:這是一個(gè)回調(diào)函數(shù)坷澡。在generic netlink收到數(shù)據(jù)時(shí)觸發(fā)衡便,運(yùn)行在進(jìn)程上下文。
doit傳入兩個(gè)參數(shù)洋访,skb為觸發(fā)此回調(diào)函數(shù)的socket buffer镣陕。第二個(gè)參數(shù)是一個(gè)genl_info結(jié)構(gòu)體
struct genl_info
{
u32 snd_seq;
u32 snd_pid;
struct nlmsghdr * nlhdr;
struct genlmsghdr * genlhdr;
void * userhdr;
struct nlattr ** attrs;
};
- snd_seq:發(fā)送序號(hào)
- snd_pid:發(fā)送客戶端的PID
- nlhdr:netlink header的指針
- genlmsghdr:genl頭部的指針(即family頭部)
- userhdr:用戶自定義頭部指針
- attrs:attrs,如果定義了genl_ops->policy姻政,這里的attrs是被policy過濾以后的結(jié)果呆抑。在完成了操作以后,如果執(zhí)行正確汁展,返回0鹊碍;否則,返回一個(gè)負(fù)數(shù)食绿。負(fù)數(shù)的返回值會(huì)觸發(fā)NLMSG_ERROR消息侈咕。當(dāng)genl_ops的flag標(biāo)志被添加了NLMSG_ERROR時(shí),即使doit返回0器紧,也會(huì)觸發(fā)NLMSG_ERROR消息耀销。
dumpit
這是一個(gè)回調(diào)函數(shù),當(dāng)genl_ops的flag標(biāo)志被添加了NLM_F_DUMP以后铲汪,每次收到genl消息即會(huì)回觸發(fā)這個(gè)函數(shù)熊尉。dumpit與doit的區(qū)別是:dumpit的第一個(gè)參數(shù)skb不會(huì)攜帶從客戶端發(fā)來的數(shù)據(jù)。相反地掌腰,開發(fā)者應(yīng)該在skb中填入需要傳給客戶端的數(shù)據(jù)狰住,然后,并skb的數(shù)據(jù)長度(可以用skb->len)return齿梁。skb中攜帶的數(shù)據(jù)會(huì)被自動(dòng)送到客戶端催植。只要dumpit的返回值大于0,dumpit函數(shù)就會(huì)再次被調(diào)用勺择,并被要求在skb中填入數(shù)據(jù)创南。當(dāng)服務(wù)端沒有數(shù)據(jù)要傳給客戶端時(shí),dumpit要返回0酵幕。如果函數(shù)中出錯(cuò)扰藕,要求返回一個(gè)負(fù)值缓苛。關(guān)于doit和dumpit的觸發(fā)過程芳撒,可以查看源碼中的genl_rcv_msg函數(shù)邓深。
ops_list
為私有字段,由系統(tǒng)自動(dòng)配置笔刹,開發(fā)者不需要做配置芥备。
Generic Netlink 服務(wù)端(內(nèi)核)初始化
初始化Generic Netlink的過程分為以下四步:定義family,定義operation舌菜,注冊(cè)family萌壳,注冊(cè)operation。
Datapath使用 generic netlink
在 dp_init()函數(shù)(datapath.c)中,調(diào)用 dp_register_genl()完成對(duì)四種類型的 family 以 及相應(yīng)操作的注冊(cè),包括 datapath日月、vport袱瓮、flow 和 packet。前三種 family,都對(duì)應(yīng)四種操 作都包括 NEW爱咬、DEL尺借、GET、SET,而 packet 的操作僅為 EXECUTE精拟。
這些 family 和操作的定義均在 datapath.c 中燎斩。 以 flow family 為例。代碼為
static const struct nla_policy flow_policy[OVS_FLOW_ATTR_MAX + 1] = {
[OVS_FLOW_ATTR_KEY] = { .type = NLA_NESTED },
[OVS_FLOW_ATTR_ACTIONS] = { .type = NLA_NESTED },
[OVS_FLOW_ATTR_CLEAR] = { .type = NLA_FLAG },
};
static struct genl_family dp_flow_genl_family = {
.id = GENL_ID_GENERATE,
.hdrsize = sizeof(struct ovs_header),
.name = OVS_FLOW_FAMILY,
.version = OVS_FLOW_VERSION,
.maxattr = OVS_FLOW_ATTR_MAX, SET_NETNSOK
};
綁定的 ops 的定義
static struct genl_ops dp_flow_genl_ops[] = {
{ .cmd = OVS_FLOW_CMD_NEW,
.flags = GENL_ADMIN_PERM, /* Requires CAP_NET_ADMIN privilege. */
.policy = flow_policy,
.doit = ovs_flow_cmd_new_or_set
},
{ .cmd = OVS_FLOW_CMD_DEL,
.flags = GENL_ADMIN_PERM, /* Requires CAP_NET_ADMIN privilege. */
.policy = flow_policy,
.doit = ovs_flow_cmd_del
},
{ .cmd = OVS_FLOW_CMD_GET,
.flags = 0, /* OK for unprivileged users. */
.policy = flow_policy,
.doit = ovs_flow_cmd_get,
.dumpit = ovs_flow_cmd_dump
},
{ .cmd = OVS_FLOW_CMD_SET,
.flags = GENL_ADMIN_PERM, /* Requires CAP_NET_ADMIN privilege. */
.policy = flow_policy,
.doit = ovs_flow_cmd_new_or_set,
},
};
ovsd 使用 netlink
ovsd 對(duì)于 netlink 的實(shí)現(xiàn),主要在 lib/netlink-socket.c 文件中蜂绎。而對(duì)這些 netlink 操作的 調(diào)用,主要在 lib/dpif-linux.c 文件(以 dpif_linux_class 為例)中對(duì)于各個(gè)行為的處理,各 種可能的消息類型在 datapath 模塊中事先進(jìn)行了內(nèi)核注冊(cè)栅表。
datapath 中對(duì) netlink family 類型進(jìn)行了注冊(cè),ovsd 在使用這些 netlink family 之前需要 獲取它們的信息,這一過程主要在 lib/dpif-linux.c 文件(以 dpif_linux_class 為例), dpif_linux_init()函數(shù)。代碼為
static int dpif_linux_init(void)
{
static int error = -1;
if (error < 0) {
unsigned int ovs_vport_mcgroup;
error = nl_lookup_genl_family(OVS_DATAPATH_FAMILY,&ovs_datapath_family);
if (error) {
VLOG_ERR("Generic Netlink family '%s' does not exist. ""The Open vSwitch kernel module is probably not loaded.",OVS_DATAPATH_FAMILY); }
if (!error) {
error = nl_lookup_genl_family(OVS_VPORT_FAMILY, &ovs_vport_family);}
if (!error) {
error = nl_lookup_genl_family(OVS_FLOW_FAMILY, &ovs_flow_family); }
if (!error) {
error = nl_lookup_genl_family(OVS_PACKET_FAMILY,&ovs_packet_family);}
if (!error) {
error = nl_sock_create(NETLINK_GENERIC, &genl_sock); }
if (!error) {
error = nl_lookup_genl_mcgroup(OVS_VPORT_FAMILY, OVS_VPORT_MCGROUP,&ovs_vport_mcgroup, OVS_VPORT_MCGROUP_FALLBACK_ID);}
if (!error) {
static struct dpif_linux_vport vport;
nln = nln_create(NETLINK_GENERIC, ovs_vport_mcgroup,
dpif_linux_nln_parse, &vport);}
}
return error;
}
完成這些查找后,ovsd 即可利用 dpif 中的 api,通過發(fā)出這些 netlink 消息給 datapath 實(shí)現(xiàn)對(duì) datapath 的操作师枣。
相關(guān)的中間層 API 定義主要在 dpif_class(位于 lib/dpif-provider.h)的抽象類型中
dpif_class結(jié)構(gòu)體的注釋:
/* Datapath interface class structure, to be defined by each implementation of
-a datapath interface.
*
- These functions return 0 if successful or a positive errno value on failure,
- except where otherwise noted.
*
- These functions are expected to execute synchronously, that is, to block as
- necessary to obtain a result. Thus, they may not return EAGAIN or
- EWOULDBLOCK or EINPROGRESS. We may relax this requirement in the future if
- and when we encounter performance problems. */
一共有兩種dpif_class實(shí)例化類型怪瓶,分別為dpif_netlink_class和dpif_netdev_class。dpif_netlink_class表示的是通過netlink和本地的datapath通信践美,而dpif_netdev_class通過網(wǎng)絡(luò)協(xié)議和遠(yuǎn)程的datapath通信
ovsd使用netlink進(jìn)行消息發(fā)送的過程:
參考內(nèi)容
庾志輝OVS 專欄
http://blog.csdn.net/column/details/openvswitch.html
datapath 模塊分析
http://vinllen.com/ovs-datapathbi-ji/
Baohua Yang的OpenvSwitch 代碼分析
OpenvSwitch2.4.0源碼解讀
http://www.cnblogs.com/cotyb/p/5103035.html?utm_source=tuicool&utm_medium=referral
GenerRic Netlink 詳解
http://www.tuicool.com/articles/jE7nim