闲谈v2ray的负载均衡

由于Oracle之前推出了always free(虽不知这个always会是多久)的云服务器,带宽限制在单台45Mbps左右,在白天电信国际出口不怎么拥塞的时候,还算是不错的一个选择,可以用来节省自己bwg cn2 gia线路为数不多的500G流量(按双向计费的规则来算,实则只有250G)。

由于我在外地是以vmess普通协议接入家中的软路由,软路由上的v2ray再中转统一走ws+tls的方式去科学上网,由于Oracle的机器有两台的免费额度,因此如果能并发用起来,效果应该还是不错的。

然而根据周末的测试结果来看,在软路由上,分别测试Oracle两台机器往家里软路由打的带宽总是超不过50Mbps,这让我觉得有点诡异,思考其中原因,应该是:

由于我的软路由性能一般,且pppoe拨号后,使得网卡的rss机制实际上是无法工作的,因为报文有一层pppoe封装,使得网卡无法看到pppoe里ppp封装的ip报文,因而无法进行多queue处理,查了内核rps的功能,似乎也没有这个功能。因而所有入方向即通常我们从外网的下载流量,都只能由单个cpu核心处理,如果没有做好cpu隔离的话,就会影响到性能

基于这个思考,且考虑到家里的电信支持多拨,马上在软路由上拨出来别外一个ppp接口,将另外一台云服务器的路由配置到此ppp接口上去。果不其然,并发流量直接搞到了100Mbps,这样从家里内网使用Oracle线路下载文件时,会被v2ray负载分担到两条云主机上,且通过不同的ppp接口出去,下载速度自然会dobule,实为物尽其用也。

写到这些,也谈谈我对负载均衡的理解,我觉得,负载均衡实际上来自于对于一个业务的拆解,比如从http协议的角度来说,http本身的处理,渲染页面实际是比较占cpu的,那么,通过前置一个4层proxy,让这个proxy主要负责tcp的报文中转,就可以将一堆http服务器置于这个proxy的后面,由于proxy只需要处理tcp报文中转,是可以实现比较高的性能的,并且可以通过硬件能力堆上去来scale up提高处理性能,而proxy则可以通过多台scale out来实现匹配到proxy的转发能力,从而提高整体集群的处理能力。

当然,如果在proxy前,再放一台三层交换机,来做下三层的ecmp,这样就又可以实现proxy一层的scale out,整个集群的能力就越来越强了。

附:

1、v2ray的负载均衡文档见:https://toutyrater.github.io/routing/balance2.html 这是该文档作者后来更新的负载均衡版本,先前他给出的方案并不好用,所以作者用了v2ray提供的新方案,我自己也是用这个模式配置的。

2、看起来这内核的维护者似乎也意识到ppp这种封装使得rps无效了?http://vger.kernel.org/~davem/davem_spain10.pdf

v2ray kitsunebi客户端与iptables dnat转发

因为手机是不限量套餐,故而在杭州我一直主用自己的电信套餐来做为日常上网的方法,亦无需开通宽带了,然而电信在浙江的漫游策略则是漫游至南京的GGSN设备以便出互联网(江苏移动就比较好,可以直接从浙江本地出去),而该GGSN的上网ip,访问我的科学上网服务器因为中间有跳电信202.97的路由器,从而无法体现我科学上网服务器全程CN2 GIA线路的特质,而我家里电信则可以享用到双程CN2 GIA线程的特质,故而我决定在家里路由器通过iptables dnat直接转发下,这样在杭州可以先连回江苏家中,从而再走国内,这样跨境仍利用我家的双程CN2 GIA线程,效果预计会更好些。

然而在家中路由器直接开启dnat却无法让我直接使用,直到抓包才发现,由于我采用了Caddy做为https来反向代理websocket方式的v2ray服务端,而kitsunebi直接将我家中路由器ddns绑定的域名做为tls 1.2协商所用的sni,而Caddy服务器绑定的域名则是另外一个,这样就导致服务器tls协商不成功,从而无法正常加速。

