haohaolee's blog

nerd以上,geek未满

用 Microsoft Application Compatibility Toolkit 管教不规矩的程序

作为一个 UAC 和 sudo 爱好者,应用程序就应该乖乖的,该干嘛干嘛。老是有些游戏或者别的什么大言不惭的在安装说明叫你关掉 UAC,这一类要么是技术不好要么就是人品不好。今天就碰到一个。家里人要在网上打打麻将斗斗地主什么的,朋友们都在玩一个叫什么 JJ比赛 的游戏,类似于腾讯的游戏大厅的东东。官网提供安装版和所谓绿色版,但是不管哪个版,只要一运行,UAC 提示框就会跳出来。我就纳闷了,一个游戏,要管理员权限干什么,而且我电脑上不是所有账户都有管理员权限。顺手看看到底是怎么回事:

看来不是 Windows 自动进行了兼容性修改,而是赤果果的 RequireAdministrator。本来这种程序直接删除了事,这次不行了,于是祭出 Microsoft Application Compatibility Toolkit,是用于评估和改善应用程序的兼容性的,特别是针对 Vista 以后的 Windows。截图一枚:

关键设置是加一条 RunAsInvoker 的修改,这些都可以根据向导一步步生成,最后安装这个 fix 到系统数据库就行了。

嗯,现在放心多了,普通账户也可以运行了。完。

注:不是所有的兼容性问题都可以这样解决,比如有些程序用了高权限的 API,那就没办法降低权限了。

Create a VLAN Trunk in TL-WR703N

TL-WR703N is a very tiny router, made by TP-LINK who markets it as “3G travel router”, although without a builtin 3G modem you cannot really say it is a 3G router, which is still an awesome device for its size. Very neat.

TL-WR703N 是一个非常小的路由器,生产商 TP-LINK 把它宣传为 “3G 旅行路由器”,然而不自带 3G 猫芯的路由器不能算作真正意义上的 3g 设备,不过它仍然是非常赞的东东,因为实在太小巧了。

There are many stories about how to make it a low power 24x7 downloading box, or a wireless audio streaming box with OpenWrt, I still wanna make it a better router. Since it has only one ethernet interface and has no switch built-in, therefore we often change its role as a normal router with wireless clients to a wireless bridge or repeater with wired clients. It would be cool if the only interface can automatically vary its role according to different situation. To be brief, our goal is:

  1. When a node’s ethernet wire is plugged in, and makes DHCP requests, the router should assign it a LAN IP.
  2. Otherwise, the router should consider the wire as an WAN path.

网上有许多关于如何用 OpenWrt 把它做成一个低功耗 24x7 的下载机,或者无线音乐播放设备的故事。不过我还是只想尝试把它折腾成一个更好的路由器。因为只有一个以太网接口并且内部也没有交换机芯片,所以我们常常要在带无线客户端的路由器和带有线客户端的中继或者网桥之间转换。但是如果不需要配置就能自动切换这两种功能就更棒了。简单地说,我们的目标是:

  1. 当有线客户端接入并且请求 DHCP 时,路由器应该分配给它一个 IP,并纳入 LAN。
  2. 其他情况,一律视作 WAN 接口。

Currently, there is a solution to this on the net is to leverage the linux kernel functionality macvlan, and make a virtual interface based on the physical interface with different MAC. This approach just works, but I don’t like the way it shares all the packets of LAN and WAN on the wire without discrimination, and OpenWrt has no uci config for macvlan so far. So I think VLAN trunk is a better way, at least for me. This approach needs configurations on the wired LAN clients, such as Computers and other linux routers. Since the typical applications of mine involve only my own devices, it’s not a big deal.

目前有一种方法可以解决,就是利用 linux kernel 的 macvlan 功能,创建一个基于物理接口的虚拟接口,只是 MAC 不同。这种方法可以工作,不过我不喜欢它对数据包完全不加以区分的方式,并且 OpenWrt 没有专门针对的 macvlan 的配置文件。这种情况下我更倾向于 VLAN trunk。但是这种办法需要在客户端,比如电脑或者其他路由器上进行配置,不过我的典型应用只涉及到我自己的设备,所以这不是什么大问题。

The config on the OpenWrt side:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# /etc/config/network

config interface 'loopback'
    option ifname 'lo'
    option proto 'static'
    option ipaddr '127.0.0.1'
    option netmask '255.0.0.0'

