boxtools on raspberry pi

- rename to_system to apu_system / cm3_system /cm4_system
- fixes in router
- firewall may be switched off
- firewall is a parameter of router, also used when no routing is configured.
This commit is contained in:
l_samenv
2024-05-13 17:55:54 +02:00
parent 793606db2b
commit 1318666db0
7 changed files with 148 additions and 90 deletions

View File

@ -7,7 +7,6 @@ import threading
import re
import queue
from utils import MainIf
from subprocess import Popen, PIPE
from configparser import ConfigParser
# display tty device

View File

@ -18,9 +18,8 @@ import types
import socket
from subprocess import Popen, PIPE
from ipaddress import IPv4Interface
from configparser import ConfigParser
from os.path import join, getmtime, exists, basename
from utils import BoxInfo
from utils import BoxInfo, check_service, unix_cmd, change_firewall
if os.geteuid() != 0:
exit("You need to have root privileges to run this script.\nPlease try again, this time using 'sudo'. Exiting.")
@ -66,7 +65,7 @@ max-lease-time 7200;
authoritative;
"""
ROUTER_TEMPLATE = f"""[Unit]
ROUTER_SERVICE = f"""[Unit]
Description = Routing to locally connected hardware
After = network.target
@ -77,7 +76,7 @@ ExecStart = /usr/bin/python3 {TOOLS}/router.py
WantedBy = multi-user.target
"""
FRAPPY_TEMPLATE = """[Unit]
FRAPPY_SERVICE = """[Unit]
Description = Running frappy server
After = network.target
@ -89,7 +88,7 @@ ExecStart = /usr/bin/python3 /home/l_samenv/frappy/bin/frappy-server %s
WantedBy = multi-user.target
"""
DISPLAY_TEMPLATE = f"""[Unit]
DISPLAY_SERVICE = f"""[Unit]
Description = status display
After = network.target
@ -111,10 +110,15 @@ pip_requirements = {
box = BoxInfo()
dhcp_server_cfg = []
TO_SYSTEM = f'{TOOLS}/to_system'
TO_SYSTEM = f'{TOOLS}/{box.typ}_system'
os.chdir(TO_SYSTEM)
def do_cmd(command):
unix_cmd(command, doit)
show.dirty = True
def frappy(cfg=None, port=None, requirements='', **kwds):
if not cfg:
return None
@ -124,10 +128,26 @@ def frappy(cfg=None, port=None, requirements='', **kwds):
cfg = '-p %s %s' % (port, cfg)
with open('/home/l_samenv/frappy/requirements.txt') as f:
req['frappy main'] = f.read()
return FRAPPY_TEMPLATE % cfg
return FRAPPY_SERVICE % cfg
def router(**opts):
def router(firewall=False, **opts):
if firewall:
active, enabled = check_service('nftables')
ports = {22}
if opts:
for port in firewall.split(','):
ports.add(int(port))
for key in opts:
try:
ports.add(int(key))
except ValueError:
pass
ports.update((1110, 1111, 1112))
if change_firewall(True, ports, doit):
show.dirty = True
elif change_firewall(False, set(), doit):
show.dirty = True
if not opts:
return None
try:
@ -136,7 +156,7 @@ def router(**opts):
pip_requirements['root']['tools'] = f.read()
except FileNotFoundError:
pass
return ROUTER_TEMPLATE
return ROUTER_SERVICE
def display_update(cfg):
@ -154,7 +174,7 @@ def display_update(cfg):
def display(**opts):
if not opts:
return None
return DISPLAY_TEMPLATE
return DISPLAY_SERVICE
def pip():
@ -176,23 +196,13 @@ def pip():
os.remove(tmpname)
else:
print(pipcmd)
unix_cmd(pipcmd, stdout=None)
unix_cmd(pipcmd, doit, stdout=None)
show.dirty = True
SERVICES = dict(router=router, frappy=frappy, display=display)
def unix_cmd(command, always=False, stdout=PIPE):
if doit or always:
if not always:
print('$ %s' % command)
result = Popen(command.split(), stdout=stdout).communicate()[0]
return (result or b'').decode()
else:
print('> %s' % command)
def write_when_new(filename, content, ignore_reduction=False):
if content is None:
lines = []
@ -490,21 +500,20 @@ def handle_config():
replace_in_file('/etc/default/isc-dhcp-server',
r'INTERFACESv4="(.*)"', ' '.join([n for n in box.macaddr if n != box.main_if]))
elif doit:
unix_cmd('systemctl stop isc-dhcp-server')
unix_cmd('systemctl disable isc-dhcp-server')
check_service('isc-dhcp-server', False)
content = []
dirty = False
for section, section_dict in config.items():
if COMMENT not in section_dict:
dirty = True
content.append('[%s]' % section)
content.append(COMMENT)
# if COMMENT not in section_dict:
# dirty = True
# content.append('[%s]' % section)
# content.append(COMMENT)
if section == 'DISPLAY':
if display_update(section_dict):
to_start['display'] = 'restart'
for key, value in section_dict.items():
content.append('%s=%s' % (key, value))
content.append('')
# for key, value in section_dict.items():
# content.append('%s=%s' % (key, value))
# content.append('')
if dirty:
print(cfgfile)
print('\n'.join(content))
@ -520,33 +529,17 @@ def handle_config():
section = service.upper()
section_dict = config.get(section, {})
servicecfg = service_func(**section_dict)
result = unix_cmd('systemctl show -p WantedBy -p ActiveState %s' % service, True)
active = False
enabled = False
for line in result.split('\n'):
if line.startswith('WantedBy=m'):
enabled = True
elif line.strip() == 'ActiveState=active':
active = True
active, enabled = check_service(service)
if servicecfg is None:
if active:
unix_cmd('systemctl stop %s' % service)
if active or enabled:
check_service(service, False, doit)
show.dirty = True
if enabled:
unix_cmd('systemctl disable %s' % service)
show.dirty = True
if service == 'router' and active or enabled:
if doit:
shutil.copy(join(TOOLS, 'nftables.conf'), '/etc/nftables.conf')
else:
print('cp nftables.conf /etc/nftables.conf')
unix_cmd('systemctl restart nftables')
else:
if not enabled:
to_start[service] = 'enable'
elif not active:
to_start[service] = 'restart'
if write_when_new('/etc/systemd/system/%s.service' % service, servicecfg):
if write_when_new(f'/etc/systemd/system/{service}.service', servicecfg):
show.dirty = True
reload_systemd = True
if servicecfg and to_start.get('service') is None:
@ -554,25 +547,25 @@ def handle_config():
pip()
if reload_systemd:
unix_cmd('systemctl daemon-reload')
do_cmd('systemctl daemon-reload')
for service, action in to_start.items():
show.dirty = True
if action == 'if_restart':
unix_cmd('ifdown %s' % service)
do_cmd(f'ifdown {service}')
for service, action in to_start.items():
if action == 'if_restart':
unix_cmd('ifup %s' % service)
do_cmd(f'ifup {service}')
else:
if action == 'restart':
unix_cmd('systemctl restart %s' % service)
unix_cmd('systemctl enable %s' % service)
do_cmd(f'systemctl restart {service}')
do_cmd(f'systemctl enable {service}')
if box.change_if_names:
print('interface name system has to be changed from enp*s0 to eth*')
if replace_in_file('/etc/default/grub',
r'GRUB_CMDLINE_LINUX_DEFAULT="quiet(.*)"',
' net.ifnames=0"'):
unix_cmd('update-grub')
do_cmd('update-grub')
result = [f'config file:\n {cfgfile}']
for section, section_dict in config.items():

View File

@ -9,9 +9,7 @@ from glob import glob
from select import select
from serial import serial_for_url
from subprocess import Popen, PIPE, check_output, call, DEVNULL
from utils import BoxInfo
FIREWALL_CONF = '/etc/nftables.conf'
from utils import BoxInfo, change_firewall
class Log:
DEBUG = 0
@ -169,7 +167,7 @@ class Router(IoHandler):
return
except Exception as e:
msg = f'error in request: {e!r}'
service.failures[service.port] = msg
self.service.failures[service.port] = msg
log.error(msg)
self.close()
@ -401,36 +399,12 @@ class Service:
self.failures.pop(self.port, None)
self.handlers[handler.fno] = handler
@classmethod
def change_firewall(cls):
pattern = re.compile('(tcp dport ({.*}) ct state new accept)', re.MULTILINE | re.DOTALL)
with open(FIREWALL_CONF) as f:
content = f.read()
try:
((prevline, prevports),) = pattern.findall(content)
except (TypeError, ValueError):
print(f'{FIREWALL_CONF} does not contain expected pattern for open ports - firewall off?')
return
# parse previous port set
prevportset = {int(p) for p in prevports[1:-1].split(',')}
# keep port numbers below 1024
ports = {p for p in prevportset if p < 1024} | set(cls.firewall_ports)
line = prevline.replace(prevports, '{ %s }' % ', '.join((str(p) for p in sorted(ports))))
if prevportset != ports:
if os.geteuid() == 0:
with open('f{FIREWALL_CONF}.tmp', 'w') as f:
f.write(content.replace(prevline, line))
os.rename('f{FIREWALL_CONF}.tmp', FIREWALL_CONF)
unix_cmd('systemctl restart nftables')
unix_cmd('systemctl enable nftables')
else:
print('need sudo rights to modify firewall')
@classmethod
def run(cls, routes):
firewall = routes.pop('firewall', None)
firewall_on = firewall is not None
if firewall_on:
cls.firewall_ports = set(int(r) for r in firewall.split(',') if r.strip())
Service(1110, None, InfoHandler, 5, handler_args=(log.DEBUG,))
Service(1111, None, InfoHandler, 5, handler_args=(log.INFO,))
Service(1112, None, InfoHandler, 5, handler_args=(log.WARN,))
@ -445,7 +419,7 @@ class Service:
else:
remoteport = port
Service(port, (host, remoteport), TcpHandler)
cls.change_firewall()
change_firewall(firewall_on, cls.firewall_ports)
while True:
try:
# log.debug('select %r', list(cls.readers))

View File

@ -1,9 +1,14 @@
import os
import socket
import threading
import re
from glob import glob
from configparser import ConfigParser
from netifaces import interfaces, ifaddresses, gateways, AF_INET, AF_LINK
from subprocess import Popen, PIPE
FIREWALL_CONF = '/etc/nftables.conf'
if os.geteuid():
@ -130,3 +135,90 @@ class MainIf:
else:
self.prev_ip = None
return self.carrier, self.ip, self.hostnameresult[0], self.gateway
def unix_cmd(command, execute=None, stdout=PIPE):
if execute != False: # None or True
if execute:
print('$ %s' % command)
result = Popen(command.split(), stdout=stdout).communicate()[0]
return (result or b'').decode()
else:
print('> %s' % command)
def check_service(service, set_on=None, execute=None):
"""check or set state of systemd service
set_on is None or not given: query only
bool(set_on) is True: start and enable if not yet done
set_on == False/0: stop and disable if not yet done
sim: print out command instead of executing
"""
result = unix_cmd(f'systemctl show -p WantedBy -p ActiveState {service}')
enabled = False
active = False
for line in result.split('\n'):
if line.startswith('WantedBy=') and line.strip() != 'WantedBy=':
enabled = True
elif line.strip() == 'ActiveState=active':
active = True
if set_on:
if not active:
unix_cmd(f'systemctl start {service}', execute)
if not enabled:
unix_cmd(f'systemctl enable {service}', execute)
elif set_on is not None:
if active:
unix_cmd(f'systemctl stop {service}', execute)
if enabled:
unix_cmd(f'systemctl disable {service}', execute)
return active, enabled
def change_firewall(set_on, ports, execute=None):
ports.add(22) # always add ssh
active, enabled = check_service('nftables')
if not set_on:
if os.geteuid() == 0:
check_service('nftables', False, execute)
else:
print('need sudo rights to modify firewall')
return active or enabled
pattern = re.compile('(tcp dport ({.*}) ct state new accept)', re.MULTILINE | re.DOTALL)
for filename in FIREWALL_CONF, os.path.join(BoxInfo.TOOLS, 'nftables.conf'):
with open(filename) as f:
content = f.read()
try:
((prevline, prevports),) = pattern.findall(content)
break
except (TypeError, ValueError):
pass
else:
print(f'{FIREWALL_CONF} does not contain expected pattern for open ports - firewall off?')
return False
# parse previous port set
prevportset = {int(p) for p in prevports[1:-1].split(',')}
line = prevline.replace(prevports, '{ %s }' % ', '.join((str(p) for p in sorted(ports))))
if prevportset == ports:
if active and enabled:
return False
check_service('nftables', True, execute)
return True
if os.geteuid() == 0:
if execute is not None:
print(f'change firewall ports to {ports}')
if execute != 0:
with open('f{FIREWALL_CONF}.tmp', 'w') as f:
f.write(content.replace(prevline, line))
os.rename('f{FIREWALL_CONF}.tmp', FIREWALL_CONF)
unix_cmd('systemctl restart nftables', execute)
unix_cmd('systemctl enable nftables', execute)
elif ports - prevportset:
print('need sudo rights to modify firewall')
return True