diff --git a/src/opnsense/mvc/app/library/OPNsense/Firewall/NatRule.php b/src/opnsense/mvc/app/library/OPNsense/Firewall/NatRule.php new file mode 100644 index 000000000..167d57b2a --- /dev/null +++ b/src/opnsense/mvc/app/library/OPNsense/Firewall/NatRule.php @@ -0,0 +1,171 @@ + 'parseIsComment', + 'rdr' => 'parseBool,no rdr,rdr', + 'pass' => 'parseBool,pass ', + 'interface' => 'parseInterface', + 'ipprotocol' => 'parsePlain', + 'protocol' => 'parseReplaceSimple,tcp/udp:{tcp udp},proto ', + 'from' => 'parsePlainCurly,from ', + 'from_port' => 'parsePlainCurly, port ', + 'to' => 'parsePlainCurly,to ', + 'to_port' => 'parsePlainCurly, port ', + 'tag' => 'parsePlain, tag ', + 'tagged' => 'parsePlain, tagged ', + 'target' => 'parsePlain, -> ', + 'localport' => 'parsePlain, port ', + 'poolopts' => 'parsePlain' + ); + + /** + * output parsing + * @param string $value field value + * @return string + */ + protected function parseIsComment($value) + { + return !empty($value) ? "#" : ""; + } + + /** + * search interfaces without a gateway other then the one provided + * @param $interface + * @return list of interfaces + */ + private function reflectionInterfaces($interface) + { + $result = array(); + foreach ($this->interfaceMapping as $intfk => $intf) { + if (empty($intf['gateway']) && empty($intf['gatewayv6']) && $interface != $intfk + && !in_array($intf['if'], $result)) { + $result[] = $intf['if']; + } + } + return $result; + } + + /** + * preprocess internal rule data to detail level of actual ruleset + * handles shortcuts, like inet46 and multiple interfaces + * @return array + */ + private function fetchActualRules() + { + $result = array(); + $result = array(); + $interfaces = empty($this->rule['interface']) ? array(null) : explode(',', $this->rule['interface']); + foreach ($interfaces as $interface) { + if (isset($this->rule['ipprotocol']) && $this->rule['ipprotocol'] == 'inet46') { + $ipprotos = array('inet', 'inet6'); + } elseif (isset($this->rule['ipprotocol'])) { + $ipprotos = array($this->rule['ipprotocol']); + } else { + $ipprotos = array(null); + } + + foreach ($ipprotos as $ipproto) { + $tmp = $this->rule; + $tmp['rdr'] = !empty($tmp['nordr']); + if (!empty($tmp['associated-rule-id']) && $tmp['associated-rule-id'] == "pass") { + $tmp['pass'] = empty($tmp['nordr']); + } + $tmp['interface'] = $interface; + $tmp['ipprotocol'] = $ipproto; + $this->convertAddress($tmp); + // target address, when invalid, disable rule + if (!empty($tmp['target'])) { + if (Util::isAlias($tmp['target'])) { + $tmp['target'] = "\${$tmp['target']}"; + } elseif (!Util::isIpAddress($tmp['target']) && !Util::isSubnet($tmp['target'])) { + $tmp['disabled'] = true; + } + } + // parse our local port + if (!empty($tmp['local-port']) && !empty($tmp['protocol']) + && in_array($tmp['protocol'], array('tcp/udp', 'udp', 'tcp'))) { + if (Util::isAlias($tmp['local-port'])) { + // We will keep this for backwards compatibility, although the alias use is very confusing. + // Because the target can only be one address or range, we will just use the first one found + // in the alias.... confusing. + $tmp_port = Util::getPortAlias($tmp['local-port']); + if (!empty($tmp_port)) { + $tmp['localport'] = $tmp_port[0]; + } + } elseif (Util::isPort($tmp['local-port'])) { + $tmp['localport'] = $tmp['local-port']; + } else { + $tmp['disabled'] = true; + } + } + + // disable rule when interface not found + if (!empty($interface) && empty($this->interfaceMapping[$interface]['if'])) { + $tmp['disabled'] = true; + } + $result[] = $tmp; + // print_r($this->interfaceMapping[$interface]); + // print_r($this->reflectionInterfaces($interface)); + } + } + return $result; + } + + /** + * output rule as string + * @return string ruleset + */ + public function __toString() + { + $ruleTxt = ''; + foreach ($this->fetchActualRules() as $rule) { + foreach ($this->procorder_rdr as $tag => $handle) { + $tmp = explode(',', $handle); + $method = $tmp[0]; + $args = array(isset($rule[$tag]) ? $rule[$tag] : null); + if (count($tmp) > 1) { + array_shift($tmp); + $args = array_merge($args, $tmp); + } + $ruleTxt .= call_user_func_array(array($this,$method), $args); + } + $ruleTxt .= "\n"; + } + return $ruleTxt; + } +} diff --git a/src/opnsense/mvc/app/library/OPNsense/Firewall/Plugin.php b/src/opnsense/mvc/app/library/OPNsense/Firewall/Plugin.php index d66a44c41..0cf276260 100644 --- a/src/opnsense/mvc/app/library/OPNsense/Firewall/Plugin.php +++ b/src/opnsense/mvc/app/library/OPNsense/Firewall/Plugin.php @@ -39,6 +39,7 @@ class Plugin { private $anchors = array(); private $filterRules = array(); + private $natRules = array(); private $interfaceMapping = array(); private $gatewayMapping = array(); private $systemDefaults = array(); @@ -218,6 +219,20 @@ class Plugin $this->filterRules[$prio][] = $rule; } + /** + * register a Nat rule + * @param int $prio priority + * @param array $conf configuration + */ + public function registerNatRule($prio, $conf) + { + $rule = new NatRule($this->interfaceMapping, $conf); + if (empty($this->natRules[$prio])) { + $this->natRules[$prio] = array(); + } + $this->natRules[$prio][] = $rule; + } + /** * filter rules to text * @return string @@ -234,6 +249,21 @@ class Plugin return $output; } + /** + * filter rules to text + * @return string + */ + public function outputNatRules() + { + $output = ""; + ksort($this->natRules); + foreach ($this->natRules as $prio => $ruleset) { + foreach ($ruleset as $rule) { + $output .= (string)$rule; + } + } + return $output; + } /** * register a pf table * @param string $name table name