而kitsunebi这样就要么在android手机上做个dns代理,仍用原来的域名,但通过dns代理为我家里路由器的ip,而这样的玩法较为复杂,而改kitsunebi的android代码,我也觉得较为费功夫;因而或可考虑再在家中的路由器再部署一个https代理来再代理一次,但这样亦较为复杂。

后来经查询发现,原来v2ray自己就有转发模式,这样可以直接在家中路由器开启vmess做为接入协议,出口则去连科学上网服务器的ws+tls服务,这样kitsunebi就可以直接通过vmess接入家中,快速部署了一下,果然成功解决。

实际测试发现下载带宽性能大约能够提升40%,油管更加顺畅,开心看视频去也。

主流公有云外网IP实现调研

因工作需要, 尝试分析主流公有云外网IP是否具有较好的隔离性,以下记录下分析结果。

主流厂商方案调研

aws

aws的实例可获得一个动态的public ip(重启后会变)以及不变的elastic ip,这两种ip官方文档上明确给出是属于1:1 NAT,在vm内部仍只能看到私有网地址,使vm只能访问内网及外网,不能向同外网子网的机器注入广播报文,具有较好的隔离安全性。

aws的私有网(即EC2-VPC)做了较多的限制,在FAQ中明确给出不支持组播及广播,在网络上也看到有其他基于第三方软件规避此问题的方案。

aws的网络实现经由了EC2-Classic到EC2-VPC的转变,目前主推EC2-VPC,提供虚拟子网功能。

相关资料:

microsoft azure

azure具有两种创建虚拟机模式,注册了试用账号,并结合文档,总结外网在不同模式下实现如下:

  1. Azure Resource Manager(v2版本,以资源方式管理一组vm): 默认动态外网ip,可配置成静态外网ip(需关机重开机) ;此种模式下无法动态或静态ip,在内部只能看到一个内网ip的接口。
  2. Azure Service Management(v1版本,称为classic deployment,以服务方式管理一组vm):默认无外网ip,需通过service的vip对外呈现,需要手工打开vip端口,并关联后端vm dip端口;可设置实例级别的外网ip,称为ilpip,vip本身在vm内部无网卡,ilpip也同样无网卡。

也即无论是v1/v2版本的部署模式,无论是共享的vip还是独立的public ip或ilpip,在vm内部均不可见,类似于floating ip技术,即vm只能访问内网及外网,不能向同外网子网的机器注入广播报文,具有较好的隔离安全性。

而对于私有网,二层做了大量的限制,比如收方向收不到不是到自己地址的报文(含L2/L3过滤),对ARP及DHCP进行了速率限制且做了spoofing处理,组网、广播、udp组播明确表明不支持(详见下附资料)。

相关资料:

google cloud engine

从GCE的官方文档来看,其实例的external public ip基于NAT实现,即vm只能访问内网及外网,不能向同外网子网的机器注入广播报文,具有较好的隔离安全性。

其子网仅支持IPv4单播,不支持IPv4组播及广播,而其资料上提到子网内ARP通过代理予以回复,综合来看,二层组播及广播显然不支持,且在其网络中,也不会存在未知单播的情况。

GCE的网络实现与AWS类似,经由了legacy network到subnet network的变化。

相关资料:

aliyun

根据资料及试用结果,aliyun classical network中云主机具有两个接口,外网接口(/22)直接暴露给用户,通过tcpdump可以监听到来自于其他节点及网关的ARP请求,说明未做了隔离,而VPC网络则进行了改进,云主机只有一个接口,外网采用1:1 NAT实现,在vm内部仍只能看到私有网地址,使vm只能访问内网及外网,不能向同外网子网的机器注入广播报文,具有较好的隔离安全性

aliyun同样经过了classic network到VPC网络的改进。

相关资料:

ucloud

根据上次在为上海客户基于ucloud临时搭建的操作经历,以及Ucloud所公开的设计细节,其EIP同样采用1:1 NAT实现,在vm内部仍只能看到私有网地址,使vm只能访问内网及外网,不能向同外网子网的机器注入广播报文,具有较好的隔离安全性。

相关资料:

tencent cloud

根据在腾讯云主机的实际验证,linux云主机内部仅有一个接口,具有一个/18子网的内网地址(10.X.X.X),且根据其资料所示,腾读云方外网IP同样采用1:1实现,使vm只能访问内网及外网,不能向同外网子网的机器注入广播报文,具有较好的隔离安全性。

