1、TPROXY是什么

你可能听说过TPROXY,它通常配合负载均衡软件HAPrxoy或者缓存软件Squid使用。
在所有"Proxy"类型的应用中都一个共同的问题,就是后端的目标服务器上看到的连接的Source IP都不再是用户原始的IP,而是前端的"Proxy"服务器的IP。

拿HAProxy举例来说,假设你有3台后端Web服务器提供服务,前端使用HAProxy作为负载均衡设备。所有用户的HTTP访问会先到达HAProxy,HAProxy作为代理,将这些请求按照指定的负载均衡规则分发到后边的3台Web服务器上。这个操作本身没有任何问题,因为HAProxy就应该是这么工作的。但是对于某些对于用户的访问IP有限制的敏感应用,问题来了: 后端服务器上的ACL无法限制哪些IP可以访问,因为在它看来,所有连接的SOURCE IP都是HAProxy的IP。

这就是为什么TPROXY产生的原因,最早TPROXY是作为Linux内核的一个patch,从2.6.28以后TPRXOY已经进入官方内核。TPRXOY允许你"模仿"用户的访问IP,就像负载均衡设备不存在一样,当HTTP请求到达后端的Web服务器时,在后端服务器上用netstat查看连接的时候,看到连接的SOURCE IP就是用户的真实IP,而不是haproxy的IP。TPROXY名字中的T表示的就是transparent(透明)。

TPROXY主要功能如下:

1.重定向一部分经过路由选择的流量到本地路由进程(类似NAT中的REDIRECT)
2.使用非本地IP作为SOURCE IP初始化连接
3.无需iptables参与,在非本地IP上起监听

如果想要了解TPROXY的具体工作原理,请参考作者本人写的PPT,介绍的比较详细了:

http://people.netfilter.org/hidden/nfws/nfws-2008-tproxy_slides.pdf

2、Centos 7系统,iptables透明网桥实现

在2.6.28以后的内核中,TPROXY已经是官方内核的一部分了。CentOS 7 默认使用的是3.10内核,所以不需要额外添加。
在一切开始之前,需要安装两个包:bridge-utils,ebtables

#yum install bridge-utils ebtables

首先建立网桥:(使用bridge) 示例 桥接eth0 与 eth1 网口

/sbin/modprobe bridge
/usr/sbin/brctl addbr br0
/sbin/ifup eth0
/sbin/ifup eth1
/usr/sbin/brctl addif br0 eth0
/usr/sbin/brctl addif br0 eth1
/sbin/ip link set br0 up

在网桥上设置IP地址,示例

# ifconfig br0 192.168.10.90 netmask 255.255.255.0

这样就完成了网桥的配置工作。但这时配置没有保存下来,如果要重启有效,需要添加网桥的配置文件,可以添加如下文件:

/etc/sysconfig/network-scripts/ifcfg-br0

DEVICE=br0
TYPE=Bridge
BOOTPROTO=static
ONBOOT=yes
IPADDR=192.168.10.90
NETMASK=255.255.255.0

/etc/sysconfig/network-scripts/ifcfg-eth0

TYPE="Ethernet"
BOOTPROTO="none"
NAME="eth0"
DEVICE="eth0"
ONBOOT="yes"
BRIDGE="br0"


/etc/sysconfig/network-scripts/ifcfg-eth1

TYPE="Ethernet"
BOOTPROTO="none"
NAME="eth1"
DEVICE="eth1"
ONBOOT="yes"
BRIDGE="br0"

这样,机器重启以后也会保留已配置好的网桥。

2、启用bridge-nf

由于网桥工作于数据链路层,在iptables没有开启 bridge-nf时,数据会直接经过网桥转发,结果就是对FORWARD的设置失效;
centos默认不开启 bridge-nf

启动bridge-nf方式:编辑文件vim /etc/sysctl.conf 添加:

net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-arptables = 1

然后执行命令:

# sysctl -p

这时候,可能会出现

# sysctl -p
sysctl: cannot stat /proc/sys/net/bridge/bridge-nf-call-ip6tables: No such file or directory
sysctl: cannot stat /proc/sys/net/bridge/bridge-nf-call-iptables: No such file or directory

是由于CentOS 7 默认没有加载 br_netfilter 模块,我们加载它:

# modprobe br_netfilter

为了让系统启动的时候,就自动加载此模块,我们得加一个模块启动

vim /etc/sysconfig/modules/br_netfilter.modules

#!/bin/sh 
/sbin/modinfo -F filename br_netfilter > /dev/null 2>&1 
if [ $? -eq 0 ]; then 
    /sbin/modprobe br_netfilter 
fi

文件保存完成,记得加上可执行权限,这样就将br_netfilter加入了开机启动。

# chmod 755 /etc/sysconfig/modules/br_netfilter.modules

