1
0
mirror of https://github.com/craigerl/aprsd.git synced 2025-09-03 21:57:47 -04:00

Updated APRSIS driver

This patch adds an override to _connect to set some more
socket keepalive options.
This commit is contained in:
Hemna 2025-01-30 10:03:14 -08:00
parent 3b57e7597d
commit 1606585d41

View File

@ -1,6 +1,7 @@
import datetime import datetime
import logging import logging
import select import select
import socket
import threading import threading
import aprslib import aprslib
@ -18,7 +19,7 @@ from aprslib.exceptions import (
import aprsd import aprsd
from aprsd.packets import core from aprsd.packets import core
LOG = logging.getLogger("APRSD") LOG = logging.getLogger('APRSD')
class Aprsdis(aprslib.IS): class Aprsdis(aprslib.IS):
@ -31,7 +32,7 @@ class Aprsdis(aprslib.IS):
aprsd_keepalive = datetime.datetime.now() aprsd_keepalive = datetime.datetime.now()
# Which server we are connected to? # Which server we are connected to?
server_string = "None" server_string = 'None'
# timeout in seconds # timeout in seconds
select_timeout = 1 select_timeout = 1
@ -39,10 +40,10 @@ class Aprsdis(aprslib.IS):
def stop(self): def stop(self):
self.thread_stop = True self.thread_stop = True
LOG.warning("Shutdown Aprsdis client.") LOG.warning('Shutdown Aprsdis client.')
def close(self): def close(self):
LOG.warning("Closing Aprsdis client.") LOG.warning('Closing Aprsdis client.')
super().close() super().close()
@wrapt.synchronized(lock) @wrapt.synchronized(lock)
@ -54,6 +55,57 @@ class Aprsdis(aprslib.IS):
"""If the connection is alive or not.""" """If the connection is alive or not."""
return self._connected return self._connected
def _connect(self):
"""
Attemps connection to the server
"""
self.logger.info(
'Attempting connection to %s:%s', self.server[0], self.server[1]
)
try:
self._open_socket()
peer = self.sock.getpeername()
self.logger.info('Connected to %s', str(peer))
# 5 second timeout to receive server banner
self.sock.setblocking(1)
self.sock.settimeout(5)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
# MACOS doesn't have TCP_KEEPIDLE
if hasattr(socket, 'TCP_KEEPIDLE'):
self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 1)
self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 3)
self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 5)
banner = self.sock.recv(512)
if is_py3:
banner = banner.decode('latin-1')
if banner[0] == '#':
self.logger.debug('Banner: %s', banner.rstrip())
else:
raise ConnectionError('invalid banner from server')
except ConnectionError as e:
self.logger.error(str(e))
self.close()
raise
except (socket.error, socket.timeout) as e:
self.close()
self.logger.error('Socket error: %s' % str(e))
if str(e) == 'timed out':
raise ConnectionError('no banner from server') from e
else:
raise ConnectionError(e) from e
self._connected = True
def _socket_readlines(self, blocking=False): def _socket_readlines(self, blocking=False):
""" """
Generator for complete lines, received from the server Generator for complete lines, received from the server
@ -61,12 +113,12 @@ class Aprsdis(aprslib.IS):
try: try:
self.sock.setblocking(0) self.sock.setblocking(0)
except OSError as e: except OSError as e:
self.logger.error(f"socket error when setblocking(0): {str(e)}") self.logger.error(f'socket error when setblocking(0): {str(e)}')
raise aprslib.ConnectionDrop("connection dropped") raise aprslib.ConnectionDrop('connection dropped') from e
while not self.thread_stop: while not self.thread_stop:
short_buf = b"" short_buf = b''
newline = b"\r\n" newline = b'\r\n'
# set a select timeout, so we get a chance to exit # set a select timeout, so we get a chance to exit
# when user hits CTRL-C # when user hits CTRL-C
@ -91,11 +143,11 @@ class Aprsdis(aprslib.IS):
# We could just not be blocking, so empty is expected # We could just not be blocking, so empty is expected
continue continue
else: else:
self.logger.error("socket.recv(): returned empty") self.logger.error('socket.recv(): returned empty')
raise aprslib.ConnectionDrop("connection dropped") raise aprslib.ConnectionDrop('connection dropped')
except OSError as e: except OSError as e:
# self.logger.error("socket error on recv(): %s" % str(e)) # self.logger.error("socket error on recv(): %s" % str(e))
if "Resource temporarily unavailable" in str(e): if 'Resource temporarily unavailable' in str(e):
if not blocking: if not blocking:
if len(self.buf) == 0: if len(self.buf) == 0:
break break
@ -111,22 +163,22 @@ class Aprsdis(aprslib.IS):
""" """
Sends login string to server Sends login string to server
""" """
login_str = "user {0} pass {1} vers github.com/craigerl/aprsd {3}{2}\r\n" login_str = 'user {0} pass {1} vers Python-APRSD {3}{2}\r\n'
login_str = login_str.format( login_str = login_str.format(
self.callsign, self.callsign,
self.passwd, self.passwd,
(" filter " + self.filter) if self.filter != "" else "", (' filter ' + self.filter) if self.filter != '' else '',
aprsd.__version__, aprsd.__version__,
) )
self.logger.debug("Sending login information") self.logger.debug('Sending login information')
try: try:
self._sendall(login_str) self._sendall(login_str)
self.sock.settimeout(5) self.sock.settimeout(5)
test = self.sock.recv(len(login_str) + 100) test = self.sock.recv(len(login_str) + 100)
if is_py3: if is_py3:
test = test.decode("latin-1") test = test.decode('latin-1')
test = test.rstrip() test = test.rstrip()
self.logger.debug("Server: '%s'", test) self.logger.debug("Server: '%s'", test)
@ -134,26 +186,26 @@ class Aprsdis(aprslib.IS):
if not test: if not test:
raise LoginError(f"Server Response Empty: '{test}'") raise LoginError(f"Server Response Empty: '{test}'")
_, _, callsign, status, e = test.split(" ", 4) _, _, callsign, status, e = test.split(' ', 4)
s = e.split(",") s = e.split(',')
if len(s): if len(s):
server_string = s[0].replace("server ", "") server_string = s[0].replace('server ', '')
else: else:
server_string = e.replace("server ", "") server_string = e.replace('server ', '')
if callsign == "": if callsign == '':
raise LoginError("Server responded with empty callsign???") raise LoginError('Server responded with empty callsign???')
if callsign != self.callsign: if callsign != self.callsign:
raise LoginError(f"Server: {test}") raise LoginError(f'Server: {test}')
if status != "verified," and self.passwd != "-1": if status != 'verified,' and self.passwd != '-1':
raise LoginError("Password is incorrect") raise LoginError('Password is incorrect')
if self.passwd == "-1": if self.passwd == '-1':
self.logger.info("Login successful (receive only)") self.logger.info('Login successful (receive only)')
else: else:
self.logger.info("Login successful") self.logger.info('Login successful')
self.logger.info(f"Connected to {server_string}") self.logger.info(f'Connected to {server_string}')
self.server_string = server_string self.server_string = server_string
except LoginError as e: except LoginError as e:
@ -164,7 +216,7 @@ class Aprsdis(aprslib.IS):
self.close() self.close()
self.logger.error(f"Failed to login '{e}'") self.logger.error(f"Failed to login '{e}'")
self.logger.exception(e) self.logger.exception(e)
raise LoginError("Failed to login") raise LoginError('Failed to login') from e
def consumer(self, callback, blocking=True, immortal=False, raw=False): def consumer(self, callback, blocking=True, immortal=False, raw=False):
""" """
@ -180,21 +232,21 @@ class Aprsdis(aprslib.IS):
""" """
if not self._connected: if not self._connected:
raise ConnectionError("not connected to a server") raise ConnectionError('not connected to a server')
line = b"" line = b''
while True and not self.thread_stop: while True and not self.thread_stop:
try: try:
for line in self._socket_readlines(blocking): for line in self._socket_readlines(blocking):
if line[0:1] != b"#": if line[0:1] != b'#':
self.aprsd_keepalive = datetime.datetime.now() self.aprsd_keepalive = datetime.datetime.now()
if raw: if raw:
callback(line) callback(line)
else: else:
callback(self._parse(line)) callback(self._parse(line))
else: else:
self.logger.debug("Server: %s", line.decode("utf8")) self.logger.debug('Server: %s', line.decode('utf8'))
self.aprsd_keepalive = datetime.datetime.now() self.aprsd_keepalive = datetime.datetime.now()
except ParseError as exp: except ParseError as exp:
self.logger.log( self.logger.log(
@ -211,7 +263,7 @@ class Aprsdis(aprslib.IS):
exp.packet, exp.packet,
) )
except LoginError as exp: except LoginError as exp:
self.logger.error("%s: %s", exp.__class__.__name__, exp) self.logger.error('%s: %s', exp.__class__.__name__, exp)
except (KeyboardInterrupt, SystemExit): except (KeyboardInterrupt, SystemExit):
raise raise
except (ConnectionDrop, ConnectionError): except (ConnectionDrop, ConnectionError):
@ -227,7 +279,7 @@ class Aprsdis(aprslib.IS):
except StopIteration: except StopIteration:
break break
except Exception: except Exception:
self.logger.error("APRS Packet: %s", line) self.logger.error('APRS Packet: %s', line)
raise raise
if not blocking: if not blocking: