ovs,全名openvswitch,是一个高质量的、多层虚拟交换机,相对于bridge的一些优势:
1)方便网络管理与监控。OVS 的引入,可以方便管理员对整套云环境中的网络状态和数据流量进行监控,比如可以分析网络中流淌的数据包是来自哪个 VM、哪个 OS 及哪个用户,这些都可以借助 OVS 提供的工具来达到。
2)加速数据包的寻路与转发。相比 Bridge 单纯的基于 MAC 地址学习的转发规则,OVS 引入流缓存的机制,可以加快数据包的转发效率。
3)基于 SDN 控制面与数据面分离的思想。上面两点其实都跟这一点有关,OVS 控制面负责流表的学习与下发,具体的转发动作则有数据面来完成。可扩展性强。
4)隧道协议支持。Bridge 只支持 VxLAN,OVS 支持 gre/vxlan/IPsec 等。
5)适用于 Xen、KVM、VirtualBox、VMware 等多种 Hypervisors。
不过这些年,openflow明显热度降低,SDN网络可以有很多实现方式,如segment routing技术,结合传统的mpls/bgp,也能很好的实现SDN,相比于基于openflow协议的SDN网络,设备厂商支持的更好,更稳定。
同bridge一样,在向ovs bridge中添加成员接口的时候,会在成员接口的dev->rx_handler 上挂载收包处理函数,如下,ovs_vport_add 中会根据加入接口类型的不同,调用接口对应的create函数,而所有create函数都会调用ovs_netdev_link,其中注册了netdev_frame_hook 作为ovs成员口的收包处理函数。
以vxlan_create为例,vxlan本身的创建和linux vxlan一样,核心函数也是vxlan_dev_configure,其次是创建ovs成员口的私有数据 vport,最后在ovs_netdev_link 函数中间vxlan和vport关联,以及挂载接口收报函数和私有数据(netdev_frame_hook,vport)
struct vport *ovs_vport_add(const struct vport_parms *parms)
{
struct vport_ops *ops;
struct vport *vport;
ops = ovs_vport_lookup(parms);
if (ops) {
struct hlist_head *bucket;
if (!try_module_get(ops->owner))
return ERR_PTR(-EAFNOSUPPORT);
vport = ops->create(parms);
......
}
struct vport *ovs_netdev_link(struct vport *vport, const char *name)
{
......
err = netdev_rx_handler_register(vport->dev, netdev_frame_hook,
vport);
......
}
EXPORT_SYMBOL_GPL(ovs_netdev_link);
网卡收到包后,走到__netif_receive_skb_core后,剥完vlan找到vlan子接口(如果有的话),如果skb->dev是ovs成员口,就会走到netdev_frame_hook处理函数。
static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc)
{
......
// ovs挂载的 netdev_frame_hook 函数。
rx_handler = rcu_dereference(skb->dev->rx_handler);
if (rx_handler) {
if (pt_prev) {
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = NULL;
}
switch (rx_handler(&skb)) {
case RX_HANDLER_CONSUMED: // 报文已经被消费,结束处理
ret = NET_RX_SUCCESS;
goto out;
case RX_HANDLER_ANOTHER: // skb->dev 被修改,重新走一次
goto another_round;
case RX_HANDLER_EXACT: /* 精确传递到ptype->dev == skb->dev */
deliver_exact = true;
case RX_HANDLER_PASS:
break;
default:
BUG();
}
}
......
}
struct vport 是ovs成员端口的核心数据结构,它挂载在net_device 的rx_handler_data 上成员上,ovs模块中用的更多的是这个结构,但对外部模块不可见(私有数据结构)。
struct vport {
struct net_device *dev;
struct datapath *dp; // 对应一个bridge,ovs中可以添加多个bridge。
struct vport_portids __rcu *upcall_portids;
u16 port_no;
struct hlist_node hash_node;
struct hlist_node dp_hash_node;
const struct vport_ops *ops; // 对应不同接口类型的操作处理函数。
struct list_head detach_list;
struct rcu_head rcu;
};
netdev_frame_hook --> netdev_port_receive -->ovs_vport_receive-->ovs_dp_process_packet 流程。在进入 ovs_dp_process_packet 之前,从tun_info和 skb,提取了流的key信息,包含 tunnel、二层、三层、四层的报文头信息,为ovs dp匹配流表提供依据。
static void netdev_port_receive(struct sk_buff *skb)
{
struct vport *vport;
vport = ovs_netdev_get_vport(skb->dev);
if (unlikely(!vport))
goto error;
if (unlikely(skb_warn_if_lro(skb)))
goto error;
skb = skb_share_check(skb, GFP_ATOMIC);
if (unlikely(!skb))
return;
// ovs 是 switch,所以智能加二层口,向一些三层的tunnel口是无法加入的
skb_push(skb, ETH_HLEN);
skb_postpush_rcsum(skb, skb->data, ETH_HLEN);
// 一些tunnel口,如vxlan、gre,会在skb的dst_entry中缓存tunnel key
ovs_vport_receive(vport, skb, skb_tunnel_info(skb));
return;
error:
kfree_skb(skb);
}
int ovs_vport_receive(struct vport *vport, struct sk_buff *skb,
const struct ip_tunnel_info *tun_info)
{
struct sw_flow_key key;
int error;
OVS_CB(skb)->input_vport = vport;
OVS_CB(skb)->mru = 0;
OVS_CB(skb)->cutlen = 0;
if (unlikely(dev_net(skb->dev) != ovs_dp_get_net(vport->dp))) {
u32 mark;
mark = skb->mark;
skb_scrub_packet(skb, true);
skb->mark = mark;
tun_info = NULL;
}
/* Extract flow from 'skb' into 'key'. */
// 这里从tun_info和 skb,提取了流的key信息,sw_flow_key 包含 tunnel、二层、三层、四层的报文头信息,为ovs dp匹配流表提供依据。
error = ovs_flow_key_extract(tun_info, skb, &key);
if (unlikely(error)) {
kfree_skb(skb);
return error;
}
ovs_dp_process_packet(skb, &key);
return 0;
}
ovs_dp_process_packet 函数会根据报文key match流表:
1、如果没找到,走upcall处理,调用 queue_userspace_packet 将报文各层协议头 (OVS_PACKET_ATTR_KEY )、skb本身数据(OVS_PACKET_ATTR_PACKET)信息上送用户态(upcall.cmd=OVS_PACKET_CMD_MISS),用户态会有线程监听消息,在udpif_start_threads中创建了处理upcall的线程,处理handler = udpif_upcall_handler,udpif_upcall_handler通过fd poll的方式等待触发,如果有upcall上送,则进入recv_upcalls的处理函数中
1)从Device接收Packet交给事先注册的event handler进行处理;
2)接收Packet后识别是否是unknown packet,是则交由upcall处理;
3)vswitchd对unknown packet找到flow rule进行处理,其中最重要的调用是通过rule_dpif_lookup_from_table查找到匹配的流表规则,进而生成actions
rule_dpif_lookup_from_table又会通过流表的级联一个个顺序查找,每单个流表都会调用rule_dpif_lookup_in_table;
4)经过process_upcall流程后,已经为upcall的流生成对应的缓存流表信息了,缓存流表的key信息以及actions动作保存在struct upcall里,接下去就是要将缓存流表put到datapath了,通知内核态ovs处理put(对应OVS_FLOW_CMD_NEW)及execute(OVS_PACKET_CMD_EXECUTE)流程;
https://www.codenong.com/cs109398201/
2、存在匹配的流表,根据流表的action,执行相应的操作。action 类型很多,但最终一般需要是output action,从一个接口发送出去。
void ovs_dp_process_packet(struct sk_buff *skb, struct sw_flow_key *key)
{
const struct vport *p = OVS_CB(skb)->input_vport;
struct datapath *dp = p->dp;
struct sw_flow *flow;
struct sw_flow_actions *sf_acts;
struct dp_stats_percpu *stats;
u64 *stats_counter;
u32 n_mask_hit;
stats = this_cpu_ptr(dp->stats_percpu);
/* Look up flow. */
// 流表查询,根据前面从报文提取的信息
flow = ovs_flow_tbl_lookup_stats(&dp->table, key, &n_mask_hit);
if (unlikely(!flow)) {
struct dp_upcall_info upcall;
int error;
// 未查找到流表,做upcall 处理
memset(&upcall, 0, sizeof(upcall));
upcall.cmd = OVS_PACKET_CMD_MISS;
upcall.portid = ovs_vport_find_upcall_portid(p, skb);
upcall.mru = OVS_CB(skb)->mru;
error = ovs_dp_upcall(dp, skb, key, &upcall, 0);
if (unlikely(error))
kfree_skb(skb);
else
consume_skb(skb);
stats_counter = &stats->n_missed;
goto out;
}
// 查找到了流表,根据流表的action,执行相应的操作。
ovs_flow_stats_update(flow, key->tp.flags, skb);
sf_acts = rcu_dereference(flow->sf_acts);
ovs_execute_actions(dp, skb, sf_acts, key);
stats_counter = &stats->n_hit;
out:
/* Update datapath statistics. */
u64_stats_update_begin(&stats->syncp);
(*stats_counter)++;
stats->n_mask_hit += n_mask_hit;
u64_stats_update_end(&stats->syncp);
}
int ovs_execute_actions(struct datapath *dp, struct sk_buff *skb,
const struct sw_flow_actions *acts,
struct sw_flow_key *key)
{
int err, level;
level = __this_cpu_inc_return(exec_actions_level);
if (unlikely(level > OVS_RECURSION_LIMIT)) {
net_crit_ratelimited("ovs: recursion limit reached on datapath %s, probable configuration error\n",
ovs_dp_name(dp));
kfree_skb(skb);
err = -ENETDOWN;
goto out;
}
OVS_CB(skb)->acts_origlen = acts->orig_len;
err = do_execute_actions(dp, skb, key,
acts->actions, acts->actions_len);
if (level == 1)
process_deferred_actions(dp);
out:
__this_cpu_dec(exec_actions_level);
return err;
}
// action 类型很多,但最终一般需要是output action,从一个接口发送出去
static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
struct sw_flow_key *key,
const struct nlattr *attr, int len)
{
/* Every output action needs a separate clone of 'skb', but the common
* case is just a single output action, so that doing a clone and
* then freeing the original skbuff is wasteful. So the following code
* is slightly obscure just to avoid that.
*/
int prev_port = -1;
const struct nlattr *a;
int rem;
for (a = attr, rem = len; rem > 0;
a = nla_next(a, &rem)) {
int err = 0;
if (unlikely(prev_port != -1)) {
struct sk_buff *out_skb = skb_clone(skb, GFP_ATOMIC);
if (out_skb)
do_output(dp, out_skb, prev_port, key);
OVS_CB(skb)->cutlen = 0;
prev_port = -1;
}
switch (nla_type(a)) {
case OVS_ACTION_ATTR_OUTPUT:
prev_port = nla_get_u32(a);
break;
case OVS_ACTION_ATTR_TRUNC: {
struct ovs_action_trunc *trunc = nla_data(a);
if (skb->len > trunc->max_len)
OVS_CB(skb)->cutlen = skb->len - trunc->max_len;
break;
}
case OVS_ACTION_ATTR_USERSPACE:
output_userspace(dp, skb, key, a, attr,
len, OVS_CB(skb)->cutlen);
OVS_CB(skb)->cutlen = 0;
break;
case OVS_ACTION_ATTR_HASH:
execute_hash(skb, key, a);
break;
case OVS_ACTION_ATTR_PUSH_MPLS:
err = push_mpls(skb, key, nla_data(a));
break;
case OVS_ACTION_ATTR_POP_MPLS:
err = pop_mpls(skb, key, nla_get_be16(a));
break;
case OVS_ACTION_ATTR_PUSH_VLAN:
err = push_vlan(skb, key, nla_data(a));
break;
case OVS_ACTION_ATTR_POP_VLAN:
err = pop_vlan(skb, key);
break;
case OVS_ACTION_ATTR_RECIRC:
err = execute_recirc(dp, skb, key, a, rem);
if (nla_is_last(a, rem)) {
/* If this is the last action, the skb has
* been consumed or freed.
* Return immediately.
*/
return err;
}
break;
case OVS_ACTION_ATTR_SET:
err = execute_set_action(skb, key, nla_data(a));
break;
case OVS_ACTION_ATTR_SET_MASKED:
case OVS_ACTION_ATTR_SET_TO_MASKED:
err = execute_masked_set_action(skb, key, nla_data(a));
break;
case OVS_ACTION_ATTR_SAMPLE:
err = sample(dp, skb, key, a, attr, len);
break;
case OVS_ACTION_ATTR_CT:
if (!is_flow_key_valid(key)) {
err = ovs_flow_key_update(skb, key);
if (err)
return err;
}
err = ovs_ct_execute(ovs_dp_get_net(dp), skb, key,
nla_data(a));
/* Hide stolen IP fragments from user space. */
if (err)
return err == -EINPROGRESS ? 0 : err;
break;
}
if (unlikely(err)) {
kfree_skb(skb);
return err;
}
}
if (prev_port != -1)
do_output(dp, skb, prev_port, key);
else
consume_skb(skb);
return 0;
}
接口的output操作,流程是 do_output -->ovs_vport_send --> vport 注册的send函数,都是直接调用的 dev_queue_xmit 函数,因为ovs进来的是二层包,流表只是对报头做了修改,再转发出去,不需要走协议栈。