3、添加iptables,ebtables 规则

在添加规则之前,先确认一下/etc/sysctl.conf 文件内容是否包含如下项:

net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-arptables = 1

net.ipv4.ip_forward = 1
net.ipv4.ip_nonlocal_bind = 1
net.ipv4.conf.default.rp_filter = 0
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.br0.rp_filter = 0
net.ipv4.conf.eth0.rp_filter = 0
net.ipv4.conf.eth1.rp_filter = 0

确认无误后,执行

# sysctl -p

让配置生效。

iptables 添加如下规则:

/sbin/iptables -F
/sbin/iptables -F -t mangle
/sbin/iptables -t mangle -N DIVERT
/sbin/iptables -t mangle -A PREROUTING -p tcp -m socket -j DIVERT
/sbin/iptables -t mangle -A DIVERT -j MARK --set-mark 1
/sbin/iptables -t mangle -A DIVERT -j ACCEPT

然后添加路由,在lo介面上建立代号为100的路由表给iptables比对fwmark

/sbin/ip -f inet rule add fwmark 1 lookup 100
/sbin/ip -f inet route add local default dev lo table 100

以下,我们以代理SSH的22端口为例,添加规则

#首先添加一台对本机访问22端口的通过规则,此例本机连接网卡是eth2
iptables -t mangle -A PREROUTING -i eth2 -p tcp --dport 22 -j ACCEPT

#将桥接模式的封包移到路由模式,此例eth1是数据流入口(接客户端),eth0是数据流出口(接服务端)
ebtables -t broute -A BROUTING -i eth0 -p ipv4 --ip-proto tcp --ip-sport 22 -j redirect --redirect-target DROP
ebtables -t broute -A BROUTING -i eth1 -p ipv4 --ip-proto tcp --ip-dport 22 -j redirect --redirect-target DROP

#将22端口的流量重定向到TCP端口9876
iptables -t mangle -A PREROUTING -p tcp --dport 22 -j TPROXY --tproxy-mark 0x1/0x1 --on-port 9876

经过如上配置,网桥上的透明代理设置基本完成了。

4、运行代理转发程序

socket有一个IP_TRANSPARENT选项,其含义就是可以使一个服务器程序侦听所有的IP地址,哪怕不是本机的IP地址,这个特性在实现透明代理服务器时十分有用,而其使用也很简单:

int opt =1;
setsockopt(server_socket,SOL_IP, IP_TRANSPARENT,&opt,sizeof(opt));

此时,自己编写一个socket代理程序,开启如上参数,监听在 9876 端口上,就可以完成透明代理的功能了。当然,我们也可以使用HAProxy这个软件实现透明代理的功能,只要在它的GROUP配置中加入如下一行;

source 0.0.0.0 usrsrc clientip

就可以达到一样的效果。此处就不再赘述,可参考HAProxy的部署文章。

0.接触Linux恐怕对SSH再熟悉不过了,还有scp,sftp各种方便的功能,一般的使用都需要ip:port(如果不是默认22的话),但有些情况比较特殊,就是想连接一台内网主机(比如公司内网,当然你肯定做不了Port Forwarding,除非你想在公司防火墙上拆个洞)。稍懂一点网络的童鞋会明白,Internet上去主动连接一台内网是不可能的,一般的解决方案分两种,一种是端口映射(Port Forwarding),将内网主机的某个端口Open出防火墙,相当于两个外网主机通信;另一种是内网主机主动连接到外网主机,又被称作反向连接(Reverse Connection),这样NAT路由/防火墙就会在内网主机和外网主机之间建立映射,自然可以相互通信了。但是,这种映射是NAT路由自动维持的,不会持续下去,如果连接断开或者网络不稳定都会导致通信失败,这时内网主机需要再次主动连接到外网主机,建立连接。

1.理论的介绍完了,下面实际操作:
A要控制B

A主机:外网,ip:123.123.123.123,sshd端口:2221

B主机:内网,sshd端口:2223

无论是外网主机A,还是内网主机B都需要跑ssh daemon

1.1.首先在B上执行

$ ssh -NfR 1234:localhost:2223 user1@123.123.123.123 -p2221

这句话的意思是将A主机的1234端口和B主机的2223端口绑定,相当于远程端口映射(Remote Port Forwarding)。

这里每次需要输入A主机user1的登陆密码,后面会讲到解决办法。

这个时候,B主机可以将端口转发到B主机内网可达的任意一个IP地址,只要将上面的localhost改成相应的IP地址即可。比如,B主机想将端口转发到内网的一个IP:192.168.1.100,port:3389,规则可以如下写:

$ ssh -NfR 1234:192.168.1.100:3389 user1@123.123.123.123 -p2221


1.2.这时在A主机上sshd会listen本地1234端口

