Это продолжение моего предыдущего вопроса.в отправке статических маршрутов из реализации DHCP-сервера Freeradiusв сочетании с VPN-сервером Strongswan.
При отладке Freeradius с помощью tcpdump и Wireshark я обнаружил, что могу отправлять бесклассовые статические маршруты с DHCP-сервера Freeradius, добавляя параметры DHCP-Classless-Static-Route
и DHCP-Site-specific-25
(они же статические маршруты Microsoft) в разделы DHCP-Discover
и DHCP-Request
файла конфигурации сервера DHCP.
Однако: похоже, что статические маршруты не принимаются клиентом Microsoft VPN, если я устанавливаю шлюз по умолчанию, как 0.0.0.0
предлагаетсяДокументация Strongswan.
По крайней мере, я не могу найти рекомендуемые маршруты на моем клиенте Windows при использовании route print -4
.
Кроме того, я не могу вручную добавлять маршруты на клиенте Windows, когда использую 0.0.0.0
стандартный шлюз через интерфейс VPN.
Однако:
Допустим, я хочу получить доступ к подсети 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
Я знаю, это выглядит немного странно, но я могу пинговать любой хост в 192.168.200.0
подсети, так что, пока это работает, я счастлив. :-)
Но: я был бы более счастлив, если бы мог сделать то же самое, объявляя маршруты со своего VPN-сервера, а не делать это вручную на всех VPN-клиентах. :-)
Это означает, что мне нужно немного динамического программирования конфигурации DHCP в Freeradius. В моем случае это означает, что мне нужно сделать ссылку на модуль perl в DHCP-Discover и DHCP-request, который захватывает назначенный клиенту 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
}
Если есть еще маршруты, то все маршруты будут объединены в одну длинную строку.
Самая сложная часть:
Предположим, у вас есть конфигурация DHCP-сервера Freeradius по умолчанию, указанная в 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
}
Затем, насколько я понял, мне нужно вызвать мой модуль Perl после того, как dhcp_sqlippool
он был вызван, и перед возвратом 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
}
Чтобы это заработало, мне нужно включить perl в папке freeradius/3.0/mods-enabled
и изменить имя файла, freeradius/3.0/mods-enabled/perl
чтобы он указывал на мой модуль perl. Например:
filename = ${modconfdir}/${.:instance}/dhcp/Options.pm
Но как правильно сослаться на вызов Perl?
Я думал, что мне нужно включить линейный вход func_post_auth = post_auth
и freeradius/3.0/mods-enabled/perl
создать sub post_auth
раздел в моем модуле Perl для обработки вызовов из 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 не передается от DHCP-сервера Freeradius к VPN-клиенту через Strongswan, но отладка UDP-пакетов с DHCP-сервера Freeradius показывает, что проблема в чем-то другом.
В любом случае, вот что я сделал:
- Включите модуль 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.