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 +4 -4
- data/CHANGELOG.md +8 -1
- data/Gemfile.lock +1 -1
- data/README.md +2 -2
- data/index.html +308 -16
- data/lib/lanet/scanner.rb +101 -135
- data/lib/lanet/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aaec61e714b22eea7e3e40e66b6250672b93d15539f6645bc2ded8ea4c282458
|
4
|
+
data.tar.gz: 8f906d539e9878b418b156f7e735898dc04e9d05924ade44bcee23729474907d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# Lanet
|
2
2
|
|
3
|
-

|
4
|
-

|
3
|
+
[](https://rubygems.org/gems/lanet)
|
4
|
+
[](https://rubygems.org/gems/lanet)
|
5
5
|
[](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:
|
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:
|
38
|
-
box-shadow: 0 2px
|
39
|
-
padding:
|
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
|
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
|
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>
|
83
|
-
<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>
|
88
|
-
<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
|
-
<
|
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
|
-
<
|
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/
|
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
|
-
#
|
55
|
-
|
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
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
96
|
-
|
97
|
-
|
98
|
-
|
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
|
-
|
102
|
-
|
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
|
-
|
105
|
-
|
106
|
-
|
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
|
-
|
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
|
-
|
120
|
-
|
121
|
-
|
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
|
-
#
|
126
|
-
|
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
|
-
#
|
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
|
-
#
|
144
|
-
|
145
|
-
|
146
|
-
|
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
|
-
#
|
153
|
-
if !is_active && (ip.end_with?(".1") || ip.end_with?(".254")
|
154
|
-
|
155
|
-
|
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
|
-
#
|
171
|
+
# ARP check
|
162
172
|
unless is_active
|
163
|
-
|
164
|
-
if
|
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
|
-
|
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
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
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
|
-
|
218
|
-
|
219
|
-
|
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 =
|
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
|
248
|
+
return true
|
263
249
|
rescue StandardError
|
264
|
-
|
250
|
+
next
|
265
251
|
end
|
266
252
|
false
|
267
253
|
end
|
268
254
|
|
269
255
|
def get_mac_address(ip)
|
270
|
-
|
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