OpenVPN多处理-为什么一定要做

做技术的。有较真的精神的孩子!小完美主义者。更高贵是,你知道要做什么,什么不该做,比方说,傻,这也是很愚蠢的CAN。今天去了苏州,无锡,随着小到风景秀丽的太湖,然后去苏州看萤火虫。我们选择今天的原因是虚张声势吓跑了很多人预测的,果然,特别是较低级的动物景区,下一个,有些个人感觉会飘小雨。不会太热,太阳。刚刚好,果然,哈哈。回到酒店就快10点了,带着电脑出来旅行,夜深人静时能够写点总结。也挺好,总结什么呢?还是OpenVPN了。只是,话说在旅行期间,千万别调试代码。那会坏了好心情。写点总结是最好只是的。由于那仅仅是一些思绪的回想,自己全然能够把控的住。或者,喝点小酒之后。写篇散文,打油诗?..可惜,我已经没那个雅兴了。
       让我从头说起。

1.James Yonan的理由

OpenVPN 2.0去除了多线程(事实上就是去除了单独处理TLS的线程。OpenVPN一直都没有实现数据通道多线程),理由非常充分,详情请搜索maillist。其中非常大的原因在于OS底层的复杂性以及接口规格不统一。

正如JY所说,即使OpenVPN本身使用了多个线程处理,那么在绝大部分的操作系统下,比方Linux。终于还是要汇集到一个内核线程处理,这是tun驱动的特性决定的。

这样的让步性质的说辞不仅仅是一种责任推卸。

事实上。在tun驱动明白支持了Multiqueue之后。这样的说法便不成立了。
       还有一方面。多个进程/线程中每个都要维护自己的socket。这个socket是从主线程继承还是分别创建呢?假设继承,那么可能会有惊群问题,假设分别创建。那么便不能bind反复的IP地址和端口。就这样,无论是涉及到tun网卡的管理方面,还是涉及到socket的负载均衡方面,当前的操作系统内核都不给力,鉴于实现的复杂性以及稳定性考虑,JY明白指出。不提供多处理版本号的OpenVPN支持。
       终于。我总结下来,JY不提供多处理OpenVPN支持的理由:1.由于OpenVPN须要使用诸多的操作系统底层的机制,内核不给力,各个操作系统平台实现并非整齐划一,用户态适配复杂性太高且各个平台实现高度不一致;2.保持紧凑的OpenVPN单进程实现。用户在外部实现多处理会更好,而且他真实给出了random remote机制。

2.我的理由

假设JY真的是那么想的。正如其在maillist所说,那么我会说。至少对于Linux。那些限制已经不存在了。为什么呢?自从内核3.9版本号開始,支持了SO_REUSEPORT这个socket选项。支持了Multiqueue TUN驱动,完美的攻克了JY的困惑。
      是时候让OpenVPN支持多处理了。然而对于JY而言,他的想法的关注点可能与我的并非一致,我们考虑的并非一个问题,他须要给出的是一个跨平台的OpenVPN源码。不仅仅针对Linux,还有Windows,BSD族。Mac OS甚至IOS...敢问全部的操作系统都支持SO_REUSEPORT以及Multiqueue TUN么?我能够肯定的是BSD支持了REUSEPORT。可是对于别的,预计够呛。难道我不能使用预编译宏来解决问题吗?比方#ifdef LINUX之类的...我能够,可是这会让本已丑陋的代码更丑陋。
       我能够坦言,自从2010年以来,我关注OpenVPN已经4年有余,期间得到了公司的支持,社区的支持。论坛的支持,以及无数个下雨的夜晚独自琢磨...使用了一切能够利用的外围技术来解决OpenVPN多处理的问题,其自身的多处理,而不是依靠外部技术实现的多处理!终于,在近期我老婆载我去苏州旅游的时候。我趁着在酒店避风雨的间隙,实现了一个原型。
       我无意攻击老一辈的JY,我仅仅是按自己的方式做了我应该做的,代码将会以别人的名义提交到github,我仅仅针对Linux,对于别的系统。我用预编译宏绕开。我仅仅是希望。对于OpenVPN这么好的一个东西,让它在多核心处理器上发挥更大的功用,而不仅仅作为一个应急用的单用户接入程序来使用。有人用它来脸书。来you土逼。发现性能不好。那么就会有还有一种声音:MD,能接入就不错了。还管什么性能!!我对这样的声音嗤之以鼻!难道就不能做的更好些吗?
       更好的是,OpenVPN改进并非以添加其复杂性为代价的。它的复杂性一点都没有添加,仅仅是利用了内核提供的新特性而已。

