diff --git a/net/rtsphelper/Makefile b/net/rtsphelper/Makefile
new file mode 100644
index 0000000000..f21a5a23d6
--- /dev/null
+++ b/net/rtsphelper/Makefile
@@ -0,0 +1,7 @@
+PLUGIN_NAME= rtsphelper
+PLUGIN_VERSION= 1.2
+#PLUGIN_DEPENDS=
+PLUGIN_COMMENT= RTSP Port Forwarder Helper
+PLUGIN_MAINTAINER= quentin.canel@o2r.fr
+
+.include "../../Mk/plugins.mk"
diff --git a/net/rtsphelper/pkg-descr b/net/rtsphelper/pkg-descr
new file mode 100644
index 0000000000..ee0c1857e2
--- /dev/null
+++ b/net/rtsphelper/pkg-descr
@@ -0,0 +1 @@
+A simple helper script that opens ports to allow mis-configured RTSP servers to work behind NAT environment
\ No newline at end of file
diff --git a/net/rtsphelper/src/etc/inc/plugins.inc.d/rtsphelper.inc b/net/rtsphelper/src/etc/inc/plugins.inc.d/rtsphelper.inc
new file mode 100644
index 0000000000..c6224149bf
--- /dev/null
+++ b/net/rtsphelper/src/etc/inc/plugins.inc.d/rtsphelper.inc
@@ -0,0 +1,112 @@
+general->enabled == '1';
+}
+
+function rtsphelper_firewall($fw)
+{
+ if (!rtsphelper_enabled()) {
+ return;
+ }
+
+ $fw->registerAnchor('rtsphelper', 'rdr');
+ $fw->registerAnchor('rtsphelper', 'fw');
+}
+
+function rtsphelper_services()
+{
+ $services = array();
+
+ if (!rtsphelper_enabled()) {
+ return $services;
+ }
+
+ $pconfig = array();
+ $pconfig['name'] = 'rtsphelper';
+ $pconfig['description'] = gettext('RTSP Helper');
+ $pconfig['php']['restart'] = array('rtsphelper_stop', 'rtsphelper_start');
+ $pconfig['php']['start'] = array('rtsphelper_start');
+ $pconfig['php']['stop'] = array('rtsphelper_stop');
+ $pconfig['pidfile'] = '/var/run/rtsphelper.pid';
+ $services[] = $pconfig;
+
+ return $services;
+}
+
+function rtsphelper_start()
+{
+ if (!rtsphelper_enabled()) {
+ return;
+ }
+
+ if (isvalidpid('/var/run/rtsphelper.pid')) {
+ return;
+ }
+
+ mwexec_bg('/usr/local/bin/python3 /usr/local/opnsense/scripts/net/rtsphelper/rtsphelper.py');
+}
+
+function rtsphelper_stop()
+{
+ killbypid('/var/run/rtsphelper.pid', 'TERM', true);
+ mwexec('/sbin/pfctl -artsphelper -Fr 2>&1 >/dev/null');
+ mwexec('/sbin/pfctl -artsphelper -Fn 2>&1 >/dev/null');
+}
+
+function rtsphelper_configure()
+{
+ return array('bootup' => array('rtsphelper_configure_do'));
+}
+
+function rtsphelper_configure_do($verbose = false)
+{
+ rtsphelper_stop();
+
+ if (!rtsphelper_enabled()) {
+ return;
+ }
+
+ if ($verbose) {
+ echo 'Starting RTSP Helper...';
+ flush();
+ }
+
+ $model = new General();
+ $ext_iface = (string)$model->general->ext_iface;
+ $ext_ifname = get_real_interface($ext_iface);
+
+ // Log a warning if interface couldn't be resolved
+ // This can happen if the selected interface was deleted from the system
+ if ($ext_ifname == $ext_iface) {
+ syslog(LOG_WARNING, "rtsphelper: Interface '{$ext_iface}' could not be resolved to a device name");
+ }
+
+ $config_text = "ext_ifname={$ext_ifname}\n";
+
+ /* RTSP Helper access restrictions */
+ foreach ($model->permissions->permission->iterateItems() as $perm) {
+ $network = (string)$perm->network;
+ $port = (string)$perm->port;
+ $config_text .= "allow={$network} {$port}\n";
+ }
+
+ foreach ($model->hosts->host->iterateItems() as $host) {
+ $ip = (string)$host->ip;
+ $port = (string)$host->port;
+ $config_text .= "forward={$ip}:{$port}\n";
+ }
+
+ /* write out the configuration */
+ file_put_contents('/var/etc/rtsphelper.conf', $config_text);
+ rtsphelper_start();
+
+ if ($verbose) {
+ echo "done.\n";
+ }
+}
+
diff --git a/net/rtsphelper/src/opnsense/mvc/app/controllers/OPNsense/RTSPHelper/Api/ServiceController.php b/net/rtsphelper/src/opnsense/mvc/app/controllers/OPNsense/RTSPHelper/Api/ServiceController.php
new file mode 100644
index 0000000000..9f8e9ea694
--- /dev/null
+++ b/net/rtsphelper/src/opnsense/mvc/app/controllers/OPNsense/RTSPHelper/Api/ServiceController.php
@@ -0,0 +1,56 @@
+request->isPost()) {
+ $backend = new Backend();
+ $response = $backend->configdRun('rtsphelper start');
+ return array("response" => $response);
+ }
+ return array("response" => array());
+ }
+
+ public function stopAction()
+ {
+ if ($this->request->isPost()) {
+ $backend = new Backend();
+ $response = $backend->configdRun('rtsphelper stop');
+ return array("response" => $response);
+ }
+ return array("response" => array());
+ }
+
+ public function restartAction()
+ {
+ if ($this->request->isPost()) {
+ $backend = new Backend();
+ $response = $backend->configdRun('rtsphelper restart');
+ return array("response" => $response);
+ }
+ return array("response" => array());
+ }
+
+ public function statusAction()
+ {
+ $backend = new Backend();
+ $response = $backend->configdRun('rtsphelper status');
+ return array("status" => trim($response));
+ }
+
+ public function reconfigureAction()
+ {
+ if ($this->request->isPost()) {
+ $backend = new Backend();
+ $response = $backend->configdRun('rtsphelper configure');
+ return array("response" => $response);
+ }
+ return array("response" => array());
+ }
+}
diff --git a/net/rtsphelper/src/opnsense/mvc/app/controllers/OPNsense/RTSPHelper/Api/SettingsController.php b/net/rtsphelper/src/opnsense/mvc/app/controllers/OPNsense/RTSPHelper/Api/SettingsController.php
new file mode 100644
index 0000000000..6049cbe698
--- /dev/null
+++ b/net/rtsphelper/src/opnsense/mvc/app/controllers/OPNsense/RTSPHelper/Api/SettingsController.php
@@ -0,0 +1,11 @@
+configdRun('rtsphelper connections');
+ $rows = array();
+
+ foreach (explode("\n", $response) as $line) {
+ if (preg_match("/on (.*) inet proto (.*) from (.*) to (.*) port = (.*) -> (.*)/", $line, $matches)) {
+ $rows[] = array(
+ "interface" => $matches[1],
+ "proto" => $matches[2],
+ "source" => $matches[3],
+ "destination" => $matches[4],
+ "port" => $matches[5],
+ "redirect_to" => $matches[6]
+ );
+ }
+ }
+
+ return array("rows" => $rows);
+ }
+}
diff --git a/net/rtsphelper/src/opnsense/mvc/app/controllers/OPNsense/RTSPHelper/SettingsController.php b/net/rtsphelper/src/opnsense/mvc/app/controllers/OPNsense/RTSPHelper/SettingsController.php
new file mode 100644
index 0000000000..fa6d86b902
--- /dev/null
+++ b/net/rtsphelper/src/opnsense/mvc/app/controllers/OPNsense/RTSPHelper/SettingsController.php
@@ -0,0 +1,16 @@
+view->pick('OPNsense/RTSPHelper/index');
+ $this->view->formGeneral = $this->getForm("general");
+ $this->view->formDialogHost = $this->getForm("dialog_host");
+ $this->view->formDialogPermission = $this->getForm("dialog_permission");
+ }
+}
diff --git a/net/rtsphelper/src/opnsense/mvc/app/controllers/OPNsense/RTSPHelper/forms/dialog_host.xml b/net/rtsphelper/src/opnsense/mvc/app/controllers/OPNsense/RTSPHelper/forms/dialog_host.xml
new file mode 100644
index 0000000000..ced57ee5ab
--- /dev/null
+++ b/net/rtsphelper/src/opnsense/mvc/app/controllers/OPNsense/RTSPHelper/forms/dialog_host.xml
@@ -0,0 +1,14 @@
+
diff --git a/net/rtsphelper/src/opnsense/mvc/app/controllers/OPNsense/RTSPHelper/forms/dialog_permission.xml b/net/rtsphelper/src/opnsense/mvc/app/controllers/OPNsense/RTSPHelper/forms/dialog_permission.xml
new file mode 100644
index 0000000000..f014745529
--- /dev/null
+++ b/net/rtsphelper/src/opnsense/mvc/app/controllers/OPNsense/RTSPHelper/forms/dialog_permission.xml
@@ -0,0 +1,14 @@
+
diff --git a/net/rtsphelper/src/opnsense/mvc/app/controllers/OPNsense/RTSPHelper/forms/general.xml b/net/rtsphelper/src/opnsense/mvc/app/controllers/OPNsense/RTSPHelper/forms/general.xml
new file mode 100644
index 0000000000..dccafaa3a3
--- /dev/null
+++ b/net/rtsphelper/src/opnsense/mvc/app/controllers/OPNsense/RTSPHelper/forms/general.xml
@@ -0,0 +1,14 @@
+
diff --git a/net/rtsphelper/src/opnsense/mvc/app/models/OPNsense/RTSPHelper/ACL/ACL.xml b/net/rtsphelper/src/opnsense/mvc/app/models/OPNsense/RTSPHelper/ACL/ACL.xml
new file mode 100644
index 0000000000..d94c300888
--- /dev/null
+++ b/net/rtsphelper/src/opnsense/mvc/app/models/OPNsense/RTSPHelper/ACL/ACL.xml
@@ -0,0 +1,9 @@
+
+
+ Service: RTSP Helper
+
+ ui/rtsphelper/*
+ api/rtsphelper/*
+
+
+
diff --git a/net/rtsphelper/src/opnsense/mvc/app/models/OPNsense/RTSPHelper/General.php b/net/rtsphelper/src/opnsense/mvc/app/models/OPNsense/RTSPHelper/General.php
new file mode 100644
index 0000000000..c46cb4282e
--- /dev/null
+++ b/net/rtsphelper/src/opnsense/mvc/app/models/OPNsense/RTSPHelper/General.php
@@ -0,0 +1,9 @@
+
+ //OPNsense/RTSPHelper
+ 1.0.0
+
+
+
+ 0
+ Y
+
+
+ Y
+ N
+
+
+
+
+
+ Y
+ Please specify a valid IP address.
+
+
+ Y
+ Please specify a valid port number.
+
+
+
+
+
+
+ Y
+ Please specify a valid network (CIDR) or IP address.
+
+
+ Y
+ /^(\d{1,5})(?:-(\d{1,5}))?$/
+ Please specify a valid port or port range (e.g. 1024-65535).
+
+
+
+
+
diff --git a/net/rtsphelper/src/opnsense/mvc/app/models/OPNsense/RTSPHelper/Menu/Menu.xml b/net/rtsphelper/src/opnsense/mvc/app/models/OPNsense/RTSPHelper/Menu/Menu.xml
new file mode 100644
index 0000000000..447a185cb9
--- /dev/null
+++ b/net/rtsphelper/src/opnsense/mvc/app/models/OPNsense/RTSPHelper/Menu/Menu.xml
@@ -0,0 +1,7 @@
+
diff --git a/net/rtsphelper/src/opnsense/mvc/app/views/OPNsense/RTSPHelper/index.volt b/net/rtsphelper/src/opnsense/mvc/app/views/OPNsense/RTSPHelper/index.volt
new file mode 100644
index 0000000000..ad83cbaafc
--- /dev/null
+++ b/net/rtsphelper/src/opnsense/mvc/app/views/OPNsense/RTSPHelper/index.volt
@@ -0,0 +1,219 @@
+
+
+
+ {{ partial("layout_partials/base_form",['fields':formGeneral,'id':'frm_general_settings'])}}
+
+
+
{{ lang._('Hosts to enable') }}
+
+
+
+ | {{ lang._('IP Address') }} |
+ {{ lang._('Port') }} |
+ {{ lang._('Commands')
+ }} |
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+
+
+
+
{{ lang._('User specified permissions') }}
+
+
+
+ | {{ lang._('Network') }} |
+ {{ lang._('Port / Range') }} |
+ {{ lang._('Commands')
+ }} |
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | {{ lang._('Interface') }} |
+ {{ lang._('Protocol') }} |
+ {{ lang._('Source') }} |
+ {{ lang._('Destination') }} |
+ {{ lang._('Port') }} |
+ {{ lang._('Redirect To') }} |
+
+
+
+
+
+
+
+
+
+
+
+
+
+{{ partial("layout_partials/base_dialog",['fields':formDialogHost,'id':'DialogHost','label':lang._('Edit Host')])}}
+{{ partial("layout_partials/base_dialog",['fields':formDialogPermission,'id':'DialogPermission','label':lang._('Edit
+Permission')])}}
+
+
\ No newline at end of file
diff --git a/net/rtsphelper/src/opnsense/scripts/net/rtsphelper/configure.php b/net/rtsphelper/src/opnsense/scripts/net/rtsphelper/configure.php
new file mode 100644
index 0000000000..ae1c8aacb2
--- /dev/null
+++ b/net/rtsphelper/src/opnsense/scripts/net/rtsphelper/configure.php
@@ -0,0 +1,8 @@
+#!/usr/local/bin/php
+ None:
+ self.forward: socket.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+
+ def start(self, host: str, port: int) -> socket.socket | bool:
+ try:
+ self.forward.connect((host, port))
+ return self.forward
+ except Exception as e:
+ print(e)
+ return False
+
+class ProxyServer:
+ input_list: list[Any] = []
+ channel: dict[Any, Any] = {}
+ clients: list[list[Any]] = []
+ forward_to: list[str | int] = []
+ perms: list[PermType] = []
+
+ def __init__(self, remoteHost: str, remotePort: int, portManager: PortManager, perms: list[PermType]) -> None:
+ self.pm: PortManager = portManager
+ self.forward_to = [remoteHost, remotePort]
+ self.perms = perms
+ self.server: socket.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ self.server.bind(('127.0.0.1', 0))
+ self.server.listen(200)
+ self.pm.addLocalBinding(remoteHost, remotePort, self.server.getsockname()[1])
+ self.input_list.append(self.server)
+
+ def main_loop(self) -> None:
+ ss = select.select
+ inputready, outputready, exceptready = ss(self.input_list, [], [])
+ for self.s in inputready:
+ if self.s == self.server:
+ self.on_accept()
+ break
+
+ try:
+ self.data = self.s.recv(buffer_size)
+ if len(self.data) == 0:
+ self.on_close()
+ break
+ else:
+ self.on_recv()
+ except socket.error as e:
+ self.on_close()
+
+ def on_accept(self) -> None:
+ clientsock, clientaddr = self.server.accept()
+
+ if allowedIP(clientaddr[0], self.perms):
+ forward = Forward().start(self.forward_to[0], self.forward_to[1]) # type: ignore
+ if forward:
+ self.clients.append([clientaddr,clientsock,forward])
+ self.pm.addClient(clientaddr)
+ self.input_list.append(clientsock)
+ self.input_list.append(forward)
+ self.channel[clientsock] = forward
+ self.channel[forward] = clientsock
+ else:
+ print("Can't establish connection with remote server.")
+ print("Closing connection with client side", clientaddr)
+ clientsock.close()
+ else:
+ print("Forbidden client IP")
+ clientsock.close()
+
+ def on_close(self) -> None:
+ #remove objects from input_list
+ self.input_list.remove(self.s)
+ self.input_list.remove(self.channel[self.s])
+ out = self.channel[self.s]
+ # close the connection with client
+ self.channel[out].close() # equivalent to do self.s.close()
+ # close the connection with remote server
+ self.channel[self.s].close()
+ # delete both objects from channel dict
+ del self.channel[out]
+ del self.channel[self.s]
+
+ for c in self.clients:
+ if c[1] == self.s:
+ break
+ self.clients.remove(c)
+ self.pm.removeClient(c[0])
+
+ def on_recv(self) -> None:
+ data = self.data
+ # here we can parse and/or modify the data before send forward
+ self.channel[self.s].send(data)
+ for c in self.clients:
+ if c[1] == self.s:
+ self.parseData(data, c)
+ break
+
+ def parseData(self, data: bytes, client: list[Any]) -> None:
+ for line in data.splitlines():
+ lineSplit = line.decode().split(':', 1)
+ if lineSplit[0] == "Transport":
+ for transportOpt in lineSplit[1].split(';'):
+ if transportOpt.split('=')[0] == "client_port":
+ askedPorts = transportOpt.split('=')[1].split('-')
+ allowedPorts = []
+ for port in askedPorts:
+ if allowedPortForward(client[0][0], port, self.perms):
+ allowedPorts.append(port)
+ self.pm.updatePorts(client[0], allowedPorts)
+
+class PortManager:
+ forwardedPorts: dict[Any, list[str]] = {}
+ localBindings: list[list[str | int]] = []
+ allowedNets: list[str] = []
+
+ def __init__(self, perms: list[list[str]]) -> None:
+ for perm in perms:
+ network = perm[0]
+ self.allowedNets.append(network)
+ self.removeAll()
+ self.applyRules()
+
+ def addClient(self, client: Any) -> None:
+ self.forwardedPorts[client] = []
+
+ def updatePorts(self, client: Any, ports: list[str]) -> None:
+ print("Forwarding ports for client " + client[0] + ". New list of ports is: {0}".format(ports))
+ self.forwardedPorts[client] = ports
+ self.applyRules()
+
+
+ def removeClient(self, client: Any) -> None:
+ print("Remove client: " + client[0])
+ self.forwardedPorts.pop(client)
+ self.applyRules()
+
+ def removeAll(self) -> None:
+ f = open('/tmp/rtsphelper.rules', 'w')
+ f.close()
+ subprocess.call(['pfctl', '-a', 'rtsphelper', '-F', 'nat'], stdout=FNULL, stderr=subprocess.STDOUT)
+ subprocess.call(['pfctl', '-a', 'rtsphelper', '-F', 'rules'], stdout=FNULL, stderr=subprocess.STDOUT)
+ subprocess.call(['pfctl', '-a', 'rtsphelper', '-F', 'state'], stdout=FNULL, stderr=subprocess.STDOUT)
+
+ def addLocalBinding(self, ip: str, port: int, local_port: int) -> None:
+ self.localBindings.append([ip, port, local_port])
+ self.applyRules()
+
+ def applyRules(self) -> None:
+ config_rule_1 = 'rdr inet proto tcp from any to {} port {} -> {} port {}\n'
+ config_rule_2 = 'block in quick on {} proto tcp from any to {} port {}\n'
+ config_rule_3 = 'pass in quick proto tcp from {} to {} port {}\n'
+
+ pass_rule = 'pass in quick on {} inet proto udp from any to {} port {} keep state label "{}"\n'
+ rdr_rule = 'rdr on {} inet proto udp from any to any port {} -> {}\n'
+
+ f = open('/tmp/rtsphelper.rules', 'w')
+
+ for localBinding in self.localBindings:
+ f.write(config_rule_1.format(localBinding[0], localBinding[1], '127.0.0.1', localBinding[2]))
+
+ for client,ports in self.forwardedPorts.items():
+ ip = client[0]
+ for port in ports:
+ f.write(rdr_rule.format(config['ext_if'], port, ip))
+
+ f.write('\n')
+ for localBinding in self.localBindings:
+ f.write(config_rule_2.format(config['ext_if'], '127.0.0.1', localBinding[2]))
+ for network in self.allowedNets:
+ f.write(config_rule_3.format(network, '127.0.0.1', localBinding[2]))
+
+ for client,ports in self.forwardedPorts.items():
+ ip = client[0]
+ for port in ports:
+ f.write(pass_rule.format(config['ext_if'], ip, port, 'RTSP'))
+
+ f.close()
+ subprocess.call(['pfctl', '-a', 'rtsphelper', '-f', '/tmp/rtsphelper.rules'], stdout=FNULL)
+
+
+def writePidFile() -> None:
+ pid = str(os.getpid())
+ f = open('/var/run/rtsphelper.pid', 'w')
+ f.write(pid)
+ f.close()
+
+def ip_to_u32(ip: str) -> int:
+ return int(''.join('%02x' % int(d) for d in ip.split('.')), 16)
+
+def allowedIP(ipstr: str, perms: list[PermType]) -> bool:
+ ip = ip_to_u32(ipstr)
+ for perm in perms:
+ mask, net = perm[0]
+ if ip & mask == net:
+ return True
+ return False
+
+def allowedPortForward(ipstr: str, port: str, perms: list[PermType]) -> bool:
+ if not allowedIP(ipstr, perms):
+ return False
+ else:
+ ip = ip_to_u32(ipstr)
+ for perm in perms:
+ mask, net = perm[0]
+ ports = perm[1]
+ if ip & mask == net:
+ if int(port) >= int(ports[0]) and int(port) <= int(ports[1]):
+ return True
+ return False
+
+def buildPerms(perms: list[list[str]]) -> list[PermType]:
+ masks: list[PermType] = []
+ for perm in perms:
+ cidr = perm[0]
+ portRange = perm[1]
+ if '/' in cidr:
+ netstr, bits = cidr.split('/')
+ mask: int = (0xffffffff << (32 - int(bits))) & 0xffffffff
+ net: int = ip_to_u32(netstr) & mask
+ else:
+ mask = 0xffffffff
+ net = ip_to_u32(cidr)
+ masks.append(((mask, net), (min(portRange.split('-')[0],portRange.split('-')[1]),max(portRange.split('-')[0],portRange.split('-')[1]))))
+ return masks
+
+if __name__ == '__main__':
+ writePidFile()
+
+ config['forward_to'] = []
+ config['perms'] = []
+
+ with open(config_file, 'r') as cf:
+ line = cf.readline()
+ while line:
+ key,value = line.strip().split('=')
+ if key == 'ext_ifname':
+ config['ext_if'] = value
+ elif key == 'forward':
+ config['forward_to'].append([value.split(':')[0],int(value.split(':')[1])])
+ elif key == 'allow':
+ config['perms'].append(value.split(' '))
+
+ line = cf.readline()
+
+ perms = buildPerms(config['perms'])
+ servers: list[ProxyServer] = []
+
+ pm = PortManager(config['perms'])
+
+ for forward in config['forward_to']:
+ servers.append(ProxyServer(forward[0], forward[1], pm, perms))
+
+ def handle_exit_signal(sig: int, frame: Any) -> None:
+ handle_exit()
+
+ def handle_exit() -> None:
+ print("Exiting...")
+ pm.removeAll()
+ sys.exit(0)
+
+ signal.signal(signal.SIGTERM, handle_exit_signal)
+ try:
+ while 1:
+ time.sleep(delay)
+ for server in servers:
+ server.main_loop()
+ except KeyboardInterrupt:
+ print("Ctrl C - Stopping server")
+ handle_exit()
+sys.exit(1)
\ No newline at end of file
diff --git a/net/rtsphelper/src/opnsense/service/conf/actions.d/actions_rtsphelper.conf b/net/rtsphelper/src/opnsense/service/conf/actions.d/actions_rtsphelper.conf
new file mode 100644
index 0000000000..6ab718290f
--- /dev/null
+++ b/net/rtsphelper/src/opnsense/service/conf/actions.d/actions_rtsphelper.conf
@@ -0,0 +1,29 @@
+[start]
+command:/usr/local/bin/python3 /usr/local/opnsense/scripts/net/rtsphelper/rtsphelper.py
+type:script
+message:starting rtsphelper
+
+[stop]
+command:kill -TERM $(cat /var/run/rtsphelper.pid) 2> /dev/null; /sbin/pfctl -artsphelper -Fr 2> /dev/null; /sbin/pfctl -artsphelper -Fn 2> /dev/null; exit 0
+type:script
+message:stopping rtsphelper
+
+[restart]
+command:kill -TERM $(cat /var/run/rtsphelper.pid) 2> /dev/null; /sbin/pfctl -artsphelper -Fr 2> /dev/null; /sbin/pfctl -artsphelper -Fn 2> /dev/null; /usr/local/bin/python3 /usr/local/opnsense/scripts/net/rtsphelper/rtsphelper.py
+type:script
+message:restarting rtsphelper
+
+[status]
+command:if [ -f /var/run/rtsphelper.pid ] && pgrep -F /var/run/rtsphelper.pid > /dev/null; then echo "running"; else echo "stopped"; fi
+type:script_output
+message:get rtsphelper status
+
+[connections]
+command:/sbin/pfctl -artsphelper -sn 2> /dev/null
+type:script_output
+message:list rtsphelper connections
+
+[configure]
+command:/usr/local/bin/php /usr/local/opnsense/scripts/net/rtsphelper/configure.php
+type:script
+message:configuring rtsphelper
diff --git a/net/rtsphelper/src/www/services_rtsphelper.php b/net/rtsphelper/src/www/services_rtsphelper.php
new file mode 100644
index 0000000000..2c5b12c355
--- /dev/null
+++ b/net/rtsphelper/src/www/services_rtsphelper.php
@@ -0,0 +1,279 @@
+ 32) {
+ return false;
+ }
+ } elseif (count($ip_array) != 1) {
+ return false;
+ }
+
+ /* validate ip */
+ if (!is_ipaddr($ip_array[0])) {
+ return false;
+ }
+
+ return true;
+}
+
+function rtsphelper_validate_forward($forward)
+{
+ $fw_array = array();
+ $fw_array = explode(':', $forward);
+
+ if (!is_ipaddr($fw_array[0])) {
+ return false;
+ }
+
+ $sub = $fw_array[1];
+ if ($sub < 0 || $sub > 65535 || !is_numeric($sub)) {
+ return false;
+ }
+
+ return true;
+}
+
+function rtsphelper_validate_port($port)
+{
+ foreach (explode('-', $port) as $sub) {
+ if ($sub < 0 || $sub > 65535 || !is_numeric($sub)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+if ($_SERVER['REQUEST_METHOD'] === 'GET') {
+ $pconfig = array();
+
+ $copy_fields = array('enable', 'ext_iface');
+
+ foreach (rtsphelper_permuser_list() as $permuser) {
+ $copy_fields[] = $permuser;
+ }
+
+ foreach (rtsphelper_forward_list() as $forward) {
+ $copy_fields[] = $forward;
+ }
+
+ foreach ($copy_fields as $fieldname) {
+ if (isset($config['installedpackages']['rtsphelper']['config'][0][$fieldname])) {
+ $pconfig[$fieldname] = $config['installedpackages']['rtsphelper']['config'][0][$fieldname];
+ }
+ }
+} elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ $input_errors = array();
+ $pconfig = $_POST;
+
+ /* user permissions validation */
+ foreach (rtsphelper_permuser_list() as $i => $permuser) {
+ if (!empty($pconfig[$permuser])) {
+ $perm = explode(' ', $pconfig[$permuser]);
+ /* should explode to 2 args */
+ if (count($perm) != 2) {
+ $input_errors[] = sprintf(gettext("You must follow the specified format in the 'User specified permissions %s' field"), $i);
+ } else {
+ /* verify port or port range */
+ if (!rtsphelper_validate_port($perm[1]) ) {
+ $input_errors[] = sprintf(gettext("You must specify a port or port range between 0 and 65535 in the 'User specified permissions %s' field"), $i);
+ }
+ /* verify ip address */
+ if (!rtsphelper_validate_ip($perm[0])) {
+ $input_errors[] = sprintf(gettext("You must specify a valid ip address in the 'User specified permissions %s' field"), $i);
+ }
+ }
+ }
+ }
+
+ foreach (rtsphelper_forward_list() as $i => $forward) {
+ if (!empty($pconfig[$forward])) {
+ if (!rtsphelper_validate_forward($pconfig[$forward])) {
+ $input_errors[] = sprintf(gettext("You must specify a valid ip and port in the 'Hosts to enable %s' field"), $i);
+ }
+ }
+ }
+
+ if (count($input_errors) == 0) {
+ // save form data
+ $rtsp = array();
+ // boolean types
+ foreach (array('enable') as $fieldname) {
+ $rtsp[$fieldname] = !empty($pconfig[$fieldname]);
+ }
+ // text field types
+ foreach (array('ext_iface') as $fieldname) {
+ $rtsp[$fieldname] = $pconfig[$fieldname];
+ }
+ foreach (rtsphelper_permuser_list() as $fieldname) {
+ $rtsp[$fieldname] = $pconfig[$fieldname];
+ }
+ foreach (rtsphelper_forward_list() as $forward) {
+ $rtsp[$forward] = $pconfig[$forward];
+ }
+ // sync to config
+ $config['installedpackages']['rtsphelper']['config'] = $rtsp;
+
+ write_config('Modified RTSP Helper settings');
+ rtsphelper_configure_do();
+ filter_configure();
+ header(url_safe('Location: /services_rtsphelper.php'));
+ exit;
+ }
+}
+
+
+$service_hook = 'rtsphelper';
+legacy_html_escape_form_data($pconfig);
+include("head.inc");
+?>
+
+
+
+
+
+ 0) print_input_errors($input_errors); ?>
+
+
+
+
+
diff --git a/net/rtsphelper/src/www/status_rtsphelper.php b/net/rtsphelper/src/www/status_rtsphelper.php
new file mode 100644
index 0000000000..76e5c5c39c
--- /dev/null
+++ b/net/rtsphelper/src/www/status_rtsphelper.php
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+ = gettext('RTSP Helper is currently disabled.') ?>
+
+
+
+
+
+
+ | =gettext("Internal IP");?> |
+ =gettext("Int. Port");?> |
+
+
+
+ (.*)/", $rdr_entry, $matches)) {
+ continue;
+ }
+ $rdr_ip = $matches[6];
+ $rdr_iport = $matches[5];
+ ?>
+
+ | =$rdr_ip;?> |
+ =$rdr_iport;?> |
+
+
+
+
+
+ |
+
+ |
+
+
+
+
+
+
+
+
+
+
+