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 +4 -4
- data/CHANGELOG.md +27 -1
- data/Gemfile.lock +1 -1
- data/README.md +42 -0
- data/index.html +339 -108
- data/lib/lanet/cli.rb +57 -0
- data/lib/lanet/traceroute.rb +400 -0
- data/lib/lanet/version.rb +1 -1
- data/lib/lanet.rb +6 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0fa5904938004f84fe7b8d599da632140a532925cd0a0c48d5986d44443c0987
|
4
|
+
data.tar.gz: f039a015dcd75704b7fe80f2a84afbd758505048e78bc86001b9821c1be7a353
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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
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;
|
11
|
-
|
12
|
-
|
13
|
-
|
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;
|
20
|
+
background-color: #f4f6f9;
|
21
|
+
/* Lighter background color */
|
16
22
|
}
|
23
|
+
|
17
24
|
h1 {
|
18
|
-
color: #34495e;
|
19
|
-
|
20
|
-
|
21
|
-
|
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;
|
25
|
-
|
26
|
-
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;
|
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;
|
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;
|
40
|
-
|
59
|
+
margin-bottom: 15px;
|
60
|
+
/* Increased paragraph spacing */
|
61
|
+
color: #555;
|
62
|
+
/* Slightly softer paragraph text color */
|
41
63
|
}
|
42
|
-
|
64
|
+
|
65
|
+
ul,
|
66
|
+
ol {
|
43
67
|
margin-bottom: 15px;
|
44
68
|
color: #555;
|
45
69
|
}
|
70
|
+
|
46
71
|
pre {
|
47
|
-
background-color: #f0f0f0;
|
48
|
-
|
49
|
-
border
|
50
|
-
|
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;
|
53
|
-
|
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;
|
89
|
+
color: #2c3e50;
|
90
|
+
/* Darker code text color */
|
58
91
|
}
|
92
|
+
|
59
93
|
.container {
|
60
94
|
background-color: #fff;
|
61
|
-
border-radius: 10px;
|
62
|
-
|
63
|
-
|
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;
|
67
|
-
|
68
|
-
|
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;
|
73
|
-
|
74
|
-
|
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;
|
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;
|
83
|
-
|
84
|
-
|
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;
|
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;
|
145
|
+
margin-right: 12px;
|
146
|
+
/* Increased margin */
|
93
147
|
font-weight: bold;
|
94
|
-
display: inline-block;
|
95
|
-
|
96
|
-
|
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;
|
157
|
+
margin-bottom: 40px;
|
158
|
+
/* Increased bottom margin */
|
100
159
|
background-color: #f8f9fa;
|
101
|
-
padding: 25px;
|
102
|
-
|
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;
|
107
|
-
|
108
|
-
|
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;
|
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;
|
182
|
+
border-radius: 8px;
|
183
|
+
/* More rounded corners */
|
116
184
|
overflow: hidden;
|
117
|
-
margin: 25px 0;
|
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;
|
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;
|
197
|
+
padding: 12px 25px;
|
198
|
+
/* Increased padding */
|
126
199
|
background-color: transparent;
|
127
200
|
border: none;
|
128
201
|
cursor: pointer;
|
129
|
-
border-right: none;
|
202
|
+
border-right: none;
|
203
|
+
/* Removed right border */
|
130
204
|
transition: background-color 0.3s;
|
131
|
-
font-weight: 500;
|
132
|
-
|
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;
|
136
|
-
|
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;
|
140
|
-
|
141
|
-
|
142
|
-
|
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;
|
147
|
-
|
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;
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
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;
|
167
|
-
|
168
|
-
|
169
|
-
|
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;
|
274
|
+
color: #333;
|
275
|
+
/* Make "Note:" bold and slightly darker */
|
174
276
|
}
|
277
|
+
|
175
278
|
footer {
|
176
|
-
margin-top: 50px;
|
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;
|
284
|
+
border-top: 1px solid #ddd;
|
285
|
+
/* Added top border to footer */
|
181
286
|
}
|
287
|
+
|
182
288
|
footer p {
|
183
|
-
color: #95a5a6;
|
184
|
-
|
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
|
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
|
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
|
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
|
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
|
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
|
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"
|
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
|
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
|
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
|
-
|
434
|
-
|
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
|
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.
|
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
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
|
+
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-
|
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
|