这仅仅是万事俱备,仅仅欠东风的等待过程,过程中也曾跃跃欲试。如今。东风来了。

3.我的思绪图

我不会引入专业的思维导图工具以展示自己的思考过程,而仅仅是使用相似一种不标准的流程图的方式来说明。

从2011年底至今,我完毕了多个OpenVPN多处理的框架,非常多感兴趣的朋友也经过了实际測试,发现效果良好(讽刺的是。我自己没有測试过。),日前,我决定实现一个最少利用外部机制的一个OpenVPN多处理框架。有人会问,那么之前的成果不都白费了么?不!我想说的是事情是一步步做的。没有前面的成果,就不会明白它的缺陷,就不会更进一步思考。

因此。自从2010年/2011年開始。一直到如今,我的思路是清晰的。思绪图例如以下:

4.我的路子没有跑偏

眼下的Linux3.10+版本号的内核基本上基本上实现了我希望它实现的。然而在此之前,我自己也以前独立实现过这些终于被并入主干的特性,比方“多个OpenVPN进程使用一个TUN网卡”,比方IP/端口的负载均衡。

可悲的是,我没有及时关注诸如goolge之类提交的patch,以至于我又一次发明了不能滚动的轮子,更可悲的是,我缺乏提交源码的动力,技术与途径。假设拥有此动力,技术与途径,可能OpenVPN的Changelog上将会出现我的名字了...
       无论如何,路子没有跑偏。

5.影响OpenVPN网络多处理的新特性与影响

是时候来展示一下东风了。

5.1.SO_REUSEPORT

这个特性最初是google的一个patch,终于被合并在了主干版本号。能够说这是一个创举。在以往的编程模型中。一般都是在主线程创建listen socket,然后要么直接accept,每来一个连接fork/pthread出一个进程/线程。要么预先fork/pthread一系列的工作进程/线程,同一时候accept。无论是哪一种。一次仅仅能接受一个连接(注意。对于多进程/线程同一时候accept一个socket而言。为了避免惊群,Linux内核採用WQ_FLAG_EXCLUSIVE方式唤醒。每次仅仅能唤醒一个进程/线程)。这对于当前的多核心处理器或者超多核处理器而言是极其不友好的,为了实现同一个服务的多处理,你不得不选择侦听不同的端口或者IP地址。事实上。系统全然有能力同一时间处理到达同一IP地址/端口的多个连接。现实其中,对于大并发环境而言,多个訪问请求同一时候到达并非稀罕事。
       由于TCP本身就是有连接的,所以说针对TCP的ESTABLISH状态的socket管理和REUSEPORT无关,仅仅是针对TCP的listen套接字(三次握手在listen套接字上进行,此时状态还未建立)以及UDP套接字REUSEPORT才有意义。

SO_REUSEPORT这个socket选项将同一时候处理多个到达同一IP/端口的能力发挥了出来。先看看LWN上的一篇文章是怎么说的:
 SO_REUSEPORT can be used with both TCP and UDP sockets. With TCP sockets, it allows multiple listening sockets—normally each in a different thread—to be bound to the same port. Each thread can then accept incoming connections on the port by calling accept(). This presents an alternative to the traditional approaches used by multithreaded servers that accept incoming connections on a single socket.
