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