Show HN: Lanet: A tiny tool for secure and easy P2P communication on LANs

2 weeks ago 2

Gem Version Gem Total Downloads MIT License

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):

lanet send --target 192.168.1.5 --message "Signed message" --private-key-file lanet_private.key

Send an encrypted and signed message:

lanet send --target 192.168.1.5 --message "Secure signed message" --key "my_secret_key" --private-key-file lanet_private.key

Broadcast a signed message to all devices:

lanet broadcast --message "Important announcement" --private-key-file lanet_private.key

Receiving and Verifying Signed Messages

Listen for messages and verify signatures:

lanet listen --public-key-file lanet_public.key

Listen for encrypted and signed messages:

lanet listen --encryption-key "my_secret_key" --public-key-file lanet_public.key

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 scan --range 192.168.1.0/24 --verbose

Control scan performance with threads:

lanet scan --range 192.168.1.0/24 --threads 16 --timeout 2

The scanner employs multiple detection methods to find active hosts:

  • TCP port connection attempts
  • ICMP ping requests
  • UDP packet probing
  • ARP table lookups

Verbose scanning provides rich device information:

IP: 192.168.1.1 Hostname: router.home MAC: a4:2b:b0:8a:5c:de Response time: 5.23ms Detection method: TCP Open ports: - 80: HTTP - 443: HTTPS - 22: SSH

Scanning shows real-time progress for tracking large network scans:

Scanning network: 67.5% complete (162/240)

Sending a message to a specific target

lanet send --target 192.168.1.5 --message "Hello there!"

Sending an encrypted message

lanet send --target 192.168.1.5 --message "Secret message" --key "my_secret_key"

Broadcasting a message to all devices

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

Ping continuously with custom timeout:

lanet ping 192.168.1.5 --continuous --timeout 2

Ping multiple hosts continuously:

lanet ping --hosts 192.168.1.5,192.168.1.6 --continuous

Send files with encryption:

lanet send-file --target 192.168.1.5 --file document.pdf --key "my_secret_key"

Send files with encryption and digital signatures:

lanet send-file --target 192.168.1.5 --file document.pdf --key "my_secret_key" --private-key-file lanet_private.key

Listen for incoming files:

lanet receive-file --output ./downloads

Receive encrypted files:

lanet receive-file --output ./downloads --encryption-key "my_secret_key"

Receive encrypted files with signature verification:

lanet receive-file --output ./downloads --encryption-key "my_secret_key" --public-key-file lanet_public.key

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

Trace using ICMP protocol:

lanet traceroute --host google.com --protocol icmp

Trace using TCP protocol:

lanet traceroute --host google.com --protocol tcp --max-hops 15

Customize traceroute parameters:

lanet traceroute --host github.com --protocol tcp --max-hops 20 --timeout 2 --queries 4

You can also use Lanet programmatically in your Ruby applications:

require 'lanet' # Create a scanner and find active devices scanner = Lanet.scanner active_ips = scanner.scan('192.168.1.0/24') puts "Found devices: #{active_ips.join(', ')}" # Send a message to a specific IP sender = Lanet.sender sender.send_to('192.168.1.5', 'Hello from Ruby!') sender.close # Proper cleanup (new in v1.0.0) # Listen for incoming messages receiver = Lanet.receiver receiver.listen do |data, ip| puts "Received from #{ip}: #{data}" end receiver.close # Proper cleanup (new in v1.0.0)
# Scan with verbose option for detailed output detailed_hosts = scanner.scan('192.168.1.0/24', 1, 32, true) detailed_hosts.each do |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(', ')}" if host[:ports] end # Customize scanning performance with timeout and thread count active_ips = scanner.scan('192.168.1.0/24', 0.5, 16) # 0.5 second timeout, 16 threads # Broadcast a message to all devices sender = Lanet.sender sender.broadcast('Announcement to all devices!') sender.close # Work with encrypted messages encrypted = Lanet.encrypt('Secret message', 'my_encryption_key') decrypted = Lanet.decrypt(encrypted, 'my_encryption_key')
# Ping a specific host pinger = Lanet.pinger result = 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 output pinger = Lanet.pinger(timeout: 2, count: 5) result = pinger.ping_host('192.168.1.5', true) # true enables real-time output # Ping continuously until interrupted pinger = Lanet.pinger pinger.ping_host('192.168.1.5', true, true) # true, true enables real-time continuous output # Check if a host is reachable if pinger.reachable?('192.168.1.5') puts "Host is up!" else puts "Host is down!" end # Ping multiple hosts results = pinger.ping_hosts(['192.168.1.5', '192.168.1.6', '192.168.1.7']) results.each do |host, result| status = result[:status] ? "up" : "down" puts "#{host} is #{status}. Response time: #{result[:response_time] || 'N/A'}" end # Ping multiple hosts continuously pinger.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 tracking file_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 handling file_transfer.receive_file('./downloads', 'encryption_key') do |event, data| case event when :start puts "Receiving file: #{data[:file_name]} from #{data[:sender_ip]}" when :progress puts "Progress: #{data[:progress]}%" when :complete puts "File saved to: #{data[:file_path]}" when :error puts "Error: #{data[:error]}" end end
# Mesh Networking mesh = Lanet.mesh_network mesh.start # Start the mesh node and discovery service # Send a message through the mesh network mesh.send_message(target_node_id, "Hello through the mesh!", "optional-encryption-key") # Get info about mesh connections puts "Node ID: #{mesh.node_id}" puts "Connected to #{mesh.connections.size} nodes" mesh.connections.each do |node_id, info| puts " #{node_id} (#{info[:ip]})" end # Check mesh health (new in v1.0.0) if mesh.healthy? stats = mesh.stats puts "Mesh is healthy: #{stats[:connections]} connections, #{stats[:processed_messages]} messages processed" end # Properly stop the mesh node mesh.stop
# Trace route to a host with different protocols tracer = Lanet.traceroute(protocol: :udp) hops = tracer.trace('github.com') hops.each do |hop| puts "Hop #{hop[:ttl]}: #{hop[:ip]} - Response: #{hop[:avg_time]}ms" end # Use TCP protocol with custom parameters tcp_tracer = Lanet.traceroute(protocol: :tcp, max_hops: 15, timeout: 2) tcp_tracer.trace('google.com')

