25Python module for easy networking. This module intends to make networking
26easy. It supports tcp and unix domain sockets. Connection targets can be
27specified in several ways.
30'''@package network Python module for easy networking.
31This module intends to make networking easy. It supports tcp and unix domain
32sockets. Connection targets can be specified in several ways.
46fhs.module_info(modulename,
'Networking made easy',
'0.4',
'Bas Wijnen <wijnen@debian.org>')
47fhs.module_option(modulename,
'tls',
'default tls hostname for server sockets. The code may ignore this option. Set to - to request that tls is disabled on the server. If left empty, detects hostname.', default =
'')
72 makestr =
lambda x: str(x,
'utf8',
'replace')
if isinstance(x, bytes)
else x
76log_output = sys.stderr
86 global log_output, log_date
102def log(*message, filename = None, line = None, funcname = None, depth = 0):
103 t = time.strftime(
'%F %T' if log_date
else '%T')
104 source = inspect.currentframe().f_back
105 for d
in range(depth):
106 source = source.f_back
109 filename = os.path.basename(code.co_filename)
111 funcname = code.co_name
113 line = source.f_lineno
115 log_output.write(
''.join([
'%s %s:%s:%d:\t%s\n' % (t, filename, funcname, line, m)
for m
in str(msg).split(
'\n')]))
124 if isinstance(service, int):
127 return socket.getservbyname(service)
145 def __init__(self, i, o = None):
147 self._o = o
if o
is not None else i
154 def sendall(self, data):
156 fd = self._o
if isinstance(self._o, int)
else self._o.fileno()
157 ret = os.write(fd, data)
161 log(
'network.py: Failed to write data')
162 traceback.print_exc()
165 def recv(self, maxsize):
167 return os.read(self._i.fileno(), maxsize)
172 return self._i.fileno()
182 return Socket(_Fake(i, o))
204 def __init__(self, address, tls = None, disconnect_cb = None, remote = None, connections = None):
218 if isinstance(address, (_Fake, socket.socket)):
222 if isinstance(address, str):
223 r = re.match(
'^(?:([a-z0-9-]+)://)?([^:/?#]+)(?::([^:/?#]+))?([:/?#].*)?$', address)
229 protocol = r.group(1)
230 hostname = r.group(2)
238 self.
tls = protocol !=
'ws' and protocol.endswith(
's')
243 if address.startswith(
'./')
or address.startswith(
'/')
or (port
is None and '/' in address):
247 self.
socket = socket.socket(socket.AF_UNIX)
252 hostname =
'localhost'
257 hostname =
'localhost'
260 hostname =
'localhost'
269 def _setup_connection(self):
274 self.
socket = ssl.wrap_socket(self.
socket, ssl_version = ssl.PROTOCOL_TLSv1)
279 elif self.
tls is True:
282 self.
socket = ssl.wrap_socket(self.
socket, ssl_version = ssl.PROTOCOL_TLSv1)
283 except ssl.SSLError
as e:
284 raise TypeError(
'Socket does not seem to support TLS: ' + str(e))
319 except BrokenPipeError:
332 self.
socket.sendall((data +
'\n').encode(
'utf-8'))
346 def recv(self, maxsize = 4096):
348 log(
'recv on closed socket')
349 raise EOFError(
'recv on closed socket')
353 if hasattr(self.
socket,
'pending'):
354 while self.
socket.pending():
357 log(
'Error reading from socket: %s' % sys.exc_info()[1])
363 raise EOFError(
'network connection closed')
393 def read(self, callback, error = None, maxsize = 4096):
422 def readlines(self, callback, error = None, maxsize = 4096):
494 def __init__(self, port, obj, address = '', backlog = 5, tls = False, disconnect_cb = None):
507 if isinstance(port, str)
and '/' in port:
511 self.
_socket = socket.socket(socket.AF_UNIX)
519 self.
_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
520 self.
_socket.bind((address, port))
523 self.
_socket6 = socket.socket(socket.AF_INET6)
524 self.
_socket6.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
532 def _cb(self, is_ipv6):
536 new_socket = self.
_socket.accept()
541 new_socket = (ssl.wrap_socket(new_socket[0], ssl_version = ssl.PROTOCOL_TLSv1, server_side =
True, certfile = self.
_tls_cert, keyfile = self.
_tls_key), new_socket[1])
542 except ssl.SSLError
as e:
543 log(
'Rejecting (non-TLS?) connection for %s: %s' % (repr(new_socket[1]), str(e)))
545 new_socket[0].shutdown(socket.SHUT_RDWR)
550 except socket.error
as e:
551 log(
'Rejecting connection for %s: %s' % (repr(new_socket[1]), str(e)))
553 new_socket[0].shutdown(socket.SHUT_RDWR)
571 if isinstance(self.
port, str)
and '/' in self.
port:
582 if self.
tls in (
False,
'-'):
585 if self.
tls in (
None,
True,
''):
586 self.
tls = fhs.module_get_config(
'network')[
'tls']
588 self.
tls = socket.getfqdn()
589 elif self.
tls ==
'-':
593 fc = fhs.read_data(os.path.join(
'certs', self.
tls + os.extsep +
'pem'), opened =
False, packagename =
'network')
594 fk = fhs.read_data(os.path.join(
'private', self.
tls + os.extsep +
'key'), opened =
False, packagename =
'network')
595 if fc
is None or fk
is None:
597 certfile = fhs.write_data(os.path.join(
'certs', self.
tls + os.extsep +
'pem'), opened =
False, packagename =
'network')
598 csrfile = fhs.write_data(os.path.join(
'csr', self.
tls + os.extsep +
'csr'), opened =
False, packagename =
'network')
599 for p
in (certfile, csrfile):
600 path = os.path.dirname(p)
601 if not os.path.exists(path):
603 keyfile = fhs.write_data(os.path.join(
'private', self.
tls + os.extsep +
'key'), opened =
False, packagename =
'network')
604 path = os.path.dirname(keyfile)
605 if not os.path.exists(path):
606 os.makedirs(path, 0o700)
607 os.system(
'openssl req -x509 -nodes -days 3650 -newkey rsa:4096 -subj "/CN=%s" -keyout "%s" -out "%s"' % (self.
tls, keyfile, certfile))
608 os.system(
'openssl req -subj "/CN=%s" -new -key "%s" -out "%s"' % (self.
tls, keyfile, csrfile))
609 fc = fhs.read_data(os.path.join(
'certs', self.
tls + os.extsep +
'pem'), opened =
False, packagename =
'network')
610 fk = fhs.read_data(os.path.join(
'private', self.
tls + os.extsep +
'key'), opened =
False, packagename =
'network')
624def _handle_timeouts():
626 while not _abort
and len(_timeouts) > 0
and _timeouts[0][0] <= now:
627 _timeouts.pop(0)[1]()
628 if len(_timeouts) == 0:
630 return _timeouts[0][0] - now
641 t = _handle_timeouts()
646 ret = select.select(_fds[0], _fds[1], _fds[0] + _fds[1])
648 ret = select.select(_fds[0], _fds[1], _fds[0] + _fds[1], t)
651 if f
not in _fds[0]
and f
not in _fds[1]:
729 assert _running ==
False
730 if os.getenv(
'NETWORK_NO_FORK')
is None:
734 log(
'Not backgrounding because NETWORK_NO_FORK is set\n')
742 global _running, _abort
756 def __init__(self, fd, cb, error):
759 if error
is not None:
762 self.error = self.default_error
764 if isinstance(self.fd, int):
767 return self.fd.fileno()
768 def default_error(self):
771 log(
'Error returned from select; removed fd from read list')
775 log(
'Error returned from select; removed fd from write list')
777 log(
'Error returned from select, but fd was not in read or write list')
781 _fds[0].append(_fd_wrap(fd, cb, error))
787 _fds[1].append(_fd_wrap(fd, cb, error))
792 _timeouts.append([abstime, cb])
806 _fds[0].remove(handle)
810 _fds[1].remove(handle)
814 _timeouts.remove(handle)
tls
False or the hostname for which the TLS keys are used.
ipv6
Whether the server listens for IPv6.
port
Port that is listened on.
disconnect_cb
Disconnect handler, to be used for new sockets.
connections
Currently active connections for this server.
close(self)
Stop the server.
Listen on a network port and accept connections.
recv(self, maxsize=4096)
Read data from the network.
connections
connections set where this socket is registered.
sendline(self, data)
Send a line of text.
close(self)
Close the network connection.
readlines(self, callback, error=None, maxsize=4096)
Buffer incoming data until a line is received, then call a function.
send(self, data)
Send data over the network.
remote
remote end of the network connection.
rawread(self, callback, error=None)
Register function to be called when data is ready for reading.
tls
read only variable which indicates whether TLS encryption is used on this socket.
disconnect_cb(self, disconnect_cb)
Change the callback for disconnect notification.
unread(self)
Cancel a read() or rawread() callback.
socket
underlying socket object.
read(self, callback, error=None, maxsize=4096)
Register function to be called when data is received.
bgloop()
Like fgloop, but forks to the background.
log(*message, filename=None, line=None, funcname=None, depth=0)
Log a message.
add_read(fd, cb, error=None)
lookup(service)
Convert int or str with int or service to int port.
wrap(i, o=None)
Wrap two files into a fake socket.
set_log_output(file)
Change target for log().
iteration(block=False)
Do a single iteration of the main loop.
add_write(fd, cb, error=None)
endloop(force=False)
Stop a loop that was started with fgloop() or bgloop().
fgloop()
Wait for events and handle them.