config interface 'wan'
    option proto 'dhcp'
    option ifname 'eth0'

config interface 'lan'
    option type 'bridge'
    option proto 'static'
    option netmask '255.255.255.0'
    option ipaddr '172.16.0.1'
    option ifname 'eth0.2'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# /etc/config/wireless

config wifi-device 'radio0'
    option type 'mac80211'
    option macaddr 'xx:xx:xx:xx:xx:xx'
    option hwmode '11ng'
    option htmode 'HT20'
    list ht_capab 'SHORT-GI-20'
    list ht_capab 'SHORT-GI-40'
    list ht_capab 'RX-STBC1'
    list ht_capabb 'DSSS_CCK-40'
    option txpower '27'
    option country 'US'
    option channel '11'

config wifi-iface
    option device 'radio0'
    option mode 'ap'
    option ssid 'OpenWrt'
    option encryption 'none'
    option network 'lan'

eth0 is WAN, which should work without change; eth0.2 is a VLAN interface created by linux vconfig tagged with ID 2, which is not a hardware VLAN, and eth0.2 is bridged with wireless.

eth0 是 WAN 口,像往常一样不需变动;eth0.2 是 linux vconfig 创建的 VLAN 口, ID 为 2,并非硬件 VLAN,并且 eth0.2 和无线桥接到了一起。

On the client side, the configurations depend on the NIC driver. Most recent intel NICs have VLAN support with official drivers, so do the Realtek ones, but they need additional utility called Windows Diagnostic Program to do this. Here are screen shots:

在客户端这边,配置取决于网卡驱动,大部分现代的 Intel 网卡都支持 VLAN,Realtek 也支持,只不过需要专门的工具来进行配置。截图几枚:

TIL What the Emplace Operation Is in C++11

There are some new operations added to STL containers in C++11, which include emplace, emplace_hint, emplace_front, emplace_after and emplace_back.

Nearly all containers in STL have this kind of operations except std::array, because it’s immutable. All these operations share the common characteristic, which is ‘constructing elements in containers directly without copy or move’.

Motivation

Since C++11 introduced rvalue-reference and its moveable semantic, although a big improvement on performance, sometimes there are still some redundant copy operations. For instance, say we have:

1
2
vector<tuple<int, double, long double>> v;
v.push_back(make_tuple(2, 3.14, 2.71828));

First, the tuple is a POD, therefore no movable constructor, actually it is obvious you cannot move anything from within a POD object. The above code may make some copies of the tuple which depends on the compiler optimaztion.

Second, even when operating on moveable object, some copies still cannot be avoided. You can move a vector, but you still need copy the internal pointer that holds the resource.

Emplace can do better for this, thanks to variadic template and the perfect forwarding. Instead of the above code, we can do:

1
2
vector<tuple<int, double, long double>> v;
v.emplace_back(2, 3.14, 2.71828);

Here emplace_back will forward all the parameters to some internal function where the vector constructs its elements, just like what a tuple<int, double, long double> t(2, 3.14, 2.71828); does. No copies, no moves, constructs directly.

Another example

1
2
3
4
5
6
7
vector<vector<int>> vec_2d;

// add a vector filled with five ones

vec_2d.push_back(vector<int>(5, 1)); // using push_back, and expect to move

vec_2d.emplace_back(5, 1); //or using emplace_back, constructing it directly

Note you only need provide the parameters for the element’s constructor by order.

C++2011 Memory Model 笔记

C++0x 的内存模型怕是 0x 里面最难啃的骨头之一了吧,至少相对于那些语法糖的增加来说。每次回来看都有些新的收获,这里做个记录。没有系统地总结,尽量给出处吧。标准参考的是 n3291,和 FDIS 是一样的。

Memory ordering

C++ 的内存模型有3种 Memory Ordering

  1. sequential-consistent ordering

  2. acquire-release ordering

  3. relaxed ordering

标准中对 atomic type 的 order 定义如下(29.3):

1
2
3
4
5
6
7
8
9
10
    namespace std {
     typedef enum memory_order {
       memory_order_relaxed, // relaxed ordering
       memory_order_consume, // acquire-release ordering (acquire part)
       memory_order_acquire, // acquire-release ordering (acquire part)
       memory_order_release, // acquire-release ordering (release part)
       memory_order_acq_rel, // acquire-release ordering (both)
       memory_order_seq_cst  // sequential-consistent ordering
     } memory_order;
    }