腾讯云EIP 15年底发布,之前与aliyun一样,仍是实例级的额外接口配置公网IP,相当于经过了优化。

相关资料:

qingcloud

青云经历了网络1.0到网络2.0的演进,2.0支持实例直接绑公网IP,不依赖于路由器做端口转发了,网络2.0支持EIP,根据验证及资料,外网IP同样采用1:1实现,使vm只能访问内网及外网,不能向同外网子网的机器注入广播报文,具有较好的隔离安全性。

相关资料:

云服务商外网IP实现汇总

总体上前述所有公有云厂商均最终采用了EIP这样的分离方案,具有较好的隔离安全性。

linux max open files限制过小导致openvswitch工作异常

上篇分析了linux内核的netdev_max_backlog默认设置导致了openvswitch在大量virtual port转发异常的问题,本篇再额外记录一处与大量virtual port相关的调优点。

自openvswitch 2.x版本开始,ovs引入了多线程的支持,在非dpdk即传统的ovs应用模式下,多线程主要用于处理内核fastpath lookup miss的流表,此过程称为upcall;并对这些流表进行统计、老化处理,此过程称为revalidate。

upcall和revalidate的线程数量计算,以openvswitch 2.3.x为例,代码中的判断是将物理核数约3/4分配给upcall,而revalidate则占1/4,这个分配直观上是合理的,毕竟upcall处理转发,要尽可能的快,处理能力也要更强。

而我们知道openvswitch datapath对用户态的每个virtual port都会在linux内核datapath中创建一个vport,内核datapath通过netlink发送消息给upcall,为了更快的处理每个vport的upcall请求,openvswitch将会为每个virtual port分配等同upcall线程数量的netlink socket,内核datapath根据flow hash将同一vport的upcall分发给不同的netlink socket,实现不同upcall线程间的并发处理,提升upcall性能。

从这里可以看出,openvswitch在优化性能方面确实做足了功夫,但这里确存在一个值得考虑的地方,我们知道linux process存在一个max open files的限制,当限制达到后,新的fd将无法被分配,导致建立文件、创建socket等需要用到fd资源的操作无法执行。

那么我们可以估计一个24 physical core的系统上,如果有2000个virtual port,openvswitch将会有多少个netlink socket,243/42000即36000,也即单音用于处理vport的netlink socket就占用了36000个fd,如果对openvswitch的max open files限制过小,很容易导致在大量virtual port下,出现各种工作异常。

对于此问题主要的优化思路如下:

  • 调整ovs-vswitchd进程的max open files limit上限,可按照前述算法估量
  • openvswitch提供了一个n-handler-threads的配置,可通过ovs-vsctl持久化配置,根据性能需求适当的减少upcall线程数量

linux内核netdev_max_backlog设置在极端情况下导致openvswitch转发不通

近期在调试问题时,遇到了一个从配置上看openvswitch无任何问题,但却导致转发不通的问题,特此记录下说,说明在openvswitch在大规模部署时,仍需要有较多调优之处。

设想如下openstack场景,大量tenant router通过linux namespace即仿真vrouter访问外网,此namespace中通过openvswitch internal port(或veth,如果不追求转发性能)连接到另一公网namespace,此时如果公网namespace中接口数量过多,比如说2000个(比如openstack中的qg-*接口),将有可能面临转发不通的情况。

从openvswitch的角度,我们知道openvswitch在内核态存在一个datapath,负责生成内核流表,实现高性能转发,对于一个拥有上千个port的ovs bridge来说,比如对应于上面的公网namespace,收到一个arp请求报文,由于是广播,报文通守openvswitch在userspace命中默认的normal流表后将推送广播至其他所有成员端口的内核流表,此后arp报文广播复制逻辑将在内核datapath模块处执行,最终命中以下内核代码:

