这来主要看看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);
}
转载地址:http://blog.csdn.net/vonzhoufz/article/details/19840683