默认是 memory_order_seq_cst,也就是 sequential-consistent,注意这里说的都是针对 atomic types。因为 C++ 标准不允许 non-atomic 的 data race,包含 data race 的程序视为 undefined behavior(1.10-21)。所以解决潜在的 data race 必须依赖 locks 以及 atomic operation。再强调一下,C++11 中凡是非 atomic type 的 data race 都是未定义行为,程序不可移植,等价于错误的程序。lock 这里不谈,因为使用上是符合直觉以及传统的,目的就是保护好 shared 对象,防止 data race。

Seqential Consistent Ordering

默认的 sequential-consistent model 是沿袭 Java 的内存模型(确切的说是 sequential-consistent with data-race-free),所有的 atomic 操作都可以看作满足唯一 total order 的操作,也就是说,可以把这样的多线程程序理解成一个有先有后交叉运行的单线程程序(对atomic type而言),这在推理时是有帮助的。每个 shared atomic 对象的改变在每个线程看来都是一样的,包括时间顺序以及值。这样一个默认的模型是相对来说符合直觉和容易处理的。举一个 Double-checked locking 的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    class singleton
    {
        mutable std::mutex m;
        mutable std::atomic<expensive_data *> data;

    public:
    // initialize data to 0 in constructor
    //.....
        const expensive_data* get_instance() const
        {
            expensive_data* result = data.load();
            if(!result)
            {
                std::lock_guard<std::mutex> lk(m);
                result = data.load();
                if(!result)
                {
                    result = new expensive_data;
                    data.store(result);
                }
            }
            return result;
        }
    };

这里的 load 和 store 默认参数都是memory_order_seq_cst, 等价于调用 data.load(std::memory_order_seq_cst)data.store(result, std::memory_order_seq_cst)

  1. shared data 要么在 mutex 内部,要么必须是 atomic type

  2. 这个正确实现是符合直觉的,而且它和其它平台(比如 Java)的实现几乎是一样的。只要不考虑其它的 ordering,正确性相对容易保证。

结论:默认的 sequential consistent 语义虽然相对保守,但是容易写出正确的程序,这也是推荐的处理方式。不到万不得已(性能不可接受),不要使用其它语义,即上面提到的另外两种 ordering

Relaxed Memory Ordering

一旦进入剩下两种 ordering 的世界,程序的编写就得格外小心——因为它处处违反直觉。举标准一例:

1
2
3
4
5
6
7
8
    // Thread 1:
    r1 = y.load(memory_order_relaxed);
    x.store(r1, memory_order_relaxed);


    // Thread 2:
    r2 = x.load(memory_order_relaxed);
    y.store(42, memory_order_relaxed);

x,y 初始值为0,该程序结果不唯一,这是显而易见的。但是 r1 == r2 == 42 也是一个正确的结果,是不是很违反直觉?要产生这个结果,那么实际顺序必须是:

1
2
3
4
    y.store(42, memory_order_relaxed);
    r1 = y.load(memory_order_relaxed);
    x.store(r1, memory_order_relaxed);
    r2 = x.load(memory_order_relaxed);
  1. 对于 thread 1 而言,thread 2 的执行不是顺序的(所谓的 program order)

  2. 整体顺序完全不确定,这4条指令完全可以以任意顺序排列(实际执行也许根本不是 sequential 的)

relaxed ordering 是最松散的一种语义:

  • 完全不参与多线程间的同步,编译器可以随便优化

  • 对于自身线程,data-dependency 产生的依赖需要满足,这是单线程程序正确性的要求

  • 此外它唯一的作用就是属于原子操作,所以不会产生 data race

Acquire-Release Ordering

该语义即同步语义,每一个 acquire 对应一个 release(跨线程的),详细定义参见标准(29.3-2)。需要注意的是 C++ 的同步语义不是先验的,我们无法通过之前的执行判断之后两个操作是否同步了,而只能通过推理所有的情况来验证程序的正确性。具体来说,对于一个可能的 acquire-release pair,我必须考虑他们同步时的情况,也需要考虑他们没有同步时的情况。推理 C++ 多线程程序的正确性,特别是在这两种 ordering 情况下,需要塑模 happens-before 的关系(标准1.10-12),通俗点说,就是推理出相关操作之间所有可能发生的顺序(或者根本就无序)。所以说带锁的多线程的程序很难写正确,更别说无锁的,可见一斑。 举例:

