-
Notifications
You must be signed in to change notification settings - Fork 69
Description
Hello everyone. Thank you very much for this great project. My plan is to use a script to create/activate/deactivate port forwarding on a timer. The reason for this is that port 80 is required for LE extension on one NAS and then also on another, and I don't always want to have port 80 open permanently. I am using a Fritz Box 7590 and two Synology NAS devices. Ideally, I would also like to be able to control the call from an external web server, but if I understand correctly, the call must be made internally.
What works is:
Connect to the Fritz Box
List the services
List the port forwarding.
However, setting and deleting one does not work.
I am using FRITZ!OS: 8.03 and Python 3.13.
I haven't found any relevant documentation either, so I don't know whether a parameter is missing or in the wrong format or something similar.
log:
2025-08-08 13:43:58,385 - INFO - ✅ Verbindung zur Fritzbox erfolgreich hergestellt. 2025-08-08 13:43:58,386 - INFO - Verfügbare Dienste: ['any1', 'WANCommonIFC1', 'WANDSLLinkC1', 'WANIPConn1', 'WANIPv6Firewall1', 'DeviceInfo1', 'DeviceConfig1', 'Layer3Forwarding1', 'LANConfigSecurity1', 'ManagementServer1', 'Time1', 'UserInterface1', 'X_AVM-DE_Storage1', 'X_AVM-DE_WebDAVClient1', 'X_AVM-DE_UPnP1', 'X_AVM-DE_Speedtest1', 'X_AVM-DE_RemoteAccess1', 'X_AVM-DE_MyFritz1', 'X_VoIP1', 'X_AVM-DE_OnTel1', 'X_AVM-DE_Dect1', 'X_AVM-DE_TAM1', 'X_AVM-DE_AppSetup1', 'X_AVM-DE_Homeauto1', 'X_AVM-DE_Homeplug1', 'X_AVM-DE_Filelinks1', 'X_AVM-DE_Auth1', 'X_AVM-DE_HostFilter1', 'X_AVM-DE_USPController1', 'WLANConfiguration1', 'WLANConfiguration2', 'WLANConfiguration3', 'Hosts1', 'LANEthernetInterfaceConfig1', 'LANHostConfigManagement1', 'WANCommonInterfaceConfig1', 'WANDSLInterfaceConfig1', 'X_AVM-DE_WANMobileConnection1', 'WANDSLLinkConfig1', 'WANEthernetLinkConfig1', 'WANPPPConnection1', 'WANIPConnection1'] 2025-08-08 13:43:58,459 - INFO - Mapping 0: {'NewRemoteHost': '0.0.0.0', 'NewExternalPort': 8080, 'NewProtocol': 'TCP', 'NewInternalPort': 8080, 'NewInternalClient': '192.168.115.33', 'NewEnabled': True, 'NewPortMappingDescription': 'HTTP-Server', 'NewLeaseDuration': 0} 2025-08-08 13:43:58,522 - INFO - Mapping 1: {'NewRemoteHost': '0.0.0.0', 'NewExternalPort': '8083-8088', 'NewProtocol': 'TCP', 'NewInternalPort': 8083, 'NewInternalClient': '192.168.115.72', 'NewEnabled': True, 'NewPortMappingDescription': 'HTTP-Server', 'NewLeaseDuration': 0} 2025-08-08 13:43:58,592 - INFO - Mapping 2: {'NewRemoteHost': '0.0.0.0', 'NewExternalPort': 5002, 'NewProtocol': 'TCP', 'NewInternalPort': 5001, 'NewInternalClient': '192.168.115.112', 'NewEnabled': True, 'NewPortMappingDescription': 'HTTPS-Server', 'NewLeaseDuration': 0} 2025-08-08 13:43:58,661 - INFO - Mapping 3: {'NewRemoteHost': '0.0.0.0', 'NewExternalPort': 5001, 'NewProtocol': 'TCP', 'NewInternalPort': 5001, 'NewInternalClient': '192.168.115.155', 'NewEnabled': True, 'NewPortMappingDescription': 'HTTPS-Server', 'NewLeaseDuration': 0} 2025-08-08 13:43:58,734 - INFO - Mapping 4: {'NewRemoteHost': '0.0.0.0', 'NewExternalPort': 445, 'NewProtocol': 'TCP', 'NewInternalPort': 445, 'NewInternalClient': '192.168.115.155', 'NewEnabled': False, 'NewPortMappingDescription': 'smb', 'NewLeaseDuration': 0} 2025-08-08 13:43:58,807 - INFO - Mapping 5: {'NewRemoteHost': '0.0.0.0', 'NewExternalPort': 80, 'NewProtocol': 'TCP', 'NewInternalPort': 5000, 'NewInternalClient': '192.168.115.155', 'NewEnabled': True, 'NewPortMappingDescription': 'NAS_192_168_115_155', 'NewLeaseDuration': 0} 2025-08-08 13:43:58,856 - INFO - Keine weiteren Mappings. 2025-08-08 13:43:59,451 - ERROR - ❌ Fehler beim Setzen der Portweiterleitung: UPnPError: errorCode: 600 errorDescription: Argument Value Invalid
script:
`import logging
import os
import sys
from fritzconnection import FritzConnection
from dotenv import load_dotenv
load_dotenv('config.env')
NewRemoteHost = ''
FRITZ_HOST = os.getenv('FRITZ_HOST')
FRITZ_USER = os.getenv('FRITZ_USER')
FRITZ_PASSWORD = os.getenv('FRITZ_PASSWORD')
NAS_IP = sys.argv[2] if len(sys.argv) > 2 else '192.168.115.155'
NAS_NAME = f"LE_NAS_{NAS_IP.replace('.', '_')}"
ENABLE = sys.argv[1].lower() == 'on' # "on" oder "off"
external_port = 80
internal_port = 5001
Logging konfigurieren
logging.basicConfig(
filename='fritz_portmapping.log',
filemode='a',
format='%(asctime)s - %(levelname)s - %(message)s',
level=logging.INFO
)
Verbindungsaufbau
try:
fc = FritzConnection(address=FRITZ_HOST, user=FRITZ_USER, password=FRITZ_PASSWORD)
logging.info("✅ Verbindung zur Fritzbox erfolgreich hergestellt.")
print("✅ Verbindung zur Fritzbox erfolgreich hergestellt.")
except Exception as e:
logging.error(f"❌ Verbindung zur Fritzbox fehlgeschlagen: {e}")
print(f"❌ Verbindung zur Fritzbox fehlgeschlagen: {e}")
sys.exit(1)
Dienste ausgeben und loggen
logging.info(f"Verfügbare Dienste: {list(fc.services.keys())}")
print(f"Verfügbare Dienste: {list(fc.services.keys())}")
service_name = 'WANIPConnection1'
service = fc.services.get(service_name)
if not service:
logging.error(f"❌ Service {service_name} nicht gefunden.")
print(f"❌ Service {service_name} nicht gefunden.")
sys.exit(1)
Aktuelle Port-Mappings ausgeben und loggen
index = 0
while True:
try:
mapping = fc.call_action(service_name, 'GetGenericPortMappingEntry', NewPortMappingIndex=index)
logging.info(f"Mapping {index}: {mapping}")
print(f"Mapping {index}: {mapping}")
index += 1
except Exception:
logging.info("Keine weiteren Mappings.")
print("Keine weiteren Mappings.")
break
Alte Mappings für NAS entfernen
num_rules = int(fc.call_action(service_name, 'GetPortMappingNumberOfEntries')['NewPortMappingNumberOfEntries'])
for i in range(num_rules):
try:
data = fc.call_action(service_name, 'GetGenericPortMappingEntry', NewPortMappingIndex=i)
if data['NewPortMappingDescription'] == NAS_NAME:
remote_host = data.get('NewRemoteHost', '')
ext_port = int(data.get('NewExternalPort'))
protocol = data.get('NewProtocol')
logging.info(f"🛠️ Lösche Mapping mit: Host={remote_host}, Port={ext_port}, Protocol={protocol}")
print(f"🛠️ Lösche Mapping mit: Host={remote_host}, Port={ext_port}, Protocol={protocol}")
logging.info(f"🗑️ Versuche Portweiterleitung zu löschen: "
f"{data['NewExternalPort']} {data['NewProtocol']} "
f"{data['NewRemoteHost']} ({data['NewPortMappingDescription']})")
remote_host = data['NewRemoteHost']
if remote_host == '0.0.0.0':
remote_host = '' # Fritzbox erwartet leeren String ?
fc.call_action(service_name, 'DeletePortMapping',
NewRemoteHost=remote_host,
NewExternalPort=int(data['NewExternalPort']),
NewProtocol=data['NewProtocol']
)
logging.info(f"🗑️ Alte Portweiterleitung {NAS_NAME} auf Port {data['NewExternalPort']} gelöscht.")
print(f"🗑️ Alte Portweiterleitung {NAS_NAME} auf Port {data['NewExternalPort']} gelöscht.")
except Exception as e:
logging.error(f"❌ Fehler beim Löschen der Portweiterleitung {NAS_NAME}: {e}")
print(f"❌ Fehler beim Löschen der Portweiterleitung {NAS_NAME}: {e}")
def port_mapping_exists(fc, external_port, protocol='TCP'):
index = 0
while True:
try:
mapping = fc.call_action('WANIPConnection1', 'GetGenericPortMappingEntry', NewPortMappingIndex=index)
if (int(mapping['NewExternalPort']) == external_port and
mapping['NewProtocol'] == protocol):
return True
index += 1
except Exception:
return False
if ENABLE:
if not port_mapping_exists(fc, external_port):
try:
print(f"Setze Portweiterleitung: Extern {external_port} -> Intern {NAS_IP}:{internal_port}")
fc.call_action(service_name, 'AddPortMapping',
NewRemoteHost='',
NewExternalPort=int(external_port),
NewProtocol='TCP',
NewInternalPort=int(internal_port),
NewInternalClient=NAS_IP,
NewEnabled=1,
NewPortMappingDescription=NAS_NAME,
NewLeaseDuration=0)
logging.info(f"✅ Port {external_port} wurde auf {NAS_IP}:{internal_port} weitergeleitet.")
print(f"✅ Port {external_port} wurde auf {NAS_IP}:{internal_port} weitergeleitet.")
except Exception as e:
logging.error(f"❌ Fehler beim Setzen der Portweiterleitung: {e}")
print(f"❌ Fehler beim Setzen der Portweiterleitung: {e}")
else:
logging.info("ℹ️ Mapping existiert bereits.")
print("Mapping existiert bereits.")
else:
# Portweiterleitung entfernen
try:
fc.call_action(service_name, 'DeletePortMapping',
NewRemoteHost='',
NewExternalPort=int(external_port),
NewProtocol='TCP')
logging.info(f"🛑 Portweiterleitung zu {NAS_IP} wurde entfernt.")
print(f"🛑 Portweiterleitung zu {NAS_IP} wurde entfernt.")
except Exception as e:
logging.error(f"❌ Fehler beim Entfernen der Portweiterleitung: {e}")
print(f"❌ Fehler beim Entfernen der Portweiterleitung: {e}")`