diff --git a/src/lan/__init__.py b/src/lan/__init__.py new file mode 100644 index 00000000..621940c4 --- /dev/null +++ b/src/lan/__init__.py @@ -0,0 +1,38 @@ +"""Onionr - Private P2P Communication. + +LAN manager +""" +from typing import TYPE_CHECKING +from threading import Thread +if TYPE_CHECKING: + from toomanyobjs import TooMany + +from . import server +from .client import Client +from .discover import learn_services, advertise_service +""" + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +""" + + +class LANManager: + """Initialize and start/top LAN transport threads.""" + + def __init__(self, too_many: "TooMany"): + self.too_many = too_many + + def start(self): + Thread(target=learn_services, daemon=True).start() + Thread(target=advertise_service, daemon=True).start() + diff --git a/src/lan/client/__init__.py b/src/lan/client/__init__.py new file mode 100644 index 00000000..9c8da42f --- /dev/null +++ b/src/lan/client/__init__.py @@ -0,0 +1,23 @@ +"""Onionr - Private P2P Communication. + +LAN transport client thread +""" +""" + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +""" + + +class Client: + def __init__(self): + return diff --git a/src/lan/discover.py b/src/lan/discover.py new file mode 100644 index 00000000..7d5435de --- /dev/null +++ b/src/lan/discover.py @@ -0,0 +1,65 @@ +"""Onionr - Private P2P Communication. + +Discover and publish private-network +""" +import socket +import struct + +from .getip import lan_ips +from utils.bettersleep import better_sleep +""" + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +""" +MCAST_GRP = '224.13.3.7' +MCAST_PORT = 1337 +IS_ALL_GROUPS = True +ANNOUNCE_LOOP_SLEEP = 30 + + +def learn_services(): + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + if IS_ALL_GROUPS: + # on this port, receives ALL multicast groups + sock.bind(('', MCAST_PORT)) + else: + # on this port, listen ONLY to MCAST_GRP + sock.bind((MCAST_GRP, MCAST_PORT)) + mreq = struct.pack("4sl", socket.inet_aton(MCAST_GRP), socket.INADDR_ANY) + + sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) + + while True: + rec_data = sock.recv(1024) + print('reced_data', rec_data) + + sock.shutdown() + sock.close() + + +def advertise_service(): + # regarding socket.IP_MULTICAST_TTL + # --------------------------------- + # for all packets sent, after three hops on the network the packet will not + # be re-sent/broadcast (see https://www.tldp.org/HOWTO/Multicast-HOWTO-6.html) + MULTICAST_TTL = 3 + ips = '-'.join(lan_ips) + + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) + sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, MULTICAST_TTL) + while True: + sock.sendto(f"onionr-{ips}".encode('utf-8'), (MCAST_GRP, MCAST_PORT)) + better_sleep(ANNOUNCE_LOOP_SLEEP) + sock.shutdown() + sock.close() diff --git a/src/lan/getip.py b/src/lan/getip.py new file mode 100644 index 00000000..f0486bb6 --- /dev/null +++ b/src/lan/getip.py @@ -0,0 +1,19 @@ +from ipaddress import IPv4Address + +from ifaddr import get_adapters + +lan_ips = [] + +for adapter in get_adapters(): + for ip in adapter.ips: + ip = ip.ip + try: + ip = IPv4Address(ip) + if not ip.is_private or ip.is_loopback: + raise ValueError + except ValueError: + # Raised if not ipv4 or not link local + continue + else: + lan_ips.append(ip.exploded) + diff --git a/src/lan/server/__init__.py b/src/lan/server/__init__.py new file mode 100644 index 00000000..4d5b4796 --- /dev/null +++ b/src/lan/server/__init__.py @@ -0,0 +1,18 @@ +"""Onionr - Private P2P Communication. + +LAN transport server thread +""" +""" + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +""" diff --git a/src/onionrcommands/daemonlaunch/__init__.py b/src/onionrcommands/daemonlaunch/__init__.py index 974f4225..d48d0793 100755 --- a/src/onionrcommands/daemonlaunch/__init__.py +++ b/src/onionrcommands/daemonlaunch/__init__.py @@ -36,6 +36,7 @@ from utils.bettersleep import better_sleep from netcontroller.torcontrol.onionservicecreator import create_onion_service from .quotes import QUOTE from utils.boxprint import bordered +from lan import LANManager """ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -162,6 +163,7 @@ def daemon(): events.event('init', threaded=False) events.event('daemon_start') + LANManager(shared_state).start() communicator.startCommunicator(shared_state) clean_ephemeral_services() diff --git a/tests/integration-tests/test_lan_discover.py b/tests/integration-tests/test_lan_discover.py new file mode 100644 index 00000000..ad98e2a9 --- /dev/null +++ b/tests/integration-tests/test_lan_discover.py @@ -0,0 +1,35 @@ +from unittest.mock import patch +import sys, os +sys.path.append(".") +sys.path.append("src/") +import unittest, uuid +TEST_DIR = 'testdata/%s-%s' % (uuid.uuid4(), os.path.basename(__file__)) + '/' +print("Test directory:", TEST_DIR) +os.environ["ONIONR_HOME"] = TEST_DIR +from utils import createdirs +from onionrcommands import parser +import onionrsetup as setup +from netcontroller.torcontrol import customtorrc +from utils import createdirs +from onionrsetup import setup_config, setup_default_plugins +from coredb import blockmetadb +from etc.onionrvalues import BLOCK_EXPORT_FILE_EXT +from threading import Thread + +createdirs.create_dirs() +setup_config() +setup_default_plugins() +import config +from filepaths import export_location + +class OnionrTests(unittest.TestCase): + def test_lan_discover(self): + testargs = ['onionr.py', 'start'] + with patch.object(sys, 'argv', testargs): + try: + Thread(target=parser.register, daemon=True).start() + except SystemExit: + pass + + +unittest.main() diff --git a/tests/test_get_lan_ips.py b/tests/test_get_lan_ips.py new file mode 100644 index 00000000..1a6a2050 --- /dev/null +++ b/tests/test_get_lan_ips.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +import sys, os +sys.path.append(".") +sys.path.append("src/") +import uuid +import ipaddress +TEST_DIR = 'testdata/%s-%s' % (uuid.uuid4(), os.path.basename(__file__)) + '/' +print("Test directory:", TEST_DIR) +os.environ["ONIONR_HOME"] = TEST_DIR +import unittest + +from lan.getip import lan_ips + +class TestGetLanIps(unittest.TestCase): + def test_get_lan_ips(self): + self.assertGreater(len(lan_ips), 0) + for ip in lan_ips: + ip = ipaddress.IPv4Address(ip) + if not ip.is_private or ip.is_multicast or ip.is_reserved: + raise ValueError + + +unittest.main() diff --git a/tests/test_lan_server_thread.py b/tests/test_lan_server_thread.py new file mode 100644 index 00000000..e69de29b