lanet 0.4.0 → 0.5.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: a1e22dfda5b58811a99f1d763c3ff32d84b2e8e291ab07b8f1ee90e3c1849446
4
- data.tar.gz: 70a12c6ea35bd5981acc401f0a7dc482c2f2161c2a67a8b4ffcefc00ac1777a4
3
+ metadata.gz: 0fa5904938004f84fe7b8d599da632140a532925cd0a0c48d5986d44443c0987
4
+ data.tar.gz: f039a015dcd75704b7fe80f2a84afbd758505048e78bc86001b9821c1be7a353
5
5
  SHA512:
6
- metadata.gz: e45f31e4dacefa4d0622f1fdfe8d8cb48dca0c4ad256481a06f8bf7d1dea2ef231be657db2b8c92c1e129faa8375bdc36221152fb7967cca15ae7bc3c5fce89b
7
- data.tar.gz: 4cceff6bc76a51aeebb0bc0585ec9228d53151421927e8e8461e39a79f0208ed3a793455f8b9e0714364ce9cb169e987ab757a29e7ad4ab3acc908ab72f8a11f
6
+ metadata.gz: ac7892c9bd9179483ea9ebb796fb567a11f79aab6197b1c49a11d2797167e91c59be5da17e861a3e08069bc4ff745eae8534e8cdcda516739906a8011ab9ffc8
7
+ data.tar.gz: ef408dedc43f2461c69d0a00ae207f4bd139e06f171cb5cfd0fd6f40e58d2e0aa1bc59403428587f6982a378418ff241fecb1805254b37ea63d1a39ccc8116e3
data/CHANGELOG.md CHANGED
@@ -5,7 +5,33 @@ 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.4.0] - 2023-11-15
8
+ ## [0.5.1] - 2025-03-20
9
+
10
+ ### Changed
11
+ - Optimized traceroute implementation for better performance and code readability
12
+ - Simplified protocol selection logic with dedicated methods
13
+ - Improved error handling for socket operations
14
+ - Enhanced hostname extraction in traceroute results
15
+ - Reorganized code structure for better maintainability
16
+ - Added Windows-specific socket handling optimizations
17
+
18
+ ### Fixed
19
+ - Fixed redundant code in socket handling for different operating systems
20
+ - Improved error messages for unprivileged traceroute attempts
21
+ - Fixed potential memory leaks in socket resource management
22
+ - Added proper handling for non-standard traceroute output formats
23
+
24
+ ## [0.5.0] - 2025-03-12
25
+
26
+ ### Added
27
+ - Advanced traceroute functionality for network path analysis
28
+ - Multiple protocol support for traceroute: ICMP, UDP, and TCP
29
+ - Load balancing detection in traceroute results
30
+ - CLI command: `traceroute` with protocol selection and customizable parameters
31
+ - Ruby API for programmatic traceroute operations
32
+ - Comprehensive documentation and examples for traceroute feature
33
+
34
+ ## [0.4.0] - 2025-03-10
9
35
 
10
36
  ### Added
11
37
  - Mesh networking functionality for decentralized communication
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- lanet (0.4.0)
4
+ lanet (0.5.1)
5
5
  thor (~> 1.2)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -20,6 +20,7 @@ A lightweight, powerful LAN communication tool that enables secure message excha
20
20
  - **Digital Signatures**: Ensure message authenticity and integrity
21
21
  - **File Transfers**: Securely send encrypted files over the LAN with progress tracking and integrity verification
22
22
  - **Mesh Networking**: Create resilient mesh networks for decentralized communication, enabling messages to be routed through multiple hops without central infrastructure
23
+ - **Advanced Traceroute**: Analyze network paths using multiple protocols (ICMP, UDP, and TCP) with intelligent fallback mechanisms
23
24
 
24
25
  ## Security Features
25
26
 
@@ -334,6 +335,36 @@ View information about your mesh network:
334
335
  lanet mesh info
335
336
  ```
336
337
 
338
+ #### Tracing the route to a target host
339
+
340
+ Basic traceroute using UDP (default protocol):
341
+
342
+ ```bash
343
+ # Simple format
344
+ lanet traceroute google.com
345
+
346
+ # Option format
347
+ lanet traceroute --host google.com
348
+ ```
349
+
350
+ Trace using ICMP protocol:
351
+
352
+ ```bash
353
+ lanet traceroute --host google.com --protocol icmp
354
+ ```
355
+
356
+ Trace using TCP protocol:
357
+
358
+ ```bash
359
+ lanet traceroute --host google.com --protocol tcp --max-hops 15
360
+ ```
361
+
362
+ Customize traceroute parameters:
363
+
364
+ ```bash
365
+ lanet traceroute --host github.com --protocol tcp --max-hops 20 --timeout 2 --queries 4
366
+ ```
367
+
337
368
  ### Ruby API
338
369
 
339
370
  You can also use Lanet programmatically in your Ruby applications:
@@ -443,6 +474,17 @@ end
443
474
 
444
475
  # Properly stop the mesh node
445
476
  mesh.stop
477
+
478
+ # Trace route to a host with different protocols
479
+ tracer = Lanet.traceroute(protocol: :udp)
480
+ hops = tracer.trace('github.com')
481
+ hops.each do |hop|
482
+ puts "Hop #{hop[:ttl]}: #{hop[:ip]} - Response: #{hop[:avg_time]}ms"
483
+ end
484
+
485
+ # Use TCP protocol with custom parameters
486
+ tcp_tracer = Lanet.traceroute(protocol: :tcp, max_hops: 15, timeout: 2)
487
+ tcp_tracer.trace('google.com')
446
488
  ```
447
489
 
448
490
  ## Mesh Network
data/index.html CHANGED
@@ -1,5 +1,6 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en">
3
+
3
4
  <head>
4
5
  <meta charset="UTF-8">
5
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -7,188 +8,297 @@
7
8
  <style>
8
9
  body {
9
10
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
10
- line-height: 1.7; /* Increased line height for better readability */
11
- color: #34495e; /* Darker, more professional text color */
12
- max-width: 980px; /* Slightly wider max width for content */
13
- margin: 20px auto; /* Added top and bottom margin for better spacing */
11
+ line-height: 1.7;
12
+ /* Increased line height for better readability */
13
+ color: #34495e;
14
+ /* Darker, more professional text color */
15
+ max-width: 980px;
16
+ /* Slightly wider max width for content */
17
+ margin: 20px auto;
18
+ /* Added top and bottom margin for better spacing */
14
19
  padding: 20px;
15
- background-color: #f4f6f9; /* Lighter background color */
20
+ background-color: #f4f6f9;
21
+ /* Lighter background color */
16
22
  }
