這來主要看看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);
}