帶有 dhcp 伺服器的 Freeradius:呼叫 perl 模組回傳錯誤

帶有 dhcp 伺服器的 Freeradius:呼叫 perl 模組回傳錯誤

這是我之前問題的延續從 Freeradius DHCP 伺服器發送靜態路由實現與 Strongswan VPN 伺服器結合使用。

當使用 tcpdump 和 Wireshark 調試 Freeradius 時,我發現可以透過將DHCP-Classless-Static-RouteDHCP-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 套件意味著問題出在其他地方。

無論如何,這就是我所做的:

  1. 啟用 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
}
  1. 修改freeradius/3.0/sites-enabled/dhcp 相關地方為DHCP-DiscoverDHCP-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
}
  1. 建立位於以下位置的 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 設定檔中向讀者定義靜態路由。

相關內容