23
+
17
24
  h1 {
18
- color: #34495e; /* Darker heading color */
19
- border-bottom: 3px solid #3498db; /* Thicker border */
20
- padding-bottom: 12px; /* Increased padding */
21
- letter-spacing: -0.5px; /* Slightly tighter letter spacing for headings */
25
+ color: #34495e;
26
+ /* Darker heading color */
27
+ border-bottom: 3px solid #3498db;
28
+ /* Thicker border */
29
+ padding-bottom: 12px;
30
+ /* Increased padding */
31
+ letter-spacing: -0.5px;
32
+ /* Slightly tighter letter spacing for headings */
22
33
  }
34
+
23
35
  h2 {
24
- color: #3498db; /* Primary blue color for subheadings */
25
- margin-top: 35px; /* Increased top margin */
26
- margin-bottom: 10px; /* Added bottom margin */
36
+ color: #3498db;
37
+ /* Primary blue color for subheadings */
38
+ margin-top: 35px;
39
+ /* Increased top margin */
40
+ margin-bottom: 10px;
41
+ /* Added bottom margin */
27
42
  }
43
+
28
44
  h3 {
29
- color: #2ecc71; /* Vibrant green for section titles */
45
+ color: #2ecc71;
46
+ /* Vibrant green for section titles */
30
47
  margin-top: 30px;
31
48
  margin-bottom: 8px;
32
49
  }
50
+
33
51
  h4 {
34
- color: #34495e; /* Darker color for sub-subheadings */
52
+ color: #34495e;
53
+ /* Darker color for sub-subheadings */
35
54
  margin-top: 25px;
36
55
  margin-bottom: 5px;
37
56
  }
57
+
38
58
  p {
39
- margin-bottom: 15px; /* Increased paragraph spacing */
40
- color: #555; /* Slightly softer paragraph text color */
59
+ margin-bottom: 15px;
60
+ /* Increased paragraph spacing */
61
+ color: #555;
62
+ /* Slightly softer paragraph text color */
41
63
  }
