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.
@@ -34,14 +34,7 @@ module Lanet
34
34
  destination_ip = resolve_destination(destination)
35
35
 
36
36
  begin
37
- case @protocol
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
- "traceroute -I"
62
- when :tcp
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 all IPs from the hop details to support load-balancing detection
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) if times.any?
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, there might be a special format
124
+ # If no IPs were found in a non-timeout line, try alternate format
138
125
  if ips.empty? && !hop_details.include?("*")
139
- # Try to find any IP-like patterns (some traceroute outputs format differently)
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
- if RbConfig::CONFIG["host_os"] =~ /mswin|mingw|cygwin/
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
- response_time = nil
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lanet
4
- VERSION = "0.5.0"
4
+ VERSION = "1.0.0"
5
5
  end
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.5.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-03-12 00:00:00.000000000 Z
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, and host pinging capabilities.
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: