這是我之前問題的延續從 Freeradius DHCP 伺服器發送靜態路由實現與 Strongswan VPN 伺服器結合使用。
當使用 tcpdump 和 Wireshark 調試 Freeradius 時,我發現可以透過將DHCP-Classless-Static-Route
和DHCP-Site-specific-25
(又稱 Microsoft 靜態路由)選項新增至dhcp 伺服器設定檔的DHCP-Discover
和部分,從 Freeradius DHCP 伺服器傳送無類別靜態路由。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 命令聲明子網路 192.168.200.0/24 可透過 192.168.201.2 訪問,在 Windows 用戶端建立靜態路由:
route add 192.168.200.0 mask 255.255.255.0 192.168.201.2
我知道這看起來有點奇怪,但我可以 ping192.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
該模組將 ip 位址分配給 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 設定檔中向讀者定義靜態路由。