42
- ul, ol {
64
+
65
+ ul,
66
+ ol {
43
67
  margin-bottom: 15px;
44
68
  color: #555;
45
69
  }
70
+
46
71
  pre {
47
- background-color: #f0f0f0; /* Slightly darker pre background */
48
- border: 1px solid #ccc; /* Lighter border */
49
- border-left: 5px solid #3498db; /* Thicker, more prominent left border */
50
- padding: 16px; /* Increased padding */
72
+ background-color: #f0f0f0;
73
+ /* Slightly darker pre background */
74
+ border: 1px solid #ccc;
75
+ /* Lighter border */
76
+ border-left: 5px solid #3498db;
77
+ /* Thicker, more prominent left border */
78
+ padding: 16px;
79
+ /* Increased padding */
51
80
  overflow-x: auto;
52
- border-radius: 6px; /* More rounded corners */
53
- font-size: 0.95em; /* Slightly smaller font size in code blocks */
81
+ border-radius: 6px;
82
+ /* More rounded corners */
83
+ font-size: 0.95em;
84
+ /* Slightly smaller font size in code blocks */
54
85
  }
86
+
55
87
  code {
56
88
  font-family: 'Courier New', Courier, monospace;
57
- color: #2c3e50; /* Darker code text color */
89
+ color: #2c3e50;
90
+ /* Darker code text color */
58
91
  }
92
+
59
93
  .container {
60
94
  background-color: #fff;
61
- border-radius: 10px; /* More rounded container corners */
62
- box-shadow: 0 3px 20px rgba(0, 0, 0, 0.08); /* Softer, more subtle shadow */
63
- padding: 40px; /* Increased container padding */
95
+ border-radius: 10px;
96
+ /* More rounded container corners */
97
+ box-shadow: 0 3px 20px rgba(0, 0, 0, 0.08);
98
+ /* Softer, more subtle shadow */
99
+ padding: 40px;
100
+ /* Increased container padding */
64
101
  }
102
+
65
103
  .feature {
66
- margin: 25px 0; /* Increased feature margin */
67
- padding-left: 25px; /* Increased padding */
68
- border-left: 5px solid #2ecc71; /* Thicker feature border */
104
+ margin: 25px 0;
105
+ /* Increased feature margin */
106
+ padding-left: 25px;
107
+ /* Increased padding */
108
+ border-left: 5px solid #2ecc71;
109
+ /* Thicker feature border */
69
110
  }
111
+
70
112
  .security-feature {
71
113
  background-color: #e8f4fc;
72
- padding: 20px; /* Increased padding */
73
- margin: 15px 0; /* Increased margin */
74
- border-radius: 8px; /* More rounded corners */
114
+ padding: 20px;
115
+ /* Increased padding */
116
+ margin: 15px 0;
117
+ /* Increased margin */
118
+ border-radius: 8px;
119
+ /* More rounded corners */
75
120
  }
121
+
76
122
  .security-feature ul li {
77
- margin-bottom: 8px; /* Spacing in security feature lists */
123
+ margin-bottom: 8px;
124
+ /* Spacing in security feature lists */
78
125
  }
126
+
79
127
  .cli-example {
80
128
  background-color: #2c3e50;
81
129
  color: #ecf0f1;
82
- padding: 15px 20px; /* Increased padding */
83
- margin: 15px 0; /* Increased margin */
84
- border-radius: 8px; /* More rounded corners */
130
+ padding: 15px 20px;
131
+ /* Increased padding */
132
+ margin: 15px 0;
133
+ /* Increased margin */
134
+ border-radius: 8px;
135
+ /* More rounded corners */
85
136
  font-family: 'Courier New', Courier, monospace;
86
137
  position: relative;
87
- font-size: 0.95em; /* Slightly smaller font size in CLI examples */
138
+ font-size: 0.95em;
139
+ /* Slightly smaller font size in CLI examples */
88
140
  }
141
+
89
142
  .cli-example::before {
90
143
  content: "$";
91
144
  color: #3498db;
92
- margin-right: 12px; /* Increased margin */
145
+ margin-right: 12px;
146
+ /* Increased margin */
93
147
  font-weight: bold;
94
- display: inline-block; /* Ensure proper spacing */
95
- width: 15px; /* Fixed width for the dollar sign to align commands */
96
- text-align: right; /* Align dollar sign to the right */
148
+ display: inline-block;
149
+ /* Ensure proper spacing */
150
+ width: 15px;
151
+ /* Fixed width for the dollar sign to align commands */
152
+ text-align: right;
153
+ /* Align dollar sign to the right */
97
154
  }
155
+
98
156
  .cli-section {
99
- margin-bottom: 40px; /* Increased bottom margin */
157
+ margin-bottom: 40px;
158
+ /* Increased bottom margin */
100
159
  background-color: #f8f9fa;
101
- padding: 25px; /* Increased padding */
102
- border-radius: 8px; /* More rounded corners */
160
+ padding: 25px;
161
+ /* Increased padding */
162
+ border-radius: 8px;
163
+ /* More rounded corners */
103
164
  }
165
+
104
166
  .output-example {
105
167
  background-color: #f0f0f0;
106
- padding: 15px; /* Increased padding */
107
- margin: 15px 0; /* Increased margin */
108
- border-radius: 8px; /* More rounded corners */
168
+ padding: 15px;
169
+ /* Increased padding */
170
+ margin: 15px 0;
171
+ /* Increased margin */
172
+ border-radius: 8px;
173
+ /* More rounded corners */
109
174
  font-family: 'Courier New', Courier, monospace;
110
175
  font-size: 0.9em;
111
- border-left: 5px solid #9b59b6; /* Thicker border */
176
+ border-left: 5px solid #9b59b6;
177
+ /* Thicker border */
112
178
  }
179
+
113
180
  .tab-container {
114
181
  border: 1px solid #ddd;
115
- border-radius: 8px; /* More rounded corners */
182
+ border-radius: 8px;
183
+ /* More rounded corners */
116
184
  overflow: hidden;
117
- margin: 25px 0; /* Increased margin */
185
+ margin: 25px 0;
186
+ /* Increased margin */
118
187
  }
188
+
119
189
  .tab-buttons {
120
190
  display: flex;
121
191
  background-color: #f5f5f5;
122
- border-bottom: 1px solid #ddd; /* Added bottom border to tab buttons container */
192
+ border-bottom: 1px solid #ddd;
193
+ /* Added bottom border to tab buttons container */
123
194
  }
195
+
124
196
  .tab-button {
125
- padding: 12px 25px; /* Increased padding */
197
+ padding: 12px 25px;
198
+ /* Increased padding */
126
199
  background-color: transparent;
127
200
  border: none;
128
201
  cursor: pointer;
129
- border-right: none; /* Removed right border */
202
+ border-right: none;
203
+ /* Removed right border */
130
204
  transition: background-color 0.3s;
131
- font-weight: 500; /* Slightly bolder tab button text */
132
- color: #777; /* Slightly muted tab button text color */
205
+ font-weight: 500;
206
+ /* Slightly bolder tab button text */
207
+ color: #777;
208
+ /* Slightly muted tab button text color */
133
209
  }
210
+
134
211
  .tab-button:hover {
135
- background-color: #f0f0f0; /* Lighter hover background */
136
- color: #555; /* Darker hover text color */
212
+ background-color: #f0f0f0;
213
+ /* Lighter hover background */
214
+ color: #555;
215
+ /* Darker hover text color */
137
216
  }
217
+
138
218
  .tab-button.active {
139
- background-color: #fff; /* White background for active tab */
140
- border-bottom: 3px solid #3498db; /* Underline for active tab */
141
- color: #34495e; /* Darker text for active tab */
142
- font-weight: bold; /* Bold text for active tab */
219
+ background-color: #fff;
220
+ /* White background for active tab */
221
+ border-bottom: 3px solid #3498db;
222
+ /* Underline for active tab */
223
+ color: #34495e;
224
+ /* Darker text for active tab */
225
+ font-weight: bold;
226
+ /* Bold text for active tab */
143
227
  }
228
+
144
229
  .tab-content {
145
230
  display: none;
146
- padding: 20px; /* Increased padding */
147
- background-color: #fff; /* White background for tab content */
231
+ padding: 20px;
232
+ /* Increased padding */
233
+ background-color: #fff;
234
+ /* White background for tab content */
148
235
  }
236
+
149
237
  .tab-content.active {
150
238
  display: block;
151
239
  }
240
+
152
241
  .badge {
153
242
  display: inline-block;
154
- padding: 4px 10px; /* Increased badge padding */
155
- border-radius: 6px; /* More rounded corners */
156
- font-size: 13px; /* Slightly larger font size */
157
- font-weight: 600; /* Bolder badge text */
158
- letter-spacing: 0.5px; /* Added letter spacing */
243
+ padding: 4px 10px;
244
+ /* Increased badge padding */
245
+ border-radius: 6px;
246
+ /* More rounded corners */
247
+ font-size: 13px;
248
+ /* Slightly larger font size */
249
+ font-weight: 600;
250
+ /* Bolder badge text */
251
+ letter-spacing: 0.5px;
252
+ /* Added letter spacing */
159
253
  }
254
+
160
255
  .badge-new {
161
256
  background-color: #2ecc71;
162
257
  color: white;
163
258
  }
259
+
164
260
  .note {
165
261
  background-color: #fff8e1;
166
- padding: 15px; /* Increased padding */
167
- border-left: 5px solid #ffc107; /* Thicker border */
168
- margin: 15px 0; /* Increased margin */
169
- border-radius: 6px; /* More rounded corners */
262
+ padding: 15px;
263
+ /* Increased padding */
264
+ border-left: 5px solid #ffc107;
265
+ /* Thicker border */
266
+ margin: 15px 0;
267
+ /* Increased margin */
268
+ border-radius: 6px;
269
+ /* More rounded corners */
170
270
  }
271
+
171
272
  .note strong {
172
273
  font-weight: bold;
173
- color: #333; /* Make "Note:" bold and slightly darker */
274
+ color: #333;
275
+ /* Make "Note:" bold and slightly darker */
174
276
  }
277
+
175
278
  footer {
176
- margin-top: 50px; /* Increased footer margin */
279
+ margin-top: 50px;
280
+ /* Increased footer margin */
177
281
  text-align: center;
178
282
  color: #7f8c8d;
179
283
  padding-top: 20px;
180
- border-top: 1px solid #ddd; /* Added top border to footer */
284
+ border-top: 1px solid #ddd;
285
+ /* Added top border to footer */
181
286
  }
287
+
182
288
  footer p {
183
- color: #95a5a6; /* Softer footer text color */
184
- font-size: 0.9em; /* Smaller footer font size */
289
+ color: #95a5a6;
290
+ /* Softer footer text color */
291
+ font-size: 0.9em;
292
+ /* Smaller footer font size */
185
293
  }
186
294
  </style>
187
295
  </head>
296
+
188
297
  <body>
189
298
  <div class="container">
190
299
  <h1>Lanet</h1>
191
- <p>A secure network communication library that enables reliable and protected message exchange between devices on the same network.</p>
300
+ <p>A secure network communication library that enables reliable and protected message exchange between devices
301
+ on the same network.</p>
192
302
 
193
303
  <h2>Key Features</h2>
194
304
 
@@ -205,9 +315,11 @@
205
315
  <h4>Benefits of Digital Signatures:</h4>
206
316
  <ul>
207
317
  <li><strong>Authentication</strong>: Verify that the message came from the claimed sender</li>
208
- <li><strong>Data Integrity</strong>: Ensure the message hasn't been tampered with during transit</li>
318
+ <li><strong>Data Integrity</strong>: Ensure the message hasn't been tampered with during transit
319
+ </li>
209
320
  <li><strong>Non-repudiation</strong>: Senders cannot deny sending a message they signed</li>
210
- <li><strong>Protection against MITM attacks</strong>: Detect man-in-the-middle tampering attempts</li>
321
+ <li><strong>Protection against MITM attacks</strong>: Detect man-in-the-middle tampering attempts
322
+ </li>
211
323
  </ul>
212
324
  </div>
213
325
  </div>
@@ -224,13 +336,15 @@
224
336
 
225
337
  <div class="feature">
226
338
  <h3>File Transfer <span class="badge badge-new">New in v0.3.0</span></h3>
227
- <p>Securely transfer files between devices with encryption, digital signatures, and integrity verification.</p>
339
+ <p>Securely transfer files between devices with encryption, digital signatures, and integrity verification.
340
+ </p>
228
341
  </div>
229
342
 
230
343
  <div class="feature">
231
344
  <h3>Mesh Networking <span class="badge badge-new">New in v0.4.0</span></h3>
232
- <p>Create resilient, decentralized mesh networks that enable communication between devices even without direct connectivity.</p>
233
-
345
+ <p>Create resilient, decentralized mesh networks that enable communication between devices even without
346
+ direct connectivity.</p>
347
+
234
348
  <div class="security-feature">
235
349
  <h4>Benefits of Mesh Networking:</h4>
236
350
  <ul>
@@ -243,10 +357,34 @@
243
357
  </div>
244
358
  </div>
245
359
 
360
+ <div class="feature">
361
+ <h3>Advanced Traceroute <span class="badge badge-new">New in v0.5.1</span></h3>
362
+ <p>Analyze network paths with multi-protocol traceroute capabilities to understand connectivity and
363
+ troubleshoot network issues.</p>
364
+
365
+ <div class="security-feature">
366
+ <h4>Features of Advanced Traceroute:</h4>
367
+ <ul>
368
+ <li><strong>Multi-protocol support</strong>: Use ICMP, UDP, or TCP protocols for different network
369
+ environments</li>
370
+ <li><strong>Load balancing detection</strong>: Identify multi-path routing and load balancers in the
371
+ network</li>
372
+ <li><strong>Response time analysis</strong>: Measure latency at each hop in the network path</li>
373
+ <li><strong>Customizable parameters</strong>: Adjust max hops, timeouts, and query count for
374
+ different scenarios</li>
375
+ <li><strong>Hostname resolution</strong>: Automatically resolve IP addresses to hostnames for easier
376
+ identification</li>
377
+ <li><strong>Fallback mechanism</strong>: Automatically uses system traceroute when elevated
378
+ permissions aren't available</li>
379
+ </ul>
380
+ </div>
381
+ </div>
382
+
246
383
  <h2>Command Line Interface</h2>
247
384
 
248
385
  <div class="note">
249
- <strong>Note:</strong> All Lanet commands have detailed help available. Try <code>lanet [command] --help</code> for more options.
386
+ <strong>Note:</strong> All Lanet commands have detailed help available. Try
387
+ <code>lanet [command] --help</code> for more options.
250
388
  </div>
251
389
 
252
390
  <div class="cli-section">
@@ -264,12 +402,12 @@
264
402
  </div>
265
403
 
266
404
  <div class="output-example">
267
- Key pair generated!
268
- Private key saved to: /home/user/.lanet_keys/lanet_private.key
269
- Public key saved to: /home/user/.lanet_keys/lanet_public.key
405
+ Key pair generated!
406
+ Private key saved to: /home/user/.lanet_keys/lanet_private.key
407
+ Public key saved to: /home/user/.lanet_keys/lanet_public.key
270
408
 
271
- IMPORTANT: Keep your private key secure and never share it.
272
- Share your public key with others who need to verify your messages.
409
+ IMPORTANT: Keep your private key secure and never share it.
410
+ Share your public key with others who need to verify your messages.
273
411
  </div>
274
412
 
275
413
  <h4>Send a Signed Message</h4>
@@ -279,7 +417,8 @@ Share your public key with others who need to verify your messages.
279
417
 
280
418
  <h4>Send a Signed & Encrypted Message</h4>
281
419
  <div class="cli-example">
282
- lanet send --target 192.168.1.5 --message "Secure signed message" --key "my_secret_key" --private-key-file lanet_private.key
420
+ lanet send --target 192.168.1.5 --message "Secure signed message" --key "my_secret_key"
421
+ --private-key-file lanet_private.key
283
422
  </div>
284
423
 
285
424
  <h4>Broadcast a Signed Message</h4>
@@ -299,10 +438,10 @@ Share your public key with others who need to verify your messages.
299
438
 
300
439
  <p>Example output when receiving a signed message:</p>
301
440
  <div class="output-example">
302
- Message from 192.168.1.5:
303
- Content: Hello, this is a signed message
304
- Signature: ✓ VERIFIED
305
- ----------------------------------------
441
+ Message from 192.168.1.5:
442
+ Content: Hello, this is a signed message
443
+ Signature: ✓ VERIFIED
444
+ ----------------------------------------
306
445
  </div>
307
446
  </div>
308
447
 
@@ -375,7 +514,8 @@ Signature: ✓ VERIFIED
375
514
 
376
515
  <p>Send a file with encryption and digital signature:</p>
377
516
  <div class="cli-example">
378
- lanet send-file --target 192.168.1.5 --file document.pdf --key "my_secret_key" --private-key-file lanet_private.key
517
+ lanet send-file --target 192.168.1.5 --file document.pdf --key "my_secret_key" --private-key-file
518
+ lanet_private.key
379
519
  </div>
380
520
 
381
521
  <h4>Receive Files</h4>
@@ -385,15 +525,16 @@ Signature: ✓ VERIFIED
385
525
 
386
526
  <p>With signature verification:</p>
387
527
  <div class="cli-example">
388
- lanet receive-file --output ./downloads --encryption-key "my_secret_key" --public-key-file lanet_public.key
528
+ lanet receive-file --output ./downloads --encryption-key "my_secret_key" --public-key-file
529
+ lanet_public.key
389
530
  </div>
390
531
 
391
532
  <p>Example output during file transfer:</p>
392
533
  <div class="output-example">
393
- Receiving file: document.pdf from 192.168.1.5
394
- Size: 1048576 bytes
395
- Transfer ID: 8a7b6c5d-4e3f-2g1h-0i9j-8k7l6m5n4o3p
396
- Progress: 75% (786432/1048576 bytes)
534
+ Receiving file: document.pdf from 192.168.1.5
535
+ Size: 1048576 bytes
536
+ Transfer ID: 8a7b6c5d-4e3f-2g1h-0i9j-8k7l6m5n4o3p
537
+ Progress: 75% (786432/1048576 bytes)
397
538
  </div>
398
539
  </div>
399
540
 
@@ -427,13 +568,55 @@ Progress: 75% (786432/1048576 bytes)
427
568
 
428
569
  <p>Example output when viewing mesh info:</p>
429
570
  <div class="output-example">
430
- Mesh Node ID: 4f9a8b7c-6d5e-4f3e-2d1c-0b9a8b7c6d5e
571
+ Mesh Node ID: 4f9a8b7c-6d5e-4f3e-2d1c-0b9a8b7c6d5e
431
572
 
432
- Connected nodes:
433
- b1c2d3e4-f5g6-7h8i-9j0k-l1m2n3o4p5q6 (192.168.1.5, last seen 12s ago)
434
- c5d4e3f2-g1h0-i9j8-k7l6-m5n4o3p2q1r0 (192.168.1.10, last seen 5s ago)
573
+ Connected nodes:
574
+ b1c2d3e4-f5g6-7h8i-9j0k-l1m2n3o4p5q6 (192.168.1.5, last seen 12s ago)
575
+ c5d4e3f2-g1h0-i9j8-k7l6-m5n4o3p2q1r0 (192.168.1.10, last seen 5s ago)
435
576
 
436
- Message cache: 24 messages
577
+ Message cache: 24 messages
578
+ </div>
579
+ </div>
580
+
581
+ <div class="cli-section">
582
+ <h3>Traceroute Commands <span class="badge badge-new">New in v0.5.0</span></h3>
583
+
584
+ <h4>Basic Traceroute (UDP protocol)</h4>
585
+ <div class="cli-example">
586
+ lanet traceroute --host google.com
587
+ </div>
588
+
589
+ <h4>Traceroute with ICMP Protocol</h4>
590
+ <div class="cli-example">
591
+ lanet traceroute --host google.com --protocol icmp
592
+ </div>
593
+
594
+ <h4>Traceroute with TCP Protocol</h4>
595
+ <div class="cli-example">
596
+ lanet traceroute --host github.com --protocol tcp
597
+ </div>
598
+
599
+ <h4>Customize Traceroute Parameters</h4>
600
+ <div class="cli-example">
601
+ lanet traceroute --host cloudflare.com --protocol tcp --max-hops 20 --timeout 2 --queries 4
602
+ </div>
603
+
604
+ <p>Example output of a traceroute:</p>
605
+ <div class="output-example">
606
+ Tracing route to github.com using UDP protocol
607
+ Maximum hops: 30, Timeout: 1s, Queries: 3
608
+ ======================================================================
609
+ TTL IP Address Hostname Response Time
610
+ ----------------------------------------------------------------------
611
+ 1 192.168.1.1 router.home 2.34ms
612
+ 2 172.16.42.1 isp-gateway.net 8.72ms
613
+ 3 216.58.223.14 15.35ms
614
+ 4 172.217.170.78 edge-router.google.com 22.89ms
615
+ 5 * * Request timed out
616
+ 6 140.82.121.4 github.com 45.23ms
617
+ Destination unreachable
618
+ ======================================================================
619
+ Trace complete.
437
620
  </div>
438
621
  </div>
439
622
 
@@ -446,6 +629,7 @@ Message cache: 24 messages
446
629
  <button class="tab-button" onclick="openTab(event, 'tab-advanced')">Advanced Usage</button>
447
630
  <button class="tab-button" onclick="openTab(event, 'tab-filetransfer')">File Transfer</button>
448
631
  <button class="tab-button" onclick="openTab(event, 'tab-mesh')">Mesh Network</button>
632
+ <button class="tab-button" onclick="openTab(event, 'tab-traceroute')">Traceroute</button>
449
633
  </div>
450
634
 
451
635
  <div id="tab-basic" class="tab-content active">
@@ -626,6 +810,51 @@ end
626
810
  # Always stop the mesh node when done
627
811
  mesh.stop</code></pre>
628
812
  </div>
813
+
814
+ <div id="tab-traceroute" class="tab-content">
815
+ <pre><code>require 'lanet'
816
+
817
+ # Create a traceroute instance with UDP protocol (default)
818
+ tracer = Lanet.traceroute
819
+ results = tracer.trace('github.com')
820
+
821
+ # Display the results
822
+ puts "Path to github.com:"
823
+ results.each do |hop|
824
+ if hop[:ip].nil?
825
+ puts "Hop #{hop[:ttl]}: * * * Request timed out"
826
+ else
827
+ hostname = hop[:hostname] ? hop[:hostname] : ""
828
+ time = hop[:avg_time] ? "#{hop[:avg_time]}ms" : "*"
829
+ puts "Hop #{hop[:ttl]}: #{hop[:ip]} (#{hostname}) #{time}"
830
+
831
+ # Check for load balancing (multiple IPs at the same hop)
832
+ if hop[:all_ips] && hop[:all_ips].size > 1
833
+ puts " Multiple IPs detected (load balancing):"
834
+ hop[:all_ips].each { |ip| puts " - #{ip}" }
835
+ end
836
+ end
837
+ end
838
+
839
+ # Use ICMP protocol (may require root/admin privileges)
840
+ begin
841
+ icmp_tracer = Lanet.traceroute(protocol: :icmp, max_hops: 10)
842
+ icmp_tracer.trace('google.com')
843
+ rescue StandardError => e
844
+ puts "Error with ICMP traceroute: #{e.message}"
845
+ end
846
+
847
+ # Use TCP protocol with custom parameters
848
+ tcp_tracer = Lanet.traceroute(protocol: :tcp, max_hops: 15,
849
+ timeout: 2, queries: 4)
850
+ tcp_results = tcp_tracer.trace('cloudflare.com')
851
+
852
+ # Analyze a specific hop
853
+ interesting_hop = tcp_results[5] # Sixth hop
854
+ if interesting_hop && interesting_hop[:unreachable]
855
+ puts "Destination unreachable at hop #{interesting_hop[:ttl]}"
856
+ end</code></pre>
857
+ </div>
629
858
  </div>
630
859
 
631
860
  <h2>Installation</h2>
@@ -637,10 +866,11 @@ mesh.stop</code></pre>
637
866
  <pre><code>gem install lanet</code></pre>
638
867
 
639
868
  <h2>Documentation</h2>
640
- <p>For complete documentation, please visit the <a href="https://github.com/davidesantangelo/lanet">GitHub repository</a>.</p>
869
+ <p>For complete documentation, please visit the <a href="https://github.com/davidesantangelo/lanet">GitHub
870
+ repository</a>.</p>
641
871
 
642
872
  <footer style="margin-top: 40px; text-align: center; color: #7f8c8d;">
643
- <p>Lanet v0.4.0 - Secure Network Communications Library</p>
873
+ <p>Lanet v0.5.1 - Secure Network Communications Library</p>
644
874
  </footer>
645
875
  </div>
646
876
 
@@ -663,4 +893,5 @@ mesh.stop</code></pre>
663
893
  }