:::c
/*
* enqueue_to_backlog is called to queue an skb to a per CPU backlog
* queue (may be a remote CPU queue).
*/
static int enqueue_to_backlog(struct sk_buff *skb, int cpu, unsigned int *qtail)
{
    struct softnet_data *sd;
    unsigned long flags;

    sd = &per_cpu(softnet_data, cpu);

    local_irq_save(flags);

    rps_lock(sd);
    if (skb_queue_len(&sd->input_pkt_queue) <= netdev_max_backlog) {
        if (skb_queue_len(&sd->input_pkt_queue)) {
enqueue:
	        __skb_queue_tail(&sd->input_pkt_queue, skb);
		     input_queue_tail_incr_save(sd, qtail);
             rps_unlock(sd);
             local_irq_restore(flags);
             return NET_RX_SUCCESS;
		 }

         /* Schedule NAPI for backlog device
          * We can use non atomic operation since we own the queue lock
          */
         if (!__test_and_set_bit(NAPI_STATE_SCHED, &sd->backlog.state)) {
             if (!rps_ipi_queued(sd))
                 ____napi_schedule(sd, &sd->backlog);
         }
         goto enqueue;
    }

    sd->dropped++;
    rps_unlock(sd);

    local_irq_restore(flags);

    atomic_long_inc(&skb->dev->rx_dropped);
    kfree_skb(skb);
    return NET_RX_DROP;
}

可以注意到在上面的代码中有一个对sd->input_pkt_queue的检查过程,而sd是每个cpu核心net rx soft irq用于维护待处理报文的队列,netdev_max_backlog则是内核所提供的一个配置选项,默认为1000。

在ovs datapath复制报文的过程中,将对上千个port进行遍历处理,这个处理过程中不会进行cpu释放的操作,也即将持续调用enqueue_to_backlog(openvswitch datapath最初调用的是netif_rx,但最终调到enqueue_to_backlog),那么默认的1000限制将很快达到,假如上述的qg-*所公用的外网网关碰巧位于1000个以后,那么很显然,某个vrouter namespace发过来的ARP请求则无法被发送给公网外网网关接口,导致vrouter所对应的内网无法访问外网,如果是在生产环境中,势必影响客户业务。

对于此问题的修改,有如下两种思路:

  • 直接提高netdev_max_backlog数量,根据可能存在的接口数量合理设置
  • 通过sdn控制器配置arp或动态回复arp请求

由于目前项目中所采用的方案,我们通过方案1进行了改造,较好的解决了此问题。

emacs与konsole不对付的那点事

作为Linux重度用户,折腾过各式各样的WM/DE,最终还是觉得KDE PLASMA的易用性和美观程度是最佳的,考虑到不折腾的缘故,一直使用KDE至今,虽则时有crash,但总体上不优点大于缺点,瑕不掩瑜。

在linux上,存在较多的终端仿真器,即terminal emulator,所谓的仿真器就是把图形系统即X窗口上的键盘输入,转化成virtual terminal的输入,传送给那些将标准输入关联在/dev/pts这些伪终端设备上的控制台程序,使得这些控制台程序能够像在文本模式下那样工作。

这些仿真器中就包括KDE自带的konsole。

但konsole有一个问题就是,如果我们在终端仿真器里进行emacs -nw即文本模式的emacs(图形模式下不存在下面所述问题),则ctrl+2/ctrl+2这个emacs默认用来激活mark的命令大概率不能用,当然ctrl+space也可以用,但对于中文用户而言,ctrl+space大多绑定到输入法中英文切换,相应的键不能用,还是带来了诸多不便。

由于konsole可以通过手工设置key binding,因此可以手工将ctrl+2这个键绑定到发送\0这个控制序列给终端程序,来解决此问题,但对于适应了ctrl+@这个相似功能的键位的用户来说,目前key binding无法设置,仍是非常不便。

https://bugs.kde.org/show_bug.cgi?id=341157 KDE社区有人提了类似的bug,且已经解决,但似乎只解决了西语用户所关注的ctrl+space问题,但ctrl+@这个仍未解决,我已经提供了相关的反馈,期待进一步的进展。

:::console
fortitude@fortitude:~$ konsole -version
QCoreApplication::arguments: Please instantiate the QApplication object first
Qt: 5.5.1
KDE Frameworks: 5.19.0ppp
Konsole: 15.12.2