1
2
3
4
5
6
7
8
9
10
11
    // x0, x1 is atomic bool and initialized to false
    // victim is atomic int and initialized to 0
    // Thread 0
    x0.store(true, memory_order_relaxed);
    r0 = victim.exchange(0, memory_order_acq_rel);
    r1 = x1.load(memory_order_acquire);

    // Thread 1
    x1.store(true, memory_order_relaxed);
    r2 = victim.exchange(1, memory_order_acq_rel);
    r3 = x0.load(memory_order_acquire);

如何分析线程间可能的同步状态?因为 acquire 要和 release 对应,而两个线程中唯一可能同步的就是 victim 变量了,因为它的 exchange(即 read-modify-write 操作) 是 memory_order_acq_req,先读后写,读时 acquire,写时 release。这里先总结相关要点:

  1. 当一个 atomic 对象的 acquire 同步 release 时(不同线程),acquire 读到的值不一定是 release 上次写的值(不保证cache-coherence),也可能是之前该 atomic 对象的值。准确的说,对于一个 atomic 对象,它存在唯一一个 modification order,即该对象从诞生以来被修改的顺序,可以想成一个 list {init, x1, x2, x3, …}。而 acquire 读到的值可以是从上次读取到最近修改中间任意一个(包括上次读取的值)。假如 acquire 这次读到一个值 x1,那么下次 acquire 就可能读到 x1, x2, x3 等等,但是不可能读到 init 了。

  2. read-modify-write 操作含有特殊的语义,它每次都可以读到该 atomic 变量的最新的值。所以当该类操作同步时,读到的都是 modification order 最后的值。

    • 如果线程0中的 victim 读到 1,即 r0 返回 1,说明和线程1的 victim 写入同步,我们可以推理如下,按时间顺序:

      1. 线程1中 “x1 置为 true” happens before “victim 置为 1”,这是 program order 保证的

      2. 线程1中 “victim 置为 1” happens before 线程0 “victim exchange”,这是同步

      3. 线程0中 “victim 读到 1,然后置为 0” happens before “x1 load”

    所以结论是 r1 == true r2 == 1,而 r3 未知,可以是 true,也可以是初始值 false,因为他们没有同步关系。

    • 如果线程0中的 victim 读到 0,即 r0 返回 1,那么 victim 读取的是初始值 0,那么反过来线程1的 victim 就会读到线程0 的 victim 值了:

      1. 线程0中 “x0 置为 true” happens before “victim 置为 0”

      2. 线程0中 “victim 置为 0” happens before 线程1中 “victim exchange”

      3. 线程1中 “victim 读到 0,然后置为 1” happens before “x0 load”

    所以结论是 r3 == true r2 == 0,r1 是不确定的。

这里只有两个线程,同时各执行一次,atomic 修改的次数也不多。所以推理起来尚属容易。

Peterson’s lock

最后以一个 Peterson’s lock 为例结束本文。该例是从 这篇blog 抄来的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
    std::atomic<int> flag0(0),flag1(0),turn(0);

    void lock(unsigned index)
    {
        if (0 == index)
        {
            flag0.store(1, std::memory_order_relaxed);
            turn.exchange(1, std::memory_order_acq_rel);

            while (flag1.load(std::memory_order_acquire)
                && 1 == turn.load(std::memory_order_relaxed))
                std::this_thread::yield();
        }
        else
        {
            flag1.store(1, std::memory_order_relaxed);
            turn.exchange(0, std::memory_order_acq_rel);

            while (flag0.load(std::memory_order_acquire)
                && 0 == turn.load(std::memory_order_relaxed))
                std::this_thread::yield();
        }
    }

    void unlock(unsigned index)
    {
        if (0 == index)
        {
            flag0.store(0, std::memory_order_release);
        }
        else
        {
            flag1.store(0, std::memory_order_release);
        }
    }

Peterson lock 是最简单的锁实现之一,它只提供两个线程之间的锁同步。该实现为了追求性能,采用了更弱的 ordering,所以这意味着正确性需要严格的证明。这里只总结一下要点:

  1. 代码并非处处使用 acquire-release 同步,也有使用 relaxed ordering 的地方,但是正确性需要严格证明

  2. 正确的关键在 turn.exchange 的使用和处理,exchange 保证了读取的值是最近修改的,而 memory_order_acq_rel 则保证了同步,从而使得跨线程的 happens-before 关系正确建立,并且 while 循环里 turnflag 可以读到最新的值