664
894
  </script>
665
895
  </body>
896
+
666
897
  </html>
data/lib/lanet/cli.rb CHANGED
@@ -6,6 +6,7 @@ require "lanet/receiver"
6
6
  require "lanet/scanner"
7
7
  require "lanet/ping"
8
8
  require "lanet/encryptor"
9
+ require "lanet/traceroute"
9
10
 
10
11
  module Lanet
11
12
  class CLI < Thor
@@ -366,6 +367,62 @@ module Lanet
366
367
  mesh.stop
367
368
  end
368
369
 
370
+ desc "traceroute [HOST]", "Trace the route to a target host using different protocols"
371
+ method_option :host, type: :string, desc: "Target host to trace route"
372
+ method_option :protocol, type: :string, default: "udp", desc: "Protocol to use (icmp, udp, tcp)"
373
+ method_option :max_hops, type: :numeric, default: 30, desc: "Maximum number of hops"
374
+ method_option :timeout, type: :numeric, default: 1, desc: "Timeout in seconds for each probe"
375
+ method_option :queries, type: :numeric, default: 3, desc: "Number of queries per hop"
376
+ def traceroute(single_host = nil)
377
+ # Use the positional parameter if provided, otherwise use the --host option
378
+ target_host = single_host || options[:host]
379
+
380
+ # Ensure we have a host to trace
381
+ unless target_host
382
+ puts "Error: No host specified. Please provide a host as an argument or use --host option."
383
+ return
384
+ end
385
+
386
+ tracer = Lanet::Traceroute.new(
387
+ protocol: options[:protocol].to_sym,
388
+ max_hops: options[:max_hops],
389
+ timeout: options[:timeout],
390
+ queries: options[:queries]
391
+ )
392
+
393
+ puts "Tracing route to #{target_host} using #{options[:protocol].upcase} protocol"
394
+ puts "Maximum hops: #{options[:max_hops]}, Timeout: #{options[:timeout]}s, Queries: #{options[:queries]}"
395
+ puts "=" * 70
396
+ puts format("%3s %-15s %-30s %-10s", "TTL", "IP Address", "Hostname", "Response Time")
397
+ puts "-" * 70
398
+
399
+ tracer.trace(target_host).each do |hop|
400
+ if hop[:ip].nil?
401
+ puts format("%3d %-15s %-30s %-10s", hop[:ttl], "*", "*", "Request timed out")
402
+ else
403
+ hostname = hop[:hostname] || ""
404
+ time_str = hop[:avg_time] ? "#{hop[:avg_time]}ms" : "*"
405
+ puts format("%3d %-15s %-30s %-10s", hop[:ttl], hop[:ip], hostname, time_str)
406
+
407
+ # Show all IPs if there are multiple (for load balancing detection)
408
+ if hop[:all_ips] && hop[:all_ips].size > 1
409
+ puts " Multiple IPs detected (possible load balancing):"
410
+ hop[:all_ips].each do |ip|
411
+ puts " - #{ip}"
412
+ end
413
+ end
414
+
415
+ # Show unreachable marker
416
+ puts " Destination unreachable" if hop[:unreachable]
417
+ end
418
+ end
419
+ puts "=" * 70
420
+ puts "Trace complete."
421
+ rescue StandardError => e
422
+ puts "Error performing traceroute: #{e.message}"
423
+ puts e.backtrace if options[:verbose]
424
+ end
425
+
369
426
  private
