diff --git a/plist b/plist index 15cdc7011d0..b98236d7bc0 100644 --- a/plist +++ b/plist @@ -302,6 +302,7 @@ /usr/local/opnsense/mvc/app/controllers/OPNsense/Firewall/Api/AliasController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Firewall/Api/AliasUtilController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Firewall/Api/CategoryController.php +/usr/local/opnsense/mvc/app/controllers/OPNsense/Firewall/Api/DNatController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Firewall/Api/FilterBaseController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Firewall/Api/FilterController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Firewall/Api/FilterUtilController.php @@ -310,12 +311,14 @@ /usr/local/opnsense/mvc/app/controllers/OPNsense/Firewall/Api/OneToOneController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Firewall/Api/SourceNatController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Firewall/CategoryController.php +/usr/local/opnsense/mvc/app/controllers/OPNsense/Firewall/DNatController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Firewall/FilterController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Firewall/GroupController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Firewall/NptController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Firewall/OneToOneController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Firewall/SourceNatController.php /usr/local/opnsense/mvc/app/controllers/OPNsense/Firewall/forms/categoryEdit.xml +/usr/local/opnsense/mvc/app/controllers/OPNsense/Firewall/forms/dialogDNatRule.xml /usr/local/opnsense/mvc/app/controllers/OPNsense/Firewall/forms/dialogFilterRule.xml /usr/local/opnsense/mvc/app/controllers/OPNsense/Firewall/forms/dialogNptRule.xml /usr/local/opnsense/mvc/app/controllers/OPNsense/Firewall/forms/dialogOneToOneRule.xml @@ -729,6 +732,8 @@ /usr/local/opnsense/mvc/app/models/OPNsense/Firewall/Alias.xml /usr/local/opnsense/mvc/app/models/OPNsense/Firewall/Category.php /usr/local/opnsense/mvc/app/models/OPNsense/Firewall/Category.xml +/usr/local/opnsense/mvc/app/models/OPNsense/Firewall/DNat.php +/usr/local/opnsense/mvc/app/models/OPNsense/Firewall/DNat.xml /usr/local/opnsense/mvc/app/models/OPNsense/Firewall/DynamicAliases/CaptivePortalAliases.php /usr/local/opnsense/mvc/app/models/OPNsense/Firewall/DynamicAliases/InterfaceNetworkAliases.php /usr/local/opnsense/mvc/app/models/OPNsense/Firewall/DynamicAliases/README.md @@ -736,11 +741,17 @@ /usr/local/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/AliasContentField.php /usr/local/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/AliasField.php /usr/local/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/AliasNameField.php +/usr/local/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/CategoryField.php +/usr/local/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/CategoryMapField.php +/usr/local/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/DNatAssociatedRuleField.php +/usr/local/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/DNatSequenceField.php /usr/local/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/FilterRuleField.php /usr/local/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/FilterSequenceField.php /usr/local/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/GroupField.php /usr/local/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/GroupNameField.php /usr/local/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/InterfaceField.php +/usr/local/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/NetworkMappedField.php +/usr/local/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/PortMappedField.php /usr/local/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/ScheduleField.php /usr/local/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/SourceNatRuleField.php /usr/local/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/TosField.php @@ -946,6 +957,7 @@ /usr/local/opnsense/mvc/app/views/OPNsense/Firewall/alias.volt /usr/local/opnsense/mvc/app/views/OPNsense/Firewall/alias_util.volt /usr/local/opnsense/mvc/app/views/OPNsense/Firewall/category.volt +/usr/local/opnsense/mvc/app/views/OPNsense/Firewall/dnat_rule.volt /usr/local/opnsense/mvc/app/views/OPNsense/Firewall/filter.volt /usr/local/opnsense/mvc/app/views/OPNsense/Firewall/filter_rule.volt /usr/local/opnsense/mvc/app/views/OPNsense/Firewall/group.volt @@ -2476,8 +2488,6 @@ /usr/local/www/diag_authentication.php /usr/local/www/diag_backup.php /usr/local/www/fbegin.inc -/usr/local/www/firewall_nat.php -/usr/local/www/firewall_nat_edit.php /usr/local/www/firewall_nat_out.php /usr/local/www/firewall_nat_out_edit.php /usr/local/www/firewall_rule_lookup.php diff --git a/src/etc/inc/filter.inc b/src/etc/inc/filter.inc index 24f916ac605..5eb0b358614 100644 --- a/src/etc/inc/filter.inc +++ b/src/etc/inc/filter.inc @@ -253,13 +253,8 @@ function filter_configure_sync($verbose = false, $load_aliases = true) } } - if (!empty($config['nat']['rule'])) { - // register user forward rules - foreach ($config['nat']['rule'] as $rule) { - if (is_array($rule)) { - $fw->registerForwardRule(600, $rule); - } - } + foreach ((new OPNsense\Firewall\DNat())->rule->sortedBy(['sequence']) as $key => $rule) { + $fw->registerForwardRule(600, $rule->getNodeContent()); } openlog("firewall", LOG_DAEMON, LOG_LOCAL4); diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Firewall/Api/DNatController.php b/src/opnsense/mvc/app/controllers/OPNsense/Firewall/Api/DNatController.php new file mode 100644 index 00000000000..8f3f0bc8c0d --- /dev/null +++ b/src/opnsense/mvc/app/controllers/OPNsense/Firewall/Api/DNatController.php @@ -0,0 +1,213 @@ +updated->time = sprintf('%0.2f', microtime(true)); + $node->updated->username = $this->getUserName(); + $node->updated->description = sprintf('%s made changes', $_SERVER['SCRIPT_NAME']); + if ($node->created->time->isEmpty()) { + $node->created->time = $node->updated->time; + $node->created->username = $node->updated->username; + $node->created->description = $node->updated->description; + } + } + + public function searchRuleAction() + { + $category = (array)$this->request->get('category'); + $filter_funct = function ($record) use ($category) { + /* categories are indexed by name in the record, but offered as uuid in the selector */ + $catids = !empty((string)$record->categories) ? explode(',', (string)$record->categories) : []; + return empty($category) || array_intersect($catids, $category); + }; + + $results = $this->searchBase("rule", null, "sequence", $filter_funct); + + /* carry results */ + foreach ($results['rows'] as &$record) { + /* offer list of colors to be used by the frontend */ + $record['category_colors'] = $this->getCategoryColors(explode(',', $record['categories'])); + /* format "networks" and ports */ + foreach (['source.network','source.port','destination.network','destination.port', 'target', 'local-port'] as $field) { + if (!empty($record[$field])) { + $record["alias_meta_{$field}"] = $this->getNetworks($record[$field]); + } + } + } + + return $results; + } + + public function setRuleAction($uuid) + { + /* prevent created metadata being overwritten or offered */ + if (is_array($_POST['rule']) && isset($_POST['rule']['created'])) { + unset($_POST['rule']['created']); + } + return $this->setBase("rule", "rule", $uuid); + } + + public function addRuleAction() + { + /* prevent created metadata being overwritten or offered */ + if (is_array($_POST['rule']) && isset($_POST['rule']['created'])) { + unset($_POST['rule']['created']); + } + return $this->addBase("rule", "rule"); + } + + public function getRuleAction($uuid = null) + { + return $this->getBase("rule", "rule", $uuid); + } + + public function delRuleAction($uuid) + { + return $this->delBase("rule", $uuid); + } + + /** + * opposite toggle (disable instead of enable) + */ + public function toggleRuleAction($uuid, $disabled = null) + { + $result = ['result' => 'failed']; + if ($this->request->isPost() && $uuid != null) { + Config::getInstance()->lock(); + $node = $this->getModel()->getNodeByReference('rule.' . $uuid); + if ($node != null) { + if (in_array($disabled, ['0', '1'])) { + $node->disabled = (string)$disabled; + } else { + $node->disabled = (string)$node->disabled == '1' ? '0' : '1'; + } + $result['result'] = $node->disabled->isEmpty() ? 'Enabled' : 'Disabled'; + $this->save(false, true); + } + } + return $result; + } + + /** + * Moves the selected rule so that it appears immediately before the target rule. + * + * Uses integer gap numbering to update the sequence for only the moved rule. + * Rules will be renumbered within the selected range to prevent movements causing overlaps, + * but try to keep the changes as minimal as possible. + * + * @param string $selected_uuid The UUID of the rule to be moved. + * @param string $target_uuid The UUID of the target rule (the rule before which the selected rule is to be placed). + * @return array Returns ["status" => "ok"] on success, throws a userexception otherwise. + */ + public function moveRuleBeforeAction($selected_uuid, $target_uuid) + { + if (!$this->request->isPost()) { + return ["status" => "error", "message" => gettext("Invalid request method")]; + } + $target_node = $this->getModel()->getNodeByReference('rule.' . $target_uuid); + $selected_node = $this->getModel()->getNodeByReference('rule.' . $selected_uuid); + if ($target_node === null || $selected_node === null) { + throw new UserException( + gettext("Either source or destination is not a rule managed with this component"), + gettext("DNat") + ); + } + $step_size = 50; + $new_key = null; + $prev_record = null; + foreach ($this->getModel()->rule->sortedBy(['sequence']) as $record) { + $uuid = $record->getAttribute('uuid'); + if ($target_uuid === $uuid) { + $prev_sequence = (($prev_record?->sequence->asFloat()) ?? 1); + $distance = $record->sequence->asFloat() - $prev_sequence; + if ($distance > 2) { + $new_key = intdiv($distance, 2) + $prev_sequence; + break; + } else { + $new_key = $prev_record === null ? 1 : ($prev_sequence + $step_size); + $record->sequence = (string)($new_key + $step_size); + } + } elseif ($new_key !== null) { + if ($record->sequence->asFloat() < $prev_record?->sequence->asFloat()) { + $record->sequence = (string)($prev_record?->sequence->asFloat() + $step_size); + } + } + $prev_record = $record; + } + if ($new_key !== null) { + $selected_node->sequence = (string)$new_key; + /* we're only changing sequences, forcefully save */ + $this->getModel()->serializeToConfig(false, true); + Config::getInstance()->save(); + } + + return ["status" => "ok"]; + } + + public function toggleRuleLogAction($uuid, $log) + { + if (!$this->request->isPost()) { + return ['status' => 'error', 'message' => gettext('Invalid request method')]; + } + + $mdl = $this->getModel(); + $node = null; + foreach ($mdl->rule->iterateItems() as $item) { + if ((string)$item->getAttribute('uuid') === $uuid) { + $node = $item; + break; + } + } + if ($node === null) { + throw new UserException(gettext("Rule not found"), gettext("DNat")); + } + + $node->log = $log; + $mdl->serializeToConfig(); + Config::getInstance()->save(); + + return ['status' => 'ok']; + } + +} + diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Firewall/DNatController.php b/src/opnsense/mvc/app/controllers/OPNsense/Firewall/DNatController.php new file mode 100644 index 00000000000..86d1209aeb6 --- /dev/null +++ b/src/opnsense/mvc/app/controllers/OPNsense/Firewall/DNatController.php @@ -0,0 +1,38 @@ +view->pick('OPNsense/Firewall/dnat_rule'); + $this->view->formDialogDNatRule = $this->getForm("dialogDNatRule"); + $this->view->formGridDNatRule = $this->getFormGrid('dialogDNatRule'); + } +} diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Firewall/forms/dialogDNatRule.xml b/src/opnsense/mvc/app/controllers/OPNsense/Firewall/forms/dialogDNatRule.xml new file mode 100644 index 00000000000..14863d54d3c --- /dev/null +++ b/src/opnsense/mvc/app/controllers/OPNsense/Firewall/forms/dialogDNatRule.xml @@ -0,0 +1,272 @@ +
+ + header + + + + rule.disabled + + checkbox + Disable this rule so it will not be used. + + boolean + rowtoggle + 5 + 50 + center + + + + rule.sequence + + text + Rules are evaluated in sequence order. + true + + false + asc + + + + rule.category + + select_multiple + + Assign categories for rule organization. + + category + 1 + 80 + + + + rule.descr + + text + Enter a description to identify this rule. + + 110 + 100 + + + + header + + + + rule.interface + + select_multiple + any + Choose the interface(s) on which the traffic originates. + + interfaces + 25 + + + + rule.ipprotocol + + dropdown + Select IPv4, IPv6 or both. + + any + 30 + + + + rule.protocol + + dropdown + Select the protocol this rule should match. + + any + 35 + + + + header + + true + + + rule.source.not + + checkbox + Match everything except the specified source. + + true + source.not + + + + rule.source.network + + text + + Specify the source network or alias to match. + + + alias + 50 + 100 + source.network + + + + rule.source.port + + text + + Source port or port range. + + + 60 + alias + 60 + source.port + + + + header + + + + rule.destination.not + + checkbox + Match everything except the specified destination. + + true + destination.not + + + + rule.destination.network + + text + + Destination address or alias to match. + + + alias + 70 + 100 + destination.network + + + + rule.destination.port + + text + + Destination port or port range. + + + 80 + alias + 60 + destination.port + + + + header + + + + rule.target + + text + + The internal IP address to forward traffic to. + + alias + 90 + 100 + + + + rule.local-port + + text + + The port on the internal host to forward traffic to. + + alias + 95 + 60 + + + + rule.poolopts + + dropdown + Choose how traffic is distributed when multiple target IPs are used. + true + + true + + + + header + + + + rule.log + + checkbox + Log packets matching this rule. + + true + + + + rule.nosync + + checkbox + Exclude this rule from synchronizing to HA peers. + true + + false + + + + rule.natreflection + + dropdown + Control NAT reflection for this rule. + true + + true + + + + rule.tag + + text + Assign a tag to packets matching this rule. + true + + false + + + + rule.tagged + + text + Only match packets that have this tag. + true + + false + + + + rule.pass + + checkbox + When this checkbox is set, traffic matching this nat rule will automatically be passed without inspecting firewall rules. We do advise to create rules manually for better visibility, please keep in mind the destination for the rule should match the target defined in this NAT rule. + + false + + +
diff --git a/src/opnsense/mvc/app/library/OPNsense/Firewall/ForwardRule.php b/src/opnsense/mvc/app/library/OPNsense/Firewall/ForwardRule.php index 407f3b162c6..765f524224f 100644 --- a/src/opnsense/mvc/app/library/OPNsense/Firewall/ForwardRule.php +++ b/src/opnsense/mvc/app/library/OPNsense/Firewall/ForwardRule.php @@ -100,7 +100,7 @@ private function parseRdrRules() foreach ($this->reader() as $tmp) { $tmp['rule_types'] = array("rdr"); $tmp['nordr'] = !empty($tmp['nordr']); - if (!empty($tmp['associated-rule-id']) && $tmp['associated-rule-id'] == "pass") { + if (!empty($tmp['pass'])) { $tmp['pass'] = empty($tmp['nordr']); } // target address, when invalid, disable rule diff --git a/src/opnsense/mvc/app/library/OPNsense/Firewall/Plugin.php b/src/opnsense/mvc/app/library/OPNsense/Firewall/Plugin.php index b461ddacfc6..94722ac618f 100644 --- a/src/opnsense/mvc/app/library/OPNsense/Firewall/Plugin.php +++ b/src/opnsense/mvc/app/library/OPNsense/Firewall/Plugin.php @@ -296,7 +296,11 @@ public function registerFilterRule($prio, $conf, $defaults = null) public function registerForwardRule($prio, $conf) { if (!empty($this->systemDefaults['forward'])) { - $conf = array_merge($this->systemDefaults['forward'], $conf); + foreach ($this->systemDefaults['forward'] as $key => $val) { + if (empty($conf[$key])) { + $conf[$key] = $val; + } + } } $rule = new ForwardRule($this->interfaceMapping, $conf); if (empty($this->natRules[$prio])) { diff --git a/src/opnsense/mvc/app/library/OPNsense/Firewall/Rule.php b/src/opnsense/mvc/app/library/OPNsense/Firewall/Rule.php index d5be8a56b90..7d5757a42c5 100644 --- a/src/opnsense/mvc/app/library/OPNsense/Firewall/Rule.php +++ b/src/opnsense/mvc/app/library/OPNsense/Firewall/Rule.php @@ -353,14 +353,14 @@ protected function legacyMoveAddressFields(&$rule) $interfaces = $this->interfaceMapping; foreach ($fields as $tag => $target) { if (!empty($rule[$tag])) { - if (isset($rule[$tag]['any'])) { - $rule[$target] = 'any'; - } elseif (!empty($rule[$tag]['network'])) { + if (!empty($rule[$tag]['network'])) { $rule[$target] = $rule[$tag]['network']; } elseif (!empty($rule[$tag]['address'])) { $rule[$target] = $rule[$tag]['address']; + } else { + $rule[$target] = 'any'; } - $rule[$target . '_not'] = isset($rule[$tag]['not']); /* to be used in mapAddressInfo() */ + $rule[$target . '_not'] = !empty($rule[$tag]['not']); /* to be used in mapAddressInfo() */ if ( isset($rule['protocol']) && @@ -369,13 +369,6 @@ protected function legacyMoveAddressFields(&$rule) ) { $rule[$target . "_port"] = $rule[$tag]['port']; } - if (!isset($rule[$target])) { - // couldn't convert address, disable rule - // dump all tag contents in target (from/to) for reference - $rule['disabled'] = true; - $this->log("Unable to convert address, see {$target} for details"); - $rule[$target] = json_encode($rule[$tag]); - } } } } diff --git a/src/opnsense/mvc/app/models/OPNsense/Core/ACL/ACL.xml b/src/opnsense/mvc/app/models/OPNsense/Core/ACL/ACL.xml index d9e566d5abe..7c8b910fa9c 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Core/ACL/ACL.xml +++ b/src/opnsense/mvc/app/models/OPNsense/Core/ACL/ACL.xml @@ -215,17 +215,12 @@ firewall_nat_out_edit.php* - - Firewall: NAT: Port Forward - - firewall_nat.php* - api/firewall/category/search_item* - - - Firewall: NAT: Port Forward: Edit + Firewall: NAT: Destination NAT - firewall_nat_edit.php* + ui/firewall/d_nat + api/firewall/d_nat/* + api/firewall/category/search_item* diff --git a/src/opnsense/mvc/app/models/OPNsense/Core/Menu/Menu.xml b/src/opnsense/mvc/app/models/OPNsense/Core/Menu/Menu.xml index cf21b3cafa2..1acff659209 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Core/Menu/Menu.xml +++ b/src/opnsense/mvc/app/models/OPNsense/Core/Menu/Menu.xml @@ -92,9 +92,6 @@ - - - diff --git a/src/opnsense/mvc/app/models/OPNsense/Firewall/DNat.php b/src/opnsense/mvc/app/models/OPNsense/Firewall/DNat.php new file mode 100644 index 00000000000..0b6be5cd9ad --- /dev/null +++ b/src/opnsense/mvc/app/models/OPNsense/Firewall/DNat.php @@ -0,0 +1,41 @@ + + /nat/rule+ + 1.0.0 + Destination NAT (port-forward) + + + + 1 + 999999 + Sequence shall be between 1 and 999999. + Y + + + + + + Y + + /^(?!lo0$).*/ + + Y + + + + + + IPv4 + IPv6 + IPv4+IPv6 + + any + + + + + + TCP/UDP + + any + + + + +
+ + Y + Y + Y + Please specify a valid portnumber, name, alias or range. + + + + + + +
+ + Y + Y + Y + Please specify a valid portnumber, name, alias or range. + + + + + + Y + Please specify a valid portnumber, name, alias or range. + + + + Default + + Round Robin + Round Robin with Sticky Address + Random + Random with Sticky Address + Source Hash + Bitmask + + + + + Y + + + + + + + + + Use system default + + Enable + Disable + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/CategoryField.php b/src/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/CategoryField.php new file mode 100644 index 00000000000..243c5b141b2 --- /dev/null +++ b/src/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/CategoryField.php @@ -0,0 +1,51 @@ +categories->category->iterateItems() as $category) { + self::$categories[$category->name->getValue()] = $category->name->getValue(); + } + } + $this->internalOptionList = self::$categories; + return parent::actionPostLoadingEvent(); + } +} diff --git a/src/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/CategoryMapField.php b/src/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/CategoryMapField.php new file mode 100644 index 00000000000..8838d82293b --- /dev/null +++ b/src/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/CategoryMapField.php @@ -0,0 +1,53 @@ +categories->category->iterateItems() as $key => $category) { + self::$catidmap[$category->name->getValue()] = $key; + } + } + $record = $this->getParentNode(); + $catids = array_values(array_intersect_key(self::$catidmap, array_flip($record->category->getValues()))); + $this->internalValue = implode(',', $catids); + return parent::actionPostLoadingEvent(); + } +} diff --git a/src/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/DNatAssociatedRuleField.php b/src/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/DNatAssociatedRuleField.php new file mode 100644 index 00000000000..2f19b39eb57 --- /dev/null +++ b/src/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/DNatAssociatedRuleField.php @@ -0,0 +1,49 @@ +getParentNode()->pass = '1'; + } + /* unused, flush */ + return parent::setValue(''); + } +} diff --git a/src/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/DNatSequenceField.php b/src/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/DNatSequenceField.php new file mode 100644 index 00000000000..81609b43407 --- /dev/null +++ b/src/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/DNatSequenceField.php @@ -0,0 +1,69 @@ +internalValue = (string)self::$current_max_seq; + } + + protected function actionPostLoadingEvent() + { + if (self::$current_max_seq === null) { + // Start from the minimum value if no entries exist + self::$current_max_seq = (int)$this->minimum_value; + if (isset($this->internalParentNode->internalParentNode)) { + foreach ($this->internalParentNode->internalParentNode->iterateItems() as $node) { + $currentNumber = (int)((string)$node->{$this->internalXMLTagName}); + // Update maxNumber if this value is greater + if ($currentNumber >= self::$current_max_seq) { + self::$current_max_seq = $currentNumber; + } + } + } + } + if ($this->internalValue == '') { + self::$current_max_seq += 100; + $this->internalValue = (string)self::$current_max_seq; + } + return parent::actionPostLoadingEvent(); + } +} diff --git a/src/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/NetworkMappedField.php b/src/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/NetworkMappedField.php new file mode 100644 index 00000000000..dd1c8cf4c98 --- /dev/null +++ b/src/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/NetworkMappedField.php @@ -0,0 +1,59 @@ +getParentNode(); + foreach (['address', 'network'] as $fname) { + if (isset($pnode->$fname)) { + $pnode->$fname = $value; + } + } + } + return parent::setValue((string)$value); + } + + return parent::setValue($value); + } +} diff --git a/src/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/PortMappedField.php b/src/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/PortMappedField.php new file mode 100644 index 00000000000..e009eaf4b9a --- /dev/null +++ b/src/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/PortMappedField.php @@ -0,0 +1,50 @@ + + + + diff --git a/src/opnsense/mvc/app/views/OPNsense/Firewall/dnat_rule.volt b/src/opnsense/mvc/app/views/OPNsense/Firewall/dnat_rule.volt new file mode 100644 index 00000000000..0d25639ea99 --- /dev/null +++ b/src/opnsense/mvc/app/views/OPNsense/Firewall/dnat_rule.volt @@ -0,0 +1,627 @@ +{# + # Copyright (c) 2025 Deciso B.V. + # All rights reserved. + # + # Redistribution and use in source and binary forms, with or without modification, + # are permitted provided that the following conditions are met: + # + # 1. Redistributions of source code must retain the above copyright notice, + # this list of conditions and the following disclaimer. + # + # 2. Redistributions in binary form must reproduce the above copyright notice, + # this list of conditions and the following disclaimer in the documentation + # and/or other materials provided with the distribution. + # + # THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + # AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + # AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + # OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + # POSSIBILITY OF SUCH DAMAGE. + #} + + + + + +
+ + + {{ partial('layout_partials/base_bootgrid_table', formGridDNatRule + {'command_width':'150'}) }} +
+ +{{ partial('layout_partials/base_apply_button', {'data_endpoint': '/api/firewall/d_nat/apply'}) }} +{{ partial("layout_partials/base_dialog",{'fields':formDialogDNatRule,'id':formGridDNatRule['edit_dialog_id'],'label':lang._('Edit Destination Nat')}) }} diff --git a/src/www/firewall_nat.php b/src/www/firewall_nat.php deleted file mode 100644 index 73c3f48f382..00000000000 --- a/src/www/firewall_nat.php +++ /dev/null @@ -1,570 +0,0 @@ - - * Copyright (C) 2004 Scott Ullrich - * Copyright (C) 2003-2004 Manuel Kasper - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -require_once("guiconfig.inc"); -require_once("interfaces.inc"); -require_once("filter.inc"); - -/****f* itemid/delete_id (duplicate to remove itemid.inc) - * NAME - * delete_id - delete an item with ['id'] = $id from $array - * INPUTS - * $id - int: The ID to delete - * $array - array to delete the item from - * RESULT - * boolean - true if item was found and deleted - ******/ -function delete_id($id, &$array) -{ - // Index to delete - $delete_index = NULL; - - if (!isset($array)) { - return false; - } - - // Search for the item in the array - foreach ($array as $key => $item){ - // If this item is the one we want to delete - if(isset($item['associated-rule-id']) && $item['associated-rule-id']==$id ){ - $delete_index = $key; - break; - } - } - - // If we found the item, unset it - if( $delete_index!==NULL ){ - unset($array[$delete_index]); - return true; - } else { - return false; - } -} - - -$a_nat = &config_read_array('nat', 'rule'); -if ($_SERVER['REQUEST_METHOD'] === 'POST') { - $pconfig = $_POST; - if (isset($pconfig['id']) && isset($a_nat[$pconfig['id']])) { - // id found and valid - $id = $pconfig['id']; - } - if (isset($pconfig['apply'])) { - write_config(); - filter_configure(); - $savemsg = get_std_save_message(); - clear_subsystem_dirty('natconf'); - clear_subsystem_dirty('filter'); - } elseif (isset($pconfig['act']) && in_array($pconfig['act'], ['del', 'del_x'])) { - if ($pconfig['act'] == 'del') { - $pconfig['rule'] = isset($id) ? [$id] : []; - } elseif (empty($pconfig['rule'])) { - $pconfig['rule'] = []; - } - /* delete selected rules */ - foreach ($pconfig['rule'] as $rulei) { - if (isset($a_nat[$rulei])) { - $target = $rule['target']; - // Check for filter rule associations - if (isset($a_nat[$rulei]['associated-rule-id'])){ - delete_id($a_nat[$rulei]['associated-rule-id'], $config['filter']['rule']); - mark_subsystem_dirty('filter'); - } - unset($a_nat[$rulei]); - } - } - write_config(); - mark_subsystem_dirty('natconf'); - header(url_safe('Location: /firewall_nat.php')); - exit; - } elseif (isset($pconfig['act']) && in_array($pconfig['act'], ['toggle', 'toggle_enable', 'toggle_disable'])) { - if ($pconfig['act'] == 'toggle') { - $pconfig['rule'] = isset($id) ? [$id] : []; - } elseif (empty($pconfig['rule'])) { - $pconfig['rule'] = []; - } - foreach ($pconfig['rule'] as $rulei) { - if ($pconfig['act'] == 'toggle') { - $a_nat[$rulei]['disabled'] = !$a_nat[$rulei]['disabled']; - } else { - $a_nat[$rulei]['disabled'] = $pconfig['act'] == 'toggle_disable'; - } - $natent = $a_nat[$rulei]; - if (!empty($natent['associated-rule-id']) && !empty($config['filter']['rule'])) { - foreach ($config['filter']['rule'] as $key => &$item){ - if (isset($item['associated-rule-id']) && $item['associated-rule-id'] == $natent['associated-rule-id']) { - $item['disabled'] = $natent['disabled']; - break; - } - } - } - } - write_config('Firewall: NAT: Port Forward, toggle NAT rule'); - mark_subsystem_dirty('natconf'); - header(url_safe('Location: /firewall_nat.php')); - exit; - } elseif ( isset($pconfig['act']) && $pconfig['act'] == 'move') { - // move records - if (isset($pconfig['rule']) && count($pconfig['rule']) > 0) { - // if rule not set/found, move to end - if (!isset($id)) { - $id = count($a_nat); - } - $a_nat = legacy_move_config_list_items($a_nat, $id, $pconfig['rule']); - } - write_config(); - mark_subsystem_dirty('natconf'); - header(url_safe('Location: /firewall_nat.php')); - exit; - } -} - -include("head.inc"); - -legacy_html_escape_form_data($a_nat); - -$lockout_spec = filter_core_get_antilockout(); - -?> - - - - -
-
-
- - - -" . gettext("You must apply the changes in order for them to take effect."));?>
- -
-
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - $lockout_prts): ?> - - - - - - - - - - - - - - - - - - " data-category=""> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   - - - - - - - - - -
TCP** - -
- - - - "> - - - "> - - - - - - - - - - - - - - - - - - - - - - - - -  " data-toggle="tooltip"> - - - - - -   - - " data-toggle="tooltip"> - - - - - - - - - " class="act_move btn btn-default btn-xs"> - - - " class="btn btn-default btn-xs"> - - - " data-toggle="tooltip" class="act_delete btn btn-default btn-xs"> - - - "> - - -
-
-
-
-
-
-
-
- diff --git a/src/www/firewall_nat_edit.php b/src/www/firewall_nat_edit.php deleted file mode 100644 index 7675c2f758f..00000000000 --- a/src/www/firewall_nat_edit.php +++ /dev/null @@ -1,1144 +0,0 @@ - - * Copyright (C) 2003-2004 Manuel Kasper - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -require_once("guiconfig.inc"); -require_once("filter.inc"); - -$a_nat = &config_read_array('nat', 'rule'); - -if ($_SERVER['REQUEST_METHOD'] === 'GET') { - // load form data from config - if (isset($_GET['id']) && is_numericint($_GET['id']) && isset($a_nat[$_GET['id']])) { - $id = $_GET['id']; - $configId = $id; // load form data from id - } elseif (isset($_GET['dup']) && isset($a_nat[$_GET['dup']])){ - $after = $_GET['dup']; - $configId = $_GET['dup']; // load form data from id - } - if (isset($_GET['after']) && isset($a_nat[$_GET['after']])) { - $after = $_GET['after']; - } - - // initialize form and set defaults - $pconfig = array(); - $pconfig['protocol'] = "tcp"; - $pconfig['srcbeginport'] = "any"; - $pconfig['srcendport'] = "any"; - $pconfig['interface'] = ["wan"]; - $pconfig['dstbeginport'] = 80 ; - $pconfig['dstendport'] = 80 ; - $pconfig['local-port'] = 80; - $pconfig['associated-rule-id'] = "add-associated"; - if (isset($configId)) { - // copy 1-on-1 - foreach (array('protocol','target','local-port','descr','interface','nosync','log', - 'natreflection','created','updated','ipprotocol','tag','tagged','poolopts', 'category') as $fieldname) { - if (isset($a_nat[$configId][$fieldname])) { - $pconfig[$fieldname] = $a_nat[$configId][$fieldname]; - } else { - $pconfig[$fieldname] = null; - } - } - // fields with some kind of logic. - if (!isset($_GET['dup']) && isset($a_nat[$configId]['associated-rule-id'])) { - $pconfig['associated-rule-id'] = $a_nat[$configId]['associated-rule-id']; - } - $pconfig['disabled'] = isset($a_nat[$configId]['disabled']); - $pconfig['nordr'] = isset($a_nat[$configId]['nordr']); - $pconfig['interface'] = explode(",", $pconfig['interface']); - address_to_pconfig($a_nat[$configId]['source'], $pconfig['src'], - $pconfig['srcmask'], $pconfig['srcnot'], - $pconfig['srcbeginport'], $pconfig['srcendport']); - - address_to_pconfig($a_nat[$configId]['destination'], $pconfig['dst'], - $pconfig['dstmask'], $pconfig['dstnot'], - $pconfig['dstbeginport'], $pconfig['dstendport']); - if (empty($pconfig['ipprotocol'])) { - if (strpos($pconfig['src'].$pconfig['dst'].$pconfig['target'], ":") !== false) { - $pconfig['ipprotocol'] = 'inet6'; - } else { - $pconfig['ipprotocol'] = 'inet'; - } - } - } elseif (isset($_GET['template']) && $_GET['template'] == 'transparent_proxy') { - // new rule for transparent proxy reflection, to use as sample - $pconfig['interface'] = ["lan"]; - $pconfig['src'] = "lan"; - $pconfig['dst'] = "any"; - $pconfig['ipprotocol'] = "inet"; - if (isset($_GET['https'])){ - $pconfig['dstbeginport'] = 443; - $pconfig['dstendport'] = 443; - if (isset($config['OPNsense']['proxy']['forward']['sslbumpport'])) { - $pconfig['local-port'] = $config['OPNsense']['proxy']['forward']['sslbumpport']; - } else { - $pconfig['local-port'] = 3129; - } - } else { - $pconfig['dstbeginport'] = 80; - $pconfig['dstendport'] = 80; - // try to read the proxy configuration to determine the current port - // this has some disadvantages in case of dependencies, but there isn't - // a much better solution available at the moment. - if (isset($config['OPNsense']['proxy']['forward']['port'])) { - $pconfig['local-port'] = $config['OPNsense']['proxy']['forward']['port']; - } else { - $pconfig['local-port'] = 3128; - } - } - $pconfig['target'] = '127.0.0.1'; - - $pconfig['natreflection'] = 'enable'; - $pconfig['descr'] = gettext("redirect traffic to proxy"); - } else { - $pconfig['src'] = "any"; - } - // init empty fields - foreach (array('dst','dstmask','srcmask','dstbeginport','dstendport','target', - 'local-port','natreflection','descr','disabled','nosync','ipprotocol', - 'tag','tagged','poolopts') as $fieldname) { - if (!isset($pconfig[$fieldname])) { - $pconfig[$fieldname] = null; - } - } - $pconfig['category'] = !empty($pconfig['category']) ? explode(",", $pconfig['category']) : []; -} elseif ($_SERVER['REQUEST_METHOD'] === 'POST') { - $pconfig = $_POST; - $input_errors = array(); - // validate id and store if usable - if (isset($pconfig['id']) && is_numericint($pconfig['id']) && isset($a_nat[$pconfig['id']])) { - $id = $_POST['id']; - } - if (isset($pconfig['after']) && isset($a_nat[$pconfig['after']])) { - // place record after provided sequence number - $after = $pconfig['after']; - } - - /* Validate input data */ - if ($pconfig['protocol'] == 'tcp' || $pconfig['protocol'] == 'udp' || $pconfig['protocol'] == 'tcp/udp') { - $reqdfields = explode(" ", "interface protocol dstbeginport dstendport"); - $reqdfieldsn = array(gettext("Interface"),gettext("Protocol"),gettext("Destination port from"),gettext("Destination port to")); - } else { - $reqdfields = explode(" ", "interface protocol"); - $reqdfieldsn = array(gettext("Interface"),gettext("Protocol")); - } - - $reqdfields[] = "src"; - $reqdfieldsn[] = gettext("Source address"); - $reqdfields[] = "dst"; - $reqdfieldsn[] = gettext("Destination address"); - - if (empty($pconfig['nordr'])) { - $reqdfields[] = "target"; - $reqdfieldsn[] = gettext("Redirect target IP"); - } - - do_input_validation($pconfig, $reqdfields, $reqdfieldsn, $input_errors); - - if (!isset($pconfig['nordr']) && ($pconfig['target'] && !is_ipaddroralias($pconfig['target']) && !is_subnet($pconfig['target']))) { - $input_errors[] = sprintf(gettext("\"%s\" is not a valid redirect target IP address, network or host alias."), $pconfig['target']); - } - if (!empty($pconfig['srcbeginport']) && $pconfig['srcbeginport'] != 'any' && !is_portoralias($pconfig['srcbeginport'])) - $input_errors[] = sprintf(gettext("%s is not a valid start source port. It must be a port alias or integer between 1 and 65535."), $pconfig['srcbeginport']); - if (!empty($pconfig['srcendport']) && $pconfig['srcendport'] != 'any' && !is_portoralias($pconfig['srcendport'])) - $input_errors[] = sprintf(gettext("%s is not a valid end source port. It must be a port alias or integer between 1 and 65535."), $pconfig['srcendport']); - if (!empty($pconfig['dstbeginport']) && $pconfig['dstbeginport'] != 'any' && !is_portoralias($pconfig['dstbeginport'])) - $input_errors[] = sprintf(gettext("%s is not a valid start destination port. It must be a port alias or integer between 1 and 65535."), $pconfig['dstbeginport']); - if (!empty($pconfig['dstendport']) && $pconfig['dstendport'] != 'any' && !is_portoralias($pconfig['dstendport'])) - $input_errors[] = sprintf(gettext("%s is not a valid end destination port. It must be a port alias or integer between 1 and 65535."), $pconfig['dstendport']); - - if (($pconfig['protocol'] == "tcp" || $pconfig['protocol'] == "udp" || $_POST['protocol'] == "tcp/udp") && (!isset($pconfig['nordr']) && !is_portoralias($pconfig['local-port']))) { - $input_errors[] = sprintf(gettext("A valid redirect target port must be specified. It must be a port alias or integer between 1 and 65535."), $pconfig['local-port']); - } - - if (!is_specialnet($pconfig['src']) && !is_ipaddroralias($pconfig['src'])) { - $input_errors[] = sprintf(gettext("%s is not a valid source IP address or alias."), $pconfig['src']); - } - - // validate ipv4/v6, addresses should use selected address family - foreach (array('src', 'dst', 'target') as $fieldname) { - if (is_ipaddrv6($pconfig[$fieldname]) && $pconfig['ipprotocol'] != 'inet6') { - $input_errors[] = sprintf(gettext("%s is not a valid IPv4 address."), $pconfig[$fieldname]); - } - if (is_ipaddrv4($pconfig[$fieldname]) && $pconfig['ipprotocol'] != 'inet') { - $input_errors[] = sprintf(gettext("%s is not a valid IPv6 address."), $pconfig[$fieldname]); - } - } - - if (!empty($pconfig['srcmask']) && !is_numericint($pconfig['srcmask'])) { - $input_errors[] = gettext("A valid source bit count must be specified."); - } - - if (!is_specialnet($pconfig['dst']) && !is_ipaddroralias($pconfig['dst'])) { - $input_errors[] = sprintf(gettext("%s is not a valid destination IP address or alias."), $pconfig['dst']); - } - - if (!empty($pconfig['dstmask']) && !is_numericint($pconfig['dstmask'])) { - $input_errors[] = gettext("A valid destination bit count must be specified."); - } - if (!isset($_POST['nordr']) - && is_numericint($pconfig['dstbeginport']) && is_numericint($pconfig['dstendport']) && is_numericint($pconfig['local-port']) - && - (max($pconfig['dstendport'],$pconfig['dstbeginport']) - min($pconfig['dstendport'],$pconfig['dstbeginport']) + $pconfig['local-port']) > 65535) { - $input_errors[] = gettext("The target port range must be an integer between 1 and 65535."); - } - - if (count($input_errors) == 0) { - $natent = array(); - - if ($pconfig['protocol'] != 'any') { - $natent['protocol'] = $pconfig['protocol']; - } - $natent['interface'] = !empty($pconfig['interface']) ? implode(",", $pconfig['interface']) : null; - $natent['category'] = !empty($pconfig['category']) ? implode(",", $pconfig['category']) : null; - $natent['ipprotocol'] = $pconfig['ipprotocol']; - $natent['descr'] = $pconfig['descr']; - $natent['tag'] = $pconfig['tag']; - $natent['tagged'] = $pconfig['tagged']; - $natent['poolopts'] = $pconfig['poolopts']; - - if (!empty($natent['nordr'])) { - $natent['associated-rule-id'] = ''; - } elseif (!empty($pconfig['associated-rule-id']) && $pconfig['associated-rule-id'] == "pass") { - $natent['associated-rule-id'] = "pass"; - } elseif (!empty($pconfig['associated-rule-id']) && !in_array($pconfig['associated-rule-id'], ['add-associated', 'add-unassociated'])) { - $natent['associated-rule-id'] = $pconfig['associated-rule-id']; - } else { - $natent['associated-rule-id'] = null; - } - - $natent['disabled'] = !empty($pconfig['disabled']); - $natent['nordr'] = !empty($pconfig['nordr']); - $natent['nosync'] = !empty($pconfig['nosync']); - $natent['log'] = !empty($pconfig['log']); - - if (empty($natent['nordr'])) { - $natent['target'] = $pconfig['target']; - $natent['local-port'] = $pconfig['local-port']; - } - - pconfig_to_address($natent['source'], $pconfig['src'], - $pconfig['srcmask'], !empty($pconfig['srcnot']), - $pconfig['srcbeginport'], $pconfig['srcendport']); - - pconfig_to_address($natent['destination'], $pconfig['dst'], - $pconfig['dstmask'], !empty($pconfig['dstnot']), - $pconfig['dstbeginport'], $pconfig['dstendport']); - - if ($pconfig['natreflection'] == "purenat" || $pconfig['natreflection'] == "disable") { - $natent['natreflection'] = $pconfig['natreflection']; - } - - // If we used to have an associated filter rule, but no-longer should have one - if (isset($id) && !empty($a_nat[$id]['associated-rule-id']) && ( empty($natent['associated-rule-id']) || $natent['associated-rule-id'] != $a_nat[$id]['associated-rule-id'] ) ) { - // Delete the previous rule - foreach ($config['filter']['rule'] as $key => $item){ - if (isset($item['associated-rule-id']) && $item['associated-rule-id']==$a_nat[$id]['associated-rule-id'] ){ - unset($config['filter']['rule'][$key]); - break; - } - } - mark_subsystem_dirty('filter'); - } - - // Updating a rule with a filter rule associated - if (!empty($natent['associated-rule-id']) || in_array($pconfig['associated-rule-id'], ['add-associated', 'add-unassociated'])) { - /* auto-generate a matching firewall rule */ - $filterent = ['type' => 'pass']; - if (in_array($pconfig['associated-rule-id'], ['add-associated', 'add-unassociated'])) { - $filterent['associated-rule-id'] = $natent['associated-rule-id']; - } else { - $filterent['associated-rule-id'] = $natent['associated-rule-id']; - foreach ($config['filter']['rule'] as $key => &$item){ - if (isset($item['associated-rule-id']) && $item['associated-rule-id']==$natent['associated-rule-id']) { - $filterent = &config_read_array('filter', 'rule', $key); - break; - } - } - } - pconfig_to_address($filterent['source'], $pconfig['src'], - $pconfig['srcmask'], !empty($pconfig['srcnot']), - $pconfig['srcbeginport'], $pconfig['srcendport']); - - // Update interface, protocol and destination - $filterent['interface'] = $natent['interface']; - $filterent['statetype'] = "keep state"; - if (!empty($natent['protocol'])) { - $filterent['protocol'] = $natent['protocol']; - } elseif (isset($filterent['protocol'])) { - unset($filterent['protocol']); - } - $filterent['ipprotocol'] = $natent['ipprotocol']; - if (!isset($filterent['destination'])) { - $filterent['destination'] = array(); - } - $filterent['destination']['address'] = $pconfig['target']; - if (count($pconfig['interface']) > 1) { - $filterent['floating'] = true; - $filterent['quick'] = "yes"; - } else { - unset($filterent['floating']); - unset($filterent['quick']); - } - - if (!empty($pconfig['log'])) { - $filterent['log'] = true; - } elseif (isset($filterent['log'])) { - unset($filterent['log']); - } - - if (!empty($pconfig['disabled'])) { - $filterent['disabled'] = true; - } else { - unset($filterent['disabled']); - } - - if (is_numericint($pconfig['local-port']) && is_numericint($pconfig['dstendport']) && is_numericint($pconfig['dstbeginport'])) { - $dstpfrom = $pconfig['local-port']; - $dstpto = $dstpfrom + max($pconfig['dstendport'], $pconfig['dstbeginport']) - min($pconfig['dstbeginport'],$pconfig['dstendport']) ; - if ($dstpfrom == $dstpto) { - $filterent['destination']['port'] = $dstpfrom; - } else { - $filterent['destination']['port'] = $dstpfrom . "-" . $dstpto; - } - } else { - // if any of the ports is an alias, copy contents of local-port - $filterent['destination']['port'] = $pconfig['local-port']; - } - - $filterent['descr'] = $pconfig['descr']; - $filterent['category'] = $natent['category']; - - // If this is a new rule, create an ID and add the rule - if ( - !empty($pconfig['associated-rule-id']) && - in_array($pconfig['associated-rule-id'], ['add-associated', 'add-unassociated']) - ) { - if ($pconfig['associated-rule-id'] == 'add-associated') { - $filterent['associated-rule-id'] = $natent['associated-rule-id'] = uniqid("nat_", true); - } - $filterent['created'] = make_config_revision_entry(); - $config['filter']['rule'][] = $filterent; - } - - mark_subsystem_dirty('filter'); - } - - // Update the NAT entry now - $natent['updated'] = make_config_revision_entry(); - if (isset($id)) { - if (isset($a_nat[$id]['created'])) { - $natent['created'] = $a_nat[$id]['created']; - } - $a_nat[$id] = $natent; - } else { - $natent['created'] = make_config_revision_entry(); - if (isset($after)) { - array_splice($a_nat, $after+1, 0, array($natent)); - } else { - $a_nat[] = $natent; - } - } - - OPNsense\Core\Config::getInstance()->fromArray($config); - $catmdl = new OPNsense\Firewall\Category(); - if ($catmdl->sync()) { - $catmdl->serializeToConfig(); - $config = OPNsense\Core\Config::getInstance()->toArray(listtags()); - } - write_config(); - mark_subsystem_dirty('natconf'); - - header(url_safe('Location: /firewall_nat.php')); - exit; - } -} - -legacy_html_escape_form_data($pconfig); - -include("head.inc"); -?> - - - - - - - -
-
-
- 0) print_input_errors($input_errors); ?> -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - $filter_rule) { - if ( - isset($filter_rule['associated-rule-id']) && - $filter_rule['associated-rule-id'] == $pconfig['associated-rule-id'] - ) { - $linkedrule = $filter_rule['associated-rule-id']; - $linkedrule_descr = htmlspecialchars('Rule ' . $filter_rule['descr']); - } - } - } -?> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
-
-
-