这篇文章是我事后找到的,它的分析和我前面的分析惊人得一致:
 The first of the traditional approaches is to have a single listener thread that accepts all incoming connections and then passes these off to other threads for processing. The problem with this approach is that the listening thread can become a bottleneck in extreme cases. In early discussions on SO_REUSEPORT, Tom noted that he was dealing with applications that accepted 40,000 connections per second. Given that sort of number, it‘s unsurprising to learn that Tom works at Google.    ----要么直接accept,每来一个连接fork/pthread出一个进程/线程

The second of the traditional approaches used by multithreaded servers operating on a single port is to have all of the threads (or processes) perform an accept() call on a single listening socket in a simple event loop of the form:

while (1) {
        new_fd = accept(...);
        process_connection(new_fd);
    }  ----要么预先fork/pthread一系列的工作进程/线程,同一时候accept

The problem with this technique, as Tom pointed out, is that when multiple threads are waiting in the accept() call, wake-ups are not fair, so that, under high load, incoming connections may be distributed across threads in a very unbalanced fashion. At Google, they have seen a factor-of-three difference between the thread accepting the most connections and the thread accepting the fewest connections; that sort of imbalance can lead to underutilization of CPU cores. By contrast, the SO_REUSEPORT implementation distributes connections evenly across all of the threads (or processes) that are blocked in accept() on the same port.

这说明。我的思路是没有错的。另外,这篇文章中论述了Linux 3.9中对于SO_EEUSEPORT实现的问题,和我之前的分析也是一致的:

The firstof these is a useful aspect of the implementation. Incoming connections anddatagrams are distributed to the server sockets using a hash based on the4-tuple of the connection—that is, the peer IP address and port plusthe local IP address and port. This means, for example, that if a clientuses the same socket to send a series of datagrams to the server port, thenthose datagrams will all be directed to the same receiving server (as longas it continues to exist    ----注意,假设进程挂掉或者又一次启动了,将会导致bind同一IP地址/端口的全部socket的位置相对变化或者说又一次排序,就无法保证之前的流量被路由到同一个socket). This eases the task of conducting statefulconversations between the client and server.    ----记得我以前说过,由于是依照源IP。源端口。目标IP。目标端口来做hash的。且没有不论什么随机因素。仅仅要源IP,源端口保持不变。就一定能保证同一个进程/线程处理同一个4元组流(仅仅要期间没有不论什么进程重新启动或者挂掉。也没有不论什么进程/线程新加入进来)。
The other noteworthy point is that there is a defect in the current implementation of TCP SO_REUSEPORT. If the number of listening sockets bound to a port changes because new servers are started or existing servers terminate, it is possible that incoming connections can be dropped during the three-way handshake. The problem is that connection requests are tied to a specific listening socket when the initial SYN packet is received during the handshake. If the number of servers bound to the port changes, then the SO_REUSEPORT logic might not route the final ACK of the handshake to the correct listening socket. In this case, the client connection will be reset, and the server is left with an orphaned request structure. A solution to the problem is still being worked on, and may consist of implementing a connection request table that can be shared among multiple listening sockets. 
  ----本质上。这个问题和上面提到的一样,由于socket数量的改变,hash算法的结果socket可能会改变,假设socket数量改变时。恰有TCP三次握手发生。这会影响基于状态的TCP三次握手过程,比方处理SYN的listen socket是s1,而之后的ACK却被路由到了s2...
以上就是对LWN上一篇文章的原文节选以及论述,看样子,REUSEPORT机制是为无状态的socket引入了一种朴素的状态机制,该状态表现为:在总的套接字数量不变的前提下,一个数据流的数据包将始终被路由到同一个套接字。

这一点是非常可利用的。
       当然,仅仅要有一种声音。就会有还有一种声音,此所谓众口难调。

即使没有还有一种声音。引入REUSEPORT也是有代价的。以下的说法就是一个引入REUSEPORT后带来的问题:
 If you used this technique on multiple threads accepting on the same traditional socket, you would be fixing one thing and breaking another. Today, if a thread is blocked in accept() and no other thread is, and a connection request arrives, the thread gets it. It sounds like with a SO_REUSEPORT socket, the connection request would wait until its predetermined accepter is ready to take it.

