Skip to content

Commit ec6f9be

Browse files
Merge pull request #11 from NordSecurity/xray_bug_fixes
XRay bug fixes
2 parents a04bc67 + 439209e commit ec6f9be

File tree

6 files changed

+79
-26
lines changed

6 files changed

+79
-26
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@
66
**/*.pcap
77
**/*.sk
88
**/*.pk
9+
**/__pycache__

xray/README.md

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,29 @@ Execution consists of these steps:
1818
5. The wireguard interface is destroyed
1919
6. The `.csv` file and pcap are analyzed
2020

21+
The structure and exeuction of the XRay binary can be represented with the following image:
22+
23+
```mermaid
24+
flowchart LR
25+
subgraph Encryption boundary
26+
subgraph Crypto socket
27+
tunn[noise::Tunn]<-->cs[UdpSocket]
28+
end
29+
subgraph Wireguard interface
30+
wg[NepTUN/WireguardGo/LinuxNative/etc]
31+
end
32+
end
33+
subgraph Plaintext socket
34+
ps[UdpSocket]
35+
end
36+
cp["Packet(index: u64)"]-->tunn
37+
cs<--Encrypted packet-->wg
38+
wg<--Plaintext packet-->ps[Plaintext Socket]
39+
ps-->pp["Packet(index: u64)"]
40+
```
41+
42+
The red arrows represent crypto packets and the blue arrows represent plaintext packets.
43+
2144
## Running it
2245

2346
X-Ray currently only works on linux
@@ -48,8 +71,6 @@ The application is executed with the `run.py` script. I takes some arguments, al
4871

4972
## Known issues
5073

51-
- The analysis of pcaps is quite limited right now because it doesn't decrypt the packets (this is being worked on)
74+
- The analysis of pcaps is quite limited right now because it doesn't decrypt the packets (this is being worked on)
5275

5376
- There are multiple inefficiencies that could potentially impact the test results, the main one being not reusing buffers when creating and sending packets. Each packet that gets constructed allocates a new buffer when they could all reuse the same one
54-
55-
- Sometimes the wireguard handshake times out. When that happens, just rerun and it should be fine. Having to rerun multiple times is rare, but it happens

xray/analyze.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ def __init__(self, csv_path):
2222
reader = csv.reader(csvfile, delimiter=",", quotechar="|")
2323
next(reader)
2424
for row in reader:
25-
self.indices.append(int(row[0]))
25+
if len(row[0]) > 0:
26+
self.indices.append(int(row[0]))
2627
send_ts = int(row[1])
2728
if len(row[2]) > 0:
2829
recv_ts = int(row[2])
@@ -147,7 +148,12 @@ def packet_latency(self, ax):
147148
ax.hist(buckets, color="blue", bins=num_buckets)
148149

149150
def dropped_packets(self, ax):
150-
num_buckets = 100
151+
if self.count >= 100:
152+
num_buckets = 100
153+
elif self.count >= 10:
154+
num_buckets = 10
155+
else:
156+
num_buckets = self.count
151157
bucket_size = int(self.count / (num_buckets - 1))
152158
buckets = []
153159
for iter, index in enumerate(self.csv_data.indices):

xray/src/client.rs

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use std::net::{SocketAddr, SocketAddrV4};
1+
use std::{
2+
net::{SocketAddr, SocketAddrV4},
3+
time::Duration,
4+
};
25

