标签:
CORE网络数据包接收传递过程分析
能够接收实际网络流量是CORE的一个显著优点,这使得已有的系统能方便地接入虚拟网络进行模拟。CORE对网络设备的虚拟是通过LXC技术来实现的,而对网络的虚拟则是通过虚拟网卡(veth)、网桥(Bridge)、Quagga来实现的。本文档主要通过分析CORE中网络数据传递过程,来理解CORE网络模拟。
为了方便描述,以如图1所示拓扑结构为例子,分析数据流从网卡eth0到虚拟节点n2的过程。
图1 示例拓扑
虚拟网络创建由CORE后台根据前台的拓扑结构和配置,执行相应的命令进行实现,如下:
#创建容器n1
/usr/sbin/vnoded -v -c /tmp/pycore.52385/n1 -l /tmp/pycore.52385/n1.log -p /tmp/pycore.52385/n1.pid -C /tmp/pycore.52385/n1.conf
#创建容器n2
/usr/sbin/vnoded -v -c /tmp/pycore.52385/n2 -l /tmp/pycore.52385/n2.log -p /tmp/pycore.52385/n2.pid -C /tmp/pycore.52385/n2.conf
#创建网桥br.38079.56309
/usr/sbin/brctl addbr b.38079.56309
#关闭spanning tree protocol and forwarding delay并启动网桥
/usr/sbin/brctl stp b.38079.56309 off
/usr/sbin/brctl setfd b.38079.56309 0
/sbin/ip link set b.38079.56309 up
#创建网络转发过滤器ebtables,规则为接收和转发
/sbin/ebtables -N b.38079.56309 -P ACCEPT
/sbin/ebtables -A FORWARD --logical- in b.38079.56309 –j b.38079.56309
#通过vcmd执行,关闭多播探测
echo‘0’> /sys/devices/virtual/net/b.38079.56311/bridge/multicast_snooping
#创建虚拟网卡n1.eth0.44
/sbin/ip link add name n1.eth0.44 type veth peer name n1.0.44
#将虚拟网卡加入到容器的命名空间中,并命名为eth0
/sbin/ip link set n1.0.44 netns 17122 #17122为节点n1的vnoded进程的ID
/sbin/ip link set n1.0.44 name eth0
/sbin/ip link set n1.eth0.44 up
#将虚拟网卡绑定到网桥上
/usr/sbin/brctl addif b. 38079.56309 n1.eth0.44
/sbin/ip link set n1.eth0.44 up
#通过vcmd执行,设置网卡mac地址
/sbin/ip link set dev eth0 address 00:00:00:aa:00:00 #node内执行
/sbin/ip addr add 10.0.0.1/24 dev eth0 #node内执行
/sbin/ip link set eth0 up #node内执行
#通过vcmd执行,在容器n1启动路由服务
sh quaggaboot.sh zebra
sh quaggaboot.sh ospfd
sh quaggaboot.sh vtysh
不难发现示例拓扑内部结构为图2。
图2 示例拓扑内部结构
在示例拓扑中,CORE创建了两个容器(命名空间),两个网桥,三个虚拟网卡对(其中一端放进了容器中),并在命名空间中虚拟路由层,为了接收数据,命名空间中的虚拟网卡被连接到网桥上,而网桥可以连接物理网卡或其它虚拟网卡。这样就实现了虚拟节点、物理网卡之间的互联。
以示例拓扑结构为例,第一步是网卡接收数据包,在网卡硬件中断响应中完成。为了尽可能快速处理,网卡接收中断响应函数会将接收数据生成skb结构体,将通过调用netif_rx(skb)将任务以软中断的方式插入到CPU调度队列,然后立即返回。第二步,ksoftirqd处理软中断。这个过程,ksoftirqd根据软中断类型,调用不同的响应函数。本例中ksoftirqd将调用netif_receive_skb函数对skb包进行处理。netif_receive_skb可以理解为从物理层接收数据包,因此它也算是链路层的入口函数。在这个函数中,会调用到handle_bridge函数,将skb交给网卡进行处理(如果配置了网桥),调用br_handle_frame_hook。第三步,网桥会依据ebtables规则对skb作转发、广播、丢弃等操作。CORE中定义了ebtables规则为FORWARDING,因此网桥会将ebtables转发给连接到它的另一个(虚拟)网卡,调用veth_xmit。第四步,对于虚拟网卡而言,发送就是接收,于是在veth_xmit中,它将skb重新以软中断的方式插入到CPU调度队列,此时skb已在容器中运行。第五步,ksoftirqd调用netif_receive_skb函数,由于容器中没有配置网桥,netif_receive_skb函数会调用packet_type成员.func = ip_rcv将数据包送至L3层路由层。第六步,路由层从ip_rcv接收skb,调用ip_rcv_finish对skb进行路由(调用skb_rtable),决定本地接收还是转发skb。
图3 数据流接收转发过程
物理网卡,也叫以太网卡,在内核的表现形式为ethernet_device(简写为eth0)。它的实现是由网卡驱动提供的。网卡既是PCI设备,也是网络设备。它在开启时需要注册中断响应函数xxx_interrupt。
request_irq(dev->irq, &xxx_interrupt,
lp->shared_irq ? IRQF_SHARED : 0, dev->name,
(void *)dev)
当接收数据包时,会触发网卡中断,网卡驱动会为新到来的数据包申请skb, 并将网卡中的数据读到skb, 并通过netif_rx发送到内核协议栈上层。
if (rtl8169_rx_vlan_skb(tp, desc, skb) < 0)
netif_rx(skb);
dev->stats.rx_bytes += pkt_size;
dev->stats.rx_packets++;
这里有必要说一下skb,它用于存储网络数据包,结构如图所示。skb由网卡生成。由于它包含了net_device,协议类型,地址等。每一层在遇到skb时都能根据自己的业务逻辑进行处理。
图4 skb结构
网桥(Bridge)是L2层(数据链路层)设备,用于实现以太网设备之间的桥接。网桥可以绑定若干个以太网接口设备(利用brctl addif命令),从而实现网卡之间的互联。
(1) 模块初始化
在Linux中,网桥由动态加载模块Bridge完成。模块初始化时,除了完成自身模块初始化外,最重要一点就是向系统注册网桥处理HOOK。该HOOK将在handle_bridgge中调用。
br_handle_frame_hook = br_handle_frame;
(2) 与网卡绑定
图4 net_bridge结构体
网桥与网卡绑定通过与网桥端口绑定来实现。如图4所示,net_bridge结构体中维护了一个net_bridge_port结构体链表,而net_device(包含在eth设备结构体中)有指向net_bridge_port结构体的指针,该指针可以指向net_bridge_port结构体链表中的元素。
(3) 桥接处理
桥接处理一方面是接收数据,另一方面是发送数据。本例中没有通过网桥来发送数据,在此不作讨论。接收数据从br_handle_frame开始。
const unsigned char *dest = eth_hdr(skb)->h_dest;
int (*rhook)(struct sk_buff *skb);
//判断是否为有效的物理地址,非全0地址以及非广播地址
if (!is_valid_ether_addr(eth_hdr(skb)->h_source))goto drop;
//判断skb包是否被共享skb->users != 1,若是,则复制一份,否则直接返回
skb = skb_share_check(skb, GFP_ATOMIC);
if (!skb) return NULL;
const unsigned char *dest = eth_hdr(skb)->h_dest;
int (*rhook)(struct sk_buff *skb);
//判断是否为有效的物理地址,非全0地址以及非广播地址
if (!is_valid_ether_addr(eth_hdr(skb)->h_source))goto drop;
//判断skb包是否被共享skb->users != 1,若是,则复制一份,否则直接返回
skb = skb_share_check(skb, GFP_ATOMIC);
if (!skb) return NULL;
//这个函数是在判断是否为链路本地多播地址,01:80:c2:00:00:0x
if (unlikely(is_link_local(dest))) {
/* Pause frames shouldn‘t be passed up by driver anyway */
if (skb->protocol == htons(ETH_P_PAUSE)) goto drop;
/* If STP is turned off, then forward */
if (p->br->stp_enabled == BR_NO_STP && dest[5] == 0) goto forward;
if (NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_IN, skb, skb->dev,
NULL, br_handle_local_finish)) return NULL; /* frame consumed by filter */
else return skb; /* continue processing */
}
forward:
switch (p->state) {
case BR_STATE_FORWARDING:
//如果网桥处于forwarding状态,且该报文要走L3层进行转发,则直接返回
//br_should_route_hook钩子函数在ebtable里面设置为ebt_broute函数,它根据用户的规决定该报文是否要通过L3层来转发;一般rhook为空
rhook = rcu_dereference(br_should_route_hook);
if (rhook != NULL) {
if (rhook(skb))
return skb;
dest = eth_hdr(skb)->h_dest;
}
/* fall through */
case BR_STATE_LEARNING:
//如果数据包的目的mac地址为虚拟网桥设备的mac地址,则标记为host
if (!compare_ether_addr(p->br->dev->dev_addr, dest))
skb->pkt_type = PACKET_HOST;
//调用网桥在NF_BR_PREROUTING处挂载的钩子函数,
NF_HOOK(PF_BRIDGE, NF_BR_PRE_ROUTING, skb, skb->dev, NULL,
br_handle_frame_finish);
break;
default:
drop:
kfree_skb(skb);
}
return NULL;
FORWARDING以及LEARNING为网桥的状态,网桥端口一般有5种状态:
1) disable 被管理员禁用
2) blcok 休息,不参与数据包转发
3) listening 监听
4) learning 学习ARP信息,准备向工作状态改变
5) forwarding 正常工作,转发数据包
(4) 网桥过滤器ebtables_filter
网桥提供可配置的过滤转发规则,这一功能通过ebtables_filter来实现。ebtables_filter,ebtables和xt_tables都是可装载模块。ebtables用于存储过滤规则,每条过滤规则都对应ebtables中一个表项。如图5所示,在ebtables_filter模块初始化时向Bridge注册了各类HOOK函数。Bridge在接受、发送、转发数据包时调用相应的HOOK函数,实现过滤功能。
图5 net_filter结构体
为了与容器中的节点通信,CORE会为每个节点创建一个虚拟网卡对,并将一端移至容器中。
#将虚拟网卡对
/sbin/ip link add name n1.eth0.44 type veth peer name n1.0.44
#将虚拟网卡对的一端加入到容器的命名空间中,并命名为eth0
/sbin/ip link set n1.0.44 netns 17122 #17122为节点n1的vnoded进程的ID
虚拟网卡是去掉了硬件相关操作的网卡驱动程序。与物理网卡不一样,它没有接收外部数据的功能,只能由别的模块给它发送数据,通过调用veth_xmit实现。
stats->tx_bytes += length;
stats->tx_packets++;
rcv_stats->rx_bytes += length;
rcv_stats->rx_packets++;
netif_rx(skb);
在veth_xmit实现中,veth会同时将自己的接收和发送数据统计增加,并调用netif_rx(skb)将skb包交给上层处理。
CORE在虚拟网络里传递数据包时,都是通过Linux的软中断机制进行。当数据流量特别大的时候,软中断守护进程(ksoftirqd)负载会很大,有时候会达到100%,这时候可能发生丢包。这种丢包与网络协议会对格式、校验进行检查后做的丢弃坏包不一样,是CORE网络仿真系统的瓶颈。我们看一下netif_rx函数。
int netif_rx(struct sk_buff *skb)
{
struct softnet_data *queue;
unsigned long flags;
/* if netpoll wants it, pretend we never saw it */
if (netpoll_rx(skb)) return NET_RX_DROP;
if (!skb->tstamp.tv64) net_timestamp(skb); /* 时间戳处理*/
local_irq_save(flags); /* 保存当前中断状态,关中断*/
queue = &__get_cpu_var(softnet_data); /* 时间戳处理*/
__get_cpu_var(netdev_rx_stat).total++;
if (queue->input_pkt_queue.qlen <= netdev_max_backlog) {
if (queue->input_pkt_queue.qlen) { /* 插入调度队列*/
enqueue:
__skb_queue_tail(&queue->input_pkt_queue, skb); /* 插入调度队列*/
local_irq_restore(flags); /* 打开中断*/
return NET_RX_SUCCESS;
}
napi_schedule(&queue->backlog); /*唤醒ksoftirqd进行队列处理*/
goto enqueue;
}
__get_cpu_var(netdev_rx_stat).dropped++;
local_irq_restore(flags); /* 打开中断*/
kfree_skb(skb);
return NET_RX_DROP;
}
netif_rx函数会将skb包加入到当前cpu的softnet_data队列,如果队列为满,则丢弃数据包操作。可以通过cpu的netdev_rx_stat状态查看到丢包的状态。
标签:
原文地址:http://www.cnblogs.com/gqyan82/p/4656679.html