一、工作流程(datapath數(shù)據(jù)路徑)

ovs數(shù)據(jù)包處理流程圖.png
- 一般的數(shù)據(jù)包在linux網(wǎng)絡(luò)協(xié)議棧中的流向為黑色箭頭流向:從網(wǎng)卡eth0上接收到數(shù)據(jù)包后層層往上分析,最后離開內(nèi)核態(tài),把數(shù)據(jù)傳送到用戶態(tài)。當(dāng)然也有些數(shù)據(jù)包只是在內(nèi)核網(wǎng)絡(luò)協(xié)議棧中操作,然后再從某個網(wǎng)卡發(fā)出去。
- 【以下代碼分析基于Open vSwitch 2.5.0版本】
- 但當(dāng)其中有Open Vswitch時,數(shù)據(jù)包的流向就不一樣了。首先是創(chuàng)建一個網(wǎng)橋:
ovs-vsctl add-br br0——底層實現(xiàn)原理是調(diào)用datapath/datapath.c中的ovs_dp_cmd_new函數(shù)和new_vport函數(shù)去初始化一個datapath對象和vport對象。
然后是綁定某個網(wǎng)卡:綁定網(wǎng)卡:ovs-vsctl add-port br0 eth0;這里綁定了eth0網(wǎng)卡。 - linux內(nèi)核通過netif_receive_skb()函數(shù)從網(wǎng)卡eth0收到的一個數(shù)據(jù)包
struct sk_buff *skb的流向是從網(wǎng)卡eth0上轉(zhuǎn)到Open Vswitch的端口vport上進入Open Vswitch中,首先是調(diào)用的datapath/vport-netdev.c中的struct sk_buff *netdev_frame_hook(struct sk_buff *skb)函數(shù)并再調(diào)用void netdev_port_receive(struct sk_buff *skb, struct ip_tunnel_info *tun_info)函數(shù),對數(shù)據(jù)包做一些檢查, - 然后調(diào)用
datapath/vport.c中的int ovs_vport_receive(struct vport *vport, struct sk_buff *skb, const struct ip_tunnel_info *tun_info)函數(shù),調(diào)用ovs_flow_extract函數(shù)基于skb生成key值,并檢查是否有錯。 - 然后轉(zhuǎn)到調(diào)用
datapath/datapath.c中的void ovs_dp_process_packet(struct sk_buff *skb, struct sw_flow_key *key)函數(shù)來處理數(shù)據(jù)包是根據(jù)key值調(diào)用ovs_flow_tbl_lookup_stats函數(shù)進行流表的匹配。如果匹配成功,則根據(jù)流表中對應(yīng)的action找到其對應(yīng)的操作方法,調(diào)用ovs_execute_actions執(zhí)行對應(yīng)的action完成相應(yīng)的動作(這個動作有可能是把數(shù)據(jù)包從其他端口發(fā)出去,也有可能是直接丟棄,也可以自己設(shè)計action);如果匹配不成功,則調(diào)用ovs_dp_upcall上傳至用戶空間的vswitchd守護進程進行處理,該進程又和sdn控制器直接進行通信。
數(shù)據(jù)包匹配流表走向
*【具體函數(shù)定義如下】
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;
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;
int error;
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); //轉(zhuǎn)到上層用戶空間處理
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); //獲取流表動作集
ovs_execute_actions(dp, skb, sf_acts, key); //執(zhí)行流表的動作
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);
}
二、數(shù)據(jù)包struct sk_buff *skb的內(nèi)容
- skb數(shù)據(jù)包是由linux內(nèi)核從網(wǎng)卡接收,該結(jié)構(gòu)體也由linux內(nèi)核定義,結(jié)構(gòu)體中包含的信息很豐富,若是在上一節(jié)列出的某個處理接收數(shù)據(jù)包的函數(shù)中對數(shù)據(jù)包進行解析統(tǒng)計一條流(五元組匹配確定)的信息,可用于構(gòu)建用于分類識別的流量數(shù)據(jù)集。
struct sk_buff {
/* These two members must be first. */
struct sk_buff *next; //因為sk_buff結(jié)構(gòu)體是雙向鏈表,所以有前驅(qū)后繼。
struct sk_buff *prev; //這是指向前一個sk_buff結(jié)構(gòu)體指針
//2.6版本以前應(yīng)該還有個字段:sk_buff_head *list 即每個sk_buff結(jié)構(gòu)都有個指針指向頭節(jié)點
struct sock *sk; // 指向擁有此緩沖的套接字sock結(jié)構(gòu)體
ktime_t tstamp; // 時間戳,表示這個skb的接收到的時間,
//一般是在包從驅(qū)動中往二層發(fā)送的接口函數(shù)中設(shè)置
struct net_device *dev; // 表示一個網(wǎng)絡(luò)設(shè)備
unsigned long _skb_dst; // 主要用于路由子系統(tǒng),保存路由有關(guān)的東西
char cb[48]; // 保存每層的控制信息,每一層的私有信息
unsigned int len, // 表示數(shù)據(jù)區(qū)的長度(tail - data)與分片結(jié)構(gòu)體數(shù)據(jù)區(qū)的長度之和。
//其實這個len中數(shù)據(jù)區(qū)長度是個有效長度,
// 因為不刪除協(xié)議頭,所以只計算有效協(xié)議頭和包內(nèi)容。如:當(dāng)在L3時,不會計算L2的協(xié)議頭長度。
data_len; // 只表示分片結(jié)構(gòu)體數(shù)據(jù)區(qū)的長度,所以len = (tail - data) + data_len;
__u16 mac_len, // mac報頭的長度
hdr_len; // 用于clone時,表示clone的skb的頭長度
// 接下來是校驗相關(guān)域
__u32 priority; // 優(yōu)先級,主要用于QOS
kmemcheck_bitfield_begin(flags1);
__u8 local_df:1, // 是否可以本地切片的標(biāo)志
cloned:1, // 為1表示該結(jié)構(gòu)被克隆,或者自己是個克隆的結(jié)構(gòu)體;
//同理被克隆時,自身skb和克隆skb的cloned都要置1
ip_summed:2,
nohdr:1, // nohdr標(biāo)識payload是否被單獨引用,不存在協(xié)議首部。
// 如果被引用,則決不能再修改協(xié)議首部,也不能通過skb->data來訪問協(xié)議首部。
nfctinfo:3;
__u8 pkt_type:3, // 標(biāo)記幀的類型
fclone:2, // 這個成員字段是克隆時使用,表示克隆狀態(tài)
ipvs_property:1,
peeked:1,
nf_trace:1;
__be16 protocol:16; // 這是包的協(xié)議類型,標(biāo)識是IP包還是ARP包或者其他數(shù)據(jù)包。
kmemcheck_bitfield_end(flags1);
void (*destructor)(struct sk_buff *skb); //這是析構(gòu)函數(shù),后期在skb內(nèi)存銷毀時會用到
#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
struct nf_conntrack *nfct;
struct sk_buff *nfct_reasm;
#endif
#ifdef CONFIG_BRIDGE_NETFILTER
struct nf_bridge_info *nf_bridge;
#endif
int iif; // 接受設(shè)備的index
#ifdef CONFIG_NET_SCHED
__u16 tc_index; /* traffic control index */
#ifdef CONFIG_NET_CLS_ACT
__u16 tc_verd; /* traffic control verdict */
#endif
#endif
kmemcheck_bitfield_begin(flags2);
__u16 queue_mapping:16;
#ifdef CONFIG_IPV6_NDISC_NODETYPE
__u8 ndisc_nodetype:2;
#endif
kmemcheck_bitfield_end(flags2);
/* 0/14 bit hole */
#ifdef CONFIG_NET_DMA
dma_cookie_t dma_cookie;
#endif
#ifdef CONFIG_NETWORK_SECMARK
__u32 secmark;
#endif
__u32 mark;
__u16 vlan_tci;
sk_buff_data_t transport_header; // 指向四層幀頭結(jié)構(gòu)體指針
sk_buff_data_t network_header; // 指向三層IP頭結(jié)構(gòu)體指針
sk_buff_data_t mac_header; // 指向二層mac頭的頭
/* These elements must be at the end, see alloc_skb() for details. */
sk_buff_data_t tail; // 指向數(shù)據(jù)區(qū)中實際數(shù)據(jù)結(jié)束的位置
sk_buff_data_t end; // 指向數(shù)據(jù)區(qū)中結(jié)束的位置(非實際數(shù)據(jù)區(qū)域結(jié)束位置)
unsigned char *head, // 指向數(shù)據(jù)區(qū)中開始的位置(非實際數(shù)據(jù)區(qū)域開始位置)
*data; // 指向數(shù)據(jù)區(qū)中實際數(shù)據(jù)開始的位置
unsigned int truesize; //表示總長度包括sk_buff自身長度和數(shù)據(jù)區(qū)以及分片結(jié)構(gòu)體的數(shù)據(jù)區(qū)長度
atomic_t users; // skb被克隆引用的次數(shù),在內(nèi)存申請和克隆時會用到
}; //end sk_buff
