这是我上一个问题的延续从 Freeradius DHCP 服务器发送静态路由的实现与 Strongswan VPN 服务器结合使用。
当使用 tcpdump 和 Wireshark 调试 Freeradius 时,我发现我可以通过向dhcp 服务器配置文件的我的和部分添加DHCP-Classless-Static-Route
和DHCP-Site-specific-25
(又名 Microsoft 静态路由)选项从 Freeradius DHCP 服务器发送无类静态路由。DHCP-Discover
DHCP-Request
0.0.0.0
但是:如果我将默认网关设置为建议的,则Microsoft VPN 客户端似乎不接受静态路由Strongswan 文档。
至少我在使用时无法在 Windows 客户端上找到所宣传的路线route print -4
。
0.0.0.0
另外,当我使用VPN 接口上的标准网关时,我无法在 Windows 客户端上手动添加路由。
然而:
假设我想192.168.200.0/24
通过 VPN 访问子网,并且我的 VPN 服务器将地址分配192.168.201.2/24
给我的 Windows 客户端。那么实际上可以在 Windows 客户端上创建静态路由,方法是使用 Windows 命令声明子网 192.168.200.0/24 可通过 192.168.201.2 访问:
route add 192.168.200.0 mask 255.255.255.0 192.168.201.2
我知道这看起来有点奇怪,但我可以 ping 子192.168.200.0
网上的任何主机,所以只要它能工作我就很高兴。:-)
但是:如果我可以通过从我的 VPN 服务器宣传路由来做同样的事情而不是在所有 VPN 客户端上手动执行此操作,我会更高兴。:-)
这意味着我必须对 Freeradius 中的 DHCP 配置进行一些动态编程。就我而言,这意味着我必须在 DHCP-Discover 和 DHCP-request 中引用一个 perl 模块,该模块会获取分配的客户端 vpn ip 地址,将其转换为八位字节,并将其与同样以八位字节给出的静态路由相结合。
一个例子:
子网将按照首先对子网掩码进行编码的方式192.168.200.0/24
进行编码。0x18c0a8c8
客户端192.168.201.2/24
将被编码,就像0xc0a8c902
将 IP 地址中的每个数字转换为十六进制一样。
该路线的最终编码将是:0x18c0a8c8c0a8c902
因为它只是两个字符串的连接。
然后我必须使用update reply
以下代码:
update reply {
&DHCP-Classless-Static-Route = 0x18c0a8c8c0a8c902
&DHCP-Site-specific-25 = 0x18c0a8c8c0a8c902
}
如果还有更多路线,则所有路线将被连接成一个长字符串。
棘手的部分:
假设您拥有文件中所含的 Freeradius DHCP 服务器的默认配置freeradius/3.0/sites-available/dhcp
。
DHCP-Discover 和 DHCP-Request 文件的一般结构如下:
dhcp DHCP-Request {
update reply {
&DHCP-Message-Type = DHCP-Ack
}
update reply {
# General DHCP options, such as default GW, DNS, IP-address lease time etc.
}
update control {
&Pool-Name := "vpn_pool"
}
dhcp_sqlippool
ok
}
据我所知,我需要在dhcp_sqlippool
调用之后和返回之前调用我的 perl 模块ok
,因为dhcp_sqlippool
该模块将 ipaddress 分配给 VPN 客户端。
这意味着我的版本将是这样的:
dhcp DHCP-Request {
update reply {
&DHCP-Message-Type = DHCP-Ack
}
update reply {
# General DHCP options, such as default GW, DNS, IP-address lease time etc.
}
update control {
&Pool-Name := "vpn_pool"
}
dhcp_sqlippool
perl
# If perl module returned no error
if(ok) {
update reply {
# Perl-Route contains a hex encoded string with all routes.
&DHCP-Classless-Static-Route = Perl-Route
&DHCP-Site-specific-25 = Perl-Route
}
}
# Not sure if this one is needed?
update reply {
&DHCP-End-Of-Options = 255
}
ok
}
为了使其工作,我必须在freeradius/3.0/mods-enabled
文件夹下启用 perl 并修改文件名以freeradius/3.0/mods-enabled/perl
将其指向我的 perl 模块。例如:
filename = ${modconfdir}/${.:instance}/dhcp/Options.pm
但是我该如何以正确的方式引用对 perl 的调用?
我认为我必须启用该行func_post_auth = post_auth
并在我的 perl 模块中freeradius/3.0/mods-enabled/perl
创建一个sub post_auth
部分来处理来自 Freeradius 的调用,但据我在我的日志中看到,我在 Freeradius 中收到以下错误:
(8) perl: perl_embed:: module = /etc/freeradius/3.0/mods-config/perl/dhcp/Options.pm ,
func = post_auth exit status= Undefined subroutine &main::post_auth called.
...
(8) [perl] = fail
(8) } # dhcp DHCP-Discover = fail
那么我没有看到的到底是什么呢?
答案1
我几次撞墙,但至少我让 perl 模块工作了,虽然我还没有完全达到我想要的状态,因为通过 DHCP 的静态路由不会通过 Strongswan 从 Freeradius DHCP 服务器传递到 VPN 客户端,但从 Freeradius DHCP 服务器调试 UDP 包意味着问题出在其他地方。
无论如何,这就是我所做的:
- 启用 perl 模块
freeradius/3.0/mods-enabled
并至少设置以下几行:
perl {
# Perl code location: ("freeradius/3.0/mods-config/dhcp/Options.pm")
filename = ${modconfdir}/${.:instance}/dhcp/Options.pm
# DHCP module is called during freeradius post_auth
func_post_auth = post_auth
}
- 修改
freeradius/3.0/sites-enabled/dhcp
相关的地方是DHCP-Discover
和DHCP-Request
:
dhcp DHCP-Discover {
update reply {
DHCP-Message-Type = DHCP-Offer
}
# The contents here are invented. Change them!
update reply {
&DHCP-Domain-Name-Server = 192.168.200.1
&DHCP-Subnet-Mask = 255.255.255.0
&DHCP-IP-Address-Lease-Time = 86400
&DHCP-DHCP-Server-Identifier = 192.168.200.4
}
# Or, allocate IPs from the DHCP pool in SQL. You may need to
# set the pool name here if you haven't set it elsewhere.
update control {
&Pool-Name := "vpn_pool"
}
dhcp_sqlippool
# Call static route generation.
perl
ok
}
dhcp DHCP-Request {
# Response packet type. See DHCP-Discover section above.
update reply {
&DHCP-Message-Type = DHCP-Ack
}
# The contents here are invented. Change them!
update reply {
&DHCP-Domain-Name-Server = 192.168.200.1
&DHCP-Subnet-Mask = 255.255.255.0
&DHCP-IP-Address-Lease-Time = 86400
&DHCP-DHCP-Server-Identifier = 192.168.200.4
}
# Or, allocate IPs from the DHCP pool in SQL. You may need to
# set the pool name here if you haven't set it elsewhere.
update control {
&Pool-Name := "vpn_pool"
}
dhcp_sqlippool
# Call static route generation.
perl
ok
}
- 创建位于以下位置的 perl 代码
freeradius/3.0/mods-config/perl/dhcp/Options.pm
:
use strict;
use warnings;
use Data::Dumper;
use Net::IP;
# Bring the global hashes into the package scope
our (%RAD_REQUEST, %RAD_REPLY, %RAD_CHECK);
#
# This the remapping of return values
#
use constant {
RLM_MODULE_REJECT => 0, # immediately reject the request
RLM_MODULE_OK => 2, # the module is OK, continue
RLM_MODULE_HANDLED => 3, # the module handled the request, so stop
RLM_MODULE_INVALID => 4, # the module considers the request invalid
RLM_MODULE_USERLOCK => 5, # reject the request (user is locked out)
RLM_MODULE_NOTFOUND => 6, # user not found
RLM_MODULE_NOOP => 7, # module succeeded without doing anything
RLM_MODULE_UPDATED => 8, # OK (pairs modified)
RLM_MODULE_NUMCODES => 9 # How many return codes there are
};
# Same as src/include/radiusd.h
use constant L_DBG=> 1;
use constant L_AUTH=> 2;
use constant L_INFO=> 3;
use constant L_ERR=> 4;
use constant L_PROXY=> 5;
use constant L_ACCT=> 6;
# Function to handle post_auth
sub post_auth {
# Get VPN Client IP from Freeradius DHCP server.
my $client_ip = new Net::IP ( $RAD_REQUEST{'DHCP-Requested-IP-Address'} ) or die (Net::IP::Error());
# An example of 2 routing rules sent ('192.168.20.0/24' and '192.168.200.0/24')
my @routes = (new Net::IP('192.168.20/24'), new Net::IP('192.168.200/24'));
# Measure how many elements there is in the routes array.
my $size = @routes;
# Convert client ip into hex code.
my $client_octets = get_ip_octets ($client_ip->ip(),$client_ip->prefixlen());
# Freeradius want the encoded string start with '0x'
# followed by the encoded octets as hex.
my $octet_str = "0x";
for(my $i = 0; $i < $size; $i++)
{
# Convert subnet into octets, skipping ending zeroes.
my $route_octets = get_ip_octets ($routes[$i]->ip(),$routes[$i]->prefixlen());
# Convert network prefix into octets
my $hex_prefix = sprintf("%02x", $routes[$i]->prefixlen());
# Route is encoded by network octets followed by subnet octets
$route_octets = $hex_prefix . $route_octets;
# The entire route string is the route octets followed by gateway octets ('the client vpn ip').
my $route_str = $route_octets . $client_octets;
$octet_str = $octet_str . $route_str;
}
# Classless static routing (dhcp option 121)
$RAD_REPLY{'DHCP-Classless-Static-Route'} = $octet_str;
# Microsoft classless static routing (dhcp option 249)
$RAD_REPLY{'DHCP-Site-specific-25'} = $octet_str;
return RLM_MODULE_OK;
}
sub get_ip_octets {
# First parameter: Source ip address
my $sip = $_[0];
# Second parameter: Bitlength of network (aka CIDR notation).
my $cidr = $_[1];
my @decimals = split('\.', $sip);
my $index = int($cidr / 8) ;
my $result = '';
for(my $i = 0; $i < $index; $i++)
{
# Convert each number in ip address to hex and format with leading
# zero in case converted number is less than 16.
$result = $result . sprintf("%02x", $decimals[$i]);
}
return $result;
}
可以从这里调整 perl 代码,因此选项 121或者选项 249 的发送取决于客户端操作系统。
我还留下了使代码更通用的可能性,因此读者可以在 Freeradius 配置文件中直接定义静态路由。