您的位置:首页 > 其它

OVS datapath模块分析:packet处理流程

2014-02-24 20:43 267 查看
这来主要看看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 首先检查是否skb被共享,若是则得到一个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)

{

if (unlikely(!vport))

goto error;

if (unlikely(skb_warn_if_lro(skb)))

goto error;

// check if buffer is shared and if so clone it

skb = skb_share_check(skb, GFP_ATOMIC);

if (unlikely(!skb))

return;

skb_push(skb, ETH_HLEN);

if (unlikely(compute_ip_summed(skb, false)))

goto error;

vlan_copy_skb_tci(skb);//直接忽略;

//交付给我们的vport通用层来处理;

ovs_vport_receive(vport, skb);

return;

error:

kfree_skb(skb);

}

接下来将收到的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);

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: