Openvswitch源码阅读笔记-从vport开始

Linux网络接收数据包与ovs

Linux接收数据包
  1. Linux网络接收数据包是从网卡开始,网卡接收到数据包后写入内存中,随后向CPU发送中断,告诉CPU有数据到了。
  2. CPU根据中断表查找相应的中断函数,该函数会调用网卡驱动,网卡驱动向网卡发送一个软中断,阻止网卡继续向CPU发送中断。代表着CPU已经知道有数据到了。
  3. CPU会通过轮询逐一处理内存中的数据包。

ovs接收数据包

  1. 当Linux中建立有网桥,并绑定网卡时
ovs-vsctl add-br br0 
ovs-vsctl add-port br0 eth0
  1. 网卡会将数据包交给vport,通过函数ovs_vport_receive转入内核态的datapath处理

本文会详细分析ovs_vport_receive的流程

// vport 数据结构
/**
 * struct vport - one port within a datapath
 * @dev: Pointer to net_device.
 * @dp: Datapath to which this port belongs.
 * @upcall_portids: RCU protected 'struct vport_portids'.
 * @port_no: Index into @dp's @ports array.
 * @hash_node: Element in @dev_table hash table in vport.c.
 * @dp_hash_node: Element in @datapath->ports hash table in datapath.c.
 * @ops: Class structure.
 * @detach_list: list used for detaching vport in net-exit call.
 * @rcu: RCU callback head for deferred destruction.
 */
struct vport {
	struct net_device *dev;
	struct datapath	*dp;
	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;
};

详细的数据结构源码注释中已经写得十分明白:

  1. dev是网络设备的指针,即指向该vport绑定的网卡
  2. dp指向该vport所属的datapath
  3. 值得一提的是RCU,Linux2.6引进的新的锁机制Read-Copy Update,RCU所保护的文件,reader可以直接访问,而writer改写的则是文件副本,最后通过callback机制将原来的文件指针指向改写后的副本,完成update
  4. 端口号
  5. 存储vport的hash表
  6. 存储dp的hash表
  7. 指向vport操作函数的指针
  8. list_head和rcu_head都是RCU锁的处理机制
// ovs_vport_receive传参
int ovs_vport_receive(struct vport *vport, struct sk_buff *skb,
		      const struct ip_tunnel_info *tun_info) 

vport结构上面已经解析过,sk_buff是网络数据包的存放地点

struct sk_buff {
  union {unnamed_union};
  __u16 inner_transport_header;
  __u16 inner_network_header;
  __u16 inner_mac_header;
  __be16 protocol;
  __u16 transport_header;
  __u16 network_header;
  __u16 mac_header;
  sk_buff_data_t tail;
  sk_buff_data_t end;
  unsigned char * head;
  unsigned char * data;
  unsigned int truesize;
  atomic_t users;
};  

观其数据结构可知,sk_buff实现了数据链路层到传输层的数据结构,skb_buff通过指针指向数据包在内存中的实际存储地址。

struct ip_tunnel_info {
	struct ip_tunnel_key	key;
	struct dst_cache        dst_cache;
	u8			options_len;
	u8			mode;
};

ip_tunnel_info存储了创建vport时制定的隧道信息,ovs支持gre vxlan geneve协议,关于建立隧道的详情另讲。

ovs_vport_receive代码如下

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;
	// likely unlikely 在Linux2.6中随处可见
	// 告诉编译器括号内的可能性谁更大
	if (unlikely(dev_net(skb->dev) != ovs_dp_get_net(vport->dp))) {
		u32 mark;

		mark = skb->mark;
		// 该函数可以清理一些信息
		// 当skb被注入到一个命名空间时,必须清理对隔离造成影响的信息(xnet=true)
		skb_scrub_packet(skb, true);
		skb->mark = mark;
		tun_info = NULL;
	}

	// inner_protocol从本机端转变为大端(网络字节序和主机字节序)
	// ovs处理整型应该默认是大端的
	ovs_skb_init_inner_protocol(skb);
	// 清理gso标记
	// gso是延迟分段标记,数据包在到达网卡驱动前再分片
	skb_clear_ovs_gso_cb(skb);
	/* Extract flow from 'skb' into 'key'. */
	error = ovs_flow_key_extract(tun_info, skb, &key);
	if (unlikely(error)) {
		kfree_skb(skb);
		return error;
	}
	// 进入datapath处理
	ovs_dp_process_packet(skb, &key);
	return 0;
}