36
use neptun::noise::{Tunn, TunnResult};
47
use pnet::packet::{
@@ -14,6 +17,9 @@ use crate::{
1417
XRayError, XRayResult,
1518
};
1619

20+
const HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(2);
21+
const HANDSHAKE_MAX_TRIES: usize = 5;
22+
1723
pub struct Client {
1824
pub addr: SocketAddrV4,
1925
pub tunn: Option<Tunn>,
@@ -33,6 +39,26 @@ impl Client {
3339

3440
pub async fn do_handshake(&mut self, wg_addr: SocketAddrV4) -> XRayResult<()> {
3541
println!("Handshake: starting");
42+
for _ in 0..HANDSHAKE_MAX_TRIES {
43+
match self.try_handshake(wg_addr).await {
44+
Ok(_) => {
45+
println!("Handshake: done");
46+
return Ok(());
47+
}
48+
Err(XRayError::HandshakeTimedOut) => {
49+
println!("Handshake timed out")
50+
}
51+
Err(err) => {
52+
println!("Handshake failed");
53+
return Err(err);
54+
}
55+
}
56+
}
57+
println!("Could not establish handshake");
58+
Err(XRayError::HandshakeTimedOut)
59+
}
60+
61+
async fn try_handshake(&mut self, wg_addr: SocketAddrV4) -> XRayResult<()> {
3662
let tunn = self
3763
.tunn
3864
.as_mut()
@@ -46,18 +72,17 @@ impl Client {
4672
}
4773
}
4874
let mut handshake_buf = vec![0; 512];
49-
let handshake_timeout = tokio::time::sleep(tokio::time::Duration::from_secs(3));
50-
tokio::pin!(handshake_timeout);
75+
tokio::time::timeout(HANDSHAKE_TIMEOUT, self.recv_handshake(&mut handshake_buf))
76+
.await
77+
.map_err(|_| XRayError::HandshakeTimedOut)??;
78+
Ok(())
79+
}
80+
81+
async fn recv_handshake(&mut self, buf: &mut [u8]) -> XRayResult<()> {
5182
loop {
52-
tokio::select! {
53-
Ok(recv_type) = self.recv_encrypted(&mut handshake_buf) => {
54-
if matches!(recv_type, RecvType::HandshakeResponse) {
55-
println!("Handshake: done");
56-
return Ok(());
57-
}
58-
}
59-
_ = &mut handshake_timeout => {
60-
return Err(XRayError::HandshakeTimedOut);
83+
if let Ok(recv_type) = self.recv_encrypted(buf).await {
84+
if matches!(recv_type, RecvType::HandshakeResponse) {
85+
return Ok(());
6186
}
6287
}
6388
}

xray/src/event_loop.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ impl EventLoop {
4242
is_done: false,
4343
crypto_buf: vec![0; 1024],
4444
plaintext_buf: vec![0; 1024],
45-
recv_counter: 1,
45+
recv_counter: 0,
4646
}
4747
}
4848

@@ -55,7 +55,7 @@ impl EventLoop {
5555
loop {
5656
tokio::select! {
5757
_ = &mut finish_timeout, if self.is_done => {
58-
self.on_finished(self.packets.len()).await?;
58+
self.on_finished(self.recv_counter).await?;
5959
break;
6060
},
6161
_ = wg_tick_interval.tick() => {
@@ -150,8 +150,9 @@ impl EventLoop {
150150
RecvType::Data { length: bytes_read } => {
151151
if bytes_read == Packet::send_size() {
152152
if self.recv_counter % (self.cli_args.packet_count / 10) == 0 {
153-
println!("[Crypto] Received {} packets", self.recv_counter);
153+
println!("[Crypto] Received {} packets", self.recv_counter + 1);
154154
}
155+
self.recv_counter += 1;
155156
let send_index = u64::from_le_bytes(
156157
self.crypto_buf[0..8]
157158
.try_into()
@@ -162,7 +163,6 @@ impl EventLoop {
162163
.as_micros();
163164
self.packets[send_index].recv_index = Some(self.recv_counter as u64);
164165
self.packets[send_index].recv_ts = Some(recv_ts);
165-
self.recv_counter += 1;
166166
self.on_maybe_recv_all(finish_timeout);
167167
}
168168
}
@@ -178,8 +178,9 @@ impl EventLoop {
178178
if let RecvType::Data { length: bytes_read } = rt {
179179
if bytes_read == Packet::send_size() {
180180
if self.recv_counter % (self.cli_args.packet_count / 10) == 0 {
181-
println!("[Plaintext] Received {} packets", self.recv_counter);
181+
println!("[Plaintext] Received {} packets", self.recv_counter + 1);
182182
}
183+
self.recv_counter += 1;
183184
let send_index = u64::from_le_bytes(
184185
self.plaintext_buf[0..8]
185186
.try_into()
@@ -190,19 +191,18 @@ impl EventLoop {
190191
.as_micros();
191192
self.packets[send_index].recv_index = Some(self.recv_counter as u64);
192193
self.packets[send_index].recv_ts = Some(recv_ts);
193-
self.recv_counter += 1;
194194
self.on_maybe_recv_all(finish_timeout);
195195
}
196196
}
197197
Ok(())
198198
}
199199

200200
fn on_maybe_recv_all(&self, finish_timeout: &mut Pin<&mut tokio::time::Sleep>) {
201-
if self.recv_counter > self.cli_args.packet_count {
202-
println!("All packets were received. Waiting 3 seconds to make sure pcap is properly populated");
201+
if self.recv_counter >= self.cli_args.packet_count {
202+
println!("All packets were received. Finishing test...");
203203
finish_timeout
204204
.as_mut()
205-
.reset(Instant::now() + Duration::from_secs(3));
205+
.reset(Instant::now() + Duration::from_secs(1));
206206
}
207207
}
208208

xray/xray_setup.png

52.2 KB
Loading

0 commit comments

Comments
 (0)