$ ss -ant

State      Recv-Q Send-Q        Local Address:Port          Peer Address:Port
LISTEN     0      128               127.0.0.1:1234                     *:*

1.3.像平时一样连接到A主机的1234端口就可以控制内网B主机了

$ ssh localhost -p1234

1.4.如上,这个时候只能在A主机的本地连接1234端口,因为监听在了127.0.0.1上,这个时候如果想在另外一台机器访问A主机而连接到B主机,一般有两种方法解决这个问题。

a)、使用iptables进行本地端口转发

b)、修改sshd.conf让ssh的远程端口转发可以监听在除127.0.0.1以外的IP上

这里我们采用第一种方法作示例:

首先执行:

sysctl -w net.ipv4.conf.eth0.route_localnet=1

(用本地真实的网卡代替eth0)
默认情况下,该值为0,它指示内核不要路由到127.0.0.0/8的外部流量。这只是为了安全,因为这样的流量不正常。

然后添加iptables规则:

iptables -t nat -I PREROUTING -p tcp -d 123.123.123.123 --dport 1234 -j DNAT --to-destination 127.0.0.1:1234

这样,只要访问123.123.123.123的1234端口就自动连接上B主机的2223端口了。

2.一开始提到,这种反向连接(Reverse Connection)不稳定,可能随时断开,需要内网主机B再次向外网A发起连接,这时需要个“朋友”帮你在内网B主机执行这条命令。它就是Autossh。

在此之前还要解决之前的一个问题,那就是每次内网主机B连接外网主机A时都需要输入密码,这个问题ssh本身是提供另外一种验证方式——通过密钥验证用户身份,实现自动登录。

2.1.在内网B主机上生产公钥和私钥

$ ssh-keygen
...(一直按Enter,最后在~/.ssh/下生成密钥)


2.2.复制B主机上生成的公钥到外网A主机上,并将内容加入到~/.ssh/authorized_keys中

$ ssh-copy-id user1@123.123.123.123


2.3.再来看看Autossh的用法

$ autossh -M 5678 -NR 1234:localhost:2223 user1@123.123.123.123 -p2221

比之前的命令添加的一个-M 5678参数,负责通过5678端口监视连接状态,连接有问题时就会自动重连,去掉了一个-f参数,因为autossh本身就会在background运行。

3.终极方案:当重启内网B主机,谁来自动Autossh呢,加入daemon吧
以daemon方式执行,相当于root去执行autossh, ssh,这时刚才普通用户目录下的.ssh/authorized_keys文件会不起效。有两种办法解决,一种是用autossh的参数指定.ssh路径;另外一种是以普通用户身份执行daemon,下面是第二种方式。

/bin/su -c '/usr/bin/autossh -M 5678 -NR 1234:localhost:2223 user1@123.123.123.123 -p2221' - user1

autossh还有很多参数,用来设置重连间隔等等。

将上面命令放入下面各启动方式中,根据自己系统自己配置:

SysV:/etc/inid.d/autossh

Upstart: /etc/init/autossh.conf

systemd: /usr/lib/systemd/system/autossh.service

P.S.

1.家里是ADSL的话,用DDNS,解决ip问题

2.外网有路由的可设下端口映射

3.虽然有密钥和密码保护,但还请小心使用

最近在一台服务器上处理包过滤的程序,服务器上集成了6块 Intel 82574L 的千兆网卡,这块网卡出来的比较早,性能也比较均衡,很多服务器设备上也有集成。包过滤程序使用了pf_ring作为抓包的模块,操作系统使用的是CentOS 6.9,内核是2.6.32版本。在测试的时候发现几次很异常的情况,抓包一段时间以后(时长不定),被抓包的网卡上就再也没有流量上来了。使用ifconfig -a 查看

eth1  Link encap:Ethernet  HWaddr 00:0C:29:F6:7F:E1  
      inet addr:192.168.2.192  Bcast:192.168.2.255  Mask:255.255.255.0
      inet6 addr: fe80::20c:29ff:fef6:7fe1/64 Scope:Link
      UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
      RX packets:1738132 errors:0 dropped:6356865 overruns:0 frame:0
      TX packets:1328354 errors:0 dropped:0 overruns:0 carrier:0
      collisions:0 txqueuelen:1000 
      RX bytes:840530707 (801.5 MiB)  TX bytes:1706701844 (1.5 GiB)

发现RX dropped 非常的高,自没有流量上来以后,所有的包都被丢弃了。

