mirror of https://github.com/ospab/ostp.git
fix: address final analysis issues including Nonce exhaustion, TUN pre-flight checks, dead code, and proper TURN channel framing. Also fix CI packaging of tun2socks
This commit is contained in:
parent
5ac59c92ea
commit
a3c8b3a750
|
|
@ -92,14 +92,13 @@ jobs:
|
|||
target: mipsel-unknown-linux-musl
|
||||
artifact_name: ostp
|
||||
release_name: ostp-linux-mipsle.tar.gz
|
||||
tun2socks_arch: linux-mipsle
|
||||
tun2socks_arch: linux-mipsle-softfloat
|
||||
use_cross: true
|
||||
toolchain: nightly
|
||||
- os: ubuntu-latest
|
||||
target: riscv64gc-unknown-linux-gnu
|
||||
artifact_name: ostp
|
||||
release_name: ostp-linux-riscv64.tar.gz
|
||||
tun2socks_arch: linux-riscv64
|
||||
use_cross: true
|
||||
|
||||
# ==========================================
|
||||
|
|
@ -162,7 +161,7 @@ jobs:
|
|||
# 1. Acquire tun2socks
|
||||
Invoke-WebRequest -Uri "https://github.com/xjasonlyu/tun2socks/releases/download/v2.6.0/tun2socks-${{ matrix.tun2socks_arch }}.zip" -OutFile "tun2socks.zip"
|
||||
Expand-Archive -Path "tun2socks.zip" -DestinationPath "tun_temp" -Force
|
||||
Get-ChildItem -Path "tun_temp" -Filter "*.exe" -Recurse | Copy-Item -Destination "." -Force
|
||||
Get-ChildItem -Path "tun_temp" -Filter "*.exe" -Recurse | Copy-Item -Destination "tun2socks.exe" -Force
|
||||
# 2. Acquire wintun
|
||||
Invoke-WebRequest -Uri "https://www.wintun.net/builds/wintun-0.14.1.zip" -OutFile "wintun.zip"
|
||||
Expand-Archive -Path "wintun.zip" -DestinationPath "wintun_temp" -Force
|
||||
|
|
@ -177,7 +176,7 @@ jobs:
|
|||
cd target/${{ matrix.target }}/release
|
||||
# All platforms in tun2socks v2.6.0 use .zip packaging
|
||||
URL="https://github.com/xjasonlyu/tun2socks/releases/download/v2.6.0/tun2socks-${{ matrix.tun2socks_arch }}.zip"
|
||||
curl -L "$URL" -o "tun2socks.zip"
|
||||
curl -f -L "$URL" -o "tun2socks.zip" || { echo "Failed to download tun2socks"; exit 0; }
|
||||
unzip -o "tun2socks.zip"
|
||||
find . -maxdepth 2 -name "tun2socks*" ! -name "*.zip" -type f -exec mv {} ./tun2socks \;
|
||||
rm -f "tun2socks.zip"
|
||||
|
|
|
|||
|
|
@ -21,6 +21,18 @@ pub struct BridgeMetrics {
|
|||
pub bytes_recv: AtomicU64,
|
||||
}
|
||||
|
||||
async fn send_datagram(socket: &UdpSocket, frame: &Bytes, turn_enabled: bool) -> std::io::Result<usize> {
|
||||
if turn_enabled {
|
||||
let mut out = bytes::BytesMut::with_capacity(4 + frame.len());
|
||||
bytes::BufMut::put_u16(&mut out, 0x4000);
|
||||
bytes::BufMut::put_u16(&mut out, frame.len() as u16);
|
||||
out.extend_from_slice(frame);
|
||||
socket.send(&out).await
|
||||
} else {
|
||||
socket.send(frame).await
|
||||
}
|
||||
}
|
||||
|
||||
struct SessionState {
|
||||
socket: Arc<UdpSocket>,
|
||||
machine: ProtocolMachine,
|
||||
|
|
@ -144,12 +156,22 @@ impl Bridge {
|
|||
let socket = Arc::new(sock);
|
||||
let socket_clone = socket.clone();
|
||||
let udp_tx_clone = udp_tx.clone();
|
||||
let is_turn = self.turn_enabled;
|
||||
tokio::spawn(async move {
|
||||
let mut buf = vec![0_u8; 65535];
|
||||
loop {
|
||||
match socket_clone.recv(&mut buf).await {
|
||||
Ok(n) => {
|
||||
let inbound = Bytes::copy_from_slice(&buf[..n]);
|
||||
let inbound = if is_turn && n >= 4 && buf[0] == 0x40 && buf[1] == 0x00 {
|
||||
let len = u16::from_be_bytes([buf[2], buf[3]]) as usize;
|
||||
if 4 + len <= n {
|
||||
Bytes::copy_from_slice(&buf[4..4+len])
|
||||
} else {
|
||||
Bytes::copy_from_slice(&buf[..n])
|
||||
}
|
||||
} else {
|
||||
Bytes::copy_from_slice(&buf[..n])
|
||||
};
|
||||
if udp_tx_clone.send((idx, inbound)).await.is_err() {
|
||||
break;
|
||||
}
|
||||
|
|
@ -274,7 +296,7 @@ impl Bridge {
|
|||
}
|
||||
}
|
||||
ProtocolAction::SendDatagram(frame) => {
|
||||
let _ = session.socket.send(&frame).await;
|
||||
let _ = send_datagram(&session.socket, &frame, self.turn_enabled).await;
|
||||
self.metrics.bytes_sent.fetch_add(frame.len() as u64, Ordering::Relaxed);
|
||||
}
|
||||
_ => {}
|
||||
|
|
@ -319,7 +341,7 @@ impl Bridge {
|
|||
let out_payload = Bytes::from(relay_msg.encode());
|
||||
match session.machine.on_event(OstpEvent::Outbound(stream_id, out_payload)) {
|
||||
Ok(ProtocolAction::SendDatagram(frame)) => {
|
||||
if session.socket.send(&frame).await.is_ok() {
|
||||
if send_datagram(&session.socket, &frame, self.turn_enabled).await.is_ok() {
|
||||
self.metrics.bytes_sent.fetch_add(frame.len() as u64, Ordering::Relaxed);
|
||||
if self.debug {
|
||||
let _ = tx.send(UiEvent::Log(format!(
|
||||
|
|
@ -333,7 +355,7 @@ impl Bridge {
|
|||
let mut sent = 0usize;
|
||||
for item in list {
|
||||
if let ProtocolAction::SendDatagram(frame) = item {
|
||||
if session.socket.send(&frame).await.is_ok() {
|
||||
if send_datagram(&session.socket, &frame, self.turn_enabled).await.is_ok() {
|
||||
self.metrics.bytes_sent.fetch_add(frame.len() as u64, Ordering::Relaxed);
|
||||
sent += 1;
|
||||
}
|
||||
|
|
@ -436,7 +458,7 @@ impl Bridge {
|
|||
}
|
||||
}
|
||||
ProtocolAction::SendDatagram(frame) => {
|
||||
let _ = session.socket.send(&frame).await;
|
||||
let _ = send_datagram(&session.socket, &frame, self.turn_enabled).await;
|
||||
self.metrics.bytes_sent.fetch_add(frame.len() as u64, Ordering::Relaxed);
|
||||
}
|
||||
_ => {}
|
||||
|
|
@ -568,7 +590,7 @@ impl Bridge {
|
|||
ProtocolAction::SendDatagram(frame) => frame,
|
||||
_ => anyhow::bail!("protocol did not emit handshake datagram"),
|
||||
};
|
||||
socket.send(&handshake_frame).await?;
|
||||
send_datagram(&socket, &handshake_frame, self.turn_enabled).await?;
|
||||
self.metrics.bytes_sent.fetch_add(handshake_frame.len() as u64, Ordering::Relaxed);
|
||||
|
||||
let mut buf = vec![0_u8; 4096];
|
||||
|
|
@ -580,7 +602,16 @@ impl Bridge {
|
|||
.context("handshake timeout waiting server response")??;
|
||||
self.metrics.bytes_recv.fetch_add(size as u64, Ordering::Relaxed);
|
||||
|
||||
let inbound = Bytes::copy_from_slice(&buf[..size]);
|
||||
let inbound = if self.turn_enabled && size >= 4 && buf[0] == 0x40 && buf[1] == 0x00 {
|
||||
let len = u16::from_be_bytes([buf[2], buf[3]]) as usize;
|
||||
if 4 + len <= size {
|
||||
Bytes::copy_from_slice(&buf[4..4+len])
|
||||
} else {
|
||||
Bytes::copy_from_slice(&buf[..size])
|
||||
}
|
||||
} else {
|
||||
Bytes::copy_from_slice(&buf[..size])
|
||||
};
|
||||
machine.on_event(OstpEvent::Inbound(inbound))?;
|
||||
let rtt_ms = start.elapsed().as_secs_f64() * 1000.0;
|
||||
|
||||
|
|
|
|||
|
|
@ -61,10 +61,37 @@ pub async fn run_linux_tunnel(
|
|||
if in_path {
|
||||
tun2socks_exe = std::path::PathBuf::from("tun2socks");
|
||||
} else {
|
||||
return Err(anyhow!("tun2socks executable not found in local dir or PATH. Please ensure dependencies are present."));
|
||||
return Err(anyhow!(
|
||||
"CRITICAL: 'tun2socks' binary is missing!\n\
|
||||
OSTP requires tun2socks for TUN mode on Linux. Please download the appropriate binary for your architecture from: \n\
|
||||
https://github.com/xjasonlyu/tun2socks/releases \n\
|
||||
and place it in the same directory as the ostp executable ({}), or install it globally in your PATH.",
|
||||
dir.display()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// 1.5. Pre-flight system checks
|
||||
let is_root = Command::new("id")
|
||||
.arg("-u")
|
||||
.output()
|
||||
.map(|o| String::from_utf8_lossy(&o.stdout).trim() == "0")
|
||||
.unwrap_or(false);
|
||||
|
||||
if !is_root {
|
||||
return Err(anyhow!("FATAL: OSTP TUN mode requires root privileges on Linux. Please run via sudo."));
|
||||
}
|
||||
|
||||
let has_ip_cmd = Command::new("which")
|
||||
.arg("ip")
|
||||
.output()
|
||||
.map(|o| o.status.success())
|
||||
.unwrap_or(false);
|
||||
|
||||
if !has_ip_cmd {
|
||||
return Err(anyhow!("FATAL: 'ip' command not found. OSTP TUN mode requires 'iproute2' package to be installed."));
|
||||
}
|
||||
|
||||
// 2. Resolve Server IP for routing table exclusion
|
||||
let server_ip = config.ostp.server_addr.to_socket_addrs()
|
||||
.map_err(|e| anyhow!("Failed to resolve remote server IP: {}", e))?
|
||||
|
|
|
|||
|
|
@ -41,7 +41,13 @@ pub async fn run_wintun_tunnel(
|
|||
let tun2socks_exe = dir.join("tun2socks.exe");
|
||||
|
||||
if !tun2socks_exe.exists() {
|
||||
return Err(anyhow!("tun2socks.exe not found in current directory! Please make sure the pre-packaged dependency is present near the executable."));
|
||||
return Err(anyhow!(
|
||||
"CRITICAL: 'tun2socks.exe' binary is missing!\n\
|
||||
OSTP requires tun2socks for TUN mode on Windows. Please download the appropriate binary from: \n\
|
||||
https://github.com/xjasonlyu/tun2socks/releases \n\
|
||||
and place it in the same directory as the ostp executable ({}).",
|
||||
dir.display()
|
||||
));
|
||||
}
|
||||
|
||||
// 2. Resolve Server IP for routing table exclusion
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ impl TryFrom<u8> for FrameKind {
|
|||
pub struct FrameHeader {
|
||||
pub version: u8,
|
||||
pub kind: FrameKind,
|
||||
pub flags: u8,
|
||||
pub stream_id: u16,
|
||||
pub payload_len: u32,
|
||||
pub pad_len: u16,
|
||||
|
|
@ -45,8 +44,7 @@ impl FrameHeader {
|
|||
pub fn encode(&self, out: &mut BytesMut) {
|
||||
out.put_u8(self.version);
|
||||
out.put_u8(self.kind as u8);
|
||||
out.put_u8(self.flags);
|
||||
out.put_u8(0); // reserved
|
||||
out.put_u16(0); // 2 reserved bytes
|
||||
out.put_u16(self.stream_id);
|
||||
out.put_u32(self.payload_len);
|
||||
out.put_u16(self.pad_len);
|
||||
|
|
@ -59,7 +57,7 @@ impl FrameHeader {
|
|||
|
||||
let version = buf[0];
|
||||
let kind = FrameKind::try_from(buf[1])?;
|
||||
let flags = buf[2];
|
||||
// buf[2] and buf[3] are reserved
|
||||
let stream_id = u16::from_be_bytes([buf[4], buf[5]]);
|
||||
let payload_len = u32::from_be_bytes([buf[6], buf[7], buf[8], buf[9]]);
|
||||
let pad_len = u16::from_be_bytes([buf[10], buf[11]]);
|
||||
|
|
@ -67,7 +65,6 @@ impl FrameHeader {
|
|||
Ok(Self {
|
||||
version,
|
||||
kind,
|
||||
flags,
|
||||
stream_id,
|
||||
payload_len,
|
||||
pad_len,
|
||||
|
|
|
|||
|
|
@ -294,6 +294,7 @@ impl ProtocolMachine {
|
|||
if nonce == self.expected_recv_nonce {
|
||||
app_actions.push(action);
|
||||
self.expected_recv_nonce = self.expected_recv_nonce.checked_add(1).ok_or_else(|| {
|
||||
tracing::error!("FATAL: Recv nonce sequence exhausted (2^64 frames). Session must be terminated to prevent AEAD keystream reuse!");
|
||||
ProtocolError::Crypto("recv nonce sequence exhausted".to_string())
|
||||
})?;
|
||||
|
||||
|
|
@ -301,6 +302,7 @@ impl ProtocolMachine {
|
|||
while let Some(buffered_action) = self.reorder_buffer.remove(&self.expected_recv_nonce) {
|
||||
app_actions.push(buffered_action);
|
||||
self.expected_recv_nonce = self.expected_recv_nonce.checked_add(1).ok_or_else(|| {
|
||||
tracing::error!("FATAL: Recv nonce sequence exhausted (2^64 frames). Session must be terminated to prevent AEAD keystream reuse!");
|
||||
ProtocolError::Crypto("recv nonce sequence exhausted".to_string())
|
||||
})?;
|
||||
}
|
||||
|
|
@ -359,7 +361,6 @@ impl ProtocolMachine {
|
|||
let header = FrameHeader {
|
||||
version: 1,
|
||||
kind,
|
||||
flags: 0,
|
||||
stream_id,
|
||||
payload_len: payload.len() as u32,
|
||||
pad_len: padding.len() as u16,
|
||||
|
|
@ -379,6 +380,7 @@ impl ProtocolMachine {
|
|||
|
||||
let nonce = self.send_nonce;
|
||||
self.send_nonce = self.send_nonce.checked_add(1).ok_or_else(|| {
|
||||
tracing::error!("FATAL: Send nonce sequence exhausted (2^64 frames). Session must be terminated to prevent AEAD keystream reuse!");
|
||||
ProtocolError::Crypto("send nonce sequence exhausted".to_string())
|
||||
})?;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue