Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 75 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ jobs:
runner: ubuntu-latest
target: x86_64-unknown-linux-gnu
artifact_name: slipstream-linux-x86_64
- name: windows-x86_64
runner: windows-latest
target: x86_64-pc-windows-msvc
artifact_name: slipstream-windows-x86_64
steps:
- name: Check out slipstream-rust
uses: actions/checkout@v4
Expand All @@ -144,16 +148,77 @@ jobs:
echo "OPENSSL_ROOT_DIR=$(brew --prefix openssl@3)" >> "$GITHUB_ENV"
echo "PKG_CONFIG_PATH=$(brew --prefix openssl@3)/lib/pkgconfig" >> "$GITHUB_ENV"

- name: Install build dependencies (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
choco install cmake pkgconfiglite strawberryperl -y

# Find and configure OpenSSL
$opensslPaths = @(
"C:\Program Files\OpenSSL",
"C:\Program Files\OpenSSL-Win64",
"C:\OpenSSL-Win64",
"C:\OpenSSL"
)

foreach ($path in $opensslPaths) {
if (Test-Path $path) {
$libDir = Get-ChildItem -Path "$path\lib" -Filter "libcrypto*.lib" -Recurse -ErrorAction SilentlyContinue |
Select-Object -First 1 | ForEach-Object { $_.DirectoryName }

if ($libDir) {
echo "OPENSSL_DIR=$path" >> $env:GITHUB_ENV
echo "OPENSSL_ROOT_DIR=$path" >> $env:GITHUB_ENV
echo "OPENSSL_LIB_DIR=$libDir" >> $env:GITHUB_ENV
echo "OPENSSL_INCLUDE_DIR=$path\include" >> $env:GITHUB_ENV
Write-Host "OpenSSL configured at $path (libs in $libDir)"
break
}
}
}

# Setup Perl for OpenSSL vendored build
$perlPaths = @(
"C:\Strawberry\perl\bin",
"C:\Program Files\Strawberry\perl\bin",
"${env:ProgramFiles}\Strawberry\perl\bin"
)

foreach ($path in $perlPaths) {
$exe = Join-Path $path "perl.exe"
if (Test-Path $exe) {
$env:Path = "$path;$env:Path"
echo "PATH=$path`;$env:Path" >> $env:GITHUB_ENV
echo "PERL=$exe" >> $env:GITHUB_ENV
Write-Host "Found Strawberry Perl at $path"

# Install Locale::Maketext::Simple if needed
$moduleCheck = & $exe -e "use Locale::Maketext::Simple; print 'OK'" 2>&1
if ($LASTEXITCODE -ne 0) {
$cpanmExe = Join-Path $path "cpanm.bat"
if (Test-Path $cpanmExe) {
& $cpanmExe --notest Locale::Maketext::Simple 2>&1 | Out-String | Write-Host
}
}
break
}
}

- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}

- name: Build slipstream-client
run: cargo build -p slipstream-client --release --target ${{ matrix.target }}
shell: bash
run: |
cargo build -p slipstream-client --release --target ${{ matrix.target }}

- name: Build slipstream-server
run: cargo build -p slipstream-server --release --target ${{ matrix.target }}
shell: bash
run: |
cargo build -p slipstream-server --release --target ${{ matrix.target }}

- name: Package binaries
shell: bash
Expand All @@ -162,13 +227,17 @@ jobs:
dist_dir="dist"
base_name="${{ matrix.artifact_name }}"
bin_dir="target/${{ matrix.target }}/release"
bin_suffix=""
if [[ "${RUNNER_OS}" == "Windows" ]]; then
bin_suffix=".exe"
fi
mkdir -p "${dist_dir}"
cp "${bin_dir}/slipstream-client" "${dist_dir}/"
cp "${bin_dir}/slipstream-server" "${dist_dir}/"
cp "${bin_dir}/slipstream-client${bin_suffix}" "${dist_dir}/"
cp "${bin_dir}/slipstream-server${bin_suffix}" "${dist_dir}/"
if command -v sha256sum >/dev/null 2>&1; then
(cd "${dist_dir}" && sha256sum slipstream-client slipstream-server > "${base_name}.sha256")
(cd "${dist_dir}" && sha256sum "slipstream-client${bin_suffix}" "slipstream-server${bin_suffix}" > "${base_name}.sha256")
else
(cd "${dist_dir}" && shasum -a 256 slipstream-client slipstream-server > "${base_name}.sha256")
(cd "${dist_dir}" && shasum -a 256 "slipstream-client${bin_suffix}" "slipstream-server${bin_suffix}" > "${base_name}.sha256")
fi

- name: Upload binaries
Expand Down
24 changes: 24 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/slipstream-client/src/dns/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ pub(crate) fn add_paths(
return Ok(());
}

