diff --git a/container/dnsmasq.conf b/container/dnsmasq.conf index 751a5ce..6536dec 100644 --- a/container/dnsmasq.conf +++ b/container/dnsmasq.conf @@ -2,7 +2,7 @@ # container related configuration keep-in-foreground -log-facility=/dev/stdout +log-facility=- cache-size=4000 # DNS diff --git a/imageroot/actions/configure-module/10validate b/imageroot/actions/configure-module/10validate index 2855957..c84822d 100755 --- a/imageroot/actions/configure-module/10validate +++ b/imageroot/actions/configure-module/10validate @@ -65,7 +65,7 @@ if request["dhcp-server"]["enabled"]: sys.exit(2) if request["dns-server"]["enabled"]: - is_dns_bound = network.are_ports_53_bound() + is_dns_bound = network.are_ports_53_bound() or bool(network.get_local_samba_dcs()) # read config.json and determine if dns is used for this instance config = json.load(open("config.json")) is_dns_enabled = config["dns-server"]["enabled"] diff --git a/imageroot/actions/configure-module/20configure b/imageroot/actions/configure-module/20configure index 6af0614..6918545 100755 --- a/imageroot/actions/configure-module/20configure +++ b/imageroot/actions/configure-module/20configure @@ -7,29 +7,11 @@ import json import sys +import agent request = json.load(sys.stdin) # write dnsmasq configuration to easy json file, will be used by UI open("config.json", "w").write(json.dumps(request)) -# convert json to configuration file -with open("dnsmasq.d/00config.conf", "w") as file: - file.write("# This file is automatically generated by NethServer, manual changes will be lost.\n") - # write interface only if dhcp-server or dns-server are enabled - if request["dhcp-server"]["enabled"] or request["dns-server"]["enabled"]: - file.write("interface=" + request["interface"] + "\n") - - # write dhcp-server configuration - if request["dhcp-server"]["enabled"]: - file.write("dhcp-range=set:default," + request["dhcp-server"]["start"] + "," + request["dhcp-server"]["end"] + "," + str(request["dhcp-server"]["lease"]) + "h\n") - - # write dns-server configuration - if request["dns-server"]["enabled"]: - file.write("server=" + request["dns-server"]["primary-server"] + "\n") - if request["dns-server"]["secondary-server"] != "": - file.write("server=" + request["dns-server"]["secondary-server"] + "\n") - - else: - # shut down dns server if not enabled - file.write("port=0\n") +agent.run_helper('expand-config').check_returncode() diff --git a/imageroot/actions/create-module/20default_config b/imageroot/actions/create-module/20default_config index ce559b4..969c6ce 100755 --- a/imageroot/actions/create-module/20default_config +++ b/imageroot/actions/create-module/20default_config @@ -6,22 +6,9 @@ # import json +import agent -config = { - "interface": "", - "dhcp-server": { - "enabled": False, - "start": "", - "end": "", - "lease": 12 - }, - "dns-server": { - "enabled": False, - "primary-server": "", - "secondary-server": "" - }, -} - -open("config.json", "w").write(json.dumps(config)) +# Force the initialization of config.json for get-configuration: +agent.run_helper('expand-config').check_returncode() open("dns-records.json", "w").write(json.dumps({"records": []})) diff --git a/imageroot/actions/get-configuration/10get b/imageroot/actions/get-configuration/10get index 1d14817..cf321c9 100755 --- a/imageroot/actions/get-configuration/10get +++ b/imageroot/actions/get-configuration/10get @@ -7,7 +7,7 @@ import json import sys - +import agent import network config = json.load(open("config.json")) @@ -18,10 +18,16 @@ if config["interface"] != "" and config["dhcp-server"]["start"] == "" and config config["dhcp-server"]["start"] = str(interface["start"]) config["dhcp-server"]["end"] = str(interface["end"]) -# we test if tcp/53 or udp/53 is bound to the interface -config["is_dns_bound"] = network.are_ports_53_bound() -# check if dnsmasq is enabled in the configuration, needed to determine in the UI if the DNS server was enabled and used by dnsmasq. -# the dnsmasq service is always running, we cannot state if it is enabled/active or not. -config['is_dns_enabled'] = config["dns-server"]["enabled"] +# we test if tcp/53 or udp/53 is bound to the interface, or local Samba DCs are present +local_samba_dcs = network.get_local_samba_dcs() +if len(local_samba_dcs) > 0: + config["is_dns_bound"] = True + config["is_dns_enabled"] = False + config["dns-server"]["enabled"] = False +else: + config["is_dns_bound"] = network.are_ports_53_bound() + # check if dnsmasq is enabled in the configuration, needed to determine in the UI if the DNS server was enabled and used by dnsmasq. + # the dnsmasq service is always running, we cannot state if it is enabled/active or not. + config['is_dns_enabled'] = config["dns-server"]["enabled"] json.dump(config, sys.stdout) diff --git a/imageroot/bin/expand-config b/imageroot/bin/expand-config new file mode 100755 index 0000000..d7230a5 --- /dev/null +++ b/imageroot/bin/expand-config @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 + +# +# Copyright (C) 2024 Nethesis S.r.l. +# SPDX-License-Identifier: GPL-3.0-or-later +# + +import json +import sys +import agent +import network + +# Read configuration from a JSON file or initialize it. +try: + config = json.load(open("config.json")) +except FileNotFoundError: + print(agent.SD_NOTICE, "Generating a new config.json file...", file=sys.stderr) + config = { + "interface": "", + "dhcp-server": { + "enabled": False, + "start": "", + "end": "", + "lease": 12 + }, + "dns-server": { + "enabled": False, + "primary-server": "", + "secondary-server": "" + }, + } + json.dump(config, fp=open("config.json", "w")) + + +# Lookup local Samba DCs. They want to bind DNS port 53 like us. +local_samba_dcs = network.get_local_samba_dcs() + +# convert json to configuration file +with open("dnsmasq.d/00config.conf", "w") as file: + file.write("# This file is automatically generated by NethServer, manual changes will be lost.\n") + # write interface only if dhcp-server or dns-server are enabled + if config["dhcp-server"]["enabled"] or config["dns-server"]["enabled"]: + file.write("interface=" + config["interface"] + "\n") + + # write dhcp-server configuration + if config["dhcp-server"]["enabled"]: + file.write("dhcp-range=set:default," + config["dhcp-server"]["start"] + "," + config["dhcp-server"]["end"] + "," + str(config["dhcp-server"]["lease"]) + "h\n") + + # write dns-server configuration, if no local Samba DC is present + if len(local_samba_dcs) > 0: + print("Local Active Directory DC found, DNS feature is blocked.", local_samba_dcs, file=sys.stderr) + file.write("port=0\n") + elif config["dns-server"]["enabled"]: + file.write("server=" + config["dns-server"]["primary-server"] + "\n") + if config["dns-server"]["secondary-server"] != "": + file.write("server=" + config["dns-server"]["secondary-server"] + "\n") + else: + # shut down dns server if not enabled + file.write("port=0\n") diff --git a/imageroot/dnsmasq.service b/imageroot/dnsmasq.service index a75e8aa..08f19a1 100644 --- a/imageroot/dnsmasq.service +++ b/imageroot/dnsmasq.service @@ -8,19 +8,19 @@ RequiresMountsFor=%t/containers [Service] Environment=PODMAN_SYSTEMD_UNIT=%n EnvironmentFile=/var/lib/nethserver/%N/state/environment -Restart=on-failure +Restart=always TimeoutStopSec=70 ExecStartPre=/bin/rm \ -f %t/%n.ctr-id ExecStartPre=runagent -m %N reload_hosts +ExecStartPre=runagent -m %N expand-config ExecStart=/usr/bin/podman run \ --cidfile=%t/%n.ctr-id \ --cgroups=no-conmon \ --rm \ --sdnotify=conmon \ - -d \ - --replace \ - --name=dnsmasq \ + --detach \ + --replace --name=%N \ --network=host \ --cap-add=NET_ADMIN,NET_RAW \ --volume=/var/lib/nethserver/%N/state/dnsmasq.d:/etc/dnsmasq.d:Z \ diff --git a/imageroot/events/module-added/10restart_dnsmasq b/imageroot/events/module-added/10restart_dnsmasq new file mode 100755 index 0000000..4ffc2fb --- /dev/null +++ b/imageroot/events/module-added/10restart_dnsmasq @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 + +# +# Copyright (C) 2024 Nethesis S.r.l. +# SPDX-License-Identifier: GPL-3.0-or-later +# + +import json +import os +import sys +import agent + +data = json.load(sys.stdin) + +# skip if the event comes from another node +if os.environ['NODE_ID'] != str(data['node']): + sys.exit(0) + +agent.run_helper('systemctl', 'try-restart', os.getenv('MODULE_ID')) diff --git a/imageroot/events/module-removed/10restart_dnsmasq b/imageroot/events/module-removed/10restart_dnsmasq new file mode 100755 index 0000000..4ffc2fb --- /dev/null +++ b/imageroot/events/module-removed/10restart_dnsmasq @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 + +# +# Copyright (C) 2024 Nethesis S.r.l. +# SPDX-License-Identifier: GPL-3.0-or-later +# + +import json +import os +import sys +import agent + +data = json.load(sys.stdin) + +# skip if the event comes from another node +if os.environ['NODE_ID'] != str(data['node']): + sys.exit(0) + +agent.run_helper('systemctl', 'try-restart', os.getenv('MODULE_ID')) diff --git a/imageroot/pypkg/network.py b/imageroot/pypkg/network.py index d2c10d2..84be975 100644 --- a/imageroot/pypkg/network.py +++ b/imageroot/pypkg/network.py @@ -5,6 +5,9 @@ # SPDX-License-Identifier: GPL-3.0-or-later # +import sys +import os +import agent import ipaddress import json import subprocess @@ -78,3 +81,18 @@ def are_ports_53_bound(ip='127.0.0.1'): Check if both TCP and UDP ports 53 are bound on a specific IP address. """ return __is_port_bound(53, 'tcp', ip) or __is_port_bound(53, 'udp', ip) + +def get_local_samba_dcs(): + """ + Lookup Samba modules installed on the local node. Returns an array of + Samba module IDs that were installed on the local node. Typically the + array has 1 element at most. + """ + rdb = agent.redis_connect(use_replica=True) + local_samba_dcs = [] + for module_id, node_id in rdb.hgetall("cluster/module_node").items(): + if node_id != os.environ["NODE_ID"]: + continue + if module_id.startswith('samba'): + local_samba_dcs.append(module_id) + return local_samba_dcs