lanet 0.5.0 → 1.0.0
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 +67 -0
- data/Gemfile.lock +1 -1
- data/README.md +71 -18
- data/index.html +467 -190
- data/lib/lanet/cli.rb +1 -5
- data/lib/lanet/encryptor.rb +138 -79
- data/lib/lanet/file_transfer.rb +64 -72
- data/lib/lanet/receiver.rb +62 -6
- data/lib/lanet/sender.rb +35 -3
- data/lib/lanet/traceroute.rb +66 -92
- data/lib/lanet/version.rb +1 -1
- data/lib/lanet.rb +2 -0
- metadata +6 -3
data/lib/lanet/traceroute.rb
CHANGED
|
@@ -34,14 +34,7 @@ module Lanet
|
|
|
34
34
|
destination_ip = resolve_destination(destination)
|
|
35
35
|
|
|
36
36
|
begin
|
|
37
|
-
|
|
38
|
-
when :icmp
|
|
39
|
-
trace_icmp(destination_ip)
|
|
40
|
-
when :udp
|
|
41
|
-
trace_udp(destination_ip)
|
|
42
|
-
when :tcp
|
|
43
|
-
trace_tcp(destination_ip)
|
|
44
|
-
end
|
|
37
|
+
trace_protocol(destination_ip)
|
|
45
38
|
rescue StandardError => e
|
|
46
39
|
raise e unless e.message.include?("Must run as root/administrator")
|
|
47
40
|
|
|
@@ -54,15 +47,20 @@ module Lanet
|
|
|
54
47
|
|
|
55
48
|
private
|
|
56
49
|
|
|
50
|
+
def trace_protocol(destination_ip)
|
|
51
|
+
case @protocol
|
|
52
|
+
when :icmp then trace_icmp(destination_ip)
|
|
53
|
+
when :udp then trace_udp(destination_ip)
|
|
54
|
+
when :tcp then trace_tcp(destination_ip)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
57
58
|
def trace_using_system_command(destination)
|
|
58
59
|
# Build the appropriate system traceroute command
|
|
59
60
|
system_cmd = case @protocol
|
|
60
|
-
when :icmp
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
"traceroute -T"
|
|
64
|
-
else
|
|
65
|
-
"traceroute" # UDP is the default for most traceroute commands
|
|
61
|
+
when :icmp then "traceroute -I"
|
|
62
|
+
when :tcp then "traceroute -T"
|
|
63
|
+
else "traceroute" # UDP is the default
|
|
66
64
|
end
|
|
67
65
|
|
|
68
66
|
# Add options for max hops, timeout, and queries/retries
|
|
@@ -90,32 +88,18 @@ module Lanet
|
|
|
90
88
|
hop_details = Regexp.last_match(2)
|
|
91
89
|
|
|
92
90
|
# Parse the hop details
|
|
93
|
-
hostname = nil
|
|
94
|
-
avg_time = nil
|
|
95
|
-
|
|
96
|
-
# Check for timeout indicated by asterisks
|
|
97
91
|
if ["* * *", "*"].include?(hop_details.strip)
|
|
98
92
|
# All timeouts at this hop
|
|
99
93
|
@results << { ttl: hop_num, ip: nil, hostname: nil, avg_time: nil, timeouts: @queries }
|
|
100
94
|
next
|
|
101
95
|
end
|
|
102
96
|
|
|
103
|
-
# Extract
|
|
97
|
+
# Extract IP, hostname, and timing info
|
|
104
98
|
all_ips = extract_multiple_ips(hop_details)
|
|
105
|
-
|
|
106
|
-
# Extract the first IP (primary IP for this hop)
|
|
107
99
|
ip = all_ips&.first
|
|
108
|
-
|
|
109
|
-
# Try to extract hostname if present
|
|
110
|
-
# Format: "hostname (ip)"
|
|
111
|
-
if hop_details =~ /([^\s(]+)\s+\(([0-9.]+)\)/
|
|
112
|
-
hostname = Regexp.last_match(1)
|
|
113
|
-
# We already have the IP from all_ips, so no need to set it again
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
# Extract response times - typically format is "X.XXX ms Y.YYY ms Z.ZZZ ms"
|
|
100
|
+
hostname = extract_hostname(hop_details)
|
|
117
101
|
times = hop_details.scan(/(\d+\.\d+)\s*ms/).flatten.map(&:to_f)
|
|
118
|
-
avg_time = (times.sum / times.size).round(2)
|
|
102
|
+
avg_time = times.any? ? (times.sum / times.size).round(2) : nil
|
|
119
103
|
|
|
120
104
|
# Add to results
|
|
121
105
|
@results << {
|
|
@@ -124,22 +108,22 @@ module Lanet
|
|
|
124
108
|
hostname: hostname,
|
|
125
109
|
avg_time: avg_time,
|
|
126
110
|
timeouts: @queries - times.size,
|
|
127
|
-
# Include all IPs if there are multiple
|
|
128
111
|
all_ips: all_ips&.size && all_ips.size > 1 ? all_ips : nil
|
|
129
112
|
}
|
|
130
113
|
end
|
|
131
114
|
end
|
|
132
115
|
|
|
116
|
+
def extract_hostname(hop_details)
|
|
117
|
+
hop_details =~ /([^\s(]+)\s+\(([0-9.]+)\)/ ? Regexp.last_match(1) : nil
|
|
118
|
+
end
|
|
119
|
+
|
|
133
120
|
def extract_multiple_ips(hop_details)
|
|
134
121
|
# Match all IP addresses in the hop details
|
|
135
122
|
ips = hop_details.scan(/\b(?:\d{1,3}\.){3}\d{1,3}\b/).uniq
|
|
136
123
|
|
|
137
|
-
# If no IPs were found in a non-timeout line,
|
|
124
|
+
# If no IPs were found in a non-timeout line, try alternate format
|
|
138
125
|
if ips.empty? && !hop_details.include?("*")
|
|
139
|
-
|
|
140
|
-
potential_ips = hop_details.split(/\s+/).select do |part|
|
|
141
|
-
part =~ /\b(?:\d{1,3}\.){3}\d{1,3}\b/
|
|
142
|
-
end
|
|
126
|
+
potential_ips = hop_details.split(/\s+/).select { |part| part =~ /\b(?:\d{1,3}\.){3}\d{1,3}\b/ }
|
|
143
127
|
ips = potential_ips unless potential_ips.empty?
|
|
144
128
|
end
|
|
145
129
|
|
|
@@ -153,14 +137,10 @@ module Lanet
|
|
|
153
137
|
# Otherwise, resolve the hostname to an IPv4 address
|
|
154
138
|
begin
|
|
155
139
|
addresses = Resolv.getaddresses(destination)
|
|
156
|
-
|
|
157
|
-
# If no addresses are returned, the hostname is unresolvable
|
|
158
140
|
raise Resolv::ResolvError, "no address for #{destination}" if addresses.empty?
|
|
159
141
|
|
|
160
142
|
# Find the first IPv4 address
|
|
161
143
|
ipv4_address = addresses.find { |addr| addr =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ }
|
|
162
|
-
|
|
163
|
-
# If no IPv4 address is found, raise an error
|
|
164
144
|
raise "No IPv4 address found for hostname: #{destination}" if ipv4_address.nil?
|
|
165
145
|
|
|
166
146
|
ipv4_address
|
|
@@ -181,10 +161,8 @@ module Lanet
|
|
|
181
161
|
hop_info = trace_hop_icmp(destination_ip, ttl)
|
|
182
162
|
@results << hop_info
|
|
183
163
|
|
|
184
|
-
# Stop if we've reached the destination
|
|
185
|
-
break if hop_info[:ip] == destination_ip
|
|
186
|
-
# Stop if we've hit an unreachable marker
|
|
187
|
-
break if hop_info[:unreachable]
|
|
164
|
+
# Stop if we've reached the destination or hit unreachable
|
|
165
|
+
break if hop_info[:ip] == destination_ip || hop_info[:unreachable]
|
|
188
166
|
end
|
|
189
167
|
end
|
|
190
168
|
|
|
@@ -193,9 +171,7 @@ module Lanet
|
|
|
193
171
|
|
|
194
172
|
# Use ping with increasing TTL values
|
|
195
173
|
@queries.times do
|
|
196
|
-
Time.now
|
|
197
174
|
cmd = ping_command_with_ttl(destination_ip, ttl)
|
|
198
|
-
|
|
199
175
|
ip = nil
|
|
200
176
|
response_time = nil
|
|
201
177
|
|
|
@@ -246,8 +222,6 @@ module Lanet
|
|
|
246
222
|
|
|
247
223
|
def trace_hop_udp(destination_ip, ttl)
|
|
248
224
|
hop_info = { ttl: ttl, responses: [] }
|
|
249
|
-
|
|
250
|
-
# Create a listener socket for ICMP responses
|
|
251
225
|
icmp_socket = create_icmp_socket
|
|
252
226
|
|
|
253
227
|
@queries.times do |i|
|
|
@@ -295,25 +269,23 @@ module Lanet
|
|
|
295
269
|
|
|
296
270
|
def create_icmp_socket
|
|
297
271
|
socket = Socket.new(Socket::AF_INET, Socket::SOCK_RAW, Socket::IPPROTO_ICMP)
|
|
298
|
-
|
|
299
|
-
# Windows requires different socket setup
|
|
300
|
-
else
|
|
301
|
-
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
|
|
302
|
-
end
|
|
272
|
+
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true) unless windows?
|
|
303
273
|
socket
|
|
304
274
|
rescue Errno::EPERM, Errno::EACCES
|
|
305
275
|
raise "Must run as root/administrator to create raw sockets for traceroute"
|
|
306
276
|
end
|
|
307
277
|
|
|
278
|
+
def windows?
|
|
279
|
+
RbConfig::CONFIG["host_os"] =~ /mswin|mingw|cygwin/
|
|
280
|
+
end
|
|
281
|
+
|
|
308
282
|
def trace_tcp(destination_ip)
|
|
309
283
|
1.upto(@max_hops) do |ttl|
|
|
310
284
|
hop_info = trace_hop_tcp(destination_ip, ttl)
|
|
311
285
|
@results << hop_info
|
|
312
286
|
|
|
313
|
-
# Stop if we've reached the destination
|
|
314
|
-
break if hop_info[:ip] == destination_ip
|
|
315
|
-
# Stop if we've hit an unreachable marker
|
|
316
|
-
break if hop_info[:unreachable]
|
|
287
|
+
# Stop if we've reached the destination or hit unreachable
|
|
288
|
+
break if hop_info[:ip] == destination_ip || hop_info[:unreachable]
|
|
317
289
|
end
|
|
318
290
|
end
|
|
319
291
|
|
|
@@ -324,15 +296,16 @@ module Lanet
|
|
|
324
296
|
# Use different ports for each query
|
|
325
297
|
port = 80 + i
|
|
326
298
|
start_time = Time.now
|
|
299
|
+
socket = nil
|
|
327
300
|
|
|
328
301
|
begin
|
|
329
302
|
# Create TCP socket with specific TTL
|
|
330
303
|
socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
|
|
331
304
|
socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_TTL, ttl)
|
|
305
|
+
sockaddr = Socket.sockaddr_in(port, destination_ip)
|
|
332
306
|
|
|
333
307
|
# Attempt to connect with timeout
|
|
334
308
|
Timeout.timeout(@timeout) do
|
|
335
|
-
sockaddr = Socket.sockaddr_in(port, destination_ip)
|
|
336
309
|
socket.connect_nonblock(sockaddr)
|
|
337
310
|
end
|
|
338
311
|
|
|
@@ -345,39 +318,7 @@ module Lanet
|
|
|
345
318
|
}
|
|
346
319
|
rescue IO::WaitWritable
|
|
347
320
|
# Connection in progress - need to use select for non-blocking socket
|
|
348
|
-
|
|
349
|
-
ip = nil
|
|
350
|
-
|
|
351
|
-
begin
|
|
352
|
-
Timeout.timeout(@timeout) do
|
|
353
|
-
_, writable, = IO.select(nil, [socket], nil, @timeout)
|
|
354
|
-
if writable&.any?
|
|
355
|
-
# Socket is writable, check for errors
|
|
356
|
-
begin
|
|
357
|
-
socket.connect_nonblock(sockaddr) # Will raise Errno::EISCONN if connected
|
|
358
|
-
rescue Errno::EISCONN
|
|
359
|
-
# Successfully connected
|
|
360
|
-
response_time = ((Time.now - start_time) * 1000).round(2)
|
|
361
|
-
ip = destination_ip
|
|
362
|
-
rescue SystemCallError
|
|
363
|
-
# Get the intermediary IP from the error
|
|
364
|
-
# This is a simplification - in reality, we'd need to use raw sockets
|
|
365
|
-
# and analyze TCP packets with specific TTL values
|
|
366
|
-
ip = nil
|
|
367
|
-
end
|
|
368
|
-
end
|
|
369
|
-
end
|
|
370
|
-
rescue Timeout::Error
|
|
371
|
-
hop_info[:responses] << { ip: nil, response_time: nil, timeout: true }
|
|
372
|
-
end
|
|
373
|
-
|
|
374
|
-
if ip
|
|
375
|
-
hop_info[:responses] << {
|
|
376
|
-
ip: ip,
|
|
377
|
-
response_time: response_time,
|
|
378
|
-
timeout: false
|
|
379
|
-
}
|
|
380
|
-
end
|
|
321
|
+
handle_wait_writable(socket, sockaddr, start_time, destination_ip, hop_info)
|
|
381
322
|
rescue SystemCallError, Timeout::Error
|
|
382
323
|
hop_info[:responses] << { ip: nil, response_time: nil, timeout: true }
|
|
383
324
|
ensure
|
|
@@ -388,6 +329,39 @@ module Lanet
|
|
|
388
329
|
process_hop_responses(hop_info)
|
|
389
330
|
end
|
|
390
331
|
|
|
332
|
+
def handle_wait_writable(socket, sockaddr, start_time, destination_ip, hop_info)
|
|
333
|
+
response_time = nil
|
|
334
|
+
ip = nil
|
|
335
|
+
|
|
336
|
+
begin
|
|
337
|
+
Timeout.timeout(@timeout) do
|
|
338
|
+
_, writable, = IO.select(nil, [socket], nil, @timeout)
|
|
339
|
+
if writable&.any?
|
|
340
|
+
begin
|
|
341
|
+
socket.connect_nonblock(sockaddr) # Will raise Errno::EISCONN if connected
|
|
342
|
+
rescue Errno::EISCONN
|
|
343
|
+
# Successfully connected
|
|
344
|
+
response_time = ((Time.now - start_time) * 1000).round(2)
|
|
345
|
+
ip = destination_ip
|
|
346
|
+
rescue SystemCallError
|
|
347
|
+
# Get the intermediary IP from the error - would need raw sockets to do properly
|
|
348
|
+
ip = nil
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
rescue Timeout::Error
|
|
353
|
+
hop_info[:responses] << { ip: nil, response_time: nil, timeout: true }
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
return unless ip
|
|
357
|
+
|
|
358
|
+
hop_info[:responses] << {
|
|
359
|
+
ip: ip,
|
|
360
|
+
response_time: response_time,
|
|
361
|
+
timeout: false
|
|
362
|
+
}
|
|
363
|
+
end
|
|
364
|
+
|
|
391
365
|
def process_hop_responses(hop_info)
|
|
392
366
|
# Count timeouts
|
|
393
367
|
timeouts = hop_info[:responses].count { |r| r[:timeout] }
|
data/lib/lanet/version.rb
CHANGED
data/lib/lanet.rb
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "lanet/version"
|
|
4
|
+
require "lanet/config"
|
|
4
5
|
require "lanet/sender"
|
|
5
6
|
require "lanet/receiver"
|
|
6
7
|
require "lanet/scanner"
|
|
7
8
|
require "lanet/encryptor"
|
|
9
|
+
require "lanet/transfer_state"
|
|
8
10
|
require "lanet/cli"
|
|
9
11
|
require "lanet/ping"
|
|
10
12
|
require "lanet/file_transfer"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lanet
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Davide Santangelo
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-
|
|
11
|
+
date: 2025-10-19 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: thor
|
|
@@ -68,7 +68,10 @@ dependencies:
|
|
|
68
68
|
version: '3.0'
|
|
69
69
|
description: Lanet provides a simple yet powerful API for LAN device discovery, secure
|
|
70
70
|
messaging, and real-time network monitoring. Features include encrypted communications,
|
|
71
|
-
network scanning, targeted and broadcast messaging,
|
|
71
|
+
network scanning, targeted and broadcast messaging, host pinging, secure file transfers,
|
|
72
|
+
mesh networking, and advanced traceroute capabilities. Version 1.0.0 brings significant
|
|
73
|
+
architectural improvements with better resource management, enhanced error handling,
|
|
74
|
+
and improved code quality while maintaining 100% backward compatibility.
|
|
72
75
|
email:
|
|
73
76
|
- davide.santangelo@example.com
|
|
74
77
|
executables:
|