5.2.Multiqueue tun驱动版本号

这个特性是非常直接的。但凡一个懂网络的。都不会针对一个单一的逻辑生成多块虚拟网卡,否则让策略路由情何以堪。多块虚拟网卡终于将面临数据包如何路由到设备的复杂问题,而要解决此问题。你就须要没完没了地对数据包进行分类,诚然,有非常多的包分类框架,然而为何不在虚拟网卡内部实现这些呢??
       高端网卡比方Intel 825XX系列为我们提供了思路。这些网卡内部实现了多队列,能够将进入或者将要发出的数据包分类到不同的队列中。注意,这个队列处理是在驱动本身完毕的。

这个思路让我实现了多个OpenVPN实例共享一块TUN网卡的驱动。终于,Linux的主干推出了一个更好的实现,即直接实现TUN的多队列!

如上面的思绪图所看到的,由于反向数据流数据包的路由问题,我以前改动TUN驱动为广播模式,然而为何要改它呢?人家底层已经实现了如此好的模型。你OpenVPN总不能总是坐享其成一点不改吧。于是我改动了OpenVPN!

Multiqueue TUN的代码比較简单,也没有什么文档性描写叙述,究事实上现。看代码就好。顺便说一句,PHP是最好的语言。
       性能都是最后要考虑的因素

5.3.SO_REUSEPOT和Multiqueue TUN的影响

首先要说的是,REUSEPORT尽管添加了查找套接字时的一些hash计算,但并没有添加什么查找开销,由于REUSEPORT仅仅针对UDP套接字以及TCP的Listen套接字。而这两类套接字本身就不会太多。对于TCP而言,查找套接字的大量开销都在查找ESTABLISH套接字(假设最大连接数指标超级高的话)以及TIME-WAIT套接字(假设大量短连接并主动断开的话),良好的设计中。真正的Listen套接字数量不会超过CPU核心数量太多。对于UDP而言,由于它本身无连接无状态。它查找的不是4元组连接,而仅仅是一个与目标IP和端口绑定的套接字。这类套接字的数量也不会太多。设计良好的UDP服务套接字数量不会超过CPU核心数量太多。因此REUSEPORT套接字的查找差点儿不会引入开销。
       另外,SO_REUSEPORT机制使用的算法是没有随机因素的4元组hash计算。之后将结果映射到一个特定的套接字,因此仅仅要4元组不变,套接字永远都是那一个。而4元组标识了一个流,因此SO_REUSEPORT确定的套接字本身就拥有朴素的流特征。

我们再看Multiqueue TUN驱动,相同的,一个queue相应一个OpenVPN进程。而queue的确定也是通过4元组做hash运算的结果。因此也能保证同一个流被绑定到特定那一个queue,终于交给特定的那一个OpenVPN进程,这就是说,多线程版本号的OpenVPN中对multi_instance hash表的锁开销也非常低(仅仅对hash冲突链表加锁),毕竟对一个特定的multi_instance的增删改基本上都是特定的OpenVPN线程干的。而这正是SO_REUSEPORT机制hash运算以及TUN的select queue算法决定的。

仅仅有在同一个multi_instance由不同的OpenVPN线程处理的时候,锁才是真正必要的,而这出如今以下的情况:
时间点1:一个数据包N在线程1被解密后写入到了TUN字符设备,绑定到了队列1,更新flow entry;
时间点2:长时间数据包N的返回包没有到来。导致flow entry过期被删除;
时间点3:数据包N的返回包到来。由于没有找到flow entry。于是依照别的算法被分派到了队列2;
时间点4:队列2和OpenVPN线程2相应,线程2从字符设备读取数据包;
时间点5:线程2在multi_instance虚拟地址hash表中查找multi_instance,同一时候在线程1中该multi_instance相应的客户端断开。multi_instance被删除,此时须要一个锁。
鉴于此,我改动了锁方案,不再对hash冲突链表加锁,尽管冲突链表的粒度已经足够细。可是还是有点粗,为何不正确multi_instance本身加锁呢?对。就是这个思想。

