add beginnings of tunnel code #35

This commit is contained in:
Kevin Hester
2020-12-23 12:05:08 +08:00
parent 976d27d0e2
commit 8c4d48b956
4 changed files with 150 additions and 4 deletions

8
.vscode/launch.json vendored
View File

@@ -20,6 +20,14 @@
"justMyCode": true,
"args": ["--info"]
},
{
"name": "meshtastic tunnel",
"type": "python",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
"args": ["--tunnel", "--debug"]
},
{
"name": "meshtastic debug",
"type": "python",

View File

@@ -1,7 +1,7 @@
#!python3
import argparse
from . import SerialInterface, TCPInterface, BLEInterface, test, remote_hardware
from . import SerialInterface, TCPInterface, BLEInterface, test, remote_hardware, tunnel
import logging
import sys
from pubsub import pub
@@ -274,6 +274,11 @@ def onConnected(interface):
print(f"Channel URL {interface.channelURL}")
url = pyqrcode.create(interface.channelURL)
print(url.terminal())
if args.tunnel:
closeNow = False # Even if others said we could close, stay open if the user asked for a tunnel
tunnel.Tunnel(interface)
except Exception as ex:
print(ex)
@@ -394,6 +399,9 @@ def main():
parser.add_argument('--unset-router', dest='router',
action='store_false', help="Turns off router mode")
parser.add_argument('--tunnel',
action='store_true', help="Create a TUN tunnel device for forwarding IP packets over the mesh")
parser.set_defaults(router=None)
parser.add_argument('--version', action='version', version=f"{pkg_resources.require('meshtastic')[0].version}")
@@ -411,7 +419,7 @@ def main():
if args.info or args.set or args.seturl or args.setowner or args.setlat or args.setlon or \
args.settime or \
args.setch_longslow or args.setch_shortfast or args.setstr or args.setchan or args.sendtext or \
args.router != None or args.qr:
args.tunnel or args.router != None or args.qr:
args.seriallog = "none" # assume no debug output in this case
else:
args.seriallog = "stdout" # default to stdout

124
meshtastic/tunnel.py Normal file
View File

@@ -0,0 +1,124 @@
# code for IP tunnel over a mesh
# Note python-pytuntap was too buggy
# using pip3 install pytap2
# make sure to "sudo setcap cap_net_admin+eip /usr/bin/python3.8" so python can access tun device without being root
# sudo ip tuntap del mode tun tun0
# FIXME: set MTU correctly
# select local ip address based on nodeid
# print known node ids as IP addresses
# change dev name to mesh
from . import portnums_pb2
from pubsub import pub
from pytap2 import TapDevice
import logging
import threading
"""A list of chatty UDP services we should never accidentally
forward to our slow network"""
udpBlacklist = {
1900, # SSDP
5353, # multicast DNS
}
"""A list of TCP services to block"""
tcpBlacklist = {}
"""A list of protocols we ignore"""
protocolBlacklist = {
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"""
p = packet["decoded"]["data"]["payload"]
logging.debug(f"Received tunnel message")
class Tunnel:
"""A TUN based IP tunnel over meshtastic"""
def __init__(self, iface):
"""
Constructor
iface is the already open MeshInterface instance
"""
self.iface = iface
logging.info("Starting IP to mesh tunnel (you must be root for this pre-alpha feature to work)")
pub.subscribe(onTunnelReceive, "meshtastic.receive.data.IP_TUNNEL_APP")
logging.debug("creating TUN device")
self.tun = TapDevice(mtu=200)
# tun.create()
self.tun.up()
self.tun.ifconfig(address="10.115.1.2",netmask="255.255.0.0")
logging.debug("starting TUN reader")
self._rxThread = threading.Thread(target=self.__tunReader, args=(), daemon=True)
self._rxThread.start()
def __tunReader(self):
tap = self.tun
logging.debug("TUN reader running")
while True:
p = tap.read()
protocol = p[8 + 1]
srcaddr = p[12:16]
destaddr = p[16:20]
subheader = 20
ignore = False # Assume we will be forwarding the packet
if protocol in protocolBlacklist:
ignore = True
logging.debug(f"Ignoring blacklisted protocol 0x{protocol:02x}")
elif protocol == 0x01: # ICMP
logging.debug("forwarding ICMP message")
# 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
srcport = readnet_u16(p, subheader)
destport = readnet_u16(p, subheader + 2)
logging.debug(f"udp srcport={srcport}, destport={destport}")
if destport in udpBlacklist:
ignore = True
logging.debug(f"ignoring blacklisted UDP port {destport}")
elif protocol == 0x06: # TCP
srcport = readnet_u16(p, subheader)
destport = readnet_u16(p, subheader + 2)
logging.debug(f"tcp srcport={srcport}, destport={destport}")
if destport in tcpBlacklist:
ignore = True
logging.debug(f"ignoring blacklisted TCP port {destport}")
else:
logging.warning(f"unexpected protocol 0x{protocol:02x}, src={ipstr(srcaddr)}, dest={ipstr(destaddr)}")
if not ignore:
logging.debug(f"Forwarding packet bytelen={len(p)} src={ipstr(srcaddr)}, dest={ipstr(destaddr)}")
def close(self):
self.tun.close()

View File

@@ -1,6 +1,7 @@
# delete me eventually
# Note python-pytuntap was too buggy
# using pip3 install pytap2
# make sure to "sudo setcap cap_net_admin+eip /usr/bin/python3.8" so python can access tun device without being root
# sudo ip tuntap del mode tun tun0
# FIXME: set MTU correctly
@@ -28,8 +29,13 @@ protocolBlacklist = {
}
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]
@@ -66,10 +72,10 @@ def readtest(tap):
ignore = True
logging.debug(f"ignoring blacklisted TCP port {destport}")
else:
logging.warning(f"unexpected protocol 0x{protocol:02x}, srcadddr {hexstr(srcaddr)}")
logging.warning(f"unexpected protocol 0x{protocol:02x}, src={ipstr(srcaddr)}, dest={ipstr(destaddr)}")
if not ignore:
logging.debug(f"Forwarding packet bytes={hexstr(p)}")
logging.debug(f"Forwarding packet bytelen={len(p)} src={ipstr(srcaddr)}, dest={ipstr(destaddr)}")