完整的证明可以参考出处

值得一提的是,如果采用 sequential-consistent ordering —— flagturn 都用 memory_order_seq_cst 的话 —— 立刻就得到一个正确的实现,这也是目前大多数 peterson lock 的实现。基于 sequential-consistent 的 peterson lock 证明也更容易,可以参考 The Art of Multiprocessor Programming 第二章。

本文参考:

  1. C++ atomics and memory ordering

  2. The Inscrutable C++ Memory Model

  3. Peterson’s lock with C++0x atomics

  4. n3291

  5. 为什么程序员需要关心顺序一致性(Sequential Consistency)而不是Cache一致性(Cache Coherence?)

Posted via UltraBlog.vim.

使用 StrongSwan 搭建 Ipsec VPN (2) for iOS

再不写这个 part 2,搞不好就胎死腹中了。好嘛,书接上回,这次我们在 part 1 的基础上搭建一个 iOS 可以用的 ipsec VPN。

iOS 其实还可以用 L2TP VPN,这里且不谈我对 L2TP over ipsec 的无尽bs,如果要支持手上众多设备,还是采用纯 ipsec 的解决方案比较好。顺便吐个槽,窃以为支不支持以 Cisco 为代表的众多商用 VPN 服务器是一个移动设备是否成熟到胜任商业应用的标准,对,说的就是你,Android 。人家 iOS 和塞班都支持,你都出道几年了?看不到怨声载道吗?

正文

  • 服务器

首先在 part 1 的基础上,修改三个配置文件就 OK 了:

ipsec.conf:

在配置文件把 setup 一节开头添加两行:

1
2
3
4
5
6
7
config setup
    # add pluto for ikev1
    plutostart=yes
    nat_traversal=yes  
    strictcrlpolicy=no
    charonstart=yes
    uniqueids=yes

然后在末尾添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
conn ipad
    type=tunnel
    authby=xauthrsasig
    xauth=server
    keyexchange=ikev1
    ike=aes128-md5-modp1024
    esp=aes128-md5
    auto=add
    modeconfig=push
    compress=yes
    dpdaction=clear
    pfs=no
    leftcert=strongswan.crt
    leftsubnet=0.0.0.0/0
    right=%any
    rightsourceip=192.168.66.248/29
    rightca=%same

注意到 authby=xauthrsasig 这一行,因为 iOS 设备采用 rsa + xauth 的混合认证,需要同时配置证书和密码认证。服务器端证书 strongswan.crt 的颁发和配置参考 part 1 的说明。同样的,ip 地址段按自己喜好随意,因为 part 1 已经配置了一个 ip 段,这里就复用一下,防火墙的配置也可以省省了。

ipsec.secret:

同上,在末尾添加:

1
username : XAUTH "password"

用户名和密码自己随意,其他保持不变

strongswan.conf:

修改 pluto 一项为:

1
2
3
4
pluto {
    dns1 = 8.8.8.8

}

这里一样加上 google 的 dns 以防万一客户端没有被推送到默认的 dns

  • 客户端

首先按照 part 1 证书一节所述,给你的 iOS 设备颁发证书,假设得到的是 iOS.p12,然后把该文件和 ca.crt (iOS 对证书格式要求不高,不一定要是 der) 一起传到设备上。我习惯通过 email,然后在 safari 里面点击附件就可以安装了。

最后的最后,还是截个图了事,一图胜千言啊:

设置的时候选择你刚刚安装的证书就行了。

update 2011/5/21: 这里有一份官方 wiki 专门讲 iOS 配置,一并参考: http://wiki.strongswan.org/projects/strongswan/wiki/IOS_(Apple))

如果你觉得这篇文章有用,可以考虑给我点 Bitcoin 小费: 14kB4s43d1CNy4VipGfpfennivJAgHkEnK

Posted via UltraBlog.vim.

使用 StrongSwan 搭建 Ipsec VPN (1) for Nokia Mobile VPN Client

背景