事实上。对于删除multi_instance这样的操作,根本不须要锁,用原子操作的引用计数就够了,而对于更新multi_instance中的字段,我在multi_instance结构体本身加了一把锁,由于SO_REUSEPORT以及Multiqueue select的特殊hash算法。针对每个OpenVPN客户端的multi_instance,差点儿总是同一个OpenVPN线程处理,而同一个OpenVPN线程的运行流是串行的。所以这把锁的使用场合并不多。

6.关于三项工作代码

正如思绪图所看到的,终于的工作归结为了三项任务。

尽管2.6的内核不支持Multiqueue TUN,也不支持SO_REUSEPORT,可是能够从3.9内核上移植它们过来。

6.1.移植TUN驱动

从3.96内核将drivers/net/tun.c移植到2.6内核,直接编译肯定是不通过的,由于这两个内核版本号的数据结构以及接口有非常大的差异。比方hlist_for_each_entry就不同。因此这些都是须要改动的。总的改动并不多,主要体如今一些宏的定义以及一些2.6内核版本号中没有的接口的又一次封装,比方__skb_get_rxhash。另外。为了简化实现。我并没有採用4元组来做hash,而仅仅是採用了源和目标IP地址,因此,我改动了skb_flow_dissect的实现:

bool skb_flow_dissect(const struct sk_buff *skb, u32 *flow)
{
    int nhoff = skb_network_offset(skb);
    __be16 proto = skb->protocol;

    memset(flow, 0, 2*sizeof(u32));

    switch (proto) {
        case __constant_htons(ETH_P_IP): {
            const struct iphdr *iph;
            struct iphdr _iph;
            iph = skb_header_pointer(skb, nhoff, sizeof(_iph), &_iph);
            if (!iph)
                return false;
            flow[0] = iph->saddr;
            flow[1] = iph->daddr;
            break;
        }
        case __constant_htons(ETH_P_IPV6): {
            //TODO
            return false;
            break;
        }
        default:
            return false;
    }
    return true;
}

其他的没有什么好说的。有点内核开发经验的基本都能够在两个小时内完毕移植。

6.2.改动OpenVPN

OpenVPN的多线程版本号改动在我之前的文章《OpenVPN多处理之-多队列TUN多线程》中已经有所提及,这里主要给出一些兼容性考虑的问题。
       假设你还没有移植SO_REUSEPORT机制到2.6内核,OpenVPN能不能用呢?问题是如此一来,多个OpenVPN线程不得不bind不同的端口了。怎么来解决问题呢?为此,我改动了socket.c中的resolve_bind_local函数:

static void
resolve_bind_local (struct link_socket *sock)
{
  struct gc_arena gc = gc_new ();

  /* resolve local address if undefined */
  if (!addr_defined (&sock->info.lsa->local))
    {
      sock->info.lsa->local.sa.sin_family = AF_INET;
      sock->info.lsa->local.sa.sin_addr.s_addr =
    (sock->local_host ? getaddr (GETADDR_RESOLVE | GETADDR_WARN_ON_SIGNAL | GETADDR_FATAL,
                     sock->local_host,
                     0,
                     NULL,
                     NULL)
     : htonl (INADDR_ANY));
#ifdef REUSEPORT
      /*
       * 假设设置了reuseport,则设置SO_REUSEPORT这个sockopt,假设设置失败
       * 则表示内核眼下并不支持它,那么则将递增的端口号作为bind端口号。

*/
#define SO_REUSEPORT 15
      if (sock->info.proto != PROTO_UDPv4 ) {
        sock->info.lsa->local.sa.sin_port = htons (sock->local_port);
      } else {
          if (sock->sockflags & SF_REUSE_PORT) {
                int optval = 1;
                sock->info.lsa->local.sa.sin_port = htons (sock->local_port);
                if (!setsockopt(sock->sd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))) {
                    goto normal;
                }
                if (!setsockopt(sock->sd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval))) {
                    goto normal;
                }
          } else {
                static int lport = 0;
                static int initial = 0;
                if (initial == 0) {
                    lport = sock->local_port;
                    initial = 1;
                }
normal:
                sock->info.lsa->local.sa.sin_port = htons (lport);
                lport ++;
          }
      }