我们来逐段解析这部分代码

    OVS_CB(skb)->input_vport = vport;
	OVS_CB(skb)->mru = 0;
	OVS_CB(skb)->cutlen = 0;

这一部分,是ovs初始化内部储存的数据,ovs_cb的结构如下:

/**
 * struct ovs_skb_cb - OVS data in skb CB
 * @input_vport: The original vport packet came in on. This value is cached
 * when a packet is received by OVS.
 * @mru: The maximum received fragement size; 0 if the packet is not
 * fragmented.
 * @acts_origlen: The netlink size of the flow actions applied to this skb.
 * @cutlen: The number of bytes from the packet end to be removed.
 */
struct ovs_skb_cb {
	struct vport		*input_vport;
	u16			mru;
	u16			acts_origlen;
	u32			cutlen;
};
#define OVS_CB(skb) ((struct ovs_skb_cb *)(skb)->cb)

skb CB是skb_buff里面的一个成员,是一个控制缓存,由每一层自己维护并使用。一般情况下是40个字节。
ovs维护了一个自己的skb_cb,结构如上所示,字段意思也在源码注释中写得十分清楚:

  1. input_vport是记录数据包进来时的vport
  2. mru最大接收单元
  3. 针对skb的流操作的netlink大小
  4. 从数据包端删除的字节数。我的理解是分片后数据报文剩余的部分,0代表未分片。
if (unlikely(dev_net(skb->dev) != ovs_dp_get_net(vport->dp))) {
		u32 mark;

		mark = skb->mark;
		// 该函数可以清理一些信息
		// 当skb被注入到一个命名空间时,必须清理对隔离造成影响的信息(xnet=true)
		skb_scrub_packet(skb, true);
		skb->mark = mark;
		tun_info = NULL;
	}

skb_scrub_packet是Linux内核函数。可以将数据包封装在隧道或从隧道中解包后使用。这一段的意思是,当我们要把数据包注入到vport的datapath时,首先要将数据包从隧道中解包(如果有的话),并清理对隔离造成影响的信息。

error = ovs_flow_key_extract(tun_info, skb, &key);
	if (unlikely(error)) {
		kfree_skb(skb);
		return error;
	}

ovs_flow_key_extract是从skb的以太网帧中提取相关信息构建sw_flow_key,sw_flow_key是流表的索引,通过hash可以找到相应的flow entry,从而执行流表设定的action。
实际上流表key的提取是通过key_extract实现的,ovs_flow_key_extract封装了一层对tunnel的处理。

int ovs_flow_key_extract(const struct ip_tunnel_info *tun_info,
			 struct sk_buff *skb, struct sw_flow_key *key)
{
	int res, err;

	/* Extract metadata from packet. */
	if (tun_info) {
		key->tun_proto = ip_tunnel_info_af(tun_info);
		memcpy(&key->tun_key, &tun_info->key, sizeof(key->tun_key));
		BUILD_BUG_ON(((1 << (sizeof(tun_info->options_len) * 8)) - 1) >
			     sizeof(key->tun_opts));

		if (tun_info->options_len) {
			ip_tunnel_info_opts_get(TUN_METADATA_OPTS(key, tun_info->options_len),
						tun_info);
			key->tun_opts_len = tun_info->options_len;
		} else {
			key->tun_opts_len = 0;
		}
	} else  {
		key->tun_proto = 0;
		key->tun_opts_len = 0;
		memset(&key->tun_key, 0, sizeof(key->tun_key));
	}

	key->phy.priority = skb->priority;
	key->phy.in_port = OVS_CB(skb)->input_vport->port_no;
	key->phy.skb_mark = skb->mark;
	key->ovs_flow_hash = 0;
	res = key_extract_mac_proto(skb);
	if (res < 0)
		return res;
	key->mac_proto = res;
	key->recirc_id = 0;

	err = key_extract(skb, key);
	if (!err)
		ovs_ct_fill_key(skb, key);   /* Must be after key_extract(). */
	return err;
}

下一节解读函数ovs_dp_process_packet,datapath接收到数据包后的处理。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值