再查看一下ethtool -S eth1

 NIC statistics:
     rx_packets: 1740086
     tx_packets: 327205
     rx_bytes: 847640005
     tx_bytes: 1652632934
     rx_broadcast: 0
     tx_broadcast: 0
     rx_multicast: 0
     tx_multicast: 0
     rx_errors: 0
     tx_errors: 0
     tx_dropped: 0
     multicast: 0
     collisions: 0
     rx_length_errors: 0
     rx_over_errors: 0
     rx_crc_errors: 0
     rx_frame_errors: 0
     rx_no_buffer_count: 0
     rx_missed_errors: 6356865 
     tx_aborted_errors: 0
     tx_carrier_errors: 0
     tx_fifo_errors: 0
     tx_heartbeat_errors: 0
     tx_window_errors: 0
     tx_abort_late_coll: 0
     tx_deferred_ok: 0
     tx_single_coll_ok: 0
     tx_multi_coll_ok: 0
     tx_timeout_count: 0
     tx_restart_queue: 0
     rx_long_length_errors: 0
     rx_short_length_errors: 0
     rx_align_errors: 0
     tx_tcp_seg_good: 91453
     tx_tcp_seg_failed: 0
     rx_flow_control_xon: 0
     rx_flow_control_xoff: 0
     tx_flow_control_xon: 0
     tx_flow_control_xoff: 0
     rx_long_byte_count: 847640005
     rx_csum_offload_good: 822676
     rx_csum_offload_errors: 0
     alloc_rx_buff_failed: 0
     tx_smbus: 0
     rx_smbus: 0
     dropped_smbus: 0

里面的rx_missed_errors很高。

出现问题的时候,查询了其它网卡,数据都是正常的。只有抓包的那个网卡出现问题。原以为是pf_ring的驱动模块出现了问题,把pfring.ko卸载以后,还是老样子。暂时没有头绪,就放狗去搜。找到几篇讨论的文章:

Problem with Intel 82579V and 82574L NIC.
Intel 82574L Gigabit network card - issues and resolution

82574 adapters are known to have a hardware problem that requires a special fix in the driver. I am unsure if the fix for this has made it into CentOS 6.1 or 6.2 but the kmod-e1000e from ELrepo does have the fix and can be installed from there.

这里讲到,在旧内核里面82574L的驱动是有问题的,是需要用一个特殊的驱动去修复的。后来查了一些其它相关资料。内核版本在2.6.35以后才修复了这个驱动,但是CentOS 6 的内核是2.6.32,所以没有办法解决。只有升级成ELrepo 提供的第三方驱动去解决了。

ELrepo 提供的e1000e驱动地址:

升级完成以后,持续测试了两天,都没有再出现断流的问题了。应该是解决了。

最近把小主机升级到了ESXI6.5,debian 9也已经出来了。所以就配了个虚拟机装了最新的debian 9,安装一切无话。安装好以后配了nginx等相关服务。系统装好后测试一切正常,就放着不动了。

等第二天,发现博客首页上不去了,去ping debian 9的IP地址也ping不通了,去ESXI管理界面界面上一看,vCPU占用100%,感觉情况不妙。打开系统终端控制台,发现无论输入什么都没有反映,系统已经挂了的感觉,按crtl+alt+ins也没反应。最后只能强制重启了,当时以为是意外。遂没有管它。当再过了一天,又出现同样的情况了,就说明不是意外。查看了系统的相关日志,看不出有什么异常的地方,想起来装过vmware tools ,当即把它卸载了再观察。可是再过了一天,情况还是老样子,又死了。所以,应该和vmware tools没什么关系。

没有头绪,只能放狗去搜,找到一篇老外的讨论:
VM becomes unresponsive, some vCPUs are 100% utilized

里面有讲到应该和网卡类型和 vmxnet3 有关

All of the VMs use the vmxnet3 vNIC. On one occasion, after resetting the VM, I looked at the kernel log and it displayed some vmxnet3 messages which led me to believe the problem may be caused by http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=2005717. I moved some VMs to the host running ESXi v5.0 and the problem still occurred, so that theory was debunked. Also, this problem has happened quite a few times but those vmxnet3 kernel messages only showed up on one occasion.

然后,我就把网卡类型改成了:e1000e,就没有再出过这个问题了,在这里记录一下备查。使用vmxnet3时,在系统里看到的网卡速率是10000M,使用e1000e类型就只有1000M。ESXI 6.5用debian 9 时,网卡已经没有e1000的类型选择了。

centos5/redhat5 自带的GCC已经很低了,一般是4.1左右,但如果安装一些软件需求高版本的gcc就比较麻烦了。

除了下源码编译安装外,这里介绍通过YUM源来直接安装

wget http://people.centos.org/tru/devtools-2/devtools-2.repo -O /etc/yum.repos.d/devtools-2.repo
yum install devtoolset-2-gcc devtoolset-2-binutils devtoolset-2-gcc-c++
ln -s /opt/rh/devtoolset-2/root/usr/bin/* /usr/local/bin/

hash -r
gcc --version

大功告成!