由于C++代码不够熟悉,自己也没有过我的精力去钻研这里,就先写在这里,供遇到相似问题的朋友参考关注。

OpenStack网络方案近期状态分析

目前OpenStack网络方案存在有较多的选择,liberty发布已经有大半多时间,mikata版本也快要发布,有必要比较一下近期各种openstack网络方案的状态。

主流方案介绍

当前,比较主流的方案有如下几种:

  • ovs-agent
  • ofagent
  • ovn
  • dragonflow
  • midonet/opencontrail

除此之外,还有一些闭源的商业方案,如nsx、plumgrid,以及一些试验性的开源方案如networking-odl等,本次分析暂不考虑。

现有网络方案特性集及特点

  • ovs-agent
功能点支持情况备注
L2 isolation using overlayY支持vlan/vxlan/gre
arp responderY要求ovs 2.1 + l2pop使能
L3 DVRY要求每台计算节点都需要安装l3-agent,通过netns及linux协议栈实现
ovsdb nativeY独立功能,与openflow native无关,可单独开启,有一定的性能提升
openflow nativeYovs-agent将内置ryu,运行一个of app用来处理与本地ovs的交互,基本逻辑与原ovs-agent相似,通过driver机制实现不同方式的bridge操作,目前仍处于实验阶段
security groupY目前仍基于iptables,随着ovs conntrack功能的完善,可以演进至基于ovs conntrack实现
dhcpY仍采用dhcp agent
north-sourth流量Y仍采用l3agent进行集中式转发,即便在dvr模式下
  • ofagent

如同ofagent在openstack上的wiki所述:

OFAgent is a neutron core-plugin, implemented as ML2 mechanism driver. It aims to support pure OpenFlow1.3 switches.

ofagent关注的更多是指向向设备(如vswitch/pswitch)可移植性,因此基于纯openflow协议进行实现,而纵观业界,基本上没有基于纯openflow的openstack网络方案(目前仍成功商用的则是bigswitch的big fabric,但其采用了深度修改的openflow),因此这种方式可能是过于理想化的方案,可能并不一定能够容易落地。

从ovs的状态来看,ovs 2.5中为支持conntrack,引入了ct_state/ct这样的match/atction,这些nicira扩展落入openflow spec的时间点可能还比较长,而为了实现dv足够多的功能,ovs将会继续引入扩展,如果基于纯openflow实现的话,这些功能都将难以利用。

从wiki上的比较来看,ofagent也不支持dvr,实现上同样基于ryu,而部署模式上与ovs-agent相似,在compute/network节点都需部署。

  • ovn

ovn是由vmware所主导的新项目,其介绍如下所示:

OVN, the Open Virtual Network, is a system to support virtual network abstraction. OVN complements the existing capabilities of OVS to add native support for virtual network abstractions, such as virtual L2 and L3 overlays and security groups. Services such as DHCP are also desirable features. Just like OVS, OVN’s design goal is to have a production-quality implementation that can operate at significant scale.

目前已经实现的功能集如下:

功能点支持情况备注
L2 isolation using overlayYhypervisor间仅支持geneve/stt,与l2gw之间可采用vxlan连接
arp responderY直接基于流表实现
L3 DVRY不依赖netns,基于ovs patch port及纯流表实现,对vrouter网关的icmp请求将由流表进行回复
ovsdb nativeYovsdb目前既做northbound db,又做southbound db,同时又做hypervisor上的本地db,但操作上均是通过c语言访问native接口实现的
openflow nativeY在hypervisor上需运行一个基于c语言实现的local controller,通过native openflow消息操作vswitchd
security groupN基于OVN ACL实现,目前已经提供部分代码,尚未实现
dhcpY暂时仍采用dhcp agent,后续将内置实现
north-sourth流量Y暂仍采用l3 agent,后续将内置实现,并预期支持L3HA

nn从功能点上来说,ovn还有大量的功能有待开发,并且很多功能一方面依赖于ovs,一方面依赖于kernel,尽管邮件列表上计划在mikita版本有试验性的支持,从当前现状来看,达到生产级质量水平的可能性较小。

从架构上来看,尽管ovn也认识到northdb、southdb目前都存在ovsdb-server上存在单点故障并且ovsdb尚不支持clustering,但目前仍关注于feature的完备性,故而还未投入过多精力处理。

  • dragonflow