370
427
 
371
428
  def display_ping_details(host, result)
@@ -0,0 +1,400 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
4
+ require "timeout"
5
+ require "resolv"
6
+
7
+ module Lanet
8
+ class Traceroute
9
+ # Supported protocols
10
+ PROTOCOLS = %i[icmp udp tcp].freeze
11
+
12
+ # Default settings
13
+ DEFAULT_MAX_HOPS = 30
14
+ DEFAULT_TIMEOUT = 1
15
+ DEFAULT_QUERIES = 3
16
+ DEFAULT_PORT = 33_434 # Starting port for UDP traceroute
17
+
18
+ attr_reader :results, :protocol, :max_hops, :timeout, :queries
19
+
20
+ def initialize(protocol: :udp, max_hops: DEFAULT_MAX_HOPS, timeout: DEFAULT_TIMEOUT, queries: DEFAULT_QUERIES)
21
+ @protocol = protocol.to_sym
22
+ @max_hops = max_hops
23
+ @timeout = timeout
24
+ @queries = queries
25
+ @results = []
26
+
27
+ return if PROTOCOLS.include?(@protocol)
28
+
29
+ raise ArgumentError, "Protocol must be one of #{PROTOCOLS.join(", ")}"
30
+ end
31
+
32
+ def trace(destination)
33
+ @results = []
34
+ destination_ip = resolve_destination(destination)
35
+
36
+ begin
37
+ trace_protocol(destination_ip)
38
+ rescue StandardError => e
39
+ raise e unless e.message.include?("Must run as root/administrator")
40
+
41
+ # Fall back to system traceroute command if we don't have root privileges
42
+ trace_using_system_command(destination)
43
+ end
44
+
45
+ @results
46
+ end
47
+
48
+ private
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
+
58
+ def trace_using_system_command(destination)
59
+ # Build the appropriate system traceroute command
60
+ system_cmd = case @protocol
61
+ when :icmp then "traceroute -I"
62
+ when :tcp then "traceroute -T"
63
+ else "traceroute" # UDP is the default
64
+ end
65
+
66
+ # Add options for max hops, timeout, and queries/retries
67
+ system_cmd += " -m #{@max_hops} -w #{@timeout} -q #{@queries} #{destination}"
68
+
69
+ # Execute the command and capture output
70
+ output = `#{system_cmd}`
71
+
72
+ # Parse the output to build our results
73
+ parse_system_traceroute_output(output)
74
+ end
75
+
76
+ def parse_system_traceroute_output(output)
77
+ lines = output.split("\n")
78
+
79
+ # Skip only the header line which typically starts with "traceroute to..."
80
+ lines.shift if lines.any? && lines.first.start_with?("traceroute to")
81
+
82
+ # Process each line of output
83
+ lines.each do |line|
84
+ # Extract hop number and details
85
+ next unless line =~ /^\s*(\d+)\s+(.+)$/
86
+
87
+ hop_num = Regexp.last_match(1).to_i
88
+ hop_details = Regexp.last_match(2)
89
+
90
+ # Parse the hop details
91
+ if ["* * *", "*"].include?(hop_details.strip)
92
+ # All timeouts at this hop
93
+ @results << { ttl: hop_num, ip: nil, hostname: nil, avg_time: nil, timeouts: @queries }
94
+ next
95
+ end
96
+
97
+ # Extract IP, hostname, and timing info
98
+ all_ips = extract_multiple_ips(hop_details)
99
+ ip = all_ips&.first
100
+ hostname = extract_hostname(hop_details)
101
+ times = hop_details.scan(/(\d+\.\d+)\s*ms/).flatten.map(&:to_f)
102
+ avg_time = times.any? ? (times.sum / times.size).round(2) : nil
103
+
104
+ # Add to results
105
+ @results << {
106
+ ttl: hop_num,
107
+ ip: ip,
108
+ hostname: hostname,
109
+ avg_time: avg_time,
110
+ timeouts: @queries - times.size,
111
+ all_ips: all_ips&.size && all_ips.size > 1 ? all_ips : nil
112
+ }
113
+ end
114
+ end
115
+
116
+ def extract_hostname(hop_details)
117
+ hop_details =~ /([^\s(]+)\s+\(([0-9.]+)\)/ ? Regexp.last_match(1) : nil
118
+ end
119
+
120
+ def extract_multiple_ips(hop_details)
121
+ # Match all IP addresses in the hop details
122
+ ips = hop_details.scan(/\b(?:\d{1,3}\.){3}\d{1,3}\b/).uniq
123
+
124
+ # If no IPs were found in a non-timeout line, try alternate format
125
+ if ips.empty? && !hop_details.include?("*")
126
+ potential_ips = hop_details.split(/\s+/).select { |part| part =~ /\b(?:\d{1,3}\.){3}\d{1,3}\b/ }
127
+ ips = potential_ips unless potential_ips.empty?
128
+ end
129
+
130
+ ips
131
+ end
132
+
133
+ def resolve_destination(destination)
134
+ # If destination is already an IPv4 address, return it
135
+ return destination if destination =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/
136
+
137
+ # Otherwise, resolve the hostname to an IPv4 address
138
+ begin
139
+ addresses = Resolv.getaddresses(destination)
140
+ raise Resolv::ResolvError, "no address for #{destination}" if addresses.empty?
141
+
142
+ # Find the first IPv4 address
143
+ ipv4_address = addresses.find { |addr| addr =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ }
144
+ raise "No IPv4 address found for hostname: #{destination}" if ipv4_address.nil?
145
+
146
+ ipv4_address
147
+ rescue Resolv::ResolvError => e
148
+ raise "Unable to resolve hostname: #{e.message}"
149
+ end
150
+ end
151
+
152
+ def get_hostname(ip)
153
+ Timeout.timeout(1) { Resolv.getname(ip) }
154
+ rescue StandardError
155
+ nil
156
+ end
157
+
158
+ def trace_icmp(destination_ip)
159
+ # ICMP traceroute implementation
160
+ 1.upto(@max_hops) do |ttl|
161
+ hop_info = trace_hop_icmp(destination_ip, ttl)
162
+ @results << hop_info
163
+
164
+ # Stop if we've reached the destination or hit unreachable
165
+ break if hop_info[:ip] == destination_ip || hop_info[:unreachable]
166
+ end
167
+ end
168
+
169
+ def trace_hop_icmp(destination_ip, ttl)
170
+ hop_info = { ttl: ttl, responses: [] }
171
+
172
+ # Use ping with increasing TTL values
173
+ @queries.times do
174
+ cmd = ping_command_with_ttl(destination_ip, ttl)
175
+ ip = nil
176
+ response_time = nil
177
+
178
+ # Execute the ping command and parse the output
179
+ begin
180
+ output = `#{cmd}`
181
+
182
+ # Parse the response to get the responding IP
183
+ if output =~ /from (\d+\.\d+\.\d+\.\d+).*time=(\d+\.?\d*)/
184
+ ip = ::Regexp.last_match(1)
185
+ response_time = ::Regexp.last_match(2).to_f
186
+ end
187
+ rescue StandardError
188
+ # Handle errors
189
+ end
190
+
191
+ hop_info[:responses] << {
192
+ ip: ip,
193
+ response_time: response_time,
194
+ timeout: ip.nil?
195
+ }
196
+ end
197
+
198
+ # Process the responses
199
+ process_hop_responses(hop_info)
200
+ end
201
+
202
+ def ping_command_with_ttl(ip, ttl)
203
+ case RbConfig::CONFIG["host_os"]
204
+ when /mswin|mingw|cygwin/
205
+ "ping -n 1 -i #{ttl} -w #{@timeout * 1000} #{ip}"
206
+ when /darwin/
207
+ "ping -c 1 -m #{ttl} -t #{@timeout} #{ip}"
208
+ else
209
+ "ping -c 1 -t #{ttl} -W #{@timeout} #{ip}"
210
+ end
211
+ end
212
+
213
+ def trace_udp(destination_ip)
214
+ 1.upto(@max_hops) do |ttl|
215
+ hop_info = trace_hop_udp(destination_ip, ttl)
216
+ @results << hop_info
217
+
218
+ # Stop if we've reached the destination or hit a destination unreachable
219
+ break if hop_info[:ip] == destination_ip || hop_info[:unreachable]
220
+ end
221
+ end
222
+
223
+ def trace_hop_udp(destination_ip, ttl)
224
+ hop_info = { ttl: ttl, responses: [] }
225
+ icmp_socket = create_icmp_socket
226
+
227
+ @queries.times do |i|
228
+ start_time = Time.now
229
+ port = DEFAULT_PORT + i + (ttl * @queries)
230
+
231
+ begin
232
+ # Create and configure the sending socket
233
+ sender = UDPSocket.new
234
+ sender.setsockopt(Socket::IPPROTO_IP, Socket::IP_TTL, ttl)
235
+
236
+ # Send the UDP packet
237
+ Timeout.timeout(@timeout) do
238
+ sender.send("TRACE", 0, destination_ip, port)
239
+
240
+ # Wait for ICMP response
241
+ data, addr = icmp_socket.recvfrom(512)
242
+ response_time = ((Time.now - start_time) * 1000).round(2)
243
+
244
+ # The responding IP is in addr[2]
245
+ ip = addr[2]
246
+
247
+ # Check if we've received an ICMP destination unreachable message
248
+ unreachable = data.bytes[20] == 3 # ICMP Type 3 is Destination Unreachable
249
+
250
+ hop_info[:responses] << {
251
+ ip: ip,
252
+ response_time: response_time,
253
+ timeout: false,
254
+ unreachable: unreachable
255
+ }
256
+ end
257
+ rescue Timeout::Error
258
+ hop_info[:responses] << { ip: nil, response_time: nil, timeout: true }
259
+ rescue StandardError => e
260
+ hop_info[:responses] << { ip: nil, response_time: nil, timeout: true, error: e.message }
261
+ ensure
262
+ sender&.close
263
+ end
264
+ end
265
+
266
+ icmp_socket.close
267
+ process_hop_responses(hop_info)
268
+ end
269
+
270
+ def create_icmp_socket
271
+ socket = Socket.new(Socket::AF_INET, Socket::SOCK_RAW, Socket::IPPROTO_ICMP)
272
+ socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true) unless windows?
273
+ socket
274
+ rescue Errno::EPERM, Errno::EACCES
275
+ raise "Must run as root/administrator to create raw sockets for traceroute"
276
+ end
277
+
278
+ def windows?
279
+ RbConfig::CONFIG["host_os"] =~ /mswin|mingw|cygwin/
280
+ end
281
+
282
+ def trace_tcp(destination_ip)
283
+ 1.upto(@max_hops) do |ttl|
284
+ hop_info = trace_hop_tcp(destination_ip, ttl)
285
+ @results << hop_info
286
+
287
+ # Stop if we've reached the destination or hit unreachable
288
+ break if hop_info[:ip] == destination_ip || hop_info[:unreachable]
289
+ end
290
+ end
291
+
292
+ def trace_hop_tcp(destination_ip, ttl)
293
+ hop_info = { ttl: ttl, responses: [] }
294
+
295
+ @queries.times do |i|
296
+ # Use different ports for each query
297
+ port = 80 + i
298
+ start_time = Time.now
299
+ socket = nil
300
+
301
+ begin
302
+ # Create TCP socket with specific TTL
303
+ socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
304
+ socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_TTL, ttl)
305
+ sockaddr = Socket.sockaddr_in(port, destination_ip)
306
+
307
+ # Attempt to connect with timeout
308
+ Timeout.timeout(@timeout) do
309
+ socket.connect_nonblock(sockaddr)
310
+ end
311
+
312
+ # If we get here, we successfully connected (likely at the final hop)
313
+ response_time = ((Time.now - start_time) * 1000).round(2)
314
+ hop_info[:responses] << {
315
+ ip: destination_ip,
316
+ response_time: response_time,
317
+ timeout: false
318
+ }
319
+ rescue IO::WaitWritable
320
+ # Connection in progress - need to use select for non-blocking socket
321
+ handle_wait_writable(socket, sockaddr, start_time, destination_ip, hop_info)
322
+ rescue SystemCallError, Timeout::Error
323
+ hop_info[:responses] << { ip: nil, response_time: nil, timeout: true }
324
+ ensure
325
+ socket&.close
326
+ end
327
+ end
328
+
329
+ process_hop_responses(hop_info)
330
+ end
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
+
365
+ def process_hop_responses(hop_info)
366
+ # Count timeouts
367
+ timeouts = hop_info[:responses].count { |r| r[:timeout] }
368
+
369
+ # If all queries timed out
370
+ return { ttl: hop_info[:ttl], ip: nil, hostname: nil, avg_time: nil, timeouts: timeouts } if timeouts == @queries
371
+
372
+ # Get all responding IPs (could be different if load balancing is in effect)
373
+ ips = hop_info[:responses].map { |r| r[:ip] }.compact.uniq
374
+
375
+ # Most common responding IP
376
+ ip = ips.max_by { |i| hop_info[:responses].count { |r| r[:ip] == i } }
377
+
378
+ # Calculate average response time for responses from the most common IP
379
+ valid_times = hop_info[:responses].select { |r| r[:ip] == ip && r[:response_time] }.map { |r| r[:response_time] }
380
+ avg_time = valid_times.empty? ? nil : (valid_times.sum / valid_times.size).round(2)
381
+
382
+ # Check if any responses indicated "unreachable"
383
+ unreachable = hop_info[:responses].any? { |r| r[:unreachable] }
384
+
385
+ # Get hostname for the IP
386
+ hostname = get_hostname(ip)
387
+
388
+ {
389
+ ttl: hop_info[:ttl],
390
+ ip: ip,
391
+ hostname: hostname,
392
+ avg_time: avg_time,
393
+ timeouts: timeouts,
394
+ unreachable: unreachable,
395
+ # Include all IPs if there are different ones (for load balancing detection)
396
+ all_ips: ips.size > 1 ? ips : nil
397
+ }
398
+ end
399
+ end
400
+ 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.4.0"
4
+ VERSION = "0.5.1"
5
5
  end