Configuration (New in v1.0.0)

# Access centralized configuration require 'lanet' # View default settings puts "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 logger Lanet::Config.configure do |config| config.logger = Logger.new('lanet.log') config.logger.level = Logger::DEBUG end # Use the configured logger Lanet::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' if Gem::Platform.local.os == 'darwin' class NetworkMonitor def initialize(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']}" end def scan_network puts "\n=== Full Network Scan: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')} ===" results = @scanner.scan(@config['network_range'], 1, 32, true) # Find unexpected devices known_ips = @config['devices'].map { |d| d['ip'] } unknown_devices = results.reject { |host| known_ips.include?(host[:ip]) } if unknown_devices.any? puts "\n⚠️ Unknown devices detected on network:" unknown_devices.each do |device| puts " - IP: #{device[:ip]}, Hostname: #{device[:hostname] || 'unknown'}" end # Alert admin about unknown devices message = "#{unknown_devices.size} unknown devices found on network!" notify_admin(message) end results end def monitor_critical_devices puts "\n=== Checking Critical Devices: #{Time.now.strftime('%H:%M:%S')} ===" @config['devices'].select { |d| d['critical'] == true }.each do |device| result = @pinger.ping_host(device['ip']) current_status = result[:status] if @last_status[device['ip']] != current_status status_changed(device, current_status) end @last_status[device['ip']] = current_status status_text = current_status ? "✅ ONLINE" : "❌ OFFLINE" puts "#{device['name']} (#{device['ip']}): #{status_text}" puts " Response time: #{result[:response_time]}ms" if current_status end end def status_changed(device, new_status) message = if new_status "🟢 #{device['name']} is back ONLINE" else "🔴 ALERT: #{device['name']} (#{device['ip']}) is DOWN!" end puts "\n#{message}\n" notify_admin(message) # Send notification to all network admin devices @config['admin_devices'].each do |admin_device| @sender.send_to(admin_device['ip'], message) end end def notify_admin(message) # Send desktop notification on macOS if Gem::Platform.local.os == 'darwin' TerminalNotifier.notify(message, title: 'Network Monitor Alert') end # You could also add SMS, email, or other notification methods here end def run_continuous_monitoring # Initial full network scan scan_network puts "\nStarting continuous monitoring (press Ctrl+C to stop)..." # Set up a listener for incoming alerts receiver_thread = Thread.new do receiver = Lanet.receiver receiver.listen do |message, source_ip| puts "\n📨 Message from #{source_ip}: #{message}" end end # Main monitoring loop loop do monitor_critical_devices # Full network scan every hour scan_network if Time.now.min == 0 sleep @config['check_interval'] end rescue Interrupt puts "\nMonitoring stopped." ensure receiver_thread.kill if defined?(receiver_thread) && receiver_thread end end # 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' class SecureTeamFileSharing def initialize(team_key, keys_dir = '~/.lanet_keys') @team_key = team_key @keys_dir = File.expand_path(keys_dir) @transfer = Lanet.file_transfer # Ensure keys directory exists FileUtils.mkdir_p(@keys_dir) unless Dir.exist?(@keys_dir) # Generate keys if they don't exist unless File.exist?(private_key_path) && File.exist?(public_key_path) puts "Generating new key pair for secure file sharing..." key_pair = Lanet::Signer.generate_key_pair File.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) end def share_file(target_ip, file_path) unless File.exist?(file_path) puts "Error: File not found: #{file_path}" return false end puts "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)" end puts "\nFile shared successfully!" true rescue StandardError => e puts "\nError sharing file: #{e.message}" false end end def start_receiver(output_dir = './shared_files') FileUtils.mkdir_p(output_dir) unless Dir.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| case event when :start puts "\nIncoming file: #{data[:file_name]} from #{data[:sender_ip]}" puts "Size: #{data[:file_size]} bytes" when :progress print "\rReceiving: #{data[:progress]}% complete" when :complete puts "\nFile received: #{data[:file_path]}" puts "Signature verified: Authentic file from team member" when :error puts "\nError: #{data[:error]}" end end end private def private_key_path File.join(@keys_dir, 'team_private.key') end def public_key_path File.join(@keys_dir, 'team_public.key') end end # 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.

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. 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.

Read Entire Article