PowerDNS Recursor设定默认EDNS Client Subnet值

授权DNS服务器可以根据递归DNS服务器发送的EDNS Client Subnet(ECS)中的值,返回不同的结果。如果客户端是通过内网IP向递归DNS服务器发起查询的,而且递归DNS服务器发出的递归查询使用的公网IP跟客户端使用的公网IP不是一个地区时,就无法给客户端提供一个最优的解析结果了。

PowerDNS Recurse 4.2增加了一个设置“ecs-add-for”,可以指定哪些subnet允许作为ECS的值,对于不允许作为ECS值的subnet,将会取ecs-scope-zero-address的值作为ECS的值。所以通过这两个设置可以为内网客户端设定一个默认的ECS值。

配置文件内容大概如下:

配置生效后,对PowerDNS Recursor发出的查询进行抓包,可以发现来自内网的查询,Client Subnet的值是1.1.1.1,而有公网IP的客户端查询,将会是客户端本身的公网IP。

Nginx ssl_preread传递客户端IP

ssl_preread是基于L4的反代方案,TLS SNI握手时客户端会提供域名,所以可以让Nginx在无需完成TLS握手的情况下,就根据域名进行后端服务器的选择。简单来讲,你只需要给后端服务器配置一个SSL证书,而提供反代功能的Nginx则无需配置。文档见此:http://nginx.org/en/docs/stream/ngx_stream_ssl_preread_module.html

因为这是L4反代,所以通过“proxy_set_header”传递客户端IP的方法是行不通的。其实传递客户端IP的解决办法跟上一篇文章类似:L4(传输层)IP透明反向代理的实现(传递客户端真实IP),nginx也是实现了IP_TRANSPARENT的:https://www.nginx.com/blog/ip-transparency-direct-server-return-nginx-plus-transparent-proxy#ip-transparency,所以,在server里面加上下面一行:

然后按前一篇文章的思路,对路由表进行配置即可。

提醒一下,ssl_preread是配置在stream block下的,放在别的地方,会提示:

所以正确的nginx.conf结构应该如下:

另外,如果在stream中listen了443,http中就无法listen 443了,启动nginx会提示:

可以让http中的server listen loopback地址的另一个端口,让stream中的server proxy_pass这个loopback地址的端口,例如127.0.2.1:8443,然后通过127.0.2.1做策略路由:

然后配置策略路由:

 

L4(传输层)IP透明反向代理的实现(传递客户端真实IP)

这种需求,一般来说,会在应用层加个标记标明客户端的IP,例如说HTTP,就是添加个请求头的事情。但并不是所有服务器程序、协议你都能这样下手。所以能不能在不对协议和服务器程序本身做任何改动的情况下,传递客户端的IP呢?

最直接的方法应该就是,把L3的源IP改掉,没对协议进行任何改动,对上层完全透明。先来看看改了L3的source IP会出现什么问题:

上图中的A是个L4负载均衡,Router会把来自Internet的客户端请求转发给A,A再根据策略转发到B、C或D。

假设这时A选择了B,A把请求转发给B时,把L3的源IP改成了客户端的IP,那B收到数据包后,根据B系统上的路由表,回复的包会直接经switch交给router送出Internet,也就是说响应的内容不会经过A送回给客户端。听起来好像没什么问题,还省得A耗费资源转发响应内容,不过TCP需要三次握手,客户端跟A握手成功后,A还需要跟B握手:A这时以客户端的IP向B发出SYN,B收到的SYN后,响应一个目的地IP是客户端的SYN ACK,这个SYN ACK是直接由router送出Internet的,根本不会送到A,所以A跟B是无法成功握手的,那客户端的请求也自然无法送达B。

UDP虽然不用握手,但A转发到B用的UDP源端口不一定跟客户端到A的一样,所以客户端不一定能正常接收到B响应的UDP包。要解决这个问题,就要做到请求从哪转发过来的,响应就得送回哪去,既然是从A来的,就送回给A。

A虽然改了送往B的包的源IP,但即使B把响应送回A了,默认情况下,A收到数据包的目的地IP不是系统已绑定的IP时,也就是非送往本地的数据包,kernel只会选择转发或者丢弃,这种情况下,A跟B也是无法握手成功的,所以,这里还要解决的就是,让A对来自B的非本地数据包,当作本地数据包处理。

到这里,实现这个L4 IP透明代理要解决的问题就很清晰了:

  1. 反向代理服务器修改L3源IP
  2. 被代理服务器把响应转发回给反向代理服务器
  3. 反向代理服务器处理来自被代理服务器的非本地数据包

1. 修改L3源IP

Linux的kernel,有一个IP_TRANSPARENT的选项:

简单来说,这个选项允许应用程序的socket,绑定一个操作系统没有绑定的IP,并且以那个IP对外通讯。

所以,A的反向代理程序,通过这个选项让socket绑定客户端的IP,就能以客户端的IP与B进行通讯:

