lanet 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 46d63ec0fb7276e1741780b97152879fb319bf42c0fd37c079b18faf355170ae
4
- data.tar.gz: c7d07927e338e9ce4248c6909c2ec65903d23af9f09da94cf837181d14ac5ed0
3
+ metadata.gz: aaec61e714b22eea7e3e40e66b6250672b93d15539f6645bc2ded8ea4c282458
4
+ data.tar.gz: 8f906d539e9878b418b156f7e735898dc04e9d05924ade44bcee23729474907d
5
5
  SHA512:
6
- metadata.gz: '018bc023c9d38cce80a28495df69d6ae66aeaa0977862fbab115e4eb77340337366b615ce73041b41f416c10baa4905da00ca4966008875553a1b881b8ee3678'
7
- data.tar.gz: 4f173fb766cb88c84340c84327db204853328016be6a50cd4ea3cd183501aaabfd7f1019ba306d79e110daa5b0a6d1fe0a2a57d2bf8f0f1fec8beb2e54c52cc0
6
+ metadata.gz: d33ad462b0f69392f3f4fe4ce44ef405d2a734acf26f6ce892b3b1545f0dacf99cb545bbbc39ce7d2697ec89989ff1ec843e47f74943178faf66b4a284e51888
7
+ data.tar.gz: 73a1f2985e2592931ff80588358b5e83e10d99da077e7001e6ec12b614d37f30af0c7a2f2546f83382133a61c6b2e5d4ec1961edb02fa4d8803b359efd48e9a0
data/CHANGELOG.md CHANGED
@@ -5,7 +5,14 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [0.2.0] - 2025-03-08
8
+ ## [0.2.1] - 2025-03-07
9
+
10
+ ### Fixed
11
+ - Fixed thread handling in Scanner class to properly handle thread termination
12
+ - Improved test coverage for thread management in scanner specs
13
+ - Resolved issue with ARP updater thread cleanup
14
+
15
+ ## [0.2.0] - 2025-03-07
9
16
 
10
17
  ### Added
11
18
  - Digital signature support for message authentication and integrity
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- lanet (0.2.0)
4
+ lanet (0.2.1)
5
5
  thor (~> 1.2)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Lanet
2
2
 