#else
      sock->info.lsa->local.sa.sin_port = htons (sock->local_port);
#endif /* REUSEPORT */
    }

  /* bind to local address/port */
  if (sock->bind_local)
    {
          socket_bind (sock->sd, &sock->info.lsa->local, "TCP/UDP");
    }
  gc_free (&gc);
} 

假设你连Multiqueue TUN也没有移植,那么正如JY所言,OpenVPN改成多线程是没有多大意义的。所以说,请尽量让内核先支持掉Multiqueue TUN以及SO_REUSEPORT。

6.3.移植SO_REUSEPORT

这个改动是最easy的了,仅仅以udp的REUSEPORT套接字查找为例,现如今的2.6内核的逻辑例如以下所看到的:

begin:
    result = NULL;
    badness = -1;
    sk_nulls_for_each_rcu(sk, node, &hslot->head) {
        score = compute_score(sk, net, saddr, hnum, sport,
                      daddr, dport, dif);
                // 简单的一次冒泡
        if (score > badness) {
            result = sk;
            badness = score;
        }
    }
    /*
     * if the nulls value we got at the end of this lookup is
     * not the expected one, we must restart lookup.
     * We probably met an item that was moved to another chain.
     */
    if (get_nulls_value(node) != hash)
        goto begin;

就是一次简单的冒泡过程。在REUSEPORT机制下,它终于仅仅会找到第一个合适的sk,由于对于后面找到的一模一样的sk,if (score > badness)这个推断将不会通过。如今改动的思路是,加一个else if (score == badness && reuseport)逻辑。这意味着针对相同的sk,也要处理。那么怎么处理呢?这就要引入关于源IP和源端口的hash了:

begin:
    result = NULL;
    badness = -1;
    sk_nulls_for_each_rcu(sk, node, &hslot->head) {
        score = compute_score(sk, net, saddr, hnum, sport,
                      daddr, dport, dif);
        if (score > badness) {
            result = sk;
            // 添加一个reuseport标志,用来指示该sk能否够reuseport
            reuseport = sk->sk_reuseport;
            if (reuseport) {
                // 依据4元组计算一个hash值
                hash = inet_ehashfn(net, daddr, hnum,
                            saddr, htons(sport));
                matches = 1;
            }
            badness = score;
        } else if (score == badness && reuseport) {
            // matches++意味着又多找到了一个匹配的sk
            matches++;
            // 是否由该sk替换上次匹配到的sk,就看hash值的影响了
            if (((u64)hash * matches) >> 32 == 0)
                result = sk;
            // 为了更好的散列,这里对已经有的hash值做了一个变换
            hash = hash * 1664525 + 1013904223;
        }
    }
    /*
     * if the nulls value we got at the end of this lookup is
     * not the expected one, we must restart lookup.
     * We probably met an item that was moved to another chain.
     */
    if (get_nulls_value(node) != hash)
        goto begin;

注意,假设你看3.9以上的内核源码,会发现__udp4_lib_lookup非常复杂,实际上那些复杂的部分都不是重点。而是优化。

比方在if (hslot->count > 10)的情况下,意味着链表非常长。于是就希望用一种旨在降低链表长度的算法进行优化。hslot最初是仅仅通过端口算出来的。由于引入目标IP地址的话,会加大计算量,假设此时冲突链表过长,遍历它相同会加大计算量。此时再次算出一个hslot,这次引入了目标IP地址。看看计算结果能否降低冲突链表的长度,假设确实降低了,那么就在该链表上遍历,假设没有降低。或者反而添加了,则进入正常遍历流程。最坏的情况,那就是hslot冲突链表长度大于10。然后引入目标IP地址又一次计算hslot,链表反而添加了。于是进入正常流程,还不如直接就開始正常流程呢?什么是正常流程呢?就是我上面贴出的那段。看来优化是有一点点冒险意味在里面的。

