diff --git a/README.md b/README.md index af589d3..bcda5e2 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,8 @@ sudo usermod -a -G dialout ## A note to developers of this lib +We use the visual-studio-code default python formatting conventions. So if you use that IDE you should be able to use "Format Document" and not generate unrelated diffs. If you use some other editor, please don't change formatting on lines you haven't changed. + If you need to build a new release you'll need: ``` apt install pandoc diff --git a/meshtastic/__init__.py b/meshtastic/__init__.py index 2ecb871..6c3e21f 100644 --- a/meshtastic/__init__.py +++ b/meshtastic/__init__.py @@ -55,11 +55,19 @@ interface = meshtastic.SerialInterface() """ -import socket import pygatt import google.protobuf.json_format -import serial, threading, logging, sys, random, traceback, time, base64, platform -from . import mesh_pb2, portnums_pb2, util +import serial +import threading +import logging +import sys +import random +import traceback +import time +import base64 +import platform +import socket +from . import mesh_pb2, portnums_pb2, apponly_pb2, util from pubsub import pub from dotmap import DotMap @@ -79,7 +87,8 @@ MY_CONFIG_ID = 42 format is Mmmss (where M is 1+the numeric major number. i.e. 20120 means 1.1.20 """ -OUR_APP_VERSION = 20200 +OUR_APP_VERSION = 20200 + def catchAndIgnore(reason, closure): """Call a closure but if it throws an excpetion print it and continue""" @@ -88,6 +97,7 @@ def catchAndIgnore(reason, closure): except BaseException as ex: logging.error(f"Exception thrown in {reason}: {ex}") + class MeshInterface: """Interface class for meshtastic devices @@ -108,7 +118,7 @@ class MeshInterface: self.nodes = None # FIXME self.isConnected = threading.Event() self.noProto = noProto - random.seed() # FIXME, we should not clobber the random seedval here, instead tell user they must call it + random.seed() # FIXME, we should not clobber the random seedval here, instead tell user they must call it self.currentPacketId = random.randint(0, 0xffffffff) self._startConfig() @@ -117,7 +127,8 @@ class MeshInterface: def __exit__(self, exc_type, exc_value, traceback): if exc_type is not None and exc_value is not None: - logging.error(f'An exception of type {exc_type} with value {exc_value} has occurred') + logging.error( + f'An exception of type {exc_type} with value {exc_value} has occurred') if traceback is not None: logging.error(f'Traceback: {traceback}') self.close() @@ -265,6 +276,7 @@ class MeshInterface: """Set device owner name""" nChars = 3 minChars = 2 + fixme("update for 1.2") if long_name is not None: long_name = long_name.strip() if short_name is None: @@ -292,27 +304,30 @@ class MeshInterface: def channelURL(self): """The sharable URL that describes the current channel """ - bytes = self.radioConfig.channel_settings.SerializeToString() + channelSet = apponly_pb2.ChannelSet() + fixme("fill channelSet from self.channels") + bytes = channelSet.SerializeToString() s = base64.urlsafe_b64encode(bytes).decode('ascii') return f"https://www.meshtastic.org/c/#{s}" - def setURL(self, url, write=True): + def setURL(self, url): """Set mesh network URL""" if self.radioConfig == None: raise Exception("No RadioConfig has been read") - # URLs are of the form https://www.meshtastic.org/c/#{base64_channel_settings} + # URLs are of the form https://www.meshtastic.org/c/#{base64_channel_set} # Split on '/#' to find the base64 encoded channel settings splitURL = url.split("/#") decodedURL = base64.urlsafe_b64decode(splitURL[-1]) - self.radioConfig.channel_settings.ParseFromString(decodedURL) - if write: - self.writeConfig() + channelSet = apponly_pb2.ChannelSet() + channelSet.ParseFromString(decodedURL) + fixme("set self.channels") + self.writeChannels() def _waitConnected(self): """Block until the initial node db download is complete, or timeout and raise an exception""" - if not self.isConnected.wait(5.0): # timeout after 5 seconds + if not self.isConnected.wait(5.0): # timeout after 5 seconds raise Exception("Timed out waiting for connection completion") def _generatePacketId(self): @@ -326,13 +341,15 @@ class MeshInterface: def _disconnected(self): """Called by subclasses to tell clients this interface has disconnected""" self.isConnected.clear() - catchAndIgnore("disconnection publish", lambda: pub.sendMessage("meshtastic.connection.lost", interface=self)) + catchAndIgnore("disconnection publish", lambda: pub.sendMessage( + "meshtastic.connection.lost", interface=self)) def _connected(self): """Called by this class to tell clients we are now fully connected to a node """ self.isConnected.set() - catchAndIgnore("connection publish", lambda: pub.sendMessage("meshtastic.connection.established", interface=self)) + catchAndIgnore("connection publish", lambda: pub.sendMessage( + "meshtastic.connection.established", interface=self)) def _startConfig(self): """Start device packets flowing""" @@ -348,7 +365,8 @@ class MeshInterface: def _sendToRadio(self, toRadio): """Send a ToRadio protobuf to the device""" if self.noProto: - logging.warn(f"Not sending packet because protocol use is disabled by noProto") + logging.warn( + f"Not sending packet because protocol use is disabled by noProto") else: logging.debug(f"Sending toRadio: {toRadio}") self._sendToRadioImpl(toRadio) @@ -357,6 +375,15 @@ class MeshInterface: """Send a ToRadio protobuf to the device""" logging.error(f"Subclass must provide toradio: {toRadio}") + def _handleConfigComplete(self): + """ + Done with initial config messages, now send regular MeshPackets to ask for settings and channels + """ + self._requestSettings() + self._requestChannels() + # FIXME, the following should only be called after we have settings and channels + self._connected() # Tell everone else we are ready to go + def _handleFromRadio(self, fromRadioBytes): """ Handle a packet that arrived from the radio(update model and publish events) @@ -383,10 +410,11 @@ class MeshInterface: self.nodesByNum[node["num"]] = node if "user" in node: # Some nodes might not have user/ids assigned yet self.nodes[node["user"]["id"]] = node - pub.sendMessage("meshtastic.node.updated", node=node, interface=self) + pub.sendMessage("meshtastic.node.updated", + node=node, interface=self) elif fromRadio.config_complete_id == MY_CONFIG_ID: # we ignore the config_complete_id, it is unneeded for our stream API fromRadio.config_complete_id - self._connected() + self._handleConfigComplete() elif fromRadio.HasField("packet"): self._handlePacketFromRadio(fromRadio.packet) elif fromRadio.rebooted: @@ -465,7 +493,8 @@ class MeshInterface: # UNKNOWN_APP is the default protobuf portnum value, and therefore if not set it will not be populated at all # to make API usage easier, set it to prevent confusion if not "portnum" in asDict["decoded"]: - asDict["decoded"]["portnum"] = portnums_pb2.PortNum.Name(portnums_pb2.PortNum.UNKNOWN_APP) + asDict["decoded"]["portnum"] = portnums_pb2.PortNum.Name( + portnums_pb2.PortNum.UNKNOWN_APP) portnum = asDict["decoded"]["portnum"] @@ -482,7 +511,8 @@ class MeshInterface: # Usually btw this problem is caused by apps sending binary data but setting the payload type to # text. try: - asDict["decoded"]["text"] = meshPacket.decoded.payload.decode("utf-8") + asDict["decoded"]["text"] = meshPacket.decoded.payload.decode( + "utf-8") except Exception as ex: logging.error(f"Malformatted utf8 in text message: {ex}") @@ -511,7 +541,8 @@ class MeshInterface: self.nodes[u["id"]] = n logging.debug(f"Publishing topic {topic}") - catchAndIgnore(f"publishing {topic}", lambda: pub.sendMessage(topic, packet=asDict, interface=self)) + catchAndIgnore(f"publishing {topic}", lambda: pub.sendMessage( + topic, packet=asDict, interface=self)) # Our standard BLE characteristics @@ -600,7 +631,7 @@ class StreamInterface(MeshInterface): time.sleep(0.1) # wait 100ms to give device time to start running self._rxThread.start() - if not self.noProto: # Wait for the db download if using the protocol + if not self.noProto: # Wait for the db download if using the protocol self._waitConnected() def _disconnected(self): @@ -686,13 +717,16 @@ class StreamInterface(MeshInterface): # logging.debug(f"timeout") pass except serial.SerialException as ex: - if not self._wantExit: # We might intentionally get an exception during shutdown - logging.warn(f"Meshtastic serial port disconnected, disconnecting... {ex}") + if not self._wantExit: # We might intentionally get an exception during shutdown + logging.warn( + f"Meshtastic serial port disconnected, disconnecting... {ex}") except OSError as ex: - if not self._wantExit: # We might intentionally get an exception during shutdown - logging.error(f"Unexpected OSError, terminating meshtastic reader... {ex}") + if not self._wantExit: # We might intentionally get an exception during shutdown + logging.error( + f"Unexpected OSError, terminating meshtastic reader... {ex}") except Exception as ex: - logging.error(f"Unexpected exception, terminating meshtastic reader... {ex}") + logging.error( + f"Unexpected exception, terminating meshtastic reader... {ex}") finally: logging.debug("reader is exiting") self._disconnected() @@ -740,6 +774,7 @@ class SerialInterface(StreamInterface): StreamInterface.__init__( self, debugOut=debugOut, noProto=noProto, connectNow=connectNow) + class TCPInterface(StreamInterface): """Interface class for meshtastic devices over a TCP link""" @@ -766,9 +801,9 @@ class TCPInterface(StreamInterface): def close(self): """Close a connection to the device""" logging.debug("Closing TCP stream") - # Sometimes the socket read might be blocked in the reader thread. Therefore we force the shutdown by closing + # Sometimes the socket read might be blocked in the reader thread. Therefore we force the shutdown by closing # the socket here - self._wantExit = True + self._wantExit = True if not self.socket is None: self.socket.shutdown(socket.SHUT_RDWR) self.socket.close() diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index dc10406..1a0cc37 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -1,6 +1,11 @@ #!python3 -import argparse, platform, logging, sys, codecs, base64 +import argparse +import platform +import logging +import sys +import codecs +import base64 from . import SerialInterface, TCPInterface, BLEInterface, test, remote_hardware from pubsub import pub from . import mesh_pb2, portnums_pb2 @@ -20,6 +25,7 @@ args = None """The parser for arguments""" parser = argparse.ArgumentParser() + def onReceive(packet, interface): """Callback invoked when a packet arrives""" logging.debug(f"Received: {packet}") @@ -54,6 +60,7 @@ def onConnection(interface, topic=pub.AUTO_TOPIC): trueTerms = {"t", "true", "yes"} falseTerms = {"f", "false", "no"} + def fromStr(valstr): """try to parse as int, float or bool (and fallback to a string as last resort) @@ -63,7 +70,8 @@ def fromStr(valstr): valstr (string): A user provided string """ if(valstr.startswith('0x')): - val = bytes.fromhex(valstr[2:]) # if needed convert to string with asBytes.decode('utf-8') + # if needed convert to string with asBytes.decode('utf-8') + val = bytes.fromhex(valstr[2:]) elif valstr in trueTerms: val = True elif valstr in falseTerms: @@ -94,7 +102,7 @@ def setRouter(interface, on): # FIXME as of 1.1.24 of the device code, the following is all deprecated. After that release # has been out a while, just set is_router and warn the user about deprecation - # + # prefs.is_low_power = True prefs.gps_operation = mesh_pb2.GpsOpMobile @@ -135,17 +143,21 @@ def setRouter(interface, on): prefs.gps_update_interval = 0 -#Returns formatted value +# Returns formatted value def formatFloat(value, formatStr="{:.2f}", unit="", default="N/A"): return formatStr.format(value)+unit if value else default -#Returns Last Heard Time in human readable format +# Returns Last Heard Time in human readable format + + def getLH(ts, default="N/A"): return datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S') if ts else default -#Print Nodes +# Print Nodes + + def printNodes(nodes): - #Create the table and define the structure + # Create the table and define the structure table = EasyTable("Nodes") table.setCorners("/", "\\", "\\", "/") table.setOuterStructure("|", "-") @@ -153,19 +165,20 @@ def printNodes(nodes): tableData = [] for node in nodes: - #aux var to get not defined keys - LH= getLH(node['position'].get("time")) - lat=formatFloat(node['position'].get("latitude"), "{:.4f}", "°") - lon=formatFloat(node['position'].get("longitude"), "{:.4f}", "°") - alt=formatFloat(node['position'].get("altitude"), "{:.0f}", " m") - batt=formatFloat(node['position'].get("batteryLevel"), "{:.2f}", "%") - snr=formatFloat(node.get("snr"), "{:.2f}", " dB") - tableData.append({"User":node['user']['longName'], - "Position":"Lat:"+lat+", Lon:"+lon+", Alt:"+alt, - "Battery":batt, "SNR":snr, "LastHeard":LH}) + # aux var to get not defined keys + LH = getLH(node['position'].get("time")) + lat = formatFloat(node['position'].get("latitude"), "{:.4f}", "°") + lon = formatFloat(node['position'].get("longitude"), "{:.4f}", "°") + alt = formatFloat(node['position'].get("altitude"), "{:.0f}", " m") + batt = formatFloat(node['position'].get("batteryLevel"), "{:.2f}", "%") + snr = formatFloat(node.get("snr"), "{:.2f}", " dB") + tableData.append({"User": node['user']['longName'], + "Position": "Lat:"+lat+", Lon:"+lon+", Alt:"+alt, + "Battery": batt, "SNR": snr, "LastHeard": LH}) table.setData(tableData) table.displayTable() + def onConnected(interface): """Callback invoked when we connect to a radio""" closeNow = False # Should we drop the connection after we finish? @@ -227,7 +240,8 @@ def onConnected(interface): for wrpair in (args.gpiowrb or []): bitmask |= 1 << int(wrpair[0]) bitval |= int(wrpair[1]) << int(wrpair[0]) - print(f"Writing GPIO mask 0x{bitmask:x} with value 0x{bitval:x} to {args.dest}") + print( + f"Writing GPIO mask 0x{bitmask:x} with value 0x{bitval:x} to {args.dest}") rhc.writeGPIOs(args.dest, bitmask, bitval) if args.gpiord: @@ -238,10 +252,10 @@ def onConnected(interface): if args.gpiowatch: bitmask = int(args.gpiowatch) print(f"Watching GPIO mask 0x{bitmask:x} from {args.dest}") - rhc.watchGPIOs(args.dest, bitmask) + rhc.watchGPIOs(args.dest, bitmask) if args.set or args.setstr or args.setchan or args.setch_longslow or args.setch_shortfast \ - or args.seturl or args.router != None: + or args.seturl or args.router != None: closeNow = True def setPref(attributes, name, valStr): @@ -263,7 +277,7 @@ def onConnected(interface): """Set one of the simple modem_config only based channels""" ch = mesh_pb2.ChannelSettings() ch.modem_config = modem_config - ch.psk = bytes([1]) # Use default channel psk 1 + ch.psk = bytes([1]) # Use default channel psk 1 interface.radioConfig.channel_settings.CopyFrom(ch) if args.router != None: @@ -280,10 +294,12 @@ def onConnected(interface): # handle the simple channel set commands if args.setch_longslow: - setSimpleChannel(mesh_pb2.ChannelSettings.ModemConfig.Bw125Cr48Sf4096) + setSimpleChannel( + mesh_pb2.ChannelSettings.ModemConfig.Bw125Cr48Sf4096) if args.setch_shortfast: - setSimpleChannel(mesh_pb2.ChannelSettings.ModemConfig.Bw500Cr45Sf128) + setSimpleChannel( + mesh_pb2.ChannelSettings.ModemConfig.Bw500Cr45Sf128) # Handle the channel settings for pref in (args.setchan or []): @@ -316,9 +332,10 @@ def onConnected(interface): url = pyqrcode.create(interface.channelURL) print(url.terminal()) - if have_tunnel and args.tunnel : + if have_tunnel and args.tunnel: from . import tunnel - closeNow = False # Even if others said we could close, stay open if the user asked for a tunnel + # Even if others said we could close, stay open if the user asked for a tunnel + closeNow = False tunnel.Tunnel(interface, subnet=args.tunnel_net) except Exception as ex: @@ -328,6 +345,7 @@ def onConnected(interface): if (not args.seriallog) and closeNow: interface.close() # after running command then exit + def onNode(node): """Callback invoked when the node DB changes""" print(f"Node changed: {node}") @@ -383,6 +401,7 @@ def common(): client = SerialInterface( args.port, debugOut=logfile, noProto=args.noproto) + def initParser(): global parser, args @@ -403,7 +422,7 @@ def initParser(): parser.add_argument("--info", help="Read and display the radio config information", action="store_true") - parser.add_argument("--nodes", help="Print Node List in a pretty formatted table", + parser.add_argument("--nodes", help="Print Node List in a pretty formatted table", action="store_true") parser.add_argument("--qr", help="Display the QR code that corresponds to the current channel", @@ -439,7 +458,7 @@ def initParser(): parser.add_argument( "--sendping", help="Send a ping message (which requests a reply)", action="store_true") - #parser.add_argument( + # parser.add_argument( # "--repeat", help="Normally the send commands send only one message, use this option to request repeated sends") parser.add_argument( @@ -486,21 +505,24 @@ def initParser(): if have_tunnel: parser.add_argument('--tunnel', - action='store_true', help="Create a TUN tunnel device for forwarding IP packets over the mesh") + action='store_true', help="Create a TUN tunnel device for forwarding IP packets over the mesh") parser.add_argument( "--subnet", dest='tunnel_net', help="Read from a GPIO mask", default=None) parser.set_defaults(router=None) - parser.add_argument('--version', action='version', version=f"{pkg_resources.require('meshtastic')[0].version}") + parser.add_argument('--version', action='version', + version=f"{pkg_resources.require('meshtastic')[0].version}") args = parser.parse_args() + def main(): """Perform command line meshtastic operations""" initParser() common() + def tunnelMain(): """Run a meshtastic IP tunnel""" global args @@ -508,5 +530,6 @@ def tunnelMain(): args.tunnel = True common() + if __name__ == "__main__": main() diff --git a/meshtastic/tunnel.py b/meshtastic/tunnel.py index d897a92..0aa9dc0 100644 --- a/meshtastic/tunnel.py +++ b/meshtastic/tunnel.py @@ -6,7 +6,7 @@ # sudo bin/run.sh --port /dev/ttyUSB0 --setch-shortfast # sudo bin/run.sh --port /dev/ttyUSB0 --tunnel --debug # ssh -Y root@192.168.10.151 (or dietpi), default password p -# ncat -e /bin/cat -k -u -l 1235 +# ncat -e /bin/cat -k -u -l 1235 # ncat -u 10.115.64.152 1235 # ping -c 1 -W 20 10.115.64.152 # ping -i 30 -W 30 10.115.64.152 @@ -15,7 +15,8 @@ from . import portnums_pb2 from pubsub import pub -import logging, threading +import logging +import threading # A new non standard log level that is lower level than DEBUG LOG_TRACE = 5 @@ -26,8 +27,8 @@ tunnelInstance = None """A list of chatty UDP services we should never accidentally forward to our slow network""" udpBlacklist = { - 1900, # SSDP - 5353, # multicast DNS + 1900, # SSDP + 5353, # multicast DNS } """A list of TCP services to block""" @@ -35,31 +36,36 @@ tcpBlacklist = {} """A list of protocols we ignore""" protocolBlacklist = { - 0x02, # IGMP - 0x80, # Service-Specific Connection-Oriented Protocol in a Multilink and Connectionless Environment + 0x02, # IGMP + 0x80, # Service-Specific Connection-Oriented Protocol in a Multilink and Connectionless Environment } + def hexstr(barray): """Print a string of hex digits""" return ":".join('{:02x}'.format(x) for x in barray) + def ipstr(barray): """Print a string of ip digits""" return ".".join('{}'.format(x) for x in barray) + def readnet_u16(p, offset): """Read big endian u16 (network byte order)""" return p[offset] * 256 + p[offset + 1] + def onTunnelReceive(packet, interface): """Callback for received tunneled messages from mesh - + FIXME figure out how to do closures with methods in python""" tunnelInstance.onReceive(packet) + class Tunnel: """A TUN based IP tunnel over meshtastic""" - + def __init__(self, iface, subnet=None, netmask="255.255.0.0"): """ Constructor @@ -77,7 +83,8 @@ class Tunnel: global tunnelInstance tunnelInstance = self - logging.info("Starting IP to mesh tunnel (you must be root for this *pre-alpha* feature to work). Mesh members:") + logging.info( + "Starting IP to mesh tunnel (you must be root for this *pre-alpha* feature to work). Mesh members:") pub.subscribe(onTunnelReceive, "meshtastic.receive.data.IP_TUNNEL_APP") myAddr = self._nodeNumToIp(self.iface.myInfo.my_node_num) @@ -85,24 +92,26 @@ class Tunnel: for node in self.iface.nodes.values(): nodeId = node["user"]["id"] ip = self._nodeNumToIp(node["num"]) - logging.info(f"Node { nodeId } has IP address { ip }") + logging.info(f"Node { nodeId } has IP address { ip }") logging.debug("creating TUN device with MTU=200") # FIXME - figure out real max MTU, it should be 240 - the overhead bytes for SubPacket and Data from pytap2 import TapDevice self.tun = TapDevice(name="mesh") self.tun.up() - self.tun.ifconfig(address=myAddr,netmask=netmask,mtu=200) + self.tun.ifconfig(address=myAddr, netmask=netmask, mtu=200) logging.debug(f"starting TUN reader, our IP address is {myAddr}") - self._rxThread = threading.Thread(target=self.__tunReader, args=(), daemon=True) + self._rxThread = threading.Thread( + target=self.__tunReader, args=(), daemon=True) self._rxThread.start() def onReceive(self, packet): - p = packet["decoded"]["data"]["payload"] + p = packet["decoded"]["payload"] if packet["from"] == self.iface.myInfo.my_node_num: logging.debug("Ignoring message we sent") else: - logging.debug(f"Received mesh tunnel message type={type(p)} len={len(p)}") + logging.debug( + f"Received mesh tunnel message type={type(p)} len={len(p)}") # we don't really need to check for filtering here (sender should have checked), but this provides # useful debug printing on types of packets received if not self._shouldFilterPacket(p): @@ -114,36 +123,43 @@ class Tunnel: srcaddr = p[12:16] destAddr = p[16:20] subheader = 20 - ignore = False # Assume we will be forwarding the packet + ignore = False # Assume we will be forwarding the packet if protocol in protocolBlacklist: ignore = True - logging.log(LOG_TRACE, f"Ignoring blacklisted protocol 0x{protocol:02x}") - elif protocol == 0x01: # ICMP + logging.log( + LOG_TRACE, f"Ignoring blacklisted protocol 0x{protocol:02x}") + elif protocol == 0x01: # ICMP icmpType = p[20] icmpCode = p[21] checksum = p[22:24] - logging.debug(f"forwarding ICMP message src={ipstr(srcaddr)}, dest={ipstr(destAddr)}, type={icmpType}, code={icmpCode}, checksum={checksum}") + logging.debug( + f"forwarding ICMP message src={ipstr(srcaddr)}, dest={ipstr(destAddr)}, type={icmpType}, code={icmpCode}, checksum={checksum}") # reply to pings (swap src and dest but keep rest of packet unchanged) #pingback = p[:12]+p[16:20]+p[12:16]+p[20:] - #tap.write(pingback) - elif protocol == 0x11: # UDP + # tap.write(pingback) + elif protocol == 0x11: # UDP srcport = readnet_u16(p, subheader) destport = readnet_u16(p, subheader + 2) if destport in udpBlacklist: ignore = True - logging.log(LOG_TRACE, f"ignoring blacklisted UDP port {destport}") + logging.log( + LOG_TRACE, f"ignoring blacklisted UDP port {destport}") else: - logging.debug(f"forwarding udp srcport={srcport}, destport={destport}") - elif protocol == 0x06: # TCP + logging.debug( + f"forwarding udp srcport={srcport}, destport={destport}") + elif protocol == 0x06: # TCP srcport = readnet_u16(p, subheader) destport = readnet_u16(p, subheader + 2) if destport in tcpBlacklist: ignore = True - logging.log(LOG_TRACE, f"ignoring blacklisted TCP port {destport}") + logging.log( + LOG_TRACE, f"ignoring blacklisted TCP port {destport}") else: - logging.debug(f"forwarding tcp srcport={srcport}, destport={destport}") + logging.debug( + f"forwarding tcp srcport={srcport}, destport={destport}") else: - logging.warning(f"forwarding unexpected protocol 0x{protocol:02x}, src={ipstr(srcaddr)}, dest={ipstr(destAddr)}") + logging.warning( + f"forwarding unexpected protocol 0x{protocol:02x}, src={ipstr(srcaddr)}, dest={ipstr(destAddr)}") return ignore @@ -179,14 +195,13 @@ class Tunnel: """Forward the provided IP packet into the mesh""" nodeId = self._ipToNodeId(destAddr) if nodeId is not None: - logging.debug(f"Forwarding packet bytelen={len(p)} dest={ipstr(destAddr)}, destNode={nodeId}") - self.iface.sendData(p, nodeId, portnums_pb2.IP_TUNNEL_APP, wantAck = False) + logging.debug( + f"Forwarding packet bytelen={len(p)} dest={ipstr(destAddr)}, destNode={nodeId}") + self.iface.sendData( + p, nodeId, portnums_pb2.IP_TUNNEL_APP, wantAck=False) else: - logging.warning(f"Dropping packet because no node found for destIP={ipstr(destAddr)}") + logging.warning( + f"Dropping packet because no node found for destIP={ipstr(destAddr)}") def close(self): self.tun.close() - - - -