3
- ![Gem Version](https://img.shields.io/gem/v/lanet?style=flat)
4
- ![Gem Total Downloads](https://img.shields.io/gem/dt/lanet?style=flat)
3
+ [![Gem Version](https://img.shields.io/gem/v/lanet?style=flat)](https://rubygems.org/gems/lanet)
4
+ [![Gem Total Downloads](https://img.shields.io/gem/dt/lanet?style=flat)](https://rubygems.org/gems/lanet)
5
5
  [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)
6
6
 
7
7
  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.
data/index.html CHANGED
@@ -9,9 +9,10 @@
9
9
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
10
10
  line-height: 1.6;
11
11
  color: #333;
12
- max-width: 800px;
12
+ max-width: 900px;
13
13
  margin: 0 auto;
14
14
  padding: 20px;
15
+ background-color: #f9f9f9;
15
16
  }
16
17
  h1 {
17
18
  color: #2c3e50;
@@ -22,21 +23,30 @@
22
23
  color: #2980b9;
23
24
  margin-top: 30px;
24
25
  }
26
+ h3 {
27
+ color: #16a085;
28
+ margin-top: 25px;
29
+ }
30
+ h4 {
31
+ color: #27ae60;
32
+ margin-top: 20px;
33
+ }
25
34
  pre {
26
35
  background-color: #f8f8f8;
27
36
  border: 1px solid #ddd;
28
37
  border-left: 3px solid #3498db;
29
38
  padding: 15px;
30
39
  overflow-x: auto;
40
+ border-radius: 4px;
31
41
  }
32
42
  code {
33
43
  font-family: 'Courier New', Courier, monospace;
34
44
  }
35
45
  .container {
36
46
  background-color: #fff;
37
- border-radius: 5px;
38
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
39
- padding: 20px;
47
+ border-radius: 8px;
48
+ box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
49
+ padding: 30px;
40
50
  }
41
51
  .feature {
42
52
  margin: 20px 0;
@@ -47,6 +57,82 @@
47
57
  background-color: #e8f4fc;
48
58
  padding: 15px;
49
59
  margin: 10px 0;
60
+ border-radius: 6px;
61
+ }
62
+ .cli-example {
63
+ background-color: #2c3e50;
64
+ color: #ecf0f1;
65
+ padding: 12px 18px;
66
+ margin: 10px 0;
67
+ border-radius: 6px;
68
+ font-family: 'Courier New', Courier, monospace;
69
+ position: relative;
70
+ }
71
+ .cli-example::before {
72
+ content: "$";
73
+ color: #3498db;
74
+ margin-right: 10px;
75
+ font-weight: bold;
76
+ }
77
+ .cli-section {
78
+ margin-bottom: 30px;
79
+ background-color: #f8f9fa;
80
+ padding: 20px;
81
+ border-radius: 6px;
82
+ }
83
+ .output-example {
84
+ background-color: #f0f0f0;
85
+ padding: 12px;
86
+ margin: 10px 0;
87
+ border-radius: 6px;
88
+ font-family: 'Courier New', Courier, monospace;
89
+ font-size: 0.9em;
90
+ border-left: 3px solid #9b59b6;
91
+ }
92
+ .tab-container {
93
+ border: 1px solid #ddd;
94
+ border-radius: 6px;
95
+ overflow: hidden;
96
+ margin: 20px 0;
97
+ }
98
+ .tab-buttons {
99
+ display: flex;
100
+ background-color: #f5f5f5;
101
+ }
102
+ .tab-button {
103
+ padding: 10px 20px;
104
+ background-color: transparent;
105
+ border: none;
106
+ cursor: pointer;
107
+ border-right: 1px solid #ddd;
108
+ transition: background-color 0.3s;
109
+ }
110
+ .tab-button:hover, .tab-button.active {
111
+ background-color: #e0e0e0;
112
+ }
113
+ .tab-content {
114
+ display: none;
115
+ padding: 15px;
116
+ }
117
+ .tab-content.active {
118
+ display: block;
119
+ }
120
+ .badge {
121
+ display: inline-block;
122
+ padding: 3px 8px;
123
+ border-radius: 4px;
124
+ font-size: 12px;
125
+ font-weight: bold;
126
+ }
127
+ .badge-new {
128
+ background-color: #2ecc71;
129
+ color: white;
130
+ }
131
+ .note {
132
+ background-color: #fff8e1;
133
+ padding: 12px;
134
+ border-left: 4px solid #ffc107;
135
+ margin: 10px 0;
50
136
  border-radius: 4px;
51
137
  }
52
138
  </style>
@@ -54,17 +140,17 @@
54
140
  <body>
55
141
  <div class="container">
56
142
  <h1>Lanet</h1>
57
- <p>A secure network communication library for Ruby applications.</p>
143
+ <p>A secure network communication library that enables reliable and protected message exchange between devices on the same network.</p>
58
144
 
59
145
  <h2>Key Features</h2>
60
146
 
61
147
  <div class="feature">
62
148
  <h3>Encrypted Communication</h3>
63
- <p>Protect your messages with military-grade AES-256-CBC encryption to ensure confidentiality.</p>
149
+ <p>Protect your messages with AES-256-CBC encryption to ensure confidentiality during transmission.</p>
64
150
  </div>
65
151
 
66
152
  <div class="feature">
67
- <h3>Digital Signatures</h3>
153
+ <h3>Digital Signatures <span class="badge badge-new">New in v0.2.0</span></h3>
68
154
  <p>Verify message authenticity and integrity with RSA-based digital signatures.</p>
69
155
 
70
156
  <div class="security-feature">
@@ -79,18 +165,166 @@
79
165
  </div>
80
166
 
81
167
  <div class="feature">
82
- <h3>Simple UDP-based Messaging</h3>
83
- <p>Send and receive messages easily with straightforward sender and receiver classes.</p>
168
+ <h3>Network Discovery</h3>
169
+ <p>Automatically find active devices on your local network with advanced scanning capabilities.</p>
84
170
  </div>
85
171
 
86
172
  <div class="feature">
87
- <h3>Network Broadcasting</h3>
88
- <p>Support for broadcasting messages across your entire network.</p>
173
+ <h3>Broadcasting</h3>
174
+ <p>Send messages to all devices on your network simultaneously.</p>
175
+ </div>
176
+
177
+ <h2>Command Line Interface</h2>
178
+
179
+ <div class="note">
180
+ <strong>Note:</strong> All Lanet commands have detailed help available. Try <code>lanet [command] --help</code> for more options.
89
181
  </div>
90
182
 
91
- <h2>Quick Example</h2>
183
+ <div class="cli-section">
184
+ <h3>Digital Signature Commands <span class="badge badge-new">New in v0.2.0</span></h3>
185
+
186
+ <h4>Generate a Key Pair</h4>
187
+ <p>Create RSA keys for signing and verifying messages:</p>
188
+ <div class="cli-example">
189
+ lanet keygen
190
+ </div>
191
+
192
+ <p>Generate keys with custom options:</p>
193
+ <div class="cli-example">
194
+ lanet keygen --bits 4096 --output ~/.lanet_keys
195
+ </div>
196
+
197
+ <div class="output-example">
198
+ Key pair generated!
199
+ Private key saved to: /home/user/.lanet_keys/lanet_private.key
200
+ Public key saved to: /home/user/.lanet_keys/lanet_public.key
201
+
202
+ IMPORTANT: Keep your private key secure and never share it.
203
+ Share your public key with others who need to verify your messages.
204
+ </div>
205
+
206
+ <h4>Send a Signed Message</h4>
207
+ <div class="cli-example">
208
+ lanet send --target 192.168.1.5 --message "Signed message" --private-key-file lanet_private.key
209
+ </div>
210
+
211
+ <h4>Send a Signed & Encrypted Message</h4>
212
+ <div class="cli-example">
213
+ lanet send --target 192.168.1.5 --message "Secure signed message" --key "my_secret_key" --private-key-file lanet_private.key
214
+ </div>
215
+
216
+ <h4>Broadcast a Signed Message</h4>
217
+ <div class="cli-example">
218
+ lanet broadcast --message "Important announcement" --private-key-file lanet_private.key
219
+ </div>
220
+
221
+ <h4>Listen for and Verify Signed Messages</h4>
222
+ <div class="cli-example">
223
+ lanet listen --public-key-file lanet_public.key
224
+ </div>
225
+
226
+ <h4>Listen for Encrypted & Signed Messages</h4>
227
+ <div class="cli-example">
228
+ lanet listen --encryption-key "my_secret_key" --public-key-file lanet_public.key
229
+ </div>
230
+
231
+ <p>Example output when receiving a signed message:</p>
232
+ <div class="output-example">
233
+ Message from 192.168.1.5:
234
+ Content: Hello, this is a signed message
235
+ Signature: ✓ VERIFIED
236
+ ----------------------------------------
237
+ </div>
238
+ </div>
239
+
240
+ <div class="cli-section">
241
+ <h3>Basic Message Commands</h3>
242
+
243
+ <h4>Send a Message</h4>
244
+ <div class="cli-example">
245
+ lanet send --target 192.168.1.5 --message "Hello there!"
246
+ </div>
247
+
248
+ <h4>Send an Encrypted Message</h4>
249
+ <div class="cli-example">
250
+ lanet send --target 192.168.1.5 --message "Secret message" --key "my_secret_key"
251
+ </div>
252
+
253
+ <h4>Broadcast a Message</h4>
254
+ <div class="cli-example">
255
+ lanet broadcast --message "Announcement for everyone!"
256
+ </div>
257
+
258
+ <h4>Listen for Messages</h4>
259
+ <div class="cli-example">
260
+ lanet listen
261
+ </div>
262
+
263
+ <h4>Listen for Encrypted Messages</h4>
264
+ <div class="cli-example">
265
+ lanet listen --encryption-key "my_secret_key"
266
+ </div>
267
+ </div>
268
+
269
+ <div class="cli-section">
270
+ <h3>Network Discovery Commands</h3>
271
+
272
+ <h4>Scan Network</h4>
273
+ <div class="cli-example">
274
+ lanet scan --range 192.168.1.0/24
275
+ </div>
276
+
277
+ <h4>Detailed Network Scan</h4>
278
+ <div class="cli-example">
279
+ lanet scan --range 192.168.1.0/24 --verbose --threads 16 --timeout 2
280
+ </div>
281
+
282
+ <h4>Ping a Host</h4>
283
+ <div class="cli-example">
284
+ lanet ping 192.168.1.5
285
+ </div>
286
+
287
+ <h4>Continuous Ping</h4>
288
+ <div class="cli-example">
289
+ lanet ping 192.168.1.5 --continuous
290
+ </div>
291
+
292
+ <h4>Ping Multiple Hosts</h4>
293
+ <div class="cli-example">
294
+ lanet ping --hosts 192.168.1.5,192.168.1.6,192.168.1.7 --count 5
295
+ </div>
296
+ </div>
92
297
 
93
- <pre><code>require 'lanet'
298
+ <h2>Ruby Code Examples</h2>
299
+
300
+ <div class="tab-container">
301
+ <div class="tab-buttons">
302
+ <button class="tab-button active" onclick="openTab(event, 'tab-basic')">Basic Usage</button>
303
+ <button class="tab-button" onclick="openTab(event, 'tab-signatures')">Digital Signatures</button>
304
+ <button class="tab-button" onclick="openTab(event, 'tab-advanced')">Advanced Usage</button>
305
+ </div>
306
+
307
+ <div id="tab-basic" class="tab-content active">
308
+ <pre><code>require 'lanet'
309
+
310
+ # Send a message to a specific IP
311
+ sender = Lanet::Sender.new(5000)
312
+ sender.send_to('192.168.1.5', 'Hello from Ruby!')
313
+
314
+ # Listen for incoming messages
315
+ receiver = Lanet::Receiver.new(5000)
316
+ receiver.listen do |data, ip|
317
+ puts "Received from #{ip}: #{data}"
318
+ end
319
+
320
+ # Work with encrypted messages
321
+ encrypted = Lanet::Encryptor.prepare_message('Secret message', 'my_encryption_key')
322
+ # Send the encrypted message
323
+ sender.send_to('192.168.1.5', encrypted)</code></pre>
324
+ </div>
325
+
326
+ <div id="tab-signatures" class="tab-content">
327
+ <pre><code>require 'lanet'
94
328
 
95
329
  # Generate RSA key pair for signing
96
330
  key_pair = Lanet::Signer.generate_key_pair
@@ -121,7 +355,46 @@ receiver.listen do |data, sender_ip|
121
355
 
122
356
  puts "Message: #{result[:content]}"
123
357
  puts "Verified: #{result[:verified]}"
358
+ puts "Verification status: #{result[:verification_status]}"
124
359
  end</code></pre>
360
+ </div>
361
+
362
+ <div id="tab-advanced" class="tab-content">
363
+ <pre><code>require 'lanet'
364
+
365
+ # Create a scanner and find active devices
366
+ scanner = Lanet::Scanner.new
367
+ active_hosts = scanner.scan('192.168.1.0/24', 1, 32, true)
368
+
369
+ active_hosts.each do |host|
370
+ puts "Host: #{host[:ip]}, Hostname: #{host[:hostname]}"
371
+ puts "Response time: #{host[:response_time]}ms"
372
+
373
+ if host[:ports]
374
+ puts "Open ports:"
375
+ host[:ports].each do |port, service|
376
+ puts " - #{port}: #{service}"
377
+ end
378
+ end
379
+ end
380
+
381
+ # Ping a specific host
382
+ pinger = Lanet::Ping.new
383
+ result = pinger.ping_host('192.168.1.5', true)
384
+ puts "Host reachable: #{result[:status]}"
385
+ puts "Response time: #{result[:response_time]}ms"
386
+
387
+ # Broadcast a signed and encrypted message
388
+ encryption_key = "network-shared-key"
389
+ message = "Important system notification"
390
+ signed_message = Lanet::Encryptor.prepare_message(
391
+ message,
392
+ encryption_key,
393
+ private_key
394
+ )
395
+ sender.broadcast(signed_message)</code></pre>
396
+ </div>
397
+ </div>
125
398
 
126
399
  <h2>Installation</h2>
127
400
 
@@ -132,11 +405,30 @@ end</code></pre>
132
405
  <pre><code>gem install lanet</code></pre>
133
406
 
134
407
  <h2>Documentation</h2>
135
- <p>For complete documentation, please visit the <a href="https://github.com/yourusername/lanet">GitHub repository</a>.</p>
408
+ <p>For complete documentation, please visit the <a href="https://github.com/davidesantangelo/lanet">GitHub repository</a>.</p>
136
409
 
137
410
  <footer style="margin-top: 40px; text-align: center; color: #7f8c8d;">
138
- <p>Lanet - Secure Network Communications Library</p>
411
+ <p>Lanet v0.2.0 - Secure Network Communications Library</p>
139
412
  </footer>
140
413
  </div>
414
+
415
+ <script>
416
+ function openTab(evt, tabName) {
417
+ var i, tabContent, tabButtons;
418
+
419
+ tabContent = document.getElementsByClassName("tab-content");
420
+ for (i = 0; i < tabContent.length; i++) {
421
+ tabContent[i].className = tabContent[i].className.replace(" active", "");
422
+ }
423
+
424
+ tabButtons = document.getElementsByClassName("tab-button");
425
+ for (i = 0; i < tabButtons.length; i++) {
426
+ tabButtons[i].className = tabButtons[i].className.replace(" active", "");
427
+ }
428
+
429
+ document.getElementById(tabName).className += " active";
430
+ evt.currentTarget.className += " active";
431
+ }
432
+ </script>
141
433
  </body>
142
- </html>
434
+ </html>
data/lib/lanet/scanner.rb CHANGED
@@ -25,55 +25,54 @@ module Lanet
25
25
  8443 => "HTTPS-ALT"
26
26
  }.freeze
27
27
 
28
- # Ports to check during scan
29
28
  QUICK_CHECK_PORTS = [80, 443, 22, 445, 139, 8080].freeze
30
29
 
31
30
  def initialize
32
31
  @hosts = []
33
32
  @mutex = Mutex.new
33
+ @arp_cache = {}
34
34
  end
35
35
 
36
- # Scan network and return active hosts
37
36
  def scan(cidr, timeout = 1, max_threads = 32, verbose = false)
38
37
  @verbose = verbose
39
38
  @timeout = timeout
40
-
41
- # Clear previous scan results
42
39
  @hosts = []
43
-
44
- # Get the range of IP addresses to scan
45
40
  range = IPAddr.new(cidr).to_range
46
-
47
- # Create a queue of IPs to scan
48
41
  queue = Queue.new
49
42
  range.each { |ip| queue << ip.to_s }
50
-
51
43
  total_ips = queue.size
52
44
  completed = 0
53
45
 
54
- # Pre-populate ARP table to improve MAC address resolution
55
- update_arp_table(cidr, max_threads)
46
+ # Initial ARP cache population
47
+ @arp_cache = parse_arp_table
56
48
 
57
- # Create worker threads to process the queue
58
49
  threads = Array.new([max_threads, total_ips].min) do
59
50
  Thread.new do
60
- while (ip = begin
61
- queue.pop(true)
62
- rescue ThreadError
63
- nil
64
- end)
51
+ loop do
52
+ begin
53
+ ip = queue.pop(true)
54
+ rescue ThreadError
55
+ break
56
+ end
65
57
  scan_host(ip)
66
58
  @mutex.synchronize do
67
59
  completed += 1
68
60
  if total_ips < 100 || (completed % 10).zero? || completed == total_ips
69
- print_progress(completed,
70
- total_ips)
61
+ print_progress(completed, total_ips)
71
62
  end
72
63
  end
73
64
  end
74
65
  end
75
66
  end
76
67
 
68
+ # Periodically update ARP cache
69
+ arp_updater = Thread.new do
70
+ while threads.any?(&:alive?)
71
+ sleep 5
72
+ @mutex.synchronize { @arp_cache = parse_arp_table }
73
+ end
74
+ end
75
+
77
76
  begin
78
77
  threads.each(&:join)
79
78
  print_progress(total_ips, total_ips)
@@ -82,6 +81,8 @@ module Lanet
82
81
  rescue Interrupt
83
82
  puts "\nScan interrupted. Returning partial results..."
84
83
  @verbose ? @hosts : @hosts.map { |h| h[:ip] }
84
+ ensure
85
+ arp_updater.kill if arp_updater.alive?
85
86
  end
86
87
  end
87
88
 
@@ -92,47 +93,62 @@ module Lanet
92
93
  print "\rScanning network: #{percent}% complete (#{completed}/#{total})"
93
94
  end
94
95
 
95
- def update_arp_table(cidr, max_threads = 10)
96
- # Use fast ping method to update ARP table
97
- range = IPAddr.new(cidr).to_range
98
- queue = Queue.new
99
- range.each { |ip| queue << ip.to_s }
96
+ def parse_arp_table
97
+ cmd = RbConfig::CONFIG["host_os"] =~ /mswin|mingw|cygwin/ ? "arp -a" : "arp -a"
98
+ output = `#{cmd}`
99
+ arp_cache = {}
100
100
 
101
- total = queue.size
102
- processed = 0
101
+ case RbConfig::CONFIG["host_os"]
102
+ when /darwin/
103
+ output.each_line do |line|
104
+ next unless line =~ /\((\d+\.\d+\.\d+\.\d+)\) at ([0-9a-f:]+) on/
103
105
 
104
- threads = Array.new([max_threads, total].min) do
105
- Thread.new do
106
- while (ip = begin
107
- queue.pop(true)
108
- rescue ThreadError
109
- nil
110
- end)
111
- # Use system ping to update ARP table
112
- system("ping -c 1 -W 1 #{ip} > /dev/null 2>&1 &")
113
- sleep 0.01 # Small delay to prevent overwhelming the system
114
- processed += 1
115
- end
106
+ ip = ::Regexp.last_match(1)
107
+ mac = ::Regexp.last_match(2).downcase
108
+ arp_cache[ip] = mac unless mac == "(incomplete)"
116
109
  end
117
- end
110
+ when /linux/
111
+ output.each_line do |line|
112
+ next unless line =~ /^(\d+\.\d+\.\d+\.\d+)\s+\w+\s+([0-9a-f:]+)\s+/
113
+
114
+ ip = ::Regexp.last_match(1)
115
+ mac = ::Regexp.last_match(2).downcase
116
+ arp_cache[ip] = mac unless mac == "00:00:00:00:00:00"
117
+ end
118
+ when /mswin|mingw|cygwin/
119
+ output.each_line do |line|
120
+ next unless line =~ /^\s*(\d+\.\d+\.\d+\.\d+)\s+([0-9a-f-]+)\s+/
118
121
 
119
- # Wait for ping operations to complete
120
- threads.each(&:join)
121
- sleep 1 # Give the system time to update ARP table
122
+ ip = ::Regexp.last_match(1)
123
+ mac = ::Regexp.last_match(2).gsub("-", ":").downcase
124
+ arp_cache[ip] = mac
125
+ end
126
+ end
127
+ arp_cache
122
128
  end
123
129
 
124
130
  def scan_host(ip)
125
- # Skip special addresses to save time
126
- return if ip.end_with?(".0") && !ip.end_with?(".0.0") # Skip network addresses except 0.0.0.0
131
+ # Handle broadcast addresses immediately
132
+ if ip.end_with?(".255") || ip == "255.255.255.255"
133
+ host_info = { ip: ip, mac: "ff:ff:ff:ff:ff:ff", response_time: 0, detection_method: "Broadcast" }
134
+ if @verbose
135
+ host_info[:hostname] = "Broadcast"
136
+ host_info[:ports] = {}
137
+ end
138
+ @mutex.synchronize { @hosts << host_info }
139
+ return
140
+ end
141
+
142
+ # Skip network addresses
143
+ return if ip.end_with?(".0") && !ip.end_with?(".0.0")
127
144
 
128
- # Use multiple methods to detect if a host is alive
129
145
  is_active = false
130
146
  detection_method = nil
131
147
  response_time = nil
132
148
  start_time = Time.now
133
149
  open_ports = []
134
150
 
135
- # Method 1: Try TCP port scan (most reliable)
151
+ # TCP port scan
136
152
  tcp_result = tcp_port_scan(ip, QUICK_CHECK_PORTS)
137
153
  if tcp_result[:active]
138
154
  is_active = true
@@ -140,116 +156,86 @@ module Lanet
140
156
  open_ports = tcp_result[:open_ports]
141
157
  end
142
158
 
143
- # Method 2: Try ICMP ping if TCP didn't work
144
- unless is_active
145
- ping_result = ping_check(ip)
146
- if ping_result
147
- is_active = true
148
- detection_method = "ICMP"
149
- end
159
+ # ICMP ping
160
+ if !is_active && ping_check(ip)
161
+ is_active = true
162
+ detection_method = "ICMP"
150
163
  end
151
164
 
152
- # Method 3: If host is a common network device (e.g., router), check with UDP
153
- if !is_active && (ip.end_with?(".1") || ip.end_with?(".254") || ip.end_with?(".255"))
154
- udp_result = udp_check(ip)
155
- if udp_result
156
- is_active = true
157
- detection_method = "UDP"
158
- end
165
+ # UDP check for common network devices
166
+ if !is_active && (ip.end_with?(".1") || ip.end_with?(".254")) && udp_check(ip)
167
+ is_active = true
168
+ detection_method = "UDP"
159
169
  end
160
170
 
161
- # Method 4: ARP Check - if we have a MAC, the host is likely active
171
+ # ARP check
162
172
  unless is_active
163
- mac_address = get_mac_address(ip)
164
- if mac_address && mac_address != "(incomplete)"
173
+ mac = get_mac_address(ip)
174
+ if mac && mac != "(incomplete)"
165
175
  is_active = true
166
176
  detection_method = "ARP"
167
177
  end
168
178
  end
169
179
 
170
- # For broadcast addresses, always consider them active
171
- if ip.end_with?(".255") || ip == "255.255.255.255"
172
- is_active = true
173
- detection_method = "Broadcast"
174
- end
175
-
176
- # Calculate response time
177
180
  response_time = ((Time.now - start_time) * 1000).round(2) if is_active
178
-
179
181
  return unless is_active
180
182
 
181
- # For active hosts, collect more information if in verbose mode
182
- host_info = {
183
- ip: ip,
184
- mac: get_mac_address(ip),
185
- response_time: response_time,
186
- detection_method: detection_method
187
- }
183
+ host_info = { ip: ip, mac: get_mac_address(ip), response_time: response_time, detection_method: detection_method }
188
184
 
189
185
  if @verbose
190
- # For verbose mode, try to resolve hostname
191
- begin
192
- Timeout.timeout(1) do
193
- host_info[:hostname] = Resolv.getname(ip)
194
- end
195
- rescue Resolv::ResolvError, Timeout::Error
196
- host_info[:hostname] = "Unknown"
186
+ host_info[:hostname] = begin
187
+ Timeout.timeout(1) { Resolv.getname(ip) }
188
+ rescue StandardError
189
+ "Unknown"
197
190
  end
198
-
199
- # For verbose mode, scan more ports if TCP detection method was successful
200
191
  if detection_method == "TCP"
201
192
  extra_ports = tcp_port_scan(ip, COMMON_PORTS.keys - QUICK_CHECK_PORTS)[:open_ports]
202
193
  open_ports += extra_ports
203
194
  end
204
-
205
195
  host_info[:ports] = open_ports.map { |port| [port, COMMON_PORTS[port] || "Unknown"] }.to_h
206
196
  end
207
197
 
208
198
  @mutex.synchronize { @hosts << host_info }
209
- rescue StandardError => e
210
- puts "\nError scanning host #{ip}: #{e.message}" if $DEBUG
211
199
  end
212
200
 
213
201
  def tcp_port_scan(ip, ports)
214
202
  open_ports = []
215
203
  is_active = false
204
+ threads = ports.map do |port|
205
+ Thread.new(port) do |p|
206
+ Timeout.timeout(@timeout) do
207
+ socket = TCPSocket.new(ip, p)
208
+ Thread.current[:open] = p
209
+ socket.close
210
+ end
211
+ rescue Errno::ECONNREFUSED
212
+ Thread.current[:active] = true
213
+ rescue StandardError
214
+ # Port closed or filtered
215
+ end
216
+ end
216
217
 
217
- ports.each do |port|
218
- Timeout.timeout(@timeout) do
219
- socket = TCPSocket.new(ip, port)
218
+ threads.each do |thread|
219
+ thread.join
220
+ if thread[:open]
221
+ open_ports << thread[:open]
222
+ is_active = true
223
+ elsif thread[:active]
220
224
  is_active = true
221
- open_ports << port
222
- socket.close
223
225
  end
224
- rescue Errno::ECONNREFUSED
225
- # Connection refused means host is up but port is closed
226
- is_active = true
227
- rescue StandardError
228
- # Other errors mean port is probably closed or filtered
229
226
  end
230
227
 
231
228
  { active: is_active, open_ports: open_ports }
232
229
  end
233
230
 
234
231
  def ping_check(ip)
235
- cmd = case RbConfig::CONFIG["host_os"]
236
- when /darwin/
237
- "ping -c 1 -W 1 #{ip}"
238
- when /linux/
239
- "ping -c 1 -W 1 #{ip}"
240
- when /mswin|mingw|cygwin/
241
- "ping -n 1 -w 1000 #{ip}"
242
- else
243
- "ping -c 1 -W 1 #{ip}"
244
- end
245
-
232
+ cmd = RbConfig::CONFIG["host_os"] =~ /mswin|mingw|cygwin/ ? "ping -n 1 -w 1000 #{ip}" : "ping -c 1 -W 1 #{ip}"
246
233
  system("#{cmd} > /dev/null 2>&1")
247
234
  $CHILD_STATUS.exitstatus.zero?
248
235
  end
249
236
 
250
237
  def udp_check(ip)
251
238
  common_udp_ports = [53, 67, 68, 123, 137, 138, 1900, 5353]
252
-
253
239
  common_udp_ports.each do |port|
254
240
  Timeout.timeout(0.5) do
255
241
  socket = UDPSocket.new
@@ -259,35 +245,15 @@ module Lanet
259
245
  return true
260
246
  end
261
247
  rescue Errno::ECONNREFUSED
262
- return true # Connection refused means host is up
248
+ return true
263
249
  rescue StandardError
264
- # Try next port
250
+ next
265
251
  end
266
252
  false
267
253
  end
268
254
 
269
255
  def get_mac_address(ip)
270
- return "ff:ff:ff:ff:ff:ff" if ip.end_with?(".255") # Special case for broadcast
271
-
272
- # Get MAC from ARP table
273
- cmd = case RbConfig::CONFIG["host_os"]
274
- when /darwin/
275
- "arp -n #{ip}"
276
- when /linux/
277
- "arp -n #{ip}"
278
- when /mswin|mingw|cygwin/
279
- "arp -a #{ip}"
280
- else
281
- "arp -n #{ip}"
282
- end
283
-
284
- output = `#{cmd}`
285
-
286
- if output =~ /([0-9a-fA-F]{1,2}[:-][0-9a-fA-F]{1,2}[:-][0-9a-fA-F]{1,2}[:-][0-9a-fA-F]{1,2}[:-][0-9a-fA-F]{1,2}[:-][0-9a-fA-F]{1,2})/
287
- ::Regexp.last_match(1).downcase
288
- else
289
- "(incomplete)"
290
- end
256
+ @mutex.synchronize { @arp_cache[ip] || "(incomplete)" }
291
257
  end
292
258
  end
293
259
  end
data/lib/lanet/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lanet
4
- VERSION = "0.2.0"
4
+ VERSION = "0.2.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lanet
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Davide Santangelo