Project

lanet

0.01
The project is in a healthy, maintained state
Lanet provides a simple yet powerful API for LAN device discovery, secure messaging, and real-time network monitoring. Features include encrypted communications, network scanning, targeted and broadcast messaging, and host pinging capabilities.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies

Development

~> 2.0
~> 13.0
~> 3.0

Runtime

~> 1.2
 Project Readme

Lanet

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.

Features

  • 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

Security Features

Encryption

Lanet uses AES-256-CBC encryption to protect the content of messages. This ensures confidentiality during transmission.

Digital Signatures

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

Installation

Add this line to your application's Gemfile:

gem 'lanet'

And then execute:

bundle install

Or install it yourself as:

gem install lanet

Usage

Command Line Interface

Lanet provides a powerful CLI for common network operations:

Generating Signature Keys

Generate a key pair for digital signatures:

lanet keygen

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

Sending Signed 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
----------------------------------------

Scanning the network

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

lanet listen

Listening for encrypted messages

lanet listen --key "my_secret_key"

Pinging a specific host

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

Pinging multiple hosts

# 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

Sending Files Securely

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

Receiving Files

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

Mesh Networking

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

Ruby API

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(', ')}"

# 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

# Send a message to a specific IP
sender = Lanet.sender
sender.send_to('192.168.1.5', 'Hello from Ruby!')

# Broadcast a message to all devices
sender.broadcast('Announcement to all devices!')

# Listen for incoming messages
receiver = Lanet.receiver
receiver.listen do |data, ip|
  puts "Received from #{ip}: #{data}"
end

# 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

# Ping without real-time output (for programmatic use)
result = pinger.ping_host('192.168.1.5')
puts "Host reachable: #{result[:status]}"
puts "Response time: #{result[:response_time]}ms"

# 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
file_transfer = Lanet.file_transfer
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
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]}"
  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

# 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')

Mesh Network

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

Configuration

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

Development

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.

Contributing

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

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the Lanet project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.