话说如今这年头,翻墙已经不是要不要,而是怎么翻的问题。所以网上各种免费的收费的代理VPN满天飞,活生生在天朝创造了一个新兴的市场。可是对于使用 nokia s60 的用户来讲,各种不方便啊。ssh 代理不好用,vpn 呢官方客户端只支持 ipsec,还有个商业客户端 symVPN 支持 pptp 但是太贵且不实用。经过大半个月的摸索,笔者最终使用开源软件 strongSwan 成功搭建了 Nokia S60 3rd 可以使用的VPN服务器。(理论上 v5 symbian3 应该也行,只要使用的是 Nokia Mobile VPN,Maemo 就不提了)。

网上有关搭建Nokia VPN的中文文章据我所知只有 alpha2beta 这一篇,该文采用的是 openswan,和 strongswan 其实差不太多,都实现了 ipsec 协议。强烈推荐先去那里看看,如果:

  1. 你想要快速搭建一个 Nokia 手机可用的 VPN,因为那篇文章采用的是 PSK 认证(pre-shared keys),所以相对来说准备工作和步骤要少很多,实际上会容易一些
  2. 你并不需要同时支持其它客户端,比如 iOS,Windows 等等

如果你和笔者一样需要支持手头上的众多设备,那么请继续往下看吧。在本文基础上,可以很容易添加其它的设备。不过请做好心理准备,因为 ipsec 是公认的难搞,再加上 Nokia 设备的极度不友好,所以请一定要有耐心。笔者曾经碰到到相同的配置应用到 Nokia 上,第一次行第二次不行的情况,实在是太诡异了。

不过搞定 ipsec 的一个好处是,ipsec 这样一个古老标准有着极其广泛的支持,几乎所有支持 VPN 的客户端原生支持着某种 ipsec 协议(大多数是兼容 Cisco 的 VPN 或者 L2TP over ipsec),不像 openvpn 或者 pptp 配置起来很容易,但是往往设备不支持或者需要另外安装。

本文假设

  1. 基本的 linux 使用经验,会 make,会 build,会查 log,会配 iptables,当然您也可以现在就学起来~

  2. 有一台 public ip 的linux服务器,如果是 VPS 最好是 xen 的( openvz 似乎不能安装 strongSwan ),并且服务器的public ip 是分配在某个 interface 上的,比如eth0,这意味服务器不能在NAT后面。(在NAT后面其实也是可以的,但是那会涉及到更多复杂的情况,本文不考虑,好在如今VPS不是都像EC2这样玩的)

  3. 有一个指向该 ip 的域名

步骤

  • 建立自己的 CA & 颁发证书

  • 安装&配置 StrongSwan

  • 配置手机客户端

  • 杂项

建立自己的 CA & 颁发证书

因为针对不同设备需要不同的配置,而 psk 是共享同一个密钥,所以服务器无法区分客户端。因此我们需要采用 x.509 证书认证。关于 x.509 体系的具体情况,请查阅相关资料,笔者也不是这方面的专家,不敢误人子弟。大致来说,就是通过同时颁发证书给服务器和客户端使它们互相信任并且识别对方,达到认证的目的,另外好处是比密码更安全。

颁发证书一般都采用 openssl。为了简便,尽量少的涉及到细节,这里采用 easy-rsa —— 一套脚本来完成这种任务。OpenVPN 项目就含有这一套脚本,这里以 linux 下的操作为例。Windows 的 OpenVPN 安装包也可以完成类似任务,不赘述。

将 easy-rsa 目录 copy 到你的工作目录,比如 $HOME 。easy-rsa 一般在 OpenVPN 的例子目录中,比如 /usr/share/openvpn/easy-rsa ,然后进入easy-rsa。我们需要修改两个文件:

  • openssl.cnf (如果 openssl 的版本是0.9.6,那就是修改openssl-0.9.6.cnf) 以下带有注释的行是需要增加或者修改的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
......
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer:always
extendedKeyUsage=clientAuth      # add or modify this line
......
......
[ server ]

# JY ADDED -- Make a cert with nsCertType set to "server"
basicConstraints=CA:FALSE
nsCertType          = server
nsComment           = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer:always
extendedKeyUsage=clientAuth, serverAuth, 1.3.6.1.5.5.8.2.2   # add or modify this line
subjectAltName=DNS:your.vpn.domain                           # replace with your domain
......

subjectAltName 可以指定为 ip 或者域名,域名显然更灵活一些

  • vars 修改最后几行,主要是证书的名称,机构,email等等,对应你自己的情况。例如:
1
2
3
4
5
export KEY_COUNTRY="TC"
export KEY_PROVINCE="HX"
export KEY_CITY="modu"
export KEY_ORG=""
export KEY_EMAIL="whatever@xxx.com"

