server将topology推送至client,这就是为何必须实现net30模式的原因,就是为了兼容windows,由于windows的低版本tap-win32驱动只支持这种模式,而后的topology为p2p(实际上windows的tap-win32驱动通过简单修改,比如改掉自问自答的那一部分逻辑看上去也支持这种拓扑,但是如果涉及到dhcp,事情就复杂了,因为通过net30拓扑分给windows的两个主机ip中有一个会充当虚假的dhcp服务器,如果在windows上采用了p2p拓扑,那么tap-win32网卡将直连server的虚拟网卡,而server的虚拟网卡可不敢担此大任),对于server来讲,没有什么变化,只是对于client来讲,解放了ip的占用,但是client必须是非windows的,不过server倒是可以是 windows,因为对于net30和p2p而言,server端的虚拟ip地址是一样的,不同的是对client的影响
vpn服务器中有一个ifconfig_pool结构体用于保存ip地址池的重要信息:
struct ifconfig_pool
{
in_addr_t base; //初始ip地址
int size; //pool的大小,也就是容量,即能同时分配给几个client
int type; //指示topology
bool duplicate_cn;
struct ifconfig_pool_entry *list;//每一个pool保存一个链表,用于遍历和查找,每次分配和释放时要用
};
服务器启动的时候首先要初始化ip地址的pool,用于以后为连接的客户端分配ip地址,在multi_init有以下代码:
if (t->options.ifconfig_pool_defined) {
if (dev == DEV_TYPE_TAP) { //tap模式的初始化
m->ifconfig_pool = ifconfig_pool_init (IFCONFIG_POOL_INDIV,
t->options.ifconfig_pool_start,
t->options.ifconfig_pool_end,
t->options.duplicate_cn);
} else if (dev == DEV_TYPE_TUN) { //tun模式的初始化
m->ifconfig_pool = ifconfig_pool_init (
(t->options.topology == TOP_NET30) ? IFCONFIG_POOL_30NET : IFCONFIG_POOL_INDIV,
t->options.ifconfig_pool_start,
t->options.ifconfig_pool_end,
t->options.duplicate_cn);
}
...
}
struct ifconfig_pool *ifconfig_pool_init (int type, in_addr_t start, in_addr_t end, const bool duplicate_cn)
{
struct gc_arena gc = gc_new ();
struct ifconfig_pool *pool = NULL;
pool->type = type;
pool->duplicate_cn = duplicate_cn;
switch (type) {
case IFCONFIG_POOL_30NET://兼容诸如windows的tap-win32之类驱动,每次分配4个ip地址,当中除去一个网络地址一个广播地址,仅剩下两个可用,这就是tap-win32网卡的本地地址和虚拟出来的远端地址(详情可见arp自问自答)
pool->base = start & ~3;//252掩码模式中,ip分配是按照4个一组分配的,因此要按4对齐
pool->size = (((end | 3) + 1) - pool->base) >> 2;//最后要除以4,因为4个ip一组
break;
case IFCONFIG_POOL_INDIV://其余的模式就是实打实得分配,是几个就是几个
pool->base = start;
pool->size = end - start + 1;
break;
...
}
...
}
static void multi_select_virtual_addr (struct multi_context *m, struct multi_instance *mi)
{
struct gc_arena gc = gc_new ();
...//省略从文件中导入ip地址,只看动态分配
else if (m->ifconfig_pool && mi->vaddr_handle < 0) {
in_addr_t local=0, remote=0;
const char *cn = NULL;
if (!mi->context.options.duplicate_cn)
cn = tls_common_name (mi->context.c2.tls_multi, true);
mi->vaddr_handle = ifconfig_pool_acquire (m->ifconfig_pool, &local, &remote, cn); //见下面分析
if (mi->vaddr_handle >= 0) {
const int tunnel_type = TUNNEL_TYPE (mi->context.c1.tuntap);
const int tunnel_topology = TUNNEL_TOPOLOGY (mi->context.c1.tuntap);
mi->context.c2.push_ifconfig_local = remote; //无论如何都要将从pool中找到的ip地址给与client,余下的client虚拟网卡的配置信息还差两个,如果是net30或者p2p拓扑的话,需要一个“对端”地址,如果是subnet拓扑的话,需要一个子网掩码,故push_ifconfig_remote_netmask的含义就是既可以表示“对端”地址又可以表示子网掩码
if (tunnel_type == DEV_TYPE_TAP || (tunnel_type == DEV_TYPE_TUN && tunnel_topology == TOP_SUBNET)) {
//对于subnet来讲,最简单,直接将从pool中找到的ip地址分配给client,然后将自己的子网掩码给client的子网掩码
mi->context.c2.push_ifconfig_remote_netmask = mi->context.options.ifconfig_pool_netmask;
if (!mi->context.c2.push_ifconfig_remote_netmask)
mi->context.c2.push_ifconfig_remote_netmask = mi->context.c1.tuntap->remote_netmask;
} else if (tunnel_type == DEV_TYPE_TUN) { //只有tun模式拥有topology的概念
if (tunnel_topology == TOP_P2P) //对于p2p来讲,直接将server端虚拟网卡的ip给client作为ifconfig命令pointopoint的另一端,注意client不能是windows这种多事又麻烦的系统
mi->context.c2.push_ifconfig_remote_netmask = mi->context.c1.tuntap->local;
else if (tunnel_topology == TOP_NET30) //对于net30来讲,
mi->context.c2.push_ifconfig_remote_netmask = local;
}
if (mi->context.c2.push_ifconfig_remote_netmask)
mi->context.c2.push_ifconfig_defined = true;
...
}
ifconfig_pool_handle ifconfig_pool_acquire (struct ifconfig_pool *pool, in_addr_t *local, in_addr_t *remote, const char *common_name)
{
int i;
i = ifconfig_pool_find (pool, common_name); //在所有可用的ip-entry中选择一个索引,注意并不是直接选择ip,选择ip的逻辑在下面。OpenVPN内核将每次分配给client的一系列(一个或者四个)ip地址组织成ifconfig_pool_entry结构体,这样就将ip的分配和topology的配置分离了。该算法很简单,就是遍历链表,然后选出in_use为0的。
if (i >= 0) {
struct ifconfig_pool_entry *ipe = &pool->list[i];
ifconfig_pool_entry_free (ipe, true);
ipe->in_use = true; //设置使用标志
if (common_name)
ipe->common_name = string_alloc (common_name, NULL);
switch (pool->type) {
case IFCONFIG_POOL_30NET: {
in_addr_t b = pool->base + (i << 2); //由于按照4个一组分配,所以要i*4
*local = b + 1; //对于client来讲是remote
*remote = b + 2; //对于server来讲是remote
break;
}
case IFCONFIG_POOL_INDIV: {
in_addr_t b = pool->base + i;
*local = 0; //其余的两种topology,client直连server的实际虚拟ip
*remote = b; //直接将可用的ip地址分配给client,无需再分配一个模拟的server端ip
break;
}
...
}
}
return i;
}
因此,OpenVPN为client端分配ip地址实际上很简单,总结如下:
tun模式:
-
subnet:
a. 从pool中选择一个ip作为client的虚拟网卡ip;
b. 将自己的子网掩码作为client的子网掩码。
-
p2p:
a.从pool中选择一个ip作为client的虚拟网卡ip;
b.将自己的实际虚拟网卡ip作为client的对端ip。
-
net30:
a.从pool中选择4个掩码为30的ip,将中间两个ip中的大者作为client的虚拟网卡ip;
b.将小者作为client的对端ip。
tap模式:
- 完全按照tun模式的1来分配。
信息一旦被push到了client端,client就会打开虚拟网卡设备,并且初始化之,然后会按照server端push过去的信息进行虚拟网卡的配置,最终调用do_ifconfig完成网卡配置,在这最后一步中,需要按照不同的OS进行不同的定制,可以想见,windows还是最多事最麻烦的,事实证明正是如此。
可以看到,ip地址的分配逻辑主要在multi_select_virtual_addr中进行,可是multi_select_virtual_addr是在何时被调用的呢?不考虑复杂的情况,最基本的是在multi_connection_established中被调用的,而multi_connection_established则是在client连接到server的时候被调用的。client连接server的时候,首先要进行TLS/SSL握手,这个过程在tls_process中进行,一旦成功将会调用link_socket_set_outgoing_addr,之后逐步地到达multi_connection_established,由此可见,隧道是在TLS握手成功之后才建立的,隧道的建立和client的虚拟网卡被初始化是同时进行的。tls_process是在check_tls的路径中被调用的,而check_tls则是vpn隧道建立过程中的第一步。