当前有这样一个场景,客户A到服务器B去GET一个http请求,中间要过一个防火墙,防火墙要监控GET请求,如果发现会进行截断。

这里有2个要求:

1、客户端A如果抓包,发现自己连接的服务器是B的ip

2、服务器端如果转,发现连接自己的是客户端A

3、如果有中转服务器,不能承受太大的压力


如此一来,A就不能使用普通的***服务到达B,因为通过普通的***服务后,服务器B看见的是×××服务器的ip地址而不是客户端A的ip地址。


这里考虑用ip隧道来实现,只将用户的上行请求通过ip隧道送到防火墙外面,这样对中转设备的压力也不大,因为一般的上行请求量很小。

注:这里只是验证流程是否能走通,所以没有对ip隧道进行加密。


wKiom1c-v8rSOpnmAACEmNajaVc971.jpg


现在3台服务器,10.0.2.244(客户端A,公网地址103.250.226.148),10.0.2.201(中转机A),10.0.3.29(中转机B,公网地址无所谓),服务器B(一个是百度服务器,61.135.169.121,另一个是10.0.2.26)

其中,中转机A和中转机B分别在防火墙2侧。


对百度服务器的ip隧道

我们对10.0.2.244做操作

route add 61.135.169.121 netmask 0.0.0.0 gw 10.0.2.201

将百度地址指向中转机A

route add 10.0.2.26 netmask 0.0.0.0 gw 10.0.2.201

将10.0.2.26指向中转机A


对10.0.2.201做操作

ip tunnel add ethn mode ipip local 10.0.2.201 remote 10.0.3.29

ifconfig ethn 40.200.20.70

建立一条ip隧道,其中40.200.20.70是胡乱写的,反正也只是内部用。


route add -host 60.70.100.100 dev ethn

给隧道建立路由,60.70.100.100是隧道另一边的假ip


route add -host 61.135.169.121 dev ethn

将百度地址指向隧道

route add -host 10.0.2.26 dev ethn

将10.0.2.26指向隧道


对10.0.3.29做操作

ip tunnel add ethm mode ipip local 10.0.3.29 remote 10.0.2.201

ifconfig ethm 60.70.100.100

建立一条ip隧道,其中60.70.100.100是胡乱写的,反正也只是内部用。


route add -host 40.200.20.70 dev ethm

隧道路由,40.200.20.70是另一边的假ip


route add -host 103.250.226.148 dev ethm

route add -host 10.0.2.244 dev ethm

这里有点奇怪,如果我不把客户端A的ip配置进去,就不进行转发,有可能是我当时没有配置

/etc/sysctl.conf里面net.ipv4.ip_forward = 1的原因,如果没有配置,那么将源ip指向路由,就可以转发数据包了,具体逻辑我也没弄明白,路由原理还需要学习。


那么现在我们做个操作

在中转机A上ping 60.70.100.100,得到了应答,说明ip隧道建立成功。


在客户端10.0.2.244上面

wget http://61.135.169.121/ --header='Host: www.baidu.com' -O 1.txt --bind-address='103.250.226.148'


wget http://10.0.2.26/200.php --header='Host: www.baidu.com' -O 1.txt --bind-address='10.0.2.244'


都可以收到应答。


通过抓包可以发现,上行包,都从网关10.0.2.201出去了,而下行包,直接由网卡收取。

通过这样的配置,我们顺利的实现了上行下行分离的功能。


这里有2个问题,

为什么不直接将客户端A配置为中转机A,因为我发现,如果机器上有2块网卡,监听的时候绑定0.0.0.0,理论上可以收到所有网卡的数据,但是实际发现,如果同一个五元组,无论是tcp数据还是udp数据,从网卡A发出去的数据,如果应答从网卡B回来,那么协议栈是收不到包的。如果可能如下数据(源ip端口,目的ip端口,进程号,协议号)都相同,那么第一次从哪个网卡出去,以后的数据都归哪个网卡负责。因此我们只好独立一台中转机A。


wget http://61.135.169.121/ --header='Host: www.baidu.com' -O 1.txt --bind-address='103.250.226.148'

这里在wget的时候,将ip地址绑定到公网的ip上,然后通过路由

route add 61.135.169.121 netmask 0.0.0.0 gw 10.0.2.201

将数据包直接扔给中转机A,未来收到原站的数据,还是收到到公网网卡上,这样10.0.2.244的内网网卡就不会使用到了,也避免了来回报文不在一个网卡上,导致收不到包的问题。


第二,这个ip隧道是没有加密的,想加密还需要使用ipsec来实现,这里没有具体调研,另外路由器的L2L功能可以实现加密的ip隧道,使用路由器比服务器更加经济实惠。


拓展

如果不使用第三方工具,可以考虑修改linux内核,对上行数据进行简单的加密解密就可以了。

具体代码在net/ipv4/ipip.c里面,具体函数发包是ipip_tunnel_xmit,收包是ipip_rcv。

可以参考

http://blog.chinaunix.net/uid-26321024-id-2954317.html

1.  注册一个伪设备

    static const struct net_device_ops ipip_netdev_ops = {

        .ndo_uninit     = ipip_tunnel_uninit,

        .ndo_start_xmit = ipip_tunnel_xmit,

        .ndo_do_ioctl   = ipip_tunnel_ioctl,

        .ndo_change_mtu = ipip_tunnel_change_mtu,

        

}; 

tunnel 的peer两端ip地址都保存在ip_tunnel->param中(ip_tunnel_param)

2. 发包

ipip_tunnel_xmit:

1)tunnel之后的包不能再路由到本tunnel

2) 可能会重新分配skb

3) 直接在原来ip头前面加一个ip头,恒为20字节。挪动network_header和transport_header。

4)做完之后call ip_local_out,之前会nf_reset。表示会过两次ip协议栈,过两次netfilter chain

3.收包

ip_rcv -> ip_local_deliver_finish中会找L4层handler,处理rcv

对于ipip,走到 ipip_rcv (注册在xfrm_tunnel中的ipip_handler),ipip_init中会注册

1. 挪动network_header和transport_header把开头20个字节偏掉,ipip头设为mac_header,把skb->dev指向tunnel->dev

2. nf_reset。skb->protocol(L2协议)设为ETH_P_IP 

3. netif_rx->入backlog, 注册软中断NET_RX->net_rx_action->process_backlog

  ->__netif_receive_skb->ip_rcv, 重走ip协议栈,重走所有netfilter chain

4. ip tunnel 注册, 走的ioctl

 新建一个ipip tunnel. 可给定local ip. remote ip. dev

譬如ip tunnel add tunnel mode ipip  remote xx local yy dev zz

其中remote, local, dev都可不设置,类似ipip自己初始化时创建的设备tunnel0

根据既不设置remote和local, 只设置remote,只设置local,同时设置remote和local分了4个hash表。

struct ipip_net {

        struct ip_tunnel *tunnels_r_l[HASH_SIZE];

        struct ip_tunnel *tunnels_r[HASH_SIZE];

        struct ip_tunnel *tunnels_l[HASH_SIZE];

        struct ip_tunnel *tunnels_wc[1];

        struct ip_tunnel **tunnels[4];

        

        struct net_device *fb_tunnel_dev;

};   

用户ip tunnel设定的选项保存在ip_tunnel.param中,即ip_tunnel_param

指定的dev设备作为路由表查找的指定出口设备,类似socket的bind_output_dev。如果不指定,就是让

路由抉择选择哪个出口出去。