Show HN: Lanet: A tiny tool for secure and easy P2P communication on LANs
2 weeks ago
2
A lightweight, powerful LAN communication tool that enables secure message exchange between devices on the same network. Lanet makes peer-to-peer networking simple with built-in encryption, network discovery, and both targeted and broadcast messaging capabilities.
Simple API - An intuitive Ruby interface makes network communication straightforward.
Built-in encryption - Optional message encryption with AES-256-GCM for confidentiality.
Network scanning - Automatically discover active devices on your local network.
Targeted messaging - Send messages to specific IP addresses.
Broadcasting - Send messages to all devices on the network.
Host pinging - Check host availability and measure response times (with a familiar ping interface).
Command-line interface - Perform common network operations directly from your terminal.
Extensible - Easily build custom tools and integrations using the Lanet API.
Configurable: Adjust port settings, encryption keys, and network scan ranges.
Digital Signatures: Ensure message authenticity and integrity
File Transfers: Securely send encrypted files over the LAN with progress tracking and integrity verification
Mesh Networking: Create resilient mesh networks for decentralized communication, enabling messages to be routed through multiple hops without central infrastructure
Advanced Traceroute: Analyze network paths using multiple protocols (ICMP, UDP, and TCP) with intelligent fallback mechanisms
Lanet uses AES-256-CBC encryption to protect the content of messages. This ensures confidentiality during transmission.
Digital signatures provide:
Authentication: Verify the identity of the message sender
Data Integrity: Ensure the message hasn't been tampered with during transit
Non-repudiation: Senders cannot deny sending a message they signed
Add this line to your application's Gemfile:
And then execute:
Or install it yourself as:
Lanet provides a powerful CLI for common network operations:
Generating Signature Keys
Generate a key pair for digital signatures:
Generate a key pair with specific options:
lanet keygen --bits 4096 --output ~/.lanet_keys
The command will generate two files:
lanet_private.key: Keep this secure and don't share it
lanet_public.key: Share this with others who need to verify your messages
Send a digitally signed message (without encryption):
When a signed message is received, the output will show verification status:
Message from 192.168.1.5:
Content: Hello, this is a signed message
Signature: VERIFIED
----------------------------------------
If signature verification fails:
Message from 192.168.1.5:
Content: Hello, this message was tampered with
Signature: NOT VERIFIED: Signature verification failed
----------------------------------------
lanet scan --range 192.168.1.0/24
With verbose output (shows detailed host information):
lanet broadcast --message "Announcement for everyone!"
Listening for incoming messages
Listening for encrypted messages
lanet listen --key "my_secret_key"
You can ping a host using either of these formats:
# Simple format
lanet ping 192.168.1.5
# Option format
lanet ping --host 192.168.1.5
The ping command displays real-time responses just like the standard ping utility:
PING 192.168.1.5 (192.168.1.5): 56 data bytes
64 bytes from 192.168.1.5: icmp_seq=0 ttl=64 time=2.929 ms
64 bytes from 192.168.1.5: icmp_seq=1 ttl=64 time=2.845 ms
64 bytes from 192.168.1.5: icmp_seq=2 ttl=64 time=3.069 ms
64 bytes from 192.168.1.5: icmp_seq=3 ttl=64 time=3.090 ms
64 bytes from 192.168.1.5: icmp_seq=4 ttl=64 time=3.228 ms
--- 192.168.1.5 ping statistics ---
5 packets transmitted, 5 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 2.845/3.032/3.228/0.134 ms
# Option format with multiple hosts
lanet ping --hosts 192.168.1.5,192.168.1.6,192.168.1.7 --timeout 2 --count 5
For only showing ping summaries:
# Simple format with quiet option
lanet ping 192.168.1.5 --quiet
# Option format with quiet option
lanet ping --host 192.168.1.5 --quiet
Continuous ping (like traditional ping)
Ping continuously until manually interrupted (Ctrl+C):
# Simple format with continuous option
lanet ping 192.168.1.5 --continuous
# Option format with continuous option
lanet ping --host 192.168.1.5 --continuous
Create a decentralized mesh network where devices can communicate even without direct connectivity:
# Start a mesh network node
lanet mesh start
# Start a mesh network node with custom settings
lanet mesh start --port 5050 --max-hops 15
Send messages through the mesh network:
# Send a message through the mesh network to a specific node ID
lanet mesh send --target a1b2c3d4-5678-90ef-ghij --message "Hello Mesh Network"# Send an encrypted message through the mesh network
lanet mesh send --target a1b2c3d4-5678-90ef-ghij --message "Secret mesh message" --key "secret-key"# Send a signed message through the mesh network
lanet mesh send --target a1b2c3d4-5678-90ef-ghij --message "Authenticated mesh message" --private-key-file lanet_private.key
View information about your mesh network:
# Display information about mesh network connections
lanet mesh info
Tracing the route to a target host
Basic traceroute using UDP (default protocol):
# Simple format
lanet traceroute google.com
# Option format
lanet traceroute --host google.com
You can also use Lanet programmatically in your Ruby applications:
require'lanet'# Create a scanner and find active devicesscanner=Lanet.scanneractive_ips=scanner.scan('192.168.1.0/24')puts"Found devices: #{active_ips.join(', ')}"# Send a message to a specific IPsender=Lanet.sendersender.send_to('192.168.1.5','Hello from Ruby!')sender.close# Proper cleanup (new in v1.0.0)# Listen for incoming messagesreceiver=Lanet.receiverreceiver.listendo |data,ip|
puts"Received from #{ip}: #{data}"endreceiver.close# Proper cleanup (new in v1.0.0)
# Scan with verbose option for detailed outputdetailed_hosts=scanner.scan('192.168.1.0/24',1,32,true)detailed_hosts.eachdo |host|
puts"Host: #{host[:ip]}, Hostname: #{host[:hostname]}, Response Time: #{host[:response_time]}ms"puts"Open ports: #{host[:ports].map{ |port,service| "#{port} (#{service})"}.join(', ')}"ifhost[:ports]end# Customize scanning performance with timeout and thread countactive_ips=scanner.scan('192.168.1.0/24',0.5,16)# 0.5 second timeout, 16 threads# Broadcast a message to all devicessender=Lanet.sendersender.broadcast('Announcement to all devices!')sender.close# Work with encrypted messagesencrypted=Lanet.encrypt('Secret message','my_encryption_key')decrypted=Lanet.decrypt(encrypted,'my_encryption_key')
# Ping a specific hostpinger=Lanet.pingerresult=pinger.ping_host('192.168.1.5')puts"Host reachable: #{result[:status]}"puts"Response time: #{result[:response_time]}ms"# Ping a specific host with real-time outputpinger=Lanet.pinger(timeout: 2,count: 5)result=pinger.ping_host('192.168.1.5',true)# true enables real-time output# Ping continuously until interruptedpinger=Lanet.pingerpinger.ping_host('192.168.1.5',true,true)# true, true enables real-time continuous output# Check if a host is reachableifpinger.reachable?('192.168.1.5')puts"Host is up!"elseputs"Host is down!"end# Ping multiple hostsresults=pinger.ping_hosts(['192.168.1.5','192.168.1.6','192.168.1.7'])results.eachdo |host,result|
status=result[:status] ? "up" : "down"puts"#{host} is #{status}. Response time: #{result[:response_time] || 'N/A'}"end# Ping multiple hosts continuouslypinger.ping_hosts(['192.168.1.5','192.168.1.6'],true,true)
# Work with secure file transfers (improved in v1.0.0)file_transfer=Lanet.file_transfer# Send file with progress trackingfile_transfer.send_file('192.168.1.5','document.pdf','encryption_key')do |progress,bytes,total|
puts"Progress: #{progress}% (#{bytes}/#{total} bytes)"end# Receive files with event handlingfile_transfer.receive_file('./downloads','encryption_key')do |event,data|
caseeventwhen:startputs"Receiving file: #{data[:file_name]} from #{data[:sender_ip]}"when:progressputs"Progress: #{data[:progress]}%"when:completeputs"File saved to: #{data[:file_path]}"when:errorputs"Error: #{data[:error]}"endend
# Mesh Networkingmesh=Lanet.mesh_networkmesh.start# Start the mesh node and discovery service# Send a message through the mesh networkmesh.send_message(target_node_id,"Hello through the mesh!","optional-encryption-key")# Get info about mesh connectionsputs"Node ID: #{mesh.node_id}"puts"Connected to #{mesh.connections.size} nodes"mesh.connections.eachdo |node_id,info|
puts" #{node_id} (#{info[:ip]})"end# Check mesh health (new in v1.0.0)ifmesh.healthy?stats=mesh.statsputs"Mesh is healthy: #{stats[:connections]} connections, #{stats[:processed_messages]} messages processed"end# Properly stop the mesh nodemesh.stop
# Trace route to a host with different protocolstracer=Lanet.traceroute(protocol: :udp)hops=tracer.trace('github.com')hops.eachdo |hop|
puts"Hop #{hop[:ttl]}: #{hop[:ip]} - Response: #{hop[:avg_time]}ms"end# Use TCP protocol with custom parameterstcp_tracer=Lanet.traceroute(protocol: :tcp,max_hops: 15,timeout: 2)tcp_tracer.trace('google.com')
Configuration (New in v1.0.0)
# Access centralized configurationrequire'lanet'# View default settingsputs"Default port: #{Lanet::Config::DEFAULT_PORT}"puts"File transfer port: #{Lanet::Config::FILE_TRANSFER_PORT}"puts"Mesh port: #{Lanet::Config::MESH_PORT}"puts"Chunk size: #{Lanet::Config::CHUNK_SIZE}"# Configure custom loggerLanet::Config.configuredo |config|
config.logger=Logger.new('lanet.log')config.logger.level=Logger::DEBUGend# Use the configured loggerLanet::Config.logger.info("Starting Lanet application")
The mesh networking feature provides decentralized communication capabilities:
Auto-discovery: Nodes automatically find each other on the network
Multi-hop routing: Messages can be routed through intermediate nodes
Self-healing: Adapts to changing network conditions and lost connections
Store and forward: Messages persist until they can be delivered
End-to-end security: Messages remain encrypted across multiple hops
Verification: Digital signatures ensure message integrity through the mesh
Ideal for:
IoT networks where devices may not have direct connectivity
Ad-hoc networks without fixed infrastructure
Networks requiring high resilience and redundancy
Applications needing peer-to-peer communication
Lanet can be configured with several options:
Port: Default is 5000, but can be changed for both sending and receiving
Encryption Keys: Use your own encryption keys for secure communication
Custom Ranges: Scan specific network ranges to discover devices
Use Case Example: Small Office Network Monitoring
This example demonstrates how Lanet can be used to create a simple network monitoring system for a small office, checking device availability and sending notifications when issues are detected.
require'lanet'require'json'require'terminal-notifier'ifGem::Platform.local.os == 'darwin'classNetworkMonitordefinitialize(config_file='network_config.json')@config=JSON.parse(File.read(config_file))@scanner=Lanet.scanner@sender=Lanet.sender@pinger=Lanet.pinger(timeout: 1,count: 3)@last_status={}puts"Network Monitor initialized for #{@config['network_name']}"puts"Monitoring #{@config['devices'].size} devices on #{@config['network_range']}"enddefscan_networkputs"\n=== Full Network Scan: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')} ==="results=@scanner.scan(@config['network_range'],1,32,true)# Find unexpected devicesknown_ips=@config['devices'].map{ |d| d['ip']}unknown_devices=results.reject{ |host| known_ips.include?(host[:ip])}ifunknown_devices.any?puts"\n⚠️ Unknown devices detected on network:"unknown_devices.eachdo |device|
puts" - IP: #{device[:ip]}, Hostname: #{device[:hostname] || 'unknown'}"end# Alert admin about unknown devicesmessage="#{unknown_devices.size} unknown devices found on network!"notify_admin(message)endresultsenddefmonitor_critical_devicesputs"\n=== Checking Critical Devices: #{Time.now.strftime('%H:%M:%S')} ==="@config['devices'].select{ |d| d['critical'] == true}.eachdo |device|
result=@pinger.ping_host(device['ip'])current_status=result[:status]if@last_status[device['ip']] != current_statusstatus_changed(device,current_status)end@last_status[device['ip']]=current_statusstatus_text=current_status ? "✅ ONLINE" : "❌ OFFLINE"puts"#{device['name']} (#{device['ip']}): #{status_text}"puts" Response time: #{result[:response_time]}ms"ifcurrent_statusendenddefstatus_changed(device,new_status)message=ifnew_status"🟢 #{device['name']} is back ONLINE"else"🔴 ALERT: #{device['name']} (#{device['ip']}) is DOWN!"endputs"\n#{message}\n"notify_admin(message)# Send notification to all network admin devices@config['admin_devices'].eachdo |admin_device|
@sender.send_to(admin_device['ip'],message)endenddefnotify_admin(message)# Send desktop notification on macOSifGem::Platform.local.os == 'darwin'TerminalNotifier.notify(message,title: 'Network Monitor Alert')end# You could also add SMS, email, or other notification methods hereenddefrun_continuous_monitoring# Initial full network scanscan_networkputs"\nStarting continuous monitoring (press Ctrl+C to stop)..."# Set up a listener for incoming alertsreceiver_thread=Thread.newdoreceiver=Lanet.receiverreceiver.listendo |message,source_ip|
puts"\n📨 Message from #{source_ip}: #{message}"endend# Main monitoring looploopdomonitor_critical_devices# Full network scan every hourscan_networkifTime.now.min == 0sleep@config['check_interval']endrescueInterruptputs"\nMonitoring stopped."ensurereceiver_thread.killifdefined?(receiver_thread) && receiver_threadendend# Example configuration file (network_config.json):# {# "network_name": "Office Network",# "network_range": "192.168.1.0/24",# "check_interval": 300,# "devices": [# {"name": "Router", "ip": "192.168.1.1", "critical": true},# {"name": "File Server", "ip": "192.168.1.10", "critical": true},# {"name": "Printer", "ip": "192.168.1.20", "critical": false}# ],# "admin_devices": [# {"name": "IT Manager Laptop", "ip": "192.168.1.100"}# ]# }# Usage:# monitor = NetworkMonitor.new('network_config.json')# monitor.run_continuous_monitoring
Use Case Example: Securely Sharing Files in a Team Environment
This example demonstrates how to use Lanet's file transfer capabilities to securely share files within a team:
require'lanet'require'fileutils'classSecureTeamFileSharingdefinitialize(team_key,keys_dir='~/.lanet_keys')@team_key=team_key@keys_dir=File.expand_path(keys_dir)@transfer=Lanet.file_transfer# Ensure keys directory existsFileUtils.mkdir_p(@keys_dir)unlessDir.exist?(@keys_dir)# Generate keys if they don't existunlessFile.exist?(private_key_path) && File.exist?(public_key_path)puts"Generating new key pair for secure file sharing..."key_pair=Lanet::Signer.generate_key_pairFile.write(private_key_path,key_pair[:private_key])File.write(public_key_path,key_pair[:public_key])puts"Keys generated successfully."end# Load the private key@private_key=File.read(private_key_path)enddefshare_file(target_ip,file_path)unlessFile.exist?(file_path)puts"Error: File not found: #{file_path}"returnfalseendputs"Sharing file: #{File.basename(file_path)} (#{File.size(file_path)} bytes)"puts"Target: #{target_ip}"puts"Security: Encrypted with team key and digitally signed"begin@transfer.send_file(target_ip,file_path,@team_key,@private_key)do |progress,bytes,total|
print"\rProgress: #{progress}% (#{bytes}/#{total} bytes)"endputs"\nFile shared successfully!"truerescueStandardError=>eputs"\nError sharing file: #{e.message}"falseendenddefstart_receiver(output_dir='./shared_files')FileUtils.mkdir_p(output_dir)unlessDir.exist?(output_dir)puts"Listening for incoming files..."puts"Files will be saved to: #{output_dir}"@transfer.receive_file(output_dir,@team_key,File.read(public_key_path))do |event,data|
caseeventwhen:startputs"\nIncoming file: #{data[:file_name]} from #{data[:sender_ip]}"puts"Size: #{data[:file_size]} bytes"when:progressprint"\rReceiving: #{data[:progress]}% complete"when:completeputs"\nFile received: #{data[:file_path]}"puts"Signature verified: Authentic file from team member"when:errorputs"\nError: #{data[:error]}"endendendprivatedefprivate_key_pathFile.join(@keys_dir,'team_private.key')enddefpublic_key_pathFile.join(@keys_dir,'team_public.key')endend# Usage:# sharing = SecureTeamFileSharing.new("team-secret-key-123")## To share a file:# sharing.share_file("192.168.1.5", "important_document.pdf")## To receive files:# sharing.start_receiver("./team_files")
This example:
Creates a secure file sharing system with end-to-end encryption
Uses team-wide encryption key for confidentiality
Implements digital signatures to verify file authenticity
Provides real-time progress tracking for both sending and receiving files
Handles errors gracefully with user-friendly messages
After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.
Bug reports and pull requests are welcome on GitHub at https://github.com/davidesantangelo/lanet. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
Fork it
Create your feature branch (git checkout -b my-new-feature)
Commit your changes (git commit -am 'Add some feature')
Push to the branch (git push origin my-new-feature)
Create new Pull Request
The gem is available as open source under the terms of the MIT License.
Everyone interacting in the Lanet project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.