Dragonflow implements Neutron using a lightweight embedded SDN Controller. Dragonflow is available in two configurations: Distributed and Centralized. Our project mission is to Implement advanced networking services in a manner that is efficient, elegant and resource-nimble

dragonflow由华为以色利团队开发,相比ofagent那种仅为提升性能而采用ryu实现openflow native语义的方式,dagonflow更为激进,采用了ryu app来实现l2转发及l3 dvr,并且从代码上来看,dhcp功能也已经做为ryu app实现了,并且dragonflow强调自己实现了pluggable db的支持,即通过引入外置第三方db来实现数据同步,与openstack rpc解耦,相比ovn采用ovsdb来说,db层面的可靠性及扩展性更好。

目前dragonflow的功能如下:

功能点支持情况备注
L2 isolation using overlayY支持vxlan/gre
arp responderY基于流表实现
L3 DVRY基于流表实现,无需netns
ovsdb nativeY通过ovsdb python idl访问ovsdb
openflow nativeY内置ryu,通过openflow协议处理packet-in及流表
security groupN计划采用ovs conntrack实现,但尚未开发
dhcpY采用运行在ryu里的dhcp app,实现分布式dhcp
xnorth-sourth流量Y仍采用l3agent进行集中式转发
  • midonet

midonet是midokura公司开源的openstack网络方案,核心实现是基于分布式数据库(zookeeper)实现neutron数据分发,并通过ovs kmod实现转发,但在用户态完全舍弃ovs,采用自己通过java+scala实现的本地控制器,其核心转发过程称为simulation,即通过本地控制器所获知到的虚拟网络拓朴,来判断一个报文最终的转发结果,并将结果添加到ovs kmod所维护的内核态流表中。

国外有部署云客户采用该公司的方案,国内据说乐视私有云采用了该方案。

目前midonet的功能如下:

功能点支持情况备注
L2 isolation using overlayY支持vxlan
arp responderY由本地控制器仿真实现
L3 DVRY由本地控制器仿真实现,将下发至内核态流表
security groupY由本地控制器仿真实现
dhcpY由本地控制器实现
north-sourth流量Y支持DNAT/SNAT,并实现多l3时的snat同步,并可以和外部通过bgp进行l3对接
load-balancerY由HA Proxy实现
  • opencontrail

opencontrail为juniper所开发的独立网络虚拟化方案,采用专用的内核模块实现virtual network,不依赖于ovs,并采用大量的私有或公有如bgp协议实现控制平面,与juniper的设备有较好的对接,国外部分公有云如tcpcloud等采用其方案。

从部署上来说该方案与现有方案差异较大,不做过多描述。

选型建议

从上述分析来看,除社区ovsagent外,midonet/opencontrail是较为完善的方案,但前者社区较小(尽管目前部分代码已经托管至openstack官方),且采用小众语言开发,深入定制有一定的难度;如果半年内要升级的话,ovsagent似乎是较为可行的选择,半年后可以关注ovn/dragonflow等相关项目的状态再做选择。

至于转发性能方案,各方案主要还是解决管理上的复杂度,性能上dvr算是一个较大的亮点,转发性能上可以通过后续引入对组网影响较小的vxlan offload nic来提升。

关于网卡VXLAN offload能力的误解

网络技术这个行业里,厂商通常会对一些技术做出高大上的包装,像网卡VXLAN offload这种技术,就是一个典型的例子,然而其底层技术,也许并不像想象中的那么神秘。

做为网络交换机的开发人员,近年来一直从事数据中心网络虚拟化的相关工作,最近读取一篇关于青云SDN 2.0的文章,里面提到了采用网卡VXLAN offload这种技术后overlay情形下的虚拟网络性能得到了较大规模的提升,考虑到后续我亦将加入云计算公司进行网络虚拟化相关的研发,自然也十分想了解这种技术的底层实现如何,以便从理论上分析其性能是否具有较大的价值。

分析之前,稍微VXLAN的封装格式进行简单的说明,本质上VXLAN类似于一种l2vpn技术,即将二层以太报文封装在udp报文里,从而跨越underlay L3网络,实现不同服务器或不同数据中心间的互联,其格式总结如下:

