From f63d5b66b49abe5190703d9d251b7e2d462e993b Mon Sep 17 00:00:00 2001 From: Helder Pereira Date: Sat, 20 Dec 2008 23:57:09 +0000 Subject: This patch adds the initial support for layer7 protocol inspection gui. --- etc/inc/shaper.inc | 390 +++++++++++++++++- etc/inc/xmlparse.inc | 4 +- usr/local/www/diag_patterns.php | 130 ++++++ usr/local/www/firewall_shaper.php | 3 +- usr/local/www/firewall_shaper_layer7.php | 592 +++++++++++++++++++++++++++ usr/local/www/firewall_shaper_queues.php | 3 +- usr/local/www/firewall_shaper_vinterface.php | 3 +- usr/local/www/firewall_shaper_wizards.php | 3 +- 8 files changed, 1120 insertions(+), 8 deletions(-) create mode 100755 usr/local/www/diag_patterns.php create mode 100755 usr/local/www/firewall_shaper_layer7.php diff --git a/etc/inc/shaper.inc b/etc/inc/shaper.inc index 63a14ff..24b02b4 100644 --- a/etc/inc/shaper.inc +++ b/etc/inc/shaper.inc @@ -3032,6 +3032,392 @@ class dnqueue_class extends dummynet_class { } } +// List of layer7 objects +$layer7_rules_list = array(); + +class layer7 { + + var $rname; //alias + var $rdescription; //alias description + var $rport; //divert port + var $renabled; //rule enabled + var $rsets = array(); //array of l7 associations + + // Auxiliary functions + + function GetRName() { + return $this->rname; + } + function SetRName($rname) { + $this->rname = $rname; + } + function GetRDescription() { + return $this->rdescription; + } + function SetRDescription($rdescription) { + $this->rdescription = $rdescription; + } + function GetRPort() { + return $this->rport; + } + function SetRPort($rport) { + $this->rport = $rport; + } + function GetREnabled() { + return $this->renabled; + } + function SetREnabled($value) { + $this->renabled = $value; + } + function GetRl7() { + return $this->rsets; + } + function SetRl7($rsets) { + $this->rsets = $rsets; + } + + //Add a tuple (rule,sctructure,element) to the $rsets + + function add_rule($l7set) { + $this->rsets[] = $l7set; + } + + // Build the layer7 rules + function build_l7_rules() { + if($this->GetREnabled() == "") { + return; + } + //$l7rules = "#" . $this->rdescription . "\n"; + foreach ($this->rsets as $rl7) { + $l7rules .= $rl7->build_rules(); + } + return $l7rules; + } + + // Read the config from array + function ReadConfig(&$qname, &$q) { + $this->SetRName($qname); + $this->SetREnabled($q['enabled']); + $this->SetRPort($q['divert_port']); + if(isset($q['description']) && $q['description'] <> "") + $this->SetRDescription($q['description']); + $rsets = $q['l7rules']; + //Put individual rules in the array + if(is_array($rsets)) { + foreach($rsets as $l7r) { + $l7obj = new l7rule(); + $l7obj->SetRProtocol($l7r['protocol']); + $l7obj->SetRStructure($l7r['structure']); + $l7obj->SetRBehaviour($l7r['behaviour']); + $this->add_rule($l7obj); + } + } + } + + //Generate a random port for the divert socket + function gen_divert_port() { + $dports = get_divert_ports(); //array of used ports + $divert_port = rand(40000, 60000); + while(true) { + foreach($dports as $dport) { + $dupe = false; + if($dport == $divert_port) { + $divert_port = rand(40000, 60000); + $dupe = true; + break; + } + } + if(!$dupe) { + break; + } + } + return $divert_port; + } + + //Helps building the left tree + function build_tree() { + $tree = "
  • GetRName() ."&action=show\">"; + $tree .= $this->GetRName() . ""; + $tree .= "
  • "; + + return $tree; + } + + function build_form() { + $form = "
    "; + $form .= "Enable/Disable"; + $form .= ""; + $form .= " GetREnabled()) { + $form .= "checked = \"CHECKED\""; + } + $form .= " > Enable/Disable layer7 Container"; + $form .= ""; + $form .= "
    Name"; + $form .= ""; + $form .= "GetRName()."\">"; + $form .= ""; + $form .= "Description"; + $form .= ""; + $form .= "GetRDescription(); + $form .= "\">"; + $form .= "
    "; + $form .= "You may enter a description here "; + $form .= "for your reference (not parsed)."; + $form .= ""; + + return $form; + } + + //Write the setting to the $config array + function wconfig() { + global $config; + + if(!is_array($config['l7shaper']['container'])) { + $config['l7shaper']['container'] = array(); + } + // + $cflink =& get_l7c_reference_to_me_in_config($this->GetRName()); + // Test if this rule does exists already + if(!$cflink) { + $cflink =& $config['l7shaper']['container'][]; + } + $cflink['name'] = $this->GetRName(); + $cflink['enabled'] = $this->GetREnabled(); + $cflink['description'] = $this->GetRDescription(); + $cflink['divert_port'] = $this->GetRPort(); + + //Destroy previously existent rules + if(is_array($cflink['rules'])) { + unset($cflink['l7rules']); + } + + $cflink['l7rules'] = array(); + + $i = 0; + foreach($this->rsets as $rulel7) { + $cflink['l7rules'][$i]['protocol'] = $rulel7->GetRProtocol(); + $cflink['l7rules'][$i]['structure'] = $rulel7->GetRStructure(); + $cflink['l7rules'][$i]['behaviour'] = $rulel7->GetRBehaviour(); + $i++; + } + } + + //This function is necessary to help producing the overload options for keep state + function get_unique_structures() { + + $unique_structures = array("action" => false, "dummynet" => false, "altq" => false); + foreach($this->rsets as $l7rule) { + if($l7rule->GetRStructure() == "action") + $unique_structures['action'] = true; + else if($l7rule->GetRStructure() == "limiter") + $unique_structures['dummynet'] = true; + else + $unique_structures['altq'] = true; + } + //Delete non used structures so we don't have to check this in filter.inc + foreach($unique_structures as $key => $value) + if(!$value) + unset($unique_structures[$key]); + return $unique_structures; + } + + function validate_input($data, &$input_errors) { + $reqfields[] = "container"; + $reqdfieldsn[] = "Name"; + + shaper_do_input_validation($data, $reqdfields, $reqdfieldsn, $input_errors); + + if (!preg_match("/^[a-zA-Z0-9_-]+$/", $data['container'])) + $input_errors[] = "Queue names must be alphanumeric and _ or - only."; + } + + function delete_l7c() { + unset_l7_object_by_reference($this->GetRName()); + cleanup_l7_from_rules($this->GetRName()); + } +} + +class l7rule { + + var $rprotocol; //protocol + var $rstructure; //action, limiter, queue + var $rbehaviour; //allow, block, queue_name, pipe_number ... + + //Auxiliary Functions + + function GetRProtocol() { + return $this->rprotocol; + } + function SetRProtocol($rprotocol) { + $this->rprotocol = $rprotocol; + } + function GetRStructure() { + return $this->rstructure; + } + function SetRStructure($rstructure) { + $this->rstructure = $rstructure; + } + function GetRBehaviour() { + return $this->rbehaviour; + } + function SetRBehaviour($rbehaviour) { + $this->rbehaviour = $rbehaviour; + } + + //XXX Do we need to test any particularity for AltQ queues? + function build_rules() { + global $dummynet_pipe_list; + switch ($this->GetRStructure()) { + case "limiter": + read_dummynet_config(); + $dn_list =& get_unique_dnqueue_list(); + $found = false; + if(is_array($dn_list)) { + foreach($dn_list as $key => $value) { + if($key == $this->GetRBehaviour()) { + if($value[0] == "?") + $l7rule = $this->GetRProtocol() . " = dnqueue " . substr($value, 1) . "\n"; + else + $l7rule = $this->GetRProtocol() . " = dnpipe " . $value . "\n"; + $found = true; + } + if($found) + break; + } + } + break; + default: //This is for action and for altq + $l7rule = $this->GetRProtocol() . " = " . $this->GetRStructure() . " " . $this->GetRBehaviour() . "\n"; + break; + } + return $l7rule; + } +} + +/* + * This function allows to return an array with all the used divert socket ports + */ +function get_divert_ports() { + global $layer7_rules_list; + $dports = array(); + + foreach($layer7_rules_list as $l7r) + $dports[] = $l7r->GetRPort(); + + return $dports; +} + +function &get_l7c_reference_to_me_in_config(&$name) { + global $config; + + $ptr = NULL; + + if(is_array($config['l7shaper']['container'])) { + foreach($config['l7shaper']['container'] as $key => $value) { + if($value['name'] == $name) + $ptr =& $config['l7shaper']['container'][$key]; + } + } + return $ptr; +// $ptr can be null. has to be checked later +} + +function unset_l7_object_by_reference(&$name) { + global $config; + + if(is_array($config['l7shaper']['container'])) { + foreach($config['l7shaper']['container'] as $key => $value) { + if($value['name'] == $name) { + unset($config['l7shaper']['container'][$key]['l7rules']); + unset($config['l7shaper']['container'][$key]); + break; + } + } + } +} + +function read_layer7_config() { + global $layer7_rules_list, $config; + + $l7cs = &$config['l7shaper']['container']; + + $layer7_rules_list = array(); + + if (!is_array($config['l7shaper']['container']) || !count($config['l7shaper']['container'])) + return; + + foreach ($l7cs as $conf) { + if (empty($conf['name'])) + continue; /* XXX: grrrrrr at php */ + $root =& new layer7(); + $root->ReadConfig($conf['name'],$conf); + $layer7_rules_list[$root->GetRName()] = &$root; + } +} + +function generate_layer7_files() { + global $layer7_rules_list; + + read_layer7_config(); + + if (!empty($layer7_rules_list)) { + mwexec("kldload ipdivert.ko"); + mwexec("killall -9 ipfw-classifyd"); + } + + foreach($layer7_rules_list as $l7rules) { + if($l7rules->GetREnabled()) { + $filename = $l7rules->GetRName() . ".l7"; + $path = "/tmp/" . $filename; + + $rules = $l7rules->build_l7_rules(); + + $fp = fopen($path,'w'); + fwrite($fp,$rules); + fclose($fp); + + $ipfw_classifyd_init = "ipfw-classifyd -c " . $path . " -p " . $l7rules->GetRPort() . " -P /usr/local/share/protocols"; + mwexec($ipfw_classifyd_init); + } + } +} + +// This function uses /usr/local/share/protocols as a default directory for searching .pat files +function generate_protocols_array() { + $protocols = return_dir_as_array("/usr/local/share/protocols"); + if(is_array($protocols)) { + foreach($protocols as $key => $proto) + $protocols[$key] =& str_replace(".pat", "", $proto); + sort($protocols); + } + return $protocols; +} + +function get_l7_unique_list() { + global $layer7_rules_list; + + $l7list = array(); + if(is_array($layer7_rules_list)) + foreach($layer7_rules_list as $l7c) + if($l7c->GetREnabled()) + $l7list[] = $l7c->GetRName(); + + return $l7list; +} + +// Disable a removed l7 container from the filter +function cleanup_l7_from_rules(&$name) { + global $config; + + foreach ($config['filter']['rule'] as $key => $rule) { + //We have to change this + if ($rule['l7container'] == $name) + unset($config['filter']['rule'][$key]['l7container']); + } +} /* * XXX: TODO Make a class shaper to hide all these function @@ -3290,14 +3676,14 @@ $default_shaper_msg = ""; $default_shaper_msg .= "

    Welcome to the pfSense Traffic Shaper.
    "; $default_shaper_msg .= "The tree on the left helps you navigate through the queues
    "; $default_shaper_msg .= "buttons at the bottom represent queue actions and are activated accordingly."; -$default_shaper_ms .= "

    "; +$default_shaper_msg .= "

    "; $default_shaper_msg .= ""; $dn_default_shaper_msg = ""; $dn_default_shaper_msg .= "

    Welcome to the pfSense Traffic Shaper.
    "; $dn_default_shaper_msg .= "The tree on the left helps you navigate through the queues
    "; $dn_default_shaper_msg .= "buttons at the bottom represent queue actions and are activated accordingly."; -$dn_default_shaper_ms .= "

    "; +$dn_default_shaper_msg .= "

    "; $dn_default_shaper_msg .= ""; diff --git a/etc/inc/xmlparse.inc b/etc/inc/xmlparse.inc index e406f5a..c1caaa7 100644 --- a/etc/inc/xmlparse.inc +++ b/etc/inc/xmlparse.inc @@ -36,11 +36,11 @@ function listtags() { * I know it's a pain, but it's a pain to find stuff too if it's not */ $ret = explode(" ", - "alias aliasurl allowedip authserver bridged ca cacert cert config ". + "alias aliasurl allowedip authserver bridged ca cacert cert config container ". "columnitem depends_on_package disk dnsserver dnsupdate domainoverrides ". "dyndns earlyshellcmd element encryption-algorithm-option field ". "fieldname hash-algorithm-option gateway_item gateway_group gif gre ". - "group hosts member interface_array item key lagg lbaction lbpool ". + "group hosts member interface_array item key lagg lbaction lbpool l7rules ". "lbprotocol member menu tab mobilekey monitor_type mount ntpserver onetoone ". "openvpn-server openvpn-client openvpn-csc " . "option ppp package passthrumac phase1 phase2 priv proxyarpnet queue ". diff --git a/usr/local/www/diag_patterns.php b/usr/local/www/diag_patterns.php new file mode 100755 index 0000000..551fc44 --- /dev/null +++ b/usr/local/www/diag_patterns.php @@ -0,0 +1,130 @@ + + + + + + + +

    You can upload new layer7 patterns to your system!

    +" . $ulmsg . "

    \n"; ?> + + +
    +
    + + + + + + + + + + + + + + + + +
    Upload
    File to upload: + +
        +
    +
    + + + + + + diff --git a/usr/local/www/firewall_shaper.php b/usr/local/www/firewall_shaper.php index 9f74264..ec803a1 100755 --- a/usr/local/www/firewall_shaper.php +++ b/usr/local/www/firewall_shaper.php @@ -424,7 +424,8 @@ include("fbegin.inc"); $tab_array[0] = array("By Interface", true, "firewall_shaper.php"); $tab_array[1] = array("By Queue", false, "firewall_shaper_queues.php"); $tab_array[2] = array("Limiter", false, "firewall_shaper_vinterface.php"); - $tab_array[3] = array("Wizards", false, "firewall_shaper_wizards.php"); + $tab_array[3] = array("Layer7", false, "firewall_shaper_layer7.php"); + $tab_array[4] = array("Wizards", false, "firewall_shaper_wizards.php"); display_top_tabs($tab_array); ?> diff --git a/usr/local/www/firewall_shaper_layer7.php b/usr/local/www/firewall_shaper_layer7.php new file mode 100755 index 0000000..71574ec --- /dev/null +++ b/usr/local/www/firewall_shaper_layer7.php @@ -0,0 +1,592 @@ +"; +$default_layer7shaper_msg .= "

    Note:
    "; +$default_layer7shaper_msg .= "
    You can add new layer7 protocol patterns by simply uploading the file here.
    "; +$default_layer7shaper_msg .= ""; + + +read_layer7_config(); + +if($_GET['reset'] <> "") { + mwexec("killall -9 pfctl php"); + mwexec("killall -9 ipfw-classifyd"); //kill all ipfw-classifyd processes + exit; +} + +if ($_GET) { + if ($_GET['container']) + $name = trim($_GET['container']); + if ($_GET['action']) + $action = $_GET['action']; +} + +if($_POST) { + if ($_POST['container']) { + $name = trim($_POST['container']); + } +} + +if ($name) { + //Get the object from the 7rules list + $container = $layer7_rules_list[$name]; +} + + +if ($_GET) { + switch ($action) { + case "add": + $show_proto_form = true; + $container = new layer7(); + $output_form .= $container->build_form(); //constructs the graphical interface on the right side + unset($container); + break; + case "show": + $show_proto_form = true; + if($container) { + $output_form .= $container->build_form(); + } + else { + $show_proto_form = false; + $input_errors[] = "Layer7 Rules Container not found!"; + } + break; + default: + echo log_error("Get default"); + $show_proto_form = false; + $output_form .= "

    " . $dn_default_shaper_msg . $default_layer7shaper_msg . "

    "; + break; + } +} + +//add a new l7rules container +else if ($_POST) { + $show_proto_form = true; + unset($input_errors); + + if($_POST['submit']) { + $l7r =& new layer7(); + $_POST['divert_port'] = $l7r->gen_divert_port(); + for($i=0; $i<100; $i++) { + if($_POST['protocol'][$i] <> "") { + $_POST['l7rules'][$i]['protocol'] = $_POST['protocol'][$i]; + $_POST['l7rules'][$i]['structure'] = $_POST['structure'][$i]; + $_POST['l7rules'][$i]['behaviour'] = $_POST['behaviour'][$i]; + } + else { + break; + } + } + $l7r->validate_input($_POST,&$input_errors); + $l7r->ReadConfig($_POST['container'], $_POST); + //Before writing the results, we need to test for repeated protocols + $non_dupes = array(); + $dupes = array(); + for($j=0; $j<$i; $j++) { + if(!$non_dupes[$_POST['protocol'][$j]]) + $non_dupes[$_POST['protocol'][$j]] = true; + else + $dupes[] = $_POST['protocol'][$j]; + } + unset($non_dupes); + if(sizeof($dupes) == 0 && !$input_errors) { + $l7r->wconfig(); + write_config(); + touch($d_shaperconfdirty_path); + + read_layer7_config(); + } + else { + if(sizeof($dupes) > 0) { + $dupe_error = "Found the following repeated protocol definitions: "; + foreach($dupes as $dupe) + $dupe_error .= "$dupe "; + $input_errors[] .= $dupe_error; + } + } + unset($dupes); + unset($dupe_error); + //Even if there are repeated protocols, we won't loose any previous values + //The user will be able to solve the situation + $output_form .= $l7r->build_form(); + //Necessary to correctly build the proto form + $container = $layer7_rules_list[$name]; + if($input_errors) + $container =& $l7r; + } else if($_POST['apply']) { + write_config(); + + $retval = 0; + $savemsg = get_std_save_message($retval); + + config_lock(); + $retval = filter_configure(); + config_unlock(); + + if(stristr($retval, "error") <> true) + $savemsg = get_std_save_message($retval); + else + $savemsg = $retval; + + unlink($d_shaperconfdirty_path); + + if($container) { + $output_form .= $container->build_form(); + } else { + $show_proto_form = false; + $output_form .= "

    " . $dn_default_shaper_msg . $default_layer7shaper_msg . "

    "; + } + } else if ($_POST['delete']) { + $container->delete_l7c(); + write_config(); + touch($d_shaperconfdirty_path); + unset($container); + + header("Location: firewall_shaper_layer7.php"); + exit; + } + else { + $show_proto_form = false; + } +} +else { + $show_proto_form = false; + $output_form .= "

    " . $dn_default_shaper_msg . $default_layer7shaper_msg . "

    "; +} + +// Builds the left tree +$tree = ""; + +$output = "
    "; +$output .= $output_form; + +include("head.inc"); +?> + + + + + + + + +
    + + +
    + + +

    +You must apply the changes in order for them to take effect.");?>
    + + + + + + +
    + +
    +
    + + + 0): ?> + + + + + + +
    +
    + +

    + + Create new l7 rules group +
    +
    + + + + + + + + + + + + + + + + + +
    +
    + Rule(s) +
    +
    + + + + + + + + + + + + + + + + rsets as $l7rule) { + ?> + + + + + + + + + + + + +
    +
    + Add one or more rules +
    +
    +
    + Protocol +
    +
    +
    + Structure +
    +
    +
    + Behaviour +
    +
    + + + + + + + GetRStructure() == "queue"): ?> + + + + + + GetRStructure() == "limiter"): ?> + + + + + + + +
    + + +
    +   + + + + + + + + + +
    + +
    +
    +
    +

    + + + + \ No newline at end of file diff --git a/usr/local/www/firewall_shaper_queues.php b/usr/local/www/firewall_shaper_queues.php index 061314b..342083e 100755 --- a/usr/local/www/firewall_shaper_queues.php +++ b/usr/local/www/firewall_shaper_queues.php @@ -188,7 +188,8 @@ include("head.inc"); $tab_array[0] = array("By Interface", false, "firewall_shaper.php"); $tab_array[1] = array("By Queue", true, "firewall_shaper_queues.php"); $tab_array[2] = array("Limiter", false, "firewall_shaper_vinterface.php"); - $tab_array[3] = array("Wizards", false, "firewall_shaper_wizards.php"); + $tab_array[3] = array("Layer7", false, "firewall_shaper_layer7.php"); + $tab_array[4] = array("Wizards", false, "firewall_shaper_wizards.php"); display_top_tabs($tab_array); ?> diff --git a/usr/local/www/firewall_shaper_vinterface.php b/usr/local/www/firewall_shaper_vinterface.php index b1bf9a5..75b7e6c 100644 --- a/usr/local/www/firewall_shaper_vinterface.php +++ b/usr/local/www/firewall_shaper_vinterface.php @@ -367,7 +367,8 @@ include("fbegin.inc"); $tab_array[0] = array("By Interface", false, "firewall_shaper.php"); $tab_array[1] = array("By Queue", false, "firewall_shaper_queues.php"); $tab_array[2] = array("Limiter", true, "firewall_shaper_vinterface.php"); - $tab_array[3] = array("Wizards", false, "firewall_shaper_wizards.php"); + $tab_array[3] = array("Layer7", false, "firewall_shaper_layer7.php"); + $tab_array[4] = array("Wizards", false, "firewall_shaper_wizards.php"); display_top_tabs($tab_array); ?> diff --git a/usr/local/www/firewall_shaper_wizards.php b/usr/local/www/firewall_shaper_wizards.php index a086cc1..2290a3f 100755 --- a/usr/local/www/firewall_shaper_wizards.php +++ b/usr/local/www/firewall_shaper_wizards.php @@ -97,7 +97,8 @@ include("fbegin.inc"); $tab_array[0] = array("By Interface", false, "firewall_shaper.php"); $tab_array[1] = array("By Queue", false, "firewall_shaper_queues.php"); $tab_array[2] = array("Limiter", false, "firewall_shaper_vinterface.php"); - $tab_array[3] = array("Wizards", true, "firewall_shaper_wizards.php"); + $tab_array[3] = array("Layer7", false, "firewall_shaper_layer7.php"); + $tab_array[4] = array("Wizards", true, "firewall_shaper_wizards.php"); display_top_tabs($tab_array); ?> -- cgit v1.1