pfsense_package_ddadv

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

pfsense_package_ddadv [2012/06/06 12:15] (current)
Line 1: Line 1:
 +====== HOWTO: advanced dynamic DNS package for pfSense ======
 +
 +En aquest document es descriuen els passos que he seguit per programar un servei de configuració de 'dynamic DNS' amb un comportament diferent del servei per defecte del propi pfSense. Aquest document també pot servir de guia per si algú vol fer-se els seus propis paquets de pfSense per personalitzar certs comportaments del firewall.
 +
 +Si teniu dubtes preguntes o correccions a fer, si us plau feu-les a l'article del blog:
 +
 +[[http://oriolrius.cat/blog/2008/10/21/el-meu-primer-package-de-pfsense-dd_adv|El meu primer package de pfSense 'dd_adv']]
 +
 +===== Com funciona 'dynamic DNS' per defecte al pfSense =====
 +
 +
 +S'ha de reconeixer que el suport és per molts serveis públics, i de pagament, per publicar un nemónic per una IP dinàmica. Cosa que el fa molt bo. Però hi ha diversos comportaments del suport que té pfSense per aquestes funcions que no s'ajustaben a les meves necessitats. Tal com esta programat aquest suport el pfSense el que fa és agafar la IP pública de la interficie WAN i cada cop que aquesta canvia es fa l'actualització de la IP pública al servei de DNS remot.
 +
 +===== El problema =====
 +
 +
 +El meu problema bé de dos llocs, per un costat la IP no la tinc assignada a la interficie amb internet, sinó a una altre interficie. Per altre banda, la interficie que té internet no té la IP pública assignada directament a ella sinó que la té un router que a través d'un procés de NAT li dona connectivitat a internet. Així doncs, el problema esta per un costat en saber sobre quina interficie accedirem a internet i per altre banda, quina és la IP pública a través de la qual s'accedeix a la xarxa. De retruc tenim encara un altre problema degut a que la IP pública no esta assignada a l'interficie del pfSense, suposo que és obvi que el problema és que no sabem quan aquesta canvia. Per tant, no sabem quan ho hem d'actualitzar al servei de DNS dinàmica.
 +
 +
 +===== La solució =====
 +
 +
 +La solució que he adoptat és crear un nou servei que a través d'un servei de pooling el que fa és anar preguntant de forma periódica a internet quina és la IP pública que té la nostre interficie. A més s'ha de poder seleccionar quina és aquesta interficie sobre la qual volem verificar quina és la IP pública. Ja que hem de forçar que el tràfic que es genera per descobrir la IP pública surti per la interficie que realment té connexió a internet. Aquesta casuística per extranya que sembli és molt habitual, perquè a una oficina o similar ens podem trobar amb la necessitat de tenir dues línies que van a internet, una que estarà a l'interficie WAN del pfSense i una altre que va per una altre interficie. Sovint a la interficie WAN hi tenim la sortida principal a internet i a una altre interficie la sortida de backup. Un exemple habitual en aquests dies que corren és usar una ADSL a la WAN i una altre sortida a través d'un router 3G de Vodafone o similar per una altre interficie.
 +
 +
 +===== Canviant el repositori de paquets del pfSense =====
 +
 +
 +Per crear un paquet de pfSense, el primer que s'ha de fer és deixar d'usar el repositori oficial de paquets, creant en un servidor propi el nostre repositori de paquets que pot apuntar a la resta de paquets públics. Però que a més, pot tenir els nostres propis paquets afegits a la llista. Per tal de fer això primer de tot cal disposar d'un servidor HTTP amb suport de PHP. Després anem al servidor i allà descarreguem el fitxer 'xmlrpc.tar.gz' del servidor de pfSense i el descomprimim a l'arrel d'un dels dominis virtuals o del domini real que tinguem al servidor HTTP. La idea és que si fem 'http://host/pfSense/xmlrpc.php' allà s'hi executi el servei d'XML-RPC del pfSense per altre banda hi ha un altre directori que es crearà al decomprimir el paquet 'xmlrpc.tar.gz' aquest directori s'anomena 'packages'. Allà hi trobem dos fitxers, un d'ells el 'pkg_config.7.xml'. Aquest fitxer conté la llista dels paquets que es poden veure al menú 'packages' del pfSense, així doncs, aquí és on afegirem el nostre paquet cretament entre els tags <packages>...</packages>.
 +
 +Passos:
 +
 +<code shell>
 +/var/www/localhost/htdocs # wget http://www.pfsense.org/xmlrpc.tar.gz
 +/var/www/localhost/htdocs # tar xvfz xmlrpc.tar.gz
 +/var/www/localhost/htdocs/packages # vim pkg_config.7.xml
 +</code>
 +
 +Afegim el nostre paquet al fitxer pkg_config.7.xml:
 +
 +<code shell>
 +...
 + <package>
 + <name>DDNS_adv</name>
 + <website>http://oriolrius.cat</website>
 + <descr>Dynamic DNS advanced parameters for custom functonalities</descr>
 + <category>Services</category>
 + <config_file>http://mini4.joor.net/dd/dd.xml</config_file>
 + <version>0.1</version>
 + <status>ALPHA</status>
 + <maintainer>oriol@joor.net</maintainer>
 + <configurationfile>dd.xml</configurationfile>
 + <required_version>1.2.1</required_version>
 + </package>
 +  </packages>
 +</pfsensepkgs>
 +
 +</code>
 +
 +Fixeu-vos que bàsicament es tracta de referenciar el nom del paquet, descripció, versió, status del mateix i d'altres dades de l'autor. Però el més important és dir on hi ha el fitxer de configuració, bàsicament es tracta d'especificar una URL d'on es descarregarà un XML que hem de definir describint on s'instal·la el paquet i com queda integrat dintre del sistema del pfSense. En aquesta cas fixeu-vos que aquesta informació esta a:
 +
 +<code shell>
 +<config_file>http://mini4.joor.net/dd/dd.xml</config_file>
 +</code>
 +
 +Ara cal que entrem per SSH al pfSense o través de la seva consola i que modifiquem el fitxer '/etc/inc/globalconf.inc'. Concretament cal buscar una variable que es diu '$xmlrpcurlbase' i posar el host on tenim el nostre nou servidor de paquets. En el nostre cas la cosa queda així:
 +
 +<code shell>
 +...
 + $xmlrpcurlbase='oriol.joor.net',
 +...
 +</code>
 +
 +
 +Després d'això si anem al menú dels 'packages' ja podem veure el nostre paquet a la llista de paquets per instal·lar:
 +
 +{{ http://oriolrius.cat/wiki/data/media/wiki/pfsense_package_ddadv/pfsense_packages_list.png }}
 +
 +===== Fitxer de configuració del paquet =====
 +
 +
 +Com ja he comentat aquest fitxer conté els detalls de com s'ha d'instal·lar el paquet i com s'integrarà amb el pfSense. Amb això vull dir que diu d'on s'han de treure d'altres fitxers necessaris, on s'han de copiar i amb quins permisos dintre del pfSense, si ha d'apareixer alguna nova opció en menú, quins camps ha de tenir el formulari per definir els paràmetres de configuració del nostre nou servei. A més també es poden definir els //hooks// necessaris perquè el procés d'instal·lació, configuració i d'altres s'ajustin a les nostres necessitats. Perquè quedi més clar tot el tema dels //hooks// explicaré com ho he hagut d'usar en el meu cas: l'únic que m'ha calgut és capturar el moment en que es guarda la configuració en el formulari i després d'això generar el fitxer de configuració que hem cal perquè funcioni bé el dimoni. Esta molt bé disposar de totes les llibreries de pfSense per fer tot això, malgrat has de llegir el codi per entendre com funcionen perquè no he trobat documentació. Però ens permet, per exemple, integrar els paràmetres de configuració del nostre paquet al fitxer de configuració general del sistema.
 +
 +El fitxer de configuració que he fet és 'dd.xml':
 +
 +<code xml>
 +<?xml version="1.0" encoding="utf-8" ?>
 +<!DOCTYPE packagegui SYSTEM "./schema/packages.dtd">
 +<?xml-stylesheet type="text/xsl" href="./xsl/package.xsl"?>
 +<packagegui>
 +  <name>ddns_adv</name>
 +  <version>0.1</version>
 +  <title>DynDNS advanced settings</title>
 +  <configpath>['installedpackages']['ddnsadv']['config']</configpath>
 +  <additional_files_needed>
 +    <prefix>/usr/local/pkg/</prefix>
 +    <chmod>0644</chmod>
 +    <item>http://oriol.joor.net/dd/dd.inc</item>
 +  </additional_files_needed>
 +  <additional_files_needed>
 +    <prefix>/etc/rc.d/</prefix>
 +    <chmod>0700</chmod>
 +    <item>http://oriol.joor.net/dd/dd_service</item>
 +  </additional_files_needed>
 +  <additional_files_needed>
 +    <prefix>/usr/local/sbin/</prefix>
 +    <chmod>0700</chmod>
 +    <item>http://oriol.joor.net/dd/dd_adv.php.sh</item>
 +  </additional_files_needed>
 +  <additional_files_needed>
 +    <prefix>/etc/inc/</prefix>
 +    <chmod>0644</chmod>
 +    <item>http://oriol.joor.net/dd/dyndns_bis.class</item>
 +  </additional_files_needed>
 +  <service>
 +    <name>dd_adv</name>
 +    <rcfile>dd_service</rcfile>
 +    <executable>dd_service</executable>
 +  </service>
 +  <menu>
 +    <name>DDNS adv</name>
 +    <tooltiptext>Dynamic DNS advanced settings</tooltiptext>
 +    <section>Services</section>
 +    <url>/pkg_edit.php?xml=dd.xml&amp;id=0</url>
 +  </menu>
 +  <fields>
 +    <field>
 +      <fielddescr>Enable service</fielddescr>
 +      <fieldname>enabled</fieldname>
 +      <type>checkbox</type>
 +    </field>
 +    <field>
 +      <fielddescr>Get public IP across this interface...</fielddescr>
 +      <fieldname>selectinterface</fieldname>
 +      <description>Getting public IP across this interface...</description>
 +      <value>wan</value>
 +      <multiple>false</multiple>
 +      <size>5</size>
 +      <type>interfaces_selection</type>
 +    </field>
 +    <field>
 +    <fielddescr>Periode</fielddescr>
 +       <fieldname>periode</fieldname>
 +       <description>in minutes, every when check public IP address</description>
 +       <size>3</size>
 +       <type>input</type>
 +    </field>
 +  </fields>
 +<include_file>/usr/local/pkg/dd.inc</include_file>
 +<custom_php_resync_config_command>sync_package_dd();</custom_php_resync_config_command>
 +</packagegui>
 +</code>
 +
 +A continuació comento els camps i seccions del fitxer més importants, començaré per el següent tag:
 +
 +<code xml>
 +<configpath>['installedpackages']['ddnsadv']['config']</configpath>
 +</code>
 +
 +Aquí s'indica en quina part del fitxer de configuració general del pfSense es guardarà la configuració, aquest fitxer de configuració és un fitxer XML anomenat 'config.xml' que bàsicament és el fitxer que es descarrega al fer una còpia de seguretat del sistema. Com que es tracta d'un fitxer XML és senzill referir-se a una posició en el seu interior en forma d'array multi-dimencional. A més com que el meu paquet no forma part dels paquets per defecte el col·loco a la secció de paquets instal·lats, després indico dos noms arbitraris a partir dels quals posteriorment podré accedir als paràmetres de configuració definits més endavant en el mateix fitxer.
 +
 +Les seccions tancades dintre de tags: 'additional_files_needed', tenen el següent aspecte i es refereixen a fitxers que cal instal·lar durant el procés d'instal·lació del paquet:
 +
 +<code xml>
 +  <additional_files_needed>
 +    <prefix>/usr/local/pkg/</prefix>
 +    <chmod>0644</chmod>
 +    <item>http://oriol.joor.net/dd/dd.inc</item>
 +  </additional_files_needed>
 +</code>
 +
 +Fixeu-vos que es defineix on anirà el fitxer instal·lat, els permisos amb que es copiarà el fitxer i de quina URL es descarregarà. A través d'aquestes seccions és d'on podem referenciar els paquets binaris (tar.gz o similars) que poden contenir el codi compilat necessari perquè corri dins del pfSense.
 +
 +En cas de que el nostre paquet hagi de correr com a dimoni, podem deifinir com es dirà el servei i així després a través de les funcions de control de dimonis de pfSense aquest es podrà posar en marxar, parar, reiniciar o iniciar quan el pfSense es posi en marxa.
 +
 +<code xml>
 +  <service>
 +    <name>dd_adv</name>
 +    <rcfile>dd_service</rcfile>
 +    <executable>dd_service</executable>
 +  </service>
 +</code>
 +
 +Sovint els fitxers que es referencien en aquesta secció s'espera trobar-los a /etc/rc.d en el meu cas, es llençarà el dimoni via '/etc/rc.d/dd_service'.
 +
 +Als tags de 'menu' es defineix en quins menús es cridarà la pantalla de configuració que es definirà més endavant en el mateix fitxer XML. La secció menú té com a única peça difícil d'entendre el PATH de la URL que es cridarà. Bàsicament el que es fa és cridar a un interpret que serà capaç de montar la pantalla de configuració del paquet a partir de l'XML:
 +
 +<code xml>
 +  <menu>
 +    <name>DDNS adv</name>
 +    <tooltiptext>Dynamic DNS advanced settings</tooltiptext>
 +    <section>Services</section>
 +    <url>/pkg_edit.php?xml=dd.xml&amp;id=0</url>
 +  </menu>
 +</code>
 +
 +Dins de les etiquetes 'fields' es tanquen la descripció dels camps: 'field', aquests especifiquen el que es veurà en el formulari i que podrà configurar l'usuari i també el que es guardarà en el fitxer de configuració general. Si doneu un cop d'ull en aquesta secció us adonareu que el codi és molt autodescriptiu i és molt senzill d'entendre. Bàsicament hi ha el nom del camp, la descripció i el tipus de dades a guardar/demanar.
 +
 +Una altre etiqueta important és la del 'include_file', sovint amb el mateix nom que el fitxer XML, en el nostre cas el fitxer XML s'anomena 'dd.xml' i per tant, aquest fitxer es diu 'dd.inc'. Es tracta d'un fitxer amb les funcions de PHP necessaries pels //hooks// que ens facin falta:
 +
 +<code xml>
 +<include_file>/usr/local/pkg/dd.inc</include_file>
 +</code>
 +
 +Finalment arriba l'hora de definir quins //hooks// usarem, en el meu cas només n'uso un definit amb l'etiqueta 'custom_php_resync_config_command':
 +
 +<code xml>
 +<custom_php_resync_config_command>sync_package_dd();</custom_php_resync_config_command>
 +</code>
 +
 +que com es pot veure només crida una funció que esta definida en el 'dd.inc', aquest //hook// fa que s'executi aquesta funció després de guardar la configuració del formulari. En cas de que no volguem definir el codi en el fitxer INC també ho podem fer entre els dos tags el d'obertura i el de tancament del //hook//. Tot i que sota el meu punt de vista sempre queda més ordenat i més net cridar només a funcions des dels //hooks// i deixar tot el codi PHP al fitxer INC.
 +
 +
 +<code php>
 +<?php
 +require_once("config.inc");
 +require_once("functions.inc");
 +require_once("dyndns_bis.class");
 +require_once('globals.inc');
 +require_once('service-utils.inc');
 +
 +
 +function dd_notice ($msg) { syslog(LOG_NOTICE, "dd: {$msg}"); }
 +function dd_warn ($msg) { syslog(LOG_WARNING, "dd: {$msg}"); }
 +
 +function sync_package_dd() {
 + global $config;
 +
 + if ($config['installedpackages']['ddnsadv']['config'][0]['enabled']=='on') {
 + $if = $config['installedpackages']['ddnsadv']['config'][0]['selectinterface'];
 + $periode = $config['installedpackages']['ddnsadv']['config'][0]['periode']*60;
 +
 + $script = <<<EOD
 +#!/bin/sh
 +
 +while true;
 +do
 + /usr/local/sbin/dd_adv.php.sh
 + sleep $periode
 +done
 +
 +
 +EOD;
 + file_put_contents('/usr/local/sbin/dd_adv.sh',$script);
 + system('chmod 700 /usr/local/sbin/dd_adv.sh');
 + system('/etc/rc.d/dd_service restart');
 + dd_notice('started');
 + } else {
 + system('/etc/rc.d/dd_service stop');
 + dd_notice('stoped');
 + }
 +}
 +
 +?>
 +</code>
 +
 +En el meu cas, el que faig és refer el fitxer '/usr/local/sbin/dd_adv.sh'. Aquest fitxer és un fitxer que té un bucle infinit que es repeteix cada N minuts, sent N un paràmetre definit en la configuració del servei. En cada una de les interacions s'executa un codi en PHP que és l'encarregat de fer l'actualització de la IP, si cal.
 +
 +Tot això es fa en cas de que el servei hagi estat activat des de l'interficie de configuració, però si el servei no ha estat activat directament el que fem és buidar el fitxer '/usr/local/sbin/dd_adv.sh'. L'objectiu d'això malgrat també es pari el servei és que si es reinicia el firewall i es llenci el servei quan aquest cridi el fitxer que ens ocupa com que aquest no tindrà contingut no farà l'iteració. Per tant, serà el mateix que si no hagués estat llençat. Potser hi ha alguna forma més elegant de fer això però crec que acompleix la seva funció així doncs no m'hi he complicat més.
 +
 +Tornant al fitxer 'dd.inc' si ús hi fixeu també defineix dues funcions més la 'dd_notice' i la 'dd_warn' que bàsicament s'usen per enviar missatges a syslog en cas de que en la funció principal hem calgui enviar algo de quin és l'estat del servei.
 +
 +
 +===== El fitxer '/usr/local/sbin/dd_adv.sh' =====
 +
 +Aquest dimoni, com hem vist és crea en temps de configuració depenent dels paràmetres que té assignats. La seva funcionalitat l'he descrita unes línies més amunt i és ben senzilla però com que té entitat pròpia a continuació engaxo un dels possibles aspectes que pot tenir:
 +
 +<code shell>
 +#!/bin/sh
 +
 +while true;
 +do
 + /usr/local/sbin/dd_adv.php.sh
 + sleep 600
 +done
 +</code>
 +
 +Com podeu veure això el que fa és executar el shell script programat en PHP i anomenat 'dd_adv.php.sh' i després esperar 600 segons fins a tornar a demanar de nou l'execució d'aquest script. Per tant, aquí tenim el servei de pooling.
 +
 +===== Fitxer '/usr/local/sbin/dd_adv.php.sh =====
 +
 +El codi d'aquest script esta fet en PHP però es llençarà com un shell script, com podem veure en el codi del fitxer 'dd_adv.sh' és llençat periódicament i l'únic que fa és crear rutes estàtiques que obliguin al pfSense a usar la interficie definida en la configuració per arribar a 'http://checkip.dyndns.org', aquesta web ens informar de la IP que hem usat per connectar-nos a ella. Per tant, la nostre IP pública. Després a través de la classe 'dyndns_bis.class' actualitzem la IP, si cal. Ara ja podem borrar les rutes estàtiques i tornar-ho a deixar tot tal com estava.
 +
 +Cal destacar que l'script té en compte que la web a la que s'accedeix pot tenir més d'una IP. Per això es creen tantes rutes com IPs té la web que ens informa de la nostre IP. A més cal que ens fixem que ens recolzem en diverses llibreries internes del pfSense per recollir informacions com la configuració del sistema, saber quin és el gateway de la interficie que s'ha definit com interficie alternativa de sortida a internet i per tal de relacionar-nos amb el servei de DNS dinàmic usem la llibreria 'dyndns_bis.class', que és una modificació de la llibreria 'dyndns.class' original del pfSense.
 +
 +<code php>
 +#!/usr/local/bin/php -q
 +<?php
 +
 +require_once('/etc/inc/config.inc');
 +require_once('/etc/inc/functions.inc');
 +require_once('/etc/inc/dyndns_bis.class');
 +require_once('/etc/inc/pfsense-utils.inc');
 +
 +$s = "checkip.dyndns.org";
 +
 + // getting interface gateway
 +        $if=$config['installedpackages']['ddnsadv']['config'][0]['selectinterface'];
 +        $gw=get_interface_gateway($if);
 +
 + // add static routes to $s;
 + $ips=gethostbynamel($s);
 + foreach ($ips as $ip) {
 + $ruta="route add -net ".$ip."/32 ".$gw;
 + system($ruta);
 + }
 +
 + // get public IP address via fixed interface
 + $h=new updatedns_bis($config['dyndns']['type'],
 +                      $config['dyndns']['host'],
 +                      $config['dyndns']['username'],
 +                      $config['dyndns']['password'],
 +                      $config['dyndns']['wildcard'],
 +                      $config['dyndns']['mx']);
 + // dump result
 + var_dump($h);
 +
 + // remove static routes to $s
 + foreach ($ips as $ip) {
 + $ruta="route del -net ".$ip."/32 ".$gw;
 + system($ruta);
 + }
 +
 +
 +?>
 +</code>
 +
 +
 +===== Classe '/etc/inc/dyndns_bis.class' =====
 +
 +Aquesta classe és una modificació molt i molt simple del codi 'dyndns.class' del pfSense, bàsicament afegeix un nou mètode privat que obté la IP pública de la interficie després de connectar-se a 'http://checkip.dyndns.org'. Així doncs, en la resta de classes cada cop que s'intenta agafar la IP pública a través d'accedir a la IP de l'interficie WAN he modificat el codi perquè el que faci sigui accedir a la web en qüestió.
 +
 +El codi de la classe no l'engaxo aquí perquè és molt llarg, el podeu trobar a: URL_CODI cal notar que el codi de la classe esta prou ben fent i implementa un servei de caché, que en cas de que la IP no hagi canviat no s'envii una actualització de la mateixa, a més també controla el 'timestamp' de quan es va fer l'última actualització perquè es pugui controlar un mínim de temps de cada quan s'ha d'actualitzar la IP malgrat aquesta no hagi canviat. Tot plegat m'ha estalviat molta feina a l'hora de no haver de montar-me jo el meu propi sistema d'actualització d'IPs.
 +
 +Potser el que és una mica més curiós és veure com es parseja la informació que es rep de la web que ens dona la IP pública, per cert, aquest codi no l'he escrit jo sinó que l'he trobat a través dels formus de pfSense. Aprofito doncs per donar-li les gràcies a 'Tri Tu' l'autor del codi.
 +
 +<code php>
 +/* Private function for getting real IP */
 +/* Author: Tri Tu */
 +function _checkip() {
 +   log_error("DynDns: Running _checkip() for real WAN IP");
 +   $ch = curl_init();
 +   curl_setopt($ch, CURLOPT_URL, 'http://checkip.dyndns.com');
 +   curl_setopt($ch, CURLOPT_HEADER, 0);
 +   curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
 +   $data = curl_exec($ch);
 +   curl_close($ch);
 +   list($part1, $part2) = split(': ', $data, 2);
 +   list($ip, $junk) = split('<', $part2);
 +   return $ip;
 +}
 +</code>
 +
 +Per cert, fixeu-vos que usa CURL per fer la petició HTTP, en cas de que no conegueu aquesta llibreria ús recomano que li doneu un cop d'ull perquè crec que és realment una de les millors llibreries client d'HTTP, HTTPs i similars que he vist. Tan per PHP, com per C, C++, Python i molts altres llenguatges.
 +
 +===== Service launcher =====
 +
 +L'únic fitxer que hem falta per explicar és ben simple, és el que va a /etc/rc.d/dd_service i que s'encarrega de llençar el shell script que itera infinitament vigilant si la IP a canviat. En aquest fitxer no he usat les llibreries internes de pfSense per progamar-lo perquè aquestes no suporten llençar dimonis que siguin 'shell scripts'. Així doncs, m'he fet una implementació molt bàsica de les funcionalitats que fan falta per poder llençar, parar i reiniciar el dimoini:
 +
 +<code shell>
 +#!/bin/sh
 +
 +
 +start()
 +{
 +        /usr/local/sbin/dd_adv.sh > /dev/null 2>&1 &
 +}
 +
 +stop()
 +{
 +        PID=`ps ax | grep "/bin/sh /usr/local/sbin/dd_adv.sh" | grep -v grep | cut -c 1-7`
 +        kill -9 $PID
 +}
 +
 +
 +# main
 +
 +        case $1 in
 +
 +        start)
 +                start
 +                return 0
 +                ;;
 +        stop)
 +                stop
 +                return 0
 +                ;;
 +        restart)
 +                stop
 +                start
 +                return 0
 +                ;;
 +        *)
 +                echo "usage: $0 <start|stop|restart>"
 +                return 1
 +                ;;
 +        esac
 +
 +</code>
 +
 +
 +
 +
  
  • pfsense_package_ddadv.txt
  • Last modified: 2012/06/06 12:15
  • (external edit)