let mut local_storage: libc::sockaddr_storage = unsafe { std::mem::zeroed() };
let mut local_storage: slipstream_ffi::SockaddrStorage = unsafe { std::mem::zeroed() };
let ret = unsafe { picoquic_get_path_addr(cnx, 0, 1, &mut local_storage) };
if ret != 0 {
return Ok(());
Expand Down
6 changes: 3 additions & 3 deletions crates/slipstream-client/src/dns/poll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pub(crate) async fn send_poll_queries(
cnx: *mut picoquic_cnx_t,
udp: &TokioUdpSocket,
config: &ClientConfig<'_>,
local_addr_storage: &mut libc::sockaddr_storage,
local_addr_storage: &mut slipstream_ffi::SockaddrStorage,
dns_id: &mut u16,
resolver: &mut ResolverState,
remaining: &mut usize,
Expand All @@ -54,8 +54,8 @@ pub(crate) async fn send_poll_queries(
}

let mut send_length: libc::size_t = 0;
let mut addr_to: libc::sockaddr_storage = unsafe { std::mem::zeroed() };
let mut addr_from: libc::sockaddr_storage = unsafe { std::mem::zeroed() };
let mut addr_to: slipstream_ffi::SockaddrStorage = unsafe { std::mem::zeroed() };
let mut addr_from: slipstream_ffi::SockaddrStorage = unsafe { std::mem::zeroed() };
let mut if_index: libc::c_int = 0;
let ret = unsafe {
picoquic_prepare_packet_ex(
Expand Down
6 changes: 3 additions & 3 deletions crates/slipstream-client/src/dns/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use super::debug::DebugMetrics;

pub(crate) struct ResolverState {
pub(crate) addr: SocketAddr,
pub(crate) storage: libc::sockaddr_storage,
pub(crate) local_addr_storage: Option<libc::sockaddr_storage>,
pub(crate) storage: slipstream_ffi::SockaddrStorage,
pub(crate) local_addr_storage: Option<slipstream_ffi::SockaddrStorage>,
pub(crate) mode: ResolverMode,
pub(crate) added: bool,
pub(crate) path_id: libc::c_int,
Expand Down Expand Up @@ -93,7 +93,7 @@ pub(crate) fn reset_resolver_path(resolver: &mut ResolverState) {
}

pub(crate) fn sockaddr_storage_to_socket_addr(
storage: &libc::sockaddr_storage,
storage: &slipstream_ffi::SockaddrStorage,
) -> Result<SocketAddr, ClientError> {
slipstream_ffi::sockaddr_storage_to_socket_addr(storage).map_err(ClientError::new)
}
Expand Down
2 changes: 1 addition & 1 deletion crates/slipstream-client/src/dns/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const MAX_POLL_BURST: usize = PICOQUIC_PACKET_LOOP_RECV_MAX;

pub(crate) struct DnsResponseContext<'a> {
pub(crate) quic: *mut picoquic_quic_t,
pub(crate) local_addr_storage: &'a libc::sockaddr_storage,
pub(crate) local_addr_storage: &'a slipstream_ffi::SockaddrStorage,
pub(crate) resolvers: &'a mut [ResolverState],
}

Expand Down
4 changes: 2 additions & 2 deletions crates/slipstream-client/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,8 +355,8 @@ pub async fn run_client(config: &ClientConfig<'_>) -> Result<i32, ClientError> {
for _ in 0..packet_loop_send_max {
let current_time = unsafe { picoquic_current_time() };
let mut send_length: libc::size_t = 0;
let mut addr_to: libc::sockaddr_storage = unsafe { std::mem::zeroed() };
let mut addr_from: libc::sockaddr_storage = unsafe { std::mem::zeroed() };
let mut addr_to: slipstream_ffi::SockaddrStorage = unsafe { std::mem::zeroed() };
let mut addr_from: slipstream_ffi::SockaddrStorage = unsafe { std::mem::zeroed() };
let mut if_index: libc::c_int = 0;
let mut log_cid = picoquic_connection_id_t {
id: [0; PICOQUIC_CONNECTION_ID_MAX_SIZE],
Expand Down
2 changes: 1 addition & 1 deletion crates/slipstream-client/src/runtime/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ pub(crate) fn drain_path_events(
}

fn path_peer_addr(cnx: *mut picoquic_cnx_t, unique_path_id: u64) -> Option<SocketAddr> {
let mut storage: libc::sockaddr_storage = unsafe { std::mem::zeroed() };
let mut storage: slipstream_ffi::SockaddrStorage = unsafe { std::mem::zeroed() };
let ret = unsafe { picoquic_get_path_addr(cnx, unique_path_id, 2, &mut storage) };
if ret != 0 {
return None;
Expand Down
19 changes: 16 additions & 3 deletions crates/slipstream-client/src/runtime/setup.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::error::ClientError;
use slipstream_core::net::{bind_first_resolved, bind_tcp_listener_addr, bind_udp_socket_addr};
use std::net::{Ipv6Addr, SocketAddr, SocketAddrV6};
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
use tokio::net::{TcpListener as TokioTcpListener, UdpSocket as TokioUdpSocket};
use tracing::warn;

pub(crate) fn compute_mtu(domain_len: usize) -> Result<u32, ClientError> {
if domain_len >= 240 {
Expand All @@ -19,8 +20,20 @@ pub(crate) fn compute_mtu(domain_len: usize) -> Result<u32, ClientError> {
}

pub(crate) async fn bind_udp_socket() -> Result<TokioUdpSocket, ClientError> {
let bind_addr = SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, 0, 0, 0));
bind_udp_socket_addr(bind_addr, "UDP socket").map_err(map_io)
// Try IPv6 dual-stack first (works on most systems), fall back to IPv4
let bind_addr_v6 = SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, 0, 0, 0));
match bind_udp_socket_addr(bind_addr_v6, "UDP socket") {
Ok(socket) => Ok(socket),
Err(err) => {
// Fall back to IPv4 if IPv6 is not available (common on Windows)
warn!(
"Failed to bind UDP socket on IPv6 {}: {}. Falling back to IPv4",
bind_addr_v6, err
);
let bind_addr_v4 = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0));
bind_udp_socket_addr(bind_addr_v4, "UDP socket").map_err(map_io)
}
}
}

pub(crate) async fn bind_tcp_listener(
Expand Down
26 changes: 21 additions & 5 deletions crates/slipstream-core/src/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,32 @@ use tokio::net::{lookup_host, TcpListener as TokioTcpListener, UdpSocket as Toki

pub fn is_transient_udp_error(err: &Error) -> bool {
match err.kind() {
ErrorKind::WouldBlock | ErrorKind::TimedOut | ErrorKind::Interrupted => {
ErrorKind::WouldBlock
| ErrorKind::TimedOut
| ErrorKind::Interrupted
| ErrorKind::ConnectionReset => {
return true;
}
_ => {}
}

matches!(
err.raw_os_error(),
Some(code) if code == libc::ENETUNREACH || code == libc::EHOSTUNREACH
)
#[cfg(not(windows))]
{
matches!(
err.raw_os_error(),
Some(code) if code == libc::ENETUNREACH || code == libc::EHOSTUNREACH
)
}
#[cfg(windows)]
{
// Windows uses WinSock error codes: WSAENETUNREACH = 10051, WSAEHOSTUNREACH = 10065
const WSAENETUNREACH: i32 = 10051;
const WSAEHOSTUNREACH: i32 = 10065;
matches!(
err.raw_os_error(),
Some(code) if code == WSAENETUNREACH || code == WSAEHOSTUNREACH
)
}
}

pub async fn bind_first_resolved<T, F>(
Expand Down
6 changes: 6 additions & 0 deletions crates/slipstream-ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,17 @@ license = "Apache-2.0"
repository = "https://github.com/Mygod/slipstream-rust"
readme = "../../README.md"

[build-dependencies]
cc = "1.0"

[dependencies]
libc = "0.2"
openssl-sys = { version = "0.9", optional = true, features = ["vendored"] }
slipstream-core = { path = "../slipstream-core" }

[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = ["winsock2", "ws2def", "ws2ipdef"] }

[features]
default = []
openssl-vendored = ["dep:openssl-sys", "openssl-sys/vendored", "openssl-static"]
Expand Down
14 changes: 13 additions & 1 deletion crates/slipstream-ffi/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {

let openssl_paths = resolve_openssl_paths();
let target = env::var("TARGET").unwrap_or_default();
let is_windows = target.contains("windows") || target.contains("pc-windows");
let auto_build = env_flag("PICOQUIC_AUTO_BUILD", true);
let explicit_picoquic_include = env::var_os("PICOQUIC_INCLUDE_DIR").is_some();
let explicit_picoquic_lib = env::var_os("PICOQUIC_LIB_DIR").is_some();
Expand Down Expand Up @@ -225,14 +226,25 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
if cfg!(feature = "openssl-static") {
println!("cargo:rustc-link-lib=static=ssl");
println!("cargo:rustc-link-lib=static=crypto");
} else if is_windows {
if let Ok(openssl_lib_dir) = env::var("OPENSSL_LIB_DIR") {
println!("cargo:rustc-link-search=native={}", openssl_lib_dir);
}
println!("cargo:rustc-link-lib=dylib=libssl");
println!("cargo:rustc-link-lib=dylib=libcrypto");
} else {
println!("cargo:rustc-link-lib=dylib=ssl");
println!("cargo:rustc-link-lib=dylib=crypto");
}
}

if !target.contains("android") {
println!("cargo:rustc-link-lib=dylib=pthread");
if is_windows {
println!("cargo:rustc-link-lib=dylib=ws2_32");
println!("cargo:rustc-link-lib=dylib=bcrypt");
} else {
println!("cargo:rustc-link-lib=dylib=pthread");
}
} else {
maybe_link_android_builtins(&target, &cc);
}
Expand Down
Loading
Loading