另外,KEY_SIZE 可以是1024也可以是2048,2048理论上更安全一些。

然后,执行以下命令:

1
2
3
4
. ./vars
./clean-all
./build-dh
./build-ca

过程中会问你一些问题,因为上面设置过了,大多数可以直接回车。 末了,会在 keys 目录下生成 ca.crt 和 ca.key,前者是证书,后者是 key,key 是不能公开的

然后,继续:

1
./build-key-server your.vpn.domain

替换 your.vpn.domain 为你自己的域名。生成一大堆东西,我们需要的包括 your.vpn.domain.crt 和 your.vpn.domain.key,这里用域名的原因是证书中的 common name 需要指定域名,作为参数传进去省事;当然你也可以手动指定。

继续:

1
./build-key-pkcs12 client_name

过程和 server 的差不多,末了,会让你输入一个密码。这个密码是用来打包证书和 key 的。目录下会产生 client_name.crt,client_name.key 以及 client_name.p12

最后还有一步,执行:

1
openssl x509 -inform PEM -outform DER -in keys/ca.crt -out keys/ca.cer

这一步其实是 Symbian 需要的,它只能识别 der 格式,所以需要转换一下。 这样,CA 和证书就弄好了,这里我们建立了一个 root CA,一对服务器 证书/密钥, 一对客户端 证书/密钥。注意一定要保存好 keys 目录的现场,因为以后还需要颁发更多的证书。下文中我将 your.vpn.domain.crt/key 改名为 strongswan.crt/key, 客户端则假设为 E71.crt/key/p12

安装&配置 strongSwan

strongSwan 我使用的最新的 release 4.5.1,你可以使用自己的 distro 自带的版本,也可以自行 build,这里略过。唯一需要注意的是,你使用的版本最好支持 NAT traversal,虽然对于本文针对的 Nokia VPN 的情况下不一定需要,但是很多情况下客户端在 NAT 后面并且使用 ikev1 的时候是需要的(特别是以后关于 iOS 的部分)。 NAT traversal 是由一个编译选项 enable-nat-transport 决定的。这里给我 build 时使用的 config 作为参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
./configure   --prefix=/usr --sysconfdir=/etc --libexecdir=/usr/lib \
              --with-ipsecdir=/usr/lib/strongswan \
              --enable-sqlite --enable-smartcard --enable-cisco-quirks \
              --enable-openssl --enable-curl \
              --enable-sql --enable-attr-sql \
              --enable-farp --enable-dhcp \
              --enable-eap-sim --enable-eap-sim-file --enable-eap-simaka-pseudonym \
              --enable-eap-simaka-reauth --enable-eap-identity --enable-eap-md5 \
              --enable-eap-gtc --enable-eap-aka --enable-eap-aka-3gpp2 \
              --enable-eap-mschapv2 --enable-eap-radius \
              --enable-ha \
              --enable-nat-transport \
              --disable-mysql --disable-ldap \
              --disable-static --enable-shared

安装好以后,可以看看是否存在 ipsec 命令,以及是否存在 /etc/ipsec.d/ 目录

  • 安装证书 这里我们需要的证书和密钥包括 ca.crt,strongswan.crt,strongswan.key。 将 ca.crt copy 到 /etc/ipsec.d/cacerts/ ,strongswan.crt copy 到 /etc/ipsec.d/certs/ ,strongswan.key copy 到 /etc/ipsec.d/private/

  • 配置 /etc/ipsec.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# ipsec.conf - strongSwan IPsec configuration file

config setup
    strictcrlpolicy=no
    charonstart=yes
    uniqueids=yes

conn %default
    authby=rsasig
    leftrsasigkey=%cert
    rightrsasigkey=%cert
    left=%defaultroute

conn ikev2
    keyexchange=ikev2
    leftsubnet=0.0.0.0/0
    leftcert=strongswan.crt
    rightca=%same
    dpddelay=90
    dpdtimeout=90
    dpdaction=clear
    right=%any
    rightsourceip=192.168.66.240/29
    auto=add

这里 192 打头的地址段是分配给客户端的虚拟 ip,你可以任意配置,我这里掩码用了29 bit,所以同时支持的客户端只有8个

/etc/strongswan.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# strongswan.conf - strongSwan configuration file

