這來主要看看ovs從網絡接口收到packet后的一系列操作。
在內核模塊啟動的時候會初始化vport子系統(ovs_vport_init),各種vport類型,那么什么時候會調用相應的函數與實際網絡設備建立聯系?其實當我們在為網橋增設端口的時候,就會進入ovs_netdev_vport_ops中的create方法,進而 注冊網絡設備。
看[ovs-vsctl add-port br0 eth1?實際做了什么?](http://blog.csdn.net/vonzhoufz/article/details/19981911)
~~~
struct?netdev_vport?{
??? struct rcu_head rcu;
???struct net_device *dev;
};
const struct vport_ops?ovs_netdev_vport_ops?= {
??? .type????????? = OVS_VPORT_TYPE_NETDEV,
??? .flags????????? = VPORT_F_REQUIRED,
????.init????????? = netdev_init, ??//之后的內核版本,這里直接return 0;
??? .exit????????? = netdev_exit,
????.create????????? = netdev_create,
??? .destroy???? = netdev_destroy,
??? .set_addr???? = ovs_netdev_set_addr,
??? .get_name???? = ovs_netdev_get_name,
??? .get_addr???? = ovs_netdev_get_addr,
??? .get_kobj???? = ovs_netdev_get_kobj,
??? .get_dev_flags???? = ovs_netdev_get_dev_flags,
??? .is_running???? = ovs_netdev_is_running,
??? .get_operstate???? = ovs_netdev_get_operstate,
??? .get_ifindex???? = ovs_netdev_get_ifindex,
??? .get_mtu???? = ovs_netdev_get_mtu,
??? .send????????? = netdev_send,
};
--datapath/vport-netdev.c
static struct vport *netdev_create(const struct vport_parms *parms)
{
??? struct vport *vport;
??? struct netdev_vport *netdev_vport;
??? int err;
??? vport = ovs_vport_alloc(sizeof(struct netdev_vport),? ?&ovs_netdev_vport_ops, parms);
? ?//有ovs_netdev_vport_ops和vport parameters 來構造初始化一個vport;
? ?netdev_vport = netdev_vport_priv(vport);
? ?//獲得vport私有區域??
????netdev_vport->dev = dev_get_by_name(ovs_dp_get_net(vport->dp), parms->name);
???[//通過interface]()?name比如eth0 得到具體具體的net_device 結構體,然后下面注冊 rx_handler;
??? if (netdev_vport->dev->flags & IFF_LOOPBACK ||? netdev_vport->dev->type != ARPHRD_ETHER ||
??? ????ovs_is_internal_dev(netdev_vport->dev)) {
???????? err = -EINVAL;
???????? goto error_put;
??? }
???//不是環回接口;而且底層鏈路層是以太網;netdev->netdev_ops == &internal_dev_netdev_ops 顯然為false
????err =?netdev_rx_handler_register(netdev_vport->dev, netdev_frame_hook,?vport);
? ??[//核心,收到packet后會調用]()?netdev_frame_hook處理;
??? dev_set_promiscuity(netdev_vport->dev, 1); ?//設置為混雜模式;
??? netdev_vport->dev->priv_flags |= IFF_OVS_DATAPATH; ?//設置netdevice私有區域的標識;
??? return vport;
}
--datapath/vport.h 創建vport所需要的參數結構
struct?vport_parms?{
??? const char *name; ? ?
??? enum ovs_vport_type type;
??? struct nlattr *options; ??[//利于必要的時候從 netlink msg通過屬性OVS_VPORT_ATTR_OPTIONS取得
]()
??? /* For ovs_vport_alloc(). */
????struct datapath *dp; ?// 這個vport所從屬的datapath
??? u16 port_no; ? //端口號
????u32 upcall_portid;?// 如果從這個vport收到的包 在flow table沒有得到匹配就會從 netlink端口upcall_portid 發送到用戶空間;
};
~~~
函數netdev_rx_handler_register(struct net_device *dev,rx_handler_func_t *rx_handler, void *rx_handler_data)定義在 linux/netdevice.h 實現在 net/core/dev.c 中,為網絡設備dev注冊一個receive handler,rx_handler_data指向的是這個receive handler是用的內存區域(這里存的是vport,里面有datapath的相關信息)。這個handler 以后會被 __netif_receive_skb() 呼叫,實際就是更新netdevice中的兩個指針域,rcu_assign_pointer(dev->rx_handler_data, rx_handler_data),? rcu_assign_pointer(dev->rx_handler, rx_handler) 。
?
netif_receive_skb(struct sk_buff *skb)從網絡中接收數據,它是主要的接收數據處理函數,總是成功,這個buffer在擁塞處理或協議層的時候可能被丟棄。這個函數只能從軟中斷環境(softirq context)中調用,并且中斷允許。返回值?NET_RX_SUCCESS表示沒有擁塞,NET_RX_DROP包丟棄。(實現細節暫時沒看)
接下來進入我們的鉤子函數 netdev_frame_hook(datapath/vport-netdev.c)這里主要看內核版本>=2.6.39的實現。
~~~
? ?static rx_handler_result_t?netdev_frame_hook(struct sk_buff **pskb)
{
??? struct sk_buff *skb = *pskb;
??? struct vport *vport;
??? if (unlikely(skb->pkt_type == PACKET_LOOPBACK))
???????? return RX_HANDLER_PASS;
??? vport = ovs_netdev_get_vport(skb->dev);
? ?//提攜出前面存入的那個vport結構體,vport-netdev.c line 401;
????netdev_port_receive(vport, skb);
??? return RX_HANDLER_CONSUMED;
}
~~~
函數 netdev_port_receive 首先得到一個packet的拷貝,否則會損壞先于我們而來的packet使用者?(e.g. tcpdump via AF_PACKET),我們之后沒有這種情況,因為會告知handle_bridge()我們獲得了那個packet 。
skb_push是將skb的數據區向后移動*_HLEN長度,為了存入幀頭;而skb_put是擴展數據區后面為存數據memcpy做準備。
~~~
static void?netdev_port_receive(struct vport *vport, struct sk_buff *skb)
{
? ?skb = skb_share_check(skb, GFP_ATOMIC);
? ? //GFP_ATOMIC用于在中斷處理例程或其它運行于進程上下文之外的地方分配內存,不會休眠(LDD214)。
??? skb_push(skb, ETH_HLEN);
? ?//疑問:剛接收到的packet應該是有 ether header的,為何還執行這個操作??
??? if (unlikely(compute_ip_summed(skb, false)))
???????? goto error;
??? vlan_copy_skb_tci(skb); // <2.6.27版本的時候才需要VLAN field;
????ovs_vport_receive(vport, skb);
??? return;
? ? .................
}
~~~
接下來將收到的packet傳給datapath處理(datapath/vport.c),參數vport是收到這個包的vport(表征物理接口和datapath),skb是收到的數據。讀的時候要用rcu_read_lock,這個包不能被共享而且skb->data?應該指向以太網頭域,而且調用者要確保已經執行過?compute_ip_summed()?初始化那些校驗和域。
~~~
void?ovs_vport_receive(struct vport *vport, struct sk_buff *skb)
{
??? struct vport_percpu_stats *stats;
??? stats = per_cpu_ptr(vport->percpu_stats, smp_processor_id());
? ?//每當收發數據的時候更新這個vport的狀態(包數,字節數),struct vport_percpu_stats定義在vport.h中。
??? u64_stats_update_begin(&stats->sync);
??? stats->rx_packets++;
??? stats->rx_bytes += skb->len;
??? u64_stats_update_end(&stats->sync);
??? if (!(vport->ops->flags & VPORT_F_FLOW))
???????? OVS_CB(skb)->flow = NULL;
? ?[//vport->ops->flags]()?(VPORT_F_*)影響的是這個通用vport層如何處理這個packet;
??? if (!(vport->ops->flags & VPORT_F_TUN_ID))
???????? OVS_CB(skb)->tun_key = NULL;
????ovs_dp_process_received_packet(vport, skb);
}
~~~
接下來我們的datapath模塊來處理傳上來的packet(datapath/datapath.c),首先我們要判斷如果存在skb->cb域中的OVS ?data sw_flow 是空的話,就要從packet中提攜構造;函數?ovs_flow_extract 從以太網幀中構造 sw_flow_key,為接下來的流表查詢做準備;流表結構struct flow_table定義在flow.h中,流表實在ovs_flow_init的時候初始化的?? 如果沒有match成功,就會upcall遞交給用戶空間處理(見vswitchd模塊分析),匹配成功的話執行flow action(接下來就是openflow相關)。
~~~
void?ovs_dp_process_received_packet(struct vport *p, struct sk_buff *skb)
{
??? struct datapath *dp = p->dp;
??? struct sw_flow *flow;
??? struct dp_stats_percpu *stats;
??? u64 *stats_counter;
??? int error;
??? stats = per_cpu_ptr(dp->stats_percpu, smp_processor_id());
??? if (!OVS_CB(skb)->flow) {
???????? struct sw_flow_key key;
???????? int key_len;
? ? ? ?/* Extract flow from 'skb' into 'key'. */
???????? error =?ovs_flow_extract(skb, p->port_no, &key, &key_len);
? ? ??
? ? ? ? /* Look up flow. */
???????? flow =?ovs_flow_tbl_lookup(rcu_dereference(dp->table),? &key, key_len);
???????? if (unlikely(!flow)) {
????????????? struct dp_upcall_info upcall;
????????????? upcall.cmd = OVS_PACKET_CMD_MISS;
????????????? upcall.key = &key;
????????????? upcall.userdata = NULL;
????????????? upcall.portid = p->upcall_portid;
??????????????ovs_dp_upcall(dp, skb, &upcall);
????????????? consume_skb(skb);
????????????? stats_counter = &stats->n_missed;
????????????? goto out;
???????? }
???????? OVS_CB(skb)->flow = flow;
??? }
??? stats_counter = &stats->n_hit;
??? ovs_flow_used(OVS_CB(skb)->flow, skb);
????ovs_execute_actions(dp, skb);
out:
??? /* Update datapath statistics. */
??? u64_stats_update_begin(&stats->sync);
??? (*stats_counter)++;
??? u64_stats_update_end(&stats->sync);
}
~~~
轉載地址:http://blog.csdn.net/vonzhoufz/article/details/19840683
- 前言
- OVS datapath模塊分析:packet處理流程
- openVswitch(OVS)源代碼分析之簡介
- openVswitch(OVS)源代碼分析之數據結構
- openVswitch(OVS)源代碼分析之工作流程(收發數據包)
- openVswitch(OVS)源代碼分析之工作流程(數據包處理)
- openVswitch(OVS)源代碼分析之工作流程(key值得提取)
- openVswitch(OVS)源代碼分析之工作流程(flow流表查詢)
- openVswitch(OVS)源代碼的分析技巧(哈希桶結構體為例)
- openVswitch(OVS)源代碼分析之工作流程(哈希桶結構體的解釋)
- openVswitch(OVS)源代碼之linux RCU鎖機制分析
- openVswitch(OVS)源代碼分析 upcall調用(之linux中的NetLink通信機制)
- openVswitch(OVS)源代碼分析 upcall調用(一)