在A上编译运行上面的源码,抓包可以看到A在以192.168.2.2作为源IP向192.168.1.20:80发送SYN,ss或者netstat也可以看到程序的local IP是192.168.2.2,而A系统本身是没有绑定192.168.2.2的:

目前来说A还是无法与B成功握手的,抓包也只会抓到A一直在向B发送SYN,晚一点还会输出”connect(): Connection timed out”。

2. 让B把响应送回给A

在B上抓包,可以看到B会收到来自2.2的SYN,也会响应SYN ACK,但是会收到2.2的RST:

如何让B辨别来自A的包,并把响应送回A呢?

2.1 选择一:根据B的源IP和端口做策略路由

给B分配多个IP,例如默认用1.19出网,1.20专门给A访问用,可以用这种方法。如果只有一个IP,不建议用这种方法,因为这样B会把所有从被代理的端口发出的数据包都交给A,如果请求不是转发自A的,就无法正常处理。

因为A向B发出的请求的目的IP是1.20,所以B发出响应的源IP一定是1.20。而不是对A的响应,B默认使用1.19,不会发送给A。

2.2 选择二:通过mark做策略路由

例如可以通过mac地址识别来自A的包,并且做个标记:

3. 让A处理非本地数据包

第二步的策略路由做好后,A上抓包,可以看到B的SYN ACK送回来了,但是A没有响应ACK:

因为A没有绑定192.168.2.2这个IP,前面提到过kernel是不会对非本地的数据包做出响应的。

哪些目的地IP作为要本地IP处理,是可以在路由表上指定的,可以把0.0.0.0/0,也就是任何IP都视为本地IP处理,当然不能加在默认路由表上,这样就无法对外通讯了,需要做策略路由。

上面有一个路由规则:“local 0.0.0.0/0”,意思是任何IP都作为本地IP处理,注意规则是加在表60的,符合条件的数据包,才会应用这个表。

第三步的规则做好后,再次运行ip_transparent,就可以看到立刻输出“connection established”了:

4. IP透明反向代理

上面的ip_transparent只是测试A和B之间的握手,并不能作为客户端与B之间通讯的代理,这里用Go实现了一个比较简单的反向代理:

上面核心的部分是函数func createTransparentSocket(client net.Conn, destination string) (net.Conn, error),简单来说,就是获取客户端的IP,然后通过bind()绑定为socket的本地IP,再向B发出构建连接的请求。

通过第一个运行参数指定本地监听的IP和端口,第二个运行参数指定被代理服务器的IP和端口:

从client向A发出请求:

B上查看Nginx的访问日志,可以看到记录的IP是client的:

A上的反向代理程序的输出:

这个程序只实现了IPv4 TCP的IP透明反向代理,UDP以及IPv6的实现也同理。

除了反向代理,其实正向代理也是可以通过IP_TRANSPARENT实现,配合iptables的TPROXY模块即可发挥作用,本文的目的只是反向代理,正向代理具体细节就不在本文介绍了。

双路超线程物理服务器的QEMU CPU affinity调整

首先运行 virsh capabilities 查看物理机物理核心及其线程与逻辑处理器(线程)的关系:

其中的socket_id代表CPU的槽位,core_id代表CPU物理核心,siblings表示哪些逻辑处理器(线程)是属于同一个物理核心的。

例如id为0以及8的逻辑处理器,都属于同一个CPU槽位且属于同一个物理核心。

在这台物理机上建立的虚拟机,想得到最佳的性能,首先不要跨CPU插槽,其次,不要让多个虚拟机CPU共享同一个物理核心。

根据上面的信息,4核心8线程的虚拟机CPU亲和度配置如下:

其中vcpu 0和1在虚拟机中对应一个物理核心的两个线程,将其亲和度设定到逻辑处理器0和8,后面vcpu 2-7,同理。

启动虚拟机,通过 virsh vcpupin 进行验证:

到虚拟机中查看/proc/cpuinfo:

上面输出的信息可以确定设定无误,因为第一、二个逻辑核心对应了第一个物理核心,后面第三到第八个也同理。如果输出的core id与cputune处设定时所期望的顺序不对,调整cputune中的参数即可。

一款鲜为人知的杰出VPN方案 —— ACCEL-PPP

因PPTP简单、方便,无特别需要,平时需要用到VPN时一般都会首选PPTP。

数月前使用PPTP的过程中发现,Linux下的PoPToP方案极限速率仅有20 Mbps左右,而Windows下“网络策略和访问服务”提供的PPTP上限速率则高些,能达到70 – 80 Mbps。

当时了解到一款名为ACCEL-PPP的方案,尝试了一番,效果不负其名——ACCEL。

但今天发现,此方案的中文介绍、资料近乎无(英文介绍也不多),遂撰写此文,欲让更多人了解到此杰作。

 

服务器配置:

  • CPU: Intel Xeon L5520 *2
  • RAM: 48GB
  • 网络适配器: Intel 千兆网络适配器
  • 硬盘: 256GB SSD

 

