25 Python module for easy networking. This module intends to make networking
26 easy. It supports tcp and unix domain sockets. Connection targets can be
27 specified in several ways.
30 '''@package network Python module for easy networking.
31 This module intends to make networking easy. It supports tcp and unix domain
32 sockets. Connection targets can be specified in several ways.
45 modulename =
'network'
46 fhs.module_info(modulename,
'Networking made easy',
'0.2',
'Bas Wijnen <wijnen@debian.org>')
47 fhs.module_option(modulename,
'tls',
'tls hostname for server sockets or True/False for client sockets. Set to - to disable tls on server. If left empty, uses hostname for server, True for client sockets.', default =
'')
71 if sys.version >=
'3':
72 makestr =
lambda x: str(x,
'utf8',
'replace')
if isinstance(x, bytes)
else x
76 log_output = sys.stderr
86 global log_output, log_date
102 def 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 = False, disconnect_cb = None, remote = None, connections = None):
218 if isinstance(address, (_Fake, socket.socket)):
220 self.
socketsocket = address
222 if isinstance(address, str)
and '/' in address:
225 self.
remoteremote = address
226 self.
socketsocket = socket.socket(socket.AF_UNIX)
229 if isinstance(address, str)
and ':' in address:
230 host, port = address.rsplit(
':', 1)
232 host, port =
'localhost', address
240 def _setup_connection(self):
241 self.
socketsocket = socket.create_connection(self.
remoteremote)
242 if self.
tlstls
is None:
245 self.
socketsocket = ssl.wrap_socket(self.
socketsocket, ssl_version = ssl.PROTOCOL_TLSv1)
249 self.
socketsocket = socket.create_connection(self.
remoteremote)
250 elif self.
tlstls
is True:
253 self.
socketsocket = ssl.wrap_socket(self.
socketsocket, ssl_version = ssl.PROTOCOL_TLSv1)
254 except ssl.SSLError
as e:
255 raise TypeError(
'Socket does not seem to support TLS: ' + str(e))
270 data = self.
unreadunread()
285 if self.
socketsocket
is None:
289 self.
socketsocket.sendall(data)
290 except BrokenPipeError:
300 if self.
socketsocket
is None:
303 self.
socketsocket.sendall((data +
'\n').encode(
'utf-8'))
317 def recv(self, maxsize = 4096):
318 if self.
socketsocket
is None:
319 log(
'recv on closed socket')
320 raise EOFError(
'recv on closed socket')
324 if hasattr(self.
socketsocket,
'pending'):
325 while self.
socketsocket.pending():
328 log(
'Error reading from socket: %s' % sys.exc_info()[1])
332 ret = self.
closeclose()
334 raise EOFError(
'network connection closed')
346 if self.
socketsocket
is None:
349 self.
_callback_callback = (callback,
None)
364 def read(self, callback, error = None, maxsize = 4096):
365 if self.
socketsocket
is None:
367 first = self.
unreadunread()
369 self.
_callback_callback = (callback,
False)
393 def readlines(self, callback, error = None, maxsize = 4096):
394 if self.
socketsocket
is None:
398 self.
_callback_callback = (callback,
True)
404 assert self.
_callback_callback[1]
is not None
465 def __init__(self, port, obj, address = '', backlog = 5, tls = False, disconnect_cb = None):
478 if isinstance(port, str)
and '/' in port:
482 self.
_socket_socket = socket.socket(socket.AF_UNIX)
485 self.
_socket_socket.listen(backlog)
489 self.
_socket_socket = socket.socket()
490 self.
_socket_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
491 self.
_socket_socket.bind((address, port))
492 self.
_socket_socket.listen(backlog)
494 self.
_socket6_socket6 = socket.socket(socket.AF_INET6)
495 self.
_socket6_socket6.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
496 self.
_socket6_socket6.bind((
'::1', port))
497 self.
_socket6_socket6.listen(backlog)
503 def _cb(self, is_ipv6):
505 new_socket = self.
_socket6_socket6.accept()
507 new_socket = self.
_socket_socket.accept()
512 new_socket = (ssl.wrap_socket(new_socket[0], ssl_version = ssl.PROTOCOL_TLSv1, server_side =
True, certfile = self.
_tls_cert_tls_cert, keyfile = self.
_tls_key_tls_key), new_socket[1])
513 except ssl.SSLError
as e:
514 log(
'Rejecting (non-TLS?) connection for %s: %s' % (repr(new_socket[1]), str(e)))
516 new_socket[0].shutdown(socket.SHUT_RDWR)
521 except socket.error
as e:
522 log(
'Rejecting connection for %s: %s' % (repr(new_socket[1]), str(e)))
524 new_socket[0].shutdown(socket.SHUT_RDWR)
542 if isinstance(self.
portport, str)
and '/' in self.
portport:
543 os.remove(self.
portport)
549 if self.
_socket_socket
is not None:
553 if self.
tlstls
in (
False,
'-'):
556 if self.
tlstls
in (
None,
True,
''):
557 self.
tlstls = fhs.module_get_config(
'network')[
'tls']
558 if self.
tlstls ==
'':
559 self.
tlstls = socket.getfqdn()
560 elif self.
tlstls ==
'-':
564 fc = fhs.read_data(os.path.join(
'certs', self.
tlstls + os.extsep +
'pem'), opened =
False, packagename =
'network')
565 fk = fhs.read_data(os.path.join(
'private', self.
tlstls + os.extsep +
'key'), opened =
False, packagename =
'network')
566 if fc
is None or fk
is None:
568 certfile = fhs.write_data(os.path.join(
'certs', self.
tlstls + os.extsep +
'pem'), opened =
False, packagename =
'network')
569 csrfile = fhs.write_data(os.path.join(
'csr', self.
tlstls + os.extsep +
'csr'), opened =
False, packagename =
'network')
570 for p
in (certfile, csrfile):
571 path = os.path.dirname(p)
572 if not os.path.exists(path):
574 keyfile = fhs.write_data(os.path.join(
'private', self.
tlstls + os.extsep +
'key'), opened =
False, packagename =
'network')
575 path = os.path.dirname(keyfile)
576 if not os.path.exists(path):
577 os.makedirs(path, 0o700)
578 os.system(
'openssl req -x509 -nodes -days 3650 -newkey rsa:4096 -subj "/CN=%s" -keyout "%s" -out "%s"' % (self.
tlstls, keyfile, certfile))
579 os.system(
'openssl req -subj "/CN=%s" -new -key "%s" -out "%s"' % (self.
tlstls, keyfile, csrfile))
580 fc = fhs.read_data(os.path.join(
'certs', self.
tlstls + os.extsep +
'pem'), opened =
False, packagename =
'network')
581 fk = fhs.read_data(os.path.join(
'private', self.
tlstls + os.extsep +
'key'), opened =
False, packagename =
'network')
595 def _handle_timeouts():
597 while not _abort
and len(_timeouts) > 0
and _timeouts[0][0] <= now:
598 _timeouts.pop(0)[1]()
599 if len(_timeouts) == 0:
601 return _timeouts[0][0] - now
612 t = _handle_timeouts()
617 ret = select.select(_fds[0], _fds[1], _fds[0] + _fds[1])
619 ret = select.select(_fds[0], _fds[1], _fds[0] + _fds[1], t)
622 if f
not in _fds[0]
and f
not in _fds[1]:
696 assert _running ==
False
697 if os.getenv(
'NETWORK_NO_FORK')
is None:
701 log(
'Not backgrounding because NETWORK_NO_FORK is set\n')
709 global _running, _abort
723 def __init__(self, fd, cb, error):
726 if error
is not None:
729 self.error = self.default_error
731 if isinstance(self.fd, int):
734 return self.fd.fileno()
735 def default_error(self):
738 log(
'Error returned from select; removed fd from read list')
742 log(
'Error returned from select; removed fd from write list')
744 log(
'Error returned from select, but fd was not in read or write list')
748 _fds[0].append(_fd_wrap(fd, cb, error))
754 _fds[1].append(_fd_wrap(fd, cb, error))
759 _timeouts.append([abstime, cb])
773 _fds[0].remove(handle)
777 _fds[1].remove(handle)
781 _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)