Skip to content

Commit

Permalink
std.net: use send/recv for streams, support vectorized io on windows
Browse files Browse the repository at this point in the history
The primary goal of this change is to use `send` and `recv` for network
streams, this also enables passing per-message flags like `MSG_NOSIGNAL`
instead of installing a SIGPIPE handler.

- Alongside using recv and send, windows now uses WSARecvFrom and WSASendTo
  for readv and writev. This enables vectorized network I/O on windows.

Introduces a mostly platform agnostic iovec as `std.net.iovec` and
`std.net.iovec_const`. This provides a singular interface to vectorized
network I/O on windows and posix.

- The only problem this introduces is windows using `u32` for lengths
  and posix using `usize`. This requires any code currently using `usize`
  to `@intCast` into the length field.

- As part of this change, `std.posix.iovec` and `std.windows.ws2_32.WSABUF`
  now use `.ptr` and `.len` instead of their posix/windows specific names.

Removes std.os.windows.ws2_32.WSARecvMsg, as it is not present in ws2_32.dll
and must be fetched via WSAIoctl.

- The `sendto` and `recvfrom` wrappers around `WSASendTo` and `WSARecvFrom`
  have been removed from `std.os.windows`. These functions are already
  provided by `ws2_32` and are not needed.

A os-agnostic IoVec struct (re #7699) would likely be more convienent to
use, but I could not design an interface that both works well and is
reasonable to interact with.

A potential problem this PR introduces is passing `WSABUF_const` to
`WSASendTo` and `WSASend` is that this type doesn't actually exist in
windows. Windows makes no guarantees that the buffers will not be modified.

This enables the "general client/server API coverage" test on windows.
The comment on the skip claims that the test was never passing, however
during my tests the test passed 100% of the time.
  • Loading branch information
truemedian committed Apr 23, 2024
1 parent b87baad commit 74a9a6a
Show file tree
Hide file tree
Showing 24 changed files with 433 additions and 363 deletions.
2 changes: 1 addition & 1 deletion lib/compiler/objcopy.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1285,7 +1285,7 @@ const ElfFileHelper = struct {
for (consolidated.items) |cmd| {
switch (cmd) {
.write_data => |data| {
var iovec = [_]std.posix.iovec_const{.{ .iov_base = data.data.ptr, .iov_len = data.data.len }};
var iovec = [_]std.posix.iovec_const{.{ .ptr = data.data.ptr, .len = data.data.len }};
try out_file.pwritevAll(&iovec, data.out_offset);
},
.copy_range => |range| {
Expand Down
88 changes: 44 additions & 44 deletions lib/std/crypto/tls/Client.zig
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ pub const StreamInterface = struct {
/// The `iovecs` parameter is mutable because so that function may to
/// mutate the fields in order to handle partial reads from the underlying
/// stream layer.
pub fn readv(this: @This(), iovecs: []std.posix.iovec) ReadError!usize {
pub fn readv(this: @This(), iovecs: []std.net.iovec) ReadError!usize {
_ = .{ this, iovecs };
@panic("unimplemented");
}
Expand All @@ -72,7 +72,7 @@ pub const StreamInterface = struct {

/// Returns the number of bytes read, which may be less than the buffer
/// space provided. A short read does not indicate end-of-stream.
pub fn writev(this: @This(), iovecs: []const std.posix.iovec_const) WriteError!usize {
pub fn writev(this: @This(), iovecs: []const std.net.iovec_const) WriteError!usize {
_ = .{ this, iovecs };
@panic("unimplemented");
}
Expand All @@ -81,7 +81,7 @@ pub const StreamInterface = struct {
/// space provided, indicating end-of-stream.
/// The `iovecs` parameter is mutable in case this function needs to mutate
/// the fields in order to handle partial writes from the underlying layer.
pub fn writevAll(this: @This(), iovecs: []std.posix.iovec_const) WriteError!usize {
pub fn writevAll(this: @This(), iovecs: []std.net.iovec_const) WriteError!usize {
// This can be implemented in terms of writev, or specialized if desired.
_ = .{ this, iovecs };
@panic("unimplemented");
Expand Down Expand Up @@ -215,14 +215,14 @@ pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) In
} ++ int2(@intCast(out_handshake.len + host_len)) ++ out_handshake;

{
var iovecs = [_]std.posix.iovec_const{
var iovecs = [_]std.net.iovec_const{
.{
.iov_base = &plaintext_header,
.iov_len = plaintext_header.len,
.ptr = &plaintext_header,
.len = plaintext_header.len,
},
.{
.iov_base = host.ptr,
.iov_len = host.len,
.ptr = host.ptr,
.len = @intCast(host.len),
},
};
try stream.writevAll(&iovecs);
Expand Down Expand Up @@ -677,9 +677,9 @@ pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) In
P.AEAD.encrypt(ciphertext, auth_tag, &out_cleartext, ad, nonce, p.client_handshake_key);

const both_msgs = client_change_cipher_spec_msg ++ finished_msg;
var both_msgs_vec = [_]std.posix.iovec_const{.{
.iov_base = &both_msgs,
.iov_len = both_msgs.len,
var both_msgs_vec = [_]std.net.iovec_const{.{
.ptr = &both_msgs,
.len = both_msgs.len,
}};
try stream.writevAll(&both_msgs_vec);

Expand Down Expand Up @@ -755,7 +755,7 @@ pub fn writeAllEnd(c: *Client, stream: anytype, bytes: []const u8, end: bool) !v
/// TLS session, or a truncation attack.
pub fn writeEnd(c: *Client, stream: anytype, bytes: []const u8, end: bool) !usize {
var ciphertext_buf: [tls.max_ciphertext_record_len * 4]u8 = undefined;
var iovecs_buf: [6]std.posix.iovec_const = undefined;
var iovecs_buf: [6]std.net.iovec_const = undefined;
var prepared = prepareCiphertextRecord(c, &iovecs_buf, &ciphertext_buf, bytes, .application_data);
if (end) {
prepared.iovec_end += prepareCiphertextRecord(
Expand All @@ -776,8 +776,8 @@ pub fn writeEnd(c: *Client, stream: anytype, bytes: []const u8, end: bool) !usiz
var total_amt: usize = 0;
while (true) {
var amt = try stream.writev(iovecs_buf[i..iovec_end]);
while (amt >= iovecs_buf[i].iov_len) {
const encrypted_amt = iovecs_buf[i].iov_len;
while (amt >= iovecs_buf[i].len) {
const encrypted_amt = iovecs_buf[i].len;
total_amt += encrypted_amt - overhead_len;
amt -= encrypted_amt;
i += 1;
Expand All @@ -789,14 +789,14 @@ pub fn writeEnd(c: *Client, stream: anytype, bytes: []const u8, end: bool) !usiz
// not sent; otherwise the caller would not know to retry the call.
if (amt == 0 and (!end or i < iovec_end - 1)) return total_amt;
}
iovecs_buf[i].iov_base += amt;
iovecs_buf[i].iov_len -= amt;
iovecs_buf[i].ptr += amt;
iovecs_buf[i].len -= @intCast(amt);
}
}

fn prepareCiphertextRecord(
c: *Client,
iovecs: []std.posix.iovec_const,
iovecs: []std.net.iovec_const,
ciphertext_buf: []u8,
bytes: []const u8,
inner_content_type: tls.ContentType,
Expand Down Expand Up @@ -864,8 +864,8 @@ fn prepareCiphertextRecord(

const record = ciphertext_buf[record_start..ciphertext_end];
iovecs[iovec_end] = .{
.iov_base = record.ptr,
.iov_len = record.len,
.ptr = record.ptr,
.len = @intCast(record.len),
};
iovec_end += 1;
}
Expand All @@ -885,7 +885,7 @@ pub fn eof(c: Client) bool {
/// If the number read is less than `len` it means the stream reached the end.
/// Reaching the end of the stream is not an error condition.
pub fn readAtLeast(c: *Client, stream: anytype, buffer: []u8, len: usize) !usize {
var iovecs = [1]std.posix.iovec{.{ .iov_base = buffer.ptr, .iov_len = buffer.len }};
var iovecs = [1]std.net.iovec{.{ .ptr = buffer.ptr, .len = @intCast(buffer.len) }};
return readvAtLeast(c, stream, &iovecs, len);
}

Expand All @@ -908,7 +908,7 @@ pub fn readAll(c: *Client, stream: anytype, buffer: []u8) !usize {
/// stream is not an error condition.
/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
/// order to handle partial reads from the underlying stream layer.
pub fn readv(c: *Client, stream: anytype, iovecs: []std.posix.iovec) !usize {
pub fn readv(c: *Client, stream: anytype, iovecs: []std.net.iovec) !usize {
return readvAtLeast(c, stream, iovecs, 1);
}

Expand All @@ -919,7 +919,7 @@ pub fn readv(c: *Client, stream: anytype, iovecs: []std.posix.iovec) !usize {
/// Reaching the end of the stream is not an error condition.
/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
/// order to handle partial reads from the underlying stream layer.
pub fn readvAtLeast(c: *Client, stream: anytype, iovecs: []std.posix.iovec, len: usize) !usize {
pub fn readvAtLeast(c: *Client, stream: anytype, iovecs: []std.net.iovec, len: usize) !usize {
if (c.eof()) return 0;

var off_i: usize = 0;
Expand All @@ -928,12 +928,12 @@ pub fn readvAtLeast(c: *Client, stream: anytype, iovecs: []std.posix.iovec, len:
var amt = try c.readvAdvanced(stream, iovecs[vec_i..]);
off_i += amt;
if (c.eof() or off_i >= len) return off_i;
while (amt >= iovecs[vec_i].iov_len) {
amt -= iovecs[vec_i].iov_len;
while (amt >= iovecs[vec_i].len) {
amt -= iovecs[vec_i].len;
vec_i += 1;
}
iovecs[vec_i].iov_base += amt;
iovecs[vec_i].iov_len -= amt;
iovecs[vec_i].ptr += amt;
iovecs[vec_i].len -= @intCast(amt);
}
}

Expand All @@ -945,7 +945,7 @@ pub fn readvAtLeast(c: *Client, stream: anytype, iovecs: []std.posix.iovec, len:
/// function asserts that `eof()` is `false`.
/// See `readv` for a higher level function that has the same, familiar API as
/// other read functions, such as `std.fs.File.read`.
pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.posix.iovec) !usize {
pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.net.iovec) !usize {
var vp: VecPut = .{ .iovecs = iovecs };

// Give away the buffered cleartext we have, if any.
Expand Down Expand Up @@ -998,14 +998,14 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.posix.iove
c.partial_cleartext_idx = 0;
const first_iov = c.partially_read_buffer[c.partial_ciphertext_end..];

var ask_iovecs_buf: [2]std.posix.iovec = .{
var ask_iovecs_buf: [2]std.net.iovec = .{
.{
.iov_base = first_iov.ptr,
.iov_len = first_iov.len,
.ptr = first_iov.ptr,
.len = @intCast(first_iov.len),
},
.{
.iov_base = &in_stack_buffer,
.iov_len = in_stack_buffer.len,
.ptr = &in_stack_buffer,
.len = in_stack_buffer.len,
},
};

Expand Down Expand Up @@ -1352,7 +1352,7 @@ fn SchemeEddsa(comptime scheme: tls.SignatureScheme) type {

/// Abstraction for sending multiple byte buffers to a slice of iovecs.
const VecPut = struct {
iovecs: []const std.posix.iovec,
iovecs: []const std.net.iovec,
idx: usize = 0,
off: usize = 0,
total: usize = 0,
Expand All @@ -1364,12 +1364,12 @@ const VecPut = struct {
var bytes_i: usize = 0;
while (true) {
const v = vp.iovecs[vp.idx];
const dest = v.iov_base[vp.off..v.iov_len];
const dest = v.ptr[vp.off..v.len];
const src = bytes[bytes_i..][0..@min(dest.len, bytes.len - bytes_i)];
@memcpy(dest[0..src.len], src);
bytes_i += src.len;
vp.off += src.len;
if (vp.off >= v.iov_len) {
if (vp.off >= v.len) {
vp.off = 0;
vp.idx += 1;
if (vp.idx >= vp.iovecs.len) {
Expand All @@ -1388,15 +1388,15 @@ const VecPut = struct {
fn peek(vp: VecPut) []u8 {
if (vp.idx >= vp.iovecs.len) return &.{};
const v = vp.iovecs[vp.idx];
return v.iov_base[vp.off..v.iov_len];
return v.ptr[vp.off..v.len];
}

// After writing to the result of peek(), one can call next() to
// advance the cursor.
fn next(vp: *VecPut, len: usize) void {
vp.total += len;
vp.off += len;
if (vp.off >= vp.iovecs[vp.idx].iov_len) {
if (vp.off >= vp.iovecs[vp.idx].len) {
vp.off = 0;
vp.idx += 1;
}
Expand All @@ -1405,22 +1405,22 @@ const VecPut = struct {
fn freeSize(vp: VecPut) usize {
if (vp.idx >= vp.iovecs.len) return 0;
var total: usize = 0;
total += vp.iovecs[vp.idx].iov_len - vp.off;
total += vp.iovecs[vp.idx].len - vp.off;
if (vp.idx + 1 >= vp.iovecs.len) return total;
for (vp.iovecs[vp.idx + 1 ..]) |v| total += v.iov_len;
for (vp.iovecs[vp.idx + 1 ..]) |v| total += v.len;
return total;
}
};

/// Limit iovecs to a specific byte size.
fn limitVecs(iovecs: []std.posix.iovec, len: usize) []std.posix.iovec {
var bytes_left: usize = len;
fn limitVecs(iovecs: []std.net.iovec, len: usize) []std.net.iovec {
var bytes_left: u32 = @intCast(len);
for (iovecs, 0..) |*iovec, vec_i| {
if (bytes_left <= iovec.iov_len) {
iovec.iov_len = bytes_left;
if (bytes_left <= iovec.len) {
iovec.len = bytes_left;
return iovecs[0 .. vec_i + 1];
}
bytes_left -= iovec.iov_len;
bytes_left -= @intCast(iovec.len);
}
return iovecs;
}
Expand Down
Loading

0 comments on commit 74a9a6a

Please sign in to comment.