inital commit
This commit is contained in:
7
README.md
Normal file
7
README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# APU server
|
||||||
|
|
||||||
|
The APU is a Linux box to be used as a control box at LIN sample environment
|
||||||
|
|
||||||
|
servercfg/<hostname>.cfg contain the configuration for all boxes
|
||||||
|
|
||||||
|
to_system contains the files to be installed in the system
|
80
install.py
Normal file
80
install.py
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
"""install.py:
|
||||||
|
|
||||||
|
copy files from to_system into system directories
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import filecmp
|
||||||
|
import shutil
|
||||||
|
from os.path import join, getmtime, exists
|
||||||
|
|
||||||
|
os.chdir('to_system')
|
||||||
|
|
||||||
|
DEL = '__to_delete__'
|
||||||
|
|
||||||
|
def walk(action):
|
||||||
|
for dirpath, _, files in os.walk('.'):
|
||||||
|
syspath = dirpath[1:] # remove leading '.'
|
||||||
|
action.dirpath = dirpath
|
||||||
|
action.syspath = syspath
|
||||||
|
if files:
|
||||||
|
match, mismatch, missing = filecmp.cmpfiles(dirpath, syspath, files)
|
||||||
|
if mismatch:
|
||||||
|
newer = [f for f in mismatch if getmtime(join(syspath, f)) > getmtime(join(dirpath, f))]
|
||||||
|
if newer:
|
||||||
|
action.newer(newer)
|
||||||
|
if len(newer) < len(mismatch):
|
||||||
|
newer = set(newer)
|
||||||
|
action.older([f for f in mismatch if f not in newer])
|
||||||
|
if missing:
|
||||||
|
if DEL in missing:
|
||||||
|
missing.remove(DEL)
|
||||||
|
with open(join(dirpath, DEL)) as fil:
|
||||||
|
to_delete = []
|
||||||
|
for f in fil:
|
||||||
|
f = f.strip()
|
||||||
|
if f and exists(join(syspath, f)):
|
||||||
|
if exists(join(dirpath, f)):
|
||||||
|
print('ERROR: %s in %s, but also in repo -> ignored' % (f, DEL))
|
||||||
|
else:
|
||||||
|
to_delete.append(f)
|
||||||
|
action.delete(to_delete)
|
||||||
|
if missing:
|
||||||
|
action.missing(missing)
|
||||||
|
|
||||||
|
class Show:
|
||||||
|
def show(self, title, files):
|
||||||
|
print('%s %s:\n %s' % (title, self.syspath, ' '.join(files)))
|
||||||
|
|
||||||
|
def newer(self, files):
|
||||||
|
self.show('get from', files)
|
||||||
|
|
||||||
|
def older(self, files):
|
||||||
|
self.show('replace in', files)
|
||||||
|
|
||||||
|
def missing(self, files):
|
||||||
|
self.show('install in', files)
|
||||||
|
|
||||||
|
def delete(self, files):
|
||||||
|
self.show('remove from', files)
|
||||||
|
|
||||||
|
class Do:
|
||||||
|
def newer(self, files):
|
||||||
|
for file in files:
|
||||||
|
shutil.copy(join(self.syspath, file), join(self.dirpath, file))
|
||||||
|
|
||||||
|
def older(self, files):
|
||||||
|
for file in files:
|
||||||
|
shutil.copy(join(self.dirpath, file), join(self.syspath, file))
|
||||||
|
|
||||||
|
def missing(self, files):
|
||||||
|
self.older(files)
|
||||||
|
|
||||||
|
def delete(self, files):
|
||||||
|
for file in files:
|
||||||
|
os.remove(join(self.syspath, file))
|
||||||
|
|
||||||
|
walk(Show())
|
||||||
|
answer = input('do above?')
|
||||||
|
if answer.lower().startswith('y'):
|
||||||
|
walk(Do())
|
191
router.py
Normal file
191
router.py
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
import socket
|
||||||
|
from select import select
|
||||||
|
from serial import serial_for_url
|
||||||
|
from subprocess import Popen, PIPE, check_output, call
|
||||||
|
from configparser import ConfigParser
|
||||||
|
|
||||||
|
FILTER = "iptables -i enp4s0 -p tcp -m tcp --dport %d -j ACCEPT"
|
||||||
|
|
||||||
|
BASIC = """
|
||||||
|
iptables -P INPUT %(accept)s
|
||||||
|
iptables -P FORWARD %(accept)s
|
||||||
|
iptables -P OUTPUT ACCEPT
|
||||||
|
iptables -A INPUT -i lo -j ACCEPT
|
||||||
|
"""
|
||||||
|
|
||||||
|
sim = False
|
||||||
|
|
||||||
|
def unix_cmd(command):
|
||||||
|
if sim:
|
||||||
|
print('> %r' % command)
|
||||||
|
else:
|
||||||
|
print('$ %r' % command)
|
||||||
|
return Popen(command.split(), stdout=PIPE).communicate()[0].decode()
|
||||||
|
|
||||||
|
|
||||||
|
class IoHandler:
|
||||||
|
client = None
|
||||||
|
handler = None
|
||||||
|
|
||||||
|
def __init__(self, client, handler):
|
||||||
|
self.handler = handler
|
||||||
|
self.client = client
|
||||||
|
|
||||||
|
def request(self):
|
||||||
|
try:
|
||||||
|
data = self.client.recv(1024)
|
||||||
|
if data:
|
||||||
|
self.write(data)
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
print('ERROR in request: %s' % e)
|
||||||
|
self.close()
|
||||||
|
self.handler.close_client(self)
|
||||||
|
|
||||||
|
def reply(self):
|
||||||
|
try:
|
||||||
|
self.client.sendall(self.read())
|
||||||
|
return
|
||||||
|
except ConnectionResetError:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
print('ERROR in reply: %s' % e)
|
||||||
|
self.close()
|
||||||
|
self.handler.close_client(self)
|
||||||
|
|
||||||
|
|
||||||
|
class TcpHandler(IoHandler):
|
||||||
|
def __init__(self, client, handler):
|
||||||
|
self.socket = socket.create_connection(handler.addr)
|
||||||
|
self.fno = self.socket.fileno()
|
||||||
|
super().__init__(client, handler)
|
||||||
|
|
||||||
|
def read(self):
|
||||||
|
data = self.socket.recv(1024)
|
||||||
|
if not data:
|
||||||
|
raise ConnectionResetError('disconnected')
|
||||||
|
return data
|
||||||
|
|
||||||
|
def write(self, data):
|
||||||
|
self.socket.sendall(data)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
try:
|
||||||
|
self.socket.close()
|
||||||
|
except Exception as e:
|
||||||
|
print('ERROR in close: %s' % e)
|
||||||
|
|
||||||
|
|
||||||
|
class SerialHandler(IoHandler):
|
||||||
|
def __init__(self, client, handler):
|
||||||
|
self.serial = serial_for_url(handler.addr, timeout=10)
|
||||||
|
self.serial.timeout = None
|
||||||
|
self.fno = self.serial.fileno()
|
||||||
|
super().__init__(client, handler)
|
||||||
|
|
||||||
|
def read(self):
|
||||||
|
return self.serial.read(self.serial.in_waiting)
|
||||||
|
|
||||||
|
def write(self, data):
|
||||||
|
self.serial.write(data)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.serial.close()
|
||||||
|
|
||||||
|
|
||||||
|
class AcceptHandler:
|
||||||
|
"""handler for routing
|
||||||
|
|
||||||
|
:param: port offered port for routing
|
||||||
|
:param: addr where to route
|
||||||
|
:param: iocls the io ahndler class, currently TcpHandler or SerialHandler
|
||||||
|
:param: maxcount the maximal number of concurrent connections. defauls to 1
|
||||||
|
as a side effect, if the destination is a web server, the traffic
|
||||||
|
are serialized (only one connection at a time), which helps for
|
||||||
|
some moxa device servers. might be a problem, if connections are
|
||||||
|
reused: in this case maxcount has to be increased ...
|
||||||
|
"""
|
||||||
|
readers = {}
|
||||||
|
|
||||||
|
def __init__(self, port, addr, iocls, maxcount=1):
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
s.bind(('0.0.0.0', port))
|
||||||
|
s.listen()
|
||||||
|
self.socket = s
|
||||||
|
self.addr = addr
|
||||||
|
self.iocls = iocls
|
||||||
|
self.readers[s.fileno()] = self.accept
|
||||||
|
self.port = port
|
||||||
|
self.available = maxcount
|
||||||
|
self.pending = 0
|
||||||
|
print('listening at port %d for %s(%r)' % (port, iocls.__name__, addr) )
|
||||||
|
|
||||||
|
def close_client(self, iohandler):
|
||||||
|
self.readers.pop(iohandler.fno, None)
|
||||||
|
try:
|
||||||
|
client = iohandler.client
|
||||||
|
self.readers.pop(client.fileno())
|
||||||
|
client.close()
|
||||||
|
except Exception as e:
|
||||||
|
print('ERROR in close_client: %s' % e)
|
||||||
|
iohandler.client = None
|
||||||
|
iohandler.fno = None
|
||||||
|
self.available += 1
|
||||||
|
if self.pending:
|
||||||
|
self.pending -= 1
|
||||||
|
self.accept()
|
||||||
|
|
||||||
|
def accept(self):
|
||||||
|
if not self.available:
|
||||||
|
self.pending += 1
|
||||||
|
return
|
||||||
|
client, addr = self.socket.accept()
|
||||||
|
handler = self.iocls(client, self)
|
||||||
|
self.readers[client.fileno()] = handler.request
|
||||||
|
self.readers[handler.fno] = handler.reply
|
||||||
|
self.available -= 1
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def run(cls, routes, restrict=None):
|
||||||
|
if restrict is not None:
|
||||||
|
lines = BASIC % dict(accept='DROP' if restrict else 'ACCEPT')
|
||||||
|
unix_cmd('iptables -F')
|
||||||
|
for line in lines.split('\n'):
|
||||||
|
if line.strip():
|
||||||
|
unix_cmd(line)
|
||||||
|
if restrict:
|
||||||
|
unix_cmd(FILTER % 22)
|
||||||
|
|
||||||
|
for port, dest in routes.items():
|
||||||
|
port=int(port)
|
||||||
|
if restrict:
|
||||||
|
unix_cmd(FILTER % port)
|
||||||
|
if '/' in dest:
|
||||||
|
AcceptHandler(port, dest, SerialHandler)
|
||||||
|
else:
|
||||||
|
host, _, remoteport = dest.partition(':')
|
||||||
|
if remoteport:
|
||||||
|
remoteport = int(remoteport)
|
||||||
|
else:
|
||||||
|
remoteport = port
|
||||||
|
AcceptHandler(port, (host, remoteport), TcpHandler)
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
ready, _, _ = select(cls.readers, [], [])
|
||||||
|
except Exception as e:
|
||||||
|
for r in cls.readers:
|
||||||
|
try:
|
||||||
|
select([r], [], [], 0.1)
|
||||||
|
except Exception as e:
|
||||||
|
print(r, e)
|
||||||
|
raise
|
||||||
|
for fno in ready:
|
||||||
|
cls.readers[fno]()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
parser = ConfigParser()
|
||||||
|
parser.read('/root/aputools/servercfg/%s.cfg' % socket.gethostname())
|
||||||
|
AcceptHandler.run(parser['ROUTER'])
|
11
servercfg/apumaster.cfg
Normal file
11
servercfg/apumaster.cfg
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[NETWORK]
|
||||||
|
address=00:0d:b9:59:1f:ac
|
||||||
|
enp2s0=192.168.127.2/24
|
||||||
|
enp3s0=192.168.2.3/24
|
||||||
|
enp4s0=192.168.2.4/24
|
||||||
|
|
||||||
|
[ROUTER]
|
||||||
|
3000=/dev/ttyUSB0
|
||||||
|
5900=192.168.2.33
|
||||||
|
8080=192.168.127.254:80
|
||||||
|
3001=192.168.127.254:3001
|
12
servercfg/apuslave1.cfg
Normal file
12
servercfg/apuslave1.cfg
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[HOSTNAME]
|
||||||
|
apuslave1=00:0d:b9:59:20:a8
|
||||||
|
|
||||||
|
[NETWORK]
|
||||||
|
enp2s0=192.168.127.2/24
|
||||||
|
enp3s0=192.168.2.3/24
|
||||||
|
enp4s0=192.168.2.4/24
|
||||||
|
|
||||||
|
[ROUTER]
|
||||||
|
3000=/dev/ttyUSB0
|
||||||
|
5900=192.168.2.33
|
||||||
|
8080=192.168.127.254:80
|
10
sethostname.sh
Normal file
10
sethostname.sh
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
ETHNAME=$(cat /sys/class/net/enp1s0/address)
|
||||||
|
FOUND=$(grep -H address=$ETHNAME /root/aputools/servercfg/*.cfg)
|
||||||
|
if [ -z "$FOUND" ]; then
|
||||||
|
ETHNAME=${ETHNAME//:/}
|
||||||
|
HOSTNAME=apu${ETHNAME:6:6}
|
||||||
|
else
|
||||||
|
FOUND=$(basename $FOUND) # remove directory part
|
||||||
|
HOSTNAME=${FOUND%%.*} # remove extension and all the rest
|
||||||
|
fi
|
||||||
|
echo $HOSTNAME > /etc/hostname
|
10
to_system/etc/systemd/system/router.service
Normal file
10
to_system/etc/systemd/system/router.service
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
[Unit]
|
||||||
|
Description = Routing to locally connected hardware
|
||||||
|
After = network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart = /usr/bin/python3 /root/aputools/router.py
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy = multi-user.target
|
||||||
|
|
12
to_system/etc/systemd/system/sethostname.service
Normal file
12
to_system/etc/systemd/system/sethostname.service
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[Unit]
|
||||||
|
Description = Create host name from ethernet address
|
||||||
|
Wants=network-pre.target
|
||||||
|
Before=network.pre-target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart = /usr/bin/bash /root/aputools/sethostname.sh
|
||||||
|
RemainAfterExit=yes
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy = network.target
|
Reference in New Issue
Block a user