|ETH_HEADER|IP_HEADER|UDP_HEADER|VXLAN_HEADER|INNER_ETH_HEADER|INNER_IP_HEADER|…|

在采用VXLAN技术后,由于vm/docker的报文被封装于外层的UDP报文中予以传输,使得以往的TCP SEGMENT OPTIMIZATION、TCP CHECKSUM OFFLOAD等功能对于内层VM的TCP数据收发失效,较大地影响了VM间通信的性能,给最终用户带来了很差的用户体验。

下面我们来看看不同的方案都是如何实现的:

支持Overlay的交换机

目前较新的万兆交换机大多支持Overlay功能,即支持将以太报文封装在VXLAN报文中予以转发,也即将VXLAN封装功能放在专用的网络设备上,来提供确定性的线速转发性能,但这很明显增加了交换机与服务器之间的耦合,给管理维护带来了一定的复杂度。

NIC VXLAN OFFLOAD

而网卡VXLAN offload则不像Overlay交换机那样要求对组网方案进行较大的变更,而是对于网卡的能力进行了增加,与网卡驱动配合,使得网卡能够知晓VXLAN内部以太报文的位置,从而使得TSO、TCP CHECKSUM OFFLOAD这些技术能够对内部以太报文生效,从而提升TCP性能。

以TSO为例,内核封装出的TCP报文如下:

|ETH_HEADER|IP_HEADER|UDP_HEADER|VXLAN_HEADER|INNER_ETH_HEADER|INNER_IP_HEADER|INNER_TCP_HEADER|INNER_TCP_DATA_NEED_TSO|

该报文的内部TCP数据需要进行TSO处理,即切成满足网卡MTU的报文,内核驱动将报文通过DMA送给网卡后(同时要提供一些元信息,如VXLAN头位置等),网卡负责将内部的TCP报文转换为多个内部TCP报文,封装相同的外层VXLAN头后进行转发,从而减少CPU的干预,大规模提升性能。

小结

个人认为VXLAN OFFLOAD这个名字不如用VXLAN-AWARE OPTIMIZATION比较直观,但厂商为了吸引眼球,自然是需要适当的包装,可以理解。

从实现的角度来说,万兆甚至更高性能的网卡本来就比较贵,这样的优化可能并不一定导致过高的复杂度,但在实现上却能够不依赖于OVERLAY交换机更实现性能的提升,应该是做为优化OVERLAY性能的首选方案。

linux下的session leader及controlling terminal

最近在调试嵌入式下设备启动基于busybox的init程序所执行的rc.sysinit脚本时,发现该脚本执行的后台脚本,会因为没有关联到串口设备(通过ps的TTY一列可以确认) ,导致我们做了tty设备检测的程序无法顺利执行,借此机会,查阅了busybox的init代码,并对linux的session group和controlling terminal有了一定的理解。

在libc的info文档中Job Control一节,有如下几个重要的定义:

  • process group,即进程组,用管道符创建的一堆命令就行成了一个进程组。
  • session,即会话,通常从login shell登陆后的所有进程都属于一个session,即session由login shell创建,注意libc的文档还是从传统的语义来讲的,对于现代的大多数桌面环境而言(DE,如KDE/GNOME等),一个图形会话,往往会通过setsid函数起大量的session),一个session下可以管理多个process group,一个进程可以在同一个session下的不同process group中迁移,但将一个进程移到另外的会话中则只能通过setsid这个函数来创建新的会话实现。
  • session leader,即会话leader,创建会话的进程称为会话leader,从上面的描述可知,login shell一般为会话leader,首次调用setsid的进程也将成为session leader。
  • controlling terminal,即控制终端,进程的重要属性之一,由fork创建的子进程继续父进程的controlling terminal,而从setsid的libc文档中可以看出(或者用man 3 setsid查看),setsid调用后,进程将做为新的会话的会话leader,并且丢失controlling terminal属性,而其后该会话leader打开的首个tty设备,将成为该会话的controlling terminal(见附2说明);shell通常只会将controlling terminal这个属性给予一个进程组,以便由这个进程组通过终端设备获取输入或者输出信息,这组进程将称为前台任务,而未获得输入或输出等权限的进程组,在尝试向controlling terminal读取或写入数据时,将收到SIGTTIN或SIGTTOU信号,默认情况下这个信号将中止相关程序的执行。这点是合理的,因为如果不做此限制,后台程序很可能扰乱终端输出或者输入的处理。

