OVS源碼--packet處理流程(十五)

這來主要看看ovs從網絡接口收到packet後的一系列操作。

在內核模塊啓動的時候會初始化vport子系統(ovs_vport_init),各種vport類型,那麼什麼時候會調用相應的函數與實際網絡設備建立聯繫?其實當我們在爲網橋增設端口的時候,就會進入ovs_netdev_vport_ops中的create方法,進而 註冊網絡設備。

看ovs-vsctl add-port br0 eth1 實際做了什麼?

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);  
}

原文鏈接:https://www.kancloud.cn/digest/openvswitch/117245

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章