网络环境:

  • ISP: 中国电信
  • 公网速率: 100 Mbps
  • 路由器: EdgeRouter X SFP (with hwnat, ipsec offload enable)

 

本文部分词语定义:

极限速率 —— 当数据传输速率在30秒内不能稳定提升,则视当前已达极限速率

 

PoPToP

PoPToP就是Linux更新源所提供的的pptpd。

达到极限速率后,使用top看到服务器上pptpd进程的CPU使用率仅有25-30%:

图1 —— PoPToP极限速率
图2 —— pptpd进程CPU使用率1
图3 —— pptpd进程CPU使用率2

速率已达极限,而资源使用却未达极限,明显问题在于PoPToP方案的资源利用率底下。

ACCEL-PPP PPTP

图4 —— ACCEL-PPP PPTP极限速率
图5 —— ACCEL-PPP PPTP CPU使用率

ACCEL-PPP的PPTP极限速率能达到50 Mbps多,使用top查看CPU使用率,可以发现大部分都被ksoftirqd占用,且使用率几乎可达一个核心的极限。

虽然未能达到我公网带宽的极限速率,但能充分利用资源达到如此高的速率已经很不错了。

ACCEL-PPP的文档没对其工作原理作出介绍,我也未深究其源码,暂不能解释其“高效”、以及受限于其“极限”的原因。

ACCEL-PPP L2TP与xl2tpd的性能对比

使用StrongSwan,IPSec PSK

xl2tpd

图6 —— xl2tpd效率1
图7 —— xl2tpd效率2

ACCEL-PPP L2TP

图8 —— ACCEL-PPP L2TP效率1
图9 —— ACCEL-PPP效率2

孰优孰劣,明显至极。

ACCEL-PPP的安装

本节以Debian 9为例,安装ACCEL-PPP。

安装编译器,cmake:

取得ACCEL-PPP源码(编写本文时,SourceForge的ACCEL-PPP 1.11.2的源码扩展名虽为.tar.bz2,但实际上只由tar打包,并无使用bzip压缩)

编译ACCEL-PPP:

ACCEL-PPP编译前需要使用cmake对所需的功能进行设置,支持的选项有以下:

  • -DBUILD_PPTP_DRIVER=TRUE —— 本选项用于编译PPTP内核模块,内核版本>= 2.6.37已内置PPTP模块,无需启用该选项。
  • -DBUILD_IPOE_DRIVER=TRUE —— 本选项用于编译IPoE内核模块。IPoE共享接口模式或VLAN监控下需要此模块。
  • -DBUILD_VLAN_MON_DRIVER=TRUE —— 编译VLAN监控模块。
  • -DKDIR=/usr/src/linux —— 若需要构建PPTP内核模块,则需要使用本选项指定内核源码目录。
  • -DCMAKE_INSTALL_PREFIX=/some/location —— 指定ACCEL-PPP安装目录,默认为/usr/local。
  • -DCMAKE_BUILD_TYPE=Debug —— 选择编译为DEBUG版本以用于调试抑或为RELEASE版本。
  • -DLOG_PGSQL=TRUE —— 编译log_pgsql模块用于使用PostreSQL数据库记录日志。
  • -DRADIUS=FALSE —— 关闭radius模块。
  • -DNETSNMP=TRUE —— 启用SNMP模块。
  • -DLUA=TRUE —— 启用LUA支持(仅用于IPoE)。
  • -DSHAPE=TRUE —— 启用流量控制功能。

本文编译为Release版本,关闭Radius,PGSQL,流量控制等功能。

重命名配置文件:

ACCEL-PPP的配置

从man可获取到accel-ppp配置文件的完整文档:

本小节仅对部分配置项进行说明。

[modules]

本部分定义了ACCEL-PPP需要启用的功能。

例如

则表示启用文件日志,pptp,l2tp等功能。

[core]

用于配置核心模块的参数。

当前版本支持的有以下两项:

[ppp]

ACCEL-PPP内置的ppp模块参数配置。

具体略。

[dns]

[client-ip-range]

[pptp]

[l2tp]

[chap-secrets]

[ip-pool]

示例配置文件

运行并使用ACCEL-PPP

客户端的使用方式与平常无差异,使用IPSec的,按照正常方式对IPSec进行配置即可。

总结

ACCEL-PPP网站有有言:

ACCEL-PPP是一个高性能的Linux VPN服务器应用。

致力于聚合各种热门的VPN技术。

单VPN上,对比如今广泛使用的PoPToP与xl2tp,ACCEL-PPP的优势非常明显,但如此杰作却鲜为人知,实为作者感到不平。

若你尝试ACCEL-PPP后,觉得好用,记得推荐给你身边的朋友!

ACCEL-PPP官网:http://accel-ppp.org/

SourceForge项目:https://sourceforge.net/projects/accel-ppp/

社区:http://accel-ppp.org/forum/