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:
@ -7,7 +7,6 @@ import threading
|
|||||||
import re
|
import re
|
||||||
import queue
|
import queue
|
||||||
from utils import MainIf
|
from utils import MainIf
|
||||||
from subprocess import Popen, PIPE
|
|
||||||
from configparser import ConfigParser
|
from configparser import ConfigParser
|
||||||
|
|
||||||
# display tty device
|
# display tty device
|
||||||
|
105
install.py
105
install.py
@ -18,9 +18,8 @@ import types
|
|||||||
import socket
|
import socket
|
||||||
from subprocess import Popen, PIPE
|
from subprocess import Popen, PIPE
|
||||||
from ipaddress import IPv4Interface
|
from ipaddress import IPv4Interface
|
||||||
from configparser import ConfigParser
|
|
||||||
from os.path import join, getmtime, exists, basename
|
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:
|
if os.geteuid() != 0:
|
||||||
exit("You need to have root privileges to run this script.\nPlease try again, this time using 'sudo'. Exiting.")
|
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;
|
authoritative;
|
||||||
"""
|
"""
|
||||||
|
|
||||||
ROUTER_TEMPLATE = f"""[Unit]
|
ROUTER_SERVICE = f"""[Unit]
|
||||||
Description = Routing to locally connected hardware
|
Description = Routing to locally connected hardware
|
||||||
After = network.target
|
After = network.target
|
||||||
|
|
||||||
@ -77,7 +76,7 @@ ExecStart = /usr/bin/python3 {TOOLS}/router.py
|
|||||||
WantedBy = multi-user.target
|
WantedBy = multi-user.target
|
||||||
"""
|
"""
|
||||||
|
|
||||||
FRAPPY_TEMPLATE = """[Unit]
|
FRAPPY_SERVICE = """[Unit]
|
||||||
Description = Running frappy server
|
Description = Running frappy server
|
||||||
After = network.target
|
After = network.target
|
||||||
|
|
||||||
@ -89,7 +88,7 @@ ExecStart = /usr/bin/python3 /home/l_samenv/frappy/bin/frappy-server %s
|
|||||||
WantedBy = multi-user.target
|
WantedBy = multi-user.target
|
||||||
"""
|
"""
|
||||||
|
|
||||||
DISPLAY_TEMPLATE = f"""[Unit]
|
DISPLAY_SERVICE = f"""[Unit]
|
||||||
Description = status display
|
Description = status display
|
||||||
After = network.target
|
After = network.target
|
||||||
|
|
||||||
@ -111,10 +110,15 @@ pip_requirements = {
|
|||||||
box = BoxInfo()
|
box = BoxInfo()
|
||||||
dhcp_server_cfg = []
|
dhcp_server_cfg = []
|
||||||
|
|
||||||
TO_SYSTEM = f'{TOOLS}/to_system'
|
TO_SYSTEM = f'{TOOLS}/{box.typ}_system'
|
||||||
os.chdir(TO_SYSTEM)
|
os.chdir(TO_SYSTEM)
|
||||||
|
|
||||||
|
|
||||||
|
def do_cmd(command):
|
||||||
|
unix_cmd(command, doit)
|
||||||
|
show.dirty = True
|
||||||
|
|
||||||
|
|
||||||
def frappy(cfg=None, port=None, requirements='', **kwds):
|
def frappy(cfg=None, port=None, requirements='', **kwds):
|
||||||
if not cfg:
|
if not cfg:
|
||||||
return None
|
return None
|
||||||
@ -124,10 +128,26 @@ def frappy(cfg=None, port=None, requirements='', **kwds):
|
|||||||
cfg = '-p %s %s' % (port, cfg)
|
cfg = '-p %s %s' % (port, cfg)
|
||||||
with open('/home/l_samenv/frappy/requirements.txt') as f:
|
with open('/home/l_samenv/frappy/requirements.txt') as f:
|
||||||
req['frappy main'] = f.read()
|
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:
|
if not opts:
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
@ -136,7 +156,7 @@ def router(**opts):
|
|||||||
pip_requirements['root']['tools'] = f.read()
|
pip_requirements['root']['tools'] = f.read()
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
return ROUTER_TEMPLATE
|
return ROUTER_SERVICE
|
||||||
|
|
||||||
|
|
||||||
def display_update(cfg):
|
def display_update(cfg):
|
||||||
@ -154,7 +174,7 @@ def display_update(cfg):
|
|||||||
def display(**opts):
|
def display(**opts):
|
||||||
if not opts:
|
if not opts:
|
||||||
return None
|
return None
|
||||||
return DISPLAY_TEMPLATE
|
return DISPLAY_SERVICE
|
||||||
|
|
||||||
|
|
||||||
def pip():
|
def pip():
|
||||||
@ -176,23 +196,13 @@ def pip():
|
|||||||
os.remove(tmpname)
|
os.remove(tmpname)
|
||||||
else:
|
else:
|
||||||
print(pipcmd)
|
print(pipcmd)
|
||||||
unix_cmd(pipcmd, stdout=None)
|
unix_cmd(pipcmd, doit, stdout=None)
|
||||||
show.dirty = True
|
show.dirty = True
|
||||||
|
|
||||||
|
|
||||||
SERVICES = dict(router=router, frappy=frappy, display=display)
|
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):
|
def write_when_new(filename, content, ignore_reduction=False):
|
||||||
if content is None:
|
if content is None:
|
||||||
lines = []
|
lines = []
|
||||||
@ -490,21 +500,20 @@ def handle_config():
|
|||||||
replace_in_file('/etc/default/isc-dhcp-server',
|
replace_in_file('/etc/default/isc-dhcp-server',
|
||||||
r'INTERFACESv4="(.*)"', ' '.join([n for n in box.macaddr if n != box.main_if]))
|
r'INTERFACESv4="(.*)"', ' '.join([n for n in box.macaddr if n != box.main_if]))
|
||||||
elif doit:
|
elif doit:
|
||||||
unix_cmd('systemctl stop isc-dhcp-server')
|
check_service('isc-dhcp-server', False)
|
||||||
unix_cmd('systemctl disable isc-dhcp-server')
|
|
||||||
content = []
|
content = []
|
||||||
dirty = False
|
dirty = False
|
||||||
for section, section_dict in config.items():
|
for section, section_dict in config.items():
|
||||||
if COMMENT not in section_dict:
|
# if COMMENT not in section_dict:
|
||||||
dirty = True
|
# dirty = True
|
||||||
content.append('[%s]' % section)
|
# content.append('[%s]' % section)
|
||||||
content.append(COMMENT)
|
# content.append(COMMENT)
|
||||||
if section == 'DISPLAY':
|
if section == 'DISPLAY':
|
||||||
if display_update(section_dict):
|
if display_update(section_dict):
|
||||||
to_start['display'] = 'restart'
|
to_start['display'] = 'restart'
|
||||||
for key, value in section_dict.items():
|
# for key, value in section_dict.items():
|
||||||
content.append('%s=%s' % (key, value))
|
# content.append('%s=%s' % (key, value))
|
||||||
content.append('')
|
# content.append('')
|
||||||
if dirty:
|
if dirty:
|
||||||
print(cfgfile)
|
print(cfgfile)
|
||||||
print('\n'.join(content))
|
print('\n'.join(content))
|
||||||
@ -520,33 +529,17 @@ def handle_config():
|
|||||||
section = service.upper()
|
section = service.upper()
|
||||||
section_dict = config.get(section, {})
|
section_dict = config.get(section, {})
|
||||||
servicecfg = service_func(**section_dict)
|
servicecfg = service_func(**section_dict)
|
||||||
result = unix_cmd('systemctl show -p WantedBy -p ActiveState %s' % service, True)
|
active, enabled = check_service(service)
|
||||||
active = False
|
|
||||||
enabled = False
|
|
||||||
for line in result.split('\n'):
|
|
||||||
if line.startswith('WantedBy=m'):
|
|
||||||
enabled = True
|
|
||||||
elif line.strip() == 'ActiveState=active':
|
|
||||||
active = True
|
|
||||||
if servicecfg is None:
|
if servicecfg is None:
|
||||||
if active:
|
if active or enabled:
|
||||||
unix_cmd('systemctl stop %s' % service)
|
check_service(service, False, doit)
|
||||||
show.dirty = True
|
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:
|
else:
|
||||||
if not enabled:
|
if not enabled:
|
||||||
to_start[service] = 'enable'
|
to_start[service] = 'enable'
|
||||||
elif not active:
|
elif not active:
|
||||||
to_start[service] = 'restart'
|
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
|
show.dirty = True
|
||||||
reload_systemd = True
|
reload_systemd = True
|
||||||
if servicecfg and to_start.get('service') is None:
|
if servicecfg and to_start.get('service') is None:
|
||||||
@ -554,25 +547,25 @@ def handle_config():
|
|||||||
|
|
||||||
pip()
|
pip()
|
||||||
if reload_systemd:
|
if reload_systemd:
|
||||||
unix_cmd('systemctl daemon-reload')
|
do_cmd('systemctl daemon-reload')
|
||||||
for service, action in to_start.items():
|
for service, action in to_start.items():
|
||||||
show.dirty = True
|
show.dirty = True
|
||||||
if action == 'if_restart':
|
if action == 'if_restart':
|
||||||
unix_cmd('ifdown %s' % service)
|
do_cmd(f'ifdown {service}')
|
||||||
for service, action in to_start.items():
|
for service, action in to_start.items():
|
||||||
if action == 'if_restart':
|
if action == 'if_restart':
|
||||||
unix_cmd('ifup %s' % service)
|
do_cmd(f'ifup {service}')
|
||||||
else:
|
else:
|
||||||
if action == 'restart':
|
if action == 'restart':
|
||||||
unix_cmd('systemctl restart %s' % service)
|
do_cmd(f'systemctl restart {service}')
|
||||||
unix_cmd('systemctl enable %s' % service)
|
do_cmd(f'systemctl enable {service}')
|
||||||
|
|
||||||
if box.change_if_names:
|
if box.change_if_names:
|
||||||
print('interface name system has to be changed from enp*s0 to eth*')
|
print('interface name system has to be changed from enp*s0 to eth*')
|
||||||
if replace_in_file('/etc/default/grub',
|
if replace_in_file('/etc/default/grub',
|
||||||
r'GRUB_CMDLINE_LINUX_DEFAULT="quiet(.*)"',
|
r'GRUB_CMDLINE_LINUX_DEFAULT="quiet(.*)"',
|
||||||
' net.ifnames=0"'):
|
' net.ifnames=0"'):
|
||||||
unix_cmd('update-grub')
|
do_cmd('update-grub')
|
||||||
|
|
||||||
result = [f'config file:\n {cfgfile}']
|
result = [f'config file:\n {cfgfile}']
|
||||||
for section, section_dict in config.items():
|
for section, section_dict in config.items():
|
||||||
|
40
router.py
40
router.py
@ -9,9 +9,7 @@ from glob import glob
|
|||||||
from select import select
|
from select import select
|
||||||
from serial import serial_for_url
|
from serial import serial_for_url
|
||||||
from subprocess import Popen, PIPE, check_output, call, DEVNULL
|
from subprocess import Popen, PIPE, check_output, call, DEVNULL
|
||||||
from utils import BoxInfo
|
from utils import BoxInfo, change_firewall
|
||||||
|
|
||||||
FIREWALL_CONF = '/etc/nftables.conf'
|
|
||||||
|
|
||||||
class Log:
|
class Log:
|
||||||
DEBUG = 0
|
DEBUG = 0
|
||||||
@ -169,7 +167,7 @@ class Router(IoHandler):
|
|||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
msg = f'error in request: {e!r}'
|
msg = f'error in request: {e!r}'
|
||||||
service.failures[service.port] = msg
|
self.service.failures[service.port] = msg
|
||||||
log.error(msg)
|
log.error(msg)
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
@ -401,36 +399,12 @@ class Service:
|
|||||||
self.failures.pop(self.port, None)
|
self.failures.pop(self.port, None)
|
||||||
self.handlers[handler.fno] = handler
|
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
|
@classmethod
|
||||||
def run(cls, routes):
|
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(1110, None, InfoHandler, 5, handler_args=(log.DEBUG,))
|
||||||
Service(1111, None, InfoHandler, 5, handler_args=(log.INFO,))
|
Service(1111, None, InfoHandler, 5, handler_args=(log.INFO,))
|
||||||
Service(1112, None, InfoHandler, 5, handler_args=(log.WARN,))
|
Service(1112, None, InfoHandler, 5, handler_args=(log.WARN,))
|
||||||
@ -445,7 +419,7 @@ class Service:
|
|||||||
else:
|
else:
|
||||||
remoteport = port
|
remoteport = port
|
||||||
Service(port, (host, remoteport), TcpHandler)
|
Service(port, (host, remoteport), TcpHandler)
|
||||||
cls.change_firewall()
|
change_firewall(firewall_on, cls.firewall_ports)
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
# log.debug('select %r', list(cls.readers))
|
# log.debug('select %r', list(cls.readers))
|
||||||
|
92
utils.py
92
utils.py
@ -1,9 +1,14 @@
|
|||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
import threading
|
import threading
|
||||||
|
import re
|
||||||
from glob import glob
|
from glob import glob
|
||||||
from configparser import ConfigParser
|
from configparser import ConfigParser
|
||||||
from netifaces import interfaces, ifaddresses, gateways, AF_INET, AF_LINK
|
from netifaces import interfaces, ifaddresses, gateways, AF_INET, AF_LINK
|
||||||
|
from subprocess import Popen, PIPE
|
||||||
|
|
||||||
|
|
||||||
|
FIREWALL_CONF = '/etc/nftables.conf'
|
||||||
|
|
||||||
|
|
||||||
if os.geteuid():
|
if os.geteuid():
|
||||||
@ -130,3 +135,90 @@ class MainIf:
|
|||||||
else:
|
else:
|
||||||
self.prev_ip = None
|
self.prev_ip = None
|
||||||
return self.carrier, self.ip, self.hostnameresult[0], self.gateway
|
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
|
||||||
|
Reference in New Issue
Block a user