Skip to main content

 路由器设置 > 新闻资讯 >

OpenVPN的按需连接实现

2013-12-09 00:28 浏览:

万恶的心跳!只是证明自己还活着...为何不能到有事情来的时候再做,没事情时就休息呢?何必一直保持心跳呢?
上帝按照自己的形象,造出了人,人按照自己的喜好,造出了计算机,计算机也都有心跳。操作系统靠时钟中断这种心跳来推进机器的时间,然而后来Linux实现了NOHZ,即没有事情的时候,不再无谓地触发时钟中断,而是彻底halt,有事请来的时候,其它的中断会将机器唤醒,继续心跳。这种nohz机制节省了资源,最小化了bug触发率。
IPSec VPN完全是按需连接的,隧道模式下,如果数据包过来,匹配到了加密策略,那么就看加密隧道有没有建立,如果还没有建立,则触发IKE协商,如果已经建立了,则直接通过隧道传输数据,当然IPSec也可以使用万恶的心跳...
心跳保持的VPN长连接有几个问题,第一,如果收不到心跳,隧道就要被迫断开一次,这种断开事件会被审计为一次异常事件,因为按照正常看来,既然要保持长连接,那它就不应该断开,现在断开了,那是不应该的。第二,对于那种带宽属于稀缺资源的环境,心跳报文会占用可观的资源,比如3G用户,在没有实际数据传输的情况下,发送的心跳报文将是完全的浪费。
VPN为何要保持长连接呢?难道随用随连不好吗?隧道的长连接难道仅仅为了展示自己是一个基础设施吗?对于网到网拓扑来讲,这可能是真实的,但是对于终端用户而言,长连接基础设施反而意味着压力,因为终端用户要为心跳报文买单。OpenVPN是一种高度灵活的VPN,它的灵活性以及好处在于你如何使它和外界关联配合,而不在于它本身!记住这一点非常重要!
OpenVPN也可以实现按需连接,即没有数据的时候隧道断开,有数据来的时候创建隧道,并且在持续一段时间没有数据的时候再次断开隧道。这些事件中,除了”持续一段时间没有数据的时候再次断开隧道“这个功能(--inactive参数)是OpenVPN本身的功能外,其它的都需要外部的协助,首先我们要考虑的是,如何按需创建连接。
      通过iptables识别什么是”需要通过OpenVPN加密传输“的数据,识别到了以后,必须”堵住“该数据,直到隧道建立后再将它放行,堵在哪里呢?当然是堵在队列里面,如果隧道建立的时间比较久,那队列无疑会满掉,此时丢弃新来者是迫不得已的行为。有了此大方向,接下来就是具体的方案。在Linux上,几乎什么东西都不需要自己重做,以上这个功能可以通过iptables的QUEUE来实现,该target将数据包queue,用户态进程read这些排队的数据包到用户态,然后...
然后该进程可以直接将包重新注入回内核,也可以在注入之前先去建立OpenVPN隧道,隧道建立以后再注入数据包,既然此时隧道已经建立了,路由规则该添加的也添加了,那么后续的数据包就不需要再QUEUE到用户态了,这个又是怎么实现的呢?还好,Netfilter有一个addons扩展,扩充了iptables的功能,我们需要的就是一个叫做condition的模块,该模块的使用使得多条iptables规则之间可以完成很多复杂的控制逻辑,如下一条:
iptables -t mangle  -A PREROUTING -d 192.168.1.1/32 -m condition --condition "vpn"  -j QUEUE
它的意思就是只有在vpn这个condition变量的值为1的时候,发往192.168.1.1的数据包才会去排队,否则就直接匹配下一条规则,不会被排队。到此为止,所有的准备工作基本就绪,剩余的工作即是如何把这一切拼接在一起形成方案。为了快速说明问题,我就不再使用OpenVPN举例了,我用一种等价的方式说明问题:
只有当到达192.168.1.1的数据包来的时候才建立其明细路由,持续没有到达192.168.1.1的流量5秒钟后删掉该明细路由。
这个足以说明问题了,它足够简单。虽然说即使它这么简单,最终还还是偷了懒,没有实现完全。首先添加上面列举的那条iptables规则,然后创建一个用户态进程,代码如下:

#include <linux/netfilter.h>

#include <string.h>
#include <stdlib.h>
#include <libipq.h>
#include <stdio.h>
#include <libnetfilter_queue/libnetfilter_queue.h>
#include <linux/ip.h>
#include <signal.h>

#define BUFSIZE 2048
static int condition = 0;

void condition_handler(int num)
{
        if (condition == 1) {
                condition = 0;
        } else {
                condition = 1;
        }
}

int main(int argc, char **argv)
{
        int status;
        unsigned char buf[BUFSIZE];
        struct ipq_handle *h;
        signal (SIGUSR1, condition_handler);
        h = ipq_create_handle(0, 2/*PROTO_IPV4*/);
        status = ipq_set_mode(h, IPQ_COPY_PACKET, BUFSIZE);
        do{
                status = ipq_read(h, buf, BUFSIZE, 0);
                switch (ipq_message_type(buf)) {
                case IPQM_PACKET: {
                        ipq_packet_msg_t *m = ipq_get_packet(buf);
                        size_t data_len = m->data_len;
                       //读取到有数据包后,触发执行启动VPN脚本的命令
                        system("/home/zhaoya/start_vpn param1 param2 paramX");
                       //等待start_vpn命令执行完毕,它会建立VPN隧道,建立完成发送信号给该进程
                        while(!condition) {
                                pause();
                        }              
                       //成功建立隧道以后,将数据包原封不动地重新注入回去
                        status = ipq_set_verdict(h, m->packet_id,
                                NF_ACCEPT, data_len+sizeof(struct iphdr), (char*)m->payload);
                        break;
                        }
                default:
                        break;
                }
        } while (1);
        ipq_destroy_handle(h);
        return 0;
}
上述程序很简单,逻辑上它就是读取queue的数据包,然后触发命令建立隧道,建立好隧道后,发送信号给进程,进程接着将数据包重新注入。隧道建立好以后,还需要修改vpn这个condition变量的值,使后续的数据包不再queue,如此策略化的事情用脚本封装是比较好的选择:

#!/bin/bash
# 模拟创建隧道,添加一条路由
ip route add 192.168.1.1/32 via 172.16.49.88
echo 0 >/proc/net/nf_condition/vpn
killall -SIGUSR1 queue

# 模拟5秒没有流量
sleep 5;

# 模拟断开隧道
ip route del 192.168.1.1/32
echo 1 >/proc/net/nf_condition/vpn
killall -SIGUSR1 queue
经过测试,效果非常不错!但是那个用户态进程实在需要再润色润色啊。最后,我觉得最重要的不是如何想出解决问题的办法,而是想出如何验证你的解决方案是正确的方法,在你不可能实际证明的时候,或者当你实际证明要付出很大代价的时候,就要想出模拟的办法。但是很多人只是闷头做自己擅长的事情,如果模拟不了15454500053535300042453535004343535350043534647430043532578053535353005330535305454636365363530046464640535353500个用户,他们就要想办法模拟146560053353666535005353535305463320465479024214794546424235364657007684000646465789032558970032646320053646758320个用户,精确本身没有错,错在知道精确的尺度和粒度以及层次更加重要。要认为什么东西都可以通过“设置”解决,远远不是!你需要的是:删除!