mirror of
https://github.com/opnsense/plugins.git
synced 2026-02-03 20:40:37 -05:00
rtsphelper: fix pfctl state flush command and add type annotations
- Fix pfctl command in removeAll(): use '-F state' instead of invalid '-F RTSP' to properly flush firewall states within rtsphelper anchor - Add Python type annotations throughout rtsphelper.py for better code quality - Clean up plugins.inc.d/rtsphelper.inc
This commit is contained in:
parent
0173779337
commit
88d9411b02
2 changed files with 49 additions and 64 deletions
|
|
@ -80,28 +80,10 @@ function rtsphelper_configure_do($verbose = false)
|
|||
$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) {
|
||||
// get_real_interface returns the input if it fails or is already real?
|
||||
// Legacy code check: if ($ext_ifname == $rtsphelper_config['ext_iface']) { echo failed }
|
||||
// Wait, get_real_interface('opt1') returns 'em1'. If 'em1' passed, returns 'em1'.
|
||||
// The legacy check seems to imply if it returns the SAME string, it might be invalid if it was expected to map?
|
||||
// Or maybe it checks if it's NOT a valid interface?
|
||||
// Let's assume get_real_interface returns the interface name.
|
||||
// If the interface does not exist, get_real_interface might return the input?
|
||||
// Let's keep the legacy check logic but adapted.
|
||||
// Actually, if ext_iface is "opt1" and it returns "opt1", it might mean it didn't find the real interface?
|
||||
// But if I select "em0", it returns "em0".
|
||||
// Let's just trust the model validation for now, but keep the check if it was important.
|
||||
// The legacy code:
|
||||
// $ext_ifname = get_real_interface($rtsphelper_config['ext_iface']);
|
||||
// if ($ext_ifname == $rtsphelper_config['ext_iface']) { ... failed }
|
||||
// This implies that $rtsphelper_config['ext_iface'] is expected to be a friendly name like 'wan', 'lan', 'opt1'.
|
||||
// If it returns the same, it means it couldn't resolve it?
|
||||
// But if I select a physical interface in the UI?
|
||||
// In MVC InterfaceField, it stores the handle (e.g. 'wan', 'opt1') or physical if not assigned?
|
||||
// Usually 'wan'.
|
||||
// So if get_real_interface('wan') returns 'wan', that's bad? No, it should return 'em0'.
|
||||
// If it returns 'wan', it means it failed to resolve.
|
||||
syslog(LOG_WARNING, "rtsphelper: Interface '{$ext_iface}' could not be resolved to a device name");
|
||||
}
|
||||
|
||||
$config_text = "ext_ifname={$ext_ifname}\n";
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/python
|
||||
from __future__ import annotations
|
||||
import socket
|
||||
import select
|
||||
import time
|
||||
|
|
@ -6,20 +7,24 @@ import sys
|
|||
import os
|
||||
import signal
|
||||
import subprocess
|
||||
from typing import Any
|
||||
|
||||
buffer_size = 4096
|
||||
delay = 0.0001
|
||||
|
||||
config_file = '/var/etc/rtsphelper.conf'
|
||||
config = {}
|
||||
config: dict[str, Any] = {}
|
||||
|
||||
FNULL = open(os.devnull, 'w')
|
||||
|
||||
class Forward:
|
||||
def __init__(self):
|
||||
self.forward = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
# Type alias for permissions structure: ((mask, net), (port_min, port_max))
|
||||
PermType = tuple[tuple[int, int], tuple[str, str]]
|
||||
|
||||
def start(self, host, port):
|
||||
class Forward:
|
||||
def __init__(self) -> 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
|
||||
|
|
@ -28,26 +33,24 @@ class Forward:
|
|||
return False
|
||||
|
||||
class ProxyServer:
|
||||
input_list = []
|
||||
channel = {}
|
||||
clients = []
|
||||
input_list: list[Any] = []
|
||||
channel: dict[Any, Any] = {}
|
||||
clients: list[list[Any]] = []
|
||||
forward_to: list[str | int] = []
|
||||
perms: list[PermType] = []
|
||||
|
||||
forward_to = []
|
||||
|
||||
perms = []
|
||||
|
||||
def __init__(self, remoteHost, remotePort, portManager, perms):
|
||||
self.pm = portManager
|
||||
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.AF_INET, socket.SOCK_STREAM)
|
||||
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):
|
||||
def main_loop(self) -> None:
|
||||
ss = select.select
|
||||
inputready, outputready, exceptready = ss(self.input_list, [], [])
|
||||
for self.s in inputready:
|
||||
|
|
@ -65,11 +68,11 @@ class ProxyServer:
|
|||
except socket.error as e:
|
||||
self.on_close()
|
||||
|
||||
def on_accept(self):
|
||||
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])
|
||||
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)
|
||||
|
|
@ -85,7 +88,7 @@ class ProxyServer:
|
|||
print("Forbidden client IP")
|
||||
clientsock.close()
|
||||
|
||||
def on_close(self):
|
||||
def on_close(self) -> None:
|
||||
#remove objects from input_list
|
||||
self.input_list.remove(self.s)
|
||||
self.input_list.remove(self.channel[self.s])
|
||||
|
|
@ -104,7 +107,7 @@ class ProxyServer:
|
|||
self.clients.remove(c)
|
||||
self.pm.removeClient(c[0])
|
||||
|
||||
def on_recv(self):
|
||||
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)
|
||||
|
|
@ -113,7 +116,7 @@ class ProxyServer:
|
|||
self.parseData(data, c)
|
||||
break
|
||||
|
||||
def parseData(self, data, client):
|
||||
def parseData(self, data: bytes, client: list[Any]) -> None:
|
||||
for line in data.splitlines():
|
||||
lineSplit = line.decode().split(':', 1)
|
||||
if lineSplit[0] == "Transport":
|
||||
|
|
@ -127,43 +130,43 @@ class ProxyServer:
|
|||
self.pm.updatePorts(client[0], allowedPorts)
|
||||
|
||||
class PortManager:
|
||||
forwardedPorts = {}
|
||||
localBindings = []
|
||||
allowedNets = []
|
||||
forwardedPorts: dict[Any, list[str]] = {}
|
||||
localBindings: list[list[str | int]] = []
|
||||
allowedNets: list[str] = []
|
||||
|
||||
def __init__(self, perms):
|
||||
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):
|
||||
def addClient(self, client: Any) -> None:
|
||||
self.forwardedPorts[client] = []
|
||||
|
||||
def updatePorts(self, client, ports):
|
||||
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):
|
||||
def removeClient(self, client: Any) -> None:
|
||||
print("Remove client: " + client[0])
|
||||
self.forwardedPorts.pop(client)
|
||||
self.applyRules()
|
||||
|
||||
def removeAll(self):
|
||||
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', '-k', 'label', '-k', 'RTSP'], stdout=FNULL, stderr=subprocess.STDOUT)
|
||||
subprocess.call(['pfctl', '-a', 'rtsphelper', '-F', 'state'], stdout=FNULL, stderr=subprocess.STDOUT)
|
||||
|
||||
def addLocalBinding(self, ip, port, local_port):
|
||||
def addLocalBinding(self, ip: str, port: int, local_port: int) -> None:
|
||||
self.localBindings.append([ip, port, local_port])
|
||||
self.applyRules()
|
||||
|
||||
def applyRules(self):
|
||||
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'
|
||||
|
|
@ -196,16 +199,16 @@ class PortManager:
|
|||
subprocess.call(['pfctl', '-a', 'rtsphelper', '-f', '/tmp/rtsphelper.rules'], stdout=FNULL)
|
||||
|
||||
|
||||
def writePidFile():
|
||||
def writePidFile() -> None:
|
||||
pid = str(os.getpid())
|
||||
f = open('/var/run/rtsphelper.pid', 'w')
|
||||
f.write(pid)
|
||||
f.close()
|
||||
|
||||
def ip_to_u32(ip):
|
||||
def ip_to_u32(ip: str) -> int:
|
||||
return int(''.join('%02x' % int(d) for d in ip.split('.')), 16)
|
||||
|
||||
def allowedIP(ipstr, perms):
|
||||
def allowedIP(ipstr: str, perms: list[PermType]) -> bool:
|
||||
ip = ip_to_u32(ipstr)
|
||||
for perm in perms:
|
||||
mask, net = perm[0]
|
||||
|
|
@ -213,7 +216,7 @@ def allowedIP(ipstr, perms):
|
|||
return True
|
||||
return False
|
||||
|
||||
def allowedPortForward(ipstr, port, perms):
|
||||
def allowedPortForward(ipstr: str, port: str, perms: list[PermType]) -> bool:
|
||||
if not allowedIP(ipstr, perms):
|
||||
return False
|
||||
else:
|
||||
|
|
@ -226,15 +229,15 @@ def allowedPortForward(ipstr, port, perms):
|
|||
return True
|
||||
return False
|
||||
|
||||
def buildPerms(perms):
|
||||
masks = [ ]
|
||||
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 = (0xffffffff << (32 - int(bits))) & 0xffffffff
|
||||
net = ip_to_u32(netstr) & mask
|
||||
mask: int = (0xffffffff << (32 - int(bits))) & 0xffffffff
|
||||
net: int = ip_to_u32(netstr) & mask
|
||||
else:
|
||||
mask = 0xffffffff
|
||||
net = ip_to_u32(cidr)
|
||||
|
|
@ -261,17 +264,17 @@ if __name__ == '__main__':
|
|||
line = cf.readline()
|
||||
|
||||
perms = buildPerms(config['perms'])
|
||||
servers = []
|
||||
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, frame):
|
||||
def handle_exit_signal(sig: int, frame: Any) -> None:
|
||||
handle_exit()
|
||||
|
||||
def handle_exit():
|
||||
def handle_exit() -> None:
|
||||
print("Exiting...")
|
||||
pm.removeAll()
|
||||
sys.exit(0)
|
||||
|
|
|
|||
Loading…
Reference in a new issue