Freeradius con servidor dhcp: las llamadas al módulo perl devuelven un error

Freeradius con servidor dhcp: las llamadas al módulo perl devuelven un error

Esta es una continuación de mi pregunta anterior.en el envío de rutas estáticas desde la implementación del servidor DHCP de Freeradiusen combinación con el servidor VPN Strongswan.

Al depurar Freeradius usando tcpdump y Wireshark, descubrí que puedo enviar rutas estáticas sin clases desde el servidor DHCP de Freeradius agregando opciones DHCP-Classless-Static-Routey DHCP-Site-specific-25(también conocidas como ruta estática de Microsoft) a mis secciones DHCP-Discovery DHCP-Requestal archivo de configuración del servidor dhcp.

Sin embargo: parece que el cliente VPN de Microsoft no acepta las rutas estáticas si configuro la puerta de enlace predeterminada 0.0.0.0como lo sugiereDocumentación de Strongswan.

Al menos no puedo encontrar las rutas anunciadas en mi cliente Windows cuando uso route print -4.

Además, no puedo agregar las rutas manualmente en el cliente de Windows cuando lo uso 0.0.0.0como puerta de enlace estándar a través de una interfaz VPN.

Sin embargo:

Digamos que quiero acceder a la subred 192.168.200.0/24a través de VPN y mi servidor VPN asigna la dirección 192.168.201.2/24a mi cliente de Windows. Entonces es posible crear una ruta estática en el lado del cliente de Windows declarando que se puede acceder a la subred 192.168.200.0/24 a través de 192.168.201.2 usando el comando de Windows:

route add 192.168.200.0 mask 255.255.255.0 192.168.201.2

Sé que parece un poco extraño, pero puedo hacer ping a cualquier host de la 192.168.200.0subred, así que mientras funcione, estaré contento. :-)

Pero: estaría más feliz si pudiera hacer lo mismo anunciando las rutas desde mi servidor VPN en lugar de hacerlo manualmente en todos los clientes VPN. :-)

Eso significa que tengo que hacer un poco de programación dinámica en la configuración de DHCP en Freeradius. En mi caso, significa que tengo que hacer una referencia a un módulo perl en DHCP-Discover y DHCP-request que toma la dirección IP vpn del cliente asignada, la convierte en octetos y la combina con las rutas estáticas que también se proporcionan como octetos.

Un ejemplo:

La subred 192.168.200.0/24se codificará como 0x18c0a8c8se codifica primero la máscara de subred.

El cliente 192.168.201.2/24se codificará como 0xc0a8c902si simplemente estuviera convirtiendo cada número en la dirección IP a hexadecimal.

La codificación final de la ruta será: 0x18c0a8c8c0a8c902ya que es solo una concatinación de las dos cadenas.

Luego tengo que usar update replycon el siguiente código:

  update reply {
    &DHCP-Classless-Static-Route = 0x18c0a8c8c0a8c902
    &DHCP-Site-specific-25 = 0x18c0a8c8c0a8c902
  }

Si hay más rutas, todas las rutas se concatenarán en una cadena larga.

La parte complicada:

Supongamos que tiene la configuración predeterminada del servidor DHCP de Freeradius como se encuentra en freeradius/3.0/sites-available/dhcpel archivo.

La estructura general del archivo para DHCP-Discover y DHCP-Request es la siguiente:

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
}

Entonces, hasta donde tengo entendido, necesito llamar a mi módulo Perl después de dhcp_sqlippoolhaberlo llamado y antes de regresar ok, porque dhcp_sqlippooles el módulo que asigna la dirección IP al cliente VPN.

Eso significa que mi versión sería algo como:

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
}

Para que funcione, tengo que habilitar Perl en la freeradius/3.0/mods-enabledcarpeta y modificar el nombre del archivo freeradius/3.0/mods-enabled/perlpara que apunte a mi módulo Perl. Como por ejemplo:

filename = ${modconfdir}/${.:instance}/dhcp/Options.pm

Pero, ¿cómo hago referencia a la llamada a Perl de la manera correcta?

Pensé que tenía que habilitar la línea func_post_auth = post_authy freeradius/3.0/mods-enabled/perlcrear una sub post_authsección en mi módulo Perl para manejar llamadas desde Freeradius, pero por lo que puedo ver en mi registro, aparece el siguiente error en 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

Entonces, ¿qué es lo que no veo?

Respuesta1

Me golpeé la cabeza contra la pared varias veces, pero al menos logré que el módulo Perl funcionara, aunque no estoy completamente donde quiero estar, ya que el enrutamiento estático a través de DHCP no pasa del servidor DHCP de Freeradius al cliente VPN a través de Strongswan. , pero la depuración de paquetes UDP desde el servidor DHCP de Freeradius implica que el problema está en otra parte.

De todos modos esto es lo que hice:

  1. Habilite el módulo Perl freeradius/3.0/mods-enabledy configure al menos las siguientes líneas:
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. Modificar freeradius/3.0/sites-enabled/dhcp Los lugares relevantes son DHCP-Discovery 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
}
  1. Cree el código perl ubicado en 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;
}

El código Perl se puede modificar desde aquí, por lo que la opción 121oLa opción 249 se envía dependiendo del sistema operativo del cliente.

También dejo la posibilidad de hacer el código más genérico, de modo que las rutas estáticas se puedan definir directamente en el archivo de configuración de Freeradius para el lector.

información relacionada