7.接下来干什么

还用问吗?写了三个多小时,手都麻了。如今正是午夜时分。

知道在哪里最恐怖吗?绝不是什么墓地之类的,也并非医院,而是酒店。在酒店一个人看恐怖片绝对是一种享受!窗外漆黑一片。你又不知隔壁住的是什么(注意,我没有加‘人’字,或许我应该用being这个词),拉开房门。我仅仅看到一扇扇紧闭的和我的一样的房门,然而并非开着的,什么声音都没有。

走廊里昏暗的灯光预示着将要到来的being真的会到来,而且不须要太强烈的照明。

嘀嗒。嘀嗒。近了...
       假设我说仅仅有我一个人。那么事实上也没什么,由于我最不怕的就是一个人。关键是,关键是...我们今天一共两个家庭一起出游,女人和孩子们去了还有一个房间,我知道我要写些东西,所以嘛就提出了这个点子,事实上嘛。非常多人都猜到我是由于懒。不想半夜看孩子...实际上,我并非这么想的...
       同来的还有一个家庭的男人和我同住,这是肯定的啊。

这个人长期不在家。长期在外地,我从来都没有见他有过笑脸,他女人也从来不说他的事。就是这么一个人。和我同住。

我在走廊里透风的时候。他在洗澡。据他女人说他不爱空调,也从不喜欢开窗,所以我仅仅能在走廊透风,当我走进房间的时候,厕所里面的灯并没有开。他却依旧在洗澡。哗啦啦的淋浴水声掩盖了外面的全部声响,我发现门卡在取点槽里松动了,于是我又一次插拔了一下,等凉了,他依旧在洗。没有听到他说什么...理由嘛。简单,第一,他女人说过,他不爱说话。第二。我耳朵背...
       我等他出来。我进去洗,他躺下就睡了。我去洗了,灯一直亮着,我进去洗澡的时间是01:00,而我在0:00的时候完毕了此文。

知道我是什么时候回到酒店的吗?是9:40多,写完三个多小时,正是我进去洗澡的时间。我不想说什么了。我仅仅是认为,最恐怖是主角事实上不是那个你同睡但不熟悉的人。事实上是我自己...于是我接下来就独自看了一部电影《捉迷藏》,约翰.普尔森地作品。
       同一时候,我认为。时刻看看他在做什么。或者想做什么,即使在梦里。事实上,他也是这么想的,我想,他一定能够让事爱情变得更加真实。由于真实的东西,这是不可怕...我的背有点凉。你呢?

版权声明:本文博主原创文章,博客,未经同意不得转载。

时间: 2024-10-02 22:56:44

OpenVPN多处理-为什么一定要做的相关文章

使用ZeroMQ彻底重构OpenVPN的设想以及一些新想法

在一个迟到的雨夜,我怀着无比激动的心情写了不到20行代码...但是这不到20行代码却是一个新发现,它彻底解决了OpenVPN的三个重大问题,是的,彻底解决. ZeroMQ的到来 我接触ZeroMQ这玩意确实有点晚,那是上一个下雨的周日,我自己宅在家里看罗马史,畅想着这个辉煌的帝国,伟大的制度.       ZeroMQ彻底颠覆了以往的socket编程模型.它使得底层的BSD socket对程序员不再可见,程序员只需要处理自己业务即可,即收到某个消息,将其做一些处理,然后要么回复一个消息,要么转发

安装多版本的JDK出现问题及解决办法

一,背景 1,原来安装了jdk1.8.0_91,因为要在本地跑服务端程序,需要1.7版本的,需要安装的是1.7.0_79: 2,原来的安装目录: D:\Program Files (x64)\java\jdk1.8.0_91 D:\Program Files (x64)\java\jre1.8.0_91 3,低版本的安装目录: D:\Program Files (x64)\java\jdk1.7.0_79 D:\Program Files (x64)\java\jre7 也就是所有的jdk和jr