经过上述讲述,以代码形式给出上述概念的演示。

#include "stdlib.h"
#include "errno.h"
#include "unistd.h"
#include "fcntl.h"

int main()
{
	int err, fd;
	pid_t pid;
	char *pts_name;

    pid = fork();
    if (pid != 0) {
        exit(0); // 主进程退出。
	}

    pid = setsid(); // 在子进程中创建新的会话

    // 打印从父进程继续而来的tty,注意,该tty已经不是我们的controlling terminal
	printf("before we prepare the new stdio, the child inherit its parent's stdio %s\n", ttyname(0));

    // 如下一段代码用于创建一个pseudo-terminal,将作为tty设备被新的会话leader打开,成为该会话的controlling terminal
    fd = getpt();
    pts_name = ptsname(fd);
    printf("allocated a new pts is %s\n", pts_name); 
    grantpt(fd);
    unlockpt(fd);

    // 首次open的tty设备将成为controlling terminal
	fd = open(ptsname(fd), O_RDWR);

    // 将该fd做为标准输入,在本例中意义不大
	close(STDIN_FILENO);
	dup2(fd, STDIN_FILENO);

    // 停留2分,以便我们可以通过ps检验是否获得了新的session及controlling terminal
	sleep(120);
}

编译并执行上述程序后,执行后,立即用如下命令,可以查看到新进程确实拥有了新的controlling terminal了。

ps  -u fortitude -o pid,args,tt,sess,ppid

PID COMMAND                     TT        SESS  PPID
8086 ./sessionleader             pts/5     8086     1

参考文档:

  1. libc info,可在shell下info libc,或在emacs下info libc查看
  2. http://uw714doc.sco.com/en/SDK_sysprog/_The_Controlling-Terminal_and_Pr.htmlWhen a session-leader without a controlling-terminal opens a terminal-device-file and the flag O_NOCTTY is clear on open, that terminal becomes the controlling-terminal assigned to the session-leader if the terminal is not already assigned to some session (see open(2)). When any process other than a session-leader opens a terminal-device-file, or the flag O_NOCTTY is set on open, that terminal does not become the controlling-terminal assigned to the calling-process.

shell中通过set builtin操作positional parameter

最近在调试ubuntu 14.04上的openvswitch服务时,发现openvswitch在upstart里的脚本实现频繁用到了set这样一个shell builtin,代码写的不是很好理解,后来经过查询shell相关文档,终于理解了通过set这个builtin来操作位置变量确实是一个很不错的办法。

bash的info文档中专门有一章节来介绍The Set Builtin,并且由于builtin is so complicated that it deserves its own section,但从本文的角度来看,我们认为set就是用来操作位置变量的,以下用一个简单的示例,就可以很容易的给出set的妙用了。

set -- # --这个特殊的用法来清空位置变量,即用$@来访问位置变量将得到空,除了--,set还有其他很多用法,具体可以查看shell的info文档
set "$@" ls # 用ls来初始化位置变量,ls将被赋值给$1
set "$@" -l # 将$@展开后,与-l一起重新赋值给位置变量,使得ls被赋值给$1,-l给赋值给$@
$@ # 展开位置变量,并执行,等同于执行ls -l命令

从上面的代码示例可以看出,set可以将其后的一串参数赋值给位置变量,从而方便对命令行扩充,支持更好的扩展性,并且由于@这个位置变量全集的通用性,使得在脚本嵌套时,可以方便在被执行的脚本中,对原调用脚本传过来的参数进行进一步的编辑,从而实现更为复杂的应用,比如上述openvswitch的upstart脚本,就在这点上大做了文章。

从这点感觉,bash设计的还是很精妙的,工作中大多数采用C编程,对于shell脚本的编程实践较少,后续需要继续掌握这些细节和强大之处:)