charon {
    # number of worker threads in charon
    threads = 16

    # send strongswan vendor ID?
    # send_vendor_id = yes

    dns1 = 8.8.8.8

    filelog {
            /var/log/charon.log {
                # loggers to files also accept the append option to open files in
                # append mode at startup (default is yes)
                # the default loglevel for all daemon subsystems (defaults to 1).
                default = 1
            }
            stderr {
                # more detailed loglevel for a specific subsystem, overriding the
                # default loglevel.
                ike = 2
                knl = 3
            }
    }

    plugins {

            sql {
              # loglevel to log into sql database
              loglevel = -1

              # URI to the database
              # database = sqlite:///path/to/file.db
              # database = mysql://user:password@localhost/database
          }
    }

}

pluto {

}

libstrongswan {

    #  set to no, the DH exponent size is optimized
    #  dh_exponent_ansi_x9_42 = no

}

dns1 是 push 到客户端的 dns,这里用的 google anycast dns,另外 filelog 是一些 log 选项

/etc/ipsec.secrets

1
: RSA /etc/ipsec.d/private/strongswan.key

这里指定的是服务器使用的密钥,冒号之前可以用具体的 ip 来限制客户端,这里表示任意客户端。

  • 防火墙 ipsec 需要打开 udp 500,udp 4500,ESP (协议号50),AH (协议号51). 你还需要将服务器配置为一个路由器(如果不是的话),这意味你需要打开 ip 转发,并且要为你的虚拟 ip 段做 SNAT (除非你玩的是ipv6)。这部分内容请参考其他资料,一般不同的发行版都有专门文档阐述。

你可以尝试启动 ipsec 服务,如果没有错误信息的话,服务器就配置好了。log 信息是在 /var/log 下面 auth.log 以及 charon.log

配置手机客户端

Nokia Mobile VPN Client,E系列我记得是自带的,其他手机可能要手动下载安装。另外,这里讨论的客户端版本>=3.1,因为3.1之后的客户端支持用一个后缀为 vpn 的 zip 包作为 vpn 策略文件,很方便。之前的版本需要创建 sis 文件,搞不好还要签名。所以这里只考虑3.1以后的。据我所知大都能升级到至少3.1。

目前网上的文章都是基于手工制作 vpn 策略文件,很容易出错。所幸我找到一个官方的程序可以自动生成策略文件,减少了出错的可能。

下载地址 需要 .Net Framwork 2.0

现在切换到 Windows 平台下面。准备以下文件 ca.cer (不是ca.crt),E71.p12,都是建立 CA 时生成的。过程都用截图算了,很简单。在 vista 和 win7 下需要管理员权限来执行。

注意正确替换你的域名或ip

将 cert store 改为 DEVICE,这样不用每次启动 VPN 都要输入密码

然后执行 generate VPN policy,就OK了。至于如何安装及其设置接入点,网上已有很多资料,不再赘述。比如这里这里

设置好接入点之后就可以测试了。 如果连接失败了,一方面需要看手机上的日志(虽然信息量有限),另一方面需要看服务器上日志,特别是 charon.log,这是 ikev2 的日志。 如果能连上,但是不能上网,那8成是服务器没有做好 ip 转发或者 SNAT 的问题,还有可能是 DNS 的问题

杂项

最后说一下相关细节,没兴趣可以不看。网上其它有关使用 openswan 或者 strongSwan 配置 ipsec 的文章大都使用的 ikev1 协议,但是 Nokia 的客户端恰好支持更先进的 ikev2 协议(不能不赞一下~),具有更好的连接稳定性,更好的 NAT 穿透性。之前我配置 Nokia VPN 和 openswan 使用 ikev1 也成功了,但是因为该死的 E71 的 bug (也许吧,看这里)要给 openswan 打补丁才行,整个折腾过程更悲催。经 @tjmao 提醒才想起对 ikev2 支持更好的 strongSwan,才有了本文。

另外,如果你通过本文,成功建立了 VPN 服务器。那么一个 bonus 就是,你同时支持了 windows 7 的 ikev2 客户端,只要给 win7 也颁发安装证书,然后可以了。而且从此远离恶心的 L2TP over ipsec。你可以看这里 以及 这里

最后的最后,本文一部分是边实验边写的,还有一部分是凭记忆写的,所以如果有什么问题,欢迎在下面提出来,看看有什么错误或者漏掉的。

Good luck~

Posted via UltraBlog.vim.