data/lib/lanet.rb CHANGED
@@ -9,6 +9,7 @@ require "lanet/cli"
9
9
  require "lanet/ping"
10
10
  require "lanet/file_transfer"
11
11
  require "lanet/mesh"
12
+ require "lanet/traceroute"
12
13
 
13
14
  module Lanet
14
15
  class Error < StandardError; end
@@ -56,5 +57,10 @@ module Lanet
56
57
  def mesh_network(port = 5050, max_hops = 10)
57
58
  Mesh.new(port, max_hops)
58
59
  end
60
+
61
+ # Create a new traceroute instance
62
+ def traceroute(protocol: :udp, max_hops: 30, timeout: 1, queries: 3)
63
+ Traceroute.new(protocol: protocol, max_hops: max_hops, timeout: timeout, queries: queries)
64
+ end
59
65
  end
60
66
  end
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.0
4
+ version: 0.5.1
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-10 00:00:00.000000000 Z
11
+ date: 2025-03-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -100,6 +100,7 @@ files:
100
100
  - lib/lanet/scanner.rb
101
101
  - lib/lanet/sender.rb
102
102
  - lib/lanet/signer.rb
103
+ - lib/lanet/traceroute.rb
103
104
  - lib/lanet/version.rb
104
105
  - sig/lanet.rbs
105
106
  homepage: https://github.com/davidesantangelo/lanet