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 = '')
57# {{{ Interface description
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.
def close(self)
Stop the server.
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.
Listen on a network port and accept connections.
def recv(self, maxsize=4096)
Read data from the network.
connections
connections set where this socket is registered.
def disconnect_cb(self, disconnect_cb)
Change the callback for disconnect notification.
def read(self, callback, error=None, maxsize=4096)
Register function to be called when data is received.
def readlines(self, callback, error=None, maxsize=4096)
Buffer incoming data until a line is received, then call a function.
def send(self, data)
Send data over the network.
def close(self)
Close the network connection.
remote
remote end of the network connection.
def sendline(self, data)
Send a line of text.
tls
read only variable which indicates whether TLS encryption is used on this socket.
def rawread(self, callback, error=None)
Register function to be called when data is ready for reading.
socket
underlying socket object.
def unread(self)
Cancel a read() or rawread() callback.
def add_timeout(abstime, cb)
def bgloop()
Like fgloop, but forks to the background.
def add_write(fd, cb, error=None)
def lookup(service)
Convert int or str with int or service to int port.
def add_read(fd, cb, error=None)
def iteration(block=False)
Do a single iteration of the main loop.
def log(*message, filename=None, line=None, funcname=None, depth=0)
Log a message.
def wrap(i, o=None)
Wrap two files into a fake socket.
def fgloop()
Wait for events and handle them.
def set_log_output(file)
Change target for log().
def endloop(force=False)
Stop a loop that was started with fgloop() or bgloop().
def remove_timeout(handle)