adito 配置说明

adito的前身,是ssl-explorer,一个出色的ssl vpn开源软件,现在还可以看到很多关于ssl-explorer应用的文档.因为ssl-explorer做的太好,结果连公司带产品被一家商业vpn 厂家收购.随后开源社区的人另起炉灶,分出了adito,使用了ssl-explorer最后一个版本的源代码并加以修改.之后adito的开发者受到 openvpn的邀请,就将adito做为openvpn的一个子项目,改名为openvpn als,但最新的版本仍然还是adito 0.9.1. a

OpenVPN多处理之-为什么一定要做

做技术的,就是有一种较真儿的精神!有点完美主义,更高尚的是,知道什么该做,什么不该做,如果说傻,那也真的傻的可以.今天去了无锡苏州,带小小去太湖景区,然后去苏州看萤火虫.之所以选择今天是因为虚张声势的天气预报吓退了很多人,果然,景区的高级动物特别少,其次,个人感觉会飘一些雨点,不会太热,没有大太阳,刚刚好,果然,哈哈.回到酒店就快10点了,带着电脑出来旅行,夜深人静时可以写点总结,也挺好,总结什么呢?还是OpenVPN了.不过,话说在旅行期间,千万别调试代码,那会坏了好心情.写点总结是最好不过的

CentOS6.8中openvpn联动windows ldap做认证

使用 LDAP 的方式认证 1.实际上也有二种 一种用 openvpn-auth-ldap 即直接通过 LDAP 验证, 一种与 mysql 认证相似使用 pam-ldap -->通过 PAM -->然后再找 LDAP 验证. 这里主要用 openvpn-auth-ldap (另一方法,安装 yum install nss_ldap 包后找文件 /usr/local/etc/auth-ldap.conf 复制 /usr/share/doc/nss_ldap_253/ldap.conf.pam_

openvpn 的安装和使用

这里我参考的文章有 OpenVpn https://my.oschina.net/mn1127/blog/855842http://linuxchina.blog.51cto.com/938835/1130158  vpn 参数的详细含义http://www.oyblog.cn/OuYang-Blog-520175.htmlhttp://zhuimengbk.top/?post=29https://github.com/search?o=desc&q=openvpn+web&s=stars

openvpn实现分流,指定IP走VPN,其它走本地网络

最近研究了Openvpn好久.现在写一点心得出来. 客户需求:看香港某几个网站使用openvpn翻墙过去,其它所有访问不能用翻墙,要走本地. 系统环境: Centos x64位 6.8, 使用在线yum安装 一.服务器端配置 详细配置参考其它配置文档 [[email protected] openvpn]# rpm -qa | grep openvpn openvpn-2.3.11-1.el6.x86_64 生成ta.key文件,用于tls-auth认证. # openvpn --genkey

CentOS系统搭建OpenVPN远程访问

修改2处(有效文件再此目录/usr/share/easy-rsa/2.0/:/etc/openvpn/checkpsw.sh 需要加X权限) openvpn是一个vpn工具,用于创建虚拟专用网络(Virtual Private Network)加密通道的免费开源软件,提供证书验证功能,也支持用户名密码认证登录方式,当然也支持两者合一,为服务器登录和连接提供更加安全的方式,可以在不同网络访问场所之间搭建类似于局域网的专用网络通道,配合特定的代理服务器,可用于访问特定受限网站(你懂得)或者突破内部网

OpenVPN使用用户名密码认证

紧接上一篇,OpenVPN使用openldap进行认证,这一次让openvpn读取本地文件中的用户名密码,通过判断用户名密码是否存在文件中进行认证,搭建openvpn环境就不多做说明了,只要把openvpn搭建好,客户端能够连接就可以了. 1.修改openvpn配置文件 vi /etc/server.conf 编辑/etc/server.conf文件,并添加如下内容: auth-user-pass-verify /etc/openvpn/checkpsw.sh via-envclient-cer