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
|
target: mipsel-unknown-linux-musl
|
||||||
artifact_name: ostp
|
artifact_name: ostp
|
||||||
release_name: ostp-linux-mipsle.tar.gz
|
release_name: ostp-linux-mipsle.tar.gz
|
||||||
tun2socks_arch: linux-mipsle
|
tun2socks_arch: linux-mipsle-softfloat
|
||||||
use_cross: true
|
use_cross: true
|
||||||
toolchain: nightly
|
toolchain: nightly
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
target: riscv64gc-unknown-linux-gnu
|
target: riscv64gc-unknown-linux-gnu
|
||||||
artifact_name: ostp
|
artifact_name: ostp
|
||||||
release_name: ostp-linux-riscv64.tar.gz
|
release_name: ostp-linux-riscv64.tar.gz
|
||||||
tun2socks_arch: linux-riscv64
|
|
||||||
use_cross: true
|
use_cross: true
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
|
|
@ -162,7 +161,7 @@ jobs:
|
||||||
# 1. Acquire tun2socks
|
# 1. Acquire tun2socks
|
||||||
Invoke-WebRequest -Uri "https://github.com/xjasonlyu/tun2socks/releases/download/v2.6.0/tun2socks-${{ matrix.tun2socks_arch }}.zip" -OutFile "tun2socks.zip"
|
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
|
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
|
# 2. Acquire wintun
|
||||||
Invoke-WebRequest -Uri "https://www.wintun.net/builds/wintun-0.14.1.zip" -OutFile "wintun.zip"
|
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
|
Expand-Archive -Path "wintun.zip" -DestinationPath "wintun_temp" -Force
|
||||||
|
|
@ -177,7 +176,7 @@ jobs:
|
||||||
cd target/${{ matrix.target }}/release
|
cd target/${{ matrix.target }}/release
|
||||||
# All platforms in tun2socks v2.6.0 use .zip packaging
|
# 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"
|
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"
|
unzip -o "tun2socks.zip"
|
||||||
find . -maxdepth 2 -name "tun2socks*" ! -name "*.zip" -type f -exec mv {} ./tun2socks \;
|
find . -maxdepth 2 -name "tun2socks*" ! -name "*.zip" -type f -exec mv {} ./tun2socks \;
|
||||||
rm -f "tun2socks.zip"
|
rm -f "tun2socks.zip"
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,18 @@ pub struct BridgeMetrics {
|
||||||
pub bytes_recv: AtomicU64,
|
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 {
|
struct SessionState {
|
||||||
socket: Arc<UdpSocket>,
|
socket: Arc<UdpSocket>,
|
||||||
machine: ProtocolMachine,
|
machine: ProtocolMachine,
|
||||||
|
|
@ -144,12 +156,22 @@ impl Bridge {
|
||||||
let socket = Arc::new(sock);
|
let socket = Arc::new(sock);
|
||||||
let socket_clone = socket.clone();
|
let socket_clone = socket.clone();
|
||||||
let udp_tx_clone = udp_tx.clone();
|
let udp_tx_clone = udp_tx.clone();
|
||||||
|
let is_turn = self.turn_enabled;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut buf = vec![0_u8; 65535];
|
let mut buf = vec![0_u8; 65535];
|
||||||
loop {
|
loop {
|
||||||
match socket_clone.recv(&mut buf).await {
|
match socket_clone.recv(&mut buf).await {
|
||||||
Ok(n) => {
|
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() {
|
if udp_tx_clone.send((idx, inbound)).await.is_err() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -274,7 +296,7 @@ impl Bridge {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ProtocolAction::SendDatagram(frame) => {
|
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);
|
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());
|
let out_payload = Bytes::from(relay_msg.encode());
|
||||||
match session.machine.on_event(OstpEvent::Outbound(stream_id, out_payload)) {
|
match session.machine.on_event(OstpEvent::Outbound(stream_id, out_payload)) {
|
||||||
Ok(ProtocolAction::SendDatagram(frame)) => {
|
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);
|
self.metrics.bytes_sent.fetch_add(frame.len() as u64, Ordering::Relaxed);
|
||||||
if self.debug {
|
if self.debug {
|
||||||
let _ = tx.send(UiEvent::Log(format!(
|
let _ = tx.send(UiEvent::Log(format!(
|
||||||
|
|
@ -333,7 +355,7 @@ impl Bridge {
|
||||||
let mut sent = 0usize;
|
let mut sent = 0usize;
|
||||||
for item in list {
|
for item in list {
|
||||||
if let ProtocolAction::SendDatagram(frame) = item {
|
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);
|
self.metrics.bytes_sent.fetch_add(frame.len() as u64, Ordering::Relaxed);
|
||||||
sent += 1;
|
sent += 1;
|
||||||
}
|
}
|
||||||
|
|
@ -436,7 +458,7 @@ impl Bridge {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ProtocolAction::SendDatagram(frame) => {
|
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);
|
self.metrics.bytes_sent.fetch_add(frame.len() as u64, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
@ -568,7 +590,7 @@ impl Bridge {
|
||||||
ProtocolAction::SendDatagram(frame) => frame,
|
ProtocolAction::SendDatagram(frame) => frame,
|
||||||
_ => anyhow::bail!("protocol did not emit handshake datagram"),
|
_ => 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);
|
self.metrics.bytes_sent.fetch_add(handshake_frame.len() as u64, Ordering::Relaxed);
|
||||||
|
|
||||||
let mut buf = vec![0_u8; 4096];
|
let mut buf = vec![0_u8; 4096];
|
||||||
|
|
@ -580,7 +602,16 @@ impl Bridge {
|
||||||
.context("handshake timeout waiting server response")??;
|
.context("handshake timeout waiting server response")??;
|
||||||
self.metrics.bytes_recv.fetch_add(size as u64, Ordering::Relaxed);
|
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))?;
|
machine.on_event(OstpEvent::Inbound(inbound))?;
|
||||||
let rtt_ms = start.elapsed().as_secs_f64() * 1000.0;
|
let rtt_ms = start.elapsed().as_secs_f64() * 1000.0;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,10 +61,37 @@ pub async fn run_linux_tunnel(
|
||||||
if in_path {
|
if in_path {
|
||||||
tun2socks_exe = std::path::PathBuf::from("tun2socks");
|
tun2socks_exe = std::path::PathBuf::from("tun2socks");
|
||||||
} else {
|
} 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
|
// 2. Resolve Server IP for routing table exclusion
|
||||||
let server_ip = config.ostp.server_addr.to_socket_addrs()
|
let server_ip = config.ostp.server_addr.to_socket_addrs()
|
||||||
.map_err(|e| anyhow!("Failed to resolve remote server IP: {}", e))?
|
.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");
|
let tun2socks_exe = dir.join("tun2socks.exe");
|
||||||
|
|
||||||
if !tun2socks_exe.exists() {
|
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
|
// 2. Resolve Server IP for routing table exclusion
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,6 @@ impl TryFrom<u8> for FrameKind {
|
||||||
pub struct FrameHeader {
|
pub struct FrameHeader {
|
||||||
pub version: u8,
|
pub version: u8,
|
||||||
pub kind: FrameKind,
|
pub kind: FrameKind,
|
||||||
pub flags: u8,
|
|
||||||
pub stream_id: u16,
|
pub stream_id: u16,
|
||||||
pub payload_len: u32,
|
pub payload_len: u32,
|
||||||
pub pad_len: u16,
|
pub pad_len: u16,
|
||||||
|
|
@ -45,8 +44,7 @@ impl FrameHeader {
|
||||||
pub fn encode(&self, out: &mut BytesMut) {
|
pub fn encode(&self, out: &mut BytesMut) {
|
||||||
out.put_u8(self.version);
|
out.put_u8(self.version);
|
||||||
out.put_u8(self.kind as u8);
|
out.put_u8(self.kind as u8);
|
||||||
out.put_u8(self.flags);
|
out.put_u16(0); // 2 reserved bytes
|
||||||
out.put_u8(0); // reserved
|
|
||||||
out.put_u16(self.stream_id);
|
out.put_u16(self.stream_id);
|
||||||
out.put_u32(self.payload_len);
|
out.put_u32(self.payload_len);
|
||||||
out.put_u16(self.pad_len);
|
out.put_u16(self.pad_len);
|
||||||
|
|
@ -59,7 +57,7 @@ impl FrameHeader {
|
||||||
|
|
||||||
let version = buf[0];
|
let version = buf[0];
|
||||||
let kind = FrameKind::try_from(buf[1])?;
|
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 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 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]]);
|
let pad_len = u16::from_be_bytes([buf[10], buf[11]]);
|
||||||
|
|
@ -67,7 +65,6 @@ impl FrameHeader {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
version,
|
version,
|
||||||
kind,
|
kind,
|
||||||
flags,
|
|
||||||
stream_id,
|
stream_id,
|
||||||
payload_len,
|
payload_len,
|
||||||
pad_len,
|
pad_len,
|
||||||
|
|
|
||||||
|
|
@ -294,6 +294,7 @@ impl ProtocolMachine {
|
||||||
if nonce == self.expected_recv_nonce {
|
if nonce == self.expected_recv_nonce {
|
||||||
app_actions.push(action);
|
app_actions.push(action);
|
||||||
self.expected_recv_nonce = self.expected_recv_nonce.checked_add(1).ok_or_else(|| {
|
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())
|
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) {
|
while let Some(buffered_action) = self.reorder_buffer.remove(&self.expected_recv_nonce) {
|
||||||
app_actions.push(buffered_action);
|
app_actions.push(buffered_action);
|
||||||
self.expected_recv_nonce = self.expected_recv_nonce.checked_add(1).ok_or_else(|| {
|
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())
|
ProtocolError::Crypto("recv nonce sequence exhausted".to_string())
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
@ -359,7 +361,6 @@ impl ProtocolMachine {
|
||||||
let header = FrameHeader {
|
let header = FrameHeader {
|
||||||
version: 1,
|
version: 1,
|
||||||
kind,
|
kind,
|
||||||
flags: 0,
|
|
||||||
stream_id,
|
stream_id,
|
||||||
payload_len: payload.len() as u32,
|
payload_len: payload.len() as u32,
|
||||||
pad_len: padding.len() as u16,
|
pad_len: padding.len() as u16,
|
||||||
|
|
@ -379,6 +380,7 @@ impl ProtocolMachine {
|
||||||
|
|
||||||
let nonce = self.send_nonce;
|
let nonce = self.send_nonce;
|
||||||
self.send_nonce = self.send_nonce.checked_add(1).ok_or_else(|| {
|
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())
|
ProtocolError::Crypto("send nonce sequence exhausted".to_string())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue